@absolutejs/absolute 0.20.0-beta.122 → 0.20.0-beta.124

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,6 +12,9 @@ import {
12
12
  import {
13
13
  scanListeners
14
14
  } from "./index-fhaf3tdk.js";
15
+ import {
16
+ resolveDevBunExecutable
17
+ } from "./index-hty8111z.js";
15
18
  import {
16
19
  stripStringsAndComments
17
20
  } from "./index-fc9vxw56.js";
@@ -1504,6 +1507,8 @@ var dev = async (serverEntry, configPath, options = {}) => {
1504
1507
  let selectedRemoteMacProfile;
1505
1508
  let expoDevProject;
1506
1509
  markBoot("dev command entered");
1510
+ const bunExecutable = await resolveDevBunExecutable();
1511
+ markBoot("dev bun runtime selected");
1507
1512
  let prescanConfig;
1508
1513
  try {
1509
1514
  const config = await loadConfig(configPath);
@@ -2499,7 +2504,7 @@ var dev = async (serverEntry, configPath, options = {}) => {
2499
2504
  markBoot("dev child spawn requested");
2500
2505
  const prescanForSpawn = prescan;
2501
2506
  prescan = null;
2502
- const proc = nodeSpawn("bun", [
2507
+ const proc = nodeSpawn(bunExecutable, [
2503
2508
  "--hot",
2504
2509
  "--no-clear-screen",
2505
2510
  ...heapSnapshotEnabled ? ["--preload", heapPreloadPath] : [],
@@ -0,0 +1,410 @@
1
+ // @bun
2
+ // src/cli/patchedBun.ts
3
+ import { spawnSync } from "child_process";
4
+ import {
5
+ chmodSync,
6
+ existsSync,
7
+ mkdirSync,
8
+ readFileSync,
9
+ renameSync,
10
+ rmSync,
11
+ writeFileSync
12
+ } from "fs";
13
+ import { homedir } from "os";
14
+ import { dirname, join } from "path";
15
+ import { createInterface } from "readline/promises";
16
+ import { inflateRawSync } from "zlib";
17
+ var PATCHED_BUN_RELEASE = {
18
+ bunVersion: "1.4.0",
19
+ issue: "https://github.com/oven-sh/bun/issues/32919",
20
+ repo: "absolutejs/patched-bun",
21
+ sha256: {
22
+ "bun-darwin-aarch64": "940d8c7eadb9fb4f39f06b0a086bbb6851d1bf3bc164bd3ba3a6966f8229f53c",
23
+ "bun-darwin-x64": "847f215cdd147431d025ffec94063467d1e4e80a5e4c311efff4570e04fa06ed",
24
+ "bun-freebsd-aarch64": "6d1c6c51cb782a1f9d2f4e2481aa7b856eff05be430094fa3b8fb86adf4067a3",
25
+ "bun-freebsd-x64": "c5395cd411bf3df75c3943e6ebb7722b5dd369df9a97c622449d36129cc6277a",
26
+ "bun-linux-aarch64": "fd5931bf59ff2bbaeab5fa37a1b003a3f10ada6b7fee2d3d8f15e606adad61f9",
27
+ "bun-linux-aarch64-android": "ab560ea72357554c93b4b0390b869ec62769c9b5f676ab196ecb3b0e328d7126",
28
+ "bun-linux-aarch64-musl": "b9c3f9936f4c7b6b0b749090f70806a1f73ca48d5a9762709ef9a4e9a5b0fed8",
29
+ "bun-linux-x64": "d36584f9aa71773fada3831ddf01a34c6089988988b900fd968586db099a0567",
30
+ "bun-linux-x64-android": "1a9712c77516fc224ed3307e083a12ea6e4f1a2956e920b9590bf12e4f59304e",
31
+ "bun-linux-x64-musl": "061a8bd6afad866f807d93125508c303f16c2dbf000387c91d22dd777c072951",
32
+ "bun-windows-aarch64": "91ed75e937b191eaf18bb361040acc24cd363b38f7bdaab5228fadd813f2a9ea",
33
+ "bun-windows-x64": "f7a7a22349dc7d4df0347da3f3058fe746763e22950bc77f4ae8021dab75a7b3"
34
+ },
35
+ tag: "bun-v1.4.0-absolute.2"
36
+ };
37
+ var HOURS_PER_DAY = 24;
38
+ var MS_PER_HOUR = 3600000;
39
+ var ASK_LATER_MS = HOURS_PER_DAY * MS_PER_HOUR;
40
+ var PREFERENCE_FILE = "patched-bun.json";
41
+ var EXECUTABLE_MODE = 493;
42
+ var VERSION_PARTS = 3;
43
+ var PROBE_TIMEOUT_MS = 30000;
44
+ var MUSL_LOADERS = ["/lib/ld-musl-x86_64.so.1", "/lib/ld-musl-aarch64.so.1"];
45
+ var DEPENDENCY_FIELDS = [
46
+ "dependencies",
47
+ "devDependencies",
48
+ "peerDependencies"
49
+ ];
50
+ var ZIP = {
51
+ directoryEntry: 33639248,
52
+ endDirectoryOffset: 16,
53
+ endEntryCount: 10,
54
+ endOfDirectory: 101010256,
55
+ endRecordSize: 22,
56
+ entryCommentLength: 32,
57
+ entryCompressedSize: 20,
58
+ entryExtraLength: 30,
59
+ entryHeaderSize: 46,
60
+ entryLocalOffset: 42,
61
+ entryMethod: 10,
62
+ entryNameLength: 28,
63
+ localExtraLength: 28,
64
+ localHeader: 67324752,
65
+ localHeaderSize: 30,
66
+ localNameLength: 26,
67
+ methodDeflate: 8,
68
+ methodStored: 0
69
+ };
70
+ var isAsset = (name) => Object.hasOwn(PATCHED_BUN_RELEASE.sha256, name);
71
+ var detectLibc = () => {
72
+ if (process.platform === "android")
73
+ return "android";
74
+ if (MUSL_LOADERS.some((loader) => existsSync(loader)))
75
+ return "musl";
76
+ return "glibc";
77
+ };
78
+ var absoluteHome = (home) => join(home, ".absolutejs");
79
+ var compareVersions = (left, right) => {
80
+ const parse = (version) => version.split("-")[0]?.split(".").map(Number) ?? [];
81
+ const leftParts = parse(left);
82
+ const rightParts = parse(right);
83
+ const differences = Array.from({ length: VERSION_PARTS }, (_, index) => (leftParts[index] ?? 0) - (rightParts[index] ?? 0));
84
+ return differences.find((difference) => difference !== 0) ?? 0;
85
+ };
86
+ var preferencePath = (home) => join(absoluteHome(home), PREFERENCE_FILE);
87
+ var installedPatchedBun = (home = homedir()) => {
88
+ const asset = patchedBunAsset();
89
+ if (!asset)
90
+ return null;
91
+ const path = patchedBunPath(asset, home);
92
+ return existsSync(path) ? path : null;
93
+ };
94
+ var patchedBunAsset = (platform = process.platform, arch = process.arch, libc = detectLibc()) => {
95
+ const cpu = { arm64: "aarch64", x64: "x64" }[arch];
96
+ const system = {
97
+ android: "linux",
98
+ darwin: "darwin",
99
+ freebsd: "freebsd",
100
+ linux: "linux",
101
+ win32: "windows"
102
+ }[platform];
103
+ if (!cpu || !system)
104
+ return null;
105
+ const abi = system === "linux" && libc !== "glibc" ? `-${libc}` : "";
106
+ const name = `bun-${system}-${cpu}${abi}`;
107
+ return isAsset(name) ? name : null;
108
+ };
109
+ var patchedBunPath = (asset, home = homedir()) => join(absoluteHome(home), "bun", PATCHED_BUN_RELEASE.tag, asset, asset.startsWith("bun-windows") ? "bun.exe" : "bun");
110
+ var runtimeSupportsReactFastRefresh = () => {
111
+ const options = { loader: "tsx", reactFastRefresh: true };
112
+ try {
113
+ return new Bun.Transpiler(options).transformSync("export function Probe(){return null;}").includes("$RefreshReg$");
114
+ } catch {
115
+ return false;
116
+ }
117
+ };
118
+ var parsePreference = (parsed) => {
119
+ if (typeof parsed !== "object" || parsed === null)
120
+ return null;
121
+ if (!("decision" in parsed))
122
+ return null;
123
+ if (parsed.decision === "never") {
124
+ const never = { decision: "never" };
125
+ return never;
126
+ }
127
+ if (parsed.decision !== "later" || !("askAfter" in parsed) || typeof parsed.askAfter !== "string")
128
+ return null;
129
+ const later = { askAfter: parsed.askAfter, decision: "later" };
130
+ return later;
131
+ };
132
+ var patchedBunOfferState = (context) => {
133
+ if (context.supported)
134
+ return "runtime-supports-refresh";
135
+ if (context.installed)
136
+ return "installed";
137
+ if (!context.usesReact)
138
+ return "not-a-react-project";
139
+ if (!context.asset)
140
+ return "unsupported-platform";
141
+ if (compareVersions(context.bunVersion, PATCHED_BUN_RELEASE.bunVersion) > 0)
142
+ return "runtime-newer";
143
+ if (context.preference?.decision === "never")
144
+ return "declined";
145
+ if (context.preference?.decision === "later" && new Date(context.preference.askAfter) > context.now)
146
+ return "asked-recently";
147
+ if (context.ci || !context.interactive)
148
+ return "non-interactive";
149
+ return "offer";
150
+ };
151
+ var readPatchedBunPreference = (home = homedir()) => {
152
+ try {
153
+ return parsePreference(JSON.parse(readFileSync(preferencePath(home), "utf-8")));
154
+ } catch {
155
+ return null;
156
+ }
157
+ };
158
+ var findEndOfDirectory = (view) => {
159
+ for (let offset = view.byteLength - ZIP.endRecordSize;offset >= 0; offset--) {
160
+ if (view.getUint32(offset, true) === ZIP.endOfDirectory)
161
+ return offset;
162
+ }
163
+ throw new Error("Not a zip archive.");
164
+ };
165
+ var inflateEntry = (data, method) => {
166
+ if (method === ZIP.methodStored)
167
+ return data;
168
+ if (method === ZIP.methodDeflate)
169
+ return new Uint8Array(inflateRawSync(data));
170
+ throw new Error(`Unsupported zip compression method ${method}.`);
171
+ };
172
+ var readEntryData = (archive, view, entry) => {
173
+ if (view.getUint32(entry.localOffset, true) !== ZIP.localHeader)
174
+ throw new Error("Corrupt zip entry.");
175
+ const dataStart = entry.localOffset + ZIP.localHeaderSize + view.getUint16(entry.localOffset + ZIP.localNameLength, true) + view.getUint16(entry.localOffset + ZIP.localExtraLength, true);
176
+ return inflateEntry(archive.subarray(dataStart, dataStart + entry.compressedSize), entry.method);
177
+ };
178
+ var readZipEntry = (archive, entryName) => {
179
+ const view = new DataView(archive.buffer, archive.byteOffset, archive.byteLength);
180
+ const end = findEndOfDirectory(view);
181
+ const entries = view.getUint16(end + ZIP.endEntryCount, true);
182
+ const decoder = new TextDecoder;
183
+ let cursor = view.getUint32(end + ZIP.endDirectoryOffset, true);
184
+ for (let index = 0;index < entries; index++) {
185
+ if (view.getUint32(cursor, true) !== ZIP.directoryEntry)
186
+ throw new Error("Corrupt zip directory.");
187
+ const nameLength = view.getUint16(cursor + ZIP.entryNameLength, true);
188
+ const nameStart = cursor + ZIP.entryHeaderSize;
189
+ const name = decoder.decode(archive.subarray(nameStart, nameStart + nameLength));
190
+ const entry = {
191
+ compressedSize: view.getUint32(cursor + ZIP.entryCompressedSize, true),
192
+ localOffset: view.getUint32(cursor + ZIP.entryLocalOffset, true),
193
+ method: view.getUint16(cursor + ZIP.entryMethod, true)
194
+ };
195
+ if (name === entryName)
196
+ return readEntryData(archive, view, entry);
197
+ cursor += ZIP.entryHeaderSize + nameLength + view.getUint16(cursor + ZIP.entryExtraLength, true) + view.getUint16(cursor + ZIP.entryCommentLength, true);
198
+ }
199
+ throw new Error(`${entryName} is not in the archive.`);
200
+ };
201
+ var writePatchedBunPreference = (preference, home = homedir()) => {
202
+ const path = preferencePath(home);
203
+ if (preference === null) {
204
+ rmSync(path, { force: true });
205
+ return;
206
+ }
207
+ mkdirSync(dirname(path), { recursive: true });
208
+ writeFileSync(path, `${JSON.stringify(preference, null, "\t")}
209
+ `);
210
+ };
211
+ var sha256 = (bytes) => new Bun.CryptoHasher("sha256").update(bytes).digest("hex");
212
+ var projectUsesReact = (projectDir) => {
213
+ const hasReact = (deps) => typeof deps === "object" && deps !== null && Object.hasOwn(deps, "react");
214
+ try {
215
+ const manifest = JSON.parse(readFileSync(join(projectDir, "package.json"), "utf-8"));
216
+ if (typeof manifest !== "object" || manifest === null)
217
+ return false;
218
+ return DEPENDENCY_FIELDS.some((field) => hasReact(Reflect.get(manifest, field)));
219
+ } catch {
220
+ return false;
221
+ }
222
+ };
223
+ var PATCHED_BUN_EXPLANATION = [
224
+ "React edits reload the page and lose component state on this Bun.",
225
+ `Stock Bun ignores \`reactFastRefresh\` on Bun.Transpiler (${PATCHED_BUN_RELEASE.issue}),`,
226
+ "so React Fast Refresh cannot keep state across edits. Until Bun ships the fix,",
227
+ `AbsoluteJS publishes Bun ${PATCHED_BUN_RELEASE.bunVersion} with only that fix: https://github.com/${PATCHED_BUN_RELEASE.repo}`,
228
+ "It installs to ~/.absolutejs/bun and only `absolute dev` uses it; your `bun` is unchanged."
229
+ ];
230
+ var installPatchedBun = async (options = {}) => {
231
+ const asset = options.asset ?? patchedBunAsset();
232
+ if (!asset)
233
+ throw new Error(`No patched Bun build for ${process.platform}-${process.arch}.`);
234
+ const home = options.home ?? homedir();
235
+ const log = options.log ?? console.log;
236
+ const url = patchedBunDownloadUrl(asset);
237
+ log(`Downloading ${url}`);
238
+ const response = await (options.fetchImpl ?? fetch)(url);
239
+ if (!response.ok)
240
+ throw new Error(`Download failed: ${url} answered ${response.status}.`);
241
+ const archive = new Uint8Array(await response.arrayBuffer());
242
+ const expected = (options.checksums ?? PATCHED_BUN_RELEASE.sha256)[asset];
243
+ const actual = sha256(archive);
244
+ if (actual !== expected)
245
+ throw new Error(`${asset}.zip does not match its pinned SHA-256 (expected ${expected}, got ${actual}); not installing it.`);
246
+ const target = patchedBunPath(asset, home);
247
+ const binary = readZipEntry(archive, `${asset}/${target.endsWith(".exe") ? "bun.exe" : "bun"}`);
248
+ mkdirSync(dirname(target), { recursive: true });
249
+ const staging = `${target}.${process.pid}.tmp`;
250
+ writeFileSync(staging, binary);
251
+ chmodSync(staging, EXECUTABLE_MODE);
252
+ renameSync(staging, target);
253
+ log(`Installed patched Bun ${PATCHED_BUN_RELEASE.bunVersion} at ${target}`);
254
+ return target;
255
+ };
256
+ var parsePatchedBunChoice = (answer) => {
257
+ const normalized = answer.trim().toLowerCase();
258
+ const choices = [
259
+ {
260
+ choice: "install",
261
+ matches: normalized === "" || normalized === "1" || normalized.startsWith("i")
262
+ },
263
+ {
264
+ choice: "later",
265
+ matches: normalized === "2" || normalized.startsWith("l")
266
+ },
267
+ {
268
+ choice: "never",
269
+ matches: normalized === "3" || normalized.startsWith("d") || normalized.startsWith("n")
270
+ }
271
+ ];
272
+ return choices.find(({ matches }) => matches)?.choice ?? null;
273
+ };
274
+ var patchedBunDownloadUrl = (asset) => `https://github.com/${PATCHED_BUN_RELEASE.repo}/releases/download/${PATCHED_BUN_RELEASE.tag}/${asset}.zip`;
275
+ var verifyPatchedBun = (path) => {
276
+ const probe = spawnSync(path, [
277
+ "-e",
278
+ 'console.log(Bun.version, new Bun.Transpiler({loader:"tsx",reactFastRefresh:true}).transformSync("export function P(){return null}").includes("$RefreshReg$"))'
279
+ ], { encoding: "utf-8", timeout: PROBE_TIMEOUT_MS });
280
+ return probe.status === 0 && probe.stdout.trim() === `${PATCHED_BUN_RELEASE.bunVersion} true`;
281
+ };
282
+ var askUntilAnswered = async (prompt) => {
283
+ const choice = parsePatchedBunChoice(await prompt.question("Choose [1-3] (default 1): "));
284
+ return choice ?? askUntilAnswered(prompt);
285
+ };
286
+ var askForChoice = async () => {
287
+ const prompt = createInterface({
288
+ input: process.stdin,
289
+ output: process.stdout
290
+ });
291
+ try {
292
+ console.log(`
293
+ ${PATCHED_BUN_EXPLANATION.join(`
294
+ `)}
295
+ `);
296
+ console.log(` 1) Install now
297
+ 2) Ask me later
298
+ 3) Don't ask again
299
+ `);
300
+ return await askUntilAnswered(prompt);
301
+ } finally {
302
+ prompt.close();
303
+ }
304
+ };
305
+ var installVerified = async (home) => {
306
+ const path = await installPatchedBun({ home });
307
+ if (!verifyPatchedBun(path)) {
308
+ rmSync(path, { force: true });
309
+ throw new Error("the installed binary did not pass its check; removed it");
310
+ }
311
+ writePatchedBunPreference(null, home);
312
+ return path;
313
+ };
314
+ var recordDecline = (choice, home) => {
315
+ if (choice === "never") {
316
+ writePatchedBunPreference({ decision: "never" }, home);
317
+ console.log("OK, not asking again. `absolute bun-patch install` installs it any time; `absolute bun-patch reset` asks again.");
318
+ return;
319
+ }
320
+ writePatchedBunPreference({
321
+ askAfter: new Date(Date.now() + ASK_LATER_MS).toISOString(),
322
+ decision: "later"
323
+ }, home);
324
+ console.log("OK, asking again tomorrow. `absolute bun-patch install` installs it any time.");
325
+ };
326
+ var resolveDevBunExecutable = async (projectDir = process.cwd(), home = homedir()) => {
327
+ if (process.env.ABSOLUTE_PATCHED_BUN === "0")
328
+ return "bun";
329
+ const installed = installedPatchedBun(home);
330
+ if (installed) {
331
+ console.log(`Using AbsoluteJS patched Bun ${PATCHED_BUN_RELEASE.bunVersion} for React Fast Refresh (${installed}).`);
332
+ return installed;
333
+ }
334
+ const state = patchedBunOfferState({
335
+ asset: patchedBunAsset(),
336
+ bunVersion: Bun.version,
337
+ ci: Boolean(process.env.CI),
338
+ installed: false,
339
+ interactive: Boolean(process.stdin.isTTY && process.stdout.isTTY),
340
+ now: new Date,
341
+ preference: readPatchedBunPreference(home),
342
+ supported: runtimeSupportsReactFastRefresh(),
343
+ usesReact: projectUsesReact(projectDir)
344
+ });
345
+ if (state === "non-interactive")
346
+ console.log(`React edits will reload the page: stock Bun lacks the reactFastRefresh fix (${PATCHED_BUN_RELEASE.issue}). Run \`absolute bun-patch install\` to use AbsoluteJS's patched Bun.`);
347
+ if (state !== "offer")
348
+ return "bun";
349
+ const choice = await askForChoice();
350
+ if (choice !== "install") {
351
+ recordDecline(choice, home);
352
+ return "bun";
353
+ }
354
+ try {
355
+ return await installVerified(home);
356
+ } catch (error) {
357
+ console.error(`Could not install the patched Bun (${error instanceof Error ? error.message : String(error)}). Continuing with your bun; React edits will reload the page.`);
358
+ return "bun";
359
+ }
360
+ };
361
+ var describePreference = (preference) => {
362
+ if (preference === null)
363
+ return "will ask";
364
+ if (preference.decision === "never")
365
+ return "won't ask again";
366
+ return `asking again after ${preference.askAfter}`;
367
+ };
368
+ var describeInstalled = (installed) => {
369
+ if (!installed)
370
+ return "no";
371
+ if (verifyPatchedBun(installed))
372
+ return installed;
373
+ return `${installed} (FAILED its check; run \`absolute bun-patch install\`)`;
374
+ };
375
+ var printStatus = (home) => {
376
+ const supported = runtimeSupportsReactFastRefresh();
377
+ console.log(`Release: ${PATCHED_BUN_RELEASE.tag} (${PATCHED_BUN_RELEASE.issue})`);
378
+ console.log(`Platform build: ${patchedBunAsset() ?? "none for this platform"}`);
379
+ console.log(`Your bun: ${Bun.version} (${supported ? "already has the fix" : "stock, no fix"})`);
380
+ console.log(`Installed: ${describeInstalled(installedPatchedBun(home))}`);
381
+ console.log(`Prompt: ${describePreference(readPatchedBunPreference(home))}`);
382
+ };
383
+ var bunPatch = async (args, home = homedir()) => {
384
+ const [action = "status"] = args;
385
+ if (action === "status") {
386
+ printStatus(home);
387
+ return;
388
+ }
389
+ if (action === "install") {
390
+ await installVerified(home);
391
+ console.log("`absolute dev` will use it from now on.");
392
+ return;
393
+ }
394
+ if (action === "remove") {
395
+ rmSync(join(absoluteHome(home), "bun"), {
396
+ force: true,
397
+ recursive: true
398
+ });
399
+ console.log("Removed the patched Bun. `absolute dev` uses your bun again.");
400
+ return;
401
+ }
402
+ if (action === "reset") {
403
+ writePatchedBunPreference(null, home);
404
+ console.log("`absolute dev` will offer the patched Bun again.");
405
+ return;
406
+ }
407
+ throw new Error(`Unknown bun-patch action "${action}". Use status, install, remove or reset.`);
408
+ };
409
+
410
+ export { PATCHED_BUN_RELEASE, installedPatchedBun, patchedBunAsset, patchedBunPath, runtimeSupportsReactFastRefresh, patchedBunOfferState, readPatchedBunPreference, readZipEntry, writePatchedBunPreference, PATCHED_BUN_EXPLANATION, installPatchedBun, parsePatchedBunChoice, patchedBunDownloadUrl, verifyPatchedBun, resolveDevBunExecutable, bunPatch };
package/dist/cli/index.js CHANGED
@@ -58,13 +58,16 @@ if (command === "dev") {
58
58
  }
59
59
  const positionalArgs = stripNamedArgs("--config", "--android-device", "--ios-device").filter((arg) => arg !== "--no-mobile" && arg !== "--eager");
60
60
  const serverEntry = positionalArgs[0] ?? DEFAULT_SERVER_ENTRY;
61
- const { dev } = await import("./dev-pdefc9ac.js");
61
+ const { dev } = await import("./dev-s7kct3xq.js");
62
62
  await dev(serverEntry, configPath, {
63
63
  androidDevice,
64
64
  eager: args.includes("--eager"),
65
65
  iosDevice,
66
66
  mobile: !args.includes("--no-mobile")
67
67
  });
68
+ } else if (command === "bun-patch") {
69
+ const { bunPatch } = await import("./patchedBun-zjtrn2s4.js");
70
+ await bunPatch(args);
68
71
  } else if (command === "start") {
69
72
  sendTelemetryEvent("cli:command", { command });
70
73
  const outdir = parseNamedArg("--outdir");
@@ -208,7 +211,7 @@ if (command === "dev") {
208
211
  sendTelemetryEvent("cli:command", {
209
212
  command: `mobile:${workspaceCommand ?? "unknown"}`
210
213
  });
211
- const { runMobile } = await import("./mobile-00bqw12c.js");
214
+ const { runMobile } = await import("./mobile-6kdsrhxy.js");
212
215
  try {
213
216
  await runMobile(args);
214
217
  } catch (error) {
@@ -267,6 +270,7 @@ if (command === "dev") {
267
270
  console.error(" routes [--json] List every route (pages + API) of a running dev server");
268
271
  console.error(" typecheck Run type checkers for all frameworks");
269
272
  console.error(" migrate Report what moving this project to AbsoluteJS involves");
273
+ console.error(" bun-patch [status|install|remove|reset] Bun with the React Fast Refresh fix (oven-sh/bun#32919)");
270
274
  console.error(" telemetry Manage anonymous telemetry");
271
275
  console.error(" tunnel-relay Run the public reverse-tunnel relay (for webhook dev)");
272
276
  process.exit(1);
@@ -0,0 +1,38 @@
1
+ // @bun
2
+ import {
3
+ PATCHED_BUN_EXPLANATION,
4
+ PATCHED_BUN_RELEASE,
5
+ bunPatch,
6
+ installPatchedBun,
7
+ installedPatchedBun,
8
+ parsePatchedBunChoice,
9
+ patchedBunAsset,
10
+ patchedBunDownloadUrl,
11
+ patchedBunOfferState,
12
+ patchedBunPath,
13
+ readPatchedBunPreference,
14
+ readZipEntry,
15
+ resolveDevBunExecutable,
16
+ runtimeSupportsReactFastRefresh,
17
+ verifyPatchedBun,
18
+ writePatchedBunPreference
19
+ } from "./index-hty8111z.js";
20
+ import"./index-mywsk201.js";
21
+ export {
22
+ PATCHED_BUN_EXPLANATION,
23
+ PATCHED_BUN_RELEASE,
24
+ bunPatch,
25
+ installPatchedBun,
26
+ installedPatchedBun,
27
+ parsePatchedBunChoice,
28
+ patchedBunAsset,
29
+ patchedBunDownloadUrl,
30
+ patchedBunOfferState,
31
+ patchedBunPath,
32
+ readPatchedBunPreference,
33
+ readZipEntry,
34
+ resolveDevBunExecutable,
35
+ runtimeSupportsReactFastRefresh,
36
+ verifyPatchedBun,
37
+ writePatchedBunPreference
38
+ };
@@ -7,7 +7,6 @@ import type {} from '../../../types/globals';
7
7
  import { hideErrorOverlay } from '../errorOverlay';
8
8
  import { detectCurrentFramework } from '../frameworkDetect';
9
9
  import { sendAbsoluteHmrTiming } from '../hmrTiming';
10
- import { swapCSSStylesheet } from '../cssUtils';
11
10
 
12
11
  const reloadReactPage = () => {
13
12
  const url = new URL(window.location.href);
@@ -21,7 +20,9 @@ export const handleReactUpdate = (message: {
21
20
  hasCSSChanges?: boolean;
22
21
  hasComponentChanges?: boolean;
23
22
  manifest?: Record<string, string>;
23
+ moduleUrls?: string[];
24
24
  pageModuleUrl?: string;
25
+ pageModuleUrls?: Record<string, string>;
25
26
  primarySource?: string;
26
27
  serverDuration?: number;
27
28
  };
@@ -30,30 +31,26 @@ export const handleReactUpdate = (message: {
30
31
  const currentFramework = detectCurrentFramework();
31
32
  if (currentFramework !== 'react') return;
32
33
 
33
- const hasComponentChanges = message.data.hasComponentChanges !== false;
34
- const hasCSSChanges = message.data.hasCSSChanges === true;
35
- const cssPath =
36
- message.data.manifest && message.data.manifest.ReactExampleCSS;
37
- if (!hasComponentChanges && hasCSSChanges && cssPath) {
38
- const clientStart = performance.now();
39
- void reloadReactCSS(cssPath).then((applied) => {
40
- sendAbsoluteHmrTiming({
41
- clientStart,
42
- kind: 'css',
43
- outcome: applied ? 'applied' : 'failed',
44
- serverMs: message.data.serverDuration,
45
- updateId: message.timestamp
46
- });
47
- });
48
-
49
- return;
50
- }
34
+ // BUN-REACT-REFRESH-LEGACY: the remount/reload fallback for a dev server on
35
+ // stock Bun, which cannot emit React Fast Refresh registrations. Remove it,
36
+ // and the fastRefreshSupported message field, once the minimum Bun has the fix.
51
37
  if (message.data.fastRefreshSupported === false) {
52
- const { pageModuleUrl } = message.data;
38
+ // The remount re-renders the whole page, so it needs this page's
39
+ // module; a changed child's module would be rendered as the page.
40
+ const componentKey = window.__REACT_COMPONENT_KEY__;
41
+ const pageUrls = message.data.pageModuleUrls;
42
+ if (pageUrls && componentKey && !pageUrls[componentKey]) {
43
+ // This edit does not reach the page on screen.
44
+ return;
45
+ }
46
+ const remountUrl =
47
+ pageUrls && componentKey
48
+ ? pageUrls[componentKey]
49
+ : message.data.pageModuleUrl;
53
50
  const remount = window.__ABS_REACT_REMOUNT__;
54
- if (pageModuleUrl && remount) {
51
+ if (remountUrl && remount) {
55
52
  applyRemountImport(
56
- pageModuleUrl,
53
+ remountUrl,
57
54
  remount,
58
55
  message.data.serverDuration,
59
56
  message.timestamp
@@ -75,10 +72,15 @@ export const handleReactUpdate = (message: {
75
72
  const refreshRuntime = window.$RefreshRuntime$;
76
73
  const { serverDuration } = message.data;
77
74
  const { pageModuleUrl } = message.data;
75
+ const fallbackUrls = pageModuleUrl ? [pageModuleUrl] : [];
76
+ const moduleUrls =
77
+ message.data.moduleUrls && message.data.moduleUrls.length > 0
78
+ ? message.data.moduleUrls
79
+ : fallbackUrls;
78
80
 
79
- if (pageModuleUrl && refreshRuntime) {
81
+ if (moduleUrls.length > 0 && refreshRuntime) {
80
82
  applyRefreshImport(
81
- pageModuleUrl,
83
+ moduleUrls,
82
84
  refreshRuntime,
83
85
  serverDuration,
84
86
  message.timestamp
@@ -117,13 +119,16 @@ const finishUpdate = (
117
119
  };
118
120
 
119
121
  const applyRefreshImport = (
120
- moduleUrl: string,
122
+ moduleUrls: string[],
121
123
  refreshRuntime: { performReactRefresh: () => unknown },
122
124
  serverDuration?: number,
123
125
  updateId?: number
124
126
  ) => {
125
127
  const clientStart = performance.now();
126
- import(`${moduleUrl}?t=${Date.now()}`)
128
+ const stamp = Date.now();
129
+ // Every edited module has to run before the refresh, or a page that
130
+ // now renders a new child refreshes without it.
131
+ Promise.all(moduleUrls.map((url) => import(`${url}?t=${stamp}`)))
127
132
  .then(() => {
128
133
  refreshRuntime.performReactRefresh();
129
134
  finishUpdate(clientStart, serverDuration, updateId);
@@ -175,16 +180,3 @@ const applyRemountImport = (
175
180
  reloadReactPage();
176
181
  });
177
182
  };
178
-
179
- const reloadReactCSS = (cssPath: string) =>
180
- swapCSSStylesheet(cssPath, (href) => {
181
- const hrefBase = (href.split('?')[0] ?? '').split('/').pop() ?? '';
182
- const cssPathBase =
183
- (cssPath.split('?')[0] ?? '').split('/').pop() ?? '';
184
-
185
- return (
186
- hrefBase === cssPathBase ||
187
- href.includes('react-example') ||
188
- cssPathBase.includes(hrefBase)
189
- );
190
- });
@@ -200,6 +200,7 @@ type HMRMessage = {
200
200
  affectedFrameworks?: string[];
201
201
  column?: number;
202
202
  error?: string;
203
+ // BUN-REACT-REFRESH-LEGACY: false only on stock Bun (see handlers/react.ts).
203
204
  fastRefreshSupported?: boolean;
204
205
  file?: string;
205
206
  framework?: string;
@@ -209,8 +210,10 @@ type HMRMessage = {
209
210
  line?: number;
210
211
  lineText?: string;
211
212
  manifest?: Record<string, string>;
213
+ moduleUrls?: string[];
212
214
  moduleVersions?: Record<string, number>;
213
215
  pageModuleUrl?: string;
216
+ pageModuleUrls?: Record<string, string>;
214
217
  primarySource?: string;
215
218
  scriptUrl?: string;
216
219
  serverDuration?: number;
@@ -633,6 +633,16 @@ var markBootAt = (label, epochMs) => {
633
633
  };
634
634
  var processStartEpochMs = () => Date.now() - process.uptime() * MILLISECONDS_IN_A_SECOND;
635
635
 
636
+ // src/dev/hotRejectionGuard.ts
637
+ var installHotRejectionGuard = () => {
638
+ if (globalThis.__absoluteHotRejectionGuard)
639
+ return;
640
+ globalThis.__absoluteHotRejectionGuard = true;
641
+ process.on("unhandledRejection", (reason) => {
642
+ console.error(reason);
643
+ });
644
+ };
645
+
636
646
  // src/dev/importCost/hint.ts
637
647
  var ENTRY_GRAPH_START = "server entry import start";
638
648
  var ENTRY_GRAPH_END = "framework runtime imported";
@@ -733,6 +743,7 @@ var isStaleAbsoluteServerEntryCopy = (name, currentPid = process.pid, ownerIsAli
733
743
  adoptParentBootMarks();
734
744
  markBootAt("child process start", processStartEpochMs());
735
745
  markBoot("bootstrap imports evaluated");
746
+ installHotRejectionGuard();
736
747
  var originalEntry = process.env.ABSOLUTE_SERVER_ENTRY;
737
748
  if (!originalEntry) {
738
749
  throw new Error("ABSOLUTE_SERVER_ENTRY is required by the AbsoluteJS dev bootstrap");