@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.
Files changed (2) hide show
  1. package/install.js +152 -228
  2. 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
- result.help = true;
94
- continue;
95
- }
96
- if (arg === '--list' || arg === '-l') {
97
- result.list = true;
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
- // --- Fuzzy addon resolution with interactive prompts ---
149
-
150
- const resolveAddon = async (input, addons) => {
151
- if (addons.includes(input)) return input;
152
-
153
- const suggestion = closestMatch(input, addons);
154
- console.log();
155
- log(` ${COLORS.red}✗${COLORS.reset} Add-on ${COLORS.bold}${input}${COLORS.reset} not found`, 'red');
156
-
157
- if (suggestion) {
158
- log(` ${COLORS.yellow}Did you mean:${COLORS.reset} ${COLORS.cyan}${suggestion}${COLORS.reset}?`);
159
- console.log();
160
-
161
- const answer = await promptUser(` Use "${suggestion}"? ${COLORS.dim}(y/n n shows available add-ons)${COLORS.reset}`);
162
- const a = answer.toLowerCase();
163
-
164
- if (a === 'yes' || a === 'y') return suggestion;
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
- console.log();
172
- log(` ${COLORS.bold}Available add-ons:${COLORS.reset}`, 'blue');
173
- listAddons(addons);
174
- console.log();
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 input2 = await promptUser(` Add-on to install ${COLORS.dim}(name or number)${COLORS.reset}`);
152
+ const input = pending || await promptUser(` Add-on to install ${COLORS.dim}(name or number)${COLORS.reset}`);
153
+ pending = null;
178
154
 
179
- if (!input2) {
180
- log(` ${COLORS.red}✗${COLORS.reset} Selection cannot be empty`, 'red');
181
- console.log();
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(input2, 10);
161
+ const numInput = parseInt(input, 10);
186
162
  if (!isNaN(numInput) && numInput >= 1 && numInput <= addons.length) return addons[numInput - 1];
187
- if (addons.includes(input2)) return input2;
163
+ if (addons.includes(input)) return input;
188
164
 
189
- const suggestion2 = closestMatch(input2, addons);
190
- log(` ${COLORS.red}✗${COLORS.reset} Invalid selection ${COLORS.bold}${input2}${COLORS.reset}`, 'red');
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(` ${COLORS.cyan}${i + 1}.${COLORS.reset} ${name}`));
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
- console.log();
202
- log(` ╭${''.repeat(62)}╮`);
203
- log(`${COLORS.bold}Simpl Add-on Installer${COLORS.reset}${' '.repeat(38)}│`);
204
- log(` ╰${'─'.repeat(62)}╯`);
205
- console.log();
206
- log(` ${COLORS.bold}Usage:${COLORS.reset}`, 'blue');
207
- log(` ${COLORS.dim}npx @ijuantm/simpl-addon${COLORS.reset}`);
208
- log(` ${COLORS.dim}npx @ijuantm/simpl-addon --addon=<name>${COLORS.reset}`);
209
- log(` ${COLORS.dim}npx @ijuantm/simpl-addon --help${COLORS.reset}`);
210
- console.log();
211
- log(` ${COLORS.bold}Options:${COLORS.reset}`, 'blue');
212
- log(` ${COLORS.dim}--addon=<name>, -a=<name>${COLORS.reset} Add-on to install`);
213
- log(` ${COLORS.dim}--list, -l${COLORS.reset} List available add-ons`);
214
- log(` ${COLORS.dim}--help, -h${COLORS.reset} Show this help message`);
215
- console.log();
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
- const req = https.get(`${CDN_BASE}/versions.json`, {timeout: 5000}, res => {
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*(["'])(.+?)\1\s*\)/);
256
- const beforeMatch = line.match(/@addon-insert:before\s*\(\s*(["'])(.+?)\1\s*\)/);
257
- const replaceMatch = line.match(/@addon-insert:replace\s*\(\s*(["'])(.+?)\1\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
- if (process.platform === 'win32') await execAsync(`powershell -command "Expand-Archive -Path '${zipPath}' -DestinationPath '${destDir}' -Force"`);
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
- console.log();
453
- log(` 💻 Using local add-on files`, 'bold');
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 ${COLORS.cyan}•${COLORS.reset} ${COLORS.dim}${relativePath}${COLORS.reset}`);
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(` ${COLORS.red}✗ Error:${COLORS.reset} ${error.message}`, 'red');
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
- console.log();
511
- log(` ${COLORS.yellow}⚠${COLORS.reset} Unknown option: ${COLORS.bold}${flag}${COLORS.reset}`, 'yellow');
512
- if (suggestion) log(` ${COLORS.dim}Did you mean ${COLORS.reset}${COLORS.cyan}${suggestion}${COLORS.reset}${COLORS.dim}?${COLORS.reset}`);
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(` ${COLORS.dim}Run with --help to see all available options.${COLORS.reset}`);
517
- console.log();
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
- console.log();
527
- log(` ${COLORS.red}✗${COLORS.reset} ${error.message}`, 'red');
528
- console.log();
477
+ log();
478
+ log(` ${COLORS.red}✗${COLORS.reset} ${error.message}`);
479
+ log();
529
480
  process.exit(1);
530
481
  }
531
482
 
532
- console.log();
533
- log(` ╭${'─'.repeat(62)}╮`);
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
- console.log();
543
- log(` ${COLORS.red}✗${COLORS.reset} Failed to fetch version data`, 'red');
544
- if (error.message === 'CDN server is currently unreachable') log(` ${COLORS.dim}The CDN server is currently unavailable. Please try again later.${COLORS.reset}`);
545
- console.log();
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
- console.log();
552
- log(` ${COLORS.red}✗${COLORS.reset} Version ${COLORS.bold}${version}${COLORS.reset} not found`, 'red');
553
- console.log();
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
- console.log();
559
- log(` ${COLORS.red}✗${COLORS.reset} Version ${COLORS.bold}${version}${COLORS.reset} is not compatible with this installer`, 'red');
560
- console.log();
561
- log(` ${COLORS.bold}Manual download:${COLORS.reset}`, 'blue');
562
- log(` ${COLORS.cyan}${CDN_BASE}/${version}/add-ons/`, 'cyan');
563
- console.log();
564
- log(` ${COLORS.bold}Available add-ons for this version:${COLORS.reset}`, 'blue');
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(` ${COLORS.dim}No add-ons available${COLORS.reset}`);
516
+ if (addons.length === 0) log(` ${COLORS.dim}No add-ons available${COLORS.reset}`);
568
517
  else addons.forEach(name => {
569
- log(` ${COLORS.cyan}•${COLORS.reset} ${name}: ${COLORS.dim}${CDN_BASE}/${version}/add-ons/${name}.zip${COLORS.reset}`);
518
+ log(` ${COLORS.cyan}•${COLORS.reset} ${name}: ${COLORS.dim}${CDN_BASE}/${version}/add-ons/${name}.zip${COLORS.reset}`);
570
519
  });
571
520
 
572
- console.log();
521
+ log();
573
522
  process.exit(1);
574
523
  }
575
524
 
576
525
  if (!parsed.addon) {
577
- console.log();
578
- log(' 🗄️ Fetching available add-ons...', 'bold');
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
- console.log();
587
- log(` ${COLORS.red}✗${COLORS.reset} Failed to fetch add-ons`, 'red');
588
- if (error.message === 'CDN server is currently unreachable') log(` ${COLORS.dim}The CDN server is currently unavailable. Please try again later.${COLORS.reset}`);
589
- console.log();
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
- console.log();
595
- log(` ${COLORS.yellow}⚠${COLORS.reset} No add-ons available for this version`);
596
- console.log();
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
- console.log();
602
- log(` ${COLORS.bold}Available add-ons:${COLORS.reset}`, 'blue');
550
+ log();
551
+ log(` ${COLORS.bold}Available add-ons:${COLORS.reset}`, 'blue');
603
552
  listAddons(addons);
604
- console.log();
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 resolveAddon(parsed.addon, addons);
612
- printAnswer(' Add-on to install', addonName);
560
+ addonName = await promptAddon(addons, parsed.addon);
561
+ printAnswer(' Add-on to install', addonName);
613
562
  } else {
614
- console.log();
615
- log(` ${COLORS.bold}Available add-ons:${COLORS.reset}`, 'blue');
563
+ log();
564
+ log(` ${COLORS.bold}Available add-ons:${COLORS.reset}`, 'blue');
616
565
  listAddons(addons);
617
- console.log();
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
- if (!input) {
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
- console.log();
647
- log(` ╭${'─'.repeat(62)}╮`);
648
- log(` │ ${COLORS.bold}Installing: ${COLORS.cyan}${addonName}${COLORS.reset} ${COLORS.dim}(v${version})${COLORS.reset}${' '.repeat(44 - addonName.length - version.length)}│`);
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
- console.log();
659
- log(` ${COLORS.red}✗${COLORS.reset} Installation failed`, 'red');
660
- if (error.message === 'CDN server is currently unreachable') log(` ${COLORS.dim}The CDN server is currently unavailable. Please try again later.${COLORS.reset}`);
661
- else log(` ${COLORS.dim}Please verify the add-on exists and try again${COLORS.reset}`);
662
- console.log();
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
- console.log();
668
- log(` ${COLORS.green}✓${COLORS.reset} Copied ${COLORS.bold}${copied.length}${COLORS.reset} new file${copied.length !== 1 ? 's' : ''}`);
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
- console.log();
673
- log(` ${COLORS.gray}○${COLORS.reset} ${COLORS.dim}Skipped ${skipped.length} file${skipped.length !== 1 ? 's' : ''} (no merge markers):${COLORS.reset}`);
674
- skipped.forEach(file => log(` ${COLORS.dim}• ${file}${COLORS.reset}`));
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
- console.log();
679
- log(' 🔀 Merging existing files...', 'bold');
600
+ log();
601
+ log(' 🔀 Merging existing files...', 'bold');
680
602
  const {merged, failed, unchanged} = mergeFiles(toMerge);
681
603
 
682
- console.log();
683
- log(' ' + '─'.repeat(16), 'gray');
684
- console.log();
604
+ log();
605
+ log(' ' + '─'.repeat(16), 'gray');
606
+ log();
685
607
 
686
- if (merged.length > 0) log(` ${COLORS.green}✓${COLORS.reset} Successfully merged ${COLORS.bold}${merged.length}${COLORS.reset} file${merged.length !== 1 ? 's' : ''}`);
687
- if (unchanged.length > 0) log(` ${COLORS.gray}○${COLORS.reset} ${COLORS.dim}${unchanged.length} file${unchanged.length !== 1 ? 's' : ''} unchanged (content already exists)${COLORS.reset}`);
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
- console.log();
691
- log(` ${COLORS.yellow}⚠${COLORS.reset} ${COLORS.yellow}${failed.length} file${failed.length !== 1 ? 's' : ''} failed to merge${COLORS.reset}`);
692
- log(` ${COLORS.yellow}Please review manually:${COLORS.reset}`);
693
- failed.forEach(file => log(` ${COLORS.cyan}• ${file}${COLORS.reset}`));
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
- console.log();
698
- log(` ${COLORS.green}✓${COLORS.reset} ${COLORS.bold}${COLORS.green}Installation complete!${COLORS.reset}`, 'green');
699
- console.log();
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(`\n ${COLORS.red}✗${COLORS.reset} Fatal error occurred\n`, 'red');
625
+ log();
626
+ log(` ${COLORS.red}✗${COLORS.reset} Fatal error occurred`);
627
+ log();
704
628
  process.exit(1);
705
629
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ijuantm/simpl-addon",
3
3
  "description": "CLI tool to install Simpl framework add-ons.",
4
- "version": "2.6.2",
4
+ "version": "2.6.4",
5
5
  "scripts": {
6
6
  "link": "npm link",
7
7
  "unlink": "npm unlink -g @ijuantm/simpl-addon"