@akanjs/cli 3.1.0-rc.0 → 3.1.0-rc.2

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 (76) hide show
  1. package/.build-stamp +1 -1
  2. package/{CodeAgent-q7a4srt6.js → CodeAgent-q50k4s45.js} +2 -2
  3. package/{CodeTui-ak23dxc9.js → CodeTui-62g35ztk.js} +1 -1
  4. package/TunnelShare-f1egrktx.js +13 -0
  5. package/{agent.command-f3gsc1gd.js → agent.command-adfd5299.js} +5 -5
  6. package/akanCode.js +2 -2
  7. package/{application.command-3v2d53gd.js → application.command-hj3gv1rx.js} +52 -41
  8. package/{applicationBuildRunner-1yaqsrdq.js → applicationBuildRunner-by10r9gf.js} +16 -30
  9. package/buildBatch.proc.js +22 -13
  10. package/{cloud.command-kk2rwkvw.js → cloud.command-k2fhaape.js} +60 -63
  11. package/cloud.script-z8p5dz7e.js +23 -0
  12. package/{code.command-30sca09d.js → code.command-kt5zgcc2.js} +6 -6
  13. package/{context.command-k7eqacq6.js → context.command-8fgn42jw.js} +16 -13
  14. package/context.runner-hnh0rg7m.js +24 -0
  15. package/{guideline.command-dtczrqpf.js → guideline.command-3yxxr2mm.js} +2 -2
  16. package/guidelines/conventions/conventions.instruction.md +30 -15
  17. package/guidelines/cssRule/cssRule.instruction.md +12 -6
  18. package/guidelines/mcpRule/mcpRule.instruction.md +32 -11
  19. package/guidelines/runtimeRule/runtimeRule.instruction.md +24 -15
  20. package/guidelines/transportRule/transportRule.instruction.md +28 -5
  21. package/incrementalBuilder.proc.js +9 -6
  22. package/{index-kjvrq3nc.js → index-0909wckt.js} +361 -282
  23. package/{index-p9ystjrp.js → index-20cebtth.js} +92 -52
  24. package/{index-55mynnqs.js → index-2etqq75t.js} +455 -206
  25. package/{index-6jjtnhkw.js → index-31ce9vhg.js} +51 -9
  26. package/{index-y8q31011.js → index-3reaaa1h.js} +42 -153
  27. package/{index-ww9bhp67.js → index-4pf8g7ff.js} +1 -1
  28. package/{index-qt22xstj.js → index-58vvz603.js} +5 -3
  29. package/index-6726qr1m.js +668 -0
  30. package/index-8935h3n1.js +33 -0
  31. package/{index-nz15k0b6.js → index-8zdwpxef.js} +1 -1
  32. package/{index-c2yrsxdh.js → index-azh29dc8.js} +96 -320
  33. package/{index-c1x0be5j.js → index-bea2gwc6.js} +2 -2
  34. package/{index-v29746vb.js → index-e9prnmyt.js} +3 -3
  35. package/{index-tn4rpqp1.js → index-fjsv10hd.js} +73 -49
  36. package/index-g0vwzy9m.js +241 -0
  37. package/{index-c76k723e.js → index-h85cxfkg.js} +2 -2
  38. package/{index-rwq3ekqc.js → index-hnby7p9n.js} +7 -7
  39. package/{index-2etcv0sc.js → index-jwqtchha.js} +1 -1
  40. package/index-kc8z134d.js +248 -0
  41. package/index-nq045px5.js +1124 -0
  42. package/{index-fm46xrf1.js → index-nq2ja5e0.js} +3 -3
  43. package/{index-0bm04n5g.js → index-pyxn1b53.js} +10 -8
  44. package/{index-05g3jayg.js → index-rhbabmja.js} +1 -1
  45. package/{index-2mx692qq.js → index-rk9sdys3.js} +2 -2
  46. package/{index-7bymavqk.js → index-sby8fe1p.js} +1 -1
  47. package/{index-ctanya41.js → index-sqfdmb5b.js} +4 -4
  48. package/{index-2t4tqd1c.js → index-t7nws9mx.js} +3 -3
  49. package/{index-jyv387qw.js → index-tv8fjaba.js} +4 -4
  50. package/{index-gnyf1fqy.js → index-tz5evhdr.js} +2 -2
  51. package/{index-aprt7pkz.js → index-xpdgxsj5.js} +229 -1327
  52. package/{index-e0cvze1s.js → index-z507nzhg.js} +4 -4
  53. package/index.js +22 -22
  54. package/{library.command-gaga5bmq.js → library.command-8tw59xzj.js} +3 -3
  55. package/{localRegistry.command-9e84e32q.js → localRegistry.command-tf3dmznd.js} +12 -9
  56. package/{module.command-7vpf6db4.js → module.command-9vm8hffy.js} +5 -5
  57. package/{package.command-twmvvf69.js → package.command-erbxe2tz.js} +3 -3
  58. package/package.json +2 -2
  59. package/{page.command-5asq7fdf.js → page.command-jpavcj0p.js} +3 -3
  60. package/{pipeline.command-d0v38572.js → pipeline.command-b8rzvjvv.js} +220 -219
  61. package/{primitive.command-dyrs2fm8.js → primitive.command-sm32chzk.js} +6 -6
  62. package/{projectTypecheck-cvtkrzp2.js → projectTypecheck-jamtx95x.js} +1 -1
  63. package/{quality.command-6emsqpbg.js → quality.command-pkd17n6y.js} +2 -2
  64. package/{repair.command-8t6aegv5.js → repair.command-8y4mq6hs.js} +5 -5
  65. package/{scalar.command-bnggy1x7.js → scalar.command-q8khmyx5.js} +4 -4
  66. package/{subspace.command-f02j5tge.js → subspace.command-51n0ykbx.js} +421 -362
  67. package/templates/app/env/env.client.type.ts +2 -3
  68. package/templates/app/page/styles.css.template +8 -0
  69. package/{tunnel.command-9x8nfn75.js → tunnel.command-7820vtxq.js} +7 -4
  70. package/{workflow.command-jx0wag5r.js → workflow.command-n5sv4wqz.js} +10 -10
  71. package/{workspace.command-cmj6mqyk.js → workspace.command-zdy60jh8.js} +33 -30
  72. package/TunnelShare-21mm6dr5.js +0 -9
  73. package/cloud.script-r7eyfey4.js +0 -20
  74. package/context.runner-hqd1ppqe.js +0 -21
  75. package/index-6w0rbr9d.js +0 -68
  76. package/index-r2tpeb3n.js +0 -392
@@ -4,94 +4,336 @@ import {
4
4
  FileSys2
5
5
  } from "./index-52w0fvh8.js";
6
6
 
7
- // pkgs/@akanjs/devkit/transforms/serverEnvProfile.ts
7
+ // pkgs/@akanjs/devkit/typecheck/projectTypecheck.ts
8
+ import { mkdir, rm } from "fs/promises";
8
9
  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 ProjectTypecheck2 {
12
+ projectDir;
13
+ workspaceRoot;
14
+ env;
15
+ static #testIncludes = ["**/*.test.ts", "**/*.test.tsx", "**/*.spec.ts", "**/*.spec.tsx"];
16
+ static #excludes = [".akan/**", "node_modules/**", "local/**"];
17
+ constructor(projectDir, workspaceRoot, env) {
18
+ this.projectDir = projectDir;
19
+ this.workspaceRoot = workspaceRoot;
20
+ this.env = env;
21
+ }
22
+ get typecheckDir() {
23
+ return path.join(this.projectDir, ".akan", "typecheck");
24
+ }
25
+ async run(entries, { clean = false, incremental = true, tests = false } = {}) {
26
+ const name = tests ? "tsconfig.tests" : "tsconfig";
27
+ await mkdir(this.typecheckDir, { recursive: true });
28
+ const tsconfig = {
29
+ extends: "../../tsconfig.json",
30
+ compilerOptions: { noEmit: true, incremental, tsBuildInfoFile: `./${name}.tsbuildinfo` },
31
+ include: [...entries, ...tests ? ProjectTypecheck2.#testIncludes.map((glob) => `../../${glob}`) : []],
32
+ ...tests ? { exclude: ProjectTypecheck2.#excludes.map((glob) => `../../${glob}`) } : {},
33
+ references: []
34
+ };
35
+ const tsconfigPath = path.join(this.typecheckDir, `${name}.json`);
36
+ await Bun.write(tsconfigPath, `${JSON.stringify(tsconfig, null, 2)}
37
+ `);
38
+ if (clean)
39
+ await rm(path.join(this.typecheckDir, `${name}.tsbuildinfo`), { force: true });
40
+ await this.#check(tsconfigPath);
41
+ return tsconfigPath;
42
+ }
43
+ async#check(tsconfigPath) {
44
+ const proc = Bun.spawn([process.execPath, await ProjectTypecheck2.workerEntry(this.workspaceRoot)], {
45
+ cwd: this.workspaceRoot,
46
+ env: { ...this.env, AKAN_COMMAND_TYPE: "typecheck", AKAN_TYPECHECK_TSCONFIG: tsconfigPath },
47
+ stdout: "pipe",
48
+ stderr: "pipe"
49
+ });
50
+ const [stdout, stderr, exitCode] = await Promise.all([
51
+ new Response(proc.stdout).text(),
52
+ new Response(proc.stderr).text(),
53
+ proc.exited
54
+ ]);
55
+ if (exitCode !== 0)
56
+ throw new Error((stderr || stdout).trim() || `Typecheck failed with exit code ${exitCode}`);
57
+ }
58
+ static async workerEntry(workspaceRoot) {
59
+ const candidates = [
60
+ path.join(workspaceRoot, "pkgs/@akanjs/devkit/typecheck/typecheck.proc.ts"),
61
+ path.join(workspaceRoot, "node_modules/@akanjs/devkit/typecheck/typecheck.proc.ts"),
62
+ path.join(import.meta.dir, "typecheck.proc.js"),
63
+ path.join(import.meta.dir, "typecheck.proc.ts"),
64
+ path.join(import.meta.dir, "..", "typecheck.proc.js")
65
+ ];
66
+ for (const candidate of candidates)
67
+ if (await Bun.file(candidate).exists())
68
+ return candidate;
69
+ throw new Error(`[devkit] typecheck worker entry not found; looked in: ${candidates.join(", ")}`);
70
+ }
71
+ }
72
+
73
+ // pkgs/@akanjs/devkit/transforms/serverEnvProfile.ts
74
+ import { existsSync, realpathSync } from "fs";
75
+ import path2 from "path";
76
+ import { Logger, toError } from "akanjs/common";
10
77
 
11
78
  class ServerEnvProfile {
12
79
  static namePattern = /^[a-z][a-zA-Z0-9-]*$/;
80
+ static envFilePattern = /[\\/]env[\\/]env\.server\.[A-Za-z0-9-]+\.ts$/;
13
81
  static fileName(environment, profile) {
14
82
  return `env.server.${environment}.${profile}.ts`;
15
83
  }
16
84
  static profileFileOf(file, profile) {
17
85
  return file.replace(/\.ts$/, `.${profile}.ts`);
18
86
  }
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 }] : [];
87
+ static #url(value) {
88
+ if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(value))
89
+ return null;
90
+ try {
91
+ return new URL(value);
92
+ } catch {
93
+ return null;
40
94
  }
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
95
  }
