@gilav21/shadcn-angular 0.0.24 → 0.0.26

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.
@@ -0,0 +1,43 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ const __filename = fileURLToPath(import.meta.url);
5
+ const __dirname = path.dirname(__filename);
6
+ export function validateBranch(branch) {
7
+ if (!/^[\w.\-/]+$/.test(branch)) {
8
+ throw new Error(`Invalid branch name: ${branch}`);
9
+ }
10
+ }
11
+ function getDefaultRegistryBaseUrl(branch) {
12
+ validateBranch(branch);
13
+ return `https://raw.githubusercontent.com/gilav21/shadcn-angular/${branch}/packages/components`;
14
+ }
15
+ export function getRegistryBaseUrl(branch, customRegistry) {
16
+ const base = customRegistry ?? getDefaultRegistryBaseUrl(branch);
17
+ return `${base}/ui`;
18
+ }
19
+ export function getLibRegistryBaseUrl(branch, customRegistry) {
20
+ const base = customRegistry ?? getDefaultRegistryBaseUrl(branch);
21
+ return `${base}/lib`;
22
+ }
23
+ export function getLocalComponentsDir() {
24
+ const localPath = path.resolve(__dirname, '../../../../components/ui');
25
+ return fs.existsSync(localPath) ? localPath : null;
26
+ }
27
+ export function getLocalLibDir() {
28
+ const localPath = path.resolve(__dirname, '../../../../components/lib');
29
+ return fs.existsSync(localPath) ? localPath : null;
30
+ }
31
+ export function resolveProjectPath(cwd, inputPath) {
32
+ const resolved = path.resolve(cwd, inputPath);
33
+ const relative = path.relative(cwd, resolved);
34
+ if (relative.startsWith('..') || path.isAbsolute(relative)) {
35
+ throw new Error(`Path must stay inside the project directory: ${inputPath}`);
36
+ }
37
+ return resolved;
38
+ }
39
+ export function aliasToProjectPath(aliasOrPath) {
40
+ return aliasOrPath.startsWith('@/')
41
+ ? path.join('src', aliasOrPath.slice(2))
42
+ : aliasOrPath;
43
+ }
@@ -1,18 +1,6 @@
1
1
  import fs from 'fs-extra';
2
2
  import path from 'node:path';
