fauxnix-cli 0.9.2 → 0.9.3
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 +9 -1
- package/dist/commands/archive.js +3 -2
- package/dist/commands/files.js +4 -2
- package/dist/commands/net.js +3 -4
- package/dist/commands/text-filters.js +33 -36
- package/dist/commands/text-io.js +89 -10
- package/dist/errors.d.ts +4 -0
- package/dist/errors.js +18 -5
- package/dist/executor.js +92 -102
- package/dist/registry.d.ts +2 -0
- package/dist/registry.js +4 -1
- package/dist/translator.js +18 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -58,6 +58,14 @@ Linux command line — file ops, text processing, process management, archives,
|
|
|
58
58
|
maps cleanly onto PowerShell + .NET. fauxnix implements that subset faithfully and *fails loudly
|
|
59
59
|
and helpfully* on what it can't translate, so the agent never gets silently-wrong results.
|
|
60
60
|
|
|
61
|
+
Labs now train computer-use agents on fleets of real desktops. Reporting in 2026 (*The
|
|
62
|
+
Information*, widely repeated) has OpenAI buying tens of thousands of Mac mini / Mac Studio
|
|
63
|
+
boxes — no screen, no keyboard — to reinforcement-learn agents that click, edit, test, and
|
|
64
|
+
run bash workflows, and Anthropic renting Mac minis through AWS for the same class of work.
|
|
65
|
+
That scoring environment is macOS. Windows users should not have to install a guest Unix to
|
|
66
|
+
keep up: the agent keeps writing bash; fauxnix makes the Windows box answer like the box the
|
|
67
|
+
agent was trained on. See [`docs/rfc-computer-use-windows.md`](docs/rfc-computer-use-windows.md).
|
|
68
|
+
|
|
61
69
|
## Install
|
|
62
70
|
|
|
63
71
|
```bash
|
|
@@ -156,7 +164,7 @@ development:
|
|
|
156
164
|
|
|
157
165
|
`cp` / `mv` / `rm` / `touch` / `du` / `ls` / `ll` / `mkdir` / `rmdir` / `mktemp` / `ln` /
|
|
158
166
|
`readlink` / `realpath` / `basename` / `dirname` / `stat` / `file` / `df` / `chmod` / `chown` /
|
|
159
|
-
`diff` / `tee` / `grep` / `head` carry a `CommandSpec`: unknown options fail with a GNU-style
|
|
167
|
+
`diff` / `tee` / `grep` / `head` / `echo` / `printf` / `cat` / `tail` / `wc` carry a `CommandSpec`: unknown options fail with a GNU-style
|
|
160
168
|
usage error instead of being ignored (`find` stays unspec'd so predicates like `-name` still
|
|
161
169
|
compile). Implemented GNU holes: `cp -n` / `mv -n` / `touch -c` / `tee --append` / `grep -m` /
|
|
162
170
|
`head --lines` / `du --max-depth`. `fauxnix list --json` and `docs/command-specs.md` dump the
|
package/dist/commands/archive.js
CHANGED
|
@@ -214,8 +214,9 @@ const tar = (args) => {
|
|
|
214
214
|
' if ($fx_c) { $fx_tar = $fx_c.Source } else { $fx_tar = $null }',
|
|
215
215
|
'}',
|
|
216
216
|
'if ($fx_tar) {',
|
|
217
|
-
|
|
218
|
-
|
|
217
|
+
// fx-native captures stdout as pipeline strings and sets fx_exit.
|
|
218
|
+
// [object[]]@(...) keeps an empty argv from unwrapping to $null on PS 5.1.
|
|
219
|
+
' fx-native $fx_tar ([object[]]@($fx_args))',
|
|
219
220
|
'} else {',
|
|
220
221
|
" [Console]::Error.WriteLine('tar: fauxnix: tar.exe not found (Windows 10+ ships bsdtar as tar.exe)')",
|
|
221
222
|
' $script:fx_exit = 1',
|
package/dist/commands/files.js
CHANGED
|
@@ -55,7 +55,7 @@ function psIsLink(it) {
|
|
|
55
55
|
/* ls */
|
|
56
56
|
/* ------------------------------------------------------------------ */
|
|
57
57
|
const ls = (args) => {
|
|
58
|
-
const { flags, longs, values, operandWords } = parseWords(args, [], ['--format']);
|
|
58
|
+
const { flags, longs, values, operandWords } = parseWords(args, [], ['--format', '--color']);
|
|
59
59
|
const long = flags.has('l') || longs.has('--long') || values.get('--format') === 'long';
|
|
60
60
|
const all = flags.has('a') || longs.has('--all');
|
|
61
61
|
const almost = flags.has('A') || longs.has('--almost-all');
|
|
@@ -725,7 +725,7 @@ const find = (args) => {
|
|
|
725
725
|
const preds = raw.slice(pathEnd);
|
|
726
726
|
if (preds.includes('-exec') || preds.includes('-execdir')) {
|
|
727
727
|
return ('[Console]::Error.WriteLine(' +
|
|
728
|
-
psStr('find: -exec is not supported by fauxnix;
|
|
728
|
+
psStr('find: -exec is not supported by fauxnix; use `find . -name "*.log" -delete` or grep -r instead') +
|
|
729
729
|
'); $script:fx_exit = 1');
|
|
730
730
|
}
|
|
731
731
|
const plan = parseFindPreds(preds);
|
|
@@ -967,6 +967,8 @@ export const specs = [
|
|
|
967
967
|
opt('S', undefined),
|
|
968
968
|
opt('r', undefined),
|
|
969
969
|
opt('R', '--recursive', 'unsupported', { reason: 'recursive listing' }),
|
|
970
|
+
// GNU optional WHEN; takesValue so --color=auto is valid. No ANSI.
|
|
971
|
+
opt(undefined, '--color', 'implemented', { takesValue: true }),
|
|
970
972
|
], ls),
|
|
971
973
|
fileSpec(['mkdir'], ['write'], [opt('p', '--parents'), opt('v', '--verbose')], mkdir),
|
|
972
974
|
fileSpec(['rmdir'], ['delete'], [], rmdir),
|
package/dist/commands/net.js
CHANGED
|
@@ -79,10 +79,9 @@ function synthWord(text) {
|
|
|
79
79
|
}
|
|
80
80
|
/** A native-exe invocation obeying the fauxnix contract (string lines + exit code). */
|
|
81
81
|
function nativeCall(exe, argArray) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
].join('\n');
|
|
82
|
+
// fx-native captures stdout as pipeline strings and sets fx_exit.
|
|
83
|
+
// [object[]]@(...) keeps an empty argv from unwrapping to $null on PS 5.1.
|
|
84
|
+
return 'fx-native ' + psStr(exe) + ' ([object[]]@(' + argArray + '))';
|
|
86
85
|
}
|
|
87
86
|
/* ------------------------------------------------------------------ */
|
|
88
87
|
/* curl */
|
|
@@ -583,48 +583,45 @@ const grep = (args) => {
|
|
|
583
583
|
scan.push(' $fx_mleft--');
|
|
584
584
|
if (onlyMatch && !inv) {
|
|
585
585
|
if (fixed) {
|
|
586
|
+
// GNU -o: emit leftmost-longest matches in input order, not per-needle.
|
|
586
587
|
if (ci)
|
|
587
588
|
scan.push(' $lx = $fx_l.ToLower()');
|
|
588
589
|
const hay = ci ? '$lx' : '$fx_l';
|
|
589
|
-
const
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
needle +
|
|
611
|
-
'.Length)) }');
|
|
612
|
-
}
|
|
613
|
-
else {
|
|
614
|
-
scan.push(indent + ' fx-emitline $fx_i (' + hay + '.Substring($p, ' + needle + '.Length))');
|
|
615
|
-
}
|
|
616
|
-
scan.push(indent + ' $p = ' + hay + '.IndexOf(' + needle + ', $p + 1)');
|
|
617
|
-
scan.push(indent + '}');
|
|
618
|
-
};
|
|
619
|
-
if (multiFixed) {
|
|
620
|
-
const arr = ci ? '$fx_needles_ll' : '$fx_needles';
|
|
621
|
-
scan.push(' foreach ($fx_needle in ' + arr + ') {');
|
|
622
|
-
emitFixedHits('$fx_needle', ' ');
|
|
623
|
-
scan.push(' }');
|
|
590
|
+
const needleArr = multiFixed
|
|
591
|
+
? ci
|
|
592
|
+
? '$fx_needles_ll'
|
|
593
|
+
: '$fx_needles'
|
|
594
|
+
: '@(' + (ci ? '$fx_needle_ll' : '$fx_needle') + ')';
|
|
595
|
+
scan.push(' $fx_cands = New-Object System.Collections.Generic.List[object]');
|
|
596
|
+
scan.push(' foreach ($fx_needle in ' + needleArr + ') {');
|
|
597
|
+
scan.push(' if ($fx_needle.Length -lt 1) { continue }');
|
|
598
|
+
scan.push(' $p = ' + hay + '.IndexOf($fx_needle)');
|
|
599
|
+
scan.push(' while ($p -ge 0) {');
|
|
600
|
+
if (word) {
|
|
601
|
+
scan.push(' $ok = $true');
|
|
602
|
+
scan.push(" if ($p -gt 0) { $c = " +
|
|
603
|
+
hay +
|
|
604
|
+
"[$p - 1]; $ok = -not ([char]::IsLetterOrDigit($c) -or $c -eq '_') }");
|
|
605
|
+
scan.push(' if ($ok) { $e = $p + $fx_needle.Length; if ($e -lt ' +
|
|
606
|
+
hay +
|
|
607
|
+
'.Length) { $c = ' +
|
|
608
|
+
hay +
|
|
609
|
+
"[$e]; $ok = -not ([char]::IsLetterOrDigit($c) -or $c -eq '_') } }");
|
|
610
|
+
scan.push(' if ($ok) { [void]$fx_cands.Add([pscustomobject]@{ Start = $p; Len = $fx_needle.Length }) }');
|
|
624
611
|
}
|
|
625
612
|
else {
|
|
626
|
-
|
|
613
|
+
scan.push(' [void]$fx_cands.Add([pscustomobject]@{ Start = $p; Len = $fx_needle.Length })');
|
|
627
614
|
}
|
|
615
|
+
scan.push(' $p = ' + hay + '.IndexOf($fx_needle, $p + 1)');
|
|
616
|
+
scan.push(' }');
|
|
617
|
+
scan.push(' }');
|
|
618
|
+
scan.push(' $fx_end = 0');
|
|
619
|
+
scan.push(' foreach ($fx_c in @($fx_cands | Sort-Object Start, @{ Expression = { $_.Len }; Descending = $true })) {');
|
|
620
|
+
scan.push(' if ($fx_c.Start -ge $fx_end) {');
|
|
621
|
+
scan.push(' fx-emitline $fx_i (' + hay + '.Substring($fx_c.Start, $fx_c.Len))');
|
|
622
|
+
scan.push(' $fx_end = $fx_c.Start + $fx_c.Len');
|
|
623
|
+
scan.push(' }');
|
|
624
|
+
scan.push(' }');
|
|
628
625
|
}
|
|
629
626
|
else {
|
|
630
627
|
scan.push(' foreach ($fx_m in $fx_re.Matches($fx_l)) { fx-emitline $fx_i $fx_m.Value }');
|
package/dist/commands/text-io.js
CHANGED
|
@@ -1093,6 +1093,9 @@ const xargs = (args) => {
|
|
|
1093
1093
|
noRunIfEmpty = true;
|
|
1094
1094
|
else if (ch === 't')
|
|
1095
1095
|
trace = true;
|
|
1096
|
+
else if (ch === '0') {
|
|
1097
|
+
return psErrExpr(psStr('xargs: -0 is not supported by fauxnix'));
|
|
1098
|
+
}
|
|
1096
1099
|
else if (ch === 'n' || ch === 'I' || ch === 'L') {
|
|
1097
1100
|
const restv = body.slice(c + 1);
|
|
1098
1101
|
let val;
|
|
@@ -1140,12 +1143,12 @@ const xargs = (args) => {
|
|
|
1140
1143
|
}
|
|
1141
1144
|
const n = chunkN !== null && Number.isFinite(chunkN) && chunkN > 0 ? chunkN : 0;
|
|
1142
1145
|
const replExpr = repl !== null ? psStr(repl) : null;
|
|
1146
|
+
// fx-native already records $script:fx_exit from ExitCode.
|
|
1143
1147
|
const invoke = [
|
|
1144
1148
|
' if (' +
|
|
1145
1149
|
pb(trace) +
|
|
1146
1150
|
") { [Console]::Error.WriteLine(((@($fx_cmd) + @($fx_argv)) -join ' ')) }",
|
|
1147
|
-
'
|
|
1148
|
-
' if ($LASTEXITCODE -ne 0 -and $script:fx_exit -eq 0) { $script:fx_exit = $LASTEXITCODE }',
|
|
1151
|
+
' fx-native $fx_cmd $fx_argv',
|
|
1149
1152
|
];
|
|
1150
1153
|
let dispatch;
|
|
1151
1154
|
const guard = ' if (' + pb(noRunIfEmpty) + ' -and $fx_args.Count -eq 0) { }' + '\n' + ' else {';
|
|
@@ -1154,7 +1157,7 @@ const xargs = (args) => {
|
|
|
1154
1157
|
dispatch = [
|
|
1155
1158
|
guard,
|
|
1156
1159
|
' foreach ($fx_l in $fx_args) {',
|
|
1157
|
-
' $fx_argv = @()',
|
|
1160
|
+
' $fx_argv = [object[]]@()',
|
|
1158
1161
|
' $fx_hit = $false',
|
|
1159
1162
|
' foreach ($fx_a in $fx_base) {',
|
|
1160
1163
|
' if ($fx_a.Contains(' + replExpr + ')) {',
|
|
@@ -1175,7 +1178,7 @@ const xargs = (args) => {
|
|
|
1175
1178
|
' $fx_i = 0',
|
|
1176
1179
|
' $fx_ran = $false',
|
|
1177
1180
|
' while ($fx_i -lt $fx_args.Count) {',
|
|
1178
|
-
' $fx_argv = @($fx_base)',
|
|
1181
|
+
' $fx_argv = [object[]]@($fx_base)',
|
|
1179
1182
|
' $fx_j = 0',
|
|
1180
1183
|
' while ($fx_j -lt ' + n + ' -and $fx_i -lt $fx_args.Count) {',
|
|
1181
1184
|
' $fx_argv += $fx_args[$fx_i]',
|
|
@@ -1185,10 +1188,8 @@ const xargs = (args) => {
|
|
|
1185
1188
|
...invoke,
|
|
1186
1189
|
' }',
|
|
1187
1190
|
' if (-not $fx_ran) {',
|
|
1188
|
-
' $fx_argv = @($fx_base)',
|
|
1189
|
-
'
|
|
1190
|
-
' ' + invoke[1],
|
|
1191
|
-
' ' + invoke[2],
|
|
1191
|
+
' $fx_argv = [object[]]@($fx_base)',
|
|
1192
|
+
...invoke.map((line) => ' ' + line),
|
|
1192
1193
|
' }',
|
|
1193
1194
|
' }',
|
|
1194
1195
|
];
|
|
@@ -1196,17 +1197,27 @@ const xargs = (args) => {
|
|
|
1196
1197
|
else {
|
|
1197
1198
|
dispatch = [
|
|
1198
1199
|
guard,
|
|
1199
|
-
' $fx_argv = @($fx_base) + @($fx_args)',
|
|
1200
|
+
' $fx_argv = [object[]](@($fx_base) + @($fx_args))',
|
|
1200
1201
|
...invoke,
|
|
1201
1202
|
' }',
|
|
1202
1203
|
];
|
|
1203
1204
|
}
|
|
1205
|
+
// Default GNU xargs splits on blanks; -I keeps whole lines as one item.
|
|
1206
|
+
const collectArgs = replExpr !== null
|
|
1207
|
+
? "$fx_args = @($fx_in | Where-Object { $_ -ne '' })"
|
|
1208
|
+
: [
|
|
1209
|
+
'$fx_args = @()',
|
|
1210
|
+
'foreach ($fx_l in $fx_in) {',
|
|
1211
|
+
" if ($fx_l -eq '') { continue }",
|
|
1212
|
+
" $fx_args += @($fx_l -split '[ \\t]+' | Where-Object { $_ -ne '' })",
|
|
1213
|
+
'}',
|
|
1214
|
+
].join('\n');
|
|
1204
1215
|
return [
|
|
1205
1216
|
PS_SPLITLINES_FN,
|
|
1206
1217
|
STDIN_INLINES,
|
|
1207
1218
|
'$fx_tg = ' + argListExpr(target, exprOfWord),
|
|
1208
1219
|
"if ($fx_tg.Count -eq 0) { $fx_cmd = ''; $fx_base = @() } else { $fx_cmd = [string]$fx_tg[0]; $fx_base = $(if ($fx_tg.Count -gt 1) { @($fx_tg[1..($fx_tg.Count - 1)]) } else { @() }) }",
|
|
1209
|
-
|
|
1220
|
+
collectArgs,
|
|
1210
1221
|
...dispatch,
|
|
1211
1222
|
].join('\n');
|
|
1212
1223
|
};
|
|
@@ -1236,6 +1247,74 @@ export const specs = [
|
|
|
1236
1247
|
dispatch: 'translated',
|
|
1237
1248
|
handler: head,
|
|
1238
1249
|
},
|
|
1250
|
+
{
|
|
1251
|
+
names: ['echo'],
|
|
1252
|
+
options: [
|
|
1253
|
+
{ short: 'n', support: 'implemented' },
|
|
1254
|
+
{ short: 'e', support: 'implemented' },
|
|
1255
|
+
{ short: 'E', support: 'implemented' },
|
|
1256
|
+
],
|
|
1257
|
+
effects: [],
|
|
1258
|
+
platform: 'windows-ps51',
|
|
1259
|
+
dispatch: 'translated',
|
|
1260
|
+
usageExit: 2,
|
|
1261
|
+
leadingOptions: true,
|
|
1262
|
+
handler: echo,
|
|
1263
|
+
},
|
|
1264
|
+
{
|
|
1265
|
+
names: ['printf'],
|
|
1266
|
+
options: [],
|
|
1267
|
+
effects: [],
|
|
1268
|
+
platform: 'windows-ps51',
|
|
1269
|
+
dispatch: 'translated',
|
|
1270
|
+
usageExit: 2,
|
|
1271
|
+
leadingOptions: true,
|
|
1272
|
+
handler: printf,
|
|
1273
|
+
},
|
|
1274
|
+
{
|
|
1275
|
+
names: ['cat'],
|
|
1276
|
+
options: [
|
|
1277
|
+
{ short: 'n', support: 'implemented' },
|
|
1278
|
+
{ short: 'b', support: 'implemented' },
|
|
1279
|
+
{ short: 's', support: 'implemented' },
|
|
1280
|
+
{ short: 'E', support: 'implemented' },
|
|
1281
|
+
{ short: 'T', support: 'implemented' },
|
|
1282
|
+
{ short: 'A', support: 'implemented' },
|
|
1283
|
+
],
|
|
1284
|
+
effects: ['read'],
|
|
1285
|
+
platform: 'windows-ps51',
|
|
1286
|
+
dispatch: 'translated',
|
|
1287
|
+
handler: cat,
|
|
1288
|
+
},
|
|
1289
|
+
{
|
|
1290
|
+
names: ['tail'],
|
|
1291
|
+
options: [
|
|
1292
|
+
{ short: 'n', long: '--lines', takesValue: true, support: 'implemented' },
|
|
1293
|
+
{ short: 'c', long: '--bytes', takesValue: true, support: 'implemented' },
|
|
1294
|
+
{ short: 'q', long: '--quiet', support: 'implemented' },
|
|
1295
|
+
{ long: '--silent', support: 'implemented' },
|
|
1296
|
+
{ short: 'v', long: '--verbose', support: 'implemented' },
|
|
1297
|
+
{ short: 'f', support: 'unsupported', reason: 'no persistent tty' },
|
|
1298
|
+
{ short: 'F', support: 'unsupported', reason: 'no persistent tty' },
|
|
1299
|
+
],
|
|
1300
|
+
effects: ['read'],
|
|
1301
|
+
platform: 'windows-ps51',
|
|
1302
|
+
dispatch: 'translated',
|
|
1303
|
+
handler: tail,
|
|
1304
|
+
},
|
|
1305
|
+
{
|
|
1306
|
+
names: ['wc'],
|
|
1307
|
+
options: [
|
|
1308
|
+
{ short: 'l', support: 'implemented' },
|
|
1309
|
+
{ short: 'w', support: 'implemented' },
|
|
1310
|
+
{ short: 'c', support: 'implemented' },
|
|
1311
|
+
{ short: 'm', support: 'implemented' },
|
|
1312
|
+
],
|
|
1313
|
+
effects: ['read'],
|
|
1314
|
+
platform: 'windows-ps51',
|
|
1315
|
+
dispatch: 'translated',
|
|
1316
|
+
handler: wc,
|
|
1317
|
+
},
|
|
1239
1318
|
];
|
|
1240
1319
|
export const handlers = {
|
|
1241
1320
|
echo,
|
package/dist/errors.d.ts
CHANGED
|
@@ -2,4 +2,8 @@
|
|
|
2
2
|
* Error normalization — make PowerShell failures look like bash failures
|
|
3
3
|
* so agents can pattern-match on familiar Linux error styles.
|
|
4
4
|
*/
|
|
5
|
+
/** Missing `python3` on Windows (Mac/Linux agents emit this; do not alias). */
|
|
6
|
+
export declare const PYTHON3_WINDOWS_HINT = " (fauxnix: try `python` or `py` on Windows)";
|
|
7
|
+
/** `.sh` cannot be CreateProcess'd; fx-native never emits the PS not-recognized line. */
|
|
8
|
+
export declare const SH_SCRIPT_WINDOWS_HINT = " (fauxnix: .sh scripts cannot run natively on Windows)";
|
|
5
9
|
export declare function normalizeStderr(stderr: string): string;
|
package/dist/errors.js
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
* Error normalization — make PowerShell failures look like bash failures
|
|
3
3
|
* so agents can pattern-match on familiar Linux error styles.
|
|
4
4
|
*/
|
|
5
|
+
/** Missing `python3` on Windows (Mac/Linux agents emit this; do not alias). */
|
|
6
|
+
export const PYTHON3_WINDOWS_HINT = ' (fauxnix: try `python` or `py` on Windows)';
|
|
7
|
+
/** `.sh` cannot be CreateProcess'd; fx-native never emits the PS not-recognized line. */
|
|
8
|
+
export const SH_SCRIPT_WINDOWS_HINT = ' (fauxnix: .sh scripts cannot run natively on Windows)';
|
|
9
|
+
function commandNotFound(name) {
|
|
10
|
+
const msg = 'bash: ' + name + ': command not found';
|
|
11
|
+
const base = name.replace(/^.*[/\\]/, '');
|
|
12
|
+
if (/^python3(\.exe)?$/i.test(base))
|
|
13
|
+
return msg + PYTHON3_WINDOWS_HINT;
|
|
14
|
+
if (/\.sh$/i.test(name))
|
|
15
|
+
return msg + SH_SCRIPT_WINDOWS_HINT;
|
|
16
|
+
return msg;
|
|
17
|
+
}
|
|
5
18
|
/** Lines produced by PowerShell error formatting that bash would never show. */
|
|
6
19
|
const PS_NOISE = [
|
|
7
20
|
/^\s*\+ CategoryInfo\s*:/,
|
|
@@ -62,15 +75,15 @@ export function normalizeStderr(stderr) {
|
|
|
62
75
|
// "The term 'x' is not recognized as a name of a cmdlet, function, ..."
|
|
63
76
|
let m = line.match(/^The term '(.+?)' is not recognized/);
|
|
64
77
|
if (m)
|
|
65
|
-
return
|
|
78
|
+
return commandNotFound(m[1]);
|
|
66
79
|
// zh-CN: 无法将"x"项识别为 cmdlet、函数、脚本文件或可运行程序的名称
|
|
67
80
|
m = line.match(/^无法将["'”]?([^"'”]+)["'”]?项识别为/);
|
|
68
81
|
if (m)
|
|
69
|
-
return
|
|
82
|
+
return commandNotFound(m[1]);
|
|
70
83
|
// "x : The term 'y' is not recognized ..." (with source prefix)
|
|
71
84
|
m = line.match(/^(\S+)\s*:\s*The term '(.+?)' is not recognized/);
|
|
72
85
|
if (m)
|
|
73
|
-
return
|
|
86
|
+
return commandNotFound(m[2]);
|
|
74
87
|
// "cat : Cannot find path 'D:\x' because it does not exist."
|
|
75
88
|
m = line.match(/^(\S+)\s*:\s*Cannot find path '(.+?)' because it does not exist\.?$/);
|
|
76
89
|
if (m) {
|
|
@@ -93,9 +106,9 @@ export function normalizeStderr(stderr) {
|
|
|
93
106
|
m = line.match(/^(\S+)\s*:\s*(.*)Access to the path '(.+?)' is denied\.?$/);
|
|
94
107
|
if (m)
|
|
95
108
|
return m[1].toLowerCase() + ': cannot remove \'' + m[3] + '\': Permission denied';
|
|
96
|
-
//
|
|
109
|
+
// leftover PS not-recognized lines that the rewrites above did not catch
|
|
97
110
|
if (/\.sh'?/.test(line) && /is not recognized/.test(line)) {
|
|
98
|
-
return line +
|
|
111
|
+
return line + SH_SCRIPT_WINDOWS_HINT;
|
|
99
112
|
}
|
|
100
113
|
return line;
|
|
101
114
|
});
|
package/dist/executor.js
CHANGED
|
@@ -26,7 +26,35 @@ function isNulPath(p) {
|
|
|
26
26
|
const base = p.split(/[/\\]/).pop() ?? p;
|
|
27
27
|
return /^NUL$/i.test(base);
|
|
28
28
|
}
|
|
29
|
-
function
|
|
29
|
+
function applyRedirectDest(op, target, stdout, stderr) {
|
|
30
|
+
if (op === '2>&1')
|
|
31
|
+
return { stdout, stderr: stdout };
|
|
32
|
+
if (op === '1>&2')
|
|
33
|
+
return { stdout: stderr, stderr };
|
|
34
|
+
if (op === '<' || target === undefined)
|
|
35
|
+
return { stdout, stderr };
|
|
36
|
+
const dest = isNulPath(target) ? { kind: 'nul' } : { kind: 'file', path: target };
|
|
37
|
+
if (op === '>' || op === '>>')
|
|
38
|
+
return { stdout: dest, stderr };
|
|
39
|
+
if (op === '2>' || op === '2>>')
|
|
40
|
+
return { stdout, stderr: dest };
|
|
41
|
+
if (op === '&>' || op === '&>>')
|
|
42
|
+
return { stdout: dest, stderr: dest };
|
|
43
|
+
return { stdout, stderr };
|
|
44
|
+
}
|
|
45
|
+
/** Last-stage output fds only — captured stdout/stderr apply. */
|
|
46
|
+
function lastStageOutputDests(redirects, resolveTarget) {
|
|
47
|
+
let stdout = { kind: 'caller', fd: 1 };
|
|
48
|
+
let stderr = { kind: 'caller', fd: 2 };
|
|
49
|
+
for (const r of redirects) {
|
|
50
|
+
const target = r.op === '2>&1' || r.op === '1>&2' || r.op === '<'
|
|
51
|
+
? undefined
|
|
52
|
+
: resolveTarget(winTarget(r.target));
|
|
53
|
+
({ stdout, stderr } = applyRedirectDest(r.op, target, stdout, stderr));
|
|
54
|
+
}
|
|
55
|
+
return { stdout, stderr };
|
|
56
|
+
}
|
|
57
|
+
function emitToPrepDest(dest, msg, fds, caller) {
|
|
30
58
|
if (dest.kind === 'nul')
|
|
31
59
|
return;
|
|
32
60
|
if (dest.kind === 'file') {
|
|
@@ -35,10 +63,14 @@ function emitToPrepDest(dest, msg, fds, fallback) {
|
|
|
35
63
|
return;
|
|
36
64
|
}
|
|
37
65
|
catch {
|
|
38
|
-
|
|
66
|
+
caller.stderr(msg);
|
|
67
|
+
return;
|
|
39
68
|
}
|
|
40
69
|
}
|
|
41
|
-
|
|
70
|
+
if (dest.fd === 1)
|
|
71
|
+
caller.stdout(msg);
|
|
72
|
+
else
|
|
73
|
+
caller.stderr(msg);
|
|
42
74
|
}
|
|
43
75
|
function writeAllSync(fd, data) {
|
|
44
76
|
let off = 0;
|
|
@@ -94,9 +126,6 @@ function planRedirects(redirects) {
|
|
|
94
126
|
appendStdout: false,
|
|
95
127
|
stderrFile: null,
|
|
96
128
|
appendStderr: false,
|
|
97
|
-
mergeStderr: false,
|
|
98
|
-
devNull: false,
|
|
99
|
-
swallowStderr: false,
|
|
100
129
|
};
|
|
101
130
|
for (const red of redirects) {
|
|
102
131
|
const target = winTarget(red.target);
|
|
@@ -107,41 +136,30 @@ function planRedirects(redirects) {
|
|
|
107
136
|
case '>':
|
|
108
137
|
case '&>':
|
|
109
138
|
if (isNulPath(target)) {
|
|
110
|
-
r.devNull = true;
|
|
111
139
|
r.stdoutFile = null;
|
|
112
|
-
if (red.op === '&>')
|
|
113
|
-
r.swallowStderr = true;
|
|
140
|
+
if (red.op === '&>')
|
|
114
141
|
r.stderrFile = null;
|
|
115
|
-
}
|
|
116
142
|
}
|
|
117
143
|
else {
|
|
118
|
-
r.devNull = false;
|
|
119
144
|
r.stdoutFile = target;
|
|
120
145
|
r.appendStdout = false;
|
|
121
|
-
if (red.op === '&>')
|
|
146
|
+
if (red.op === '&>')
|
|
122
147
|
r.stderrFile = target;
|
|
123
|
-
r.swallowStderr = false;
|
|
124
|
-
}
|
|
125
148
|
}
|
|
126
149
|
break;
|
|
127
150
|
case '>>':
|
|
128
151
|
case '&>>':
|
|
129
152
|
if (isNulPath(target)) {
|
|
130
|
-
r.devNull = true;
|
|
131
153
|
r.stdoutFile = null;
|
|
132
|
-
if (red.op === '&>>')
|
|
133
|
-
r.swallowStderr = true;
|
|
154
|
+
if (red.op === '&>>')
|
|
134
155
|
r.stderrFile = null;
|
|
135
|
-
}
|
|
136
156
|
}
|
|
137
157
|
else {
|
|
138
|
-
r.devNull = false;
|
|
139
158
|
r.stdoutFile = target;
|
|
140
159
|
r.appendStdout = true;
|
|
141
160
|
if (red.op === '&>>') {
|
|
142
161
|
r.stderrFile = target;
|
|
143
162
|
r.appendStderr = true;
|
|
144
|
-
r.swallowStderr = false;
|
|
145
163
|
}
|
|
146
164
|
}
|
|
147
165
|
break;
|
|
@@ -149,31 +167,22 @@ function planRedirects(redirects) {
|
|
|
149
167
|
if (isNulPath(target)) {
|
|
150
168
|
// stderr only — must not undo a prior >/dev/null
|
|
151
169
|
r.stderrFile = null;
|
|
152
|
-
r.swallowStderr = true;
|
|
153
170
|
}
|
|
154
171
|
else {
|
|
155
172
|
r.stderrFile = target;
|
|
156
173
|
r.appendStderr = false;
|
|
157
|
-
r.swallowStderr = false;
|
|
158
174
|
}
|
|
159
175
|
break;
|
|
160
176
|
case '2>>':
|
|
161
177
|
if (isNulPath(target)) {
|
|
162
178
|
r.stderrFile = null;
|
|
163
|
-
r.swallowStderr = true;
|
|
164
179
|
}
|
|
165
180
|
else {
|
|
166
181
|
r.stderrFile = target;
|
|
167
182
|
r.appendStderr = true;
|
|
168
|
-
r.swallowStderr = false;
|
|
169
183
|
}
|
|
170
184
|
break;
|
|
171
|
-
|
|
172
|
-
r.mergeStderr = true;
|
|
173
|
-
break;
|
|
174
|
-
case '1>&2':
|
|
175
|
-
r.mergeStderr = false;
|
|
176
|
-
r.stdoutToStderr = true;
|
|
185
|
+
default:
|
|
177
186
|
break;
|
|
178
187
|
}
|
|
179
188
|
}
|
|
@@ -364,18 +373,19 @@ async function runPlans(plans, session, opts, afterSegment, ensureHost) {
|
|
|
364
373
|
let redirectPrepFailed = false;
|
|
365
374
|
// Snapshot fd destinations as we walk. `2>&1` copies stdout *at that
|
|
366
375
|
// moment*; a later `>file` must not drag stderr along (bash fd dup).
|
|
367
|
-
let prepStdout = { kind: 'caller' };
|
|
368
|
-
let prepStderr = { kind: 'caller' };
|
|
369
|
-
const emitPrepError = (msg) => emitToPrepDest(prepStderr, msg, prepFds,
|
|
370
|
-
|
|
376
|
+
let prepStdout = { kind: 'caller', fd: 1 };
|
|
377
|
+
let prepStderr = { kind: 'caller', fd: 2 };
|
|
378
|
+
const emitPrepError = (msg) => emitToPrepDest(prepStderr, msg, prepFds, {
|
|
379
|
+
stdout: (s) => {
|
|
380
|
+
stdout += s;
|
|
381
|
+
},
|
|
382
|
+
stderr: (s) => {
|
|
383
|
+
stderr += s;
|
|
384
|
+
},
|
|
371
385
|
});
|
|
372
386
|
for (const r of plan.redirects) {
|
|
373
|
-
if (r.op === '2>&1') {
|
|
374
|
-
prepStderr = prepStdout;
|
|
375
|
-
continue;
|
|
376
|
-
}
|
|
377
|
-
if (r.op === '1>&2') {
|
|
378
|
-
prepStdout = prepStderr;
|
|
387
|
+
if (r.op === '2>&1' || r.op === '1>&2') {
|
|
388
|
+
({ stdout: prepStdout, stderr: prepStderr } = applyRedirectDest(r.op, undefined, prepStdout, prepStderr));
|
|
379
389
|
continue;
|
|
380
390
|
}
|
|
381
391
|
const target = resolveTarget(winTarget(r.target));
|
|
@@ -389,33 +399,16 @@ async function runPlans(plans, session, opts, afterSegment, ensureHost) {
|
|
|
389
399
|
}
|
|
390
400
|
continue;
|
|
391
401
|
}
|
|
392
|
-
if (isNulPath(target)) {
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
prepStderr = { kind: 'nul' };
|
|
402
|
+
if (!isNulPath(target)) {
|
|
403
|
+
const append = r.op === '>>' || r.op === '2>>' || r.op === '&>>';
|
|
404
|
+
const fail = prepareRedirectFile(target, append, prepFds);
|
|
405
|
+
if (fail) {
|
|
406
|
+
emitPrepError('bash: ' + fail + '\n');
|
|
407
|
+
redirectPrepFailed = true;
|
|
408
|
+
break;
|
|
400
409
|
}
|
|
401
|
-
continue;
|
|
402
|
-
}
|
|
403
|
-
const append = r.op === '>>' || r.op === '2>>' || r.op === '&>>';
|
|
404
|
-
const fail = prepareRedirectFile(target, append, prepFds);
|
|
405
|
-
if (fail) {
|
|
406
|
-
emitPrepError('bash: ' + fail + '\n');
|
|
407
|
-
redirectPrepFailed = true;
|
|
408
|
-
break;
|
|
409
|
-
}
|
|
410
|
-
const fileDest = { kind: 'file', path: target };
|
|
411
|
-
if (r.op === '>' || r.op === '>>')
|
|
412
|
-
prepStdout = fileDest;
|
|
413
|
-
else if (r.op === '2>' || r.op === '2>>')
|
|
414
|
-
prepStderr = fileDest;
|
|
415
|
-
else if (r.op === '&>' || r.op === '&>>') {
|
|
416
|
-
prepStdout = fileDest;
|
|
417
|
-
prepStderr = fileDest;
|
|
418
410
|
}
|
|
411
|
+
({ stdout: prepStdout, stderr: prepStderr } = applyRedirectDest(r.op, target, prepStdout, prepStderr));
|
|
419
412
|
}
|
|
420
413
|
if (redirectPrepFailed) {
|
|
421
414
|
exitCode = 1;
|
|
@@ -438,11 +431,15 @@ async function runPlans(plans, session, opts, afterSegment, ensureHost) {
|
|
|
438
431
|
break;
|
|
439
432
|
}
|
|
440
433
|
const encoded = wrapScript(plan.body, { mode: 'host' });
|
|
434
|
+
// Last-stage fds last-win independently. `2>&1 >/dev/null` snapshots
|
|
435
|
+
// stderr onto the caller's stdout before stdout is pointed at NUL, so
|
|
436
|
+
// captured stderr is still returned; `>/dev/null 2>&1` points both at NUL.
|
|
437
|
+
const applyDests = lastStageOutputDests(plan.outputRedirects, resolveTarget);
|
|
441
438
|
// Response budgets cap what the CALLER receives — streams redirected to
|
|
442
439
|
// files must never be truncated by them (Codex-review P1: `printf … > f`
|
|
443
440
|
// with a small stdoutLimit was writing a clipped file). The final
|
|
444
441
|
// clipUtf8 below still enforces the returned-data budget.
|
|
445
|
-
const fileRedirected =
|
|
442
|
+
const fileRedirected = applyDests.stdout.kind === 'file' || applyDests.stderr.kind === 'file';
|
|
446
443
|
const inv = await ensureHost().invoke(encoded, {
|
|
447
444
|
FAUXNIX_CWD: currentDir,
|
|
448
445
|
FAUXNIX_PREV_EXIT: session.prevExit === null ? '' : String(session.prevExit),
|
|
@@ -470,52 +467,45 @@ async function runPlans(plans, session, opts, afterSegment, ensureHost) {
|
|
|
470
467
|
// GNU line discipline: the PS host terminates Write-Output lines with
|
|
471
468
|
// CRLF. Exact writers (fx-write / printf / echo -n) must keep embedded
|
|
472
469
|
// CR so `printf 'a\r\nb' > out` stays 4 bytes.
|
|
473
|
-
|
|
470
|
+
const segOut = normalizeHostNewlines(decodeOutput(inv.stdout, decodePref));
|
|
474
471
|
let segErr = normalizeHostNewlines(normalizeStderr(decodeOutput(inv.stderr, decodePref)));
|
|
475
472
|
if (inv.timedOut) {
|
|
476
473
|
segErr += timeoutMessage;
|
|
477
474
|
}
|
|
478
|
-
if (red.mergeStderr) {
|
|
479
|
-
segOut += (segOut && !segOut.endsWith('\n') && segErr ? '\n' : '') + segErr;
|
|
480
|
-
segErr = '';
|
|
481
|
-
}
|
|
482
|
-
const stdoutToStderr = red.stdoutToStderr;
|
|
483
|
-
if (stdoutToStderr) {
|
|
484
|
-
segErr += segOut;
|
|
485
|
-
segOut = '';
|
|
486
|
-
}
|
|
487
|
-
if (red.swallowStderr)
|
|
488
|
-
segErr = '';
|
|
489
|
-
if (red.devNull)
|
|
490
|
-
segOut = '';
|
|
491
475
|
// Write captured streams through the fds opened during preflight
|
|
492
476
|
// (bash: the redirect refers to the open file, not the path). Reopening
|
|
493
477
|
// the path would recreate a file the command just unlinked
|
|
494
478
|
// (`rm out.txt > out.txt`).
|
|
495
479
|
let redirectOk = true;
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
480
|
+
const deliverCaptured = (dest, data, fromStdout) => {
|
|
481
|
+
if (!data)
|
|
482
|
+
return;
|
|
483
|
+
if (dest.kind === 'nul')
|
|
484
|
+
return;
|
|
485
|
+
if (dest.kind === 'file') {
|
|
486
|
+
try {
|
|
487
|
+
writeToPrepFd(prepFds, dest.path, data);
|
|
488
|
+
}
|
|
489
|
+
catch (e) {
|
|
490
|
+
if (fromStdout) {
|
|
491
|
+
stderr += 'bash: ' + dest.path + ': cannot create: ' + e.message + '\n';
|
|
492
|
+
exitCode = 1;
|
|
493
|
+
redirectOk = false;
|
|
494
|
+
stdout += data;
|
|
495
|
+
}
|
|
496
|
+
else {
|
|
497
|
+
stderr += data;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
return;
|
|
515
501
|
}
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
502
|
+
if (dest.fd === 1)
|
|
503
|
+
stdout += data;
|
|
504
|
+
else
|
|
505
|
+
stderr += data;
|
|
506
|
+
};
|
|
507
|
+
deliverCaptured(applyDests.stdout, segOut, true);
|
|
508
|
+
deliverCaptured(applyDests.stderr, segErr, false);
|
|
519
509
|
if (inv.truncated)
|
|
520
510
|
truncated = true;
|
|
521
511
|
exitCode = inv.timedOut ? 124 : inv.cancelled ? 130 : inv.exitCode;
|
package/dist/registry.d.ts
CHANGED
|
@@ -99,6 +99,8 @@ export interface CommandSpec {
|
|
|
99
99
|
dispatch?: 'translated' | 'native' | 'dynamic';
|
|
100
100
|
/** GNU usage/syntax exit (grep uses 2; cp/mv/rm use 1). */
|
|
101
101
|
usageExit?: number;
|
|
102
|
+
/** First non-option operand ends option scanning (echo/printf). */
|
|
103
|
+
leadingOptions?: boolean;
|
|
102
104
|
handler: Handler;
|
|
103
105
|
}
|
|
104
106
|
/** Register a spec'd command. Unknown/unsupported options become GNU-style usage errors. */
|
package/dist/registry.js
CHANGED
|
@@ -197,7 +197,8 @@ export function specsMarkdown() {
|
|
|
197
197
|
for (const spec of registeredSpecs()) {
|
|
198
198
|
lines.push('## `' + spec.names.join('` / `') + '`');
|
|
199
199
|
lines.push('');
|
|
200
|
-
lines.push('Effects: ' +
|
|
200
|
+
lines.push('Effects: ' +
|
|
201
|
+
(spec.effects.length ? spec.effects.map((e) => '`' + e + '`').join(', ') : 'none'));
|
|
201
202
|
lines.push('');
|
|
202
203
|
if (!spec.options.length) {
|
|
203
204
|
lines.push('No options declared.');
|
|
@@ -319,6 +320,8 @@ export function specOptionError(spec, args, cmdName) {
|
|
|
319
320
|
i++;
|
|
320
321
|
continue;
|
|
321
322
|
}
|
|
323
|
+
if (spec.leadingOptions)
|
|
324
|
+
onlyOperands = true;
|
|
322
325
|
i++;
|
|
323
326
|
}
|
|
324
327
|
return null;
|
package/dist/translator.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { FauxnixParseError, isUnquotedLiteral, } from './ast.js';
|
|
2
2
|
import { parseCommand } from './parser.js';
|
|
3
3
|
import { lookup, psStr } from './registry.js';
|
|
4
|
+
import { PYTHON3_WINDOWS_HINT, SH_SCRIPT_WINDOWS_HINT } from './errors.js';
|
|
4
5
|
/* ------------------------------------------------------------------ */
|
|
5
6
|
/* Variable mapping */
|
|
6
7
|
/* ------------------------------------------------------------------ */
|
|
@@ -1216,8 +1217,9 @@ export function wrapScript(body, opts = {}) {
|
|
|
1216
1217
|
'}',
|
|
1217
1218
|
],
|
|
1218
1219
|
'fx-winargv': [
|
|
1219
|
-
'function fx-winargv($argv) {',
|
|
1220
|
+
'function fx-winargv($argv, $cmdmeta) {',
|
|
1220
1221
|
// Empty [object[]] unwraps to $null on PS 5.1; @($null) is one empty arg.
|
|
1222
|
+
// $cmdmeta: also quote & | () <> ^ so cmd.exe /c does not split the tail.
|
|
1221
1223
|
' if ($null -eq $argv) { $argv = @() }',
|
|
1222
1224
|
' $parts = New-Object System.Collections.Generic.List[string]',
|
|
1223
1225
|
' foreach ($a in @($argv)) {',
|
|
@@ -1226,6 +1228,7 @@ export function wrapScript(body, opts = {}) {
|
|
|
1226
1228
|
' $need = $false',
|
|
1227
1229
|
' foreach ($ch in $s.ToCharArray()) {',
|
|
1228
1230
|
" if ($ch -eq ' ' -or $ch -eq ([char]9) -or $ch -eq [char]34) { $need = $true; break }",
|
|
1231
|
+
" if ($cmdmeta -and ($ch -eq '&' -or $ch -eq '|' -or $ch -eq '(' -or $ch -eq ')' -or $ch -eq '<' -or $ch -eq '>' -or $ch -eq '^')) { $need = $true; break }",
|
|
1229
1232
|
' }',
|
|
1230
1233
|
' if (-not $need) { $parts.Add($s); continue }',
|
|
1231
1234
|
' $sb = New-Object System.Text.StringBuilder',
|
|
@@ -1259,7 +1262,14 @@ export function wrapScript(body, opts = {}) {
|
|
|
1259
1262
|
// operator is only for names that are not executables.
|
|
1260
1263
|
' $cmd = Get-Command -Name $name -ErrorAction SilentlyContinue | Select-Object -First 1',
|
|
1261
1264
|
' if ($null -eq $cmd) {',
|
|
1262
|
-
"
|
|
1265
|
+
" $fx_nf = 'bash: ' + $name + ': command not found'",
|
|
1266
|
+
" $fx_n = [string]$name",
|
|
1267
|
+
// Hint only — never alias python3→python (wrong interpreter).
|
|
1268
|
+
" if ($fx_n -eq 'python3' -or $fx_n -eq 'python3.exe') { $fx_nf += '" +
|
|
1269
|
+
PYTHON3_WINDOWS_HINT +
|
|
1270
|
+
"' }",
|
|
1271
|
+
" elseif ($fx_n -like '*.sh') { $fx_nf += '" + SH_SCRIPT_WINDOWS_HINT + "' }",
|
|
1272
|
+
' [Console]::Error.WriteLine($fx_nf)',
|
|
1263
1273
|
' $script:fx_exit = 127',
|
|
1264
1274
|
' return',
|
|
1265
1275
|
' }',
|
|
@@ -1272,6 +1282,8 @@ export function wrapScript(body, opts = {}) {
|
|
|
1272
1282
|
' $psi = New-Object System.Diagnostics.ProcessStartInfo',
|
|
1273
1283
|
' $ext = [IO.Path]::GetExtension([string]$app.Source)',
|
|
1274
1284
|
// CreateProcess cannot launch .cmd/.bat with UseShellExecute=false (npm.cmd).
|
|
1285
|
+
// /s strips one outer quote pair from the /c tail; CRT-quote cmd
|
|
1286
|
+
// metacharacters so `&`/`|`/`()` do not start a second command.
|
|
1275
1287
|
" if ($ext -eq '.cmd' -or $ext -eq '.bat') {",
|
|
1276
1288
|
' $comspec = Get-Command -Name cmd -CommandType Application -ErrorAction SilentlyContinue | Select-Object -First 1',
|
|
1277
1289
|
' if ($null -eq $comspec) {',
|
|
@@ -1280,8 +1292,10 @@ export function wrapScript(body, opts = {}) {
|
|
|
1280
1292
|
' return',
|
|
1281
1293
|
' }',
|
|
1282
1294
|
' $psi.FileName = $comspec.Source',
|
|
1283
|
-
' $
|
|
1284
|
-
|
|
1295
|
+
' $fx_app = fx-winargv $app.Source $true',
|
|
1296
|
+
' $fx_rest = fx-winargv $argv $true',
|
|
1297
|
+
" if ($fx_rest.Length -gt 0) { $fx_tail = $fx_app + ' ' + $fx_rest } else { $fx_tail = $fx_app }",
|
|
1298
|
+
' $psi.Arguments = \'/d /s /c "\' + $fx_tail + \'"\'',
|
|
1285
1299
|
' } else {',
|
|
1286
1300
|
' $psi.FileName = $app.Source',
|
|
1287
1301
|
' $psi.Arguments = fx-winargv $argv',
|
package/package.json
CHANGED