@akanjs/cli 2.4.2-rc.3 → 3.0.0-alpha.0

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 (79) hide show
  1. package/.build-stamp +1 -1
  2. package/{abstractCompactor-ghh1q9an.js → abstractCompactor-rk6twzvj.js} +4 -4
  3. package/{agent.command-de4r6vk8.js → agent.command-jfz8yfwy.js} +8 -7
  4. package/{application.command-nfsfm96y.js → application.command-3smsv7mq.js} +5 -5
  5. package/{applicationBuildRunner-554a2vnk.js → applicationBuildRunner-aq7mb3z6.js} +2 -1
  6. package/buildBatch.proc.js +7 -6
  7. package/{capacitorApp-yg9b4ajz.js → capacitorApp-q3mpmkkz.js} +3 -3
  8. package/{cloud.command-1qf95x67.js → cloud.command-rk4e9vrt.js} +10 -10
  9. package/{context.command-hhjyjtp7.js → context.command-ygzyb0xb.js} +16 -15
  10. package/{guideline.command-vj956kfr.js → guideline.command-1m1ctar3.js} +10 -10
  11. package/guidelines/componentRule/componentRule.instruction.md +2 -2
  12. package/guidelines/cssRule/cssRule.generate.json +1 -1
  13. package/guidelines/cssRule/cssRule.instruction.md +57 -34
  14. package/guidelines/framework/framework.instruction.md +1 -1
  15. package/guidelines/recipeRule/recipeRule.generate.json +31 -0
  16. package/guidelines/recipeRule/recipeRule.instruction.md +72 -0
  17. package/incrementalBuilder.proc.js +5 -5
  18. package/{index-v9jqrdd1.js → index-4yp06pn1.js} +100 -2
  19. package/{index-pfnh87f9.js → index-4zgs8g1w.js} +1 -1
  20. package/{index-7ppn9v4n.js → index-b978vs2e.js} +3 -3
  21. package/{index-s9s0n5fz.js → index-cn24dcqn.js} +2 -2
  22. package/{index-h971erkm.js → index-csqk9z5j.js} +3 -3
  23. package/{index-z36qzgzp.js → index-e3qskyt2.js} +3 -4
  24. package/{index-fbv96xaw.js → index-ev6rjw9n.js} +11 -11
  25. package/{index-h6eav7zx.js → index-f4gqk0dd.js} +6 -6
  26. package/{index-z5v766nx.js → index-fmgbwsr5.js} +6 -6
  27. package/{index-y1e79mz3.js → index-h0bngh79.js} +13 -13
  28. package/{index-dgeaqwmw.js → index-hjzr8ja1.js} +1 -1
  29. package/{index-d0h48d2f.js → index-jk9q6pse.js} +1 -1
  30. package/{index-xwfg9bam.js → index-jtm86byd.js} +120 -4
  31. package/{index-h5cvg9z9.js → index-ktprgc5g.js} +4 -4
  32. package/{index-zn9rh7sh.js → index-n0z3f2xg.js} +73 -24
  33. package/{index-7k9sj7j5.js → index-pyt2b4nw.js} +212 -0
  34. package/{index-acwxfncv.js → index-r4w8cry5.js} +4 -4
  35. package/{index-zfp1gz9f.js → index-sggm9qkv.js} +1 -1
  36. package/{index-04as43rp.js → index-vhv3xnjz.js} +16 -15
  37. package/index-xjd3rqzj.js +129 -0
  38. package/{index-xftstr5s.js → index-xwgm94zr.js} +1 -1
  39. package/index-xys926f2.js +136 -0
  40. package/index.js +19 -19
  41. package/{library.command-68ah07a3.js → library.command-pwfx8dx2.js} +4 -4
  42. package/{localRegistry.command-84dz000g.js → localRegistry.command-bxhqmd1d.js} +9 -9
  43. package/{module.command-t68xhamx.js → module.command-0t3ewata.js} +8 -8
  44. package/{package.command-00ncttab.js → package.command-7hmkxvpa.js} +4 -4
  45. package/package.json +2 -3
  46. package/{page.command-1096s18n.js → page.command-st6r4s0q.js} +4 -4
  47. package/{primitive.command-02eqpx6t.js → primitive.command-1qkxpzsn.js} +9 -9
  48. package/{quality.command-r9cageqb.js → quality.command-hy5vt1bh.js} +3 -3
  49. package/recipeScanner-tghkzrrt.js +12 -0
  50. package/{repair.command-7dynpnf1.js → repair.command-nakkq2zp.js} +7 -6
  51. package/{scalar.command-3kxy3fkh.js → scalar.command-fjq272gs.js} +7 -7
  52. package/templates/app/page/_index.tsx +55 -52
  53. package/templates/app/page/styles.css.template +12 -71
  54. package/templates/appSample/lib/task/Task.Template.tsx +2 -2
  55. package/templates/appSample/lib/task/Task.Unit.tsx +11 -11
  56. package/templates/appSample/lib/task/Task.Util.tsx +27 -28
  57. package/templates/appSample/lib/task/Task.View.tsx +22 -19
  58. package/templates/appSample/lib/task/Task.Zone.tsx +3 -3
  59. package/templates/appSample/page/task/[taskId]/_index.tsx +3 -3
  60. package/templates/appSample/page/task/[taskId]/edit.tsx +5 -5
  61. package/templates/appSample/page/task/_index.tsx +4 -4
  62. package/templates/appSample/page/task/_layout.tsx +1 -1
  63. package/templates/appSample/page/task/new.tsx +5 -5
  64. package/templates/appSample/ui/GlobalLoading.tsx +4 -4
  65. package/templates/appSample/ui/QuantityControl.tsx +5 -4
  66. package/templates/crudPages/[__model__Id]/page.tsx +1 -1
  67. package/templates/crudPages/page.tsx +1 -1
  68. package/templates/crudSinglePage/page.tsx +1 -1
  69. package/templates/module/__Model__.Template.tsx +2 -1
  70. package/templates/module/__Model__.Unit.tsx +2 -1
  71. package/templates/module/__Model__.Util.tsx +2 -1
  72. package/templates/module/__Model__.View.tsx +4 -3
  73. package/templates/module/__Model__.Zone.tsx +2 -1
  74. package/templates/workspaceRoot/AGENTS.md.template +11 -13
  75. package/templates/workspaceRoot/biome.json.template +1 -1
  76. package/{workflow.command-yy0d9gk3.js → workflow.command-jswbgk90.js} +15 -14
  77. package/{workspace.command-stv9g4a0.js → workspace.command-p9r9nckg.js} +87 -27
  78. package/{applicationReleasePackager-pjbxc793.js → applicationReleasePackager-yrrhbkv3.js} +3 -3
  79. package/{index-nsj2ftxj.js → index-3pgtw35d.js} +3 -3
