@akanjs/cli 3.0.0-beta.3 → 3.0.0-beta.30

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 (194) hide show
  1. package/.build-stamp +1 -1
  2. package/CodeAgent-xnfyyz8h.js +9 -0
  3. package/CodeAgentRpcHost-p7h6n2hp.js +7 -0
  4. package/CodeAgentRpcListener-7p7z5dd7.js +97 -0
  5. package/CodeTui-h66xb1fh.js +1457 -0
  6. package/README.md +3 -0
  7. package/TunnelShare-cwh9ezsm.js +9 -0
  8. package/{agent.command-d2gfxfck.js → agent.command-c4v7kjff.js} +8 -9
  9. package/akanCode.js +102 -0
  10. package/application.command-yzsswrz6.js +241 -0
  11. package/{applicationBuildRunner-kxwbssgn.js → applicationBuildRunner-qkt6spd5.js} +121 -77
  12. package/buildBatch.proc.js +120 -32
  13. package/cloud.command-v8h688jc.js +95 -0
  14. package/cloud.script-yct1sk4k.js +20 -0
  15. package/code.command-jff3tw7y.js +154 -0
  16. package/commandManifest.json +1 -1
  17. package/consoleToStderr-h5bryj94.js +7 -0
  18. package/context.command-y5sevk3c.js +48 -0
  19. package/context.runner-90hsmacr.js +21 -0
  20. package/{dependencyScanner-shs82ynp.js → dependencyScanner-06z2dmrr.js} +2 -2
  21. package/{devTui-40ggmd99.js → devTui-q7k7bakd.js} +122 -40
  22. package/fileSys-wzzrntkw.js +9 -0
  23. package/guideline.command-11cndccb.js +42 -0
  24. package/guidelines/agentRule/agentRule.instruction.md +209 -33
  25. package/guidelines/conventions/conventions.instruction.md +143 -41
  26. package/guidelines/fieldRule/fieldRule.instruction.md +1 -0
  27. package/guidelines/mcpRule/mcpRule.instruction.md +7 -5
  28. package/guidelines/queryRule/queryRule.instruction.md +21 -7
  29. package/guidelines/runtimeRule/runtimeRule.instruction.md +268 -6
  30. package/guidelines/ssrRule/ssrRule.instruction.md +4 -2
  31. package/guidelines/transportRule/transportRule.instruction.md +51 -0
  32. package/guidelines/workspaceOnboarding/workspaceOnboarding.instruction.md +20 -0
  33. package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +66 -3
  34. package/incrementalBuilder.proc.js +564 -109
  35. package/{index-fz5q3gd7.js → index-0ncc0mbk.js} +10 -17
  36. package/index-167c77aq.js +69 -0
  37. package/{index-kcxmv48c.js → index-2ahj896a.js} +45 -79
  38. package/index-2wc41k5w.js +49 -0
  39. package/{index-j9yhg404.js → index-35mmm9ca.js} +4 -7
  40. package/{index-a64esmtb.js → index-376gpdds.js} +523 -1022
  41. package/index-3hx1z96c.js +87 -0
  42. package/{index-v0qh106m.js → index-3rh7thcx.js} +961 -892
  43. package/{index-hwkygn9s.js → index-48k96p55.js} +325 -36
  44. package/{index-q0y2jq9j.js → index-52w0fvh8.js} +21 -4
  45. package/index-5rp0h6na.js +1119 -0
  46. package/index-6x9p8dpb.js +4300 -0
  47. package/index-7md2g9vw.js +6 -0
  48. package/index-8tv7356t.js +700 -0
  49. package/index-94s080qm.js +266 -0
  50. package/index-9dp031by.js +12 -0
  51. package/{index-ws5e0j51.js → index-a4s954kq.js} +43 -26
  52. package/{index-3d9y066c.js → index-bjwxs904.js} +9 -8
  53. package/{index-ne2qy0nk.js → index-bxmejydw.js} +12 -12
  54. package/index-c6eggbee.js +123 -0
  55. package/index-d2242ksy.js +99 -0
  56. package/{index-92zztqy1.js → index-dy0jx9ay.js} +2 -17
  57. package/{index-259dsnz3.js → index-gxamdf18.js} +5 -8
  58. package/{index-4edzf1gr.js → index-hf6f4xnb.js} +77 -150
  59. package/{index-by5gpp8x.js → index-hj6w4ka1.js} +2 -1
  60. package/{index-gyd220p2.js → index-hmdq5pqm.js} +554 -577
  61. package/{index-600zhnve.js → index-hqd0h1pb.js} +75 -203
  62. package/index-kb16nzyt.js +997 -0
  63. package/index-mk3b9rk1.js +2729 -0
  64. package/{index-2tv3fdx4.js → index-n3jk2501.js} +1205 -1458
  65. package/index-p6pbz9n5.js +25 -0
  66. package/{index-zry5nz81.js → index-qcejqgts.js} +51 -30
  67. package/{index-7737gxcp.js → index-rtwhw4aq.js} +107 -241
  68. package/{index-pnqbkf5p.js → index-se643zvp.js} +6 -2
  69. package/index-tsb0f8ya.js +53 -0
  70. package/{index-ktb5njgy.js → index-tzc1gtrg.js} +49 -99
  71. package/index-wa5jbxj1.js +760 -0
  72. package/index-wfwgcnyc.js +187 -0
  73. package/{index-mak3cd28.js → index-xb7qhxa8.js} +137 -255
  74. package/{index-h7yw2z9n.js → index-zzvvc845.js} +8 -14
  75. package/index.js +23 -22
  76. package/{library.command-nbsky672.js → library.command-n9kp7yms.js} +4 -5
  77. package/{localRegistry.command-yeq57wjr.js → localRegistry.command-jx6kxt2x.js} +23 -42
  78. package/{module.command-29vqzrh8.js → module.command-q2xjf6cx.js} +8 -11
  79. package/{package.command-axv0abmn.js → package.command-q5a3nsw5.js} +6 -7
  80. package/package.json +15 -7
  81. package/{page.command-vs9s3zrh.js → page.command-ppenrxjn.js} +4 -5
  82. package/{primitive.command-cbxmqbtc.js → primitive.command-dvgg8nem.js} +9 -22
  83. package/prompter-76k56b37.js +8 -0
  84. package/{quality.command-gmbs7nws.js → quality.command-pz7kg3pc.js} +205 -377
  85. package/{recipeScanner-fpagtgc1.js → recipeScanner-4b69ybe0.js} +1 -1
  86. package/{repair.command-bqsjptsj.js → repair.command-a6py9xvz.js} +11 -35
  87. package/{routeSourceValidator-apyqw7wk.js → routeSourceValidator-7ycnfz0e.js} +0 -6
  88. package/{scalar.command-6k1vhe9s.js → scalar.command-7f3xfpen.js} +7 -10
  89. package/skills/akan-code/SKILL.md +130 -0
  90. package/skills/akan-debug/SKILL.md +42 -0
  91. package/skills/akan-deps/SKILL.md +50 -0
  92. package/skills/akan-err/SKILL.md +39 -0
  93. package/skills/akan-module/SKILL.md +45 -0
  94. package/skills/akan-scaffold/SKILL.md +76 -0
  95. package/skills/akan-scalar/SKILL.md +52 -0
  96. package/skills/akan-service/SKILL.md +34 -0
  97. package/skills/akan-store/SKILL.md +67 -0
  98. package/skills/akan-ui/SKILL.md +63 -0
  99. package/skills/akan-validate/SKILL.md +44 -0
  100. package/{subspace.command-fwjfcts7.js → subspace.command-z7nqs508.js} +138 -67
  101. package/templates/__scalar/__model__/__model__.abstract.md.template +8 -0
  102. package/templates/appSample/lib/__scalar/workHistory/workHistory.abstract.ts +8 -39
  103. package/templates/appSample/lib/_noti/noti.abstract.ts +6 -29
  104. package/templates/appSample/lib/task/task.abstract.ts +12 -32
  105. package/templates/appSample/lib/task/task.signal.spec.ts +15 -18
  106. package/templates/appSample/lib/task/task.signal.test.ts +15 -1
  107. package/templates/appSample/lib/task/task.signal.ts +1 -1
  108. package/templates/appSample/srvkit/{AuthGuard.ts → guards.ts} +7 -4
  109. package/templates/appSample/srvkit/{SessionInternalArg.ts → internalArgs.ts} +6 -4
  110. package/templates/client.ts +7 -30
  111. package/templates/crudPages/[__model__Id]/edit/page.tsx +2 -1
  112. package/templates/crudPages/[__model__Id]/page.tsx +2 -1
  113. package/templates/crudPages/new/page.tsx +2 -1
  114. package/templates/crudPages/page.tsx +2 -1
  115. package/templates/crudSinglePage/page.tsx +2 -1
  116. package/templates/lib/__lib/lib.dictionary.ts +0 -2
  117. package/templates/lib/__lib/lib.document.ts +0 -2
  118. package/templates/lib/sig.ts +8 -30
  119. package/templates/localDev/docker-compose.yaml.template +3 -14
  120. package/templates/module/__model__.signal.ts +12 -4
  121. package/templates/pkgRoot/tsconfig.json.template +2 -7
  122. package/templates/server.ts +1 -3
  123. package/templates/service/__model__.abstract.md.template +8 -0
  124. package/templates/workspaceRoot/.gitignore.template +0 -1
  125. package/templates/workspaceRoot/package.json.template +1 -3
  126. package/tunnel.command-zy4wbcgt.js +129 -0
  127. package/{typeChecker-3xgrzc2v.js → typeChecker-px49mezc.js} +1 -1
  128. package/typecheck.proc.js +1 -1
  129. package/workflow.command-f142t17s.js +79 -0
  130. package/{workspace.command-8s050m0h.js → workspace.command-gpk1149x.js} +77 -109
  131. package/abstractCompactor-mjh5ppsq.js +0 -101
  132. package/application.command-pj2m7584.js +0 -204
  133. package/applicationReleasePackager-4yasxwv9.js +0 -244
  134. package/capacitorApp-36rbr858.js +0 -12
  135. package/cloud.command-jshwdq22.js +0 -93
  136. package/context.command-ez8a0zyz.js +0 -80
  137. package/devTuiView-c08ccbat.js +0 -9
  138. package/getDirname-dr3jz1xg.js +0 -7
  139. package/guideline.command-xmqwx8zm.js +0 -359
  140. package/guidelines/agentRule/agentRule.generate.json +0 -31
  141. package/guidelines/componentRule/componentRule.generate.json +0 -31
  142. package/guidelines/conventions/conventions.generate.json +0 -25
  143. package/guidelines/cssRule/cssRule.generate.json +0 -31
  144. package/guidelines/docPageRule/docPageRule.generate.json +0 -31
  145. package/guidelines/docPageRule/docPageRule.instruction.md +0 -25
  146. package/guidelines/docSyncRule/docSyncRule.generate.json +0 -31
  147. package/guidelines/docSyncRule/docSyncRule.instruction.md +0 -25
  148. package/guidelines/enumConstant/enumConstant.generate.json +0 -31
  149. package/guidelines/enumConstant/enumConstant.instruction.md +0 -28
  150. package/guidelines/fieldRule/fieldRule.generate.json +0 -31
  151. package/guidelines/framework/framework.generate.json +0 -32
  152. package/guidelines/mcpRule/mcpRule.generate.json +0 -35
  153. package/guidelines/modelConstant/modelConstant.generate.json +0 -32
  154. package/guidelines/modelDictionary/modelDictionary.generate.json +0 -32
  155. package/guidelines/modelDocument/modelDocument.generate.json +0 -31
  156. package/guidelines/modelService/modelService.generate.json +0 -31
  157. package/guidelines/modelSignal/modelSignal.generate.json +0 -31
  158. package/guidelines/modelStore/modelStore.generate.json +0 -31
  159. package/guidelines/modelTemplate/modelTemplate.generate.json +0 -31
  160. package/guidelines/modelUnit/modelUnit.generate.json +0 -31
  161. package/guidelines/modelUtil/modelUtil.generate.json +0 -31
  162. package/guidelines/modelView/modelView.generate.json +0 -31
  163. package/guidelines/modelZone/modelZone.generate.json +0 -31
  164. package/guidelines/moduleCodegen/moduleCodegen.generate.json +0 -31
  165. package/guidelines/moduleCodegen/moduleCodegen.instruction.md +0 -28
  166. package/guidelines/moduleOverview/moduleOverview.generate.json +0 -31
  167. package/guidelines/queryRule/queryRule.generate.json +0 -31
  168. package/guidelines/recipeRule/recipeRule.generate.json +0 -31
  169. package/guidelines/runtimeRule/runtimeRule.generate.json +0 -25
  170. package/guidelines/scalarConstant/scalarConstant.generate.json +0 -32
  171. package/guidelines/scalarDictionary/scalarDictionary.generate.json +0 -32
  172. package/guidelines/scalarModule/scalarModule.generate.json +0 -31
  173. package/guidelines/sharedUiUsage/sharedUiUsage.generate.json +0 -31
  174. package/guidelines/sharedUiUsage/sharedUiUsage.instruction.md +0 -25
  175. package/guidelines/ssrRule/ssrRule.generate.json +0 -31
  176. package/guidelines/transportRule/transportRule.generate.json +0 -31
  177. package/guidelines/utilUiUsage/utilUiUsage.generate.json +0 -31
  178. package/guidelines/utilUiUsage/utilUiUsage.instruction.md +0 -25
  179. package/guidelines/workspaceOnboarding/workspaceOnboarding.generate.json +0 -24
  180. package/guidelines/workspaceRecipes/workspaceRecipes.generate.json +0 -33
  181. package/index-2hq52jq3.js +0 -465
  182. package/index-47pj3aee.js +0 -76
  183. package/index-6wwzs1bc.js +0 -7
  184. package/index-8pm30m73.js +0 -841
  185. package/index-kn1h7p1x.js +0 -560
  186. package/index-meh558jy.js +0 -65
  187. package/index-rwg2aa4f.js +0 -229
  188. package/index-w8kmx1b6.js +0 -2176
  189. package/index-wt7qrtd4.js +0 -1516
  190. package/templates/__scalar/__model__/__model__.abstract.md +0 -28
  191. package/templates/module/__model__.abstract.md +0 -28
  192. package/templates/service/__model__.abstract.md +0 -28
  193. package/templates/workspaceRoot/patches/react-dom@19.2.7.patch.template +0 -62
  194. package/workflow.command-nwpcdc8p.js +0 -107
