@akanjs/cli 3.0.0-alpha.4 → 3.0.0-alpha.41

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 (108) hide show
  1. package/.build-stamp +1 -1
  2. package/README.ko.md +1 -1
  3. package/README.md +1 -1
  4. package/{abstractCompactor-rk6twzvj.js → abstractCompactor-20e0cwbs.js} +3 -3
  5. package/{agent.command-jfz8yfwy.js → agent.command-e835fjem.js} +9 -8
  6. package/{application.command-mxyqtd6p.js → application.command-sjtbk6tr.js} +7 -6
  7. package/{applicationBuildRunner-cwk52sb7.js → applicationBuildRunner-7gnfz7ww.js} +7 -8
  8. package/{applicationReleasePackager-yrrhbkv3.js → applicationReleasePackager-93nzdfsy.js} +2 -2
  9. package/buildBatch.proc.js +6 -5
  10. package/{capacitorApp-j357ak1g.js → capacitorApp-qv7g3dp2.js} +29 -28
  11. package/{cloud.command-f87dwe52.js → cloud.command-y0e4eyqz.js} +11 -10
  12. package/{context.command-z5bf8bn7.js → context.command-vn87jb08.js} +17 -16
  13. package/{dependencyScanner-f6grrep3.js → dependencyScanner-rbs2407b.js} +1 -1
  14. package/getDirname-ka11hm7e.js +8 -0
  15. package/{guideline.command-1m1ctar3.js → guideline.command-s4hn2bsy.js} +7 -6
  16. package/guidelines/conventions/conventions.generate.json +25 -0
  17. package/guidelines/conventions/conventions.instruction.md +1193 -0
  18. package/guidelines/cssRule/cssRule.instruction.md +7 -0
  19. package/guidelines/framework/framework.instruction.md +1 -0
  20. package/guidelines/ssrRule/ssrRule.generate.json +31 -0
  21. package/guidelines/ssrRule/ssrRule.instruction.md +108 -0
  22. package/guidelines/workspaceOnboarding/workspaceOnboarding.generate.json +24 -0
  23. package/guidelines/workspaceOnboarding/workspaceOnboarding.instruction.md +972 -0
  24. package/incrementalBuilder.proc.js +7 -6
  25. package/{index-1s71fwrq.js → index-0h212yqe.js} +13 -13
  26. package/index-1577bej2.js +7 -0
  27. package/{index-e3qskyt2.js → index-33zce8gt.js} +8 -4
  28. package/{index-csqk9z5j.js → index-3dp7x6rc.js} +3 -3
  29. package/{index-cn24dcqn.js → index-73rnbwgg.js} +15 -7
  30. package/{index-tq1y56tp.js → index-8d2sv6pw.js} +39 -16
  31. package/{index-jtm86byd.js → index-8vkkm7v0.js} +125 -66
  32. package/{index-r4w8cry5.js → index-9jsnb4vw.js} +4 -4
  33. package/{index-ybybkec0.js → index-becx1t0k.js} +6 -6
  34. package/{index-4yp06pn1.js → index-g9e2fhw2.js} +49 -32
  35. package/{index-cap2k2ch.js → index-hg0qvhmx.js} +152 -241
  36. package/{index-xwgm94zr.js → index-j37qq1f2.js} +1 -1
  37. package/{index-n0z3f2xg.js → index-k6fvbcj1.js} +37 -6
  38. package/{index-6pz1j0zj.js → index-mxvakhsm.js} +6 -2
  39. package/{index-vhv3xnjz.js → index-mykmf6wd.js} +8 -8
  40. package/{index-y0k42k04.js → index-mz5jtaq7.js} +1 -1
  41. package/{index-jk9q6pse.js → index-n706txcv.js} +5 -3
  42. package/{index-hjzr8ja1.js → index-qc0tnhs2.js} +1 -1
  43. package/{index-rh16j3c7.js → index-qnv0aqmm.js} +12 -8
  44. package/{index-3pgtw35d.js → index-sh01r3pm.js} +2 -2
  45. package/{index-xj8bks3p.js → index-t71zvmt9.js} +9 -11
  46. package/{index-4zgs8g1w.js → index-tsmjxph2.js} +1 -1
  47. package/{index-fmgbwsr5.js → index-x33gnap3.js} +4 -4
  48. package/{index-b978vs2e.js → index-xyrb7pk5.js} +3 -3
  49. package/index.js +21 -20
  50. package/{library.command-pwfx8dx2.js → library.command-v5cyaw9t.js} +6 -5
  51. package/{localRegistry.command-r4zjx5py.js → localRegistry.command-53t758pt.js} +10 -9
  52. package/{module.command-0t3ewata.js → module.command-pq6w84xg.js} +10 -9
  53. package/{package.command-7hmkxvpa.js → package.command-qkcs60ts.js} +6 -5
  54. package/package.json +3 -4
  55. package/{page.command-st6r4s0q.js → page.command-qj5bfnh9.js} +6 -5
  56. package/{primitive.command-1qkxpzsn.js → primitive.command-bw0nes6x.js} +11 -10
  57. package/{quality.command-hy5vt1bh.js → quality.command-g1s2wt8b.js} +532 -87
  58. package/{recipeScanner-tghkzrrt.js → recipeScanner-zrzev25x.js} +3 -3
  59. package/{repair.command-nakkq2zp.js → repair.command-mzwtejpk.js} +8 -7
  60. package/{routeSourceValidator-cncd92fv.js → routeSourceValidator-7fycdqyv.js} +3 -39
  61. package/{scalar.command-fjq272gs.js → scalar.command-rm6k3c65.js} +9 -8
  62. package/templates/app/env/env.server.debug.ts.template +1 -5
  63. package/templates/app/env/env.server.develop.ts.template +1 -5
  64. package/templates/app/env/env.server.local.ts.template +1 -5
  65. package/templates/app/env/env.server.main.ts.template +1 -5
  66. package/templates/app/page/styles.css.template +3 -3
  67. package/templates/appSample/srvkit/AuthGuard.ts +9 -5
  68. package/templates/appSample/srvkit/SessionInternalArg.ts +4 -3
  69. package/templates/crudPages/[__model__Id]/edit/page.tsx +1 -1
  70. package/templates/crudPages/new/page.tsx +1 -1
  71. package/templates/crudSinglePage/page.tsx +1 -1
  72. package/templates/env/_env.server.type.ts +7 -5
  73. package/templates/lib/db.ts +4 -2
  74. package/templates/lib/dict.ts +1 -1
  75. package/templates/lib/sig.ts +1 -1
  76. package/templates/lib/srv.ts +6 -2
  77. package/templates/lib/useClient.ts +9 -3
  78. package/templates/module/__Model__.Util.tsx +1 -1
  79. package/templates/module/__Model__.Zone.tsx +3 -5
  80. package/templates/workspaceRoot/AGENTS.md.template +0 -831
  81. package/templates/workspaceRoot/CLAUDE.md.template +19 -0
  82. package/templates/workspaceRoot/biome.json.template +4 -217
  83. package/{typeChecker-kravn7ns.js → typeChecker-g6bh0xfv.js} +1 -1
  84. package/typecheck.proc.js +1 -1
  85. package/{workflow.command-jswbgk90.js → workflow.command-g2cgcgxn.js} +16 -15
  86. package/{workspace.command-h9wwcjpt.js → workspace.command-f7jerqb2.js} +70 -52
  87. package/templates/libRoot/base/baseLogic.ts +0 -14
  88. package/templates/libRoot/base/index.ts +0 -8
  89. package/templates/workspaceRoot/.cursor/rules/akan-page-routing.mdc.template +0 -43
  90. package/templates/workspaceRoot/.cursor/rules/akan-sync-conventions.mdc.template +0 -33
  91. package/templates/workspaceRoot/.cursor/rules/application-test-commands.mdc.template +0 -16
  92. package/templates/workspaceRoot/.cursor/rules/change-scope.mdc.template +0 -12
  93. package/templates/workspaceRoot/.cursor/rules/client-server-boundaries.mdc.template +0 -17
  94. package/templates/workspaceRoot/.cursor/rules/coding-comments.mdc.template +0 -25
  95. package/templates/workspaceRoot/.cursor/rules/coding-style.mdc.template +0 -44
  96. package/templates/workspaceRoot/.cursor/rules/domain-module-conventions.mdc.template +0 -17
  97. package/templates/workspaceRoot/.cursor/rules/format-lint.mdc.template +0 -12
  98. package/templates/workspaceRoot/.cursor/rules/lint-enforced-rules.mdc.template +0 -18
  99. package/templates/workspaceRoot/.cursor/rules/module-file-playbook.mdc.template +0 -61
  100. package/templates/workspaceRoot/.cursor/rules/naming-conventions.mdc.template +0 -15
  101. package/templates/workspaceRoot/.cursor/rules/react-component-style.mdc.template +0 -21
  102. package/templates/workspaceRoot/.cursor/rules/repo-overview.mdc.template +0 -17
  103. package/templates/workspaceRoot/.cursor/rules/scalar-modeling.mdc.template +0 -58
  104. package/templates/workspaceRoot/.cursor/rules/secrets-and-env.mdc.template +0 -13
  105. package/templates/workspaceRoot/.cursor/rules/service-signal-conventions.mdc.template +0 -83
  106. package/templates/workspaceRoot/.cursor/rules/srvkit-adapters-and-guards.mdc.template +0 -50
  107. package/templates/workspaceRoot/.cursor/rules/typescript-imports.mdc.template +0 -16
  108. /package/{index-r24hmh0q.js → index-yb2wsc7f.js} +0 -0
