Description
Version
23.x
Platform
Windows
Subsystem
No response
What steps will reproduce the bug?
Write the following file as registryServer.mjs
:
registryServer.mjs
The original file is https://github.com/nodejs/corepack/blob/main/tests/_registryServer.mjs, I tried to trim the unrelated stuff but it's still a large file:
import { createHash, createSign, generateKeyPairSync } from "node:crypto";
import { once } from "node:events";
import { createServer } from "node:http";
import { gzipSync } from "node:zlib";
let privateKey, keyid;
({ privateKey } = generateKeyPairSync(`ec`, {
namedCurve: `sect239k1`,
}));
const { privateKey: p, publicKey } = generateKeyPairSync(`ec`, {
namedCurve: `sect239k1`,
publicKeyEncoding: {
type: `spki`,
format: `pem`,
},
});
privateKey ??= p;
keyid = `SHA256:${createHash(`SHA256`).end(publicKey).digest(`base64`)}`;
process.env.COREPACK_INTEGRITY_KEYS = JSON.stringify({
npm: [
{
expires: null,
keyid,
keytype: `ecdsa-sha2-sect239k1`,
scheme: `ecdsa-sha2-sect239k1`,
key: publicKey.split(`\n`).slice(1, -2).join(``),
},
],
});
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(new Date(2000, 1, 1) / 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/package.json`,
JSON.stringify({
bin: {
pnpm: `bin/pnpm.js`,
},
})
),
Buffer.alloc(1024),
])
);
const shasum = createHash(`sha1`).update(mockPackageTarGz).digest(`hex`);
const integrity = `sha512-${createHash(`sha512`)
.update(mockPackageTarGz)
.digest(`base64`)}`;
const registry = {
__proto__: null,
pnpm: [`42.9998.9999`],
};
function generateSignature(packageName, version) {
if (privateKey == null) return undefined;
const sign = createSign(`SHA256`).end(
`${packageName}@${version}:${integrity}`
);
return {
integrity,
signatures: [
{
keyid,
sig: sign.sign(privateKey, `base64`),
},
],
};
}
function generateVersionMetadata(packageName, version) {
return {
name: packageName,
version,
bin: {
[packageName]: `./bin/${packageName}.js`,
},
dist: {
shasum,
size: mockPackageTarGz.length,
tarball: `https://registry.npmjs.org/${packageName}/-/${packageName}-${version}.tgz`,
...generateSignature(packageName, version),
},
};
}
const server = createServer((req, res) => {
let slashPosition = req.url.indexOf(`/`, 1);
if (req.url.charAt(1) === `@`)
slashPosition = req.url.indexOf(`/`, slashPosition + 1);
const packageName = req.url.slice(
1,
slashPosition === -1 ? undefined : slashPosition
);
if (packageName in registry) {
if (req.url === `/${packageName}`) {
// eslint-disable-next-line @typescript-eslint/naming-convention
res.end(
JSON.stringify({
"dist-tags": {
latest: registry[packageName].at(-1),
},
versions: Object.fromEntries(
registry[packageName].map((version) => [
version,
generateVersionMetadata(packageName, version),
])
),
})
);
return;
}
const isDownloadingRequest =
req.url.slice(packageName.length + 1, packageName.length + 4) === `/-/`;
let version;
if (isDownloadingRequest) {
const match = /^(.+)-(.+)\.tgz$/.exec(
req.url.slice(packageName.length + 4)
);
if (match?.[1] === packageName) {
version = match[2];
}
} else {
version = req.url.slice(packageName.length + 2);
}
if (version === `latest`) version = registry[packageName].at(-1);
if (registry[packageName].includes(version)) {
res.end(
isDownloadingRequest
? mockPackageTarGz
: JSON.stringify(generateVersionMetadata(packageName, version))
);
} else {
res.writeHead(404).end(`Not Found`);
throw new Error(`unsupported request`, {
cause: { url: req.url, packageName, version, isDownloadingRequest },
});
}
} else {
res.writeHead(500).end(`Internal Error`);
throw new Error(`unsupported request`, {
cause: { url: req.url, packageName },
});
}
});
server.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();
Then run the following commands:
$env:COREPACK_ENABLE_PROJECT_SPEC=0
$env:NODE_OPTIONS="--import ./registryServer.mjs"
corepack pnpm@42.x --version
or, a simpler repro taken from #58091:
"use strict";
(async function(){
var url = 'https://google.com/';
var code = await fetch(url).then(function(r){
return r.status;
});
console.log(code);
process.exit();
})();
How often does it reproduce? Is there a required condition?
Always on Windows with Node.js 23.x, no required condition, tested with 23.0.0 (libuv 1.48.0), 23.4.0 (libuv 1.49.1), and 23.6.0 (libuv 1.49.2).
It does not reproduce on Linux nor macOS.
It does not reproduce on 22.13.2 (libuv 1.49.2), which makes me think it's not a libuv bug, but a Node.js one.
What is the expected behavior? Why is that the expected behavior?
No assertions, the exit code should be 1
What do you see instead?
Assertion failed: !(handle->flags & UV_HANDLE_CLOSING), file c:\ws\deps\uv\src\win\async.c, line 76
The exit code is 3221226505.
Additional information
My initial thought was that it might be related to having an exception thrown while handling an HTTP request, but I wasn't able to reproduce with just that.