@ijuantm/simpl-addon 2.6.2 → 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 +152 -228
- 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,94 +118,80 @@ 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
|
-
|
|
149
|
-
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
if (a !== 'no' && a !== 'n') {
|
|
167
|
-
log(` ${COLORS.dim}Please answer y or n${COLORS.reset}`);
|
|
124
|
+
const promptAddon = async (addons, firstInput = null) => {
|
|
125
|
+
const askSuggestion = async (input) => {
|
|
126
|
+
const suggestion = closestMatch(input, addons);
|
|
127
|
+
log();
|
|
128
|
+
log(` ${COLORS.red}✗${COLORS.reset} Add-on ${COLORS.bold}${input}${COLORS.reset} not found`);
|
|
129
|
+
|
|
130
|
+
if (suggestion) {
|
|
131
|
+
log(` Did you mean: ${COLORS.blue}${suggestion}${COLORS.reset}?`);
|
|
132
|
+
log();
|
|
133
|
+
|
|
134
|
+
while (true) {
|
|
135
|
+
const answer = await promptUser(` Use "${suggestion}"? ${COLORS.dim}(yes / no (lists available add-ons))${COLORS.reset}`);
|
|
136
|
+
const a = answer.toLowerCase();
|
|
137
|
+
if (a === 'yes' || a === 'y') return suggestion;
|
|
138
|
+
if (a === 'no' || a === 'n') break;
|
|
139
|
+
log(` ${COLORS.yellow}⚠${COLORS.reset} ${COLORS.dim}Please answer yes or no${COLORS.reset}`);
|
|
140
|
+
}
|
|
168
141
|
}
|
|
169
|
-
}
|
|
170
142
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
143
|
+
log();
|
|
144
|
+
log(` ${COLORS.bold}Available add-ons:${COLORS.reset}`, 'blue');
|
|
145
|
+
listAddons(addons);
|
|
146
|
+
log();
|
|
147
|
+
return null;
|
|
148
|
+
};
|
|
175
149
|
|
|
150
|
+
let pending = firstInput;
|
|
176
151
|
while (true) {
|
|
177
|
-
const
|
|
152
|
+
const input = pending || await promptUser(` Add-on to install ${COLORS.dim}(name or number)${COLORS.reset}`);
|
|
153
|
+
pending = null;
|
|
178
154
|
|
|
179
|
-
if (!
|
|
180
|
-
log(`
|
|
181
|
-
|
|
155
|
+
if (!input) {
|
|
156
|
+
log(` ${COLORS.yellow}⚠${COLORS.reset} ${COLORS.dim}Selection cannot be empty${COLORS.reset}`);
|
|
157
|
+
log();
|
|
182
158
|
continue;
|
|
183
159
|
}
|
|
184
160
|
|
|
185
|
-
const numInput = parseInt(
|
|
161
|
+
const numInput = parseInt(input, 10);
|
|
186
162
|
if (!isNaN(numInput) && numInput >= 1 && numInput <= addons.length) return addons[numInput - 1];
|
|
187
|
-
if (addons.includes(
|
|
163
|
+
if (addons.includes(input)) return input;
|
|
188
164
|
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
if (suggestion2) log(` ${COLORS.dim}Did you mean ${COLORS.reset}${COLORS.cyan}${suggestion2}${COLORS.reset}${COLORS.dim}?${COLORS.reset}`);
|
|
192
|
-
console.log();
|
|
165
|
+
const resolved = await askSuggestion(input);
|
|
166
|
+
if (resolved) return resolved;
|
|
193
167
|
}
|
|
194
168
|
};
|
|
195
169
|
|
|
196
|
-
const listAddons = (addons) => addons.forEach((name, i) => log(`
|
|
197
|
-
|
|
198
|
-
// --- Help ---
|
|
170
|
+
const listAddons = (addons) => addons.forEach((name, i) => log(` ${COLORS.cyan}${i + 1}.${COLORS.reset} ${name}`));
|
|
199
171
|
|
|
200
172
|
const showHelp = () => {
|
|
201
|
-
|
|
202
|
-
log(`
|
|
203
|
-
log(`
|
|
204
|
-
log(`
|
|
205
|
-
|
|
206
|
-
log(
|
|
207
|
-
log(`
|
|
208
|
-
log(`
|
|
209
|
-
log(`
|
|
210
|
-
|
|
211
|
-
log(
|
|
212
|
-
log(`
|
|
213
|
-
log(`
|
|
214
|
-
log(`
|
|
215
|
-
|
|
216
|
-
log(` ${COLORS.bold}Note:${COLORS.reset}`, 'blue');
|
|
217
|
-
log(` Run this command from the root of your Simpl project.`);
|
|
218
|
-
log(` The add-on version will match your Simpl framework version.`);
|
|
219
|
-
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();
|
|
220
188
|
};
|
|
221
189
|
|
|
222
190
|
const checkServerAvailability = () => new Promise(resolve => {
|
|
223
|
-
|
|
191
|
+
https.get(`${CDN_BASE}/versions.json`, {timeout: 5000}, res => {
|
|
224
192
|
res.resume();
|
|
225
193
|
resolve(res.statusCode === 200);
|
|
226
|
-
});
|
|
227
|
-
req.on('error', () => resolve(false));
|
|
228
|
-
req.on('timeout', () => {
|
|
229
|
-
req.destroy();
|
|
230
|
-
resolve(false);
|
|
231
|
-
});
|
|
194
|
+
}).on('error', () => resolve(false)).on('timeout', () => resolve(false));
|
|
232
195
|
});
|
|
233
196
|
|
|
234
197
|
const getVersionsData = async () => {
|
|
@@ -250,11 +213,10 @@ const getAvailableAddons = async (version) => {
|
|
|
250
213
|
|
|
251
214
|
const extractMarkers = (content) => {
|
|
252
215
|
const markers = [];
|
|
253
|
-
|
|
254
216
|
content.split('\n').forEach((line, i) => {
|
|
255
|
-
const afterMatch = line.match(/@addon-insert:after\s*\(\s*(["'])(
|
|
256
|
-
const beforeMatch = line.match(/@addon-insert:before\s*\(\s*(["'])(
|
|
257
|
-
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*\)/);
|
|
258
220
|
|
|
259
221
|
if (afterMatch) markers.push({type: 'after', lineIndex: i, searchText: afterMatch[2]});
|
|
260
222
|
else if (beforeMatch) markers.push({type: 'before', lineIndex: i, searchText: beforeMatch[2]});
|
|
@@ -262,18 +224,15 @@ const extractMarkers = (content) => {
|
|
|
262
224
|
else if (line.includes('@addon-insert:prepend')) markers.push({type: 'prepend', lineIndex: i});
|
|
263
225
|
else if (line.includes('@addon-insert:append')) markers.push({type: 'append', lineIndex: i});
|
|
264
226
|
});
|
|
265
|
-
|
|
266
227
|
return markers;
|
|
267
228
|
};
|
|
268
229
|
|
|
269
230
|
const collectContentBetweenMarkers = (lines, startIndex) => {
|
|
270
231
|
const content = [];
|
|
271
|
-
|
|
272
232
|
for (let i = startIndex + 1; i < lines.length; i++) {
|
|
273
233
|
if (lines[i].trim().includes('@addon-end')) break;
|
|
274
234
|
content.push(lines[i]);
|
|
275
235
|
}
|
|
276
|
-
|
|
277
236
|
return content;
|
|
278
237
|
};
|
|
279
238
|
|
|
@@ -281,10 +240,8 @@ const normalizeContent = (lines) => lines.map(l => l.trim()).filter(l => l && !l
|
|
|
281
240
|
|
|
282
241
|
const processEnvContent = (content, targetContent) => {
|
|
283
242
|
const envVarsToAdd = [], comments = [];
|
|
284
|
-
|
|
285
243
|
content.forEach(line => {
|
|
286
244
|
const trimmed = line.trim();
|
|
287
|
-
|
|
288
245
|
if (trimmed.startsWith('#') || !trimmed) {
|
|
289
246
|
comments.push(line);
|
|
290
247
|
return;
|
|
@@ -293,7 +250,6 @@ const processEnvContent = (content, targetContent) => {
|
|
|
293
250
|
const match = line.match(/^([A-Z_][A-Z0-9_]*)=/);
|
|
294
251
|
if (match && !new RegExp(`^${match[1]}=`, 'm').test(targetContent)) envVarsToAdd.push(line);
|
|
295
252
|
});
|
|
296
|
-
|
|
297
253
|
return {content: [...comments, ...envVarsToAdd], count: envVarsToAdd.length};
|
|
298
254
|
};
|
|
299
255
|
|
|
@@ -368,12 +324,11 @@ const mergeFile = (targetPath, addonContent, markers, isEnv = false) => {
|
|
|
368
324
|
});
|
|
369
325
|
|
|
370
326
|
if (newContent !== targetContent) fs.writeFileSync(targetPath, newContent, 'utf8');
|
|
371
|
-
|
|
372
327
|
return {modified: newContent !== targetContent, operations};
|
|
373
328
|
};
|
|
374
329
|
|
|
375
330
|
const printMergeResults = (relativePath, isEnv, result) => {
|
|
376
|
-
const indent = '
|
|
331
|
+
const indent = ' ';
|
|
377
332
|
const varText = isEnv ? 'environment variable' : 'line';
|
|
378
333
|
let hasChanges = false;
|
|
379
334
|
|
|
@@ -396,18 +351,15 @@ const printMergeResults = (relativePath, isEnv, result) => {
|
|
|
396
351
|
|
|
397
352
|
const extractZip = async (zipPath, destDir) => {
|
|
398
353
|
fs.mkdirSync(destDir, {recursive: true});
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
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);
|
|
402
356
|
|
|
403
357
|
const entries = fs.readdirSync(destDir, {withFileTypes: true});
|
|
404
|
-
|
|
405
358
|
if (entries.length === 1 && entries[0].isDirectory()) {
|
|
406
359
|
const nestedDir = path.join(destDir, entries[0].name);
|
|
407
360
|
fs.readdirSync(nestedDir).forEach(item => fs.renameSync(path.join(nestedDir, item), path.join(destDir, item)));
|
|
408
361
|
fs.rmdirSync(nestedDir);
|
|
409
362
|
}
|
|
410
|
-
|
|
411
363
|
return destDir;
|
|
412
364
|
};
|
|
413
365
|
|
|
@@ -439,7 +391,6 @@ const processAddonFiles = (addonDir, targetDir) => {
|
|
|
439
391
|
});
|
|
440
392
|
|
|
441
393
|
processDirectory(addonDir);
|
|
442
|
-
|
|
443
394
|
return {copied, skipped, toMerge};
|
|
444
395
|
};
|
|
445
396
|
|
|
@@ -449,8 +400,8 @@ const downloadAddon = async (addonName, version, targetDir) => {
|
|
|
449
400
|
|
|
450
401
|
try {
|
|
451
402
|
if (fs.existsSync(localZipPath)) {
|
|
452
|
-
|
|
453
|
-
log(`
|
|
403
|
+
log();
|
|
404
|
+
log(` 💻 Using local add-on files`, 'bold');
|
|
454
405
|
const sourceDir = await extractZip(localZipPath, tempExtract);
|
|
455
406
|
const result = processAddonFiles(sourceDir, targetDir);
|
|
456
407
|
fs.rmSync(tempExtract, {recursive: true, force: true});
|
|
@@ -479,14 +430,14 @@ const mergeFiles = (toMerge) => {
|
|
|
479
430
|
|
|
480
431
|
toMerge.forEach(({content, destPath, relativePath, markers}) => {
|
|
481
432
|
const isEnv = path.basename(destPath) === '.env';
|
|
482
|
-
log(`\n
|
|
433
|
+
log(`\n ${COLORS.cyan}•${COLORS.reset} ${COLORS.dim}${relativePath}${COLORS.reset}`);
|
|
483
434
|
|
|
484
435
|
try {
|
|
485
436
|
const result = mergeFile(destPath, content, markers, isEnv);
|
|
486
437
|
if (printMergeResults(relativePath, isEnv, result)) merged.push(relativePath);
|
|
487
438
|
else unchanged.push(relativePath);
|
|
488
439
|
} catch (error) {
|
|
489
|
-
log(`
|
|
440
|
+
log(` ${COLORS.red}✗ Error:${COLORS.reset} ${error.message}`);
|
|
490
441
|
failed.push(relativePath);
|
|
491
442
|
}
|
|
492
443
|
});
|
|
@@ -507,14 +458,14 @@ const main = async () => {
|
|
|
507
458
|
for (const flag of parsed.unknownFlags) {
|
|
508
459
|
const flagName = flag.includes('=') ? flag.slice(0, flag.indexOf('=')) : flag;
|
|
509
460
|
const suggestion = closestMatch(flagName, KNOWN_FLAGS);
|
|
510
|
-
|
|
511
|
-
log(`
|
|
512
|
-
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}`);
|
|
513
464
|
}
|
|
514
465
|
|
|
515
466
|
if (parsed.unknownFlags.length > 0) {
|
|
516
|
-
log(`
|
|
517
|
-
|
|
467
|
+
log(` ${COLORS.dim}Run with --help to see all available options.${COLORS.reset}`);
|
|
468
|
+
log();
|
|
518
469
|
process.exit(1);
|
|
519
470
|
}
|
|
520
471
|
|
|
@@ -523,59 +474,57 @@ const main = async () => {
|
|
|
523
474
|
try {
|
|
524
475
|
version = getSimplVersion();
|
|
525
476
|
} catch (error) {
|
|
526
|
-
|
|
527
|
-
log(`
|
|
528
|
-
|
|
477
|
+
log();
|
|
478
|
+
log(` ${COLORS.red}✗${COLORS.reset} ${error.message}`);
|
|
479
|
+
log();
|
|
529
480
|
process.exit(1);
|
|
530
481
|
}
|
|
531
482
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
log(` │ ${COLORS.bold}Simpl Add-on Installer${COLORS.reset} ${COLORS.dim}(v${version})${COLORS.reset}${' '.repeat(34 - version.length)}│`);
|
|
535
|
-
log(` ╰${'─'.repeat(62)}╯`);
|
|
483
|
+
log();
|
|
484
|
+
box(`Simpl Add-on Installer${COLORS.dim}(v${version})${COLORS.reset}`);
|
|
536
485
|
|
|
537
486
|
let versionsData;
|
|
538
487
|
|
|
539
488
|
try {
|
|
540
489
|
versionsData = await getVersionsData();
|
|
541
490
|
} catch (error) {
|
|
542
|
-
|
|
543
|
-
log(`
|
|
544
|
-
if (error.message === 'CDN server is currently unreachable') log(`
|
|
545
|
-
|
|
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();
|
|
546
495
|
process.exit(1);
|
|
547
496
|
}
|
|
548
497
|
|
|
549
498
|
const versionMeta = versionsData.versions[version];
|
|
550
499
|
if (!versionMeta) {
|
|
551
|
-
|
|
552
|
-
log(`
|
|
553
|
-
|
|
500
|
+
log();
|
|
501
|
+
log(` ${COLORS.red}✗${COLORS.reset} Version ${COLORS.bold}${version}${COLORS.reset} not found`);
|
|
502
|
+
log();
|
|
554
503
|
process.exit(1);
|
|
555
504
|
}
|
|
556
505
|
|
|
557
506
|
if (versionMeta['script-compatible'] === false) {
|
|
558
|
-
|
|
559
|
-
log(`
|
|
560
|
-
|
|
561
|
-
log(`
|
|
562
|
-
log(`
|
|
563
|
-
|
|
564
|
-
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');
|
|
565
514
|
|
|
566
515
|
const addons = versionMeta['add-ons'] || [];
|
|
567
|
-
if (addons.length === 0) log(`
|
|
516
|
+
if (addons.length === 0) log(` ${COLORS.dim}No add-ons available${COLORS.reset}`);
|
|
568
517
|
else addons.forEach(name => {
|
|
569
|
-
log(`
|
|
518
|
+
log(` ${COLORS.cyan}•${COLORS.reset} ${name}: ${COLORS.dim}${CDN_BASE}/${version}/add-ons/${name}.zip${COLORS.reset}`);
|
|
570
519
|
});
|
|
571
520
|
|
|
572
|
-
|
|
521
|
+
log();
|
|
573
522
|
process.exit(1);
|
|
574
523
|
}
|
|
575
524
|
|
|
576
525
|
if (!parsed.addon) {
|
|
577
|
-
|
|
578
|
-
log('
|
|
526
|
+
log();
|
|
527
|
+
log(' 🗄️ Fetching available add-ons...', 'bold');
|
|
579
528
|
}
|
|
580
529
|
|
|
581
530
|
let addons;
|
|
@@ -583,123 +532,98 @@ const main = async () => {
|
|
|
583
532
|
try {
|
|
584
533
|
addons = await getAvailableAddons(version);
|
|
585
534
|
} catch (error) {
|
|
586
|
-
|
|
587
|
-
log(`
|
|
588
|
-
if (error.message === 'CDN server is currently unreachable') log(`
|
|
589
|
-
|
|
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();
|
|
590
539
|
process.exit(1);
|
|
591
540
|
}
|
|
592
541
|
|
|
593
542
|
if (addons.length === 0) {
|
|
594
|
-
|
|
595
|
-
log(`
|
|
596
|
-
|
|
543
|
+
log();
|
|
544
|
+
log(` ${COLORS.yellow}⚠${COLORS.reset} No add-ons available for this version`);
|
|
545
|
+
log();
|
|
597
546
|
process.exit(0);
|
|
598
547
|
}
|
|
599
548
|
|
|
600
549
|
if (parsed.list) {
|
|
601
|
-
|
|
602
|
-
log(`
|
|
550
|
+
log();
|
|
551
|
+
log(` ${COLORS.bold}Available add-ons:${COLORS.reset}`, 'blue');
|
|
603
552
|
listAddons(addons);
|
|
604
|
-
|
|
553
|
+
log();
|
|
605
554
|
process.exit(0);
|
|
606
555
|
}
|
|
607
556
|
|
|
608
557
|
let addonName;
|
|
609
558
|
|
|
610
559
|
if (parsed.addon) {
|
|
611
|
-
addonName = await
|
|
612
|
-
printAnswer('
|
|
560
|
+
addonName = await promptAddon(addons, parsed.addon);
|
|
561
|
+
printAnswer(' Add-on to install', addonName);
|
|
613
562
|
} else {
|
|
614
|
-
|
|
615
|
-
log(`
|
|
563
|
+
log();
|
|
564
|
+
log(` ${COLORS.bold}Available add-ons:${COLORS.reset}`, 'blue');
|
|
616
565
|
listAddons(addons);
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
while (true) {
|
|
620
|
-
const input = await promptUser(` Add-on to install ${COLORS.dim}(name or number)${COLORS.reset}`);
|
|
566
|
+
log();
|
|
621
567
|
|
|
622
|
-
|
|
623
|
-
log(` ${COLORS.red}✗${COLORS.reset} Selection cannot be empty`, 'red');
|
|
624
|
-
console.log();
|
|
625
|
-
continue;
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
const numInput = parseInt(input, 10);
|
|
629
|
-
if (!isNaN(numInput) && numInput >= 1 && numInput <= addons.length) {
|
|
630
|
-
addonName = addons[numInput - 1];
|
|
631
|
-
break;
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
if (addons.includes(input)) {
|
|
635
|
-
addonName = input;
|
|
636
|
-
break;
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
const suggestion = closestMatch(input, addons);
|
|
640
|
-
log(` ${COLORS.red}✗${COLORS.reset} Invalid selection ${COLORS.bold}${input}${COLORS.reset}`, 'red');
|
|
641
|
-
if (suggestion) log(` ${COLORS.dim}Did you mean ${COLORS.reset}${COLORS.cyan}${suggestion}${COLORS.reset}${COLORS.dim}?${COLORS.reset}`);
|
|
642
|
-
console.log();
|
|
643
|
-
}
|
|
568
|
+
addonName = await promptAddon(addons);
|
|
644
569
|
}
|
|
645
570
|
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
log(`
|
|
649
|
-
log(` ╰${'─'.repeat(62)}╯`);
|
|
650
|
-
console.log();
|
|
651
|
-
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');
|
|
652
574
|
|
|
653
575
|
let copied, skipped, toMerge;
|
|
654
576
|
|
|
655
577
|
try {
|
|
656
578
|
({copied, skipped, toMerge} = await downloadAddon(addonName, version, process.cwd()));
|
|
657
579
|
} catch (error) {
|
|
658
|
-
|
|
659
|
-
log(`
|
|
660
|
-
if (error.message === 'CDN server is currently unreachable') log(`
|
|
661
|
-
else log(`
|
|
662
|
-
|
|
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();
|
|
663
585
|
process.exit(1);
|
|
664
586
|
}
|
|
665
587
|
|
|
666
588
|
if (copied.length > 0) {
|
|
667
|
-
|
|
668
|
-
log(`
|
|
589
|
+
log();
|
|
590
|
+
log(` ${COLORS.green}✓${COLORS.reset} Copied ${COLORS.bold}${copied.length}${COLORS.reset} new file${copied.length !== 1 ? 's' : ''}`);
|
|
669
591
|
}
|
|
670
592
|
|
|
671
593
|
if (skipped.length > 0) {
|
|
672
|
-
|
|
673
|
-
log(`
|
|
674
|
-
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}`));
|
|
675
597
|
}
|
|
676
598
|
|
|
677
599
|
if (toMerge.length > 0) {
|
|
678
|
-
|
|
679
|
-
log('
|
|
600
|
+
log();
|
|
601
|
+
log(' 🔀 Merging existing files...', 'bold');
|
|
680
602
|
const {merged, failed, unchanged} = mergeFiles(toMerge);
|
|
681
603
|
|
|
682
|
-
|
|
683
|
-
log('
|
|
684
|
-
|
|
604
|
+
log();
|
|
605
|
+
log(' ' + '─'.repeat(16), 'gray');
|
|
606
|
+
log();
|
|
685
607
|
|
|
686
|
-
if (merged.length > 0) log(`
|
|
687
|
-
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}`);
|
|
688
610
|
|
|
689
611
|
if (failed.length > 0) {
|
|
690
|
-
|
|
691
|
-
log(`
|
|
692
|
-
log(`
|
|
693
|
-
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}`));
|
|
694
616
|
}
|
|
695
617
|
}
|
|
696
618
|
|
|
697
|
-
|
|
698
|
-
log(`
|
|
699
|
-
|
|
619
|
+
log();
|
|
620
|
+
log(` ${COLORS.green}✓${COLORS.reset} ${COLORS.bold}${COLORS.green}Installation complete!${COLORS.reset}`, 'green');
|
|
621
|
+
log();
|
|
700
622
|
};
|
|
701
623
|
|
|
702
624
|
main().catch(() => {
|
|
703
|
-
log(
|
|
625
|
+
log();
|
|
626
|
+
log(` ${COLORS.red}✗${COLORS.reset} Fatal error occurred`);
|
|
627
|
+
log();
|
|
704
628
|
process.exit(1);
|
|
705
629
|
});
|