@ijuantm/simpl-addon 2.6.3 → 2.6.5
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/install.js +134 -187
- package/package.json +1 -1
package/install.js
CHANGED
|
@@ -16,8 +16,20 @@ const COLORS = {
|
|
|
16
16
|
|
|
17
17
|
const CDN_BASE = 'https://cdn.simpl.iwanvanderwal.nl/framework';
|
|
18
18
|
const LOCAL_RELEASES_DIR = process.env.SIMPL_LOCAL_RELEASES || path.join(process.cwd(), 'local-releases');
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
const BOX_WIDTH = 62;
|
|
20
|
+
|
|
21
|
+
const log = (message = '', color = 'reset') => console.log(`${COLORS[color]}${message}${COLORS.reset}`);
|
|
22
|
+
|
|
23
|
+
const box = (title) => {
|
|
24
|
+
log();
|
|
25
|
+
log(` ╭${'─'.repeat(BOX_WIDTH)}╮`);
|
|
26
|
+
const contentWidth = BOX_WIDTH - 2; // Remove the two padding characters on each side
|
|
27
|
+
const remainingSpace = contentWidth - (typeof title === 'string' ? title.length : 0);
|
|
28
|
+
const padding = remainingSpace > 0 ? ' '.repeat(remainingSpace) : '';
|
|
29
|
+
log(` │ ${COLORS.bold}${title}${COLORS.reset}${padding}│`);
|
|
30
|
+
log(` ╰${'─'.repeat(BOX_WIDTH)}╯`);
|
|
31
|
+
log();
|
|
32
|
+
};
|
|
21
33
|
|
|
22
34
|
const fetchUrl = (url) => new Promise((resolve, reject) => {
|
|
23
35
|
https.get(url, res => {
|
|
@@ -62,7 +74,6 @@ const downloadFile = (url, dest) => new Promise((resolve, reject) => {
|
|
|
62
74
|
const promptUser = (question, defaultValue = '') => new Promise(resolve => {
|
|
63
75
|
const rl = readline.createInterface({input: process.stdin, output: process.stdout});
|
|
64
76
|
const prompt = defaultValue ? `${question} ${COLORS.dim}(${defaultValue})${COLORS.reset}: ` : `${question}: `;
|
|
65
|
-
|
|
66
77
|
rl.question(prompt, answer => {
|
|
67
78
|
rl.close();
|
|
68
79
|
resolve(answer.trim() || defaultValue);
|
|
@@ -73,48 +84,22 @@ const printAnswer = (question, value) => console.log(`${question}: ${COLORS.cyan
|
|
|
73
84
|
|
|
74
85
|
const getSimplVersion = () => {
|
|
75
86
|
const simplFile = path.join(process.cwd(), '.simpl');
|
|
76
|
-
|
|
77
87
|
if (!fs.existsSync(simplFile)) throw new Error('Not a Simpl project. Missing .simpl file in current directory.');
|
|
78
|
-
|
|
79
88
|
const config = JSON.parse(fs.readFileSync(simplFile, 'utf8'));
|
|
80
|
-
|
|
81
89
|
if (!config.version) throw new Error('Invalid .simpl file: missing version field');
|
|
82
|
-
|
|
83
90
|
return config.version;
|
|
84
91
|
};
|
|
85
92
|
|
|
86
|
-
// --- Argument parsing ---
|
|
87
|
-
|
|
88
93
|
const parseArgs = (args) => {
|
|
89
94
|
const result = {addon: null, unknownFlags: [], help: false, list: false};
|
|
90
|
-
|
|
91
95
|
for (const arg of args) {
|
|
92
|
-
if (arg === '--help' || arg === '-h')
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if (arg
|
|
97
|
-
|
|
98
|
-
continue;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (arg.startsWith('--addon=')) {
|
|
102
|
-
result.addon = arg.slice(8).trim() || null;
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
105
|
-
if (arg.startsWith('-a=')) {
|
|
106
|
-
result.addon = arg.slice(3).trim() || null;
|
|
107
|
-
continue;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (arg.startsWith('-') && !arg.startsWith('--addon') && !arg.startsWith('-a')) {
|
|
111
|
-
result.unknownFlags.push(arg);
|
|
112
|
-
continue;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (!arg.startsWith('-') && !result.addon) result.addon = arg;
|
|
96
|
+
if (arg === '--help' || arg === '-h') result.help = true;
|
|
97
|
+
else if (arg === '--list' || arg === '-l') result.list = true;
|
|
98
|
+
else if (arg.startsWith('--addon=')) result.addon = arg.slice(8).trim() || null;
|
|
99
|
+
else if (arg.startsWith('-a=')) result.addon = arg.slice(3).trim() || null;
|
|
100
|
+
else if (arg.startsWith('-') && !arg.startsWith('--addon') && !arg.startsWith('-a')) result.unknownFlags.push(arg);
|
|
101
|
+
else if (!arg.startsWith('-') && !result.addon) result.addon = arg;
|
|
116
102
|
}
|
|
117
|
-
|
|
118
103
|
return result;
|
|
119
104
|
};
|
|
120
105
|
|
|
@@ -123,17 +108,12 @@ const KNOWN_FLAGS = ['--addon', '-a', '--help', '-h', '--list', '-l'];
|
|
|
123
108
|
const levenshtein = (a, b) => {
|
|
124
109
|
const m = a.length, n = b.length;
|
|
125
110
|
const dp = Array.from({length: m + 1}, (_, i) => Array.from({length: n + 1}, (_, j) => i === 0 ? j : j === 0 ? i : 0));
|
|
126
|
-
|
|
127
|
-
for (let i = 1; i <= m; i++)
|
|
128
|
-
for (let j = 1; j <= n; j++)
|
|
129
|
-
dp[i][j] = a[i - 1] === b[j - 1] ? dp[i - 1][j - 1] : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
|
|
130
|
-
|
|
111
|
+
for (let i = 1; i <= m; i++) for (let j = 1; j <= n; j++) dp[i][j] = a[i - 1] === b[j - 1] ? dp[i - 1][j - 1] : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
|
|
131
112
|
return dp[m][n];
|
|
132
113
|
};
|
|
133
114
|
|
|
134
115
|
const closestMatch = (input, options) => {
|
|
135
116
|
let best = null, bestDist = Infinity;
|
|
136
|
-
|
|
137
117
|
for (const opt of options) {
|
|
138
118
|
const dist = levenshtein(input.toLowerCase(), opt.toLowerCase());
|
|
139
119
|
if (dist < bestDist) {
|
|
@@ -141,50 +121,43 @@ const closestMatch = (input, options) => {
|
|
|
141
121
|
best = opt;
|
|
142
122
|
}
|
|
143
123
|
}
|
|
144
|
-
|
|
145
124
|
return bestDist <= Math.max(3, Math.floor(input.length / 2)) ? best : null;
|
|
146
125
|
};
|
|
147
126
|
|
|
148
|
-
// --- Fuzzy addon resolution with interactive prompts ---
|
|
149
|
-
|
|
150
127
|
const promptAddon = async (addons, firstInput = null) => {
|
|
151
128
|
const askSuggestion = async (input) => {
|
|
152
129
|
const suggestion = closestMatch(input, addons);
|
|
153
|
-
|
|
154
|
-
log(`
|
|
130
|
+
log();
|
|
131
|
+
log(` ${COLORS.red}✗${COLORS.reset} Add-on ${COLORS.bold}${input}${COLORS.reset} not found`);
|
|
155
132
|
|
|
156
133
|
if (suggestion) {
|
|
157
|
-
log(`
|
|
158
|
-
|
|
134
|
+
log(` Did you mean: ${COLORS.blue}${suggestion}${COLORS.reset}?`);
|
|
135
|
+
log();
|
|
159
136
|
|
|
160
137
|
while (true) {
|
|
161
|
-
const answer = await promptUser(`
|
|
138
|
+
const answer = await promptUser(` Use "${suggestion}"? ${COLORS.dim}(yes / no (lists available add-ons))${COLORS.reset}`);
|
|
162
139
|
const a = answer.toLowerCase();
|
|
163
|
-
|
|
164
140
|
if (a === 'yes' || a === 'y') return suggestion;
|
|
165
141
|
if (a === 'no' || a === 'n') break;
|
|
166
|
-
|
|
167
|
-
log(` ${COLORS.dim}Please answer yes or no${COLORS.reset}`);
|
|
142
|
+
log(` ${COLORS.yellow}⚠${COLORS.reset} ${COLORS.dim}Please answer yes or no${COLORS.reset}`);
|
|
168
143
|
}
|
|
169
144
|
}
|
|
170
145
|
|
|
171
|
-
|
|
172
|
-
log(`
|
|
146
|
+
log();
|
|
147
|
+
log(` ${COLORS.bold}Available add-ons:${COLORS.reset}`, 'blue');
|
|
173
148
|
listAddons(addons);
|
|
174
|
-
|
|
175
|
-
|
|
149
|
+
log();
|
|
176
150
|
return null;
|
|
177
151
|
};
|
|
178
152
|
|
|
179
153
|
let pending = firstInput;
|
|
180
|
-
|
|
181
154
|
while (true) {
|
|
182
|
-
const input = pending || await promptUser(`
|
|
155
|
+
const input = pending || await promptUser(` Add-on to install ${COLORS.dim}(name or number)${COLORS.reset}`);
|
|
183
156
|
pending = null;
|
|
184
157
|
|
|
185
158
|
if (!input) {
|
|
186
|
-
log(`
|
|
187
|
-
|
|
159
|
+
log(` ${COLORS.yellow}⚠${COLORS.reset} ${COLORS.dim}Selection cannot be empty${COLORS.reset}`);
|
|
160
|
+
log();
|
|
188
161
|
continue;
|
|
189
162
|
}
|
|
190
163
|
|
|
@@ -197,42 +170,31 @@ const promptAddon = async (addons, firstInput = null) => {
|
|
|
197
170
|
}
|
|
198
171
|
};
|
|
199
172
|
|
|
200
|
-
const listAddons = (addons) => addons.forEach((name, i) => log(`
|
|
201
|
-
|
|
202
|
-
// --- Help ---
|
|
173
|
+
const listAddons = (addons) => addons.forEach((name, i) => log(` ${COLORS.cyan}${i + 1}.${COLORS.reset} ${name}`));
|
|
203
174
|
|
|
204
175
|
const showHelp = () => {
|
|
205
|
-
|
|
206
|
-
log(`
|
|
207
|
-
log(`
|
|
208
|
-
log(`
|
|
209
|
-
|
|
210
|
-
log(
|
|
211
|
-
log(`
|
|
212
|
-
log(`
|
|
213
|
-
log(`
|
|
214
|
-
|
|
215
|
-
log(
|
|
216
|
-
log(`
|
|
217
|
-
log(`
|
|
218
|
-
log(`
|
|
219
|
-
|
|
220
|
-
log(` ${COLORS.bold}Note:${COLORS.reset}`, 'blue');
|
|
221
|
-
log(` Run this command from the root of your Simpl project.`);
|
|
222
|
-
log(` The add-on version will match your Simpl framework version.`);
|
|
223
|
-
console.log();
|
|
176
|
+
box('Simpl Add-on Installer');
|
|
177
|
+
log(` ${COLORS.bold}Usage:${COLORS.reset}`, 'blue');
|
|
178
|
+
log(` ${COLORS.dim}npx @ijuantm/simpl-addon${COLORS.reset}`);
|
|
179
|
+
log(` ${COLORS.dim}npx @ijuantm/simpl-addon --addon=<name>${COLORS.reset}`);
|
|
180
|
+
log(` ${COLORS.dim}npx @ijuantm/simpl-addon --help${COLORS.reset}`);
|
|
181
|
+
log();
|
|
182
|
+
log(` ${COLORS.bold}Options:${COLORS.reset}`, 'blue');
|
|
183
|
+
log(` ${COLORS.dim}--addon=<name>, -a=<name>${COLORS.reset} Add-on to install`);
|
|
184
|
+
log(` ${COLORS.dim}--list, -l${COLORS.reset} List available add-ons`);
|
|
185
|
+
log(` ${COLORS.dim}--help, -h${COLORS.reset} Show this help message`);
|
|
186
|
+
log();
|
|
187
|
+
log(` ${COLORS.bold}Note:${COLORS.reset}`, 'blue');
|
|
188
|
+
log(` Run this command from the root of your Simpl project.`);
|
|
189
|
+
log(` The add-on version will match your Simpl framework version.`);
|
|
190
|
+
log();
|
|
224
191
|
};
|
|
225
192
|
|
|
226
193
|
const checkServerAvailability = () => new Promise(resolve => {
|
|
227
|
-
|
|
194
|
+
https.get(`${CDN_BASE}/versions.json`, {timeout: 5000}, res => {
|
|
228
195
|
res.resume();
|
|
229
196
|
resolve(res.statusCode === 200);
|
|
230
|
-
});
|
|
231
|
-
req.on('error', () => resolve(false));
|
|
232
|
-
req.on('timeout', () => {
|
|
233
|
-
req.destroy();
|
|
234
|
-
resolve(false);
|
|
235
|
-
});
|
|
197
|
+
}).on('error', () => resolve(false)).on('timeout', () => resolve(false));
|
|
236
198
|
});
|
|
237
199
|
|
|
238
200
|
const getVersionsData = async () => {
|
|
@@ -254,11 +216,10 @@ const getAvailableAddons = async (version) => {
|
|
|
254
216
|
|
|
255
217
|
const extractMarkers = (content) => {
|
|
256
218
|
const markers = [];
|
|
257
|
-
|
|
258
219
|
content.split('\n').forEach((line, i) => {
|
|
259
|
-
const afterMatch = line.match(/@addon-insert:after\s*\(\s*(["'])(
|
|
260
|
-
const beforeMatch = line.match(/@addon-insert:before\s*\(\s*(["'])(
|
|
261
|
-
const replaceMatch = line.match(/@addon-insert:replace\s*\(\s*(["'])(
|
|
220
|
+
const afterMatch = line.match(/@addon-insert:after\s*\(\s*(["'])(.*?)\1\s*\)/);
|
|
221
|
+
const beforeMatch = line.match(/@addon-insert:before\s*\(\s*(["'])(.*?)\1\s*\)/);
|
|
222
|
+
const replaceMatch = line.match(/@addon-insert:replace\s*\(\s*(["'])(.*?)\1\s*\)/);
|
|
262
223
|
|
|
263
224
|
if (afterMatch) markers.push({type: 'after', lineIndex: i, searchText: afterMatch[2]});
|
|
264
225
|
else if (beforeMatch) markers.push({type: 'before', lineIndex: i, searchText: beforeMatch[2]});
|
|
@@ -266,18 +227,15 @@ const extractMarkers = (content) => {
|
|
|
266
227
|
else if (line.includes('@addon-insert:prepend')) markers.push({type: 'prepend', lineIndex: i});
|
|
267
228
|
else if (line.includes('@addon-insert:append')) markers.push({type: 'append', lineIndex: i});
|
|
268
229
|
});
|
|
269
|
-
|
|
270
230
|
return markers;
|
|
271
231
|
};
|
|
272
232
|
|
|
273
233
|
const collectContentBetweenMarkers = (lines, startIndex) => {
|
|
274
234
|
const content = [];
|
|
275
|
-
|
|
276
235
|
for (let i = startIndex + 1; i < lines.length; i++) {
|
|
277
236
|
if (lines[i].trim().includes('@addon-end')) break;
|
|
278
237
|
content.push(lines[i]);
|
|
279
238
|
}
|
|
280
|
-
|
|
281
239
|
return content;
|
|
282
240
|
};
|
|
283
241
|
|
|
@@ -285,10 +243,8 @@ const normalizeContent = (lines) => lines.map(l => l.trim()).filter(l => l && !l
|
|
|
285
243
|
|
|
286
244
|
const processEnvContent = (content, targetContent) => {
|
|
287
245
|
const envVarsToAdd = [], comments = [];
|
|
288
|
-
|
|
289
246
|
content.forEach(line => {
|
|
290
247
|
const trimmed = line.trim();
|
|
291
|
-
|
|
292
248
|
if (trimmed.startsWith('#') || !trimmed) {
|
|
293
249
|
comments.push(line);
|
|
294
250
|
return;
|
|
@@ -297,7 +253,6 @@ const processEnvContent = (content, targetContent) => {
|
|
|
297
253
|
const match = line.match(/^([A-Z_][A-Z0-9_]*)=/);
|
|
298
254
|
if (match && !new RegExp(`^${match[1]}=`, 'm').test(targetContent)) envVarsToAdd.push(line);
|
|
299
255
|
});
|
|
300
|
-
|
|
301
256
|
return {content: [...comments, ...envVarsToAdd], count: envVarsToAdd.length};
|
|
302
257
|
};
|
|
303
258
|
|
|
@@ -372,12 +327,11 @@ const mergeFile = (targetPath, addonContent, markers, isEnv = false) => {
|
|
|
372
327
|
});
|
|
373
328
|
|
|
374
329
|
if (newContent !== targetContent) fs.writeFileSync(targetPath, newContent, 'utf8');
|
|
375
|
-
|
|
376
330
|
return {modified: newContent !== targetContent, operations};
|
|
377
331
|
};
|
|
378
332
|
|
|
379
333
|
const printMergeResults = (relativePath, isEnv, result) => {
|
|
380
|
-
const indent = '
|
|
334
|
+
const indent = ' ';
|
|
381
335
|
const varText = isEnv ? 'environment variable' : 'line';
|
|
382
336
|
let hasChanges = false;
|
|
383
337
|
|
|
@@ -400,18 +354,15 @@ const printMergeResults = (relativePath, isEnv, result) => {
|
|
|
400
354
|
|
|
401
355
|
const extractZip = async (zipPath, destDir) => {
|
|
402
356
|
fs.mkdirSync(destDir, {recursive: true});
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
else await execAsync(`unzip -q "${zipPath}" -d "${destDir}"`);
|
|
357
|
+
const cmd = process.platform === 'win32' ? `powershell -command "Expand-Archive -Path '${zipPath}' -DestinationPath '${destDir}' -Force"` : `unzip -q "${zipPath}" -d "${destDir}"`;
|
|
358
|
+
await execAsync(cmd);
|
|
406
359
|
|
|
407
360
|
const entries = fs.readdirSync(destDir, {withFileTypes: true});
|
|
408
|
-
|
|
409
361
|
if (entries.length === 1 && entries[0].isDirectory()) {
|
|
410
362
|
const nestedDir = path.join(destDir, entries[0].name);
|
|
411
363
|
fs.readdirSync(nestedDir).forEach(item => fs.renameSync(path.join(nestedDir, item), path.join(destDir, item)));
|
|
412
364
|
fs.rmdirSync(nestedDir);
|
|
413
365
|
}
|
|
414
|
-
|
|
415
366
|
return destDir;
|
|
416
367
|
};
|
|
417
368
|
|
|
@@ -443,7 +394,6 @@ const processAddonFiles = (addonDir, targetDir) => {
|
|
|
443
394
|
});
|
|
444
395
|
|
|
445
396
|
processDirectory(addonDir);
|
|
446
|
-
|
|
447
397
|
return {copied, skipped, toMerge};
|
|
448
398
|
};
|
|
449
399
|
|
|
@@ -453,8 +403,8 @@ const downloadAddon = async (addonName, version, targetDir) => {
|
|
|
453
403
|
|
|
454
404
|
try {
|
|
455
405
|
if (fs.existsSync(localZipPath)) {
|
|
456
|
-
|
|
457
|
-
log(`
|
|
406
|
+
log();
|
|
407
|
+
log(` 💻 Using local add-on files`, 'bold');
|
|
458
408
|
const sourceDir = await extractZip(localZipPath, tempExtract);
|
|
459
409
|
const result = processAddonFiles(sourceDir, targetDir);
|
|
460
410
|
fs.rmSync(tempExtract, {recursive: true, force: true});
|
|
@@ -483,14 +433,14 @@ const mergeFiles = (toMerge) => {
|
|
|
483
433
|
|
|
484
434
|
toMerge.forEach(({content, destPath, relativePath, markers}) => {
|
|
485
435
|
const isEnv = path.basename(destPath) === '.env';
|
|
486
|
-
log(`\n
|
|
436
|
+
log(`\n ${COLORS.cyan}•${COLORS.reset} ${COLORS.dim}${relativePath}${COLORS.reset}`);
|
|
487
437
|
|
|
488
438
|
try {
|
|
489
439
|
const result = mergeFile(destPath, content, markers, isEnv);
|
|
490
440
|
if (printMergeResults(relativePath, isEnv, result)) merged.push(relativePath);
|
|
491
441
|
else unchanged.push(relativePath);
|
|
492
442
|
} catch (error) {
|
|
493
|
-
log(`
|
|
443
|
+
log(` ${COLORS.red}✗ Error:${COLORS.reset} ${error.message}`);
|
|
494
444
|
failed.push(relativePath);
|
|
495
445
|
}
|
|
496
446
|
});
|
|
@@ -511,14 +461,14 @@ const main = async () => {
|
|
|
511
461
|
for (const flag of parsed.unknownFlags) {
|
|
512
462
|
const flagName = flag.includes('=') ? flag.slice(0, flag.indexOf('=')) : flag;
|
|
513
463
|
const suggestion = closestMatch(flagName, KNOWN_FLAGS);
|
|
514
|
-
|
|
515
|
-
log(`
|
|
516
|
-
if (suggestion) log(`
|
|
464
|
+
log();
|
|
465
|
+
log(` ${COLORS.yellow}⚠${COLORS.reset} Unknown option: ${COLORS.bold}${flag}${COLORS.reset}`, 'yellow');
|
|
466
|
+
if (suggestion) log(` ${COLORS.dim}Did you mean ${COLORS.reset}${COLORS.cyan}${suggestion}${COLORS.reset}${COLORS.dim}?${COLORS.reset}`);
|
|
517
467
|
}
|
|
518
468
|
|
|
519
469
|
if (parsed.unknownFlags.length > 0) {
|
|
520
|
-
log(`
|
|
521
|
-
|
|
470
|
+
log(` ${COLORS.dim}Run with --help to see all available options.${COLORS.reset}`);
|
|
471
|
+
log();
|
|
522
472
|
process.exit(1);
|
|
523
473
|
}
|
|
524
474
|
|
|
@@ -527,59 +477,57 @@ const main = async () => {
|
|
|
527
477
|
try {
|
|
528
478
|
version = getSimplVersion();
|
|
529
479
|
} catch (error) {
|
|
530
|
-
|
|
531
|
-
log(`
|
|
532
|
-
|
|
480
|
+
log();
|
|
481
|
+
log(` ${COLORS.red}✗${COLORS.reset} ${error.message}`);
|
|
482
|
+
log();
|
|
533
483
|
process.exit(1);
|
|
534
484
|
}
|
|
535
485
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
log(` │ ${COLORS.bold}Simpl Add-on Installer${COLORS.reset} ${COLORS.dim}(v${version})${COLORS.reset}${' '.repeat(34 - version.length)}│`);
|
|
539
|
-
log(` ╰${'─'.repeat(62)}╯`);
|
|
486
|
+
log();
|
|
487
|
+
box(`Simpl Add-on Installer${COLORS.dim}(v${version})${COLORS.reset}`);
|
|
540
488
|
|
|
541
489
|
let versionsData;
|
|
542
490
|
|
|
543
491
|
try {
|
|
544
492
|
versionsData = await getVersionsData();
|
|
545
493
|
} catch (error) {
|
|
546
|
-
|
|
547
|
-
log(`
|
|
548
|
-
if (error.message === 'CDN server is currently unreachable') log(`
|
|
549
|
-
|
|
494
|
+
log();
|
|
495
|
+
log(` ${COLORS.red}✗${COLORS.reset} Failed to fetch version data`);
|
|
496
|
+
if (error.message === 'CDN server is currently unreachable') log(` ${COLORS.dim}The CDN server is currently unavailable. Please try again later.${COLORS.reset}`);
|
|
497
|
+
log();
|
|
550
498
|
process.exit(1);
|
|
551
499
|
}
|
|
552
500
|
|
|
553
501
|
const versionMeta = versionsData.versions[version];
|
|
554
502
|
if (!versionMeta) {
|
|
555
|
-
|
|
556
|
-
log(`
|
|
557
|
-
|
|
503
|
+
log();
|
|
504
|
+
log(` ${COLORS.red}✗${COLORS.reset} Version ${COLORS.bold}${version}${COLORS.reset} not found`);
|
|
505
|
+
log();
|
|
558
506
|
process.exit(1);
|
|
559
507
|
}
|
|
560
508
|
|
|
561
509
|
if (versionMeta['script-compatible'] === false) {
|
|
562
|
-
|
|
563
|
-
log(`
|
|
564
|
-
|
|
565
|
-
log(`
|
|
566
|
-
log(`
|
|
567
|
-
|
|
568
|
-
log(`
|
|
510
|
+
log();
|
|
511
|
+
log(` ${COLORS.red}✗${COLORS.reset} Version ${COLORS.bold}${version}${COLORS.reset} is not compatible with this installer`);
|
|
512
|
+
log();
|
|
513
|
+
log(` ${COLORS.bold}Manual download:${COLORS.reset}`, 'blue');
|
|
514
|
+
log(` ${COLORS.cyan}${CDN_BASE}/${version}/add-ons/`, 'cyan');
|
|
515
|
+
log();
|
|
516
|
+
log(` ${COLORS.bold}Available add-ons for this version:${COLORS.reset}`, 'blue');
|
|
569
517
|
|
|
570
518
|
const addons = versionMeta['add-ons'] || [];
|
|
571
|
-
if (addons.length === 0) log(`
|
|
519
|
+
if (addons.length === 0) log(` ${COLORS.dim}No add-ons available${COLORS.reset}`);
|
|
572
520
|
else addons.forEach(name => {
|
|
573
|
-
log(`
|
|
521
|
+
log(` ${COLORS.cyan}•${COLORS.reset} ${name}: ${COLORS.dim}${CDN_BASE}/${version}/add-ons/${name}.zip${COLORS.reset}`);
|
|
574
522
|
});
|
|
575
523
|
|
|
576
|
-
|
|
524
|
+
log();
|
|
577
525
|
process.exit(1);
|
|
578
526
|
}
|
|
579
527
|
|
|
580
528
|
if (!parsed.addon) {
|
|
581
|
-
|
|
582
|
-
log('
|
|
529
|
+
log();
|
|
530
|
+
log(' 🗄️ Fetching available add-ons...', 'bold');
|
|
583
531
|
}
|
|
584
532
|
|
|
585
533
|
let addons;
|
|
@@ -587,25 +535,25 @@ const main = async () => {
|
|
|
587
535
|
try {
|
|
588
536
|
addons = await getAvailableAddons(version);
|
|
589
537
|
} catch (error) {
|
|
590
|
-
|
|
591
|
-
log(`
|
|
592
|
-
if (error.message === 'CDN server is currently unreachable') log(`
|
|
593
|
-
|
|
538
|
+
log();
|
|
539
|
+
log(` ${COLORS.red}✗${COLORS.reset} Failed to fetch add-ons`);
|
|
540
|
+
if (error.message === 'CDN server is currently unreachable') log(` ${COLORS.dim}The CDN server is currently unavailable. Please try again later.${COLORS.reset}`);
|
|
541
|
+
log();
|
|
594
542
|
process.exit(1);
|
|
595
543
|
}
|
|
596
544
|
|
|
597
545
|
if (addons.length === 0) {
|
|
598
|
-
|
|
599
|
-
log(`
|
|
600
|
-
|
|
546
|
+
log();
|
|
547
|
+
log(` ${COLORS.yellow}⚠${COLORS.reset} No add-ons available for this version`);
|
|
548
|
+
log();
|
|
601
549
|
process.exit(0);
|
|
602
550
|
}
|
|
603
551
|
|
|
604
552
|
if (parsed.list) {
|
|
605
|
-
|
|
606
|
-
log(`
|
|
553
|
+
log();
|
|
554
|
+
log(` ${COLORS.bold}Available add-ons:${COLORS.reset}`, 'blue');
|
|
607
555
|
listAddons(addons);
|
|
608
|
-
|
|
556
|
+
log();
|
|
609
557
|
process.exit(0);
|
|
610
558
|
}
|
|
611
559
|
|
|
@@ -613,73 +561,72 @@ const main = async () => {
|
|
|
613
561
|
|
|
614
562
|
if (parsed.addon) {
|
|
615
563
|
addonName = await promptAddon(addons, parsed.addon);
|
|
616
|
-
printAnswer('
|
|
564
|
+
printAnswer(' Add-on to install', addonName);
|
|
617
565
|
} else {
|
|
618
|
-
|
|
619
|
-
log(`
|
|
566
|
+
log();
|
|
567
|
+
log(` ${COLORS.bold}Available add-ons:${COLORS.reset}`, 'blue');
|
|
620
568
|
listAddons(addons);
|
|
621
|
-
|
|
569
|
+
log();
|
|
622
570
|
|
|
623
571
|
addonName = await promptAddon(addons);
|
|
624
572
|
}
|
|
625
573
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
log(`
|
|
629
|
-
log(` ╰${'─'.repeat(62)}╯`);
|
|
630
|
-
console.log();
|
|
631
|
-
log(` 📦 Downloading ${COLORS.cyan}${addonName}${COLORS.reset} add-on...`, 'bold');
|
|
574
|
+
log();
|
|
575
|
+
box(`Installing: ${COLORS.cyan}${addonName}${COLORS.reset} ${COLORS.dim}(v${version})${COLORS.reset}`);
|
|
576
|
+
log(` 📦 Downloading ${COLORS.cyan}${addonName}${COLORS.reset} add-on...`, 'bold');
|
|
632
577
|
|
|
633
578
|
let copied, skipped, toMerge;
|
|
634
579
|
|
|
635
580
|
try {
|
|
636
581
|
({copied, skipped, toMerge} = await downloadAddon(addonName, version, process.cwd()));
|
|
637
582
|
} catch (error) {
|
|
638
|
-
|
|
639
|
-
log(`
|
|
640
|
-
if (error.message === 'CDN server is currently unreachable') log(`
|
|
641
|
-
else log(`
|
|
642
|
-
|
|
583
|
+
log();
|
|
584
|
+
log(` ${COLORS.red}✗${COLORS.reset} Installation failed`);
|
|
585
|
+
if (error.message === 'CDN server is currently unreachable') log(` ${COLORS.dim}The CDN server is currently unavailable. Please try again later.${COLORS.reset}`);
|
|
586
|
+
else log(` ${COLORS.dim}Please verify the add-on exists and try again${COLORS.reset}`);
|
|
587
|
+
log();
|
|
643
588
|
process.exit(1);
|
|
644
589
|
}
|
|
645
590
|
|
|
646
591
|
if (copied.length > 0) {
|
|
647
|
-
|
|
648
|
-
log(`
|
|
592
|
+
log();
|
|
593
|
+
log(` ${COLORS.green}✓${COLORS.reset} Copied ${COLORS.bold}${copied.length}${COLORS.reset} new file${copied.length !== 1 ? 's' : ''}`);
|
|
649
594
|
}
|
|
650
595
|
|
|
651
596
|
if (skipped.length > 0) {
|
|
652
|
-
|
|
653
|
-
log(`
|
|
654
|
-
skipped.forEach(file => log(`
|
|
597
|
+
log();
|
|
598
|
+
log(` ${COLORS.gray}○${COLORS.reset} ${COLORS.dim}Skipped ${skipped.length} file${skipped.length !== 1 ? 's' : ''} (no merge markers):${COLORS.reset}`);
|
|
599
|
+
skipped.forEach(file => log(` ${COLORS.dim}• ${file}${COLORS.reset}`));
|
|
655
600
|
}
|
|
656
601
|
|
|
657
602
|
if (toMerge.length > 0) {
|
|
658
|
-
|
|
659
|
-
log('
|
|
603
|
+
log();
|
|
604
|
+
log(' 🔀 Merging existing files...', 'bold');
|
|
660
605
|
const {merged, failed, unchanged} = mergeFiles(toMerge);
|
|
661
606
|
|
|
662
|
-
|
|
663
|
-
log('
|
|
664
|
-
|
|
607
|
+
log();
|
|
608
|
+
log(' ' + '─'.repeat(16), 'gray');
|
|
609
|
+
log();
|
|
665
610
|
|
|
666
|
-
if (merged.length > 0) log(`
|
|
667
|
-
if (unchanged.length > 0) log(`
|
|
611
|
+
if (merged.length > 0) log(` ${COLORS.green}✓${COLORS.reset} Successfully merged ${COLORS.bold}${merged.length}${COLORS.reset} file${merged.length !== 1 ? 's' : ''}`);
|
|
612
|
+
if (unchanged.length > 0) log(` ${COLORS.gray}○${COLORS.reset} ${COLORS.dim}${unchanged.length} file${unchanged.length !== 1 ? 's' : ''} unchanged (content already exists)${COLORS.reset}`);
|
|
668
613
|
|
|
669
614
|
if (failed.length > 0) {
|
|
670
|
-
|
|
671
|
-
log(`
|
|
672
|
-
log(`
|
|
673
|
-
failed.forEach(file => log(`
|
|
615
|
+
log();
|
|
616
|
+
log(` ${COLORS.yellow}⚠${COLORS.reset} ${COLORS.yellow}${failed.length} file${failed.length !== 1 ? 's' : ''} failed to merge${COLORS.reset}`);
|
|
617
|
+
log(` ${COLORS.yellow}Please review manually:${COLORS.reset}`);
|
|
618
|
+
failed.forEach(file => log(` ${COLORS.cyan}• ${file}${COLORS.reset}`));
|
|
674
619
|
}
|
|
675
620
|
}
|
|
676
621
|
|
|
677
|
-
|
|
678
|
-
log(`
|
|
679
|
-
|
|
622
|
+
log();
|
|
623
|
+
log(` ${COLORS.green}✓${COLORS.reset} ${COLORS.bold}${COLORS.green}Installation complete!${COLORS.reset}`, 'green');
|
|
624
|
+
log();
|
|
680
625
|
};
|
|
681
626
|
|
|
682
627
|
main().catch(() => {
|
|
683
|
-
log(
|
|
628
|
+
log();
|
|
629
|
+
log(` ${COLORS.red}✗${COLORS.reset} Fatal error occurred`);
|
|
630
|
+
log();
|
|
684
631
|
process.exit(1);
|
|
685
632
|
});
|