Skip to content

Commit 4f28493

Browse files
authored
refactor: replace libnpm with npm cli (#139)
* refactor: add npm version resolver * refactor: replace libnpm with npm cli execution * refactor: remove libnpm from project * chore: fix linting issues * chore: rebuild project
1 parent 3134e26 commit 4f28493

11 files changed

Lines changed: 142 additions & 1424 deletions

File tree

build/setup/index.js

Lines changed: 12 additions & 48 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

build/setup/index.map.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
"@actions/exec": "^1.1.0",
2727
"@actions/io": "^1.1.1",
2828
"@actions/tool-cache": "^1.7.1",
29-
"libnpm": "^3.0.1",
3029
"semver": "^7.3.5"
3130
},
3231
"devDependencies": {

src/actions/setup.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { addPath, getBooleanInput, getInput, group, info } from '@actions/core';
22

33
import { install } from '../install';
4+
import { resolveVersion } from '../packager';
45
import * as tools from '../tools';
56

67
// Auto-execute in GitHub actions
@@ -33,7 +34,7 @@ async function installCli(name: tools.PackageName): Promise<string | void> {
3334
return info(`Skipping installation of ${name}, \`${shortName}-version\` not provided.`);
3435
}
3536

36-
const version = await tools.resolveVersion(name, inputVersion);
37+
const version = await resolveVersion(name, inputVersion);
3738
const cache = getBooleanInput(`${shortName}-cache`);
3839

3940
try {

src/packager.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { exec } from '@actions/exec';
2+
3+
/**
4+
* Resolve a package with version range to an exact version.
5+
* This is useful to invalidate the cache _and_ using dist-tags or version ranges.
6+
* It executes `npm info` and parses the latest manifest.
7+
*/
8+
export async function resolveVersion(name: string, range: string): Promise<string> {
9+
let stdout = '';
10+
11+
try {
12+
await exec('npm', ['info', `${name}@${range}`, 'version', '--json'], {
13+
silent: true,
14+
listeners: {
15+
stdout(data) {
16+
stdout += data.toString();
17+
},
18+
},
19+
});
20+
} catch (error) {
21+
throw new Error(`Could not resolve version "${range}" of "${name}", reason:\n${error.message || error}`);
22+
}
23+
24+
// thanks npm, for returning a "x.x.x" json value...
25+
if (stdout.startsWith('"')) {
26+
stdout = `[${stdout}]`;
27+
}
28+
29+
return JSON.parse(stdout).at(-1);
30+
}

src/tools.ts

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ import * as core from '@actions/core';
22
import * as cli from '@actions/exec';
33
import semver from 'semver';
44

5-
// eslint-disable-next-line @typescript-eslint/no-var-requires
6-
const registry = require('libnpm');
5+
import { resolveVersion } from './packager';
76

87
export type PackageName = 'expo-cli' | 'eas-cli';
98

@@ -23,15 +22,6 @@ export function getBinaryName(name: PackageName, forWindows = false): string {
2322
return forWindows ? `${bin}.cmd` : bin;
2423
}
2524

26-
/**
27-
* Resolve the provided semver to exact version of `expo-cli`.
28-
* This uses the npm registry and accepts latest, dist-tags or version ranges.
29-
* It's used to determine the cached version of `expo-cli`.
30-
*/
31-
export async function resolveVersion(name: PackageName, version: string): Promise<string> {
32-
return (await registry.manifest(`${name}@${version}`)).version;
33-
}
34-
3525
/**
3626
* Authenticate with Expo using either the token or username/password method.
3727
* If both of them are set, token has priority.

tests/actions/setup.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import { getToolsMock, mockInput } from '../utils';
22

33
jest.mock('../../src/tools', () => getToolsMock());
4+
jest.mock('../../src/packager', () => ({
5+
async resolveVersion(_: string, version: string) {
6+
return version;
7+
},
8+
}));
49

510
import * as core from '@actions/core';
611

tests/packager.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { valid as validVersion } from 'semver';
2+
import { resolveVersion } from '../src/packager';
3+
4+
describe(resolveVersion, () => {
5+
it('resolves expo-cli@^2.0.0 to 2.21.2', async () => {
6+
await expect(resolveVersion('expo-cli', '^2.0.0')).resolves.toBe('2.21.2');
7+
});
8+
9+
it('resolves expo-cli@~3.15.0 to 3.15.5', async () => {
10+
await expect(resolveVersion('expo-cli', '~3.15.0')).resolves.toBe('3.15.5');
11+
});
12+
13+
it('resolves eas-cli@~0.33.0 to 0.33.1', async () => {
14+
await expect(resolveVersion('eas-cli', '~0.33.0')).resolves.toBe('0.33.1');
15+
});
16+
17+
it('resolves expo-cli@latest to a valid version', async () => {
18+
const version = await resolveVersion('expo-cli', 'latest');
19+
expect(validVersion(version)).not.toBeNull();
20+
});
21+
22+
it('rejects donotpublishthispackageoryouwillbefired with proper error', async () => {
23+
await expect(resolveVersion('donotpublishthispackageoryouwillbefired', 'latest')).rejects.toThrow(
24+
'Could not resolve version "latest" of "donotpublishthispackageoryouwillbefired"'
25+
);
26+
});
27+
});

tests/tools.test.ts

Lines changed: 15 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
const registry = { manifest: jest.fn() };
2-
jest.mock('libnpm', () => registry);
3-
41
import * as core from '@actions/core';
52
import * as cli from '@actions/exec';
63

74
import * as tools from '../src/tools';
5+
import * as packager from '../src/packager';
86
import * as utils from './utils';
97

108
describe(tools.getBinaryName, () => {
@@ -21,20 +19,6 @@ describe(tools.getBinaryName, () => {
2119
});
2220
});
2321

24-
describe(tools.resolveVersion, () => {
25-
it('fetches exact version of expo-cli', async () => {
26-
registry.manifest.mockResolvedValue({ version: '3.0.10' });
27-
expect(await tools.resolveVersion('expo-cli', 'latest')).toBe('3.0.10');
28-
expect(registry.manifest).toBeCalledWith('expo-cli@latest');
29-
});
30-
31-
it('fetches exact version of eas-cli', async () => {
32-
registry.manifest.mockResolvedValue({ version: '4.2.0' });
33-
expect(await tools.resolveVersion('eas-cli', 'latest')).toBe('4.2.0');
34-
expect(registry.manifest).toBeCalledWith('eas-cli@latest');
35-
});
36-
});
37-
3822
describe(tools.maybeAuthenticate, () => {
3923
const token = 'ABC123';
4024
const username = 'bycedric';
@@ -268,21 +252,25 @@ describe(tools.maybeWarnForUpdate, () => {
268252
let spy: { [key: string]: jest.SpyInstance } = {};
269253

270254
beforeEach(() => {
271-
spy = { warning: jest.spyOn(core, 'warning').mockImplementation() };
255+
spy = {
256+
warning: jest.spyOn(core, 'warning').mockImplementation(),
257+
resolveVersion: jest.spyOn(packager, 'resolveVersion').mockImplementation(),
258+
};
272259
});
273260

274261
afterAll(() => {
275262
spy.warning.mockRestore();
263+
spy.resolveVersion.mockRestore();
276264
});
277265

278266
it('is silent when major version is up to date', async () => {
279-
registry.manifest.mockResolvedValueOnce({ version: '4.1.0' }).mockResolvedValueOnce({ version: '4.0.1' });
267+
spy.resolveVersion.mockResolvedValueOnce('4.1.0').mockResolvedValueOnce('4.0.1');
280268
await tools.maybeWarnForUpdate('eas-cli');
281269
expect(spy.warning).not.toBeCalled();
282270
});
283271

284272
it('warns when major version is outdated', async () => {
285-
registry.manifest.mockResolvedValueOnce({ version: '4.1.0' }).mockResolvedValueOnce({ version: '3.0.1' });
273+
spy.resolveVersion.mockResolvedValueOnce('4.1.0').mockResolvedValueOnce('3.0.1');
286274
await tools.maybeWarnForUpdate('expo-cli');
287275
expect(spy.warning).toBeCalledWith('There is a new major version available of the Expo CLI (4.1.0)');
288276
expect(spy.warning).toBeCalledWith('If you run into issues, try upgrading your workflow to "expo-version: 4.x"');
@@ -293,23 +281,27 @@ describe(tools.handleError, () => {
293281
let spy: { [key: string]: jest.SpyInstance } = {};
294282

295283
beforeEach(() => {
296-
spy = { setFailed: jest.spyOn(core, 'setFailed').mockImplementation() };
284+
spy = {
285+
setFailed: jest.spyOn(core, 'setFailed').mockImplementation(),
286+
resolveVersion: jest.spyOn(packager, 'resolveVersion').mockImplementation(),
287+
};
297288
});
298289

299290
afterAll(() => {
300291
spy.setFailed.mockRestore();
292+
spy.resolveVersion.mockRestore();
301293
});
302294

303295
it('marks the job as failed with expo-cli', async () => {
304296
const error = new Error('test');
305-
registry.manifest.mockResolvedValue('4.0.0');
297+
spy.resolveVersion.mockResolvedValue('4.0.0');
306298
await tools.handleError('expo-cli', error);
307299
expect(core.setFailed).toBeCalledWith(error);
308300
});
309301

310302
it('fails with original error when update warning failed', async () => {
311303
const error = new Error('test');
312-
registry.manifest.mockRejectedValue(new Error('npm issue'));
304+
spy.resolveVersion.mockRejectedValue(new Error('npm issue'));
313305
await tools.handleError('eas-cli', error);
314306
expect(core.setFailed).toBeCalledWith(error);
315307
});

tests/utils.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ export function restoreEnv(): void {
4141
export function getToolsMock() {
4242
return {
4343
getBinaryName: jest.fn(v => v.replace('-cli', '')),
44-
resolveVersion: jest.fn((n, v) => v),
4544
maybeAuthenticate: jest.fn(),
4645
maybePatchWatchers: jest.fn(),
4746
maybeWarnForUpdate: jest.fn(),

0 commit comments

Comments
 (0)