@akanjs/devkit 3.0.0-beta.19 → 3.0.0-beta.20
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/abstractDoc.test.ts +6 -15
- package/abstractDoc.ts +1 -4
- package/agentsIndex.test.ts +4 -7
- package/agentsIndex.ts +6 -22
- package/akanApp/BackendImportGraph.test.ts +8 -22
- package/akanApp/BackendImportGraph.ts +1 -20
- package/akanApp/akanApp.host.test.ts +1 -3
- package/akanApp/akanApp.host.ts +64 -250
- package/akanApp/devHostPolicy.test.ts +10 -34
- package/akanApp/devHostPolicy.ts +12 -98
- package/akanConfig/akanConfig.test.ts +30 -39
- package/akanConfig/akanConfig.ts +40 -136
- package/akanContext.ts +106 -159
- package/akanMcpContract.ts +28 -73
- package/appSelectionMemory.ts +1 -4
- package/applicationBuildReporter.ts +37 -5
- package/applicationBuildRunner.ts +25 -59
- package/applicationReleasePackager.ts +24 -44
- package/applicationTestPreload.ts +3 -12
- package/artifact/implicitRootLayout.test.ts +3 -19
- package/artifact/implicitRootLayout.ts +34 -91
- package/artifact/routeSeedIndex.ts +11 -44
- package/biomeBase.ts +2 -5
- package/capacitorApp.test.ts +4 -14
- package/capacitorApp.ts +86 -235
- package/cloud/cloudApi.test.ts +202 -0
- package/cloud/cloudApi.ts +33 -57
- package/cloud/configLock.test.ts +64 -0
- package/cloud/configLock.ts +96 -0
- package/cloud/constants.ts +3 -2
- package/cloud/globalConfig.ts +64 -31
- package/codeAgent/agent/AkanCodeServices.ts +15 -57
- package/codeAgent/agent/AkanContextFiles.ts +5 -28
- package/codeAgent/agent/CodeAgent.ts +33 -108
- package/codeAgent/agent/CodeAgentAsks.ts +7 -30
- package/codeAgent/agent/CodeAgentEventMapper.ts +9 -52
- package/codeAgent/agent/CodeAgentGate.ts +8 -34
- package/codeAgent/agent/CodeAgentProxy.ts +4 -13
- package/codeAgent/agent/CodeAgentRpcHost.ts +3 -16
- package/codeAgent/agent/CodeAgentRpcListener.ts +2 -10
- package/codeAgent/agent/CodeAgentStreamPrinter.ts +4 -15
- package/codeAgent/agent/CodeAgentSuspended.ts +1 -8
- package/codeAgent/agent/CodeAgentUi.ts +2 -7
- package/codeAgent/agent/CodeMailbox.ts +7 -34
- package/codeAgent/agent/CodeSessionFork.ts +4 -19
- package/codeAgent/agent/CodeSessionIndex.ts +5 -31
- package/codeAgent/agent/SubagentPool.ts +48 -58
- package/codeAgent/agent/akanCodeModel.ts +5 -39
- package/codeAgent/agent/akanCodePaths.ts +4 -24
- package/codeAgent/agent/akanContextFiles.test.ts +0 -2
- package/codeAgent/agent/akanSystemPrompt.ts +1 -9
- package/codeAgent/agent/codeAgent.test.ts +178 -0
- package/codeAgent/agent/codeAgentContract.test.ts +5 -28
- package/codeAgent/agent/codeMailbox.test.ts +0 -4
- package/codeAgent/agent/codeSessionFork.test.ts +0 -2
- package/codeAgent/agent/codeSessionIndex.test.ts +9 -4
- package/codeAgent/agent/consoleToStderr.ts +2 -12
- package/codeAgent/agent/jsonLines.ts +29 -0
- package/codeAgent/agent/stringArg.ts +5 -0
- package/codeAgent/feedback/DevLogFeedback.ts +3 -18
- package/codeAgent/feedback/PreviewChrome.ts +5 -29
- package/codeAgent/feedback/PreviewFeedback.ts +2 -9
- package/codeAgent/feedback/PreviewView.ts +3 -15
- package/codeAgent/feedback/TurnFeedback.ts +2 -17
- package/codeAgent/feedback/VerifyFeedback.ts +1 -7
- package/codeAgent/feedback/previewChrome.test.ts +1 -1
- package/codeAgent/index.ts +2 -10
- package/codeAgent/skills/akan-code/SKILL.md +12 -4
- package/codeAgent/tools/AkanCli.ts +4 -16
- package/codeAgent/tools/AkanCodePlugins.ts +21 -43
- package/codeAgent/tools/AkanEditScope.ts +2 -12
- package/codeAgent/tools/AkanEnvKeys.ts +3 -11
- package/codeAgent/tools/AkanToolPack.ts +6 -28
- package/codeAgent/tools/AkanVerifier.ts +1 -8
- package/codeAgent/tools/AskToolPack.ts +3 -15
- package/codeAgent/tools/MailToolPack.ts +0 -9
- package/codeAgent/tools/McpClient.ts +3 -24
- package/codeAgent/tools/McpOAuth.ts +6 -41
- package/codeAgent/tools/McpOAuthLoopback.ts +2 -15
- package/codeAgent/tools/McpServerConfig.ts +7 -26
- package/codeAgent/tools/McpSignIn.ts +46 -49
- package/codeAgent/tools/McpTokenStore.ts +6 -17
- package/codeAgent/tools/McpToolPack.ts +107 -35
- package/codeAgent/tools/SessionToolPack.ts +1 -11
- package/codeAgent/tools/WebToolPack.ts +1 -7
- package/codeAgent/tools/akanCodePlugins.test.ts +231 -63
- package/codeAgent/tools/askToolPack.test.ts +0 -3
- package/codeAgent/tools/mcpOAuth.test.ts +315 -27
- package/codeAgent/tools/mcpServerConfig.test.ts +21 -14
- package/codegenLock.test.ts +4 -13
- package/codegenLock.ts +3 -17
- package/commandDecorators/argMeta.ts +1 -5
- package/commandDecorators/camelToKebabCase.ts +1 -0
- package/commandDecorators/command.ts +44 -102
- package/commandDecorators/commandBuilder.ts +30 -61
- package/commandDecorators/commandDecorators.test.ts +1 -9
- package/commandDecorators/dependencyBuilder.ts +10 -1
- package/commandDecorators/helpFormatter.ts +66 -144
- package/commandDecorators/index.ts +0 -2
- package/commandDecorators/targetMeta.ts +9 -7
- package/createTunnel.ts +7 -14
- package/dependencyScanner.test.ts +4 -16
- package/dependencyScanner.ts +27 -78
- package/devkitUtils.test.ts +47 -19
- package/executors.test.ts +114 -60
- package/executors.ts +165 -484
- package/fileEditor.ts +9 -37
- package/{getDirname.test.ts → fileSys.test.ts} +1 -1
- package/fileSys.ts +6 -8
- package/formSetterScanner.ts +3 -16
- package/frontendBuild/allRoutesBuilder.ts +7 -21
- package/frontendBuild/autoImportSync.test.ts +14 -38
- package/frontendBuild/autoImportSync.ts +15 -67
- package/frontendBuild/buildRouteClient.test.ts +10 -36
- package/frontendBuild/bundleDefine.ts +12 -0
- package/frontendBuild/clientBuildTypes.ts +5 -36
- package/frontendBuild/clientEntriesBundler.ts +9 -40
- package/frontendBuild/clientEntryDiscovery.ts +6 -41
- package/frontendBuild/csrArtifactBuilder.ts +6 -25
- package/frontendBuild/cssCandidateCache.test.ts +0 -6
- package/frontendBuild/cssCandidateCache.ts +6 -41
- package/frontendBuild/cssCompiler.ts +29 -78
- package/frontendBuild/cssImportResolver.ts +5 -18
- package/frontendBuild/devChangePlanner.ts +7 -10
- package/frontendBuild/devGeneratedIndexSync.test.ts +12 -27
- package/frontendBuild/devGeneratedIndexSync.ts +1 -4
- package/frontendBuild/fontOptimizer.test.ts +5 -13
- package/frontendBuild/fontOptimizer.ts +15 -72
- package/frontendBuild/fontPruner.test.ts +5 -10
- package/frontendBuild/fontPruner.ts +5 -20
- package/frontendBuild/frontendBuild.test.ts +27 -52
- package/frontendBuild/hmrChangeClassifier.ts +2 -2
- package/frontendBuild/hmrWatcher.test.ts +7 -31
- package/frontendBuild/hmrWatcher.ts +14 -71
- package/frontendBuild/pagesBundleBuilder.ts +4 -29
- package/frontendBuild/precompressArtifacts.ts +5 -18
- package/frontendBuild/routeClientBuilder.ts +7 -47
- package/frontendBuild/routesManifestArtifactSerializer.ts +5 -36
- package/frontendBuild/sourceMtimeIndex.test.ts +37 -94
- package/frontendBuild/sourceMtimeIndex.ts +19 -130
- package/frontendBuild/ssrBaseArtifactBuilder.ts +2 -8
- package/frontendBuild/styleContract.ts +1 -8
- package/frontendBuild/themeValidator.ts +4 -26
- package/frontendBuild/watchRootResolver.test.ts +23 -42
- package/frontendBuild/watchRootResolver.ts +5 -27
- package/getRelatedCnsts.ts +12 -92
- package/incrementalBuilder/buildBatch.proc.ts +22 -57
- package/incrementalBuilder/buildBatchProtocol.ts +8 -36
- package/incrementalBuilder/buildBatchRunner.test.ts +34 -0
- package/incrementalBuilder/buildBatchRunner.ts +10 -31
- package/incrementalBuilder/builderChannel.test.ts +24 -49
- package/incrementalBuilder/builderChannel.ts +6 -37
- package/incrementalBuilder/builderRequestRouter.test.ts +0 -8
- package/incrementalBuilder/builderRequestRouter.ts +5 -24
- package/incrementalBuilder/devWatchBatch.test.ts +14 -18
- package/incrementalBuilder/incrementalBuilder.host.test.ts +18 -55
- package/incrementalBuilder/incrementalBuilder.host.ts +18 -76
- package/incrementalBuilder/incrementalBuilder.proc.test.ts +81 -0
- package/incrementalBuilder/incrementalBuilder.proc.ts +48 -161
- package/index.ts +3 -15
- package/integration/devResourceProbe.ts +50 -56
- package/integration/devStability.integration.test.ts +47 -194
- package/integration/devStabilityHarness.test.ts +9 -29
- package/integration/devStabilityHarness.ts +65 -330
- package/integration/ssrMemoryProbe.ts +41 -167
- package/libSource.test.ts +4 -15
- package/libSource.ts +19 -31
- package/lint/gritRules.test.ts +6 -23
- package/linter.test.ts +1 -2
- package/linter.ts +22 -115
- package/mobile/mobileTarget.ts +14 -13
- package/package.json +2 -2
- package/packageExports.test.ts +3 -18
- package/packageExportsMap.ts +3 -19
- package/piDependencyPins.test.ts +3 -14
- package/platformTest/IdleWatchedProcess.test.ts +5 -13
- package/platformTest/IdleWatchedProcess.ts +1 -5
- package/platformTest/LinuxDockerTarget.ts +20 -35
- package/platformTest/PlatformTestRun.test.ts +5 -9
- package/platformTest/PlatformTestTarget.ts +24 -2
- package/platformTest/TestSnapshot.test.ts +6 -14
- package/platformTest/TestSnapshot.ts +1 -5
- package/platformTest/WindowsSshTarget.ts +6 -22
- package/prompter.ts +3 -6
- package/qualityScanner.test.ts +6 -24
- package/qualityScanner.ts +196 -284
- package/recipeScanner.test.ts +7 -12
- package/recipeScanner.ts +9 -38
- package/repoIdentity.ts +2 -9
- package/routeSourceValidator.ts +7 -30
- package/scanInfo.ts +82 -214
- package/selectModel.ts +1 -2
- package/semver.ts +3 -5
- package/slicePlanner.test.ts +6 -24
- package/slicePlanner.ts +11 -32
- package/spinner.test.ts +3 -8
- package/spinner.ts +4 -16
- package/sshTunnel.ts +1 -6
- package/ssrScanner.test.ts +1 -6
- package/ssrScanner.ts +103 -113
- package/subspace.test.ts +6 -25
- package/subspace.ts +31 -109
- package/subspaceConfig.ts +3 -11
- package/testHelpers.ts +49 -34
- package/transforms/asyncDefaultExportDetector.ts +3 -11
- package/transforms/barrelAnalyzer.ts +43 -135
- package/transforms/barrelImportsPlugin.ts +38 -175
- package/transforms/externalizeFrameworkPlugin.test.ts +39 -118
- package/transforms/externalizeFrameworkPlugin.ts +13 -87
- package/transforms/moduleSyntax.ts +32 -0
- package/transforms/rscUseClientTransform.ts +35 -20
- package/transforms/transforms.test.ts +153 -44
- package/transforms/tsconfigPackageResolver.test.ts +6 -24
- package/transforms/useClientBundlePlugin.ts +2 -22
- package/typeChecker.ts +7 -63
- package/uploadRelease.ts +10 -25
- package/workflow/artifacts.ts +18 -0
- package/workflow/executor.test.ts +1 -12
- package/workflow/executor.ts +85 -146
- package/workflow/index.ts +0 -1
- package/workflow/moduleIndex.test.ts +4 -15
- package/workflow/moduleIndex.ts +9 -49
- package/workflow/plan.ts +18 -25
- package/workflow/render.ts +80 -96
- package/workflow/source.ts +33 -56
- package/workflow/sourceAst.ts +50 -0
- package/workspaceLayout.ts +4 -19
- package/commandDecorators/commandMeta.ts +0 -7
- package/commandDecorators/types.ts +0 -10
- package/getDirname.ts +0 -4
- package/local/optimize-resource/ipcprobe/child.ts +0 -1
- package/local/optimize-resource/ipcprobe/parent.ts +0 -11
- package/workflow/primitive.ts +0 -59
|
@@ -12,24 +12,12 @@ export interface WatcherOptions {
|
|
|
12
12
|
debounceMs?: number;
|
|
13
13
|
logger: Logger;
|
|
14
14
|
onBatch: (batch: ChangeBatch) => void | Promise<void>;
|
|
15
|
-
/**
|
|
16
|
-
* Delay before re-checking mtimes for changes `fs.watch` never reported. Must clear Bun's ~200ms
|
|
17
|
-
* coalescing window, or a write late in the same window as a delivered event stays invisible.
|
|
18
|
-
*/
|
|
15
|
+
/** Delay (ms, default 250) before the mtime re-check; must outlast Bun's ~200ms fs.watch coalescing window. */
|
|
19
16
|
verifyDelayMs?: number;
|
|
20
17
|
}
|
|
21
18
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
* classification coarse (`code` / `css` / `config`) so the orchestrator can
|
|
25
|
-
* decide whether a full rebuild-and-reload or a narrower action (e.g. CSS
|
|
26
|
-
* hot-swap) is sufficient.
|
|
27
|
-
*
|
|
28
|
-
* Bun's event payloads are treated as a hint, not the answer: its recursive `fs.watch` reports about one
|
|
29
|
-
* path per coalescing window and silently discards the rest, so what changed is resolved against a
|
|
30
|
-
* `SourceMtimeIndex` instead (see that class for the measurements). Events still drive *when* to look,
|
|
31
|
-
* which is what keeps this cheap — Bun does reliably deliver at least one event per window.
|
|
32
|
-
*/
|
|
19
|
+
// Bun delivers at least one fs.watch event per coalescing window but drops the other paths, so events decide
|
|
20
|
+
// when to look and the SourceMtimeIndex decides what changed.
|
|
33
21
|
export class HmrWatcher {
|
|
34
22
|
readonly #roots: string[];
|
|
35
23
|
readonly #debounceMs: number;
|
|
@@ -38,7 +26,6 @@ export class HmrWatcher {
|
|
|
38
26
|
readonly #logger: Logger;
|
|
39
27
|
readonly #watchers: fs.FSWatcher[] = [];
|
|
40
28
|
readonly #pending = new Map<string, Exclude<ChangeKind, "ignore">>();
|
|
41
|
-
/** Paths the watcher's own events named since the last batch — diagnostics only, see `#queue`. */
|
|
42
29
|
readonly #hinted = new Set<string>();
|
|
43
30
|
readonly #classifier = new HmrChangeClassifier();
|
|
44
31
|
readonly #index: SourceMtimeIndex;
|
|
@@ -59,19 +46,12 @@ export class HmrWatcher {
|
|
|
59
46
|
this.#index = new SourceMtimeIndex({ roots: this.#roots, classifier: this.#classifier });
|
|
60
47
|
}
|
|
61
48
|
|
|
62
|
-
/**
|
|
63
|
-
* How many changes the mtime scan found that `fs.watch` never reported. Non-zero means this watcher is
|
|
64
|
-
* compensating for the Bun defect rather than the defect being absent, which is the number to watch if
|
|
65
|
-
* it is ever fixed upstream.
|
|
66
|
-
*/
|
|
49
|
+
/** Changes the mtime scan found that fs.watch never reported; non-zero means it is compensating for Bun. */
|
|
67
50
|
get unreportedChanges(): number {
|
|
68
51
|
return this.#unreportedChanges;
|
|
69
52
|
}
|
|
70
53
|
|
|
71
|
-
/**
|
|
72
|
-
* Watchers are installed before the baseline is taken, so an edit made during priming is reported by
|
|
73
|
-
* the event side even though the baseline already reflects it.
|
|
74
|
-
*/
|
|
54
|
+
/** Installs the watchers before priming, so an edit made while priming is still reported by the event side. */
|
|
75
55
|
async start(): Promise<void> {
|
|
76
56
|
for (const root of this.#roots) {
|
|
77
57
|
try {
|
|
@@ -88,8 +68,7 @@ export class HmrWatcher {
|
|
|
88
68
|
}
|
|
89
69
|
try {
|
|
90
70
|
await this.#index.prime();
|
|
91
|
-
//
|
|
92
|
-
// waiting for a save to surface it means waiting for a save that never rebuilds.
|
|
71
|
+
// At boot: edits under an unreadable root never rebuild, so no later save would surface it.
|
|
93
72
|
this.#reportCoverageGaps();
|
|
94
73
|
this.#logger.verbose(`[hmr] tracking ${this.#index.trackedFileCount} source files for change verification`);
|
|
95
74
|
} catch (err) {
|
|
@@ -112,23 +91,14 @@ export class HmrWatcher {
|
|
|
112
91
|
}
|
|
113
92
|
}
|
|
114
93
|
|
|
115
|
-
/**
|
|
116
|
-
* Adopt writes the batch handler made itself (regenerated barrels, inserted imports) so the
|
|
117
|
-
* verification scan does not spend a second generation rebuilding content this one already consumed.
|
|
118
|
-
*/
|
|
94
|
+
/** Baseline the batch handler's own writes so the verification scan does not rebuild them a second time. */
|
|
119
95
|
async absorb(paths: string[]): Promise<void> {
|
|
120
96
|
if (paths.length === 0) return;
|
|
121
97
|
await this.#index.absorb(paths);
|
|
122
98
|
}
|
|
123
99
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
* used purely to decide when to look, and the mtime scan names the files.
|
|
127
|
-
*
|
|
128
|
-
* Taking the payload as well would double-report: Bun does deliver a real event for some of the paths a
|
|
129
|
-
* scan has already emitted, and adding it back here produced a second batch for the same save — one more
|
|
130
|
-
* generation and one more build for no change.
|
|
131
|
-
*/
|
|
100
|
+
// Once primed, a payload only schedules a scan: Bun also delivers events for paths the scan already emitted,
|
|
101
|
+
// so adding payloads to the batch double-reports a save.
|
|
132
102
|
#queue(abs: string): void {
|
|
133
103
|
const kind = this.#classifier.classify(abs);
|
|
134
104
|
if (!this.#index.primed) {
|
|
@@ -138,15 +108,12 @@ export class HmrWatcher {
|
|
|
138
108
|
this.#scheduleFlush();
|
|
139
109
|
return;
|
|
140
110
|
}
|
|
141
|
-
// An ignored path
|
|
142
|
-
// ends in a burst under `.akan/`, and the burst is what Bun reports instead of the save beside it.
|
|
143
|
-
// Rescheduling on each one folds a build's whole burst into a single scan once it goes quiet.
|
|
111
|
+
// An ignored path (a build's .akan/ burst) still marks a coalescing window that may hide a real save.
|
|
144
112
|
if (kind === "ignore") {
|
|
145
113
|
this.#scheduleVerify();
|
|
146
114
|
return;
|
|
147
115
|
}
|
|
148
|
-
//
|
|
149
|
-
// what is in a batch.
|
|
116
|
+
// Diagnostics only, for `unreportedChanges`; it never decides what is in a batch.
|
|
150
117
|
this.#hinted.add(abs);
|
|
151
118
|
this.#scheduleFlush();
|
|
152
119
|
}
|
|
@@ -186,10 +153,6 @@ export class HmrWatcher {
|
|
|
186
153
|
}
|
|
187
154
|
}
|
|
188
155
|
|
|
189
|
-
/**
|
|
190
|
-
* Fold in everything the mtime index has seen change, since a delivered event names at most one of the
|
|
191
|
-
* paths that moved in its window.
|
|
192
|
-
*/
|
|
193
156
|
async #mergeDetectedChanges(): Promise<void> {
|
|
194
157
|
const detected = await this.#index.collectChanges().catch((err) => {
|
|
195
158
|
this.#logger.error(`[hmr] mtime scan failed: ${(err as Error).message}`);
|
|
@@ -205,8 +168,6 @@ export class HmrWatcher {
|
|
|
205
168
|
}
|
|
206
169
|
if (unreported === 0) return;
|
|
207
170
|
this.#unreportedChanges += unreported;
|
|
208
|
-
// Once at info, so it is visible that the watcher is compensating rather than the defect being absent;
|
|
209
|
-
// per-batch detail stays at verbose because a save-all trips this on every save.
|
|
210
171
|
if (!this.#reportedCompensating) {
|
|
211
172
|
this.#reportedCompensating = true;
|
|
212
173
|
this.#logger.verbose(
|
|
@@ -218,13 +179,6 @@ export class HmrWatcher {
|
|
|
218
179
|
);
|
|
219
180
|
}
|
|
220
181
|
|
|
221
|
-
/**
|
|
222
|
-
* Say so when part of the tree cannot be read, and say so again when it recovers.
|
|
223
|
-
*
|
|
224
|
-
* A blind spot here means edits under that path are not rebuilt at all, which is indistinguishable from
|
|
225
|
-
* the dev server being broken. Keyed on the gap list itself so a persistent failure logs once rather than
|
|
226
|
-
* once per save, while a *different* gap appearing still gets its own line.
|
|
227
|
-
*/
|
|
228
182
|
#reportCoverageGaps(): void {
|
|
229
183
|
const gaps = this.#index.coverageGaps;
|
|
230
184
|
const key = gaps
|
|
@@ -247,10 +201,7 @@ export class HmrWatcher {
|
|
|
247
201
|
);
|
|
248
202
|
}
|
|
249
203
|
|
|
250
|
-
|
|
251
|
-
* One scan after the coalescing window closes. A write that lands in the same window as an already
|
|
252
|
-
* delivered event produces no further event of its own, so nothing else would ever look for it.
|
|
253
|
-
*/
|
|
204
|
+
// A write in the same window as a delivered event raises no event of its own, so one scan follows each window.
|
|
254
205
|
#scheduleVerify(): void {
|
|
255
206
|
if (this.#stopped || this.#verifyDelayMs <= 0) return;
|
|
256
207
|
if (this.#verifyTimer) clearTimeout(this.#verifyTimer);
|
|
@@ -260,16 +211,8 @@ export class HmrWatcher {
|
|
|
260
211
|
}, this.#verifyDelayMs);
|
|
261
212
|
}
|
|
262
213
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
* artifacts under `.akan/` — which the classifier ignores — while codegen writes are content-guarded,
|
|
266
|
-
* so a rebuild does not move a tracked mtime.
|
|
267
|
-
*
|
|
268
|
-
* The one case that does schedule another is a directory the index could not date reliably (Linux stamps
|
|
269
|
-
* directory mtimes on a 1ms clock, see `SourceMtimeIndex`). That still terminates: the next scan is
|
|
270
|
-
* `verifyDelayMs` later, by which point the timestamp has settled unless something is writing to that
|
|
271
|
-
* directory right now — in which case looking again is the right answer anyway.
|
|
272
|
-
*/
|
|
214
|
+
// Terminates: build output lands in ignored .akan/ and codegen writes are content-guarded, so a rebuild moves no
|
|
215
|
+
// tracked mtime; only an unsettled directory schedules another scan.
|
|
273
216
|
async #verify(): Promise<void> {
|
|
274
217
|
if (this.#stopped || this.#flushing) return;
|
|
275
218
|
await this.#mergeDetectedChanges();
|
|
@@ -5,18 +5,15 @@ import { resolveSsrPageEntriesForApp } from "../artifact/implicitRootLayout";
|
|
|
5
5
|
import type { App } from "../commandDecorators";
|
|
6
6
|
import { createBarrelImportsPlugin } from "../transforms/barrelImportsPlugin";
|
|
7
7
|
import { createExternalizeFrameworkPlugin } from "../transforms/externalizeFrameworkPlugin";
|
|
8
|
+
import { loaderFor } from "../transforms/moduleSyntax";
|
|
8
9
|
import { transformUseClient } from "../transforms/rscUseClientTransform";
|
|
9
10
|
import { createUseClientBundlePlugin } from "../transforms/useClientBundlePlugin";
|
|
11
|
+
import { bundleDefine } from "./bundleDefine";
|
|
10
12
|
import { PagesEntrySourceGenerator } from "./pagesEntrySourceGenerator";
|
|
11
13
|
|
|
12
14
|
export interface BuildPagesBundleResult {
|
|
13
|
-
/** Absolute path to the emitted `pages-[hash].js`. */
|
|
14
15
|
bundlePath: string;
|
|
15
|
-
/**
|
|
16
|
-
* Monotonic build identifier. Bun.build emits a fresh filename whenever
|
|
17
|
-
* any input changes, but importers still benefit from a `?v=<buildId>`
|
|
18
|
-
* query-string cache bust — `buildId` is that value.
|
|
19
|
-
*/
|
|
16
|
+
/** Cache-bust value for `import(bundlePath?v=<buildId>)`. */
|
|
20
17
|
buildId: number;
|
|
21
18
|
splitting: boolean;
|
|
22
19
|
entryBytes: number;
|
|
@@ -27,10 +24,6 @@ export interface BuildPagesBundleResult {
|
|
|
27
24
|
|
|
28
25
|
const VIRTUAL_PAGES_ENTRY = "akan-pages-entry";
|
|
29
26
|
|
|
30
|
-
/**
|
|
31
|
-
* Build the server-side pages bundle. The RSC worker loads the result with
|
|
32
|
-
* `await import(bundlePath?v=buildId)`.
|
|
33
|
-
*/
|
|
34
27
|
export class PagesBundleBuilder {
|
|
35
28
|
#app: App;
|
|
36
29
|
#command: "build" | "start";
|
|
@@ -61,7 +54,7 @@ export class PagesBundleBuilder {
|
|
|
61
54
|
chunk: "chunks/[name]-[hash].[ext]",
|
|
62
55
|
asset: "assets/[name]-[hash].[ext]",
|
|
63
56
|
},
|
|
64
|
-
define: this.#
|
|
57
|
+
define: bundleDefine(this.#app, this.#command, "ssr"),
|
|
65
58
|
plugins: [
|
|
66
59
|
PagesBundleBuilder.createPagesEntryPlugin(entrySource),
|
|
67
60
|
PagesBundleBuilder.createCssStubPlugin(),
|
|
@@ -110,17 +103,6 @@ export class PagesBundleBuilder {
|
|
|
110
103
|
return process.env.AKAN_SERVER_PAGES_SPLITTING === "1";
|
|
111
104
|
}
|
|
112
105
|
|
|
113
|
-
#define(): Record<string, string> {
|
|
114
|
-
const nodeEnv = this.#command === "build" ? "production" : (process.env.NODE_ENV ?? "development");
|
|
115
|
-
return {
|
|
116
|
-
"process.env.NODE_ENV": JSON.stringify(nodeEnv),
|
|
117
|
-
"process.env.AKAN_PUBLIC_RENDER_ENV": JSON.stringify("ssr"),
|
|
118
|
-
...Object.fromEntries(
|
|
119
|
-
Object.entries(this.#app.getPublicEnv()).map(([key, value]) => [`process.env.${key}`, JSON.stringify(value)]),
|
|
120
|
-
),
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
|
|
124
106
|
static createPagesEntryPlugin(source: string): BunPlugin {
|
|
125
107
|
return {
|
|
126
108
|
name: "akan-pages-entry",
|
|
@@ -176,10 +158,3 @@ export class PagesBundleBuilder {
|
|
|
176
158
|
);
|
|
177
159
|
}
|
|
178
160
|
}
|
|
179
|
-
|
|
180
|
-
function loaderFor(absPath: string): "ts" | "tsx" | "js" | "jsx" {
|
|
181
|
-
if (absPath.endsWith(".tsx")) return "tsx";
|
|
182
|
-
if (absPath.endsWith(".jsx")) return "jsx";
|
|
183
|
-
if (absPath.endsWith(".ts")) return "ts";
|
|
184
|
-
return "js";
|
|
185
|
-
}
|
|
@@ -2,17 +2,15 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import zlib from "node:zlib";
|
|
4
4
|
import type { App } from "../commandDecorators";
|
|
5
|
+
import { FontPruner } from "./fontPruner";
|
|
5
6
|
|
|
6
7
|
const COMPRESSIBLE_EXTS = new Set([".css", ".html", ".js", ".json", ".svg"]);
|
|
7
8
|
const MIN_COMPRESS_BYTES = 1024;
|
|
8
|
-
|
|
9
|
-
* Quality 11 is roughly 100x slower than gzip, so it is spent only where it pays: there is one CSS asset
|
|
10
|
-
* per basePath and it is the largest single file the app ships, worth ~20% over gzip for ~0.2s. Raising the
|
|
11
|
-
* several hundred JS chunks from 9 to 11 costs ~12s of build time to save a few hundred KB, so they stay at 9.
|
|
12
|
-
*/
|
|
9
|
+
// Brotli 11 is ~100x slower than gzip: worth it for the one CSS asset per basePath, not for hundreds of JS chunks.
|
|
13
10
|
const BROTLI_QUALITY_BY_EXT = { ".css": 11 } as const;
|
|
14
11
|
const DEFAULT_BROTLI_QUALITY = 9;
|
|
15
12
|
const GZIP_LEVEL = 9;
|
|
13
|
+
const { formatBytes } = FontPruner;
|
|
16
14
|
|
|
17
15
|
export interface PrecompressArtifactsResult {
|
|
18
16
|
files: number;
|
|
@@ -22,8 +20,7 @@ export interface PrecompressArtifactsResult {
|
|
|
22
20
|
}
|
|
23
21
|
|
|
24
22
|
export async function precompressArtifacts(app: App): Promise<PrecompressArtifactsResult> {
|
|
25
|
-
//*
|
|
26
|
-
//* one CSS asset per basePath is the largest uncompressed payload the app ships.
|
|
23
|
+
//* WebRouter serves client/ and styles/ through the same sidecar-aware #fileResponse.
|
|
27
24
|
const roots = [
|
|
28
25
|
path.join(app.dist.cwdPath, ".akan/artifact/client"),
|
|
29
26
|
path.join(app.dist.cwdPath, ".akan/artifact/styles"),
|
|
@@ -47,7 +44,7 @@ async function precompressRoot(root: string, result: PrecompressArtifactsResult)
|
|
|
47
44
|
for await (const filePath of glob.scan({ cwd: root, absolute: true })) {
|
|
48
45
|
if (!(await shouldPrecompress(filePath))) continue;
|
|
49
46
|
const bytes = await Bun.file(filePath).bytes();
|
|
50
|
-
const buffer =
|
|
47
|
+
const buffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as ArrayBuffer;
|
|
51
48
|
const gz = Bun.gzipSync(buffer, { level: GZIP_LEVEL });
|
|
52
49
|
const br = brotliCompress(buffer, path.extname(filePath).toLowerCase());
|
|
53
50
|
await Promise.all([Bun.write(`${filePath}.gz`, gz), Bun.write(`${filePath}.br`, br)]);
|
|
@@ -75,13 +72,3 @@ async function shouldPrecompress(filePath: string): Promise<boolean> {
|
|
|
75
72
|
if (!(await file.exists())) return false;
|
|
76
73
|
return file.size >= MIN_COMPRESS_BYTES;
|
|
77
74
|
}
|
|
78
|
-
|
|
79
|
-
function toArrayBuffer(bytes: Uint8Array): ArrayBuffer {
|
|
80
|
-
return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as ArrayBuffer;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function formatBytes(bytes: number): string {
|
|
84
|
-
if (bytes < 1024) return `${bytes}B`;
|
|
85
|
-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
|
|
86
|
-
return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
|
|
87
|
-
}
|
|
@@ -3,20 +3,12 @@ import path from "node:path";
|
|
|
3
3
|
import type { BaseBuildArtifact, ClientManifest, SsrManifest } from "akanjs/server";
|
|
4
4
|
import type { App } from "../commandDecorators";
|
|
5
5
|
import { createBarrelImportsPlugin } from "../transforms/barrelImportsPlugin";
|
|
6
|
-
import { toClientReferencePath } from "../transforms/rscUseClientTransform";
|
|
6
|
+
import { scanUseClientExports, toClientReferencePath } from "../transforms/rscUseClientTransform";
|
|
7
7
|
import type { ClientBundleTarget, ClientEntryDiscovery } from "./clientBuildTypes";
|
|
8
8
|
import { ClientEntriesBundler } from "./clientEntriesBundler";
|
|
9
9
|
import { GraphClientEntryDiscovery } from "./clientEntryDiscovery";
|
|
10
10
|
import { VENDOR_SPECIFIERS } from "./vendorSpecifiers";
|
|
11
11
|
|
|
12
|
-
const SSR_CLIENT_EXTERNALS = [
|
|
13
|
-
"react",
|
|
14
|
-
"react-dom",
|
|
15
|
-
"react-dom/client",
|
|
16
|
-
"react/jsx-runtime",
|
|
17
|
-
"react/jsx-dev-runtime",
|
|
18
|
-
"akanjs/fetch",
|
|
19
|
-
] as const;
|
|
20
12
|
const SSR_CLIENT_ALIAS_EXTERNALS = [
|
|
21
13
|
"react",
|
|
22
14
|
"react-dom",
|
|
@@ -24,47 +16,28 @@ const SSR_CLIENT_ALIAS_EXTERNALS = [
|
|
|
24
16
|
"react/jsx-runtime",
|
|
25
17
|
"react/jsx-dev-runtime",
|
|
26
18
|
] as const;
|
|
19
|
+
const SSR_CLIENT_EXTERNALS = [...SSR_CLIENT_ALIAS_EXTERNALS, "akanjs/fetch"] as const;
|
|
27
20
|
|
|
28
21
|
export interface BuildRouteClientOptions {
|
|
29
22
|
app: App;
|
|
30
23
|
seeds: string[];
|
|
31
|
-
/**
|
|
32
|
-
* Seeds of every route, so a build that has to run at all bundles the whole app's client entries.
|
|
33
|
-
*
|
|
34
|
-
* A page loads entries first built for other routes, and entries from two `Bun.build` runs each carry
|
|
35
|
-
* their own copy of every app and lib module — one page evaluated a lib's module-scope registration
|
|
36
|
-
* twice. The whole app costs about what one route did, since every entry already imports the client
|
|
37
|
-
* barrel, and content-hashed chunk names keep unchanged chunks at the URLs the browser already holds.
|
|
38
|
-
*/
|
|
24
|
+
/** Every route's seeds: a build that runs at all bundles the whole app, since two builds each copy shared modules. */
|
|
39
25
|
graphSeeds?: string[];
|
|
40
26
|
artifact: BaseBuildArtifact;
|
|
41
27
|
knownEntries?: Set<string>;
|
|
42
28
|
routeId?: string;
|
|
43
29
|
command?: "build" | "start";
|
|
44
30
|
discovery?: ClientEntryDiscovery;
|
|
45
|
-
/**
|
|
46
|
-
* Client entries resolved by the caller, which skips discovery and bundles exactly this list.
|
|
47
|
-
*
|
|
48
|
-
* `AllRoutesBuilder` passes every route's entries at once so they share one `Bun.build`. Chunk
|
|
49
|
-
* splitting is scoped to a single invocation, so a dependency reachable from entries spread across
|
|
50
|
-
* several invocations is emitted once per invocation — mermaid landed in `apps/akan` four times that
|
|
51
|
-
* way. Dev passes `graphSeeds` instead, for the same reason.
|
|
52
|
-
*/
|
|
31
|
+
/** Pre-resolved client entries: skips discovery and bundles exactly this list. */
|
|
53
32
|
entries?: string[];
|
|
54
33
|
}
|
|
55
34
|
|
|
56
35
|
export interface BuildRouteClientResult {
|
|
57
|
-
/** Newly-emitted manifest rows keyed by `${absEntry}#${exportName}`, for every entry this build bundled. */
|
|
58
36
|
manifestDelta: ClientManifest;
|
|
59
|
-
/** Newly-emitted ssrManifest rows keyed by entry URL. */
|
|
60
37
|
ssrManifestDelta: SsrManifest;
|
|
61
|
-
/** Absolute paths of the `"use client"` leaves this build added. */
|
|
62
38
|
newEntries: string[];
|
|
63
|
-
/** Absolute paths of all `"use client"` leaves discovered from this route's seeds. */
|
|
64
39
|
discoveredEntries?: string[];
|
|
65
|
-
/** Absolute source files included in the browser bundles for this route's bundled entries. */
|
|
66
40
|
clientDeps: string[];
|
|
67
|
-
/** Absolute source files included in the browser bundle, grouped by original client entry. */
|
|
68
41
|
clientDepsByEntry?: Record<string, string[]>;
|
|
69
42
|
}
|
|
70
43
|
|
|
@@ -223,16 +196,7 @@ export class RouteClientBuilder {
|
|
|
223
196
|
}
|
|
224
197
|
|
|
225
198
|
async #scanExportNames(absEntry: string): Promise<string[]> {
|
|
226
|
-
|
|
227
|
-
const transpiler = new Bun.Transpiler({ loader: this.#loaderFor(absEntry) });
|
|
228
|
-
return transpiler.scan(source).exports;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
#loaderFor(absPath: string): "ts" | "tsx" | "js" | "jsx" {
|
|
232
|
-
if (absPath.endsWith(".tsx")) return "tsx";
|
|
233
|
-
if (absPath.endsWith(".jsx")) return "jsx";
|
|
234
|
-
if (absPath.endsWith(".ts")) return "ts";
|
|
235
|
-
return "js";
|
|
199
|
+
return scanUseClientExports(await Bun.file(absEntry).text(), absEntry, this.#app.workspace.workspaceRoot);
|
|
236
200
|
}
|
|
237
201
|
|
|
238
202
|
static normalizeNamedDefaultFunctionForFastRefresh(source: string): string | null {
|
|
@@ -255,12 +219,8 @@ export class RouteClientBuilder {
|
|
|
255
219
|
return { [Bun.resolveSync("akanjs/fetch", serverEntry)]: "akanjs/fetch" };
|
|
256
220
|
}
|
|
257
221
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
* resolved through its `browser` export condition can touch `document` at module scope and throw mid-render,
|
|
261
|
-
* degrading the whole document to client rendering. Bun's `conditions` only adds to the target's defaults —
|
|
262
|
-
* `browser` still wins — so the target itself has to say server.
|
|
263
|
-
*/
|
|
222
|
+
// `target: "bun"` is load-bearing: a `browser` export condition can touch `document` at module scope mid-SSR, and
|
|
223
|
+
// Bun's `conditions` only add to the target's defaults, so the target itself must say server.
|
|
264
224
|
static resolveSsrClientBundleOptions(command: "build" | "start"): {
|
|
265
225
|
target: ClientBundleTarget;
|
|
266
226
|
external: readonly string[];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import type { RoutesManifest } from "akanjs/server";
|
|
2
|
+
import { RoutesManifestStore } from "akanjs/server/artifact/routesManifestStore";
|
|
3
3
|
|
|
4
4
|
export type SerializedRoutesManifest = Omit<RoutesManifest, "knownEntries"> &
|
|
5
5
|
Partial<Pick<RoutesManifest, "knownEntries">>;
|
|
@@ -11,7 +11,7 @@ export class RoutesManifestArtifactSerializer {
|
|
|
11
11
|
|
|
12
12
|
constructor(manifest: RoutesManifest, artifactDir: string, options: { production?: boolean } = {}) {
|
|
13
13
|
this.#manifest = manifest;
|
|
14
|
-
this.#artifactDir =
|
|
14
|
+
this.#artifactDir = artifactDir;
|
|
15
15
|
this.#production = options.production ?? false;
|
|
16
16
|
}
|
|
17
17
|
|
|
@@ -24,39 +24,8 @@ export class RoutesManifestArtifactSerializer {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
serialize(): SerializedRoutesManifest {
|
|
27
|
-
const serialized: SerializedRoutesManifest =
|
|
28
|
-
...this.#manifest,
|
|
29
|
-
knownEntries: this.#manifest.knownEntries.map((entry) => this.#serializeArtifactPath(entry)),
|
|
30
|
-
};
|
|
27
|
+
const serialized: SerializedRoutesManifest = RoutesManifestStore.serialize(this.#manifest, this.#artifactDir);
|
|
31
28
|
if (this.#production) delete serialized.knownEntries;
|
|
32
|
-
return
|
|
33
|
-
...serialized,
|
|
34
|
-
clientManifest: Object.fromEntries(
|
|
35
|
-
Object.entries(this.#manifest.clientManifest).map(([key, row]) => [this.#serializeArtifactPath(key), row]),
|
|
36
|
-
),
|
|
37
|
-
ssrManifest: {
|
|
38
|
-
...this.#manifest.ssrManifest,
|
|
39
|
-
moduleMap: Object.fromEntries(
|
|
40
|
-
Object.entries(this.#manifest.ssrManifest.moduleMap).map(([entryUrl, byName]) => [
|
|
41
|
-
entryUrl,
|
|
42
|
-
Object.fromEntries(
|
|
43
|
-
Object.entries(byName).map(([name, entry]) => [
|
|
44
|
-
name,
|
|
45
|
-
{
|
|
46
|
-
...entry,
|
|
47
|
-
id: this.#serializeArtifactPath(entry.id),
|
|
48
|
-
chunks: entry.chunks.map((chunk) => this.#serializeArtifactPath(chunk)),
|
|
49
|
-
} satisfies SsrManifestEntry,
|
|
50
|
-
]),
|
|
51
|
-
),
|
|
52
|
-
]),
|
|
53
|
-
),
|
|
54
|
-
},
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
#serializeArtifactPath(artifactPath: string): string {
|
|
59
|
-
if (!path.isAbsolute(artifactPath)) return artifactPath;
|
|
60
|
-
return path.relative(this.#artifactDir, artifactPath).split(path.sep).join("/");
|
|
29
|
+
return serialized;
|
|
61
30
|
}
|
|
62
31
|
}
|