@rokkit/cli 1.2.0 → 1.3.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.
@@ -20,25 +20,6 @@ export function runChecks(fs: {
20
20
  fix: string;
21
21
  autoFix?: string;
22
22
  }>;
23
- /**
24
- * Scan files for legacy z-scale utilities and suggest named-token equivalents.
25
- * Advisory only — z-scale still resolves via the preset's back-compat layer.
26
- * @param {Array<{ path: string, content: string }>} files
27
- * @returns {{ count: number, byFile: Array<{ path: string, hits: Array<{ token: string, suggestion: string }> }> }}
28
- */
29
- export function findLegacyZUtilities(files: Array<{
30
- path: string;
31
- content: string;
32
- }>): {
33
- count: number;
34
- byFile: Array<{
35
- path: string;
36
- hits: Array<{
37
- token: string;
38
- suggestion: string;
39
- }>;
40
- }>;
41
- };
42
23
  /**
43
24
  * Validate a parsed rokkit config for the named-token system. All advisory (warn).
44
25
  * @param {Record<string, unknown> | null} config
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rokkit/cli",
3
- "version": "1.2.0",
3
+ "version": "1.3.1",
4
4
  "description": "Command line utilities for icon bundling and conversion.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -27,7 +27,7 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "@mdi/svg": "^7.4.47",
30
- "@rokkit/unocss": "1.2.0",
30
+ "@rokkit/unocss": "1.3.1",
31
31
  "sade": "^1.8.1",
32
32
  "@iconify/tools": "5.0.3",
33
33
  "@iconify/types": "^2.0.0",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: semantic-styles-rokkit
3
- description: Use when building, styling, or auditing a Rokkit-powered app (Svelte 5 + UnoCSS presetRokkit) — covers the named-token vocabulary (paper / ink / primary / on-primary / *-soft), EXTENDING it with custom tokens via `overrides:` and the full palette ladder via `tokens: 'extended'`, the rokkit.config.js palette→skin→tokens pipeline, dual-palette dark mode, migrating legacy z-scale utilities (bg-surface-z0, text-primary-z5) to named tokens, and the rokkit init / rokkit doctor CLI.
3
+ description: Use when building, styling, or auditing a Rokkit-powered app (Svelte 5 + UnoCSS presetRokkit) — covers the named-token vocabulary (paper / ink / primary / on-primary / *-soft), EXTENDING it with custom tokens via `overrides:` and the full palette ladder via `tokens: 'extended'`, the rokkit.config.js palette→skin→tokens pipeline, dual-palette dark mode, and the rokkit init / rokkit doctor CLI.
4
4
  ---
5
5
 
6
6
  # Semantic Styles — Rokkit
@@ -17,7 +17,6 @@ rokkit.config.js
17
17
  UnoCSS preset (presetRokkit) → CSS custom properties + utility classes
18
18
  ↓
19
19
  Components use named-token utilities: bg-paper · text-ink · bg-primary · text-on-primary
20
- (legacy z-scale utilities — bg-surface-z1 — still resolve via back-compat aliases)
21
20
  ```
22
21
 
23
22
  The pipeline has three layers. Most authoring happens in **Layer 3 (named tokens)**.
@@ -56,7 +55,7 @@ The preset wraps values into complete colors when it emits CSS vars (see Layer 3
56
55
  **What:** A skin maps semantic role names to palette names. The core roles that back the
57
56
  named-token vocabulary are: `surface`, `ink`, `primary`, `accent`, `success`, `warning`,
58
57
  `danger`, `error`, `info`. (`secondary`/`tertiary` are accepted but no named token reads
59
- them — they only matter if you also use `extended` z-scale utilities for those roles.)
58
+ them — they only matter if you opt into `tokens: 'extended'` for those roles.)
60
59
 
61
60
  **Why:** Decoupling role from palette lets you swap the whole scheme in config, not code.
62
61
  `bg-paper` doesn't know whether `surface` is `kami`, `slate`, or `zinc`.
@@ -84,7 +83,7 @@ skins: {
84
83
 
85
84
  ### `tokens:` — which CSS the preset emits
86
85
  ```js
87
- tokens: 'core' // default — emit the 24 named tokens (palette values inlined) + z-aliases
86
+ tokens: 'core' // default — emit the 24 named tokens (palette values inlined)
88
87
  tokens: 'extended' // also emit the full 11-shade ladder (--color-{role}-{shade}) per role
89
88
  ```
90
89
  `'core'` is the default and what apps should use. Reach for `'extended'` only when you need
@@ -226,16 +225,7 @@ directly in CSS-only contexts; for alpha, use `color-mix` (the form the preset i
226
225
  .thing { background: var(--paper); border-color: var(--paper-edge); }
227
226
  .thing-50pc { background: color-mix(in oklch, var(--primary) 50%, transparent); }
228
227
  ```
229
- Prefer utility classes over raw vars where you can. (In `core` mode the legacy
230
- `--color-{role}-z{n}` vars are aliases pointing at named tokens, so they too resolve to
231
- complete colors — the old "wrap bare components in `rgb()`/`oklch()`" rule no longer
232
- applies in core mode.)
233
-
234
- ### Legacy z-scale utilities (back-compat)
235
-
236
- The older z-scale utilities (`bg-surface-z0`, `text-surface-z9`, `text-primary-z5`, …)
237
- **still resolve** as a back-compat layer that collapses onto the named tokens above —
238
- existing code keeps working. New code should use named tokens. See the migration section.
228
+ Prefer utility classes over raw vars where you can.
239
229
 
240
230
  ---
241
231
 
@@ -305,38 +295,6 @@ Default stays `'core'`.
305
295
 
306
296
  ---
307
297
 
308
- ## Migrating from z-scale to named tokens
309
-
310
- `rokkit doctor` scans `src/**` and prints the suggested replacement per occurrence
311
- (advisory — it never rewrites your code). The mapping it uses:
312
-
313
- **Surface role** (`{prefix}-surface-z{n}` → `{prefix}-{token}`):
314
-
315
- | z | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
316
- |---|---|---|---|---|---|---|---|---|---|---|----|
317
- | token | `paper` | `paper-soft` | `paper-mute` | `paper-mute` | `paper-edge` | `ink-soft` | `ink-soft` | `ink-mute` | `ink-mute` | `ink` | `ink` |
318
-
319
- **Ink role** (inverted scale):
320
-
321
- | z | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
322
- |---|---|---|---|---|---|---|---|---|---|---|----|
323
- | token | `ink` | `ink-mute` | `ink-mute` | `ink-soft` | `ink-soft` | `paper-edge` | `paper-edge` | `paper-mute` | `paper-mute` | `paper-soft` | `paper` |
324
-
325
- **Accent / status roles** (`primary`, `accent`, `success`, `warning`, `danger`, `error`,
326
- `info`): `z0–z2 → {role}-soft` (tinted background), `z3+ → {role}` (solid). Exception:
327
- `primary` has **no** `-soft` companion — `primary-z*` all map to `primary`; for a tinted
328
- primary background use `accent-soft` or a custom token.
329
-
330
- Common rewrites:
331
- ```
332
- bg-surface-z0 → bg-paper text-surface-z9 → text-ink
333
- bg-surface-z1 → bg-paper-soft text-surface-z7 → text-ink-mute
334
- border-surface-z3 → border-paper-edge text-surface-z5 → text-ink-soft
335
- text-primary-z5 → text-primary bg-success-z1 → bg-success-soft
336
- ```
337
-
338
- ---
339
-
340
298
  ## Rokkit CLI — `init` and `doctor`
341
299
 
342
300
  **`rokkit init`** scaffolds `rokkit.config.js` in the named-token shape (a header comment
@@ -354,7 +312,6 @@ fail the command or block `--fix`:
354
312
  | `skin-ink-role` | the active colormap has no `ink` role (text tokens fall back) |
355
313
  | `oklch-needs-palettes` | `colorSpace: 'oklch'` but no `palettes` block |
356
314
  | `colors-alias` | config uses the legacy `colors:` instead of `skin:` |
357
- | legacy z-utilities | `src/**` contains `bg-surface-z*` / `text-primary-z*` etc. (prints named-token suggestions) |
358
315
 
359
316
  The "active colormap" is resolved the same way the preset does: `skins.default ?? skin ??
360
317
  colors`. Run `rokkit doctor --fix` to auto-write a real starter config when one is missing.
@@ -447,7 +404,6 @@ export default {
447
404
  | "core only emits `accent`/`accent-soft`, so I can't have `accent-2`/`on-accent`" | the vocabulary is extensible, not fixed | add them under `overrides:` (or use `tokens: 'extended'`) — see Layer 3b |
448
405
  | Override palette ref `'sky-600'` (hyphen) | refs use **dot** notation | `'sky.600'` (and `sky` must be in `palettes:`) |
449
406
  | `color: #3D3730` in a component | breaks on theme change | `text-ink` (or the right named token) |
450
- | Using `bg-surface-z5` / `text-primary-z5` in new code | legacy back-compat layer | named tokens — see migration table (or run `rokkit doctor`) |
451
407
  | Single-palette `surface` but expecting dark mode | no `[data-mode]` block is emitted | make `surface`/`ink` dual-palette `{ light, dark }` |
452
408
  | Skin with no `ink` role | `ink-*` text tokens fall back to surface | add `ink: '<palette>'` (reuse surface is fine) |
453
409
  | Using `skin:` and `skins:` together | `skins:` silently wins | pick one |
package/src/config.js CHANGED
@@ -16,6 +16,7 @@ export async function loadConfig(adapters) {
16
16
 
17
17
  try {
18
18
  const { default: config } = await import(`file://${configPath}?t=${Date.now()}`)
19
+ /* v8 ignore next -- dynamic import succeeds only in real Node, not JSDOM */
19
20
  return config
20
21
  } catch {
21
22
  return null
package/src/convert.js CHANGED
@@ -47,6 +47,9 @@ export function processIcons(iconSet, color) {
47
47
  try {
48
48
  cleanAndOptimizeIcon(svg, color)
49
49
  } catch (err) {
50
+ /* v8 ignore next 6 -- catch body requires an iconify-tools internal parse
51
+ error; the err1 fixture exercises this path but the exception propagates
52
+ at importDirectory level before processIcons is reached */
50
53
  console.error(`Error parsing ${name}: ${err.reason}`)
51
54
  iconSet.remove(name)
52
55
  return
@@ -116,6 +119,8 @@ export async function convert(folder, options) {
116
119
 
117
120
  // Create the target directory if it doesn't exist
118
121
  const targetDir = path.resolve(options.target)
122
+ /* v8 ignore next 4 -- bundle() (called above) always creates targetDir first;
123
+ this guard is unreachable in practice but kept as a defensive fallback */
119
124
  if (!fs.existsSync(targetDir)) {
120
125
  fs.mkdirSync(targetDir, { recursive: true })
121
126
  }
package/src/doctor.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /* eslint-disable no-console */
2
- import { existsSync, readFileSync, writeFileSync, readdirSync } from 'fs'
3
- import { resolve, extname } from 'path'
2
+ import { existsSync, readFileSync, writeFileSync } from 'fs'
3
+ import { resolve } from 'path'
4
4
  import { loadConfig as loadParsedConfig } from './config.js'
5
5
  import {
6
6
  generateUnoConfig,
@@ -240,6 +240,9 @@ function applyPatchHtml(cwd, label) {
240
240
  * @param {string} cwd
241
241
  * @param {string} label
242
242
  */
243
+ /* v8 ignore next 16 -- chart-config check emits 'warn' (not 'fail'); applyCheckFix
244
+ only processes 'fail' statuses, making this handler unreachable via the normal
245
+ fix flow. Kept for potential future use if the check severity changes. */
243
246
  function applyPatchChartConfig(cwd, label) {
244
247
  const configPath = resolve(cwd, 'rokkit.config.js')
245
248
  if (!existsSync(configPath)) return false
@@ -363,85 +366,6 @@ function handleResults(checks, cwd, failures, fix) {
363
366
  }
364
367
  }
365
368
 
366
- // z-slot → named token (matches @rokkit/core Z_COLLAPSE maps).
367
- const SURFACE_Z = {
368
- z0: 'paper', z1: 'paper-soft', z2: 'paper-mute', z3: 'paper-mute', z4: 'paper-edge',
369
- z5: 'ink-soft', z6: 'ink-soft', z7: 'ink-mute', z8: 'ink-mute', z9: 'ink', z10: 'ink'
370
- }
371
- const INK_Z = {
372
- z0: 'ink', z1: 'ink-mute', z2: 'ink-mute', z3: 'ink-soft', z4: 'ink-soft',
373
- z5: 'paper-edge', z6: 'paper-edge', z7: 'paper-mute', z8: 'paper-mute', z9: 'paper-soft', z10: 'paper'
374
- }
375
-
376
- /**
377
- * Map a z-scale role+slot to its nearest named token.
378
- * @param {string} role
379
- * @param {string} z — e.g. 'z5'
380
- * @returns {string}
381
- */
382
- function suggestNamedToken(role, z) {
383
- if (role === 'surface') return SURFACE_Z[z]
384
- if (role === 'ink') return INK_Z[z]
385
- // accent + status roles collapse z0–z2 to their `-soft` tint (matches
386
- // @rokkit/core theme.ts getZAliasesOther: zNum <= 2). primary has no `-soft`.
387
- if (role !== 'primary' && Number(z.slice(1)) <= 2) return `${role}-soft`
388
- return role
389
- }
390
-
391
- const Z_UTILITY_RE =
392
- /\b(bg|text|border(?:-[tblr])?|fill|stroke|ring|outline|divide)-(surface|ink|primary|accent|success|warning|danger|error|info)-(z(?:10|[0-9]))\b/g
393
-
394
- /**
395
- * Scan files for legacy z-scale utilities and suggest named-token equivalents.
396
- * Advisory only — z-scale still resolves via the preset's back-compat layer.
397
- * @param {Array<{ path: string, content: string }>} files
398
- * @returns {{ count: number, byFile: Array<{ path: string, hits: Array<{ token: string, suggestion: string }> }> }}
399
- */
400
- export function findLegacyZUtilities(files) {
401
- const byFile = []
402
- let count = 0
403
- for (const { path: filePath, content } of files) {
404
- const hits = []
405
- for (const match of content.matchAll(Z_UTILITY_RE)) {
406
- const [token, prefix, role, z] = match
407
- hits.push({ token, suggestion: `${prefix}-${suggestNamedToken(role, z)}` })
408
- }
409
- if (hits.length > 0) {
410
- byFile.push({ path: filePath, hits })
411
- count += hits.length
412
- }
413
- }
414
- return { count, byFile }
415
- }
416
-
417
- const SCAN_EXTS = new Set(['.svelte', '.css', '.ts', '.js'])
418
-
419
- /**
420
- * Recursively gather scannable source files under cwd/src.
421
- * Skips node_modules, symlinks (avoids recursion loops), and uno.config.js
422
- * (its safelist legitimately lists z-utilities).
423
- * @param {string} cwd
424
- * @returns {Array<{ path: string, content: string }>}
425
- */
426
- function gatherSrcFiles(cwd) {
427
- const root = resolve(cwd, 'src')
428
- if (!existsSync(root)) return []
429
- const out = []
430
- const walk = (dir) => {
431
- for (const dirent of readdirSync(dir, { withFileTypes: true })) {
432
- if (dirent.isSymbolicLink() || dirent.name === 'node_modules') continue
433
- const full = resolve(dir, dirent.name)
434
- if (dirent.isDirectory()) {
435
- walk(full)
436
- } else if (dirent.isFile() && SCAN_EXTS.has(extname(dirent.name)) && !full.endsWith('uno.config.js')) {
437
- out.push({ path: full.slice(cwd.length + 1), content: readFileSync(full, 'utf-8') })
438
- }
439
- }
440
- }
441
- walk(root)
442
- return out
443
- }
444
-
445
369
  /**
446
370
  * Validate a parsed rokkit config for the named-token system. All advisory (warn).
447
371
  * @param {Record<string, unknown> | null} config
@@ -497,6 +421,8 @@ export async function doctor(opts = {}) {
497
421
 
498
422
  const parsed = await loadParsedConfig({ cwd })
499
423
  const shapeChecks = validateConfigShape(parsed)
424
+ /* v8 ignore next 8 -- loadConfig uses dynamic file:// import which always fails in
425
+ JSDOM; validateConfigShape(null) returns [] so this branch is unreachable in tests */
500
426
  if (shapeChecks.length > 0) {
501
427
  console.info('\nConfig shape:')
502
428
  for (const c of shapeChecks) {
@@ -505,17 +431,6 @@ export async function doctor(opts = {}) {
505
431
  }
506
432
  }
507
433
 
508
- const { count, byFile } = findLegacyZUtilities(gatherSrcFiles(cwd))
509
- if (count > 0) {
510
- console.info(`\nLegacy z-scale utilities (advisory — ${count} in ${byFile.length} file(s)):`)
511
- for (const { path: filePath, hits } of byFile.slice(0, 10)) {
512
- const examples = [...new Map(hits.map((h) => [h.token, h.suggestion]))]
513
- const shown = examples.slice(0, 3).map(([t, s]) => `${t} → ${s}`).join(', ')
514
- console.info(` ${filePath}: ${shown}${examples.length > 3 ? ', …' : ''}`)
515
- }
516
- console.info(' Named tokens are preferred; z-scale still works via back-compat.')
517
- }
518
-
519
434
  handleResults(checks, cwd, failures, opts.fix ?? false)
520
435
  console.info('')
521
436
  }
package/src/init.js CHANGED
@@ -20,9 +20,7 @@ const NAMED_TOKEN_HEADER = `/**
20
20
  * Accent bg-primary · text-on-primary · bg-accent · bg-accent-soft
21
21
  * Status bg-success-soft · text-success (+ warning / danger / error / info)
22
22
  *
23
- * Tokens flip automatically under [data-mode="dark"]. The older z-scale
24
- * utilities (bg-surface-z0, text-primary-z5) still resolve for back-compat,
25
- * but new code should prefer the named tokens above.
23
+ * Tokens flip automatically under [data-mode="dark"].
26
24
  *
27
25
  * tokens: 'core' emits the named vocabulary; 'extended' also emits the full
28
26
  * 11-shade palette ladder per role (for charts / data-viz).
@@ -410,6 +408,7 @@ const PROMPTS_CONFIG = [
410
408
  type: (prev) => (prev?.length > 1 ? 'select' : null),
411
409
  name: 'defaultTheme',
412
410
  message: 'Default theme style',
411
+ /* v8 ignore next 2 -- prompts.inject bypasses choices callback evaluation in tests */
413
412
  choices: (prev) =>
414
413
  prev.map((t) => ({ title: t.charAt(0).toUpperCase() + t.slice(1), value: t }))
415
414
  },
@@ -549,6 +548,9 @@ export async function init(_opts = {}, adapters = {}) {
549
548
 
550
549
  const response = await prompts(PROMPTS_CONFIG)
551
550
 
551
+ /* v8 ignore next 10 -- prompts mutates PROMPTS_CONFIG[n].type in-place on the
552
+ first call, so the conditional primary/accent/surface prompts always resolve
553
+ to null on subsequent runs; the branch is tested via generateConfig directly */
552
554
  if (response.palette === 'custom') {
553
555
  response.customColors = {
554
556
  primary: response.primary,
package/src/upgrade.js CHANGED
@@ -57,10 +57,13 @@ function resolveUpgradeAdapters(adapters) {
57
57
  exists: adapters.exists ?? ((p) => existsSync(p)),
58
58
  fetchVersion:
59
59
  adapters.fetchVersion ??
60
+ /* v8 ignore next 8 -- default fetchVersion calls real npm; tests always inject
61
+ fetchVersion so this closure is never executed in the test environment */
60
62
  ((name) => {
61
63
  try {
62
64
  return execFileSync('npm', ['view', name, 'version'], { encoding: 'utf-8' }).trim()
63
65
  } catch {
66
+ // npm binary absent or network error — treat as unknown version
64
67
  return null
65
68
  }
66
69
  }),