File size: 7,273 Bytes
e1cc3bc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
#!/usr/bin/env node
// Immediate log to verify script execution
console.log("[setup-bun] Script loaded");

/**
 * Postinstall script to set up bun from platform-specific optional dependencies.
 * Handles Windows ARM64 by downloading x64-baseline via emulation.
 */
import {
  existsSync,
  mkdirSync,
  symlinkSync,
  unlinkSync,
  copyFileSync,
  chmodSync,
  writeFileSync,
} from "fs";
import { join, dirname } from "path";
import { spawnSync } from "child_process";
import { fileURLToPath } from "url";
import { get } from "https";
import { createGunzip } from "zlib";

const __dirname = dirname(fileURLToPath(import.meta.url));
const projectRoot = join(__dirname, "..");
const nodeModules = join(projectRoot, "node_modules");
const binDir = join(nodeModules, ".bin");

const os = process.platform;
const arch = process.arch;
const isWindows = os === "win32";
const bunExe = isWindows ? "bun.exe" : "bun";

// Detect libc type on Linux (glibc vs musl)
function detectLibc() {
  if (os !== "linux") return null;

  // Check for musl-specific loader
  const muslLoaders = [
    `/lib/ld-musl-${arch === "arm64" ? "aarch64" : "x86_64"}.so.1`,
    "/lib/ld-musl-x86_64.so.1",
    "/lib/ld-musl-aarch64.so.1",
  ];

  for (const loader of muslLoaders) {
    if (existsSync(loader)) {
      console.log(`  Detected musl libc (found ${loader})`);
      return "musl";
    }
  }

  // Default to glibc on Linux
  console.log("  Detected glibc (no musl loader found)");
  return "glibc";
}

// Platform to package mapping (matches @oven/bun-* package names)
// For Linux, separate glibc and musl packages
const platformPackages = {
  darwin: {
    arm64: ["bun-darwin-aarch64"],
    x64: ["bun-darwin-x64", "bun-darwin-x64-baseline"],
  },
  linux: {
    arm64: {
      glibc: ["bun-linux-aarch64"],
      musl: ["bun-linux-aarch64-musl"],
    },
    x64: {
      glibc: ["bun-linux-x64", "bun-linux-x64-baseline"],
      musl: ["bun-linux-x64-musl", "bun-linux-x64-musl-baseline"],
    },
  },
  win32: {
    x64: ["bun-windows-x64", "bun-windows-x64-baseline"],
    arm64: ["bun-windows-x64-baseline"], // x64 runs via emulation on ARM64
  },
};

function findBunBinary() {
  let packages = platformPackages[os]?.[arch];

  // For Linux, select packages based on libc type
  if (os === "linux" && packages && typeof packages === "object") {
    const libc = detectLibc();
    packages = packages[libc] || [];
  }

  packages = packages || [];
  console.log(
    `Looking for bun packages: ${packages.join(", ") || "(none for this platform)"}`,
  );

  for (const pkg of packages) {
    const binPath = join(nodeModules, "@oven", pkg, "bin", bunExe);
    console.log(`  Checking: ${binPath}`);
    if (existsSync(binPath)) {
      console.log(`  Found bun at: ${binPath}`);
      return binPath;
    } else {
      console.log(`  Not found`);
    }
  }

  return null;
}

