Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

fix(bun-release): support windows in npm package #9873

Merged
merged 8 commits into from
Apr 4, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions .github/workflows/bun-release-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# This workflow tests bun-release's code and the packages to ensure that npm,
# yarn, and pnpm can install bun on all platforms. This does not test that bun
# itself works as it hardcodes 1.1.0 as the version to package.
name: bun-release-test
concurrency: release-test

on:
pull_request:
paths:
- "packages/bun-release/**"
- ".github/workflows/bun-release-test.yml"

jobs:
test-release-script:
name: Test Release Script
strategy:
matrix:
machine: [namespace-profile-bun-linux-x64, linux-arm64, macos-arm64, macos-12-large, windows-latest]
fail-fast: false
runs-on: ${{ matrix.machine }}
permissions:
contents: read
defaults:
run:
working-directory: packages/bun-release
timeout-minutes: 5
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Bun
uses: oven-sh/setup-bun@v1
with:
bun-version: "1.1.0"
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install Dependencies
run: bun install && npm i -g pnpm yarn npm

- name: Release
run: bun upload-npm -- 1.1.0 test
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -140,4 +140,4 @@ txt.js
x64
yarn.lock
zig-cache
zig-out
zig-out
14 changes: 8 additions & 6 deletions packages/bun-release/.gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
.DS_Store
.env
node_modules
/npm/**/bin
/npm/**/*.js
/npm/**/.npmrc
.DS_Store
.env
node_modules
/npm/**/bin
/npm/**/*.js
/npm/**/package.json
/npm/**/.npmrc
*.tgz
Binary file modified packages/bun-release/bun.lockb
Binary file not shown.
16 changes: 0 additions & 16 deletions packages/bun-release/npm/@oven/bun-darwin-aarch64/package.json

This file was deleted.

This file was deleted.

16 changes: 0 additions & 16 deletions packages/bun-release/npm/@oven/bun-darwin-x64/package.json

This file was deleted.

16 changes: 0 additions & 16 deletions packages/bun-release/npm/@oven/bun-linux-aarch64/package.json

This file was deleted.

16 changes: 0 additions & 16 deletions packages/bun-release/npm/@oven/bun-linux-x64-baseline/package.json

This file was deleted.

16 changes: 0 additions & 16 deletions packages/bun-release/npm/@oven/bun-linux-x64/package.json

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Bun

This is the Windows x64 binary for Bun, a fast all-in-one JavaScript runtime. https://bun.sh

