@akanjs/devkit 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/DEV_RUNTIME_KNOBS.md +44 -26
- package/README.ko.md +21 -7
- package/README.md +17 -8
- package/abstractDoc.test.ts +6 -15
- package/abstractDoc.ts +6 -8
- 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 +56 -3
- package/akanApp/akanApp.host.ts +218 -278
- package/akanApp/devBootLatch.test.ts +39 -0
- package/akanApp/devBootLatch.ts +39 -0
- package/akanApp/devHostPolicy.test.ts +81 -35
- package/akanApp/devHostPolicy.ts +41 -102
- package/akanConfig/akanBin.test.ts +62 -0
- package/akanConfig/akanBin.ts +78 -0
- package/akanConfig/akanConfig.test.ts +439 -141
- package/akanConfig/akanConfig.ts +359 -275
- package/akanConfig/index.ts +1 -0
- package/akanConfig/types.ts +22 -8
- package/akanContext.ts +110 -160
- package/akanMcpContract.ts +28 -73
- package/appSelectionMemory.ts +1 -4
- package/applicationBuildReporter.ts +37 -5
- package/applicationBuildRunner.test.ts +41 -5
- package/applicationBuildRunner.ts +71 -63
- package/applicationTestPreload.ts +3 -12
- package/artifact/implicitRootLayout.test.ts +32 -30
- package/artifact/implicitRootLayout.ts +58 -106
- package/artifact/routeSeedIndex.test.ts +7 -6
- package/artifact/routeSeedIndex.ts +11 -44
- package/biome.base.json +35 -2
- package/biomeBase.ts +2 -5
- package/cloud/cloudApi.test.ts +202 -0
- package/cloud/cloudApi.ts +77 -39
- package/cloud/configLock.test.ts +64 -0
- package/cloud/configLock.ts +96 -0
- package/cloud/constants.ts +26 -5
- package/cloud/globalConfig.ts +71 -32
- package/codeAgent/agent/AkanCodeServices.ts +97 -0
- package/codeAgent/agent/AkanContextFiles.ts +94 -0
- package/codeAgent/agent/CodeAgent.ts +551 -0
- package/codeAgent/agent/CodeAgentAsks.ts +66 -0
- package/codeAgent/agent/CodeAgentEventMapper.ts +225 -0
- package/codeAgent/agent/CodeAgentGate.ts +93 -0
- package/codeAgent/agent/CodeAgentProxy.ts +82 -0
- package/codeAgent/agent/CodeAgentRpcHost.ts +103 -0
- package/codeAgent/agent/CodeAgentRpcListener.ts +109 -0
- package/codeAgent/agent/CodeAgentStreamPrinter.ts +86 -0
- package/codeAgent/agent/CodeAgentSuspended.ts +85 -0
- package/codeAgent/agent/CodeAgentUi.ts +47 -0
- package/codeAgent/agent/CodeMailbox.ts +148 -0
- package/codeAgent/agent/CodeSessionFork.ts +56 -0
- package/codeAgent/agent/CodeSessionIndex.ts +78 -0
- package/codeAgent/agent/SubagentPool.ts +214 -0
- package/codeAgent/agent/akanCodeModel.ts +47 -0
- package/codeAgent/agent/akanCodePaths.ts +31 -0
- package/codeAgent/agent/akanContextFiles.test.ts +114 -0
- package/codeAgent/agent/akanSystemPrompt.ts +46 -0
- package/codeAgent/agent/codeAgent.test.ts +178 -0
- package/codeAgent/agent/codeAgentContract.test.ts +523 -0
- package/codeAgent/agent/codeAgentProxy.test.ts +74 -0
- package/codeAgent/agent/codeAgentRpcListener.test.ts +113 -0
- package/codeAgent/agent/codeAgentSuspended.test.ts +47 -0
- package/codeAgent/agent/codeMailbox.test.ts +76 -0
- package/codeAgent/agent/codeSessionFork.test.ts +58 -0
- package/codeAgent/agent/codeSessionIndex.test.ts +77 -0
- package/codeAgent/agent/consoleToStderr.ts +8 -0
- package/codeAgent/agent/jsonLines.ts +29 -0
- package/codeAgent/agent/stringArg.ts +5 -0
- package/codeAgent/feedback/DevLogFeedback.ts +82 -0
- package/codeAgent/feedback/PreviewChrome.ts +82 -0
- package/codeAgent/feedback/PreviewFeedback.ts +46 -0
- package/codeAgent/feedback/PreviewView.ts +95 -0
- package/codeAgent/feedback/TurnFeedback.ts +98 -0
- package/codeAgent/feedback/VerifyFeedback.ts +40 -0
- package/codeAgent/feedback/previewChrome.test.ts +73 -0
- package/codeAgent/index.ts +34 -0
- package/codeAgent/skills/akan-code/SKILL.md +130 -0
- package/codeAgent/skills/akan-debug/SKILL.md +42 -0
- package/codeAgent/skills/akan-deps/SKILL.md +50 -0
- package/codeAgent/skills/akan-err/SKILL.md +39 -0
- package/codeAgent/skills/akan-module/SKILL.md +45 -0
- package/codeAgent/skills/akan-scaffold/SKILL.md +76 -0
- package/codeAgent/skills/akan-scalar/SKILL.md +52 -0
- package/codeAgent/skills/akan-service/SKILL.md +34 -0
- package/codeAgent/skills/akan-store/SKILL.md +67 -0
- package/codeAgent/skills/akan-ui/SKILL.md +63 -0
- package/codeAgent/skills/akan-validate/SKILL.md +44 -0
- package/codeAgent/tools/AkanCli.ts +50 -0
- package/codeAgent/tools/AkanCodePlugins.ts +136 -0
- package/codeAgent/tools/AkanEditScope.ts +48 -0
- package/codeAgent/tools/AkanEnvKeys.ts +50 -0
- package/codeAgent/tools/AkanToolPack.ts +92 -0
- package/codeAgent/tools/AkanVerifier.ts +48 -0
- package/codeAgent/tools/AskToolPack.ts +99 -0
- package/codeAgent/tools/MailToolPack.ts +76 -0
- package/codeAgent/tools/McpClient.ts +187 -0
- package/codeAgent/tools/McpOAuth.ts +233 -0
- package/codeAgent/tools/McpOAuthLoopback.ts +64 -0
- package/codeAgent/tools/McpServerConfig.ts +129 -0
- package/codeAgent/tools/McpSignIn.ts +158 -0
- package/codeAgent/tools/McpTokenStore.ts +81 -0
- package/codeAgent/tools/McpToolPack.ts +199 -0
- package/codeAgent/tools/SessionToolPack.ts +91 -0
- package/codeAgent/tools/WebToolPack.ts +147 -0
- package/codeAgent/tools/akanCodePlugins.test.ts +677 -0
- package/codeAgent/tools/askToolPack.test.ts +116 -0
- package/codeAgent/tools/mcpOAuth.test.ts +612 -0
- package/codeAgent/tools/mcpServerConfig.test.ts +212 -0
- 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 +46 -102
- package/commandDecorators/commandBuilder.ts +30 -61
- package/commandDecorators/commandDecorators.test.ts +2 -10
- package/commandDecorators/dependencyBuilder.ts +10 -1
- package/commandDecorators/helpFormatter.ts +68 -145
- package/commandDecorators/index.ts +0 -2
- package/commandDecorators/targetMeta.ts +9 -7
- package/createTunnel.ts +7 -14
- package/csrE2e/csrE2eHarness.fixture.ts +505 -0
- package/csrE2e/csrFastRefresh.e2e.test.ts +233 -0
- package/csrE2e/csrPageStack.e2e.test.ts +252 -0
- package/csrE2e/csrPatchFirst.e2e.test.ts +87 -0
- package/csrE2e/csrRootLayout.e2e.test.ts +134 -0
- package/csrE2e/devHmrLatency.e2e.test.ts +391 -0
- package/csrE2e/registryVendor.e2e.test.ts +221 -0
- package/csrE2e/ssrRegistry.e2e.test.ts +644 -0
- package/dependencyScanner.test.ts +4 -16
- package/dependencyScanner.ts +31 -80
- package/devkitUtils.test.ts +48 -20
- package/executors.linkFallback.test.ts +103 -0
- package/executors.test.ts +176 -106
- package/executors.ts +212 -498
- package/fileEditor.ts +9 -37
- package/{getDirname.test.ts → fileSys.test.ts} +5 -3
- package/fileSys.ts +22 -9
- package/formSetterScanner.ts +3 -16
- package/frontendBuild/allRoutesBuilder.ts +7 -21
- package/frontendBuild/autoImportSync.test.ts +27 -38
- package/frontendBuild/autoImportSync.ts +31 -65
- package/frontendBuild/buildRouteClient.test.ts +123 -37
- package/frontendBuild/bundleDefine.test.ts +12 -0
- package/frontendBuild/bundleDefine.ts +16 -0
- package/frontendBuild/clientBuildTypes.ts +5 -36
- package/frontendBuild/clientEntriesBundler.ts +9 -40
- package/frontendBuild/clientEntryDiscovery.test.ts +112 -0
- package/frontendBuild/clientEntryDiscovery.ts +122 -110
- package/frontendBuild/csrArtifactBuilder.ts +9 -31
- package/frontendBuild/csrDevArtifactWriter.test.ts +151 -0
- package/frontendBuild/csrDevArtifactWriter.ts +307 -0
- package/frontendBuild/csrDevBundler.test.ts +66 -0
- package/frontendBuild/csrDevBundler.ts +95 -0
- package/frontendBuild/csrDevFactoryCheck.test.ts +33 -0
- package/frontendBuild/csrDevFactoryCheck.ts +39 -0
- package/frontendBuild/csrDevModuleCompiler.test.ts +293 -0
- package/frontendBuild/csrDevModuleCompiler.ts +422 -0
- package/frontendBuild/csrDevPatcher.test.ts +572 -0
- package/frontendBuild/csrDevPatcher.ts +548 -0
- package/frontendBuild/csrDevPaths.ts +145 -0
- package/frontendBuild/csrDevResolver.test.ts +239 -0
- package/frontendBuild/csrDevResolver.ts +363 -0
- package/frontendBuild/csrDevTypes.ts +61 -0
- package/frontendBuild/csrEntryFiles.ts +24 -0
- package/frontendBuild/cssCandidateCache.test.ts +0 -6
- package/frontendBuild/cssCandidateCache.ts +6 -41
- package/frontendBuild/cssCompiler.ts +31 -82
- package/frontendBuild/cssImportResolver.ts +6 -19
- package/frontendBuild/devChangePlanner.ts +7 -10
- package/frontendBuild/devGeneratedIndexSync.test.ts +12 -27
- package/frontendBuild/devGeneratedIndexSync.ts +1 -4
- package/frontendBuild/devRegistryBundler.ts +192 -0
- package/frontendBuild/fontOptimizer.test.ts +6 -14
- package/frontendBuild/fontOptimizer.ts +15 -72
- package/frontendBuild/fontPruner.test.ts +5 -10
- package/frontendBuild/fontPruner.ts +5 -20
- package/frontendBuild/frontendBuild.test.ts +57 -70
- package/frontendBuild/hmrChangeClassifier.ts +2 -2
- package/frontendBuild/hmrWatcher.test.ts +59 -32
- package/frontendBuild/hmrWatcher.ts +31 -74
- package/frontendBuild/index.ts +4 -0
- package/frontendBuild/pagesBundleBuilder.ts +28 -30
- package/frontendBuild/pagesEntrySourceGenerator.ts +56 -6
- package/frontendBuild/precompressArtifacts.ts +5 -18
- package/frontendBuild/registryMetadataFingerprint.test.ts +63 -0
- package/frontendBuild/registryMetadataFingerprint.ts +28 -0
- package/frontendBuild/routeClientBuilder.ts +100 -62
- package/frontendBuild/routesManifestArtifactSerializer.ts +5 -36
- package/frontendBuild/serverGraphFile.test.ts +77 -0
- package/frontendBuild/serverGraphFile.ts +93 -0
- package/frontendBuild/sourceMtimeIndex.test.ts +41 -97
- package/frontendBuild/sourceMtimeIndex.ts +19 -130
- package/frontendBuild/ssrBaseArtifactBuilder.ts +19 -13
- package/frontendBuild/ssrDevBundler.ts +85 -0
- 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 +147 -63
- package/incrementalBuilder/buildBatchProtocol.ts +13 -38
- package/incrementalBuilder/buildBatchRunner.test.ts +65 -0
- package/incrementalBuilder/buildBatchRunner.ts +26 -38
- 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/builderWorkQueue.test.ts +94 -0
- package/incrementalBuilder/builderWorkQueue.ts +126 -0
- package/incrementalBuilder/devWatchBatch.test.ts +21 -24
- package/incrementalBuilder/incrementalBuilder.host.test.ts +53 -56
- package/incrementalBuilder/incrementalBuilder.host.ts +68 -81
- package/incrementalBuilder/incrementalBuilder.proc.test.ts +270 -0
- package/incrementalBuilder/incrementalBuilder.proc.ts +596 -208
- package/index.ts +5 -19
- package/integration/devResourceProbe.ts +50 -56
- package/integration/devStability.integration.test.ts +105 -199
- package/integration/devStabilityHarness.test.ts +13 -28
- 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/__fixtures__/no-document-cookie/bad.tsx +4 -0
- package/lint/__fixtures__/no-document-cookie/good.tsx +7 -0
- package/lint/__fixtures__/no-web-only-api-outside-webkit/bad.tsx +7 -0
- package/lint/__fixtures__/no-web-only-api-outside-webkit/good.tsx +11 -0
- package/lint/__fixtures__/no-web-storage/bad.tsx +6 -0
- package/lint/__fixtures__/no-web-storage/good.tsx +7 -0
- package/lint/gritRules.test.ts +8 -24
- package/lint/no-deep-internal-import.grit +2 -2
- package/lint/no-document-cookie.grit +29 -0
- package/lint/no-redeclare-predefined-endpoint.grit +2 -1
- package/lint/no-web-only-api-outside-webkit.grit +44 -0
- package/lint/no-web-storage.grit +30 -0
- package/linter.test.ts +1 -2
- package/linter.ts +26 -116
- package/mobile/desktopBin.test.ts +182 -0
- package/mobile/desktopBin.ts +144 -0
- package/mobile/desktopServerStage.test.ts +252 -0
- package/mobile/desktopServerStage.ts +178 -0
- package/mobile/index.ts +9 -0
- package/mobile/mobileTarget.test.ts +57 -8
- package/mobile/mobileTarget.ts +45 -26
- package/mobile/nativeApi.test.ts +30 -0
- package/mobile/nativeApi.ts +40 -0
- package/mobile/nativeApp.test.ts +184 -0
- package/mobile/nativeApp.ts +454 -0
- package/mobile/nativeAppLine.test.ts +69 -0
- package/mobile/nativeAppLine.ts +71 -0
- package/mobile/nativeConfig.test.ts +438 -0
- package/mobile/nativeConfig.ts +294 -0
- package/mobile/nativePluginFolders.test.ts +81 -0
- package/mobile/nativePluginFolders.ts +60 -0
- package/mobile/nativeWebDir.test.ts +80 -0
- package/mobile/nativeWebDir.ts +66 -0
- package/mobile/placeholderAppId.test.ts +13 -0
- package/mobile/placeholderAppId.ts +16 -0
- package/mobile/usageDescriptions.test.ts +27 -0
- package/mobile/usageDescriptions.ts +24 -0
- package/package.json +11 -11
- package/packageExports.test.ts +3 -18
- package/packageExportsMap.ts +3 -19
- package/piDependencyPins.test.ts +46 -0
- package/platformTest/BunTestReport.test.ts +60 -0
- package/platformTest/BunTestReport.ts +65 -0
- package/platformTest/IdleWatchedProcess.test.ts +56 -0
- package/platformTest/IdleWatchedProcess.ts +73 -0
- package/platformTest/LinuxDockerTarget.ts +105 -0
- package/platformTest/PlatformTestRun.test.ts +64 -0
- package/platformTest/PlatformTestRun.ts +141 -0
- package/platformTest/PlatformTestTarget.ts +142 -0
- package/platformTest/TestSnapshot.test.ts +72 -0
- package/platformTest/TestSnapshot.ts +103 -0
- package/platformTest/WindowsSshTarget.ts +151 -0
- package/prompter.ts +3 -13
- package/qualityScanner.test.ts +9 -26
- package/qualityScanner.ts +200 -286
- package/recipeScanner.test.ts +7 -12
- package/recipeScanner.ts +9 -38
- package/repoIdentity.ts +2 -9
- package/routeSourceValidator.test.ts +0 -1
- package/routeSourceValidator.ts +7 -42
- package/scanInfo.ts +91 -225
- 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/stripAnsi.ts +3 -0
- package/subspace.test.ts +9 -25
- package/subspace.ts +48 -115
- package/subspaceConfig.ts +3 -11
- package/testHelpers.ts +194 -0
- 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 +42 -20
- package/transforms/serverEnvPlugin.test.ts +103 -0
- package/transforms/serverEnvPlugin.ts +41 -0
- package/transforms/transforms.test.ts +166 -44
- package/transforms/tsconfigPackageResolver.test.ts +6 -24
- package/transforms/useClientBundlePlugin.ts +6 -23
- package/typeChecker.ts +7 -63
- package/types.ts +2 -0
- package/uploadRelease.ts +10 -25
- package/workflow/artifacts.ts +18 -0
- package/workflow/executor.test.ts +1 -12
- package/workflow/executor.ts +101 -110
- 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/rolloutGate.test.ts +1 -1
- package/workflow/source.ts +52 -69
- package/workflow/sourceAst.ts +50 -0
- package/workspaceLayout.test.ts +18 -3
- package/workspaceLayout.ts +22 -23
- package/abstractCompactor.ts +0 -112
- package/aiEditor.test.ts +0 -60
- package/aiEditor.ts +0 -387
- package/applicationReleasePackager.ts +0 -209
- package/capacitorApp.test.ts +0 -372
- package/capacitorApp.ts +0 -1648
- package/commandDecorators/commandMeta.ts +0 -7
- package/commandDecorators/types.ts +0 -10
- package/getDirname.ts +0 -4
- package/guideline.ts +0 -19
- package/local/optimize-resource/ipcprobe/child.ts +0 -1
- package/local/optimize-resource/ipcprobe/parent.ts +0 -11
- package/workflow/primitive.ts +0 -59
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { afterEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { CSR_DEV_PATCHING_MARKER, SSR_DEV_DIRNAME } from "akanjs/server/hmr/csrDevManifest";
|
|
2
6
|
import { MemoryLimit } from "akanjs/server/memoryLimit";
|
|
3
7
|
import { IncrementalBuilderHost } from "./incrementalBuilder.host";
|
|
4
8
|
|
|
@@ -26,18 +30,25 @@ const mockSpawns = (): SpawnRecord[] => {
|
|
|
26
30
|
return spawns;
|
|
27
31
|
};
|
|
28
32
|
|
|
33
|
+
const createHost = (onMessage: (message: unknown) => void = () => undefined) =>
|
|
34
|
+
new IncrementalBuilderHost({ app: { cwdPath: "/tmp/app" } as never, entry: "/tmp/builder.ts", env: {}, onMessage });
|
|
35
|
+
|
|
36
|
+
const startRecordingHost = () => {
|
|
37
|
+
const spawns = mockSpawns();
|
|
38
|
+
const messages: unknown[] = [];
|
|
39
|
+
const host = createHost((message) => messages.push(message));
|
|
40
|
+
host.start();
|
|
41
|
+
spawns[0]?.options.ipc?.({ type: "builder-ready" });
|
|
42
|
+
return { spawns, messages, host };
|
|
43
|
+
};
|
|
44
|
+
|
|
29
45
|
describe("IncrementalBuilderHost", () => {
|
|
30
46
|
test("restarts after a ready builder exits", async () => {
|
|
31
47
|
const spawns = mockSpawns();
|
|
32
48
|
|
|
33
49
|
const onReady = mock();
|
|
34
50
|
const onRestartReady = mock();
|
|
35
|
-
const host =
|
|
36
|
-
app: { cwdPath: "/tmp/app" } as never,
|
|
37
|
-
entry: "/tmp/builder.ts",
|
|
38
|
-
env: {},
|
|
39
|
-
onMessage: () => undefined,
|
|
40
|
-
});
|
|
51
|
+
const host = createHost();
|
|
41
52
|
|
|
42
53
|
host.start({ onReady, onRestartReady });
|
|
43
54
|
spawns[0]?.options.ipc?.({ type: "builder-ready" });
|
|
@@ -60,12 +71,7 @@ describe("IncrementalBuilderHost", () => {
|
|
|
60
71
|
test("recycles a ready builder gracefully and replaces it immediately", async () => {
|
|
61
72
|
const spawns = mockSpawns();
|
|
62
73
|
const onRestartReady = mock();
|
|
63
|
-
const host =
|
|
64
|
-
app: { cwdPath: "/tmp/app" } as never,
|
|
65
|
-
entry: "/tmp/builder.ts",
|
|
66
|
-
env: {},
|
|
67
|
-
onMessage: () => undefined,
|
|
68
|
-
});
|
|
74
|
+
const host = createHost();
|
|
69
75
|
|
|
70
76
|
host.start({ onRestartReady });
|
|
71
77
|
spawns[0]?.options.ipc?.({ type: "builder-ready" });
|
|
@@ -73,18 +79,14 @@ describe("IncrementalBuilderHost", () => {
|
|
|
73
79
|
|
|
74
80
|
const reason = "rss=1300MiB>=1200MiB after 3 build(s)";
|
|
75
81
|
expect(host.recycle(reason)).toBe(true);
|
|
76
|
-
// Graceful: the builder is asked to drain, not killed, so a rebuild in flight still completes.
|
|
77
82
|
expect(spawns[0]?.proc.send).toHaveBeenCalledWith({ type: "builder-shutdown", reason });
|
|
78
83
|
expect(spawns[0]?.proc.kill).not.toHaveBeenCalled();
|
|
79
84
|
expect(host.recycle("second request")).toBe(false);
|
|
80
85
|
|
|
81
|
-
// The drain refuses everything that arrives during it, so a request sent here is a request the
|
|
82
|
-
// developer gets an error page for. Reporting the state is what lets the host hold it instead.
|
|
83
86
|
expect(host.status).toBe("recycling");
|
|
84
87
|
expect(host.send({ type: "build-route", id: 9, routeId: "z", seeds: [], knownEntries: [] })).toBe(false);
|
|
85
88
|
expect(spawns[0]?.proc.send).toHaveBeenCalledTimes(1);
|
|
86
89
|
|
|
87
|
-
// A planned exit skips the crash backoff — the dev server has no file watcher until it is back.
|
|
88
90
|
spawns[0]?.options.onExit?.();
|
|
89
91
|
expect(spawns).toHaveLength(2);
|
|
90
92
|
expect(spawns[1]?.options.env?.AKAN_BUILDER_ANNOUNCE_BOOT).toBe("1");
|
|
@@ -103,17 +105,7 @@ describe("IncrementalBuilderHost", () => {
|
|
|
103
105
|
});
|
|
104
106
|
|
|
105
107
|
test("fails the requests a departing builder never answered", async () => {
|
|
106
|
-
const spawns =
|
|
107
|
-
const messages: unknown[] = [];
|
|
108
|
-
const host = new IncrementalBuilderHost({
|
|
109
|
-
app: { cwdPath: "/tmp/app" } as never,
|
|
110
|
-
entry: "/tmp/builder.ts",
|
|
111
|
-
env: {},
|
|
112
|
-
onMessage: (message) => messages.push(message),
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
host.start();
|
|
116
|
-
spawns[0]?.options.ipc?.({ type: "builder-ready" });
|
|
108
|
+
const { spawns, messages, host } = startRecordingHost();
|
|
117
109
|
expect(host.send({ type: "build-route", id: 1, routeId: "a", seeds: [], knownEntries: [] })).toBe(true);
|
|
118
110
|
expect(host.send({ type: "build-csr", id: 2, reason: "device webview" })).toBe(true);
|
|
119
111
|
// Answered before the exit, so this one must not be failed again afterwards.
|
|
@@ -124,8 +116,6 @@ describe("IncrementalBuilderHost", () => {
|
|
|
124
116
|
host.recycle("rss=1300MiB>=1200MiB after 3 build(s)");
|
|
125
117
|
spawns[0]?.options.onExit?.();
|
|
126
118
|
|
|
127
|
-
// Nothing else answers these: the builder only refuses requests that arrive after it starts shutting
|
|
128
|
-
// down, and a kill or a truncated write sends nothing at all.
|
|
129
119
|
expect(messages).toEqual([
|
|
130
120
|
{
|
|
131
121
|
type: "build-route-res",
|
|
@@ -152,17 +142,7 @@ describe("IncrementalBuilderHost", () => {
|
|
|
152
142
|
});
|
|
153
143
|
|
|
154
144
|
test("names a crash rather than a recycle, and answers on stop too", async () => {
|
|
155
|
-
const spawns =
|
|
156
|
-
const messages: unknown[] = [];
|
|
157
|
-
const host = new IncrementalBuilderHost({
|
|
158
|
-
app: { cwdPath: "/tmp/app" } as never,
|
|
159
|
-
entry: "/tmp/builder.ts",
|
|
160
|
-
env: {},
|
|
161
|
-
onMessage: (message) => messages.push(message),
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
host.start();
|
|
165
|
-
spawns[0]?.options.ipc?.({ type: "builder-ready" });
|
|
145
|
+
const { spawns, messages, host } = startRecordingHost();
|
|
166
146
|
host.send({ type: "build-route", id: 1, routeId: "a", seeds: [], knownEntries: [] });
|
|
167
147
|
messages.length = 0;
|
|
168
148
|
spawns[0]?.options.onExit?.();
|
|
@@ -175,8 +155,6 @@ describe("IncrementalBuilderHost", () => {
|
|
|
175
155
|
},
|
|
176
156
|
]);
|
|
177
157
|
|
|
178
|
-
// `stop()` clears the process before its exit callback runs, so the callback bails on its identity
|
|
179
|
-
// check and cannot be the only place this happens.
|
|
180
158
|
await wait(1_050);
|
|
181
159
|
spawns[1]?.options.ipc?.({ type: "builder-ready" });
|
|
182
160
|
messages.length = 0;
|
|
@@ -189,12 +167,7 @@ describe("IncrementalBuilderHost", () => {
|
|
|
189
167
|
|
|
190
168
|
test("only recycles a builder that is ready", () => {
|
|
191
169
|
const spawns = mockSpawns();
|
|
192
|
-
const host =
|
|
193
|
-
app: { cwdPath: "/tmp/app" } as never,
|
|
194
|
-
entry: "/tmp/builder.ts",
|
|
195
|
-
env: {},
|
|
196
|
-
onMessage: () => undefined,
|
|
197
|
-
});
|
|
170
|
+
const host = createHost();
|
|
198
171
|
|
|
199
172
|
host.start();
|
|
200
173
|
expect(host.recycle("while still booting")).toBe(false);
|
|
@@ -203,6 +176,36 @@ describe("IncrementalBuilderHost", () => {
|
|
|
203
176
|
host.stop();
|
|
204
177
|
expect(host.recycle("after stop")).toBe(false);
|
|
205
178
|
});
|
|
179
|
+
|
|
180
|
+
test("a patcher a crash turned off stays off in the host that replaces this one", async () => {
|
|
181
|
+
const cwdPath = await mkdtemp(path.join(os.tmpdir(), "akan-builder-patcher-"));
|
|
182
|
+
try {
|
|
183
|
+
const spawns = mockSpawns();
|
|
184
|
+
const app = { cwdPath } as never;
|
|
185
|
+
const host = new IncrementalBuilderHost({ app, entry: "/tmp/builder.ts", env: {}, onMessage: () => undefined });
|
|
186
|
+
host.start();
|
|
187
|
+
spawns[0]?.options.ipc?.({ type: "builder-ready" });
|
|
188
|
+
const marker = path.join(cwdPath, ".akan/artifact", SSR_DEV_DIRNAME, CSR_DEV_PATCHING_MARKER);
|
|
189
|
+
await Bun.write(marker, "");
|
|
190
|
+
spawns[0]?.options.onExit?.();
|
|
191
|
+
await wait(1_050);
|
|
192
|
+
expect(spawns[1]?.options.env?.AKAN_DEV_CSR_PATCHER).toBe("off");
|
|
193
|
+
expect(host.patcherOff).toBe(true);
|
|
194
|
+
host.stop();
|
|
195
|
+
const next = new IncrementalBuilderHost({
|
|
196
|
+
app,
|
|
197
|
+
entry: "/tmp/builder.ts",
|
|
198
|
+
env: {},
|
|
199
|
+
onMessage: () => undefined,
|
|
200
|
+
patcherOff: host.patcherOff,
|
|
201
|
+
});
|
|
202
|
+
next.start();
|
|
203
|
+
expect(spawns[2]?.options.env?.AKAN_DEV_CSR_PATCHER).toBe("off");
|
|
204
|
+
next.stop();
|
|
205
|
+
} finally {
|
|
206
|
+
await rm(cwdPath, { recursive: true, force: true });
|
|
207
|
+
}
|
|
208
|
+
});
|
|
206
209
|
});
|
|
207
210
|
|
|
208
211
|
describe("IncrementalBuilderHost.maxRssBytes", () => {
|
|
@@ -222,23 +225,17 @@ describe("IncrementalBuilderHost.maxRssBytes", () => {
|
|
|
222
225
|
}
|
|
223
226
|
};
|
|
224
227
|
|
|
225
|
-
|
|
226
|
-
* Only where nothing else supplies a limit, which is not true in a container: a cgroup `memory.max`
|
|
227
|
-
* makes `resolveMaxRssBytes` derive from that instead. Measured under `docker --memory=7g`, this
|
|
228
|
-
* returned 2.45GiB rather than the fallback — the assertion was about the runner, not the code.
|
|
229
|
-
*/
|
|
228
|
+
// Skipped under a cgroup `memory.max`, which `resolveMaxRssBytes` derives the ceiling from instead.
|
|
230
229
|
test.skipIf(MemoryLimit.readCgroupBytes() !== null)("defaults to a dev ceiling well above a fresh boot", () => {
|
|
231
230
|
withEnv(
|
|
232
231
|
{ AKAN_BUILDER_MAX_RSS_MB: undefined, AKAN_BUILDER_MAX_RSS: undefined, AKAN_MEMORY_LIMIT: undefined },
|
|
233
232
|
() => {
|
|
234
|
-
expect(IncrementalBuilderHost.maxRssBytes()).toBe(
|
|
233
|
+
expect(IncrementalBuilderHost.maxRssBytes()).toBe(2_048 * 1024 * 1024);
|
|
235
234
|
},
|
|
236
235
|
);
|
|
237
236
|
});
|
|
238
237
|
|
|
239
238
|
test("derives the ceiling from the sandbox's own limit, wherever it runs", () => {
|
|
240
|
-
// The property the fallback test cannot assert in a container, stated so it holds on every runner:
|
|
241
|
-
// the builder gets 35% of whatever the sandbox is allowed.
|
|
242
239
|
withEnv(
|
|
243
240
|
{
|
|
244
241
|
AKAN_BUILDER_MAX_RSS_MB: undefined,
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
1
2
|
import path from "node:path";
|
|
2
3
|
import { Logger } from "akanjs/common";
|
|
3
4
|
import type { BuilderMessage } from "akanjs/server";
|
|
5
|
+
import { CSR_DEV_DIRNAME, CSR_DEV_PATCHING_MARKER, SSR_DEV_DIRNAME } from "akanjs/server/hmr/csrDevManifest";
|
|
4
6
|
import { MemoryLimit } from "akanjs/server/memoryLimit";
|
|
5
7
|
import type { App } from "../commandDecorators";
|
|
6
8
|
|
|
@@ -11,15 +13,13 @@ const builderMsgTypeSet = new Set<BuilderMessage["type"]>([
|
|
|
11
13
|
"invalidate",
|
|
12
14
|
"css-updated",
|
|
13
15
|
"pages-updated",
|
|
16
|
+
"csr-updated",
|
|
17
|
+
"ssr-updated",
|
|
14
18
|
"build-status",
|
|
15
19
|
"builder-metrics",
|
|
20
|
+
"boot-armed",
|
|
16
21
|
]);
|
|
17
|
-
/**
|
|
18
|
-
* Where a dev child process writes. `"pipe"` is not only about keeping a TUI's frame clean: a Bun child
|
|
19
|
-
* that inherits the controlling terminal snapshots its termios at spawn and writes that snapshot back
|
|
20
|
-
* when it exits, so a builder spawned while something holds the terminal raw turns it raw again on the
|
|
21
|
-
* first recycle. See the XXX comment on `Spinner.oraOptions`.
|
|
22
|
-
*/
|
|
22
|
+
/** Prefer `"pipe"` under a TUI: a Bun child that inherits the terminal restores its spawn-time termios on exit. */
|
|
23
23
|
export type DevStdioMode = "inherit" | "pipe";
|
|
24
24
|
|
|
25
25
|
interface IncrementalBuilderHostOptions {
|
|
@@ -28,49 +28,32 @@ interface IncrementalBuilderHostOptions {
|
|
|
28
28
|
env: Record<string, string>;
|
|
29
29
|
stdio?: DevStdioMode;
|
|
30
30
|
onMessage: (message: BuilderMessage) => void;
|
|
31
|
-
/**
|
|
32
|
-
* Required when `stdio` is `"pipe"`: an undrained pipe fills and blocks the builder's next write.
|
|
33
|
-
* Text arrives as decoded chunks, not lines — a consumer that needs lines splits them itself.
|
|
34
|
-
*/
|
|
31
|
+
/** Required with `stdio: "pipe"` (an undrained pipe blocks the builder); receives decoded chunks, not lines. */
|
|
35
32
|
onOutput?: (kind: "stdout" | "stderr", text: string) => void;
|
|
33
|
+
/** Carried over from the host this one replaces: a patcher a crash turned off stays off. */
|
|
34
|
+
patcherOff?: boolean;
|
|
36
35
|
}
|
|
37
36
|
|
|
38
|
-
/**
|
|
39
|
-
* `recycling` is the drain: the builder is still alive and still holds the work it accepted, but it
|
|
40
|
-
* refuses anything new. Saying so here is what lets the host hold those requests for the replacement
|
|
41
|
-
* instead of handing the developer the refusal.
|
|
42
|
-
*/
|
|
37
|
+
/** `recycling`: the builder finishes accepted work but refuses new requests; hold them for the replacement. */
|
|
43
38
|
export type IncrementalBuilderStatus = "starting" | "ready" | "recycling" | "restarting" | "stopped";
|
|
44
39
|
|
|
45
40
|
interface IncrementalBuilderStartOptions {
|
|
46
41
|
onExit?: () => void;
|
|
47
42
|
onReady?: () => void;
|
|
48
43
|
onRestartReady?: () => void;
|
|
49
|
-
/**
|
|
50
|
-
* The builder is gone and a replacement is on its way. Distinct from `onExit`, which reports the
|
|
51
|
-
* builder giving up: this one says the dev server is temporarily without a watcher.
|
|
52
|
-
*/
|
|
44
|
+
/** The builder is gone and a replacement is coming (unwatched until then); `onExit` means it gave up. */
|
|
53
45
|
onAway?: () => void;
|
|
54
|
-
/**
|
|
55
|
-
* Ask the builder to re-announce the artifact it boots with. Needed whenever a *previous* builder's
|
|
56
|
-
* artifact may still be live in a running backend — after an rss recycle, and after an idle wake.
|
|
57
|
-
*/
|
|
46
|
+
/** Re-announce the boot artifact, for when a previous builder's artifact may still be live in the backend. */
|
|
58
47
|
announceBootState?: boolean;
|
|
59
48
|
}
|
|
60
49
|
|
|
61
50
|
export class IncrementalBuilderHost {
|
|
62
51
|
static readonly #restartBaseDelayMs = 1_000;
|
|
63
52
|
static readonly #restartMaxDelayMs = 30_000;
|
|
64
|
-
/**
|
|
65
|
-
* A builder that has stopped answering has to be replaced anyway; killing it after this long turns
|
|
66
|
-
* a wedged drain into an ordinary restart instead of leaving the recycle stuck forever.
|
|
67
|
-
*/
|
|
68
53
|
static readonly #recycleDrainTimeoutMs = 30_000;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
*/
|
|
73
|
-
static readonly #devMaxRssBytes = 1_200 * 1024 * 1024;
|
|
54
|
+
// apps/akan's builder peaks near 950MB holding the SSR registry's patcher through a route build, and arming CSR adds
|
|
55
|
+
// a second patcher: this recycles only a builder grown well past both.
|
|
56
|
+
static readonly #devMaxRssBytes = 2_048 * 1024 * 1024;
|
|
74
57
|
logger = new Logger("IncrementalBuilderHost");
|
|
75
58
|
entry: string;
|
|
76
59
|
env: Record<string, string>;
|
|
@@ -86,35 +69,36 @@ export class IncrementalBuilderHost {
|
|
|
86
69
|
#recycleTimer: ReturnType<typeof setTimeout> | null = null;
|
|
87
70
|
#recycleRequested: boolean = false;
|
|
88
71
|
#spawnAfterRecycle: boolean = false;
|
|
72
|
+
#patcherOff: boolean = false;
|
|
89
73
|
#manualStop = false;
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
* correlation id.
|
|
93
|
-
*
|
|
94
|
-
* Nothing else answers a request whose builder exits while holding it: the builder only refuses
|
|
95
|
-
* requests that arrive *after* it starts shutting down, a kill or crash sends nothing at all, and even
|
|
96
|
-
* a clean drain races its own `process.exit`. The builder exits routinely — it is recycled whenever its
|
|
97
|
-
* RSS passes the ceiling — so a page request that happened to be mid route-build left the backend's
|
|
98
|
-
* promise pending and the browser tab spinning with no error and nothing to retry.
|
|
99
|
-
*/
|
|
74
|
+
// Nothing else answers a request whose builder exits holding it: a crash or kill sends nothing, and a drain races
|
|
75
|
+
// its own exit, so an unanswered page request would spin forever.
|
|
100
76
|
readonly #inFlight = new Map<number, "build-route" | "build-csr">();
|
|
101
77
|
#startOptions: IncrementalBuilderStartOptions = {};
|
|
102
|
-
constructor({
|
|
78
|
+
constructor({
|
|
79
|
+
app,
|
|
80
|
+
entry,
|
|
81
|
+
env,
|
|
82
|
+
stdio = "inherit",
|
|
83
|
+
onMessage,
|
|
84
|
+
onOutput,
|
|
85
|
+
patcherOff = false,
|
|
86
|
+
}: IncrementalBuilderHostOptions) {
|
|
103
87
|
this.app = app;
|
|
104
88
|
this.entry = entry;
|
|
105
89
|
this.env = env;
|
|
106
90
|
this.#stdio = stdio;
|
|
107
91
|
this.#onMessage = onMessage;
|
|
108
92
|
this.#onOutput = onOutput ?? null;
|
|
93
|
+
this.#patcherOff = patcherOff;
|
|
109
94
|
}
|
|
110
95
|
get status() {
|
|
111
96
|
return this.#status;
|
|
112
97
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
*/
|
|
98
|
+
get patcherOff(): boolean {
|
|
99
|
+
return this.#patcherOff;
|
|
100
|
+
}
|
|
101
|
+
/** For reading RSS between builds: the builder's own metrics sample the post-work peak, stale once arenas return. */
|
|
118
102
|
get pid(): number | null {
|
|
119
103
|
return this.#proc?.pid ?? null;
|
|
120
104
|
}
|
|
@@ -129,14 +113,18 @@ export class IncrementalBuilderHost {
|
|
|
129
113
|
#spawn(isRestart: boolean) {
|
|
130
114
|
this.#status = isRestart ? "restarting" : "starting";
|
|
131
115
|
this.ready = false;
|
|
132
|
-
// A fresh builder rebuilds every artifact while the running backend still holds the previous one;
|
|
133
|
-
// the flag is what tells it to re-announce what it booted with.
|
|
134
116
|
const afterRecycle = this.#spawnAfterRecycle;
|
|
135
117
|
this.#spawnAfterRecycle = false;
|
|
118
|
+
this.#checkPatchingMarker(isRestart && !afterRecycle);
|
|
136
119
|
let proc!: Bun.Subprocess<"ignore", "inherit" | "pipe", "inherit" | "pipe">;
|
|
137
120
|
proc = Bun.spawn(["bun", this.entry], {
|
|
138
121
|
cwd: this.app.cwdPath,
|
|
139
|
-
env: {
|
|
122
|
+
env: {
|
|
123
|
+
...this.env,
|
|
124
|
+
AKAN_WATCH: "1",
|
|
125
|
+
...(afterRecycle ? { AKAN_BUILDER_ANNOUNCE_BOOT: "1" } : {}),
|
|
126
|
+
...(this.#patcherOff ? { AKAN_DEV_CSR_PATCHER: "off" } : {}),
|
|
127
|
+
},
|
|
140
128
|
stdio: ["ignore", this.#stdio, this.#stdio],
|
|
141
129
|
ipc: (msg: BuilderMessage) => {
|
|
142
130
|
if (this.#proc !== proc) return;
|
|
@@ -170,11 +158,9 @@ export class IncrementalBuilderHost {
|
|
|
170
158
|
this.#startOptions.onExit?.();
|
|
171
159
|
return;
|
|
172
160
|
}
|
|
173
|
-
//
|
|
174
|
-
// has primed its own index — and an edit that lands in that window is reported by nobody.
|
|
161
|
+
// Both branches below leave the tree unwatched until the replacement primes its index.
|
|
175
162
|
this.#startOptions.onAway?.();
|
|
176
|
-
// A
|
|
177
|
-
// crash backoff — the dev server is without a watcher until the replacement is up.
|
|
163
|
+
// A planned exit: no failed attempt, no crash backoff.
|
|
178
164
|
if (wasRecycle) {
|
|
179
165
|
this.logger.verbose("builder exited for a recycle; spawning its replacement now");
|
|
180
166
|
this.#spawnAfterRecycle = true;
|
|
@@ -192,6 +178,23 @@ export class IncrementalBuilderHost {
|
|
|
192
178
|
}
|
|
193
179
|
this.logger.verbose(`builder spawned pid=${proc.pid} entry=${this.entry}${isRestart ? " restart=1" : ""}`);
|
|
194
180
|
}
|
|
181
|
+
//? The resident builder patches the dev CSR bundle in its own process, so a bundler crash there takes the file watcher
|
|
182
|
+
//? with it. A builder that died holding the marker hands every later CSR save to build workers for this session;
|
|
183
|
+
//? a marker any other exit left behind (Ctrl-C mid-patch) is cleared so it cannot turn the patcher off next session.
|
|
184
|
+
#checkPatchingMarker(afterCrash: boolean): void {
|
|
185
|
+
const markers = [CSR_DEV_DIRNAME, SSR_DEV_DIRNAME]
|
|
186
|
+
.map((dirName) => path.join(this.app.cwdPath, ".akan/artifact", dirName, CSR_DEV_PATCHING_MARKER))
|
|
187
|
+
.filter((marker) => fs.existsSync(marker));
|
|
188
|
+
if (markers.length === 0) return;
|
|
189
|
+
if (afterCrash && !this.#patcherOff) {
|
|
190
|
+
this.#patcherOff = true;
|
|
191
|
+
this.logger.warn(
|
|
192
|
+
"the builder died while patching a dev module registry; registry saves go to a build worker until the next config or metadata restart (AKAN_DEV_CSR_PATCHER=off)",
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
for (const marker of markers) fs.rmSync(marker, { force: true });
|
|
196
|
+
}
|
|
197
|
+
|
|
195
198
|
async #drain(stream: ReadableStream<Uint8Array> | undefined | null, kind: "stdout" | "stderr") {
|
|
196
199
|
if (!stream) return;
|
|
197
200
|
const decoder = new TextDecoder();
|
|
@@ -220,20 +223,14 @@ export class IncrementalBuilderHost {
|
|
|
220
223
|
this.#spawn(true);
|
|
221
224
|
}, delay);
|
|
222
225
|
}
|
|
223
|
-
/**
|
|
224
|
-
* Ask the builder to drain its queues and exit so the OS reclaims the bundler arenas `Bun.build`
|
|
225
|
-
* never gives back; `onExit` then spawns the replacement. Graceful rather than `kill()` so a rebuild
|
|
226
|
-
* in flight is never truncated, with a watchdog for a builder that stops answering.
|
|
227
|
-
*/
|
|
226
|
+
/** Drain-and-exit rather than `kill()`, so no rebuild is truncated; false when the request was not sent. */
|
|
228
227
|
recycle(reason: string): boolean {
|
|
229
228
|
if (!this.#proc || this.#status !== "ready" || this.#recycleRequested) return false;
|
|
230
229
|
const proc = this.#proc;
|
|
231
230
|
if (!this.send({ type: "builder-shutdown", reason })) return false;
|
|
232
231
|
this.#recycleRequested = true;
|
|
233
|
-
//
|
|
234
|
-
//
|
|
235
|
-
// one at a time, into the dev error page a recycle is supposed to be invisible to. `ready` the
|
|
236
|
-
// field is deliberately untouched: `onExit` reads it to tell a planned exit from a boot failure.
|
|
232
|
+
// Not `ready`: the draining builder refuses new requests. The `ready` field stays, since `onExit` reads it to tell
|
|
233
|
+
// a planned exit from a boot failure.
|
|
237
234
|
this.#status = "recycling";
|
|
238
235
|
this.logger.debug(`recycling builder pid=${proc.pid} (${reason})`);
|
|
239
236
|
this.#recycleTimer = setTimeout(() => {
|
|
@@ -252,11 +249,7 @@ export class IncrementalBuilderHost {
|
|
|
252
249
|
clearTimeout(this.#recycleTimer);
|
|
253
250
|
this.#recycleTimer = null;
|
|
254
251
|
}
|
|
255
|
-
/**
|
|
256
|
-
* RSS at which the builder is recycled: an explicit override, else a share of the container's limit
|
|
257
|
-
* (the builder is one of several dev processes), else the dev default. Set
|
|
258
|
-
* `AKAN_BUILDER_MAX_RSS_MB=0` to leave it unbounded.
|
|
259
|
-
*/
|
|
252
|
+
/** RSS that triggers a recycle; `null` (`AKAN_BUILDER_MAX_RSS_MB=0`) means unbounded. */
|
|
260
253
|
static maxRssBytes(): number | null {
|
|
261
254
|
if (process.env.AKAN_BUILDER_MAX_RSS_MB === "0") return null;
|
|
262
255
|
return MemoryLimit.resolveMaxRssBytes({
|
|
@@ -268,8 +261,7 @@ export class IncrementalBuilderHost {
|
|
|
268
261
|
}
|
|
269
262
|
send(message: BuilderMessage): boolean {
|
|
270
263
|
if (!this.#proc || this.#status !== "ready") {
|
|
271
|
-
//
|
|
272
|
-
// states nothing is recovering from are worth a warning.
|
|
264
|
+
// Recycling/restarting is routine (the caller holds the message), so only unrecoverable states warn.
|
|
273
265
|
if (this.#status === "recycling" || this.#status === "restarting")
|
|
274
266
|
this.logger.verbose(`incrementalBuilderHost is ${this.#status}; ${message.type} is for the replacement`);
|
|
275
267
|
else this.logger.warn(`incrementalBuilderHost is ${this.#status}; cannot send ${message.type}`);
|
|
@@ -286,19 +278,13 @@ export class IncrementalBuilderHost {
|
|
|
286
278
|
return false;
|
|
287
279
|
}
|
|
288
280
|
}
|
|
289
|
-
|
|
290
|
-
* Answer every request the departing builder still owed, as if it had failed them itself. `onExit`
|
|
291
|
-
* cannot do this alone: `stop()` clears `#proc` first, so the exit callback bails on its identity check.
|
|
292
|
-
*/
|
|
281
|
+
// Called from `stop()` too: it clears `#proc` first, so `onExit` bails on its identity check.
|
|
293
282
|
#failInFlight(reason: string): void {
|
|
294
283
|
if (!this.#inFlight.size) return;
|
|
295
284
|
const lost = [...this.#inFlight];
|
|
296
285
|
this.#inFlight.clear();
|
|
297
286
|
this.logger.warn(`failing ${lost.length} unanswered builder request(s): ${reason}`);
|
|
298
|
-
for (const [id, type] of lost) {
|
|
299
|
-
if (type === "build-route") this.#onMessage({ type: "build-route-res", id, ok: false, error: reason });
|
|
300
|
-
else this.#onMessage({ type: "build-csr-res", id, ok: false, error: reason });
|
|
301
|
-
}
|
|
287
|
+
for (const [id, type] of lost) this.#onMessage({ type: `${type}-res`, id, ok: false, error: reason });
|
|
302
288
|
}
|
|
303
289
|
stop() {
|
|
304
290
|
this.#manualStop = true;
|
|
@@ -320,7 +306,8 @@ export class IncrementalBuilderHost {
|
|
|
320
306
|
{
|
|
321
307
|
stdio = "inherit",
|
|
322
308
|
onOutput,
|
|
323
|
-
|
|
309
|
+
patcherOff = false,
|
|
310
|
+
}: Pick<IncrementalBuilderHostOptions, "stdio" | "onOutput" | "patcherOff"> = {},
|
|
324
311
|
) {
|
|
325
312
|
const candidates = [
|
|
326
313
|
path.join(app.workspace.workspaceRoot, "pkgs/@akanjs/devkit/incrementalBuilder/incrementalBuilder.proc.ts"),
|
|
@@ -333,7 +320,7 @@ export class IncrementalBuilderHost {
|
|
|
333
320
|
];
|
|
334
321
|
for (const c of candidates)
|
|
335
322
|
if (await Bun.file(c).exists())
|
|
336
|
-
return new IncrementalBuilderHost({ app, entry: c, env, stdio, onMessage, onOutput });
|
|
323
|
+
return new IncrementalBuilderHost({ app, entry: c, env, stdio, onMessage, onOutput, patcherOff });
|
|
337
324
|
throw new Error(`[cli] frontend builder entry not found; looked in: ${candidates.join(", ")}`);
|
|
338
325
|
}
|
|
339
326
|
}
|