@akanjs/cli 3.0.0-alpha.7 → 3.0.0-alpha.70

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 (119) hide show
  1. package/.build-stamp +1 -1
  2. package/README.ko.md +1 -1
  3. package/README.md +1 -1
  4. package/{abstractCompactor-e5yq60qz.js → abstractCompactor-20e0cwbs.js} +2 -2
  5. package/{agent.command-pggr92jj.js → agent.command-964bf8qg.js} +9 -7
  6. package/{application.command-692yap1d.js → application.command-n45n18sc.js} +8 -6
  7. package/{applicationBuildRunner-ha3y8ew8.js → applicationBuildRunner-af0wtspv.js} +19 -14
  8. package/{applicationReleasePackager-gg8ccbeh.js → applicationReleasePackager-93nzdfsy.js} +1 -1
  9. package/buildBatch.proc.js +7 -5
  10. package/{capacitorApp-p0z5yab2.js → capacitorApp-vnvnvdhf.js} +29 -27
  11. package/{cloud.command-wqfsyzv2.js → cloud.command-kx1bh5yq.js} +12 -10
  12. package/{context.command-9bxtdn0w.js → context.command-g1a65x6j.js} +17 -15
  13. package/{dependencyScanner-f6grrep3.js → dependencyScanner-rbs2407b.js} +1 -1
  14. package/getDirname-ka11hm7e.js +8 -0
  15. package/{guideline.command-nfqahxex.js → guideline.command-xphyhvwh.js} +8 -6
  16. package/guidelines/agentRule/agentRule.generate.json +31 -0
  17. package/guidelines/agentRule/agentRule.instruction.md +525 -0
  18. package/guidelines/conventions/conventions.generate.json +25 -0
  19. package/guidelines/conventions/conventions.instruction.md +1020 -0
  20. package/guidelines/cssRule/cssRule.instruction.md +7 -0
  21. package/guidelines/framework/framework.instruction.md +1 -0
  22. package/guidelines/mcpRule/mcpRule.generate.json +25 -0
  23. package/guidelines/mcpRule/mcpRule.instruction.md +220 -0
  24. package/guidelines/workspaceOnboarding/workspaceOnboarding.generate.json +24 -0
  25. package/guidelines/workspaceOnboarding/workspaceOnboarding.instruction.md +638 -0
  26. package/incrementalBuilder.proc.js +8 -6
  27. package/{index-azv0xjb5.js → index-0ngvvgry.js} +8 -4
  28. package/index-1577bej2.js +7 -0
  29. package/{index-p3f4q532.js → index-4rw4qhe6.js} +14 -13
  30. package/{index-bjpxzr6s.js → index-5vy2jzar.js} +1 -1
  31. package/{index-js368gk9.js → index-8gybxemv.js} +6 -6
  32. package/{index-qb61h0k7.js → index-abyjt72y.js} +333 -540
  33. package/{index-4hek879p.js → index-bqqts262.js} +1 -1
  34. package/{index-6rhabeev.js → index-e31ghfp5.js} +5 -3
  35. package/index-f39512t6.js +255 -0
  36. package/{index-5pmx7bkg.js → index-fahkq20v.js} +3 -3
  37. package/{index-xhgtkekh.js → index-hee7egtm.js} +1 -1
  38. package/{index-7myvn0f0.js → index-j37qq1f2.js} +1 -1
  39. package/{index-5dw57f0d.js → index-mkn5qagt.js} +14 -14
  40. package/{index-ztpw544k.js → index-nj3w0r34.js} +3 -3
  41. package/{index-vg0sj9nc.js → index-nkwmnqwn.js} +257 -97
  42. package/{index-a1e0j63x.js → index-p681w089.js} +15 -7
  43. package/{index-j9cxndc0.js → index-qnv0aqmm.js} +9 -6
  44. package/{index-w6wbb612.js → index-qt4nfr0n.js} +13 -13
  45. package/{index-bz2sfzzt.js → index-rb65xnw9.js} +4 -4
  46. package/{index-gex7c8kd.js → index-rbnpe8cp.js} +37 -6
  47. package/{index-0cj2zxbm.js → index-sh01r3pm.js} +1 -1
  48. package/{index-gmy45vz2.js → index-v5fdxdre.js} +4 -4
  49. package/{index-mssqn2sq.js → index-va15r8h5.js} +54 -15
  50. package/{index-vtj3zps1.js → index-yg3hxk2k.js} +8 -8
  51. package/{index-2c3e2xh8.js → index-ytaqtc04.js} +1 -1
  52. package/index.js +21 -19
  53. package/{library.command-md9715jz.js → library.command-rfpd27yv.js} +6 -4
  54. package/{localRegistry.command-exkbamyj.js → localRegistry.command-pyxfe0fs.js} +11 -9
  55. package/{module.command-w5aaddxk.js → module.command-qhaxad0p.js} +10 -8
  56. package/{package.command-yfkrcy0z.js → package.command-2k22q9nt.js} +6 -4
  57. package/package.json +4 -5
  58. package/{page.command-echbavp4.js → page.command-ymk9dqh4.js} +6 -4
  59. package/{primitive.command-9fbhspw2.js → primitive.command-9k45zpc1.js} +11 -9
  60. package/{quality.command-3pnt9yep.js → quality.command-bf6vkxe5.js} +192 -97
  61. package/{recipeScanner-tghkzrrt.js → recipeScanner-zrzev25x.js} +3 -3
  62. package/{repair.command-carj4vax.js → repair.command-egvvm6xf.js} +8 -6
  63. package/{routeSourceValidator-e4jmbfcx.js → routeSourceValidator-7fycdqyv.js} +1 -1
  64. package/{scalar.command-51ryzypc.js → scalar.command-bt1facyj.js} +9 -7
  65. package/templates/app/env/env.client.debug.ts.template +1 -5
  66. package/templates/app/env/env.client.develop.ts.template +1 -5
  67. package/templates/app/env/env.client.local.ts.template +1 -5
  68. package/templates/app/env/env.client.main.ts.template +1 -5
  69. package/templates/app/env/env.client.testing.ts.template +1 -5
  70. package/templates/app/env/env.client.type.ts +2 -6
  71. package/templates/app/env/env.server.debug.ts.template +1 -5
  72. package/templates/app/env/env.server.develop.ts.template +1 -5
  73. package/templates/app/env/env.server.local.ts.template +1 -5
  74. package/templates/app/env/env.server.main.ts.template +1 -5
  75. package/templates/app/main.ts +3 -1
  76. package/templates/app/page/styles.css.template +3 -3
  77. package/templates/appSample/srvkit/AuthGuard.ts +9 -5
  78. package/templates/appSample/srvkit/SessionInternalArg.ts +4 -3
  79. package/templates/crudPages/[__model__Id]/edit/page.tsx +1 -1
  80. package/templates/crudPages/new/page.tsx +1 -1
  81. package/templates/crudSinglePage/page.tsx +1 -1
  82. package/templates/env/_env.server.type.ts +7 -5
  83. package/templates/lib/db.ts +1 -1
  84. package/templates/lib/dict.ts +1 -1
  85. package/templates/lib/sig.ts +1 -1
  86. package/templates/lib/srv.ts +6 -2
  87. package/templates/module/__Model__.Util.tsx +1 -1
  88. package/templates/module/__Model__.Zone.tsx +3 -5
  89. package/templates/workspaceRoot/AGENTS.md.template +0 -972
  90. package/templates/workspaceRoot/CLAUDE.md.template +19 -0
  91. package/templates/workspaceRoot/biome.json.template +4 -244
  92. package/templates/workspaceRoot/docs/AI-DEVELOPMENT.md.template +10 -14
  93. package/templates/workspaceRoot/docs/GENERATED.md.template +12 -10
  94. package/templates/workspaceRoot/package.json.template +1 -1
  95. package/{typeChecker-kravn7ns.js → typeChecker-g6bh0xfv.js} +1 -1
  96. package/typecheck.proc.js +1 -1
  97. package/{workflow.command-91wmvm8k.js → workflow.command-v1n3wg7v.js} +16 -14
  98. package/{workspace.command-wa10mfd9.js → workspace.command-yvvqvhzz.js} +71 -52
  99. package/templates/workspaceRoot/.cursor/rules/akan-page-routing.mdc.template +0 -43
  100. package/templates/workspaceRoot/.cursor/rules/akan-sync-conventions.mdc.template +0 -34
  101. package/templates/workspaceRoot/.cursor/rules/application-test-commands.mdc.template +0 -16
  102. package/templates/workspaceRoot/.cursor/rules/change-scope.mdc.template +0 -12
  103. package/templates/workspaceRoot/.cursor/rules/client-server-boundaries.mdc.template +0 -18
  104. package/templates/workspaceRoot/.cursor/rules/coding-comments.mdc.template +0 -25
  105. package/templates/workspaceRoot/.cursor/rules/coding-style.mdc.template +0 -44
  106. package/templates/workspaceRoot/.cursor/rules/domain-module-conventions.mdc.template +0 -17
  107. package/templates/workspaceRoot/.cursor/rules/format-lint.mdc.template +0 -12
  108. package/templates/workspaceRoot/.cursor/rules/lint-enforced-rules.mdc.template +0 -19
  109. package/templates/workspaceRoot/.cursor/rules/module-file-playbook.mdc.template +0 -62
  110. package/templates/workspaceRoot/.cursor/rules/naming-conventions.mdc.template +0 -15
  111. package/templates/workspaceRoot/.cursor/rules/react-component-style.mdc.template +0 -21
  112. package/templates/workspaceRoot/.cursor/rules/repo-overview.mdc.template +0 -17
  113. package/templates/workspaceRoot/.cursor/rules/scalar-modeling.mdc.template +0 -99
  114. package/templates/workspaceRoot/.cursor/rules/secrets-and-env.mdc.template +0 -13
  115. package/templates/workspaceRoot/.cursor/rules/service-signal-conventions.mdc.template +0 -83
  116. package/templates/workspaceRoot/.cursor/rules/srvkit-adapters-and-guards.mdc.template +0 -50
  117. package/templates/workspaceRoot/.cursor/rules/ssr-first.mdc.template +0 -59
  118. package/templates/workspaceRoot/.cursor/rules/typescript-imports.mdc.template +0 -16
  119. /package/{index-r24hmh0q.js → index-yb2wsc7f.js} +0 -0
@@ -9,276 +9,26 @@ import {
9
9
  createTsconfigPackageResolver,
10
10
  rewriteBarrelImports,
11
11
  transformUseClient
12
- } from "./index-j9cxndc0.js";
12
+ } from "./index-qnv0aqmm.js";
13
+ import {
14
+ resolveSsrPageEntriesForApp
15
+ } from "./index-f39512t6.js";
13
16
  import {
14
17
  __require
15
- } from "./index-r24hmh0q.js";
18
+ } from "./index-yb2wsc7f.js";
16
19
 
17
20
  // pkgs/@akanjs/devkit/frontendBuild/allRoutesBuilder.ts
