hlmnhkl-ui 0.20.0 → 0.20.2

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/README.md CHANGED
@@ -50,6 +50,8 @@ Using Next.js or React Router? The [installation guide](https://ui.hlmnhkl.my/in
50
50
 
51
51
  Every copied file starts with one line naming hlmnhkl/ui and the MIT licence, so the licence notice travels with the code. `diff` and `update` ignore that line.
52
52
 
53
+ `add`, `diff` and `update` never change your `tokens.css`. If the latest one has tokens yours lacks, they list them after the result, with the lines to copy.
54
+
53
55
  **With an AI assistant:** point it at [llms.txt](https://ui.hlmnhkl.my/llms.txt) (every component and the rules) or [llms-full.txt](https://ui.hlmnhkl.my/llms-full.txt) (props, keyboard notes and an example for each free component). There is also a [registry.json](https://ui.hlmnhkl.my/registry.json).
54
56
 
55
57
  ## What you get
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hlmnhkl-ui",
3
- "version": "0.20.0",
3
+ "version": "0.20.2",
4
4
  "description": "React components in a monochrome style. Copy them into your app with npx hlmnhkl-ui add <name>.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://ui.hlmnhkl.my",
package/scripts/add.js CHANGED
@@ -464,9 +464,45 @@ async function runUpdate(opts) {
464
464
  console.log(`Updated ${plural(todo.length, 'file')}.`)
465
465
  }
466
466
 
467
+ const tokenNames = (text) => new Set([...text.replace(/\/\*[\s\S]*?\*\//g, '').matchAll(/(--[\w-]+)\s*:/g)].map((m) => m[1]))
468
+
469
+ // tokens.css is never replaced, so a new component can read a token the project's copy does not have yet.
470
+ function warnMissingTokens(opts) {
471
+ const mine = join(targetFolder(opts).to, 'tokens.css')
472
+ if (!isFile(mine)) return
473
+ const latest = join(SRC, 'tokens.css')
474
+ const text = readFileSync(latest, 'utf8')
475
+ const have = tokenNames(readFileSync(mine, 'utf8'))
476
+ const missing = [...tokenNames(text)].filter((name) => !have.has(name))
477
+ if (!missing.length) return
478
+ console.log(`\nYour tokens.css is missing ${plural(missing.length, 'token')} that the latest components use: ${missing.join(', ')}`)
479
+ console.log(`Your file was not changed. Copy these lines from the latest tokens.css into ${shown(CWD, mine)}, each into the same block:`)
480
+ // A token can be set in more than one block (dark theme, reduced motion), so every line is listed under its block.
481
+ const blocks = new Map()
482
+ const stack = []
483
+ let selector = ''
484
+ for (const line of text.split('\n').map((l) => l.trim())) {
485
+ if (line.endsWith('{')) {
486
+ stack.push(`${selector}${line.slice(0, -1)}`.trim())
487
+ selector = ''
488
+ } else if (line.startsWith('}')) stack.pop()
489
+ else if (line.endsWith(',') && !line.includes(': ')) selector += `${line} `
490
+ else if (missing.some((name) => line.startsWith(`${name}:`))) {
491
+ const where = stack.join(' / ')
492
+ blocks.set(where, [...(blocks.get(where) ?? []), line])
493
+ }
494
+ }
495
+ for (const [where, found] of blocks) {
496
+ console.log(` In ${where}:`)
497
+ for (const line of found) console.log(` ${line}`)
498
+ }
499
+ console.log(`The latest file is at ${latest}`)
500
+ }
501
+
467
502
  const opts = parseArgs(process.argv.slice(2))
468
503
  if (opts.command === 'init') runInit(opts)
469
504
  else if (opts.command === 'list') runList(opts)
470
505
  else if (opts.command === 'diff') runDiff(opts)
471
506
  else if (opts.command === 'update') await runUpdate(opts)
472
507
  else runAdd(opts)
508
+ if (['add', 'diff', 'update'].includes(opts.command)) warnMissingTokens(opts)