@gilav21/shadcn-angular 0.0.20 → 0.0.21

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.
@@ -1,6 +1,6 @@
1
1
  import fs from 'fs-extra';
2
- import path from 'path';
3
- import { fileURLToPath } from 'url';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
4
  import prompts from 'prompts';
5
5
  import chalk from 'chalk';
6
6
  import ora from 'ora';
@@ -10,25 +10,25 @@ import { installPackages } from '../utils/package-manager.js';
10
10
  import { writeShortcutRegistryIndex } from '../utils/shortcut-registry.js';
11
11
  const __filename = fileURLToPath(import.meta.url);
12
12
  const __dirname = path.dirname(__filename);
13
+ // ---------------------------------------------------------------------------
14
+ // Path & URL helpers
15
+ // ---------------------------------------------------------------------------
16
+ function validateBranch(branch) {
17
+ if (!/^[\w.\-/]+$/.test(branch)) {
18
+ throw new Error(`Invalid branch name: ${branch}`);
19
+ }
20
+ }
13
21
  function getRegistryBaseUrl(branch) {
22
+ validateBranch(branch);
14
23
  return `https://raw.githubusercontent.com/gilav21/shadcn-angular/${branch}/packages/components/ui`;
15
24
  }
16
25
  function getLibRegistryBaseUrl(branch) {
26
+ validateBranch(branch);
17
27
  return `https://raw.githubusercontent.com/gilav21/shadcn-angular/${branch}/packages/components/lib`;
18
28
  }
19
- // Components source directory (relative to CLI dist folder) for local dev
20
29
  function getLocalComponentsDir() {
21
- // From dist/commands/add.js -> packages/components/ui
22
- const fromDist = path.resolve(__dirname, '../../../components/ui');
23
- if (fs.existsSync(fromDist)) {
24
- return fromDist;
25
- }
26
- // Fallback: from src/commands/add.ts -> packages/components/ui
27
- const fromSrc = path.resolve(__dirname, '../../../components/ui');
28
- if (fs.existsSync(fromSrc)) {
29
- return fromSrc;
30
- }
31
- return null;
30
+ const localPath = path.resolve(__dirname, '../../../components/ui');
31
+ return fs.existsSync(localPath) ? localPath : null;
32
32
  }