@@ -1,97 +1,32 @@
1
1
  // @bun
2
2
  import {
3
- AsyncDefaultExportDetector,
4
3
  resolveSsrPageEntriesForApp
5
- } from "./index-2tv3fdx4.js";
4
+ } from "./index-n3jk2501.js";
6
5
  import {
6
+ computeRouteSeedIndex,
7
+ saveRouteSeedIndex,
7
8
  BarrelAnalyzer,
8
- createBarrelImportsPlugin,
9
9
  createTsconfigPackageResolver,
10
10
  rewriteBarrelImports,
11
11
  GraphClientEntryDiscovery,
12
- transformUseClient,
12
+ bundleDefine,
13
13
  ClientEntriesBundler,
14
14
  VENDOR_SPECIFIERS,
15
- RouteClientBuilder
16
- } from "./index-w8kmx1b6.js";
15
+ RouteClientBuilder,
16
+ CsrEntryFiles,
17
+ PagesEntrySourceGenerator,
18
+ PagesBundleBuilder
19
+ } from "./index-6x9p8dpb.js";
20
+ import {
21
+ SOURCE_EXTS
22
+ } from "./index-48k96p55.js";
17
23
  var __require = import.meta.require;
18
24
 
19
25
  // pkgs/@akanjs/devkit/frontendBuild/allRoutesBuilder.ts
20
- import path3 from "path";
21
-
22
- // pkgs/@akanjs/devkit/artifact/routeSeedIndex.ts
23
26
  import path from "path";
24
- import { assertUniqueRoutePatterns, compareRouteSpecificity, parseRouteModuleKey } from "akanjs/common";
25
- function computeRouteSeedIndex(pageEntries) {
26
- const layoutsByPrefix = new Map;
27
- const pagesBySegments = [];
28
- for (const { key, moduleAbsPath, seedAbsPaths } of pageEntries) {
29
- const parsed = parseRouteModuleKey(key);
30
- const files = [path.resolve(moduleAbsPath), ...(seedAbsPaths ?? []).map((seed) => path.resolve(seed))];
31
- if (parsed.kind === "layout" || parsed.kind === "overrides") {
32
- const prefix = parsed.routeSegments.join("/");
33
- const prev = layoutsByPrefix.get(prefix) ?? [];
34
- layoutsByPrefix.set(prefix, [...prev, ...files]);
35
- } else if (parsed.kind === "page") {
36
- pagesBySegments.push({
37
- key,
38
- pattern: parsed.pattern,
39
- segments: parsed.routeSegments,
40
- files,
41
- includeOwnLayout: parsed.leaf === "_index"
42
- });
43
- }
44
- }
45
- assertUniqueRoutePatterns(pagesBySegments);
46
- const rootLayouts = layoutsByPrefix.get("") ?? [];
47
- const globalLayoutFiles = rootLayouts;
48
- const seedEntries = [];
49
- for (const { pattern, segments, files, includeOwnLayout } of pagesBySegments) {
50
- const layouts = [];
51
- const maxPrefixLength = includeOwnLayout ? segments.length : Math.max(segments.length - 1, 0);
52
- for (let i = 0;i <= maxPrefixLength; i++) {
53
- const prefix = segments.slice(0, i).join("/");
54
- const layoutFiles = layoutsByPrefix.get(prefix);
55
- if (layoutFiles)
56
- layouts.push(...layoutFiles);
57
- }
58
- const seeds = Array.from(new Set([...layouts, ...files]));
59
- const routeId = pattern || "/";
60
- seedEntries.push({ routeId, pattern: routeId, seeds });
61
- }
62
- seedEntries.sort((a, b) => compareRouteSpecificity(a.pattern, b.pattern));
63
- return { entries: seedEntries, globalLayoutFiles };
64
- }
65
- var ROUTE_SEED_INDEX_JSON = "route-seed-index.json";
66
- function serializeRouteSeedIndexForArtifact(index, artifactDir, options = {}) {
67
- const normalizedArtifactDir = path.resolve(artifactDir);
68
- if (options.production) {
69
- return {
70
- entries: index.entries.map((entry) => ({ routeId: entry.routeId }))
71
- };
72
- }
73
- return {
74
- entries: index.entries.map((entry) => ({
75
- ...entry,
76
- seeds: entry.seeds.map((seed) => serializeArtifactPath(seed, normalizedArtifactDir))
77
- })),
78
- globalLayoutFiles: index.globalLayoutFiles.map((file) => serializeArtifactPath(file, normalizedArtifactDir))
79
- };
80
- }
81
- async function saveRouteSeedIndex(artifactDir, index, options = {}) {
82
- const absPath = path.join(path.resolve(artifactDir), ROUTE_SEED_INDEX_JSON);
83
- await Bun.write(absPath, `${JSON.stringify(serializeRouteSeedIndexForArtifact(index, artifactDir, options), null, 2)}
84
- `);
85
- return absPath;
86
- }
87
- function serializeArtifactPath(artifactPath, artifactDir) {
88
- if (!path.isAbsolute(artifactPath))
89
- return artifactPath;
90
- return path.relative(artifactDir, artifactPath).split(path.sep).join("/");
91
- }
92
27
 
93
28
  // pkgs/@akanjs/devkit/frontendBuild/routesManifestArtifactSerializer.ts
94
- import path2 from "path";
29
+ import { RoutesManifestStore } from "akanjs/server/artifact/routesManifestStore";
95
30
 