@@ -9,10 +9,10 @@ import {
9
9
  createTsconfigPackageResolver,
10
10
  rewriteBarrelImports,
11
11
  transformUseClient
12
- } from "./index-rh16j3c7.js";
12
+ } from "./index-qnv0aqmm.js";
13
13
  import {
14
14
  __require
15
- } from "./index-r24hmh0q.js";
15
+ } from "./index-yb2wsc7f.js";
16
16
 
17
17
  // pkgs/@akanjs/devkit/frontendBuild/allRoutesBuilder.ts
18
18
  import path4 from "path";
@@ -415,11 +415,21 @@ class AllRoutesBuilder {
415
415
  const seedIndex = computeRouteSeedIndex(pageEntries);
416
416
  this.#app.verbose(`[build-all] discovered ${seedIndex.entries.length} routes`);
417
417
  this.#discovery = await GraphClientEntryDiscovery.create(this.#app);
418
+ const allEntries = [];
419
+ const seen = new Set;
418
420
  for (const entry of seedIndex.entries) {
419
421
  const seeds = Array.from(new Set([...seedIndex.globalLayoutFiles, ...entry.seeds]));
420
- const delta = await this.#buildRoute(entry.routeId, seeds);
421
- this.#mergeRoute(entry.routeId, delta);
422
+ for (const discovered of await this.#discovery.discover(seeds)) {
423
+ if (seen.has(discovered))
424
+ continue;
425
+ seen.add(discovered);
426
+ allEntries.push(discovered);
427
+ }
428
+ this.#routeIds.push(entry.routeId);
422
429
  }
430
+ this.#app.verbose(`[build-all] ${allEntries.length} client entries across ${this.#routeIds.length} routes`);
431
+ const delta = await this.#buildEntries(allEntries);
432
+ this.#mergeDelta(delta);
423
433
  this.#merged.knownEntries = Array.from(this.#knownSet);
424
434
  const manifest = {
425
435
  routeIds: this.#routeIds,
@@ -433,23 +443,30 @@ class AllRoutesBuilder {
433
443
  this.#app.verbose(`[build-all] wrote ${manifestPath} routes=${this.#routeIds.length} entries=${this.#merged.knownEntries.length}`);
434
444
  return { manifest, manifestPath, seedIndex };
435
445
  }
436
- async#buildRoute(routeId, seeds) {
446
+ async#buildEntries(entries) {
437
447
  if (!this.#discovery)
438
448
  throw new Error("[build-all] client entry discovery is not initialized");
449
+ if (entries.length === 0)
450
+ return {
451
+ manifestDelta: {},
452
+ ssrManifestDelta: { moduleLoading: null, moduleMap: {} },
453
+ newEntries: [],
454
+ clientDeps: []
455
+ };
439
456
  const started = Date.now();
440
457
  const delta = await new RouteClientBuilder({
441
458
  app: this.#app,
442
- routeId,
443
- seeds,
459
+ seeds: [],
460
+ entries,
444
461
  artifact: this.#artifact,
445
462
  knownEntries: this.#knownSet,
446
463
  discovery: this.#discovery,
447
464
  command: this.#command
448
465
  }).build();
449
- this.#app.verbose(`[build-all] ${routeId} +${delta.newEntries.length} entries (${Date.now() - started}ms)`);
466
+ this.#app.verbose(`[build-all] bundled ${delta.newEntries.length} entries (${Date.now() - started}ms)`);
450
467
  return delta;
451
468
  }
452
- #mergeRoute(routeId, delta) {
469
+ #mergeDelta(delta) {
453
470
  for (const [key, row] of Object.entries(delta.manifestDelta))
454
471
  this.#merged.clientManifest[key] = row;
455
472
  for (const [url, byName] of Object.entries(delta.ssrManifestDelta.moduleMap)) {
@@ -457,7 +474,6 @@ class AllRoutesBuilder {
457
474
  }
458
475
  for (const abs of delta.newEntries)
459
476
  this.#knownSet.add(abs);
460
- this.#routeIds.push(routeId);
461
477
  }
462
478
  }
463
479
 
@@ -794,10 +810,10 @@ import { compile } from "tailwindcss";
794
810
 
795
811
  // pkgs/@akanjs/devkit/frontendBuild/cssCandidateCache.ts
796
812
  import { stat } from "fs/promises";
797
- var CANDIDATE_RE = /-?(?:[\w@]|\[[^\]]+\])[\w:/.-]*(?:\[[^\]]+\][\w:/.-]*)*/g;
813
+ var CANDIDATE_RE = /-?(?:[\w@]|\[[^\]\s]+\])[\w:/.-]*(?:\[[^\]\s]+\][\w:/.-]*)*/g;
798
814
 
