@hirobius/design-system 0.21.0 → 0.23.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 (117) hide show
  1. package/AGENTS.md +13 -1
  2. package/CHANGELOG.md +1043 -0
  3. package/CONSUMING.md +25 -11
  4. package/DESIGN.md +24 -24
  5. package/MIGRATIONS.md +603 -0
  6. package/NOTICE.md +1 -1
  7. package/README.md +22 -12
  8. package/UPGRADING.md +243 -0
  9. package/codemods/hds-prefix.mjs +28 -13
  10. package/codemods/jsx-fold.mjs +28 -13
  11. package/codemods/lib/atomic-write.mjs +28 -0
  12. package/codemods/lib/installed-version.mjs +354 -0
  13. package/codemods/lib/project.mjs +395 -0
  14. package/codemods/lib/record.mjs +110 -0
  15. package/codemods/lib/scan.mjs +323 -0
  16. package/codemods/lib/semver.mjs +57 -0
  17. package/codemods/not-found-pattern.mjs +3 -3
  18. package/codemods/patterns-subpath.mjs +28 -12
  19. package/codemods/registry.mjs +67 -0
  20. package/codemods/tile-grid.mjs +3 -3
  21. package/codemods/upgrade.mjs +909 -0
  22. package/dist/chunks/{empty-state-DNoThgfV.js → empty-state-ChC_xZfg.js} +109 -92
  23. package/dist/chunks/{form-By82qZcC.js → form-zCZ_B83I.js} +16 -16
  24. package/dist/css-contract.json +1 -0
  25. package/dist/fonts/ibm-plex-mono-400.woff2 +0 -0
  26. package/dist/fonts.css +5 -5
  27. package/dist/form.js +1 -1
  28. package/dist/hirobius-ui.js +1254 -1181
  29. package/dist/manifest.js +6 -5
  30. package/dist/patterns.js +164 -162
  31. package/dist/styles.css +1 -1
  32. package/dist/tokens.css +1 -1
  33. package/dist/tokens.js +14 -1
  34. package/dist/types/src/app/components/alert-dialog.d.ts +1 -1
  35. package/dist/types/src/app/components/alert.d.ts +1 -1
  36. package/dist/types/src/app/components/asset-img.d.ts +2 -1
  37. package/dist/types/src/app/components/avatar-group.d.ts +1 -1
  38. package/dist/types/src/app/components/avatar.d.ts +1 -1
  39. package/dist/types/src/app/components/badge.d.ts +1 -1
  40. package/dist/types/src/app/components/blockquote.d.ts +1 -1
  41. package/dist/types/src/app/components/breadcrumb.d.ts +1 -1
  42. package/dist/types/src/app/components/button.d.ts +2 -2
  43. package/dist/types/src/app/components/callout.d.ts +3 -2
  44. package/dist/types/src/app/components/card.d.ts +6 -4
  45. package/dist/types/src/app/components/checkbox.d.ts +1 -1
  46. package/dist/types/src/app/components/code-block.d.ts +2 -1
  47. package/dist/types/src/app/components/combobox.d.ts +2 -2
  48. package/dist/types/src/app/components/data-table-section.d.ts +2 -1
  49. package/dist/types/src/app/components/destructive-section.d.ts +2 -1
  50. package/dist/types/src/app/components/dialog.d.ts +1 -1
  51. package/dist/types/src/app/components/disclosure.d.ts +1 -1
  52. package/dist/types/src/app/components/divider.d.ts +2 -2
  53. package/dist/types/src/app/components/empty-state.d.ts +1 -1
  54. package/dist/types/src/app/components/field.d.ts +1 -1
  55. package/dist/types/src/app/components/form-actions.d.ts +2 -1
  56. package/dist/types/src/app/components/form.d.ts +2 -1
  57. package/dist/types/src/app/components/grid.d.ts +1 -1
  58. package/dist/types/src/app/components/hds-tooltip.d.ts +1 -1
  59. package/dist/types/src/app/components/icon.d.ts +2 -2
  60. package/dist/types/src/app/components/inline-link.d.ts +1 -1
  61. package/dist/types/src/app/components/input.d.ts +1 -1
  62. package/dist/types/src/app/components/kbd.d.ts +1 -1
  63. package/dist/types/src/app/components/menu.d.ts +1 -1
  64. package/dist/types/src/app/components/metadata-list.d.ts +1 -1
  65. package/dist/types/src/app/components/metric-tiles.d.ts +3 -1
  66. package/dist/types/src/app/components/page-header.d.ts +1 -0
  67. package/dist/types/src/app/components/page.d.ts +1 -0
  68. package/dist/types/src/app/components/pagination.d.ts +1 -1
  69. package/dist/types/src/app/components/popover.d.ts +1 -1
  70. package/dist/types/src/app/components/progress.d.ts +1 -1
  71. package/dist/types/src/app/components/radio.d.ts +1 -1
  72. package/dist/types/src/app/components/reveal.d.ts +1 -0
  73. package/dist/types/src/app/components/select.d.ts +1 -1
  74. package/dist/types/src/app/components/skeleton.d.ts +1 -1
  75. package/dist/types/src/app/components/slider.d.ts +1 -1
  76. package/dist/types/src/app/components/spinner.d.ts +1 -1
  77. package/dist/types/src/app/components/stack.d.ts +1 -1
  78. package/dist/types/src/app/components/stat.d.ts +1 -1
  79. package/dist/types/src/app/components/status-list-item.d.ts +1 -1
  80. package/dist/types/src/app/components/status-tile.d.ts +1 -1
  81. package/dist/types/src/app/components/table.d.ts +1 -1
  82. package/dist/types/src/app/components/tabs.d.ts +4 -2
  83. package/dist/types/src/app/components/tag.d.ts +1 -1
  84. package/dist/types/src/app/components/text.d.ts +1 -1
  85. package/dist/types/src/app/components/textarea.d.ts +1 -1
  86. package/dist/types/src/app/components/timestamp.d.ts +1 -1
  87. package/dist/types/src/app/components/toast.d.ts +1 -1
  88. package/dist/types/src/app/components/toggle.d.ts +1 -1
  89. package/dist/types/src/app/context/hds-theme.d.ts +1 -1
  90. package/dist/types/src/app/design-system/generated-token-refs.d.ts +13 -0
  91. package/dist/types/src/app/design-system/generated-token-values.d.ts +14 -1
  92. package/dist/types/src/app/hooks/useInteractionState.d.ts +7 -1
  93. package/dist/types/src/index.d.ts +126 -32
  94. package/dist/variables.css +33 -22
  95. package/docs/CONSUMING.md +2 -2
  96. package/hirobius.tokens.json +173 -79
  97. package/llms.txt +29 -7
  98. package/mcp/catalog.mjs +10 -7
  99. package/mcp/core-set.mjs +66 -0
  100. package/mcp/guide.mjs +9 -52
  101. package/package.json +28 -11
  102. package/public/hds-manifest.json +365 -207
  103. package/public/llms/components.txt +1 -1
  104. package/public/llms-full.txt +54 -32
  105. package/public/llms.txt +29 -7
  106. package/src/app/data/component-api.json +104 -71
  107. package/upgrade/index.json +215 -0
  108. package/upgrade/releases/0.17.0.json +962 -0
  109. package/upgrade/releases/0.18.0.json +8 -0
  110. package/upgrade/releases/0.19.0.json +233 -0
  111. package/upgrade/releases/0.19.1.json +8 -0
  112. package/upgrade/releases/0.20.0.json +5108 -0
  113. package/upgrade/releases/0.21.0.json +538 -0
  114. package/upgrade/releases/0.22.0.json +94 -0
  115. package/upgrade/releases/0.23.0.json +259 -0
  116. package/upgrade/schema.json +1102 -0
  117. package/dist/fonts/geist-mono-400.woff2 +0 -0
