website-to-markdown 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.d.ts CHANGED
@@ -1,3 +1,10 @@
1
1
  #!/usr/bin/env bun
2
- export {};
2
+ /**
3
+ * Fetch a public URL as markdown and print the result. Page metadata goes to
4
+ * stderr; markdown goes to stdout.
5
+ */
6
+ import { type CliIO } from 'hanzio/cli';
7
+ export declare const cli: import("hanzio/cli").Cli;
8
+ /** The bin: 1.0 printed the help and exited 1 when no URL was given, even with `--help`. */
9
+ export declare function main(argv: readonly string[], io?: CliIO): Promise<number>;
3
10
  //# sourceMappingURL=cli.d.ts.map
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;GAGG;AACH,OAAO,EAAE,KAAK,KAAK,EAAsC,MAAM,YAAY,CAAA;AAI3E,eAAO,MAAM,GAAG,0BAiEd,CAAA;AAEF,4FAA4F;AAC5F,wBAAsB,IAAI,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,EAAE,CAAC,EAAE,KAAK,mBAI7D"}
package/dist/cli.js CHANGED
@@ -3,64 +3,67 @@
3
3
  * Fetch a public URL as markdown and print the result. Page metadata goes to
4
4
  * stderr; markdown goes to stdout.
5
5
  */
6
+ import { CliError, defineCli, defineCommand } from 'hanzio/cli';
7
+ import { z } from 'zod';
6
8
  import { createWebsiteToMarkdown } from './fetch-page.js';
