fauxnix-cli 0.5.0 → 0.5.1
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 +2 -1
- package/dist/ast.d.ts +2 -1
- package/dist/ast.js +2 -1
- package/dist/mcp.js +12 -12
- package/dist/parser.js +6 -0
- package/dist/translator.d.ts +2 -0
- package/dist/translator.js +246 -169
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -193,7 +193,8 @@ fauxnix optimizes for the commands agents actually run. Documented deviations:
|
|
|
193
193
|
word expansion precedes the temporary environment).
|
|
194
194
|
- `yes` is capped at 65,536 lines — PS 5.1 pipelines cannot signal upstream producers to stop, so
|
|
195
195
|
an unbounded `yes | head` would hang.
|
|
196
|
-
- `tail -f`, `eval`, `alias`, heredocs, `while`/`until`/`case
|
|
196
|
+
- `tail -f`, `eval`, `alias`, heredocs, `while`/`until`/`case`, word-level
|
|
197
|
+
`$((...))` arithmetic expansion
|
|
197
198
|
and background `&` are rejected with actionable error messages instead of misbehaving.
|
|
198
199
|
(`if/then/else/fi`, `for x in ...`, backtick substitution, `command -v`, pipeline `read`,
|
|
199
200
|
and dotenv-style `source` are supported.)
|
package/dist/ast.d.ts
CHANGED
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
*
|
|
13
13
|
* Explicitly unsupported (parser throws a helpful FauxnixError):
|
|
14
14
|
* heredocs, subshells (...), background &, while/until/case,
|
|
15
|
-
* globs inside quotes,
|
|
15
|
+
* word-level $((...)) arithmetic expansion, globs inside quotes,
|
|
16
|
+
* process substitution <(...).
|
|
16
17
|
*/
|
|
17
18
|
export interface CommandList {
|
|
18
19
|
kind: 'CommandList';
|
package/dist/ast.js
CHANGED
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
*
|
|
13
13
|
* Explicitly unsupported (parser throws a helpful FauxnixError):
|
|
14
14
|
* heredocs, subshells (...), background &, while/until/case,
|
|
15
|
-
* globs inside quotes,
|
|
15
|
+
* word-level $((...)) arithmetic expansion, globs inside quotes,
|
|
16
|
+
* process substitution <(...).
|
|
16
17
|
*/
|
|
17
18
|
export function wordToString(w) {
|
|
18
19
|
return w.map(partToString).join('');
|
package/dist/mcp.js
CHANGED
|
@@ -30,18 +30,18 @@ const SESSION_ANNOTATIONS = {
|
|
|
30
30
|
idempotentHint: true,
|
|
31
31
|
openWorldHint: false,
|
|
32
32
|
};
|
|
33
|
-
const TOOL_DESCRIPTION = `Execute a Linux/bash-style command on this Windows machine.
|
|
34
|
-
|
|
35
|
-
Commands are deterministically translated to PowerShell and executed natively — no WSL or VM.
|
|
36
|
-
Output is formatted to look like GNU/Linux tooling (ls -l, ps aux, df -h ...), errors look like bash errors, and text encoding (UTF-8/GBK) is handled automatically.
|
|
37
|
-
|
|
38
|
-
Supported: pipes (|), && / || / ;, redirections (> >> 2> 2>&1 < /dev/null), variables ($VAR $HOME ~), command substitution $(...), and ${registeredNames().length}+ coreutils-style commands (${registeredNames().slice(0, 18).join(', ')}...).
|
|
39
|
-
Unknown commands (git, node, npm, python, cargo...) are passed through and executed natively with argv-style quoting.
|
|
40
|
-
Not supported: heredocs, while/until/case, background jobs. if/then/else/fi and for-in loops are supported.
|
|
41
|
-
|
|
42
|
-
CWD, environment variables, export/unset and cd persist across calls within this session — but prefer COMBINING related commands in one call with ; or && (e.g. 'cd src && ls | wc -l'); each call is a fresh translation+process, so batching is faster than many tiny calls.
|
|
43
|
-
Exit codes follow bash conventions (0 ok, 1 fail, 2 usage/serious, 127 command not found, 124 timeout).
|
|
44
|
-
|
|
33
|
+
const TOOL_DESCRIPTION = `Execute a Linux/bash-style command on this Windows machine.
|
|
34
|
+
|
|
35
|
+
Commands are deterministically translated to PowerShell and executed natively — no WSL or VM.
|
|
36
|
+
Output is formatted to look like GNU/Linux tooling (ls -l, ps aux, df -h ...), errors look like bash errors, and text encoding (UTF-8/GBK) is handled automatically.
|
|
37
|
+
|
|
38
|
+
Supported: pipes (|), && / || / ;, redirections (> >> 2> 2>&1 < /dev/null), variables ($VAR $HOME ~), command substitution $(...), and ${registeredNames().length}+ coreutils-style commands (${registeredNames().slice(0, 18).join(', ')}...).
|
|
39
|
+
Unknown commands (git, node, npm, python, cargo...) are passed through and executed natively with argv-style quoting.
|
|
40
|
+
Not supported: heredocs, while/until/case, word-level \$((...)) arithmetic expansion, background jobs. if/then/else/fi and for-in loops are supported.
|
|
41
|
+
|
|
42
|
+
CWD, environment variables, export/unset and cd persist across calls within this session — but prefer COMBINING related commands in one call with ; or && (e.g. 'cd src && ls | wc -l'); each call is a fresh translation+process, so batching is faster than many tiny calls.
|
|
43
|
+
Exit codes follow bash conventions (0 ok, 1 fail, 2 usage/serious, 127 command not found, 124 timeout).
|
|
44
|
+
|
|
45
45
|
Platform requirement: the execution backend is native Windows PowerShell 5.1+. On hosts without PowerShell on PATH (e.g. Linux containers/sandboxes), the bash tool returns exit code 127 with an actionable error instead of running the command.`;
|
|
46
46
|
export async function startMcpServer() {
|
|
47
47
|
const server = new McpServer({ name: 'fauxnix', version: pkgVersion }, { capabilities: { tools: {} } });
|
package/dist/parser.js
CHANGED
|
@@ -256,6 +256,12 @@ function readDollar(input, i) {
|
|
|
256
256
|
}
|
|
257
257
|
return { part: { kind: 'Var', name }, next: end + 1 };
|
|
258
258
|
}
|
|
259
|
+
// $((...)) is arithmetic expansion, not command substitution of a
|
|
260
|
+
// parenthesized body. Today it was parsed as $( (expr) ) and became an
|
|
261
|
+
// empty/confusing command. Reject loudly until word-level arith lands.
|
|
262
|
+
if (input[j] === '(' && j + 1 < n && input[j + 1] === '(') {
|
|
263
|
+
throw new FauxnixParseError('fauxnix: $((...)) arithmetic expansion is not supported; compute the value in the agent or compare with [[ $n -eq m ]]');
|
|
264
|
+
}
|
|
259
265
|
// $(cmd substitution) — captured with balanced parens; the translator
|
|
260
266
|
// recursively translates this text before embedding it.
|
|
261
267
|
if (input[j] === '(') {
|
package/dist/translator.d.ts
CHANGED
|
@@ -85,5 +85,7 @@ export declare function translateCommandList(list: CommandList): SegmentPlan[];
|
|
|
85
85
|
/**
|
|
86
86
|
* Wrap a pipeline body with the Fauxnix executor contract:
|
|
87
87
|
* UTF-8 everywhere, bash-style exit codes, cwd/env persistence channels.
|
|
88
|
+
* Only the fx- helpers the body actually calls are emitted — the full
|
|
89
|
+
* catalog is ~170 lines and was paid on every `echo hi`.
|
|
88
90
|
*/
|
|
89
91
|
export declare function wrapScript(body: string): string;
|
package/dist/translator.js
CHANGED
|
@@ -775,11 +775,72 @@ export function translateCommandList(list) {
|
|
|
775
775
|
}
|
|
776
776
|
return plans;
|
|
777
777
|
}
|
|
778
|
+
const WRAP_HELPER_ORDER = [
|
|
779
|
+
'fx-readlines',
|
|
780
|
+
'fx-csub',
|
|
781
|
+
'fx-svenc',
|
|
782
|
+
'fx-svdec',
|
|
783
|
+
'fx-arrload',
|
|
784
|
+
'fx-scalar0',
|
|
785
|
+
'fx-ifs1',
|
|
786
|
+
'fx-arrdrop',
|
|
787
|
+
'fx-arrhas',
|
|
788
|
+
'fx-arrpackget',
|
|
789
|
+
'fx-arrpackset',
|
|
790
|
+
'fx-arrput',
|
|
791
|
+
'fx-arrclr',
|
|
792
|
+
'fx-subget',
|
|
793
|
+
];
|
|
794
|
+
const WRAP_HELPER_DEPS = {
|
|
795
|
+
'fx-readlines': [],
|
|
796
|
+
'fx-csub': [],
|
|
797
|
+
'fx-svenc': [],
|
|
798
|
+
'fx-svdec': [],
|
|
799
|
+
'fx-arrload': ['fx-scalar0', 'fx-svdec'],
|
|
800
|
+
'fx-scalar0': ['fx-svdec'],
|
|
801
|
+
'fx-ifs1': ['fx-scalar0'],
|
|
802
|
+
'fx-arrdrop': [],
|
|
803
|
+
'fx-arrhas': [],
|
|
804
|
+
'fx-arrpackget': [],
|
|
805
|
+
'fx-arrpackset': ['fx-arrdrop'],
|
|
806
|
+
'fx-arrput': ['fx-arrdrop', 'fx-svenc'],
|
|
807
|
+
'fx-arrclr': ['fx-arrdrop'],
|
|
808
|
+
'fx-subget': ['fx-arrload', 'fx-ifs1'],
|
|
809
|
+
};
|
|
810
|
+
/** Helpers the body calls that wrapScript still has to emit (not already defined there). */
|
|
811
|
+
function wrapHelpersNeeded(body) {
|
|
812
|
+
const defined = new Set();
|
|
813
|
+
const defRe = /function\s+(fx-[A-Za-z0-9]+)/g;
|
|
814
|
+
let m;
|
|
815
|
+
while ((m = defRe.exec(body)))
|
|
816
|
+
defined.add(m[1]);
|
|
817
|
+
const seeds = [];
|
|
818
|
+
const callRe = /\b(fx-[A-Za-z0-9]+)\b/g;
|
|
819
|
+
while ((m = callRe.exec(body))) {
|
|
820
|
+
const n = m[1];
|
|
821
|
+
if (!defined.has(n) && n in WRAP_HELPER_DEPS)
|
|
822
|
+
seeds.push(n);
|
|
823
|
+
}
|
|
824
|
+
const needed = new Set();
|
|
825
|
+
const stack = seeds.slice();
|
|
826
|
+
while (stack.length) {
|
|
827
|
+
const n = stack.pop();
|
|
828
|
+
if (needed.has(n) || defined.has(n))
|
|
829
|
+
continue;
|
|
830
|
+
needed.add(n);
|
|
831
|
+
for (const d of WRAP_HELPER_DEPS[n])
|
|
832
|
+
stack.push(d);
|
|
833
|
+
}
|
|
834
|
+
return needed;
|
|
835
|
+
}
|
|
778
836
|
/**
|
|
779
837
|
* Wrap a pipeline body with the Fauxnix executor contract:
|
|
780
838
|
* UTF-8 everywhere, bash-style exit codes, cwd/env persistence channels.
|
|
839
|
+
* Only the fx- helpers the body actually calls are emitted — the full
|
|
840
|
+
* catalog is ~170 lines and was paid on every `echo hi`.
|
|
781
841
|
*/
|
|
782
842
|
export function wrapScript(body) {
|
|
843
|
+
const needed = wrapHelpersNeeded(body);
|
|
783
844
|
const lines = [
|
|
784
845
|
'$ErrorActionPreference = "Continue"',
|
|
785
846
|
"$ProgressPreference = 'SilentlyContinue'",
|
|
@@ -803,175 +864,191 @@ export function wrapScript(body) {
|
|
|
803
864
|
// .NET APIs (ReadAllBytes & friends) resolve relative paths against the
|
|
804
865
|
// process working directory, NOT the PS location — keep them in sync.
|
|
805
866
|
'try { [Environment]::CurrentDirectory = (Get-Location).ProviderPath } catch {}',
|
|
806
|
-
// byte-sniffing line reader for `< file` stdin redirects (UTF-8 → GBK)
|
|
807
|
-
'function fx-readlines($p) {',
|
|
808
|
-
' $b = [IO.File]::ReadAllBytes($p)',
|
|
809
|
-
' $t = $null',
|
|
810
|
-
' try { $t = (New-Object System.Text.UTF8Encoding($false, $true)).GetString($b) } catch {}',
|
|
811
|
-
" if ($null -eq $t) { try { $t = [System.Text.Encoding]::GetEncoding(936).GetString($b) } catch { $t = [System.Text.Encoding]::ASCII.GetString($b) } }",
|
|
812
|
-
' $t = $t -replace "`r`n", "`n"',
|
|
813
|
-
' $t = $t -replace "`r", "`n"',
|
|
814
|
-
' $parts = @($t.Split("`n"))',
|
|
815
|
-
" if ($parts.Count -eq 1 -and $parts[0] -eq '') { return @() }",
|
|
816
|
-
" if ($parts[$parts.Count - 1] -eq '') { $parts = $parts[0..($parts.Count - 2)] }",
|
|
817
|
-
' return $parts',
|
|
818
|
-
'}',
|
|
819
|
-
'function fx-csub([scriptblock]$b) {',
|
|
820
|
-
' $fx_prevcs = $script:fx_csub',
|
|
821
|
-
' $script:fx_csub = $true',
|
|
822
|
-
' try { $fx_o = @(& $b | ForEach-Object { [string]$_ }) }',
|
|
823
|
-
' finally { $script:fx_csub = $fx_prevcs }',
|
|
824
|
-
' $fx_s = ($fx_o -join [string][char]10)',
|
|
825
|
-
' while ($fx_s.Length -gt 0 -and $fx_s[$fx_s.Length - 1] -eq [char]10) {',
|
|
826
|
-
' $fx_s = $fx_s.Substring(0, $fx_s.Length - 1)',
|
|
827
|
-
' }',
|
|
828
|
-
' return $fx_s',
|
|
829
|
-
'}',
|
|
830
|
-
'function fx-svenc($s) {',
|
|
831
|
-
' return ([string]$s).Replace([string][char]92, ([string][char]92 + [string][char]92)).Replace([string][char]13, ([string][char]92 + [char]114)).Replace([string][char]10, ([string][char]92 + [char]110))',
|
|
832
|
-
'}',
|
|
833
|
-
'function fx-svdec($s) {',
|
|
834
|
-
' $s = [string]$s',
|
|
835
|
-
' $sb = New-Object System.Text.StringBuilder',
|
|
836
|
-
' $i = 0',
|
|
837
|
-
' while ($i -lt $s.Length) {',
|
|
838
|
-
' $c = $s[$i]',
|
|
839
|
-
' if ($c -eq [char]92 -and ($i + 1) -lt $s.Length) {',
|
|
840
|
-
' $n2 = $s[$i + 1]',
|
|
841
|
-
' if ($n2 -eq [char]110) { [void]$sb.Append([char]10); $i += 2; continue }',
|
|
842
|
-
' if ($n2 -eq [char]114) { [void]$sb.Append([char]13); $i += 2; continue }',
|
|
843
|
-
' if ($n2 -eq [char]92) { [void]$sb.Append([char]92); $i += 2; continue }',
|
|
844
|
-
' }',
|
|
845
|
-
' [void]$sb.Append($c)',
|
|
846
|
-
' $i++',
|
|
847
|
-
' }',
|
|
848
|
-
' return [string]$sb',
|
|
849
|
-
'}',
|
|
850
|
-
'function fx-arrload($n) {',
|
|
851
|
-
' $n = [string]$n',
|
|
852
|
-
' foreach ($fx_pair in @($env:FAUXNIX_ARRS -split [string][char]10)) {',
|
|
853
|
-
' $fx_eq = $fx_pair.IndexOf([char]61)',
|
|
854
|
-
' if ($fx_eq -lt 1) { continue }',
|
|
855
|
-
' if ($fx_pair.Substring(0, $fx_eq) -cne $n) { continue }',
|
|
856
|
-
' $out = @()',
|
|
857
|
-
' foreach ($el in @($fx_pair.Substring($fx_eq + 1) -split [string][char]30)) { $out += ,(fx-svdec $el) }',
|
|
858
|
-
' return $out',
|
|
859
|
-
' }',
|
|
860
|
-
' $s0 = fx-scalar0 $n',
|
|
861
|
-
' if ($null -eq $s0) { return @() }',
|
|
862
|
-
' return @([string]$s0)',
|
|
863
|
-
'}',
|
|
864
|
-
'function fx-scalar0($n) {',
|
|
865
|
-
' $n = [string]$n',
|
|
866
|
-
" if (@($env:FAUXNIX_UNSETVARS -split ';' | Where-Object { $_ -ceq $n }).Count -gt 0) { return $null }",
|
|
867
|
-
' foreach ($fx_pair in @($env:FAUXNIX_SETVALS -split [string][char]10)) {',
|
|
868
|
-
' $fx_eq = $fx_pair.IndexOf([char]61)',
|
|
869
|
-
' if ($fx_eq -lt 1) { continue }',
|
|
870
|
-
' if ($fx_pair.Substring(0, $fx_eq) -ceq $n) { return (fx-svdec $fx_pair.Substring($fx_eq + 1)) }',
|
|
871
|
-
' }',
|
|
872
|
-
" if ($n -ceq 'HOME') { return [string]$HOME }",
|
|
873
|
-
" if ($n -ceq 'PWD') { return [string]$PWD.Path }",
|
|
874
|
-
" if ($n -ceq 'USER' -or $n -ceq 'LOGNAME') { return [string]$env:USERNAME }",
|
|
875
|
-
" if ($n -ceq 'PATH') { return [string]$env:PATH }",
|
|
876
|
-
" if ($n -ceq 'SHELL') { return 'powershell' }",
|
|
877
|
-
" if ($n -ceq 'TERM') { return 'xterm-256color' }",
|
|
878
|
-
" if ($n -ceq 'OLDPWD') { return $(if ($env:FAUXNIX_OLDPWD) { [string]$env:FAUXNIX_OLDPWD } else { $null }) }",
|
|
879
|
-
" if ($n -ceq 'HOSTNAME') { return [string]$env:COMPUTERNAME }",
|
|
880
|
-
' $ev = Get-ChildItem Env: | Where-Object { $_.Name -ceq $n } | Select-Object -First 1',
|
|
881
|
-
' if ($ev) { return [string]$ev.Value }',
|
|
882
|
-
' return $null',
|
|
883
|
-
'}',
|
|
884
|
-
'function fx-ifs1 {',
|
|
885
|
-
" $s = fx-scalar0 'IFS'",
|
|
886
|
-
" if ($null -eq $s) { return ' ' }",
|
|
887
|
-
" if ([string]$s -eq '') { return '' }",
|
|
888
|
-
' return [string]$s[0]',
|
|
889
|
-
'}',
|
|
890
|
-
'function fx-arrdrop($n) {',
|
|
891
|
-
' $n = [string]$n',
|
|
892
|
-
' $fx_sm = @()',
|
|
893
|
-
' foreach ($fx_pair in @($env:FAUXNIX_ARRS -split [string][char]10)) {',
|
|
894
|
-
' $fx_eq = $fx_pair.IndexOf([char]61)',
|
|
895
|
-
' if ($fx_eq -lt 1) { continue }',
|
|
896
|
-
' if ($fx_pair.Substring(0, $fx_eq) -cne $n) { $fx_sm += $fx_pair }',
|
|
897
|
-
' }',
|
|
898
|
-
' $env:FAUXNIX_ARRS = ($fx_sm -join [string][char]10)',
|
|
899
|
-
'}',
|
|
900
|
-
'function fx-arrhas($n) {',
|
|
901
|
-
' $n = [string]$n',
|
|
902
|
-
' foreach ($fx_pair in @($env:FAUXNIX_ARRS -split [string][char]10)) {',
|
|
903
|
-
' $fx_eq = $fx_pair.IndexOf([char]61)',
|
|
904
|
-
' if ($fx_eq -lt 1) { continue }',
|
|
905
|
-
' if ($fx_pair.Substring(0, $fx_eq) -ceq $n) { return $true }',
|
|
906
|
-
' }',
|
|
907
|
-
' return $false',
|
|
908
|
-
'}',
|
|
909
|
-
'function fx-arrpackget($n) {',
|
|
910
|
-
' $n = [string]$n',
|
|
911
|
-
' foreach ($fx_pair in @($env:FAUXNIX_ARRS -split [string][char]10)) {',
|
|
912
|
-
' $fx_eq = $fx_pair.IndexOf([char]61)',
|
|
913
|
-
' if ($fx_eq -lt 1) { continue }',
|
|
914
|
-
' if ($fx_pair.Substring(0, $fx_eq) -ceq $n) { return $fx_pair.Substring($fx_eq + 1) }',
|
|
915
|
-
' }',
|
|
916
|
-
' return $null',
|
|
917
|
-
'}',
|
|
918
|
-
'function fx-arrpackset($n, $pay) {',
|
|
919
|
-
' fx-arrdrop $n',
|
|
920
|
-
' if ($null -eq $pay) { return }',
|
|
921
|
-
" $env:FAUXNIX_ARRS = ((@($env:FAUXNIX_ARRS -split [string][char]10 | Where-Object { $_ -ne '' }) + ([string]$n + [string][char]61 + [string]$pay)) -join [string][char]10)",
|
|
922
|
-
'}',
|
|
923
|
-
'function fx-arrput($n, $vals) {',
|
|
924
|
-
' $n = [string]$n',
|
|
925
|
-
' $vals = @($vals)',
|
|
926
|
-
' fx-arrdrop $n',
|
|
927
|
-
' if ($vals.Count -eq 0) { } else {',
|
|
928
|
-
' $encs = @(); foreach ($v in $vals) { $encs += (fx-svenc $v) }',
|
|
929
|
-
" $env:FAUXNIX_ARRS = ((@($env:FAUXNIX_ARRS -split [string][char]10 | Where-Object { $_ -ne '' }) + ($n + [string][char]61 + ($encs -join [string][char]30))) -join [string][char]10)",
|
|
930
|
-
' }',
|
|
931
|
-
" $fx_0 = $(if ($vals.Count -gt 0) { [string]$vals[0] } else { '' })",
|
|
932
|
-
' Set-Item -LiteralPath (\'Env:\\\' + $n) -Value $fx_0',
|
|
933
|
-
" $env:FAUXNIX_SETVARS = ((@($env:FAUXNIX_SETVARS -split ';' | Where-Object { $_ -ne '' -and $_ -cne $n }) + $n) -join ';')",
|
|
934
|
-
" $env:FAUXNIX_UNSETVARS = (@($env:FAUXNIX_UNSETVARS -split ';' | Where-Object { $_ -ne '' -and $_ -cne $n }) -join ';')",
|
|
935
|
-
' $fx_sv = @(); foreach ($fx_pair in @($env:FAUXNIX_SETVALS -split [string][char]10)) { $fx_eq = $fx_pair.IndexOf([char]61); if ($fx_eq -lt 1) { continue }; if ($fx_pair.Substring(0, $fx_eq) -cne $n) { $fx_sv += $fx_pair } }',
|
|
936
|
-
" $fx_sv += ($n + [string][char]61 + (fx-svenc $fx_0)); $env:FAUXNIX_SETVALS = ($fx_sv -join [string][char]10)",
|
|
937
|
-
'}',
|
|
938
|
-
'function fx-arrclr($n) {',
|
|
939
|
-
' $n = [string]$n',
|
|
940
|
-
' fx-arrdrop $n',
|
|
941
|
-
" Remove-Item -LiteralPath ('Env:\\' + $n) -ErrorAction SilentlyContinue",
|
|
942
|
-
" $env:FAUXNIX_SETVARS = (@($env:FAUXNIX_SETVARS -split ';' | Where-Object { $_ -ne '' -and $_ -cne $n }) -join ';')",
|
|
943
|
-
" $env:FAUXNIX_UNSETVARS = ((@($env:FAUXNIX_UNSETVARS -split ';' | Where-Object { $_ -ne '' -and $_ -cne $n }) + $n) -join ';')",
|
|
944
|
-
' $fx_sv = @(); foreach ($fx_pair in @($env:FAUXNIX_SETVALS -split [string][char]10)) { $fx_eq = $fx_pair.IndexOf([char]61); if ($fx_eq -lt 1) { continue }; if ($fx_pair.Substring(0, $fx_eq) -cne $n) { $fx_sv += $fx_pair } }; $env:FAUXNIX_SETVALS = ($fx_sv -join [string][char]10)',
|
|
945
|
-
'}',
|
|
946
|
-
'function fx-subget($n, $ix) {',
|
|
947
|
-
' $arr = @(fx-arrload $n)',
|
|
948
|
-
' $ix = [string]$ix',
|
|
949
|
-
// argv-level `@` is expanded by argListExpr; this is the scalar/quoted-* join.
|
|
950
|
-
" if ($ix -eq '*') { return ($arr -join (fx-ifs1)) }",
|
|
951
|
-
" if ($ix -eq '@') { return ($arr -join (fx-ifs1)) }",
|
|
952
|
-
' $i = 0',
|
|
953
|
-
' if (-not [int]::TryParse($ix, [ref]$i)) { return \'\' }',
|
|
954
|
-
" if ($i -lt 0 -or $i -ge $arr.Count) { return '' }",
|
|
955
|
-
' return [string]$arr[$i]',
|
|
956
|
-
'}',
|
|
957
|
-
'try {',
|
|
958
|
-
...body.split('\n').map((l) => ' ' + l),
|
|
959
|
-
'} catch [System.Management.Automation.CommandNotFoundException] {',
|
|
960
|
-
" [Console]::Error.WriteLine('bash: ' + $_.Exception.TargetName + ': command not found')",
|
|
961
|
-
' $script:fx_exit = 127',
|
|
962
|
-
'} catch {',
|
|
963
|
-
' [Console]::Error.WriteLine(($_.Exception.Message).Split("`n")[0])',
|
|
964
|
-
' $script:fx_exit = 1',
|
|
965
|
-
'}',
|
|
966
|
-
'# persist session cwd and environment for the next segment',
|
|
967
|
-
'try { [IO.File]::WriteAllText($env:FAUXNIX_CWD_FILE, (Get-Location).Path) } catch {}',
|
|
968
|
-
'if ((Get-Location).Path -ne $fx_oldcwd) { $env:FAUXNIX_OLDPWD = $fx_oldcwd }',
|
|
969
|
-
'try {',
|
|
970
|
-
' $envObj = @{}',
|
|
971
|
-
' Get-ChildItem Env: | ForEach-Object { $envObj[$_.Name] = $_.Value }',
|
|
972
|
-
' [IO.File]::WriteAllText($env:FAUXNIX_ENV_FILE, (ConvertTo-Json $envObj -Compress))',
|
|
973
|
-
'} catch {}',
|
|
974
|
-
'exit $script:fx_exit',
|
|
975
867
|
];
|
|
868
|
+
const helpers = {
|
|
869
|
+
'fx-readlines': [
|
|
870
|
+
'function fx-readlines($p) {',
|
|
871
|
+
' $b = [IO.File]::ReadAllBytes($p)',
|
|
872
|
+
' $t = $null',
|
|
873
|
+
' try { $t = (New-Object System.Text.UTF8Encoding($false, $true)).GetString($b) } catch {}',
|
|
874
|
+
" if ($null -eq $t) { try { $t = [System.Text.Encoding]::GetEncoding(936).GetString($b) } catch { $t = [System.Text.Encoding]::ASCII.GetString($b) } }",
|
|
875
|
+
' $t = $t -replace "`r`n", "`n"',
|
|
876
|
+
' $t = $t -replace "`r", "`n"',
|
|
877
|
+
' $parts = @($t.Split("`n"))',
|
|
878
|
+
" if ($parts.Count -eq 1 -and $parts[0] -eq '') { return @() }",
|
|
879
|
+
" if ($parts[$parts.Count - 1] -eq '') { $parts = $parts[0..($parts.Count - 2)] }",
|
|
880
|
+
' return $parts',
|
|
881
|
+
'}',
|
|
882
|
+
],
|
|
883
|
+
'fx-csub': [
|
|
884
|
+
'function fx-csub([scriptblock]$b) {',
|
|
885
|
+
' $fx_prevcs = $script:fx_csub',
|
|
886
|
+
' $script:fx_csub = $true',
|
|
887
|
+
' try { $fx_o = @(& $b | ForEach-Object { [string]$_ }) }',
|
|
888
|
+
' finally { $script:fx_csub = $fx_prevcs }',
|
|
889
|
+
' $fx_s = ($fx_o -join [string][char]10)',
|
|
890
|
+
' while ($fx_s.Length -gt 0 -and $fx_s[$fx_s.Length - 1] -eq [char]10) {',
|
|
891
|
+
' $fx_s = $fx_s.Substring(0, $fx_s.Length - 1)',
|
|
892
|
+
' }',
|
|
893
|
+
' return $fx_s',
|
|
894
|
+
'}',
|
|
895
|
+
],
|
|
896
|
+
'fx-svenc': [
|
|
897
|
+
'function fx-svenc($s) {',
|
|
898
|
+
' return ([string]$s).Replace([string][char]92, ([string][char]92 + [string][char]92)).Replace([string][char]13, ([string][char]92 + [char]114)).Replace([string][char]10, ([string][char]92 + [char]110))',
|
|
899
|
+
'}',
|
|
900
|
+
],
|
|
901
|
+
'fx-svdec': [
|
|
902
|
+
'function fx-svdec($s) {',
|
|
903
|
+
' $s = [string]$s',
|
|
904
|
+
' $sb = New-Object System.Text.StringBuilder',
|
|
905
|
+
' $i = 0',
|
|
906
|
+
' while ($i -lt $s.Length) {',
|
|
907
|
+
' $c = $s[$i]',
|
|
908
|
+
' if ($c -eq [char]92 -and ($i + 1) -lt $s.Length) {',
|
|
909
|
+
' $n2 = $s[$i + 1]',
|
|
910
|
+
' if ($n2 -eq [char]110) { [void]$sb.Append([char]10); $i += 2; continue }',
|
|
911
|
+
' if ($n2 -eq [char]114) { [void]$sb.Append([char]13); $i += 2; continue }',
|
|
912
|
+
' if ($n2 -eq [char]92) { [void]$sb.Append([char]92); $i += 2; continue }',
|
|
913
|
+
' }',
|
|
914
|
+
' [void]$sb.Append($c)',
|
|
915
|
+
' $i++',
|
|
916
|
+
' }',
|
|
917
|
+
' return [string]$sb',
|
|
918
|
+
'}',
|
|
919
|
+
],
|
|
920
|
+
'fx-arrload': [
|
|
921
|
+
'function fx-arrload($n) {',
|
|
922
|
+
' $n = [string]$n',
|
|
923
|
+
' foreach ($fx_pair in @($env:FAUXNIX_ARRS -split [string][char]10)) {',
|
|
924
|
+
' $fx_eq = $fx_pair.IndexOf([char]61)',
|
|
925
|
+
' if ($fx_eq -lt 1) { continue }',
|
|
926
|
+
' if ($fx_pair.Substring(0, $fx_eq) -cne $n) { continue }',
|
|
927
|
+
' $out = @()',
|
|
928
|
+
' foreach ($el in @($fx_pair.Substring($fx_eq + 1) -split [string][char]30)) { $out += ,(fx-svdec $el) }',
|
|
929
|
+
' return $out',
|
|
930
|
+
' }',
|
|
931
|
+
' $s0 = fx-scalar0 $n',
|
|
932
|
+
' if ($null -eq $s0) { return @() }',
|
|
933
|
+
' return @([string]$s0)',
|
|
934
|
+
'}',
|
|
935
|
+
],
|
|
936
|
+
'fx-scalar0': [
|
|
937
|
+
'function fx-scalar0($n) {',
|
|
938
|
+
' $n = [string]$n',
|
|
939
|
+
" if (@($env:FAUXNIX_UNSETVARS -split ';' | Where-Object { $_ -ceq $n }).Count -gt 0) { return $null }",
|
|
940
|
+
' foreach ($fx_pair in @($env:FAUXNIX_SETVALS -split [string][char]10)) {',
|
|
941
|
+
' $fx_eq = $fx_pair.IndexOf([char]61)',
|
|
942
|
+
' if ($fx_eq -lt 1) { continue }',
|
|
943
|
+
' if ($fx_pair.Substring(0, $fx_eq) -ceq $n) { return (fx-svdec $fx_pair.Substring($fx_eq + 1)) }',
|
|
944
|
+
' }',
|
|
945
|
+
" if ($n -ceq 'HOME') { return [string]$HOME }",
|
|
946
|
+
" if ($n -ceq 'PWD') { return [string]$PWD.Path }",
|
|
947
|
+
" if ($n -ceq 'USER' -or $n -ceq 'LOGNAME') { return [string]$env:USERNAME }",
|
|
948
|
+
" if ($n -ceq 'PATH') { return [string]$env:PATH }",
|
|
949
|
+
" if ($n -ceq 'SHELL') { return 'powershell' }",
|
|
950
|
+
" if ($n -ceq 'TERM') { return 'xterm-256color' }",
|
|
951
|
+
" if ($n -ceq 'OLDPWD') { return $(if ($env:FAUXNIX_OLDPWD) { [string]$env:FAUXNIX_OLDPWD } else { $null }) }",
|
|
952
|
+
" if ($n -ceq 'HOSTNAME') { return [string]$env:COMPUTERNAME }",
|
|
953
|
+
' $ev = Get-ChildItem Env: | Where-Object { $_.Name -ceq $n } | Select-Object -First 1',
|
|
954
|
+
' if ($ev) { return [string]$ev.Value }',
|
|
955
|
+
' return $null',
|
|
956
|
+
'}',
|
|
957
|
+
],
|
|
958
|
+
'fx-ifs1': [
|
|
959
|
+
'function fx-ifs1 {',
|
|
960
|
+
" $s = fx-scalar0 'IFS'",
|
|
961
|
+
" if ($null -eq $s) { return ' ' }",
|
|
962
|
+
" if ([string]$s -eq '') { return '' }",
|
|
963
|
+
' return [string]$s[0]',
|
|
964
|
+
'}',
|
|
965
|
+
],
|
|
966
|
+
'fx-arrdrop': [
|
|
967
|
+
'function fx-arrdrop($n) {',
|
|
968
|
+
' $n = [string]$n',
|
|
969
|
+
' $fx_sm = @()',
|
|
970
|
+
' foreach ($fx_pair in @($env:FAUXNIX_ARRS -split [string][char]10)) {',
|
|
971
|
+
' $fx_eq = $fx_pair.IndexOf([char]61)',
|
|
972
|
+
' if ($fx_eq -lt 1) { continue }',
|
|
973
|
+
' if ($fx_pair.Substring(0, $fx_eq) -cne $n) { $fx_sm += $fx_pair }',
|
|
974
|
+
' }',
|
|
975
|
+
' $env:FAUXNIX_ARRS = ($fx_sm -join [string][char]10)',
|
|
976
|
+
'}',
|
|
977
|
+
],
|
|
978
|
+
'fx-arrhas': [
|
|
979
|
+
'function fx-arrhas($n) {',
|
|
980
|
+
' $n = [string]$n',
|
|
981
|
+
' foreach ($fx_pair in @($env:FAUXNIX_ARRS -split [string][char]10)) {',
|
|
982
|
+
' $fx_eq = $fx_pair.IndexOf([char]61)',
|
|
983
|
+
' if ($fx_eq -lt 1) { continue }',
|
|
984
|
+
' if ($fx_pair.Substring(0, $fx_eq) -ceq $n) { return $true }',
|
|
985
|
+
' }',
|
|
986
|
+
' return $false',
|
|
987
|
+
'}',
|
|
988
|
+
],
|
|
989
|
+
'fx-arrpackget': [
|
|
990
|
+
'function fx-arrpackget($n) {',
|
|
991
|
+
' $n = [string]$n',
|
|
992
|
+
' foreach ($fx_pair in @($env:FAUXNIX_ARRS -split [string][char]10)) {',
|
|
993
|
+
' $fx_eq = $fx_pair.IndexOf([char]61)',
|
|
994
|
+
' if ($fx_eq -lt 1) { continue }',
|
|
995
|
+
' if ($fx_pair.Substring(0, $fx_eq) -ceq $n) { return $fx_pair.Substring($fx_eq + 1) }',
|
|
996
|
+
' }',
|
|
997
|
+
' return $null',
|
|
998
|
+
'}',
|
|
999
|
+
],
|
|
1000
|
+
'fx-arrpackset': [
|
|
1001
|
+
'function fx-arrpackset($n, $pay) {',
|
|
1002
|
+
' fx-arrdrop $n',
|
|
1003
|
+
' if ($null -eq $pay) { return }',
|
|
1004
|
+
" $env:FAUXNIX_ARRS = ((@($env:FAUXNIX_ARRS -split [string][char]10 | Where-Object { $_ -ne '' }) + ([string]$n + [string][char]61 + [string]$pay)) -join [string][char]10)",
|
|
1005
|
+
'}',
|
|
1006
|
+
],
|
|
1007
|
+
'fx-arrput': [
|
|
1008
|
+
'function fx-arrput($n, $vals) {',
|
|
1009
|
+
' $n = [string]$n',
|
|
1010
|
+
' $vals = @($vals)',
|
|
1011
|
+
' fx-arrdrop $n',
|
|
1012
|
+
' if ($vals.Count -eq 0) { } else {',
|
|
1013
|
+
' $encs = @(); foreach ($v in $vals) { $encs += (fx-svenc $v) }',
|
|
1014
|
+
" $env:FAUXNIX_ARRS = ((@($env:FAUXNIX_ARRS -split [string][char]10 | Where-Object { $_ -ne '' }) + ($n + [string][char]61 + ($encs -join [string][char]30))) -join [string][char]10)",
|
|
1015
|
+
' }',
|
|
1016
|
+
" $fx_0 = $(if ($vals.Count -gt 0) { [string]$vals[0] } else { '' })",
|
|
1017
|
+
' Set-Item -LiteralPath (\'Env:\\\' + $n) -Value $fx_0',
|
|
1018
|
+
" $env:FAUXNIX_SETVARS = ((@($env:FAUXNIX_SETVARS -split ';' | Where-Object { $_ -ne '' -and $_ -cne $n }) + $n) -join ';')",
|
|
1019
|
+
" $env:FAUXNIX_UNSETVARS = (@($env:FAUXNIX_UNSETVARS -split ';' | Where-Object { $_ -ne '' -and $_ -cne $n }) -join ';')",
|
|
1020
|
+
' $fx_sv = @(); foreach ($fx_pair in @($env:FAUXNIX_SETVALS -split [string][char]10)) { $fx_eq = $fx_pair.IndexOf([char]61); if ($fx_eq -lt 1) { continue }; if ($fx_pair.Substring(0, $fx_eq) -cne $n) { $fx_sv += $fx_pair } }',
|
|
1021
|
+
" $fx_sv += ($n + [string][char]61 + (fx-svenc $fx_0)); $env:FAUXNIX_SETVALS = ($fx_sv -join [string][char]10)",
|
|
1022
|
+
'}',
|
|
1023
|
+
],
|
|
1024
|
+
'fx-arrclr': [
|
|
1025
|
+
'function fx-arrclr($n) {',
|
|
1026
|
+
' $n = [string]$n',
|
|
1027
|
+
' fx-arrdrop $n',
|
|
1028
|
+
" Remove-Item -LiteralPath ('Env:\\' + $n) -ErrorAction SilentlyContinue",
|
|
1029
|
+
" $env:FAUXNIX_SETVARS = (@($env:FAUXNIX_SETVARS -split ';' | Where-Object { $_ -ne '' -and $_ -cne $n }) -join ';')",
|
|
1030
|
+
" $env:FAUXNIX_UNSETVARS = ((@($env:FAUXNIX_UNSETVARS -split ';' | Where-Object { $_ -ne '' -and $_ -cne $n }) + $n) -join ';')",
|
|
1031
|
+
' $fx_sv = @(); foreach ($fx_pair in @($env:FAUXNIX_SETVALS -split [string][char]10)) { $fx_eq = $fx_pair.IndexOf([char]61); if ($fx_eq -lt 1) { continue }; if ($fx_pair.Substring(0, $fx_eq) -cne $n) { $fx_sv += $fx_pair } }; $env:FAUXNIX_SETVALS = ($fx_sv -join [string][char]10)',
|
|
1032
|
+
'}',
|
|
1033
|
+
],
|
|
1034
|
+
'fx-subget': [
|
|
1035
|
+
'function fx-subget($n, $ix) {',
|
|
1036
|
+
' $arr = @(fx-arrload $n)',
|
|
1037
|
+
' $ix = [string]$ix',
|
|
1038
|
+
// argv-level `@` is expanded by argListExpr; this is the scalar/quoted-* join.
|
|
1039
|
+
" if ($ix -eq '*') { return ($arr -join (fx-ifs1)) }",
|
|
1040
|
+
" if ($ix -eq '@') { return ($arr -join (fx-ifs1)) }",
|
|
1041
|
+
' $i = 0',
|
|
1042
|
+
' if (-not [int]::TryParse($ix, [ref]$i)) { return \'\' }',
|
|
1043
|
+
" if ($i -lt 0 -or $i -ge $arr.Count) { return '' }",
|
|
1044
|
+
' return [string]$arr[$i]',
|
|
1045
|
+
'}',
|
|
1046
|
+
],
|
|
1047
|
+
};
|
|
1048
|
+
for (const name of WRAP_HELPER_ORDER) {
|
|
1049
|
+
if (needed.has(name))
|
|
1050
|
+
lines.push(...helpers[name]);
|
|
1051
|
+
}
|
|
1052
|
+
lines.push('try {', ...body.split('\n').map((l) => ' ' + l), '} catch [System.Management.Automation.CommandNotFoundException] {', " [Console]::Error.WriteLine('bash: ' + $_.Exception.TargetName + ': command not found')", ' $script:fx_exit = 127', '} catch {', ' [Console]::Error.WriteLine(($_.Exception.Message).Split("`n")[0])', ' $script:fx_exit = 1', '}', '# persist session cwd and environment for the next segment', 'try { [IO.File]::WriteAllText($env:FAUXNIX_CWD_FILE, (Get-Location).Path) } catch {}', 'if ((Get-Location).Path -ne $fx_oldcwd) { $env:FAUXNIX_OLDPWD = $fx_oldcwd }', 'try {', ' $envObj = @{}', ' Get-ChildItem Env: | ForEach-Object { $envObj[$_.Name] = $_.Value }', ' [IO.File]::WriteAllText($env:FAUXNIX_ENV_FILE, (ConvertTo-Json $envObj -Compress))', '} catch {}', 'exit $script:fx_exit');
|
|
976
1053
|
return lines.join('\n');
|
|
977
1054
|
}
|
package/package.json
CHANGED