@@ -1,26 +1,37 @@
1
1
  // @bun
2
2
  import {
3
3
  AkanContextAnalyzer
4
- } from "./index-v9jqrdd1.js";
4
+ } from "./index-4yp06pn1.js";
5
5
  import {
6
6
  Prompter
7
- } from "./index-xftstr5s.js";
7
+ } from "./index-xwgm94zr.js";
8
8
  import {
9
9
  runner,
10
10
  script
11
- } from "./index-d0h48d2f.js";
11
+ } from "./index-jk9q6pse.js";
12
+ import {
13
+ AGENT_BLOCK_END,
14
+ AGENT_BLOCK_START,
15
+ AppExecutor,
16
+ LibExecutor,
17
+ renderRecipeEntries,
18
+ upsertAgentBlock
19
+ } from "./index-jtm86byd.js";
20
+ import {
21
+ collectRecipeSources,
22
+ scanRecipes
23
+ } from "./index-xys926f2.js";
12
24
 
13
25
  // pkgs/@akanjs/cli/agent/agent.script.ts
14
- import { Logger } from "akanjs/common";
26
+ import { Logger as Logger2 } from "akanjs/common";
15
27
 
16
28
  // pkgs/@akanjs/cli/agent/agent.runner.ts
29
+ import { Logger } from "akanjs/common";
17
30
  var targetPaths = {
18
31
  cursor: ".cursor/rules/akan.mdc",
19
32
  "agents-md": "AGENTS.md",
20
33
  claude: "CLAUDE.md"
21
34
  };