33
33
  function getLocalLibDir() {
34
34
  const fromDist = path.resolve(__dirname, '../../../components/lib');
@@ -50,16 +50,20 @@ function aliasToProjectPath(aliasOrPath) {
50
50
  ? path.join('src', aliasOrPath.slice(2))
51
51
  : aliasOrPath;
52
52
  }
53
+ function normalizeContent(str) {
54
+ return str.replaceAll('\r\n', '\n').trim();
55
+ }
56
+ // ---------------------------------------------------------------------------
57
+ // Remote content fetching
58
+ // ---------------------------------------------------------------------------
53
59
  async function fetchComponentContent(file, options) {
54
60
  const localDir = getLocalComponentsDir();
55
- // 1. Prefer local if available and not forced remote
56
61
  if (localDir && !options.remote) {
57
62
  const localPath = path.join(localDir, file);
58
63
  if (await fs.pathExists(localPath)) {
59
64
  return fs.readFile(localPath, 'utf-8');
60
65
  }
61
66
  }
62
- // 2. Fetch from remote registry
63
67
  const url = `${getRegistryBaseUrl(options.branch)}/${file}`;
64
68
  try {
65
69
  const response = await fetch(url);
@@ -90,36 +94,19 @@ async function fetchLibContent(file, options) {
90
94
  }
91
95
  return response.text();
92
96
  }
93
- function collectInstalledShortcutEntries(targetDir) {
94
- const entries = [];
95
- for (const definition of Object.values(registry)) {
96
- if (!definition.shortcutDefinitions?.length) {
97
- continue;
98
- }
99
- for (const shortcutDefinition of definition.shortcutDefinitions) {
100
- const sourcePath = path.join(targetDir, shortcutDefinition.sourceFile);
101
- if (fs.existsSync(sourcePath)) {
102
- entries.push(shortcutDefinition);
103
- }
104
- }
105
- }
106
- return entries;
97
+ async function fetchAndTransform(file, options, utilsAlias) {
98
+ let content = await fetchComponentContent(file, options);
99
+ content = content.replaceAll(/(\.\.\/)+lib\//, utilsAlias + '/');
100
+ return content;
107
101
  }
108
- export async function add(components, options) {
109
- const cwd = process.cwd();
110
- // Load config
111
- const config = await getConfig(cwd);
112
- if (!config) {
113
- console.log(chalk.red('Error: components.json not found.'));
114
- console.log(chalk.dim('Run `npx @gilav21/shadcn-angular init` first.'));
115
- process.exit(1);
116
- }
117
- // Get components to add
118
- let componentsToAdd = [];
102
+ // ---------------------------------------------------------------------------
103
+ // Component selection & dependency resolution
104
+ // ---------------------------------------------------------------------------
105
+ async function selectComponents(components, options) {
119
106
  if (options.all) {
120
- componentsToAdd = Object.keys(registry);
107
+ return Object.keys(registry);
121
108
  }
122
- else if (components.length === 0) {
109
+ if (components.length === 0) {
123
110
  const { selected } = await prompts({
124
111
  type: 'multiselect',
125
112
  name: 'selected',
@@ -130,146 +117,307 @@ export async function add(components, options) {
130
117
  })),
131
118
  hint: '- Space to select, Enter to confirm',
132
119
  });
133
- componentsToAdd = selected;
134
- }
135
- else {
136
- componentsToAdd = components;
120
+ return selected;
137
121
  }
138
- if (!componentsToAdd || componentsToAdd.length === 0) {
139
- console.log(chalk.dim('No components selected.'));
140
- return;
141
- }
142
- // Validate components exist
143
- const invalidComponents = componentsToAdd.filter(c => !registry[c]);
144
- if (invalidComponents.length > 0) {
145
- console.log(chalk.red(`Invalid component(s): ${invalidComponents.join(', ')}`));
122
+ return components;
123
+ }
124
+ function validateComponents(names) {
125
+ const invalid = names.filter(c => !registry[c]);
126
+ if (invalid.length > 0) {
127
+ console.log(chalk.red(`Invalid component(s): ${invalid.join(', ')}`));
146
128
  console.log(chalk.dim('Available components: ' + Object.keys(registry).join(', ')));
147
129
  process.exit(1);
148
130
  }
149
- // Resolve dependencies
150
- const allComponents = new Set();
151
- const resolveDeps = (name) => {
152
- if (allComponents.has(name))
131
+ }
132
+ export function resolveDependencies(names) {
133
+ const all = new Set();
134
+ const walk = (name) => {
135
+ if (all.has(name))
153
136
  return;
154
- allComponents.add(name);
137
+ all.add(name);
138
+ registry[name].dependencies?.forEach(dep => walk(dep));
139
+ };
140
+ names.forEach(walk);
141
+ return all;
142
+ }
143
+ export async function promptOptionalDependencies(resolved, options) {
144
+ const seen = new Set();
145
+ const choices = [];
146
+ for (const name of resolved) {
155
147
  const component = registry[name];
156
- if (component.dependencies) {
157
- component.dependencies.forEach(dep => resolveDeps(dep));
148
+ if (!component.optionalDependencies)
149
+ continue;
150
+ for (const opt of component.optionalDependencies) {
151
+ if (resolved.has(opt.name) || seen.has(opt.name))
152
+ continue;
153
+ seen.add(opt.name);
154
+ choices.push({ name: opt.name, description: opt.description, requestedBy: name });
158
155
  }
159
- };
160
- componentsToAdd.forEach(c => resolveDeps(c));
161
- const uiBasePath = options.path ?? aliasToProjectPath(config.aliases.ui || 'src/components/ui');
162
- const targetDir = resolveProjectPath(cwd, uiBasePath);
163
- // Check for existing files and diff
164
- const componentsToInstall = [];
165
- const componentsToSkip = [];
166
- const conflictingComponents = [];
156
+ }
157
+ if (choices.length === 0)
158
+ return [];
159
+ if (options.yes)
160
+ return [];
161
+ if (options.all)
162
+ return choices.map(c => c.name);
163
+ const { selected } = await prompts({
164
+ type: 'multiselect',
165
+ name: 'selected',
166
+ message: 'Optional companion components available:',
167
+ choices: choices.map(c => ({
168
+ title: c.name + ' ' + chalk.dim('- ' + c.description + ' (for ' + c.requestedBy + ')'),
169
+ value: c.name,
170
+ })),
171
+ hint: '- Space to select, Enter to confirm (or press Enter to skip)',
172
+ });
173
+ return selected || [];
174
+ }
175
+ // ---------------------------------------------------------------------------
176
+ // Conflict detection
177
+ // ---------------------------------------------------------------------------
178
+ async function checkFileConflict(file, targetDir, options, utilsAlias, contentCache) {
179
+ const targetPath = path.join(targetDir, file);
180
+ if (!await fs.pathExists(targetPath)) {
181
+ return 'missing';
182
+ }
183
+ const localContent = await fs.readFile(targetPath, 'utf-8');
184
+ try {
185
+ const remoteContent = await fetchAndTransform(file, options, utilsAlias);
186
+ contentCache.set(file, remoteContent);
187
+ return normalizeContent(localContent) === normalizeContent(remoteContent)
188
+ ? 'identical'
189
+ : 'changed';
190
+ }
191
+ catch {
192
+ return 'changed';
193
+ }
194
+ }
195
+ async function checkPeerFiles(component, targetDir, options, utilsAlias, contentCache, peerFilesToUpdate) {
196
+ if (!component.peerFiles)
197
+ return false;
198
+ let hasChanges = false;
199
+ for (const file of component.peerFiles) {
200
+ const status = await checkFileConflict(file, targetDir, options, utilsAlias, contentCache);
201
+ if (status === 'changed') {
202
+ hasChanges = true;
203
+ peerFilesToUpdate.add(file);
204
+ }
205
+ }
206
+ return hasChanges;
207
+ }
208
+ async function classifyComponent(name, targetDir, options, utilsAlias, contentCache, peerFilesToUpdate) {
209
+ const component = registry[name];
210
+ let hasChanges = false;
211
+ let isFullyPresent = true;
212
+ for (const file of component.files) {
213
+ const status = await checkFileConflict(file, targetDir, options, utilsAlias, contentCache);
214
+ if (status === 'missing')
215
+ isFullyPresent = false;
216
+ if (status === 'changed')
217
+ hasChanges = true;
218
+ }
219
+ const peerChanged = await checkPeerFiles(component, targetDir, options, utilsAlias, contentCache, peerFilesToUpdate);
220
+ if (peerChanged)
221
+ hasChanges = true;
222
+ if (isFullyPresent && !hasChanges)
223
+ return 'skip';
224
+ if (hasChanges)
225
+ return 'conflict';
226
+ return 'install';
227
+ }
228
+ async function detectConflicts(allComponents, targetDir, options, utilsAlias) {
229
+ const toInstall = [];
230
+ const toSkip = [];
231
+ const conflicting = [];
232
+ const peerFilesToUpdate = new Set();
167
233
  const contentCache = new Map();
168
- const checkSpinner = ora('Checking for conflicts...').start();
169
234
  for (const name of allComponents) {
170
- const component = registry[name];
171
- let hasChanges = false;
172
- let isFullyPresent = true;
173
- for (const file of component.files) {
174
- const targetPath = path.join(targetDir, file);
175
- if (await fs.pathExists(targetPath)) {
176
- const localContent = await fs.readFile(targetPath, 'utf-8');
177
- try {
178
- let remoteContent = await fetchComponentContent(file, options);
179
- // Transform all lib/ imports for comparison
180
- remoteContent = remoteContent.replace(/(\.\.\/)+lib\//g, config.aliases.utils + '/');
181
- const normalize = (str) => str.replace(/\r\n/g, '\n').trim();
182
- if (normalize(localContent) !== normalize(remoteContent)) {
183
- hasChanges = true;
184
- }
185
- contentCache.set(file, remoteContent); // Cache for installation
186
- }
187
- catch (error) {
188
- // unexpected error fetching remote
189
- console.warn(`Could not fetch remote content for comparison: ${file}`);
190
- hasChanges = true; // Assume changed/unknown
191
- }
192
- }
193
- else {
194
- isFullyPresent = false;
195
- }
235
+ const result = await classifyComponent(name, targetDir, options, utilsAlias, contentCache, peerFilesToUpdate);
236
+ if (result === 'skip')
237
+ toSkip.push(name);
238
+ else if (result === 'conflict')
239
+ conflicting.push(name);
240
+ else
241
+ toInstall.push(name);
242
+ }
243
+ return { toInstall, toSkip, conflicting, peerFilesToUpdate, contentCache };
244
+ }
245
+ // ---------------------------------------------------------------------------
246
+ // Overwrite prompt
247
+ // ---------------------------------------------------------------------------
248
+ async function promptOverwrite(conflicting, options) {
249
+ if (conflicting.length === 0)
250
+ return [];
251
+ if (options.overwrite)
252
+ return conflicting;
253
+ if (options.yes)
254
+ return [];
255
+ console.log(chalk.yellow(`\n${conflicting.length} component(s) have local changes or are different from remote.`));
256
+ const { selected } = await prompts({
257
+ type: 'multiselect',
258
+ name: 'selected',
259
+ message: 'Select components to OVERWRITE (Unselected will be skipped):',
260
+ choices: conflicting.map(name => ({ title: name, value: name })),
261
+ hint: '- Space to select, Enter to confirm',
262
+ });
263
+ return selected || [];
264
+ }
265
+ // ---------------------------------------------------------------------------
266
+ // File writing
267
+ // ---------------------------------------------------------------------------
268
+ async function writeComponentFiles(component, targetDir, options, utilsAlias, contentCache, spinner) {
269
+ let success = true;
270
+ for (const file of component.files) {
271
+ const targetPath = path.join(targetDir, file);
272
+ try {
273
+ const content = contentCache.get(file)
274
+ ?? await fetchAndTransform(file, options, utilsAlias);
275
+ await fs.ensureDir(path.dirname(targetPath));
276
+ await fs.writeFile(targetPath, content);
196
277
  }
197
- if (isFullyPresent && !hasChanges) {
198
- componentsToSkip.push(name);
278
+ catch (err) {
279
+ const message = err instanceof Error ? err.message : String(err);
280
+ spinner.warn(`Could not add ${file}: ${message}`);
281
+ success = false;
199
282
  }
200
- else if (hasChanges) {
201
- conflictingComponents.push(name);
283
+ }
284
+ return success;
285
+ }
286
+ async function writePeerFiles(component, targetDir, options, utilsAlias, contentCache, peerFilesToUpdate, spinner) {
287
+ if (!component.peerFiles)
288
+ return;
289
+ for (const file of component.peerFiles) {
290
+ if (!peerFilesToUpdate.has(file))
291
+ continue;
292
+ const targetPath = path.join(targetDir, file);
293
+ try {
294
+ const content = contentCache.get(file)
295
+ ?? await fetchAndTransform(file, options, utilsAlias);
296
+ await fs.writeFile(targetPath, content);
297
+ spinner.text = `Updated peer file ${file}`;
202
298
  }
203
- else {
204
- componentsToInstall.push(name);
299
+ catch (err) {
300
+ const message = err instanceof Error ? err.message : String(err);
301
+ spinner.warn(`Could not update peer file ${file}: ${message}`);
205
302
  }
206
303
  }
207
- checkSpinner.stop();
208
- let componentsToOverwrite = [];
209
- if (conflictingComponents.length > 0) {
210
- if (options.overwrite) {
211
- componentsToOverwrite = conflictingComponents;
304
+ }
305
+ async function installLibFiles(allComponents, cwd, libDir, options) {
306
+ const required = new Set();
307
+ for (const name of allComponents) {
308
+ registry[name].libFiles?.forEach(f => required.add(f));
309
+ }
310
+ if (required.size === 0)
311
+ return;
312
+ await fs.ensureDir(libDir);
313
+ for (const libFile of required) {
314
+ const targetPath = path.join(libDir, libFile);
315
+ if (!await fs.pathExists(targetPath) || options.overwrite) {
316
+ try {
317
+ const content = await fetchLibContent(libFile, options);
318
+ await fs.writeFile(targetPath, content);
319
+ }
320
+ catch (err) {
321
+ const message = err instanceof Error ? err.message : String(err);
322
+ console.warn(chalk.yellow(`Could not install lib file ${libFile}: ${message}`));
323
+ }
212
324
  }
213
- else if (options.yes) {
214
- componentsToOverwrite = []; // Skip conflicts in non-interactive mode unless --overwrite
325
+ }
326
+ }
327
+ async function installNpmDependencies(finalComponents, cwd) {
328
+ const deps = new Set();
329
+ for (const name of finalComponents) {
330
+ registry[name].npmDependencies?.forEach(dep => deps.add(dep));
331
+ }
332
+ if (deps.size === 0)
333
+ return;
334
+ const spinner = ora('Installing dependencies...').start();
335
+ try {
336
+ await installPackages(Array.from(deps), { cwd });
337
+ spinner.succeed('Dependencies installed.');
338
+ }
339
+ catch (e) {
340
+ spinner.fail('Failed to install dependencies.');
341
+ console.error(e);
342
+ }
343
+ }
344
+ async function ensureShortcutService(targetDir, cwd, config, options) {
345
+ const entries = collectInstalledShortcutEntries(targetDir);
346
+ if (entries.length > 0) {
347
+ const libDir = resolveProjectPath(cwd, aliasToProjectPath(config.aliases.utils));
348
+ const servicePath = path.join(libDir, 'shortcut-binding.service.ts');
349
+ if (!await fs.pathExists(servicePath)) {
350
+ const content = await fetchLibContent('shortcut-binding.service.ts', options);
351
+ await fs.ensureDir(libDir);
352
+ await fs.writeFile(servicePath, content);
215
353
  }
216
- else {
217
- console.log(chalk.yellow(`\n${conflictingComponents.length} component(s) have local changes or are different from remote.`));
218
- const { selected } = await prompts({
219
- type: 'multiselect',
220
- name: 'selected',
221
- message: 'Select components to OVERWRITE (Unselected will be skipped):',
222
- choices: conflictingComponents.map(name => ({
223
- title: name,
224
- value: name,
225
- })),
226
- hint: '- Space to select, Enter to confirm',
227
- });
228
- componentsToOverwrite = selected || [];
354
+ }
355
+ await writeShortcutRegistryIndex(cwd, config, entries);
356
+ }
357
+ function collectInstalledShortcutEntries(targetDir) {
358
+ const entries = [];
359
+ for (const definition of Object.values(registry)) {
360
+ if (!definition.shortcutDefinitions?.length)
361
+ continue;
362
+ for (const sd of definition.shortcutDefinitions) {
363
+ if (fs.existsSync(path.join(targetDir, sd.sourceFile))) {
364
+ entries.push(sd);
365
+ }
229
366
  }
230
367
  }
231
- // Final list of components to process
232
- // We process:
233
- // 1. componentsToInstall (Brand new or partial)
234
- // 2. componentsToOverwrite (User selected)
235
- // We SKIP:
236
- // 1. componentsToSkip (Identical)
237
- // 2. conflictingComponents NOT in componentsToOverwrite
238
- const finalComponents = [...componentsToInstall, ...componentsToOverwrite];
239
- if (finalComponents.length === 0 && componentsToSkip.length > 0) {
240
- console.log(chalk.green(`\nAll components are up to date! (${componentsToSkip.length} skipped)`));
368
+ return entries;
369
+ }
370
+ // ---------------------------------------------------------------------------
371
+ // Main entry point
372
+ // ---------------------------------------------------------------------------
373
+ export async function add(components, options) {
374
+ const cwd = process.cwd();
375
+ const config = await getConfig(cwd);
376
+ if (!config) {
377
+ console.log(chalk.red('Error: components.json not found.'));
378
+ console.log(chalk.dim('Run `npx @gilav21/shadcn-angular init` first.'));
379
+ process.exit(1);
380
+ }
381
+ const componentsToAdd = await selectComponents(components, options);
382
+ if (!componentsToAdd || componentsToAdd.length === 0) {
383
+ console.log(chalk.dim('No components selected.'));
241
384
  return;
242
385
  }
386
+ validateComponents(componentsToAdd);
387
+ const resolvedComponents = resolveDependencies(componentsToAdd);
388
+ const optionalChoices = await promptOptionalDependencies(resolvedComponents, options);
389
+ const allComponents = optionalChoices.length > 0
390
+ ? resolveDependencies([...resolvedComponents, ...optionalChoices])
391
+ : resolvedComponents;
392
+ const uiBasePath = options.path ?? aliasToProjectPath(config.aliases.ui || 'src/components/ui');
393
+ const targetDir = resolveProjectPath(cwd, uiBasePath);
394
+ const utilsAlias = config.aliases.utils;
395
+ // Detect conflicts
396
+ const checkSpinner = ora('Checking for conflicts...').start();
397
+ const { toInstall, toSkip, conflicting, peerFilesToUpdate, contentCache } = await detectConflicts(allComponents, targetDir, options, utilsAlias);
398
+ checkSpinner.stop();
399
+ // Prompt user for overwrite decisions
400
+ const toOverwrite = await promptOverwrite(conflicting, options);
401
+ const finalComponents = [...toInstall, ...toOverwrite];
243
402
  if (finalComponents.length === 0) {
244
- console.log(chalk.dim('\nNo components to install.'));
403
+ if (toSkip.length > 0) {
404
+ console.log(chalk.green(`\nAll components are up to date! (${toSkip.length} skipped)`));
405
+ }
406
+ else {
407
+ console.log(chalk.dim('\nNo components to install.'));
408
+ }
245
409
  return;
246
410
  }
411
+ // Install component files
247
412
  const spinner = ora('Installing components...').start();
248
413
  let successCount = 0;
249
414
  try {
250
415
  await fs.ensureDir(targetDir);
251
416
  for (const name of finalComponents) {
252
417
  const component = registry[name];
253
- let componentSuccess = true;
254
- for (const file of component.files) {
255
- const targetPath = path.join(targetDir, file);
256
- try {
257
- let content = contentCache.get(file);
258
- if (!content) {
259
- content = await fetchComponentContent(file, options);
260
- // Transform all lib/ imports if not already transformed (cached is transformed)
261
- content = content.replace(/(\.\.\/)+lib\//g, config.aliases.utils + '/');
262
- }
263
- await fs.ensureDir(path.dirname(targetPath));
264
- await fs.writeFile(targetPath, content);
265
- // spinner.text = `Added ${file}`; // Too verbose?
266
- }
267
- catch (err) {
268
- spinner.warn(`Could not add ${file}: ${err.message}`);
269
- componentSuccess = false;
270
- }
271
- }
272
- if (componentSuccess) {
418
+ const ok = await writeComponentFiles(component, targetDir, options, utilsAlias, contentCache, spinner);
419
+ await writePeerFiles(component, targetDir, options, utilsAlias, contentCache, peerFilesToUpdate, spinner);
420
+ if (ok) {
273
421
  successCount++;
274
422
  spinner.text = `Added ${name}`;
275
423
  }
@@ -277,75 +425,19 @@ export async function add(components, options) {
277
425
  if (successCount > 0) {
278
426
  spinner.succeed(chalk.green(`Success! Added ${successCount} component(s)`));
279
427
  console.log('\n' + chalk.dim('Components added:'));
280
- finalComponents.forEach(name => {
281
- console.log(chalk.dim(' - ') + chalk.cyan(name));
282
- });
428
+ finalComponents.forEach(name => console.log(chalk.dim(' - ') + chalk.cyan(name)));
283
429
  }
284
430
  else {
285
431
  spinner.info('No new components installed.');
286
432
  }
287
- // Install required lib utility files
288
- if (finalComponents.length > 0) {
289
- const requiredLibFiles = new Set();
290
- for (const name of allComponents) {
291
- const component = registry[name];
292
- if (component.libFiles) {
293
- component.libFiles.forEach(f => requiredLibFiles.add(f));
294
- }
295
- }
296
- if (requiredLibFiles.size > 0) {
297
- const libDir = resolveProjectPath(cwd, aliasToProjectPath(config.aliases.utils));
298
- await fs.ensureDir(libDir);
299
- for (const libFile of requiredLibFiles) {
300
- const libTargetPath = path.join(libDir, libFile);
301
- if (!await fs.pathExists(libTargetPath) || options.overwrite) {
302
- try {
303
- const libContent = await fetchLibContent(libFile, options);
304
- await fs.writeFile(libTargetPath, libContent);
305
- }
306
- catch (err) {
307
- console.warn(chalk.yellow(`Could not install lib file ${libFile}: ${err.message}`));
308
- }
309
- }
310
- }
311
- }
312
- }
313
- if (finalComponents.length > 0) {
314
- const npmDependencies = new Set();
315
- for (const name of finalComponents) {
316
- const component = registry[name];
317
- if (component.npmDependencies) {
318
- component.npmDependencies.forEach(dep => npmDependencies.add(dep));
319
- }
320
- }
321
- if (npmDependencies.size > 0) {
322
- const depSpinner = ora('Installing dependencies...').start();
323
- try {
324
- await installPackages(Array.from(npmDependencies), { cwd });
325
- depSpinner.succeed('Dependencies installed.');
326
- }
327
- catch (e) {
328
- depSpinner.fail('Failed to install dependencies.');
329
- console.error(e);
330
- }
331
- }
332
- }
333
- const shortcutEntries = collectInstalledShortcutEntries(targetDir);
334
- if (shortcutEntries.length > 0) {
335
- const libDir2 = resolveProjectPath(cwd, aliasToProjectPath(config.aliases.utils));
336
- const shortcutServicePath = path.join(libDir2, 'shortcut-binding.service.ts');
337
- if (!await fs.pathExists(shortcutServicePath)) {
338
- const shortcutServiceContent = await fetchLibContent('shortcut-binding.service.ts', options);
339
- await fs.ensureDir(libDir2);
340
- await fs.writeFile(shortcutServicePath, shortcutServiceContent);
341
- }
342
- }
343
- await writeShortcutRegistryIndex(cwd, config, shortcutEntries);
344
- if (componentsToSkip.length > 0) {
433
+ // Post-install: lib files, npm deps, shortcuts
434
+ const libDir = resolveProjectPath(cwd, aliasToProjectPath(utilsAlias));
435
+ await installLibFiles(allComponents, cwd, libDir, options);
436
+ await installNpmDependencies(finalComponents, cwd);
437
+ await ensureShortcutService(targetDir, cwd, config, options);
438
+ if (toSkip.length > 0) {
345
439
  console.log('\n' + chalk.dim('Components skipped (up to date):'));
346
- componentsToSkip.forEach(name => {
347
- console.log(chalk.dim(' - ') + chalk.gray(name));
348
- });
440
+ toSkip.forEach(name => console.log(chalk.dim(' - ') + chalk.gray(name)));
349
441
  }
350
442
  console.log('');
351
443
  }
@@ -0,0 +1 @@
1
+ export {};