96
31
  class RoutesManifestArtifactSerializer {
97
32
  #manifest;
@@ -99,42 +34,17 @@ class RoutesManifestArtifactSerializer {
99
34
  #production;
100
35
  constructor(manifest, artifactDir, options = {}) {
101
36
  this.#manifest = manifest;
102
- this.#artifactDir = path2.resolve(artifactDir);
37
+ this.#artifactDir = artifactDir;
103
38
  this.#production = options.production ?? false;
104
39
  }
105
40
  static serialize(manifest, artifactDir, options = {}) {
106
41
  return new RoutesManifestArtifactSerializer(manifest, artifactDir, options).serialize();
107
42
  }
108
43
  serialize() {
109
- const serialized = {
110
- ...this.#manifest,
111
- knownEntries: this.#manifest.knownEntries.map((entry) => this.#serializeArtifactPath(entry))
112
- };
44
+ const serialized = RoutesManifestStore.serialize(this.#manifest, this.#artifactDir);
113
45
  if (this.#production)
114
46
  delete serialized.knownEntries;
115
- return {
116
- ...serialized,
117
- clientManifest: Object.fromEntries(Object.entries(this.#manifest.clientManifest).map(([key, row]) => [this.#serializeArtifactPath(key), row])),
118
- ssrManifest: {
119
- ...this.#manifest.ssrManifest,
120
- moduleMap: Object.fromEntries(Object.entries(this.#manifest.ssrManifest.moduleMap).map(([entryUrl, byName]) => [
121
- entryUrl,
122
- Object.fromEntries(Object.entries(byName).map(([name, entry]) => [
123
- name,
124
- {
125
- ...entry,
126
- id: this.#serializeArtifactPath(entry.id),
127
- chunks: entry.chunks.map((chunk) => this.#serializeArtifactPath(chunk))
128
- }
129
- ]))
130
- ]))
131
- }
132
- };
133
- }
134
- #serializeArtifactPath(artifactPath) {
135
- if (!path2.isAbsolute(artifactPath))
136
- return artifactPath;
137
- return path2.relative(this.#artifactDir, artifactPath).split(path2.sep).join("/");
47
+ return serialized;
138
48
  }
139
49
  }
140
50
 
@@ -164,27 +74,22 @@ class AllRoutesBuilder {
164
74
  const seedIndex = computeRouteSeedIndex(pageEntries);
165
75
  this.#app.verbose(`[build-all] discovered ${seedIndex.entries.length} routes`);
166
76
  this.#discovery = await GraphClientEntryDiscovery.create(this.#app);
167
- const allEntries = [];
168
- const seen = new Set;
77
+ const allEntries = new Set;
169
78
  for (const entry of seedIndex.entries) {
170
79
  const seeds = Array.from(new Set([...seedIndex.globalLayoutFiles, ...entry.seeds]));
171
- for (const discovered of await this.#discovery.discover(seeds)) {
172
- if (seen.has(discovered))
173
- continue;
174
- seen.add(discovered);
175
- allEntries.push(discovered);
176
- }
80
+ for (const discovered of await this.#discovery.discover(seeds))
81
+ allEntries.add(discovered);
177
82
  this.#routeIds.push(entry.routeId);
178
83
  }
179
- this.#app.verbose(`[build-all] ${allEntries.length} client entries across ${this.#routeIds.length} routes`);
180
- const delta = await this.#buildEntries(allEntries);
84
+ this.#app.verbose(`[build-all] ${allEntries.size} client entries across ${this.#routeIds.length} routes`);
85
+ const delta = await this.#buildEntries([...allEntries]);
181
86
  this.#mergeDelta(delta);
182
87
  this.#merged.knownEntries = Array.from(this.#knownSet);
183
88
  const manifest = {
184
89
  routeIds: this.#routeIds,
185
90
  ...this.#merged
186
91
  };
187
- const manifestPath = path3.join(path3.resolve(this.#artifactDir), "routes-manifest.json");
92
+ const manifestPath = path.join(path.resolve(this.#artifactDir), "routes-manifest.json");
188
93
  await Bun.write(manifestPath, `${JSON.stringify(RoutesManifestArtifactSerializer.serialize(manifest, this.#artifactDir, {
189
94
  production: this.#command === "build"
190
95
  }), null, 2)}
@@ -216,18 +121,231 @@ class AllRoutesBuilder {
216
121
  return delta;
217
122
  }
218
123
  #mergeDelta(delta) {
219
- for (const [key, row] of Object.entries(delta.manifestDelta))
220
- this.#merged.clientManifest[key] = row;
221
- for (const [url, byName] of Object.entries(delta.ssrManifestDelta.moduleMap)) {
222
- this.#merged.ssrManifest.moduleMap[url] = byName;
223
- }
124
+ Object.assign(this.#merged.clientManifest, delta.manifestDelta);
125
+ Object.assign(this.#merged.ssrManifest.moduleMap, delta.ssrManifestDelta.moduleMap);
224
126
  for (const abs of delta.newEntries)
225
127
  this.#knownSet.add(abs);
226
128
  }
227
129
  }
228
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
+
229
347
  // pkgs/@akanjs/devkit/frontendBuild/cssCompiler.ts
230
- import path5 from "path";
348
+ import path4 from "path";
231
349
  import { Logger } from "akanjs/common";
232
350
  import { compile } from "tailwindcss";
233
351
 
@@ -298,7 +416,7 @@ class CssCandidateCache {
298
416
  }
299
417
 
300
418
  // pkgs/@akanjs/devkit/frontendBuild/cssImportResolver.ts
301
- import path4 from "path";
419
+ import path3 from "path";
302
420
  var CSS_IMPORT_EXTS = ["", ".css", "/styles.css", "/index.css"];
303
421
 
304
422
  class CssImportResolver {
@@ -317,8 +435,8 @@ class CssImportResolver {
317
435
  async resolve(id, fromBase) {
318
436
  for (const resolve of [
319
437
  () => this.#resolveWithTsconfig(id),
320
- () => this.#resolveWithBun(id, fromBase),
321
- () => this.#resolveWithRequire(id, fromBase),
438
+ () => this.#resolveCssWith(fromBase, (base) => Bun.resolveSync(id, base)),
439
+ () => this.#resolveCssWith(fromBase, (base) => __require.resolve(id, { paths: [base] })),
322
440
  () => this.#resolvePackageStyle(id, fromBase)
323
441
  ]) {
324
442
  const resolved = await resolve();
@@ -327,20 +445,10 @@ class CssImportResolver {
327
445
  }
328
446
  return null;
329
447
  }
330
- #resolveWithBun(id, fromBase) {
331
- for (const base of this.#resolutionBases(fromBase)) {
332
- try {
333
- const resolved = Bun.resolveSync(id, base);
334
- if (CssImportResolver.isCssFile(resolved))
335
- return resolved;
336
- } catch {}
337
- }
338
- return null;
339
- }
340
- #resolveWithRequire(id, fromBase) {
448
+ #resolveCssWith(fromBase, resolveFrom) {
341
449
  for (const base of this.#resolutionBases(fromBase)) {
342
450
  try {
343
- const resolved = __require.resolve(id, { paths: [base] });
451
+ const resolved = resolveFrom(base);
344
452
  if (CssImportResolver.isCssFile(resolved))
345
453
  return resolved;
346
454
  } catch {}
@@ -351,7 +459,7 @@ class CssImportResolver {
351
459
  const exact = this.#paths[id];
352
460
  if (exact) {
353
461
  for (const repl of exact) {
354
- const resolved = await this.#firstExisting(path4.resolve(this.#workspaceRoot, repl));
462
+ const resolved = await this.#firstExisting(path3.resolve(this.#workspaceRoot, repl));
355
463
  if (resolved)
356
464
  return resolved;
357
465
  }
@@ -362,7 +470,7 @@ class CssImportResolver {
362
470
  const suffix = id.slice(prefix.length);
363
471
  for (const repl of replacements) {
364
472
  const replPath = repl.endsWith("/*") ? repl.slice(0, -1) : repl;
365
- const resolved = await this.#firstExisting(path4.resolve(this.#workspaceRoot, replPath + suffix));
473
+ const resolved = await this.#firstExisting(path3.resolve(this.#workspaceRoot, replPath + suffix));
366
474
  if (resolved)
367
475
  return resolved;
368
476
  }
@@ -392,34 +500,34 @@ class CssImportResolver {
392
500
  try {
393
501
  if (!await Bun.file(pkgPath).exists())
394
502
  return null;
395
- const pkgDir = path4.dirname(pkgPath);
503
+ const pkgDir = path3.dirname(pkgPath);
396
504
  const pkg = await Bun.file(pkgPath).json();
397
505
  const subpath = id === pkgName ? "." : `.${id.slice(pkgName.length)}`;
398
506
  const exportValue = pkg.exports?.[subpath];
399
507
  const exportedEntry = typeof exportValue === "string" ? exportValue : exportValue?.style || exportValue?.import || exportValue?.default;
400
508
  if (exportedEntry)
401
- return await this.#firstExisting(path4.resolve(pkgDir, exportedEntry));
509
+ return await this.#firstExisting(path3.resolve(pkgDir, exportedEntry));
402
510
  if (subpath !== ".")
403
- return await this.#firstExisting(path4.resolve(pkgDir, subpath));
404
- return await this.#firstExisting(path4.resolve(pkgDir, pkg.exports?.["."]?.style || pkg.style || "index.css"));
511
+ return await this.#firstExisting(path3.resolve(pkgDir, subpath));
512
+ return await this.#firstExisting(path3.resolve(pkgDir, pkg.exports?.["."]?.style || pkg.style || "index.css"));
405
513
  } catch {
406
514
  return null;
407
515
  }
408
516
  }
409
517
  #resolutionBases(fromBase) {
410
- return [fromBase, this.#workspaceRoot, path4.dirname(Bun.main), path4.resolve(path4.dirname(Bun.main), "../..")];
518
+ return [fromBase, this.#workspaceRoot, path3.dirname(Bun.main), path3.resolve(path3.dirname(Bun.main), "../..")];
411
519
  }
412
520
  #packageJsonCandidates(pkgName) {
413
521
  return [
414
- path4.join(this.#workspaceRoot, "pkgs", pkgName, "package.json"),
415
- path4.join(this.#workspaceRoot, "node_modules", pkgName, "package.json"),
416
- path4.join(path4.dirname(Bun.main), "node_modules", pkgName, "package.json"),
417
- path4.join(path4.dirname(Bun.main), "../../", pkgName, "package.json")
522
+ path3.join(this.#workspaceRoot, "pkgs", pkgName, "package.json"),
523
+ path3.join(this.#workspaceRoot, "node_modules", pkgName, "package.json"),
524
+ path3.join(path3.dirname(Bun.main), "node_modules", pkgName, "package.json"),
525
+ path3.join(path3.dirname(Bun.main), "../../", pkgName, "package.json")
418
526
  ];
419
527
  }
420
528
  async#firstExisting(basePath) {
421
529
  for (const suffix of CSS_IMPORT_EXTS) {
422
- const candidate = `${basePath}${suffix}`;
530
+ const candidate = path3.normalize(`${basePath}${suffix}`);
423
531
  if (await Bun.file(candidate).exists())
424
532
  return candidate;
425
533
  }
@@ -432,12 +540,11 @@ class CssImportResolver {
432
540
  return parts[0] ?? null;
433
541
  }
434
542
  static isCssFile(filePath) {
435
- return path4.extname(filePath) === ".css";
543
+ return path3.extname(filePath) === ".css";
436
544
  }
437
545
  }
438
546
 
439
547
  // pkgs/@akanjs/devkit/frontendBuild/cssCompiler.ts
440
- var SOURCE_EXTS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
441
548
  var NON_SOURCE_EXT_RE = /\.(json|svg|png|jpe?g|webp|gif|avif|ico|woff2?|ttf|otf|mp3|mp4|wav)$/i;
442
549
  var NODE_MODULES_RE = /[\\/]node_modules[\\/]/;
443
550
  var AKANJS_NODE_MODULE_RE = /[\\/]node_modules[\\/]akanjs[\\/]/;
@@ -451,7 +558,7 @@ class CssCompiler {
451
558
  this.#app = app;
452
559
  }
453
560
  get #candidateCachePath() {
454
- return path5.join(this.#app.cwdPath, ".akan/cache/cssCandidates.json");
561
+ return path4.join(this.#app.cwdPath, ".akan/cache/cssCandidates.json");
455
562
  }
456
563
  #cssText = null;
457
564
  #cssTextByBasePath = null;
@@ -481,7 +588,7 @@ class CssCompiler {
481
588
  return filePath;
482
589
  }
483
590
  for (const ext of SOURCE_EXTS) {
484
- const filePath = path5.join(absPathNoExt, `index${ext}`);
591
+ const filePath = path4.join(absPathNoExt, `index${ext}`);
485
592
  if (await this.#fileExists(filePath))
486
593
  return filePath;
487
594
  }
@@ -511,42 +618,33 @@ class CssCompiler {
511
618
  const pageKeys = await this.#app.getPageKeys({ refresh });
512
619
  const basePaths = [...akanConfig.basePaths];
513
620
  const rootPageKeys = pageKeys.filter((pageKey) => getPageKeyBasePath(pageKey, basePaths) === null);
621
+ const compileBasePath = async (basePath, basePathPageKeys, label = basePath) => {
622
+ if (basePathPageKeys.length === 0)
623
+ return [basePath, ""];
624
+ const started = Date.now();
625
+ const { cssPaths, sourcePaths } = await this.discoverCssAndSources({ refresh, pageKeys: basePathPageKeys });
626
+ const { css, imported } = await this.#compileWithImports(cssPaths, sourcePaths);
627
+ this.importedStylesheetsByBasePath[basePath] = imported;
628
+ this.#logger.verbose(`css base=${label} paths=${cssPaths.length} sources=${sourcePaths.length} in ${Date.now() - started}ms`);
629
+ return [basePath, css];
630
+ };
514
631
  const cssEntries = await Promise.all([
515
- (async () => {
516
- if (rootPageKeys.length === 0)
517
- return ["", ""];
518
- const started = Date.now();
519
- const { cssPaths, sourcePaths } = await this.discoverCssAndSources({ refresh, pageKeys: rootPageKeys });
520
- const { css, imported } = await this.#compileWithImports(cssPaths, sourcePaths);
521
- this.importedStylesheetsByBasePath[""] = imported;
522
- this.#logger.verbose(`css base=root paths=${cssPaths.length} sources=${sourcePaths.length} in ${Date.now() - started}ms`);
523
- return ["", css];
524
- })(),
525
- ...basePaths.map(async (basePath) => {
526
- const basePathPageKeys = pageKeys.filter((pageKey) => getPageKeyBasePath(pageKey, basePaths) === basePath);
527
- if (basePathPageKeys.length === 0)
528
- return [basePath, ""];
529
- const started = Date.now();
530
- const { cssPaths, sourcePaths } = await this.discoverCssAndSources({ refresh, pageKeys: basePathPageKeys });
531
- const { css, imported } = await this.#compileWithImports(cssPaths, sourcePaths);
532
- this.importedStylesheetsByBasePath[basePath] = imported;
533
- this.#logger.verbose(`css base=${basePath} paths=${cssPaths.length} sources=${sourcePaths.length} in ${Date.now() - started}ms`);
534
- return [basePath, css];
535
- })
632
+ compileBasePath("", rootPageKeys, "root"),
633
+ ...basePaths.map((basePath) => compileBasePath(basePath, pageKeys.filter((pageKey) => getPageKeyBasePath(pageKey, basePaths) === basePath)))
536
634
  ]);
537
635
  this.#cssTextByBasePath = Object.fromEntries(cssEntries);
538
636
  await this.#warnUnreachableStylesheets();
539
637
  return this.#cssTextByBasePath;
540
638
  }
541
639
  async#warnUnreachableStylesheets() {
542
- const pageDir = path5.join(this.#app.cwdPath, "page");
640
+ const pageDir = path4.join(this.#app.cwdPath, "page");
543
641
  const glob = new Bun.Glob("**/*.css");
544
642
  for await (const cssPath of glob.scan({ cwd: pageDir, absolute: true })) {
545
- if (cssPath.includes(`${path5.sep}(libs)${path5.sep}`))
643
+ if (cssPath.includes(`${path4.sep}(libs)${path4.sep}`))
546
644
  continue;
547
645
  if (this.#discoveredCssPaths.has(cssPath))
548
646
  continue;
549
- this.#logger.warn(`css ${path5.relative(this.#app.cwdPath, cssPath)} is imported by no route and never compiled`);
647
+ this.#logger.warn(`css ${path4.relative(this.#app.cwdPath, cssPath)} is imported by no route and never compiled`);
550
648
  }
551
649
  }
552
650
  async discoverCss({ refresh } = {}) {
@@ -558,7 +656,7 @@ class CssCompiler {
558
656
  pageKeys
559
657
  } = {}) {
560
658
  pageKeys ??= await this.#app.getPageKeys({ refresh });
561
- const seeds = pageKeys.map((key) => path5.resolve(this.#app.cwdPath, "page", key));
659
+ const seeds = pageKeys.map((key) => path4.resolve(this.#app.cwdPath, "page", key));
562
660
  const cssFiles = new Set;
563
661
  const sourceFiles = new Set;
564
662
  const queue = [...seeds];
@@ -587,772 +685,198 @@ class CssCompiler {
587
685
  let imports;
588
686
  try {
589
687
  imports = this.#transpiler.scanImports(source);
590
- } catch {
591
- continue;
592
- }
593
- const importerDir = path5.dirname(filePath);
594
- for (const imp of imports) {
595
- const spec = imp.path;
596
- if (!spec)
597
- continue;
598
- if (spec.endsWith(".css")) {
599
- const cssPath = await this.#resolveCssImport(spec, importerDir);
600
- cssFiles.add(cssPath);
601
- continue;
602
- }
603
- if (NON_SOURCE_EXT_RE.test(spec))
604
- continue;
605
- const resolved = await this.#resolveSourceImport(spec, importerDir, resolvePackage);
606
- if (!resolved || sourceFiles.has(resolved) || isIgnoredNodeModuleSource(resolved))
607
- continue;
608
- queue.push(resolved);
609
- }
610
- }
611
- const tokenPaths = await this.#libTokenStylesheets(sourceFiles);
612
- const cssPaths = [...new Set([...tokenPaths, ...cssFiles])];
613
- for (const cssPath of cssPaths)
614
- this.#discoveredCssPaths.add(cssPath);
615
- return { cssPaths, sourcePaths: [...sourceFiles] };
616
- }
617
- async#libTokenStylesheets(sourceFiles) {
618
- const libsRoot = path5.join(this.#app.workspace.workspaceRoot, "libs");
619
- const libNames = new Set;
620
- for (const filePath of sourceFiles) {
621
- const relPath = path5.relative(libsRoot, filePath);
622
- if (relPath.startsWith("..") || path5.isAbsolute(relPath))
623
- continue;
624
- const [libName] = relPath.split(path5.sep);
625
- if (libName)
626
- libNames.add(libName);
627
- }
628
- const tokenPaths = await Promise.all([...libNames].sort().map(async (libName) => {
629
- const tokensPath = path5.join(libsRoot, libName, "ui/tokens.css");
630
- return await this.#fileExists(tokensPath) ? tokensPath : null;
631
- }));
632
- return tokenPaths.filter((tokensPath) => !!tokensPath);
633
- }
634
- async compileCss(cssPaths, sourcePaths) {
635
- const { css } = await this.#compileWithImports(cssPaths, sourcePaths);
636
- return css;
637
- }
638
- async#compileWithImports(cssPaths, sourcePaths) {
639
- if (cssPaths.length === 0)
640
- return { css: "", imported: [] };
641
- const compileStarted = Date.now();
642
- const compilers = await Promise.all(cssPaths.map(async (cssPath) => {
643
- const css = await Bun.file(cssPath).text();
644
- const base = path5.dirname(cssPath);
645
- const imported = new Map;
646
- const compiler = await compile(css, {
647
- base,
648
- loadStylesheet: (id, fromBase) => this.#loadStylesheet(id, fromBase, imported),
649
- loadModule: (id, fromBase) => this.#loadModule(id, fromBase)
650
- });
651
- return { cssPath, compiler, imported };
652
- }));
653
- const sourceDirs = new Set;
654
- for (const entry of compilers) {
655
- if (!entry)
656
- continue;
657
- for (const s of entry.compiler.sources)
658
- sourceDirs.add(s.base);
659
- }
660
- const scanStarted = Date.now();
661
- const candidates = await this.#scanCandidates(sourcePaths, [...sourceDirs]);
662
- this.#logger.verbose(`css candidates scanned count=${candidates.length} sources=${sourcePaths.length} dirs=${sourceDirs.size} in ${Date.now() - scanStarted}ms`);
663
- const parts = [];
664
- const imported = [];
665
- for (const entry of compilers) {
666
- if (!entry)
667
- continue;
668
- const part = entry.compiler.build(candidates);
669
- parts.push(part);
670
- for (const [cssPath, content] of entry.imported) {
671
- const declaredNames = declaredCustomProperties(content);
672
- imported.push({ cssPath, declaredNames });
673
- if (declaredNames.length === 0 || declaredNames.some((name) => part.includes(`${name}:`)))
674
- continue;
675
- this.#logger.warn(`css @import ${cssPath} was loaded by ${entry.cssPath} but none of its ${declaredNames.length} declaration(s) reached the compiled CSS`);
676
- }
677
- }
678
- this.#logger.verbose(`css compiled paths=${cssPaths.length} candidates=${candidates.length} in ${Date.now() - compileStarted}ms`);
679
- return { css: parts.join(`
680
- `), imported };
681
- }
682
- async#loadStylesheet(id, fromBase, imported) {
683
- const p = await this.#resolveCssImport(id, fromBase);
684
- this.#discoveredCssPaths.add(p);
685
- const content = await Bun.file(p).text();
686
- imported?.set(p, content);
687
- this.#logger.verbose(`css import "${id}" from ${fromBase} -> ${p} (${content.length} bytes)`);
688
- return { path: p, base: path5.dirname(p), content };
689
- }
690
- async#resolveCssImport(id, fromBase) {
691
- if (id.startsWith(".") || id.startsWith("/")) {
692
- const filePath = path5.resolve(fromBase, id);
693
- if (await this.#fileExists(filePath))
694
- return filePath;
695
- throw new Error(`[css] failed to resolve stylesheet import "${id}" from ${fromBase} (no file at ${filePath})`);
696
- }
697
- const resolver = await this.#getCssImportResolver();
698
- const resolved = await resolver.resolve(id, fromBase);
699
- if (resolved)
700
- return resolved;
701
- throw new Error(`[css] failed to resolve stylesheet import "${id}" from ${fromBase}`);
702
- }
703
- async#getCssImportResolver() {
704
- if (this.#cssImportResolver)
705
- return this.#cssImportResolver;
706
- this.#cssImportResolver = await CssImportResolver.create(this.#app);
707
- return this.#cssImportResolver;
708
- }
709
- async#loadModule(id, fromBase) {
710
- const p = __require.resolve(id, { paths: [fromBase] });
711
- const mod = await import(p);
712
- return { path: p, base: path5.dirname(p), module: mod.default ?? mod };
713
- }
714
- #resolveSourceImport(id, fromBase, resolvePackage) {
715
- const cacheKey = `${fromBase}\x00${id}`;
716
- let cached = this.#resolvedSpecifierCache.get(cacheKey);
717
- if (cached)
718
- return cached;
719
- cached = this.#resolveSourceImportUncached(id, fromBase, resolvePackage);
720
- this.#resolvedSpecifierCache.set(cacheKey, cached);
721
- return cached;
722
- }
723
- async#resolveSourceImportUncached(id, fromBase, resolvePackage) {
724
- if (id.startsWith(".") || id.startsWith("/")) {
725
- const abs = id.startsWith("/") ? id : path5.resolve(fromBase, id);
726
- return this.#resolveSourceFileCandidate(abs);
727
- }
728
- const pkg = await resolvePackage(id);
729
- if (pkg)
730
- return pkg.entryFile;
731
- for (const resolve of [() => resolveSourceWithBun(id, fromBase), () => resolveSourceWithRequire(id, fromBase)]) {
732
- const resolved = await resolve();
733
- if (resolved)
734
- return resolved;
735
- }
736
- return null;
737
- }
738
- async#scanCandidates(sourcePaths, dirs) {
739
- const candidates = new Set;
740
- const glob = new Bun.Glob("**/*.{tsx,ts,jsx,js,html}");
741
- const files = new Set(sourcePaths);
742
- await Promise.all(dirs.map(async (dir) => {
743
- for await (const file of glob.scan({ cwd: dir, absolute: true })) {
744
- if (isIgnoredNodeModuleSource(file))
745
- continue;
746
- files.add(file);
747
- }
748
- }));
749
- const cache = await new CssCandidateCache(this.#candidateCachePath).load();
750
- await Promise.all([...files].map(async (file) => {
751
- for (const candidate of await cache.candidatesFor(file))
752
- candidates.add(candidate);
753
- }));
754
- await cache.save(files);
755
- this.#logger.verbose(`css candidate cache reused=${cache.reused} rescanned=${cache.rescanned}`);
756
- return [...candidates];
757
- }
758
- }
759
- function resolveSourceWithBun(id, fromBase) {
760
- try {
761
- const resolved = Bun.resolveSync(id, fromBase);
762
- return isSourceFile(resolved) ? resolved : null;
763
- } catch {
764
- return null;
765
- }
766
- }
767
- function resolveSourceWithRequire(id, fromBase) {
768
- try {
769
- const resolved = __require.resolve(id, { paths: [fromBase] });
770
- return isSourceFile(resolved) ? resolved : null;
771
- } catch {
772
- return null;
773
- }
774
- }
775
- function isSourceFile(filePath) {
776
- return SOURCE_EXTS.includes(path5.extname(filePath));
777
- }
778
- function isIgnoredNodeModuleSource(filePath) {
779
- return NODE_MODULES_RE.test(filePath) && !AKANJS_NODE_MODULE_RE.test(filePath);
780
- }
781
- function declaredCustomProperties(css) {
782
- const withoutThemeBlocks = css.replace(/@theme[^{]*\{[^}]*\}/g, "");
783
- return [...new Set([...withoutThemeBlocks.matchAll(/(?:^|[\s;{])(--[\w-]+)\s*:/g)].map(([, name]) => name))].filter((name) => !!name);
784
- }
785
- function getPageKeyBasePath(pageKey, basePaths) {
786
- const normalized = pageKey.split(path5.sep).join("/").replace(/^\.\//, "");
787
- const segments = normalized.split("/");
788
- const firstPublicSegment = segments.find((segment) => segment !== "[lang]" && !/^\(.+\)$/.test(segment));
789
- return firstPublicSegment && basePaths.includes(firstPublicSegment) ? firstPublicSegment : null;
790
- }
791
-
792
- // pkgs/@akanjs/devkit/frontendBuild/pagesBundleBuilder.ts
793
- import path8 from "path";
794
-
795
- // pkgs/@akanjs/devkit/transforms/externalizeFrameworkPlugin.ts
796
- import path6 from "path";
797
- var DEFAULT_INCLUDE = ["akanjs/", "@apps/", "@libs/"];
798
- var DEFAULT_EXCLUDE_EXACT = new Set(["akanjs/webkit", "@akanjs/cli", "@akanjs/devkit"]);
799
- var DEFAULT_EXCLUDE_PREFIX = ["@akanjs/cli/", "@akanjs/devkit/"];
800
- var OPTIONAL_BACKEND_EXTERNAL_EXACT = new Set([
801
- "@libsql/client",
802
- "bullmq",
803
- "ioredis",
804
- "postgres",
805
- "protobufjs"
806
- ]);
807
- var RUNTIME_EXTERNAL_EXACT = new Set([
808
- "react",
809
- "react-dom",
810
- "react/jsx-runtime",
811
- "react/jsx-dev-runtime",
812
- "react-server-dom-webpack",
813
- "react-server-dom-webpack/server.node",
814
- "react-server-dom-webpack/client.node",
815
- "react-server-dom-webpack/client.browser"
816
- ]);
817
- var RUNTIME_EXTERNAL_PREFIX = ["react-dom/", "react-server-dom-webpack/"];
818
- var CANDIDATE_EXTS = [".tsx", ".ts", ".jsx", ".js", ".mjs", ".cjs"];
819
- async function createExternalizeFrameworkPlugin(options) {
820
- const tsconfig = await options.app.getTsConfig();
821
- const includePrefixes = options.include ?? DEFAULT_INCLUDE;
822
- const extraExact = new Set(options.extra ?? []);
823
- const workspaceRoot = options.app.workspace.workspaceRoot;
824
- const tsconfigPaths = tsconfig.compilerOptions.paths ?? {};
825
- const rootEntries = Object.entries(tsconfigPaths).filter(([k]) => !k.endsWith("/*")).map(([k, v]) => ({ pkg: k, entryFile: v[0] ?? null })).filter((e) => e.entryFile !== null);
826
- 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);
827
- async function resolveWorkspaceSubpath(spec) {
828
- if (!workspaceRoot)
829
- return null;
830
- for (const { prefix, replacements } of wildcardEntries) {
831
- if (!spec.startsWith(prefix))
832
- continue;
833
- const suffix = spec.slice(prefix.length);
834
- for (const repl of replacements) {
835
- const replPath = repl?.endsWith("/*") ? repl.slice(0, -1) : repl ?? "";
836
- if (!replPath)
837
- continue;
838
- const candidate = path6.resolve(workspaceRoot, replPath + suffix);
839
- const hit = await firstExisting(candidate);
840
- if (hit)
841
- return hit;
842
- }
843
- }
844
- for (const { pkg, entryFile } of rootEntries) {
845
- if (spec !== pkg && !spec.startsWith(`${pkg}/`))
846
- continue;
847
- if (spec === pkg)
848
- continue;
849
- const suffix = spec.slice(pkg.length + 1);
850
- const pkgDir = path6.dirname(path6.resolve(workspaceRoot, entryFile));
851
- const candidate = path6.join(pkgDir, suffix);
852
- const hit = await firstExisting(candidate);
853
- if (hit)
854
- return hit;
855
- }
856
- return null;
857
- }
858
- return {
859
- name: "akan-externalize-framework",
860
- setup(build) {
861
- build.onResolve({ filter: /.*/ }, async (args) => {
862
- const spec = args.path;
863
- if (spec === "." || spec === ".." || spec.startsWith("./") || spec.startsWith("../") || spec.startsWith("/"))
864
- return;
865
- if (extraExact.has(spec) || DEFAULT_EXCLUDE_EXACT.has(spec))
866
- return { path: spec, external: true };
867
- for (const prefix of DEFAULT_EXCLUDE_PREFIX) {
868
- if (spec.startsWith(prefix))
869
- return { path: spec, external: true };
870
- }
871
- if (OPTIONAL_BACKEND_EXTERNAL_EXACT.has(spec))
872
- return { path: spec, external: true };
873
- if (RUNTIME_EXTERNAL_EXACT.has(spec))
874
- return { path: spec, external: true };
875
- for (const prefix of RUNTIME_EXTERNAL_PREFIX) {
876
- if (spec.startsWith(prefix))
877
- return { path: spec, external: true };
878
- }
879
- for (const prefix of includePrefixes) {
880
- if (!spec.startsWith(prefix))
881
- continue;
882
- const resolved = await resolveWorkspaceSubpath(spec);
883
- if (resolved)
884
- return { path: resolved };
885
- return;
886
- }
887
- return;
888
- });
889
- }
890
- };
891
- }
892
- async function firstExisting(basePath) {
893
- if (await Bun.file(basePath).exists())
894
- return basePath;
895
- for (const ext of CANDIDATE_EXTS) {
896
- const candidate = `${basePath}${ext}`;
897
- if (await Bun.file(candidate).exists())
898
- return candidate;
899
- }
900
- for (const ext of CANDIDATE_EXTS) {
901
- const candidate = path6.join(basePath, `index${ext}`);
902
- if (await Bun.file(candidate).exists())
903
- return candidate;
904
- }
905
- return null;
906
- }
907
-
908
- // pkgs/@akanjs/devkit/transforms/useClientBundlePlugin.ts
909
- function createUseClientBundlePlugin(options = {}) {
910
- return {
911
- name: "akan-use-client-bundle",
912
- setup(build) {
913
- build.onLoad({ filter: /\.(tsx|ts|jsx|js)$/ }, async (args) => {
914
- if (args.path.includes("/node_modules/"))
915
- return;
916
- let source;
917
- try {
918
- source = await Bun.file(args.path).text();
919
- } catch {
920
- return;
921
- }
922
- const stubbed = transformUseClient(source, {
923
- path: args.path,
924
- workspaceRoot: options.workspaceRoot
925
- });
926
- if (stubbed === null)
927
- return;
928
- return { contents: stubbed, loader: loaderFor(args.path) };
929
- });
930
- }
931
- };
932
- }
933
- function loaderFor(absPath) {
934
- if (absPath.endsWith(".tsx"))
935
- return "tsx";
936
- if (absPath.endsWith(".jsx"))
937
- return "jsx";
938
- if (absPath.endsWith(".ts"))
939
- return "ts";
940
- return "js";
941
- }
942
-
943
- // pkgs/@akanjs/devkit/frontendBuild/pagesEntrySourceGenerator.ts
944
- import path7 from "path";
945
- class PagesEntrySourceGenerator {
946
- #pageEntries;
947
- constructor(pageEntries) {
948
- this.#pageEntries = pageEntries;
949
- }
950
- static generate(pageEntries) {
951
- return new PagesEntrySourceGenerator(pageEntries).generate();
952
- }
953
- generate() {
954
- const lines = this.#pageEntries.map(({ key, moduleAbsPath }) => {
955
- const specifier = PagesEntrySourceGenerator.#toImportSpecifier(moduleAbsPath);
956
- return ` ${JSON.stringify(key)}: () => import(${JSON.stringify(specifier)}),`;
957
- });
958
- return `export const pages = {
959
- ${lines.join(`
960
- `)}
961
- };
962
- `;
963
- }
964
- static async generateStatic(pageEntries) {
965
- return await new PagesEntrySourceGenerator(pageEntries).generateStatic();
966
- }
967
- async generateStatic() {
968
- const imports = this.#pageEntries.map(({ moduleAbsPath }, index) => {
969
- const specifier = PagesEntrySourceGenerator.#toImportSpecifier(moduleAbsPath);
970
- return `import * as page${index} from ${JSON.stringify(specifier)};`;
971
- });
972
- const entries = await Promise.all(this.#pageEntries.map(async ({ key, moduleAbsPath }, index) => {
973
- const isAsyncDefault = await AsyncDefaultExportDetector.detect(moduleAbsPath);
974
- return ` ${JSON.stringify(key)}: { loader: async () => page${index}, isAsyncDefault: ${isAsyncDefault} },`;
975
- }));
976
- return `${imports.join(`
977
- `)}
978
- export const pages = {
979
- ${entries.join(`
980
- `)}
981
- };
982
- `;
983
- }
984
- static #toImportSpecifier(moduleAbsPath) {
985
- return path7.resolve(moduleAbsPath).split(path7.sep).join("/");
986
- }
987
- }
988
-
989
- // pkgs/@akanjs/devkit/frontendBuild/pagesBundleBuilder.ts
990
- var VIRTUAL_PAGES_ENTRY = "akan-pages-entry";
991
-
992
- class PagesBundleBuilder {
993
- #app;
994
- #command;
995
- #pageEntries;
996
- #started = Date.now();
997
- constructor(app, command = "start", pageEntries) {
998
- this.#app = app;
999
- this.#command = command;
1000
- this.#pageEntries = pageEntries;
1001
- }
1002
- async build() {
1003
- const akanConfig = await this.#app.getConfig();
1004
- const resolvedEntries = this.#pageEntries ?? await resolveSsrPageEntriesForApp(this.#app, await this.#app.getPageKeys());
1005
- const entrySource = PagesEntrySourceGenerator.generate(resolvedEntries);
1006
- const workspaceRoot = this.#app.workspace.workspaceRoot;
1007
- const result = await Bun.build({
1008
- entrypoints: [VIRTUAL_PAGES_ENTRY],
1009
- outdir: `${this.#artifactDir}/server`,
1010
- target: "bun",
1011
- format: "esm",
1012
- splitting: this.#splitting,
1013
- minify: this.#command === "build",
1014
- naming: {
1015
- entry: "pages-[hash].[ext]",
1016
- chunk: "chunks/[name]-[hash].[ext]",
1017
- asset: "assets/[name]-[hash].[ext]"
1018
- },
1019
- define: this.#define(),
1020
- plugins: [
1021
- PagesBundleBuilder.createPagesEntryPlugin(entrySource),
1022
- PagesBundleBuilder.createCssStubPlugin(),
1023
- PagesBundleBuilder.createServerUseClientFetchPlugin(),
1024
- await createExternalizeFrameworkPlugin({ app: this.#app, extra: akanConfig.externalLibs }),
1025
- akanConfig.barrelImports.length > 0 ? await createBarrelImportsPlugin(this.#app, {
1026
- pipeAfter: (source, args) => transformUseClient(source, {
1027
- path: args.path,
1028
- workspaceRoot
1029
- })
1030
- }) : createUseClientBundlePlugin({ workspaceRoot })
1031
- ]
1032
- });
1033
- if (!result.success)
1034
- throw new AggregateError(result.logs, "[PagesBundleBuilder] Bun.build failed");
1035
- const entryArtifact = result.outputs.find((a) => a.kind === "entry-point");
1036
- if (!entryArtifact)
1037
- throw new Error("[PagesBundleBuilder] Bun.build emitted no entry-point artifact");
1038
- const bundlePath = path8.resolve(entryArtifact.path);
1039
- const buildId = Date.now();
1040
- const outputBytes = result.outputs.reduce((sum, output) => sum + output.size, 0);
1041
- const chunkCount = result.outputs.filter((output) => output.kind === "chunk").length;
1042
- this.#app.verbose(`[PagesBundleBuilder] ${path8.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`);
1043
- return {
1044
- bundlePath,
1045
- buildId,
1046
- splitting: this.#splitting,
1047
- entryBytes: entryArtifact.size,
1048
- outputBytes,
1049
- outputCount: result.outputs.length,
1050
- chunkCount
1051
- };
1052
- }
1053
- get #artifactDir() {
1054
- return `${this.#command === "build" ? this.#app.dist.cwdPath : this.#app.cwdPath}/.akan/artifact`;
1055
- }
1056
- get #splitting() {
1057
- return process.env.AKAN_SERVER_PAGES_SPLITTING === "1";
1058
- }
1059
- #define() {
1060
- const nodeEnv = this.#command === "build" ? "production" : "development";
1061
- return {
1062
- "process.env.NODE_ENV": JSON.stringify(nodeEnv),
1063
- "process.env.AKAN_PUBLIC_RENDER_ENV": JSON.stringify("ssr"),
1064
- ...Object.fromEntries(Object.entries(this.#app.getPublicEnv()).map(([key, value]) => [`process.env.${key}`, JSON.stringify(value)]))
1065
- };
1066
- }
1067
- static createPagesEntryPlugin(source) {
1068
- return {
1069
- name: "akan-pages-entry",
1070
- setup(build) {
1071
- build.onResolve({ filter: /^akan-pages-entry$/ }, () => ({
1072
- path: VIRTUAL_PAGES_ENTRY,
1073
- namespace: "akan-virtual"
1074
- }));
1075
- build.onLoad({ filter: /^akan-pages-entry$/, namespace: "akan-virtual" }, () => ({
1076
- contents: source,
1077
- loader: "tsx"
1078
- }));
1079
- }
1080
- };
1081
- }
1082
- static createCssStubPlugin() {
1083
- return {
1084
- name: "akan-css-stub",
1085
- setup(build) {
1086
- build.onLoad({ filter: /\.css$/ }, () => ({
1087
- contents: "",
1088
- loader: "js"
1089
- }));
1090
- }
1091
- };
1092
- }
1093
- static createServerUseClientFetchPlugin() {
1094
- return {
1095
- name: "akan-server-use-client-fetch",
1096
- setup(build) {
1097
- build.onLoad({ filter: /[/\\]lib[/\\]useClient\.(ts|tsx|js|jsx)$/ }, async (args) => {
1098
- const source = await Bun.file(args.path).text();
1099
- const transformed = PagesBundleBuilder.transformServerUseClientFetchSource(source);
1100
- if (transformed === source)
1101
- return;
1102
- return { contents: transformed, loader: loaderFor2(args.path) };
1103
- });
1104
- }
1105
- };
1106
- }
1107
- static transformServerUseClientFetchSource(source) {
1108
- if (!source.includes(`with { type: "macro" }`) || !source.includes("FetchClient.build"))
1109
- return source;
1110
- 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 });");
1111
- }
1112
- }
1113
- function loaderFor2(absPath) {
1114
- if (absPath.endsWith(".tsx"))
1115
- return "tsx";
1116
- if (absPath.endsWith(".jsx"))
1117
- return "jsx";
1118
- if (absPath.endsWith(".ts"))
1119
- return "ts";
1120
- return "js";
1121
- }
1122
-
1123
- // pkgs/@akanjs/devkit/frontendBuild/csrArtifactBuilder.ts
1124
- import { mkdir, rm, unlink } from "fs/promises";
1125
- import path9 from "path";
1126
- class CsrArtifactBuilder {
1127
- #app;
1128
- #command;
1129
- #lang;
1130
- constructor(app, command = "start", lang = "en") {
1131
- this.#app = app;
1132
- this.#command = command;
1133
- this.#lang = lang;
1134
- }
1135
- async build() {
1136
- const pageKeys = await this.#app.getPageKeys();
1137
- if (pageKeys.length === 0) {
1138
- this.#app.log(`[cli] no route files under ${this.#app.cwdPath}/page \u2014 skipping CSR build`);
1139
- return null;
1140
- }
1141
- const pageEntries = await resolveSsrPageEntriesForApp(this.#app, pageKeys);
1142
- const akanConfig = await this.#app.getConfig();
1143
- const cssAssets = await this.#loadCssAssets();
1144
- const basePaths = [...akanConfig.basePaths];
1145
- const htmlBasePaths = basePaths.length > 0 ? basePaths : [""];
1146
- await rm(this.#outputDir, { recursive: true, force: true });
1147
- await mkdir(this.#generatedDir, { recursive: true });
1148
- const generatedFiles = Object.fromEntries((await Promise.all(htmlBasePaths.map(async (basePath) => [
1149
- this.#createHtmlFile(basePath),
1150
- await this.#createEntryFile(basePath, CsrArtifactBuilder.pageEntriesForBasePath(pageEntries, basePath, basePaths))
1151
- ]))).flat());
1152
- const result = await Bun.build({
1153
- target: "browser",
1154
- entrypoints: htmlBasePaths.map((basePath) => this.#generatedPath(CsrArtifactBuilder.htmlFilename(basePath))),
1155
- files: generatedFiles,
1156
- root: this.#generatedDir,
1157
- outdir: this.#outputDir,
1158
- splitting: false,
1159
- minify: true,
1160
- env: "AKAN_PUBLIC_*",
1161
- define: this.#define(),
1162
- optimizeImports: akanConfig.optimizeImports,
1163
- plugins: [PagesBundleBuilder.createCssStubPlugin()]
1164
- });
1165
- if (!result.success) {
1166
- const logs = result.logs.map((log) => log.message).join(`
1167
- `);
1168
- throw new Error(`[csr-build] failed${logs ? `
1169
- ${logs}` : ""}`);
1170
- }
1171
- await this.#inlineCsrArtifacts(cssAssets);
1172
- this.#app.verbose(`[csr-build] output -> ${this.#outputDir}`);
1173
- return { outputDir: this.#outputDir };
1174
- }
1175
- static pageEntriesForBasePath(pageEntries, basePath, basePaths) {
1176
- return pageEntries.filter((entry) => {
1177
- const entryBasePath = getPageKeyBasePath(entry.key, basePaths);
1178
- return entryBasePath === null || entryBasePath === basePath;
1179
- });
1180
- }
1181
- static htmlFilename(basePath) {
1182
- return `${basePath || "index"}.html`;
1183
- }
1184
- static entryFilename(basePath) {
1185
- return `${basePath || "index"}.csr.tsx`;
1186
- }
1187
- static basePathOfHtml(htmlPath) {
1188
- const name = path9.basename(htmlPath, ".html");
1189
- return name === "index" ? "" : name;
1190
- }
1191
- get #outputDir() {
1192
- return path9.join(this.#command === "build" ? this.#app.dist.cwdPath : this.#app.cwdPath, this.#command === "build" ? "csr" : ".akan/artifact/csr");
1193
- }
1194
- get #generatedDir() {
1195
- return path9.join(this.#app.cwdPath, ".akan/generated/csr");
1196
- }
1197
- get #artifactDir() {
1198
- return path9.join(this.#command === "build" ? this.#app.dist.cwdPath : this.#app.cwdPath, ".akan/artifact");
1199
- }
1200
- #generatedPath(filename) {
1201
- return path9.join(this.#generatedDir, filename);
1202
- }
1203
- #define() {
1204
- const nodeEnv = this.#command === "build" ? "production" : "development";
1205
- return {
1206
- "process.env.NODE_ENV": JSON.stringify(nodeEnv),
1207
- "process.env.AKAN_PUBLIC_RENDER_ENV": JSON.stringify("csr"),
1208
- ...Object.fromEntries(Object.entries(this.#app.getPublicEnv()).map(([key, value]) => [`process.env.${key}`, JSON.stringify(value)]))
1209
- };
1210
- }
1211
- async#createEntryFile(basePath, pageEntries) {
1212
- return [
1213
- this.#generatedPath(CsrArtifactBuilder.entryFilename(basePath)),
1214
- `import { bootCsr } from "akanjs/webkit";
1215
- ${await PagesEntrySourceGenerator.generateStatic(pageEntries)}
1216
- void bootCsr(pages);
1217
- `
1218
- ];
1219
- }
1220
- #createHtmlFile(basePath) {
1221
- return [
1222
- this.#generatedPath(CsrArtifactBuilder.htmlFilename(basePath)),
1223
- `<!doctype html>
1224
- <html lang="${this.#lang}">
1225
- <head>
1226
- <meta charset="utf-8" />
1227
- <title>${this.#app.name}</title>
1228
- <base href="/" />
1229
- <meta name="viewport" content="width=device-width, initial-scale=1" />
1230
- </head>
1231
- <body>
1232
- <div id="root"></div>
1233
- <script type="module" src="./${CsrArtifactBuilder.entryFilename(basePath)}"></script>
1234
- </body>
1235
- </html>
1236
- `
1237
- ];
1238
- }
1239
- async#loadCssAssets() {
1240
- const artifactFile = Bun.file(path9.join(this.#artifactDir, "base-artifact.json"));
1241
- if (!await artifactFile.exists())
1242
- return {};
1243
- const artifact = await artifactFile.json();
1244
- return artifact.cssAssets ?? {};
1245
- }
1246
- async#inlineCsrArtifacts(cssAssets) {
1247
- const jsFiles = new Set;
1248
- for (const htmlPath of await this.#htmlOutputPaths()) {
1249
- const htmlFile = Bun.file(htmlPath);
1250
- if (!await htmlFile.exists())
688
+ } catch {
1251
689
  continue;
1252
- const basePath = CsrArtifactBuilder.basePathOfHtml(htmlPath);
1253
- const cssAsset = cssAssets[basePath];
1254
- if (!cssAsset) {
1255
- this.#app.logger.warn(`[csr-build] base-artifact.json has no compiled stylesheet for ${basePath || "root"}; ${path9.basename(htmlPath)} ships without CSS`);
1256
690
  }
1257
- const inlined = await this.#inlineHtmlAssets(await htmlFile.text(), htmlPath, cssAsset);
1258
- for (const filePath of inlined.jsFiles)
1259
- jsFiles.add(filePath);
1260
- await Bun.write(htmlPath, inlined.html);
1261
- }
1262
- for (const filePath of jsFiles)
1263
- await unlink(filePath).catch(() => {
1264
- return;
1265
- });
1266
- const remainingAssets = await this.#listOutputFiles((filePath) => filePath.endsWith(".js") || filePath.endsWith(".css"));
1267
- if (remainingAssets.length > 0) {
1268
- throw new Error(`[csr-build] expected single-file HTML, but CSR assets remain:
1269
- ${remainingAssets.join(`
1270
- `)}`);
691
+ const importerDir = path4.dirname(filePath);
692
+ for (const imp of imports) {
693
+ const spec = imp.path;
694
+ if (!spec)
695
+ continue;
696
+ if (spec.endsWith(".css")) {
697
+ const cssPath = await this.#resolveCssImport(spec, importerDir);
698
+ cssFiles.add(cssPath);
699
+ continue;
700
+ }
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);
707
+ }
1271
708
  }
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] };
1272
714
  }
1273
- async#inlineHtmlAssets(html, htmlPath, cssAsset) {
1274
- let next = html;
1275
- if (cssAsset) {
1276
- const css = await Bun.file(path9.join(this.#artifactDir, cssAsset.cssRelPath)).text();
1277
- next = CsrArtifactBuilder.injectBeforeHeadEnd(next, CsrArtifactBuilder.createInlineStyle(css));
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);
1278
725
  }
1279
- const jsFiles = [];
1280
- next = await CsrArtifactBuilder.replaceModuleScriptSrc(next, async (src) => {
1281
- const jsPath = CsrArtifactBuilder.resolveHtmlAssetPath(htmlPath, src);
1282
- jsFiles.push(jsPath);
1283
- return await Bun.file(jsPath).text();
1284
- });
1285
- return { html: next, jsFiles };
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);
1286
731
  }
1287
- async#htmlOutputPaths() {
1288
- return await this.#listOutputFiles((filePath) => filePath.endsWith(".html"));
732
+ async compileCss(cssPaths, sourcePaths) {
733
+ const { css } = await this.#compileWithImports(cssPaths, sourcePaths);
734
+ return css;
1289
735
  }
1290
- async#listOutputFiles(predicate) {
1291
- const glob = new Bun.Glob("**/*");
1292
- const files = [];
1293
- for await (const filePath of glob.scan({ cwd: this.#outputDir, absolute: true })) {
1294
- if (predicate(filePath))
1295
- files.push(filePath);
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);
1296
755
  }
1297
- return files.sort();
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`);
770
+ }
771
+ }
772
+ this.#logger.verbose(`css compiled paths=${cssPaths.length} candidates=${candidates.length} in ${Date.now() - compileStarted}ms`);
773
+ return { css: parts.join(`
774
+ `), imported };
1298
775
  }
1299
- static injectBeforeHeadEnd(html, snippet) {
1300
- const scannable = CsrArtifactBuilder.blankEmbeddedContent(html);
1301
- const headEnd = scannable.search(/<\/head\s*>/i);
1302
- if (headEnd !== -1)
1303
- return `${html.slice(0, headEnd)}${snippet}
1304
- ${html.slice(headEnd)}`;
1305
- const bodyStart = scannable.search(/<body(?:\s|>)/i);
1306
- if (bodyStart !== -1)
1307
- return `${html.slice(0, bodyStart)}${snippet}
1308
- ${html.slice(bodyStart)}`;
1309
- return `${html}
1310
- ${snippet}`;
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 };
1311
783
  }
1312
- static blankEmbeddedContent(html) {
1313
- return html.replace(/<script\b[^>]*>[\s\S]*?<\/script\s*>|<style\b[^>]*>[\s\S]*?<\/style\s*>|<!--[\s\S]*?-->/gi, (match) => " ".repeat(match.length));
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})`);
790
+ }
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}`);
1314
796
  }
1315
- static createInlineStyle(css) {
1316
- return `<style data-akan-css="active">
1317
- ${css.replace(/<\/style/gi, "<\\/style")}
1318
- </style>`;
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 };
1319
801
  }
1320
- static async replaceModuleScriptSrc(html, loadScript) {
1321
- const scriptRe = /<script\b(?=[^>]*\btype=["']module["'])(?=[^>]*\bsrc=["']([^"']+)["'])[^>]*>\s*<\/script>/gi;
1322
- let result = "";
1323
- let lastIndex = 0;
1324
- let matched = false;
1325
- for (const match of html.matchAll(scriptRe)) {
1326
- const full = match[0];
1327
- const src = match[1];
1328
- if (match.index === undefined || !src)
1329
- continue;
1330
- matched = true;
1331
- result += html.slice(lastIndex, match.index);
1332
- result += `<script type="module">
1333
- ${CsrArtifactBuilder.escapeInlineScript(await loadScript(src))}
1334
- </script>`;
1335
- lastIndex = match.index + full.length;
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;
810
+ }
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);
1336
815
  }
1337
- if (!matched)
1338
- return html;
1339
- return result + html.slice(lastIndex);
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;
823
+ }
824
+ return null;
1340
825
  }
1341
- static escapeInlineScript(source) {
1342
- return source.replace(/<\/script/gi, "<\\/script");
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];
1343
845
  }
1344
- static resolveHtmlAssetPath(htmlPath, src) {
1345
- if (/^[a-z][a-z0-9+.-]*:/i.test(src) || src.startsWith("//")) {
1346
- throw new Error(`[csr-build] cannot inline external script: ${src}`);
1347
- }
1348
- const normalized = src.startsWith("/") ? src.slice(1) : src;
1349
- return path9.resolve(path9.dirname(htmlPath), normalized);
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;
853
+ }
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;
1350
861
  }
1351
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
+ }
1352
876
 
1353
877
  // pkgs/@akanjs/devkit/frontendBuild/fontOptimizer.ts
1354
878
  import { mkdir as mkdir2, stat as stat2 } from "fs/promises";
1355
- import path10 from "path";
879
+ import path5 from "path";
1356
880
  import ts from "typescript";
1357
881
  var FONT_URL_PREFIX = "/_akan/fonts";
1358
882
  var DEFAULT_FONT_SUBSETS = ["latin"];
@@ -1369,7 +893,7 @@ class FontOptimizer {
1369
893
  constructor(app, command = "start") {
1370
894
  this.#app = app;
1371
895
  this.#command = command;
1372
- this.#artifactRoot = path10.join(command === "build" ? app.dist.cwdPath : app.cwdPath, ".akan/artifact");
896
+ this.#artifactRoot = path5.join(command === "build" ? app.dist.cwdPath : app.cwdPath, ".akan/artifact");
1373
897
  }
1374
898
  async optimize() {
1375
899
  const fonts = await this.discoverFonts();
@@ -1394,7 +918,7 @@ class FontOptimizer {
1394
918
  return result;
1395
919
  }
1396
920
  get #cachePath() {
1397
- return path10.join(this.#artifactRoot, "fontCache.json");
921
+ return path5.join(this.#artifactRoot, "fontCache.json");
1398
922
  }
1399
923
  async#buildCacheKey(fonts) {
1400
924
  const sources = [];
@@ -1409,7 +933,7 @@ class FontOptimizer {
1409
933
  sources.push({ optimizedSrc: face.optimizedSrc, ...stamp });
1410
934
  }
1411
935
  for (const filePath of font.subsetFiles ?? []) {
1412
- const abs = path10.isAbsolute(filePath) ? filePath : path10.join(this.#app.cwdPath, filePath);
936
+ const abs = path5.isAbsolute(filePath) ? filePath : path5.join(this.#app.cwdPath, filePath);
1413
937
  const stamp = await this.#fileStamp(abs);
1414
938
  if (!stamp)
1415
939
  return null;
@@ -1435,7 +959,7 @@ class FontOptimizer {
1435
959
  const cache = await Bun.file(this.#cachePath).json().catch(() => null);
1436
960
  if (cache?.version !== FontOptimizer.#cacheVersion || cache.key !== key)
1437
961
  return null;
1438
- const files = cache.files.map((relativePath) => path10.join(this.#artifactRoot, relativePath));
962
+ const files = cache.files.map((relativePath) => path5.join(this.#artifactRoot, relativePath));
1439
963
  for (const filePath of files) {
1440
964
  if (!await Bun.file(filePath).exists())
1441
965
  return null;
@@ -1447,7 +971,7 @@ class FontOptimizer {
1447
971
  version: FontOptimizer.#cacheVersion,
1448
972
  key,
1449
973
  css: result.css,
1450
- files: result.files.map((filePath) => path10.relative(this.#artifactRoot, filePath))
974
+ files: result.files.map((filePath) => path5.relative(this.#artifactRoot, filePath))
1451
975
  };
1452
976
  await Bun.write(this.#cachePath, JSON.stringify(cache));
1453
977
  }
@@ -1455,7 +979,7 @@ class FontOptimizer {
1455
979
  const pageKeys = await this.#app.getPageKeys();
1456
980
  const fonts = [];
1457
981
  await Promise.all(pageKeys.map(async (key) => {
1458
- const filePath = path10.resolve(this.#app.cwdPath, "page", key);
982
+ const filePath = path5.resolve(this.#app.cwdPath, "page", key);
1459
983
  const file = Bun.file(filePath);
1460
984
  if (!await file.exists())
1461
985
  return;
@@ -1464,7 +988,7 @@ class FontOptimizer {
1464
988
  return;
1465
989
  fonts.push(...this.#extractFontsExport(source, filePath));
1466
990
  }));
1467
- return this.#dedupeFonts(fonts);
991
+ return [...new Map(fonts.map((font) => [JSON.stringify(font), font])).values()];
1468
992
  }
1469
993
  async#optimizeFont(font) {
1470
994
  const faceCss = [];
@@ -1474,8 +998,8 @@ class FontOptimizer {
1474
998
  this.#app.logger.warn(`[font] source not found: ${face.src}`);
1475
999
  continue;
1476
1000
  }
1477
- const outputPath = path10.join(this.#artifactRoot, face.optimizedSrc.replace(/^\/_akan\//, ""));
1478
- await mkdir2(path10.dirname(outputPath), { recursive: true });
1001
+ const outputPath = path5.join(this.#artifactRoot, face.optimizedSrc.replace(/^\/_akan\//, ""));
1002
+ await mkdir2(path5.dirname(outputPath), { recursive: true });
1479
1003
  const sourceBuffer = Buffer.from(await Bun.file(sourcePath).arrayBuffer());
1480
1004
  const outputBuffer = await this.#buildFontBuffer(font, sourceBuffer, sourcePath);
1481
1005
  await Bun.write(outputPath, outputBuffer);
@@ -1497,8 +1021,8 @@ class FontOptimizer {
1497
1021
  const entries = Array.isArray(value) ? value : [];
1498
1022
  const fonts = entries.filter((entry) => this.#isReadableFont(entry));
1499
1023
  if (fonts.length !== entries.length || !Array.isArray(value))
1500
- this.#app.logger.warn(`[font] ${path10.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`);
1501
- return fonts.map((font) => this.#withFontDefaults(font));
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`);
1025
+ return fonts.map((font) => ({ ...font, subsets: font.subsets ?? [...DEFAULT_FONT_SUBSETS] }));
1502
1026
  }
