mirror of
https://github.com/appium/appium.git
synced 2026-02-20 18:30:11 -06:00
* chore(docutils): remove tsconfig validation * chore(docutils): remove ts validation * chore(docutils): remove npm validation * chore(docutils): remove more unused validation code * chore(docutils): remove obsolete validate CLI options * chore(docutils): remove ts/tsconfig usage * chore(docutils): remove unused var
275 lines
7.7 KiB
TypeScript
275 lines
7.7 KiB
TypeScript
/**
|
|
* Functions which touch the filesystem
|
|
* @module
|
|
*/
|
|
|
|
import {fs} from '@appium/support';
|
|
import _ from 'lodash';
|
|
import path from 'node:path';
|
|
import _pkgDir from 'pkg-dir';
|
|
import readPkg, {NormalizedPackageJson, PackageJson} from 'read-pkg';
|
|
import {JsonValue} from 'type-fest';
|
|
import YAML from 'yaml';
|
|
import {
|
|
MESSAGE_PYTHON_MISSING,
|
|
NAME_MIKE,
|
|
NAME_MKDOCS,
|
|
NAME_MKDOCS_YML,
|
|
NAME_PACKAGE_JSON,
|
|
NAME_PYTHON,
|
|
} from './constants';
|
|
import {DocutilsError} from './error';
|
|
import {getLogger} from './logger';
|
|
import {MkDocsYml} from './model';
|
|
import {exec} from 'teen_process';
|
|
|
|
const log = getLogger('fs');
|
|
|
|
/**
|
|
* Finds path to closest `package.json`
|
|
*
|
|
* Caches result
|
|
*/
|
|
const findPkgDir = _.memoize(_pkgDir);
|
|
|
|
/**
|
|
* Stringifies a thing into a YAML
|
|
* @param value Something to yamlify
|
|
* @returns Some nice YAML 4 u
|
|
*/
|
|
export const stringifyYaml: (value: JsonValue) => string = _.partialRight(
|
|
YAML.stringify,
|
|
{indent: 2},
|
|
undefined,
|
|
);
|
|
|
|
/**
|
|
* Pretty-stringifies a JSON value
|
|
* @param value Something to stringify
|
|
* @returns JSON string
|
|
*/
|
|
export const stringifyJson: (value: JsonValue) => string = _.partialRight(
|
|
JSON.stringify,
|
|
2,
|
|
undefined,
|
|
);
|
|
|
|
/**
|
|
* Reads a YAML file, parses it and caches the result
|
|
*/
|
|
const readYaml = _.memoize(async (filepath: string) =>
|
|
YAML.parse(await fs.readFile(filepath, 'utf8'), {
|
|
prettyErrors: false,
|
|
logLevel: 'silent',
|
|
}),
|
|
);
|
|
|
|
/**
|
|
* Finds a file from `cwd`. Searches up to the package root (dir containing `package.json`).
|
|
*
|
|
* @param filename Filename to look for
|
|
* @param cwd Dir it should be in
|
|
* @returns
|
|
*/
|
|
export async function findInPkgDir(
|
|
filename: string,
|
|
cwd = process.cwd(),
|
|
): Promise<string | undefined> {
|
|
const pkgDir = await findPkgDir(cwd);
|
|
if (!pkgDir) {
|
|
return;
|
|
}
|
|
return path.join(pkgDir, filename);
|
|
}
|
|
|
|
/**
|
|
* Finds an `mkdocs.yml`, expected to be a sibling of `package.json`
|
|
*
|
|
* Caches the result.
|
|
* @param cwd - Current working directory
|
|
* @returns Path to `mkdocs.yml`
|
|
*/
|
|
export const findMkDocsYml = _.memoize(_.partial(findInPkgDir, NAME_MKDOCS_YML));
|
|
|
|
/**
|
|
* Given a directory path, finds closest `package.json` and reads it.
|
|
* @param cwd - Current working directory
|
|
* @param normalize - Whether or not to normalize the result
|
|
* @returns A {@linkcode PackageJson} object if `normalize` is `false`, otherwise a {@linkcode NormalizedPackageJson} object
|
|
*/
|
|
async function _readPkgJson(
|
|
cwd: string,
|
|
normalize: true,
|
|
): Promise<{pkgPath: string; pkg: NormalizedPackageJson}>;
|
|
async function _readPkgJson(
|
|
cwd: string,
|
|
normalize?: false,
|
|
): Promise<{pkgPath: string; pkg: PackageJson}>;
|
|
async function _readPkgJson(
|
|
cwd: string,
|
|
normalize?: boolean,
|
|
): Promise<{pkgPath: string; pkg: PackageJson | NormalizedPackageJson}> {
|
|
const pkgDir = await findPkgDir(cwd);
|
|
if (!pkgDir) {
|
|
throw new DocutilsError(
|
|
`Could not find a ${NAME_PACKAGE_JSON} near ${cwd}; please create it before using this utility`,
|
|
);
|
|
}
|
|
const pkgPath = path.join(pkgDir, NAME_PACKAGE_JSON);
|
|
log.debug('Found `package.json` at %s', pkgPath);
|
|
if (normalize) {
|
|
const pkg = await readPkg({cwd: pkgDir, normalize});
|
|
return {pkg, pkgPath};
|
|
} else {
|
|
const pkg = await readPkg({cwd: pkgDir});
|
|
return {pkg, pkgPath};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Given a directory to start from, reads a `package.json` file and returns its path and contents
|
|
*/
|
|
export const readPackageJson = _.memoize(_readPkgJson);
|
|
|
|
/**
|
|
* Reads a JSON file and parses it
|
|
*/
|
|
export const readJson = _.memoize(
|
|
async <T extends JsonValue>(filepath: string): Promise<T> =>
|
|
JSON.parse(await fs.readFile(filepath, 'utf8')),
|
|
);
|
|
|
|
/**
|
|
* Writes contents to a file. Any JSON objects are stringified
|
|
* @param filepath - Path to file
|
|
* @param content - File contents
|
|
*/
|
|
export function writeFileString(filepath: string, content: JsonValue) {
|
|
const data: string = _.isString(content) ? content : JSON.stringify(content, undefined, 2);
|
|
return fs.writeFile(filepath, data, {
|
|
encoding: 'utf8',
|
|
});
|
|
}
|
|
|
|
type WhichFunction = (cmd: string, opts?: {nothrow: boolean}) => Promise<string|null>;
|
|
|
|
/**
|
|
* `which` with memoization
|
|
*/
|
|
const cachedWhich = _.memoize(fs.which as WhichFunction);
|
|
|
|
/**
|
|
* Finds `python` executable
|
|
*/
|
|
const whichPython = _.partial(cachedWhich, NAME_PYTHON, {nothrow: true});
|
|
|
|
/**
|
|
* Finds `python3` executable
|
|
*/
|
|
const whichPython3 = _.partial(cachedWhich, `${NAME_PYTHON}3`, {nothrow: true});
|
|
|
|
/**
|
|
* Check if `mkdocs` is installed
|
|
*/
|
|
export const isMkDocsInstalled = _.memoize(async (): Promise<boolean> => {
|
|
// see if it's in PATH
|
|
const mkDocsPath = await cachedWhich(NAME_MKDOCS, {nothrow: true});
|
|
if (mkDocsPath) {
|
|
return true;
|
|
}
|
|
// if it isn't, it should be invokable via `python -m`
|
|
const pythonPath = await findPython();
|
|
if (!pythonPath) {
|
|
return false;
|
|
}
|
|
try {
|
|
await exec(pythonPath, ['-m', NAME_MKDOCS]);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
});
|
|
|
|
/**
|
|
* `mike` cannot be invoked via `python -m`, so we need to find the script.
|
|
*/
|
|
export const findMike = _.partial(async () => {
|
|
// see if it's in PATH
|
|
let mikePath = await cachedWhich(NAME_MIKE, {nothrow: true});
|
|
if (mikePath) {
|
|
return mikePath;
|
|
}
|
|
// if it isn't, it may be in a user dir
|
|
const pythonPath = await findPython();
|
|
if (!pythonPath) {
|
|
return;
|
|
}
|
|
try {
|
|
// the user dir can be found this way.
|
|
// usually it's something like ~/.local
|
|
const {stdout} = await exec(pythonPath, ['-m', 'site', '--user-base']);
|
|
if (stdout) {
|
|
mikePath = path.join(stdout.trim(), 'bin', 'mike');
|
|
if (await fs.isExecutable(mikePath)) {
|
|
return mikePath;
|
|
}
|
|
}
|
|
} catch {}
|
|
});
|
|
|
|
/**
|
|
* Finds the `python3` or `python` executable in the user's `PATH`.
|
|
*
|
|
* `python3` is preferred over `python`, since the latter could be Python 2.
|
|
*/
|
|
export const findPython = _.memoize(
|
|
async (): Promise<string | null> => (await whichPython3()) ?? (await whichPython()),
|
|
);
|
|
|
|
/**
|
|
* Check if a path to Python exists, otherwise raise DocutilsError
|
|
*/
|
|
export async function requirePython(pythonPath?: string): Promise<string> {
|
|
const foundPythonPath = pythonPath ?? (await findPython());
|
|
if (!foundPythonPath) {
|
|
throw new DocutilsError(MESSAGE_PYTHON_MISSING);
|
|
}
|
|
return foundPythonPath;
|
|
}
|
|
|
|
/**
|
|
* Reads an `mkdocs.yml` file, merges inherited configs, and returns the result. The result is cached.
|
|
*
|
|
* **IMPORTANT**: The paths of `site_dir` and `docs_dir` are resolved to absolute paths, since they
|
|
* are expressed as relative paths, and each inherited config file can live in different paths.
|
|
* @param filepath Patgh to an `mkdocs.yml` file
|
|
* @returns Parsed `mkdocs.yml` file
|
|
*/
|
|
export const readMkDocsYml = _.memoize(
|
|
async (filepath: string, cwd = process.cwd()): Promise<MkDocsYml> => {
|
|
let mkDocsYml = (await readYaml(filepath)) as MkDocsYml;
|
|
if (mkDocsYml.site_dir) {
|
|
mkDocsYml.site_dir = path.resolve(cwd, path.dirname(filepath), mkDocsYml.site_dir);
|
|
}
|
|
if (mkDocsYml.INHERIT) {
|
|
let inheritPath: string | undefined = path.resolve(path.dirname(filepath), mkDocsYml.INHERIT);
|
|
while (inheritPath) {
|
|
const inheritYml = (await readYaml(inheritPath)) as MkDocsYml;
|
|
if (inheritYml.site_dir) {
|
|
inheritYml.site_dir = path.resolve(path.dirname(inheritPath), inheritYml.site_dir);
|
|
log.debug('Resolved site_dir to %s', inheritYml.site_dir);
|
|
}
|
|
if (inheritYml.docs_dir) {
|
|
inheritYml.docs_dir = path.resolve(path.dirname(inheritPath), inheritYml.docs_dir);
|
|
log.debug('Resolved docs_dir to %s', inheritYml.docs_dir);
|
|
}
|
|
mkDocsYml = _.defaultsDeep(mkDocsYml, inheritYml);
|
|
inheritPath = inheritYml.INHERIT
|
|
? path.resolve(path.dirname(inheritPath), inheritYml.INHERIT)
|
|
: undefined;
|
|
}
|
|
}
|
|
return mkDocsYml;
|
|
},
|
|
);
|