18
- import path4 from "path";
19
-
20
- // pkgs/@akanjs/devkit/artifact/implicitRootLayout.ts
21
- import { mkdir } from "fs/promises";
22
- import path from "path";
23
- var LAYOUT_KEY_RE = /^\.\/(.+\/)?_layout\.(tsx|ts|jsx|js)$/;
24
- async function appHasStModule(appCwdPath) {
25
- return Bun.file(path.join(appCwdPath, "lib", "st.ts")).exists();
26
- }
27
- var IMPLICIT_LAYOUT_DIR = path.join(".akan", "generated", "root-layouts");
28
- var IMPLICIT_DICT_DIR = path.join(".akan", "generated", "dict");
29
- var IMPLICIT_OVERRIDES_DIR = path.join(".akan", "generated", "overrides");
30
- var OVERRIDES_KEY_RE = /^\.\/(.+\/)?_overrides\.(tsx|ts|jsx|js)$/;
31
- async function writeGeneratedOverridesLayoutFile(opts) {
32
- const filename = `${opts.key.replace(/^\.\//, "").replace(/[^a-zA-Z0-9]+/g, "_")}.tsx`;
33
- const absPath = path.join(path.resolve(opts.appCwdPath), IMPLICIT_OVERRIDES_DIR, filename);
34
- const userRel = path.relative(path.dirname(absPath), opts.userAbsPath).split(path.sep).join("/");
35
- const userSpecifier = userRel.startsWith(".") ? userRel : `./${userRel}`;
36
- const source = `"use client";
37
- import { UiOverrideProvider } from "akanjs/ui";
38
- import { createElement, type ReactNode } from "react";
39
- import value from ${JSON.stringify(userSpecifier)};
40
-
41
- export default function AkanUiOverridesLayout({ children }: { children?: ReactNode }) {
42
- return createElement(UiOverrideProvider, { value }, children);
43
- }
44
- `;
45
- await Bun.write(absPath, source);
46
- return absPath;
47
- }
48
- function getRootBoundarySegments(key) {
49
- const match = LAYOUT_KEY_RE.exec(key);
50
- if (!match)
51
- return null;
52
- const prefix = match[1]?.replace(/\/$/, "");
53
- if (!prefix)
54
- return [];
55
- return prefix.split("/").filter(Boolean);
56
- }
57
- function implicitRootLayoutKey(segments) {
58
- return `./${[...segments, "__root_layout"].join("/")}.tsx`;
59
- }
60
- function implicitRootLayoutAbsPath(appCwdPath, segments) {
61
- const filename = segments.length ? `${segments.join("__")}__root_layout.tsx` : "__root_layout.tsx";
62
- return path.join(path.resolve(appCwdPath), IMPLICIT_LAYOUT_DIR, filename);
63
- }
64
- function implicitDictionaryMacroAbsPath(appCwdPath) {
65
- return path.join(path.resolve(appCwdPath), IMPLICIT_DICT_DIR, "useDict.ts");
66
- }
67
- function isRootBoundarySegments(segments, basePaths) {
68
- const firstVisibleIndex = segments.findIndex((segment) => !/^\(.+\)$/.test(segment));
69
- if (firstVisibleIndex === -1)
70
- return segments.length <= 1;
71
- if (segments.slice(firstVisibleIndex + 1).some((segment) => /^\(.+\)$/.test(segment)))
72
- return false;
73
- const visible = segments.slice(firstVisibleIndex);
74
- const allowedBasePaths = new Set([...basePaths].map((basePath) => basePath.trim()).filter(Boolean));
75
- return visible.length === 1 && (firstVisibleIndex > 0 || allowedBasePaths.has(visible[0] ?? ""));
76
- }
77
- function findRootBoundaries(pageKeys, appCwdPath, basePaths) {
78
- const boundaries = new Map;
79
- for (const key of pageKeys) {
80
- const segments = getRootBoundarySegments(key);
81
- if (!segments)
82
- continue;
83
- if (!isRootBoundarySegments(segments, basePaths))
84
- continue;
85
- const id = segments.join("/");
86
- boundaries.set(id, {
87
- sourceKey: key,
88
- sourceAbsPath: path.resolve(appCwdPath, "page", key.replace(/^\.\//, "")),
89
- segments
90
- });
91
- }
92
- const hasExplicitRootBoundary = [...boundaries.values()].some((boundary) => boundary.segments.length === 0);
93
- if (!hasExplicitRootBoundary && boundaries.size === 0) {
94
- boundaries.set("", { sourceKey: null, sourceAbsPath: null, segments: [] });
95
- }
96
- return [...boundaries.values()].sort((a, b) => a.segments.join("/").localeCompare(b.segments.join("/")));
97
- }
98
- function hasAncestorRootBoundary(boundary, boundaries) {
99
- return boundaries.some((candidate) => candidate !== boundary && candidate.segments.length < boundary.segments.length && candidate.segments.every((segment, index) => boundary.segments[index] === segment));
100
- }
101
- function findExplicitRootLayoutAbsPath(pageKeys, appCwdPath) {
102
- const rootLayoutKey = pageKeys.find((key) => {
103
- const segments = getRootBoundarySegments(key);
104
- return segments !== null && segments.length === 0;
105
- });
106
- return rootLayoutKey ? path.resolve(appCwdPath, "page", rootLayoutKey.replace(/^\.\//, "")) : null;
107
- }
108
- function routePrefixForSegments(segments) {
109
- const visible = segments.filter((segment) => !/^\(.+\)$/.test(segment));
110
- return visible[0] ?? null;
111
- }
112
- async function assertEnvClientConvention(appCwdPath, appName) {
113
- const envPath = path.join(appCwdPath, "env", "env.client.ts");
114
- if (!await Bun.file(envPath).exists()) {
115
- throw new Error(`[route-convention] app "${appName}" must provide env/env.client.ts exporting "env" for generated System.Provider`);
116
- }
117
- }
118
- async function writeGeneratedDictionaryMacroFile(appCwdPath, appName) {
119
- const absPath = implicitDictionaryMacroAbsPath(appCwdPath);
120
- await mkdir(path.dirname(absPath), { recursive: true });
121
- await Bun.write(absPath, `import { getAllDictionary } from "@apps/${appName}/lib/dict" with { type: "macro" };
122
-
123
- export const allDictionary = getAllDictionary();
124
- `);
125
- return absPath;
126
- }
127
- async function writeGeneratedRootLayoutFile(opts) {
128
- await assertEnvClientConvention(opts.appCwdPath, opts.appName);
129
- const dictMacroAbsPath = opts.includeSystemProvider ? await writeGeneratedDictionaryMacroFile(opts.appCwdPath, opts.appName) : null;
130
- const absPath = implicitRootLayoutAbsPath(opts.appCwdPath, opts.boundary.segments);
131
- await mkdir(path.dirname(absPath), { recursive: true });
132
- const dictMacroRel = dictMacroAbsPath ? path.relative(path.dirname(absPath), dictMacroAbsPath).split(path.sep).join("/") : null;
133
- const dictMacroSpecifier = dictMacroRel ? dictMacroRel.startsWith(".") ? dictMacroRel : `./${dictMacroRel}` : null;
134
- const sourceRel = opts.boundary.sourceAbsPath ? path.relative(path.dirname(absPath), opts.boundary.sourceAbsPath).split(path.sep).join("/") : null;
135
- const sourceSpecifier = sourceRel ? sourceRel.startsWith(".") ? sourceRel : `./${sourceRel}` : null;
136
- const inheritedSourceAbsPath = opts.rootSourceAbsPath && opts.rootSourceAbsPath !== opts.boundary.sourceAbsPath ? opts.rootSourceAbsPath : null;
137
- const inheritedSourceRel = inheritedSourceAbsPath ? path.relative(path.dirname(absPath), inheritedSourceAbsPath).split(path.sep).join("/") : null;
138
- const inheritedSourceSpecifier = inheritedSourceRel ? inheritedSourceRel.startsWith(".") ? inheritedSourceRel : `./${inheritedSourceRel}` : null;
139
- const clientImport = opts.includeStInit ? `import { st } from "@apps/${opts.appName}/client";
140
- void st;
141
- ` : `import "@apps/${opts.appName}/client";
142
- `;
143
- const inheritedImport = inheritedSourceSpecifier ? `import * as inheritedLayout from ${JSON.stringify(inheritedSourceSpecifier)};
144
- ` : `const inheritedLayout = {};
145
- `;
146
- const prefix = routePrefixForSegments(opts.boundary.segments);
147
- const userImport = sourceSpecifier ? `import UserLayout, * as userLayout from ${JSON.stringify(sourceSpecifier)};
148
- ` : `const UserLayout = ({ children }) => children;
149
- const userLayout = {};
150
- `;
151
- const source = opts.includeSystemProvider ? `import type { LayoutProps, PageProps } from "akanjs/client";
152
- import { loadFonts } from "akanjs/client";
153
- import { System } from "akanjs/ui";
154
- import { env } from "@apps/${opts.appName}/env/env.client";
155
- import { allDictionary } from ${JSON.stringify(dictMacroSpecifier)};
156
- ${clientImport}${inheritedImport}${userImport}
157
- const userFonts = userLayout.fonts ?? inheritedLayout.fonts ?? [];
158
- const defaultFonts = userFonts.filter((font) => font.default);
159
- if (defaultFonts.length > 1) throw new Error("[route-convention] only one default font is allowed per root layout");
160
- const defaultFont = defaultFonts[0];
161
- const defaultFontClassName = defaultFont ? (defaultFont.className ?? \`font-\${defaultFont.name}\`) : undefined;
162
-
163
- export async function generateHead(props: PageProps) {
164
- if (userLayout.generateHead) return userLayout.generateHead(props);
165
- if (userLayout.head !== undefined) return userLayout.head;
166
- if (inheritedLayout.generateHead) return inheritedLayout.generateHead(props);
167
- return inheritedLayout.head;
168
- }
169
-
170
- export async function generateMetadata(props: PageProps) {
171
- if (userLayout.generateMetadata) return userLayout.generateMetadata(props);
172
- if (userLayout.metadata !== undefined) return userLayout.metadata;
173
- if (inheritedLayout.generateMetadata) return inheritedLayout.generateMetadata(props);
174
- return inheritedLayout.metadata;
175
- }
176
-
177
- export const NotFound = userLayout.NotFound ?? inheritedLayout.NotFound;
178
- export const Error = userLayout.Error ?? inheritedLayout.Error;
179
- export const pageConfig = userLayout.pageConfig ?? inheritedLayout.pageConfig;
180
-
181
- export default function GeneratedLayout({ children, params, searchParams }: LayoutProps) {
182
- return (
183
- <System.Provider
184
- of={GeneratedLayout as never}
185
- appName=${JSON.stringify(opts.appName)}
186
- ${prefix ? `prefix=${JSON.stringify(prefix)}
187
- ` : ""}params={params}
188
- manifest={userLayout.manifest ?? inheritedLayout.manifest}
189
- env={env}
190
- theme={userLayout.theme ?? inheritedLayout.theme}
191
- fonts={loadFonts(userFonts)}
192
- className={defaultFontClassName}
193
- gaTrackingId={userLayout.gaTrackingId ?? inheritedLayout.gaTrackingId}
194
- layoutStyle={userLayout.layoutStyle ?? inheritedLayout.layoutStyle}
195
- reconnect={userLayout.reconnect ?? inheritedLayout.reconnect ?? false}
196
- wsConnect={userLayout.wsConnect ?? inheritedLayout.wsConnect ?? true}
197
- allDictionary={process.env.AKAN_PUBLIC_RENDER_ENV === "ssr" ? allDictionary : undefined}
198
- >
199
- <UserLayout params={params} searchParams={searchParams}>{children}</UserLayout>
200
- </System.Provider>
201
- );
202
- }
203
- ` : `import type { LayoutProps, PageProps } from "akanjs/client";
204
- ${inheritedImport}${userImport}
205
- export async function generateHead(props: PageProps) {
206
- if (userLayout.generateHead) return userLayout.generateHead(props);
207
- if (userLayout.head !== undefined) return userLayout.head;
208
- if (inheritedLayout.generateHead) return inheritedLayout.generateHead(props);
209
- return inheritedLayout.head;
210
- }
211
-
212
- export async function generateMetadata(props: PageProps) {
213
- if (userLayout.generateMetadata) return userLayout.generateMetadata(props);
214
- if (userLayout.metadata !== undefined) return userLayout.metadata;
215
- if (inheritedLayout.generateMetadata) return inheritedLayout.generateMetadata(props);
216
- return inheritedLayout.metadata;
217
- }
218
-
219
- export const NotFound = userLayout.NotFound ?? inheritedLayout.NotFound;
220
- export const Error = userLayout.Error ?? inheritedLayout.Error;
221
- export const pageConfig = userLayout.pageConfig ?? inheritedLayout.pageConfig;
222
-
223
- export default function GeneratedLayout({ children, params, searchParams }: LayoutProps) {
224
- return <UserLayout params={params} searchParams={searchParams}>{children}</UserLayout>;
225
- }
226
- `;
227
- await Bun.write(absPath, source);
228
- return absPath;
229
- }
230
- async function resolveSsrPageEntries(opts) {
231
- const absPageDir = path.resolve(opts.appCwdPath, "page");
232
- const hasSt = await appHasStModule(opts.appCwdPath);
233
- const basePaths = opts.basePaths ?? [];
234
- const rootSourceAbsPath = findExplicitRootLayoutAbsPath(opts.pageKeys, opts.appCwdPath);
235
- const rootBoundaries = findRootBoundaries(opts.pageKeys, opts.appCwdPath, basePaths);
236
- const rootLayoutKeys = new Set(opts.pageKeys.filter((key) => {
237
- const segments = getRootBoundarySegments(key);
238
- return segments !== null && isRootBoundarySegments(segments, basePaths);
239
- }));
240
- const base = await Promise.all(opts.pageKeys.filter((key) => !rootLayoutKeys.has(key)).map(async (key) => {
241
- const userAbsPath = path.resolve(absPageDir, key);
242
- if (OVERRIDES_KEY_RE.test(key)) {
243
- const moduleAbsPath = await writeGeneratedOverridesLayoutFile({
244
- appCwdPath: opts.appCwdPath,
245
- key,
246
- userAbsPath
247
- });
248
- return { key, moduleAbsPath, seedAbsPaths: [userAbsPath] };
249
- }
250
- return { key, moduleAbsPath: userAbsPath };
251
- }));
252
- const generated = await Promise.all(rootBoundaries.map(async (boundary) => ({
253
- key: implicitRootLayoutKey(boundary.segments),
254
- moduleAbsPath: await writeGeneratedRootLayoutFile({
255
- appCwdPath: opts.appCwdPath,
256
- appName: opts.appName,
257
- boundary,
258
- rootSourceAbsPath,
259
- includeStInit: hasSt && boundary.segments.length === 0,
260
- includeSystemProvider: !hasAncestorRootBoundary(boundary, rootBoundaries)
261
- }),
262
- seedAbsPaths: [...new Set([boundary.sourceAbsPath, rootSourceAbsPath].filter((absPath) => absPath !== null))]
263
- })));
264
- const entries = [...base, ...generated];
265
- entries.sort((a, b) => a.key.localeCompare(b.key));
266
- return entries;
267
- }
268
- async function resolveSsrPageEntriesForApp(app, pageKeys) {
269
- const config = await app.getConfig();
270
- return resolveSsrPageEntries({ appCwdPath: app.cwdPath, appName: app.name, pageKeys, basePaths: config.basePaths });
271
- }
21
+ import path3 from "path";
272
22
 
273
23
  // pkgs/@akanjs/devkit/artifact/routeSeedIndex.ts
274
- import path2 from "path";
24
+ import path from "path";
275
25
  import { assertUniqueRoutePatterns, compareRouteSpecificity, parseRouteModuleKey } from "akanjs/common";
276
26
  function computeRouteSeedIndex(pageEntries) {
277
27
  const layoutsByPrefix = new Map;
278
28
  const pagesBySegments = [];
279
29
  for (const { key, moduleAbsPath, seedAbsPaths } of pageEntries) {
280
30
  const parsed = parseRouteModuleKey(key);
281
- const files = [path2.resolve(moduleAbsPath), ...(seedAbsPaths ?? []).map((seed) => path2.resolve(seed))];
31
+ const files = [path.resolve(moduleAbsPath), ...(seedAbsPaths ?? []).map((seed) => path.resolve(seed))];
282
32
  if (parsed.kind === "layout" || parsed.kind === "overrides") {
283
33
  const prefix = parsed.routeSegments.join("/");
284
34
  const prev = layoutsByPrefix.get(prefix) ?? [];
@@ -315,7 +65,7 @@ function computeRouteSeedIndex(pageEntries) {
315
65
  }
316
66
  var ROUTE_SEED_INDEX_JSON = "route-seed-index.json";
317
67
  function serializeRouteSeedIndexForArtifact(index, artifactDir, options = {}) {
318
- const normalizedArtifactDir = path2.resolve(artifactDir);
68
+ const normalizedArtifactDir = path.resolve(artifactDir);
319
69
  if (options.production) {
320
70
  return {
321
71
  entries: index.entries.map((entry) => ({ routeId: entry.routeId }))
@@ -330,19 +80,19 @@ function serializeRouteSeedIndexForArtifact(index, artifactDir, options = {}) {
330
80
  };
331
81
  }
332
82
  async function saveRouteSeedIndex(artifactDir, index, options = {}) {
333
- const absPath = path2.join(path2.resolve(artifactDir), ROUTE_SEED_INDEX_JSON);
83
+ const absPath = path.join(path.resolve(artifactDir), ROUTE_SEED_INDEX_JSON);
334
84
  await Bun.write(absPath, `${JSON.stringify(serializeRouteSeedIndexForArtifact(index, artifactDir, options), null, 2)}
335
85
  `);
336
86
  return absPath;
337
87
  }
338
88
  function serializeArtifactPath(artifactPath, artifactDir) {
339
- if (!path2.isAbsolute(artifactPath))
89
+ if (!path.isAbsolute(artifactPath))
340
90
  return artifactPath;
341
- return path2.relative(artifactDir, artifactPath).split(path2.sep).join("/");
91
+ return path.relative(artifactDir, artifactPath).split(path.sep).join("/");
342
92
  }
343
93
 
344
94
  // pkgs/@akanjs/devkit/frontendBuild/routesManifestArtifactSerializer.ts
345
- import path3 from "path";
95
+ import path2 from "path";
346
96
 
347
97
  class RoutesManifestArtifactSerializer {
348
98
  #manifest;
@@ -350,7 +100,7 @@ class RoutesManifestArtifactSerializer {
350
100
  #production;
351
101
  constructor(manifest, artifactDir, options = {}) {
352
102
  this.#manifest = manifest;
353
- this.#artifactDir = path3.resolve(artifactDir);
103
+ this.#artifactDir = path2.resolve(artifactDir);
354
104
  this.#production = options.production ?? false;
355
105
  }
356
106
  static serialize(manifest, artifactDir, options = {}) {
@@ -383,9 +133,9 @@ class RoutesManifestArtifactSerializer {
383
133
  };
384
134
  }
385
135
  #serializeArtifactPath(artifactPath) {
386
- if (!path3.isAbsolute(artifactPath))
136
+ if (!path2.isAbsolute(artifactPath))
387
137
  return artifactPath;
388
- return path3.relative(this.#artifactDir, artifactPath).split(path3.sep).join("/");
138
+ return path2.relative(this.#artifactDir, artifactPath).split(path2.sep).join("/");
389
139
  }
390
140
  }
391
141
 
@@ -435,7 +185,7 @@ class AllRoutesBuilder {
435
185
  routeIds: this.#routeIds,
436
186
  ...this.#merged
437
187
  };
438
- const manifestPath = path4.join(path4.resolve(this.#artifactDir), "routes-manifest.json");
188
+ const manifestPath = path3.join(path3.resolve(this.#artifactDir), "routes-manifest.json");
439
189
  await Bun.write(manifestPath, `${JSON.stringify(RoutesManifestArtifactSerializer.serialize(manifest, this.#artifactDir, {
440
190
  production: this.#command === "build"
441
191
  }), null, 2)}
@@ -478,12 +228,12 @@ class AllRoutesBuilder {
478
228
  }
479
229
 
480
230
  // pkgs/@akanjs/devkit/frontendBuild/csrArtifactBuilder.ts
481
- import { mkdir as mkdir2, rm, unlink } from "fs/promises";
482
- import path6 from "path";
231
+ import { mkdir, rm, unlink } from "fs/promises";
232
+ import path5 from "path";
483
233
 
484
234
  // pkgs/@akanjs/devkit/frontendBuild/pagesEntrySourceGenerator.ts
485
235
  import fs from "fs";
486
- import path5 from "path";
236
+ import path4 from "path";
487
237
  import ts from "typescript";
488
238
 
489
239
  class PagesEntrySourceGenerator {
@@ -526,11 +276,11 @@ ${entries.join(`
526
276
  `;
527
277
  }
528
278
  static #toImportSpecifier(moduleAbsPath) {
529
- return path5.resolve(moduleAbsPath).split(path5.sep).join("/");
279
+ return path4.resolve(moduleAbsPath).split(path4.sep).join("/");
530
280
  }
531
281
  static #hasAsyncDefaultExport(moduleAbsPath) {
532
282
  try {
533
- const source = fs.readFileSync(path5.resolve(moduleAbsPath), "utf8");
283
+ const source = fs.readFileSync(path4.resolve(moduleAbsPath), "utf8");
534
284
  const sourceFile = ts.createSourceFile(moduleAbsPath, source, ts.ScriptTarget.Latest, true, PagesEntrySourceGenerator.#scriptKind(moduleAbsPath));
535
285
  return PagesEntrySourceGenerator.#sourceFileHasAsyncDefaultExport(sourceFile);
536
286
  } catch {
@@ -609,7 +359,7 @@ class CsrArtifactBuilder {
609
359
  const csrBasePaths = [...akanConfig.basePaths];
610
360
  const htmlEntries = csrBasePaths.length > 0 ? csrBasePaths : ["index"];
611
361
  await rm(this.#outputDir, { recursive: true, force: true });
612
- await mkdir2(path6.join(this.#app.cwdPath, ".akan/generated/csr"), { recursive: true });
362
+ await mkdir(path5.join(this.#app.cwdPath, ".akan/generated/csr"), { recursive: true });
613
363
  const generatedHtmlFiles = Object.fromEntries(htmlEntries.map((basePath) => this.#createHtmlFile(basePath)));
614
364
  const result = await Bun.build({
615
365
  target: "browser",
@@ -641,7 +391,7 @@ ${logs}` : ""}`);
641
391
  return { outputDir: this.#outputDir };
642
392
  }
643
393
  get #outputDir() {
644
- return path6.join(this.#command === "build" ? this.#app.dist.cwdPath : this.#app.cwdPath, this.#command === "build" ? "csr" : ".akan/artifact/csr");
394
+ return path5.join(this.#command === "build" ? this.#app.dist.cwdPath : this.#app.cwdPath, this.#command === "build" ? "csr" : ".akan/artifact/csr");
645
395
  }
646
396
  #define() {
647
397
  const nodeEnv = this.#command === "build" ? "production" : "development";
@@ -672,8 +422,8 @@ ${logs}` : ""}`);
672
422
  ];
673
423
  }
674
424
  async#loadCsrArtifact() {
675
- const artifactDir = path6.join(this.#command === "build" ? this.#app.dist.cwdPath : this.#app.cwdPath, ".akan/artifact");
676
- const artifactFile = Bun.file(path6.join(artifactDir, "base-artifact.json"));
425
+ const artifactDir = path5.join(this.#command === "build" ? this.#app.dist.cwdPath : this.#app.cwdPath, ".akan/artifact");
426
+ const artifactFile = Bun.file(path5.join(artifactDir, "base-artifact.json"));
677
427
  if (!await artifactFile.exists())
678
428
  return { cssAssets: {} };
679
429
  const artifact = await artifactFile.json();
@@ -686,7 +436,7 @@ ${logs}` : ""}`);
686
436
  const htmlFile = Bun.file(htmlPath);
687
437
  if (!await htmlFile.exists())
688
438
  continue;
689
- const basePath = path6.basename(htmlPath, ".html") === "index" ? "" : path6.basename(htmlPath, ".html");
439
+ const basePath = path5.basename(htmlPath, ".html") === "index" ? "" : path5.basename(htmlPath, ".html");
690
440
  const inlined = await this.#inlineHtmlAssets(await htmlFile.text(), htmlPath, cssAssets[basePath]);
691
441
  for (const filePath of inlined.jsFiles)
692
442
  jsFiles.add(filePath);
@@ -728,7 +478,7 @@ ${remainingAssets.join(`
728
478
  next = CsrArtifactBuilder.injectBeforeHeadEnd(next, style);
729
479
  }
730
480
  if (cssAsset) {
731
- const cssPath = path6.join(this.#command === "build" ? this.#app.dist.cwdPath : this.#app.cwdPath, ".akan/artifact", cssAsset.cssRelPath);
481
+ const cssPath = path5.join(this.#command === "build" ? this.#app.dist.cwdPath : this.#app.cwdPath, ".akan/artifact", cssAsset.cssRelPath);
732
482
  const css = await Bun.file(cssPath).text();
733
483
  const style = CsrArtifactBuilder.createInlineStyle(css);
734
484
  if (!next.includes(style))
@@ -799,12 +549,12 @@ ${CsrArtifactBuilder.escapeInlineScript(await loadScript(src))}
799
549
  throw new Error(`[csr-build] cannot inline external script: ${src}`);
800
550
  }
801
551
  const normalized = src.startsWith("/") ? src.slice(1) : src;
802
- return path6.resolve(path6.dirname(htmlPath), normalized);
552
+ return path5.resolve(path5.dirname(htmlPath), normalized);
803
553
  }
804
554
  }
805
555
 
806
556
  // pkgs/@akanjs/devkit/frontendBuild/cssCompiler.ts
807
- import path8 from "path";
557
+ import path7 from "path";
808
558
  import { Logger } from "akanjs/common";
809
559
  import { compile } from "tailwindcss";
810
560
 
@@ -875,7 +625,7 @@ class CssCandidateCache {
875
625
  }
876
626
 
877
627
  // pkgs/@akanjs/devkit/frontendBuild/cssImportResolver.ts
878
- import path7 from "path";
628
+ import path6 from "path";
879
629
  var CSS_IMPORT_EXTS = ["", ".css", "/styles.css", "/index.css"];
880
630
 
881
631
  class CssImportResolver {
@@ -928,7 +678,7 @@ class CssImportResolver {
928
678
  const exact = this.#paths[id];
929
679
  if (exact) {
930
680
  for (const repl of exact) {
931
- const resolved = await this.#firstExisting(path7.resolve(this.#workspaceRoot, repl));
681
+ const resolved = await this.#firstExisting(path6.resolve(this.#workspaceRoot, repl));
932
682
  if (resolved)
933
683
  return resolved;
934
684
  }
@@ -939,7 +689,7 @@ class CssImportResolver {
939
689
  const suffix = id.slice(prefix.length);
940
690
  for (const repl of replacements) {
941
691
  const replPath = repl.endsWith("/*") ? repl.slice(0, -1) : repl;
942
- const resolved = await this.#firstExisting(path7.resolve(this.#workspaceRoot, replPath + suffix));
692
+ const resolved = await this.#firstExisting(path6.resolve(this.#workspaceRoot, replPath + suffix));
943
693
  if (resolved)
944
694
  return resolved;
945
695
  }
@@ -969,25 +719,29 @@ class CssImportResolver {
969
719
  try {
970
720
  if (!await Bun.file(pkgPath).exists())
971
721
  return null;
972
- const pkgDir = path7.dirname(pkgPath);
722
+ const pkgDir = path6.dirname(pkgPath);
973
723
  const pkg = await Bun.file(pkgPath).json();
974
724
  const subpath = id === pkgName ? "." : `.${id.slice(pkgName.length)}`;
975
725
  const exportValue = pkg.exports?.[subpath];
976
- const styleEntry = (typeof exportValue === "string" ? exportValue : exportValue?.style || exportValue?.import || exportValue?.default) || pkg.exports?.["."]?.style || pkg.style || "index.css";
977
- return await this.#firstExisting(path7.resolve(pkgDir, styleEntry));
726
+ const exportedEntry = typeof exportValue === "string" ? exportValue : exportValue?.style || exportValue?.import || exportValue?.default;
727
+ if (exportedEntry)
728
+ return await this.#firstExisting(path6.resolve(pkgDir, exportedEntry));
729
+ if (subpath !== ".")
730
+ return await this.#firstExisting(path6.resolve(pkgDir, subpath));
731
+ return await this.#firstExisting(path6.resolve(pkgDir, pkg.exports?.["."]?.style || pkg.style || "index.css"));
978
732
  } catch {
979
733
  return null;
980
734
  }
981
735
  }
982
736
  #resolutionBases(fromBase) {
983
- return [fromBase, this.#workspaceRoot, path7.dirname(Bun.main), path7.resolve(path7.dirname(Bun.main), "../..")];
737
+ return [fromBase, this.#workspaceRoot, path6.dirname(Bun.main), path6.resolve(path6.dirname(Bun.main), "../..")];
984
738
  }
985
739
  #packageJsonCandidates(pkgName) {
986
740
  return [
987
- path7.join(this.#workspaceRoot, "pkgs", pkgName, "package.json"),
988
- path7.join(this.#workspaceRoot, "node_modules", pkgName, "package.json"),
989
- path7.join(path7.dirname(Bun.main), "node_modules", pkgName, "package.json"),
990
- path7.join(path7.dirname(Bun.main), "../../", pkgName, "package.json")
741
+ path6.join(this.#workspaceRoot, "pkgs", pkgName, "package.json"),
742
+ path6.join(this.#workspaceRoot, "node_modules", pkgName, "package.json"),
743
+ path6.join(path6.dirname(Bun.main), "node_modules", pkgName, "package.json"),
744
+ path6.join(path6.dirname(Bun.main), "../../", pkgName, "package.json")
991
745
  ];
992
746
  }
993
747
  async#firstExisting(basePath) {
@@ -1005,7 +759,7 @@ class CssImportResolver {
1005
759
  return parts[0] ?? null;
1006
760
  }
1007
761
  static isCssFile(filePath) {
1008
- return path7.extname(filePath) === ".css";
762
+ return path6.extname(filePath) === ".css";
1009
763
  }
1010
764
  }
1011
765
 
@@ -1024,13 +778,15 @@ class CssCompiler {
1024
778
  this.#app = app;
1025
779
  }
1026
780
  get #candidateCachePath() {
1027
- return path8.join(this.#app.cwdPath, ".akan/cache/cssCandidates.json");
781
+ return path7.join(this.#app.cwdPath, ".akan/cache/cssCandidates.json");
1028
782
  }
1029
783
  #cssText = null;
1030
784
  #cssTextByBasePath = null;
1031
785
  #fileExistsCache = new Map;
1032
786
  #resolvedFileCache = new Map;
1033
787
  #resolvedSpecifierCache = new Map;
788
+ #discoveredCssPaths = new Set;
789
+ importedStylesheetsByBasePath = {};
1034
790
  #fileExists(absPath) {
1035
791
  let cached = this.#fileExistsCache.get(absPath);
1036
792
  if (!cached) {
@@ -1052,7 +808,7 @@ class CssCompiler {
1052
808
  return filePath;
1053
809
  }
1054
810
  for (const ext of SOURCE_EXTS) {
1055
- const filePath = path8.join(absPathNoExt, `index${ext}`);
811
+ const filePath = path7.join(absPathNoExt, `index${ext}`);
1056
812
  if (await this.#fileExists(filePath))
1057
813
  return filePath;
1058
814
  }
@@ -1064,13 +820,20 @@ class CssCompiler {
1064
820
  async getCss({ refresh } = {}) {
1065
821
  if (this.#cssText !== null && !refresh)
1066
822
  return this.#cssText;
823
+ this.#discoveredCssPaths.clear();
824
+ this.importedStylesheetsByBasePath = {};
1067
825
  const { cssPaths, sourcePaths } = await this.discoverCssAndSources({ refresh });
1068
- this.#cssText = await this.compileCss(cssPaths, sourcePaths);
826
+ const { css, imported } = await this.#compileWithImports(cssPaths, sourcePaths);
827
+ this.#cssText = css;
828
+ this.importedStylesheetsByBasePath = { "": imported };
829
+ await this.#warnUnreachableStylesheets();
1069
830
  return this.#cssText;
1070
831
  }
1071
832
  async getCssByBasePath({ refresh } = {}) {
1072
833
  if (this.#cssTextByBasePath !== null && !refresh)
1073
834
  return this.#cssTextByBasePath;
835
+ this.#discoveredCssPaths.clear();
836
+ this.importedStylesheetsByBasePath = {};
1074
837
  const akanConfig = await this.#app.getConfig({ refresh });
1075
838
  const pageKeys = await this.#app.getPageKeys({ refresh });
1076
839
  const basePaths = [...akanConfig.basePaths];
@@ -1081,7 +844,8 @@ class CssCompiler {
1081
844
  return ["", ""];
1082
845
  const started = Date.now();
1083
846
  const { cssPaths, sourcePaths } = await this.discoverCssAndSources({ refresh, pageKeys: rootPageKeys });
1084
- const css = await this.compileCss(cssPaths, sourcePaths);
847
+ const { css, imported } = await this.#compileWithImports(cssPaths, sourcePaths);
848
+ this.importedStylesheetsByBasePath[""] = imported;
1085
849
  this.#logger.verbose(`css base=root paths=${cssPaths.length} sources=${sourcePaths.length} in ${Date.now() - started}ms`);
1086
850
  return ["", css];
1087
851
  })(),
@@ -1091,14 +855,27 @@ class CssCompiler {
1091
855
  return [basePath, ""];
1092
856
  const started = Date.now();
1093
857
  const { cssPaths, sourcePaths } = await this.discoverCssAndSources({ refresh, pageKeys: basePathPageKeys });
1094
- const css = await this.compileCss(cssPaths, sourcePaths);
858
+ const { css, imported } = await this.#compileWithImports(cssPaths, sourcePaths);
859
+ this.importedStylesheetsByBasePath[basePath] = imported;
1095
860
  this.#logger.verbose(`css base=${basePath} paths=${cssPaths.length} sources=${sourcePaths.length} in ${Date.now() - started}ms`);
1096
861
  return [basePath, css];
1097
862
  })
1098
863
  ]);
1099
864
  this.#cssTextByBasePath = Object.fromEntries(cssEntries);
865
+ await this.#warnUnreachableStylesheets();
1100
866
  return this.#cssTextByBasePath;
1101
867
  }
868
+ async#warnUnreachableStylesheets() {
869
+ const pageDir = path7.join(this.#app.cwdPath, "page");
870
+ const glob = new Bun.Glob("**/*.css");
871
+ for await (const cssPath of glob.scan({ cwd: pageDir, absolute: true })) {
872
+ if (cssPath.includes(`${path7.sep}(libs)${path7.sep}`))
873
+ continue;
874
+ if (this.#discoveredCssPaths.has(cssPath))
875
+ continue;
876
+ this.#logger.warn(`css ${path7.relative(this.#app.cwdPath, cssPath)} is imported by no route and never compiled`);
877
+ }
878
+ }
1102
879
  async discoverCss({ refresh } = {}) {
1103
880
  const { cssPaths } = await this.discoverCssAndSources({ refresh });
1104
881
  return cssPaths;
@@ -1108,7 +885,7 @@ class CssCompiler {
1108
885
  pageKeys
1109
886
  } = {}) {
1110
887
  pageKeys ??= await this.#app.getPageKeys({ refresh });
1111
- const seeds = pageKeys.map((key) => path8.resolve(this.#app.cwdPath, "page", key));
888
+ const seeds = pageKeys.map((key) => path7.resolve(this.#app.cwdPath, "page", key));
1112
889
  const cssFiles = new Set;
1113
890
  const sourceFiles = new Set;
1114
891
  const queue = [...seeds];
@@ -1140,7 +917,7 @@ class CssCompiler {
1140
917
  } catch {
1141
918
  continue;
1142
919
  }
1143
- const importerDir = path8.dirname(filePath);
920
+ const importerDir = path7.dirname(filePath);
1144
921
  for (const imp of imports) {
1145
922
  const spec = imp.path;
1146
923
  if (!spec)
@@ -1158,21 +935,47 @@ class CssCompiler {
1158
935
  queue.push(resolved);
1159
936
  }
1160
937
  }
1161
- return { cssPaths: [...cssFiles], sourcePaths: [...sourceFiles] };
938
+ const tokenPaths = await this.#libTokenStylesheets(sourceFiles);
939
+ const cssPaths = [...new Set([...tokenPaths, ...cssFiles])];
940
+ for (const cssPath of cssPaths)
941
+ this.#discoveredCssPaths.add(cssPath);
942
+ return { cssPaths, sourcePaths: [...sourceFiles] };
943
+ }
944
+ async#libTokenStylesheets(sourceFiles) {
945
+ const libsRoot = path7.join(this.#app.workspace.workspaceRoot, "libs");
946
+ const libNames = new Set;
947
+ for (const filePath of sourceFiles) {
948
+ const relPath = path7.relative(libsRoot, filePath);
949
+ if (relPath.startsWith("..") || path7.isAbsolute(relPath))
950
+ continue;
951
+ const [libName] = relPath.split(path7.sep);
952
+ if (libName)
953
+ libNames.add(libName);
954
+ }
955
+ const tokenPaths = await Promise.all([...libNames].sort().map(async (libName) => {
956
+ const tokensPath = path7.join(libsRoot, libName, "ui/tokens.css");
957
+ return await this.#fileExists(tokensPath) ? tokensPath : null;
958
+ }));
959
+ return tokenPaths.filter((tokensPath) => !!tokensPath);
1162
960
  }
1163
961
  async compileCss(cssPaths, sourcePaths) {
962
+ const { css } = await this.#compileWithImports(cssPaths, sourcePaths);
963
+ return css;
964
+ }
965
+ async#compileWithImports(cssPaths, sourcePaths) {
1164
966
  if (cssPaths.length === 0)
1165
- return "";
967
+ return { css: "", imported: [] };
1166
968
  const compileStarted = Date.now();
1167
969
  const compilers = await Promise.all(cssPaths.map(async (cssPath) => {
1168
970
  const css = await Bun.file(cssPath).text();
1169
- const base = path8.dirname(cssPath);
971
+ const base = path7.dirname(cssPath);
972
+ const imported2 = new Map;
1170
973
  const compiler = await compile(css, {
1171
974
  base,
1172
- loadStylesheet: (id, fromBase) => this.#loadStylesheet(id, fromBase),
975
+ loadStylesheet: (id, fromBase) => this.#loadStylesheet(id, fromBase, imported2),
1173
976
  loadModule: (id, fromBase) => this.#loadModule(id, fromBase)
1174
977
  });
1175
- return { cssPath, compiler };
978
+ return { cssPath, compiler, imported: imported2 };
1176
979
  }));
1177
980
  const sourceDirs = new Set;
1178
981
  for (const entry of compilers) {
@@ -1185,23 +988,39 @@ class CssCompiler {
1185
988
  const candidates = await this.#scanCandidates(sourcePaths, [...sourceDirs]);
1186
989
  this.#logger.verbose(`css candidates scanned count=${candidates.length} sources=${sourcePaths.length} dirs=${sourceDirs.size} in ${Date.now() - scanStarted}ms`);
1187
990
  const parts = [];
991
+ const imported = [];
1188
992
  for (const entry of compilers) {
1189
993
  if (!entry)
1190
994
  continue;
1191
- parts.push(entry.compiler.build(candidates));
995
+ const part = entry.compiler.build(candidates);
996
+ parts.push(part);
997
+ for (const [cssPath, content] of entry.imported) {
998
+ const declaredNames = declaredCustomProperties(content);
999
+ imported.push({ cssPath, declaredNames });
1000
+ if (declaredNames.length === 0 || declaredNames.some((name) => part.includes(`${name}:`)))
1001
+ continue;
1002
+ this.#logger.warn(`css @import ${cssPath} was loaded by ${entry.cssPath} but none of its ${declaredNames.length} declaration(s) reached the compiled CSS`);
1003
+ }
1192
1004
  }
1193
1005
  this.#logger.verbose(`css compiled paths=${cssPaths.length} candidates=${candidates.length} in ${Date.now() - compileStarted}ms`);
1194
- return parts.join(`
1195
- `);
1006
+ return { css: parts.join(`
1007
+ `), imported };
1196
1008
  }
1197
- async#loadStylesheet(id, fromBase) {
1009
+ async#loadStylesheet(id, fromBase, imported) {
1198
1010
  const p = await this.#resolveCssImport(id, fromBase);
1011
+ this.#discoveredCssPaths.add(p);
1199
1012
  const content = await Bun.file(p).text();
1200
- return { path: p, base: path8.dirname(p), content };
1013
+ imported?.set(p, content);
1014
+ this.#logger.verbose(`css import "${id}" from ${fromBase} -> ${p} (${content.length} bytes)`);
1015
+ return { path: p, base: path7.dirname(p), content };
1201
1016
  }
1202
1017
  async#resolveCssImport(id, fromBase) {
1203
- if (id.startsWith(".") || id.startsWith("/"))
1204
- return path8.resolve(fromBase, id);
1018
+ if (id.startsWith(".") || id.startsWith("/")) {
1019
+ const filePath = path7.resolve(fromBase, id);
1020
+ if (await this.#fileExists(filePath))
1021
+ return filePath;
1022
+ throw new Error(`[css] failed to resolve stylesheet import "${id}" from ${fromBase} (no file at ${filePath})`);
1023
+ }
1205
1024
  const resolver = await this.#getCssImportResolver();
1206
1025
  const resolved = await resolver.resolve(id, fromBase);
1207
1026
  if (resolved)
@@ -1217,7 +1036,7 @@ class CssCompiler {
1217
1036
  async#loadModule(id, fromBase) {
1218
1037
  const p = __require.resolve(id, { paths: [fromBase] });
1219
1038
  const mod = await import(p);
1220
- return { path: p, base: path8.dirname(p), module: mod.default ?? mod };
1039
+ return { path: p, base: path7.dirname(p), module: mod.default ?? mod };
1221
1040
  }
1222
1041
  #resolveSourceImport(id, fromBase, resolvePackage) {
1223
1042
  const cacheKey = `${fromBase}\x00${id}`;
@@ -1230,7 +1049,7 @@ class CssCompiler {
1230
1049
  }
1231
1050
  async#resolveSourceImportUncached(id, fromBase, resolvePackage) {
1232
1051
  if (id.startsWith(".") || id.startsWith("/")) {
1233
- const abs = id.startsWith("/") ? id : path8.resolve(fromBase, id);
1052
+ const abs = id.startsWith("/") ? id : path7.resolve(fromBase, id);
1234
1053
  return this.#resolveSourceFileCandidate(abs);
1235
1054
  }
1236
1055
  const pkg = await resolvePackage(id);
@@ -1281,21 +1100,25 @@ function resolveSourceWithRequire(id, fromBase) {
1281
1100
  }
1282
1101
  }
1283
1102
  function isSourceFile(filePath) {
1284
- return SOURCE_EXTS.includes(path8.extname(filePath));
1103
+ return SOURCE_EXTS.includes(path7.extname(filePath));
1285
1104
  }
1286
1105
  function isIgnoredNodeModuleSource(filePath) {
1287
1106
  return NODE_MODULES_RE.test(filePath) && !AKANJS_NODE_MODULE_RE.test(filePath);
1288
1107
  }
1108
+ function declaredCustomProperties(css) {
1109
+ const withoutThemeBlocks = css.replace(/@theme[^{]*\{[^}]*\}/g, "");
1110
+ return [...new Set([...withoutThemeBlocks.matchAll(/(?:^|[\s;{])(--[\w-]+)\s*:/g)].map(([, name]) => name))].filter((name) => !!name);
1111
+ }
1289
1112
  function getPageKeyBasePath(pageKey, basePaths) {
1290
- const normalized = pageKey.split(path8.sep).join("/").replace(/^\.\//, "");
1113
+ const normalized = pageKey.split(path7.sep).join("/").replace(/^\.\//, "");
1291
1114
  const segments = normalized.split("/");
1292
1115
  const firstPublicSegment = segments.find((segment) => segment !== "[lang]" && !/^\(.+\)$/.test(segment));
1293
1116
  return firstPublicSegment && basePaths.includes(firstPublicSegment) ? firstPublicSegment : null;
1294
1117
  }
1295
1118
 
1296
1119
  // pkgs/@akanjs/devkit/frontendBuild/fontOptimizer.ts
1297
- import { mkdir as mkdir3, stat as stat2 } from "fs/promises";
1298
- import path9 from "path";
1120
+ import { mkdir as mkdir2, stat as stat2 } from "fs/promises";
1121
+ import path8 from "path";
1299
1122
  import ts2 from "typescript";
1300
1123
  var FONT_URL_PREFIX = "/_akan/fonts";
1301
1124
  var DEFAULT_FONT_SUBSETS = ["latin"];
@@ -1312,7 +1135,7 @@ class FontOptimizer {
1312
1135
  constructor(app, command = "start") {
1313
1136
  this.#app = app;
1314
1137
  this.#command = command;
1315
- this.#artifactRoot = path9.join(command === "build" ? app.dist.cwdPath : app.cwdPath, ".akan/artifact");
1138
+ this.#artifactRoot = path8.join(command === "build" ? app.dist.cwdPath : app.cwdPath, ".akan/artifact");
1316
1139
  }
1317
1140
  async optimize() {
1318
1141
  const fonts = await this.discoverFonts();
@@ -1337,7 +1160,7 @@ class FontOptimizer {
1337
1160
  return result;
1338
1161
  }
1339
1162
  get #cachePath() {
1340
- return path9.join(this.#artifactRoot, "fontCache.json");
1163
+ return path8.join(this.#artifactRoot, "fontCache.json");
1341
1164
  }
1342
1165
  async#buildCacheKey(fonts) {
1343
1166
  const sources = [];
@@ -1352,7 +1175,7 @@ class FontOptimizer {
1352
1175
  sources.push({ optimizedSrc: face.optimizedSrc, ...stamp });
1353
1176
  }
1354
1177
  for (const filePath of font.subsetFiles ?? []) {
1355
- const abs = path9.isAbsolute(filePath) ? filePath : path9.join(this.#app.cwdPath, filePath);
1178
+ const abs = path8.isAbsolute(filePath) ? filePath : path8.join(this.#app.cwdPath, filePath);
1356
1179
  const stamp = await this.#fileStamp(abs);
1357
1180
  if (!stamp)
1358
1181
  return null;
@@ -1375,7 +1198,7 @@ class FontOptimizer {
1375
1198
  const cache = await Bun.file(this.#cachePath).json().catch(() => null);
1376
1199
  if (cache?.version !== FontOptimizer.#cacheVersion || cache.key !== key)
1377
1200
  return null;
1378
- const files = cache.files.map((relativePath) => path9.join(this.#artifactRoot, relativePath));
1201
+ const files = cache.files.map((relativePath) => path8.join(this.#artifactRoot, relativePath));
1379
1202
  for (const filePath of files) {
1380
1203
  if (!await Bun.file(filePath).exists())
1381
1204
  return null;
@@ -1387,7 +1210,7 @@ class FontOptimizer {
1387
1210
  version: FontOptimizer.#cacheVersion,
1388
1211
  key,
1389
1212
  css: result.css,
1390
- files: result.files.map((filePath) => path9.relative(this.#artifactRoot, filePath))
1213
+ files: result.files.map((filePath) => path8.relative(this.#artifactRoot, filePath))
1391
1214
  };
1392
1215
  await Bun.write(this.#cachePath, JSON.stringify(cache));
1393
1216
  }
@@ -1395,7 +1218,7 @@ class FontOptimizer {
1395
1218
  const pageKeys = await this.#app.getPageKeys();
1396
1219
  const fonts = [];
1397
1220
  await Promise.all(pageKeys.map(async (key) => {
1398
- const filePath = path9.resolve(this.#app.cwdPath, "page", key);
1221
+ const filePath = path8.resolve(this.#app.cwdPath, "page", key);
1399
1222
  const file = Bun.file(filePath);
1400
1223
  if (!await file.exists())
1401
1224
  return;
@@ -1414,8 +1237,8 @@ class FontOptimizer {
1414
1237
  this.#app.logger.warn(`[font] source not found: ${face.src}`);
1415
1238
  continue;
1416
1239
  }
1417
- const outputPath = path9.join(this.#artifactRoot, face.optimizedSrc.replace(/^\/_akan\//, ""));
1418
- await mkdir3(path9.dirname(outputPath), { recursive: true });
1240
+ const outputPath = path8.join(this.#artifactRoot, face.optimizedSrc.replace(/^\/_akan\//, ""));
1241
+ await mkdir2(path8.dirname(outputPath), { recursive: true });
1419
1242
  const sourceBuffer = Buffer.from(await Bun.file(sourcePath).arrayBuffer());
1420
1243
  const outputBuffer = await this.#buildFontBuffer(font, sourceBuffer, sourcePath);
1421
1244
  await Bun.write(outputPath, outputBuffer);
@@ -1562,8 +1385,8 @@ class FontOptimizer {
1562
1385
  return null;
1563
1386
  const rel = src.replace(/^\//, "");
1564
1387
  const candidates = [
1565
- this.#command === "build" ? path9.join(this.#app.dist.cwdPath, "public", rel) : null,
1566
- path9.join(this.#app.cwdPath, "public", rel),
1388
+ this.#command === "build" ? path8.join(this.#app.dist.cwdPath, "public", rel) : null,
1389
+ path8.join(this.#app.cwdPath, "public", rel),
1567
1390
  this.#resolveWorkspacePublicPath(rel)
1568
1391
  ].filter(Boolean);
1569
1392
  for (const candidate of candidates) {
@@ -1598,7 +1421,7 @@ class FontOptimizer {
1598
1421
  return "woff2";
1599
1422
  if (signature === "OTTO")
1600
1423
  return "otf";
1601
- const ext = path9.extname(sourcePath).slice(1).toLowerCase();
1424
+ const ext = path8.extname(sourcePath).slice(1).toLowerCase();
1602
1425
  if (ext === "otf" || ext === "woff" || ext === "woff2")
1603
1426
  return ext;
1604
1427
  return "ttf";
@@ -1607,7 +1430,7 @@ class FontOptimizer {
1607
1430
  const [root, dep, ...rest] = rel.split("/");
1608
1431
  if (root !== "libs" || !dep || rest.length === 0)
1609
1432
  return null;
1610
- return path9.join(this.#app.workspace.workspaceRoot, "libs", dep, "public", ...rest);
1433
+ return path8.join(this.#app.workspace.workspaceRoot, "libs", dep, "public", ...rest);
1611
1434
  }
1612
1435
  async#getSubsetText(font) {
1613
1436
  const parts = new Set;
@@ -1617,7 +1440,7 @@ class FontOptimizer {
1617
1440
  if (font.subsetText)
1618
1441
  parts.add(font.subsetText);
1619
1442
  for (const filePath of font.subsetFiles ?? []) {
1620
- const abs = path9.isAbsolute(filePath) ? filePath : path9.join(this.#app.cwdPath, filePath);
1443
+ const abs = path8.isAbsolute(filePath) ? filePath : path8.join(this.#app.cwdPath, filePath);
1621
1444
  const file = Bun.file(abs);
1622
1445
  if (await file.exists())
1623
1446
  parts.add(await file.text());
@@ -1637,7 +1460,7 @@ class FontOptimizer {
1637
1460
  }
1638
1461
  async#collectAutoSubsetText() {
1639
1462
  const libPageRoots = (await this.#app.getPageRoots()).filter((root) => root.keyPrefix).map((root) => root.dir);
1640
- const roots = [...["page", "ui"].map((dir) => path9.join(this.#app.cwdPath, dir)), ...libPageRoots];
1463
+ const roots = [...["page", "ui"].map((dir) => path8.join(this.#app.cwdPath, dir)), ...libPageRoots];
1641
1464
  const glob = new Bun.Glob("**/*.{ts,tsx,js,jsx,html,md}");
1642
1465
  const parts = [];
1643
1466
  await Promise.all(roots.map(async (root) => {
@@ -1752,6 +1575,170 @@ ${declarations.map(([prop, value]) => ` ${prop}: ${value};`).join(`
1752
1575
  }
1753
1576
  }
1754
1577
 
1578
+ // pkgs/@akanjs/devkit/frontendBuild/fontPruner.ts
1579
+ import { rm as rm2, rmdir, stat as stat3 } from "fs/promises";
1580
+ import path9 from "path";
1581
+ import { isFontOptimizationEnabled } from "akanjs/client";
1582
+ var FONT_GLOB = "**/*.{woff2,woff,ttf,otf,ttc,eot}";
1583
+ var REFERRER_GLOB = "**/*.{css,js,mjs,cjs,json,html,htm,svg,xml,webmanifest}";
1584
+
1585
+ class FontPruner {
1586
+ #app;
1587
+ #keepGlobs;
1588
+ #publicRoot;
1589
+ #artifactRoot;
1590
+ constructor(app, { keepFonts = [] } = {}) {
1591
+ this.#app = app;
1592
+ this.#keepGlobs = keepFonts;
1593
+ this.#publicRoot = path9.join(app.dist.cwdPath, "public");
1594
+ this.#artifactRoot = path9.join(app.dist.cwdPath, ".akan/artifact");
1595
+ }
1596
+ async prune() {
1597
+ const empty = { removed: [], kept: [], freedBytes: 0 };
1598
+ if (!await this.#isDirectory(this.#publicRoot))
1599
+ return empty;
1600
+ const candidates = await this.#collectCandidates();
1601
+ if (!candidates.length)
1602
+ return empty;
1603
+ const fonts = await new FontOptimizer(this.#app, "build").discoverFonts();
1604
+ const unoptimizedSrcs = new Set;
1605
+ const optimizedSrcs = new Set;
1606
+ for (const font of fonts) {
1607
+ const target = isFontOptimizationEnabled(font) ? optimizedSrcs : unoptimizedSrcs;
1608
+ for (const fontPath of font.paths)
1609
+ target.add(path9.posix.basename(fontPath.src));
1610
+ }
1611
+ const result = { removed: [], kept: [], freedBytes: 0 };
1612
+ const undecided = [];
1613
+ for (const candidate of candidates) {
1614
+ const reason = this.#staticKeepReason(candidate, unoptimizedSrcs);
1615
+ if (reason)
1616
+ result.kept.push({ file: candidate.rel, bytes: candidate.bytes, reason });
1617
+ else
1618
+ undecided.push(candidate);
1619
+ }
1620
+ if (undecided.length) {
1621
+ const referrers = await this.#findReferrers(undecided, optimizedSrcs);
1622
+ for (const candidate of undecided) {
1623
+ const referrer = referrers.get(candidate.rel);
1624
+ if (referrer)
1625
+ result.kept.push({ file: candidate.rel, bytes: candidate.bytes, reason: { kind: "referenced", referrer } });
1626
+ else
1627
+ result.removed.push({ file: candidate.rel, bytes: candidate.bytes });
1628
+ }
1629
+ }
1630
+ await this.#remove(result.removed.map(({ file }) => file));
1631
+ result.freedBytes = result.removed.reduce((total, { bytes }) => total + bytes, 0);
1632
+ this.#report(result);
1633
+ return result;
1634
+ }
1635
+ #staticKeepReason(candidate, unoptimizedSrcs) {
1636
+ if (unoptimizedSrcs.has(candidate.basename))
1637
+ return { kind: "unoptimized" };
1638
+ for (const glob of this.#keepGlobs) {
1639
+ if (new Bun.Glob(glob).match(candidate.rel))
1640
+ return { kind: "declared", glob };
1641
+ }
1642
+ return null;
1643
+ }
1644
+ async#collectCandidates() {
1645
+ const candidates = [];
1646
+ for await (const rel of new Bun.Glob(FONT_GLOB).scan({ cwd: this.#publicRoot, dot: false })) {
1647
+ const entry = await stat3(path9.join(this.#publicRoot, rel)).catch(() => null);
1648
+ if (!entry?.isFile())
1649
+ continue;
1650
+ candidates.push({ rel: rel.split(path9.sep).join("/"), basename: path9.basename(rel), bytes: entry.size });
1651
+ }
1652
+ return candidates;
1653
+ }
1654
+ async#findReferrers(candidates, optimizedSrcs) {
1655
+ const referrers = new Map;
1656
+ const authoritative = candidates;
1657
+ const bundled = candidates.filter((candidate) => !optimizedSrcs.has(candidate.basename));
1658
+ const roots = [
1659
+ { dir: this.#publicRoot, label: "public", needles: authoritative },
1660
+ { dir: path9.join(this.#artifactRoot, "styles"), label: ".akan/artifact/styles", needles: authoritative },
1661
+ { dir: path9.join(this.#artifactRoot, "client"), label: ".akan/artifact/client", needles: bundled },
1662
+ { dir: path9.join(this.#artifactRoot, "client-ssr"), label: ".akan/artifact/client-ssr", needles: bundled },
1663
+ { dir: path9.join(this.#artifactRoot, "server"), label: ".akan/artifact/server", needles: bundled },
1664
+ { dir: path9.join(this.#app.dist.cwdPath, "csr"), label: "csr", needles: bundled }
1665
+ ];
1666
+ for (const root of roots) {
1667
+ const pending = root.needles.filter((candidate) => !referrers.has(candidate.rel));
1668
+ if (!pending.length || !await this.#isDirectory(root.dir))
1669
+ continue;
1670
+ await this.#scanRoot(root, pending, referrers);
1671
+ }
1672
+ return referrers;
1673
+ }
1674
+ async#scanRoot(root, pending, referrers) {
1675
+ const needles = pending.map((candidate) => ({
1676
+ rel: candidate.rel,
1677
+ forms: [...new Set([candidate.basename, encodeURIComponent(candidate.basename)])]
1678
+ }));
1679
+ for await (const rel of new Bun.Glob(REFERRER_GLOB).scan({ cwd: root.dir, dot: false })) {
1680
+ const remaining = needles.filter((needle) => !referrers.has(needle.rel));
1681
+ if (!remaining.length)
1682
+ return;
1683
+ const text = await Bun.file(path9.join(root.dir, rel)).text().catch(() => "");
1684
+ if (!text)
1685
+ continue;
1686
+ for (const needle of remaining) {
1687
+ if (needle.forms.some((form) => text.includes(form)))
1688
+ referrers.set(needle.rel, `${root.label}/${rel.split(path9.sep).join("/")}`);
1689
+ }
1690
+ }
1691
+ }
1692
+ async#remove(files) {
1693
+ await Promise.all(files.map((file) => rm2(path9.join(this.#publicRoot, file), { force: true })));
1694
+ const dirs = [...new Set(files.map((file) => path9.posix.dirname(file)))].filter((dir) => dir !== "." && dir !== "/").sort((a, b) => b.length - a.length);
1695
+ for (const dir of dirs)
1696
+ await rmdir(path9.join(this.#publicRoot, dir)).catch(() => {
1697
+ return;
1698
+ });
1699
+ }
1700
+ #report(result) {
1701
+ if (!result.removed.length && !result.kept.length)
1702
+ return;
1703
+ for (const { file, bytes } of result.removed)
1704
+ this.#app.verbose(`[font-prune] dropped public/${file} (${FontPruner.formatBytes(bytes)})`);
1705
+ for (const { file, reason } of result.kept)
1706
+ this.#app.verbose(`[font-prune] kept public/${file} \u2014 ${FontPruner.describe(reason)}`);
1707
+ const byReferrer = new Map;
1708
+ for (const { bytes, reason } of result.kept) {
1709
+ if (reason.kind !== "referenced")
1710
+ continue;
1711
+ const entry = byReferrer.get(reason.referrer) ?? { files: 0, bytes: 0 };
1712
+ entry.files += 1;
1713
+ entry.bytes += bytes;
1714
+ byReferrer.set(reason.referrer, entry);
1715
+ }
1716
+ if (!byReferrer.size)
1717
+ return;
1718
+ const ranked = [...byReferrer.entries()].sort(([, a], [, b]) => b.bytes - a.bytes);
1719
+ const named = ranked.slice(0, 3).map(([referrer, { files, bytes }]) => `${referrer} (${files}, ${FontPruner.formatBytes(bytes)})`);
1720
+ const rest = ranked.length - named.length;
1721
+ this.#app.logger.info(`[font-prune] kept ${FontPruner.formatBytes(ranked.reduce((total, [, entry]) => total + entry.bytes, 0))} of fonts in public/ because these reference them: ${named.join(", ")}${rest > 0 ? ` and ${rest} more` : ""}`);
1722
+ }
1723
+ static describe(reason) {
1724
+ if (reason.kind === "unoptimized")
1725
+ return "declared with optimize: false, so the runtime CSS serves it";
1726
+ if (reason.kind === "declared")
1727
+ return `kept by assets.keepFonts "${reason.glob}"`;
1728
+ return `referenced by ${reason.referrer}`;
1729
+ }
1730
+ static formatBytes(bytes) {
1731
+ if (bytes < 1024)
1732
+ return `${bytes}B`;
1733
+ if (bytes < 1024 * 1024)
1734
+ return `${(bytes / 1024).toFixed(1)}KB`;
1735
+ return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
1736
+ }
1737
+ async#isDirectory(dir) {
1738
+ return await stat3(dir).then((entry) => entry.isDirectory(), () => false);
1739
+ }
1740
+ }
1741
+
1755
1742
  // pkgs/@akanjs/devkit/frontendBuild/pagesBundleBuilder.ts
1756
1743
  import path11 from "path";
1757
1744
 
@@ -1763,7 +1750,6 @@ var DEFAULT_EXCLUDE_PREFIX = ["@akanjs/cli/", "@akanjs/devkit/"];
1763
1750
  var OPTIONAL_BACKEND_EXTERNAL_EXACT = new Set([
1764
1751
  "@libsql/client",
1765
1752
  "bullmq",
1766
- "croner",
1767
1753
  "ioredis",
1768
1754
  "postgres",
1769
1755
  "protobufjs"
@@ -2154,6 +2140,7 @@ class SsrBaseArtifactBuilder {
2154
2140
  branches: [...akanConfig.branches],
2155
2141
  i18n: akanConfig.i18n,
2156
2142
  imageConfig: akanConfig.images,
2143
+ web: akanConfig.web,
2157
2144
  deepLinkAssociations: Object.values(akanConfig.mobile.targets).filter((target) => (target.deepLinks?.domains?.length ?? 0) > 0).map((target) => ({
2158
2145
  targetName: target.name,
2159
2146
  appId: target.appId,
@@ -2180,7 +2167,7 @@ class SsrBaseArtifactBuilder {
2180
2167
  const ssrBundle = await new ClientEntriesBundler({
2181
2168
  app: this.#app,
2182
2169
  entries: [rscSegmentOutletEntry],
2183
- ...RouteClientBuilder.resolveSsrClientExternalOptions(this.#command),
2170
+ ...RouteClientBuilder.resolveSsrClientBundleOptions(this.#command),
2184
2171
  outputSubdir: "client-ssr",
2185
2172
  command: this.#command
2186
2173
  }).bundle();
@@ -2232,13 +2219,13 @@ class SsrBaseArtifactBuilder {
2232
2219
  `);
2233
2220
  if (!cssText)
2234
2221
  return [];
2235
- return [this.#writeCssAsset(basePath, cssText)];
2222
+ return [this.#writeCssAsset(basePath, cssText, cssCompiler.importedStylesheetsByBasePath[basePath] ?? [])];
2236
2223
  })));
2237
2224
  if (optimizedFonts.files.length > 0)
2238
2225
  this.#app.verbose(`[base-artifact] optimized ${optimizedFonts.files.length} font files`);
2239
2226
  return { cssCompiler, optimizedFonts, cssAssets };
2240
2227
  }
2241
- async#writeCssAsset(basePath, cssText) {
2228
+ async#writeCssAsset(basePath, cssText, imported) {
2242
2229
  const cssAssetName = basePath || "root";
2243
2230
  const preparedCssText = await prepareCssAsset(this.#command, basePath, cssText);
2244
2231
  const cssHash = Bun.hash(`${basePath}
@@ -2248,9 +2235,17 @@ ${preparedCssText}`).toString(36);
2248
2235
  `/_akan/styles/${cssAssetName}-${cssHash}.css`
2249
2236
  ];
2250
2237
  await Bun.write(path13.join(this.#absArtifactDir, cssRelPath), preparedCssText);
2238
+ SsrBaseArtifactBuilder.#warnDroppedImports(this.#app, cssRelPath, preparedCssText, imported);
2251
2239
  this.#app.verbose(`[base-artifact] wrote ${preparedCssText.length} bytes of CSS for ${basePath} -> ${cssRelPath}`);
2252
2240
  return [basePath, { cssUrl, cssRelPath }];
2253
2241
  }
2242
+ static #warnDroppedImports(app, cssRelPath, css, imported) {
2243
+ for (const { cssPath, declaredNames } of imported) {
2244
+ if (declaredNames.length === 0 || declaredNames.some((name) => css.includes(`${name}:`)))
2245
+ continue;
2246
+ app.logger.warn(`[base-artifact] @import ${cssPath} declares ${declaredNames.length} custom propert${declaredNames.length === 1 ? "y" : "ies"} and none of them are in ${cssRelPath}`);
2247
+ }
2248
+ }
2254
2249
  }
2255
2250
  // pkgs/@akanjs/devkit/frontendBuild/styleGuard.ts
2256
2251
  var PREFIX = "(?:bg|text|border(?:-[tblrxy])?(?:-[se])?|ring(?:-offset)?|fill|stroke|shadow|from|to|via|divide|outline|decoration|placeholder|caret|accent)";
@@ -2258,210 +2253,8 @@ var PALETTE = "(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|gre
2258
2253
  var LEAD = "(^|[\\s\"'`{(\\[:!])";
2259
2254
  var TAIL = "(?=[\\s\"'`})\\]:/!,]|$)";
2260
2255
  var RAW_PALETTE_RE = new RegExp(`${LEAD}((?:${PREFIX})-)${PALETTE}-\\d{2,3}${TAIL}`, "g");
2261
- var ARBITRARY_COLOR_RE = /\[(#[0-9a-fA-F]{3,8}|(?:rgb|rgba|hsl|hsla|oklch|oklab|lab|lch|hwb|color)\([^\]]*\))\]/g;
2262
2256
  var INTERPOLATED_ARBITRARY_RE = new RegExp(`${LEAD}[a-z][a-z0-9-]*-\\[[^\\]\`]*\\$\\{`, "g");
2263
- var STYLE_OBJECT_RE = /style=\{\{([\s\S]*?)\}\}/g;
2264
- var STYLE_TAG_RE = /<style[^>]*>([\s\S]*?)<\/style>/g;
2265
- var INLINE_COLOR_LITERAL_RE = /#[0-9a-fA-F]{3,8}\b|(?:rgb|rgba|hsl|hsla|oklch|oklab|lab|lch|hwb)\(/g;
2266
2257
  var DAISYUI_LEGACY_RE = new RegExp(`${LEAD}(?:` + "btn-(?:primary|secondary|accent|neutral|info|success|warning|error|ghost|link|outline|square|circle|wide|block|xs|sm|md|lg)|" + "badge-(?:primary|secondary|accent|neutral|info|success|warning|error|ghost|outline)|" + "alert-(?:info|success|warning|error)|" + "input-(?:bordered|primary|secondary|accent|ghost|error)|" + "select-(?:bordered|primary|ghost)|textarea-(?:bordered|primary|ghost)|" + "checkbox-(?:primary|secondary|accent)|toggle-(?:primary|secondary|accent)|" + "loading-(?:spinner|dots|ring|ball|bars|infinity)|" + "card-(?:body|title|actions)|modal-(?:box|action|backdrop)|" + "collapse-(?:title|content|arrow|plus)|dropdown-(?:content|end|start|hover)|" + "stat-(?:title|value|desc)|tabs-(?:boxed|lifted|bordered)|tab-active|" + "menu-(?:title|dropdown)|steps-(?:horizontal|vertical)|join-item|" + "mockup-(?:code|phone|browser|window)|drawer-(?:side|content|toggle)" + `)${TAIL}`, "g");
2267
- var ALL_RULES = [
2268
- "raw-palette",
2269
- "arbitrary-color",
2270
- "inline-color",
2271
- "daisyui-legacy",
2272
- "interpolated-arbitrary"
2273
- ];
2274
-
2275
- class StyleGuard {
2276
- run(files) {
2277
- const out = [];
2278
- for (const file of files) {
2279
- const directives = this.#parseDirectives(file.content);
2280
- const scan = this.#stripComments(file.content);
2281
- const local = [];
2282
- this.#scanClassLiterals(file, scan, local);
2283
- this.#scanInlineColors(file, scan, local);
2284
- for (const v of local)
2285
- if (!this.#isSuppressed(v, directives))
2286
- out.push(v);
2287
- }
2288
- return out;
2289
- }
2290
- #stripComments(src) {
2291
- const out = src.split("");
2292
- const blank = (i2) => {
2293
- if (src[i2] !== `
2294
- `)
2295
- out[i2] = " ";
2296
- };
2297
- let state = "code";
2298
- let i = 0;
2299
- while (i < src.length) {
2300
- const c = src[i];
2301
- const d = src[i + 1];
2302
- if (state === "code") {
2303
- if (c === "/" && d === "/") {
2304
- blank(i);
2305
- blank(i + 1);
2306
- state = "line";
2307
- i += 2;
2308
- } else if (c === "/" && d === "*") {
2309
- blank(i);
2310
- blank(i + 1);
2311
- state = "block";
2312
- i += 2;
2313
- } else {
2314
- if (c === "'")
2315
- state = "squote";
2316
- else if (c === '"')
2317
- state = "dquote";
2318
- else if (c === "`")
2319
- state = "template";
2320
- i++;
2321
- }
2322
- } else if (state === "line") {
2323
- if (c === `
2324
- `)
2325
- state = "code";
2326
- else
2327
- blank(i);
2328
- i++;
2329
- } else if (state === "block") {
2330
- if (c === "*" && d === "/") {
2331
- blank(i);
2332
- blank(i + 1);
2333
- state = "code";
2334
- i += 2;
2335
- } else {
2336
- blank(i);
2337
- i++;
2338
- }
2339
- } else {
2340
- const close = state === "squote" ? "'" : state === "dquote" ? '"' : "`";
2341
- if (c === "\\")
2342
- i += 2;
2343
- else {
2344
- if (c === close)
2345
- state = "code";
2346
- i++;
2347
- }
2348
- }
2349
- }
2350
- return out.join("");
2351
- }
2352
- #parseDirectives(content) {
2353
- const directives = { file: null, nextLine: new Map };
2354
- const lines = content.split(`
2355
- `);
2356
- for (let i = 0;i < lines.length; i++) {
2357
- const line = lines[i];
2358
- const nextLineMatch = line.match(/styleguard-disable-next-line\b(.*)$/);
2359
- if (nextLineMatch) {
2360
- directives.nextLine.set(i + 2, this.#extractRules(nextLineMatch[1]));
2361
- continue;
2362
- }
2363
- const fileMatch = line.match(/styleguard-disable\b(.*)$/);
2364
- if (fileMatch)
2365
- directives.file = this.#mergeFileScope(directives.file, this.#extractRules(fileMatch[1]));
2366
- }
2367
- return directives;
2368
- }
2369
- #extractRules(tail) {
2370
- const found = ALL_RULES.filter((rule) => tail.includes(rule));
2371
- return found.length === 0 ? "all" : new Set(found);
2372
- }
2373
- #mergeFileScope(prev, next) {
2374
- if (prev === "all" || next === "all")
2375
- return "all";
2376
- return new Set([...prev ?? [], ...next]);
2377
- }
2378
- #isSuppressed(v, directives) {
2379
- if (directives.file === "all" || directives.file?.has(v.rule))
2380
- return true;
2381
- const nextLine = directives.nextLine.get(v.line);
2382
- return nextLine === "all" || (nextLine?.has(v.rule) ?? false);
2383
- }
2384
- #scanClassLiterals(file, scan, out) {
2385
- for (const m of scan.matchAll(RAW_PALETTE_RE)) {
2386
- out.push(this.#violation(file, m.index ?? 0, {
2387
- rule: "raw-palette",
2388
- severity: "error",
2389
- suggestion: "\uC2DC\uB9E8\uD2F1 \uD1A0\uD070\uC73C\uB85C \uAD50\uCCB4\uD558\uC138\uC694 \u2014 \uC608: text-gray-500\u2192text-muted-foreground, bg-red-500\u2192bg-destructive, bg-blue-500\u2192bg-info. \uBE0C\uB79C\uB4DC \uC0C9\uC740 page/styles.css \uD1A0\uD070\uC5D0\uC11C \uC870\uC815\uD569\uB2C8\uB2E4."
2390
- }));
2391
- }
2392
- for (const m of scan.matchAll(ARBITRARY_COLOR_RE)) {
2393
- out.push(this.#violation(file, m.index ?? 0, {
2394
- rule: "arbitrary-color",
2395
- severity: "error",
2396
- suggestion: "\uC784\uC758 \uC0C9 \uB9AC\uD130\uB7F4 \uB300\uC2E0 \uD1A0\uD070\uC744 \uC4F0\uC138\uC694 \u2014 bg-[#3b82f6]\u2192bg-info, text-[rgb(...)]\u2192\uC2DC\uB9E8\uD2F1 \uD1A0\uD070. \uAF2D \uD544\uC694\uD558\uBA74 \uC2A4\uCF54\uD504 \uD1A0\uD070(.campaign-x { --primary: \u2026 })\uC744 \uC815\uC758\uD558\uC138\uC694."
2397
- }));
2398
- }
2399
- for (const m of scan.matchAll(DAISYUI_LEGACY_RE)) {
2400
- out.push(this.#violation(file, m.index ?? 0, {
2401
- rule: "daisyui-legacy",
2402
- severity: "error",
2403
- suggestion: "daisyUI \uD074\uB798\uC2A4\uB294 \uC81C\uAC70\uB410\uC2B5\uB2C8\uB2E4. akanjs/ui \uD504\uB9AC\uBBF8\uD2F0\uBE0C(Button/Badge \uB4F1)\uB098 buttonRecipe()/badgeRecipe() + \uC2DC\uB9E8\uD2F1 \uD1A0\uD070\uC73C\uB85C \uAD50\uCCB4\uD558\uC138\uC694."
2404
- }));
2405
- }
2406
- for (const m of scan.matchAll(INTERPOLATED_ARBITRARY_RE)) {
2407
- out.push(this.#violation(file, m.index ?? 0, {
2408
- rule: "interpolated-arbitrary",
2409
- severity: "error",
2410
- suggestion: "\uB7F0\uD0C0\uC784 \uAC12\uC73C\uB85C \uC784\uC758\uAC12 \uD074\uB798\uC2A4\uB97C \uC870\uB9BD\uD558\uBA74 CSS \uAC00 \uC0DD\uC131\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4(\uC2A4\uCE90\uB108\uB294 \uC18C\uC2A4 \uD14D\uC2A4\uD2B8\uB97C \uC77D\uC2B5\uB2C8\uB2E4). \uD06C\uAE30/\uC704\uCE58\uB294 style prop \uC73C\uB85C \uB118\uAE30\uC138\uC694 \u2014 style={{ minHeight }}. \uAC12\uC774 enum \uC774\uBA74 \uB9AC\uD130\uB7F4 \uD074\uB798\uC2A4 \uB9F5\uC73C\uB85C \uB450\uC138\uC694."
2411
- }));
2412
- }
2413
- }
2414
- #scanInlineColors(file, scan, out) {
2415
- const push = (index) => out.push(this.#violation(file, index, {
2416
- rule: "inline-color",
2417
- severity: "error",
2418
- suggestion: "\uC778\uB77C\uC778 \uC0C9 \uB9AC\uD130\uB7F4 \uB300\uC2E0 CSS \uBCC0\uC218/\uD1A0\uD070\uC744 \uCC38\uC870\uD558\uC138\uC694 \u2014 style={{ color: 'var(--primary)' }} \uB610\uB294 \uC2DC\uB9E8\uD2F1 \uD074\uB798\uC2A4(text-primary). \uD558\uB4DC\uCF54\uB529 hex/rgb \uB294 \uD14C\uB9C8 \uC804\uD658\uC744 \uAE68\uB728\uB9BD\uB2C8\uB2E4."
2419
- }));
2420
- for (const region of [STYLE_OBJECT_RE, STYLE_TAG_RE]) {
2421
- for (const m of scan.matchAll(region)) {
2422
- const inner = m[1] ?? "";
2423
- const innerStart = (m.index ?? 0) + m[0].indexOf(inner);
2424
- for (const lit of inner.matchAll(INLINE_COLOR_LITERAL_RE)) {
2425
- push(innerStart + (lit.index ?? 0));
2426
- }
2427
- }
2428
- }
2429
- }
2430
- #violation(file, index, base) {
2431
- const before = file.content.slice(0, index);
2432
- const line = before.length === 0 ? 1 : before.split(`
2433
- `).length;
2434
- const lineStart = before.lastIndexOf(`
2435
- `) + 1;
2436
- const lineEnd = file.content.indexOf(`
2437
- `, index);
2438
- const snippet = file.content.slice(lineStart, lineEnd === -1 ? undefined : lineEnd).trim();
2439
- return { ...base, path: file.path, line, snippet };
2440
- }
2441
- static countNamedComponentClasses(css) {
2442
- const names = new Set;
2443
- const layerRe = /@layer\s+components\s*\{/g;
2444
- for (const layer of css.matchAll(layerRe)) {
2445
- const bodyStart = (layer.index ?? 0) + layer[0].length;
2446
- const body = StyleGuard.#extractBalanced(css, bodyStart);
2447
- for (const sel of body.matchAll(/(^|[\s}])\.(-?[a-zA-Z_][\w-]*)/g))
2448
- names.add(sel[2]);
2449
- }
2450
- return { count: names.size, names: [...names].sort() };
2451
- }
2452
- static #extractBalanced(css, start) {
2453
- let depth = 1;
2454
- for (let i = start;i < css.length; i++) {
2455
- const ch = css[i];
2456
- if (ch === "{")
2457
- depth++;
2458
- else if (ch === "}") {
2459
- depth--;
2460
- if (depth === 0)
2461
- return css.slice(start, i);
2462
- }
2463
- }
2464
- return css.slice(start);
2465
- }
2466
- }
2467
- export { AllRoutesBuilder, CsrArtifactBuilder, CssCompiler, FontOptimizer, PagesBundleBuilder, precompressArtifacts, SsrBaseArtifactBuilder };
2258
+ var DAISYUI_TOKEN = "(?:base-(?:100|200|300|content)|(?:primary|secondary|accent|neutral|info|success|warning|error)-content|error)";
2259
+ var DAISYUI_TOKEN_RE = new RegExp(`${LEAD}(?:${PREFIX})-${DAISYUI_TOKEN}${TAIL}`, "g");
2260
+ export { AllRoutesBuilder, CsrArtifactBuilder, CssCompiler, FontOptimizer, FontPruner, PagesBundleBuilder, precompressArtifacts, SsrBaseArtifactBuilder };