@akanjs/cli 3.0.2 → 3.1.0-rc.1

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 (87) hide show
  1. package/.build-stamp +1 -1
  2. package/{CodeAgent-szbjv4gv.js → CodeAgent-kyprmy06.js} +2 -2
  3. package/{CodeTui-5nw86ab6.js → CodeTui-21mtze3j.js} +1 -1
  4. package/{TunnelShare-cwh9ezsm.js → TunnelShare-q4jq08qg.js} +2 -2
  5. package/{agent.command-np1dffkb.js → agent.command-f3gsc1gd.js} +5 -5
  6. package/akanCode.js +2 -2
  7. package/{application.command-gk69v1xs.js → application.command-5s780vnz.js} +37 -15
  8. package/{applicationBuildRunner-wx09n9g6.js → applicationBuildRunner-1yaqsrdq.js} +115 -103
  9. package/buildBatch.proc.js +16 -5
  10. package/cloud.command-yj6xb2a7.js +192 -0
  11. package/cloud.script-h6pvcpa2.js +20 -0
  12. package/{code.command-hmtx14b2.js → code.command-3wkn4ja5.js} +6 -6
  13. package/commandManifest.json +1 -1
  14. package/{context.command-6aqvdx43.js → context.command-9jjn3gcw.js} +13 -13
  15. package/context.runner-atcrzz5b.js +21 -0
  16. package/{guideline.command-axvf68gc.js → guideline.command-dtczrqpf.js} +2 -2
  17. package/guidelines/agentRule/agentRule.instruction.md +10 -2
  18. package/guidelines/conventions/conventions.instruction.md +70 -25
  19. package/guidelines/cssRule/cssRule.instruction.md +19 -0
  20. package/guidelines/mcpRule/mcpRule.instruction.md +72 -12
  21. package/guidelines/queryRule/queryRule.instruction.md +11 -1
  22. package/guidelines/runtimeRule/runtimeRule.instruction.md +97 -16
  23. package/guidelines/ssrRule/ssrRule.instruction.md +10 -7
  24. package/guidelines/transportRule/transportRule.instruction.md +19 -0
  25. package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +6 -6
  26. package/incrementalBuilder.proc.js +24 -3
  27. package/{index-h9ht6s60.js → index-05g3jayg.js} +1 -1
  28. package/{index-jk9xnck6.js → index-1w2qaee2.js} +240 -133
  29. package/{index-g38nhv87.js → index-2etcv0sc.js} +1 -1
  30. package/{index-zyw622y1.js → index-2mx692qq.js} +3 -3
  31. package/{index-50wb2a26.js → index-55mynnqs.js} +354 -129
  32. package/{index-2wc41k5w.js → index-56d2g8ms.js} +2 -2
  33. package/{index-q6ft9fkc.js → index-6jjtnhkw.js} +8 -3
  34. package/index-6w0rbr9d.js +68 -0
  35. package/{index-hxnnfy57.js → index-8ggztp8k.js} +204 -8
  36. package/{index-zwd84m0w.js → index-8r4d7cmt.js} +4 -4
  37. package/{index-j38qkvkk.js → index-ae5pwhgs.js} +12 -10
  38. package/{index-gtjgnv36.js → index-aprt7pkz.js} +6 -9
  39. package/{index-1bjkfvm9.js → index-c1x0be5j.js} +2 -2
  40. package/{index-z750gk1j.js → index-c2yrsxdh.js} +13 -7
  41. package/{index-cb6yc5mb.js → index-c76k723e.js} +2 -2
  42. package/{index-6keyf4sg.js → index-ctanya41.js} +4 -4
  43. package/{index-f4e08ptp.js → index-e0cvze1s.js} +4 -4
  44. package/{index-hp3stk1m.js → index-fm46xrf1.js} +3 -3
  45. package/{index-3xmzr1p1.js → index-gnyf1fqy.js} +2 -2
  46. package/{index-nwd6qzvv.js → index-jx4bzvwg.js} +138 -55
  47. package/{index-jb0hmt3k.js → index-krq7btbt.js} +3 -3
  48. package/{index-94s080qm.js → index-pgj2rt78.js} +139 -1
  49. package/index-q958tqqg.js +77 -0
  50. package/{index-50jjwg8y.js → index-qt22xstj.js} +1 -1
  51. package/index-rc5rwyps.js +201 -0
  52. package/{index-xwprn9nr.js → index-rtct7d43.js} +1 -1
  53. package/{index-bsv901fd.js → index-rwq3ekqc.js} +7 -7
  54. package/{index-1gs9g12n.js → index-v29746vb.js} +4 -4
  55. package/index-ww9bhp67.js +62 -0
  56. package/index.js +22 -21
  57. package/{library.command-9m2rdwwz.js → library.command-gaga5bmq.js} +3 -3
  58. package/{localRegistry.command-kda81jyk.js → localRegistry.command-vsreyy42.js} +9 -9
  59. package/{module.command-q63dffya.js → module.command-7vpf6db4.js} +5 -5
  60. package/{package.command-y2jh0m99.js → package.command-twmvvf69.js} +3 -3
  61. package/package.json +2 -2
  62. package/{page.command-f3e4cwhh.js → page.command-5asq7fdf.js} +3 -3
  63. package/pipeline.command-d0v38572.js +1334 -0
  64. package/{primitive.command-r4kq75qj.js → primitive.command-dyrs2fm8.js} +6 -6
  65. package/projectTypecheck-cvtkrzp2.js +7 -0
  66. package/{quality.command-f7gzh26z.js → quality.command-6emsqpbg.js} +5 -4
  67. package/{repair.command-0qmyr6qa.js → repair.command-8t6aegv5.js} +5 -5
  68. package/{scalar.command-kddn7av4.js → scalar.command-bnggy1x7.js} +4 -4
  69. package/{subspace.command-ft65qvc4.js → subspace.command-md2teygq.js} +158 -91
  70. package/templates/app/env/env.client.type.ts +2 -3
  71. package/templates/app/page/styles.css.template +8 -0
  72. package/templates/appSample/page/task/[taskId]/_index.tsx +2 -2
  73. package/templates/appSample/page/task/[taskId]/edit.tsx +2 -2
  74. package/templates/appSample/page/task/_index.tsx +2 -2
  75. package/templates/crudPages/[__model__Id]/edit/page.tsx +2 -2
  76. package/templates/crudPages/[__model__Id]/page.tsx +3 -3
  77. package/templates/crudPages/page.tsx +2 -2
  78. package/templates/crudSinglePage/page.tsx +3 -3
  79. package/templates/lib/cnst.ts +2 -2
  80. package/templates/lib/sig.ts +3 -3
  81. package/{tunnel.command-b3ymdk4f.js → tunnel.command-jx3tdm2d.js} +16 -13
  82. package/{workflow.command-z1srcdfw.js → workflow.command-jx0wag5r.js} +10 -10
  83. package/{workspace.command-vp6vmx3z.js → workspace.command-yxd7rr7g.js} +57 -27
  84. package/cloud.command-tw06613r.js +0 -95
  85. package/cloud.script-64gzba8r.js +0 -20
  86. package/context.runner-hnkvv3z3.js +0 -21
  87. package/index-t91a7339.js +0 -104
@@ -4,13 +4,94 @@ import {
4
4
  FileSys2
5
5
  } from "./index-52w0fvh8.js";
6
6
 
7
+ // pkgs/@akanjs/devkit/transforms/serverEnvProfile.ts
8
+ import path from "path";
9
+ var secretKey = /password|passwd|passphrase|^pass$|token|jwt|authorization|cookie|secret|api[-_]?key|private[-_]?key|access[-_]?key|auth[-_]?key|hash[-_]?key|webhook|cred|iap[-_]?verify|service[-_]?account/i;
10
+
11
+ class ServerEnvProfile {
12
+ static namePattern = /^[a-z][a-zA-Z0-9-]*$/;
13
+ static fileName(environment, profile) {
14
+ return `env.server.${environment}.${profile}.ts`;
15
+ }
16
+ static profileFileOf(file, profile) {
17
+ return file.replace(/\.ts$/, `.${profile}.ts`);
18
+ }
19
+ static strip(value, at = "", underSecret = false) {
20
+ if (typeof value === "string")
21
+ return underSecret && value.trim() ? { value: undefined, stripped: [at] } : { value, stripped: [] };
22
+ if (!value || typeof value !== "object")
23
+ return { value, stripped: [] };
24
+ const stripped = [];
25
+ const strip = (key, child) => {
26
+ const result = ServerEnvProfile.strip(child, at ? `${at}.${key}` : key, underSecret || secretKey.test(key));
27
+ stripped.push(...result.stripped);
28
+ return result.value;
29
+ };
30
+ if (Array.isArray(value))
31
+ return { value: value.map((child, idx) => strip(String(idx), child)), stripped };
32
+ const kept = Object.fromEntries(Object.entries(value).map(([key, child]) => [key, strip(key, child)]).filter(([, child]) => child !== undefined));
33
+ return { value: kept, stripped };
34
+ }
35
+ static secretValues(value, at = "", underSecret = false) {
36
+ if (typeof value === "string") {
37
+ const named = secretKey.test(at.split(".").at(-1) ?? "");
38
+ const distinct = value.length >= 16 && !/^https?:\/\//.test(value) && !/^[a-z]+(?:[._-][a-z]+)*$/.test(value);
39
+ return underSecret && value.length >= 8 && (named || distinct) ? [{ path: at, value }] : [];
40
+ }
41
+ if (!value || typeof value !== "object")
42
+ return [];
43
+ return Object.entries(value).flatMap(([key, child]) => ServerEnvProfile.secretValues(child, at ? `${at}.${key}` : key, underSecret || secretKey.test(key)));
44
+ }
45
+ static secretPaths(value, at = "", underSecret = false) {
46
+ if (typeof value === "string")
47
+ return underSecret && value.trim() ? [at] : [];
48
+ if (!value || typeof value !== "object")
49
+ return [];
50
+ return Object.entries(value).flatMap(([key, child]) => ServerEnvProfile.secretPaths(child, at ? `${at}.${key}` : key, underSecret || secretKey.test(key)));
51
+ }
52
+ static async evaluate(file, cwd) {
53
+ const script = [
54
+ "const { env } = await import(process.env.AKAN_PROFILE_ENV_FILE);",
55
+ 'const plain = (_, value) => (typeof value === "function" || typeof value === "symbol" ? undefined : typeof value === "bigint" ? String(value) : value);',
56
+ "process.stdout.write(JSON.stringify(env ?? null, plain));"
57
+ ].join(`
58
+ `);
59
+ const child = Bun.spawn([process.execPath, "--eval", script], {
60
+ cwd,
61
+ env: { PATH: process.env.PATH ?? "", HOME: process.env.HOME ?? "", AKAN_PROFILE_ENV_FILE: file },
62
+ stdout: "pipe",
63
+ stderr: "pipe"
64
+ });
65
+ const [stdout, stderr, exitCode] = await Promise.all([
66
+ new Response(child.stdout).text(),
67
+ new Response(child.stderr).text(),
68
+ child.exited
69
+ ]);
70
+ if (exitCode !== 0)
71
+ throw new Error(`${path.basename(file)} could not be evaluated to check it for secrets:
72
+ ${stderr.trim()}`);
73
+ return JSON.parse(stdout || "null");
74
+ }
75
+ static async assertShippable({ envDir, environment, profile }) {
76
+ if (!ServerEnvProfile.namePattern.test(profile))
77
+ throw new Error(`The server env profile "${profile}" must be a lowercase-first name of letters, digits and dashes.`);
78
+ const name = ServerEnvProfile.fileName(environment, profile);
79
+ const file = path.join(envDir, name);
80
+ if (!await Bun.file(file).exists())
81
+ throw new Error(`The server env profile "${profile}" bundles env/${name} in place of env/env.server.${environment}.ts, and there is no such file.`);
82
+ const secrets = ServerEnvProfile.secretPaths(await ServerEnvProfile.evaluate(file, path.dirname(envDir)));
83
+ if (secrets.length)
84
+ throw new Error(`env/${name} holds secret-named values, which a profile's bundle carries to every computer it runs on: ${secrets.join(", ")}. Leave them out, or read them from the process env of the server that needs them.`);
85
+ }
86
+ }
87
+
7
88
  // pkgs/@akanjs/devkit/artifact/implicitRootLayout.ts
