Skip to content

Commit

Permalink
Merge pull request #242 from lifion/bugfix/respect-locked-versions
Browse files Browse the repository at this point in the history
Add fix to properly handle versions with ~ and locked versions
  • Loading branch information
jennyEckstein committed Dec 13, 2021
2 parents fa5a8ba + 7e41f3b commit 670b910
Show file tree
Hide file tree
Showing 6 changed files with 941 additions and 917 deletions.
15 changes: 14 additions & 1 deletion README.md
Expand Up @@ -38,6 +38,7 @@ verifyDeps({ dir: './path-to/project-directory' })
* [~getInstalledVersion(currentDir, name, logger)](#module_lifion-verify-deps--verifyDeps..getInstalledVersion) ⇒ <code>string</code> \| <code>null</code>
* [~pushPkgs(params)](#module_lifion-verify-deps--verifyDeps..pushPkgs) ⇒ <code>Array.&lt;Promise.&lt;PackageStatus&gt;&gt;</code>
* [~getPkgIds(filteredPkgs)](#module_lifion-verify-deps--verifyDeps..getPkgIds) ⇒ <code>string</code>
* [~removeLockedDependencies(deps)](#module_lifion-verify-deps--verifyDeps..removeLockedDependencies) ⇒ <code>Object.&lt;string, string&gt;</code> \| <code>Object</code>
<a name="exp_module_lifion-verify-deps--verifyDeps"></a>
Expand Down Expand Up @@ -146,7 +147,7 @@ Builds list of packages to update.
| Param | Type | Description |
| --- | --- | --- |
| params | <code>Object</code> | Object with parameters. |
| params.deps | <code>\*</code> | List of dependencies. |
| params.deps | <code>Object.&lt;string, string&gt;</code> | List of dependencies. |
| params.dir | <code>string</code> | Directory location. |
| params.logger | [<code>Logger</code>](#Logger) | Logging tool. |
| params.type | <code>string</code> | Type of dependency. |
Expand All @@ -163,6 +164,18 @@ Formats package name for installation.
| --- | --- | --- |
| filteredPkgs | [<code>Array.&lt;PackageStatus&gt;</code>](#PackageStatus) | Package properties. |
<a name="module_lifion-verify-deps--verifyDeps..removeLockedDependencies"></a>
#### verifyDeps~removeLockedDependencies(deps) ⇒ <code>Object.&lt;string, string&gt;</code> \| <code>Object</code>
Filters out dependencies with locked versions.
**Kind**: inner method of [<code>verifyDeps</code>](#exp_module_lifion-verify-deps--verifyDeps)
**Returns**: <code>Object.&lt;string, string&gt;</code> \| <code>Object</code> - List of dependencies excluding locked semver versions.
| Param | Type | Description |
| --- | --- | --- |
| deps | <code>Object.&lt;string, string&gt;</code> | List of dependencies. |
## License
Expand Down
56 changes: 37 additions & 19 deletions lib/index.js
Expand Up @@ -5,15 +5,14 @@
'use strict';

const chalk = require('chalk');
const path = require('path');
const semver = require('semver');
const path = require('path');
const semverPrerelease = require('semver/functions/prerelease');
const { exec } = require('child_process');
const { promisify } = require('util');
const validatePackageName = require('validate-npm-package-name');

const { execp, requireModule } = require('./utils');

const { blue, bold, green, red } = chalk;
const execAsync = promisify(exec);

/**
* Validates package name.
Expand All @@ -29,6 +28,7 @@ function isValidNpmPackageName(name) {
}

/**
*
* Gets available versions for provided package name.
*
* @param {string} name - Package name.
Expand All @@ -38,13 +38,13 @@ function isValidNpmPackageName(name) {
async function getLatestVersions(name) {
isValidNpmPackageName(name);
try {
const { stdout } = await execAsync(`npm view ${name} versions --json`);
const { stdout } = await execp(`npm view ${name} versions --json`);
const versions = JSON.parse(stdout);
return Array.isArray(versions) ? versions : [versions];
} catch (err) {
const message =
err instanceof SyntaxError
? `Failed to parse output from NPM view - ${err.toString()}`
? `Failed to parse output from NPM view when getting versions - ${err.toString()}`
: `Error getting latest versions - ${err}`;
throw new Error(message);
}
Expand All @@ -60,13 +60,13 @@ async function getLatestVersions(name) {
async function getLatestTag(name) {
isValidNpmPackageName(name);
try {
const { stdout } = await execAsync(`npm view ${name} dist-tags --json`);
const { stdout } = await execp(`npm view ${name} dist-tags --json`);
const { latest } = JSON.parse(stdout);
return latest;
} catch (err) {
const message =
err instanceof SyntaxError
? `Failed to parse output from NPM view - ${err.toString()}`
? `Failed to parse output from NPM view when getting tags - ${err.toString()}`
: `Error getting latest tag - ${err}`;
throw new Error(message);
}
Expand All @@ -84,7 +84,6 @@ async function getLatestVersion(name, wanted) {
const versions = await getLatestVersions(name);
const latest = await getLatestTag(name);
let applicableVersions = versions.filter((i) => semver.satisfies(i, wanted));

const prereleases = [];
if (semverPrerelease(wanted.slice(1))) {
for (const version of versions) {
Expand Down Expand Up @@ -122,7 +121,8 @@ async function getLatestVersion(name, wanted) {
*/
function getInstalledVersion(currentDir, name, logger) {
try {
return require(path.join(currentDir, 'node_modules', name, 'package.json')).version;
const { version } = requireModule(path.join(currentDir, 'node_modules', name, 'package.json'));
return version;
} catch (err) {
logger.info(`Error getting a list of installed modules from package.json - ${err}`);
return null;
Expand All @@ -133,16 +133,15 @@ function getInstalledVersion(currentDir, name, logger) {
* Builds list of packages to update.
*
* @param {Object} params - Object with parameters.
* @param {*} params.deps - List of dependencies.
* @param {Object.<string, string>} params.deps - List of dependencies.
* @param {string} params.dir - Directory location.
* @param {Logger} params.logger - Logging tool.
* @param {string} params.type - Type of dependency.
* @returns {Array<Promise<PackageStatus>>} - NPM package state.
*/
function pushPkgs({ dir, logger, deps = {}, type }) {
function pushPkgs({ deps, dir, logger, type }) {
return Object.keys(deps).map(async (name) => {
let wanted = deps[name];
if (!wanted.startsWith('^')) wanted = `^${wanted}`;
const wanted = deps[name];
const installed = getInstalledVersion(dir, name, logger);
const latest = await getLatestVersion(name, wanted);
const wantedFixed = wanted.slice(1);
Expand Down Expand Up @@ -171,6 +170,25 @@ function getPkgIds(filteredPkgs) {
return filteredPkgs.map(({ latest, name }) => `${name}@${latest}`).join(' ');
}

/**
* Filters out dependencies with locked versions.
*
* @param {Object.<string, string>} deps - List of dependencies.
* @returns {Object.<string, string> | {}} List of dependencies excluding locked semver versions.
*/
function removeLockedDependencies(deps) {
let depsToUpgrade = {};

for (const name of Object.keys(deps)) {
const version = deps[name];
const firstChar = version.charAt(0);
if (firstChar === '^' || firstChar === '~') {
depsToUpgrade = { ...depsToUpgrade, [name]: version };
}
}
return depsToUpgrade;
}

/**
* Verifies the dependencies listed in the package.json of the given directory.
*
Expand All @@ -181,12 +199,12 @@ function getPkgIds(filteredPkgs) {
* @param {Logger} [options.logger] - A logger instance, with a similar API as the console object.
*/
async function verifyDeps({ autoUpgrade = false, dir = '', logger = console } = {}) {
const { dependencies, devDependencies } = require(path.join(dir, 'package.json'));
const { dependencies = {}, devDependencies = {} } = requireModule(path.join(dir, 'package.json'));
logger.info(blue('Verifying dependencies…\n'));

const pkgs = await Promise.all([
...pushPkgs({ deps: dependencies, dir, logger, type: 'prod' }),
...pushPkgs({ deps: devDependencies, dir, logger, type: 'dev' })
...pushPkgs({ deps: removeLockedDependencies(dependencies), dir, logger, type: 'prod' }),
...pushPkgs({ deps: removeLockedDependencies(devDependencies), dir, logger, type: 'dev' })
]);
const toInstall = pkgs.filter(({ shouldBeInstalled }) => shouldBeInstalled);
if (toInstall.length > 0) {
Expand All @@ -203,8 +221,8 @@ async function verifyDeps({ autoUpgrade = false, dir = '', logger = console } =
if (autoUpgrade) {
logger.info('UPGRADING…');
logger.info(upgradePackages);
const prodResult = await execAsync(`npm i ${getPkgIds(prodPkgs)}`);
const devResult = await execAsync(`npm i -D ${getPkgIds(devPkgs)}`);
const prodResult = await execp(`npm i ${getPkgIds(prodPkgs)}`);
const devResult = await execp(`npm i -D ${getPkgIds(devPkgs)}`);
logger.info(`${green(`${bold('Upgraded dependencies:\n')}${prodResult.stdout}`)}`);
logger.info(`${green(`${bold('Upgraded development dependencies:\n')}${devResult.stdout}`)}`);
} else {
Expand Down

0 comments on commit 670b910

Please sign in to comment.