fauxnix-cli 0.11.0 → 0.12.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 +52 -28
- package/dist/cli.d.ts +2 -1
- package/dist/cli.js +61 -21
- package/dist/commands/archive.js +87 -24
- package/dist/commands/install-all.js +2 -1
- package/dist/commands/net.d.ts +9 -0
- package/dist/commands/net.js +51 -15
- package/dist/commands/sysinfo.d.ts +2 -1
- package/dist/commands/sysinfo.js +564 -72
- package/dist/commands/text-filters.js +36 -11
- package/dist/commands/text-io.js +72 -43
- package/dist/doctor.d.ts +1 -0
- package/dist/doctor.js +44 -3
- package/dist/errors.js +14 -4
- package/dist/executor.d.ts +4 -1
- package/dist/executor.js +194 -51
- package/dist/install.js +43 -2
- package/dist/mcp.d.ts +13 -0
- package/dist/mcp.js +15 -14
- package/dist/parser.js +11 -3
- package/dist/powershell.d.ts +22 -0
- package/dist/powershell.js +129 -0
- package/dist/ps-host.d.ts +41 -13
- package/dist/ps-host.js +302 -40
- package/dist/qwen-launch.d.ts +12 -0
- package/dist/qwen-launch.js +29 -0
- package/dist/registry.d.ts +22 -0
- package/dist/registry.js +109 -1
- package/dist/translator.d.ts +13 -6
- package/dist/translator.js +411 -86
- package/package.json +3 -1
package/dist/translator.js
CHANGED
|
@@ -1,7 +1,181 @@
|
|
|
1
|
-
import { FauxnixParseError, isUnquotedLiteral, } from './ast.js';
|
|
1
|
+
import { FauxnixParseError, isUnquotedLiteral, wordToString, } from './ast.js';
|
|
2
2
|
import { parseCommand } from './parser.js';
|
|
3
3
|
import { lookup, psStr } from './registry.js';
|
|
4
4
|
import { PYTHON3_WINDOWS_HINT, SH_SCRIPT_WINDOWS_HINT } from './errors.js';
|
|
5
|
+
export const EXECUTE_TRANSLATION = Object.freeze({ mode: 'execute' });
|
|
6
|
+
export const PURE_TRANSLATION = Object.freeze({ mode: 'pure' });
|
|
7
|
+
export const PURE_SED_FILE_MESSAGE = 'fauxnix: translate does not read sed script files; use -e with the script text, or run the command to use -f';
|
|
8
|
+
/** Match the sed option rules without opening the referenced script file. */
|
|
9
|
+
function sedUsesScriptFile(args) {
|
|
10
|
+
const raw = args.map((word) => wordToString(word));
|
|
11
|
+
let onlyOperands = false;
|
|
12
|
+
for (let i = 0; i < raw.length; i++) {
|
|
13
|
+
const arg = raw[i];
|
|
14
|
+
if (!onlyOperands && arg === '--') {
|
|
15
|
+
onlyOperands = true;
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
if (onlyOperands || !arg.startsWith('-') || arg.length === 1 || arg.startsWith('--')) {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
const body = arg.slice(1);
|
|
22
|
+
for (let c = 0; c < body.length; c++) {
|
|
23
|
+
const flag = body[c];
|
|
24
|
+
if (flag === 'f')
|
|
25
|
+
return c < body.length - 1 || i + 1 < raw.length;
|
|
26
|
+
if (flag === 'e') {
|
|
27
|
+
if (c === body.length - 1)
|
|
28
|
+
i++;
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
if (flag === 'i')
|
|
32
|
+
break;
|
|
33
|
+
if (!['n', 'E', 'r', 's', 'u', 'z'].includes(flag))
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
function assertPureWord(word) {
|
|
40
|
+
const visitPart = (part) => {
|
|
41
|
+
if (part.kind === 'CmdSub') {
|
|
42
|
+
assertPureCommandList(parseCommand(part.cmd));
|
|
43
|
+
}
|
|
44
|
+
else if (part.kind === 'DoubleQuoted' || part.kind === 'Arith') {
|
|
45
|
+
for (const nested of part.parts)
|
|
46
|
+
visitPart(nested);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
for (const part of word)
|
|
50
|
+
visitPart(part);
|
|
51
|
+
}
|
|
52
|
+
function wrappedSimpleCommand(command, name) {
|
|
53
|
+
const raw = command.args.map((word) => wordToString(word));
|
|
54
|
+
let commandIndex = -1;
|
|
55
|
+
if (name === 'env') {
|
|
56
|
+
for (let i = 0; i < raw.length;) {
|
|
57
|
+
const arg = raw[i];
|
|
58
|
+
if (arg === '--') {
|
|
59
|
+
commandIndex = i + 1;
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
if (arg === '-i' || arg === '--ignore-environment')
|
|
63
|
+
return null;
|
|
64
|
+
if (arg === '-u' || arg === '--unset') {
|
|
65
|
+
i += 2;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (arg.startsWith('-u=') || arg.startsWith('--unset=')) {
|
|
69
|
+
i++;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (arg.startsWith('-')) {
|
|
73
|
+
i++;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (/^[A-Za-z_][A-Za-z0-9_]*=/.test(arg)) {
|
|
77
|
+
i++;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
commandIndex = i;
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
else if (name === 'command') {
|
|
85
|
+
let identify = false;
|
|
86
|
+
let i = 0;
|
|
87
|
+
while (i < raw.length) {
|
|
88
|
+
const arg = raw[i];
|
|
89
|
+
if (arg === '-v' || arg === '-V') {
|
|
90
|
+
identify = true;
|
|
91
|
+
i++;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (arg === '--') {
|
|
95
|
+
i++;
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
if (arg.startsWith('-')) {
|
|
99
|
+
i++;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
if (identify)
|
|
105
|
+
return null;
|
|
106
|
+
commandIndex = i;
|
|
107
|
+
}
|
|
108
|
+
else if (name === 'timeout') {
|
|
109
|
+
let i = 0;
|
|
110
|
+
while (i < raw.length && raw[i].startsWith('-') && raw[i] !== '-' && raw[i] !== '--')
|
|
111
|
+
i++;
|
|
112
|
+
if (i < raw.length && raw[i] === '--')
|
|
113
|
+
i++;
|
|
114
|
+
commandIndex = i + 1;
|
|
115
|
+
}
|
|
116
|
+
if (commandIndex < 0 || commandIndex >= command.args.length)
|
|
117
|
+
return null;
|
|
118
|
+
return {
|
|
119
|
+
kind: 'SimpleCommand',
|
|
120
|
+
assignments: [],
|
|
121
|
+
name: command.args[commandIndex],
|
|
122
|
+
args: command.args.slice(commandIndex + 1),
|
|
123
|
+
redirects: [],
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function assertPureShellCommand(command) {
|
|
127
|
+
if (command.kind === 'SimpleCommand') {
|
|
128
|
+
const name = command.name === null ? null : literalOfWord(command.name);
|
|
129
|
+
if (name === 'sed' && sedUsesScriptFile(command.args)) {
|
|
130
|
+
throw new FauxnixParseError(PURE_SED_FILE_MESSAGE);
|
|
131
|
+
}
|
|
132
|
+
if (command.name)
|
|
133
|
+
assertPureWord(command.name);
|
|
134
|
+
for (const arg of command.args)
|
|
135
|
+
assertPureWord(arg);
|
|
136
|
+
for (const assignment of command.assignments) {
|
|
137
|
+
assertPureWord(assignment.value);
|
|
138
|
+
for (const value of assignment.values ?? [])
|
|
139
|
+
assertPureWord(value);
|
|
140
|
+
}
|
|
141
|
+
if (name === 'env' || name === 'command' || name === 'timeout') {
|
|
142
|
+
const nested = wrappedSimpleCommand(command, name);
|
|
143
|
+
if (nested)
|
|
144
|
+
assertPureShellCommand(nested);
|
|
145
|
+
}
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if (command.kind === 'If') {
|
|
149
|
+
assertPureCommandList(command.test);
|
|
150
|
+
assertPureCommandList(command.then);
|
|
151
|
+
if (command.else)
|
|
152
|
+
assertPureCommandList(command.else);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (command.kind === 'For') {
|
|
156
|
+
for (const word of command.words)
|
|
157
|
+
assertPureWord(word);
|
|
158
|
+
assertPureCommandList(command.body);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (command.kind === 'While') {
|
|
162
|
+
assertPureCommandList(command.test);
|
|
163
|
+
assertPureCommandList(command.body);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
assertPureWord(command.word);
|
|
167
|
+
for (const arm of command.arms) {
|
|
168
|
+
for (const pattern of arm.patterns)
|
|
169
|
+
assertPureWord(pattern);
|
|
170
|
+
assertPureCommandList(arm.body);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
function assertPureCommandList(list) {
|
|
174
|
+
for (const segment of list.segments) {
|
|
175
|
+
for (const command of segment.pipeline.commands)
|
|
176
|
+
assertPureShellCommand(command);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
5
179
|
/* ------------------------------------------------------------------ */
|
|
6
180
|
/* Variable mapping */
|
|
7
181
|
/* ------------------------------------------------------------------ */
|
|
@@ -421,8 +595,8 @@ export function argListExpr(words, fn = exprOfWord) {
|
|
|
421
595
|
* Lists (`;` `&&` `||`) reuse translateListInline inside the fx-csub
|
|
422
596
|
* scriptblock so the newline contract is unchanged.
|
|
423
597
|
*/
|
|
424
|
-
export function translateCmdSub(cmdText, keepNl = false) {
|
|
425
|
-
const inner = translateListInline(parseCommand(cmdText));
|
|
598
|
+
export function translateCmdSub(cmdText, keepNl = false, translation = EXECUTE_TRANSLATION) {
|
|
599
|
+
const inner = translateListInline(parseCommand(cmdText), translation);
|
|
426
600
|
const collected = '(fx-csub { ' + inner + ' })';
|
|
427
601
|
if (keepNl)
|
|
428
602
|
return collected;
|
|
@@ -439,7 +613,10 @@ function indentBlock(s) {
|
|
|
439
613
|
.map((l) => (l ? ' ' + l : l))
|
|
440
614
|
.join('\n');
|
|
441
615
|
}
|
|
442
|
-
export function translateSimple(cmd, position, hasStdin) {
|
|
616
|
+
export function translateSimple(cmd, position, hasStdin, translation = EXECUTE_TRANSLATION) {
|
|
617
|
+
const nativeTerm = "(-not $script:fx_csub -and (($MyInvocation.MyCommand.Name -eq '') -or " +
|
|
618
|
+
(position === 'last' ? '$true' : '$false') +
|
|
619
|
+
'))';
|
|
443
620
|
// assignment-only segment (`X=1; cmd`): bash semantics are "set for the
|
|
444
621
|
// rest of the shell". Reuse the export code path — persist + env shadow —
|
|
445
622
|
// so empty values (`X=`) and `[[ -v X ]]` behave like bash (documented
|
|
@@ -456,8 +633,9 @@ export function translateSimple(cmd, position, hasStdin) {
|
|
|
456
633
|
}
|
|
457
634
|
else {
|
|
458
635
|
const words = [[{ kind: 'Text', text: a.name + '=' }, ...a.value]];
|
|
459
|
-
if (exportHandler)
|
|
460
|
-
chunks.push(exportHandler(words, { position, hasStdin }));
|
|
636
|
+
if (exportHandler) {
|
|
637
|
+
chunks.push(exportHandler(words, { position, hasStdin, translationMode: translation.mode }));
|
|
638
|
+
}
|
|
461
639
|
}
|
|
462
640
|
}
|
|
463
641
|
return chunks.join('\n');
|
|
@@ -475,7 +653,7 @@ export function translateSimple(cmd, position, hasStdin) {
|
|
|
475
653
|
name: cmd.args[0],
|
|
476
654
|
args: cmd.args.slice(1),
|
|
477
655
|
redirects: cmd.redirects,
|
|
478
|
-
}, position, hasStdin);
|
|
656
|
+
}, position, hasStdin, translation);
|
|
479
657
|
const emptyCmdLines = [
|
|
480
658
|
'$fx_cw = @(' + splatLoadCall(nameSplat.name) + ')',
|
|
481
659
|
];
|
|
@@ -491,20 +669,23 @@ export function translateSimple(cmd, position, hasStdin) {
|
|
|
491
669
|
emptyCmdLines.push('if ($fx_cw.Count -eq 0) {',
|
|
492
670
|
// No words left → bash null command (exit 0). Remaining words are
|
|
493
671
|
// known at compile time, so reuse translateSimple (handlers, not `&`).
|
|
494
|
-
promoted ? indentBlock(promoted) : ' ', '} else {', ' $fx_na = [object[]](' + argListExpr(cmd.args) + ')', ' $fx_cmd = [string]$fx_cw[0]', ' if ($fx_cw.Count -gt 1) { $fx_na = [object[]](@($fx_cw[1..($fx_cw.Count - 1)]) + $fx_na) }', ' ' +
|
|
672
|
+
promoted ? indentBlock(promoted) : ' ', '} else {', ' $fx_na = [object[]](' + argListExpr(cmd.args) + ')', ' $fx_cmd = [string]$fx_cw[0]', ' if ($fx_cw.Count -gt 1) { $fx_na = [object[]](@($fx_cw[1..($fx_cw.Count - 1)]) + $fx_na) }', ' ' +
|
|
673
|
+
(hasStdin
|
|
674
|
+
? '($input | fx-native $fx_cmd $fx_na ' + nativeTerm + ')'
|
|
675
|
+
: 'fx-native $fx_cmd $fx_na ' + nativeTerm), '}');
|
|
495
676
|
body = emptyCmdLines.join('\n');
|
|
496
677
|
}
|
|
497
678
|
else if (nameLit !== null) {
|
|
498
679
|
const handler = lookup(nameLit);
|
|
499
680
|
if (handler && !(nameLit === '[[' && !isUnquotedLiteral(cmd.name, '[['))) {
|
|
500
|
-
body = handler(cmd.args, { position, hasStdin });
|
|
681
|
+
body = handler(cmd.args, { position, hasStdin, translationMode: translation.mode });
|
|
501
682
|
}
|
|
502
683
|
else {
|
|
503
684
|
// passthrough: native command (git, node, npm, python, cargo, ...)
|
|
504
685
|
// via fx-native (Win32 command line + Process). `& name @array` on
|
|
505
686
|
// PS 5.1 drops empty argv entries and eats embedded quotes.
|
|
506
687
|
const nameExpr = psStr(nameLit);
|
|
507
|
-
const invoke = 'fx-native ' + nameExpr + ' $fx_na';
|
|
688
|
+
const invoke = 'fx-native ' + nameExpr + ' $fx_na ' + nativeTerm;
|
|
508
689
|
body = [
|
|
509
690
|
'$fx_na = [object[]](' + argListExpr(cmd.args) + ')',
|
|
510
691
|
(hasStdin ? '($input | ' + invoke + ')' : invoke),
|
|
@@ -514,7 +695,7 @@ export function translateSimple(cmd, position, hasStdin) {
|
|
|
514
695
|
else {
|
|
515
696
|
// dynamic command name — evaluate it
|
|
516
697
|
const nameExpr = exprOfWord(cmd.name);
|
|
517
|
-
const invoke = 'fx-native (' + nameExpr + ') $fx_na';
|
|
698
|
+
const invoke = 'fx-native (' + nameExpr + ') $fx_na ' + nativeTerm;
|
|
518
699
|
body = [
|
|
519
700
|
'$fx_na = [object[]](' + argListExpr(cmd.args) + ')',
|
|
520
701
|
(hasStdin ? '($input | ' + invoke + ')' : invoke),
|
|
@@ -807,10 +988,10 @@ let pipelineSeq = 0;
|
|
|
807
988
|
* multi-command pipelines become generated functions chained with `|`
|
|
808
989
|
* (PS 5.1 forbids parenthesized expressions as non-first pipeline elements).
|
|
809
990
|
*/
|
|
810
|
-
function translateListInline(list) {
|
|
991
|
+
function translateListInline(list, translation = EXECUTE_TRANSLATION) {
|
|
811
992
|
const chunks = [];
|
|
812
993
|
for (const seg of list.segments) {
|
|
813
|
-
const { defs, call } = translatePipelineBody(seg.pipeline);
|
|
994
|
+
const { defs, call } = translatePipelineBody(seg.pipeline, translation);
|
|
814
995
|
const body = (defs ? defs + '\n' : '') + call;
|
|
815
996
|
if (seg.op === '&&') {
|
|
816
997
|
chunks.push('if ($script:fx_exit -eq 0) {\n' + body + '\n}');
|
|
@@ -824,29 +1005,29 @@ function translateListInline(list) {
|
|
|
824
1005
|
}
|
|
825
1006
|
return chunks.join('\n');
|
|
826
1007
|
}
|
|
827
|
-
function translateIf(cmd) {
|
|
1008
|
+
function translateIf(cmd, translation) {
|
|
828
1009
|
// Branch bodies reset fx_exit first: the compound's exit status must come
|
|
829
1010
|
// from the taken branch's last command (bash semantics), not leak the test's
|
|
830
1011
|
// failure — `if false; then A; else B; fi` exits 0 in bash.
|
|
831
|
-
const lines = [translateListInline(cmd.test), 'if ($script:fx_exit -eq 0) {', ' $script:fx_exit = 0'];
|
|
832
|
-
for (const l of translateListInline(cmd.then).split('\n'))
|
|
1012
|
+
const lines = [translateListInline(cmd.test, translation), 'if ($script:fx_exit -eq 0) {', ' $script:fx_exit = 0'];
|
|
1013
|
+
for (const l of translateListInline(cmd.then, translation).split('\n'))
|
|
833
1014
|
lines.push(l ? ' ' + l : l);
|
|
834
1015
|
lines.push('} else {', ' $script:fx_exit = 0');
|
|
835
1016
|
if (cmd.else) {
|
|
836
|
-
for (const l of translateListInline(cmd.else).split('\n'))
|
|
1017
|
+
for (const l of translateListInline(cmd.else, translation).split('\n'))
|
|
837
1018
|
lines.push(l ? ' ' + l : l);
|
|
838
1019
|
}
|
|
839
1020
|
lines.push('}');
|
|
840
1021
|
return lines.join('\n');
|
|
841
1022
|
}
|
|
842
|
-
function translateCase(cmd) {
|
|
1023
|
+
function translateCase(cmd, translation) {
|
|
843
1024
|
const lines = ['$script:fx_exit = 0', '$fx_cw = ' + exprOfWord(cmd.word)];
|
|
844
1025
|
for (let i = 0; i < cmd.arms.length; i++) {
|
|
845
1026
|
const arm = cmd.arms[i];
|
|
846
1027
|
const pats = arm.patterns.map((w) => exprOfWord(w)).join(',');
|
|
847
1028
|
const head = i === 0 ? 'if' : 'elseif';
|
|
848
1029
|
lines.push(head + ' (fx-casematch $fx_cw @(' + pats + ')) {');
|
|
849
|
-
const body = translateListInline(arm.body);
|
|
1030
|
+
const body = translateListInline(arm.body, translation);
|
|
850
1031
|
if (body) {
|
|
851
1032
|
for (const l of body.split('\n'))
|
|
852
1033
|
lines.push(l ? ' ' + l : l);
|
|
@@ -855,7 +1036,7 @@ function translateCase(cmd) {
|
|
|
855
1036
|
}
|
|
856
1037
|
return lines.join('\n');
|
|
857
1038
|
}
|
|
858
|
-
function translateFor(cmd) {
|
|
1039
|
+
function translateFor(cmd, translation) {
|
|
859
1040
|
const n = cmd.name.replace(/'/g, "''");
|
|
860
1041
|
const lines = [
|
|
861
1042
|
'$fx_for = ' + argListExpr(cmd.words),
|
|
@@ -877,22 +1058,22 @@ function translateFor(cmd) {
|
|
|
877
1058
|
n +
|
|
878
1059
|
"' + [string][char]61 + (fx-svenc ([string]$fx_it))); $env:FAUXNIX_SETVALS = ($fx_sv -join [string][char]10)",
|
|
879
1060
|
];
|
|
880
|
-
for (const l of translateListInline(cmd.body).split('\n'))
|
|
1061
|
+
for (const l of translateListInline(cmd.body, translation).split('\n'))
|
|
881
1062
|
lines.push(l ? ' ' + l : l);
|
|
882
1063
|
lines.push('}');
|
|
883
1064
|
return lines.join('\n');
|
|
884
1065
|
}
|
|
885
|
-
function translateWhile(cmd) {
|
|
1066
|
+
function translateWhile(cmd, translation) {
|
|
886
1067
|
// Bash: last executed body owns status; a test that ends the loop does not.
|
|
887
1068
|
// Never-entered loops (`while false; do …; done`, `until true; do …; done`)
|
|
888
1069
|
// exit 0. Save the body status and restore it on the failing test.
|
|
889
1070
|
const fail = cmd.until ? '$script:fx_exit -eq 0' : '$script:fx_exit -ne 0';
|
|
890
1071
|
const lines = ['$fx_wst = 0', 'do {'];
|
|
891
|
-
for (const l of translateListInline(cmd.test).split('\n'))
|
|
1072
|
+
for (const l of translateListInline(cmd.test, translation).split('\n'))
|
|
892
1073
|
lines.push(l ? ' ' + l : l);
|
|
893
1074
|
lines.push(' if (' + fail + ') { $script:fx_exit = $fx_wst; break }');
|
|
894
1075
|
lines.push(' $script:fx_exit = 0');
|
|
895
|
-
for (const l of translateListInline(cmd.body).split('\n'))
|
|
1076
|
+
for (const l of translateListInline(cmd.body, translation).split('\n'))
|
|
896
1077
|
lines.push(l ? ' ' + l : l);
|
|
897
1078
|
lines.push(' $fx_wst = $script:fx_exit');
|
|
898
1079
|
lines.push('} while ($true)');
|
|
@@ -917,19 +1098,41 @@ function isStdoutFileRedirect(op) {
|
|
|
917
1098
|
return op === '>' || op === '>>' || op === '&>' || op === '&>>';
|
|
918
1099
|
}
|
|
919
1100
|
const NONLAST_STDOUT_REDIRECT_MSG = 'fauxnix: stdout redirect on a non-last pipeline stage is not supported yet; write the file in a previous list segment (cmd >f; cat f) or wait for per-stage fds (#157)';
|
|
920
|
-
|
|
1101
|
+
const NONLAST_STDERR_FILE_REDIRECT_MSG = 'fauxnix: stderr redirect ({op}) on a non-last pipeline stage is not supported yet; spool the stage first (cmd >out {op}err; cat out | next) or wait for per-stage fds (#157)';
|
|
1102
|
+
function nonLastFdRedirectMessage(op) {
|
|
1103
|
+
if (isStdoutFileRedirect(op))
|
|
1104
|
+
return NONLAST_STDOUT_REDIRECT_MSG;
|
|
1105
|
+
if (op === '2>' || op === '2>>') {
|
|
1106
|
+
return NONLAST_STDERR_FILE_REDIRECT_MSG.replaceAll('{op}', op);
|
|
1107
|
+
}
|
|
1108
|
+
if (op === '2>&1') {
|
|
1109
|
+
return 'fauxnix: 2>&1 on a non-last pipeline stage is not supported yet; spool the merged output first (cmd >out 2>&1; cat out | next) or wait for per-stage fds (#157)';
|
|
1110
|
+
}
|
|
1111
|
+
if (op === '1>&2') {
|
|
1112
|
+
return 'fauxnix: 1>&2 on a non-last pipeline stage is not supported yet; run the stage separately (cmd 1>&2; next </dev/null) or wait for per-stage fds (#157)';
|
|
1113
|
+
}
|
|
1114
|
+
return null;
|
|
1115
|
+
}
|
|
1116
|
+
function rejectNonLastFdRedirects(commands) {
|
|
921
1117
|
if (commands.length < 2)
|
|
922
1118
|
return;
|
|
923
1119
|
for (let i = 0; i < commands.length - 1; i++) {
|
|
924
|
-
|
|
925
|
-
|
|
1120
|
+
for (const redirect of commands[i].redirects) {
|
|
1121
|
+
const message = nonLastFdRedirectMessage(redirect.op);
|
|
1122
|
+
if (message)
|
|
1123
|
+
throw new FauxnixParseError(message);
|
|
926
1124
|
}
|
|
927
1125
|
}
|
|
928
1126
|
}
|
|
929
|
-
export function translatePipelineBody(p) {
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
1127
|
+
export function translatePipelineBody(p, translation = EXECUTE_TRANSLATION) {
|
|
1128
|
+
if (translation.mode === 'pure') {
|
|
1129
|
+
for (const command of p.commands)
|
|
1130
|
+
assertPureShellCommand(command);
|
|
1131
|
+
}
|
|
1132
|
+
// Last-stage output fds are applied by Node. On an earlier stage they would
|
|
1133
|
+
// be prepared but not owned by that stage, so output would still reach the
|
|
1134
|
+
// wrong destination. Fail loud until routed in-stage fds land (#157).
|
|
1135
|
+
rejectNonLastFdRedirects(p.commands);
|
|
933
1136
|
// Every pipeline stage needs its own status slot. Handlers deliberately use
|
|
934
1137
|
// `$script:fx_exit` because their helper functions run in child scopes; in a
|
|
935
1138
|
// pipeline that shared flag lets an earlier failure leak into a successful
|
|
@@ -942,15 +1145,15 @@ export function translatePipelineBody(p) {
|
|
|
942
1145
|
const hasStdin = i > 0 || c.redirects.some((r) => r.op === '<');
|
|
943
1146
|
const position = i === 0 ? 'first' : i === p.commands.length - 1 ? 'last' : 'middle';
|
|
944
1147
|
if (c.kind === 'If')
|
|
945
|
-
bodies.push(translateIf(c));
|
|
1148
|
+
bodies.push(translateIf(c, translation));
|
|
946
1149
|
else if (c.kind === 'For')
|
|
947
|
-
bodies.push(translateFor(c));
|
|
1150
|
+
bodies.push(translateFor(c, translation));
|
|
948
1151
|
else if (c.kind === 'While')
|
|
949
|
-
bodies.push(translateWhile(c));
|
|
1152
|
+
bodies.push(translateWhile(c, translation));
|
|
950
1153
|
else if (c.kind === 'Case')
|
|
951
|
-
bodies.push(translateCase(c));
|
|
1154
|
+
bodies.push(translateCase(c, translation));
|
|
952
1155
|
else
|
|
953
|
-
bodies.push(translateSimple(c, position, hasStdin));
|
|
1156
|
+
bodies.push(translateSimple(c, position, hasStdin, translation));
|
|
954
1157
|
}
|
|
955
1158
|
if (bodies.length === 1) {
|
|
956
1159
|
return { defs: '', call: '(& {\n' + bodies[0] + '\n})' };
|
|
@@ -1009,7 +1212,7 @@ export function translatePipelineBody(p) {
|
|
|
1009
1212
|
].join('\n'));
|
|
1010
1213
|
return { defs: defs.join('\n'), call: pipelineName };
|
|
1011
1214
|
}
|
|
1012
|
-
export function translateCommandList(list) {
|
|
1215
|
+
export function translateCommandList(list, translation = EXECUTE_TRANSLATION) {
|
|
1013
1216
|
const plans = [];
|
|
1014
1217
|
for (const seg of list.segments) {
|
|
1015
1218
|
const cmds = seg.pipeline.commands;
|
|
@@ -1020,7 +1223,7 @@ export function translateCommandList(list) {
|
|
|
1020
1223
|
const stdinRedirects = cmds.length
|
|
1021
1224
|
? cmds[0].redirects.filter((r) => r.op === '<')
|
|
1022
1225
|
: [];
|
|
1023
|
-
const { defs, call } = translatePipelineBody(seg.pipeline);
|
|
1226
|
+
const { defs, call } = translatePipelineBody(seg.pipeline, translation);
|
|
1024
1227
|
let body = defs ? defs + '\n' + call : call;
|
|
1025
1228
|
// First-stage `< file` feeds stage zero via FAUXNIX_STDIN_FILE.
|
|
1026
1229
|
// Later-stage `<` is owned inside the pipeline body, not this wrapper.
|
|
@@ -1070,6 +1273,7 @@ const WRAP_HELPER_ORDER = [
|
|
|
1070
1273
|
'fx-subst',
|
|
1071
1274
|
'fx-slice',
|
|
1072
1275
|
'fx-winargv',
|
|
1276
|
+
'fx-cmdargv',
|
|
1073
1277
|
'fx-native',
|
|
1074
1278
|
];
|
|
1075
1279
|
const WRAP_HELPER_DEPS = {
|
|
@@ -1095,7 +1299,8 @@ const WRAP_HELPER_DEPS = {
|
|
|
1095
1299
|
'fx-subst': [],
|
|
1096
1300
|
'fx-slice': [],
|
|
1097
1301
|
'fx-winargv': [],
|
|
1098
|
-
'fx-
|
|
1302
|
+
'fx-cmdargv': ['fx-winargv'],
|
|
1303
|
+
'fx-native': ['fx-cmdargv'],
|
|
1099
1304
|
};
|
|
1100
1305
|
/** Helpers the body calls that wrapScript still has to emit (not already defined there). */
|
|
1101
1306
|
function wrapHelpersNeeded(body) {
|
|
@@ -1529,8 +1734,48 @@ export function wrapScript(body, opts = {}) {
|
|
|
1529
1734
|
" return (($parts.ToArray()) -join ' ')",
|
|
1530
1735
|
'}',
|
|
1531
1736
|
],
|
|
1737
|
+
'fx-cmdargv': [
|
|
1738
|
+
'function fx-cmdargv($argv) {',
|
|
1739
|
+
// cmd.exe performs percent expansion even inside quotes, and embedded
|
|
1740
|
+
// quotes can reopen its metacharacter grammar. CR/LF/NUL cannot be
|
|
1741
|
+
// represented as one batch argument. Reject those values instead of
|
|
1742
|
+
// silently handing a different argv to the shim.
|
|
1743
|
+
' if ($null -eq $argv) { $argv = @() }',
|
|
1744
|
+
' foreach ($a in @($argv)) {',
|
|
1745
|
+
' $s = [string]$a',
|
|
1746
|
+
" if ($s.IndexOf('%') -ge 0) { throw \"fauxnix: cannot pass '%' to a .cmd/.bat file without changing the argument; invoke the underlying executable directly\" }",
|
|
1747
|
+
' if ($s.IndexOf([char]34) -ge 0) { throw \'fauxnix: cannot pass a double quote to a .cmd/.bat file without changing the argument; invoke the underlying executable directly\' }',
|
|
1748
|
+
' if ($s.IndexOf([char]13) -ge 0 -or $s.IndexOf([char]10) -ge 0) { throw \'fauxnix: cannot pass a line break to a .cmd/.bat file as one argument; invoke the underlying executable directly\' }',
|
|
1749
|
+
' if ($s.IndexOf([char]0) -ge 0) { throw \'fauxnix: cannot pass NUL to a .cmd/.bat file as one argument; invoke the underlying executable directly\' }',
|
|
1750
|
+
' }',
|
|
1751
|
+
' return (fx-winargv $argv $true)',
|
|
1752
|
+
'}',
|
|
1753
|
+
],
|
|
1532
1754
|
'fx-native': [
|
|
1533
|
-
|
|
1755
|
+
"if (-not ('FauxnixTextPump' -as [type])) {",
|
|
1756
|
+
" Add-Type -TypeDefinition @'",
|
|
1757
|
+
'using System;',
|
|
1758
|
+
'using System.IO;',
|
|
1759
|
+
'using System.Text;',
|
|
1760
|
+
'using System.Threading.Tasks;',
|
|
1761
|
+
'public static class FauxnixTextPump {',
|
|
1762
|
+
' public static async Task CopyAsync(TextReader reader, TextWriter writer) {',
|
|
1763
|
+
' var buffer = new char[4096];',
|
|
1764
|
+
' int read;',
|
|
1765
|
+
' while ((read = await reader.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false)) > 0) {',
|
|
1766
|
+
' await writer.WriteAsync(buffer, 0, read).ConfigureAwait(false);',
|
|
1767
|
+
' }',
|
|
1768
|
+
' await writer.FlushAsync().ConfigureAwait(false);',
|
|
1769
|
+
' }',
|
|
1770
|
+
' public static async Task CopyFileAsync(TextReader reader, string path) {',
|
|
1771
|
+
' using (var writer = new StreamWriter(path, false, new UTF8Encoding(false))) {',
|
|
1772
|
+
' await CopyAsync(reader, writer).ConfigureAwait(false);',
|
|
1773
|
+
' }',
|
|
1774
|
+
' }',
|
|
1775
|
+
'}',
|
|
1776
|
+
"'@",
|
|
1777
|
+
'}',
|
|
1778
|
+
'function fx-native($name, $argv, $term) {',
|
|
1534
1779
|
' if ($null -eq $argv) { $argv = @() } else { $argv = [object[]]@($argv) }',
|
|
1535
1780
|
' $app = Get-Command -Name $name -CommandType Application -ErrorAction SilentlyContinue | Select-Object -First 1',
|
|
1536
1781
|
' if ($null -eq $app) {',
|
|
@@ -1559,8 +1804,8 @@ export function wrapScript(body, opts = {}) {
|
|
|
1559
1804
|
' $psi = New-Object System.Diagnostics.ProcessStartInfo',
|
|
1560
1805
|
' $ext = [IO.Path]::GetExtension([string]$app.Source)',
|
|
1561
1806
|
// CreateProcess cannot launch .cmd/.bat with UseShellExecute=false (npm.cmd).
|
|
1562
|
-
// /s strips one outer quote pair from the /c tail
|
|
1563
|
-
//
|
|
1807
|
+
// /s strips one outer quote pair from the /c tail. Build only the
|
|
1808
|
+
// subset of batch argv that cmd.exe can pass through unchanged.
|
|
1564
1809
|
" if ($ext -eq '.cmd' -or $ext -eq '.bat') {",
|
|
1565
1810
|
' $comspec = Get-Command -Name cmd -CommandType Application -ErrorAction SilentlyContinue | Select-Object -First 1',
|
|
1566
1811
|
' if ($null -eq $comspec) {',
|
|
@@ -1569,10 +1814,16 @@ export function wrapScript(body, opts = {}) {
|
|
|
1569
1814
|
' return',
|
|
1570
1815
|
' }',
|
|
1571
1816
|
' $psi.FileName = $comspec.Source',
|
|
1572
|
-
'
|
|
1573
|
-
'
|
|
1817
|
+
' try {',
|
|
1818
|
+
' $fx_app = fx-cmdargv $app.Source',
|
|
1819
|
+
' $fx_rest = fx-cmdargv $argv',
|
|
1820
|
+
' } catch {',
|
|
1821
|
+
// Let the common wrapper report the validation error and stop the
|
|
1822
|
+
// current pipeline. Returning here would let xargs mask the failure.
|
|
1823
|
+
' throw $_.Exception',
|
|
1824
|
+
' }',
|
|
1574
1825
|
" if ($fx_rest.Length -gt 0) { $fx_tail = $fx_app + ' ' + $fx_rest } else { $fx_tail = $fx_app }",
|
|
1575
|
-
' $psi.Arguments = \'/d /s /c "\' + $fx_tail + \'"\'',
|
|
1826
|
+
' $psi.Arguments = \'/d /s /v:off /c "\' + $fx_tail + \'"\'',
|
|
1576
1827
|
' } else {',
|
|
1577
1828
|
' $psi.FileName = $app.Source',
|
|
1578
1829
|
' $psi.Arguments = fx-winargv $argv',
|
|
@@ -1583,35 +1834,40 @@ export function wrapScript(body, opts = {}) {
|
|
|
1583
1834
|
' $psi.RedirectStandardError = $true',
|
|
1584
1835
|
' $psi.CreateNoWindow = $true',
|
|
1585
1836
|
' $psi.WorkingDirectory = [Environment]::CurrentDirectory',
|
|
1586
|
-
//
|
|
1587
|
-
//
|
|
1837
|
+
// Drain both child pipes concurrently into disk-backed spools before
|
|
1838
|
+
// replaying them. This prevents either 64KB OS pipe from blocking the
|
|
1839
|
+
// child and avoids retaining the complete output in a .NET string.
|
|
1588
1840
|
" if ($env:FAUXNIX_NATIVE_ENCODING -eq 'ansi') { $enc = [System.Text.Encoding]::GetEncoding(936) } else { $enc = New-Object System.Text.UTF8Encoding $false }",
|
|
1589
1841
|
' $psi.StandardOutputEncoding = $enc',
|
|
1590
1842
|
' $psi.StandardErrorEncoding = $enc',
|
|
1591
1843
|
' $p = New-Object System.Diagnostics.Process',
|
|
1592
1844
|
' $p.StartInfo = $psi',
|
|
1593
|
-
'
|
|
1594
|
-
' $
|
|
1595
|
-
'
|
|
1596
|
-
'
|
|
1597
|
-
|
|
1598
|
-
'
|
|
1599
|
-
'
|
|
1600
|
-
'
|
|
1601
|
-
'
|
|
1602
|
-
'
|
|
1603
|
-
'
|
|
1604
|
-
'
|
|
1605
|
-
'
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
'
|
|
1609
|
-
|
|
1610
|
-
'
|
|
1845
|
+
' $fx_no = $null',
|
|
1846
|
+
' $fx_spoolUtf8 = New-Object System.Text.UTF8Encoding $false',
|
|
1847
|
+
' try {',
|
|
1848
|
+
' if (-not $term) {',
|
|
1849
|
+
" if ($env:FAUXNIX_NATIVE_SPOOL_DIR) { $fx_no = Join-Path $env:FAUXNIX_NATIVE_SPOOL_DIR (([guid]::NewGuid().ToString('N')) + '.out') }",
|
|
1850
|
+
' else { $fx_no = [IO.Path]::GetTempFileName() }',
|
|
1851
|
+
' }',
|
|
1852
|
+
' [void]$p.Start()',
|
|
1853
|
+
' if ($term) { $outTask = [FauxnixTextPump]::CopyAsync($p.StandardOutput, [Console]::Out) }',
|
|
1854
|
+
' else { $outTask = [FauxnixTextPump]::CopyFileAsync($p.StandardOutput, $fx_no) }',
|
|
1855
|
+
' $errTask = [FauxnixTextPump]::CopyAsync($p.StandardError, [Console]::Error)',
|
|
1856
|
+
' foreach ($fx_ln in $input) { $p.StandardInput.WriteLine([string]$fx_ln) }',
|
|
1857
|
+
' $p.StandardInput.Close()',
|
|
1858
|
+
' [void][System.Threading.Tasks.Task]::WaitAll(@($outTask, $errTask))',
|
|
1859
|
+
' [void]$p.WaitForExit()',
|
|
1860
|
+
' if (-not $term) {',
|
|
1861
|
+
' $fx_or = New-Object System.IO.StreamReader($fx_no, $fx_spoolUtf8)',
|
|
1862
|
+
' try { while (($fx_line = $fx_or.ReadLine()) -ne $null) { $fx_line } }',
|
|
1863
|
+
' finally { $fx_or.Dispose() }',
|
|
1864
|
+
' }',
|
|
1865
|
+
' $code = [int]$p.ExitCode',
|
|
1866
|
+
' if ($code -gt 0) { $script:fx_exit = $code } elseif ($code -lt 0) { $script:fx_exit = 1 }',
|
|
1867
|
+
' } finally {',
|
|
1868
|
+
' try { $p.Close() } catch {}',
|
|
1869
|
+
' if ($null -ne $fx_no) { Remove-Item -LiteralPath $fx_no -Force -ErrorAction SilentlyContinue }',
|
|
1611
1870
|
' }',
|
|
1612
|
-
' $code = [int]$p.ExitCode',
|
|
1613
|
-
' if ($code -gt 0) { $script:fx_exit = $code } elseif ($code -lt 0) { $script:fx_exit = 1 }',
|
|
1614
|
-
' try { $p.Close() } catch {}',
|
|
1615
1871
|
'}',
|
|
1616
1872
|
],
|
|
1617
1873
|
};
|
|
@@ -1631,7 +1887,7 @@ function wrapHelperCatalog() {
|
|
|
1631
1887
|
}
|
|
1632
1888
|
/**
|
|
1633
1889
|
* Resident-host bootstrap: encoding + full fx-* catalog + JSON-line RPC loop.
|
|
1634
|
-
* Loaded once via
|
|
1890
|
+
* Loaded once via the selected PowerShell's `-File`. Must never `exit` a successful frame.
|
|
1635
1891
|
*/
|
|
1636
1892
|
export function hostBootstrapScript() {
|
|
1637
1893
|
const helpers = wrapHelperCatalog();
|
|
@@ -1647,6 +1903,46 @@ export function hostBootstrapScript() {
|
|
|
1647
1903
|
}
|
|
1648
1904
|
/** Raw UTF-8 JSON lines on stdin/stdout; command streams captured per frame. */
|
|
1649
1905
|
const HOST_RPC_LOOP = `
|
|
1906
|
+
if (-not ('FauxnixBoundedStream' -as [type])) {
|
|
1907
|
+
Add-Type -TypeDefinition @'
|
|
1908
|
+
using System;
|
|
1909
|
+
using System.IO;
|
|
1910
|
+
public sealed class FauxnixBoundedStream : Stream {
|
|
1911
|
+
private readonly MemoryStream inner;
|
|
1912
|
+
private readonly long limit;
|
|
1913
|
+
private readonly long storageLimit;
|
|
1914
|
+
private long totalWritten;
|
|
1915
|
+
public bool Truncated { get; private set; }
|
|
1916
|
+
public FauxnixBoundedStream(long limit) {
|
|
1917
|
+
this.limit = Math.Max(0, limit);
|
|
1918
|
+
this.storageLimit = this.limit + 3;
|
|
1919
|
+
this.inner = new MemoryStream((int)Math.Min(this.storageLimit, 65536));
|
|
1920
|
+
}
|
|
1921
|
+
public byte[] ToArray() { return inner.ToArray(); }
|
|
1922
|
+
public override bool CanRead { get { return false; } }
|
|
1923
|
+
public override bool CanSeek { get { return false; } }
|
|
1924
|
+
public override bool CanWrite { get { return true; } }
|
|
1925
|
+
public override long Length { get { return inner.Length; } }
|
|
1926
|
+
public override long Position { get { return inner.Position; } set { throw new NotSupportedException(); } }
|
|
1927
|
+
public override void Flush() { }
|
|
1928
|
+
public override int Read(byte[] buffer, int offset, int count) { throw new NotSupportedException(); }
|
|
1929
|
+
public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); }
|
|
1930
|
+
public override void SetLength(long value) { throw new NotSupportedException(); }
|
|
1931
|
+
public override void Write(byte[] buffer, int offset, int count) {
|
|
1932
|
+
long remaining = Math.Max(0, storageLimit - inner.Length);
|
|
1933
|
+
int keep = (int)Math.Min((long)count, remaining);
|
|
1934
|
+
if (keep > 0) inner.Write(buffer, offset, keep);
|
|
1935
|
+
totalWritten += count;
|
|
1936
|
+
if (totalWritten > limit) Truncated = true;
|
|
1937
|
+
}
|
|
1938
|
+
public override void WriteByte(byte value) {
|
|
1939
|
+
if (inner.Length < storageLimit) inner.WriteByte(value);
|
|
1940
|
+
totalWritten++;
|
|
1941
|
+
if (totalWritten > limit) Truncated = true;
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
'@
|
|
1945
|
+
}
|
|
1650
1946
|
$fx_utf8 = New-Object System.Text.UTF8Encoding $false
|
|
1651
1947
|
$fx_in = [Console]::OpenStandardInput()
|
|
1652
1948
|
$fx_out = [Console]::OpenStandardOutput()
|
|
@@ -1666,8 +1962,8 @@ function fx-emit-chunks($type, $id, [byte[]]$bytes, $limit, [ref]$seq) {
|
|
|
1666
1962
|
if ($null -ne $bytes) { $n = $bytes.Length }
|
|
1667
1963
|
$use = $n
|
|
1668
1964
|
$trunc = $false
|
|
1669
|
-
# limit
|
|
1670
|
-
if ($limit -
|
|
1965
|
+
# limit -1 = uncapped; zero is a real empty caller budget
|
|
1966
|
+
if ($limit -ge 0 -and $use -gt $limit) {
|
|
1671
1967
|
$use = $limit
|
|
1672
1968
|
$trunc = $true
|
|
1673
1969
|
# back the cut off to a valid UTF-8 boundary — a split codepoint makes
|
|
@@ -1693,6 +1989,18 @@ function fx-emit-chunks($type, $id, [byte[]]$bytes, $limit, [ref]$seq) {
|
|
|
1693
1989
|
}
|
|
1694
1990
|
return $trunc
|
|
1695
1991
|
}
|
|
1992
|
+
function fx-new-capture($mode, $limit, $spoolPath) {
|
|
1993
|
+
if ([string]$mode -eq 'discard') { return [System.IO.Stream]::Null }
|
|
1994
|
+
if ([string]$mode -eq 'spool') {
|
|
1995
|
+
return (New-Object System.IO.FileStream([string]$spoolPath, [System.IO.FileMode]::Create, [System.IO.FileAccess]::Write, [System.IO.FileShare]::Read))
|
|
1996
|
+
}
|
|
1997
|
+
return (New-Object FauxnixBoundedStream ([Math]::Max(0, [long]$limit)))
|
|
1998
|
+
}
|
|
1999
|
+
function fx-capture-bytes($stream) {
|
|
2000
|
+
if ($stream -is [FauxnixBoundedStream]) { return $stream.ToArray() }
|
|
2001
|
+
if ($stream -is [System.IO.MemoryStream]) { return $stream.ToArray() }
|
|
2002
|
+
return (New-Object byte[] 0)
|
|
2003
|
+
}
|
|
1696
2004
|
while ($true) {
|
|
1697
2005
|
$fx_line = $fx_reader.ReadLine()
|
|
1698
2006
|
if ($null -eq $fx_line) { break }
|
|
@@ -1720,12 +2028,24 @@ while ($true) {
|
|
|
1720
2028
|
}
|
|
1721
2029
|
}
|
|
1722
2030
|
$fx_script = [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String([string]$fx_req.scriptB64))
|
|
1723
|
-
$
|
|
1724
|
-
$
|
|
2031
|
+
$fx_outLimit = 8388608
|
|
2032
|
+
$fx_errLimit = 1048576
|
|
2033
|
+
if ($null -ne $fx_req.PSObject.Properties['stdoutLimit']) { $fx_outLimit = [int]$fx_req.stdoutLimit }
|
|
2034
|
+
if ($null -ne $fx_req.PSObject.Properties['stderrLimit']) { $fx_errLimit = [int]$fx_req.stderrLimit }
|
|
2035
|
+
$fx_outMode = 'capture'
|
|
2036
|
+
$fx_errMode = 'capture'
|
|
2037
|
+
$fx_outSpool = ''
|
|
2038
|
+
$fx_errSpool = ''
|
|
2039
|
+
if ($null -ne $fx_req.PSObject.Properties['stdoutMode']) { $fx_outMode = [string]$fx_req.stdoutMode }
|
|
2040
|
+
if ($null -ne $fx_req.PSObject.Properties['stderrMode']) { $fx_errMode = [string]$fx_req.stderrMode }
|
|
2041
|
+
if ($null -ne $fx_req.PSObject.Properties['stdoutSpoolPath']) { $fx_outSpool = [string]$fx_req.stdoutSpoolPath }
|
|
2042
|
+
if ($null -ne $fx_req.PSObject.Properties['stderrSpoolPath']) { $fx_errSpool = [string]$fx_req.stderrSpoolPath }
|
|
2043
|
+
$fx_msOut = fx-new-capture $fx_outMode $fx_outLimit $fx_outSpool
|
|
2044
|
+
$fx_msErr = fx-new-capture $fx_errMode $fx_errLimit $fx_errSpool
|
|
1725
2045
|
$fx_outW = New-Object System.IO.StreamWriter($fx_msOut, $fx_utf8, 1024, $true)
|
|
1726
2046
|
$fx_errW = New-Object System.IO.StreamWriter($fx_msErr, $fx_utf8, 1024, $true)
|
|
1727
|
-
$fx_outW.NewLine = [string][char]
|
|
1728
|
-
$fx_errW.NewLine = [string][char]
|
|
2047
|
+
$fx_outW.NewLine = [string][char]10
|
|
2048
|
+
$fx_errW.NewLine = [string][char]10
|
|
1729
2049
|
$fx_outW.AutoFlush = $true
|
|
1730
2050
|
$fx_errW.AutoFlush = $true
|
|
1731
2051
|
[Console]::SetOut($fx_outW)
|
|
@@ -1753,24 +2073,29 @@ while ($true) {
|
|
|
1753
2073
|
}
|
|
1754
2074
|
$fx_outBytes = New-Object byte[] 0
|
|
1755
2075
|
$fx_errBytes = New-Object byte[] 0
|
|
1756
|
-
if ($null -ne $fx_msOut) { $fx_outBytes = $fx_msOut
|
|
1757
|
-
if ($null -ne $fx_msErr) { $fx_errBytes = $fx_msErr
|
|
2076
|
+
if ($null -ne $fx_msOut) { $fx_outBytes = fx-capture-bytes $fx_msOut }
|
|
2077
|
+
if ($null -ne $fx_msErr) { $fx_errBytes = fx-capture-bytes $fx_msErr }
|
|
2078
|
+
try { if ($null -ne $fx_outW) { $fx_outW.Dispose() } } catch {}
|
|
2079
|
+
try { if ($null -ne $fx_errW) { $fx_errW.Dispose() } } catch {}
|
|
2080
|
+
try { if ($null -ne $fx_msOut -and $fx_msOut -ne [System.IO.Stream]::Null) { $fx_msOut.Dispose() } } catch {}
|
|
2081
|
+
try { if ($null -ne $fx_msErr -and $fx_msErr -ne [System.IO.Stream]::Null) { $fx_msErr.Dispose() } } catch {}
|
|
1758
2082
|
if ($fx_v2) {
|
|
1759
|
-
$fx_outLimit = 8388608
|
|
1760
|
-
$fx_errLimit = 1048576
|
|
1761
|
-
if ($null -ne $fx_req.PSObject.Properties['stdoutLimit']) { $fx_outLimit = [int]$fx_req.stdoutLimit }
|
|
1762
|
-
if ($null -ne $fx_req.PSObject.Properties['stderrLimit']) { $fx_errLimit = [int]$fx_req.stderrLimit }
|
|
1763
2083
|
$fx_outSeq = 0
|
|
1764
2084
|
$fx_errSeq = 0
|
|
1765
|
-
$
|
|
1766
|
-
|
|
1767
|
-
if (
|
|
2085
|
+
$fx_outTrunc = $false
|
|
2086
|
+
$fx_errTrunc = $false
|
|
2087
|
+
if ($fx_msOut -is [FauxnixBoundedStream] -and $fx_msOut.Truncated) { $fx_outTrunc = $true }
|
|
2088
|
+
if ($fx_msErr -is [FauxnixBoundedStream] -and $fx_msErr.Truncated) { $fx_errTrunc = $true }
|
|
2089
|
+
$fx_outEmitLimit = $(if ($fx_outMode -eq 'capture') { $fx_outLimit } else { -1 })
|
|
2090
|
+
$fx_errEmitLimit = $(if ($fx_errMode -eq 'capture') { $fx_errLimit } else { -1 })
|
|
2091
|
+
if (fx-emit-chunks 'stdout' $fx_id $fx_outBytes $fx_outEmitLimit ([ref]$fx_outSeq)) { $fx_outTrunc = $true }
|
|
2092
|
+
if (fx-emit-chunks 'stderr' $fx_id $fx_errBytes $fx_errEmitLimit ([ref]$fx_errSeq)) { $fx_errTrunc = $true }
|
|
1768
2093
|
$fx_nativeErr = [Console]::OpenStandardError()
|
|
1769
2094
|
$fx_mark = $fx_utf8.GetBytes(('FAUXNIX_ERR_END:' + $fx_id + [char]10))
|
|
1770
2095
|
$fx_nativeErr.Write($fx_mark, 0, $fx_mark.Length)
|
|
1771
2096
|
$fx_nativeErr.Flush()
|
|
1772
|
-
$
|
|
1773
|
-
|
|
2097
|
+
$fx_trunc = $fx_outTrunc -or $fx_errTrunc
|
|
2098
|
+
$fx_end = '{"v":2,"type":"end","id":"' + $fx_id + '","exitCode":' + $fx_code + ',"timedOut":false,"cancelled":false,"truncated":' + ([string]$fx_trunc).ToLowerInvariant() + ',"stdoutTruncated":' + ([string]$fx_outTrunc).ToLowerInvariant() + ',"stderrTruncated":' + ([string]$fx_errTrunc).ToLowerInvariant() + '}'
|
|
1774
2099
|
$fx_proto.WriteLine($fx_end)
|
|
1775
2100
|
} else {
|
|
1776
2101
|
$fx_outB64 = ''
|