45
- static secretPaths(value, at = "", underSecret = false) {
96
+ static #carriesCredential(url) {
97
+ return !!url.password || [...url.searchParams.keys()].some((name) => Logger.isSecretKey(name));
98
+ }
99
+ static #isCredentialUrl(value) {
100
+ const url = ServerEnvProfile.#url(value);
101
+ return !!url && ServerEnvProfile.#carriesCredential(url);
102
+ }
103
+ static #isSecret(value, underSecret) {
104
+ return underSecret && !!value.trim() || ServerEnvProfile.#isCredentialUrl(value);
105
+ }
106
+ static #fold(value, fold, at = "", underSecret = false) {
46
107
  if (typeof value === "string")
47
- return underSecret && value.trim() ? [at] : [];
108
+ return fold.text(value, at, underSecret);
48
109
  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)));
110
+ return fold.other(value);
111
+ return fold.node(value, Object.entries(value).map(([key, child]) => [
112
+ key,
113
+ ServerEnvProfile.#fold(child, fold, at ? `${at}.${key}` : key, underSecret || Logger.isSecretKey(key))
114
+ ]));
115
+ }
116
+ static #collect(text) {
117
+ return { text, other: () => [], node: (_, children) => children.flatMap(([, found]) => found) };
118
+ }
119
+ static strip(value) {
120
+ return ServerEnvProfile.#fold(value, {
121
+ text: (text, at, underSecret) => ServerEnvProfile.#isSecret(text, underSecret) ? { value: undefined, stripped: [at] } : { value: text, stripped: [] },
122
+ other: (other) => ({ value: other, stripped: [] }),
123
+ node: (node, children) => {
124
+ const stripped = children.flatMap(([, result]) => result.stripped);
125
+ if (Array.isArray(node))
126
+ return {
127
+ value: children.flatMap(([, result]) => result.value === undefined || result.stripped.length && typeof result.value === "object" && !Object.keys(result.value ?? {}).length ? [] : [result.value]),
128
+ stripped
129
+ };
130
+ return {
131
+ value: Object.fromEntries(children.flatMap(([key, result]) => result.value === undefined ? [] : [[key, result.value]])),
132
+ stripped
133
+ };
134
+ }
135
+ });
51
136
  }
52
- static async evaluate(file, cwd) {
137
+ static secretValues(value) {
138
+ return ServerEnvProfile.#fold(value, ServerEnvProfile.#collect((text, at, underSecret) => {
139
+ const url = ServerEnvProfile.#url(text);
140
+ if (url)
141
+ return ServerEnvProfile.#carriesCredential(url) || underSecret && (/webhook/i.test(at) || ServerEnvProfile.distinctive(text)) ? [...new Set([text, url.password])].filter((part) => part.length >= 8).map((part) => ({ path: at, value: part })) : [];
142
+ const named = Logger.isSecretKey(at.split(".").at(-1) ?? "");
143
+ return underSecret && text.length >= 8 && (named || ServerEnvProfile.distinctive(text)) ? [{ path: at, value: text }] : [];
144
+ }));
145
+ }
146
+ static distinctive(value) {
147
+ const url = ServerEnvProfile.#url(value);
148
+ if (url)
149
+ return ServerEnvProfile.#carriesCredential(url) || [...url.pathname.split("/"), ...url.searchParams.values()].some((part) => ServerEnvProfile.distinctive(part));
150
+ return value.length >= 16 && !/^https?:\/\//.test(value) && !/^[a-z]+(?:[._-][a-z]+)*$/.test(value);
151
+ }
152
+ static secretPaths(value) {
153
+ return ServerEnvProfile.#fold(value, ServerEnvProfile.#collect((text, at, underSecret) => ServerEnvProfile.#isSecret(text, underSecret) ? [at] : []));
154
+ }
155
+ static async evaluate(file, cwd, load) {
156
+ return (await ServerEnvProfile.#evaluated(file, cwd, load)).value;
157
+ }
158
+ static async#evaluated(file, cwd, load) {
159
+ const pattern = (regex, flags = "") => `new RegExp(${JSON.stringify(regex.source)}, "${flags}")`;
160
+ const literal = /(["'`])((?:\\[\s\S]|(?!\1)[^\\])*)\1/;
161
+ const frame = /^\s*at (?:[^(]*\()?(.+):(\d+):(\d+)\)?\s*$/;
53
162
  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));"
163
+ "const waiting = new Map();",
164
+ 'process.on("message", ({ load, error, loader, contents }) => {',
165
+ " const reply = waiting.get(load);",
166
+ " waiting.delete(load);",
167
+ " if (error === undefined) reply?.resolve({ loader, contents });",
168
+ " else reply?.reject(new Error(error));",
169
+ "});",
170
+ "if (process.env.AKAN_PROFILE_ENV_LOAD)",
171
+ " Bun.plugin({",
172
+ ' name: "akan-server-env",',
173
+ " setup(build) {",
174
+ " build.onLoad({ filter: new RegExp(process.env.AKAN_PROFILE_ENV_LOAD) }, (args) =>",
175
+ " new Promise((resolve, reject) => {",
176
+ " waiting.set(args.path, { resolve, reject });",
177
+ " process.send({ load: args.path });",
178
+ " }));",
179
+ " },",
180
+ " });",
181
+ `const literal = ${pattern(literal, "g")};`,
182
+ `const interpolation = ${pattern(/\$\{[^}]*\}/)};`,
183
+ `const frameOf = ${pattern(frame)};`,
184
+ `const evaluating = ${pattern(/\s*\(evaluating '[\s\S]*'\)\s*$/)};`,
185
+ "const literalsOf = async (file) =>",
186
+ ' [...(await Bun.file(file).text().catch(() => "")).matchAll(literal)].flatMap((match) => match[2].split(interpolation));',
187
+ "const describe = async (error) => {",
188
+ " const first = Array.isArray(error?.errors) && error.errors.length ? error.errors[0] : error;",
189
+ ' const frame = String(first?.stack ?? "").split("\\n").map((line) => frameOf.exec(line)).find(Boolean);',
190
+ " const at = first?.position?.file ? [first.position.file, first.position.line, first.position.column] : frame ? frame.slice(1, 4) : [];",
191
+ " const files = [process.env.AKAN_PROFILE_ENV_FILE, at[0]].filter(Boolean);",
192
+ " const literals = (await Promise.all(files.map(literalsOf))).flat().filter((part) => part.length >= 4);",
193
+ ' let message = String(first?.message ?? first).replace(evaluating, "");',
194
+ " for (const part of literals.sort((a, b) => b.length - a.length))",
195
+ ' message = message.split(part).join("[redacted]");',
196
+ ' const name = typeof first?.name === "string" && /^[A-Za-z]+$/.test(first.name) ? first.name : "Error";',
197
+ ' return name + ": " + message.slice(0, 200) + (at.length ? " (at " + at.join(":") + ")" : "");',
198
+ "};",
199
+ "try {",
200
+ " const { env } = await import(process.env.AKAN_PROFILE_ENV_FILE);",
201
+ " const lossy = [];",
202
+ " const walk = (value, at) => {",
203
+ ' if (typeof value === "function" || typeof value === "symbol" || typeof value === "bigint" || (typeof value === "number" && !Number.isFinite(value))) return lossy.push(at);',
204
+ ' if (!value || typeof value !== "object") return;',
205
+ " const proto = Object.getPrototypeOf(value);",
206
+ " if (!Array.isArray(value) && proto !== Object.prototype && proto !== null) return lossy.push(at);",
207
+ ' for (const [key, child] of Object.entries(value)) walk(child, at ? at + "." + key : key);',
208
+ " };",
209
+ ' walk(env, "");',
210
+ ' const plain = (_, value) => (typeof value === "function" || typeof value === "symbol" ? undefined : typeof value === "bigint" ? String(value) : value);',
211
+ ' process.send({ env: JSON.stringify(env ?? null, plain) ?? "null", lossy }, undefined, undefined, () => process.disconnect());',
212
+ "} catch (error) {",
213
+ " process.exitCode = 1;",
214
+ " process.send({ failure: await describe(error) }, undefined, undefined, () => process.disconnect());",
215
+ "}"
57
216
  ].join(`
58
217
  `);
218
+ let sent;
219
+ let failure;
220
+ let loadError;
59
221
  const child = Bun.spawn([process.execPath, "--eval", script], {
60
222
  cwd,
61
- env: { PATH: process.env.PATH ?? "", HOME: process.env.HOME ?? "", AKAN_PROFILE_ENV_FILE: file },
62
- stdout: "pipe",
63
- stderr: "pipe"
223
+ env: {
224
+ PATH: process.env.PATH ?? "",
225
+ HOME: process.env.HOME ?? "",
226
+ AKAN_PROFILE_ENV_FILE: file,
227
+ ...load ? { AKAN_PROFILE_ENV_LOAD: ServerEnvProfile.envFilePattern.source } : {}
228
+ },
229
+ stdout: "ignore",
230
+ stderr: "ignore",
231
+ serialization: "json",
232
+ ipc: (message, subprocess) => {
233
+ if ("env" in message) {
234
+ sent = message;
235
+ return;
236
+ }
237
+ if ("failure" in message) {
238
+ failure = message.failure;
239
+ return;
240
+ }
241
+ if (!load)
242
+ return;
243
+ const requested = message.load;
244
+ load(requested).then(({ loader, contents }) => ({ load: requested, loader, contents }), (error) => {
245
+ loadError ??= error;
246
+ return { load: requested, error: toError(error).message };
247
+ }).then((reply) => subprocess.send(reply)).catch(() => {
248
+ return;
249
+ });
250
+ }
64
251
  });
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");
252
+ const exitCode = await child.exited;
253
+ if (exitCode !== 0 || !sent) {
254
+ if (loadError)
255
+ throw loadError;
256
+ throw new Error(`${path2.basename(file)} could not be evaluated to check it for secrets: ${failure ?? `its process exited with code ${exitCode} and sent nothing`}`);
257
+ }
258
+ return { value: JSON.parse(sent.env), lossy: sent.lossy };
259
+ }
260
+ static async sourceInPlaceOf(profileFile, replacedFile, profile) {
261
+ const contents = await Bun.file(profileFile).text();
262
+ const dir = path2.dirname(profileFile);
263
+ const replaced = ServerEnvProfile.#real(replacedFile);
264
+ const selfImports = new Bun.Transpiler({ loader: "ts" }).scanImports(contents).flatMap(({ path: specifier }) => {
265
+ try {
266
+ return ServerEnvProfile.#real(Bun.resolveSync(specifier, dir)) === replaced ? [specifier] : [];
267
+ } catch {
268
+ return [];
269
+ }
270
+ });
271
+ if (selfImports.length)
272
+ throw new Error(`${path2.basename(profileFile)} imports ${selfImports.join(", ")}, the file a "${profile}" build bundles it in place of, so it would import itself. Write the values it needs into it instead.`);
273
+ return contents;
274
+ }
275
+ static #real(file) {
276
+ return existsSync(file) ? realpathSync.native(file) : path2.resolve(file);
277
+ }
278
+ static async libEnvSource(file, profile, chain = []) {
279
+ if (chain.includes(file))
280
+ throw new Error(`${[...chain, file].join(" -> ")}: lib env files reach each other, which a "${profile}" build cannot swap.`);
281
+ const profileFile = ServerEnvProfile.profileFileOf(file, profile);
282
+ const libRoot = path2.dirname(path2.dirname(file));
283
+ if (await Bun.file(profileFile).exists()) {
284
+ const contents = await ServerEnvProfile.sourceInPlaceOf(profileFile, file, profile);
285
+ const reached = [...chain, file];
286
+ const value = await ServerEnvProfile.evaluate(profileFile, libRoot, (envFile) => ServerEnvProfile.#underProfile(envFile, profile, new Set, reached));
287
+ const secrets = ServerEnvProfile.secretPaths(value);
288
+ if (secrets.length)
289
+ throw new Error(`${profileFile} holds secret-named values, which the "${profile}" bundle carries to every computer it runs on: ${secrets.join(", ")}.`);
290
+ return { loader: "ts", contents, profileFile: true, stripped: [] };
291
+ }
292
+ const { exports } = new Bun.Transpiler({ loader: "ts" }).scan(await Bun.file(file).text());
293
+ if (exports.some((name) => name !== "env"))
294
+ throw new Error(`${file} exports ${exports.join(", ")}: a "${profile}" build carries only a lib env's \`env\`, so give the lib ${path2.basename(profileFile)}.`);
295
+ const evaluated = await ServerEnvProfile.#evaluated(file, libRoot);
296
+ if (evaluated.lossy.length)
297
+ throw new Error(`${file} holds ${evaluated.lossy.join(", ")}, which a "${profile}" build cannot carry: it bundles a lib env's defaults as JSON, which keeps no function, Map, Set, Date or class instance. Give the lib ${path2.basename(profileFile)}.`);
298
+ const { value, stripped } = ServerEnvProfile.strip(evaluated.value);
299
+ return {
300
+ loader: "js",
301
+ contents: `export const env = ${JSON.stringify(value ?? {})};
302
+ `,
303
+ profileFile: false,
304
+ stripped
305
+ };
306
+ }
307
+ static async#underProfile(envFile, profile, appEnvDirs, chain) {
308
+ const name = path2.basename(envFile, ".ts").slice("env.server.".length);
309
+ if (appEnvDirs.has(path2.dirname(envFile)) || name === "type" || name === "example")
310
+ return { loader: "ts", contents: await Bun.file(envFile).text() };
311
+ const { loader, contents } = await ServerEnvProfile.libEnvSource(envFile, profile, chain);
312
+ return { loader, contents };
74
313
  }
