@akanjs/cli 2.4.1-rc.2 → 2.4.1-rc.4
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 -0
- package/{agent.command-h4afc69n.js → agent.command-t4346579.js} +6 -6
- package/{application.command-47mj9qsy.js → application.command-zkmeemdt.js} +5 -4
- package/{applicationBuildRunner-yz144508.js → applicationBuildRunner-5w8j9yp7.js} +2 -1
- package/buildBatch.proc.js +176 -0
- package/{capacitorApp-y0h6cgft.js → capacitorApp-wnxzrppx.js} +2 -2
- package/{cloud.command-hxcgsf80.js → cloud.command-5ztqxppm.js} +9 -8
- package/{context.command-nqbtak4f.js → context.command-smwxwgwf.js} +14 -14
- package/{guideline.command-ya0dh44f.js → guideline.command-1r0esdjs.js} +3 -3
- package/incrementalBuilder.proc.js +281 -160
- package/index-0fn1r7gg.js +466 -0
- package/{index-qhtr07v8.js → index-0jwvs8vp.js} +19 -19
- package/{index-a6sbyy0b.js → index-0t7pwff2.js} +3 -0
- package/{index-h6ca6qg0.js → index-2v10yr8g.js} +1 -1
- package/{index-73pr2cmy.js → index-2v6a4wd5.js} +4 -4
- package/{index-45aj5ry0.js → index-3sd2vape.js} +3 -3
- package/{index-y3hdhy4p.js → index-ahpcr9ss.js} +5 -5
- package/{index-8rc0bm04.js → index-eay4t1te.js} +2 -2
- package/{index-qaq13qk3.js → index-eqxmxan6.js} +1 -1
- package/{index-pmm9e2jf.js → index-fh990y67.js} +2 -2
- package/{index-sgmas1fc.js → index-gr4cjv99.js} +5 -5
- package/{index-1xdrsbry.js → index-hk58kr1m.js} +1 -1
- package/{index-85msc0wg.js → index-jbtn8h1y.js} +3 -3
- package/{index-a5rmdgy4.js → index-khzzttv1.js} +1362 -1447
- package/{index-77crfweb.js → index-n5y2gbf2.js} +478 -20
- package/{index-n6h3482q.js → index-nv7et4t3.js} +2 -2
- package/{index-hwzpw9c1.js → index-pya1h7wx.js} +3 -3
- package/{index-hdqztm58.js → index-qpz4csbs.js} +1 -1
- package/{index-mq6ns0f9.js → index-s9yajzkj.js} +9 -9
- package/{index-8pkbzj26.js → index-wh201a76.js} +7 -7
- package/{index-swf4bmbg.js → index-y4kg682s.js} +1 -1
- package/index.js +18 -18
- package/{library.command-r15zdqvp.js → library.command-jrew04m6.js} +3 -3
- package/{localRegistry.command-6z4s13mj.js → localRegistry.command-m08dkm1b.js} +8 -7
- package/{module.command-qrj3kmyz.js → module.command-p98t7522.js} +6 -6
- package/{package.command-5x5m0ej1.js → package.command-57dyrfmn.js} +3 -3
- package/package.json +2 -2
- package/{page.command-c6xdx0xm.js → page.command-tas6f3na.js} +3 -3
- package/{primitive.command-pv9ssmtf.js → primitive.command-bjrbsakw.js} +7 -7
- package/{quality.command-es67wvdp.js → quality.command-zavaafbw.js} +2 -2
- package/{repair.command-677675vw.js → repair.command-je57wx67.js} +5 -5
- package/{scalar.command-kabkd6wd.js → scalar.command-gqqyy1kr.js} +5 -5
- package/{workflow.command-64r6cw0w.js → workflow.command-d6mrc47s.js} +10 -10
- package/{workspace.command-875aj35r.js → workspace.command-bbg96z8k.js} +24 -23
|
@@ -5,41 +5,129 @@ import {
|
|
|
5
5
|
import {
|
|
6
6
|
AppExecutor,
|
|
7
7
|
WorkspaceExecutor
|
|
8
|
-
} from "./index-
|
|
8
|
+
} from "./index-0t7pwff2.js";
|
|
9
|
+
import"./index-61keag0s.js";
|
|
9
10
|
import {
|
|
10
11
|
AutoImportSync,
|
|
11
|
-
CsrArtifactBuilder,
|
|
12
12
|
DevChangePlanner,
|
|
13
13
|
DevGeneratedIndexSync,
|
|
14
|
-
FontOptimizer,
|
|
15
14
|
GraphClientEntryDiscovery,
|
|
16
|
-
HmrWatcher,
|
|
17
|
-
PagesBundleBuilder,
|
|
18
15
|
RouteClientBuilder,
|
|
19
|
-
SsrBaseArtifactBuilder
|
|
16
|
+
SsrBaseArtifactBuilder
|
|
17
|
+
} from "./index-khzzttv1.js";
|
|
18
|
+
import {
|
|
19
|
+
HmrWatcher,
|
|
20
20
|
WatchRootResolver
|
|
21
|
-
} from "./index-
|
|
22
|
-
import"./index-61keag0s.js";
|
|
21
|
+
} from "./index-0fn1r7gg.js";
|
|
23
22
|
import"./index-6pz1j0zj.js";
|
|
24
23
|
import"./index-r24hmh0q.js";
|
|
25
24
|
|
|
26
25
|
// pkgs/@akanjs/devkit/incrementalBuilder/incrementalBuilder.proc.ts
|
|
26
|
+
import path2 from "path";
|
|
27
|
+
import { Logger as Logger2 } from "akanjs/common";
|
|
28
|
+
|
|
29
|
+
// pkgs/@akanjs/devkit/incrementalBuilder/buildBatchRunner.ts
|
|
27
30
|
import path from "path";
|
|
28
31
|
import { Logger } from "akanjs/common";
|
|
32
|
+
|
|
33
|
+
class BuildBatchRunner {
|
|
34
|
+
static #entryCandidates = (workspaceRoot) => [
|
|
35
|
+
path.join(workspaceRoot, "pkgs/@akanjs/devkit/incrementalBuilder/buildBatch.proc.ts"),
|
|
36
|
+
path.join(workspaceRoot, "node_modules/@akanjs/devkit/incrementalBuilder/buildBatch.proc.ts"),
|
|
37
|
+
path.join(import.meta.dir, "buildBatch.proc.js"),
|
|
38
|
+
path.join(import.meta.dir, "buildBatch.proc.ts")
|
|
39
|
+
];
|
|
40
|
+
#logger = new Logger("BuildBatchRunner");
|
|
41
|
+
#entry = null;
|
|
42
|
+
#workspaceRoot;
|
|
43
|
+
#cwd;
|
|
44
|
+
constructor({ workspaceRoot, cwd }) {
|
|
45
|
+
this.#workspaceRoot = workspaceRoot;
|
|
46
|
+
this.#cwd = cwd;
|
|
47
|
+
}
|
|
48
|
+
async#resolveEntry() {
|
|
49
|
+
if (this.#entry)
|
|
50
|
+
return this.#entry;
|
|
51
|
+
const candidates = BuildBatchRunner.#entryCandidates(this.#workspaceRoot);
|
|
52
|
+
for (const candidate of candidates) {
|
|
53
|
+
if (!await Bun.file(candidate).exists())
|
|
54
|
+
continue;
|
|
55
|
+
this.#entry = candidate;
|
|
56
|
+
return candidate;
|
|
57
|
+
}
|
|
58
|
+
throw new Error(`[build-batch] worker entry not found; looked in: ${candidates.join(", ")}`);
|
|
59
|
+
}
|
|
60
|
+
async run(request, onMessage = () => {
|
|
61
|
+
return;
|
|
62
|
+
}) {
|
|
63
|
+
const started = Date.now();
|
|
64
|
+
const entry = await this.#resolveEntry();
|
|
65
|
+
let result = null;
|
|
66
|
+
const proc = Bun.spawn(["bun", entry, JSON.stringify(request)], {
|
|
67
|
+
cwd: this.#cwd,
|
|
68
|
+
env: process.env,
|
|
69
|
+
stdio: ["ignore", "inherit", "inherit"],
|
|
70
|
+
serialization: "advanced",
|
|
71
|
+
ipc: (message2) => {
|
|
72
|
+
if (!message2 || typeof message2 !== "object")
|
|
73
|
+
return;
|
|
74
|
+
if (message2.type === "build-batch-result")
|
|
75
|
+
result = message2.data;
|
|
76
|
+
else
|
|
77
|
+
onMessage(message2);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
const exitCode = await proc.exited;
|
|
81
|
+
if (result) {
|
|
82
|
+
this.#logger.verbose(`[build-batch] generation=${request.generation} needs=${request.needs.join(",")} done in ${Date.now() - started}ms`);
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
const message = `build worker exited with code ${exitCode} before reporting a result`;
|
|
86
|
+
this.#logger.error(`[build-batch] generation=${request.generation} ${message}`);
|
|
87
|
+
return {
|
|
88
|
+
generation: request.generation,
|
|
89
|
+
errors: Object.fromEntries(request.needs.map((need) => [need, message])),
|
|
90
|
+
crashed: true
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// pkgs/@akanjs/devkit/incrementalBuilder/builderReply.ts
|
|
96
|
+
class BuilderReply {
|
|
97
|
+
static #flushTimeoutMs = 5000;
|
|
98
|
+
static async send(res) {
|
|
99
|
+
const send = process.send;
|
|
100
|
+
if (!send)
|
|
101
|
+
return;
|
|
102
|
+
await new Promise((resolve) => {
|
|
103
|
+
const timer = setTimeout(resolve, BuilderReply.#flushTimeoutMs);
|
|
104
|
+
send.call(process, res, undefined, undefined, () => {
|
|
105
|
+
clearTimeout(timer);
|
|
106
|
+
resolve();
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// pkgs/@akanjs/devkit/incrementalBuilder/incrementalBuilder.proc.ts
|
|
29
113
|
class IncrementalBuilder {
|
|
30
|
-
#logger = new
|
|
114
|
+
#logger = new Logger2("IncrementalBuilder");
|
|
31
115
|
#app;
|
|
32
116
|
#artifact;
|
|
33
117
|
#watch;
|
|
34
|
-
#cssCompiler;
|
|
35
118
|
#optimizedFonts;
|
|
36
119
|
#discovery;
|
|
120
|
+
#batchRunner;
|
|
37
121
|
#changePlanner;
|
|
38
122
|
#generatedIndexSync;
|
|
39
123
|
#autoImportSync;
|
|
124
|
+
#watcher = null;
|
|
40
125
|
#generation = 0;
|
|
41
126
|
#csrActive = IncrementalBuilder.#csrArmedByEnv();
|
|
42
127
|
#workQueue = Promise.resolve();
|
|
128
|
+
#inFlight = 0;
|
|
129
|
+
#workCount = 0;
|
|
130
|
+
#shuttingDown = false;
|
|
43
131
|
#cssRebuildQueue = Promise.resolve();
|
|
44
132
|
#cssRebuildTimer = null;
|
|
45
133
|
#pendingCssRebuild = null;
|
|
@@ -47,10 +135,13 @@ class IncrementalBuilder {
|
|
|
47
135
|
this.#app = options.app;
|
|
48
136
|
this.#artifact = options.artifact;
|
|
49
137
|
this.#watch = options.watch;
|
|
50
|
-
this.#cssCompiler = options.cssCompiler;
|
|
51
138
|
this.#optimizedFonts = options.optimizedFonts;
|
|
52
139
|
this.#discovery = options.discovery;
|
|
53
140
|
this.#generation = options.initialGeneration ?? 0;
|
|
141
|
+
this.#batchRunner = new BuildBatchRunner({
|
|
142
|
+
workspaceRoot: options.app.workspace.workspaceRoot,
|
|
143
|
+
cwd: options.app.cwdPath
|
|
144
|
+
});
|
|
54
145
|
this.#changePlanner = new DevChangePlanner({ workspaceRoot: options.app.workspace.workspaceRoot });
|
|
55
146
|
this.#generatedIndexSync = new DevGeneratedIndexSync({ workspaceRoot: options.app.workspace.workspaceRoot });
|
|
56
147
|
this.#autoImportSync = new AutoImportSync({ workspaceRoot: options.app.workspace.workspaceRoot });
|
|
@@ -59,7 +150,7 @@ class IncrementalBuilder {
|
|
|
59
150
|
return `${this.#app.cwdPath}/.akan/artifact`;
|
|
60
151
|
}
|
|
61
152
|
async handleBuildRoute(msg) {
|
|
62
|
-
|
|
153
|
+
await this.#enqueueWork(`build-route:${msg.routeId}`, async () => BuilderReply.send(await this.#handleBuildRoute(msg)));
|
|
63
154
|
}
|
|
64
155
|
async#handleBuildRoute(msg) {
|
|
65
156
|
try {
|
|
@@ -111,6 +202,7 @@ class IncrementalBuilder {
|
|
|
111
202
|
}
|
|
112
203
|
async#enqueueWork(label, fn) {
|
|
113
204
|
const started = Date.now();
|
|
205
|
+
this.#inFlight += 1;
|
|
114
206
|
const run = this.#workQueue.then(fn, fn);
|
|
115
207
|
this.#workQueue = run.then(() => {
|
|
116
208
|
return;
|
|
@@ -120,14 +212,51 @@ class IncrementalBuilder {
|
|
|
120
212
|
try {
|
|
121
213
|
return await run;
|
|
122
214
|
} finally {
|
|
215
|
+
this.#inFlight -= 1;
|
|
216
|
+
this.#workCount += 1;
|
|
123
217
|
this.#logger.verbose(`[work-queue] ${label} finished in ${Date.now() - started}ms`);
|
|
218
|
+
this.#reportMetrics();
|
|
124
219
|
}
|
|
125
220
|
}
|
|
221
|
+
get #idle() {
|
|
222
|
+
return this.#inFlight === 0 && this.#cssRebuildTimer === null;
|
|
223
|
+
}
|
|
224
|
+
#reportMetrics() {
|
|
225
|
+
if (!this.#idle || this.#shuttingDown)
|
|
226
|
+
return;
|
|
227
|
+
process.send?.({
|
|
228
|
+
type: "builder-metrics",
|
|
229
|
+
data: { rssBytes: process.memoryUsage.rss(), generation: this.#generation, workCount: this.#workCount }
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
async shutdown(reason) {
|
|
233
|
+
if (this.#shuttingDown)
|
|
234
|
+
return;
|
|
235
|
+
this.#shuttingDown = true;
|
|
236
|
+
const started = Date.now();
|
|
237
|
+
this.#logger.info(`shutdown requested (${reason}); draining ${this.#inFlight} work item(s)`);
|
|
238
|
+
if (this.#cssRebuildTimer) {
|
|
239
|
+
clearTimeout(this.#cssRebuildTimer);
|
|
240
|
+
this.#cssRebuildTimer = null;
|
|
241
|
+
this.#pendingCssRebuild = null;
|
|
242
|
+
}
|
|
243
|
+
await this.#workQueue.catch(() => {
|
|
244
|
+
return;
|
|
245
|
+
});
|
|
246
|
+
await this.#cssRebuildQueue.catch(() => {
|
|
247
|
+
return;
|
|
248
|
+
});
|
|
249
|
+
this.#logger.info(`drained in ${Date.now() - started}ms; exiting for recycle`);
|
|
250
|
+
process.exit(0);
|
|
251
|
+
}
|
|
252
|
+
get shuttingDown() {
|
|
253
|
+
return this.#shuttingDown;
|
|
254
|
+
}
|
|
126
255
|
batchTouchesPagesTree(appDir, batch) {
|
|
127
|
-
const absAppDir =
|
|
256
|
+
const absAppDir = path2.resolve(appDir);
|
|
128
257
|
for (const f of batch.files) {
|
|
129
|
-
const abs =
|
|
130
|
-
if (!abs.startsWith(`${absAppDir}${
|
|
258
|
+
const abs = path2.resolve(f);
|
|
259
|
+
if (!abs.startsWith(`${absAppDir}${path2.sep}`) && abs !== absAppDir)
|
|
131
260
|
continue;
|
|
132
261
|
if (/\.(tsx|ts|jsx|js)$/.test(abs))
|
|
133
262
|
return true;
|
|
@@ -135,67 +264,22 @@ class IncrementalBuilder {
|
|
|
135
264
|
return false;
|
|
136
265
|
}
|
|
137
266
|
async batchMayChangePageKeys(appDir, batch) {
|
|
138
|
-
const absAppDir =
|
|
139
|
-
const pageKeys = new Set((await this.#app.getPageKeys()).map((key) =>
|
|
267
|
+
const absAppDir = path2.resolve(appDir);
|
|
268
|
+
const pageKeys = new Set((await this.#app.getPageKeys()).map((key) => path2.normalize(key)));
|
|
140
269
|
for (const f of batch.files) {
|
|
141
|
-
const abs =
|
|
142
|
-
if (!abs.startsWith(`${absAppDir}${
|
|
270
|
+
const abs = path2.resolve(f);
|
|
271
|
+
if (!abs.startsWith(`${absAppDir}${path2.sep}`) && abs !== absAppDir)
|
|
143
272
|
continue;
|
|
144
273
|
if (!/\.(tsx|ts|jsx|js)$/.test(abs))
|
|
145
274
|
continue;
|
|
146
|
-
const rel =
|
|
275
|
+
const rel = path2.normalize(path2.relative(absAppDir, abs));
|
|
147
276
|
if (!await Bun.file(abs).exists() || !pageKeys.has(rel))
|
|
148
277
|
return true;
|
|
149
278
|
}
|
|
150
279
|
return false;
|
|
151
280
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const cssByBasePathStarted = Date.now();
|
|
155
|
-
const cssByBasePath = await this.#cssCompiler.getCssByBasePath({ refresh });
|
|
156
|
-
this.#logger.verbose(`css-get-by-base-path ok (${Date.now() - cssByBasePathStarted}ms)`);
|
|
157
|
-
const fontStarted = Date.now();
|
|
158
|
-
const optimizedFonts = await this.#getOptimizedFonts(changedFiles ?? []);
|
|
159
|
-
this.#logger.verbose(`font-assets ready (${Date.now() - fontStarted}ms)`);
|
|
160
|
-
const cssAssetEntries = [];
|
|
161
|
-
const cssBase64ByUrl = {};
|
|
162
|
-
await Promise.all(Object.entries(cssByBasePath).flatMap(([basePath, baseCssText]) => {
|
|
163
|
-
const cssText = [baseCssText, optimizedFonts.css].filter(Boolean).join(`
|
|
164
|
-
`);
|
|
165
|
-
if (!cssText)
|
|
166
|
-
return [];
|
|
167
|
-
return [
|
|
168
|
-
(async () => {
|
|
169
|
-
const cssAssetName = basePath || "root";
|
|
170
|
-
const cssHash = Bun.hash(`${basePath}
|
|
171
|
-
${cssText}`).toString(36);
|
|
172
|
-
const cssRelPath = `styles/${cssAssetName}-${cssHash}.css`;
|
|
173
|
-
const cssUrl = `/_akan/styles/${cssAssetName}-${cssHash}.css`;
|
|
174
|
-
await Bun.write(path.join(artifactDir, cssRelPath), cssText);
|
|
175
|
-
cssAssetEntries.push([basePath, { cssUrl, cssRelPath }]);
|
|
176
|
-
cssBase64ByUrl[cssUrl] = Buffer.from(new TextEncoder().encode(cssText)).toString("base64");
|
|
177
|
-
})()
|
|
178
|
-
];
|
|
179
|
-
}));
|
|
180
|
-
const cssAssets = Object.fromEntries(cssAssetEntries);
|
|
181
|
-
if (JSON.stringify(this.#artifact.cssAssets ?? {}) === JSON.stringify(cssAssets)) {
|
|
182
|
-
this.#logger.verbose("css-rebuild unchanged assets; broadcast skipped");
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
this.#artifact = { ...this.#artifact, cssAssets };
|
|
186
|
-
this.#logger.verbose(`css-compile ok assets=${Object.keys(cssAssets).length} (${Date.now() - cssStarted}ms)`);
|
|
187
|
-
process.send?.({
|
|
188
|
-
type: "css-updated",
|
|
189
|
-
data: {
|
|
190
|
-
cssAssets,
|
|
191
|
-
cssBase64ByUrl,
|
|
192
|
-
generation,
|
|
193
|
-
changedFiles
|
|
194
|
-
}
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
|
-
scheduleCssRebuild(artifactDir, { refresh, generation, changedFiles }) {
|
|
198
|
-
this.#pendingCssRebuild = { artifactDir, refresh, generation, changedFiles };
|
|
281
|
+
scheduleCssRebuild({ generation, changedFiles }) {
|
|
282
|
+
this.#pendingCssRebuild = { generation, changedFiles };
|
|
199
283
|
if (this.#cssRebuildTimer)
|
|
200
284
|
clearTimeout(this.#cssRebuildTimer);
|
|
201
285
|
this.#cssRebuildTimer = setTimeout(() => {
|
|
@@ -204,15 +288,13 @@ ${cssText}`).toString(36);
|
|
|
204
288
|
this.#pendingCssRebuild = null;
|
|
205
289
|
if (!next)
|
|
206
290
|
return;
|
|
291
|
+
this.#inFlight += 1;
|
|
207
292
|
this.#cssRebuildQueue = this.#cssRebuildQueue.then(async () => {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
changedFiles: next.changedFiles
|
|
293
|
+
await this.#runBatch({
|
|
294
|
+
generation: next.generation ?? this.#generation,
|
|
295
|
+
needs: ["css"],
|
|
296
|
+
changedFiles: next.changedFiles ?? []
|
|
213
297
|
});
|
|
214
|
-
this.#sendBuildStatus("css", { generation: next.generation, ok: true, files: next.changedFiles });
|
|
215
|
-
this.#logger.verbose(`css-rebuild checked (${Date.now() - started}ms)`);
|
|
216
298
|
}).catch((err) => {
|
|
217
299
|
const message = err instanceof Error ? err.message : String(err);
|
|
218
300
|
this.#logger.error(`css-rebuild failed: ${message}`);
|
|
@@ -222,29 +304,12 @@ ${cssText}`).toString(36);
|
|
|
222
304
|
files: next.changedFiles,
|
|
223
305
|
message
|
|
224
306
|
});
|
|
307
|
+
}).finally(() => {
|
|
308
|
+
this.#inFlight -= 1;
|
|
309
|
+
this.#reportMetrics();
|
|
225
310
|
});
|
|
226
311
|
}, 150);
|
|
227
312
|
}
|
|
228
|
-
async#getOptimizedFonts(changedFiles) {
|
|
229
|
-
if (!this.#shouldReoptimizeFonts(changedFiles)) {
|
|
230
|
-
this.#logger.verbose(`font-optimize cached files=${this.#optimizedFonts.files.length}`);
|
|
231
|
-
return this.#optimizedFonts;
|
|
232
|
-
}
|
|
233
|
-
const started = Date.now();
|
|
234
|
-
this.#optimizedFonts = await new FontOptimizer(this.#app, "start").optimize();
|
|
235
|
-
this.#logger.verbose(`font-optimize ok files=${this.#optimizedFonts.files.length} (${Date.now() - started}ms)`);
|
|
236
|
-
return this.#optimizedFonts;
|
|
237
|
-
}
|
|
238
|
-
#shouldReoptimizeFonts(changedFiles) {
|
|
239
|
-
if (changedFiles.length === 0)
|
|
240
|
-
return false;
|
|
241
|
-
return changedFiles.some((file) => {
|
|
242
|
-
const normalized = path.resolve(file);
|
|
243
|
-
if (/\.(woff2?|ttf|otf)$/i.test(normalized))
|
|
244
|
-
return true;
|
|
245
|
-
return this.#optimizedFonts.files.some((fontFile) => path.resolve(fontFile) === normalized);
|
|
246
|
-
});
|
|
247
|
-
}
|
|
248
313
|
async installWatcher() {
|
|
249
314
|
const [appDir, artifactDir] = [`${this.#app.cwdPath}/page`, this.#artifactDir];
|
|
250
315
|
const roots = await new WatchRootResolver(this.#app).resolve();
|
|
@@ -255,7 +320,8 @@ ${cssText}`).toString(36);
|
|
|
255
320
|
await this.#enqueueWork("hmr-batch", async () => this.#handleWatchBatch(appDir, artifactDir, batch));
|
|
256
321
|
}
|
|
257
322
|
});
|
|
258
|
-
watcher.start();
|
|
323
|
+
await watcher.start();
|
|
324
|
+
this.#watcher = watcher;
|
|
259
325
|
this.#logger.verbose(`watching ${roots.length} roots`);
|
|
260
326
|
}
|
|
261
327
|
async#handleWatchBatch(appDir, artifactDir, batch) {
|
|
@@ -269,6 +335,7 @@ ${cssText}`).toString(36);
|
|
|
269
335
|
if (autoImport.changedFiles.length > 0)
|
|
270
336
|
this.#logger.verbose(`[auto-import] inserted imports into ${autoImport.changedFiles.length} file(s)`);
|
|
271
337
|
const indexSync = await this.#generatedIndexSync.syncForBatch(batch.files);
|
|
338
|
+
await this.#watcher?.absorb([...autoImport.changedFiles, ...indexSync.changedFiles]);
|
|
272
339
|
const { files, kinds, expandedBatch, event, hasSyncErrors } = prepareDevWatchBatch({
|
|
273
340
|
generation,
|
|
274
341
|
batch,
|
|
@@ -306,42 +373,60 @@ ${cssText}`).toString(36);
|
|
|
306
373
|
} else if (kinds.includes("code") && rebuildClient && this.batchTouchesPagesTree(appDir, expandedBatch)) {
|
|
307
374
|
this.#logger.verbose("pageKeys refresh skipped; changed page source cannot add/remove a route key");
|
|
308
375
|
}
|
|
309
|
-
|
|
310
|
-
try {
|
|
311
|
-
const started = Date.now();
|
|
312
|
-
await new CsrArtifactBuilder(this.#app).build();
|
|
313
|
-
this.#sendBuildStatus("csr", { generation, ok: true, files });
|
|
314
|
-
this.#logger.verbose(`csr-rebundle ok (${Date.now() - started}ms)`);
|
|
315
|
-
} catch (err) {
|
|
316
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
317
|
-
this.#logger.error(`csr-rebundle failed: ${message}`);
|
|
318
|
-
this.#sendBuildStatus("csr", { generation, ok: false, files, message });
|
|
319
|
-
}
|
|
320
|
-
} else if (kinds.includes("code") && rebuildClient) {
|
|
321
|
-
this.#logger.verbose(`csr-rebundle skipped; request /__csr or ?csr=true (or set AKAN_DEV_CSR_REBUILD=1) to enable per-save CSR rebuilds`);
|
|
322
|
-
}
|
|
323
|
-
process.send?.(event);
|
|
376
|
+
const needs = [];
|
|
324
377
|
if (kinds.includes("code") && rebuildClient) {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
});
|
|
332
|
-
this.#sendBuildStatus("pages", { generation, ok: true, files });
|
|
333
|
-
this.#logger.verbose(`pages-rebundle ok buildId=${next.buildId} (${Date.now() - started}ms)`);
|
|
334
|
-
} catch (err) {
|
|
335
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
336
|
-
this.#logger.error(`pages-rebundle failed: ${message}`);
|
|
337
|
-
this.#sendBuildStatus("pages", { generation, ok: false, files, message });
|
|
338
|
-
}
|
|
378
|
+
if (this.#shouldRebuildCsr())
|
|
379
|
+
needs.push("csr");
|
|
380
|
+
else
|
|
381
|
+
this.#logger.verbose(`csr-rebundle skipped; request /__csr or ?csr=true (or set AKAN_DEV_CSR_REBUILD=1) to enable per-save CSR rebuilds`);
|
|
382
|
+
needs.push("pages");
|
|
383
|
+
needs.push("css");
|
|
339
384
|
}
|
|
340
|
-
|
|
341
|
-
|
|
385
|
+
process.send?.(event);
|
|
386
|
+
if (needs.length > 0)
|
|
387
|
+
await this.#runBatch({ generation, needs, changedFiles: files });
|
|
388
|
+
else if (kinds.includes("css")) {
|
|
389
|
+
this.scheduleCssRebuild({ generation, changedFiles: files });
|
|
342
390
|
this.#logger.verbose(`css-rebuild scheduled generation=${generation}`);
|
|
343
391
|
}
|
|
344
392
|
}
|
|
393
|
+
async#runBatch({
|
|
394
|
+
generation,
|
|
395
|
+
needs,
|
|
396
|
+
changedFiles
|
|
397
|
+
}) {
|
|
398
|
+
const started = Date.now();
|
|
399
|
+
const result = await this.#batchRunner.run(await this.#batchRequest({ generation, needs, changedFiles }), (msg) => process.send?.(msg));
|
|
400
|
+
if (result.optimizedFonts)
|
|
401
|
+
this.#optimizedFonts = result.optimizedFonts;
|
|
402
|
+
if (result.cssAssets)
|
|
403
|
+
this.#artifact = { ...this.#artifact, cssAssets: result.cssAssets };
|
|
404
|
+
if (result.crashed) {
|
|
405
|
+
for (const need of needs)
|
|
406
|
+
this.#sendBuildStatus(need, { generation, ok: false, files: changedFiles, message: result.errors[need] });
|
|
407
|
+
}
|
|
408
|
+
if (needs.includes("css"))
|
|
409
|
+
this.#logger.verbose(`css-rebuild checked (${Date.now() - started}ms)`);
|
|
410
|
+
return result;
|
|
411
|
+
}
|
|
412
|
+
async#batchRequest({
|
|
413
|
+
generation,
|
|
414
|
+
needs,
|
|
415
|
+
changedFiles
|
|
416
|
+
}) {
|
|
417
|
+
return {
|
|
418
|
+
appName: this.#app.name,
|
|
419
|
+
workspaceRoot: this.#app.workspace.workspaceRoot,
|
|
420
|
+
repoName: this.#app.workspace.repoName,
|
|
421
|
+
generation,
|
|
422
|
+
needs,
|
|
423
|
+
changedFiles,
|
|
424
|
+
pageKeys: await this.#app.getPageKeys(),
|
|
425
|
+
optimizedFonts: this.#optimizedFonts,
|
|
426
|
+
cssAssets: this.#artifact.cssAssets ?? null,
|
|
427
|
+
artifactDir: path2.resolve(this.#artifactDir)
|
|
428
|
+
};
|
|
429
|
+
}
|
|
345
430
|
async boot() {
|
|
346
431
|
if (this.#watch)
|
|
347
432
|
await this.installWatcher();
|
|
@@ -351,34 +436,46 @@ ${cssText}`).toString(36);
|
|
|
351
436
|
async announceRecoveredState(changedFiles) {
|
|
352
437
|
const generation = ++this.#generation;
|
|
353
438
|
await this.#enqueueWork("boot-recovered", async () => {
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
this.#
|
|
364
|
-
this.#
|
|
439
|
+
await this.#runBatch({ generation, needs: ["pages", "css"], changedFiles });
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
async announceBootState() {
|
|
443
|
+
const generation = ++this.#generation;
|
|
444
|
+
const reason = "builder-recycle";
|
|
445
|
+
process.send?.({
|
|
446
|
+
type: "pages-updated",
|
|
447
|
+
data: {
|
|
448
|
+
bundlePath: this.#artifact.pagesBundlePath,
|
|
449
|
+
buildId: this.#artifact.pagesBundleBuildId,
|
|
450
|
+
generation,
|
|
451
|
+
changedFiles: [],
|
|
452
|
+
reason
|
|
365
453
|
}
|
|
366
|
-
this.scheduleCssRebuild(this.#artifactDir, { refresh: true, generation, changedFiles });
|
|
367
454
|
});
|
|
455
|
+
const cssAssets = this.#artifact.cssAssets ?? {};
|
|
456
|
+
const cssBase64ByUrl = Object.fromEntries(await Promise.all(Object.values(cssAssets).map(async ({ cssUrl, cssRelPath }) => [
|
|
457
|
+
cssUrl,
|
|
458
|
+
Buffer.from(await Bun.file(path2.join(this.#artifactDir, cssRelPath)).arrayBuffer()).toString("base64")
|
|
459
|
+
])));
|
|
460
|
+
process.send?.({
|
|
461
|
+
type: "css-updated",
|
|
462
|
+
data: { cssAssets, cssBase64ByUrl, generation, changedFiles: [], reason }
|
|
463
|
+
});
|
|
464
|
+
this.#logger.verbose(`announced boot state after recycle generation=${generation}`);
|
|
368
465
|
}
|
|
369
466
|
async handleBuildCsr(msg) {
|
|
370
|
-
|
|
467
|
+
await this.#enqueueWork("build-csr", async () => {
|
|
371
468
|
const started = Date.now();
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
this.#logger.
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
379
|
-
this.#logger.error(`csr-build failed: ${message}`);
|
|
380
|
-
return { type: "build-csr-res", id: msg.id, ok: false, error: message };
|
|
469
|
+
const result = await this.#batchRunner.run(await this.#batchRequest({ generation: this.#generation, needs: ["csr"], changedFiles: [] }));
|
|
470
|
+
const error = result.errors.csr;
|
|
471
|
+
if (error) {
|
|
472
|
+
this.#logger.error(`csr-build failed: ${error}`);
|
|
473
|
+
await BuilderReply.send({ type: "build-csr-res", id: msg.id, ok: false, error });
|
|
474
|
+
return;
|
|
381
475
|
}
|
|
476
|
+
this.#csrActive = true;
|
|
477
|
+
this.#logger.info(`csr-build ok on demand (${Date.now() - started}ms); rebuilding CSR on every save now`);
|
|
478
|
+
await BuilderReply.send({ type: "build-csr-res", id: msg.id, ok: true });
|
|
382
479
|
});
|
|
383
480
|
}
|
|
384
481
|
#shouldRebuildCsr() {
|
|
@@ -388,11 +485,21 @@ ${cssText}`).toString(36);
|
|
|
388
485
|
return process.env.AKAN_DEV_CSR_REBUILD === "1";
|
|
389
486
|
}
|
|
390
487
|
static async#buildBootDeps(app) {
|
|
391
|
-
const { artifact,
|
|
392
|
-
if (IncrementalBuilder.#csrArmedByEnv())
|
|
393
|
-
await new CsrArtifactBuilder(app).build();
|
|
488
|
+
const { artifact, optimizedFonts } = await new SsrBaseArtifactBuilder(app).build();
|
|
394
489
|
const discovery = await GraphClientEntryDiscovery.create(app);
|
|
395
|
-
return { artifact,
|
|
490
|
+
return { artifact, optimizedFonts, discovery };
|
|
491
|
+
}
|
|
492
|
+
async rearmCsrFromEnv() {
|
|
493
|
+
if (!IncrementalBuilder.#csrArmedByEnv())
|
|
494
|
+
return;
|
|
495
|
+
this.#csrActive = true;
|
|
496
|
+
await this.#enqueueWork("build-csr-rearm", async () => {
|
|
497
|
+
const result = await this.#batchRunner.run(await this.#batchRequest({ generation: this.#generation, needs: ["csr"], changedFiles: [] }));
|
|
498
|
+
if (result.errors.csr)
|
|
499
|
+
this.#logger.error(`csr-rearm failed: ${result.errors.csr}`);
|
|
500
|
+
else
|
|
501
|
+
this.#logger.verbose("csr-rearm ok; this session had CSR armed before the builder restarted");
|
|
502
|
+
});
|
|
396
503
|
}
|
|
397
504
|
static #recoverBoot(app, bootError, logger) {
|
|
398
505
|
const firstMessage = bootError instanceof Error ? bootError.message : String(bootError);
|
|
@@ -430,13 +537,13 @@ ${cssText}`).toString(36);
|
|
|
430
537
|
}
|
|
431
538
|
}
|
|
432
539
|
});
|
|
433
|
-
watcher.start();
|
|
540
|
+
await watcher.start();
|
|
434
541
|
logger.warn(`[degraded] watching ${roots.length} roots for a fix`);
|
|
435
542
|
})().catch(reject);
|
|
436
543
|
});
|
|
437
544
|
}
|
|
438
545
|
static async main() {
|
|
439
|
-
const logger = new
|
|
546
|
+
const logger = new Logger2("IncrementalBuilder");
|
|
440
547
|
const { appName, repoName, workspaceRoot } = WorkspaceExecutor.getBaseDevEnv();
|
|
441
548
|
if (!workspaceRoot || !appName)
|
|
442
549
|
throw new Error("AKAN_WORKSPACE_ROOT or AKAN_PUBLIC_APP_NAME is not set");
|
|
@@ -445,23 +552,34 @@ ${cssText}`).toString(36);
|
|
|
445
552
|
const watch = process.env.AKAN_WATCH !== "0";
|
|
446
553
|
let builder = null;
|
|
447
554
|
const bootingError = "builder is recovering from a failed boot build; retry after the build error is fixed";
|
|
555
|
+
const recyclingError = "builder is recycling to release bundler memory; retry after it restarts";
|
|
448
556
|
process.on("message", (msg) => {
|
|
449
557
|
if (!msg || typeof msg !== "object")
|
|
450
558
|
return;
|
|
451
|
-
if (msg.type === "
|
|
559
|
+
if (msg.type === "builder-shutdown") {
|
|
452
560
|
if (!builder) {
|
|
453
|
-
|
|
561
|
+
logger.warn(`ignoring shutdown request (${msg.reason}); builder is still recovering from a failed boot`);
|
|
454
562
|
return;
|
|
455
563
|
}
|
|
456
|
-
builder.
|
|
564
|
+
builder.shutdown(msg.reason);
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
if (msg.type === "build-route") {
|
|
568
|
+
const error = builder?.shuttingDown ? recyclingError : bootingError;
|
|
569
|
+
if (!builder || builder.shuttingDown) {
|
|
570
|
+
process.send?.({ type: "build-route-res", id: msg.id, ok: false, error });
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
builder.handleBuildRoute(msg);
|
|
457
574
|
return;
|
|
458
575
|
}
|
|
459
576
|
if (msg.type === "build-csr") {
|
|
460
|
-
|
|
461
|
-
|
|
577
|
+
const error = builder?.shuttingDown ? recyclingError : bootingError;
|
|
578
|
+
if (!builder || builder.shuttingDown) {
|
|
579
|
+
process.send?.({ type: "build-csr-res", id: msg.id, ok: false, error });
|
|
462
580
|
return;
|
|
463
581
|
}
|
|
464
|
-
builder.handleBuildCsr(msg)
|
|
582
|
+
builder.handleBuildCsr(msg);
|
|
465
583
|
}
|
|
466
584
|
});
|
|
467
585
|
process.on("disconnect", () => {
|
|
@@ -481,6 +599,9 @@ ${cssText}`).toString(36);
|
|
|
481
599
|
await builder.boot();
|
|
482
600
|
if (recoveredFiles)
|
|
483
601
|
await builder.announceRecoveredState(recoveredFiles);
|
|
602
|
+
else if (process.env.AKAN_BUILDER_ANNOUNCE_BOOT === "1")
|
|
603
|
+
await builder.announceBootState();
|
|
604
|
+
await builder.rearmCsrFromEnv();
|
|
484
605
|
}
|
|
485
606
|
}
|
|
486
607
|
IncrementalBuilder.main().catch((err) => {
|