22
- var AGENT_BLOCK_START = "<!-- akan:agent:start -->";
23
- var AGENT_BLOCK_END = "<!-- akan:agent:end -->";
24
35
  var SAMPLE_ARTIFACTS = [
25
36
  { probe: "lib/task/task.constant.ts", target: "lib/task", label: "sample database module" },
26
37
  { probe: "lib/_noti/noti.service.ts", target: "lib/_noti", label: "sample service module" },
@@ -60,12 +71,44 @@ ${items.join(`
60
71
 
61
72
  Keep a sample only while you are still learning its pattern; delete it once your own modules cover the same ground.
62
73
 
74
+ `;
75
+ };
76
+ var renderRecipeIndex = async (workspace) => {
77
+ const sources = await collectRecipeSources(`${workspace.workspaceRoot}/pkgs/akanjs/ui`, "akanjs/ui", "recipe");
78
+ const recipes = scanRecipes(sources);
79
+ const scopeIndexPointer = `App and lib recipes are **not** listed here. Each app/lib carries its own generated index \u2014
80
+ \`apps/<app>/AGENTS.md\` / \`libs/<lib>/AGENTS.md\` (\`## Recipes In Scope\`) \u2014 regenerated by \`akan sync\` and
81
+ verified by \`akan lint\`. When working inside an app or lib, consult that file before consuming or authoring a look.`;
82
+ if (recipes.length === 0)
83
+ return `## Recipes
84
+
85
+ ${scopeIndexPointer}
86
+
87
+ `;
88
+ return `## Recipes
89
+
90
+ Framework UI recipes (Tailwind-variant look factories), importable from every app and lib. Consume by exact name \u2014
91
+ \`import { <name> } from "akanjs/ui"\`, then \`<name>(variants?, className?)\`. The second arg merges internally and
92
+ takes **an array too**, so never wrap it in \`cn()\`: \`<name>({}, ["h-full", isWide && "w-full", className])\`.
93
+ Do not guess recipe names, import paths, or variant values; the list below carries
94
+ the full contract (\`*\` marks the default, \`key?\` is a boolean flag), so there is no need to open the recipe file to
95
+ consume one. tsc still reports variant mistakes. **Before inlining a repeated surface (card, box,
96
+ tile, \u2026): reuse a recipe, or add one as \`apps/<app>/ui/Recipe/<name>.ts\` (one recipe per file, re-exported from
97
+ that folder's \`index.ts\`) \u2014 never re-implement the same look inline in several places, and never author a
98
+ near-duplicate.** Full authoring/consumption policy: the \`recipeRule\` guideline.
99
+
100
+ ${renderRecipeEntries(recipes)}
101
+
102
+ ${scopeIndexPointer}
103
+
63
104
  `;
64
105
  };
65
106
  var renderManagedBlock = async (workspace) => {
66
107
  const context = await AkanContextAnalyzer.analyze(workspace);
67
108
  const frameworkGuide = await Prompter.getInstruction("framework");
68
- const sampleCleanup = await renderSampleCleanup(workspace, context.apps.map((app) => app.name));
109
+ const appNames = context.apps.map((app) => app.name);
110
+ const sampleCleanup = await renderSampleCleanup(workspace, appNames);
111
+ const recipeIndex = await renderRecipeIndex(workspace);
69
112
  return `## Workspace
70
113
 
71
114
  - Repo: ${context.repoName}
@@ -86,7 +129,7 @@ ${sampleCleanup}## Akan Module Abstracts
86
129
  Do not hand-edit generated Akan files such as ${context.generatedFiles.map((file) => `\`${file}\``).join(", ")}.
87
130
  If generated output is stale or broken, update the owning source file and run \`akan repair generated\` or \`akan sync <app-or-lib>\`.
88
131
 
89
- ## MCP Workflow Policy
132
+ ${recipeIndex}## MCP Workflow Policy
90
133
 
91
134
  - Prefer Akan MCP workflows before direct source edits.
92
135
  - Direct source edits are denied when an allowlisted Akan workflow or repair tool can perform the change.
@@ -107,24 +150,10 @@ ${context.validationCommands.map((command) => `- \`${command}\``).join(`
107
150
 
108
151
  ${frameworkGuide.trim()}`;
109
152
  };
