Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: re-add support for custom registries with auth #397

Merged
merged 8 commits into from Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 13 additions & 0 deletions sources/httpUtils.ts
Expand Up @@ -33,6 +33,19 @@ export async function fetch(input: string | URL, init?: RequestInit) {
dispatcher: agent,
});
} catch (error) {
if (error?.message?.includes?.(`Request cannot be constructed from a URL that includes credentials`)) {
const url = new URL(input as string);
const authorization = `Bearer ${Buffer.from(`${url.username}:${url.password}`).toString(`base64`)}`;
url.username = ``;
url.password = ``;
return fetch(url, {
...init,
headers: {
...init?.headers,
authorization,
},
});
}
aduh95 marked this conversation as resolved.
Show resolved Hide resolved
throw new Error(
`Error when performing the request to ${input}; for troubleshooting help, see https://github.com/nodejs/corepack#troubleshooting`,
{cause: error},
Expand Down
109 changes: 109 additions & 0 deletions tests/_registryServer.mjs
@@ -0,0 +1,109 @@
import {createHash} from 'node:crypto';
import {once} from 'node:events';
import {createServer} from 'node:http';
import {gzipSync} from 'node:zlib';

function createSimpleTarArchive(fileName, fileContent, mode = 0o644) {
const contentBuffer = Buffer.from(fileContent);

const header = Buffer.alloc(512); // TAR headers are 512 bytes
header.write(fileName);
header.write(`100${mode.toString(8)} `, 100, 7, `utf-8`); // File mode (octal) followed by a space
header.write(`0001750 `, 108, 8, `utf-8`); // Owner's numeric user ID (octal) followed by a space
header.write(`0001750 `, 116, 8, `utf-8`); // Group's numeric user ID (octal) followed by a space
header.write(`${contentBuffer.length.toString(8)} `, 124, 12, `utf-8`); // File size in bytes (octal) followed by a space
header.write(`${Math.floor(Date.now() / 1000).toString(8)} `, 136, 12, `utf-8`); // Last modification time in numeric Unix time format (octal) followed by a space
header.fill(` `, 148, 156); // Fill checksum area with spaces for calculation
header.write(`ustar `, 257, 8, `utf-8`); // UStar indicator

// Calculate and write the checksum. Note: This is a simplified calculation not recommended for production
const checksum = header.reduce((sum, value) => sum + value, 0);
header.write(`${checksum.toString(8)}\0 `, 148, 8, `utf-8`); // Write checksum in octal followed by null and space


return Buffer.concat([
header,
contentBuffer,
Buffer.alloc(512 - (contentBuffer.length % 512)),
]);
}

const mockPackageTarGz = gzipSync(Buffer.concat([
createSimpleTarArchive(`package/bin/pnpm.js`, `#!/usr/bin/env node\nconsole.log("pnpm: Hello from custom registry");\n`, 0o755),
createSimpleTarArchive(`package/bin/yarn.js`, `#!/usr/bin/env node\nconsole.log("yarn: Hello from custom registry");\n`, 0o755),
createSimpleTarArchive(`package/package.json`, JSON.stringify({bin: {yarn: `bin/yarn.js`, pnpm: `bin/pnpm.js`}})),
Buffer.alloc(1024),
]));
const shasum = createHash(`sha1`).update(mockPackageTarGz).digest(`hex`);


const server = createServer((req, res) => {
const auth = req.headers.authorization;
if (!auth?.startsWith(`Bearer `) || Buffer.from(auth.slice(`Bearer `.length), `base64`).toString() !== `user:pass`) {
res.statusCode = 401;
res.end(`Unauthorized`);
return;
}
switch (req.url) {
case `/yarn`: {
res.end(JSON.stringify({"dist-tags": {
latest: `1.9998.9999`,
}, versions: {'1.9998.9999': {
dist: {
shasum,
size: mockPackageTarGz.length,
noattachment: false,
tarball: `${process.env.COREPACK_NPM_REGISTRY}/yarn.tgz`,
},
}}}));
break;
}

case `/pnpm`: {
res.end(JSON.stringify({"dist-tags": {
latest: `1.9998.9999`,
}, versions: {'1.9998.9999': {
dist: {
shasum,
size: mockPackageTarGz.length,
noattachment: false,
tarball: `${process.env.COREPACK_NPM_REGISTRY}/pnpm/-/pnpm-1.9998.9999.tgz`,
},
}}}));
break;
}

case `/@yarnpkg/cli-dist`: {
res.end(JSON.stringify({"dist-tags": {
latest: `5.9999.9999`,
}, versions: {'5.9999.9999': {
bin: {
yarn: `./bin/yarn.js`,
yarnpkg: `./bin/yarn.js`,
},
dist: {
shasum,
size: mockPackageTarGz.length,
noattachment: false,
tarball: `${process.env.COREPACK_NPM_REGISTRY}/yarn.tgz`,
},
}}}));
break;
}

case `/pnpm/-/pnpm-1.9998.9999.tgz`:
case `/yarn.tgz`:
res.end(mockPackageTarGz);
break;

default:
throw new Error(`unsupported request`, {cause: req.url});
}
}).listen(0, `localhost`);

await once(server, `listening`);

const {address, port} = server.address();
process.env.COREPACK_NPM_REGISTRY = `http://user:pass@${address.includes(`:`) ? `[${address}]` : address}:${port}`;

server.unref();
6 changes: 4 additions & 2 deletions tests/_runCli.ts
@@ -1,14 +1,16 @@
import {PortablePath, npath} from '@yarnpkg/fslib';
import {spawn} from 'child_process';
import * as path from 'path';
import {pathToFileURL} from 'url';

export async function runCli(cwd: PortablePath, argv: Array<string>): Promise<{exitCode: number | null, stdout: string, stderr: string}> {
export async function runCli(cwd: PortablePath, argv: Array<string>, withCustomRegistry?: boolean): Promise<{exitCode: number | null, stdout: string, stderr: string}> {
const out: Array<Buffer> = [];
const err: Array<Buffer> = [];

return new Promise((resolve, reject) => {
if (process.env.RUN_CLI_ID)
(process.env.RUN_CLI_ID as any)++;
const child = spawn(process.execPath, [`--no-warnings`, `-r`, require.resolve(`./recordRequests.js`), require.resolve(`../dist/corepack.js`), ...argv], {
const child = spawn(process.execPath, [`--no-warnings`, `-r`, require.resolve(`./recordRequests.js`), ...(withCustomRegistry ? [`--import`, pathToFileURL(path.join(__dirname, `_registryServer.mjs`))] : []), require.resolve(`../dist/corepack.js`), ...argv], {
cwd: npath.fromPortablePath(cwd),
env: process.env,
stdio: `pipe`,
Expand Down
34 changes: 34 additions & 0 deletions tests/main.test.ts
Expand Up @@ -758,3 +758,37 @@ it(`should be able to show the latest version`, async () => {
});
});
});

it(`should download yarn classic from custom registry with auth`, async () => {
await xfs.mktempPromise(async cwd => {
await expect(runCli(cwd, [`yarn@1.x`, `--version`], true)).resolves.toMatchObject({
exitCode: 0,
stdout: `yarn: Hello from custom registry\n`,
stderr: ``,
});
});
});

it(`should download yarn berry from custom registry with auth`, async () => {
await xfs.mktempPromise(async cwd => {
await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as Filename), {
packageManager: `yarn@3.0.0`,
});

await expect(runCli(cwd, [`yarn@5.x`, `--version`], true)).resolves.toMatchObject({
exitCode: 0,
stdout: `yarn: Hello from custom registry\n`,
stderr: ``,
});
});
});

it(`should download pnpm from custom registry with auth`, async () => {
await xfs.mktempPromise(async cwd => {
await expect(runCli(cwd, [`pnpm@1.x`, `--version`], true)).resolves.toMatchObject({
exitCode: 0,
stdout: `pnpm: Hello from custom registry\n`,
stderr: ``,
});
});
});