@gilav21/shadcn-angular 0.0.15 → 0.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/add.js +100 -10
- package/dist/commands/init.js +75 -15
- package/dist/registry/index.d.ts +6 -0
- package/dist/registry/index.js +94 -1
- package/dist/templates/utils.js +31 -1
- package/dist/utils/shortcut-registry.d.ts +7 -0
- package/dist/utils/shortcut-registry.js +58 -0
- package/package.json +1 -1
- package/src/commands/add.ts +405 -303
- package/src/commands/init.ts +145 -77
- package/src/registry/index.ts +100 -1
- package/src/templates/utils.ts +31 -1
- package/src/utils/shortcut-registry.ts +79 -0
package/src/commands/add.ts
CHANGED
|
@@ -1,303 +1,405 @@
|
|
|
1
|
-
import fs from 'fs-extra';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { fileURLToPath } from 'url';
|
|
4
|
-
import prompts from 'prompts';
|
|
5
|
-
import chalk from 'chalk';
|
|
6
|
-
import ora from 'ora';
|
|
7
|
-
import { getConfig } from '../utils/config.js';
|
|
8
|
-
import { registry, type ComponentName } from '../registry/index.js';
|
|
9
|
-
import { installPackages } from '../utils/package-manager.js';
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
if (
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
}
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import prompts from 'prompts';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import ora from 'ora';
|
|
7
|
+
import { getConfig } from '../utils/config.js';
|
|
8
|
+
import { registry, type ComponentName } from '../registry/index.js';
|
|
9
|
+
import { installPackages } from '../utils/package-manager.js';
|
|
10
|
+
import { writeShortcutRegistryIndex, type ShortcutRegistryEntry } from '../utils/shortcut-registry.js';
|
|
11
|
+
|
|
12
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
+
const __dirname = path.dirname(__filename);
|
|
14
|
+
|
|
15
|
+
// Base URL for the component registry (GitHub raw content)
|
|
16
|
+
const REGISTRY_BASE_URL = 'https://raw.githubusercontent.com/gilav21/shadcn-angular/master/packages/components/ui';
|
|
17
|
+
const LIB_REGISTRY_BASE_URL = 'https://raw.githubusercontent.com/gilav21/shadcn-angular/master/packages/components/lib';
|
|
18
|
+
|
|
19
|
+
// Components source directory (relative to CLI dist folder) for local dev
|
|
20
|
+
function getLocalComponentsDir(): string | null {
|
|
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;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface AddOptions {
|
|
35
|
+
yes?: boolean;
|
|
36
|
+
overwrite?: boolean;
|
|
37
|
+
all?: boolean;
|
|
38
|
+
path?: string;
|
|
39
|
+
remote?: boolean; // Force remote fetch
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function getLocalLibDir(): string | null {
|
|
43
|
+
const fromDist = path.resolve(__dirname, '../../../components/lib');
|
|
44
|
+
if (fs.existsSync(fromDist)) {
|
|
45
|
+
return fromDist;
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function resolveProjectPath(cwd: string, inputPath: string): string {
|
|
51
|
+
const resolved = path.resolve(cwd, inputPath);
|
|
52
|
+
const relative = path.relative(cwd, resolved);
|
|
53
|
+
if (relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
54
|
+
throw new Error(`Path must stay inside the project directory: ${inputPath}`);
|
|
55
|
+
}
|
|
56
|
+
return resolved;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function aliasToProjectPath(aliasOrPath: string): string {
|
|
60
|
+
return aliasOrPath.startsWith('@/')
|
|
61
|
+
? path.join('src', aliasOrPath.slice(2))
|
|
62
|
+
: aliasOrPath;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function fetchComponentContent(file: string, options: AddOptions): Promise<string> {
|
|
66
|
+
const localDir = getLocalComponentsDir();
|
|
67
|
+
|
|
68
|
+
// 1. Prefer local if available and not forced remote
|
|
69
|
+
if (localDir && !options.remote) {
|
|
70
|
+
const localPath = path.join(localDir, file);
|
|
71
|
+
if (await fs.pathExists(localPath)) {
|
|
72
|
+
return fs.readFile(localPath, 'utf-8');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 2. Fetch from remote registry
|
|
77
|
+
const url = `${REGISTRY_BASE_URL}/${file}`;
|
|
78
|
+
try {
|
|
79
|
+
const response = await fetch(url);
|
|
80
|
+
if (!response.ok) {
|
|
81
|
+
throw new Error(`Failed to fetch component from ${url}: ${response.statusText}`);
|
|
82
|
+
}
|
|
83
|
+
return await response.text();
|
|
84
|
+
} catch (error) {
|
|
85
|
+
if (localDir) {
|
|
86
|
+
throw new Error(`Component file not found locally or remotely: ${file}`);
|
|
87
|
+
}
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function fetchLibContent(file: string, options: AddOptions): Promise<string> {
|
|
93
|
+
const localDir = getLocalLibDir();
|
|
94
|
+
|
|
95
|
+
if (localDir && !options.remote) {
|
|
96
|
+
const localPath = path.join(localDir, file);
|
|
97
|
+
if (await fs.pathExists(localPath)) {
|
|
98
|
+
return fs.readFile(localPath, 'utf-8');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const url = `${LIB_REGISTRY_BASE_URL}/${file}`;
|
|
103
|
+
const response = await fetch(url);
|
|
104
|
+
if (!response.ok) {
|
|
105
|
+
throw new Error(`Failed to fetch library file from ${url}: ${response.statusText}`);
|
|
106
|
+
}
|
|
107
|
+
return response.text();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function collectInstalledShortcutEntries(targetDir: string): ShortcutRegistryEntry[] {
|
|
111
|
+
const entries: ShortcutRegistryEntry[] = [];
|
|
112
|
+
for (const definition of Object.values(registry)) {
|
|
113
|
+
if (!definition.shortcutDefinitions?.length) {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
for (const shortcutDefinition of definition.shortcutDefinitions) {
|
|
117
|
+
const sourcePath = path.join(targetDir, shortcutDefinition.sourceFile);
|
|
118
|
+
if (fs.existsSync(sourcePath)) {
|
|
119
|
+
entries.push(shortcutDefinition);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return entries;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export async function add(components: string[], options: AddOptions) {
|
|
127
|
+
const cwd = process.cwd();
|
|
128
|
+
|
|
129
|
+
// Load config
|
|
130
|
+
const config = await getConfig(cwd);
|
|
131
|
+
if (!config) {
|
|
132
|
+
console.log(chalk.red('Error: components.json not found.'));
|
|
133
|
+
console.log(chalk.dim('Run `npx @gilav21/shadcn-angular init` first.'));
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Get components to add
|
|
138
|
+
let componentsToAdd: ComponentName[] = [];
|
|
139
|
+
|
|
140
|
+
if (options.all) {
|
|
141
|
+
componentsToAdd = Object.keys(registry) as ComponentName[];
|
|
142
|
+
} else if (components.length === 0) {
|
|
143
|
+
const { selected } = await prompts({
|
|
144
|
+
type: 'multiselect',
|
|
145
|
+
name: 'selected',
|
|
146
|
+
message: 'Which components would you like to add?',
|
|
147
|
+
choices: Object.keys(registry).map(name => ({
|
|
148
|
+
title: name,
|
|
149
|
+
value: name,
|
|
150
|
+
})),
|
|
151
|
+
hint: '- Space to select, Enter to confirm',
|
|
152
|
+
});
|
|
153
|
+
componentsToAdd = selected;
|
|
154
|
+
} else {
|
|
155
|
+
componentsToAdd = components as ComponentName[];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (!componentsToAdd || componentsToAdd.length === 0) {
|
|
159
|
+
console.log(chalk.dim('No components selected.'));
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Validate components exist
|
|
164
|
+
const invalidComponents = componentsToAdd.filter(c => !registry[c]);
|
|
165
|
+
if (invalidComponents.length > 0) {
|
|
166
|
+
console.log(chalk.red(`Invalid component(s): ${invalidComponents.join(', ')}`));
|
|
167
|
+
console.log(chalk.dim('Available components: ' + Object.keys(registry).join(', ')));
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Resolve dependencies
|
|
172
|
+
const allComponents = new Set<ComponentName>();
|
|
173
|
+
const resolveDeps = (name: ComponentName) => {
|
|
174
|
+
if (allComponents.has(name)) return;
|
|
175
|
+
allComponents.add(name);
|
|
176
|
+
const component = registry[name];
|
|
177
|
+
if (component.dependencies) {
|
|
178
|
+
component.dependencies.forEach(dep => resolveDeps(dep as ComponentName));
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
componentsToAdd.forEach(c => resolveDeps(c));
|
|
182
|
+
|
|
183
|
+
const uiBasePath = options.path ?? aliasToProjectPath(config.aliases.ui || 'src/components/ui');
|
|
184
|
+
const targetDir = resolveProjectPath(cwd, uiBasePath);
|
|
185
|
+
|
|
186
|
+
// Check for existing files and diff
|
|
187
|
+
const componentsToInstall: ComponentName[] = [];
|
|
188
|
+
const componentsToSkip: string[] = [];
|
|
189
|
+
const conflictingComponents: ComponentName[] = [];
|
|
190
|
+
const contentCache = new Map<string, string>();
|
|
191
|
+
|
|
192
|
+
const checkSpinner = ora('Checking for conflicts...').start();
|
|
193
|
+
|
|
194
|
+
for (const name of allComponents) {
|
|
195
|
+
const component = registry[name];
|
|
196
|
+
let hasChanges = false;
|
|
197
|
+
let isFullyPresent = true;
|
|
198
|
+
|
|
199
|
+
for (const file of component.files) {
|
|
200
|
+
const targetPath = path.join(targetDir, file);
|
|
201
|
+
if (await fs.pathExists(targetPath)) {
|
|
202
|
+
const localContent = await fs.readFile(targetPath, 'utf-8');
|
|
203
|
+
|
|
204
|
+
try {
|
|
205
|
+
let remoteContent = await fetchComponentContent(file, options);
|
|
206
|
+
// Transform all lib/ imports for comparison
|
|
207
|
+
const libAlias = config.aliases.utils.replace(/\/[^/]+$/, '');
|
|
208
|
+
remoteContent = remoteContent.replace(/(\.\.\/)+lib\//g, libAlias + '/');
|
|
209
|
+
|
|
210
|
+
const normalize = (str: string) => str.replace(/\r\n/g, '\n').trim();
|
|
211
|
+
if (normalize(localContent) !== normalize(remoteContent)) {
|
|
212
|
+
hasChanges = true;
|
|
213
|
+
}
|
|
214
|
+
contentCache.set(file, remoteContent); // Cache for installation
|
|
215
|
+
} catch (error) {
|
|
216
|
+
// unexpected error fetching remote
|
|
217
|
+
console.warn(`Could not fetch remote content for comparison: ${file}`);
|
|
218
|
+
hasChanges = true; // Assume changed/unknown
|
|
219
|
+
}
|
|
220
|
+
} else {
|
|
221
|
+
isFullyPresent = false;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (isFullyPresent && !hasChanges) {
|
|
226
|
+
componentsToSkip.push(name);
|
|
227
|
+
} else if (hasChanges) {
|
|
228
|
+
conflictingComponents.push(name);
|
|
229
|
+
} else {
|
|
230
|
+
componentsToInstall.push(name);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
checkSpinner.stop();
|
|
235
|
+
|
|
236
|
+
let componentsToOverwrite: ComponentName[] = [];
|
|
237
|
+
|
|
238
|
+
if (conflictingComponents.length > 0) {
|
|
239
|
+
if (options.overwrite) {
|
|
240
|
+
componentsToOverwrite = conflictingComponents;
|
|
241
|
+
} else if (options.yes) {
|
|
242
|
+
componentsToOverwrite = []; // Skip conflicts in non-interactive mode unless --overwrite
|
|
243
|
+
} else {
|
|
244
|
+
console.log(chalk.yellow(`\n${conflictingComponents.length} component(s) have local changes or are different from remote.`));
|
|
245
|
+
const { selected } = await prompts({
|
|
246
|
+
type: 'multiselect',
|
|
247
|
+
name: 'selected',
|
|
248
|
+
message: 'Select components to OVERWRITE (Unselected will be skipped):',
|
|
249
|
+
choices: conflictingComponents.map(name => ({
|
|
250
|
+
title: name,
|
|
251
|
+
value: name,
|
|
252
|
+
})),
|
|
253
|
+
hint: '- Space to select, Enter to confirm',
|
|
254
|
+
});
|
|
255
|
+
componentsToOverwrite = selected || [];
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Final list of components to process
|
|
260
|
+
// We process:
|
|
261
|
+
// 1. componentsToInstall (Brand new or partial)
|
|
262
|
+
// 2. componentsToOverwrite (User selected)
|
|
263
|
+
// We SKIP:
|
|
264
|
+
// 1. componentsToSkip (Identical)
|
|
265
|
+
// 2. conflictingComponents NOT in componentsToOverwrite
|
|
266
|
+
|
|
267
|
+
const finalComponents = [...componentsToInstall, ...componentsToOverwrite];
|
|
268
|
+
|
|
269
|
+
if (finalComponents.length === 0 && componentsToSkip.length > 0) {
|
|
270
|
+
console.log(chalk.green(`\nAll components are up to date! (${componentsToSkip.length} skipped)`));
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (finalComponents.length === 0) {
|
|
275
|
+
console.log(chalk.dim('\nNo components to install.'));
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const spinner = ora('Installing components...').start();
|
|
280
|
+
let successCount = 0;
|
|
281
|
+
|
|
282
|
+
try {
|
|
283
|
+
await fs.ensureDir(targetDir);
|
|
284
|
+
|
|
285
|
+
for (const name of finalComponents) {
|
|
286
|
+
const component = registry[name];
|
|
287
|
+
let componentSuccess = true;
|
|
288
|
+
|
|
289
|
+
for (const file of component.files) {
|
|
290
|
+
const targetPath = path.join(targetDir, file);
|
|
291
|
+
|
|
292
|
+
try {
|
|
293
|
+
let content = contentCache.get(file);
|
|
294
|
+
if (!content) {
|
|
295
|
+
content = await fetchComponentContent(file, options);
|
|
296
|
+
// Transform all lib/ imports if not already transformed (cached is transformed)
|
|
297
|
+
const libAlias = config.aliases.utils.replace(/\/[^/]+$/, '');
|
|
298
|
+
content = content.replace(/(\.\.\/)+lib\//g, libAlias + '/');
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
await fs.ensureDir(path.dirname(targetPath));
|
|
302
|
+
await fs.writeFile(targetPath, content);
|
|
303
|
+
// spinner.text = `Added ${file}`; // Too verbose?
|
|
304
|
+
} catch (err: any) {
|
|
305
|
+
spinner.warn(`Could not add ${file}: ${err.message}`);
|
|
306
|
+
componentSuccess = false;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
if (componentSuccess) {
|
|
310
|
+
successCount++;
|
|
311
|
+
spinner.text = `Added ${name}`;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (successCount > 0) {
|
|
316
|
+
spinner.succeed(chalk.green(`Success! Added ${successCount} component(s)`));
|
|
317
|
+
|
|
318
|
+
console.log('\n' + chalk.dim('Components added:'));
|
|
319
|
+
finalComponents.forEach(name => {
|
|
320
|
+
console.log(chalk.dim(' - ') + chalk.cyan(name));
|
|
321
|
+
});
|
|
322
|
+
} else {
|
|
323
|
+
spinner.info('No new components installed.');
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Install required lib utility files
|
|
327
|
+
if (finalComponents.length > 0) {
|
|
328
|
+
const requiredLibFiles = new Set<string>();
|
|
329
|
+
for (const name of allComponents) {
|
|
330
|
+
const component = registry[name];
|
|
331
|
+
if (component.libFiles) {
|
|
332
|
+
component.libFiles.forEach(f => requiredLibFiles.add(f));
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (requiredLibFiles.size > 0) {
|
|
337
|
+
const utilsPathResolved = resolveProjectPath(cwd, aliasToProjectPath(config.aliases.utils) + '.ts');
|
|
338
|
+
const libDir = path.dirname(utilsPathResolved);
|
|
339
|
+
await fs.ensureDir(libDir);
|
|
340
|
+
|
|
341
|
+
for (const libFile of requiredLibFiles) {
|
|
342
|
+
const libTargetPath = path.join(libDir, libFile);
|
|
343
|
+
if (!await fs.pathExists(libTargetPath) || options.overwrite) {
|
|
344
|
+
try {
|
|
345
|
+
const libContent = await fetchLibContent(libFile, options);
|
|
346
|
+
await fs.writeFile(libTargetPath, libContent);
|
|
347
|
+
} catch (err: any) {
|
|
348
|
+
console.warn(chalk.yellow(`Could not install lib file ${libFile}: ${err.message}`));
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (finalComponents.length > 0) {
|
|
356
|
+
const npmDependencies = new Set<string>();
|
|
357
|
+
for (const name of finalComponents) {
|
|
358
|
+
const component = registry[name];
|
|
359
|
+
if (component.npmDependencies) {
|
|
360
|
+
component.npmDependencies.forEach(dep => npmDependencies.add(dep));
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (npmDependencies.size > 0) {
|
|
365
|
+
const depSpinner = ora('Installing dependencies...').start();
|
|
366
|
+
try {
|
|
367
|
+
await installPackages(Array.from(npmDependencies), { cwd });
|
|
368
|
+
depSpinner.succeed('Dependencies installed.');
|
|
369
|
+
} catch (e) {
|
|
370
|
+
depSpinner.fail('Failed to install dependencies.');
|
|
371
|
+
console.error(e);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const shortcutEntries = collectInstalledShortcutEntries(targetDir);
|
|
377
|
+
if (shortcutEntries.length > 0) {
|
|
378
|
+
const utilsPathResolved = resolveProjectPath(cwd, aliasToProjectPath(config.aliases.utils) + '.ts');
|
|
379
|
+
const utilsDir = path.dirname(utilsPathResolved);
|
|
380
|
+
const shortcutServicePath = path.join(utilsDir, 'shortcut-binding.service.ts');
|
|
381
|
+
|
|
382
|
+
if (!await fs.pathExists(shortcutServicePath)) {
|
|
383
|
+
const shortcutServiceContent = await fetchLibContent('shortcut-binding.service.ts', options);
|
|
384
|
+
await fs.ensureDir(utilsDir);
|
|
385
|
+
await fs.writeFile(shortcutServicePath, shortcutServiceContent);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
await writeShortcutRegistryIndex(cwd, config, shortcutEntries);
|
|
390
|
+
|
|
391
|
+
if (componentsToSkip.length > 0) {
|
|
392
|
+
console.log('\n' + chalk.dim('Components skipped (up to date):'));
|
|
393
|
+
componentsToSkip.forEach(name => {
|
|
394
|
+
console.log(chalk.dim(' - ') + chalk.gray(name));
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
console.log('');
|
|
399
|
+
|
|
400
|
+
} catch (error) {
|
|
401
|
+
spinner.fail('Failed to add components');
|
|
402
|
+
console.error(error);
|
|
403
|
+
process.exit(1);
|
|
404
|
+
}
|
|
405
|
+
}
|