mirror of
https://github.com/appium/appium.git
synced 2026-10-23 09:09:16 +08:00
2766dcca4c
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.
386 lines
9.7 KiB
TypeScript
386 lines
9.7 KiB
TypeScript
/**
|
|
* Scaffolding functions for CLI `init` command
|
|
*
|
|
* @module
|
|
*/
|
|
|
|
import * as JSON5 from 'json5';
|
|
import {
|
|
NAME_MKDOCS_YML,
|
|
NAME_TSCONFIG_JSON,
|
|
NAME_PYTHON,
|
|
REQUIREMENTS_TXT_PATH,
|
|
NAME_TYPEDOC_JSON,
|
|
NAME_PACKAGE_JSON,
|
|
} from './constants';
|
|
import YAML from 'yaml';
|
|
import {exec} from 'teen_process';
|
|
import {PackageJson, Simplify} from 'type-fest';
|
|
import {DocutilsError} from './error';
|
|
import {createScaffoldTask, ScaffoldTaskOptions} from './scaffold';
|
|
import {getLogger} from './logger';
|
|
import {MkDocsYml, TsConfigJson, TypeDocJson} from './model';
|
|
import _ from 'lodash';
|
|
import {findPython, stringifyJson5, stringifyYaml} from './fs';
|
|
|
|
/**
|
|
* Data for the base `mkdocs.yml` file
|
|
*/
|
|
const BASE_MKDOCS_YML: Readonly<MkDocsYml> = Object.freeze({
|
|
INHERIT: './node_modules/@appium/docutils/base-mkdocs.yml',
|
|
docs_dir: 'docs',
|
|
site_dir: 'site',
|
|
});
|
|
|
|
/**
|
|
* Data for the base `typedoc.json` file
|
|
*/
|
|
const BASE_TYPEDOC_JSON: Readonly<TypeDocJson> = Object.freeze({
|
|
$schema: 'https://typedoc.org/schema.json',
|
|
cleanOutputDir: true,
|
|
entryPointStrategy: 'packages',
|
|
theme: 'appium',
|
|
readme: 'none',
|
|
entryPoints: ['.'],
|
|
});
|
|
|
|
/**
|
|
* Data for the base `tsconfig.json` file
|
|
*/
|
|
const BASE_TSCONFIG_JSON: Readonly<TsConfigJson> = Object.freeze({
|
|
$schema: 'https://json.schemastore.org/tsconfig',
|
|
extends: '@appium/tsconfig/tsconfig.json',
|
|
compilerOptions: {
|
|
outDir: 'build',
|
|
},
|
|
});
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
export const initTsConfigJson = createScaffoldTask<InitTsConfigOptions, TsConfigJson>(
|
|
NAME_TSCONFIG_JSON,
|
|
BASE_TSCONFIG_JSON,
|
|
'TypeScript configuration',
|
|
{
|
|
/**
|
|
* Merges the contents of the `include` property with any passed on the CLI. If neither exists,
|
|
* uses the default set of includes.
|
|
* @param content Parsed and/or scaffolded `tsconfig.json`
|
|
* @param opts Options specific to this task
|
|
* @returns `tsconfig.json` content with potentially-modified `include` prop
|
|
*/
|
|
transform: (content, {include}) => {
|
|
include = [...(content.include ?? include ?? [])];
|
|
if (_.isEmpty(include)) {
|
|
include = [...DEFAULT_INCLUDE];
|
|
}
|
|
return {
|
|
...content,
|
|
include: _.uniq(include),
|
|
};
|
|
},
|
|
deserialize: JSON5.parse,
|
|
serialize: stringifyJson5,
|
|
}
|
|
);
|
|
|
|
/**
|
|
* Function which scaffolds a `typedoc.json` file
|
|
*/
|
|
export const initTypeDocJson = createScaffoldTask<InitTypeDocOptions, TypeDocJson>(
|
|
NAME_TYPEDOC_JSON,
|
|
BASE_TYPEDOC_JSON,
|
|
'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
|
|
*/
|
|
export const initMkDocs = createScaffoldTask<InitMkDocsOptions, MkDocsYml>(
|
|
NAME_MKDOCS_YML,
|
|
BASE_MKDOCS_YML,
|
|
'MkDocs configuration',
|
|
{
|
|
deserialize: YAML.parse,
|
|
serialize: stringifyYaml,
|
|
transform: (content, opts, pkg) => {
|
|
let siteName = opts.siteName ?? content.site_name;
|
|
if (!siteName) {
|
|
siteName = pkg.name ?? '(no name)';
|
|
if (siteName) {
|
|
log.info('Using site name from package.json: %s', siteName);
|
|
}
|
|
}
|
|
let repoUrl: string | undefined = opts.repoUrl ?? content.repo_url;
|
|
if (!repoUrl) {
|
|
repoUrl = pkg.repository?.url;
|
|
if (repoUrl) {
|
|
log.info('Using repo URL from package.json: %s', repoUrl);
|
|
}
|
|
}
|
|
let repoName = opts.repoName ?? content.repo_name;
|
|
if (repoUrl && !repoName) {
|
|
let {pathname} = new URL(repoUrl);
|
|
pathname = pathname.slice(1);
|
|
const pathparts = pathname.split('/');
|
|
const owner = pathparts[0];
|
|
let repo = pathparts[1];
|
|
repo = repo.replace(/\.git$/, '');
|
|
repoName = [owner, repo].join('/');
|
|
if (repoName) {
|
|
log.info('Using repo name from package.json: %s', repoName);
|
|
}
|
|
}
|
|
let siteDescription = opts.siteDescription ?? content.site_description;
|
|
if (!siteDescription) {
|
|
siteDescription = pkg.description;
|
|
if (siteDescription) {
|
|
log.info('Using site description URL from package.json: %s', siteDescription);
|
|
}
|
|
}
|
|
return {
|
|
...content,
|
|
site_name: siteName,
|
|
repo_url: repoUrl,
|
|
repo_name: repoName,
|
|
site_description: siteDescription,
|
|
};
|
|
},
|
|
}
|
|
);
|
|
|
|
/**
|
|
* Installs Python dependencies
|
|
* @param opts Options
|
|
*/
|
|
export async function initPython({
|
|
pythonPath,
|
|
dryRun = false,
|
|
upgrade = false,
|
|
}: InitPythonOptions = {}): Promise<void> {
|
|
pythonPath = pythonPath ?? (await findPython()) ?? NAME_PYTHON;
|
|
|
|
const args = ['-m', 'pip', 'install', '-r', REQUIREMENTS_TXT_PATH];
|
|
if (upgrade) {
|
|
args.push('--upgrade');
|
|
}
|
|
if (dryRun) {
|
|
dryRunLog.info('Would execute command: %s %s', pythonPath, args.join(' '));
|
|
} else {
|
|
log.debug('Executing command: %s %s', pythonPath, args.join(' '));
|
|
log.info('Installing Python dependencies...');
|
|
try {
|
|
const result = await exec(pythonPath, args, {shell: true});
|
|
const {code, stdout} = result;
|
|
if (code !== 0) {
|
|
throw new DocutilsError(`Could not install Python dependencies. Reason: ${stdout}`);
|
|
}
|
|
} catch (err) {
|
|
throw new DocutilsError(
|
|
`Could not install Python dependencies. Reason: ${(err as Error).message}`
|
|
);
|
|
}
|
|
}
|
|
log.success('Installed Python dependencies (or dependencies already installed)');
|
|
}
|
|
|
|
/**
|
|
* Options for {@linkcode initMkDocs}
|
|
*/
|
|
export interface InitMkDocsOptions extends ScaffoldTaskOptions {
|
|
copyright?: string;
|
|
repoName?: string;
|
|
repoUrl?: string;
|
|
siteDescription?: string;
|
|
siteName?: string;
|
|
}
|
|
|
|
/**
|
|
* Main handler for `init` command.
|
|
*
|
|
* This runs tasks in serial; it _could_ run in parallel, but it has deleterious effects upon
|
|
* console output which would need mitigation.
|
|
*/
|
|
export async function init({
|
|
typescript,
|
|
typedoc,
|
|
python,
|
|
tsconfigJson: tsconfigJsonPath,
|
|
packageJson: packageJsonPath,
|
|
overwrite,
|
|
include,
|
|
mkdocs,
|
|
mkdocsYml: mkdocsYmlPath,
|
|
siteName,
|
|
repoName,
|
|
repoUrl,
|
|
copyright,
|
|
dryRun,
|
|
cwd,
|
|
pythonPath,
|
|
upgrade,
|
|
typedocJson: typeDocJsonPath,
|
|
entryPoint: typeDocEntryPoint,
|
|
}: InitOptions = {}): Promise<void> {
|
|
if (!typescript && typedoc) {
|
|
log.warn(
|
|
'Initialization of tsconfig.json disabled. TypeDoc requires a tsconfig.json; please ensure it exists'
|
|
);
|
|
}
|
|
|
|
if (typescript && !upgrade) {
|
|
await initTsConfigJson({
|
|
dest: tsconfigJsonPath,
|
|
packageJson: packageJsonPath,
|
|
overwrite,
|
|
include,
|
|
dryRun,
|
|
cwd,
|
|
});
|
|
}
|
|
|
|
if (typedoc && !upgrade) {
|
|
await initTypeDocJson({
|
|
dest: typeDocJsonPath,
|
|
packageJson: packageJsonPath,
|
|
overwrite,
|
|
dryRun,
|
|
cwd,
|
|
});
|
|
await initTypeDocPkgJson({
|
|
packageJson: packageJsonPath,
|
|
overwrite: true, // <-- always overwrite
|
|
dryRun,
|
|
cwd,
|
|
typeDocEntryPoint,
|
|
});
|
|
}
|
|
|
|
if (python) {
|
|
await initPython({pythonPath, dryRun, upgrade});
|
|
}
|
|
|
|
if (mkdocs && !upgrade) {
|
|
await initMkDocs({
|
|
dest: mkdocsYmlPath,
|
|
cwd,
|
|
siteName,
|
|
repoUrl,
|
|
repoName,
|
|
copyright,
|
|
packageJson: packageJsonPath,
|
|
overwrite,
|
|
dryRun,
|
|
});
|
|
}
|
|
}
|
|
|
|
export interface InitTypeDocOptions extends ScaffoldTaskOptions {
|
|
typeDocEntryPoint?: string;
|
|
}
|
|
|
|
export interface InitTsConfigOptions extends ScaffoldTaskOptions {
|
|
/**
|
|
* List of source files (globs supported); typically `src` or `lib`
|
|
*/
|
|
include?: string[];
|
|
}
|
|
export interface InitPythonOptions extends ScaffoldTaskOptions {
|
|
/**
|
|
* Path to `python` (v3.x) executable
|
|
*/
|
|
pythonPath?: string;
|
|
|
|
/**
|
|
* If true, upgrade only
|
|
*/
|
|
upgrade?: boolean;
|
|
}
|
|
|
|
/**
|
|
* Options for `init` command handler
|
|
*
|
|
* The props of the various "path" options are rewritten as `dest` for the scaffold tasks functions.
|
|
*/
|
|
export type InitOptions = Simplify<
|
|
Omit<InitPythonOptions & InitTsConfigOptions & InitTypeDocOptions & InitMkDocsOptions, 'dest'> & {
|
|
/**
|
|
* If `true` will initialize a `tsconfig.json` file
|
|
*/
|
|
typescript?: boolean;
|
|
/**
|
|
* If `true` will initialize a `typedoc.json` file
|
|
*/
|
|
typedoc?: boolean;
|
|
/**
|
|
* If `true` will install Python deps
|
|
*/
|
|
python?: boolean;
|
|
/**
|
|
* If `true` will initialize a `mkdocs.yml` file
|
|
*/
|
|
mkdocs?: boolean;
|
|
/**
|
|
* Path to new or existing `typedoc.json` file
|
|
*/
|
|
typedocJson?: string;
|
|
/**
|
|
* Path to new or existing `tsconfig.json` file
|
|
*/
|
|
tsconfigJson?: string;
|
|
/**
|
|
* Path to existing `package.json` file
|
|
*/
|
|
packageJson?: string;
|
|
/**
|
|
* Path to new or existing `mkdocs.yml` file
|
|
*/
|
|
mkdocsYml?: string;
|
|
|
|
/**
|
|
* If `true`, upgrade only
|
|
*/
|
|
upgrade?: boolean;
|
|
|
|
/**
|
|
* Path to entry point of extension (source; not "main" field)
|
|
*/
|
|
entryPoint?: string;
|
|
}
|
|
>;
|