Skip to content

Commit

Permalink
Resolve to latest version when latest is less than most recent
Browse files Browse the repository at this point in the history
  • Loading branch information
jennyEckstein committed Jan 7, 2021
1 parent 6e59525 commit b0b94cf
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 2 deletions.
14 changes: 14 additions & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,24 @@ async function getLatestVersions(name) {
}
}

async function getLatestTag(name) {
const { stdout } = await execAsync(`npm view ${name} dist-tags --json`);
try {
return JSON.parse(stdout);
} catch (err) {
throw new Error(`Failed to parse output from NPM view - ${err.toString()}`);
}
}

async function getLatestVersion(name, wanted) {
const versions = await getLatestVersions(name);
const { latest } = await getLatestTag(name);
const applicableVersions = versions.filter(i => semver.satisfies(i, wanted));
applicableVersions.sort((a, b) => semver.rcompare(a, b));

if (latest && semver.lt(latest, applicableVersions[0])) {
return latest;
}
return applicableVersions[0];
}

Expand Down
57 changes: 55 additions & 2 deletions lib/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ jest.mock('chalk', () => ({
}));

const chance = new Chance();
const moduleNameRegex = new RegExp('npm view (.*) versions --json');
const moduleNameRegexVersions = new RegExp('npm view (.*) versions --json');
const moduleNameRegexTags = new RegExp('npm view (.*) dist-tags --json');
const pathString = './index.test.js';
const mockExecAsync = getMockExecAsync();
const mockExports = {};
Expand All @@ -34,7 +35,8 @@ describe('lib/index', () => {
let olderVersion;
let newerVersion;
const mock = command => {
const moduleName = command.match(moduleNameRegex)[1];
const result = command.match(moduleNameRegexVersions) || command.match(moduleNameRegexTags);
const moduleName = result[1];
const versions = [olderVersion];
if (moduleName === outdatedDep || moduleName === outdatedDevDep) versions.push(newerVersion);
return Promise.resolve({ stdout: JSON.stringify(versions) });
Expand Down Expand Up @@ -185,6 +187,25 @@ describe('lib/index', () => {
);
});

it('should show dependency install required if fetching tags does not return valid JSON output', async () => {
const invalidOutput = chance.word();
mockExecAsync
.mockImplementationOnce(() => ({ stdout: JSON.stringify(['1.1.1']) }))
.mockImplementationOnce(() => ({ stdout: JSON.stringify(['1.1.1']) }))
.mockImplementationOnce(() => ({ stdout: JSON.stringify(['1.1.1']) }))
.mockImplementationOnce(() => ({ stdout: JSON.stringify(['1.1.1']) }));
mockExecAsync.mockImplementation(() => ({ stdout: invalidOutput }));
let syntaxError;
try {
JSON.parse(invalidOutput);
} catch (err) {
syntaxError = err;
}
await expect(verifyDeps({ dir, logger })).rejects.toThrow(
`Failed to parse output from NPM view - ${syntaxError.toString()}`
);
});

it('should show dependency install required if latest module is installed but not reflected in package.json', async () => {
mockExports.dependencies[outdatedDep] = `^${newerVersion}`;
mockExports.devDependencies[outdatedDevDep] = `^${newerVersion}`;
Expand Down Expand Up @@ -257,6 +278,34 @@ describe('lib/index', () => {
console.info = consoleInfo;
});

it('should update to version aliased as latest when aliased latest is less that most recent published version', async () => {
mockExports.dependencies = { foo1: '1.2.3' };
mockExports.devDependencies = { fooDev1: '1.2.3' };

mockExecAsync
.mockImplementationOnce(() => Promise.resolve({ stdout: JSON.stringify(['1.2.4', '1.2.5']) }))
.mockImplementationOnce(() => Promise.resolve({ stdout: JSON.stringify(['1.2.4', '1.2.5']) }))
.mockImplementationOnce(() =>
Promise.resolve({ stdout: JSON.stringify({ latest: '1.2.4' }) })
)
.mockImplementationOnce(() =>
Promise.resolve({ stdout: JSON.stringify({ latest: '1.2.4' }) })
)
.mockImplementationOnce(() => Promise.resolve({ stdout: JSON.stringify(['1.2.4']) }))
.mockImplementationOnce(() => Promise.resolve({ stdout: JSON.stringify(['1.2.4']) }));

await verifyDeps({ autoUpgrade: true, dir, logger });

expect(logger.info).toHaveBeenCalledTimes(7);
expect(logger.info).toHaveBeenNthCalledWith(1, 'Verifying dependencies…\n');
expect(logger.info).toHaveBeenNthCalledWith(2, `foo1 is outdated: 1.2.3 → 1.2.4`);
expect(logger.info).toHaveBeenNthCalledWith(3, `fooDev1 is outdated: 1.2.3 → 1.2.4`);
expect(logger.info).toHaveBeenNthCalledWith(4, 'UPGRADING…');
expect(logger.info).toHaveBeenNthCalledWith(5, `npm i foo1@1.2.4 \nnpm i -D fooDev1@1.2.4 `);
expect(logger.info).toHaveBeenNthCalledWith(6, `Upgraded dependencies:\n["1.2.4"]`);
expect(logger.info).toHaveBeenNthCalledWith(7, `Upgraded development dependencies:\n["1.2.4"]`);
});

test('autoUpgrade modules', async () => {
const mock2 = command => {
const moduleName = command.match('npm i (.*)')[1];
Expand All @@ -266,6 +315,10 @@ describe('lib/index', () => {
};

mockExecAsync
.mockImplementationOnce(mock)
.mockImplementationOnce(mock)
.mockImplementationOnce(mock)
.mockImplementationOnce(mock)
.mockImplementationOnce(mock)
.mockImplementationOnce(mock)
.mockImplementationOnce(mock)
Expand Down

0 comments on commit b0b94cf

Please sign in to comment.