@mintlify/cli 4.0.953 → 4.0.955

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mintlify/cli",
3
- "version": "4.0.953",
3
+ "version": "4.0.955",
4
4
  "description": "The Mintlify CLI",
5
5
  "engines": {
6
6
  "node": ">=18.0.0"
@@ -45,12 +45,13 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "@inquirer/prompts": "7.9.0",
48
- "@mintlify/common": "1.0.728",
49
- "@mintlify/link-rot": "3.0.888",
48
+ "@mintlify/common": "1.0.729",
49
+ "@mintlify/link-rot": "3.0.890",
50
50
  "@mintlify/models": "0.0.272",
51
- "@mintlify/prebuild": "1.0.864",
52
- "@mintlify/previewing": "4.0.921",
53
- "@mintlify/validation": "0.1.594",
51
+ "@mintlify/prebuild": "1.0.866",
52
+ "@mintlify/previewing": "4.0.923",
53
+ "@mintlify/scraping": "4.0.591",
54
+ "@mintlify/validation": "0.1.595",
54
55
  "adm-zip": "0.5.16",
55
56
  "chalk": "5.2.0",
56
57
  "color": "4.2.3",
@@ -86,5 +87,5 @@
86
87
  "vitest": "2.0.4",
87
88
  "vitest-mock-process": "1.0.4"
88
89
  },
89
- "gitHead": "fdbcfc062b0d175a55530b0021aca2acaa98906b"
90
+ "gitHead": "c40f23d742edb39a24f4259970f9cd5f0a506f3c"
90
91
  }
package/src/cli.tsx CHANGED
@@ -12,6 +12,7 @@ import {
12
12
  BrokenLinksLog,
13
13
  WarningLog,
14
14
  } from '@mintlify/previewing';
15
+ import { checkUrl } from '@mintlify/scraping';
15
16
  import { render, Text } from 'ink';
16
17
  import path from 'path';
17
18
  import yargs from 'yargs';
@@ -32,6 +33,7 @@ import {
32
33
  import { init } from './init.js';
33
34
  import { mdxLinter } from './mdxLinter.js';
34
35
  import { migrateMdx } from './migrateMdx.js';
36
+ import { scrapeSite, scrapePage, scrapeOpenApi } from './scrape.js';
35
37
  import { update } from './update.js';
36
38
 
37
39
  export const cli = ({ packageName = 'mint' }: { packageName?: string }) => {
@@ -408,6 +410,80 @@ export const cli = ({ packageName = 'mint' }: { packageName?: string }) => {
408
410
  }
409
411
  }
410
412
  )
413
+ .command('scrape', 'Scrape documentation from external sites', (yargs) =>
414
+ yargs
415
+ .command(
416
+ ['$0 <url>', 'site <url>'],
417
+ 'Scrape an entire documentation site',
418
+ (yargs) =>
419
+ yargs
420
+ .positional('url', {
421
+ describe: 'The URL of the documentation site to scrape',
422
+ type: 'string',
423
+ demandOption: true,
424
+ })
425
+ .option('filter', {
426
+ describe:
427
+ 'Only scrape URLs matching this path filter (e.g. /docs will match /docs and /docs/*)',
428
+ type: 'string',
429
+ alias: 'f',
430
+ })
431
+ .check(checkUrl),
432
+ async ({ url, filter }) => {
433
+ await scrapeSite(url, filter);
434
+ }
435
+ )
436
+ .command(
437
+ 'page <url>',
438
+ 'Scrape a single documentation page',
439
+ (yargs) =>
440
+ yargs
441
+ .positional('url', {
442
+ describe: 'The URL of the documentation page to scrape',
443
+ type: 'string',
444
+ demandOption: true,
445
+ })
446
+ .check(checkUrl),
447
+ async ({ url }) => {
448
+ await scrapePage(url);
449
+ }
450
+ )
451
+ .command(
452
+ 'openapi <openapiLocation>',
453
+ 'Generate MDX files from an OpenAPI spec',
454
+ (yargs) =>
455
+ yargs
456
+ .positional('openapiLocation', {
457
+ describe: 'The filename or URL location of the OpenAPI spec',
458
+ type: 'string',
459
+ demandOption: true,
460
+ })
461
+ .option('writeFiles', {
462
+ describe: 'Whether or not to write the frontmatter files',
463
+ default: true,
464
+ type: 'boolean',
465
+ alias: 'w',
466
+ })
467
+ .option('outDir', {
468
+ describe: 'The folder in which to write any created frontmatter files',
469
+ type: 'string',
470
+ alias: 'o',
471
+ })
472
+ .option('overwrite', {
473
+ describe: 'Whether or not to overwrite existing files',
474
+ default: false,
475
+ type: 'boolean',
476
+ }),
477
+ async (argv) => {
478
+ await scrapeOpenApi({
479
+ openapiLocation: argv.openapiLocation,
480
+ writeFiles: argv.writeFiles,
481
+ outDir: argv.outDir,
482
+ overwrite: argv.overwrite,
483
+ });
484
+ }
485
+ )
486
+ )
411
487
  // Print the help menu when the user enters an invalid command.
412
488
  .strictCommands()
413
489
  .demandCommand(1, 'unknown command. see above for the list of supported commands.')
