@akanjs/cli 3.0.0-beta.20 → 3.0.0-beta.21

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.
Files changed (64) hide show
  1. package/.build-stamp +1 -1
  2. package/{CodeAgent-awv258wb.js → CodeAgent-q2m47rqp.js} +2 -2
  3. package/{CodeTui-1nqh35ez.js → CodeTui-pdqhc3sn.js} +1 -1
  4. package/{agent.command-1s9ag7sb.js → agent.command-ttn09rx3.js} +5 -5
  5. package/akanCode.js +2 -2
  6. package/{application.command-49hwy6va.js → application.command-7gweenv7.js} +43 -41
  7. package/{applicationBuildRunner-ybyp97pv.js → applicationBuildRunner-38exmejd.js} +2 -2
  8. package/buildBatch.proc.js +10 -4
  9. package/{cloud.command-d7rqtzt9.js → cloud.command-bhztw7c2.js} +9 -8
  10. package/cloud.script-aabyhgba.js +20 -0
  11. package/{code.command-nn48xzej.js → code.command-6s8c0k91.js} +6 -6
  12. package/commandManifest.json +1 -1
  13. package/{context.command-s3mcmvpa.js → context.command-phmgywt2.js} +13 -13
  14. package/context.runner-yvyw5g2d.js +21 -0
  15. package/{guideline.command-sct4qnt5.js → guideline.command-7jk47fgq.js} +2 -2
  16. package/guidelines/agentRule/agentRule.instruction.md +3 -3
  17. package/guidelines/conventions/conventions.instruction.md +17 -5
  18. package/guidelines/runtimeRule/runtimeRule.instruction.md +3 -3
  19. package/incrementalBuilder.proc.js +1 -1
  20. package/{index-5pc1qrbv.js → index-1db2cw0y.js} +2 -2
  21. package/{index-ahemdr5n.js → index-4dn1p115.js} +3 -3
  22. package/{index-13ffe9wp.js → index-7cqjvraf.js} +4 -4
  23. package/{index-8sf7wmdr.js → index-8kb5p54p.js} +5 -5
  24. package/{index-ygn77a5w.js → index-9629w7ns.js} +5 -3
  25. package/{index-ddxvdd1p.js → index-aadpbnt2.js} +5 -5
  26. package/{index-ztp3e73t.js → index-d0arnrm8.js} +1 -1
  27. package/{index-nv5976x3.js → index-dg97qp24.js} +24 -13
  28. package/{index-9d9bgcx4.js → index-ga6hygxr.js} +514 -480
  29. package/index-hr5n7t4x.js +465 -0
  30. package/{index-xqpzbab5.js → index-m0ms3n1c.js} +7 -7
  31. package/{index-as7xmyz8.js → index-mgfx3mfs.js} +5 -4
  32. package/{index-3sq321ak.js → index-pp54p5zk.js} +2 -2
  33. package/{index-xqjwd384.js → index-q44jbw48.js} +1 -1
  34. package/{index-w2zdcw3k.js → index-rmcw8yc8.js} +1 -1
  35. package/{index-dzf3r49a.js → index-rnt66vym.js} +1 -1
  36. package/{index-8mna0947.js → index-tgjy8xf3.js} +4 -4
  37. package/{index-xsjh6nzh.js → index-v5eayqqw.js} +3 -3
  38. package/{index-przybvy5.js → index-wp43xrjv.js} +4 -4
  39. package/{index-31yr441d.js → index-ya5qbmyg.js} +3 -3
  40. package/{index-axzjaqve.js → index-yjkte9aj.js} +5 -4
  41. package/{index-myhc3c1j.js → index-yy4s1j72.js} +1 -1
  42. package/{index-ww4z397j.js → index-z50k041j.js} +82 -168
  43. package/index.js +21 -21
  44. package/{library.command-1gx04jgn.js → library.command-ccb1cp92.js} +3 -3
  45. package/{localRegistry.command-zndxj1q6.js → localRegistry.command-ajb9n87j.js} +8 -7
  46. package/{module.command-3waddk95.js → module.command-5k6h407d.js} +5 -5
  47. package/{package.command-zrc7br1y.js → package.command-e6ntvpez.js} +3 -3
  48. package/package.json +2 -3
  49. package/{page.command-vpjgardh.js → page.command-v4f90wb8.js} +3 -3
  50. package/{primitive.command-sz84tk4t.js → primitive.command-g6km57q5.js} +6 -6
  51. package/{quality.command-g869hafs.js → quality.command-de3p36pz.js} +5 -4
  52. package/{repair.command-chsnjvvv.js → repair.command-d3x1javq.js} +5 -5
  53. package/{scalar.command-2v21newx.js → scalar.command-7ezrac2b.js} +4 -4
  54. package/{subspace.command-ckcbwfp1.js → subspace.command-ps71ynp6.js} +4 -4
  55. package/templates/workspaceRoot/.gitignore.template +0 -1
  56. package/{tunnel.command-kd2yr4fx.js → tunnel.command-hsrv2mny.js} +2 -2
  57. package/{workflow.command-44252cqv.js → workflow.command-z2p32nmf.js} +10 -10
  58. package/{workspace.command-fqcztt5p.js → workspace.command-tmdqkrh0.js} +20 -20
  59. package/applicationReleasePackager-esnrphw8.js +0 -224
  60. package/capacitorApp-1synz4en.js +0 -11
  61. package/cloud.script-bkbpcr96.js +0 -19
  62. package/context.runner-k99nb5pk.js +0 -21
  63. package/index-5e0n4hqx.js +0 -75
  64. package/index-mxav6j3c.js +0 -1377
@@ -4,9 +4,324 @@ import {
4
4
  FileSys2
5
5
  } from "./index-5b6tavv3.js";
6
6
 
7
- // pkgs/@akanjs/devkit/codegenLock.ts
8
- import { mkdir, open, readFile, rm, stat } from "fs/promises";
7
+ // pkgs/@akanjs/devkit/artifact/implicitRootLayout.ts
8
+ import { readdir, rm } from "fs/promises";
9
+ import path2 from "path";
10
+
11
+ // pkgs/@akanjs/devkit/transforms/asyncDefaultExportDetector.ts
12
+ import fs from "fs";
9
13
  import path from "path";