110
- var upsertManagedBlock = (existing, block) => {
111
- const managed = `${AGENT_BLOCK_START}
112
- ${block}
113
- ${AGENT_BLOCK_END}`;
114
- const startIndex = existing.indexOf(AGENT_BLOCK_START);
115
- const endIndex = existing.indexOf(AGENT_BLOCK_END);
116
- if (startIndex >= 0 && endIndex > startIndex) {
117
- return `${existing.slice(0, startIndex)}${managed}${existing.slice(endIndex + AGENT_BLOCK_END.length)}`;
118
- }
119
- return `${existing.replace(/\s*$/, "")}
120
-
121
- ${managed}
122
- `;
123
- };
124
153
  var renderAgentsMd = async (workspace, existing) => {
125
154
  const block = await renderManagedBlock(workspace);
126
155
  if (existing?.trim())
127
- return upsertManagedBlock(existing, block);
156
+ return upsertAgentBlock(existing, block);
128
157
  const context = await AkanContextAnalyzer.analyze(workspace);
129
158
  return `# ${context.repoName} Agent Guide
130
159
 
@@ -176,6 +205,26 @@ class AgentRunner extends runner("agent") {
176
205
  await workspace.writeFile(filePath, content, { overwrite: true });
177
206
  written.push(filePath);
178
207
  }
208
+ if (targets.includes("agents-md"))
209
+ written.push(...await this.#installScopeIndexes(workspace));
210
+ return written;
211
+ }
212
+ async#installScopeIndexes(workspace) {
213
+ const [appNames, libNames] = await workspace.getExecs();
214
+ const written = [];
215
+ const syncScope = async (exec) => {
216
+ try {
217
+ await exec.syncAgentsIndex();
218
+ written.push(`${exec.type}s/${exec.name}/AGENTS.md`);
219
+ } catch (error) {
220
+ const message = error instanceof Error ? error.message : String(error);
221
+ Logger.rawLog(`Skipped ${exec.type}s/${exec.name}/AGENTS.md (${message})`, undefined, "warn");
222
+ }
223
+ };
224
+ for (const name of libNames)
225
+ await syncScope(LibExecutor.from(workspace, name));
226
+ for (const name of appNames)
227
+ await syncScope(AppExecutor.from(workspace, name));
179
228
  return written;
180
229
  }
181
230
  }
@@ -194,7 +243,7 @@ class AgentScript extends script("agent", [AgentRunner]) {
194
243
  if (action !== "install")
195
244
  throw new Error(`Unknown agent action: ${action}. Use "install".`);
196
245
  const written = await this.agentRunner.install(workspace, resolveTargets(target), { force });
197
- Logger.rawLog(`Agent rules written:
246
+ Logger2.rawLog(`Agent rules written:
198
247
  ${written.map((file) => `- ${file}`).join(`
199
248
  `)}`);
200
249
  }
@@ -2217,4 +2217,216 @@ ${preparedCssText}`).toString(36);
2217
2217
  return [basePath, { cssUrl, cssRelPath }];
2218
2218
  }
2219
2219
  }
2220
+ // pkgs/@akanjs/devkit/frontendBuild/styleGuard.ts
2221
+ var PREFIX = "(?:bg|text|border(?:-[tblrxy])?(?:-[se])?|ring(?:-offset)?|fill|stroke|shadow|from|to|via|divide|outline|decoration|placeholder|caret|accent)";
2222
+ var PALETTE = "(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)";
2223
+ var LEAD = "(^|[\\s\"'`{(\\[:!])";
2224
+ var TAIL = "(?=[\\s\"'`})\\]:/!,]|$)";
2225
+ 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
+ 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
+ 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
+ }
2220
2432
  export { AllRoutesBuilder, CsrArtifactBuilder, CssCompiler, FontOptimizer, PagesBundleBuilder, precompressArtifacts, SsrBaseArtifactBuilder };
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  import {
3
3
  ModuleScript
4
- } from "./index-04as43rp.js";
4
+ } from "./index-vhv3xnjz.js";
5
5
  import {
6
6
  addFieldUiPolicyForType,
7
7
  coerceFieldDefault,
@@ -34,15 +34,15 @@ import {
34
34
  sourceFile,
35
35
  validationCommandsForTarget,
36
36
  viaBuilderParameterName
37
- } from "./index-pfnh87f9.js";
37
+ } from "./index-4zgs8g1w.js";
38
38
  import {
39
39
  script
40
- } from "./index-d0h48d2f.js";
40
+ } from "./index-jk9q6pse.js";
41
41
  import {
42
42
  AppExecutor,
43
43
  LibExecutor,
44
44
  ModuleExecutor
45
- } from "./index-xwfg9bam.js";
45
+ } from "./index-jtm86byd.js";
46
46
 
