@akanjs/devkit 3.0.0-beta.3 → 3.0.0-beta.30
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 +2 -2
- package/README.md +3 -2
- 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 +404 -141
- package/akanConfig/akanConfig.ts +317 -274
- package/akanConfig/index.ts +1 -0
- package/akanConfig/types.ts +21 -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 +269 -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 +177 -0
- package/mobile/desktopBin.ts +143 -0
- package/mobile/desktopServerStage.test.ts +204 -0
- package/mobile/desktopServerStage.ts +138 -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 +151 -0
- package/mobile/nativeApp.ts +405 -0
- package/mobile/nativeAppLine.test.ts +69 -0
- package/mobile/nativeAppLine.ts +71 -0
- package/mobile/nativeConfig.test.ts +414 -0
- package/mobile/nativeConfig.ts +296 -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 +30 -25
- package/subspace.ts +81 -115
- package/subspaceConfig.ts +11 -9
- 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,3 +1,5 @@
|
|
|
1
|
+
import { realpathSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
1
3
|
import { Logger } from "akanjs/common";
|
|
2
4
|
|
|
3
5
|
export interface ApplicationBuildPhaseResult {
|
|
@@ -35,12 +37,12 @@ export class ApplicationBuildReporter {
|
|
|
35
37
|
Logger.rawLog(`Done in ${ApplicationBuildReporter.formatDuration(result.durationMs)}`);
|
|
36
38
|
}
|
|
37
39
|
|
|
38
|
-
static formatError(error: unknown): string {
|
|
40
|
+
static formatError(error: unknown, workspaceRoot = process.cwd()): string {
|
|
39
41
|
if (error instanceof AggregateError) {
|
|
40
42
|
const nestedMessages = error.errors
|
|
41
43
|
.map(
|
|
42
44
|
(nestedError, index) =>
|
|
43
|
-
ApplicationBuildReporter.formatError(nestedError).trim() || `Unknown error ${index + 1}`,
|
|
45
|
+
ApplicationBuildReporter.formatError(nestedError, workspaceRoot).trim() || `Unknown error ${index + 1}`,
|
|
44
46
|
)
|
|
45
47
|
.map((message) => message.replace(/^/gm, " "))
|
|
46
48
|
.join("\n");
|
|
@@ -48,13 +50,43 @@ export class ApplicationBuildReporter {
|
|
|
48
50
|
return nestedMessages ? `${error.message}\n${nestedMessages}` : error.message;
|
|
49
51
|
}
|
|
50
52
|
if (error instanceof Error) {
|
|
51
|
-
const causeMessage = error.cause
|
|
52
|
-
|
|
53
|
+
const causeMessage = error.cause
|
|
54
|
+
? `\nCaused by: ${ApplicationBuildReporter.formatError(error.cause, workspaceRoot)}`
|
|
55
|
+
: "";
|
|
56
|
+
return `${ApplicationBuildReporter.#withLocation(error.message, error, workspaceRoot)}${causeMessage}`;
|
|
53
57
|
}
|
|
54
|
-
if (typeof error === "object" && error !== null && "message" in error)
|
|
58
|
+
if (typeof error === "object" && error !== null && "message" in error)
|
|
59
|
+
return ApplicationBuildReporter.#withLocation(String(error.message), error, workspaceRoot);
|
|
55
60
|
return String(error);
|
|
56
61
|
}
|
|
57
62
|
|
|
63
|
+
static #withLocation(message: string, error: object, workspaceRoot: string): string {
|
|
64
|
+
const { position } = error as { position?: { file: string; line: number; column: number } | null };
|
|
65
|
+
if (!position?.file) return message;
|
|
66
|
+
const file = ApplicationBuildReporter.#relativeToWorkspace(position.file, workspaceRoot);
|
|
67
|
+
const location = position.line > 0 ? `${file}:${position.line}:${position.column}` : file;
|
|
68
|
+
return message.includes(location) ? message : message.replace(/^.*/, (headline) => `${headline} (${location})`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Bun names the file by its realpath, so a workspace root reached through a symlink is matched by its own too.
|
|
72
|
+
static #relativeToWorkspace(file: string, workspaceRoot: string): string {
|
|
73
|
+
if (!path.isAbsolute(file)) return file;
|
|
74
|
+
for (const root of [workspaceRoot, ApplicationBuildReporter.#realpathOf(workspaceRoot)]) {
|
|
75
|
+
const relative = path.relative(root, file);
|
|
76
|
+
if (!relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative))
|
|
77
|
+
return relative.replaceAll(path.sep, "/");
|
|
78
|
+
}
|
|
79
|
+
return file;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
static #realpathOf(dir: string): string {
|
|
83
|
+
try {
|
|
84
|
+
return realpathSync(dir);
|
|
85
|
+
} catch {
|
|
86
|
+
return dir;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
58
90
|
static formatDuration(ms: number): string {
|
|
59
91
|
if (ms < 1000) return `${ms}ms`;
|
|
60
92
|
if (ms < 60_000) return `${Math.round(ms / 100) / 10}s`;
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { rm } from "node:fs/promises";
|
|
3
|
-
import
|
|
2
|
+
import { mkdtemp, readFile, rm } from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { AkanAppConfig } from "./akanConfig";
|
|
6
|
+
import { AKAN_BACKEND_MINIFY, AKAN_OPTIONAL_BACKEND_EXTERNALS, ApplicationBuildRunner } from "./applicationBuildRunner";
|
|
7
|
+
import type { App } from "./commandDecorators";
|
|
8
|
+
import { Executor } from "./executors";
|
|
4
9
|
|
|
5
10
|
describe("ApplicationBuildRunner", () => {
|
|
6
11
|
test("externalizes Akan optional backend dependencies", () => {
|
|
@@ -16,8 +21,8 @@ describe("ApplicationBuildRunner", () => {
|
|
|
16
21
|
});
|
|
17
22
|
|
|
18
23
|
test("a bundler that mangles identifiers loses the class name a Logger reads", async () => {
|
|
19
|
-
const dir =
|
|
20
|
-
const entry =
|
|
24
|
+
const dir = path.join(os.tmpdir(), `akan-minify-${Bun.randomUUIDv7()}`);
|
|
25
|
+
const entry = path.join(dir, "entry.ts");
|
|
21
26
|
await Bun.write(
|
|
22
27
|
entry,
|
|
23
28
|
`const serve = () => class Service { readonly name = this.constructor.name; };
|
|
@@ -26,7 +31,7 @@ console.info(new SampleService().name);
|
|
|
26
31
|
`,
|
|
27
32
|
);
|
|
28
33
|
const build = async (minify: Bun.BuildConfig["minify"]) => {
|
|
29
|
-
const result = await Bun.build({ entrypoints: [entry], target: "bun", minify, outdir:
|
|
34
|
+
const result = await Bun.build({ entrypoints: [entry], target: "bun", minify, outdir: path.join(dir, "out") });
|
|
30
35
|
expect(result.success).toBe(true);
|
|
31
36
|
const proc = Bun.spawn([process.execPath, result.outputs[0].path], { stdout: "pipe" });
|
|
32
37
|
return (await new Response(proc.stdout).text()).trim();
|
|
@@ -42,4 +47,35 @@ console.info(new SampleService().name);
|
|
|
42
47
|
await rm(dir, { recursive: true, force: true });
|
|
43
48
|
}
|
|
44
49
|
});
|
|
50
|
+
|
|
51
|
+
test("an image of a --env build boots the env its server carries, not the root .env's", async () => {
|
|
52
|
+
const root = await mkdtemp(path.join(os.tmpdir(), "akan-build-meta-"));
|
|
53
|
+
const baseDevEnv = {
|
|
54
|
+
repoName: "akanjs",
|
|
55
|
+
serveDomain: "akanjs.com",
|
|
56
|
+
env: "local" as const,
|
|
57
|
+
portOffset: 0,
|
|
58
|
+
workspaceRoot: root,
|
|
59
|
+
};
|
|
60
|
+
const config = new AkanAppConfig(
|
|
61
|
+
{ name: "portal" } as never,
|
|
62
|
+
[],
|
|
63
|
+
{ name: "repo", version: "1.0.0", description: "repo" },
|
|
64
|
+
{},
|
|
65
|
+
baseDevEnv,
|
|
66
|
+
);
|
|
67
|
+
const app = {
|
|
68
|
+
getConfig: async () => config,
|
|
69
|
+
dist: new Executor("dist", root),
|
|
70
|
+
workspace: { workspaceRoot: root },
|
|
71
|
+
} as unknown as App;
|
|
72
|
+
try {
|
|
73
|
+
await new ApplicationBuildRunner(app, { environment: "debug" }).buildAppMeta();
|
|
74
|
+
expect(await readFile(path.join(root, "Dockerfile"), "utf8")).toContain("ENV AKAN_PUBLIC_ENV=debug\n");
|
|
75
|
+
await new ApplicationBuildRunner(app).buildAppMeta();
|
|
76
|
+
expect(await readFile(path.join(root, "Dockerfile"), "utf8")).toContain("ENV AKAN_PUBLIC_ENV=local\n");
|
|
77
|
+
} finally {
|
|
78
|
+
await rm(root, { recursive: true, force: true });
|
|
79
|
+
}
|
|
80
|
+
});
|
|
45
81
|
});
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
SsrBaseArtifactBuilder,
|
|
16
16
|
} from "./frontendBuild";
|
|
17
17
|
import { Spinner } from "./spinner";
|
|
18
|
+
import { createServerEnvPlugin } from "./transforms/serverEnvPlugin";
|
|
18
19
|
|
|
19
20
|
export interface TypecheckOptions {
|
|
20
21
|
clean?: boolean;
|
|
@@ -29,6 +30,8 @@ export type BuildProgressReporter = ApplicationBuildProgressReporter;
|
|
|
29
30
|
export interface ApplicationBuildRunnerOptions {
|
|
30
31
|
fast?: boolean;
|
|
31
32
|
reporter?: BuildProgressReporter;
|
|
33
|
+
/** The backend env the build is for, when it is not the workspace's own (a mobile or desktop build's `--env`). */
|
|
34
|
+
environment?: string;
|
|
32
35
|
}
|
|
33
36
|
export interface BuildOptions {
|
|
34
37
|
spinner?: boolean;
|
|
@@ -60,9 +63,8 @@ const SSR_RENDER_EXTERNALS = [
|
|
|
60
63
|
"react-server-dom-webpack/client.browser",
|
|
61
64
|
] as const;
|
|
62
65
|
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
// `minify.keepNames` typechecks and does nothing as of Bun 1.4.2.
|
|
66
|
+
// Mangling renames classes, and `this.constructor.name` names loggers, exceptions and guards; `minify.keepNames`
|
|
67
|
+
// is a no-op as of Bun 1.4.2.
|
|
66
68
|
export const AKAN_BACKEND_MINIFY = { whitespace: true, syntax: true, identifiers: false } as const;
|
|
67
69
|
|
|
68
70
|
export const AKAN_OPTIONAL_BACKEND_EXTERNALS = [
|
|
@@ -77,29 +79,29 @@ export class ApplicationBuildRunner {
|
|
|
77
79
|
#app: App;
|
|
78
80
|
#fast: boolean;
|
|
79
81
|
#reporter?: BuildProgressReporter;
|
|
82
|
+
#environment?: string;
|
|
83
|
+
#spinner?: boolean;
|
|
80
84
|
#startedAt = Date.now();
|
|
81
85
|
#phases: BuildPhaseResult[] = [];
|
|
82
86
|
|
|
83
|
-
constructor(app: App, { fast = false, reporter }: ApplicationBuildRunnerOptions = {}) {
|
|
87
|
+
constructor(app: App, { fast = false, reporter, environment }: ApplicationBuildRunnerOptions = {}) {
|
|
84
88
|
this.#app = app;
|
|
85
89
|
this.#fast = fast;
|
|
86
90
|
this.#reporter = reporter;
|
|
91
|
+
this.#environment = environment;
|
|
87
92
|
}
|
|
88
93
|
|
|
89
94
|
async build({ spinner = false }: BuildOptions = {}): Promise<BuildResult> {
|
|
90
95
|
// serial build is needed because of Bun.build is unstable for parallel build
|
|
91
|
-
|
|
96
|
+
this.#spinner = spinner;
|
|
92
97
|
const { web, assets } = await this.#app.getConfig();
|
|
93
|
-
await this.#runPhase("prepare", "Preparing output directory", () => this.#app.prepareCommand("build")
|
|
94
|
-
|
|
95
|
-
});
|
|
96
|
-
if (!this.#fast) await this.#runPhase("typecheck", "Typechecking", () => this.typecheck(), undefined, phaseOptions);
|
|
98
|
+
await this.#runPhase("prepare", "Preparing output directory", () => this.#app.prepareCommand("build"));
|
|
99
|
+
if (!this.#fast) await this.#runPhase("typecheck", "Typechecking", () => this.typecheck());
|
|
97
100
|
await this.#runPhase(
|
|
98
101
|
"backend",
|
|
99
102
|
"Compiling backend",
|
|
100
103
|
() => this.#buildBackend(),
|
|
101
104
|
(result) => `${result.entrypoints} entrypoints, ${result.outputs} outputs`,
|
|
102
|
-
phaseOptions,
|
|
103
105
|
);
|
|
104
106
|
await this.#runPhase(
|
|
105
107
|
"ssr",
|
|
@@ -111,14 +113,12 @@ export class ApplicationBuildRunner {
|
|
|
111
113
|
: web.ssr
|
|
112
114
|
? "skipped"
|
|
113
115
|
: "disabled by akan.config.ts web.ssr",
|
|
114
|
-
phaseOptions,
|
|
115
116
|
);
|
|
116
117
|
await this.#runPhase(
|
|
117
118
|
"csr",
|
|
118
119
|
"Building CSR assets",
|
|
119
|
-
async () => (web.csr ? await this.#
|
|
120
|
+
async () => (web.csr ? await new CsrArtifactBuilder(this.#app, "build").build() : null),
|
|
120
121
|
(result) => result?.outputDir ?? (web.csr ? "skipped" : "disabled by akan.config.ts web.csr"),
|
|
121
|
-
phaseOptions,
|
|
122
122
|
);
|
|
123
123
|
await this.#runPhase(
|
|
124
124
|
"assets",
|
|
@@ -128,7 +128,6 @@ export class ApplicationBuildRunner {
|
|
|
128
128
|
result
|
|
129
129
|
? `${result.removed.length} font file(s) dropped, ${ApplicationBuildRunner.formatBytes(result.freedBytes)} freed; ${result.kept.length} kept`
|
|
130
130
|
: "disabled by akan.config.ts assets.pruneFonts",
|
|
131
|
-
phaseOptions,
|
|
132
131
|
);
|
|
133
132
|
await this.#runPhase(
|
|
134
133
|
"compress",
|
|
@@ -138,11 +137,8 @@ export class ApplicationBuildRunner {
|
|
|
138
137
|
result.files > 0
|
|
139
138
|
? `${result.files} files, ${ApplicationBuildRunner.formatBytes(result.inputBytes)} -> gzip ${ApplicationBuildRunner.formatBytes(result.outputBytes)} / br ${ApplicationBuildRunner.formatBytes(result.brotliBytes)}`
|
|
140
139
|
: "no files",
|
|
141
|
-
phaseOptions,
|
|
142
140
|
);
|
|
143
|
-
await this.#runPhase("metadata", "Writing production metadata", () => this
|
|
144
|
-
spinner,
|
|
145
|
-
});
|
|
141
|
+
await this.#runPhase("metadata", "Writing production metadata", () => this.buildAppMeta());
|
|
146
142
|
return {
|
|
147
143
|
phases: this.#phases,
|
|
148
144
|
durationMs: Date.now() - this.#startedAt,
|
|
@@ -154,7 +150,7 @@ export class ApplicationBuildRunner {
|
|
|
154
150
|
async typecheck(options: TypecheckOptions = {}) {
|
|
155
151
|
const { clean = false, incremental = true } = options;
|
|
156
152
|
await this.#app.getPageKeys({ refresh: true });
|
|
157
|
-
const { typecheckDir, tsconfigPath } = await this.#writeTypecheckTsconfig(
|
|
153
|
+
const { typecheckDir, tsconfigPath } = await this.#writeTypecheckTsconfig(incremental);
|
|
158
154
|
if (clean) await rm(path.join(typecheckDir, "tsconfig.tsbuildinfo"), { force: true });
|
|
159
155
|
await this.#checkProjectInChildProcess(tsconfigPath);
|
|
160
156
|
}
|
|
@@ -164,19 +160,17 @@ export class ApplicationBuildRunner {
|
|
|
164
160
|
label: string,
|
|
165
161
|
task: () => Promise<T>,
|
|
166
162
|
summarize?: (result: T) => string | undefined,
|
|
167
|
-
options: BuildPhaseRunOptions = {},
|
|
168
163
|
) {
|
|
169
164
|
this.#reporter?.phaseStart?.({ id, label });
|
|
170
165
|
const phaseStartedAt = Date.now();
|
|
171
|
-
const spinner =
|
|
166
|
+
const spinner = this.#spinner
|
|
172
167
|
? new Spinner(label, { prefix: `${BUILD_PHASE_EMOJIS[id]} ${id}` }).start()
|
|
173
168
|
: undefined;
|
|
174
169
|
try {
|
|
175
170
|
const result = await task();
|
|
176
171
|
const phase = { id, label, durationMs: Date.now() - phaseStartedAt, summary: summarize?.(result) };
|
|
177
172
|
this.#phases.push(phase);
|
|
178
|
-
|
|
179
|
-
spinner?.succeed(`${label}${summary}`);
|
|
173
|
+
spinner?.succeed(`${label}${phase.summary ? `: ${phase.summary}` : ""}`);
|
|
180
174
|
this.#reporter?.phaseDone?.(phase);
|
|
181
175
|
return result;
|
|
182
176
|
} catch (error) {
|
|
@@ -186,71 +180,94 @@ export class ApplicationBuildRunner {
|
|
|
186
180
|
}
|
|
187
181
|
}
|
|
188
182
|
|
|
189
|
-
async
|
|
183
|
+
async buildAppMeta() {
|
|
190
184
|
const akanConfig = await this.#app.getConfig();
|
|
185
|
+
const dockerfile = akanConfig.dockerfileFor(this.#environment ?? akanConfig.baseDevEnv.env);
|
|
191
186
|
await Promise.all([
|
|
192
187
|
this.#app.dist.writeJson("package.json", akanConfig.getProductionPackageJson()),
|
|
193
|
-
this.#app.dist.writeFile(`${this.#app.dist.cwdPath}/Dockerfile`,
|
|
188
|
+
this.#app.dist.writeFile(`${this.#app.dist.cwdPath}/Dockerfile`, dockerfile),
|
|
189
|
+
this.#app.dist.writeJson("akan.build.json", {
|
|
190
|
+
buildId: await this.#resolveBuildId(),
|
|
191
|
+
akanVersion: akanConfig.akanVersion,
|
|
192
|
+
builtAt: new Date().toISOString(),
|
|
193
|
+
}),
|
|
194
194
|
]);
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
+
//* Read back at runtime as `buildId` on the ops channel; a deployment's own AKAN_BUILD_ID still wins over it there.
|
|
198
|
+
async #resolveBuildId() {
|
|
199
|
+
const fromEnv = process.env.AKAN_BUILD_ID?.trim();
|
|
200
|
+
if (fromEnv) return fromEnv;
|
|
201
|
+
const root = this.#app.workspace.workspaceRoot;
|
|
202
|
+
const sha = Bun.spawnSync(["git", "rev-parse", "--short=12", "HEAD"], { cwd: root, stderr: "ignore" });
|
|
203
|
+
if (sha.exitCode !== 0) return null;
|
|
204
|
+
const dirty = Bun.spawnSync(["git", "status", "--porcelain", "--untracked-files=no"], {
|
|
205
|
+
cwd: root,
|
|
206
|
+
stderr: "ignore",
|
|
207
|
+
});
|
|
208
|
+
const suffix = dirty.exitCode === 0 && dirty.stdout.toString().trim() ? "-dirty" : "";
|
|
209
|
+
return `${sha.stdout.toString().trim()}${suffix}`;
|
|
210
|
+
}
|
|
211
|
+
|
|
197
212
|
async #buildBackend() {
|
|
198
|
-
const
|
|
199
|
-
const backendExternals = [
|
|
200
|
-
...new Set([...akanConfig.externalLibs, ...SSR_RENDER_EXTERNALS, ...AKAN_OPTIONAL_BACKEND_EXTERNALS]),
|
|
201
|
-
];
|
|
202
|
-
const { web } = akanConfig;
|
|
213
|
+
const { externalLibs, web, baseDevEnv, branches } = await this.#app.getConfig();
|
|
203
214
|
const backendEntryPoints = [`${this.#app.cwdPath}/main.ts`, `${this.#app.cwdPath}/server.ts`];
|
|
204
215
|
for (const entrypoint of backendEntryPoints) {
|
|
205
216
|
if (!(await Bun.file(entrypoint).exists())) throw new Error(`Backend entrypoint not found: ${entrypoint}`);
|
|
206
217
|
}
|
|
207
|
-
const
|
|
208
|
-
entrypoints: backendEntryPoints,
|
|
218
|
+
const sharedConfig = {
|
|
209
219
|
outdir: this.#app.dist.cwdPath,
|
|
210
220
|
target: "bun",
|
|
211
221
|
minify: AKAN_BACKEND_MINIFY,
|
|
212
222
|
naming: { entry: "[name].[ext]", chunk: "chunk-[hash].[ext]" },
|
|
223
|
+
// `akan build` must embed production react-server-dom regardless of the shell's NODE_ENV.
|
|
213
224
|
define: { "process.env.NODE_ENV": JSON.stringify("production") },
|
|
214
|
-
|
|
225
|
+
} satisfies Omit<Bun.BuildConfig, "entrypoints">;
|
|
226
|
+
const serverEnvPlugin = createServerEnvPlugin({
|
|
227
|
+
envDir: path.join(this.#app.cwdPath, "env"),
|
|
228
|
+
//? The root .env wins over the shell in baseDevEnv, so a `--env` build names its own; a desktop app runs it.
|
|
229
|
+
environment: this.#environment ?? baseDevEnv.env,
|
|
230
|
+
environments: ["local", "testing", ...branches],
|
|
215
231
|
});
|
|
232
|
+
const backendConfig = {
|
|
233
|
+
...sharedConfig,
|
|
234
|
+
plugins: [this.#createExternalSpecifiersPlugin(externalLibs), serverEnvPlugin],
|
|
235
|
+
};
|
|
236
|
+
//* Built apart so main.js keeps its own module copies; splitting moves lazy vendor `import()`s out of the boot parse.
|
|
237
|
+
const [mainResult, serverResult] = [
|
|
238
|
+
await this.#buildOrThrow("backend", { ...backendConfig, entrypoints: [backendEntryPoints[0]] }),
|
|
239
|
+
await this.#buildOrThrow("backend", { ...backendConfig, entrypoints: [backendEntryPoints[1]], splitting: true }),
|
|
240
|
+
];
|
|
216
241
|
// Nothing spawns the RSC worker without SSR, so an api-only image does not carry it.
|
|
217
242
|
const rscWorkerResult = web.ssr
|
|
218
243
|
? await this.#buildOrThrow("rsc-worker", {
|
|
244
|
+
...backendConfig,
|
|
219
245
|
entrypoints: [this.#resolveRscWorkerBuildEntry()],
|
|
220
|
-
outdir: this.#app.dist.cwdPath,
|
|
221
|
-
target: "bun",
|
|
222
|
-
minify: AKAN_BACKEND_MINIFY,
|
|
223
|
-
naming: { entry: "[name].[ext]", chunk: "chunk-[hash].[ext]" },
|
|
224
246
|
conditions: ["react-server"],
|
|
225
|
-
// `akan build` must embed production react-server-dom regardless of the shell's NODE_ENV.
|
|
226
|
-
define: { "process.env.NODE_ENV": JSON.stringify("production") },
|
|
227
|
-
plugins: backendExternals.length > 0 ? [this.#createExternalSpecifiersPlugin(backendExternals)] : [],
|
|
228
247
|
})
|
|
229
248
|
: null;
|
|
230
249
|
const consoleRuntimeResult = await this.#buildOrThrow("console-runtime", {
|
|
250
|
+
...sharedConfig,
|
|
231
251
|
entrypoints: [this.#resolveConsoleRuntimeBuildEntry()],
|
|
232
|
-
outdir: this.#app.dist.cwdPath,
|
|
233
|
-
target: "bun",
|
|
234
|
-
minify: AKAN_BACKEND_MINIFY,
|
|
235
252
|
naming: { entry: "console-runtime.[ext]", chunk: "chunk-[hash].[ext]" },
|
|
236
|
-
define: { "process.env.NODE_ENV": JSON.stringify("production") },
|
|
237
253
|
});
|
|
238
254
|
await this.#writeConsoleShim();
|
|
255
|
+
const results = [mainResult, serverResult, rscWorkerResult, consoleRuntimeResult];
|
|
239
256
|
return {
|
|
240
257
|
entrypoints: backendEntryPoints.length + (rscWorkerResult ? 2 : 1),
|
|
241
|
-
outputs:
|
|
242
|
-
backendResult.outputs.length + (rscWorkerResult?.outputs.length ?? 0) + consoleRuntimeResult.outputs.length + 1,
|
|
258
|
+
outputs: results.reduce((sum, result) => sum + (result?.outputs.length ?? 0), 1),
|
|
243
259
|
};
|
|
244
260
|
}
|
|
245
261
|
|
|
246
262
|
async #writeConsoleShim() {
|
|
247
263
|
await Bun.write(
|
|
248
264
|
path.join(this.#app.dist.cwdPath, "console.js"),
|
|
249
|
-
`
|
|
250
|
-
|
|
265
|
+
`process.env.AKAN_COMMAND_TYPE = "console";
|
|
266
|
+
const { cnst, db, dict, option, server, sig, srv } = await import("./server.js");
|
|
267
|
+
const { assertAkanConsoleAllowed, startAkanConsole } = await import("./console-runtime.js");
|
|
251
268
|
|
|
252
269
|
const run = async () => {
|
|
253
|
-
assertAkanConsoleAllowed(
|
|
270
|
+
assertAkanConsoleAllowed();
|
|
254
271
|
await server.start({ listen: false, web: false });
|
|
255
272
|
try {
|
|
256
273
|
await startAkanConsole(server, { globals: { cnst, db, dict, option, sig, srv } });
|
|
@@ -283,10 +300,6 @@ void run().catch((error) => {
|
|
|
283
300
|
}
|
|
284
301
|
}
|
|
285
302
|
|
|
286
|
-
async #buildCsr() {
|
|
287
|
-
return await new CsrArtifactBuilder(this.#app, "build").build();
|
|
288
|
-
}
|
|
289
|
-
|
|
290
303
|
async #buildSsr() {
|
|
291
304
|
const pageKeys = await this.#app.getPageKeys();
|
|
292
305
|
if (pageKeys.length === 0) {
|
|
@@ -298,7 +311,7 @@ void run().catch((error) => {
|
|
|
298
311
|
return { base, allRoutes };
|
|
299
312
|
}
|
|
300
313
|
|
|
301
|
-
async #writeTypecheckTsconfig(
|
|
314
|
+
async #writeTypecheckTsconfig(incremental: boolean) {
|
|
302
315
|
const typecheckDir = path.join(this.#app.cwdPath, ".akan", "typecheck");
|
|
303
316
|
await mkdir(typecheckDir, { recursive: true });
|
|
304
317
|
//* TypeScript's `include` globs do not cross a symlink, so synced lib pages need their real path.
|
|
@@ -367,11 +380,10 @@ void run().catch((error) => {
|
|
|
367
380
|
return result;
|
|
368
381
|
}
|
|
369
382
|
|
|
370
|
-
#createExternalSpecifiersPlugin(
|
|
371
|
-
const
|
|
372
|
-
const escaped =
|
|
383
|
+
#createExternalSpecifiersPlugin(externalLibs: readonly string[]): BunPlugin {
|
|
384
|
+
const specifiers = new Set([...externalLibs, ...SSR_RENDER_EXTERNALS, ...AKAN_OPTIONAL_BACKEND_EXTERNALS]);
|
|
385
|
+
const escaped = [...specifiers].map((specifier) => specifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
373
386
|
const filter = new RegExp(`^(${escaped.join("|")})(?:/.*)?$`);
|
|
374
|
-
|
|
375
387
|
return {
|
|
376
388
|
name: "akan-backend-externalize-specifiers",
|
|
377
389
|
setup(build) {
|
|
@@ -380,9 +392,5 @@ void run().catch((error) => {
|
|
|
380
392
|
};
|
|
381
393
|
}
|
|
382
394
|
|
|
383
|
-
static formatBytes
|
|
384
|
-
if (bytes < 1024) return `${bytes}B`;
|
|
385
|
-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
|
|
386
|
-
return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
|
|
387
|
-
}
|
|
395
|
+
static formatBytes = FontPruner.formatBytes;
|
|
388
396
|
}
|
|
@@ -8,7 +8,7 @@ export interface SignalTestPreloadTarget {
|
|
|
8
8
|
|
|
9
9
|
export async function resolveSignalTestPreloadPath(target: SignalTestPreloadTarget) {
|
|
10
10
|
const candidates: string[] = [];
|
|
11
|
-
const
|
|
11
|
+
for (const basePath of [target.cwdPath, process.cwd(), path.dirname(Bun.main), import.meta.dir]) {
|
|
12
12
|
try {
|
|
13
13
|
candidates.push(
|
|
14
14
|
path.join(path.dirname(Bun.resolveSync("akanjs/package.json", basePath)), SIGNAL_TEST_PRELOAD_PATH),
|
|
@@ -16,13 +16,7 @@ export async function resolveSignalTestPreloadPath(target: SignalTestPreloadTarg
|
|
|
16
16
|
} catch {
|
|
17
17
|
// Source workspaces and published installs can resolve Akan packages from different roots.
|
|
18
18
|
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
addResolvedPackageCandidate(target.cwdPath);
|
|
22
|
-
addResolvedPackageCandidate(process.cwd());
|
|
23
|
-
addResolvedPackageCandidate(path.dirname(Bun.main));
|
|
24
|
-
addResolvedPackageCandidate(import.meta.dir);
|
|
25
|
-
|
|
19
|
+
}
|
|
26
20
|
candidates.push(
|
|
27
21
|
path.join(target.cwdPath, "../../node_modules/akanjs", SIGNAL_TEST_PRELOAD_PATH),
|
|
28
22
|
path.join(target.cwdPath, "../../pkgs/akanjs", SIGNAL_TEST_PRELOAD_PATH),
|
|
@@ -33,10 +27,7 @@ export async function resolveSignalTestPreloadPath(target: SignalTestPreloadTarg
|
|
|
33
27
|
);
|
|
34
28
|
|
|
35
29
|
const uniqueCandidates = [...new Set(candidates)];
|
|
36
|
-
for (const candidate of uniqueCandidates)
|
|
37
|
-
if (await Bun.file(candidate).exists()) return candidate;
|
|
38
|
-
}
|
|
39
|
-
|
|
30
|
+
for (const candidate of uniqueCandidates) if (await Bun.file(candidate).exists()) return candidate;
|
|
40
31
|
throw new Error(
|
|
41
32
|
`Failed to locate ${SIGNAL_TEST_PRELOAD_PATH} from ${target.cwdPath}.\nProbed paths:\n${uniqueCandidates
|
|
42
33
|
.map((candidate) => ` - ${candidate}`)
|
|
@@ -1,25 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
3
|
-
import os from "node:os";
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
4
2
|
import path from "node:path";
|
|
3
|
+
import { tempDirs, writeText as write } from "../testHelpers";
|
|
5
4
|
import { resolveSsrPageEntries } from "./implicitRootLayout";
|
|
6
5
|
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
const makeTempRoot = async () => {
|
|
10
|
-
const root = await mkdtemp(path.join(os.tmpdir(), "akan-implicit-root-layout-"));
|
|
11
|
-
tempRoots.push(root);
|
|
12
|
-
return root;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
const write = async (filePath: string, content: string) => {
|
|
16
|
-
await mkdir(path.dirname(filePath), { recursive: true });
|
|
17
|
-
await writeFile(filePath, content);
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
afterEach(async () => {
|
|
21
|
-
await Promise.all(tempRoots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
|
|
22
|
-
});
|
|
6
|
+
const makeTempRoot = tempDirs("akan-implicit-root-layout-");
|
|
23
7
|
|
|
24
8
|
describe("resolveSsrPageEntries", () => {
|
|
25
9
|
test("inherits root layout static exports for grouped root boundaries", async () => {
|
|
@@ -31,11 +15,11 @@ describe("resolveSsrPageEntries", () => {
|
|
|
31
15
|
await write(path.join(appRoot, "env", "env.client.ts"), "export const env = {};\n");
|
|
32
16
|
await write(
|
|
33
17
|
rootLayoutPath,
|
|
34
|
-
'export const theme = "dark";\nexport const fonts = [{ name: "pretendard" }];\nexport const
|
|
18
|
+
'export const theme = "dark";\nexport const fonts = [{ name: "pretendard" }];\nexport const head = null;\n',
|
|
35
19
|
);
|
|
36
20
|
await write(
|
|
37
21
|
groupedLayoutPath,
|
|
38
|
-
|
|
22
|
+
"export function generateHead() { return null; }\nexport default function Layout({ children }) { return children; }\n",
|
|
39
23
|
);
|
|
40
24
|
|
|
41
25
|
const entries = await resolveSsrPageEntries({
|
|
@@ -54,15 +38,10 @@ describe("resolveSsrPageEntries", () => {
|
|
|
54
38
|
expect(generatedSource).toContain('import * as inheritedModule from "../../../page/_layout.tsx";');
|
|
55
39
|
expect(generatedSource).toContain("const inheritedLayout = resolveRouteModule(inheritedModule as never");
|
|
56
40
|
expect(generatedSource).not.toContain("<System.Provider");
|
|
57
|
-
expect(generatedSource).toContain("export async function
|
|
58
|
-
expect(generatedSource).toContain("if (userLayout.
|
|
59
|
-
expect(generatedSource).toContain("
|
|
60
|
-
expect(generatedSource).toContain(
|
|
61
|
-
"if (inheritedLayout.generateMetadata) return inheritedLayout.generateMetadata(props);",
|
|
62
|
-
);
|
|
63
|
-
expect(generatedSource).toContain("return inheritedLayout.metadata;");
|
|
64
|
-
expect(generatedSource).not.toContain("Object.keys(userLayout.metadata)");
|
|
65
|
-
expect(generatedSource).not.toContain("Object.keys(inheritedLayout.metadata)");
|
|
41
|
+
expect(generatedSource).toContain("export async function generateHead(props: PageProps)");
|
|
42
|
+
expect(generatedSource).toContain("if (userLayout.generateHead) return userLayout.generateHead(props);");
|
|
43
|
+
expect(generatedSource).toContain("return inheritedLayout.head;");
|
|
44
|
+
expect(generatedSource).not.toContain("generateMetadata");
|
|
66
45
|
expect(generatedSource).toContain("export const NotFound = userLayout.NotFound ?? inheritedLayout.NotFound;");
|
|
67
46
|
expect(generatedSource).toContain("export const Error = userLayout.Error ?? inheritedLayout.Error;");
|
|
68
47
|
expect(generatedSource).toContain("export const pageConfig = userLayout.pageConfig ?? inheritedLayout.pageConfig;");
|
|
@@ -171,4 +150,27 @@ describe("resolveSsrPageEntries", () => {
|
|
|
171
150
|
expect(generatedSource).toContain("? await UserLayout({ params, searchParams, children })");
|
|
172
151
|
expect(generatedSource).toContain(" return layout;\n}");
|
|
173
152
|
});
|
|
153
|
+
|
|
154
|
+
test("prunes generated wrappers whose route file moved away", async () => {
|
|
155
|
+
const appRoot = await makeTempRoot();
|
|
156
|
+
const pageRoot = path.join(appRoot, "page");
|
|
157
|
+
await write(path.join(appRoot, "env", "env.client.ts"), "export const env = {};\n");
|
|
158
|
+
await write(path.join(pageRoot, "a", "_overrides.tsx"), "export default {};\n");
|
|
159
|
+
await write(path.join(pageRoot, "b", "_overrides.tsx"), "export default {};\n");
|
|
160
|
+
await write(path.join(pageRoot, "(home)", "_layout.tsx"), "export default ({ children }) => children;\n");
|
|
161
|
+
const resolve = async (pageKeys: string[]) =>
|
|
162
|
+
await resolveSsrPageEntries({ appCwdPath: appRoot, appName: "demo", pageKeys });
|
|
163
|
+
const generatedPaths = async (pageKeys: string[]) =>
|
|
164
|
+
(await resolve(pageKeys))
|
|
165
|
+
.filter((entry) => entry.moduleAbsPath.includes(`${path.sep}.akan${path.sep}`))
|
|
166
|
+
.map((entry) => entry.moduleAbsPath);
|
|
167
|
+
|
|
168
|
+
const before = await generatedPaths(["./(home)/_layout.tsx", "./a/_overrides.tsx", "./a/x.tsx"]);
|
|
169
|
+
const after = await generatedPaths(["./b/_overrides.tsx", "./b/y.tsx"]);
|
|
170
|
+
|
|
171
|
+
expect(before).toHaveLength(2);
|
|
172
|
+
for (const stale of before) expect(await Bun.file(stale).exists()).toBe(false);
|
|
173
|
+
expect(after).toHaveLength(2);
|
|
174
|
+
for (const fresh of after) expect(await Bun.file(fresh).exists()).toBe(true);
|
|
175
|
+
});
|
|
174
176
|
});
|