@@ -0,0 +1,354 @@
1
+ /**
2
+ * installed-version — which @hirobius/design-system each importer has installed
3
+ * (hds#453; the upgrade command in hds#452 reuses it).
4
+ *
5
+ * Everything here takes lockfile TEXT and returns plain data, so the same code
6
+ * reads a lockfile from disk, from `git show HEAD:<lockfile>` or from the GitHub
7
+ * contents API. Node builtins only: it runs on consumer machines.
8
+ *
9
+ * readLockfile('pnpm-lock.yaml', text)
10
+ * → { kind: 'pnpm', version: '9.0',
11
+ * importers: { '.': '0.16.0', 'apps/site': '0.20.0' },
12
+ * versions: ['0.16.0', '0.20.0'] }
13
+ *
14
+ * `importers` maps each importer that declares the package (the repo root is
15
+ * '.', a workspace is its directory) to the version the lockfile resolves for
16
+ * it. `versions` is every distinct version the lockfile holds, transitive
17
+ * copies included, oldest first: more than one means two copies of HDS.
18
+ */
19
+
20
+ export const HDS_PACKAGE = '@hirobius/design-system';
21
+
22
+ // ── tiny YAML subset ─────────────────────────────────────────────────────────
23
+ // pnpm lockfiles are block mappings of scalars. List items, flow collections
24
+ // and comments are kept as plain strings or skipped; nothing here needs them.
25
+
26
+ function unquote(value) {
27
+ const v = value.trim();
28
+ if (v.length >= 2 && (v[0] === "'" || v[0] === '"') && v.at(-1) === v[0]) {
29
+ return v[0] === "'" ? v.slice(1, -1).replace(/''/g, "'") : v.slice(1, -1);
30
+ }
31
+ return v;
32
+ }
33
+
34
+ /** Split `key: value` (the key may be quoted and contain colons). */
35
+ function splitKey(line) {
36
+ if (line[0] === "'" || line[0] === '"') {
37
+ const quote = line[0];
38
+ let i = 1;
39
+ while (i < line.length) {
40
+ if (line[i] === quote) {
41
+ if (quote === "'" && line[i + 1] === "'") {
42
+ i += 2;
43
+ continue;
44
+ }
45
+ break;
46
+ }
47
+ i += 1;
48
+ }
49
+ if (line[i + 1] !== ':') return null;
50
+ return { key: unquote(line.slice(0, i + 1)), rest: line.slice(i + 2).trim() };
51
+ }
52
+ const m = /^([^:]+?):(?:\s+(.*))?$/.exec(line);
53
+ return m ? { key: m[1].trim(), rest: (m[2] ?? '').trim() } : null;
54
+ }
55
+
56
+ /** Nested plain objects from an indentation-structured YAML mapping. */
57
+ function parseYamlMap(text) {
58
+ const root = {};
59
+ const stack = [{ indent: -1, node: root }];
60
+ for (const raw of text.split(/\r?\n/)) {
61
+ const content = raw.trimStart();
62
+ if (!content || content.startsWith('#') || content.startsWith('- ')) continue;
63
+ const indent = raw.length - content.length;
64
+ const kv = splitKey(content);
65
+ if (!kv) continue;
66
+ while (stack.length > 1 && stack.at(-1).indent >= indent) stack.pop();
67
+ const parent = stack.at(-1).node;
68
+ if (kv.rest === '') {
69
+ const child = {};
70
+ parent[kv.key] = child;
71
+ stack.push({ indent, node: child });
72
+ } else {
73
+ parent[kv.key] = unquote(kv.rest);
74
+ }
75
+ }
76
+ return root;
77
+ }
78
+
79
+ // ── versions ─────────────────────────────────────────────────────────────────
80
+
81
+ const SEMVER = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
82
+
83
+ /** -1/0/1-signed difference of two semver versions; a pre-release sorts before its release. */
84
+ export function compareVersions(a, b) {
85
+ const [coreA, preA = ''] = a.split('-');
86
+ const [coreB, preB = ''] = b.split('-');
87
+ const na = coreA.split('.').map(Number);
88
+ const nb = coreB.split('.').map(Number);
89
+ for (let i = 0; i < 3; i += 1) if (na[i] !== nb[i]) return na[i] - nb[i];
90
+ if (preA === preB) return 0;
91
+ if (!preA) return 1;
92
+ if (!preB) return -1;
93
+ return preA < preB ? -1 : 1;
94
+ }
95
+
96
+ function distinctSorted(list) {
97
+ return [...new Set(list.filter((v) => v && SEMVER.test(v)))].sort(compareVersions);
98
+ }
99
+
100
+ // ── pnpm (lockfile v6 and v9) ────────────────────────────────────────────────
101
+
102
+ const IMPORTER_FIELDS = ['dependencies', 'devDependencies', 'optionalDependencies'];
103
+
104
+ /** `0.16.0(react@18.3.1)`, `/@x/y@0.16.0(…)` or v5's `0.16.0_react@18` → `0.16.0`. */
105
+ function pnpmVersion(ref, name) {
106
+ if (typeof ref !== 'string') return null;
107
+ let v = ref.replace(/^\//, '');
108
+ if (v.startsWith(`${name}@`)) v = v.slice(name.length + 1);
109
+ v = v.replace(/\(.*$/, '').replace(/_.*$/, '');
110
+ return SEMVER.test(v) ? v : null;
111
+ }
112
+
113
+ function parsePnpm(text, name) {
114
+ const doc = parseYamlMap(text);
115
+ // v9 (and multi-package v6) list importers; a single-package v6 lockfile
116
+ // keeps the root's dependencies at the top level.
117
+ const importerMap =
118
+ doc.importers && typeof doc.importers === 'object' ? doc.importers : { '.': doc };
119
+ const importers = {};
120
+ for (const [dir, entry] of Object.entries(importerMap)) {
121
+ for (const field of IMPORTER_FIELDS) {
122
+ const dep = entry?.[field]?.[name];
123
+ if (dep === undefined) continue;
124
+ importers[dir] = pnpmVersion(typeof dep === 'object' ? dep.version : dep, name);
125
+ break;
126
+ }
127
+ }
128
+ const found = [];
129
+ for (const section of ['packages', 'snapshots']) {
130
+ for (const key of Object.keys(doc[section] ?? {})) {
131
+ const bare = key.replace(/^\//, '');
132
+ if (bare.startsWith(`${name}@`)) found.push(pnpmVersion(bare, name));
133
+ }
134
+ }
135
+ found.push(...Object.values(importers));
136
+ return {
137
+ kind: 'pnpm',
138
+ version: String(doc.lockfileVersion ?? ''),
139
+ importers,
140
+ versions: distinctSorted(found),
141
+ };
142
+ }
143
+
144
+ // ── npm (package-lock.json / npm-shrinkwrap.json v2 and v3) ──────────────────
145
+
146
+ const DECLARING_FIELDS = [...IMPORTER_FIELDS, 'peerDependencies'];
147
+
148
+ const declares = (entry, name) => DECLARING_FIELDS.some((f) => entry?.[f]?.[name] !== undefined);
149
+
150
+ /** Node's lookup from `dir` upward: dir/node_modules/<name>, then each parent's. */
151
+ function npmLookup(packages, dir, name) {
152
+ const parts = dir ? dir.split('/') : [];
153
+ for (let i = parts.length; i >= 0; i -= 1) {
154
+ const base = parts.slice(0, i).join('/');
155
+ let entry = packages[`${base ? `${base}/` : ''}node_modules/${name}`];
156
+ if (entry?.link && typeof entry.resolved === 'string') entry = packages[entry.resolved];
157
+ if (entry?.version) return entry.version;
158
+ }
159
+ return null;
160
+ }
161
+
162
+ function parseNpm(text, name) {
163
+ const doc = JSON.parse(text);
164
+ const packages = doc.packages ?? {};
165
+ const importers = {};
166
+ const found = [];
167
+ for (const [key, entry] of Object.entries(packages)) {
168
+ if (key === `node_modules/${name}` || key.endsWith(`/node_modules/${name}`)) {
169
+ found.push(entry?.version);
170
+ } else if (!key.split('/').includes('node_modules') && declares(entry, name)) {
171
+ importers[key || '.'] = npmLookup(packages, key, name);
172
+ }
173
+ }
174
+ return {
175
+ kind: 'npm',
176
+ version: String(doc.lockfileVersion ?? ''),
177
+ importers,
178
+ versions: distinctSorted(found),
179
+ };
180
+ }
181
+
182
+ // ── yarn (v1 and berry) ──────────────────────────────────────────────────────
183
+
184
+ const descriptorsOf = (key) => key.split(/,\s*/).map(unquote);
185
+
186
+ /** v1 is not YAML: `"a@^1", a@~1.2:` headers over ` version "1.2.3"` bodies. */
187
+ function yarnV1Entries(text) {
188
+ const entries = [];
189
+ let current = null;
190
+ for (const line of text.split(/\r?\n/)) {
191
+ if (!line.trim() || line.startsWith('#')) continue;
192
+ if (!/^\s/.test(line) && line.trimEnd().endsWith(':')) {
193
+ current = { descriptors: descriptorsOf(line.trimEnd().slice(0, -1)), version: null };
194
+ entries.push(current);
195
+ } else if (current && /^ {2}version /.test(line)) {
196
+ current.version = unquote(line.trim().slice('version '.length));
197
+ }
198
+ }
199
+ return entries;
200
+ }
201
+
202
+ function berryEntries(doc) {
203
+ return Object.entries(doc)
204
+ .filter(([key]) => key !== '__metadata')
205
+ .map(([key, entry]) => ({ descriptors: descriptorsOf(key), ...entry }));
206
+ }
207
+
208
+ function parseYarn(text, name, ranges) {
209
+ const berry = /^__metadata:/m.test(text);
210
+ const doc = berry ? parseYamlMap(text) : null;
211
+ const entries = berry ? berryEntries(doc) : yarnV1Entries(text);
212
+ const resolve = (range) => {
213
+ const wanted = [`${name}@${range}`, `${name}@npm:${range}`];
214
+ return entries.find((e) => wanted.some((d) => e.descriptors.includes(d)))?.version ?? null;
215
+ };
216
+ const importers = {};
217
+ if (berry) {
218
+ // Berry records each workspace as `<name>@workspace:<dir>` with its own deps.
219
+ for (const entry of entries) {
220
+ const ws = /@workspace:(.*)$/.exec(entry.resolution ?? '');
221
+ if (!ws) continue;
222
+ const range = DECLARING_FIELDS.map((f) => entry[f]?.[name]).find((r) => r !== undefined);
223
+ if (range !== undefined) importers[ws[1] || '.'] = resolve(range);
224
+ }
225
+ } else {
226
+ // v1 records no workspaces, so each importer's declared range is the key.
227
+ for (const [dir, range] of Object.entries(ranges)) importers[dir] = resolve(range);
228
+ }
229
+ const found = entries
230
+ .filter((e) => e.descriptors.some((d) => d.startsWith(`${name}@`)))
231
+ .map((e) => e.version);
232
+ return {
233
+ kind: 'yarn',
234
+ version: berry ? String(doc.__metadata?.version ?? '') : 'v1',
235
+ importers,
236
+ versions: distinctSorted(found),
237
+ };
238
+ }
239
+
240
+ // ── bun (text bun.lock; the binary bun.lockb is not readable here) ───────────
241
+
242
+ /** JSON.parse after dropping the trailing commas bun writes (strings left alone). */
243
+ function parseTrailingCommaJson(text) {
244
+ const closes = /\s*[}\]]/y;
245
+ const kept = [];
246
+ let from = 0;
247
+ let inString = false;
248
+ for (let i = 0; i < text.length; i += 1) {
249
+ const ch = text[i];
250
+ if (inString) {
251
+ if (ch === '\\') i += 1;
252
+ else if (ch === '"') inString = false;
253
+ } else if (ch === '"') {
254
+ inString = true;
255
+ } else if (ch === ',') {
256
+ closes.lastIndex = i + 1;
257
+ if (closes.test(text)) {
258
+ kept.push(text.slice(from, i));
259
+ from = i + 1;
260
+ }
261
+ }
262
+ }
263
+ kept.push(text.slice(from));
264
+ return JSON.parse(kept.join(''));
265
+ }
266
+
267
+ function parseBun(text, name) {
268
+ const doc = parseTrailingCommaJson(text);
269
+ const packages = doc.packages ?? {};
270
+ const versionAt = (key) => {
271
+ const ident = Array.isArray(packages[key]) ? packages[key][0] : null;
272
+ return typeof ident === 'string' && ident.startsWith(`${name}@`)
273
+ ? ident.slice(name.length + 1)
274
+ : null;
275
+ };
276
+ const importers = {};
277
+ for (const [dir, ws] of Object.entries(doc.workspaces ?? {})) {
278
+ if (!declares(ws, name)) continue;
279
+ // A copy only this workspace uses is keyed under its package name.
280
+ const keys = [ws.name && `${ws.name}/${name}`, dir && `${dir}/${name}`, name].filter(Boolean);
281
+ importers[dir || '.'] = keys.map(versionAt).find(Boolean) ?? null;
282
+ }
283
+ const found = Object.keys(packages)
284
+ .filter((key) => key === name || key.endsWith(`/${name}`))
285
+ .map(versionAt);
286
+ return {
287
+ kind: 'bun',
288
+ version: String(doc.lockfileVersion ?? ''),
289
+ importers,
290
+ versions: distinctSorted(found),
291
+ };
292
+ }
293
+
294
+ // ── which lockfile ───────────────────────────────────────────────────────────
295
+
296
+ /** Readable lockfiles by package manager, in the order each manager reads them. */
297
+ const LOCKFILES_BY_MANAGER = {
298
+ pnpm: ['pnpm-lock.yaml'],
299
+ npm: ['npm-shrinkwrap.json', 'package-lock.json'],
300
+ yarn: ['yarn.lock'],
301
+ bun: ['bun.lock'],
302
+ };
303
+
304
+ /** The package manager that writes a lockfile name, or null for any other file. */
305
+ export function lockfileKind(fileName) {
306
+ const entry = Object.entries(LOCKFILES_BY_MANAGER).find(([, files]) => files.includes(fileName));
307
+ return entry ? entry[0] : null;
308
+ }
309
+
310
+ /**
311
+ * The lockfile to read among the files at a project root, or null. With more
312
+ * than one present, package.json's `packageManager` (`yarn@4.5.0`) decides;
313
+ * otherwise pnpm, then npm, then yarn, then bun.
314
+ *
315
+ * @param {Iterable<string>} fileNames base names of the files at the root
316
+ * @param {string} [packageManager] package.json `packageManager`, if any
317
+ * @returns {string|null}
318
+ */
319
+ export function pickLockfile(fileNames, packageManager) {
320
+ const present = new Set(fileNames);
321
+ const preferred = String(packageManager ?? '').split('@')[0];
322
+ const order = [
323
+ ...(LOCKFILES_BY_MANAGER[preferred] ?? []),
324
+ ...Object.values(LOCKFILES_BY_MANAGER).flat(),
325
+ ];
326
+ return order.find((file) => present.has(file)) ?? null;
327
+ }
328
+
329
+ // ── dispatch ─────────────────────────────────────────────────────────────────
330
+
331
+ const READERS = {
332
+ 'bun.lock': parseBun,
333
+ 'pnpm-lock.yaml': parsePnpm,
334
+ 'package-lock.json': parseNpm,
335
+ 'npm-shrinkwrap.json': parseNpm,
336
+ 'yarn.lock': parseYarn,
337
+ };
338
+
339
+ /**
340
+ * Read one lockfile.
341
+ *
342
+ * @param {string} fileName the lockfile's base name (pnpm-lock.yaml, …)
343
+ * @param {string} text the lockfile's contents
344
+ * @param {{ name?: string, ranges?: Record<string, string> }} [options]
345
+ * `name`: the package to resolve (default HDS). `ranges`: importer directory
346
+ * → the range its package.json declares. Only yarn v1 needs it, because that
347
+ * lockfile records no importers; every other format ignores it.
348
+ * @returns {{ kind: string, version: string, importers: Record<string, string|null>, versions: string[] }}
349
+ */
350
+ export function readLockfile(fileName, text, { name = HDS_PACKAGE, ranges = {} } = {}) {
351
+ const reader = READERS[fileName];
352
+ if (!reader) throw new Error(`Unsupported lockfile: ${fileName}`);
353
+ return reader(String(text), name, ranges);
354
+ }