47
47
  // pkgs/@akanjs/cli/primitive/primitive.script.ts
48
48
  import { capitalize } from "akanjs/common";
@@ -5,7 +5,7 @@ import {
5
5
  } from "./index-76rn3g2c.js";
6
6
  import {
7
7
  CommandExecutionError
8
- } from "./index-xwfg9bam.js";
8
+ } from "./index-jtm86byd.js";
9
9
 
10
10
  // pkgs/@akanjs/devkit/capacitorApp.ts
11
11
  import { cp, mkdir, readFile, rm, writeFile } from "fs/promises";
@@ -1,10 +1,13 @@
1
1
  // @bun
2
2
  import {
3
3
  PageScript
4
- } from "./index-dgeaqwmw.js";
4
+ } from "./index-hjzr8ja1.js";
5
5
  import {
6
6
  pluralizeName
7
7
  } from "./index-ss469dec.js";
8
+ import {
9
+ AiSession
10
+ } from "./index-3pgtw35d.js";
8
11
  import {
9
12
  bilingualDescriptionForField,
10
13
  bilingualLabelForField,
@@ -12,20 +15,17 @@ import {
12
15
  generatedFilesForSync,
13
16
  moduleSourcePaths,
14
17
  sourceFile
15
- } from "./index-pfnh87f9.js";
18
+ } from "./index-4zgs8g1w.js";
16
19
  import {
17
20
  Prompter
18
- } from "./index-xftstr5s.js";
19
- import {
20
- AiSession
21
- } from "./index-nsj2ftxj.js";
21
+ } from "./index-xwgm94zr.js";
22
22
  import {
23
23
  runner,
24
24
  script
25
- } from "./index-d0h48d2f.js";
25
+ } from "./index-jk9q6pse.js";
26
26
  import {
27
27
  ModuleExecutor
28
- } from "./index-xwfg9bam.js";
28
+ } from "./index-jtm86byd.js";
29
29
  import {
30
30
  FileSys
31
31
  } from "./index-67546d0j.js";