async function downloadBunForWindowsArm64() {
  // Windows ARM64 can run x64 binaries via emulation
  const pkg = "bun-windows-x64-baseline";
  const version = "1.2.21";
  const url = `https://registry.npmjs.org/@oven/${pkg}/-/${pkg}-${version}.tgz`;
  const destDir = join(nodeModules, "@oven", pkg);

  console.log(`Downloading ${pkg} for Windows ARM64 emulation...`);

  return new Promise((resolve, reject) => {
    get(url, (response) => {
      if (response.statusCode === 302 || response.statusCode === 301) {
        get(response.headers.location, handleResponse).on("error", reject);
      } else {
        handleResponse(response);
      }

      function handleResponse(res) {
        if (res.statusCode !== 200) {
          reject(new Error(`Failed to download: ${res.statusCode}`));
          return;
        }

        const chunks = [];
        const gunzip = createGunzip();

        res.pipe(gunzip);

        gunzip.on("data", (chunk) => chunks.push(chunk));
        gunzip.on("end", () => {
          try {
            extractTar(Buffer.concat(chunks), destDir);
            const binPath = join(destDir, "bin", bunExe);
            if (existsSync(binPath)) {
              resolve(binPath);
            } else {
              reject(new Error("Binary not found after extraction"));
            }
          } catch (err) {
            reject(err);
          }
        });
        gunzip.on("error", reject);
      }
    }).on("error", reject);
  });
}

function extractTar(buffer, destDir) {
  // Simple tar extraction (512-byte blocks)
  let offset = 0;
  while (offset < buffer.length) {
    const name = buffer
      .toString("utf-8", offset, offset + 100)
      .replace(/\0.*$/, "")
      .replace("package/", "");
    const size = parseInt(
      buffer.toString("utf-8", offset + 124, offset + 136).trim(),
      8,
    );

    offset += 512;

    if (!isNaN(size) && size > 0 && name) {
      const filePath = join(destDir, name);
      const fileDir = dirname(filePath);
      if (!existsSync(fileDir)) {
        mkdirSync(fileDir, { recursive: true });
      }
      const content = buffer.subarray(offset, offset + size);
      writeFileSync(filePath, content);

      // Make executable
      if (name.endsWith(bunExe) || name === "bin/bun") {
        try {
          chmodSync(filePath, 0o755);
        } catch {}
      }

      offset += Math.ceil(size / 512) * 512;
    }
  }
}

function setupBinLink(bunPath) {
  if (!existsSync(binDir)) {
    mkdirSync(binDir, { recursive: true });
  }

  const bunLink = join(binDir, bunExe);
  const bunxLink = join(binDir, isWindows ? "bunx.exe" : "bunx");

  // Remove existing links
  for (const link of [bunLink, bunxLink]) {
    try {
      unlinkSync(link);
    } catch {}
  }

  if (isWindows) {
    // On Windows, copy the binary (symlinks may not work without admin)
    copyFileSync(bunPath, bunLink);
    copyFileSync(bunPath, bunxLink);
  } else {
    // On Unix, use symlinks
    symlinkSync(bunPath, bunLink);
    symlinkSync(bunPath, bunxLink);
  }

  console.log(`Bun linked to: ${bunLink}`);
}

// Force immediate output
process.stdout.write("[setup-bun] Script starting...\n");

async function main() {
  process.stdout.write(`[setup-bun] Setting up bun for ${os} ${arch}...\n`);
  process.stdout.write(`[setup-bun] Project root: ${projectRoot}\n`);
  process.stdout.write(`[setup-bun] Node modules: ${nodeModules}\n`);

  let bunPath = findBunBinary();

  if (!bunPath && os === "win32" && arch === "arm64") {
    try {
      bunPath = await downloadBunForWindowsArm64();
    } catch (err) {
      console.error("Failed to download bun for Windows ARM64:", err.message);
    }
  }

  if (!bunPath) {
    console.log("No bun binary found in optional dependencies.");
    console.log("Bun will need to be installed separately.");
    console.log("See: https://bun.sh/docs/installation");
    process.exit(0); // Don't fail the install
  }

  try {
    setupBinLink(bunPath);

    // Verify installation
    const result = spawnSync(bunPath, ["--version"], { encoding: "utf-8" });
    if (result.status === 0) {
      console.log(`Bun ${result.stdout.trim()} installed successfully!`);
    }
  } catch (err) {
    console.error("Failed to set up bun:", err.message);
    process.exit(0); // Don't fail the install
  }
}

main().catch((err) => {
  console.error(err);
  process.exit(0); // Don't fail the install
});