8
89
  import { readdir, rm } from "fs/promises";
9
- import path2 from "path";
90
+ import path3 from "path";
10
91
 
11
92
  // pkgs/@akanjs/devkit/transforms/asyncDefaultExportDetector.ts
12
93
  import fs from "fs";
13
- import path from "path";
94
+ import path2 from "path";
14
95
 
15
96
  class AsyncDefaultExportDetector {
16
97
  static #typescriptLoad;
@@ -21,7 +102,7 @@ class AsyncDefaultExportDetector {
21
102
  static async detect(moduleAbsPath) {
22
103
  try {
23
104
  const typescript = await AsyncDefaultExportDetector.#loadTypescript();
24
- const source = fs.readFileSync(path.resolve(moduleAbsPath), "utf8");
105
+ const source = fs.readFileSync(path2.resolve(moduleAbsPath), "utf8");
25
106
  const sourceFile = typescript.createSourceFile(moduleAbsPath, source, typescript.ScriptTarget.Latest, true, /\.[tj]sx$/.test(moduleAbsPath) ? typescript.ScriptKind.TSX : typescript.ScriptKind.TS);
26
107
  return new AsyncDefaultExportDetector(typescript).detectInSourceFile(sourceFile);
27
108
  } catch {
@@ -85,17 +166,17 @@ class AsyncDefaultExportDetector {
85
166
 
86
167
  // pkgs/@akanjs/devkit/artifact/implicitRootLayout.ts
87
168
  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");
169
+ var IMPLICIT_LAYOUT_DIR = path3.join(".akan", "generated", "root-layouts");
170
+ var IMPLICIT_DICT_DIR = path3.join(".akan", "generated", "dict");
171
+ var IMPLICIT_OVERRIDES_DIR = path3.join(".akan", "generated", "overrides");
91
172
  var OVERRIDES_KEY_RE = /^\.\/(.+\/)?_overrides\.(tsx|ts|jsx|js)$/;
92
173
  var importSpecifier = (fromAbsPath, toAbsPath) => {
93
- const rel = path2.relative(path2.dirname(fromAbsPath), toAbsPath).split(path2.sep).join("/");
174
+ const rel = path3.relative(path3.dirname(fromAbsPath), toAbsPath).split(path3.sep).join("/");
94
175
  return rel.startsWith(".") ? rel : `./${rel}`;
95
176
  };
96
177
  async function writeGeneratedOverridesLayoutFile(opts) {
97
178
  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);
179
+ const absPath = path3.join(path3.resolve(opts.appCwdPath), IMPLICIT_OVERRIDES_DIR, filename);
99
180
  const source = `"use client";
100
181
  import { UiOverrideProvider } from "akanjs/ui";
101
182
  import { createElement, type ReactNode } from "react";
@@ -111,7 +192,7 @@ export default function AkanUiOverridesLayout({ children }: { children?: ReactNo
111
192
  async function pruneGeneratedFiles(dirAbsPath, keepAbsPaths) {
112
193
  const keep = new Set(keepAbsPaths);
113
194
  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 })));
195
+ await Promise.all(names.map((name) => path3.join(dirAbsPath, name)).filter((absPath) => !keep.has(absPath)).map((absPath) => rm(absPath, { force: true })));
115
196
  }
116
197
  function getRootBoundarySegments(key) {
117
198
  const match = LAYOUT_KEY_RE.exec(key);
@@ -127,7 +208,7 @@ function implicitRootLayoutKey(segments) {
127
208
  }
128
209
  function implicitRootLayoutAbsPath(appCwdPath, segments) {
129
210
  const filename = segments.length ? `${segments.join("__")}__root_layout.tsx` : "__root_layout.tsx";
130
- return path2.join(path2.resolve(appCwdPath), IMPLICIT_LAYOUT_DIR, filename);
211
+ return path3.join(path3.resolve(appCwdPath), IMPLICIT_LAYOUT_DIR, filename);
131
212
  }
132
213
  function isRootBoundarySegments(segments, basePaths) {
133
214
  const firstVisibleIndex = segments.findIndex((segment) => !/^\(.+\)$/.test(segment));
@@ -150,7 +231,7 @@ function findRootBoundaries(pageKeys, appCwdPath, basePaths) {
150
231
  const id = segments.join("/");
151
232
  boundaries.set(id, {
152
233
  sourceKey: key,
153
- sourceAbsPath: path2.resolve(appCwdPath, "page", key.replace(/^\.\//, "")),
234
+ sourceAbsPath: path3.resolve(appCwdPath, "page", key.replace(/^\.\//, "")),
154
235
  segments
155
236
  });
156
237
  }
@@ -166,16 +247,16 @@ function findExplicitRootLayoutAbsPath(pageKeys, appCwdPath) {
166
247
  const segments = getRootBoundarySegments(key);
167
248
  return segments !== null && segments.length === 0;
168
249
  });
169
- return rootLayoutKey ? path2.resolve(appCwdPath, "page", rootLayoutKey.replace(/^\.\//, "")) : null;
250
+ return rootLayoutKey ? path3.resolve(appCwdPath, "page", rootLayoutKey.replace(/^\.\//, "")) : null;
170
251
  }
171
252
  async function assertEnvClientConvention(appCwdPath, appName) {
172
- const envPath = path2.join(appCwdPath, "env", "env.client.ts");
253
+ const envPath = path3.join(appCwdPath, "env", "env.client.ts");
173
254
  if (!await Bun.file(envPath).exists()) {
174
255
  throw new Error(`[route-convention] app "${appName}" must provide env/env.client.ts exporting "env" for generated System.Provider`);
175
256
  }
176
257
  }
177
258
  async function writeGeneratedDictionaryMacroFile(appCwdPath, appName) {
178
- const absPath = path2.join(path2.resolve(appCwdPath), IMPLICIT_DICT_DIR, "useDict.ts");
259
+ const absPath = path3.join(path3.resolve(appCwdPath), IMPLICIT_DICT_DIR, "useDict.ts");
179
260
  await Bun.write(absPath, `import { getAllDictionary } from "@apps/${appName}/lib/dict" with { type: "macro" };
180
261
 
181
262
  export const allDictionary = getAllDictionary();
@@ -194,13 +275,13 @@ async function writeGeneratedRootLayoutFile(opts) {
194
275
  void st;
195
276
  ` : `import "@apps/${opts.appName}/client";
196
277
  `;
197
- const inheritedLabel = inheritedSourceAbsPath ? path2.relative(opts.appCwdPath, inheritedSourceAbsPath) : "";
278
+ const inheritedLabel = inheritedSourceAbsPath ? path3.relative(opts.appCwdPath, inheritedSourceAbsPath) : "";
198
279
  const inheritedImport = inheritedSourceSpecifier ? `import * as inheritedModule from ${JSON.stringify(inheritedSourceSpecifier)};
199
280
  const inheritedLayout = resolveRouteModule(inheritedModule as never, ${JSON.stringify(inheritedLabel)}).module as LayoutModule;
200
281
  ` : `const inheritedLayout: LayoutModule = {};
201
282
  `;
202
283
  const prefix = opts.boundary.segments.find((segment) => !/^\(.+\)$/.test(segment)) ?? null;
203
- const userLabel = opts.boundary.sourceAbsPath ? path2.relative(opts.appCwdPath, opts.boundary.sourceAbsPath) : "";
284
+ const userLabel = opts.boundary.sourceAbsPath ? path3.relative(opts.appCwdPath, opts.boundary.sourceAbsPath) : "";
204
285
  const userImport = sourceSpecifier ? `import * as userModule from ${JSON.stringify(sourceSpecifier)};
205
286
  const userLayout = resolveRouteModule(userModule as never, ${JSON.stringify(userLabel)}).module as LayoutModule;
206
287
  const UserLayout = userLayout.default as (props: LayoutProps) => ReactNode | Promise<ReactNode>;
@@ -217,11 +298,12 @@ const userLayout: LayoutModule = {};
217
298
  ` : "";
218
299
  const layoutChild = isAsyncUserLayout ? "{layout}" : userLayoutElement;
219
300
  const layoutReturn = isAsyncUserLayout ? "layout" : userLayoutElement;
301
+ const headLayouts = opts.includeSystemProvider ? "[inheritedLayout, userLayout]" : "[userLayout]";
220
302
  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;
303
+ const heads = await Promise.all(
304
+ ${headLayouts}.map((layout) => (layout.generateHead ? layout.generateHead(props) : layout.head)),
305
+ );
306
+ return RouteHead.mergeNodes(heads);
225
307
  }
226
308
 
227
309
  export const NotFound = userLayout.NotFound ?? inheritedLayout.NotFound;
@@ -231,7 +313,7 @@ export const pageConfig = userLayout.pageConfig ?? inheritedLayout.pageConfig;
231
313
  ${layoutSignature}({ children, params, searchParams }: LayoutProps) {
232
314
  ${layoutBinding} return `;
233
315
  const source = opts.includeSystemProvider ? `import type { LayoutModule, LayoutProps, PageProps } from "akanjs/client";
234
- import { loadFonts, resolveRouteModule } from "akanjs/client";
316
+ import { loadFonts, resolveRouteModule, RouteHead } from "akanjs/client";
235
317
  import type { ReactNode } from "react";
236
318
  import { System } from "akanjs/ui";
237
319
  import { env } from "@apps/${opts.appName}/env/env.client";
@@ -243,6 +325,8 @@ if (defaultFonts.length > 1) throw new Error("[route-convention] only one defaul
243
325
  const defaultFont = defaultFonts[0];
244
326
  const defaultFontClassName = defaultFont ? (defaultFont.className ?? \`font-\${defaultFont.name}\`) : undefined;
245
327
  export const theme = userLayout.theme ?? inheritedLayout.theme;
328
+ export const viewport = userLayout.viewport ?? inheritedLayout.viewport;
329
+ export const manifest = userLayout.manifest ?? inheritedLayout.manifest;
246
330
 
247
331
  ${layoutExports}(
248
332
  <System.Provider
@@ -250,9 +334,10 @@ ${layoutExports}(
250
334
  appName=${JSON.stringify(opts.appName)}
251
335
  ${prefix ? `prefix=${JSON.stringify(prefix)}
252
336
  ` : ""}params={params}
253
- manifest={userLayout.manifest ?? inheritedLayout.manifest}
337
+ manifest={manifest}
254
338
  env={env}
255
339
  theme={theme}
340
+ viewport={viewport}
256
341
  fonts={loadFonts(userFonts)}
257
342
  className={defaultFontClassName}
258
343
  layoutStyle={userLayout.layoutStyle ?? inheritedLayout.layoutStyle}
@@ -265,7 +350,7 @@ ${layoutExports}(
265
350
  );
266
351
  }
267
352
  ` : `import type { LayoutModule, LayoutProps, PageProps } from "akanjs/client";
268
- import { resolveRouteModule } from "akanjs/client";
353
+ import { resolveRouteModule, RouteHead } from "akanjs/client";
269
354
  import type { ReactNode } from "react";
270
355
  ${inheritedImport}${userImport}
271
356
  ${layoutExports}${layoutReturn};
@@ -275,8 +360,8 @@ ${layoutExports}${layoutReturn};
275
360
  return absPath;
276
361
  }
277
362
  async function resolveSsrPageEntries(opts) {
278
- const absPageDir = path2.resolve(opts.appCwdPath, "page");
279
- const hasSt = await Bun.file(path2.join(opts.appCwdPath, "lib", "st.ts")).exists();
363
+ const absPageDir = path3.resolve(opts.appCwdPath, "page");
364
+ const hasSt = await Bun.file(path3.join(opts.appCwdPath, "lib", "st.ts")).exists();
280
365
  const basePaths = opts.basePaths ?? [];
281
366
  const rootSourceAbsPath = findExplicitRootLayoutAbsPath(opts.pageKeys, opts.appCwdPath);
282
367
  const rootBoundaries = findRootBoundaries(opts.pageKeys, opts.appCwdPath, basePaths);
@@ -285,7 +370,7 @@ async function resolveSsrPageEntries(opts) {
285
370
  return segments !== null && isRootBoundarySegments(segments, basePaths);
286
371
  }));
287
372
  const base = await Promise.all(opts.pageKeys.filter((key) => !rootLayoutKeys.has(key)).map(async (key) => {
288
- const userAbsPath = path2.resolve(absPageDir, key);
373
+ const userAbsPath = path3.resolve(absPageDir, key);
289
374
  if (OVERRIDES_KEY_RE.test(key)) {
290
375
  const moduleAbsPath = await writeGeneratedOverridesLayoutFile({
291
376
  appCwdPath: opts.appCwdPath,
@@ -308,10 +393,10 @@ async function resolveSsrPageEntries(opts) {
308
393
  }),
309
394
  seedAbsPaths: [...new Set([boundary.sourceAbsPath, rootSourceAbsPath].filter((absPath) => absPath !== null))]
310
395
  })));
311
- const appCwdAbsPath = path2.resolve(opts.appCwdPath);
396
+ const appCwdAbsPath = path3.resolve(opts.appCwdPath);
312
397
  await Promise.all([
313
- pruneGeneratedFiles(path2.join(appCwdAbsPath, IMPLICIT_OVERRIDES_DIR), base.filter(({ key }) => OVERRIDES_KEY_RE.test(key)).map(({ moduleAbsPath }) => moduleAbsPath)),
314
- pruneGeneratedFiles(path2.join(appCwdAbsPath, IMPLICIT_LAYOUT_DIR), generated.map(({ moduleAbsPath }) => moduleAbsPath))
398
+ pruneGeneratedFiles(path3.join(appCwdAbsPath, IMPLICIT_OVERRIDES_DIR), base.filter(({ key }) => OVERRIDES_KEY_RE.test(key)).map(({ moduleAbsPath }) => moduleAbsPath)),
399
+ pruneGeneratedFiles(path3.join(appCwdAbsPath, IMPLICIT_LAYOUT_DIR), generated.map(({ moduleAbsPath }) => moduleAbsPath))
315
400
  ]);
316
401
  return [...base, ...generated].sort((a, b) => a.key.localeCompare(b.key));
317
402
  }
@@ -322,7 +407,7 @@ async function resolveSsrPageEntriesForApp(app, pageKeys) {
322
407
 
323
408
  // pkgs/@akanjs/devkit/codegenLock.ts
324
409
  import { mkdir, open, readFile, rm as rm2, stat } from "fs/promises";
325
- import path3 from "path";
410
+ import path4 from "path";
326
411
  import { Logger } from "akanjs/common";
327
412
 
328
413
  class CodegenLock {
@@ -333,7 +418,7 @@ class CodegenLock {
333
418
  static #logger = new Logger("CodegenLock");
334
419
  static #queue = Promise.resolve();
335
420
  static pathIn(workspaceRoot) {
336
- return path3.join(workspaceRoot, "local", ".akan", CodegenLock.fileName);
421
+ return path4.join(workspaceRoot, "local", ".akan", CodegenLock.fileName);
337
422
  }
338
423
  static async run(workspaceRoot, label, fn) {
339
424
  const ahead = CodegenLock.#queue;
@@ -363,7 +448,7 @@ class CodegenLock {
363
448
  }
364
449
  static async#withFileLock(workspaceRoot, label, fn) {
365
450
  const lockPath = CodegenLock.pathIn(workspaceRoot);
366
- await mkdir(path3.dirname(lockPath), { recursive: true }).catch(() => {
451
+ await mkdir(path4.dirname(lockPath), { recursive: true }).catch(() => {
367
452
  return;
368
453
  });
369
454
  const held = await CodegenLock.#acquire(lockPath, label);
@@ -517,7 +602,7 @@ import {
517
602
  stat as stat2,
518
603
  symlink
519
604
  } from "fs/promises";
520
- import path10 from "path";
605
+ import path12 from "path";
521
606
  import { pathToFileURL } from "url";
522
607
  import {
523
608
  capitalize,
@@ -670,10 +755,10 @@ ${CLAUDE_COMMENT_RULE}
670
755
  `;
671
756
 
672
757
  // pkgs/@akanjs/devkit/akanConfig/akanBin.ts
673
- import path4 from "path";
758
+ import path5 from "path";
674
759
 
675
760
  // pkgs/@akanjs/devkit/akanConfig/types.ts
676
- import { archs, binPlatforms } from "akanjs";
761
+ import { archs, binPlatforms, defaultAppPipeline } from "akanjs";
677
762
 
678
763
  // pkgs/@akanjs/devkit/akanConfig/akanBin.ts
679
764
  class AkanBin {
@@ -721,7 +806,7 @@ class AkanBin {
721
806
  if (!archive)
722
807
  throw new Error(`${at}: file picks the executable out of an archive, and this source is none`);
723
808
  const parts = typeof file === "string" ? file.split(/[\\/]/).filter((part) => part && part !== ".") : [];
724
- if (!parts.length || path4.isAbsolute(file) || parts.includes(".."))
809
+ if (!parts.length || path5.isAbsolute(file) || parts.includes(".."))
725
810
  throw new Error(`${at}: file must be a path inside the archive (got ${JSON.stringify(file)})`);
726
811
  return { ...resolved, file: parts.join("/") };
727
812
  }
@@ -737,12 +822,12 @@ class AkanBin {
737
822
  throw new Error(`${at}: path must name a file`);
738
823
  if (sha256 !== undefined)
739
824
  throw new Error(`${at}: sha256 checks a download, and a path source downloads nothing`);
740
- return { path: path4.resolve(baseDir, localPath) };
825
+ return { path: path5.resolve(baseDir, localPath) };
741
826
  }
742
827
  }
743
828
  // pkgs/@akanjs/devkit/akanConfig/akanConfig.ts
744
829
  import fs2 from "fs";
745
- import path5 from "path";
830
+ import path6 from "path";
746
831
  import { fileURLToPath } from "url";
747
832
  import { DatabaseModes, normalizeRoutePrefix } from "akanjs/base";
748
833
  import { resolveAkanI18nConfig } from "akanjs/common";
@@ -938,8 +1023,8 @@ class AkanAppConfig {
938
1023
  if (basePath && !this.basePaths.has(basePath))
939
1024
  throw new Error(`Native target '${name}' uses unknown basePath '${basePath}' in ${configPath}`);
940
1025
  const { updates } = merged;
941
- if (updates && (!updates.url || !updates.publicKey))
942
- throw new Error(`${where}.updates in ${configPath} has no url or publicKey${named ? "; give them in native.updates or the target's own" : ""}.`);
1026
+ if (updates && (!updates.url || !(updates.publicKey || updates.publicKeys?.length)))
1027
+ throw new Error(`${where}.updates in ${configPath} has no url or publicKeys${named ? "; give them in native.updates or the target's own" : ""}.`);
943
1028
  AkanAppConfig.#assertFiles(own ?? {}, where, configPath);
944
1029
  const {
945
1030
  basePath: _basePath,
@@ -980,12 +1065,64 @@ class AkanAppConfig {
980
1065
  if (server === undefined || typeof server === "boolean")
981
1066
  return desktop;
982
1067
  if (!AkanAppConfig.#isPlainObject(server))
983
- throw new Error(`${where} in ${configPath} must be true or { omit: string[] }.`);
984
- const omit = server.omit;
985
- if (omit !== undefined && (!Array.isArray(omit) || omit.some((name) => typeof name !== "string" || !name.trim())))
986
- throw new Error(`${where}.omit in ${configPath} must be a list of package names.`);
987
- const names = [...new Set((omit ?? []).map((name) => name.trim()))].sort();
988
- return { ...desktop, server: { omit: names } };
1068
+ throw new Error(`${where} in ${configPath} must be true or { omit, profile, modules }.`);
1069
+ const names = (list, key, what) => {
1070
+ if (list === undefined)
1071
+ return [];
1072
+ if (!Array.isArray(list) || list.some((name) => typeof name !== "string" || !name.trim()))
1073
+ throw new Error(`${where}.${key} in ${configPath} must be a list of ${what}.`);
1074
+ return [...new Set(list.map((name) => name.trim()))].sort();
1075
+ };
1076
+ const { profile } = server;
1077
+ if (profile !== undefined && (typeof profile !== "string" || !ServerEnvProfile.namePattern.test(profile)))
1078
+ throw new Error(`${where}.profile in ${configPath} names env/env.server.<env>.<profile>.ts: a lowercase-first name of letters, digits and dashes.`);
1079
+ const modules = names(server.modules, "modules", "module names or @<operationMode> selectors");
1080
+ const env = AkanAppConfig.#desktopServerEnv(server.env, where, configPath);
1081
+ const { restart } = server;
1082
+ if (restart !== undefined && restart !== "forever" && !(Number.isInteger(restart) && restart > 0))
1083
+ throw new Error(`${where}.restart in ${configPath} must be "forever" or a count of crashes in a row from 1.`);
1084
+ const watchdog = AkanAppConfig.#desktopServerWatchdog(server.watchdog, where, configPath);
1085
+ return {
1086
+ ...desktop,
1087
+ server: {
1088
+ omit: names(server.omit, "omit", "package names"),
1089
+ ...profile ? { profile } : {},
1090
+ ...modules.length ? { modules } : {},
1091
+ ...Object.keys(env).length ? { env } : {},
1092
+ ...restart !== undefined ? { restart } : {},
1093
+ ...watchdog ? { watchdog } : {}
1094
+ }
1095
+ };
1096
+ }
1097
+ static #stagedServerEnv = new Set([
1098
+ "AKAN_PUBLIC_OPERATION_MODE",
1099
+ "AKAN_DATABASE_MODE",
1100
+ "AKAN_DATABASE_MODES",
1101
+ "AKAN_SSR",
1102
+ "AKAN_CSR",
1103
+ "AKAN_MODULES"
1104
+ ]);
1105
+ static #desktopServerEnv(env, where, configPath) {
1106
+ if (env === undefined)
1107
+ return {};
1108
+ if (!AkanAppConfig.#isPlainObject(env) || Object.values(env).some((value) => typeof value !== "string"))
1109
+ throw new Error(`${where}.env in ${configPath} must be an object of strings.`);
1110
+ const staged = Object.keys(env).filter((key) => AkanAppConfig.#stagedServerEnv.has(key.toUpperCase()));
1111
+ if (staged.length)
1112
+ throw new Error(`${where}.env in ${configPath} sets ${staged.join(", ")}, which the carried server's stage owns (modules: desktop.server.modules).`);
1113
+ const secrets = ServerEnvProfile.secretPaths(env);
1114
+ if (secrets.length)
1115
+ throw new Error(`${where}.env in ${configPath} holds secret-named values (${secrets.join(", ")}), and the app carries its env where anyone on the computer reads it. Leave them out.`);
1116
+ return env;
1117
+ }
1118
+ static #desktopServerWatchdog(watchdog, where, configPath) {
1119
+ if (watchdog === undefined || watchdog === false)
1120
+ return null;
1121
+ const { intervalMs = 1e4, failures = 3 } = watchdog === true ? {} : watchdog;
1122
+ const whole = (value) => Number.isInteger(value) && value > 0;
1123
+ if (watchdog !== true && !AkanAppConfig.#isPlainObject(watchdog) || !whole(intervalMs) || !whole(failures))
1124
+ throw new Error(`${where}.watchdog in ${configPath} must be true or { intervalMs, failures }, whole numbers from 1.`);
1125
+ return { intervalMs, failures };
989
1126
  }
990
1127
  static #platformIndexPath(section) {
991
1128
  const { indexPath: rawIndexPath, ...rest } = section;
@@ -996,7 +1133,7 @@ class AkanAppConfig {
996
1133
  const merge = (kept, value, top, key) => {
997
1134
  if (!AkanAppConfig.#isPlainObject(kept) || !AkanAppConfig.#isPlainObject(value))
998
1135
  return value;
999
- if (top && (key === "icon" || key === "splash"))
1136
+ if (top && (key === "icon" || key === "splash" || key === "backgroundColor"))
1000
1137
  return value;
1001
1138
  const out = { ...kept };
1002
1139
  for (const [inner, next] of Object.entries(value))
@@ -1024,10 +1161,12 @@ class AkanAppConfig {
1024
1161
  "buildNum",
1025
1162
  "icon",
1026
1163
  "splash",
1164
+ "backgroundColor",
1027
1165
  "permissions",
1028
1166
  "plugins",
1029
1167
  "deepLinks",
1030
1168
  "updates",
1169
+ "devtools",
1031
1170
  "ios",
1032
1171
  "android",
1033
1172
  "desktop"
@@ -1044,8 +1183,18 @@ class AkanAppConfig {
1044
1183
  "activity",
1045
1184
  "files"
1046
1185
  ],
1047
- desktop: ["indexPath", "server", "recovery", "window", "screenCapture", "entitlements", "dmg"],
1048
- desktopServer: ["omit"],
1186
+ desktop: [
1187
+ "indexPath",
1188
+ "server",
1189
+ "recovery",
1190
+ "window",
1191
+ "screenCapture",
1192
+ "webview2",
1193
+ "publisher",
1194
+ "entitlements",
1195
+ "dmg"
1196
+ ],
1197
+ desktopServer: ["omit", "profile", "modules", "env", "restart", "watchdog"],
1049
1198
  dmg: ["background", "background2x", "window", "iconSize", "textSize", "app", "applications"],
1050
1199
  deepLinks: ["schemes", "domains"]
1051
1200
  };
@@ -1195,12 +1344,18 @@ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
1195
1344
  static async from(app, { bustImportCache = false } = {}) {
1196
1345
  const [configImp, baseDevEnv, libs, rootPackageJson] = await Promise.all([
1197
1346
  AkanAppConfig.importConfigModule(app.cwdPath, { bustImportCache }),
1198
- WorkspaceExecutor.getBaseDevEnv(path5.join(app.workspace.workspaceRoot, ".env")),
1347
+ WorkspaceExecutor.getBaseDevEnv(path6.join(app.workspace.workspaceRoot, ".env")),
1199
1348
  app.workspace.getLibs(),
1200
1349
  app.workspace.getPackageJson()
1201
1350
  ]);
1202
1351
  const resolved = typeof configImp === "function" ? configImp(app) : configImp;
1203
- const { plugins, ...config } = resolved ?? {};
1352
+ const {
1353
+ plugins,
1354
+ pipeline: _pipeline,
1355
+ ...config
1356
+ } = resolved ?? {};
1357
+ if ("subspace" in config)
1358
+ throw new Error(`apps/${app.name}/akan.config.ts declares \`subspace\`, a workspace setting: it belongs in the workspace root akan.config.ts.`);
1204
1359
  const libContributions = await AkanAppConfig.#collectLibContributions(app, libs, bustImportCache);
1205
1360
  return new AkanAppConfig(app, libs, rootPackageJson, config, baseDevEnv, plugins ?? [], libContributions);
1206
1361
  }
@@ -1263,6 +1418,9 @@ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
1263
1418
  return libs.filter((lib) => !rootDependencies[lib]).map((lib) => `${lib}@${this.#resolveProductionDependencyVersion(lib)}`);
1264
1419
  }
1265
1420
  get akanVersion() {
1421
+ return AkanAppConfig.akanVersion;
1422
+ }
1423
+ static get akanVersion() {
1266
1424
  return getAkanPackageJson().version;
1267
1425
  }
1268
1426
  getProductionPackageJson(data = {}) {
@@ -1284,11 +1442,11 @@ var akanPackageJson = null;
1284
1442
  function getAkanPackageJson() {
1285
1443
  if (akanPackageJson)
1286
1444
  return akanPackageJson;
1287
- const sourceDir = path5.dirname(fileURLToPath(import.meta.url));
1445
+ const sourceDir = path6.dirname(fileURLToPath(import.meta.url));
1288
1446
  const packageJsonPaths = [
1289
- path5.join(sourceDir, "../../../akanjs/package.json"),
1290
- path5.join(process.cwd(), "pkgs/akanjs/package.json"),
1291
- path5.join(path5.dirname(Bun.main), "node_modules/akanjs/package.json")
1447
+ path6.join(sourceDir, "../../../akanjs/package.json"),
1448
+ path6.join(process.cwd(), "pkgs/akanjs/package.json"),
1449
+ path6.join(path6.dirname(Bun.main), "node_modules/akanjs/package.json")
1292
1450
  ];
1293
1451
  try {
1294
1452
  packageJsonPaths.unshift(Bun.resolveSync("akanjs/package.json", sourceDir));
@@ -1342,26 +1500,81 @@ class AkanLibConfig {
1342
1500
  const configImp = await AkanAppConfig.importConfigModule(lib.cwdPath, { bustImportCache });
1343
1501
  const resolved = typeof configImp === "function" ? configImp(lib) : configImp;
1344
1502
  const { plugins, ...config } = resolved ?? {};
1503
+ if ("pipeline" in config)
1504
+ throw new Error(`libs/${lib.name}/akan.config.ts declares \`pipeline\`, which a lib does not take: pipelines belong to apps and the workspace root, and a lib's build-time work goes in an akan build plugin.`);
1505
+ if ("subspace" in config)
1506
+ throw new Error(`libs/${lib.name}/akan.config.ts declares \`subspace\`, a workspace setting: it belongs in the workspace root akan.config.ts.`);
1345
1507
  return new AkanLibConfig(lib, config, plugins ?? []);
1346
1508
  }
1347
1509
  }
1348
1510
  //! need to refactor
1349
1511
  var shiftBuildNum = async (app, delta) => {
1350
1512
  const { buildNum } = (await AkanAppConfig.from(app)).native;
1351
- const akanConfigPath = path5.join(app.cwdPath, "akan.config.ts");
1513
+ const akanConfigPath = path6.join(app.cwdPath, "akan.config.ts");
1352
1514
  const akanConfig = fs2.readFileSync(akanConfigPath, "utf8");
1353
1515
  fs2.writeFileSync(akanConfigPath, akanConfig.replace(`buildNum: ${buildNum}`, `buildNum: ${buildNum + delta}`));
1354
1516
  };
1355
1517
  var increaseBuildNum = async (app) => await shiftBuildNum(app, 1);
1356
1518
  var decreaseBuildNum = async (app) => await shiftBuildNum(app, -1);
1519
+ // pkgs/@akanjs/devkit/akanConfig/akanWorkspaceConfig.ts
1520
+ import path7 from "path";
1521
+ class AkanWorkspaceConfig {
1522
+ static fileName = "akan.config.ts";
1523
+ static #keys = ["subspace", "pipeline", "cloud"];
1524
+ static #loopbackHosts = ["localhost", "127.0.0.1", "[::1]"];
1525
+ static async from(workspace, { bustImportCache = false } = {}) {
1526
+ const file = path7.join(workspace.workspaceRoot, AkanWorkspaceConfig.fileName);
1527
+ if (!await FileSys2.fileExists(file))
1528
+ return new AkanWorkspaceConfig(null, {});
1529
+ const input = await AkanAppConfig.importConfigModule(workspace.workspaceRoot, { bustImportCache });
1530
+ return new AkanWorkspaceConfig(file, input);
1531
+ }
1532
+ file;
1533
+ subspace;
1534
+ pipeline;
1535
+ cloudHost;
1536
+ constructor(file, input) {
1537
+ this.file = file;
1538
+ if (typeof input !== "object" || input === null || Array.isArray(input))
1539
+ throw new Error("The workspace root akan.config.ts must default-export a WorkspaceConfig object.");
1540
+ for (const key of Object.keys(input))
1541
+ if (!AkanWorkspaceConfig.#keys.includes(key))
1542
+ throw new Error(`The workspace root akan.config.ts declares \`${key}\`, which it does not take: it holds \`subspace\`, \`pipeline\` and \`cloud\`, and app or lib settings belong in apps/<app>/akan.config.ts or libs/<lib>/akan.config.ts.`);
1543
+ const config = input;
1544
+ this.subspace = config.subspace ?? null;
1545
+ this.pipeline = config.pipeline ?? null;
1546
+ this.cloudHost = AkanWorkspaceConfig.#cloudHost(config.cloud);
1547
+ }
1548
+ static #cloudHost(cloud) {
1549
+ if (cloud === undefined)
1550
+ return null;
1551
+ const refuse = (why) => {
1552
+ throw new Error(`The workspace root akan.config.ts has cloud ${why}.`);
1553
+ };
1554
+ if (typeof cloud !== "object" || cloud === null || Array.isArray(cloud))
1555
+ return refuse("that is not an object");
1556
+ const unknown = Object.keys(cloud).filter((key) => key !== "host");
1557
+ if (unknown.length)
1558
+ return refuse(`keys it does not take: ${unknown.join(", ")} (only \`host\`)`);
1559
+ const { host } = cloud;
1560
+ if (host === undefined)
1561
+ return null;
1562
+ const url = typeof host === "string" && URL.canParse(host) ? new URL(host) : null;
1563
+ if (!url || url.search || url.hash || url.username || url.password)
1564
+ return refuse(`.host ${JSON.stringify(host)}, which is not a URL like "https://cloud.example.com"`);
1565
+ if (url.protocol !== "https:" && !(url.protocol === "http:" && AkanWorkspaceConfig.#loopbackHosts.includes(url.hostname)))
1566
+ return refuse(`.host ${JSON.stringify(host)}: https only, or http on this computer`);
1567
+ return `${url.origin}${url.pathname.replace(/\/+$/, "")}`;
1568
+ }
1569
+ }
1357
1570
  // pkgs/@akanjs/devkit/jsonFormatter.ts
1358
- import path6 from "path";
1571
+ import path8 from "path";
1359
1572
 
1360
1573
  class JsonFormatter {
1361
1574
  static lineWidth = 120;
1362
1575
  static stringify(value, filePath) {
1363
1576
  const json = JSON.stringify(value, null, 2);
1364
- if (path6.basename(filePath) === "package.json")
1577
+ if (path8.basename(filePath) === "package.json")
1365
1578
  return `${json}
1366
1579
  `;
1367
1580
  return `${JsonFormatter.#print(JSON.parse(json), 0, { column: 0, trailing: 0 })}
@@ -1432,7 +1645,7 @@ ${" ".repeat(depth)}]`;
1432
1645
  // pkgs/@akanjs/devkit/linter.ts
1433
1646
  import { spawn } from "child_process";
1434
1647
  import { existsSync, readFileSync } from "fs";
1435
- import path7 from "path";
1648
+ import path9 from "path";
1436
1649
  import chalk from "chalk";
1437
1650
  var parseBiomeReport = (output) => {
1438
1651
  for (let start = output.indexOf("{");start !== -1; start = output.indexOf("{", start + 1)) {
@@ -1498,13 +1711,13 @@ class Linter {
1498
1711
  #biomeBin;
1499
1712
  constructor(cwdPath) {
1500
1713
  this.lintRoot = this.#findBiomeRootPath(cwdPath);
1501
- this.configPath = Linter.#configPathIn(this.lintRoot) ?? path7.join(this.lintRoot, "biome.json");
1502
- const localBiomeBin = path7.join(this.lintRoot, "node_modules", ".bin", Linter.biomeBinName);
1714
+ this.configPath = Linter.#configPathIn(this.lintRoot) ?? path9.join(this.lintRoot, "biome.json");
1715
+ const localBiomeBin = path9.join(this.lintRoot, "node_modules", ".bin", Linter.biomeBinName);
1503
1716
  this.#biomeBin = existsSync(localBiomeBin) ? localBiomeBin : "biome";
1504
1717
  }
1505
1718
  static #configPathIn(dir) {
1506
1719
  for (const fileName of BIOME_CONFIG_FILES) {
1507
- const configPath = path7.join(dir, fileName);
1720
+ const configPath = path9.join(dir, fileName);
1508
1721
  if (existsSync(configPath))
1509
1722
  return configPath;
1510
1723
  }
@@ -1513,19 +1726,19 @@ class Linter {
1513
1726
  #findBiomeRootPath(dir) {
1514
1727
  if (Linter.#configPathIn(dir))
1515
1728
  return dir;
1516
- const parentDir = path7.dirname(dir);
1729
+ const parentDir = path9.dirname(dir);
1517
1730
  if (parentDir === dir)
1518
1731
  throw new Error(`biome.json not found from ${dir}`);
1519
1732
  return this.#findBiomeRootPath(parentDir);
1520
1733
  }
1521
1734
  #toBiomePath(filePath) {
1522
- const relativePath = path7.relative(this.lintRoot, filePath);
1523
- if (!relativePath.startsWith("..") && !path7.isAbsolute(relativePath))
1735
+ const relativePath = path9.relative(this.lintRoot, filePath);
1736
+ if (!relativePath.startsWith("..") && !path9.isAbsolute(relativePath))
1524
1737
  return relativePath;
1525
1738
  return filePath;
1526
1739
  }
1527
1740
  #resolveFilePath(filePath) {
1528
- return path7.isAbsolute(filePath) ? filePath : path7.join(this.lintRoot, filePath);
1741
+ return path9.isAbsolute(filePath) ? filePath : path9.join(this.lintRoot, filePath);
1529
1742
  }
1530
1743
  async#runBiome(args, input) {
1531
1744
  return await new Promise((resolve, reject) => {
@@ -1550,7 +1763,7 @@ class Linter {
1550
1763
  const diagnosticPath = diagnostic.location?.path;
1551
1764
  if (!diagnosticPath)
1552
1765
  return fallbackFilePath;
1553
- return path7.isAbsolute(diagnosticPath) ? diagnosticPath : path7.join(this.lintRoot, diagnosticPath);
1766
+ return path9.isAbsolute(diagnosticPath) ? diagnosticPath : path9.join(this.lintRoot, diagnosticPath);
1554
1767
  }
1555
1768
  #createLintMessage(diagnostic) {
1556
1769
  const start = diagnostic.location?.start;
@@ -1886,7 +2099,7 @@ class ManifestDependencies {
1886
2099
 
1887
2100
  // pkgs/@akanjs/devkit/repoIdentity.ts
1888
2101
  import { execFileSync } from "child_process";
1889
- import path8 from "path";
2102
+ import path10 from "path";
1890
2103
  var resolved = new Map;
1891
2104
  var readRemoteName = (workspaceRoot) => {
1892
2105
  try {
@@ -1904,13 +2117,13 @@ var resolveRepoName = (workspaceRoot) => {
1904
2117
  const cached = resolved.get(workspaceRoot);
1905
2118
  if (cached)
1906
2119
  return cached;
1907
- const repoName = readRemoteName(workspaceRoot) ?? path8.basename(workspaceRoot);
2120
+ const repoName = readRemoteName(workspaceRoot) ?? path10.basename(workspaceRoot);
1908
2121
  resolved.set(workspaceRoot, repoName);
1909
2122
  return repoName;
1910
2123
  };
1911
2124
 
1912
2125
  // pkgs/@akanjs/devkit/scanInfo.ts
1913
- import path9 from "path";
2126
+ import path11 from "path";
1914
2127
 
1915
2128
  // pkgs/@akanjs/devkit/workspaceLayout.ts
1916
2129
  var appRootAllowedFiles = new Set([
@@ -1981,8 +2194,11 @@ var libFacetRootAllowedFiles = new Set([
1981
2194
  "useClient.ts",
1982
2195
  "useServer.ts"
1983
2196
  ]);
1984
- var libFacetRootTestPattern = /^[A-Za-z][A-Za-z0-9_-]*\.signal\.(test|spec)\.(ts|tsx)$/;
1985
- var isAllowedLibFacetRootFile = (filename) => libFacetRootAllowedFiles.has(filename) || libFacetRootTestPattern.test(filename);
2197
+ var isAllowedLibFacetRootFile = (filename) => libFacetRootAllowedFiles.has(filename);
2198
+ var testSuffix = /\.(test|spec)(\.tsx?)$/;
2199
+ var libFacetRootHintOf = (filename) => testSuffix.test(filename) ? "a test beside the generated barrels; move it into the module folder it covers " + "(lib/<model>/<model>.signal.test.ts), or into srvkit/ when it drives several modules" : null;
2200
+ var moduleTestTargetOf = (filename) => testSuffix.test(filename) ? filename.replace(testSuffix, "$2") : null;
2201
+ var moduleTestHint = "a module test is named after the one module file it covers (<model>.<role>.test.ts); add a describe block to " + "that file's suite instead, or move a suite that drives several modules into srvkit/";
1986
2202
  var barrelFacetDirs = ["ui", "webkit", "srvkit", "common", "plugin"];
1987
2203
  var barrelShadowingIndexFiles = new Set(["index.tsx", "index.jsx", "index.mts"]);
1988
2204
  var facetIndexShadowOf = (sysRelativePath) => {
@@ -2029,9 +2245,9 @@ var moduleUiFileTypes = {
2029
2245
  scalar: new Set(["Template", "Unit"])
2030
2246
  };
2031
2247
  var createDependencyScanner = async (exec) => (await import("./dependencyScanner-06z2dmrr.js")).TypeScriptDependencyScanner.from(exec);
2032
- var getScanPath = (exec, relativePath) => path9.posix.join(`${exec.type}s`, exec.name, relativePath.split(path9.sep).join("/"));
2248
+ var getScanPath = (exec, relativePath) => path11.posix.join(`${exec.type}s`, exec.name, relativePath.split(path11.sep).join("/"));
2033
2249
  var getModuleNameFromPath = (kind, modulePath) => {
2034
- const dirname = path9.basename(modulePath);
2250
+ const dirname = path11.basename(modulePath);
2035
2251
  return kind === "service" ? dirname.replace(/^_+/, "") : dirname;
2036
2252
  };
2037
2253
  async function assertScanConvention(exec, libRoot) {
@@ -2052,24 +2268,24 @@ async function assertScanConvention(exec, libRoot) {
2052
2268
  for (const filename of await exec.readdir(facet)) {
2053
2269
  const shadow = facetIndexShadowOf(`${facet}/${filename}`);
2054
2270
  if (shadow)
2055
- addViolation(path9.join(facet, filename), shadow);
2271
+ addViolation(path11.join(facet, filename), shadow);
2056
2272
  }
2057
2273
  if (exec.type === "lib")
2058
2274
  for (const { relativePath, reason } of await exec.getPageConventionViolations())
2059
2275
  addViolation(relativePath, reason);
2060
2276
  for (const filename of libRoot.files)
2061
2277
  if (!isAllowedLibFacetRootFile(filename))
2062
- addViolation(path9.join("lib", filename), "unsupported lib facet root file");
2278
+ addViolation(path11.join("lib", filename), libFacetRootHintOf(filename) ?? "unsupported lib facet root file");
2063
2279
  for (const dirname of libRoot.dirs)
2064
2280
  if (dirname.startsWith("__") && !internalLibDirs.has(dirname))
2065
- addViolation(path9.join("lib", dirname), "unsupported internal lib folder");
2281
+ addViolation(path11.join("lib", dirname), "unsupported internal lib folder");
2066
2282
  const databaseDirs = libRoot.dirs.filter((dirname) => !dirname.startsWith("_"));
2067
2283
  const serviceDirs = libRoot.dirs.filter((dirname) => dirname.startsWith("_") && !dirname.startsWith("__"));
2068
2284
  const scalarDirs = await exec.readdir("lib/__scalar");
2069
2285
  await Promise.all([
2070
- ...databaseDirs.map((dirname) => validateModuleFiles(exec, violations, "database", path9.join("lib", dirname))),
2071
- ...serviceDirs.map((dirname) => validateModuleFiles(exec, violations, "service", path9.join("lib", dirname))),
2072
- ...scalarDirs.map((dirname) => validateModuleFiles(exec, violations, "scalar", path9.join("lib/__scalar", dirname)))
2286
+ ...databaseDirs.map((dirname) => validateModuleFiles(exec, violations, "database", path11.join("lib", dirname))),
2287
+ ...serviceDirs.map((dirname) => validateModuleFiles(exec, violations, "service", path11.join("lib", dirname))),
2288
+ ...scalarDirs.map((dirname) => validateModuleFiles(exec, violations, "scalar", path11.join("lib/__scalar", dirname)))
2073
2289
  ]);
2074
2290
  if (violations.length > 0) {
2075
2291
  throw new Error(`[scan-convention]
@@ -2081,19 +2297,21 @@ async function validateModuleFiles(exec, violations, kind, modulePath) {
2081
2297
  const { files, dirs } = await exec.getFilesAndDirs(modulePath);
2082
2298
  const moduleName = getModuleNameFromPath(kind, modulePath);
2083
2299
  dirs.forEach((dirname) => {
2084
- violations.push(`${getScanPath(exec, path9.join(modulePath, dirname))}: unsupported module folder`);
2300
+ violations.push(`${getScanPath(exec, path11.join(modulePath, dirname))}: unsupported module folder`);
2085
2301
  });
2086
2302
  const uiModuleName = moduleName[0].toUpperCase() + moduleName.slice(1);
2087
2303
  for (const filename of files) {
2088
- const scanPath = getScanPath(exec, path9.join(modulePath, filename));
2089
- if (filename === "index.ts" || filename === "index.tsx" || /\.(test|spec)\.(ts|tsx)$/.test(filename))
2304
+ const scanPath = getScanPath(exec, path11.join(modulePath, filename));
2305
+ if (filename === "index.ts" || filename === "index.tsx")
2090
2306
  continue;
2091
2307
  if (filename === `${moduleName}.abstract.md`)
2092
2308
  continue;
2093
- const uiMatch = filename.match(/^([A-Z][A-Za-z0-9]+)\.([A-Z][A-Za-z0-9]*)\.tsx$/);
2094
- const match = uiMatch ?? filename.match(/^([a-z][a-zA-Z0-9]*)\.([a-z][a-z0-9]*)\.ts$/);
2309
+ const testTarget = moduleTestTargetOf(filename);
2310
+ const sourceName = testTarget ?? filename;
2311
+ const uiMatch = sourceName.match(/^([A-Z][A-Za-z0-9]+)\.([A-Z][A-Za-z0-9]*)\.tsx$/);
2312
+ const match = uiMatch ?? sourceName.match(/^([a-z][a-zA-Z0-9]*)\.([a-z][a-z0-9]*)\.ts$/);
2095
2313
  if (!match) {
2096
- violations.push(`${scanPath}: unsupported module file`);
2314
+ violations.push(`${scanPath}: ${testTarget ? moduleTestHint : "unsupported module file"}`);
2097
2315
  continue;
2098
2316
  }
2099
2317
  const [, fileModuleName, fileType] = match;
@@ -2101,7 +2319,7 @@ async function validateModuleFiles(exec, violations, kind, modulePath) {
2101
2319
  if (fileModuleName !== expectedName)
2102
2320
  violations.push(`${scanPath}: module name mismatch: expected '${expectedName}', got '${fileModuleName}'`);
2103
2321
  if (!(uiMatch ? moduleUiFileTypes : moduleNonUiFileTypes)[kind].has(fileType))
2104
- violations.push(`${scanPath}: unsupported ${kind}${uiMatch ? " UI" : ""} file`);
2322
+ violations.push(`${scanPath}: ${testTarget ? moduleTestHint : `unsupported ${kind}${uiMatch ? " UI" : ""} file`}`);
2105
2323
  }
2106
2324
  }
2107
2325
 
@@ -2158,16 +2376,16 @@ class ScanInfo {
2158
2376
  }
2159
2377
  };
2160
2378
  await Promise.all([
2161
- ...databaseDirs.map((name) => collect(path9.join("lib", name), name, "databases")),
2162
- ...serviceDirs.map((dirname) => collect(path9.join("lib", dirname), dirname.slice(1), "services")),
2163
- ...scalarDirs.map((name) => collect(path9.join("lib/__scalar", name), name, "scalars"))
2379
+ ...databaseDirs.map((name) => collect(path11.join("lib", name), name, "databases")),
2380
+ ...serviceDirs.map((dirname) => collect(path11.join("lib", dirname), dirname.slice(1), "services")),
2381
+ ...scalarDirs.map((name) => collect(path11.join("lib/__scalar", name), name, "scalars"))
2164
2382
  ]);
2165
2383
  const routes = exec.type === "lib" ? [] : await exec.getPageKeys();
2166
2384
  const common = {
2167
2385
  name: exec.name,
2168
2386
  type: exec.type,
2169
2387
  repoName: exec.workspace.repoName,
2170
- serveDomain: WorkspaceExecutor.getBaseDevEnv(path9.join(exec.workspace.workspaceRoot, ".env")).serveDomain,
2388
+ serveDomain: WorkspaceExecutor.getBaseDevEnv(path11.join(exec.workspace.workspaceRoot, ".env")).serveDomain,
2171
2389
  files,
2172
2390
  libDeps,
2173
2391
  pkgDeps,
@@ -2549,10 +2767,10 @@ class Executor {
2549
2767
  return this.#settle(proc, target, { redStderr: true }).then(({ code, signal }) => ({ code, signal }));
2550
2768
  }
2551
2769
  getPath(filePath) {
2552
- if (path10.isAbsolute(filePath))
2770
+ if (path12.isAbsolute(filePath))
2553
2771
  return filePath;
2554
2772
  if (filePath.startsWith("."))
2555
- return path10.join(this.cwdPath, filePath);
2773
+ return path12.join(this.cwdPath, filePath);
2556
2774
  const baseParts = this.cwdPath.split(/[\\/]/).filter(Boolean);
2557
2775
  const targetParts = filePath.split(/[\\/]/).filter(Boolean);
2558
2776
  let overlapLength = 0;
@@ -2566,7 +2784,7 @@ class Executor {
2566
2784
  if (isOverlap)
2567
2785
  overlapLength = i;
2568
2786
  }
2569
- return path10.join(this.cwdPath, ...targetParts.slice(overlapLength));
2787
+ return path12.join(this.cwdPath, ...targetParts.slice(overlapLength));
2570
2788
  }
2571
2789
  async mkdir(dirPath) {
2572
2790
  const writePath = this.getPath(dirPath);
@@ -2607,7 +2825,7 @@ class Executor {
2607
2825
  return this;
2608
2826
  }
2609
2827
  async removeDir(dirPath) {
2610
- const readPath = path10.join(this.getPath(dirPath), ".");
2828
+ const readPath = path12.join(this.getPath(dirPath), ".");
2611
2829
  if (await FileSys2.entryExists(readPath))
2612
2830
  await rm3(readPath, { recursive: true, force: true });
2613
2831
  this.logger.verbose(`Remove directory ${readPath}`);
@@ -2615,7 +2833,7 @@ class Executor {
2615
2833
  }
2616
2834
  async writeFile(filePath, content, { overwrite = true, silent = false } = {}) {
2617
2835
  const writePath = this.getPath(filePath);
2618
- const dir = path10.dirname(writePath);
2836
+ const dir = path12.dirname(writePath);
2619
2837
  if (!await FileSys2.dirExists(dir))
2620
2838
  await mkdir2(dir, { recursive: true });
2621
2839
  let contentStr = typeof content === "string" ? content : JsonFormatter.stringify(content, writePath);
@@ -2640,7 +2858,7 @@ class Executor {
2640
2858
  await this.writeFile(filePath, content);
2641
2859
  }
2642
2860
  async getLocalFile(targetPath) {
2643
- const filePath = path10.isAbsolute(targetPath) ? targetPath : targetPath.replace(this.cwdPath, "");
2861
+ const filePath = path12.isAbsolute(targetPath) ? targetPath : targetPath.replace(this.cwdPath, "");
2644
2862
  const content = await this.readFile(filePath);
2645
2863
  return { filePath, content };
2646
2864
  }
@@ -2727,9 +2945,9 @@ class Executor {
2727
2945
  const result = await getContent.default(scanInfo ?? null, dict, options);
2728
2946
  if (result === null)
2729
2947
  return null;
2730
- const filename = typeof result === "object" ? result.filename : path10.basename(targetPath).replace(".js", ".ts");
2948
+ const filename = typeof result === "object" ? result.filename : path12.basename(targetPath).replace(".js", ".ts");
2731
2949
  const content = typeof result === "object" ? result.content : result;
2732
- const convertedTargetPath = convertPath(`${path10.dirname(targetPath)}/${filename}`);
2950
+ const convertedTargetPath = convertPath(`${path12.dirname(targetPath)}/${filename}`);
2733
2951
  this.logger.verbose(`Apply template ${templatePath} to ${convertedTargetPath}`);
2734
2952
  return this.writeFile(convertedTargetPath, content, { overwrite });
2735
2953
  } else if (targetPath.endsWith(".template")) {
@@ -2738,10 +2956,10 @@ class Executor {
2738
2956
  const convertedContent = Object.entries(dict).reduce((data, [key, value]) => data.replace(new RegExp(`<%= ${key} %>`, "g"), value), content);
2739
2957
  this.logger.verbose(`Apply template ${templatePath} to ${convertedTargetPath}`);
2740
2958
  return this.writeFile(convertedTargetPath, convertedContent, { overwrite });
2741
- } else if (staticTemplateFileExtensions.has(path10.extname(targetPath).toLowerCase())) {
2959
+ } else if (staticTemplateFileExtensions.has(path12.extname(targetPath).toLowerCase())) {
2742
2960
  const convertedTargetPath = convertPath(targetPath);
2743
2961
  const writePath = this.getPath(convertedTargetPath);
2744
- const dirname = path10.dirname(writePath);
2962
+ const dirname = path12.dirname(writePath);
2745
2963
  if (!await FileSys2.dirExists(dirname))
2746
2964
  await mkdir2(dirname, { recursive: true });
2747
2965
  await copyFile(templatePath, writePath);
@@ -2763,7 +2981,7 @@ class Executor {
2763
2981
  if ((await stat2(templatePath)).isFile()) {
2764
2982
  const fileContent = await this.#applyTemplateFile({
2765
2983
  templatePath,
2766
- targetPath: path10.join(basePath, path10.basename(templatePath)),
2984
+ targetPath: path12.join(basePath, path12.basename(templatePath)),
2767
2985
  scanInfo,
2768
2986
  overwrite
2769
2987
  }, dict, options);
@@ -2771,19 +2989,19 @@ class Executor {
2771
2989
  } else {
2772
2990
  const subdirs = await readDirEntries(templatePath);
2773
2991
  const fileContents = (await Promise.all(subdirs.map(async (subdir) => {
2774
- const subpath = path10.join(templatePath, subdir);
2992
+ const subpath = path12.join(templatePath, subdir);
2775
2993
  if ((await stat2(subpath)).isFile()) {
2776
2994
  const fileContent = await this.#applyTemplateFile({
2777
2995
  templatePath: subpath,
2778
- targetPath: path10.join(basePath, subdir),
2996
+ targetPath: path12.join(basePath, subdir),
2779
2997
  scanInfo,
2780
2998
  overwrite
2781
2999
  }, dict, options);
2782
3000
  return fileContent ? [fileContent] : [];
2783
3001
  } else
2784
3002
  return await this._applyTemplate({
2785
- basePath: path10.join(basePath, subdir),
2786
- template: path10.join(template, subdir),
3003
+ basePath: path12.join(basePath, subdir),
3004
+ template: path12.join(template, subdir),
2787
3005
  scanInfo,
2788
3006
  dict,
2789
3007
  overwrite,
@@ -2795,8 +3013,8 @@ class Executor {
2795
3013
  }
2796
3014
  async#resolveTemplateRoot() {
2797
3015
  const candidates = [
2798
- path10.resolve(getDirname2(import.meta.url), "templates"),
2799
- path10.resolve(getDirname2(import.meta.url), "../cli/templates")
3016
+ path12.resolve(getDirname2(import.meta.url), "templates"),
3017
+ path12.resolve(getDirname2(import.meta.url), "../cli/templates")
2800
3018
  ];
2801
3019
  for (const candidate of candidates) {
2802
3020
  if (await FileSys2.dirExists(candidate))
@@ -2864,11 +3082,11 @@ class Executor {
2864
3082
  async#resolveTypecheckWorkerEntry() {
2865
3083
  const dirname = getDirname2(import.meta.url);
2866
3084
  const candidates = [
2867
- path10.join(process.cwd(), "pkgs/@akanjs/devkit/typecheck/typecheck.proc.ts"),
2868
- path10.join(process.cwd(), "node_modules/@akanjs/devkit/typecheck/typecheck.proc.ts"),
2869
- path10.join(dirname, "typecheck/typecheck.proc.ts"),
2870
- path10.join(dirname, "typecheck.proc.js"),
2871
- path10.join(dirname, "typecheck.proc.ts")
3085
+ path12.join(process.cwd(), "pkgs/@akanjs/devkit/typecheck/typecheck.proc.ts"),
3086
+ path12.join(process.cwd(), "node_modules/@akanjs/devkit/typecheck/typecheck.proc.ts"),
3087
+ path12.join(dirname, "typecheck/typecheck.proc.ts"),
3088
+ path12.join(dirname, "typecheck.proc.js"),
3089
+ path12.join(dirname, "typecheck.proc.ts")
2872
3090
  ];
2873
3091
  for (const candidate of candidates)
2874
3092
  if (await Bun.file(candidate).exists())
@@ -2942,6 +3160,13 @@ class WorkspaceExecutor extends Executor {
2942
3160
  async scan() {
2943
3161
  return await WorkspaceInfo.fromExecutor(this);
2944
3162
  }
3163
+ #akanConfig = null;
3164
+ async getConfig({ refresh } = {}) {
3165
+ if (this.#akanConfig && !refresh)
3166
+ return this.#akanConfig;
3167
+ this.#akanConfig = await AkanWorkspaceConfig.from(this, { bustImportCache: refresh });
3168
+ return this.#akanConfig;
3169
+ }
2945
3170
  async getApps() {
2946
3171
  return await this.#getDirHasFile(`${this.workspaceRoot}/apps`, "akan.config.ts");
2947
3172
  }
@@ -2988,7 +3213,7 @@ class WorkspaceExecutor extends Executor {
2988
3213
  await Promise.all(dirs.map(async (dir) => {
2989
3214
  if (dir.includes("_") || AVOID_DIRS.includes(dir))
2990
3215
  return;
2991
- const dirPath = path10.join(dirname, dir);
3216
+ const dirPath = path12.join(dirname, dir);
2992
3217
  if ((await stat2(dirPath)).isDirectory()) {
2993
3218
  results.push(`${prefix}${dir}`);
2994
3219
  if (maxDepth > 0)
@@ -3025,9 +3250,9 @@ class WorkspaceExecutor extends Executor {
3025
3250
  await Promise.all(dirs.map(async (dir) => {
3026
3251
  if (AVOID_DIRS.includes(dir))
3027
3252
  return;
3028
- const dirPath = path10.join(dirname, dir);
3253
+ const dirPath = path12.join(dirname, dir);
3029
3254
  if (await FileSys2.dirExists(dirPath)) {
3030
- const hasTargetFile = await FileSys2.fileExists(path10.join(dirPath, targetFilename));
3255
+ const hasTargetFile = await FileSys2.fileExists(path12.join(dirPath, targetFilename));
3031
3256
  if (hasTargetFile)
3032
3257
  results.push(`${prefix}${dir}`);
3033
3258
  if (maxDepth > 0)
@@ -3086,7 +3311,7 @@ class SysExecutor extends Executor {
3086
3311
  return [];
3087
3312
  const violations = [];
3088
3313
  for (const entry of await this.getAllFiles("**/*", { cwd: this.getPath("page") })) {
3089
- const relativePath = entry.split(path10.sep).join("/");
3314
+ const relativePath = entry.split(path12.sep).join("/");
3090
3315
  const reason = getPageSourceFileViolation(relativePath);
3091
3316
  if (reason)
3092
3317
  violations.push({ relativePath: `page/${relativePath}`, reason });
@@ -3181,7 +3406,7 @@ class SysExecutor extends Executor {
3181
3406
  await this.setPackageJson({ ...packageJson, dependencies, devDependencies });
3182
3407
  }
3183
3408
  async getLocalFile(targetPath) {
3184
- const filePath = path10.isAbsolute(targetPath) ? targetPath : `${this.type}s/${this.name}/${targetPath}`;
3409
+ const filePath = path12.isAbsolute(targetPath) ? targetPath : `${this.type}s/${this.name}/${targetPath}`;
3185
3410
  const content = await this.workspace.readFile(filePath);
3186
3411
  return { filePath, content };
3187
3412
  }
@@ -3248,7 +3473,7 @@ class SysExecutor extends Executor {
3248
3473
  }
3249
3474
  async applyTemplate(options) {
3250
3475
  const scanInfo = await this.scan();
3251
- const fileContents = await this._applyTemplate({ ...options, scanInfo, dict: withCapitalizedDict(options.dict) });
3476
+ const fileContents = await super.applyTemplate({ ...options, scanInfo });
3252
3477
  await this.scan();
3253
3478
  return fileContents;
3254
3479
  }
@@ -3269,19 +3494,19 @@ class AppExecutor extends SysExecutor {
3269
3494
  static from(executor, name) {
3270
3495
  return new AppExecutor({ workspace: executor instanceof WorkspaceExecutor ? executor : executor.workspace, name });
3271
3496
  }
3272
- static #keptOnStart = [path10.join(".akan", "cache", "bin")];
3273
- static #legacyNative = path10.join(".akan", "native");
3497
+ static #keptOnStart = [path12.join(".akan", "cache", "bin")];
3498
+ static #legacyNative = path12.join(".akan", "native");
3274
3499
  async#removeDevOutput(dir = ".akan") {
3275
3500
  const entries = await readDirEntries(this.getPath(dir)).catch(() => []);
3276
3501
  await Promise.all(entries.map(async (name) => {
3277
- const entry = path10.join(dir, name);
3502
+ const entry = path12.join(dir, name);
3278
3503
  if (entry === AppExecutor.#legacyNative) {
3279
3504
  this.logger.warn(`apps/${this.name}/.akan/native holds native builds from before they moved to dist/native/${this.name}: upload any release waiting in its updates folders, then delete it.`);
3280
3505
  return;
3281
3506
  }
3282
3507
  if (AppExecutor.#keptOnStart.includes(entry))
3283
3508
  return;
3284
- if (AppExecutor.#keptOnStart.some((kept) => kept.startsWith(`${entry}${path10.sep}`)))
3509
+ if (AppExecutor.#keptOnStart.some((kept) => kept.startsWith(`${entry}${path12.sep}`)))
3285
3510
  return await this.#removeDevOutput(entry);
3286
3511
  await this.removeDir(entry);
3287
3512
  }));
@@ -3405,13 +3630,13 @@ class AppExecutor extends SysExecutor {
3405
3630
  absolute: false,
3406
3631
  onlyFiles: true
3407
3632
  })) {
3408
- const segments = rel.split(path10.sep);
3633
+ const segments = rel.split(path12.sep);
3409
3634
  if (segments.some((s) => s === "node_modules"))
3410
3635
  continue;
3411
3636
  const posix = `${root.keyPrefix}${segments.join("/")}`;
3412
3637
  if (!root.keyPrefix && libKeyPrefixes.some((prefix) => posix.startsWith(prefix)))
3413
3638
  continue;
3414
- const absPath = path10.join(root.dir, ...segments);
3639
+ const absPath = path12.join(root.dir, ...segments);
3415
3640
  validatePageSourceFile(posix, { filePath: absPath });
3416
3641
  if (!isRouteSourceFile(posix))
3417
3642
  continue;
@@ -3552,7 +3777,7 @@ class AppExecutor extends SysExecutor {
3552
3777
  static async#linkLibAsset(targetPath, linkPath) {
3553
3778
  const isWindows = process.platform === "win32";
3554
3779
  try {
3555
- const target = isWindows ? targetPath : path10.relative(path10.dirname(linkPath), targetPath);
3780
+ const target = isWindows ? targetPath : path12.relative(path12.dirname(linkPath), targetPath);
3556
3781
  await symlink(target, linkPath, isWindows ? "junction" : "dir");
3557
3782
  } catch (error) {
3558
3783
  if (!isWindows)
@@ -3595,7 +3820,7 @@ class AppExecutor extends SysExecutor {
3595
3820
  await this.writeFile(rel, content, opts);
3596
3821
  },
3597
3822
  readEnvClient: async () => {
3598
- const envClientPath = path10.join(this.cwdPath, "env", "env.client.ts");
3823
+ const envClientPath = path12.join(this.cwdPath, "env", "env.client.ts");
3599
3824
  if (!await FileSys2.fileExists(envClientPath))
3600
3825
  return null;
3601
3826
  try {
@@ -3689,7 +3914,7 @@ class PkgExecutor extends Executor {
3689
3914
  return overrideVersion;
3690
3915
  try {
3691
3916
  const packageJsonPath = `pkgs/${dep}/package.json`;
3692
- if (!await Bun.file(path10.join(this.workspace.workspaceRoot, packageJsonPath)).exists())
3917
+ if (!await Bun.file(path12.join(this.workspace.workspaceRoot, packageJsonPath)).exists())
3693
3918
  return;
3694
3919
  const packageJson = await this.workspace.readJson(packageJsonPath);
3695
3920
  if (packageJson.name === dep)
@@ -3782,4 +4007,4 @@ class ModuleExecutor extends Executor {
3782
4007
  }
3783
4008
  }
3784
4009
 
3785
- export { AGENT_BLOCK_START, AGENT_BLOCK_END, stampBlockVersion, extractBlockVersion, readDevkitVersion, upsertAgentBlock, extractAgentBlock, renderRecipeEntries, collectScopeRecipeSources, renderScopeAgentBlock, CLAUDE_COMMENT_RULE, binPlatforms, AsyncDefaultExportDetector, resolveSsrPageEntriesForApp, CodegenLock, Linter, LibSource, formatLibStatuses, rootAllowedFiles, rootAllowedDirs, isAllowedLibFacetRootFile, barrelFacetDirs, facetIndexShadowOf, rootEntryHintOf, isScannedRootEntry, AppInfo, Spinner, Executor, WorkspaceExecutor, SysExecutor, AppExecutor, LibExecutor, PkgExecutor, ModuleExecutor };
4010
+ export { AGENT_BLOCK_START, AGENT_BLOCK_END, stampBlockVersion, extractBlockVersion, readDevkitVersion, upsertAgentBlock, extractAgentBlock, renderRecipeEntries, collectScopeRecipeSources, renderScopeAgentBlock, CLAUDE_COMMENT_RULE, binPlatforms, ServerEnvProfile, AkanAppConfig, AkanWorkspaceConfig, AsyncDefaultExportDetector, resolveSsrPageEntriesForApp, CodegenLock, Linter, LibSource, formatLibStatuses, rootAllowedFiles, rootAllowedDirs, isAllowedLibFacetRootFile, libFacetRootHintOf, barrelFacetDirs, facetIndexShadowOf, rootEntryHintOf, isScannedRootEntry, AppInfo, Spinner, Executor, WorkspaceExecutor, SysExecutor, AppExecutor, LibExecutor, PkgExecutor, ModuleExecutor };