_Note: "Baseline" builds are for machines that do not support [AVX2](https://en.wikipedia.org/wiki/Advanced_Vector_Extensions) instructions._
3 changes: 3 additions & 0 deletions packages/bun-release/npm/@oven/bun-windows-x64/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Bun

This is the Windows x64 binary for Bun, a fast all-in-one JavaScript runtime. https://bun.sh
42 changes: 0 additions & 42 deletions packages/bun-release/npm/bun/package.json

This file was deleted.

2 changes: 1 addition & 1 deletion packages/bun-release/package.json
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@
},
"devDependencies": {
"@octokit/types": "^8.1.1",
"bun-types": "^0.4.0",
"bun-types": "^1.1.0",
"prettier": "^2.8.2"
},
"scripts": {
2 changes: 1 addition & 1 deletion packages/bun-release/scripts/npm-exec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { importBun } from "../src/npm/install";
import { importBun, optimizeBun } from "../src/npm/install";
import { execFileSync } from "child_process";

importBun()
195 changes: 167 additions & 28 deletions packages/bun-release/scripts/upload-npm.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { join, copy, exists, chmod, write, writeJson } from "../src/fs";
import { mkdtemp } from "fs/promises";
import { rmSync, mkdirSync } from "fs";
import { tmpdir } from "os";
import { dirname } from "path";
import { fetch } from "../src/fetch";
import { spawn } from "../src/spawn";
import type { Platform } from "../src/platform";
@@ -10,41 +14,51 @@ import { buildSync, formatMessagesSync } from "esbuild";
import type { JSZipObject } from "jszip";
import { loadAsync } from "jszip";
import { debug, log, error } from "../src/console";
import { expect } from "bun:test";

const module = "bun";
const owner = "@oven";
let version: string;

const [tag, action] = process.argv.slice(2);

await build(tag);
const release = await getRelease(tag);
const version = await getSemver(release.tag_name);

if (action !== "test-only") await build();

if (action === "publish") {
await publish();
} else if (action === "dry-run") {
await publish(true);
} else if (action === "test") {
await publish(true);
await test();
} else if (action === "test-only") {
await test();
} else if (action) {
throw new Error(`Unknown action: ${action}`);
}
process.exit(0); // HACK

async function build(tag?: string): Promise<void> {
const release = await getRelease(tag);
version = await getSemver(release.tag_name);
async function build(): Promise<void> {
await buildRootModule();
for (const platform of platforms) {
if (action !== "publish" && (platform.os !== process.platform || platform.arch !== process.arch)) continue;
await buildModule(release, platform);
}
}

async function publish(dryRun?: boolean): Promise<void> {
const modules = platforms.map(({ bin }) => `${owner}/${bin}`);
const modules = platforms
.filter(({ os, arch }) => action === "publish" || (os === process.platform && arch === process.arch))
.map(({ bin }) => `${owner}/${bin}`);
modules.push(module);
for (const module of modules) {
publishModule(module, dryRun);
}
}

async function buildRootModule() {
async function buildRootModule(dryRun?: boolean) {
log("Building:", `${module}@${version}`);
const cwd = join("npm", module);
const define = {
@@ -54,28 +68,53 @@ async function buildRootModule() {
};
bundle(join("scripts", "npm-postinstall.ts"), join(cwd, "install.js"), {
define,
});
bundle(join("scripts", "npm-exec.ts"), join(cwd, "bin", "bun"), {
define,
banner: {
js: "#!/usr/bin/env node",
js: "// Source code: https://github.com/oven-sh/bun/blob/main/packages/bun-release/scripts/npm-postinstall.ts",
},
});
write(join(cwd, "bin", "bun.exe"), "");
write(
join(cwd, "bin", "README.txt"),
`The 'bun.exe' file is a placeholder for the binary file, which
is replaced by Bun's 'postinstall' script. For this to work, make
sure that you do not use --ignore-scripts while installing.
The postinstall script is responsible for linking the binary file
directly into 'node_modules/.bin' and avoiding a Node.js wrapper
script being called on every invocation of 'bun'. If this wasn't
done, Bun would seem to be slower than Node.js, because it would
be executing a copy of Node.js every time!
Unfortunately, it is not possible to fix all cases on all platforms
without *requiring* a postinstall script.
`,
);
const os = [...new Set(platforms.map(({ os }) => os))];
const cpu = [...new Set(platforms.map(({ arch }) => arch))];
writeJson(join(cwd, "package.json"), {
name: module,
description: "Bun is a fast all-in-one JavaScript runtime.",
version: version,
scripts: {
postinstall: "node install.js",
},
optionalDependencies: Object.fromEntries(platforms.map(({ bin }) => [`${owner}/${bin}`, version])),
optionalDependencies: Object.fromEntries(
platforms.map(({ bin }) => [
`${owner}/${bin}`,
dryRun ? `file:./oven-${bin.replaceAll("/", "-") + "-" + version + ".tgz"}` : version,
]),
),
bin: {
bun: "bin/bun",
bunx: "bin/bun",
bun: "bin/bun.exe",
bunx: "bin/bun.exe",
},
os,
cpu,
keywords: ["bun", "bun.js", "node", "node.js", "runtime", "bundler", "transpiler", "typescript"],
homepage: "https://bun.sh",
bugs: "https://github.com/oven-sh/issues",
license: "MIT",
repository: "https://github.com/oven-sh/bun",
});
if (exists(".npmrc")) {
copy(".npmrc", join(cwd, ".npmrc"));
@@ -95,11 +134,17 @@ async function buildModule(
}
const bun = await extractFromZip(asset.browser_download_url, `${bin}/bun`);
const cwd = join("npm", module);
mkdirSync(dirname(join(cwd, exe)), { recursive: true });
write(join(cwd, exe), await bun.async("arraybuffer"));
chmod(join(cwd, exe), 0o755);
writeJson(join(cwd, "package.json"), {
name: module,
version: version,
description: "This is the macOS arm64 binary for Bun, a fast all-in-one JavaScript runtime.",
homepage: "https://bun.sh",
bugs: "https://github.com/oven-sh/issues",
license: "MIT",
repository: "https://github.com/oven-sh/bun",
preferUnplugged: true,
os: [os],
cpu: [arch],
@@ -111,22 +156,33 @@ async function buildModule(

function publishModule(name: string, dryRun?: boolean): void {
log(dryRun ? "Dry-run Publishing:" : "Publishing:", `${name}@${version}`);
const { exitCode, stdout, stderr } = spawn(
"npm",
[
"publish",
"--access",
"public",
"--tag",
version.includes("canary") ? "canary" : "latest",
...(dryRun ? ["--dry-run"] : []),
],
{
if (!dryRun) {
const { exitCode, stdout, stderr } = spawn(
"npm",
[
"publish",
"--access",
"public",
"--tag",
version.includes("canary") ? "canary" : "latest",
...(dryRun ? ["--dry-run"] : []),
],
{
cwd: join("npm", name),
},
);
error(stderr || stdout);
if (exitCode !== 0) {
throw new Error("npm publish failed with code " + exitCode);
}
} else {
const { exitCode, stdout, stderr } = spawn("npm", ["pack"], {
cwd: join("npm", name),
},
);
if (exitCode === 0) {
});
error(stderr || stdout);
if (exitCode !== 0) {
throw new Error("npm pack failed with code " + exitCode);
}
}
}

@@ -162,3 +218,86 @@ function bundle(src: string, dst: string, options: BuildOptions = {}): void {
throw new Error(messages.join("\n"));
}
}

async function test() {
const root = await mkdtemp(join(tmpdir(), "bun-release-test-"));
const $ = new Bun.$.Shell().cwd(root);

for (const platform of platforms) {
if (platform.os !== process.platform) continue;
if (platform.arch !== process.arch) continue;
copy(
join(
import.meta.dir,
"../npm/@oven/",
platform.bin,
"oven-" + platform.bin.replaceAll("/", "-") + `-${version}.tgz`,
),
join(root, `${platform.bin}-${version}.tgz`),
);
}

copy(join(import.meta.dir, "../npm", "bun", "bun-" + version + ".tgz"), join(root, "bun-" + version + ".tgz"));

console.log(root);
for (const [install, exec] of [
["npm i", "npm exec"],
["yarn set version berry; yarn add", "yarn"],
["yarn set version latest; yarn add", "yarn"],
["pnpm i", "pnpm"],
["bun i", "bun run"],
]) {
rmSync(join(root, "node_modules"), { recursive: true, force: true });
rmSync(join(root, "package-lock.json"), { recursive: true, force: true });
rmSync(join(root, "package.json"), { recursive: true, force: true });
rmSync(join(root, "pnpm-lock.yaml"), { recursive: true, force: true });
rmSync(join(root, "yarn.lock"), { recursive: true, force: true });
writeJson(join(root, "package.json"), {
name: "bun-release-test",
});

console.log("Testing", install + " bun");
await $`${{ raw: install }} ./bun-${version}.tgz`;

console.log("Running " + exec + " bun");

// let output = await $`${{
// raw: exec,
// }} bun -- -e "console.log(JSON.stringify([Bun.version, process.platform, process.arch, process.execPath]))"`.text();
const split = exec.split(" ");
let {
stdout: output,
stderr,
exitCode,
} = spawn(
split[0],
[
...split.slice(1),
"--",
"bun",
"-e",
"console.log(JSON.stringify([Bun.version, process.platform, process.arch, process.execPath]))",
],
{
cwd: root,
},
);
if (exitCode !== 0) {
console.error(stderr);
throw new Error("Failed to run " + exec + " bun, exit code: " + exitCode);
}

try {
output = JSON.parse(output);
} catch (e) {
console.log({ output });
throw e;
}

expect(output[0]).toBe(version);
expect(output[1]).toBe(process.platform);
expect(output[2]).toBe(process.arch);
expect(output[3]).toStartWith(root);
expect(output[3]).toInclude("bun");
}
}
11 changes: 2 additions & 9 deletions packages/bun-release/src/npm/install.ts
Original file line number Diff line number Diff line change
@@ -121,16 +121,9 @@ async function downloadBun(platform: Platform, dst: string): Promise<void> {
}

export function optimizeBun(path: string): void {
const installScript =
os === "win32" ? 'powershell -c "irm bun.sh/install.ps1 | iex"' : "curl -fsSL https://bun.sh/install | bash";
const { npm_config_user_agent } = process.env;
if (npm_config_user_agent && /\byarn\//.test(npm_config_user_agent)) {
throw new Error(
`Yarn does not support bun, because it does not allow linking to binaries. To use bun, install using the following command: ${installScript}`,
);
}
const installScript = os === "win32" ? 'powershell -c "irm bun.sh/install.ps1 | iex"' : "curl -fsSL https://bun.sh/install | bash";
try {
rename(path, join(__dirname, "bin", "bun"));
rename(path, join(__dirname, "bin", "bun.exe"));
return;
} catch (error) {
debug("optimizeBun failed", error);
13 changes: 11 additions & 2 deletions packages/bun-release/src/platform.ts
Original file line number Diff line number Diff line change
@@ -106,6 +106,15 @@ function isRosetta2(): boolean {
}

function isWindowsAVX2(): boolean {
// TODO: Implement AVX2 detection on Windows
return false;
try {
return (
spawn("powershell", [
"-c",
`(Add-Type -MemberDefinition '[DllImport("kernel32.dll")] public static extern bool IsProcessorFeaturePresent(int ProcessorFeature);' -Name 'Kernel32' -Namespace 'Win32' -PassThru)::IsProcessorFeaturePresent(40);`,
]).stdout == "True"
);
} catch (error) {
debug("isWindowsAVX2 failed", error);
return false;
}
}