@akanjs/cli 3.1.0-rc.4 → 3.1.0-rc.6

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 (89) hide show
  1. package/.build-stamp +1 -1
  2. package/{CodeAgent-ymte1tv8.js → CodeAgent-rkjyxgec.js} +2 -2
  3. package/{CodeTui-4rhdm1qw.js → CodeTui-h7pyj6xa.js} +1 -1
  4. package/TunnelShare-661cxz5e.js +14 -0
  5. package/{agent.command-r5x1mch5.js → agent.command-3qxf4xcj.js} +5 -5
  6. package/akanCode.js +2 -2
  7. package/{application.command-3dkr7rdz.js → application.command-m1kcd7jf.js} +12 -11
  8. package/{applicationBuildRunner-h0j2swx1.js → applicationBuildRunner-dj0xkrep.js} +16 -9
  9. package/buildBatch.proc.js +7 -7
  10. package/{cloud.command-4kyxfw0y.js → cloud.command-3jzym8q7.js} +17 -16
  11. package/cloud.script-r4nskbc6.js +24 -0
  12. package/{code.command-sjvzt8fc.js → code.command-0ad6gw2a.js} +6 -6
  13. package/{context.command-j9frz0dz.js → context.command-0qx447mb.js} +17 -16
  14. package/context.runner-1vsjztjg.js +25 -0
  15. package/{dependencyScanner-06z2dmrr.js → dependencyScanner-3afj2vt1.js} +1 -1
  16. package/{guideline.command-1epw6hey.js → guideline.command-cv5bmrx2.js} +2 -2
  17. package/guidelines/conventions/conventions.instruction.md +450 -1084
  18. package/guidelines/cssRule/cssRule.instruction.md +16 -1
  19. package/guidelines/fieldRule/fieldRule.instruction.md +57 -0
  20. package/guidelines/framework/framework.instruction.md +4 -7
  21. package/guidelines/mcpRule/mcpRule.instruction.md +11 -8
  22. package/guidelines/modelConstant/modelConstant.instruction.md +2 -1
  23. package/guidelines/modelStore/modelStore.instruction.md +9 -0
  24. package/guidelines/pageRule/pageRule.instruction.md +80 -0
  25. package/guidelines/queryRule/queryRule.instruction.md +35 -8
  26. package/guidelines/runtimeRule/runtimeRule.instruction.md +89 -2
  27. package/guidelines/ssrRule/ssrRule.instruction.md +13 -0
  28. package/guidelines/transportRule/transportRule.instruction.md +76 -8
  29. package/guidelines/workspaceOnboarding/workspaceOnboarding.instruction.md +4 -4
  30. package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +49 -4
  31. package/incrementalBuilder.proc.js +4 -4
  32. package/{index-jw6st2rd.js → index-192ha4xh.js} +356 -307
  33. package/{index-1hkr7mrb.js → index-1p98exm2.js} +2 -2
  34. package/{index-8byzcvxp.js → index-1xj2t7cb.js} +7 -7
  35. package/{index-er3hekv9.js → index-20bxxyvn.js} +80 -80
  36. package/{index-xe3x2kw5.js → index-35hc95h5.js} +1 -1
  37. package/{index-p5nrc5he.js → index-4mhy6qme.js} +4 -4
  38. package/{index-0qc5nd61.js → index-5edzv8fn.js} +5 -5
  39. package/{index-k9rrten6.js → index-5nd1wtkv.js} +92 -94
  40. package/index-62md4dwy.js +260 -0
  41. package/{index-z3v7abrk.js → index-665zp9rs.js} +38 -15
  42. package/{index-1t2xw0p0.js → index-6xn2n442.js} +4 -4
  43. package/{index-jg3fj8h0.js → index-86jt2a0e.js} +1 -1
  44. package/{index-k33me5sp.js → index-8fp3b47h.js} +419 -418
  45. package/{index-shastb7m.js → index-9z426msb.js} +17 -12
  46. package/{index-gdybjw3x.js → index-awbdfcf2.js} +4 -4
  47. package/{index-3hx1z96c.js → index-bqbf654f.js} +1 -1
  48. package/{index-jppthsff.js → index-cpb3nvma.js} +57 -58
  49. package/{index-b60ccq2s.js → index-d47ypebv.js} +17 -15
  50. package/{index-gc05vqd2.js → index-d72hfmdm.js} +13 -13
  51. package/{index-akha197v.js → index-fmzcs8v6.js} +4 -4
  52. package/{index-28ftaz3d.js → index-fyq1z6kt.js} +29 -27
  53. package/{index-vwmv64ad.js → index-g1mbppa9.js} +238 -236
  54. package/{index-hedt951c.js → index-h47cf1gg.js} +7 -7
  55. package/{index-hfqs9jvc.js → index-j4mkhz93.js} +3 -3
  56. package/{index-08a5ga6n.js → index-jw1zm273.js} +28 -30
  57. package/{index-q958tqqg.js → index-m9jj544v.js} +1 -1
  58. package/{index-8935h3n1.js → index-mdjc93e1.js} +1 -1
  59. package/{index-2wyn17fa.js → index-pydbnfez.js} +2 -2
  60. package/{index-bjh4wbtk.js → index-qwa5k1j8.js} +1 -1
  61. package/{index-81a1ywp6.js → index-tamvyvj3.js} +3 -3
  62. package/{index-dez4t5sd.js → index-trpasv9q.js} +260 -259
  63. package/{index-2ahj896a.js → index-tvehn2sp.js} +1 -1
  64. package/{index-2k0t8p3p.js → index-va06nr10.js} +1086 -1075
  65. package/{index-9c3cd767.js → index-vk0pa0mn.js} +20 -21
  66. package/{index-qyaqzh8e.js → index-w44cy4vs.js} +2216 -2216
  67. package/index.js +22 -22
  68. package/{library.command-n58dgakb.js → library.command-erp2f56n.js} +3 -3
  69. package/{localRegistry.command-f2768sww.js → localRegistry.command-xxpqgxjw.js} +15 -14
  70. package/{module.command-xzjg5k7n.js → module.command-gs2r615e.js} +5 -5
  71. package/{package.command-24sh1j1q.js → package.command-7343f74r.js} +4 -4
  72. package/package.json +2 -2
  73. package/{page.command-fqnm88na.js → page.command-4h4a8pnh.js} +3 -3
  74. package/{pipeline.command-vn6taz75.js → pipeline.command-a2nrk26m.js} +3 -3
  75. package/{primitive.command-4j1g1a0d.js → primitive.command-w6t0asqz.js} +6 -6
  76. package/{projectTypecheck-3y7ayty0.js → projectTypecheck-f534enqj.js} +1 -1
  77. package/{quality.command-5hqmcdwt.js → quality.command-77x7vbbx.js} +4 -4
  78. package/{repair.command-rw5tk460.js → repair.command-y6sstev7.js} +5 -5
  79. package/{scalar.command-0jpap45m.js → scalar.command-20b8cy0k.js} +4 -4
  80. package/{subspace.command-y7n9sghk.js → subspace.command-92xkc0s0.js} +11 -10
  81. package/templates/lib/cnst.ts +0 -1
  82. package/templates/workspaceRoot/.gitignore.template +24 -53
  83. package/{tunnel.command-5gjwh1f8.js → tunnel.command-2kw8vt9g.js} +8 -7
  84. package/{workflow.command-3t22f7eb.js → workflow.command-2djwmpqf.js} +10 -10
  85. package/{workspace.command-cq5xg2jq.js → workspace.command-5se6spqb.js} +28 -27
  86. package/TunnelShare-p8ymkww8.js +0 -13
  87. package/cloud.script-jc3hc5d9.js +0 -23
  88. package/context.runner-v0def5my.js +0 -24
  89. package/index-n68jx79w.js +0 -120
@@ -1,8 +1,11 @@
1
1
  // @bun
2
+ import {
3
+ FileSys2
4
+ } from "./index-52w0fvh8.js";
2
5
  import {
3
6
  SOURCE_EXTS,
4
7
  WatchRootResolver
5
- } from "./index-k9rrten6.js";
8
+ } from "./index-5nd1wtkv.js";
6
9
  import {
7
10
  bundleDefine,
8
11
  CsrEntryFiles,
@@ -16,1359 +19,1019 @@ import {
16
19
  CsrDevPaths,
17
20
  PagesEntrySourceGenerator,
18
21
  PagesBundleBuilder
19
- } from "./index-jw6st2rd.js";
20
- import {
21
- FileSys2
22
- } from "./index-52w0fvh8.js";
22
+ } from "./index-192ha4xh.js";
23
23
  import {
24
24
  AsyncDefaultExportDetector,
25
25
  resolveSsrPageEntriesForApp
26
- } from "./index-2k0t8p3p.js";
26
+ } from "./index-va06nr10.js";
27
27
 
28
- // pkgs/@akanjs/devkit/artifact/routeSeedIndex.ts
28
+ // pkgs/@akanjs/devkit/frontendBuild/autoImportSync.ts
29
+ import { readdir, readFile, stat } from "fs/promises";
29
30
  import path from "path";
