Skip to content

Commit

Permalink
refactor: separate chosen checks from options
Browse files Browse the repository at this point in the history
  • Loading branch information
voxpelli committed Mar 11, 2024
1 parent 211018c commit a00867a
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 87 deletions.
4 changes: 2 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
/** @typedef {import('./lib/check-version-range.js').VersionRangeResult} VersionRangeResult */
/** @typedef {import('./lib/get-installed-data.js').PackageJsonLike} PackageJsonLike */
/** @typedef {import('./lib/get-installed-data.js').InstalledDependencies} InstalledDependencies */
/** @typedef {import('./lib/installed-check.js').InstalledCheckOptions} InstalledCheckOptions */
/** @typedef {import('./lib/installed-check.js').InstalledCheckResult} InstalledCheckResult */
/** @typedef {import('./lib/perform-installed-check.js').InstalledCheckOptions} InstalledCheckOptions */
/** @typedef {import('./lib/perform-installed-check.js').InstalledCheckResult} InstalledCheckResult */

export { checkVersionRange } from './lib/check-version-range.js';
export { getInstalledData } from './lib/get-installed-data.js';
Expand Down
8 changes: 4 additions & 4 deletions lib/check-version-range.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import { getStringValueByPath } from './utils.js';

/**
* @typedef VersionRangeOptions
* @property {boolean} [expectedInDependencies=false] When set a warning will be issued when the key is empty or not found in a dependency
* @property {boolean} [noDev=false] If set then dev dependencies won't be included in the check
* @property {string[]} [ignore] If set then the specified module names won't be included in the
* @property {boolean} [strict=false] Converts most warnings into failures
* @property {boolean|undefined} [expectedInDependencies=false] When set a warning will be issued when the key is empty or not found in a dependency
* @property {boolean|undefined} [noDev=false] If set then dev dependencies won't be included in the check
* @property {string[]|undefined} [ignore] If set then the specified module names won't be included in the
* @property {boolean|undefined} [strict=false] Converts most warnings into failures
*/

/** @typedef {VersionRangeItem & { packageNotes: Array<VersionRangeItem & { name: string }> }} VersionRangeResult */
Expand Down
30 changes: 6 additions & 24 deletions lib/installed-check.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,14 @@ import { getInstalledData } from './get-installed-data.js';
import { performInstalledCheck } from './perform-installed-check.js';

/**
* @typedef InstalledCheckResult
* @property {string[]} errors
* @property {string[]} warnings
* @param {import('./perform-installed-check.js').InstalledChecks[]} checks
* @param {import('./perform-installed-check.js').InstalledCheckOptions & { path?: string }} [options]
* @returns {Promise<import('./perform-installed-check.js').InstalledCheckResult>}
*/

/**
* @typedef InstalledCheckOptions
* @property {string|undefined} [path]
* @property {boolean|undefined} engineCheck
* @property {string[]|undefined} [engineIgnores]
* @property {boolean|undefined} [engineNoDev]
* @property {boolean} [strict]
* @property {boolean|undefined} versionCheck
*/

