Skip to content

Commit 2f0fffb

Browse files
authored
Feat/generate document (#69)
* feat: add generate document command to fetch content from remote url * feat: adding a login command to save browser session * chore: add changeset * chore: update readme
1 parent 3064f5d commit 2f0fffb

File tree

8 files changed

+150
-7
lines changed

8 files changed

+150
-7
lines changed

.changeset/blue-bobcats-push.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
"@heymp/scratchpad": minor
3+
---
4+
5+
Add new generate commands
6+
7+
**Generate document**
8+
9+
Fetch HTML source of url and save it to a local file. This is helpful when using the `rerouteDocument` command.
10+
11+
```bash
12+
npx @heymp/scratchpad@next generate document https://www.example.com pages
13+
```
14+
15+
**Generate login**
16+
17+
Launch a new browser that saves your session so it can be reused.
18+
19+
```bash
20+
npx @heymp/scratchpad@next generate login
21+
```
22+
23+
You can then reuse the session by using the `login` option when using the `run` command.
24+
25+
```bash
26+
npx @heymp/scratchpad@next run --login
27+
```

README.md

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Options:
2626

2727
Commands:
2828
run [options] <file> Execute a file in a browser.
29-
generate Generate files from templates.
29+
generate Generate files from templates or remote data.
3030
help [command] display help for command
3131
```
3232

@@ -75,6 +75,20 @@ export default defineConfig({
7575
});
7676
```
7777

78+
#### Save browser session
79+
80+
Using Playwright, you can launch a new session with the `generate login` command. This will launch a new browser session where you can authenticate to a website. After you have authenticated you can close the browser session. You session will be saved to a local file `.scratchpad/login.json`.
81+
82+
```bash
83+
npx @heymp/scratchpad@next login
84+
```
85+
86+
You can then reuse the session by using the `login` option when using the `run` command.
87+
88+
```bash
89+
npx @heymp/scratchpad@next run --login
90+
```
91+
7892
#### Reroute Documents
7993

8094
The `rerouteDocument` function allows you to replace the HTML content of any webpage with a local HTML file from your system. This is incredibly useful for testing changes or developing components in the context of a live site without deploying your code.
@@ -163,9 +177,16 @@ export default Scratchpad.defineConfig({
163177
});
164178
```
165179

166-
## Logging
180+
## Generate files
181+
182+
| Method | Description | Example |
183+
|--------|--------------------------------------------|-----------|
184+
| document | Fetch HTML source of url and save it to a local file. This is helpful when using the `rerouteDocument` command. | `npx @heymp/scratchpad@next generate document https://www.example.com pages ` |
185+
| login | Launch a new browser that saves your session so it can be reused. | `npx @heymp/scratchpad@next generate login` |
186+
187+
## Default exposed functions
167188

168-
To send log information from the Chromium browser to node.js we expose the following functions.
189+
The following functions are exposed by default that can be used in the `run` commands target file.
169190

170191
| Method | Description | Example |
171192
|--------|--------------------------------------------|-----------|
@@ -176,7 +197,7 @@ To send log information from the Chromium browser to node.js we expose the follo
176197
| readFile | read data from a local file. | `readFile('./log.txt', 'utf8')` |
177198

178199

179-
Example
200+
**Log example**
180201

181202
```js
182203
clear();

src/Processor.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export type ProcessorOpts = {
1212
headless?: boolean;
1313
devtools?: boolean;
1414
tsWrite?: boolean;
15+
login?: boolean;
1516
url?: string;
1617
playwright?: any;
1718
file: string;
@@ -41,6 +42,13 @@ export class Processor extends EventTarget {
4142

4243
watcher() {
4344
const file = this.opts.file;
45+
46+
if (this.opts.login) {
47+
if (!fs.existsSync(join(process.cwd(), '.scratchpad', 'login.json'))) {
48+
throw new Error(`Session file not found.`);
49+
}
50+
}
51+
4452
if (!fs.existsSync(file)) {
4553
throw new Error(`${file} file not found.`);
4654
}

src/browser.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import util from 'node:util';
33
import { join } from 'node:path'
44
import fs from 'node:fs/promises';
55
import type { Processor } from './Processor.js';
6+
import { getSession } from './login.js';
67
util.inspect.defaultOptions.maxArrayLength = null;
78
util.inspect.defaultOptions.depth = null;
89

@@ -23,12 +24,15 @@ function readFile(...args: Parameters<typeof fs.readFile>) {
2324
}
2425

2526
export async function browser(processor: Processor) {
27+
// Get session login session
2628
// Launch the browser
2729
const browser = await playwright['chromium'].launch({
2830
headless: !!processor.opts.headless,
2931
devtools: !!processor.opts.devtools
3032
});
31-
const context = await browser.newContext();
33+
const context = await browser.newContext({
34+
storageState: processor.opts.login ? await getSession('.scratchpad/login.json') : undefined,
35+
});
3236
const page = await context.newPage();
3337
const playwrightConfig = processor.opts.playwright;
3438

src/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export type Config = {
1414
devtools?: boolean;
1515
tsWrite?: boolean;
1616
url?: string;
17+
login?: boolean;
1718
playwright?: (page: PlaywrightConfig) => Promise<void>
1819
}
1920

src/generateCommand.ts

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
import { Command } from '@commander-js/extra-typings';
22
import * as readline from 'node:readline/promises';
33
import { stdin, stdout } from 'node:process';
4-
import { writeFile, readFile, stat } from 'node:fs/promises';
4+
import { writeFile, readFile, mkdir } from 'node:fs/promises';
55
import { dirname, join } from 'node:path';
6+
import { URL } from 'node:url';
67
import { fileURLToPath } from 'node:url';
8+
import assert from 'node:assert';
9+
import { login } from './login.js';
10+
import { getConfig } from './config.js';
11+
import { Processor } from './Processor.js';
712
const __filename = fileURLToPath(import.meta.url);
813
const __dirname = dirname(__filename);
914

@@ -27,6 +32,42 @@ ${outpath} [Y/n]: `);
2732
rl.close();
2833
});
2934

