feat(docutils): add support for deploying with mike

This is enabled via `appium-docs build --deploy`.  Instead of invoking `mkdocs` directly, `mike` will be invoked.

Implementation is in `lib/builder/deploy.ts`.  It's very similar to the MkDocs implementation in `lib/builder/site.ts`; just with new/different options.

I marked the `Mike` class as deprecated, since the new one is fancy
This commit is contained in:
Christopher Hiller
2023-02-06 13:56:57 -08:00
parent 5326cb1840
commit c9a82926a8
6 changed files with 363 additions and 18 deletions
+188
View File
@@ -0,0 +1,188 @@
/**
* Functions for running `mike`
*
* @module
*/
import {exec, SubProcess, TeenProcessExecOptions} from 'teen_process';
import {
DEFAULT_DEPLOY_BRANCH,
DEFAULT_DEPLOY_REMOTE,
DEFAULT_SERVE_HOST,
DEFAULT_SERVE_PORT,
NAME_BIN,
NAME_MKDOCS_YML,
} from '../constants';
import {DocutilsError} from '../error';
import {findMkDocsYml, whichMike} from '../fs';
import logger from '../logger';
import {argify, stopwatch, TeenProcessSubprocessStartOpts} from '../util';
const log = logger.withTag('builder:deploy');
/**
* Runs `mike serve`
* @param args Extra args to `mike build`
* @param opts Extra options for `teen_process.Subprocess.start`
* @param mikePath Path to `mike` executable
*/
async function doServe(
args: string[] = [],
{startDetector, detach, timeoutMs}: TeenProcessSubprocessStartOpts = {},
mikePath?: string
) {
mikePath = mikePath ?? (await whichMike());
const finalArgs = ['serve', ...args];
log.debug('Launching %s with args: %O', mikePath, finalArgs);
const proc = new SubProcess(mikePath, finalArgs);
return await proc.start(startDetector, detach, timeoutMs);
}
/**
* Runs `mike build`
* @param args Extra args to `mike build`
* @param opts Extra options to `teen_process.exec`
* @param mikePath Path to `mike` executable
*/
async function doDeploy(args: string[] = [], opts: TeenProcessExecOptions = {}, mikePath?: string) {
mikePath = mikePath ?? (await whichMike());
const finalArgs = ['deploy', ...args];
log.debug('Launching %s with args: %O', mikePath, finalArgs);
return await exec(mikePath, finalArgs, opts);
}
/**
* Runs `mike build` or `mike serve`
* @param opts
*/
export async function deploy({
mkDocsYml: mkDocsYmlPath,
cwd = process.cwd(),
serve = false,
push = false,
branch = DEFAULT_DEPLOY_BRANCH,
remote = DEFAULT_DEPLOY_REMOTE,
prefix,
message,
deployVersion,
alias,
rebase = true,
port = DEFAULT_SERVE_PORT,
host = DEFAULT_SERVE_HOST,
serveOpts,
execOpts,
}: DeployOpts = {}) {
const stop = stopwatch('deploy');
mkDocsYmlPath = mkDocsYmlPath ?? (await findMkDocsYml(cwd));
if (!mkDocsYmlPath) {
throw new DocutilsError(
`Could not find ${NAME_MKDOCS_YML} from ${cwd}; run "${NAME_BIN} init" to create it`
);
}
const mikeOpts = {
'config-file': mkDocsYmlPath,
push,
remote,
branch,
prefix,
message,
deployVersion,
alias,
rebase,
port,
host,
};
const mikeArgs = argify(mikeOpts);
if (serve) {
// unsure about how SIGHUP is handled here
await doServe(mikeArgs, serveOpts);
} else {
await doDeploy(mikeArgs, execOpts);
log.success('Mike 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
*/
prefix?: 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;
/**
* If `true`, rebase `branch` before pushing
*/
rebase?: boolean;
/**
* 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 teen_process.Subprocess.start}
*/
serveOpts?: TeenProcessSubprocessStartOpts;
}
+1
View File
@@ -1,2 +1,3 @@
export * from './deploy';
export * from './site';
export * from './reference';
+135 -17
View File
@@ -1,12 +1,16 @@
import {CommandModule, InferredOptionTypes, Options} from 'yargs';
import {buildReferenceDocs, buildSite} from '../../builder';
import {buildReferenceDocs, buildSite, deploy} from '../../builder';
import {NAME_BIN} from '../../constants';
import logger from '../../logger';
import {updateNav} from '../../nav';
import {stopwatch} from '../../util';
const log = logger.withTag('build');
const NAME_GROUP_BUILD = 'Build API:';
const NAME_GROUP_BUILD = 'Build Options:';
const NAME_GROUP_DEPLOY = 'Deployment Options:';
const NAME_GROUP_SERVE = 'Serve Options:';
const NAME_GROUP_BUILD_PATHS = 'Paths:';
const opts = {
reference: {
@@ -24,7 +28,7 @@ const opts = {
'site-dir': {
alias: 'd',
describe: 'HTML output directory',
group: NAME_GROUP_BUILD,
group: NAME_GROUP_BUILD_PATHS,
nargs: 1,
requiresArg: true,
type: 'string',
@@ -35,7 +39,7 @@ const opts = {
'package-json': {
defaultDescription: './package.json',
describe: 'Path to package.json',
group: NAME_GROUP_BUILD,
group: NAME_GROUP_BUILD_PATHS,
nargs: 1,
normalize: true,
requiresArg: true,
@@ -52,7 +56,16 @@ const opts = {
'tsconfig-json': {
defaultDescription: './tsconfig.json',
describe: 'Path to tsconfig.json',
group: NAME_GROUP_BUILD,
group: NAME_GROUP_BUILD_PATHS,
nargs: 1,
normalize: true,
requiresArg: true,
type: 'string',
},
'mkdocs-yml': {
defaultDescription: './mkdocs.yml',
description: 'Path to mkdocs.yml',
group: NAME_GROUP_BUILD_PATHS,
nargs: 1,
normalize: true,
requiresArg: true,
@@ -61,25 +74,116 @@ const opts = {
'typedoc-json': {
defaultDescription: './typedoc.json',
describe: 'Path to typedoc.json',
group: NAME_GROUP_BUILD,
group: NAME_GROUP_BUILD_PATHS,
nargs: 1,
normalize: true,
requiresArg: true,
type: 'string',
},
'reference-header': {
describe: 'Navigation header for API reference',
default: 'Reference',
all: {
describe: 'Output all reference docs (not just Appium comands)',
group: NAME_GROUP_BUILD,
implies: 'site',
type: 'boolean',
},
deploy: {
describe: 'Commit HTML output',
group: NAME_GROUP_DEPLOY,
type: 'boolean',
implies: 'site',
},
push: {
describe: 'Push after deploy',
group: NAME_GROUP_DEPLOY,
type: 'boolean',
implies: 'deploy',
},
branch: {
alias: 'b',
describe: 'Branch to commit to',
implies: 'deploy',
group: NAME_GROUP_DEPLOY,
type: 'string',
requiresArg: true,
nargs: 1,
defaultDescription: 'gh-pages',
},
remote: {
alias: 'r',
describe: 'Remote to push to',
implies: ['deploy', 'push'],
group: NAME_GROUP_DEPLOY,
type: 'string',
requiresArg: true,
nargs: 1,
defaultDescription: 'origin',
},
prefix: {
describe: 'Subdirectory within <branch> to commit to',
implies: ['deploy', 'branch'],
group: NAME_GROUP_DEPLOY,
type: 'string',
nargs: 1,
requiresArg: true,
type: 'string',
},
'no-reference-header': {
describe: 'Do not add a navigation header for API reference',
group: NAME_GROUP_BUILD,
message: {
alias: 'm',
describe: 'Commit message',
implies: 'deploy',
group: NAME_GROUP_DEPLOY,
type: 'string',
nargs: 1,
requiresArg: true,
},
'deploy-version': {
describe: 'Version (directory) to deploy build to',
implies: 'deploy',
group: NAME_GROUP_DEPLOY,
type: 'string',
nargs: 1,
requiresArg: true,
defaultDescription: '(derived from package.json)',
},
alias: {
describe: 'Alias for the build (e.g., "latest"); triggers alias update',
implies: 'deploy',
group: NAME_GROUP_DEPLOY,
type: 'string',
nargs: 1,
requiresArg: true,
defaultDescription: 'latest',
},
rebase: {
describe: 'Rebase <branch> with remote before deploy',
implies: ['deploy', 'branch', 'remote'],
group: NAME_GROUP_DEPLOY,
type: 'boolean',
},
serve: {
describe: 'Start development server',
group: NAME_GROUP_SERVE,
type: 'boolean',
},
port: {
alias: 'p',
describe: 'Development server port',
group: NAME_GROUP_SERVE,
type: 'number',
defaultDescription: '8000',
implies: 'serve',
nargs: 1,
requiresArg: true,
},
host: {
alias: 'h',
describe: 'Development server host',
group: NAME_GROUP_SERVE,
type: 'string',
nargs: 1,
requiresArg: true,
implies: 'serve',
defaultDescription: 'localhost',
},
} as const;
opts as Record<string, Options>;
@@ -88,7 +192,17 @@ type BuildOptions = InferredOptionTypes<typeof opts>;
const buildCommand: CommandModule<{}, BuildOptions> = {
command: 'build',
describe: 'Build Appium extension documentation',
builder: opts,
builder: (yargs) =>
yargs.options(opts).check((argv) => {
// either this method doesn't provide camel-cased props, or the types are wrong.
if (argv.deploy === true && argv['site-dir']) {
log.error(
`--site-dir is unsupported when running "${NAME_BIN} deploy"; use --prefix if needd, but remember that the default behavior is to deploy to the root of the branch (${argv.branch}) instead of a subdirectory`
);
return false;
}
return true;
}),
async handler(args) {
const stop = stopwatch('build');
log.debug('Build command called with args: %O', args);
@@ -98,12 +212,16 @@ const buildCommand: CommandModule<{}, BuildOptions> = {
'Cannot use both --no-site (--site=false) and --no-reference (--reference=false)'
);
}
if (args.site) {
if (args.reference) {
await buildReferenceDocs(args);
}
if (args.reference) {
if (args.site) {
await updateNav(args);
await buildSite(args);
if (args.deploy) {
await deploy(args);
} else {
await buildSite(args);
}
}
log.success('Done! (total: %dms)', stop());
},
+30 -1
View File
@@ -2,7 +2,6 @@
* Constants used across various modules in this package
* @module
*/
import {LogLevel} from 'consola';
import {readFileSync} from 'node:fs';
import {fs} from '@appium/support';
@@ -49,6 +48,11 @@ export const NAME_SCHEMA = '$schema';
*/
export const NAME_MKDOCS = 'mkdocs';
/**
* Name of the `mike` executable
*/
export const NAME_MIKE = 'mike';
/**
* Name of the `typedoc` executable
*/
@@ -109,6 +113,26 @@ export const REQUIREMENTS_TXT_PATH = path.join(PKG_ROOT_DIR, NAME_REQUIREMENTS_T
*/
export const DEFAULT_REL_TYPEDOC_OUT_PATH = path.join('docs', 'reference');
/**
* The default branch to deploy to
*/
export const DEFAULT_DEPLOY_BRANCH = 'gh-pages';
/**
* The default remote to push the deployed branch to
*/
export const DEFAULT_DEPLOY_REMOTE = 'origin';
/**
* The default port for serving docs
*/
export const DEFAULT_SERVE_PORT = 8000;
/**
* The default host for serving docs
*/
export const DEFAULT_SERVE_HOST = 'localhost';
/**
* Mapping of `@appium/docutils`' log levels to `consola` log levels
*/
@@ -119,3 +143,8 @@ export const LogLevelMap = {
info: LogLevel.Info,
debug: LogLevel.Debug,
} as const;
/**
* Default site nav header text
*/
export const DEFAULT_NAV_HEADER = 'Reference';
+6
View File
@@ -20,6 +20,7 @@ import {
NAME_MKDOCS,
NAME_NPM,
NAME_PYTHON,
NAME_MIKE,
} from './constants';
import {DocutilsError} from './error';
import {MkDocsYml} from './model';
@@ -217,6 +218,11 @@ export const whichNpm = _.partial(cachedWhich, NAME_NPM);
*/
export const whichPython = _.partial(cachedWhich, NAME_PYTHON);
/**
* Finds `mike` executable
*/
export const whichMike = _.partial(cachedWhich, NAME_MIKE);
/**
* Reads an `mkdocs.yml` file, merges inherited configs, and returns the result. The result is cached.
*
+3
View File
@@ -6,6 +6,9 @@ const DEFAULT_REMOTE = 'origin';
const DEFAULT_BRANCH = 'gh-pages';
const MIKE_VER_STRING = 'mike 1.';
/**
* @deprecated Use the `deploy` export from `@appium/docutils`
*/
export class Mike {
/** @type {string} */ remote;
/** @type {string} */ branch;