diff --git a/package.json b/package.json index 0a1041d83..434df741a 100644 --- a/package.json +++ b/package.json @@ -86,6 +86,7 @@ "@types/gulp": "4.0.9", "@types/json-schema": "7.0.9", "@types/mocha": "9.0.0", + "@types/node": "16.11.7", "@types/sinon": "10.0.6", "@types/sinon-chai": "3.2.5", "@types/through2": "2.0.36", diff --git a/packages/appium/lib/config-file.js b/packages/appium/lib/config-file.js index d0e2c3981..73ee82f99 100644 --- a/packages/appium/lib/config-file.js +++ b/packages/appium/lib/config-file.js @@ -4,18 +4,13 @@ import betterAjvErrors from '@sidvind/better-ajv-errors'; import { lilconfig } from 'lilconfig'; import _ from 'lodash'; import yaml from 'yaml'; -import log from './logger'; -import { - getSchema, - validate -} from './schema/schema'; +import { getSchema, validate } from './schema/schema'; /** * lilconfig loader to handle `.yaml` files * @type {import('lilconfig').LoaderSync} */ function yamlLoader (filepath, content) { - log.debug(`Attempting to parse ${filepath} as YAML`); return yaml.parse(content); } @@ -33,7 +28,6 @@ const rawConfig = new Map(); * @type {import('lilconfig').LoaderSync} */ function jsonLoader (filepath, content) { - log.debug(`Attempting to parse ${filepath} as JSON`); rawConfig.set(filepath, content); return JSON.parse(content); } @@ -45,16 +39,17 @@ function jsonLoader (filepath, content) { * @returns {Promise} */ async function loadConfigFile (lc, filepath) { - log.debug(`Attempting to load config at filepath ${filepath}`); try { // removing "await" will cause any rejection to _not_ be caught in this block! return await lc.load(filepath); - } catch (err) { - if (err.code === 'ENOENT') { - err.message = `Config file not found at user-provided path: ${filepath}`; + } catch (/** @type {unknown} */err) { + if (/** @type {NodeJS.ErrnoException} */(err).code === 'ENOENT') { + /** @type {NodeJS.ErrnoException} */(err).message = `Config file not found at user-provided path: ${filepath}`; + throw err; } else if (err instanceof SyntaxError) { // generally invalid JSON err.message = `Config file at user-provided path ${filepath} is invalid:\n${err.message}`; + throw err; } throw err; } @@ -66,12 +61,7 @@ async function loadConfigFile (lc, filepath) { * @returns {Promise} */ async function searchConfigFile (lc) { - log.debug('No config file specified; searching...'); - const result = await lc.search(); - if (!result?.filepath) { - log.debug('Could not find an Appium server config file'); - } - return result; + return await lc.search(); } /** @@ -83,7 +73,7 @@ async function searchConfigFile (lc) { * was in JSON format. If present, it will associate line numbers with errors. * - If `errors` happens to be empty, this will throw. * @param {import('ajv').ErrorObject[]} errors - Non-empty array of errors. Required. - * @param {import('./config-file').ReadConfigFileResult['config']|any} [config] - + * @param {ReadConfigFileResult['config']|any} [config] - * Configuration & metadata * @param {FormatConfigErrorsOptions} [opts] * @throws {TypeError} If `errors` is empty @@ -93,16 +83,10 @@ export function formatErrors (errors = [], config = {}, opts = {}) { if (errors && !errors.length) { throw new TypeError('Array of errors must be non-empty'); } - // cached from the JSON loader; will be `undefined` if not JSON - const json = opts.json; - const format = opts.pretty ?? true ? 'cli' : 'js'; - - return /** @type {string} */ ( - betterAjvErrors(getSchema(opts.schemaId), config, errors, { - json, - format, - }) - ); + return betterAjvErrors(getSchema(opts.schemaId), config, errors, { + json: opts.json, + format: 'cli', + }); } /** @@ -129,7 +113,6 @@ export async function readConfigFile (filepath, opts = {}) { : await searchConfigFile(lc); if (result && !result.isEmpty && result.filepath) { - log.debug(`Config file found at ${result.filepath}`); const {normalize = true, pretty = true} = opts; try { /** @type {ReadConfigFileResult} */ @@ -176,12 +159,10 @@ function normalizeConfig (config) { * @returns Normalized section of config */ const normalize = (config, section) => { - // @ts-ignore - const obj = /** @type {object} */ (_.get(config, section, config)); // section is allowed to be `undefined` + const obj = _.isUndefined(section) ? config : _.get(config, section, config); - const mappedObj = _.mapKeys( - obj, - (__, prop) => _.camelCase(schema.properties[prop]?.appiumCliDest ?? prop), + const mappedObj = _.mapKeys(obj, (__, prop) => + schema.properties[prop]?.appiumCliDest ?? _.camelCase(prop), ); return _.mapValues(mappedObj, (value, property) => { @@ -206,7 +187,7 @@ function normalizeConfig (config) { * @property {string} [filepath] - The path to the config file, if found * @property {boolean} [isEmpty] - If `true`, the config file exists but is empty * @property {AppiumConfig} [config] - The parsed configuration - * @property {string|import('@sidvind/better-ajv-errors').IOutputError[]} [reason] - Human-readable error messages and suggestions. If the `pretty` option is `true`, this will be a nice string to print. + * @property {string|betterAjvErrors.IOutputError[]} [reason] - Human-readable error messages and suggestions. If the `pretty` option is `true`, this will be a nice string to print. */ /** diff --git a/packages/appium/lib/schema/schema.js b/packages/appium/lib/schema/schema.js index 1c73ef6ce..e88f1bc7b 100644 --- a/packages/appium/lib/schema/schema.js +++ b/packages/appium/lib/schema/schema.js @@ -247,7 +247,6 @@ class AppiumSchema { * - Sets the {@link AppiumSchema._finalized _finalized} flag to `false` * * If you need to call {@link AppiumSchema.finalize} again, you'll want to call this first. - * @public * @returns {void} */ reset () { @@ -351,14 +350,14 @@ class AppiumSchema { * module's `toParserArgs`, which converts the finalized schema to parameters * used by `argparse`. * @throws If {@link AppiumSchema.finalize} has not been called yet. - * @returns {{schema: SchemaObject, argSpec: ArgSpec}[]} + * @returns {FlattenedSchema} */ flatten () { const schema = this.getSchema(); /** @type {{properties: SchemaObject, prefix: string[]}[]} */ const stack = [{properties: schema.properties, prefix: []}]; - /** @type {{schema: SchemaObject, argSpec: ArgSpec}[]} */ + /** @type {FlattenedSchema} */ const flattened = []; // this bit is a recursive algorithm rewritten as a for loop. @@ -569,3 +568,10 @@ export const {isAllowedSchemaFileExtension} = AppiumSchema; * A {@link SchemaObject} with `additionalProperties: false` * @typedef {SchemaObject & StrictProp} StrictSchemaObject */ + +/** + * A list of schemas associated with properties and their corresponding {@link ArgSpec} objects. + * + * Intermediate data structure used when converting the entire schema down to CLI arguments. + * @typedef {{schema: SchemaObject, argSpec: ArgSpec}[]} FlattenedSchema + */ diff --git a/packages/appium/test/config-file-specs.js b/packages/appium/test/config-file-specs.js index f3bf7f1d4..f08f6a344 100644 --- a/packages/appium/test/config-file-specs.js +++ b/packages/appium/test/config-file-specs.js @@ -350,19 +350,6 @@ describe('config-file', function () { }); }); - describe('when `opts.pretty` is `false`', function () { - it('should call `betterAjvErrors()` with option `format: "js"`', function () { - // @ts-ignore - configFileModule.formatErrors([{}], {}, {pretty: false}); - expect(mocks['@sidvind/better-ajv-errors']).to.have.been.calledWith( - schema.getSchema(), - {}, - [{}], - {format: 'js', json: undefined}, - ); - }); - }); - describe('when `opts.json` is a string', function () { it('should call `betterAjvErrors()` with option `json: opts.json`', function () { // @ts-ignore diff --git a/tsconfig.json b/tsconfig.json index b1b1c1eaa..384ca2077 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,7 +7,8 @@ "target": "es2015", "noEmit": true, "allowJs": true, - "allowSyntheticDefaultImports": true + "allowSyntheticDefaultImports": true, + "types": ["node", "mocha", "chai", "sinon-chai", "chai-as-promised"] }, "include": [ "packages/*/**/*.d.ts",