diff --git a/README.md b/README.md index 4cc6705cd..3803b49e0 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,7 @@ Commands: aegir build Builds a browser bundle and TS type declarations from the `src` folder. aegir check Check project aegir docs Generate documentation from TS type declarations. + aegir doc-check Verify TS code snippets in documentation. aegir lint Lint all project files aegir release Release your code onto the world aegir test-dependant [repo] Run the tests of an module that depends on this module to see if the current changes have caused a regression @@ -103,8 +104,6 @@ Aegir can be fully configured using a config file named `.aegir.js` or the packa ```js // file: .aegir.js - - /** @type {import('aegir').PartialOptions} */ module.exports = { tsRepo: true, diff --git a/md/github-actions.md b/md/github-actions.md index 36caef3ed..931a63a0f 100644 --- a/md/github-actions.md +++ b/md/github-actions.md @@ -19,6 +19,7 @@ jobs: - run: npx aegir lint - run: npx aegir build - run: npx aegir dep-check + - run: npx aegir doc-check - uses: ipfs/aegir/actions/bundle-size@master with: github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/package.json b/package.json index d7328c9c4..b3be82bca 100644 --- a/package.json +++ b/package.json @@ -213,6 +213,7 @@ "test": "node src/index.js test", "docs": "node src/index.js docs", "dep-check": "node src/index.js dep-check", + "doc-check": "node src/index.js doc-check", "test:node": "node src/index.js test -t node --cov", "test:chrome": "node src/index.js test -t browser --cov", "test:chrome-webworker": "node src/index.js test -t webworker", @@ -310,6 +311,7 @@ "typedoc-plugin-mdn-links": "^2.0.0", "typedoc-plugin-missing-exports": "^1.0.0", "typescript": "^4.6.3", + "typescript-docs-verifier": "2.4.0", "uint8arrays": "^4.0.2", "undici": "^5.0.0", "update-notifier": "^6.0.2", diff --git a/src/cmds/document-check.js b/src/cmds/document-check.js new file mode 100644 index 000000000..09a5c4c62 --- /dev/null +++ b/src/cmds/document-check.js @@ -0,0 +1,49 @@ +/* eslint-disable no-console */ + +import { loadUserConfig } from '../config/user.js' +import docCheck from '../document-check.js' + +/** + * @typedef {import("yargs").Argv} Argv + * @typedef {import("yargs").Arguments} Arguments + * @typedef {import("yargs").CommandModule} CommandModule + */ + +const EPILOG = ` +Docs are verified using typescript-docs-verifier (https://github.com/bbc/typescript-docs-verifier#typescript-docs-verifier) +For more info read: https://github.com/bbc/typescript-docs-verifier#how-it-works +` + +/** @type {CommandModule} */ +export default { + command: 'document-check [input...]', + aliases: ['doc-check'], + describe: 'Run `document-check` cli with aegir defaults.', + /** + * @param {Argv} yargs + */ + builder: async (yargs) => { + const userConfig = await loadUserConfig() + + return yargs + .epilog(EPILOG) + .options({ + inputFiles: { + array: true, + describe: 'The files to verify, defaults to `README.md`', + default: userConfig.documentCheck.inputFiles + }, + tsConfigPath: { + type: 'string', + describe: 'The path to the `tsconfig.json`, defaults to the root.', + default: userConfig.documentCheck.tsConfigPath + } + }) + }, + /** + * @param {any} argv + */ + async handler (argv) { + await docCheck.run(argv) + } +} diff --git a/src/config/user.js b/src/config/user.js index 3e40db1bc..e00aa53db 100644 --- a/src/config/user.js +++ b/src/config/user.js @@ -68,6 +68,14 @@ const defaults = { email: 'aegir[bot]@users.noreply.github.com', directory: '.docs' }, + // document check cmd options + documentCheck: { + inputFiles: [ + '*.md', + 'src/*.md' + ], + tsConfigPath: '.' + }, // ts cmd options ts: { preset: undefined, diff --git a/src/document-check.js b/src/document-check.js new file mode 100644 index 000000000..3dd44851c --- /dev/null +++ b/src/document-check.js @@ -0,0 +1,60 @@ +/* eslint-disable no-console */ + +import Listr from 'listr' +import { hasTsconfig } from './utils.js' +import { globby } from 'globby' +import { compileSnippets } from 'typescript-docs-verifier' +/** + * @typedef {import("./types").GlobalOptions} GlobalOptions + * @typedef {import("./types").DocsVerifierOptions} DocsVerifierOptions + * @typedef {import("listr").ListrTaskWrapper} Task + */ + +const tasks = new Listr( + [ + { + title: 'typescript-doc-verify', + /** + * @param {GlobalOptions & DocsVerifierOptions} ctx + */ + enabled: ctx => hasTsconfig, + /** + * @param {GlobalOptions & DocsVerifierOptions} ctx + * @param {Task} task + */ + task: async (ctx, task) => { + let tsconfigPath = 'tsconfig.json' + let markdownFiles = ['README.md'] + + if (ctx.tsConfigPath) { + tsconfigPath = `${ctx.tsConfigPath}/tsconfig.json` + } + + if (ctx.inputFiles) { + markdownFiles = await globby(ctx.inputFiles) + } + + compileSnippets({ markdownFiles, project: tsconfigPath }) + .then((results) => { + results.forEach((result) => { + if (result.error) { + console.log(`Error compiling example code block ${result.index} in file ${result.file}`) + console.log(result.error.message) + console.log('Original code:') + console.log(result.snippet) + } + }) + }) + .catch((error) => { + console.error('Error compiling TypeScript snippets', error) + }) + } + + } + ], + { + renderer: 'verbose' + } +) + +export default tasks diff --git a/src/index.js b/src/index.js index 8d8d35d61..26a233a67 100755 --- a/src/index.js +++ b/src/index.js @@ -18,6 +18,7 @@ import releaseRcCmd from './cmds/release-rc.js' import testDependantCmd from './cmds/test-dependant.js' import testCmd from './cmds/test.js' import docsCmd from './cmds/docs.js' +import docVerifyCmd from './cmds/document-check.js' import execCmd from './cmds/exec.js' import runCmd from './cmds/run.js' @@ -87,6 +88,7 @@ async function main () { res.command(cleanCmd) res.command(dependencyCheckCmd) res.command(docsCmd) + res.command(docVerifyCmd) res.command(lintPackageJsonCmd) res.command(lintCmd) res.command(releaseCmd) diff --git a/src/lint.js b/src/lint.js index 1eb405994..c5b771b67 100644 --- a/src/lint.js +++ b/src/lint.js @@ -7,7 +7,7 @@ import path from 'path' import { execa } from 'execa' import fs from 'fs-extra' import merge from 'merge-options' -import { fromRoot, readJson, hasTsconfig, isTypescript, findBinary } from './utils.js' +import { fromRoot, readJson, hasTsconfig, isTypescript, findBinary, hasDocCheck } from './utils.js' import { fileURLToPath } from 'url' import kleur from 'kleur' @@ -99,6 +99,15 @@ const tasks = new Listr( fs.removeSync(fromRoot('dist', 'tsconfig-check.aegir.tsbuildinfo')) } } + }, + { + title: 'doc-check', + enabled: () => hasDocCheck, + task: async () => { + await execa('npm', ['run', 'doc-check', '--if-present', '--', '--publish', 'false'], { + stdio: 'inherit' + }) + } } ], { diff --git a/src/types.ts b/src/types.ts index 063eceeb6..f8e677f24 100644 --- a/src/types.ts +++ b/src/types.ts @@ -36,6 +36,10 @@ interface Options extends GlobalOptions { * Options for the `dependency-check` command */ dependencyCheck: DependencyCheckOptions + /** + * Options for the `document-check` command + */ + documentCheck: DocsVerifierOptions /** * Options for the `exec` command */ @@ -82,6 +86,10 @@ interface PartialOptions { * Options for the `dependency-check` command */ dependencyCheck?: DependencyCheckOptions + /** + * Options for the `document-check` command + */ + documentCheck?: DocsVerifierOptions } interface GlobalOptions { @@ -164,6 +172,18 @@ interface DocsOptions { directory: string } +interface DocsVerifierOptions { + /** + * The Markdown files to be verified, defaults to `README.md` + */ + inputFiles?: string[] + + /** + * An alternative `.tsconfig.json` path to be used seperately from the default + */ + tsConfigPath?: string +} + interface LintOptions { /** * Automatically fix errors if possible. @@ -350,6 +370,7 @@ export type { LintOptions, TestOptions, ReleaseOptions, + DocsVerifierOptions, ReleaseRcOptions, DependencyCheckOptions, ExecOptions, diff --git a/src/utils.js b/src/utils.js index f4ae0fb33..2fc847e4d 100644 --- a/src/utils.js +++ b/src/utils.js @@ -252,6 +252,7 @@ export const hasIndexTs = hasFile('src/index.ts') export const hasIndexJs = hasFile('src/index.js') export const isMonorepoParent = Boolean(pkg.workspaces) export const hasDocs = Boolean(pkg.scripts?.docs) +export const hasDocCheck = Boolean(pkg.scripts && pkg.scripts['doc-check']) // our project types: