@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.
- package/README.md +1 -0
- package/install.js +141 -30
- 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
|
|
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}
|
|
96
|
-
log(` ${COLORS.dim}--
|
|
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
|
|
330
|
+
const replaceIndex = findInsertIndex(targetLines, marker.markerName, 'before');
|
|
234
331
|
|
|
235
|
-
if (
|
|
236
|
-
operations.push({success: false, type: 'notfound',
|
|
332
|
+
if (replaceIndex === -1) {
|
|
333
|
+
operations.push({success: false, type: 'notfound', searchText: marker.markerName});
|
|
237
334
|
return;
|
|
238
335
|
}
|
|
239
336
|
|
|
240
|
-
targetLines.splice(
|
|
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
|
|
484
|
+
const parsed = parseArgs(args);
|
|
388
485
|
|
|
389
|
-
if (
|
|
486
|
+
if (parsed.help) {
|
|
390
487
|
showHelp();
|
|
391
488
|
process.exit(0);
|
|
392
489
|
}
|
|
393
490
|
|
|
394
|
-
|
|
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 (!
|
|
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 (
|
|
478
|
-
|
|
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
|
|
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
|
-
|
|
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
|
}
|