chore: unify package root detection & package.json reading

This replaces all of the ad-hoc ways to find the closest `package.json` file (usually for reading the `version` field) with utility functions in the `fs` module of `@appium/support`.

- add `fs.findRoot(<dir>)` - Finds the closest parent directory from `dir` containing a `package.json`
- add `fs.readPackageJsonFrom(<dir>)` - Like above, except reads and returns the parsed contents of `package.json`
- Remove `find-root` dep from `appium` and add it to `@appium/support` instead
- Leverage these functions where needed
- Remove unnecessary `pkgRoot` export in `@appium/doctor`
- `fs-specs.js`: fix some tests which make assumptions about CWD; remove a low-value test
This commit is contained in:
Christopher Hiller
2021-08-09 16:21:49 -07:00
parent 6c769a2b7c
commit 2ad262b775
10 changed files with 124 additions and 34 deletions

View File

@@ -4,6 +4,7 @@ import { ArgumentParser } from 'argparse';
import { sharedArgs, serverArgs, extensionArgs } from './args';
import { DRIVER_TYPE, PLUGIN_TYPE } from '../extension-config';
import { rootDir } from '../utils';
import { fs } from '@appium/support';
function makeDebugParser (parser) {
@@ -23,7 +24,7 @@ function getParser (debug = false) {
}
parser.add_argument('-v', '--version', {
action: 'version',
version: require(path.resolve(rootDir, 'package.json')).version
version: fs.readPackageJsonFrom(rootDir).version
});
const subParsers = parser.add_subparsers({dest: 'subcommand'});

View File

@@ -1,5 +1,5 @@
import _ from 'lodash';
import { mkdirp, system } from '@appium/support';
import { mkdirp, system, fs } from '@appium/support';
import axios from 'axios';
import { exec } from 'teen_process';
import { rootDir } from './utils';
@@ -10,12 +10,7 @@ import {
} from './cli/argparse-actions';
import findUp from 'find-up';
let npmPackage;
try {
npmPackage = require('../package.json');
} catch {
npmPackage = require('../../package.json');
}
const npmPackage = fs.readPackageJsonFrom(__dirname);
const APPIUM_VER = npmPackage.version;
const MIN_NODE_VERSION = npmPackage.engines.node;

View File

@@ -1,8 +1,8 @@
import _ from 'lodash';
import logger from './logger';
import { processCapabilities, PROTOCOLS } from '@appium/base-driver';
import findRoot from 'find-root';
import { parseJsonStringOrFile } from './cli/parser-helpers';
import { fs } from '@appium/support';
const W3C_APPIUM_PREFIX = 'appium';
@@ -229,7 +229,7 @@ function pullSettings (caps) {
return result;
}
const rootDir = findRoot(__dirname);
const rootDir = fs.findRoot(__dirname);
export {
inspectObject, parseCapsForInnerDriver, insertAppiumPrefixes, rootDir,

View File

@@ -61,7 +61,6 @@
"axios": "^0.21.0",
"bluebird": "3.x",
"continuation-local-storage": "3.x",
"find-root": "^1.1.0",
"find-up": "^5.0.0",
"lodash": "^4.17.11",
"longjohn": "^0.2.12",

View File

@@ -1,14 +1,9 @@
import 'colors';
import _ from 'lodash';
import log from './logger';
import { fs } from '@appium/support';
// for test compat
let version;
try {
version = require('../../package.json').version;
} catch {
version = require('../package.json').version;
}
const {version} = fs.readPackageJsonFrom(__dirname);
class FixSkippedError extends Error {
}

View File

@@ -1,5 +1,4 @@
import B from 'bluebird';
import path from 'path';
import _inquirer from 'inquirer';
import log from '../lib/logger';
import authorize from 'authorize-ios';
@@ -10,14 +9,6 @@ import { isFunction } from 'lodash';
// rename to make more sense
const authorizeIos = authorize;
// test support
let pkgRoot;
try {
pkgRoot = path.dirname(require.resolve('../package.json'));
} catch {
pkgRoot = path.dirname(require.resolve('../../package.json'));
}
function ok (message) {
return {ok: true, optional: false, message};
}
@@ -121,5 +112,5 @@ async function getNpmPackageInfo (packageName) {
return null;
}
export { pkgRoot, ok, nok, okOptional, nokOptional, inquirer, configureBinaryLog,
export { ok, nok, okOptional, nokOptional, inquirer, configureBinaryLog,
authorizeIos, resolveExecutablePath, getNpmPackageInfo, resetLog };

View File

@@ -1,6 +1,6 @@
// transpile:mocha
import { pkgRoot, configureBinaryLog, resetLog } from '../lib/utils';
import { configureBinaryLog, resetLog } from '../lib/utils';
import { fs } from '@appium/support';
import chai from 'chai';
import path from 'path';
@@ -11,14 +11,14 @@ chai.should();
describe('utils', function () {
it('fs.readFile', async function () {
(await fs.readFile(path.resolve(pkgRoot, 'test', 'fixtures',
(await fs.readFile(path.resolve(__dirname, 'fixtures',
'wow.txt'), 'utf8')).should.include('WOW');
});
it('fs.exists', async function () {
(await fs.exists(path.resolve(pkgRoot, 'test', 'fixtures',
(await fs.exists(path.resolve(__dirname, 'fixtures',
'wow.txt'))).should.be.ok;
(await fs.exists(path.resolve(pkgRoot, 'test', 'fixtures',
(await fs.exists(path.resolve(__dirname, 'fixtures',
'notwow.txt'))).should.not.be.ok;
});

View File

@@ -1,4 +1,6 @@
// jshint ignore: start
import _ from 'lodash';
import path from 'path';
import _fs from 'fs';
import rimraf from 'rimraf';
import ncp from 'ncp';
@@ -9,12 +11,14 @@ import glob from 'glob';
import crypto from 'crypto';
import klaw from 'klaw';
import sanitize from 'sanitize-filename';
import findRoot from 'find-root';
import { pluralize } from './util';
import log from './logger';
import Timer from './timing';
const mkdirAsync = B.promisify(_fs.mkdir);
const ncpAsync = B.promisify(ncp);
const findRootCached = _.memoize(findRoot);
const fs = {
async hasAccess (path) {
@@ -141,6 +145,39 @@ const fs = {
walker.destroy();
}
});
},
/**
* Reads the closest `package.json` file from absolute path `dir`.
* @param {string} dir - Directory to search from
* @throws {TypeError} If `dir` is not a nonempty string or relative path
* @throws {Error} If there were problems finding or reading a `package.json` file
* @returns {object} A parsed `package.json`
*/
readPackageJsonFrom (dir) {
const root = fs.findRoot(dir);
try {
return JSON.parse(_fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
} catch (err) {
err.message = `Failed to read a \`package.json\` from dir \`${dir}\`:\n\n${err.message}`;
throw err;
}
},
/**
* Finds the project root directory from `dir`.
* @param {string} dir - Directory to search from
* @throws {TypeError} If `dir` is not a nonempty string or relative path
* @throws {Error} If there were problems finding the project root
* @returns {string} The closeset parent dir containing `package.json`
*/
findRoot (dir) {
if (!dir || !path.isAbsolute(dir)) {
throw new TypeError('`findRoot()` must be provided a non-empty, absolute path');
}
const result = findRootCached(dir);
if (!result) {
throw new Error(`\`findRoot()\` could not find \`package.json\` from ${dir}`);
}
return result;
}
};

View File

@@ -33,6 +33,7 @@
"bluebird": "^3.5.1",
"bplist-creator": "^0",
"bplist-parser": "^0.x",
"find-root": "^1.1.0",
"form-data": "^4.0.0",
"get-stream": "^6.0.1",
"glob": "^7.1.7",

View File

@@ -4,7 +4,9 @@ import path from 'path';
import { exec } from 'teen_process';
import B from 'bluebird';
import _ from 'lodash';
import chaiAsPromised from 'chai-as-promised';
chai.use(chaiAsPromised);
const should = chai.should();
@@ -14,6 +16,7 @@ describe('fs', function () {
this.timeout(MOCHA_TIMEOUT);
const existingPath = path.resolve(__dirname, 'fs-specs.js');
it('should have expected methods', function () {
should.exist(fs.open);
should.exist(fs.close);
@@ -149,8 +152,8 @@ describe('fs', function () {
});
});
it('glob', async function () {
let glob = 'test/*-specs.js';
let tests = await fs.glob(glob);
let glob = '*-specs.js';
let tests = await fs.glob(glob, {cwd: __dirname});
tests.should.be.an('array');
tests.should.have.length.above(2);
});
@@ -195,4 +198,72 @@ describe('fs', function () {
_.isNil(filePath).should.be.true;
});
});
describe('findRoot()', function () {
describe('when not provided an argument', function () {
it('should throw', function () {
(() => fs.findRoot()).should.throw(TypeError);
});
});
describe('when provided a relative path', function () {
it('should throw', function () {
(() => fs.findRoot('./foo')).should.throw(TypeError);
});
});
describe('when provided an empty string', function () {
it('should throw', function () {
(() => fs.findRoot('')).should.throw(TypeError);
});
});
describe('when provided an absolute path', function () {
describe('when the path has a parent `package.json`', function () {
it('should locate the dir with the closest `package.json`', function () {
fs.findRoot(__dirname).should.be.a('string');
});
});
describe('when the path does not have a parent `package.json`', function () {
it('should throw', function () {
(() => fs.findRoot('/')).should.throw(Error);
});
});
});
});
describe('readPackageJsonFrom()', function () {
describe('when not provided an argument', function () {
it('should throw', function () {
(() => fs.readPackageJsonFrom()).should.throw(TypeError, /non-empty, absolute path/);
});
});
describe('when provided a relative path', function () {
it('should throw', function () {
(() => fs.readPackageJsonFrom('./foo')).should.throw(TypeError);
});
});
describe('when provided an empty string', function () {
it('should throw', function () {
(() => fs.readPackageJsonFrom('')).should.throw(TypeError);
});
});
describe('when provided an absolute path', function () {
describe('when the path does not have a parent `package.json`', function () {
it('should throw', function () {
(() => fs.readPackageJsonFrom('/')).should.throw(Error);
});
});
describe('when the path has a parent `package.json`', function () {
it('should read the `package.json` found in the root dir', function () {
fs.readPackageJsonFrom(__dirname).should.be.an('object');
});
});
});
});
});