diff --git a/README.md b/README.md index 9148a2a3..68b822f1 100644 --- a/README.md +++ b/README.md @@ -36,10 +36,17 @@ CLI tool to generate API documentation of a Node.js project. Options: -i, --input [patterns...] Specify input file patterns using glob syntax - --ignore [patterns...] Specify files to be ignored from the input using glob syntax + --ignore [patterns...] Specify which input files to ignore using glob syntax -o, --output Specify the relative or absolute output directory - -v, --version Specify the target version of Node.js, semver compliant (default: "v22.6.0") - -c, --changelog Specify the path (file: or https://) to the CHANGELOG.md file (default: "https://raw.githubusercontent.com/nodejs/node/HEAD/CHANGELOG.md") - -t, --target [mode...] Set the processing target modes (choices: "json-simple", "legacy-html", "legacy-html-all", "man-page", "legacy-json", "legacy-json-all", "addon-verify", "api-links", "orama-db") + -v, --version Specify the target version of Node.js, semver compliant (default: "v22.11.0") + -c, --changelog Specify the path (file: or https://) to the CHANGELOG.md file (default: + "https://raw.githubusercontent.com/nodejs/node/HEAD/CHANGELOG.md") + -t, --target [mode...] Set the processing target modes (choices: "json-simple", "legacy-html", "legacy-html-all", + "man-page", "legacy-json", "legacy-json-all", "addon-verify", "api-links", "orama-db") + --disable-rule [rule...] Disable a specific linter rule (choices: "invalid-change-version", + "missing-change-version", "missing-introduced-in", default: []) + --lint-dry-run Run linter in dry-run mode (default: false) + --git-ref A git ref/commit URL pointing to Node.js + -r, --reporter [reporter] Specify the linter reporter (choices: "console", "github", default: "console") -h, --help display help for command ``` diff --git a/bin/cli.mjs b/bin/cli.mjs index df1cbc4e..f911bf97 100755 --- a/bin/cli.mjs +++ b/bin/cli.mjs @@ -9,12 +9,12 @@ import { coerce } from 'semver'; import { DOC_NODE_CHANGELOG_URL, DOC_NODE_VERSION } from '../src/constants.mjs'; import createGenerator from '../src/generators.mjs'; import generators from '../src/generators/index.mjs'; -import createMarkdownLoader from '../src/loaders/markdown.mjs'; -import createMarkdownParser from '../src/parsers/markdown.mjs'; -import createNodeReleases from '../src/releases.mjs'; import createLinter from '../src/linter/index.mjs'; import reporters from '../src/linter/reporters/index.mjs'; import rules from '../src/linter/rules/index.mjs'; +import createMarkdownLoader from '../src/loaders/markdown.mjs'; +import createMarkdownParser from '../src/parsers/markdown.mjs'; +import createNodeReleases from '../src/releases.mjs'; const availableGenerators = Object.keys(generators); @@ -67,6 +67,11 @@ program .addOption( new Option('--lint-dry-run', 'Run linter in dry-run mode').default(false) ) + .addOption( + new Option('--git-ref', 'A git ref/commit URL pointing to Node.js').default( + 'https://github.com/nodejs/node/tree/HEAD' + ) + ) .addOption( new Option('-r, --reporter [reporter]', 'Specify the linter reporter') .choices(Object.keys(reporters)) @@ -85,6 +90,7 @@ program * @property {string} changelog Specifies the path to the Node.js CHANGELOG.md file. * @property {string[]} disableRule Specifies the linter rules to disable. * @property {boolean} lintDryRun Specifies whether the linter should run in dry-run mode. + * @property {boolean} useGit Specifies whether the parser should execute optional git commands. (Should only be used within a git repo) * @property {keyof reporters} reporter Specifies the linter reporter. * * @name ProgramOptions @@ -100,6 +106,7 @@ const { changelog, disableRule, lintDryRun, + gitRef, reporter, } = program.opts(); @@ -117,23 +124,28 @@ const { runGenerators } = createGenerator(parsedApiDocs); // Retrieves Node.js release metadata from a given Node.js version and CHANGELOG.md file const { getAllMajors } = createNodeReleases(changelog); +// Runs the Linter on the parsed API docs linter.lintAll(parsedApiDocs); -if (target && output) { +if (target) { await runGenerators({ // A list of target modes for the API docs parser generators: target, // Resolved `input` to be used input: input, // Resolved `output` path to be used - output: resolve(output), + output: output && resolve(output), // Resolved SemVer of current Node.js version version: coerce(version), // A list of all Node.js major versions with LTS status releases: await getAllMajors(), + // An URL containing a git ref URL pointing to the commit or ref that was used + // to generate the API docs. This is used to link to the source code of the + gitRef, }); } +// Reports Lint Content linter.report(reporter); exit(Number(linter.hasError())); diff --git a/src/generators.mjs b/src/generators.mjs index b276d779..bb03c290 100644 --- a/src/generators.mjs +++ b/src/generators.mjs @@ -13,8 +13,8 @@ const availableGenerators = { }; /** - * @typedef {{ ast: import('./generators/types.d.ts').GeneratorMetadata}} AstGenerator The AST "generator" is a facade for the AST tree and it isn't really a generator - * @typedef {import('./generators/types.d.ts').AvailableGenerators & AstGenerator} AllGenerators A complete set of the available generators, including the AST one + * @typedef {{ ast: GeneratorMetadata}} AstGenerator The AST "generator" is a facade for the AST tree and it isn't really a generator + * @typedef {AvailableGenerators & AstGenerator} AllGenerators A complete set of the available generators, including the AST one * @param markdownInput * @param jsInput * @@ -41,14 +41,12 @@ const createGenerator = markdownInput => { * * @type {{ [K in keyof AllGenerators]: ReturnType }} */ - const cachedGenerators = { - ast: Promise.resolve(markdownInput), - }; + const cachedGenerators = { ast: Promise.resolve(markdownInput) }; /** * Runs the Generator engine with the provided top-level input and the given generator options * - * @param {import('./generators/types.d.ts').GeneratorOptions} options The options for the generator runtime + * @param {GeneratorOptions} options The options for the generator runtime */ const runGenerators = async ({ generators, ...extra }) => { // Note that this method is blocking, and will only execute one generator per-time diff --git a/src/generators/addon-verify/index.mjs b/src/generators/addon-verify/index.mjs index 76a91ee8..450bf962 100644 --- a/src/generators/addon-verify/index.mjs +++ b/src/generators/addon-verify/index.mjs @@ -20,7 +20,7 @@ import { * * @typedef {Array} Input * - * @type {import('../types.d.ts').GeneratorMetadata} + * @type {GeneratorMetadata} */ export default { name: 'addon-verify', diff --git a/src/generators/api-links/index.mjs b/src/generators/api-links/index.mjs index b0b00640..93441450 100644 --- a/src/generators/api-links/index.mjs +++ b/src/generators/api-links/index.mjs @@ -1,11 +1,7 @@ 'use strict'; -import { basename, dirname, join } from 'node:path'; +import { basename, join } from 'node:path'; import { writeFile } from 'node:fs/promises'; -import { - getBaseGitHubUrl, - getCurrentGitHash, -} from './utils/getBaseGitHubUrl.mjs'; import { extractExports } from './utils/extractExports.mjs'; import { findDefinitions } from './utils/findDefinitions.mjs'; import { checkIndirectReferences } from './utils/checkIndirectReferences.mjs'; @@ -20,7 +16,7 @@ import { checkIndirectReferences } from './utils/checkIndirectReferences.mjs'; * * @typedef {Array} Input * - * @type {import('../types.d.ts').GeneratorMetadata>} + * @type {GeneratorMetadata>} */ export default { name: 'api-links', @@ -40,56 +36,37 @@ export default { * @param {Input} input * @param {Partial} options */ - async generate(input, { output }) { + async generate(input, { output, gitRef }) { /** * @type Record */ const definitions = {}; - /** - * @type {string} - */ - let baseGithubLink; - - if (input.length > 0) { - const repositoryDirectory = dirname(input[0].path); - - const repository = getBaseGitHubUrl(repositoryDirectory); - - const tag = getCurrentGitHash(repositoryDirectory); - - baseGithubLink = `${repository}/blob/${tag}`; - } - input.forEach(program => { /** * Mapping of definitions to their line number + * * @type {Record} * @example { 'someclass.foo': 10 } */ const nameToLineNumberMap = {}; // `http.js` -> `http` - const programBasename = basename(program.path, '.js'); + const baseName = basename(program.path, '.js'); - const exports = extractExports( - program, - programBasename, - nameToLineNumberMap - ); + const exports = extractExports(program, baseName, nameToLineNumberMap); - findDefinitions(program, programBasename, nameToLineNumberMap, exports); + findDefinitions(program, baseName, nameToLineNumberMap, exports); checkIndirectReferences(program, exports, nameToLineNumberMap); - const githubLink = - `${baseGithubLink}/lib/${programBasename}.js`.replaceAll('\\', '/'); + const fullGitUrl = `${gitRef}/lib/${baseName}.js`; // Add the exports we found in this program to our output Object.keys(nameToLineNumberMap).forEach(key => { const lineNumber = nameToLineNumberMap[key]; - definitions[key] = `${githubLink}#L${lineNumber}`; + definitions[key] = `${fullGitUrl}#L${lineNumber}`; }); }); diff --git a/src/generators/api-links/test/fixtures.test.mjs b/src/generators/api-links/test/fixtures.test.mjs index 9f8ee53e..028ea5a9 100644 --- a/src/generators/api-links/test/fixtures.test.mjs +++ b/src/generators/api-links/test/fixtures.test.mjs @@ -19,7 +19,9 @@ describe('api links', () => { input: [sourceFile], }); - const actualOutput = await apiLinks.generate(astJsResult, {}); + const actualOutput = await apiLinks.generate(astJsResult, { + gitRef: 'https://github.com/nodejs/node/tree/HEAD', + }); for (const [k, v] of Object.entries(actualOutput)) { actualOutput[k] = v.replace(/.*(?=lib\/)/, ''); diff --git a/src/generators/api-links/utils/getBaseGitHubUrl.mjs b/src/generators/api-links/utils/getBaseGitHubUrl.mjs deleted file mode 100644 index 5bbe313b..00000000 --- a/src/generators/api-links/utils/getBaseGitHubUrl.mjs +++ /dev/null @@ -1,35 +0,0 @@ -'use strict'; - -import { execSync } from 'node:child_process'; - -/** - * @param {string} cwd - */ -export function getBaseGitHubUrl(cwd) { - let url = execSync('git remote get-url origin', { cwd }).toString().trim(); - - if (url.startsWith('git@')) { - // It's an ssh url, we need to transform it to be https - // Ex/ git@github.com:nodejs/node.git -> https://github.com/nodejs/node.git - let [, repository] = url.split(':'); - - url = `https://github.com/${repository}`; - } - - // https://github.com/nodejs/node.git -> https://github.com/nodejs/node - if (url.endsWith('.git')) { - url = url.substring(0, url.length - 4); - } - - return url; -} - -/** - * Grabs the current Git commit hash within a directory - * @param cwd - */ -export function getCurrentGitHash(cwd) { - const hash = execSync('git rev-parse HEAD', { cwd }).toString().trim(); - - return hash; -} diff --git a/src/generators/ast-js/index.mjs b/src/generators/ast-js/index.mjs index 440365a5..b31cb047 100644 --- a/src/generators/ast-js/index.mjs +++ b/src/generators/ast-js/index.mjs @@ -11,7 +11,7 @@ import createJsParser from '../../parsers/javascript.mjs'; * * @typedef {unknown} Input * - * @type {import('../types.d.ts').GeneratorMetadata>} + * @type {GeneratorMetadata>} */ export default { name: 'ast-js', diff --git a/src/generators/json-simple/index.mjs b/src/generators/json-simple/index.mjs index a551593c..979e2121 100644 --- a/src/generators/json-simple/index.mjs +++ b/src/generators/json-simple/index.mjs @@ -17,7 +17,7 @@ import { getRemark } from '../../utils/remark.mjs'; * * @typedef {Array} Input * - * @type {import('../types.d.ts').GeneratorMetadata} + * @type {GeneratorMetadata} */ export default { name: 'json-simple', diff --git a/src/generators/legacy-html-all/index.mjs b/src/generators/legacy-html-all/index.mjs index a2abdb2e..7004aa4f 100644 --- a/src/generators/legacy-html-all/index.mjs +++ b/src/generators/legacy-html-all/index.mjs @@ -29,7 +29,7 @@ import { getRemarkRehype } from '../../utils/remark.mjs'; * * @typedef {Array} Input * - * @type {import('../types.d.ts').GeneratorMetadata} + * @type {GeneratorMetadata} */ export default { name: 'legacy-html-all', diff --git a/src/generators/legacy-html/index.mjs b/src/generators/legacy-html/index.mjs index 33358c33..c7f836d7 100644 --- a/src/generators/legacy-html/index.mjs +++ b/src/generators/legacy-html/index.mjs @@ -31,7 +31,7 @@ import { getRemarkRehype } from '../../utils/remark.mjs'; * * @typedef {Array} Input * - * @type {import('../types.d.ts').GeneratorMetadata>} + * @type {GeneratorMetadata>} */ export default { name: 'legacy-html', diff --git a/src/generators/legacy-json-all/index.mjs b/src/generators/legacy-json-all/index.mjs index 9bb36310..b2e9cdf8 100644 --- a/src/generators/legacy-json-all/index.mjs +++ b/src/generators/legacy-json-all/index.mjs @@ -9,7 +9,7 @@ import { join } from 'node:path'; * * @typedef {Array} Input * - * @type {import('../types.d.ts').GeneratorMetadata} + * @type {GeneratorMetadata} */ export default { name: 'legacy-json-all', diff --git a/src/generators/legacy-json/index.mjs b/src/generators/legacy-json/index.mjs index 8cf70e88..add7ff87 100644 --- a/src/generators/legacy-json/index.mjs +++ b/src/generators/legacy-json/index.mjs @@ -16,7 +16,7 @@ import { createSectionBuilder } from './utils/buildSection.mjs'; * * @typedef {Array} Input * - * @type {import('../types.d.ts').GeneratorMetadata} + * @type {GeneratorMetadata} */ export default { name: 'legacy-json', diff --git a/src/generators/man-page/index.mjs b/src/generators/man-page/index.mjs index 8ee045aa..f5ec60ab 100644 --- a/src/generators/man-page/index.mjs +++ b/src/generators/man-page/index.mjs @@ -16,7 +16,7 @@ import { DOC_SLUG_ENVIRONMENT, DOC_SLUG_OPTIONS } from '../../constants.mjs'; * * @typedef {Array} Input * - * @type {import('../types.d.ts').GeneratorMetadata} + * @type {GeneratorMetadata} */ export default { name: 'man-page', diff --git a/src/generators/orama-db/index.mjs b/src/generators/orama-db/index.mjs index d6624f90..c2210c6b 100644 --- a/src/generators/orama-db/index.mjs +++ b/src/generators/orama-db/index.mjs @@ -12,7 +12,7 @@ import { createSectionBuilder } from '../legacy-json/utils/buildSection.mjs'; * * @typedef {Array} Input * - * @type {import('../types.d.ts').GeneratorMetadata} + * @type {GeneratorMetadata} */ export default { name: 'orama-db', diff --git a/src/generators/types.d.ts b/src/generators/types.d.ts index 110b5dc0..a3eb07b6 100644 --- a/src/generators/types.d.ts +++ b/src/generators/types.d.ts @@ -1,6 +1,6 @@ import type { SemVer } from 'semver'; -import type availableGenerators from './index.mjs'; import type { ApiDocReleaseEntry } from '../types'; +import type availableGenerators from './index.mjs'; declare global { // All available generators as an inferable type, to allow Generator interfaces @@ -30,6 +30,12 @@ declare global { // A list of all Node.js major versions and their respective release information releases: Array; + + // An URL containing a git ref URL pointing to the commit or ref that was used + // to generate the API docs. This is used to link to the source code of the + // i.e. https://github.com/nodejs/node/tree/2cb1d07e0f6d9456438016bab7db4688ab354fd2 + // i.e. https://gitlab.com/someone/node/tree/HEAD + gitRef: string; } export interface GeneratorMetadata {