UNPKG

@mintlify/cli

Version:

The Mintlify CLI

223 lines (214 loc) 7.51 kB
import { validate, getOpenApiDocumentFromUrl, isAllowedLocalSchemaUrl } from '@mintlify/common'; import { getBrokenInternalLinks, renameFilesAndUpdateLinksInContent } from '@mintlify/link-rot'; import { dev } from '@mintlify/previewing'; import Chalk from 'chalk'; import fs from 'fs/promises'; import yaml from 'js-yaml'; import path from 'path'; import semver from 'semver'; import yargs from 'yargs'; import { hideBin } from 'yargs/helpers'; import { LOCAL_LINKED_VERSION, MINIMUM_CLI_VERSION } from './constants.js'; import { checkPort, checkForMintJson, checkNodeVersion, upgradeConfig, checkForDocsJson, getCliVersion, getVersions, suppressConsoleWarnings, } from './helpers.js'; import { update } from './update.js'; export const cli = () => yargs(hideBin(process.argv)) .middleware(checkNodeVersion) .middleware(suppressConsoleWarnings) .command( 'dev', 'Runs Mintlify project locally.', (yargs) => yargs .option('open', { type: 'boolean', default: true, description: 'Open Mintlify in the browser', }) .option('local-schema', { type: 'boolean', default: false, hidden: true, description: 'Use a locally hosted schema file (note: only https protocol is supported in production)', }) .option('client-version', { type: 'string', hidden: true, description: 'The version of the client to use for cli testing', }) .usage('Usage: mintlify dev [options]') .example('mintlify dev', 'Run with default settings (opens in browser)') .example('mintlify dev --no-open', 'Run without opening in browser'), async (argv) => { const port = await checkPort(argv); const packageName = process.argv[1]?.split('/').pop() ?? 'mintlify'; const cliVersion = getCliVersion(); if ( cliVersion && cliVersion !== LOCAL_LINKED_VERSION && semver.lt(cliVersion, MINIMUM_CLI_VERSION) ) { await update({ packageName, silent: true }); } if (port != undefined) { await dev({ ...argv, port, packageName, cliVersion: cli, }); } else { console.error(`No available port found.`); } } ) .command( 'openapi-check <openapiFilenameOrUrl>', 'Validate an OpenAPI spec', (yargs) => yargs .positional('openapiFilenameOrUrl', { describe: 'The filename of the OpenAPI spec (e.g. ./openapi.yaml) or the URL to the OpenAPI spec (e.g. https://petstore3.swagger.io/api/v3/openapi.json)', type: 'string', demandOption: true, }) .option('local-schema', { type: 'boolean', default: false, description: 'Use a locally hosted schema file (note: only https protocol is supported in production)', }), async ({ openapiFilenameOrUrl, 'local-schema': localSchema }) => { try { if (isAllowedLocalSchemaUrl(openapiFilenameOrUrl, localSchema)) { await getOpenApiDocumentFromUrl(openapiFilenameOrUrl); console.log('✅ Your OpenAPI definition is valid.'); process.exit(0); } const pathname = path.resolve(process.cwd(), openapiFilenameOrUrl); const file = await fs.readFile(pathname, 'utf-8'); const document = yaml.load(file) as Record<string, unknown> | undefined; if (!document) { throw new Error( 'Failed to parse OpenAPI spec: could not parse file correctly, please check for any syntax errors.' ); } await validate(document); console.log('✅ Your OpenAPI definition is valid.'); } catch (err) { console.error(Chalk.red(err)); process.exit(1); } } ) .command( 'broken-links', 'Check for broken links in your Mintlify project.', () => undefined, async () => { const hasMintJson = await checkForMintJson(); if (!hasMintJson) { await checkForDocsJson(); } console.log(Chalk.bold('Checking for broken links...\n')); try { const brokenLinks = await getBrokenInternalLinks(); if (brokenLinks.length === 0) { console.log(Chalk.green('No broken links found.')); return; } const brokenLinksByFile: Record<string, string[]> = {}; brokenLinks.forEach((mdxPath) => { const filename = path.join(mdxPath.relativeDir, mdxPath.filename); const brokenLinksForFile = brokenLinksByFile[filename]; if (brokenLinksForFile) { brokenLinksForFile.push(mdxPath.originalPath); } else { brokenLinksByFile[filename] = [mdxPath.originalPath]; } }); Object.entries(brokenLinksByFile).forEach(([fileName, brokenLinks]) => { console.group(`${Chalk.underline(fileName)}`); console.log(brokenLinks.join('\n'), '\n'); console.groupEnd(); }); console.error(Chalk.yellow(`${brokenLinks.length} broken links found.`)); process.exit(1); } catch (err) { console.error(Chalk.red(err)); process.exit(1); } } ) .command( 'rename <from> <to>', 'Rename file in a Mintlify project and update the internal link references.', (yargs) => yargs .positional('from', { describe: 'The file to rename', type: 'string', }) .positional('to', { describe: 'The new name for the file', type: 'string', }) .demandOption(['from', 'to']) .epilog('Example: `mintlify rename introduction.mdx overview.mdx`'), async ({ from, to }) => { const hasMintJson = await checkForMintJson(); if (!hasMintJson) { await checkForDocsJson(); } await renameFilesAndUpdateLinksInContent(from, to); } ) .command( 'update', 'Update the Mintlify client and cli to the latest version', () => undefined, async () => { const packageName = process.argv[1]?.split('/').pop() ?? 'mintlify'; await update({ packageName }); } ) .command( 'upgrade', 'Upgrade the mint.json file to v2 (docs.json)', () => undefined, async () => { const hasMintJson = await checkForMintJson(); if (!hasMintJson) { await checkForDocsJson(); } await upgradeConfig(); } ) .command( ['version', 'v'], 'Print the current version of the Mintlify CLI and client', () => undefined, () => { const { cli, client } = getVersions(); console.log(`Mintlify CLI version: ${cli}`); console.log(`Mintlify Client version: ${client}`); } ) // Print the help menu when the user enters an invalid command. .strictCommands() .demandCommand(1, 'Unknown command. See above for the list of supported commands.') // Alias option flags --help = -h, default --version = -v .alias('h', 'help') .alias('v', 'version') .parse();