3
- function aliasToProjectPath(aliasOrPath) {
4
- return aliasOrPath.startsWith('@/')
5
- ? path.join('src', aliasOrPath.slice(2))
6
- : aliasOrPath;
7
- }
8
- function resolveProjectPath(cwd, inputPath) {
9
- const resolved = path.resolve(cwd, inputPath);
10
- const relative = path.relative(cwd, resolved);
11
- if (relative.startsWith('..') || path.isAbsolute(relative)) {
12
- throw new Error(`Path must stay inside the project directory: ${inputPath}`);
13
- }
14
- return resolved;
15
- }
3
+ import { resolveProjectPath, aliasToProjectPath } from './paths.js';
16
4
  function getShortcutRegistryIndexPath(cwd, config) {
17
5
  const libDir = resolveProjectPath(cwd, aliasToProjectPath(config.aliases.utils));
18
6
  return path.join(libDir, 'shortcut-registry.index.ts');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gilav21/shadcn-angular",
3
- "version": "0.0.24",
3
+ "version": "0.0.26",
4
4
  "description": "CLI for adding shadcn-angular components to your project",
5
5
  "bin": {
6
6
  "shadcn-angular": "./dist/index.js"
@@ -0,0 +1,347 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * Recursively walks imports from each component's entry file,
4
+ * stopping at other component boundaries to discover only the
5
+ * files that belong to THIS component's tree.
6
+ *
7
+ * Usage:
8
+ * npx tsx packages/cli/scripts/sync-registry.ts # report only
9
+ * npx tsx packages/cli/scripts/sync-registry.ts --fix # update registry
10
+ */
11
+
12
+ import { readFileSync, existsSync, writeFileSync } from 'node:fs';
13
+ import path from 'node:path';
14
+ import { fileURLToPath } from 'node:url';
15
+
16
+ const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
17
+ const CLI_SRC = path.resolve(SCRIPT_DIR, '../src');
18
+ const COMPONENTS_ROOT = path.resolve(SCRIPT_DIR, '../../components');
19
+ const REGISTRY_PATH = path.join(CLI_SRC, 'registry/index.ts');
20
+
21
+ const IMPORT_REGEX = /from\s+['"](\.[^'"]+)['"]/g;
22
+
23
+ // utils.ts is always installed during init — not a component libFile
24
+ const BASELINE_LIB_FILES = new Set(['utils.ts']);
25
+
26
+ // ── Parse registry ──────────────────────────────────────────────────────
27
+
28
+ interface RegistryEntry {
29
+ name: string;
30
+ files: string[];
31
+ libFiles: string[];
32
+ dependencies: string[];
33
+ }
34
+
35
+ function parseRegistry(): RegistryEntry[] {
36
+ const source = readFileSync(REGISTRY_PATH, 'utf-8');
37
+ const entries: RegistryEntry[] = [];
38
+
39
+ const blockRegex = /['"]?([\w-]+)['"]?\s*:\s*\{[^}]*?name:\s*['"]([^'"]+)['"][^}]*?files:\s*\[([\s\S]*?)\]/g;
40
+ let match: RegExpExecArray | null;
41
+
42
+ while ((match = blockRegex.exec(source)) !== null) {
43
+ const name = match[2];
44
+ const filesRaw = match[3];
45
+ const files = [...filesRaw.matchAll(/['"]([^'"]+)['"]/g)].map(m => m[1]);
46
+
47
+ const blockEnd = source.indexOf('},', match.index + match[0].length);
48
+ const fullBlock = source.slice(match.index, blockEnd === -1 ? undefined : blockEnd);
49
+ const libFilesMatch = /libFiles:\s*\[([\s\S]*?)\]/.exec(fullBlock);
50
+ const libFiles = libFilesMatch
51
+ ? [...libFilesMatch[1].matchAll(/['"]([^'"]+)['"]/g)].map(m => m[1])
52
+ : [];
53
+
54
+ const depsMatch = /dependencies:\s*\[([\s\S]*?)\]/.exec(fullBlock);
55
+ const dependencies = depsMatch
56
+ ? [...depsMatch[1].matchAll(/['"]([^'"]+)['"]/g)].map(m => m[1])
57
+ : [];
58
+
59
+ entries.push({ name, files, libFiles, dependencies });
60
+ }
61
+
62
+ return entries;
63
+ }
64
+
65
+ // ── Resolve imports ─────────────────────────────────────────────────────
66
+
67
+ function resolveImport(importPath: string, fromFile: string): string | null {
68
+ const fromDir = path.dirname(path.join(COMPONENTS_ROOT, fromFile));
69
+ let resolved = path.resolve(fromDir, importPath);
70
+
71
+ if (!existsSync(resolved)) resolved += '.ts';
72
+ if (!existsSync(resolved)) return null;
73
+
74
+ return path.relative(COMPONENTS_ROOT, resolved).replaceAll('\\', '/');
75
+ }
76
+
77
+ // ── Tree walker with component boundaries ───────────────────────────────
78
+
79
+ interface WalkResult {
80
+ ownFiles: Set<string>;
81
+ discoveredDeps: Set<string>;
82
+ }
83
+
84
+ function walkTree(
85
+ entryFile: string,
86
+ componentName: string,
87
+ entryFileToComponent: Map<string, string>,
88
+ ): WalkResult {
89
+ const ownFiles = new Set<string>();
90
+ const discoveredDeps = new Set<string>();
91
+
92
+ function collect(file: string): void {
93
+ if (ownFiles.has(file)) return;
94
+
95
+ // If this file is the entry point of ANOTHER component → dependency, stop
96
+ const ownerComponent = entryFileToComponent.get(file);
97
+ if (ownerComponent && ownerComponent !== componentName) {
98
+ discoveredDeps.add(ownerComponent);
99
+ return;
100
+ }
101
+
102
+ ownFiles.add(file);
103
+
104
+ const fullPath = path.join(COMPONENTS_ROOT, file);
105
+ if (!existsSync(fullPath)) return;
106
+
107
+ const content = readFileSync(fullPath, 'utf-8');
108
+ const regex = new RegExp(IMPORT_REGEX.source, IMPORT_REGEX.flags);
109
+ let match: RegExpExecArray | null;
110
+
111
+ while ((match = regex.exec(content)) !== null) {
112
+ const resolved = resolveImport(match[1], file);
113
+ if (resolved) collect(resolved);
114
+ }
115
+ }
116
+
117
+ collect(entryFile);
118
+ return { ownFiles, discoveredDeps };
119
+ }
120
+
121
+ // ── Split files into ui/ and lib/ ───────────────────────────────────────
122
+
123
+ function splitFiles(allFiles: Set<string>): { uiFiles: string[]; libFiles: string[] } {
124
+ const uiFiles: string[] = [];
125
+ const libFiles: string[] = [];
126
+
127
+ for (const file of allFiles) {
128
+ if (file.startsWith('ui/')) {
129
+ uiFiles.push(file.slice(3));
130
+ } else if (file.startsWith('lib/')) {
131
+ const libName = file.slice(4);
132
+ if (!BASELINE_LIB_FILES.has(libName)) {
133
+ libFiles.push(libName);
134
+ }
135
+ }
136
+ }
137
+
138
+ uiFiles.sort((a, b) => a.localeCompare(b));
139
+ libFiles.sort((a, b) => a.localeCompare(b));
140
+ return { uiFiles, libFiles };
141
+ }
142
+
143
+ // ── Helpers ─────────────────────────────────────────────────────────────
144
+
145
+ function getEntryFile(entry: RegistryEntry): string {
146
+ // Barrel index.ts re-exports everything — use it when available
147
+ const indexFile = entry.files.find(f => f.endsWith('index.ts'));
148
+ if (indexFile) return indexFile;
149
+
150
+ // Convention: main file is {name}.component.ts or {name}.directive.ts
151
+ const baseName = entry.name.split('/').at(-1) ?? entry.name;
152
+ const conventionFile = entry.files.find(f =>
153
+ f.endsWith(`${baseName}.component.ts`) || f.endsWith(`${baseName}.directive.ts`),
154
+ );
155
+ return conventionFile ?? entry.files[0];
156
+ }
157
+
158
+ interface ComponentUpdate {
159
+ name: string;
160
+ files: string[];
161
+ libFiles: string[];
162
+ dependencies: string[];
163
+ }
164
+
165
+ function buildBoundaryMap(entries: RegistryEntry[]): Map<string, string> {
166
+ const map = new Map<string, string>();
167
+ for (const entry of entries) {
168
+ map.set('ui/' + getEntryFile(entry), entry.name);
169
+ }
170
+ return map;
171
+ }
172
+
173
+ function analyzeComponent(
174
+ entry: RegistryEntry,
175
+ entryFileToComponent: Map<string, string>,
176
+ ): { update: ComponentUpdate; changed: boolean } {
177
+ const entryFile = 'ui/' + getEntryFile(entry);
178
+ const { ownFiles, discoveredDeps } = walkTree(entryFile, entry.name, entryFileToComponent);
179
+ const { uiFiles, libFiles: discoveredLibs } = splitFiles(ownFiles);
180
+
181
+ const mergedLibFiles = [...new Set([...entry.libFiles, ...discoveredLibs])];
182
+ mergedLibFiles.sort((a, b) => a.localeCompare(b));
183
+
184
+ const finalDeps = [...discoveredDeps].sort((a, b) => a.localeCompare(b));
185
+
186
+ const addedFiles = uiFiles.filter(f => !entry.files.includes(f));
187
+ const removedFiles = entry.files.filter(f => !uiFiles.includes(f));
188
+ const addedLibs = discoveredLibs.filter(f => !entry.libFiles.includes(f));
189
+ const addedDeps = finalDeps.filter(d => !entry.dependencies.includes(d));
190
+ const removedDeps = entry.dependencies.filter(d => !finalDeps.includes(d));
191
+ const changed = addedFiles.length > 0 || removedFiles.length > 0
192
+ || addedLibs.length > 0 || addedDeps.length > 0 || removedDeps.length > 0;
193
+
194
+ if (changed) {
195
+ console.log(` ${entry.name}:`);
196
+ for (const f of addedFiles) console.log(` + files: ${f}`);
197
+ for (const f of removedFiles) console.log(` - files: ${f}`);
198
+ for (const f of addedLibs) console.log(` + libFiles: ${f}`);
199
+ for (const d of addedDeps) console.log(` + dependencies: ${d}`);
200
+ for (const d of removedDeps) console.log(` - dependencies: ${d}`);
201
+ }
202
+
203
+ return {
204
+ update: { name: entry.name, files: uiFiles, libFiles: mergedLibFiles, dependencies: finalDeps },
205
+ changed,
206
+ };
207
+ }
208
+
209
+ function findNamePos(source: string, name: string): number {
210
+ const singleQuote = source.indexOf(`name: '${name}'`);
211
+ if (singleQuote >= 0) return singleQuote;
212
+ return source.indexOf(`name: "${name}"`);
213
+ }
214
+
215
+ function replaceFilesArray(source: string, name: string, filesArrayStr: string): string {
216
+ const namePos = findNamePos(source, name);
217
+ if (namePos === -1) return source;
218
+
219
+ const filesStart = source.indexOf('files: [', namePos);
220
+ if (filesStart === -1) return source;
221
+ const filesEnd = source.indexOf(']', filesStart + 8);
222
+ if (filesEnd === -1) return source;
223
+
224
+ return source.slice(0, filesStart + 8) + filesArrayStr + source.slice(filesEnd);
225
+ }
226
+
227
+ function updateLibFiles(source: string, name: string, libArrayStr: string): string {
228
+ const updatedNamePos = findNamePos(source, name);
229
+ if (updatedNamePos === -1) return source;
230
+ const nextNamePos = source.indexOf('name: ', updatedNamePos + name.length + 8);
231
+ const blockEnd = nextNamePos === -1 ? source.length : nextNamePos;
232
+ const blockSlice = source.slice(updatedNamePos, blockEnd);
233
+
234
+ const libOffset = blockSlice.indexOf('libFiles: [');
235
+ if (libOffset >= 0) {
236
+ const absLibStart = updatedNamePos + libOffset;
237
+ const libEnd = source.indexOf(']', absLibStart + 11);
238
+ return source.slice(0, absLibStart + 11) + libArrayStr + source.slice(libEnd);
239
+ }
240
+
241
+ const absFilesStart = source.indexOf('files: [', updatedNamePos);
242
+ const absFilesEnd = source.indexOf(']', absFilesStart + 8);
243
+ return source.slice(0, absFilesEnd + 1) + `,\n libFiles: [${libArrayStr}]` + source.slice(absFilesEnd + 1);
244
+ }
245
+
246
+ function updateDependencies(source: string, name: string, depsArrayStr: string): string {
247
+ const updatedNamePos = findNamePos(source, name);
248
+ if (updatedNamePos === -1) return source;
249
+ const nextNamePos = source.indexOf('name: ', updatedNamePos + name.length + 8);
250
+ const blockEnd = nextNamePos === -1 ? source.length : nextNamePos;
251
+ const blockSlice = source.slice(updatedNamePos, blockEnd);
252
+
253
+ const depsOffset = blockSlice.indexOf('dependencies: [');
254
+ if (depsOffset >= 0) {
255
+ const absDepsStart = updatedNamePos + depsOffset;
256
+ const depsEnd = source.indexOf(']', absDepsStart + 15);
257
+ return source.slice(0, absDepsStart + 15) + depsArrayStr + source.slice(depsEnd);
258
+ }
259
+
260
+ // Insert after files array (or after libFiles if present)
261
+ const libOffset = blockSlice.indexOf('libFiles: [');
262
+ if (libOffset >= 0) {
263
+ const absLibStart = updatedNamePos + libOffset;
264
+ const libEnd = source.indexOf(']', absLibStart + 11);
265
+ return source.slice(0, libEnd + 1) + `,\n dependencies: [${depsArrayStr}]` + source.slice(libEnd + 1);
266
+ }
267
+
268
+ const absFilesStart = source.indexOf('files: [', updatedNamePos);
269
+ const absFilesEnd = source.indexOf(']', absFilesStart + 8);
270
+ return source.slice(0, absFilesEnd + 1) + `,\n dependencies: [${depsArrayStr}]` + source.slice(absFilesEnd + 1);
271
+ }
272
+
273
+ function removeDependencies(source: string, name: string): string {
274
+ const namePos = findNamePos(source, name);
275
+ if (namePos === -1) return source;
276
+ const nextNamePos = source.indexOf('name: ', namePos + name.length + 8);
277
+ const blockEnd = nextNamePos === -1 ? source.length : nextNamePos;
278
+ const blockSlice = source.slice(namePos, blockEnd);
279
+
280
+ // Match leading comma + whitespace + dependencies: [...] but NOT trailing comma
281
+ const depsRegex = /,?\s*dependencies:\s*\[[\s\S]*?\]/;
282
+ const depsMatch = depsRegex.exec(blockSlice);
283
+ if (!depsMatch) return source;
284
+
285
+ const absStart = namePos + depsMatch.index;
286
+ const absEnd = absStart + depsMatch[0].length;
287
+ return source.slice(0, absStart) + source.slice(absEnd);
288
+ }
289
+
290
+ function applyUpdates(updates: ComponentUpdate[]): void {
291
+ let source = readFileSync(REGISTRY_PATH, 'utf-8');
292
+
293
+ for (const update of updates) {
294
+ const filesArrayStr = update.files.map(f => `'${f}'`).join(', ');
295
+ source = replaceFilesArray(source, update.name, filesArrayStr);
296
+
297
+ if (update.libFiles.length > 0) {
298
+ const libArrayStr = update.libFiles.map(f => `'${f}'`).join(', ');
299
+ source = updateLibFiles(source, update.name, libArrayStr);
300
+ }
301
+
302
+ if (update.dependencies.length > 0) {
303
+ const depsStr = update.dependencies.map(d => `'${d}'`).join(', ');
304
+ source = updateDependencies(source, update.name, depsStr);
305
+ } else {
306
+ source = removeDependencies(source, update.name);
307
+ }
308
+ }
309
+
310
+ writeFileSync(REGISTRY_PATH, source);
311
+ console.log('Registry updated.');
312
+ }
313
+
314
+ // ── Main ────────────────────────────────────────────────────────────────
315
+
316
+ function main(): void {
317
+ const fix = process.argv.includes('--fix');
318
+ const entries = parseRegistry();
319
+ const entryFileToComponent = buildBoundaryMap(entries);
320
+
321
+ console.log(`Scanning ${entries.length} components...\n`);
322
+
323
+ let hasChanges = false;
324
+ const updates: ComponentUpdate[] = [];
325
+
326
+ for (const entry of entries) {
327
+ const { update, changed } = analyzeComponent(entry, entryFileToComponent);
328
+ if (changed) hasChanges = true;
329
+ updates.push(update);
330
+ }
331
+
332
+ if (!hasChanges) {
333
+ console.log('All components are in sync.');
334
+ return;
335
+ }
336
+
337
+ console.log('');
338
+
339
+ if (fix) {
340
+ applyUpdates(updates);
341
+ } else {
342
+ console.log('Run with --fix to update the registry.');
343
+ process.exitCode = 1;
344
+ }
345
+ }
346
+
347
+ main();
@@ -7,9 +7,8 @@ import {
7
7
  checkFileConflict,
8
8
  classifyComponent,
9
9
  detectConflicts,
10
- type AddOptions,
11
10
  } from './add.js';
12
- import { registry, type ComponentName, type ComponentDefinition } from '../registry/index.js';
11
+ import { registry, type ComponentName } from '../registry/index.js';
13
12
  import fs from 'fs-extra';
14
13
 
15
14
  // ---------------------------------------------------------------------------
@@ -353,22 +352,22 @@ describe('detectConflicts', () => {
353
352
  describe('peer file filtering logic', () => {
354
353
  it('removes peer files of declined components from peerFilesToUpdate', () => {
355
354
  const peerFilesToUpdate = new Set(['shared/peer-a.ts', 'shared/peer-b.ts', 'shared/peer-c.ts']);
356
- const conflicting: string[] = ['compA', 'compB'];
357
- const toOverwrite: string[] = ['compA'];
358
- const finalComponents: string[] = ['compA'];
355
+ const conflicting = new Set(['compA', 'compB']);
356
+ const toOverwrite = new Set(['compA']);
357
+ const finalComponents = new Set(['compA']);
359
358
 
360
359
  const mockPeerFiles: Record<string, string[] | undefined> = {
361
360
  compA: ['shared/peer-a.ts'],
362
361
  compB: ['shared/peer-b.ts', 'shared/peer-c.ts'],
363
362
  };
364
363
 
365
- const declined = conflicting.filter(c => !toOverwrite.includes(c));
364
+ const declined = [...conflicting].filter(c => !toOverwrite.has(c));
366
365
 
367
366
  for (const name of declined) {
368
367
  const peerFiles = mockPeerFiles[name];
369
368
  if (!peerFiles) continue;
370
369
  for (const file of peerFiles) {
371
- const stillNeeded = finalComponents.some(fc =>
370
+ const stillNeeded = [...finalComponents].some(fc =>
372
371
  mockPeerFiles[fc]?.includes(file),
373
372
  );
374
373
  if (!stillNeeded) {
@@ -384,22 +383,22 @@ describe('peer file filtering logic', () => {
384
383
 
385
384
  it('keeps shared peer files when another final component still needs them', () => {
386
385
  const peerFilesToUpdate = new Set(['shared/common.ts']);
387
- const conflicting: string[] = ['compA', 'compB'];
388
- const toOverwrite: string[] = ['compA'];
389
- const finalComponents: string[] = ['compA'];
386
+ const conflicting = new Set(['compA', 'compB']);
387
+ const toOverwrite = new Set(['compA']);
388
+ const finalComponents = new Set(['compA']);
390
389
 
391
390
  const mockPeerFiles: Record<string, string[] | undefined> = {
392
391
  compA: ['shared/common.ts'],
393
392
  compB: ['shared/common.ts'],
394
393
  };
395
394
 
396
- const declined = conflicting.filter(c => !toOverwrite.includes(c));
395
+ const declined = [...conflicting].filter(c => !toOverwrite.has(c));
397
396
 
398
397
  for (const name of declined) {
399
398
  const peerFiles = mockPeerFiles[name];
400
399
  if (!peerFiles) continue;
401
400
  for (const file of peerFiles) {
402
- const stillNeeded = finalComponents.some(fc =>
401
+ const stillNeeded = [...finalComponents].some(fc =>
403
402
  mockPeerFiles[fc]?.includes(file),
404
403
  );
405
404
  if (!stillNeeded) {
@@ -413,27 +412,18 @@ describe('peer file filtering logic', () => {
413
412
 
414
413
  it('removes all peer files when all components are declined', () => {
415
414
  const peerFilesToUpdate = new Set(['shared/peer-a.ts', 'shared/peer-b.ts']);
416
- const conflicting: string[] = ['compA', 'compB'];
417
- const toOverwrite: string[] = [];
418
- const finalComponents: string[] = [];
415
+ const declined = ['compA', 'compB'];
419
416
 
420
417
  const mockPeerFiles: Record<string, string[] | undefined> = {
421
418
  compA: ['shared/peer-a.ts'],
422
419
  compB: ['shared/peer-b.ts'],
423
420
  };
424
421
 
425
- const declined = conflicting.filter(c => !toOverwrite.includes(c));
426
-
427
422
  for (const name of declined) {
428
423
  const peerFiles = mockPeerFiles[name];
429
424
  if (!peerFiles) continue;
430
425
  for (const file of peerFiles) {
431
- const stillNeeded = finalComponents.some(fc =>
432
- mockPeerFiles[fc]?.includes(file),
433
- );
434
- if (!stillNeeded) {
435
- peerFilesToUpdate.delete(file);
436
- }
426
+ peerFilesToUpdate.delete(file);
437
427
  }
438
428
  }
439
429
 
@@ -468,9 +458,9 @@ describe('resolveDependencies', () => {
468
458
  });
469
459
 
470
460
  it('deduplicates shared dependencies across multiple inputs', () => {
471
- const result = resolveDependencies(['button-group', 'speed-dial']);
472
- expect(result).toContain('button-group');
473
- expect(result).toContain('speed-dial');
461
+ const result = resolveDependencies(['date-picker', 'sparkles']);
462
+ expect(result).toContain('date-picker');
463
+ expect(result).toContain('sparkles');
474
464
  expect(result).toContain('button');
475
465
  expect(result).toContain('ripple');
476
466
 
@@ -547,25 +537,25 @@ describe('registry optional dependencies', () => {
547
537
  });
548
538
 
549
539
  it('every optional dependency name is a valid registry key', () => {
550
- for (const [componentName, definition] of Object.entries(registry) as [string, ComponentDefinition][]) {
540
+ for (const [componentName, definition] of Object.entries(registry)) {
551
541
  if (!definition.optionalDependencies) continue;
552
542
  for (const opt of definition.optionalDependencies) {
553
543
  expect(
554
- registry[opt.name],
544
+ opt.name in registry,
555
545
  `Optional dep "${opt.name}" in "${componentName}" is not a valid registry key`,
556
- ).toBeDefined();
546
+ ).toBe(true);
557
547
  }
558
548
  }
559
549
  });
560
550
 
561
551
  it('every dependency name is a valid registry key', () => {
562
- for (const [componentName, definition] of Object.entries(registry) as [string, ComponentDefinition][]) {
552
+ for (const [componentName, definition] of Object.entries(registry)) {
563
553
  if (!definition.dependencies) continue;
564
554
  for (const dep of definition.dependencies) {
565
555
  expect(
566
- registry[dep],
556
+ dep in registry,
567
557
  `Dependency "${dep}" in "${componentName}" is not a valid registry key`,
568
- ).toBeDefined();
558
+ ).toBe(true);
569
559
  }
570
560
  }
571
561
  });