75
314
  static async assertShippable({ envDir, environment, profile }) {
76
315
  if (!ServerEnvProfile.namePattern.test(profile))
77
316
  throw new Error(`The server env profile "${profile}" must be a lowercase-first name of letters, digits and dashes.`);
78
317
  const name = ServerEnvProfile.fileName(environment, profile);
79
- const file = path.join(envDir, name);
318
+ const file = path2.join(envDir, name);
80
319
  if (!await Bun.file(file).exists())
81
320
  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)));
321
+ await ServerEnvProfile.sourceInPlaceOf(file, path2.join(envDir, `env.server.${environment}.ts`), profile);
322
+ const appEnvDirs = new Set([path2.resolve(envDir), realpathSync.native(envDir)]);
323
+ const value = await ServerEnvProfile.evaluate(file, path2.dirname(envDir), (envFile) => ServerEnvProfile.#underProfile(envFile, profile, appEnvDirs, []));
324
+ const secrets = ServerEnvProfile.secretPaths(value);
83
325
  if (secrets.length)
84
326
  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
327
  }
86
328
  }
87
329
 
88
330
  // pkgs/@akanjs/devkit/artifact/implicitRootLayout.ts
89
- import { readdir, rm } from "fs/promises";
90
- import path3 from "path";
331
+ import { readdir, rm as rm2 } from "fs/promises";
332
+ import path4 from "path";
91
333
 
92
334
  // pkgs/@akanjs/devkit/transforms/asyncDefaultExportDetector.ts
93
335
  import fs from "fs";
94
- import path2 from "path";
336
+ import path3 from "path";
95
337
 