/**
* @throws {Error}
* @param {InstalledCheckOptions} options
* @returns {Promise<InstalledCheckResult>}
*/
export async function installedCheck (options) {
if (!options) throw new TypeError('Expected options to be set');

const { path = '.', ...checkOptions } = options;
export async function installedCheck (checks, options) {
const { path = '.', ...checkOptions } = options || {};

const { installedDependencies, mainPackage } = await getInstalledData(path);

return performInstalledCheck(mainPackage, installedDependencies, checkOptions);
return performInstalledCheck(checks, mainPackage, installedDependencies, checkOptions);
}
76 changes: 48 additions & 28 deletions lib/perform-installed-check.js
Original file line number Diff line number Diff line change
@@ -1,54 +1,74 @@
import { explainVariable, typedObjectKeys } from '@voxpelli/typed-utils';

import { checkEngineVersions } from './check-engine-versions.js';
import { checkPackageVersions } from './check-package-versions.js';

/**
* @throws {Error}
* @typedef InstalledCheckResult
* @property {string[]} errors
* @property {string[]} warnings
*/

/** @typedef {'engine' | 'version'} InstalledChecks */

/** @type {Record<InstalledChecks, true>} */
const checkTypeMap = {
'engine': true,
'version': true,
};

const checkTypes = typedObjectKeys(checkTypeMap);

/**
* @typedef InstalledCheckOptions
* @property {string[]|undefined} [ignores]
* @property {boolean|undefined} [noDev]
* @property {boolean|undefined} [strict]
*/

/**
* @param {InstalledChecks[]} checks
* @param {import('./get-installed-data.js').PackageJsonLike} mainPackage
* @param {import('./get-installed-data.js').InstalledDependencies} installedDependencies
* @param {Omit<import('./installed-check.js').InstalledCheckOptions, 'path'>} options
* @returns {Promise<import('./installed-check.js').InstalledCheckResult>}
* @param {InstalledCheckOptions} options
* @returns {Promise<InstalledCheckResult>}
*/
export async function performInstalledCheck (mainPackage, installedDependencies, options) {
if (!mainPackage) throw new TypeError('Expected mainPackage to be set');
if (!installedDependencies) throw new TypeError('Expected installedDependencies to be set');
if (!options) throw new TypeError('Expected options to be set');

const {
engineCheck = false,
engineIgnores = [],
engineNoDev = false,
strict = false,
versionCheck = false,
} = options;

if (!engineCheck && !versionCheck) {
throw new Error('Expected to run at least one check. Add engineCheck and/or versionCheck');
export async function performInstalledCheck (checks, mainPackage, installedDependencies, options) {
if (!checks || !Array.isArray(checks)) {
throw new TypeError('Expected a "checks" array, got: ' + explainVariable(checks));
}
if (!mainPackage || typeof mainPackage !== 'object') {
throw new TypeError('Expected a "mainPackage" object, got: ' + explainVariable(mainPackage));
}
if (!installedDependencies || typeof installedDependencies !== 'object') {
throw new TypeError('Expected a "installedDependencies" object, got: ' + explainVariable(installedDependencies));
}
if (!options || typeof options !== 'object') {
throw new TypeError('Expected a "options" object, got :' + explainVariable(options));
}

let hasCheck = false;
/** @type {string[]} */
let errors = [];
/** @type {string[]} */
let warnings = [];

const results = [
versionCheck && checkPackageVersions(mainPackage, installedDependencies),
engineCheck && checkEngineVersions(
mainPackage,
installedDependencies,
{
noDev: engineNoDev,
ignore: engineIgnores,
strict,
}
),
checks.includes('version') && checkPackageVersions(mainPackage, installedDependencies),
checks.includes('engine') && checkEngineVersions(mainPackage, installedDependencies, options),
];

for (const result of results) {
if (result) {
hasCheck = true;
errors = [...errors, ...result.errors];
warnings = [...warnings, ...result.warnings];
}
}

if (!hasCheck) {
throw new Error('Expected to run at least one check. "checks" should include at least one of: ' + checkTypes.join(', '));
}

return { errors, warnings };
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
},
"dependencies": {
"@voxpelli/semver-set": "^5.0.2",
"@voxpelli/typed-utils": "^1.4.1",
"@voxpelli/typed-utils": "^1.6.0",
"list-installed": "^4.2.1",
"pony-cause": "^2.1.10",
"read-pkg": "^9.0.1",
Expand Down
39 changes: 11 additions & 28 deletions test/installed-check.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,40 +17,33 @@ describe('installedCheck()', () => {
it('should error when no options', async () => {
// @ts-ignore
await installedCheck()
.should.be.rejectedWith('Expected options to be set');
.should.be.rejectedWith(TypeError, 'Expected a "checks" array, got: undefined');
});

it('should error when invalid options', async () => {
// @ts-ignore
await installedCheck({})
.should.be.rejectedWith('Expected to run at least one check. Add engineCheck and/or versionCheck');
await installedCheck([])
.should.be.rejectedWith(/Expected to run at least one check\. "checks" should include at least one of: engine,/);
});

it('should error on missing package.json file', async () => {
await installedCheck({
await installedCheck(['engine', 'version'], {
path: join(import.meta.url, 'fixtures/missing-package-json'),
engineCheck: true,
versionCheck: true,
})
.should.be.rejectedWith(/Failed to read package\.json/);
});

it('should error on inability to list installed modules', async () => {
await installedCheck({
await installedCheck(['engine', 'version'], {
path: join(import.meta.url, 'fixtures/missing-node-modules'),
engineCheck: true,
versionCheck: true,
})
.should.be.rejectedWith(/Failed to list installed modules/);
});
});

describe('functionality', () => {
it('should return an empty result on valid setup', async () => {
await installedCheck({
await installedCheck(['engine', 'version'], {
path: join(import.meta.url, 'fixtures/valid'),
engineCheck: true,
versionCheck: true,
})
.should.eventually.deep.equal({
errors: [],
Expand All @@ -59,10 +52,8 @@ describe('installedCheck()', () => {
});

it('should return an empty result on an aliased setup', async () => {
await installedCheck({
await installedCheck(['engine', 'version'], {
path: join(import.meta.url, 'fixtures/aliased'),
engineCheck: true,
versionCheck: true,
})
.should.eventually.deep.equal({
errors: [],
Expand All @@ -71,10 +62,8 @@ describe('installedCheck()', () => {
});

it('should return errors and warnings on invalid setup', async () => {
await installedCheck({
await installedCheck(['engine', 'version'], {
path: join(import.meta.url, 'fixtures/invalid'),
engineCheck: true,
versionCheck: true,
})
.should.eventually.deep.equal({
'errors': [
Expand Down Expand Up @@ -105,10 +94,8 @@ describe('installedCheck()', () => {
});

it('should check engine even when no target engines are set', async () => {
await installedCheck({
await installedCheck(['engine'], {
path: join(import.meta.url, 'fixtures/missing-engines'),
engineCheck: true,
versionCheck: false,
})
.should.eventually.deep.equal({
'errors': [
Expand All @@ -122,10 +109,8 @@ describe('installedCheck()', () => {
});

it('should not suggest an engine configuration when engines are incompatible', async () => {
await installedCheck({
await installedCheck(['engine'], {
path: join(import.meta.url, 'fixtures/incompatible-engines'),
engineCheck: true,
versionCheck: false,
})
.should.eventually.deep.equal({
'errors': [
Expand All @@ -137,10 +122,8 @@ describe('installedCheck()', () => {
});

it('should handle engine ranges', async () => {
await installedCheck({
await installedCheck(['engine'], {
path: join(import.meta.url, 'fixtures/engine-ranges'),
engineCheck: true,
versionCheck: false,
})
.should.eventually.deep.equal({
'errors': [],
Expand Down

0 comments on commit a00867a

Please sign in to comment.