aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2021-09-14 16:59:21 -0700
committerGravatar Jarred Sumner <jarred@jarredsumner.com> 2021-09-14 16:59:21 -0700
commit5dbbad5cc42edf6627d0979bc7e40c4ecd72bf20 (patch)
tree89e4dc64f1aa9124bf7190f5b85263f1c2811c20
parent24522f7d743140a8f8be11eac4c2c3458a3fb937 (diff)
downloadbun-5dbbad5cc42edf6627d0979bc7e40c4ecd72bf20.tar.gz
bun-5dbbad5cc42edf6627d0979bc7e40c4ecd72bf20.tar.zst
bun-5dbbad5cc42edf6627d0979bc7e40c4ecd72bf20.zip
Support installing bun from npm
-rw-r--r--.gitignore7
-rw-r--r--Makefile57
-rw-r--r--README.md2
-rw-r--r--build-id2
-rw-r--r--build.zig45
-rw-r--r--packages/bun-cli-darwin-x64/package.json8
-rw-r--r--packages/bun-cli/.npmignore2
-rw-r--r--packages/bun-cli/package.json11
-rwxr-xr-xpackages/bun-cli/reset-bin.js13
-rw-r--r--packages/bun-cli/scripts/postinstall.ts359
-rw-r--r--src/cli.zig16
-rw-r--r--src/global.zig4
-rw-r--r--src/http.zig16
-rw-r--r--src/runtime.zig17
14 files changed, 516 insertions, 43 deletions
diff --git a/.gitignore b/.gitignore
index 6d511368b..0410a7c72 100644
--- a/.gitignore
+++ b/.gitignore
@@ -62,4 +62,9 @@ src/node-fallbacks/node_modules
sign.json
release/
*.dmg
-sign.*.json \ No newline at end of file
+sign.*.json
+packages/debug-*
+packages/bun-cli/postinstall.js
+packages/bun-*/bin/*
+
+packages/bun-cli/bin/* \ No newline at end of file
diff --git a/Makefile b/Makefile
index e6173bd17..d6780ee8d 100644
--- a/Makefile
+++ b/Makefile
@@ -1,3 +1,17 @@
+OS_NAME := $(shell uname -s | tr '[:upper:]' '[:lower:]')
+ARCH_NAME_DENORAMLZIED_1 := $(shell uname -m)
+ARCH_NAME_DENORAMLZIED_2 := $(shell tr '[_]' '[--]' <<< $(ARCH_NAME_DENORAMLZIED_1))
+ARCH_NAME := $(shell sed s/x86-64/x64/ <<< $(ARCH_NAME_DENORAMLZIED_2))
+TRIPLET := $(OS_NAME)-$(ARCH_NAME)
+PACKAGE_DIR := packages/bun-cli-$(TRIPLET)
+DEBUG_PACKAGE_DIR := packages/debug-bun-cli-$(TRIPLET)
+BIN_DIR := $(PACKAGE_DIR)/bin
+RELEASE_BIN := $(BIN_DIR)/bun
+DEBUG_BIN := $(DEBUG_PACKAGE_DIR)/bin/bun-debug
+BUILD_ID := $(shell cat ./build-id)
+PACKAGE_JSON_VERSION := 0.0.0-$(BUILD_ID)
+BUN_BUILD_TAG := bun-v$(PACKAGE_JSON_VERSION)
+
bun: vendor build-obj bun-link-lld-release
vendor: api node-fallbacks runtime_js fallback_decoder bun_error mimalloc picohttp jsc
@@ -11,9 +25,12 @@ sign-macos-x64:
sign-macos-aarch64:
gon sign.macos-aarch64.json
-release-macos-x64: build-obj jsc-bindings-mac bun-link-lld-release sign-macos-x64 release-macos-x64-push
+release-macos-x64: build-obj jsc-bindings-mac bun-link-lld-release
release-macos-aarch64: build-obj jsc-bindings-mac bun-link-lld-release sign-macos-aarch64
+bin-dir:
+ @echo $(BIN_DIR)
+
api:
npm install; ./node_modules/.bin/peechy --schema src/api/schema.peechy --esm src/api/schema.js --ts src/api/schema.d.ts --zig src/api/schema.zig
@@ -21,10 +38,10 @@ node-fallbacks:
cd src/node-fallbacks; npm install; npm run --silent build
fallback_decoder:
- esbuild --target=esnext --bundle src/fallback.ts --format=iife --platform=browser --minify > src/fallback.out.js
+ @esbuild --target=esnext --bundle src/fallback.ts --format=iife --platform=browser --minify > src/fallback.out.js
runtime_js:
- NODE_ENV=production esbuild --define:process.env.NODE_ENV="production" --target=esnext --bundle src/runtime/index.ts --format=iife --platform=browser --global-name=BUN_RUNTIME --minify --external:/bun:* > src/runtime.out.js; cat src/runtime.footer.js >> src/runtime.out.js
+ @NODE_ENV=production esbuild --define:process.env.NODE_ENV="production" --target=esnext --bundle src/runtime/index.ts --format=iife --platform=browser --global-name=BUN_RUNTIME --minify --external:/bun:* > src/runtime.out.js; cat src/runtime.footer.js >> src/runtime.out.js
bun_error:
cd packages/bun-error; npm install; npm run --silent build
@@ -37,25 +54,37 @@ jsc-bindings-headers:
mkdir -p src/JavaScript/jsc/bindings-obj/
zig build headers
-BUILD_ID := $(shell cat ./build-id)
-
-bump-build-id:
+bump:
expr $(BUILD_ID) + 1 > build-id
-BUN_BUILD_TAG := bun-build-$(BUILD_ID)
+
+build_postinstall:
+ @esbuild --bundle --format=cjs --platform=node --define:BUN_VERSION="\"$(PACKAGE_JSON_VERSION)\"" packages/bun-cli/scripts/postinstall.ts > packages/bun-cli/postinstall.js
+
+write-package-json-version-cli:
+ jq -S --raw-output '.version = "${PACKAGE_JSON_VERSION}"' packages/bun-cli/package.json > packages/bun-cli/package.json.new
+ mv packages/bun-cli/package.json.new packages/bun-cli/package.json
+
+write-package-json-version-arch:
+ jq -S --raw-output '.version = "${PACKAGE_JSON_VERSION}"' $(PACKAGE_DIR)/package.json > $(PACKAGE_DIR)/package.json.new
+ mv $(PACKAGE_DIR)/package.json.new $(PACKAGE_DIR)/package.json
tag:
git tag $(BUN_BUILD_TAG)
git push --tags
-prepare-release: bump-build-id tag release-create
+prepare-release: build_postinstall tag release-create write-package-json-version-arch write-package-json-version-cli
release-create:
- gh release create --title "Bun - build $(BUILD_ID)" "$(BUN_BUILD_TAG)"
+ gh release create --title "Bun v$(PACKAGE_JSON_VERSION)" "$(BUN_BUILD_TAG)"
-release-macos-x64-push:
- gh release upload $(BUN_BUILD_TAG) --clobber release/bun-macos-x64.zip
+release-cli-push:
+ cd packages/bun-cli && npm pack --pack-destination /tmp/
+ gh release upload $(BUN_BUILD_TAG) --clobber /tmp/bun-cli-$(PACKAGE_JSON_VERSION).tgz
+release-macos-x64-push:
+ cd packages/bun-cli-darwin-x64 && npm pack --pack-destination /tmp/
+ gh release upload $(BUN_BUILD_TAG) --clobber /tmp/bun-cli-darwin-x64-$(PACKAGE_JSON_VERSION).tgz
jsc-copy-headers:
find src/JavaScript/jsc/WebKit/WebKitBuild/Release/JavaScriptCore/Headers/JavaScriptCore/ -name "*.h" -exec cp {} src/JavaScript/jsc/WebKit/WebKitBuild/Release/JavaScriptCore/PrivateHeaders/JavaScriptCore \;
@@ -140,12 +169,13 @@ bun-link-lld-debug:
bun-link-lld-release:
clang++ $(BUN_LLD_FLAGS) \
- build/macos-x86_64/bun.o \
- -o build/macos-x86_64/bun \
+ packages/bun-cli-darwin-x64/bin/bun.o \
+ -o packages/bun-cli-darwin-x64/bin/bun \
-Wl,-dead_strip \
-ftls-model=local-exec \
-flto \
-O3
+ rm packages/bun-cli-darwin-x64/bin/bun.o
bun-link-lld-release-aarch64:
clang++ $(BUN_LLD_FLAGS) \
@@ -169,3 +199,4 @@ sizegen:
picohttp:
clang -O3 -g -c src/deps/picohttpparser.c -Isrc/deps -o src/deps/picohttpparser.o; cd ../../
+
diff --git a/README.md b/README.md
index e6b8efbb4..eeae1629a 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# Bun: a fast bundler & transpiler for developing web software
+# Bun
Bun is a new:
diff --git a/build-id b/build-id
index 301160a93..f599e28b8 100644
--- a/build-id
+++ b/build-id
@@ -1 +1 @@
-8 \ No newline at end of file
+10
diff --git a/build.zig b/build.zig
index 612dccdeb..9fff785c0 100644
--- a/build.zig
+++ b/build.zig
@@ -24,8 +24,8 @@ pub fn addPicoHTTP(step: *std.build.LibExeObjStep, comptime with_obj: bool) void
// set -gx ICU_INCLUDE_DIRS "/usr/local/opt/icu4c/include"
// homebrew-provided icu4c
}
-
-pub fn build(b: *std.build.Builder) void {
+var x64 = "x64";
+pub fn build(b: *std.build.Builder) !void {
// Standard target options allows the person running `zig build` to choose
// what target to build for. Here we do not override the defaults, which
// means any target is allowed, and the default is native. Other options
@@ -39,12 +39,39 @@ pub fn build(b: *std.build.Builder) void {
const cwd: []const u8 = b.pathFromRoot(".");
var exe: *std.build.LibExeObjStep = undefined;
var output_dir_buf = std.mem.zeroes([4096]u8);
- var bin_label = if (mode == std.builtin.Mode.Debug) "/debug/" else "/";
- const output_dir = b.pathFromRoot(std.fmt.bufPrint(&output_dir_buf, "build{s}{s}-{s}", .{ bin_label, @tagName(target.getOs().tag), @tagName(target.getCpuArch()) }) catch unreachable);
+ var bin_label = if (mode == std.builtin.Mode.Debug) "packages/debug-bun-cli-" else "packages/bun-cli-";
+
+ var triplet_buf: [64]u8 = undefined;
+ var os_tagname = @tagName(target.getOs().tag);
+ if (std.mem.eql(u8, os_tagname, "macos")) {
+ os_tagname = "darwin";
+ }
+
+ std.mem.copy(
+ u8,
+ &triplet_buf,
+ os_tagname,
+ );
+ var osname = triplet_buf[0..os_tagname.len];
+ triplet_buf[osname.len] = '-';
+
+ std.mem.copy(u8, triplet_buf[osname.len + 1 ..], @tagName(target.getCpuArch()));
+ var cpuArchName = triplet_buf[osname.len + 1 ..][0..@tagName(target.getCpuArch()).len];
+ std.mem.replaceScalar(u8, cpuArchName, '_', '-');
+ if (std.mem.eql(u8, cpuArchName, "x86-64")) {
+ std.mem.copy(u8, cpuArchName, "x64");
+ cpuArchName = cpuArchName[0..3];
+ }
+
+ var triplet = triplet_buf[0 .. osname.len + cpuArchName.len + 1];
+
+ const output_dir_base = try std.fmt.bufPrint(&output_dir_buf, "{s}{s}/bin", .{ bin_label, triplet });
+ const output_dir = b.pathFromRoot(output_dir_base);
+ const bun_executable_name = if (mode == std.builtin.Mode.Debug) "bun-debug" else "bun";
if (target.getOsTag() == .wasi) {
exe.enable_wasmtime = true;
- exe = b.addExecutable("bun", "src/main_wasi.zig");
+ exe = b.addExecutable(bun_executable_name, "src/main_wasi.zig");
exe.linkage = .dynamic;
exe.setOutputDir(output_dir);
} else if (target.getCpuArch().isWasm()) {
@@ -54,7 +81,7 @@ pub fn build(b: *std.build.Builder) void {
// );
// exe.is_linking_libc = false;
// exe.is_dynamic = true;
- var lib = b.addExecutable("bun", "src/main_wasm.zig");
+ var lib = b.addExecutable(bun_executable_name, "src/main_wasm.zig");
lib.single_threaded = true;
// exe.want_lto = true;
// exe.linkLibrary(lib);
@@ -94,7 +121,7 @@ pub fn build(b: *std.build.Builder) void {
return;
} else {
- exe = b.addExecutable("bun", "src/main.zig");
+ exe = b.addExecutable(bun_executable_name, "src/main.zig");
}
// exe.setLibCFile("libc.txt");
exe.linkLibC();
@@ -244,7 +271,7 @@ pub fn build(b: *std.build.Builder) void {
}
var obj_step = b.step("obj", "Build Bun as a .o file");
- var obj = b.addObject("bun", exe.root_src.?.path);
+ var obj = b.addObject(bun_executable_name, exe.root_src.?.path);
obj.bundle_compiler_rt = true;
addPicoHTTP(obj, false);
obj.addPackage(.{
@@ -275,7 +302,7 @@ pub fn build(b: *std.build.Builder) void {
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
- var log_step = b.addLog("Destination: {s}/{s}\n", .{ output_dir, "bun" });
+ var log_step = b.addLog("Destination: {s}/{s}\n", .{ output_dir, bun_executable_name });
log_step.step.dependOn(&exe.step);
var typings_cmd: *std.build.RunStep = typings_exe.run();
diff --git a/packages/bun-cli-darwin-x64/package.json b/packages/bun-cli-darwin-x64/package.json
new file mode 100644
index 000000000..af0b3d066
--- /dev/null
+++ b/packages/bun-cli-darwin-x64/package.json
@@ -0,0 +1,8 @@
+{
+ "directories": {
+ "bin": "bin"
+ },
+ "name": "bun-cli-darwin-x64",
+ "repository": "https://github.com/jarred-sumner/bun",
+ "version": "0.0.0-10"
+}
diff --git a/packages/bun-cli/.npmignore b/packages/bun-cli/.npmignore
new file mode 100644
index 000000000..9ce822f7c
--- /dev/null
+++ b/packages/bun-cli/.npmignore
@@ -0,0 +1,2 @@
+scripts
+reset-bin.js
diff --git a/packages/bun-cli/package.json b/packages/bun-cli/package.json
index 331aaeea3..b40d2add4 100644
--- a/packages/bun-cli/package.json
+++ b/packages/bun-cli/package.json
@@ -1,4 +1,13 @@
{
+ "bin": {
+ "bun": "bin/bun"
+ },
+ "license": "MIT",
"name": "bun-cli",
- "version": "0.0.0-2"
+ "repository": "https://github.com/jarred-sumner/bun",
+ "scripts": {
+ "postinstall": "node postinstall.js",
+ "prepublishOnly": "rm -rf ./bin/bun; chmod +x ./reset-bin.js; cp ./reset-bin.js ./bin/bun"
+ },
+ "version": "0.0.0-10"
}
diff --git a/packages/bun-cli/reset-bin.js b/packages/bun-cli/reset-bin.js
new file mode 100755
index 000000000..233061ef2
--- /dev/null
+++ b/packages/bun-cli/reset-bin.js
@@ -0,0 +1,13 @@
+#!/usr/bin/env node
+throw new Error(`bun-cli: Failed to install correctly
+
+Make sure you don't have "ignore-scripts" set to true. You can check this with
+"npm config get ignore-scripts". If that returns true you can reset it back to
+false using "npm config set ignore-scripts false" and then reinstall bun.
+
+If you're using npm v7, make sure your package-lock.json file contains either
+"lockfileVersion": 1 or the code "hasInstallScript": true. If it doesn't have
+either of those, then it is likely the case that a known bug in npm v7 has
+corrupted your package-lock.json file. Regenerating your package-lock.json file
+should fix this issue.
+`);
diff --git a/packages/bun-cli/scripts/postinstall.ts b/packages/bun-cli/scripts/postinstall.ts
new file mode 100644
index 000000000..3ebe0300b
--- /dev/null
+++ b/packages/bun-cli/scripts/postinstall.ts
@@ -0,0 +1,359 @@
+// This is almost verbatim esbuild's postinstall script.
+// Thank you @evanw.
+
+import fs = require("fs");
+import os = require("os");
+import path = require("path");
+import zlib = require("zlib");
+import https = require("https");
+import child_process = require("child_process");
+
+declare const BUN_VERSION: string;
+
+const version = BUN_VERSION;
+const binPath = path.join(__dirname, "bin", "bun");
+
+async function installBinaryFromPackage(
+ name: string,
+ fromPath: string,
+ toPath: string
+): Promise<void> {
+ // Try to install from the cache if possible
+ const cachePath = getCachePath(name);
+ try {
+ // Copy from the cache
+ fs.copyFileSync(cachePath, toPath);
+ fs.chmodSync(toPath, 0o755);
+
+ // Verify that the binary is the correct version
+ validateBinaryVersion(toPath);
+
+ // Mark the cache entry as used for LRU
+ const now = new Date();
+ fs.utimesSync(cachePath, now, now);
+ return;
+ } catch {}
+
+ // Next, try to install using npm. This should handle various tricky cases
+ // such as environments where requests to npmjs.org will hang (in which case
+ // there is probably a proxy and/or a custom registry configured instead).
+ let buffer: Buffer | undefined;
+ let didFail = false;
+ try {
+ buffer = installUsingNPM(name, fromPath);
+ } catch (err) {
+ didFail = true;
+ console.error(`Trying to install "${name}" using npm`);
+ console.error(
+ `Failed to install "${name}" using npm: ${(err && err.message) || err}`
+ );
+ }
+
+ // If that fails, the user could have npm configured incorrectly or could not
+ // have npm installed. Try downloading directly from npm as a last resort.
+ if (!buffer) {
+ const url = `https://registry.npmjs.org/${name}/-/${name}-${version}.tgz`;
+ console.error(`Trying to download ${JSON.stringify(url)}`);
+ try {
+ buffer = extractFileFromTarGzip(await fetch(url), fromPath);
+ } catch (err) {
+ console.error(
+ `Failed to download ${JSON.stringify(url)}: ${
+ (err && err.message) || err
+ }`
+ );
+ }
+ }
+
+ // Give up if none of that worked
+ if (!buffer) {
+ console.error(`Install unsuccessful`);
+ process.exit(1);
+ }
+
+ // Write out the binary executable that was extracted from the package
+ fs.writeFileSync(toPath, buffer, { mode: 0o755 });
+
+ // Verify that the binary is the correct version
+ try {
+ validateBinaryVersion(toPath);
+ } catch (err) {
+ console.error(
+ `The version of the downloaded binary is incorrect: ${
+ (err && err.message) || err
+ }`
+ );
+ console.error(`Install unsuccessful`);
+ process.exit(1);
+ }
+
+ // Also try to cache the file to speed up future installs
+ try {
+ fs.mkdirSync(path.dirname(cachePath), {
+ recursive: true,
+ mode: 0o700, // https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
+ });
+ fs.copyFileSync(toPath, cachePath);
+ cleanCacheLRU(cachePath);
+ } catch {}
+
+ if (didFail) console.error(`Install successful`);
+}
+
+function validateBinaryVersion(binaryPath: string): void {
+ const stdout = child_process
+ .execFileSync(binaryPath, ["--version"])
+ .toString()
+ .trim();
+ if (stdout !== version) {
+ throw new Error(
+ `Expected ${JSON.stringify(version)} but got ${JSON.stringify(stdout)}`
+ );
+ }
+}
+
+function getCachePath(name: string): string {
+ const home = os.homedir();
+ const common = ["bun", "bin", `${name}@${version}`];
+ if (process.platform === "darwin")
+ return path.join(home, "Library", "Caches", ...common);
+ if (process.platform === "win32")
+ return path.join(home, "AppData", "Local", "Cache", ...common);
+
+ // https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
+ const XDG_CACHE_HOME = process.env.XDG_CACHE_HOME;
+ if (
+ process.platform === "linux" &&
+ XDG_CACHE_HOME &&
+ path.isAbsolute(XDG_CACHE_HOME)
+ )
+ return path.join(XDG_CACHE_HOME, ...common);
+
+ return path.join(home, ".cache", ...common);
+}
+
+function cleanCacheLRU(fileToKeep: string): void {
+ // Gather all entries in the cache
+ const dir = path.dirname(fileToKeep);
+ const entries: { path: string; mtime: Date }[] = [];
+ for (const entry of fs.readdirSync(dir)) {
+ const entryPath = path.join(dir, entry);
+ try {
+ const stats = fs.statSync(entryPath);
+ entries.push({ path: entryPath, mtime: stats.mtime });
+ } catch {}
+ }
+
+ // Only keep the most recent entries
+ entries.sort((a, b) => +b.mtime - +a.mtime);
+ for (const entry of entries.slice(5)) {
+ try {
+ fs.unlinkSync(entry.path);
+ } catch {}
+ }
+}
+
+function fetch(url: string): Promise<Buffer> {
+ return new Promise((resolve, reject) => {
+ https
+ .get(url, (res) => {
+ if (
+ (res.statusCode === 301 || res.statusCode === 302) &&
+ res.headers.location
+ )
+ return fetch(res.headers.location).then(resolve, reject);
+ if (res.statusCode !== 200)
+ return reject(new Error(`Server responded with ${res.statusCode}`));
+ let chunks: Buffer[] = [];
+ res.on("data", (chunk) => chunks.push(chunk));
+ res.on("end", () => resolve(Buffer.concat(chunks)));
+ })
+ .on("error", reject);
+ });
+}
+
+function extractFileFromTarGzip(buffer: Buffer, file: string): Buffer {
+ try {
+ buffer = zlib.unzipSync(buffer);
+ } catch (err) {
+ throw new Error(
+ `Invalid gzip data in archive: ${(err && err.message) || err}`
+ );
+ }
+ let str = (i: number, n: number) =>
+ String.fromCharCode(...buffer.subarray(i, i + n)).replace(/\0.*$/, "");
+ let offset = 0;
+ file = `package/${file}`;
+ while (offset < buffer.length) {
+ let name = str(offset, 100);
+ let size = parseInt(str(offset + 124, 12), 8);
+ offset += 512;
+ if (!isNaN(size)) {
+ if (name === file) return buffer.subarray(offset, offset + size);
+ offset += (size + 511) & ~511;
+ }
+ }
+ throw new Error(`Could not find ${JSON.stringify(file)} in archive`);
+}
+
+function installUsingNPM(name: string, file: string): Buffer {
+ const installDir = path.join(
+ os.tmpdir(),
+ "bun-cli-" + Math.random().toString(36).slice(2)
+ );
+ fs.mkdirSync(installDir, { recursive: true });
+ fs.writeFileSync(path.join(installDir, "package.json"), "{}");
+
+ // Erase "npm_config_global" so that "npm install --global bun" works.
+ // Otherwise this nested "npm install" will also be global, and the install
+ // will deadlock waiting for the global installation lock.
+ const env = { ...process.env, npm_config_global: undefined };
+
+ child_process.execSync(
+ `npm install --loglevel=error --prefer-offline --no-audit --progress=false ${name}@${version}`,
+ { cwd: installDir, stdio: "pipe", env }
+ );
+ const buffer = fs.readFileSync(
+ path.join(installDir, "node_modules", name, file)
+ );
+ try {
+ removeRecursive(installDir);
+ } catch (e) {
+ // Removing a file or directory can randomly break on Windows, returning
+ // EBUSY for an arbitrary length of time. I think this happens when some
+ // other program has that file or directory open (e.g. an anti-virus
+ // program). This is fine on Unix because the OS just unlinks the entry
+ // but keeps the reference around until it's unused. In this case we just
+ // ignore errors because this directory is in a temporary directory, so in
+ // theory it should get cleaned up eventually anyway.
+ }
+ return buffer;
+}
+
+function removeRecursive(dir: string): void {
+ for (const entry of fs.readdirSync(dir)) {
+ const entryPath = path.join(dir, entry);
+ let stats;
+ try {
+ stats = fs.lstatSync(entryPath);
+ } catch (e) {
+ continue; // Guard against https://github.com/nodejs/node/issues/4760
+ }
+ if (stats.isDirectory()) removeRecursive(entryPath);
+ else fs.unlinkSync(entryPath);
+ }
+ fs.rmdirSync(dir);
+}
+
+function isYarnBerryOrNewer(): boolean {
+ const { npm_config_user_agent } = process.env;
+ if (npm_config_user_agent) {
+ const match = npm_config_user_agent.match(/yarn\/(\d+)/);
+ if (match && match[1]) {
+ return parseInt(match[1], 10) >= 2;
+ }
+ }
+ return false;
+}
+
+function installDirectly(name: string) {
+ if (process.env.BUN_BINARY_PATH) {
+ fs.copyFileSync(process.env.BUN_BINARY_PATH, binPath);
+ validateBinaryVersion(binPath);
+ } else {
+ // Write to a temporary file, then move the file into place. This is an
+ // attempt to avoid problems with package managers like pnpm which will
+ // usually turn each file into a hard link. We don't want to mutate the
+ // hard-linked file which may be shared with other files.
+ const tempBinPath = binPath + "__";
+ installBinaryFromPackage(name, "bin/bun", tempBinPath)
+ .then(() => fs.renameSync(tempBinPath, binPath))
+ .catch((e) =>
+ setImmediate(() => {
+ throw e;
+ })
+ );
+ }
+}
+
+function installWithWrapper(
+ name: string,
+ fromPath: string,
+ toPath: string
+): void {
+ fs.writeFileSync(
+ binPath,
+ `#!/usr/bin/env node
+const path = require('path');
+const bun_exe = path.join(__dirname, '..', ${JSON.stringify(toPath)});
+const child_process = require('child_process');
+console.warn("[Bun] Yarn 2's lack of binary support slows Bun down. Consider using a different package manager until https://github.com/yarnpkg/berry/issues/882 is fixed.\n");
+const { status } = child_process.spawnSync(bun_exe, process.argv.slice(2), { stdio: 'inherit' });
+process.exitCode = status === null ? 1 : status;
+`
+ );
+ const absToPath = path.join(__dirname, toPath);
+ if (process.env.BUN_BINARY_PATH) {
+ fs.copyFileSync(process.env.BUN_BINARY_PATH, absToPath);
+ validateBinaryVersion(absToPath);
+ } else {
+ installBinaryFromPackage(name, fromPath, absToPath).catch((e) =>
+ setImmediate(() => {
+ throw e;
+ })
+ );
+ }
+}
+
+function installOnUnix(name: string): void {
+ // Yarn 2 is deliberately incompatible with binary modules because the
+ // developers of Yarn 2 don't think they should be used. See this thread for
+ // details: https://github.com/yarnpkg/berry/issues/882.
+ //
+ // We want to avoid slowing down bun for everyone just because of this
+ // decision by the Yarn 2 developers, so we explicitly detect if bun is
+ // being installed using Yarn 2 and install a compatability shim only for
+ // Yarn 2. Normal package managers can just run the binary directly for
+ // maximum speed.
+ if (isYarnBerryOrNewer()) {
+ installWithWrapper(name, "bin/bun", "bun");
+ } else {
+ installDirectly(name);
+ }
+}
+
+function installOnWindows(name: string): void {
+ installWithWrapper(name, "bun.exe", "bun.exe");
+}
+
+const platformKey = `${process.platform} ${os.arch()} ${os.endianness()}`;
+const knownWindowsPackages: Record<string, string> = {
+ // "win32 arm64 LE": "bun-cli-windows-arm64",
+ // "win32 ia32 LE": "bun-cli-windows-32",
+ // "win32 x64 LE": "bun-cli-windows-64",
+};
+const knownUnixlikePackages: Record<string, string> = {
+ // "android arm64 LE": "bun-cli-android-arm64",
+ // "darwin arm64 LE": "bun-cli-darwin-arm64",
+ "darwin x64 LE": "bun-cli-darwin-x64",
+ // "freebsd arm64 LE": "bun-cli-freebsd-arm64",
+ // "freebsd x64 LE": "bun-cli-freebsd-64",
+ // "openbsd x64 LE": "bun-cli-openbsd-64",
+ // "linux arm LE": "bun-cli-linux-arm",
+ // "linux arm64 LE": "bun-cli-linux-arm64",
+ // "linux ia32 LE": "bun-cli-linux-32",
+ // "linux mips64el LE": "bun-cli-linux-mips64le",
+ // "linux ppc64 LE": "bun-cli-linux-ppc64le",
+ // "linux x64 LE": "bun-cli-linux-64",
+ // "sunos x64 LE": "bun-cli-sunos-64",
+};
+
+// Pick a package to install
+if (platformKey in knownWindowsPackages) {
+ installOnWindows(knownWindowsPackages[platformKey]);
+} else if (platformKey in knownUnixlikePackages) {
+ installOnUnix(knownUnixlikePackages[platformKey]);
+} else {
+ console.error(`Unsupported platform: ${platformKey}`);
+ process.exit(1);
+}
diff --git a/src/cli.zig b/src/cli.zig
index ee0c131f9..d66f3d9c5 100644
--- a/src/cli.zig
+++ b/src/cli.zig
@@ -150,7 +150,7 @@ pub const Arguments = struct {
pub const ParamType = clap.Param(clap.Help);
- const params: [23]ParamType = brk: {
+ const params: [24]ParamType = brk: {
@setEvalBranchQuota(9999);
break :brk [_]ParamType{
clap.parseParam("--use <STR> Choose a framework, e.g. \"--use next\". It checks first for a package named \"bun-framework-packagename\" and then \"packagename\".") catch unreachable,
@@ -166,6 +166,7 @@ pub const Arguments = struct {
clap.parseParam("--jsx-runtime <STR> \"automatic\" (default) or \"classic\"") catch unreachable,
clap.parseParam("--main-fields <STR>... Main fields to lookup in package.json. Defaults to --platform dependent") catch unreachable,
clap.parseParam("--no-summary   Don't print a summary (when generating .bun") catch unreachable,
+ clap.parseParam("--version   Print version and exit") catch unreachable,
clap.parseParam("--origin <STR> Rewrite import paths to start with --origin. Default: \"/\"") catch unreachable,
clap.parseParam("--platform <STR> \"browser\" or \"node\". Defaults to \"browser\"") catch unreachable,
// clap.parseParam("--production   [not implemented] generate production code") catch unreachable,
@@ -184,6 +185,13 @@ pub const Arguments = struct {
};
};
+ fn printVersionAndExit() noreturn {
+ @setCold(true);
+ Output.writer().writeAll(Global.package_json_version) catch {};
+ Output.flush();
+ std.os.exit(0);
+ }
+
pub fn parse(allocator: *std.mem.Allocator, comptime cmd: Command.Tag) !Api.TransformOptions {
var diag = clap.Diagnostic{};
@@ -193,6 +201,10 @@ pub const Arguments = struct {
return err;
};
+ if (args.flag("--version")) {
+ printVersionAndExit();
+ }
+
var cwd_paths = [_]string{args.option("--cwd") orelse try std.process.getCwdAlloc(allocator)};
var cwd = try std.fs.path.resolve(allocator, &cwd_paths);
@@ -252,7 +264,7 @@ pub const Arguments = struct {
))) {
entry_points = entry_points[1..];
}
- },
+ },
.DevCommand => {
if (entry_points.len > 0 and (strings.eqlComptime(
entry_points[0],
diff --git a/src/global.zig b/src/global.zig
index 5871d3ba3..f82f8bc1d 100644
--- a/src/global.zig
+++ b/src/global.zig
@@ -385,6 +385,10 @@ pub const Output = struct {
pub const Global = struct {
pub const build_id = std.fmt.parseInt(u64, std.mem.trim(u8, @embedFile("../build-id"), "\n \r\t"), 10) catch unreachable;
+ pub const package_json_version = if (isDebug)
+ std.fmt.comptimePrint("0.0.0-{d}_debug", .{build_id})
+ else
+ std.fmt.comptimePrint("0.0.0-{d}", .{build_id});
pub fn panic(comptime fmt: string, args: anytype) noreturn {
@setCold(true);
diff --git a/src/http.zig b/src/http.zig
index 40e828245..f0434fcdb 100644
--- a/src/http.zig
+++ b/src/http.zig
@@ -2538,29 +2538,29 @@ pub const Server = struct {
if (std.mem.readIntNative(u32, &addr.ipv4.host.octets) == 0 or std.mem.readIntNative(u128, &addr.ipv6.host.octets) == 0) {
if (server.bundler.options.routes.single_page_app_routing) {
Output.prettyError(
- " Bun!! <d>build {d}<r>\n\n\n<d> Link:<r> <b><cyan>http://localhost:{d}<r>\n <d>./{s}/index.html<r> \n\n\n",
+ " Bun!! <d>v{s}<r>\n\n\n Link:<r> <b><cyan>http://localhost:{d}<r>\n <d>./{s}/index.html<r> \n\n\n",
.{
- Global.build_id,
+ Global.package_json_version,
addr.ipv4.port,
resolve_path.relative(server.bundler.fs.top_level_dir, server.bundler.options.routes.static_dir),
},
);
} else {
- Output.prettyError(" Bun!! <d>build {d}<r>\n\n\n<d> Link:<r> <b><cyan>http://localhost:{d}<r>\n\n\n", .{
- Global.build_id,
+ Output.prettyError(" Bun!! <d>v{s}<r>\n\n\n<d> Link:<r> <b><cyan>http://localhost:{d}<r>\n\n\n", .{
+ Global.package_json_version,
addr.ipv4.port,
});
}
} else {
if (server.bundler.options.routes.single_page_app_routing) {
- Output.prettyError(" Bun!! <d>build {d}<r>\n\n\n<d> Link:<r> <b><cyan>http://{s}<r>\n <d>./{s}/index.html<r> \n\n\n", .{
- Global.build_id,
+ Output.prettyError(" Bun!! <d>v{s}<r>\n\n\n<d> Link:<r> <b><cyan>http://{s}<r>\n <d>./{s}/index.html<r> \n\n\n", .{
+ Global.package_json_version,
addr,
resolve_path.relative(server.bundler.fs.top_level_dir, server.bundler.options.routes.static_dir),
});
} else {
- Output.prettyError(" Bun!! <d>build {d}\n\n\n<d> Link:<r> <b><cyan>http://{s}<r>\n\n\n", .{
- Global.build_id,
+ Output.prettyError(" Bun!! <d>v{s}\n\n\n<d> Link:<r> <b><cyan>http://{s}<r>\n\n\n", .{
+ Global.package_json_version,
addr,
});
}
diff --git a/src/runtime.zig b/src/runtime.zig
index 78f0776f3..046e1a863 100644
--- a/src/runtime.zig
+++ b/src/runtime.zig
@@ -6,20 +6,23 @@ const resolve_path = @import("./resolver/resolve_path.zig");
const Fs = @import("./fs.zig");
const Schema = @import("./api/schema.zig");
+// packages/bun-cli-*/bin/bun
+const BUN_ROOT = "../../../";
+
const Api = Schema.Api;
pub const ErrorCSS = struct {
- const ErrorCSSPath = "../packages/bun-error/dist/bun-error.css";
- const ErrorCSSPathDev = "../packages/bun-error/bun-error.css";
+ const ErrorCSSPath = "packages/bun-error/dist/bun-error.css";
+ const ErrorCSSPathDev = "packages/bun-error/bun-error.css";
- pub const ProdSourceContent = @embedFile(ErrorCSSPath);
+ pub const ProdSourceContent = @embedFile("../" ++ ErrorCSSPath);
pub fn sourceContent() string {
if (comptime isDebug) {
var env = std.process.getEnvMap(default_allocator) catch unreachable;
var out_buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined;
var dirname = std.fs.selfExeDirPath(&out_buffer) catch unreachable;
- var paths = [_]string{ dirname, "../../", ErrorCSSPathDev };
+ var paths = [_]string{ dirname, BUN_ROOT, ErrorCSSPathDev };
const file = std.fs.cwd().openFile(
resolve_path.joinAbsString(dirname, std.mem.span(&paths), .auto),
.{
@@ -35,16 +38,16 @@ pub const ErrorCSS = struct {
};
pub const ErrorJS = struct {
- const ErrorJSPath = "../packages/bun-error/dist/index.js";
+ const ErrorJSPath = "packages/bun-error/dist/index.js";
- pub const ProdSourceContent = @embedFile(ErrorJSPath);
+ pub const ProdSourceContent = @embedFile("../" ++ ErrorJSPath);
pub fn sourceContent() string {
if (comptime isDebug) {
var env = std.process.getEnvMap(default_allocator) catch unreachable;
var out_buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined;
var dirname = std.fs.selfExeDirPath(&out_buffer) catch unreachable;
- var paths = [_]string{ dirname, "../../", ErrorJSPath };
+ var paths = [_]string{ dirname, BUN_ROOT, ErrorJSPath };
const file = std.fs.cwd().openFile(
resolve_path.joinAbsString(dirname, std.mem.span(&paths), .auto),
.{