30
- import { assertUniqueRoutePatterns, compareRouteSpecificity, parseRouteModuleKey } from "akanjs/common";
31
- import {
32
- ROUTE_SEED_INDEX_JSON
33
- } from "akanjs/server/artifact/routeSeedIndexStore";
34
- function computeRouteSeedIndex(pageEntries) {
35
- const layoutsByPrefix = new Map;
36
- const pagesBySegments = [];
37
- for (const { key, moduleAbsPath, seedAbsPaths } of pageEntries) {
38
- const parsed = parseRouteModuleKey(key);
39
- const files = [path.resolve(moduleAbsPath), ...(seedAbsPaths ?? []).map((seed) => path.resolve(seed))];
40
- if (parsed.kind === "layout" || parsed.kind === "overrides") {
41
- layoutsByPrefix.getOrInsert(parsed.routeSegments.join("/"), []).push(...files);
42
- } else if (parsed.kind === "page") {
43
- pagesBySegments.push({
44
- key,
45
- pattern: parsed.pattern,
46
- segments: parsed.routeSegments,
47
- files,
48
- includeOwnLayout: parsed.leaf === "_index"
49
- });
31
+ import ts from "typescript";
32
+ var TEST_FILE_RE = /\.(test|spec)\.(ts|tsx)$/;
33
+ var AKAN_BASE = {
34
+ names: ["Int", "Float", "ID", "Any", "Upload", "enumOf", "dayjs"],
35
+ specifier: "akanjs/base",
36
+ kind: "named"
37
+ };
38
+ var AKAN_BASE_TYPES = { names: ["Dayjs"], specifier: "akanjs/base", kind: "named", typeOnly: true };
39
+ var CNST_NS = { names: ["cnst"], specifier: "../cnst", kind: "namespace" };
40
+ var DB_NS = { names: ["db"], specifier: "../db", kind: "namespace" };
41
+ var SRV_NS = { names: ["srv"], specifier: "../srv", kind: "namespace" };
42
+ var ERR_DICT = { names: ["Err"], specifier: "../dict", kind: "named" };
43
+ var RULES = {
44
+ constant: [AKAN_BASE, AKAN_BASE_TYPES, { names: ["via"], specifier: "akanjs/constant", kind: "named" }],
45
+ document: [
46
+ AKAN_BASE,
47
+ AKAN_BASE_TYPES,
48
+ CNST_NS,
49
+ DB_NS,
50
+ ERR_DICT,
51
+ {
52
+ names: ["by", "from", "into", "SchemaOf", "DataInputOf", "DocumentUpdateHelper", "documentQueryHelper", "Mdl"],
53
+ specifier: "akanjs/document",
54
+ kind: "named"
50
55
  }
51
- }
52
- assertUniqueRoutePatterns(pagesBySegments);
53
- const globalLayoutFiles = layoutsByPrefix.get("") ?? [];
54
- const seedEntries = [];
55
- for (const { pattern, segments, files, includeOwnLayout } of pagesBySegments) {
56
- const layouts = [];
57
- const maxPrefixLength = includeOwnLayout ? segments.length : Math.max(segments.length - 1, 0);
58
- for (let i = 0;i <= maxPrefixLength; i++) {
59
- const prefix = segments.slice(0, i).join("/");
60
- const layoutFiles = layoutsByPrefix.get(prefix);
61
- if (layoutFiles)
62
- layouts.push(...layoutFiles);
56
+ ],
57
+ service: [
58
+ AKAN_BASE,
59
+ AKAN_BASE_TYPES,
60
+ DB_NS,
61
+ SRV_NS,
62
+ CNST_NS,
63
+ ERR_DICT,
64
+ { names: ["serve"], specifier: "akanjs/service", kind: "named" },
65
+ {
66
+ names: ["DataInputOf", "ListQueryOption", "DatabaseRegistry", "getFilterInfoByKey"],
67
+ specifier: "akanjs/document",
68
+ kind: "named"
63
69
  }
64
- const seeds = Array.from(new Set([...layouts, ...files]));
65
- const routeId = pattern || "/";
66
- seedEntries.push({ routeId, pattern: routeId, seeds });
67
- }
68
- seedEntries.sort((a, b) => compareRouteSpecificity(a.pattern, b.pattern));
69
- return { entries: seedEntries, globalLayoutFiles };
70
- }
71
- function serializeRouteSeedIndexForArtifact(index, artifactDir, options = {}) {
72
- const normalizedArtifactDir = path.resolve(artifactDir);
73
- if (options.production)
74
- return { entries: index.entries.map((entry) => ({ routeId: entry.routeId })) };
75
- return {
76
- entries: index.entries.map((entry) => ({
77
- ...entry,
78
- seeds: entry.seeds.map((seed) => serializeArtifactPath(seed, normalizedArtifactDir))
79
- })),
80
- globalLayoutFiles: index.globalLayoutFiles.map((file) => serializeArtifactPath(file, normalizedArtifactDir))
81
- };
82
- }
83
- async function saveRouteSeedIndex(artifactDir, index, options = {}) {
84
- const absPath = path.join(path.resolve(artifactDir), ROUTE_SEED_INDEX_JSON);
85
- await Bun.write(absPath, `${JSON.stringify(serializeRouteSeedIndexForArtifact(index, artifactDir, options), null, 2)}
86
- `);
87
- return absPath;
88
- }
89
- function serializeArtifactPath(artifactPath, artifactDir) {
90
- if (!path.isAbsolute(artifactPath))
91
- return artifactPath;
92
- return path.relative(artifactDir, artifactPath).split(path.sep).join("/");
93
- }
94
-
95
- // pkgs/@akanjs/devkit/frontendBuild/clientEntryDiscovery.ts
96
- import fs from "fs";
97
- import path2 from "path";
98
- var USE_CLIENT_RE = /^\s*(?:\/\*[\s\S]*?\*\/\s*|\/\/[^\n]*\n\s*)*["']use client["']/;
99
- var NODE_MODULES_RE = /[\\/]node_modules[\\/]/;
100
- var AKANJS_NODE_MODULE_RE = /[\\/]node_modules[\\/]akanjs[\\/]/;
101
- var NON_SOURCE_EXT_RE = /\.(css|scss|sass|less|json|svg|png|jpe?g|webp|gif|avif|ico|woff2?|ttf|otf|mp3|mp4|wav)$/i;
102
- var shouldSkipNodeModule = (absPath) => NODE_MODULES_RE.test(absPath) && !AKANJS_NODE_MODULE_RE.test(absPath);
103
-
104
- class GraphClientEntryDiscovery {
105
- #akanConfig;
106
- #resolvePackage;
107
- #analyzer;
108
- #tsTranspiler = new Bun.Transpiler({ loader: "tsx" });
109
- #fileExistsCache = new Map;
110
- #factsCache = new Map;
111
- #resolvedFileCache = new Map;
112
- #resolvedSpecifierCache = new Map;
113
- #reachableEntriesCache = new Map;
114
- #readFiles = new Set;
115
- #epoch = 0;
116
- constructor(akanConfig, resolvePackage) {
117
- this.#akanConfig = akanConfig;
118
- this.#resolvePackage = resolvePackage;
119
- this.#analyzer = new BarrelAnalyzer({ resolvePackage });
120
- }
121
- static async create(app) {
122
- return new GraphClientEntryDiscovery(await app.getConfig(), await createTsconfigPackageResolver(app));
123
- }
124
- async discover(seeds) {
125
- const entries = new Set;
126
- for (const seed of seeds) {
127
- const walk = { visiting: new Set, partial: new Set, memo: new Map };
128
- for (const entry of await this.#discoverFromFile(seed, walk))
129
- entries.add(entry);
70
+ ],
71
+ signal: [
72
+ AKAN_BASE,
73
+ AKAN_BASE_TYPES,
74
+ SRV_NS,
75
+ CNST_NS,
76
+ ERR_DICT,
77
+ {
78
+ names: ["endpoint", "internal", "slice", "Public", "Req", "Ws", "None", "Res"],
79
+ specifier: "akanjs/signal",
80
+ kind: "named"
130
81
  }
131
- return Array.from(entries).sort();
82
+ ],
83
+ dictionary: [
84
+ {
85
+ names: ["modelDictionary", "scalarDictionary", "serviceDictionary"],
86
+ specifier: "akanjs/dictionary",
87
+ kind: "named"
88
+ }
89
+ ],
90
+ srvkit: [
91
+ AKAN_BASE,
92
+ AKAN_BASE_TYPES,
93
+ { names: ["Logger", "HttpClient", "sleep"], specifier: "akanjs/common", kind: "named" },
94
+ { names: ["adapt"], specifier: "akanjs/service", kind: "named" },
95
+ { names: ["SignalContext", "Guard", "InternalArg", "Middleware"], specifier: "akanjs/signal", kind: "named" }
96
+ ],
97
+ common: [AKAN_BASE, AKAN_BASE_TYPES],
98
+ store: [
99
+ CNST_NS,
100
+ { names: ["store"], specifier: "akanjs/store", kind: "named" },
101
+ { names: ["fetch", "usePage", "sig"], specifier: "../useClient", kind: "named" },
102
+ { names: ["st"], specifier: "../st", kind: "named" },
103
+ { names: ["RootStore"], specifier: "../st", kind: "named", typeOnly: true }
104
+ ],
105
+ client: [
106
+ {
107
+ names: ["cnst", "fetch", "st", "usePage"],
108
+ specifier: (ctx) => `@${ctx.scope}/${ctx.project}/client`,
109
+ kind: "named"
110
+ }
111
+ ]
112
+ };
113
+ var PASCAL_CASE_RE = /^[A-Z][A-Za-z0-9]*$/;
114
+ var DOMAIN_ROLE_KINDS = {
115
+ constant: ["constant"],
116
+ dictionary: ["constant", "document", "signal"],
117
+ common: ["constant"]
118
+ };
119
+ var LIB_BARRELS = {
120
+ Err: { barrel: "dict", kind: "named" },
121
+ db: { barrel: "db", kind: "namespace" },
122
+ cnst: { barrel: "cnst", kind: "namespace" },
123
+ srv: { barrel: "srv", kind: "namespace" }
124
+ };
125
+ var GLOBAL_NAMES = new Set(["fetch"]);
126
+ var DOMAIN_DENYLIST = new Set([
127
+ "Map",
128
+ "Set",
129
+ "WeakMap",
130
+ "WeakSet",
131
+ "Date",
132
+ "Promise",
133
+ "Array",
134
+ "Object",
135
+ "Error",
136
+ "TypeError",
137
+ "RangeError",
138
+ "RegExp",
139
+ "Symbol",
140
+ "Proxy",
141
+ "Reflect",
142
+ "JSON",
143
+ "Math",
144
+ "Number",
145
+ "BigInt",
146
+ "Function",
147
+ "Boolean",
148
+ "String",
149
+ "Record",
150
+ "Partial",
151
+ "Required",
152
+ "Readonly",
153
+ "Pick",
154
+ "Omit",
155
+ "Exclude",
156
+ "Extract",
157
+ "NonNullable",
158
+ "ReturnType",
159
+ "Parameters",
160
+ "Awaited",
161
+ "InstanceType"
162
+ ]);
163
+
164
+ class AutoImportSync {
165
+ #workspaceRoot;
166
+ #domainCache = new Map;
167
+ constructor({ workspaceRoot }) {
168
+ this.#workspaceRoot = path.resolve(workspaceRoot);
132
169
  }
133
- invalidate(files) {
134
- if (files.length === 0)
135
- return;
136
- let moved = false;
170
+ async syncForBatch(files) {
171
+ const changedFiles = [];
172
+ const errors = [];
173
+ const seen = new Set;
137
174
  for (const file of files) {
138
- const absPath = path2.resolve(file);
139
- const exists = fs.existsSync(absPath);
140
- if (exists !== this.#readFiles.has(absPath))
141
- moved = true;
142
- if (!exists)
143
- this.#readFiles.delete(absPath);
144
- this.#factsCache.delete(absPath);
145
- this.#fileExistsCache.delete(absPath);
175
+ const pkgRoot = this.#domainPkgRootOf(file);
176
+ if (pkgRoot)
177
+ this.#domainCache.delete(pkgRoot);
146
178
  }
147
- this.#epoch += 1;
148
- this.#reachableEntriesCache.clear();
149
- if (!moved)
150
- return;
151
- this.#fileExistsCache.clear();
152
- this.#resolvedFileCache.clear();
153
- this.#resolvedSpecifierCache.clear();
154
- }
155
- #lookup(cache, key, run, isFound) {
156
- const cached = cache.get(key);
157
- if (cached && (cached.found || cached.epoch === this.#epoch))
158
- return cached.promise;
159
- const entry = { epoch: this.#epoch, promise: run() };
160
- entry.promise.then((value) => {
161
- entry.found = isFound(value);
162
- }, () => {
163
- entry.found = false;
164
- });
165
- cache.set(key, entry);
166
- return entry.promise;
167
- }
168
- async#fileExists(p) {
169
- const absPath = path2.resolve(p);
170
- return await this.#lookup(this.#fileExistsCache, absPath, () => Bun.file(absPath).exists(), (exists) => exists);
171
- }
172
- #facts(file) {
173
- const absPath = path2.resolve(file);
174
- let cached = this.#factsCache.get(absPath);
175
- if (!cached) {
176
- cached = (async () => {
177
- const content = await Bun.file(absPath).text().catch(() => null);
178
- if (content === null)
179
- return null;
180
- this.#readFiles.add(absPath);
181
- if (USE_CLIENT_RE.test(content))
182
- return { isClientEntry: true, imports: [] };
183
- return { isClientEntry: false, imports: this.#scanImports(await this.#rewrite(content)) };
184
- })();
185
- this.#factsCache.set(absPath, cached);
179
+ for (const file of files) {
180
+ const abs = path.resolve(file);
181
+ if (seen.has(abs))
182
+ continue;
183
+ seen.add(abs);
184
+ const ctx = this.#contextFor(abs);
185
+ if (!ctx)
186
+ continue;
187
+ try {
188
+ const changed = await this.#syncFile(abs, ctx);
189
+ if (changed)
190
+ changedFiles.push(abs);
191
+ } catch (err) {
192
+ errors.push(`[auto-import] sync failed for ${file}: ${formatError(err)}`);
193
+ }
186
194
  }
187
- return cached;
195
+ return { changedFiles, errors };
188
196
  }
189
- async#rewrite(content) {
190
- if (this.#akanConfig.barrelImports.length === 0)
191
- return content;
192
- try {
193
- return await rewriteBarrelImports(content, this.#akanConfig.barrelImports, this.#analyzer) ?? content;
194
- } catch {
195
- return content;
196
- }
197
+ #contextFor(abs) {
198
+ const rel = path.relative(this.#workspaceRoot, abs);
199
+ if (rel.startsWith("..") || path.isAbsolute(rel))
200
+ return null;
201
+ const base = path.basename(abs);
202
+ if (TEST_FILE_RE.test(base) || base === "index.ts" || base === "index.tsx" || base.endsWith(".d.ts"))
203
+ return null;
204
+ const parts = rel.split(path.sep).filter(Boolean);
205
+ const [scope, project, facet] = parts;
206
+ if (scope !== "apps" && scope !== "libs" || !project || !facet)
207
+ return null;
208
+ if (facet === "lib" && parts[3] === "__lib")
209
+ return null;
210
+ const role = roleFor(facet, base);
211
+ if (!role)
212
+ return null;
213
+ return { role, scope, project };
197
214
  }
198
- #scanImports(source) {
199
- try {
200
- return this.#tsTranspiler.scanImports(source);
201
- } catch {
202
- return [];
203
- }
215
+ async#syncFile(abs, ctx) {
216
+ const stats = await stat(abs).catch(() => null);
217
+ if (!stats?.isFile())
218
+ return false;
219
+ const source = await readFile(abs, "utf8");
220
+ const resolveExtra = await this.#extraResolverFor(abs, ctx);
221
+ const next = transformSource(source, abs, ctx, resolveExtra);
222
+ if (next === null || next === source)
223
+ return false;
224
+ await FileSys2.writeTextAtomic(abs, next);
225
+ return true;
204
226
  }
205
- async#resolveFileCandidate(absPathNoExt) {
206
- const cacheKey = path2.resolve(absPathNoExt);
207
- return await this.#lookup(this.#resolvedFileCache, cacheKey, async () => {
208
- if (await this.#fileExists(cacheKey))
209
- return cacheKey;
210
- for (const ext of SOURCE_EXTS) {
211
- const f = `${cacheKey}${ext}`;
212
- if (await this.#fileExists(f))
213
- return f;
214
- }
215
- for (const ext of SOURCE_EXTS) {
216
- const f = path2.join(cacheKey, `index${ext}`);
217
- if (await this.#fileExists(f))
218
- return f;
227
+ async#extraResolverFor(abs, ctx) {
228
+ const resolvers = [];
229
+ if (ctx.role === "srvkit" || ctx.role === "common")
230
+ resolvers.push(await this.#libBarrelResolver(abs, ctx));
231
+ const kinds = DOMAIN_ROLE_KINDS[ctx.role];
232
+ if (kinds)
233
+ resolvers.push(await this.#domainResolver(abs, ctx, kinds));
234
+ if (resolvers.length === 0)
235
+ return;
236
+ return (symbol) => {
237
+ for (const resolve of resolvers) {
238
+ const target = resolve(symbol);
239
+ if (target)
240
+ return target;
219
241
  }
220
242
  return null;
221
- }, (file) => file !== null);
243
+ };
222
244
  }
223
- async#resolveSpecifier(spec, importerDir) {
224
- const cacheKey = `${importerDir}\x00${spec}`;
225
- return await this.#lookup(this.#resolvedSpecifierCache, cacheKey, async () => {
226
- if (spec.startsWith(".") || spec.startsWith("/")) {
227
- const abs = spec.startsWith("/") ? spec : path2.resolve(importerDir, spec);
228
- return await this.#resolveFileCandidate(abs);
229
- }
230
- const pkg = await this.#resolvePackage(spec);
231
- return pkg ? pkg.entryFile : null;
232
- }, (file) => file !== null);
245
+ async#domainResolver(abs, ctx, kinds) {
246
+ const index = await this.#domainIndex(path.join(this.#workspaceRoot, ctx.scope, ctx.project));
247
+ const fileDir = path.dirname(abs);
248
+ return (symbol) => {
249
+ if (!PASCAL_CASE_RE.test(symbol) || DOMAIN_DENYLIST.has(symbol))
250
+ return null;
251
+ const entries = (index.get(symbol) ?? []).filter((entry) => kinds.includes(entry.kind));
252
+ if (entries.length !== 1)
253
+ return null;
254
+ return { specifier: relativeSpecifier(fileDir, entries[0].file), kind: "named" };
255
+ };
233
256
  }
234
- async#discoverFromFile(file, walk) {
235
- const epoch = this.#epoch;
236
- const absPath = path2.resolve(file);
237
- const cached = this.#reachableEntriesCache.get(absPath);
238
- if (cached)
239
- return new Set(cached);
240
- const memo = walk.memo.get(absPath);
241
- if (memo) {
242
- if (walk.partial.has(absPath))
243
- for (const onPath of walk.visiting)
244
- walk.partial.add(onPath);
245
- return new Set(memo);
246
- }
247
- if (walk.visiting.has(absPath)) {
248
- let below = false;
249
- for (const onPath of walk.visiting) {
250
- if (below)
251
- walk.partial.add(onPath);
252
- else
253
- below = onPath === absPath;
254
- }
255
- return new Set;
256
- }
257
- if (shouldSkipNodeModule(absPath))
258
- return new Set;
259
- walk.visiting.add(absPath);
260
- const entries = new Set;
261
- const facts = await this.#facts(absPath);
262
- if (!facts)
263
- return this.#finishDiscovery(absPath, walk, entries, epoch);
264
- if (facts.isClientEntry) {
265
- entries.add(absPath);
266
- return this.#finishDiscovery(absPath, walk, entries, epoch);
267
- }
268
- const importerDir = path2.dirname(absPath);
269
- for (const imp of facts.imports) {
270
- const spec = imp.path;
271
- if (!spec)
272
- continue;
273
- if (NON_SOURCE_EXT_RE.test(spec))
274
- continue;
275
- const resolved = await this.#resolveSpecifier(spec, importerDir);
276
- if (!resolved)
277
- continue;
278
- if (shouldSkipNodeModule(resolved))
279
- continue;
280
- for (const entry of await this.#discoverFromFile(resolved, walk))
281
- entries.add(entry);
257
+ async#libBarrelResolver(abs, ctx) {
258
+ const fileDir = path.dirname(abs);
259
+ const libDir = path.join(this.#workspaceRoot, ctx.scope, ctx.project, "lib");
260
+ const available = new Map;
261
+ for (const [symbol, { barrel, kind }] of Object.entries(LIB_BARRELS)) {
262
+ if (await fileExists(path.join(libDir, `${barrel}.ts`)))
263
+ available.set(symbol, { specifier: relativeSpecifier(fileDir, path.join(libDir, barrel)), kind });
282
264
  }
283
- return this.#finishDiscovery(absPath, walk, entries, epoch);
265
+ return (symbol) => available.get(symbol) ?? null;
284
266
  }
285
- #finishDiscovery(absPath, walk, entries, epoch) {
286
- walk.visiting.delete(absPath);
287
- walk.memo.set(absPath, entries);
288
- if (epoch === this.#epoch && !walk.partial.has(absPath))
289
- this.#reachableEntriesCache.set(absPath, entries);
290
- return new Set(entries);
267
+ async#domainIndex(pkgRoot) {
268
+ const cached = this.#domainCache.get(pkgRoot);
269
+ if (cached)
270
+ return cached;
271
+ const index = await buildDomainIndex(path.join(pkgRoot, "lib"));
272
+ this.#domainCache.set(pkgRoot, index);
273
+ return index;
274
+ }
275
+ #domainPkgRootOf(file) {
276
+ const rel = path.relative(this.#workspaceRoot, path.resolve(file));
277
+ if (rel.startsWith("..") || path.isAbsolute(rel))
278
+ return null;
279
+ const [scope, project, facet] = rel.split(path.sep).filter(Boolean);
280
+ if (scope !== "apps" && scope !== "libs" || !project || facet !== "lib")
281
+ return null;
282
+ if (!domainKindOf(path.basename(file)))
283
+ return null;
284
+ return path.join(this.#workspaceRoot, scope, project);
291
285
  }
292
286
  }
293
-
294
- // pkgs/@akanjs/devkit/frontendBuild/routeClientBuilder.ts
295
- import { mkdir } from "fs/promises";
296
- import path4 from "path";
297
- import { SSR_DEV_CHUNK, SSR_DEV_ID_PREFIX } from "akanjs/server/hmr/csrDevManifest";
298
-
299
- // pkgs/@akanjs/devkit/frontendBuild/clientEntriesBundler.ts
300
- import fs2 from "fs";
301
- import path3 from "path";
302
-
303
- // pkgs/@akanjs/devkit/frontendBuild/clientBuildTypes.ts
304
- var CLIENT_BUNDLE_NAMING = {
305
- entry: "[name]-[hash].[ext]",
306
- chunk: "chunks/[hash].[ext]",
307
- asset: "assets/[hash].[ext]"
287
+ var roleFor = (facet, base) => {
288
+ if (facet === "lib") {
289
+ if (base.endsWith(".constant.ts"))
290
+ return "constant";
291
+ if (base.endsWith(".document.ts"))
292
+ return "document";
293
+ if (base.endsWith(".service.ts"))
294
+ return "service";
295
+ if (base.endsWith(".signal.ts"))
296
+ return "signal";
297
+ if (base.endsWith(".dictionary.ts"))
298
+ return "dictionary";
299
+ if (base.endsWith(".store.ts"))
300
+ return "store";
301
+ if (base.endsWith(".tsx"))
302
+ return "client";
303
+ return null;
304
+ }
305
+ if (facet === "ui" || facet === "page")
306
+ return base.endsWith(".tsx") ? "client" : null;
307
+ if (facet === "webkit")
308
+ return base.endsWith(".ts") || base.endsWith(".tsx") ? "client" : null;
309
+ if (facet === "srvkit")
310
+ return base.endsWith(".ts") ? "srvkit" : null;
311
+ if (facet === "common")
312
+ return base.endsWith(".ts") || base.endsWith(".tsx") ? "common" : null;
313
+ return null;
308
314
  };
309
-
310
- // pkgs/@akanjs/devkit/frontendBuild/clientEntriesBundler.ts
311
- class ClientEntriesBundler {
312
- #app;
313
- #entries;
314
- #plugins;
315
- #external;
316
- #externalSubpaths;
317
- #externalAliases;
318
- #command;
319
- #target;
320
- #outputSubdir;
321
- #reactFastRefresh;
322
- #outdir;
323
- #servePrefix;
324
- #manifest = {};
325
- #ssrManifest = { moduleLoading: null, moduleMap: {} };
326
- #entryUrlsByAbsPath = new Map;
327
- #entryOutputAbsByAbsPath = new Map;
328
- #entryDepsByAbsPath = new Map;
329
- #clientReferenceIdByAbsPath = new Map;
330
- #opaqueEntries = null;
331
- #metafileOutputsByAbs = new Map;
332
- constructor(options) {
333
- this.#app = options.app;
334
- this.#entries = options.entries;
335
- this.#plugins = options.plugins ?? [];
336
- this.#external = options.external ?? [];
337
- this.#externalSubpaths = options.externalSubpaths ?? [];
338
- this.#externalAliases = options.externalAliases ?? {};
339
- this.#command = options.command ?? "start";
340
- this.#target = options.target ?? "browser";
341
- this.#outputSubdir = options.outputSubdir ?? "client";
342
- this.#reactFastRefresh = options.reactFastRefresh ?? false;
343
- const artifactDir = `${this.#command === "build" ? this.#app.dist.cwdPath : this.#app.cwdPath}/.akan/artifact`;
344
- this.#outdir = `${artifactDir}/${this.#outputSubdir}`;
345
- this.#servePrefix = `/_akan/${this.#outputSubdir}`;
315
+ var targetFor = (symbol, ctx) => {
316
+ for (const rule of RULES[ctx.role]) {
317
+ if (!rule.names.includes(symbol))
318
+ continue;
319
+ const specifier = typeof rule.specifier === "function" ? rule.specifier(ctx) : rule.specifier;
320
+ return { specifier, kind: rule.kind, typeOnly: rule.typeOnly };
346
321
  }
347
- async bundle() {
348
- const akanConfig = await this.#app.getConfig();
349
- this.#opaqueEntries = await this.#createOpaqueEntryAliases();
350
- const result = await Bun.build({
351
- entrypoints: this.#opaqueEntries.entries,
352
- outdir: this.#outdir,
353
- splitting: true,
354
- target: this.#target,
355
- format: "esm",
356
- naming: CLIENT_BUNDLE_NAMING,
357
- metafile: true,
358
- define: bundleDefine(this.#app, this.#command, "ssr"),
359
- minify: this.#command === "build",
360
- optimizeImports: akanConfig.optimizeImports,
361
- reactFastRefresh: this.#command === "start" && this.#outputSubdir === "client" && this.#reactFastRefresh,
362
- plugins: this.#external.length > 0 || this.#externalSubpaths.length > 0 || Object.keys(this.#externalAliases).length > 0 ? [...this.#plugins, this.#createExternalSpecifiersPlugin()] : this.#plugins
363
- });
364
- if (!result.success)
365
- throw new AggregateError(result.logs, "[ClientEntriesBundler] Bun.build failed");
366
- await this.#rewriteExternalImportSpecifiers(result.outputs);
367
- const metafile = result.metafile;
368
- if (!metafile)
369
- throw new Error("[ClientEntriesBundler] metafile is missing");
370
- this.#metafileOutputsByAbs = new Map(Object.entries(metafile.outputs).map(([k, v]) => [this.#absFromOutdir(k), v]));
371
- for (const artifact of result.outputs.filter((a) => a.kind === "entry-point"))
372
- this.#addEntryArtifact(artifact.path);
373
- return {
374
- manifest: this.#manifest,
375
- ssrManifest: this.#ssrManifest,
376
- entryUrlsByAbsPath: this.#entryUrlsByAbsPath,
377
- entryOutputAbsByAbsPath: this.#entryOutputAbsByAbsPath,
378
- entryDepsByAbsPath: this.#entryDepsByAbsPath,
379
- clientReferenceIdByAbsPath: this.#clientReferenceIdByAbsPath
380
- };
322
+ return null;
323
+ };
324
+ var transformSource = (source, fileName, ctx, resolveExtra) => {
325
+ const sf = ts.createSourceFile(fileName, source, ts.ScriptTarget.Latest, true, scriptKindFor(fileName));
326
+ const bound = collectBoundNames(sf);
327
+ const used = collectUsedReferences(sf);
328
+ const bareGlobals = collectBareGlobalReferences(sf);
329
+ const namedBySpecifier = new Map;
330
+ const namespaceImports = [];
331
+ for (const name of used) {
332
+ if (bound.has(name) || bareGlobals.has(name))
333
+ continue;
334
+ const target = targetFor(name, ctx) ?? resolveExtra?.(name) ?? null;
335
+ if (!target)
336
+ continue;
337
+ if (target.kind === "namespace")
338
+ namespaceImports.push({ name, specifier: target.specifier });
339
+ else {
340
+ const names = namedBySpecifier.get(target.specifier) ?? new Map;
341
+ names.set(name, target.typeOnly ?? false);
342
+ namedBySpecifier.set(target.specifier, names);
343
+ }
381
344
  }
382
- async#createOpaqueEntryAliases() {
383
- const aliasDir = path3.join(this.#app.cwdPath, ".akan", "generated", "client-entry-alias", this.#outputSubdir);
384
- fs2.mkdirSync(aliasDir, { recursive: true });
385
- const originalByAlias = new Map;
386
- const aliasedEntries = await Promise.all(this.#entries.map(async (entry) => {
387
- const absEntry = path3.resolve(entry);
388
- const hash = Bun.hash(`${this.#app.name}
389
- ${this.#outputSubdir}
390
- ${absEntry}`).toString(36);
391
- const aliasPath = path3.join(aliasDir, `${hash}.tsx`);
392
- await Bun.write(aliasPath, this.#createOpaqueEntryAliasSource(absEntry, await this.#scanEntryExportNames(absEntry)));
393
- originalByAlias.set(path3.resolve(aliasPath), absEntry);
394
- return aliasPath;
395
- }));
396
- return { entries: aliasedEntries, originalByAlias, aliasDir };
345
+ if (namedBySpecifier.size === 0 && namespaceImports.length === 0)
346
+ return null;
347
+ const importDecls = sf.statements.filter(ts.isImportDeclaration);
348
+ const anchor = importDecls.at(-1) ?? null;
349
+ const namedImportsBySpecifier = collectExistingNamedImports(importDecls);
350
+ const edits = [];
351
+ const newStatements = [];
352
+ for (const [specifier, names] of namedBySpecifier) {
353
+ const existing = namedImportsBySpecifier.get(specifier);
354
+ if (existing) {
355
+ const merged = new Map(existing.names);
356
+ for (const [name, isType] of names)
357
+ if (!merged.has(name))
358
+ merged.set(name, isType);
359
+ edits.push({
360
+ start: existing.decl.getStart(sf),
361
+ end: existing.decl.getEnd(),
362
+ text: formatNamedImport(specifier, merged)
363
+ });
364
+ } else
365
+ newStatements.push(formatNamedImport(specifier, names));
397
366
  }
398
- #createOpaqueEntryAliasSource(absEntry, exportNames) {
399
- const entryLit = JSON.stringify(path3.resolve(absEntry));
400
- if (exportNames.length === 0)
401
- return `export * from ${entryLit};
402
- `;
403
- const namedExports = exportNames.filter((name) => name !== "default");
404
- const lines = namedExports.length > 0 ? [`export { ${namedExports.join(", ")} } from ${entryLit};`] : [];
405
- if (exportNames.includes("default"))
406
- lines.push(`export { default } from ${entryLit};`);
407
- return `${lines.join(`
367
+ for (const ns of namespaceImports)
368
+ newStatements.push(`import * as ${ns.name} from "${ns.specifier}";`);
369
+ if (newStatements.length > 0) {
370
+ const anchorEdit = anchor ? edits.find((edit) => edit.start === anchor.getStart(sf)) : undefined;
371
+ if (anchorEdit)
372
+ anchorEdit.text = `${anchorEdit.text}
373
+ ${newStatements.join(`
374
+ `)}`;
375
+ else if (anchor)
376
+ edits.push({ start: anchor.getEnd(), end: anchor.getEnd(), text: `
377
+ ${newStatements.join(`
378
+ `)}` });
379
+ else {
380
+ const prologueEnd = directivePrologueEnd(sf);
381
+ if (prologueEnd >= 0)
382
+ edits.push({ start: prologueEnd, end: prologueEnd, text: `
383
+ ${newStatements.join(`
384
+ `)}` });
385
+ else
386
+ edits.push({ start: 0, end: 0, text: `${newStatements.join(`
408
387
  `)}
409
- `;
410
- }
411
- async#scanEntryExportNames(absEntry) {
412
- return scanUseClientExports(await Bun.file(absEntry).text(), absEntry, this.#app.workspace.workspaceRoot);
413
- }
414
- #createExternalSpecifiersPlugin() {
415
- const set = new Set(this.#external);
416
- const subpathSet = new Set(this.#externalSubpaths);
417
- const aliases = this.#externalAliases;
418
- const specifiers = [...new Set([...this.#external, ...this.#externalSubpaths, ...Object.keys(aliases)])];
419
- const escaped = specifiers.map((s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
420
- const filter = new RegExp(`^(${escaped.join("|")})(?:/.*)?$`);
421
- return {
422
- name: "akan-externalize-specifiers",
423
- setup(build) {
424
- build.onResolve({ filter }, (args) => {
425
- const alias = aliases[args.path];
426
- if (alias)
427
- return { path: alias, external: true };
428
- if (!ClientEntriesBundler.#matchesExternalSpecifier(args.path, set, subpathSet))
429
- return;
430
- return { path: args.path, external: true };
431
- });
432
- }
433
- };
434
- }
435
- static #matchesExternalSpecifier(specifier, exactExternals, subpathExternals) {
436
- if (exactExternals.has(specifier) || subpathExternals.has(specifier))
437
- return true;
438
- for (const external of subpathExternals)
439
- if (specifier.startsWith(`${external}/`))
440
- return true;
441
- return false;
442
- }
443
- async#rewriteExternalImportSpecifiers(outputs) {
444
- if (Object.keys(this.#externalAliases).length === 0)
445
- return;
446
- await Promise.all(outputs.filter((output) => output.path.endsWith(".js")).map(async (output) => {
447
- const source = await Bun.file(output.path).text();
448
- const rewritten = ClientEntriesBundler.rewriteExternalImportSpecifiers(source, this.#externalAliases);
449
- if (rewritten !== source)
450
- await Bun.write(output.path, rewritten);
451
- }));
452
- }
453
- static rewriteExternalImportSpecifiers(source, aliases) {
454
- let rewritten = source;
455
- for (const [specifier, alias] of Object.entries(aliases)) {
456
- if (!alias)
457
- continue;
458
- const escaped = specifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
459
- rewritten = rewritten.replace(new RegExp(`(\\bfrom\\s*["'])${escaped}(["'])`, "g"), (_match, prefix, suffix) => {
460
- return `${prefix}${alias}${suffix}`;
461
- }).replace(new RegExp(`(\\bimport\\s*["'])${escaped}(["'])`, "g"), (_match, prefix, suffix) => {
462
- return `${prefix}${alias}${suffix}`;
463
- });
388
+
389
+ ` });
464
390
  }
465
- return rewritten;
466
- }
467
- #toServeUrl(absOutPath) {
468
- const rel = path3.relative(this.#outdir, absOutPath).split(path3.sep).join("/");
469
- return `${this.#servePrefix}/${rel}`;
470
- }
471
- #absFromOutdir(p) {
472
- return path3.isAbsolute(p) ? p : path3.resolve(this.#outdir, p);
473
- }
474
- #absFromEntryPoint(p) {
475
- if (path3.isAbsolute(p))
476
- return path3.resolve(p);
477
- const candidates = [path3.resolve(process.cwd(), p), path3.resolve(this.#app.cwdPath, p)];
478
- return candidates.find((candidate) => fs2.existsSync(candidate)) ?? candidates[0];
479
391
  }
480
- #collectChunkUrls(absOutPath, visited = new Set) {
481
- if (visited.has(absOutPath))
482
- return [];
483
- visited.add(absOutPath);
484
- const info = this.#metafileOutputsByAbs.get(absOutPath);
485
- if (!info)
486
- return [this.#toServeUrl(absOutPath)];
487
- const urls = [this.#toServeUrl(absOutPath)];
488
- for (const imp of info.imports) {
489
- if (imp.kind === "dynamic-import")
490
- continue;
491
- const absImp = this.#absFromOutdir(imp.path);
492
- if (!absImp.endsWith(".js"))
493
- continue;
494
- urls.push(...this.#collectChunkUrls(absImp, visited));
392
+ if (edits.length === 0)
393
+ return null;
394
+ edits.sort((a, b) => b.start - a.start);
395
+ let out = source;
396
+ for (const edit of edits)
397
+ out = out.slice(0, edit.start) + edit.text + out.slice(edit.end);
398
+ return out === source ? null : out;
399
+ };
400
+ var collectBoundNames = (sf) => {
401
+ const names = new Set;
402
+ const visit = (node) => {
403
+ if (ts.isImportClause(node)) {
404
+ if (node.name)
405
+ names.add(node.name.text);
406
+ const bindings = node.namedBindings;
407
+ if (bindings && ts.isNamespaceImport(bindings))
408
+ names.add(bindings.name.text);
409
+ if (bindings && ts.isNamedImports(bindings))
410
+ for (const el of bindings.elements)
411
+ names.add(el.name.text);
495
412
  }
496
- return urls;
497
- }
498
- #collectClientDeps(absOutPath, visited = new Set) {
499
- if (visited.has(absOutPath))
500
- return new Set;
501
- visited.add(absOutPath);
502
- const deps = new Set;
503
- const info = this.#metafileOutputsByAbs.get(absOutPath);
504
- if (!info)
505
- return deps;
506
- if (!this.#opaqueEntries)
507
- throw new Error("[ClientEntriesBundler] opaque entries are missing");
508
- for (const input of Object.keys(info.inputs ?? {})) {
509
- const absInput = this.#absFromEntryPoint(input);
510
- if (!absInput.includes("node_modules") && !absInput.startsWith(this.#opaqueEntries.aliasDir))
511
- deps.add(absInput);
512
- }
513
- for (const imp of info.imports) {
514
- if (imp.kind === "dynamic-import")
515
- continue;
516
- const absImp = this.#absFromOutdir(imp.path);
517
- if (!absImp.endsWith(".js"))
518
- continue;
519
- for (const dep of this.#collectClientDeps(absImp, visited))
520
- deps.add(dep);
521
- }
522
- return deps;
523
- }
524
- #addEntryArtifact(absOut) {
525
- if (!this.#opaqueEntries)
526
- throw new Error("[ClientEntriesBundler] opaque entries are missing");
527
- const info = this.#metafileOutputsByAbs.get(absOut);
528
- const buildEntryPointAbs = info?.entryPoint ? this.#absFromEntryPoint(info.entryPoint) : null;
529
- const entryPointAbs = buildEntryPointAbs ? this.#opaqueEntries.originalByAlias.get(buildEntryPointAbs) ?? buildEntryPointAbs : null;
530
- if (!entryPointAbs)
531
- return;
532
- const chunkUrls = this.#collectChunkUrls(absOut);
533
- const entryUrl = chunkUrls[0] ?? this.#toServeUrl(absOut);
534
- this.#entryUrlsByAbsPath.set(entryPointAbs, entryUrl);
535
- this.#entryOutputAbsByAbsPath.set(entryPointAbs, absOut);
536
- this.#entryDepsByAbsPath.set(entryPointAbs, [...this.#collectClientDeps(absOut)].sort());
537
- const clientReferenceId = toClientReferencePath(entryPointAbs, this.#app.workspace.workspaceRoot);
538
- this.#clientReferenceIdByAbsPath.set(entryPointAbs, clientReferenceId);
539
- const flatChunks = [];
540
- for (const url of chunkUrls)
541
- flatChunks.push(url, url);
542
- const exportNames = info?.exports && info.exports.length > 0 ? info.exports : ["default"];
543
- const ssrEntriesByName = {};
544
- for (const name of exportNames) {
545
- const key = `${clientReferenceId}#${name}`;
546
- this.#manifest[key] = { id: entryUrl, chunks: flatChunks, name, async: true };
547
- ssrEntriesByName[name] = { id: absOut, chunks: [absOut, absOut], name, async: true };
413
+ if ((ts.isVariableDeclaration(node) || ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node) || ts.isParameter(node) || ts.isBindingElement(node) || ts.isEnumDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isInterfaceDeclaration(node)) && node.name && ts.isIdentifier(node.name))
414
+ names.add(node.name.text);
415
+ ts.forEachChild(node, visit);
416
+ };
417
+ ts.forEachChild(sf, visit);
418
+ return names;
419
+ };
420
+ var collectUsedReferences = (sf) => {
421
+ const used = new Set;
422
+ const visit = (node) => {
423
+ if (ts.isIdentifier(node) && isReferencePosition(node))
424
+ used.add(node.text);
425
+ ts.forEachChild(node, visit);
426
+ };
427
+ ts.forEachChild(sf, visit);
428
+ return used;
429
+ };
430
+ var collectBareGlobalReferences = (sf) => {
431
+ const bare = new Set;
432
+ const visit = (node) => {
433
+ if (ts.isIdentifier(node) && GLOBAL_NAMES.has(node.text) && isReferencePosition(node)) {
434
+ const parent = node.parent;
435
+ if (!(parent && ts.isPropertyAccessExpression(parent) && parent.expression === node))
436
+ bare.add(node.text);
548
437
  }
549
- this.#ssrManifest.moduleMap[entryUrl] = ssrEntriesByName;
438
+ ts.forEachChild(node, visit);
439
+ };
440
+ ts.forEachChild(sf, visit);
441
+ return bare;
442
+ };
443
+ var isReferencePosition = (node) => {
444
+ const parent = node.parent;
445
+ if (!parent)
446
+ return true;
447
+ if (ts.isPropertyAccessExpression(parent) && parent.name === node)
448
+ return false;
449
+ if (ts.isQualifiedName(parent) && parent.right === node)
450
+ return false;
451
+ if (ts.isPropertyAssignment(parent) && parent.name === node)
452
+ return false;
453
+ if (ts.isPropertySignature(parent) && parent.name === node)
454
+ return false;
455
+ if (ts.isBindingElement(parent) && parent.propertyName === node)
456
+ return false;
457
+ if (ts.isImportSpecifier(parent) || ts.isExportSpecifier(parent))
458
+ return false;
459
+ return true;
460
+ };
461
+ var collectExistingNamedImports = (importDecls) => {
462
+ const map = new Map;
463
+ for (const decl of importDecls) {
464
+ const clause = decl.importClause;
465
+ const bindings = clause?.namedBindings;
466
+ if (!clause || !bindings || !ts.isNamedImports(bindings))
467
+ continue;
468
+ if (!ts.isStringLiteral(decl.moduleSpecifier))
469
+ continue;
470
+ const specifier = decl.moduleSpecifier.text;
471
+ if (map.has(specifier))
472
+ continue;
473
+ const names = new Map;
474
+ for (const el of bindings.elements)
475
+ names.set(el.name.text, clause.isTypeOnly || el.isTypeOnly);
476
+ map.set(specifier, { decl, names });
550
477
  }
551
- }
552
-
553
- // pkgs/@akanjs/devkit/frontendBuild/vendorSpecifiers.ts
554
- var VENDOR_SPECIFIERS = [
555
- "react",
556
- "react-dom",
557
- "react-dom/client",
558
- "react/jsx-runtime",
559
- "react/jsx-dev-runtime",
560
- "react-refresh/runtime",
561
- "scheduler",
562
- "react-server-dom-webpack/client.browser",
563
- "akanjs/store",
564
- "akanjs/base",
565
- "akanjs/common",
566
- "akanjs/constant",
567
- "akanjs/fetch"
568
- ];
569
-
570
- // pkgs/@akanjs/devkit/frontendBuild/routeClientBuilder.ts
571
- var SSR_CLIENT_ALIAS_EXTERNALS = [
572
- "react",
573
- "react-dom",
574
- "react-dom/client",
575
- "react/jsx-runtime",
576
- "react/jsx-dev-runtime"
577
- ];
578
- var SSR_CLIENT_EXTERNALS = [...SSR_CLIENT_ALIAS_EXTERNALS, "akanjs/fetch"];
579
-
580
- class RouteClientBuilder {
581
- #app;
582
- #seeds;
583
- #graphSeeds;
584
- #knownEntries;
585
- #command;
586
- #discovery;
587
- #entries;
588
- #browser;
589
- constructor(options) {
590
- this.#app = options.app;
591
- this.#seeds = options.seeds;
592
- this.#graphSeeds = options.graphSeeds;
593
- this.#knownEntries = options.knownEntries ?? new Set;
594
- this.#command = options.command ?? "start";
595
- this.#discovery = options.discovery;
596
- this.#entries = options.entries;
597
- this.#browser = options.browser ?? (this.#command === "start" ? "registry" : "chunks");
478
+ return map;
479
+ };
480
+ var formatNamedImport = (specifier, names) => {
481
+ const entries = [...names.entries()].sort((a, b) => compareNames(a[0], b[0]));
482
+ if (entries.every(([, isType]) => isType))
483
+ return `import type { ${entries.map(([name]) => name).join(", ")} } from "${specifier}";`;
484
+ const parts = entries.map(([name, isType]) => isType ? `type ${name}` : name);
485
+ return `import { ${parts.join(", ")} } from "${specifier}";`;
486
+ };
487
+ var directivePrologueEnd = (sf) => {
488
+ let end = -1;
489
+ for (const stmt of sf.statements) {
490
+ if (ts.isExpressionStatement(stmt) && ts.isStringLiteral(stmt.expression))
491
+ end = stmt.getEnd();
492
+ else
493
+ break;
598
494
  }
599
- async build() {
600
- const discovered = this.#entries ?? await (await this.#getDiscovery()).discover(this.#seeds);
601
- if (discovered.every((e) => this.#knownEntries.has(e)))
602
- return this.#emptyResult(discovered);
603
- const entries = this.#graphSeeds ? [...new Set([...discovered, ...await (await this.#getDiscovery()).discover(this.#graphSeeds)])].sort() : discovered.filter((e) => !this.#knownEntries.has(e));
604
- const bootstrapEntries = await this.#createBootstrapEntries(entries);
605
- const registry = this.#browser === "registry" ? new CsrDevPaths(this.#app.workspace.workspaceRoot) : null;
606
- const browserBundle = registry ? null : await this.#buildBrowserBundle(bootstrapEntries);
607
- const ssrBundle = await this.#buildSsrBundle(bootstrapEntries);
608
- const referenceBundle = browserBundle ?? ssrBundle;
609
- const acceptedEntries = new Set(entries);
610
- const routeEntries = new Set(discovered);
611
- const manifestDelta = {};
612
- const ssrModuleMap = {};
613
- const clientDeps = new Set;
614
- const clientDepsByEntry = {};
615
- for (const [key, bundleRow] of Object.entries(referenceBundle.manifest)) {
616
- const manifestEntry = RouteClientBuilder.resolveOriginalManifestEntry(key, bootstrapEntries.originalByBuildEntry, referenceBundle.clientReferenceIdByAbsPath, this.#app.workspace.workspaceRoot);
617
- if (!manifestEntry)
618
- continue;
619
- if (!acceptedEntries.has(manifestEntry.originalEntry))
620
- continue;
621
- const row = registry ? RouteClientBuilder.#registryRow(registry, manifestEntry.originalEntry, bundleRow) : bundleRow;
622
- manifestDelta[manifestEntry.key] = row;
623
- const ssrOutput = ssrBundle.entryOutputAbsByAbsPath.get(manifestEntry.buildEntry);
624
- if (!ssrOutput)
625
- continue;
626
- ssrModuleMap[row.id] ??= {};
627
- ssrModuleMap[row.id][row.name] = { id: ssrOutput, chunks: [ssrOutput, ssrOutput], name: row.name, async: true };
628
- }
629
- for (const entry of bootstrapEntries.buildEntries) {
630
- const buildEntry = path4.resolve(entry);
631
- const originalEntry = path4.resolve(bootstrapEntries.originalByBuildEntry.get(buildEntry) ?? buildEntry);
632
- if (!acceptedEntries.has(originalEntry))
633
- continue;
634
- const deps = new Set([originalEntry]);
635
- for (const dep of referenceBundle.entryDepsByAbsPath.get(buildEntry) ?? [])
636
- deps.add(path4.resolve(dep));
637
- const sortedDeps = [...deps].sort();
638
- clientDepsByEntry[originalEntry] = sortedDeps;
639
- if (routeEntries.has(originalEntry))
640
- for (const dep of sortedDeps)
641
- clientDeps.add(dep);
495
+ return end;
496
+ };
497
+ var scriptKindFor = (fileName) => fileName.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS;
498
+ var compareNames = (a, b) => {
499
+ const [la, lb] = [a.toLowerCase(), b.toLowerCase()];
500
+ if (la !== lb)
501
+ return la < lb ? -1 : 1;
502
+ return a < b ? -1 : a > b ? 1 : 0;
503
+ };
504
+ var formatError = (err) => err instanceof Error ? err.message : String(err);
505
+ var fileExists = async (file) => stat(file).then((s) => s.isFile()).catch(() => false);
506
+ var domainKindOf = (base) => {
507
+ if (base.endsWith(".constant.ts"))
508
+ return "constant";
509
+ if (base.endsWith(".document.ts"))
510
+ return "document";
511
+ if (base.endsWith(".signal.ts"))
512
+ return "signal";
513
+ return null;
514
+ };
515
+ var buildDomainIndex = async (libDir) => {
516
+ const index = new Map;
517
+ for (const file of await collectDomainFiles(libDir)) {
518
+ const kind = domainKindOf(path.basename(file));
519
+ if (!kind)
520
+ continue;
521
+ const src = await readFile(file, "utf8").catch(() => null);
522
+ if (src === null)
523
+ continue;
524
+ for (const name of exportedPascalNames(src, file)) {
525
+ const entries = index.get(name) ?? [];
526
+ entries.push({ file, kind });
527
+ index.set(name, entries);
642
528
  }
643
- return {
644
- manifestDelta,
645
- ssrManifestDelta: { moduleLoading: null, moduleMap: ssrModuleMap },
646
- newEntries: entries.filter((e) => !this.#knownEntries.has(e)),
647
- discoveredEntries: discovered,
648
- clientDeps: [...clientDeps].sort(),
649
- clientDepsByEntry,
650
- ...registry ? { registryEntries: [...acceptedEntries].sort() } : {}
651
- };
652
- }
653
- static #registryRow(paths, entry, row) {
654
- return {
655
- id: `${SSR_DEV_ID_PREFIX}${paths.idOf(entry)}`,
656
- chunks: [SSR_DEV_CHUNK, SSR_DEV_CHUNK],
657
- name: row.name,
658
- async: true
659
- };
660
529
  }
661
- async#getDiscovery() {
662
- this.#discovery ??= await GraphClientEntryDiscovery.create(this.#app);
663
- return this.#discovery;
530
+ return index;
531
+ };
532
+ var collectDomainFiles = async (dir) => {
533
+ const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
534
+ const files = [];
535
+ for (const entry of entries) {
536
+ if (entry.name.startsWith(".") || entry.name === "node_modules")
537
+ continue;
538
+ const full = path.join(dir, entry.name);
539
+ if (entry.isDirectory())
540
+ files.push(...await collectDomainFiles(full));
541
+ else if (domainKindOf(entry.name) && !TEST_FILE_RE.test(entry.name))
542
+ files.push(full);
664
543
  }
665
- #emptyResult(discoveredEntries = []) {
666
- return {
667
- manifestDelta: {},
668
- ssrManifestDelta: { moduleLoading: null, moduleMap: {} },
669
- newEntries: [],
670
- discoveredEntries,
671
- clientDeps: [],
672
- clientDepsByEntry: {}
673
- };
544
+ return files;
545
+ };
546
+ var exportedPascalNames = (source, fileName) => {
547
+ const sf = ts.createSourceFile(fileName, source, ts.ScriptTarget.Latest, true);
548
+ const isExported = (node) => ts.canHaveModifiers(node) && (ts.getModifiers(node) ?? []).some((m) => m.kind === ts.SyntaxKind.ExportKeyword);
549
+ const names = [];
550
+ for (const stmt of sf.statements) {
551
+ if ((ts.isClassDeclaration(stmt) || ts.isFunctionDeclaration(stmt) || ts.isEnumDeclaration(stmt)) && stmt.name) {
552
+ if (isExported(stmt))
553
+ names.push(stmt.name.text);
554
+ } else if (ts.isVariableStatement(stmt) && isExported(stmt)) {
555
+ for (const decl of stmt.declarationList.declarations)
556
+ if (ts.isIdentifier(decl.name))
557
+ names.push(decl.name.text);
558
+ } else if (ts.isExportDeclaration(stmt) && stmt.exportClause && ts.isNamedExports(stmt.exportClause)) {
559
+ for (const el of stmt.exportClause.elements)
560
+ names.push(el.name.text);
561
+ }
674
562
  }
675
- async#buildBrowserBundle(bootstrapEntries) {
676
- const reactFastRefresh = process.env.AKAN_REACT_FAST_REFRESH !== "0";
677
- return new ClientEntriesBundler({
678
- app: this.#app,
679
- entries: bootstrapEntries.buildEntries,
680
- plugins: [
681
- await createBarrelImportsPlugin(this.#app, {
682
- pipeAfter: reactFastRefresh ? RouteClientBuilder.normalizeNamedDefaultFunctionForFastRefresh : undefined
683
- })
684
- ],
685
- external: VENDOR_SPECIFIERS,
686
- command: this.#command,
687
- reactFastRefresh
688
- }).bundle();
563
+ return names.filter((name) => PASCAL_CASE_RE.test(name));
564
+ };
565
+ var relativeSpecifier = (fromDir, toFile) => {
566
+ const rel = path.relative(fromDir, toFile).replace(/\.tsx?$/, "").split(path.sep).join("/");
567
+ return rel.startsWith(".") ? rel : `./${rel}`;
568
+ };
569
+
570
+ // pkgs/@akanjs/devkit/frontendBuild/clientEntryDiscovery.ts
571
+ import fs from "fs";
572
+ import path2 from "path";
573
+ var USE_CLIENT_RE = /^\s*(?:\/\*[^*]*\*+(?:[^/*][^*]*\*+)*\/\s*|\/\/[^\n]*\n\s*)*["']use client["']/;
574
+ var NODE_MODULES_RE = /[\\/]node_modules[\\/]/;
575
+ var AKANJS_NODE_MODULE_RE = /[\\/]node_modules[\\/]akanjs[\\/]/;
576
+ var NON_SOURCE_EXT_RE = /\.(css|scss|sass|less|json|svg|png|jpe?g|webp|gif|avif|ico|woff2?|ttf|otf|mp3|mp4|wav)$/i;
577
+ var shouldSkipNodeModule = (absPath) => NODE_MODULES_RE.test(absPath) && !AKANJS_NODE_MODULE_RE.test(absPath);
578
+
579
+ class GraphClientEntryDiscovery {
580
+ #akanConfig;
581
+ #resolvePackage;
582
+ #analyzer;
583
+ #tsTranspiler = new Bun.Transpiler({ loader: "tsx" });
584
+ #fileExistsCache = new Map;
585
+ #factsCache = new Map;
586
+ #resolvedFileCache = new Map;
587
+ #resolvedSpecifierCache = new Map;
588
+ #reachableEntriesCache = new Map;
589
+ #readFiles = new Set;
590
+ #epoch = 0;
591
+ constructor(akanConfig, resolvePackage) {
592
+ this.#akanConfig = akanConfig;
593
+ this.#resolvePackage = resolvePackage;
594
+ this.#analyzer = new BarrelAnalyzer({ resolvePackage });
689
595
  }
690
- async#buildSsrBundle(bootstrapEntries) {
691
- return new ClientEntriesBundler({
692
- app: this.#app,
693
- entries: bootstrapEntries.buildEntries,
694
- plugins: [await createBarrelImportsPlugin(this.#app)],
695
- ...RouteClientBuilder.resolveSsrClientBundleOptions(this.#command),
696
- outputSubdir: "client-ssr",
697
- command: this.#command
698
- }).bundle();
596
+ static async create(app) {
597
+ return new GraphClientEntryDiscovery(await app.getConfig(), await createTsconfigPackageResolver(app));
699
598
  }
700
- async#createBootstrapEntries(entries) {
701
- if (!await Bun.file(path4.join(this.#app.cwdPath, "lib", "st.ts")).exists()) {
702
- return { buildEntries: entries, originalByBuildEntry: new Map };
599
+ async discover(seeds) {
600
+ const entries = new Set;
601
+ for (const seed of seeds) {
602
+ const walk = { visiting: new Set, partial: new Set, memo: new Map };
603
+ for (const entry of await this.#discoverFromFile(seed, walk))
604
+ entries.add(entry);
703
605
  }
704
- const outdir = path4.join(this.#app.cwdPath, ".akan", "generated", "client-entry-bootstrap");
705
- await mkdir(outdir, { recursive: true });
706
- const originalByBuildEntry = new Map;
707
- const buildEntries = await Promise.all(entries.map(async (entry) => {
708
- const absEntry = path4.resolve(entry);
709
- const hash = Bun.hash(`${this.#app.name}
710
- ${absEntry}`).toString(36);
711
- const base = path4.basename(absEntry).replace(/[^A-Za-z0-9._-]/g, "_");
712
- const wrapperEntry = path4.join(outdir, `${base}-${hash}.tsx`);
713
- const exportNames = await this.#scanExportNames(absEntry);
714
- await Bun.write(wrapperEntry, RouteClientBuilder.createStoreBootstrapEntrySource({
715
- appName: this.#app.name,
716
- originalEntry: absEntry,
717
- exportNames
718
- }));
719
- originalByBuildEntry.set(path4.resolve(wrapperEntry), absEntry);
720
- return wrapperEntry;
721
- }));
722
- return { buildEntries, originalByBuildEntry };
723
- }
724
- async#scanExportNames(absEntry) {
725
- return scanUseClientExports(await Bun.file(absEntry).text(), absEntry, this.#app.workspace.workspaceRoot);
606
+ return Array.from(entries).sort();
726
607
  }
727
- static normalizeNamedDefaultFunctionForFastRefresh(source, { path: filePath } = {}) {
728
- const declared = RouteClientBuilder.#declaredNamedDefaultFunctions(source, filePath);
729
- if (declared.length === 0)
730
- return null;
731
- let next = "";
732
- let cursor = 0;
733
- for (const { index, text, lineStart, indent, asyncKeyword, name } of declared) {
734
- next += `${source.slice(cursor, index)}${lineStart}${indent}${asyncKeyword}function ${name}`;
735
- cursor = index + text.length;
608
+ invalidate(files) {
609
+ if (files.length === 0)
610
+ return;
611
+ let moved = false;
612
+ for (const file of files) {
613
+ const absPath = path2.resolve(file);
614
+ const exists = fs.existsSync(absPath);
615
+ if (exists !== this.#readFiles.has(absPath))
616
+ moved = true;
617
+ if (!exists)
618
+ this.#readFiles.delete(absPath);
619
+ this.#factsCache.delete(absPath);
620
+ this.#fileExistsCache.delete(absPath);
736
621
  }
737
- next += source.slice(cursor);
738
- return `${next}
739
- ${declared.map(({ name }) => `export default ${name};`).join(`
740
- `)}
741
- `;
622
+ this.#epoch += 1;
623
+ this.#reachableEntriesCache.clear();
624
+ if (!moved)
625
+ return;
626
+ this.#fileExistsCache.clear();
627
+ this.#resolvedFileCache.clear();
628
+ this.#resolvedSpecifierCache.clear();
742
629
  }
743
- static #declaredNamedDefaultFunctions(source, filePath) {
744
- const candidates = [
745
- ...source.matchAll(/(^|\n)(\s*)export\s+default\s+(async\s+)?function\s+([A-Za-z_$][\w$]*)(?=\s*(?:<|\())/g)
746
- ].map((match) => ({
747
- index: match.index,
748
- text: match[0],
749
- lineStart: match[1] ?? "",
750
- indent: match[2] ?? "",
751
- asyncKeyword: match[3] ?? "",
752
- name: match[4] ?? ""
753
- }));
754
- if (candidates.length === 0)
755
- return [];
756
- const probeName = (idx) => `__akanNamedDefault${idx}`;
757
- let probe = "";
758
- let cursor = 0;
759
- candidates.forEach(({ index, text, lineStart, indent, asyncKeyword }, idx) => {
760
- probe += `${source.slice(cursor, index)}${lineStart}${indent}export ${asyncKeyword}function ${probeName(idx)}`;
761
- cursor = index + text.length;
630
+ #lookup(cache, key, run, isFound) {
631
+ const cached = cache.get(key);
632
+ if (cached && (cached.found || cached.epoch === this.#epoch))
633
+ return cached.promise;
634
+ const entry = { epoch: this.#epoch, promise: run() };
635
+ entry.promise.then((value) => {
636
+ entry.found = isFound(value);
637
+ }, () => {
638
+ entry.found = false;
762
639
  });
763
- probe += source.slice(cursor);
764
- const exported = RouteClientBuilder.#scanExports(probe, filePath);
765
- return candidates.filter((_, idx) => exported.has(probeName(idx)));
640
+ cache.set(key, entry);
641
+ return entry.promise;
766
642
  }
767
- static #scanExports(source, filePath) {
643
+ async#fileExists(p) {
644
+ const absPath = path2.resolve(p);
645
+ return await this.#lookup(this.#fileExistsCache, absPath, () => Bun.file(absPath).exists(), (exists) => exists);
646
+ }
647
+ #facts(file) {
648
+ const absPath = path2.resolve(file);
649
+ let cached = this.#factsCache.get(absPath);
650
+ if (!cached) {
651
+ cached = (async () => {
652
+ const content = await Bun.file(absPath).text().catch(() => null);
653
+ if (content === null)
654
+ return null;
655
+ this.#readFiles.add(absPath);
656
+ if (USE_CLIENT_RE.test(content))
657
+ return { isClientEntry: true, imports: [] };
658
+ return { isClientEntry: false, imports: this.#scanImports(await this.#rewrite(content)) };
659
+ })();
660
+ this.#factsCache.set(absPath, cached);
661
+ }
662
+ return cached;
663
+ }
664
+ async#rewrite(content) {
665
+ if (this.#akanConfig.barrelImports.length === 0)
666
+ return content;
768
667
  try {
769
- return new Set(new Bun.Transpiler({ loader: filePath ? loaderFor(filePath) : "tsx" }).scan(source).exports);
668
+ return await rewriteBarrelImports(content, this.#akanConfig.barrelImports, this.#analyzer) ?? content;
669
+ } catch {
670
+ return content;
671
+ }
672
+ }
673
+ #scanImports(source) {
674
+ try {
675
+ return this.#tsTranspiler.scanImports(source);
770
676
  } catch {
677
+ return [];
678
+ }
679
+ }
680
+ async#resolveFileCandidate(absPathNoExt) {
681
+ const cacheKey = path2.resolve(absPathNoExt);
682
+ return await this.#lookup(this.#resolvedFileCache, cacheKey, async () => {
683
+ if (await this.#fileExists(cacheKey))
684
+ return cacheKey;
685
+ for (const ext of SOURCE_EXTS) {
686
+ const f = `${cacheKey}${ext}`;
687
+ if (await this.#fileExists(f))
688
+ return f;
689
+ }
690
+ for (const ext of SOURCE_EXTS) {
691
+ const f = path2.join(cacheKey, `index${ext}`);
692
+ if (await this.#fileExists(f))
693
+ return f;
694
+ }
695
+ return null;
696
+ }, (file) => file !== null);
697
+ }
698
+ async#resolveSpecifier(spec, importerDir) {
699
+ const cacheKey = `${importerDir}\x00${spec}`;
700
+ return await this.#lookup(this.#resolvedSpecifierCache, cacheKey, async () => {
701
+ if (spec.startsWith(".") || spec.startsWith("/")) {
702
+ const abs = spec.startsWith("/") ? spec : path2.resolve(importerDir, spec);
703
+ return await this.#resolveFileCandidate(abs);
704
+ }
705
+ const pkg = await this.#resolvePackage(spec);
706
+ return pkg ? pkg.entryFile : null;
707
+ }, (file) => file !== null);
708
+ }
709
+ async#discoverFromFile(file, walk) {
710
+ const epoch = this.#epoch;
711
+ const absPath = path2.resolve(file);
712
+ const cached = this.#reachableEntriesCache.get(absPath);
713
+ if (cached)
714
+ return new Set(cached);
715
+ const memo = walk.memo.get(absPath);
716
+ if (memo) {
717
+ if (walk.partial.has(absPath))
718
+ for (const onPath of walk.visiting)
719
+ walk.partial.add(onPath);
720
+ return new Set(memo);
721
+ }
722
+ if (walk.visiting.has(absPath)) {
723
+ let below = false;
724
+ for (const onPath of walk.visiting) {
725
+ if (below)
726
+ walk.partial.add(onPath);
727
+ else
728
+ below = onPath === absPath;
729
+ }
771
730
  return new Set;
772
731
  }
773
- }
774
- static resolveSsrClientRuntimeAliases() {
775
- const serverEntry = RouteClientBuilder.resolveAkanServerEntry();
776
- return { [Bun.resolveSync("akanjs/fetch", serverEntry)]: "akanjs/fetch" };
777
- }
778
- static resolveSsrClientBundleOptions(command) {
779
- if (command === "start") {
780
- return {
781
- target: "bun",
782
- external: SSR_CLIENT_EXTERNALS,
783
- externalSubpaths: ["akanjs/fetch"],
784
- externalAliases: RouteClientBuilder.resolveSsrClientRuntimeAliases()
785
- };
732
+ if (shouldSkipNodeModule(absPath))
733
+ return new Set;
734
+ walk.visiting.add(absPath);
735
+ const entries = new Set;
736
+ const facts = await this.#facts(absPath);
737
+ if (!facts)
738
+ return this.#finishDiscovery(absPath, walk, entries, epoch);
739
+ if (facts.isClientEntry) {
740
+ entries.add(absPath);
741
+ return this.#finishDiscovery(absPath, walk, entries, epoch);
786
742
  }
787
- return { target: "bun", external: SSR_CLIENT_ALIAS_EXTERNALS };
788
- }
789
- static resolveAkanServerEntry() {
790
- try {
791
- return Bun.resolveSync("akanjs/server", import.meta.dir);
792
- } catch {
793
- return path4.resolve(import.meta.dir, "../../../server/index.ts");
743
+ const importerDir = path2.dirname(absPath);
744
+ for (const imp of facts.imports) {
745
+ const spec = imp.path;
746
+ if (!spec)
747
+ continue;
748
+ if (NON_SOURCE_EXT_RE.test(spec))
749
+ continue;
750
+ const resolved = await this.#resolveSpecifier(spec, importerDir);
751
+ if (!resolved)
752
+ continue;
753
+ if (shouldSkipNodeModule(resolved))
754
+ continue;
755
+ for (const entry of await this.#discoverFromFile(resolved, walk))
756
+ entries.add(entry);
794
757
  }
758
+ return this.#finishDiscovery(absPath, walk, entries, epoch);
795
759
  }
796
- static createStoreBootstrapEntrySource(args) {
797
- const originalEntry = JSON.stringify(path4.resolve(args.originalEntry));
798
- const namedExports = args.exportNames.filter((name) => name !== "default");
799
- const lines = [
800
- `import ${JSON.stringify(`@apps/${args.appName}/client`)};`,
801
- ...namedExports.length > 0 ? [`export { ${namedExports.join(", ")} } from ${originalEntry};`] : []
802
- ];
803
- if (args.exportNames.includes("default"))
804
- lines.push(`export { default } from ${originalEntry};`);
805
- return `${lines.join(`
806
- `)}
807
- `;
808
- }
809
- static resolveOriginalManifestEntry(manifestKey, originalByBuildEntry, clientReferenceIdByBuildEntry = new Map, workspaceRoot = process.cwd()) {
810
- const hashIdx = manifestKey.lastIndexOf("#");
811
- if (hashIdx < 0)
812
- return null;
813
- const buildReferenceId = manifestKey.slice(0, hashIdx);
814
- const name = manifestKey.slice(hashIdx + 1);
815
- const buildEntry = [...clientReferenceIdByBuildEntry.entries()].find(([, referenceId]) => referenceId === buildReferenceId)?.[0] ?? buildReferenceId;
816
- const originalEntry = originalByBuildEntry.get(buildEntry) ?? buildEntry;
817
- const originalReferenceId = toClientReferencePath(originalEntry, workspaceRoot);
818
- return { buildEntry, originalEntry, name, key: `${originalReferenceId}#${name}` };
760
+ #finishDiscovery(absPath, walk, entries, epoch) {
761
+ walk.visiting.delete(absPath);
762
+ walk.memo.set(absPath, entries);
763
+ if (epoch === this.#epoch && !walk.partial.has(absPath))
764
+ this.#reachableEntriesCache.set(absPath, entries);
765
+ return new Set(entries);
819
766
  }
820
767
  }
821
768
 
822
- // pkgs/@akanjs/devkit/frontendBuild/autoImportSync.ts
823
- import { readdir, readFile, stat } from "fs/promises";
824
- import path5 from "path";
825
- import ts from "typescript";
826
- var TEST_FILE_RE = /\.(test|spec)\.(ts|tsx)$/;
827
- var AKAN_BASE = {
828
- names: ["Int", "Float", "ID", "Any", "Upload", "enumOf", "dayjs"],
829
- specifier: "akanjs/base",
830
- kind: "named"
831
- };
832
- var AKAN_BASE_TYPES = { names: ["Dayjs"], specifier: "akanjs/base", kind: "named", typeOnly: true };
833
- var CNST_NS = { names: ["cnst"], specifier: "../cnst", kind: "namespace" };
834
- var DB_NS = { names: ["db"], specifier: "../db", kind: "namespace" };
835
- var SRV_NS = { names: ["srv"], specifier: "../srv", kind: "namespace" };
836
- var ERR_DICT = { names: ["Err"], specifier: "../dict", kind: "named" };
837
- var RULES = {
838
- constant: [AKAN_BASE, AKAN_BASE_TYPES, { names: ["via"], specifier: "akanjs/constant", kind: "named" }],
839
- document: [
840
- AKAN_BASE,
841
- AKAN_BASE_TYPES,
842
- CNST_NS,
843
- DB_NS,
844
- ERR_DICT,
845
- {
846
- names: ["by", "from", "into", "SchemaOf", "DataInputOf", "DocumentUpdateHelper", "documentQueryHelper", "Mdl"],
847
- specifier: "akanjs/document",
848
- kind: "named"
849
- }
850
- ],
851
- service: [
852
- AKAN_BASE,
853
- AKAN_BASE_TYPES,
854
- DB_NS,
855
- SRV_NS,
856
- CNST_NS,
857
- ERR_DICT,
858
- { names: ["serve"], specifier: "akanjs/service", kind: "named" },
859
- {
860
- names: ["DataInputOf", "ListQueryOption", "DatabaseRegistry", "getFilterInfoByKey"],
861
- specifier: "akanjs/document",
862
- kind: "named"
863
- }
864
- ],
865
- signal: [
866
- AKAN_BASE,
867
- AKAN_BASE_TYPES,
868
- SRV_NS,
869
- CNST_NS,
870
- ERR_DICT,
871
- {
872
- names: ["endpoint", "internal", "slice", "Public", "Req", "Ws", "None", "Res"],
873
- specifier: "akanjs/signal",
874
- kind: "named"
875
- }
876
- ],
877
- dictionary: [
878
- {
879
- names: ["modelDictionary", "scalarDictionary", "serviceDictionary"],
880
- specifier: "akanjs/dictionary",
881
- kind: "named"
882
- }
883
- ],
884
- srvkit: [
885
- AKAN_BASE,
886
- AKAN_BASE_TYPES,
887
- { names: ["Logger", "HttpClient", "sleep"], specifier: "akanjs/common", kind: "named" },
888
- { names: ["adapt"], specifier: "akanjs/service", kind: "named" },
889
- { names: ["SignalContext", "Guard", "InternalArg", "Middleware"], specifier: "akanjs/signal", kind: "named" }
890
- ],
891
- common: [AKAN_BASE, AKAN_BASE_TYPES],
892
- store: [
893
- CNST_NS,
894
- { names: ["store"], specifier: "akanjs/store", kind: "named" },
895
- { names: ["fetch", "usePage", "sig"], specifier: "../useClient", kind: "named" },
896
- { names: ["st"], specifier: "../st", kind: "named" },
897
- { names: ["RootStore"], specifier: "../st", kind: "named", typeOnly: true }
898
- ],
899
- client: [
900
- {
901
- names: ["cnst", "fetch", "st", "usePage"],
902
- specifier: (ctx) => `@${ctx.scope}/${ctx.project}/client`,
903
- kind: "named"
904
- }
905
- ]
906
- };
907
- var PASCAL_CASE_RE = /^[A-Z][A-Za-z0-9]*$/;
908
- var DOMAIN_ROLE_KINDS = {
909
- constant: ["constant"],
910
- dictionary: ["constant", "document", "signal"],
911
- common: ["constant"]
912
- };
913
- var LIB_BARRELS = {
914
- Err: { barrel: "dict", kind: "named" },
915
- db: { barrel: "db", kind: "namespace" },
916
- cnst: { barrel: "cnst", kind: "namespace" },
917
- srv: { barrel: "srv", kind: "namespace" }
918
- };
919
- var GLOBAL_NAMES = new Set(["fetch"]);
920
- var DOMAIN_DENYLIST = new Set([
921
- "Map",
922
- "Set",
923
- "WeakMap",
924
- "WeakSet",
925
- "Date",
926
- "Promise",
927
- "Array",
928
- "Object",
929
- "Error",
930
- "TypeError",
931
- "RangeError",
932
- "RegExp",
933
- "Symbol",
934
- "Proxy",
935
- "Reflect",
936
- "JSON",
937
- "Math",
938
- "Number",
939
- "BigInt",
940
- "Function",
941
- "Boolean",
942
- "String",
943
- "Record",
944
- "Partial",
945
- "Required",
946
- "Readonly",
947
- "Pick",
948
- "Omit",
949
- "Exclude",
950
- "Extract",
951
- "NonNullable",
952
- "ReturnType",
953
- "Parameters",
954
- "Awaited",
955
- "InstanceType"
956
- ]);
769
+ // pkgs/@akanjs/devkit/frontendBuild/csrDevBundler.ts
770
+ import { mkdir as mkdir3 } from "fs/promises";
771
+ import path11 from "path";
772
+ import { CSR_DEV_DIRNAME, CSR_DEV_ROUTE_PREFIX } from "akanjs/server/hmr/csrDevManifest";
957
773
 
958
- class AutoImportSync {
959
- #workspaceRoot;
960
- #domainCache = new Map;
961
- constructor({ workspaceRoot }) {
962
- this.#workspaceRoot = path5.resolve(workspaceRoot);
774
+ // pkgs/@akanjs/devkit/frontendBuild/devRegistryBundler.ts
775
+ import fs7 from "fs";
776
+ import path10 from "path";
777
+ import { isAkanRuntimeMetadataFile } from "akanjs/server/hmr/runtimeMetadataFile";
778
+
779
+ // pkgs/@akanjs/devkit/frontendBuild/csrDevArtifactWriter.ts
780
+ import fs2 from "fs";
781
+ import { mkdir, readdir as readdir2, rm } from "fs/promises";
782
+ import path3 from "path";
783
+ import {
784
+ appGenerationOf,
785
+ CSR_DEV_APP_FILE,
786
+ CSR_DEV_KEPT_PATCHES,
787
+ CSR_DEV_MANIFEST_FILE,
788
+ CSR_DEV_PATCHING_MARKER,
789
+ csrDevModuleFile
790
+ } from "akanjs/server/hmr/csrDevManifest";
791
+ class CsrDevArtifactWriter {
792
+ static #keptVendors = 3;
793
+ static #buildingMarker = ".building";
794
+ #outDir;
795
+ #routePrefix;
796
+ #library;
797
+ constructor(outDir, { routePrefix, library = false }) {
798
+ this.#outDir = outDir;
799
+ this.#routePrefix = routePrefix;
800
+ this.#library = library;
963
801
  }
964
- async syncForBatch(files) {
965
- const changedFiles = [];
966
- const errors = [];
967
- const seen = new Set;
968
- for (const file of files) {
969
- const pkgRoot = this.#domainPkgRootOf(file);
970
- if (pkgRoot)
971
- this.#domainCache.delete(pkgRoot);
972
- }
973
- for (const file of files) {
974
- const abs = path5.resolve(file);
975
- if (seen.has(abs))
976
- continue;
977
- seen.add(abs);
978
- const ctx = this.#contextFor(abs);
979
- if (!ctx)
980
- continue;
981
- try {
982
- const changed = await this.#syncFile(abs, ctx);
983
- if (changed)
984
- changedFiles.push(abs);
985
- } catch (err) {
986
- errors.push(`[auto-import] sync failed for ${file}: ${formatError(err)}`);
987
- }
988
- }
989
- return { changedFiles, errors };
802
+ async markBuilding() {
803
+ await Bun.write(path3.join(this.#outDir, CsrDevArtifactWriter.#buildingMarker), String(process.pid));
990
804
  }
991
- #contextFor(abs) {
992
- const rel = path5.relative(this.#workspaceRoot, abs);
993
- if (rel.startsWith("..") || path5.isAbsolute(rel))
994
- return null;
995
- const base = path5.basename(abs);
996
- if (TEST_FILE_RE.test(base) || base === "index.ts" || base === "index.tsx" || base.endsWith(".d.ts"))
997
- return null;
998
- const parts = rel.split(path5.sep).filter(Boolean);
999
- const [scope, project, facet] = parts;
1000
- if (scope !== "apps" && scope !== "libs" || !project || !facet)
1001
- return null;
1002
- if (facet === "lib" && parts[3] === "__lib")
1003
- return null;
1004
- const role = roleFor(facet, base);
1005
- if (!role)
1006
- return null;
1007
- return { role, scope, project };
805
+ async clearBuilding() {
806
+ await CsrDevArtifactWriter.#remove(path3.join(this.#outDir, CsrDevArtifactWriter.#buildingMarker));
1008
807
  }
1009
- async#syncFile(abs, ctx) {
1010
- const stats = await stat(abs).catch(() => null);
1011
- if (!stats?.isFile())
1012
- return false;
1013
- const source = await readFile(abs, "utf8");
1014
- const resolveExtra = await this.#extraResolverFor(abs, ctx);
1015
- const next = transformSource(source, abs, ctx, resolveExtra);
1016
- if (next === null || next === source)
1017
- return false;
1018
- await FileSys2.writeTextAtomic(abs, next);
1019
- return true;
808
+ async isBuilding() {
809
+ return await Bun.file(path3.join(this.#outDir, CsrDevArtifactWriter.#buildingMarker)).exists();
1020
810
  }
1021
- async#extraResolverFor(abs, ctx) {
1022
- const resolvers = [];
1023
- if (ctx.role === "srvkit" || ctx.role === "common")
1024
- resolvers.push(await this.#libBarrelResolver(abs, ctx));
1025
- const kinds = DOMAIN_ROLE_KINDS[ctx.role];
1026
- if (kinds)
1027
- resolvers.push(await this.#domainResolver(abs, ctx, kinds));
1028
- if (resolvers.length === 0)
1029
- return;
1030
- return (symbol) => {
1031
- for (const resolve of resolvers) {
1032
- const target = resolve(symbol);
1033
- if (target)
1034
- return target;
1035
- }
811
+ async forgetPatch(generation) {
812
+ await Promise.all([`patch-${generation}.js`, `patch-${generation}.js.layout.json`].map((name) => CsrDevArtifactWriter.#remove(path3.join(this.#outDir, name))));
813
+ }
814
+ async readJson(name) {
815
+ const file = Bun.file(path3.join(this.#outDir, name));
816
+ if (!await file.exists())
1036
817
  return null;
1037
- };
818
+ return await file.json().catch(() => null);
1038
819
  }
1039
- async#domainResolver(abs, ctx, kinds) {
1040
- const index = await this.#domainIndex(path5.join(this.#workspaceRoot, ctx.scope, ctx.project));
1041
- const fileDir = path5.dirname(abs);
1042
- return (symbol) => {
1043
- if (!PASCAL_CASE_RE.test(symbol) || DOMAIN_DENYLIST.has(symbol))
1044
- return null;
1045
- const entries = (index.get(symbol) ?? []).filter((entry) => kinds.includes(entry.kind));
1046
- if (entries.length !== 1)
1047
- return null;
1048
- return { specifier: relativeSpecifier(fileDir, entries[0].file), kind: "named" };
1049
- };
820
+ async writeJson(name, value) {
821
+ await this.#writeAtomic(name, JSON.stringify(value));
1050
822
  }
1051
- async#libBarrelResolver(abs, ctx) {
1052
- const fileDir = path5.dirname(abs);
1053
- const libDir = path5.join(this.#workspaceRoot, ctx.scope, ctx.project, "lib");
1054
- const available = new Map;
1055
- for (const [symbol, { barrel, kind }] of Object.entries(LIB_BARRELS)) {
1056
- if (await fileExists(path5.join(libDir, `${barrel}.ts`)))
1057
- available.set(symbol, { specifier: relativeSpecifier(fileDir, path5.join(libDir, barrel)), kind });
823
+ async writeModules(compiled) {
824
+ await mkdir(path3.join(this.#outDir, "modules"), { recursive: true });
825
+ const helpers = new Map(compiled.flatMap((module) => module.helpers ? [[module.helpers.hash, module.helpers]] : []));
826
+ await Promise.all([
827
+ ...compiled.flatMap((module) => [
828
+ Bun.write(this.#modulePath(module.id, ".js"), module.factory),
829
+ ...module.sourceMap ? [Bun.write(this.#modulePath(module.id, ".js.map"), module.sourceMap)] : []
830
+ ]),
831
+ ...[...helpers.values()].map((shared) => Bun.write(this.#helpersPath(shared.hash), shared.definition))
832
+ ]);
833
+ }
834
+ async readCode(graph) {
835
+ const ids = Object.keys(graph.modules).filter((id) => !graph.modules[id]?.vendor && this.hasModule(id));
836
+ const hashes = [...new Set(ids.flatMap((id) => graph.modules[id]?.helpers ?? []))];
837
+ const [modules, helpers] = await Promise.all([
838
+ Promise.all(ids.map(async (id) => [id, await Bun.file(this.#modulePath(id, ".js")).text()])),
839
+ Promise.all(hashes.map(async (hash) => [hash, await Bun.file(this.#helpersPath(hash)).text()]))
840
+ ]);
841
+ return { modules: new Map(modules), helpers: new Map(helpers) };
842
+ }
843
+ hasModule(id) {
844
+ return fs2.existsSync(this.#modulePath(id, ".js"));
845
+ }
846
+ async forgetModules(ids) {
847
+ await Promise.all(ids.flatMap((id) => [
848
+ CsrDevArtifactWriter.#remove(this.#modulePath(id, ".js")),
849
+ CsrDevArtifactWriter.#remove(this.#modulePath(id, ".js.map"))
850
+ ]));
851
+ }
852
+ async writeVendor(graph) {
853
+ const ids = Object.keys(graph.modules).filter((id) => graph.modules[id]?.vendor).sort();
854
+ const stubs = new Set(Object.values(graph.modules).flatMap((module) => module.deps.filter(CsrDevPaths.isStub)));
855
+ const lines = [await this.#helperDefinitions(graph, ids), ...await this.#defineLines(ids)];
856
+ for (const stub of [...stubs].sort()) {
857
+ if (stub.startsWith(CsrDevPaths.emptyStubPrefix)) {
858
+ lines.push(`__akan.define(${JSON.stringify(stub)}, function () {});
859
+ `);
860
+ continue;
861
+ }
862
+ const message = `[akan-csr] ${stub.slice(CsrDevPaths.stubPrefix.length)} is a Node built-in the browser does not have`;
863
+ lines.push(`__akan.define(${JSON.stringify(stub)}, function () {
864
+ throw new Error(${JSON.stringify(message)});
865
+ });
866
+ `);
1058
867
  }
1059
- return (symbol) => available.get(symbol) ?? null;
868
+ const code = lines.join("");
869
+ const vendorFile = `vendor-${Bun.hash(code).toString(36)}.js`;
870
+ await this.#writeAtomic(vendorFile, code);
871
+ return vendorFile;
1060
872
  }
1061
- async#domainIndex(pkgRoot) {
1062
- const cached = this.#domainCache.get(pkgRoot);
1063
- if (cached)
1064
- return cached;
1065
- const index = await buildDomainIndex(path5.join(pkgRoot, "lib"));
1066
- this.#domainCache.set(pkgRoot, index);
1067
- return index;
873
+ async writeApp(graph, { generation, vendorFile, epoch }, code) {
874
+ const ids = Object.keys(graph.modules).filter((id) => !graph.modules[id]?.vendor).sort();
875
+ const lines = await this.#defineLines(ids, code);
876
+ const blocks = ids.map((id, index) => [id, lines[index] ?? ""]);
877
+ const start = this.#library ? `__akan.startLibrary(${JSON.stringify({ generation, refresh: graph.refresh, bootstrap: graph.entries[""], vendorFile, epoch })});
878
+ ` : `__akan.start(${JSON.stringify({ generation, refresh: graph.refresh })});
879
+ `;
880
+ await this.#writeWithSourceMap(CSR_DEV_APP_FILE, await this.#helperDefinitions(graph, ids, code), blocks, start);
1068
881
  }
1069
- #domainPkgRootOf(file) {
1070
- const rel = path5.relative(this.#workspaceRoot, path5.resolve(file));
1071
- if (rel.startsWith("..") || path5.isAbsolute(rel))
1072
- return null;
1073
- const [scope, project, facet] = rel.split(path5.sep).filter(Boolean);
1074
- if (scope !== "apps" && scope !== "libs" || !project || facet !== "lib")
1075
- return null;
1076
- if (!domainKindOf(path5.basename(file)))
1077
- return null;
1078
- return path5.join(this.#workspaceRoot, scope, project);
882
+ async writePatch(generation, modules) {
883
+ const patchFile = `patch-${generation}.js`;
884
+ const blocks = modules.map((module, index) => [module.id, `${JSON.stringify(module.id)}: ${module.factory}${index < modules.length - 1 ? `,
885
+ ` : `
886
+ `}`]);
887
+ const helpers = new Map(modules.flatMap((module) => module.helpers ? [[module.helpers.hash, module.helpers]] : []));
888
+ const header = `${[...helpers.values()].map((shared) => shared.definition).join("")}__akan.update(${generation}, {
889
+ `;
890
+ await this.#writeWithSourceMap(patchFile, header, blocks, `});
891
+ `);
892
+ return `${this.#routePrefix}${patchFile}`;
1079
893
  }
1080
- }
1081
- var roleFor = (facet, base) => {
1082
- if (facet === "lib") {
1083
- if (base.endsWith(".constant.ts"))
1084
- return "constant";
1085
- if (base.endsWith(".document.ts"))
1086
- return "document";
1087
- if (base.endsWith(".service.ts"))
1088
- return "service";
1089
- if (base.endsWith(".signal.ts"))
1090
- return "signal";
1091
- if (base.endsWith(".dictionary.ts"))
1092
- return "dictionary";
1093
- if (base.endsWith(".store.ts"))
1094
- return "store";
1095
- if (base.endsWith(".tsx"))
1096
- return "client";
1097
- return null;
894
+ async writeState(graph, manifest) {
895
+ await this.writeJson("graph.json", graph);
896
+ await this.writeJson(CSR_DEV_MANIFEST_FILE, manifest);
1098
897
  }
1099
- if (facet === "ui" || facet === "page")
1100
- return base.endsWith(".tsx") ? "client" : null;
1101
- if (facet === "webkit")
1102
- return base.endsWith(".ts") || base.endsWith(".tsx") ? "client" : null;
1103
- if (facet === "srvkit")
1104
- return base.endsWith(".ts") ? "srvkit" : null;
1105
- if (facet === "common")
1106
- return base.endsWith(".ts") || base.endsWith(".tsx") ? "common" : null;
1107
- return null;
1108
- };
1109
- var targetFor = (symbol, ctx) => {
1110
- for (const rule of RULES[ctx.role]) {
1111
- if (!rule.names.includes(symbol))
1112
- continue;
1113
- const specifier = typeof rule.specifier === "function" ? rule.specifier(ctx) : rule.specifier;
1114
- return { specifier, kind: rule.kind, typeOnly: rule.typeOnly };
898
+ async commitPatch(graph, manifest, announce, { modules, code }) {
899
+ await this.writeJson(CSR_DEV_MANIFEST_FILE, manifest);
900
+ announce();
901
+ await CsrDevArtifactWriter.#appWriteDelay();
902
+ await this.writeModules(modules);
903
+ await this.writeJson("graph.json", graph);
904
+ await this.writeApp(graph, manifest, code);
905
+ await this.writeJson(CSR_DEV_MANIFEST_FILE, { ...manifest, appGeneration: manifest.generation });
1115
906
  }
1116
- return null;
1117
- };
1118
- var transformSource = (source, fileName, ctx, resolveExtra) => {
1119
- const sf = ts.createSourceFile(fileName, source, ts.ScriptTarget.Latest, true, scriptKindFor(fileName));
1120
- const bound = collectBoundNames(sf);
1121
- const used = collectUsedReferences(sf);
1122
- const bareGlobals = collectBareGlobalReferences(sf);
1123
- const namedBySpecifier = new Map;
1124
- const namespaceImports = [];
1125
- for (const name of used) {
1126
- if (bound.has(name) || bareGlobals.has(name))
1127
- continue;
1128
- const target = targetFor(name, ctx) ?? resolveExtra?.(name) ?? null;
1129
- if (!target)
1130
- continue;
1131
- if (target.kind === "namespace")
1132
- namespaceImports.push({ name, specifier: target.specifier });
1133
- else {
1134
- const names = namedBySpecifier.get(target.specifier) ?? new Map;
1135
- names.set(name, target.typeOnly ?? false);
1136
- namedBySpecifier.set(target.specifier, names);
1137
- }
907
+ async healApp(graph, manifest, code) {
908
+ if (appGenerationOf(manifest) >= manifest.generation)
909
+ return manifest;
910
+ await this.writeApp(graph, manifest, code);
911
+ const healed = { ...manifest, appGeneration: manifest.generation };
912
+ await this.writeJson(CSR_DEV_MANIFEST_FILE, healed);
913
+ return healed;
914
+ }
915
+ async prune(vendorFile, generation) {
916
+ const names = await readdir2(this.#outDir);
917
+ const stale = names.filter((name) => {
918
+ const patch = /^patch-(\d+)\.js(?:\.layout\.json)?$/.exec(name);
919
+ return patch ? Number(patch[1]) <= generation - CSR_DEV_KEPT_PATCHES : false;
920
+ });
921
+ const vendors = names.filter((name) => /^vendor-[\w-]+\.js$/.test(name) && name !== vendorFile).map((name) => ({ name, mtimeMs: CsrDevPaths.mtimeOf(path3.join(this.#outDir, name)) })).sort((a, b) => b.mtimeMs - a.mtimeMs).slice(CsrDevArtifactWriter.#keptVendors - 1).map(({ name }) => name);
922
+ await Promise.all([...stale, ...vendors].map((name) => CsrDevArtifactWriter.#remove(path3.join(this.#outDir, name))));
923
+ }
924
+ async pruneAfterFullBuild(graph, vendorFile, { generation, assets }) {
925
+ const kept = new Set(Object.keys(graph.modules).flatMap((id) => [this.#modulePath(id, ".js"), this.#modulePath(id, ".js.map")]));
926
+ for (const module of Object.values(graph.modules))
927
+ if (module.helpers)
928
+ kept.add(this.#helpersPath(module.helpers));
929
+ for (const asset of assets)
930
+ kept.add(path3.resolve(asset));
931
+ const listed = async (dir) => (await readdir2(path3.join(this.#outDir, dir)).catch(() => [])).map((name) => path3.join(this.#outDir, dir, name));
932
+ const stale = [
933
+ ...(await listed("modules")).filter((file) => !kept.has(file)),
934
+ ...(await listed("assets")).filter((file) => !kept.has(file)),
935
+ ...(await listed(".")).filter((file) => {
936
+ const name = path3.basename(file);
937
+ return /^patch-\d+\.js(?:\.layout\.json)?$/.test(name) || name.endsWith(".tmp");
938
+ }),
939
+ path3.join(this.#outDir, CSR_DEV_PATCHING_MARKER)
940
+ ];
941
+ await Promise.all(stale.map((file) => CsrDevArtifactWriter.#remove(file)));
942
+ await this.prune(vendorFile, generation);
1138
943
  }
1139
- if (namedBySpecifier.size === 0 && namespaceImports.length === 0)
1140
- return null;
1141
- const importDecls = sf.statements.filter(ts.isImportDeclaration);
1142
- const anchor = importDecls.at(-1) ?? null;
1143
- const namedImportsBySpecifier = collectExistingNamedImports(importDecls);
1144
- const edits = [];
1145
- const newStatements = [];
1146
- for (const [specifier, names] of namedBySpecifier) {
1147
- const existing = namedImportsBySpecifier.get(specifier);
1148
- if (existing) {
1149
- const merged = new Map(existing.names);
1150
- for (const [name, isType] of names)
1151
- if (!merged.has(name))
1152
- merged.set(name, isType);
1153
- edits.push({
1154
- start: existing.decl.getStart(sf),
1155
- end: existing.decl.getEnd(),
1156
- text: formatNamedImport(specifier, merged)
1157
- });
1158
- } else
1159
- newStatements.push(formatNamedImport(specifier, names));
944
+ async#helperDefinitions(graph, ids, code) {
945
+ const hashes = [...new Set(ids.flatMap((id) => graph.modules[id]?.helpers ?? []))].sort();
946
+ return (await Promise.all(hashes.map(async (hash) => code?.helpers.get(hash) ?? Bun.file(this.#helpersPath(hash)).text()))).join("");
1160
947
  }
1161
- for (const ns of namespaceImports)
1162
- newStatements.push(`import * as ${ns.name} from "${ns.specifier}";`);
1163
- if (newStatements.length > 0) {
1164
- const anchorEdit = anchor ? edits.find((edit) => edit.start === anchor.getStart(sf)) : undefined;
1165
- if (anchorEdit)
1166
- anchorEdit.text = `${anchorEdit.text}
1167
- ${newStatements.join(`
1168
- `)}`;
1169
- else if (anchor)
1170
- edits.push({ start: anchor.getEnd(), end: anchor.getEnd(), text: `
1171
- ${newStatements.join(`
1172
- `)}` });
1173
- else {
1174
- const prologueEnd = directivePrologueEnd(sf);
1175
- if (prologueEnd >= 0)
1176
- edits.push({ start: prologueEnd, end: prologueEnd, text: `
1177
- ${newStatements.join(`
1178
- `)}` });
1179
- else
1180
- edits.push({ start: 0, end: 0, text: `${newStatements.join(`
1181
- `)}
1182
-
1183
- ` });
1184
- }
948
+ async#defineLines(ids, code) {
949
+ return await Promise.all(ids.map(async (id) => {
950
+ const factory = code?.modules.get(id) ?? await Bun.file(this.#modulePath(id, ".js")).text();
951
+ return `__akan.define(${JSON.stringify(id)}, ${factory});
952
+ `;
953
+ }));
1185
954
  }
1186
- if (edits.length === 0)
1187
- return null;
1188
- edits.sort((a, b) => b.start - a.start);
1189
- let out = source;
1190
- for (const edit of edits)
1191
- out = out.slice(0, edit.start) + edit.text + out.slice(edit.end);
1192
- return out === source ? null : out;
1193
- };
1194
- var collectBoundNames = (sf) => {
1195
- const names = new Set;
1196
- const visit = (node) => {
1197
- if (ts.isImportClause(node)) {
1198
- if (node.name)
1199
- names.add(node.name.text);
1200
- const bindings = node.namedBindings;
1201
- if (bindings && ts.isNamespaceImport(bindings))
1202
- names.add(bindings.name.text);
1203
- if (bindings && ts.isNamedImports(bindings))
1204
- for (const el of bindings.elements)
1205
- names.add(el.name.text);
1206
- }
1207
- if ((ts.isVariableDeclaration(node) || ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node) || ts.isParameter(node) || ts.isBindingElement(node) || ts.isEnumDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isInterfaceDeclaration(node)) && node.name && ts.isIdentifier(node.name))
1208
- names.add(node.name.text);
1209
- ts.forEachChild(node, visit);
1210
- };
1211
- ts.forEachChild(sf, visit);
1212
- return names;
1213
- };
1214
- var collectUsedReferences = (sf) => {
1215
- const used = new Set;
1216
- const visit = (node) => {
1217
- if (ts.isIdentifier(node) && isReferencePosition(node))
1218
- used.add(node.text);
1219
- ts.forEachChild(node, visit);
1220
- };
1221
- ts.forEachChild(sf, visit);
1222
- return used;
1223
- };
1224
- var collectBareGlobalReferences = (sf) => {
1225
- const bare = new Set;
1226
- const visit = (node) => {
1227
- if (ts.isIdentifier(node) && GLOBAL_NAMES.has(node.text) && isReferencePosition(node)) {
1228
- const parent = node.parent;
1229
- if (!(parent && ts.isPropertyAccessExpression(parent) && parent.expression === node))
1230
- bare.add(node.text);
955
+ async#writeWithSourceMap(name, header, blocks, footer) {
956
+ const layout = { lineCount: 0, modules: [] };
957
+ let line = CsrDevPaths.lineCount(header);
958
+ for (const [id, text] of blocks) {
959
+ layout.modules.push([id, line + 1]);
960
+ line += CsrDevPaths.lineCount(text);
1231
961
  }
1232
- ts.forEachChild(node, visit);
1233
- };
1234
- ts.forEachChild(sf, visit);
1235
- return bare;
1236
- };
1237
- var isReferencePosition = (node) => {
1238
- const parent = node.parent;
1239
- if (!parent)
1240
- return true;
1241
- if (ts.isPropertyAccessExpression(parent) && parent.name === node)
1242
- return false;
1243
- if (ts.isQualifiedName(parent) && parent.right === node)
1244
- return false;
1245
- if (ts.isPropertyAssignment(parent) && parent.name === node)
1246
- return false;
1247
- if (ts.isPropertySignature(parent) && parent.name === node)
1248
- return false;
1249
- if (ts.isBindingElement(parent) && parent.propertyName === node)
1250
- return false;
1251
- if (ts.isImportSpecifier(parent) || ts.isExportSpecifier(parent))
1252
- return false;
1253
- return true;
1254
- };
1255
- var collectExistingNamedImports = (importDecls) => {
1256
- const map = new Map;
1257
- for (const decl of importDecls) {
1258
- const clause = decl.importClause;
1259
- const bindings = clause?.namedBindings;
1260
- if (!clause || !bindings || !ts.isNamedImports(bindings))
1261
- continue;
1262
- if (!ts.isStringLiteral(decl.moduleSpecifier))
1263
- continue;
1264
- const specifier = decl.moduleSpecifier.text;
1265
- if (map.has(specifier))
1266
- continue;
1267
- const names = new Map;
1268
- for (const el of bindings.elements)
1269
- names.set(el.name.text, clause.isTypeOnly || el.isTypeOnly);
1270
- map.set(specifier, { decl, names });
962
+ const code = `${header}${blocks.map(([, text]) => text).join("")}${footer}//# sourceMappingURL=${name}.map
963
+ `;
964
+ layout.lineCount = CsrDevPaths.lineCount(code);
965
+ await this.writeJson(`${name}.layout.json`, layout);
966
+ await this.#writeAtomic(name, code);
1271
967
  }
1272
- return map;
1273
- };
1274
- var formatNamedImport = (specifier, names) => {
1275
- const entries = [...names.entries()].sort((a, b) => compareNames(a[0], b[0]));
1276
- if (entries.every(([, isType]) => isType))
1277
- return `import type { ${entries.map(([name]) => name).join(", ")} } from "${specifier}";`;
1278
- const parts = entries.map(([name, isType]) => isType ? `type ${name}` : name);
1279
- return `import { ${parts.join(", ")} } from "${specifier}";`;
1280
- };
1281
- var directivePrologueEnd = (sf) => {
1282
- let end = -1;
1283
- for (const stmt of sf.statements) {
1284
- if (ts.isExpressionStatement(stmt) && ts.isStringLiteral(stmt.expression))
1285
- end = stmt.getEnd();
1286
- else
1287
- break;
968
+ async#writeAtomic(name, content) {
969
+ const target = path3.join(this.#outDir, name);
970
+ const temp = `${target}.${process.pid}.tmp`;
971
+ await Bun.write(temp, content);
972
+ await FileSys2.replace(temp, target);
1288
973
  }
1289
- return end;
1290
- };
1291
- var scriptKindFor = (fileName) => fileName.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS;
1292
- var compareNames = (a, b) => {
1293
- const [la, lb] = [a.toLowerCase(), b.toLowerCase()];
1294
- if (la !== lb)
1295
- return la < lb ? -1 : 1;
1296
- return a < b ? -1 : a > b ? 1 : 0;
1297
- };
1298
- var formatError = (err) => err instanceof Error ? err.message : String(err);
1299
- var fileExists = async (file) => stat(file).then((s) => s.isFile()).catch(() => false);
1300
- var domainKindOf = (base) => {
1301
- if (base.endsWith(".constant.ts"))
1302
- return "constant";
1303
- if (base.endsWith(".document.ts"))
1304
- return "document";
1305
- if (base.endsWith(".signal.ts"))
1306
- return "signal";
1307
- return null;
1308
- };
1309
- var buildDomainIndex = async (libDir) => {
1310
- const index = new Map;
1311
- for (const file of await collectDomainFiles(libDir)) {
1312
- const kind = domainKindOf(path5.basename(file));
1313
- if (!kind)
1314
- continue;
1315
- const src = await readFile(file, "utf8").catch(() => null);
1316
- if (src === null)
1317
- continue;
1318
- for (const name of exportedPascalNames(src, file)) {
1319
- const entries = index.get(name) ?? [];
1320
- entries.push({ file, kind });
1321
- index.set(name, entries);
974
+ static async#remove(file) {
975
+ await rm(file, { force: true }).catch(() => {
976
+ return;
977
+ });
978
+ }
979
+ static async#appWriteDelay() {
980
+ const ms = Number(process.env.AKAN_CSR_DEV_APP_WRITE_DELAY_MS);
981
+ if (Number.isInteger(ms) && ms > 0)
982
+ await Bun.sleep(ms);
983
+ }
984
+ #modulePath(id, extension) {
985
+ return path3.join(this.#outDir, csrDevModuleFile(id, extension));
986
+ }
987
+ #helpersPath(hash) {
988
+ return path3.join(this.#outDir, "modules", `helpers-${hash}.js`);
989
+ }
990
+ }
991
+
992
+ // pkgs/@akanjs/devkit/frontendBuild/csrDevModuleCompiler.ts
993
+ import path7 from "path";
994
+
995
+ // pkgs/@akanjs/devkit/frontendBuild/csrDevFactoryCheck.ts
996
+ class CsrDevFactoryCheck {
997
+ static #parsed = new Set;
998
+ static problemOf(factory, helperDefinition = "") {
999
+ const key = Bun.hash(`${helperDefinition}\x00${factory}`);
1000
+ if (CsrDevFactoryCheck.#parsed.has(key))
1001
+ return null;
1002
+ const problem = CsrDevFactoryCheck.#syntaxError(`${helperDefinition}return (${factory});`);
1003
+ if (problem === null) {
1004
+ CsrDevFactoryCheck.#parsed.add(key);
1005
+ return null;
1006
+ }
1007
+ if (problem.includes("import.meta")) {
1008
+ const line = factory.split(`
1009
+ `).findIndex((text) => /(?<![\w$.])import\.meta\b/.test(text));
1010
+ return `import.meta is left in it${line > 0 ? ` (compiled line ${line})` : ""}, which a classic script cannot hold: ${problem}`;
1322
1011
  }
1012
+ if (CsrDevFactoryCheck.#syntaxError(`${helperDefinition}return (async ${factory});`) === null)
1013
+ return `a top-level await is left in it, which a CommonJS factory cannot wait on: ${problem}`;
1014
+ return problem;
1323
1015
  }
1324
- return index;
1325
- };
1326
- var collectDomainFiles = async (dir) => {
1327
- const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
1328
- const files = [];
1329
- for (const entry of entries) {
1330
- if (entry.name.startsWith(".") || entry.name === "node_modules")
1331
- continue;
1332
- const full = path5.join(dir, entry.name);
1333
- if (entry.isDirectory())
1334
- files.push(...await collectDomainFiles(full));
1335
- else if (domainKindOf(entry.name) && !TEST_FILE_RE.test(entry.name))
1336
- files.push(full);
1016
+ static thrower(id, problem) {
1017
+ const message = `[akan-csr] ${id} was left out of the dev registry: ${problem}`;
1018
+ return `function () {
1019
+ throw new Error(${JSON.stringify(message)});
1020
+ }`;
1337
1021
  }
1338
- return files;
1339
- };
1340
- var exportedPascalNames = (source, fileName) => {
1341
- const sf = ts.createSourceFile(fileName, source, ts.ScriptTarget.Latest, true);
1342
- const isExported = (node) => ts.canHaveModifiers(node) && (ts.getModifiers(node) ?? []).some((m) => m.kind === ts.SyntaxKind.ExportKeyword);
1343
- const names = [];
1344
- for (const stmt of sf.statements) {
1345
- if ((ts.isClassDeclaration(stmt) || ts.isFunctionDeclaration(stmt) || ts.isEnumDeclaration(stmt)) && stmt.name) {
1346
- if (isExported(stmt))
1347
- names.push(stmt.name.text);
1348
- } else if (ts.isVariableStatement(stmt) && isExported(stmt)) {
1349
- for (const decl of stmt.declarationList.declarations)
1350
- if (ts.isIdentifier(decl.name))
1351
- names.push(decl.name.text);
1352
- } else if (ts.isExportDeclaration(stmt) && stmt.exportClause && ts.isNamedExports(stmt.exportClause)) {
1353
- for (const el of stmt.exportClause.elements)
1354
- names.push(el.name.text);
1022
+ static #syntaxError(source) {
1023
+ try {
1024
+ new Function(source);
1025
+ return null;
1026
+ } catch (error) {
1027
+ return error instanceof Error ? error.message : String(error);
1355
1028
  }
1356
1029
  }
1357
- return names.filter((name) => PASCAL_CASE_RE.test(name));
1358
- };
1359
- var relativeSpecifier = (fromDir, toFile) => {
1360
- const rel = path5.relative(fromDir, toFile).replace(/\.tsx?$/, "").split(path5.sep).join("/");
1361
- return rel.startsWith(".") ? rel : `./${rel}`;
1362
- };
1363
-
1364
- // pkgs/@akanjs/devkit/frontendBuild/csrDevPatcher.ts
1365
- import fs4 from "fs";
1366
- import path7 from "path";
1367
- import { appGenerationOf, CSR_DEV_MANIFEST_FILE } from "akanjs/server/hmr/csrDevManifest";
1030
+ }
1368
1031
 
1369
1032
  // pkgs/@akanjs/devkit/frontendBuild/csrDevResolver.ts
1370
1033
  import fs3 from "fs";
1371
- import path6 from "path";
1034
+ import path4 from "path";
1372
1035
  class CsrDevResolver {
1373
1036
  static inline = "inline";
1374
1037
  #paths;
@@ -1450,7 +1113,7 @@ class CsrDevResolver {
1450
1113
  }
1451
1114
  #record(inputs, only) {
1452
1115
  for (const [input, { imports }] of Object.entries(inputs)) {
1453
- const importer = CsrDevPaths.realpath(path6.resolve(input));
1116
+ const importer = CsrDevPaths.realpath(path4.resolve(input));
1454
1117
  if (only && !only.has(importer))
1455
1118
  continue;
1456
1119
  if (imports.every((record) => record.original))
@@ -1463,7 +1126,7 @@ class CsrDevResolver {
1463
1126
  this.#remember(importer, record.original, `${CsrDevPaths.stubPrefix}${record.original}`);
1464
1127
  continue;
1465
1128
  }
1466
- const target = path6.resolve(record.path);
1129
+ const target = path4.resolve(record.path);
1467
1130
  if (!fs3.existsSync(target))
1468
1131
  continue;
1469
1132
  this.#remember(importer, record.original, CsrDevPaths.realpath(target));
@@ -1495,9 +1158,9 @@ class CsrDevResolver {
1495
1158
  return false;
1496
1159
  if (!specifier.startsWith(".") || CsrDevPaths.isVendorFile(importer))
1497
1160
  return true;
1498
- if (this.#hasBrowserMap(path6.dirname(importer)))
1161
+ if (this.#hasBrowserMap(path4.dirname(importer)))
1499
1162
  return true;
1500
- const onDisk = CsrDevPaths.resolveRelative(path6.dirname(importer), specifier, kind);
1163
+ const onDisk = CsrDevPaths.resolveRelative(path4.dirname(importer), specifier, kind);
1501
1164
  return onDisk !== null && CsrDevPaths.realpath(onDisk) === known;
1502
1165
  }
1503
1166
  isRegistryModule(file) {
@@ -1512,12 +1175,12 @@ class CsrDevResolver {
1512
1175
  });
1513
1176
  }
1514
1177
  #disabledInBrowser(importer, specifier, kind) {
1515
- const map = this.#browserMapOf(path6.dirname(importer));
1178
+ const map = this.#browserMapOf(path4.dirname(importer));
1516
1179
  if (!map || map.offFiles.size === 0 && map.offNames.size === 0)
1517
1180
  return null;
1518
- if (!specifier.startsWith(".") && !path6.isAbsolute(specifier))
1181
+ if (!specifier.startsWith(".") && !path4.isAbsolute(specifier))
1519
1182
  return map.offNames.has(specifier) ? `${CsrDevPaths.emptyStubPrefix}${specifier}` : null;
1520
- const target = CsrDevPaths.resolveRelative(path6.dirname(importer), specifier, kind);
1183
+ const target = CsrDevPaths.resolveRelative(path4.dirname(importer), specifier, kind);
1521
1184
  return target && map.offFiles.has(CsrDevPaths.realpath(target)) ? `${CsrDevPaths.emptyStubPrefix}${this.#paths.idOf(target)}` : null;
1522
1185
  }
1523
1186
  #hasBrowserMap(dir) {
@@ -1527,8 +1190,8 @@ class CsrDevResolver {
1527
1190
  const cached = this.#browserMaps.get(dir);
1528
1191
  if (cached !== undefined)
1529
1192
  return cached;
1530
- const pkgFile = path6.join(dir, "package.json");
1531
- const parent = path6.dirname(dir);
1193
+ const pkgFile = path4.join(dir, "package.json");
1194
+ const parent = path4.dirname(dir);
1532
1195
  let map = null;
1533
1196
  if (fs3.existsSync(pkgFile)) {
1534
1197
  try {
@@ -1563,10 +1226,10 @@ class CsrDevResolver {
1563
1226
  #resolveUnknown(importer, specifier, kind) {
1564
1227
  if (specifier.startsWith("node:"))
1565
1228
  return `${CsrDevPaths.stubPrefix}${specifier}`;
1566
- const relative = specifier.startsWith(".") || path6.isAbsolute(specifier);
1229
+ const relative = specifier.startsWith(".") || path4.isAbsolute(specifier);
1567
1230
  const cannotResolve = () => new Error(`[csr-dev] cannot resolve "${specifier}" from ${this.#paths.idOf(importer)}`);
1568
1231
  if (relative) {
1569
- const onDisk = CsrDevPaths.resolveRelative(path6.dirname(importer), specifier, kind) ?? CsrDevResolver.#onDisk(CsrDevPaths.tryResolve(specifier, path6.dirname(importer)));
1232
+ const onDisk = CsrDevPaths.resolveRelative(path4.dirname(importer), specifier, kind) ?? CsrDevResolver.#onDisk(CsrDevPaths.tryResolve(specifier, path4.dirname(importer)));
1570
1233
  if (!onDisk)
1571
1234
  throw cannotResolve();
1572
1235
  return CsrDevPaths.realpath(onDisk);
@@ -1574,9 +1237,9 @@ class CsrDevResolver {
1574
1237
  const sibling = this.#resolvedBySibling(importer, specifier);
1575
1238
  if (sibling)
1576
1239
  return sibling;
1577
- const resolved = CsrDevPaths.tryResolve(specifier, path6.dirname(importer));
1578
- const moved = resolved !== null && path6.isAbsolute(resolved) && !fs3.existsSync(resolved);
1579
- const installed = resolved === null && CsrDevPaths.installedSince(specifier, path6.dirname(importer), this.#startedAt);
1240
+ const resolved = CsrDevPaths.tryResolve(specifier, path4.dirname(importer));
1241
+ const moved = resolved !== null && path4.isAbsolute(resolved) && !fs3.existsSync(resolved);
1242
+ const installed = resolved === null && CsrDevPaths.installedSince(specifier, path4.dirname(importer), this.#startedAt);
1580
1243
  if (!this.#prepassDone || moved) {
1581
1244
  if (resolved === null && !installed && !CsrDevPaths.isVendorFile(importer))
1582
1245
  throw cannotResolve();
@@ -1587,14 +1250,14 @@ class CsrDevResolver {
1587
1250
  return null;
1588
1251
  throw cannotResolve();
1589
1252
  }
1590
- if (!path6.isAbsolute(resolved))
1253
+ if (!path4.isAbsolute(resolved))
1591
1254
  return `${CsrDevPaths.stubPrefix}${specifier}`;
1592
1255
  this.#fallbacks.add(specifier);
1593
1256
  this.#runtimeResolved.set(importer, (this.#runtimeResolved.get(importer) ?? new Set).add(specifier));
1594
1257
  return CsrDevPaths.realpath(resolved);
1595
1258
  }
1596
1259
  static #onDisk(resolved) {
1597
- return resolved && path6.isAbsolute(resolved) && fs3.existsSync(resolved) ? resolved : null;
1260
+ return resolved && path4.isAbsolute(resolved) && fs3.existsSync(resolved) ? resolved : null;
1598
1261
  }
1599
1262
  #resolvedBySibling(importer, specifier) {
1600
1263
  const scope = CsrDevResolver.#packageScopeOf(importer);
@@ -1614,7 +1277,7 @@ class CsrDevResolver {
1614
1277
  this.#resolution.set(importer, byImporter.set(specifier, target));
1615
1278
  }
1616
1279
  #isInlineSpecifier(specifier) {
1617
- if (specifier.startsWith(".") || path6.isAbsolute(specifier) || specifier.startsWith("node:"))
1280
+ if (specifier.startsWith(".") || path4.isAbsolute(specifier) || specifier.startsWith("node:"))
1618
1281
  return false;
1619
1282
  return this.#context.optimizeImports.some((pattern) => {
1620
1283
  const base = CsrDevResolver.#patternBase(pattern);
@@ -1625,991 +1288,1259 @@ class CsrDevResolver {
1625
1288
  return pattern.endsWith("/*") ? pattern.slice(0, -2) : pattern;
1626
1289
  }
1627
1290
  static #packageNameOf(file) {
1628
- const marker = `${path6.sep}node_modules${path6.sep}`;
1291
+ const marker = `${path4.sep}node_modules${path4.sep}`;
1629
1292
  const index = file.lastIndexOf(marker);
1630
1293
  if (index < 0)
1631
1294
  return null;
1632
- const [scope, name] = file.slice(index + marker.length).split(path6.sep);
1295
+ const [scope, name] = file.slice(index + marker.length).split(path4.sep);
1633
1296
  return (scope?.startsWith("@") ? `${scope}/${name}` : scope) || null;
1634
1297
  }
1635
1298
  static #packageScopeOf(file) {
1636
- const marker = `${path6.sep}node_modules${path6.sep}`;
1299
+ const marker = `${path4.sep}node_modules${path4.sep}`;
1637
1300
  const index = file.lastIndexOf(marker);
1638
1301
  if (index < 0)
1639
1302
  return "";
1640
- const [scope, name] = file.slice(index + marker.length).split(path6.sep);
1641
- return file.slice(0, index + marker.length) + (scope?.startsWith("@") ? `${scope}${path6.sep}${name}` : scope);
1303
+ const [scope, name] = file.slice(index + marker.length).split(path4.sep);
1304
+ return file.slice(0, index + marker.length) + (scope?.startsWith("@") ? `${scope}${path4.sep}${name}` : scope);
1642
1305
  }
1643
1306
  }
1644
1307
 
1645
- // pkgs/@akanjs/devkit/frontendBuild/csrDevPatcher.ts
1646
- class CsrDevPatcher {
1647
- #bundler;
1648
- #resident;
1649
- #state = null;
1650
- #delegated = new Map;
1651
- constructor(bundler, { resident = false } = {}) {
1652
- this.#bundler = bundler;
1653
- this.#resident = resident;
1308
+ // pkgs/@akanjs/devkit/frontendBuild/routeClientBuilder.ts
1309
+ import { mkdir as mkdir2 } from "fs/promises";
1310
+ import path6 from "path";
1311
+ import { SSR_DEV_CHUNK, SSR_DEV_ID_PREFIX } from "akanjs/server/hmr/csrDevManifest";
1312
+
1313
+ // pkgs/@akanjs/devkit/frontendBuild/clientEntriesBundler.ts
1314
+ import fs4 from "fs";
1315
+ import path5 from "path";
1316
+
1317
+ // pkgs/@akanjs/devkit/frontendBuild/clientBuildTypes.ts
1318
+ var CLIENT_BUNDLE_NAMING = {
1319
+ entry: "[name]-[hash].[ext]",
1320
+ chunk: "chunks/[hash].[ext]",
1321
+ asset: "assets/[hash].[ext]"
1322
+ };
1323
+
1324
+ // pkgs/@akanjs/devkit/frontendBuild/clientEntriesBundler.ts
1325
+ class ClientEntriesBundler {
1326
+ #app;
1327
+ #entries;
1328
+ #plugins;
1329
+ #external;
1330
+ #externalSubpaths;
1331
+ #externalAliases;
1332
+ #command;
1333
+ #target;
1334
+ #outputSubdir;
1335
+ #reactFastRefresh;
1336
+ #outdir;
1337
+ #servePrefix;
1338
+ #manifest = {};
1339
+ #ssrManifest = { moduleLoading: null, moduleMap: {} };
1340
+ #entryUrlsByAbsPath = new Map;
1341
+ #entryOutputAbsByAbsPath = new Map;
1342
+ #entryDepsByAbsPath = new Map;
1343
+ #clientReferenceIdByAbsPath = new Map;
1344
+ #opaqueEntries = null;
1345
+ #metafileOutputsByAbs = new Map;
1346
+ constructor(options) {
1347
+ this.#app = options.app;
1348
+ this.#entries = options.entries;
1349
+ this.#plugins = options.plugins ?? [];
1350
+ this.#external = options.external ?? [];
1351
+ this.#externalSubpaths = options.externalSubpaths ?? [];
1352
+ this.#externalAliases = options.externalAliases ?? {};
1353
+ this.#command = options.command ?? "start";
1354
+ this.#target = options.target ?? "browser";
1355
+ this.#outputSubdir = options.outputSubdir ?? "client";
1356
+ this.#reactFastRefresh = options.reactFastRefresh ?? false;
1357
+ const artifactDir = `${this.#command === "build" ? this.#app.dist.cwdPath : this.#app.cwdPath}/.akan/artifact`;
1358
+ this.#outdir = `${artifactDir}/${this.#outputSubdir}`;
1359
+ this.#servePrefix = `/_akan/${this.#outputSubdir}`;
1654
1360
  }
1655
- forget() {
1656
- this.#state = null;
1361
+ async bundle() {
1362
+ const akanConfig = await this.#app.getConfig();
1363
+ this.#opaqueEntries = await this.#createOpaqueEntryAliases();
1364
+ const result = await Bun.build({
1365
+ entrypoints: this.#opaqueEntries.entries,
1366
+ outdir: this.#outdir,
1367
+ splitting: true,
1368
+ target: this.#target,
1369
+ format: "esm",
1370
+ naming: CLIENT_BUNDLE_NAMING,
1371
+ metafile: true,
1372
+ define: bundleDefine(this.#app, this.#command, "ssr"),
1373
+ minify: this.#command === "build",
1374
+ optimizeImports: akanConfig.optimizeImports,
1375
+ reactFastRefresh: this.#command === "start" && this.#outputSubdir === "client" && this.#reactFastRefresh,
1376
+ plugins: this.#external.length > 0 || this.#externalSubpaths.length > 0 || Object.keys(this.#externalAliases).length > 0 ? [...this.#plugins, this.#createExternalSpecifiersPlugin()] : this.#plugins
1377
+ });
1378
+ if (!result.success)
1379
+ throw new AggregateError(result.logs, "[ClientEntriesBundler] Bun.build failed");
1380
+ await this.#rewriteExternalImportSpecifiers(result.outputs);
1381
+ const metafile = result.metafile;
1382
+ if (!metafile)
1383
+ throw new Error("[ClientEntriesBundler] metafile is missing");
1384
+ this.#metafileOutputsByAbs = new Map(Object.entries(metafile.outputs).map(([k, v]) => [this.#absFromOutdir(k), v]));
1385
+ for (const artifact of result.outputs.filter((a) => a.kind === "entry-point"))
1386
+ this.#addEntryArtifact(artifact.path);
1387
+ return {
1388
+ manifest: this.#manifest,
1389
+ ssrManifest: this.#ssrManifest,
1390
+ entryUrlsByAbsPath: this.#entryUrlsByAbsPath,
1391
+ entryOutputAbsByAbsPath: this.#entryOutputAbsByAbsPath,
1392
+ entryDepsByAbsPath: this.#entryDepsByAbsPath,
1393
+ clientReferenceIdByAbsPath: this.#clientReferenceIdByAbsPath
1394
+ };
1657
1395
  }
1658
- releaseHandedOver() {
1659
- for (const [id, handedOver] of this.#delegated)
1660
- if (!handedOver.failed)
1661
- this.#delegated.delete(id);
1396
+ async#createOpaqueEntryAliases() {
1397
+ const aliasDir = path5.join(this.#app.cwdPath, ".akan", "generated", "client-entry-alias", this.#outputSubdir);
1398
+ fs4.mkdirSync(aliasDir, { recursive: true });
1399
+ const originalByAlias = new Map;
1400
+ const aliasedEntries = await Promise.all(this.#entries.map(async (entry) => {
1401
+ const absEntry = path5.resolve(entry);
1402
+ const hash = Bun.hash(`${this.#app.name}
1403
+ ${this.#outputSubdir}
1404
+ ${absEntry}`).toString(36);
1405
+ const aliasPath = path5.join(aliasDir, `${hash}.tsx`);
1406
+ await Bun.write(aliasPath, this.#createOpaqueEntryAliasSource(absEntry, await this.#scanEntryExportNames(absEntry)));
1407
+ originalByAlias.set(path5.resolve(aliasPath), absEntry);
1408
+ return aliasPath;
1409
+ }));
1410
+ return { entries: aliasedEntries, originalByAlias, aliasDir };
1662
1411
  }
1663
- async update(changedFiles = [], { announce, roots = [], onlyRoots = false, allowWholeAppBuilds = false } = {}) {
1664
- const context = await this.#bundler.context();
1665
- if (!context)
1666
- return { kind: "unchanged" };
1667
- const resident = this.#state;
1668
- const state = resident ?? await this.#load();
1669
- if (!state) {
1670
- const manifest = await this.#bundler.writer.readJson(CSR_DEV_MANIFEST_FILE);
1671
- const reason = !manifest ? "first build" : await this.#bundler.writer.isBuilding() ? "the last whole build was cut short" : "the dev bundle config changed";
1672
- return this.#handBack(reason, (manifest?.generation ?? 0) + 1, context, { first: !manifest });
1673
- }
1674
- const generation = state.manifest.generation + 1;
1675
- if (state.graph.configKey !== context.configKey)
1676
- return this.#handBack("the dev bundle config changed", generation, context);
1677
- const metadataFile = changedFiles.find((file) => this.#bundler.isMetadataFile(file));
1678
- if (metadataFile)
1679
- return this.#handBack(`${path7.basename(metadataFile)} changed`, generation, context);
1680
- if (!resident && state.graph.metadata !== await this.#bundler.metadataFingerprint())
1681
- return this.#handBack("signal or dictionary metadata changed since the registry was built", generation, context);
1682
- this.#state = this.#resident ? state : null;
1683
- const healable = appGenerationOf(state.manifest) < state.manifest.generation && this.#withoutFactory(state, (file) => fs4.existsSync(file)).length === 0;
1684
- if (healable)
1685
- state.manifest = await this.#bundler.writer.healApp(state.graph, state.manifest, await this.#code(state));
1686
- return await this.#patch(context, state, changedFiles, generation, {
1687
- announce,
1688
- roots,
1689
- onlyRoots,
1690
- allowWholeAppBuilds
1691
- });
1412
+ #createOpaqueEntryAliasSource(absEntry, exportNames) {
1413
+ const entryLit = JSON.stringify(path5.resolve(absEntry));
1414
+ if (exportNames.length === 0)
1415
+ return `export * from ${entryLit};
1416
+ `;
1417
+ const namedExports = exportNames.filter((name) => name !== "default");
1418
+ const lines = namedExports.length > 0 ? [`export { ${namedExports.join(", ")} } from ${entryLit};`] : [];
1419
+ if (exportNames.includes("default"))
1420
+ lines.push(`export { default } from ${entryLit};`);
1421
+ return `${lines.join(`
1422
+ `)}
1423
+ `;
1692
1424
  }
1693
- async#patch(context, state, changedFiles, generation, { announce, roots = [], onlyRoots = false, allowWholeAppBuilds }) {
1694
- const { paths, writer } = this.#bundler;
1695
- const { graph } = state;
1696
- const entries = await this.#bundler.writeEntries(context);
1697
- const entryIds = this.#bundler.entryIds(entries.files);
1698
- const present = CsrDevPatcher.#presence(changedFiles);
1699
- const forgotten = this.#forgetDeletedModules(state, present);
1700
- const changed = onlyRoots ? new Set : await this.#changedModules(graph, changedFiles, present);
1701
- for (const id of this.#withoutFactory(state, present))
1702
- changed.add(id);
1703
- for (const file of entries.changed)
1704
- changed.add(paths.idOf(file));
1705
- const rootFiles = [...Object.values(entries.files), ...roots.filter((file) => fs4.existsSync(file))];
1706
- for (const file of rootFiles) {
1707
- const id = paths.idOf(file);
1708
- if (!graph.modules[id] && !(onlyRoots && await this.#parked(graph, id)))
1709
- changed.add(id);
1710
- }
1711
- const carried = onlyRoots ? [] : await this.#carriedRoots(graph, changed, present);
1712
- const routesMoved = this.#bundler.reloadsOnEntryChange && (entries.changed.length > 0 || JSON.stringify(entryIds) !== JSON.stringify(graph.entries));
1713
- if (changed.size === 0) {
1714
- if (carried.length === 0)
1715
- return { kind: "unchanged" };
1716
- return await this.#retryCarried(context, state, carried, { announce, allowWholeAppBuilds });
1717
- }
1718
- const resolver = new CsrDevResolver({
1719
- paths,
1720
- context,
1721
- entryFiles: rootFiles.map((file) => CsrDevPaths.realpath(file)),
1722
- resolution: graph.resolution,
1723
- runtimeResolved: graph.runtimeResolved
1724
- });
1725
- const known = new Set(Object.keys(graph.modules).map((id) => paths.fileOf(id)));
1726
- const compile = async (ids) => await this.#bundler.compiler(context, resolver).compile(ids.map((id) => paths.fileOf(id)), known, { refuseNewVendor: !allowWholeAppBuilds, refusePrepass: !allowWholeAppBuilds });
1727
- let result;
1728
- let orphans;
1729
- try {
1730
- ({ result, orphans } = await this.#compileLeavingOrphans(compile, graph, changed));
1731
- for (const module of result.modules)
1732
- known.add(module.file);
1733
- result = await this.#withTwinImporters(compile, graph, result);
1734
- } catch (error) {
1735
- graph.pending = [...new Set([...graph.pending, ...changed, ...carried])];
1736
- if (!this.#resident)
1737
- graph.failed = {
1738
- roots: [...changed, ...carried],
1739
- files: await CsrDevPatcher.#hashesOf(paths, CsrDevPatcher.#implicatedFiles(error))
1740
- };
1741
- await writer.writeJson("graph.json", graph);
1742
- throw error;
1743
- }
1744
- const refusal = result.refusedVendors.length > 0 ? "an npm module joined the graph" : result.unresolved.length > 0 ? `a new import needs the resolution build: ${result.unresolved[0]}` : null;
1745
- if (refusal) {
1746
- if (routesMoved)
1747
- await writer.writeJson("graph.json", { ...graph, entries: {} });
1748
- if (onlyRoots) {
1749
- for (const id of changed)
1750
- if (!graph.modules[id])
1751
- this.#delegated.set(id, {
1752
- hash: await CsrDevPaths.hashOf(paths.fileOf(id)),
1753
- before: JSON.stringify(graph.failed ?? null)
1754
- });
1425
+ async#scanEntryExportNames(absEntry) {
1426
+ return scanUseClientExports(await Bun.file(absEntry).text(), absEntry, this.#app.workspace.workspaceRoot);
1427
+ }
1428
+ #createExternalSpecifiersPlugin() {
1429
+ const set = new Set(this.#external);
1430
+ const subpathSet = new Set(this.#externalSubpaths);
1431
+ const aliases = this.#externalAliases;
1432
+ const specifiers = [...new Set([...this.#external, ...this.#externalSubpaths, ...Object.keys(aliases)])];
1433
+ const escaped = specifiers.map((s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
1434
+ const filter = new RegExp(`^(${escaped.join("|")})(?:/.*)?$`);
1435
+ return {
1436
+ name: "akan-externalize-specifiers",
1437
+ setup(build) {
1438
+ build.onResolve({ filter }, (args) => {
1439
+ const alias = aliases[args.path];
1440
+ if (alias)
1441
+ return { path: alias, external: true };
1442
+ if (!ClientEntriesBundler.#matchesExternalSpecifier(args.path, set, subpathSet))
1443
+ return;
1444
+ return { path: args.path, external: true };
1445
+ });
1755
1446
  }
1756
- return this.#handBack(refusal, generation, context);
1757
- }
1758
- const compiled = result.modules;
1759
- const vendorJoined = compiled.some((module) => module.vendor && !graph.modules[module.id]);
1760
- const code = await this.#code(state);
1761
- for (const id of [...orphans, ...this.#caseTwins(graph, compiled)]) {
1762
- delete graph.modules[id];
1763
- resolver.forget(paths.fileOf(id));
1764
- code?.modules.delete(id);
1765
- forgotten.push(id);
1447
+ };
1448
+ }
1449
+ static #matchesExternalSpecifier(specifier, exactExternals, subpathExternals) {
1450
+ if (exactExternals.has(specifier) || subpathExternals.has(specifier))
1451
+ return true;
1452
+ for (const external of subpathExternals)
1453
+ if (specifier.startsWith(`${external}/`))
1454
+ return true;
1455
+ return false;
1456
+ }
1457
+ async#rewriteExternalImportSpecifiers(outputs) {
1458
+ if (Object.keys(this.#externalAliases).length === 0)
1459
+ return;
1460
+ await Promise.all(outputs.filter((output) => output.path.endsWith(".js")).map(async (output) => {
1461
+ const source = await Bun.file(output.path).text();
1462
+ const rewritten = ClientEntriesBundler.rewriteExternalImportSpecifiers(source, this.#externalAliases);
1463
+ if (rewritten !== source)
1464
+ await Bun.write(output.path, rewritten);
1465
+ }));
1466
+ }
1467
+ static rewriteExternalImportSpecifiers(source, aliases) {
1468
+ let rewritten = source;
1469
+ for (const [specifier, alias] of Object.entries(aliases)) {
1470
+ if (!alias)
1471
+ continue;
1472
+ const escaped = specifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1473
+ rewritten = rewritten.replace(new RegExp(`(\\bfrom\\s*["'])${escaped}(["'])`, "g"), (_match, prefix, suffix) => {
1474
+ return `${prefix}${alias}${suffix}`;
1475
+ }).replace(new RegExp(`(\\bimport\\s*["'])${escaped}(["'])`, "g"), (_match, prefix, suffix) => {
1476
+ return `${prefix}${alias}${suffix}`;
1477
+ });
1766
1478
  }
1767
- graph.pending = graph.pending.filter((id) => !orphans.includes(id));
1768
- this.#bundler.merge(graph, resolver, compiled);
1769
- if (!this.#resident)
1770
- delete graph.failed;
1771
- for (const module of compiled)
1772
- this.#delegated.delete(module.id);
1773
- graph.entries = entryIds;
1774
- for (const module of compiled) {
1775
- if (module.vendor || !code)
1479
+ return rewritten;
1480
+ }
1481
+ #toServeUrl(absOutPath) {
1482
+ const rel = path5.relative(this.#outdir, absOutPath).split(path5.sep).join("/");
1483
+ return `${this.#servePrefix}/${rel}`;
1484
+ }
1485
+ #absFromOutdir(p) {
1486
+ return path5.isAbsolute(p) ? p : path5.resolve(this.#outdir, p);
1487
+ }
1488
+ #absFromEntryPoint(p) {
1489
+ if (path5.isAbsolute(p))
1490
+ return path5.resolve(p);
1491
+ const candidates = [path5.resolve(process.cwd(), p), path5.resolve(this.#app.cwdPath, p)];
1492
+ return candidates.find((candidate) => fs4.existsSync(candidate)) ?? candidates[0];
1493
+ }
1494
+ #collectChunkUrls(absOutPath, visited = new Set) {
1495
+ if (visited.has(absOutPath))
1496
+ return [];
1497
+ visited.add(absOutPath);
1498
+ const info = this.#metafileOutputsByAbs.get(absOutPath);
1499
+ if (!info)
1500
+ return [this.#toServeUrl(absOutPath)];
1501
+ const urls = [this.#toServeUrl(absOutPath)];
1502
+ for (const imp of info.imports) {
1503
+ if (imp.kind === "dynamic-import")
1776
1504
  continue;
1777
- code.modules.set(module.id, module.factory);
1778
- if (module.helpers)
1779
- code.helpers.set(module.helpers.hash, module.helpers.definition);
1505
+ const absImp = this.#absFromOutdir(imp.path);
1506
+ if (!absImp.endsWith(".js"))
1507
+ continue;
1508
+ urls.push(...this.#collectChunkUrls(absImp, visited));
1780
1509
  }
1781
- const changedIds = compiled.filter((module) => !module.vendor).map((module) => module.id);
1782
- const constantId = changedIds.find((id) => id.endsWith(".constant.ts"));
1783
- const reason = vendorJoined ? "an npm module joined the graph" : routesMoved ? "the route table changed" : constantId ? `${path7.basename(constantId)} changed` : undefined;
1784
- if (compiled.length === 0 && !reason) {
1785
- await writer.writeJson("graph.json", graph);
1786
- await writer.forgetModules(forgotten);
1787
- if (carried.length === 0)
1788
- return { kind: "unchanged" };
1789
- return await this.#retryCarried(context, state, carried, { announce, allowWholeAppBuilds });
1510
+ return urls;
1511
+ }
1512
+ #collectClientDeps(absOutPath, visited = new Set) {
1513
+ if (visited.has(absOutPath))
1514
+ return new Set;
1515
+ visited.add(absOutPath);
1516
+ const deps = new Set;
1517
+ const info = this.#metafileOutputsByAbs.get(absOutPath);
1518
+ if (!info)
1519
+ return deps;
1520
+ if (!this.#opaqueEntries)
1521
+ throw new Error("[ClientEntriesBundler] opaque entries are missing");
1522
+ for (const input of Object.keys(info.inputs ?? {})) {
1523
+ const absInput = this.#absFromEntryPoint(input);
1524
+ if (!absInput.includes("node_modules") && !absInput.startsWith(this.#opaqueEntries.aliasDir))
1525
+ deps.add(absInput);
1790
1526
  }
1791
- const previous = state.manifest;
1792
- const next = { ...previous, generation, entries: entryIds };
1793
- const update = (patchUrl) => ({
1794
- generation,
1795
- reload: !!reason,
1796
- reason,
1797
- patchUrl,
1798
- changedIds,
1799
- moduleCount: Object.keys(graph.modules).length,
1800
- epoch: previous.epoch
1801
- });
1802
- let outcome;
1803
- if (reason) {
1804
- await writer.forgetPatch(generation);
1805
- await writer.writeModules(compiled);
1806
- const vendorFile = vendorJoined ? await writer.writeVendor(graph) : previous.vendorFile;
1807
- await writer.writeApp(graph, { generation, vendorFile, epoch: previous.epoch }, code);
1808
- state.manifest = { ...next, vendorFile, appGeneration: generation };
1809
- await writer.writeState(graph, state.manifest);
1810
- await writer.prune(vendorFile, generation);
1811
- const reloaded = update();
1812
- announce?.(reloaded);
1813
- outcome = { kind: "update", update: reloaded };
1814
- } else {
1815
- const patched = update(await writer.writePatch(generation, compiled.filter((module) => !module.vendor)));
1816
- await writer.commitPatch(graph, { ...next, appGeneration: appGenerationOf(previous) }, () => announce?.(patched), { modules: compiled, code });
1817
- state.manifest = { ...next, appGeneration: generation };
1818
- await writer.prune(previous.vendorFile, generation);
1819
- outcome = { kind: "update", update: patched };
1527
+ for (const imp of info.imports) {
1528
+ if (imp.kind === "dynamic-import")
1529
+ continue;
1530
+ const absImp = this.#absFromOutdir(imp.path);
1531
+ if (!absImp.endsWith(".js"))
1532
+ continue;
1533
+ for (const dep of this.#collectClientDeps(absImp, visited))
1534
+ deps.add(dep);
1820
1535
  }
1821
- await writer.forgetModules(forgotten);
1822
- if (carried.length === 0)
1823
- return outcome;
1824
- const retried = await this.#retryCarried(context, state, carried, { announce, allowWholeAppBuilds });
1825
- return retried.kind === "delegate" ? retried : outcome;
1536
+ return deps;
1826
1537
  }
1827
- async#retryCarried(context, state, carried, { announce, allowWholeAppBuilds }) {
1828
- const { paths, writer } = this.#bundler;
1829
- const retried = await this.#patch(context, state, [], state.manifest.generation + 1, {
1830
- announce,
1831
- roots: carried.map((id) => paths.fileOf(id)),
1832
- onlyRoots: true,
1833
- allowWholeAppBuilds
1834
- });
1835
- if (retried.kind === "delegate") {
1836
- state.graph.pending = [...new Set([...state.graph.pending, ...carried])];
1837
- await writer.writeJson("graph.json", state.graph);
1538
+ #addEntryArtifact(absOut) {
1539
+ if (!this.#opaqueEntries)
1540
+ throw new Error("[ClientEntriesBundler] opaque entries are missing");
1541
+ const info = this.#metafileOutputsByAbs.get(absOut);
1542
+ const buildEntryPointAbs = info?.entryPoint ? this.#absFromEntryPoint(info.entryPoint) : null;
1543
+ const entryPointAbs = buildEntryPointAbs ? this.#opaqueEntries.originalByAlias.get(buildEntryPointAbs) ?? buildEntryPointAbs : null;
1544
+ if (!entryPointAbs)
1545
+ return;
1546
+ const chunkUrls = this.#collectChunkUrls(absOut);
1547
+ const entryUrl = chunkUrls[0] ?? this.#toServeUrl(absOut);
1548
+ this.#entryUrlsByAbsPath.set(entryPointAbs, entryUrl);
1549
+ this.#entryOutputAbsByAbsPath.set(entryPointAbs, absOut);
1550
+ this.#entryDepsByAbsPath.set(entryPointAbs, [...this.#collectClientDeps(absOut)].sort());
1551
+ const clientReferenceId = toClientReferencePath(entryPointAbs, this.#app.workspace.workspaceRoot);
1552
+ this.#clientReferenceIdByAbsPath.set(entryPointAbs, clientReferenceId);
1553
+ const flatChunks = [];
1554
+ for (const url of chunkUrls)
1555
+ flatChunks.push(url, url);
1556
+ const exportNames = info?.exports && info.exports.length > 0 ? info.exports : ["default"];
1557
+ const ssrEntriesByName = {};
1558
+ for (const name of exportNames) {
1559
+ const key = `${clientReferenceId}#${name}`;
1560
+ this.#manifest[key] = { id: entryUrl, chunks: flatChunks, name, async: true };
1561
+ ssrEntriesByName[name] = { id: absOut, chunks: [absOut, absOut], name, async: true };
1562
+ }
1563
+ this.#ssrManifest.moduleMap[entryUrl] = ssrEntriesByName;
1564
+ }
1565
+ }
1566
+
1567
+ // pkgs/@akanjs/devkit/frontendBuild/vendorSpecifiers.ts
1568
+ var VENDOR_SPECIFIERS = [
1569
+ "react",
1570
+ "react-dom",
1571
+ "react-dom/client",
1572
+ "react/jsx-runtime",
1573
+ "react/jsx-dev-runtime",
1574
+ "react-refresh/runtime",
1575
+ "scheduler",
1576
+ "react-server-dom-webpack/client.browser",
1577
+ "akanjs/store",
1578
+ "akanjs/base",
1579
+ "akanjs/common",
1580
+ "akanjs/constant",
1581
+ "akanjs/fetch"
1582
+ ];
1583
+
1584
+ // pkgs/@akanjs/devkit/frontendBuild/routeClientBuilder.ts
1585
+ var SSR_CLIENT_ALIAS_EXTERNALS = [
1586
+ "react",
1587
+ "react-dom",
1588
+ "react-dom/client",
1589
+ "react/jsx-runtime",
1590
+ "react/jsx-dev-runtime"
1591
+ ];
1592
+ var SSR_CLIENT_EXTERNALS = [...SSR_CLIENT_ALIAS_EXTERNALS, "akanjs/fetch"];
1593
+
1594
+ class RouteClientBuilder {
1595
+ #app;
1596
+ #seeds;
1597
+ #graphSeeds;
1598
+ #knownEntries;
1599
+ #command;
1600
+ #discovery;
1601
+ #entries;
1602
+ #browser;
1603
+ constructor(options) {
1604
+ this.#app = options.app;
1605
+ this.#seeds = options.seeds;
1606
+ this.#graphSeeds = options.graphSeeds;
1607
+ this.#knownEntries = options.knownEntries ?? new Set;
1608
+ this.#command = options.command ?? "start";
1609
+ this.#discovery = options.discovery;
1610
+ this.#entries = options.entries;
1611
+ this.#browser = options.browser ?? (this.#command === "start" ? "registry" : "chunks");
1612
+ }
1613
+ async build() {
1614
+ const discovered = this.#entries ?? await (await this.#getDiscovery()).discover(this.#seeds);
1615
+ if (discovered.every((e) => this.#knownEntries.has(e)))
1616
+ return this.#emptyResult(discovered);
1617
+ const entries = this.#graphSeeds ? [...new Set([...discovered, ...await (await this.#getDiscovery()).discover(this.#graphSeeds)])].sort() : discovered.filter((e) => !this.#knownEntries.has(e));
1618
+ const bootstrapEntries = await this.#createBootstrapEntries(entries);
1619
+ const registry = this.#browser === "registry" ? new CsrDevPaths(this.#app.workspace.workspaceRoot) : null;
1620
+ const browserBundle = registry ? null : await this.#buildBrowserBundle(bootstrapEntries);
1621
+ const ssrBundle = await this.#buildSsrBundle(bootstrapEntries);
1622
+ const referenceBundle = browserBundle ?? ssrBundle;
1623
+ const acceptedEntries = new Set(entries);
1624
+ const routeEntries = new Set(discovered);
1625
+ const manifestDelta = {};
1626
+ const ssrModuleMap = {};
1627
+ const clientDeps = new Set;
1628
+ const clientDepsByEntry = {};
1629
+ for (const [key, bundleRow] of Object.entries(referenceBundle.manifest)) {
1630
+ const manifestEntry = RouteClientBuilder.resolveOriginalManifestEntry(key, bootstrapEntries.originalByBuildEntry, referenceBundle.clientReferenceIdByAbsPath, this.#app.workspace.workspaceRoot);
1631
+ if (!manifestEntry)
1632
+ continue;
1633
+ if (!acceptedEntries.has(manifestEntry.originalEntry))
1634
+ continue;
1635
+ const row = registry ? RouteClientBuilder.#registryRow(registry, manifestEntry.originalEntry, bundleRow) : bundleRow;
1636
+ manifestDelta[manifestEntry.key] = row;
1637
+ const ssrOutput = ssrBundle.entryOutputAbsByAbsPath.get(manifestEntry.buildEntry);
1638
+ if (!ssrOutput)
1639
+ continue;
1640
+ ssrModuleMap[row.id] ??= {};
1641
+ ssrModuleMap[row.id][row.name] = { id: ssrOutput, chunks: [ssrOutput, ssrOutput], name: row.name, async: true };
1838
1642
  }
1839
- return retried;
1840
- }
1841
- async#compileLeavingOrphans(compile, graph, changed) {
1842
- const { paths } = this.#bundler;
1843
- try {
1844
- return { result: await compile([...changed]), orphans: [] };
1845
- } catch (error) {
1846
- const failing = new Set(CsrDevPatcher.#failingFiles(error).map((file) => paths.idOf(file)));
1847
- const broken = [...changed].filter((id) => failing.has(id) && graph.modules[id]);
1848
- if (broken.length === 0)
1849
- throw error;
1850
- const rest = [...changed].filter((id) => !broken.includes(id));
1851
- const empty = { modules: [], refusedVendors: [], unresolved: [] };
1852
- const result = rest.length === 0 ? empty : await compile(rest).catch(() => {
1853
- throw error;
1854
- });
1855
- const modules = { ...graph.modules };
1856
- for (const module of result.modules)
1857
- modules[module.id] = {
1858
- vendor: module.vendor,
1859
- mtimeMs: module.mtimeMs,
1860
- hash: module.hash,
1861
- deps: module.deps.map((dep) => CsrDevPaths.isStub(dep) ? dep : paths.idOf(dep))
1862
- };
1863
- const reached = await this.#bundler.reachable({ ...graph, modules });
1864
- if (broken.some((id) => reached.has(id)))
1865
- throw error;
1866
- const orphans = Object.keys(modules).filter((id) => !reached.has(id) && !modules[id]?.vendor);
1867
- return { result, orphans };
1643
+ for (const entry of bootstrapEntries.buildEntries) {
1644
+ const buildEntry = path6.resolve(entry);
1645
+ const originalEntry = path6.resolve(bootstrapEntries.originalByBuildEntry.get(buildEntry) ?? buildEntry);
1646
+ if (!acceptedEntries.has(originalEntry))
1647
+ continue;
1648
+ const deps = new Set([originalEntry]);
1649
+ for (const dep of referenceBundle.entryDepsByAbsPath.get(buildEntry) ?? [])
1650
+ deps.add(path6.resolve(dep));
1651
+ const sortedDeps = [...deps].sort();
1652
+ clientDepsByEntry[originalEntry] = sortedDeps;
1653
+ if (routeEntries.has(originalEntry))
1654
+ for (const dep of sortedDeps)
1655
+ clientDeps.add(dep);
1868
1656
  }
1869
- }
1870
- #caseTwins(graph, compiled) {
1871
- const { paths } = this.#bundler;
1872
- const idsByFold = new Map;
1873
- for (const id of Object.keys(graph.modules))
1874
- idsByFold.set(id.toLowerCase(), [...idsByFold.get(id.toLowerCase()) ?? [], id]);
1875
- return compiled.flatMap((module) => (idsByFold.get(module.id.toLowerCase()) ?? []).filter((id) => id !== module.id && !graph.modules[id]?.vendor && !CsrDevPaths.isNamed(paths.fileOf(id))));
1876
- }
1877
- async#withTwinImporters(compile, graph, result) {
1878
- const twins = new Set(this.#caseTwins(graph, result.modules));
1879
- if (twins.size === 0)
1880
- return result;
1881
- const compiledIds = new Set(result.modules.map((module) => module.id));
1882
- const importers = Object.entries(graph.modules).filter(([id, module]) => !module.vendor && !compiledIds.has(id) && !twins.has(id)).filter(([, module]) => module.deps.some((dep) => twins.has(dep))).map(([id]) => id);
1883
- if (importers.length === 0)
1884
- return result;
1885
- const more = await compile(importers);
1886
1657
  return {
1887
- modules: [...result.modules, ...more.modules],
1888
- refusedVendors: [...result.refusedVendors, ...more.refusedVendors],
1889
- unresolved: [...result.unresolved, ...more.unresolved]
1658
+ manifestDelta,
1659
+ ssrManifestDelta: { moduleLoading: null, moduleMap: ssrModuleMap },
1660
+ newEntries: entries.filter((e) => !this.#knownEntries.has(e)),
1661
+ discoveredEntries: discovered,
1662
+ clientDeps: [...clientDeps].sort(),
1663
+ clientDepsByEntry,
1664
+ ...registry ? { registryEntries: [...acceptedEntries].sort() } : {}
1890
1665
  };
1891
1666
  }
1892
- static #implicatedFiles(error) {
1893
- const failing = CsrDevPatcher.#failingFiles(error);
1894
- const messages = error instanceof AggregateError ? error.errors : [];
1895
- const named = messages.flatMap((message) => {
1896
- const text = String(message.message ?? "");
1897
- const bases = [process.cwd(), ...failing.map((file) => path7.dirname(file))];
1898
- return [...text.matchAll(/"([^"\n]+)"/g)].flatMap(([, quoted = ""]) => {
1899
- const found = bases.map((base) => path7.resolve(base, quoted)).find((file) => CsrDevPaths.isScript(file) && fs4.existsSync(file));
1900
- return found ? [CsrDevPaths.realpath(found)] : [];
1901
- });
1902
- });
1903
- return [...new Set([...failing, ...named])];
1904
- }
1905
- static #failingFiles(error) {
1906
- const messages = error instanceof AggregateError ? error.errors : [];
1907
- return messages.map((message) => message.position?.file).filter((file) => typeof file === "string" && path7.isAbsolute(file)).map((file) => CsrDevPaths.realpath(file));
1667
+ static #registryRow(paths, entry, row) {
1668
+ return {
1669
+ id: `${SSR_DEV_ID_PREFIX}${paths.idOf(entry)}`,
1670
+ chunks: [SSR_DEV_CHUNK, SSR_DEV_CHUNK],
1671
+ name: row.name,
1672
+ async: true
1673
+ };
1908
1674
  }
1909
- async#carriedRoots(graph, changed, present) {
1910
- const { paths } = this.#bundler;
1911
- const outside = graph.pending.filter((id) => !graph.modules[id] && !changed.has(id) && present(paths.fileOf(id)));
1912
- if (outside.length === 0)
1913
- return [];
1914
- const wanted = await this.#bundler.wantedRoots(outside.map((id) => paths.fileOf(id)), graph);
1915
- const kept = outside.filter((id) => wanted.has(paths.fileOf(id)));
1916
- graph.pending = graph.pending.filter((id) => graph.modules[id] || kept.includes(id));
1917
- const carried = [];
1918
- for (const id of kept)
1919
- if (!await this.#parked(graph, id))
1920
- carried.push(id);
1921
- return carried;
1675
+ async#getDiscovery() {
1676
+ this.#discovery ??= await GraphClientEntryDiscovery.create(this.#app);
1677
+ return this.#discovery;
1922
1678
  }
1923
- async#parked(graph, id) {
1924
- const { paths } = this.#bundler;
1925
- const handedOver = this.#delegated.get(id);
1926
- if (!handedOver || handedOver.hash !== await CsrDevPaths.hashOf(paths.fileOf(id)))
1927
- return false;
1928
- if (!handedOver.failed) {
1929
- if (JSON.stringify(graph.failed ?? null) === handedOver.before)
1930
- return true;
1931
- if (graph.failed?.roots.includes(id))
1932
- handedOver.failed = graph.failed.files;
1933
- }
1934
- if (!handedOver.failed)
1935
- return false;
1936
- for (const [failedId, hash] of Object.entries(handedOver.failed))
1937
- if (await CsrDevPaths.hashOf(paths.fileOf(failedId)) !== hash)
1938
- return false;
1939
- return true;
1679
+ #emptyResult(discoveredEntries = []) {
1680
+ return {
1681
+ manifestDelta: {},
1682
+ ssrManifestDelta: { moduleLoading: null, moduleMap: {} },
1683
+ newEntries: [],
1684
+ discoveredEntries,
1685
+ clientDeps: [],
1686
+ clientDepsByEntry: {}
1687
+ };
1940
1688
  }
1941
- static async#hashesOf(paths, files) {
1942
- return Object.fromEntries(await Promise.all(files.map(async (file) => [paths.idOf(file), await CsrDevPaths.hashOf(file)])));
1689
+ async#buildBrowserBundle(bootstrapEntries) {
1690
+ const reactFastRefresh = process.env.AKAN_REACT_FAST_REFRESH !== "0";
1691
+ return new ClientEntriesBundler({
1692
+ app: this.#app,
1693
+ entries: bootstrapEntries.buildEntries,
1694
+ plugins: [
1695
+ await createBarrelImportsPlugin(this.#app, {
1696
+ pipeAfter: reactFastRefresh ? RouteClientBuilder.normalizeNamedDefaultFunctionForFastRefresh : undefined
1697
+ })
1698
+ ],
1699
+ external: VENDOR_SPECIFIERS,
1700
+ command: this.#command,
1701
+ reactFastRefresh
1702
+ }).bundle();
1943
1703
  }
1944
- #withoutFactory({ graph, code }, present) {
1945
- const { paths, writer } = this.#bundler;
1946
- const hasFactory = (id) => code ? code.modules.has(id) : writer.hasModule(id);
1947
- return Object.entries(graph.modules).filter(([id, module]) => !module.vendor && !hasFactory(id) && present(paths.fileOf(id))).map(([id]) => id);
1704
+ async#buildSsrBundle(bootstrapEntries) {
1705
+ return new ClientEntriesBundler({
1706
+ app: this.#app,
1707
+ entries: bootstrapEntries.buildEntries,
1708
+ plugins: [await createBarrelImportsPlugin(this.#app)],
1709
+ ...RouteClientBuilder.resolveSsrClientBundleOptions(this.#command),
1710
+ outputSubdir: "client-ssr",
1711
+ command: this.#command
1712
+ }).bundle();
1948
1713
  }
1949
- static #presence(changedFiles) {
1950
- const caseMoved = changedFiles.some((file) => fs4.existsSync(file) && !CsrDevPaths.isNamed(file));
1951
- return caseMoved ? (file) => CsrDevPaths.isNamed(file) : (file) => fs4.existsSync(file);
1714
+ async#createBootstrapEntries(entries) {
1715
+ if (!await Bun.file(path6.join(this.#app.cwdPath, "lib", "st.ts")).exists()) {
1716
+ return { buildEntries: entries, originalByBuildEntry: new Map };
1717
+ }
1718
+ const outdir = path6.join(this.#app.cwdPath, ".akan", "generated", "client-entry-bootstrap");
1719
+ await mkdir2(outdir, { recursive: true });
1720
+ const originalByBuildEntry = new Map;
1721
+ const buildEntries = await Promise.all(entries.map(async (entry) => {
1722
+ const absEntry = path6.resolve(entry);
1723
+ const hash = Bun.hash(`${this.#app.name}
1724
+ ${absEntry}`).toString(36);
1725
+ const base = path6.basename(absEntry).replace(/[^A-Za-z0-9._-]/g, "_");
1726
+ const wrapperEntry = path6.join(outdir, `${base}-${hash}.tsx`);
1727
+ const exportNames = await this.#scanExportNames(absEntry);
1728
+ await Bun.write(wrapperEntry, RouteClientBuilder.createStoreBootstrapEntrySource({
1729
+ appName: this.#app.name,
1730
+ originalEntry: absEntry,
1731
+ exportNames
1732
+ }));
1733
+ originalByBuildEntry.set(path6.resolve(wrapperEntry), absEntry);
1734
+ return wrapperEntry;
1735
+ }));
1736
+ return { buildEntries, originalByBuildEntry };
1952
1737
  }
1953
- #handBack(reason, generation, context, { first = false } = {}) {
1954
- this.#state = null;
1955
- return { kind: "delegate", reason, generation, context, ...first ? { first } : {} };
1738
+ async#scanExportNames(absEntry) {
1739
+ return scanUseClientExports(await Bun.file(absEntry).text(), absEntry, this.#app.workspace.workspaceRoot);
1956
1740
  }
1957
- async#load() {
1958
- const manifest = await this.#bundler.writer.readJson(CSR_DEV_MANIFEST_FILE);
1959
- const graph = await this.#bundler.writer.readJson("graph.json");
1960
- if (!manifest || !graph || graph.version !== 1 || await this.#bundler.writer.isBuilding())
1741
+ static normalizeNamedDefaultFunctionForFastRefresh(source, { path: filePath } = {}) {
1742
+ const declared = RouteClientBuilder.#declaredNamedDefaultFunctions(source, filePath);
1743
+ if (declared.length === 0)
1961
1744
  return null;
1962
- return { graph, manifest, code: null };
1963
- }
1964
- async#code(state) {
1965
- if (!this.#resident)
1966
- return;
1967
- state.code ??= await this.#bundler.writer.readCode(state.graph);
1968
- return state.code;
1969
- }
1970
- async#changedModules(graph, changedFiles, present) {
1971
- const { paths } = this.#bundler;
1972
- const changed = new Set(graph.pending.filter((id) => graph.modules[id]));
1973
- for (const file of changedFiles) {
1974
- const id = paths.idOf(file);
1975
- if (graph.modules[id])
1976
- changed.add(id);
1977
- }
1978
- for (const id of this.#shadowedImporters(graph, changedFiles))
1979
- changed.add(id);
1980
- for (const [id, module] of Object.entries(graph.modules)) {
1981
- if (module.vendor || changed.has(id))
1982
- continue;
1983
- const file = paths.fileOf(id);
1984
- if (CsrDevPaths.mtimeOf(file) === module.mtimeMs)
1985
- continue;
1986
- if (await CsrDevPaths.hashOf(file) !== module.hash)
1987
- changed.add(id);
1988
- }
1989
- for (const [id, module] of Object.entries(graph.modules)) {
1990
- if (changed.has(id))
1991
- continue;
1992
- for (const dep of module.deps) {
1993
- if (graph.modules[dep] || CsrDevPaths.isStub(dep))
1994
- continue;
1995
- const file = paths.fileOf(dep);
1996
- if (present(file) && CsrDevPaths.isNamed(file))
1997
- changed.add(dep);
1998
- else if (!module.vendor)
1999
- changed.add(id);
2000
- }
1745
+ let next = "";
1746
+ let cursor = 0;
1747
+ for (const { index, text, lineStart, indent, asyncKeyword, name } of declared) {
1748
+ next += `${source.slice(cursor, index)}${lineStart}${indent}${asyncKeyword}function ${name}`;
1749
+ cursor = index + text.length;
2001
1750
  }
2002
- return changed;
1751
+ next += source.slice(cursor);
1752
+ return `${next}
1753
+ ${declared.map(({ name }) => `export default ${name};`).join(`
1754
+ `)}
1755
+ `;
2003
1756
  }
2004
- #shadowedImporters(graph, changedFiles) {
2005
- const { paths } = this.#bundler;
2006
- const bases = new Set(changedFiles.map((file) => CsrDevPaths.realpath(file)).filter((file) => !graph.modules[paths.idOf(file)] && CsrDevPaths.isScript(file) && fs4.existsSync(file)).flatMap((file) => {
2007
- const stem = file.slice(0, file.length - path7.extname(file).length);
2008
- return path7.basename(stem) === "index" ? [stem, path7.dirname(stem)] : [stem];
1757
+ static #declaredNamedDefaultFunctions(source, filePath) {
1758
+ const candidates = [
1759
+ ...source.matchAll(/(^|\n)([^\S\n]*)export\s+default\s+(async\s+)?function\s+([A-Za-z_$][\w$]*)(?=\s*(?:<|\())/g)
1760
+ ].map((match) => ({
1761
+ index: match.index,
1762
+ text: match[0],
1763
+ lineStart: match[1] ?? "",
1764
+ indent: match[2] ?? "",
1765
+ asyncKeyword: match[3] ?? "",
1766
+ name: match[4] ?? ""
2009
1767
  }));
2010
- if (bases.size === 0)
1768
+ if (candidates.length === 0)
2011
1769
  return [];
2012
- return Object.entries(graph.resolution).filter(([importer, bySpecifier]) => {
2013
- if (!graph.modules[importer] || graph.modules[importer]?.vendor)
2014
- return false;
2015
- const dir = path7.dirname(paths.fileOf(importer));
2016
- return Object.keys(bySpecifier).some((specifier) => specifier.startsWith(".") && bases.has(path7.resolve(dir, specifier)));
2017
- }).map(([importer]) => importer);
1770
+ const probeName = (idx) => `__akanNamedDefault${idx}`;
1771
+ let probe = "";
1772
+ let cursor = 0;
1773
+ candidates.forEach(({ index, text, lineStart, indent, asyncKeyword }, idx) => {
1774
+ probe += `${source.slice(cursor, index)}${lineStart}${indent}export ${asyncKeyword}function ${probeName(idx)}`;
1775
+ cursor = index + text.length;
1776
+ });
1777
+ probe += source.slice(cursor);
1778
+ const exported = RouteClientBuilder.#scanExports(probe, filePath);
1779
+ return candidates.filter((_, idx) => exported.has(probeName(idx)));
2018
1780
  }
2019
- #forgetDeletedModules({ graph, code }, present) {
2020
- const { paths } = this.#bundler;
2021
- const deleted = Object.keys(graph.modules).filter((id) => !graph.modules[id]?.vendor && !present(paths.fileOf(id)));
2022
- for (const id of deleted) {
2023
- delete graph.modules[id];
2024
- code?.modules.delete(id);
1781
+ static #scanExports(source, filePath) {
1782
+ try {
1783
+ return new Set(new Bun.Transpiler({ loader: filePath ? loaderFor(filePath) : "tsx" }).scan(source).exports);
1784
+ } catch {
1785
+ return new Set;
2025
1786
  }
2026
- return deleted;
1787
+ }
1788
+ static resolveSsrClientRuntimeAliases() {
1789
+ const serverEntry = RouteClientBuilder.resolveAkanServerEntry();
1790
+ return { [Bun.resolveSync("akanjs/fetch", serverEntry)]: "akanjs/fetch" };
1791
+ }
1792
+ static resolveSsrClientBundleOptions(command) {
1793
+ if (command === "start") {
1794
+ return {
1795
+ target: "bun",
1796
+ external: SSR_CLIENT_EXTERNALS,
1797
+ externalSubpaths: ["akanjs/fetch"],
1798
+ externalAliases: RouteClientBuilder.resolveSsrClientRuntimeAliases()
1799
+ };
1800
+ }
1801
+ return { target: "bun", external: SSR_CLIENT_ALIAS_EXTERNALS };
1802
+ }
1803
+ static resolveAkanServerEntry() {
1804
+ try {
1805
+ return Bun.resolveSync("akanjs/server", import.meta.dir);
1806
+ } catch {
1807
+ return path6.resolve(import.meta.dir, "../../../server/index.ts");
1808
+ }
1809
+ }
1810
+ static createStoreBootstrapEntrySource(args) {
1811
+ const originalEntry = JSON.stringify(path6.resolve(args.originalEntry));
1812
+ const namedExports = args.exportNames.filter((name) => name !== "default");
1813
+ const lines = [
1814
+ `import ${JSON.stringify(`@apps/${args.appName}/client`)};`,
1815
+ ...namedExports.length > 0 ? [`export { ${namedExports.join(", ")} } from ${originalEntry};`] : []
1816
+ ];
1817
+ if (args.exportNames.includes("default"))
1818
+ lines.push(`export { default } from ${originalEntry};`);
1819
+ return `${lines.join(`
1820
+ `)}
1821
+ `;
1822
+ }
1823
+ static resolveOriginalManifestEntry(manifestKey, originalByBuildEntry, clientReferenceIdByBuildEntry = new Map, workspaceRoot = process.cwd()) {
1824
+ const hashIdx = manifestKey.lastIndexOf("#");
1825
+ if (hashIdx < 0)
1826
+ return null;
1827
+ const buildReferenceId = manifestKey.slice(0, hashIdx);
1828
+ const name = manifestKey.slice(hashIdx + 1);
1829
+ const buildEntry = [...clientReferenceIdByBuildEntry.entries()].find(([, referenceId]) => referenceId === buildReferenceId)?.[0] ?? buildReferenceId;
1830
+ const originalEntry = originalByBuildEntry.get(buildEntry) ?? buildEntry;
1831
+ const originalReferenceId = toClientReferencePath(originalEntry, workspaceRoot);
1832
+ return { buildEntry, originalEntry, name, key: `${originalReferenceId}#${name}` };
2027
1833
  }
2028
1834
  }
2029
1835
 
2030
- // pkgs/@akanjs/devkit/frontendBuild/csrDevBundler.ts
2031
- import { mkdir as mkdir3 } from "fs/promises";
2032
- import path12 from "path";
2033
- import { CSR_DEV_DIRNAME, CSR_DEV_ROUTE_PREFIX } from "akanjs/server/hmr/csrDevManifest";
2034
-
2035
- // pkgs/@akanjs/devkit/frontendBuild/devRegistryBundler.ts
2036
- import fs7 from "fs";
2037
- import path11 from "path";
2038
- import { isAkanRuntimeMetadataFile } from "akanjs/server/hmr/runtimeMetadataFile";
2039
-
2040
- // pkgs/@akanjs/devkit/frontendBuild/csrDevArtifactWriter.ts
2041
- import fs5 from "fs";
2042
- import { mkdir as mkdir2, readdir as readdir2, rm } from "fs/promises";
2043
- import path8 from "path";
2044
- import {
2045
- appGenerationOf as appGenerationOf2,
2046
- CSR_DEV_APP_FILE,
2047
- CSR_DEV_KEPT_PATCHES,
2048
- CSR_DEV_MANIFEST_FILE as CSR_DEV_MANIFEST_FILE2,
2049
- CSR_DEV_PATCHING_MARKER,
2050
- csrDevModuleFile
2051
- } from "akanjs/server/hmr/csrDevManifest";
2052
- class CsrDevArtifactWriter {
2053
- static #keptVendors = 3;
2054
- static #buildingMarker = ".building";
1836
+ // pkgs/@akanjs/devkit/frontendBuild/csrDevModuleCompiler.ts
1837
+ class CsrDevModuleCompiler {
1838
+ static #storeRoot = /(?:^|\/)lib\/st\.ts$/;
1839
+ static #macroImport = /\b(?:with|assert)\s*\{\s*type\s*:\s*["']macro["']/;
1840
+ static #factoryArgument = /(?<![\w$.])(?:require|module|exports|__akanImport|\$RefreshReg\$|\$RefreshSig\$)(?![\w$]|\s*:)/;
1841
+ static #helperPatches = [
1842
+ ["__toESM", "(mod, isNodeMode, target)", "toESM"],
1843
+ ["__reExport", "(target, mod, secondTarget)", "reExport"]
1844
+ ];
1845
+ #app;
1846
+ #paths;
1847
+ #resolver;
1848
+ #context;
2055
1849
  #outDir;
2056
1850
  #routePrefix;
2057
- #library;
2058
- constructor(outDir, { routePrefix, library = false }) {
1851
+ #assets = new Set;
1852
+ constructor({ app, paths, resolver, context, outDir, routePrefix }) {
1853
+ this.#app = app;
1854
+ this.#paths = paths;
1855
+ this.#resolver = resolver;
1856
+ this.#context = context;
2059
1857
  this.#outDir = outDir;
2060
1858
  this.#routePrefix = routePrefix;
2061
- this.#library = library;
2062
- }
2063
- async markBuilding() {
2064
- await Bun.write(path8.join(this.#outDir, CsrDevArtifactWriter.#buildingMarker), String(process.pid));
2065
- }
2066
- async clearBuilding() {
2067
- await CsrDevArtifactWriter.#remove(path8.join(this.#outDir, CsrDevArtifactWriter.#buildingMarker));
2068
- }
2069
- async isBuilding() {
2070
- return await Bun.file(path8.join(this.#outDir, CsrDevArtifactWriter.#buildingMarker)).exists();
2071
- }
2072
- async forgetPatch(generation) {
2073
- await Promise.all([`patch-${generation}.js`, `patch-${generation}.js.layout.json`].map((name) => CsrDevArtifactWriter.#remove(path8.join(this.#outDir, name))));
2074
- }
2075
- async readJson(name) {
2076
- const file = Bun.file(path8.join(this.#outDir, name));
2077
- if (!await file.exists())
2078
- return null;
2079
- return await file.json().catch(() => null);
2080
1859
  }
2081
- async writeJson(name, value) {
2082
- await this.#writeAtomic(name, JSON.stringify(value));
1860
+ get assets() {
1861
+ return [...this.#assets];
2083
1862
  }
2084
- async writeModules(compiled) {
2085
- await mkdir2(path8.join(this.#outDir, "modules"), { recursive: true });
2086
- const helpers = new Map(compiled.flatMap((module) => module.helpers ? [[module.helpers.hash, module.helpers]] : []));
2087
- await Promise.all([
2088
- ...compiled.flatMap((module) => [
2089
- Bun.write(this.#modulePath(module.id, ".js"), module.factory),
2090
- ...module.sourceMap ? [Bun.write(this.#modulePath(module.id, ".js.map"), module.sourceMap)] : []
2091
- ]),
2092
- ...[...helpers.values()].map((shared) => Bun.write(this.#helpersPath(shared.hash), shared.definition))
2093
- ]);
1863
+ async compile(startFiles, known, { refuseNewVendor = false, refusePrepass = false } = {}) {
1864
+ const compiled = new Map;
1865
+ const seen = new Set([...known, ...startFiles]);
1866
+ let frontier = [...new Set(startFiles)];
1867
+ while (frontier.length > 0) {
1868
+ const refusedVendors = refuseNewVendor ? frontier.filter((file) => CsrDevPaths.isVendorFile(file)) : [];
1869
+ if (refusedVendors.length > 0)
1870
+ return { modules: [...compiled.values()], refusedVendors, unresolved: [] };
1871
+ await this.#resolver.cover(frontier);
1872
+ const round = await this.#compileRound(frontier);
1873
+ if (round.misses.length > 0 && !this.#resolver.prepassDone && refusePrepass)
1874
+ return { modules: [...compiled.values()], refusedVendors: [], unresolved: round.misses };
1875
+ if (round.misses.length > 0 && !this.#resolver.prepassDone) {
1876
+ this.#app.verbose(`[csr-dev] ${round.misses.length} unresolved import(s); rerunning the resolution build`);
1877
+ await this.#resolver.prepass();
1878
+ continue;
1879
+ }
1880
+ const next = [];
1881
+ for (const module of round.modules) {
1882
+ compiled.set(module.file, module);
1883
+ for (const dep of module.deps) {
1884
+ if (CsrDevPaths.isStub(dep) || seen.has(dep))
1885
+ continue;
1886
+ seen.add(dep);
1887
+ next.push(dep);
1888
+ }
1889
+ }
1890
+ frontier = next;
1891
+ }
1892
+ const { fallbacks } = this.#resolver;
1893
+ if (fallbacks.size > 0)
1894
+ this.#app.verbose(`[csr-dev] ${fallbacks.size} import(s) outside the browser build's graph resolved with Bun's runtime resolver: ${[...fallbacks].slice(0, 5).join(", ")}${fallbacks.size > 5 ? ", ..." : ""}`);
1895
+ return { modules: [...compiled.values()], refusedVendors: [], unresolved: [] };
2094
1896
  }
2095
- async readCode(graph) {
2096
- const ids = Object.keys(graph.modules).filter((id) => !graph.modules[id]?.vendor && this.hasModule(id));
2097
- const hashes = [...new Set(ids.flatMap((id) => graph.modules[id]?.helpers ?? []))];
2098
- const [modules, helpers] = await Promise.all([
2099
- Promise.all(ids.map(async (id) => [id, await Bun.file(this.#modulePath(id, ".js")).text()])),
2100
- Promise.all(hashes.map(async (hash) => [hash, await Bun.file(this.#helpersPath(hash)).text()]))
1897
+ async#compileRound(files) {
1898
+ const appFiles = files.filter((file) => !CsrDevPaths.isVendorFile(file));
1899
+ const vendorFiles = files.filter((file) => CsrDevPaths.isVendorFile(file));
1900
+ const rounds = await Promise.all([
1901
+ appFiles.length > 0 ? this.#bunBuild(appFiles, false) : null,
1902
+ vendorFiles.length > 0 ? this.#bunBuild(vendorFiles, true) : null
2101
1903
  ]);
2102
- return { modules: new Map(modules), helpers: new Map(helpers) };
2103
- }
2104
- hasModule(id) {
2105
- return fs5.existsSync(this.#modulePath(id, ".js"));
2106
- }
2107
- async forgetModules(ids) {
2108
- await Promise.all(ids.flatMap((id) => [
2109
- CsrDevArtifactWriter.#remove(this.#modulePath(id, ".js")),
2110
- CsrDevArtifactWriter.#remove(this.#modulePath(id, ".js.map"))
2111
- ]));
1904
+ const modules = rounds.flatMap((round) => round?.modules ?? []);
1905
+ const misses = rounds.flatMap((round) => round?.misses ?? []);
1906
+ for (const file of rounds.flatMap((round) => round?.tangled ?? [])) {
1907
+ const single = await this.#bunBuild([file], CsrDevPaths.isVendorFile(file));
1908
+ if (single.tangled.length > 0)
1909
+ throw new Error(`[csr-dev] ${this.#paths.idOf(file)} kept inlining another module`);
1910
+ modules.push(...single.modules);
1911
+ misses.push(...single.misses);
1912
+ }
1913
+ return { modules, misses };
2112
1914
  }
2113
- async writeVendor(graph) {
2114
- const ids = Object.keys(graph.modules).filter((id) => graph.modules[id]?.vendor).sort();
2115
- const stubs = new Set(Object.values(graph.modules).flatMap((module) => module.deps.filter(CsrDevPaths.isStub)));
2116
- const lines = [await this.#helperDefinitions(graph, ids), ...await this.#defineLines(ids)];
2117
- for (const stub of [...stubs].sort()) {
2118
- if (stub.startsWith(CsrDevPaths.emptyStubPrefix)) {
2119
- lines.push(`__akan.define(${JSON.stringify(stub)}, function () {});
2120
- `);
1915
+ async#bunBuild(files, vendor) {
1916
+ const mtimes = new Map(files.map((file) => [CsrDevPaths.realpath(file), CsrDevPaths.mtimeOf(file)]));
1917
+ const hashes = new Map(await Promise.all(files.map(async (file) => [CsrDevPaths.realpath(file), vendor ? "" : await CsrDevPaths.hashOf(file)])));
1918
+ const misses = [];
1919
+ const macroOutputs = vendor ? new Map : await this.#evaluateMacros(files);
1920
+ let result;
1921
+ try {
1922
+ result = await Bun.build({
1923
+ entrypoints: files,
1924
+ root: this.#paths.root,
1925
+ target: "browser",
1926
+ format: "cjs",
1927
+ reactFastRefresh: !vendor,
1928
+ sourcemap: vendor ? "none" : "external",
1929
+ naming: { entry: "[dir]/[name].[ext]", asset: "assets/[name]-[hash].[ext]" },
1930
+ publicPath: this.#routePrefix,
1931
+ metafile: true,
1932
+ define: { ...this.#context.define, "import.meta": "__akanMeta" },
1933
+ optimizeImports: this.#context.optimizeImports,
1934
+ plugins: [this.#registryPlugin(vendor, misses, macroOutputs)]
1935
+ });
1936
+ } catch (error) {
1937
+ if (vendor && error instanceof AggregateError)
1938
+ return await this.#withoutFailed(files, error);
1939
+ throw error;
1940
+ }
1941
+ const { outputs } = result.metafile;
1942
+ const sourceMaps = new Map(result.outputs.filter((artifact) => artifact.kind === "sourcemap").map((artifact) => [artifact.path, artifact]));
1943
+ const modules = [];
1944
+ const tangled = [];
1945
+ for (const artifact of result.outputs) {
1946
+ if (artifact.kind === "asset") {
1947
+ const assetPath = path7.join(this.#outDir, artifact.path);
1948
+ await Bun.write(assetPath, artifact);
1949
+ this.#assets.add(assetPath);
1950
+ continue;
1951
+ }
1952
+ if (artifact.kind !== "entry-point")
1953
+ continue;
1954
+ const meta = outputs[artifact.path];
1955
+ if (!meta?.entryPoint)
1956
+ throw new Error(`[csr-dev] no entry point recorded for ${artifact.path}`);
1957
+ const file = CsrDevPaths.realpath(path7.resolve(meta.entryPoint));
1958
+ const inputs = Object.keys(meta.inputs).map((input) => CsrDevPaths.realpath(path7.resolve(input)));
1959
+ if (inputs.some((input) => input !== file && this.#resolver.isRegistryModule(input))) {
1960
+ tangled.push(file);
2121
1961
  continue;
2122
1962
  }
2123
- const message = `[akan-csr] ${stub.slice(CsrDevPaths.stubPrefix.length)} is a Node built-in the browser does not have`;
2124
- lines.push(`__akan.define(${JSON.stringify(stub)}, function () {
2125
- throw new Error(${JSON.stringify(message)});
2126
- });
2127
- `);
1963
+ const code = await artifact.text();
1964
+ const id = this.#paths.idOf(file);
1965
+ const built = CsrDevModuleCompiler.#factory(id, code);
1966
+ const problem = CsrDevFactoryCheck.problemOf(built.factory, built.helpers?.definition);
1967
+ if (problem && !vendor)
1968
+ throw new Error(`[csr-dev] ${id}: its factory does not parse as a classic script: ${problem}`);
1969
+ if (problem) {
1970
+ modules.push(this.#leftOut(file, problem));
1971
+ continue;
1972
+ }
1973
+ const sourceMap = sourceMaps.get(`${artifact.path}.map`);
1974
+ modules.push({
1975
+ id,
1976
+ file,
1977
+ vendor,
1978
+ ...built,
1979
+ sourceMap: sourceMap ? this.#rebaseSourceMap(await sourceMap.text()) : undefined,
1980
+ deps: this.#emittedDeps(code),
1981
+ mtimeMs: mtimes.get(file) ?? CsrDevPaths.mtimeOf(file),
1982
+ hash: hashes.get(file) ?? ""
1983
+ });
2128
1984
  }
2129
- const code = lines.join("");
2130
- const vendorFile = `vendor-${Bun.hash(code).toString(36)}.js`;
2131
- await this.#writeAtomic(vendorFile, code);
2132
- return vendorFile;
2133
- }
2134
- async writeApp(graph, { generation, vendorFile, epoch }, code) {
2135
- const ids = Object.keys(graph.modules).filter((id) => !graph.modules[id]?.vendor).sort();
2136
- const lines = await this.#defineLines(ids, code);
2137
- const blocks = ids.map((id, index) => [id, lines[index] ?? ""]);
2138
- const start = this.#library ? `__akan.startLibrary(${JSON.stringify({ generation, refresh: graph.refresh, bootstrap: graph.entries[""], vendorFile, epoch })});
2139
- ` : `__akan.start(${JSON.stringify({ generation, refresh: graph.refresh })});
2140
- `;
2141
- await this.#writeWithSourceMap(CSR_DEV_APP_FILE, await this.#helperDefinitions(graph, ids, code), blocks, start);
2142
- }
2143
- async writePatch(generation, modules) {
2144
- const patchFile = `patch-${generation}.js`;
2145
- const blocks = modules.map((module, index) => [module.id, `${JSON.stringify(module.id)}: ${module.factory}${index < modules.length - 1 ? `,
2146
- ` : `
2147
- `}`]);
2148
- const helpers = new Map(modules.flatMap((module) => module.helpers ? [[module.helpers.hash, module.helpers]] : []));
2149
- const header = `${[...helpers.values()].map((shared) => shared.definition).join("")}__akan.update(${generation}, {
2150
- `;
2151
- await this.#writeWithSourceMap(patchFile, header, blocks, `});
2152
- `);
2153
- return `${this.#routePrefix}${patchFile}`;
2154
- }
2155
- async writeState(graph, manifest) {
2156
- await this.writeJson("graph.json", graph);
2157
- await this.writeJson(CSR_DEV_MANIFEST_FILE2, manifest);
1985
+ return { modules, misses, tangled };
2158
1986
  }
2159
- async commitPatch(graph, manifest, announce, { modules, code }) {
2160
- await this.writeJson(CSR_DEV_MANIFEST_FILE2, manifest);
2161
- announce();
2162
- await CsrDevArtifactWriter.#appWriteDelay();
2163
- await this.writeModules(modules);
2164
- await this.writeJson("graph.json", graph);
2165
- await this.writeApp(graph, manifest, code);
2166
- await this.writeJson(CSR_DEV_MANIFEST_FILE2, { ...manifest, appGeneration: manifest.generation });
1987
+ async#withoutFailed(files, error) {
1988
+ const reasons = new Map;
1989
+ for (const message of error.errors) {
1990
+ const file = message.position?.file;
1991
+ if (typeof file !== "string" || !path7.isAbsolute(file))
1992
+ continue;
1993
+ const real = CsrDevPaths.realpath(file);
1994
+ const at = message.position?.line ? `:${message.position.line}` : "";
1995
+ reasons.set(real, [...reasons.get(real) ?? [], `${String(message.message)} (${this.#paths.idOf(real)}${at})`]);
1996
+ }
1997
+ const failed = new Set(files.filter((file) => reasons.has(CsrDevPaths.realpath(file))));
1998
+ if (failed.size === 0)
1999
+ throw error;
2000
+ const rest = files.filter((file) => !failed.has(file));
2001
+ const round = rest.length > 0 ? await this.#bunBuild(rest, true) : { modules: [], misses: [], tangled: [] };
2002
+ for (const file of failed) {
2003
+ const text = (reasons.get(CsrDevPaths.realpath(file)) ?? []).join("; ");
2004
+ const awaits = text.includes('"await" can only be used');
2005
+ round.modules.push(this.#leftOut(file, awaits ? `a top-level await, which a CommonJS factory cannot wait on: ${text}` : text));
2006
+ }
2007
+ return round;
2167
2008
  }
2168
- async healApp(graph, manifest, code) {
2169
- if (appGenerationOf2(manifest) >= manifest.generation)
2170
- return manifest;
2171
- await this.writeApp(graph, manifest, code);
2172
- const healed = { ...manifest, appGeneration: manifest.generation };
2173
- await this.writeJson(CSR_DEV_MANIFEST_FILE2, healed);
2174
- return healed;
2009
+ #leftOut(file, problem) {
2010
+ const real = CsrDevPaths.realpath(file);
2011
+ const id = this.#paths.idOf(real);
2012
+ this.#app.logger.error(`[csr-dev] ${id} is left out of the dev registry: ${problem}`);
2013
+ return {
2014
+ id,
2015
+ file: real,
2016
+ vendor: true,
2017
+ factory: CsrDevFactoryCheck.thrower(id, problem),
2018
+ helpers: null,
2019
+ deps: [],
2020
+ mtimeMs: CsrDevPaths.mtimeOf(real),
2021
+ hash: ""
2022
+ };
2175
2023
  }
2176
- async prune(vendorFile, generation) {
2177
- const names = await readdir2(this.#outDir);
2178
- const stale = names.filter((name) => {
2179
- const patch = /^patch-(\d+)\.js(?:\.layout\.json)?$/.exec(name);
2180
- return patch ? Number(patch[1]) <= generation - CSR_DEV_KEPT_PATCHES : false;
2181
- });
2182
- const vendors = names.filter((name) => /^vendor-[\w-]+\.js$/.test(name) && name !== vendorFile).map((name) => ({ name, mtimeMs: CsrDevPaths.mtimeOf(path8.join(this.#outDir, name)) })).sort((a, b) => b.mtimeMs - a.mtimeMs).slice(CsrDevArtifactWriter.#keptVendors - 1).map(({ name }) => name);
2183
- await Promise.all([...stale, ...vendors].map((name) => CsrDevArtifactWriter.#remove(path8.join(this.#outDir, name))));
2024
+ async#evaluateMacros(files) {
2025
+ const sources = await Promise.all(files.map(async (file) => [file, await Bun.file(file).text()]));
2026
+ const importers = sources.filter(([, source]) => CsrDevModuleCompiler.#macroImport.test(source));
2027
+ const outputs = await Promise.all(importers.map(async ([file]) => {
2028
+ const result = await Bun.build({
2029
+ entrypoints: [file],
2030
+ target: "browser",
2031
+ format: "esm",
2032
+ plugins: [
2033
+ {
2034
+ name: "akan-csr-macro-pass",
2035
+ setup: (build) => void build.onResolve({ filter: /.*/ }, (args) => args.importer ? { path: args.path, external: true } : undefined)
2036
+ }
2037
+ ]
2038
+ });
2039
+ const [output] = result.outputs;
2040
+ if (!output)
2041
+ throw new Error(`[csr-dev] ${this.#paths.idOf(file)}: its macro pass wrote no output`);
2042
+ const text = await output.text();
2043
+ return [
2044
+ [file, text],
2045
+ [CsrDevPaths.realpath(file), text]
2046
+ ];
2047
+ }));
2048
+ return new Map(outputs.flat());
2184
2049
  }
2185
- async pruneAfterFullBuild(graph, vendorFile, { generation, assets }) {
2186
- const kept = new Set(Object.keys(graph.modules).flatMap((id) => [this.#modulePath(id, ".js"), this.#modulePath(id, ".js.map")]));
2187
- for (const module of Object.values(graph.modules))
2188
- if (module.helpers)
2189
- kept.add(this.#helpersPath(module.helpers));
2190
- for (const asset of assets)
2191
- kept.add(path8.resolve(asset));
2192
- const listed = async (dir) => (await readdir2(path8.join(this.#outDir, dir)).catch(() => [])).map((name) => path8.join(this.#outDir, dir, name));
2193
- const stale = [
2194
- ...(await listed("modules")).filter((file) => !kept.has(file)),
2195
- ...(await listed("assets")).filter((file) => !kept.has(file)),
2196
- ...(await listed(".")).filter((file) => {
2197
- const name = path8.basename(file);
2198
- return /^patch-\d+\.js(?:\.layout\.json)?$/.test(name) || name.endsWith(".tmp");
2199
- }),
2200
- path8.join(this.#outDir, CSR_DEV_PATCHING_MARKER)
2201
- ];
2202
- await Promise.all(stale.map((file) => CsrDevArtifactWriter.#remove(file)));
2203
- await this.prune(vendorFile, generation);
2050
+ #registryPlugin(vendor, misses, macroOutputs) {
2051
+ return {
2052
+ name: "akan-csr-registry",
2053
+ setup: (build) => {
2054
+ build.onLoad({ filter: /\.css$/ }, () => ({ contents: "", loader: "js" }));
2055
+ if (macroOutputs.size > 0) {
2056
+ const exact = [...macroOutputs.keys()].map((file) => file.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
2057
+ build.onLoad({ filter: new RegExp(`^(?:${exact.join("|")})$`) }, (args) => {
2058
+ const contents = macroOutputs.get(args.path);
2059
+ return contents === undefined ? undefined : { contents, loader: "js" };
2060
+ });
2061
+ }
2062
+ if (!vendor)
2063
+ build.onLoad({ filter: /\.tsx$/ }, async (args) => {
2064
+ if (CsrDevPaths.isVendorFile(args.path))
2065
+ return;
2066
+ const normalized = RouteClientBuilder.normalizeNamedDefaultFunctionForFastRefresh(await Bun.file(args.path).text(), { path: args.path });
2067
+ return normalized ? { contents: normalized, loader: "tsx" } : undefined;
2068
+ });
2069
+ build.onResolve({ filter: /.*/ }, (args) => {
2070
+ if (args.kind === "entry-point-build" || !args.importer)
2071
+ return;
2072
+ if (this.#context.externals?.includes(args.path))
2073
+ return { path: `${CsrDevPaths.vendorPrefix}${args.path}`, external: true };
2074
+ const importer = CsrDevPaths.realpath(args.importer);
2075
+ const target = this.#resolver.resolve(importer, args.path, args.kind === "require-call" || args.kind === "require-resolve" ? "require" : "import");
2076
+ if (target === CsrDevResolver.inline)
2077
+ return;
2078
+ if (target === null) {
2079
+ misses.push(`${args.path} from ${this.#paths.idOf(importer)}`);
2080
+ return { path: `akan-miss:${args.path}`, external: true };
2081
+ }
2082
+ const external = CsrDevPaths.isStub(target) ? target : `${CsrDevPaths.modulePrefix}${this.#paths.idOf(target)}`;
2083
+ return { path: external, external: true };
2084
+ });
2085
+ }
2086
+ };
2204
2087
  }
2205
- async#helperDefinitions(graph, ids, code) {
2206
- const hashes = [...new Set(ids.flatMap((id) => graph.modules[id]?.helpers ?? []))].sort();
2207
- return (await Promise.all(hashes.map(async (hash) => code?.helpers.get(hash) ?? Bun.file(this.#helpersPath(hash)).text()))).join("");
2088
+ #rebaseSourceMap(text) {
2089
+ const map = JSON.parse(text);
2090
+ const files = map.sources.map((source) => path7.resolve(source));
2091
+ map.sources = files.map((file) => `/${this.#paths.idOf(file)}`);
2092
+ map.sourcesContent = files.map((file, index) => CsrDevPaths.isVendorFile(file) ? null : map.sourcesContent?.[index] ?? null);
2093
+ return JSON.stringify(map);
2208
2094
  }
2209
- async#defineLines(ids, code) {
2210
- return await Promise.all(ids.map(async (id) => {
2211
- const factory = code?.modules.get(id) ?? await Bun.file(this.#modulePath(id, ".js")).text();
2212
- return `__akan.define(${JSON.stringify(id)}, ${factory});
2213
- `;
2214
- }));
2095
+ #emittedDeps(code) {
2096
+ const deps = new Set;
2097
+ const references = /\b(?:require|import|__akanImport)\("((?:akan-module|stub):[^"]+)"\)/g;
2098
+ for (const [, reference] of code.matchAll(references)) {
2099
+ if (!reference)
2100
+ continue;
2101
+ deps.add(CsrDevPaths.isStub(reference) ? reference : this.#paths.fileOf(reference.slice(CsrDevPaths.modulePrefix.length)));
2102
+ }
2103
+ return [...deps];
2215
2104
  }
2216
- async#writeWithSourceMap(name, header, blocks, footer) {
2217
- const layout = { lineCount: 0, modules: [] };
2218
- let line = CsrDevPaths.lineCount(header);
2219
- for (const [id, text] of blocks) {
2220
- layout.modules.push([id, line + 1]);
2221
- line += CsrDevPaths.lineCount(text);
2105
+ static #factory(id, code) {
2106
+ const prefix = CsrDevPaths.modulePrefix;
2107
+ let patched = code.replace(/^#!/, "//").replaceAll(`import("${prefix}`, `__akanImport("${prefix}`).replace(/^\/\/# debugId=.*$/m, "");
2108
+ const marker = patched.search(/^\/\/ /m);
2109
+ const body = marker < 0 ? "" : patched.slice(marker);
2110
+ for (const [name, params, method] of CsrDevModuleCompiler.#helperPatches) {
2111
+ const definition = `var ${name} = ${params} => {`;
2112
+ if (patched.includes(definition))
2113
+ patched = patched.replace(definition, `var ${name} = __akan.${method}, __bun${name} = ${params} => {`);
2114
+ else if (patched.includes(`${name}(`) && !new RegExp(`\\b(?:var|let|const|function)\\s+${name}\\b`).test(body))
2115
+ throw new Error(`[csr-dev] ${id}: Bun's ${name} helper no longer reads "${definition}"; CsrDevModuleCompiler must follow`);
2222
2116
  }
2223
- const code = `${header}${blocks.map(([, text]) => text).join("")}${footer}//# sourceMappingURL=${name}.map
2224
- `;
2225
- layout.lineCount = CsrDevPaths.lineCount(code);
2226
- await this.writeJson(`${name}.layout.json`, layout);
2227
- await this.#writeAtomic(name, code);
2117
+ if (CsrDevModuleCompiler.#storeRoot.test(id))
2118
+ patched += `
2119
+ module.hot.accept();`;
2120
+ const shared = CsrDevModuleCompiler.#shareHelpers(patched);
2121
+ const meta = /(?<![\w$.])__akanMeta(?![\w$])/.test(patched) ? ` var __akanMeta = { url: new URL(${JSON.stringify(id.startsWith("/") ? id : `/${id}`)}, self.location.href).href };` : "";
2122
+ return {
2123
+ factory: `function (require, module, exports, $RefreshReg$, $RefreshSig$, __akanImport) {${meta}
2124
+ ${shared?.code ?? patched}
2125
+ }`,
2126
+ helpers: shared?.helpers ?? null
2127
+ };
2228
2128
  }
2229
- async#writeAtomic(name, content) {
2230
- const target = path8.join(this.#outDir, name);
2231
- const temp = `${target}.${process.pid}.tmp`;
2232
- await Bun.write(temp, content);
2233
- await FileSys2.replace(temp, target);
2129
+ static #shareHelpers(code) {
2130
+ const marker = code.search(/^\/\/ /m);
2131
+ if (marker <= 0)
2132
+ return null;
2133
+ const preamble = code.slice(0, marker);
2134
+ const names = [...preamble.matchAll(/^(?:var|let|const|function)\s+([\w$]+)/gm)].map(([, name]) => name).filter((name) => !!name);
2135
+ if (names.length === 0 || CsrDevModuleCompiler.#factoryArgument.test(preamble))
2136
+ return null;
2137
+ const hash = Bun.hash(preamble).toString(36);
2138
+ const binding = `var { ${names.join(", ")} } = __akan.helpers(${JSON.stringify(hash)});`;
2139
+ return {
2140
+ code: `${binding}${`
2141
+ `.repeat(CsrDevPaths.lineCount(preamble))}${code.slice(marker)}`,
2142
+ helpers: {
2143
+ hash,
2144
+ definition: `__akan.defineHelpers(${JSON.stringify(hash)}, function () {
2145
+ ${preamble}return { ${names.join(", ")} };
2146
+ });
2147
+ `
2148
+ }
2149
+ };
2234
2150
  }
2235
- static async#remove(file) {
2236
- await rm(file, { force: true }).catch(() => {
2237
- return;
2238
- });
2151
+ }
2152
+
2153
+ // pkgs/@akanjs/devkit/frontendBuild/csrDevPatcher.ts
2154
+ import fs5 from "fs";
2155
+ import path8 from "path";
2156
+ import { appGenerationOf as appGenerationOf2, CSR_DEV_MANIFEST_FILE as CSR_DEV_MANIFEST_FILE2 } from "akanjs/server/hmr/csrDevManifest";
2157
+ class CsrDevPatcher {
2158
+ #bundler;
2159
+ #resident;
2160
+ #state = null;
2161
+ #delegated = new Map;
2162
+ constructor(bundler, { resident = false } = {}) {
2163
+ this.#bundler = bundler;
2164
+ this.#resident = resident;
2239
2165
  }
2240
- static async#appWriteDelay() {
2241
- const ms = Number(process.env.AKAN_CSR_DEV_APP_WRITE_DELAY_MS);
2242
- if (Number.isInteger(ms) && ms > 0)
2243
- await Bun.sleep(ms);
2166
+ forget() {
2167
+ this.#state = null;
2244
2168
  }
2245
- #modulePath(id, extension) {
2246
- return path8.join(this.#outDir, csrDevModuleFile(id, extension));
2169
+ releaseHandedOver() {
2170
+ for (const [id, handedOver] of this.#delegated)
2171
+ if (!handedOver.failed)
2172
+ this.#delegated.delete(id);
2247
2173
  }
2248
- #helpersPath(hash) {
2249
- return path8.join(this.#outDir, "modules", `helpers-${hash}.js`);
2174
+ async update(changedFiles = [], { announce, roots = [], onlyRoots = false, allowWholeAppBuilds = false } = {}) {
2175
+ const context = await this.#bundler.context();
2176
+ if (!context)
2177
+ return { kind: "unchanged" };
2178
+ const resident = this.#state;
2179
+ const state = resident ?? await this.#load();
2180
+ if (!state) {
2181
+ const manifest = await this.#bundler.writer.readJson(CSR_DEV_MANIFEST_FILE2);
2182
+ const reason = !manifest ? "first build" : await this.#bundler.writer.isBuilding() ? "the last whole build was cut short" : "the dev bundle config changed";
2183
+ return this.#handBack(reason, (manifest?.generation ?? 0) + 1, context, { first: !manifest });
2184
+ }
2185
+ const generation = state.manifest.generation + 1;
2186
+ if (state.graph.configKey !== context.configKey)
2187
+ return this.#handBack("the dev bundle config changed", generation, context);
2188
+ const metadataFile = changedFiles.find((file) => this.#bundler.isMetadataFile(file));
2189
+ if (metadataFile)
2190
+ return this.#handBack(`${path8.basename(metadataFile)} changed`, generation, context);
2191
+ if (!resident && state.graph.metadata !== await this.#bundler.metadataFingerprint())
2192
+ return this.#handBack("signal or dictionary metadata changed since the registry was built", generation, context);
2193
+ this.#state = this.#resident ? state : null;
2194
+ const healable = appGenerationOf2(state.manifest) < state.manifest.generation && this.#withoutFactory(state, (file) => fs5.existsSync(file)).length === 0;
2195
+ if (healable)
2196
+ state.manifest = await this.#bundler.writer.healApp(state.graph, state.manifest, await this.#code(state));
2197
+ return await this.#patch(context, state, changedFiles, generation, {
2198
+ announce,
2199
+ roots,
2200
+ onlyRoots,
2201
+ allowWholeAppBuilds
2202
+ });
2250
2203
  }
2251
- }
2252
-
2253
- // pkgs/@akanjs/devkit/frontendBuild/csrDevModuleCompiler.ts
2254
- import path9 from "path";
2255
-
2256
- // pkgs/@akanjs/devkit/frontendBuild/csrDevFactoryCheck.ts
2257
- class CsrDevFactoryCheck {
2258
- static #parsed = new Set;
2259
- static problemOf(factory, helperDefinition = "") {
2260
- const key = Bun.hash(`${helperDefinition}\x00${factory}`);
2261
- if (CsrDevFactoryCheck.#parsed.has(key))
2262
- return null;
2263
- const problem = CsrDevFactoryCheck.#syntaxError(`${helperDefinition}return (${factory});`);
2264
- if (problem === null) {
2265
- CsrDevFactoryCheck.#parsed.add(key);
2266
- return null;
2204
+ async#patch(context, state, changedFiles, generation, { announce, roots = [], onlyRoots = false, allowWholeAppBuilds }) {
2205
+ const { paths, writer } = this.#bundler;
2206
+ const { graph } = state;
2207
+ const entries = await this.#bundler.writeEntries(context);
2208
+ const entryIds = this.#bundler.entryIds(entries.files);
2209
+ const present = CsrDevPatcher.#presence(changedFiles);
2210
+ const forgotten = this.#forgetDeletedModules(state, present);
2211
+ const changed = onlyRoots ? new Set : await this.#changedModules(graph, changedFiles, present);
2212
+ for (const id of this.#withoutFactory(state, present))
2213
+ changed.add(id);
2214
+ for (const file of entries.changed)
2215
+ changed.add(paths.idOf(file));
2216
+ const rootFiles = [...Object.values(entries.files), ...roots.filter((file) => fs5.existsSync(file))];
2217
+ for (const file of rootFiles) {
2218
+ const id = paths.idOf(file);
2219
+ if (!graph.modules[id] && !(onlyRoots && await this.#parked(graph, id)))
2220
+ changed.add(id);
2267
2221
  }
2268
- if (problem.includes("import.meta")) {
2269
- const line = factory.split(`
2270
- `).findIndex((text) => /(?<![\w$.])import\.meta\b/.test(text));
2271
- return `import.meta is left in it${line > 0 ? ` (compiled line ${line})` : ""}, which a classic script cannot hold: ${problem}`;
2222
+ const carried = onlyRoots ? [] : await this.#carriedRoots(graph, changed, present);
2223
+ const routesMoved = this.#bundler.reloadsOnEntryChange && (entries.changed.length > 0 || JSON.stringify(entryIds) !== JSON.stringify(graph.entries));
2224
+ if (changed.size === 0) {
2225
+ if (carried.length === 0)
2226
+ return { kind: "unchanged" };
2227
+ return await this.#retryCarried(context, state, carried, { announce, allowWholeAppBuilds });
2272
2228
  }
2273
- if (CsrDevFactoryCheck.#syntaxError(`${helperDefinition}return (async ${factory});`) === null)
2274
- return `a top-level await is left in it, which a CommonJS factory cannot wait on: ${problem}`;
2275
- return problem;
2276
- }
2277
- static thrower(id, problem) {
2278
- const message = `[akan-csr] ${id} was left out of the dev registry: ${problem}`;
2279
- return `function () {
2280
- throw new Error(${JSON.stringify(message)});
2281
- }`;
2282
- }
2283
- static #syntaxError(source) {
2229
+ const resolver = new CsrDevResolver({
2230
+ paths,
2231
+ context,
2232
+ entryFiles: rootFiles.map((file) => CsrDevPaths.realpath(file)),
2233
+ resolution: graph.resolution,
2234
+ runtimeResolved: graph.runtimeResolved
2235
+ });
2236
+ const known = new Set(Object.keys(graph.modules).map((id) => paths.fileOf(id)));
2237
+ const compile = async (ids) => await this.#bundler.compiler(context, resolver).compile(ids.map((id) => paths.fileOf(id)), known, { refuseNewVendor: !allowWholeAppBuilds, refusePrepass: !allowWholeAppBuilds });
2238
+ let result;
2239
+ let orphans;
2284
2240
  try {
2285
- new Function(source);
2286
- return null;
2241
+ ({ result, orphans } = await this.#compileLeavingOrphans(compile, graph, changed));
2242
+ for (const module of result.modules)
2243
+ known.add(module.file);
2244
+ result = await this.#withTwinImporters(compile, graph, result);
2287
2245
  } catch (error) {
2288
- return error instanceof Error ? error.message : String(error);
2246
+ graph.pending = [...new Set([...graph.pending, ...changed, ...carried])];
2247
+ if (!this.#resident)
2248
+ graph.failed = {
2249
+ roots: [...changed, ...carried],
2250
+ files: await CsrDevPatcher.#hashesOf(paths, CsrDevPatcher.#implicatedFiles(error))
2251
+ };
2252
+ await writer.writeJson("graph.json", graph);
2253
+ throw error;
2289
2254
  }
2290
- }
2291
- }
2292
-
2293
- // pkgs/@akanjs/devkit/frontendBuild/csrDevModuleCompiler.ts
2294
- class CsrDevModuleCompiler {
2295
- static #storeRoot = /(?:^|\/)lib\/st\.ts$/;
2296
- static #macroImport = /\b(?:with|assert)\s*\{\s*type\s*:\s*["']macro["']/;
2297
- static #factoryArgument = /(?<![\w$.])(?:require|module|exports|__akanImport|\$RefreshReg\$|\$RefreshSig\$)(?![\w$]|\s*:)/;
2298
- static #helperPatches = [
2299
- ["__toESM", "(mod, isNodeMode, target)", "toESM"],
2300
- ["__reExport", "(target, mod, secondTarget)", "reExport"]
2301
- ];
2302
- #app;
2303
- #paths;
2304
- #resolver;
2305
- #context;
2306
- #outDir;
2307
- #routePrefix;
2308
- #assets = new Set;
2309
- constructor({ app, paths, resolver, context, outDir, routePrefix }) {
2310
- this.#app = app;
2311
- this.#paths = paths;
2312
- this.#resolver = resolver;
2313
- this.#context = context;
2314
- this.#outDir = outDir;
2315
- this.#routePrefix = routePrefix;
2316
- }
2317
- get assets() {
2318
- return [...this.#assets];
2319
- }
2320
- async compile(startFiles, known, { refuseNewVendor = false, refusePrepass = false } = {}) {
2321
- const compiled = new Map;
2322
- const seen = new Set([...known, ...startFiles]);
2323
- let frontier = [...new Set(startFiles)];
2324
- while (frontier.length > 0) {
2325
- const refusedVendors = refuseNewVendor ? frontier.filter((file) => CsrDevPaths.isVendorFile(file)) : [];
2326
- if (refusedVendors.length > 0)
2327
- return { modules: [...compiled.values()], refusedVendors, unresolved: [] };
2328
- await this.#resolver.cover(frontier);
2329
- const round = await this.#compileRound(frontier);
2330
- if (round.misses.length > 0 && !this.#resolver.prepassDone && refusePrepass)
2331
- return { modules: [...compiled.values()], refusedVendors: [], unresolved: round.misses };
2332
- if (round.misses.length > 0 && !this.#resolver.prepassDone) {
2333
- this.#app.verbose(`[csr-dev] ${round.misses.length} unresolved import(s); rerunning the resolution build`);
2334
- await this.#resolver.prepass();
2335
- continue;
2336
- }
2337
- const next = [];
2338
- for (const module of round.modules) {
2339
- compiled.set(module.file, module);
2340
- for (const dep of module.deps) {
2341
- if (CsrDevPaths.isStub(dep) || seen.has(dep))
2342
- continue;
2343
- seen.add(dep);
2344
- next.push(dep);
2345
- }
2255
+ const refusal = result.refusedVendors.length > 0 ? "an npm module joined the graph" : result.unresolved.length > 0 ? `a new import needs the resolution build: ${result.unresolved[0]}` : null;
2256
+ if (refusal) {
2257
+ if (routesMoved)
2258
+ await writer.writeJson("graph.json", { ...graph, entries: {} });
2259
+ if (onlyRoots) {
2260
+ for (const id of changed)
2261
+ if (!graph.modules[id])
2262
+ this.#delegated.set(id, {
2263
+ hash: await CsrDevPaths.hashOf(paths.fileOf(id)),
2264
+ before: JSON.stringify(graph.failed ?? null)
2265
+ });
2346
2266
  }
2347
- frontier = next;
2267
+ return this.#handBack(refusal, generation, context);
2268
+ }
2269
+ const compiled = result.modules;
2270
+ const vendorJoined = compiled.some((module) => module.vendor && !graph.modules[module.id]);
2271
+ const code = await this.#code(state);
2272
+ for (const id of [...orphans, ...this.#caseTwins(graph, compiled)]) {
2273
+ delete graph.modules[id];
2274
+ resolver.forget(paths.fileOf(id));
2275
+ code?.modules.delete(id);
2276
+ forgotten.push(id);
2277
+ }
2278
+ graph.pending = graph.pending.filter((id) => !orphans.includes(id));
2279
+ this.#bundler.merge(graph, resolver, compiled);
2280
+ if (!this.#resident)
2281
+ delete graph.failed;
2282
+ for (const module of compiled)
2283
+ this.#delegated.delete(module.id);
2284
+ graph.entries = entryIds;
2285
+ for (const module of compiled) {
2286
+ if (module.vendor || !code)
2287
+ continue;
2288
+ code.modules.set(module.id, module.factory);
2289
+ if (module.helpers)
2290
+ code.helpers.set(module.helpers.hash, module.helpers.definition);
2291
+ }
2292
+ const changedIds = compiled.filter((module) => !module.vendor).map((module) => module.id);
2293
+ const constantId = changedIds.find((id) => id.endsWith(".constant.ts"));
2294
+ const reason = vendorJoined ? "an npm module joined the graph" : routesMoved ? "the route table changed" : constantId ? `${path8.basename(constantId)} changed` : undefined;
2295
+ if (compiled.length === 0 && !reason) {
2296
+ await writer.writeJson("graph.json", graph);
2297
+ await writer.forgetModules(forgotten);
2298
+ if (carried.length === 0)
2299
+ return { kind: "unchanged" };
2300
+ return await this.#retryCarried(context, state, carried, { announce, allowWholeAppBuilds });
2301
+ }
2302
+ const previous = state.manifest;
2303
+ const next = { ...previous, generation, entries: entryIds };
2304
+ const update = (patchUrl) => ({
2305
+ generation,
2306
+ reload: !!reason,
2307
+ reason,
2308
+ patchUrl,
2309
+ changedIds,
2310
+ moduleCount: Object.keys(graph.modules).length,
2311
+ epoch: previous.epoch
2312
+ });
2313
+ let outcome;
2314
+ if (reason) {
2315
+ await writer.forgetPatch(generation);
2316
+ await writer.writeModules(compiled);
2317
+ const vendorFile = vendorJoined ? await writer.writeVendor(graph) : previous.vendorFile;
2318
+ await writer.writeApp(graph, { generation, vendorFile, epoch: previous.epoch }, code);
2319
+ state.manifest = { ...next, vendorFile, appGeneration: generation };
2320
+ await writer.writeState(graph, state.manifest);
2321
+ await writer.prune(vendorFile, generation);
2322
+ const reloaded = update();
2323
+ announce?.(reloaded);
2324
+ outcome = { kind: "update", update: reloaded };
2325
+ } else {
2326
+ const patched = update(await writer.writePatch(generation, compiled.filter((module) => !module.vendor)));
2327
+ await writer.commitPatch(graph, { ...next, appGeneration: appGenerationOf2(previous) }, () => announce?.(patched), { modules: compiled, code });
2328
+ state.manifest = { ...next, appGeneration: generation };
2329
+ await writer.prune(previous.vendorFile, generation);
2330
+ outcome = { kind: "update", update: patched };
2348
2331
  }
2349
- const { fallbacks } = this.#resolver;
2350
- if (fallbacks.size > 0)
2351
- this.#app.verbose(`[csr-dev] ${fallbacks.size} import(s) outside the browser build's graph resolved with Bun's runtime resolver: ${[...fallbacks].slice(0, 5).join(", ")}${fallbacks.size > 5 ? ", ..." : ""}`);
2352
- return { modules: [...compiled.values()], refusedVendors: [], unresolved: [] };
2332
+ await writer.forgetModules(forgotten);
2333
+ if (carried.length === 0)
2334
+ return outcome;
2335
+ const retried = await this.#retryCarried(context, state, carried, { announce, allowWholeAppBuilds });
2336
+ return retried.kind === "delegate" ? retried : outcome;
2353
2337
  }
2354
- async#compileRound(files) {
2355
- const appFiles = files.filter((file) => !CsrDevPaths.isVendorFile(file));
2356
- const vendorFiles = files.filter((file) => CsrDevPaths.isVendorFile(file));
2357
- const rounds = await Promise.all([
2358
- appFiles.length > 0 ? this.#bunBuild(appFiles, false) : null,
2359
- vendorFiles.length > 0 ? this.#bunBuild(vendorFiles, true) : null
2360
- ]);
2361
- const modules = rounds.flatMap((round) => round?.modules ?? []);
2362
- const misses = rounds.flatMap((round) => round?.misses ?? []);
2363
- for (const file of rounds.flatMap((round) => round?.tangled ?? [])) {
2364
- const single = await this.#bunBuild([file], CsrDevPaths.isVendorFile(file));
2365
- if (single.tangled.length > 0)
2366
- throw new Error(`[csr-dev] ${this.#paths.idOf(file)} kept inlining another module`);
2367
- modules.push(...single.modules);
2368
- misses.push(...single.misses);
2338
+ async#retryCarried(context, state, carried, { announce, allowWholeAppBuilds }) {
2339
+ const { paths, writer } = this.#bundler;
2340
+ const retried = await this.#patch(context, state, [], state.manifest.generation + 1, {
2341
+ announce,
2342
+ roots: carried.map((id) => paths.fileOf(id)),
2343
+ onlyRoots: true,
2344
+ allowWholeAppBuilds
2345
+ });
2346
+ if (retried.kind === "delegate") {
2347
+ state.graph.pending = [...new Set([...state.graph.pending, ...carried])];
2348
+ await writer.writeJson("graph.json", state.graph);
2369
2349
  }
2370
- return { modules, misses };
2350
+ return retried;
2371
2351
  }
2372
- async#bunBuild(files, vendor) {
2373
- const mtimes = new Map(files.map((file) => [CsrDevPaths.realpath(file), CsrDevPaths.mtimeOf(file)]));
2374
- const hashes = new Map(await Promise.all(files.map(async (file) => [CsrDevPaths.realpath(file), vendor ? "" : await CsrDevPaths.hashOf(file)])));
2375
- const misses = [];
2376
- const macroOutputs = vendor ? new Map : await this.#evaluateMacros(files);
2377
- let result;
2352
+ async#compileLeavingOrphans(compile, graph, changed) {
2353
+ const { paths } = this.#bundler;
2378
2354
  try {
2379
- result = await Bun.build({
2380
- entrypoints: files,
2381
- root: this.#paths.root,
2382
- target: "browser",
2383
- format: "cjs",
2384
- reactFastRefresh: !vendor,
2385
- sourcemap: vendor ? "none" : "external",
2386
- naming: { entry: "[dir]/[name].[ext]", asset: "assets/[name]-[hash].[ext]" },
2387
- publicPath: this.#routePrefix,
2388
- metafile: true,
2389
- define: { ...this.#context.define, "import.meta": "__akanMeta" },
2390
- optimizeImports: this.#context.optimizeImports,
2391
- plugins: [this.#registryPlugin(vendor, misses, macroOutputs)]
2392
- });
2355
+ return { result: await compile([...changed]), orphans: [] };
2393
2356
  } catch (error) {
2394
- if (vendor && error instanceof AggregateError)
2395
- return await this.#withoutFailed(files, error);
2396
- throw error;
2397
- }
2398
- const { outputs } = result.metafile;
2399
- const sourceMaps = new Map(result.outputs.filter((artifact) => artifact.kind === "sourcemap").map((artifact) => [artifact.path, artifact]));
2400
- const modules = [];
2401
- const tangled = [];
2402
- for (const artifact of result.outputs) {
2403
- if (artifact.kind === "asset") {
2404
- const assetPath = path9.join(this.#outDir, artifact.path);
2405
- await Bun.write(assetPath, artifact);
2406
- this.#assets.add(assetPath);
2407
- continue;
2408
- }
2409
- if (artifact.kind !== "entry-point")
2410
- continue;
2411
- const meta = outputs[artifact.path];
2412
- if (!meta?.entryPoint)
2413
- throw new Error(`[csr-dev] no entry point recorded for ${artifact.path}`);
2414
- const file = CsrDevPaths.realpath(path9.resolve(meta.entryPoint));
2415
- const inputs = Object.keys(meta.inputs).map((input) => CsrDevPaths.realpath(path9.resolve(input)));
2416
- if (inputs.some((input) => input !== file && this.#resolver.isRegistryModule(input))) {
2417
- tangled.push(file);
2418
- continue;
2419
- }
2420
- const code = await artifact.text();
2421
- const id = this.#paths.idOf(file);
2422
- const built = CsrDevModuleCompiler.#factory(id, code);
2423
- const problem = CsrDevFactoryCheck.problemOf(built.factory, built.helpers?.definition);
2424
- if (problem && !vendor)
2425
- throw new Error(`[csr-dev] ${id}: its factory does not parse as a classic script: ${problem}`);
2426
- if (problem) {
2427
- modules.push(this.#leftOut(file, problem));
2428
- continue;
2429
- }
2430
- const sourceMap = sourceMaps.get(`${artifact.path}.map`);
2431
- modules.push({
2432
- id,
2433
- file,
2434
- vendor,
2435
- ...built,
2436
- sourceMap: sourceMap ? this.#rebaseSourceMap(await sourceMap.text()) : undefined,
2437
- deps: this.#emittedDeps(code),
2438
- mtimeMs: mtimes.get(file) ?? CsrDevPaths.mtimeOf(file),
2439
- hash: hashes.get(file) ?? ""
2357
+ const failing = new Set(CsrDevPatcher.#failingFiles(error).map((file) => paths.idOf(file)));
2358
+ const broken = [...changed].filter((id) => failing.has(id) && graph.modules[id]);
2359
+ if (broken.length === 0)
2360
+ throw error;
2361
+ const rest = [...changed].filter((id) => !broken.includes(id));
2362
+ const empty = { modules: [], refusedVendors: [], unresolved: [] };
2363
+ const result = rest.length === 0 ? empty : await compile(rest).catch(() => {
2364
+ throw error;
2440
2365
  });
2366
+ const modules = { ...graph.modules };
2367
+ for (const module of result.modules)
2368
+ modules[module.id] = {
2369
+ vendor: module.vendor,
2370
+ mtimeMs: module.mtimeMs,
2371
+ hash: module.hash,
2372
+ deps: module.deps.map((dep) => CsrDevPaths.isStub(dep) ? dep : paths.idOf(dep))
2373
+ };
2374
+ const reached = await this.#bundler.reachable({ ...graph, modules });
2375
+ if (broken.some((id) => reached.has(id)))
2376
+ throw error;
2377
+ const orphans = Object.keys(modules).filter((id) => !reached.has(id) && !modules[id]?.vendor);
2378
+ return { result, orphans };
2441
2379
  }
2442
- return { modules, misses, tangled };
2443
- }
2444
- async#withoutFailed(files, error) {
2445
- const reasons = new Map;
2446
- for (const message of error.errors) {
2447
- const file = message.position?.file;
2448
- if (typeof file !== "string" || !path9.isAbsolute(file))
2449
- continue;
2450
- const real = CsrDevPaths.realpath(file);
2451
- const at = message.position?.line ? `:${message.position.line}` : "";
2452
- reasons.set(real, [...reasons.get(real) ?? [], `${String(message.message)} (${this.#paths.idOf(real)}${at})`]);
2453
- }
2454
- const failed = new Set(files.filter((file) => reasons.has(CsrDevPaths.realpath(file))));
2455
- if (failed.size === 0)
2456
- throw error;
2457
- const rest = files.filter((file) => !failed.has(file));
2458
- const round = rest.length > 0 ? await this.#bunBuild(rest, true) : { modules: [], misses: [], tangled: [] };
2459
- for (const file of failed) {
2460
- const text = (reasons.get(CsrDevPaths.realpath(file)) ?? []).join("; ");
2461
- const awaits = text.includes('"await" can only be used');
2462
- round.modules.push(this.#leftOut(file, awaits ? `a top-level await, which a CommonJS factory cannot wait on: ${text}` : text));
2463
- }
2464
- return round;
2465
- }
2466
- #leftOut(file, problem) {
2467
- const real = CsrDevPaths.realpath(file);
2468
- const id = this.#paths.idOf(real);
2469
- this.#app.logger.error(`[csr-dev] ${id} is left out of the dev registry: ${problem}`);
2470
- return {
2471
- id,
2472
- file: real,
2473
- vendor: true,
2474
- factory: CsrDevFactoryCheck.thrower(id, problem),
2475
- helpers: null,
2476
- deps: [],
2477
- mtimeMs: CsrDevPaths.mtimeOf(real),
2478
- hash: ""
2479
- };
2480
2380
  }
2481
- async#evaluateMacros(files) {
2482
- const sources = await Promise.all(files.map(async (file) => [file, await Bun.file(file).text()]));
2483
- const importers = sources.filter(([, source]) => CsrDevModuleCompiler.#macroImport.test(source));
2484
- const outputs = await Promise.all(importers.map(async ([file]) => {
2485
- const result = await Bun.build({
2486
- entrypoints: [file],
2487
- target: "browser",
2488
- format: "esm",
2489
- plugins: [
2490
- {
2491
- name: "akan-csr-macro-pass",
2492
- setup: (build) => void build.onResolve({ filter: /.*/ }, (args) => args.importer ? { path: args.path, external: true } : undefined)
2493
- }
2494
- ]
2495
- });
2496
- const [output] = result.outputs;
2497
- if (!output)
2498
- throw new Error(`[csr-dev] ${this.#paths.idOf(file)}: its macro pass wrote no output`);
2499
- const text = await output.text();
2500
- return [
2501
- [file, text],
2502
- [CsrDevPaths.realpath(file), text]
2503
- ];
2504
- }));
2505
- return new Map(outputs.flat());
2381
+ #caseTwins(graph, compiled) {
2382
+ const { paths } = this.#bundler;
2383
+ const idsByFold = new Map;
2384
+ for (const id of Object.keys(graph.modules))
2385
+ idsByFold.set(id.toLowerCase(), [...idsByFold.get(id.toLowerCase()) ?? [], id]);
2386
+ return compiled.flatMap((module) => (idsByFold.get(module.id.toLowerCase()) ?? []).filter((id) => id !== module.id && !graph.modules[id]?.vendor && !CsrDevPaths.isNamed(paths.fileOf(id))));
2506
2387
  }
2507
- #registryPlugin(vendor, misses, macroOutputs) {
2388
+ async#withTwinImporters(compile, graph, result) {
2389
+ const twins = new Set(this.#caseTwins(graph, result.modules));
2390
+ if (twins.size === 0)
2391
+ return result;
2392
+ const compiledIds = new Set(result.modules.map((module) => module.id));
2393
+ const importers = Object.entries(graph.modules).filter(([id, module]) => !module.vendor && !compiledIds.has(id) && !twins.has(id)).filter(([, module]) => module.deps.some((dep) => twins.has(dep))).map(([id]) => id);
2394
+ if (importers.length === 0)
2395
+ return result;
2396
+ const more = await compile(importers);
2508
2397
  return {
2509
- name: "akan-csr-registry",
2510
- setup: (build) => {
2511
- build.onLoad({ filter: /\.css$/ }, () => ({ contents: "", loader: "js" }));
2512
- if (macroOutputs.size > 0) {
2513
- const exact = [...macroOutputs.keys()].map((file) => file.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
2514
- build.onLoad({ filter: new RegExp(`^(?:${exact.join("|")})$`) }, (args) => {
2515
- const contents = macroOutputs.get(args.path);
2516
- return contents === undefined ? undefined : { contents, loader: "js" };
2517
- });
2518
- }
2519
- if (!vendor)
2520
- build.onLoad({ filter: /\.tsx$/ }, async (args) => {
2521
- if (CsrDevPaths.isVendorFile(args.path))
2522
- return;
2523
- const normalized = RouteClientBuilder.normalizeNamedDefaultFunctionForFastRefresh(await Bun.file(args.path).text(), { path: args.path });
2524
- return normalized ? { contents: normalized, loader: "tsx" } : undefined;
2525
- });
2526
- build.onResolve({ filter: /.*/ }, (args) => {
2527
- if (args.kind === "entry-point-build" || !args.importer)
2528
- return;
2529
- if (this.#context.externals?.includes(args.path))
2530
- return { path: `${CsrDevPaths.vendorPrefix}${args.path}`, external: true };
2531
- const importer = CsrDevPaths.realpath(args.importer);
2532
- const target = this.#resolver.resolve(importer, args.path, args.kind === "require-call" || args.kind === "require-resolve" ? "require" : "import");
2533
- if (target === CsrDevResolver.inline)
2534
- return;
2535
- if (target === null) {
2536
- misses.push(`${args.path} from ${this.#paths.idOf(importer)}`);
2537
- return { path: `akan-miss:${args.path}`, external: true };
2538
- }
2539
- const external = CsrDevPaths.isStub(target) ? target : `${CsrDevPaths.modulePrefix}${this.#paths.idOf(target)}`;
2540
- return { path: external, external: true };
2541
- });
2542
- }
2398
+ modules: [...result.modules, ...more.modules],
2399
+ refusedVendors: [...result.refusedVendors, ...more.refusedVendors],
2400
+ unresolved: [...result.unresolved, ...more.unresolved]
2543
2401
  };
2544
2402
  }
2545
- #rebaseSourceMap(text) {
2546
- const map = JSON.parse(text);
2547
- const files = map.sources.map((source) => path9.resolve(source));
2548
- map.sources = files.map((file) => `/${this.#paths.idOf(file)}`);
2549
- map.sourcesContent = files.map((file, index) => CsrDevPaths.isVendorFile(file) ? null : map.sourcesContent?.[index] ?? null);
2550
- return JSON.stringify(map);
2403
+ static #implicatedFiles(error) {
2404
+ const failing = CsrDevPatcher.#failingFiles(error);
2405
+ const messages = error instanceof AggregateError ? error.errors : [];
2406
+ const named = messages.flatMap((message) => {
2407
+ const text = String(message.message ?? "");
2408
+ const bases = [process.cwd(), ...failing.map((file) => path8.dirname(file))];
2409
+ return [...text.matchAll(/"([^"\n]+)"/g)].flatMap(([, quoted = ""]) => {
2410
+ const found = bases.map((base) => path8.resolve(base, quoted)).find((file) => CsrDevPaths.isScript(file) && fs5.existsSync(file));
2411
+ return found ? [CsrDevPaths.realpath(found)] : [];
2412
+ });
2413
+ });
2414
+ return [...new Set([...failing, ...named])];
2551
2415
  }
2552
- #emittedDeps(code) {
2553
- const deps = new Set;
2554
- const references = /\b(?:require|import|__akanImport)\("((?:akan-module|stub):[^"]+)"\)/g;
2555
- for (const [, reference] of code.matchAll(references)) {
2556
- if (!reference)
2557
- continue;
2558
- deps.add(CsrDevPaths.isStub(reference) ? reference : this.#paths.fileOf(reference.slice(CsrDevPaths.modulePrefix.length)));
2559
- }
2560
- return [...deps];
2416
+ static #failingFiles(error) {
2417
+ const messages = error instanceof AggregateError ? error.errors : [];
2418
+ return messages.map((message) => message.position?.file).filter((file) => typeof file === "string" && path8.isAbsolute(file)).map((file) => CsrDevPaths.realpath(file));
2561
2419
  }
2562
- static #factory(id, code) {
2563
- const prefix = CsrDevPaths.modulePrefix;
2564
- let patched = code.replace(/^#!/, "//").replaceAll(`import("${prefix}`, `__akanImport("${prefix}`).replace(/^\/\/# debugId=.*$/m, "");
2565
- const marker = patched.search(/^\/\/ /m);
2566
- const body = marker < 0 ? "" : patched.slice(marker);
2567
- for (const [name, params, method] of CsrDevModuleCompiler.#helperPatches) {
2568
- const definition = `var ${name} = ${params} => {`;
2569
- if (patched.includes(definition))
2570
- patched = patched.replace(definition, `var ${name} = __akan.${method}, __bun${name} = ${params} => {`);
2571
- else if (patched.includes(`${name}(`) && !new RegExp(`\\b(?:var|let|const|function)\\s+${name}\\b`).test(body))
2572
- throw new Error(`[csr-dev] ${id}: Bun's ${name} helper no longer reads "${definition}"; CsrDevModuleCompiler must follow`);
2420
+ async#carriedRoots(graph, changed, present) {
2421
+ const { paths } = this.#bundler;
2422
+ const outside = graph.pending.filter((id) => !graph.modules[id] && !changed.has(id) && present(paths.fileOf(id)));
2423
+ if (outside.length === 0)
2424
+ return [];
2425
+ const wanted = await this.#bundler.wantedRoots(outside.map((id) => paths.fileOf(id)), graph);
2426
+ const kept = outside.filter((id) => wanted.has(paths.fileOf(id)));
2427
+ graph.pending = graph.pending.filter((id) => graph.modules[id] || kept.includes(id));
2428
+ const carried = [];
2429
+ for (const id of kept)
2430
+ if (!await this.#parked(graph, id))
2431
+ carried.push(id);
2432
+ return carried;
2433
+ }
2434
+ async#parked(graph, id) {
2435
+ const { paths } = this.#bundler;
2436
+ const handedOver = this.#delegated.get(id);
2437
+ if (!handedOver || handedOver.hash !== await CsrDevPaths.hashOf(paths.fileOf(id)))
2438
+ return false;
2439
+ if (!handedOver.failed) {
2440
+ if (JSON.stringify(graph.failed ?? null) === handedOver.before)
2441
+ return true;
2442
+ if (graph.failed?.roots.includes(id))
2443
+ handedOver.failed = graph.failed.files;
2573
2444
  }
2574
- if (CsrDevModuleCompiler.#storeRoot.test(id))
2575
- patched += `
2576
- module.hot.accept();`;
2577
- const shared = CsrDevModuleCompiler.#shareHelpers(patched);
2578
- const meta = /(?<![\w$.])__akanMeta(?![\w$])/.test(patched) ? ` var __akanMeta = { url: new URL(${JSON.stringify(id.startsWith("/") ? id : `/${id}`)}, self.location.href).href };` : "";
2579
- return {
2580
- factory: `function (require, module, exports, $RefreshReg$, $RefreshSig$, __akanImport) {${meta}
2581
- ${shared?.code ?? patched}
2582
- }`,
2583
- helpers: shared?.helpers ?? null
2584
- };
2445
+ if (!handedOver.failed)
2446
+ return false;
2447
+ for (const [failedId, hash] of Object.entries(handedOver.failed))
2448
+ if (await CsrDevPaths.hashOf(paths.fileOf(failedId)) !== hash)
2449
+ return false;
2450
+ return true;
2585
2451
  }
2586
- static #shareHelpers(code) {
2587
- const marker = code.search(/^\/\/ /m);
2588
- if (marker <= 0)
2589
- return null;
2590
- const preamble = code.slice(0, marker);
2591
- const names = [...preamble.matchAll(/^(?:var|let|const|function)\s+([\w$]+)/gm)].map(([, name]) => name).filter((name) => !!name);
2592
- if (names.length === 0 || CsrDevModuleCompiler.#factoryArgument.test(preamble))
2452
+ static async#hashesOf(paths, files) {
2453
+ return Object.fromEntries(await Promise.all(files.map(async (file) => [paths.idOf(file), await CsrDevPaths.hashOf(file)])));
2454
+ }
2455
+ #withoutFactory({ graph, code }, present) {
2456
+ const { paths, writer } = this.#bundler;
2457
+ const hasFactory = (id) => code ? code.modules.has(id) : writer.hasModule(id);
2458
+ return Object.entries(graph.modules).filter(([id, module]) => !module.vendor && !hasFactory(id) && present(paths.fileOf(id))).map(([id]) => id);
2459
+ }
2460
+ static #presence(changedFiles) {
2461
+ const caseMoved = changedFiles.some((file) => fs5.existsSync(file) && !CsrDevPaths.isNamed(file));
2462
+ return caseMoved ? (file) => CsrDevPaths.isNamed(file) : (file) => fs5.existsSync(file);
2463
+ }
2464
+ #handBack(reason, generation, context, { first = false } = {}) {
2465
+ this.#state = null;
2466
+ return { kind: "delegate", reason, generation, context, ...first ? { first } : {} };
2467
+ }
2468
+ async#load() {
2469
+ const manifest = await this.#bundler.writer.readJson(CSR_DEV_MANIFEST_FILE2);
2470
+ const graph = await this.#bundler.writer.readJson("graph.json");
2471
+ if (!manifest || !graph || graph.version !== 1 || await this.#bundler.writer.isBuilding())
2593
2472
  return null;
2594
- const hash = Bun.hash(preamble).toString(36);
2595
- const binding = `var { ${names.join(", ")} } = __akan.helpers(${JSON.stringify(hash)});`;
2596
- return {
2597
- code: `${binding}${`
2598
- `.repeat(CsrDevPaths.lineCount(preamble))}${code.slice(marker)}`,
2599
- helpers: {
2600
- hash,
2601
- definition: `__akan.defineHelpers(${JSON.stringify(hash)}, function () {
2602
- ${preamble}return { ${names.join(", ")} };
2603
- });
2604
- `
2473
+ return { graph, manifest, code: null };
2474
+ }
2475
+ async#code(state) {
2476
+ if (!this.#resident)
2477
+ return;
2478
+ state.code ??= await this.#bundler.writer.readCode(state.graph);
2479
+ return state.code;
2480
+ }
2481
+ async#changedModules(graph, changedFiles, present) {
2482
+ const { paths } = this.#bundler;
2483
+ const changed = new Set(graph.pending.filter((id) => graph.modules[id]));
2484
+ for (const file of changedFiles) {
2485
+ const id = paths.idOf(file);
2486
+ if (graph.modules[id])
2487
+ changed.add(id);
2488
+ }
2489
+ for (const id of this.#shadowedImporters(graph, changedFiles))
2490
+ changed.add(id);
2491
+ for (const [id, module] of Object.entries(graph.modules)) {
2492
+ if (module.vendor || changed.has(id))
2493
+ continue;
2494
+ const file = paths.fileOf(id);
2495
+ if (CsrDevPaths.mtimeOf(file) === module.mtimeMs)
2496
+ continue;
2497
+ if (await CsrDevPaths.hashOf(file) !== module.hash)
2498
+ changed.add(id);
2499
+ }
2500
+ for (const [id, module] of Object.entries(graph.modules)) {
2501
+ if (changed.has(id))
2502
+ continue;
2503
+ for (const dep of module.deps) {
2504
+ if (graph.modules[dep] || CsrDevPaths.isStub(dep))
2505
+ continue;
2506
+ const file = paths.fileOf(dep);
2507
+ if (present(file) && CsrDevPaths.isNamed(file))
2508
+ changed.add(dep);
2509
+ else if (!module.vendor)
2510
+ changed.add(id);
2605
2511
  }
2606
- };
2512
+ }
2513
+ return changed;
2514
+ }
2515
+ #shadowedImporters(graph, changedFiles) {
2516
+ const { paths } = this.#bundler;
2517
+ const bases = new Set(changedFiles.map((file) => CsrDevPaths.realpath(file)).filter((file) => !graph.modules[paths.idOf(file)] && CsrDevPaths.isScript(file) && fs5.existsSync(file)).flatMap((file) => {
2518
+ const stem = file.slice(0, file.length - path8.extname(file).length);
2519
+ return path8.basename(stem) === "index" ? [stem, path8.dirname(stem)] : [stem];
2520
+ }));
2521
+ if (bases.size === 0)
2522
+ return [];
2523
+ return Object.entries(graph.resolution).filter(([importer, bySpecifier]) => {
2524
+ if (!graph.modules[importer] || graph.modules[importer]?.vendor)
2525
+ return false;
2526
+ const dir = path8.dirname(paths.fileOf(importer));
2527
+ return Object.keys(bySpecifier).some((specifier) => specifier.startsWith(".") && bases.has(path8.resolve(dir, specifier)));
2528
+ }).map(([importer]) => importer);
2529
+ }
2530
+ #forgetDeletedModules({ graph, code }, present) {
2531
+ const { paths } = this.#bundler;
2532
+ const deleted = Object.keys(graph.modules).filter((id) => !graph.modules[id]?.vendor && !present(paths.fileOf(id)));
2533
+ for (const id of deleted) {
2534
+ delete graph.modules[id];
2535
+ code?.modules.delete(id);
2536
+ }
2537
+ return deleted;
2607
2538
  }
2608
2539
  }
2609
2540
 
2610
2541
  // pkgs/@akanjs/devkit/frontendBuild/registryMetadataFingerprint.ts
2611
2542
  import fs6 from "fs";
2612
- import path10 from "path";
2543
+ import path9 from "path";
2613
2544
 
2614
2545
  class RegistryMetadataFingerprint {
2615
2546
  static #sources = new Bun.Glob("**/*.{signal,dictionary}.ts");
@@ -2617,12 +2548,12 @@ class RegistryMetadataFingerprint {
2617
2548
  static async of(roots, isMetadataFile) {
2618
2549
  const stamps = [];
2619
2550
  for (const root of new Set(roots)) {
2620
- const libDir = path10.join(root, "lib");
2551
+ const libDir = path9.join(root, "lib");
2621
2552
  if (!fs6.existsSync(libDir))
2622
2553
  continue;
2623
- const files = RegistryMetadataFingerprint.#libFiles.map((name) => path10.join(libDir, name));
2554
+ const files = RegistryMetadataFingerprint.#libFiles.map((name) => path9.join(libDir, name));
2624
2555
  for await (const relative of RegistryMetadataFingerprint.#sources.scan({ cwd: libDir, onlyFiles: true }))
2625
- files.push(path10.join(libDir, relative));
2556
+ files.push(path9.join(libDir, relative));
2626
2557
  for (const file of files) {
2627
2558
  if (!isMetadataFile(file))
2628
2559
  continue;
@@ -2648,8 +2579,8 @@ class DevRegistryBundler {
2648
2579
  this.app = app;
2649
2580
  this.routePrefix = routePrefix;
2650
2581
  this.paths = new CsrDevPaths(app.workspace.workspaceRoot);
2651
- this.outDir = path11.join(app.cwdPath, ".akan/artifact", dirName);
2652
- this.entryDir = path11.join(app.cwdPath, ".akan/generated", dirName);
2582
+ this.outDir = path10.join(app.cwdPath, ".akan/artifact", dirName);
2583
+ this.entryDir = path10.join(app.cwdPath, ".akan/generated", dirName);
2653
2584
  this.writer = new CsrDevArtifactWriter(this.outDir, { routePrefix, library });
2654
2585
  }
2655
2586
  async update(changedFiles = [], options = {}) {
@@ -2687,10 +2618,10 @@ class DevRegistryBundler {
2687
2618
  return isAkanRuntimeMetadataFile(file);
2688
2619
  }
2689
2620
  async metadataFingerprint() {
2690
- const libsContainer = path11.resolve(this.app.workspace.workspaceRoot, "libs");
2621
+ const libsContainer = path10.resolve(this.app.workspace.workspaceRoot, "libs");
2691
2622
  const roots = await new WatchRootResolver(this.app).resolve();
2692
- const libRoots = roots.includes(libsContainer) ? fs7.readdirSync(libsContainer).map((name) => path11.join(libsContainer, name)) : roots.filter((root) => path11.dirname(root) === libsContainer);
2693
- return await RegistryMetadataFingerprint.of([path11.resolve(this.app.cwdPath), ...libRoots], (file) => this.isMetadataFile(file));
2623
+ const libRoots = roots.includes(libsContainer) ? fs7.readdirSync(libsContainer).map((name) => path10.join(libsContainer, name)) : roots.filter((root) => path10.dirname(root) === libsContainer);
2624
+ return await RegistryMetadataFingerprint.of([path10.resolve(this.app.cwdPath), ...libRoots], (file) => this.isMetadataFile(file));
2694
2625
  }
2695
2626
  async fullBuild(context, generation, reason) {
2696
2627
  const metadata = await this.metadataFingerprint();
@@ -2783,7 +2714,7 @@ class CsrDevBundler extends DevRegistryBundler {
2783
2714
  const files = {};
2784
2715
  const changed = [];
2785
2716
  for (const basePath of context.htmlBasePaths) {
2786
- const file = path12.join(this.entryDir, CsrEntryFiles.entryFilename(basePath));
2717
+ const file = path11.join(this.entryDir, CsrEntryFiles.entryFilename(basePath));
2787
2718
  const entryPages = CsrEntryFiles.pageEntriesForBasePath(context.pageEntries, basePath, context.basePaths);
2788
2719
  const generator = new PagesEntrySourceGenerator(entryPages, {
2789
2720
  isAsyncDefault: (moduleAbsPath) => this.#isAsyncDefault(moduleAbsPath)
@@ -2822,7 +2753,7 @@ ${hot}`;
2822
2753
  }
2823
2754
  #resolveRefreshRuntime() {
2824
2755
  const akanWebkit = CsrDevPaths.tryResolve("akanjs/webkit", this.app.cwdPath);
2825
- const bases = [this.app.cwdPath, this.paths.root, ...akanWebkit ? [path12.dirname(path12.dirname(akanWebkit))] : []];
2756
+ const bases = [this.app.cwdPath, this.paths.root, ...akanWebkit ? [path11.dirname(path11.dirname(akanWebkit))] : []];
2826
2757
  for (const base of bases) {
2827
2758
  const resolved = CsrDevPaths.tryResolve("react-refresh/runtime", base);
2828
2759
  if (resolved)
@@ -2833,7 +2764,7 @@ ${hot}`;
2833
2764
  }
2834
2765
 
2835
2766
  // pkgs/@akanjs/devkit/frontendBuild/devChangePlanner.ts
2836
- import path13 from "path";
2767
+ import path12 from "path";
2837
2768
  var CONFIG_BASENAMES = new Set(["akan.config.ts", "bunfig.toml", "tsconfig.json", "package.json"]);
2838
2769
  var BARREL_FACETS = new Set(["common", "srvkit", "ui", "webkit", "plugin"]);
2839
2770
  var CLIENT_SUFFIXES = [".Template.tsx", ".Unit.tsx", ".Util.tsx", ".View.tsx", ".Zone.tsx", ".store.ts"];
@@ -2844,11 +2775,11 @@ var RUNTIME_METADATA_BASENAMES = new Set(["dict.ts", "sig.ts", "useClient.ts", "
2844
2775
  class DevChangePlanner {
2845
2776
  #workspaceRoot;
2846
2777
  constructor({ workspaceRoot }) {
2847
- this.#workspaceRoot = path13.resolve(workspaceRoot);
2778
+ this.#workspaceRoot = path12.resolve(workspaceRoot);
2848
2779
  }
2849
2780
  plan({ generation, files, kinds, generatedFiles = [] }) {
2850
2781
  const fileList = uniqueResolved([...files, ...generatedFiles]);
2851
- const generatedSet = new Set(generatedFiles.map((file) => path13.resolve(file)));
2782
+ const generatedSet = new Set(generatedFiles.map((file) => path12.resolve(file)));
2852
2783
  const kindSet = new Set(kinds);
2853
2784
  const roles = new Set;
2854
2785
  const actions = new Set;
@@ -2865,13 +2796,13 @@ class DevChangePlanner {
2865
2796
  }
2866
2797
  for (const file of fileList) {
2867
2798
  const reasons = new Set;
2868
- const fileRoles = this.#rolesForFile(file, { isGenerated: generatedSet.has(path13.resolve(file)), reasons });
2799
+ const fileRoles = this.#rolesForFile(file, { isGenerated: generatedSet.has(path12.resolve(file)), reasons });
2869
2800
  for (const role of fileRoles)
2870
2801
  roles.add(role);
2871
2802
  if (reasons.has("runtime-metadata"))
2872
2803
  actions.add("restart-builder");
2873
2804
  if (reasons.size > 0)
2874
- reasonByFile[path13.resolve(file)] = [...reasons].sort();
2805
+ reasonByFile[path12.resolve(file)] = [...reasons].sort();
2875
2806
  }
2876
2807
  if (roles.has("barrel"))
2877
2808
  actions.add("sync-generated");
@@ -2892,12 +2823,12 @@ class DevChangePlanner {
2892
2823
  }
2893
2824
  #rolesForFile(file, { isGenerated, reasons }) {
2894
2825
  const roles = new Set;
2895
- const abs = path13.resolve(file);
2896
- const base = path13.basename(abs);
2897
- const ext = path13.extname(abs).toLowerCase();
2826
+ const abs = path12.resolve(file);
2827
+ const base = path12.basename(abs);
2828
+ const ext = path12.extname(abs).toLowerCase();
2898
2829
  const isSource = SOURCE_EXTS.has(ext);
2899
- const rel = path13.relative(this.#workspaceRoot, abs);
2900
- const parts = rel.split(path13.sep).filter(Boolean);
2830
+ const rel = path12.relative(this.#workspaceRoot, abs);
2831
+ const parts = rel.split(path12.sep).filter(Boolean);
2901
2832
  if (CONFIG_BASENAMES.has(base)) {
2902
2833
  roles.add("config");
2903
2834
  reasons.add("config-file");
@@ -2968,17 +2899,17 @@ class DevChangePlanner {
2968
2899
  return true;
2969
2900
  }
2970
2901
  #isWorkspaceSource(rel) {
2971
- if (!rel || rel.startsWith("..") || path13.isAbsolute(rel))
2902
+ if (!rel || rel.startsWith("..") || path12.isAbsolute(rel))
2972
2903
  return false;
2973
- const [scope] = rel.split(path13.sep);
2904
+ const [scope] = rel.split(path12.sep);
2974
2905
  return scope === "apps" || scope === "libs" || scope === "pkgs";
2975
2906
  }
2976
2907
  }
2977
- var uniqueResolved = (files) => [...new Set(files.map((file) => path13.resolve(file)))].sort();
2908
+ var uniqueResolved = (files) => [...new Set(files.map((file) => path12.resolve(file)))].sort();
2978
2909
 
2979
2910
  // pkgs/@akanjs/devkit/frontendBuild/devGeneratedIndexSync.ts
2980
2911
  import { mkdir as mkdir4, readdir as readdir3, readFile as readFile2, rm as rm2, stat as stat2 } from "fs/promises";
2981
- import path14 from "path";
2912
+ import path13 from "path";
2982
2913
  import { capitalize } from "akanjs/common";
2983
2914
  var BARREL_FACETS2 = new Set(["common", "srvkit", "ui", "webkit", "plugin"]);
2984
2915
  var FACET_SOURCE_FILE_RE = /\.(ts|tsx)$/;
@@ -2992,7 +2923,7 @@ var SCALAR_UI_TYPES = ["Template", "Unit"];
2992
2923
  class DevGeneratedIndexSync {
2993
2924
  #workspaceRoot;
2994
2925
  constructor({ workspaceRoot }) {
2995
- this.#workspaceRoot = path14.resolve(workspaceRoot);
2926
+ this.#workspaceRoot = path13.resolve(workspaceRoot);
2996
2927
  }
2997
2928
  async syncForBatch(files) {
2998
2929
  const indexPaths = new Set;
@@ -3021,11 +2952,11 @@ class DevGeneratedIndexSync {
3021
2952
  return { changedFiles, errors };
3022
2953
  }
3023
2954
  #facetIndexFor(file) {
3024
- const abs = path14.resolve(file);
3025
- const rel = path14.relative(this.#workspaceRoot, abs);
3026
- if (rel.startsWith("..") || path14.isAbsolute(rel))
2955
+ const abs = path13.resolve(file);
2956
+ const rel = path13.relative(this.#workspaceRoot, abs);
2957
+ if (rel.startsWith("..") || path13.isAbsolute(rel))
3027
2958
  return null;
3028
- const parts = rel.split(path14.sep).filter(Boolean);
2959
+ const parts = rel.split(path13.sep).filter(Boolean);
3029
2960
  if (parts.length < 4)
3030
2961
  return null;
3031
2962
  const [scope, project, facet, child] = parts;
@@ -3033,32 +2964,32 @@ class DevGeneratedIndexSync {
3033
2964
  return null;
3034
2965
  if (!child || child.startsWith(".") || child === "index.ts" || child === "index.tsx")
3035
2966
  return null;
3036
- return path14.join(this.#workspaceRoot, scope, project, facet, "index.ts");
2967
+ return path13.join(this.#workspaceRoot, scope, project, facet, "index.ts");
3037
2968
  }
3038
2969
  async#moduleIndexForDirectoryEvent(file) {
3039
- const abs = path14.resolve(file);
3040
- const rel = path14.relative(this.#workspaceRoot, abs);
3041
- if (rel.startsWith("..") || path14.isAbsolute(rel))
2970
+ const abs = path13.resolve(file);
2971
+ const rel = path13.relative(this.#workspaceRoot, abs);
2972
+ if (rel.startsWith("..") || path13.isAbsolute(rel))
3042
2973
  return null;
3043
- const parts = rel.split(path14.sep).filter(Boolean);
2974
+ const parts = rel.split(path13.sep).filter(Boolean);
3044
2975
  if (parts.length !== 4 && parts.length !== 5)
3045
2976
  return null;
3046
2977
  const [scope, project, libSegment, moduleSegment, scalarSegment] = parts;
3047
2978
  if (scope !== "apps" && scope !== "libs" || !project || libSegment !== "lib")
3048
2979
  return null;
3049
2980
  const isExistingDirectory = await stat2(abs).then((s) => s.isDirectory()).catch(() => false);
3050
- const looksLikeDeletedDirectory = !path14.extname(abs);
2981
+ const looksLikeDeletedDirectory = !path13.extname(abs);
3051
2982
  if (!isExistingDirectory && !looksLikeDeletedDirectory)
3052
2983
  return null;
3053
2984
  if (moduleSegment === "__scalar" && scalarSegment) {
3054
- return path14.join(this.#workspaceRoot, scope, project, "lib", "__scalar", scalarSegment, "index.ts");
2985
+ return path13.join(this.#workspaceRoot, scope, project, "lib", "__scalar", scalarSegment, "index.ts");
3055
2986
  }
3056
2987
  if (!moduleSegment || moduleSegment === "__scalar")
3057
2988
  return null;
3058
- return path14.join(this.#workspaceRoot, scope, project, "lib", moduleSegment, "index.ts");
2989
+ return path13.join(this.#workspaceRoot, scope, project, "lib", moduleSegment, "index.ts");
3059
2990
  }
3060
2991
  async#syncIndex(indexPath) {
3061
- const dir = path14.dirname(indexPath);
2992
+ const dir = path13.dirname(indexPath);
3062
2993
  const content = await this.#contentForIndex(indexPath);
3063
2994
  if (content === null) {
3064
2995
  if (!await exists(indexPath))
@@ -3074,14 +3005,14 @@ class DevGeneratedIndexSync {
3074
3005
  return true;
3075
3006
  }
3076
3007
  async#contentForIndex(indexPath) {
3077
- const parts = path14.relative(this.#workspaceRoot, indexPath).split(path14.sep).filter(Boolean);
3008
+ const parts = path13.relative(this.#workspaceRoot, indexPath).split(path13.sep).filter(Boolean);
3078
3009
  const facet = parts.at(-2);
3079
3010
  if (facet && BARREL_FACETS2.has(facet))
3080
- return this.#facetContent(path14.dirname(indexPath));
3081
- return this.#moduleContent(path14.dirname(indexPath));
3011
+ return this.#facetContent(path13.dirname(indexPath));
3012
+ return this.#moduleContent(path13.dirname(indexPath));
3082
3013
  }
3083
3014
  async#facetContent(dir) {
3084
- const nameCasePattern = path14.basename(dir) === "ui" ? FACET_PASCAL_CASE_RE : FACET_CAMEL_CASE_RE;
3015
+ const nameCasePattern = path13.basename(dir) === "ui" ? FACET_PASCAL_CASE_RE : FACET_CAMEL_CASE_RE;
3085
3016
  const entries = await readdir3(dir, { withFileTypes: true }).catch(() => null);
3086
3017
  if (!entries)
3087
3018
  return null;
@@ -3106,8 +3037,8 @@ class DevGeneratedIndexSync {
3106
3037
  `;
3107
3038
  }
3108
3039
  async#moduleContent(dir) {
3109
- const rel = path14.relative(this.#workspaceRoot, dir);
3110
- const parts = rel.split(path14.sep).filter(Boolean);
3040
+ const rel = path13.relative(this.#workspaceRoot, dir);
3041
+ const parts = rel.split(path13.sep).filter(Boolean);
3111
3042
  const moduleSegment = parts.at(-1);
3112
3043
  if (!moduleSegment)
3113
3044
  return null;
@@ -3119,7 +3050,7 @@ class DevGeneratedIndexSync {
3119
3050
  const allowedTypes = isScalar ? SCALAR_UI_TYPES : moduleSegment.startsWith("_") ? SERVICE_UI_TYPES : MODULE_UI_TYPES;
3120
3051
  const fileTypes = [];
3121
3052
  for (const type of allowedTypes) {
3122
- if (await exists(path14.join(dir, `${modelName}.${type}.tsx`)))
3053
+ if (await exists(path13.join(dir, `${modelName}.${type}.tsx`)))
3123
3054
  fileTypes.push(type);
3124
3055
  }
3125
3056
  if (fileTypes.length === 0)
@@ -3156,6 +3087,75 @@ var formatError2 = (err) => err instanceof Error ? err.message : String(err);
3156
3087
  import { mkdir as mkdir5 } from "fs/promises";
3157
3088
  import path15 from "path";
3158
3089
  import { SSR_DEV_DIRNAME, SSR_DEV_ROUTE_PREFIX } from "akanjs/server/hmr/csrDevManifest";
3090
+
3091
+ // pkgs/@akanjs/devkit/artifact/routeSeedIndex.ts
3092
+ import path14 from "path";
3093
+ import { assertUniqueRoutePatterns, compareRouteSpecificity, parseRouteModuleKey } from "akanjs/common";
3094
+ import {
3095
+ ROUTE_SEED_INDEX_JSON
3096
+ } from "akanjs/server/artifact/routeSeedIndexStore";
3097
+ function computeRouteSeedIndex(pageEntries) {
3098
+ const layoutsByPrefix = new Map;
3099
+ const pagesBySegments = [];
3100
+ for (const { key, moduleAbsPath, seedAbsPaths } of pageEntries) {
3101
+ const parsed = parseRouteModuleKey(key);
3102
+ const files = [path14.resolve(moduleAbsPath), ...(seedAbsPaths ?? []).map((seed) => path14.resolve(seed))];
3103
+ if (parsed.kind === "layout" || parsed.kind === "overrides") {
3104
+ layoutsByPrefix.getOrInsert(parsed.routeSegments.join("/"), []).push(...files);
3105
+ } else if (parsed.kind === "page") {
3106
+ pagesBySegments.push({
3107
+ key,
3108
+ pattern: parsed.pattern,
3109
+ segments: parsed.routeSegments,
3110
+ files,
3111
+ includeOwnLayout: parsed.leaf === "_index"
3112
+ });
3113
+ }
3114
+ }
3115
+ assertUniqueRoutePatterns(pagesBySegments);
3116
+ const globalLayoutFiles = layoutsByPrefix.get("") ?? [];
3117
+ const seedEntries = [];
3118
+ for (const { pattern, segments, files, includeOwnLayout } of pagesBySegments) {
3119
+ const layouts = [];
3120
+ const maxPrefixLength = includeOwnLayout ? segments.length : Math.max(segments.length - 1, 0);
3121
+ for (let i = 0;i <= maxPrefixLength; i++) {
3122
+ const prefix = segments.slice(0, i).join("/");
3123
+ const layoutFiles = layoutsByPrefix.get(prefix);
3124
+ if (layoutFiles)
3125
+ layouts.push(...layoutFiles);
3126
+ }
3127
+ const seeds = Array.from(new Set([...layouts, ...files]));
3128
+ const routeId = pattern || "/";
3129
+ seedEntries.push({ routeId, pattern: routeId, seeds });
3130
+ }
3131
+ seedEntries.sort((a, b) => compareRouteSpecificity(a.pattern, b.pattern));
3132
+ return { entries: seedEntries, globalLayoutFiles };
3133
+ }
3134
+ function serializeRouteSeedIndexForArtifact(index, artifactDir, options = {}) {
3135
+ const normalizedArtifactDir = path14.resolve(artifactDir);
3136
+ if (options.production)
3137
+ return { entries: index.entries.map((entry) => ({ routeId: entry.routeId })) };
3138
+ return {
3139
+ entries: index.entries.map((entry) => ({
3140
+ ...entry,
3141
+ seeds: entry.seeds.map((seed) => serializeArtifactPath(seed, normalizedArtifactDir))
3142
+ })),
3143
+ globalLayoutFiles: index.globalLayoutFiles.map((file) => serializeArtifactPath(file, normalizedArtifactDir))
3144
+ };
3145
+ }
3146
+ async function saveRouteSeedIndex(artifactDir, index, options = {}) {
3147
+ const absPath = path14.join(path14.resolve(artifactDir), ROUTE_SEED_INDEX_JSON);
3148
+ await Bun.write(absPath, `${JSON.stringify(serializeRouteSeedIndexForArtifact(index, artifactDir, options), null, 2)}
3149
+ `);
3150
+ return absPath;
3151
+ }
3152
+ function serializeArtifactPath(artifactPath, artifactDir) {
3153
+ if (!path14.isAbsolute(artifactPath))
3154
+ return artifactPath;
3155
+ return path14.relative(artifactDir, artifactPath).split(path14.sep).join("/");
3156
+ }
3157
+
3158
+ // pkgs/@akanjs/devkit/frontendBuild/ssrDevBundler.ts
3159
3159
  class SsrDevBundler extends DevRegistryBundler {
3160
3160
  static #formatVersion = 2;
3161
3161
  static bootstrapKey = "";