7
- const HELP = `
8
- Fetch a public URL as markdown.
9
-
10
- Usage:
11
- website-to-markdown <url> [--no-render]
12
-
13
- Options:
14
- --no-render Skip the Browser Rendering tier (HTML tiers only).
15
-
16
- Environment:
17
- CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID
18
-
19
- Example:
20
- website-to-markdown https://example.com/about
21
- `.trim();
22
- const args = Bun.argv.slice(2);
23
- const url = args.find((arg) => !arg.startsWith('-'));
24
- if (!url || args.includes('-h') || args.includes('--help')) {
25
- console.log(HELP);
26
- process.exit(url ? 0 : 1);
27
- }
28
- const { CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID } = process.env;
29
- if (!CLOUDFLARE_API_TOKEN || !CLOUDFLARE_ACCOUNT_ID) {
30
- console.error('Set CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID.');
31
- process.exit(1);
32
- }
33
- const { fetchPageAsMarkdown } = createWebsiteToMarkdown({
34
- cloudflare: {
35
- apiToken: CLOUDFLARE_API_TOKEN,
36
- accountId: CLOUDFLARE_ACCOUNT_ID
37
- }
9
+ export const cli = defineCli({
10
+ name: 'website-to-markdown',
11
+ commands: defineCommand({
12
+ description: 'Fetch a public URL as markdown. Needs CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID.',
13
+ input: z.object({
14
+ url: z
15
+ .string()
16
+ .describe('e.g. https://example.com/about')
17
+ .meta({ positional: true }),
18
+ render: z
19
+ .boolean()
20
+ .default(true)
21
+ .describe('Use the Browser Rendering tier; --no-render keeps to HTML tiers.')
22
+ }),
23
+ run: async ({ url, render }, ctx) => {
24
+ const { CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID } = ctx.env;
25
+ if (!CLOUDFLARE_API_TOKEN || !CLOUDFLARE_ACCOUNT_ID)
26
+ throw new CliError('Set CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID.');
27
+ const { fetchPageAsMarkdown } = createWebsiteToMarkdown({
28
+ cloudflare: {
29
+ apiToken: CLOUDFLARE_API_TOKEN,
30
+ accountId: CLOUDFLARE_ACCOUNT_ID
31
+ }
32
+ });
33
+ ctx.stderr.write(`Fetching ${url}…\n`);
34
+ const page = await fetchPageAsMarkdown({ url, render });
35
+ ctx.stderr.write(`${JSON.stringify({
36
+ outcome: page.outcome,
37
+ status: page.status,
38
+ source: page.source,
39
+ url: page.url,
40
+ finalUrl: page.finalUrl,
41
+ contentType: page.contentType,
42
+ etag: page.etag,
43
+ lastModified: page.lastModified,
44
+ title: page.title,
45
+ lang: page.lang,
46
+ canonical: page.canonical,
47
+ metaDescription: page.metaDescription,
48
+ jsonLdBlocks: page.jsonLd.length,
49
+ links: page.links.length,
50
+ images: page.images.length,
51
+ markdownChars: page.markdown.length,
52
+ durationMs: page.durationMs
53
+ }, null, 2)}\n`);
54
+ if (page.outcome !== 'ok')
55
+ ctx.exitCode = 1;
56
+ return page.markdown;
57
+ }
58
+ })
38
59
  });
39
- console.error(`Fetching ${url}…`);
40
- const page = await fetchPageAsMarkdown({
41
- url,
42
- render: !args.includes('--no-render')
43
- });
44
- console.error(JSON.stringify({
45
- outcome: page.outcome,
46
- status: page.status,
47
- source: page.source,
48
- url: page.url,
49
- finalUrl: page.finalUrl,
50
- contentType: page.contentType,
51
- etag: page.etag,
52
- lastModified: page.lastModified,
53
- title: page.title,
54
- lang: page.lang,
55
- canonical: page.canonical,
56
- metaDescription: page.metaDescription,
57
- jsonLdBlocks: page.jsonLd.length,
58
- links: page.links.length,
59
- images: page.images.length,
60
- markdownChars: page.markdown.length,
61
- durationMs: page.durationMs
62
- }, null, 2));
63
- console.log(page.markdown);
64
- if (page.outcome !== 'ok')
65
- process.exit(1);
60
+ /** The bin: 1.0 printed the help and exited 1 when no URL was given, even with `--help`. */
61
+ export async function main(argv, io) {
62
+ if (argv.some((arg) => !arg.startsWith('-')))
63
+ return cli.run(argv, io);
64
+ await cli.run(['--help'], io);
65
+ return 1;
66
+ }
67
+ if (import.meta.main)
68
+ process.exitCode = await main(process.argv.slice(2));
66
69
  //# sourceMappingURL=cli.js.map
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;GAGG;AACH,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAA;AAEzD,MAAM,IAAI,GAAG;;;;;;;;;;;;;;CAcZ,CAAC,IAAI,EAAE,CAAA;AAER,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;AAC9B,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAA;AACpD,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;IAC5D,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;IACjB,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;AAC1B,CAAC;AAED,MAAM,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,GAAG,OAAO,CAAC,GAAG,CAAA;AACnE,IAAI,CAAC,oBAAoB,IAAI,CAAC,qBAAqB,EAAE,CAAC;IACrD,OAAO,CAAC,KAAK,CAAC,qDAAqD,CAAC,CAAA;IACpE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AAChB,CAAC;AAED,MAAM,EAAE,mBAAmB,EAAE,GAAG,uBAAuB,CAAC;IACvD,UAAU,EAAE;QACX,QAAQ,EAAE,oBAAoB;QAC9B,SAAS,EAAE,qBAAqB;KAChC;CACD,CAAC,CAAA;AAEF,OAAO,CAAC,KAAK,CAAC,YAAY,GAAG,GAAG,CAAC,CAAA;AAEjC,MAAM,IAAI,GAAG,MAAM,mBAAmB,CAAC;IACtC,GAAG;IACH,MAAM,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC;CACrC,CAAC,CAAA;AAEF,OAAO,CAAC,KAAK,CACZ,IAAI,CAAC,SAAS,CACb;IACC,OAAO,EAAE,IAAI,CAAC,OAAO;IACrB,MAAM,EAAE,IAAI,CAAC,MAAM;IACnB,MAAM,EAAE,IAAI,CAAC,MAAM;IACnB,GAAG,EAAE,IAAI,CAAC,GAAG;IACb,QAAQ,EAAE,IAAI,CAAC,QAAQ;IACvB,WAAW,EAAE,IAAI,CAAC,WAAW;IAC7B,IAAI,EAAE,IAAI,CAAC,IAAI;IACf,YAAY,EAAE,IAAI,CAAC,YAAY;IAC/B,KAAK,EAAE,IAAI,CAAC,KAAK;IACjB,IAAI,EAAE,IAAI,CAAC,IAAI;IACf,SAAS,EAAE,IAAI,CAAC,SAAS;IACzB,eAAe,EAAE,IAAI,CAAC,eAAe;IACrC,YAAY,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM;IAChC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM;IACxB,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM;IAC1B,aAAa,EAAE,IAAI,CAAC,QAAQ,CAAC,MAAM;IACnC,UAAU,EAAE,IAAI,CAAC,UAAU;CAC3B,EACD,IAAI,EACJ,CAAC,CACD,CACD,CAAA;AAED,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;AAC1B,IAAI,IAAI,CAAC,OAAO,KAAK,IAAI;IAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA","sourcesContent":["#!/usr/bin/env bun\n/**\n * Fetch a public URL as markdown and print the result. Page metadata goes to\n * stderr; markdown goes to stdout.\n */\nimport { createWebsiteToMarkdown } from './fetch-page.js'\n\nconst HELP = `\nFetch a public URL as markdown.\n\nUsage:\n website-to-markdown <url> [--no-render]\n\nOptions:\n --no-render Skip the Browser Rendering tier (HTML tiers only).\n\nEnvironment:\n CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID\n\nExample:\n website-to-markdown https://example.com/about\n`.trim()\n\nconst args = Bun.argv.slice(2)\nconst url = args.find((arg) => !arg.startsWith('-'))\nif (!url || args.includes('-h') || args.includes('--help')) {\n\tconsole.log(HELP)\n\tprocess.exit(url ? 0 : 1)\n}\n\nconst { CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID } = process.env\nif (!CLOUDFLARE_API_TOKEN || !CLOUDFLARE_ACCOUNT_ID) {\n\tconsole.error('Set CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID.')\n\tprocess.exit(1)\n}\n\nconst { fetchPageAsMarkdown } = createWebsiteToMarkdown({\n\tcloudflare: {\n\t\tapiToken: CLOUDFLARE_API_TOKEN,\n\t\taccountId: CLOUDFLARE_ACCOUNT_ID\n\t}\n})\n\nconsole.error(`Fetching ${url}…`)\n\nconst page = await fetchPageAsMarkdown({\n\turl,\n\trender: !args.includes('--no-render')\n})\n\nconsole.error(\n\tJSON.stringify(\n\t\t{\n\t\t\toutcome: page.outcome,\n\t\t\tstatus: page.status,\n\t\t\tsource: page.source,\n\t\t\turl: page.url,\n\t\t\tfinalUrl: page.finalUrl,\n\t\t\tcontentType: page.contentType,\n\t\t\tetag: page.etag,\n\t\t\tlastModified: page.lastModified,\n\t\t\ttitle: page.title,\n\t\t\tlang: page.lang,\n\t\t\tcanonical: page.canonical,\n\t\t\tmetaDescription: page.metaDescription,\n\t\t\tjsonLdBlocks: page.jsonLd.length,\n\t\t\tlinks: page.links.length,\n\t\t\timages: page.images.length,\n\t\t\tmarkdownChars: page.markdown.length,\n\t\t\tdurationMs: page.durationMs\n\t\t},\n\t\tnull,\n\t\t2\n\t)\n)\n\nconsole.log(page.markdown)\nif (page.outcome !== 'ok') process.exit(1)\n"]}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env bun\n/**\n * Fetch a public URL as markdown and print the result. Page metadata goes to\n * stderr; markdown goes to stdout.\n */\nimport { type CliIO, CliError, defineCli, defineCommand } from 'hanzio/cli'\nimport { z } from 'zod'\nimport { createWebsiteToMarkdown } from './fetch-page.js'\n\nexport const cli = defineCli({\n\tname: 'website-to-markdown',\n\tcommands: defineCommand({\n\t\tdescription:\n\t\t\t'Fetch a public URL as markdown. Needs CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID.',\n\t\tinput: z.object({\n\t\t\turl: z\n\t\t\t\t.string()\n\t\t\t\t.describe('e.g. https://example.com/about')\n\t\t\t\t.meta({ positional: true }),\n\t\t\trender: z\n\t\t\t\t.boolean()\n\t\t\t\t.default(true)\n\t\t\t\t.describe(\n\t\t\t\t\t'Use the Browser Rendering tier; --no-render keeps to HTML tiers.'\n\t\t\t\t)\n\t\t}),\n\t\trun: async ({ url, render }, ctx) => {\n\t\t\tconst { CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID } = ctx.env\n\t\t\tif (!CLOUDFLARE_API_TOKEN || !CLOUDFLARE_ACCOUNT_ID)\n\t\t\t\tthrow new CliError(\n\t\t\t\t\t'Set CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID.'\n\t\t\t\t)\n\n\t\t\tconst { fetchPageAsMarkdown } = createWebsiteToMarkdown({\n\t\t\t\tcloudflare: {\n\t\t\t\t\tapiToken: CLOUDFLARE_API_TOKEN,\n\t\t\t\t\taccountId: CLOUDFLARE_ACCOUNT_ID\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tctx.stderr.write(`Fetching ${url}…\\n`)\n\n\t\t\tconst page = await fetchPageAsMarkdown({ url, render })\n\n\t\t\tctx.stderr.write(\n\t\t\t\t`${JSON.stringify(\n\t\t\t\t\t{\n\t\t\t\t\t\toutcome: page.outcome,\n\t\t\t\t\t\tstatus: page.status,\n\t\t\t\t\t\tsource: page.source,\n\t\t\t\t\t\turl: page.url,\n\t\t\t\t\t\tfinalUrl: page.finalUrl,\n\t\t\t\t\t\tcontentType: page.contentType,\n\t\t\t\t\t\tetag: page.etag,\n\t\t\t\t\t\tlastModified: page.lastModified,\n\t\t\t\t\t\ttitle: page.title,\n\t\t\t\t\t\tlang: page.lang,\n\t\t\t\t\t\tcanonical: page.canonical,\n\t\t\t\t\t\tmetaDescription: page.metaDescription,\n\t\t\t\t\t\tjsonLdBlocks: page.jsonLd.length,\n\t\t\t\t\t\tlinks: page.links.length,\n\t\t\t\t\t\timages: page.images.length,\n\t\t\t\t\t\tmarkdownChars: page.markdown.length,\n\t\t\t\t\t\tdurationMs: page.durationMs\n\t\t\t\t\t},\n\t\t\t\t\tnull,\n\t\t\t\t\t2\n\t\t\t\t)}\\n`\n\t\t\t)\n\n\t\t\tif (page.outcome !== 'ok') ctx.exitCode = 1\n\t\t\treturn page.markdown\n\t\t}\n\t})\n})\n\n/** The bin: 1.0 printed the help and exited 1 when no URL was given, even with `--help`. */\nexport async function main(argv: readonly string[], io?: CliIO) {\n\tif (argv.some((arg) => !arg.startsWith('-'))) return cli.run(argv, io)\n\tawait cli.run(['--help'], io)\n\treturn 1\n}\n\nif (import.meta.main) process.exitCode = await main(process.argv.slice(2))\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "website-to-markdown",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Turn URLs, HTML and files into markdown, with sitemap discovery and per-host limits.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -62,16 +62,16 @@
62
62
  "fflate": "^0.8.2"
63
63
  },
64
64
  "peerDependencies": {
65
- "hanzio": "^3.0.0",
65
+ "hanzio": "^3.1.0",
66
66
  "zod": "^4.0.0"
67
67
  },
68
68
  "devDependencies": {
69
+ "@arethetypeswrong/cli": "^0.18.2",
69
70
  "@biomejs/biome": "^2.2.4",
70
71
  "@types/bun": "^1.2.0",
71
- "typescript": "^5.9.2",
72
- "@arethetypeswrong/cli": "^0.18.2",
73
- "hanzio": "^3.0.0",
72
+ "hanzio": "^3.1.0",
74
73
  "publint": "^0.3.12",
74
+ "typescript": "^5.9.2",
75
75
  "zod": "^4.4.3"
76
76
  },
77
77
  "bugs": {
package/src/cli.ts CHANGED
@@ -3,76 +3,82 @@
3
3
  * Fetch a public URL as markdown and print the result. Page metadata goes to
4
4
  * stderr; markdown goes to stdout.
5
5
  */
6
+ import { type CliIO, CliError, defineCli, defineCommand } from 'hanzio/cli'
7
+ import { z } from 'zod'
6
8
  import { createWebsiteToMarkdown } from './fetch-page.js'
7
9
 
8
- const HELP = `
9
- Fetch a public URL as markdown.
10
+ export const cli = defineCli({
11
+ name: 'website-to-markdown',
12
+ commands: defineCommand({
13
+ description:
14
+ 'Fetch a public URL as markdown. Needs CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID.',
15
+ input: z.object({
16
+ url: z
17
+ .string()
18
+ .describe('e.g. https://example.com/about')
19
+ .meta({ positional: true }),
20
+ render: z
21
+ .boolean()
22
+ .default(true)
23
+ .describe(
24
+ 'Use the Browser Rendering tier; --no-render keeps to HTML tiers.'
25
+ )
26
+ }),
27
+ run: async ({ url, render }, ctx) => {
28
+ const { CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID } = ctx.env
29
+ if (!CLOUDFLARE_API_TOKEN || !CLOUDFLARE_ACCOUNT_ID)
30
+ throw new CliError(
31
+ 'Set CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID.'
32
+ )
10
33
 
11
- Usage:
12
- website-to-markdown <url> [--no-render]
34
+ const { fetchPageAsMarkdown } = createWebsiteToMarkdown({
35
+ cloudflare: {
36
+ apiToken: CLOUDFLARE_API_TOKEN,
37
+ accountId: CLOUDFLARE_ACCOUNT_ID
38
+ }
39
+ })
13
40
 
14
- Options:
15
- --no-render Skip the Browser Rendering tier (HTML tiers only).
41
+ ctx.stderr.write(`Fetching ${url}…\n`)
16
42
 
17
- Environment:
18
- CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID
43
+ const page = await fetchPageAsMarkdown({ url, render })
19
44
 
20
- Example:
21
- website-to-markdown https://example.com/about
22
- `.trim()
45
+ ctx.stderr.write(
46
+ `${JSON.stringify(
47
+ {
48
+ outcome: page.outcome,
49
+ status: page.status,
50
+ source: page.source,
51
+ url: page.url,
52
+ finalUrl: page.finalUrl,
53
+ contentType: page.contentType,
54
+ etag: page.etag,
55
+ lastModified: page.lastModified,
56
+ title: page.title,
57
+ lang: page.lang,
58
+ canonical: page.canonical,
59
+ metaDescription: page.metaDescription,
60
+ jsonLdBlocks: page.jsonLd.length,
61
+ links: page.links.length,
62
+ images: page.images.length,
63
+ markdownChars: page.markdown.length,
64
+ durationMs: page.durationMs
65
+ },
66
+ null,
67
+ 2
68
+ )}\n`
69
+ )
23
70
 
24
- const args = Bun.argv.slice(2)
25
- const url = args.find((arg) => !arg.startsWith('-'))
26
- if (!url || args.includes('-h') || args.includes('--help')) {
27
- console.log(HELP)
28
- process.exit(url ? 0 : 1)
29
- }
30
-
31
- const { CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID } = process.env
32
- if (!CLOUDFLARE_API_TOKEN || !CLOUDFLARE_ACCOUNT_ID) {
33
- console.error('Set CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID.')
34
- process.exit(1)
35
- }
36
-
37
- const { fetchPageAsMarkdown } = createWebsiteToMarkdown({
38
- cloudflare: {
39
- apiToken: CLOUDFLARE_API_TOKEN,
40
- accountId: CLOUDFLARE_ACCOUNT_ID
41
- }
71
+ if (page.outcome !== 'ok') ctx.exitCode = 1
72
+ return page.markdown
73
+ }
74
+ })
42
75
  })
