@akanjs/cli 3.0.0-beta.9 → 3.0.0
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-2vmp9qe0.js +9 -0
- package/CodeAgentRpcHost-p7h6n2hp.js +7 -0
- package/CodeAgentRpcListener-7p7z5dd7.js +97 -0
- package/CodeTui-5ed6bg2a.js +1457 -0
- package/README.ko.md +83 -28
- package/README.md +70 -27
- package/TunnelShare-cwh9ezsm.js +9 -0
- package/{agent.command-0q17pnrs.js → agent.command-gn73rhe7.js} +8 -9
- package/akanCode.js +102 -0
- package/application.command-e968t0hw.js +246 -0
- package/{applicationBuildRunner-kxwbssgn.js → applicationBuildRunner-64taxseg.js} +121 -77
- package/buildBatch.proc.js +120 -32
- package/cloud.command-0jm301rr.js +95 -0
- package/cloud.script-nemnzjpv.js +20 -0
- package/code.command-18kcm7hq.js +154 -0
- package/commandManifest.json +1 -1
- package/consoleToStderr-h5bryj94.js +7 -0
- package/context.command-trd9bn18.js +48 -0
- package/context.runner-y5hwh673.js +21 -0
- package/{dependencyScanner-shs82ynp.js → dependencyScanner-06z2dmrr.js} +2 -2
- package/{devTui-m1atgkc9.js → devTui-q7k7bakd.js} +122 -40
- package/fileSys-wzzrntkw.js +9 -0
- package/guideline.command-bdmdn2de.js +42 -0
- package/guidelines/agentRule/agentRule.instruction.md +154 -26
- package/guidelines/conventions/conventions.instruction.md +134 -48
- package/guidelines/mcpRule/mcpRule.instruction.md +7 -5
- package/guidelines/queryRule/queryRule.instruction.md +21 -7
- package/guidelines/runtimeRule/runtimeRule.instruction.md +291 -6
- package/guidelines/ssrRule/ssrRule.instruction.md +4 -2
- package/guidelines/transportRule/transportRule.instruction.md +10 -8
- package/guidelines/workspaceOnboarding/workspaceOnboarding.instruction.md +20 -0
- package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +66 -3
- package/incrementalBuilder.proc.js +564 -109
- package/index-0wpybxtr.js +2729 -0
- package/{index-kcxmv48c.js → index-2ahj896a.js} +45 -79
- package/index-2wc41k5w.js +49 -0
- package/index-3hx1z96c.js +87 -0
- package/{index-2tv3fdx4.js → index-3w94e7n3.js} +1228 -1458
- package/{index-hwkygn9s.js → index-48k96p55.js} +325 -36
- package/index-4npz05nm.js +187 -0
- package/{index-q0y2jq9j.js → index-52w0fvh8.js} +21 -4
- package/index-6he6gvfg.js +4300 -0
- package/index-7md2g9vw.js +6 -0
- package/{index-04bk599g.js → index-93j60em0.js} +10 -17
- package/index-94s080qm.js +266 -0
- package/index-9dp031by.js +12 -0
- package/{index-ktb5njgy.js → index-9z771ptz.js} +49 -99
- package/{index-y6n8nmny.js → index-abxpehf8.js} +75 -203
- package/{index-3d9y066c.js → index-bjwxs904.js} +9 -8
- package/{index-9077wzd2.js → index-by9pxkr0.js} +43 -26
- package/index-c6eggbee.js +123 -0
- package/index-d2242ksy.js +99 -0
- package/index-drehwpdt.js +1119 -0
- package/{index-92zztqy1.js → index-dy0jx9ay.js} +2 -17
- package/{index-ffvk10zz.js → index-f3c78nd6.js} +107 -241
- package/index-gbggbqcp.js +1058 -0
- package/{index-by5gpp8x.js → index-hj6w4ka1.js} +2 -1
- package/index-hsmtysz4.js +53 -0
- package/{index-j9yhg404.js → index-jmrn54tt.js} +4 -7
- package/{index-a64esmtb.js → index-kcdj1z5v.js} +523 -1022
- package/{index-d43r08tf.js → index-kqabytj7.js} +77 -150
- package/{index-vyvre9en.js → index-mbxbhay5.js} +6 -2
- package/{index-vxtad5bd.js → index-mgxpx3f2.js} +5 -8
- package/index-p6pbz9n5.js +25 -0
- package/{index-sj5vxr8f.js → index-p993efkq.js} +981 -897
- package/{index-2xp0kfkt.js → index-pmhqttv3.js} +37 -25
- package/index-tqm7jjcn.js +700 -0
- package/{index-0g7r8xz3.js → index-vpj42xwt.js} +114 -232
- package/index-wa5jbxj1.js +760 -0
- package/{index-k2j1hc4r.js → index-wprnsjms.js} +12 -12
- package/{index-gyd220p2.js → index-y3mphr1m.js} +554 -577
- package/index-yg7jhz8x.js +69 -0
- package/{index-h7yw2z9n.js → index-zzvvc845.js} +8 -14
- package/index.js +23 -22
- package/{library.command-qzzbj6d8.js → library.command-9h749a1x.js} +4 -5
- package/{localRegistry.command-vyrgg4xw.js → localRegistry.command-2bqqhxyz.js} +23 -43
- package/{module.command-rq5hq3ax.js → module.command-nv8nzgft.js} +8 -12
- package/{package.command-d9hd6v9n.js → package.command-mq4bggp6.js} +6 -7
- package/package.json +15 -7
- package/{page.command-126d56te.js → page.command-j639n707.js} +4 -5
- package/{primitive.command-taxd1538.js → primitive.command-kz6849th.js} +9 -23
- package/prompter-76k56b37.js +8 -0
- package/{quality.command-6gsna0a3.js → quality.command-4hyabwkb.js} +205 -377
- package/{recipeScanner-fpagtgc1.js → recipeScanner-4b69ybe0.js} +1 -1
- package/{repair.command-tq2dgy72.js → repair.command-a9awpbqr.js} +11 -35
- package/{routeSourceValidator-apyqw7wk.js → routeSourceValidator-7ycnfz0e.js} +0 -6
- package/{scalar.command-3bcmrrkv.js → scalar.command-ma4fpm9m.js} +7 -11
- package/skills/akan-code/SKILL.md +130 -0
- package/skills/akan-debug/SKILL.md +42 -0
- package/skills/akan-deps/SKILL.md +50 -0
- package/skills/akan-err/SKILL.md +39 -0
- package/skills/akan-module/SKILL.md +45 -0
- package/skills/akan-scaffold/SKILL.md +76 -0
- package/skills/akan-scalar/SKILL.md +52 -0
- package/skills/akan-service/SKILL.md +34 -0
- package/skills/akan-store/SKILL.md +67 -0
- package/skills/akan-ui/SKILL.md +63 -0
- package/skills/akan-validate/SKILL.md +44 -0
- package/{subspace.command-eddzqpx3.js → subspace.command-3k2pmrs4.js} +53 -62
- package/templates/__scalar/__model__/__model__.abstract.md.template +8 -0
- package/templates/app/page/_index.tsx +257 -258
- package/templates/app/page/_layout.tsx +5 -1
- package/templates/app/page/_overrides.tsx +12 -0
- package/templates/app/page/styles.css.template +316 -14
- package/templates/app/public/apple-touch-icon.png +0 -0
- package/templates/app/public/favicon.ico +0 -0
- package/templates/app/public/jelly/cloud.webp +0 -0
- package/templates/app/public/jelly/comet.webp +0 -0
- package/templates/app/public/jelly/moon.webp +0 -0
- package/templates/app/public/jelly/planet.webp +0 -0
- package/templates/app/public/jelly/rocket.webp +0 -0
- package/templates/app/public/jelly/star.webp +0 -0
- package/templates/app/public/jelly/sugar.webp +0 -0
- package/templates/app/public/logo.png +0 -0
- package/templates/app/ui/Recipe/index.ts +9 -0
- package/templates/app/ui/Recipe/jellyButton.ts +63 -0
- package/templates/appSample/lib/__scalar/workHistory/workHistory.abstract.ts +8 -39
- package/templates/appSample/lib/_noti/noti.abstract.ts +6 -29
- package/templates/appSample/lib/task/Task.Unit.tsx +1 -1
- package/templates/appSample/lib/task/Task.Util.tsx +10 -13
- package/templates/appSample/lib/task/Task.View.tsx +2 -2
- package/templates/appSample/lib/task/Task.Zone.tsx +5 -2
- package/templates/appSample/lib/task/task.abstract.ts +12 -32
- package/templates/appSample/lib/task/task.signal.spec.ts +1 -15
- package/templates/appSample/lib/task/task.signal.test.ts +15 -1
- package/templates/appSample/lib/task/task.signal.ts +10 -8
- package/templates/appSample/page/task/[taskId]/_index.tsx +6 -6
- package/templates/appSample/page/task/[taskId]/edit.tsx +5 -4
- package/templates/appSample/page/task/_index.tsx +4 -3
- package/templates/appSample/page/task/_layout.tsx +1 -1
- package/templates/appSample/page/task/new.tsx +5 -4
- package/templates/appSample/srvkit/{AuthGuard.ts → guards.ts} +7 -4
- package/templates/appSample/srvkit/{SessionInternalArg.ts → internalArgs.ts} +6 -4
- package/templates/appSample/ui/GlobalLoading.tsx +2 -1
- package/templates/appSample/ui/QuantityControl.tsx +3 -3
- package/templates/client.ts +7 -30
- package/templates/crudPages/[__model__Id]/edit/page.tsx +2 -1
- package/templates/crudPages/[__model__Id]/page.tsx +2 -1
- package/templates/crudPages/new/page.tsx +2 -1
- package/templates/crudPages/page.tsx +2 -1
- package/templates/crudSinglePage/page.tsx +2 -1
- package/templates/lib/__lib/lib.dictionary.ts +0 -2
- package/templates/lib/__lib/lib.document.ts +0 -2
- package/templates/lib/sig.ts +8 -30
- package/templates/localDev/docker-compose.yaml.template +3 -14
- package/templates/module/__model__.signal.ts +12 -4
- package/templates/pkgRoot/tsconfig.json.template +2 -7
- package/templates/server.ts +1 -3
- package/templates/service/__model__.abstract.md.template +8 -0
- package/templates/workspaceRoot/.gitignore.template +0 -1
- package/templates/workspaceRoot/AGENTS.md.template +6 -0
- package/templates/workspaceRoot/package.json.template +1 -3
- package/tunnel.command-xwr46b66.js +129 -0
- package/{typeChecker-3xgrzc2v.js → typeChecker-px49mezc.js} +1 -1
- package/typecheck.proc.js +1 -1
- package/workflow.command-t7wqe4h7.js +79 -0
- package/{workspace.command-06wvstgr.js → workspace.command-4derhzrh.js} +79 -109
- package/abstractCompactor-gmkc0z8j.js +0 -102
- package/application.command-18y0s485.js +0 -204
- package/applicationReleasePackager-4yasxwv9.js +0 -244
- package/capacitorApp-36rbr858.js +0 -12
- package/cloud.command-av97ndh6.js +0 -94
- package/cloud.script-cm11xjdz.js +0 -20
- package/context.command-5v37ef3n.js +0 -81
- package/devTuiView-0hc2emhe.js +0 -9
- package/getDirname-dr3jz1xg.js +0 -7
- package/guideline.command-4pz9g1rt.js +0 -360
- package/guidelines/agentRule/agentRule.generate.json +0 -31
- package/guidelines/componentRule/componentRule.generate.json +0 -31
- package/guidelines/conventions/conventions.generate.json +0 -25
- package/guidelines/cssRule/cssRule.generate.json +0 -31
- package/guidelines/docPageRule/docPageRule.generate.json +0 -31
- package/guidelines/docPageRule/docPageRule.instruction.md +0 -25
- package/guidelines/docSyncRule/docSyncRule.generate.json +0 -31
- package/guidelines/docSyncRule/docSyncRule.instruction.md +0 -25
- package/guidelines/enumConstant/enumConstant.generate.json +0 -31
- package/guidelines/enumConstant/enumConstant.instruction.md +0 -28
- package/guidelines/fieldRule/fieldRule.generate.json +0 -31
- package/guidelines/framework/framework.generate.json +0 -32
- package/guidelines/mcpRule/mcpRule.generate.json +0 -35
- package/guidelines/modelConstant/modelConstant.generate.json +0 -32
- package/guidelines/modelDictionary/modelDictionary.generate.json +0 -32
- package/guidelines/modelDocument/modelDocument.generate.json +0 -31
- package/guidelines/modelService/modelService.generate.json +0 -31
- package/guidelines/modelSignal/modelSignal.generate.json +0 -31
- package/guidelines/modelStore/modelStore.generate.json +0 -31
- package/guidelines/modelTemplate/modelTemplate.generate.json +0 -31
- package/guidelines/modelUnit/modelUnit.generate.json +0 -31
- package/guidelines/modelUtil/modelUtil.generate.json +0 -31
- package/guidelines/modelView/modelView.generate.json +0 -31
- package/guidelines/modelZone/modelZone.generate.json +0 -31
- package/guidelines/moduleCodegen/moduleCodegen.generate.json +0 -31
- package/guidelines/moduleCodegen/moduleCodegen.instruction.md +0 -28
- package/guidelines/moduleOverview/moduleOverview.generate.json +0 -31
- package/guidelines/queryRule/queryRule.generate.json +0 -31
- package/guidelines/recipeRule/recipeRule.generate.json +0 -31
- package/guidelines/runtimeRule/runtimeRule.generate.json +0 -25
- package/guidelines/scalarConstant/scalarConstant.generate.json +0 -32
- package/guidelines/scalarDictionary/scalarDictionary.generate.json +0 -32
- package/guidelines/scalarModule/scalarModule.generate.json +0 -31
- package/guidelines/sharedUiUsage/sharedUiUsage.generate.json +0 -31
- package/guidelines/sharedUiUsage/sharedUiUsage.instruction.md +0 -25
- package/guidelines/ssrRule/ssrRule.generate.json +0 -31
- package/guidelines/transportRule/transportRule.generate.json +0 -31
- package/guidelines/utilUiUsage/utilUiUsage.generate.json +0 -31
- package/guidelines/utilUiUsage/utilUiUsage.instruction.md +0 -25
- package/guidelines/workspaceOnboarding/workspaceOnboarding.generate.json +0 -24
- package/guidelines/workspaceRecipes/workspaceRecipes.generate.json +0 -33
- package/index-47pj3aee.js +0 -76
- package/index-5ystnsd5.js +0 -841
- package/index-6wwzs1bc.js +0 -7
- package/index-a17x49vg.js +0 -229
- package/index-m0xccmmm.js +0 -228
- package/index-meh558jy.js +0 -65
- package/index-rtxxan88.js +0 -471
- package/index-te54x9zm.js +0 -338
- package/index-w8kmx1b6.js +0 -2176
- package/index-wt7qrtd4.js +0 -1516
- package/templates/__scalar/__model__/__model__.abstract.md +0 -28
- package/templates/module/__model__.abstract.md +0 -28
- package/templates/service/__model__.abstract.md +0 -28
- package/templates/workspaceRoot/patches/react-dom@19.2.7.patch.template +0 -62
- package/workflow.command-gtdnzvbz.js +0 -108
|
@@ -1,41 +1,41 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
import {
|
|
3
|
+
ApplicationBuildReporter,
|
|
4
|
+
HmrWatcher,
|
|
5
|
+
WatchRootResolver
|
|
6
|
+
} from "./index-48k96p55.js";
|
|
2
7
|
import {
|
|
3
8
|
CodegenLock,
|
|
4
9
|
WorkspaceExecutor,
|
|
5
10
|
AppExecutor
|
|
6
|
-
} from "./index-
|
|
7
|
-
import"./index-
|
|
8
|
-
import"./index-6wwzs1bc.js";
|
|
11
|
+
} from "./index-3w94e7n3.js";
|
|
12
|
+
import"./index-52w0fvh8.js";
|
|
9
13
|
import {
|
|
10
14
|
GraphClientEntryDiscovery,
|
|
11
15
|
RouteClientBuilder,
|
|
12
16
|
AutoImportSync,
|
|
17
|
+
ServerGraphFile,
|
|
18
|
+
CsrDevPatcher,
|
|
19
|
+
CsrDevBundler,
|
|
13
20
|
DevChangePlanner,
|
|
14
|
-
DevGeneratedIndexSync
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
HmrWatcher,
|
|
18
|
-
WatchRootResolver
|
|
19
|
-
} from "./index-hwkygn9s.js";
|
|
21
|
+
DevGeneratedIndexSync,
|
|
22
|
+
SsrDevBundler
|
|
23
|
+
} from "./index-6he6gvfg.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";
|
|
30
36
|
import { Logger } from "akanjs/common";
|
|
31
37
|
|
|
32
38
|
class BuildBatchRunner {
|
|
33
|
-
static #entryCandidates = (workspaceRoot) => [
|
|
34
|
-
path.join(workspaceRoot, "pkgs/@akanjs/devkit/incrementalBuilder/buildBatch.proc.ts"),
|
|
35
|
-
path.join(workspaceRoot, "node_modules/@akanjs/devkit/incrementalBuilder/buildBatch.proc.ts"),
|
|
36
|
-
path.join(import.meta.dir, "buildBatch.proc.js"),
|
|
37
|
-
path.join(import.meta.dir, "buildBatch.proc.ts")
|
|
38
|
-
];
|
|
39
39
|
#logger = new Logger("BuildBatchRunner");
|
|
40
40
|
#entry = null;
|
|
41
41
|
#workspaceRoot;
|
|
@@ -47,7 +47,12 @@ class BuildBatchRunner {
|
|
|
47
47
|
async#resolveEntry() {
|
|
48
48
|
if (this.#entry)
|
|
49
49
|
return this.#entry;
|
|
50
|
-
const candidates =
|
|
50
|
+
const candidates = [
|
|
51
|
+
path.join(this.#workspaceRoot, "pkgs/@akanjs/devkit/incrementalBuilder/buildBatch.proc.ts"),
|
|
52
|
+
path.join(this.#workspaceRoot, "node_modules/@akanjs/devkit/incrementalBuilder/buildBatch.proc.ts"),
|
|
53
|
+
path.join(import.meta.dir, "buildBatch.proc.js"),
|
|
54
|
+
path.join(import.meta.dir, "buildBatch.proc.ts")
|
|
55
|
+
];
|
|
51
56
|
for (const candidate of candidates) {
|
|
52
57
|
if (!await Bun.file(candidate).exists())
|
|
53
58
|
continue;
|
|
@@ -62,7 +67,9 @@ class BuildBatchRunner {
|
|
|
62
67
|
const started = Date.now();
|
|
63
68
|
const entry = await this.#resolveEntry();
|
|
64
69
|
let result = null;
|
|
65
|
-
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)], {
|
|
66
73
|
cwd: this.#cwd,
|
|
67
74
|
env: process.env,
|
|
68
75
|
stdio: ["ignore", "inherit", "inherit"],
|
|
@@ -72,8 +79,11 @@ class BuildBatchRunner {
|
|
|
72
79
|
return;
|
|
73
80
|
if (message.type === "build-batch-result")
|
|
74
81
|
result = message.data;
|
|
75
|
-
else
|
|
82
|
+
else {
|
|
83
|
+
if (message.type === "build-status")
|
|
84
|
+
reported.set(message.data.phase, message.data.message);
|
|
76
85
|
onMessage(message);
|
|
86
|
+
}
|
|
77
87
|
}
|
|
78
88
|
});
|
|
79
89
|
const exitCode = await proc.exited;
|
|
@@ -83,11 +93,14 @@ class BuildBatchRunner {
|
|
|
83
93
|
}
|
|
84
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`;
|
|
85
95
|
this.#logger.error(`[build-batch] generation=${request.generation} ${message}`);
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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 };
|
|
91
104
|
}
|
|
92
105
|
}
|
|
93
106
|
|
|
@@ -124,6 +137,90 @@ class BuilderChannel {
|
|
|
124
137
|
}
|
|
125
138
|
}
|
|
126
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
|
+
|
|
127
224
|
// pkgs/@akanjs/devkit/incrementalBuilder/incrementalBuilder.proc.ts
|
|
128
225
|
class IncrementalBuilder {
|
|
129
226
|
#logger = new Logger2("IncrementalBuilder");
|
|
@@ -138,8 +235,29 @@ class IncrementalBuilder {
|
|
|
138
235
|
#autoImportSync;
|
|
139
236
|
#watcher = null;
|
|
140
237
|
#generation = 0;
|
|
238
|
+
#discoveredThrough = 0;
|
|
141
239
|
#csrActive = IncrementalBuilder.#csrArmedByEnv();
|
|
142
|
-
#
|
|
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;
|
|
143
261
|
#inFlight = 0;
|
|
144
262
|
#workCount = 0;
|
|
145
263
|
#shuttingDown = false;
|
|
@@ -153,6 +271,7 @@ class IncrementalBuilder {
|
|
|
153
271
|
this.#optimizedFonts = options.optimizedFonts;
|
|
154
272
|
this.#discovery = options.discovery;
|
|
155
273
|
this.#generation = options.initialGeneration ?? 0;
|
|
274
|
+
this.#discoveredThrough = this.#generation;
|
|
156
275
|
this.#batchRunner = new BuildBatchRunner({
|
|
157
276
|
workspaceRoot: options.app.workspace.workspaceRoot,
|
|
158
277
|
cwd: options.app.cwdPath
|
|
@@ -160,25 +279,41 @@ class IncrementalBuilder {
|
|
|
160
279
|
this.#changePlanner = new DevChangePlanner({ workspaceRoot: options.app.workspace.workspaceRoot });
|
|
161
280
|
this.#generatedIndexSync = new DevGeneratedIndexSync({ workspaceRoot: options.app.workspace.workspaceRoot });
|
|
162
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
|
+
});
|
|
163
294
|
}
|
|
164
295
|
get #artifactDir() {
|
|
165
296
|
return `${this.#app.cwdPath}/.akan/artifact`;
|
|
166
297
|
}
|
|
167
298
|
async handleBuildRoute(msg) {
|
|
168
|
-
await this.#
|
|
299
|
+
await this.#workQueue.enqueue(`build-route:${msg.routeId}`, async () => BuilderChannel.send(await this.#handleBuildRoute(msg)));
|
|
169
300
|
}
|
|
170
301
|
async#handleBuildRoute(msg) {
|
|
302
|
+
const seenGeneration = this.#discoveredThrough;
|
|
171
303
|
try {
|
|
172
304
|
const delta = await new RouteClientBuilder({
|
|
173
305
|
app: this.#app,
|
|
174
306
|
routeId: msg.routeId,
|
|
175
307
|
seeds: msg.seeds,
|
|
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
|
-
const errMsg = err
|
|
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);
|
|
@@ -335,24 +492,24 @@ class IncrementalBuilder {
|
|
|
335
492
|
}, 150);
|
|
336
493
|
}
|
|
337
494
|
async installWatcher() {
|
|
338
|
-
const artifactDir = this.#artifactDir;
|
|
339
495
|
const roots = await new WatchRootResolver(this.#app).resolve();
|
|
340
496
|
const watcher = new HmrWatcher({
|
|
341
497
|
roots,
|
|
342
498
|
logger: this.#logger,
|
|
343
499
|
onBatch: async (batch) => {
|
|
344
|
-
await this.#
|
|
500
|
+
await this.#enqueueFast("hmr-batch", async () => this.#handleWatchBatch(batch));
|
|
345
501
|
}
|
|
346
502
|
});
|
|
347
503
|
await watcher.start();
|
|
348
504
|
this.#watcher = watcher;
|
|
349
505
|
this.#logger.verbose(`watching ${roots.length} roots`);
|
|
350
506
|
}
|
|
351
|
-
async#handleWatchBatch(
|
|
507
|
+
async#handleWatchBatch(batch) {
|
|
352
508
|
const rawKinds = new Set(batch.kinds);
|
|
353
509
|
if (rawKinds.size === 0)
|
|
354
510
|
return;
|
|
355
511
|
const generation = ++this.#generation;
|
|
512
|
+
const trace = { ...batch.trace, batchAt: Date.now() };
|
|
356
513
|
const [autoImport, indexSync] = await CodegenLock.run(this.#app.workspace.workspaceRoot, `hmr-batch:${this.#app.name}`, async () => {
|
|
357
514
|
const auto = await this.#autoImportSync.syncForBatch(batch.files);
|
|
358
515
|
return [auto, await this.#generatedIndexSync.syncForBatch(batch.files)];
|
|
@@ -371,22 +528,19 @@ class IncrementalBuilder {
|
|
|
371
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)"}`);
|
|
372
529
|
for (const error of indexSync.errors)
|
|
373
530
|
this.#logger.error(error);
|
|
374
|
-
|
|
375
|
-
const started = Date.now();
|
|
376
|
-
if (kinds.includes("config"))
|
|
377
|
-
this.#discovery = await GraphClientEntryDiscovery.create(this.#app);
|
|
378
|
-
else
|
|
379
|
-
this.#discovery.invalidate?.(files);
|
|
380
|
-
this.#logger.verbose(`client-entry-discovery ${kinds.includes("config") ? "refreshed" : "invalidated"} (${Date.now() - started}ms)`);
|
|
381
|
-
}
|
|
531
|
+
const discovery = kinds.includes("code") ? { files, refresh: kinds.includes("config"), generation } : undefined;
|
|
382
532
|
if (hasSyncErrors) {
|
|
533
|
+
this.#barrelFailed = true;
|
|
383
534
|
this.#sendBuildStatus("barrel", { generation, ok: false, files, message: indexSync.errors.join(`
|
|
384
535
|
`) });
|
|
385
536
|
BuilderChannel.emit(event);
|
|
537
|
+
if (discovery)
|
|
538
|
+
this.#workQueue.enqueue("discovery", async () => await this.#refreshDiscovery(discovery)).catch(this.#slowLaneFailed("discovery"));
|
|
386
539
|
return;
|
|
387
540
|
}
|
|
388
|
-
if (indexSync.changedFiles.length > 0)
|
|
541
|
+
if (indexSync.changedFiles.length > 0 || this.#barrelFailed)
|
|
389
542
|
this.#sendBuildStatus("barrel", { generation, ok: true, files });
|
|
543
|
+
this.#barrelFailed = false;
|
|
390
544
|
const rebuildClient = devPlan.actions.includes("rebuild-client");
|
|
391
545
|
if (kinds.includes("code") && !rebuildClient) {
|
|
392
546
|
this.#logger.verbose(`client rebuild skipped; devPlan actions=${devPlan.actions.join(",") || "(none)"}`);
|
|
@@ -399,48 +553,318 @@ class IncrementalBuilder {
|
|
|
399
553
|
} else if (kinds.includes("code") && rebuildClient && this.batchTouchesPagesTree(pageRoots, expandedBatch)) {
|
|
400
554
|
this.#logger.verbose("pageKeys refresh skipped; changed page source cannot add/remove a route key");
|
|
401
555
|
}
|
|
402
|
-
|
|
556
|
+
BuilderChannel.emit(event);
|
|
403
557
|
if (kinds.includes("code") && rebuildClient) {
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
else
|
|
558
|
+
const needs = ["pages", "css"];
|
|
559
|
+
if (!this.#csrActive)
|
|
407
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`);
|
|
408
|
-
|
|
409
|
-
|
|
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;
|
|
410
582
|
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
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")) {
|
|
415
586
|
this.scheduleCssRebuild({ generation, changedFiles: files });
|
|
416
587
|
this.#logger.verbose(`css-rebuild scheduled generation=${generation}`);
|
|
417
588
|
}
|
|
418
589
|
}
|
|
419
|
-
async#
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
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;
|
|
424
597
|
const started = Date.now();
|
|
425
|
-
const
|
|
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
|
+
}
|
|
840
|
+
async#runBatch(work) {
|
|
841
|
+
const { generation, needs, changedFiles } = work;
|
|
842
|
+
const started = Date.now();
|
|
843
|
+
const result = await this.#batchRunner.run(await this.#batchRequest(work), (msg) => BuilderChannel.emit(msg));
|
|
426
844
|
if (result.optimizedFonts)
|
|
427
845
|
this.#optimizedFonts = result.optimizedFonts;
|
|
428
846
|
if (result.cssAssets)
|
|
429
847
|
this.#artifact = { ...this.#artifact, cssAssets: result.cssAssets };
|
|
430
848
|
if (result.crashed) {
|
|
431
|
-
|
|
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)
|
|
432
855
|
if (need !== "base")
|
|
433
|
-
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
|
+
});
|
|
434
862
|
}
|
|
435
863
|
if (needs.includes("css"))
|
|
436
864
|
this.#logger.verbose(`css-rebuild checked (${Date.now() - started}ms)`);
|
|
437
865
|
return result;
|
|
438
866
|
}
|
|
439
|
-
async#batchRequest({
|
|
440
|
-
generation,
|
|
441
|
-
needs,
|
|
442
|
-
changedFiles
|
|
443
|
-
}) {
|
|
867
|
+
async#batchRequest({ generation, needs, changedFiles, trace }) {
|
|
444
868
|
return {
|
|
445
869
|
appName: this.#app.name,
|
|
446
870
|
workspaceRoot: this.#app.workspace.workspaceRoot,
|
|
@@ -451,7 +875,8 @@ class IncrementalBuilder {
|
|
|
451
875
|
pageKeys: await this.#app.getPageKeys(),
|
|
452
876
|
optimizedFonts: this.#optimizedFonts,
|
|
453
877
|
cssAssets: this.#artifact.cssAssets ?? null,
|
|
454
|
-
artifactDir: path2.resolve(this.#artifactDir)
|
|
878
|
+
artifactDir: path2.resolve(this.#artifactDir),
|
|
879
|
+
...trace ? { trace } : {}
|
|
455
880
|
};
|
|
456
881
|
}
|
|
457
882
|
async boot() {
|
|
@@ -462,7 +887,7 @@ class IncrementalBuilder {
|
|
|
462
887
|
}
|
|
463
888
|
async announceRecoveredState(changedFiles) {
|
|
464
889
|
const generation = ++this.#generation;
|
|
465
|
-
await this.#
|
|
890
|
+
await this.#workQueue.enqueue("boot-recovered", async () => {
|
|
466
891
|
await this.#runBatch({ generation, needs: ["pages", "css"], changedFiles });
|
|
467
892
|
});
|
|
468
893
|
}
|
|
@@ -491,22 +916,47 @@ class IncrementalBuilder {
|
|
|
491
916
|
this.#logger.verbose(`announced boot state after recycle generation=${generation}`);
|
|
492
917
|
}
|
|
493
918
|
async handleBuildCsr(msg) {
|
|
494
|
-
|
|
919
|
+
this.#beginCsrArm();
|
|
920
|
+
this.#csrActive = true;
|
|
921
|
+
const armed = this.#workQueue.enqueue("build-csr", async () => {
|
|
495
922
|
const started = Date.now();
|
|
496
|
-
|
|
497
|
-
|
|
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
|
+
}
|
|
498
930
|
if (error) {
|
|
499
931
|
this.#logger.error(`csr-build failed: ${error}`);
|
|
500
932
|
await BuilderChannel.send({ type: "build-csr-res", id: msg.id, ok: false, error });
|
|
501
933
|
return;
|
|
502
934
|
}
|
|
503
|
-
this.#
|
|
935
|
+
this.#csrArmSucceeded = true;
|
|
936
|
+
this.#patcher?.forget();
|
|
504
937
|
this.#logger.info(`csr-build ok on demand (${Date.now() - started}ms); rebuilding CSR on every save now`);
|
|
505
938
|
await BuilderChannel.send({ type: "build-csr-res", id: msg.id, ok: true });
|
|
506
939
|
});
|
|
940
|
+
this.#csrGate = this.#armingCsr(armed);
|
|
941
|
+
await armed;
|
|
507
942
|
}
|
|
508
|
-
#
|
|
509
|
-
|
|
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);
|
|
510
960
|
}
|
|
511
961
|
static #csrArmedByEnv() {
|
|
512
962
|
return process.env.AKAN_DEV_CSR_REBUILD === "1";
|
|
@@ -534,19 +984,24 @@ class IncrementalBuilder {
|
|
|
534
984
|
async rearmCsrFromEnv() {
|
|
535
985
|
if (!IncrementalBuilder.#csrArmedByEnv())
|
|
536
986
|
return;
|
|
987
|
+
this.#beginCsrArm();
|
|
537
988
|
this.#csrActive = true;
|
|
538
|
-
|
|
539
|
-
|
|
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();
|
|
540
993
|
if (result.errors.csr)
|
|
541
994
|
this.#logger.error(`csr-rearm failed: ${result.errors.csr}`);
|
|
542
995
|
else
|
|
543
996
|
this.#logger.verbose("csr-rearm ok; this session had CSR armed before the builder restarted");
|
|
544
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)}`));
|
|
545
1000
|
}
|
|
546
|
-
static #recoverBoot(app, bootError, logger, runner) {
|
|
1001
|
+
static #recoverBoot(app, bootError, logger, runner, initialGeneration) {
|
|
547
1002
|
const firstMessage = bootError instanceof Error ? bootError.message : String(bootError);
|
|
548
1003
|
logger.error(`boot build failed; entering degraded watch mode until the error is fixed: ${firstMessage}`);
|
|
549
|
-
let generation =
|
|
1004
|
+
let generation = initialGeneration;
|
|
550
1005
|
const sendFailure = (files, message) => {
|
|
551
1006
|
BuilderChannel.emit({
|
|
552
1007
|
type: "build-status",
|
|
@@ -555,6 +1010,7 @@ class IncrementalBuilder {
|
|
|
555
1010
|
};
|
|
556
1011
|
sendFailure([], firstMessage);
|
|
557
1012
|
BuilderChannel.emit({ type: "builder-ready" });
|
|
1013
|
+
BuilderChannel.emit({ type: "boot-armed" });
|
|
558
1014
|
return new Promise((resolve, reject) => {
|
|
559
1015
|
(async () => {
|
|
560
1016
|
const roots = await new WatchRootResolver(app).resolve();
|
|
@@ -606,23 +1062,15 @@ class IncrementalBuilder {
|
|
|
606
1062
|
builder.shutdown(msg.reason);
|
|
607
1063
|
return;
|
|
608
1064
|
}
|
|
609
|
-
if (msg.type
|
|
610
|
-
const error = builder?.shuttingDown ? recyclingError : bootingError;
|
|
611
|
-
if (!builder || builder.shuttingDown) {
|
|
612
|
-
BuilderChannel.emit({ type: "build-route-res", id: msg.id, ok: false, error });
|
|
613
|
-
return;
|
|
614
|
-
}
|
|
615
|
-
builder.handleBuildRoute(msg);
|
|
1065
|
+
if (msg.type !== "build-route" && msg.type !== "build-csr")
|
|
616
1066
|
return;
|
|
617
|
-
|
|
618
|
-
if (msg.type === "build-csr") {
|
|
1067
|
+
if (!builder || builder.shuttingDown) {
|
|
619
1068
|
const error = builder?.shuttingDown ? recyclingError : bootingError;
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
return;
|
|
623
|
-
}
|
|
624
|
-
builder.handleBuildCsr(msg);
|
|
1069
|
+
BuilderChannel.emit({ type: `${msg.type}-res`, id: msg.id, ok: false, error });
|
|
1070
|
+
return;
|
|
625
1071
|
}
|
|
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)}`));
|
|
626
1074
|
});
|
|
627
1075
|
process.on("disconnect", () => {
|
|
628
1076
|
logger.warn("host IPC channel closed; exiting builder");
|
|
@@ -630,12 +1078,14 @@ class IncrementalBuilder {
|
|
|
630
1078
|
});
|
|
631
1079
|
let recoveredFiles = null;
|
|
632
1080
|
const bootRunner = new BuildBatchRunner({ workspaceRoot, cwd: app.cwdPath });
|
|
1081
|
+
const initialGeneration = IncrementalBuilder.#initialGeneration();
|
|
633
1082
|
try {
|
|
634
|
-
|
|
1083
|
+
const deps = await IncrementalBuilder.#buildBootDeps(app, bootRunner);
|
|
1084
|
+
builder = new IncrementalBuilder({ app, watch, initialGeneration, ...deps });
|
|
635
1085
|
} catch (err) {
|
|
636
1086
|
if (!watch)
|
|
637
1087
|
throw err;
|
|
638
|
-
const recovered = await IncrementalBuilder.#recoverBoot(app, err, logger, bootRunner);
|
|
1088
|
+
const recovered = await IncrementalBuilder.#recoverBoot(app, err, logger, bootRunner, initialGeneration);
|
|
639
1089
|
builder = recovered.builder;
|
|
640
1090
|
recoveredFiles = recovered.changedFiles;
|
|
641
1091
|
}
|
|
@@ -644,10 +1094,15 @@ class IncrementalBuilder {
|
|
|
644
1094
|
await builder.announceRecoveredState(recoveredFiles);
|
|
645
1095
|
else if (process.env.AKAN_BUILDER_ANNOUNCE_BOOT === "1")
|
|
646
1096
|
await builder.announceBootState();
|
|
647
|
-
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;
|
|
648
1103
|
}
|
|
649
1104
|
}
|
|
650
|
-
IncrementalBuilder.main().catch((err) => {
|
|
1105
|
+
await IncrementalBuilder.main().catch((err) => {
|
|
651
1106
|
console.error(err);
|
|
652
1107
|
process.exit(1);
|
|
653
1108
|
});
|