@akanjs/devkit 3.0.0-beta.19 → 3.0.0-beta.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/abstractDoc.test.ts +6 -15
- package/abstractDoc.ts +1 -4
- package/agentsIndex.test.ts +4 -7
- package/agentsIndex.ts +6 -22
- package/akanApp/BackendImportGraph.test.ts +8 -22
- package/akanApp/BackendImportGraph.ts +1 -20
- package/akanApp/akanApp.host.test.ts +1 -3
- package/akanApp/akanApp.host.ts +64 -250
- package/akanApp/devHostPolicy.test.ts +10 -34
- package/akanApp/devHostPolicy.ts +12 -98
- package/akanConfig/akanConfig.test.ts +30 -39
- package/akanConfig/akanConfig.ts +40 -136
- package/akanContext.ts +106 -159
- package/akanMcpContract.ts +28 -73
- package/appSelectionMemory.ts +1 -4
- package/applicationBuildReporter.ts +37 -5
- package/applicationBuildRunner.ts +25 -59
- package/applicationReleasePackager.ts +24 -44
- package/applicationTestPreload.ts +3 -12
- package/artifact/implicitRootLayout.test.ts +3 -19
- package/artifact/implicitRootLayout.ts +34 -91
- package/artifact/routeSeedIndex.ts +11 -44
- package/biomeBase.ts +2 -5
- package/capacitorApp.test.ts +4 -14
- package/capacitorApp.ts +86 -235
- package/cloud/cloudApi.test.ts +202 -0
- package/cloud/cloudApi.ts +33 -57
- package/cloud/configLock.test.ts +64 -0
- package/cloud/configLock.ts +96 -0
- package/cloud/constants.ts +3 -2
- package/cloud/globalConfig.ts +64 -31
- package/codeAgent/agent/AkanCodeServices.ts +15 -57
- package/codeAgent/agent/AkanContextFiles.ts +5 -28
- package/codeAgent/agent/CodeAgent.ts +33 -108
- package/codeAgent/agent/CodeAgentAsks.ts +7 -30
- package/codeAgent/agent/CodeAgentEventMapper.ts +9 -52
- package/codeAgent/agent/CodeAgentGate.ts +8 -34
- package/codeAgent/agent/CodeAgentProxy.ts +4 -13
- package/codeAgent/agent/CodeAgentRpcHost.ts +3 -16
- package/codeAgent/agent/CodeAgentRpcListener.ts +2 -10
- package/codeAgent/agent/CodeAgentStreamPrinter.ts +4 -15
- package/codeAgent/agent/CodeAgentSuspended.ts +1 -8
- package/codeAgent/agent/CodeAgentUi.ts +2 -7
- package/codeAgent/agent/CodeMailbox.ts +7 -34
- package/codeAgent/agent/CodeSessionFork.ts +4 -19
- package/codeAgent/agent/CodeSessionIndex.ts +5 -31
- package/codeAgent/agent/SubagentPool.ts +48 -58
- package/codeAgent/agent/akanCodeModel.ts +5 -39
- package/codeAgent/agent/akanCodePaths.ts +4 -24
- package/codeAgent/agent/akanContextFiles.test.ts +0 -2
- package/codeAgent/agent/akanSystemPrompt.ts +1 -9
- package/codeAgent/agent/codeAgent.test.ts +178 -0
- package/codeAgent/agent/codeAgentContract.test.ts +5 -28
- package/codeAgent/agent/codeMailbox.test.ts +0 -4
- package/codeAgent/agent/codeSessionFork.test.ts +0 -2
- package/codeAgent/agent/codeSessionIndex.test.ts +9 -4
- package/codeAgent/agent/consoleToStderr.ts +2 -12
- package/codeAgent/agent/jsonLines.ts +29 -0
- package/codeAgent/agent/stringArg.ts +5 -0
- package/codeAgent/feedback/DevLogFeedback.ts +3 -18
- package/codeAgent/feedback/PreviewChrome.ts +5 -29
- package/codeAgent/feedback/PreviewFeedback.ts +2 -9
- package/codeAgent/feedback/PreviewView.ts +3 -15
- package/codeAgent/feedback/TurnFeedback.ts +2 -17
- package/codeAgent/feedback/VerifyFeedback.ts +1 -7
- package/codeAgent/feedback/previewChrome.test.ts +1 -1
- package/codeAgent/index.ts +2 -10
- package/codeAgent/skills/akan-code/SKILL.md +12 -4
- package/codeAgent/tools/AkanCli.ts +4 -16
- package/codeAgent/tools/AkanCodePlugins.ts +21 -43
- package/codeAgent/tools/AkanEditScope.ts +2 -12
- package/codeAgent/tools/AkanEnvKeys.ts +3 -11
- package/codeAgent/tools/AkanToolPack.ts +6 -28
- package/codeAgent/tools/AkanVerifier.ts +1 -8
- package/codeAgent/tools/AskToolPack.ts +3 -15
- package/codeAgent/tools/MailToolPack.ts +0 -9
- package/codeAgent/tools/McpClient.ts +3 -24
- package/codeAgent/tools/McpOAuth.ts +6 -41
- package/codeAgent/tools/McpOAuthLoopback.ts +2 -15
- package/codeAgent/tools/McpServerConfig.ts +7 -26
- package/codeAgent/tools/McpSignIn.ts +46 -49
- package/codeAgent/tools/McpTokenStore.ts +6 -17
- package/codeAgent/tools/McpToolPack.ts +107 -35
- package/codeAgent/tools/SessionToolPack.ts +1 -11
- package/codeAgent/tools/WebToolPack.ts +1 -7
- package/codeAgent/tools/akanCodePlugins.test.ts +231 -63
- package/codeAgent/tools/askToolPack.test.ts +0 -3
- package/codeAgent/tools/mcpOAuth.test.ts +315 -27
- package/codeAgent/tools/mcpServerConfig.test.ts +21 -14
- package/codegenLock.test.ts +4 -13
- package/codegenLock.ts +3 -17
- package/commandDecorators/argMeta.ts +1 -5
- package/commandDecorators/camelToKebabCase.ts +1 -0
- package/commandDecorators/command.ts +44 -102
- package/commandDecorators/commandBuilder.ts +30 -61
- package/commandDecorators/commandDecorators.test.ts +1 -9
- package/commandDecorators/dependencyBuilder.ts +10 -1
- package/commandDecorators/helpFormatter.ts +66 -144
- package/commandDecorators/index.ts +0 -2
- package/commandDecorators/targetMeta.ts +9 -7
- package/createTunnel.ts +7 -14
- package/dependencyScanner.test.ts +4 -16
- package/dependencyScanner.ts +27 -78
- package/devkitUtils.test.ts +47 -19
- package/executors.test.ts +114 -60
- package/executors.ts +165 -484
- package/fileEditor.ts +9 -37
- package/{getDirname.test.ts → fileSys.test.ts} +1 -1
- package/fileSys.ts +6 -8
- package/formSetterScanner.ts +3 -16
- package/frontendBuild/allRoutesBuilder.ts +7 -21
- package/frontendBuild/autoImportSync.test.ts +14 -38
- package/frontendBuild/autoImportSync.ts +15 -67
- package/frontendBuild/buildRouteClient.test.ts +10 -36
- package/frontendBuild/bundleDefine.ts +12 -0
- package/frontendBuild/clientBuildTypes.ts +5 -36
- package/frontendBuild/clientEntriesBundler.ts +9 -40
- package/frontendBuild/clientEntryDiscovery.ts +6 -41
- package/frontendBuild/csrArtifactBuilder.ts +6 -25
- package/frontendBuild/cssCandidateCache.test.ts +0 -6
- package/frontendBuild/cssCandidateCache.ts +6 -41
- package/frontendBuild/cssCompiler.ts +29 -78
- package/frontendBuild/cssImportResolver.ts +5 -18
- package/frontendBuild/devChangePlanner.ts +7 -10
- package/frontendBuild/devGeneratedIndexSync.test.ts +12 -27
- package/frontendBuild/devGeneratedIndexSync.ts +1 -4
- package/frontendBuild/fontOptimizer.test.ts +5 -13
- package/frontendBuild/fontOptimizer.ts +15 -72
- package/frontendBuild/fontPruner.test.ts +5 -10
- package/frontendBuild/fontPruner.ts +5 -20
- package/frontendBuild/frontendBuild.test.ts +27 -52
- package/frontendBuild/hmrChangeClassifier.ts +2 -2
- package/frontendBuild/hmrWatcher.test.ts +7 -31
- package/frontendBuild/hmrWatcher.ts +14 -71
- package/frontendBuild/pagesBundleBuilder.ts +4 -29
- package/frontendBuild/precompressArtifacts.ts +5 -18
- package/frontendBuild/routeClientBuilder.ts +7 -47
- package/frontendBuild/routesManifestArtifactSerializer.ts +5 -36
- package/frontendBuild/sourceMtimeIndex.test.ts +37 -94
- package/frontendBuild/sourceMtimeIndex.ts +19 -130
- package/frontendBuild/ssrBaseArtifactBuilder.ts +2 -8
- package/frontendBuild/styleContract.ts +1 -8
- package/frontendBuild/themeValidator.ts +4 -26
- package/frontendBuild/watchRootResolver.test.ts +23 -42
- package/frontendBuild/watchRootResolver.ts +5 -27
- package/getRelatedCnsts.ts +12 -92
- package/incrementalBuilder/buildBatch.proc.ts +22 -57
- package/incrementalBuilder/buildBatchProtocol.ts +8 -36
- package/incrementalBuilder/buildBatchRunner.test.ts +34 -0
- package/incrementalBuilder/buildBatchRunner.ts +10 -31
- package/incrementalBuilder/builderChannel.test.ts +24 -49
- package/incrementalBuilder/builderChannel.ts +6 -37
- package/incrementalBuilder/builderRequestRouter.test.ts +0 -8
- package/incrementalBuilder/builderRequestRouter.ts +5 -24
- package/incrementalBuilder/devWatchBatch.test.ts +14 -18
- package/incrementalBuilder/incrementalBuilder.host.test.ts +18 -55
- package/incrementalBuilder/incrementalBuilder.host.ts +18 -76
- package/incrementalBuilder/incrementalBuilder.proc.test.ts +81 -0
- package/incrementalBuilder/incrementalBuilder.proc.ts +48 -161
- package/index.ts +3 -15
- package/integration/devResourceProbe.ts +50 -56
- package/integration/devStability.integration.test.ts +47 -194
- package/integration/devStabilityHarness.test.ts +9 -29
- package/integration/devStabilityHarness.ts +65 -330
- package/integration/ssrMemoryProbe.ts +41 -167
- package/libSource.test.ts +4 -15
- package/libSource.ts +19 -31
- package/lint/gritRules.test.ts +6 -23
- package/linter.test.ts +1 -2
- package/linter.ts +22 -115
- package/mobile/mobileTarget.ts +14 -13
- package/package.json +2 -2
- package/packageExports.test.ts +3 -18
- package/packageExportsMap.ts +3 -19
- package/piDependencyPins.test.ts +3 -14
- package/platformTest/IdleWatchedProcess.test.ts +5 -13
- package/platformTest/IdleWatchedProcess.ts +1 -5
- package/platformTest/LinuxDockerTarget.ts +20 -35
- package/platformTest/PlatformTestRun.test.ts +5 -9
- package/platformTest/PlatformTestTarget.ts +24 -2
- package/platformTest/TestSnapshot.test.ts +6 -14
- package/platformTest/TestSnapshot.ts +1 -5
- package/platformTest/WindowsSshTarget.ts +6 -22
- package/prompter.ts +3 -6
- package/qualityScanner.test.ts +6 -24
- package/qualityScanner.ts +196 -284
- package/recipeScanner.test.ts +7 -12
- package/recipeScanner.ts +9 -38
- package/repoIdentity.ts +2 -9
- package/routeSourceValidator.ts +7 -30
- package/scanInfo.ts +82 -214
- package/selectModel.ts +1 -2
- package/semver.ts +3 -5
- package/slicePlanner.test.ts +6 -24
- package/slicePlanner.ts +11 -32
- package/spinner.test.ts +3 -8
- package/spinner.ts +4 -16
- package/sshTunnel.ts +1 -6
- package/ssrScanner.test.ts +1 -6
- package/ssrScanner.ts +103 -113
- package/subspace.test.ts +6 -25
- package/subspace.ts +31 -109
- package/subspaceConfig.ts +3 -11
- package/testHelpers.ts +49 -34
- package/transforms/asyncDefaultExportDetector.ts +3 -11
- package/transforms/barrelAnalyzer.ts +43 -135
- package/transforms/barrelImportsPlugin.ts +38 -175
- package/transforms/externalizeFrameworkPlugin.test.ts +39 -118
- package/transforms/externalizeFrameworkPlugin.ts +13 -87
- package/transforms/moduleSyntax.ts +32 -0
- package/transforms/rscUseClientTransform.ts +35 -20
- package/transforms/transforms.test.ts +153 -44
- package/transforms/tsconfigPackageResolver.test.ts +6 -24
- package/transforms/useClientBundlePlugin.ts +2 -22
- package/typeChecker.ts +7 -63
- package/uploadRelease.ts +10 -25
- package/workflow/artifacts.ts +18 -0
- package/workflow/executor.test.ts +1 -12
- package/workflow/executor.ts +85 -146
- package/workflow/index.ts +0 -1
- package/workflow/moduleIndex.test.ts +4 -15
- package/workflow/moduleIndex.ts +9 -49
- package/workflow/plan.ts +18 -25
- package/workflow/render.ts +80 -96
- package/workflow/source.ts +33 -56
- package/workflow/sourceAst.ts +50 -0
- package/workspaceLayout.ts +4 -19
- package/commandDecorators/commandMeta.ts +0 -7
- package/commandDecorators/types.ts +0 -10
- package/getDirname.ts +0 -4
- package/local/optimize-resource/ipcprobe/child.ts +0 -1
- package/local/optimize-resource/ipcprobe/parent.ts +0 -11
- package/workflow/primitive.ts +0 -59
|
@@ -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) {
|
|
@@ -215,57 +204,43 @@ export class ApplicationBuildRunner {
|
|
|
215
204
|
}
|
|
216
205
|
|
|
217
206
|
async #buildBackend() {
|
|
218
|
-
const
|
|
219
|
-
const backendExternals = [
|
|
220
|
-
...new Set([...akanConfig.externalLibs, ...SSR_RENDER_EXTERNALS, ...AKAN_OPTIONAL_BACKEND_EXTERNALS]),
|
|
221
|
-
];
|
|
222
|
-
const { web } = akanConfig;
|
|
207
|
+
const { externalLibs, web } = await this.#app.getConfig();
|
|
223
208
|
const backendEntryPoints = [`${this.#app.cwdPath}/main.ts`, `${this.#app.cwdPath}/server.ts`];
|
|
224
209
|
for (const entrypoint of backendEntryPoints) {
|
|
225
210
|
if (!(await Bun.file(entrypoint).exists())) throw new Error(`Backend entrypoint not found: ${entrypoint}`);
|
|
226
211
|
}
|
|
227
|
-
const
|
|
212
|
+
const sharedConfig = {
|
|
228
213
|
outdir: this.#app.dist.cwdPath,
|
|
229
214
|
target: "bun",
|
|
230
215
|
minify: AKAN_BACKEND_MINIFY,
|
|
231
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.
|
|
232
218
|
define: { "process.env.NODE_ENV": JSON.stringify("production") },
|
|
233
|
-
plugins: backendExternals.length > 0 ? [this.#createExternalSpecifiersPlugin(backendExternals)] : [],
|
|
234
219
|
} satisfies Omit<Bun.BuildConfig, "entrypoints">;
|
|
235
|
-
|
|
236
|
-
//*
|
|
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.
|
|
237
222
|
const [mainResult, serverResult] = [
|
|
238
223
|
await this.#buildOrThrow("backend", { ...backendConfig, entrypoints: [backendEntryPoints[0]] }),
|
|
239
224
|
await this.#buildOrThrow("backend", { ...backendConfig, entrypoints: [backendEntryPoints[1]], splitting: true }),
|
|
240
225
|
];
|
|
241
|
-
const backendResult = { outputs: [...mainResult.outputs, ...serverResult.outputs] };
|
|
242
226
|
// Nothing spawns the RSC worker without SSR, so an api-only image does not carry it.
|
|
243
227
|
const rscWorkerResult = web.ssr
|
|
244
228
|
? await this.#buildOrThrow("rsc-worker", {
|
|
229
|
+
...backendConfig,
|
|
245
230
|
entrypoints: [this.#resolveRscWorkerBuildEntry()],
|
|
246
|
-
outdir: this.#app.dist.cwdPath,
|
|
247
|
-
target: "bun",
|
|
248
|
-
minify: AKAN_BACKEND_MINIFY,
|
|
249
|
-
naming: { entry: "[name].[ext]", chunk: "chunk-[hash].[ext]" },
|
|
250
231
|
conditions: ["react-server"],
|
|
251
|
-
// `akan build` must embed production react-server-dom regardless of the shell's NODE_ENV.
|
|
252
|
-
define: { "process.env.NODE_ENV": JSON.stringify("production") },
|
|
253
|
-
plugins: backendExternals.length > 0 ? [this.#createExternalSpecifiersPlugin(backendExternals)] : [],
|
|
254
232
|
})
|
|
255
233
|
: null;
|
|
256
234
|
const consoleRuntimeResult = await this.#buildOrThrow("console-runtime", {
|
|
235
|
+
...sharedConfig,
|
|
257
236
|
entrypoints: [this.#resolveConsoleRuntimeBuildEntry()],
|
|
258
|
-
outdir: this.#app.dist.cwdPath,
|
|
259
|
-
target: "bun",
|
|
260
|
-
minify: AKAN_BACKEND_MINIFY,
|
|
261
237
|
naming: { entry: "console-runtime.[ext]", chunk: "chunk-[hash].[ext]" },
|
|
262
|
-
define: { "process.env.NODE_ENV": JSON.stringify("production") },
|
|
263
238
|
});
|
|
264
239
|
await this.#writeConsoleShim();
|
|
240
|
+
const results = [mainResult, serverResult, rscWorkerResult, consoleRuntimeResult];
|
|
265
241
|
return {
|
|
266
242
|
entrypoints: backendEntryPoints.length + (rscWorkerResult ? 2 : 1),
|
|
267
|
-
outputs:
|
|
268
|
-
backendResult.outputs.length + (rscWorkerResult?.outputs.length ?? 0) + consoleRuntimeResult.outputs.length + 1,
|
|
243
|
+
outputs: results.reduce((sum, result) => sum + (result?.outputs.length ?? 0), 1),
|
|
269
244
|
};
|
|
270
245
|
}
|
|
271
246
|
|
|
@@ -310,10 +285,6 @@ void run().catch((error) => {
|
|
|
310
285
|
}
|
|
311
286
|
}
|
|
312
287
|
|
|
313
|
-
async #buildCsr() {
|
|
314
|
-
return await new CsrArtifactBuilder(this.#app, "build").build();
|
|
315
|
-
}
|
|
316
|
-
|
|
317
288
|
async #buildSsr() {
|
|
318
289
|
const pageKeys = await this.#app.getPageKeys();
|
|
319
290
|
if (pageKeys.length === 0) {
|
|
@@ -325,7 +296,7 @@ void run().catch((error) => {
|
|
|
325
296
|
return { base, allRoutes };
|
|
326
297
|
}
|
|
327
298
|
|
|
328
|
-
async #writeTypecheckTsconfig(
|
|
299
|
+
async #writeTypecheckTsconfig(incremental: boolean) {
|
|
329
300
|
const typecheckDir = path.join(this.#app.cwdPath, ".akan", "typecheck");
|
|
330
301
|
await mkdir(typecheckDir, { recursive: true });
|
|
331
302
|
//* TypeScript's `include` globs do not cross a symlink, so synced lib pages need their real path.
|
|
@@ -394,11 +365,10 @@ void run().catch((error) => {
|
|
|
394
365
|
return result;
|
|
395
366
|
}
|
|
396
367
|
|
|
397
|
-
#createExternalSpecifiersPlugin(
|
|
398
|
-
const
|
|
399
|
-
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, "\\$&"));
|
|
400
371
|
const filter = new RegExp(`^(${escaped.join("|")})(?:/.*)?$`);
|
|
401
|
-
|
|
402
372
|
return {
|
|
403
373
|
name: "akan-backend-externalize-specifiers",
|
|
404
374
|
setup(build) {
|
|
@@ -407,9 +377,5 @@ void run().catch((error) => {
|
|
|
407
377
|
};
|
|
408
378
|
}
|
|
409
379
|
|
|
410
|
-
static formatBytes
|
|
411
|
-
if (bytes < 1024) return `${bytes}B`;
|
|
412
|
-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
|
|
413
|
-
return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
|
|
414
|
-
}
|
|
380
|
+
static formatBytes = FontPruner.formatBytes;
|
|
415
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 () => {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { mkdir } from "node:fs/promises";
|
|
2
1
|
import path from "node:path";
|
|
3
2
|
import type { App } from "../commandDecorators";
|
|
4
3
|
import { AsyncDefaultExportDetector } from "../transforms/asyncDefaultExportDetector";
|
|
@@ -11,23 +10,17 @@ export interface PageEntry {
|
|
|
11
10
|
|
|
12
11
|
const LAYOUT_KEY_RE = /^\.\/(.+\/)?_layout\.(tsx|ts|jsx|js)$/;
|
|
13
12
|
|
|
14
|
-
async function appHasStModule(appCwdPath: string): Promise<boolean> {
|
|
15
|
-
return Bun.file(path.join(appCwdPath, "lib", "st.ts")).exists();
|
|
16
|
-
}
|
|
17
|
-
|
|
18
13
|
const IMPLICIT_LAYOUT_DIR = path.join(".akan", "generated", "root-layouts");
|
|
19
14
|
const IMPLICIT_DICT_DIR = path.join(".akan", "generated", "dict");
|
|
20
15
|
const IMPLICIT_OVERRIDES_DIR = path.join(".akan", "generated", "overrides");
|
|
21
16
|
const OVERRIDES_KEY_RE = /^\.\/(.+\/)?_overrides\.(tsx|ts|jsx|js)$/;
|
|
22
17
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
* resolve it — and the author never writes `"use client"`.
|
|
30
|
-
*/
|
|
18
|
+
const importSpecifier = (fromAbsPath: string, toAbsPath: string) => {
|
|
19
|
+
const rel = path.relative(path.dirname(fromAbsPath), toAbsPath).split(path.sep).join("/");
|
|
20
|
+
return rel.startsWith(".") ? rel : `./${rel}`;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// UiOverrideProvider is a client component, so `_overrides.tsx` mounts through a generated "use client" layout.
|
|
31
24
|
async function writeGeneratedOverridesLayoutFile(opts: {
|
|
32
25
|
appCwdPath: string;
|
|
33
26
|
key: string;
|
|
@@ -35,12 +28,10 @@ async function writeGeneratedOverridesLayoutFile(opts: {
|
|
|
35
28
|
}): Promise<string> {
|
|
36
29
|
const filename = `${opts.key.replace(/^\.\//, "").replace(/[^a-zA-Z0-9]+/g, "_")}.tsx`;
|
|
37
30
|
const absPath = path.join(path.resolve(opts.appCwdPath), IMPLICIT_OVERRIDES_DIR, filename);
|
|
38
|
-
const userRel = path.relative(path.dirname(absPath), opts.userAbsPath).split(path.sep).join("/");
|
|
39
|
-
const userSpecifier = userRel.startsWith(".") ? userRel : `./${userRel}`;
|
|
40
31
|
const source = `"use client";
|
|
41
32
|
import { UiOverrideProvider } from "akanjs/ui";
|
|
42
33
|
import { createElement, type ReactNode } from "react";
|
|
43
|
-
import value from ${JSON.stringify(
|
|
34
|
+
import value from ${JSON.stringify(importSpecifier(absPath, opts.userAbsPath))};
|
|
44
35
|
|
|
45
36
|
export default function AkanUiOverridesLayout({ children }: { children?: ReactNode }) {
|
|
46
37
|
return createElement(UiOverrideProvider, { value }, children);
|
|
@@ -73,10 +64,6 @@ function implicitRootLayoutAbsPath(appCwdPath: string, segments: string[]): stri
|
|
|
73
64
|
return path.join(path.resolve(appCwdPath), IMPLICIT_LAYOUT_DIR, filename);
|
|
74
65
|
}
|
|
75
66
|
|
|
76
|
-
function implicitDictionaryMacroAbsPath(appCwdPath: string): string {
|
|
77
|
-
return path.join(path.resolve(appCwdPath), IMPLICIT_DICT_DIR, "useDict.ts");
|
|
78
|
-
}
|
|
79
|
-
|
|
80
67
|
export function isRootBoundarySegments(segments: string[], basePaths: Iterable<string>): boolean {
|
|
81
68
|
const firstVisibleIndex = segments.findIndex((segment) => !/^\(.+\)$/.test(segment));
|
|
82
69
|
if (firstVisibleIndex === -1) return segments.length <= 1;
|
|
@@ -99,10 +86,7 @@ function findRootBoundaries(pageKeys: string[], appCwdPath: string, basePaths: I
|
|
|
99
86
|
segments,
|
|
100
87
|
});
|
|
101
88
|
}
|
|
102
|
-
|
|
103
|
-
if (!hasExplicitRootBoundary && boundaries.size === 0) {
|
|
104
|
-
boundaries.set("", { sourceKey: null, sourceAbsPath: null, segments: [] });
|
|
105
|
-
}
|
|
89
|
+
if (boundaries.size === 0) boundaries.set("", { sourceKey: null, sourceAbsPath: null, segments: [] });
|
|
106
90
|
return [...boundaries.values()].sort((a, b) => a.segments.join("/").localeCompare(b.segments.join("/")));
|
|
107
91
|
}
|
|
108
92
|
|
|
@@ -123,11 +107,6 @@ function findExplicitRootLayoutAbsPath(pageKeys: string[], appCwdPath: string):
|
|
|
123
107
|
return rootLayoutKey ? path.resolve(appCwdPath, "page", rootLayoutKey.replace(/^\.\//, "")) : null;
|
|
124
108
|
}
|
|
125
109
|
|
|
126
|
-
function routePrefixForSegments(segments: string[]): string | null {
|
|
127
|
-
const visible = segments.filter((segment) => !/^\(.+\)$/.test(segment));
|
|
128
|
-
return visible[0] ?? null;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
110
|
async function assertEnvClientConvention(appCwdPath: string, appName: string) {
|
|
132
111
|
const envPath = path.join(appCwdPath, "env", "env.client.ts");
|
|
133
112
|
if (!(await Bun.file(envPath).exists())) {
|
|
@@ -138,8 +117,7 @@ async function assertEnvClientConvention(appCwdPath: string, appName: string) {
|
|
|
138
117
|
}
|
|
139
118
|
|
|
140
119
|
async function writeGeneratedDictionaryMacroFile(appCwdPath: string, appName: string): Promise<string> {
|
|
141
|
-
const absPath =
|
|
142
|
-
await mkdir(path.dirname(absPath), { recursive: true });
|
|
120
|
+
const absPath = path.join(path.resolve(appCwdPath), IMPLICIT_DICT_DIR, "useDict.ts");
|
|
143
121
|
await Bun.write(
|
|
144
122
|
absPath,
|
|
145
123
|
`import { getAllDictionary } from "@apps/${appName}/lib/dict" with { type: "macro" };
|
|
@@ -163,35 +141,20 @@ async function writeGeneratedRootLayoutFile(opts: {
|
|
|
163
141
|
? await writeGeneratedDictionaryMacroFile(opts.appCwdPath, opts.appName)
|
|
164
142
|
: null;
|
|
165
143
|
const absPath = implicitRootLayoutAbsPath(opts.appCwdPath, opts.boundary.segments);
|
|
166
|
-
|
|
167
|
-
const
|
|
168
|
-
? path.relative(path.dirname(absPath), dictMacroAbsPath).split(path.sep).join("/")
|
|
169
|
-
: null;
|
|
170
|
-
const dictMacroSpecifier = dictMacroRel ? (dictMacroRel.startsWith(".") ? dictMacroRel : `./${dictMacroRel}`) : null;
|
|
171
|
-
const sourceRel = opts.boundary.sourceAbsPath
|
|
172
|
-
? path.relative(path.dirname(absPath), opts.boundary.sourceAbsPath).split(path.sep).join("/")
|
|
173
|
-
: null;
|
|
174
|
-
const sourceSpecifier = sourceRel ? (sourceRel.startsWith(".") ? sourceRel : `./${sourceRel}`) : null;
|
|
144
|
+
const dictMacroSpecifier = dictMacroAbsPath ? importSpecifier(absPath, dictMacroAbsPath) : null;
|
|
145
|
+
const sourceSpecifier = opts.boundary.sourceAbsPath ? importSpecifier(absPath, opts.boundary.sourceAbsPath) : null;
|
|
175
146
|
const inheritedSourceAbsPath =
|
|
176
147
|
opts.rootSourceAbsPath && opts.rootSourceAbsPath !== opts.boundary.sourceAbsPath ? opts.rootSourceAbsPath : null;
|
|
177
|
-
const
|
|
178
|
-
? path.relative(path.dirname(absPath), inheritedSourceAbsPath).split(path.sep).join("/")
|
|
179
|
-
: null;
|
|
180
|
-
const inheritedSourceSpecifier = inheritedSourceRel
|
|
181
|
-
? inheritedSourceRel.startsWith(".")
|
|
182
|
-
? inheritedSourceRel
|
|
183
|
-
: `./${inheritedSourceRel}`
|
|
184
|
-
: null;
|
|
148
|
+
const inheritedSourceSpecifier = inheritedSourceAbsPath ? importSpecifier(absPath, inheritedSourceAbsPath) : null;
|
|
185
149
|
const clientImport = opts.includeStInit
|
|
186
150
|
? `import { st } from "@apps/${opts.appName}/client";\nvoid st;\n`
|
|
187
151
|
: `import "@apps/${opts.appName}/client";\n`;
|
|
188
|
-
|
|
189
|
-
// the same afterwards, and this file never has to know which shape the app wrote.
|
|
152
|
+
//? resolveRouteModule reads a rootLayout() chain and the legacy named exports alike.
|
|
190
153
|
const inheritedLabel = inheritedSourceAbsPath ? path.relative(opts.appCwdPath, inheritedSourceAbsPath) : "";
|
|
191
154
|
const inheritedImport = inheritedSourceSpecifier
|
|
192
155
|
? `import * as inheritedModule from ${JSON.stringify(inheritedSourceSpecifier)};\nconst inheritedLayout = resolveRouteModule(inheritedModule as never, ${JSON.stringify(inheritedLabel)}).module as LayoutModule;\n`
|
|
193
156
|
: "const inheritedLayout: LayoutModule = {};\n";
|
|
194
|
-
const prefix =
|
|
157
|
+
const prefix = opts.boundary.segments.find((segment) => !/^\(.+\)$/.test(segment)) ?? null;
|
|
195
158
|
const userLabel = opts.boundary.sourceAbsPath ? path.relative(opts.appCwdPath, opts.boundary.sourceAbsPath) : "";
|
|
196
159
|
const userImport = sourceSpecifier
|
|
197
160
|
? `import * as userModule from ${JSON.stringify(sourceSpecifier)};\nconst userLayout = resolveRouteModule(userModule as never, ${JSON.stringify(userLabel)}).module as LayoutModule;\nconst UserLayout = userLayout.default as (props: LayoutProps) => ReactNode | Promise<ReactNode>;\n`
|
|
@@ -200,9 +163,8 @@ async function writeGeneratedRootLayoutFile(opts: {
|
|
|
200
163
|
? await AsyncDefaultExportDetector.detect(opts.boundary.sourceAbsPath)
|
|
201
164
|
: false;
|
|
202
165
|
const userLayoutElement = "<UserLayout params={params} searchParams={searchParams}>{children}</UserLayout>";
|
|
203
|
-
// React has no async client component, so
|
|
204
|
-
//
|
|
205
|
-
// the layout's own awaits instead of streaming it as its own Flight chunk.
|
|
166
|
+
// React has no async client component, so CSR awaits an async layout's node; RSC keeps the element, since
|
|
167
|
+
// awaiting there would hold the shell behind the layout's own awaits instead of streaming it.
|
|
206
168
|
const layoutSignature = isAsyncUserLayout
|
|
207
169
|
? "export default async function GeneratedLayout"
|
|
208
170
|
: "export default function GeneratedLayout";
|
|
@@ -215,6 +177,19 @@ async function writeGeneratedRootLayoutFile(opts: {
|
|
|
215
177
|
: "";
|
|
216
178
|
const layoutChild = isAsyncUserLayout ? "{layout}" : userLayoutElement;
|
|
217
179
|
const layoutReturn = isAsyncUserLayout ? "layout" : userLayoutElement;
|
|
180
|
+
const layoutExports = `export async function generateHead(props: PageProps) {
|
|
181
|
+
if (userLayout.generateHead) return userLayout.generateHead(props);
|
|
182
|
+
if (userLayout.head !== undefined) return userLayout.head;
|
|
183
|
+
if (inheritedLayout.generateHead) return inheritedLayout.generateHead(props);
|
|
184
|
+
return inheritedLayout.head;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export const NotFound = userLayout.NotFound ?? inheritedLayout.NotFound;
|
|
188
|
+
export const Error = userLayout.Error ?? inheritedLayout.Error;
|
|
189
|
+
export const pageConfig = userLayout.pageConfig ?? inheritedLayout.pageConfig;
|
|
190
|
+
|
|
191
|
+
${layoutSignature}({ children, params, searchParams }: LayoutProps) {
|
|
192
|
+
${layoutBinding} return `;
|
|
218
193
|
const source = opts.includeSystemProvider
|
|
219
194
|
? `import type { LayoutModule, LayoutProps, PageProps } from "akanjs/client";
|
|
220
195
|
import { loadFonts, resolveRouteModule } from "akanjs/client";
|
|
@@ -229,19 +204,7 @@ if (defaultFonts.length > 1) throw new Error("[route-convention] only one defaul
|
|
|
229
204
|
const defaultFont = defaultFonts[0];
|
|
230
205
|
const defaultFontClassName = defaultFont ? (defaultFont.className ?? \`font-\${defaultFont.name}\`) : undefined;
|
|
231
206
|
|
|
232
|
-
|
|
233
|
-
if (userLayout.generateHead) return userLayout.generateHead(props);
|
|
234
|
-
if (userLayout.head !== undefined) return userLayout.head;
|
|
235
|
-
if (inheritedLayout.generateHead) return inheritedLayout.generateHead(props);
|
|
236
|
-
return inheritedLayout.head;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
export const NotFound = userLayout.NotFound ?? inheritedLayout.NotFound;
|
|
240
|
-
export const Error = userLayout.Error ?? inheritedLayout.Error;
|
|
241
|
-
export const pageConfig = userLayout.pageConfig ?? inheritedLayout.pageConfig;
|
|
242
|
-
|
|
243
|
-
${layoutSignature}({ children, params, searchParams }: LayoutProps) {
|
|
244
|
-
${layoutBinding} return (
|
|
207
|
+
${layoutExports}(
|
|
245
208
|
<System.Provider
|
|
246
209
|
of={GeneratedLayout as never}
|
|
247
210
|
appName=${JSON.stringify(opts.appName)}
|
|
@@ -265,29 +228,13 @@ ${layoutBinding} return (
|
|
|
265
228
|
import { resolveRouteModule } from "akanjs/client";
|
|
266
229
|
import type { ReactNode } from "react";
|
|
267
230
|
${inheritedImport}${userImport}
|
|
268
|
-
|
|
269
|
-
if (userLayout.generateHead) return userLayout.generateHead(props);
|
|
270
|
-
if (userLayout.head !== undefined) return userLayout.head;
|
|
271
|
-
if (inheritedLayout.generateHead) return inheritedLayout.generateHead(props);
|
|
272
|
-
return inheritedLayout.head;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
export const NotFound = userLayout.NotFound ?? inheritedLayout.NotFound;
|
|
276
|
-
export const Error = userLayout.Error ?? inheritedLayout.Error;
|
|
277
|
-
export const pageConfig = userLayout.pageConfig ?? inheritedLayout.pageConfig;
|
|
278
|
-
|
|
279
|
-
${layoutSignature}({ children, params, searchParams }: LayoutProps) {
|
|
280
|
-
${layoutBinding} return ${layoutReturn};
|
|
231
|
+
${layoutExports}${layoutReturn};
|
|
281
232
|
}
|
|
282
233
|
`;
|
|
283
234
|
await Bun.write(absPath, source);
|
|
284
235
|
return absPath;
|
|
285
236
|
}
|
|
286
237
|
|
|
287
|
-
/**
|
|
288
|
-
* When no root `page/_layout.*` exists on disk, merge a generated implicit root layout
|
|
289
|
-
* (with generated client runtime registration and optional `void st` when `lib/st.ts` exists).
|
|
290
|
-
*/
|
|
291
238
|
export async function resolveSsrPageEntries(opts: {
|
|
292
239
|
appCwdPath: string;
|
|
293
240
|
appName: string;
|
|
@@ -295,7 +242,7 @@ export async function resolveSsrPageEntries(opts: {
|
|
|
295
242
|
basePaths?: Iterable<string>;
|
|
296
243
|
}): Promise<PageEntry[]> {
|
|
297
244
|
const absPageDir = path.resolve(opts.appCwdPath, "page");
|
|
298
|
-
const hasSt = await
|
|
245
|
+
const hasSt = await Bun.file(path.join(opts.appCwdPath, "lib", "st.ts")).exists();
|
|
299
246
|
const basePaths = opts.basePaths ?? [];
|
|
300
247
|
const rootSourceAbsPath = findExplicitRootLayoutAbsPath(opts.pageKeys, opts.appCwdPath);
|
|
301
248
|
const rootBoundaries = findRootBoundaries(opts.pageKeys, opts.appCwdPath, basePaths);
|
|
@@ -310,9 +257,7 @@ export async function resolveSsrPageEntries(opts: {
|
|
|
310
257
|
.filter((key) => !rootLayoutKeys.has(key))
|
|
311
258
|
.map(async (key) => {
|
|
312
259
|
const userAbsPath = path.resolve(absPageDir, key);
|
|
313
|
-
|
|
314
|
-
// so the author writes no directive; the raw manifest becomes a build seed so its slot components (and
|
|
315
|
-
// the manifest itself) enter the client graph / RSC client manifest.
|
|
260
|
+
//? The raw manifest is a seed, so it and its slot components enter the client graph and RSC client manifest.
|
|
316
261
|
if (OVERRIDES_KEY_RE.test(key)) {
|
|
317
262
|
const moduleAbsPath = await writeGeneratedOverridesLayoutFile({
|
|
318
263
|
appCwdPath: opts.appCwdPath,
|
|
@@ -338,9 +283,7 @@ export async function resolveSsrPageEntries(opts: {
|
|
|
338
283
|
seedAbsPaths: [...new Set([boundary.sourceAbsPath, rootSourceAbsPath].filter((absPath) => absPath !== null))],
|
|
339
284
|
})),
|
|
340
285
|
);
|
|
341
|
-
|
|
342
|
-
entries.sort((a, b) => a.key.localeCompare(b.key));
|
|
343
|
-
return entries;
|
|
286
|
+
return [...base, ...generated].sort((a, b) => a.key.localeCompare(b.key));
|
|
344
287
|
}
|
|
345
288
|
|
|
346
289
|
export async function resolveSsrPageEntriesForApp(app: App, pageKeys: string[]): Promise<PageEntry[]> {
|