@akanjs/devkit 3.0.0-beta.2 → 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/README.md +1 -0
- 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 +1 -3
- package/akanApp/akanApp.host.ts +82 -254
- package/akanApp/devHostPolicy.test.ts +10 -34
- package/akanApp/devHostPolicy.ts +12 -98
- package/akanConfig/akanConfig.test.ts +85 -69
- package/akanConfig/akanConfig.ts +69 -153
- package/akanConfig/types.ts +1 -0
- package/akanContext.ts +106 -159
- package/akanMcpContract.ts +28 -73
- package/appSelectionMemory.ts +1 -4
- package/applicationBuildReporter.ts +37 -5
- package/applicationBuildRunner.test.ts +5 -3
- package/applicationBuildRunner.ts +54 -61
- package/applicationReleasePackager.ts +24 -44
- package/applicationTestPreload.ts +3 -12
- package/artifact/implicitRootLayout.test.ts +9 -30
- package/artifact/implicitRootLayout.ts +34 -106
- package/artifact/routeSeedIndex.test.ts +7 -6
- package/artifact/routeSeedIndex.ts +11 -44
- package/biome.base.json +6 -1
- package/biomeBase.ts +2 -5
- package/capacitorApp.test.ts +32 -16
- package/capacitorApp.ts +110 -238
- 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 +70 -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/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 +132 -106
- package/executors.ts +194 -484
- package/fileEditor.ts +9 -37
- package/{getDirname.test.ts → fileSys.test.ts} +5 -3
- package/fileSys.ts +6 -8
- 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 +62 -37
- 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 +6 -19
- package/frontendBuild/devChangePlanner.ts +7 -10
- package/frontendBuild/devGeneratedIndexSync.test.ts +12 -27
- package/frontendBuild/devGeneratedIndexSync.ts +1 -4
- 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 +46 -69
- package/frontendBuild/hmrChangeClassifier.ts +2 -2
- package/frontendBuild/hmrWatcher.test.ts +8 -32
- package/frontendBuild/hmrWatcher.ts +14 -71
- package/frontendBuild/pagesBundleBuilder.ts +4 -29
- package/frontendBuild/precompressArtifacts.ts +5 -18
- package/frontendBuild/routeClientBuilder.ts +23 -46
- package/frontendBuild/routesManifestArtifactSerializer.ts +5 -36
- package/frontendBuild/sourceMtimeIndex.test.ts +41 -97
- 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 +21 -24
- 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 +49 -161
- package/index.ts +5 -17
- package/integration/devResourceProbe.ts +50 -56
- package/integration/devStability.integration.test.ts +47 -194
- 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/gritRules.test.ts +8 -24
- package/lint/no-deep-internal-import.grit +2 -2
- package/lint/no-redeclare-predefined-endpoint.grit +2 -1
- package/linter.test.ts +1 -2
- package/linter.ts +26 -116
- package/mobile/mobileTarget.ts +14 -13
- package/package.json +10 -5
- 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 +197 -285
- 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 +87 -216
- 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 +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/types.ts +1 -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.ts +4 -19
- package/abstractCompactor.ts +0 -112
- package/aiEditor.test.ts +0 -60
- package/aiEditor.ts +0 -387
- 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,5 +1,7 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
import { rm } from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
3
5
|
import { AKAN_BACKEND_MINIFY, AKAN_OPTIONAL_BACKEND_EXTERNALS } from "./applicationBuildRunner";
|
|
4
6
|
|
|
5
7
|
describe("ApplicationBuildRunner", () => {
|
|
@@ -16,8 +18,8 @@ describe("ApplicationBuildRunner", () => {
|
|
|
16
18
|
});
|
|
17
19
|
|
|
18
20
|
test("a bundler that mangles identifiers loses the class name a Logger reads", async () => {
|
|
19
|
-
const dir =
|
|
20
|
-
const entry =
|
|
21
|
+
const dir = path.join(os.tmpdir(), `akan-minify-${Bun.randomUUIDv7()}`);
|
|
22
|
+
const entry = path.join(dir, "entry.ts");
|
|
21
23
|
await Bun.write(
|
|
22
24
|
entry,
|
|
23
25
|
`const serve = () => class Service { readonly name = this.constructor.name; };
|
|
@@ -26,7 +28,7 @@ console.info(new SampleService().name);
|
|
|
26
28
|
`,
|
|
27
29
|
);
|
|
28
30
|
const build = async (minify: Bun.BuildConfig["minify"]) => {
|
|
29
|
-
const result = await Bun.build({ entrypoints: [entry], target: "bun", minify, outdir:
|
|
31
|
+
const result = await Bun.build({ entrypoints: [entry], target: "bun", minify, outdir: path.join(dir, "out") });
|
|
30
32
|
expect(result.success).toBe(true);
|
|
31
33
|
const proc = Bun.spawn([process.execPath, result.outputs[0].path], { stdout: "pipe" });
|
|
32
34
|
return (await new Response(proc.stdout).text()).trim();
|
|
@@ -60,9 +60,8 @@ const SSR_RENDER_EXTERNALS = [
|
|
|
60
60
|
"react-server-dom-webpack/client.browser",
|
|
61
61
|
] as const;
|
|
62
62
|
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
// `minify.keepNames` typechecks and does nothing as of Bun 1.4.2.
|
|
63
|
+
// Mangling renames classes, and `this.constructor.name` names loggers, exceptions and guards; `minify.keepNames`
|
|
64
|
+
// is a no-op as of Bun 1.4.2.
|
|
66
65
|
export const AKAN_BACKEND_MINIFY = { whitespace: true, syntax: true, identifiers: false } as const;
|
|
67
66
|
|
|
68
67
|
export const AKAN_OPTIONAL_BACKEND_EXTERNALS = [
|
|
@@ -77,6 +76,7 @@ export class ApplicationBuildRunner {
|
|
|
77
76
|
#app: App;
|
|
78
77
|
#fast: boolean;
|
|
79
78
|
#reporter?: BuildProgressReporter;
|
|
79
|
+
#spinner?: boolean;
|
|
80
80
|
#startedAt = Date.now();
|
|
81
81
|
#phases: BuildPhaseResult[] = [];
|
|
82
82
|
|
|
@@ -88,18 +88,15 @@ export class ApplicationBuildRunner {
|
|
|
88
88
|
|
|
89
89
|
async build({ spinner = false }: BuildOptions = {}): Promise<BuildResult> {
|
|
90
90
|
// serial build is needed because of Bun.build is unstable for parallel build
|
|
91
|
-
|
|
91
|
+
this.#spinner = spinner;
|
|
92
92
|
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);
|
|
93
|
+
await this.#runPhase("prepare", "Preparing output directory", () => this.#app.prepareCommand("build"));
|
|
94
|
+
if (!this.#fast) await this.#runPhase("typecheck", "Typechecking", () => this.typecheck());
|
|
97
95
|
await this.#runPhase(
|
|
98
96
|
"backend",
|
|
99
97
|
"Compiling backend",
|
|
100
98
|
() => this.#buildBackend(),
|
|
101
99
|
(result) => `${result.entrypoints} entrypoints, ${result.outputs} outputs`,
|
|
102
|
-
phaseOptions,
|
|
103
100
|
);
|
|
104
101
|
await this.#runPhase(
|
|
105
102
|
"ssr",
|
|
@@ -111,14 +108,12 @@ export class ApplicationBuildRunner {
|
|
|
111
108
|
: web.ssr
|
|
112
109
|
? "skipped"
|
|
113
110
|
: "disabled by akan.config.ts web.ssr",
|
|
114
|
-
phaseOptions,
|
|
115
111
|
);
|
|
116
112
|
await this.#runPhase(
|
|
117
113
|
"csr",
|
|
118
114
|
"Building CSR assets",
|
|
119
|
-
async () => (web.csr ? await this.#
|
|
115
|
+
async () => (web.csr ? await new CsrArtifactBuilder(this.#app, "build").build() : null),
|
|
120
116
|
(result) => result?.outputDir ?? (web.csr ? "skipped" : "disabled by akan.config.ts web.csr"),
|
|
121
|
-
phaseOptions,
|
|
122
117
|
);
|
|
123
118
|
await this.#runPhase(
|
|
124
119
|
"assets",
|
|
@@ -128,7 +123,6 @@ export class ApplicationBuildRunner {
|
|
|
128
123
|
result
|
|
129
124
|
? `${result.removed.length} font file(s) dropped, ${ApplicationBuildRunner.formatBytes(result.freedBytes)} freed; ${result.kept.length} kept`
|
|
130
125
|
: "disabled by akan.config.ts assets.pruneFonts",
|
|
131
|
-
phaseOptions,
|
|
132
126
|
);
|
|
133
127
|
await this.#runPhase(
|
|
134
128
|
"compress",
|
|
@@ -138,11 +132,8 @@ export class ApplicationBuildRunner {
|
|
|
138
132
|
result.files > 0
|
|
139
133
|
? `${result.files} files, ${ApplicationBuildRunner.formatBytes(result.inputBytes)} -> gzip ${ApplicationBuildRunner.formatBytes(result.outputBytes)} / br ${ApplicationBuildRunner.formatBytes(result.brotliBytes)}`
|
|
140
134
|
: "no files",
|
|
141
|
-
phaseOptions,
|
|
142
135
|
);
|
|
143
|
-
await this.#runPhase("metadata", "Writing production metadata", () => this.#buildAppMeta()
|
|
144
|
-
spinner,
|
|
145
|
-
});
|
|
136
|
+
await this.#runPhase("metadata", "Writing production metadata", () => this.#buildAppMeta());
|
|
146
137
|
return {
|
|
147
138
|
phases: this.#phases,
|
|
148
139
|
durationMs: Date.now() - this.#startedAt,
|
|
@@ -154,7 +145,7 @@ export class ApplicationBuildRunner {
|
|
|
154
145
|
async typecheck(options: TypecheckOptions = {}) {
|
|
155
146
|
const { clean = false, incremental = true } = options;
|
|
156
147
|
await this.#app.getPageKeys({ refresh: true });
|
|
157
|
-
const { typecheckDir, tsconfigPath } = await this.#writeTypecheckTsconfig(
|
|
148
|
+
const { typecheckDir, tsconfigPath } = await this.#writeTypecheckTsconfig(incremental);
|
|
158
149
|
if (clean) await rm(path.join(typecheckDir, "tsconfig.tsbuildinfo"), { force: true });
|
|
159
150
|
await this.#checkProjectInChildProcess(tsconfigPath);
|
|
160
151
|
}
|
|
@@ -164,19 +155,17 @@ export class ApplicationBuildRunner {
|
|
|
164
155
|
label: string,
|
|
165
156
|
task: () => Promise<T>,
|
|
166
157
|
summarize?: (result: T) => string | undefined,
|
|
167
|
-
options: BuildPhaseRunOptions = {},
|
|
168
158
|
) {
|
|
169
159
|
this.#reporter?.phaseStart?.({ id, label });
|
|
170
160
|
const phaseStartedAt = Date.now();
|
|
171
|
-
const spinner =
|
|
161
|
+
const spinner = this.#spinner
|
|
172
162
|
? new Spinner(label, { prefix: `${BUILD_PHASE_EMOJIS[id]} ${id}` }).start()
|
|
173
163
|
: undefined;
|
|
174
164
|
try {
|
|
175
165
|
const result = await task();
|
|
176
166
|
const phase = { id, label, durationMs: Date.now() - phaseStartedAt, summary: summarize?.(result) };
|
|
177
167
|
this.#phases.push(phase);
|
|
178
|
-
|
|
179
|
-
spinner?.succeed(`${label}${summary}`);
|
|
168
|
+
spinner?.succeed(`${label}${phase.summary ? `: ${phase.summary}` : ""}`);
|
|
180
169
|
this.#reporter?.phaseDone?.(phase);
|
|
181
170
|
return result;
|
|
182
171
|
} catch (error) {
|
|
@@ -191,66 +180,79 @@ export class ApplicationBuildRunner {
|
|
|
191
180
|
await Promise.all([
|
|
192
181
|
this.#app.dist.writeJson("package.json", akanConfig.getProductionPackageJson()),
|
|
193
182
|
this.#app.dist.writeFile(`${this.#app.dist.cwdPath}/Dockerfile`, akanConfig.dockerfile),
|
|
183
|
+
this.#app.dist.writeJson("akan.build.json", {
|
|
184
|
+
buildId: await this.#resolveBuildId(),
|
|
185
|
+
akanVersion: akanConfig.akanVersion,
|
|
186
|
+
builtAt: new Date().toISOString(),
|
|
187
|
+
}),
|
|
194
188
|
]);
|
|
195
189
|
}
|
|
196
190
|
|
|
191
|
+
//* Read back at runtime as `buildId` on the ops channel; a deployment's own AKAN_BUILD_ID still wins over it there.
|
|
192
|
+
async #resolveBuildId() {
|
|
193
|
+
const fromEnv = process.env.AKAN_BUILD_ID?.trim();
|
|
194
|
+
if (fromEnv) return fromEnv;
|
|
195
|
+
const root = this.#app.workspace.workspaceRoot;
|
|
196
|
+
const sha = Bun.spawnSync(["git", "rev-parse", "--short=12", "HEAD"], { cwd: root, stderr: "ignore" });
|
|
197
|
+
if (sha.exitCode !== 0) return null;
|
|
198
|
+
const dirty = Bun.spawnSync(["git", "status", "--porcelain", "--untracked-files=no"], {
|
|
199
|
+
cwd: root,
|
|
200
|
+
stderr: "ignore",
|
|
201
|
+
});
|
|
202
|
+
const suffix = dirty.exitCode === 0 && dirty.stdout.toString().trim() ? "-dirty" : "";
|
|
203
|
+
return `${sha.stdout.toString().trim()}${suffix}`;
|
|
204
|
+
}
|
|
205
|
+
|
|
197
206
|
async #buildBackend() {
|
|
198
|
-
const
|
|
199
|
-
const backendExternals = [
|
|
200
|
-
...new Set([...akanConfig.externalLibs, ...SSR_RENDER_EXTERNALS, ...AKAN_OPTIONAL_BACKEND_EXTERNALS]),
|
|
201
|
-
];
|
|
202
|
-
const { web } = akanConfig;
|
|
207
|
+
const { externalLibs, web } = await this.#app.getConfig();
|
|
203
208
|
const backendEntryPoints = [`${this.#app.cwdPath}/main.ts`, `${this.#app.cwdPath}/server.ts`];
|
|
204
209
|
for (const entrypoint of backendEntryPoints) {
|
|
205
210
|
if (!(await Bun.file(entrypoint).exists())) throw new Error(`Backend entrypoint not found: ${entrypoint}`);
|
|
206
211
|
}
|
|
207
|
-
const
|
|
208
|
-
entrypoints: backendEntryPoints,
|
|
212
|
+
const sharedConfig = {
|
|
209
213
|
outdir: this.#app.dist.cwdPath,
|
|
210
214
|
target: "bun",
|
|
211
215
|
minify: AKAN_BACKEND_MINIFY,
|
|
212
216
|
naming: { entry: "[name].[ext]", chunk: "chunk-[hash].[ext]" },
|
|
217
|
+
// `akan build` must embed production react-server-dom regardless of the shell's NODE_ENV.
|
|
213
218
|
define: { "process.env.NODE_ENV": JSON.stringify("production") },
|
|
214
|
-
|
|
215
|
-
}
|
|
219
|
+
} satisfies Omit<Bun.BuildConfig, "entrypoints">;
|
|
220
|
+
const backendConfig = { ...sharedConfig, plugins: [this.#createExternalSpecifiersPlugin(externalLibs)] };
|
|
221
|
+
//* Built apart so main.js keeps its own module copies; splitting moves lazy vendor `import()`s out of the boot parse.
|
|
222
|
+
const [mainResult, serverResult] = [
|
|
223
|
+
await this.#buildOrThrow("backend", { ...backendConfig, entrypoints: [backendEntryPoints[0]] }),
|
|
224
|
+
await this.#buildOrThrow("backend", { ...backendConfig, entrypoints: [backendEntryPoints[1]], splitting: true }),
|
|
225
|
+
];
|
|
216
226
|
// Nothing spawns the RSC worker without SSR, so an api-only image does not carry it.
|
|
217
227
|
const rscWorkerResult = web.ssr
|
|
218
228
|
? await this.#buildOrThrow("rsc-worker", {
|
|
229
|
+
...backendConfig,
|
|
219
230
|
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
231
|
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
232
|
})
|
|
229
233
|
: null;
|
|
230
234
|
const consoleRuntimeResult = await this.#buildOrThrow("console-runtime", {
|
|
235
|
+
...sharedConfig,
|
|
231
236
|
entrypoints: [this.#resolveConsoleRuntimeBuildEntry()],
|
|
232
|
-
outdir: this.#app.dist.cwdPath,
|
|
233
|
-
target: "bun",
|
|
234
|
-
minify: AKAN_BACKEND_MINIFY,
|
|
235
237
|
naming: { entry: "console-runtime.[ext]", chunk: "chunk-[hash].[ext]" },
|
|
236
|
-
define: { "process.env.NODE_ENV": JSON.stringify("production") },
|
|
237
238
|
});
|
|
238
239
|
await this.#writeConsoleShim();
|
|
240
|
+
const results = [mainResult, serverResult, rscWorkerResult, consoleRuntimeResult];
|
|
239
241
|
return {
|
|
240
242
|
entrypoints: backendEntryPoints.length + (rscWorkerResult ? 2 : 1),
|
|
241
|
-
outputs:
|
|
242
|
-
backendResult.outputs.length + (rscWorkerResult?.outputs.length ?? 0) + consoleRuntimeResult.outputs.length + 1,
|
|
243
|
+
outputs: results.reduce((sum, result) => sum + (result?.outputs.length ?? 0), 1),
|
|
243
244
|
};
|
|
244
245
|
}
|
|
245
246
|
|
|
246
247
|
async #writeConsoleShim() {
|
|
247
248
|
await Bun.write(
|
|
248
249
|
path.join(this.#app.dist.cwdPath, "console.js"),
|
|
249
|
-
`
|
|
250
|
-
|
|
250
|
+
`process.env.AKAN_COMMAND_TYPE = "console";
|
|
251
|
+
const { cnst, db, dict, option, server, sig, srv } = await import("./server.js");
|
|
252
|
+
const { assertAkanConsoleAllowed, startAkanConsole } = await import("./console-runtime.js");
|
|
251
253
|
|
|
252
254
|
const run = async () => {
|
|
253
|
-
assertAkanConsoleAllowed(
|
|
255
|
+
assertAkanConsoleAllowed();
|
|
254
256
|
await server.start({ listen: false, web: false });
|
|
255
257
|
try {
|
|
256
258
|
await startAkanConsole(server, { globals: { cnst, db, dict, option, sig, srv } });
|
|
@@ -283,10 +285,6 @@ void run().catch((error) => {
|
|
|
283
285
|
}
|
|
284
286
|
}
|
|
285
287
|
|
|
286
|
-
async #buildCsr() {
|
|
287
|
-
return await new CsrArtifactBuilder(this.#app, "build").build();
|
|
288
|
-
}
|
|
289
|
-
|
|
290
288
|
async #buildSsr() {
|
|
291
289
|
const pageKeys = await this.#app.getPageKeys();
|
|
292
290
|
if (pageKeys.length === 0) {
|
|
@@ -298,7 +296,7 @@ void run().catch((error) => {
|
|
|
298
296
|
return { base, allRoutes };
|
|
299
297
|
}
|
|
300
298
|
|
|
301
|
-
async #writeTypecheckTsconfig(
|
|
299
|
+
async #writeTypecheckTsconfig(incremental: boolean) {
|
|
302
300
|
const typecheckDir = path.join(this.#app.cwdPath, ".akan", "typecheck");
|
|
303
301
|
await mkdir(typecheckDir, { recursive: true });
|
|
304
302
|
//* TypeScript's `include` globs do not cross a symlink, so synced lib pages need their real path.
|
|
@@ -367,11 +365,10 @@ void run().catch((error) => {
|
|
|
367
365
|
return result;
|
|
368
366
|
}
|
|
369
367
|
|
|
370
|
-
#createExternalSpecifiersPlugin(
|
|
371
|
-
const
|
|
372
|
-
const escaped =
|
|
368
|
+
#createExternalSpecifiersPlugin(externalLibs: readonly string[]): BunPlugin {
|
|
369
|
+
const specifiers = new Set([...externalLibs, ...SSR_RENDER_EXTERNALS, ...AKAN_OPTIONAL_BACKEND_EXTERNALS]);
|
|
370
|
+
const escaped = [...specifiers].map((specifier) => specifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
373
371
|
const filter = new RegExp(`^(${escaped.join("|")})(?:/.*)?$`);
|
|
374
|
-
|
|
375
372
|
return {
|
|
376
373
|
name: "akan-backend-externalize-specifiers",
|
|
377
374
|
setup(build) {
|
|
@@ -380,9 +377,5 @@ void run().catch((error) => {
|
|
|
380
377
|
};
|
|
381
378
|
}
|
|
382
379
|
|
|
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
|
-
}
|
|
380
|
+
static formatBytes = FontPruner.formatBytes;
|
|
388
381
|
}
|
|
@@ -63,13 +63,7 @@ export class ApplicationReleasePackager {
|
|
|
63
63
|
await rm(`${buildRoot}/frontend/.next`, { recursive: true, force: true });
|
|
64
64
|
|
|
65
65
|
const releaseRoot = this.#app.workspace.workspaceRoot;
|
|
66
|
-
|
|
67
|
-
// which GNU tar would interpret as a remote "host:file" spec.
|
|
68
|
-
const releaseArchivePath = path
|
|
69
|
-
.relative(releaseRoot, `${releaseRoot}/releases/builds/${this.#app.name}-release.tar.gz`)
|
|
70
|
-
.split(path.sep)
|
|
71
|
-
.join("/");
|
|
72
|
-
await this.#app.workspace.spawn("tar", ["-zcf", releaseArchivePath, "-C", buildRoot, "./"], { cwd: releaseRoot });
|
|
66
|
+
await this.#tar(releaseRoot, `${releaseRoot}/releases/builds/${this.#app.name}-release.tar.gz`, buildRoot);
|
|
73
67
|
await this.#writeCsrZipIfPresent();
|
|
74
68
|
return { platformVersion };
|
|
75
69
|
}
|
|
@@ -112,26 +106,19 @@ export class ApplicationReleasePackager {
|
|
|
112
106
|
await this.#writeSourceTsconfig(sourceRoot, libDeps);
|
|
113
107
|
await Bun.write(`${sourceRoot}/README.md`, readme);
|
|
114
108
|
const sourceCwd = this.#app.workspace.cwdPath;
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
await this.#app.workspace.spawn("tar", ["-zcf",
|
|
109
|
+
await this.#tar(sourceCwd, `${sourceCwd}/releases/sources/${this.#app.name}-source.tar.gz`, sourceRoot);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async #tar(cwd: string, archivePath: string, sourceDir: string) {
|
|
113
|
+
// Relative to cwd so tar never sees a Windows drive letter ("C:\..."), which GNU tar reads as a remote "host:file".
|
|
114
|
+
const relativeArchivePath = path.relative(cwd, archivePath).split(path.sep).join("/");
|
|
115
|
+
await this.#app.workspace.spawn("tar", ["-zcf", relativeArchivePath, "-C", sourceDir, "./"], { cwd });
|
|
122
116
|
}
|
|
123
117
|
|
|
124
118
|
async #resetSourceRoot(sourceRoot: string): Promise<void> {
|
|
125
|
-
if (await FileSys.dirExists(sourceRoot))
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
try {
|
|
129
|
-
await rm(sourceRoot, { recursive: true, force: true });
|
|
130
|
-
} catch {
|
|
131
|
-
//
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
119
|
+
if (await FileSys.dirExists(sourceRoot))
|
|
120
|
+
for (let attempt = 0; attempt < 3; attempt++)
|
|
121
|
+
await rm(sourceRoot, { recursive: true, force: true }).catch(() => undefined);
|
|
135
122
|
await mkdir(sourceRoot, { recursive: true });
|
|
136
123
|
}
|
|
137
124
|
|
|
@@ -147,6 +134,17 @@ export class ApplicationReleasePackager {
|
|
|
147
134
|
await Bun.write(`${sourceRoot}/tsconfig.json`, JSON.stringify(tsconfig, null, 2));
|
|
148
135
|
}
|
|
149
136
|
|
|
137
|
+
static #envSetup = ` cat <<EOF >> .env
|
|
138
|
+
# ENV For Server => debug | debug.local | develop | develop.local | main | main.local
|
|
139
|
+
SERVER_ENV=debug.local
|
|
140
|
+
# Run Mode For Server => federation | batch | all
|
|
141
|
+
SERVER_MODE=federation
|
|
142
|
+
# ENV For Client => debug | debug.local | develop | develop.local | main | main.local
|
|
143
|
+
AKAN_PUBLIC_CLIENT_ENV=debug.local
|
|
144
|
+
ANALYZE=false
|
|
145
|
+
EOF
|
|
146
|
+
`;
|
|
147
|
+
|
|
150
148
|
#confidentialReadme(): string {
|
|
151
149
|
return `# ${this.#app.name}
|
|
152
150
|
본 프로젝트의 소스코드 및 관련자료는 모두 비밀정보로 관리됩니다.
|
|
@@ -157,16 +155,7 @@ export class ApplicationReleasePackager {
|
|
|
157
155
|
bun install -g bun
|
|
158
156
|
bun i
|
|
159
157
|
|
|
160
|
-
|
|
161
|
-
# ENV For Server => debug | debug.local | develop | develop.local | main | main.local
|
|
162
|
-
SERVER_ENV=debug.local
|
|
163
|
-
# Run Mode For Server => federation | batch | all
|
|
164
|
-
SERVER_MODE=federation
|
|
165
|
-
# ENV For Client => debug | debug.local | develop | develop.local | main | main.local
|
|
166
|
-
AKAN_PUBLIC_CLIENT_ENV=debug.local
|
|
167
|
-
ANALYZE=false
|
|
168
|
-
EOF
|
|
169
|
-
|
|
158
|
+
${ApplicationReleasePackager.#envSetup}
|
|
170
159
|
akn start-backend ${this.#app.name}
|
|
171
160
|
# or akn start-frontend ${this.#app.name}, etc
|
|
172
161
|
\`\`\`
|
|
@@ -185,16 +174,7 @@ export class ApplicationReleasePackager {
|
|
|
185
174
|
## Get Started
|
|
186
175
|
Run the code below.
|
|
187
176
|
\`\`\`
|
|
188
|
-
|
|
189
|
-
# ENV For Server => debug | debug.local | develop | develop.local | main | main.local
|
|
190
|
-
SERVER_ENV=debug.local
|
|
191
|
-
# Run Mode For Server => federation | batch | all
|
|
192
|
-
SERVER_MODE=federation
|
|
193
|
-
# ENV For Client => debug | debug.local | develop | develop.local | main | main.local
|
|
194
|
-
AKAN_PUBLIC_CLIENT_ENV=debug.local
|
|
195
|
-
ANALYZE=false
|
|
196
|
-
EOF
|
|
197
|
-
|
|
177
|
+
${ApplicationReleasePackager.#envSetup}
|
|
198
178
|
akan start ${this.#app.name}
|
|
199
179
|
\`\`\`
|
|
200
180
|
|
|
@@ -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;");
|