@akanjs/cli 3.0.0-beta.24 → 3.0.0-beta.25

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 (70) hide show
  1. package/.build-stamp +1 -1
  2. package/{CodeAgent-0dh9fzzg.js → CodeAgent-p65jja35.js} +2 -2
  3. package/{CodeTui-drwdvwg9.js → CodeTui-5xr8kxx4.js} +1 -1
  4. package/TunnelShare-cwh9ezsm.js +9 -0
  5. package/{agent.command-ttn09rx3.js → agent.command-rp505zxa.js} +7 -7
  6. package/akanCode.js +2 -2
  7. package/{application.command-z7a4bdrq.js → application.command-2x9jqy0k.js} +7 -7
  8. package/{applicationBuildRunner-n6xfexaa.js → applicationBuildRunner-tm0vba5t.js} +4 -4
  9. package/buildBatch.proc.js +77 -23
  10. package/{cloud.command-j0fcz44z.js → cloud.command-j6m607bp.js} +12 -12
  11. package/cloud.script-r5t7nhee.js +20 -0
  12. package/{code.command-1jgk14tc.js → code.command-jhrjttm1.js} +7 -7
  13. package/{context.command-4vpjmg22.js → context.command-xncdvv7n.js} +14 -14
  14. package/context.runner-pjy89k2s.js +21 -0
  15. package/{dependencyScanner-ye2e6pdz.js → dependencyScanner-06z2dmrr.js} +2 -2
  16. package/{fileSys-5ngz93ga.js → fileSys-wzzrntkw.js} +1 -1
  17. package/{guideline.command-7jk47fgq.js → guideline.command-d5qmxx29.js} +4 -4
  18. package/guidelines/conventions/conventions.instruction.md +7 -5
  19. package/incrementalBuilder.proc.js +544 -72
  20. package/{index-ga6hygxr.js → index-0n9q1f23.js} +3 -3
  21. package/{index-3t7g9e4r.js → index-2ahj896a.js} +1 -1
  22. package/{index-zabxte74.js → index-2wc41k5w.js} +1 -1
  23. package/{index-khpfpcsd.js → index-48k96p55.js} +27 -7
  24. package/{index-ne50hc2m.js → index-4mgbzgb5.js} +456 -1623
  25. package/{index-2gh9sndr.js → index-50rq2qms.js} +4 -4
  26. package/{index-5b6tavv3.js → index-52w0fvh8.js} +15 -1
  27. package/{index-zbc2q6a6.js → index-56xwsgyn.js} +3 -3
  28. package/{index-rmcw8yc8.js → index-58cyna2s.js} +2 -2
  29. package/{index-1db2cw0y.js → index-5mm3g24t.js} +2 -2
  30. package/{index-wp43xrjv.js → index-6dt6zft6.js} +4 -4
  31. package/{index-vj1cwwy5.js → index-7yxndzea.js} +7 -7
  32. package/{index-q44jbw48.js → index-860zjnr1.js} +1 -1
  33. package/{index-d4mfjgm0.js → index-8k93esfr.js} +6 -6
  34. package/{index-fzd22vjh.js → index-94s080qm.js} +3 -17
  35. package/{index-yy4s1j72.js → index-d65fzxvd.js} +1 -1
  36. package/{index-m0ms3n1c.js → index-da72pfdv.js} +7 -7
  37. package/index-dt1x62ec.js +4108 -0
  38. package/{index-4dn1p115.js → index-ggfhz9ab.js} +4 -4
  39. package/{index-9629w7ns.js → index-hzy4pe24.js} +3 -3
  40. package/{index-ymgtbmf1.js → index-pb10njz7.js} +191 -58
  41. package/{index-mgfx3mfs.js → index-per2kqy9.js} +4 -4
  42. package/{index-aaz7n8s2.js → index-qm4fppfk.js} +4 -4
  43. package/{index-ya5qbmyg.js → index-t02g6dpn.js} +3 -3
  44. package/{index-hb8sgepf.js → index-tpdn30ya.js} +1 -1
  45. package/{index-rnt66vym.js → index-v4hnkqz9.js} +1 -1
  46. package/{index-pp54p5zk.js → index-xfgdmm5d.js} +2 -2
  47. package/{index-tgjy8xf3.js → index-zazvn45w.js} +4 -4
  48. package/{index-0bq14vr1.js → index-zzvvc845.js} +1 -1
  49. package/index.js +22 -22
  50. package/{library.command-ccb1cp92.js → library.command-sv6g1h2g.js} +4 -4
  51. package/{localRegistry.command-gmj1k6q8.js → localRegistry.command-4kj56531.js} +11 -11
  52. package/{module.command-5k6h407d.js → module.command-ccdeder9.js} +6 -6
  53. package/{package.command-e6ntvpez.js → package.command-xmw605b1.js} +5 -5
  54. package/package.json +3 -3
  55. package/{page.command-v4f90wb8.js → page.command-ky7vafsn.js} +4 -4
  56. package/{primitive.command-g6km57q5.js → primitive.command-bh6j8nfs.js} +7 -7
  57. package/{prompter-ptaa3nk0.js → prompter-76k56b37.js} +2 -2
  58. package/{quality.command-de3p36pz.js → quality.command-rqqrpe98.js} +3 -3
  59. package/{repair.command-d3x1javq.js → repair.command-n9nsm9qe.js} +6 -6
  60. package/{scalar.command-7ezrac2b.js → scalar.command-t09gmck8.js} +5 -5
  61. package/{subspace.command-ksenprez.js → subspace.command-ygfdqpbj.js} +6 -6
  62. package/{tunnel.command-54947jck.js → tunnel.command-4g95vhsn.js} +5 -5
  63. package/{workflow.command-z2p32nmf.js → workflow.command-b91ph8sx.js} +11 -11
  64. package/{workspace.command-tkn8g0bn.js → workspace.command-p44d21qf.js} +24 -24
  65. package/README.ko.md +0 -72
  66. package/README.md +0 -88
  67. package/TunnelShare-pemaknmd.js +0 -9
  68. package/cloud.script-g2dcnr8c.js +0 -20
  69. package/context.runner-ehjs419v.js +0 -21
  70. package/index-fgq75haa.js +0 -2084
@@ -1,95 +1,29 @@
1
1
  // @bun
2
2
  import {
3
- AsyncDefaultExportDetector,
4
3
  resolveSsrPageEntriesForApp
5
- } from "./index-ga6hygxr.js";
4
+ } from "./index-0n9q1f23.js";
6
5
  import {
7
- SOURCE_EXTS
8
- } from "./index-khpfpcsd.js";
9
- import {
10
- loaderFor,
6
+ computeRouteSeedIndex,
7
+ saveRouteSeedIndex,
11
8
  BarrelAnalyzer,
12
- createBarrelImportsPlugin,
13
9
  createTsconfigPackageResolver,
14
10
  rewriteBarrelImports,
15
11
  GraphClientEntryDiscovery,
16
- transformUseClient,
17
12
  bundleDefine,
18
13
  ClientEntriesBundler,
19
14
  VENDOR_SPECIFIERS,
20
- RouteClientBuilder
21
- } from "./index-fgq75haa.js";
22
- var __require = import.meta.require;
23
-
24
- // pkgs/@akanjs/devkit/artifact/routeSeedIndex.ts
25
- import path from "path";
26
- import { assertUniqueRoutePatterns, compareRouteSpecificity, parseRouteModuleKey } from "akanjs/common";
15
+ RouteClientBuilder,
16
+ CsrEntryFiles,
17
+ PagesEntrySourceGenerator,
18
+ PagesBundleBuilder
19
+ } from "./index-dt1x62ec.js";
27
20
  import {
28
- ROUTE_SEED_INDEX_JSON
29
- } from "akanjs/server/artifact/routeSeedIndexStore";
30
- function computeRouteSeedIndex(pageEntries) {
31
- const layoutsByPrefix = new Map;
32
- const pagesBySegments = [];
33
- for (const { key, moduleAbsPath, seedAbsPaths } of pageEntries) {
34
- const parsed = parseRouteModuleKey(key);
35
- const files = [path.resolve(moduleAbsPath), ...(seedAbsPaths ?? []).map((seed) => path.resolve(seed))];
36
- if (parsed.kind === "layout" || parsed.kind === "overrides") {
37
- layoutsByPrefix.getOrInsert(parsed.routeSegments.join("/"), []).push(...files);
38
- } else if (parsed.kind === "page") {
39
- pagesBySegments.push({
40
- key,
41
- pattern: parsed.pattern,
42
- segments: parsed.routeSegments,
43
- files,
44
- includeOwnLayout: parsed.leaf === "_index"
45
- });
46
- }
47
- }
48
- assertUniqueRoutePatterns(pagesBySegments);
49
- const globalLayoutFiles = layoutsByPrefix.get("") ?? [];
50
- const seedEntries = [];
51
- for (const { pattern, segments, files, includeOwnLayout } of pagesBySegments) {
52
- const layouts = [];
53
- const maxPrefixLength = includeOwnLayout ? segments.length : Math.max(segments.length - 1, 0);
54
- for (let i = 0;i <= maxPrefixLength; i++) {
55
- const prefix = segments.slice(0, i).join("/");
56
- const layoutFiles = layoutsByPrefix.get(prefix);
57
- if (layoutFiles)
58
- layouts.push(...layoutFiles);
59
- }
60
- const seeds = Array.from(new Set([...layouts, ...files]));
61
- const routeId = pattern || "/";
62
- seedEntries.push({ routeId, pattern: routeId, seeds });
63
- }
64
- seedEntries.sort((a, b) => compareRouteSpecificity(a.pattern, b.pattern));
65
- return { entries: seedEntries, globalLayoutFiles };
66
- }
67
- function serializeRouteSeedIndexForArtifact(index, artifactDir, options = {}) {
68
- const normalizedArtifactDir = path.resolve(artifactDir);
69
- if (options.production)
70
- return { entries: index.entries.map((entry) => ({ routeId: entry.routeId })) };
71
- return {
72
- entries: index.entries.map((entry) => ({
73
- ...entry,
74
- seeds: entry.seeds.map((seed) => serializeArtifactPath(seed, normalizedArtifactDir))
75
- })),
76
- globalLayoutFiles: index.globalLayoutFiles.map((file) => serializeArtifactPath(file, normalizedArtifactDir))
77
- };
78
- }
79
- async function saveRouteSeedIndex(artifactDir, index, options = {}) {
80
- const absPath = path.join(path.resolve(artifactDir), ROUTE_SEED_INDEX_JSON);
81
- await Bun.write(absPath, `${JSON.stringify(serializeRouteSeedIndexForArtifact(index, artifactDir, options), null, 2)}
82
- `);
83
- return absPath;
84
- }
85
- function serializeArtifactPath(artifactPath, artifactDir) {
86
- if (!path.isAbsolute(artifactPath))
87
- return artifactPath;
88
- return path.relative(artifactDir, artifactPath).split(path.sep).join("/");
89
- }
21
+ SOURCE_EXTS
22
+ } from "./index-48k96p55.js";
23
+ var __require = import.meta.require;
90
24
 
91
25
  // pkgs/@akanjs/devkit/frontendBuild/allRoutesBuilder.ts
92
- import path2 from "path";
26
+ import path from "path";
93
27
 
94
28
  // pkgs/@akanjs/devkit/frontendBuild/routesManifestArtifactSerializer.ts
95
29
  import { RoutesManifestStore } from "akanjs/server/artifact/routesManifestStore";
@@ -155,7 +89,7 @@ class AllRoutesBuilder {
155
89
  routeIds: this.#routeIds,
156
90
  ...this.#merged
157
91
  };
