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