@akanjs/cli 3.0.0-beta.23 → 3.0.0-beta.25
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.
- package/.build-stamp +1 -1
- package/{CodeAgent-0dh9fzzg.js → CodeAgent-p65jja35.js} +2 -2
- package/{CodeTui-drwdvwg9.js → CodeTui-5xr8kxx4.js} +1 -1
- package/TunnelShare-cwh9ezsm.js +9 -0
- package/{agent.command-ttn09rx3.js → agent.command-rp505zxa.js} +7 -7
- package/akanCode.js +2 -2
- package/{application.command-b6rck8d6.js → application.command-2x9jqy0k.js} +7 -7
- package/{applicationBuildRunner-vh74vrhg.js → applicationBuildRunner-tm0vba5t.js} +4 -4
- package/buildBatch.proc.js +99 -12
- package/{cloud.command-gwdypy7k.js → cloud.command-j6m607bp.js} +12 -12
- package/cloud.script-r5t7nhee.js +20 -0
- package/{code.command-1jgk14tc.js → code.command-jhrjttm1.js} +7 -7
- package/{context.command-4vpjmg22.js → context.command-xncdvv7n.js} +14 -14
- package/context.runner-pjy89k2s.js +21 -0
- package/{dependencyScanner-ye2e6pdz.js → dependencyScanner-06z2dmrr.js} +2 -2
- package/{fileSys-5ngz93ga.js → fileSys-wzzrntkw.js} +1 -1
- package/{guideline.command-7jk47fgq.js → guideline.command-d5qmxx29.js} +4 -4
- package/guidelines/agentRule/agentRule.instruction.md +7 -0
- package/guidelines/conventions/conventions.instruction.md +7 -5
- package/incrementalBuilder.proc.js +544 -72
- package/{index-ga6hygxr.js → index-0n9q1f23.js} +3 -3
- package/{index-3t7g9e4r.js → index-2ahj896a.js} +1 -1
- package/{index-smme4r47.js → index-2wc41k5w.js} +1 -1
- package/{index-khpfpcsd.js → index-48k96p55.js} +27 -7
- package/{index-776sswhv.js → index-4mgbzgb5.js} +281 -651
- package/{index-2gh9sndr.js → index-50rq2qms.js} +4 -4
- package/{index-5b6tavv3.js → index-52w0fvh8.js} +15 -1
- package/{index-yjkte9aj.js → index-56xwsgyn.js} +3 -3
- package/{index-rmcw8yc8.js → index-58cyna2s.js} +2 -2
- package/{index-1db2cw0y.js → index-5mm3g24t.js} +2 -2
- package/{index-wp43xrjv.js → index-6dt6zft6.js} +4 -4
- package/{index-vj1cwwy5.js → index-7yxndzea.js} +7 -7
- package/{index-q44jbw48.js → index-860zjnr1.js} +1 -1
- package/{index-jn07hxrr.js → index-8k93esfr.js} +6 -6
- package/{index-22d57v4c.js → index-94s080qm.js} +3 -3
- package/{index-yy4s1j72.js → index-d65fzxvd.js} +1 -1
- package/{index-m0ms3n1c.js → index-da72pfdv.js} +7 -7
- package/index-dt1x62ec.js +4108 -0
- package/{index-4dn1p115.js → index-ggfhz9ab.js} +4 -4
- package/{index-9629w7ns.js → index-hzy4pe24.js} +3 -3
- package/{index-jg5e15db.js → index-pb10njz7.js} +192 -58
- package/{index-mgfx3mfs.js → index-per2kqy9.js} +4 -4
- package/{index-aaz7n8s2.js → index-qm4fppfk.js} +4 -4
- package/{index-ya5qbmyg.js → index-t02g6dpn.js} +3 -3
- package/{index-hb8sgepf.js → index-tpdn30ya.js} +1 -1
- package/{index-rnt66vym.js → index-v4hnkqz9.js} +1 -1
- package/{index-pp54p5zk.js → index-xfgdmm5d.js} +2 -2
- package/{index-tgjy8xf3.js → index-zazvn45w.js} +4 -4
- package/{index-0bq14vr1.js → index-zzvvc845.js} +1 -1
- package/index.js +22 -22
- package/{library.command-ccb1cp92.js → library.command-sv6g1h2g.js} +4 -4
- package/{localRegistry.command-cxvgn7fr.js → localRegistry.command-4kj56531.js} +11 -11
- package/{module.command-5k6h407d.js → module.command-ccdeder9.js} +6 -6
- package/{package.command-e6ntvpez.js → package.command-xmw605b1.js} +5 -5
- package/package.json +4 -4
- package/{page.command-v4f90wb8.js → page.command-ky7vafsn.js} +4 -4
- package/{primitive.command-g6km57q5.js → primitive.command-bh6j8nfs.js} +7 -7
- package/{prompter-ptaa3nk0.js → prompter-76k56b37.js} +2 -2
- package/{quality.command-de3p36pz.js → quality.command-rqqrpe98.js} +3 -3
- package/{repair.command-d3x1javq.js → repair.command-n9nsm9qe.js} +6 -6
- package/{scalar.command-7ezrac2b.js → scalar.command-t09gmck8.js} +5 -5
- package/{subspace.command-0cep72ce.js → subspace.command-ygfdqpbj.js} +6 -6
- package/templates/workspaceRoot/package.json.template +1 -3
- package/{tunnel.command-hsrv2mny.js → tunnel.command-4g95vhsn.js} +5 -5
- package/{workflow.command-z2p32nmf.js → workflow.command-b91ph8sx.js} +11 -11
- package/{workspace.command-axwscbnn.js → workspace.command-p44d21qf.js} +24 -24
- package/README.ko.md +0 -72
- package/README.md +0 -88
- package/TunnelShare-scf4e4pd.js +0 -9
- package/cloud.script-at2beffs.js +0 -20
- package/context.runner-ehjs419v.js +0 -21
- package/index-fgq75haa.js +0 -2084
- package/templates/workspaceRoot/patches/react-dom@19.2.7.patch.template +0 -62
|
@@ -3,27 +3,33 @@ import {
|
|
|
3
3
|
ApplicationBuildReporter,
|
|
4
4
|
HmrWatcher,
|
|
5
5
|
WatchRootResolver
|
|
6
|
-
} from "./index-
|
|
6
|
+
} from "./index-48k96p55.js";
|
|
7
7
|
import {
|
|
8
8
|
CodegenLock,
|
|
9
9
|
WorkspaceExecutor,
|
|
10
10
|
AppExecutor
|
|
11
|
-
} from "./index-
|
|
12
|
-
import"./index-
|
|
11
|
+
} from "./index-0n9q1f23.js";
|
|
12
|
+
import"./index-52w0fvh8.js";
|
|
13
13
|
import {
|
|
14
14
|
GraphClientEntryDiscovery,
|
|
15
15
|
RouteClientBuilder,
|
|
16
16
|
AutoImportSync,
|
|
17
|
+
ServerGraphFile,
|
|
18
|
+
CsrDevPatcher,
|
|
19
|
+
CsrDevBundler,
|
|
17
20
|
DevChangePlanner,
|
|
18
|
-
DevGeneratedIndexSync
|
|
19
|
-
|
|
21
|
+
DevGeneratedIndexSync,
|
|
22
|
+
SsrDevBundler
|
|
23
|
+
} from "./index-dt1x62ec.js";
|
|
20
24
|
import {
|
|
21
25
|
prepareDevWatchBatch
|
|
22
26
|
} from "./index-a5vmm6x4.js";
|
|
23
27
|
|
|
24
28
|
// pkgs/@akanjs/devkit/incrementalBuilder/incrementalBuilder.proc.ts
|
|
29
|
+
import { rm } from "fs/promises";
|
|
25
30
|
import path2 from "path";
|
|
26
31
|
import { Logger as Logger2 } from "akanjs/common";
|
|
32
|
+
import { CSR_DEV_PATCHING_MARKER } from "akanjs/server/hmr/csrDevManifest";
|
|
27
33
|
|
|
28
34
|
// pkgs/@akanjs/devkit/incrementalBuilder/buildBatchRunner.ts
|
|
29
35
|
import path from "path";
|
|
@@ -61,7 +67,9 @@ class BuildBatchRunner {
|
|
|
61
67
|
const started = Date.now();
|
|
62
68
|
const entry = await this.#resolveEntry();
|
|
63
69
|
let result = null;
|
|
64
|
-
const
|
|
70
|
+
const reported = new Map;
|
|
71
|
+
const payload = request.trace ? { ...request, trace: { ...request.trace, spawnAt: Date.now() } } : request;
|
|
72
|
+
const proc = Bun.spawn(["bun", entry, JSON.stringify(payload)], {
|
|
65
73
|
cwd: this.#cwd,
|
|
66
74
|
env: process.env,
|
|
67
75
|
stdio: ["ignore", "inherit", "inherit"],
|
|
@@ -71,8 +79,11 @@ class BuildBatchRunner {
|
|
|
71
79
|
return;
|
|
72
80
|
if (message.type === "build-batch-result")
|
|
73
81
|
result = message.data;
|
|
74
|
-
else
|
|
82
|
+
else {
|
|
83
|
+
if (message.type === "build-status")
|
|
84
|
+
reported.set(message.data.phase, message.data.message);
|
|
75
85
|
onMessage(message);
|
|
86
|
+
}
|
|
76
87
|
}
|
|
77
88
|
});
|
|
78
89
|
const exitCode = await proc.exited;
|
|
@@ -82,11 +93,14 @@ class BuildBatchRunner {
|
|
|
82
93
|
}
|
|
83
94
|
const message = proc.signalCode ? `build worker was killed by ${proc.signalCode} before reporting a result${proc.signalCode === "SIGKILL" ? " \u2014 most often the kernel OOM killer; check the sandbox's memory limit" : ""}` : `build worker exited with code ${exitCode} before reporting a result`;
|
|
84
95
|
this.#logger.error(`[build-batch] generation=${request.generation} ${message}`);
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
96
|
+
const crashedNeeds = request.needs.filter((need) => !reported.has(need));
|
|
97
|
+
const failed = request.needs.flatMap((need) => {
|
|
98
|
+
if (!reported.has(need))
|
|
99
|
+
return [[need, message]];
|
|
100
|
+
const error = reported.get(need);
|
|
101
|
+
return error ? [[need, error]] : [];
|
|
102
|
+
});
|
|
103
|
+
return { generation: request.generation, errors: Object.fromEntries(failed), crashed: true, crashedNeeds };
|
|
90
104
|
}
|
|
91
105
|
}
|
|
92
106
|
|
|
@@ -123,6 +137,90 @@ class BuilderChannel {
|
|
|
123
137
|
}
|
|
124
138
|
}
|
|
125
139
|
|
|
140
|
+
// pkgs/@akanjs/devkit/incrementalBuilder/builderWorkQueue.ts
|
|
141
|
+
class BuilderWorkQueue {
|
|
142
|
+
#runBatch;
|
|
143
|
+
#onSettled;
|
|
144
|
+
#pending = [];
|
|
145
|
+
#running = null;
|
|
146
|
+
#idle = Promise.resolve();
|
|
147
|
+
#wakeIdle = null;
|
|
148
|
+
constructor({ runBatch, onSettled }) {
|
|
149
|
+
this.#runBatch = runBatch;
|
|
150
|
+
this.#onSettled = onSettled;
|
|
151
|
+
}
|
|
152
|
+
get size() {
|
|
153
|
+
return this.#pending.length + (this.#running ? 1 : 0);
|
|
154
|
+
}
|
|
155
|
+
enqueueBatch(batch) {
|
|
156
|
+
const tail = this.#pending.at(-1);
|
|
157
|
+
if (tail?.batch) {
|
|
158
|
+
tail.batch = BuilderWorkQueue.merge(tail.batch, batch);
|
|
159
|
+
return tail.settle.promise;
|
|
160
|
+
}
|
|
161
|
+
const job = this.#job("batch", batch, async () => {
|
|
162
|
+
return;
|
|
163
|
+
});
|
|
164
|
+
job.run = async () => await this.#runBatch(job.batch ?? batch);
|
|
165
|
+
return this.#push(job);
|
|
166
|
+
}
|
|
167
|
+
enqueue(label, run) {
|
|
168
|
+
let result;
|
|
169
|
+
const job = this.#job(label, null, async () => {
|
|
170
|
+
result = await run();
|
|
171
|
+
});
|
|
172
|
+
return this.#push(job).then(() => result);
|
|
173
|
+
}
|
|
174
|
+
async drain() {
|
|
175
|
+
while (this.size > 0)
|
|
176
|
+
await this.#idle;
|
|
177
|
+
}
|
|
178
|
+
static merge(into, next) {
|
|
179
|
+
const discovery = into.discovery || next.discovery ? {
|
|
180
|
+
files: [...new Set([...into.discovery?.files ?? [], ...next.discovery?.files ?? []])],
|
|
181
|
+
refresh: !!into.discovery?.refresh || !!next.discovery?.refresh,
|
|
182
|
+
generation: Math.max(into.discovery?.generation ?? 0, next.discovery?.generation ?? 0)
|
|
183
|
+
} : undefined;
|
|
184
|
+
return {
|
|
185
|
+
generation: Math.max(into.generation, next.generation),
|
|
186
|
+
needs: [...new Set([...into.needs, ...next.needs])],
|
|
187
|
+
changedFiles: [...new Set([...into.changedFiles, ...next.changedFiles])],
|
|
188
|
+
trace: next.trace ?? into.trace,
|
|
189
|
+
...discovery ? { discovery } : {}
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
#job(label, batch, run) {
|
|
193
|
+
return { label, batch, run, settle: Promise.withResolvers() };
|
|
194
|
+
}
|
|
195
|
+
#push(job) {
|
|
196
|
+
if (this.size === 0)
|
|
197
|
+
this.#idle = new Promise((resolve) => {
|
|
198
|
+
this.#wakeIdle = resolve;
|
|
199
|
+
});
|
|
200
|
+
this.#pending.push(job);
|
|
201
|
+
if (!this.#running)
|
|
202
|
+
this.#pump();
|
|
203
|
+
return job.settle.promise;
|
|
204
|
+
}
|
|
205
|
+
async#pump() {
|
|
206
|
+
for (let job = this.#pending.shift();job; job = this.#pending.shift()) {
|
|
207
|
+
this.#running = job;
|
|
208
|
+
const started = Date.now();
|
|
209
|
+
try {
|
|
210
|
+
await job.run();
|
|
211
|
+
job.settle.resolve();
|
|
212
|
+
} catch (error) {
|
|
213
|
+
job.settle.reject(error);
|
|
214
|
+
} finally {
|
|
215
|
+
this.#running = null;
|
|
216
|
+
this.#onSettled?.(job.label, Date.now() - started);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
this.#wakeIdle?.();
|
|
220
|
+
this.#wakeIdle = null;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
126
224
|
// pkgs/@akanjs/devkit/incrementalBuilder/incrementalBuilder.proc.ts
|
|
127
225
|
class IncrementalBuilder {
|
|
128
226
|
#logger = new Logger2("IncrementalBuilder");
|
|
@@ -137,8 +235,29 @@ class IncrementalBuilder {
|
|
|
137
235
|
#autoImportSync;
|
|
138
236
|
#watcher = null;
|
|
139
237
|
#generation = 0;
|
|
238
|
+
#discoveredThrough = 0;
|
|
140
239
|
#csrActive = IncrementalBuilder.#csrArmedByEnv();
|
|
141
|
-
#
|
|
240
|
+
#csrBundler;
|
|
241
|
+
#patcher;
|
|
242
|
+
#csrGate = Promise.resolve();
|
|
243
|
+
#csrArming = false;
|
|
244
|
+
#csrArms = 0;
|
|
245
|
+
#crashRetryTimer = null;
|
|
246
|
+
#crashRetryEntries = new Set;
|
|
247
|
+
#csrWasActive = false;
|
|
248
|
+
#csrArmSucceeded = false;
|
|
249
|
+
#ssrBundler;
|
|
250
|
+
#ssrPatcher;
|
|
251
|
+
#ssrLock = Promise.resolve();
|
|
252
|
+
#delegations = new Map;
|
|
253
|
+
#ssrArming = null;
|
|
254
|
+
#ssrBroken = false;
|
|
255
|
+
#ssrCrashedAt = null;
|
|
256
|
+
static #crashRetryMs = 1e4;
|
|
257
|
+
#beside = new Set;
|
|
258
|
+
#barrelFailed = false;
|
|
259
|
+
#fastQueue = Promise.resolve();
|
|
260
|
+
#workQueue;
|
|
142
261
|
#inFlight = 0;
|
|
143
262
|
#workCount = 0;
|
|
144
263
|
#shuttingDown = false;
|
|
@@ -152,6 +271,7 @@ class IncrementalBuilder {
|
|
|
152
271
|
this.#optimizedFonts = options.optimizedFonts;
|
|
153
272
|
this.#discovery = options.discovery;
|
|
154
273
|
this.#generation = options.initialGeneration ?? 0;
|
|
274
|
+
this.#discoveredThrough = this.#generation;
|
|
155
275
|
this.#batchRunner = new BuildBatchRunner({
|
|
156
276
|
workspaceRoot: options.app.workspace.workspaceRoot,
|
|
157
277
|
cwd: options.app.cwdPath
|
|
@@ -159,14 +279,27 @@ class IncrementalBuilder {
|
|
|
159
279
|
this.#changePlanner = new DevChangePlanner({ workspaceRoot: options.app.workspace.workspaceRoot });
|
|
160
280
|
this.#generatedIndexSync = new DevGeneratedIndexSync({ workspaceRoot: options.app.workspace.workspaceRoot });
|
|
161
281
|
this.#autoImportSync = new AutoImportSync({ workspaceRoot: options.app.workspace.workspaceRoot });
|
|
282
|
+
this.#csrBundler = new CsrDevBundler(options.app);
|
|
283
|
+
this.#patcher = process.env.AKAN_DEV_CSR_PATCHER === "off" ? null : new CsrDevPatcher(this.#csrBundler, { resident: true });
|
|
284
|
+
this.#ssrBundler = new SsrDevBundler(options.app, { discovery: () => this.#discovery });
|
|
285
|
+
this.#ssrPatcher = process.env.AKAN_DEV_CSR_PATCHER === "off" ? null : new CsrDevPatcher(this.#ssrBundler, { resident: true });
|
|
286
|
+
this.#workQueue = new BuilderWorkQueue({
|
|
287
|
+
runBatch: async (batch) => await this.#runQueuedBatch(batch),
|
|
288
|
+
onSettled: (label, ms) => {
|
|
289
|
+
this.#workCount += 1;
|
|
290
|
+
this.#logger.verbose(`[work-queue] ${label} finished in ${ms}ms`);
|
|
291
|
+
this.#reportMetrics();
|
|
292
|
+
}
|
|
293
|
+
});
|
|
162
294
|
}
|
|
163
295
|
get #artifactDir() {
|
|
164
296
|
return `${this.#app.cwdPath}/.akan/artifact`;
|
|
165
297
|
}
|
|
166
298
|
async handleBuildRoute(msg) {
|
|
167
|
-
await this.#
|
|
299
|
+
await this.#workQueue.enqueue(`build-route:${msg.routeId}`, async () => BuilderChannel.send(await this.#handleBuildRoute(msg)));
|
|
168
300
|
}
|
|
169
301
|
async#handleBuildRoute(msg) {
|
|
302
|
+
const seenGeneration = this.#discoveredThrough;
|
|
170
303
|
try {
|
|
171
304
|
const delta = await new RouteClientBuilder({
|
|
172
305
|
app: this.#app,
|
|
@@ -175,10 +308,12 @@ class IncrementalBuilder {
|
|
|
175
308
|
graphSeeds: msg.graphSeeds,
|
|
176
309
|
artifact: this.#artifact,
|
|
177
310
|
knownEntries: new Set(msg.knownEntries),
|
|
178
|
-
discovery: this.#discovery
|
|
311
|
+
discovery: this.#discovery,
|
|
312
|
+
browser: "registry"
|
|
179
313
|
}).build();
|
|
314
|
+
await this.#ensureSsrEntries(delta.registryEntries ?? []);
|
|
180
315
|
this.#logger.verbose(`build-route ok routeId=${msg.routeId} newEntries=${delta.newEntries.length}`);
|
|
181
|
-
this.#sendBuildStatus("route", { generation:
|
|
316
|
+
this.#sendBuildStatus("route", { generation: this.#generation, ok: true, files: msg.seeds, scope: msg.routeId });
|
|
182
317
|
return {
|
|
183
318
|
type: "build-route-res",
|
|
184
319
|
id: msg.id,
|
|
@@ -191,35 +326,42 @@ class IncrementalBuilder {
|
|
|
191
326
|
clientDeps: delta.clientDeps,
|
|
192
327
|
clientDepsByEntry: delta.clientDepsByEntry,
|
|
193
328
|
routeId: msg.routeId,
|
|
194
|
-
generation: msg.generation
|
|
329
|
+
generation: msg.generation,
|
|
330
|
+
seenGeneration
|
|
195
331
|
}
|
|
196
332
|
};
|
|
197
333
|
} catch (err) {
|
|
198
334
|
const errMsg = ApplicationBuildReporter.formatError(err, this.#app.workspace.workspaceRoot);
|
|
199
335
|
this.#logger.error(`build-route failed routeId=${msg.routeId}: ${errMsg}`);
|
|
200
|
-
this.#sendBuildStatus("route", {
|
|
336
|
+
this.#sendBuildStatus("route", {
|
|
337
|
+
generation: this.#generation,
|
|
338
|
+
ok: false,
|
|
339
|
+
files: msg.seeds,
|
|
340
|
+
message: errMsg,
|
|
341
|
+
scope: msg.routeId
|
|
342
|
+
});
|
|
201
343
|
return { type: "build-route-res", id: msg.id, ok: false, error: errMsg };
|
|
202
344
|
}
|
|
203
345
|
}
|
|
204
|
-
#sendBuildStatus(phase, {
|
|
346
|
+
#sendBuildStatus(phase, {
|
|
347
|
+
generation,
|
|
348
|
+
ok,
|
|
349
|
+
files,
|
|
350
|
+
message,
|
|
351
|
+
scope
|
|
352
|
+
}) {
|
|
205
353
|
if (typeof generation !== "number")
|
|
206
354
|
return;
|
|
207
355
|
BuilderChannel.emit({
|
|
208
356
|
type: "build-status",
|
|
209
|
-
data: {
|
|
210
|
-
generation,
|
|
211
|
-
phase,
|
|
212
|
-
ok,
|
|
213
|
-
files: files ?? [],
|
|
214
|
-
message
|
|
215
|
-
}
|
|
357
|
+
data: { generation, phase, ok, files: files ?? [], message, ...scope !== undefined ? { scope } : {} }
|
|
216
358
|
});
|
|
217
359
|
}
|
|
218
|
-
async#
|
|
360
|
+
async#enqueueFast(label, fn) {
|
|
219
361
|
const started = Date.now();
|
|
220
362
|
this.#inFlight += 1;
|
|
221
|
-
const run = this.#
|
|
222
|
-
this.#
|
|
363
|
+
const run = this.#fastQueue.then(fn, fn);
|
|
364
|
+
this.#fastQueue = run.then(() => {
|
|
223
365
|
return;
|
|
224
366
|
}).catch(() => {
|
|
225
367
|
return;
|
|
@@ -229,12 +371,22 @@ class IncrementalBuilder {
|
|
|
229
371
|
} finally {
|
|
230
372
|
this.#inFlight -= 1;
|
|
231
373
|
this.#workCount += 1;
|
|
232
|
-
this.#logger.verbose(`[
|
|
374
|
+
this.#logger.verbose(`[fast-queue] ${label} finished in ${Date.now() - started}ms`);
|
|
233
375
|
this.#reportMetrics();
|
|
234
376
|
}
|
|
235
377
|
}
|
|
236
378
|
get #idle() {
|
|
237
|
-
return this.#inFlight === 0 && this.#cssRebuildTimer === null;
|
|
379
|
+
return this.#inFlight === 0 && this.#workQueue.size === 0 && this.#cssRebuildTimer === null;
|
|
380
|
+
}
|
|
381
|
+
#runBeside(work) {
|
|
382
|
+
this.#inFlight += 1;
|
|
383
|
+
const tracked = work.finally(() => {
|
|
384
|
+
this.#inFlight -= 1;
|
|
385
|
+
this.#beside.delete(tracked);
|
|
386
|
+
this.#reportMetrics();
|
|
387
|
+
});
|
|
388
|
+
this.#beside.add(tracked);
|
|
389
|
+
return tracked;
|
|
238
390
|
}
|
|
239
391
|
#reportMetrics() {
|
|
240
392
|
if (!this.#idle || this.#shuttingDown)
|
|
@@ -256,12 +408,17 @@ class IncrementalBuilder {
|
|
|
256
408
|
this.#cssRebuildTimer = null;
|
|
257
409
|
this.#pendingCssRebuild = null;
|
|
258
410
|
}
|
|
259
|
-
await this.#
|
|
411
|
+
await this.#fastQueue.catch(() => {
|
|
260
412
|
return;
|
|
261
413
|
});
|
|
414
|
+
await this.#workQueue.drain();
|
|
262
415
|
await this.#cssRebuildQueue.catch(() => {
|
|
263
416
|
return;
|
|
264
417
|
});
|
|
418
|
+
while (this.#beside.size > 0)
|
|
419
|
+
await Promise.all([...this.#beside]);
|
|
420
|
+
if (this.#delegations.size > 0)
|
|
421
|
+
this.#logger.verbose(`registry saves handed to build workers this session: ${[...this.#delegations].map(([reason, count]) => `${reason}=${count}`).join(", ")}`);
|
|
265
422
|
const flushed = await BuilderChannel.drain();
|
|
266
423
|
this.#logger.debug(`drained in ${Date.now() - started}ms${flushed ? ` after flushing ${flushed} ipc write(s)` : ""}; exiting for recycle`);
|
|
267
424
|
process.exit(0);
|
|
@@ -340,7 +497,7 @@ class IncrementalBuilder {
|
|
|
340
497
|
roots,
|
|
341
498
|
logger: this.#logger,
|
|
342
499
|
onBatch: async (batch) => {
|
|
343
|
-
await this.#
|
|
500
|
+
await this.#enqueueFast("hmr-batch", async () => this.#handleWatchBatch(batch));
|
|
344
501
|
}
|
|
345
502
|
});
|
|
346
503
|
await watcher.start();
|
|
@@ -352,6 +509,7 @@ class IncrementalBuilder {
|
|
|
352
509
|
if (rawKinds.size === 0)
|
|
353
510
|
return;
|
|
354
511
|
const generation = ++this.#generation;
|
|
512
|
+
const trace = { ...batch.trace, batchAt: Date.now() };
|
|
355
513
|
const [autoImport, indexSync] = await CodegenLock.run(this.#app.workspace.workspaceRoot, `hmr-batch:${this.#app.name}`, async () => {
|
|
356
514
|
const auto = await this.#autoImportSync.syncForBatch(batch.files);
|
|
357
515
|
return [auto, await this.#generatedIndexSync.syncForBatch(batch.files)];
|
|
@@ -370,22 +528,19 @@ class IncrementalBuilder {
|
|
|
370
528
|
this.#logger.verbose(`[hmr] batch generation=${generation} kinds=${kinds.join(",")} files=${files.length} generated=${indexSync.changedFiles.length} roles=${devPlan.roles.join(",") || "(none)"} actions=${devPlan.actions.join(",") || "(none)"}`);
|
|
371
529
|
for (const error of indexSync.errors)
|
|
372
530
|
this.#logger.error(error);
|
|
373
|
-
|
|
374
|
-
const started = Date.now();
|
|
375
|
-
if (kinds.includes("config"))
|
|
376
|
-
this.#discovery = await GraphClientEntryDiscovery.create(this.#app);
|
|
377
|
-
else
|
|
378
|
-
this.#discovery.invalidate?.(files);
|
|
379
|
-
this.#logger.verbose(`client-entry-discovery ${kinds.includes("config") ? "refreshed" : "invalidated"} (${Date.now() - started}ms)`);
|
|
380
|
-
}
|
|
531
|
+
const discovery = kinds.includes("code") ? { files, refresh: kinds.includes("config"), generation } : undefined;
|
|
381
532
|
if (hasSyncErrors) {
|
|
533
|
+
this.#barrelFailed = true;
|
|
382
534
|
this.#sendBuildStatus("barrel", { generation, ok: false, files, message: indexSync.errors.join(`
|
|
383
535
|
`) });
|
|
384
536
|
BuilderChannel.emit(event);
|
|
537
|
+
if (discovery)
|
|
538
|
+
this.#workQueue.enqueue("discovery", async () => await this.#refreshDiscovery(discovery)).catch(this.#slowLaneFailed("discovery"));
|
|
385
539
|
return;
|
|
386
540
|
}
|
|
387
|
-
if (indexSync.changedFiles.length > 0)
|
|
541
|
+
if (indexSync.changedFiles.length > 0 || this.#barrelFailed)
|
|
388
542
|
this.#sendBuildStatus("barrel", { generation, ok: true, files });
|
|
543
|
+
this.#barrelFailed = false;
|
|
389
544
|
const rebuildClient = devPlan.actions.includes("rebuild-client");
|
|
390
545
|
if (kinds.includes("code") && !rebuildClient) {
|
|
391
546
|
this.#logger.verbose(`client rebuild skipped; devPlan actions=${devPlan.actions.join(",") || "(none)"}`);
|
|
@@ -398,23 +553,290 @@ class IncrementalBuilder {
|
|
|
398
553
|
} else if (kinds.includes("code") && rebuildClient && this.batchTouchesPagesTree(pageRoots, expandedBatch)) {
|
|
399
554
|
this.#logger.verbose("pageKeys refresh skipped; changed page source cannot add/remove a route key");
|
|
400
555
|
}
|
|
401
|
-
|
|
556
|
+
BuilderChannel.emit(event);
|
|
402
557
|
if (kinds.includes("code") && rebuildClient) {
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
else
|
|
558
|
+
const needs = ["pages", "css"];
|
|
559
|
+
if (!this.#csrActive)
|
|
406
560
|
this.#logger.verbose(`csr-rebundle skipped; request /__csr or ?csr=true (or set AKAN_DEV_CSR_REBUILD=1) to enable per-save CSR rebuilds`);
|
|
407
|
-
|
|
408
|
-
|
|
561
|
+
const patchCsr = async () => this.#csrActive ? await this.#patchCsr(generation, files, trace) : false;
|
|
562
|
+
const patchSsr = async () => await this.#patchSsr(generation, files, trace);
|
|
563
|
+
let csrToWorker;
|
|
564
|
+
let ssrToWorker;
|
|
565
|
+
if (this.#csrArming) {
|
|
566
|
+
ssrToWorker = await patchSsr();
|
|
567
|
+
csrToWorker = await patchCsr();
|
|
568
|
+
} else {
|
|
569
|
+
csrToWorker = await patchCsr();
|
|
570
|
+
ssrToWorker = await patchSsr();
|
|
571
|
+
}
|
|
572
|
+
if (csrToWorker)
|
|
573
|
+
needs.unshift("csr");
|
|
574
|
+
if (ssrToWorker)
|
|
575
|
+
needs.unshift("ssr");
|
|
576
|
+
const batch = { generation, needs, changedFiles: files, trace, ...discovery ? { discovery } : {} };
|
|
577
|
+
if (needs.includes("csr") || needs.includes("ssr"))
|
|
578
|
+
await this.#workQueue.enqueueBatch(batch);
|
|
579
|
+
else
|
|
580
|
+
this.#workQueue.enqueueBatch(batch).catch(this.#slowLaneFailed("batch"));
|
|
581
|
+
return;
|
|
409
582
|
}
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
else if (kinds.includes("css")) {
|
|
583
|
+
if (discovery)
|
|
584
|
+
this.#workQueue.enqueue("discovery", async () => await this.#refreshDiscovery(discovery)).catch(this.#slowLaneFailed("discovery"));
|
|
585
|
+
if (kinds.includes("css")) {
|
|
414
586
|
this.scheduleCssRebuild({ generation, changedFiles: files });
|
|
415
587
|
this.#logger.verbose(`css-rebuild scheduled generation=${generation}`);
|
|
416
588
|
}
|
|
417
589
|
}
|
|
590
|
+
async#patchCsr(generation, files, trace) {
|
|
591
|
+
const patcher = this.#patcher;
|
|
592
|
+
if (!patcher)
|
|
593
|
+
return true;
|
|
594
|
+
await this.#csrGate;
|
|
595
|
+
if (!this.#csrActive)
|
|
596
|
+
return false;
|
|
597
|
+
const started = Date.now();
|
|
598
|
+
const marker = path2.join(this.#csrBundler.outDir, CSR_DEV_PATCHING_MARKER);
|
|
599
|
+
await Bun.write(marker, String(process.pid));
|
|
600
|
+
try {
|
|
601
|
+
const result = await patcher.update(files, {
|
|
602
|
+
announce: (update) => {
|
|
603
|
+
const now = Date.now();
|
|
604
|
+
BuilderChannel.emit({
|
|
605
|
+
type: "csr-updated",
|
|
606
|
+
data: {
|
|
607
|
+
generation: update.generation,
|
|
608
|
+
mode: "registry",
|
|
609
|
+
reload: update.reload,
|
|
610
|
+
reason: update.reason,
|
|
611
|
+
patchUrl: update.patchUrl,
|
|
612
|
+
changedIds: update.changedIds,
|
|
613
|
+
trace: { ...trace, patchAt: now, sentAt: now }
|
|
614
|
+
}
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
});
|
|
618
|
+
if (result.kind === "delegate") {
|
|
619
|
+
this.#logger.verbose(`csr-patch handed to a build worker (${this.#countDelegation("csr", result.reason)}): ${result.reason}`);
|
|
620
|
+
return true;
|
|
621
|
+
}
|
|
622
|
+
this.#sendBuildStatus("csr", { generation, ok: true, files });
|
|
623
|
+
this.#logger.verbose(result.kind === "unchanged" ? `csr-patch unchanged (${Date.now() - started}ms)` : `csr-patch generation=${result.update.generation} ${result.update.reload ? `reload (${result.update.reason})` : `patch modules=${result.update.changedIds.length}`} (${Date.now() - started}ms)`);
|
|
624
|
+
return false;
|
|
625
|
+
} catch (err) {
|
|
626
|
+
const message = ApplicationBuildReporter.formatError(err, this.#app.workspace.workspaceRoot);
|
|
627
|
+
if (IncrementalBuilder.#isCompileError(err)) {
|
|
628
|
+
this.#sendBuildStatus("csr", { generation, ok: false, files, message });
|
|
629
|
+
return false;
|
|
630
|
+
}
|
|
631
|
+
this.#logger.verbose(`csr-patch threw; a build worker takes this save: ${message}`);
|
|
632
|
+
patcher.forget();
|
|
633
|
+
return true;
|
|
634
|
+
} finally {
|
|
635
|
+
await rm(marker, { force: true });
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
async#patchSsr(generation, files, trace) {
|
|
639
|
+
const patcher = this.#ssrPatcher;
|
|
640
|
+
if (!patcher)
|
|
641
|
+
return true;
|
|
642
|
+
const hold = await ServerGraphFile.touches(await ServerGraphFile.read(this.#artifactDir), files, (file) => ServerGraphFile.clientExportsOf(file));
|
|
643
|
+
this.#discovery.invalidate?.(files);
|
|
644
|
+
return await this.#withSsrLock(async () => await this.#runSsrPatcher(patcher, files, { trace, hold, batchGeneration: generation }));
|
|
645
|
+
}
|
|
646
|
+
async#ensureSsrEntries(entries, { crashRetry = false } = {}) {
|
|
647
|
+
if (entries.length === 0)
|
|
648
|
+
return;
|
|
649
|
+
const arming = this.#ssrArming;
|
|
650
|
+
if (arming) {
|
|
651
|
+
this.#runBeside(arming.then(async () => await this.#ensureSsrEntries(entries, { crashRetry })).catch(this.#slowLaneFailed("ssr-ensure")));
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
await this.#withSsrLock(async () => {
|
|
655
|
+
const patcher = this.#ssrPatcher;
|
|
656
|
+
const generation = this.#generation;
|
|
657
|
+
if (patcher && !await this.#runSsrPatcher(patcher, [], { roots: entries, onlyRoots: true, batchGeneration: generation }))
|
|
658
|
+
return;
|
|
659
|
+
if (!patcher && await this.#ssrBundler.holds(entries))
|
|
660
|
+
return;
|
|
661
|
+
if (this.#ssrBroken)
|
|
662
|
+
return;
|
|
663
|
+
if (this.#ssrCrashedAt !== null && Date.now() - this.#ssrCrashedAt < IncrementalBuilder.#crashRetryMs) {
|
|
664
|
+
patcher?.releaseHandedOver();
|
|
665
|
+
this.#retryAfterCrash(this.#ssrCrashedAt, entries);
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
await this.#runSsrWorker(generation).catch((err) => {
|
|
669
|
+
this.#logger.error(`ssr-registry could not take a route's entries; the save that fixes it rebuilds it: ${ApplicationBuildReporter.formatError(err, this.#app.workspace.workspaceRoot)}`);
|
|
670
|
+
});
|
|
671
|
+
if (this.#ssrCrashedAt !== null && !crashRetry)
|
|
672
|
+
this.#retryAfterCrash(this.#ssrCrashedAt, entries);
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
#retryAfterCrash(crashedAt, entries) {
|
|
676
|
+
for (const entry of entries)
|
|
677
|
+
this.#crashRetryEntries.add(entry);
|
|
678
|
+
if (this.#crashRetryTimer)
|
|
679
|
+
return;
|
|
680
|
+
const timer = setTimeout(() => {
|
|
681
|
+
this.#crashRetryTimer = null;
|
|
682
|
+
const retried = [...this.#crashRetryEntries];
|
|
683
|
+
this.#crashRetryEntries.clear();
|
|
684
|
+
if (this.shuttingDown || retried.length === 0)
|
|
685
|
+
return;
|
|
686
|
+
this.#runBeside(this.#ensureSsrEntries(retried, { crashRetry: true })).catch(this.#slowLaneFailed("ssr-crash-retry"));
|
|
687
|
+
}, Math.max(0, crashedAt + IncrementalBuilder.#crashRetryMs - Date.now()));
|
|
688
|
+
timer.unref?.();
|
|
689
|
+
this.#crashRetryTimer = timer;
|
|
690
|
+
}
|
|
691
|
+
async armSsrRegistry() {
|
|
692
|
+
const delayMs = Number(process.env.AKAN_DEV_SSR_ARM_DELAY_MS);
|
|
693
|
+
if (Number.isInteger(delayMs) && delayMs > 0)
|
|
694
|
+
await Bun.sleep(delayMs);
|
|
695
|
+
const arm = async () => await this.#withSsrLock(async () => {
|
|
696
|
+
const generation = this.#generation;
|
|
697
|
+
if (this.#ssrPatcher && !await this.#runSsrPatcher(this.#ssrPatcher, [], { batchGeneration: generation }))
|
|
698
|
+
return;
|
|
699
|
+
await this.#runSsrWorker(generation);
|
|
700
|
+
}).catch((err) => {
|
|
701
|
+
this.#logger.error(`ssr-registry boot build failed; the next save retries it: ${ApplicationBuildReporter.formatError(err, this.#app.workspace.workspaceRoot)}`);
|
|
702
|
+
});
|
|
703
|
+
const arming = this.#runBeside((async () => {
|
|
704
|
+
await arm();
|
|
705
|
+
const crashedAt = this.#ssrCrashedAt;
|
|
706
|
+
if (crashedAt === null)
|
|
707
|
+
return;
|
|
708
|
+
while (!this.shuttingDown && Date.now() < crashedAt + IncrementalBuilder.#crashRetryMs)
|
|
709
|
+
await Bun.sleep(250);
|
|
710
|
+
if (!this.shuttingDown)
|
|
711
|
+
await arm();
|
|
712
|
+
})());
|
|
713
|
+
this.#ssrArming = arming;
|
|
714
|
+
await arming;
|
|
715
|
+
this.#ssrArming = null;
|
|
716
|
+
}
|
|
717
|
+
static #isCompileError(error) {
|
|
718
|
+
return error instanceof AggregateError;
|
|
719
|
+
}
|
|
720
|
+
#slowLaneFailed(label) {
|
|
721
|
+
return (error) => this.#logger.error(`${label} failed in the slow lane: ${ApplicationBuildReporter.formatError(error, this.#app.workspace.workspaceRoot)}`);
|
|
722
|
+
}
|
|
723
|
+
#countDelegation(registry, reason) {
|
|
724
|
+
const key = `${registry}:${reason.split(":")[0]}`;
|
|
725
|
+
const count = (this.#delegations.get(key) ?? 0) + 1;
|
|
726
|
+
this.#delegations.set(key, count);
|
|
727
|
+
return `#${count} for this reason`;
|
|
728
|
+
}
|
|
729
|
+
async#withSsrLock(fn) {
|
|
730
|
+
const run = this.#ssrLock.then(fn, fn);
|
|
731
|
+
this.#ssrLock = run.then(() => {
|
|
732
|
+
return;
|
|
733
|
+
}, () => {
|
|
734
|
+
return;
|
|
735
|
+
});
|
|
736
|
+
return await run;
|
|
737
|
+
}
|
|
738
|
+
async#runSsrWorker(generation) {
|
|
739
|
+
let result;
|
|
740
|
+
try {
|
|
741
|
+
result = await this.#runBatch({ generation, needs: ["ssr"], changedFiles: [] });
|
|
742
|
+
} finally {
|
|
743
|
+
this.#ssrPatcher?.forget();
|
|
744
|
+
}
|
|
745
|
+
this.#noteSsrWorker(result);
|
|
746
|
+
if (result.errors.ssr)
|
|
747
|
+
throw new Error(result.errors.ssr);
|
|
748
|
+
}
|
|
749
|
+
#noteSsrWorker(result) {
|
|
750
|
+
const crashed = !!result.crashed && (result.crashedNeeds ?? ["ssr"]).includes("ssr");
|
|
751
|
+
this.#ssrBroken = !!result.errors.ssr && !crashed;
|
|
752
|
+
this.#ssrCrashedAt = crashed ? Date.now() : null;
|
|
753
|
+
}
|
|
754
|
+
async#runSsrPatcher(patcher, files, {
|
|
755
|
+
roots,
|
|
756
|
+
onlyRoots,
|
|
757
|
+
trace,
|
|
758
|
+
hold,
|
|
759
|
+
batchGeneration
|
|
760
|
+
} = {}) {
|
|
761
|
+
const started = Date.now();
|
|
762
|
+
const marker = path2.join(this.#ssrBundler.outDir, CSR_DEV_PATCHING_MARKER);
|
|
763
|
+
await Bun.write(marker, String(process.pid));
|
|
764
|
+
try {
|
|
765
|
+
const result = await patcher.update(files, {
|
|
766
|
+
roots,
|
|
767
|
+
onlyRoots,
|
|
768
|
+
announce: (update) => {
|
|
769
|
+
const now = Date.now();
|
|
770
|
+
BuilderChannel.emit({
|
|
771
|
+
type: "ssr-updated",
|
|
772
|
+
data: {
|
|
773
|
+
generation: update.generation,
|
|
774
|
+
reload: update.reload,
|
|
775
|
+
reason: update.reason,
|
|
776
|
+
patchUrl: update.patchUrl,
|
|
777
|
+
changedIds: update.changedIds,
|
|
778
|
+
...trace ? { trace: { ...trace, patchAt: now, sentAt: now } } : {},
|
|
779
|
+
...hold ? { hold } : {},
|
|
780
|
+
...batchGeneration !== undefined ? { batchGeneration } : {},
|
|
781
|
+
...update.epoch !== undefined ? { epoch: update.epoch } : {},
|
|
782
|
+
...update.first ? { first: true } : {}
|
|
783
|
+
}
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
});
|
|
787
|
+
if (result.kind === "delegate") {
|
|
788
|
+
this.#logger.verbose(`ssr-patch handed to a build worker (${this.#countDelegation("ssr", result.reason)}): ${result.reason}`);
|
|
789
|
+
return true;
|
|
790
|
+
}
|
|
791
|
+
if (!onlyRoots) {
|
|
792
|
+
this.#ssrBroken = false;
|
|
793
|
+
this.#ssrCrashedAt = null;
|
|
794
|
+
this.#sendBuildStatus("ssr", { generation: batchGeneration, ok: true, files });
|
|
795
|
+
}
|
|
796
|
+
this.#logger.verbose(result.kind === "unchanged" ? `ssr-patch unchanged (${Date.now() - started}ms)` : `ssr-patch generation=${result.update.generation} ${result.update.reload ? `reload (${result.update.reason})` : `patch modules=${result.update.changedIds.length}`} (${Date.now() - started}ms)`);
|
|
797
|
+
return false;
|
|
798
|
+
} catch (err) {
|
|
799
|
+
const message = ApplicationBuildReporter.formatError(err, this.#app.workspace.workspaceRoot);
|
|
800
|
+
if (IncrementalBuilder.#isCompileError(err)) {
|
|
801
|
+
this.#sendBuildStatus("ssr", { generation: batchGeneration, ok: false, files, message });
|
|
802
|
+
return false;
|
|
803
|
+
}
|
|
804
|
+
this.#logger.verbose(`ssr-patch threw; a build worker takes it: ${message}`);
|
|
805
|
+
patcher.forget();
|
|
806
|
+
return true;
|
|
807
|
+
} finally {
|
|
808
|
+
await rm(marker, { force: true });
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
async#runQueuedBatch({ discovery, ...work }) {
|
|
812
|
+
if (discovery)
|
|
813
|
+
await this.#refreshDiscovery(discovery);
|
|
814
|
+
this.#discoveredThrough = Math.max(this.#discoveredThrough, work.generation);
|
|
815
|
+
try {
|
|
816
|
+
if (work.needs.includes("ssr"))
|
|
817
|
+
await this.#withSsrLock(async () => {
|
|
818
|
+
try {
|
|
819
|
+
this.#noteSsrWorker(await this.#runBatch(work));
|
|
820
|
+
} finally {
|
|
821
|
+
this.#ssrPatcher?.forget();
|
|
822
|
+
}
|
|
823
|
+
});
|
|
824
|
+
else
|
|
825
|
+
await this.#runBatch(work);
|
|
826
|
+
} finally {
|
|
827
|
+
if (work.needs.includes("csr"))
|
|
828
|
+
this.#patcher?.forget();
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
async#refreshDiscovery({ files, refresh, generation }) {
|
|
832
|
+
const started = Date.now();
|
|
833
|
+
if (refresh)
|
|
834
|
+
this.#discovery = await GraphClientEntryDiscovery.create(this.#app);
|
|
835
|
+
else
|
|
836
|
+
this.#discovery.invalidate?.(files);
|
|
837
|
+
this.#discoveredThrough = Math.max(this.#discoveredThrough, generation);
|
|
838
|
+
this.#logger.verbose(`client-entry-discovery ${refresh ? "refreshed" : "invalidated"} (${Date.now() - started}ms)`);
|
|
839
|
+
}
|
|
418
840
|
async#runBatch(work) {
|
|
419
841
|
const { generation, needs, changedFiles } = work;
|
|
420
842
|
const started = Date.now();
|
|
@@ -424,15 +846,25 @@ class IncrementalBuilder {
|
|
|
424
846
|
if (result.cssAssets)
|
|
425
847
|
this.#artifact = { ...this.#artifact, cssAssets: result.cssAssets };
|
|
426
848
|
if (result.crashed) {
|
|
427
|
-
|
|
849
|
+
const crashedNeeds = result.crashedNeeds ?? needs;
|
|
850
|
+
if (crashedNeeds.includes("csr"))
|
|
851
|
+
this.#patcher?.releaseHandedOver();
|
|
852
|
+
if (crashedNeeds.includes("ssr"))
|
|
853
|
+
this.#ssrPatcher?.releaseHandedOver();
|
|
854
|
+
for (const need of crashedNeeds)
|
|
428
855
|
if (need !== "base")
|
|
429
|
-
this.#sendBuildStatus(need, {
|
|
856
|
+
this.#sendBuildStatus(need, {
|
|
857
|
+
generation,
|
|
858
|
+
ok: false,
|
|
859
|
+
files: changedFiles,
|
|
860
|
+
message: `${result.errors[need] ?? "the build worker crashed"}; the next save builds it again`
|
|
861
|
+
});
|
|
430
862
|
}
|
|
431
863
|
if (needs.includes("css"))
|
|
432
864
|
this.#logger.verbose(`css-rebuild checked (${Date.now() - started}ms)`);
|
|
433
865
|
return result;
|
|
434
866
|
}
|
|
435
|
-
async#batchRequest({ generation, needs, changedFiles }) {
|
|
867
|
+
async#batchRequest({ generation, needs, changedFiles, trace }) {
|
|
436
868
|
return {
|
|
437
869
|
appName: this.#app.name,
|
|
438
870
|
workspaceRoot: this.#app.workspace.workspaceRoot,
|
|
@@ -443,7 +875,8 @@ class IncrementalBuilder {
|
|
|
443
875
|
pageKeys: await this.#app.getPageKeys(),
|
|
444
876
|
optimizedFonts: this.#optimizedFonts,
|
|
445
877
|
cssAssets: this.#artifact.cssAssets ?? null,
|
|
446
|
-
artifactDir: path2.resolve(this.#artifactDir)
|
|
878
|
+
artifactDir: path2.resolve(this.#artifactDir),
|
|
879
|
+
...trace ? { trace } : {}
|
|
447
880
|
};
|
|
448
881
|
}
|
|
449
882
|
async boot() {
|
|
@@ -454,7 +887,7 @@ class IncrementalBuilder {
|
|
|
454
887
|
}
|
|
455
888
|
async announceRecoveredState(changedFiles) {
|
|
456
889
|
const generation = ++this.#generation;
|
|
457
|
-
await this.#
|
|
890
|
+
await this.#workQueue.enqueue("boot-recovered", async () => {
|
|
458
891
|
await this.#runBatch({ generation, needs: ["pages", "css"], changedFiles });
|
|
459
892
|
});
|
|
460
893
|
}
|
|
@@ -483,19 +916,47 @@ class IncrementalBuilder {
|
|
|
483
916
|
this.#logger.verbose(`announced boot state after recycle generation=${generation}`);
|
|
484
917
|
}
|
|
485
918
|
async handleBuildCsr(msg) {
|
|
486
|
-
|
|
919
|
+
this.#beginCsrArm();
|
|
920
|
+
this.#csrActive = true;
|
|
921
|
+
const armed = this.#workQueue.enqueue("build-csr", async () => {
|
|
487
922
|
const started = Date.now();
|
|
488
|
-
|
|
489
|
-
|
|
923
|
+
let error;
|
|
924
|
+
try {
|
|
925
|
+
const result = await this.#batchRunner.run(await this.#batchRequest({ generation: this.#generation, needs: ["csr"], changedFiles: [] }));
|
|
926
|
+
error = result.errors.csr;
|
|
927
|
+
} catch (err) {
|
|
928
|
+
error = ApplicationBuildReporter.formatError(err, this.#app.workspace.workspaceRoot);
|
|
929
|
+
}
|
|
490
930
|
if (error) {
|
|
491
931
|
this.#logger.error(`csr-build failed: ${error}`);
|
|
492
932
|
await BuilderChannel.send({ type: "build-csr-res", id: msg.id, ok: false, error });
|
|
493
933
|
return;
|
|
494
934
|
}
|
|
495
|
-
this.#
|
|
935
|
+
this.#csrArmSucceeded = true;
|
|
936
|
+
this.#patcher?.forget();
|
|
496
937
|
this.#logger.info(`csr-build ok on demand (${Date.now() - started}ms); rebuilding CSR on every save now`);
|
|
497
938
|
await BuilderChannel.send({ type: "build-csr-res", id: msg.id, ok: true });
|
|
498
939
|
});
|
|
940
|
+
this.#csrGate = this.#armingCsr(armed);
|
|
941
|
+
await armed;
|
|
942
|
+
}
|
|
943
|
+
#beginCsrArm() {
|
|
944
|
+
if (this.#csrArms === 0) {
|
|
945
|
+
this.#csrWasActive = this.#csrActive;
|
|
946
|
+
this.#csrArmSucceeded = false;
|
|
947
|
+
}
|
|
948
|
+
this.#csrArms += 1;
|
|
949
|
+
this.#csrArming = true;
|
|
950
|
+
}
|
|
951
|
+
#armingCsr(armed) {
|
|
952
|
+
const settle = () => {
|
|
953
|
+
this.#csrArms -= 1;
|
|
954
|
+
if (this.#csrArms > 0)
|
|
955
|
+
return;
|
|
956
|
+
this.#csrArming = false;
|
|
957
|
+
this.#csrActive = this.#csrWasActive || this.#csrArmSucceeded;
|
|
958
|
+
};
|
|
959
|
+
return armed.then(settle, settle);
|
|
499
960
|
}
|
|
500
961
|
static #csrArmedByEnv() {
|
|
501
962
|
return process.env.AKAN_DEV_CSR_REBUILD === "1";
|
|
@@ -523,19 +984,24 @@ class IncrementalBuilder {
|
|
|
523
984
|
async rearmCsrFromEnv() {
|
|
524
985
|
if (!IncrementalBuilder.#csrArmedByEnv())
|
|
525
986
|
return;
|
|
987
|
+
this.#beginCsrArm();
|
|
526
988
|
this.#csrActive = true;
|
|
527
|
-
|
|
528
|
-
|
|
989
|
+
this.#csrArmSucceeded = true;
|
|
990
|
+
const rearmed = this.#workQueue.enqueue("build-csr-rearm", async () => {
|
|
991
|
+
const result = await this.#batchRunner.run(await this.#batchRequest({ generation: this.#generation, needs: ["csr"], changedFiles: [] }), (msg) => BuilderChannel.emit(msg));
|
|
992
|
+
this.#patcher?.forget();
|
|
529
993
|
if (result.errors.csr)
|
|
530
994
|
this.#logger.error(`csr-rearm failed: ${result.errors.csr}`);
|
|
531
995
|
else
|
|
532
996
|
this.#logger.verbose("csr-rearm ok; this session had CSR armed before the builder restarted");
|
|
533
997
|
});
|
|
998
|
+
this.#csrGate = this.#armingCsr(rearmed);
|
|
999
|
+
await rearmed.catch((err) => this.#logger.error(`csr-rearm failed: ${ApplicationBuildReporter.formatError(err, this.#app.workspace.workspaceRoot)}`));
|
|
534
1000
|
}
|
|
535
|
-
static #recoverBoot(app, bootError, logger, runner) {
|
|
1001
|
+
static #recoverBoot(app, bootError, logger, runner, initialGeneration) {
|
|
536
1002
|
const firstMessage = bootError instanceof Error ? bootError.message : String(bootError);
|
|
537
1003
|
logger.error(`boot build failed; entering degraded watch mode until the error is fixed: ${firstMessage}`);
|
|
538
|
-
let generation =
|
|
1004
|
+
let generation = initialGeneration;
|
|
539
1005
|
const sendFailure = (files, message) => {
|
|
540
1006
|
BuilderChannel.emit({
|
|
541
1007
|
type: "build-status",
|
|
@@ -544,6 +1010,7 @@ class IncrementalBuilder {
|
|
|
544
1010
|
};
|
|
545
1011
|
sendFailure([], firstMessage);
|
|
546
1012
|
BuilderChannel.emit({ type: "builder-ready" });
|
|
1013
|
+
BuilderChannel.emit({ type: "boot-armed" });
|
|
547
1014
|
return new Promise((resolve, reject) => {
|
|
548
1015
|
(async () => {
|
|
549
1016
|
const roots = await new WatchRootResolver(app).resolve();
|
|
@@ -602,10 +1069,8 @@ class IncrementalBuilder {
|
|
|
602
1069
|
BuilderChannel.emit({ type: `${msg.type}-res`, id: msg.id, ok: false, error });
|
|
603
1070
|
return;
|
|
604
1071
|
}
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
else
|
|
608
|
-
builder.handleBuildCsr(msg);
|
|
1072
|
+
const handled = msg.type === "build-route" ? builder.handleBuildRoute(msg) : builder.handleBuildCsr(msg);
|
|
1073
|
+
handled.catch((err) => logger.error(`${msg.type} failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
609
1074
|
});
|
|
610
1075
|
process.on("disconnect", () => {
|
|
611
1076
|
logger.warn("host IPC channel closed; exiting builder");
|
|
@@ -613,12 +1078,14 @@ class IncrementalBuilder {
|
|
|
613
1078
|
});
|
|
614
1079
|
let recoveredFiles = null;
|
|
615
1080
|
const bootRunner = new BuildBatchRunner({ workspaceRoot, cwd: app.cwdPath });
|
|
1081
|
+
const initialGeneration = IncrementalBuilder.#initialGeneration();
|
|
616
1082
|
try {
|
|
617
|
-
|
|
1083
|
+
const deps = await IncrementalBuilder.#buildBootDeps(app, bootRunner);
|
|
1084
|
+
builder = new IncrementalBuilder({ app, watch, initialGeneration, ...deps });
|
|
618
1085
|
} catch (err) {
|
|
619
1086
|
if (!watch)
|
|
620
1087
|
throw err;
|
|
621
|
-
const recovered = await IncrementalBuilder.#recoverBoot(app, err, logger, bootRunner);
|
|
1088
|
+
const recovered = await IncrementalBuilder.#recoverBoot(app, err, logger, bootRunner, initialGeneration);
|
|
622
1089
|
builder = recovered.builder;
|
|
623
1090
|
recoveredFiles = recovered.changedFiles;
|
|
624
1091
|
}
|
|
@@ -627,7 +1094,12 @@ class IncrementalBuilder {
|
|
|
627
1094
|
await builder.announceRecoveredState(recoveredFiles);
|
|
628
1095
|
else if (process.env.AKAN_BUILDER_ANNOUNCE_BOOT === "1")
|
|
629
1096
|
await builder.announceBootState();
|
|
630
|
-
await builder.rearmCsrFromEnv();
|
|
1097
|
+
await Promise.all([builder.rearmCsrFromEnv(), builder.armSsrRegistry()]);
|
|
1098
|
+
BuilderChannel.emit({ type: "boot-armed" });
|
|
1099
|
+
}
|
|
1100
|
+
static #initialGeneration() {
|
|
1101
|
+
const generation = Number(process.env.AKAN_BUILDER_INITIAL_GENERATION);
|
|
1102
|
+
return Number.isInteger(generation) && generation > 0 ? generation + 1 : 0;
|
|
631
1103
|
}
|
|
632
1104
|
}
|
|
633
1105
|
await IncrementalBuilder.main().catch((err) => {
|