96
338
  class AsyncDefaultExportDetector {
97
339
  static #typescriptLoad;
@@ -102,7 +344,7 @@ class AsyncDefaultExportDetector {
102
344
  static async detect(moduleAbsPath) {
103
345
  try {
104
346
  const typescript = await AsyncDefaultExportDetector.#loadTypescript();
105
- const source = fs.readFileSync(path2.resolve(moduleAbsPath), "utf8");
347
+ const source = fs.readFileSync(path3.resolve(moduleAbsPath), "utf8");
106
348
  const sourceFile = typescript.createSourceFile(moduleAbsPath, source, typescript.ScriptTarget.Latest, true, /\.[tj]sx$/.test(moduleAbsPath) ? typescript.ScriptKind.TSX : typescript.ScriptKind.TS);
107
349
  return new AsyncDefaultExportDetector(typescript).detectInSourceFile(sourceFile);
108
350
  } catch {
@@ -166,17 +408,17 @@ class AsyncDefaultExportDetector {
166
408
 
167
409
  // pkgs/@akanjs/devkit/artifact/implicitRootLayout.ts
168
410
  var LAYOUT_KEY_RE = /^\.\/(.+\/)?_layout\.(tsx|ts|jsx|js)$/;
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");
411
+ var IMPLICIT_LAYOUT_DIR = path4.join(".akan", "generated", "root-layouts");
412
+ var IMPLICIT_DICT_DIR = path4.join(".akan", "generated", "dict");
413
+ var IMPLICIT_OVERRIDES_DIR = path4.join(".akan", "generated", "overrides");
172
414
  var OVERRIDES_KEY_RE = /^\.\/(.+\/)?_overrides\.(tsx|ts|jsx|js)$/;
173
415
  var importSpecifier = (fromAbsPath, toAbsPath) => {
174
- const rel = path3.relative(path3.dirname(fromAbsPath), toAbsPath).split(path3.sep).join("/");
416
+ const rel = path4.relative(path4.dirname(fromAbsPath), toAbsPath).split(path4.sep).join("/");
175
417
  return rel.startsWith(".") ? rel : `./${rel}`;
176
418
  };
177
419
  async function writeGeneratedOverridesLayoutFile(opts) {
178
420
  const filename = `${opts.key.replace(/^\.\//, "").replace(/[^a-zA-Z0-9]+/g, "_")}.tsx`;
179
- const absPath = path3.join(path3.resolve(opts.appCwdPath), IMPLICIT_OVERRIDES_DIR, filename);
421
+ const absPath = path4.join(path4.resolve(opts.appCwdPath), IMPLICIT_OVERRIDES_DIR, filename);
180
422
  const source = `"use client";
181
423
  import { UiOverrideProvider } from "akanjs/ui";
182
424
  import { createElement, type ReactNode } from "react";
@@ -192,7 +434,7 @@ export default function AkanUiOverridesLayout({ children }: { children?: ReactNo
192
434
  async function pruneGeneratedFiles(dirAbsPath, keepAbsPaths) {
193
435
  const keep = new Set(keepAbsPaths);
194
436
  const names = await readdir(dirAbsPath).catch(() => []);
195
- await Promise.all(names.map((name) => path3.join(dirAbsPath, name)).filter((absPath) => !keep.has(absPath)).map((absPath) => rm(absPath, { force: true })));
437
+ await Promise.all(names.map((name) => path4.join(dirAbsPath, name)).filter((absPath) => !keep.has(absPath)).map((absPath) => rm2(absPath, { force: true })));
196
438
  }
197
439
  function getRootBoundarySegments(key) {
198
440
  const match = LAYOUT_KEY_RE.exec(key);
@@ -208,7 +450,7 @@ function implicitRootLayoutKey(segments) {
208
450
  }
209
451
  function implicitRootLayoutAbsPath(appCwdPath, segments) {
210
452
  const filename = segments.length ? `${segments.join("__")}__root_layout.tsx` : "__root_layout.tsx";
211
- return path3.join(path3.resolve(appCwdPath), IMPLICIT_LAYOUT_DIR, filename);
453
+ return path4.join(path4.resolve(appCwdPath), IMPLICIT_LAYOUT_DIR, filename);
212
454
  }
213
455
  function isRootBoundarySegments(segments, basePaths) {
214
456
  const firstVisibleIndex = segments.findIndex((segment) => !/^\(.+\)$/.test(segment));
@@ -231,7 +473,7 @@ function findRootBoundaries(pageKeys, appCwdPath, basePaths) {
231
473
  const id = segments.join("/");
232
474
  boundaries.set(id, {
233
475
  sourceKey: key,
234
- sourceAbsPath: path3.resolve(appCwdPath, "page", key.replace(/^\.\//, "")),
476
+ sourceAbsPath: path4.resolve(appCwdPath, "page", key.replace(/^\.\//, "")),
235
477
  segments
236
478
  });
237
479
  }
@@ -247,16 +489,16 @@ function findExplicitRootLayoutAbsPath(pageKeys, appCwdPath) {
247
489
  const segments = getRootBoundarySegments(key);
248
490
  return segments !== null && segments.length === 0;
249
491
  });
250
- return rootLayoutKey ? path3.resolve(appCwdPath, "page", rootLayoutKey.replace(/^\.\//, "")) : null;
492
+ return rootLayoutKey ? path4.resolve(appCwdPath, "page", rootLayoutKey.replace(/^\.\//, "")) : null;
251
493
  }
252
494
  async function assertEnvClientConvention(appCwdPath, appName) {
253
- const envPath = path3.join(appCwdPath, "env", "env.client.ts");
495
+ const envPath = path4.join(appCwdPath, "env", "env.client.ts");
254
496
  if (!await Bun.file(envPath).exists()) {
255
497
  throw new Error(`[route-convention] app "${appName}" must provide env/env.client.ts exporting "env" for generated System.Provider`);
256
498
  }
257
499
  }
258
500
  async function writeGeneratedDictionaryMacroFile(appCwdPath, appName) {
259
- const absPath = path3.join(path3.resolve(appCwdPath), IMPLICIT_DICT_DIR, "useDict.ts");
501
+ const absPath = path4.join(path4.resolve(appCwdPath), IMPLICIT_DICT_DIR, "useDict.ts");
260
502
  await Bun.write(absPath, `import { getAllDictionary } from "@apps/${appName}/lib/dict" with { type: "macro" };
261
503
 
262
504
  export const allDictionary = getAllDictionary();
@@ -275,13 +517,13 @@ async function writeGeneratedRootLayoutFile(opts) {
275
517
  void st;
276
518
  ` : `import "@apps/${opts.appName}/client";
277
519
  `;
278
- const inheritedLabel = inheritedSourceAbsPath ? path3.relative(opts.appCwdPath, inheritedSourceAbsPath) : "";
520
+ const inheritedLabel = inheritedSourceAbsPath ? path4.relative(opts.appCwdPath, inheritedSourceAbsPath) : "";
279
521
  const inheritedImport = inheritedSourceSpecifier ? `import * as inheritedModule from ${JSON.stringify(inheritedSourceSpecifier)};
280
522
  const inheritedLayout = resolveRouteModule(inheritedModule as never, ${JSON.stringify(inheritedLabel)}).module as LayoutModule;
281
523
  ` : `const inheritedLayout: LayoutModule = {};
282
524
  `;
283
525
  const prefix = opts.boundary.segments.find((segment) => !/^\(.+\)$/.test(segment)) ?? null;
284
- const userLabel = opts.boundary.sourceAbsPath ? path3.relative(opts.appCwdPath, opts.boundary.sourceAbsPath) : "";
526
+ const userLabel = opts.boundary.sourceAbsPath ? path4.relative(opts.appCwdPath, opts.boundary.sourceAbsPath) : "";
285
527
  const userImport = sourceSpecifier ? `import * as userModule from ${JSON.stringify(sourceSpecifier)};
286
528
  const userLayout = resolveRouteModule(userModule as never, ${JSON.stringify(userLabel)}).module as LayoutModule;
287
529
  const UserLayout = userLayout.default as (props: LayoutProps) => ReactNode | Promise<ReactNode>;
@@ -360,8 +602,8 @@ ${layoutExports}${layoutReturn};
360
602
  return absPath;
361
603
  }
362
604
  async function resolveSsrPageEntries(opts) {
363
- const absPageDir = path3.resolve(opts.appCwdPath, "page");
364
- const hasSt = await Bun.file(path3.join(opts.appCwdPath, "lib", "st.ts")).exists();
605
+ const absPageDir = path4.resolve(opts.appCwdPath, "page");
606
+ const hasSt = await Bun.file(path4.join(opts.appCwdPath, "lib", "st.ts")).exists();
365
607
  const basePaths = opts.basePaths ?? [];
366
608
  const rootSourceAbsPath = findExplicitRootLayoutAbsPath(opts.pageKeys, opts.appCwdPath);
367
609
  const rootBoundaries = findRootBoundaries(opts.pageKeys, opts.appCwdPath, basePaths);
@@ -370,7 +612,7 @@ async function resolveSsrPageEntries(opts) {
370
612
  return segments !== null && isRootBoundarySegments(segments, basePaths);
371
613
  }));
372
614
  const base = await Promise.all(opts.pageKeys.filter((key) => !rootLayoutKeys.has(key)).map(async (key) => {
373
- const userAbsPath = path3.resolve(absPageDir, key);
615
+ const userAbsPath = path4.resolve(absPageDir, key);
374
616
  if (OVERRIDES_KEY_RE.test(key)) {
375
617
  const moduleAbsPath = await writeGeneratedOverridesLayoutFile({
376
618
  appCwdPath: opts.appCwdPath,
@@ -393,10 +635,10 @@ async function resolveSsrPageEntries(opts) {
393
635
  }),
394
636
  seedAbsPaths: [...new Set([boundary.sourceAbsPath, rootSourceAbsPath].filter((absPath) => absPath !== null))]
395
637
  })));
396
- const appCwdAbsPath = path3.resolve(opts.appCwdPath);
638
+ const appCwdAbsPath = path4.resolve(opts.appCwdPath);
397
639
  await Promise.all([
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))
640
+ pruneGeneratedFiles(path4.join(appCwdAbsPath, IMPLICIT_OVERRIDES_DIR), base.filter(({ key }) => OVERRIDES_KEY_RE.test(key)).map(({ moduleAbsPath }) => moduleAbsPath)),
641
+ pruneGeneratedFiles(path4.join(appCwdAbsPath, IMPLICIT_LAYOUT_DIR), generated.map(({ moduleAbsPath }) => moduleAbsPath))
400
642
  ]);
401
643
  return [...base, ...generated].sort((a, b) => a.key.localeCompare(b.key));
402
644
  }
@@ -406,19 +648,19 @@ async function resolveSsrPageEntriesForApp(app, pageKeys) {
406
648
  }
407
649
 
408
650
  // pkgs/@akanjs/devkit/codegenLock.ts
409
- import { mkdir, open, readFile, rm as rm2, stat } from "fs/promises";
410
- import path4 from "path";
411
- import { Logger } from "akanjs/common";
651
+ import { mkdir as mkdir2, open, readFile, rm as rm3, stat } from "fs/promises";
652
+ import path5 from "path";
653
+ import { Logger as Logger2 } from "akanjs/common";
412
654
 
413
655
  class CodegenLock {
414
656
  static fileName = "codegen.lock";
415
657
  static waitTimeoutMs = 1e4;
416
658
  static unknownHolderStaleMs = 60000;
417
659
  static #pollMs = 25;
418
- static #logger = new Logger("CodegenLock");
660
+ static #logger = new Logger2("CodegenLock");
419
661
  static #queue = Promise.resolve();
420
662
  static pathIn(workspaceRoot) {
421
- return path4.join(workspaceRoot, "local", ".akan", CodegenLock.fileName);
663
+ return path5.join(workspaceRoot, "local", ".akan", CodegenLock.fileName);
422
664
  }
423
665
  static async run(workspaceRoot, label, fn) {
424
666
  const ahead = CodegenLock.#queue;
@@ -448,7 +690,7 @@ class CodegenLock {
448
690
  }
449
691
  static async#withFileLock(workspaceRoot, label, fn) {
450
692
  const lockPath = CodegenLock.pathIn(workspaceRoot);
451
- await mkdir(path4.dirname(lockPath), { recursive: true }).catch(() => {
693
+ await mkdir2(path5.dirname(lockPath), { recursive: true }).catch(() => {
452
694
  return;
453
695
  });
454
696
  const held = await CodegenLock.#acquire(lockPath, label);
@@ -456,7 +698,7 @@ class CodegenLock {
456
698
  return await fn();
457
699
  } finally {
458
700
  if (held)
459
- await rm2(lockPath, { force: true }).catch(() => {
701
+ await rm3(lockPath, { force: true }).catch(() => {
460
702
  return;
461
703
  });
462
704
  }
@@ -493,7 +735,7 @@ class CodegenLock {
493
735
  return false;
494
736
  } else if (Date.now() - info.mtimeMs < CodegenLock.unknownHolderStaleMs)
495
737
  return false;
496
- await rm2(lockPath, { force: true }).catch(() => {
738
+ await rm3(lockPath, { force: true }).catch(() => {
497
739
  return;
498
740
  });
499
741
  return true;
@@ -590,26 +832,27 @@ import {
590
832
  fork,
591
833
  spawn as spawn2
592
834
  } from "child_process";
593
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
835
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
594
836
  import {
595
837
  copyFile,
596
838
  cp as cpEntry,
597
839
  lstat,
598
- mkdir as mkdir2,
840
+ mkdir as mkdir3,
599
841
  readdir as readDirEntries,
600
842
  realpath,
601
- rm as rm3,
843
+ rm as rm4,
602
844
  stat as stat2,
603
845
  symlink
604
846
  } from "fs/promises";
605
- import path12 from "path";
847
+ import path13 from "path";
606
848
  import { pathToFileURL } from "url";
607
849
  import {
608
850
  capitalize,
609
851
  getPageSourceFileViolation,
610
852
  isRouteSourceFile,
611
- Logger as Logger2,
853
+ Logger as Logger3,
612
854
  parseRouteModuleKey,
855
+ toError as toError2,
613
856
  validatePageSourceFile,
614
857
  validateSubRoutePageKey
615
858
  } from "akanjs/common";
@@ -755,7 +998,7 @@ ${CLAUDE_COMMENT_RULE}
755
998
  `;
756
999
 
757
1000
  // pkgs/@akanjs/devkit/akanConfig/akanBin.ts
758
- import path5 from "path";
1001
+ import path6 from "path";
759
1002
 
760
1003
  // pkgs/@akanjs/devkit/akanConfig/types.ts
761
1004
  import { archs, binPlatforms, defaultAppPipeline } from "akanjs";
@@ -806,7 +1049,7 @@ class AkanBin {
806
1049
  if (!archive)
807
1050
  throw new Error(`${at}: file picks the executable out of an archive, and this source is none`);
808
1051
  const parts = typeof file === "string" ? file.split(/[\\/]/).filter((part) => part && part !== ".") : [];
809
- if (!parts.length || path5.isAbsolute(file) || parts.includes(".."))
1052
+ if (!parts.length || path6.isAbsolute(file) || parts.includes(".."))
810
1053
  throw new Error(`${at}: file must be a path inside the archive (got ${JSON.stringify(file)})`);
811
1054
  return { ...resolved, file: parts.join("/") };
812
1055
  }
@@ -822,12 +1065,12 @@ class AkanBin {
822
1065
  throw new Error(`${at}: path must name a file`);
823
1066
  if (sha256 !== undefined)
824
1067
  throw new Error(`${at}: sha256 checks a download, and a path source downloads nothing`);
825
- return { path: path5.resolve(baseDir, localPath) };
1068
+ return { path: path6.resolve(baseDir, localPath) };
826
1069
  }
827
1070
  }
828
1071
  // pkgs/@akanjs/devkit/akanConfig/akanConfig.ts
829
1072
  import fs2 from "fs";
830
- import path6 from "path";
1073
+ import path7 from "path";
831
1074
  import { fileURLToPath } from "url";
832
1075
  import { DatabaseModes, normalizeRoutePrefix } from "akanjs/base";
833
1076
  import { resolveAkanI18nConfig } from "akanjs/common";
@@ -915,6 +1158,9 @@ var dedupeDockerRuns = (runs) => {
915
1158
  return [...byKey.values()];
916
1159
  };
917
1160
 
1161
+ class LibConfigRefusal extends Error {
1162
+ }
1163
+
918
1164
  class AkanAppConfig {
919
1165
  app;
920
1166
  rootPackageJson;
@@ -1025,6 +1271,8 @@ class AkanAppConfig {
1025
1271
  const { updates } = merged;
1026
1272
  if (updates && (!updates.url || !(updates.publicKey || updates.publicKeys?.length)))
1027
1273
  throw new Error(`${where}.updates in ${configPath} has no url or publicKeys${named ? "; give them in native.updates or the target's own" : ""}.`);
1274
+ if (updates?.publicKey !== undefined && updates.publicKeys !== undefined)
1275
+ throw new Error(`${where}.updates in ${configPath} has both publicKey and publicKeys${named ? ", native.updates merged in: give one, in native.updates' own form when overriding its keys" : ": give one"}.`);
1028
1276
  AkanAppConfig.#assertFiles(own ?? {}, where, configPath);
1029
1277
  const {
1030
1278
  basePath: _basePath,
@@ -1100,6 +1348,8 @@ class AkanAppConfig {
1100
1348
  "AKAN_DATABASE_MODES",
1101
1349
  "AKAN_SSR",
1102
1350
  "AKAN_CSR",
1351
+ "AKAN_MCP",
1352
+ "AKAN_SHUTDOWN_TIMEOUT_MS",
1103
1353
  "AKAN_MODULES"
1104
1354
  ]);
1105
1355
  static #desktopServerEnv(env, where, configPath) {
@@ -1118,11 +1368,14 @@ class AkanAppConfig {
1118
1368
  static #desktopServerWatchdog(watchdog, where, configPath) {
1119
1369
  if (watchdog === undefined || watchdog === false)
1120
1370
  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))
1371
+ const options = watchdog === true ? {} : watchdog;
1372
+ const whole = (value) => value === undefined || Number.isInteger(value) && value > 0;
1373
+ if (!AkanAppConfig.#isPlainObject(options) || Object.keys(options).some((key) => key !== "intervalMs" && key !== "failures") || !whole(options.intervalMs) || !whole(options.failures))
1124
1374
  throw new Error(`${where}.watchdog in ${configPath} must be true or { intervalMs, failures }, whole numbers from 1.`);
1125
- return { intervalMs, failures };
1375
+ return {
1376
+ intervalMs: options.intervalMs ?? 1e4,
1377
+ failures: options.failures ?? 3
1378
+ };
1126
1379
  }
1127
1380
  static #platformIndexPath(section) {
1128
1381
  const { indexPath: rawIndexPath, ...rest } = section;
@@ -1344,7 +1597,7 @@ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
1344
1597
  static async from(app, { bustImportCache = false } = {}) {
1345
1598
  const [configImp, baseDevEnv, libs, rootPackageJson] = await Promise.all([
1346
1599
  AkanAppConfig.importConfigModule(app.cwdPath, { bustImportCache }),
1347
- WorkspaceExecutor.getBaseDevEnv(path6.join(app.workspace.workspaceRoot, ".env")),
1600
+ WorkspaceExecutor.getBaseDevEnv(path7.join(app.workspace.workspaceRoot, ".env")),
1348
1601
  app.workspace.getLibs(),
1349
1602
  app.workspace.getPackageJson()
1350
1603
  ]);
@@ -1361,6 +1614,8 @@ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
1361
1614
  }
1362
1615
  static async#collectLibContributions(app, libs, bustImportCache) {
1363
1616
  const libConfigs = await Promise.all(libs.map(async (libName) => LibExecutor.from(app, libName).getConfig({ refresh: bustImportCache }).catch((error) => {
1617
+ if (error instanceof LibConfigRefusal)
1618
+ throw error;
1364
1619
  app.logger.warn(`Skipped libs/${libName}/akan.config.ts contributions: ${String(error)}`);
1365
1620
  return null;
1366
1621
  })));
@@ -1442,11 +1697,11 @@ var akanPackageJson = null;
1442
1697
  function getAkanPackageJson() {
1443
1698
  if (akanPackageJson)
1444
1699
  return akanPackageJson;
1445
- const sourceDir = path6.dirname(fileURLToPath(import.meta.url));
1700
+ const sourceDir = path7.dirname(fileURLToPath(import.meta.url));
1446
1701
  const packageJsonPaths = [
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")
1702
+ path7.join(sourceDir, "../../../akanjs/package.json"),
1703
+ path7.join(process.cwd(), "pkgs/akanjs/package.json"),
1704
+ path7.join(path7.dirname(Bun.main), "node_modules/akanjs/package.json")
1450
1705
  ];
1451
1706
  try {
1452
1707
  packageJsonPaths.unshift(Bun.resolveSync("akanjs/package.json", sourceDir));
@@ -1501,29 +1756,29 @@ class AkanLibConfig {
1501
1756
  const resolved = typeof configImp === "function" ? configImp(lib) : configImp;
1502
1757
  const { plugins, ...config } = resolved ?? {};
1503
1758
  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.`);
1759
+ throw new LibConfigRefusal(`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
1760
  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.`);
1761
+ throw new LibConfigRefusal(`libs/${lib.name}/akan.config.ts declares \`subspace\`, a workspace setting: it belongs in the workspace root akan.config.ts.`);
1507
1762
  return new AkanLibConfig(lib, config, plugins ?? []);
1508
1763
  }
1509
1764
  }
1510
1765
  //! need to refactor
1511
1766
  var shiftBuildNum = async (app, delta) => {
1512
1767
  const { buildNum } = (await AkanAppConfig.from(app)).native;
1513
- const akanConfigPath = path6.join(app.cwdPath, "akan.config.ts");
1768
+ const akanConfigPath = path7.join(app.cwdPath, "akan.config.ts");
1514
1769
  const akanConfig = fs2.readFileSync(akanConfigPath, "utf8");
1515
1770
  fs2.writeFileSync(akanConfigPath, akanConfig.replace(`buildNum: ${buildNum}`, `buildNum: ${buildNum + delta}`));
1516
1771
  };
1517
1772
  var increaseBuildNum = async (app) => await shiftBuildNum(app, 1);
1518
1773
  var decreaseBuildNum = async (app) => await shiftBuildNum(app, -1);
1519
1774
  // pkgs/@akanjs/devkit/akanConfig/akanWorkspaceConfig.ts
1520
- import path7 from "path";
1775
+ import path8 from "path";
1521
1776
  class AkanWorkspaceConfig {
1522
1777
  static fileName = "akan.config.ts";
1523
1778
  static #keys = ["subspace", "pipeline", "cloud"];
1524
1779
  static #loopbackHosts = ["localhost", "127.0.0.1", "[::1]"];
1525
1780
  static async from(workspace, { bustImportCache = false } = {}) {
1526
- const file = path7.join(workspace.workspaceRoot, AkanWorkspaceConfig.fileName);
1781
+ const file = path8.join(workspace.workspaceRoot, AkanWorkspaceConfig.fileName);
1527
1782
  if (!await FileSys2.fileExists(file))
1528
1783
  return new AkanWorkspaceConfig(null, {});
1529
1784
  const input = await AkanAppConfig.importConfigModule(workspace.workspaceRoot, { bustImportCache });
@@ -1568,13 +1823,13 @@ class AkanWorkspaceConfig {
1568
1823
  }
1569
1824
  }
1570
1825
  // pkgs/@akanjs/devkit/jsonFormatter.ts
1571
- import path8 from "path";
1826
+ import path9 from "path";
1572
1827
 
1573
1828
  class JsonFormatter {
1574
1829
  static lineWidth = 120;
1575
1830
  static stringify(value, filePath) {
1576
1831
  const json = JSON.stringify(value, null, 2);
1577
- if (path8.basename(filePath) === "package.json")
1832
+ if (path9.basename(filePath) === "package.json")
1578
1833
  return `${json}
1579
1834
  `;
1580
1835
  return `${JsonFormatter.#print(JSON.parse(json), 0, { column: 0, trailing: 0 })}
@@ -1644,8 +1899,8 @@ ${" ".repeat(depth)}]`;
1644
1899
 
1645
1900
  // pkgs/@akanjs/devkit/linter.ts
1646
1901
  import { spawn } from "child_process";
1647
- import { existsSync, readFileSync } from "fs";
1648
- import path9 from "path";
1902
+ import { existsSync as existsSync2, readFileSync } from "fs";
1903
+ import path10 from "path";
1649
1904
  import chalk from "chalk";
1650
1905
  var parseBiomeReport = (output) => {
1651
1906
  for (let start = output.indexOf("{");start !== -1; start = output.indexOf("{", start + 1)) {
@@ -1711,14 +1966,14 @@ class Linter {
1711
1966
  #biomeBin;
1712
1967
  constructor(cwdPath) {
1713
1968
  this.lintRoot = this.#findBiomeRootPath(cwdPath);
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);
1716
- this.#biomeBin = existsSync(localBiomeBin) ? localBiomeBin : "biome";
1969
+ this.configPath = Linter.#configPathIn(this.lintRoot) ?? path10.join(this.lintRoot, "biome.json");
1970
+ const localBiomeBin = path10.join(this.lintRoot, "node_modules", ".bin", Linter.biomeBinName);
1971
+ this.#biomeBin = existsSync2(localBiomeBin) ? localBiomeBin : "biome";
1717
1972
  }
1718
1973
  static #configPathIn(dir) {
1719
1974
  for (const fileName of BIOME_CONFIG_FILES) {
1720
- const configPath = path9.join(dir, fileName);
1721
- if (existsSync(configPath))
1975
+ const configPath = path10.join(dir, fileName);
1976
+ if (existsSync2(configPath))
1722
1977
  return configPath;
1723
1978
  }
1724
1979
  return null;
@@ -1726,19 +1981,19 @@ class Linter {
1726
1981
  #findBiomeRootPath(dir) {
1727
1982
  if (Linter.#configPathIn(dir))
1728
1983
  return dir;
1729
- const parentDir = path9.dirname(dir);
1984
+ const parentDir = path10.dirname(dir);
1730
1985
  if (parentDir === dir)
1731
1986
  throw new Error(`biome.json not found from ${dir}`);
1732
1987
  return this.#findBiomeRootPath(parentDir);
1733
1988
  }
1734
1989
  #toBiomePath(filePath) {
1735
- const relativePath = path9.relative(this.lintRoot, filePath);
1736
- if (!relativePath.startsWith("..") && !path9.isAbsolute(relativePath))
1990
+ const relativePath = path10.relative(this.lintRoot, filePath);
1991
+ if (!relativePath.startsWith("..") && !path10.isAbsolute(relativePath))
1737
1992
  return relativePath;
1738
1993
  return filePath;
1739
1994
  }
1740
1995
  #resolveFilePath(filePath) {
1741
- return path9.isAbsolute(filePath) ? filePath : path9.join(this.lintRoot, filePath);
1996
+ return path10.isAbsolute(filePath) ? filePath : path10.join(this.lintRoot, filePath);
1742
1997
  }
1743
1998
  async#runBiome(args, input) {
1744
1999
  return await new Promise((resolve, reject) => {
@@ -1763,7 +2018,7 @@ class Linter {
1763
2018
  const diagnosticPath = diagnostic.location?.path;
1764
2019
  if (!diagnosticPath)
1765
2020
  return fallbackFilePath;
1766
- return path9.isAbsolute(diagnosticPath) ? diagnosticPath : path9.join(this.lintRoot, diagnosticPath);
2021
+ return path10.isAbsolute(diagnosticPath) ? diagnosticPath : path10.join(this.lintRoot, diagnosticPath);
1767
2022
  }
1768
2023
  #createLintMessage(diagnostic) {
1769
2024
  const start = diagnostic.location?.start;
@@ -1806,7 +2061,7 @@ class Linter {
1806
2061
  };
1807
2062
  }
1808
2063
  async#checkFile(filePath, { write = false } = {}) {
1809
- const originalContent = existsSync(filePath) ? readFileSync(filePath, "utf8") : "";
2064
+ const originalContent = existsSync2(filePath) ? readFileSync(filePath, "utf8") : "";
1810
2065
  const { stdout, stderr } = await this.#runBiome([
1811
2066
  "check",
1812
2067
  ...write ? ["--write"] : [],
@@ -1820,7 +2075,7 @@ class Linter {
1820
2075
  const report = parseBiomeReport(stdout || stderr);
1821
2076
  const results = this.#toLintResults(report, filePath);
1822
2077
  const { errors, warnings } = this.#splitMessages(results);
1823
- const output = write && existsSync(filePath) ? readFileSync(filePath, "utf8") : undefined;
2078
+ const output = write && existsSync2(filePath) ? readFileSync(filePath, "utf8") : undefined;
1824
2079
  return {
1825
2080
  fixed: write && output !== originalContent,
1826
2081
  output,
@@ -1836,7 +2091,7 @@ class Linter {
1836
2091
  }
1837
2092
  async lintFile(filePath) {
1838
2093
  const resolvedFilePath = this.#resolveFilePath(filePath);
1839
- if (!existsSync(resolvedFilePath))
2094
+ if (!existsSync2(resolvedFilePath))
1840
2095
  throw new Error(`File not found: ${filePath}`);
1841
2096
  return await this.#checkFile(resolvedFilePath);
1842
2097
  }
@@ -1853,7 +2108,7 @@ class Linter {
1853
2108
  output.push(`
1854
2109
  ${chalk.cyan(result.filePath)}`);
1855
2110
  let sourceLines = [];
1856
- if (existsSync(result.filePath)) {
2111
+ if (existsSync2(result.filePath)) {
1857
2112
  try {
1858
2113
  const sourceContent = readFileSync(result.filePath, "utf8");
1859
2114
  sourceLines = sourceContent.split(`
@@ -1925,7 +2180,7 @@ ${errorText}, ${warningText} found`];
1925
2180
  }
1926
2181
  async fixFile(filePath, dryRun = false) {
1927
2182
  const resolvedFilePath = this.#resolveFilePath(filePath);
1928
- if (!existsSync(resolvedFilePath))
2183
+ if (!existsSync2(resolvedFilePath))
1929
2184
  throw new Error(`File not found: ${filePath}`);
1930
2185
  if (!dryRun)
1931
2186
  return await this.#checkFile(resolvedFilePath, { write: true });
@@ -1935,7 +2190,7 @@ ${errorText}, ${warningText} found`];
1935
2190
  return { ...lintResult, fixed: stdout !== source, output: stdout };
1936
2191
  }
1937
2192
  async fixFiles(filePaths) {
1938
- const resolved = filePaths.map((filePath) => this.#resolveFilePath(filePath)).filter(existsSync);
2193
+ const resolved = filePaths.map((filePath) => this.#resolveFilePath(filePath)).filter(existsSync2);
1939
2194
  if (resolved.length === 0)
1940
2195
  return { fixed: [] };
1941
2196
  const before = new Map(resolved.map((filePath) => [filePath, readFileSync(filePath, "utf8")]));
@@ -1955,7 +2210,7 @@ ${errorText}, ${warningText} found`];
1955
2210
  }
1956
2211
  async getConfigForFile(filePath) {
1957
2212
  const resolvedFilePath = this.#resolveFilePath(filePath);
1958
- if (!existsSync(resolvedFilePath))
2213
+ if (!existsSync2(resolvedFilePath))
1959
2214
  throw new Error(`File not found: ${filePath}`);
1960
2215
  return JSON.parse(readFileSync(this.configPath, "utf8"));
1961
2216
  }
@@ -2099,7 +2354,7 @@ class ManifestDependencies {
2099
2354
 
2100
2355
  // pkgs/@akanjs/devkit/repoIdentity.ts
2101
2356
  import { execFileSync } from "child_process";
2102
- import path10 from "path";
2357
+ import path11 from "path";
2103
2358
  var resolved = new Map;
2104
2359
  var readRemoteName = (workspaceRoot) => {
2105
2360
  try {
@@ -2117,13 +2372,13 @@ var resolveRepoName = (workspaceRoot) => {
2117
2372
  const cached = resolved.get(workspaceRoot);
2118
2373
  if (cached)
2119
2374
  return cached;
2120
- const repoName = readRemoteName(workspaceRoot) ?? path10.basename(workspaceRoot);
2375
+ const repoName = readRemoteName(workspaceRoot) ?? path11.basename(workspaceRoot);
2121
2376
  resolved.set(workspaceRoot, repoName);
2122
2377
  return repoName;
2123
2378
  };
2124
2379
 
2125
2380
  // pkgs/@akanjs/devkit/scanInfo.ts
2126
- import path11 from "path";
2381
+ import path12 from "path";
2127
2382
 
2128
2383
  // pkgs/@akanjs/devkit/workspaceLayout.ts
2129
2384
  var appRootAllowedFiles = new Set([
@@ -2245,9 +2500,9 @@ var moduleUiFileTypes = {
2245
2500
  scalar: new Set(["Template", "Unit"])
2246
2501
  };
2247
2502
  var createDependencyScanner = async (exec) => (await import("./dependencyScanner-06z2dmrr.js")).TypeScriptDependencyScanner.from(exec);
2248
- var getScanPath = (exec, relativePath) => path11.posix.join(`${exec.type}s`, exec.name, relativePath.split(path11.sep).join("/"));
2503
+ var getScanPath = (exec, relativePath) => path12.posix.join(`${exec.type}s`, exec.name, relativePath.split(path12.sep).join("/"));
2249
2504
  var getModuleNameFromPath = (kind, modulePath) => {
2250
- const dirname = path11.basename(modulePath);
2505
+ const dirname = path12.basename(modulePath);
2251
2506
  return kind === "service" ? dirname.replace(/^_+/, "") : dirname;
2252
2507
  };
2253
2508
  async function assertScanConvention(exec, libRoot) {
@@ -2268,24 +2523,24 @@ async function assertScanConvention(exec, libRoot) {
2268
2523
  for (const filename of await exec.readdir(facet)) {
2269
2524
  const shadow = facetIndexShadowOf(`${facet}/${filename}`);
2270
2525
  if (shadow)
2271
- addViolation(path11.join(facet, filename), shadow);
2526
+ addViolation(path12.join(facet, filename), shadow);
2272
2527
  }
2273
2528
  if (exec.type === "lib")
2274
2529
  for (const { relativePath, reason } of await exec.getPageConventionViolations())
2275
2530
  addViolation(relativePath, reason);
2276
2531
  for (const filename of libRoot.files)
2277
2532
  if (!isAllowedLibFacetRootFile(filename))
2278
- addViolation(path11.join("lib", filename), libFacetRootHintOf(filename) ?? "unsupported lib facet root file");
2533
+ addViolation(path12.join("lib", filename), libFacetRootHintOf(filename) ?? "unsupported lib facet root file");
2279
2534
  for (const dirname of libRoot.dirs)
2280
2535
  if (dirname.startsWith("__") && !internalLibDirs.has(dirname))
2281
- addViolation(path11.join("lib", dirname), "unsupported internal lib folder");
2536
+ addViolation(path12.join("lib", dirname), "unsupported internal lib folder");
2282
2537
  const databaseDirs = libRoot.dirs.filter((dirname) => !dirname.startsWith("_"));
2283
2538
  const serviceDirs = libRoot.dirs.filter((dirname) => dirname.startsWith("_") && !dirname.startsWith("__"));
2284
2539
  const scalarDirs = await exec.readdir("lib/__scalar");
2285
2540
  await Promise.all([
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)))
2541
+ ...databaseDirs.map((dirname) => validateModuleFiles(exec, violations, "database", path12.join("lib", dirname))),
2542
+ ...serviceDirs.map((dirname) => validateModuleFiles(exec, violations, "service", path12.join("lib", dirname))),
2543
+ ...scalarDirs.map((dirname) => validateModuleFiles(exec, violations, "scalar", path12.join("lib/__scalar", dirname)))
2289
2544
  ]);
2290
2545
  if (violations.length > 0) {
2291
2546
  throw new Error(`[scan-convention]
@@ -2297,11 +2552,11 @@ async function validateModuleFiles(exec, violations, kind, modulePath) {
2297
2552
  const { files, dirs } = await exec.getFilesAndDirs(modulePath);
2298
2553
  const moduleName = getModuleNameFromPath(kind, modulePath);
2299
2554
  dirs.forEach((dirname) => {
2300
- violations.push(`${getScanPath(exec, path11.join(modulePath, dirname))}: unsupported module folder`);
2555
+ violations.push(`${getScanPath(exec, path12.join(modulePath, dirname))}: unsupported module folder`);
2301
2556
  });
2302
2557
  const uiModuleName = moduleName[0].toUpperCase() + moduleName.slice(1);
2303
2558
  for (const filename of files) {
2304
- const scanPath = getScanPath(exec, path11.join(modulePath, filename));
2559
+ const scanPath = getScanPath(exec, path12.join(modulePath, filename));
2305
2560
  if (filename === "index.ts" || filename === "index.tsx")
2306
2561
  continue;
2307
2562
  if (filename === `${moduleName}.abstract.md`)
@@ -2376,16 +2631,16 @@ class ScanInfo {
2376
2631
  }
2377
2632
  };
2378
2633
  await Promise.all([
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"))
2634
+ ...databaseDirs.map((name) => collect(path12.join("lib", name), name, "databases")),
2635
+ ...serviceDirs.map((dirname) => collect(path12.join("lib", dirname), dirname.slice(1), "services")),
2636
+ ...scalarDirs.map((name) => collect(path12.join("lib/__scalar", name), name, "scalars"))
2382
2637
  ]);
2383
2638
  const routes = exec.type === "lib" ? [] : await exec.getPageKeys();
2384
2639
  const common = {
2385
2640
  name: exec.name,
2386
2641
  type: exec.type,
2387
2642
  repoName: exec.workspace.repoName,
2388
- serveDomain: WorkspaceExecutor.getBaseDevEnv(path11.join(exec.workspace.workspaceRoot, ".env")).serveDomain,
2643
+ serveDomain: WorkspaceExecutor.getBaseDevEnv(path12.join(exec.workspace.workspaceRoot, ".env")).serveDomain,
2389
2644
  files,
2390
2645
  libDeps,
2391
2646
  pkgDeps,
@@ -2709,16 +2964,16 @@ class Executor {
2709
2964
  linter = null;
2710
2965
  constructor(name, cwdPath) {
2711
2966
  this.name = name;
2712
- this.logger = new Logger2(name);
2967
+ this.logger = new Logger3(name);
2713
2968
  this.logs = [];
2714
2969
  this.cwdPath = cwdPath;
2715
2970
  }
2716
2971
  #stdout(data) {
2717
2972
  if (Executor.verbose)
2718
- Logger2.raw(chalk2.dim(data.toString()));
2973
+ Logger3.raw(chalk2.dim(data.toString()));
2719
2974
  }
2720
2975
  #stderr(data) {
2721
- Logger2.raw(chalk2.red(data.toString()));
2976
+ Logger3.raw(chalk2.red(data.toString()));
2722
2977
  }
2723
2978
  #settle(proc, target, { onClose = false, keepLogs = false, redStderr = false } = {}) {
2724
2979
  let stdout = "";
@@ -2767,10 +3022,10 @@ class Executor {
2767
3022
  return this.#settle(proc, target, { redStderr: true }).then(({ code, signal }) => ({ code, signal }));
2768
3023
  }
2769
3024
  getPath(filePath) {
2770
- if (path12.isAbsolute(filePath))
3025
+ if (path13.isAbsolute(filePath))
2771
3026
  return filePath;
2772
3027
  if (filePath.startsWith("."))
2773
- return path12.join(this.cwdPath, filePath);
3028
+ return path13.join(this.cwdPath, filePath);
2774
3029
  const baseParts = this.cwdPath.split(/[\\/]/).filter(Boolean);
2775
3030
  const targetParts = filePath.split(/[\\/]/).filter(Boolean);
2776
3031
  let overlapLength = 0;
@@ -2784,12 +3039,12 @@ class Executor {
2784
3039
  if (isOverlap)
2785
3040
  overlapLength = i;
2786
3041
  }
2787
- return path12.join(this.cwdPath, ...targetParts.slice(overlapLength));
3042
+ return path13.join(this.cwdPath, ...targetParts.slice(overlapLength));
2788
3043
  }
2789
3044
  async mkdir(dirPath) {
2790
3045
  const writePath = this.getPath(dirPath);
2791
3046
  if (!await FileSys2.dirExists(writePath))
2792
- await mkdir2(writePath, { recursive: true });
3047
+ await mkdir3(writePath, { recursive: true });
2793
3048
  this.logger.verbose(`Make directory ${writePath}`);
2794
3049
  return this;
2795
3050
  }
@@ -2825,26 +3080,28 @@ class Executor {
2825
3080
  return this;
2826
3081
  }
2827
3082
  async removeDir(dirPath) {
2828
- const readPath = path12.join(this.getPath(dirPath), ".");
3083
+ const readPath = path13.join(this.getPath(dirPath), ".");
2829
3084
  if (await FileSys2.entryExists(readPath))
2830
- await rm3(readPath, { recursive: true, force: true });
3085
+ await rm4(readPath, { recursive: true, force: true });
2831
3086
  this.logger.verbose(`Remove directory ${readPath}`);
2832
3087
  return this;
2833
3088
  }
2834
3089
  async writeFile(filePath, content, { overwrite = true, silent = false } = {}) {
2835
3090
  const writePath = this.getPath(filePath);
2836
- const dir = path12.dirname(writePath);
3091
+ const dir = path13.dirname(writePath);
2837
3092
  if (!await FileSys2.dirExists(dir))
2838
- await mkdir2(dir, { recursive: true });
3093
+ await mkdir3(dir, { recursive: true });
2839
3094
  let contentStr = typeof content === "string" ? content : JsonFormatter.stringify(content, writePath);
3095
+ let written = true;
2840
3096
  if (await FileSys2.fileExists(writePath)) {
2841
3097
  const currentContent = await FileSys2.readText(writePath);
2842
3098
  if (currentContent === contentStr || !overwrite) {
2843
3099
  this.logger.verbose(`File ${writePath} is unchanged`);
2844
3100
  contentStr = currentContent;
3101
+ written = false;
2845
3102
  } else {
2846
3103
  await FileSys2.writeText(writePath, contentStr);
2847
- if (Logger2.isVerbose())
3104
+ if (Logger3.isVerbose())
2848
3105
  this.logger.rawLog(chalk2.yellow(`File Update: ${filePath}`));
2849
3106
  }
2850
3107
  } else {
@@ -2852,13 +3109,13 @@ class Executor {
2852
3109
  if (!silent)
2853
3110
  this.logger.rawLog(chalk2.green(`File Create: ${filePath}`));
2854
3111
  }
2855
- return { filePath: writePath, content: contentStr };
3112
+ return { filePath: writePath, content: contentStr, written };
2856
3113
  }
2857
3114
  async writeJson(filePath, content) {
2858
3115
  await this.writeFile(filePath, content);
2859
3116
  }
2860
3117
  async getLocalFile(targetPath) {
2861
- const filePath = path12.isAbsolute(targetPath) ? targetPath : targetPath.replace(this.cwdPath, "");
3118
+ const filePath = path13.isAbsolute(targetPath) ? targetPath : targetPath.replace(this.cwdPath, "");
2862
3119
  const content = await this.readFile(filePath);
2863
3120
  return { filePath, content };
2864
3121
  }
@@ -2875,7 +3132,7 @@ class Executor {
2875
3132
  return;
2876
3133
  const isDirectory = (await stat2(src)).isDirectory();
2877
3134
  if (!await FileSys2.exists(dest) && isDirectory)
2878
- await mkdir2(dest, { recursive: true });
3135
+ await mkdir3(dest, { recursive: true });
2879
3136
  if (dereference || process.platform === "win32")
2880
3137
  await cpEntry(src, dest, { recursive: isDirectory, dereference: true, force: true });
2881
3138
  else
@@ -2945,9 +3202,9 @@ class Executor {
2945
3202
  const result = await getContent.default(scanInfo ?? null, dict, options);
2946
3203
  if (result === null)
2947
3204
  return null;
2948
- const filename = typeof result === "object" ? result.filename : path12.basename(targetPath).replace(".js", ".ts");
3205
+ const filename = typeof result === "object" ? result.filename : path13.basename(targetPath).replace(".js", ".ts");
2949
3206
  const content = typeof result === "object" ? result.content : result;
2950
- const convertedTargetPath = convertPath(`${path12.dirname(targetPath)}/${filename}`);
3207
+ const convertedTargetPath = convertPath(`${path13.dirname(targetPath)}/${filename}`);
2951
3208
  this.logger.verbose(`Apply template ${templatePath} to ${convertedTargetPath}`);
2952
3209
  return this.writeFile(convertedTargetPath, content, { overwrite });
2953
3210
  } else if (targetPath.endsWith(".template")) {
@@ -2956,15 +3213,17 @@ class Executor {
2956
3213
  const convertedContent = Object.entries(dict).reduce((data, [key, value]) => data.replace(new RegExp(`<%= ${key} %>`, "g"), value), content);
2957
3214
  this.logger.verbose(`Apply template ${templatePath} to ${convertedTargetPath}`);
2958
3215
  return this.writeFile(convertedTargetPath, convertedContent, { overwrite });
2959
- } else if (staticTemplateFileExtensions.has(path12.extname(targetPath).toLowerCase())) {
3216
+ } else if (staticTemplateFileExtensions.has(path13.extname(targetPath).toLowerCase())) {
2960
3217
  const convertedTargetPath = convertPath(targetPath);
2961
3218
  const writePath = this.getPath(convertedTargetPath);
2962
- const dirname = path12.dirname(writePath);
3219
+ if (!overwrite && await FileSys2.fileExists(writePath))
3220
+ return { filePath: writePath, content: "", written: false };
3221
+ const dirname = path13.dirname(writePath);
2963
3222
  if (!await FileSys2.dirExists(dirname))
2964
- await mkdir2(dirname, { recursive: true });
3223
+ await mkdir3(dirname, { recursive: true });
2965
3224
  await copyFile(templatePath, writePath);
2966
3225
  this.logger.verbose(`Apply template ${templatePath} to ${convertedTargetPath}`);
2967
- return { filePath: writePath, content: "" };
3226
+ return { filePath: writePath, content: "", written: true };
2968
3227
  } else
2969
3228
  return null;
2970
3229
  }
@@ -2981,7 +3240,7 @@ class Executor {
2981
3240
  if ((await stat2(templatePath)).isFile()) {
2982
3241
  const fileContent = await this.#applyTemplateFile({
2983
3242
  templatePath,
2984
- targetPath: path12.join(basePath, path12.basename(templatePath)),
3243
+ targetPath: path13.join(basePath, path13.basename(templatePath)),
2985
3244
  scanInfo,
2986
3245
  overwrite
2987
3246
  }, dict, options);
@@ -2989,19 +3248,19 @@ class Executor {
2989
3248
  } else {
2990
3249
  const subdirs = await readDirEntries(templatePath);
2991
3250
  const fileContents = (await Promise.all(subdirs.map(async (subdir) => {
2992
- const subpath = path12.join(templatePath, subdir);
3251
+ const subpath = path13.join(templatePath, subdir);
2993
3252
  if ((await stat2(subpath)).isFile()) {
2994
3253
  const fileContent = await this.#applyTemplateFile({
2995
3254
  templatePath: subpath,
2996
- targetPath: path12.join(basePath, subdir),
3255
+ targetPath: path13.join(basePath, subdir),
2997
3256
  scanInfo,
2998
3257
  overwrite
2999
3258
  }, dict, options);
3000
3259
  return fileContent ? [fileContent] : [];
3001
3260
  } else
3002
3261
  return await this._applyTemplate({
3003
- basePath: path12.join(basePath, subdir),
3004
- template: path12.join(template, subdir),
3262
+ basePath: path13.join(basePath, subdir),
3263
+ template: path13.join(template, subdir),
3005
3264
  scanInfo,
3006
3265
  dict,
3007
3266
  overwrite,
@@ -3013,8 +3272,8 @@ class Executor {
3013
3272
  }
3014
3273
  async#resolveTemplateRoot() {
3015
3274
  const candidates = [
3016
- path12.resolve(getDirname2(import.meta.url), "templates"),
3017
- path12.resolve(getDirname2(import.meta.url), "../cli/templates")
3275
+ path13.resolve(getDirname2(import.meta.url), "templates"),
3276
+ path13.resolve(getDirname2(import.meta.url), "../cli/templates")
3018
3277
  ];
3019
3278
  for (const candidate of candidates) {
3020
3279
  if (await FileSys2.dirExists(candidate))
@@ -3024,7 +3283,7 @@ class Executor {
3024
3283
  }
3025
3284
  async applyTemplate(options) {
3026
3285
  const fileContents = await this._applyTemplate({ ...options, dict: withCapitalizedDict(options.dict) });
3027
- await this.#formatAppliedTemplate(fileContents);
3286
+ await this.#formatAppliedTemplate(fileContents.filter((fileContent) => fileContent.written));
3028
3287
  return fileContents;
3029
3288
  }
3030
3289
  async#formatAppliedTemplate(fileContents) {
@@ -3036,7 +3295,7 @@ class Executor {
3036
3295
  if (fixed.length > 0)
3037
3296
  this.logger.verbose(`Formatted ${fixed.length} scaffolded file(s)`);
3038
3297
  } catch (err) {
3039
- this.logger.verbose(`Skipped formatting scaffolded files: ${err instanceof Error ? err.message : String(err)}`);
3298
+ this.logger.verbose(`Skipped formatting scaffolded files: ${toError2(err).message}`);
3040
3299
  }
3041
3300
  }
3042
3301
  async getTypeChecker() {
@@ -3053,7 +3312,7 @@ class Executor {
3053
3312
  }
3054
3313
  async typeCheckAsync(filePath) {
3055
3314
  const path = this.getPath(filePath);
3056
- const entry = await this.#resolveTypecheckWorkerEntry();
3315
+ const entry = await ProjectTypecheck2.workerEntry(process.cwd());
3057
3316
  const proc = Bun.spawn([process.execPath, entry], {
3058
3317
  cwd: this.cwdPath,
3059
3318
  env: {
@@ -3079,20 +3338,6 @@ class Executor {
3079
3338
  message: result.message
3080
3339
  };
3081
3340
  }
3082
- async#resolveTypecheckWorkerEntry() {
3083
- const dirname = getDirname2(import.meta.url);
3084
- const candidates = [
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")
3090
- ];
3091
- for (const candidate of candidates)
3092
- if (await Bun.file(candidate).exists())
3093
- return candidate;
3094
- throw new Error(`[devkit] typecheck worker entry not found; looked in: ${candidates.join(", ")}`);
3095
- }
3096
3341
  getLinter() {
3097
3342
  this.linter ??= new Linter(this.cwdPath);
3098
3343
  return this.linter;
@@ -3124,8 +3369,12 @@ class WorkspaceExecutor extends Executor {
3124
3369
  } = {}) {
3125
3370
  return new WorkspaceExecutor({ workspaceRoot, repoName });
3126
3371
  }
3372
+ static publicEnvPin = null;
3127
3373
  static getBaseDevEnv(envPath) {
3128
- const sourceEnv = envPath ? { ...process.env, ...parseEnvFile(envPath) } : process.env;
3374
+ const sourceEnv = {
3375
+ ...envPath ? { ...process.env, ...parseEnvFile(envPath) } : process.env,
3376
+ ...WorkspaceExecutor.publicEnvPin
3377
+ };
3129
3378
  const appName = sourceEnv.AKAN_PUBLIC_APP_NAME;
3130
3379
  const workspaceRoot = sourceEnv.AKAN_WORKSPACE_ROOT;
3131
3380
  const workspaceId = sourceEnv.AKAN_WORKSPACE_ID;
@@ -3213,7 +3462,7 @@ class WorkspaceExecutor extends Executor {
3213
3462
  await Promise.all(dirs.map(async (dir) => {
3214
3463
  if (dir.includes("_") || AVOID_DIRS.includes(dir))
3215
3464
  return;
3216
- const dirPath = path12.join(dirname, dir);
3465
+ const dirPath = path13.join(dirname, dir);
3217
3466
  if ((await stat2(dirPath)).isDirectory()) {
3218
3467
  results.push(`${prefix}${dir}`);
3219
3468
  if (maxDepth > 0)
@@ -3250,9 +3499,9 @@ class WorkspaceExecutor extends Executor {
3250
3499
  await Promise.all(dirs.map(async (dir) => {
3251
3500
  if (AVOID_DIRS.includes(dir))
3252
3501
  return;
3253
- const dirPath = path12.join(dirname, dir);
3502
+ const dirPath = path13.join(dirname, dir);
3254
3503
  if (await FileSys2.dirExists(dirPath)) {
3255
- const hasTargetFile = await FileSys2.fileExists(path12.join(dirPath, targetFilename));
3504
+ const hasTargetFile = await FileSys2.fileExists(path13.join(dirPath, targetFilename));
3256
3505
  if (hasTargetFile)
3257
3506
  results.push(`${prefix}${dir}`);
3258
3507
  if (maxDepth > 0)
@@ -3311,7 +3560,7 @@ class SysExecutor extends Executor {
3311
3560
  return [];
3312
3561
  const violations = [];
3313
3562
  for (const entry of await this.getAllFiles("**/*", { cwd: this.getPath("page") })) {
3314
- const relativePath = entry.split(path12.sep).join("/");
3563
+ const relativePath = entry.split(path13.sep).join("/");
3315
3564
  const reason = getPageSourceFileViolation(relativePath);
3316
3565
  if (reason)
3317
3566
  violations.push({ relativePath: `page/${relativePath}`, reason });
@@ -3406,21 +3655,21 @@ class SysExecutor extends Executor {
3406
3655
  await this.setPackageJson({ ...packageJson, dependencies, devDependencies });
3407
3656
  }
3408
3657
  async getLocalFile(targetPath) {
3409
- const filePath = path12.isAbsolute(targetPath) ? targetPath : `${this.type}s/${this.name}/${targetPath}`;
3658
+ const filePath = path13.isAbsolute(targetPath) ? targetPath : `${this.type}s/${this.name}/${targetPath}`;
3410
3659
  const content = await this.workspace.readFile(filePath);
3411
3660
  return { filePath, content };
3412
3661
  }
3413
3662
  async getDatabaseModules() {
3414
- return (await this.readdir("lib")).filter((name) => !name.startsWith("_") && !name.endsWith(".ts")).filter((name) => existsSync2(`${this.cwdPath}/lib/${name}/${name}.constant.ts`));
3663
+ return (await this.readdir("lib")).filter((name) => !name.startsWith("_") && !name.endsWith(".ts")).filter((name) => existsSync3(`${this.cwdPath}/lib/${name}/${name}.constant.ts`));
3415
3664
  }
3416
3665
  async getServiceModules() {
3417
- return (await this.readdir("lib")).filter((name) => name.startsWith("_") && !name.startsWith("__")).filter((name) => existsSync2(`${this.cwdPath}/lib/${name}/${name.slice(1)}.service.ts`));
3666
+ return (await this.readdir("lib")).filter((name) => name.startsWith("_") && !name.startsWith("__")).filter((name) => existsSync3(`${this.cwdPath}/lib/${name}/${name.slice(1)}.service.ts`));
3418
3667
  }
3419
3668
  async getScalarModules() {
3420
- return (await this.readdir("lib/__scalar")).filter((name) => !name.startsWith("_")).filter((name) => existsSync2(`${this.cwdPath}/lib/__scalar/${name}/${name}.constant.ts`));
3669
+ return (await this.readdir("lib/__scalar")).filter((name) => !name.startsWith("_")).filter((name) => existsSync3(`${this.cwdPath}/lib/__scalar/${name}/${name}.constant.ts`));
3421
3670
  }
3422
3671
  async#getComponentModules(role) {
3423
- return (await this.readdir("lib")).filter((name) => !name.startsWith("_") && !name.endsWith(".ts")).filter((name) => existsSync2(`${this.cwdPath}/lib/${name}/${capitalize(name)}.${role}.tsx`));
3672
+ return (await this.readdir("lib")).filter((name) => !name.startsWith("_") && !name.endsWith(".ts")).filter((name) => existsSync3(`${this.cwdPath}/lib/${name}/${capitalize(name)}.${role}.tsx`));
3424
3673
  }
3425
3674
  async#getComponentSources(role) {
3426
3675
  const modules = await this.#getComponentModules(role);
@@ -3494,19 +3743,19 @@ class AppExecutor extends SysExecutor {
3494
3743
  static from(executor, name) {
3495
3744
  return new AppExecutor({ workspace: executor instanceof WorkspaceExecutor ? executor : executor.workspace, name });
3496
3745
  }
3497
- static #keptOnStart = [path12.join(".akan", "cache", "bin")];
3498
- static #legacyNative = path12.join(".akan", "native");
3746
+ static #keptOnStart = [path13.join(".akan", "cache", "bin")];
3747
+ static #legacyNative = path13.join(".akan", "native");
3499
3748
  async#removeDevOutput(dir = ".akan") {
3500
3749
  const entries = await readDirEntries(this.getPath(dir)).catch(() => []);
3501
3750
  await Promise.all(entries.map(async (name) => {
3502
- const entry = path12.join(dir, name);
3751
+ const entry = path13.join(dir, name);
3503
3752
  if (entry === AppExecutor.#legacyNative) {
3504
3753
  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.`);
3505
3754
  return;
3506
3755
  }
3507
3756
  if (AppExecutor.#keptOnStart.includes(entry))
3508
3757
  return;
3509
- if (AppExecutor.#keptOnStart.some((kept) => kept.startsWith(`${entry}${path12.sep}`)))
3758
+ if (AppExecutor.#keptOnStart.some((kept) => kept.startsWith(`${entry}${path13.sep}`)))
3510
3759
  return await this.#removeDevOutput(entry);
3511
3760
  await this.removeDir(entry);
3512
3761
  }));
@@ -3630,13 +3879,13 @@ class AppExecutor extends SysExecutor {
3630
3879
  absolute: false,
3631
3880
  onlyFiles: true
3632
3881
  })) {
3633
- const segments = rel.split(path12.sep);
3882
+ const segments = rel.split(path13.sep);
3634
3883
  if (segments.some((s) => s === "node_modules"))
3635
3884
  continue;
3636
3885
  const posix = `${root.keyPrefix}${segments.join("/")}`;
3637
3886
  if (!root.keyPrefix && libKeyPrefixes.some((prefix) => posix.startsWith(prefix)))
3638
3887
  continue;
3639
- const absPath = path12.join(root.dir, ...segments);
3888
+ const absPath = path13.join(root.dir, ...segments);
3640
3889
  validatePageSourceFile(posix, { filePath: absPath });
3641
3890
  if (!isRouteSourceFile(posix))
3642
3891
  continue;
@@ -3777,12 +4026,12 @@ class AppExecutor extends SysExecutor {
3777
4026
  static async#linkLibAsset(targetPath, linkPath) {
3778
4027
  const isWindows = process.platform === "win32";
3779
4028
  try {
3780
- const target = isWindows ? targetPath : path12.relative(path12.dirname(linkPath), targetPath);
4029
+ const target = isWindows ? targetPath : path13.relative(path13.dirname(linkPath), targetPath);
3781
4030
  await symlink(target, linkPath, isWindows ? "junction" : "dir");
3782
4031
  } catch (error) {
3783
4032
  if (!isWindows)
3784
4033
  throw error;
3785
- await mkdir2(linkPath, { recursive: true });
4034
+ await mkdir3(linkPath, { recursive: true });
3786
4035
  await cpEntry(targetPath, linkPath, { recursive: true, dereference: true, force: true });
3787
4036
  }
3788
4037
  }
@@ -3820,7 +4069,7 @@ class AppExecutor extends SysExecutor {
3820
4069
  await this.writeFile(rel, content, opts);
3821
4070
  },
3822
4071
  readEnvClient: async () => {
3823
- const envClientPath = path12.join(this.cwdPath, "env", "env.client.ts");
4072
+ const envClientPath = path13.join(this.cwdPath, "env", "env.client.ts");
3824
4073
  if (!await FileSys2.fileExists(envClientPath))
3825
4074
  return null;
3826
4075
  try {
@@ -3914,7 +4163,7 @@ class PkgExecutor extends Executor {
3914
4163
  return overrideVersion;
3915
4164
  try {
3916
4165
  const packageJsonPath = `pkgs/${dep}/package.json`;
3917
- if (!await Bun.file(path12.join(this.workspace.workspaceRoot, packageJsonPath)).exists())
4166
+ if (!await Bun.file(path13.join(this.workspace.workspaceRoot, packageJsonPath)).exists())
3918
4167
  return;
3919
4168
  const packageJson = await this.workspace.readJson(packageJsonPath);
3920
4169
  if (packageJson.name === dep)
@@ -4007,4 +4256,4 @@ class ModuleExecutor extends Executor {
4007
4256
  }
4008
4257
  }
4009
4258
 
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 };
4259
+ 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, ProjectTypecheck2, Executor, WorkspaceExecutor, SysExecutor, AppExecutor, LibExecutor, PkgExecutor, ModuleExecutor };