@appium/docutils
Version:
Documentation generation utilities for Appium and related projects
248 lines (226 loc) • 6.13 kB
text/typescript
/**
* Functions for running `mike`
*
* @module
*/
import _ from 'lodash';
import path from 'node:path';
import {exec, TeenProcessExecOptions} from 'teen_process';
import {
DEFAULT_DEPLOY_ALIAS_TYPE,
DEFAULT_DEPLOY_BRANCH,
DEFAULT_DEPLOY_REMOTE,
DEFAULT_SERVE_HOST,
DEFAULT_SERVE_PORT,
NAME_BIN,
NAME_MIKE,
NAME_MKDOCS_YML,
} from '../constants';
import {DocutilsError} from '../error';
import {findMike, findMkDocsYml, isMkDocsInstalled, readPackageJson, requirePython} from '../fs';
import {getLogger} from '../logger';
import {argify, spawnBackgroundProcess, SpawnBackgroundProcessOpts, stopwatch} from '../util';
const log = getLogger('builder:deploy');
/**
* Runs `mike serve`
* @param mikePath Path to `mike` executable
* @param args Extra args to `mike build`
* @param opts Extra options for `teen_process.Subprocess.start`
*/
async function doServe(
mikePath: string,
args: string[] = [],
opts: SpawnBackgroundProcessOpts = {},
) {
const finalArgs = ['serve', ...args];
log.debug('Executing %s via: %s %O', NAME_MIKE, mikePath, finalArgs);
return spawnBackgroundProcess(mikePath, finalArgs, opts);
}
/**
* Runs `mike build`
* @param mikePath Path to `mike` executable
* @param args Extra args to `mike build`
* @param opts Extra options to `teen_process.exec`
*/
async function doDeploy(mikePath: string, args: string[] = [], opts: TeenProcessExecOptions = {}) {
const finalArgs = ['deploy', ...args];
log.debug('Executing %s via: %s %O', NAME_MIKE, mikePath, finalArgs);
return await exec(mikePath, finalArgs, opts);
}
/**
* Derives a deployment version from `package.json`
* @param packageJsonPath Path to `package.json` if known
* @param cwd Current working directory
*/
async function findDeployVersion(packageJsonPath?: string, cwd = process.cwd()): Promise<string> {
const {pkg} = await readPackageJson(packageJsonPath ? path.dirname(packageJsonPath) : cwd, true);
const version = pkg.version;
if (!version) {
throw new DocutilsError(
'No "version" field found in package.json; please add one or specify a version to deploy',
);
}
// return MAJOR.MINOR as the version by default, if that is a thing we can extract, otherwise
// just return the version as is
const versionParts = version.split('.');
if (versionParts.length === 1) {
return version;
}
return `${versionParts[0]}.${versionParts[1]}`;
}
/**
* Runs `mike build` or `mike serve`
* @param opts Options
*/
export async function deploy({
mkdocsYml: mkDocsYmlPath,
packageJson: packageJsonPath,
deployVersion: version,
cwd = process.cwd(),
serve = false,
push = false,
branch = DEFAULT_DEPLOY_BRANCH,
remote = DEFAULT_DEPLOY_REMOTE,
deployPrefix,
message,
alias,
aliasType = DEFAULT_DEPLOY_ALIAS_TYPE,
port = DEFAULT_SERVE_PORT,
host = DEFAULT_SERVE_HOST,
serveOpts,
execOpts,
}: DeployOpts = {}) {
const stop = stopwatch('deploy');
await requirePython();
const mkdocsInstalled = await isMkDocsInstalled();
if (!mkdocsInstalled) {
throw new DocutilsError(`Could not find MkDocs executable; please run "${NAME_BIN} init"`);
}
mkDocsYmlPath = mkDocsYmlPath ?? (await findMkDocsYml(cwd));
if (!mkDocsYmlPath) {
throw new DocutilsError(
`Could not find ${NAME_MKDOCS_YML} from ${cwd}; please run "${NAME_BIN} init"`,
);
}
version = version ?? (await findDeployVersion(packageJsonPath, cwd));
// substitute %s in message with version
message = message?.replace('%s', version);
const mikeOpts = {
'config-file': mkDocsYmlPath,
push,
remote,
branch,
'deploy-prefix': deployPrefix,
message,
port,
host,
};
const mikePath = await findMike();
if (!mikePath) {
throw new DocutilsError(
`Could not find ${NAME_MIKE} executable; please run "${NAME_BIN} init"`,
);
}
if (serve) {
const mikeArgs = [
...argify(_.pickBy(mikeOpts, (value) => _.isNumber(value) || Boolean(value))),
];
stop(); // discard
// unsure about how SIGHUP is handled here
await doServe(mikePath, mikeArgs, serveOpts);
} else {
log.info('Deploying into branch %s', branch);
const mikeArgs = [
...argify(
_.omitBy(
mikeOpts,
(value, key) => _.includes(['port', 'host'], key) || (!_.isNumber(value) && !value),
),
),
];
if (alias) {
mikeArgs.push('--update-aliases', '--alias-type', aliasType);
mikeArgs.push(version, alias);
} else {
mikeArgs.push(version);
}
await doDeploy(mikePath, mikeArgs, execOpts);
log.success('Finished deployment into branch %s (%dms)', branch, stop());
}
}
/**
* Options for {@linkcode deploy}.
*/
export interface DeployOpts {
/**
* Path to `mike.yml`
*/
mkdocsYml?: string;
/**
* Current working directory
* @defaultValue `process.cwd()`
*/
cwd?: string;
/**
* Path to `package.json`
*
* Used to find `mike.yml` if unspecified.
*/
packageJson?: string;
/**
* If `true`, run `mike serve` instead of `mike build`
*/
serve?: boolean;
/**
* If `true`, push `branch` to `remote`
*/
push?: boolean;
/**
* Branch to commit to
* @defaultValue gh-pages
*/
branch?: string;
/**
* Remote to push to
* @defaultValue origin
*/
remote?: string;
/**
* Subdirectory within `branch` to deploy to
*/
deployPrefix?: string;
/**
* Commit message
*/
message?: string;
/**
* Version (dir) to deploy build to
*/
deployVersion?: string;
/**
* Alias for the build (e.g., `latest`); triggers alias update
*/
alias?: string;
/**
* The approach for creating build alias (`symlink`, `redirect` or `copy`)
*/
aliasType?: string;
/**
* Port to serve on
* @defaultValue 8000
*/
port?: number;
/**
* Host to serve on
* @defaultValue localhost
*/
host?: string;
/**
* Extra options for {@linkcode teen_process.exec}
*/
execOpts?: TeenProcessExecOptions;
/**
* Extra options for {@linkcode spawnBackgroundProcess}
*/
serveOpts?: SpawnBackgroundProcessOpts;
}