fuzzrunx 0.1.3 → 0.1.5
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/package.json +1 -1
- package/src/cli.js +117 -13
- package/src/installer.js +68 -8
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -211,6 +211,22 @@ function normalizeToken(value) {
|
|
|
211
211
|
return String(value || '').toLowerCase();
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
+
function commandKey(command) {
|
|
215
|
+
const base = path.basename(String(command || ''));
|
|
216
|
+
const ext = path.extname(base);
|
|
217
|
+
return normalizeToken(ext ? base.slice(0, -ext.length) : base);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function isInteractiveCommand(command, args) {
|
|
221
|
+
const key = commandKey(command);
|
|
222
|
+
if (INTERACTIVE_BASES.has(key)) return true;
|
|
223
|
+
const subcommand = args && args.length ? normalizeToken(args[0]) : '';
|
|
224
|
+
if (subcommand && INTERACTIVE_SUBCOMMANDS[key] && INTERACTIVE_SUBCOMMANDS[key].has(subcommand)) {
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
|
|
214
230
|
function damerauLevenshtein(a, b, maxDistance = 2) {
|
|
215
231
|
const aNorm = normalizeToken(a);
|
|
216
232
|
const bNorm = normalizeToken(b);
|
|
@@ -276,6 +292,76 @@ function collectPathCommands() {
|
|
|
276
292
|
}
|
|
277
293
|
|
|
278
294
|
const PATH_COMMANDS = collectPathCommands();
|
|
295
|
+
const RESOLVE_CACHE = new Map();
|
|
296
|
+
const PATHEXT = (process.env.PATHEXT || '.COM;.EXE;.BAT;.CMD')
|
|
297
|
+
.split(';')
|
|
298
|
+
.map((value) => value.toLowerCase())
|
|
299
|
+
.filter(Boolean);
|
|
300
|
+
const INTERACTIVE_BASES = new Set(
|
|
301
|
+
(process.env.FUZZRUN_INTERACTIVE_BASES || 'npm,npx,yarn,pnpm')
|
|
302
|
+
.split(',')
|
|
303
|
+
.map((value) => normalizeToken(value).trim())
|
|
304
|
+
.filter(Boolean)
|
|
305
|
+
);
|
|
306
|
+
const INTERACTIVE_SUBCOMMANDS = {
|
|
307
|
+
npm: new Set(['publish', 'login', 'adduser']),
|
|
308
|
+
yarn: new Set(['publish', 'login']),
|
|
309
|
+
pnpm: new Set(['publish', 'login'])
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
function looksLikePath(value) {
|
|
313
|
+
if (!value) return false;
|
|
314
|
+
return value.includes('/') || value.includes('\\') || value.includes(':');
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function resolveCommand(command) {
|
|
318
|
+
if (!command || looksLikePath(command)) return command;
|
|
319
|
+
if (RESOLVE_CACHE.has(command)) return RESOLVE_CACHE.get(command);
|
|
320
|
+
|
|
321
|
+
let resolved = null;
|
|
322
|
+
if (process.platform === 'win32') {
|
|
323
|
+
const result = spawnSync('where', [command], { encoding: 'utf8' });
|
|
324
|
+
if (result.status === 0) {
|
|
325
|
+
const lines = String(result.stdout || '')
|
|
326
|
+
.split(/\r?\n/)
|
|
327
|
+
.map((value) => value.trim())
|
|
328
|
+
.filter(Boolean);
|
|
329
|
+
const preferred = lines.find((line) => PATHEXT.includes(path.extname(line).toLowerCase()));
|
|
330
|
+
resolved = preferred || lines[0] || null;
|
|
331
|
+
if (resolved && !path.extname(resolved)) {
|
|
332
|
+
for (const ext of PATHEXT) {
|
|
333
|
+
const candidate = `${resolved}${ext.toLowerCase()}`;
|
|
334
|
+
if (fs.existsSync(candidate)) {
|
|
335
|
+
resolved = candidate;
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
} else {
|
|
342
|
+
const result = spawnSync('which', [command], { encoding: 'utf8' });
|
|
343
|
+
if (result.status === 0) {
|
|
344
|
+
const line = String(result.stdout || '').trim();
|
|
345
|
+
if (line) resolved = line;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
RESOLVE_CACHE.set(command, resolved || command);
|
|
350
|
+
return resolved || command;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function normalizePowerShellGetPrefix(command) {
|
|
354
|
+
if (!command) return command;
|
|
355
|
+
const lowered = normalizeToken(command);
|
|
356
|
+
if (!lowered.startsWith('get-')) return command;
|
|
357
|
+
if (PATH_COMMANDS.has(command) || PATH_COMMANDS.has(lowered)) return command;
|
|
358
|
+
const stripped = command.slice(4);
|
|
359
|
+
if (!stripped) return command;
|
|
360
|
+
if (PATH_COMMANDS.has(stripped)) return stripped;
|
|
361
|
+
const match = findBestMatch(PATH_COMMANDS, stripped, MAX_DISTANCE);
|
|
362
|
+
if (match) return stripped;
|
|
363
|
+
return command;
|
|
364
|
+
}
|
|
279
365
|
|
|
280
366
|
function findBestMatch(candidates, target, maxDistance = MAX_DISTANCE) {
|
|
281
367
|
if (!candidates || !target) return null;
|
|
@@ -313,18 +399,35 @@ function parseSuggestion(text) {
|
|
|
313
399
|
return null;
|
|
314
400
|
}
|
|
315
401
|
|
|
316
|
-
function run(cmd, args) {
|
|
317
|
-
const
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
402
|
+
function run(cmd, args) {
|
|
403
|
+
const interactive = isInteractiveCommand(cmd, args);
|
|
404
|
+
const result = spawnSync(cmd, args, {
|
|
405
|
+
encoding: 'utf8',
|
|
406
|
+
stdio: interactive ? 'inherit' : ['inherit', 'pipe', 'pipe']
|
|
407
|
+
});
|
|
408
|
+
if (result.error && result.error.code === 'ENOENT') {
|
|
409
|
+
const resolved = resolveCommand(cmd);
|
|
410
|
+
if (resolved && resolved !== cmd) {
|
|
411
|
+
const retryInteractive = isInteractiveCommand(resolved, args);
|
|
412
|
+
const retry = spawnSync(resolved, args, {
|
|
413
|
+
encoding: 'utf8',
|
|
414
|
+
stdio: retryInteractive ? 'inherit' : ['inherit', 'pipe', 'pipe']
|
|
415
|
+
});
|
|
416
|
+
return {
|
|
417
|
+
code: typeof retry.status === 'number' ? retry.status : retry.error ? 1 : 0,
|
|
418
|
+
stdout: retryInteractive ? '' : retry.stdout || '',
|
|
419
|
+
stderr: retryInteractive ? '' : retry.stderr || '',
|
|
420
|
+
error: retry.error
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
return {
|
|
425
|
+
code: typeof result.status === 'number' ? result.status : result.error ? 1 : 0,
|
|
426
|
+
stdout: interactive ? '' : result.stdout || '',
|
|
427
|
+
stderr: interactive ? '' : result.stderr || '',
|
|
428
|
+
error: result.error
|
|
429
|
+
};
|
|
430
|
+
}
|
|
328
431
|
|
|
329
432
|
function logFix(from, to) {
|
|
330
433
|
process.stderr.write(`fuzzrun: auto-correcting "${from}" -> "${to}"\n`);
|
|
@@ -485,8 +588,9 @@ function main() {
|
|
|
485
588
|
}
|
|
486
589
|
}
|
|
487
590
|
|
|
488
|
-
|
|
591
|
+
let baseCommand = argv[0];
|
|
489
592
|
const rest = argv.slice(1);
|
|
593
|
+
baseCommand = normalizePowerShellGetPrefix(baseCommand);
|
|
490
594
|
const firstRun = run(baseCommand, rest);
|
|
491
595
|
|
|
492
596
|
if (firstRun.error && firstRun.error.code === 'ENOENT') {
|
package/src/installer.js
CHANGED
|
@@ -27,10 +27,21 @@ function getBinPath(packageRoot) {
|
|
|
27
27
|
function getProfileTargets() {
|
|
28
28
|
const home = os.homedir();
|
|
29
29
|
if (process.platform === 'win32') {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
const roots = new Set([path.join(home, 'Documents')]);
|
|
31
|
+
const oneDriveRoots = [
|
|
32
|
+
process.env.OneDrive,
|
|
33
|
+
process.env.OneDriveConsumer,
|
|
34
|
+
process.env.OneDriveCommercial
|
|
35
|
+
].filter(Boolean);
|
|
36
|
+
for (const root of oneDriveRoots) {
|
|
37
|
+
roots.add(path.basename(root).toLowerCase() === 'documents' ? root : path.join(root, 'Documents'));
|
|
38
|
+
}
|
|
39
|
+
const targets = [];
|
|
40
|
+
for (const root of roots) {
|
|
41
|
+
targets.push(path.join(root, 'PowerShell', 'Microsoft.PowerShell_profile.ps1'));
|
|
42
|
+
targets.push(path.join(root, 'WindowsPowerShell', 'Microsoft.PowerShell_profile.ps1'));
|
|
43
|
+
}
|
|
44
|
+
return [...new Set(targets)];
|
|
34
45
|
}
|
|
35
46
|
return [path.join(home, '.bashrc'), path.join(home, '.zshrc')];
|
|
36
47
|
}
|
|
@@ -47,15 +58,64 @@ function buildPowerShellSnippet(binPath) {
|
|
|
47
58
|
const lines = [
|
|
48
59
|
MARKER_START,
|
|
49
60
|
`$fuzzrun = "${binPath}"`,
|
|
61
|
+
'$global:FuzzRunLastLine = $null',
|
|
62
|
+
'if (Get-Module -ListAvailable -Name PSReadLine) {',
|
|
63
|
+
' try {',
|
|
64
|
+
' Import-Module PSReadLine -ErrorAction SilentlyContinue | Out-Null',
|
|
65
|
+
' Set-PSReadLineKeyHandler -Key Enter -ScriptBlock {',
|
|
66
|
+
' param($key, $arg)',
|
|
67
|
+
' $line = $null',
|
|
68
|
+
' $cursor = $null',
|
|
69
|
+
' [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor)',
|
|
70
|
+
' $global:FuzzRunLastLine = $line',
|
|
71
|
+
' [Microsoft.PowerShell.PSConsoleReadLine]::AcceptLine()',
|
|
72
|
+
' }',
|
|
73
|
+
' } catch {}',
|
|
74
|
+
'}',
|
|
50
75
|
'function global:fuzzrun { node $fuzzrun @args }',
|
|
51
76
|
'$ExecutionContext.InvokeCommand.CommandNotFoundAction = {',
|
|
52
77
|
' param($commandName, $eventArgs)',
|
|
53
|
-
'
|
|
78
|
+
' $cmd = $commandName',
|
|
79
|
+
' $fzArgs = @($eventArgs.Arguments)',
|
|
80
|
+
' $line = $global:FuzzRunLastLine',
|
|
81
|
+
' $global:FuzzRunLastLine = $null',
|
|
82
|
+
' if (-not $line -and $eventArgs.CommandLine) {',
|
|
83
|
+
' $line = $eventArgs.CommandLine',
|
|
84
|
+
' }',
|
|
85
|
+
' if (-not $line -and $eventArgs.CommandScriptBlock) {',
|
|
86
|
+
' $line = $eventArgs.CommandScriptBlock.ToString()',
|
|
87
|
+
' }',
|
|
88
|
+
' if (-not $line) {',
|
|
89
|
+
' $history = Get-History -Count 1 -ErrorAction SilentlyContinue',
|
|
90
|
+
' if ($history) { $line = $history.CommandLine }',
|
|
91
|
+
' }',
|
|
92
|
+
' $argv = @()',
|
|
93
|
+
' if ($line) {',
|
|
94
|
+
' $tokens = [System.Management.Automation.PSParser]::Tokenize($line, [ref]$null)',
|
|
95
|
+
" foreach ($token in $tokens) {",
|
|
96
|
+
" if ($token.Type -in @('Command','CommandArgument','CommandParameter','String','Number')) {",
|
|
97
|
+
' $argv += $token.Content',
|
|
98
|
+
' }',
|
|
99
|
+
' }',
|
|
100
|
+
' }',
|
|
101
|
+
' if ($argv.Count -gt 0) {',
|
|
102
|
+
' $cmd = $argv[0]',
|
|
103
|
+
' }',
|
|
104
|
+
' if ($argv.Count -gt 1) {',
|
|
105
|
+
' $fzArgs = $argv[1..($argv.Count - 1)]',
|
|
106
|
+
' }',
|
|
107
|
+
' $eventArgs.CommandScriptBlock = { fuzzrun $cmd @fzArgs }.GetNewClosure()',
|
|
108
|
+
' $eventArgs.StopSearch = $true',
|
|
54
109
|
'}'
|
|
55
110
|
];
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
111
|
+
lines.push(`$__fuzzrunBases = @(${WRAP_BASES.map((base) => `'${base}'`).join(', ')})`);
|
|
112
|
+
lines.push('foreach ($base in $__fuzzrunBases) {');
|
|
113
|
+
lines.push(' $resolved = Get-Command $base -ErrorAction SilentlyContinue | Where-Object { $_.CommandType -eq "Application" } | Select-Object -First 1');
|
|
114
|
+
lines.push(' if ($resolved) {');
|
|
115
|
+
lines.push(' $cmdName = $base');
|
|
116
|
+
lines.push(' Set-Item -Path ("Function:$cmdName") -Value { fuzzrun $cmdName @args }.GetNewClosure()');
|
|
117
|
+
lines.push(' }');
|
|
118
|
+
lines.push('}');
|
|
59
119
|
lines.push(MARKER_END, '');
|
|
60
120
|
return lines.join('\n');
|
|
61
121
|
}
|