43
76
 
44
- console.error(`Fetching ${url}…`)
45
-
46
- const page = await fetchPageAsMarkdown({
47
- url,
48
- render: !args.includes('--no-render')
49
- })
50
-
51
- console.error(
52
- JSON.stringify(
53
- {
54
- outcome: page.outcome,
55
- status: page.status,
56
- source: page.source,
57
- url: page.url,
58
- finalUrl: page.finalUrl,
59
- contentType: page.contentType,
60
- etag: page.etag,
61
- lastModified: page.lastModified,
62
- title: page.title,
63
- lang: page.lang,
64
- canonical: page.canonical,
65
- metaDescription: page.metaDescription,
66
- jsonLdBlocks: page.jsonLd.length,
67
- links: page.links.length,
68
- images: page.images.length,
69
- markdownChars: page.markdown.length,
70
- durationMs: page.durationMs
71
- },
72
- null,
73
- 2
74
- )
75
- )
77
+ /** The bin: 1.0 printed the help and exited 1 when no URL was given, even with `--help`. */
78
+ export async function main(argv: readonly string[], io?: CliIO) {
79
+ if (argv.some((arg) => !arg.startsWith('-'))) return cli.run(argv, io)
80
+ await cli.run(['--help'], io)
81
+ return 1
82
+ }
76
83
 
77
- console.log(page.markdown)
78
- if (page.outcome !== 'ok') process.exit(1)
84
+ if (import.meta.main) process.exitCode = await main(process.argv.slice(2))