@@ -352,9 +352,9 @@ var requestTemplate = ({
352
352
  \uCF54\uB529 \uADDC\uCE59
353
353
  - \uB77C\uC774\uBE0C\uB7EC\uB9AC \uC0AC\uC6A9
354
354
  - \uC544\uC774\uCF58: react-icons \uB77C\uC774\uBE0C\uB7EC\uB9AC \uC0AC\uC6A9
355
- - CSS: tailwind, DaisyUI(card/hero \uAC19\uC740 \uBCF5\uC7A1\uD55C \uCEF4\uD3EC\uB10C\uD2B8 \uC0AC\uC6A9 X) \uC0AC\uC6A9
355
+ - CSS: tailwind + akanjs/ui \uD504\uB9AC\uBBF8\uD2F0\uBE0C(Button/Badge/Input \uB4F1) + \uC2DC\uB9E8\uD2F1 \uD1A0\uD070(bg-background/text-foreground/bg-primary \uB4F1) \uC0AC\uC6A9 (DaisyUI \uAE08\uC9C0)
356
356
  - Ui Component: @util/ui \uB77C\uC774\uBE0C\uB7EC\uB9AC \uC0AC\uC6A9
357
- - \uC870\uAC74\uBD80 \uD074\uB798\uC2A4: clsx \uB77C\uC774\uBE0C\uB7EC\uB9AC \uC0AC\uC6A9
357
+ - \uC870\uAC74\uBD80 \uD074\uB798\uC2A4: cn \uD568\uC218 \uC0AC\uC6A9 (akanjs/client, cond && 'x' \uD615\uD0DC \u2014 \uAC1D\uCCB4 \uBB38\uBC95 \uC5C6\uC74C)
358
358
  \uCF54\uB4DC \uC2A4\uD0C0\uC77C
359
359
  - \uC0C9\uC0C1: \uD558\uB4DC\uCF54\uB529(bg-red) \uB300\uC2E0 \uD14C\uB9C8 \uC0C9\uC0C1(bg-primary) \uC0AC\uC6A9
360
360
  - \uC870\uAC74\uBD80 \uB80C\uB354\uB9C1: field && <div>... \uB300\uC2E0 field ? <div>... : null \uC0AC\uC6A9
@@ -410,9 +410,9 @@ var requestView = ({
410
410
  \uCF54\uB529 \uADDC\uCE59
411
411
  - \uB77C\uC774\uBE0C\uB7EC\uB9AC \uC0AC\uC6A9
412
412
  - \uC544\uC774\uCF58: react-icons \uB77C\uC774\uBE0C\uB7EC\uB9AC \uC0AC\uC6A9
413
- - CSS: tailwind, DaisyUI(card/hero \uAC19\uC740 \uBCF5\uC7A1\uD55C \uCEF4\uD3EC\uB10C\uD2B8 \uC0AC\uC6A9 X) \uC0AC\uC6A9
413
+ - CSS: tailwind + akanjs/ui \uD504\uB9AC\uBBF8\uD2F0\uBE0C(Button/Badge/Input \uB4F1) + \uC2DC\uB9E8\uD2F1 \uD1A0\uD070(bg-background/text-foreground/bg-primary \uB4F1) \uC0AC\uC6A9 (DaisyUI \uAE08\uC9C0)
414
414
  - Ui Component: @util/ui \uB77C\uC774\uBE0C\uB7EC\uB9AC \uC0AC\uC6A9
415
- - \uC870\uAC74\uBD80 \uD074\uB798\uC2A4: clsx \uB77C\uC774\uBE0C\uB7EC\uB9AC \uC0AC\uC6A9
415
+ - \uC870\uAC74\uBD80 \uD074\uB798\uC2A4: cn \uD568\uC218 \uC0AC\uC6A9 (akanjs/client, cond && 'x' \uD615\uD0DC \u2014 \uAC1D\uCCB4 \uBB38\uBC95 \uC5C6\uC74C)
416
416
  \uCF54\uB4DC \uC2A4\uD0C0\uC77C
417
417
  - \uC0C9\uC0C1: \uD558\uB4DC\uCF54\uB529(bg-red) \uB300\uC2E0 \uD14C\uB9C8 \uC0C9\uC0C1(bg-primary) \uC0AC\uC6A9
418
418
  - \uC870\uAC74\uBD80 \uB80C\uB354\uB9C1: field && <div>... \uB300\uC2E0 field ? <div>... : null \uC0AC\uC6A9
@@ -471,9 +471,9 @@ var requestUnit = ({
471
471
  \uCF54\uB529 \uADDC\uCE59
472
472
  - \uB77C\uC774\uBE0C\uB7EC\uB9AC \uC0AC\uC6A9
473
473
  - \uC544\uC774\uCF58: react-icons \uB77C\uC774\uBE0C\uB7EC\uB9AC \uC0AC\uC6A9
474
- - CSS: tailwind, DaisyUI(card/hero \uAC19\uC740 \uBCF5\uC7A1\uD55C \uCEF4\uD3EC\uB10C\uD2B8 \uC0AC\uC6A9 X) \uC0AC\uC6A9
474
+ - CSS: tailwind + akanjs/ui \uD504\uB9AC\uBBF8\uD2F0\uBE0C(Button/Badge/Input \uB4F1) + \uC2DC\uB9E8\uD2F1 \uD1A0\uD070(bg-background/text-foreground/bg-primary \uB4F1) \uC0AC\uC6A9 (DaisyUI \uAE08\uC9C0)
475
475
  - Ui Component: @util/ui \uB77C\uC774\uBE0C\uB7EC\uB9AC \uC0AC\uC6A9
476
- - \uC870\uAC74\uBD80 \uD074\uB798\uC2A4: clsx \uB77C\uC774\uBE0C\uB7EC\uB9AC \uC0AC\uC6A9
476
+ - \uC870\uAC74\uBD80 \uD074\uB798\uC2A4: cn \uD568\uC218 \uC0AC\uC6A9 (akanjs/client, cond && 'x' \uD615\uD0DC \u2014 \uAC1D\uCCB4 \uBB38\uBC95 \uC5C6\uC74C)
477
477
  \uCF54\uB4DC \uC2A4\uD0C0\uC77C
478
478
  - \uC0C9\uC0C1: \uD558\uB4DC\uCF54\uB529(bg-red) \uB300\uC2E0 \uD14C\uB9C8 \uC0C9\uC0C1(bg-primary) \uC0AC\uC6A9
479
479
  - \uC870\uAC74\uBD80 \uB80C\uB354\uB9C1: field && <div>... \uB300\uC2E0 field ? <div>... : null \uC0AC\uC6A9
@@ -579,7 +579,7 @@ class ModuleRunner extends runner("module") {
579
579
  await module.sys.applyTemplate({
580
580
  basePath: `./lib/${module.name}`,
581
581
  template: `module/__Model__.${capitalize2(type)}.tsx`,
582
- dict: { model: module.name, appName: module.sys.name }
582
+ dict: { model: module.name, sysName: module.sys.name, sysType: module.sys.type }
583
583
  });
584
584
  return {
585
585
  component: {
@@ -612,6 +612,7 @@ class ModuleRunner extends runner("module") {
612
612
  model: module.name,
613
613
  models: names,
614
614
  sysName: module.sys.name,
615
+ sysType: module.sys.type,
615
616
  modelLabelEn: modelLabel.en,
616
617
  modelLabelKo: modelLabel.ko,
617
618
  modelDescEn: modelDescription.en,
@@ -0,0 +1,129 @@
1
+ // @bun
2
+ // pkgs/@akanjs/devkit/frontendBuild/styleContract.ts
3
+ var countBlocking = (v) => v.style.filter((s) => s.severity === "error").length + v.theme.length;
4
+ var formatStyleContract = (v) => {
5
+ const lines = [];
6
+ for (const s of v.style) {
7
+ lines.push(` [${s.severity}] ${s.rule} ${s.path}:${s.line}`);
8
+ lines.push(` ${s.snippet}`);
9
+ lines.push(` \u2192 ${s.suggestion}`);
10
+ }
11
+ for (const t of v.theme) {
12
+ lines.push(` [error] contrast ${t.scope} ${t.pair} = ${t.ratio}:1 (min ${t.threshold}:1)`);
13
+ lines.push(` \u2192 ${t.suggestion}`);
14
+ }
15
+ return lines.join(`
16
+ `);
17
+ };
18
+
19
+ // pkgs/@akanjs/devkit/frontendBuild/themeValidator.ts
20
+ var PAIRS = [
21
+ { base: "background", fg: "foreground", threshold: 4.5 },
22
+ { base: "primary", fg: "primary-foreground", threshold: 4.5 },
23
+ { base: "secondary", fg: "secondary-foreground", threshold: 4.5 },
24
+ { base: "accent", fg: "accent-foreground", threshold: 4.5 },
25
+ { base: "neutral", fg: "neutral-foreground", threshold: 4.5 },
26
+ { base: "card", fg: "card-foreground", threshold: 4.5 },
27
+ { base: "popover", fg: "popover-foreground", threshold: 4.5 },
28
+ { base: "muted", fg: "muted-foreground", threshold: 3 },
29
+ { base: "info", fg: "info-foreground", threshold: 3 },
30
+ { base: "success", fg: "success-foreground", threshold: 3 },
31
+ { base: "warning", fg: "warning-foreground", threshold: 3 },
32
+ { base: "destructive", fg: "destructive-foreground", threshold: 3 },
33
+ { base: "open", fg: "open-foreground", threshold: 3 }
34
+ ];
35
+ var THEME_SCOPES = new Set([":root", '[data-theme="dark"]', '[data-theme="light"]']);
36
+
37
+ class ThemeValidator {
38
+ validate(css) {
39
+ const tokensByScope = ThemeValidator.parseThemeTokens(css);
40
+ const violations = [];
41
+ for (const [scope, tokens] of Object.entries(tokensByScope)) {
42
+ if (!THEME_SCOPES.has(scope))
43
+ continue;
44
+ violations.push(...this.validateScope(tokens, scope));
45
+ }
46
+ return violations;
47
+ }
48
+ validateScope(tokens, scope) {
49
+ const violations = [];
50
+ for (const { base, fg, threshold } of PAIRS) {
51
+ const bg = tokens[base];
52
+ const front = tokens[fg];
53
+ if (!bg || !front)
54
+ continue;
55
+ const bgRgb = ThemeValidator.parseHex(bg);
56
+ const fgRgb = ThemeValidator.parseHex(front);
57
+ if (!bgRgb || !fgRgb)
58
+ continue;
59
+ const ratio = ThemeValidator.contrastRatio(bgRgb, fgRgb);
60
+ if (ratio >= threshold)
61
+ continue;
62
+ violations.push({
63
+ scope,
64
+ pair: `${base} / ${fg}`,
65
+ background: bg,
66
+ foreground: front,
67
+ ratio: Math.round(ratio * 100) / 100,
68
+ threshold,
69
+ suggestion: `${scope}\uC758 --${base}(${bg})\uC640 --${fg}(${front}) \uB300\uBE44\uAC00 ${ratio.toFixed(2)}:1 \uB85C \uCD5C\uC18C ${threshold}:1 \uBBF8\uB9CC\uC785\uB2C8\uB2E4. \uD55C\uCABD\uC744 \uB354 \uBC1D\uAC8C/\uC5B4\uB461\uAC8C \uC870\uC815\uD574 \uB300\uBE44\uB97C \uD655\uBCF4\uD558\uC138\uC694.`
70
+ });
71
+ }
72
+ return violations;
73
+ }
74
+ static parseThemeTokens(css) {
75
+ const result = {};
76
+ const blockRe = /(?:^|})\s*([^{}@]+?)\s*\{([^{}]*)\}/g;
77
+ for (const block of css.matchAll(blockRe)) {
78
+ const selectors = block[1].split(",").map((s) => s.trim());
79
+ const relevant = selectors.filter((s) => THEME_SCOPES.has(ThemeValidator.#normalizeScope(s)));
80
+ if (relevant.length === 0)
81
+ continue;
82
+ const decls = {};
83
+ for (const decl of block[2].matchAll(/--([\w-]+)\s*:\s*([^;]+?)\s*(?:;|$)/g)) {
84
+ decls[decl[1]] = decl[2].trim();
85
+ }
86
+ for (const selector of relevant) {
87
+ const scope = ThemeValidator.#normalizeScope(selector);
88
+ result[scope] = { ...result[scope] ?? {}, ...decls };
89
+ }
90
+ }
91
+ return result;
92
+ }
93
+ static #normalizeScope(selector) {
94
+ return selector.replace(/\[data-theme=['"]?([\w-]+)['"]?\]/g, '[data-theme="$1"]').trim();
95
+ }
96
+ static parseHex(value) {
97
+ const v = value.trim();
98
+ if (!v.startsWith("#"))
99
+ return null;
100
+ const hex = v.slice(1);
101
+ let full;
102
+ if (hex.length === 3 || hex.length === 4)
103
+ full = hex.slice(0, 3).split("").map((c) => c + c).join("");
104
+ else if (hex.length === 6 || hex.length === 8)
105
+ full = hex.slice(0, 6);
106
+ else
107
+ return null;
108
+ if (!/^[0-9a-fA-F]{6}$/.test(full))
109
+ return null;
110
+ const n = Number.parseInt(full, 16);
111
+ return [n >> 16 & 255, n >> 8 & 255, n & 255];
112
+ }
113
+ static #relativeLuminance([r, g, b]) {
114
+ const channel = (c) => {
115
+ const s = c / 255;
116
+ return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
117
+ };
118
+ return 0.2126 * channel(r) + 0.7152 * channel(g) + 0.0722 * channel(b);
119
+ }
120
+ static contrastRatio(a, b) {
121
+ const la = ThemeValidator.#relativeLuminance(a);
122
+ const lb = ThemeValidator.#relativeLuminance(b);
123
+ const hi = Math.max(la, lb);
124
+ const lo = Math.min(la, lb);
125
+ return (hi + 0.05) / (lo + 0.05);
126
+ }
127
+ }
128
+
129
+ export { countBlocking, formatStyleContract, ThemeValidator };
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  import {
3
3
  getDirname
4
- } from "./index-xwfg9bam.js";
4
+ } from "./index-jtm86byd.js";
5
5
 
6
6
  // pkgs/@akanjs/devkit/prompter.ts
7
7
  import fsPromise from "fs/promises";