158
- const manifestPath = path2.join(path2.resolve(this.#artifactDir), "routes-manifest.json");
92
+ const manifestPath = path.join(path.resolve(this.#artifactDir), "routes-manifest.json");
159
93
  await Bun.write(manifestPath, `${JSON.stringify(RoutesManifestArtifactSerializer.serialize(manifest, this.#artifactDir, {
160
94
  production: this.#command === "build"
161
95
  }), null, 2)}
@@ -194,6 +128,222 @@ class AllRoutesBuilder {
194
128
  }
195
129
  }
196
130
 
131
+ // pkgs/@akanjs/devkit/frontendBuild/csrArtifactBuilder.ts
132
+ import { mkdir, rm, unlink } from "fs/promises";
133
+ import path2 from "path";
134
+ class CsrArtifactBuilder {
135
+ #app;
136
+ #command;
137
+ #lang;
138
+ constructor(app, command = "start", lang = "en") {
139
+ this.#app = app;
140
+ this.#command = command;
141
+ this.#lang = lang;
142
+ }
143
+ async build() {
144
+ const pageKeys = await this.#app.getPageKeys();
145
+ if (pageKeys.length === 0) {
146
+ this.#app.log(`[cli] no route files under ${this.#app.cwdPath}/page \u2014 skipping CSR build`);
147
+ return null;
148
+ }
149
+ const pageEntries = await resolveSsrPageEntriesForApp(this.#app, pageKeys);
150
+ const akanConfig = await this.#app.getConfig();
151
+ const cssAssets = await this.#loadCssAssets();
152
+ const basePaths = [...akanConfig.basePaths];
153
+ const htmlBasePaths = basePaths.length > 0 ? basePaths : [""];
154
+ await rm(this.#outputDir, { recursive: true, force: true });
155
+ await mkdir(this.#generatedDir, { recursive: true });
156
+ const generatedFiles = Object.fromEntries((await Promise.all(htmlBasePaths.map(async (basePath) => [
157
+ this.#createHtmlFile(basePath),
158
+ await this.#createEntryFile(basePath, CsrArtifactBuilder.pageEntriesForBasePath(pageEntries, basePath, basePaths))
159
+ ]))).flat());
160
+ const result = await Bun.build({
161
+ target: "browser",
162
+ entrypoints: htmlBasePaths.map((basePath) => this.#generatedPath(CsrArtifactBuilder.htmlFilename(basePath))),
163
+ files: generatedFiles,
164
+ root: this.#generatedDir,
165
+ outdir: this.#outputDir,
166
+ splitting: false,
167
+ minify: true,
168
+ env: "AKAN_PUBLIC_*",
169
+ define: bundleDefine(this.#app, this.#command, "csr"),
170
+ optimizeImports: akanConfig.optimizeImports,
171
+ plugins: [PagesBundleBuilder.createCssStubPlugin()]
172
+ });
173
+ if (!result.success) {
174
+ const logs = result.logs.map((log) => log.message).join(`
175
+ `);
176
+ throw new Error(`[csr-build] failed${logs ? `
177
+ ${logs}` : ""}`);
178
+ }
179
+ await this.#inlineCsrArtifacts(cssAssets);
180
+ this.#app.verbose(`[csr-build] output -> ${this.#outputDir}`);
181
+ return { outputDir: this.#outputDir };
182
+ }
183
+ static pageEntriesForBasePath(pageEntries, basePath, basePaths) {
184
+ return CsrEntryFiles.pageEntriesForBasePath(pageEntries, basePath, basePaths);
185
+ }
186
+ static htmlFilename(basePath) {
187
+ return `${basePath || "index"}.html`;
188
+ }
189
+ static entryFilename(basePath) {
190
+ return CsrEntryFiles.entryFilename(basePath);
191
+ }
192
+ static basePathOfHtml(htmlPath) {
193
+ const name = path2.basename(htmlPath, ".html");
194
+ return name === "index" ? "" : name;
195
+ }
196
+ get #outputDir() {
197
+ return path2.join(this.#command === "build" ? this.#app.dist.cwdPath : this.#app.cwdPath, this.#command === "build" ? "csr" : ".akan/artifact/csr");
198
+ }
199
+ get #generatedDir() {
200
+ return path2.join(this.#app.cwdPath, ".akan/generated/csr");
201
+ }
202
+ get #artifactDir() {
203
+ return path2.join(this.#command === "build" ? this.#app.dist.cwdPath : this.#app.cwdPath, ".akan/artifact");
204
+ }
205
+ #generatedPath(filename) {
206
+ return path2.join(this.#generatedDir, filename);
207
+ }
208
+ async#createEntryFile(basePath, pageEntries) {
209
+ return [
210
+ this.#generatedPath(CsrArtifactBuilder.entryFilename(basePath)),
211
+ `import { bootCsr } from "akanjs/webkit";
212
+ ${await PagesEntrySourceGenerator.generateStatic(pageEntries)}
213
+ void bootCsr(pages);
214
+ `
215
+ ];
216
+ }
217
+ #createHtmlFile(basePath) {
218
+ return [
219
+ this.#generatedPath(CsrArtifactBuilder.htmlFilename(basePath)),
220
+ `<!doctype html>
221
+ <html lang="${this.#lang}">
222
+ <head>
223
+ <meta charset="utf-8" />
224
+ <title>${this.#app.name}</title>
225
+ <base href="/" />
226
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
227
+ </head>
228
+ <body>
229
+ <div id="root"></div>
230
+ <script type="module" src="./${CsrArtifactBuilder.entryFilename(basePath)}"></script>
231
+ </body>
232
+ </html>
233
+ `
234
+ ];
235
+ }
236
+ async#loadCssAssets() {
237
+ const artifactFile = Bun.file(path2.join(this.#artifactDir, "base-artifact.json"));
238
+ if (!await artifactFile.exists())
239
+ return {};
240
+ const artifact = await artifactFile.json();
241
+ return artifact.cssAssets ?? {};
242
+ }
243
+ async#inlineCsrArtifacts(cssAssets) {
244
+ const jsFiles = new Set;
245
+ for (const htmlPath of await this.#listOutputFiles((filePath) => filePath.endsWith(".html"))) {
246
+ const htmlFile = Bun.file(htmlPath);
247
+ if (!await htmlFile.exists())
248
+ continue;
249
+ const basePath = CsrArtifactBuilder.basePathOfHtml(htmlPath);
250
+ const cssAsset = cssAssets[basePath];
251
+ if (!cssAsset) {
252
+ this.#app.logger.warn(`[csr-build] base-artifact.json has no compiled stylesheet for ${basePath || "root"}; ${path2.basename(htmlPath)} ships without CSS`);
253
+ }
254
+ const inlined = await this.#inlineHtmlAssets(await htmlFile.text(), htmlPath, cssAsset);
255
+ for (const filePath of inlined.jsFiles)
256
+ jsFiles.add(filePath);
257
+ await Bun.write(htmlPath, inlined.html);
258
+ }
259
+ for (const filePath of jsFiles)
260
+ await unlink(filePath).catch(() => {
261
+ return;
262
+ });
263
+ const remainingAssets = await this.#listOutputFiles((filePath) => filePath.endsWith(".js") || filePath.endsWith(".css"));
264
+ if (remainingAssets.length > 0) {
265
+ throw new Error(`[csr-build] expected single-file HTML, but CSR assets remain:
266
+ ${remainingAssets.join(`
267
+ `)}`);
268
+ }
269
+ }
270
+ async#inlineHtmlAssets(html, htmlPath, cssAsset) {
271
+ let next = html;
272
+ if (cssAsset) {
273
+ const css = await Bun.file(path2.join(this.#artifactDir, cssAsset.cssRelPath)).text();
274
+ next = CsrArtifactBuilder.injectBeforeHeadEnd(next, CsrArtifactBuilder.createInlineStyle(css));
275
+ }
276
+ const jsFiles = [];
277
+ next = await CsrArtifactBuilder.replaceModuleScriptSrc(next, async (src) => {
278
+ const jsPath = CsrArtifactBuilder.resolveHtmlAssetPath(htmlPath, src);
279
+ jsFiles.push(jsPath);
280
+ return await Bun.file(jsPath).text();
281
+ });
282
+ return { html: next, jsFiles };
283
+ }
284
+ async#listOutputFiles(predicate) {
285
+ const glob = new Bun.Glob("**/*");
286
+ const files = [];
287
+ for await (const filePath of glob.scan({ cwd: this.#outputDir, absolute: true })) {
288
+ if (predicate(filePath))
289
+ files.push(filePath);
290
+ }
291
+ return files.sort();
292
+ }
293
+ static injectBeforeHeadEnd(html, snippet) {
294
+ const scannable = CsrArtifactBuilder.blankEmbeddedContent(html);
295
+ const headEnd = scannable.search(/<\/head\s*>/i);
296
+ if (headEnd !== -1)
297
+ return `${html.slice(0, headEnd)}${snippet}
298
+ ${html.slice(headEnd)}`;
299
+ const bodyStart = scannable.search(/<body(?:\s|>)/i);
300
+ if (bodyStart !== -1)
301
+ return `${html.slice(0, bodyStart)}${snippet}
302
+ ${html.slice(bodyStart)}`;
303
+ return `${html}
304
+ ${snippet}`;
305
+ }
306
+ static blankEmbeddedContent(html) {
307
+ return html.replace(/<script\b[^>]*>[\s\S]*?<\/script\s*>|<style\b[^>]*>[\s\S]*?<\/style\s*>|<!--[\s\S]*?-->/gi, (match) => " ".repeat(match.length));
308
+ }
309
+ static createInlineStyle(css) {
310
+ return `<style data-akan-css="active">
311
+ ${css.replace(/<\/style/gi, "<\\/style")}
312
+ </style>`;
313
+ }
314
+ static async replaceModuleScriptSrc(html, loadScript) {
315
+ const scriptRe = /<script\b(?=[^>]*\btype=["']module["'])(?=[^>]*\bsrc=["']([^"']+)["'])[^>]*>\s*<\/script>/gi;
316
+ let result = "";
317
+ let lastIndex = 0;
318
+ let matched = false;
319
+ for (const match of html.matchAll(scriptRe)) {
320
+ const full = match[0];
321
+ const src = match[1];
322
+ if (match.index === undefined || !src)
323
+ continue;
324
+ matched = true;
325
+ result += html.slice(lastIndex, match.index);
326
+ result += `<script type="module">
327
+ ${CsrArtifactBuilder.escapeInlineScript(await loadScript(src))}
328
+ </script>`;
329
+ lastIndex = match.index + full.length;
330
+ }
331
+ if (!matched)
332
+ return html;
333
+ return result + html.slice(lastIndex);
334
+ }
335
+ static escapeInlineScript(source) {
336
+ return source.replace(/<\/script/gi, "<\\/script");
337
+ }
338
+ static resolveHtmlAssetPath(htmlPath, src) {
339
+ if (/^[a-z][a-z0-9+.-]*:/i.test(src) || src.startsWith("//")) {
340
+ throw new Error(`[csr-build] cannot inline external script: ${src}`);
341
+ }
342
+ const normalized = src.startsWith("/") ? src.slice(1) : src;
343
+ return path2.resolve(path2.dirname(htmlPath), normalized);
344
+ }
345
+ }
346
+
197
347
  // pkgs/@akanjs/devkit/frontendBuild/cssCompiler.ts
198
348
  import path4 from "path";
199
349
  import { Logger } from "akanjs/common";
@@ -526,1524 +676,207 @@ class CssCompiler {
526
676
  }
527
677
  let source = content;
528
678
  if (akanConfig.barrelImports.length > 0) {
529
- try {
530
- const rewritten = await rewriteBarrelImports(content, akanConfig.barrelImports, analyzer);
531
- if (rewritten !== null)
532
- source = rewritten;
533
- } catch {}
534
- }
535
- let imports;
536
- try {
537
- imports = this.#transpiler.scanImports(source);
538
- } catch {
539
- continue;
540
- }
541
- const importerDir = path4.dirname(filePath);
542
- for (const imp of imports) {
543
- const spec = imp.path;
544
- if (!spec)
545
- continue;
546
- if (spec.endsWith(".css")) {
547
- const cssPath = await this.#resolveCssImport(spec, importerDir);
548
- cssFiles.add(cssPath);
549
- continue;
550
- }
551
- if (NON_SOURCE_EXT_RE.test(spec))
552
- continue;
553
- const resolved = await this.#resolveSourceImport(spec, importerDir, resolvePackage);
554
- if (!resolved || sourceFiles.has(resolved) || isIgnoredNodeModuleSource(resolved))
555
- continue;
556
- queue.push(resolved);
557
- }
558
- }
559
- const tokenPaths = await this.#libTokenStylesheets(sourceFiles);
560
- const cssPaths = [...new Set([...tokenPaths, ...cssFiles])];
561
- for (const cssPath of cssPaths)
562
- this.#discoveredCssPaths.add(cssPath);
563
- return { cssPaths, sourcePaths: [...sourceFiles] };
564
- }
565
- async#libTokenStylesheets(sourceFiles) {
566
- const libsRoot = path4.join(this.#app.workspace.workspaceRoot, "libs");
567
- const libNames = new Set;
568
- for (const filePath of sourceFiles) {
569
- const relPath = path4.relative(libsRoot, filePath);
570
- if (relPath.startsWith("..") || path4.isAbsolute(relPath))
571
- continue;
572
- const [libName] = relPath.split(path4.sep);
573
- if (libName)
574
- libNames.add(libName);
575
- }
576
- const tokenPaths = await Promise.all([...libNames].sort().map(async (libName) => {
577
- const tokensPath = path4.join(libsRoot, libName, "ui/tokens.css");
578
- return await this.#fileExists(tokensPath) ? tokensPath : null;
579
- }));
580
- return tokenPaths.filter((tokensPath) => !!tokensPath);
581
- }
582
- async compileCss(cssPaths, sourcePaths) {
583
- const { css } = await this.#compileWithImports(cssPaths, sourcePaths);
584
- return css;
585
- }
586
- async#compileWithImports(cssPaths, sourcePaths) {
587
- if (cssPaths.length === 0)
588
- return { css: "", imported: [] };
589
- const compileStarted = Date.now();
590
- const compilers = await Promise.all(cssPaths.map(async (cssPath) => {
591
- const css = await Bun.file(cssPath).text();
592
- const base = path4.dirname(cssPath);
593
- const imported = new Map;
594
- const compiler = await compile(css, {
595
- base,
596
- loadStylesheet: (id, fromBase) => this.#loadStylesheet(id, fromBase, imported),
597
- loadModule: (id, fromBase) => this.#loadModule(id, fromBase)
598
- });
599
- return { cssPath, compiler, imported };
600
- }));
601
- const sourceDirs = new Set;
602
- for (const entry of compilers) {
603
- for (const s of entry.compiler.sources)
604
- sourceDirs.add(s.base);
605
- }
606
- const scanStarted = Date.now();
607
- const candidates = await this.#scanCandidates(sourcePaths, [...sourceDirs]);
608
- this.#logger.verbose(`css candidates scanned count=${candidates.length} sources=${sourcePaths.length} dirs=${sourceDirs.size} in ${Date.now() - scanStarted}ms`);
609
- const parts = [];
610
- const imported = [];
611
- for (const entry of compilers) {
612
- const part = entry.compiler.build(candidates);
613
- parts.push(part);
614
- for (const [cssPath, content] of entry.imported) {
615
- const declaredNames = declaredCustomProperties(content);
616
- imported.push({ cssPath, declaredNames });
617
- if (declaredNames.length === 0 || declaredNames.some((name) => part.includes(`${name}:`)))
618
- continue;
619
- this.#logger.warn(`css @import ${cssPath} was loaded by ${entry.cssPath} but none of its ${declaredNames.length} declaration(s) reached the compiled CSS`);
620
- }
621
- }
622
- this.#logger.verbose(`css compiled paths=${cssPaths.length} candidates=${candidates.length} in ${Date.now() - compileStarted}ms`);
623
- return { css: parts.join(`
624
- `), imported };
625
- }
626
- async#loadStylesheet(id, fromBase, imported) {
627
- const p = await this.#resolveCssImport(id, fromBase);
628
- this.#discoveredCssPaths.add(p);
629
- const content = await Bun.file(p).text();
630
- imported?.set(p, content);
631
- this.#logger.verbose(`css import "${id}" from ${fromBase} -> ${p} (${content.length} bytes)`);
632
- return { path: p, base: path4.dirname(p), content };
633
- }
634
- async#resolveCssImport(id, fromBase) {
635
- if (id.startsWith(".") || id.startsWith("/")) {
636
- const filePath = path4.resolve(fromBase, id);
637
- if (await this.#fileExists(filePath))
638
- return filePath;
639
- throw new Error(`[css] failed to resolve stylesheet import "${id}" from ${fromBase} (no file at ${filePath})`);
640
- }
641
- this.#cssImportResolver ??= await CssImportResolver.create(this.#app);
642
- const resolved = await this.#cssImportResolver.resolve(id, fromBase);
643
- if (resolved)
644
- return resolved;
645
- throw new Error(`[css] failed to resolve stylesheet import "${id}" from ${fromBase}`);
646
- }
647
- async#loadModule(id, fromBase) {
648
- const p = __require.resolve(id, { paths: [fromBase] });
649
- const mod = await import(p);
650
- return { path: p, base: path4.dirname(p), module: mod.default ?? mod };
651
- }
652
- #resolveSourceImport(id, fromBase, resolvePackage) {
653
- const cacheKey = `${fromBase}\x00${id}`;
654
- let cached = this.#resolvedSpecifierCache.get(cacheKey);
655
- if (cached)
656
- return cached;
657
- cached = this.#resolveSourceImportUncached(id, fromBase, resolvePackage);
658
- this.#resolvedSpecifierCache.set(cacheKey, cached);
659
- return cached;
660
- }
661
- async#resolveSourceImportUncached(id, fromBase, resolvePackage) {
662
- if (id.startsWith(".") || id.startsWith("/")) {
663
- const abs = id.startsWith("/") ? id : path4.resolve(fromBase, id);
664
- return this.#resolveSourceFileCandidate(abs);
665
- }
666
- const pkg = await resolvePackage(id);
667
- if (pkg)
668
- return pkg.entryFile;
669
- for (const resolve of [() => resolveSourceWithBun(id, fromBase), () => resolveSourceWithRequire(id, fromBase)]) {
670
- const resolved = await resolve();
671
- if (resolved)
672
- return resolved;
673
- }
674
- return null;
675
- }
676
- async#scanCandidates(sourcePaths, dirs) {
677
- const candidates = new Set;
678
- const glob = new Bun.Glob("**/*.{tsx,ts,jsx,js,html}");
679
- const files = new Set(sourcePaths);
680
- await Promise.all(dirs.map(async (dir) => {
681
- for await (const file of glob.scan({ cwd: dir, absolute: true })) {
682
- if (isIgnoredNodeModuleSource(file))
683
- continue;
684
- files.add(file);
685
- }
686
- }));
687
- const cache = await new CssCandidateCache(this.#candidateCachePath).load();
688
- await Promise.all([...files].map(async (file) => {
689
- for (const candidate of await cache.candidatesFor(file))
690
- candidates.add(candidate);
691
- }));
692
- await cache.save(files);
693
- this.#logger.verbose(`css candidate cache reused=${cache.reused} rescanned=${cache.rescanned}`);
694
- return [...candidates];
695
- }
696
- }
697
- function resolveSourceWithBun(id, fromBase) {
698
- try {
699
- const resolved = Bun.resolveSync(id, fromBase);
700
- return isSourceFile(resolved) ? resolved : null;
701
- } catch {
702
- return null;
703
- }
704
- }
705
- function resolveSourceWithRequire(id, fromBase) {
706
- try {
707
- const resolved = __require.resolve(id, { paths: [fromBase] });
708
- return isSourceFile(resolved) ? resolved : null;
709
- } catch {
710
- return null;
711
- }
712
- }
713
- function isSourceFile(filePath) {
714
- return SOURCE_EXTS.has(path4.extname(filePath));
715
- }
716
- function isIgnoredNodeModuleSource(filePath) {
717
- return NODE_MODULES_RE.test(filePath) && !AKANJS_NODE_MODULE_RE.test(filePath);
718
- }
719
- function declaredCustomProperties(css) {
720
- const withoutThemeBlocks = css.replace(/@theme[^{]*\{[^}]*\}/g, "");
721
- return [...new Set([...withoutThemeBlocks.matchAll(/(?:^|[\s;{])(--[\w-]+)\s*:/g)].map(([, name]) => name))].filter((name) => !!name);
722
- }
723
- function getPageKeyBasePath(pageKey, basePaths) {
724
- const normalized = pageKey.split(path4.sep).join("/").replace(/^\.\//, "");
725
- const segments = normalized.split("/");
726
- const firstPublicSegment = segments.find((segment) => segment !== "[lang]" && !/^\(.+\)$/.test(segment));
727
- return firstPublicSegment && basePaths.includes(firstPublicSegment) ? firstPublicSegment : null;
728
- }
729
-
730
- // pkgs/@akanjs/devkit/frontendBuild/pagesBundleBuilder.ts
731
- import path7 from "path";
732
-
733
- // pkgs/@akanjs/devkit/transforms/externalizeFrameworkPlugin.ts
734
- import path5 from "path";
735
- var DEFAULT_INCLUDE = ["akanjs/", "@apps/", "@libs/"];
736
- var DEFAULT_EXCLUDE_EXACT = new Set(["akanjs/webkit", "@akanjs/cli", "@akanjs/devkit"]);
737
- var DEFAULT_EXCLUDE_PREFIX = ["@akanjs/cli/", "@akanjs/devkit/"];
738
- var OPTIONAL_BACKEND_EXTERNAL_EXACT = new Set([
739
- "@libsql/client",
740
- "bullmq",
741
- "ioredis",
742
- "postgres",
743
- "protobufjs"
744
- ]);
745
- var RUNTIME_EXTERNAL_EXACT = new Set([
746
- "react",
747
- "react-dom",
748
- "react/jsx-runtime",
749
- "react/jsx-dev-runtime",
750
- "react-server-dom-webpack",
751
- "react-server-dom-webpack/server.node",
752
- "react-server-dom-webpack/client.node",
753
- "react-server-dom-webpack/client.browser"
754
- ]);
755
- var RUNTIME_EXTERNAL_PREFIX = ["react-dom/", "react-server-dom-webpack/"];
756
- var CANDIDATE_EXTS = [".tsx", ".ts", ".jsx", ".js", ".mjs", ".cjs"];
757
- async function createExternalizeFrameworkPlugin(options) {
758
- const tsconfig = await options.app.getTsConfig();
759
- const includePrefixes = options.include ?? DEFAULT_INCLUDE;
760
- const extraExact = new Set(options.extra ?? []);
761
- const workspaceRoot = options.app.workspace.workspaceRoot;
762
- const tsconfigPaths = tsconfig.compilerOptions.paths ?? {};
763
- const rootEntries = Object.entries(tsconfigPaths).filter(([k]) => !k.endsWith("/*")).map(([k, v]) => ({ pkg: k, entryFile: v[0] ?? null })).filter((e) => e.entryFile !== null);
764
- const wildcardEntries = Object.entries(tsconfigPaths).filter(([k]) => k.endsWith("/*")).map(([k, v]) => ({ prefix: k.slice(0, -1), replacements: v })).sort((a, b) => b.prefix.length - a.prefix.length);
765
- async function resolveWorkspaceSubpath(spec) {
766
- if (!workspaceRoot)
767
- return null;
768
- for (const { prefix, replacements } of wildcardEntries) {
769
- if (!spec.startsWith(prefix))
770
- continue;
771
- const suffix = spec.slice(prefix.length);
772
- for (const repl of replacements) {
773
- const replPath = repl?.endsWith("/*") ? repl.slice(0, -1) : repl ?? "";
774
- if (!replPath)
775
- continue;
776
- const candidate = path5.resolve(workspaceRoot, replPath + suffix);
777
- const hit = await firstExisting(candidate);
778
- if (hit)
779
- return hit;
780
- }
781
- }
782
- for (const { pkg, entryFile } of rootEntries) {
783
- if (!spec.startsWith(`${pkg}/`))
784
- continue;
785
- const suffix = spec.slice(pkg.length + 1);
786
- const pkgDir = path5.dirname(path5.resolve(workspaceRoot, entryFile));
787
- const candidate = path5.join(pkgDir, suffix);
788
- const hit = await firstExisting(candidate);
789
- if (hit)
790
- return hit;
791
- }
792
- return null;
793
- }
794
- return {
795
- name: "akan-externalize-framework",
796
- setup(build) {
797
- build.onResolve({ filter: /.*/ }, async (args) => {
798
- const spec = args.path;
799
- if (spec === "." || spec === ".." || spec.startsWith("./") || spec.startsWith("../") || spec.startsWith("/"))
800
- return;
801
- if (extraExact.has(spec) || DEFAULT_EXCLUDE_EXACT.has(spec))
802
- return { path: spec, external: true };
803
- for (const prefix of DEFAULT_EXCLUDE_PREFIX) {
804
- if (spec.startsWith(prefix))
805
- return { path: spec, external: true };
806
- }
807
- if (OPTIONAL_BACKEND_EXTERNAL_EXACT.has(spec))
808
- return { path: spec, external: true };
809
- if (RUNTIME_EXTERNAL_EXACT.has(spec))
810
- return { path: spec, external: true };
811
- for (const prefix of RUNTIME_EXTERNAL_PREFIX) {
812
- if (spec.startsWith(prefix))
813
- return { path: spec, external: true };
814
- }
815
- if (!includePrefixes.some((prefix) => spec.startsWith(prefix)))
816
- return;
817
- const resolved = await resolveWorkspaceSubpath(spec);
818
- return resolved ? { path: resolved } : undefined;
819
- });
820
- }
821
- };
822
- }
823
- async function firstExisting(basePath) {
824
- if (await Bun.file(basePath).exists())
825
- return basePath;
826
- for (const ext of CANDIDATE_EXTS) {
827
- const candidate = `${basePath}${ext}`;
828
- if (await Bun.file(candidate).exists())
829
- return candidate;
830
- }
831
- for (const ext of CANDIDATE_EXTS) {
832
- const candidate = path5.join(basePath, `index${ext}`);
833
- if (await Bun.file(candidate).exists())
834
- return candidate;
835
- }
836
- return null;
837
- }
838
-
839
- // pkgs/@akanjs/devkit/transforms/useClientBundlePlugin.ts
840
- function createUseClientBundlePlugin(options = {}) {
841
- return {
842
- name: "akan-use-client-bundle",
843
- setup(build) {
844
- build.onLoad({ filter: /\.(tsx|ts|jsx|js)$/ }, async (args) => {
845
- if (args.path.includes("/node_modules/"))
846
- return;
847
- let source;
848
- try {
849
- source = await Bun.file(args.path).text();
850
- } catch {
851
- return;
852
- }
853
- const stubbed = transformUseClient(source, {
854
- path: args.path,
855
- workspaceRoot: options.workspaceRoot
856
- });
857
- if (stubbed === null)
858
- return;
859
- return { contents: stubbed, loader: loaderFor(args.path) };
860
- });
861
- }
862
- };
863
- }
864
-
865
- // pkgs/@akanjs/devkit/frontendBuild/pagesEntrySourceGenerator.ts
866
- import path6 from "path";
867
- class PagesEntrySourceGenerator {
868
- #pageEntries;
869
- #asyncDefaults = new Map;
870
- constructor(pageEntries) {
871
- this.#pageEntries = pageEntries;
872
- }
873
- static generate(pageEntries) {
874
- return new PagesEntrySourceGenerator(pageEntries).generate();
875
- }
876
- generate() {
877
- const lines = this.#pageEntries.map(({ key, moduleAbsPath }) => {
878
- const specifier = PagesEntrySourceGenerator.#toImportSpecifier(moduleAbsPath);
879
- return ` ${JSON.stringify(key)}: () => import(${JSON.stringify(specifier)}),`;
880
- });
881
- return `export const pages = {
882
- ${lines.join(`
883
- `)}
884
- };
885
- `;
886
- }
887
- static async generateStatic(pageEntries, { fromDir } = {}) {
888
- return await new PagesEntrySourceGenerator(pageEntries).generateStatic({ fromDir });
889
- }
890
- async generateStatic({ fromDir } = {}) {
891
- const imports = this.#pageEntries.map(({ moduleAbsPath }, index) => {
892
- const specifier = fromDir ? PagesEntrySourceGenerator.#toRelativeSpecifier(fromDir, moduleAbsPath) : PagesEntrySourceGenerator.#toImportSpecifier(moduleAbsPath);
893
- return `import * as page${index} from ${JSON.stringify(specifier)};`;
894
- });
895
- const entries = await Promise.all(this.#pageEntries.map(async ({ key, moduleAbsPath }, index) => {
896
- const isAsyncDefault = await this.#isAsyncDefault(moduleAbsPath);
897
- return ` ${JSON.stringify(key)}: { loader: async () => page${index}, isAsyncDefault: ${isAsyncDefault} },`;
898
- }));
899
- return `${imports.join(`
900
- `)}
901
- export const pages = {
902
- ${entries.join(`
903
- `)}
904
- };
905
- `;
906
- }
907
- async generateHotReplace({
908
- fromDir,
909
- ownerId,
910
- moduleIds
911
- }) {
912
- const entries = await Promise.all(this.#pageEntries.map(async ({ key, moduleAbsPath }) => {
913
- const specifier = PagesEntrySourceGenerator.#toRelativeSpecifier(fromDir, moduleAbsPath);
914
- const isAsyncDefault = await this.#isAsyncDefault(moduleAbsPath);
915
- return ` ${JSON.stringify(key)}: { loader: async () => require(${JSON.stringify(specifier)}), isAsyncDefault: ${isAsyncDefault} },`;
916
- }));
917
- return `__akan.accept(${JSON.stringify(ownerId)}, ${JSON.stringify(moduleIds)}, async () => {
918
- const replaced = await akanWebkit.replacePages({
919
- ${entries.join(`
920
- `)}
921
- });
922
- if (!replaced) throw new Error("the route table no longer matches the pages it was built from");
923
- });
924
- `;
925
- }
926
- #isAsyncDefault(moduleAbsPath) {
927
- const known = this.#asyncDefaults.get(moduleAbsPath);
928
- if (known)
929
- return known;
930
- const detected = AsyncDefaultExportDetector.detect(moduleAbsPath);
931
- this.#asyncDefaults.set(moduleAbsPath, detected);
932
- return detected;
933
- }
934
- static #toImportSpecifier(moduleAbsPath) {
935
- return path6.resolve(moduleAbsPath).split(path6.sep).join("/");
936
- }
937
- static #toRelativeSpecifier(fromDir, moduleAbsPath) {
938
- const relative = path6.relative(fromDir, path6.resolve(moduleAbsPath)).split(path6.sep).join("/");
939
- return relative.startsWith(".") ? relative : `./${relative}`;
940
- }
941
- }
942
-
943
- // pkgs/@akanjs/devkit/frontendBuild/pagesBundleBuilder.ts
944
- var VIRTUAL_PAGES_ENTRY = "akan-pages-entry";
945
-
946
- class PagesBundleBuilder {
947
- #app;
948
- #command;
949
- #pageEntries;
950
- #started = Date.now();
951
- constructor(app, command = "start", pageEntries) {
952
- this.#app = app;
953
- this.#command = command;
954
- this.#pageEntries = pageEntries;
955
- }
956
- async build() {
957
- const akanConfig = await this.#app.getConfig();
958
- const resolvedEntries = this.#pageEntries ?? await resolveSsrPageEntriesForApp(this.#app, await this.#app.getPageKeys());
959
- const entrySource = PagesEntrySourceGenerator.generate(resolvedEntries);
960
- const workspaceRoot = this.#app.workspace.workspaceRoot;
961
- const result = await Bun.build({
962
- entrypoints: [VIRTUAL_PAGES_ENTRY],
963
- outdir: `${this.#artifactDir}/server`,
964
- target: "bun",
965
- format: "esm",
966
- splitting: this.#splitting,
967
- minify: this.#command === "build",
968
- naming: {
969
- entry: "pages-[hash].[ext]",
970
- chunk: "chunks/[name]-[hash].[ext]",
971
- asset: "assets/[name]-[hash].[ext]"
972
- },
973
- define: bundleDefine(this.#app, this.#command, "ssr"),
974
- plugins: [
975
- PagesBundleBuilder.createPagesEntryPlugin(entrySource),
976
- PagesBundleBuilder.createCssStubPlugin(),
977
- PagesBundleBuilder.createServerUseClientFetchPlugin(),
978
- await createExternalizeFrameworkPlugin({ app: this.#app, extra: akanConfig.externalLibs }),
979
- akanConfig.barrelImports.length > 0 ? await createBarrelImportsPlugin(this.#app, {
980
- pipeAfter: (source, args) => transformUseClient(source, {
981
- path: args.path,
982
- workspaceRoot
983
- })
984
- }) : createUseClientBundlePlugin({ workspaceRoot })
985
- ]
986
- });
987
- if (!result.success)
988
- throw new AggregateError(result.logs, "[PagesBundleBuilder] Bun.build failed");
989
- const entryArtifact = result.outputs.find((a) => a.kind === "entry-point");
990
- if (!entryArtifact)
991
- throw new Error("[PagesBundleBuilder] Bun.build emitted no entry-point artifact");
992
- const bundlePath = path7.resolve(entryArtifact.path);
993
- const buildId = Date.now();
994
- const outputBytes = result.outputs.reduce((sum, output) => sum + output.size, 0);
995
- const chunkCount = result.outputs.filter((output) => output.kind === "chunk").length;
996
- this.#app.verbose(`[PagesBundleBuilder] ${path7.basename(bundlePath)} emitted in ${Date.now() - this.#started}ms splitting=${this.#splitting} entry=${entryArtifact.size} bytes outputs=${result.outputs.length} chunks=${chunkCount} total=${outputBytes} bytes`);
997
- return {
998
- bundlePath,
999
- buildId,
1000
- splitting: this.#splitting,
1001
- entryBytes: entryArtifact.size,
1002
- outputBytes,
1003
- outputCount: result.outputs.length,
1004
- chunkCount
1005
- };
1006
- }
1007
- get #artifactDir() {
1008
- return `${this.#command === "build" ? this.#app.dist.cwdPath : this.#app.cwdPath}/.akan/artifact`;
1009
- }
1010
- get #splitting() {
1011
- return process.env.AKAN_SERVER_PAGES_SPLITTING === "1";
1012
- }
1013
- static createPagesEntryPlugin(source) {
1014
- return {
1015
- name: "akan-pages-entry",
1016
- setup(build) {
1017
- build.onResolve({ filter: /^akan-pages-entry$/ }, () => ({
1018
- path: VIRTUAL_PAGES_ENTRY,
1019
- namespace: "akan-virtual"
1020
- }));
1021
- build.onLoad({ filter: /^akan-pages-entry$/, namespace: "akan-virtual" }, () => ({
1022
- contents: source,
1023
- loader: "tsx"
1024
- }));
1025
- }
1026
- };
1027
- }
1028
- static createCssStubPlugin() {
1029
- return {
1030
- name: "akan-css-stub",
1031
- setup(build) {
1032
- build.onLoad({ filter: /\.css$/ }, () => ({
1033
- contents: "",
1034
- loader: "js"
1035
- }));
1036
- }
1037
- };
1038
- }
1039
- static createServerUseClientFetchPlugin() {
1040
- return {
1041
- name: "akan-server-use-client-fetch",
1042
- setup(build) {
1043
- build.onLoad({ filter: /[/\\]lib[/\\]useClient\.(ts|tsx|js|jsx)$/ }, async (args) => {
1044
- const source = await Bun.file(args.path).text();
1045
- const transformed = PagesBundleBuilder.transformServerUseClientFetchSource(source);
1046
- if (transformed === source)
1047
- return;
1048
- return { contents: transformed, loader: loaderFor(args.path) };
1049
- });
1050
- }
1051
- };
1052
- }
1053
- static transformServerUseClientFetchSource(source) {
1054
- if (!source.includes(`with { type: "macro" }`) || !source.includes("FetchClient.build"))
1055
- return source;
1056
- return source.replace(/import\s+\{\s*getSerializedSignal\s*\}\s+from\s+["']\.\/sig["']\s+with\s+\{\s*type\s*:\s*["']macro["']\s*\};/, `import { fetch as serverFetch } from "./sig";`).replace(/const\s+fetchProto\s*=\s*FetchClient\.build<[^;]+;/, "const fetchProto = FetchClient.build<typeof signal>(cnst, serverFetch.serializedSignal, { Err: pageProto.Err, base: serverFetch });");
1057
- }
1058
- }
1059
-
1060
- // pkgs/@akanjs/devkit/frontendBuild/csrArtifactBuilder.ts
1061
- import { mkdir, rm, unlink } from "fs/promises";
1062
- import path8 from "path";
1063
- class CsrArtifactBuilder {
1064
- #app;
1065
- #command;
1066
- #lang;
1067
- constructor(app, command = "start", lang = "en") {
1068
- this.#app = app;
1069
- this.#command = command;
1070
- this.#lang = lang;
1071
- }
1072
- async build() {
1073
- const pageKeys = await this.#app.getPageKeys();
1074
- if (pageKeys.length === 0) {
1075
- this.#app.log(`[cli] no route files under ${this.#app.cwdPath}/page \u2014 skipping CSR build`);
1076
- return null;
1077
- }
1078
- const pageEntries = await resolveSsrPageEntriesForApp(this.#app, pageKeys);
1079
- const akanConfig = await this.#app.getConfig();
1080
- const cssAssets = await this.#loadCssAssets();
1081
- const basePaths = [...akanConfig.basePaths];
1082
- const htmlBasePaths = basePaths.length > 0 ? basePaths : [""];
1083
- await rm(this.#outputDir, { recursive: true, force: true });
1084
- await mkdir(this.#generatedDir, { recursive: true });
1085
- const generatedFiles = Object.fromEntries((await Promise.all(htmlBasePaths.map(async (basePath) => [
1086
- this.#createHtmlFile(basePath),
1087
- await this.#createEntryFile(basePath, CsrArtifactBuilder.pageEntriesForBasePath(pageEntries, basePath, basePaths))
1088
- ]))).flat());
1089
- const result = await Bun.build({
1090
- target: "browser",
1091
- entrypoints: htmlBasePaths.map((basePath) => this.#generatedPath(CsrArtifactBuilder.htmlFilename(basePath))),
1092
- files: generatedFiles,
1093
- root: this.#generatedDir,
1094
- outdir: this.#outputDir,
1095
- splitting: false,
1096
- minify: true,
1097
- env: "AKAN_PUBLIC_*",
1098
- define: bundleDefine(this.#app, this.#command, "csr"),
1099
- optimizeImports: akanConfig.optimizeImports,
1100
- plugins: [PagesBundleBuilder.createCssStubPlugin()]
1101
- });
1102
- if (!result.success) {
1103
- const logs = result.logs.map((log) => log.message).join(`
1104
- `);
1105
- throw new Error(`[csr-build] failed${logs ? `
1106
- ${logs}` : ""}`);
1107
- }
1108
- await this.#inlineCsrArtifacts(cssAssets);
1109
- this.#app.verbose(`[csr-build] output -> ${this.#outputDir}`);
1110
- return { outputDir: this.#outputDir };
1111
- }
1112
- static pageEntriesForBasePath(pageEntries, basePath, basePaths) {
1113
- return pageEntries.filter((entry) => {
1114
- const entryBasePath = getPageKeyBasePath(entry.key, basePaths);
1115
- return entryBasePath === null || entryBasePath === basePath;
1116
- });
1117
- }
1118
- static htmlFilename(basePath) {
1119
- return `${basePath || "index"}.html`;
1120
- }
1121
- static entryFilename(basePath) {
1122
- return `${basePath || "index"}.csr.tsx`;
1123
- }
1124
- static basePathOfHtml(htmlPath) {
1125
- const name = path8.basename(htmlPath, ".html");
1126
- return name === "index" ? "" : name;
1127
- }
1128
- get #outputDir() {
1129
- return path8.join(this.#command === "build" ? this.#app.dist.cwdPath : this.#app.cwdPath, this.#command === "build" ? "csr" : ".akan/artifact/csr");
1130
- }
1131
- get #generatedDir() {
1132
- return path8.join(this.#app.cwdPath, ".akan/generated/csr");
1133
- }
1134
- get #artifactDir() {
1135
- return path8.join(this.#command === "build" ? this.#app.dist.cwdPath : this.#app.cwdPath, ".akan/artifact");
1136
- }
1137
- #generatedPath(filename) {
1138
- return path8.join(this.#generatedDir, filename);
1139
- }
1140
- async#createEntryFile(basePath, pageEntries) {
1141
- return [
1142
- this.#generatedPath(CsrArtifactBuilder.entryFilename(basePath)),
1143
- `import { bootCsr } from "akanjs/webkit";
1144
- ${await PagesEntrySourceGenerator.generateStatic(pageEntries)}
1145
- void bootCsr(pages);
1146
- `
1147
- ];
1148
- }
1149
- #createHtmlFile(basePath) {
1150
- return [
1151
- this.#generatedPath(CsrArtifactBuilder.htmlFilename(basePath)),
1152
- `<!doctype html>
1153
- <html lang="${this.#lang}">
1154
- <head>
1155
- <meta charset="utf-8" />
1156
- <title>${this.#app.name}</title>
1157
- <base href="/" />
1158
- <meta name="viewport" content="width=device-width, initial-scale=1" />
1159
- </head>
1160
- <body>
1161
- <div id="root"></div>
1162
- <script type="module" src="./${CsrArtifactBuilder.entryFilename(basePath)}"></script>
1163
- </body>
1164
- </html>
1165
- `
1166
- ];
1167
- }
1168
- async#loadCssAssets() {
1169
- const artifactFile = Bun.file(path8.join(this.#artifactDir, "base-artifact.json"));
1170
- if (!await artifactFile.exists())
1171
- return {};
1172
- const artifact = await artifactFile.json();
1173
- return artifact.cssAssets ?? {};
1174
- }
1175
- async#inlineCsrArtifacts(cssAssets) {
1176
- const jsFiles = new Set;
1177
- for (const htmlPath of await this.#listOutputFiles((filePath) => filePath.endsWith(".html"))) {
1178
- const htmlFile = Bun.file(htmlPath);
1179
- if (!await htmlFile.exists())
1180
- continue;
1181
- const basePath = CsrArtifactBuilder.basePathOfHtml(htmlPath);
1182
- const cssAsset = cssAssets[basePath];
1183
- if (!cssAsset) {
1184
- this.#app.logger.warn(`[csr-build] base-artifact.json has no compiled stylesheet for ${basePath || "root"}; ${path8.basename(htmlPath)} ships without CSS`);
1185
- }
1186
- const inlined = await this.#inlineHtmlAssets(await htmlFile.text(), htmlPath, cssAsset);
1187
- for (const filePath of inlined.jsFiles)
1188
- jsFiles.add(filePath);
1189
- await Bun.write(htmlPath, inlined.html);
1190
- }
1191
- for (const filePath of jsFiles)
1192
- await unlink(filePath).catch(() => {
1193
- return;
1194
- });
1195
- const remainingAssets = await this.#listOutputFiles((filePath) => filePath.endsWith(".js") || filePath.endsWith(".css"));
1196
- if (remainingAssets.length > 0) {
1197
- throw new Error(`[csr-build] expected single-file HTML, but CSR assets remain:
1198
- ${remainingAssets.join(`
1199
- `)}`);
1200
- }
1201
- }
1202
- async#inlineHtmlAssets(html, htmlPath, cssAsset) {
1203
- let next = html;
1204
- if (cssAsset) {
1205
- const css = await Bun.file(path8.join(this.#artifactDir, cssAsset.cssRelPath)).text();
1206
- next = CsrArtifactBuilder.injectBeforeHeadEnd(next, CsrArtifactBuilder.createInlineStyle(css));
1207
- }
1208
- const jsFiles = [];
1209
- next = await CsrArtifactBuilder.replaceModuleScriptSrc(next, async (src) => {
1210
- const jsPath = CsrArtifactBuilder.resolveHtmlAssetPath(htmlPath, src);
1211
- jsFiles.push(jsPath);
1212
- return await Bun.file(jsPath).text();
1213
- });
1214
- return { html: next, jsFiles };
1215
- }
1216
- async#listOutputFiles(predicate) {
1217
- const glob = new Bun.Glob("**/*");
1218
- const files = [];
1219
- for await (const filePath of glob.scan({ cwd: this.#outputDir, absolute: true })) {
1220
- if (predicate(filePath))
1221
- files.push(filePath);
1222
- }
1223
- return files.sort();
1224
- }
1225
- static injectBeforeHeadEnd(html, snippet) {
1226
- const scannable = CsrArtifactBuilder.blankEmbeddedContent(html);
1227
- const headEnd = scannable.search(/<\/head\s*>/i);
1228
- if (headEnd !== -1)
1229
- return `${html.slice(0, headEnd)}${snippet}
1230
- ${html.slice(headEnd)}`;
1231
- const bodyStart = scannable.search(/<body(?:\s|>)/i);
1232
- if (bodyStart !== -1)
1233
- return `${html.slice(0, bodyStart)}${snippet}
1234
- ${html.slice(bodyStart)}`;
1235
- return `${html}
1236
- ${snippet}`;
1237
- }
1238
- static blankEmbeddedContent(html) {
1239
- return html.replace(/<script\b[^>]*>[\s\S]*?<\/script\s*>|<style\b[^>]*>[\s\S]*?<\/style\s*>|<!--[\s\S]*?-->/gi, (match) => " ".repeat(match.length));
1240
- }
1241
- static createInlineStyle(css) {
1242
- return `<style data-akan-css="active">
1243
- ${css.replace(/<\/style/gi, "<\\/style")}
1244
- </style>`;
1245
- }
1246
- static async replaceModuleScriptSrc(html, loadScript) {
1247
- const scriptRe = /<script\b(?=[^>]*\btype=["']module["'])(?=[^>]*\bsrc=["']([^"']+)["'])[^>]*>\s*<\/script>/gi;
1248
- let result = "";
1249
- let lastIndex = 0;
1250
- let matched = false;
1251
- for (const match of html.matchAll(scriptRe)) {
1252
- const full = match[0];
1253
- const src = match[1];
1254
- if (match.index === undefined || !src)
1255
- continue;
1256
- matched = true;
1257
- result += html.slice(lastIndex, match.index);
1258
- result += `<script type="module">
1259
- ${CsrArtifactBuilder.escapeInlineScript(await loadScript(src))}
1260
- </script>`;
1261
- lastIndex = match.index + full.length;
1262
- }
1263
- if (!matched)
1264
- return html;
1265
- return result + html.slice(lastIndex);
1266
- }
1267
- static escapeInlineScript(source) {
1268
- return source.replace(/<\/script/gi, "<\\/script");
1269
- }
1270
- static resolveHtmlAssetPath(htmlPath, src) {
1271
- if (/^[a-z][a-z0-9+.-]*:/i.test(src) || src.startsWith("//")) {
1272
- throw new Error(`[csr-build] cannot inline external script: ${src}`);
1273
- }
1274
- const normalized = src.startsWith("/") ? src.slice(1) : src;
1275
- return path8.resolve(path8.dirname(htmlPath), normalized);
1276
- }
1277
- }
1278
-
1279
- // pkgs/@akanjs/devkit/frontendBuild/csrDevBundler.ts
1280
- import fs3 from "fs";
1281
- import { mkdir as mkdir3 } from "fs/promises";
1282
- import path13 from "path";
1283
- import { CSR_DEV_DIRNAME, CSR_DEV_MANIFEST_FILE as CSR_DEV_MANIFEST_FILE2 } from "akanjs/server/hmr/csrDevManifest";
1284
- import { isAkanRuntimeMetadataFile } from "akanjs/server/hmr/runtimeMetadataFile";
1285
-
1286
- // pkgs/@akanjs/devkit/frontendBuild/csrDevArtifactWriter.ts
1287
- import { mkdir as mkdir2, readdir, rename, rm as rm2 } from "fs/promises";
1288
- import path10 from "path";
1289
- import {
1290
- CSR_DEV_APP_FILE,
1291
- CSR_DEV_MANIFEST_FILE,
1292
- CSR_DEV_ROUTE_PREFIX,
1293
- csrDevModuleFile
1294
- } from "akanjs/server/hmr/csrDevManifest";
1295
-
1296
- // pkgs/@akanjs/devkit/frontendBuild/csrDevPaths.ts
1297
- import fs from "fs";
1298
- import path9 from "path";
1299
-
1300
- class CsrDevPaths {
1301
- static modulePrefix = "akan-module:";
1302
- static stubPrefix = "stub:";
1303
- root;
1304
- constructor(workspaceRoot) {
1305
- this.root = CsrDevPaths.realpath(workspaceRoot);
1306
- }
1307
- idOf(file) {
1308
- const real = CsrDevPaths.realpath(file);
1309
- const relative = path9.relative(this.root, real);
1310
- const id = relative.startsWith("..") || path9.isAbsolute(relative) ? real : relative;
1311
- return id.split(path9.sep).join("/");
1312
- }
1313
- fileOf(id) {
1314
- return path9.isAbsolute(id) ? id : path9.join(this.root, id);
1315
- }
1316
- static isStub(target) {
1317
- return target.startsWith(CsrDevPaths.stubPrefix);
1318
- }
1319
- static isVendorFile(file) {
1320
- return file.includes(`${path9.sep}node_modules${path9.sep}`);
1321
- }
1322
- static isScript(file) {
1323
- return /\.[cm]?[jt]sx?$/.test(file);
1324
- }
1325
- static mtimeOf(file) {
1326
- return fs.statSync(file, { throwIfNoEntry: false })?.mtimeMs ?? -1;
1327
- }
1328
- static async hashOf(file) {
1329
- const source = Bun.file(file);
1330
- return await source.exists() ? Bun.hash(await source.arrayBuffer()).toString(36) : "";
1331
- }
1332
- static realpath(file) {
1333
- return fs.existsSync(file) ? fs.realpathSync(file) : path9.resolve(file);
1334
- }
1335
- static tryResolve(specifier, from) {
1336
- try {
1337
- return Bun.resolveSync(specifier, from);
1338
- } catch {
1339
- return null;
1340
- }
1341
- }
1342
- static lineCount(text) {
1343
- let count = 0;
1344
- for (let index = text.indexOf(`
1345
- `);index !== -1; index = text.indexOf(`
1346
- `, index + 1))
1347
- count += 1;
1348
- return count;
1349
- }
1350
- }
1351
-
1352
- // pkgs/@akanjs/devkit/frontendBuild/csrDevArtifactWriter.ts
1353
- class CsrDevArtifactWriter {
1354
- static #keptPatches = 40;
1355
- static #keptVendors = 3;
1356
- #outDir;
1357
- constructor(outDir) {
1358
- this.#outDir = outDir;
1359
- }
1360
- async reset() {
1361
- await rm2(this.#outDir, { recursive: true, force: true });
1362
- await mkdir2(path10.join(this.#outDir, "modules"), { recursive: true });
1363
- }
1364
- async readJson(name) {
1365
- const file = Bun.file(path10.join(this.#outDir, name));
1366
- if (!await file.exists())
1367
- return null;
1368
- return await file.json().catch(() => null);
1369
- }
1370
- async writeJson(name, value) {
1371
- await this.#writeAtomic(name, JSON.stringify(value));
1372
- }
1373
- async writeModules(compiled) {
1374
- await mkdir2(path10.join(this.#outDir, "modules"), { recursive: true });
1375
- const helpers = new Map(compiled.flatMap((module) => module.helpers ? [[module.helpers.hash, module.helpers]] : []));
1376
- await Promise.all([
1377
- ...compiled.flatMap((module) => [
1378
- Bun.write(this.#modulePath(module.id, ".js"), module.factory),
1379
- ...module.sourceMap ? [Bun.write(this.#modulePath(module.id, ".js.map"), module.sourceMap)] : []
1380
- ]),
1381
- ...[...helpers.values()].map((shared) => Bun.write(this.#helpersPath(shared.hash), shared.definition))
1382
- ]);
1383
- }
1384
- async forgetModules(ids) {
1385
- await Promise.all(ids.flatMap((id) => [
1386
- rm2(this.#modulePath(id, ".js"), { force: true }),
1387
- rm2(this.#modulePath(id, ".js.map"), { force: true })
1388
- ]));
1389
- }
1390
- async writeVendor(graph) {
1391
- const ids = Object.keys(graph.modules).filter((id) => graph.modules[id]?.vendor).sort();
1392
- const stubs = new Set(Object.values(graph.modules).flatMap((module) => module.deps.filter(CsrDevPaths.isStub)));
1393
- const lines = [await this.#helperDefinitions(graph, ids), ...await this.#defineLines(ids)];
1394
- for (const stub of [...stubs].sort()) {
1395
- const message = `[akan-csr] ${stub.slice(CsrDevPaths.stubPrefix.length)} is a Node built-in the browser does not have`;
1396
- lines.push(`__akan.define(${JSON.stringify(stub)}, function () {
1397
- throw new Error(${JSON.stringify(message)});
1398
- });
1399
- `);
1400
- }
1401
- const code = lines.join("");
1402
- const vendorFile = `vendor-${Bun.hash(code).toString(36)}.js`;
1403
- await this.#writeAtomic(vendorFile, code);
1404
- return vendorFile;
1405
- }
1406
- async writeApp(graph, generation) {
1407
- const ids = Object.keys(graph.modules).filter((id) => !graph.modules[id]?.vendor).sort();
1408
- const lines = await this.#defineLines(ids);
1409
- const blocks = ids.map((id, index) => [id, lines[index] ?? ""]);
1410
- const start = `__akan.start(${JSON.stringify({ generation, refresh: graph.refresh })});
1411
- `;
1412
- await this.#writeWithSourceMap(CSR_DEV_APP_FILE, await this.#helperDefinitions(graph, ids), blocks, start);
1413
- }
1414
- async writePatch(generation, modules) {
1415
- const patchFile = `patch-${generation}.js`;
1416
- const blocks = modules.map((module, index) => [module.id, `${JSON.stringify(module.id)}: ${module.factory}${index < modules.length - 1 ? `,
1417
- ` : `
1418
- `}`]);
1419
- const helpers = new Map(modules.flatMap((module) => module.helpers ? [[module.helpers.hash, module.helpers]] : []));
1420
- const header = `${[...helpers.values()].map((shared) => shared.definition).join("")}__akan.update(${generation}, {
1421
- `;
1422
- await this.#writeWithSourceMap(patchFile, header, blocks, `});
1423
- `);
1424
- return `${CSR_DEV_ROUTE_PREFIX}${patchFile}`;
1425
- }
1426
- async writeState(graph, manifest) {
1427
- await this.writeJson("graph.json", graph);
1428
- await this.writeJson(CSR_DEV_MANIFEST_FILE, manifest);
1429
- }
1430
- async prune(vendorFile, generation) {
1431
- const names = await readdir(this.#outDir);
1432
- const stale = names.filter((name) => {
1433
- const patch = /^patch-(\d+)\.js(?:\.layout\.json)?$/.exec(name);
1434
- return patch ? Number(patch[1]) <= generation - CsrDevArtifactWriter.#keptPatches : false;
1435
- });
1436
- const vendors = names.filter((name) => /^vendor-[\w-]+\.js$/.test(name) && name !== vendorFile).map((name) => ({ name, mtimeMs: CsrDevPaths.mtimeOf(path10.join(this.#outDir, name)) })).sort((a, b) => b.mtimeMs - a.mtimeMs).slice(CsrDevArtifactWriter.#keptVendors - 1).map(({ name }) => name);
1437
- await Promise.all([...stale, ...vendors].map((name) => rm2(path10.join(this.#outDir, name), { force: true })));
1438
- }
1439
- async#helperDefinitions(graph, ids) {
1440
- const hashes = [...new Set(ids.flatMap((id) => graph.modules[id]?.helpers ?? []))].sort();
1441
- return (await Promise.all(hashes.map((hash) => Bun.file(this.#helpersPath(hash)).text()))).join("");
1442
- }
1443
- async#defineLines(ids) {
1444
- return await Promise.all(ids.map(async (id) => `__akan.define(${JSON.stringify(id)}, ${await Bun.file(this.#modulePath(id, ".js")).text()});
1445
- `));
1446
- }
1447
- async#writeWithSourceMap(name, header, blocks, footer) {
1448
- const layout = { lineCount: 0, modules: [] };
1449
- let line = CsrDevPaths.lineCount(header);
1450
- for (const [id, text] of blocks) {
1451
- layout.modules.push([id, line + 1]);
1452
- line += CsrDevPaths.lineCount(text);
1453
- }
1454
- const code = `${header}${blocks.map(([, text]) => text).join("")}${footer}//# sourceMappingURL=${name}.map
1455
- `;
1456
- layout.lineCount = CsrDevPaths.lineCount(code);
1457
- await this.writeJson(`${name}.layout.json`, layout);
1458
- await this.#writeAtomic(name, code);
1459
- }
1460
- async#writeAtomic(name, content) {
1461
- const target = path10.join(this.#outDir, name);
1462
- const temp = `${target}.${process.pid}.tmp`;
1463
- await Bun.write(temp, content);
1464
- await rename(temp, target);
1465
- }
1466
- #modulePath(id, extension) {
1467
- return path10.join(this.#outDir, csrDevModuleFile(id, extension));
1468
- }
1469
- #helpersPath(hash) {
1470
- return path10.join(this.#outDir, "modules", `helpers-${hash}.js`);
1471
- }
1472
- }
1473
-
1474
- // pkgs/@akanjs/devkit/frontendBuild/csrDevModuleCompiler.ts
1475
- import path12 from "path";
1476
- import { CSR_DEV_ROUTE_PREFIX as CSR_DEV_ROUTE_PREFIX2 } from "akanjs/server/hmr/csrDevManifest";
1477
-
1478
- // pkgs/@akanjs/devkit/frontendBuild/csrDevResolver.ts
1479
- import fs2 from "fs";
1480
- import path11 from "path";
1481
- class CsrDevResolver {
1482
- static inline = "inline";
1483
- #paths;
1484
- #context;
1485
- #entryFiles;
1486
- #resolution = new Map;
1487
- #fallbacks = new Set;
1488
- #prepassDone = false;
1489
- constructor({ paths, context, entryFiles, resolution = {} }) {
1490
- this.#paths = paths;
1491
- this.#context = context;
1492
- this.#entryFiles = entryFiles;
1493
- for (const [importer, bySpecifier] of Object.entries(resolution))
1494
- this.#resolution.set(paths.fileOf(importer), new Map(Object.entries(bySpecifier).map(([specifier, target]) => [
1495
- specifier,
1496
- CsrDevPaths.isStub(target) ? target : paths.fileOf(target)
1497
- ])));
1498
- }
1499
- get prepassDone() {
1500
- return this.#prepassDone;
1501
- }
1502
- get fallbacks() {
1503
- return this.#fallbacks;
1504
- }
1505
- async prepass() {
1506
- const result = await Bun.build({
1507
- entrypoints: [...this.#entryFiles, this.#context.refreshFile],
1508
- target: "browser",
1509
- metafile: true,
1510
- env: "AKAN_PUBLIC_*",
1511
- define: this.#context.define,
1512
- optimizeImports: this.#context.optimizeImports,
1513
- plugins: [PagesBundleBuilder.createCssStubPlugin()]
1514
- });
1515
- const { inputs } = result.metafile;
1516
- for (const [input, { imports }] of Object.entries(inputs)) {
1517
- const importer = CsrDevPaths.realpath(path11.resolve(input));
1518
- for (const record of imports) {
1519
- if (!record.original)
679
+ try {
680
+ const rewritten = await rewriteBarrelImports(content, akanConfig.barrelImports, analyzer);
681
+ if (rewritten !== null)
682
+ source = rewritten;
683
+ } catch {}
684
+ }
685
+ let imports;
686
+ try {
687
+ imports = this.#transpiler.scanImports(source);
688
+ } catch {
689
+ continue;
690
+ }
691
+ const importerDir = path4.dirname(filePath);
692
+ for (const imp of imports) {
693
+ const spec = imp.path;
694
+ if (!spec)
1520
695
  continue;
1521
- if (record.external) {
1522
- if (record.original.startsWith("node:"))
1523
- this.#remember(importer, record.original, `${CsrDevPaths.stubPrefix}${record.original}`);
696
+ if (spec.endsWith(".css")) {
697
+ const cssPath = await this.#resolveCssImport(spec, importerDir);
698
+ cssFiles.add(cssPath);
1524
699
  continue;
1525
700
  }
1526
- const target = path11.resolve(record.path);
1527
- if (fs2.existsSync(target))
1528
- this.#remember(importer, record.original, CsrDevPaths.realpath(target));
701
+ if (NON_SOURCE_EXT_RE.test(spec))
702
+ continue;
703
+ const resolved = await this.#resolveSourceImport(spec, importerDir, resolvePackage);
704
+ if (!resolved || sourceFiles.has(resolved) || isIgnoredNodeModuleSource(resolved))
705
+ continue;
706
+ queue.push(resolved);
1529
707
  }
1530
708
  }
1531
- this.#prepassDone = true;
1532
- }
1533
- resolve(importer, specifier) {
1534
- if (this.#isInlineSpecifier(specifier))
1535
- return CsrDevResolver.inline;
1536
- const known = this.#resolution.get(importer)?.get(specifier);
1537
- const usable = known && (CsrDevPaths.isStub(known) || fs2.existsSync(known)) ? known : null;
1538
- const target = usable ?? this.#resolveUnknown(importer, specifier);
1539
- if (target === null || CsrDevPaths.isStub(target))
1540
- return target;
1541
- if (!CsrDevPaths.isScript(target))
1542
- return CsrDevResolver.inline;
1543
- if (!usable)
1544
- this.#remember(importer, specifier, target);
1545
- return target;
1546
- }
1547
- isRegistryModule(file) {
1548
- if (!CsrDevPaths.isScript(file))
1549
- return false;
1550
- const packageName = CsrDevResolver.#packageNameOf(file);
1551
- if (packageName === null)
1552
- return true;
1553
- return !this.#context.optimizeImports.some((pattern) => {
1554
- const base = CsrDevResolver.#patternBase(pattern);
1555
- return base === packageName || base.startsWith(`${packageName}/`);
1556
- });
709
+ const tokenPaths = await this.#libTokenStylesheets(sourceFiles);
710
+ const cssPaths = [...new Set([...tokenPaths, ...cssFiles])];
711
+ for (const cssPath of cssPaths)
712
+ this.#discoveredCssPaths.add(cssPath);
713
+ return { cssPaths, sourcePaths: [...sourceFiles] };
1557
714
  }
1558
- serialize() {
1559
- return Object.fromEntries([...this.#resolution].map(([importer, bySpecifier]) => [
1560
- this.#paths.idOf(importer),
1561
- Object.fromEntries([...bySpecifier].map(([specifier, target]) => [
1562
- specifier,
1563
- CsrDevPaths.isStub(target) ? target : this.#paths.idOf(target)
1564
- ]))
1565
- ]));
1566
- }
1567
- #resolveUnknown(importer, specifier) {
1568
- if (specifier.startsWith("node:"))
1569
- return `${CsrDevPaths.stubPrefix}${specifier}`;
1570
- const relative = specifier.startsWith(".") || path11.isAbsolute(specifier);
1571
- if (!relative && !this.#prepassDone)
1572
- return null;
1573
- if (!relative) {
1574
- const sibling = this.#resolvedBySibling(importer, specifier);
1575
- if (sibling)
1576
- return sibling;
1577
- }
1578
- const resolved = CsrDevPaths.tryResolve(specifier, path11.dirname(importer));
1579
- if (!resolved)
1580
- throw new Error(`[csr-dev] cannot resolve "${specifier}" from ${this.#paths.idOf(importer)}`);
1581
- if (!path11.isAbsolute(resolved))
1582
- return `${CsrDevPaths.stubPrefix}${specifier}`;
1583
- if (!relative)
1584
- this.#fallbacks.add(specifier);
1585
- return CsrDevPaths.realpath(resolved);
1586
- }
1587
- #resolvedBySibling(importer, specifier) {
1588
- const scope = CsrDevResolver.#packageScopeOf(importer);
1589
- for (const [other, bySpecifier] of this.#resolution) {
1590
- const target = bySpecifier.get(specifier);
1591
- if (target && CsrDevResolver.#packageScopeOf(other) === scope)
1592
- return target;
715
+ async#libTokenStylesheets(sourceFiles) {
716
+ const libsRoot = path4.join(this.#app.workspace.workspaceRoot, "libs");
717
+ const libNames = new Set;
718
+ for (const filePath of sourceFiles) {
719
+ const relPath = path4.relative(libsRoot, filePath);
720
+ if (relPath.startsWith("..") || path4.isAbsolute(relPath))
721
+ continue;
722
+ const [libName] = relPath.split(path4.sep);
723
+ if (libName)
724
+ libNames.add(libName);
1593
725
  }
1594
- return null;
1595
- }
1596
- #remember(importer, specifier, target) {
1597
- const byImporter = this.#resolution.get(importer) ?? new Map;
1598
- this.#resolution.set(importer, byImporter.set(specifier, target));
1599
- }
1600
- #isInlineSpecifier(specifier) {
1601
- if (specifier.startsWith(".") || path11.isAbsolute(specifier) || specifier.startsWith("node:"))
1602
- return false;
1603
- return this.#context.optimizeImports.some((pattern) => {
1604
- const base = CsrDevResolver.#patternBase(pattern);
1605
- return specifier === base || specifier.startsWith(`${base}/`);
1606
- });
1607
- }
1608
- static #patternBase(pattern) {
1609
- return pattern.endsWith("/*") ? pattern.slice(0, -2) : pattern;
1610
- }
1611
- static #packageNameOf(file) {
1612
- const marker = `${path11.sep}node_modules${path11.sep}`;
1613
- const index = file.lastIndexOf(marker);
1614
- if (index < 0)
1615
- return null;
1616
- const [scope, name] = file.slice(index + marker.length).split(path11.sep);
1617
- return (scope?.startsWith("@") ? `${scope}/${name}` : scope) || null;
726
+ const tokenPaths = await Promise.all([...libNames].sort().map(async (libName) => {
727
+ const tokensPath = path4.join(libsRoot, libName, "ui/tokens.css");
728
+ return await this.#fileExists(tokensPath) ? tokensPath : null;
729
+ }));
730
+ return tokenPaths.filter((tokensPath) => !!tokensPath);
1618
731
  }
1619
- static #packageScopeOf(file) {
1620
- const marker = `${path11.sep}node_modules${path11.sep}`;
1621
- const index = file.lastIndexOf(marker);
1622
- if (index < 0)
1623
- return "";
1624
- const [scope, name] = file.slice(index + marker.length).split(path11.sep);
1625
- return file.slice(0, index + marker.length) + (scope?.startsWith("@") ? `${scope}${path11.sep}${name}` : scope);
732
+ async compileCss(cssPaths, sourcePaths) {
733
+ const { css } = await this.#compileWithImports(cssPaths, sourcePaths);
734
+ return css;
1626
735
  }
1627
- }
1628
-
1629
- // pkgs/@akanjs/devkit/frontendBuild/csrDevModuleCompiler.ts
1630
- class CsrDevModuleCompiler {
1631
- static #storeRoot = /(?:^|\/)lib\/st\.ts$/;
1632
- static #factoryArgument = /(?<![\w$.])(?:require|module|exports|__akanImport|\$RefreshReg\$|\$RefreshSig\$)(?![\w$]|\s*:)/;
1633
- static #helperPatches = [
1634
- ["__toESM", "(mod, isNodeMode, target)", "toESM"],
1635
- ["__reExport", "(target, mod, secondTarget)", "reExport"]
1636
- ];
1637
- #app;
1638
- #paths;
1639
- #resolver;
1640
- #context;
1641
- #outDir;
1642
- constructor({ app, paths, resolver, context, outDir }) {
1643
- this.#app = app;
1644
- this.#paths = paths;
1645
- this.#resolver = resolver;
1646
- this.#context = context;
1647
- this.#outDir = outDir;
1648
- }
1649
- async compile(startFiles, known) {
1650
- const compiled = new Map;
1651
- const seen = new Set([...known, ...startFiles]);
1652
- let frontier = [...new Set(startFiles)];
1653
- while (frontier.length > 0) {
1654
- const round = await this.#compileRound(frontier);
1655
- if (round.misses.length > 0 && !this.#resolver.prepassDone) {
1656
- this.#app.verbose(`[csr-dev] ${round.misses.length} unresolved import(s); rerunning the resolution build`);
1657
- await this.#resolver.prepass();
1658
- continue;
1659
- }
1660
- const next = [];
1661
- for (const module of round.modules) {
1662
- compiled.set(module.file, module);
1663
- for (const dep of module.deps) {
1664
- if (CsrDevPaths.isStub(dep) || seen.has(dep))
1665
- continue;
1666
- seen.add(dep);
1667
- next.push(dep);
1668
- }
1669
- }
1670
- frontier = next;
1671
- }
1672
- const { fallbacks } = this.#resolver;
1673
- if (fallbacks.size > 0)
1674
- 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 ? ", ..." : ""}`);
1675
- return [...compiled.values()];
1676
- }
1677
- async#compileRound(files) {
1678
- const appFiles = files.filter((file) => !CsrDevPaths.isVendorFile(file));
1679
- const vendorFiles = files.filter((file) => CsrDevPaths.isVendorFile(file));
1680
- const rounds = await Promise.all([
1681
- appFiles.length > 0 ? this.#bunBuild(appFiles, false) : null,
1682
- vendorFiles.length > 0 ? this.#bunBuild(vendorFiles, true) : null
1683
- ]);
1684
- const modules = rounds.flatMap((round) => round?.modules ?? []);
1685
- const misses = rounds.flatMap((round) => round?.misses ?? []);
1686
- for (const file of rounds.flatMap((round) => round?.tangled ?? [])) {
1687
- const single = await this.#bunBuild([file], CsrDevPaths.isVendorFile(file));
1688
- if (single.tangled.length > 0)
1689
- throw new Error(`[csr-dev] ${this.#paths.idOf(file)} kept inlining another module`);
1690
- modules.push(...single.modules);
1691
- misses.push(...single.misses);
736
+ async#compileWithImports(cssPaths, sourcePaths) {
737
+ if (cssPaths.length === 0)
738
+ return { css: "", imported: [] };
739
+ const compileStarted = Date.now();
740
+ const compilers = await Promise.all(cssPaths.map(async (cssPath) => {
741
+ const css = await Bun.file(cssPath).text();
742
+ const base = path4.dirname(cssPath);
743
+ const imported = new Map;
744
+ const compiler = await compile(css, {
745
+ base,
746
+ loadStylesheet: (id, fromBase) => this.#loadStylesheet(id, fromBase, imported),
747
+ loadModule: (id, fromBase) => this.#loadModule(id, fromBase)
748
+ });
749
+ return { cssPath, compiler, imported };
750
+ }));
751
+ const sourceDirs = new Set;
752
+ for (const entry of compilers) {
753
+ for (const s of entry.compiler.sources)
754
+ sourceDirs.add(s.base);
1692
755
  }
1693
- return { modules, misses };
1694
- }
1695
- async#bunBuild(files, vendor) {
1696
- const mtimes = new Map(files.map((file) => [file, CsrDevPaths.mtimeOf(file)]));
1697
- const hashes = new Map(await Promise.all(files.map(async (file) => [file, vendor ? "" : await CsrDevPaths.hashOf(file)])));
1698
- const misses = [];
1699
- const result = await Bun.build({
1700
- entrypoints: files,
1701
- root: this.#paths.root,
1702
- target: "browser",
1703
- format: "cjs",
1704
- reactFastRefresh: !vendor,
1705
- sourcemap: vendor ? "none" : "external",
1706
- naming: { entry: "[dir]/[name].[ext]", asset: "assets/[name]-[hash].[ext]" },
1707
- publicPath: CSR_DEV_ROUTE_PREFIX2,
1708
- metafile: true,
1709
- env: "AKAN_PUBLIC_*",
1710
- define: this.#context.define,
1711
- optimizeImports: this.#context.optimizeImports,
1712
- plugins: [this.#registryPlugin(vendor, misses)]
1713
- });
1714
- const { outputs } = result.metafile;
1715
- const sourceMaps = new Map(result.outputs.filter((artifact) => artifact.kind === "sourcemap").map((artifact) => [artifact.path, artifact]));
1716
- const modules = [];
1717
- const tangled = [];
1718
- for (const artifact of result.outputs) {
1719
- if (artifact.kind === "asset") {
1720
- await Bun.write(path12.join(this.#outDir, artifact.path), artifact);
1721
- continue;
1722
- }
1723
- if (artifact.kind !== "entry-point")
1724
- continue;
1725
- const meta = outputs[artifact.path];
1726
- if (!meta?.entryPoint)
1727
- throw new Error(`[csr-dev] no entry point recorded for ${artifact.path}`);
1728
- const file = CsrDevPaths.realpath(path12.resolve(meta.entryPoint));
1729
- const inputs = Object.keys(meta.inputs).map((input) => CsrDevPaths.realpath(path12.resolve(input)));
1730
- if (inputs.some((input) => input !== file && this.#resolver.isRegistryModule(input))) {
1731
- tangled.push(file);
1732
- continue;
756
+ const scanStarted = Date.now();
757
+ const candidates = await this.#scanCandidates(sourcePaths, [...sourceDirs]);
758
+ this.#logger.verbose(`css candidates scanned count=${candidates.length} sources=${sourcePaths.length} dirs=${sourceDirs.size} in ${Date.now() - scanStarted}ms`);
759
+ const parts = [];
760
+ const imported = [];
761
+ for (const entry of compilers) {
762
+ const part = entry.compiler.build(candidates);
763
+ parts.push(part);
764
+ for (const [cssPath, content] of entry.imported) {
765
+ const declaredNames = declaredCustomProperties(content);
766
+ imported.push({ cssPath, declaredNames });
767
+ if (declaredNames.length === 0 || declaredNames.some((name) => part.includes(`${name}:`)))
768
+ continue;
769
+ this.#logger.warn(`css @import ${cssPath} was loaded by ${entry.cssPath} but none of its ${declaredNames.length} declaration(s) reached the compiled CSS`);
1733
770
  }
1734
- const code = await artifact.text();
1735
- const id = this.#paths.idOf(file);
1736
- const sourceMap = sourceMaps.get(`${artifact.path}.map`);
1737
- modules.push({
1738
- id,
1739
- file,
1740
- vendor,
1741
- ...CsrDevModuleCompiler.#factory(id, code),
1742
- sourceMap: sourceMap ? this.#rebaseSourceMap(await sourceMap.text()) : undefined,
1743
- deps: this.#emittedDeps(code),
1744
- mtimeMs: mtimes.get(file) ?? CsrDevPaths.mtimeOf(file),
1745
- hash: hashes.get(file) ?? ""
1746
- });
1747
771
  }
1748
- return { modules, misses, tangled };
772
+ this.#logger.verbose(`css compiled paths=${cssPaths.length} candidates=${candidates.length} in ${Date.now() - compileStarted}ms`);
773
+ return { css: parts.join(`
774
+ `), imported };
1749
775
  }
1750
- #registryPlugin(vendor, misses) {
1751
- return {
1752
- name: "akan-csr-registry",
1753
- setup: (build) => {
1754
- build.onLoad({ filter: /\.css$/ }, () => ({ contents: "", loader: "js" }));
1755
- if (!vendor)
1756
- build.onLoad({ filter: /\.tsx$/ }, async (args) => {
1757
- if (CsrDevPaths.isVendorFile(args.path))
1758
- return;
1759
- const normalized = RouteClientBuilder.normalizeNamedDefaultFunctionForFastRefresh(await Bun.file(args.path).text());
1760
- return normalized ? { contents: normalized, loader: "tsx" } : undefined;
1761
- });
1762
- build.onResolve({ filter: /.*/ }, (args) => {
1763
- if (args.kind === "entry-point-build" || !args.importer)
1764
- return;
1765
- const importer = CsrDevPaths.realpath(args.importer);
1766
- const target = this.#resolver.resolve(importer, args.path);
1767
- if (target === CsrDevResolver.inline)
1768
- return;
1769
- if (target === null) {
1770
- misses.push(`${args.path} from ${this.#paths.idOf(importer)}`);
1771
- return { path: `akan-miss:${args.path}`, external: true };
1772
- }
1773
- const external = CsrDevPaths.isStub(target) ? target : `${CsrDevPaths.modulePrefix}${this.#paths.idOf(target)}`;
1774
- return { path: external, external: true };
1775
- });
1776
- }
1777
- };
776
+ async#loadStylesheet(id, fromBase, imported) {
777
+ const p = await this.#resolveCssImport(id, fromBase);
778
+ this.#discoveredCssPaths.add(p);
779
+ const content = await Bun.file(p).text();
780
+ imported?.set(p, content);
781
+ this.#logger.verbose(`css import "${id}" from ${fromBase} -> ${p} (${content.length} bytes)`);
782
+ return { path: p, base: path4.dirname(p), content };
1778
783
  }
1779
- #rebaseSourceMap(text) {
1780
- const map = JSON.parse(text);
1781
- const files = map.sources.map((source) => path12.resolve(source));
1782
- map.sources = files.map((file) => `/${this.#paths.idOf(file)}`);
1783
- map.sourcesContent = files.map((file, index) => CsrDevPaths.isVendorFile(file) ? null : map.sourcesContent?.[index] ?? null);
1784
- return JSON.stringify(map);
1785
- }
1786
- #emittedDeps(code) {
1787
- const deps = new Set;
1788
- const references = /\b(?:require|import|__akanImport)\("((?:akan-module|stub):[^"]+)"\)/g;
1789
- for (const [, reference] of code.matchAll(references)) {
1790
- if (!reference)
1791
- continue;
1792
- deps.add(CsrDevPaths.isStub(reference) ? reference : this.#paths.fileOf(reference.slice(CsrDevPaths.modulePrefix.length)));
1793
- }
1794
- return [...deps];
1795
- }
1796
- static #factory(id, code) {
1797
- const prefix = CsrDevPaths.modulePrefix;
1798
- let patched = code.replaceAll(`import("${prefix}`, `__akanImport("${prefix}`).replace(/^\/\/# debugId=.*$/m, "");
1799
- for (const [name, params, method] of CsrDevModuleCompiler.#helperPatches) {
1800
- const definition = `var ${name} = ${params} => {`;
1801
- if (patched.includes(definition))
1802
- patched = patched.replace(definition, `var ${name} = __akan.${method}, __bun${name} = ${params} => {`);
1803
- else if (patched.includes(`${name}(`))
1804
- throw new Error(`[csr-dev] ${id}: Bun's ${name} helper no longer reads "${definition}"; CsrDevModuleCompiler must follow`);
784
+ async#resolveCssImport(id, fromBase) {
785
+ if (id.startsWith(".") || id.startsWith("/")) {
786
+ const filePath = path4.resolve(fromBase, id);
787
+ if (await this.#fileExists(filePath))
788
+ return filePath;
789
+ throw new Error(`[css] failed to resolve stylesheet import "${id}" from ${fromBase} (no file at ${filePath})`);
1805
790
  }
1806
- if (CsrDevModuleCompiler.#storeRoot.test(id))
1807
- patched += `
1808
- module.hot.accept();`;
1809
- const shared = CsrDevModuleCompiler.#shareHelpers(patched);
1810
- return {
1811
- factory: `function (require, module, exports, $RefreshReg$, $RefreshSig$, __akanImport) {
1812
- ${shared?.code ?? patched}
1813
- }`,
1814
- helpers: shared?.helpers ?? null
1815
- };
1816
- }
1817
- static #shareHelpers(code) {
1818
- const marker = code.search(/^\/\/ /m);
1819
- if (marker <= 0)
1820
- return null;
1821
- const preamble = code.slice(0, marker);
1822
- const names = [...preamble.matchAll(/^(?:var|let|const|function)\s+([\w$]+)/gm)].map(([, name]) => name).filter((name) => !!name);
1823
- if (names.length === 0 || CsrDevModuleCompiler.#factoryArgument.test(preamble))
1824
- return null;
1825
- const hash = Bun.hash(preamble).toString(36);
1826
- const binding = `var { ${names.join(", ")} } = __akan.helpers(${JSON.stringify(hash)});`;
1827
- return {
1828
- code: `${binding}${`
1829
- `.repeat(CsrDevPaths.lineCount(preamble))}${code.slice(marker)}`,
1830
- helpers: {
1831
- hash,
1832
- definition: `__akan.defineHelpers(${JSON.stringify(hash)}, function () {
1833
- ${preamble}return { ${names.join(", ")} };
1834
- });
1835
- `
1836
- }
1837
- };
791
+ this.#cssImportResolver ??= await CssImportResolver.create(this.#app);
792
+ const resolved = await this.#cssImportResolver.resolve(id, fromBase);
793
+ if (resolved)
794
+ return resolved;
795
+ throw new Error(`[css] failed to resolve stylesheet import "${id}" from ${fromBase}`);
1838
796
  }
1839
- }
1840
-
1841
- // pkgs/@akanjs/devkit/frontendBuild/csrDevBundler.ts
1842
- class CsrDevBundler {
1843
- static #formatVersion = 3;
1844
- #app;
1845
- #paths;
1846
- #outDir;
1847
- #entryDir;
1848
- #writer;
1849
- constructor(app) {
1850
- this.#app = app;
1851
- this.#paths = new CsrDevPaths(app.workspace.workspaceRoot);
1852
- this.#outDir = path13.join(app.cwdPath, ".akan/artifact", CSR_DEV_DIRNAME);
1853
- this.#entryDir = path13.join(app.cwdPath, ".akan/generated", CSR_DEV_DIRNAME);
1854
- this.#writer = new CsrDevArtifactWriter(this.#outDir);
1855
- }
1856
- async update(changedFiles = []) {
1857
- const context = await this.#context();
1858
- if (context.pageEntries.length === 0)
1859
- return null;
1860
- const manifest = await this.#writer.readJson(CSR_DEV_MANIFEST_FILE2);
1861
- const graph = await this.#writer.readJson("graph.json");
1862
- const generation = (manifest?.generation ?? 0) + 1;
1863
- if (!manifest || !graph || graph.version !== 1 || graph.configKey !== context.configKey)
1864
- return await this.#fullBuild(context, generation, manifest ? "the dev bundle config changed" : "first build");
1865
- const metadataFile = changedFiles.find(isAkanRuntimeMetadataFile);
1866
- if (metadataFile)
1867
- return await this.#fullBuild(context, generation, `${path13.basename(metadataFile)} changed`);
1868
- return await this.#incrementalBuild(context, graph, manifest, changedFiles, generation);
1869
- }
1870
- async#context() {
1871
- const akanConfig = await this.#app.getConfig();
1872
- const pageEntries = await resolveSsrPageEntriesForApp(this.#app, await this.#app.getPageKeys());
1873
- const basePaths = [...akanConfig.basePaths];
1874
- const htmlBasePaths = basePaths.length > 0 ? basePaths : [""];
1875
- const define = bundleDefine(this.#app, "start", "csr");
1876
- const optimizeImports = [...akanConfig.optimizeImports];
1877
- const configKey = Bun.hash(JSON.stringify([CsrDevBundler.#formatVersion, Bun.version, define, optimizeImports, htmlBasePaths])).toString(36);
1878
- return {
1879
- pageEntries,
1880
- basePaths,
1881
- htmlBasePaths,
1882
- define,
1883
- optimizeImports,
1884
- configKey,
1885
- refreshFile: this.#resolveRefreshRuntime()
1886
- };
797
+ async#loadModule(id, fromBase) {
798
+ const p = __require.resolve(id, { paths: [fromBase] });
799
+ const mod = await import(p);
800
+ return { path: p, base: path4.dirname(p), module: mod.default ?? mod };
1887
801
  }
1888
- #resolveRefreshRuntime() {
1889
- const akanWebkit = CsrDevPaths.tryResolve("akanjs/webkit", this.#app.cwdPath);
1890
- const bases = [
1891
- this.#app.cwdPath,
1892
- this.#paths.root,
1893
- ...akanWebkit ? [path13.dirname(path13.dirname(akanWebkit))] : []
1894
- ];
1895
- for (const base of bases) {
1896
- const resolved = CsrDevPaths.tryResolve("react-refresh/runtime", base);
1897
- if (resolved)
1898
- return CsrDevPaths.realpath(resolved);
1899
- }
1900
- throw new Error(`[csr-dev] react-refresh/runtime is not resolvable from ${bases.join(", ")}`);
1901
- }
1902
- async#fullBuild(context, generation, reason) {
1903
- await this.#writer.reset();
1904
- const entries = await this.#writeEntries(context);
1905
- const entryFiles = Object.values(entries.files);
1906
- const resolver = new CsrDevResolver({ paths: this.#paths, context, entryFiles });
1907
- await resolver.prepass();
1908
- const compiled = await this.#compiler(context, resolver).compile([...entryFiles, context.refreshFile], new Set);
1909
- const graph = {
1910
- version: 1,
1911
- configKey: context.configKey,
1912
- refresh: this.#paths.idOf(context.refreshFile),
1913
- entries: this.#entryIds(entries.files),
1914
- modules: {},
1915
- resolution: {},
1916
- pending: []
1917
- };
1918
- this.#merge(graph, resolver, compiled);
1919
- await this.#writer.writeModules(compiled);
1920
- const vendorFile = await this.#writer.writeVendor(graph);
1921
- await this.#writer.writeApp(graph, generation);
1922
- await this.#writer.writeState(graph, { version: 1, generation, vendorFile, entries: graph.entries });
1923
- await this.#writer.prune(vendorFile, generation);
1924
- return { generation, reload: true, reason, changedIds: [], moduleCount: Object.keys(graph.modules).length };
1925
- }
1926
- async#incrementalBuild(context, graph, manifest, changedFiles, generation) {
1927
- const entries = await this.#writeEntries(context);
1928
- const entryIds = this.#entryIds(entries.files);
1929
- await this.#forgetDeletedModules(graph);
1930
- const changed = await this.#changedModules(graph, changedFiles);
1931
- for (const file of entries.changed)
1932
- changed.add(this.#paths.idOf(file));
1933
- const routesMoved = entries.changed.length > 0 || JSON.stringify(entryIds) !== JSON.stringify(graph.entries);
1934
- if (changed.size === 0)
1935
- return null;
1936
- const resolver = new CsrDevResolver({
1937
- paths: this.#paths,
1938
- context,
1939
- entryFiles: Object.values(entries.files),
1940
- resolution: graph.resolution
1941
- });
1942
- const known = new Set(Object.keys(graph.modules).map((id) => this.#paths.fileOf(id)));
1943
- let compiled;
1944
- try {
1945
- compiled = await this.#compiler(context, resolver).compile([...changed].map((id) => this.#paths.fileOf(id)), known);
1946
- } catch (error) {
1947
- graph.pending = [...changed];
1948
- await this.#writer.writeJson("graph.json", graph);
1949
- throw error;
1950
- }
1951
- const vendorJoined = compiled.some((module) => module.vendor && !graph.modules[module.id]);
1952
- this.#merge(graph, resolver, compiled);
1953
- graph.entries = entryIds;
1954
- await this.#writer.writeModules(compiled);
1955
- const vendorFile = vendorJoined ? await this.#writer.writeVendor(graph) : manifest.vendorFile;
1956
- await this.#writer.writeApp(graph, generation);
1957
- const changedIds = compiled.filter((module) => !module.vendor).map((module) => module.id);
1958
- const constantId = changedIds.find((id) => id.endsWith(".constant.ts"));
1959
- const reason = vendorJoined ? "an npm module joined the graph" : routesMoved ? "the route table changed" : constantId ? `${path13.basename(constantId)} changed` : undefined;
1960
- const patchUrl = reason ? undefined : await this.#writer.writePatch(generation, compiled.filter((module) => !module.vendor));
1961
- await this.#writer.writeState(graph, { ...manifest, generation, vendorFile, entries: entryIds });
1962
- await this.#writer.prune(vendorFile, generation);
1963
- return {
1964
- generation,
1965
- reload: !!reason,
1966
- reason,
1967
- patchUrl,
1968
- changedIds,
1969
- moduleCount: Object.keys(graph.modules).length
1970
- };
802
+ #resolveSourceImport(id, fromBase, resolvePackage) {
803
+ const cacheKey = `${fromBase}\x00${id}`;
804
+ let cached = this.#resolvedSpecifierCache.get(cacheKey);
805
+ if (cached)
806
+ return cached;
807
+ cached = this.#resolveSourceImportUncached(id, fromBase, resolvePackage);
808
+ this.#resolvedSpecifierCache.set(cacheKey, cached);
809
+ return cached;
1971
810
  }
1972
- async#changedModules(graph, changedFiles) {
1973
- const changed = new Set(graph.pending.filter((id) => graph.modules[id]));
1974
- for (const file of changedFiles) {
1975
- const id = this.#paths.idOf(file);
1976
- if (graph.modules[id])
1977
- changed.add(id);
811
+ async#resolveSourceImportUncached(id, fromBase, resolvePackage) {
812
+ if (id.startsWith(".") || id.startsWith("/")) {
813
+ const abs = id.startsWith("/") ? id : path4.resolve(fromBase, id);
814
+ return this.#resolveSourceFileCandidate(abs);
1978
815
  }
1979
- for (const [id, module] of Object.entries(graph.modules)) {
1980
- if (module.vendor || changed.has(id))
1981
- continue;
1982
- const file = this.#paths.fileOf(id);
1983
- if (CsrDevPaths.mtimeOf(file) === module.mtimeMs)
1984
- continue;
1985
- if (await CsrDevPaths.hashOf(file) !== module.hash)
1986
- changed.add(id);
816
+ const pkg = await resolvePackage(id);
817
+ if (pkg)
818
+ return pkg.entryFile;
819
+ for (const resolve of [() => resolveSourceWithBun(id, fromBase), () => resolveSourceWithRequire(id, fromBase)]) {
820
+ const resolved = await resolve();
821
+ if (resolved)
822
+ return resolved;
1987
823
  }
1988
- return changed;
824
+ return null;
1989
825
  }
1990
- async#forgetDeletedModules(graph) {
1991
- const deleted = Object.keys(graph.modules).filter((id) => !graph.modules[id]?.vendor && !fs3.existsSync(this.#paths.fileOf(id)));
1992
- for (const id of deleted)
1993
- delete graph.modules[id];
1994
- await this.#writer.forgetModules(deleted);
826
+ async#scanCandidates(sourcePaths, dirs) {
827
+ const candidates = new Set;
828
+ const glob = new Bun.Glob("**/*.{tsx,ts,jsx,js,html}");
829
+ const files = new Set(sourcePaths);
830
+ await Promise.all(dirs.map(async (dir) => {
831
+ for await (const file of glob.scan({ cwd: dir, absolute: true })) {
832
+ if (isIgnoredNodeModuleSource(file))
833
+ continue;
834
+ files.add(file);
835
+ }
836
+ }));
837
+ const cache = await new CssCandidateCache(this.#candidateCachePath).load();
838
+ await Promise.all([...files].map(async (file) => {
839
+ for (const candidate of await cache.candidatesFor(file))
840
+ candidates.add(candidate);
841
+ }));
842
+ await cache.save(files);
843
+ this.#logger.verbose(`css candidate cache reused=${cache.reused} rescanned=${cache.rescanned}`);
844
+ return [...candidates];
1995
845
  }
1996
- async#writeEntries(context) {
1997
- await mkdir3(this.#entryDir, { recursive: true });
1998
- const files = {};
1999
- const changed = [];
2000
- for (const basePath of context.htmlBasePaths) {
2001
- const file = path13.join(this.#entryDir, CsrArtifactBuilder.entryFilename(basePath));
2002
- const entryPages = CsrArtifactBuilder.pageEntriesForBasePath(context.pageEntries, basePath, context.basePaths);
2003
- const generator = new PagesEntrySourceGenerator(entryPages);
2004
- const pages = await generator.generateStatic({ fromDir: this.#entryDir });
2005
- const hot = await generator.generateHotReplace({
2006
- fromDir: this.#entryDir,
2007
- ownerId: this.#paths.idOf(file),
2008
- moduleIds: entryPages.map(({ moduleAbsPath }) => this.#paths.idOf(moduleAbsPath))
2009
- });
2010
- const source = `import * as akanWebkit from "akanjs/webkit";
2011
- ${pages}
2012
- void akanWebkit.bootCsr(pages);
2013
- ${hot}`;
2014
- const unchanged = await Bun.file(file).exists() && await Bun.file(file).text() === source;
2015
- if (!unchanged)
2016
- await Bun.write(file, source);
2017
- const entryFile = CsrDevPaths.realpath(file);
2018
- files[basePath] = entryFile;
2019
- if (!unchanged)
2020
- changed.push(entryFile);
2021
- }
2022
- return { files, changed };
2023
- }
2024
- #compiler(context, resolver) {
2025
- return new CsrDevModuleCompiler({ app: this.#app, paths: this.#paths, resolver, context, outDir: this.#outDir });
2026
- }
2027
- #merge(graph, resolver, compiled) {
2028
- for (const module of compiled)
2029
- graph.modules[module.id] = {
2030
- vendor: module.vendor,
2031
- mtimeMs: module.mtimeMs,
2032
- hash: module.hash,
2033
- deps: module.deps.map((dep) => CsrDevPaths.isStub(dep) ? dep : this.#paths.idOf(dep)),
2034
- ...module.helpers ? { helpers: module.helpers.hash } : {}
2035
- };
2036
- graph.pending = [];
2037
- graph.resolution = resolver.serialize();
846
+ }
847
+ function resolveSourceWithBun(id, fromBase) {
848
+ try {
849
+ const resolved = Bun.resolveSync(id, fromBase);
850
+ return isSourceFile(resolved) ? resolved : null;
851
+ } catch {
852
+ return null;
2038
853
  }
2039
- #entryIds(files) {
2040
- return Object.fromEntries(Object.entries(files).map(([basePath, file]) => [basePath, this.#paths.idOf(file)]));
854
+ }
855
+ function resolveSourceWithRequire(id, fromBase) {
856
+ try {
857
+ const resolved = __require.resolve(id, { paths: [fromBase] });
858
+ return isSourceFile(resolved) ? resolved : null;
859
+ } catch {
860
+ return null;
2041
861
  }
2042
862
  }
863
+ function isSourceFile(filePath) {
864
+ return SOURCE_EXTS.has(path4.extname(filePath));
865
+ }
866
+ function isIgnoredNodeModuleSource(filePath) {
867
+ return NODE_MODULES_RE.test(filePath) && !AKANJS_NODE_MODULE_RE.test(filePath);
868
+ }
869
+ function declaredCustomProperties(css) {
870
+ const withoutThemeBlocks = css.replace(/@theme[^{]*\{[^}]*\}/g, "");
871
+ return [...new Set([...withoutThemeBlocks.matchAll(/(?:^|[\s;{])(--[\w-]+)\s*:/g)].map(([, name]) => name))].filter((name) => !!name);
872
+ }
873
+ function getPageKeyBasePath(pageKey, basePaths) {
874
+ return CsrEntryFiles.basePathOfPageKey(pageKey, basePaths);
875
+ }
2043
876
 
2044
877
  // pkgs/@akanjs/devkit/frontendBuild/fontOptimizer.ts
2045
- import { mkdir as mkdir4, stat as stat2 } from "fs/promises";
2046
- import path14 from "path";
878
+ import { mkdir as mkdir2, stat as stat2 } from "fs/promises";
879
+ import path5 from "path";
2047
880
  import ts from "typescript";
2048
881
  var FONT_URL_PREFIX = "/_akan/fonts";
2049
882
  var DEFAULT_FONT_SUBSETS = ["latin"];
@@ -2060,7 +893,7 @@ class FontOptimizer {
2060
893
  constructor(app, command = "start") {
2061
894
  this.#app = app;
2062
895
  this.#command = command;
2063
- this.#artifactRoot = path14.join(command === "build" ? app.dist.cwdPath : app.cwdPath, ".akan/artifact");
896
+ this.#artifactRoot = path5.join(command === "build" ? app.dist.cwdPath : app.cwdPath, ".akan/artifact");
2064
897
  }
2065
898
  async optimize() {
2066
899
  const fonts = await this.discoverFonts();
@@ -2085,7 +918,7 @@ class FontOptimizer {
2085
918
  return result;
2086
919
  }
2087
920
  get #cachePath() {
2088
- return path14.join(this.#artifactRoot, "fontCache.json");
921
+ return path5.join(this.#artifactRoot, "fontCache.json");
2089
922
  }
2090
923
  async#buildCacheKey(fonts) {
2091
924
  const sources = [];
@@ -2100,7 +933,7 @@ class FontOptimizer {
2100
933
  sources.push({ optimizedSrc: face.optimizedSrc, ...stamp });
2101
934
  }
2102
935
  for (const filePath of font.subsetFiles ?? []) {
2103
- const abs = path14.isAbsolute(filePath) ? filePath : path14.join(this.#app.cwdPath, filePath);
936
+ const abs = path5.isAbsolute(filePath) ? filePath : path5.join(this.#app.cwdPath, filePath);
2104
937
  const stamp = await this.#fileStamp(abs);
2105
938
  if (!stamp)
2106
939
  return null;
@@ -2126,7 +959,7 @@ class FontOptimizer {
2126
959
  const cache = await Bun.file(this.#cachePath).json().catch(() => null);
2127
960
  if (cache?.version !== FontOptimizer.#cacheVersion || cache.key !== key)
2128
961
  return null;
2129
- const files = cache.files.map((relativePath) => path14.join(this.#artifactRoot, relativePath));
962
+ const files = cache.files.map((relativePath) => path5.join(this.#artifactRoot, relativePath));
2130
963
  for (const filePath of files) {
2131
964
  if (!await Bun.file(filePath).exists())
2132
965
  return null;
@@ -2138,7 +971,7 @@ class FontOptimizer {
2138
971
  version: FontOptimizer.#cacheVersion,
2139
972
  key,
2140
973
  css: result.css,
2141
- files: result.files.map((filePath) => path14.relative(this.#artifactRoot, filePath))
974
+ files: result.files.map((filePath) => path5.relative(this.#artifactRoot, filePath))
2142
975
  };
2143
976
  await Bun.write(this.#cachePath, JSON.stringify(cache));
2144
977
  }
@@ -2146,7 +979,7 @@ class FontOptimizer {
2146
979
  const pageKeys = await this.#app.getPageKeys();
2147
980
  const fonts = [];
2148
981
  await Promise.all(pageKeys.map(async (key) => {
2149
- const filePath = path14.resolve(this.#app.cwdPath, "page", key);
982
+ const filePath = path5.resolve(this.#app.cwdPath, "page", key);
2150
983
  const file = Bun.file(filePath);
2151
984
  if (!await file.exists())
2152
985
  return;
@@ -2165,8 +998,8 @@ class FontOptimizer {
2165
998
  this.#app.logger.warn(`[font] source not found: ${face.src}`);
2166
999
  continue;
2167
1000
  }
2168
- const outputPath = path14.join(this.#artifactRoot, face.optimizedSrc.replace(/^\/_akan\//, ""));
2169
- await mkdir4(path14.dirname(outputPath), { recursive: true });
1001
+ const outputPath = path5.join(this.#artifactRoot, face.optimizedSrc.replace(/^\/_akan\//, ""));
1002
+ await mkdir2(path5.dirname(outputPath), { recursive: true });
2170
1003
  const sourceBuffer = Buffer.from(await Bun.file(sourcePath).arrayBuffer());
2171
1004
  const outputBuffer = await this.#buildFontBuffer(font, sourceBuffer, sourcePath);
2172
1005
  await Bun.write(outputPath, outputBuffer);
@@ -2188,7 +1021,7 @@ class FontOptimizer {
2188
1021
  const entries = Array.isArray(value) ? value : [];
2189
1022
  const fonts = entries.filter((entry) => this.#isReadableFont(entry));
2190
1023
  if (fonts.length !== entries.length || !Array.isArray(value))
2191
- this.#app.logger.warn(`[font] ${path14.relative(this.#app.cwdPath, filePath)} declares fonts the build cannot read without evaluating the module \u2014 write the list inline, or nothing is subset for it and every /_akan/fonts request 404s`);
1024
+ this.#app.logger.warn(`[font] ${path5.relative(this.#app.cwdPath, filePath)} declares fonts the build cannot read without evaluating the module \u2014 write the list inline, or nothing is subset for it and every /_akan/fonts request 404s`);
2192
1025
  return fonts.map((font) => ({ ...font, subsets: font.subsets ?? [...DEFAULT_FONT_SUBSETS] }));
2193
1026
  }
2194
1027
  #isReadableFont(value) {
@@ -2346,8 +1179,8 @@ class FontOptimizer {
2346
1179
  return null;
2347
1180
  const rel = src.replace(/^\//, "");
2348
1181
  const candidates = [
2349
- this.#command === "build" ? path14.join(this.#app.dist.cwdPath, "public", rel) : null,
2350
- path14.join(this.#app.cwdPath, "public", rel),
1182
+ this.#command === "build" ? path5.join(this.#app.dist.cwdPath, "public", rel) : null,
1183
+ path5.join(this.#app.cwdPath, "public", rel),
2351
1184
  this.#resolveWorkspacePublicPath(rel)
2352
1185
  ].filter(Boolean);
2353
1186
  for (const candidate of candidates) {
@@ -2379,7 +1212,7 @@ class FontOptimizer {
2379
1212
  return "woff2";
2380
1213
  if (signature === "OTTO")
2381
1214
  return "otf";
2382
- const ext = path14.extname(sourcePath).slice(1).toLowerCase();
1215
+ const ext = path5.extname(sourcePath).slice(1).toLowerCase();
2383
1216
  if (ext === "otf" || ext === "woff" || ext === "woff2")
2384
1217
  return ext;
2385
1218
  return "ttf";
@@ -2388,7 +1221,7 @@ class FontOptimizer {
2388
1221
  const [root, dep, ...rest] = rel.split("/");
2389
1222
  if (root !== "libs" || !dep || rest.length === 0)
2390
1223
  return null;
2391
- return path14.join(this.#app.workspace.workspaceRoot, "libs", dep, "public", ...rest);
1224
+ return path5.join(this.#app.workspace.workspaceRoot, "libs", dep, "public", ...rest);
2392
1225
  }
2393
1226
  async#getSubsetText(font) {
2394
1227
  const parts = new Set;
@@ -2398,7 +1231,7 @@ class FontOptimizer {
2398
1231
  if (font.subsetText)
2399
1232
  parts.add(font.subsetText);
2400
1233
  for (const filePath of font.subsetFiles ?? []) {
2401
- const abs = path14.isAbsolute(filePath) ? filePath : path14.join(this.#app.cwdPath, filePath);
1234
+ const abs = path5.isAbsolute(filePath) ? filePath : path5.join(this.#app.cwdPath, filePath);
2402
1235
  const file = Bun.file(abs);
2403
1236
  if (await file.exists())
2404
1237
  parts.add(await file.text());
@@ -2418,7 +1251,7 @@ class FontOptimizer {
2418
1251
  }
2419
1252
  async#collectAutoSubsetText() {
2420
1253
  const libPageRoots = (await this.#app.getPageRoots()).filter((root) => root.keyPrefix).map((root) => root.dir);
2421
- const roots = [...["page", "ui", "lib"].map((dir) => path14.join(this.#app.cwdPath, dir)), ...libPageRoots];
1254
+ const roots = [...["page", "ui", "lib"].map((dir) => path5.join(this.#app.cwdPath, dir)), ...libPageRoots];
2422
1255
  const glob = new Bun.Glob("**/*.{ts,tsx,js,jsx,html,md}");
2423
1256
  const parts = [];
2424
1257
  for (const root of [...new Set(roots)].sort()) {
@@ -2531,8 +1364,8 @@ ${declarations.map(([prop, value]) => ` ${prop}: ${value};`).join(`
2531
1364
  }
2532
1365
 
2533
1366
  // pkgs/@akanjs/devkit/frontendBuild/fontPruner.ts
2534
- import { rm as rm3, rmdir, stat as stat3 } from "fs/promises";
2535
- import path15 from "path";
1367
+ import { rm as rm2, rmdir, stat as stat3 } from "fs/promises";
1368
+ import path6 from "path";
2536
1369
  import { isFontOptimizationEnabled } from "akanjs/client";
2537
1370
  var FONT_GLOB = "**/*.{woff2,woff,ttf,otf,ttc,eot}";
2538
1371
  var REFERRER_GLOB = "**/*.{css,js,mjs,cjs,json,html,htm,svg,xml,webmanifest}";
@@ -2545,8 +1378,8 @@ class FontPruner {
2545
1378
  constructor(app, { keepFonts = [] } = {}) {
2546
1379
  this.#app = app;
2547
1380
  this.#keepGlobs = keepFonts;
2548
- this.#publicRoot = path15.join(app.dist.cwdPath, "public");
2549
- this.#artifactRoot = path15.join(app.dist.cwdPath, ".akan/artifact");
1381
+ this.#publicRoot = path6.join(app.dist.cwdPath, "public");
1382
+ this.#artifactRoot = path6.join(app.dist.cwdPath, ".akan/artifact");
2550
1383
  }
2551
1384
  async prune() {
2552
1385
  const empty = { removed: [], kept: [], freedBytes: 0 };
@@ -2561,7 +1394,7 @@ class FontPruner {
2561
1394
  for (const font of fonts) {
2562
1395
  const target = isFontOptimizationEnabled(font) ? optimizedSrcs : unoptimizedSrcs;
2563
1396
  for (const fontPath of font.paths)
2564
- target.add(path15.posix.basename(fontPath.src));
1397
+ target.add(path6.posix.basename(fontPath.src));
2565
1398
  }
2566
1399
  const result = { removed: [], kept: [], freedBytes: 0 };
2567
1400
  const undecided = [];
@@ -2599,10 +1432,10 @@ class FontPruner {
2599
1432
  async#collectCandidates() {
2600
1433
  const candidates = [];
2601
1434
  for await (const rel of new Bun.Glob(FONT_GLOB).scan({ cwd: this.#publicRoot, dot: false })) {
2602
- const entry = await stat3(path15.join(this.#publicRoot, rel)).catch(() => null);
1435
+ const entry = await stat3(path6.join(this.#publicRoot, rel)).catch(() => null);
2603
1436
  if (!entry?.isFile())
2604
1437
  continue;
2605
- candidates.push({ rel: rel.split(path15.sep).join("/"), basename: path15.basename(rel), bytes: entry.size });
1438
+ candidates.push({ rel: rel.split(path6.sep).join("/"), basename: path6.basename(rel), bytes: entry.size });
2606
1439
  }
2607
1440
  return candidates;
2608
1441
  }
@@ -2612,11 +1445,11 @@ class FontPruner {
2612
1445
  const bundled = candidates.filter((candidate) => !optimizedSrcs.has(candidate.basename));
2613
1446
  const roots = [
2614
1447
  { dir: this.#publicRoot, label: "public", needles: authoritative },
2615
- { dir: path15.join(this.#artifactRoot, "styles"), label: ".akan/artifact/styles", needles: authoritative },
2616
- { dir: path15.join(this.#artifactRoot, "client"), label: ".akan/artifact/client", needles: bundled },
2617
- { dir: path15.join(this.#artifactRoot, "client-ssr"), label: ".akan/artifact/client-ssr", needles: bundled },
2618
- { dir: path15.join(this.#artifactRoot, "server"), label: ".akan/artifact/server", needles: bundled },
2619
- { dir: path15.join(this.#app.dist.cwdPath, "csr"), label: "csr", needles: bundled }
1448
+ { dir: path6.join(this.#artifactRoot, "styles"), label: ".akan/artifact/styles", needles: authoritative },
1449
+ { dir: path6.join(this.#artifactRoot, "client"), label: ".akan/artifact/client", needles: bundled },
1450
+ { dir: path6.join(this.#artifactRoot, "client-ssr"), label: ".akan/artifact/client-ssr", needles: bundled },
1451
+ { dir: path6.join(this.#artifactRoot, "server"), label: ".akan/artifact/server", needles: bundled },
1452
+ { dir: path6.join(this.#app.dist.cwdPath, "csr"), label: "csr", needles: bundled }
2620
1453
  ];
2621
1454
  for (const root of roots) {
2622
1455
  const pending = root.needles.filter((candidate) => !referrers.has(candidate.rel));
@@ -2635,20 +1468,20 @@ class FontPruner {
2635
1468
  const remaining = needles.filter((needle) => !referrers.has(needle.rel));
2636
1469
  if (!remaining.length)
2637
1470
  return;
2638
- const text = await Bun.file(path15.join(root.dir, rel)).text().catch(() => "");
1471
+ const text = await Bun.file(path6.join(root.dir, rel)).text().catch(() => "");
2639
1472
  if (!text)
2640
1473
  continue;
2641
1474
  for (const needle of remaining) {
2642
1475
  if (needle.forms.some((form) => text.includes(form)))
2643
- referrers.set(needle.rel, `${root.label}/${rel.split(path15.sep).join("/")}`);
1476
+ referrers.set(needle.rel, `${root.label}/${rel.split(path6.sep).join("/")}`);
2644
1477
  }
2645
1478
  }
2646
1479
  }
2647
1480
  async#remove(files) {
2648
- await Promise.all(files.map((file) => rm3(path15.join(this.#publicRoot, file), { force: true })));
2649
- const dirs = [...new Set(files.map((file) => path15.posix.dirname(file)))].filter((dir) => dir !== "." && dir !== "/").sort((a, b) => b.length - a.length);
1481
+ await Promise.all(files.map((file) => rm2(path6.join(this.#publicRoot, file), { force: true })));
1482
+ const dirs = [...new Set(files.map((file) => path6.posix.dirname(file)))].filter((dir) => dir !== "." && dir !== "/").sort((a, b) => b.length - a.length);
2650
1483
  for (const dir of dirs)
2651
- await rmdir(path15.join(this.#publicRoot, dir)).catch(() => {
1484
+ await rmdir(path6.join(this.#publicRoot, dir)).catch(() => {
2652
1485
  return;
2653
1486
  });
2654
1487
  }
@@ -2695,8 +1528,8 @@ class FontPruner {
2695
1528
  }
2696
1529
 
2697
1530
  // pkgs/@akanjs/devkit/frontendBuild/precompressArtifacts.ts
2698
- import fs4 from "fs";
2699
- import path16 from "path";
1531
+ import fs from "fs";
1532
+ import path7 from "path";
2700
1533
  import zlib from "zlib";
2701
1534
  var COMPRESSIBLE_EXTS = new Set([".css", ".html", ".js", ".json", ".svg"]);
2702
1535
  var MIN_COMPRESS_BYTES = 1024;
@@ -2706,8 +1539,8 @@ var GZIP_LEVEL = 9;
2706
1539
  var { formatBytes } = FontPruner;
2707
1540
  async function precompressArtifacts(app) {
2708
1541
  const roots = [
2709
- path16.join(app.dist.cwdPath, ".akan/artifact/client"),
2710
- path16.join(app.dist.cwdPath, ".akan/artifact/styles")
1542
+ path7.join(app.dist.cwdPath, ".akan/artifact/client"),
1543
+ path7.join(app.dist.cwdPath, ".akan/artifact/styles")
2711
1544
  ];
2712
1545
  const result = { files: 0, inputBytes: 0, outputBytes: 0, brotliBytes: 0 };
2713
1546
  await Promise.all(roots.map((root) => precompressRoot(root, result)));
@@ -2717,7 +1550,7 @@ async function precompressArtifacts(app) {
2717
1550
  return result;
2718
1551
  }
2719
1552
  async function precompressRoot(root, result) {
2720
- if (!fs4.existsSync(root) || !fs4.statSync(root).isDirectory())
1553
+ if (!fs.existsSync(root) || !fs.statSync(root).isDirectory())
2721
1554
  return;
2722
1555
  const glob = new Bun.Glob("**/*");
2723
1556
  for await (const filePath of glob.scan({ cwd: root, absolute: true })) {
@@ -2726,7 +1559,7 @@ async function precompressRoot(root, result) {
2726
1559
  const bytes = await Bun.file(filePath).bytes();
2727
1560
  const buffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
2728
1561
  const gz = Bun.gzipSync(buffer, { level: GZIP_LEVEL });
2729
- const br = brotliCompress(buffer, path16.extname(filePath).toLowerCase());
1562
+ const br = brotliCompress(buffer, path7.extname(filePath).toLowerCase());
2730
1563
  await Promise.all([Bun.write(`${filePath}.gz`, gz), Bun.write(`${filePath}.br`, br)]);
2731
1564
  result.files += 1;
2732
1565
  result.inputBytes += bytes.byteLength;
@@ -2746,7 +1579,7 @@ function brotliCompress(buffer, ext) {
2746
1579
  async function shouldPrecompress(filePath) {
2747
1580
  if (filePath.endsWith(".gz") || filePath.endsWith(".br"))
2748
1581
  return false;
2749
- if (!COMPRESSIBLE_EXTS.has(path16.extname(filePath).toLowerCase()))
1582
+ if (!COMPRESSIBLE_EXTS.has(path7.extname(filePath).toLowerCase()))
2750
1583
  return false;
2751
1584
  const file = Bun.file(filePath);
2752
1585
  if (!await file.exists())
@@ -2755,7 +1588,7 @@ async function shouldPrecompress(filePath) {
2755
1588
  }
2756
1589
 
2757
1590
  // pkgs/@akanjs/devkit/frontendBuild/ssrBaseArtifactBuilder.ts
2758
- import path17 from "path";
1591
+ import path8 from "path";
2759
1592
  import { optimize } from "@tailwindcss/node";
2760
1593
  function prepareCssAsset(command, basePath, cssText) {
2761
1594
  return optimize(cssText, { file: `${basePath || "root"}.css`, minify: command === "build" }).code;
@@ -2771,7 +1604,7 @@ class SsrBaseArtifactBuilder {
2771
1604
  this.#app = app;
2772
1605
  this.#command = command;
2773
1606
  this.#artifactDir = `${command === "build" ? app.dist.cwdPath : app.cwdPath}/.akan/artifact`;
2774
- this.#absArtifactDir = path17.resolve(this.#artifactDir);
1607
+ this.#absArtifactDir = path8.resolve(this.#artifactDir);
2775
1608
  }
2776
1609
  static servedWeb(command, web) {
2777
1610
  return command === "start" ? { ssr: true, csr: true } : web;
@@ -2796,7 +1629,7 @@ class SsrBaseArtifactBuilder {
2796
1629
  rscRuntimeSsrManifest,
2797
1630
  vendorMap,
2798
1631
  cssAssets,
2799
- pagesBundlePath: this.#command === "build" ? path17.relative(this.#absArtifactDir, pagesBundle.bundlePath) : pagesBundle.bundlePath,
1632
+ pagesBundlePath: this.#command === "build" ? path8.relative(this.#absArtifactDir, pagesBundle.bundlePath) : pagesBundle.bundlePath,
2800
1633
  pagesBundleBuildId: pagesBundle.buildId,
2801
1634
  domains: [...akanConfig.domains],
2802
1635
  subRoutes: Object.fromEntries(Array.from(akanConfig.subRoutes.entries()).map(([basePath, domains]) => [basePath, [...domains]])),
@@ -2813,18 +1646,18 @@ class SsrBaseArtifactBuilder {
2813
1646
  androidSha256CertFingerprints: target.deepLinks?.android?.sha256CertFingerprints
2814
1647
  }))
2815
1648
  };
2816
- await Bun.write(path17.join(this.#absArtifactDir, "base-artifact.json"), `${JSON.stringify(artifact, null, 2)}
1649
+ await Bun.write(path8.join(this.#absArtifactDir, "base-artifact.json"), `${JSON.stringify(artifact, null, 2)}
2817
1650
  `);
2818
1651
  this.#app.verbose(`[base-artifact] complete in ${Date.now() - this.#started}ms`);
2819
1652
  return { artifact, seedIndex, cssCompiler, optimizedFonts };
2820
1653
  }
2821
1654
  async#buildRuntimeClientEntries() {
2822
1655
  const akanServerPath = await this.#resolveAkanServerPath();
2823
- const rscClientEntry = path17.resolve(akanServerPath, "rscClient.tsx");
2824
- const rscSegmentOutletEntry = path17.resolve(akanServerPath, "rscSegmentOutlet.tsx");
1656
+ const rscClientEntry = path8.resolve(akanServerPath, "rscClient.tsx");
1657
+ const rscSegmentOutletEntry = path8.resolve(akanServerPath, "rscSegmentOutlet.tsx");
2825
1658
  const vendorEntries = VENDOR_SPECIFIERS.map((specifier) => ({
2826
1659
  specifier,
2827
- absPath: path17.resolve(akanServerPath, "vendor", `${specifier.replaceAll("/", "-").replaceAll(".", "-")}.ts`)
1660
+ absPath: path8.resolve(akanServerPath, "vendor", `${specifier.replaceAll("/", "-").replaceAll(".", "-")}.ts`)
2828
1661
  }));
2829
1662
  const entries = [rscClientEntry, rscSegmentOutletEntry, ...vendorEntries.map((v) => v.absPath)];
2830
1663
  const clientBundle = await new ClientEntriesBundler({ app: this.#app, entries, command: this.#command }).bundle();
@@ -2861,15 +1694,15 @@ class SsrBaseArtifactBuilder {
2861
1694
  async#resolveAkanServerPath() {
2862
1695
  const candidates = [];
2863
1696
  try {
2864
- candidates.push(path17.dirname(Bun.resolveSync("akanjs/server", this.#app.workspace.workspaceRoot)));
1697
+ candidates.push(path8.dirname(Bun.resolveSync("akanjs/server", this.#app.workspace.workspaceRoot)));
2865
1698
  } catch {}
2866
- candidates.push(path17.join(this.#app.workspace.workspaceRoot, "pkgs/akanjs/server"), path17.join(this.#app.workspace.workspaceRoot, "node_modules/akanjs/server"));
1699
+ candidates.push(path8.join(this.#app.workspace.workspaceRoot, "pkgs/akanjs/server"), path8.join(this.#app.workspace.workspaceRoot, "node_modules/akanjs/server"));
2867
1700
  try {
2868
- candidates.push(path17.dirname(Bun.resolveSync("akanjs/server", path17.dirname(Bun.main))));
1701
+ candidates.push(path8.dirname(Bun.resolveSync("akanjs/server", path8.dirname(Bun.main))));
2869
1702
  } catch {}
2870
- candidates.push(path17.join(path17.dirname(Bun.main), "node_modules/akanjs/server"), path17.join(path17.dirname(Bun.main), "../../akanjs/server"), path17.resolve(import.meta.dir, "../../server"), path17.resolve(import.meta.dir, "../server"));
1703
+ candidates.push(path8.join(path8.dirname(Bun.main), "node_modules/akanjs/server"), path8.join(path8.dirname(Bun.main), "../../akanjs/server"), path8.resolve(import.meta.dir, "../../server"), path8.resolve(import.meta.dir, "../server"));
2871
1704
  for (const candidate of candidates) {
2872
- if (await Bun.file(path17.join(candidate, "rscClient.tsx")).exists())
1705
+ if (await Bun.file(path8.join(candidate, "rscClient.tsx")).exists())
2873
1706
  return candidate;
2874
1707
  }
2875
1708
  throw new Error(`[base-artifact] failed to locate akanjs/server; looked in: ${candidates.join(", ")}`);
@@ -2898,7 +1731,7 @@ ${preparedCssText}`).toString(36);
2898
1731
  `styles/${cssAssetName}-${cssHash}.css`,
2899
1732
  `/_akan/styles/${cssAssetName}-${cssHash}.css`
2900
1733
  ];
2901
- await Bun.write(path17.join(this.#absArtifactDir, cssRelPath), preparedCssText);
1734
+ await Bun.write(path8.join(this.#absArtifactDir, cssRelPath), preparedCssText);
2902
1735
  SsrBaseArtifactBuilder.#warnDroppedImports(this.#app, cssRelPath, preparedCssText, imported);
2903
1736
  this.#app.verbose(`[base-artifact] wrote ${preparedCssText.length} bytes of CSS for ${basePath} -> ${cssRelPath}`);
2904
1737
  return [basePath, { cssUrl, cssRelPath }];
@@ -2911,4 +1744,4 @@ ${preparedCssText}`).toString(36);
2911
1744
  }
2912
1745
  }
2913
1746
  }
2914
- export { computeRouteSeedIndex, saveRouteSeedIndex, AllRoutesBuilder, CssCompiler, PagesBundleBuilder, CsrArtifactBuilder, CsrDevBundler, FontOptimizer, FontPruner, precompressArtifacts, SsrBaseArtifactBuilder };
1747
+ export { AllRoutesBuilder, CsrArtifactBuilder, CssCompiler, FontOptimizer, FontPruner, precompressArtifacts, SsrBaseArtifactBuilder };