@webjsdev/cli 0.10.42 → 0.10.43

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/lib/create.js CHANGED
@@ -18,6 +18,7 @@ import { existsSync } from 'node:fs';
18
18
  import { createRequire } from 'node:module';
19
19
  import { spawnSync } from 'node:child_process';
20
20
  import { bunifyProse, bunifyDockerfile, bunifyCompose, bunifyCi } from './runtime-rewrite.js';
21
+ import { leanComponentSource } from './lean-copy.js';
21
22
 
22
23
  /**
23
24
  * Detect which package manager invoked us. Reads `npm_config_user_agent`,
@@ -120,12 +121,16 @@ async function readUiComponent(name) {
120
121
  const raw = await readFile(src, 'utf8');
121
122
  // The registry component imports cn() via a relative `../lib/utils.ts`; rewrite
122
123
  // it to the scaffolded app's aliased path (cn lives at lib/utils/cn.ts).
123
- return raw
124
+ const rewritten = raw
124
125
  .replaceAll("'../lib/utils.ts'", "'#lib/utils/cn.ts'")
125
126
  .replaceAll('"../lib/utils.ts"', '"#lib/utils/cn.ts"')
126
127
  // onBeforeCache lives in its own client-only module so cn() stays pure (#819).
127
128
  .replaceAll("'../lib/dom.ts'", "'#lib/utils/dom.ts'")
128
129
  .replaceAll('"../lib/dom.ts"', '"#lib/utils/dom.ts"');
130
+ // Strip the worked @example from a Tier-1 helper (same as `webjs ui add`), so
131
+ // the scaffolded component is lean and the example is served on demand. The
132
+ // shared helper is used by the saas-template copier too, so they cannot drift.
133
+ return leanComponentSource(rewritten, name);
129
134
  }
130
135
 
131
136
  /**
@@ -0,0 +1,43 @@
1
+ /**
2
+ * The scaffold's lean-copy of a ui component (#983).
3
+ *
4
+ * `webjs create` copies a few `@webjsdev/ui` registry components into a
5
+ * generated app. To match what `webjs ui add` writes, a Tier-1 helper's worked
6
+ * `@example` is stripped (the example is served on demand by `webjs ui view` /
7
+ * the MCP `ui` tool), while a Tier-2 element file is kept whole. Both scaffold
8
+ * copiers (`create.js` and `saas-template.js`) go through THIS one helper so
9
+ * they cannot drift.
10
+ *
11
+ * The strip primitives live in `@webjsdev/ui/registry/extract`; if that subpath
12
+ * cannot be resolved, this degrades to a no-op (keep the example) so the strip
13
+ * is never a reason `webjs create` fails.
14
+ *
15
+ * @module lean-copy
16
+ */
17
+
18
+ let _mod = null;
19
+
20
+ async function loadPrimitives() {
21
+ if (_mod) return _mod;
22
+ try {
23
+ const m = await import('@webjsdev/ui/registry/extract');
24
+ _mod = { stripExample: m.stripExample, isCustomElementSource: m.isCustomElementSource };
25
+ } catch {
26
+ _mod = { stripExample: (s) => s, isCustomElementSource: () => true };
27
+ }
28
+ return _mod;
29
+ }
30
+
31
+ /**
32
+ * Return the component source as `webjs ui add` would write it: a Tier-1 helper
33
+ * has its worked `@example` stripped and a pointer left; a Tier-2 element is
34
+ * returned unchanged.
35
+ *
36
+ * @param {string} source the component source (imports already rewritten)
37
+ * @param {string} name the component name (for the pointer)
38
+ * @returns {Promise<string>}
39
+ */
40
+ export async function leanComponentSource(source, name) {
41
+ const { stripExample, isCustomElementSource } = await loadPrimitives();
42
+ return isCustomElementSource(source) ? source : stripExample(source, name);
43
+ }
@@ -5,6 +5,7 @@
5
5
 
6
6
  import { mkdir, writeFile, readFile } from 'node:fs/promises';
7
7
  import { bunifyProse } from './runtime-rewrite.js';
8
+ import { leanComponentSource } from './lean-copy.js';
8
9
  import { existsSync } from 'node:fs';
9
10
  import { join, resolve, dirname } from 'node:path';
10
11
  import { fileURLToPath } from 'node:url';
