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 <some-file>` (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.
This commit is contained in:
Christopher Hiller
2023-05-22 16:25:20 -07:00
parent f5b112bcca
commit 2766dcca4c
5 changed files with 113 additions and 11 deletions

View File

@@ -79,7 +79,10 @@ export async function checkMissingPaths<T extends Record<string, Options>>(
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');
}

View File

@@ -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<string, Options>;

View File

@@ -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);
}

View File

@@ -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<TsConfigJson> = 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<PackageJson> = 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<InitTypeDocOptions, TypeDocJso
'TypeDoc configuration'
);
/**
* Function which scaffolds a `package.json` file
*
* This only amends prop `typedoc.entryPoint` to the `package.json` file.
*
* If, strangely, `package.json` did not exist, then it will now contain _only_ that prop.
*/
export const initTypeDocPkgJson = createScaffoldTask<InitTypeDocOptions, PackageJson>(
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<void> {
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;
}
>;

View File

@@ -92,6 +92,7 @@ export function createScaffoldTask<Opts extends ScaffoldTaskOptions, T extends J
const relativeDest = relativePath(dest);
log.debug('Initializing %s', relativeDest);
let shouldWriteDest = false;
let isNew = false;
let destContent: T;
let result: ScaffoldTaskResult<T>;
try {
@@ -105,6 +106,7 @@ export function createScaffoldTask<Opts extends ScaffoldTaskOptions, T extends J
shouldWriteDest = true;
log.debug('Creating new file %s', relativeDest);
destContent = {} as T;
isNew = true;
}
const defaults: T = transform(defaultContent, opts, pkg);
@@ -113,7 +115,7 @@ export function createScaffoldTask<Opts extends ScaffoldTaskOptions, T extends J
shouldWriteDest = shouldWriteDest || !_.isEqual(destContent, finalDestContent);
if (shouldWriteDest) {
log.info('Changes needed to %s', relativeDest);
log.info('Changes needed in %s', relativeDest);
log.debug('Original %s: %O', relativeDest, destContent);
log.debug('Final %s: %O', relativeDest, finalDestContent);
const patch = makePatch(dest, destContent, finalDestContent, serialize);
@@ -126,6 +128,11 @@ export function createScaffoldTask<Opts extends ScaffoldTaskOptions, T extends J
try {
await safeWriteFile(dest, finalDestContent, overwrite);
if (isNew) {
log.success('Initialized %s', description);
} else {
log.success('Updated %s', description);
}
} catch (e) {
const err = e as NodeJS.ErrnoException;
// this should only be thrown if `force` is false
@@ -139,9 +146,9 @@ export function createScaffoldTask<Opts extends ScaffoldTaskOptions, T extends J
}
}
} else {
log.info('No changes to %s', relativeDest);
log.info('No changes necessary for %s', relativeDest);
}
log.success('Initialized %s', description);
log.success(`${description}: done`);
return {path: dest, content: finalDestContent};
};
}
@@ -153,7 +160,7 @@ export function createScaffoldTask<Opts extends ScaffoldTaskOptions, T extends J
export type ScaffoldTaskTransformer<Opts extends ScaffoldTaskOptions, T extends JsonValue> = (
content: Readonly<T>,
opts: TaskSpecificOpts<Opts>,
pkg: NormalizedPackageJson
pkg: Readonly<NormalizedPackageJson>
) => T;
/**