799
815
  class CssCandidateCache {
800
- static #version = 2;
816
+ static #version = 3;
801
817
  #path;
802
818
  #entries = new Map;
803
819
  #dirty = false;
@@ -957,8 +973,12 @@ class CssImportResolver {
957
973
  const pkg = await Bun.file(pkgPath).json();
958
974
  const subpath = id === pkgName ? "." : `.${id.slice(pkgName.length)}`;
959
975
  const exportValue = pkg.exports?.[subpath];
960
- const styleEntry = (typeof exportValue === "string" ? exportValue : exportValue?.style || exportValue?.import || exportValue?.default) || pkg.exports?.["."]?.style || pkg.style || "index.css";
961
- return await this.#firstExisting(path7.resolve(pkgDir, styleEntry));
976
+ const exportedEntry = typeof exportValue === "string" ? exportValue : exportValue?.style || exportValue?.import || exportValue?.default;
977
+ if (exportedEntry)
978
+ return await this.#firstExisting(path7.resolve(pkgDir, exportedEntry));
979
+ if (subpath !== ".")
980
+ return await this.#firstExisting(path7.resolve(pkgDir, subpath));
981
+ return await this.#firstExisting(path7.resolve(pkgDir, pkg.exports?.["."]?.style || pkg.style || "index.css"));
962
982
  } catch {
963
983
  return null;
964
984
  }
@@ -1015,6 +1035,8 @@ class CssCompiler {
1015
1035
  #fileExistsCache = new Map;
1016
1036
  #resolvedFileCache = new Map;
1017
1037
  #resolvedSpecifierCache = new Map;
1038
+ #discoveredCssPaths = new Set;
1039
+ importedStylesheetsByBasePath = {};
1018
1040
  #fileExists(absPath) {
1019
1041
  let cached = this.#fileExistsCache.get(absPath);
1020
1042
  if (!cached) {
@@ -1048,13 +1070,20 @@ class CssCompiler {
1048
1070
  async getCss({ refresh } = {}) {
1049
1071
  if (this.#cssText !== null && !refresh)
1050
1072
  return this.#cssText;
1073
+ this.#discoveredCssPaths.clear();
1074
+ this.importedStylesheetsByBasePath = {};
1051
1075
  const { cssPaths, sourcePaths } = await this.discoverCssAndSources({ refresh });
1052
- this.#cssText = await this.compileCss(cssPaths, sourcePaths);
1076
+ const { css, imported } = await this.#compileWithImports(cssPaths, sourcePaths);
1077
+ this.#cssText = css;
1078
+ this.importedStylesheetsByBasePath = { "": imported };
1079
+ await this.#warnUnreachableStylesheets();
1053
1080
  return this.#cssText;
1054
1081
  }
1055
1082
  async getCssByBasePath({ refresh } = {}) {
1056
1083
  if (this.#cssTextByBasePath !== null && !refresh)
1057
1084
  return this.#cssTextByBasePath;
1085
+ this.#discoveredCssPaths.clear();
1086
+ this.importedStylesheetsByBasePath = {};
1058
1087
  const akanConfig = await this.#app.getConfig({ refresh });
1059
1088
  const pageKeys = await this.#app.getPageKeys({ refresh });
1060
1089
  const basePaths = [...akanConfig.basePaths];
@@ -1065,7 +1094,8 @@ class CssCompiler {
1065
1094
  return ["", ""];
1066
1095
  const started = Date.now();
1067
1096
  const { cssPaths, sourcePaths } = await this.discoverCssAndSources({ refresh, pageKeys: rootPageKeys });
1068
- const css = await this.compileCss(cssPaths, sourcePaths);
1097
+ const { css, imported } = await this.#compileWithImports(cssPaths, sourcePaths);
1098
+ this.importedStylesheetsByBasePath[""] = imported;
1069
1099
  this.#logger.verbose(`css base=root paths=${cssPaths.length} sources=${sourcePaths.length} in ${Date.now() - started}ms`);
1070
1100
  return ["", css];
1071
1101
  })(),
@@ -1075,14 +1105,27 @@ class CssCompiler {
1075
1105
  return [basePath, ""];
1076
1106
  const started = Date.now();
1077
1107
  const { cssPaths, sourcePaths } = await this.discoverCssAndSources({ refresh, pageKeys: basePathPageKeys });
1078
- const css = await this.compileCss(cssPaths, sourcePaths);
1108
+ const { css, imported } = await this.#compileWithImports(cssPaths, sourcePaths);
1109
+ this.importedStylesheetsByBasePath[basePath] = imported;
1079
1110
  this.#logger.verbose(`css base=${basePath} paths=${cssPaths.length} sources=${sourcePaths.length} in ${Date.now() - started}ms`);
1080
1111
  return [basePath, css];
1081
1112
  })
1082
1113
  ]);
1083
1114
  this.#cssTextByBasePath = Object.fromEntries(cssEntries);
1115
+ await this.#warnUnreachableStylesheets();
1084
1116
  return this.#cssTextByBasePath;
1085
1117
  }
1118
+ async#warnUnreachableStylesheets() {
1119
+ const pageDir = path8.join(this.#app.cwdPath, "page");
1120
+ const glob = new Bun.Glob("**/*.css");
1121
+ for await (const cssPath of glob.scan({ cwd: pageDir, absolute: true })) {
1122
+ if (cssPath.includes(`${path8.sep}(libs)${path8.sep}`))
1123
+ continue;
1124
+ if (this.#discoveredCssPaths.has(cssPath))
1125
+ continue;
1126
+ this.#logger.warn(`css ${path8.relative(this.#app.cwdPath, cssPath)} is imported by no route and never compiled`);
1127
+ }
1128
+ }
1086
1129
  async discoverCss({ refresh } = {}) {
1087
1130
  const { cssPaths } = await this.discoverCssAndSources({ refresh });
1088
1131
  return cssPaths;
@@ -1142,21 +1185,47 @@ class CssCompiler {
1142
1185
  queue.push(resolved);
1143
1186
  }
1144
1187
  }
1145
- return { cssPaths: [...cssFiles], sourcePaths: [...sourceFiles] };
1188
+ const tokenPaths = await this.#libTokenStylesheets(sourceFiles);
1189
+ const cssPaths = [...new Set([...tokenPaths, ...cssFiles])];
1190
+ for (const cssPath of cssPaths)
1191
+ this.#discoveredCssPaths.add(cssPath);
1192
+ return { cssPaths, sourcePaths: [...sourceFiles] };
1193
+ }
1194
+ async#libTokenStylesheets(sourceFiles) {
1195
+ const libsRoot = path8.join(this.#app.workspace.workspaceRoot, "libs");
1196
+ const libNames = new Set;
1197
+ for (const filePath of sourceFiles) {
1198
+ const relPath = path8.relative(libsRoot, filePath);
1199
+ if (relPath.startsWith("..") || path8.isAbsolute(relPath))
1200
+ continue;
1201
+ const [libName] = relPath.split(path8.sep);
1202
+ if (libName)
1203
+ libNames.add(libName);
1204
+ }
1205
+ const tokenPaths = await Promise.all([...libNames].sort().map(async (libName) => {
1206
+ const tokensPath = path8.join(libsRoot, libName, "ui/tokens.css");
1207
+ return await this.#fileExists(tokensPath) ? tokensPath : null;
1208
+ }));
1209
+ return tokenPaths.filter((tokensPath) => !!tokensPath);
1146
1210
  }
1147
1211
  async compileCss(cssPaths, sourcePaths) {
1212
+ const { css } = await this.#compileWithImports(cssPaths, sourcePaths);
1213
+ return css;
1214
+ }
1215
+ async#compileWithImports(cssPaths, sourcePaths) {
1148
1216
  if (cssPaths.length === 0)
1149
- return "";
1217
+ return { css: "", imported: [] };
1150
1218
  const compileStarted = Date.now();
1151
1219
  const compilers = await Promise.all(cssPaths.map(async (cssPath) => {
1152
1220
  const css = await Bun.file(cssPath).text();
1153
1221
  const base = path8.dirname(cssPath);
1222
+ const imported2 = new Map;
1154
1223
  const compiler = await compile(css, {
1155
1224
  base,
1156
- loadStylesheet: (id, fromBase) => this.#loadStylesheet(id, fromBase),
1225
+ loadStylesheet: (id, fromBase) => this.#loadStylesheet(id, fromBase, imported2),
1157
1226
  loadModule: (id, fromBase) => this.#loadModule(id, fromBase)
1158
1227
  });
1159
- return { cssPath, compiler };
1228
+ return { cssPath, compiler, imported: imported2 };
1160
1229
  }));
1161
1230
  const sourceDirs = new Set;
1162
1231
  for (const entry of compilers) {
@@ -1169,23 +1238,39 @@ class CssCompiler {
1169
1238
  const candidates = await this.#scanCandidates(sourcePaths, [...sourceDirs]);
1170
1239
  this.#logger.verbose(`css candidates scanned count=${candidates.length} sources=${sourcePaths.length} dirs=${sourceDirs.size} in ${Date.now() - scanStarted}ms`);
1171
1240
  const parts = [];
1241
+ const imported = [];
1172
1242
  for (const entry of compilers) {
1173
1243
  if (!entry)
1174
1244
  continue;
1175
- parts.push(entry.compiler.build(candidates));
1245
+ const part = entry.compiler.build(candidates);
1246
+ parts.push(part);
1247
+ for (const [cssPath, content] of entry.imported) {
1248
+ const declaredNames = declaredCustomProperties(content);
1249
+ imported.push({ cssPath, declaredNames });
1250
+ if (declaredNames.length === 0 || declaredNames.some((name) => part.includes(`${name}:`)))
1251
+ continue;
1252
+ this.#logger.warn(`css @import ${cssPath} was loaded by ${entry.cssPath} but none of its ${declaredNames.length} declaration(s) reached the compiled CSS`);
1253
+ }
1176
1254
  }
1177
1255
  this.#logger.verbose(`css compiled paths=${cssPaths.length} candidates=${candidates.length} in ${Date.now() - compileStarted}ms`);
1178
- return parts.join(`
1179
- `);
1256
+ return { css: parts.join(`
1257
+ `), imported };
1180
1258
  }
1181
- async#loadStylesheet(id, fromBase) {
1259
+ async#loadStylesheet(id, fromBase, imported) {
1182
1260
  const p = await this.#resolveCssImport(id, fromBase);
1261
+ this.#discoveredCssPaths.add(p);
1183
1262
  const content = await Bun.file(p).text();
1263
+ imported?.set(p, content);
1264
+ this.#logger.verbose(`css import "${id}" from ${fromBase} -> ${p} (${content.length} bytes)`);
1184
1265
  return { path: p, base: path8.dirname(p), content };
1185
1266
  }
1186
1267
  async#resolveCssImport(id, fromBase) {
1187
- if (id.startsWith(".") || id.startsWith("/"))
1188
- return path8.resolve(fromBase, id);
1268
+ if (id.startsWith(".") || id.startsWith("/")) {
1269
+ const filePath = path8.resolve(fromBase, id);
1270
+ if (await this.#fileExists(filePath))
1271
+ return filePath;
1272
+ throw new Error(`[css] failed to resolve stylesheet import "${id}" from ${fromBase} (no file at ${filePath})`);
1273
+ }
1189
1274
  const resolver = await this.#getCssImportResolver();
1190
1275
  const resolved = await resolver.resolve(id, fromBase);
1191
1276
  if (resolved)
@@ -1270,6 +1355,10 @@ function isSourceFile(filePath) {
1270
1355
  function isIgnoredNodeModuleSource(filePath) {
1271
1356
  return NODE_MODULES_RE.test(filePath) && !AKANJS_NODE_MODULE_RE.test(filePath);
1272
1357
  }
1358
+ function declaredCustomProperties(css) {
1359
+ const withoutThemeBlocks = css.replace(/@theme[^{]*\{[^}]*\}/g, "");
1360
+ return [...new Set([...withoutThemeBlocks.matchAll(/(?:^|[\s;{])(--[\w-]+)\s*:/g)].map(([, name]) => name))].filter((name) => !!name);
1361
+ }
1273
1362
  function getPageKeyBasePath(pageKey, basePaths) {
1274
1363
  const normalized = pageKey.split(path8.sep).join("/").replace(/^\.\//, "");
1275
1364
  const segments = normalized.split("/");
@@ -1747,7 +1836,6 @@ var DEFAULT_EXCLUDE_PREFIX = ["@akanjs/cli/", "@akanjs/devkit/"];
1747
1836
  var OPTIONAL_BACKEND_EXTERNAL_EXACT = new Set([
1748
1837
  "@libsql/client",
1749
1838
  "bullmq",
1750
- "croner",
1751
1839
  "ioredis",
1752
1840
  "postgres",
1753
1841
  "protobufjs"
@@ -2025,14 +2113,21 @@ function loaderFor2(absPath) {
2025
2113
  // pkgs/@akanjs/devkit/frontendBuild/precompressArtifacts.ts
2026
2114
  import fs2 from "fs";
2027
2115
  import path12 from "path";
2116
+ import zlib from "zlib";
2028
2117
  var COMPRESSIBLE_EXTS = new Set([".css", ".html", ".js", ".json", ".svg"]);
2029
2118
  var MIN_COMPRESS_BYTES = 1024;
2119
+ var BROTLI_QUALITY_BY_EXT = { ".css": 11 };
2120
+ var DEFAULT_BROTLI_QUALITY = 9;
2121
+ var GZIP_LEVEL = 9;
2030
2122
  async function precompressArtifacts(app) {
2031
- const roots = [path12.join(app.dist.cwdPath, ".akan/artifact/client")];
2032
- const result = { files: 0, inputBytes: 0, outputBytes: 0 };
2123
+ const roots = [
2124
+ path12.join(app.dist.cwdPath, ".akan/artifact/client"),
2125
+ path12.join(app.dist.cwdPath, ".akan/artifact/styles")
2126
+ ];
2127
+ const result = { files: 0, inputBytes: 0, outputBytes: 0, brotliBytes: 0 };
2033
2128
  await Promise.all(roots.map((root) => precompressRoot(root, result)));
2034
2129
  if (result.files > 0) {
2035
- app.verbose(`[precompress] wrote ${result.files} gzip sidecars (${formatBytes(result.inputBytes)} -> ${formatBytes(result.outputBytes)})`);
2130
+ app.verbose(`[precompress] wrote ${result.files} sidecars (${formatBytes(result.inputBytes)} -> gzip ${formatBytes(result.outputBytes)} / br ${formatBytes(result.brotliBytes)})`);
2036
2131
  }
2037
2132
  return result;
2038
2133
  }
@@ -2044,15 +2139,27 @@ async function precompressRoot(root, result) {
2044
2139
  if (!await shouldPrecompress(filePath))
2045
2140
  continue;
2046
2141
  const bytes = await Bun.file(filePath).bytes();
2047
- const gz = Bun.gzipSync(toArrayBuffer(bytes));
2048
- await Bun.write(`${filePath}.gz`, gz);
2142
+ const buffer = toArrayBuffer(bytes);
2143
+ const gz = Bun.gzipSync(buffer, { level: GZIP_LEVEL });
2144
+ const br = brotliCompress(buffer, path12.extname(filePath).toLowerCase());
2145
+ await Promise.all([Bun.write(`${filePath}.gz`, gz), Bun.write(`${filePath}.br`, br)]);
2049
2146
  result.files += 1;
2050
2147
  result.inputBytes += bytes.byteLength;
2051
2148
  result.outputBytes += gz.byteLength;
2149
+ result.brotliBytes += br.byteLength;
2052
2150
  }
2053
2151
  }
2152
+ function brotliCompress(buffer, ext) {
2153
+ const quality = BROTLI_QUALITY_BY_EXT[ext] ?? DEFAULT_BROTLI_QUALITY;
2154
+ return zlib.brotliCompressSync(buffer, {
2155
+ params: {
2156
+ [zlib.constants.BROTLI_PARAM_QUALITY]: quality,
2157
+ [zlib.constants.BROTLI_PARAM_SIZE_HINT]: buffer.byteLength
2158
+ }
2159
+ });
2160
+ }
2054
2161
  async function shouldPrecompress(filePath) {
2055
- if (filePath.endsWith(".gz"))
2162
+ if (filePath.endsWith(".gz") || filePath.endsWith(".br"))
2056
2163
  return false;
2057
2164
  if (!COMPRESSIBLE_EXTS.has(path12.extname(filePath).toLowerCase()))
2058
2165
  return false;
@@ -2145,7 +2252,7 @@ class SsrBaseArtifactBuilder {
2145
2252
  const ssrBundle = await new ClientEntriesBundler({
2146
2253
  app: this.#app,
2147
2254
  entries: [rscSegmentOutletEntry],
2148
- ...RouteClientBuilder.resolveSsrClientExternalOptions(this.#command),
2255
+ ...RouteClientBuilder.resolveSsrClientBundleOptions(this.#command),
2149
2256
  outputSubdir: "client-ssr",
2150
2257
  command: this.#command
2151
2258
  }).bundle();
@@ -2197,13 +2304,13 @@ class SsrBaseArtifactBuilder {
2197
2304
  `);
2198
2305
  if (!cssText)
2199
2306
  return [];
2200
- return [this.#writeCssAsset(basePath, cssText)];
2307
+ return [this.#writeCssAsset(basePath, cssText, cssCompiler.importedStylesheetsByBasePath[basePath] ?? [])];
2201
2308
  })));
2202
2309
  if (optimizedFonts.files.length > 0)
2203
2310
  this.#app.verbose(`[base-artifact] optimized ${optimizedFonts.files.length} font files`);
2204
2311
  return { cssCompiler, optimizedFonts, cssAssets };
2205
2312
  }
2206
- async#writeCssAsset(basePath, cssText) {
2313
+ async#writeCssAsset(basePath, cssText, imported) {
2207
2314
  const cssAssetName = basePath || "root";
2208
2315
  const preparedCssText = await prepareCssAsset(this.#command, basePath, cssText);
2209
2316
  const cssHash = Bun.hash(`${basePath}
@@ -2213,9 +2320,17 @@ ${preparedCssText}`).toString(36);
2213
2320
  `/_akan/styles/${cssAssetName}-${cssHash}.css`
2214
2321
  ];
2215
2322
  await Bun.write(path13.join(this.#absArtifactDir, cssRelPath), preparedCssText);
2323
+ SsrBaseArtifactBuilder.#warnDroppedImports(this.#app, cssRelPath, preparedCssText, imported);
2216
2324
  this.#app.verbose(`[base-artifact] wrote ${preparedCssText.length} bytes of CSS for ${basePath} -> ${cssRelPath}`);
2217
2325
  return [basePath, { cssUrl, cssRelPath }];
2218
2326
  }
2327
+ static #warnDroppedImports(app, cssRelPath, css, imported) {
2328
+ for (const { cssPath, declaredNames } of imported) {
2329
+ if (declaredNames.length === 0 || declaredNames.some((name) => css.includes(`${name}:`)))
2330
+ continue;
2331
+ app.logger.warn(`[base-artifact] @import ${cssPath} declares ${declaredNames.length} custom propert${declaredNames.length === 1 ? "y" : "ies"} and none of them are in ${cssRelPath}`);
2332
+ }
2333
+ }
2219
2334
  }
2220
2335
  // pkgs/@akanjs/devkit/frontendBuild/styleGuard.ts
2221
2336
  var PREFIX = "(?:bg|text|border(?:-[tblrxy])?(?:-[se])?|ring(?:-offset)?|fill|stroke|shadow|from|to|via|divide|outline|decoration|placeholder|caret|accent)";
@@ -2223,210 +2338,6 @@ var PALETTE = "(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|gre
2223
2338
  var LEAD = "(^|[\\s\"'`{(\\[:!])";
2224
2339
  var TAIL = "(?=[\\s\"'`})\\]:/!,]|$)";
2225
2340
  var RAW_PALETTE_RE = new RegExp(`${LEAD}((?:${PREFIX})-)${PALETTE}-\\d{2,3}${TAIL}`, "g");
2226
- var ARBITRARY_COLOR_RE = /\[(#[0-9a-fA-F]{3,8}|(?:rgb|rgba|hsl|hsla|oklch|oklab|lab|lch|hwb|color)\([^\]]*\))\]/g;
2227
2341
  var INTERPOLATED_ARBITRARY_RE = new RegExp(`${LEAD}[a-z][a-z0-9-]*-\\[[^\\]\`]*\\$\\{`, "g");
2228
- var STYLE_OBJECT_RE = /style=\{\{([\s\S]*?)\}\}/g;
2229
- var STYLE_TAG_RE = /<style[^>]*>([\s\S]*?)<\/style>/g;
2230
- var INLINE_COLOR_LITERAL_RE = /#[0-9a-fA-F]{3,8}\b|(?:rgb|rgba|hsl|hsla|oklch|oklab|lab|lch|hwb)\(/g;
2231
2342
  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");
2232
- var ALL_RULES = [
2233
- "raw-palette",
2234
- "arbitrary-color",
2235
- "inline-color",
2236
- "daisyui-legacy",
2237
- "interpolated-arbitrary"
2238
- ];
2239
-
2240
- class StyleGuard {
2241
- run(files) {
2242
- const out = [];
2243
- for (const file of files) {
2244
- const directives = this.#parseDirectives(file.content);
2245
- const scan = this.#stripComments(file.content);
2246
- const local = [];
2247
- this.#scanClassLiterals(file, scan, local);
2248
- this.#scanInlineColors(file, scan, local);
2249
- for (const v of local)
2250
- if (!this.#isSuppressed(v, directives))
2251
- out.push(v);
2252
- }
2253
- return out;
2254
- }
2255
- #stripComments(src) {
2256
- const out = src.split("");
2257
- const blank = (i2) => {
2258
- if (src[i2] !== `
2259
- `)
2260
- out[i2] = " ";
2261
- };
2262
- let state = "code";
2263
- let i = 0;
2264
- while (i < src.length) {
2265
- const c = src[i];
2266
- const d = src[i + 1];
2267
- if (state === "code") {
2268
- if (c === "/" && d === "/") {
2269
- blank(i);
2270
- blank(i + 1);
2271
- state = "line";
2272
- i += 2;
2273
- } else if (c === "/" && d === "*") {
2274
- blank(i);
2275
- blank(i + 1);
2276
- state = "block";
2277
- i += 2;
2278
- } else {
2279
- if (c === "'")
2280
- state = "squote";
2281
- else if (c === '"')
2282
- state = "dquote";
2283
- else if (c === "`")
2284
- state = "template";
2285
- i++;
2286
- }
2287
- } else if (state === "line") {
2288
- if (c === `
2289
- `)
2290
- state = "code";
2291
- else
2292
- blank(i);
2293
- i++;
2294
- } else if (state === "block") {
2295
- if (c === "*" && d === "/") {
2296
- blank(i);
2297
- blank(i + 1);
2298
- state = "code";
2299
- i += 2;
2300
- } else {
2301
- blank(i);
2302
- i++;
2303
- }
2304
- } else {
2305
- const close = state === "squote" ? "'" : state === "dquote" ? '"' : "`";
2306
- if (c === "\\")
2307
- i += 2;
2308
- else {
2309
- if (c === close)
2310
- state = "code";
2311
- i++;
2312
- }
2313
- }
2314
- }
2315
- return out.join("");
2316
- }
2317
- #parseDirectives(content) {
2318
- const directives = { file: null, nextLine: new Map };
2319
- const lines = content.split(`
2320
- `);
2321
- for (let i = 0;i < lines.length; i++) {
2322
- const line = lines[i];
2323
- const nextLineMatch = line.match(/styleguard-disable-next-line\b(.*)$/);
2324
- if (nextLineMatch) {
2325
- directives.nextLine.set(i + 2, this.#extractRules(nextLineMatch[1]));
2326
- continue;
2327
- }
2328
- const fileMatch = line.match(/styleguard-disable\b(.*)$/);
2329
- if (fileMatch)
2330
- directives.file = this.#mergeFileScope(directives.file, this.#extractRules(fileMatch[1]));
2331
- }
2332
- return directives;
2333
- }
2334
- #extractRules(tail) {
2335
- const found = ALL_RULES.filter((rule) => tail.includes(rule));
2336
- return found.length === 0 ? "all" : new Set(found);
2337
- }
2338
- #mergeFileScope(prev, next) {
2339
- if (prev === "all" || next === "all")
2340
- return "all";
2341
- return new Set([...prev ?? [], ...next]);
2342
- }
2343
- #isSuppressed(v, directives) {
2344
- if (directives.file === "all" || directives.file?.has(v.rule))
2345
- return true;
2346
- const nextLine = directives.nextLine.get(v.line);
2347
- return nextLine === "all" || (nextLine?.has(v.rule) ?? false);
2348
- }
2349
- #scanClassLiterals(file, scan, out) {
2350
- for (const m of scan.matchAll(RAW_PALETTE_RE)) {
2351
- out.push(this.#violation(file, m.index ?? 0, {
2352
- rule: "raw-palette",
2353
- severity: "error",
2354
- 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."
2355
- }));
2356
- }
2357
- for (const m of scan.matchAll(ARBITRARY_COLOR_RE)) {
2358
- out.push(this.#violation(file, m.index ?? 0, {
2359
- rule: "arbitrary-color",
2360
- severity: "error",
2361
- 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."
2362
- }));
2363
- }
2364
- for (const m of scan.matchAll(DAISYUI_LEGACY_RE)) {
2365
- out.push(this.#violation(file, m.index ?? 0, {
2366
- rule: "daisyui-legacy",
2367
- severity: "error",
2368
- 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."
2369
- }));
2370
- }
2371
- for (const m of scan.matchAll(INTERPOLATED_ARBITRARY_RE)) {
2372
- out.push(this.#violation(file, m.index ?? 0, {
2373
- rule: "interpolated-arbitrary",
2374
- severity: "error",
2375
- 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."
2376
- }));
2377
- }
2378
- }
2379
- #scanInlineColors(file, scan, out) {
2380
- const push = (index) => out.push(this.#violation(file, index, {
2381
- rule: "inline-color",
2382
- severity: "error",
2383
- 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."
2384
- }));
2385
- for (const region of [STYLE_OBJECT_RE, STYLE_TAG_RE]) {
2386
- for (const m of scan.matchAll(region)) {
2387
- const inner = m[1] ?? "";
2388
- const innerStart = (m.index ?? 0) + m[0].indexOf(inner);
2389
- for (const lit of inner.matchAll(INLINE_COLOR_LITERAL_RE)) {
2390
- push(innerStart + (lit.index ?? 0));
2391
- }
2392
- }
2393
- }
2394
- }
2395
- #violation(file, index, base) {
2396
- const before = file.content.slice(0, index);
2397
- const line = before.length === 0 ? 1 : before.split(`
2398
- `).length;
2399
- const lineStart = before.lastIndexOf(`
2400
- `) + 1;
2401
- const lineEnd = file.content.indexOf(`
2402
- `, index);
2403
- const snippet = file.content.slice(lineStart, lineEnd === -1 ? undefined : lineEnd).trim();
2404
- return { ...base, path: file.path, line, snippet };
2405
- }
2406
- static countNamedComponentClasses(css) {
2407
- const names = new Set;
2408
- const layerRe = /@layer\s+components\s*\{/g;
2409
- for (const layer of css.matchAll(layerRe)) {
2410
- const bodyStart = (layer.index ?? 0) + layer[0].length;
2411
- const body = StyleGuard.#extractBalanced(css, bodyStart);
2412
- for (const sel of body.matchAll(/(^|[\s}])\.(-?[a-zA-Z_][\w-]*)/g))
2413
- names.add(sel[2]);
2414
- }
2415
- return { count: names.size, names: [...names].sort() };
2416
- }
2417
- static #extractBalanced(css, start) {
2418
- let depth = 1;
2419
- for (let i = start;i < css.length; i++) {
2420
- const ch = css[i];
2421
- if (ch === "{")
2422
- depth++;
2423
- else if (ch === "}") {
2424
- depth--;
2425
- if (depth === 0)
2426
- return css.slice(start, i);
2427
- }
2428
- }
2429
- return css.slice(start);
2430
- }
2431
- }
2432
2343
  export { AllRoutesBuilder, CsrArtifactBuilder, CssCompiler, FontOptimizer, PagesBundleBuilder, precompressArtifacts, SsrBaseArtifactBuilder };
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  import {
3
3
  getDirname
4
- } from "./index-jtm86byd.js";
4
+ } from "./index-1577bej2.js";
5
5
 
6
6
  // pkgs/@akanjs/devkit/prompter.ts
7
7
  import fsPromise from "fs/promises";