mirror of
https://github.com/appium/appium.git
synced 2026-10-25 18:21:19 +08:00
and not up the filesystem from the appium home dir just because someone happens to have a package.json or a node_modules dir up there somewhere
184 lines
6.1 KiB
JavaScript
184 lines
6.1 KiB
JavaScript
import path from 'path';
|
|
import semver from 'semver';
|
|
import { exec } from 'teen_process';
|
|
import { fs, mkdirp, util, system } from 'appium-support';
|
|
|
|
const INSTALL_LOCKFILE = '.appium.install.lock';
|
|
const LINK_LOCKFILE = '.appium.link.lock';
|
|
|
|
export default class NPM {
|
|
|
|
constructor (appiumHome) {
|
|
this.appiumHome = appiumHome;
|
|
}
|
|
|
|
async exec (cmd, args, opts, execOpts = {}) {
|
|
let { cwd, json, lockFile } = opts;
|
|
if (!cwd) {
|
|
cwd = this.appiumHome;
|
|
}
|
|
// ensure the directory we want to install inside of exists
|
|
await mkdirp(cwd);
|
|
|
|
// not only this, this directory needs a 'package.json' inside of it, otherwise, if any
|
|
// directory in the filesystem tree ABOVE cwd happens to have a package.json or a node_modules
|
|
// dir in it, NPM will install the module up there instead (silly NPM)
|
|
const dummyPkgJson = path.resolve(cwd, 'package.json');
|
|
if (!await fs.exists(dummyPkgJson)) {
|
|
await fs.writeFile(dummyPkgJson, '{}');
|
|
}
|
|
|
|
// make sure we perform the current operation in cwd
|
|
execOpts = {...execOpts, cwd};
|
|
|
|
args.unshift(cmd);
|
|
if (json) {
|
|
args.push('-json');
|
|
}
|
|
const npmCmd = system.isWindows() ? 'npm.cmd' : 'npm';
|
|
let runner = async () => await exec(npmCmd, args, execOpts);
|
|
if (lockFile) {
|
|
const acquireLock = util.getLockFileGuard(path.resolve(cwd, lockFile));
|
|
const _runner = runner;
|
|
runner = async () => await acquireLock(_runner);
|
|
}
|
|
const {stdout, stderr, code} = await runner();
|
|
const ret = {stdout, stderr, code, json: null};
|
|
|
|
if (json) {
|
|
// if possible, parse NPM's json output. During NPM install 3rd-party
|
|
// packages can write to stdout, so sometimes the json output can't be
|
|
// guaranteed to be parseable
|
|
try {
|
|
ret.json = JSON.parse(stdout);
|
|
} catch (ign) {}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
async getLatestVersion (pkg) {
|
|
return (await this.exec('view', [pkg, 'dist-tags'], {
|
|
json: true
|
|
})).json.latest;
|
|
}
|
|
|
|
async getLatestSafeUpgradeVersion (pkg, curVersion) {
|
|
const allVersions = (await this.exec('view', [pkg, 'versions'], {
|
|
json: true
|
|
})).json;
|
|
return this.getLatestSafeUpgradeFromVersions(curVersion, allVersions);
|
|
}
|
|
|
|
/**
|
|
* Given a current version and a list of all versions for a package, return the version which is
|
|
* the highest safely-upgradable version (meaning not crossing any major revision boundaries, and
|
|
* not including any alpha/beta/rc versions)
|
|
*
|
|
* @param {string} curVersion - the current version of a package
|
|
* @param {Array<string>} allVersions - a list of version strings
|
|
*
|
|
* @return {string|null} - the highest safely-upgradable version, or null if there isn't one
|
|
*/
|
|
getLatestSafeUpgradeFromVersions (curVersion, allVersions) {
|
|
let safeUpgradeVer = null;
|
|
const curSemver = semver.parse(curVersion);
|
|
if (curSemver === null) {
|
|
throw new Error(`Could not parse current version '${curVersion}'`);
|
|
}
|
|
for (const testVer of allVersions) {
|
|
const testSemver = semver.parse(testVer);
|
|
if (testSemver === null) {
|
|
throw new Error(`Could not parse version to test against: '${testVer}'`);
|
|
}
|
|
// if the test version is a prerelease, ignore it
|
|
if (testSemver.prerelease.length > 0) {
|
|
continue;
|
|
}
|
|
// if the current version is later than the test version, skip this test version
|
|
if (curSemver.compare(testSemver) === 1) {
|
|
continue;
|
|
}
|
|
// if the test version is newer, but crosses a major revision boundary, also skip it
|
|
if (testSemver.major > curSemver.major) {
|
|
continue;
|
|
}
|
|
// otherwise this version is safe to upgrade to. But there might be multiple ones of this
|
|
// kind, so keep iterating and keeping the highest
|
|
if (safeUpgradeVer === null || testSemver.compare(safeUpgradeVer) === 1) {
|
|
safeUpgradeVer = testSemver;
|
|
}
|
|
}
|
|
if (safeUpgradeVer) {
|
|
safeUpgradeVer = safeUpgradeVer.format();
|
|
}
|
|
return safeUpgradeVer;
|
|
}
|
|
|
|
async installPackage ({pkgDir, pkgName, pkgVer}) {
|
|
const res = await this.exec('install', [
|
|
'--no-save',
|
|
'--no-package-lock',
|
|
pkgVer ? `${pkgName}@${pkgVer}` : pkgName
|
|
], {
|
|
cwd: pkgDir,
|
|
json: true,
|
|
lockFile: INSTALL_LOCKFILE
|
|
});
|
|
|
|
if (res.json) {
|
|
// we parsed a valid json response, so if we got an error here, return that
|
|
// message straightaway
|
|
if (res.json.error) {
|
|
throw new Error(res.json.error);
|
|
}
|
|
}
|
|
|
|
// Now read package data from the installed package to return, and make sure
|
|
// everything got installed ok. Remember, pkgName might end up with a / in it due to an npm
|
|
// org, so if so, that will get correctly exploded into multiple directories, by path.resolve here
|
|
// (even on Windows!)
|
|
const pkgJson = path.resolve(pkgDir, 'node_modules', pkgName, 'package.json');
|
|
try {
|
|
return require(pkgJson);
|
|
} catch {
|
|
throw new Error('The package was not downloaded correctly; its package.json ' +
|
|
'did not exist or was unreadable. We looked for it at ' +
|
|
pkgJson);
|
|
}
|
|
}
|
|
|
|
async linkPackage (pkgPath) {
|
|
// from the path alone we don't know the npm package name, so we need to
|
|
// look in package.json
|
|
let pkgName;
|
|
try {
|
|
pkgName = require(path.resolve(pkgPath, 'package.json')).name;
|
|
} catch {
|
|
throw new Error('Could not find package.json inside the package path ' +
|
|
`provided: ${pkgPath}`);
|
|
}
|
|
|
|
const pkgHome = path.resolve(this.appiumHome, pkgName);
|
|
const res = await this.exec('link', [pkgPath], {cwd: pkgHome, lockFile: LINK_LOCKFILE});
|
|
if (res.json && res.json.error) {
|
|
throw new Error(res.json.error);
|
|
}
|
|
|
|
// now ensure it was linked to the correct place
|
|
try {
|
|
return require(path.resolve(pkgHome, 'node_modules', pkgName, 'package.json'));
|
|
} catch {
|
|
throw new Error('The package was not linked correctly; its package.json ' +
|
|
'did not exist or was unreadable');
|
|
}
|
|
}
|
|
|
|
async uninstallPackage (pkgDir, pkg) {
|
|
await this.exec('uninstall', [pkg], {
|
|
cwd: pkgDir,
|
|
lockFile: INSTALL_LOCKFILE
|
|
});
|
|
}
|
|
}
|