35+
const documentCommand = new Command('document')
36+
.description('Generates a local copy of a document from a url.')
37+
.argument('<url>', 'url to copy.')
38+
.argument('<dir>', 'source directory where you want to save the document.')
39+
.action(async (_url, dir) => {
40+
const outputDir = join(process.cwd(), dir);
41+
const url = new URL(_url);
42+
43+
// get html
44+
const res = await fetch(url);
45+
if (!res.ok) {
46+
assert.fail(`HTTP error! Status: ${res.status} - ${res.statusText} for URL: ${url}`)
47+
}
48+
const html = await res.text();
49+
50+
// save file
51+
const fileDir = join(outputDir, url.pathname.replace(/^\//, ''));
52+
const filePath = join(fileDir, 'index.html');
53+
await mkdir(fileDir, { recursive: true });
54+
await writeFile(filePath, html, 'utf8');
55+
});
56+
57+
const loginCommand = new Command('login')
58+
.description('Launches a session and saves the browser session in a local file on termination.')
59+
.action(async (...options) => {
60+
const config = await getConfig();
61+
const opts = { ...config, options };
62+
login({
63+
// type narrow the options
64+
devtools: !!opts['devtools'],
65+
url: typeof opts['url'] === 'string' ? opts['url'] : undefined,
66+
});
67+
});
68+
3069
export const generateCommand = new Command('generate')
3170
.description('Generate files from templates.')
32-
.addCommand(configCommand);
71+
.addCommand(configCommand)
72+
.addCommand(documentCommand)
73+
.addCommand(loginCommand);

src/login.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import playwright from 'playwright';
2+
import util from 'node:util';
3+
import { readFile } from 'node:fs/promises';
4+
import { join } from 'node:path';
5+
import { exists } from './utils.js';
6+
import type { Config } from './config.js';
7+
util.inspect.defaultOptions.maxArrayLength = null;
8+
util.inspect.defaultOptions.depth = null;
9+
10+
export async function login(config: Config) {
11+
const browser = await playwright['chromium'].launch({
12+
headless: false,
13+
devtools: !!config.devtools
14+
});
15+
const context = await browser.newContext();
16+
const page = await context.newPage();
17+
18+
// Create a page
19+
if (config.url) {
20+
await page.goto(config.url);
21+
}
22+
23+
page.on('close', async () => {
24+
await page.context().storageState({ path: '.scratchpad/login.json' });
25+
await browser.close();
26+
console.log(`\x1b[33m 👻 Session saved\x1b[0m`);
27+
});
28+
}
29+
30+
export async function getSession(path: string): Promise<string | undefined> {
31+
const filePath = join(process.cwd(), path);
32+
const fileExists = await exists(filePath);
33+
if (!fileExists) {
34+
return undefined;
35+
}
36+
const sessionFile = await readFile(filePath, 'utf8');
37+
const session = sessionFile ? JSON.parse(sessionFile) : undefined;
38+
return session;
39+
}

src/runCommand.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export const runCommand = new Command('run')
1010
.option('--devtools [boolean]', 'open browser devtools automatically.')
1111
.option('--ts-write [boolean]', 'write the js output of the target ts file.')
1212
.option('--url [string]', 'specify a specific url to execute the code in.')
13+
.option('--login [boolean]', `use previously saved session from 'generate login' command`)
1314
.action(async (file, options, command) => {
1415
const config = await getConfig();
1516
const opts = { ...config, ...options};
@@ -20,6 +21,7 @@ export const runCommand = new Command('run')
2021
tsWrite: !!opts['tsWrite'],
2122
url: typeof opts['url'] === 'string' ? opts['url'] : undefined,
2223
playwright: opts['playwright'],
24+
login: !!opts['login'],
2325
file,
2426
});
2527
browser(processor);

0 commit comments

Comments
 (0)