mirror of
https://github.com/oven-sh/bun
synced 2026-02-26 11:37:26 +01:00
Compare commits
92 Commits
claude/fix
...
claude/fix
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
50c7c9d994 | ||
|
|
2742418ea1 | ||
|
|
30e609e080 | ||
|
|
84e4a5ce9c | ||
|
|
89c70a76e8 | ||
|
|
b2d8504a09 | ||
|
|
e735bffaa9 | ||
|
|
347c288d75 | ||
|
|
6cc1a70198 | ||
|
|
fa3a30f075 | ||
|
|
32a89c4334 | ||
|
|
c643e0fad8 | ||
|
|
2222aa9f47 | ||
|
|
38e4340d28 | ||
|
|
6b1d6c769b | ||
|
|
ac6269a2cb | ||
|
|
6314363663 | ||
|
|
77b6406415 | ||
|
|
4d9752a1f0 | ||
|
|
51a74a81fd | ||
|
|
cb3c39be23 | ||
|
|
bc98025d93 | ||
|
|
b371bf9420 | ||
|
|
e6ec92244c | ||
|
|
b509acb533 | ||
|
|
ede635b8a9 | ||
|
|
ebb3730166 | ||
|
|
76ceb26e0a | ||
|
|
06f26e5f01 | ||
|
|
9f5970938f | ||
|
|
c01a5e08be | ||
|
|
e9db16c257 | ||
|
|
21c3439bb4 | ||
|
|
7baf50f379 | ||
|
|
76754a8ead | ||
|
|
ecd4e680eb | ||
|
|
044bb00382 | ||
|
|
655aab845d | ||
|
|
4141ef1edf | ||
|
|
e57593759f | ||
|
|
e7cf4b77ba | ||
|
|
2e5e21015f | ||
|
|
b04303cb23 | ||
|
|
b6eaa96e56 | ||
|
|
6a8f33e7b1 | ||
|
|
c3ae343fc9 | ||
|
|
1eef4368ea | ||
|
|
6e240de4e2 | ||
|
|
e216be966e | ||
|
|
e84bee5d58 | ||
|
|
fb2f304100 | ||
|
|
a350d496cb | ||
|
|
9e32360195 | ||
|
|
9785af304c | ||
|
|
9fbe6a5826 | ||
|
|
c0d97ebd88 | ||
|
|
0b580054a7 | ||
|
|
b817abe55e | ||
|
|
9256b3d777 | ||
|
|
6763fe5a8a | ||
|
|
7848648e09 | ||
|
|
379daff22d | ||
|
|
5b0db0191e | ||
|
|
9ef9ac1db1 | ||
|
|
f5d98191b7 | ||
|
|
83bca9bea8 | ||
|
|
7794cc866e | ||
|
|
70b354aa04 | ||
|
|
9d5a800c3d | ||
|
|
77ca318336 | ||
|
|
337a9f7f2b | ||
|
|
38f41dccdf | ||
|
|
883e43c371 | ||
|
|
cd17934207 | ||
|
|
f6d4ff6779 | ||
|
|
2c173529fa | ||
|
|
243fa45bec | ||
|
|
c19dcb3181 | ||
|
|
c57af9df38 | ||
|
|
3debd0a2d2 | ||
|
|
7afead629c | ||
|
|
9a72bbfae2 | ||
|
|
7a801fcf93 | ||
|
|
44541eb574 | ||
|
|
993be3f931 | ||
|
|
a68393926b | ||
|
|
e8a5f23385 | ||
|
|
16b3e7cde7 | ||
|
|
4c32f15339 | ||
|
|
635034ee33 | ||
|
|
3e792d0d2e | ||
|
|
b7d505b6c1 |
@@ -99,6 +99,23 @@ function getTargetLabel(target) {
|
||||
* @property {string[]} [features]
|
||||
*/
|
||||
|
||||
// Azure VM sizes for Windows CI runners.
|
||||
// DDSv6 = x64, DPSv6 = ARM64 (Cobalt 100). Quota: 100 cores per family in eastus2.
|
||||
const azureVmSizes = {
|
||||
"windows-x64": {
|
||||
build: "Standard_D16ds_v6", // 16 vCPU, 64 GiB — C++ build, link
|
||||
test: "Standard_D4ds_v6", // 4 vCPU, 16 GiB — test shards
|
||||
},
|
||||
"windows-aarch64": {
|
||||
build: "Standard_D16ps_v6", // 16 vCPU, 64 GiB — C++ build, link
|
||||
test: "Standard_D4ps_v6", // 4 vCPU, 16 GiB — test shards
|
||||
},
|
||||
};
|
||||
|
||||
function getAzureVmSize(os, arch, tier = "build") {
|
||||
return azureVmSizes[`${os}-${arch}`]?.[tier];
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {Platform[]}
|
||||
*/
|
||||
@@ -114,8 +131,7 @@ const buildPlatforms = [
|
||||
{ os: "linux", arch: "x64", abi: "musl", baseline: true, distro: "alpine", release: "3.23" },
|
||||
{ os: "windows", arch: "x64", release: "2019" },
|
||||
{ os: "windows", arch: "x64", baseline: true, release: "2019" },
|
||||
// TODO: Re-enable when Windows ARM64 VS component installation is resolved on Buildkite runners
|
||||
// { os: "windows", arch: "aarch64", release: "2019" },
|
||||
{ os: "windows", arch: "aarch64", release: "11" },
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -138,8 +154,7 @@ const testPlatforms = [
|
||||
{ os: "linux", arch: "x64", abi: "musl", baseline: true, distro: "alpine", release: "3.23", tier: "latest" },
|
||||
{ os: "windows", arch: "x64", release: "2019", tier: "oldest" },
|
||||
{ os: "windows", arch: "x64", release: "2019", baseline: true, tier: "oldest" },
|
||||
// TODO: Enable when Windows ARM64 CI runners are ready
|
||||
// { os: "windows", arch: "aarch64", release: "2019", tier: "oldest" },
|
||||
{ os: "windows", arch: "aarch64", release: "11", tier: "latest" },
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -304,15 +319,8 @@ function getCppAgent(platform, options) {
|
||||
};
|
||||
}
|
||||
|
||||
// Cross-compile Windows ARM64 from x64 runners
|
||||
if (os === "windows" && arch === "aarch64") {
|
||||
return getEc2Agent({ ...platform, arch: "x64" }, options, {
|
||||
instanceType: "c7i.4xlarge",
|
||||
});
|
||||
}
|
||||
|
||||
return getEc2Agent(platform, options, {
|
||||
instanceType: arch === "aarch64" ? "c8g.4xlarge" : "c7i.4xlarge",
|
||||
instanceType: os === "windows" ? getAzureVmSize(os, arch) : arch === "aarch64" ? "c8g.4xlarge" : "c7i.4xlarge",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -333,10 +341,8 @@ function getLinkBunAgent(platform, options) {
|
||||
}
|
||||
|
||||
if (os === "windows") {
|
||||
// Cross-compile Windows ARM64 from x64 runners
|
||||
const agentPlatform = arch === "aarch64" ? { ...platform, arch: "x64" } : platform;
|
||||
return getEc2Agent(agentPlatform, options, {
|
||||
instanceType: "r7i.large",
|
||||
return getEc2Agent(platform, options, {
|
||||
instanceType: getAzureVmSize(os, arch),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -363,7 +369,17 @@ function getZigPlatform() {
|
||||
* @param {PipelineOptions} options
|
||||
* @returns {Agent}
|
||||
*/
|
||||
function getZigAgent(_platform, options) {
|
||||
function getZigAgent(platform, options) {
|
||||
const { os, arch } = platform;
|
||||
|
||||
// Windows builds Zig natively on Azure
|
||||
if (os === "windows") {
|
||||
return getEc2Agent(platform, options, {
|
||||
instanceType: getAzureVmSize(os, arch),
|
||||
});
|
||||
}
|
||||
|
||||
// Everything else cross-compiles from Linux aarch64
|
||||
return getEc2Agent(getZigPlatform(), options, {
|
||||
instanceType: "r8g.large",
|
||||
});
|
||||
@@ -388,7 +404,7 @@ function getTestAgent(platform, options) {
|
||||
// TODO: delete this block when we upgrade to mimalloc v3
|
||||
if (os === "windows") {
|
||||
return getEc2Agent(platform, options, {
|
||||
instanceType: "c7i.2xlarge",
|
||||
instanceType: getAzureVmSize(os, arch, "test"),
|
||||
cpuCount: 2,
|
||||
threadsPerCore: 1,
|
||||
});
|
||||
@@ -458,24 +474,14 @@ function getBuildCommand(target, options, label) {
|
||||
|
||||
if (target.os === "windows" && label === "build-bun") {
|
||||
// Only sign release builds, not canary builds (DigiCert charges per signature)
|
||||
const enableSigning = !options.canary ? " -DENABLE_WINDOWS_CODESIGNING=ON" : "";
|
||||
// Skip signing on ARM64 for now — smctl (x64-only) silently fails under emulation
|
||||
const enableSigning = !options.canary && target.arch !== "aarch64" ? " -DENABLE_WINDOWS_CODESIGNING=ON" : "";
|
||||
return `bun run build:${buildProfile}${enableSigning}`;
|
||||
}
|
||||
|
||||
return `bun run build:${buildProfile}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get extra flags needed when cross-compiling Windows ARM64 from x64.
|
||||
* Applied to C++ and link steps (not Zig, which has its own toolchain handling).
|
||||
*/
|
||||
function getWindowsArm64CrossFlags(target) {
|
||||
if (target.os === "windows" && target.arch === "aarch64") {
|
||||
return " --toolchain windows-aarch64";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Platform} platform
|
||||
* @param {PipelineOptions} options
|
||||
@@ -483,7 +489,6 @@ function getWindowsArm64CrossFlags(target) {
|
||||
*/
|
||||
function getBuildCppStep(platform, options) {
|
||||
const command = getBuildCommand(platform, options);
|
||||
const crossFlags = getWindowsArm64CrossFlags(platform);
|
||||
|
||||
return {
|
||||
key: `${getTargetKey(platform)}-build-cpp`,
|
||||
@@ -498,7 +503,7 @@ function getBuildCppStep(platform, options) {
|
||||
// We used to build the C++ dependencies and bun in separate steps.
|
||||
// However, as long as the zig build takes longer than both sequentially,
|
||||
// it's cheaper to run them in the same step. Can be revisited in the future.
|
||||
command: [`${command}${crossFlags} --target bun`, `${command}${crossFlags} --target dependencies`],
|
||||
command: [`${command} --target bun`, `${command} --target dependencies`],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -524,7 +529,10 @@ function getBuildToolchain(target) {
|
||||
* @returns {Step}
|
||||
*/
|
||||
function getBuildZigStep(platform, options) {
|
||||
const { os, arch } = platform;
|
||||
const toolchain = getBuildToolchain(platform);
|
||||
// Native Windows builds don't need a cross-compilation toolchain
|
||||
const toolchainArg = os === "windows" ? "" : ` --toolchain ${toolchain}`;
|
||||
return {
|
||||
key: `${getTargetKey(platform)}-build-zig`,
|
||||
retry: getRetry(),
|
||||
@@ -532,7 +540,7 @@ function getBuildZigStep(platform, options) {
|
||||
agents: getZigAgent(platform, options),
|
||||
cancel_on_build_failing: isMergeQueue(),
|
||||
env: getBuildEnv(platform, options),
|
||||
command: `${getBuildCommand(platform, options)} --target bun-zig --toolchain ${toolchain}`,
|
||||
command: `${getBuildCommand(platform, options)} --target bun-zig${toolchainArg}`,
|
||||
timeout_in_minutes: 35,
|
||||
};
|
||||
}
|
||||
@@ -555,7 +563,7 @@ function getLinkBunStep(platform, options) {
|
||||
ASAN_OPTIONS: "allow_user_segv_handler=1:disable_coredump=0:detect_leaks=0",
|
||||
...getBuildEnv(platform, options),
|
||||
},
|
||||
command: `${getBuildCommand(platform, options, "build-bun")}${getWindowsArm64CrossFlags(platform)} --target bun`,
|
||||
command: `${getBuildCommand(platform, options, "build-bun")} --target bun`,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -586,8 +594,35 @@ function getTargetTriplet(platform) {
|
||||
*/
|
||||
function needsBaselineVerification(platform) {
|
||||
const { os, arch, baseline } = platform;
|
||||
if (os !== "linux") return false;
|
||||
return (arch === "x64" && baseline) || arch === "aarch64";
|
||||
if (os === "linux") return (arch === "x64" && baseline) || arch === "aarch64";
|
||||
if (os === "windows") return arch === "x64" && baseline;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the emulator binary name for the given platform.
|
||||
* Linux uses QEMU user-mode; Windows uses Intel SDE.
|
||||
* @param {Platform} platform
|
||||
* @returns {string}
|
||||
*/
|
||||
function getEmulatorBinary(platform) {
|
||||
const { os, arch } = platform;
|
||||
if (os === "windows") return "sde-external/sde.exe";
|
||||
if (arch === "aarch64") return "qemu-aarch64-static";
|
||||
return "qemu-x86_64-static";
|
||||
}
|
||||
|
||||
const SDE_VERSION = "9.58.0-2025-06-16";
|
||||
const SDE_URL = `https://downloadmirror.intel.com/859732/sde-external-${SDE_VERSION}-win.tar.xz`;
|
||||
|
||||
/**
|
||||
* @param {Platform} platform
|
||||
* @param {PipelineOptions} options
|
||||
* @returns {Step}
|
||||
*/
|
||||
function hasWebKitChanges(options) {
|
||||
const { changedFiles = [] } = options;
|
||||
return changedFiles.some(file => file.includes("SetupWebKit.cmake"));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -596,9 +631,31 @@ function needsBaselineVerification(platform) {
|
||||
* @returns {Step}
|
||||
*/
|
||||
function getVerifyBaselineStep(platform, options) {
|
||||
const { arch } = platform;
|
||||
const { os } = platform;
|
||||
const targetKey = getTargetKey(platform);
|
||||
const archArg = arch === "x64" ? "x64" : "aarch64";
|
||||
const triplet = getTargetTriplet(platform);
|
||||
const emulator = getEmulatorBinary(platform);
|
||||
const jitStressFlag = hasWebKitChanges(options) ? " --jit-stress" : "";
|
||||
|
||||
const setupCommands =
|
||||
os === "windows"
|
||||
? [
|
||||
`echo Downloading build artifacts...`,
|
||||
`buildkite-agent artifact download ${triplet}.zip . --step ${targetKey}-build-bun`,
|
||||
`echo Extracting ${triplet}.zip...`,
|
||||
`tar -xf ${triplet}.zip`,
|
||||
`echo Downloading Intel SDE...`,
|
||||
`curl.exe -fsSL -o sde.tar.xz "${SDE_URL}"`,
|
||||
`echo Extracting Intel SDE...`,
|
||||
`7z x -y sde.tar.xz`,
|
||||
`7z x -y sde.tar`,
|
||||
`ren sde-external-${SDE_VERSION}-win sde-external`,
|
||||
]
|
||||
: [
|
||||
`buildkite-agent artifact download '*.zip' . --step ${targetKey}-build-bun`,
|
||||
`unzip -o '${triplet}.zip'`,
|
||||
`chmod +x ${triplet}/bun`,
|
||||
];
|
||||
|
||||
return {
|
||||
key: `${targetKey}-verify-baseline`,
|
||||
@@ -607,57 +664,10 @@ function getVerifyBaselineStep(platform, options) {
|
||||
agents: getLinkBunAgent(platform, options),
|
||||
retry: getRetry(),
|
||||
cancel_on_build_failing: isMergeQueue(),
|
||||
timeout_in_minutes: 5,
|
||||
timeout_in_minutes: hasWebKitChanges(options) ? 30 : 10,
|
||||
command: [
|
||||
`buildkite-agent artifact download '*.zip' . --step ${targetKey}-build-bun`,
|
||||
`unzip -o '${getTargetTriplet(platform)}.zip'`,
|
||||
`unzip -o '${getTargetTriplet(platform)}-profile.zip'`,
|
||||
`chmod +x ${getTargetTriplet(platform)}/bun ${getTargetTriplet(platform)}-profile/bun-profile`,
|
||||
`./scripts/verify-baseline-cpu.sh --arch ${archArg} --binary ${getTargetTriplet(platform)}/bun`,
|
||||
`./scripts/verify-baseline-cpu.sh --arch ${archArg} --binary ${getTargetTriplet(platform)}-profile/bun-profile`,
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the PR modifies SetupWebKit.cmake (WebKit version changes).
|
||||
* JIT stress tests under QEMU should run when WebKit is updated to catch
|
||||
* JIT-generated code that uses unsupported CPU instructions.
|
||||
* @param {PipelineOptions} options
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function hasWebKitChanges(options) {
|
||||
const { changedFiles = [] } = options;
|
||||
return changedFiles.some(file => file.includes("SetupWebKit.cmake"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a step that runs JSC JIT stress tests under QEMU.
|
||||
* This verifies that JIT-compiled code doesn't use CPU instructions
|
||||
* beyond the baseline target (no AVX on x64, no LSE on aarch64).
|
||||
* @param {Platform} platform
|
||||
* @param {PipelineOptions} options
|
||||
* @returns {Step}
|
||||
*/
|
||||
function getJitStressTestStep(platform, options) {
|
||||
const { arch } = platform;
|
||||
const targetKey = getTargetKey(platform);
|
||||
const archArg = arch === "x64" ? "x64" : "aarch64";
|
||||
|
||||
return {
|
||||
key: `${targetKey}-jit-stress-qemu`,
|
||||
label: `${getTargetLabel(platform)} - jit-stress-qemu`,
|
||||
depends_on: [`${targetKey}-build-bun`],
|
||||
agents: getLinkBunAgent(platform, options),
|
||||
retry: getRetry(),
|
||||
cancel_on_build_failing: isMergeQueue(),
|
||||
// JIT stress tests are slow under QEMU emulation
|
||||
timeout_in_minutes: 30,
|
||||
command: [
|
||||
`buildkite-agent artifact download '*.zip' . --step ${targetKey}-build-bun`,
|
||||
`unzip -o '${getTargetTriplet(platform)}.zip'`,
|
||||
`chmod +x ${getTargetTriplet(platform)}/bun`,
|
||||
`./scripts/verify-jit-stress-qemu.sh --arch ${archArg} --binary ${getTargetTriplet(platform)}/bun`,
|
||||
...setupCommands,
|
||||
`bun scripts/verify-baseline.ts --binary ${triplet}/${os === "windows" ? "bun.exe" : "bun"} --emulator ${emulator}${jitStressFlag}`,
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -717,14 +727,14 @@ function getTestBunStep(platform, options, testOptions = {}) {
|
||||
agents: getTestAgent(platform, options),
|
||||
retry: getRetry(),
|
||||
cancel_on_build_failing: isMergeQueue(),
|
||||
parallelism: os === "darwin" ? 2 : 20,
|
||||
parallelism: os === "darwin" ? 2 : os === "windows" ? 8 : 20,
|
||||
timeout_in_minutes: profile === "asan" || os === "windows" ? 45 : 30,
|
||||
env: {
|
||||
ASAN_OPTIONS: "allow_user_segv_handler=1:disable_coredump=0:detect_leaks=0",
|
||||
},
|
||||
command:
|
||||
os === "windows"
|
||||
? `node .\\scripts\\runner.node.mjs ${args.join(" ")}`
|
||||
? `pwsh -NoProfile -File .\\scripts\\vs-shell.ps1 node .\\scripts\\runner.node.mjs ${args.join(" ")}`
|
||||
: `./scripts/runner.node.mjs ${args.join(" ")}`,
|
||||
};
|
||||
}
|
||||
@@ -739,6 +749,7 @@ function getBuildImageStep(platform, options) {
|
||||
const { publishImages } = options;
|
||||
const action = publishImages ? "publish-image" : "create-image";
|
||||
|
||||
const cloud = os === "windows" ? "azure" : "aws";
|
||||
const command = [
|
||||
"node",
|
||||
"./scripts/machine.mjs",
|
||||
@@ -747,7 +758,7 @@ function getBuildImageStep(platform, options) {
|
||||
`--arch=${arch}`,
|
||||
distro && `--distro=${distro}`,
|
||||
`--release=${release}`,
|
||||
"--cloud=aws",
|
||||
`--cloud=${cloud}`,
|
||||
"--ci",
|
||||
"--authorized-org=oven-sh",
|
||||
];
|
||||
@@ -1169,9 +1180,10 @@ async function getPipelineOptions() {
|
||||
skipBuilds: parseOption(/\[(skip builds?|no builds?|only tests?)\]/i),
|
||||
forceBuilds: parseOption(/\[(force builds?)\]/i),
|
||||
skipTests: parseOption(/\[(skip tests?|no tests?|only builds?)\]/i),
|
||||
buildImages: parseOption(/\[(build images?)\]/i),
|
||||
buildImages: parseOption(/\[(build (?:(?:windows|linux) )?images?)\]/i),
|
||||
dryRun: parseOption(/\[(dry run)\]/i),
|
||||
publishImages: parseOption(/\[(publish images?)\]/i),
|
||||
publishImages: parseOption(/\[(publish (?:(?:windows|linux) )?images?)\]/i),
|
||||
imageFilter: (commitMessage.match(/\[(?:build|publish) (windows|linux) images?\]/i) || [])[1]?.toLowerCase(),
|
||||
buildPlatforms: Array.from(buildPlatformsMap.values()),
|
||||
testPlatforms: Array.from(testPlatformsMap.values()),
|
||||
};
|
||||
@@ -1196,13 +1208,12 @@ async function getPipeline(options = {}) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { buildPlatforms = [], testPlatforms = [], buildImages, publishImages } = options;
|
||||
const { buildPlatforms = [], testPlatforms = [], buildImages, publishImages, imageFilter } = options;
|
||||
const imagePlatforms = new Map(
|
||||
buildImages || publishImages
|
||||
? [...buildPlatforms, ...testPlatforms]
|
||||
.filter(({ os }) => os !== "darwin")
|
||||
// Windows ARM64 cross-compiles from x64 runners, no separate image needed
|
||||
.filter(({ os, arch }) => !(os === "windows" && arch === "aarch64"))
|
||||
.filter(({ os, distro }) => !imageFilter || os === imageFilter || distro === imageFilter)
|
||||
.map(platform => [getImageKey(platform), platform])
|
||||
: [],
|
||||
);
|
||||
@@ -1256,10 +1267,6 @@ async function getPipeline(options = {}) {
|
||||
|
||||
if (needsBaselineVerification(target)) {
|
||||
steps.push(getVerifyBaselineStep(target, options));
|
||||
// Run JIT stress tests under QEMU when WebKit is updated
|
||||
if (hasWebKitChanges(options)) {
|
||||
steps.push(getJitStressTestStep(target, options));
|
||||
}
|
||||
}
|
||||
|
||||
return getStepWithDependsOn(
|
||||
@@ -1341,6 +1348,10 @@ async function main() {
|
||||
{ headers: { Authorization: `Bearer ${getSecret("GITHUB_TOKEN")}` } },
|
||||
);
|
||||
const doc = await res.json();
|
||||
if (!Array.isArray(doc)) {
|
||||
console.error(`-> page ${i}, unexpected response:`, JSON.stringify(doc));
|
||||
break;
|
||||
}
|
||||
console.log(`-> page ${i}, found ${doc.length} items`);
|
||||
if (doc.length === 0) break;
|
||||
for (const { filename, status } of doc) {
|
||||
@@ -1355,7 +1366,7 @@ async function main() {
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
if (allFiles.every(filename => filename.startsWith("docs/"))) {
|
||||
if (allFiles.length > 0 && allFiles.every(filename => filename.startsWith("docs/"))) {
|
||||
console.log(`- PR is only docs, skipping tests!`);
|
||||
return;
|
||||
}
|
||||
|
||||
30
.github/workflows/close-stale-robobun-prs.yml
vendored
Normal file
30
.github/workflows/close-stale-robobun-prs.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
name: Close stale robobun PRs
|
||||
on:
|
||||
schedule:
|
||||
- cron: "30 0 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
close-stale-robobun-prs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Close stale robobun PRs
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
run: |
|
||||
ninety_days_ago=$(date -u -d '90 days ago' +%Y-%m-%dT%H:%M:%SZ)
|
||||
|
||||
gh pr list \
|
||||
--author robobun \
|
||||
--state open \
|
||||
--json number,updatedAt \
|
||||
--limit 1000 \
|
||||
--jq ".[] | select(.updatedAt < \"$ninety_days_ago\") | .number" |
|
||||
while read -r pr_number; do
|
||||
echo "Closing PR #$pr_number (last updated before $ninety_days_ago)"
|
||||
gh pr close "$pr_number" --comment "Closing this PR because it has been inactive for more than 90 days."
|
||||
done
|
||||
33
.github/workflows/on-slop.yml
vendored
Normal file
33
.github/workflows/on-slop.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
name: Close AI Slop PRs
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [labeled]
|
||||
|
||||
jobs:
|
||||
on-slop:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.label.name == 'slop' && github.repository == 'oven-sh/bun'
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Comment and close PR
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.payload.pull_request.number,
|
||||
body: 'This PR has been closed because it was flagged as AI slop.\n\nMany AI-generated PRs are fine, but this one was identified as having one or more of the following issues:\n- Fails to verify the problem actually exists\n- Fails to test that the fix works\n- Makes incorrect assumptions about the codebase\n- Submits changes that are incomplete or misleading\n\nIf you believe this was done in error, please leave a comment explaining why.'
|
||||
});
|
||||
|
||||
await github.rest.pulls.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: context.payload.pull_request.number,
|
||||
title: 'ai slop',
|
||||
body: 'This PR has been marked as AI slop and the description has been updated to avoid confusion or misleading reviewers.\n\nMany AI PRs are fine, but sometimes they submit a PR too early, fail to test if the problem is real, fail to reproduce the problem, or fail to test that the problem is fixed. If you think this PR is not AI slop, please leave a comment.',
|
||||
state: 'closed'
|
||||
});
|
||||
25
CLAUDE.md
25
CLAUDE.md
@@ -161,6 +161,31 @@ test("(multi-file test) my feature", async () => {
|
||||
- `src/sql/` - SQL database integrations
|
||||
- `src/bake/` - Server-side rendering framework
|
||||
|
||||
#### Vendored Dependencies (`vendor/`)
|
||||
|
||||
Third-party C/C++ libraries are vendored locally and can be read from disk (these are not git submodules):
|
||||
|
||||
- `vendor/boringssl/` - BoringSSL (TLS/crypto)
|
||||
- `vendor/brotli/` - Brotli compression
|
||||
- `vendor/cares/` - c-ares (async DNS)
|
||||
- `vendor/hdrhistogram/` - HdrHistogram (latency tracking)
|
||||
- `vendor/highway/` - Google Highway (SIMD)
|
||||
- `vendor/libarchive/` - libarchive (tar/zip)
|
||||
- `vendor/libdeflate/` - libdeflate (fast deflate)
|
||||
- `vendor/libuv/` - libuv (Windows event loop)
|
||||
- `vendor/lolhtml/` - lol-html (HTML rewriter)
|
||||
- `vendor/lshpack/` - ls-hpack (HTTP/2 HPACK)
|
||||
- `vendor/mimalloc/` - mimalloc (memory allocator)
|
||||
- `vendor/nodejs/` - Node.js headers (compatibility)
|
||||
- `vendor/picohttpparser/` - PicoHTTPParser (HTTP parsing)
|
||||
- `vendor/tinycc/` - TinyCC (FFI JIT compiler, fork: oven-sh/tinycc)
|
||||
- `vendor/WebKit/` - WebKit/JavaScriptCore (JS engine)
|
||||
- `vendor/zig/` - Zig compiler/stdlib
|
||||
- `vendor/zlib/` - zlib (compression, cloudflare fork)
|
||||
- `vendor/zstd/` - Zstandard (compression)
|
||||
|
||||
Build configuration for these is in `cmake/targets/Build*.cmake`.
|
||||
|
||||
### JavaScript Class Implementation (C++)
|
||||
|
||||
When implementing JavaScript classes in C++:
|
||||
|
||||
@@ -43,7 +43,7 @@ bunx cowsay 'Hello, world!' # execute a package
|
||||
|
||||
## Install
|
||||
|
||||
Bun supports Linux (x64 & arm64), macOS (x64 & Apple Silicon) and Windows (x64).
|
||||
Bun supports Linux (x64 & arm64), macOS (x64 & Apple Silicon) and Windows (x64 & arm64).
|
||||
|
||||
> **Linux users** — Kernel version 5.6 or higher is strongly recommended, but the minimum is 5.1.
|
||||
|
||||
|
||||
108
bench/snippets/microtask-throughput.mjs
Normal file
108
bench/snippets/microtask-throughput.mjs
Normal file
@@ -0,0 +1,108 @@
|
||||
import { AsyncLocalStorage } from "node:async_hooks";
|
||||
import { bench, group, run } from "../runner.mjs";
|
||||
|
||||
// Benchmark 1: queueMicrotask throughput
|
||||
// Tests the BunPerformMicrotaskJob handler path directly.
|
||||
// The optimization removes the JS trampoline and uses callMicrotask.
|
||||
group("queueMicrotask throughput", () => {
|
||||
bench("queueMicrotask 1k", () => {
|
||||
return new Promise(resolve => {
|
||||
let remaining = 1000;
|
||||
const tick = () => {
|
||||
if (--remaining === 0) resolve();
|
||||
else queueMicrotask(tick);
|
||||
};
|
||||
queueMicrotask(tick);
|
||||
});
|
||||
});
|
||||
|
||||
bench("queueMicrotask 10k", () => {
|
||||
return new Promise(resolve => {
|
||||
let remaining = 10000;
|
||||
const tick = () => {
|
||||
if (--remaining === 0) resolve();
|
||||
else queueMicrotask(tick);
|
||||
};
|
||||
queueMicrotask(tick);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Benchmark 2: Promise.resolve chain
|
||||
// Each .then() queues a microtask via the promise machinery.
|
||||
// Benefits from smaller QueuedTask (better cache locality in the Deque).
|
||||
group("Promise.resolve chain", () => {
|
||||
bench("Promise chain 1k", () => {
|
||||
let p = Promise.resolve();
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
p = p.then(() => {});
|
||||
}
|
||||
return p;
|
||||
});
|
||||
|
||||
bench("Promise chain 10k", () => {
|
||||
let p = Promise.resolve();
|
||||
for (let i = 0; i < 10000; i++) {
|
||||
p = p.then(() => {});
|
||||
}
|
||||
return p;
|
||||
});
|
||||
});
|
||||
|
||||
// Benchmark 3: Promise.all (many simultaneous resolves)
|
||||
// All promises resolve at once, flooding the microtask queue.
|
||||
// Smaller QueuedTask = less memory, better cache utilization.
|
||||
group("Promise.all simultaneous", () => {
|
||||
bench("Promise.all 1k", () => {
|
||||
const promises = [];
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
promises.push(Promise.resolve(i));
|
||||
}
|
||||
return Promise.all(promises);
|
||||
});
|
||||
|
||||
bench("Promise.all 10k", () => {
|
||||
const promises = [];
|
||||
for (let i = 0; i < 10000; i++) {
|
||||
promises.push(Promise.resolve(i));
|
||||
}
|
||||
return Promise.all(promises);
|
||||
});
|
||||
});
|
||||
|
||||
// Benchmark 4: queueMicrotask with AsyncLocalStorage
|
||||
// Tests the inlined async context save/restore path.
|
||||
// Previously went through performMicrotaskFunction JS trampoline.
|
||||
group("queueMicrotask + AsyncLocalStorage", () => {
|
||||
const als = new AsyncLocalStorage();
|
||||
|
||||
bench("ALS.run + queueMicrotask 1k", () => {
|
||||
return als.run({ id: 1 }, () => {
|
||||
return new Promise(resolve => {
|
||||
let remaining = 1000;
|
||||
const tick = () => {
|
||||
als.getStore(); // force context read
|
||||
if (--remaining === 0) resolve();
|
||||
else queueMicrotask(tick);
|
||||
};
|
||||
queueMicrotask(tick);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Benchmark 5: async/await (each await queues microtasks)
|
||||
group("async/await chain", () => {
|
||||
async function asyncChain(n) {
|
||||
let sum = 0;
|
||||
for (let i = 0; i < n; i++) {
|
||||
sum += await Promise.resolve(i);
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
bench("async/await 1k", () => asyncChain(1000));
|
||||
bench("async/await 10k", () => asyncChain(10000));
|
||||
});
|
||||
|
||||
await run();
|
||||
@@ -434,7 +434,7 @@ function(register_command)
|
||||
endif()
|
||||
|
||||
# SKIP_CODEGEN: Skip commands that use BUN_EXECUTABLE if all outputs exist
|
||||
# This is used for Windows ARM64 builds where x64 bun crashes under emulation
|
||||
# Useful for bootstrapping new platforms where bun may not be available
|
||||
if(SKIP_CODEGEN AND CMD_EXECUTABLE STREQUAL "${BUN_EXECUTABLE}")
|
||||
set(ALL_OUTPUTS_EXIST TRUE)
|
||||
foreach(output ${CMD_OUTPUTS})
|
||||
@@ -456,7 +456,7 @@ function(register_command)
|
||||
endif()
|
||||
return()
|
||||
else()
|
||||
message(FATAL_ERROR "SKIP_CODEGEN: Cannot skip ${CMD_TARGET} - missing outputs. Run codegen on x64 first.")
|
||||
message(FATAL_ERROR "SKIP_CODEGEN: Cannot skip ${CMD_TARGET} - missing outputs.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@@ -831,13 +831,6 @@ function(register_cmake_command)
|
||||
list(APPEND MAKE_EFFECTIVE_ARGS "-DCMAKE_${flag}=${MAKE_${flag}}")
|
||||
endforeach()
|
||||
|
||||
# Workaround for CMake 4.1.0 bug: Force correct machine type for Windows ARM64
|
||||
# Use toolchain file and set CMP0197 policy to prevent duplicate /machine: flags
|
||||
if(WIN32 AND CMAKE_SYSTEM_PROCESSOR MATCHES "ARM64|aarch64|AARCH64")
|
||||
list(APPEND MAKE_EFFECTIVE_ARGS "-DCMAKE_TOOLCHAIN_FILE=${CWD}/cmake/toolchains/windows-aarch64.cmake")
|
||||
list(APPEND MAKE_EFFECTIVE_ARGS "-DCMAKE_POLICY_DEFAULT_CMP0197=NEW")
|
||||
list(APPEND MAKE_EFFECTIVE_ARGS "-DCMAKE_PROJECT_INCLUDE=${CWD}/cmake/arm64-static-lib-fix.cmake")
|
||||
endif()
|
||||
|
||||
if(DEFINED FRESH)
|
||||
list(APPEND MAKE_EFFECTIVE_ARGS --fresh)
|
||||
|
||||
@@ -4,7 +4,7 @@ endif()
|
||||
|
||||
optionx(BUN_LINK_ONLY BOOL "If only the linking step should be built" DEFAULT OFF)
|
||||
optionx(BUN_CPP_ONLY BOOL "If only the C++ part of Bun should be built" DEFAULT OFF)
|
||||
optionx(SKIP_CODEGEN BOOL "Skip JavaScript codegen (for Windows ARM64 debug)" DEFAULT OFF)
|
||||
optionx(SKIP_CODEGEN BOOL "Skip JavaScript codegen (useful for bootstrapping new platforms)" DEFAULT OFF)
|
||||
|
||||
optionx(BUILDKITE BOOL "If Buildkite is enabled" DEFAULT OFF)
|
||||
optionx(GITHUB_ACTIONS BOOL "If GitHub Actions is enabled" DEFAULT OFF)
|
||||
@@ -58,18 +58,6 @@ else()
|
||||
message(FATAL_ERROR "Unsupported architecture: ${CMAKE_SYSTEM_PROCESSOR}")
|
||||
endif()
|
||||
|
||||
# CMake 4.0+ policy CMP0197 controls how MSVC machine type flags are handled
|
||||
# Setting to NEW prevents duplicate /machine: flags being added to linker commands
|
||||
if(WIN32 AND ARCH STREQUAL "aarch64")
|
||||
set(CMAKE_POLICY_DEFAULT_CMP0197 NEW)
|
||||
set(CMAKE_MSVC_CMP0197 NEW)
|
||||
# Set linker flags for exe/shared linking
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /machine:ARM64")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /machine:ARM64")
|
||||
set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} /machine:ARM64")
|
||||
set(CMAKE_STATIC_LINKER_FLAGS "${CMAKE_STATIC_LINKER_FLAGS} /machine:ARM64")
|
||||
endif()
|
||||
|
||||
# Windows Code Signing Option
|
||||
if(WIN32)
|
||||
optionx(ENABLE_WINDOWS_CODESIGNING BOOL "Enable Windows code signing with DigiCert KeyLocker" DEFAULT OFF)
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
# This file is included after project() via CMAKE_PROJECT_INCLUDE
|
||||
# It fixes the static library creation command to use ARM64 machine type
|
||||
|
||||
if(WIN32 AND CMAKE_SYSTEM_PROCESSOR STREQUAL \"aarch64\")
|
||||
# Override the static library creation commands to avoid spurious /machine:x64 flags
|
||||
set(CMAKE_C_CREATE_STATIC_LIBRARY \"<CMAKE_AR> /nologo /machine:ARM64 /out:<TARGET> <OBJECTS>\" CACHE STRING \"\" FORCE)
|
||||
set(CMAKE_CXX_CREATE_STATIC_LIBRARY \"<CMAKE_AR> /nologo /machine:ARM64 /out:<TARGET> <OBJECTS>\" CACHE STRING \"\" FORCE)
|
||||
endif()
|
||||
@@ -21,12 +21,7 @@ if(NOT DEFINED CMAKE_HOST_SYSTEM_PROCESSOR)
|
||||
endif()
|
||||
|
||||
if(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "arm64|ARM64|aarch64|AARCH64")
|
||||
# Windows ARM64 can run x86_64 via emulation, and no native ARM64 Zig build exists yet
|
||||
if(CMAKE_HOST_WIN32)
|
||||
set(ZIG_ARCH "x86_64")
|
||||
else()
|
||||
set(ZIG_ARCH "aarch64")
|
||||
endif()
|
||||
set(ZIG_ARCH "aarch64")
|
||||
elseif(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "amd64|AMD64|x86_64|X86_64|x64|X64")
|
||||
set(ZIG_ARCH "x86_64")
|
||||
else()
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
@echo off
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
REM Wrapper for llvm-lib that strips conflicting /machine:x64 flag for ARM64 builds
|
||||
REM This is a workaround for CMake 4.1.0 bug
|
||||
|
||||
REM Find llvm-lib.exe - check LLVM_LIB env var, then PATH, then known locations
|
||||
if defined LLVM_LIB (
|
||||
set "LLVM_LIB_EXE=!LLVM_LIB!"
|
||||
) else (
|
||||
where llvm-lib.exe >nul 2>&1
|
||||
if !ERRORLEVEL! equ 0 (
|
||||
for /f "delims=" %%i in ('where llvm-lib.exe') do set "LLVM_LIB_EXE=%%i"
|
||||
) else if exist "C:\Program Files\LLVM\bin\llvm-lib.exe" (
|
||||
set "LLVM_LIB_EXE=C:\Program Files\LLVM\bin\llvm-lib.exe"
|
||||
) else (
|
||||
echo Error: Cannot find llvm-lib.exe. Set LLVM_LIB environment variable or add LLVM to PATH.
|
||||
exit /b 1
|
||||
)
|
||||
)
|
||||
|
||||
set "ARGS="
|
||||
|
||||
for %%a in (%*) do (
|
||||
set "ARG=%%a"
|
||||
if /i "!ARG!"=="/machine:x64" (
|
||||
REM Skip this argument
|
||||
) else (
|
||||
set "ARGS=!ARGS! %%a"
|
||||
)
|
||||
)
|
||||
|
||||
"!LLVM_LIB_EXE!" %ARGS%
|
||||
exit /b %ERRORLEVEL%
|
||||
@@ -1,18 +0,0 @@
|
||||
# Wrapper for llvm-lib that strips conflicting /machine:x64 flag for ARM64 builds
|
||||
# This is a workaround for CMake 4.1.0 bug where both /machine:ARM64 and /machine:x64 are added
|
||||
|
||||
# Find llvm-lib.exe - check LLVM_LIB env var, then PATH, then known locations
|
||||
if ($env:LLVM_LIB) {
|
||||
$llvmLib = $env:LLVM_LIB
|
||||
} elseif (Get-Command llvm-lib.exe -ErrorAction SilentlyContinue) {
|
||||
$llvmLib = (Get-Command llvm-lib.exe).Source
|
||||
} elseif (Test-Path "C:\Program Files\LLVM\bin\llvm-lib.exe") {
|
||||
$llvmLib = "C:\Program Files\LLVM\bin\llvm-lib.exe"
|
||||
} else {
|
||||
Write-Error "Cannot find llvm-lib.exe. Set LLVM_LIB environment variable or add LLVM to PATH."
|
||||
exit 1
|
||||
}
|
||||
|
||||
$filteredArgs = $args | Where-Object { $_ -ne "/machine:x64" }
|
||||
& $llvmLib @filteredArgs
|
||||
exit $LASTEXITCODE
|
||||
@@ -1,34 +0,0 @@
|
||||
@echo off
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
REM Wrapper for llvm-lib that strips conflicting /machine:x64 flag for ARM64 builds
|
||||
REM This is a workaround for CMake 4.1.0 bug
|
||||
|
||||
REM Find llvm-lib.exe - check LLVM_LIB env var, then PATH, then known locations
|
||||
if defined LLVM_LIB (
|
||||
set "LLVM_LIB_EXE=!LLVM_LIB!"
|
||||
) else (
|
||||
where llvm-lib.exe >nul 2>&1
|
||||
if !ERRORLEVEL! equ 0 (
|
||||
for /f "delims=" %%i in ('where llvm-lib.exe') do set "LLVM_LIB_EXE=%%i"
|
||||
) else if exist "C:\Program Files\LLVM\bin\llvm-lib.exe" (
|
||||
set "LLVM_LIB_EXE=C:\Program Files\LLVM\bin\llvm-lib.exe"
|
||||
) else (
|
||||
echo Error: Cannot find llvm-lib.exe. Set LLVM_LIB environment variable or add LLVM to PATH.
|
||||
exit /b 1
|
||||
)
|
||||
)
|
||||
|
||||
set NEWARGS=
|
||||
|
||||
for %%a in (%*) do (
|
||||
set "ARG=%%a"
|
||||
if /i "!ARG!"=="/machine:x64" (
|
||||
REM Skip /machine:x64 argument
|
||||
) else (
|
||||
set "NEWARGS=!NEWARGS! %%a"
|
||||
)
|
||||
)
|
||||
|
||||
"!LLVM_LIB_EXE!" %NEWARGS%
|
||||
exit /b %ERRORLEVEL%
|
||||
@@ -7,13 +7,6 @@ register_repository(
|
||||
4f4f5ef8ebc6e23cbf393428f0ab1b526773f7ac
|
||||
)
|
||||
|
||||
set(BORINGSSL_CMAKE_ARGS -DBUILD_SHARED_LIBS=OFF)
|
||||
|
||||
# Disable ASM on Windows ARM64 to avoid mixing non-ARM object files into ARM64 libs
|
||||
if(WIN32 AND CMAKE_SYSTEM_PROCESSOR MATCHES "ARM64|aarch64|AARCH64")
|
||||
list(APPEND BORINGSSL_CMAKE_ARGS -DOPENSSL_NO_ASM=1)
|
||||
endif()
|
||||
|
||||
register_cmake_command(
|
||||
TARGET
|
||||
boringssl
|
||||
@@ -22,7 +15,7 @@ register_cmake_command(
|
||||
ssl
|
||||
decrepit
|
||||
ARGS
|
||||
${BORINGSSL_CMAKE_ARGS}
|
||||
-DBUILD_SHARED_LIBS=OFF
|
||||
INCLUDES
|
||||
include
|
||||
)
|
||||
|
||||
@@ -341,6 +341,7 @@ register_command(
|
||||
SOURCES
|
||||
${BUN_JAVASCRIPT_CODEGEN_SOURCES}
|
||||
${BUN_CXX_SOURCES}
|
||||
${ESBUILD_EXECUTABLE}
|
||||
OUTPUTS
|
||||
${BUN_CPP_OUTPUTS}
|
||||
)
|
||||
@@ -362,7 +363,7 @@ register_command(
|
||||
)
|
||||
|
||||
if(SKIP_CODEGEN)
|
||||
# Skip JavaScript codegen - useful for Windows ARM64 debug builds where bun crashes
|
||||
# Skip JavaScript codegen - useful for bootstrapping new platforms
|
||||
message(STATUS "SKIP_CODEGEN is ON - skipping bun-js-modules codegen")
|
||||
foreach(output ${BUN_JAVASCRIPT_OUTPUTS})
|
||||
if(NOT EXISTS ${output})
|
||||
@@ -546,6 +547,7 @@ set(BUN_OBJECT_LUT_SOURCES
|
||||
${CWD}/src/bun.js/bindings/ProcessBindingHTTPParser.cpp
|
||||
${CWD}/src/bun.js/modules/NodeModuleModule.cpp
|
||||
${CODEGEN_PATH}/ZigGeneratedClasses.lut.txt
|
||||
${CWD}/src/bun.js/bindings/webcore/JSEvent.cpp
|
||||
)
|
||||
|
||||
set(BUN_OBJECT_LUT_OUTPUTS
|
||||
@@ -560,6 +562,7 @@ set(BUN_OBJECT_LUT_OUTPUTS
|
||||
${CODEGEN_PATH}/ProcessBindingHTTPParser.lut.h
|
||||
${CODEGEN_PATH}/NodeModuleModule.lut.h
|
||||
${CODEGEN_PATH}/ZigGeneratedClasses.lut.h
|
||||
${CODEGEN_PATH}/JSEvent.lut.h
|
||||
)
|
||||
|
||||
macro(WEBKIT_ADD_SOURCE_DEPENDENCIES _source _deps)
|
||||
@@ -593,6 +596,7 @@ foreach(i RANGE 0 ${BUN_OBJECT_LUT_SOURCES_MAX_INDEX})
|
||||
"Generating ${filename}.lut.h"
|
||||
DEPENDS
|
||||
${BUN_OBJECT_LUT_SOURCE}
|
||||
${CWD}/src/codegen/create_hash_table
|
||||
COMMAND
|
||||
${BUN_EXECUTABLE}
|
||||
${BUN_FLAGS}
|
||||
@@ -602,6 +606,7 @@ foreach(i RANGE 0 ${BUN_OBJECT_LUT_SOURCES_MAX_INDEX})
|
||||
${BUN_OBJECT_LUT_OUTPUT}
|
||||
SOURCES
|
||||
${BUN_OBJECT_LUT_SCRIPT}
|
||||
${CWD}/src/codegen/create_hash_table
|
||||
${BUN_OBJECT_LUT_SOURCE}
|
||||
OUTPUTS
|
||||
${BUN_OBJECT_LUT_OUTPUT}
|
||||
@@ -680,8 +685,7 @@ if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm|ARM|arm64|ARM64|aarch64|AARCH64")
|
||||
if(APPLE)
|
||||
set(ZIG_CPU "apple_m1")
|
||||
elseif(WIN32)
|
||||
# Windows ARM64: use a specific CPU with NEON support
|
||||
# Zig running under x64 emulation would detect wrong CPU with "native"
|
||||
# Windows ARM64: use a specific CPU target for consistent builds
|
||||
set(ZIG_CPU "cortex_a76")
|
||||
else()
|
||||
set(ZIG_CPU "native")
|
||||
@@ -1457,8 +1461,6 @@ if(NOT BUN_CPP_ONLY)
|
||||
# ==856230==See https://github.com/google/sanitizers/issues/856 for possible workarounds.
|
||||
# the linked issue refers to very old kernels but this still happens to us on modern ones.
|
||||
# disabling ASLR to run the binary works around it
|
||||
# Skip post-build test/features when cross-compiling (can't run the target binary on the host)
|
||||
if(NOT CMAKE_CROSSCOMPILING)
|
||||
set(TEST_BUN_COMMAND_BASE ${BUILD_PATH}/${bunExe} --revision)
|
||||
set(TEST_BUN_COMMAND_ENV_WRAP
|
||||
${CMAKE_COMMAND} -E env BUN_DEBUG_QUIET_LOGS=1)
|
||||
@@ -1507,7 +1509,6 @@ if(NOT BUN_CPP_ONLY)
|
||||
${BUILD_PATH}/features.json
|
||||
)
|
||||
endif()
|
||||
endif() # NOT CMAKE_CROSSCOMPILING
|
||||
|
||||
if(CMAKE_HOST_APPLE AND bunStrip)
|
||||
register_command(
|
||||
@@ -1554,10 +1555,7 @@ if(NOT BUN_CPP_ONLY)
|
||||
string(REPLACE bun ${bunTriplet} bunPath ${bun})
|
||||
endif()
|
||||
|
||||
set(bunFiles ${bunExe})
|
||||
if(NOT CMAKE_CROSSCOMPILING)
|
||||
list(APPEND bunFiles features.json)
|
||||
endif()
|
||||
set(bunFiles ${bunExe} features.json)
|
||||
if(WIN32)
|
||||
list(APPEND bunFiles ${bun}.pdb)
|
||||
elseif(APPLE)
|
||||
|
||||
@@ -26,7 +26,7 @@ if(RELEASE)
|
||||
list(APPEND LOLHTML_BUILD_ARGS --release)
|
||||
endif()
|
||||
|
||||
# Cross-compilation: tell cargo to target ARM64
|
||||
# Explicitly tell cargo to target ARM64 on Windows ARM64
|
||||
if(WIN32 AND CMAKE_SYSTEM_PROCESSOR MATCHES "ARM64|aarch64|AARCH64")
|
||||
list(APPEND LOLHTML_BUILD_ARGS --target aarch64-pc-windows-msvc)
|
||||
set(LOLHTML_LIBRARY ${LOLHTML_BUILD_PATH}/aarch64-pc-windows-msvc/${LOLHTML_BUILD_TYPE}/${CMAKE_STATIC_LIBRARY_PREFIX}lolhtml${CMAKE_STATIC_LIBRARY_SUFFIX})
|
||||
@@ -57,11 +57,11 @@ if(WIN32)
|
||||
if(MSVC_VERSIONS)
|
||||
list(GET MSVC_VERSIONS -1 MSVC_LATEST) # Get the latest version
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "ARM64|aarch64")
|
||||
# Use Hostx64/arm64 for cross-compilation from x64, fall back to native
|
||||
if(EXISTS "${MSVC_LATEST}/bin/Hostx64/arm64/link.exe")
|
||||
set(MSVC_LINK_PATH "${MSVC_LATEST}/bin/Hostx64/arm64/link.exe")
|
||||
else()
|
||||
# Prefer native HostARM64, fall back to Hostx64/arm64
|
||||
if(EXISTS "${MSVC_LATEST}/bin/HostARM64/arm64/link.exe")
|
||||
set(MSVC_LINK_PATH "${MSVC_LATEST}/bin/HostARM64/arm64/link.exe")
|
||||
else()
|
||||
set(MSVC_LINK_PATH "${MSVC_LATEST}/bin/Hostx64/arm64/link.exe")
|
||||
endif()
|
||||
set(CARGO_LINKER_VAR "CARGO_TARGET_AARCH64_PC_WINDOWS_MSVC_LINKER")
|
||||
set(MSVC_LIB_ARCH "arm64")
|
||||
|
||||
@@ -13,6 +13,11 @@ else()
|
||||
set(LSHPACK_INCLUDES .)
|
||||
endif()
|
||||
|
||||
# Suppress all warnings from vendored lshpack on Windows (clang-cl)
|
||||
if(WIN32)
|
||||
set(LSHPACK_CMAKE_ARGS "-DCMAKE_C_FLAGS=-w")
|
||||
endif()
|
||||
|
||||
register_cmake_command(
|
||||
TARGET
|
||||
lshpack
|
||||
@@ -28,6 +33,7 @@ register_cmake_command(
|
||||
# _lshpack_enc_get_static_name in libls-hpack.a(lshpack.c.o)
|
||||
# _update_hash in libls-hpack.a(lshpack.c.o)
|
||||
-DCMAKE_BUILD_TYPE=Release
|
||||
${LSHPACK_CMAKE_ARGS}
|
||||
INCLUDES
|
||||
${LSHPACK_INCLUDES}
|
||||
)
|
||||
|
||||
@@ -79,12 +79,22 @@ endif()
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64|ARM64|AARCH64" AND NOT APPLE)
|
||||
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_NO_OPT_ARCH=ON)
|
||||
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OPT_SIMD=ON)
|
||||
list(APPEND MIMALLOC_CMAKE_ARGS "-DCMAKE_C_FLAGS=-moutline-atomics")
|
||||
if(NOT WIN32)
|
||||
list(APPEND MIMALLOC_CMAKE_ARGS "-DCMAKE_C_FLAGS=-moutline-atomics")
|
||||
endif()
|
||||
elseif(NOT ENABLE_BASELINE)
|
||||
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OPT_ARCH=ON)
|
||||
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OPT_SIMD=ON)
|
||||
endif()
|
||||
|
||||
# Suppress all warnings from mimalloc on Windows — it's vendored C code compiled
|
||||
# as C++ (MI_USE_CXX=ON) which triggers many clang-cl warnings (-Wold-style-cast,
|
||||
# -Wzero-as-null-pointer-constant, -Wc++98-compat-pedantic, etc.)
|
||||
if(WIN32)
|
||||
list(APPEND MIMALLOC_CMAKE_ARGS "-DCMAKE_C_FLAGS=-w")
|
||||
list(APPEND MIMALLOC_CMAKE_ARGS "-DCMAKE_CXX_FLAGS=-w")
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
if(DEBUG)
|
||||
set(MIMALLOC_LIBRARY mimalloc-static-debug)
|
||||
|
||||
@@ -7,9 +7,16 @@ register_repository(
|
||||
12882eee073cfe5c7621bcfadf679e1372d4537b
|
||||
)
|
||||
|
||||
# Suppress all warnings from vendored tinycc on Windows (clang-cl)
|
||||
if(WIN32)
|
||||
set(TINYCC_CMAKE_ARGS "-DCMAKE_C_FLAGS=-w")
|
||||
endif()
|
||||
|
||||
register_cmake_command(
|
||||
TARGET
|
||||
tinycc
|
||||
ARGS
|
||||
${TINYCC_CMAKE_ARGS}
|
||||
LIBRARIES
|
||||
tcc
|
||||
)
|
||||
|
||||
@@ -7,6 +7,32 @@ register_repository(
|
||||
886098f3f339617b4243b286f5ed364b9989e245
|
||||
)
|
||||
|
||||
# cloudflare/zlib hardcodes STATIC_LIBRARY_FLAGS "/machine:x64" for 64-bit MSVC,
|
||||
# which conflicts with ARM64 object files. Patch it after clone to use the correct
|
||||
# machine type based on CMAKE_SYSTEM_PROCESSOR.
|
||||
if(WIN32 AND CMAKE_SYSTEM_PROCESSOR MATCHES "ARM64|aarch64|AARCH64")
|
||||
set(ZLIB_PATCH_SCRIPT "${BUILD_PATH}/zlib-arm64-patch.cmake")
|
||||
file(WRITE ${ZLIB_PATCH_SCRIPT} "
|
||||
file(READ \"\${ZLIB_CMAKELISTS}\" content)
|
||||
string(REPLACE \"/machine:x64\" \"/machine:ARM64\" content \"\${content}\")
|
||||
file(WRITE \"\${ZLIB_CMAKELISTS}\" \"\${content}\")
|
||||
file(TOUCH \"\${ZLIB_PATCH_MARKER}\")
|
||||
")
|
||||
register_command(
|
||||
COMMENT "Patching zlib for ARM64"
|
||||
TARGET patch-zlib
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-DZLIB_CMAKELISTS=${VENDOR_PATH}/zlib/CMakeLists.txt
|
||||
-DZLIB_PATCH_MARKER=${VENDOR_PATH}/zlib/.arm64-patched
|
||||
-P ${ZLIB_PATCH_SCRIPT}
|
||||
SOURCES ${VENDOR_PATH}/zlib/.ref
|
||||
OUTPUTS ${VENDOR_PATH}/zlib/.arm64-patched
|
||||
)
|
||||
if(TARGET clone-zlib)
|
||||
add_dependencies(patch-zlib clone-zlib)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# https://gitlab.kitware.com/cmake/cmake/-/issues/25755
|
||||
if(APPLE)
|
||||
set(ZLIB_CMAKE_C_FLAGS "-fno-define-target-os-macros")
|
||||
@@ -38,3 +64,8 @@ register_cmake_command(
|
||||
INCLUDES
|
||||
.
|
||||
)
|
||||
|
||||
# Ensure zlib is patched before configure
|
||||
if(TARGET patch-zlib)
|
||||
add_dependencies(configure-zlib patch-zlib)
|
||||
endif()
|
||||
|
||||
@@ -4,34 +4,3 @@ set(CMAKE_SYSTEM_PROCESSOR aarch64)
|
||||
set(CMAKE_C_COMPILER_WORKS ON)
|
||||
set(CMAKE_CXX_COMPILER_WORKS ON)
|
||||
set(CMAKE_CROSSCOMPILING ON)
|
||||
|
||||
# The rest only applies when building on Windows (C++ and link steps).
|
||||
# The Zig step runs on Linux and only needs CMAKE_SYSTEM_NAME/PROCESSOR above.
|
||||
if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows")
|
||||
|
||||
# Ensure clang/clang-cl targets Windows ARM64 (otherwise ARM64-specific flags like
|
||||
# -march=armv8-a are rejected as x86-only).
|
||||
set(CMAKE_C_COMPILER_TARGET aarch64-pc-windows-msvc CACHE STRING "" FORCE)
|
||||
set(CMAKE_CXX_COMPILER_TARGET aarch64-pc-windows-msvc CACHE STRING "" FORCE)
|
||||
|
||||
# ARM64 has lock-free atomics (highway's FindAtomics check can't run ARM64 test binary on x64)
|
||||
set(ATOMICS_LOCK_FREE_INSTRUCTIONS TRUE CACHE BOOL "" FORCE)
|
||||
set(HAVE_CXX_ATOMICS_WITHOUT_LIB TRUE CACHE BOOL "" FORCE)
|
||||
set(HAVE_CXX_ATOMICS64_WITHOUT_LIB TRUE CACHE BOOL "" FORCE)
|
||||
|
||||
# Force ARM64 architecture ID - this is what CMake uses to determine /machine: flag
|
||||
set(MSVC_C_ARCHITECTURE_ID ARM64 CACHE INTERNAL "")
|
||||
set(MSVC_CXX_ARCHITECTURE_ID ARM64 CACHE INTERNAL "")
|
||||
|
||||
# CMake 4.0+ policy CMP0197 controls how MSVC machine type flags are handled
|
||||
set(CMAKE_POLICY_DEFAULT_CMP0197 NEW CACHE INTERNAL "")
|
||||
|
||||
# Clear any inherited static linker flags that might have wrong machine types
|
||||
set(CMAKE_STATIC_LINKER_FLAGS "" CACHE STRING "" FORCE)
|
||||
|
||||
# Use wrapper script for llvm-lib that strips /machine:x64 flags
|
||||
# This works around CMake 4.1.0 bug where both ARM64 and x64 machine flags are added
|
||||
get_filename_component(_TOOLCHAIN_DIR "${CMAKE_CURRENT_LIST_DIR}" DIRECTORY)
|
||||
set(CMAKE_AR "${_TOOLCHAIN_DIR}/scripts/llvm-lib-wrapper.bat" CACHE FILEPATH "" FORCE)
|
||||
|
||||
endif()
|
||||
|
||||
@@ -17,13 +17,7 @@ if (NOT CI)
|
||||
set(BUN_EXECUTABLE ${BUN_EXECUTABLE} CACHE FILEPATH "Bun executable" FORCE)
|
||||
endif()
|
||||
|
||||
# On Windows ARM64, we need to add --smol flag to avoid crashes when running
|
||||
# x64 bun under WoW64 emulation
|
||||
if(WIN32 AND ARCH STREQUAL "aarch64")
|
||||
set(BUN_FLAGS "--smol" CACHE STRING "Extra flags for bun executable")
|
||||
else()
|
||||
set(BUN_FLAGS "" CACHE STRING "Extra flags for bun executable")
|
||||
endif()
|
||||
set(BUN_FLAGS "" CACHE STRING "Extra flags for bun executable")
|
||||
|
||||
# If this is not set, some advanced features are not checked.
|
||||
# https://github.com/oven-sh/bun/blob/cd7f6a1589db7f1e39dc4e3f4a17234afbe7826c/src/bun.js/javascript.zig#L1069-L1072
|
||||
|
||||
@@ -51,7 +51,7 @@ if(APPLE)
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
# Prefer standalone LLVM over VS-bundled (standalone supports cross-compilation)
|
||||
# Prefer standalone LLVM over VS-bundled
|
||||
list(APPEND LLVM_PATHS "C:/Program Files/LLVM/bin")
|
||||
endif()
|
||||
|
||||
|
||||
@@ -6,11 +6,9 @@ option(WEBKIT_LOCAL "If a local version of WebKit should be used instead of down
|
||||
option(WEBKIT_BUILD_TYPE "The build type for local WebKit (defaults to CMAKE_BUILD_TYPE)")
|
||||
|
||||
if(NOT WEBKIT_VERSION)
|
||||
set(WEBKIT_VERSION 8af7958ff0e2a4787569edf64641a1ae7cfe074a)
|
||||
set(WEBKIT_VERSION 4a6a32c32c11ffb9f5a94c310b10f50130bfe6de)
|
||||
endif()
|
||||
|
||||
# Use preview build URL for Windows ARM64 until the fix is merged to main
|
||||
set(WEBKIT_PREVIEW_PR 140)
|
||||
|
||||
string(SUBSTRING ${WEBKIT_VERSION} 0 16 WEBKIT_VERSION_PREFIX)
|
||||
string(SUBSTRING ${WEBKIT_VERSION} 0 8 WEBKIT_VERSION_SHORT)
|
||||
@@ -95,6 +93,9 @@ if(WEBKIT_LOCAL)
|
||||
-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}
|
||||
-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
|
||||
-DENABLE_REMOTE_INSPECTOR=ON
|
||||
-DENABLE_MEDIA_SOURCE=OFF
|
||||
-DENABLE_MEDIA_STREAM=OFF
|
||||
-DENABLE_WEB_RTC=OFF
|
||||
)
|
||||
|
||||
if(WIN32)
|
||||
|
||||
@@ -20,7 +20,7 @@ else()
|
||||
unsupported(CMAKE_SYSTEM_NAME)
|
||||
endif()
|
||||
|
||||
set(ZIG_COMMIT "c1423ff3fc7064635773a4a4616c5bf986eb00fe")
|
||||
set(ZIG_COMMIT "c031cbebf5b063210473ff5204a24ebfb2492c72")
|
||||
optionx(ZIG_TARGET STRING "The zig target to use" DEFAULT ${DEFAULT_ZIG_TARGET})
|
||||
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Release")
|
||||
@@ -55,7 +55,14 @@ optionx(ZIG_OBJECT_FORMAT "obj|bc" "Output file format for Zig object files" DEF
|
||||
optionx(ZIG_LOCAL_CACHE_DIR FILEPATH "The path to local the zig cache directory" DEFAULT ${CACHE_PATH}/zig/local)
|
||||
optionx(ZIG_GLOBAL_CACHE_DIR FILEPATH "The path to the global zig cache directory" DEFAULT ${CACHE_PATH}/zig/global)
|
||||
|
||||
optionx(ZIG_COMPILER_SAFE BOOL "Download a ReleaseSafe build of the Zig compiler." DEFAULT ${CI})
|
||||
# The ReleaseSafe Zig compiler for Windows ARM64 has an LLVM SEH epilogue bug
|
||||
# (incorrect size for compiler_rt.rem_pio2_large epilogue). Use the default build instead.
|
||||
if(CI AND WIN32 AND DEFAULT_ZIG_ARCH STREQUAL "aarch64")
|
||||
set(DEFAULT_ZIG_COMPILER_SAFE OFF)
|
||||
else()
|
||||
set(DEFAULT_ZIG_COMPILER_SAFE ${CI})
|
||||
endif()
|
||||
optionx(ZIG_COMPILER_SAFE BOOL "Download a ReleaseSafe build of the Zig compiler." DEFAULT ${DEFAULT_ZIG_COMPILER_SAFE})
|
||||
|
||||
setenv(ZIG_LOCAL_CACHE_DIR ${ZIG_LOCAL_CACHE_DIR})
|
||||
setenv(ZIG_GLOBAL_CACHE_DIR ${ZIG_GLOBAL_CACHE_DIR})
|
||||
|
||||
@@ -148,6 +148,9 @@ _bun_completions() {
|
||||
upgrade)
|
||||
COMPREPLY=( $(compgen -W "--version --cwd --help -v -h") );
|
||||
return;;
|
||||
repl)
|
||||
COMPREPLY=( $(compgen -W "--help -h --eval -e --print -p --preload -r --smol --config -c --cwd --env-file --no-env-file" -- "${cur_word}") );
|
||||
return;;
|
||||
run)
|
||||
_file_arguments "!(*.@(js|ts|jsx|tsx|mjs|cjs)?($|))";
|
||||
COMPREPLY+=( $(compgen -W "--version --cwd --help --silent -v -h" -- "${cur_word}" ) );
|
||||
|
||||
@@ -35,7 +35,7 @@ end
|
||||
set -l bun_install_boolean_flags yarn production optional development no-save dry-run force no-cache silent verbose global
|
||||
set -l bun_install_boolean_flags_descriptions "Write a yarn.lock file (yarn v1)" "Don't install devDependencies" "Add dependency to optionalDependencies" "Add dependency to devDependencies" "Don't update package.json or save a lockfile" "Don't install anything" "Always request the latest versions from the registry & reinstall all dependencies" "Ignore manifest cache entirely" "Don't output anything" "Excessively verbose logging" "Use global folder"
|
||||
|
||||
set -l bun_builtin_cmds_without_run dev create help bun upgrade discord install remove add update init pm x
|
||||
set -l bun_builtin_cmds_without_run dev create help bun upgrade discord install remove add update init pm x repl
|
||||
set -l bun_builtin_cmds_accepting_flags create help bun upgrade discord run init link unlink pm x update
|
||||
|
||||
function __bun_complete_bins_scripts --inherit-variable bun_builtin_cmds_without_run -d "Emit bun completions for bins and scripts"
|
||||
@@ -185,3 +185,12 @@ complete -c bun -n "__fish_use_subcommand" -a "x" -d "Execute a package binary,
|
||||
complete -c bun -n "__fish_use_subcommand" -a "outdated" -d "Display the latest versions of outdated dependencies" -f
|
||||
complete -c bun -n "__fish_use_subcommand" -a "update" -d "Update dependencies to their latest versions" -f
|
||||
complete -c bun -n "__fish_use_subcommand" -a "publish" -d "Publish your package from local to npm" -f
|
||||
complete -c bun -n "__fish_use_subcommand" -a "repl" -d "Start a REPL session with Bun" -f
|
||||
complete -c bun -n "__fish_seen_subcommand_from repl" -s "e" -l "eval" -r -d "Evaluate argument as a script, then exit" -f
|
||||
complete -c bun -n "__fish_seen_subcommand_from repl" -s "p" -l "print" -r -d "Evaluate argument as a script, print the result, then exit" -f
|
||||
complete -c bun -n "__fish_seen_subcommand_from repl" -s "r" -l "preload" -r -d "Import a module before other modules are loaded"
|
||||
complete -c bun -n "__fish_seen_subcommand_from repl" -l "smol" -d "Use less memory, but run garbage collection more often" -f
|
||||
complete -c bun -n "__fish_seen_subcommand_from repl" -s "c" -l "config" -r -d "Specify path to Bun config file"
|
||||
complete -c bun -n "__fish_seen_subcommand_from repl" -l "cwd" -r -d "Absolute path to resolve files & entry points from"
|
||||
complete -c bun -n "__fish_seen_subcommand_from repl" -l "env-file" -r -d "Load environment variables from the specified file(s)"
|
||||
complete -c bun -n "__fish_seen_subcommand_from repl" -l "no-env-file" -d "Disable automatic loading of .env files" -f
|
||||
|
||||
@@ -524,6 +524,33 @@ _bun_upgrade_completion() {
|
||||
|
||||
}
|
||||
|
||||
_bun_repl_completion() {
|
||||
_arguments -s -C \
|
||||
'1: :->cmd' \
|
||||
'--help[Print this help menu]' \
|
||||
'-h[Print this help menu]' \
|
||||
'(-p --print)--eval[Evaluate argument as a script, then exit]:script' \
|
||||
'(-p --print)-e[Evaluate argument as a script, then exit]:script' \
|
||||
'(-e --eval)--print[Evaluate argument as a script, print the result, then exit]:script' \
|
||||
'(-e --eval)-p[Evaluate argument as a script, print the result, then exit]:script' \
|
||||
'--preload[Import a module before other modules are loaded]:preload' \
|
||||
'-r[Import a module before other modules are loaded]:preload' \
|
||||
'--smol[Use less memory, but run garbage collection more often]' \
|
||||
'--config[Specify path to Bun config file]: :->config' \
|
||||
'-c[Specify path to Bun config file]: :->config' \
|
||||
'--cwd[Absolute path to resolve files & entry points from]:cwd' \
|
||||
'--env-file[Load environment variables from the specified file(s)]:env-file' \
|
||||
'--no-env-file[Disable automatic loading of .env files]' &&
|
||||
ret=0
|
||||
|
||||
case $state in
|
||||
config)
|
||||
_bun_list_bunfig_toml
|
||||
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_bun_build_completion() {
|
||||
_arguments -s -C \
|
||||
'1: :->cmd' \
|
||||
@@ -787,6 +814,10 @@ _bun() {
|
||||
upgrade)
|
||||
_bun_upgrade_completion
|
||||
|
||||
;;
|
||||
repl)
|
||||
_bun_repl_completion
|
||||
|
||||
;;
|
||||
build)
|
||||
_bun_build_completion
|
||||
@@ -870,6 +901,10 @@ _bun() {
|
||||
upgrade)
|
||||
_bun_upgrade_completion
|
||||
|
||||
;;
|
||||
repl)
|
||||
_bun_repl_completion
|
||||
|
||||
;;
|
||||
build)
|
||||
_bun_build_completion
|
||||
|
||||
@@ -198,13 +198,16 @@ const myPlugin: BunPlugin = {
|
||||
};
|
||||
```
|
||||
|
||||
The builder object provides some methods for hooking into parts of the bundling process. Bun implements `onResolve` and `onLoad`; it does not yet implement the esbuild hooks `onStart`, `onEnd`, and `onDispose`, and `resolve` utilities. `initialOptions` is partially implemented, being read-only and only having a subset of esbuild's options; use `config` (same thing but with Bun's `BuildConfig` format) instead.
|
||||
The builder object provides some methods for hooking into parts of the bundling process. Bun implements `onStart`, `onEnd`, `onResolve`, and `onLoad`. It does not yet implement the esbuild hooks `onDispose` and `resolve`. `initialOptions` is partially implemented, being read-only and only having a subset of esbuild's options; use `config` (same thing but with Bun's `BuildConfig` format) instead.
|
||||
|
||||
```ts title="myPlugin.ts" icon="/icons/typescript.svg"
|
||||
import type { BunPlugin } from "bun";
|
||||
const myPlugin: BunPlugin = {
|
||||
name: "my-plugin",
|
||||
setup(builder) {
|
||||
builder.onStart(() => {
|
||||
/* called when the bundle starts */
|
||||
});
|
||||
builder.onResolve(
|
||||
{
|
||||
/* onResolve.options */
|
||||
@@ -225,6 +228,9 @@ const myPlugin: BunPlugin = {
|
||||
};
|
||||
},
|
||||
);
|
||||
builder.onEnd(result => {
|
||||
/* called when the bundle is complete */
|
||||
});
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
@@ -157,6 +157,31 @@ To build for Windows x64:
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
To build for Windows arm64:
|
||||
|
||||
<Tabs>
|
||||
<Tab title="CLI">
|
||||
```bash icon="terminal" terminal
|
||||
bun build --compile --target=bun-windows-arm64 ./path/to/my/app.ts --outfile myapp
|
||||
|
||||
# note: if no .exe extension is provided, Bun will automatically add it for Windows executables
|
||||
```
|
||||
|
||||
</Tab>
|
||||
<Tab title="JavaScript">
|
||||
```ts build.ts icon="/icons/typescript.svg"
|
||||
await Bun.build({
|
||||
entrypoints: ["./path/to/my/app.ts"],
|
||||
compile: {
|
||||
target: "bun-windows-arm64",
|
||||
outfile: "./myapp", // .exe added automatically
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
To build for macOS arm64:
|
||||
|
||||
<Tabs>
|
||||
@@ -203,16 +228,16 @@ To build for macOS x64:
|
||||
|
||||
The order of the `--target` flag does not matter, as long as they're delimited by a `-`.
|
||||
|
||||
| --target | Operating System | Architecture | Modern | Baseline | Libc |
|
||||
| --------------------- | ---------------- | ------------ | ------ | -------- | ----- |
|
||||
| bun-linux-x64 | Linux | x64 | ✅ | ✅ | glibc |
|
||||
| bun-linux-arm64 | Linux | arm64 | ✅ | N/A | glibc |
|
||||
| bun-windows-x64 | Windows | x64 | ✅ | ✅ | - |
|
||||
| ~~bun-windows-arm64~~ | ~~Windows~~ | ~~arm64~~ | ❌ | ❌ | - |
|
||||
| bun-darwin-x64 | macOS | x64 | ✅ | ✅ | - |
|
||||
| bun-darwin-arm64 | macOS | arm64 | ✅ | N/A | - |
|
||||
| bun-linux-x64-musl | Linux | x64 | ✅ | ✅ | musl |
|
||||
| bun-linux-arm64-musl | Linux | arm64 | ✅ | N/A | musl |
|
||||
| --target | Operating System | Architecture | Modern | Baseline | Libc |
|
||||
| -------------------- | ---------------- | ------------ | ------ | -------- | ----- |
|
||||
| bun-linux-x64 | Linux | x64 | ✅ | ✅ | glibc |
|
||||
| bun-linux-arm64 | Linux | arm64 | ✅ | N/A | glibc |
|
||||
| bun-windows-x64 | Windows | x64 | ✅ | ✅ | - |
|
||||
| bun-windows-arm64 | Windows | arm64 | ✅ | N/A | - |
|
||||
| bun-darwin-x64 | macOS | x64 | ✅ | ✅ | - |
|
||||
| bun-darwin-arm64 | macOS | arm64 | ✅ | N/A | - |
|
||||
| bun-linux-x64-musl | Linux | x64 | ✅ | ✅ | musl |
|
||||
| bun-linux-arm64-musl | Linux | arm64 | ✅ | N/A | musl |
|
||||
|
||||
<Warning>
|
||||
On x64 platforms, Bun uses SIMD optimizations which require a modern CPU supporting AVX2 instructions. The `-baseline`
|
||||
@@ -1184,7 +1209,8 @@ Currently, the `--compile` flag can only accept a single entrypoint at a time an
|
||||
|
||||
- `--outdir` — use `outfile` instead (except when using with `--splitting`).
|
||||
- `--public-path`
|
||||
- `--target=node` or `--target=browser`
|
||||
- `--target=node`
|
||||
- `--target=browser` (without HTML entrypoints — see [Standalone HTML](/bundler/standalone-html) for `--compile --target=browser` with `.html` files)
|
||||
- `--no-bundle` - we always bundle everything into the executable.
|
||||
|
||||
---
|
||||
@@ -1251,7 +1277,8 @@ type Target =
|
||||
| "bun-linux-arm64-musl"
|
||||
| "bun-windows-x64"
|
||||
| "bun-windows-x64-baseline"
|
||||
| "bun-windows-x64-modern";
|
||||
| "bun-windows-x64-modern"
|
||||
| "bun-windows-arm64";
|
||||
```
|
||||
|
||||
### Complete example
|
||||
|
||||
@@ -481,6 +481,16 @@ All paths are resolved relative to your HTML file, making it easy to organize yo
|
||||
|
||||
This is a small wrapper around Bun's support for HTML imports in JavaScript.
|
||||
|
||||
## Standalone HTML
|
||||
|
||||
You can bundle your entire frontend into a **single self-contained `.html` file** with no external dependencies using `--compile --target=browser`. All JavaScript, CSS, and images are inlined directly into the HTML.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun build --compile --target=browser ./index.html --outdir=dist
|
||||
```
|
||||
|
||||
Learn more in the [Standalone HTML docs](/bundler/standalone-html).
|
||||
|
||||
## Adding a backend to your frontend
|
||||
|
||||
To add a backend to your frontend, you can use the "routes" option in `Bun.serve`.
|
||||
|
||||
@@ -1291,6 +1291,28 @@ declare module "bun:bundle" {
|
||||
|
||||
Ensure the file is included in your `tsconfig.json` (e.g., `"include": ["src", "env.d.ts"]`). Now `feature()` only accepts those flags, and invalid strings like `feature("TYPO")` become type errors.
|
||||
|
||||
### optimizeImports
|
||||
|
||||
Skip parsing unused submodules of barrel files (re-export index files). When you import only a few named exports from a large library, normally the bundler parses every file the barrel re-exports. With `optimizeImports`, only the submodules you actually use are parsed.
|
||||
|
||||
```ts title="build.ts" icon="/icons/typescript.svg"
|
||||
await Bun.build({
|
||||
entrypoints: ["./app.ts"],
|
||||
outdir: "./out",
|
||||
optimizeImports: ["antd", "@mui/material", "lodash-es"],
|
||||
});
|
||||
```
|
||||
|
||||
For example, `import { Button } from 'antd'` normally parses all ~3000 modules that `antd/index.js` re-exports. With `optimizeImports: ['antd']`, only the `Button` submodule is parsed.
|
||||
|
||||
This works for **pure barrel files** — files where every named export is a re-export (`export { X } from './x'`). If a barrel file has any local exports (`export const foo = ...`), or if any importer uses `import *`, all submodules are loaded.
|
||||
|
||||
`export *` re-exports are always loaded (never deferred) to avoid circular resolution issues. Only named re-exports (`export { X } from './x'`) that aren't used by any importer are deferred.
|
||||
|
||||
**Automatic mode:** Packages with `"sideEffects": false` in their `package.json` get barrel optimization automatically — no `optimizeImports` config needed. Use `optimizeImports` for packages that don't have this field.
|
||||
|
||||
**Plugins:** Resolve and load plugins work correctly with barrel optimization. Deferred submodules go through the plugin pipeline when they are eventually loaded.
|
||||
|
||||
### metafile
|
||||
|
||||
Generate metadata about the build in a structured format. The metafile contains information about all input files, output files, their sizes, imports, and exports. This is useful for:
|
||||
|
||||
@@ -15,6 +15,7 @@ Plugins can register callbacks to be run at various points in the lifecycle of a
|
||||
- `onResolve()`: Run before a module is resolved
|
||||
- `onLoad()`: Run before a module is loaded
|
||||
- `onBeforeParse()`: Run zero-copy native addons in the parser thread before a file is parsed
|
||||
- `onEnd()`: Run after the bundle is complete
|
||||
|
||||
## Reference
|
||||
|
||||
@@ -39,6 +40,7 @@ type PluginBuilder = {
|
||||
exports?: Record<string, any>;
|
||||
},
|
||||
) => void;
|
||||
onEnd(callback: (result: BuildOutput) => void | Promise<void>): void;
|
||||
config: BuildConfig;
|
||||
};
|
||||
|
||||
@@ -423,3 +425,53 @@ This lifecycle callback is run immediately before a file is parsed by Bun's bund
|
||||
As input, it receives the file's contents and can optionally return new source code.
|
||||
|
||||
<Info>This callback can be called from any thread and so the napi module implementation must be thread-safe.</Info>
|
||||
|
||||
### onEnd
|
||||
|
||||
```ts
|
||||
onEnd(callback: (result: BuildOutput) => void | Promise<void>): void;
|
||||
```
|
||||
|
||||
Registers a callback to be run after the bundle is complete. The callback receives the [`BuildOutput`](/docs/bundler#outputs) object containing the build results, including output files and any build messages.
|
||||
|
||||
```ts title="index.ts" icon="/icons/typescript.svg"
|
||||
const result = await Bun.build({
|
||||
entrypoints: ["./app.ts"],
|
||||
outdir: "./dist",
|
||||
plugins: [
|
||||
{
|
||||
name: "onEnd example",
|
||||
setup(build) {
|
||||
build.onEnd(result => {
|
||||
console.log(`Build completed with ${result.outputs.length} files`);
|
||||
for (const log of result.logs) {
|
||||
console.log(log);
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
The callback can return a `Promise`. The build output promise from `Bun.build()` will not resolve until all `onEnd()` callbacks have completed.
|
||||
|
||||
```ts title="index.ts" icon="/icons/typescript.svg"
|
||||
const result = await Bun.build({
|
||||
entrypoints: ["./app.ts"],
|
||||
outdir: "./dist",
|
||||
plugins: [
|
||||
{
|
||||
name: "Upload to S3",
|
||||
setup(build) {
|
||||
build.onEnd(async result => {
|
||||
if (!result.success) return;
|
||||
for (const output of result.outputs) {
|
||||
await uploadToS3(output);
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
314
docs/bundler/standalone-html.mdx
Normal file
314
docs/bundler/standalone-html.mdx
Normal file
@@ -0,0 +1,314 @@
|
||||
---
|
||||
title: Standalone HTML
|
||||
description: Bundle a single-page app into a single self-contained .html file with no external dependencies
|
||||
---
|
||||
|
||||
Bun can bundle your entire frontend into a **single `.html` file** with zero external dependencies. JavaScript, TypeScript, JSX, CSS, images, fonts, videos, WASM — everything gets inlined into one file.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun build --compile --target=browser ./index.html --outdir=dist
|
||||
```
|
||||
|
||||
The output is a completely self-contained HTML document. No relative paths. No external files. No server required. Just one `.html` file that works anywhere a browser can open it.
|
||||
|
||||
## One file. Upload anywhere. It just works.
|
||||
|
||||
The output is a single `.html` file you can put anywhere:
|
||||
|
||||
- **Upload it to S3** or any static file host — no directory structure to maintain, just one file
|
||||
- **Double-click it from your desktop** — it opens in the browser and works offline, no localhost server needed
|
||||
- **Embed it in your webview** — No need to deal with relative files
|
||||
- **Insert it in an `<iframe>`** — embed interactive content in another page with a single file URL
|
||||
- **Serve it from anywhere** — any HTTP server, CDN, or file share. One file, zero configuration.
|
||||
|
||||
There's nothing to install, no `node_modules` to deploy, no build artifacts to coordinate, no relative paths to think about. The entire app — framework code, stylesheets, images, everything — lives in that one file.
|
||||
|
||||
## Truly one file
|
||||
|
||||
Normally, distributing a web page means managing a folder of assets — the HTML, the JavaScript bundles, the CSS files, the images. Move the HTML without the rest and everything breaks. Browsers have tried to solve this before: Safari's `.webarchive` and `.mhtml` are supposed to save a page as a single file, but in practice they unpack into a folder of loose files on your computer — defeating the purpose.
|
||||
|
||||
Standalone HTML is different. The output is a plain `.html` file. Not an archive. Not a folder. One file, with everything inside it. Every image, every font, every line of CSS and JavaScript is embedded directly in the HTML using standard `<style>` tags, `<script>` tags, and `data:` URIs. Any browser can open it. Any server can host it. Any file host can store it.
|
||||
|
||||
This makes it practical to distribute web pages the same way you'd distribute a PDF — as a single file you can move, copy, upload, or share without worrying about broken paths or missing assets.
|
||||
|
||||
## Quick start
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```html index.html icon="file-code"
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="./styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script src="./app.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
```tsx app.tsx icon="/icons/typescript.svg"
|
||||
import React from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
|
||||
function App() {
|
||||
return <h1>Hello from a single HTML file!</h1>;
|
||||
}
|
||||
|
||||
createRoot(document.getElementById("root")!).render(<App />);
|
||||
```
|
||||
|
||||
```css styles.css icon="file-code"
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: system-ui, sans-serif;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun build --compile --target=browser ./index.html --outdir=dist
|
||||
```
|
||||
|
||||
Open `dist/index.html` — the React app works with no server.
|
||||
|
||||
## Everything gets inlined
|
||||
|
||||
Bun inlines every local asset it finds in your HTML. If it has a relative path, it gets embedded into the output file. This isn't limited to images and stylesheets — it works with any file type.
|
||||
|
||||
### What gets inlined
|
||||
|
||||
| In your source | In the output |
|
||||
| ------------------------------------------------ | ------------------------------------------------------------------------ |
|
||||
| `<script src="./app.tsx">` | `<script type="module">...bundled code...</script>` |
|
||||
| `<link rel="stylesheet" href="./styles.css">` | `<style>...bundled CSS...</style>` |
|
||||
| `<img src="./logo.png">` | `<img src="data:image/png;base64,...">` |
|
||||
| `<img src="./icon.svg">` | `<img src="data:image/svg+xml;base64,...">` |
|
||||
| `<video src="./demo.mp4">` | `<video src="data:video/mp4;base64,...">` |
|
||||
| `<audio src="./click.wav">` | `<audio src="data:audio/wav;base64,...">` |
|
||||
| `<source src="./clip.webm">` | `<source src="data:video/webm;base64,...">` |
|
||||
| `<video poster="./thumb.jpg">` | `<video poster="data:image/jpeg;base64,...">` |
|
||||
| `<link rel="icon" href="./favicon.ico">` | `<link rel="icon" href="data:image/x-icon;base64,...">` |
|
||||
| `<link rel="manifest" href="./app.webmanifest">` | `<link rel="manifest" href="data:application/manifest+json;base64,...">` |
|
||||
| CSS `url("./bg.png")` | CSS `url(data:image/png;base64,...)` |
|
||||
| CSS `@import "./reset.css"` | Flattened into the `<style>` tag |
|
||||
| CSS `url("./font.woff2")` | CSS `url(data:font/woff2;base64,...)` |
|
||||
| JS `import "./styles.css"` | Merged into the `<style>` tag |
|
||||
|
||||
Images, fonts, WASM binaries, videos, audio files, SVGs — any file referenced by a relative path gets base64-encoded into a `data:` URI and embedded directly in the HTML. The MIME type is automatically detected from the file extension.
|
||||
|
||||
External URLs (like CDN links or absolute URLs) are left untouched.
|
||||
|
||||
## Using with React
|
||||
|
||||
React apps work out of the box. Bun handles JSX transpilation and npm package resolution automatically.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun install react react-dom
|
||||
```
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```html index.html icon="file-code"
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>My App</title>
|
||||
<link rel="stylesheet" href="./styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script src="./app.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
```tsx app.tsx icon="/icons/typescript.svg"
|
||||
import React, { useState } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { Counter } from "./components/Counter.tsx";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<main>
|
||||
<h1>Single-file React App</h1>
|
||||
<Counter />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
createRoot(document.getElementById("root")!).render(<App />);
|
||||
```
|
||||
|
||||
```tsx components/Counter.tsx icon="/icons/typescript.svg"
|
||||
import React, { useState } from "react";
|
||||
|
||||
export function Counter() {
|
||||
const [count, setCount] = useState(0);
|
||||
return <button onClick={() => setCount(count + 1)}>Count: {count}</button>;
|
||||
}
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun build --compile --target=browser ./index.html --outdir=dist
|
||||
```
|
||||
|
||||
All of React, your components, and your CSS are bundled into `dist/index.html`. Upload that one file anywhere and it works.
|
||||
|
||||
## Using with Tailwind CSS
|
||||
|
||||
Install the plugin and reference Tailwind in your HTML or CSS:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun install --dev bun-plugin-tailwind
|
||||
```
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```html index.html icon="file-code"
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="tailwindcss" />
|
||||
</head>
|
||||
<body class="bg-gray-100 flex items-center justify-center min-h-screen">
|
||||
<div id="root"></div>
|
||||
<script src="./app.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
```tsx app.tsx icon="/icons/typescript.svg"
|
||||
import React from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div className="bg-white rounded-lg shadow-lg p-8 max-w-md">
|
||||
<h1 className="text-2xl font-bold text-gray-800">Hello Tailwind</h1>
|
||||
<p className="text-gray-600 mt-2">This is a single HTML file.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
createRoot(document.getElementById("root")!).render(<App />);
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
Build with the plugin using the JavaScript API:
|
||||
|
||||
```ts build.ts icon="/icons/typescript.svg"
|
||||
await Bun.build({
|
||||
entrypoints: ["./index.html"],
|
||||
compile: true,
|
||||
target: "browser",
|
||||
outdir: "./dist",
|
||||
plugins: [require("bun-plugin-tailwind")],
|
||||
});
|
||||
```
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun run build.ts
|
||||
```
|
||||
|
||||
The generated Tailwind CSS is inlined directly into the HTML file as a `<style>` tag.
|
||||
|
||||
## How it works
|
||||
|
||||
When you pass `--compile --target=browser` with an HTML entrypoint, Bun:
|
||||
|
||||
1. Parses the HTML and discovers all `<script>`, `<link>`, `<img>`, `<video>`, `<audio>`, `<source>`, and other asset references
|
||||
2. Bundles all JavaScript/TypeScript/JSX into a single module
|
||||
3. Bundles all CSS (including `@import` chains and CSS imported from JS) into a single stylesheet
|
||||
4. Converts every relative asset reference into a base64 `data:` URI
|
||||
5. Inlines the bundled JS as `<script type="module">` before `</body>`
|
||||
6. Inlines the bundled CSS as `<style>` in `<head>`
|
||||
7. Outputs a single `.html` file with no external dependencies
|
||||
|
||||
## Minification
|
||||
|
||||
Add `--minify` to minify the JavaScript and CSS:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun build --compile --target=browser --minify ./index.html --outdir=dist
|
||||
```
|
||||
|
||||
Or via the API:
|
||||
|
||||
```ts build.ts icon="/icons/typescript.svg"
|
||||
await Bun.build({
|
||||
entrypoints: ["./index.html"],
|
||||
compile: true,
|
||||
target: "browser",
|
||||
outdir: "./dist",
|
||||
minify: true,
|
||||
});
|
||||
```
|
||||
|
||||
## JavaScript API
|
||||
|
||||
You can use `Bun.build()` to produce standalone HTML programmatically:
|
||||
|
||||
```ts build.ts icon="/icons/typescript.svg"
|
||||
const result = await Bun.build({
|
||||
entrypoints: ["./index.html"],
|
||||
compile: true,
|
||||
target: "browser",
|
||||
outdir: "./dist", // optional — omit to get output as BuildArtifact
|
||||
minify: true,
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
console.error("Build failed:");
|
||||
for (const log of result.logs) {
|
||||
console.error(log);
|
||||
}
|
||||
} else {
|
||||
console.log("Built:", result.outputs[0].path);
|
||||
}
|
||||
```
|
||||
|
||||
When `outdir` is omitted, the output is available as a `BuildArtifact` in `result.outputs`:
|
||||
|
||||
```ts icon="/icons/typescript.svg"
|
||||
const result = await Bun.build({
|
||||
entrypoints: ["./index.html"],
|
||||
compile: true,
|
||||
target: "browser",
|
||||
});
|
||||
|
||||
const html = await result.outputs[0].text();
|
||||
await Bun.write("output.html", html);
|
||||
```
|
||||
|
||||
## Multiple HTML files
|
||||
|
||||
You can pass multiple HTML files as entrypoints. Each produces its own standalone HTML file:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun build --compile --target=browser ./index.html ./about.html --outdir=dist
|
||||
```
|
||||
|
||||
## Environment variables
|
||||
|
||||
Use `--env` to inline environment variables into the bundled JavaScript:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
API_URL=https://api.example.com bun build --compile --target=browser --env=inline ./index.html --outdir=dist
|
||||
```
|
||||
|
||||
References to `process.env.API_URL` in your JavaScript are replaced with the literal value at build time.
|
||||
|
||||
## Limitations
|
||||
|
||||
- **Code splitting** is not supported — `--splitting` cannot be used with `--compile --target=browser`
|
||||
- **Large assets** increase file size since they're base64-encoded (33% overhead vs the raw binary)
|
||||
- **External URLs** (CDN links, absolute URLs) are left as-is — only relative paths are inlined
|
||||
@@ -75,7 +75,7 @@
|
||||
{
|
||||
"group": "Core Runtime",
|
||||
"icon": "cog",
|
||||
"pages": ["/runtime/index", "/runtime/watch-mode", "/runtime/debugger", "/runtime/bunfig"]
|
||||
"pages": ["/runtime/index", "/runtime/watch-mode", "/runtime/debugger", "/runtime/repl", "/runtime/bunfig"]
|
||||
},
|
||||
{
|
||||
"group": "File & Module System",
|
||||
@@ -234,7 +234,7 @@
|
||||
{
|
||||
"group": "Asset Processing",
|
||||
"icon": "image",
|
||||
"pages": ["/bundler/html-static", "/bundler/css", "/bundler/loaders"]
|
||||
"pages": ["/bundler/html-static", "/bundler/standalone-html", "/bundler/css", "/bundler/loaders"]
|
||||
},
|
||||
{
|
||||
"group": "Single File Executable",
|
||||
|
||||
@@ -33,7 +33,7 @@ const stream = await renderToReadableStream(<Component message="Hello from serve
|
||||
|
||||
Combining this with `Bun.serve()`, we get a simple SSR HTTP server:
|
||||
|
||||
```tsx server.ts icon="/icons/typescript.svg"
|
||||
```tsx server.tsx icon="/icons/typescript.svg"
|
||||
Bun.serve({
|
||||
async fetch() {
|
||||
const stream = await renderToReadableStream(<Component message="Hello from server!" />);
|
||||
|
||||
@@ -13,7 +13,7 @@ mode: center
|
||||
Use the interactive CLI to create a new TanStack Start app.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun create @tanstack/start@latest my-tanstack-app
|
||||
bunx @tanstack/cli create my-tanstack-app
|
||||
```
|
||||
|
||||
</Step>
|
||||
|
||||
@@ -260,6 +260,13 @@ To download Bun binaries directly, visit the [releases page on GitHub](https://g
|
||||
>
|
||||
For older CPUs without AVX2
|
||||
</Card>
|
||||
<Card
|
||||
icon="/icons/windows.svg"
|
||||
title="Windows ARM64"
|
||||
href="https://github.com/oven-sh/bun/releases/latest/download/bun-windows-aarch64.zip"
|
||||
>
|
||||
Windows on ARM (Snapdragon, etc.)
|
||||
</Card>
|
||||
<Card
|
||||
icon="/icons/apple.svg"
|
||||
title="macOS ARM64"
|
||||
|
||||
176
docs/runtime/repl.mdx
Normal file
176
docs/runtime/repl.mdx
Normal file
@@ -0,0 +1,176 @@
|
||||
---
|
||||
title: "REPL"
|
||||
description: "An interactive JavaScript and TypeScript REPL with syntax highlighting, history, and tab completion"
|
||||
---
|
||||
|
||||
`bun repl` starts an interactive Read-Eval-Print Loop (REPL) for evaluating JavaScript and TypeScript expressions. It's useful for quickly testing code snippets, exploring APIs, and debugging.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun repl
|
||||
```
|
||||
|
||||
```txt
|
||||
Welcome to Bun v1.3.3
|
||||
Type .copy [code] to copy to clipboard. .help for more info.
|
||||
|
||||
> 1 + 1
|
||||
2
|
||||
> const greeting = "Hello, Bun!"
|
||||
undefined
|
||||
> greeting
|
||||
'Hello, Bun!'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
- **TypeScript & JSX** — Write TypeScript and JSX directly. Bun transpiles everything on the fly.
|
||||
- **Top-level `await`** — Await promises directly at the prompt without wrapping in an async function.
|
||||
- **Syntax highlighting** — Input is highlighted as you type.
|
||||
- **Persistent history** — History is saved to `~/.bun_repl_history` and persists across sessions.
|
||||
- **Tab completion** — Press `Tab` to complete property names and REPL commands.
|
||||
- **Multi-line input** — Unclosed brackets, braces, and parentheses automatically continue on the next line.
|
||||
- **Node.js globals** — `require`, `module`, `__dirname`, and `__filename` are available, resolved relative to your current working directory.
|
||||
|
||||
---
|
||||
|
||||
## Special variables
|
||||
|
||||
The REPL exposes two special variables that update after each evaluation.
|
||||
|
||||
| Variable | Description |
|
||||
| -------- | --------------------------------- |
|
||||
| `_` | The result of the last expression |
|
||||
| `_error` | The last error that was thrown |
|
||||
|
||||
```txt
|
||||
> 2 + 2
|
||||
4
|
||||
> _ * 10
|
||||
40
|
||||
> JSON.parse("oops")
|
||||
SyntaxError: JSON Parse error: Unexpected identifier "oops"
|
||||
> _error
|
||||
SyntaxError: JSON Parse error: Unexpected identifier "oops"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Top-level `await`
|
||||
|
||||
Promises are automatically awaited. You can `await` any expression directly at the prompt.
|
||||
|
||||
```txt
|
||||
> await fetch("https://api.github.com/repos/oven-sh/bun").then(r => r.json()).then(r => r.stargazers_count)
|
||||
81234
|
||||
> const response = await fetch("https://example.com")
|
||||
undefined
|
||||
> response.status
|
||||
200
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Importing modules
|
||||
|
||||
Just like Bun's runtime, you can use either `require` or `import` in the REPL and it Just Works — mix ESM and CommonJS freely at the prompt. Module resolution uses the same rules as `bun run`, so you can import from `node_modules`, relative paths, or `node:` builtins.
|
||||
|
||||
```txt
|
||||
> import { z } from "zod"
|
||||
undefined
|
||||
> const path = require("path")
|
||||
undefined
|
||||
> z.string().parse(path.join("/tmp", "file.txt"))
|
||||
'/tmp/file.txt'
|
||||
```
|
||||
|
||||
Declarations persist for the rest of the session, and `const`/`let` can be redeclared across evaluations (unlike in regular scripts) so you can re-run `import` and `require` statements while iterating.
|
||||
|
||||
---
|
||||
|
||||
## Multi-line input
|
||||
|
||||
When you press `Enter` on a line with unclosed brackets, braces, or parentheses, the REPL automatically continues on the next line. The prompt changes to `...` to indicate continuation.
|
||||
|
||||
```txt
|
||||
> function add(a, b) {
|
||||
... return a + b;
|
||||
... }
|
||||
undefined
|
||||
> add(2, 3)
|
||||
5
|
||||
```
|
||||
|
||||
For longer multi-line entries, use `.editor` to enter editor mode, which buffers all input until you press `Ctrl+D`.
|
||||
|
||||
---
|
||||
|
||||
## REPL commands
|
||||
|
||||
Type `.help` at the prompt to see all available REPL commands.
|
||||
|
||||
| Command | Description |
|
||||
| ---------- | ------------------------------------------------------------------------------------------------ |
|
||||
| `.help` | Print the help message listing commands and keybindings |
|
||||
| `.exit` | Exit the REPL |
|
||||
| `.clear` | Clear the screen |
|
||||
| `.copy` | Copy the last result to the clipboard. Pass an expression to evaluate and copy it: `.copy 1 + 1` |
|
||||
| `.load` | Load a file into the REPL session: `.load ./script.ts` |
|
||||
| `.save` | Save the current REPL history to a file: `.save ./session.txt` |
|
||||
| `.editor` | Enter multi-line editor mode (press `Ctrl+D` to evaluate, `Ctrl+C` to cancel) |
|
||||
| `.break` | Cancel the current multi-line input |
|
||||
| `.history` | Print the command history |
|
||||
|
||||
---
|
||||
|
||||
## Keybindings
|
||||
|
||||
The REPL supports Emacs-style line editing.
|
||||
|
||||
| Keybinding | Action |
|
||||
| ------------------- | -------------------------------------------------------- |
|
||||
| `Ctrl+A` | Move to start of line |
|
||||
| `Ctrl+E` | Move to end of line |
|
||||
| `Ctrl+B` / `Ctrl+F` | Move backward/forward one character |
|
||||
| `Alt+B` / `Alt+F` | Move backward/forward one word |
|
||||
| `Ctrl+U` | Delete to start of line |
|
||||
| `Ctrl+K` | Delete to end of line |
|
||||
| `Ctrl+W` | Delete word backward |
|
||||
| `Ctrl+D` | Delete character (or exit if line is empty) |
|
||||
| `Ctrl+L` | Clear screen |
|
||||
| `Ctrl+T` | Swap the two characters before the cursor |
|
||||
| `Up` / `Down` | Navigate history |
|
||||
| `Tab` | Auto-complete |
|
||||
| `Ctrl+C` | Cancel current input (press twice on empty line to exit) |
|
||||
|
||||
---
|
||||
|
||||
## History
|
||||
|
||||
REPL history is automatically saved to `~/.bun_repl_history` (up to 1000 entries) and loaded at the start of each session. Use `Up`/`Down` to navigate.
|
||||
|
||||
To export your history to a different file, use `.save`:
|
||||
|
||||
```txt
|
||||
> .save ./my-session.txt
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Non-interactive mode
|
||||
|
||||
Use `-e` / `--eval` to evaluate a script with REPL semantics and exit. Use `-p` / `--print` to additionally print the result.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun repl -e "const x: number = 42; console.log(x)"
|
||||
# 42
|
||||
|
||||
bun repl -p "await fetch('https://example.com').then(r => r.status)"
|
||||
# 200
|
||||
|
||||
bun repl -p "{ a: 1, b: 2 }"
|
||||
# { a: 1, b: 2 }
|
||||
```
|
||||
|
||||
This uses the same transforms as the interactive REPL, so a bare object literal like `{ a: 1 }` is treated as an object expression instead of a block statement. The process exits after the event loop drains (pending timers and I/O complete first). On error, the process exits with code `1`.
|
||||
@@ -22,8 +22,9 @@ bun upgrade
|
||||
- [Linux, arm64](https://www.npmjs.com/package/@oven/bun-linux-aarch64)
|
||||
- [Linux, x64](https://www.npmjs.com/package/@oven/bun-linux-x64)
|
||||
- [Linux, x64 (without AVX2 instructions)](https://www.npmjs.com/package/@oven/bun-linux-x64-baseline)
|
||||
- [Windows](https://www.npmjs.com/package/@oven/bun-windows-x64)
|
||||
- [Windows (without AVX2 instructions)](https://www.npmjs.com/package/@oven/bun-windows-x64-baseline)
|
||||
- [Windows, x64](https://www.npmjs.com/package/@oven/bun-windows-x64)
|
||||
- [Windows, x64 (without AVX2 instructions)](https://www.npmjs.com/package/@oven/bun-windows-x64-baseline)
|
||||
- [Windows ARM64](https://www.npmjs.com/package/@oven/bun-windows-aarch64)
|
||||
|
||||
### Future Platforms
|
||||
|
||||
|
||||
@@ -95,12 +95,12 @@ export const platforms: Platform[] = [
|
||||
bin: "bun-windows-x64-baseline",
|
||||
exe: "bin/bun.exe",
|
||||
},
|
||||
// {
|
||||
// os: "win32",
|
||||
// arch: "arm64",
|
||||
// bin: "bun-windows-aarch64",
|
||||
// exe: "bin/bun.exe",
|
||||
// },
|
||||
{
|
||||
os: "win32",
|
||||
arch: "arm64",
|
||||
bin: "bun-windows-aarch64",
|
||||
exe: "bin/bun.exe",
|
||||
},
|
||||
];
|
||||
|
||||
export const supportedPlatforms: Platform[] = platforms
|
||||
|
||||
50
packages/bun-types/bun.d.ts
vendored
50
packages/bun-types/bun.d.ts
vendored
@@ -2428,7 +2428,7 @@ declare module "bun" {
|
||||
}
|
||||
|
||||
namespace Build {
|
||||
type Architecture = "x64" | "arm64";
|
||||
type Architecture = "x64" | "arm64" | "aarch64";
|
||||
type Libc = "glibc" | "musl";
|
||||
type SIMD = "baseline" | "modern";
|
||||
type CompileTarget =
|
||||
@@ -2644,6 +2644,25 @@ declare module "bun" {
|
||||
*/
|
||||
features?: string[];
|
||||
|
||||
/**
|
||||
* List of package names whose barrel files (re-export index files) should
|
||||
* be optimized. When a named import comes from one of these packages,
|
||||
* only the submodules actually used are parsed — unused re-exports are
|
||||
* skipped entirely.
|
||||
*
|
||||
* This is also enabled automatically for any package with
|
||||
* `"sideEffects": false` in its `package.json`.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* await Bun.build({
|
||||
* entrypoints: ['./app.ts'],
|
||||
* optimizeImports: ['antd', '@mui/material', 'lodash-es'],
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
optimizeImports?: string[];
|
||||
|
||||
/**
|
||||
* - When set to `true`, the returned promise rejects with an AggregateError when a build failure happens.
|
||||
* - When set to `false`, returns a {@link BuildOutput} with `{success: false}`
|
||||
@@ -2781,11 +2800,17 @@ declare module "bun" {
|
||||
outdir?: string;
|
||||
|
||||
/**
|
||||
* Create a standalone executable
|
||||
* Create a standalone executable or self-contained HTML.
|
||||
*
|
||||
* When `true`, creates an executable for the current platform.
|
||||
* When a target string, creates an executable for that platform.
|
||||
*
|
||||
* When used with `target: "browser"`, produces self-contained HTML files
|
||||
* with all scripts, styles, and assets inlined. All `<script>` tags become
|
||||
* inline `<script>` with bundled code, all `<link rel="stylesheet">` tags
|
||||
* become inline `<style>` tags, and all asset references become `data:` URIs.
|
||||
* All entrypoints must be HTML files. Cannot be used with `splitting`.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // Create executable for current platform
|
||||
@@ -2803,6 +2828,13 @@ declare module "bun" {
|
||||
* compile: 'linux-x64',
|
||||
* outfile: './my-app'
|
||||
* });
|
||||
*
|
||||
* // Produce self-contained HTML
|
||||
* await Bun.build({
|
||||
* entrypoints: ['./index.html'],
|
||||
* target: 'browser',
|
||||
* compile: true,
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
compile?: boolean | Bun.Build.CompileTarget | CompileBuildOptions;
|
||||
@@ -4554,6 +4586,20 @@ declare module "bun" {
|
||||
*/
|
||||
function generateHeapSnapshot(format: "v8"): string;
|
||||
|
||||
/**
|
||||
* Show precise statistics about memory usage of your application
|
||||
*
|
||||
* Generate a V8 Heap Snapshot as an ArrayBuffer.
|
||||
*
|
||||
* This avoids the overhead of creating a JavaScript string for large heap snapshots.
|
||||
* The ArrayBuffer contains the UTF-8 encoded JSON.
|
||||
* ```ts
|
||||
* const snapshot = Bun.generateHeapSnapshot("v8", "arraybuffer");
|
||||
* await Bun.write("heap.heapsnapshot", snapshot);
|
||||
* ```
|
||||
*/
|
||||
function generateHeapSnapshot(format: "v8", encoding: "arraybuffer"): ArrayBuffer;
|
||||
|
||||
/**
|
||||
* The next time JavaScriptCore is idle, clear unused memory and attempt to reduce the heap size.
|
||||
*
|
||||
|
||||
@@ -23196,557 +23196,6 @@ CKA_TRUST_EMAIL_PROTECTION CK_TRUST CKT_NSS_TRUSTED_DELEGATOR
|
||||
CKA_TRUST_CODE_SIGNING CK_TRUST CKT_NSS_MUST_VERIFY_TRUST
|
||||
CKA_TRUST_STEP_UP_APPROVED CK_BBOOL CK_FALSE
|
||||
|
||||
#
|
||||
# Certificate "CommScope Public Trust ECC Root-01"
|
||||
#
|
||||
# Issuer: CN=CommScope Public Trust ECC Root-01,O=CommScope,C=US
|
||||
# Serial Number:43:70:82:77:cf:4d:5d:34:f1:ca:ae:32:2f:37:f7:f4:7f:75:a0:9e
|
||||
# Subject: CN=CommScope Public Trust ECC Root-01,O=CommScope,C=US
|
||||
# Not Valid Before: Wed Apr 28 17:35:43 2021
|
||||
# Not Valid After : Sat Apr 28 17:35:42 2046
|
||||
# Fingerprint (SHA-256): 11:43:7C:DA:7B:B4:5E:41:36:5F:45:B3:9A:38:98:6B:0D:E0:0D:EF:34:8E:0C:7B:B0:87:36:33:80:0B:C3:8B
|
||||
# Fingerprint (SHA1): 07:86:C0:D8:DD:8E:C0:80:98:06:98:D0:58:7A:EF:DE:A6:CC:A2:5D
|
||||
CKA_CLASS CK_OBJECT_CLASS CKO_CERTIFICATE
|
||||
CKA_TOKEN CK_BBOOL CK_TRUE
|
||||
CKA_PRIVATE CK_BBOOL CK_FALSE
|
||||
CKA_MODIFIABLE CK_BBOOL CK_FALSE
|
||||
CKA_LABEL UTF8 "CommScope Public Trust ECC Root-01"
|
||||
CKA_CERTIFICATE_TYPE CK_CERTIFICATE_TYPE CKC_X_509
|
||||
CKA_SUBJECT MULTILINE_OCTAL
|
||||
\060\116\061\013\060\011\006\003\125\004\006\023\002\125\123\061
|
||||
\022\060\020\006\003\125\004\012\014\011\103\157\155\155\123\143
|
||||
\157\160\145\061\053\060\051\006\003\125\004\003\014\042\103\157
|
||||
\155\155\123\143\157\160\145\040\120\165\142\154\151\143\040\124
|
||||
\162\165\163\164\040\105\103\103\040\122\157\157\164\055\060\061
|
||||
END
|
||||
CKA_ID UTF8 "0"
|
||||
CKA_ISSUER MULTILINE_OCTAL
|
||||
\060\116\061\013\060\011\006\003\125\004\006\023\002\125\123\061
|
||||
\022\060\020\006\003\125\004\012\014\011\103\157\155\155\123\143
|
||||
\157\160\145\061\053\060\051\006\003\125\004\003\014\042\103\157
|
||||
\155\155\123\143\157\160\145\040\120\165\142\154\151\143\040\124
|
||||
\162\165\163\164\040\105\103\103\040\122\157\157\164\055\060\061
|
||||
END
|
||||
CKA_SERIAL_NUMBER MULTILINE_OCTAL
|
||||
\002\024\103\160\202\167\317\115\135\064\361\312\256\062\057\067
|
||||
\367\364\177\165\240\236
|
||||
END
|
||||
CKA_VALUE MULTILINE_OCTAL
|
||||
\060\202\002\035\060\202\001\243\240\003\002\001\002\002\024\103
|
||||
\160\202\167\317\115\135\064\361\312\256\062\057\067\367\364\177
|
||||
\165\240\236\060\012\006\010\052\206\110\316\075\004\003\003\060
|
||||
\116\061\013\060\011\006\003\125\004\006\023\002\125\123\061\022
|
||||
\060\020\006\003\125\004\012\014\011\103\157\155\155\123\143\157
|
||||
\160\145\061\053\060\051\006\003\125\004\003\014\042\103\157\155
|
||||
\155\123\143\157\160\145\040\120\165\142\154\151\143\040\124\162
|
||||
\165\163\164\040\105\103\103\040\122\157\157\164\055\060\061\060
|
||||
\036\027\015\062\061\060\064\062\070\061\067\063\065\064\063\132
|
||||
\027\015\064\066\060\064\062\070\061\067\063\065\064\062\132\060
|
||||
\116\061\013\060\011\006\003\125\004\006\023\002\125\123\061\022
|
||||
\060\020\006\003\125\004\012\014\011\103\157\155\155\123\143\157
|
||||
\160\145\061\053\060\051\006\003\125\004\003\014\042\103\157\155
|
||||
\155\123\143\157\160\145\040\120\165\142\154\151\143\040\124\162
|
||||
\165\163\164\040\105\103\103\040\122\157\157\164\055\060\061\060
|
||||
\166\060\020\006\007\052\206\110\316\075\002\001\006\005\053\201
|
||||
\004\000\042\003\142\000\004\113\066\351\256\127\136\250\160\327
|
||||
\320\217\164\142\167\303\136\172\252\345\266\242\361\170\375\002
|
||||
\176\127\335\221\171\234\154\271\122\210\124\274\057\004\276\270
|
||||
\315\366\020\321\051\354\265\320\240\303\360\211\160\031\273\121
|
||||
\145\305\103\234\303\233\143\235\040\203\076\006\013\246\102\104
|
||||
\205\021\247\112\072\055\351\326\150\057\110\116\123\053\007\077
|
||||
\115\275\271\254\167\071\127\243\102\060\100\060\017\006\003\125
|
||||
\035\023\001\001\377\004\005\060\003\001\001\377\060\016\006\003
|
||||
\125\035\017\001\001\377\004\004\003\002\001\006\060\035\006\003
|
||||
\125\035\016\004\026\004\024\216\007\142\300\120\335\306\031\006
|
||||
\000\106\164\004\367\363\256\175\165\115\060\060\012\006\010\052
|
||||
\206\110\316\075\004\003\003\003\150\000\060\145\002\061\000\234
|
||||
\063\337\101\343\043\250\102\066\046\227\065\134\173\353\333\113
|
||||
\370\252\213\163\125\025\134\254\170\051\017\272\041\330\304\240
|
||||
\330\321\003\335\155\321\071\075\304\223\140\322\343\162\262\002
|
||||
\060\174\305\176\210\323\120\365\036\045\350\372\116\165\346\130
|
||||
\226\244\065\137\033\145\352\141\232\160\043\265\015\243\233\222
|
||||
\122\157\151\240\214\215\112\320\356\213\016\313\107\216\320\215
|
||||
\021
|
||||
END
|
||||
CKA_NSS_MOZILLA_CA_POLICY CK_BBOOL CK_TRUE
|
||||
CKA_NSS_SERVER_DISTRUST_AFTER CK_BBOOL CK_FALSE
|
||||
CKA_NSS_EMAIL_DISTRUST_AFTER CK_BBOOL CK_FALSE
|
||||
|
||||
# Trust for "CommScope Public Trust ECC Root-01"
|
||||
# Issuer: CN=CommScope Public Trust ECC Root-01,O=CommScope,C=US
|
||||
# Serial Number:43:70:82:77:cf:4d:5d:34:f1:ca:ae:32:2f:37:f7:f4:7f:75:a0:9e
|
||||
# Subject: CN=CommScope Public Trust ECC Root-01,O=CommScope,C=US
|
||||
# Not Valid Before: Wed Apr 28 17:35:43 2021
|
||||
# Not Valid After : Sat Apr 28 17:35:42 2046
|
||||
# Fingerprint (SHA-256): 11:43:7C:DA:7B:B4:5E:41:36:5F:45:B3:9A:38:98:6B:0D:E0:0D:EF:34:8E:0C:7B:B0:87:36:33:80:0B:C3:8B
|
||||
# Fingerprint (SHA1): 07:86:C0:D8:DD:8E:C0:80:98:06:98:D0:58:7A:EF:DE:A6:CC:A2:5D
|
||||
CKA_CLASS CK_OBJECT_CLASS CKO_NSS_TRUST
|
||||
CKA_TOKEN CK_BBOOL CK_TRUE
|
||||
CKA_PRIVATE CK_BBOOL CK_FALSE
|
||||
CKA_MODIFIABLE CK_BBOOL CK_FALSE
|
||||
CKA_LABEL UTF8 "CommScope Public Trust ECC Root-01"
|
||||
CKA_CERT_SHA1_HASH MULTILINE_OCTAL
|
||||
\007\206\300\330\335\216\300\200\230\006\230\320\130\172\357\336
|
||||
\246\314\242\135
|
||||
END
|
||||
CKA_CERT_MD5_HASH MULTILINE_OCTAL
|
||||
\072\100\247\374\003\214\234\070\171\057\072\242\154\266\012\026
|
||||
END
|
||||
CKA_ISSUER MULTILINE_OCTAL
|
||||
\060\116\061\013\060\011\006\003\125\004\006\023\002\125\123\061
|
||||
\022\060\020\006\003\125\004\012\014\011\103\157\155\155\123\143
|
||||
\157\160\145\061\053\060\051\006\003\125\004\003\014\042\103\157
|
||||
\155\155\123\143\157\160\145\040\120\165\142\154\151\143\040\124
|
||||
\162\165\163\164\040\105\103\103\040\122\157\157\164\055\060\061
|
||||
END
|
||||
CKA_SERIAL_NUMBER MULTILINE_OCTAL
|
||||
\002\024\103\160\202\167\317\115\135\064\361\312\256\062\057\067
|
||||
\367\364\177\165\240\236
|
||||
END
|
||||
CKA_TRUST_SERVER_AUTH CK_TRUST CKT_NSS_TRUSTED_DELEGATOR
|
||||
CKA_TRUST_EMAIL_PROTECTION CK_TRUST CKT_NSS_MUST_VERIFY_TRUST
|
||||
CKA_TRUST_CODE_SIGNING CK_TRUST CKT_NSS_MUST_VERIFY_TRUST
|
||||
CKA_TRUST_STEP_UP_APPROVED CK_BBOOL CK_FALSE
|
||||
|
||||
#
|
||||
# Certificate "CommScope Public Trust ECC Root-02"
|
||||
#
|
||||
# Issuer: CN=CommScope Public Trust ECC Root-02,O=CommScope,C=US
|
||||
# Serial Number:28:fd:99:60:41:47:a6:01:3a:ca:14:7b:1f:ef:f9:68:08:83:5d:7d
|
||||
# Subject: CN=CommScope Public Trust ECC Root-02,O=CommScope,C=US
|
||||
# Not Valid Before: Wed Apr 28 17:44:54 2021
|
||||
# Not Valid After : Sat Apr 28 17:44:53 2046
|
||||
# Fingerprint (SHA-256): 2F:FB:7F:81:3B:BB:B3:C8:9A:B4:E8:16:2D:0F:16:D7:15:09:A8:30:CC:9D:73:C2:62:E5:14:08:75:D1:AD:4A
|
||||
# Fingerprint (SHA1): 3C:3F:EF:57:0F:FE:65:93:86:9E:A0:FE:B0:F6:ED:8E:D1:13:C7:E5
|
||||
CKA_CLASS CK_OBJECT_CLASS CKO_CERTIFICATE
|
||||
CKA_TOKEN CK_BBOOL CK_TRUE
|
||||
CKA_PRIVATE CK_BBOOL CK_FALSE
|
||||
CKA_MODIFIABLE CK_BBOOL CK_FALSE
|
||||
CKA_LABEL UTF8 "CommScope Public Trust ECC Root-02"
|
||||
CKA_CERTIFICATE_TYPE CK_CERTIFICATE_TYPE CKC_X_509
|
||||
CKA_SUBJECT MULTILINE_OCTAL
|
||||
\060\116\061\013\060\011\006\003\125\004\006\023\002\125\123\061
|
||||
\022\060\020\006\003\125\004\012\014\011\103\157\155\155\123\143
|
||||
\157\160\145\061\053\060\051\006\003\125\004\003\014\042\103\157
|
||||
\155\155\123\143\157\160\145\040\120\165\142\154\151\143\040\124
|
||||
\162\165\163\164\040\105\103\103\040\122\157\157\164\055\060\062
|
||||
END
|
||||
CKA_ID UTF8 "0"
|
||||
CKA_ISSUER MULTILINE_OCTAL
|
||||
\060\116\061\013\060\011\006\003\125\004\006\023\002\125\123\061
|
||||
\022\060\020\006\003\125\004\012\014\011\103\157\155\155\123\143
|
||||
\157\160\145\061\053\060\051\006\003\125\004\003\014\042\103\157
|
||||
\155\155\123\143\157\160\145\040\120\165\142\154\151\143\040\124
|
||||
\162\165\163\164\040\105\103\103\040\122\157\157\164\055\060\062
|
||||
END
|
||||
CKA_SERIAL_NUMBER MULTILINE_OCTAL
|
||||
\002\024\050\375\231\140\101\107\246\001\072\312\024\173\037\357
|
||||
\371\150\010\203\135\175
|
||||
END
|
||||
CKA_VALUE MULTILINE_OCTAL
|
||||
\060\202\002\034\060\202\001\243\240\003\002\001\002\002\024\050
|
||||
\375\231\140\101\107\246\001\072\312\024\173\037\357\371\150\010
|
||||
\203\135\175\060\012\006\010\052\206\110\316\075\004\003\003\060
|
||||
\116\061\013\060\011\006\003\125\004\006\023\002\125\123\061\022
|
||||
\060\020\006\003\125\004\012\014\011\103\157\155\155\123\143\157
|
||||
\160\145\061\053\060\051\006\003\125\004\003\014\042\103\157\155
|
||||
\155\123\143\157\160\145\040\120\165\142\154\151\143\040\124\162
|
||||
\165\163\164\040\105\103\103\040\122\157\157\164\055\060\062\060
|
||||
\036\027\015\062\061\060\064\062\070\061\067\064\064\065\064\132
|
||||
\027\015\064\066\060\064\062\070\061\067\064\064\065\063\132\060
|
||||
\116\061\013\060\011\006\003\125\004\006\023\002\125\123\061\022
|
||||
\060\020\006\003\125\004\012\014\011\103\157\155\155\123\143\157
|
||||
\160\145\061\053\060\051\006\003\125\004\003\014\042\103\157\155
|
||||
\155\123\143\157\160\145\040\120\165\142\154\151\143\040\124\162
|
||||
\165\163\164\040\105\103\103\040\122\157\157\164\055\060\062\060
|
||||
\166\060\020\006\007\052\206\110\316\075\002\001\006\005\053\201
|
||||
\004\000\042\003\142\000\004\170\060\201\350\143\036\345\353\161
|
||||
\121\017\367\007\007\312\071\231\174\116\325\017\314\060\060\013
|
||||
\217\146\223\076\317\275\305\206\275\371\261\267\264\076\264\007
|
||||
\310\363\226\061\363\355\244\117\370\243\116\215\051\025\130\270
|
||||
\325\157\177\356\154\042\265\260\257\110\105\012\275\250\111\224
|
||||
\277\204\103\260\333\204\112\003\043\031\147\152\157\301\156\274
|
||||
\006\071\067\321\210\042\367\243\102\060\100\060\017\006\003\125
|
||||
\035\023\001\001\377\004\005\060\003\001\001\377\060\016\006\003
|
||||
\125\035\017\001\001\377\004\004\003\002\001\006\060\035\006\003
|
||||
\125\035\016\004\026\004\024\346\030\165\377\357\140\336\204\244
|
||||
\365\106\307\336\112\125\343\062\066\171\365\060\012\006\010\052
|
||||
\206\110\316\075\004\003\003\003\147\000\060\144\002\060\046\163
|
||||
\111\172\266\253\346\111\364\175\122\077\324\101\004\256\200\103
|
||||
\203\145\165\271\205\200\070\073\326\157\344\223\206\253\217\347
|
||||
\211\310\177\233\176\153\012\022\125\141\252\021\340\171\002\060
|
||||
\167\350\061\161\254\074\161\003\326\204\046\036\024\270\363\073
|
||||
\073\336\355\131\374\153\114\060\177\131\316\105\351\163\140\025
|
||||
\232\114\360\346\136\045\042\025\155\302\207\131\320\262\216\152
|
||||
END
|
||||
CKA_NSS_MOZILLA_CA_POLICY CK_BBOOL CK_TRUE
|
||||
CKA_NSS_SERVER_DISTRUST_AFTER CK_BBOOL CK_FALSE
|
||||
CKA_NSS_EMAIL_DISTRUST_AFTER CK_BBOOL CK_FALSE
|
||||
|
||||
# Trust for "CommScope Public Trust ECC Root-02"
|
||||
# Issuer: CN=CommScope Public Trust ECC Root-02,O=CommScope,C=US
|
||||
# Serial Number:28:fd:99:60:41:47:a6:01:3a:ca:14:7b:1f:ef:f9:68:08:83:5d:7d
|
||||
# Subject: CN=CommScope Public Trust ECC Root-02,O=CommScope,C=US
|
||||
# Not Valid Before: Wed Apr 28 17:44:54 2021
|
||||
# Not Valid After : Sat Apr 28 17:44:53 2046
|
||||
# Fingerprint (SHA-256): 2F:FB:7F:81:3B:BB:B3:C8:9A:B4:E8:16:2D:0F:16:D7:15:09:A8:30:CC:9D:73:C2:62:E5:14:08:75:D1:AD:4A
|
||||
# Fingerprint (SHA1): 3C:3F:EF:57:0F:FE:65:93:86:9E:A0:FE:B0:F6:ED:8E:D1:13:C7:E5
|
||||
CKA_CLASS CK_OBJECT_CLASS CKO_NSS_TRUST
|
||||
CKA_TOKEN CK_BBOOL CK_TRUE
|
||||
CKA_PRIVATE CK_BBOOL CK_FALSE
|
||||
CKA_MODIFIABLE CK_BBOOL CK_FALSE
|
||||
CKA_LABEL UTF8 "CommScope Public Trust ECC Root-02"
|
||||
CKA_CERT_SHA1_HASH MULTILINE_OCTAL
|
||||
\074\077\357\127\017\376\145\223\206\236\240\376\260\366\355\216
|
||||
\321\023\307\345
|
||||
END
|
||||
CKA_CERT_MD5_HASH MULTILINE_OCTAL
|
||||
\131\260\104\325\145\115\270\134\125\031\222\002\266\321\224\262
|
||||
END
|
||||
CKA_ISSUER MULTILINE_OCTAL
|
||||
\060\116\061\013\060\011\006\003\125\004\006\023\002\125\123\061
|
||||
\022\060\020\006\003\125\004\012\014\011\103\157\155\155\123\143
|
||||
\157\160\145\061\053\060\051\006\003\125\004\003\014\042\103\157
|
||||
\155\155\123\143\157\160\145\040\120\165\142\154\151\143\040\124
|
||||
\162\165\163\164\040\105\103\103\040\122\157\157\164\055\060\062
|
||||
END
|
||||
CKA_SERIAL_NUMBER MULTILINE_OCTAL
|
||||
\002\024\050\375\231\140\101\107\246\001\072\312\024\173\037\357
|
||||
\371\150\010\203\135\175
|
||||
END
|
||||
CKA_TRUST_SERVER_AUTH CK_TRUST CKT_NSS_TRUSTED_DELEGATOR
|
||||
CKA_TRUST_EMAIL_PROTECTION CK_TRUST CKT_NSS_MUST_VERIFY_TRUST
|
||||
CKA_TRUST_CODE_SIGNING CK_TRUST CKT_NSS_MUST_VERIFY_TRUST
|
||||
CKA_TRUST_STEP_UP_APPROVED CK_BBOOL CK_FALSE
|
||||
|
||||
#
|
||||
# Certificate "CommScope Public Trust RSA Root-01"
|
||||
#
|
||||
# Issuer: CN=CommScope Public Trust RSA Root-01,O=CommScope,C=US
|
||||
# Serial Number:3e:03:49:81:75:16:74:31:8e:4c:ab:d5:c5:90:29:96:c5:39:10:dd
|
||||
# Subject: CN=CommScope Public Trust RSA Root-01,O=CommScope,C=US
|
||||
# Not Valid Before: Wed Apr 28 16:45:54 2021
|
||||
# Not Valid After : Sat Apr 28 16:45:53 2046
|
||||
# Fingerprint (SHA-256): 02:BD:F9:6E:2A:45:DD:9B:F1:8F:C7:E1:DB:DF:21:A0:37:9B:A3:C9:C2:61:03:44:CF:D8:D6:06:FE:C1:ED:81
|
||||
# Fingerprint (SHA1): 6D:0A:5F:F7:B4:23:06:B4:85:B3:B7:97:64:FC:AC:75:F5:33:F2:93
|
||||
CKA_CLASS CK_OBJECT_CLASS CKO_CERTIFICATE
|
||||
CKA_TOKEN CK_BBOOL CK_TRUE
|
||||
CKA_PRIVATE CK_BBOOL CK_FALSE
|
||||
CKA_MODIFIABLE CK_BBOOL CK_FALSE
|
||||
CKA_LABEL UTF8 "CommScope Public Trust RSA Root-01"
|
||||
CKA_CERTIFICATE_TYPE CK_CERTIFICATE_TYPE CKC_X_509
|
||||
CKA_SUBJECT MULTILINE_OCTAL
|
||||
\060\116\061\013\060\011\006\003\125\004\006\023\002\125\123\061
|
||||
\022\060\020\006\003\125\004\012\014\011\103\157\155\155\123\143
|
||||
\157\160\145\061\053\060\051\006\003\125\004\003\014\042\103\157
|
||||
\155\155\123\143\157\160\145\040\120\165\142\154\151\143\040\124
|
||||
\162\165\163\164\040\122\123\101\040\122\157\157\164\055\060\061
|
||||
END
|
||||
CKA_ID UTF8 "0"
|
||||
CKA_ISSUER MULTILINE_OCTAL
|
||||
\060\116\061\013\060\011\006\003\125\004\006\023\002\125\123\061
|
||||
\022\060\020\006\003\125\004\012\014\011\103\157\155\155\123\143
|
||||
\157\160\145\061\053\060\051\006\003\125\004\003\014\042\103\157
|
||||
\155\155\123\143\157\160\145\040\120\165\142\154\151\143\040\124
|
||||
\162\165\163\164\040\122\123\101\040\122\157\157\164\055\060\061
|
||||
END
|
||||
CKA_SERIAL_NUMBER MULTILINE_OCTAL
|
||||
\002\024\076\003\111\201\165\026\164\061\216\114\253\325\305\220
|
||||
\051\226\305\071\020\335
|
||||
END
|
||||
CKA_VALUE MULTILINE_OCTAL
|
||||
\060\202\005\154\060\202\003\124\240\003\002\001\002\002\024\076
|
||||
\003\111\201\165\026\164\061\216\114\253\325\305\220\051\226\305
|
||||
\071\020\335\060\015\006\011\052\206\110\206\367\015\001\001\013
|
||||
\005\000\060\116\061\013\060\011\006\003\125\004\006\023\002\125
|
||||
\123\061\022\060\020\006\003\125\004\012\014\011\103\157\155\155
|
||||
\123\143\157\160\145\061\053\060\051\006\003\125\004\003\014\042
|
||||
\103\157\155\155\123\143\157\160\145\040\120\165\142\154\151\143
|
||||
\040\124\162\165\163\164\040\122\123\101\040\122\157\157\164\055
|
||||
\060\061\060\036\027\015\062\061\060\064\062\070\061\066\064\065
|
||||
\065\064\132\027\015\064\066\060\064\062\070\061\066\064\065\065
|
||||
\063\132\060\116\061\013\060\011\006\003\125\004\006\023\002\125
|
||||
\123\061\022\060\020\006\003\125\004\012\014\011\103\157\155\155
|
||||
\123\143\157\160\145\061\053\060\051\006\003\125\004\003\014\042
|
||||
\103\157\155\155\123\143\157\160\145\040\120\165\142\154\151\143
|
||||
\040\124\162\165\163\164\040\122\123\101\040\122\157\157\164\055
|
||||
\060\061\060\202\002\042\060\015\006\011\052\206\110\206\367\015
|
||||
\001\001\001\005\000\003\202\002\017\000\060\202\002\012\002\202
|
||||
\002\001\000\260\110\145\243\015\035\102\343\221\155\235\204\244
|
||||
\141\226\022\302\355\303\332\043\064\031\166\366\352\375\125\132
|
||||
\366\125\001\123\017\362\314\214\227\117\271\120\313\263\001\104
|
||||
\126\226\375\233\050\354\173\164\013\347\102\153\125\316\311\141
|
||||
\262\350\255\100\074\272\271\101\012\005\117\033\046\205\217\103
|
||||
\265\100\265\205\321\324\161\334\203\101\363\366\105\307\200\242
|
||||
\204\120\227\106\316\240\014\304\140\126\004\035\007\133\106\245
|
||||
\016\262\113\244\016\245\174\356\370\324\142\003\271\223\152\212
|
||||
\024\270\160\370\056\202\106\070\043\016\164\307\153\101\267\320
|
||||
\051\243\235\200\260\176\167\223\143\102\373\064\203\073\163\243
|
||||
\132\041\066\353\107\372\030\027\331\272\146\302\223\244\217\374
|
||||
\135\244\255\374\120\152\225\254\274\044\063\321\275\210\177\206
|
||||
\365\365\262\163\052\217\174\257\010\362\032\230\077\251\201\145
|
||||
\077\301\214\211\305\226\060\232\012\317\364\324\310\064\355\235
|
||||
\057\274\215\070\206\123\356\227\237\251\262\143\224\027\215\017
|
||||
\334\146\052\174\122\121\165\313\231\216\350\075\134\277\236\073
|
||||
\050\215\203\002\017\251\237\162\342\054\053\263\334\146\227\000
|
||||
\100\320\244\124\216\233\135\173\105\066\046\326\162\103\353\317
|
||||
\300\352\015\334\316\022\346\175\070\237\005\047\250\227\076\351
|
||||
\121\306\154\005\050\301\002\017\351\030\155\354\275\234\006\324
|
||||
\247\111\364\124\005\153\154\060\361\353\003\325\352\075\152\166
|
||||
\302\313\032\050\111\115\177\144\340\372\053\332\163\203\201\377
|
||||
\221\003\275\224\273\344\270\216\234\062\143\315\237\273\150\201
|
||||
\261\204\133\257\066\277\167\356\035\177\367\111\233\122\354\322
|
||||
\167\132\175\221\235\115\302\071\055\344\272\202\370\157\362\116
|
||||
\036\017\116\346\077\131\245\043\334\075\207\250\050\130\050\321
|
||||
\361\033\066\333\117\304\377\341\214\133\162\214\307\046\003\047
|
||||
\243\071\012\001\252\300\262\061\140\203\042\241\117\022\011\001
|
||||
\021\257\064\324\317\327\256\142\323\005\007\264\061\165\340\015
|
||||
\155\127\117\151\207\371\127\251\272\025\366\310\122\155\241\313
|
||||
\234\037\345\374\170\250\065\232\237\101\024\316\245\264\316\224
|
||||
\010\034\011\255\126\345\332\266\111\232\112\352\143\030\123\234
|
||||
\054\056\303\002\003\001\000\001\243\102\060\100\060\017\006\003
|
||||
\125\035\023\001\001\377\004\005\060\003\001\001\377\060\016\006
|
||||
\003\125\035\017\001\001\377\004\004\003\002\001\006\060\035\006
|
||||
\003\125\035\016\004\026\004\024\067\135\246\232\164\062\302\302
|
||||
\371\307\246\025\020\131\270\344\375\345\270\155\060\015\006\011
|
||||
\052\206\110\206\367\015\001\001\013\005\000\003\202\002\001\000
|
||||
\257\247\317\336\377\340\275\102\215\115\345\042\226\337\150\352
|
||||
\175\115\052\175\320\255\075\026\134\103\347\175\300\206\350\172
|
||||
\065\143\361\314\201\310\306\013\350\056\122\065\244\246\111\220
|
||||
\143\121\254\064\254\005\073\127\000\351\323\142\323\331\051\325
|
||||
\124\276\034\020\221\234\262\155\376\131\375\171\367\352\126\320
|
||||
\236\150\124\102\217\046\122\342\114\337\057\227\246\057\322\007
|
||||
\230\250\363\140\135\113\232\130\127\210\357\202\345\372\257\154
|
||||
\201\113\222\217\100\232\223\106\131\313\137\170\026\261\147\076
|
||||
\102\013\337\050\331\260\255\230\040\276\103\174\321\136\032\011
|
||||
\027\044\215\173\135\225\351\253\301\140\253\133\030\144\200\373
|
||||
\255\340\006\175\035\312\131\270\363\170\051\147\306\126\035\257
|
||||
\266\265\164\052\166\241\077\373\165\060\237\224\136\073\245\140
|
||||
\363\313\134\014\342\016\311\140\370\311\037\026\212\046\335\347
|
||||
\047\177\353\045\246\212\275\270\055\066\020\232\261\130\115\232
|
||||
\150\117\140\124\345\366\106\023\216\210\254\274\041\102\022\255
|
||||
\306\112\211\175\233\301\330\055\351\226\003\364\242\164\014\274
|
||||
\000\035\277\326\067\045\147\264\162\213\257\205\275\352\052\003
|
||||
\217\314\373\074\104\044\202\342\001\245\013\131\266\064\215\062
|
||||
\013\022\015\353\047\302\375\101\327\100\074\162\106\051\300\214
|
||||
\352\272\017\361\006\223\056\367\234\250\364\140\076\243\361\070
|
||||
\136\216\023\301\263\072\227\207\077\222\312\170\251\034\257\320
|
||||
\260\033\046\036\276\160\354\172\365\063\230\352\134\377\053\013
|
||||
\004\116\103\335\143\176\016\247\116\170\003\225\076\324\055\060
|
||||
\225\021\020\050\056\277\240\002\076\377\136\131\323\005\016\225
|
||||
\137\123\105\357\153\207\325\110\315\026\246\226\203\341\337\263
|
||||
\006\363\301\024\333\247\354\034\213\135\220\220\015\162\121\347
|
||||
\141\371\024\312\257\203\217\277\257\261\012\131\135\334\134\327
|
||||
\344\226\255\133\140\035\332\256\227\262\071\331\006\365\166\000
|
||||
\023\370\150\114\041\260\065\304\334\125\262\311\301\101\132\034
|
||||
\211\300\214\157\164\240\153\063\115\265\001\050\375\255\255\211
|
||||
\027\073\246\232\204\274\353\214\352\304\161\044\250\272\051\371
|
||||
\010\262\047\126\065\062\137\352\071\373\061\232\325\031\314\360
|
||||
END
|
||||
CKA_NSS_MOZILLA_CA_POLICY CK_BBOOL CK_TRUE
|
||||
CKA_NSS_SERVER_DISTRUST_AFTER CK_BBOOL CK_FALSE
|
||||
CKA_NSS_EMAIL_DISTRUST_AFTER CK_BBOOL CK_FALSE
|
||||
|
||||
# Trust for "CommScope Public Trust RSA Root-01"
|
||||
# Issuer: CN=CommScope Public Trust RSA Root-01,O=CommScope,C=US
|
||||
# Serial Number:3e:03:49:81:75:16:74:31:8e:4c:ab:d5:c5:90:29:96:c5:39:10:dd
|
||||
# Subject: CN=CommScope Public Trust RSA Root-01,O=CommScope,C=US
|
||||
# Not Valid Before: Wed Apr 28 16:45:54 2021
|
||||
# Not Valid After : Sat Apr 28 16:45:53 2046
|
||||
# Fingerprint (SHA-256): 02:BD:F9:6E:2A:45:DD:9B:F1:8F:C7:E1:DB:DF:21:A0:37:9B:A3:C9:C2:61:03:44:CF:D8:D6:06:FE:C1:ED:81
|
||||
# Fingerprint (SHA1): 6D:0A:5F:F7:B4:23:06:B4:85:B3:B7:97:64:FC:AC:75:F5:33:F2:93
|
||||
CKA_CLASS CK_OBJECT_CLASS CKO_NSS_TRUST
|
||||
CKA_TOKEN CK_BBOOL CK_TRUE
|
||||
CKA_PRIVATE CK_BBOOL CK_FALSE
|
||||
CKA_MODIFIABLE CK_BBOOL CK_FALSE
|
||||
CKA_LABEL UTF8 "CommScope Public Trust RSA Root-01"
|
||||
CKA_CERT_SHA1_HASH MULTILINE_OCTAL
|
||||
\155\012\137\367\264\043\006\264\205\263\267\227\144\374\254\165
|
||||
\365\063\362\223
|
||||
END
|
||||
CKA_CERT_MD5_HASH MULTILINE_OCTAL
|
||||
\016\264\025\274\207\143\135\135\002\163\324\046\070\150\163\330
|
||||
END
|
||||
CKA_ISSUER MULTILINE_OCTAL
|
||||
\060\116\061\013\060\011\006\003\125\004\006\023\002\125\123\061
|
||||
\022\060\020\006\003\125\004\012\014\011\103\157\155\155\123\143
|
||||
\157\160\145\061\053\060\051\006\003\125\004\003\014\042\103\157
|
||||
\155\155\123\143\157\160\145\040\120\165\142\154\151\143\040\124
|
||||
\162\165\163\164\040\122\123\101\040\122\157\157\164\055\060\061
|
||||
END
|
||||
CKA_SERIAL_NUMBER MULTILINE_OCTAL
|
||||
\002\024\076\003\111\201\165\026\164\061\216\114\253\325\305\220
|
||||
\051\226\305\071\020\335
|
||||
END
|
||||
CKA_TRUST_SERVER_AUTH CK_TRUST CKT_NSS_TRUSTED_DELEGATOR
|
||||
CKA_TRUST_EMAIL_PROTECTION CK_TRUST CKT_NSS_MUST_VERIFY_TRUST
|
||||
CKA_TRUST_CODE_SIGNING CK_TRUST CKT_NSS_MUST_VERIFY_TRUST
|
||||
CKA_TRUST_STEP_UP_APPROVED CK_BBOOL CK_FALSE
|
||||
|
||||
#
|
||||
# Certificate "CommScope Public Trust RSA Root-02"
|
||||
#
|
||||
# Issuer: CN=CommScope Public Trust RSA Root-02,O=CommScope,C=US
|
||||
# Serial Number:54:16:bf:3b:7e:39:95:71:8d:d1:aa:00:a5:86:0d:2b:8f:7a:05:4e
|
||||
# Subject: CN=CommScope Public Trust RSA Root-02,O=CommScope,C=US
|
||||
# Not Valid Before: Wed Apr 28 17:16:43 2021
|
||||
# Not Valid After : Sat Apr 28 17:16:42 2046
|
||||
# Fingerprint (SHA-256): FF:E9:43:D7:93:42:4B:4F:7C:44:0C:1C:3D:64:8D:53:63:F3:4B:82:DC:87:AA:7A:9F:11:8F:C5:DE:E1:01:F1
|
||||
# Fingerprint (SHA1): EA:B0:E2:52:1B:89:93:4C:11:68:F2:D8:9A:AC:22:4C:A3:8A:57:AE
|
||||
CKA_CLASS CK_OBJECT_CLASS CKO_CERTIFICATE
|
||||
CKA_TOKEN CK_BBOOL CK_TRUE
|
||||
CKA_PRIVATE CK_BBOOL CK_FALSE
|
||||
CKA_MODIFIABLE CK_BBOOL CK_FALSE
|
||||
CKA_LABEL UTF8 "CommScope Public Trust RSA Root-02"
|
||||
CKA_CERTIFICATE_TYPE CK_CERTIFICATE_TYPE CKC_X_509
|
||||
CKA_SUBJECT MULTILINE_OCTAL
|
||||
\060\116\061\013\060\011\006\003\125\004\006\023\002\125\123\061
|
||||
\022\060\020\006\003\125\004\012\014\011\103\157\155\155\123\143
|
||||
\157\160\145\061\053\060\051\006\003\125\004\003\014\042\103\157
|
||||
\155\155\123\143\157\160\145\040\120\165\142\154\151\143\040\124
|
||||
\162\165\163\164\040\122\123\101\040\122\157\157\164\055\060\062
|
||||
END
|
||||
CKA_ID UTF8 "0"
|
||||
CKA_ISSUER MULTILINE_OCTAL
|
||||
\060\116\061\013\060\011\006\003\125\004\006\023\002\125\123\061
|
||||
\022\060\020\006\003\125\004\012\014\011\103\157\155\155\123\143
|
||||
\157\160\145\061\053\060\051\006\003\125\004\003\014\042\103\157
|
||||
\155\155\123\143\157\160\145\040\120\165\142\154\151\143\040\124
|
||||
\162\165\163\164\040\122\123\101\040\122\157\157\164\055\060\062
|
||||
END
|
||||
CKA_SERIAL_NUMBER MULTILINE_OCTAL
|
||||
\002\024\124\026\277\073\176\071\225\161\215\321\252\000\245\206
|
||||
\015\053\217\172\005\116
|
||||
END
|
||||
CKA_VALUE MULTILINE_OCTAL
|
||||
\060\202\005\154\060\202\003\124\240\003\002\001\002\002\024\124
|
||||
\026\277\073\176\071\225\161\215\321\252\000\245\206\015\053\217
|
||||
\172\005\116\060\015\006\011\052\206\110\206\367\015\001\001\013
|
||||
\005\000\060\116\061\013\060\011\006\003\125\004\006\023\002\125
|
||||
\123\061\022\060\020\006\003\125\004\012\014\011\103\157\155\155
|
||||
\123\143\157\160\145\061\053\060\051\006\003\125\004\003\014\042
|
||||
\103\157\155\155\123\143\157\160\145\040\120\165\142\154\151\143
|
||||
\040\124\162\165\163\164\040\122\123\101\040\122\157\157\164\055
|
||||
\060\062\060\036\027\015\062\061\060\064\062\070\061\067\061\066
|
||||
\064\063\132\027\015\064\066\060\064\062\070\061\067\061\066\064
|
||||
\062\132\060\116\061\013\060\011\006\003\125\004\006\023\002\125
|
||||
\123\061\022\060\020\006\003\125\004\012\014\011\103\157\155\155
|
||||
\123\143\157\160\145\061\053\060\051\006\003\125\004\003\014\042
|
||||
\103\157\155\155\123\143\157\160\145\040\120\165\142\154\151\143
|
||||
\040\124\162\165\163\164\040\122\123\101\040\122\157\157\164\055
|
||||
\060\062\060\202\002\042\060\015\006\011\052\206\110\206\367\015
|
||||
\001\001\001\005\000\003\202\002\017\000\060\202\002\012\002\202
|
||||
\002\001\000\341\372\016\373\150\000\022\310\115\325\254\042\304
|
||||
\065\001\073\305\124\345\131\166\143\245\177\353\301\304\152\230
|
||||
\275\062\215\027\200\353\135\272\321\142\075\045\043\031\065\024
|
||||
\351\177\211\247\033\142\074\326\120\347\064\225\003\062\261\264
|
||||
\223\042\075\247\342\261\355\346\173\116\056\207\233\015\063\165
|
||||
\012\336\252\065\347\176\345\066\230\242\256\045\236\225\263\062
|
||||
\226\244\053\130\036\357\077\376\142\064\110\121\321\264\215\102
|
||||
\255\140\332\111\152\225\160\335\322\000\342\314\127\143\002\173
|
||||
\226\335\111\227\133\222\116\225\323\371\313\051\037\030\112\370
|
||||
\001\052\322\143\011\156\044\351\211\322\345\307\042\114\334\163
|
||||
\206\107\000\252\015\210\216\256\205\175\112\351\273\063\117\016
|
||||
\122\160\235\225\343\174\155\226\133\055\075\137\241\203\106\135
|
||||
\266\343\045\270\174\247\031\200\034\352\145\103\334\221\171\066
|
||||
\054\164\174\362\147\006\311\211\311\333\277\332\150\277\043\355
|
||||
\334\153\255\050\203\171\057\354\070\245\015\067\001\147\047\232
|
||||
\351\063\331\063\137\067\241\305\360\253\075\372\170\260\347\054
|
||||
\237\366\076\237\140\340\357\110\351\220\105\036\005\121\170\032
|
||||
\054\022\054\134\050\254\015\242\043\236\064\217\005\346\242\063
|
||||
\316\021\167\023\324\016\244\036\102\037\206\315\160\376\331\056
|
||||
\025\075\035\273\270\362\123\127\333\314\306\164\051\234\030\263
|
||||
\066\165\070\056\017\124\241\370\222\037\211\226\117\273\324\356
|
||||
\235\351\073\066\102\265\012\073\052\324\144\171\066\020\341\371
|
||||
\221\003\053\173\040\124\315\015\031\032\310\101\062\064\321\260
|
||||
\231\341\220\036\001\100\066\265\267\372\251\345\167\165\244\042
|
||||
\201\135\260\213\344\047\022\017\124\210\306\333\205\164\346\267
|
||||
\300\327\246\051\372\333\336\363\223\227\047\004\125\057\012\157
|
||||
\067\305\075\023\257\012\000\251\054\213\034\201\050\327\357\206
|
||||
\061\251\256\362\156\270\312\152\054\124\107\330\052\210\056\257
|
||||
\301\007\020\170\254\021\242\057\102\360\067\305\362\270\126\335
|
||||
\016\142\055\316\055\126\176\125\362\247\104\366\053\062\364\043
|
||||
\250\107\350\324\052\001\170\317\152\303\067\250\236\145\322\054
|
||||
\345\372\272\063\301\006\104\366\346\317\245\015\247\146\010\064
|
||||
\212\054\363\002\003\001\000\001\243\102\060\100\060\017\006\003
|
||||
\125\035\023\001\001\377\004\005\060\003\001\001\377\060\016\006
|
||||
\003\125\035\017\001\001\377\004\004\003\002\001\006\060\035\006
|
||||
\003\125\035\016\004\026\004\024\107\320\347\261\042\377\235\054
|
||||
\365\331\127\140\263\261\261\160\225\357\141\172\060\015\006\011
|
||||
\052\206\110\206\367\015\001\001\013\005\000\003\202\002\001\000
|
||||
\206\151\261\115\057\351\237\117\042\223\150\216\344\041\231\243
|
||||
\316\105\123\033\163\104\123\000\201\141\315\061\343\010\272\201
|
||||
\050\050\172\222\271\266\250\310\103\236\307\023\046\115\302\330
|
||||
\345\125\234\222\135\120\330\302\053\333\376\346\250\227\317\122
|
||||
\072\044\303\145\144\134\107\061\243\145\065\023\303\223\271\367
|
||||
\371\121\227\273\244\360\142\207\305\326\006\323\227\203\040\251
|
||||
\176\273\266\041\302\245\015\204\000\341\362\047\020\203\272\335
|
||||
\003\201\325\335\150\303\146\020\310\321\166\264\263\157\051\236
|
||||
\000\371\302\051\365\261\223\031\122\151\032\054\114\240\213\340
|
||||
\025\232\061\057\323\210\225\131\156\345\304\263\120\310\024\010
|
||||
\112\233\213\023\203\261\244\162\262\073\166\063\101\334\334\252
|
||||
\246\007\157\035\044\022\237\310\166\275\057\331\216\364\054\356
|
||||
\267\322\070\020\044\066\121\057\343\134\135\201\041\247\332\273
|
||||
\116\377\346\007\250\376\271\015\047\154\273\160\132\125\172\023
|
||||
\351\361\052\111\151\307\137\207\127\114\103\171\155\072\145\351
|
||||
\060\134\101\356\353\167\245\163\022\210\350\277\175\256\345\304
|
||||
\250\037\015\216\034\155\120\002\117\046\030\103\336\217\125\205
|
||||
\261\013\067\005\140\311\125\071\022\004\241\052\317\161\026\237
|
||||
\066\121\111\277\160\073\236\147\234\373\173\171\311\071\034\170
|
||||
\254\167\221\124\232\270\165\012\201\122\227\343\146\141\153\355
|
||||
\076\070\036\226\141\125\341\221\124\214\355\214\044\037\201\311
|
||||
\020\232\163\231\053\026\116\162\000\077\124\033\370\215\272\213
|
||||
\347\024\326\266\105\117\140\354\226\256\303\057\002\116\135\235
|
||||
\226\111\162\000\262\253\165\134\017\150\133\035\145\302\137\063
|
||||
\017\036\017\360\073\206\365\260\116\273\234\367\352\045\005\334
|
||||
\255\242\233\113\027\001\276\102\337\065\041\035\255\253\256\364
|
||||
\277\256\037\033\323\342\073\374\263\162\163\034\233\050\220\211
|
||||
\023\075\035\301\000\107\011\226\232\070\033\335\261\317\015\302
|
||||
\264\104\363\226\225\316\062\072\217\064\234\340\027\307\136\316
|
||||
\256\015\333\207\070\345\077\133\375\233\031\341\061\101\172\160
|
||||
\252\043\153\001\341\105\114\315\224\316\073\236\055\347\210\002
|
||||
\042\364\156\350\310\354\326\074\363\271\262\327\167\172\254\173
|
||||
END
|
||||
CKA_NSS_MOZILLA_CA_POLICY CK_BBOOL CK_TRUE
|
||||
CKA_NSS_SERVER_DISTRUST_AFTER CK_BBOOL CK_FALSE
|
||||
CKA_NSS_EMAIL_DISTRUST_AFTER CK_BBOOL CK_FALSE
|
||||
|
||||
# Trust for "CommScope Public Trust RSA Root-02"
|
||||
# Issuer: CN=CommScope Public Trust RSA Root-02,O=CommScope,C=US
|
||||
# Serial Number:54:16:bf:3b:7e:39:95:71:8d:d1:aa:00:a5:86:0d:2b:8f:7a:05:4e
|
||||
# Subject: CN=CommScope Public Trust RSA Root-02,O=CommScope,C=US
|
||||
# Not Valid Before: Wed Apr 28 17:16:43 2021
|
||||
# Not Valid After : Sat Apr 28 17:16:42 2046
|
||||
# Fingerprint (SHA-256): FF:E9:43:D7:93:42:4B:4F:7C:44:0C:1C:3D:64:8D:53:63:F3:4B:82:DC:87:AA:7A:9F:11:8F:C5:DE:E1:01:F1
|
||||
# Fingerprint (SHA1): EA:B0:E2:52:1B:89:93:4C:11:68:F2:D8:9A:AC:22:4C:A3:8A:57:AE
|
||||
CKA_CLASS CK_OBJECT_CLASS CKO_NSS_TRUST
|
||||
CKA_TOKEN CK_BBOOL CK_TRUE
|
||||
CKA_PRIVATE CK_BBOOL CK_FALSE
|
||||
CKA_MODIFIABLE CK_BBOOL CK_FALSE
|
||||
CKA_LABEL UTF8 "CommScope Public Trust RSA Root-02"
|
||||
CKA_CERT_SHA1_HASH MULTILINE_OCTAL
|
||||
\352\260\342\122\033\211\223\114\021\150\362\330\232\254\042\114
|
||||
\243\212\127\256
|
||||
END
|
||||
CKA_CERT_MD5_HASH MULTILINE_OCTAL
|
||||
\341\051\371\142\173\166\342\226\155\363\324\327\017\256\037\252
|
||||
END
|
||||
CKA_ISSUER MULTILINE_OCTAL
|
||||
\060\116\061\013\060\011\006\003\125\004\006\023\002\125\123\061
|
||||
\022\060\020\006\003\125\004\012\014\011\103\157\155\155\123\143
|
||||
\157\160\145\061\053\060\051\006\003\125\004\003\014\042\103\157
|
||||
\155\155\123\143\157\160\145\040\120\165\142\154\151\143\040\124
|
||||
\162\165\163\164\040\122\123\101\040\122\157\157\164\055\060\062
|
||||
END
|
||||
CKA_SERIAL_NUMBER MULTILINE_OCTAL
|
||||
\002\024\124\026\277\073\176\071\225\161\215\321\252\000\245\206
|
||||
\015\053\217\172\005\116
|
||||
END
|
||||
CKA_TRUST_SERVER_AUTH CK_TRUST CKT_NSS_TRUSTED_DELEGATOR
|
||||
CKA_TRUST_EMAIL_PROTECTION CK_TRUST CKT_NSS_MUST_VERIFY_TRUST
|
||||
CKA_TRUST_CODE_SIGNING CK_TRUST CKT_NSS_MUST_VERIFY_TRUST
|
||||
CKA_TRUST_STEP_UP_APPROVED CK_BBOOL CK_FALSE
|
||||
|
||||
#
|
||||
# Certificate "D-Trust SBR Root CA 1 2022"
|
||||
#
|
||||
|
||||
@@ -828,7 +828,7 @@ struct us_socket_t *us_socket_context_adopt_socket(int ssl, struct us_socket_con
|
||||
struct us_socket_t *new_s = s;
|
||||
if (ext_size != -1) {
|
||||
struct us_poll_t *pool_ref = &s->p;
|
||||
new_s = (struct us_socket_t *) us_poll_resize(pool_ref, loop, sizeof(struct us_socket_t) + old_ext_size, sizeof(struct us_socket_t) + ext_size);
|
||||
new_s = (struct us_socket_t *) us_poll_resize(pool_ref, loop, sizeof(struct us_socket_t) - sizeof(struct us_poll_t) + old_ext_size, sizeof(struct us_socket_t) - sizeof(struct us_poll_t) + ext_size);
|
||||
if(new_s != s) {
|
||||
/* Mark the old socket as closed */
|
||||
s->flags.is_closed = 1;
|
||||
|
||||
@@ -3182,96 +3182,6 @@ static struct us_cert_string_t root_certs[] = {
|
||||
"MvHVI5TWWA==\n"
|
||||
"-----END CERTIFICATE-----",.len=869},
|
||||
|
||||
/* CommScope Public Trust ECC Root-01 */
|
||||
{.str="-----BEGIN CERTIFICATE-----\n"
|
||||
"MIICHTCCAaOgAwIBAgIUQ3CCd89NXTTxyq4yLzf39H91oJ4wCgYIKoZIzj0EAwMwTjELMAkG\n"
|
||||
"A1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29tbVNjb3BlIFB1Ymxp\n"
|
||||
"YyBUcnVzdCBFQ0MgUm9vdC0wMTAeFw0yMTA0MjgxNzM1NDNaFw00NjA0MjgxNzM1NDJaME4x\n"
|
||||
"CzAJBgNVBAYTAlVTMRIwEAYDVQQKDAlDb21tU2NvcGUxKzApBgNVBAMMIkNvbW1TY29wZSBQ\n"
|
||||
"dWJsaWMgVHJ1c3QgRUNDIFJvb3QtMDEwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARLNumuV16o\n"
|
||||
"cNfQj3Rid8NeeqrltqLxeP0CflfdkXmcbLlSiFS8LwS+uM32ENEp7LXQoMPwiXAZu1FlxUOc\n"
|
||||
"w5tjnSCDPgYLpkJEhRGnSjot6dZoL0hOUysHP029uax3OVejQjBAMA8GA1UdEwEB/wQFMAMB\n"
|
||||
"Af8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSOB2LAUN3GGQYARnQE9/OufXVNMDAKBggq\n"
|
||||
"hkjOPQQDAwNoADBlAjEAnDPfQeMjqEI2Jpc1XHvr20v4qotzVRVcrHgpD7oh2MSg2NED3W3R\n"
|
||||
"OT3Ek2DS43KyAjB8xX6I01D1HiXo+k515liWpDVfG2XqYZpwI7UNo5uSUm9poIyNStDuiw7L\n"
|
||||
"R47QjRE=\n"
|
||||
"-----END CERTIFICATE-----",.len=792},
|
||||
|
||||
/* CommScope Public Trust ECC Root-02 */
|
||||
{.str="-----BEGIN CERTIFICATE-----\n"
|
||||
"MIICHDCCAaOgAwIBAgIUKP2ZYEFHpgE6yhR7H+/5aAiDXX0wCgYIKoZIzj0EAwMwTjELMAkG\n"
|
||||
"A1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29tbVNjb3BlIFB1Ymxp\n"
|
||||
"YyBUcnVzdCBFQ0MgUm9vdC0wMjAeFw0yMTA0MjgxNzQ0NTRaFw00NjA0MjgxNzQ0NTNaME4x\n"
|
||||
"CzAJBgNVBAYTAlVTMRIwEAYDVQQKDAlDb21tU2NvcGUxKzApBgNVBAMMIkNvbW1TY29wZSBQ\n"
|
||||
"dWJsaWMgVHJ1c3QgRUNDIFJvb3QtMDIwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAR4MIHoYx7l\n"
|
||||
"63FRD/cHB8o5mXxO1Q/MMDALj2aTPs+9xYa9+bG3tD60B8jzljHz7aRP+KNOjSkVWLjVb3/u\n"
|
||||
"bCK1sK9IRQq9qEmUv4RDsNuESgMjGWdqb8FuvAY5N9GIIvejQjBAMA8GA1UdEwEB/wQFMAMB\n"
|
||||
"Af8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTmGHX/72DehKT1RsfeSlXjMjZ59TAKBggq\n"
|
||||
"hkjOPQQDAwNnADBkAjAmc0l6tqvmSfR9Uj/UQQSugEODZXW5hYA4O9Zv5JOGq4/nich/m35r\n"
|
||||
"ChJVYaoR4HkCMHfoMXGsPHED1oQmHhS48zs73u1Z/GtMMH9ZzkXpc2AVmkzw5l4lIhVtwodZ\n"
|
||||
"0LKOag==\n"
|
||||
"-----END CERTIFICATE-----",.len=792},
|
||||
|
||||
/* CommScope Public Trust RSA Root-01 */
|
||||
{.str="-----BEGIN CERTIFICATE-----\n"
|
||||
"MIIFbDCCA1SgAwIBAgIUPgNJgXUWdDGOTKvVxZAplsU5EN0wDQYJKoZIhvcNAQELBQAwTjEL\n"
|
||||
"MAkGA1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29tbVNjb3BlIFB1\n"
|
||||
"YmxpYyBUcnVzdCBSU0EgUm9vdC0wMTAeFw0yMTA0MjgxNjQ1NTRaFw00NjA0MjgxNjQ1NTNa\n"
|
||||
"ME4xCzAJBgNVBAYTAlVTMRIwEAYDVQQKDAlDb21tU2NvcGUxKzApBgNVBAMMIkNvbW1TY29w\n"
|
||||
"ZSBQdWJsaWMgVHJ1c3QgUlNBIFJvb3QtMDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK\n"
|
||||
"AoICAQCwSGWjDR1C45FtnYSkYZYSwu3D2iM0GXb26v1VWvZVAVMP8syMl0+5UMuzAURWlv2b\n"
|
||||
"KOx7dAvnQmtVzslhsuitQDy6uUEKBU8bJoWPQ7VAtYXR1HHcg0Hz9kXHgKKEUJdGzqAMxGBW\n"
|
||||
"BB0HW0alDrJLpA6lfO741GIDuZNqihS4cPgugkY4Iw50x2tBt9Apo52AsH53k2NC+zSDO3Oj\n"
|
||||
"WiE260f6GBfZumbCk6SP/F2krfxQapWsvCQz0b2If4b19bJzKo98rwjyGpg/qYFlP8GMicWW\n"
|
||||
"MJoKz/TUyDTtnS+8jTiGU+6Xn6myY5QXjQ/cZip8UlF1y5mO6D1cv547KI2DAg+pn3LiLCuz\n"
|
||||
"3GaXAEDQpFSOm117RTYm1nJD68/A6g3czhLmfTifBSeolz7pUcZsBSjBAg/pGG3svZwG1KdJ\n"
|
||||
"9FQFa2ww8esD1eo9anbCyxooSU1/ZOD6K9pzg4H/kQO9lLvkuI6cMmPNn7togbGEW682v3fu\n"
|
||||
"HX/3SZtS7NJ3Wn2RnU3COS3kuoL4b/JOHg9O5j9ZpSPcPYeoKFgo0fEbNttPxP/hjFtyjMcm\n"
|
||||
"AyejOQoBqsCyMWCDIqFPEgkBEa801M/XrmLTBQe0MXXgDW1XT2mH+VepuhX2yFJtocucH+X8\n"
|
||||
"eKg1mp9BFM6ltM6UCBwJrVbl2rZJmkrqYxhTnCwuwwIDAQABo0IwQDAPBgNVHRMBAf8EBTAD\n"
|
||||
"AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUN12mmnQywsL5x6YVEFm45P3luG0wDQYJ\n"
|
||||
"KoZIhvcNAQELBQADggIBAK+nz97/4L1CjU3lIpbfaOp9TSp90K09FlxD533Ahuh6NWPxzIHI\n"
|
||||
"xgvoLlI1pKZJkGNRrDSsBTtXAOnTYtPZKdVUvhwQkZyybf5Z/Xn36lbQnmhUQo8mUuJM3y+X\n"
|
||||
"pi/SB5io82BdS5pYV4jvguX6r2yBS5KPQJqTRlnLX3gWsWc+QgvfKNmwrZggvkN80V4aCRck\n"
|
||||
"jXtdlemrwWCrWxhkgPut4AZ9HcpZuPN4KWfGVh2vtrV0KnahP/t1MJ+UXjulYPPLXAziDslg\n"
|
||||
"+MkfFoom3ecnf+slpoq9uC02EJqxWE2aaE9gVOX2RhOOiKy8IUISrcZKiX2bwdgt6ZYD9KJ0\n"
|
||||
"DLwAHb/WNyVntHKLr4W96ioDj8z7PEQkguIBpQtZtjSNMgsSDesnwv1B10A8ckYpwIzqug/x\n"
|
||||
"BpMu95yo9GA+o/E4Xo4TwbM6l4c/ksp4qRyv0LAbJh6+cOx69TOY6lz/KwsETkPdY34Op054\n"
|
||||
"A5U+1C0wlREQKC6/oAI+/15Z0wUOlV9TRe9rh9VIzRamloPh37MG88EU26fsHItdkJANclHn\n"
|
||||
"YfkUyq+Dj7+vsQpZXdxc1+SWrVtgHdqul7I52Qb1dgAT+GhMIbA1xNxVssnBQVocicCMb3Sg\n"
|
||||
"azNNtQEo/a2tiRc7ppqEvOuM6sRxJKi6KfkIsidWNTJf6jn7MZrVGczw\n"
|
||||
"-----END CERTIFICATE-----",.len=1935},
|
||||
|
||||
/* CommScope Public Trust RSA Root-02 */
|
||||
{.str="-----BEGIN CERTIFICATE-----\n"
|
||||
"MIIFbDCCA1SgAwIBAgIUVBa/O345lXGN0aoApYYNK496BU4wDQYJKoZIhvcNAQELBQAwTjEL\n"
|
||||
"MAkGA1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29tbVNjb3BlIFB1\n"
|
||||
"YmxpYyBUcnVzdCBSU0EgUm9vdC0wMjAeFw0yMTA0MjgxNzE2NDNaFw00NjA0MjgxNzE2NDJa\n"
|
||||
"ME4xCzAJBgNVBAYTAlVTMRIwEAYDVQQKDAlDb21tU2NvcGUxKzApBgNVBAMMIkNvbW1TY29w\n"
|
||||
"ZSBQdWJsaWMgVHJ1c3QgUlNBIFJvb3QtMDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK\n"
|
||||
"AoICAQDh+g77aAASyE3VrCLENQE7xVTlWXZjpX/rwcRqmL0yjReA61260WI9JSMZNRTpf4mn\n"
|
||||
"G2I81lDnNJUDMrG0kyI9p+Kx7eZ7Ti6Hmw0zdQreqjXnfuU2mKKuJZ6VszKWpCtYHu8//mI0\n"
|
||||
"SFHRtI1CrWDaSWqVcN3SAOLMV2MCe5bdSZdbkk6V0/nLKR8YSvgBKtJjCW4k6YnS5cciTNxz\n"
|
||||
"hkcAqg2Ijq6FfUrpuzNPDlJwnZXjfG2WWy09X6GDRl224yW4fKcZgBzqZUPckXk2LHR88mcG\n"
|
||||
"yYnJ27/aaL8j7dxrrSiDeS/sOKUNNwFnJ5rpM9kzXzehxfCrPfp4sOcsn/Y+n2Dg70jpkEUe\n"
|
||||
"BVF4GiwSLFworA2iI540jwXmojPOEXcT1A6kHkIfhs1w/tkuFT0du7jyU1fbzMZ0KZwYszZ1\n"
|
||||
"OC4PVKH4kh+Jlk+71O6d6Ts2QrUKOyrUZHk2EOH5kQMreyBUzQ0ZGshBMjTRsJnhkB4BQDa1\n"
|
||||
"t/qp5Xd1pCKBXbCL5CcSD1SIxtuFdOa3wNemKfrb3vOTlycEVS8KbzfFPROvCgCpLIscgSjX\n"
|
||||
"74Yxqa7ybrjKaixUR9gqiC6vwQcQeKwRoi9C8DfF8rhW3Q5iLc4tVn5V8qdE9isy9COoR+jU\n"
|
||||
"KgF4z2rDN6ieZdIs5fq6M8EGRPbmz6UNp2YINIos8wIDAQABo0IwQDAPBgNVHRMBAf8EBTAD\n"
|
||||
"AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUR9DnsSL/nSz12Vdgs7GxcJXvYXowDQYJ\n"
|
||||
"KoZIhvcNAQELBQADggIBAIZpsU0v6Z9PIpNojuQhmaPORVMbc0RTAIFhzTHjCLqBKCh6krm2\n"
|
||||
"qMhDnscTJk3C2OVVnJJdUNjCK9v+5qiXz1I6JMNlZFxHMaNlNRPDk7n3+VGXu6TwYofF1gbT\n"
|
||||
"l4MgqX67tiHCpQ2EAOHyJxCDut0DgdXdaMNmEMjRdrSzbymeAPnCKfWxkxlSaRosTKCL4BWa\n"
|
||||
"MS/TiJVZbuXEs1DIFAhKm4sTg7GkcrI7djNB3NyqpgdvHSQSn8h2vS/ZjvQs7rfSOBAkNlEv\n"
|
||||
"41xdgSGn2rtO/+YHqP65DSdsu3BaVXoT6fEqSWnHX4dXTEN5bTpl6TBcQe7rd6VzEojov32u\n"
|
||||
"5cSoHw2OHG1QAk8mGEPej1WFsQs3BWDJVTkSBKEqz3EWnzZRSb9wO55nnPt7eck5HHisd5FU\n"
|
||||
"mrh1CoFSl+NmYWvtPjgelmFV4ZFUjO2MJB+ByRCac5krFk5yAD9UG/iNuovnFNa2RU9g7Jau\n"
|
||||
"wy8CTl2dlklyALKrdVwPaFsdZcJfMw8eD/A7hvWwTruc9+olBdytoptLFwG+Qt81IR2tq670\n"
|
||||
"v64fG9PiO/yzcnMcmyiQiRM9HcEARwmWmjgb3bHPDcK0RPOWlc4yOo80nOAXx17Org3bhzjl\n"
|
||||
"P1v9mxnhMUF6cKojawHhRUzNlM47ni3niAIi9G7oyOzWPPO5std3eqx7\n"
|
||||
"-----END CERTIFICATE-----",.len=1935},
|
||||
|
||||
/* Telekom Security TLS ECC Root 2020 */
|
||||
{.str="-----BEGIN CERTIFICATE-----\n"
|
||||
"MIICQjCCAcmgAwIBAgIQNjqWjMlcsljN0AFdxeVXADAKBggqhkjOPQQDAzBjMQswCQYDVQQG\n"
|
||||
|
||||
@@ -462,7 +462,7 @@ void us_internal_dispatch_ready_poll(struct us_poll_t *p, int error, int eof, in
|
||||
#ifdef _WIN32
|
||||
const int recv_flags = MSG_PUSH_IMMEDIATE;
|
||||
#else
|
||||
const int recv_flags = MSG_DONTWAIT | MSG_NOSIGNAL;
|
||||
const int recv_flags = MSG_DONTWAIT;
|
||||
#endif
|
||||
|
||||
int length;
|
||||
|
||||
@@ -467,7 +467,9 @@ public:
|
||||
/* Write mark on first call to write */
|
||||
writeMark();
|
||||
|
||||
writeHeader("Transfer-Encoding", "chunked");
|
||||
if (!(httpResponseData->state & HttpResponseData<SSL>::HTTP_WROTE_TRANSFER_ENCODING_HEADER)) {
|
||||
writeHeader("Transfer-Encoding", "chunked");
|
||||
}
|
||||
Super::write("\r\n", 2);
|
||||
httpResponseData->state |= HttpResponseData<SSL>::HTTP_WRITE_CALLED;
|
||||
}
|
||||
@@ -489,7 +491,9 @@ public:
|
||||
/* Write mark on first call to write */
|
||||
writeMark();
|
||||
|
||||
writeHeader("Transfer-Encoding", "chunked");
|
||||
if (!(httpResponseData->state & HttpResponseData<SSL>::HTTP_WROTE_TRANSFER_ENCODING_HEADER)) {
|
||||
writeHeader("Transfer-Encoding", "chunked");
|
||||
}
|
||||
Super::write("\r\n", 2);
|
||||
httpResponseData->state |= HttpResponseData<SSL>::HTTP_WRITE_CALLED;
|
||||
}
|
||||
@@ -558,7 +562,9 @@ public:
|
||||
/* Write mark on first call to write */
|
||||
writeMark();
|
||||
|
||||
writeHeader("Transfer-Encoding", "chunked");
|
||||
if (!(httpResponseData->state & HttpResponseData<SSL>::HTTP_WROTE_TRANSFER_ENCODING_HEADER)) {
|
||||
writeHeader("Transfer-Encoding", "chunked");
|
||||
}
|
||||
Super::write("\r\n", 2);
|
||||
httpResponseData->state |= HttpResponseData<SSL>::HTTP_WRITE_CALLED;
|
||||
}
|
||||
|
||||
@@ -87,6 +87,7 @@ struct HttpResponseData : AsyncSocketData<SSL>, HttpParser {
|
||||
HTTP_CONNECTION_CLOSE = 16, // used
|
||||
HTTP_WROTE_CONTENT_LENGTH_HEADER = 32, // used
|
||||
HTTP_WROTE_DATE_HEADER = 64, // used
|
||||
HTTP_WROTE_TRANSFER_ENCODING_HEADER = 128, // used
|
||||
};
|
||||
|
||||
/* Shared context pointer */
|
||||
|
||||
665
scripts/azure.mjs
Normal file
665
scripts/azure.mjs
Normal file
@@ -0,0 +1,665 @@
|
||||
// Azure REST API client for machine.mjs
|
||||
// Used by the [build images] pipeline to create Windows VM images (x64 and ARM64)
|
||||
|
||||
import { getSecret, isCI } from "./utils.mjs";
|
||||
|
||||
/**
|
||||
* @typedef {Object} AzureConfig
|
||||
* @property {string} tenantId
|
||||
* @property {string} clientId
|
||||
* @property {string} clientSecret
|
||||
* @property {string} subscriptionId
|
||||
* @property {string} resourceGroup
|
||||
* @property {string} location
|
||||
* @property {string} galleryName
|
||||
*/
|
||||
|
||||
/** @returns {AzureConfig} */
|
||||
function getConfig() {
|
||||
const env = (name, fallback) => {
|
||||
if (isCI) {
|
||||
try {
|
||||
return getSecret(name, { required: !fallback }) || fallback;
|
||||
} catch {
|
||||
if (fallback) return fallback;
|
||||
throw new Error(`Azure secret not found: ${name}`);
|
||||
}
|
||||
}
|
||||
return process.env[name] || fallback;
|
||||
};
|
||||
|
||||
return {
|
||||
tenantId: env("AZURE_TENANT_ID"),
|
||||
clientId: env("AZURE_CLIENT_ID"),
|
||||
clientSecret: env("AZURE_CLIENT_SECRET"),
|
||||
subscriptionId: env("AZURE_SUBSCRIPTION_ID"),
|
||||
resourceGroup: env("AZURE_RESOURCE_GROUP", "BUN-CI"),
|
||||
location: env("AZURE_LOCATION", "eastus2"),
|
||||
galleryName: env("AZURE_GALLERY_NAME", "bunCIGallery2"),
|
||||
};
|
||||
}
|
||||
|
||||
let _config;
|
||||
function config() {
|
||||
return (_config ??= getConfig());
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Authentication
|
||||
// ============================================================================
|
||||
|
||||
let _accessToken = null;
|
||||
let _tokenExpiry = 0;
|
||||
|
||||
async function getAccessToken() {
|
||||
if (_accessToken && Date.now() < _tokenExpiry - 300_000) {
|
||||
return _accessToken;
|
||||
}
|
||||
|
||||
const { tenantId, clientId, clientSecret } = config();
|
||||
const response = await fetch(`https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||
body: new URLSearchParams({
|
||||
grant_type: "client_credentials",
|
||||
client_id: clientId,
|
||||
client_secret: clientSecret,
|
||||
scope: "https://management.azure.com/.default",
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`[azure] Auth failed: ${response.status} ${await response.text()}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
_accessToken = data.access_token;
|
||||
_tokenExpiry = Date.now() + data.expires_in * 1000;
|
||||
return _accessToken;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// REST Client
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @param {"GET"|"PUT"|"POST"|"PATCH"|"DELETE"} method
|
||||
* @param {string} path - Relative path under management.azure.com, or absolute URL
|
||||
* @param {object} [body]
|
||||
* @param {string} [apiVersion]
|
||||
*/
|
||||
async function azureFetch(method, path, body, apiVersion = "2024-07-01") {
|
||||
const token = await getAccessToken();
|
||||
|
||||
const url = path.startsWith("http") ? new URL(path) : new URL(`https://management.azure.com${path}`);
|
||||
|
||||
if (!url.searchParams.has("api-version")) {
|
||||
url.searchParams.set("api-version", apiVersion);
|
||||
}
|
||||
|
||||
const options = {
|
||||
method,
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
};
|
||||
|
||||
if (body && method !== "GET" && method !== "DELETE") {
|
||||
options.body = JSON.stringify(body);
|
||||
}
|
||||
|
||||
for (let attempt = 0; attempt < 3; attempt++) {
|
||||
const response = await fetch(url, options);
|
||||
|
||||
if (response.status === 429 || response.status >= 500) {
|
||||
const wait = Math.pow(2, attempt) * 1000;
|
||||
console.warn(`[azure] ${method} ${path} returned ${response.status}, retrying in ${wait}ms...`);
|
||||
await new Promise(r => setTimeout(r, wait));
|
||||
continue;
|
||||
}
|
||||
|
||||
// 202 Accepted — async operation, poll for completion
|
||||
if (response.status === 202) {
|
||||
const operationUrl = response.headers.get("Azure-AsyncOperation") || response.headers.get("Location");
|
||||
if (operationUrl) {
|
||||
return waitForOperation(operationUrl);
|
||||
}
|
||||
}
|
||||
|
||||
if (response.status === 204) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
const text = await response.text();
|
||||
throw new Error(`[azure] ${method} ${path} failed: ${response.status} ${text}`);
|
||||
}
|
||||
|
||||
const text = await response.text();
|
||||
return text ? JSON.parse(text) : null;
|
||||
}
|
||||
|
||||
throw new Error(`[azure] ${method} ${path} failed after 3 retries`);
|
||||
}
|
||||
|
||||
async function waitForOperation(operationUrl, maxWaitMs = 3_600_000) {
|
||||
const start = Date.now();
|
||||
let fetchErrors = 0;
|
||||
|
||||
while (Date.now() - start < maxWaitMs) {
|
||||
const token = await getAccessToken();
|
||||
|
||||
let response;
|
||||
try {
|
||||
response = await fetch(operationUrl, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
} catch (err) {
|
||||
fetchErrors++;
|
||||
if (fetchErrors > 10) {
|
||||
throw new Error(`[azure] Operation poll failed after ${fetchErrors} fetch errors`, { cause: err });
|
||||
}
|
||||
console.warn(`[azure] Operation poll fetch error (${fetchErrors}), retrying...`);
|
||||
await new Promise(r => setTimeout(r, 10_000));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`[azure] Operation poll failed: ${response.status} ${await response.text()}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.status === "Succeeded") {
|
||||
return data.properties?.output ?? data;
|
||||
}
|
||||
if (data.status === "Failed" || data.status === "Canceled") {
|
||||
throw new Error(`[azure] Operation ${data.status}: ${data.error?.message ?? "unknown"}`);
|
||||
}
|
||||
|
||||
await new Promise(r => setTimeout(r, 5000));
|
||||
}
|
||||
|
||||
throw new Error(`[azure] Operation timed out after ${maxWaitMs}ms`);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Resource helpers
|
||||
// ============================================================================
|
||||
|
||||
function rgPath() {
|
||||
const { subscriptionId, resourceGroup } = config();
|
||||
return `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}`;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Public IP
|
||||
// ============================================================================
|
||||
|
||||
async function createPublicIp(name) {
|
||||
const { location } = config();
|
||||
console.log(`[azure] Creating public IP: ${name}`);
|
||||
const result = await azureFetch("PUT", `${rgPath()}/providers/Microsoft.Network/publicIPAddresses/${name}`, {
|
||||
location,
|
||||
sku: { name: "Standard" },
|
||||
properties: {
|
||||
publicIPAllocationMethod: "Static",
|
||||
deleteOption: "Delete",
|
||||
},
|
||||
});
|
||||
return result?.properties?.ipAddress;
|
||||
}
|
||||
|
||||
async function deletePublicIp(name) {
|
||||
await azureFetch("DELETE", `${rgPath()}/providers/Microsoft.Network/publicIPAddresses/${name}`).catch(() => {});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Network Security Group
|
||||
// ============================================================================
|
||||
|
||||
// ============================================================================
|
||||
// Network Interface
|
||||
// ============================================================================
|
||||
|
||||
async function createNic(name, publicIpName, subnetId, nsgId) {
|
||||
const { location } = config();
|
||||
console.log(`[azure] Creating NIC: ${name}`);
|
||||
const publicIpId = `${rgPath()}/providers/Microsoft.Network/publicIPAddresses/${publicIpName}`;
|
||||
await azureFetch("PUT", `${rgPath()}/providers/Microsoft.Network/networkInterfaces/${name}`, {
|
||||
location,
|
||||
properties: {
|
||||
ipConfigurations: [
|
||||
{
|
||||
name: "ipconfig1",
|
||||
properties: {
|
||||
privateIPAllocationMethod: "Dynamic",
|
||||
publicIPAddress: { id: publicIpId, properties: { deleteOption: "Delete" } },
|
||||
subnet: { id: subnetId },
|
||||
},
|
||||
},
|
||||
],
|
||||
...(nsgId ? { networkSecurityGroup: { id: nsgId } } : {}),
|
||||
},
|
||||
});
|
||||
return `${rgPath()}/providers/Microsoft.Network/networkInterfaces/${name}`;
|
||||
}
|
||||
|
||||
async function deleteNic(name) {
|
||||
await azureFetch("DELETE", `${rgPath()}/providers/Microsoft.Network/networkInterfaces/${name}`).catch(() => {});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Virtual Machines
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @param {object} opts
|
||||
* @param {string} opts.name
|
||||
* @param {string} opts.vmSize
|
||||
* @param {object} opts.imageReference
|
||||
* @param {number} opts.osDiskSizeGB
|
||||
* @param {string} opts.nicId
|
||||
* @param {string} opts.adminUsername
|
||||
* @param {string} opts.adminPassword
|
||||
* @param {Record<string, string>} [opts.tags]
|
||||
*/
|
||||
async function createVm(opts) {
|
||||
const { location } = config();
|
||||
console.log(`[azure] Creating VM: ${opts.name} (${opts.vmSize})`);
|
||||
const result = await azureFetch("PUT", `${rgPath()}/providers/Microsoft.Compute/virtualMachines/${opts.name}`, {
|
||||
location,
|
||||
tags: opts.tags,
|
||||
properties: {
|
||||
hardwareProfile: { vmSize: opts.vmSize },
|
||||
storageProfile: {
|
||||
imageReference: opts.imageReference,
|
||||
osDisk: {
|
||||
createOption: "FromImage",
|
||||
diskSizeGB: opts.osDiskSizeGB,
|
||||
deleteOption: "Delete",
|
||||
managedDisk: { storageAccountType: "Premium_LRS" },
|
||||
},
|
||||
},
|
||||
osProfile: {
|
||||
computerName: opts.name.substring(0, 15),
|
||||
adminUsername: opts.adminUsername,
|
||||
adminPassword: opts.adminPassword,
|
||||
},
|
||||
securityProfile: {
|
||||
securityType: "TrustedLaunch",
|
||||
},
|
||||
networkProfile: {
|
||||
networkInterfaces: [{ id: opts.nicId, properties: { deleteOption: "Delete" } }],
|
||||
},
|
||||
},
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
async function getVm(name) {
|
||||
try {
|
||||
return await azureFetch(
|
||||
"GET",
|
||||
`${rgPath()}/providers/Microsoft.Compute/virtualMachines/${name}?$expand=instanceView`,
|
||||
);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function getVmPowerState(name) {
|
||||
const vm = await getVm(name);
|
||||
const statuses = vm?.properties?.instanceView?.statuses ?? [];
|
||||
const powerStatus = statuses.find(s => s.code?.startsWith("PowerState/"));
|
||||
return powerStatus?.code;
|
||||
}
|
||||
|
||||
async function stopVm(name) {
|
||||
console.log(`[azure] Stopping VM: ${name}`);
|
||||
await azureFetch("POST", `${rgPath()}/providers/Microsoft.Compute/virtualMachines/${name}/deallocate`);
|
||||
}
|
||||
|
||||
async function generalizeVm(name) {
|
||||
console.log(`[azure] Generalizing VM: ${name}`);
|
||||
await azureFetch("POST", `${rgPath()}/providers/Microsoft.Compute/virtualMachines/${name}/generalize`);
|
||||
}
|
||||
|
||||
async function deleteVm(name) {
|
||||
console.log(`[azure] Deleting VM: ${name}`);
|
||||
await azureFetch("DELETE", `${rgPath()}/providers/Microsoft.Compute/virtualMachines/${name}?forceDeletion=true`);
|
||||
}
|
||||
|
||||
async function getPublicIpAddress(publicIpName) {
|
||||
const result = await azureFetch("GET", `${rgPath()}/providers/Microsoft.Network/publicIPAddresses/${publicIpName}`);
|
||||
return result?.properties?.ipAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a PowerShell script on a Windows VM via Azure Run Command.
|
||||
* This works even without SSH installed on the VM.
|
||||
*/
|
||||
async function runCommand(vmName, script) {
|
||||
console.log(`[azure] Running command on VM: ${vmName}`);
|
||||
return azureFetch("POST", `${rgPath()}/providers/Microsoft.Compute/virtualMachines/${vmName}/runCommand`, {
|
||||
commandId: "RunPowerShellScript",
|
||||
script: Array.isArray(script) ? script : [script],
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Install OpenSSH server and configure authorized keys on a Windows VM.
|
||||
*/
|
||||
// SSH is not used — all remote operations go through Azure Run Command API.
|
||||
|
||||
// ============================================================================
|
||||
// Virtual Network
|
||||
// ============================================================================
|
||||
|
||||
// ============================================================================
|
||||
// Compute Gallery
|
||||
// ============================================================================
|
||||
|
||||
const GALLERY_API_VERSION = "2024-03-03";
|
||||
|
||||
async function ensureImageDefinition(name, os, arch) {
|
||||
const { location, galleryName } = config();
|
||||
const path = `${rgPath()}/providers/Microsoft.Compute/galleries/${galleryName}/images/${name}`;
|
||||
|
||||
try {
|
||||
const def = await azureFetch("GET", path, undefined, GALLERY_API_VERSION);
|
||||
if (def) return;
|
||||
} catch {}
|
||||
|
||||
console.log(`[azure] Creating image definition: ${name}`);
|
||||
await azureFetch(
|
||||
"PUT",
|
||||
path,
|
||||
{
|
||||
location,
|
||||
properties: {
|
||||
osType: os === "windows" ? "Windows" : "Linux",
|
||||
osState: "Generalized",
|
||||
hyperVGeneration: "V2",
|
||||
architecture: arch === "aarch64" ? "Arm64" : "x64",
|
||||
identifier: {
|
||||
publisher: "bun",
|
||||
offer: `${os}-${arch}-ci`,
|
||||
sku: name,
|
||||
},
|
||||
features: [
|
||||
{ name: "DiskControllerTypes", value: "SCSI, NVMe" },
|
||||
{ name: "SecurityType", value: "TrustedLaunch" },
|
||||
],
|
||||
},
|
||||
},
|
||||
GALLERY_API_VERSION,
|
||||
);
|
||||
}
|
||||
|
||||
async function createImageVersion(imageDefName, version, vmId) {
|
||||
const { location, galleryName } = config();
|
||||
const path = `${rgPath()}/providers/Microsoft.Compute/galleries/${galleryName}/images/${imageDefName}/versions/${version}`;
|
||||
|
||||
console.log(`[azure] Creating image version: ${imageDefName}/${version}`);
|
||||
const result = await azureFetch(
|
||||
"PUT",
|
||||
path,
|
||||
{
|
||||
location,
|
||||
properties: {
|
||||
storageProfile: {
|
||||
source: { virtualMachineId: vmId },
|
||||
},
|
||||
},
|
||||
},
|
||||
GALLERY_API_VERSION,
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Base Images
|
||||
// ============================================================================
|
||||
|
||||
function getBaseImageReference(os, arch) {
|
||||
if (os === "windows") {
|
||||
if (arch === "aarch64") {
|
||||
return {
|
||||
publisher: "MicrosoftWindowsDesktop",
|
||||
offer: "windows11preview-arm64",
|
||||
sku: "win11-24h2-pro",
|
||||
version: "latest",
|
||||
};
|
||||
}
|
||||
// Windows Server 2019 x64 — oldest supported version
|
||||
return {
|
||||
publisher: "MicrosoftWindowsServer",
|
||||
offer: "WindowsServer",
|
||||
sku: "2019-datacenter-gensecond",
|
||||
version: "latest",
|
||||
};
|
||||
}
|
||||
throw new Error(`[azure] Unsupported OS: ${os}`);
|
||||
}
|
||||
|
||||
function getVmSize(arch) {
|
||||
return arch === "aarch64" ? "Standard_D4ps_v6" : "Standard_D4ds_v6";
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Exports
|
||||
// ============================================================================
|
||||
|
||||
export const azure = {
|
||||
get name() {
|
||||
return "azure";
|
||||
},
|
||||
|
||||
config,
|
||||
|
||||
/**
|
||||
* @param {import("./machine.mjs").MachineOptions} options
|
||||
* @returns {Promise<import("./machine.mjs").Machine>}
|
||||
*/
|
||||
async createMachine(options) {
|
||||
const { os, arch, tags, sshKeys } = options;
|
||||
const vmName = `bun-${os}-${arch}-${Date.now()}`;
|
||||
const publicIpName = `${vmName}-ip`;
|
||||
const nicName = `${vmName}-nic`;
|
||||
const vmSize = options.instanceType || getVmSize(arch);
|
||||
const diskSizeGB = options.diskSizeGb || (os === "windows" ? 150 : 40);
|
||||
|
||||
// Generate a random password for the admin account
|
||||
const adminPassword = `P@${crypto.randomUUID().replace(/-/g, "").substring(0, 20)}!`;
|
||||
|
||||
const subnetId = `${rgPath()}/providers/Microsoft.Network/virtualNetworks/bun-ci-vnet/subnets/default`;
|
||||
const nsgId = `${rgPath()}/providers/Microsoft.Network/networkSecurityGroups/bun-ci-ssh-nsg`;
|
||||
|
||||
await createPublicIp(publicIpName);
|
||||
const nicId = await createNic(nicName, publicIpName, subnetId, nsgId);
|
||||
|
||||
// Create VM
|
||||
const imageReference = options.imageId ? { id: options.imageId } : getBaseImageReference(os, arch);
|
||||
|
||||
await createVm({
|
||||
name: vmName,
|
||||
vmSize,
|
||||
imageReference,
|
||||
osDiskSizeGB: diskSizeGB,
|
||||
nicId,
|
||||
adminUsername: "bunadmin",
|
||||
adminPassword,
|
||||
tags: tags
|
||||
? Object.fromEntries(
|
||||
Object.entries(tags)
|
||||
.filter(([_, v]) => v != null)
|
||||
.map(([k, v]) => [k, String(v)]),
|
||||
)
|
||||
: undefined,
|
||||
});
|
||||
|
||||
// Wait for public IP to be assigned
|
||||
let publicIp;
|
||||
for (let i = 0; i < 30; i++) {
|
||||
publicIp = await getPublicIpAddress(publicIpName);
|
||||
if (publicIp) break;
|
||||
await new Promise(r => setTimeout(r, 5000));
|
||||
}
|
||||
|
||||
if (!publicIp) {
|
||||
throw new Error(`[azure] Failed to get public IP for ${vmName}`);
|
||||
}
|
||||
|
||||
console.log(`[azure] VM created: ${vmName} at ${publicIp}`);
|
||||
|
||||
// Use Azure Run Command for all remote operations instead of SSH.
|
||||
// This avoids the sshd startup issues on Azure Windows VMs.
|
||||
|
||||
const spawnFn = async (command, opts) => {
|
||||
const script = command.join(" ");
|
||||
console.log(`[azure] Run: ${script}`);
|
||||
// Note: Azure Run Command output is limited to the last 4096 bytes.
|
||||
// Full output is not available — only the tail is returned.
|
||||
// value[0] = stdout (ComponentStatus/StdOut), value[1] = stderr (ComponentStatus/StdErr)
|
||||
const result = await runCommand(vmName, [script]);
|
||||
const values = result?.value ?? [];
|
||||
const stdout = values[0]?.message ?? "";
|
||||
const stderr = values[1]?.message ?? "";
|
||||
if (opts?.stdio === "inherit") {
|
||||
if (stdout) process.stdout.write(stdout);
|
||||
if (stderr) process.stderr.write(stderr);
|
||||
}
|
||||
// Only use displayStatus to detect errors — stderr often contains non-error
|
||||
// output (rustup progress, cargo warnings, PowerShell Write-Warning, etc.)
|
||||
const hasError = values.some(v => v?.displayStatus === "Provisioning failed");
|
||||
const exitCode = hasError ? 1 : 0;
|
||||
return { exitCode, stdout, stderr };
|
||||
};
|
||||
|
||||
const spawnSafeFn = async (command, opts) => {
|
||||
const result = await spawnFn(command, opts);
|
||||
if (result.exitCode !== 0) {
|
||||
const msg = result.stderr || result.stdout || "Unknown error";
|
||||
throw new Error(`[azure] Command failed (exit ${result.exitCode}): ${command.join(" ")}\n${msg}`);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
const upload = async (source, destination) => {
|
||||
// Read the file locally and write it on the VM via Run Command
|
||||
const { readFileSync } = await import("node:fs");
|
||||
const content = readFileSync(source, "utf-8");
|
||||
// Escape for PowerShell — use base64 to avoid escaping issues
|
||||
const b64 = Buffer.from(content).toString("base64");
|
||||
const script = [
|
||||
`$bytes = [Convert]::FromBase64String('${b64}')`,
|
||||
`$dir = Split-Path '${destination}' -Parent`,
|
||||
`if (-not (Test-Path $dir)) { New-Item -Path $dir -ItemType Directory -Force | Out-Null }`,
|
||||
`[IO.File]::WriteAllBytes('${destination}', $bytes)`,
|
||||
`Write-Host "Uploaded to ${destination} ($($bytes.Length) bytes)"`,
|
||||
];
|
||||
console.log(`[azure] Uploading ${source} -> ${destination}`);
|
||||
await runCommand(vmName, script);
|
||||
};
|
||||
|
||||
const attach = async () => {
|
||||
console.log(`[azure] Attach not supported via Run Command (VM: ${vmName}, IP: ${publicIp})`);
|
||||
};
|
||||
|
||||
const waitForSsh = async () => {
|
||||
// No SSH needed — Run Command works immediately after VM is provisioned
|
||||
// Just verify the VM is responsive
|
||||
console.log(`[azure] Verifying VM is responsive...`);
|
||||
await runCommand(vmName, ["Write-Host 'VM is ready'"]);
|
||||
console.log(`[azure] VM is responsive`);
|
||||
};
|
||||
|
||||
const snapshot = async label => {
|
||||
const vmId = `${rgPath()}/providers/Microsoft.Compute/virtualMachines/${vmName}`;
|
||||
|
||||
// Run sysprep inside the VM before deallocating.
|
||||
// This prepares Windows for generalization so the gallery image
|
||||
// can be used to create new VMs with OS provisioning.
|
||||
console.log(`[azure] Running sysprep on ${vmName}...`);
|
||||
await runCommand(vmName, ["C:\\Windows\\System32\\Sysprep\\sysprep.exe /generalize /oobe /shutdown /quiet"]);
|
||||
|
||||
// Wait for VM to shut down after sysprep (sysprep triggers shutdown)
|
||||
for (let i = 0; i < 60; i++) {
|
||||
const state = await getVmPowerState(vmName);
|
||||
if (state === "PowerState/stopped" || state === "PowerState/deallocated") break;
|
||||
await new Promise(r => setTimeout(r, 10000));
|
||||
}
|
||||
|
||||
// Deallocate the VM
|
||||
await stopVm(vmName);
|
||||
// Wait for VM to be deallocated
|
||||
for (let i = 0; i < 60; i++) {
|
||||
const state = await getVmPowerState(vmName);
|
||||
if (state === "PowerState/deallocated") break;
|
||||
await new Promise(r => setTimeout(r, 5000));
|
||||
}
|
||||
|
||||
await generalizeVm(vmName);
|
||||
|
||||
// Ensure gallery and image definition exist.
|
||||
// Use the label as the image definition name — this matches what ci.mjs
|
||||
// emits as the image-name agent tag, so robobun can look it up directly.
|
||||
const imageDefName = label;
|
||||
await ensureImageDefinition(imageDefName, os, arch);
|
||||
|
||||
// Create a single version "1.0.0" under this definition.
|
||||
await createImageVersion(imageDefName, "1.0.0", vmId);
|
||||
|
||||
// Wait for image replication to complete before returning.
|
||||
// Single-region replication typically takes 5-15 minutes.
|
||||
const { galleryName } = config();
|
||||
const versionPath = `${rgPath()}/providers/Microsoft.Compute/galleries/${galleryName}/images/${imageDefName}/versions/1.0.0`;
|
||||
console.log(`[azure] Waiting for image replication...`);
|
||||
for (let i = 0; i < 120; i++) {
|
||||
const ver = await azureFetch("GET", versionPath, undefined, GALLERY_API_VERSION);
|
||||
const state = ver?.properties?.provisioningState;
|
||||
if (state === "Succeeded") {
|
||||
console.log(`[azure] Image ready: ${imageDefName}/1.0.0`);
|
||||
break;
|
||||
}
|
||||
if (state === "Failed") {
|
||||
throw new Error(`[azure] Image replication failed: ${JSON.stringify(ver?.properties)}`);
|
||||
}
|
||||
if (i % 6 === 0) {
|
||||
console.log(`[azure] Image replicating... (${i}m elapsed)`);
|
||||
}
|
||||
await new Promise(r => setTimeout(r, 10_000));
|
||||
}
|
||||
|
||||
return label;
|
||||
};
|
||||
|
||||
const terminate = async () => {
|
||||
await deleteVm(vmName);
|
||||
// Resources with deleteOption=Delete are cleaned up automatically
|
||||
// But clean up anything that might be left
|
||||
await deleteNic(nicName);
|
||||
await deletePublicIp(publicIpName);
|
||||
};
|
||||
|
||||
return {
|
||||
cloud: "azure",
|
||||
id: vmName,
|
||||
imageId: options.imageId,
|
||||
instanceType: vmSize,
|
||||
region: config().location,
|
||||
get publicIp() {
|
||||
return publicIp;
|
||||
},
|
||||
spawn: spawnFn,
|
||||
spawnSafe: spawnSafeFn,
|
||||
upload,
|
||||
attach,
|
||||
snapshot,
|
||||
waitForSsh,
|
||||
close: terminate,
|
||||
[Symbol.asyncDispose]: terminate,
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
# Version: 12
|
||||
# A script that installs the dependencies needed to build and test Bun.
|
||||
# This should work on Windows 10 or newer with PowerShell.
|
||||
# Version: 14
|
||||
# A script that installs the dependencies needed to build and test Bun on Windows.
|
||||
# Supports both x64 and ARM64 using Scoop for package management.
|
||||
# Used by Azure [build images] pipeline.
|
||||
|
||||
# If this script does not work on your machine, please open an issue:
|
||||
# https://github.com/oven-sh/bun/issues
|
||||
@@ -11,7 +12,7 @@
|
||||
|
||||
param (
|
||||
[Parameter(Mandatory = $false)]
|
||||
[switch]$CI = $false,
|
||||
[switch]$CI = ($env:CI -eq "true"),
|
||||
[Parameter(Mandatory = $false)]
|
||||
[switch]$Optimize = $CI
|
||||
)
|
||||
@@ -19,17 +20,26 @@ param (
|
||||
$ErrorActionPreference = "Stop"
|
||||
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass -Force
|
||||
|
||||
function Execute-Command {
|
||||
$command = $args -join ' '
|
||||
Write-Output "$ $command"
|
||||
# Detect ARM64 from registry (works even under x64 emulation)
|
||||
$realArch = (Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment').PROCESSOR_ARCHITECTURE
|
||||
$script:IsARM64 = $realArch -eq "ARM64"
|
||||
|
||||
& $args[0] $args[1..$args.Length]
|
||||
|
||||
if ((-not $?) -or ($LASTEXITCODE -ne 0 -and $null -ne $LASTEXITCODE)) {
|
||||
throw "Command failed: $command"
|
||||
# If we're on ARM64 but running under x64 emulation, re-launch as native ARM64.
|
||||
# Azure Run Command uses x64-emulated PowerShell which breaks package installs.
|
||||
if ($script:IsARM64 -and $env:PROCESSOR_ARCHITECTURE -ne "ARM64") {
|
||||
$nativePS = "$env:SystemRoot\Sysnative\WindowsPowerShell\v1.0\powershell.exe"
|
||||
if (Test-Path $nativePS) {
|
||||
Write-Output "Re-launching bootstrap as native ARM64 PowerShell..."
|
||||
& $nativePS -NoProfile -ExecutionPolicy Bypass -File $MyInvocation.MyCommand.Path @PSBoundParameters
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Utility functions
|
||||
# ============================================================================
|
||||
|
||||
|
||||
function Which {
|
||||
param ([switch]$Required = $false)
|
||||
|
||||
@@ -46,16 +56,6 @@ function Which {
|
||||
}
|
||||
}
|
||||
|
||||
function Execute-Script {
|
||||
param (
|
||||
[Parameter(Mandatory = $true, Position = 0)]
|
||||
[string]$Path
|
||||
)
|
||||
|
||||
$pwsh = Which pwsh powershell -Required
|
||||
Execute-Command $pwsh $Path
|
||||
}
|
||||
|
||||
function Download-File {
|
||||
param (
|
||||
[Parameter(Mandatory = $true, Position = 0)]
|
||||
@@ -87,18 +87,6 @@ function Download-File {
|
||||
return $Path
|
||||
}
|
||||
|
||||
function Install-Chocolatey {
|
||||
if (Which choco) {
|
||||
return
|
||||
}
|
||||
|
||||
Write-Output "Installing Chocolatey..."
|
||||
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072
|
||||
$installScript = Download-File "https://community.chocolatey.org/install.ps1"
|
||||
Execute-Script $installScript
|
||||
Refresh-Path
|
||||
}
|
||||
|
||||
function Refresh-Path {
|
||||
$paths = @(
|
||||
[System.Environment]::GetEnvironmentVariable("Path", "Machine"),
|
||||
@@ -111,15 +99,19 @@ function Refresh-Path {
|
||||
Where-Object { $_ -and (Test-Path $_) } |
|
||||
Select-Object -Unique
|
||||
$env:Path = ($uniquePaths -join ';').TrimEnd(';')
|
||||
|
||||
if ($env:ChocolateyInstall) {
|
||||
Import-Module $env:ChocolateyInstall\helpers\chocolateyProfile.psm1 -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
function Add-To-Path {
|
||||
$absolutePath = Resolve-Path $args[0]
|
||||
$currentPath = [Environment]::GetEnvironmentVariable("Path", "Machine")
|
||||
param (
|
||||
[Parameter(Mandatory = $true, Position = 0)]
|
||||
[string]$PathToAdd,
|
||||
[Parameter(Mandatory = $false)]
|
||||
[ValidateSet("Machine", "User")]
|
||||
[string]$Scope = "Machine"
|
||||
)
|
||||
|
||||
$absolutePath = Resolve-Path $PathToAdd
|
||||
$currentPath = [Environment]::GetEnvironmentVariable("Path", $Scope)
|
||||
if ($currentPath -like "*$absolutePath*") {
|
||||
return
|
||||
}
|
||||
@@ -140,8 +132,8 @@ function Add-To-Path {
|
||||
}
|
||||
}
|
||||
|
||||
Write-Output "Adding $absolutePath to PATH..."
|
||||
[Environment]::SetEnvironmentVariable("Path", "$newPath", "Machine")
|
||||
Write-Output "Adding $absolutePath to PATH ($Scope)..."
|
||||
[Environment]::SetEnvironmentVariable("Path", "$newPath", $Scope)
|
||||
Refresh-Path
|
||||
}
|
||||
|
||||
@@ -158,104 +150,374 @@ function Set-Env {
|
||||
[System.Environment]::SetEnvironmentVariable("$Name", "$Value", "Process")
|
||||
}
|
||||
|
||||
function Install-Package {
|
||||
# ============================================================================
|
||||
# Scoop — ARM64-native package manager
|
||||
# ============================================================================
|
||||
|
||||
function Install-Scoop {
|
||||
if (Which scoop) {
|
||||
return
|
||||
}
|
||||
|
||||
Write-Output "Installing Scoop..."
|
||||
# Scoop blocks admin installs unless -RunAsAdmin is passed.
|
||||
# Install to a known global location so all users can access it.
|
||||
$env:SCOOP = "C:\Scoop"
|
||||
[Environment]::SetEnvironmentVariable("SCOOP", $env:SCOOP, "Machine")
|
||||
iex "& {$(irm get.scoop.sh)} -RunAsAdmin -ScoopDir C:\Scoop"
|
||||
Add-To-Path "C:\Scoop\shims"
|
||||
Refresh-Path
|
||||
Write-Output "Scoop version: $(scoop --version)"
|
||||
}
|
||||
|
||||
function Install-Scoop-Package {
|
||||
param (
|
||||
[Parameter(Mandatory = $true, Position = 0)]
|
||||
[string]$Name,
|
||||
[Parameter(Mandatory = $false)]
|
||||
[string]$Command = $Name,
|
||||
[Parameter(Mandatory = $false)]
|
||||
[string]$Version,
|
||||
[Parameter(Mandatory = $false)]
|
||||
[switch]$Force = $false,
|
||||
[Parameter(Mandatory = $false)]
|
||||
[string[]]$ExtraArgs = @()
|
||||
[string]$Command = $Name
|
||||
)
|
||||
|
||||
if (-not $Force `
|
||||
-and (Which $Command) `
|
||||
-and (-not $Version -or (& $Command --version) -like "*$Version*")) {
|
||||
if (Which $Command) {
|
||||
return
|
||||
}
|
||||
|
||||
Write-Output "Installing $Name..."
|
||||
$flags = @(
|
||||
"--yes",
|
||||
"--accept-license",
|
||||
"--no-progress",
|
||||
"--force"
|
||||
)
|
||||
if ($Version) {
|
||||
$flags += "--version=$Version"
|
||||
}
|
||||
|
||||
Execute-Command choco install $Name @flags @ExtraArgs
|
||||
Write-Output "Installing $Name (via Scoop)..."
|
||||
# Scoop post_install scripts can have non-fatal Remove-Item errors
|
||||
# (e.g. 7zip ARM64 7zr.exe locked, llvm-arm64 missing Uninstall.exe).
|
||||
# Suppress all error streams so they don't kill the bootstrap or Packer.
|
||||
$prevErrorPref = $ErrorActionPreference
|
||||
$ErrorActionPreference = "SilentlyContinue"
|
||||
scoop install $Name *>&1 | ForEach-Object { "$_" } | Write-Host
|
||||
$ErrorActionPreference = $prevErrorPref
|
||||
Refresh-Path
|
||||
}
|
||||
|
||||
function Install-Packages {
|
||||
foreach ($package in $args) {
|
||||
Install-Package $package
|
||||
}
|
||||
}
|
||||
|
||||
function Install-Common-Software {
|
||||
Install-Chocolatey
|
||||
Install-Pwsh
|
||||
Install-Git
|
||||
Install-Packages curl 7zip nssm
|
||||
Install-NodeJs
|
||||
Install-Bun
|
||||
Install-Cygwin
|
||||
if ($CI) {
|
||||
# FIXME: Installing tailscale causes the AWS metadata server to become unreachable
|
||||
# Install-Tailscale
|
||||
Install-Buildkite
|
||||
}
|
||||
}
|
||||
|
||||
function Install-Pwsh {
|
||||
Install-Package powershell-core -Command pwsh
|
||||
|
||||
if ($CI) {
|
||||
$shellPath = (Which pwsh -Required)
|
||||
New-ItemProperty `
|
||||
-Path "HKLM:\\SOFTWARE\\OpenSSH" `
|
||||
-Name DefaultShell `
|
||||
-Value $shellPath `
|
||||
-PropertyType String `
|
||||
-Force
|
||||
}
|
||||
}
|
||||
# ============================================================================
|
||||
# Scoop packages (native ARM64 binaries)
|
||||
# ============================================================================
|
||||
|
||||
function Install-Git {
|
||||
Install-Packages git
|
||||
Install-Scoop-Package git
|
||||
|
||||
# Git for Windows ships Unix tools (cat, head, tail, etc.) in usr\bin
|
||||
$gitUsrBin = "C:\Scoop\apps\git\current\usr\bin"
|
||||
if (Test-Path $gitUsrBin) {
|
||||
Add-To-Path $gitUsrBin
|
||||
}
|
||||
|
||||
if ($CI) {
|
||||
Execute-Command git config --system --add safe.directory "*"
|
||||
Execute-Command git config --system core.autocrlf false
|
||||
Execute-Command git config --system core.eol lf
|
||||
Execute-Command git config --system core.longpaths true
|
||||
git config --system --add safe.directory "*"
|
||||
git config --system core.autocrlf false
|
||||
git config --system core.eol lf
|
||||
git config --system core.longpaths true
|
||||
}
|
||||
}
|
||||
|
||||
function Install-NodeJs {
|
||||
Install-Package nodejs -Command node -Version "24.3.0"
|
||||
# Pin to match the ABI version Bun expects (NODE_MODULE_VERSION 137).
|
||||
# Latest Node (25.x) uses ABI 141 which breaks node-gyp tests.
|
||||
Install-Scoop-Package "nodejs@24.3.0" -Command node
|
||||
}
|
||||
|
||||
function Install-Bun {
|
||||
Install-Package bun -Version "1.3.1"
|
||||
function Install-CMake {
|
||||
Install-Scoop-Package cmake
|
||||
}
|
||||
|
||||
function Install-Llvm {
|
||||
$LLVM_VERSION = "21.1.8"
|
||||
if (Which clang-cl) {
|
||||
return
|
||||
}
|
||||
if ($script:IsARM64) {
|
||||
Install-Scoop-Package "llvm-arm64@$LLVM_VERSION" -Command clang-cl
|
||||
} else {
|
||||
Install-Scoop-Package "llvm@$LLVM_VERSION" -Command clang-cl
|
||||
}
|
||||
}
|
||||
|
||||
function Install-Ninja {
|
||||
Install-Scoop-Package ninja
|
||||
}
|
||||
|
||||
function Install-Python {
|
||||
Install-Scoop-Package python
|
||||
}
|
||||
|
||||
function Install-Go {
|
||||
Install-Scoop-Package go
|
||||
}
|
||||
|
||||
function Install-Ruby {
|
||||
Install-Scoop-Package ruby
|
||||
}
|
||||
|
||||
function Install-7zip {
|
||||
Install-Scoop-Package 7zip -Command 7z
|
||||
}
|
||||
|
||||
function Install-Make {
|
||||
Install-Scoop-Package make
|
||||
}
|
||||
|
||||
function Install-Cygwin {
|
||||
Install-Package cygwin
|
||||
Add-To-Path "C:\tools\cygwin\bin"
|
||||
# Cygwin's default mirror (mirrors.kernel.org) can be unreachable from Azure.
|
||||
# Make this non-fatal — the build will fail later if cygwin is actually needed.
|
||||
try {
|
||||
Install-Scoop-Package cygwin
|
||||
# Cygwin binaries are at <scoop>/apps/cygwin/current/root/bin
|
||||
$cygwinBin = "C:\Scoop\apps\cygwin\current\root\bin"
|
||||
if (Test-Path $cygwinBin) {
|
||||
Add-To-Path $cygwinBin # Machine scope (default) — survives Sysprep
|
||||
}
|
||||
} catch {
|
||||
Write-Warning "Cygwin installation failed (non-fatal): $_"
|
||||
}
|
||||
}
|
||||
|
||||
function Install-Tailscale {
|
||||
Install-Package tailscale
|
||||
# ============================================================================
|
||||
# Manual installs (not available or not ideal via Scoop)
|
||||
# ============================================================================
|
||||
|
||||
function Install-Pwsh {
|
||||
if (Which pwsh) {
|
||||
return
|
||||
}
|
||||
|
||||
$pwshArch = if ($script:IsARM64) { "arm64" } else { "x64" }
|
||||
Write-Output "Installing PowerShell Core ($pwshArch)..."
|
||||
$msi = Download-File "https://github.com/PowerShell/PowerShell/releases/download/v7.5.2/PowerShell-7.5.2-win-$pwshArch.msi" -Name "pwsh-$pwshArch.msi"
|
||||
$process = Start-Process msiexec -ArgumentList "/i `"$msi`" /quiet /norestart ADD_PATH=1" -Wait -PassThru -NoNewWindow
|
||||
if ($process.ExitCode -ne 0) {
|
||||
throw "Failed to install PowerShell: code $($process.ExitCode)"
|
||||
}
|
||||
Remove-Item $msi -ErrorAction SilentlyContinue
|
||||
Refresh-Path
|
||||
}
|
||||
|
||||
function Install-OpenSSH {
|
||||
$sshdService = Get-Service -Name sshd -ErrorAction SilentlyContinue
|
||||
if ($sshdService) {
|
||||
return
|
||||
}
|
||||
|
||||
Write-Output "Installing OpenSSH Server..."
|
||||
# Add-WindowsCapability requires DISM elevation which isn't available in Packer's
|
||||
# WinRM session. Download and install from GitHub releases instead.
|
||||
$arch = if ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture -eq "Arm64") { "Arm64" } else { "Win64" }
|
||||
$url = "https://github.com/PowerShell/Win32-OpenSSH/releases/download/v9.8.1.0p1-Preview/OpenSSH-${arch}.zip"
|
||||
$zip = "$env:TEMP\OpenSSH.zip"
|
||||
$dest = "$env:ProgramFiles\OpenSSH"
|
||||
Invoke-WebRequest -Uri $url -OutFile $zip -UseBasicParsing
|
||||
Expand-Archive -Path $zip -DestinationPath "$env:TEMP\OpenSSH" -Force
|
||||
New-Item -Path $dest -ItemType Directory -Force | Out-Null
|
||||
$extractedDir = Get-ChildItem -Path "$env:TEMP\OpenSSH" -Directory | Select-Object -First 1
|
||||
Get-ChildItem -Path $extractedDir.FullName -Recurse | Move-Item -Destination $dest -Force
|
||||
& "$dest\install-sshd.ps1"
|
||||
& "$dest\FixHostFilePermissions.ps1" -Confirm:$false
|
||||
Remove-Item $zip, "$env:TEMP\OpenSSH" -Recurse -Force -ErrorAction SilentlyContinue
|
||||
|
||||
# Configure sshd to start on boot (don't start now — host keys may not exist yet during image build)
|
||||
Set-Service -Name sshd -StartupType Automatic
|
||||
|
||||
# Set default shell to pwsh
|
||||
$pwshPath = (Which pwsh -ErrorAction SilentlyContinue)
|
||||
if (-not $pwshPath) { $pwshPath = (Which powershell) }
|
||||
if ($pwshPath) {
|
||||
New-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell `
|
||||
-Value $pwshPath -PropertyType String -Force
|
||||
}
|
||||
|
||||
# Firewall rule for port 22
|
||||
$rule = Get-NetFirewallRule -Name "OpenSSH-Server" -ErrorAction SilentlyContinue
|
||||
if (-not $rule) {
|
||||
New-NetFirewallRule -Profile Any -Name "OpenSSH-Server" `
|
||||
-DisplayName "OpenSSH Server (sshd)" -Enabled True `
|
||||
-Direction Inbound -Protocol TCP -Action Allow -LocalPort 22
|
||||
}
|
||||
|
||||
# Configure sshd_config for key-based auth
|
||||
$sshdConfigPath = "C:\ProgramData\ssh\sshd_config"
|
||||
if (Test-Path $sshdConfigPath) {
|
||||
$config = Get-Content $sshdConfigPath
|
||||
$config = $config -replace '#PubkeyAuthentication yes', 'PubkeyAuthentication yes'
|
||||
$config = $config -replace 'PasswordAuthentication yes', 'PasswordAuthentication no'
|
||||
Set-Content -Path $sshdConfigPath -Value $config
|
||||
}
|
||||
|
||||
Write-Output "OpenSSH Server installed and configured"
|
||||
|
||||
# Register a startup task that fetches oven-sh GitHub org members' SSH keys
|
||||
# on every boot so any bun dev can SSH in.
|
||||
$fetchScript = @'
|
||||
try {
|
||||
$members = Invoke-RestMethod -Uri "https://api.github.com/orgs/oven-sh/members" -Headers @{ "User-Agent" = "bun-ci" }
|
||||
$keys = @()
|
||||
foreach ($member in $members) {
|
||||
if ($member.type -ne "User" -or -not $member.login) { continue }
|
||||
try {
|
||||
$userKeys = (Invoke-WebRequest -Uri "https://github.com/$($member.login).keys" -UseBasicParsing).Content
|
||||
if ($userKeys) { $keys += $userKeys.Trim() }
|
||||
} catch { }
|
||||
}
|
||||
if ($keys.Count -gt 0) {
|
||||
$keysPath = "C:\ProgramData\ssh\administrators_authorized_keys"
|
||||
Set-Content -Path $keysPath -Value ($keys -join "`n") -Force
|
||||
icacls $keysPath /inheritance:r /grant "SYSTEM:(F)" /grant "Administrators:(R)" | Out-Null
|
||||
}
|
||||
} catch { }
|
||||
'@
|
||||
$scriptPath = "C:\ProgramData\ssh\fetch-ssh-keys.ps1"
|
||||
Set-Content -Path $scriptPath -Value $fetchScript -Force
|
||||
$action = New-ScheduledTaskAction -Execute "pwsh.exe" -Argument "-NoProfile -ExecutionPolicy Bypass -File `"$scriptPath`""
|
||||
$trigger = New-ScheduledTaskTrigger -AtStartup
|
||||
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
|
||||
Register-ScheduledTask -TaskName "FetchSshKeys" -Action $action -Trigger $trigger `
|
||||
-Settings $settings -User "SYSTEM" -RunLevel Highest -Force
|
||||
Write-Output "Registered FetchSshKeys startup task"
|
||||
}
|
||||
|
||||
function Install-Ccache {
|
||||
if (Which ccache) {
|
||||
return
|
||||
}
|
||||
|
||||
$version = "4.12.2"
|
||||
$archSuffix = if ($script:IsARM64) { "aarch64" } else { "x86_64" }
|
||||
Write-Output "Installing ccache $version ($archSuffix)..."
|
||||
$zip = Download-File "https://github.com/ccache/ccache/releases/download/v$version/ccache-$version-windows-$archSuffix.zip" -Name "ccache-$archSuffix.zip"
|
||||
$extractDir = "$env:TEMP\ccache-extract"
|
||||
Expand-Archive $zip $extractDir -Force
|
||||
$installDir = "$env:ProgramFiles\ccache"
|
||||
New-Item -Path $installDir -ItemType Directory -Force | Out-Null
|
||||
Copy-Item "$extractDir\ccache-$version-windows-$archSuffix\*" $installDir -Recurse -Force
|
||||
Remove-Item $zip -ErrorAction SilentlyContinue
|
||||
Remove-Item $extractDir -Recurse -ErrorAction SilentlyContinue
|
||||
Add-To-Path $installDir
|
||||
}
|
||||
|
||||
function Install-Bun {
|
||||
if (Which bun) {
|
||||
return
|
||||
}
|
||||
|
||||
if ($script:IsARM64) {
|
||||
# ARM64 bun binary from blob storage (faster than GitHub releases for CI)
|
||||
Write-Output "Installing Bun (ARM64)..."
|
||||
$zip = Download-File "https://buncistore.blob.core.windows.net/artifacts/bun-windows-aarch64.zip" -Name "bun-arm64.zip"
|
||||
$extractDir = "$env:TEMP\bun-arm64"
|
||||
Expand-Archive -Path $zip -DestinationPath $extractDir -Force
|
||||
$bunExe = Get-ChildItem $extractDir -Recurse -Filter "*.exe" | Where-Object { $_.Name -match "bun" } | Select-Object -First 1
|
||||
if ($bunExe) {
|
||||
Copy-Item $bunExe.FullName "C:\Windows\System32\bun.exe" -Force
|
||||
Write-Output "Bun ARM64 installed to C:\Windows\System32\bun.exe"
|
||||
} else {
|
||||
throw "Failed to find bun executable in ARM64 zip"
|
||||
}
|
||||
} else {
|
||||
Write-Output "Installing Bun..."
|
||||
$installScript = Download-File "https://bun.sh/install.ps1" -Name "bun-install.ps1"
|
||||
$pwsh = Which pwsh powershell -Required
|
||||
& $pwsh $installScript
|
||||
Refresh-Path
|
||||
# Copy to System32 so it survives Sysprep (user profile PATH is lost)
|
||||
$bunPath = Which bun
|
||||
if ($bunPath) {
|
||||
Copy-Item $bunPath "C:\Windows\System32\bun.exe" -Force
|
||||
Write-Output "Bun copied to C:\Windows\System32\bun.exe"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Install-Rust {
|
||||
if (Which rustc) {
|
||||
return
|
||||
}
|
||||
|
||||
$rustPath = Join-Path $env:ProgramFiles "Rust"
|
||||
if (-not (Test-Path $rustPath)) {
|
||||
New-Item -Path $rustPath -ItemType Directory | Out-Null
|
||||
}
|
||||
|
||||
# Set install paths before running rustup so it installs directly
|
||||
# to Program Files (avoids issues with SYSTEM user profile path)
|
||||
$env:CARGO_HOME = "$rustPath\cargo"
|
||||
$env:RUSTUP_HOME = "$rustPath\rustup"
|
||||
|
||||
Write-Output "Installing Rustup..."
|
||||
$rustupInit = Download-File "https://win.rustup.rs/" -Name "rustup-init.exe"
|
||||
|
||||
Write-Output "Installing Rust..."
|
||||
& $rustupInit -y
|
||||
|
||||
Write-Output "Setting environment variables for Rust..."
|
||||
Set-Env "CARGO_HOME" "$rustPath\cargo"
|
||||
Set-Env "RUSTUP_HOME" "$rustPath\rustup"
|
||||
Add-To-Path "$rustPath\cargo\bin"
|
||||
}
|
||||
|
||||
function Install-Visual-Studio {
|
||||
param (
|
||||
[Parameter(Mandatory = $false)]
|
||||
[string]$Edition = "community"
|
||||
)
|
||||
|
||||
Write-Output "Downloading Visual Studio installer..."
|
||||
$vsInstaller = Download-File "https://aka.ms/vs/17/release/vs_$Edition.exe"
|
||||
|
||||
Write-Output "Installing Visual Studio..."
|
||||
$vsInstallArgs = @(
|
||||
"--passive",
|
||||
"--norestart",
|
||||
"--wait",
|
||||
"--force",
|
||||
"--locale en-US",
|
||||
"--add Microsoft.VisualStudio.Workload.NativeDesktop",
|
||||
"--includeRecommended"
|
||||
)
|
||||
$process = Start-Process $vsInstaller -ArgumentList ($vsInstallArgs -join ' ') -Wait -PassThru -NoNewWindow
|
||||
# Exit code 3010 means "reboot required" which is not a real error
|
||||
if ($process.ExitCode -ne 0 -and $process.ExitCode -ne 3010) {
|
||||
throw "Failed to install Visual Studio: code $($process.ExitCode)"
|
||||
}
|
||||
}
|
||||
|
||||
function Install-PdbAddr2line {
|
||||
cargo install --examples "pdb-addr2line@0.11.2"
|
||||
# Also copy to System32 so it's always on PATH (like bun.exe)
|
||||
$src = Join-Path $env:CARGO_HOME "bin\pdb-addr2line.exe"
|
||||
if (Test-Path $src) {
|
||||
Copy-Item $src "C:\Windows\System32\pdb-addr2line.exe" -Force
|
||||
Write-Output "pdb-addr2line copied to C:\Windows\System32"
|
||||
}
|
||||
}
|
||||
|
||||
function Install-Nssm {
|
||||
if (Which nssm) {
|
||||
return
|
||||
}
|
||||
|
||||
# Try Scoop first, fall back to our mirror if nssm.cc is down (503 errors)
|
||||
Install-Scoop-Package nssm
|
||||
|
||||
if (-not (Which nssm)) {
|
||||
Write-Output "Scoop install of nssm failed, downloading from mirror..."
|
||||
$zip = Download-File "https://buncistore.blob.core.windows.net/artifacts/nssm-2.24-103-gdee49fc.zip" -Name "nssm.zip"
|
||||
Expand-Archive -Path $zip -DestinationPath "C:\Windows\Temp\nssm" -Force
|
||||
$nssm = Get-ChildItem "C:\Windows\Temp\nssm" -Recurse -Filter "nssm.exe" | Where-Object { $_.DirectoryName -like "*win64*" } | Select-Object -First 1
|
||||
if ($nssm) {
|
||||
Copy-Item $nssm.FullName "C:\Windows\System32\nssm.exe" -Force
|
||||
Write-Output "nssm installed to C:\Windows\System32\nssm.exe"
|
||||
} else {
|
||||
throw "Failed to install nssm"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Buildkite
|
||||
# ============================================================================
|
||||
|
||||
function Create-Buildkite-Environment-Hooks {
|
||||
param (
|
||||
[Parameter(Mandatory = $true)]
|
||||
@@ -278,6 +540,17 @@ function Create-Buildkite-Environment-Hooks {
|
||||
"@ | Set-Content -Path $environmentHook -Encoding UTF8
|
||||
|
||||
Write-Output "Environment hook created at $environmentHook"
|
||||
|
||||
# pre-exit hook: logout from Tailscale so ephemeral nodes are removed
|
||||
# instantly instead of waiting 30-60 minutes. This runs after the job
|
||||
# finishes, which is after the SSH user wait loop in runner.node.mjs.
|
||||
$preExitHook = Join-Path $hooksDir "pre-exit.ps1"
|
||||
@"
|
||||
if (Test-Path "C:\Program Files\Tailscale\tailscale.exe") {
|
||||
& "C:\Program Files\Tailscale\tailscale.exe" logout 2>`$null
|
||||
}
|
||||
"@ | Set-Content -Path $preExitHook -Encoding UTF8
|
||||
Write-Output "Pre-exit hook created at $preExitHook"
|
||||
}
|
||||
|
||||
function Install-Buildkite {
|
||||
@@ -288,7 +561,8 @@ function Install-Buildkite {
|
||||
Write-Output "Installing Buildkite agent..."
|
||||
$env:buildkiteAgentToken = "xxx"
|
||||
$installScript = Download-File "https://raw.githubusercontent.com/buildkite/agent/main/install.ps1"
|
||||
Execute-Script $installScript
|
||||
$pwsh = Which pwsh powershell -Required
|
||||
& $pwsh $installScript
|
||||
Refresh-Path
|
||||
|
||||
if ($CI) {
|
||||
@@ -300,96 +574,9 @@ function Install-Buildkite {
|
||||
}
|
||||
}
|
||||
|
||||
function Install-Build-Essentials {
|
||||
Install-Visual-Studio
|
||||
Install-Packages `
|
||||
cmake `
|
||||
make `
|
||||
ninja `
|
||||
python `
|
||||
golang `
|
||||
nasm `
|
||||
ruby `
|
||||
strawberryperl `
|
||||
mingw
|
||||
Install-Rust
|
||||
Install-Ccache
|
||||
# Needed to remap stack traces
|
||||
Install-PdbAddr2line
|
||||
Install-Llvm
|
||||
}
|
||||
|
||||
function Install-Visual-Studio {
|
||||
param (
|
||||
[Parameter(Mandatory = $false)]
|
||||
[string]$Edition = "community"
|
||||
)
|
||||
|
||||
Write-Output "Downloading Visual Studio installer..."
|
||||
$vsInstaller = Download-File "https://aka.ms/vs/17/release/vs_$Edition.exe"
|
||||
|
||||
Write-Output "Installing Visual Studio..."
|
||||
$vsInstallArgs = @(
|
||||
"--passive",
|
||||
"--norestart",
|
||||
"--wait",
|
||||
"--force",
|
||||
"--locale en-US",
|
||||
"--add Microsoft.VisualStudio.Workload.NativeDesktop",
|
||||
"--includeRecommended"
|
||||
)
|
||||
$startInfo = New-Object System.Diagnostics.ProcessStartInfo
|
||||
$startInfo.FileName = $vsInstaller
|
||||
$startInfo.Arguments = $vsInstallArgs -join ' '
|
||||
$startInfo.CreateNoWindow = $true
|
||||
$process = New-Object System.Diagnostics.Process
|
||||
$process.StartInfo = $startInfo
|
||||
$process.Start()
|
||||
$process.WaitForExit()
|
||||
if ($process.ExitCode -ne 0) {
|
||||
throw "Failed to install Visual Studio: code $($process.ExitCode)"
|
||||
}
|
||||
}
|
||||
|
||||
function Install-Rust {
|
||||
if (Which rustc) {
|
||||
return
|
||||
}
|
||||
|
||||
Write-Output "Installing Rustup..."
|
||||
$rustupInit = Download-File "https://win.rustup.rs/" -Name "rustup-init.exe"
|
||||
|
||||
Write-Output "Installing Rust..."
|
||||
Execute-Command $rustupInit -y
|
||||
|
||||
Write-Output "Moving Rust to $env:ProgramFiles..."
|
||||
$rustPath = Join-Path $env:ProgramFiles "Rust"
|
||||
if (-not (Test-Path $rustPath)) {
|
||||
New-Item -Path $rustPath -ItemType Directory
|
||||
}
|
||||
Move-Item "$env:UserProfile\.cargo" "$rustPath\cargo" -Force
|
||||
Move-Item "$env:UserProfile\.rustup" "$rustPath\rustup" -Force
|
||||
|
||||
Write-Output "Setting environment variables for Rust..."
|
||||
Set-Env "CARGO_HOME" "$rustPath\cargo"
|
||||
Set-Env "RUSTUP_HOME" "$rustPath\rustup"
|
||||
Add-To-Path "$rustPath\cargo\bin"
|
||||
}
|
||||
|
||||
function Install-Ccache {
|
||||
Install-Package ccache
|
||||
}
|
||||
|
||||
function Install-PdbAddr2line {
|
||||
Execute-Command cargo install --examples "pdb-addr2line@0.11.2"
|
||||
}
|
||||
|
||||
function Install-Llvm {
|
||||
Install-Package llvm `
|
||||
-Command clang-cl `
|
||||
-Version "21.1.8"
|
||||
Add-To-Path "$env:ProgramFiles\LLVM\bin"
|
||||
}
|
||||
# ============================================================================
|
||||
# System optimization
|
||||
# ============================================================================
|
||||
|
||||
function Optimize-System {
|
||||
Disable-Windows-Defender
|
||||
@@ -417,8 +604,11 @@ function Disable-Windows-Threat-Protection {
|
||||
}
|
||||
|
||||
function Uninstall-Windows-Defender {
|
||||
Write-Output "Uninstalling Windows Defender..."
|
||||
Uninstall-WindowsFeature -Name Windows-Defender
|
||||
# Requires a reboot — run before the windows-restart Packer provisioner.
|
||||
if (Get-Command Uninstall-WindowsFeature -ErrorAction SilentlyContinue) {
|
||||
Write-Output "Uninstalling Windows Defender..."
|
||||
Uninstall-WindowsFeature -Name Windows-Defender
|
||||
}
|
||||
}
|
||||
|
||||
function Disable-Windows-Services {
|
||||
@@ -432,8 +622,12 @@ function Disable-Windows-Services {
|
||||
)
|
||||
|
||||
foreach ($service in $services) {
|
||||
Stop-Service $service -Force
|
||||
Set-Service $service -StartupType Disabled
|
||||
try {
|
||||
Stop-Service $service -Force -ErrorAction SilentlyContinue
|
||||
Set-Service $service -StartupType Disabled -ErrorAction SilentlyContinue
|
||||
} catch {
|
||||
Write-Warning "Could not disable service: $service"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -448,13 +642,98 @@ function Disable-Power-Management {
|
||||
powercfg /change hibernate-timeout-dc 0
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Main
|
||||
# ============================================================================
|
||||
|
||||
if ($Optimize) {
|
||||
Optimize-System
|
||||
}
|
||||
|
||||
Install-Common-Software
|
||||
Install-Build-Essentials
|
||||
# Scoop package manager
|
||||
Install-Scoop
|
||||
|
||||
# Packages via Scoop (native ARM64 or x64 depending on architecture)
|
||||
# 7zip must be installed before git — git depends on 7zip via Scoop,
|
||||
# and 7zip's post_install has a cleanup error on ARM64 SYSTEM context.
|
||||
Install-7zip
|
||||
Install-Git
|
||||
Install-NodeJs
|
||||
Install-CMake
|
||||
Install-Ninja
|
||||
Install-Python
|
||||
Install-Go
|
||||
Install-Ruby
|
||||
Install-Make
|
||||
Install-Llvm
|
||||
Install-Cygwin
|
||||
Install-Nssm
|
||||
Install-Scoop-Package perl
|
||||
|
||||
# x64-only packages (not needed on ARM64)
|
||||
if (-not $script:IsARM64) {
|
||||
Install-Scoop-Package nasm
|
||||
Install-Scoop-Package mingw -Command gcc
|
||||
}
|
||||
|
||||
function Install-Tailscale {
|
||||
if (Which tailscale -ErrorAction SilentlyContinue) {
|
||||
return
|
||||
}
|
||||
|
||||
Write-Output "Installing Tailscale..."
|
||||
$msi = "$env:TEMP\tailscale-setup.msi"
|
||||
$arch = if ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture -eq "Arm64") { "arm64" } else { "amd64" }
|
||||
Invoke-WebRequest -Uri "https://pkgs.tailscale.com/stable/tailscale-setup-latest-${arch}.msi" -OutFile $msi -UseBasicParsing
|
||||
Start-Process msiexec.exe -ArgumentList "/i `"$msi`" /quiet /norestart" -Wait
|
||||
Remove-Item $msi -ErrorAction SilentlyContinue
|
||||
Refresh-Path
|
||||
Write-Output "Tailscale installed"
|
||||
|
||||
# Register a startup task that reads the tailscale authkey from Azure IMDS
|
||||
# tags and joins the tailnet. The key is set by robobun as a VM tag.
|
||||
$joinScript = @'
|
||||
try {
|
||||
$headers = @{ "Metadata" = "true" }
|
||||
$response = Invoke-RestMethod -Uri "http://169.254.169.254/metadata/instance/compute/tagsList?api-version=2021-02-01" -Headers $headers
|
||||
$authkey = ($response | Where-Object { $_.name -eq "tailscale:authkey" }).value
|
||||
if ($authkey) {
|
||||
$stepKey = ($response | Where-Object { $_.name -eq "buildkite:step-key" }).value
|
||||
$buildNumber = ($response | Where-Object { $_.name -eq "buildkite:build-number" }).value
|
||||
if ($stepKey) {
|
||||
$hostname = "azure-${stepKey}"
|
||||
if ($buildNumber) { $hostname += "-${buildNumber}" }
|
||||
} else {
|
||||
$hostname = (Invoke-RestMethod -Uri "http://169.254.169.254/metadata/instance/compute/name?api-version=2021-02-01&format=text" -Headers $headers)
|
||||
}
|
||||
& "C:\Program Files\Tailscale\tailscale.exe" up --authkey=$authkey --hostname=$hostname --unattended
|
||||
}
|
||||
} catch { }
|
||||
'@
|
||||
$scriptPath = "C:\ProgramData\tailscale-join.ps1"
|
||||
Set-Content -Path $scriptPath -Value $joinScript -Force
|
||||
$action = New-ScheduledTaskAction -Execute "pwsh.exe" -Argument "-NoProfile -ExecutionPolicy Bypass -File `"$scriptPath`""
|
||||
$trigger = New-ScheduledTaskTrigger -AtStartup
|
||||
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
|
||||
Register-ScheduledTask -TaskName "TailscaleJoin" -Action $action -Trigger $trigger `
|
||||
-Settings $settings -User "SYSTEM" -RunLevel Highest -Force
|
||||
Write-Output "Registered TailscaleJoin startup task"
|
||||
}
|
||||
|
||||
# Manual installs (not in Scoop or need special handling)
|
||||
Install-Pwsh
|
||||
Install-OpenSSH
|
||||
#Install-Tailscale # Disabled — Tailscale adapter interferes with IPv6 multicast tests (node-dgram)
|
||||
Install-Bun
|
||||
Install-Ccache
|
||||
Install-Rust
|
||||
Install-Visual-Studio
|
||||
Install-PdbAddr2line
|
||||
|
||||
if ($CI) {
|
||||
Install-Buildkite
|
||||
}
|
||||
|
||||
if ($Optimize) {
|
||||
Optimize-System-Needs-Reboot
|
||||
}
|
||||
}
|
||||
@@ -14,14 +14,7 @@ import {
|
||||
startGroup,
|
||||
} from "./utils.mjs";
|
||||
|
||||
// Detect Windows ARM64 - bun may run under x64 emulation (WoW64), so check multiple indicators
|
||||
const isWindowsARM64 =
|
||||
isWindows &&
|
||||
(process.env.PROCESSOR_ARCHITECTURE === "ARM64" ||
|
||||
process.env.VSCMD_ARG_HOST_ARCH === "arm64" ||
|
||||
process.env.MSYSTEM_CARCH === "aarch64" ||
|
||||
(process.env.PROCESSOR_IDENTIFIER || "").includes("ARMv8") ||
|
||||
process.arch === "arm64");
|
||||
const isWindowsARM64 = isWindows && process.arch === "arm64";
|
||||
|
||||
if (globalThis.Bun) {
|
||||
await import("./glob-sources.mjs");
|
||||
@@ -57,11 +50,7 @@ async function build(args) {
|
||||
if (process.platform === "win32" && !process.env["VSINSTALLDIR"]) {
|
||||
const shellPath = join(import.meta.dirname, "vs-shell.ps1");
|
||||
const scriptPath = import.meta.filename;
|
||||
// When cross-compiling to ARM64, tell vs-shell.ps1 to set up the x64_arm64 VS environment
|
||||
const toolchainIdx = args.indexOf("--toolchain");
|
||||
const requestedVsArch = toolchainIdx !== -1 && args[toolchainIdx + 1] === "windows-aarch64" ? "arm64" : undefined;
|
||||
const env = requestedVsArch ? { ...process.env, BUN_VS_ARCH: requestedVsArch } : undefined;
|
||||
return spawn("pwsh", ["-NoProfile", "-NoLogo", "-File", shellPath, process.argv0, scriptPath, ...args], { env });
|
||||
return spawn("pwsh", ["-NoProfile", "-NoLogo", "-File", shellPath, process.argv0, scriptPath, ...args]);
|
||||
}
|
||||
|
||||
if (isCI) {
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { existsSync, mkdtempSync, readdirSync } from "node:fs";
|
||||
import { chmodSync, existsSync, mkdtempSync, readdirSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { basename, extname, join, relative, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { inspect, parseArgs } from "node:util";
|
||||
import { azure } from "./azure.mjs";
|
||||
import { docker } from "./docker.mjs";
|
||||
import { tart } from "./tart.mjs";
|
||||
import {
|
||||
@@ -35,7 +37,6 @@ import {
|
||||
spawnSshSafe,
|
||||
spawnSyncSafe,
|
||||
startGroup,
|
||||
tmpdir,
|
||||
waitForPort,
|
||||
which,
|
||||
writeFile,
|
||||
@@ -1047,16 +1048,14 @@ function getRdpFile(hostname, username) {
|
||||
* @property {(options: MachineOptions) => Promise<Machine>} createMachine
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @returns {Cloud}
|
||||
*/
|
||||
function getCloud(name) {
|
||||
switch (name) {
|
||||
case "docker":
|
||||
return docker;
|
||||
case "aws":
|
||||
return aws;
|
||||
case "azure":
|
||||
return azure;
|
||||
case "tart":
|
||||
return tart;
|
||||
}
|
||||
@@ -1127,6 +1126,173 @@ function getCloud(name) {
|
||||
* @property {SshKey[]} sshKeys
|
||||
*/
|
||||
|
||||
async function getAzureToken(tenantId, clientId, clientSecret) {
|
||||
const response = await fetch(`https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||
body: `grant_type=client_credentials&client_id=${clientId}&client_secret=${encodeURIComponent(clientSecret)}&scope=https://management.azure.com/.default`,
|
||||
});
|
||||
if (!response.ok) throw new Error(`Azure auth failed: ${response.status}`);
|
||||
const data = await response.json();
|
||||
return data.access_token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a Windows image using Packer (Azure only).
|
||||
* Packer handles VM creation, bootstrap, sysprep, and gallery capture via WinRM.
|
||||
* This eliminates all the Azure Run Command issues (output truncation, x64 emulation,
|
||||
* PATH not refreshing, stderr false positives, quote escaping).
|
||||
*/
|
||||
async function buildWindowsImageWithPacker({ os, arch, release, command, ci, agentPath, bootstrapPath }) {
|
||||
const { getSecret } = await import("./utils.mjs");
|
||||
|
||||
// Determine Packer template
|
||||
const templateName = arch === "aarch64" ? "windows-arm64" : "windows-x64";
|
||||
const templateDir = resolve(import.meta.dirname, "packer");
|
||||
const templateFile = join(templateDir, `${templateName}.pkr.hcl`);
|
||||
|
||||
if (!existsSync(templateFile)) {
|
||||
throw new Error(`Packer template not found: ${templateFile}`);
|
||||
}
|
||||
|
||||
// Get Azure credentials from Buildkite secrets
|
||||
const clientId = await getSecret("AZURE_CLIENT_ID");
|
||||
const clientSecret = await getSecret("AZURE_CLIENT_SECRET");
|
||||
const subscriptionId = await getSecret("AZURE_SUBSCRIPTION_ID");
|
||||
const tenantId = await getSecret("AZURE_TENANT_ID");
|
||||
const resourceGroup = await getSecret("AZURE_RESOURCE_GROUP");
|
||||
const location = (await getSecret("AZURE_LOCATION")) || "eastus2";
|
||||
const galleryName = (await getSecret("AZURE_GALLERY_NAME")) || "bunCIGallery2";
|
||||
|
||||
// Image naming must match getImageName() in ci.mjs:
|
||||
// [publish images] / normal CI: "windows-x64-2019-v13"
|
||||
// [build images]: "windows-x64-2019-build-37194"
|
||||
const imageKey = arch === "aarch64" ? "windows-aarch64-11" : "windows-x64-2019";
|
||||
const imageDefName =
|
||||
command === "publish-image"
|
||||
? `${imageKey}-v${getBootstrapVersion(os)}`
|
||||
: ci
|
||||
? `${imageKey}-build-${getBuildNumber()}`
|
||||
: `${imageKey}-build-draft-${Date.now()}`;
|
||||
const galleryArch = arch === "aarch64" ? "Arm64" : "x64";
|
||||
console.log(`[packer] Ensuring gallery image definition: ${imageDefName}`);
|
||||
const galleryPath = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.Compute/galleries/${galleryName}/images/${imageDefName}`;
|
||||
const token = await getAzureToken(tenantId, clientId, clientSecret);
|
||||
const defResponse = await fetch(`https://management.azure.com${galleryPath}?api-version=2024-03-03`, {
|
||||
method: "PUT",
|
||||
headers: { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
location: location,
|
||||
properties: {
|
||||
osType: "Windows",
|
||||
osState: "Generalized",
|
||||
hyperVGeneration: "V2",
|
||||
architecture: galleryArch,
|
||||
identifier: { publisher: "bun", offer: `${os}-${arch}-ci`, sku: imageDefName },
|
||||
features: [
|
||||
{ name: "DiskControllerTypes", value: "SCSI, NVMe" },
|
||||
{ name: "SecurityType", value: "TrustedLaunch" },
|
||||
],
|
||||
},
|
||||
}),
|
||||
});
|
||||
if (!defResponse.ok && defResponse.status !== 409) {
|
||||
throw new Error(`Failed to create gallery image definition: ${defResponse.status} ${await defResponse.text()}`);
|
||||
}
|
||||
|
||||
// Install Packer if not available
|
||||
const packerBin = await ensurePacker();
|
||||
|
||||
// Initialize plugins
|
||||
console.log("[packer] Initializing plugins...");
|
||||
await spawnSafe([packerBin, "init", templateDir], { stdio: "inherit" });
|
||||
|
||||
// Build the image
|
||||
console.log(`[packer] Building ${templateName} image: ${imageDefName}`);
|
||||
const packerArgs = [
|
||||
packerBin,
|
||||
"build",
|
||||
"-only",
|
||||
`azure-arm.${templateName}`,
|
||||
"-var",
|
||||
`client_id=${clientId}`,
|
||||
"-var",
|
||||
`client_secret=${clientSecret}`,
|
||||
"-var",
|
||||
`subscription_id=${subscriptionId}`,
|
||||
"-var",
|
||||
`tenant_id=${tenantId}`,
|
||||
"-var",
|
||||
`resource_group=${resourceGroup}-EASTUS2`,
|
||||
"-var",
|
||||
`gallery_resource_group=${resourceGroup}`,
|
||||
"-var",
|
||||
`location=${location}`,
|
||||
"-var",
|
||||
`gallery_name=${galleryName}`,
|
||||
"-var",
|
||||
`image_name=${imageDefName}`,
|
||||
"-var",
|
||||
`bootstrap_script=${bootstrapPath}`,
|
||||
"-var",
|
||||
`agent_script=${agentPath}`,
|
||||
templateDir,
|
||||
];
|
||||
|
||||
await spawnSafe(packerArgs, {
|
||||
stdio: "inherit",
|
||||
env: {
|
||||
...process.env,
|
||||
// Packer also reads these env vars
|
||||
ARM_CLIENT_ID: clientId,
|
||||
ARM_CLIENT_SECRET: clientSecret,
|
||||
ARM_SUBSCRIPTION_ID: subscriptionId,
|
||||
ARM_TENANT_ID: tenantId,
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`[packer] Image built successfully: ${imageDefName}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Download and install Packer if not already available.
|
||||
*/
|
||||
async function ensurePacker() {
|
||||
// Check if packer is already in PATH
|
||||
const packerPath = which("packer");
|
||||
if (packerPath) {
|
||||
console.log("[packer] Found:", packerPath);
|
||||
return packerPath;
|
||||
}
|
||||
|
||||
// Check if we have a local copy
|
||||
const localPacker = join(tmpdir(), "packer");
|
||||
if (existsSync(localPacker)) {
|
||||
return localPacker;
|
||||
}
|
||||
|
||||
// Download Packer
|
||||
const version = "1.15.0";
|
||||
const platform = process.platform === "win32" ? "windows" : process.platform;
|
||||
const packerArch = process.arch === "arm64" ? "arm64" : "amd64";
|
||||
const url = `https://releases.hashicorp.com/packer/${version}/packer_${version}_${platform}_${packerArch}.zip`;
|
||||
|
||||
console.log(`[packer] Downloading Packer ${version}...`);
|
||||
const zipPath = join(tmpdir(), "packer.zip");
|
||||
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) throw new Error(`Failed to download Packer: ${response.status}`);
|
||||
const buffer = Buffer.from(await response.arrayBuffer());
|
||||
writeFileSync(zipPath, buffer);
|
||||
|
||||
// Extract
|
||||
await spawnSafe(["unzip", "-o", zipPath, "-d", tmpdir()], { stdio: "inherit" });
|
||||
chmodSync(localPacker, 0o755);
|
||||
|
||||
console.log(`[packer] Installed Packer ${version}`);
|
||||
return localPacker;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const { positionals } = parseArgs({
|
||||
allowPositionals: true,
|
||||
@@ -1269,6 +1435,13 @@ async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
// Use Packer for Windows Azure image builds — it handles VM creation,
|
||||
// bootstrap, sysprep, and gallery capture via WinRM (no Run Command hacks).
|
||||
if (args["cloud"] === "azure" && os === "windows" && (command === "create-image" || command === "publish-image")) {
|
||||
await buildWindowsImageWithPacker({ os, arch, release, command, ci, agentPath, bootstrapPath });
|
||||
return;
|
||||
}
|
||||
|
||||
/** @type {Machine} */
|
||||
const machine = await startGroup("Creating machine...", async () => {
|
||||
console.log("Creating machine:");
|
||||
@@ -1342,7 +1515,7 @@ async function main() {
|
||||
});
|
||||
}
|
||||
|
||||
await startGroup("Connecting with SSH...", async () => {
|
||||
await startGroup(`Connecting${options.cloud === "azure" ? "" : " with SSH"}...`, async () => {
|
||||
const command = os === "windows" ? ["cmd", "/c", "ver"] : ["uname", "-a"];
|
||||
await machine.spawnSafe(command, { stdio: "inherit" });
|
||||
});
|
||||
@@ -1392,7 +1565,12 @@ async function main() {
|
||||
if (cloud.name === "docker" || features?.includes("docker")) {
|
||||
return;
|
||||
}
|
||||
await machine.spawnSafe(["node", remotePath, "install"], { stdio: "inherit" });
|
||||
// Refresh PATH from registry before running agent.mjs — bootstrap added
|
||||
// buildkite-agent to PATH but Azure Run Command sessions have stale PATH.
|
||||
const cmd = `$env:PATH = [Environment]::GetEnvironmentVariable('PATH', 'Machine') + ';' + [Environment]::GetEnvironmentVariable('PATH', 'User'); C:\\Scoop\\apps\\nodejs\\current\\node.exe ${remotePath} install`;
|
||||
await machine.spawnSafe(["powershell", "-NoProfile", "-Command", cmd], {
|
||||
stdio: "inherit",
|
||||
});
|
||||
});
|
||||
} else {
|
||||
const tmpPath = "/tmp/agent.mjs";
|
||||
|
||||
74
scripts/packer/variables.pkr.hcl
Normal file
74
scripts/packer/variables.pkr.hcl
Normal file
@@ -0,0 +1,74 @@
|
||||
packer {
|
||||
required_plugins {
|
||||
azure = {
|
||||
source = "github.com/hashicorp/azure"
|
||||
version = "= 2.5.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Shared variables for all Windows image builds
|
||||
|
||||
variable "client_id" {
|
||||
type = string
|
||||
default = env("AZURE_CLIENT_ID")
|
||||
}
|
||||
|
||||
variable "client_secret" {
|
||||
type = string
|
||||
sensitive = true
|
||||
default = env("AZURE_CLIENT_SECRET")
|
||||
}
|
||||
|
||||
variable "subscription_id" {
|
||||
type = string
|
||||
default = env("AZURE_SUBSCRIPTION_ID")
|
||||
}
|
||||
|
||||
variable "tenant_id" {
|
||||
type = string
|
||||
default = env("AZURE_TENANT_ID")
|
||||
}
|
||||
|
||||
variable "resource_group" {
|
||||
type = string
|
||||
default = env("AZURE_RESOURCE_GROUP")
|
||||
}
|
||||
|
||||
variable "location" {
|
||||
type = string
|
||||
default = "eastus2"
|
||||
}
|
||||
|
||||
variable "gallery_name" {
|
||||
type = string
|
||||
default = "bunCIGallery2"
|
||||
}
|
||||
|
||||
variable "build_number" {
|
||||
type = string
|
||||
default = "0"
|
||||
}
|
||||
|
||||
variable "image_name" {
|
||||
type = string
|
||||
default = ""
|
||||
description = "Gallery image definition name. If empty, derived from build_number."
|
||||
}
|
||||
|
||||
variable "bootstrap_script" {
|
||||
type = string
|
||||
default = "scripts/bootstrap.ps1"
|
||||
}
|
||||
|
||||
variable "agent_script" {
|
||||
type = string
|
||||
default = ""
|
||||
description = "Path to bundled agent.mjs. If empty, agent install is skipped."
|
||||
}
|
||||
|
||||
variable "gallery_resource_group" {
|
||||
type = string
|
||||
default = "BUN-CI"
|
||||
description = "Resource group containing the Compute Gallery (may differ from build RG)"
|
||||
}
|
||||
143
scripts/packer/windows-arm64.pkr.hcl
Normal file
143
scripts/packer/windows-arm64.pkr.hcl
Normal file
@@ -0,0 +1,143 @@
|
||||
source "azure-arm" "windows-arm64" {
|
||||
// Authentication
|
||||
client_id = var.client_id
|
||||
client_secret = var.client_secret
|
||||
subscription_id = var.subscription_id
|
||||
tenant_id = var.tenant_id
|
||||
|
||||
// Source image — Windows 11 ARM64 (no Windows Server ARM64 exists)
|
||||
os_type = "Windows"
|
||||
image_publisher = "MicrosoftWindowsDesktop"
|
||||
image_offer = "windows11preview-arm64"
|
||||
image_sku = "win11-24h2-pro"
|
||||
image_version = "latest"
|
||||
|
||||
// Build VM — only used during image creation, not for CI runners.
|
||||
// CI runner VM sizes are set in ci.mjs (azureVmSizes).
|
||||
vm_size = "Standard_D4ps_v6"
|
||||
|
||||
// Use existing resource group instead of creating a temp one
|
||||
build_resource_group_name = var.resource_group
|
||||
os_disk_size_gb = 150
|
||||
|
||||
// Security
|
||||
security_type = "TrustedLaunch"
|
||||
secure_boot_enabled = true
|
||||
vtpm_enabled = true
|
||||
|
||||
// Networking — Packer creates a temp VNet + public IP + NSG automatically.
|
||||
|
||||
// WinRM communicator
|
||||
communicator = "winrm"
|
||||
winrm_use_ssl = true
|
||||
winrm_insecure = true
|
||||
winrm_timeout = "15m"
|
||||
winrm_username = "packer"
|
||||
|
||||
// CRITICAL: No managed_image_name — ARM64 doesn't support Managed Images.
|
||||
// Packer publishes directly from the VM to the gallery (PR #242 feature).
|
||||
|
||||
shared_image_gallery_destination {
|
||||
subscription = var.subscription_id
|
||||
resource_group = var.gallery_resource_group
|
||||
gallery_name = var.gallery_name
|
||||
image_name = var.image_name != "" ? var.image_name : "windows-aarch64-11-build-${var.build_number}"
|
||||
image_version = "1.0.0"
|
||||
storage_account_type = "Standard_LRS"
|
||||
target_region { name = var.location }
|
||||
target_region { name = "australiaeast" }
|
||||
target_region { name = "brazilsouth" }
|
||||
target_region { name = "canadacentral" }
|
||||
target_region { name = "canadaeast" }
|
||||
target_region { name = "centralindia" }
|
||||
target_region { name = "centralus" }
|
||||
target_region { name = "francecentral" }
|
||||
target_region { name = "germanywestcentral" }
|
||||
target_region { name = "italynorth" }
|
||||
target_region { name = "japaneast" }
|
||||
target_region { name = "japanwest" }
|
||||
target_region { name = "koreacentral" }
|
||||
target_region { name = "mexicocentral" }
|
||||
target_region { name = "northcentralus" }
|
||||
target_region { name = "northeurope" }
|
||||
target_region { name = "southcentralus" }
|
||||
target_region { name = "southeastasia" }
|
||||
target_region { name = "spaincentral" }
|
||||
target_region { name = "swedencentral" }
|
||||
target_region { name = "switzerlandnorth" }
|
||||
target_region { name = "uaenorth" }
|
||||
target_region { name = "ukwest" }
|
||||
target_region { name = "westeurope" }
|
||||
target_region { name = "westus" }
|
||||
target_region { name = "westus2" }
|
||||
target_region { name = "westus3" }
|
||||
}
|
||||
|
||||
azure_tags = {
|
||||
os = "windows"
|
||||
arch = "aarch64"
|
||||
build = var.build_number
|
||||
}
|
||||
}
|
||||
|
||||
build {
|
||||
sources = ["source.azure-arm.windows-arm64"]
|
||||
|
||||
// Step 1: Run bootstrap — installs all build dependencies
|
||||
provisioner "powershell" {
|
||||
script = var.bootstrap_script
|
||||
valid_exit_codes = [0, 3010]
|
||||
environment_vars = ["CI=true"]
|
||||
}
|
||||
|
||||
// Step 2: Upload agent.mjs
|
||||
provisioner "file" {
|
||||
source = var.agent_script
|
||||
destination = "C:\\buildkite-agent\\agent.mjs"
|
||||
}
|
||||
|
||||
// Step 3: Install agent service via nssm
|
||||
provisioner "powershell" {
|
||||
inline = [
|
||||
"C:\\Scoop\\apps\\nodejs\\current\\node.exe C:\\buildkite-agent\\agent.mjs install"
|
||||
]
|
||||
valid_exit_codes = [0]
|
||||
}
|
||||
|
||||
// Step 4: Reboot to clear pending updates (VS Build Tools, Windows Updates)
|
||||
provisioner "windows-restart" {
|
||||
restart_timeout = "10m"
|
||||
}
|
||||
|
||||
// Step 5: Sysprep — MUST be last provisioner
|
||||
provisioner "powershell" {
|
||||
inline = [
|
||||
"Remove-Item -Recurse -Force C:\\Windows\\Panther -ErrorAction SilentlyContinue",
|
||||
"Write-Output '>>> Clearing pending reboot flags...'",
|
||||
"Remove-Item 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Component Based Servicing\\RebootPending' -Recurse -Force -ErrorAction SilentlyContinue",
|
||||
"Remove-ItemProperty 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\WindowsUpdate\\Auto Update' -Name 'RebootRequired' -Force -ErrorAction SilentlyContinue",
|
||||
"Remove-Item 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\WindowsUpdate\\Auto Update\\RebootRequired' -Recurse -Force -ErrorAction SilentlyContinue",
|
||||
"Remove-ItemProperty 'HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Session Manager' -Name 'PendingFileRenameOperations' -Force -ErrorAction SilentlyContinue",
|
||||
"Write-Output '>>> Waiting for Azure Guest Agent...'",
|
||||
"while ((Get-Service RdAgent).Status -ne 'Running') { Start-Sleep -s 5 }",
|
||||
"while ((Get-Service WindowsAzureGuestAgent).Status -ne 'Running') { Start-Sleep -s 5 }",
|
||||
"Write-Output '>>> Running Sysprep...'",
|
||||
"$global:LASTEXITCODE = 0",
|
||||
"& $env:SystemRoot\\System32\\Sysprep\\Sysprep.exe /oobe /generalize /quiet /quit /mode:vm",
|
||||
"$timeout = 300; $elapsed = 0",
|
||||
"while ($true) {",
|
||||
" $imageState = (Get-ItemProperty HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Setup\\State).ImageState",
|
||||
" Write-Output \"ImageState: $imageState ($${elapsed}s)\"",
|
||||
" if ($imageState -eq 'IMAGE_STATE_GENERALIZE_RESEAL_TO_OOBE') { break }",
|
||||
" if ($elapsed -ge $timeout) {",
|
||||
" Write-Error \"Timed out after $${timeout}s -- stuck at $imageState\"",
|
||||
" Get-Content \"$env:SystemRoot\\System32\\Sysprep\\Panther\\setupact.log\" -Tail 100 -ErrorAction SilentlyContinue",
|
||||
" exit 1",
|
||||
" }",
|
||||
" Start-Sleep -s 10",
|
||||
" $elapsed += 10",
|
||||
"}",
|
||||
"Write-Output '>>> Sysprep complete.'"
|
||||
]
|
||||
}
|
||||
}
|
||||
144
scripts/packer/windows-x64.pkr.hcl
Normal file
144
scripts/packer/windows-x64.pkr.hcl
Normal file
@@ -0,0 +1,144 @@
|
||||
source "azure-arm" "windows-x64" {
|
||||
// Authentication (from env vars or -var flags)
|
||||
client_id = var.client_id
|
||||
client_secret = var.client_secret
|
||||
subscription_id = var.subscription_id
|
||||
tenant_id = var.tenant_id
|
||||
|
||||
// Source image — Windows Server 2019 Gen2
|
||||
os_type = "Windows"
|
||||
image_publisher = "MicrosoftWindowsServer"
|
||||
image_offer = "WindowsServer"
|
||||
image_sku = "2019-datacenter-gensecond"
|
||||
image_version = "latest"
|
||||
|
||||
// Build VM — only used during image creation, not for CI runners.
|
||||
// CI runner VM sizes are set in ci.mjs (azureVmSizes).
|
||||
vm_size = "Standard_D4ds_v6"
|
||||
|
||||
// Use existing resource group instead of creating a temp one
|
||||
build_resource_group_name = var.resource_group
|
||||
os_disk_size_gb = 150
|
||||
|
||||
// Security
|
||||
security_type = "TrustedLaunch"
|
||||
secure_boot_enabled = true
|
||||
vtpm_enabled = true
|
||||
|
||||
// Networking — Packer creates a temp VNet + public IP + NSG automatically.
|
||||
// WinRM needs the public IP to connect from CI runners.
|
||||
|
||||
// WinRM communicator — Packer auto-configures via temp Key Vault
|
||||
communicator = "winrm"
|
||||
winrm_use_ssl = true
|
||||
winrm_insecure = true
|
||||
winrm_timeout = "15m"
|
||||
winrm_username = "packer"
|
||||
|
||||
// Output — Managed Image (x64 supports this)
|
||||
|
||||
// Also publish to Compute Gallery
|
||||
shared_image_gallery_destination {
|
||||
subscription = var.subscription_id
|
||||
resource_group = var.gallery_resource_group
|
||||
gallery_name = var.gallery_name
|
||||
image_name = var.image_name != "" ? var.image_name : "windows-x64-2019-build-${var.build_number}"
|
||||
image_version = "1.0.0"
|
||||
storage_account_type = "Standard_LRS"
|
||||
target_region { name = var.location }
|
||||
target_region { name = "australiaeast" }
|
||||
target_region { name = "brazilsouth" }
|
||||
target_region { name = "canadacentral" }
|
||||
target_region { name = "canadaeast" }
|
||||
target_region { name = "centralindia" }
|
||||
target_region { name = "centralus" }
|
||||
target_region { name = "francecentral" }
|
||||
target_region { name = "germanywestcentral" }
|
||||
target_region { name = "italynorth" }
|
||||
target_region { name = "japaneast" }
|
||||
target_region { name = "japanwest" }
|
||||
target_region { name = "koreacentral" }
|
||||
target_region { name = "mexicocentral" }
|
||||
target_region { name = "northcentralus" }
|
||||
target_region { name = "northeurope" }
|
||||
target_region { name = "southcentralus" }
|
||||
target_region { name = "southeastasia" }
|
||||
target_region { name = "spaincentral" }
|
||||
target_region { name = "swedencentral" }
|
||||
target_region { name = "switzerlandnorth" }
|
||||
target_region { name = "uaenorth" }
|
||||
target_region { name = "ukwest" }
|
||||
target_region { name = "westeurope" }
|
||||
target_region { name = "westus" }
|
||||
target_region { name = "westus2" }
|
||||
target_region { name = "westus3" }
|
||||
}
|
||||
|
||||
azure_tags = {
|
||||
os = "windows"
|
||||
arch = "x64"
|
||||
build = var.build_number
|
||||
}
|
||||
}
|
||||
|
||||
build {
|
||||
sources = ["source.azure-arm.windows-x64"]
|
||||
|
||||
// Step 1: Run bootstrap — installs all build dependencies
|
||||
provisioner "powershell" {
|
||||
script = var.bootstrap_script
|
||||
valid_exit_codes = [0, 3010]
|
||||
environment_vars = ["CI=true"]
|
||||
}
|
||||
|
||||
// Step 2: Upload agent.mjs
|
||||
provisioner "file" {
|
||||
source = var.agent_script
|
||||
destination = "C:\\buildkite-agent\\agent.mjs"
|
||||
}
|
||||
|
||||
// Step 3: Install agent service via nssm
|
||||
provisioner "powershell" {
|
||||
inline = [
|
||||
"C:\\Scoop\\apps\\nodejs\\current\\node.exe C:\\buildkite-agent\\agent.mjs install"
|
||||
]
|
||||
valid_exit_codes = [0]
|
||||
}
|
||||
|
||||
// Step 4: Reboot to clear pending updates (VS Build Tools, Windows Updates)
|
||||
provisioner "windows-restart" {
|
||||
restart_timeout = "10m"
|
||||
}
|
||||
|
||||
// Step 5: Sysprep — MUST be last provisioner
|
||||
provisioner "powershell" {
|
||||
inline = [
|
||||
"Remove-Item -Recurse -Force C:\\Windows\\Panther -ErrorAction SilentlyContinue",
|
||||
"Write-Output '>>> Clearing pending reboot flags...'",
|
||||
"Remove-Item 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Component Based Servicing\\RebootPending' -Recurse -Force -ErrorAction SilentlyContinue",
|
||||
"Remove-ItemProperty 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\WindowsUpdate\\Auto Update' -Name 'RebootRequired' -Force -ErrorAction SilentlyContinue",
|
||||
"Remove-Item 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\WindowsUpdate\\Auto Update\\RebootRequired' -Recurse -Force -ErrorAction SilentlyContinue",
|
||||
"Remove-ItemProperty 'HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Session Manager' -Name 'PendingFileRenameOperations' -Force -ErrorAction SilentlyContinue",
|
||||
"Write-Output '>>> Waiting for Azure Guest Agent...'",
|
||||
"while ((Get-Service RdAgent).Status -ne 'Running') { Start-Sleep -s 5 }",
|
||||
"while ((Get-Service WindowsAzureGuestAgent).Status -ne 'Running') { Start-Sleep -s 5 }",
|
||||
"Write-Output '>>> Running Sysprep...'",
|
||||
"$global:LASTEXITCODE = 0",
|
||||
"& $env:SystemRoot\\System32\\Sysprep\\Sysprep.exe /oobe /generalize /quiet /quit /mode:vm",
|
||||
"$timeout = 300; $elapsed = 0",
|
||||
"while ($true) {",
|
||||
" $imageState = (Get-ItemProperty HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Setup\\State).ImageState",
|
||||
" Write-Output \"ImageState: $imageState ($${elapsed}s)\"",
|
||||
" if ($imageState -eq 'IMAGE_STATE_GENERALIZE_RESEAL_TO_OOBE') { break }",
|
||||
" if ($elapsed -ge $timeout) {",
|
||||
" Write-Error \"Timed out after $${timeout}s -- stuck at $imageState\"",
|
||||
" Get-Content \"$env:SystemRoot\\System32\\Sysprep\\Panther\\setupact.log\" -Tail 100 -ErrorAction SilentlyContinue",
|
||||
" exit 1",
|
||||
" }",
|
||||
" Start-Sleep -s 10",
|
||||
" $elapsed += 10",
|
||||
"}",
|
||||
"Write-Output '>>> Sysprep complete.'"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1837,6 +1837,13 @@ export function getTailscale() {
|
||||
}
|
||||
}
|
||||
|
||||
if (isWindows) {
|
||||
const tailscaleExe = "C:\\Program Files\\Tailscale\\tailscale.exe";
|
||||
if (existsSync(tailscaleExe)) {
|
||||
return tailscaleExe;
|
||||
}
|
||||
}
|
||||
|
||||
return "tailscale";
|
||||
}
|
||||
|
||||
@@ -2043,7 +2050,7 @@ export function getShell() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {"aws" | "google"} Cloud
|
||||
* @typedef {"aws" | "google" | "azure"} Cloud
|
||||
*/
|
||||
|
||||
/** @type {Cloud | undefined} */
|
||||
@@ -2136,6 +2143,37 @@ export async function isGoogleCloud() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise<boolean | undefined>}
|
||||
*/
|
||||
export async function isAzure() {
|
||||
if (typeof detectedCloud === "string") {
|
||||
return detectedCloud === "azure";
|
||||
}
|
||||
|
||||
async function detectAzure() {
|
||||
// Azure IMDS (Instance Metadata Service) — the official way to detect Azure VMs.
|
||||
// https://learn.microsoft.com/en-us/azure/virtual-machines/instance-metadata-service
|
||||
const { error, body } = await curl("http://169.254.169.254/metadata/instance?api-version=2021-02-01", {
|
||||
headers: { "Metadata": "true" },
|
||||
retries: 1,
|
||||
});
|
||||
if (!error && body) {
|
||||
try {
|
||||
const metadata = JSON.parse(body);
|
||||
if (metadata?.compute?.azEnvironment) {
|
||||
return true;
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
if (await detectAzure()) {
|
||||
detectedCloud = "azure";
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise<Cloud | undefined>}
|
||||
*/
|
||||
@@ -2151,6 +2189,10 @@ export async function getCloud() {
|
||||
if (await isGoogleCloud()) {
|
||||
return "google";
|
||||
}
|
||||
|
||||
if (await isAzure()) {
|
||||
return "azure";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2175,6 +2217,10 @@ export async function getCloudMetadata(name, cloud) {
|
||||
} else if (cloud === "google") {
|
||||
url = new URL(name, "http://metadata.google.internal/computeMetadata/v1/instance/");
|
||||
headers = { "Metadata-Flavor": "Google" };
|
||||
} else if (cloud === "azure") {
|
||||
// Azure IMDS uses a single JSON endpoint; individual fields are extracted by the caller.
|
||||
url = new URL("http://169.254.169.254/metadata/instance?api-version=2021-02-01");
|
||||
headers = { "Metadata": "true" };
|
||||
} else {
|
||||
throw new Error(`Unsupported cloud: ${inspect(cloud)}`);
|
||||
}
|
||||
@@ -2193,7 +2239,25 @@ export async function getCloudMetadata(name, cloud) {
|
||||
* @param {Cloud} [cloud]
|
||||
* @returns {Promise<string | undefined>}
|
||||
*/
|
||||
export function getCloudMetadataTag(tag, cloud) {
|
||||
export async function getCloudMetadataTag(tag, cloud) {
|
||||
cloud ??= await getCloud();
|
||||
|
||||
if (cloud === "azure") {
|
||||
// Azure IMDS returns all tags in a single JSON response.
|
||||
// Tags are in compute.tagsList as [{name, value}, ...].
|
||||
const body = await getCloudMetadata("", cloud);
|
||||
if (!body) return;
|
||||
try {
|
||||
const metadata = JSON.parse(body);
|
||||
const tags = metadata?.compute?.tagsList;
|
||||
if (Array.isArray(tags)) {
|
||||
const entry = tags.find(t => t.name === tag);
|
||||
return entry?.value;
|
||||
}
|
||||
} catch {}
|
||||
return;
|
||||
}
|
||||
|
||||
const metadata = {
|
||||
"aws": `tags/instance/${tag}`,
|
||||
"google": `labels/${tag.replace(":", "-")}`,
|
||||
|
||||
239
scripts/verify-baseline.ts
Normal file
239
scripts/verify-baseline.ts
Normal file
@@ -0,0 +1,239 @@
|
||||
// Verify that a Bun binary doesn't use CPU instructions beyond its baseline target.
|
||||
//
|
||||
// Detects the platform and chooses the appropriate emulator:
|
||||
// Linux x64: QEMU with Nehalem CPU (no AVX)
|
||||
// Linux arm64: QEMU with Cortex-A53 (no LSE/SVE)
|
||||
// Windows x64: Intel SDE with -nhm (no AVX)
|
||||
//
|
||||
// Usage:
|
||||
// bun scripts/verify-baseline.ts --binary ./bun --emulator /usr/bin/qemu-x86_64
|
||||
// bun scripts/verify-baseline.ts --binary ./bun.exe --emulator ./sde.exe
|
||||
|
||||
import { readdirSync } from "node:fs";
|
||||
import { basename, dirname, join, resolve } from "node:path";
|
||||
|
||||
const { parseArgs } = require("node:util");
|
||||
|
||||
const { values } = parseArgs({
|
||||
args: process.argv.slice(2),
|
||||
options: {
|
||||
binary: { type: "string" },
|
||||
emulator: { type: "string" },
|
||||
"jit-stress": { type: "boolean", default: false },
|
||||
},
|
||||
strict: true,
|
||||
});
|
||||
|
||||
const binary = resolve(values.binary!);
|
||||
|
||||
function resolveEmulator(name: string): string {
|
||||
const found = Bun.which(name);
|
||||
if (found) return found;
|
||||
// Try without -static suffix (e.g. qemu-aarch64 instead of qemu-aarch64-static)
|
||||
if (name.endsWith("-static")) {
|
||||
const fallback = Bun.which(name.slice(0, -"-static".length));
|
||||
if (fallback) return fallback;
|
||||
}
|
||||
// Last resort: resolve as a relative path (e.g. sde-external/sde.exe)
|
||||
return resolve(name);
|
||||
}
|
||||
|
||||
const emulatorPath = resolveEmulator(values.emulator!);
|
||||
|
||||
const scriptDir = dirname(import.meta.path);
|
||||
const repoRoot = resolve(scriptDir, "..");
|
||||
const fixturesDir = join(repoRoot, "test", "js", "bun", "jsc-stress", "fixtures");
|
||||
const wasmFixturesDir = join(fixturesDir, "wasm");
|
||||
const preloadPath = join(repoRoot, "test", "js", "bun", "jsc-stress", "preload.js");
|
||||
|
||||
// Platform detection
|
||||
const isWindows = process.platform === "win32";
|
||||
const isAarch64 = process.arch === "arm64";
|
||||
|
||||
// SDE outputs this when a chip-check violation occurs
|
||||
const SDE_VIOLATION_PATTERN = /SDE-ERROR:.*not valid for specified chip/i;
|
||||
|
||||
// Configure emulator based on platform
|
||||
const config = isWindows
|
||||
? {
|
||||
runnerCmd: [emulatorPath, "-nhm", "--"],
|
||||
cpuDesc: "Nehalem (SSE4.2, no AVX/AVX2/AVX512)",
|
||||
// SDE must run from its own directory for Pin DLL resolution
|
||||
cwd: dirname(emulatorPath),
|
||||
}
|
||||
: isAarch64
|
||||
? {
|
||||
runnerCmd: [emulatorPath, "-cpu", "cortex-a53"],
|
||||
cpuDesc: "Cortex-A53 (ARMv8.0-A+CRC, no LSE/SVE)",
|
||||
cwd: undefined,
|
||||
}
|
||||
: {
|
||||
runnerCmd: [emulatorPath, "-cpu", "Nehalem"],
|
||||
cpuDesc: "Nehalem (SSE4.2, no AVX/AVX2/AVX512)",
|
||||
cwd: undefined,
|
||||
};
|
||||
|
||||
function isInstructionViolation(exitCode: number, output: string): boolean {
|
||||
if (isWindows) return SDE_VIOLATION_PATTERN.test(output);
|
||||
return exitCode === 132; // SIGILL = 128 + signal 4
|
||||
}
|
||||
|
||||
console.log(`--- Verifying ${basename(binary)} on ${config.cpuDesc}`);
|
||||
console.log(` Binary: ${binary}`);
|
||||
console.log(` Emulator: ${config.runnerCmd.join(" ")}`);
|
||||
console.log();
|
||||
|
||||
let instructionFailures = 0;
|
||||
let otherFailures = 0;
|
||||
let passed = 0;
|
||||
const failedTests: string[] = [];
|
||||
|
||||
interface RunTestOptions {
|
||||
cwd?: string;
|
||||
/** Tee output live to the console while still capturing it for analysis */
|
||||
live?: boolean;
|
||||
}
|
||||
|
||||
/** Read a stream, write each chunk to a writable, and return the full text. */
|
||||
async function teeStream(stream: ReadableStream<Uint8Array>, output: NodeJS.WriteStream): Promise<string> {
|
||||
const chunks: Uint8Array[] = [];
|
||||
for await (const chunk of stream) {
|
||||
chunks.push(chunk);
|
||||
output.write(chunk);
|
||||
}
|
||||
return Buffer.concat(chunks).toString();
|
||||
}
|
||||
|
||||
async function runTest(label: string, binaryArgs: string[], options?: RunTestOptions): Promise<boolean> {
|
||||
console.log(`+++ ${label}`);
|
||||
|
||||
const start = performance.now();
|
||||
const live = options?.live ?? false;
|
||||
const proc = Bun.spawn([...config.runnerCmd, binary, ...binaryArgs], {
|
||||
// config.cwd takes priority — SDE on Windows must run from its own directory for Pin DLL resolution
|
||||
cwd: config.cwd ?? options?.cwd,
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
let stdout: string;
|
||||
let stderr: string;
|
||||
if (live) {
|
||||
[stdout, stderr] = await Promise.all([
|
||||
teeStream(proc.stdout as ReadableStream<Uint8Array>, process.stdout),
|
||||
teeStream(proc.stderr as ReadableStream<Uint8Array>, process.stderr),
|
||||
proc.exited,
|
||||
]);
|
||||
} else {
|
||||
[stdout, stderr] = await Promise.all([
|
||||
new Response(proc.stdout).text(),
|
||||
new Response(proc.stderr).text(),
|
||||
proc.exited,
|
||||
]);
|
||||
}
|
||||
|
||||
const exitCode = proc.exitCode!;
|
||||
const elapsed = ((performance.now() - start) / 1000).toFixed(1);
|
||||
const output = stdout + "\n" + stderr;
|
||||
|
||||
if (exitCode === 0) {
|
||||
if (!live && stdout.trim()) console.log(stdout.trim());
|
||||
console.log(` PASS (${elapsed}s)`);
|
||||
passed++;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isInstructionViolation(exitCode, output)) {
|
||||
if (!live && output.trim()) console.log(output.trim());
|
||||
console.log();
|
||||
console.log(` FAIL: CPU instruction violation detected (${elapsed}s)`);
|
||||
if (isAarch64) {
|
||||
console.log(" The aarch64 build targets Cortex-A53 (ARMv8.0-A+CRC).");
|
||||
console.log(" LSE atomics, SVE, and dotprod instructions are not allowed.");
|
||||
} else {
|
||||
console.log(" The baseline x64 build targets Nehalem (SSE4.2).");
|
||||
console.log(" AVX, AVX2, and AVX512 instructions are not allowed.");
|
||||
}
|
||||
instructionFailures++;
|
||||
failedTests.push(label);
|
||||
} else {
|
||||
if (!live && output.trim()) console.log(output.trim());
|
||||
console.log(` WARN: exit code ${exitCode} (${elapsed}s, not a CPU instruction issue)`);
|
||||
otherFailures++;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Phase 1: SIMD code path verification (always runs)
|
||||
const simdTestPath = join(repoRoot, "test", "js", "bun", "jsc-stress", "fixtures", "simd-baseline.test.ts");
|
||||
await runTest("SIMD baseline tests", ["test", simdTestPath], { live: true });
|
||||
|
||||
// Phase 2: JIT stress fixtures (only with --jit-stress, e.g. on WebKit changes)
|
||||
if (values["jit-stress"]) {
|
||||
const jsFixtures = readdirSync(fixturesDir)
|
||||
.filter(f => f.endsWith(".js"))
|
||||
.sort();
|
||||
console.log();
|
||||
console.log(`--- JS fixtures (DFG/FTL) — ${jsFixtures.length} tests`);
|
||||
for (let i = 0; i < jsFixtures.length; i++) {
|
||||
const fixture = jsFixtures[i];
|
||||
await runTest(`[${i + 1}/${jsFixtures.length}] ${fixture}`, ["--preload", preloadPath, join(fixturesDir, fixture)]);
|
||||
}
|
||||
|
||||
const wasmFixtures = readdirSync(wasmFixturesDir)
|
||||
.filter(f => f.endsWith(".js"))
|
||||
.sort();
|
||||
console.log();
|
||||
console.log(`--- Wasm fixtures (BBQ/OMG) — ${wasmFixtures.length} tests`);
|
||||
for (let i = 0; i < wasmFixtures.length; i++) {
|
||||
const fixture = wasmFixtures[i];
|
||||
await runTest(
|
||||
`[${i + 1}/${wasmFixtures.length}] ${fixture}`,
|
||||
["--preload", preloadPath, join(wasmFixturesDir, fixture)],
|
||||
{ cwd: wasmFixturesDir },
|
||||
);
|
||||
}
|
||||
} else {
|
||||
console.log();
|
||||
console.log("--- Skipping JIT stress fixtures (pass --jit-stress to enable)");
|
||||
}
|
||||
|
||||
// Summary
|
||||
console.log();
|
||||
console.log("--- Summary");
|
||||
console.log(` Passed: ${passed}`);
|
||||
console.log(` Instruction failures: ${instructionFailures}`);
|
||||
console.log(` Other failures: ${otherFailures} (warnings, not CPU instruction issues)`);
|
||||
console.log();
|
||||
|
||||
if (instructionFailures > 0) {
|
||||
console.error(" FAILED: Code uses unsupported CPU instructions.");
|
||||
|
||||
// Report to Buildkite annotations tab
|
||||
const platform = isWindows
|
||||
? isAarch64
|
||||
? "Windows aarch64"
|
||||
: "Windows x64"
|
||||
: isAarch64
|
||||
? "Linux aarch64"
|
||||
: "Linux x64";
|
||||
const annotation = [
|
||||
`<details>`,
|
||||
`<summary>CPU instruction violation on ${platform} — ${instructionFailures} failed</summary>`,
|
||||
`<p>The baseline build uses instructions not available on <code>${config.cpuDesc}</code>.</p>`,
|
||||
`<ul>${failedTests.map(t => `<li><code>${t}</code></li>`).join("")}</ul>`,
|
||||
`</details>`,
|
||||
].join("\n");
|
||||
|
||||
Bun.spawnSync(["buildkite-agent", "annotate", "--append", "--style", "error", "--context", "verify-baseline"], {
|
||||
stdin: new Blob([annotation]),
|
||||
});
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (otherFailures > 0) {
|
||||
console.log(" Some tests failed for reasons unrelated to CPU instructions.");
|
||||
}
|
||||
|
||||
console.log(` All baseline verification passed on ${config.cpuDesc}.`);
|
||||
@@ -5,22 +5,7 @@ $ErrorActionPreference = "Stop"
|
||||
|
||||
# Detect system architecture
|
||||
$script:IsARM64 = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture -eq [System.Runtime.InteropServices.Architecture]::Arm64
|
||||
|
||||
# Allow overriding the target arch (useful for cross-compiling on x64 -> ARM64)
|
||||
$script:VsArch = $null
|
||||
if ($env:BUN_VS_ARCH) {
|
||||
switch ($env:BUN_VS_ARCH.ToLowerInvariant()) {
|
||||
"arm64" { $script:VsArch = "arm64" }
|
||||
"aarch64" { $script:VsArch = "arm64" }
|
||||
"amd64" { $script:VsArch = "amd64" }
|
||||
"x64" { $script:VsArch = "amd64" }
|
||||
default { throw "Invalid BUN_VS_ARCH: $env:BUN_VS_ARCH (expected arm64|amd64)" }
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $script:VsArch) {
|
||||
$script:VsArch = if ($script:IsARM64) { "arm64" } else { "amd64" }
|
||||
}
|
||||
$script:VsArch = if ($script:IsARM64) { "arm64" } else { "amd64" }
|
||||
|
||||
if($env:VSINSTALLDIR -eq $null) {
|
||||
Write-Host "Loading Visual Studio environment, this may take a second..."
|
||||
@@ -51,10 +36,15 @@ if($env:VSINSTALLDIR -eq $null) {
|
||||
Push-Location $vsDir
|
||||
try {
|
||||
$vsShell = (Join-Path -Path $vsDir -ChildPath "Common7\Tools\Launch-VsDevShell.ps1")
|
||||
# Visual Studio's Launch-VsDevShell.ps1 only supports x86/amd64 for HostArch
|
||||
# For ARM64 builds, use amd64 as HostArch since it can cross-compile to ARM64
|
||||
# -HostArch only accepts "x86" or "amd64" — even on native ARM64, use "amd64"
|
||||
$hostArch = if ($script:VsArch -eq "arm64") { "amd64" } else { $script:VsArch }
|
||||
. $vsShell -Arch $script:VsArch -HostArch $hostArch
|
||||
|
||||
# VS dev shell with -HostArch amd64 sets PROCESSOR_ARCHITECTURE=AMD64,
|
||||
# which causes CMake to misdetect the system as x64. Restore it on ARM64.
|
||||
if ($script:IsARM64) {
|
||||
$env:PROCESSOR_ARCHITECTURE = "ARM64"
|
||||
}
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
|
||||
292
src/CLAUDE.md
292
src/CLAUDE.md
@@ -10,3 +10,295 @@ Conventions:
|
||||
- Prefer `@import` at the **bottom** of the file, but the auto formatter will move them so you don't need to worry about it.
|
||||
- **Never** use `@import()` inline inside of functions. **Always** put them at the bottom of the file or containing struct. Imports in Zig are free of side-effects, so there's no such thing as a "dynamic" import.
|
||||
- You must be patient with the build.
|
||||
|
||||
## Prefer Bun APIs over `std`
|
||||
|
||||
**Always use `bun.*` APIs instead of `std.*`.** The `bun` namespace (`@import("bun")`) provides cross-platform wrappers that preserve OS error info and never use `unreachable`. Using `std.fs`, `std.posix`, or `std.os` directly is wrong in this codebase.
|
||||
|
||||
| Instead of | Use |
|
||||
| ------------------------------------------------------------ | ------------------------------------ |
|
||||
| `std.fs.File` | `bun.sys.File` |
|
||||
| `std.fs.cwd()` | `bun.FD.cwd()` |
|
||||
| `std.posix.open/read/write/stat/mkdir/unlink/rename/symlink` | `bun.sys.*` equivalents |
|
||||
| `std.fs.path.join/dirname/basename` | `bun.path.join/dirname/basename` |
|
||||
| `std.mem.eql/indexOf/startsWith` (for strings) | `bun.strings.eql/indexOf/startsWith` |
|
||||
| `std.posix.O` / `std.posix.mode_t` / `std.posix.fd_t` | `bun.O` / `bun.Mode` / `bun.FD` |
|
||||
| `std.process.Child` | `bun.spawnSync` |
|
||||
| `catch bun.outOfMemory()` | `bun.handleOom(...)` |
|
||||
|
||||
## `bun.sys` — System Calls (`src/sys.zig`)
|
||||
|
||||
All return `Maybe(T)` — a tagged union of `.result: T` or `.err: bun.sys.Error`:
|
||||
|
||||
```zig
|
||||
const fd = switch (bun.sys.open(path, bun.O.RDONLY, 0)) {
|
||||
.result => |fd| fd,
|
||||
.err => |err| return .{ .err = err },
|
||||
};
|
||||
// Or: const fd = try bun.sys.open(path, bun.O.RDONLY, 0).unwrap();
|
||||
```
|
||||
|
||||
Key functions (all take `bun.FileDescriptor`, not `std.posix.fd_t`):
|
||||
|
||||
- `open`, `openat`, `openA` (non-sentinel) → `Maybe(bun.FileDescriptor)`
|
||||
- `read`, `readAll`, `pread` → `Maybe(usize)`
|
||||
- `write`, `pwrite`, `writev` → `Maybe(usize)`
|
||||
- `stat`, `fstat`, `lstat` → `Maybe(bun.Stat)`
|
||||
- `mkdir`, `unlink`, `rename`, `symlink`, `chmod`, `fchmod`, `fchown` → `Maybe(void)`
|
||||
- `readlink`, `getFdPath`, `getcwd` → `Maybe` of path slice
|
||||
- `getFileSize`, `dup`, `sendfile`, `mmap`
|
||||
|
||||
Use `bun.O.RDONLY`, `bun.O.WRONLY | bun.O.CREAT | bun.O.TRUNC`, etc. for open flags.
|
||||
|
||||
### `bun.sys.File` (`src/sys/File.zig`)
|
||||
|
||||
Higher-level file handle wrapping `bun.FileDescriptor`:
|
||||
|
||||
```zig
|
||||
// One-shot read: open + read + close
|
||||
const bytes = switch (bun.sys.File.readFrom(bun.FD.cwd(), path, allocator)) {
|
||||
.result => |b| b,
|
||||
.err => |err| return .{ .err = err },
|
||||
};
|
||||
|
||||
// One-shot write: open + write + close
|
||||
switch (bun.sys.File.writeFile(bun.FD.cwd(), path, data)) {
|
||||
.result => {},
|
||||
.err => |err| return .{ .err = err },
|
||||
}
|
||||
```
|
||||
|
||||
Key methods:
|
||||
|
||||
- `File.open/openat/makeOpen` → `Maybe(File)` (`makeOpen` creates parent dirs)
|
||||
- `file.read/readAll/write/writeAll` — single or looped I/O
|
||||
- `file.readToEnd(allocator)` — read entire file into allocated buffer
|
||||
- `File.readFrom(dir_fd, path, allocator)` — open + read + close
|
||||
- `File.writeFile(dir_fd, path, data)` — open + write + close
|
||||
- `file.stat()`, `file.close()`, `file.writer()`, `file.reader()`
|
||||
|
||||
### `bun.FD` (`src/fd.zig`)
|
||||
|
||||
Cross-platform file descriptor. Use `bun.FD.cwd()` for cwd, `bun.invalid_fd` for sentinel, `fd.close()` to close.
|
||||
|
||||
### `bun.sys.Error` (`src/sys/Error.zig`)
|
||||
|
||||
Preserves errno, syscall tag, and file path. Convert to JS: `err.toSystemError().toErrorInstance(globalObject)`.
|
||||
|
||||
## `bun.strings` — String Utilities (`src/string/immutable.zig`)
|
||||
|
||||
SIMD-accelerated string operations. Use instead of `std.mem` for strings.
|
||||
|
||||
```zig
|
||||
// Searching
|
||||
strings.indexOf(haystack, needle) // ?usize
|
||||
strings.contains(haystack, needle) // bool
|
||||
strings.containsChar(haystack, char) // bool
|
||||
strings.indexOfChar(haystack, char) // ?u32
|
||||
strings.indexOfAny(str, comptime chars) // ?OptionalUsize (SIMD-accelerated)
|
||||
|
||||
// Comparison
|
||||
strings.eql(a, b) // bool
|
||||
strings.eqlComptime(str, comptime literal) // bool — optimized
|
||||
strings.eqlCaseInsensitiveASCII(a, b, comptime true) // 3rd arg = check_len
|
||||
|
||||
// Prefix/Suffix
|
||||
strings.startsWith(str, prefix) // bool
|
||||
strings.endsWith(str, suffix) // bool
|
||||
strings.hasPrefixComptime(str, comptime prefix) // bool — optimized
|
||||
strings.hasSuffixComptime(str, comptime suffix) // bool — optimized
|
||||
|
||||
// Trimming
|
||||
strings.trim(str, comptime chars) // strip from both ends
|
||||
strings.trimSpaces(str) // strip whitespace
|
||||
|
||||
// Encoding conversions
|
||||
strings.toUTF8Alloc(allocator, utf16) // ![]u8
|
||||
strings.toUTF16Alloc(allocator, utf8) // !?[]u16
|
||||
strings.toUTF8FromLatin1(allocator, latin1) // !?Managed(u8)
|
||||
strings.firstNonASCII(slice) // ?u32
|
||||
```
|
||||
|
||||
Bun handles UTF-8, Latin-1, and UTF-16/WTF-16 because JSC uses Latin-1 and UTF-16 internally. Latin-1 is NOT UTF-8 — bytes 128-255 are single chars in Latin-1 but invalid UTF-8.
|
||||
|
||||
### `bun.String` (`src/string.zig`)
|
||||
|
||||
Bridges Zig and JavaScriptCore. Prefer over `ZigString` in new code.
|
||||
|
||||
```zig
|
||||
const s = bun.String.cloneUTF8(utf8_slice); // copies into WTFStringImpl
|
||||
const s = bun.String.borrowUTF8(utf8_slice); // no copy, caller keeps alive
|
||||
const utf8 = s.toUTF8(allocator); // ZigString.Slice
|
||||
defer utf8.deinit();
|
||||
const js_value = s.toJS(globalObject);
|
||||
|
||||
// Create a JS string value directly from UTF-8 bytes:
|
||||
const js_str = try bun.String.createUTF8ForJS(globalObject, utf8_slice);
|
||||
```
|
||||
|
||||
## `bun.path` — Path Manipulation (`src/resolver/resolve_path.zig`)
|
||||
|
||||
Use instead of `std.fs.path`. Platform param: `.auto` (current platform), `.posix`, `.windows`, `.loose` (both separators).
|
||||
|
||||
```zig
|
||||
// Join paths — uses threadlocal buffer, result must be copied if it needs to persist
|
||||
bun.path.join(&.{ dir, filename }, .auto)
|
||||
bun.path.joinZ(&.{ dir, filename }, .auto) // null-terminated
|
||||
|
||||
// Join into a caller-provided buffer
|
||||
bun.path.joinStringBuf(&buf, &.{ a, b }, .auto)
|
||||
bun.path.joinStringBufZ(&buf, &.{ a, b }, .auto) // null-terminated
|
||||
|
||||
// Resolve against an absolute base (like Node.js path.resolve)
|
||||
bun.path.joinAbsString(cwd, &.{ relative_path }, .auto)
|
||||
bun.path.joinAbsStringBufZ(cwd, &buf, &.{ relative_path }, .auto)
|
||||
|
||||
// Path components
|
||||
bun.path.dirname(path, .auto)
|
||||
bun.path.basename(path)
|
||||
|
||||
// Relative path between two absolute paths
|
||||
bun.path.relative(from, to)
|
||||
bun.path.relativeAlloc(allocator, from, to)
|
||||
|
||||
// Normalize (resolve `.` and `..`)
|
||||
bun.path.normalizeBuf(path, &buf, .auto)
|
||||
|
||||
// Null-terminate a path into a buffer
|
||||
bun.path.z(path, &buf) // returns [:0]const u8
|
||||
```
|
||||
|
||||
Use `bun.PathBuffer` for path buffers: `var buf: bun.PathBuffer = undefined;`
|
||||
|
||||
For pooled path buffers (avoids 64KB stack allocations on Windows):
|
||||
|
||||
```zig
|
||||
const buf = bun.path_buffer_pool.get();
|
||||
defer bun.path_buffer_pool.put(buf);
|
||||
```
|
||||
|
||||
## URL Parsing
|
||||
|
||||
Prefer `bun.jsc.URL` (WHATWG-compliant, backed by WebKit C++) over `bun.URL.parse` (internal, doesn't properly handle errors or invalid URLs).
|
||||
|
||||
```zig
|
||||
// Parse a URL string (returns null if invalid)
|
||||
const url = bun.jsc.URL.fromUTF8(href_string) orelse return error.InvalidURL;
|
||||
defer url.deinit();
|
||||
|
||||
url.protocol() // bun.String
|
||||
url.pathname() // bun.String
|
||||
url.search() // bun.String
|
||||
url.hash() // bun.String (includes leading '#')
|
||||
url.port() // u32 (maxInt(u32) if not set, otherwise u16 range)
|
||||
|
||||
// NOTE: host/hostname are SWAPPED vs JS:
|
||||
url.host() // hostname WITHOUT port (opposite of JS!)
|
||||
url.hostname() // hostname WITH port (opposite of JS!)
|
||||
|
||||
// Normalize a URL string (percent-encode, punycode, etc.)
|
||||
const normalized = bun.jsc.URL.hrefFromString(bun.String.borrowUTF8(input));
|
||||
if (normalized.tag == .Dead) return error.InvalidURL;
|
||||
defer normalized.deref();
|
||||
|
||||
// Join base + relative URLs
|
||||
const joined = bun.jsc.URL.join(base_str, relative_str);
|
||||
defer joined.deref();
|
||||
|
||||
// Convert between file paths and file:// URLs
|
||||
const file_url = bun.jsc.URL.fileURLFromString(path_str); // path → file://
|
||||
const file_path = bun.jsc.URL.pathFromFileURL(url_str); // file:// → path
|
||||
```
|
||||
|
||||
## MIME Types (`src/http/MimeType.zig`)
|
||||
|
||||
```zig
|
||||
const MimeType = bun.http.MimeType;
|
||||
|
||||
// Look up by file extension (without leading dot)
|
||||
const mime = MimeType.byExtension("html"); // MimeType{ .value = "text/html", .category = .html }
|
||||
const mime = MimeType.byExtensionNoDefault("xyz"); // ?MimeType (null if unknown)
|
||||
|
||||
// Category checks
|
||||
mime.category // .javascript, .css, .html, .json, .image, .text, .wasm, .font, .video, .audio, ...
|
||||
mime.category.isCode()
|
||||
```
|
||||
|
||||
Common constants: `MimeType.javascript`, `MimeType.json`, `MimeType.html`, `MimeType.css`, `MimeType.text`, `MimeType.wasm`, `MimeType.ico`, `MimeType.other`.
|
||||
|
||||
## Memory & Allocators
|
||||
|
||||
**Use `bun.default_allocator` for almost everything.** It's backed by mimalloc.
|
||||
|
||||
`bun.handleOom(expr)` converts `error.OutOfMemory` into a crash without swallowing other errors:
|
||||
|
||||
```zig
|
||||
const buf = bun.handleOom(allocator.alloc(u8, size)); // correct
|
||||
// NOT: allocator.alloc(u8, size) catch bun.outOfMemory() — could swallow non-OOM errors
|
||||
```
|
||||
|
||||
## Environment Variables (`src/env_var.zig`)
|
||||
|
||||
Type-safe, cached environment variable accessors via `bun.env_var`:
|
||||
|
||||
```zig
|
||||
bun.env_var.HOME.get() // ?[]const u8
|
||||
bun.env_var.CI.get() // ?bool
|
||||
bun.env_var.BUN_CONFIG_DNS_TIME_TO_LIVE_SECONDS.get() // u64 (has default: 30)
|
||||
```
|
||||
|
||||
## Logging (`src/output.zig`)
|
||||
|
||||
```zig
|
||||
const log = bun.Output.scoped(.MY_FEATURE, .visible); // .hidden = opt-in via BUN_DEBUG_MY_FEATURE=1
|
||||
log("processing {d} items", .{count});
|
||||
|
||||
// Color output (convenience wrappers auto-detect TTY):
|
||||
bun.Output.pretty("<green>success<r>: {s}\n", .{msg});
|
||||
bun.Output.prettyErrorln("<red>error<r>: {s}", .{msg});
|
||||
```
|
||||
|
||||
## Spawning Subprocesses (`src/bun.js/api/bun/process.zig`)
|
||||
|
||||
Use `bun.spawnSync` instead of `std.process.Child`:
|
||||
|
||||
```zig
|
||||
switch (bun.spawnSync(&.{
|
||||
.argv = argv,
|
||||
.envp = null, // inherit parent env
|
||||
.cwd = cwd,
|
||||
.stdout = .buffer, // capture
|
||||
.stderr = .inherit, // pass through
|
||||
.stdin = .ignore,
|
||||
|
||||
.windows = if (bun.Environment.isWindows) .{
|
||||
.loop = bun.jsc.EventLoopHandle.init(bun.jsc.MiniEventLoop.initGlobal(env, null)),
|
||||
},
|
||||
}) catch return) {
|
||||
.err => |err| { /* bun.sys.Error */ },
|
||||
.result => |result| {
|
||||
defer result.deinit();
|
||||
const stdout = result.stdout.items;
|
||||
if (result.status.isOK()) { ... }
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Options: `argv: []const []const u8`, `envp: ?[*:null]?[*:0]const u8` (null = inherit), `argv0: ?[*:0]const u8`. Stdio: `.inherit`, `.ignore`, `.buffer`.
|
||||
|
||||
## Common Patterns
|
||||
|
||||
```zig
|
||||
// Read a file
|
||||
const contents = switch (bun.sys.File.readFrom(bun.FD.cwd(), path, allocator)) {
|
||||
.result => |bytes| bytes,
|
||||
.err => |err| { globalObject.throwValue(err.toSystemError().toErrorInstance(globalObject)); return .zero; },
|
||||
};
|
||||
|
||||
// Create directories recursively
|
||||
bun.makePath(dir.stdDir(), sub_path) catch |err| { ... };
|
||||
|
||||
// Hashing
|
||||
bun.hash(bytes) // u64 — wyhash
|
||||
bun.hash32(bytes) // u32
|
||||
```
|
||||
|
||||
@@ -183,13 +183,14 @@ pub fn addUrlForCss(
|
||||
source: *const logger.Source,
|
||||
mime_type_: ?[]const u8,
|
||||
unique_key: ?[]const u8,
|
||||
force_inline: bool,
|
||||
) void {
|
||||
{
|
||||
const mime_type = if (mime_type_) |m| m else MimeType.byExtension(bun.strings.trimLeadingChar(std.fs.path.extension(source.path.text), '.')).value;
|
||||
const contents = source.contents;
|
||||
// TODO: make this configurable
|
||||
const COPY_THRESHOLD = 128 * 1024; // 128kb
|
||||
const should_copy = contents.len >= COPY_THRESHOLD and unique_key != null;
|
||||
const should_copy = !force_inline and contents.len >= COPY_THRESHOLD and unique_key != null;
|
||||
if (should_copy) return;
|
||||
this.url_for_css = url_for_css: {
|
||||
|
||||
|
||||
@@ -12,6 +12,11 @@ const ImportRef = struct {
|
||||
stmt_index: u32,
|
||||
};
|
||||
|
||||
const DeduplicatedImportResult = struct {
|
||||
namespace_ref: Ref,
|
||||
import_record_index: u32,
|
||||
};
|
||||
|
||||
pub fn convertStmt(ctx: *ConvertESMExportsForHmr, p: anytype, stmt: Stmt) !void {
|
||||
const new_stmt = switch (stmt.data) {
|
||||
else => brk: {
|
||||
@@ -195,7 +200,7 @@ pub fn convertStmt(ctx: *ConvertESMExportsForHmr, p: anytype, stmt: Stmt) !void
|
||||
return; // do not emit a statement here
|
||||
},
|
||||
.s_export_from => |st| {
|
||||
const namespace_ref = try ctx.deduplicatedImport(
|
||||
const deduped = try ctx.deduplicatedImport(
|
||||
p,
|
||||
st.import_record_index,
|
||||
st.namespace_ref,
|
||||
@@ -207,13 +212,17 @@ pub fn convertStmt(ctx: *ConvertESMExportsForHmr, p: anytype, stmt: Stmt) !void
|
||||
for (st.items) |*item| {
|
||||
const ref = item.name.ref.?;
|
||||
const symbol = &p.symbols.items[ref.innerIndex()];
|
||||
if (symbol.namespace_alias == null) {
|
||||
symbol.namespace_alias = .{
|
||||
.namespace_ref = namespace_ref,
|
||||
.alias = item.original_name,
|
||||
.import_record_index = st.import_record_index,
|
||||
};
|
||||
}
|
||||
// Always set the namespace alias using the deduplicated import
|
||||
// record. When two `export { ... } from` statements reference
|
||||
// the same source, the second import record is marked unused
|
||||
// and its items are merged into the first. The symbols may
|
||||
// already have a namespace_alias from ImportScanner pointing at
|
||||
// the now-unused record, so we must update it.
|
||||
symbol.namespace_alias = .{
|
||||
.namespace_ref = deduped.namespace_ref,
|
||||
.alias = item.original_name,
|
||||
.import_record_index = deduped.import_record_index,
|
||||
};
|
||||
try ctx.visitRefToExport(
|
||||
p,
|
||||
ref,
|
||||
@@ -234,7 +243,7 @@ pub fn convertStmt(ctx: *ConvertESMExportsForHmr, p: anytype, stmt: Stmt) !void
|
||||
return;
|
||||
},
|
||||
.s_export_star => |st| {
|
||||
const namespace_ref = try ctx.deduplicatedImport(
|
||||
const deduped = try ctx.deduplicatedImport(
|
||||
p,
|
||||
st.import_record_index,
|
||||
st.namespace_ref,
|
||||
@@ -248,13 +257,13 @@ pub fn convertStmt(ctx: *ConvertESMExportsForHmr, p: anytype, stmt: Stmt) !void
|
||||
// 'export * as ns from' creates one named property.
|
||||
try ctx.export_props.append(p.allocator, .{
|
||||
.key = Expr.init(E.String, .{ .data = alias.original_name }, stmt.loc),
|
||||
.value = Expr.initIdentifier(namespace_ref, stmt.loc),
|
||||
.value = Expr.initIdentifier(deduped.namespace_ref, stmt.loc),
|
||||
});
|
||||
} else {
|
||||
// 'export * from' creates a spread, hoisted at the top.
|
||||
try ctx.export_star_props.append(p.allocator, .{
|
||||
.kind = .spread,
|
||||
.value = Expr.initIdentifier(namespace_ref, stmt.loc),
|
||||
.value = Expr.initIdentifier(deduped.namespace_ref, stmt.loc),
|
||||
});
|
||||
}
|
||||
return;
|
||||
@@ -279,7 +288,8 @@ pub fn convertStmt(ctx: *ConvertESMExportsForHmr, p: anytype, stmt: Stmt) !void
|
||||
try ctx.stmts.append(p.allocator, new_stmt);
|
||||
}
|
||||
|
||||
/// Deduplicates imports, returning a previously used Ref if present.
|
||||
/// Deduplicates imports, returning a previously used Ref and import record
|
||||
/// index if present.
|
||||
fn deduplicatedImport(
|
||||
ctx: *ConvertESMExportsForHmr,
|
||||
p: anytype,
|
||||
@@ -289,7 +299,7 @@ fn deduplicatedImport(
|
||||
star_name_loc: ?logger.Loc,
|
||||
default_name: ?js_ast.LocRef,
|
||||
loc: logger.Loc,
|
||||
) !Ref {
|
||||
) !DeduplicatedImportResult {
|
||||
const ir = &p.import_records.items[import_record_index];
|
||||
const gop = try ctx.imports_seen.getOrPut(p.allocator, ir.path.text);
|
||||
if (gop.found_existing) {
|
||||
@@ -299,6 +309,12 @@ fn deduplicatedImport(
|
||||
ir.flags.is_unused = true;
|
||||
|
||||
const stmt = ctx.stmts.items[gop.value_ptr.stmt_index].data.s_import;
|
||||
// The surviving record may have been marked is_unused by barrel
|
||||
// optimization (when the first export-from statement's exports
|
||||
// were all deferred). Since we are merging new items into it,
|
||||
// clear is_unused so the import is actually emitted.
|
||||
p.import_records.items[stmt.import_record_index].flags.is_unused = false;
|
||||
|
||||
if (items.len > 0) {
|
||||
if (stmt.items.len == 0) {
|
||||
stmt.items = items;
|
||||
@@ -309,7 +325,7 @@ fn deduplicatedImport(
|
||||
if (namespace_ref.isValid()) {
|
||||
if (!stmt.namespace_ref.isValid()) {
|
||||
stmt.namespace_ref = namespace_ref;
|
||||
return namespace_ref;
|
||||
return .{ .namespace_ref = namespace_ref, .import_record_index = stmt.import_record_index };
|
||||
} else {
|
||||
// Erase this namespace ref, but since it may be used in
|
||||
// existing AST trees, a link must be established.
|
||||
@@ -327,7 +343,7 @@ fn deduplicatedImport(
|
||||
if (stmt.default_name == null) if (default_name) |dn| {
|
||||
stmt.default_name = dn;
|
||||
};
|
||||
return stmt.namespace_ref;
|
||||
return .{ .namespace_ref = stmt.namespace_ref, .import_record_index = stmt.import_record_index };
|
||||
}
|
||||
|
||||
try ctx.stmts.append(p.allocator, Stmt.alloc(S.Import, .{
|
||||
@@ -340,7 +356,7 @@ fn deduplicatedImport(
|
||||
}, loc));
|
||||
|
||||
gop.value_ptr.* = .{ .stmt_index = @intCast(ctx.stmts.items.len - 1) };
|
||||
return namespace_ref;
|
||||
return .{ .namespace_ref = namespace_ref, .import_record_index = import_record_index };
|
||||
}
|
||||
|
||||
fn visitBindingToExport(ctx: *ConvertESMExportsForHmr, p: anytype, binding: Binding) !void {
|
||||
|
||||
@@ -28,9 +28,6 @@ pub fn ReplTransforms(comptime P: type) type {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if there's top-level await
|
||||
const has_top_level_await = p.top_level_await_keyword.len > 0;
|
||||
|
||||
// Collect all statements into a single array
|
||||
var all_stmts = bun.handleOom(allocator.alloc(Stmt, total_stmts_count));
|
||||
var stmt_idx: usize = 0;
|
||||
@@ -41,6 +38,17 @@ pub fn ReplTransforms(comptime P: type) type {
|
||||
}
|
||||
}
|
||||
|
||||
// Check if there's top-level await or imports (imports become dynamic awaited imports)
|
||||
var has_top_level_await = p.top_level_await_keyword.len > 0;
|
||||
if (!has_top_level_await) {
|
||||
for (all_stmts) |stmt| {
|
||||
if (stmt.data == .s_import) {
|
||||
has_top_level_await = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply transform with is_async based on presence of top-level await
|
||||
try transformWithHoisting(p, parts, all_stmts, allocator, has_top_level_await);
|
||||
}
|
||||
@@ -154,6 +162,86 @@ pub fn ReplTransforms(comptime P: type) type {
|
||||
try inner_stmts.append(stmt);
|
||||
}
|
||||
},
|
||||
.s_import => |import_data| {
|
||||
// Convert static imports to dynamic imports for REPL evaluation:
|
||||
// import X from 'mod' -> var X = (await import('mod')).default
|
||||
// import { a, b } from 'mod' -> var {a, b} = await import('mod')
|
||||
// import * as X from 'mod' -> var X = await import('mod')
|
||||
// import 'mod' -> await import('mod')
|
||||
const path_str = p.import_records.items[import_data.import_record_index].path.text;
|
||||
const import_expr = p.newExpr(E.Import{
|
||||
.expr = p.newExpr(E.String{ .data = path_str }, stmt.loc),
|
||||
.import_record_index = std.math.maxInt(u32),
|
||||
}, stmt.loc);
|
||||
const await_expr = p.newExpr(E.Await{ .value = import_expr }, stmt.loc);
|
||||
|
||||
if (import_data.star_name_loc) |_| {
|
||||
// import * as X from 'mod' -> var X = await import('mod')
|
||||
try hoisted_stmts.append(p.s(S.Local{
|
||||
.kind = .k_var,
|
||||
.decls = Decl.List.fromOwnedSlice(bun.handleOom(allocator.dupe(G.Decl, &.{
|
||||
G.Decl{
|
||||
.binding = p.b(B.Identifier{ .ref = import_data.namespace_ref }, stmt.loc),
|
||||
.value = null,
|
||||
},
|
||||
}))),
|
||||
}, stmt.loc));
|
||||
const assign = p.newExpr(E.Binary{
|
||||
.op = .bin_assign,
|
||||
.left = p.newExpr(E.Identifier{ .ref = import_data.namespace_ref }, stmt.loc),
|
||||
.right = await_expr,
|
||||
}, stmt.loc);
|
||||
try inner_stmts.append(p.s(S.SExpr{ .value = assign }, stmt.loc));
|
||||
} else if (import_data.default_name) |default_name| {
|
||||
// import X from 'mod' -> var X = (await import('mod')).default
|
||||
// import X, { a } from 'mod' -> var __ns = await import('mod'); var X = __ns.default; var a = __ns.a;
|
||||
try hoisted_stmts.append(p.s(S.Local{
|
||||
.kind = .k_var,
|
||||
.decls = Decl.List.fromOwnedSlice(bun.handleOom(allocator.dupe(G.Decl, &.{
|
||||
G.Decl{
|
||||
.binding = p.b(B.Identifier{ .ref = default_name.ref.? }, default_name.loc),
|
||||
.value = null,
|
||||
},
|
||||
}))),
|
||||
}, stmt.loc));
|
||||
|
||||
if (import_data.items.len > 0) {
|
||||
// Share a single await import() between default and named imports.
|
||||
// namespace_ref is synthesized by processImportStatement for all non-star imports.
|
||||
try convertNamedImports(p, import_data, await_expr, &hoisted_stmts, &inner_stmts, allocator, stmt.loc);
|
||||
const ns_ref_expr = p.newExpr(E.Identifier{ .ref = import_data.namespace_ref }, stmt.loc);
|
||||
const dot_default = p.newExpr(E.Dot{
|
||||
.target = ns_ref_expr,
|
||||
.name = "default",
|
||||
.name_loc = stmt.loc,
|
||||
}, stmt.loc);
|
||||
const assign = p.newExpr(E.Binary{
|
||||
.op = .bin_assign,
|
||||
.left = p.newExpr(E.Identifier{ .ref = default_name.ref.? }, default_name.loc),
|
||||
.right = dot_default,
|
||||
}, stmt.loc);
|
||||
try inner_stmts.append(p.s(S.SExpr{ .value = assign }, stmt.loc));
|
||||
} else {
|
||||
const dot_default = p.newExpr(E.Dot{
|
||||
.target = await_expr,
|
||||
.name = "default",
|
||||
.name_loc = stmt.loc,
|
||||
}, stmt.loc);
|
||||
const assign = p.newExpr(E.Binary{
|
||||
.op = .bin_assign,
|
||||
.left = p.newExpr(E.Identifier{ .ref = default_name.ref.? }, default_name.loc),
|
||||
.right = dot_default,
|
||||
}, stmt.loc);
|
||||
try inner_stmts.append(p.s(S.SExpr{ .value = assign }, stmt.loc));
|
||||
}
|
||||
} else if (import_data.items.len > 0) {
|
||||
// import { a, b } from 'mod' -> destructure from await import('mod')
|
||||
try convertNamedImports(p, import_data, await_expr, &hoisted_stmts, &inner_stmts, allocator, stmt.loc);
|
||||
} else {
|
||||
// import 'mod' (side-effect only) -> await import('mod')
|
||||
try inner_stmts.append(p.s(S.SExpr{ .value = await_expr }, stmt.loc));
|
||||
}
|
||||
},
|
||||
.s_directive => |directive| {
|
||||
// In REPL mode, treat directives (string literals) as expressions
|
||||
const str_expr = p.newExpr(E.String{ .data = directive.value }, stmt.loc);
|
||||
@@ -195,6 +283,63 @@ pub fn ReplTransforms(comptime P: type) type {
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert named imports to individual var assignments from the dynamic import
|
||||
/// import { a, b as c } from 'mod' ->
|
||||
/// var a; var c; (hoisted)
|
||||
/// var __mod = await import('mod'); a = __mod.a; c = __mod.b; (inner)
|
||||
fn convertNamedImports(
|
||||
p: *P,
|
||||
import_data: *const S.Import,
|
||||
await_expr: Expr,
|
||||
hoisted_stmts: *ListManaged(Stmt),
|
||||
inner_stmts: *ListManaged(Stmt),
|
||||
allocator: Allocator,
|
||||
loc: logger.Loc,
|
||||
) !void {
|
||||
|
||||
// Store the module in the namespace ref: var __ns = await import('mod')
|
||||
try hoisted_stmts.append(p.s(S.Local{
|
||||
.kind = .k_var,
|
||||
.decls = Decl.List.fromOwnedSlice(bun.handleOom(allocator.dupe(G.Decl, &.{
|
||||
G.Decl{
|
||||
.binding = p.b(B.Identifier{ .ref = import_data.namespace_ref }, loc),
|
||||
.value = null,
|
||||
},
|
||||
}))),
|
||||
}, loc));
|
||||
const ns_assign = p.newExpr(E.Binary{
|
||||
.op = .bin_assign,
|
||||
.left = p.newExpr(E.Identifier{ .ref = import_data.namespace_ref }, loc),
|
||||
.right = await_expr,
|
||||
}, loc);
|
||||
try inner_stmts.append(p.s(S.SExpr{ .value = ns_assign }, loc));
|
||||
|
||||
// For each named import: var name; name = __ns.originalName;
|
||||
for (import_data.items) |item| {
|
||||
try hoisted_stmts.append(p.s(S.Local{
|
||||
.kind = .k_var,
|
||||
.decls = Decl.List.fromOwnedSlice(bun.handleOom(allocator.dupe(G.Decl, &.{
|
||||
G.Decl{
|
||||
.binding = p.b(B.Identifier{ .ref = item.name.ref.? }, item.name.loc),
|
||||
.value = null,
|
||||
},
|
||||
}))),
|
||||
}, loc));
|
||||
const ns_ref_expr = p.newExpr(E.Identifier{ .ref = import_data.namespace_ref }, loc);
|
||||
const prop_access = p.newExpr(E.Dot{
|
||||
.target = ns_ref_expr,
|
||||
.name = item.alias,
|
||||
.name_loc = item.name.loc,
|
||||
}, loc);
|
||||
const item_assign = p.newExpr(E.Binary{
|
||||
.op = .bin_assign,
|
||||
.left = p.newExpr(E.Identifier{ .ref = item.name.ref.? }, item.name.loc),
|
||||
.right = prop_access,
|
||||
}, loc);
|
||||
try inner_stmts.append(p.s(S.SExpr{ .value = item_assign }, loc));
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrap the last expression in return { value: expr }
|
||||
fn wrapLastExpressionWithReturn(p: *P, inner_stmts: *ListManaged(Stmt), allocator: Allocator) void {
|
||||
if (inner_stmts.items.len > 0) {
|
||||
|
||||
@@ -67,6 +67,15 @@ route_bundles: ArrayListUnmanaged(RouteBundle),
|
||||
graph_safety_lock: bun.safety.ThreadLock,
|
||||
client_graph: IncrementalGraph(.client),
|
||||
server_graph: IncrementalGraph(.server),
|
||||
/// Barrel files with deferred (is_unused) import records. These files must
|
||||
/// be re-parsed on every incremental build because the set of needed exports
|
||||
/// may have changed. Populated by applyBarrelOptimization.
|
||||
barrel_files_with_deferrals: bun.StringArrayHashMapUnmanaged(void) = .{},
|
||||
/// Accumulated barrel export requests across all builds. Maps barrel file
|
||||
/// path → set of export names that have been requested. This ensures that
|
||||
/// when a barrel is re-parsed in an incremental build, exports requested
|
||||
/// by non-stale files (from previous builds) are still kept.
|
||||
barrel_needed_exports: bun.StringArrayHashMapUnmanaged(bun.StringHashMapUnmanaged(void)) = .{},
|
||||
/// State populated during bundling and hot updates. Often cleared
|
||||
incremental_result: IncrementalResult,
|
||||
/// Quickly retrieve a framework route's index from its entry point file. These
|
||||
@@ -616,6 +625,23 @@ pub fn deinit(dev: *DevServer) void {
|
||||
},
|
||||
.server_graph = dev.server_graph.deinit(),
|
||||
.client_graph = dev.client_graph.deinit(),
|
||||
.barrel_files_with_deferrals = {
|
||||
for (dev.barrel_files_with_deferrals.keys()) |key| {
|
||||
alloc.free(key);
|
||||
}
|
||||
dev.barrel_files_with_deferrals.deinit(alloc);
|
||||
},
|
||||
.barrel_needed_exports = {
|
||||
var it = dev.barrel_needed_exports.iterator();
|
||||
while (it.next()) |entry| {
|
||||
var inner = entry.value_ptr.*;
|
||||
var inner_it = inner.keyIterator();
|
||||
while (inner_it.next()) |k| alloc.free(k.*);
|
||||
inner.deinit(alloc);
|
||||
alloc.free(entry.key_ptr.*);
|
||||
}
|
||||
dev.barrel_needed_exports.deinit(alloc);
|
||||
},
|
||||
.assets = dev.assets.deinit(alloc),
|
||||
.incremental_result = useAllFields(IncrementalResult, .{
|
||||
.had_adjusted_edges = {},
|
||||
@@ -2192,6 +2218,7 @@ pub fn finalizeBundle(
|
||||
) bun.JSError!void {
|
||||
assert(dev.magic == .valid);
|
||||
var had_sent_hmr_event = false;
|
||||
|
||||
defer {
|
||||
var heap = bv2.graph.heap;
|
||||
bv2.deinitWithoutFreeingArena();
|
||||
@@ -3070,6 +3097,11 @@ const CacheEntry = struct {
|
||||
};
|
||||
|
||||
pub fn isFileCached(dev: *DevServer, path: []const u8, side: bake.Graph) ?CacheEntry {
|
||||
// Barrel files with deferred records must always be re-parsed so the
|
||||
// barrel optimization can evaluate updated requested_exports.
|
||||
if (dev.barrel_files_with_deferrals.contains(path))
|
||||
return null;
|
||||
|
||||
dev.graph_safety_lock.lock();
|
||||
defer dev.graph_safety_lock.unlock();
|
||||
|
||||
|
||||
@@ -79,6 +79,8 @@ pub fn memoryCostDetailed(dev: *DevServer) MemoryCost {
|
||||
js_code += cost.code;
|
||||
source_maps += cost.source_maps;
|
||||
},
|
||||
.barrel_files_with_deferrals = {},
|
||||
.barrel_needed_exports = {},
|
||||
.assets = {
|
||||
assets += dev.assets.memoryCost();
|
||||
},
|
||||
|
||||
@@ -3,6 +3,17 @@ pub const webcore = @import("./bun.js/webcore.zig");
|
||||
pub const api = @import("./bun.js/api.zig");
|
||||
pub const bindgen = @import("./bun.js/bindgen.zig");
|
||||
|
||||
pub fn applyStandaloneRuntimeFlags(b: *bun.Transpiler, graph: *const bun.StandaloneModuleGraph) void {
|
||||
b.options.env.disable_default_env_files = graph.flags.disable_default_env_files;
|
||||
b.options.env.behavior = if (graph.flags.disable_default_env_files)
|
||||
.disable
|
||||
else
|
||||
.load_all_without_inlining;
|
||||
|
||||
b.resolver.opts.load_tsconfig_json = !graph.flags.disable_autoload_tsconfig;
|
||||
b.resolver.opts.load_package_json = !graph.flags.disable_autoload_package_json;
|
||||
}
|
||||
|
||||
pub const Run = struct {
|
||||
ctx: Command.Context,
|
||||
vm: *VirtualMachine,
|
||||
@@ -82,18 +93,7 @@ pub const Run = struct {
|
||||
.unspecified => {},
|
||||
}
|
||||
|
||||
// If .env loading is disabled, only load process env vars
|
||||
// Otherwise, load all .env files
|
||||
if (graph_ptr.flags.disable_default_env_files) {
|
||||
b.options.env.behavior = .disable;
|
||||
} else {
|
||||
b.options.env.behavior = .load_all_without_inlining;
|
||||
}
|
||||
|
||||
// Control loading of tsconfig.json and package.json at runtime
|
||||
// By default, these are disabled for standalone executables
|
||||
b.resolver.opts.load_tsconfig_json = !graph_ptr.flags.disable_autoload_tsconfig;
|
||||
b.resolver.opts.load_package_json = !graph_ptr.flags.disable_autoload_package_json;
|
||||
applyStandaloneRuntimeFlags(b, graph_ptr);
|
||||
|
||||
b.configureDefines() catch {
|
||||
failWithBuildError(vm);
|
||||
|
||||
@@ -1140,14 +1140,14 @@ export fn Bun__runVirtualModule(globalObject: *JSGlobalObject, specifier_ptr: *c
|
||||
fn getHardcodedModule(jsc_vm: *VirtualMachine, specifier: bun.String, hardcoded: HardcodedModule) ?ResolvedSource {
|
||||
analytics.Features.builtin_modules.insert(hardcoded);
|
||||
return switch (hardcoded) {
|
||||
.@"bun:main" => .{
|
||||
.@"bun:main" => if (jsc_vm.entry_point.generated) .{
|
||||
.allocator = null,
|
||||
.source_code = bun.String.cloneUTF8(jsc_vm.entry_point.source.contents),
|
||||
.specifier = specifier,
|
||||
.source_url = specifier,
|
||||
.tag = .esm,
|
||||
.source_code_needs_deref = true,
|
||||
},
|
||||
} else null,
|
||||
.@"bun:internal-for-testing" => {
|
||||
if (!Environment.isDebug) {
|
||||
if (!is_allowed_to_use_internal_testing_apis)
|
||||
|
||||
@@ -1616,7 +1616,7 @@ fn _resolve(
|
||||
if (strings.eqlComptime(std.fs.path.basename(specifier), Runtime.Runtime.Imports.alt_name)) {
|
||||
ret.path = Runtime.Runtime.Imports.Name;
|
||||
return;
|
||||
} else if (strings.eqlComptime(specifier, main_file_name)) {
|
||||
} else if (strings.eqlComptime(specifier, main_file_name) and jsc_vm.entry_point.generated) {
|
||||
ret.result = null;
|
||||
ret.path = jsc_vm.entry_point.source.path.text;
|
||||
return;
|
||||
@@ -2669,7 +2669,7 @@ pub fn remapZigException(
|
||||
allow_source_code_preview: bool,
|
||||
) void {
|
||||
error_instance.toZigException(this.global, exception);
|
||||
const enable_source_code_preview = allow_source_code_preview and
|
||||
var enable_source_code_preview = allow_source_code_preview and
|
||||
!(bun.feature_flag.BUN_DISABLE_SOURCE_CODE_PREVIEW.get() or
|
||||
bun.feature_flag.BUN_DISABLE_TRANSPILED_SOURCE_CODE_PREVIEW.get());
|
||||
|
||||
@@ -2764,6 +2764,12 @@ pub fn remapZigException(
|
||||
}
|
||||
}
|
||||
|
||||
// Don't show source code preview for REPL frames - it would show the
|
||||
// transformed IIFE wrapper code, not what the user typed.
|
||||
if (top.source_url.eqlComptime("[repl]")) {
|
||||
enable_source_code_preview = false;
|
||||
}
|
||||
|
||||
var top_source_url = top.source_url.toUTF8(bun.default_allocator);
|
||||
defer top_source_url.deinit();
|
||||
|
||||
@@ -2815,7 +2821,6 @@ pub fn remapZigException(
|
||||
// Avoid printing "export default 'native'"
|
||||
break :code ZigString.Slice.empty;
|
||||
}
|
||||
|
||||
var log = logger.Log.init(bun.default_allocator);
|
||||
defer log.deinit();
|
||||
|
||||
|
||||
@@ -21,16 +21,3 @@ export const gc = fn({
|
||||
},
|
||||
ret: t.usize,
|
||||
});
|
||||
|
||||
export const StringWidthOptions = t.dictionary({
|
||||
countAnsiEscapeCodes: t.boolean.default(false),
|
||||
ambiguousIsNarrow: t.boolean.default(true),
|
||||
});
|
||||
|
||||
export const stringWidth = fn({
|
||||
args: {
|
||||
str: t.DOMString.default(""),
|
||||
opts: StringWidthOptions.default({}),
|
||||
},
|
||||
ret: t.usize,
|
||||
});
|
||||
|
||||
@@ -34,6 +34,7 @@ pub const BunObject = struct {
|
||||
pub const sha = toJSCallback(host_fn.wrapStaticMethod(Crypto.SHA512_256, "hash_", true));
|
||||
pub const shellEscape = toJSCallback(Bun.shellEscape);
|
||||
pub const shrink = toJSCallback(Bun.shrink);
|
||||
pub const stringWidth = toJSCallback(Bun.stringWidth);
|
||||
pub const sleepSync = toJSCallback(Bun.sleepSync);
|
||||
pub const spawn = toJSCallback(host_fn.wrapStaticMethod(api.Subprocess, "spawn", false));
|
||||
pub const spawnSync = toJSCallback(host_fn.wrapStaticMethod(api.Subprocess, "spawnSync", false));
|
||||
@@ -179,6 +180,7 @@ pub const BunObject = struct {
|
||||
@export(&BunObject.sha, .{ .name = callbackName("sha") });
|
||||
@export(&BunObject.shellEscape, .{ .name = callbackName("shellEscape") });
|
||||
@export(&BunObject.shrink, .{ .name = callbackName("shrink") });
|
||||
@export(&BunObject.stringWidth, .{ .name = callbackName("stringWidth") });
|
||||
@export(&BunObject.sleepSync, .{ .name = callbackName("sleepSync") });
|
||||
@export(&BunObject.spawn, .{ .name = callbackName("spawn") });
|
||||
@export(&BunObject.spawnSync, .{ .name = callbackName("spawnSync") });
|
||||
@@ -1382,14 +1384,8 @@ pub fn getUnsafe(globalThis: *jsc.JSGlobalObject, _: *jsc.JSObject) jsc.JSValue
|
||||
return UnsafeObject.create(globalThis);
|
||||
}
|
||||
|
||||
pub fn stringWidth(str: bun.String, opts: gen.StringWidthOptions) usize {
|
||||
if (str.length() == 0)
|
||||
return 0;
|
||||
|
||||
if (opts.count_ansi_escape_codes)
|
||||
return str.visibleWidth(!opts.ambiguous_is_narrow);
|
||||
|
||||
return str.visibleWidthExcludeANSIColors(!opts.ambiguous_is_narrow);
|
||||
pub fn stringWidth(globalObject: *jsc.JSGlobalObject, callFrame: *jsc.CallFrame) bun.JSError!jsc.JSValue {
|
||||
return bun.String.jsGetStringWidth(globalObject, callFrame);
|
||||
}
|
||||
|
||||
/// EnvironmentVariables is runtime defined.
|
||||
|
||||
@@ -260,6 +260,10 @@ pub const JSBundler = struct {
|
||||
files: FileMap = .{},
|
||||
/// Generate metafile (JSON module graph)
|
||||
metafile: bool = false,
|
||||
/// Package names whose barrel files should be optimized.
|
||||
/// Named imports from these packages will only load the submodules
|
||||
/// that are actually used instead of parsing all re-exported submodules.
|
||||
optimize_imports: bun.StringSet = bun.StringSet.init(bun.default_allocator),
|
||||
|
||||
pub const CompileOptions = struct {
|
||||
compile_target: CompileTarget = .{},
|
||||
@@ -435,6 +439,7 @@ pub const JSBundler = struct {
|
||||
var this = Config{
|
||||
.entry_points = bun.StringSet.init(allocator),
|
||||
.external = bun.StringSet.init(allocator),
|
||||
.optimize_imports = bun.StringSet.init(allocator),
|
||||
.define = bun.StringMap.init(allocator, true),
|
||||
.dir = OwnedString.initEmpty(allocator),
|
||||
.outdir = OwnedString.initEmpty(allocator),
|
||||
@@ -819,6 +824,15 @@ pub const JSBundler = struct {
|
||||
}
|
||||
}
|
||||
|
||||
if (try config.getOwnArray(globalThis, "optimizeImports")) |optimize_imports| {
|
||||
var iter = try optimize_imports.arrayIterator(globalThis);
|
||||
while (try iter.next()) |entry| {
|
||||
var slice = try entry.toSliceOrNull(globalThis);
|
||||
defer slice.deinit();
|
||||
try this.optimize_imports.insert(slice.slice());
|
||||
}
|
||||
}
|
||||
|
||||
// if (try config.getOptional(globalThis, "dir", ZigString.Slice)) |slice| {
|
||||
// defer slice.deinit();
|
||||
// this.appendSliceExact(slice.slice()) catch unreachable;
|
||||
@@ -977,45 +991,57 @@ pub const JSBundler = struct {
|
||||
}
|
||||
|
||||
if (this.compile) |*compile| {
|
||||
this.target = .bun;
|
||||
// When compile + target=browser + all HTML entrypoints, produce standalone HTML.
|
||||
// Otherwise, default to bun executable compile.
|
||||
const has_all_html_entrypoints = brk: {
|
||||
if (this.entry_points.count() == 0) break :brk false;
|
||||
for (this.entry_points.keys()) |ep| {
|
||||
if (!strings.hasSuffixComptime(ep, ".html")) break :brk false;
|
||||
}
|
||||
break :brk true;
|
||||
};
|
||||
const is_standalone_html = this.target == .browser and has_all_html_entrypoints;
|
||||
if (!is_standalone_html) {
|
||||
this.target = .bun;
|
||||
|
||||
const define_keys = compile.compile_target.defineKeys();
|
||||
const define_values = compile.compile_target.defineValues();
|
||||
for (define_keys, define_values) |key, value| {
|
||||
try this.define.insert(key, value);
|
||||
}
|
||||
|
||||
const base_public_path = bun.StandaloneModuleGraph.targetBasePublicPath(this.compile.?.compile_target.os, "root/");
|
||||
try this.public_path.append(base_public_path);
|
||||
|
||||
// When using --compile, only `external` sourcemaps work, as we do not
|
||||
// look at the source map comment. Override any other sourcemap type.
|
||||
if (this.source_map != .none) {
|
||||
this.source_map = .external;
|
||||
}
|
||||
|
||||
if (compile.outfile.isEmpty()) {
|
||||
const entry_point = this.entry_points.keys()[0];
|
||||
var outfile = std.fs.path.basename(entry_point);
|
||||
const ext = std.fs.path.extension(outfile);
|
||||
if (ext.len > 0) {
|
||||
outfile = outfile[0 .. outfile.len - ext.len];
|
||||
const define_keys = compile.compile_target.defineKeys();
|
||||
const define_values = compile.compile_target.defineValues();
|
||||
for (define_keys, define_values) |key, value| {
|
||||
try this.define.insert(key, value);
|
||||
}
|
||||
|
||||
if (strings.eqlComptime(outfile, "index")) {
|
||||
outfile = std.fs.path.basename(std.fs.path.dirname(entry_point) orelse "index");
|
||||
const base_public_path = bun.StandaloneModuleGraph.targetBasePublicPath(this.compile.?.compile_target.os, "root/");
|
||||
try this.public_path.append(base_public_path);
|
||||
|
||||
// When using --compile, only `external` sourcemaps work, as we do not
|
||||
// look at the source map comment. Override any other sourcemap type.
|
||||
if (this.source_map != .none) {
|
||||
this.source_map = .external;
|
||||
}
|
||||
|
||||
if (strings.eqlComptime(outfile, "bun")) {
|
||||
outfile = std.fs.path.basename(std.fs.path.dirname(entry_point) orelse "bun");
|
||||
}
|
||||
if (compile.outfile.isEmpty()) {
|
||||
const entry_point = this.entry_points.keys()[0];
|
||||
var outfile = std.fs.path.basename(entry_point);
|
||||
const ext = std.fs.path.extension(outfile);
|
||||
if (ext.len > 0) {
|
||||
outfile = outfile[0 .. outfile.len - ext.len];
|
||||
}
|
||||
|
||||
// If argv[0] is "bun" or "bunx", we don't check if the binary is standalone
|
||||
if (strings.eqlComptime(outfile, "bun") or strings.eqlComptime(outfile, "bunx")) {
|
||||
return globalThis.throwInvalidArguments("cannot use compile with an output file named 'bun' because bun won't realize it's a standalone executable. Please choose a different name for compile.outfile", .{});
|
||||
}
|
||||
if (strings.eqlComptime(outfile, "index")) {
|
||||
outfile = std.fs.path.basename(std.fs.path.dirname(entry_point) orelse "index");
|
||||
}
|
||||
|
||||
try compile.outfile.appendSliceExact(outfile);
|
||||
if (strings.eqlComptime(outfile, "bun")) {
|
||||
outfile = std.fs.path.basename(std.fs.path.dirname(entry_point) orelse "bun");
|
||||
}
|
||||
|
||||
// If argv[0] is "bun" or "bunx", we don't check if the binary is standalone
|
||||
if (strings.eqlComptime(outfile, "bun") or strings.eqlComptime(outfile, "bunx")) {
|
||||
return globalThis.throwInvalidArguments("cannot use compile with an output file named 'bun' because bun won't realize it's a standalone executable. Please choose a different name for compile.outfile", .{});
|
||||
}
|
||||
|
||||
try compile.outfile.appendSliceExact(outfile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1026,6 +1052,20 @@ pub const JSBundler = struct {
|
||||
return globalThis.throwInvalidArguments("ESM bytecode requires compile: true. Use format: 'cjs' for bytecode without compile.", .{});
|
||||
}
|
||||
|
||||
// Validate standalone HTML mode: compile + browser target + all HTML entrypoints
|
||||
if (this.compile != null and this.target == .browser) {
|
||||
const has_all_html = brk: {
|
||||
if (this.entry_points.count() == 0) break :brk false;
|
||||
for (this.entry_points.keys()) |ep| {
|
||||
if (!strings.hasSuffixComptime(ep, ".html")) break :brk false;
|
||||
}
|
||||
break :brk true;
|
||||
};
|
||||
if (has_all_html and this.code_splitting) {
|
||||
return globalThis.throwInvalidArguments("Cannot use compile with target 'browser' and splitting for standalone HTML", .{});
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -1103,6 +1143,7 @@ pub const JSBundler = struct {
|
||||
self.env_prefix.deinit();
|
||||
self.footer.deinit();
|
||||
self.tsconfig_override.deinit();
|
||||
self.optimize_imports.deinit();
|
||||
self.files.deinitAndUnprotect();
|
||||
self.metafile_json_path.deinit();
|
||||
self.metafile_markdown_path.deinit();
|
||||
|
||||
@@ -4,7 +4,7 @@ const TimerObjectInternals = @This();
|
||||
/// Identifier for this timer that is exposed to JavaScript (by `+timer`)
|
||||
id: i32 = -1,
|
||||
interval: u31 = 0,
|
||||
strong_this: jsc.Strong.Optional = .empty,
|
||||
this_value: jsc.JSRef = .empty(),
|
||||
flags: Flags = .{},
|
||||
|
||||
/// Used by:
|
||||
@@ -76,31 +76,41 @@ pub fn runImmediateTask(this: *TimerObjectInternals, vm: *VirtualMachine) bool {
|
||||
// loop alive other than setImmediates
|
||||
(!this.flags.is_keeping_event_loop_alive and !vm.isEventLoopAliveExcludingImmediates()))
|
||||
{
|
||||
this.setEnableKeepingEventLoopAlive(vm, false);
|
||||
this.this_value.downgrade();
|
||||
this.deref();
|
||||
return false;
|
||||
}
|
||||
|
||||
const timer = this.strong_this.get() orelse {
|
||||
const timer = this.this_value.tryGet() orelse {
|
||||
if (Environment.isDebug) {
|
||||
@panic("TimerObjectInternals.runImmediateTask: this_object is null");
|
||||
}
|
||||
this.setEnableKeepingEventLoopAlive(vm, false);
|
||||
this.deref();
|
||||
return false;
|
||||
};
|
||||
const globalThis = vm.global;
|
||||
this.strong_this.deinit();
|
||||
this.this_value.downgrade();
|
||||
this.eventLoopTimer().state = .FIRED;
|
||||
this.setEnableKeepingEventLoopAlive(vm, false);
|
||||
timer.ensureStillAlive();
|
||||
|
||||
vm.eventLoop().enter();
|
||||
const callback = ImmediateObject.js.callbackGetCached(timer).?;
|
||||
const arguments = ImmediateObject.js.argumentsGetCached(timer).?;
|
||||
this.ref();
|
||||
const exception_thrown = this.run(globalThis, timer, callback, arguments, this.asyncID(), vm);
|
||||
this.deref();
|
||||
|
||||
if (this.eventLoopTimer().state == .FIRED) {
|
||||
this.deref();
|
||||
}
|
||||
const exception_thrown = brk: {
|
||||
this.ref();
|
||||
defer {
|
||||
if (this.eventLoopTimer().state == .FIRED) {
|
||||
this.deref();
|
||||
}
|
||||
this.deref();
|
||||
}
|
||||
break :brk this.run(globalThis, timer, callback, arguments, this.asyncID(), vm);
|
||||
};
|
||||
// --- after this point, the timer is no longer guaranteed to be alive ---
|
||||
|
||||
vm.eventLoop().exitMaybeDrainMicrotasks(!exception_thrown) catch return true;
|
||||
|
||||
@@ -120,7 +130,13 @@ pub fn fire(this: *TimerObjectInternals, _: *const timespec, vm: *jsc.VirtualMac
|
||||
this.eventLoopTimer().state = .FIRED;
|
||||
|
||||
const globalThis = vm.global;
|
||||
const this_object = this.strong_this.get().?;
|
||||
const this_object = this.this_value.tryGet() orelse {
|
||||
this.setEnableKeepingEventLoopAlive(vm, false);
|
||||
this.flags.has_cleared_timer = true;
|
||||
this.this_value.downgrade();
|
||||
this.deref();
|
||||
return;
|
||||
};
|
||||
|
||||
const callback: JSValue, const arguments: JSValue, var idle_timeout: JSValue, var repeat: JSValue = switch (kind) {
|
||||
.setImmediate => .{
|
||||
@@ -143,7 +159,7 @@ pub fn fire(this: *TimerObjectInternals, _: *const timespec, vm: *jsc.VirtualMac
|
||||
}
|
||||
this.setEnableKeepingEventLoopAlive(vm, false);
|
||||
this.flags.has_cleared_timer = true;
|
||||
this.strong_this.deinit();
|
||||
this.this_value.downgrade();
|
||||
this.deref();
|
||||
|
||||
return;
|
||||
@@ -152,7 +168,7 @@ pub fn fire(this: *TimerObjectInternals, _: *const timespec, vm: *jsc.VirtualMac
|
||||
var time_before_call: timespec = undefined;
|
||||
|
||||
if (kind != .setInterval) {
|
||||
this.strong_this.clearWithoutDeallocation();
|
||||
this.this_value.downgrade();
|
||||
} else {
|
||||
time_before_call = timespec.msFromNow(.allow_mocked_time, this.interval);
|
||||
}
|
||||
@@ -239,7 +255,7 @@ fn convertToInterval(this: *TimerObjectInternals, global: *JSGlobalObject, timer
|
||||
|
||||
// https://github.com/nodejs/node/blob/a7cbb904745591c9a9d047a364c2c188e5470047/lib/internal/timers.js#L613
|
||||
TimeoutObject.js.idleTimeoutSetCached(timer, global, repeat);
|
||||
this.strong_this.set(global, timer);
|
||||
this.this_value.setStrong(timer, global);
|
||||
this.flags.kind = .setInterval;
|
||||
this.interval = new_interval;
|
||||
this.reschedule(timer, vm, global);
|
||||
@@ -297,7 +313,7 @@ pub fn init(
|
||||
this.reschedule(timer, vm, global);
|
||||
}
|
||||
|
||||
this.strong_this.set(global, timer);
|
||||
this.this_value.setStrong(timer, global);
|
||||
}
|
||||
|
||||
pub fn doRef(this: *TimerObjectInternals, _: *jsc.JSGlobalObject, this_value: JSValue) JSValue {
|
||||
@@ -327,7 +343,7 @@ pub fn doRefresh(this: *TimerObjectInternals, globalObject: *jsc.JSGlobalObject,
|
||||
return this_value;
|
||||
}
|
||||
|
||||
this.strong_this.set(globalObject, this_value);
|
||||
this.this_value.setStrong(this_value, globalObject);
|
||||
this.reschedule(this_value, VirtualMachine.get(), globalObject);
|
||||
|
||||
return this_value;
|
||||
@@ -350,12 +366,18 @@ pub fn cancel(this: *TimerObjectInternals, vm: *VirtualMachine) void {
|
||||
this.setEnableKeepingEventLoopAlive(vm, false);
|
||||
this.flags.has_cleared_timer = true;
|
||||
|
||||
if (this.flags.kind == .setImmediate) return;
|
||||
if (this.flags.kind == .setImmediate) {
|
||||
// Release the strong reference so the GC can collect the JS object.
|
||||
// The immediate task is still in the event loop queue and will be skipped
|
||||
// by runImmediateTask when it sees has_cleared_timer == true.
|
||||
this.this_value.downgrade();
|
||||
return;
|
||||
}
|
||||
|
||||
const was_active = this.eventLoopTimer().state == .ACTIVE;
|
||||
|
||||
this.eventLoopTimer().state = .CANCELLED;
|
||||
this.strong_this.deinit();
|
||||
this.this_value.downgrade();
|
||||
|
||||
if (was_active) {
|
||||
vm.timer.remove(this.eventLoopTimer());
|
||||
@@ -442,12 +464,12 @@ pub fn getDestroyed(this: *TimerObjectInternals) bool {
|
||||
}
|
||||
|
||||
pub fn finalize(this: *TimerObjectInternals) void {
|
||||
this.strong_this.deinit();
|
||||
this.this_value.finalize();
|
||||
this.deref();
|
||||
}
|
||||
|
||||
pub fn deinit(this: *TimerObjectInternals) void {
|
||||
this.strong_this.deinit();
|
||||
this.this_value.deinit();
|
||||
const vm = VirtualMachine.get();
|
||||
const kind = this.flags.kind;
|
||||
|
||||
|
||||
@@ -698,8 +698,7 @@ pub fn setRawMode(
|
||||
|
||||
if (comptime Environment.isPosix) {
|
||||
// Use the existing TTY mode function
|
||||
const mode: c_int = if (enabled) 1 else 0;
|
||||
const tty_result = Bun__ttySetMode(this.master_fd.cast(), mode);
|
||||
const tty_result = bun.tty.setMode(this.master_fd.cast(), if (enabled) .raw else .normal);
|
||||
if (tty_result != 0) {
|
||||
return globalObject.throw("Failed to set raw mode", .{});
|
||||
}
|
||||
@@ -708,9 +707,6 @@ pub fn setRawMode(
|
||||
this.flags.raw_mode = enabled;
|
||||
return .js_undefined;
|
||||
}
|
||||
|
||||
extern fn Bun__ttySetMode(fd: c_int, mode: c_int) c_int;
|
||||
|
||||
/// POSIX termios struct for terminal flags manipulation
|
||||
const Termios = if (Environment.isPosix) std.posix.termios else void;
|
||||
|
||||
|
||||
@@ -50,23 +50,28 @@ fn getArgv0(globalThis: *jsc.JSGlobalObject, PATH: []const u8, cwd: []const u8,
|
||||
|
||||
/// `argv` for `Bun.spawn` & `Bun.spawnSync`
|
||||
fn getArgv(globalThis: *jsc.JSGlobalObject, args: JSValue, PATH: []const u8, cwd: []const u8, argv0: *?[*:0]const u8, allocator: std.mem.Allocator, argv: *std.array_list.Managed(?[*:0]const u8)) bun.JSError!void {
|
||||
var cmds_array = try args.arrayIterator(globalThis);
|
||||
// + 1 for argv0
|
||||
// + 1 for null terminator
|
||||
argv.* = try @TypeOf(argv.*).initCapacity(allocator, cmds_array.len + 2);
|
||||
|
||||
if (args.isEmptyOrUndefinedOrNull()) {
|
||||
return globalThis.throwInvalidArguments("cmd must be an array of strings", .{});
|
||||
}
|
||||
|
||||
var cmds_array = try args.arrayIterator(globalThis);
|
||||
|
||||
if (cmds_array.len == 0) {
|
||||
return globalThis.throwInvalidArguments("cmd must not be empty", .{});
|
||||
}
|
||||
|
||||
if (cmds_array.len > std.math.maxInt(u32) - 2) {
|
||||
return globalThis.throwInvalidArguments("cmd array is too large", .{});
|
||||
}
|
||||
|
||||
// + 1 for argv0
|
||||
// + 1 for null terminator
|
||||
argv.* = try @TypeOf(argv.*).initCapacity(allocator, @as(usize, cmds_array.len) + 2);
|
||||
|
||||
const argv0_result = try getArgv0(globalThis, PATH, cwd, argv0.*, (try cmds_array.next()).?, allocator);
|
||||
|
||||
argv0.* = argv0_result.argv0.ptr;
|
||||
argv.appendAssumeCapacity(argv0_result.arg0.ptr);
|
||||
try argv.append(argv0_result.arg0.ptr);
|
||||
|
||||
var arg_index: usize = 1;
|
||||
while (try cmds_array.next()) |value| {
|
||||
@@ -78,7 +83,7 @@ fn getArgv(globalThis: *jsc.JSGlobalObject, args: JSValue, PATH: []const u8, cwd
|
||||
return globalThis.ERR(.INVALID_ARG_VALUE, "The argument 'args[{d}]' must be a string without null bytes. Received \"{f}\"", .{ arg_index, arg.toZigString() }).throw();
|
||||
}
|
||||
|
||||
argv.appendAssumeCapacity(try arg.toOwnedSliceZ(allocator));
|
||||
try argv.append(try arg.toOwnedSliceZ(allocator));
|
||||
arg_index += 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -1087,8 +1087,10 @@ pub const WindowsSpawnOptions = struct {
|
||||
dup2: struct { out: bun.jsc.Subprocess.StdioKind, to: bun.jsc.Subprocess.StdioKind },
|
||||
|
||||
pub fn deinit(this: *const Stdio) void {
|
||||
if (this.* == .buffer) {
|
||||
bun.default_allocator.destroy(this.buffer);
|
||||
switch (this.*) {
|
||||
.buffer => |pipe| pipe.closeAndDestroy(),
|
||||
.ipc => |pipe| pipe.closeAndDestroy(),
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1377,36 +1379,22 @@ pub fn spawnProcessPosix(
|
||||
break :brk .{ pair[if (i == 0) 1 else 0], pair[if (i == 0) 0 else 1] };
|
||||
};
|
||||
|
||||
if (i == 0) {
|
||||
// their copy of stdin should be readable
|
||||
_ = std.c.shutdown(@intCast(fds[1].cast()), std.posix.SHUT.WR);
|
||||
|
||||
// our copy of stdin should be writable
|
||||
_ = std.c.shutdown(@intCast(fds[0].cast()), std.posix.SHUT.RD);
|
||||
|
||||
if (comptime Environment.isMac) {
|
||||
// macOS seems to default to around 8 KB for the buffer size
|
||||
// this is comically small.
|
||||
// TODO: investigate if this should be adjusted on Linux.
|
||||
const so_recvbuf: c_int = 1024 * 512;
|
||||
const so_sendbuf: c_int = 1024 * 512;
|
||||
// Note: we intentionally do NOT call shutdown() on the
|
||||
// socketpair fds. On SOCK_STREAM socketpairs, shutdown(fd, SHUT_WR)
|
||||
// sends a FIN to the peer, which causes programs that poll the
|
||||
// write end for readability (e.g. Python's asyncio connect_write_pipe)
|
||||
// to interpret it as "connection closed" and tear down their transport.
|
||||
// The socketpair is already used unidirectionally by convention.
|
||||
if (comptime Environment.isMac) {
|
||||
// macOS seems to default to around 8 KB for the buffer size
|
||||
// this is comically small.
|
||||
// TODO: investigate if this should be adjusted on Linux.
|
||||
const so_recvbuf: c_int = 1024 * 512;
|
||||
const so_sendbuf: c_int = 1024 * 512;
|
||||
if (i == 0) {
|
||||
_ = std.c.setsockopt(fds[1].cast(), std.posix.SOL.SOCKET, std.posix.SO.RCVBUF, &so_recvbuf, @sizeOf(c_int));
|
||||
_ = std.c.setsockopt(fds[0].cast(), std.posix.SOL.SOCKET, std.posix.SO.SNDBUF, &so_sendbuf, @sizeOf(c_int));
|
||||
}
|
||||
} else {
|
||||
|
||||
// their copy of stdout or stderr should be writable
|
||||
_ = std.c.shutdown(@intCast(fds[1].cast()), std.posix.SHUT.RD);
|
||||
|
||||
// our copy of stdout or stderr should be readable
|
||||
_ = std.c.shutdown(@intCast(fds[0].cast()), std.posix.SHUT.WR);
|
||||
|
||||
if (comptime Environment.isMac) {
|
||||
// macOS seems to default to around 8 KB for the buffer size
|
||||
// this is comically small.
|
||||
// TODO: investigate if this should be adjusted on Linux.
|
||||
const so_recvbuf: c_int = 1024 * 512;
|
||||
const so_sendbuf: c_int = 1024 * 512;
|
||||
} else {
|
||||
_ = std.c.setsockopt(fds[0].cast(), std.posix.SOL.SOCKET, std.posix.SO.RCVBUF, &so_recvbuf, @sizeOf(c_int));
|
||||
_ = std.c.setsockopt(fds[1].cast(), std.posix.SOL.SOCKET, std.posix.SO.SNDBUF, &so_sendbuf, @sizeOf(c_int));
|
||||
}
|
||||
@@ -1629,9 +1617,10 @@ pub fn spawnProcessWindows(
|
||||
stdio.flags = uv.UV_INHERIT_FD;
|
||||
stdio.data.fd = fd_i;
|
||||
},
|
||||
.ipc => |my_pipe| {
|
||||
// ipc option inside stdin, stderr or stdout are not supported
|
||||
bun.default_allocator.destroy(my_pipe);
|
||||
.ipc => {
|
||||
// ipc option inside stdin, stderr or stdout is not supported.
|
||||
// Don't free the pipe here — the caller owns it and will
|
||||
// clean it up via WindowsSpawnOptions.deinit().
|
||||
stdio.flags = uv.UV_IGNORE;
|
||||
},
|
||||
.ignore => {
|
||||
@@ -1829,7 +1818,7 @@ pub const sync = struct {
|
||||
.ignore => .ignore,
|
||||
.buffer => .{
|
||||
.buffer = if (Environment.isWindows)
|
||||
bun.handleOom(bun.default_allocator.create(bun.windows.libuv.Pipe)),
|
||||
bun.new(bun.windows.libuv.Pipe, std.mem.zeroes(bun.windows.libuv.Pipe)),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1707,6 +1707,15 @@ pub fn NewWrappedHandler(comptime tls: bool) type {
|
||||
|
||||
pub fn onClose(this: WrappedSocket, socket: Socket, err: c_int, data: ?*anyopaque) bun.JSError!void {
|
||||
if (comptime tls) {
|
||||
// Clean up the raw TCP socket from upgradeTLS() — its onClose
|
||||
// never fires because uws closes through the TLS context only.
|
||||
defer {
|
||||
if (!this.tcp.socket.isDetached()) {
|
||||
this.tcp.socket.detach();
|
||||
this.tcp.has_pending_activity.store(false, .release);
|
||||
this.tcp.deref();
|
||||
}
|
||||
}
|
||||
try TLSSocket.onClose(this.tls, socket, err, data);
|
||||
} else {
|
||||
try TLSSocket.onClose(this.tcp, socket, err, data);
|
||||
|
||||
@@ -851,6 +851,9 @@ pub fn getsockname(this: *Listener, globalThis: *jsc.JSGlobalObject, callFrame:
|
||||
}
|
||||
|
||||
const out = callFrame.argumentsAsArray(1)[0];
|
||||
if (!out.isObject()) {
|
||||
return globalThis.throwInvalidArguments("Expected object", .{});
|
||||
}
|
||||
const socket = this.listener.uws;
|
||||
|
||||
var buf: [64]u8 = [_]u8{0} ** 64;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const WindowsNamedPipeContext = @This();
|
||||
|
||||
ref_count: RefCount,
|
||||
named_pipe: uws.WindowsNamedPipe,
|
||||
socket: SocketType,
|
||||
|
||||
@@ -10,6 +11,14 @@ task: jsc.AnyTask,
|
||||
task_event: EventState = .none,
|
||||
is_open: bool = false,
|
||||
|
||||
const RefCount = bun.ptr.RefCount(@This(), "ref_count", scheduleDeinit, .{});
|
||||
pub const ref = RefCount.ref;
|
||||
pub const deref = RefCount.deref;
|
||||
|
||||
fn scheduleDeinit(this: *WindowsNamedPipeContext) void {
|
||||
this.deinitInNextTick();
|
||||
}
|
||||
|
||||
pub const EventState = enum(u8) {
|
||||
deinit,
|
||||
none,
|
||||
@@ -148,7 +157,7 @@ fn onClose(this: *WindowsNamedPipeContext) void {
|
||||
.none => {},
|
||||
}
|
||||
|
||||
this.deinitInNextTick();
|
||||
this.deref();
|
||||
}
|
||||
|
||||
fn runEvent(this: *WindowsNamedPipeContext) void {
|
||||
@@ -169,6 +178,7 @@ fn deinitInNextTick(this: *WindowsNamedPipeContext) void {
|
||||
pub fn create(globalThis: *jsc.JSGlobalObject, socket: SocketType) *WindowsNamedPipeContext {
|
||||
const vm = globalThis.bunVM();
|
||||
const this = WindowsNamedPipeContext.new(.{
|
||||
.ref_count = .init(),
|
||||
.vm = vm,
|
||||
.globalThis = globalThis,
|
||||
.task = undefined,
|
||||
@@ -177,8 +187,10 @@ pub fn create(globalThis: *jsc.JSGlobalObject, socket: SocketType) *WindowsNamed
|
||||
});
|
||||
|
||||
// named_pipe owns the pipe (PipeWriter owns the pipe and will close and deinit it)
|
||||
this.named_pipe = uws.WindowsNamedPipe.from(bun.handleOom(bun.default_allocator.create(uv.Pipe)), .{
|
||||
this.named_pipe = uws.WindowsNamedPipe.from(bun.new(uv.Pipe, std.mem.zeroes(uv.Pipe)), .{
|
||||
.ctx = this,
|
||||
.ref_ctx = @ptrCast(&WindowsNamedPipeContext.ref),
|
||||
.deref_ctx = @ptrCast(&WindowsNamedPipeContext.deref),
|
||||
.onOpen = @ptrCast(&WindowsNamedPipeContext.onOpen),
|
||||
.onData = @ptrCast(&WindowsNamedPipeContext.onData),
|
||||
.onHandshake = @ptrCast(&WindowsNamedPipeContext.onHandshake),
|
||||
@@ -218,7 +230,7 @@ pub fn open(globalThis: *jsc.JSGlobalObject, fd: bun.FileDescriptor, ssl_config:
|
||||
},
|
||||
.none => {},
|
||||
}
|
||||
this.deinitInNextTick();
|
||||
this.deref();
|
||||
}
|
||||
try this.named_pipe.open(fd, ssl_config).unwrap();
|
||||
return &this.named_pipe;
|
||||
@@ -238,7 +250,7 @@ pub fn connect(globalThis: *jsc.JSGlobalObject, path: []const u8, ssl_config: ?j
|
||||
},
|
||||
.none => {},
|
||||
}
|
||||
this.deinitInNextTick();
|
||||
this.deref();
|
||||
}
|
||||
|
||||
if (path[path.len - 1] == 0) {
|
||||
@@ -276,6 +288,8 @@ pub fn deinit(this: *WindowsNamedPipeContext) void {
|
||||
bun.destroy(this);
|
||||
}
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
const bun = @import("bun");
|
||||
const Output = bun.Output;
|
||||
const jsc = bun.jsc;
|
||||
|
||||
@@ -235,10 +235,10 @@ pub const Stdio = union(enum) {
|
||||
return .{ .err = .blob_used_as_out };
|
||||
}
|
||||
|
||||
break :brk .{ .buffer = bun.handleOom(bun.default_allocator.create(uv.Pipe)) };
|
||||
break :brk .{ .buffer = createZeroedPipe() };
|
||||
},
|
||||
.ipc => .{ .ipc = bun.handleOom(bun.default_allocator.create(uv.Pipe)) },
|
||||
.capture, .pipe, .array_buffer, .readable_stream => .{ .buffer = bun.handleOom(bun.default_allocator.create(uv.Pipe)) },
|
||||
.ipc => .{ .ipc = createZeroedPipe() },
|
||||
.capture, .pipe, .array_buffer, .readable_stream => .{ .buffer = createZeroedPipe() },
|
||||
.fd => |fd| .{ .pipe = fd },
|
||||
.dup2 => .{ .dup2 = .{ .out = stdio.dup2.out, .to = stdio.dup2.to } },
|
||||
.path => |pathlike| .{ .path = pathlike.slice() },
|
||||
@@ -487,12 +487,18 @@ pub const Stdio = union(enum) {
|
||||
}
|
||||
};
|
||||
|
||||
/// Allocate a zero-initialized uv.Pipe. Zero-init ensures `pipe.loop` is null
|
||||
/// for pipes that never reach `uv_pipe_init`, so `closeAndDestroy` can tell
|
||||
/// whether `uv_close` is needed.
|
||||
fn createZeroedPipe() *uv.Pipe {
|
||||
return bun.new(uv.Pipe, std.mem.zeroes(uv.Pipe));
|
||||
}
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
const bun = @import("bun");
|
||||
const Environment = bun.Environment;
|
||||
const Output = bun.Output;
|
||||
const default_allocator = bun.default_allocator;
|
||||
const uv = bun.windows.libuv;
|
||||
|
||||
const jsc = bun.jsc;
|
||||
|
||||
@@ -468,6 +468,17 @@ pub fn writeHead(this: *NodeHTTPResponse, globalObject: *jsc.JSGlobalObject, cal
|
||||
return globalObject.ERR(.HTTP_HEADERS_SENT, "Stream already started", .{}).throw();
|
||||
}
|
||||
|
||||
// Validate status message does not contain invalid characters (defense-in-depth
|
||||
// against HTTP response splitting). Matches Node.js checkInvalidHeaderChar:
|
||||
// rejects any char not in [\t\x20-\x7e\x80-\xff].
|
||||
if (status_message_slice.len > 0) {
|
||||
for (status_message_slice.slice()) |c| {
|
||||
if (c != '\t' and (c < 0x20 or c == 0x7f)) {
|
||||
return globalObject.ERR(.INVALID_CHAR, "Invalid character in statusMessage", .{}).throw();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
do_it: {
|
||||
if (status_message_slice.len == 0) {
|
||||
if (HTTPStatusText.get(@intCast(status_code))) |status_message| {
|
||||
|
||||
@@ -24,6 +24,12 @@ client_renegotiation_window: u32 = 0,
|
||||
requires_custom_request_ctx: bool = false,
|
||||
is_using_default_ciphers: bool = true,
|
||||
low_memory_mode: bool = false,
|
||||
ref_count: RC = .init(),
|
||||
cached_hash: u64 = 0,
|
||||
|
||||
const RC = bun.ptr.ThreadSafeRefCount(@This(), "ref_count", destroy, .{});
|
||||
pub const ref = RC.ref;
|
||||
pub const deref = RC.deref;
|
||||
|
||||
const ReadFromBlobError = bun.JSError || error{
|
||||
NullStore,
|
||||
@@ -113,6 +119,7 @@ pub fn forClientVerification(this: SSLConfig) SSLConfig {
|
||||
|
||||
pub fn isSame(this: *const SSLConfig, other: *const SSLConfig) bool {
|
||||
inline for (comptime std.meta.fields(SSLConfig)) |field| {
|
||||
if (comptime std.mem.eql(u8, field.name, "ref_count") or std.mem.eql(u8, field.name, "cached_hash")) continue;
|
||||
const first = @field(this, field.name);
|
||||
const second = @field(other, field.name);
|
||||
switch (field.type) {
|
||||
@@ -185,6 +192,8 @@ pub fn deinit(this: *SSLConfig) void {
|
||||
.requires_custom_request_ctx = {},
|
||||
.is_using_default_ciphers = {},
|
||||
.low_memory_mode = {},
|
||||
.ref_count = {},
|
||||
.cached_hash = {},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -222,9 +231,97 @@ pub fn clone(this: *const SSLConfig) SSLConfig {
|
||||
.requires_custom_request_ctx = this.requires_custom_request_ctx,
|
||||
.is_using_default_ciphers = this.is_using_default_ciphers,
|
||||
.low_memory_mode = this.low_memory_mode,
|
||||
.ref_count = .init(),
|
||||
.cached_hash = 0,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn contentHash(this: *SSLConfig) u64 {
|
||||
if (this.cached_hash != 0) return this.cached_hash;
|
||||
var hasher = std.hash.Wyhash.init(0);
|
||||
inline for (comptime std.meta.fields(SSLConfig)) |field| {
|
||||
if (comptime std.mem.eql(u8, field.name, "ref_count") or std.mem.eql(u8, field.name, "cached_hash")) continue;
|
||||
const value = @field(this, field.name);
|
||||
switch (field.type) {
|
||||
?[*:0]const u8 => {
|
||||
if (value) |s| {
|
||||
hasher.update(bun.asByteSlice(s));
|
||||
}
|
||||
hasher.update(&.{0});
|
||||
},
|
||||
?[][*:0]const u8 => {
|
||||
if (value) |slice| {
|
||||
for (slice) |s| {
|
||||
hasher.update(bun.asByteSlice(s));
|
||||
hasher.update(&.{0});
|
||||
}
|
||||
}
|
||||
hasher.update(&.{0});
|
||||
},
|
||||
else => {
|
||||
hasher.update(std.mem.asBytes(&value));
|
||||
},
|
||||
}
|
||||
}
|
||||
const hash = hasher.final();
|
||||
// Avoid 0 since it's the sentinel for "not computed"
|
||||
this.cached_hash = if (hash == 0) 1 else hash;
|
||||
return this.cached_hash;
|
||||
}
|
||||
|
||||
/// Called by the RC mixin when refcount reaches 0.
|
||||
fn destroy(this: *SSLConfig) void {
|
||||
GlobalRegistry.remove(this);
|
||||
this.deinit();
|
||||
bun.default_allocator.destroy(this);
|
||||
}
|
||||
|
||||
pub const GlobalRegistry = struct {
|
||||
const MapContext = struct {
|
||||
pub fn hash(_: @This(), key: *SSLConfig) u32 {
|
||||
return @truncate(key.contentHash());
|
||||
}
|
||||
pub fn eql(_: @This(), a: *SSLConfig, b: *SSLConfig, _: usize) bool {
|
||||
return a.isSame(b);
|
||||
}
|
||||
};
|
||||
|
||||
var mutex: bun.Mutex = .{};
|
||||
var configs: std.ArrayHashMapUnmanaged(*SSLConfig, void, MapContext, true) = .empty;
|
||||
|
||||
/// Takes ownership of a heap-allocated SSLConfig.
|
||||
/// If an identical config already exists in the registry, the new one is freed
|
||||
/// and the existing one is returned (with refcount incremented).
|
||||
/// If no match, the new config is registered and returned.
|
||||
pub fn intern(new_config: *SSLConfig) *SSLConfig {
|
||||
mutex.lock();
|
||||
defer mutex.unlock();
|
||||
|
||||
// Look up by content hash/equality
|
||||
const gop = bun.handleOom(configs.getOrPutContext(bun.default_allocator, new_config, .{}));
|
||||
if (gop.found_existing) {
|
||||
// Identical config already exists - free the new one, return existing
|
||||
const existing = gop.key_ptr.*;
|
||||
new_config.ref_count.clearWithoutDestructor();
|
||||
new_config.deinit();
|
||||
bun.default_allocator.destroy(new_config);
|
||||
existing.ref();
|
||||
return existing;
|
||||
}
|
||||
|
||||
// New config - it's already inserted by getOrPut
|
||||
// refcount is already 1 from initialization
|
||||
return new_config;
|
||||
}
|
||||
|
||||
/// Remove a config from the registry. Called when refcount reaches 0.
|
||||
fn remove(config: *SSLConfig) void {
|
||||
mutex.lock();
|
||||
defer mutex.unlock();
|
||||
_ = configs.swapRemoveContext(config, .{});
|
||||
}
|
||||
};
|
||||
|
||||
pub const zero = SSLConfig{};
|
||||
|
||||
pub fn fromJS(
|
||||
@@ -294,9 +391,9 @@ pub fn fromGenerated(
|
||||
|
||||
const protocols = switch (generated.alpn_protocols) {
|
||||
.none => null,
|
||||
.string => |*ref| ref.get().toOwnedSliceZ(bun.default_allocator),
|
||||
.buffer => |*ref| blk: {
|
||||
const buffer: jsc.ArrayBuffer = ref.get().asArrayBuffer();
|
||||
.string => |*val| val.get().toOwnedSliceZ(bun.default_allocator),
|
||||
.buffer => |*val| blk: {
|
||||
const buffer: jsc.ArrayBuffer = val.get().asArrayBuffer();
|
||||
break :blk try bun.default_allocator.dupeZ(u8, buffer.byteSlice());
|
||||
},
|
||||
};
|
||||
@@ -366,9 +463,9 @@ fn handleFile(
|
||||
) ReadFromBlobError!?[][*:0]const u8 {
|
||||
const single = try handleSingleFile(global, switch (file.*) {
|
||||
.none => return null,
|
||||
.string => |*ref| .{ .string = ref.get() },
|
||||
.buffer => |*ref| .{ .buffer = ref.get() },
|
||||
.file => |*ref| .{ .file = ref.get() },
|
||||
.string => |*val| .{ .string = val.get() },
|
||||
.buffer => |*val| .{ .buffer = val.get() },
|
||||
.file => |*val| .{ .file = val.get() },
|
||||
.array => |*list| return try handleFileArray(global, list.items()),
|
||||
});
|
||||
errdefer bun.freeSensitive(bun.default_allocator, single);
|
||||
@@ -391,9 +488,9 @@ fn handleFileArray(
|
||||
}
|
||||
for (elements) |*elem| {
|
||||
result.appendAssumeCapacity(try handleSingleFile(global, switch (elem.*) {
|
||||
.string => |*ref| .{ .string = ref.get() },
|
||||
.buffer => |*ref| .{ .buffer = ref.get() },
|
||||
.file => |*ref| .{ .file = ref.get() },
|
||||
.string => |*val| .{ .string = val.get() },
|
||||
.buffer => |*val| .{ .buffer = val.get() },
|
||||
.file => |*val| .{ .file = val.get() },
|
||||
}));
|
||||
}
|
||||
return try result.toOwnedSlice();
|
||||
|
||||
@@ -24,6 +24,18 @@ static inline bool isEscapeCharacter(Char c)
|
||||
}
|
||||
}
|
||||
|
||||
// SIMD comparison against exact escape character values. Used to refine
|
||||
// the broad range match (0x10-0x1F / 0x90-0x9F) to only actual escape
|
||||
// introducers: 0x1B, 0x90, 0x98, 0x9B, 0x9D, 0x9E, 0x9F.
|
||||
template<typename SIMDType>
|
||||
static auto exactEscapeMatch(std::conditional_t<sizeof(SIMDType) == 1, simde_uint8x16_t, simde_uint16x8_t> chunk)
|
||||
{
|
||||
if constexpr (sizeof(SIMDType) == 1)
|
||||
return SIMD::equal<0x1b, 0x90, 0x98, 0x9b, 0x9d, 0x9e, 0x9f>(chunk);
|
||||
else
|
||||
return SIMD::equal<u'\x1b', u'\x90', u'\x98', u'\x9b', u'\x9d', u'\x9e', u'\x9f'>(chunk);
|
||||
}
|
||||
|
||||
// Find the first escape character in a string using SIMD
|
||||
template<typename Char>
|
||||
static const Char* findEscapeCharacter(const Char* start, const Char* end)
|
||||
@@ -43,8 +55,13 @@ static const Char* findEscapeCharacter(const Char* start, const Char* end)
|
||||
const auto chunk = SIMD::load(reinterpret_cast<const SIMDType*>(it));
|
||||
const auto chunkMasked = SIMD::bitAnd(chunk, escMask);
|
||||
const auto chunkIsEsc = SIMD::equal(chunkMasked, escVector);
|
||||
if (const auto index = SIMD::findFirstNonZeroIndex(chunkIsEsc))
|
||||
return it + *index;
|
||||
if (SIMD::findFirstNonZeroIndex(chunkIsEsc)) {
|
||||
// Broad mask matched 0x10-0x1F / 0x90-0x9F. Refine with exact
|
||||
// escape character comparison to filter out false positives.
|
||||
const auto exactMatch = exactEscapeMatch<SIMDType>(chunk);
|
||||
if (const auto exactIndex = SIMD::findFirstNonZeroIndex(exactMatch))
|
||||
return it + *exactIndex;
|
||||
}
|
||||
}
|
||||
|
||||
// Check remaining characters
|
||||
|
||||
@@ -797,6 +797,7 @@ JSC_DEFINE_HOST_FUNCTION(functionGenerateHeapSnapshot, (JSC::JSGlobalObject * gl
|
||||
JSValue arg0 = callFrame->argument(0);
|
||||
auto throwScope = DECLARE_THROW_SCOPE(vm);
|
||||
bool useV8 = false;
|
||||
bool useArrayBuffer = false;
|
||||
if (!arg0.isUndefined()) {
|
||||
if (arg0.isString()) {
|
||||
auto str = arg0.toWTFString(globalObject);
|
||||
@@ -813,6 +814,31 @@ JSC_DEFINE_HOST_FUNCTION(functionGenerateHeapSnapshot, (JSC::JSGlobalObject * gl
|
||||
}
|
||||
|
||||
if (useV8) {
|
||||
JSValue arg1 = callFrame->argument(1);
|
||||
if (!arg1.isUndefined()) {
|
||||
if (arg1.isString()) {
|
||||
auto str = arg1.toWTFString(globalObject);
|
||||
RETURN_IF_EXCEPTION(throwScope, {});
|
||||
if (str == "arraybuffer"_s) {
|
||||
useArrayBuffer = true;
|
||||
} else {
|
||||
throwTypeError(globalObject, throwScope, "Expected 'arraybuffer' or undefined as second argument"_s);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (useArrayBuffer) {
|
||||
JSC::BunV8HeapSnapshotBuilder builder(heapProfiler);
|
||||
auto bytes = builder.jsonBytes();
|
||||
auto released = bytes.releaseBuffer();
|
||||
auto span = released.leakSpan();
|
||||
auto buffer = ArrayBuffer::createFromBytes(std::span<const uint8_t> { span.data(), span.size() }, createSharedTask<void(void*)>([](void* p) {
|
||||
fastFree(p);
|
||||
}));
|
||||
return JSC::JSValue::encode(JSC::JSArrayBuffer::create(vm, globalObject->arrayBufferStructure(), WTF::move(buffer)));
|
||||
}
|
||||
|
||||
JSC::BunV8HeapSnapshotBuilder builder(heapProfiler);
|
||||
return JSC::JSValue::encode(jsString(vm, builder.json()));
|
||||
}
|
||||
@@ -947,7 +973,7 @@ JSC_DEFINE_HOST_FUNCTION(functionFileURLToPath, (JSC::JSGlobalObject * globalObj
|
||||
file BunObject_callback_file DontDelete|Function 1
|
||||
fileURLToPath functionFileURLToPath DontDelete|Function 1
|
||||
gc Generated::BunObject::jsGc DontDelete|Function 1
|
||||
generateHeapSnapshot functionGenerateHeapSnapshot DontDelete|Function 1
|
||||
generateHeapSnapshot functionGenerateHeapSnapshot DontDelete|Function 2
|
||||
gunzipSync BunObject_callback_gunzipSync DontDelete|Function 1
|
||||
gzipSync BunObject_callback_gzipSync DontDelete|Function 1
|
||||
hash BunObject_lazyPropCb_wrap_hash DontDelete|PropertyCallback
|
||||
@@ -995,7 +1021,7 @@ JSC_DEFINE_HOST_FUNCTION(functionFileURLToPath, (JSC::JSGlobalObject * globalObj
|
||||
stderr BunObject_lazyPropCb_wrap_stderr DontDelete|PropertyCallback
|
||||
stdin BunObject_lazyPropCb_wrap_stdin DontDelete|PropertyCallback
|
||||
stdout BunObject_lazyPropCb_wrap_stdout DontDelete|PropertyCallback
|
||||
stringWidth Generated::BunObject::jsStringWidth DontDelete|Function 2
|
||||
stringWidth BunObject_callback_stringWidth DontDelete|Function 2
|
||||
stripANSI jsFunctionBunStripANSI DontDelete|Function 1
|
||||
wrapAnsi jsFunctionBunWrapAnsi DontDelete|Function 3
|
||||
Terminal BunObject_lazyPropCb_wrap_Terminal DontDelete|PropertyCallback
|
||||
|
||||
@@ -954,6 +954,7 @@ BUN_DEFINE_HOST_FUNCTION(jsFunctionBunPluginClear, (JSC::JSGlobalObject * global
|
||||
global->onResolvePlugins.namespaces.clear();
|
||||
|
||||
delete global->onLoadPlugins.virtualModules;
|
||||
global->onLoadPlugins.virtualModules = nullptr;
|
||||
|
||||
return JSC::JSValue::encode(JSC::jsUndefined());
|
||||
}
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
// clang-format off
|
||||
#include "ModuleLoader.h"
|
||||
#include "root.h"
|
||||
#include "ModuleLoader.h"
|
||||
#include "headers-handwritten.h"
|
||||
#include "PathInlines.h"
|
||||
#include "JSCommonJSModule.h"
|
||||
|
||||
#include <JavaScriptCore/JSBoundFunction.h>
|
||||
#include <JavaScriptCore/PropertySlot.h>
|
||||
#include <JavaScriptCore/JSMap.h>
|
||||
#include <JavaScriptCore/JSString.h>
|
||||
#include <JavaScriptCore/SourceCode.h>
|
||||
|
||||
#include "ZigGlobalObject.h"
|
||||
#include "InternalModuleRegistry.h"
|
||||
@@ -85,3 +90,44 @@ extern "C" [[ZIG_EXPORT(nothrow)]] void Bun__ExposeNodeModuleGlobals(Zig::Global
|
||||
FOREACH_EXPOSED_BUILTIN_IMR(PUT_CUSTOM_GETTER_SETTER)
|
||||
#undef PUT_CUSTOM_GETTER_SETTER
|
||||
}
|
||||
|
||||
// Set up require(), module, __filename, __dirname on globalThis for the REPL.
|
||||
// Creates a CommonJS module object rooted at the given directory so require() resolves correctly.
|
||||
extern "C" [[ZIG_EXPORT(check_slow)]] void Bun__REPL__setupGlobalRequire(
|
||||
Zig::GlobalObject* globalObject,
|
||||
const unsigned char* cwdPtr,
|
||||
size_t cwdLen)
|
||||
{
|
||||
using namespace JSC;
|
||||
auto& vm = getVM(globalObject);
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
auto cwdStr = WTF::String::fromUTF8(std::span { cwdPtr, cwdLen });
|
||||
auto* filename = jsString(vm, makeString(cwdStr, PLATFORM_SEP_s, "[repl]"_s));
|
||||
auto* dirname = jsString(vm, WTF::String(cwdStr));
|
||||
|
||||
auto* moduleObject = Bun::JSCommonJSModule::create(vm,
|
||||
globalObject->CommonJSModuleObjectStructure(),
|
||||
filename, filename, dirname, SourceCode());
|
||||
moduleObject->hasEvaluated = true;
|
||||
|
||||
auto* resolveFunction = JSBoundFunction::create(vm, globalObject,
|
||||
globalObject->requireResolveFunctionUnbound(), filename,
|
||||
ArgList(), 1, globalObject->commonStrings().resolveString(globalObject),
|
||||
makeSource("resolve"_s, SourceOrigin(), SourceTaintedOrigin::Untainted));
|
||||
RETURN_IF_EXCEPTION(scope, );
|
||||
|
||||
auto* requireFunction = JSBoundFunction::create(vm, globalObject,
|
||||
globalObject->requireFunctionUnbound(), moduleObject,
|
||||
ArgList(), 1, globalObject->commonStrings().requireString(globalObject),
|
||||
makeSource("require"_s, SourceOrigin(), SourceTaintedOrigin::Untainted));
|
||||
RETURN_IF_EXCEPTION(scope, );
|
||||
|
||||
requireFunction->putDirect(vm, vm.propertyNames->resolve, resolveFunction, 0);
|
||||
moduleObject->putDirect(vm, WebCore::clientData(vm)->builtinNames().requirePublicName(), requireFunction, 0);
|
||||
|
||||
globalObject->putDirect(vm, WebCore::builtinNames(vm).requirePublicName(), requireFunction, 0);
|
||||
globalObject->putDirect(vm, Identifier::fromString(vm, "module"_s), moduleObject, 0);
|
||||
globalObject->putDirect(vm, Identifier::fromString(vm, "__filename"_s), filename, 0);
|
||||
globalObject->putDirect(vm, Identifier::fromString(vm, "__dirname"_s), dirname, 0);
|
||||
}
|
||||
|
||||
@@ -641,13 +641,16 @@ JSC_DEFINE_CUSTOM_GETTER(errorInstanceLazyStackCustomGetter, (JSGlobalObject * g
|
||||
OrdinalNumber column;
|
||||
String sourceURL;
|
||||
auto stackTrace = errorObject->stackTrace();
|
||||
if (stackTrace == nullptr) {
|
||||
return JSValue::encode(jsUndefined());
|
||||
}
|
||||
|
||||
JSValue result = computeErrorInfoToJSValue(vm, *stackTrace, line, column, sourceURL, errorObject, nullptr);
|
||||
stackTrace->clear();
|
||||
errorObject->setStackFrames(vm, {});
|
||||
JSValue result;
|
||||
if (stackTrace == nullptr) {
|
||||
WTF::Vector<JSC::StackFrame> emptyTrace;
|
||||
result = computeErrorInfoToJSValue(vm, emptyTrace, line, column, sourceURL, errorObject, nullptr);
|
||||
} else {
|
||||
result = computeErrorInfoToJSValue(vm, *stackTrace, line, column, sourceURL, errorObject, nullptr);
|
||||
stackTrace->clear();
|
||||
errorObject->setStackFrames(vm, {});
|
||||
}
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
errorObject->putDirect(vm, vm.propertyNames->stack, result, 0);
|
||||
return JSValue::encode(result);
|
||||
@@ -687,17 +690,27 @@ JSC_DEFINE_HOST_FUNCTION(errorConstructorFuncCaptureStackTrace, (JSC::JSGlobalOb
|
||||
JSCStackTrace::getFramesForCaller(vm, callFrame, errorObject, caller, stackTrace, stackTraceLimit);
|
||||
|
||||
if (auto* instance = jsDynamicCast<JSC::ErrorInstance*>(errorObject)) {
|
||||
// Force materialization before replacing the stack frames, so that JSC's
|
||||
// internal lazy error info mechanism doesn't later see the replaced (possibly empty)
|
||||
// stack trace and fail to create the stack property.
|
||||
if (!instance->hasMaterializedErrorInfo())
|
||||
instance->materializeErrorInfoIfNeeded(vm);
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
|
||||
instance->setStackFrames(vm, WTF::move(stackTrace));
|
||||
if (instance->hasMaterializedErrorInfo()) {
|
||||
|
||||
{
|
||||
const auto& propertyName = vm.propertyNames->stack;
|
||||
VM::DeletePropertyModeScope scope(vm, VM::DeletePropertyMode::IgnoreConfigurable);
|
||||
VM::DeletePropertyModeScope deleteScope(vm, VM::DeletePropertyMode::IgnoreConfigurable);
|
||||
DeletePropertySlot slot;
|
||||
JSObject::deleteProperty(instance, globalObject, propertyName, slot);
|
||||
if (auto* zigGlobalObject = jsDynamicCast<Zig::GlobalObject*>(globalObject)) {
|
||||
instance->putDirectCustomAccessor(vm, vm.propertyNames->stack, zigGlobalObject->m_lazyStackCustomGetterSetter.get(zigGlobalObject), JSC::PropertyAttribute::CustomAccessor | 0);
|
||||
} else {
|
||||
instance->putDirectCustomAccessor(vm, vm.propertyNames->stack, CustomGetterSetter::create(vm, errorInstanceLazyStackCustomGetter, errorInstanceLazyStackCustomSetter), JSC::PropertyAttribute::CustomAccessor | 0);
|
||||
}
|
||||
}
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
|
||||
if (auto* zigGlobalObject = jsDynamicCast<Zig::GlobalObject*>(globalObject)) {
|
||||
instance->putDirectCustomAccessor(vm, vm.propertyNames->stack, zigGlobalObject->m_lazyStackCustomGetterSetter.get(zigGlobalObject), JSC::PropertyAttribute::CustomAccessor | 0);
|
||||
} else {
|
||||
instance->putDirectCustomAccessor(vm, vm.propertyNames->stack, CustomGetterSetter::create(vm, errorInstanceLazyStackCustomGetter, errorInstanceLazyStackCustomSetter), JSC::PropertyAttribute::CustomAccessor | 0);
|
||||
}
|
||||
} else {
|
||||
OrdinalNumber line;
|
||||
|
||||
@@ -144,9 +144,15 @@ static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES functionFuzzilli(JSC::JSGlob
|
||||
WTF::String output = arg1.toWTFString(globalObject);
|
||||
RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(JSC::jsUndefined()));
|
||||
|
||||
FILE* f = fdopen(REPRL_DWFD, "w");
|
||||
fprintf(f, "%s\n", output.utf8().data());
|
||||
fflush(f);
|
||||
// Use a static FILE* to avoid repeatedly calling fdopen (which
|
||||
// duplicates the descriptor and leaks) and to gracefully handle
|
||||
// the case where REPRL_DWFD is not open (i.e. running outside
|
||||
// the fuzzer harness).
|
||||
static FILE* f = fdopen(REPRL_DWFD, "w");
|
||||
if (f) {
|
||||
fprintf(f, "%s\n", output.utf8().data());
|
||||
fflush(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1164,6 +1164,7 @@ void JSCommonJSModule::visitChildrenImpl(JSCell* cell, Visitor& visitor)
|
||||
visitor.appendHidden(thisObject->m_dirname);
|
||||
visitor.appendHidden(thisObject->m_paths);
|
||||
visitor.appendHidden(thisObject->m_overriddenParent);
|
||||
visitor.appendHidden(thisObject->m_overriddenCompile);
|
||||
visitor.appendHidden(thisObject->m_childrenValue);
|
||||
visitor.appendValues(thisObject->m_children.begin(), thisObject->m_children.size());
|
||||
}
|
||||
|
||||
@@ -202,20 +202,23 @@ pub const JSType = enum(u8) {
|
||||
/// Global context for Promise.all() (new in recent WebKit).
|
||||
PromiseAllGlobalContext = 27,
|
||||
|
||||
/// Microtask dispatcher for promise/microtask queue management.
|
||||
JSMicrotaskDispatcher = 28,
|
||||
|
||||
/// Base JavaScript object type.
|
||||
/// ```js
|
||||
/// {}
|
||||
/// new Object()
|
||||
/// ```
|
||||
Object = 28,
|
||||
Object = 29,
|
||||
|
||||
/// Optimized object type for object literals with fixed properties.
|
||||
/// ```js
|
||||
/// { a: 1, b: 2 }
|
||||
/// ```
|
||||
FinalObject = 29,
|
||||
FinalObject = 30,
|
||||
|
||||
JSCallee = 30,
|
||||
JSCallee = 31,
|
||||
|
||||
/// JavaScript function object created from JavaScript source code.
|
||||
/// ```js
|
||||
@@ -225,7 +228,7 @@ pub const JSType = enum(u8) {
|
||||
/// method() {}
|
||||
/// }
|
||||
/// ```
|
||||
JSFunction = 31,
|
||||
JSFunction = 32,
|
||||
|
||||
/// Built-in function implemented in native code.
|
||||
/// ```js
|
||||
@@ -234,23 +237,23 @@ pub const JSType = enum(u8) {
|
||||
/// parseInt
|
||||
/// console.log
|
||||
/// ```
|
||||
InternalFunction = 32,
|
||||
InternalFunction = 33,
|
||||
|
||||
NullSetterFunction = 33,
|
||||
NullSetterFunction = 34,
|
||||
|
||||
/// Boxed Boolean object.
|
||||
/// ```js
|
||||
/// new Boolean(true)
|
||||
/// new Boolean(false)
|
||||
/// ```
|
||||
BooleanObject = 34,
|
||||
BooleanObject = 35,
|
||||
|
||||
/// Boxed Number object.
|
||||
/// ```js
|
||||
/// new Number(42)
|
||||
/// new Number(3.14)
|
||||
/// ```
|
||||
NumberObject = 35,
|
||||
NumberObject = 36,
|
||||
|
||||
/// JavaScript Error object and its subclasses.
|
||||
/// ```js
|
||||
@@ -258,9 +261,9 @@ pub const JSType = enum(u8) {
|
||||
/// new TypeError()
|
||||
/// throw new RangeError()
|
||||
/// ```
|
||||
ErrorInstance = 36,
|
||||
ErrorInstance = 37,
|
||||
|
||||
GlobalProxy = 37,
|
||||
GlobalProxy = 38,
|
||||
|
||||
/// Arguments object for function parameters.
|
||||
/// ```js
|
||||
@@ -269,10 +272,10 @@ pub const JSType = enum(u8) {
|
||||
/// console.log(arguments.length);
|
||||
/// }
|
||||
/// ```
|
||||
DirectArguments = 38,
|
||||
DirectArguments = 39,
|
||||
|
||||
ScopedArguments = 39,
|
||||
ClonedArguments = 40,
|
||||
ScopedArguments = 40,
|
||||
ClonedArguments = 41,
|
||||
|
||||
/// JavaScript Array object.
|
||||
/// ```js
|
||||
@@ -281,94 +284,94 @@ pub const JSType = enum(u8) {
|
||||
/// new Array(10)
|
||||
/// Array.from(iterable)
|
||||
/// ```
|
||||
Array = 41,
|
||||
Array = 42,
|
||||
|
||||
/// Array subclass created through class extension.
|
||||
/// ```js
|
||||
/// class MyArray extends Array {}
|
||||
/// const arr = new MyArray();
|
||||
/// ```
|
||||
DerivedArray = 42,
|
||||
DerivedArray = 43,
|
||||
|
||||
/// ArrayBuffer for binary data storage.
|
||||
/// ```js
|
||||
/// new ArrayBuffer(1024)
|
||||
/// ```
|
||||
ArrayBuffer = 43,
|
||||
ArrayBuffer = 44,
|
||||
|
||||
/// Typed array for 8-bit signed integers.
|
||||
/// ```js
|
||||
/// new Int8Array(buffer)
|
||||
/// new Int8Array([1, -1, 127])
|
||||
/// ```
|
||||
Int8Array = 44,
|
||||
Int8Array = 45,
|
||||
|
||||
/// Typed array for 8-bit unsigned integers.
|
||||
/// ```js
|
||||
/// new Uint8Array(buffer)
|
||||
/// new Uint8Array([0, 255])
|
||||
/// ```
|
||||
Uint8Array = 45,
|
||||
Uint8Array = 46,
|
||||
|
||||
/// Typed array for 8-bit unsigned integers with clamping.
|
||||
/// ```js
|
||||
/// new Uint8ClampedArray([0, 300]) // 300 becomes 255
|
||||
/// ```
|
||||
Uint8ClampedArray = 46,
|
||||
Uint8ClampedArray = 47,
|
||||
|
||||
/// Typed array for 16-bit signed integers.
|
||||
/// ```js
|
||||
/// new Int16Array(buffer)
|
||||
/// ```
|
||||
Int16Array = 47,
|
||||
Int16Array = 48,
|
||||
|
||||
/// Typed array for 16-bit unsigned integers.
|
||||
/// ```js
|
||||
/// new Uint16Array(buffer)
|
||||
/// ```
|
||||
Uint16Array = 48,
|
||||
Uint16Array = 49,
|
||||
|
||||
/// Typed array for 32-bit signed integers.
|
||||
/// ```js
|
||||
/// new Int32Array(buffer)
|
||||
/// ```
|
||||
Int32Array = 49,
|
||||
Int32Array = 50,
|
||||
|
||||
/// Typed array for 32-bit unsigned integers.
|
||||
/// ```js
|
||||
/// new Uint32Array(buffer)
|
||||
/// ```
|
||||
Uint32Array = 50,
|
||||
Uint32Array = 51,
|
||||
|
||||
/// Typed array for 16-bit floating point numbers.
|
||||
/// ```js
|
||||
/// new Float16Array(buffer)
|
||||
/// ```
|
||||
Float16Array = 51,
|
||||
Float16Array = 52,
|
||||
|
||||
/// Typed array for 32-bit floating point numbers.
|
||||
/// ```js
|
||||
/// new Float32Array(buffer)
|
||||
/// ```
|
||||
Float32Array = 52,
|
||||
Float32Array = 53,
|
||||
|
||||
/// Typed array for 64-bit floating point numbers.
|
||||
/// ```js
|
||||
/// new Float64Array(buffer)
|
||||
/// ```
|
||||
Float64Array = 53,
|
||||
Float64Array = 54,
|
||||
|
||||
/// Typed array for 64-bit signed BigInt values.
|
||||
/// ```js
|
||||
/// new BigInt64Array([123n, -456n])
|
||||
/// ```
|
||||
BigInt64Array = 54,
|
||||
BigInt64Array = 55,
|
||||
|
||||
/// Typed array for 64-bit unsigned BigInt values.
|
||||
/// ```js
|
||||
/// new BigUint64Array([123n, 456n])
|
||||
/// ```
|
||||
BigUint64Array = 55,
|
||||
BigUint64Array = 56,
|
||||
|
||||
/// DataView for flexible binary data access.
|
||||
/// ```js
|
||||
@@ -376,7 +379,7 @@ pub const JSType = enum(u8) {
|
||||
/// view.getInt32(0)
|
||||
/// view.setFloat64(8, 3.14)
|
||||
/// ```
|
||||
DataView = 56,
|
||||
DataView = 57,
|
||||
|
||||
/// Global object containing all global variables and functions.
|
||||
/// ```js
|
||||
@@ -384,12 +387,12 @@ pub const JSType = enum(u8) {
|
||||
/// window // in browsers
|
||||
/// global // in Node.js
|
||||
/// ```
|
||||
GlobalObject = 57,
|
||||
GlobalObject = 58,
|
||||
|
||||
GlobalLexicalEnvironment = 58,
|
||||
LexicalEnvironment = 59,
|
||||
ModuleEnvironment = 60,
|
||||
StrictEvalActivation = 61,
|
||||
GlobalLexicalEnvironment = 59,
|
||||
LexicalEnvironment = 60,
|
||||
ModuleEnvironment = 61,
|
||||
StrictEvalActivation = 62,
|
||||
|
||||
/// Scope object for with statements.
|
||||
/// ```js
|
||||
@@ -397,19 +400,19 @@ pub const JSType = enum(u8) {
|
||||
/// prop; // looks up prop in obj first
|
||||
/// }
|
||||
/// ```
|
||||
WithScope = 62,
|
||||
WithScope = 63,
|
||||
|
||||
AsyncDisposableStack = 63,
|
||||
DisposableStack = 64,
|
||||
AsyncDisposableStack = 64,
|
||||
DisposableStack = 65,
|
||||
|
||||
/// Namespace object for ES6 modules.
|
||||
/// ```js
|
||||
/// import * as ns from 'module';
|
||||
/// ns.exportedFunction()
|
||||
/// ```
|
||||
ModuleNamespaceObject = 65,
|
||||
ModuleNamespaceObject = 66,
|
||||
|
||||
ShadowRealm = 66,
|
||||
ShadowRealm = 67,
|
||||
|
||||
/// Regular expression object.
|
||||
/// ```js
|
||||
@@ -417,7 +420,7 @@ pub const JSType = enum(u8) {
|
||||
/// new RegExp('pattern', 'flags')
|
||||
/// /abc/gi
|
||||
/// ```
|
||||
RegExpObject = 67,
|
||||
RegExpObject = 68,
|
||||
|
||||
/// JavaScript Date object for date/time operations.
|
||||
/// ```js
|
||||
@@ -425,7 +428,7 @@ pub const JSType = enum(u8) {
|
||||
/// new Date('2023-01-01')
|
||||
/// Date.now()
|
||||
/// ```
|
||||
JSDate = 68,
|
||||
JSDate = 69,
|
||||
|
||||
/// Proxy object that intercepts operations on another object.
|
||||
/// ```js
|
||||
@@ -433,7 +436,7 @@ pub const JSType = enum(u8) {
|
||||
/// get(obj, prop) { return obj[prop]; }
|
||||
/// })
|
||||
/// ```
|
||||
ProxyObject = 69,
|
||||
ProxyObject = 70,
|
||||
|
||||
/// Generator object created by generator functions.
|
||||
/// ```js
|
||||
@@ -441,7 +444,7 @@ pub const JSType = enum(u8) {
|
||||
/// const g = gen();
|
||||
/// g.next()
|
||||
/// ```
|
||||
Generator = 70,
|
||||
Generator = 71,
|
||||
|
||||
/// Async generator object for asynchronous iteration.
|
||||
/// ```js
|
||||
@@ -449,17 +452,17 @@ pub const JSType = enum(u8) {
|
||||
/// yield await promise;
|
||||
/// }
|
||||
/// ```
|
||||
AsyncGenerator = 71,
|
||||
AsyncGenerator = 72,
|
||||
|
||||
/// Iterator for Array objects.
|
||||
/// ```js
|
||||
/// [1,2,3][Symbol.iterator]()
|
||||
/// for (const x of array) {}
|
||||
/// ```
|
||||
JSArrayIterator = 72,
|
||||
JSArrayIterator = 73,
|
||||
|
||||
Iterator = 73,
|
||||
IteratorHelper = 74,
|
||||
Iterator = 74,
|
||||
IteratorHelper = 75,
|
||||
|
||||
/// Iterator for Map objects.
|
||||
/// ```js
|
||||
@@ -468,32 +471,32 @@ pub const JSType = enum(u8) {
|
||||
/// map.entries()
|
||||
/// for (const [k,v] of map) {}
|
||||
/// ```
|
||||
MapIterator = 75,
|
||||
MapIterator = 76,
|
||||
|
||||
/// Iterator for Set objects.
|
||||
/// ```js
|
||||
/// set.values()
|
||||
/// for (const value of set) {}
|
||||
/// ```
|
||||
SetIterator = 76,
|
||||
SetIterator = 77,
|
||||
|
||||
/// Iterator for String objects.
|
||||
/// ```js
|
||||
/// 'hello'[Symbol.iterator]()
|
||||
/// for (const char of string) {}
|
||||
/// ```
|
||||
StringIterator = 77,
|
||||
StringIterator = 78,
|
||||
|
||||
WrapForValidIterator = 78,
|
||||
WrapForValidIterator = 79,
|
||||
|
||||
/// Iterator for RegExp string matching.
|
||||
/// ```js
|
||||
/// 'abc'.matchAll(/./g)
|
||||
/// for (const match of string.matchAll(regex)) {}
|
||||
/// ```
|
||||
RegExpStringIterator = 79,
|
||||
RegExpStringIterator = 80,
|
||||
|
||||
AsyncFromSyncIterator = 80,
|
||||
AsyncFromSyncIterator = 81,
|
||||
|
||||
/// JavaScript Promise object for asynchronous operations.
|
||||
/// ```js
|
||||
@@ -501,7 +504,7 @@ pub const JSType = enum(u8) {
|
||||
/// Promise.resolve(42)
|
||||
/// async function foo() { await promise; }
|
||||
/// ```
|
||||
JSPromise = 81,
|
||||
JSPromise = 82,
|
||||
|
||||
/// JavaScript Map object for key-value storage.
|
||||
/// ```js
|
||||
@@ -509,7 +512,7 @@ pub const JSType = enum(u8) {
|
||||
/// map.set(key, value)
|
||||
/// map.get(key)
|
||||
/// ```
|
||||
Map = 82,
|
||||
Map = 83,
|
||||
|
||||
/// JavaScript Set object for unique value storage.
|
||||
/// ```js
|
||||
@@ -517,34 +520,34 @@ pub const JSType = enum(u8) {
|
||||
/// set.add(value)
|
||||
/// set.has(value)
|
||||
/// ```
|
||||
Set = 83,
|
||||
Set = 84,
|
||||
|
||||
/// WeakMap for weak key-value references.
|
||||
/// ```js
|
||||
/// new WeakMap()
|
||||
/// weakMap.set(object, value)
|
||||
/// ```
|
||||
WeakMap = 84,
|
||||
WeakMap = 85,
|
||||
|
||||
/// WeakSet for weak value references.
|
||||
/// ```js
|
||||
/// new WeakSet()
|
||||
/// weakSet.add(object)
|
||||
/// ```
|
||||
WeakSet = 85,
|
||||
WeakSet = 86,
|
||||
|
||||
WebAssemblyModule = 86,
|
||||
WebAssemblyInstance = 87,
|
||||
WebAssemblyGCObject = 88,
|
||||
WebAssemblyModule = 87,
|
||||
WebAssemblyInstance = 88,
|
||||
WebAssemblyGCObject = 89,
|
||||
|
||||
/// Boxed String object.
|
||||
/// ```js
|
||||
/// new String("hello")
|
||||
/// ```
|
||||
StringObject = 89,
|
||||
StringObject = 90,
|
||||
|
||||
DerivedStringObject = 90,
|
||||
InternalFieldTuple = 91,
|
||||
DerivedStringObject = 91,
|
||||
InternalFieldTuple = 92,
|
||||
|
||||
MaxJS = 0b11111111,
|
||||
Event = 0b11101111,
|
||||
|
||||
@@ -560,6 +560,8 @@ JSC_DEFINE_CUSTOM_GETTER(jsX509CertificateGetter_infoAccess, (JSGlobalObject * g
|
||||
return JSValue::encode(jsUndefined());
|
||||
|
||||
BUF_MEM* bptr = bio;
|
||||
if (!bptr)
|
||||
return JSValue::encode(jsUndefined());
|
||||
return JSValue::encode(undefinedIfEmpty(jsString(vm, String::fromUTF8(std::span(bptr->data, bptr->length)))));
|
||||
}
|
||||
|
||||
@@ -602,20 +604,10 @@ JSC_DEFINE_CUSTOM_GETTER(jsX509CertificateGetter_issuerCertificate, (JSGlobalObj
|
||||
return {};
|
||||
}
|
||||
|
||||
auto issuerCert = thisObject->view().getIssuer();
|
||||
if (!issuerCert)
|
||||
return JSValue::encode(jsUndefined());
|
||||
|
||||
auto bio = issuerCert.get();
|
||||
|
||||
BUF_MEM* bptr = nullptr;
|
||||
BIO_get_mem_ptr(bio, &bptr);
|
||||
std::span<const uint8_t> span(reinterpret_cast<const uint8_t*>(bptr->data), bptr->length);
|
||||
auto* zigGlobalObject = defaultGlobalObject(globalObject);
|
||||
auto* structure = zigGlobalObject->m_JSX509CertificateClassStructure.get(zigGlobalObject);
|
||||
auto jsIssuerCert = JSX509Certificate::create(vm, structure, globalObject, span);
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
return JSValue::encode(jsIssuerCert);
|
||||
// issuerCertificate is only available when the certificate was obtained from
|
||||
// a TLS connection with a peer certificate chain. For certificates parsed
|
||||
// directly from PEM/DER data, it is always undefined (matching Node.js behavior).
|
||||
return JSValue::encode(jsUndefined());
|
||||
}
|
||||
|
||||
JSC_DEFINE_CUSTOM_GETTER(jsX509CertificateGetter_publicKey, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName))
|
||||
|
||||
@@ -570,6 +570,11 @@ static void writeFetchHeadersToUWSResponse(WebCore::FetchHeaders& headers, uWS::
|
||||
if (header.key == WebCore::HTTPHeaderName::Date) {
|
||||
data->state |= uWS::HttpResponseData<isSSL>::HTTP_WROTE_DATE_HEADER;
|
||||
}
|
||||
|
||||
// Prevent automatic Transfer-Encoding: chunked insertion when user provides one
|
||||
if (header.key == WebCore::HTTPHeaderName::TransferEncoding) {
|
||||
data->state |= uWS::HttpResponseData<isSSL>::HTTP_WROTE_TRANSFER_ENCODING_HEADER;
|
||||
}
|
||||
writeResponseHeader<isSSL>(res, name, value);
|
||||
}
|
||||
|
||||
@@ -642,6 +647,7 @@ static void NodeHTTPServer__writeHead(
|
||||
String key = propertyNames[i].string();
|
||||
String value = headerValue.toWTFString(globalObject);
|
||||
RETURN_IF_EXCEPTION(scope, void());
|
||||
|
||||
writeResponseHeader<isSSL>(response, key, value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -703,6 +703,17 @@ void NodeVMSpecialSandbox::finishCreation(VM& vm)
|
||||
|
||||
const JSC::ClassInfo NodeVMSpecialSandbox::s_info = { "NodeVMSpecialSandbox"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NodeVMSpecialSandbox) };
|
||||
|
||||
template<typename Visitor>
|
||||
void NodeVMSpecialSandbox::visitChildrenImpl(JSCell* cell, Visitor& visitor)
|
||||
{
|
||||
auto* thisObject = jsCast<NodeVMSpecialSandbox*>(cell);
|
||||
ASSERT_GC_OBJECT_INHERITS(thisObject, info());
|
||||
Base::visitChildren(thisObject, visitor);
|
||||
visitor.append(thisObject->m_parentGlobal);
|
||||
}
|
||||
|
||||
DEFINE_VISIT_CHILDREN(NodeVMSpecialSandbox);
|
||||
|
||||
NodeVMGlobalObject::NodeVMGlobalObject(JSC::VM& vm, JSC::Structure* structure, NodeVMContextOptions contextOptions, JSValue importer)
|
||||
: Base(vm, structure, &globalObjectMethodTable())
|
||||
, m_dynamicImportCallback(vm, this, importer)
|
||||
|
||||
@@ -85,6 +85,7 @@ public:
|
||||
static NodeVMSpecialSandbox* create(VM& vm, Structure* structure, NodeVMGlobalObject* globalObject);
|
||||
|
||||
DECLARE_INFO;
|
||||
DECLARE_VISIT_CHILDREN;
|
||||
template<typename, JSC::SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm);
|
||||
static Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype);
|
||||
|
||||
|
||||
@@ -1061,9 +1061,7 @@ JSC_DEFINE_HOST_FUNCTION(functionQueueMicrotask,
|
||||
|
||||
auto* globalObject = defaultGlobalObject(lexicalGlobalObject);
|
||||
JSC::JSValue asyncContext = globalObject->m_asyncContextData.get()->getInternalField(0);
|
||||
auto function = globalObject->performMicrotaskFunction();
|
||||
#if ASSERT_ENABLED
|
||||
ASSERT_WITH_MESSAGE(function, "Invalid microtask function");
|
||||
ASSERT_WITH_MESSAGE(!callback.isEmpty(), "Invalid microtask callback");
|
||||
#endif
|
||||
|
||||
@@ -1071,10 +1069,8 @@ JSC_DEFINE_HOST_FUNCTION(functionQueueMicrotask,
|
||||
asyncContext = JSC::jsUndefined();
|
||||
}
|
||||
|
||||
// BunPerformMicrotaskJob accepts a variable number of arguments (up to: performMicrotask, job, asyncContext, arg0, arg1).
|
||||
// The runtime inspects argumentCount to determine which arguments are present, so callers may pass only the subset they need.
|
||||
// Here we pass: function, callback, asyncContext.
|
||||
JSC::QueuedTask task { nullptr, JSC::InternalMicrotask::BunPerformMicrotaskJob, 0, globalObject, function, callback, asyncContext };
|
||||
// BunPerformMicrotaskJob: callback, asyncContext
|
||||
JSC::QueuedTask task { nullptr, JSC::InternalMicrotask::BunPerformMicrotaskJob, 0, globalObject, callback, asyncContext };
|
||||
globalObject->vm().queueMicrotask(WTF::move(task));
|
||||
|
||||
return JSC::JSValue::encode(JSC::jsUndefined());
|
||||
@@ -1554,63 +1550,6 @@ extern "C" napi_env ZigGlobalObject__makeNapiEnvForFFI(Zig::GlobalObject* global
|
||||
return globalObject->makeNapiEnvForFFI();
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(jsFunctionPerformMicrotask, (JSGlobalObject * globalObject, CallFrame* callframe))
|
||||
{
|
||||
auto& vm = JSC::getVM(globalObject);
|
||||
auto scope = DECLARE_TOP_EXCEPTION_SCOPE(vm);
|
||||
|
||||
auto job = callframe->argument(0);
|
||||
if (!job || job.isUndefinedOrNull()) [[unlikely]] {
|
||||
return JSValue::encode(jsUndefined());
|
||||
}
|
||||
|
||||
auto callData = JSC::getCallData(job);
|
||||
MarkedArgumentBuffer arguments;
|
||||
|
||||
if (callData.type == CallData::Type::None) [[unlikely]] {
|
||||
return JSValue::encode(jsUndefined());
|
||||
}
|
||||
|
||||
JSValue result;
|
||||
WTF::NakedPtr<JSC::Exception> exceptionPtr;
|
||||
|
||||
JSValue restoreAsyncContext = {};
|
||||
InternalFieldTuple* asyncContextData = nullptr;
|
||||
auto setAsyncContext = callframe->argument(1);
|
||||
if (!setAsyncContext.isUndefined()) {
|
||||
asyncContextData = globalObject->m_asyncContextData.get();
|
||||
restoreAsyncContext = asyncContextData->getInternalField(0);
|
||||
asyncContextData->putInternalField(vm, 0, setAsyncContext);
|
||||
}
|
||||
|
||||
size_t argCount = callframe->argumentCount();
|
||||
switch (argCount) {
|
||||
case 3: {
|
||||
arguments.append(callframe->uncheckedArgument(2));
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
arguments.append(callframe->uncheckedArgument(2));
|
||||
arguments.append(callframe->uncheckedArgument(3));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
JSC::profiledCall(globalObject, ProfilingReason::API, job, callData, jsUndefined(), arguments, exceptionPtr);
|
||||
|
||||
if (asyncContextData) {
|
||||
asyncContextData->putInternalField(vm, 0, restoreAsyncContext);
|
||||
}
|
||||
|
||||
if (auto* exception = exceptionPtr.get()) {
|
||||
Bun__reportUnhandledError(globalObject, JSValue::encode(exception));
|
||||
}
|
||||
|
||||
return JSValue::encode(jsUndefined());
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(jsFunctionPerformMicrotaskVariadic, (JSGlobalObject * globalObject, CallFrame* callframe))
|
||||
{
|
||||
auto& vm = JSC::getVM(globalObject);
|
||||
@@ -1940,11 +1879,6 @@ void GlobalObject::finishCreation(VM& vm)
|
||||
scope.assertNoExceptionExceptTermination();
|
||||
init.set(subclassStructure);
|
||||
});
|
||||
m_performMicrotaskFunction.initLater(
|
||||
[](const Initializer<JSFunction>& init) {
|
||||
init.set(JSFunction::create(init.vm, init.owner, 4, "performMicrotask"_s, jsFunctionPerformMicrotask, ImplementationVisibility::Public));
|
||||
});
|
||||
|
||||
m_performMicrotaskVariadicFunction.initLater(
|
||||
[](const Initializer<JSFunction>& init) {
|
||||
init.set(JSFunction::create(init.vm, init.owner, 4, "performMicrotaskVariadic"_s, jsFunctionPerformMicrotaskVariadic, ImplementationVisibility::Public));
|
||||
@@ -2516,6 +2450,7 @@ JSC_DEFINE_CUSTOM_GETTER(getConsoleConstructor, (JSGlobalObject * globalObject,
|
||||
if (returnedException) {
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
throwException(globalObject, scope, returnedException.get());
|
||||
return {};
|
||||
}
|
||||
console->putDirect(vm, property, result, 0);
|
||||
return JSValue::encode(result);
|
||||
|
||||
@@ -272,7 +272,6 @@ public:
|
||||
|
||||
JSC::JSObject* performanceObject() const { return m_performanceObject.getInitializedOnMainThread(this); }
|
||||
|
||||
JSC::JSFunction* performMicrotaskFunction() const { return m_performMicrotaskFunction.getInitializedOnMainThread(this); }
|
||||
JSC::JSFunction* performMicrotaskVariadicFunction() const { return m_performMicrotaskVariadicFunction.getInitializedOnMainThread(this); }
|
||||
|
||||
JSC::Structure* utilInspectOptionsStructure() const { return m_utilInspectOptionsStructure.getInitializedOnMainThread(this); }
|
||||
@@ -569,7 +568,6 @@ public:
|
||||
V(private, LazyPropertyOfGlobalObject<Structure>, m_jsonlParseResultStructure) \
|
||||
V(private, LazyPropertyOfGlobalObject<Structure>, m_pathParsedObjectStructure) \
|
||||
V(private, LazyPropertyOfGlobalObject<Structure>, m_pendingVirtualModuleResultStructure) \
|
||||
V(private, LazyPropertyOfGlobalObject<JSFunction>, m_performMicrotaskFunction) \
|
||||
V(private, LazyPropertyOfGlobalObject<JSFunction>, m_nativeMicrotaskTrampoline) \
|
||||
V(private, LazyPropertyOfGlobalObject<JSFunction>, m_performMicrotaskVariadicFunction) \
|
||||
V(private, LazyPropertyOfGlobalObject<JSFunction>, m_utilInspectFunction) \
|
||||
|
||||
@@ -2450,13 +2450,20 @@ void JSC__JSObject__putRecord(JSC::JSObject* object, JSC::JSGlobalObject* global
|
||||
descriptor.setValue(JSC::jsString(global->vm(), Zig::toStringCopy(values[0])));
|
||||
} else {
|
||||
|
||||
// Pre-convert all strings to JSValues before entering ObjectInitializationScope,
|
||||
// since jsString() allocates GC cells which is not allowed inside the scope.
|
||||
MarkedArgumentBuffer strings;
|
||||
for (size_t i = 0; i < valuesLen; ++i) {
|
||||
strings.append(JSC::jsString(global->vm(), Zig::toStringCopy(values[i])));
|
||||
}
|
||||
|
||||
JSC::JSArray* array = nullptr;
|
||||
{
|
||||
JSC::ObjectInitializationScope initializationScope(global->vm());
|
||||
if ((array = JSC::JSArray::tryCreateUninitializedRestricted(initializationScope, nullptr, global->arrayStructureForIndexingTypeDuringAllocation(JSC::ArrayWithContiguous), valuesLen))) {
|
||||
|
||||
for (size_t i = 0; i < valuesLen; ++i) {
|
||||
array->initializeIndexWithoutBarrier(initializationScope, i, JSC::jsString(global->vm(), Zig::toStringCopy(values[i])));
|
||||
array->initializeIndexWithoutBarrier(initializationScope, i, strings.at(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2490,6 +2497,13 @@ void JSC__JSValue__putRecord(JSC::EncodedJSValue objectValue, JSC::JSGlobalObjec
|
||||
descriptor.setValue(JSC::jsString(global->vm(), Zig::toString(values[0])));
|
||||
} else {
|
||||
|
||||
// Pre-convert all strings to JSValues before entering ObjectInitializationScope,
|
||||
// since jsString() allocates GC cells which is not allowed inside the scope.
|
||||
MarkedArgumentBuffer strings;
|
||||
for (size_t i = 0; i < valuesLen; ++i) {
|
||||
strings.append(JSC::jsString(global->vm(), Zig::toString(values[i])));
|
||||
}
|
||||
|
||||
JSC::JSArray* array = nullptr;
|
||||
{
|
||||
JSC::ObjectInitializationScope initializationScope(global->vm());
|
||||
@@ -2500,7 +2514,7 @@ void JSC__JSValue__putRecord(JSC::EncodedJSValue objectValue, JSC::JSGlobalObjec
|
||||
|
||||
for (size_t i = 0; i < valuesLen; ++i) {
|
||||
array->initializeIndexWithoutBarrier(
|
||||
initializationScope, i, JSC::jsString(global->vm(), Zig::toString(values[i])));
|
||||
initializationScope, i, strings.at(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3026,22 +3040,20 @@ JSC::EncodedJSValue JSC__JSValue__fromEntries(JSC::JSGlobalObject* globalObject,
|
||||
return JSC::JSValue::encode(JSC::constructEmptyObject(globalObject));
|
||||
}
|
||||
|
||||
JSC::JSObject* object = nullptr;
|
||||
{
|
||||
JSC::ObjectInitializationScope initializationScope(vm);
|
||||
object = JSC::constructEmptyObject(globalObject, globalObject->objectPrototype(), std::min(static_cast<unsigned int>(initialCapacity), JSFinalObject::maxInlineCapacity));
|
||||
JSC::JSObject* object = JSC::constructEmptyObject(globalObject, globalObject->objectPrototype(), std::min(static_cast<unsigned int>(initialCapacity), JSFinalObject::maxInlineCapacity));
|
||||
|
||||
if (!clone) {
|
||||
for (size_t i = 0; i < initialCapacity; ++i) {
|
||||
object->putDirect(
|
||||
vm, JSC::PropertyName(JSC::Identifier::fromString(vm, Zig::toString(keys[i]))),
|
||||
Zig::toJSStringGC(values[i], globalObject), 0);
|
||||
}
|
||||
} else {
|
||||
for (size_t i = 0; i < initialCapacity; ++i) {
|
||||
object->putDirect(vm, JSC::PropertyName(Zig::toIdentifier(keys[i], globalObject)),
|
||||
Zig::toJSStringGC(values[i], globalObject), 0);
|
||||
}
|
||||
if (!clone) {
|
||||
for (size_t i = 0; i < initialCapacity; ++i) {
|
||||
object->putDirect(
|
||||
vm, JSC::PropertyName(JSC::Identifier::fromString(vm, Zig::toString(keys[i]))),
|
||||
Zig::toJSStringGC(values[i], globalObject), 0);
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
}
|
||||
} else {
|
||||
for (size_t i = 0; i < initialCapacity; ++i) {
|
||||
object->putDirect(vm, JSC::PropertyName(Zig::toIdentifier(keys[i], globalObject)),
|
||||
Zig::toJSStringGC(values[i], globalObject), 0);
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3538,13 +3550,11 @@ void JSC__JSPromise__rejectOnNextTickWithHandled(JSC::JSPromise* promise, JSC::J
|
||||
|
||||
promise->internalField(JSC::JSPromise::Field::Flags).set(vm, promise, jsNumber(flags | JSC::JSPromise::isFirstResolvingFunctionCalledFlag));
|
||||
auto* globalObject = jsCast<Zig::GlobalObject*>(promise->globalObject());
|
||||
auto microtaskFunction = globalObject->performMicrotaskFunction();
|
||||
auto rejectPromiseFunction = globalObject->rejectPromiseFunction();
|
||||
|
||||
auto asyncContext = globalObject->m_asyncContextData.get()->getInternalField(0);
|
||||
|
||||
#if ASSERT_ENABLED
|
||||
ASSERT_WITH_MESSAGE(microtaskFunction, "Invalid microtask function");
|
||||
ASSERT_WITH_MESSAGE(rejectPromiseFunction, "Invalid microtask callback");
|
||||
ASSERT_WITH_MESSAGE(!value.isEmpty(), "Invalid microtask value");
|
||||
#endif
|
||||
@@ -3557,7 +3567,8 @@ void JSC__JSPromise__rejectOnNextTickWithHandled(JSC::JSPromise* promise, JSC::J
|
||||
value = jsUndefined();
|
||||
}
|
||||
|
||||
JSC::QueuedTask task { nullptr, JSC::InternalMicrotask::BunPerformMicrotaskJob, 0, globalObject, microtaskFunction, rejectPromiseFunction, globalObject->m_asyncContextData.get()->getInternalField(0), promise, value };
|
||||
// BunPerformMicrotaskJob: rejectPromiseFunction, asyncContext, promise, value
|
||||
JSC::QueuedTask task { nullptr, JSC::InternalMicrotask::BunPerformMicrotaskJob, 0, globalObject, rejectPromiseFunction, globalObject->m_asyncContextData.get()->getInternalField(0), promise, value };
|
||||
globalObject->vm().queueMicrotask(WTF::move(task));
|
||||
RETURN_IF_EXCEPTION(scope, );
|
||||
}
|
||||
@@ -5438,9 +5449,7 @@ extern "C" void JSC__JSGlobalObject__queueMicrotaskJob(JSC::JSGlobalObject* arg0
|
||||
if (microtaskArgs[3].isEmpty()) {
|
||||
microtaskArgs[3] = jsUndefined();
|
||||
}
|
||||
JSC::JSFunction* microTaskFunction = globalObject->performMicrotaskFunction();
|
||||
#if ASSERT_ENABLED
|
||||
ASSERT_WITH_MESSAGE(microTaskFunction, "Invalid microtask function");
|
||||
auto& vm = globalObject->vm();
|
||||
if (microtaskArgs[0].isCell()) {
|
||||
JSC::Integrity::auditCellFully(vm, microtaskArgs[0].asCell());
|
||||
@@ -5460,7 +5469,8 @@ extern "C" void JSC__JSGlobalObject__queueMicrotaskJob(JSC::JSGlobalObject* arg0
|
||||
|
||||
#endif
|
||||
|
||||
JSC::QueuedTask task { nullptr, JSC::InternalMicrotask::BunPerformMicrotaskJob, 0, globalObject, microTaskFunction, WTF::move(microtaskArgs[0]), WTF::move(microtaskArgs[1]), WTF::move(microtaskArgs[2]), WTF::move(microtaskArgs[3]) };
|
||||
// BunPerformMicrotaskJob: job, asyncContext, arg0, arg1
|
||||
JSC::QueuedTask task { nullptr, JSC::InternalMicrotask::BunPerformMicrotaskJob, 0, globalObject, WTF::move(microtaskArgs[0]), WTF::move(microtaskArgs[1]), WTF::move(microtaskArgs[2]), WTF::move(microtaskArgs[3]) };
|
||||
globalObject->vm().queueMicrotask(WTF::move(task));
|
||||
}
|
||||
|
||||
@@ -6141,6 +6151,166 @@ CPP_DECL [[ZIG_EXPORT(nothrow)]] unsigned int Bun__CallFrame__getLineNumber(JSC:
|
||||
return lineColumn.line;
|
||||
}
|
||||
|
||||
// REPL evaluation function - evaluates JavaScript code in the global scope
|
||||
// Returns the result value, or undefined if an exception was thrown
|
||||
// If an exception is thrown, the exception value is stored in *exception
|
||||
extern "C" JSC::EncodedJSValue Bun__REPL__evaluate(
|
||||
JSC::JSGlobalObject* globalObject,
|
||||
const unsigned char* sourcePtr,
|
||||
size_t sourceLen,
|
||||
const unsigned char* filenamePtr,
|
||||
size_t filenameLen,
|
||||
JSC::EncodedJSValue* exception)
|
||||
{
|
||||
auto& vm = JSC::getVM(globalObject);
|
||||
auto scope = DECLARE_TOP_EXCEPTION_SCOPE(vm);
|
||||
|
||||
WTF::String source = WTF::String::fromUTF8(std::span { sourcePtr, sourceLen });
|
||||
WTF::String filename = filenameLen > 0
|
||||
? WTF::String::fromUTF8(std::span { filenamePtr, filenameLen })
|
||||
: "[repl]"_s;
|
||||
|
||||
JSC::SourceCode sourceCode = JSC::makeSource(
|
||||
source,
|
||||
JSC::SourceOrigin {},
|
||||
JSC::SourceTaintedOrigin::Untainted,
|
||||
filename,
|
||||
WTF::TextPosition(),
|
||||
JSC::SourceProviderSourceType::Program);
|
||||
|
||||
WTF::NakedPtr<JSC::Exception> evalException;
|
||||
JSC::JSValue result = JSC::evaluate(globalObject, sourceCode, globalObject->globalThis(), evalException);
|
||||
|
||||
if (evalException) {
|
||||
*exception = JSC::JSValue::encode(evalException->value());
|
||||
// Set _error on the globalObject directly (not globalThis proxy)
|
||||
globalObject->putDirect(vm, JSC::Identifier::fromString(vm, "_error"_s), evalException->value());
|
||||
scope.clearException();
|
||||
return JSC::JSValue::encode(JSC::jsUndefined());
|
||||
}
|
||||
|
||||
if (scope.exception()) {
|
||||
*exception = JSC::JSValue::encode(scope.exception()->value());
|
||||
// Set _error on the globalObject directly (not globalThis proxy)
|
||||
globalObject->putDirect(vm, JSC::Identifier::fromString(vm, "_error"_s), scope.exception()->value());
|
||||
scope.clearException();
|
||||
return JSC::JSValue::encode(JSC::jsUndefined());
|
||||
}
|
||||
|
||||
// Note: _ is now set in Zig code (repl.zig) after extracting the value from
|
||||
// the REPL transform wrapper. We don't set it here anymore.
|
||||
|
||||
return JSC::JSValue::encode(result);
|
||||
}
|
||||
|
||||
// REPL completion function - gets completions for a partial property access
|
||||
// Returns an array of completion strings, or undefined if no completions
|
||||
extern "C" JSC::EncodedJSValue Bun__REPL__getCompletions(
|
||||
JSC::JSGlobalObject* globalObject,
|
||||
JSC::EncodedJSValue targetValue,
|
||||
const unsigned char* prefixPtr,
|
||||
size_t prefixLen)
|
||||
{
|
||||
auto& vm = JSC::getVM(globalObject);
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
JSC::JSValue target = JSC::JSValue::decode(targetValue);
|
||||
if (!target || target.isUndefined() || target.isNull()) {
|
||||
target = globalObject->globalThis();
|
||||
}
|
||||
|
||||
if (!target.isObject()) {
|
||||
JSObject* boxed = target.toObject(globalObject);
|
||||
RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(JSC::jsUndefined()));
|
||||
target = boxed;
|
||||
}
|
||||
|
||||
WTF::String prefix = prefixLen > 0
|
||||
? WTF::String::fromUTF8(std::span { prefixPtr, prefixLen })
|
||||
: WTF::String();
|
||||
|
||||
JSC::JSObject* object = target.getObject();
|
||||
JSC::PropertyNameArrayBuilder propertyNames(vm, JSC::PropertyNameMode::Strings, JSC::PrivateSymbolMode::Exclude);
|
||||
object->getPropertyNames(globalObject, propertyNames, DontEnumPropertiesMode::Include);
|
||||
RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(JSC::jsUndefined()));
|
||||
|
||||
JSC::JSArray* completions = JSC::constructEmptyArray(globalObject, nullptr, 0);
|
||||
RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(JSC::jsUndefined()));
|
||||
|
||||
unsigned completionIndex = 0;
|
||||
for (const auto& propertyName : propertyNames) {
|
||||
WTF::String name = propertyName.string();
|
||||
if (prefix.isEmpty() || name.startsWith(prefix)) {
|
||||
completions->putDirectIndex(globalObject, completionIndex++, JSC::jsString(vm, name));
|
||||
RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(JSC::jsUndefined()));
|
||||
}
|
||||
}
|
||||
|
||||
// Also check the prototype chain
|
||||
JSC::JSValue proto = object->getPrototype(globalObject);
|
||||
RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(completions));
|
||||
|
||||
while (proto && proto.isObject()) {
|
||||
JSC::JSObject* protoObj = proto.getObject();
|
||||
JSC::PropertyNameArrayBuilder protoNames(vm, JSC::PropertyNameMode::Strings, JSC::PrivateSymbolMode::Exclude);
|
||||
protoObj->getPropertyNames(globalObject, protoNames, DontEnumPropertiesMode::Include);
|
||||
RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(completions));
|
||||
|
||||
for (const auto& propertyName : protoNames) {
|
||||
WTF::String name = propertyName.string();
|
||||
if (prefix.isEmpty() || name.startsWith(prefix)) {
|
||||
completions->putDirectIndex(globalObject, completionIndex++, JSC::jsString(vm, name));
|
||||
RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(completions));
|
||||
}
|
||||
}
|
||||
|
||||
proto = protoObj->getPrototype(globalObject);
|
||||
RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(completions));
|
||||
}
|
||||
|
||||
return JSC::JSValue::encode(completions);
|
||||
}
|
||||
|
||||
// Format a value for REPL output using util.inspect style
|
||||
extern "C" JSC::EncodedJSValue Bun__REPL__formatValue(
|
||||
JSC::JSGlobalObject* globalObject,
|
||||
JSC::EncodedJSValue valueEncoded,
|
||||
int32_t depth,
|
||||
bool colors)
|
||||
{
|
||||
auto& vm = JSC::getVM(globalObject);
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
// Get the util.inspect function from the global object
|
||||
auto* bunGlobal = jsCast<Zig::GlobalObject*>(globalObject);
|
||||
JSC::JSValue inspectFn = bunGlobal->utilInspectFunction();
|
||||
|
||||
if (!inspectFn || !inspectFn.isCallable()) {
|
||||
// Fallback to toString if util.inspect is not available
|
||||
JSC::JSValue value = JSC::JSValue::decode(valueEncoded);
|
||||
JSString* str = value.toString(globalObject);
|
||||
RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(JSC::jsUndefined()));
|
||||
return JSC::JSValue::encode(str);
|
||||
}
|
||||
|
||||
// Create options object
|
||||
JSC::JSObject* options = JSC::constructEmptyObject(globalObject);
|
||||
options->putDirect(vm, JSC::Identifier::fromString(vm, "depth"_s), JSC::jsNumber(depth));
|
||||
options->putDirect(vm, JSC::Identifier::fromString(vm, "colors"_s), JSC::jsBoolean(colors));
|
||||
options->putDirect(vm, JSC::Identifier::fromString(vm, "maxArrayLength"_s), JSC::jsNumber(100));
|
||||
options->putDirect(vm, JSC::Identifier::fromString(vm, "maxStringLength"_s), JSC::jsNumber(10000));
|
||||
options->putDirect(vm, JSC::Identifier::fromString(vm, "breakLength"_s), JSC::jsNumber(80));
|
||||
|
||||
JSC::MarkedArgumentBuffer args;
|
||||
args.append(JSC::JSValue::decode(valueEncoded));
|
||||
args.append(options);
|
||||
|
||||
JSC::JSValue result = JSC::call(globalObject, inspectFn, JSC::ArgList(args), "util.inspect"_s);
|
||||
RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(JSC::jsUndefined()));
|
||||
|
||||
return JSC::JSValue::encode(result);
|
||||
}
|
||||
|
||||
extern "C" void JSC__ArrayBuffer__ref(JSC::ArrayBuffer* self) { self->ref(); }
|
||||
extern "C" void JSC__ArrayBuffer__deref(JSC::ArrayBuffer* self) { self->deref(); }
|
||||
extern "C" void JSC__ArrayBuffer__asBunArrayBuffer(JSC::ArrayBuffer* self, Bun__ArrayBuffer* out)
|
||||
|
||||
6
src/bun.js/bindings/headers.h
generated
6
src/bun.js/bindings/headers.h
generated
@@ -168,6 +168,12 @@ CPP_DECL uint32_t JSC__JSInternalPromise__status(const JSC::JSInternalPromise* a
|
||||
|
||||
CPP_DECL void JSC__JSFunction__optimizeSoon(JSC::EncodedJSValue JSValue0);
|
||||
|
||||
#pragma mark - REPL Functions
|
||||
|
||||
CPP_DECL JSC::EncodedJSValue Bun__REPL__evaluate(JSC::JSGlobalObject* globalObject, const unsigned char* sourcePtr, size_t sourceLen, const unsigned char* filenamePtr, size_t filenameLen, JSC::EncodedJSValue* exception);
|
||||
CPP_DECL JSC::EncodedJSValue Bun__REPL__getCompletions(JSC::JSGlobalObject* globalObject, JSC::EncodedJSValue targetValue, const unsigned char* prefixPtr, size_t prefixLen);
|
||||
CPP_DECL JSC::EncodedJSValue Bun__REPL__formatValue(JSC::JSGlobalObject* globalObject, JSC::EncodedJSValue valueEncoded, int32_t depth, bool colors);
|
||||
|
||||
#pragma mark - JSC::JSGlobalObject
|
||||
|
||||
CPP_DECL VirtualMachine* JSC__JSGlobalObject__bunVM(JSC::JSGlobalObject* arg0);
|
||||
|
||||
@@ -759,8 +759,6 @@ static void initializeColumnNames(JSC::JSGlobalObject* lexicalGlobalObject, JSSQ
|
||||
|
||||
// Slow path:
|
||||
|
||||
JSC::ObjectInitializationScope initializationScope(vm);
|
||||
|
||||
// 64 is the maximum we can preallocate here
|
||||
// see https://github.com/oven-sh/bun/issues/987
|
||||
JSObject* prototype = castedThis->userPrototype ? castedThis->userPrototype.get() : lexicalGlobalObject->objectPrototype();
|
||||
@@ -2022,7 +2020,7 @@ JSC_DEFINE_HOST_FUNCTION(jsSQLStatementSetPrototypeFunction, (JSGlobalObject * l
|
||||
return {};
|
||||
}
|
||||
|
||||
castedThis->userPrototype.set(vm, classObject, prototype.getObject());
|
||||
castedThis->userPrototype.set(vm, castedThis, prototype.getObject());
|
||||
|
||||
// Force the prototypes to be re-created
|
||||
if (castedThis->version_db) {
|
||||
|
||||
@@ -42,6 +42,13 @@ static std::optional<WTF::String> stripANSI(const std::span<const Char> input)
|
||||
// Append everything before the escape sequence
|
||||
result.append(std::span { start, escPos });
|
||||
const auto newPos = ANSI::consumeANSI(escPos, end);
|
||||
if (newPos == escPos) {
|
||||
// No ANSI found
|
||||
result.append(std::span { escPos, escPos + 1 });
|
||||
start = escPos + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
ASSERT(newPos > start);
|
||||
ASSERT(newPos <= end);
|
||||
foundANSI = true;
|
||||
@@ -50,6 +57,46 @@ static std::optional<WTF::String> stripANSI(const std::span<const Char> input)
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
struct BunANSIIterator {
|
||||
const unsigned char* input;
|
||||
size_t input_len;
|
||||
size_t cursor;
|
||||
const unsigned char* slice_ptr;
|
||||
size_t slice_len;
|
||||
};
|
||||
|
||||
extern "C" bool Bun__ANSI__next(BunANSIIterator* it)
|
||||
{
|
||||
auto start = it->input + it->cursor;
|
||||
const auto end = it->input + it->input_len;
|
||||
|
||||
// Skip past any ANSI sequences at current position
|
||||
while (start < end) {
|
||||
const auto escPos = ANSI::findEscapeCharacter(start, end);
|
||||
if (escPos != start) break;
|
||||
const auto after = ANSI::consumeANSI(start, end);
|
||||
if (after == start) {
|
||||
start++;
|
||||
break;
|
||||
}
|
||||
start = after;
|
||||
}
|
||||
|
||||
if (start >= end) {
|
||||
it->cursor = it->input_len;
|
||||
it->slice_ptr = nullptr;
|
||||
it->slice_len = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto escPos = ANSI::findEscapeCharacter(start, end);
|
||||
const auto slice_end = escPos ? escPos : end;
|
||||
|
||||
it->slice_ptr = start;
|
||||
it->slice_len = slice_end - start;
|
||||
it->cursor = slice_end - it->input;
|
||||
return true;
|
||||
}
|
||||
JSC_DEFINE_HOST_FUNCTION(jsFunctionBunStripANSI, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
|
||||
{
|
||||
auto& vm = globalObject->vm();
|
||||
|
||||
@@ -31,8 +31,17 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _WIN32
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wmicrosoft-include"
|
||||
#endif
|
||||
|
||||
#define v8 real_v8
|
||||
#define private public
|
||||
#include "node/v8.h"
|
||||
#undef private
|
||||
#undef v8
|
||||
|
||||
#ifdef _WIN32
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
|
||||
@@ -122,16 +122,18 @@ STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSEventPrototype, JSEventPrototype::Base);
|
||||
|
||||
using JSEventDOMConstructor = JSDOMConstructor<JSEvent>;
|
||||
|
||||
/* Hash table */
|
||||
/* Source for JSEvent.lut.h
|
||||
@begin JSEventTable
|
||||
isTrusted jsEvent_isTrusted DontDelete|ReadOnly|CustomAccessor|DOMAttribute
|
||||
@end
|
||||
*/
|
||||
|
||||
static const struct CompactHashIndex JSEventTableIndex[2] = {
|
||||
{ 0, -1 },
|
||||
{ -1, -1 },
|
||||
};
|
||||
|
||||
static const HashTableValue JSEventTableValues[] = {
|
||||
{ "isTrusted"_s, static_cast<unsigned>(JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute), NoIntrinsic, { HashTableValue::GetterSetterType, jsEvent_isTrusted, 0 } },
|
||||
};
|
||||
// The generated .lut.h defines JSEventTable with nullptr for classForThis,
|
||||
// but DOMAttribute properties require it for type checking. Rename the
|
||||
// generated table and redefine it with the correct classForThis.
|
||||
#define JSEventTable JSEventTable_GENERATED
|
||||
#include "JSEvent.lut.h"
|
||||
#undef JSEventTable
|
||||
|
||||
static const HashTable JSEventTable = { 1, 1, true, JSEvent::info(), JSEventTableValues, JSEventTableIndex };
|
||||
/* Hash table for constructor */
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user