@ijuantm/simpl-addon 2.4.10 → 2.6.0

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 (3) hide show
  1. package/README.md +1 -0
  2. package/install.js +141 -30
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -44,6 +44,7 @@ new AuthController();
44
44
 
45
45
  - `@addon-insert:after('text')` - Insert content after matching line
46
46
  - `@addon-insert:before('text')` - Insert content before matching line
47
+ - `@addon-insert:replace('text')` - Replace matching line with content
47
48
  - `@addon-insert:prepend` - Add content at the beginning of the file
48
49
  - `@addon-insert:append` - Add content at the end of the file
49
50
 
package/install.js CHANGED
@@ -81,6 +81,107 @@ const getSimplVersion = () => {
81
81
  return config.version;
82
82
  };
83
83
 
84
+ // --- Argument parsing ---
85
+
86
+ const parseArgs = (args) => {
87
+ const result = {addon: null, unknownFlags: [], help: false, list: false};
88
+
89
+ for (const arg of args) {
90
+ if (arg === '--help' || arg === '-h') {
91
+ result.help = true;
92
+ continue;
93
+ }
94
+ if (arg === '--list' || arg === '-l') {
95
+ result.list = true;
96
+ continue;
97
+ }
98
+
99
+ if (arg.startsWith('--addon=')) {
100
+ result.addon = arg.slice(8).trim() || null;
101
+ continue;
102
+ }
103
+ if (arg.startsWith('-a=')) {
104
+ result.addon = arg.slice(3).trim() || null;
105
+ continue;
106
+ }
107
+
108
+ if (arg.startsWith('-') && !arg.startsWith('--addon') && !arg.startsWith('-a')) {
109
+ result.unknownFlags.push(arg);
110
+ continue;
111
+ }
112
+
113
+ if (!arg.startsWith('-') && !result.addon) result.addon = arg;
114
+ }
115
+
116
+ return result;
117
+ };
118
+
119
+ const KNOWN_FLAGS = ['--addon', '-a', '--help', '-h', '--list', '-l'];
120
+
121
+ const levenshtein = (a, b) => {
122
+ const m = a.length, n = b.length;
123
+ const dp = Array.from({length: m + 1}, (_, i) => Array.from({length: n + 1}, (_, j) => i === 0 ? j : j === 0 ? i : 0));
124
+
125
+ for (let i = 1; i <= m; i++)
126
+ for (let j = 1; j <= n; j++)
127
+ 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]);
128
+
129
+ return dp[m][n];
130
+ };
131
+
132
+ const closestMatch = (input, options) => {
133
+ let best = null, bestDist = Infinity;
134
+
135
+ for (const opt of options) {
136
+ const dist = levenshtein(input.toLowerCase(), opt.toLowerCase());
137
+ if (dist < bestDist) {
138
+ bestDist = dist;
139
+ best = opt;
140
+ }
141
+ }
142
+
143
+ return bestDist <= Math.max(3, Math.floor(input.length / 2)) ? best : null;
144
+ };
145
+
146
+ // --- Fuzzy addon resolution with interactive prompts ---
147
+
148
+ const resolveAddon = async (input, addons) => {
149
+ if (addons.includes(input)) return input;
150
+
151
+ const suggestion = closestMatch(input, addons);
152
+ console.log();
153
+ log(` ${COLORS.red}✗${COLORS.reset} Add-on ${COLORS.bold}${input}${COLORS.reset} not found`, 'red');
154
+
155
+ if (suggestion) {
156
+ log(` ${COLORS.yellow}Did you mean:${COLORS.reset} ${COLORS.cyan}${suggestion}${COLORS.reset}?`);
157
+ console.log();
158
+
159
+ while (true) {
160
+ const answer = await promptUser(` Use "${suggestion}"? ${COLORS.dim}(yes / no / list)${COLORS.reset}`);
161
+ const a = answer.toLowerCase();
162
+
163
+ if (a === 'yes' || a === 'y') return suggestion;
164
+ if (a === 'list') {
165
+ listAddons(addons);
166
+ break;
167
+ }
168
+ if (a === 'no' || a === 'n') break;
169
+
170
+ log(` ${COLORS.dim}Please answer yes, no, or list${COLORS.reset}`);
171
+ }
172
+ }
173
+
174
+ console.log();
175
+ log(` ${COLORS.bold}Available add-ons:${COLORS.reset}`, 'blue');
176
+ listAddons(addons);
177
+ console.log();
178
+ process.exit(1);
179
+ };
180
+
181
+ const listAddons = (addons) => addons.forEach((name, i) => log(` ${COLORS.cyan}${i + 1}.${COLORS.reset} ${name}`));
182
+
183
+ // --- Help ---
184
+
84
185
  const showHelp = () => {
85
186
  console.log();
86
187
  log(` ╭${'─'.repeat(62)}╮`);
@@ -89,11 +190,13 @@ const showHelp = () => {
89
190
  console.log();
90
191
  log(` ${COLORS.bold}Usage:${COLORS.reset}`, 'blue');
91
192
  log(` ${COLORS.dim}npx @ijuantm/simpl-addon${COLORS.reset}`);
92
- log(` ${COLORS.dim}npx @ijuantm/simpl-addon <add-on>${COLORS.reset}`);
193
+ log(` ${COLORS.dim}npx @ijuantm/simpl-addon --addon=<name>${COLORS.reset}`);
93
194
  log(` ${COLORS.dim}npx @ijuantm/simpl-addon --help${COLORS.reset}`);
94
195
  console.log();
95
- log(` ${COLORS.bold}Commands:${COLORS.reset}`, 'blue');
96
- log(` ${COLORS.dim}--help, -h${COLORS.reset} Show this help message`);
196
+ log(` ${COLORS.bold}Options:${COLORS.reset}`, 'blue');
197
+ log(` ${COLORS.dim}--addon=<name>, -a=<name>${COLORS.reset} Add-on to install`);
198
+ log(` ${COLORS.dim}--list, -l${COLORS.reset} List available add-ons`);
199
+ log(` ${COLORS.dim}--help, -h${COLORS.reset} Show this help message`);
97
200
  console.log();
98
201
  log(` ${COLORS.bold}Note:${COLORS.reset}`, 'blue');
99
202
  log(` Run this command from the root of your Simpl project.`);
@@ -184,12 +287,6 @@ const findInsertIndex = (lines, searchText, type) => {
184
287
  return -1;
185
288
  };
186
289
 
187
- const findMarkerLine = (lines, markerName) => {
188
- const markerPattern = new RegExp(`@addon-marker\\s*\\(\\s*["']${markerName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}["']\\s*\\)`);
189
- for (let i = 0; i < lines.length; i++) if (markerPattern.test(lines[i])) return i;
190
- return -1;
191
- };
192
-
193
290
  const mergeFile = (targetPath, addonContent, markers, isEnv = false) => {
194
291
  const targetContent = fs.readFileSync(targetPath, 'utf8');
195
292
  const addonLines = addonContent.split('\n');
@@ -230,14 +327,14 @@ const mergeFile = (targetPath, addonContent, markers, isEnv = false) => {
230
327
  operations.push({success: true, type: 'append', lines: lineCount});
231
328
  } else if (marker.type === 'replace' && marker.markerName) {
232
329
  const targetLines = newContent.split('\n');
233
- const markerLine = findMarkerLine(targetLines, marker.markerName);
330
+ const replaceIndex = findInsertIndex(targetLines, marker.markerName, 'before');
234
331
 
235
- if (markerLine === -1) {
236
- operations.push({success: false, type: 'notfound', markerName: marker.markerName});
332
+ if (replaceIndex === -1) {
333
+ operations.push({success: false, type: 'notfound', searchText: marker.markerName});
237
334
  return;
238
335
  }
239
336
 
240
- targetLines.splice(markerLine, 1, ...content);
337
+ targetLines.splice(replaceIndex, 1, ...content);
241
338
  newContent = targetLines.join('\n');
242
339
  operations.push({success: true, type: 'replace', lines: lineCount, markerName: marker.markerName});
243
340
  } else if ((marker.type === 'after' || marker.type === 'before') && marker.searchText) {
@@ -384,14 +481,27 @@ const mergeFiles = (toMerge) => {
384
481
 
385
482
  const main = async () => {
386
483
  const args = process.argv.slice(2);
387
- const firstArg = args[0];
484
+ const parsed = parseArgs(args);
388
485
 
389
- if (firstArg === '--help' || firstArg === '-h') {
486
+ if (parsed.help) {
390
487
  showHelp();
391
488
  process.exit(0);
392
489
  }
393
490
 
394
- const directName = firstArg && !firstArg.startsWith('-') ? firstArg : null;
491
+ // Warn about unknown flags and suggest closest known ones
492
+ for (const flag of parsed.unknownFlags) {
493
+ const flagName = flag.includes('=') ? flag.slice(0, flag.indexOf('=')) : flag;
494
+ const suggestion = closestMatch(flagName, KNOWN_FLAGS);
495
+ console.log();
496
+ log(` ${COLORS.yellow}⚠${COLORS.reset} Unknown option: ${COLORS.bold}${flag}${COLORS.reset}`, 'yellow');
497
+ if (suggestion) log(` ${COLORS.dim}Did you mean ${COLORS.reset}${COLORS.cyan}${suggestion}${COLORS.reset}${COLORS.dim}?${COLORS.reset}`);
498
+ }
499
+
500
+ if (parsed.unknownFlags.length > 0) {
501
+ log(` ${COLORS.dim}Run with --help to see all available options.${COLORS.reset}`);
502
+ console.log();
503
+ process.exit(1);
504
+ }
395
505
 
396
506
  let version;
397
507
 
@@ -448,7 +558,7 @@ const main = async () => {
448
558
  process.exit(1);
449
559
  }
450
560
 
451
- if (!directName) {
561
+ if (!parsed.addon) {
452
562
  console.log();
453
563
  log(' 🗄️ Fetching available add-ons...', 'bold');
454
564
  }
@@ -472,23 +582,22 @@ const main = async () => {
472
582
  process.exit(0);
473
583
  }
474
584
 
585
+ if (parsed.list) {
586
+ console.log();
587
+ log(` ${COLORS.bold}Available add-ons:${COLORS.reset}`, 'blue');
588
+ listAddons(addons);
589
+ console.log();
590
+ process.exit(0);
591
+ }
592
+
475
593
  let addonName;
476
594
 
477
- if (directName) {
478
- if (!addons.includes(directName)) {
479
- console.log();
480
- log(` ${COLORS.red}✗${COLORS.reset} Add-on ${COLORS.bold}${directName}${COLORS.reset} not found`, 'red');
481
- console.log();
482
- log(` ${COLORS.bold}Available add-ons:${COLORS.reset}`, 'blue');
483
- addons.forEach((name, index) => log(` ${COLORS.cyan}${index + 1}.${COLORS.reset} ${name}`));
484
- console.log();
485
- process.exit(1);
486
- }
487
- addonName = directName;
595
+ if (parsed.addon) {
596
+ addonName = await resolveAddon(parsed.addon, addons);
488
597
  } else {
489
598
  console.log();
490
599
  log(` ${COLORS.bold}Available add-ons:${COLORS.reset}`, 'blue');
491
- addons.forEach((name, index) => log(` ${COLORS.cyan}${index + 1}.${COLORS.reset} ${name}`));
600
+ listAddons(addons);
492
601
  console.log();
493
602
 
494
603
  while (true) {
@@ -511,7 +620,9 @@ const main = async () => {
511
620
  break;
512
621
  }
513
622
 
514
- log(` ${COLORS.red}✗${COLORS.reset} Invalid selection "${input}"`, 'red');
623
+ const suggestion = closestMatch(input, addons);
624
+ log(` ${COLORS.red}✗${COLORS.reset} Invalid selection ${COLORS.bold}${input}${COLORS.reset}`, 'red');
625
+ if (suggestion) log(` ${COLORS.dim}Did you mean ${COLORS.reset}${COLORS.cyan}${suggestion}${COLORS.reset}${COLORS.dim}?${COLORS.reset}`);
515
626
  console.log();
516
627
  }
517
628
  }
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.4.10",
4
+ "version": "2.6.0",
5
5
  "scripts": {
6
6
  "link": "npm link",
7
7
  "unlink": "npm unlink -g @ijuantm/simpl-addon"