Files

127 lines
4.1 KiB
TypeScript

import {homedir} from 'node:os';
import path from 'node:path';
import * as semver from 'semver';
import {type NormalizedPackageJson, readPackage} from './internal';
import {memoize} from './util';
/**
* Path to the default `APPIUM_HOME` dir (`~/.appium`).
*/
export const DEFAULT_APPIUM_HOME: string = path.resolve(homedir(), '.appium');
/**
* Basename of extension manifest file.
*/
export const MANIFEST_BASENAME = 'extensions.yaml';
/**
* Relative path to extension manifest file from `APPIUM_HOME`.
*/
export const MANIFEST_RELATIVE_PATH = path.join('node_modules', '.cache', 'appium', MANIFEST_BASENAME);
/**
* Resolves `true` if an `appium` dependency can be found somewhere in the given `cwd`.
*/
export async function hasAppiumDependency(cwd: string): Promise<boolean> {
return Boolean(await findAppiumDependencyPackage(cwd));
}
/**
* Given `cwd`, use `npm` to find the closest package _or workspace root_, and return the path if the root depends upon `appium`.
*
* Looks at `dependencies` and `devDependencies` for `appium`.
*/
export const findAppiumDependencyPackage = memoize(async function findAppiumDependencyPackage(
cwd: string = process.cwd(),
acceptableVersionRange: string | semver.Range = '>=2.0.0-beta',
): Promise<string | undefined> {
const readPkg = async (root: string): Promise<string | undefined> => {
let pkg: NormalizedPackageJson | undefined;
try {
pkg = await readPackageInDir(root);
} catch {
return undefined;
}
if (!pkg) {
return undefined;
}
try {
const version = semver.minVersion(
String(pkg.dependencies?.appium ?? pkg.devDependencies?.appium ?? pkg.peerDependencies?.appium),
);
return version && semver.satisfies(version, acceptableVersionRange) ? root : undefined;
} catch {
return undefined;
}
};
let currentDir = path.resolve(cwd);
let isAtFsRoot = false;
while (!isAtFsRoot) {
const result = await readPkg(currentDir);
if (result) {
return result;
}
currentDir = path.dirname(currentDir);
isAtFsRoot = currentDir.length <= path.dirname(currentDir).length;
}
return undefined;
});
/**
* Read a `package.json` in dir `cwd`. If none is found, resolves with `undefined`.
* @deprecated Package.json helpers in `@appium/support` are deprecated and will be removed in the next major version.
* Read `package.json` locally instead (for example with `node:fs` and `JSON.parse`).
* @returns Parsed package data, or `undefined` when `package.json` is missing in `cwd`
*/
export const readPackageInDir = memoize(async function _readPackageInDir(
cwd: string,
): Promise<NormalizedPackageJson | undefined> {
try {
return await readPackage({cwd, normalize: true});
} catch (err) {
if (isMissingPackageJsonError(err)) {
return undefined;
}
throw err;
}
});
function isMissingPackageJsonError(err: unknown): boolean {
return err instanceof Error && 'code' in err && (err as NodeJS.ErrnoException).code === 'ENOENT';
}
/**
* Determines location of Appium's "home" dir
*
* - If `APPIUM_HOME` is set in the environment, use that
* - If we find a `package.json` in or above `cwd` and it has an `appium` dependency, use that.
*
* All returned paths will be absolute.
*/
export const resolveAppiumHome = memoize(async function _resolveAppiumHome(
cwd: string = process.cwd(),
): Promise<string> {
if (!path.isAbsolute(cwd)) {
throw new TypeError('`cwd` parameter must be an absolute path');
}
if (process.env.APPIUM_HOME) {
return path.resolve(cwd, process.env.APPIUM_HOME);
}
return (await findAppiumDependencyPackage(cwd)) ?? DEFAULT_APPIUM_HOME;
});
/**
* Figure out manifest path based on `appiumHome`.
*
* The assumption is that, if `appiumHome` has been provided, it was resolved via {@link resolveAppiumHome `resolveAppiumHome()`}! If unsure,
* don't pass a parameter and let `resolveAppiumHome()` handle it.
*/
export const resolveManifestPath = memoize(async function _resolveManifestPath(appiumHome?: string): Promise<string> {
return path.join(appiumHome ?? (await resolveAppiumHome()), MANIFEST_RELATIVE_PATH);
});