14
+
15
+ class AsyncDefaultExportDetector {
16
+ static #typescriptLoad;
17
+ static #loadTypescript() {
18
+ AsyncDefaultExportDetector.#typescriptLoad ??= import("typescript").then((mod) => mod.default ?? mod);
19
+ return AsyncDefaultExportDetector.#typescriptLoad;
20
+ }
21
+ static async detect(moduleAbsPath) {
22
+ try {
23
+ const typescript = await AsyncDefaultExportDetector.#loadTypescript();
24
+ const source = fs.readFileSync(path.resolve(moduleAbsPath), "utf8");
25
+ const sourceFile = typescript.createSourceFile(moduleAbsPath, source, typescript.ScriptTarget.Latest, true, /\.[tj]sx$/.test(moduleAbsPath) ? typescript.ScriptKind.TSX : typescript.ScriptKind.TS);
26
+ return new AsyncDefaultExportDetector(typescript).detectInSourceFile(sourceFile);
27
+ } catch {
28
+ return false;
29
+ }
30
+ }
31
+ #ts;
32
+ constructor(typescript) {
33
+ this.#ts = typescript;
34
+ }
35
+ detectInSourceFile(sourceFile) {
36
+ const ts = this.#ts;
37
+ const asyncBindings = new Map;
38
+ let defaultIdentifier = null;
39
+ for (const statement of sourceFile.statements) {
40
+ if (ts.isFunctionDeclaration(statement)) {
41
+ if (this.#hasModifier(statement, ts.SyntaxKind.DefaultKeyword)) {
42
+ return this.#hasModifier(statement, ts.SyntaxKind.AsyncKeyword);
43
+ }
44
+ if (statement.name) {
45
+ asyncBindings.set(statement.name.text, this.#hasModifier(statement, ts.SyntaxKind.AsyncKeyword));
46
+ }
47
+ continue;
48
+ }
49
+ if (ts.isVariableStatement(statement)) {
50
+ for (const declaration of statement.declarationList.declarations) {
51
+ if (!ts.isIdentifier(declaration.name))
52
+ continue;
53
+ asyncBindings.set(declaration.name.text, this.#isAsyncFunctionExpression(declaration.initializer));
54
+ }
55
+ continue;
56
+ }
57
+ if (ts.isExportAssignment(statement)) {
58
+ if (this.#isAsyncFunctionExpression(statement.expression))
59
+ return true;
60
+ if (ts.isCallExpression(statement.expression))
61
+ return true;
62
+ if (ts.isIdentifier(statement.expression))
63
+ defaultIdentifier = statement.expression.text;
64
+ continue;
65
+ }
66
+ if (ts.isExportDeclaration(statement) && statement.exportClause && ts.isNamedExports(statement.exportClause)) {
67
+ for (const specifier of statement.exportClause.elements) {
68
+ if (specifier.name.text !== "default")
69
+ continue;
70
+ defaultIdentifier = specifier.propertyName?.text ?? specifier.name.text;
71
+ }
72
+ }
73
+ }
74
+ return defaultIdentifier ? asyncBindings.get(defaultIdentifier) === true : false;
75
+ }
76
+ #hasModifier(node, kind) {
77
+ const ts = this.#ts;
78
+ return ts.canHaveModifiers(node) && (ts.getModifiers(node)?.some((modifier) => modifier.kind === kind) ?? false);
79
+ }
80
+ #isAsyncFunctionExpression(node) {
81
+ const ts = this.#ts;
82
+ return Boolean(node && (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) && this.#hasModifier(node, ts.SyntaxKind.AsyncKeyword));
83
+ }
84
+ }
85
+
86
+ // pkgs/@akanjs/devkit/artifact/implicitRootLayout.ts
87
+ var LAYOUT_KEY_RE = /^\.\/(.+\/)?_layout\.(tsx|ts|jsx|js)$/;
88
+ var IMPLICIT_LAYOUT_DIR = path2.join(".akan", "generated", "root-layouts");
89
+ var IMPLICIT_DICT_DIR = path2.join(".akan", "generated", "dict");
90
+ var IMPLICIT_OVERRIDES_DIR = path2.join(".akan", "generated", "overrides");
91
+ var OVERRIDES_KEY_RE = /^\.\/(.+\/)?_overrides\.(tsx|ts|jsx|js)$/;
92
+ var importSpecifier = (fromAbsPath, toAbsPath) => {
93
+ const rel = path2.relative(path2.dirname(fromAbsPath), toAbsPath).split(path2.sep).join("/");
94
+ return rel.startsWith(".") ? rel : `./${rel}`;
95
+ };
96
+ async function writeGeneratedOverridesLayoutFile(opts) {
97
+ const filename = `${opts.key.replace(/^\.\//, "").replace(/[^a-zA-Z0-9]+/g, "_")}.tsx`;
98
+ const absPath = path2.join(path2.resolve(opts.appCwdPath), IMPLICIT_OVERRIDES_DIR, filename);
99
+ const source = `"use client";
100
+ import { UiOverrideProvider } from "akanjs/ui";
101
+ import { createElement, type ReactNode } from "react";
102
+ import value from ${JSON.stringify(importSpecifier(absPath, opts.userAbsPath))};
103
+
104
+ export default function AkanUiOverridesLayout({ children }: { children?: ReactNode }) {
105
+ return createElement(UiOverrideProvider, { value }, children);
106
+ }
107
+ `;
108
+ await Bun.write(absPath, source);
109
+ return absPath;
110
+ }
111
+ async function pruneGeneratedFiles(dirAbsPath, keepAbsPaths) {
112
+ const keep = new Set(keepAbsPaths);
113
+ const names = await readdir(dirAbsPath).catch(() => []);
114
+ await Promise.all(names.map((name) => path2.join(dirAbsPath, name)).filter((absPath) => !keep.has(absPath)).map((absPath) => rm(absPath, { force: true })));
115
+ }
116
+ function getRootBoundarySegments(key) {
117
+ const match = LAYOUT_KEY_RE.exec(key);
118
+ if (!match)
119
+ return null;
120
+ const prefix = match[1]?.replace(/\/$/, "");
121
+ if (!prefix)
122
+ return [];
123
+ return prefix.split("/").filter(Boolean);
124
+ }
125
+ function implicitRootLayoutKey(segments) {
126
+ return `./${[...segments, "__root_layout"].join("/")}.tsx`;
127
+ }
128
+ function implicitRootLayoutAbsPath(appCwdPath, segments) {
129
+ const filename = segments.length ? `${segments.join("__")}__root_layout.tsx` : "__root_layout.tsx";
130
+ return path2.join(path2.resolve(appCwdPath), IMPLICIT_LAYOUT_DIR, filename);
131
+ }
132
+ function isRootBoundarySegments(segments, basePaths) {
133
+ const firstVisibleIndex = segments.findIndex((segment) => !/^\(.+\)$/.test(segment));
134
+ if (firstVisibleIndex === -1)
135
+ return segments.length <= 1;
136
+ if (segments.slice(firstVisibleIndex + 1).some((segment) => /^\(.+\)$/.test(segment)))
137
+ return false;
138
+ const visible = segments.slice(firstVisibleIndex);
139
+ const allowedBasePaths = new Set([...basePaths].map((basePath) => basePath.trim()).filter(Boolean));
140
+ return visible.length === 1 && (firstVisibleIndex > 0 || allowedBasePaths.has(visible[0] ?? ""));
141
+ }
142
+ function findRootBoundaries(pageKeys, appCwdPath, basePaths) {
143
+ const boundaries = new Map;
144
+ for (const key of pageKeys) {
145
+ const segments = getRootBoundarySegments(key);
146
+ if (!segments)
147
+ continue;
148
+ if (!isRootBoundarySegments(segments, basePaths))
149
+ continue;
150
+ const id = segments.join("/");
151
+ boundaries.set(id, {
152
+ sourceKey: key,
153
+ sourceAbsPath: path2.resolve(appCwdPath, "page", key.replace(/^\.\//, "")),
154
+ segments
155
+ });
156
+ }
157
+ if (boundaries.size === 0)
158
+ boundaries.set("", { sourceKey: null, sourceAbsPath: null, segments: [] });
159
+ return [...boundaries.values()].sort((a, b) => a.segments.join("/").localeCompare(b.segments.join("/")));
160
+ }
161
+ function hasAncestorRootBoundary(boundary, boundaries) {
162
+ return boundaries.some((candidate) => candidate !== boundary && candidate.segments.length < boundary.segments.length && candidate.segments.every((segment, index) => boundary.segments[index] === segment));
163
+ }
164
+ function findExplicitRootLayoutAbsPath(pageKeys, appCwdPath) {
165
+ const rootLayoutKey = pageKeys.find((key) => {
166
+ const segments = getRootBoundarySegments(key);
167
+ return segments !== null && segments.length === 0;
168
+ });
169
+ return rootLayoutKey ? path2.resolve(appCwdPath, "page", rootLayoutKey.replace(/^\.\//, "")) : null;
170
+ }
171
+ async function assertEnvClientConvention(appCwdPath, appName) {
172
+ const envPath = path2.join(appCwdPath, "env", "env.client.ts");
173
+ if (!await Bun.file(envPath).exists()) {
174
+ throw new Error(`[route-convention] app "${appName}" must provide env/env.client.ts exporting "env" for generated System.Provider`);
175
+ }
176
+ }
177
+ async function writeGeneratedDictionaryMacroFile(appCwdPath, appName) {
178
+ const absPath = path2.join(path2.resolve(appCwdPath), IMPLICIT_DICT_DIR, "useDict.ts");
179
+ await Bun.write(absPath, `import { getAllDictionary } from "@apps/${appName}/lib/dict" with { type: "macro" };
180
+
181
+ export const allDictionary = getAllDictionary();
182
+ `);
183
+ return absPath;
184
+ }
185
+ async function writeGeneratedRootLayoutFile(opts) {
186
+ await assertEnvClientConvention(opts.appCwdPath, opts.appName);
187
+ const dictMacroAbsPath = opts.includeSystemProvider ? await writeGeneratedDictionaryMacroFile(opts.appCwdPath, opts.appName) : null;
188
+ const absPath = implicitRootLayoutAbsPath(opts.appCwdPath, opts.boundary.segments);
189
+ const dictMacroSpecifier = dictMacroAbsPath ? importSpecifier(absPath, dictMacroAbsPath) : null;
190
+ const sourceSpecifier = opts.boundary.sourceAbsPath ? importSpecifier(absPath, opts.boundary.sourceAbsPath) : null;
191
+ const inheritedSourceAbsPath = opts.rootSourceAbsPath && opts.rootSourceAbsPath !== opts.boundary.sourceAbsPath ? opts.rootSourceAbsPath : null;
192
+ const inheritedSourceSpecifier = inheritedSourceAbsPath ? importSpecifier(absPath, inheritedSourceAbsPath) : null;
193
+ const clientImport = opts.includeStInit ? `import { st } from "@apps/${opts.appName}/client";
194
+ void st;
195
+ ` : `import "@apps/${opts.appName}/client";
196
+ `;
197
+ const inheritedLabel = inheritedSourceAbsPath ? path2.relative(opts.appCwdPath, inheritedSourceAbsPath) : "";
198
+ const inheritedImport = inheritedSourceSpecifier ? `import * as inheritedModule from ${JSON.stringify(inheritedSourceSpecifier)};
199
+ const inheritedLayout = resolveRouteModule(inheritedModule as never, ${JSON.stringify(inheritedLabel)}).module as LayoutModule;
200
+ ` : `const inheritedLayout: LayoutModule = {};
201
+ `;
202
+ const prefix = opts.boundary.segments.find((segment) => !/^\(.+\)$/.test(segment)) ?? null;
203
+ const userLabel = opts.boundary.sourceAbsPath ? path2.relative(opts.appCwdPath, opts.boundary.sourceAbsPath) : "";
204
+ const userImport = sourceSpecifier ? `import * as userModule from ${JSON.stringify(sourceSpecifier)};
205
+ const userLayout = resolveRouteModule(userModule as never, ${JSON.stringify(userLabel)}).module as LayoutModule;
206
+ const UserLayout = userLayout.default as (props: LayoutProps) => ReactNode | Promise<ReactNode>;
207
+ ` : `const UserLayout = ({ children }: LayoutProps) => children;
208
+ const userLayout: LayoutModule = {};
209
+ `;
210
+ const isAsyncUserLayout = opts.boundary.sourceAbsPath ? await AsyncDefaultExportDetector.detect(opts.boundary.sourceAbsPath) : false;
211
+ const userLayoutElement = "<UserLayout params={params} searchParams={searchParams}>{children}</UserLayout>";
212
+ const layoutSignature = isAsyncUserLayout ? "export default async function GeneratedLayout" : "export default function GeneratedLayout";
213
+ const layoutBinding = isAsyncUserLayout ? ` const layout =
214
+ process.env.AKAN_PUBLIC_RENDER_ENV === "csr"
215
+ ? await UserLayout({ params, searchParams, children })
216
+ : ${userLayoutElement};
217
+ ` : "";
218
+ const layoutChild = isAsyncUserLayout ? "{layout}" : userLayoutElement;
219
+ const layoutReturn = isAsyncUserLayout ? "layout" : userLayoutElement;
220
+ const layoutExports = `export async function generateHead(props: PageProps) {
221
+ if (userLayout.generateHead) return userLayout.generateHead(props);
222
+ if (userLayout.head !== undefined) return userLayout.head;
223
+ if (inheritedLayout.generateHead) return inheritedLayout.generateHead(props);
224
+ return inheritedLayout.head;
225
+ }
226
+
227
+ export const NotFound = userLayout.NotFound ?? inheritedLayout.NotFound;
228
+ export const Error = userLayout.Error ?? inheritedLayout.Error;
229
+ export const pageConfig = userLayout.pageConfig ?? inheritedLayout.pageConfig;
230
+
231
+ ${layoutSignature}({ children, params, searchParams }: LayoutProps) {
232
+ ${layoutBinding} return `;
233
+ const source = opts.includeSystemProvider ? `import type { LayoutModule, LayoutProps, PageProps } from "akanjs/client";
234
+ import { loadFonts, resolveRouteModule } from "akanjs/client";
235
+ import type { ReactNode } from "react";
236
+ import { System } from "akanjs/ui";
237
+ import { env } from "@apps/${opts.appName}/env/env.client";
238
+ import { allDictionary } from ${JSON.stringify(dictMacroSpecifier)};
239
+ ${clientImport}${inheritedImport}${userImport}
240
+ const userFonts = userLayout.fonts ?? inheritedLayout.fonts ?? [];
241
+ const defaultFonts = userFonts.filter((font) => font.default);
242
+ if (defaultFonts.length > 1) throw new Error("[route-convention] only one default font is allowed per root layout");
243
+ const defaultFont = defaultFonts[0];
244
+ const defaultFontClassName = defaultFont ? (defaultFont.className ?? \`font-\${defaultFont.name}\`) : undefined;
245
+
246
+ ${layoutExports}(
247
+ <System.Provider
248
+ of={GeneratedLayout as never}
249
+ appName=${JSON.stringify(opts.appName)}
250
+ ${prefix ? `prefix=${JSON.stringify(prefix)}
251
+ ` : ""}params={params}
252
+ manifest={userLayout.manifest ?? inheritedLayout.manifest}
253
+ env={env}
254
+ theme={userLayout.theme ?? inheritedLayout.theme}
255
+ fonts={loadFonts(userFonts)}
256
+ className={defaultFontClassName}
257
+ layoutStyle={userLayout.layoutStyle ?? inheritedLayout.layoutStyle}
258
+ reconnect={userLayout.reconnect ?? inheritedLayout.reconnect ?? false}
259
+ wsConnect={userLayout.wsConnect ?? inheritedLayout.wsConnect ?? true}
260
+ allDictionary={process.env.AKAN_PUBLIC_RENDER_ENV === "ssr" ? allDictionary : undefined}
261
+ >
262
+ ${layoutChild}
263
+ </System.Provider>
264
+ );
265
+ }
266
+ ` : `import type { LayoutModule, LayoutProps, PageProps } from "akanjs/client";
267
+ import { resolveRouteModule } from "akanjs/client";
268
+ import type { ReactNode } from "react";
269
+ ${inheritedImport}${userImport}
270
+ ${layoutExports}${layoutReturn};
271
+ }
272
+ `;
273
+ await Bun.write(absPath, source);
274
+ return absPath;
275
+ }
276
+ async function resolveSsrPageEntries(opts) {
277
+ const absPageDir = path2.resolve(opts.appCwdPath, "page");
278
+ const hasSt = await Bun.file(path2.join(opts.appCwdPath, "lib", "st.ts")).exists();
279
+ const basePaths = opts.basePaths ?? [];
280
+ const rootSourceAbsPath = findExplicitRootLayoutAbsPath(opts.pageKeys, opts.appCwdPath);
281
+ const rootBoundaries = findRootBoundaries(opts.pageKeys, opts.appCwdPath, basePaths);
282
+ const rootLayoutKeys = new Set(opts.pageKeys.filter((key) => {
283
+ const segments = getRootBoundarySegments(key);
284
+ return segments !== null && isRootBoundarySegments(segments, basePaths);
285
+ }));
286
+ const base = await Promise.all(opts.pageKeys.filter((key) => !rootLayoutKeys.has(key)).map(async (key) => {
287
+ const userAbsPath = path2.resolve(absPageDir, key);
288
+ if (OVERRIDES_KEY_RE.test(key)) {
289
+ const moduleAbsPath = await writeGeneratedOverridesLayoutFile({
290
+ appCwdPath: opts.appCwdPath,
291
+ key,
292
+ userAbsPath
293
+ });
294
+ return { key, moduleAbsPath, seedAbsPaths: [userAbsPath] };
295
+ }
296
+ return { key, moduleAbsPath: userAbsPath };
297
+ }));
298
+ const generated = await Promise.all(rootBoundaries.map(async (boundary) => ({
299
+ key: implicitRootLayoutKey(boundary.segments),
300
+ moduleAbsPath: await writeGeneratedRootLayoutFile({
301
+ appCwdPath: opts.appCwdPath,
302
+ appName: opts.appName,
303
+ boundary,
304
+ rootSourceAbsPath,
305
+ includeStInit: hasSt && boundary.segments.length === 0,
306
+ includeSystemProvider: !hasAncestorRootBoundary(boundary, rootBoundaries)
307
+ }),
308
+ seedAbsPaths: [...new Set([boundary.sourceAbsPath, rootSourceAbsPath].filter((absPath) => absPath !== null))]
309
+ })));
310
+ const appCwdAbsPath = path2.resolve(opts.appCwdPath);
311
+ await Promise.all([
312
+ pruneGeneratedFiles(path2.join(appCwdAbsPath, IMPLICIT_OVERRIDES_DIR), base.filter(({ key }) => OVERRIDES_KEY_RE.test(key)).map(({ moduleAbsPath }) => moduleAbsPath)),
313
+ pruneGeneratedFiles(path2.join(appCwdAbsPath, IMPLICIT_LAYOUT_DIR), generated.map(({ moduleAbsPath }) => moduleAbsPath))
314
+ ]);
315
+ return [...base, ...generated].sort((a, b) => a.key.localeCompare(b.key));
316
+ }
317
+ async function resolveSsrPageEntriesForApp(app, pageKeys) {
318
+ const config = await app.getConfig();
319
+ return resolveSsrPageEntries({ appCwdPath: app.cwdPath, appName: app.name, pageKeys, basePaths: config.basePaths });
320
+ }
321
+
322
+ // pkgs/@akanjs/devkit/codegenLock.ts
323
+ import { mkdir, open, readFile, rm as rm2, stat } from "fs/promises";
324
+ import path3 from "path";
10
325
  import { Logger } from "akanjs/common";
11
326
 
12
327
  class CodegenLock {
@@ -17,7 +332,7 @@ class CodegenLock {
17
332
  static #logger = new Logger("CodegenLock");
18
333
  static #queue = Promise.resolve();
19
334
  static pathIn(workspaceRoot) {
20
- return path.join(workspaceRoot, "local", ".akan", CodegenLock.fileName);
335
+ return path3.join(workspaceRoot, "local", ".akan", CodegenLock.fileName);
21
336
  }
22
337
  static async run(workspaceRoot, label, fn) {
23
338
  const ahead = CodegenLock.#queue;
@@ -47,7 +362,7 @@ class CodegenLock {
47
362
  }
48
363
  static async#withFileLock(workspaceRoot, label, fn) {
49
364
  const lockPath = CodegenLock.pathIn(workspaceRoot);
50
- await mkdir(path.dirname(lockPath), { recursive: true }).catch(() => {
365
+ await mkdir(path3.dirname(lockPath), { recursive: true }).catch(() => {
51
366
  return;
52
367
  });
53
368
  const held = await CodegenLock.#acquire(lockPath, label);
@@ -55,7 +370,7 @@ class CodegenLock {
55
370
  return await fn();
56
371
  } finally {
57
372
  if (held)
58
- await rm(lockPath, { force: true }).catch(() => {
373
+ await rm2(lockPath, { force: true }).catch(() => {
59
374
  return;
60
375
  });
61
376
  }
@@ -92,7 +407,7 @@ class CodegenLock {
92
407
  return false;
93
408
  } else if (Date.now() - info.mtimeMs < CodegenLock.unknownHolderStaleMs)
94
409
  return false;
95
- await rm(lockPath, { force: true }).catch(() => {
410
+ await rm2(lockPath, { force: true }).catch(() => {
96
411
  return;
97
412
  });
98
413
  return true;
@@ -354,8 +669,8 @@ ${CLAUDE_COMMENT_RULE}
354
669
  `;
355
670
 
356
671
  // pkgs/@akanjs/devkit/akanConfig/akanConfig.ts
357
- import fs from "fs";
358
- import path2 from "path";
672
+ import fs2 from "fs";
673
+ import path4 from "path";
359
674
  import { fileURLToPath } from "url";
360
675
  import { DatabaseModes, normalizeRoutePrefix } from "akanjs/base";
361
676
  import { resolveAkanI18nConfig } from "akanjs/common";
@@ -374,17 +689,6 @@ var DEFAULT_OPTIMIZE_IMPORTS = [
374
689
  var WORKSPACE_BARREL_FACETS = ["ui", "webkit", "common", "client", "server"];
375
690
  var DEFAULT_DOCKER_IMAGE = "oven/bun:1-slim";
376
691
  var SSR_RUNTIME_PACKAGES = ["react", "react-dom", "react-server-dom-webpack"];
377
- var MOBILE_RUNTIME_PACKAGES = [
378
- "@capacitor/cli",
379
- "@capacitor/core",
380
- "@capacitor/ios",
381
- "@capacitor/android",
382
- "@capacitor/assets"
383
- ];
384
- var MOBILE_APP_CAPACITOR_PLUGINS = [
385
- ..."app browser camera core device geolocation haptics inappbrowser keyboard preferences push-notifications".split(" ").map((name) => `@capacitor/${name}`),
386
- "capacitor-plugin-safe-area"
387
- ];
388
692
  var DEFAULT_AKAN_IMAGE_CONFIG = {
389
693
  deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
390
694
  imageSizes: [32, 48, 64, 96, 128, 256, 384],
@@ -530,7 +834,7 @@ class AkanAppConfig {
530
834
  #resolveWebConfig(web) {
531
835
  const resolved = typeof web === "object" ? { ssr: true, csr: web.csr } : { ssr: web ?? true, csr: web ?? true };
532
836
  if (!resolved.csr && this.hasMobileConfig)
533
- throw new Error(`apps/${this.app.name}/akan.config.ts turns the CSR bundle off but declares mobile targets; the Capacitor build ships that bundle. Drop the mobile section or leave CSR on.`);
837
+ throw new Error(`apps/${this.app.name}/akan.config.ts turns the CSR bundle off but declares mobile targets; the mobile apps ship that bundle. Drop the mobile section or leave CSR on.`);
534
838
  return resolved;
535
839
  }
536
840
  #resolveMobileConfig(mobile) {
@@ -539,57 +843,113 @@ class AkanAppConfig {
539
843
  indexPath: _indexPath,
540
844
  ...rawMobile
541
845
  } = mobile ?? {};
846
+ const configPath = `apps/${this.app.name}/akan.config.ts`;
847
+ AkanAppConfig.#assertMobileKeys(rawMobile, AkanAppConfig.#mobileKeys, "mobile", configPath);
542
848
  const appName = rawMobile.appName ?? this.app.name;
543
849
  const appId = rawMobile.appId ?? deriveDefaultAppId(this.baseDevEnv.repoName, this.app.name);
544
850
  const version = rawMobile.version ?? "0.0.1";
545
851
  const buildNum = rawMobile.buildNum ?? 1;
852
+ const files = AkanAppConfig.#resolveMobileFiles(rawMobile.files, "mobile", configPath);
853
+ const native = rawMobile.native;
546
854
  const defaultTargetName = this.#defaultMobileTargetName(rawTargets);
547
855
  const targetEntries = Object.entries(rawTargets ?? {
548
856
  [defaultTargetName]: {}
549
857
  });
550
858
  const targets = Object.fromEntries(targetEntries.map(([name, rawTarget]) => {
551
- const target = rawTarget;
859
+ const target = rawTarget ?? {};
860
+ const where = `mobile.targets.${name}`;
861
+ AkanAppConfig.#assertMobileKeys(target, AkanAppConfig.#targetKeys, where, configPath);
552
862
  const fallbackBasePath = !rawTargets && this.basePaths.has(name) ? name : undefined;
553
863
  const basePath = (target.basePath ?? fallbackBasePath)?.replace(/^\/+|\/+$/g, "") || undefined;
554
864
  const indexPath = normalizeIndexPath(target.indexPath);
555
865
  const deepLinks = normalizeDeepLinks(target.deepLinks);
556
866
  if (basePath && !this.basePaths.has(basePath)) {
557
- throw new Error(`Mobile target '${name}' uses unknown basePath '${basePath}' in apps/${this.app.name}/akan.config.ts`);
867
+ throw new Error(`Mobile target '${name}' uses unknown basePath '${basePath}' in ${configPath}`);
558
868
  }
869
+ const targetFiles = AkanAppConfig.#resolveMobileFiles(target.files, where, configPath);
870
+ const fileName = target.fileName ?? rawMobile.fileName;
559
871
  const resolved = {
560
- ...rawMobile,
561
- ...target,
562
872
  name,
563
873
  basePath,
564
874
  indexPath,
565
875
  deepLinks,
566
876
  appName: target.appName ?? appName,
567
877
  appId: target.appId ?? appId,
878
+ ...fileName ? { fileName } : {},
568
879
  version: target.version ?? version,
569
880
  buildNum: target.buildNum ?? buildNum,
570
- plugins: {
571
- ...rawMobile.plugins,
572
- ...target.plugins
573
- },
574
- android: {
575
- ...rawMobile.android,
576
- ...target.android
577
- },
578
- ios: {
579
- ...rawMobile.ios,
580
- ...target.ios
581
- }
881
+ ...target.assets ? { assets: target.assets } : {},
882
+ ...target.permissions ? { permissions: target.permissions } : {},
883
+ ...files || targetFiles ? { files: { ...files, ...targetFiles } } : {},
884
+ ...native || target.native ? { native: AkanAppConfig.#mergeNative(native, target.native) } : {}
582
885
  };
583
886
  return [name, resolved];
584
887
  }));
585
888
  return {
586
- ...rawMobile,
587
889
  appName,
588
890
  appId,
891
+ ...rawMobile.fileName ? { fileName: rawMobile.fileName } : {},
589
892
  version,
590
893
  buildNum,
591
- targets,
592
- plugins: rawMobile.plugins
894
+ ...files ? { files } : {},
895
+ ...native ? { native } : {},
896
+ targets
897
+ };
898
+ }
899
+ static #mobileKeys = ["appName", "appId", "fileName", "version", "buildNum", "files", "native"];
900
+ static #targetKeys = [
901
+ "basePath",
902
+ "indexPath",
903
+ "appName",
904
+ "appId",
905
+ "fileName",
906
+ "version",
907
+ "buildNum",
908
+ "assets",
909
+ "permissions",
910
+ "deepLinks",
911
+ "files",
912
+ "native"
913
+ ];
914
+ static #assertMobileKeys(section, known, where, configPath) {
915
+ for (const key of Object.keys(section)) {
916
+ if (known.includes(key))
917
+ continue;
918
+ if (key === "plugins" || key === "ios" || key === "android")
919
+ throw new Error(`${where}.${key} in ${configPath} is a Capacitor setting. The native runtime reads native.plugins, native.ios.{infoPlist,entitlements} and native.android.{manifest,application,activity,googleServices} instead.`);
920
+ throw new Error(`${where}.${key} in ${configPath} is not a mobile setting. Known: ${known.join(", ")}.`);
921
+ }
922
+ }
923
+ static #fileTargetPattern = /^(?:ios\/.+|android\/res\/[^/]+\/[^/]+|android\/assets\/.+)$/;
924
+ static #resolveMobileFiles(files, where, configPath) {
925
+ if (!files)
926
+ return;
927
+ for (const [to, from] of Object.entries(files)) {
928
+ if (typeof from !== "string")
929
+ throw new Error(`${where}.files in ${configPath} maps where a file lands to its source now, e.g. { "android/res/raw/chime.mp3": "assets/chime.mp3" }. google-services.json goes to native.android.googleServices, and GoogleService-Info.plist is no longer needed.`);
930
+ if (!AkanAppConfig.#fileTargetPattern.test(to))
931
+ throw new Error(`${where}.files["${to}"] in ${configPath} must land under ios/<path>, android/res/<type>/<file> or android/assets/<path>.`);
932
+ }
933
+ return files;
934
+ }
935
+ static #mergeNative(base, override) {
936
+ const plugins = [...new Set([...base?.plugins ?? [], ...override?.plugins ?? []])];
937
+ const ios = { ...base?.ios, ...override?.ios };
938
+ const android = {
939
+ ...base?.android,
940
+ ...override?.android
941
+ };
942
+ for (const key of ["manifest", "application", "activity"]) {
943
+ const merged = [...base?.android?.[key] ?? [], ...override?.android?.[key] ?? []];
944
+ if (merged.length)
945
+ android[key] = merged;
946
+ else
947
+ delete android[key];
948
+ }
949
+ return {
950
+ ...plugins.length ? { plugins } : {},
951
+ ...Object.keys(ios).length ? { ios } : {},
952
+ ...Object.keys(android).length ? { android } : {}
593
953
  };
594
954
  }
595
955
  #defaultMobileTargetName(rawTargets) {
@@ -695,7 +1055,7 @@ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
695
1055
  static async from(app, { bustImportCache = false } = {}) {
696
1056
  const [configImp, baseDevEnv, libs, rootPackageJson] = await Promise.all([
697
1057
  AkanAppConfig.importConfigModule(app.cwdPath, { bustImportCache }),
698
- WorkspaceExecutor.getBaseDevEnv(path2.join(app.workspace.workspaceRoot, ".env")),
1058
+ WorkspaceExecutor.getBaseDevEnv(path4.join(app.workspace.workspaceRoot, ".env")),
699
1059
  app.workspace.getLibs(),
700
1060
  app.workspace.getPackageJson()
701
1061
  ]);
@@ -736,429 +1096,116 @@ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
736
1096
  ];
737
1097
  }
738
1098
  getDatabaseModeRuntimePackages(databaseMode) {
739
- return [...DatabaseModes.drivers[databaseMode]];
740
- }
741
- getMissingDatabaseModeDependencySpecs(databaseMode) {
742
- return this.#getMissingDependencySpecs(this.getDatabaseModeRuntimePackages(databaseMode));
743
- }
744
- resolveDatabaseMode() {
745
- return DatabaseModes.resolve({
746
- requested: process.env.AKAN_DATABASE_MODE,
747
- declared: this.database.modes.join(","),
748
- local: true
749
- });
750
- }
751
- static #database(app, config) {
752
- const declared = config?.database?.modes?.filter((mode) => !!mode);
753
- const modes = declared?.length ? declared : ["single"];
754
- return { modes: DatabaseModes.parseList(modes.join(","), `database.modes in apps/${app.name}/akan.config.ts`) };
755
- }
756
- getMobileRuntimePackages() {
757
- return [...new Set([...MOBILE_RUNTIME_PACKAGES, ...MOBILE_APP_CAPACITOR_PLUGINS])];
758
- }
759
- getMissingMobileDependencySpecs() {
760
- return this.#getMissingDependencySpecs(this.getMobileRuntimePackages());
761
- }
762
- getMobileAppCapacitorPlugins() {
763
- return [...MOBILE_APP_CAPACITOR_PLUGINS];
764
- }
765
- #getMissingDependencySpecs(libs) {
766
- const rootDependencies = {
767
- ...this.rootPackageJson.dependencies,
768
- ...this.rootPackageJson.devDependencies
769
- };
770
- return libs.filter((lib) => !rootDependencies[lib]).map((lib) => `${lib}@${this.#resolveProductionDependencyVersion(lib)}`);
771
- }
772
- get akanVersion() {
773
- return getAkanPackageJson().version;
774
- }
775
- getProductionPackageJson(data = {}) {
776
- return {
777
- name: this.app.name,
778
- description: this.app.name,
779
- version: "1.0.0",
780
- main: "./main.js",
781
- dependencies: Object.fromEntries([...new Set(this.#getProductionRuntimePackages())].map((lib) => [
782
- lib,
783
- this.#resolveProductionDependencyVersion(lib)
784
- ])),
785
- ...data
786
- };
787
- }
788
- }
789
- var akanPackageJson = null;
790
- function getAkanPackageJson() {
791
- if (akanPackageJson)
792
- return akanPackageJson;
793
- const sourceDir = path2.dirname(fileURLToPath(import.meta.url));
794
- const packageJsonPaths = [
795
- path2.join(sourceDir, "../../../akanjs/package.json"),
796
- path2.join(process.cwd(), "pkgs/akanjs/package.json"),
797
- path2.join(path2.dirname(Bun.main), "node_modules/akanjs/package.json")
798
- ];
799
- try {
800
- packageJsonPaths.unshift(Bun.resolveSync("akanjs/package.json", sourceDir));
801
- } catch {}
802
- for (const packageJsonPath of packageJsonPaths) {
803
- try {
804
- akanPackageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
805
- return akanPackageJson;
806
- } catch {}
807
- }
808
- akanPackageJson = {
809
- name: "akanjs",
810
- version: "0.0.0",
811
- description: "akanjs",
812
- dependencies: {}
813
- };
814
- return akanPackageJson;
815
- }
816
- function mergeImageConfig(config = {}) {
817
- return {
818
- ...DEFAULT_AKAN_IMAGE_CONFIG,
819
- ...config,
820
- deviceSizes: config.deviceSizes ?? DEFAULT_AKAN_IMAGE_CONFIG.deviceSizes,
821
- imageSizes: config.imageSizes ?? DEFAULT_AKAN_IMAGE_CONFIG.imageSizes,
822
- formats: config.formats ?? DEFAULT_AKAN_IMAGE_CONFIG.formats,
823
- qualities: config.qualities ?? DEFAULT_AKAN_IMAGE_CONFIG.qualities,
824
- remotePatterns: config.remotePatterns ?? DEFAULT_AKAN_IMAGE_CONFIG.remotePatterns,
825
- localPatterns: config.localPatterns ?? DEFAULT_AKAN_IMAGE_CONFIG.localPatterns
826
- };
827
- }
828
-
829
- class AkanLibConfig {
830
- lib;
831
- externalLibs;
832
- docker;
833
- assets;
834
- plugins;
835
- constructor(lib, config, plugins = []) {
836
- this.lib = lib;
837
- this.externalLibs = config?.externalLibs ?? [];
838
- this.docker = { preRuns: config?.docker?.preRuns ?? [], postRuns: config?.docker?.postRuns ?? [] };
839
- this.assets = { keepFonts: normalizeKeepFonts(config?.assets?.keepFonts) };
840
- this.plugins = plugins;
841
- }
842
- static async from(lib, { bustImportCache = false } = {}) {
843
- const configImp = await AkanAppConfig.importConfigModule(lib.cwdPath, { bustImportCache });
844
- const resolved = typeof configImp === "function" ? configImp(lib) : configImp;
845
- const { plugins, ...config } = resolved ?? {};
846
- return new AkanLibConfig(lib, config, plugins ?? []);
847
- }
848
- }
849
- //! need to refactor
850
- var shiftBuildNum = async (app, delta) => {
851
- const { buildNum } = (await AkanAppConfig.from(app)).mobile;
852
- const akanConfigPath = path2.join(app.cwdPath, "akan.config.ts");
853
- const akanConfig = fs.readFileSync(akanConfigPath, "utf8");
854
- fs.writeFileSync(akanConfigPath, akanConfig.replace(`buildNum: ${buildNum}`, `buildNum: ${buildNum + delta}`));
855
- };
856
- var increaseBuildNum = async (app) => await shiftBuildNum(app, 1);
857
- var decreaseBuildNum = async (app) => await shiftBuildNum(app, -1);
858
- // pkgs/@akanjs/devkit/artifact/implicitRootLayout.ts
859
- import path4 from "path";
860
-
861
- // pkgs/@akanjs/devkit/transforms/asyncDefaultExportDetector.ts
862
- import fs2 from "fs";
863
- import path3 from "path";
864
-
865
- class AsyncDefaultExportDetector {
866
- static #typescriptLoad;
867
- static #loadTypescript() {
868
- AsyncDefaultExportDetector.#typescriptLoad ??= import("typescript").then((mod) => mod.default ?? mod);
869
- return AsyncDefaultExportDetector.#typescriptLoad;
870
- }
871
- static async detect(moduleAbsPath) {
872
- try {
873
- const typescript = await AsyncDefaultExportDetector.#loadTypescript();
874
- const source = fs2.readFileSync(path3.resolve(moduleAbsPath), "utf8");
875
- const sourceFile = typescript.createSourceFile(moduleAbsPath, source, typescript.ScriptTarget.Latest, true, /\.[tj]sx$/.test(moduleAbsPath) ? typescript.ScriptKind.TSX : typescript.ScriptKind.TS);
876
- return new AsyncDefaultExportDetector(typescript).detectInSourceFile(sourceFile);
877
- } catch {
878
- return false;
879
- }
880
- }
881
- #ts;
882
- constructor(typescript) {
883
- this.#ts = typescript;
884
- }
885
- detectInSourceFile(sourceFile) {
886
- const ts = this.#ts;
887
- const asyncBindings = new Map;
888
- let defaultIdentifier = null;
889
- for (const statement of sourceFile.statements) {
890
- if (ts.isFunctionDeclaration(statement)) {
891
- if (this.#hasModifier(statement, ts.SyntaxKind.DefaultKeyword)) {
892
- return this.#hasModifier(statement, ts.SyntaxKind.AsyncKeyword);
893
- }
894
- if (statement.name) {
895
- asyncBindings.set(statement.name.text, this.#hasModifier(statement, ts.SyntaxKind.AsyncKeyword));
896
- }
897
- continue;
898
- }
899
- if (ts.isVariableStatement(statement)) {
900
- for (const declaration of statement.declarationList.declarations) {
901
- if (!ts.isIdentifier(declaration.name))
902
- continue;
903
- asyncBindings.set(declaration.name.text, this.#isAsyncFunctionExpression(declaration.initializer));
904
- }
905
- continue;
906
- }
907
- if (ts.isExportAssignment(statement)) {
908
- if (this.#isAsyncFunctionExpression(statement.expression))
909
- return true;
910
- if (ts.isCallExpression(statement.expression))
911
- return true;
912
- if (ts.isIdentifier(statement.expression))
913
- defaultIdentifier = statement.expression.text;
914
- continue;
915
- }
916
- if (ts.isExportDeclaration(statement) && statement.exportClause && ts.isNamedExports(statement.exportClause)) {
917
- for (const specifier of statement.exportClause.elements) {
918
- if (specifier.name.text !== "default")
919
- continue;
920
- defaultIdentifier = specifier.propertyName?.text ?? specifier.name.text;
921
- }
922
- }
923
- }
924
- return defaultIdentifier ? asyncBindings.get(defaultIdentifier) === true : false;
925
- }
926
- #hasModifier(node, kind) {
927
- const ts = this.#ts;
928
- return ts.canHaveModifiers(node) && (ts.getModifiers(node)?.some((modifier) => modifier.kind === kind) ?? false);
929
- }
930
- #isAsyncFunctionExpression(node) {
931
- const ts = this.#ts;
932
- return Boolean(node && (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) && this.#hasModifier(node, ts.SyntaxKind.AsyncKeyword));
933
- }
934
- }
935
-
936
- // pkgs/@akanjs/devkit/artifact/implicitRootLayout.ts
937
- var LAYOUT_KEY_RE = /^\.\/(.+\/)?_layout\.(tsx|ts|jsx|js)$/;
938
- var IMPLICIT_LAYOUT_DIR = path4.join(".akan", "generated", "root-layouts");
939
- var IMPLICIT_DICT_DIR = path4.join(".akan", "generated", "dict");
940
- var IMPLICIT_OVERRIDES_DIR = path4.join(".akan", "generated", "overrides");
941
- var OVERRIDES_KEY_RE = /^\.\/(.+\/)?_overrides\.(tsx|ts|jsx|js)$/;
942
- var importSpecifier = (fromAbsPath, toAbsPath) => {
943
- const rel = path4.relative(path4.dirname(fromAbsPath), toAbsPath).split(path4.sep).join("/");
944
- return rel.startsWith(".") ? rel : `./${rel}`;
945
- };
946
- async function writeGeneratedOverridesLayoutFile(opts) {
947
- const filename = `${opts.key.replace(/^\.\//, "").replace(/[^a-zA-Z0-9]+/g, "_")}.tsx`;
948
- const absPath = path4.join(path4.resolve(opts.appCwdPath), IMPLICIT_OVERRIDES_DIR, filename);
949
- const source = `"use client";
950
- import { UiOverrideProvider } from "akanjs/ui";
951
- import { createElement, type ReactNode } from "react";
952
- import value from ${JSON.stringify(importSpecifier(absPath, opts.userAbsPath))};
953
-
954
- export default function AkanUiOverridesLayout({ children }: { children?: ReactNode }) {
955
- return createElement(UiOverrideProvider, { value }, children);
956
- }
957
- `;
958
- await Bun.write(absPath, source);
959
- return absPath;
960
- }
961
- function getRootBoundarySegments(key) {
962
- const match = LAYOUT_KEY_RE.exec(key);
963
- if (!match)
964
- return null;
965
- const prefix = match[1]?.replace(/\/$/, "");
966
- if (!prefix)
967
- return [];
968
- return prefix.split("/").filter(Boolean);
969
- }
970
- function implicitRootLayoutKey(segments) {
971
- return `./${[...segments, "__root_layout"].join("/")}.tsx`;
972
- }
973
- function implicitRootLayoutAbsPath(appCwdPath, segments) {
974
- const filename = segments.length ? `${segments.join("__")}__root_layout.tsx` : "__root_layout.tsx";
975
- return path4.join(path4.resolve(appCwdPath), IMPLICIT_LAYOUT_DIR, filename);
976
- }
977
- function isRootBoundarySegments(segments, basePaths) {
978
- const firstVisibleIndex = segments.findIndex((segment) => !/^\(.+\)$/.test(segment));
979
- if (firstVisibleIndex === -1)
980
- return segments.length <= 1;
981
- if (segments.slice(firstVisibleIndex + 1).some((segment) => /^\(.+\)$/.test(segment)))
982
- return false;
983
- const visible = segments.slice(firstVisibleIndex);
984
- const allowedBasePaths = new Set([...basePaths].map((basePath) => basePath.trim()).filter(Boolean));
985
- return visible.length === 1 && (firstVisibleIndex > 0 || allowedBasePaths.has(visible[0] ?? ""));
986
- }
987
- function findRootBoundaries(pageKeys, appCwdPath, basePaths) {
988
- const boundaries = new Map;
989
- for (const key of pageKeys) {
990
- const segments = getRootBoundarySegments(key);
991
- if (!segments)
992
- continue;
993
- if (!isRootBoundarySegments(segments, basePaths))
994
- continue;
995
- const id = segments.join("/");
996
- boundaries.set(id, {
997
- sourceKey: key,
998
- sourceAbsPath: path4.resolve(appCwdPath, "page", key.replace(/^\.\//, "")),
999
- segments
1099
+ return [...DatabaseModes.drivers[databaseMode]];
1100
+ }
1101
+ getMissingDatabaseModeDependencySpecs(databaseMode) {
1102
+ return this.#getMissingDependencySpecs(this.getDatabaseModeRuntimePackages(databaseMode));
1103
+ }
1104
+ resolveDatabaseMode() {
1105
+ return DatabaseModes.resolve({
1106
+ requested: process.env.AKAN_DATABASE_MODE,
1107
+ declared: this.database.modes.join(","),
1108
+ local: true
1000
1109
  });
1001
1110
  }
1002
- if (boundaries.size === 0)
1003
- boundaries.set("", { sourceKey: null, sourceAbsPath: null, segments: [] });
1004
- return [...boundaries.values()].sort((a, b) => a.segments.join("/").localeCompare(b.segments.join("/")));
1005
- }
1006
- function hasAncestorRootBoundary(boundary, boundaries) {
1007
- return boundaries.some((candidate) => candidate !== boundary && candidate.segments.length < boundary.segments.length && candidate.segments.every((segment, index) => boundary.segments[index] === segment));
1008
- }
1009
- function findExplicitRootLayoutAbsPath(pageKeys, appCwdPath) {
1010
- const rootLayoutKey = pageKeys.find((key) => {
1011
- const segments = getRootBoundarySegments(key);
1012
- return segments !== null && segments.length === 0;
1013
- });
1014
- return rootLayoutKey ? path4.resolve(appCwdPath, "page", rootLayoutKey.replace(/^\.\//, "")) : null;
1015
- }
1016
- async function assertEnvClientConvention(appCwdPath, appName) {
1017
- const envPath = path4.join(appCwdPath, "env", "env.client.ts");
1018
- if (!await Bun.file(envPath).exists()) {
1019
- throw new Error(`[route-convention] app "${appName}" must provide env/env.client.ts exporting "env" for generated System.Provider`);
1111
+ static #database(app, config) {
1112
+ const declared = config?.database?.modes?.filter((mode) => !!mode);
1113
+ const modes = declared?.length ? declared : ["single"];
1114
+ return { modes: DatabaseModes.parseList(modes.join(","), `database.modes in apps/${app.name}/akan.config.ts`) };
1115
+ }
1116
+ #getMissingDependencySpecs(libs) {
1117
+ const rootDependencies = {
1118
+ ...this.rootPackageJson.dependencies,
1119
+ ...this.rootPackageJson.devDependencies
1120
+ };
1121
+ return libs.filter((lib) => !rootDependencies[lib]).map((lib) => `${lib}@${this.#resolveProductionDependencyVersion(lib)}`);
1122
+ }
1123
+ get akanVersion() {
1124
+ return getAkanPackageJson().version;
1125
+ }
1126
+ getProductionPackageJson(data = {}) {
1127
+ return {
1128
+ name: this.app.name,
1129
+ description: this.app.name,
1130
+ version: "1.0.0",
1131
+ main: "./main.js",
1132
+ dependencies: Object.fromEntries([...new Set(this.#getProductionRuntimePackages())].map((lib) => [
1133
+ lib,
1134
+ this.#resolveProductionDependencyVersion(lib)
1135
+ ])),
1136
+ ...data
1137
+ };
1020
1138
  }
1021
1139
  }
1022
- async function writeGeneratedDictionaryMacroFile(appCwdPath, appName) {
1023
- const absPath = path4.join(path4.resolve(appCwdPath), IMPLICIT_DICT_DIR, "useDict.ts");
1024
- await Bun.write(absPath, `import { getAllDictionary } from "@apps/${appName}/lib/dict" with { type: "macro" };
1025
-
1026
- export const allDictionary = getAllDictionary();
1027
- `);
1028
- return absPath;
1140
+ var akanPackageJson = null;
1141
+ function getAkanPackageJson() {
1142
+ if (akanPackageJson)
1143
+ return akanPackageJson;
1144
+ const sourceDir = path4.dirname(fileURLToPath(import.meta.url));
1145
+ const packageJsonPaths = [
1146
+ path4.join(sourceDir, "../../../akanjs/package.json"),
1147
+ path4.join(process.cwd(), "pkgs/akanjs/package.json"),
1148
+ path4.join(path4.dirname(Bun.main), "node_modules/akanjs/package.json")
1149
+ ];
1150
+ try {
1151
+ packageJsonPaths.unshift(Bun.resolveSync("akanjs/package.json", sourceDir));
1152
+ } catch {}
1153
+ for (const packageJsonPath of packageJsonPaths) {
1154
+ try {
1155
+ akanPackageJson = JSON.parse(fs2.readFileSync(packageJsonPath, "utf8"));
1156
+ return akanPackageJson;
1157
+ } catch {}
1158
+ }
1159
+ akanPackageJson = {
1160
+ name: "akanjs",
1161
+ version: "0.0.0",
1162
+ description: "akanjs",
1163
+ dependencies: {}
1164
+ };
1165
+ return akanPackageJson;
1029
1166
  }
1030
- async function writeGeneratedRootLayoutFile(opts) {
1031
- await assertEnvClientConvention(opts.appCwdPath, opts.appName);
1032
- const dictMacroAbsPath = opts.includeSystemProvider ? await writeGeneratedDictionaryMacroFile(opts.appCwdPath, opts.appName) : null;
1033
- const absPath = implicitRootLayoutAbsPath(opts.appCwdPath, opts.boundary.segments);
1034
- const dictMacroSpecifier = dictMacroAbsPath ? importSpecifier(absPath, dictMacroAbsPath) : null;
1035
- const sourceSpecifier = opts.boundary.sourceAbsPath ? importSpecifier(absPath, opts.boundary.sourceAbsPath) : null;
1036
- const inheritedSourceAbsPath = opts.rootSourceAbsPath && opts.rootSourceAbsPath !== opts.boundary.sourceAbsPath ? opts.rootSourceAbsPath : null;
1037
- const inheritedSourceSpecifier = inheritedSourceAbsPath ? importSpecifier(absPath, inheritedSourceAbsPath) : null;
1038
- const clientImport = opts.includeStInit ? `import { st } from "@apps/${opts.appName}/client";
1039
- void st;
1040
- ` : `import "@apps/${opts.appName}/client";
1041
- `;
1042
- const inheritedLabel = inheritedSourceAbsPath ? path4.relative(opts.appCwdPath, inheritedSourceAbsPath) : "";
1043
- const inheritedImport = inheritedSourceSpecifier ? `import * as inheritedModule from ${JSON.stringify(inheritedSourceSpecifier)};
1044
- const inheritedLayout = resolveRouteModule(inheritedModule as never, ${JSON.stringify(inheritedLabel)}).module as LayoutModule;
1045
- ` : `const inheritedLayout: LayoutModule = {};
1046
- `;
1047
- const prefix = opts.boundary.segments.find((segment) => !/^\(.+\)$/.test(segment)) ?? null;
1048
- const userLabel = opts.boundary.sourceAbsPath ? path4.relative(opts.appCwdPath, opts.boundary.sourceAbsPath) : "";
1049
- const userImport = sourceSpecifier ? `import * as userModule from ${JSON.stringify(sourceSpecifier)};
1050
- const userLayout = resolveRouteModule(userModule as never, ${JSON.stringify(userLabel)}).module as LayoutModule;
1051
- const UserLayout = userLayout.default as (props: LayoutProps) => ReactNode | Promise<ReactNode>;
1052
- ` : `const UserLayout = ({ children }: LayoutProps) => children;
1053
- const userLayout: LayoutModule = {};
1054
- `;
1055
- const isAsyncUserLayout = opts.boundary.sourceAbsPath ? await AsyncDefaultExportDetector.detect(opts.boundary.sourceAbsPath) : false;
1056
- const userLayoutElement = "<UserLayout params={params} searchParams={searchParams}>{children}</UserLayout>";
1057
- const layoutSignature = isAsyncUserLayout ? "export default async function GeneratedLayout" : "export default function GeneratedLayout";
1058
- const layoutBinding = isAsyncUserLayout ? ` const layout =
1059
- process.env.AKAN_PUBLIC_RENDER_ENV === "csr"
1060
- ? await UserLayout({ params, searchParams, children })
1061
- : ${userLayoutElement};
1062
- ` : "";
1063
- const layoutChild = isAsyncUserLayout ? "{layout}" : userLayoutElement;
1064
- const layoutReturn = isAsyncUserLayout ? "layout" : userLayoutElement;
1065
- const layoutExports = `export async function generateHead(props: PageProps) {
1066
- if (userLayout.generateHead) return userLayout.generateHead(props);
1067
- if (userLayout.head !== undefined) return userLayout.head;
1068
- if (inheritedLayout.generateHead) return inheritedLayout.generateHead(props);
1069
- return inheritedLayout.head;
1167
+ function mergeImageConfig(config = {}) {
1168
+ return {
1169
+ ...DEFAULT_AKAN_IMAGE_CONFIG,
1170
+ ...config,
1171
+ deviceSizes: config.deviceSizes ?? DEFAULT_AKAN_IMAGE_CONFIG.deviceSizes,
1172
+ imageSizes: config.imageSizes ?? DEFAULT_AKAN_IMAGE_CONFIG.imageSizes,
1173
+ formats: config.formats ?? DEFAULT_AKAN_IMAGE_CONFIG.formats,
1174
+ qualities: config.qualities ?? DEFAULT_AKAN_IMAGE_CONFIG.qualities,
1175
+ remotePatterns: config.remotePatterns ?? DEFAULT_AKAN_IMAGE_CONFIG.remotePatterns,
1176
+ localPatterns: config.localPatterns ?? DEFAULT_AKAN_IMAGE_CONFIG.localPatterns
1177
+ };
1070
1178
  }
1071
1179
 
1072
- export const NotFound = userLayout.NotFound ?? inheritedLayout.NotFound;
1073
- export const Error = userLayout.Error ?? inheritedLayout.Error;
1074
- export const pageConfig = userLayout.pageConfig ?? inheritedLayout.pageConfig;
1075
-
1076
- ${layoutSignature}({ children, params, searchParams }: LayoutProps) {
1077
- ${layoutBinding} return `;
1078
- const source = opts.includeSystemProvider ? `import type { LayoutModule, LayoutProps, PageProps } from "akanjs/client";
1079
- import { loadFonts, resolveRouteModule } from "akanjs/client";
1080
- import type { ReactNode } from "react";
1081
- import { System } from "akanjs/ui";
1082
- import { env } from "@apps/${opts.appName}/env/env.client";
1083
- import { allDictionary } from ${JSON.stringify(dictMacroSpecifier)};
1084
- ${clientImport}${inheritedImport}${userImport}
1085
- const userFonts = userLayout.fonts ?? inheritedLayout.fonts ?? [];
1086
- const defaultFonts = userFonts.filter((font) => font.default);
1087
- if (defaultFonts.length > 1) throw new Error("[route-convention] only one default font is allowed per root layout");
1088
- const defaultFont = defaultFonts[0];
1089
- const defaultFontClassName = defaultFont ? (defaultFont.className ?? \`font-\${defaultFont.name}\`) : undefined;
1090
-
1091
- ${layoutExports}(
1092
- <System.Provider
1093
- of={GeneratedLayout as never}
1094
- appName=${JSON.stringify(opts.appName)}
1095
- ${prefix ? `prefix=${JSON.stringify(prefix)}
1096
- ` : ""}params={params}
1097
- manifest={userLayout.manifest ?? inheritedLayout.manifest}
1098
- env={env}
1099
- theme={userLayout.theme ?? inheritedLayout.theme}
1100
- fonts={loadFonts(userFonts)}
1101
- className={defaultFontClassName}
1102
- layoutStyle={userLayout.layoutStyle ?? inheritedLayout.layoutStyle}
1103
- reconnect={userLayout.reconnect ?? inheritedLayout.reconnect ?? false}
1104
- wsConnect={userLayout.wsConnect ?? inheritedLayout.wsConnect ?? true}
1105
- allDictionary={process.env.AKAN_PUBLIC_RENDER_ENV === "ssr" ? allDictionary : undefined}
1106
- >
1107
- ${layoutChild}
1108
- </System.Provider>
1109
- );
1110
- }
1111
- ` : `import type { LayoutModule, LayoutProps, PageProps } from "akanjs/client";
1112
- import { resolveRouteModule } from "akanjs/client";
1113
- import type { ReactNode } from "react";
1114
- ${inheritedImport}${userImport}
1115
- ${layoutExports}${layoutReturn};
1116
- }
1117
- `;
1118
- await Bun.write(absPath, source);
1119
- return absPath;
1120
- }
1121
- async function resolveSsrPageEntries(opts) {
1122
- const absPageDir = path4.resolve(opts.appCwdPath, "page");
1123
- const hasSt = await Bun.file(path4.join(opts.appCwdPath, "lib", "st.ts")).exists();
1124
- const basePaths = opts.basePaths ?? [];
1125
- const rootSourceAbsPath = findExplicitRootLayoutAbsPath(opts.pageKeys, opts.appCwdPath);
1126
- const rootBoundaries = findRootBoundaries(opts.pageKeys, opts.appCwdPath, basePaths);
1127
- const rootLayoutKeys = new Set(opts.pageKeys.filter((key) => {
1128
- const segments = getRootBoundarySegments(key);
1129
- return segments !== null && isRootBoundarySegments(segments, basePaths);
1130
- }));
1131
- const base = await Promise.all(opts.pageKeys.filter((key) => !rootLayoutKeys.has(key)).map(async (key) => {
1132
- const userAbsPath = path4.resolve(absPageDir, key);
1133
- if (OVERRIDES_KEY_RE.test(key)) {
1134
- const moduleAbsPath = await writeGeneratedOverridesLayoutFile({
1135
- appCwdPath: opts.appCwdPath,
1136
- key,
1137
- userAbsPath
1138
- });
1139
- return { key, moduleAbsPath, seedAbsPaths: [userAbsPath] };
1140
- }
1141
- return { key, moduleAbsPath: userAbsPath };
1142
- }));
1143
- const generated = await Promise.all(rootBoundaries.map(async (boundary) => ({
1144
- key: implicitRootLayoutKey(boundary.segments),
1145
- moduleAbsPath: await writeGeneratedRootLayoutFile({
1146
- appCwdPath: opts.appCwdPath,
1147
- appName: opts.appName,
1148
- boundary,
1149
- rootSourceAbsPath,
1150
- includeStInit: hasSt && boundary.segments.length === 0,
1151
- includeSystemProvider: !hasAncestorRootBoundary(boundary, rootBoundaries)
1152
- }),
1153
- seedAbsPaths: [...new Set([boundary.sourceAbsPath, rootSourceAbsPath].filter((absPath) => absPath !== null))]
1154
- })));
1155
- return [...base, ...generated].sort((a, b) => a.key.localeCompare(b.key));
1156
- }
1157
- async function resolveSsrPageEntriesForApp(app, pageKeys) {
1158
- const config = await app.getConfig();
1159
- return resolveSsrPageEntries({ appCwdPath: app.cwdPath, appName: app.name, pageKeys, basePaths: config.basePaths });
1180
+ class AkanLibConfig {
1181
+ lib;
1182
+ externalLibs;
1183
+ docker;
1184
+ assets;
1185
+ plugins;
1186
+ constructor(lib, config, plugins = []) {
1187
+ this.lib = lib;
1188
+ this.externalLibs = config?.externalLibs ?? [];
1189
+ this.docker = { preRuns: config?.docker?.preRuns ?? [], postRuns: config?.docker?.postRuns ?? [] };
1190
+ this.assets = { keepFonts: normalizeKeepFonts(config?.assets?.keepFonts) };
1191
+ this.plugins = plugins;
1192
+ }
1193
+ static async from(lib, { bustImportCache = false } = {}) {
1194
+ const configImp = await AkanAppConfig.importConfigModule(lib.cwdPath, { bustImportCache });
1195
+ const resolved = typeof configImp === "function" ? configImp(lib) : configImp;
1196
+ const { plugins, ...config } = resolved ?? {};
1197
+ return new AkanLibConfig(lib, config, plugins ?? []);
1198
+ }
1160
1199
  }
1161
-
1200
+ //! need to refactor
1201
+ var shiftBuildNum = async (app, delta) => {
1202
+ const { buildNum } = (await AkanAppConfig.from(app)).mobile;
1203
+ const akanConfigPath = path4.join(app.cwdPath, "akan.config.ts");
1204
+ const akanConfig = fs2.readFileSync(akanConfigPath, "utf8");
1205
+ fs2.writeFileSync(akanConfigPath, akanConfig.replace(`buildNum: ${buildNum}`, `buildNum: ${buildNum + delta}`));
1206
+ };
1207
+ var increaseBuildNum = async (app) => await shiftBuildNum(app, 1);
1208
+ var decreaseBuildNum = async (app) => await shiftBuildNum(app, -1);
1162
1209
  // pkgs/@akanjs/devkit/linter.ts
1163
1210
  import { spawn } from "child_process";
1164
1211
  import { existsSync, readFileSync } from "fs";
@@ -1512,7 +1559,6 @@ var resolveRepoName = (workspaceRoot) => {
1512
1559
  };
1513
1560
 
1514
1561
  // pkgs/@akanjs/devkit/scanInfo.ts
1515
- import { rm as rm2 } from "fs/promises";
1516
1562
  import path7 from "path";
1517
1563
 
1518
1564
  // pkgs/@akanjs/devkit/workspaceLayout.ts
@@ -1521,7 +1567,6 @@ var appRootAllowedFiles = new Set([
1521
1567
  "CLAUDE.md",
1522
1568
  "akan.app.json",
1523
1569
  "akan.config.ts",
1524
- "capacitor.config.ts",
1525
1570
  "client.ts",
1526
1571
  "main.ts",
1527
1572
  "package.json",
@@ -1531,12 +1576,9 @@ var appRootAllowedFiles = new Set([
1531
1576
  ]);
1532
1577
  var appRootAllowedDirs = new Set([
1533
1578
  ".akan",
1534
- "android",
1535
1579
  "common",
1536
1580
  "env",
1537
- "ios",
1538
1581
  "lib",
1539
- "mobile",
1540
1582
  "page",
1541
1583
  "plugin",
1542
1584
  "private",
@@ -1588,6 +1630,15 @@ var libFacetRootAllowedFiles = new Set([
1588
1630
  ]);
1589
1631
  var libFacetRootTestPattern = /^[A-Za-z][A-Za-z0-9_-]*\.signal\.(test|spec)\.(ts|tsx)$/;
1590
1632
  var isAllowedLibFacetRootFile = (filename) => libFacetRootAllowedFiles.has(filename) || libFacetRootTestPattern.test(filename);
1633
+ var retiredAppRootEntries = new Set([
1634
+ "android",
1635
+ "ios",
1636
+ "mobile",
1637
+ "capacitor.config.ts",
1638
+ "capacitor.config.js",
1639
+ "capacitor.config.json"
1640
+ ]);
1641
+ var rootEntryHintOf = (type, name) => type === "app" && retiredAppRootEntries.has(name) ? "a Capacitor-era entry; the native runtime generates its projects under .akan/mobile/<target>, so delete it" : null;
1591
1642
  var isScannedRootEntry = (type, name) => !name.startsWith(".") || rootAllowedDirs[type].has(name);
1592
1643
 
1593
1644
  // pkgs/@akanjs/devkit/scanInfo.ts
@@ -1605,7 +1656,6 @@ var serviceFileTypes = [
1605
1656
  var databaseFileTypes = ["constant", "dictionary", "document", ...serviceFileTypes.slice(1)];
1606
1657
  var uiFileTypes = new Set(["template", "unit", "util", "view", "zone"]);
1607
1658
  var fileTypeOf = (filename) => databaseFileTypes.find((type) => filename.endsWith(uiFileTypes.has(type) ? `.${type[0].toUpperCase()}${type.slice(1)}.tsx` : `.${type}.ts`));
1608
- var generatedRootCapacitorConfigFiles = ["capacitor.config.js", "capacitor.config.json"];
1609
1659
  var internalLibDirs = new Set(["__lib", "__scalar"]);
1610
1660
  var moduleNonUiFileTypes = {
1611
1661
  database: new Set(["constant", "dictionary", "document", "service", "signal", "store"]),
@@ -1619,17 +1669,11 @@ var moduleUiFileTypes = {
1619
1669
  };
1620
1670
  var createDependencyScanner = async (exec) => (await import("./dependencyScanner-ye2e6pdz.js")).TypeScriptDependencyScanner.from(exec);
1621
1671
  var getScanPath = (exec, relativePath) => path7.posix.join(`${exec.type}s`, exec.name, relativePath.split(path7.sep).join("/"));
1622
- async function clearGeneratedRootCapacitorConfigs(exec) {
1623
- if (exec.type !== "app")
1624
- return;
1625
- await Promise.all(generatedRootCapacitorConfigFiles.map((filename) => rm2(exec.getPath(filename), { force: true })));
1626
- }
1627
1672
  var getModuleNameFromPath = (kind, modulePath) => {
1628
1673
  const dirname = path7.basename(modulePath);
1629
1674
  return kind === "service" ? dirname.replace(/^_+/, "") : dirname;
1630
1675
  };
1631
1676
  async function assertScanConvention(exec, libRoot) {
1632
- await clearGeneratedRootCapacitorConfigs(exec);
1633
1677
  const violations = [];
1634
1678
  const addViolation = (relativePath, reason) => {
1635
1679
  violations.push(`${getScanPath(exec, relativePath)}: ${reason}`);
@@ -1639,10 +1683,10 @@ async function assertScanConvention(exec, libRoot) {
1639
1683
  const { files, dirs } = await exec.getFilesAndDirs(".");
1640
1684
  for (const filename of files)
1641
1685
  if (!allowedRootFiles.has(filename))
1642
- addViolation(filename, `unsupported ${exec.type} root file`);
1686
+ addViolation(filename, rootEntryHintOf(exec.type, filename) ?? `unsupported ${exec.type} root file`);
1643
1687
  for (const dirname of dirs)
1644
1688
  if (!allowedRootDirs.has(dirname))
1645
- addViolation(dirname, `unsupported ${exec.type} root folder`);
1689
+ addViolation(dirname, rootEntryHintOf(exec.type, dirname) ?? `unsupported ${exec.type} root folder`);
1646
1690
  if (exec.type === "lib")
1647
1691
  for (const { relativePath, reason } of await exec.getPageConventionViolations())
1648
1692
  addViolation(relativePath, reason);
@@ -3203,16 +3247,6 @@ class AppExecutor extends SysExecutor {
3203
3247
  return true;
3204
3248
  });
3205
3249
  }
3206
- async getPluginRuntimePackages() {
3207
- const plugins = await this.collectPlugins();
3208
- const appConfig = await this.getConfig();
3209
- const ctx = {
3210
- appName: this.name,
3211
- mobile: appConfig.mobile,
3212
- hasMobilePermission: (permission) => Object.values(appConfig.mobile.targets).some((target) => target.permissions?.includes(permission) ?? false)
3213
- };
3214
- return [...new Set(plugins.flatMap((plugin) => plugin.runtimePackages?.(ctx) ?? []))];
3215
- }
3216
3250
  async increaseBuildNum() {
3217
3251
  await increaseBuildNum(this);
3218
3252
  }
@@ -3368,4 +3402,4 @@ class ModuleExecutor extends Executor {
3368
3402
  }
3369
3403
  }
3370
3404
 
3371
- export { AGENT_BLOCK_START, AGENT_BLOCK_END, stampBlockVersion, extractBlockVersion, readDevkitVersion, upsertAgentBlock, extractAgentBlock, renderRecipeEntries, collectScopeRecipeSources, renderScopeAgentBlock, CLAUDE_COMMENT_RULE, AsyncDefaultExportDetector, resolveSsrPageEntriesForApp, CodegenLock, Linter, rootAllowedFiles, rootAllowedDirs, isAllowedLibFacetRootFile, isScannedRootEntry, AppInfo, Spinner, CommandExecutionError, Executor, WorkspaceExecutor, SysExecutor, AppExecutor, LibExecutor, PkgExecutor, ModuleExecutor };
3405
+ export { AGENT_BLOCK_START, AGENT_BLOCK_END, stampBlockVersion, extractBlockVersion, readDevkitVersion, upsertAgentBlock, extractAgentBlock, renderRecipeEntries, collectScopeRecipeSources, renderScopeAgentBlock, CLAUDE_COMMENT_RULE, AsyncDefaultExportDetector, resolveSsrPageEntriesForApp, CodegenLock, Linter, rootAllowedFiles, rootAllowedDirs, isAllowedLibFacetRootFile, rootEntryHintOf, isScannedRootEntry, AppInfo, Spinner, Executor, WorkspaceExecutor, SysExecutor, AppExecutor, LibExecutor, PkgExecutor, ModuleExecutor };