Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion sources/npmRegistryUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export async function fetchLatestStableVersion(packageName: string) {
});
} catch (cause) {
// TODO: consider switching to `UsageError` when https://github.com/arcanis/clipanion/issues/157 is fixed
throw new Error(`Corepack cannot download the latest stable version of ${packageName}; you can disable signature verification by setting COREPACK_INTEGRITY_CHECK to 0 in your env, or instruct Corepack to use the latest stable release known by this version of Corepack by setting COREPACK_USE_LATEST to 0`, {cause});
throw new Error(`Corepack cannot download the latest stable version of ${packageName}; you can disable signature verification by setting COREPACK_INTEGRITY_KEYS to 0 in your env, or instruct Corepack to use the latest stable release known by this version of Corepack by setting COREPACK_DEFAULT_TO_LATEST to 0`, {cause});
}
}

Expand Down
61 changes: 56 additions & 5 deletions tests/npmRegistryUtils.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import {Buffer} from 'node:buffer';
import process from 'node:process';
import {describe, beforeEach, it, expect, vi} from 'vitest';
import {Buffer} from 'node:buffer';
import process from 'node:process';
import {describe, beforeEach, it, expect, vi} from 'vitest';

import {fetchAsJson as httpFetchAsJson} from '../sources/httpUtils';
import {DEFAULT_HEADERS, DEFAULT_NPM_REGISTRY_URL, fetchAsJson} from '../sources/npmRegistryUtils';
import {fetchAsJson as httpFetchAsJson} from '../sources/httpUtils';
import {DEFAULT_HEADERS, DEFAULT_NPM_REGISTRY_URL, fetchAsJson, fetchLatestStableVersion} from '../sources/npmRegistryUtils';

vi.mock(`../sources/httpUtils`);

Expand Down Expand Up @@ -90,3 +90,54 @@ describe(`npm registry utils fetchAsJson`, () => {
expect(httpFetchAsJson).lastCalledWith(`${DEFAULT_NPM_REGISTRY_URL}/package-name`, {headers: DEFAULT_HEADERS});
});
});

// https://github.com/nodejs/corepack/issues/849
describe(`fetchLatestStableVersion`, () => {
beforeEach(() => {
vi.resetAllMocks();
});

it(`raises an error pointing at the real env vars when integrity verification fails`, async () => {
vi.mocked(httpFetchAsJson).mockResolvedValueOnce({
version: `1.0.0`,
dist: {
integrity: `sha512-AAAA`,
signatures: [],
shasum: `abc`,
},
});

let caught: Error | undefined;
try {
await fetchLatestStableVersion(`some-package`);
} catch (e) {
caught = e as Error;
}
expect(caught).toBeInstanceOf(Error);
// The error must steer users at the real env vars used by the runtime
// - COREPACK_INTEGRITY_KEYS is read by shouldSkipIntegrityCheck()
// - COREPACK_DEFAULT_TO_LATEST is read by the version-resolution path
expect(caught!.message).toContain(`COREPACK_INTEGRITY_KEYS to 0`);
expect(caught!.message).toContain(`COREPACK_DEFAULT_TO_LATEST to 0`);
// Neither of these names exist anywhere else in the codebase, so the
// error must not point users at them.
expect(caught!.message).not.toContain(`COREPACK_INTEGRITY_CHECK`);
expect(caught!.message).not.toContain(`COREPACK_USE_LATEST`);
expect(caught!.message).toContain(`some-package`);
});

it(`skips signature verification and returns when COREPACK_INTEGRITY_KEYS=0`, async () => {
process.env.COREPACK_INTEGRITY_KEYS = `0`;
vi.mocked(httpFetchAsJson).mockResolvedValueOnce({
version: `2.3.4`,
dist: {
integrity: `sha512-BBBB`,
signatures: [],
shasum: `def`,
},
});

const out = await fetchLatestStableVersion(`some-package`);
expect(out).toBe(`2.3.4+sha512.${Buffer.from(`BBBB`, `base64`).toString(`hex`)}`);
});
});