1503
1027
  #isReadableFont(value) {
1504
1028
  if (!value || typeof value !== "object")
@@ -1587,15 +1111,6 @@ class FontOptimizer {
1587
1111
  return name.text;
1588
1112
  return null;
1589
1113
  }
1590
- #dedupeFonts(fonts) {
1591
- const map = new Map;
1592
- for (const font of fonts)
1593
- map.set(JSON.stringify(font), font);
1594
- return [...map.values()];
1595
- }
1596
- #withFontDefaults(font) {
1597
- return { ...font, subsets: font.subsets ?? [...DEFAULT_FONT_SUBSETS] };
1598
- }
1599
1114
  #getFontSubsets(font) {
1600
1115
  return font.subsets ?? DEFAULT_FONT_SUBSETS;
1601
1116
  }
@@ -1608,11 +1123,8 @@ class FontOptimizer {
1608
1123
  #isFontOptimizationEnabled(font) {
1609
1124
  return font.optimize !== false;
1610
1125
  }
1611
- #getFontStyles(font) {
1612
- return font.styles?.length ? font.styles : ["normal"];
1613
- }
1614
1126
  #getFontFaces(font) {
1615
- const enabledStyles = new Set(this.#getFontStyles(font));
1127
+ const enabledStyles = new Set(font.styles?.length ? font.styles : ["normal"]);
1616
1128
  return font.paths.map((fontPath) => {
1617
1129
  const style = fontPath.style ?? "normal";
1618
1130
  return {
@@ -1667,8 +1179,8 @@ class FontOptimizer {
1667
1179
  return null;
1668
1180
  const rel = src.replace(/^\//, "");
1669
1181
  const candidates = [
1670
- this.#command === "build" ? path10.join(this.#app.dist.cwdPath, "public", rel) : null,
1671
- path10.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),
1672
1184
  this.#resolveWorkspacePublicPath(rel)
1673
1185
  ].filter(Boolean);
1674
1186
  for (const candidate of candidates) {
@@ -1685,15 +1197,12 @@ class FontOptimizer {
1685
1197
  }
1686
1198
  async#convertToWoff2(buffer, sourcePath) {
1687
1199
  const { createFont } = await import("fonteditor-core");
1688
- await this.#initWoff2();
1689
- const font = createFont(buffer, { type: this.#getFontType(sourcePath, buffer) });
1690
- return font.write({ type: "woff2", toBuffer: true });
1691
- }
1692
- async#initWoff2() {
1693
1200
  this.#woff2Ready ??= import("fonteditor-core").then(({ woff2 }) => woff2.init()).then(() => {
1694
1201
  return;
1695
1202
  });
1696
- return this.#woff2Ready;
1203
+ await this.#woff2Ready;
1204
+ const font = createFont(buffer, { type: this.#getFontType(sourcePath, buffer) });
1205
+ return font.write({ type: "woff2", toBuffer: true });
1697
1206
  }
1698
1207
  #getFontType(sourcePath, buffer) {
1699
1208
  const signature = buffer.toString("ascii", 0, 4);
@@ -1703,7 +1212,7 @@ class FontOptimizer {
1703
1212
  return "woff2";
1704
1213
  if (signature === "OTTO")
1705
1214
  return "otf";
1706
- const ext = path10.extname(sourcePath).slice(1).toLowerCase();
1215
+ const ext = path5.extname(sourcePath).slice(1).toLowerCase();
1707
1216
  if (ext === "otf" || ext === "woff" || ext === "woff2")
1708
1217
  return ext;
1709
1218
  return "ttf";
@@ -1712,7 +1221,7 @@ class FontOptimizer {
1712
1221
  const [root, dep, ...rest] = rel.split("/");
1713
1222
  if (root !== "libs" || !dep || rest.length === 0)
1714
1223
  return null;
1715
- return path10.join(this.#app.workspace.workspaceRoot, "libs", dep, "public", ...rest);
1224
+ return path5.join(this.#app.workspace.workspaceRoot, "libs", dep, "public", ...rest);
1716
1225
  }
1717
1226
  async#getSubsetText(font) {
1718
1227
  const parts = new Set;
@@ -1722,7 +1231,7 @@ class FontOptimizer {
1722
1231
  if (font.subsetText)
1723
1232
  parts.add(font.subsetText);
1724
1233
  for (const filePath of font.subsetFiles ?? []) {
1725
- const abs = path10.isAbsolute(filePath) ? filePath : path10.join(this.#app.cwdPath, filePath);
1234
+ const abs = path5.isAbsolute(filePath) ? filePath : path5.join(this.#app.cwdPath, filePath);
1726
1235
  const file = Bun.file(abs);
1727
1236
  if (await file.exists())
1728
1237
  parts.add(await file.text());
@@ -1731,9 +1240,9 @@ class FontOptimizer {
1731
1240
  }
1732
1241
  async#getSubsetPresetText(subset) {
1733
1242
  if (subset === "latin")
1734
- return this.#rangeText(32, 126);
1243
+ return FontOptimizer.#rangeText(32, 126);
1735
1244
  if (subset === "latin-ext")
1736
- return `${this.#rangeText(32, 126)}${this.#rangeText(160, 591)}`;
1245
+ return `${FontOptimizer.#rangeText(32, 126)}${FontOptimizer.#rangeText(160, 591)}`;
1737
1246
  if (subset === "ks-x-1001")
1738
1247
  return FontOptimizer.#getKsX1001Text();
1739
1248
  if (subset === "auto")
@@ -1742,7 +1251,7 @@ class FontOptimizer {
1742
1251
  }
1743
1252
  async#collectAutoSubsetText() {
1744
1253
  const libPageRoots = (await this.#app.getPageRoots()).filter((root) => root.keyPrefix).map((root) => root.dir);
1745
- const roots = [...["page", "ui", "lib"].map((dir) => path10.join(this.#app.cwdPath, dir)), ...libPageRoots];
1254
+ const roots = [...["page", "ui", "lib"].map((dir) => path5.join(this.#app.cwdPath, dir)), ...libPageRoots];
1746
1255
  const glob = new Bun.Glob("**/*.{ts,tsx,js,jsx,html,md}");
1747
1256
  const parts = [];
1748
1257
  for (const root of [...new Set(roots)].sort()) {
@@ -1757,12 +1266,6 @@ class FontOptimizer {
1757
1266
  }
1758
1267
  return parts.join("");
1759
1268
  }
1760
- #rangeText(start, end) {
1761
- let text = "";
1762
- for (let code = start;code <= end; code++)
1763
- text += String.fromCodePoint(code);
1764
- return text;
1765
- }
1766
1269
  static #getKsX1001Text() {
1767
1270
  if (FontOptimizer.#ksX1001Text)
1768
1271
  return FontOptimizer.#ksX1001Text;
@@ -1778,11 +1281,11 @@ class FontOptimizer {
1778
1281
  }
1779
1282
  FontOptimizer.#ksX1001Text = [...chars].join("");
1780
1283
  } catch {
1781
- FontOptimizer.#ksX1001Text = FontOptimizer.#rangeTextStatic(44032, 55203);
1284
+ FontOptimizer.#ksX1001Text = FontOptimizer.#rangeText(44032, 55203);
1782
1285
  }
1783
1286
  return FontOptimizer.#ksX1001Text;
1784
1287
  }
1785
- static #rangeTextStatic(start, end) {
1288
+ static #rangeText(start, end) {
1786
1289
  let text = "";
1787
1290
  for (let code = start;code <= end; code++)
1788
1291
  text += String.fromCodePoint(code);
@@ -1862,7 +1365,7 @@ ${declarations.map(([prop, value]) => ` ${prop}: ${value};`).join(`
1862
1365
 
1863
1366
  // pkgs/@akanjs/devkit/frontendBuild/fontPruner.ts
1864
1367
  import { rm as rm2, rmdir, stat as stat3 } from "fs/promises";
1865
- import path11 from "path";
1368
+ import path6 from "path";
1866
1369
  import { isFontOptimizationEnabled } from "akanjs/client";
1867
1370
  var FONT_GLOB = "**/*.{woff2,woff,ttf,otf,ttc,eot}";
1868
1371
  var REFERRER_GLOB = "**/*.{css,js,mjs,cjs,json,html,htm,svg,xml,webmanifest}";
@@ -1875,8 +1378,8 @@ class FontPruner {
1875
1378
  constructor(app, { keepFonts = [] } = {}) {
1876
1379
  this.#app = app;
1877
1380
  this.#keepGlobs = keepFonts;
1878
- this.#publicRoot = path11.join(app.dist.cwdPath, "public");
1879
- this.#artifactRoot = path11.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");
1880
1383
  }
1881
1384
  async prune() {
1882
1385
  const empty = { removed: [], kept: [], freedBytes: 0 };
@@ -1891,7 +1394,7 @@ class FontPruner {
1891
1394
  for (const font of fonts) {
1892
1395
  const target = isFontOptimizationEnabled(font) ? optimizedSrcs : unoptimizedSrcs;
1893
1396
  for (const fontPath of font.paths)
1894
- target.add(path11.posix.basename(fontPath.src));
1397
+ target.add(path6.posix.basename(fontPath.src));
1895
1398
  }
1896
1399
  const result = { removed: [], kept: [], freedBytes: 0 };
1897
1400
  const undecided = [];
@@ -1929,10 +1432,10 @@ class FontPruner {
1929
1432
  async#collectCandidates() {
1930
1433
  const candidates = [];
1931
1434
  for await (const rel of new Bun.Glob(FONT_GLOB).scan({ cwd: this.#publicRoot, dot: false })) {
1932
- const entry = await stat3(path11.join(this.#publicRoot, rel)).catch(() => null);
1435
+ const entry = await stat3(path6.join(this.#publicRoot, rel)).catch(() => null);
1933
1436
  if (!entry?.isFile())
1934
1437
  continue;
1935
- candidates.push({ rel: rel.split(path11.sep).join("/"), basename: path11.basename(rel), bytes: entry.size });
1438
+ candidates.push({ rel: rel.split(path6.sep).join("/"), basename: path6.basename(rel), bytes: entry.size });
1936
1439
  }
1937
1440
  return candidates;
1938
1441
  }
@@ -1942,11 +1445,11 @@ class FontPruner {
1942
1445
  const bundled = candidates.filter((candidate) => !optimizedSrcs.has(candidate.basename));
1943
1446
  const roots = [
1944
1447
  { dir: this.#publicRoot, label: "public", needles: authoritative },
1945
- { dir: path11.join(this.#artifactRoot, "styles"), label: ".akan/artifact/styles", needles: authoritative },
1946
- { dir: path11.join(this.#artifactRoot, "client"), label: ".akan/artifact/client", needles: bundled },
1947
- { dir: path11.join(this.#artifactRoot, "client-ssr"), label: ".akan/artifact/client-ssr", needles: bundled },
1948
- { dir: path11.join(this.#artifactRoot, "server"), label: ".akan/artifact/server", needles: bundled },
1949
- { dir: path11.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 }
1950
1453
  ];
1951
1454
  for (const root of roots) {
1952
1455
  const pending = root.needles.filter((candidate) => !referrers.has(candidate.rel));
@@ -1965,20 +1468,20 @@ class FontPruner {
1965
1468
  const remaining = needles.filter((needle) => !referrers.has(needle.rel));
1966
1469
  if (!remaining.length)
1967
1470
  return;
1968
- const text = await Bun.file(path11.join(root.dir, rel)).text().catch(() => "");
1471
+ const text = await Bun.file(path6.join(root.dir, rel)).text().catch(() => "");
1969
1472
  if (!text)
1970
1473
  continue;
1971
1474
  for (const needle of remaining) {
1972
1475
  if (needle.forms.some((form) => text.includes(form)))
1973
- referrers.set(needle.rel, `${root.label}/${rel.split(path11.sep).join("/")}`);
1476
+ referrers.set(needle.rel, `${root.label}/${rel.split(path6.sep).join("/")}`);
1974
1477
  }
1975
1478
  }
1976
1479
  }
1977
1480
  async#remove(files) {
1978
- await Promise.all(files.map((file) => rm2(path11.join(this.#publicRoot, file), { force: true })));
1979
- const dirs = [...new Set(files.map((file) => path11.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);
1980
1483
  for (const dir of dirs)
1981
- await rmdir(path11.join(this.#publicRoot, dir)).catch(() => {
1484
+ await rmdir(path6.join(this.#publicRoot, dir)).catch(() => {
1982
1485
  return;
1983
1486
  });
1984
1487
  }
@@ -2026,17 +1529,18 @@ class FontPruner {
2026
1529
 
2027
1530
  // pkgs/@akanjs/devkit/frontendBuild/precompressArtifacts.ts
2028
1531
  import fs from "fs";
2029
- import path12 from "path";
1532
+ import path7 from "path";
2030
1533
  import zlib from "zlib";
2031
1534
  var COMPRESSIBLE_EXTS = new Set([".css", ".html", ".js", ".json", ".svg"]);
2032
1535
  var MIN_COMPRESS_BYTES = 1024;
2033
1536
  var BROTLI_QUALITY_BY_EXT = { ".css": 11 };
2034
1537
  var DEFAULT_BROTLI_QUALITY = 9;
2035
1538
  var GZIP_LEVEL = 9;
1539
+ var { formatBytes } = FontPruner;
2036
1540
  async function precompressArtifacts(app) {
2037
1541
  const roots = [
2038
- path12.join(app.dist.cwdPath, ".akan/artifact/client"),
2039
- path12.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")
2040
1544
  ];
2041
1545
  const result = { files: 0, inputBytes: 0, outputBytes: 0, brotliBytes: 0 };
2042
1546
  await Promise.all(roots.map((root) => precompressRoot(root, result)));
@@ -2053,9 +1557,9 @@ async function precompressRoot(root, result) {
2053
1557
  if (!await shouldPrecompress(filePath))
2054
1558
  continue;
2055
1559
  const bytes = await Bun.file(filePath).bytes();
2056
- const buffer = toArrayBuffer(bytes);
1560
+ const buffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
2057
1561
  const gz = Bun.gzipSync(buffer, { level: GZIP_LEVEL });
2058
- const br = brotliCompress(buffer, path12.extname(filePath).toLowerCase());
1562
+ const br = brotliCompress(buffer, path7.extname(filePath).toLowerCase());
2059
1563
  await Promise.all([Bun.write(`${filePath}.gz`, gz), Bun.write(`${filePath}.br`, br)]);
2060
1564
  result.files += 1;
2061
1565
  result.inputBytes += bytes.byteLength;
@@ -2075,26 +1579,16 @@ function brotliCompress(buffer, ext) {
2075
1579
  async function shouldPrecompress(filePath) {
2076
1580
  if (filePath.endsWith(".gz") || filePath.endsWith(".br"))
2077
1581
  return false;
2078
- if (!COMPRESSIBLE_EXTS.has(path12.extname(filePath).toLowerCase()))
1582
+ if (!COMPRESSIBLE_EXTS.has(path7.extname(filePath).toLowerCase()))
2079
1583
  return false;
2080
1584
  const file = Bun.file(filePath);
2081
1585
  if (!await file.exists())
2082
1586
  return false;
2083
1587
  return file.size >= MIN_COMPRESS_BYTES;
2084
1588
  }
2085
- function toArrayBuffer(bytes) {
2086
- return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
2087
- }
2088
- function formatBytes(bytes) {
2089
- if (bytes < 1024)
2090
- return `${bytes}B`;
2091
- if (bytes < 1024 * 1024)
2092
- return `${(bytes / 1024).toFixed(1)}KB`;
2093
- return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
2094
- }
2095
1589
 
2096
1590
  // pkgs/@akanjs/devkit/frontendBuild/ssrBaseArtifactBuilder.ts
2097
- import path13 from "path";
1591
+ import path8 from "path";
2098
1592
  import { optimize } from "@tailwindcss/node";
2099
1593
  function prepareCssAsset(command, basePath, cssText) {
2100
1594
  return optimize(cssText, { file: `${basePath || "root"}.css`, minify: command === "build" }).code;
@@ -2110,7 +1604,10 @@ class SsrBaseArtifactBuilder {
2110
1604
  this.#app = app;
2111
1605
  this.#command = command;
2112
1606
  this.#artifactDir = `${command === "build" ? app.dist.cwdPath : app.cwdPath}/.akan/artifact`;
2113
- this.#absArtifactDir = path13.resolve(this.#artifactDir);
1607
+ this.#absArtifactDir = path8.resolve(this.#artifactDir);
1608
+ }
1609
+ static servedWeb(command, web) {
1610
+ return command === "start" ? { ssr: true, csr: true } : web;
2114
1611
  }
2115
1612
  async build() {
2116
1613
  const akanConfig = await this.#app.getConfig();
@@ -2132,7 +1629,7 @@ class SsrBaseArtifactBuilder {
2132
1629
  rscRuntimeSsrManifest,
2133
1630
  vendorMap,
2134
1631
  cssAssets,
2135
- pagesBundlePath: this.#command === "build" ? path13.relative(this.#absArtifactDir, pagesBundle.bundlePath) : pagesBundle.bundlePath,
1632
+ pagesBundlePath: this.#command === "build" ? path8.relative(this.#absArtifactDir, pagesBundle.bundlePath) : pagesBundle.bundlePath,
2136
1633
  pagesBundleBuildId: pagesBundle.buildId,
2137
1634
  domains: [...akanConfig.domains],
2138
1635
  subRoutes: Object.fromEntries(Array.from(akanConfig.subRoutes.entries()).map(([basePath, domains]) => [basePath, [...domains]])),
@@ -2140,27 +1637,28 @@ class SsrBaseArtifactBuilder {
2140
1637
  branches: [...akanConfig.branches],
2141
1638
  i18n: akanConfig.i18n,
2142
1639
  imageConfig: akanConfig.images,
2143
- web: akanConfig.web,
2144
- deepLinkAssociations: Object.values(akanConfig.mobile.targets).filter((target) => (target.deepLinks?.domains?.length ?? 0) > 0).map((target) => ({
1640
+ web: SsrBaseArtifactBuilder.servedWeb(this.#command, akanConfig.web),
1641
+ deepLinkAssociations: Object.values(akanConfig.native.targets).filter((target) => (target.deepLinks?.domains?.length ?? 0) > 0).map((target) => ({
2145
1642
  targetName: target.name,
2146
- appId: target.appId,
1643
+ iosAppId: SsrBaseArtifactBuilder.#appIdOn(target.appId, "ios"),
1644
+ androidAppId: SsrBaseArtifactBuilder.#appIdOn(target.appId, "android"),
2147
1645
  domains: target.deepLinks?.domains ?? [],
2148
- iosTeamId: target.deepLinks?.ios?.teamId,
2149
- androidSha256CertFingerprints: target.deepLinks?.android?.sha256CertFingerprints
1646
+ iosTeamId: target.ios?.teamId,
1647
+ androidSha256CertFingerprints: target.android?.sha256CertFingerprints
2150
1648
  }))
2151
1649
  };
2152
- await Bun.write(path13.join(this.#absArtifactDir, "base-artifact.json"), `${JSON.stringify(artifact, null, 2)}
1650
+ await Bun.write(path8.join(this.#absArtifactDir, "base-artifact.json"), `${JSON.stringify(artifact, null, 2)}
2153
1651
  `);
2154
1652
  this.#app.verbose(`[base-artifact] complete in ${Date.now() - this.#started}ms`);
2155
1653
  return { artifact, seedIndex, cssCompiler, optimizedFonts };
2156
1654
  }
2157
1655
  async#buildRuntimeClientEntries() {
2158
1656
  const akanServerPath = await this.#resolveAkanServerPath();
2159
- const rscClientEntry = path13.resolve(akanServerPath, "rscClient.tsx");
2160
- const rscSegmentOutletEntry = path13.resolve(akanServerPath, "rscSegmentOutlet.tsx");
1657
+ const rscClientEntry = path8.resolve(akanServerPath, "rscClient.tsx");
1658
+ const rscSegmentOutletEntry = path8.resolve(akanServerPath, "rscSegmentOutlet.tsx");
2161
1659
  const vendorEntries = VENDOR_SPECIFIERS.map((specifier) => ({
2162
1660
  specifier,
2163
- absPath: path13.resolve(akanServerPath, "vendor", `${specifier.replaceAll("/", "-").replaceAll(".", "-")}.ts`)
1661
+ absPath: path8.resolve(akanServerPath, "vendor", `${specifier.replaceAll("/", "-").replaceAll(".", "-")}.ts`)
2164
1662
  }));
2165
1663
  const entries = [rscClientEntry, rscSegmentOutletEntry, ...vendorEntries.map((v) => v.absPath)];
2166
1664
  const clientBundle = await new ClientEntriesBundler({ app: this.#app, entries, command: this.#command }).bundle();
@@ -2197,15 +1695,15 @@ class SsrBaseArtifactBuilder {
2197
1695
  async#resolveAkanServerPath() {
2198
1696
  const candidates = [];
2199
1697
  try {
2200
- candidates.push(path13.dirname(Bun.resolveSync("akanjs/server", this.#app.workspace.workspaceRoot)));
1698
+ candidates.push(path8.dirname(Bun.resolveSync("akanjs/server", this.#app.workspace.workspaceRoot)));
2201
1699
  } catch {}
2202
- candidates.push(path13.join(this.#app.workspace.workspaceRoot, "pkgs/akanjs/server"), path13.join(this.#app.workspace.workspaceRoot, "node_modules/akanjs/server"));
1700
+ candidates.push(path8.join(this.#app.workspace.workspaceRoot, "pkgs/akanjs/server"), path8.join(this.#app.workspace.workspaceRoot, "node_modules/akanjs/server"));
2203
1701
  try {
2204
- candidates.push(path13.dirname(Bun.resolveSync("akanjs/server", path13.dirname(Bun.main))));
1702
+ candidates.push(path8.dirname(Bun.resolveSync("akanjs/server", path8.dirname(Bun.main))));
2205
1703
  } catch {}
2206
- candidates.push(path13.join(path13.dirname(Bun.main), "node_modules/akanjs/server"), path13.join(path13.dirname(Bun.main), "../../akanjs/server"), path13.resolve(import.meta.dir, "../../server"), path13.resolve(import.meta.dir, "../server"));
1704
+ 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"));
2207
1705
  for (const candidate of candidates) {
2208
- if (await Bun.file(path13.join(candidate, "rscClient.tsx")).exists())
1706
+ if (await Bun.file(path8.join(candidate, "rscClient.tsx")).exists())
2209
1707
  return candidate;
2210
1708
  }
2211
1709
  throw new Error(`[base-artifact] failed to locate akanjs/server; looked in: ${candidates.join(", ")}`);
@@ -2234,11 +1732,14 @@ ${preparedCssText}`).toString(36);
2234
1732
  `styles/${cssAssetName}-${cssHash}.css`,
2235
1733
  `/_akan/styles/${cssAssetName}-${cssHash}.css`
2236
1734
  ];
2237
- await Bun.write(path13.join(this.#absArtifactDir, cssRelPath), preparedCssText);
1735
+ await Bun.write(path8.join(this.#absArtifactDir, cssRelPath), preparedCssText);
2238
1736
  SsrBaseArtifactBuilder.#warnDroppedImports(this.#app, cssRelPath, preparedCssText, imported);
2239
1737
  this.#app.verbose(`[base-artifact] wrote ${preparedCssText.length} bytes of CSS for ${basePath} -> ${cssRelPath}`);
2240
1738
  return [basePath, { cssUrl, cssRelPath }];
2241
1739
  }
1740
+ static #appIdOn(appId, platform) {
1741
+ return typeof appId === "string" ? appId : appId[platform] ?? appId.default ?? null;
1742
+ }
2242
1743
  static #warnDroppedImports(app, cssRelPath, css, imported) {
2243
1744
  for (const { cssPath, declaredNames } of imported) {
2244
1745
  if (declaredNames.length === 0 || declaredNames.some((name) => css.includes(`${name}:`)))
@@ -2247,4 +1748,4 @@ ${preparedCssText}`).toString(36);
2247
1748
  }
2248
1749
  }
2249
1750
  }
2250
- export { AllRoutesBuilder, CssCompiler, PagesBundleBuilder, CsrArtifactBuilder, FontOptimizer, FontPruner, precompressArtifacts, SsrBaseArtifactBuilder };
1751
+ export { AllRoutesBuilder, CsrArtifactBuilder, CssCompiler, FontOptimizer, FontPruner, precompressArtifacts, SsrBaseArtifactBuilder };