mirror of
https://github.com/appium/appium.git
synced 2026-02-21 10:49:52 -06:00
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:
@@ -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');
|
||||
}
|
||||
|
||||
|
||||
@@ -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>;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
>;
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user