From 2766dcca4cfdc74904cc21f4881d748ed8ffd198 Mon Sep 17 00:00:00 2001 From: Christopher Hiller Date: Mon, 22 May 2023 16:25:20 -0700 Subject: [PATCH] feat(docutils): init: scafffold package.json This makes `appium-docs init` add a needed prop (`typedoc.entryPoint`) to `package.json` of an extension. It will write to `package.json` _by default_, even if it exists--which is different than the default behavior of other scaffolding tasks. Note that `appium-docs init` will now _fail_ if `--entry-point ` (or `-e`) is not provided; there's no way for us to guess what it is (this is the _source_ entry point; not necessarily the `main` file). I guess that's technically breaking, but `appium-docs init` should not be part of anyone's workflow. --- packages/docutils/lib/cli/check.ts | 5 ++- packages/docutils/lib/cli/command/init.ts | 22 ++++++++++ packages/docutils/lib/cli/index.ts | 31 ++++++++++++-- packages/docutils/lib/init.ts | 51 ++++++++++++++++++++++- packages/docutils/lib/scaffold.ts | 15 +++++-- 5 files changed, 113 insertions(+), 11 deletions(-) diff --git a/packages/docutils/lib/cli/check.ts b/packages/docutils/lib/cli/check.ts index ec8d49e9f..f72c17f02 100644 --- a/packages/docutils/lib/cli/check.ts +++ b/packages/docutils/lib/cli/check.ts @@ -79,7 +79,10 @@ export async function checkMissingPaths>( if (missingPaths.length) { return missingPaths - .map(({id, path}) => `Path specified using --${_.kebabCase(id)} (${path}) does not exist`) + .map( + ({id, path}) => + `Default or specified path via --${_.kebabCase(id)} (${path}) does not exist` + ) .join('\n'); } diff --git a/packages/docutils/lib/cli/command/init.ts b/packages/docutils/lib/cli/command/init.ts index ddb728ce8..df637b482 100644 --- a/packages/docutils/lib/cli/command/init.ts +++ b/packages/docutils/lib/cli/command/init.ts @@ -30,6 +30,7 @@ const opts = { nargs: 1, requiresArg: true, type: 'string', + implies: 'mkdocs', }, dir: { default: '.', @@ -59,6 +60,7 @@ const opts = { group: InitCommandGroup.MkDocs, requiresArg: true, type: 'string', + implies: 'mkdocs', }, mkdocs: { default: true, @@ -74,6 +76,7 @@ const opts = { normalize: true, requiresArg: true, type: 'string', + implies: 'mkdocs', }, 'package-json': { defaultDescription: './package.json', @@ -98,6 +101,7 @@ const opts = { normalize: true, requiresArg: true, type: 'string', + implies: 'python', }, 'repo-name': { defaultDescription: '(derived from --repo-url)', @@ -106,6 +110,7 @@ const opts = { nargs: 1, requiresArg: true, type: 'string', + implies: 'mkdocs', }, 'repo-url': { defaultDescription: '(from package.json)', @@ -114,6 +119,7 @@ const opts = { nargs: 1, requiresArg: true, type: 'string', + implies: 'mkdocs', }, 'site-description': { defaultDescription: '(from package.json)', @@ -122,6 +128,7 @@ const opts = { nargs: 1, requiresArg: true, type: 'string', + implies: 'mkdocs', }, 'site-name': { defaultDescription: '(extension package name)', @@ -130,6 +137,7 @@ const opts = { nargs: 1, requiresArg: true, type: 'string', + implies: 'mkdocs', }, 'tsconfig-json': { defaultDescription: './tsconfig.json', @@ -139,12 +147,14 @@ const opts = { normalize: true, requiresArg: true, type: 'string', + implies: 'typescript', }, typedoc: { default: true, description: 'Create typedoc.json if needed', group: InitCommandGroup.Behavior, type: 'boolean', + implies: 'entry-point', }, 'typedoc-json': { defaultDescription: './typedoc.json', @@ -154,6 +164,17 @@ const opts = { normalize: true, requiresArg: true, type: 'string', + implies: 'typedoc', + }, + 'entry-point': { + alias: 'e', + describe: 'Source entry point of extension', + group: InitCommandGroup.Paths, + nargs: 1, + normalize: false, + requiresArg: true, + type: 'string', + implies: 'typedoc', }, typescript: { default: true, @@ -167,6 +188,7 @@ const opts = { group: InitCommandGroup.Behavior, type: 'boolean', conflicts: 'force', + implies: 'python', }, } as const satisfies Record; diff --git a/packages/docutils/lib/cli/index.ts b/packages/docutils/lib/cli/index.ts index be45d81f2..21ccfdaa9 100644 --- a/packages/docutils/lib/cli/index.ts +++ b/packages/docutils/lib/cli/index.ts @@ -8,19 +8,20 @@ import {getLogger} from '../logger'; +import {fs} from '@appium/support'; import _ from 'lodash'; +import {sync as readPkg} from 'read-pkg'; import {hideBin} from 'yargs/helpers'; import yargs from 'yargs/yargs'; import {DEFAULT_LOG_LEVEL, LogLevelMap, NAME_BIN} from '../constants'; import {DocutilsError} from '../error'; import {build, init, validate} from './command'; import {findConfig} from './config'; -import {fs} from '@appium/support'; -import {sync as readPkg} from 'read-pkg'; const pkg = readPkg({cwd: fs.findRoot(__dirname)}); - const log = getLogger('cli'); +const IMPLICATIONS_FAILED_REGEX = /implications\s+failed:\n\s*(.+)\s->\s(.+)$/i; + export async function main(argv = hideBin(process.argv)) { const config = await findConfig(argv); @@ -70,12 +71,34 @@ export async function main(argv = hideBin(process.argv)) { * Custom failure handler so we can log nicely. */ (msg: string | null, error) => { + /** + * yargs' default output if an "implication" fails (e.g., arg _A_ requires arg _B_) leaves much to be desired. + * + * @remarks Unfortunately, we do not have access to the parsed arguments object, since it may have failed parsing. + * @param msg Implication failure message + * @returns Whether the message was an implication failure + */ + const handleImplicationFailure = (msg: string | null): boolean => { + let match: RegExpMatchArray | null | undefined; + if (!(match = msg?.match(IMPLICATIONS_FAILED_REGEX))) { + return false; + } + const [, arg, missingArg] = match; + log.error( + `Argument "--${arg}" requires "--${missingArg}"; note that "--${arg}" may be enabled by default` + ); + return true; + }; + // if it is a DocutilsError, it has nothing to do with the CLI if (error instanceof DocutilsError) { log.error(error.message); } else { y.showHelp(); - log.error(`\n\n${msg ?? error.message}`); + + if (!handleImplicationFailure(msg)) { + log.error(`\n\n${msg ?? error.message}`); + } } y.exit(1, error); } diff --git a/packages/docutils/lib/init.ts b/packages/docutils/lib/init.ts index 801119da0..efa46db02 100644 --- a/packages/docutils/lib/init.ts +++ b/packages/docutils/lib/init.ts @@ -11,10 +11,11 @@ import { NAME_PYTHON, REQUIREMENTS_TXT_PATH, NAME_TYPEDOC_JSON, + NAME_PACKAGE_JSON, } from './constants'; import YAML from 'yaml'; import {exec} from 'teen_process'; -import {Simplify} from 'type-fest'; +import {PackageJson, Simplify} from 'type-fest'; import {DocutilsError} from './error'; import {createScaffoldTask, ScaffoldTaskOptions} from './scaffold'; import {getLogger} from './logger'; @@ -54,9 +55,19 @@ const BASE_TSCONFIG_JSON: Readonly = Object.freeze({ }, }); +/** + * Data for the base `package.json` file. + * We expect `package.json` to exist, and we are not in the business of creating it. + * However, we will need to add a `typedoc.entryPoint` prop to it. + */ +const BASE_PACKAGE_JSON: Readonly = Object.freeze({}); + const log = getLogger('init'); const dryRunLog = getLogger('dry-run', log); +/** + * Files included, by default, in `tsconfig.json` + */ const DEFAULT_INCLUDE = ['lib', 'test', 'index.js']; /** * Function which scaffolds a `tsconfig.json` file @@ -97,6 +108,26 @@ export const initTypeDocJson = createScaffoldTask( + NAME_PACKAGE_JSON, + BASE_PACKAGE_JSON, + 'Package configuration for TypeDoc', + { + transform: (content, opts) => + ({ + ...content, + typedoc: {entryPoint: opts.typeDocEntryPoint}, + } as PackageJson), + } +); + /** * Function which scaffolds an `mkdocs.yml` file */ @@ -224,6 +255,7 @@ export async function init({ pythonPath, upgrade, typedocJson: typeDocJsonPath, + entryPoint: typeDocEntryPoint, }: InitOptions = {}): Promise { if (!typescript && typedoc) { log.warn( @@ -250,6 +282,13 @@ export async function init({ dryRun, cwd, }); + await initTypeDocPkgJson({ + packageJson: packageJsonPath, + overwrite: true, // <-- always overwrite + dryRun, + cwd, + typeDocEntryPoint, + }); } if (python) { @@ -271,7 +310,10 @@ export async function init({ } } -export type InitTypeDocOptions = ScaffoldTaskOptions; +export interface InitTypeDocOptions extends ScaffoldTaskOptions { + typeDocEntryPoint?: string; +} + export interface InitTsConfigOptions extends ScaffoldTaskOptions { /** * List of source files (globs supported); typically `src` or `lib` @@ -334,5 +376,10 @@ export type InitOptions = Simplify< * If `true`, upgrade only */ upgrade?: boolean; + + /** + * Path to entry point of extension (source; not "main" field) + */ + entryPoint?: string; } >; diff --git a/packages/docutils/lib/scaffold.ts b/packages/docutils/lib/scaffold.ts index a34110b31..ae52f986d 100644 --- a/packages/docutils/lib/scaffold.ts +++ b/packages/docutils/lib/scaffold.ts @@ -92,6 +92,7 @@ export function createScaffoldTask; try { @@ -105,6 +106,7 @@ export function createScaffoldTask = ( content: Readonly, opts: TaskSpecificOpts, - pkg: NormalizedPackageJson + pkg: Readonly ) => T; /**