@gilav21/shadcn-angular 0.0.14 → 0.0.16

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.
@@ -7,10 +7,12 @@ import ora from 'ora';
7
7
  import { getConfig } from '../utils/config.js';
8
8
  import { registry } from '../registry/index.js';
9
9
  import { installPackages } from '../utils/package-manager.js';
10
+ import { writeShortcutRegistryIndex } from '../utils/shortcut-registry.js';
10
11
  const __filename = fileURLToPath(import.meta.url);
11
12
  const __dirname = path.dirname(__filename);
12
13
  // Base URL for the component registry (GitHub raw content)
13
14
  const REGISTRY_BASE_URL = 'https://raw.githubusercontent.com/gilav21/shadcn-angular/master/packages/components/ui';
15
+ const LIB_REGISTRY_BASE_URL = 'https://raw.githubusercontent.com/gilav21/shadcn-angular/master/packages/components/lib';
14
16
  // Components source directory (relative to CLI dist folder) for local dev
15
17
  function getLocalComponentsDir() {
16
18
  // From dist/commands/add.js -> packages/components/ui
@@ -25,6 +27,26 @@ function getLocalComponentsDir() {
25
27
  }
26
28
  return null;
27
29
  }
30
+ function getLocalLibDir() {
31
+ const fromDist = path.resolve(__dirname, '../../../components/lib');
32
+ if (fs.existsSync(fromDist)) {
33
+ return fromDist;
34
+ }
35
+ return null;
36
+ }
37
+ function resolveProjectPath(cwd, inputPath) {
38
+ const resolved = path.resolve(cwd, inputPath);
39
+ const relative = path.relative(cwd, resolved);
40
+ if (relative.startsWith('..') || path.isAbsolute(relative)) {
41
+ throw new Error(`Path must stay inside the project directory: ${inputPath}`);
42
+ }
43
+ return resolved;
44
+ }
45
+ function aliasToProjectPath(aliasOrPath) {
46
+ return aliasOrPath.startsWith('@/')
47
+ ? path.join('src', aliasOrPath.slice(2))
48
+ : aliasOrPath;
49
+ }
28
50
  async function fetchComponentContent(file, options) {
29
51
  const localDir = getLocalComponentsDir();
30
52
  // 1. Prefer local if available and not forced remote
@@ -50,6 +72,36 @@ async function fetchComponentContent(file, options) {
50
72
  throw error;
51
73
  }
52
74
  }
75
+ async function fetchLibContent(file, options) {
76
+ const localDir = getLocalLibDir();
77
+ if (localDir && !options.remote) {
78
+ const localPath = path.join(localDir, file);
79
+ if (await fs.pathExists(localPath)) {
80
+ return fs.readFile(localPath, 'utf-8');
81
+ }
82
+ }
83
+ const url = `${LIB_REGISTRY_BASE_URL}/${file}`;
84
+ const response = await fetch(url);
85
+ if (!response.ok) {
86
+ throw new Error(`Failed to fetch library file from ${url}: ${response.statusText}`);
87
+ }
88
+ return response.text();
89
+ }
90
+ function collectInstalledShortcutEntries(targetDir) {
91
+ const entries = [];
92
+ for (const definition of Object.values(registry)) {
93
+ if (!definition.shortcutDefinitions?.length) {
94
+ continue;
95
+ }
96
+ for (const shortcutDefinition of definition.shortcutDefinitions) {
97
+ const sourcePath = path.join(targetDir, shortcutDefinition.sourceFile);
98
+ if (fs.existsSync(sourcePath)) {
99
+ entries.push(shortcutDefinition);
100
+ }
101
+ }
102
+ }
103
+ return entries;
104
+ }
53
105
  export async function add(components, options) {
54
106
  const cwd = process.cwd();
55
107
  // Load config
@@ -103,9 +155,8 @@ export async function add(components, options) {
103
155
  }
104
156
  };
105
157
  componentsToAdd.forEach(c => resolveDeps(c));
106
- const targetDir = options.path
107
- ? path.join(cwd, options.path)
108
- : path.join(cwd, 'src/components/ui');
158
+ const uiBasePath = options.path ?? aliasToProjectPath(config.aliases.ui || 'src/components/ui');
159
+ const targetDir = resolveProjectPath(cwd, uiBasePath);
109
160
  // Check for existing files and diff
