@akanjs/cli 2.4.2-rc.3 → 3.0.0-alpha.1
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.
- package/.build-stamp +1 -1
- package/{abstractCompactor-ghh1q9an.js → abstractCompactor-e5yq60qz.js} +4 -4
- package/{agent.command-de4r6vk8.js → agent.command-yv00p0s0.js} +8 -7
- package/{application.command-nfsfm96y.js → application.command-0qsczf4k.js} +6 -6
- package/{applicationBuildRunner-554a2vnk.js → applicationBuildRunner-ct2pxyvw.js} +3 -2
- package/{applicationReleasePackager-pjbxc793.js → applicationReleasePackager-gg8ccbeh.js} +3 -3
- package/buildBatch.proc.js +8 -7
- package/{capacitorApp-yg9b4ajz.js → capacitorApp-4r9sm3qg.js} +3 -3
- package/{cloud.command-1qf95x67.js → cloud.command-dxfv18sa.js} +10 -10
- package/{context.command-hhjyjtp7.js → context.command-8d9e3k6b.js} +16 -15
- package/{guideline.command-vj956kfr.js → guideline.command-3jbkxzh6.js} +10 -10
- package/guidelines/componentRule/componentRule.instruction.md +2 -2
- package/guidelines/cssRule/cssRule.generate.json +1 -1
- package/guidelines/cssRule/cssRule.instruction.md +57 -34
- package/guidelines/framework/framework.instruction.md +1 -1
- package/guidelines/recipeRule/recipeRule.generate.json +31 -0
- package/guidelines/recipeRule/recipeRule.instruction.md +72 -0
- package/guidelines/ssrRule/ssrRule.generate.json +31 -0
- package/guidelines/ssrRule/ssrRule.instruction.md +108 -0
- package/incrementalBuilder.proc.js +6 -6
- package/{index-nsj2ftxj.js → index-0cj2zxbm.js} +3 -3
- package/{index-y1e79mz3.js → index-1pvex4az.js} +13 -13
- package/{index-h971erkm.js → index-2q1f5j9k.js} +3 -3
- package/{index-s9s0n5fz.js → index-7c6k4yvg.js} +2 -2
- package/{index-d0h48d2f.js → index-8hsbnyes.js} +1 -1
- package/{index-v9jqrdd1.js → index-a00jg8x8.js} +100 -2
- package/{index-dgeaqwmw.js → index-bbkek1bp.js} +1 -1
- package/{index-acwxfncv.js → index-bekddz9q.js} +4 -4
- package/{index-fbv96xaw.js → index-e16gnn26.js} +39 -16
- package/{index-z5v766nx.js → index-ec09pws3.js} +6 -6
- package/{index-zn9rh7sh.js → index-ej426mcr.js} +73 -24
- package/{index-xwfg9bam.js → index-hgna5rjy.js} +120 -4
- package/{index-z36qzgzp.js → index-k95xkh4p.js} +3 -4
- package/{index-7ppn9v4n.js → index-mr5s7mqd.js} +3 -3
- package/{index-6pz1j0zj.js → index-mxvakhsm.js} +6 -2
- package/{index-h6eav7zx.js → index-nm6wghb6.js} +6 -6
- package/{index-7k9sj7j5.js → index-pyt2b4nw.js} +212 -0
- package/{index-pfnh87f9.js → index-rzwcpcc3.js} +1 -1
- package/{index-zfp1gz9f.js → index-t3723f99.js} +1 -1
- package/{index-xftstr5s.js → index-tdbas9r0.js} +1 -1
- package/index-xjd3rqzj.js +129 -0
- package/index-xys926f2.js +136 -0
- package/{index-04as43rp.js → index-yzmhasdb.js} +16 -15
- package/{index-h5cvg9z9.js → index-zasydjxd.js} +4 -4
- package/index.js +19 -19
- package/{library.command-68ah07a3.js → library.command-ebdj1vnm.js} +4 -4
- package/{localRegistry.command-84dz000g.js → localRegistry.command-gb178ne8.js} +9 -9
- package/{module.command-t68xhamx.js → module.command-9c1bnsx4.js} +8 -8
- package/{package.command-00ncttab.js → package.command-t93p4jkj.js} +4 -4
- package/package.json +2 -3
- package/{page.command-1096s18n.js → page.command-k3k0meh2.js} +4 -4
- package/{primitive.command-02eqpx6t.js → primitive.command-de6nktdt.js} +9 -9
- package/{quality.command-r9cageqb.js → quality.command-z5kth2wa.js} +451 -48
- package/recipeScanner-tghkzrrt.js +12 -0
- package/{repair.command-7dynpnf1.js → repair.command-ehf2g112.js} +7 -6
- package/{scalar.command-3kxy3fkh.js → scalar.command-7a2jtv33.js} +7 -7
- package/templates/app/page/_index.tsx +55 -52
- package/templates/app/page/styles.css.template +12 -71
- package/templates/appSample/lib/task/Task.Template.tsx +2 -2
- package/templates/appSample/lib/task/Task.Unit.tsx +11 -11
- package/templates/appSample/lib/task/Task.Util.tsx +27 -28
- package/templates/appSample/lib/task/Task.View.tsx +22 -19
- package/templates/appSample/lib/task/Task.Zone.tsx +3 -3
- package/templates/appSample/page/task/[taskId]/_index.tsx +3 -3
- package/templates/appSample/page/task/[taskId]/edit.tsx +5 -5
- package/templates/appSample/page/task/_index.tsx +4 -4
- package/templates/appSample/page/task/_layout.tsx +1 -1
- package/templates/appSample/page/task/new.tsx +5 -5
- package/templates/appSample/ui/GlobalLoading.tsx +4 -4
- package/templates/appSample/ui/QuantityControl.tsx +5 -4
- package/templates/crudPages/[__model__Id]/page.tsx +1 -1
- package/templates/crudPages/page.tsx +1 -1
- package/templates/crudSinglePage/page.tsx +1 -1
- package/templates/module/__Model__.Template.tsx +2 -1
- package/templates/module/__Model__.Unit.tsx +2 -1
- package/templates/module/__Model__.Util.tsx +2 -1
- package/templates/module/__Model__.View.tsx +4 -3
- package/templates/module/__Model__.Zone.tsx +2 -1
- package/templates/workspaceRoot/.cursor/rules/client-server-boundaries.mdc.template +1 -0
- package/templates/workspaceRoot/.cursor/rules/ssr-first.mdc.template +59 -0
- package/templates/workspaceRoot/AGENTS.md.template +139 -20
- package/templates/workspaceRoot/biome.json.template +1 -1
- package/{workflow.command-yy0d9gk3.js → workflow.command-e0tt0vk2.js} +15 -14
- package/{workspace.command-stv9g4a0.js → workspace.command-fpda0evj.js} +87 -27
|
@@ -74,11 +74,12 @@ Use the local module shape before adding a new abstraction.
|
|
|
74
74
|
9. Keep page and layout files server-oriented: route-level `usePage`, headers, static layout, and `akanjs/ui` `Tab` composition belong there.
|
|
75
75
|
10. Put interactive loading, submit/import actions, local form state, and `router.push` in client `Util`, `Template`, or `Zone` components.
|
|
76
76
|
11. Prefer `Tab` for static mode selection instead of extra `useState`; render one focused client component inside each `Tab.Panel`.
|
|
77
|
-
12.
|
|
78
|
-
13.
|
|
79
|
-
14.
|
|
80
|
-
15.
|
|
81
|
-
16.
|
|
77
|
+
12. Default to server rendering. Add `"use client"` only for a real client-only capability, and keep markup out of the component that carries it — see **SSR First** below.
|
|
78
|
+
13. Keep server-only logic out of client surfaces and client-only code out of server imports.
|
|
79
|
+
14. Treat `AKAN_PUBLIC_*` env vars as public. Never put secrets in them.
|
|
80
|
+
15. Add or update tests when behavior, contracts, or CLI output changes.
|
|
81
|
+
16. Update `*.abstract.md` when business invariants, workflows, or public behavior change.
|
|
82
|
+
17. Run the smallest relevant verification command after changes. After touching any `.tsx`, that includes `akan quality ssr`.
|
|
82
83
|
|
|
83
84
|
## Common Commands
|
|
84
85
|
|
|
@@ -111,7 +112,11 @@ akan typecheck <%= appName %>
|
|
|
111
112
|
# 3. Test — Run the test code (lib/*/*.signal.test.ts or others)
|
|
112
113
|
akan test <%= appName %>
|
|
113
114
|
|
|
114
|
-
# 4.
|
|
115
|
+
# 4. SSR balance — only after touching .tsx files. Reports the server render share
|
|
116
|
+
# per app/lib and flags client code that should render on the server.
|
|
117
|
+
akan quality ssr
|
|
118
|
+
|
|
119
|
+
# 5. Full production build — bundles the app, runs all type/lint checks combined
|
|
115
120
|
akan build <%= appName %>
|
|
116
121
|
```
|
|
117
122
|
|
|
@@ -128,6 +133,8 @@ akan create-scalar <scalar-name> <%= appName %> # Add a scalar module (
|
|
|
128
133
|
akan create-service <service-name> <%= appName %> # Add a service module (lib/_<service-name>/)
|
|
129
134
|
akan test <%= appName %> # Run the test code (lib/*/*.signal.test.ts or others)
|
|
130
135
|
akan lint <%= appName %> # Lint only (no typecheck)
|
|
136
|
+
akan quality scan # All code-quality warnings + SSR balance
|
|
137
|
+
akan quality ssr # SSR balance and client-boundary warnings only
|
|
131
138
|
```
|
|
132
139
|
|
|
133
140
|
**CLI argument conventions.** Two argument styles, and mixing them up is a common mistake:
|
|
@@ -239,8 +246,8 @@ final fallback when no CLI command covers the change.
|
|
|
239
246
|
| Add a pure workflow / integration (e.g., Payment, Email) | `lib/_<service>/` → service, signal, store, dictionary, abstract | `akan sync <name>` |
|
|
240
247
|
| Add a reusable value type (e.g., Address, WorkHistory) | `lib/__scalar/<type>/` → constant, dictionary, abstract | `akan sync <name>` |
|
|
241
248
|
| Create a new URL-visitable page | `page/` → `_index.tsx`, `_layout.tsx`, `[param]/_index.tsx` | Rebuild (akan start auto-detects) |
|
|
242
|
-
| Change the app color theme / design tokens | `apps/<app>/page/styles.css` →
|
|
243
|
-
| Add a form or reusable UI component | `ui/` → PascalCase `.tsx
|
|
249
|
+
| Change the app color theme / design tokens | `apps/<app>/page/styles.css` → override the semantic token values under `:root, [data-theme="dark"]` and `[data-theme="light"]` (`--primary`, `--background`, `--foreground`, …) | akan start hot-reloads |
|
|
250
|
+
| Add a form or reusable UI component | `ui/` → PascalCase `.tsx`, **no** `"use client"` unless it uses a hook, event handler, store, or browser API | `akan sync <name>` |
|
|
244
251
|
| Add a React hook or browser helper | `webkit/` → camelCase `.ts` with `"use client"` | `akan sync <name>` |
|
|
245
252
|
| Add a server-only guard, middleware, or adaptor | `srvkit/` → PascalCase `.ts` | `akan sync <name>` |
|
|
246
253
|
| Add a pure helper (no DOM, no server API) | `common/` → camelCase `.ts` | `akan sync <name>` |
|
|
@@ -261,6 +268,120 @@ final fallback when no CLI command covers the change.
|
|
|
261
268
|
| Use "use client" or `useState`/`useEffect` in pages/*.tsx, *.Unit.tsx, and *.View.tsx files | Server code cannot use React hooks. Wrap in a separate `"use client"` component. | Move hook logic to `webkit/` or a `"use client"` UI component |
|
|
262
269
|
| Use `<a>` tag for internal navigation between pages | Akan.js uses `<Link>` from `akanjs/ui` for client-side navigation — avoids full page reloads. | `import { Link } from "akanjs/ui"` and use `<Link href="/task">...</Link>` |
|
|
263
270
|
| Name a custom `Endpoint`/`Slice` like a generated CRUD op — `create<Model>`, `update<Model>`, `remove<Model>`, `view<Model>`, `edit<Model>`, `merge<Model>` | These names are already auto-generated. A collision can pass sync/typecheck/build and only fail at runtime. | Pick a distinct verb, e.g. `startTask`/`archiveTask`, never `createTask` for a custom endpoint |
|
|
271
|
+
| Add `"use client"` to a file that renders markup but uses no hook, event handler, store, or browser API | The directive is a cost, not a formality: everything behind it ships twice, as HTML and as JS that re-runs in the browser. | Delete the directive. `akan quality ssr` flags this as `akan.ssr.unnecessary-use-client` |
|
|
272
|
+
| Put a large static subtree inside a client component that only needs one `onClick` | The whole subtree lands in the bundle for the sake of one handler. | Keep the handler in a small client shell and pass the static markup in as `children`, or move it to a `Unit`/`View` |
|
|
273
|
+
| Load server data with `useEffect(() => { void st.do.initTaskInTodo(); }, [])` | The page renders an empty shell, hydrates, then round-trips for data the server already had. | `await fetch.initTaskInTodo()` in the page and pass `init` to the `Zone` |
|
|
274
|
+
|
|
275
|
+
## SSR First — Server Rendering Is The Default
|
|
276
|
+
|
|
277
|
+
Akan is SSR-first. Every JSX element that renders on the server ships as HTML and costs nothing to hydrate; every
|
|
278
|
+
element behind `"use client"` ships twice — as markup and as bundled JS that must re-run in the browser. The
|
|
279
|
+
boundary question is not which file *may* be client, it is **how little** ends up on the client side.
|
|
280
|
+
|
|
281
|
+
**The default is server. `"use client"` is a cost you justify per component, not a habit.** A component earns the
|
|
282
|
+
directive only by using a client-only capability: a React hook, a JSX event handler, the store (`st.use.*` /
|
|
283
|
+
`st.do.*`), a browser global, or a client-only third-party package. Rendering markup, mapping over data, reading a
|
|
284
|
+
route param, and calling `l()` are all server work — `usePage()` and `l()` are legal in server components and never
|
|
285
|
+
force a boundary.
|
|
286
|
+
|
|
287
|
+
The file role decides *where* the boundary sits (`Template`/`Zone`/`Util` are client, `Unit`/`View` are server); it
|
|
288
|
+
does not decide how much markup sits behind it. A `Zone` that hoards markup is still an SSR regression.
|
|
289
|
+
|
|
290
|
+
Measure with `akan quality ssr` (`--format json` for tooling). It reports the server render share per app and lib —
|
|
291
|
+
server-rendered JSX elements over total — plus the warnings below. Treat **50% server share as the floor** and a
|
|
292
|
+
falling share as a regression to justify or revert.
|
|
293
|
+
|
|
294
|
+
| Rule | Means |
|
|
295
|
+
|---|---|
|
|
296
|
+
| `akan.ssr.unnecessary-use-client` | The directive is there but nothing in the file needs it. Delete it. |
|
|
297
|
+
| `akan.ssr.client-static-component` | A component in a client file renders real markup with zero client-only capability — pure server work sitting in the bundle. |
|
|
298
|
+
| `akan.ssr.client-static-markup` | A large subtree wraps one or two interactive touches. Split it: interaction stays client, markup goes server. |
|
|
299
|
+
| `akan.ssr.client-mount-load` | A `useEffect(…, [])` loads server data. The page can fetch it before the first byte. |
|
|
300
|
+
| `akan.ssr.module-missing-server-view` | A module renders only from `Template`/`Zone`/`Util` and has no `Unit`/`View` at all. |
|
|
301
|
+
| `akan.ssr.template-client-state` | A `Template` holds form state in `useState` instead of the store. |
|
|
302
|
+
|
|
303
|
+
A third-party client package and the `ui/<Folder>/index_.tsx` + `lazy()` boundary both justify the directive and are
|
|
304
|
+
not flagged. Interaction-driven `fetch.*` inside an `onClick` is not flagged either — only mount-time loads are,
|
|
305
|
+
because those are the ones the server could have performed.
|
|
306
|
+
|
|
307
|
+
### Server-Side Implementation Playbook
|
|
308
|
+
|
|
309
|
+
**① Wrap the interaction, not the UI.** The smallest useful client component adds one behaviour and renders
|
|
310
|
+
`children` untouched, so the markup inside never reaches the bundle.
|
|
311
|
+
|
|
312
|
+
```tsx
|
|
313
|
+
// apps/<%= appName %>/ui/ClickWrapper.tsx
|
|
314
|
+
"use client";
|
|
315
|
+
export const ClickWrapper = ({ children, onPick }: ClickWrapperProps) => <div onClick={onPick}>{children}</div>;
|
|
316
|
+
|
|
317
|
+
// in a server page — Task.Unit.Card stays server-rendered
|
|
318
|
+
<ClickWrapper onPick={…}>
|
|
319
|
+
<Task.Unit.Card task={task} />
|
|
320
|
+
</ClickWrapper>
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
**② Split compound components so panels stay on the server.** A tab, accordion, or disclosure needs client state
|
|
324
|
+
only for *which* part is visible — never for what the parts contain. `Tab` / `Tab.Menus` / `Tab.Menu` / `Tab.Panel`
|
|
325
|
+
from `akanjs/ui` is the reference shape: only the provider and menu hold state, and `<Tab.Panel>` renders its
|
|
326
|
+
children as-is, so a server `Unit`/`View` passed in stays server-rendered.
|
|
327
|
+
|
|
328
|
+
```tsx
|
|
329
|
+
<Tab defaultMenu="detail">
|
|
330
|
+
<Tab.Menus>
|
|
331
|
+
<Tab.Menu menu="detail">{l("task.detail")}</Tab.Menu>
|
|
332
|
+
<Tab.Menu menu="history">{l("task.history")}</Tab.Menu>
|
|
333
|
+
</Tab.Menus>
|
|
334
|
+
<Tab.Panel menu="detail">
|
|
335
|
+
<Task.View.General task={task} />
|
|
336
|
+
</Tab.Panel>
|
|
337
|
+
</Tab>
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
Never collapse this into one `"use client"` file with a mode `useState` and every panel body inlined.
|
|
341
|
+
|
|
342
|
+
**③ Sync state instead of fetching it.** A server component cannot hold state, so render the initial data on the
|
|
343
|
+
server and hand it across the boundary as a serializable object. That is what `init` / `view` props are: the page
|
|
344
|
+
calls `fetch.initTaskInTodo()` / `fetch.viewTask(id)`, passes the result into a `Zone`, and `Load.Units` /
|
|
345
|
+
`Load.View` hydrate the store from it.
|
|
346
|
+
|
|
347
|
+
```tsx
|
|
348
|
+
export default async function Page() {
|
|
349
|
+
const [{ taskInitInTodo }] = await Promise.all([fetch.initTaskInTodo()]);
|
|
350
|
+
return <Task.Zone.Card init={taskInitInTodo} sliceName="taskInTodo" />;
|
|
351
|
+
}
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
**④ Push the boundary down to the leaf that needs it.** When a `Zone` reads the store, it should hold *zero* markup
|
|
355
|
+
and delegate to a server `View`, so the whole detail surface renders server-side wherever a page uses the `View`
|
|
356
|
+
directly.
|
|
357
|
+
|
|
358
|
+
```tsx
|
|
359
|
+
"use client";
|
|
360
|
+
export const Self = () => <Task.View.General task={st.use.task()} />;
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
**⑤ Hand the promise across, not the awaited value.** `ClientInit` / `ClientView` are `PromiseOrObject<T>`, so a
|
|
364
|
+
page may pass an unawaited `fetch.initTaskInTodo()` and `Load.*` resolves it behind a skeleton. `await` blocks the
|
|
365
|
+
shell for data the page needs immediately; passing the promise streams the rest. Independent fetches still go
|
|
366
|
+
through one `Promise.all`.
|
|
367
|
+
|
|
368
|
+
**⑥ Use named `ReactNode` slots, not just `children`.** A client shell can take several server-rendered subtrees:
|
|
369
|
+
`Layout.Navbar` accepts `title`, `back`, `left`, `right`, and `children`, so it composes server content in five
|
|
370
|
+
places instead of absorbing it.
|
|
371
|
+
|
|
372
|
+
**⑦ Let the server do the derived work.** Display and predicate logic belongs on `LightTask` (`isNew()`,
|
|
373
|
+
`canWrite(user?)`, `formatTimes()`), and enum→class lookups belong in a module-scope `as const` map. Both sides call
|
|
374
|
+
the same method, so a client component that exists only to compute a label is markup in the wrong place.
|
|
375
|
+
|
|
376
|
+
**⑧ Gate auth on the server.** Check the session in `_layout.tsx` and redirect there, before any HTML is sent. A
|
|
377
|
+
client-side auth check costs a hydration round-trip and flashes the wrong UI first.
|
|
378
|
+
|
|
379
|
+
**⑨ Prefer CSS over client state for pure visibility.** A `data-*` attribute plus `group-data-[…]` variants, or
|
|
380
|
+
`<details>`/`<summary>`, keeps both branches server-rendered. Reach for `useState` when the state is real, not when
|
|
381
|
+
a Tailwind variant would do.
|
|
382
|
+
|
|
383
|
+
**⑩ Keep the heavy island out of the first load.** A large client-only widget goes behind the
|
|
384
|
+
`ui/<Folder>/index_.tsx` + `lazy()` pair so the server renders the page around it.
|
|
264
385
|
|
|
265
386
|
## Code Style
|
|
266
387
|
|
|
@@ -439,11 +560,9 @@ const form = st.use.taskForm();
|
|
|
439
560
|
|
|
440
561
|
// 4. apps/<app>/lib/<model>/<Model>.Unit.tsx
|
|
441
562
|
// Display the new field in card/list views
|
|
442
|
-
<
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
"badge-ghost": task.priority === "low",
|
|
446
|
-
})}>{task.priority}</span>
|
|
563
|
+
<Badge variant={task.priority === "high" ? "error" : task.priority === "medium" ? "warning" : "default"}>
|
|
564
|
+
{task.priority}
|
|
565
|
+
</Badge>
|
|
447
566
|
|
|
448
567
|
// 5. Regenerate barrels
|
|
449
568
|
// akan sync <name>
|
|
@@ -632,7 +751,7 @@ async startTask(taskId: string) {
|
|
|
632
751
|
|
|
633
752
|
// 6. <Model>.Util.tsx — Reusable button component
|
|
634
753
|
export const Start = ({ taskId }: { taskId: string }) => (
|
|
635
|
-
<button className=
|
|
754
|
+
<button className={buttonRecipe({ variant: "primary", size: "xs" })} onClick={() => st.do.startTask(taskId)}>
|
|
636
755
|
Start
|
|
637
756
|
</button>
|
|
638
757
|
);
|
|
@@ -694,13 +813,13 @@ export class TaskInsight extends via(Task, (field) => ({
|
|
|
694
813
|
// 3. <Model>.View.tsx — Display component consuming an Insight model
|
|
695
814
|
export const Stats = ({ taskInsight }: { taskInsight: cnst.TaskInsight }) => (
|
|
696
815
|
<div className="grid grid-cols-2 gap-4">
|
|
697
|
-
<div className="
|
|
698
|
-
<div className="
|
|
699
|
-
<div className="
|
|
816
|
+
<div className="rounded-lg border border-border bg-card p-4">
|
|
817
|
+
<div className="text-muted-foreground text-sm">{l("task.totalCount")}</div>
|
|
818
|
+
<div className="font-bold text-2xl text-primary">{taskInsight.totalCount}</div>
|
|
700
819
|
</div>
|
|
701
|
-
<div className="
|
|
702
|
-
<div className="
|
|
703
|
-
<div className="
|
|
820
|
+
<div className="rounded-lg border border-border bg-card p-4">
|
|
821
|
+
<div className="text-muted-foreground text-sm">{l("task.completedCount")}</div>
|
|
822
|
+
<div className="font-bold text-2xl text-success">{taskInsight.completedCount}</div>
|
|
704
823
|
</div>
|
|
705
824
|
</div>
|
|
706
825
|
);
|
|
@@ -1,32 +1,33 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import {
|
|
3
3
|
WorkflowRunner
|
|
4
|
-
} from "./index-
|
|
5
|
-
import {
|
|
6
|
-
ScalarScript
|
|
7
|
-
} from "./index-z5v766nx.js";
|
|
4
|
+
} from "./index-2q1f5j9k.js";
|
|
8
5
|
import {
|
|
9
6
|
PrimitiveScript
|
|
10
|
-
} from "./index-
|
|
7
|
+
} from "./index-bekddz9q.js";
|
|
11
8
|
import {
|
|
12
9
|
ModuleScript
|
|
13
|
-
} from "./index-
|
|
14
|
-
import"./index-
|
|
10
|
+
} from "./index-yzmhasdb.js";
|
|
11
|
+
import"./index-bbkek1bp.js";
|
|
12
|
+
import {
|
|
13
|
+
ScalarScript
|
|
14
|
+
} from "./index-ec09pws3.js";
|
|
15
15
|
import"./index-ss469dec.js";
|
|
16
|
-
import"./index-
|
|
16
|
+
import"./index-0cj2zxbm.js";
|
|
17
|
+
import"./index-a00jg8x8.js";
|
|
17
18
|
import {
|
|
18
19
|
createWorkflowStepRegistry
|
|
19
|
-
} from "./index-
|
|
20
|
-
import"./index-
|
|
21
|
-
import"./index-nsj2ftxj.js";
|
|
20
|
+
} from "./index-rzwcpcc3.js";
|
|
21
|
+
import"./index-tdbas9r0.js";
|
|
22
22
|
import {
|
|
23
23
|
Workspace,
|
|
24
24
|
command,
|
|
25
25
|
script
|
|
26
|
-
} from "./index-
|
|
27
|
-
import"./index-
|
|
26
|
+
} from "./index-8hsbnyes.js";
|
|
27
|
+
import"./index-hgna5rjy.js";
|
|
28
|
+
import"./index-mxvakhsm.js";
|
|
29
|
+
import"./index-xys926f2.js";
|
|
28
30
|
import"./index-67546d0j.js";
|
|
29
|
-
import"./index-6pz1j0zj.js";
|
|
30
31
|
import"./index-r24hmh0q.js";
|
|
31
32
|
|
|
32
33
|
// pkgs/@akanjs/cli/workflow/workflow.script.ts
|
|
@@ -1,61 +1,74 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import {
|
|
3
3
|
ContextScript
|
|
4
|
-
} from "./index-
|
|
5
|
-
import"./index-
|
|
6
|
-
import"./index-
|
|
4
|
+
} from "./index-1pvex4az.js";
|
|
5
|
+
import"./index-mr5s7mqd.js";
|
|
6
|
+
import"./index-2q1f5j9k.js";
|
|
7
|
+
import"./index-bekddz9q.js";
|
|
8
|
+
import"./index-yzmhasdb.js";
|
|
9
|
+
import"./index-bbkek1bp.js";
|
|
7
10
|
import {
|
|
8
11
|
CloudScript
|
|
9
|
-
} from "./index-
|
|
10
|
-
import"./index-z5v766nx.js";
|
|
11
|
-
import"./index-acwxfncv.js";
|
|
12
|
-
import"./index-04as43rp.js";
|
|
13
|
-
import"./index-dgeaqwmw.js";
|
|
14
|
-
import"./index-ss469dec.js";
|
|
15
|
-
import"./index-zfp1gz9f.js";
|
|
16
|
-
import {
|
|
17
|
-
AgentScript
|
|
18
|
-
} from "./index-zn9rh7sh.js";
|
|
19
|
-
import"./index-v9jqrdd1.js";
|
|
20
|
-
import"./index-pfnh87f9.js";
|
|
21
|
-
import"./index-xftstr5s.js";
|
|
12
|
+
} from "./index-nm6wghb6.js";
|
|
22
13
|
import {
|
|
23
14
|
getLatestPackageVersion,
|
|
24
15
|
getNpmRegistryUrl
|
|
25
|
-
} from "./index-
|
|
16
|
+
} from "./index-zasydjxd.js";
|
|
26
17
|
import {
|
|
27
18
|
PackageScript
|
|
28
|
-
} from "./index-
|
|
19
|
+
} from "./index-k95xkh4p.js";
|
|
20
|
+
import"./index-ec09pws3.js";
|
|
21
|
+
import"./index-ss469dec.js";
|
|
22
|
+
import"./index-t3723f99.js";
|
|
29
23
|
import {
|
|
30
24
|
GlobalConfig
|
|
31
|
-
} from "./index-
|
|
25
|
+
} from "./index-0cj2zxbm.js";
|
|
26
|
+
import {
|
|
27
|
+
AgentScript
|
|
28
|
+
} from "./index-ej426mcr.js";
|
|
29
|
+
import"./index-a00jg8x8.js";
|
|
30
|
+
import"./index-rzwcpcc3.js";
|
|
31
|
+
import"./index-tdbas9r0.js";
|
|
32
32
|
import {
|
|
33
33
|
ApplicationScript
|
|
34
|
-
} from "./index-
|
|
34
|
+
} from "./index-e16gnn26.js";
|
|
35
35
|
import"./index-76rn3g2c.js";
|
|
36
36
|
import {
|
|
37
37
|
LibraryScript
|
|
38
|
-
} from "./index-
|
|
38
|
+
} from "./index-7c6k4yvg.js";
|
|
39
39
|
import {
|
|
40
40
|
Exec,
|
|
41
41
|
Workspace,
|
|
42
42
|
command,
|
|
43
43
|
runner,
|
|
44
44
|
script
|
|
45
|
-
} from "./index-
|
|
45
|
+
} from "./index-8hsbnyes.js";
|
|
46
46
|
import"./index-fgc8r6dj.js";
|
|
47
|
+
import {
|
|
48
|
+
ThemeValidator,
|
|
49
|
+
countBlocking,
|
|
50
|
+
formatStyleContract
|
|
51
|
+
} from "./index-xjd3rqzj.js";
|
|
52
|
+
import"./index-bjpxzr6s.js";
|
|
47
53
|
import {
|
|
48
54
|
AppExecutor,
|
|
49
55
|
LibExecutor,
|
|
50
56
|
PkgExecutor,
|
|
51
|
-
|
|
52
|
-
|
|
57
|
+
SysExecutor,
|
|
58
|
+
WorkspaceExecutor,
|
|
59
|
+
collectScopeRecipeSources,
|
|
60
|
+
extractAgentBlock,
|
|
61
|
+
renderScopeAgentBlock
|
|
62
|
+
} from "./index-hgna5rjy.js";
|
|
63
|
+
import"./index-mxvakhsm.js";
|
|
64
|
+
import {
|
|
65
|
+
collectRecipeSources,
|
|
66
|
+
scanRecipes
|
|
67
|
+
} from "./index-xys926f2.js";
|
|
53
68
|
import"./index-46tjzh6s.js";
|
|
54
69
|
import {
|
|
55
70
|
FileSys
|
|
56
71
|
} from "./index-67546d0j.js";
|
|
57
|
-
import"./index-bjpxzr6s.js";
|
|
58
|
-
import"./index-6pz1j0zj.js";
|
|
59
72
|
import"./index-r24hmh0q.js";
|
|
60
73
|
|
|
61
74
|
// pkgs/@akanjs/cli/workspace/workspace.script.ts
|
|
@@ -69,7 +82,6 @@ var defaultWorkspacePeerDependencies = new Set([
|
|
|
69
82
|
"@use-gesture/react",
|
|
70
83
|
"chance",
|
|
71
84
|
"croner",
|
|
72
|
-
"daisyui",
|
|
73
85
|
"react",
|
|
74
86
|
"react-dom",
|
|
75
87
|
"react-icons",
|
|
@@ -231,6 +243,54 @@ class WorkspaceRunner extends runner("workspace") {
|
|
|
231
243
|
"--no-errors-on-unmatched",
|
|
232
244
|
exec.cwdPath
|
|
233
245
|
]);
|
|
246
|
+
await this.#enforceStyleContract(exec);
|
|
247
|
+
await this.#enforceRecipeGate(exec);
|
|
248
|
+
await this.#enforceAgentsIndex(exec);
|
|
249
|
+
}
|
|
250
|
+
async#enforceAgentsIndex(exec) {
|
|
251
|
+
if (!(exec instanceof SysExecutor))
|
|
252
|
+
return;
|
|
253
|
+
const scope = { type: exec.type, name: exec.name };
|
|
254
|
+
const scanInfo = await exec.scan({ write: false });
|
|
255
|
+
const sources = await collectScopeRecipeSources(exec.workspace.workspaceRoot, scope, scanInfo.getScanResult().libDeps);
|
|
256
|
+
const expected = renderScopeAgentBlock(scope, scanRecipes(sources));
|
|
257
|
+
const existing = await exec.exists("AGENTS.md") ? await exec.readFile("AGENTS.md") : null;
|
|
258
|
+
const actual = existing ? extractAgentBlock(existing) : null;
|
|
259
|
+
if (actual === expected.trim())
|
|
260
|
+
return;
|
|
261
|
+
throw new Error(`[agentsIndex] ${scope.type}s/${scope.name}/AGENTS.md \uC758 recipe \uC0C9\uC778\uC774 \uC18C\uC2A4\uC640 \uB2E4\uB985\uB2C8\uB2E4(stale). ` + `\`akan sync ${scope.name}\` \uC744 \uC2E4\uD589\uD574 \uC7AC\uC0DD\uC131\uD558\uC138\uC694.`);
|
|
262
|
+
}
|
|
263
|
+
async#enforceRecipeGate(exec) {
|
|
264
|
+
const cwdPath = exec.cwdPath;
|
|
265
|
+
if (!cwdPath)
|
|
266
|
+
return;
|
|
267
|
+
const uiDir = path.join(cwdPath, "ui");
|
|
268
|
+
const sources = [
|
|
269
|
+
...await collectRecipeSources(uiDir, "ui/Recipe"),
|
|
270
|
+
...await collectRecipeSources(uiDir, "ui/recipe", "recipe")
|
|
271
|
+
];
|
|
272
|
+
if (sources.length === 0)
|
|
273
|
+
return;
|
|
274
|
+
const offenders = scanRecipes(sources).filter((recipe) => !Object.values(recipe.variants).some((values) => values.length >= 2 || values.length === 1 && values[0] === "true"));
|
|
275
|
+
if (offenders.length === 0)
|
|
276
|
+
return;
|
|
277
|
+
throw new Error(`[recipeGate] variant-less recipe(s): ${offenders.map((recipe) => recipe.name).join(", ")}
|
|
278
|
+
` + "\uACE0\uB97C \uC635\uC158 \uC5C6\uB294 look \uC740 recipe \uB85C \uB450\uC9C0 \uB9C8\uC138\uC694 \u2014 \uC790\uAE30 \uB9C8\uD06C\uC5C5\uC774 \uC788\uC73C\uBA74 \uCEF4\uD3EC\uB10C\uD2B8\uB85C \uC2B9\uACA9\uD558\uACE0, " + "\uB2E4\uB978 \uCEF4\uD3EC\uB10C\uD2B8\uC758 className \uC5D0 \uC8FC\uC785\uD558\uB294 \uC2A4\uD0A8\uC774\uBA74 \uACF5\uC720 \uD074\uB798\uC2A4 \uC0C1\uC218\uB85C \uB450\uC138\uC694.");
|
|
279
|
+
}
|
|
280
|
+
async#enforceStyleContract(exec) {
|
|
281
|
+
const cwdPath = exec.cwdPath;
|
|
282
|
+
if (!cwdPath)
|
|
283
|
+
return;
|
|
284
|
+
const stylesCssPath = path.join(cwdPath, "page", "styles.css");
|
|
285
|
+
const theme = await Bun.file(stylesCssPath).exists() ? new ThemeValidator().validate(await Bun.file(stylesCssPath).text()) : [];
|
|
286
|
+
const violations = { style: [], theme };
|
|
287
|
+
const blocking = countBlocking(violations);
|
|
288
|
+
if (blocking === 0)
|
|
289
|
+
return;
|
|
290
|
+
throw new Error(`[themeValidator] ${blocking} blocking contrast violation(s):
|
|
291
|
+
${formatStyleContract(violations)}
|
|
292
|
+
|
|
293
|
+
` + "\uD14C\uB9C8 \uD1A0\uD070 \uAC12\uC758 \uC804\uACBD/\uBC30\uACBD \uCF58\uD2B8\uB77C\uC2A4\uD2B8\uB97C WCAG \uAE30\uC900 \uC774\uC0C1\uC73C\uB85C \uC870\uC815\uD558\uC138\uC694.");
|
|
234
294
|
}
|
|
235
295
|
async writeTopLevelEnv(workspace, devProjectId) {
|
|
236
296
|
await workspace.writeFile(".env", `AKAN_WORKSPACE_ID=${devProjectId}
|