@@ -25,7 +26,7 @@ async function readUiComponent(name) {
25
26
  const raw = await readFile(src, 'utf8');
26
27
  // The registry component imports cn() via a relative `../lib/utils.ts`; rewrite
27
28
  // it to the scaffolded app's aliased path (cn lives at lib/utils/cn.ts).
28
- return raw
29
+ const rewritten = raw
29
30
  .replaceAll("'../lib/utils.ts'", "'#lib/utils/cn.ts'")
30
31
  .replaceAll('"../lib/utils.ts"', '"#lib/utils/cn.ts"')
31
32
  // onBeforeCache lives in its own client-only module so cn() stays pure (#819).
@@ -33,6 +34,9 @@ async function readUiComponent(name) {
33
34
  // (which resolves to a nonexistent components/lib/dom.ts) and fails typecheck.
34
35
  .replaceAll("'../lib/dom.ts'", "'#lib/utils/dom.ts'")
35
36
  .replaceAll('"../lib/dom.ts"', '"#lib/utils/dom.ts"');
37
+ // Strip a Tier-1 helper's worked @example (same as create.js + `webjs ui add`)
38
+ // so switch / checkbox are lean, not just the full-stack base set (#983).
39
+ return leanComponentSource(rewritten, name);
36
40
  }
37
41
 
38
42
  /** Copy named registry components into `<appDir>/components/ui/`. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webjsdev/cli",
3
- "version": "0.10.42",
3
+ "version": "0.10.43",
4
4
  "type": "module",
5
5
  "description": "webjs CLI - dev, start, create, db",
6
6
  "bin": {
@@ -44,6 +44,7 @@ Classify the task first, then load the smallest useful reference set. Each refer
44
44
  | Tailwind, light-DOM tag-prefix rule, tokens, fixed headers, no-reflow layout | `references/styling.md` |
45
45
  | Client router, prefetch, frames, view transitions, Suspense streaming | `references/client-router-and-streaming.md` |
46
46
  | Optimistic UI for a user-facing mutation | `references/optimistic-ui.md` |
47
+ | The `@webjsdev/ui` component kit (a `components.json` is present): class helpers, tokens, `add` / `view`, the MCP `ui` tool | `references/ui-kit.md` |
47
48
  | TypeScript at runtime, erasable syntax, full-stack types | `references/typescript.md` |
48
49
  | Unit, browser, e2e tests, the `handle()` harness, Bun parity | `references/testing.md` |
49
50
  | Auth, caching, env vars, rate limit, file storage, the `webjs` config block | `references/built-ins.md` |
@@ -0,0 +1,68 @@
1
+ # The `@webjsdev/ui` component kit
2
+
3
+ Load this when the app has a `components.json` (it uses `@webjsdev/ui`, the
4
+ shadcn-style kit for WebJs). The source is copied into your repo (`components/ui/`),
5
+ so you own and edit it. Two tiers:
6
+
7
+ - **Tier 1, class helpers (23 components).** Pure functions returning Tailwind
8
+ class strings (`buttonClass({ variant })`, `cardClass()`), composed with
9
+ whatever native element you write. Reach for these instead of expanding
10
+ Tailwind by hand: the call site is a fraction of the tokens and the class list
11
+ cannot drift.
12
+ - **Tier 2, stateful custom elements (9 components).** `<ui-dialog>`, `<ui-tabs>`,
13
+ `<ui-dropdown-menu>`, and friends own their ARIA (focus trap, roving tabindex,
14
+ `aria-controls` / `inert`, live regions). Write the tag and the accessible
15
+ behaviour comes with it. Do NOT hand-roll these; the wiring is easy to get
16
+ subtly wrong.
17
+
18
+ ## The workflow: query for the structure, do not guess it
19
+
20
+ `add` copies a Tier-1 component's class helpers plus a lean header (what each
21
+ helper is, the accessibility obligations) and a one-line pointer. It does NOT
22
+ copy the worked structural example, because that example is guidance you consume
23
+ once while composing, not code that should sit in your repo. Get the full
24
+ paste-ready structure on demand:
25
+
26
+ - **MCP `ui` tool** (preferred when available): call `ui` with no args for the
27
+ kit inventory (each component's tier, helper signatures, npm deps); pass
28
+ `{ name: "accordion" }` for one component's helper signatures, the paste-ready
29
+ structural example, the accessibility header, and deps.
30
+ - **CLI**: `webjs ui list` (inventory), `webjs ui view <name>` (the projected
31
+ view plus the full source). Same data as the MCP tool (one shared projector).
32
+
33
+ So the loop is: `add` the component, then query `ui <name>` (MCP) or
34
+ `webjs ui view <name>` for the accessible structure, paste it, and fill it in.
35
+
36
+ ## Setup and resolution
37
+
38
+ - `webjs ui init` writes `components.json`, `lib/utils.ts`, and the CSS design
39
+ tokens the helpers render against (`--background`, `--foreground`,
40
+ `--destructive`, ...). It HARD-FAILS if the tokens cannot be written, so a
41
+ clean exit means the kit is styled. `add` self-heals the tokens if they go
42
+ missing.
43
+ - Resolution is LOCAL-FIRST: `init` / `add` / `list` / `view` read the registry
44
+ that ships inside the installed `@webjsdev/ui`, with no network. This pins you
45
+ to the installed version; run `webjs ui diff` to see where your local copies
46
+ drift from the upstream (that command alone compares against the live registry).
47
+
48
+ ## Inventory (run `webjs ui list` or the MCP `ui` tool for the authoritative, current set)
49
+
50
+ **Tier 1 (class helpers):** accordion, alert, aspect-ratio, avatar, badge,
51
+ breadcrumb, button, card, checkbox, collapsible, input, kbd, label,
52
+ native-select, pagination, popover, progress, radio-group, separator, skeleton,
53
+ switch, table, textarea.
54
+
55
+ **Tier 2 (custom elements, own their ARIA):** alert-dialog, dialog,
56
+ dropdown-menu, hover-card, sonner, tabs, tooltip, plus toggle and toggle-group
57
+ (these two register an element AND export a `*Class` helper).
58
+
59
+ ## Idioms
60
+
61
+ - A helper is a function, so compose it: `class=${buttonClass({ variant: 'outline' })}`.
62
+ The unquoted `${...}` is a normal `html` attribute hole.
63
+ - Tier-1 helpers assume the design tokens exist; if a component paints unstyled,
64
+ the tokens are missing (re-run `webjs ui init` or let `add` self-heal them).
65
+ - Custom elements are display-only-safe at SSR and hydrate in the browser, the
66
+ standard WebJs component model (`references/components.md`).
67
+
68
+ Full per-package reference lives in the installed `@webjsdev/ui/AGENTS.md`.