package/src/scrape.tsx ADDED
@@ -0,0 +1,122 @@
1
+ import { MintConfigType } from '@mintlify/models';
2
+ import { addLog, ErrorLog, SuccessLog, SpinnerLog, InfoLog } from '@mintlify/previewing';
3
+ import {
4
+ scrapePageGroup,
5
+ scrapeAllSiteTabs,
6
+ htmlToHast,
7
+ detectFramework,
8
+ framework,
9
+ fetchPageHtml,
10
+ write,
11
+ getErrorMessage,
12
+ generateOpenApiPages,
13
+ FINAL_SUCCESS_MESSAGE,
14
+ } from '@mintlify/scraping';
15
+ import { upgradeToDocsConfig } from '@mintlify/validation';
16
+
17
+ import { terminate } from './helpers.js';
18
+
19
+ export async function scrapeSite(url: string, filter?: string) {
20
+ try {
21
+ const urlObj = new URL(url);
22
+ addLog(<SpinnerLog message={`Fetching ${urlObj.toString()}...`} />);
23
+ const html = await fetchPageHtml(urlObj);
24
+ addLog(<SuccessLog message={`Successfully retrieved HTML from ${urlObj.toString()}`} />);
25
+
26
+ addLog(<SpinnerLog message="Scraping site..." />);
27
+ const result = await scrapeAllSiteTabs(html, urlObj, { filter });
28
+ if (result.success) {
29
+ const mintConfig = result.data as MintConfigType;
30
+ const docsConfig = upgradeToDocsConfig(mintConfig, {
31
+ shouldUpgradeTheme: true,
32
+ });
33
+ docsConfig.theme = 'aspen';
34
+ write('docs.json', JSON.stringify(docsConfig, undefined, 2));
35
+ addLog(<SuccessLog message={FINAL_SUCCESS_MESSAGE} />);
36
+ } else {
37
+ addLog(<ErrorLog message={result.message} />);
38
+ await terminate(1);
39
+ }
40
+ await terminate(0);
41
+ } catch (error) {
42
+ const errorMessage = getErrorMessage(error);
43
+ addLog(<ErrorLog message={errorMessage} />);
44
+ await terminate(1);
45
+ }
46
+ }
47
+
48
+ export async function scrapePage(url: string) {
49
+ try {
50
+ const urlObj = new URL(url);
51
+ addLog(<SpinnerLog message={`Fetching ${urlObj.toString()}...`} />);
52
+ const html = await fetchPageHtml(urlObj);
53
+ addLog(<SuccessLog message={`Successfully retrieved HTML from ${urlObj.toString()}`} />);
54
+
55
+ const hast = htmlToHast(html);
56
+ detectFramework(hast);
57
+
58
+ const needsBrowser = framework.vendor === 'gitbook';
59
+ addLog(<SpinnerLog message="Scraping page..." />);
60
+ const results = await scrapePageGroup([urlObj], needsBrowser);
61
+ const result = results[0] || {
62
+ success: false,
63
+ message: `An unknown error occurred when scraping ${url}`,
64
+ };
65
+
66
+ if (result.success) {
67
+ addLog(
68
+ <SuccessLog
69
+ message={`Successfully scraped ${url} ${result.data ? `into ${result.data[1]}` : ''}`}
70
+ />
71
+ );
72
+ } else {
73
+ addLog(<ErrorLog message={result.message} />);
74
+ await terminate(1);
75
+ }
76
+ await terminate(0);
77
+ } catch (error) {
78
+ const errorMessage = getErrorMessage(error);
79
+ addLog(<ErrorLog message={errorMessage} />);
80
+ await terminate(1);
81
+ }
82
+ }
83
+
84
+ interface ScrapeOpenApiOptions {
85
+ openapiLocation: string;
86
+ writeFiles: boolean;
87
+ outDir?: string;
88
+ overwrite: boolean;
89
+ }
90
+
91
+ export async function scrapeOpenApi({
92
+ openapiLocation,
93
+ writeFiles,
94
+ outDir,
95
+ overwrite,
96
+ }: ScrapeOpenApiOptions) {
97
+ try {
98
+ addLog(<SpinnerLog message={`Processing OpenAPI spec from ${openapiLocation}...`} />);
99
+ const { nav, isUrl } = await generateOpenApiPages(openapiLocation, {
100
+ openApiFilePath: undefined,
101
+ version: undefined,
102
+ writeFiles,
103
+ outDir,
104
+ overwrite,
105
+ });
106
+ addLog(<SuccessLog message="Successfully generated OpenAPI pages" />);
107
+ addLog(<InfoLog message="Navigation object suggestion:" />);
108
+ addLog(<InfoLog message={JSON.stringify(nav, undefined, 2)} />);
109
+ if (isUrl) {
110
+ addLog(<InfoLog message="OpenAPI location suggestion:" />);
111
+ addLog(<InfoLog message={`openapi: ${openapiLocation}`} />);
112
+ }
113
+ await terminate(0);
114
+ } catch (error) {
115
+ if (error instanceof Error) {
116
+ addLog(<ErrorLog message={error.message} />);
117
+ } else {
118
+ addLog(<ErrorLog message={String(error)} />);
119
+ }
120
+ await terminate(1);
121
+ }
122
+ }