110
161
  const componentsToInstall = [];
111
162
  const componentsToSkip = [];
@@ -125,7 +176,7 @@ export async function add(components, options) {
125
176
  // Transform imports for comparison
126
177
  const utilsAlias = config.aliases.utils;
127
178
  remoteContent = remoteContent.replace(/(\.\.\/)+lib\/utils/g, utilsAlias);
128
- const normalize = (str) => str.replace(/\s+/g, '').trim();
179
+ const normalize = (str) => str.replace(/\r\n/g, '\n').trim();
129
180
  if (normalize(localContent) !== normalize(remoteContent)) {
130
181
  hasChanges = true;
131
182
  }
@@ -252,6 +303,18 @@ export async function add(components, options) {
252
303
  }
253
304
  }
254
305
  }
306
+ const shortcutEntries = collectInstalledShortcutEntries(targetDir);
307
+ if (shortcutEntries.length > 0) {
308
+ const utilsPathResolved = resolveProjectPath(cwd, aliasToProjectPath(config.aliases.utils) + '.ts');
309
+ const utilsDir = path.dirname(utilsPathResolved);
310
+ const shortcutServicePath = path.join(utilsDir, 'shortcut-binding.service.ts');
311
+ if (!await fs.pathExists(shortcutServicePath)) {
312
+ const shortcutServiceContent = await fetchLibContent('shortcut-binding.service.ts', options);
313
+ await fs.ensureDir(utilsDir);
314
+ await fs.writeFile(shortcutServicePath, shortcutServiceContent);
315
+ }
316
+ }
317
+ await writeShortcutRegistryIndex(cwd, config, shortcutEntries);
255
318
  if (componentsToSkip.length > 0) {
256
319
  console.log('\n' + chalk.dim('Components skipped (up to date):'));
257
320
  componentsToSkip.forEach(name => {
@@ -1,12 +1,53 @@
1
1
  import fs from 'fs-extra';
2
2
  import path from 'path';
3
+ import { fileURLToPath } from 'url';
3
4
  import prompts from 'prompts';
4
5
  import chalk from 'chalk';
5
6
  import ora from 'ora';
6
- import { execa } from 'execa';
7
7
  import { getDefaultConfig } from '../utils/config.js';
8
8
  import { getStylesTemplate } from '../templates/styles.js';
9
9
  import { getUtilsTemplate } from '../templates/utils.js';
10
+ import { installPackages } from '../utils/package-manager.js';
11
+ import { writeShortcutRegistryIndex } from '../utils/shortcut-registry.js';
12
+ const LIB_REGISTRY_BASE_URL = 'https://raw.githubusercontent.com/gilav21/shadcn-angular/master/packages/components/lib';
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = path.dirname(__filename);
15
+ function getLocalLibDir() {
16
+ const fromDist = path.resolve(__dirname, '../../../components/lib');
17
+ if (fs.existsSync(fromDist)) {
18
+ return fromDist;
19
+ }
20
+ return null;
21
+ }
22
+ async function fetchLibFileContent(file) {
23
+ const localLibDir = getLocalLibDir();
24
+ if (localLibDir) {
25
+ const localPath = path.join(localLibDir, file);
26
+ if (await fs.pathExists(localPath)) {
27
+ return fs.readFile(localPath, 'utf-8');
28
+ }
29
+ }
30
+ const url = `${LIB_REGISTRY_BASE_URL}/${file}`;
31
+ const response = await fetch(url);
32
+ if (!response.ok) {
33
+ throw new Error(`Failed to fetch library file from ${url}: ${response.statusText}`);
34
+ }
35
+ return response.text();
36
+ }
37
+ function resolveProjectPath(cwd, inputPath) {
38
+ const resolved = path.resolve(cwd, inputPath);
39
+ const relative = path.relative(cwd, resolved);
40
+ if (relative.startsWith('..') || path.isAbsolute(relative)) {
41
+ throw new Error(`Path must stay inside the project directory: ${inputPath}`);
42
+ }
43
+ return resolved;
44
+ }
45
+ function resolveAliasOrPath(cwd, aliasOrPath) {
46
+ const normalized = aliasOrPath.startsWith('@/')
47
+ ? path.join('src', aliasOrPath.slice(2))
48
+ : aliasOrPath;
49
+ return resolveProjectPath(cwd, normalized);
50
+ }
10
51
  export async function init(options) {
11
52
  console.log(chalk.bold('\n🎨 Welcome to shadcn-angular!\n'));
12
53
  const cwd = process.cwd();
@@ -20,20 +61,24 @@ export async function init(options) {
20
61
  // Check if already initialized
21
62
  const componentsJsonPath = path.join(cwd, 'components.json');
22
63
  if (await fs.pathExists(componentsJsonPath)) {
23
- const { overwrite } = await prompts({
24
- type: 'confirm',
25
- name: 'overwrite',
26
- message: 'components.json already exists. Overwrite?',
27
- initial: false,
28
- });
64
+ const overwrite = options.yes
65
+ ? true
66
+ : (await prompts({
67
+ type: 'confirm',
68
+ name: 'overwrite',
69
+ message: 'components.json already exists. Overwrite?',
70
+ initial: false,
71
+ })).overwrite;
29
72
  if (!overwrite) {
30
73
  console.log(chalk.dim('Initialization cancelled.'));
31
74
  return;
32
75
  }
33
76
  }
34
77
  let config;
78
+ let createShortcutRegistry = true;
35
79
  if (options.defaults || options.yes) {
36
80
  config = getDefaultConfig();
81
+ createShortcutRegistry = true;
37
82
  }
38
83
  else {
39
84
  const THEME_COLORS = {
@@ -115,6 +160,12 @@ export async function init(options) {
115
160
  message: 'Where is your global styles file?',
116
161
  initial: 'src/styles.scss',
117
162
  },
163
+ {
164
+ type: 'confirm',
165
+ name: 'createShortcutRegistry',
166
+ message: 'Would you like to create a shortcut registry scaffold?',
167
+ initial: true,
168
+ },
118
169
  ]);
119
170
  config = {
120
171
  $schema: 'https://shadcn-angular.dev/schema.json',
@@ -130,8 +181,8 @@ export async function init(options) {
130
181
  utils: responses.utilsPath.replace('src/', '@/').replace('.ts', ''),
131
182
  ui: responses.componentsPath.replace('src/', '@/'),
132
183
  },
133
- iconLibrary: 'lucide-angular',
134
184
  };
185
+ createShortcutRegistry = responses.createShortcutRegistry ?? true;
135
186
  }
136
187
  const spinner = ora('Initializing project...').start();
137
188
  try {
@@ -144,19 +195,28 @@ export async function init(options) {
144
195
  // So we should rely on config to reconstruct the path, or better yet, if we are in 'defaults' mode, check what config is.
145
196
  // If config came from defaults, aliases are set.
146
197
  // We can reverse-map alias to path: @/ -> src/
147
- const utilsPathResolved = config.aliases.utils.replace('@/', 'src/');
148
- const utilsDir = path.dirname(path.join(cwd, utilsPathResolved + '.ts')); // utils usually ends in path/to/utils
198
+ const utilsPathResolved = resolveAliasOrPath(cwd, config.aliases.utils + '.ts');
199
+ const utilsDir = path.dirname(utilsPathResolved); // utils usually ends in path/to/utils
149
200
  await fs.ensureDir(utilsDir);
150
- await fs.writeFile(path.join(cwd, utilsPathResolved + '.ts'), getUtilsTemplate());
201
+ await fs.writeFile(utilsPathResolved, getUtilsTemplate());
151
202
  spinner.text = 'Created utils.ts';
203
+ const shortcutServicePath = path.join(utilsDir, 'shortcut-binding.service.ts');
204
+ const shortcutServiceContent = await fetchLibFileContent('shortcut-binding.service.ts');
205
+ await fs.writeFile(shortcutServicePath, shortcutServiceContent);
206
+ spinner.text = 'Created shortcut-binding.service.ts';
207
+ if (createShortcutRegistry) {
208
+ await writeShortcutRegistryIndex(cwd, config, []);
209
+ spinner.text = 'Created shortcut-registry.index.ts';
210
+ }
152
211
  // Create tailwind.css file in the same directory as the global styles
153
- const stylesDir = path.dirname(path.join(cwd, config.tailwind.css));
212
+ const userStylesPath = resolveProjectPath(cwd, config.tailwind.css);
213
+ const stylesDir = path.dirname(userStylesPath);
154
214
  const tailwindCssPath = path.join(stylesDir, 'tailwind.css');
155
215
  // Write the tailwind.css file with all Tailwind directives
216
+ await fs.ensureDir(stylesDir);
156
217
  await fs.writeFile(tailwindCssPath, getStylesTemplate(config.tailwind.baseColor, config.tailwind.theme));
157
218
  spinner.text = 'Created tailwind.css';
158
219
  // Add import to the user's global styles file if not already present
159
- const userStylesPath = path.join(cwd, config.tailwind.css);
160
220
  let userStyles = await fs.pathExists(userStylesPath)
161
221
  ? await fs.readFile(userStylesPath, 'utf-8')
162
222
  : '';
@@ -168,8 +228,7 @@ export async function init(options) {
168
228
  spinner.text = 'Added tailwind.css import to styles';
169
229
  }
170
230
  // Create components/ui directory
171
- const uiPathResolved = config.aliases.ui.replace('@/', 'src/');
172
- const uiDir = path.join(cwd, uiPathResolved);
231
+ const uiDir = resolveAliasOrPath(cwd, config.aliases.ui);
173
232
  await fs.ensureDir(uiDir);
174
233
  spinner.text = 'Created components directory';
175
234
  // Install dependencies
@@ -178,12 +237,11 @@ export async function init(options) {
178
237
  'clsx',
179
238
  'tailwind-merge',
180
239
  'class-variance-authority',
181
- 'lucide-angular',
182
240
  'tailwindcss',
183
241
  'postcss',
184
242
  '@tailwindcss/postcss'
185
243
  ];
186
- await execa('npm', ['install', ...dependencies], { cwd });
244
+ await installPackages(dependencies, { cwd });
187
245
  // Setup PostCSS - create .postcssrc.json which is the preferred format for Angular
188
246
  spinner.text = 'Configuring PostCSS...';
189
247
  const postcssrcPath = path.join(cwd, '.postcssrc.json');
@@ -195,35 +253,6 @@ export async function init(options) {
195
253
  };
196
254
  await fs.writeJson(postcssrcPath, configContent, { spaces: 4 });
197
255
  }
198
- // Configure app.config.ts with Lucide icons
199
- spinner.text = 'Configuring icons in app.config.ts...';
200
- const appConfigPath = path.join(cwd, 'src/app/app.config.ts');
201
- if (await fs.pathExists(appConfigPath)) {
202
- let appConfigContent = await fs.readFile(appConfigPath, 'utf-8');
203
- // Add imports
204
- if (!appConfigContent.includes('LucideAngularModule')) {
205
- const iconImports = "import { LucideAngularModule, ArrowDown, ArrowUp, ChevronsUpDown, ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from 'lucide-angular';";
206
- appConfigContent = iconImports + '\n' + appConfigContent;
207
- }
208
- if (!appConfigContent.includes('importProvidersFrom')) {
209
- appConfigContent = "import { importProvidersFrom } from '@angular/core';\n" + appConfigContent;
210
- }
211
- // Add provider
212
- const providerCode = `
213
- importProvidersFrom(LucideAngularModule.pick({
214
- ArrowDown,
215
- ArrowUp,
216
- ChevronsUpDown,
217
- ChevronLeft,
218
- ChevronRight,
219
- ChevronsLeft,
220
- ChevronsRight
221
- }))`;
222
- if (!appConfigContent.includes('LucideAngularModule.pick')) {
223
- appConfigContent = appConfigContent.replace(/providers:\s*\[/, `providers: [${providerCode},`);
224
- await fs.writeFile(appConfigPath, appConfigContent);
225
- }
226
- }
227
256
  spinner.succeed(chalk.green('Project initialized successfully!'));
228
257
  console.log('\n' + chalk.bold('Next steps:'));
229
258
  console.log(chalk.dim(' 1. Add components: ') + chalk.cyan('npx @gilav21/shadcn-angular add button'));
@@ -3,6 +3,11 @@ export interface ComponentDefinition {
3
3
  files: string[];
4
4
  dependencies?: string[];
5
5
  npmDependencies?: string[];
6
+ shortcutDefinitions?: {
7
+ exportName: string;
8
+ componentName: string;
9
+ sourceFile: string;
10
+ }[];
6
11
  }
7
12
  export type ComponentName = keyof typeof registry;
8
13
  export declare const registry: Record<string, ComponentDefinition>;
@@ -39,6 +39,7 @@ export const registry = {
39
39
  button: {
40
40
  name: 'button',
41
41
  files: ['button.component.ts'],
42
+ dependencies: ['ripple'],
42
43
  },
43
44
  'button-group': {
44
45
  name: 'button-group',
@@ -79,6 +80,13 @@ export const registry = {
79
80
  name: 'command',
80
81
  files: ['command.component.ts'],
81
82
  dependencies: ['dialog'],
83
+ shortcutDefinitions: [
84
+ {
85
+ exportName: 'COMMAND_DIALOG_SHORTCUT_DEFINITIONS',
86
+ componentName: 'command-dialog',
87
+ sourceFile: 'command.component.ts',
88
+ },
89
+ ],
82
90
  },
83
91
  'context-menu': {
84
92
  name: 'context-menu',
@@ -130,6 +138,7 @@ export const registry = {
130
138
  'pagination',
131
139
  'popover',
132
140
  'component-outlet',
141
+ 'icon',
133
142
  ],
134
143
  },
135
144
  dialog: {
@@ -144,11 +153,12 @@ export const registry = {
144
153
  'dock-icon.component.ts',
145
154
  'dock-label.component.ts',
146
155
  ],
156
+ dependencies: ['icon'],
147
157
  },
148
158
  'tree-select': {
149
159
  name: 'tree-select',
150
160
  files: ['tree-select.component.ts'],
151
- dependencies: ['popover', 'tree'],
161
+ dependencies: ['popover', 'tree', 'icon'],
152
162
  },
153
163
  'virtual-scroll': {
154
164
  name: 'virtual-scroll',
@@ -175,6 +185,10 @@ export const registry = {
175
185
  name: 'field',
176
186
  files: ['field.component.ts'],
177
187
  },
188
+ icon: {
189
+ name: 'icon',
190
+ files: ['icon.component.ts'],
191
+ },
178
192
  'file-upload': {
179
193
  name: 'file-upload',
180
194
  files: ['file-upload.component.ts'],
@@ -264,7 +278,7 @@ export const registry = {
264
278
  sidebar: {
265
279
  name: 'sidebar',
266
280
  files: ['sidebar.component.ts'],
267
- dependencies: ['scroll-area', 'tooltip'],
281
+ dependencies: ['scroll-area', 'tooltip', 'icon'],
268
282
  },
269
283
  skeleton: {
270
284
  name: 'skeleton',
@@ -321,6 +335,7 @@ export const registry = {
321
335
  tree: {
322
336
  name: 'tree',
323
337
  files: ['tree.component.ts'],
338
+ dependencies: ['icon'],
324
339
  },
325
340
  'speed-dial': {
326
341
  name: 'speed-dial',
@@ -335,7 +350,7 @@ export const registry = {
335
350
  'emoji-picker': {
336
351
  name: 'emoji-picker',
337
352
  files: ['emoji-picker.component.ts', 'emoji-data.ts'],
338
- dependencies: ['button', 'input', 'scroll-area', 'popover'],
353
+ dependencies: ['input', 'scroll-area', 'tooltip'],
339
354
  },
340
355
  'rich-text-editor': {
341
356
  name: 'rich-text-editor',
@@ -344,18 +359,30 @@ export const registry = {
344
359
  'rich-text-toolbar.component.ts',
345
360
  'rich-text-sanitizer.service.ts',
346
361
  'rich-text-markdown.service.ts',
362
+ 'rich-text-paste-normalizer.service.ts',
363
+ 'rich-text-command-registry.service.ts',
347
364
  'rich-text-mention.component.ts',
348
365
  'rich-text-image-resizer.component.ts',
366
+ 'rich-text-locales.ts',
349
367
  ],
350
368
  dependencies: [
351
369
  'button',
352
370
  'separator',
353
371
  'popover',
354
372
  'emoji-picker',
373
+ 'autocomplete',
355
374
  'select',
356
375
  'input',
376
+ 'dialog',
357
377
  'scroll-area',
358
378
  ],
379
+ shortcutDefinitions: [
380
+ {
381
+ exportName: 'RICH_TEXT_SHORTCUT_DEFINITIONS',
382
+ componentName: 'rich-text-editor',
383
+ sourceFile: 'rich-text-editor.component.ts',
384
+ },
385
+ ],
359
386
  },
360
387
  // Chart Components
361
388
  'pie-chart': {
@@ -424,7 +451,7 @@ export const registry = {
424
451
  },
425
452
  'bento-grid': {
426
453
  name: 'bento-grid',
427
- dependencies: ['context-menu', 'component-outlet'],
454
+ dependencies: ['context-menu', 'component-outlet', 'icon'],
428
455
  files: [
429
456
  'bento-grid.component.ts',
430
457
  ],
@@ -438,12 +465,14 @@ export const registry = {
438
465
  'label',
439
466
  'select',
440
467
  'switch',
441
- 'slider'
468
+ 'slider',
469
+ 'icon'
442
470
  ],
443
471
  files: [
444
472
  'page-builder/page-builder.component.ts',
445
473
  'page-builder/page-builder.types.ts',
446
- 'page-builder/property-editor.component.ts'
474
+ 'page-builder/property-editor.component.ts',
475
+ 'page-builder/page-renderer.component.ts'
447
476
  ],
448
477
  },
449
478
  'component-outlet': {
@@ -455,4 +484,75 @@ export const registry = {
455
484
  files: ['split-button.component.ts'],
456
485
  dependencies: ['button', 'dropdown-menu'],
457
486
  },
487
+ // Animations
488
+ 'gradient-text': {
489
+ name: 'gradient-text',
490
+ files: ['gradient-text.component.ts'],
491
+ },
492
+ 'flip-text': {
493
+ name: 'flip-text',
494
+ files: ['flip-text.component.ts'],
495
+ },
496
+ meteors: {
497
+ name: 'meteors',
498
+ files: ['meteors.component.ts'],
499
+ },
500
+ 'shine-border': {
501
+ name: 'shine-border',
502
+ files: ['shine-border.component.ts'],
503
+ },
504
+ 'scroll-progress': {
505
+ name: 'scroll-progress',
506
+ files: ['scroll-progress.component.ts'],
507
+ },
508
+ 'blur-fade': {
509
+ name: 'blur-fade',
510
+ files: ['blur-fade.component.ts'],
511
+ },
512
+ ripple: {
513
+ name: 'ripple',
514
+ files: ['ripple.directive.ts'],
515
+ },
516
+ marquee: {
517
+ name: 'marquee',
518
+ files: ['marquee.component.ts'],
519
+ },
520
+ 'word-rotate': {
521
+ name: 'word-rotate',
522
+ files: ['word-rotate.component.ts'],
523
+ },
524
+ 'morphing-text': {
525
+ name: 'morphing-text',
526
+ files: ['morphing-text.component.ts'],
527
+ },
528
+ 'typing-animation': {
529
+ name: 'typing-animation',
530
+ files: ['typing-animation.component.ts'],
531
+ },
532
+ 'wobble-card': {
533
+ name: 'wobble-card',
534
+ files: ['wobble-card.component.ts'],
535
+ },
536
+ magnetic: {
537
+ name: 'magnetic',
538
+ files: ['magnetic.directive.ts'],
539
+ },
540
+ orbit: {
541
+ name: 'orbit',
542
+ files: ['orbit.component.ts'],
543
+ },
544
+ 'stagger-children': {
545
+ name: 'stagger-children',
546
+ files: ['stagger-children.component.ts'],
547
+ },
548
+ particles: {
549
+ name: 'particles',
550
+ files: ['particles.component.ts'],
551
+ },
552
+ // Kanban
553
+ kanban: {
554
+ name: 'kanban',
555
+ files: ['kanban.component.ts'],
556
+ dependencies: ['badge', 'avatar', 'scroll-area', 'separator'],
557
+ },
458
558
  };
@@ -12,7 +12,6 @@ export interface Config {
12
12
  utils: string;
13
13
  ui: string;
14
14
  };
15
- iconLibrary: string;
16
15
  }
17
16
  export declare function getDefaultConfig(): Config;
18
17
  export declare function getConfig(cwd: string): Promise<Config | null>;
@@ -15,7 +15,6 @@ export function getDefaultConfig() {
15
15
  utils: '@/components/lib/utils',
16
16
  ui: '@/components/ui',
17
17
  },
18
- iconLibrary: 'lucide-angular',
19
18
  };
20
19
  }
21
20
  export async function getConfig(cwd) {
@@ -0,0 +1,7 @@
1
+ import type { Config } from './config.js';
2
+ export interface ShortcutRegistryEntry {
3
+ exportName: string;
4
+ componentName: string;
5
+ sourceFile: string;
6
+ }
7
+ export declare function writeShortcutRegistryIndex(cwd: string, config: Config, entries: ShortcutRegistryEntry[]): Promise<string>;
@@ -0,0 +1,58 @@
1
+ import fs from 'fs-extra';
2
+ import path from '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
+ }
16
+ function getShortcutRegistryIndexPath(cwd, config) {
17
+ const utilsFilePath = resolveProjectPath(cwd, aliasToProjectPath(config.aliases.utils) + '.ts');
18
+ const utilsDir = path.dirname(utilsFilePath);
19
+ return path.join(utilsDir, 'shortcut-registry.index.ts');
20
+ }
21
+ export async function writeShortcutRegistryIndex(cwd, config, entries) {
22
+ const registryPath = getShortcutRegistryIndexPath(cwd, config);
23
+ await fs.ensureDir(path.dirname(registryPath));
24
+ const uniqueEntries = Array.from(new Map(entries.map(entry => [entry.exportName, entry])).values())
25
+ .sort((a, b) => a.exportName.localeCompare(b.exportName));
26
+ const uiAlias = config.aliases.ui;
27
+ const utilsAliasDir = config.aliases.utils.includes('/')
28
+ ? config.aliases.utils.slice(0, config.aliases.utils.lastIndexOf('/'))
29
+ : config.aliases.utils;
30
+ const shortcutServiceImport = `${utilsAliasDir}/shortcut-binding.service`;
31
+ const imports = uniqueEntries
32
+ .map(entry => {
33
+ const importPath = `${uiAlias}/${entry.sourceFile.replace(/\.ts$/, '')}`;
34
+ return `import { ${entry.exportName} } from '${importPath}';`;
35
+ })
36
+ .join('\n');
37
+ const catalogItems = uniqueEntries
38
+ .map(entry => ` { componentName: '${entry.componentName}', definitions: ${entry.exportName} },`)
39
+ .join('\n');
40
+ const content = `// Auto-generated by shadcn-angular CLI. Do not edit manually.
41
+ import type { ShortcutBindingService, ShortcutDefinition } from '${shortcutServiceImport}';
42
+ ${imports ? `${imports}\n` : ''}
43
+ export const GENERATED_SHORTCUT_CATALOG: ReadonlyArray<{
44
+ componentName: string;
45
+ definitions: ReadonlyArray<ShortcutDefinition>;
46
+ }> = [
47
+ ${catalogItems}
48
+ ];
49
+
50
+ export function registerGeneratedShortcutCatalog(shortcuts: ShortcutBindingService): void {
51
+ for (const entry of GENERATED_SHORTCUT_CATALOG) {
52
+ shortcuts.defineShortcuts(entry.componentName, [...entry.definitions]);
53
+ }
54
+ }
55
+ `;
56
+ await fs.writeFile(registryPath, content);
57
+ return registryPath;
58
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gilav21/shadcn-angular",
3
- "version": "0.0.14",
3
+ "version": "0.0.16",
4
4
  "description": "CLI for adding shadcn-angular components to your project",
5
5
  "bin": {
6
6
  "shadcn-angular": "./dist/index.js"