@wrongstack/tools 0.310.1 → 0.313.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.
@@ -121,21 +121,16 @@ export declare function looksLikePowerShellExtended(command: string): boolean;
121
121
  * don't block waiting for user input. `$WhatIfPreference=$false` ensures
122
122
  * commands actually RUN (not just print what they would do).
123
123
  *
124
- * Do not prefix the stdin script with a UTF-8 BOM. Some PowerShell builds do
125
- * not strip it when reading `-Command -` and treat it as part of the first
126
- * command name (`\uFEFF[Console]::OutputEncoding`), causing every wrapped
127
- * command to fail before the user's script runs.
124
+ * The caller encodes this script as UTF-16LE Base64 for `-EncodedCommand`.
125
+ * Do not prefix it with a BOM: the encoded command is already decoded using
126
+ * PowerShell's required UTF-16LE representation.
128
127
  */
129
128
  export declare function wrapPowerShellScript(command: string): string;
130
129
  /**
131
130
  * Return the argv prefix for a given shell. The bash tool passes a single
132
131
  * command string and expects the shell to interpret it. cmd.exe uses
133
- * `/c <cmd>`; PowerShell uses `-NoLogo -NoProfile -NonInteractive -Command -`
134
- * and reads the script from stdin (the `-` at the end is the documented way
135
- * to tell PowerShell "the script is on stdin, not as an argument"). Stdin
136
- * pipe sidesteps the entire class of quoting bugs that arise from
137
- * interpolating multi-line / single-quoted / dollar-sign-laden scripts into
138
- * a `-Command "..."` string.
132
+ * `/c <cmd>`; PowerShell uses `-EncodedCommand <base64>`. The encoded payload
133
+ * avoids quoting bugs for multi-line, quoted, and dollar-sign-laden scripts.
139
134
  */
140
135
  export declare function shellArgs(shell: BashShell): string[];
141
136
  /**
package/dist/bash.js CHANGED
@@ -119,6 +119,124 @@ function createOutputSpool(opts) {
119
119
  };
120
120
  }
121
121
 
122
+ // src/_shell-pick.ts
123
+ var POSIX_DEFAULT = "cmd";
124
+ function pickShell(platform4, command, env) {
125
+ if (platform4 !== "win32") return POSIX_DEFAULT;
126
+ const override = env.get("WRONGSTACK_SHELL")?.trim().toLowerCase();
127
+ if (override === "cmd" || override === "cmd.exe") return "cmd";
128
+ if (override === "powershell" || override === "powershell.exe") return "powershell";
129
+ if (override === "pwsh" || override === "pwsh.exe") return "pwsh";
130
+ if (looksLikePowerShell(command)) return "pwsh";
131
+ return "cmd";
132
+ }
133
+ function looksLikePowerShell(command) {
134
+ if (!command) return false;
135
+ const trimmed = command.trimStart();
136
+ if (/\.ps1\b/i.test(trimmed)) return true;
137
+ if (/^\s*#requires\s/i.test(trimmed)) return true;
138
+ if (/^\s*param\s*\(/i.test(trimmed)) return true;
139
+ if (/\$[\w:{]/i.test(trimmed)) return true;
140
+ if (/\$\(/.test(trimmed)) return true;
141
+ if (/@\s*['"]/.test(trimmed)) return true;
142
+ if (/&\s+\$/.test(trimmed)) return true;
143
+ if (/(^|\s)@\s*\(/.test(trimmed)) return true;
144
+ if (/(^|\s)@\{/.test(trimmed)) return true;
145
+ if (/(?:^|[\s[({,;])(?:-eq|-ne|-lt|-gt|-le|-ge|-like|-notlike|-match|-notmatch|-contains|-notcontains|-in|-notin|-and|-or|-not|-band|-bor|-bxor|-replace|-isplit|-csplit|-osplit|-join|-is|-as|-f)(?:$|[\s\])},;])/i.test(
146
+ trimmed
147
+ )) {
148
+ return true;
149
+ }
150
+ if (PS_VERB_RE.test(trimmed)) return true;
151
+ if (/(?:^|[\s;&|])(gci|gi|gp|gcm|gps)\b/i.test(trimmed)) {
152
+ return true;
153
+ }
154
+ if (looksLikePowerShellExtended(command)) return true;
155
+ return false;
156
+ }
157
+ function looksLikePowerShellExtended(command) {
158
+ if (!command) return false;
159
+ const trimmed = command.trimStart();
160
+ if (/(?:^|\s)[-/](?:WhatIf|Confirm|ErrorAction)(?::[^\s]+|\s|=|$)/i.test(trimmed)) {
161
+ return true;
162
+ }
163
+ if (/(?:^|[\s;&|])(Where-Object|ForEach-Object|Select-Object|Sort-Object|Group-Object|Measure-Object|Compare-Object|Tee-Object)(?:\s|$)/i.test(
164
+ trimmed
165
+ )) {
166
+ return true;
167
+ }
168
+ if (/\bWrite-(?:Host|Output|Error|Warning|Verbose|Debug|Information)(?:\s|$)/i.test(trimmed)) {
169
+ return true;
170
+ }
171
+ if (/HK(?:LM|CU|CR|U|CC|DD|PD):\\/i.test(trimmed)) return true;
172
+ if (/\[(?:string|int|bool|xml|double|float|decimal|char|byte|long|System\.)/i.test(trimmed)) {
173
+ return true;
174
+ }
175
+ if (/^\s*<#|#>\s*$/m.test(trimmed)) return true;
176
+ if (/(?:^|\s)[-//](?:AsPlainText|PipelineVariable|pv|FilterHashtable|OutVariable|ov)(?:\s|=|$)/i.test(
177
+ trimmed
178
+ )) {
179
+ return true;
180
+ }
181
+ return false;
182
+ }
183
+ function wrapPowerShellScript(command) {
184
+ const bootstrap = "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8;$ConfirmPreference='None';$WhatIfPreference=$false";
185
+ return bootstrap + "\n$ErrorActionPreference='Stop'\n" + command + "\nif ($LASTEXITCODE -is [int]) { exit $LASTEXITCODE }";
186
+ }
187
+ var PS_VERB_RE = new RegExp(
188
+ // Boundaries: start-of-string, whitespace, `;`, `&`, `|`, `(`, `{`, `,`.
189
+ "(?:^|[\\s;&|\\(\\{,])(?:Get|Set|New|Remove|Add|Clear|Copy|Move|Rename|Test|Update|Write|Read|Push|Pop|Invoke|Start|Stop|Wait|Out|Format|Group|Measure|Compare|Resolve|ConvertTo|ConvertFrom|Convert|Import|Export|Select|Where|ForEach|Sort|Tee|Split|Join|Limit|Skip|Step|Trace|Debug|Register|Unregister|Enable|Disable|Restart|Suspend|Resume|Save|Open|Close|Lock|Unlock|Mount|Dismount|Enter|Exit|Use|Show|Hide|Find|Search|Watch|Initialize|Optimize|Compress|Expand|Merge|Checkpoint|Undo|Redo|Approve|Deny|Block|Grant|Revoke|Assert|Confirm|Receive|Send|Connect|Disconnect|Reset|Backup|Restore|Publish|Unpublish|Install|Uninstall|Build|Rebuild|Deploy|Submit|Process|Complete|Approve|Revoke|Pay|Refund|Decline|Receive|Send)-[A-Za-z][A-Za-z0-9]+(?:[\\-\\+][A-Za-z][A-Za-z0-9]+)*(?:$|[\\s\\-\\;\\&\\|\\(\\)\\{\\},])",
190
+ "i"
191
+ );
192
+ function shellArgs(shell) {
193
+ if (shell === "powershell" || shell === "pwsh") {
194
+ return ["-NoLogo", "-NoProfile", "-NonInteractive", "-EncodedCommand"];
195
+ }
196
+ return ["/c"];
197
+ }
198
+ function diagnoseBashism(command, shell) {
199
+ if (!command) return void 0;
200
+ const isCmd = shell === "cmd";
201
+ const hints = [];
202
+ const add = (h) => {
203
+ if (!hints.includes(h)) hints.push(h);
204
+ };
205
+ if (/\/dev\/null/.test(command)) {
206
+ add(
207
+ isCmd ? "use `nul` instead of `/dev/null` (e.g. `2>nul`)" : "use `$null` instead of `/dev/null` (e.g. `2>$null`)"
208
+ );
209
+ }
210
+ if (/(^|[;&|]\s*)export\s+[A-Za-z_]\w*=/.test(command)) {
211
+ add(
212
+ isCmd ? "set env vars with `set NAME=value`, not `export`" : "set env vars with `$env:NAME = 'value'`, not `export`"
213
+ );
214
+ }
215
+ if (/<<-?\s*['"]?[A-Za-z_]\w*/.test(command)) {
216
+ add(
217
+ isCmd ? "cmd has no heredocs \u2014 write the content to a file or use multiple `echo` lines" : "PowerShell has no heredocs \u2014 use a single-quoted here-string `@'\u2026'@` (closing `'@` at column 0)"
218
+ );
219
+ }
220
+ if (shell === "powershell" && /(&&|\|\|)/.test(command)) {
221
+ add(
222
+ "Windows PowerShell 5.1 has no `&&`/`||` \u2014 separate commands with `;` (check `$LASTEXITCODE`)"
223
+ );
224
+ }
225
+ if (/\brm\s+-[A-Za-z]*[rf]/.test(command)) {
226
+ add(
227
+ isCmd ? "`rm` is not a cmd builtin \u2014 use `del` (files) or `rmdir /s /q` (dirs)" : "use `Remove-Item -Recurse -Force` \u2014 the `rm -rf` bash flags don't exist in PowerShell"
228
+ );
229
+ }
230
+ if (/\bwhich\s+\S/.test(command)) {
231
+ add(
232
+ isCmd ? "use `where <cmd>` instead of `which`" : "use `Get-Command <cmd>` instead of `which`"
233
+ );
234
+ }
235
+ if (hints.length === 0) return void 0;
236
+ const label = isCmd ? "cmd.exe" : shell === "pwsh" ? "PowerShell 7" : "Windows PowerShell";
237
+ return `[wrongstack] This command failed and contains bash/POSIX syntax that ${label} does not accept \u2014 ${hints.join("; ")}. Rewrite it in ${isCmd ? "cmd" : "PowerShell"} syntax and retry.`;
238
+ }
239
+
122
240
  // src/_util.ts
123
241
  import * as Core from "@wrongstack/core/utils";
124
242
  var COMMAND_OUTPUT_MAX_BYTES = 32768;
@@ -192,6 +310,54 @@ function normalizeCommandOutput(raw, opts = {}) {
192
310
  return truncateHeadTail(text, opts.maxBytes ?? COMMAND_OUTPUT_MAX_BYTES);
193
311
  }
194
312
 
313
+ // src/_win32-resolve.ts
314
+ import * as fs from "node:fs";
315
+ import * as path2 from "node:path";
316
+ function resolveWin32Command(cmd) {
317
+ if (process.platform !== "win32") return cmd;
318
+ if (cmd.includes("/") || cmd.includes("\\") || path2.extname(cmd.replace(/\//g, "\\"))) {
319
+ return cmd;
320
+ }
321
+ const pathext = (process.env["PATHEXT"] ?? ".COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC").toLowerCase().split(";");
322
+ const pathDirs = (process.env["PATH"] ?? "").split(path2.delimiter);
323
+ for (const dir of pathDirs) {
324
+ const base = path2.join(dir, cmd);
325
+ for (const ext of pathext) {
326
+ const full = `${base}${ext}`;
327
+ try {
328
+ fs.accessSync(full, fs.constants.X_OK);
329
+ return full;
330
+ } catch {
331
+ }
332
+ }
333
+ }
334
+ return cmd;
335
+ }
336
+ function resolvePowerShell(cmd) {
337
+ if (process.platform !== "win32") return cmd;
338
+ const lower = cmd.toLowerCase();
339
+ if (lower !== "pwsh" && lower !== "powershell" && lower !== "pwsh.exe" && lower !== "powershell.exe") {
340
+ return resolveWin32Command(cmd);
341
+ }
342
+ const primary = lower.startsWith("pwsh") ? "pwsh" : "powershell";
343
+ const fallback = lower.startsWith("pwsh") ? "powershell" : "pwsh";
344
+ const resolved = resolveWin32Command(primary);
345
+ if (resolved === primary) {
346
+ const fb = resolveWin32Command(fallback);
347
+ return fb === fallback ? cmd : fb;
348
+ }
349
+ return resolved;
350
+ }
351
+
352
+ // src/bash-kill-guard.ts
353
+ import * as os3 from "node:os";
354
+
355
+ // src/process-registry-persistent.ts
356
+ import * as fs2 from "node:fs/promises";
357
+ import * as os2 from "node:os";
358
+ import { wstackGlobalRoot as wstackGlobalRoot3 } from "@wrongstack/core/utils";
359
+ import * as path3 from "node:path";
360
+
195
361
  // src/process-registry.ts
196
362
  import { spawn } from "node:child_process";
197
363
  import * as os from "node:os";
@@ -841,14 +1007,7 @@ function getProcessRegistry() {
841
1007
  return _registry;
842
1008
  }
843
1009
 
844
- // src/bash-kill-guard.ts
845
- import * as os3 from "node:os";
846
-
847
1010
  // src/process-registry-persistent.ts
848
- import * as fs from "node:fs/promises";
849
- import * as os2 from "node:os";
850
- import { wstackGlobalRoot as wstackGlobalRoot3 } from "@wrongstack/core/utils";
851
- import * as path2 from "node:path";
852
1011
  var REGISTRY_FILE = "process-registry.json";
853
1012
  function toErrorMessage(err) {
854
1013
  return err instanceof Error ? err.message : String(err);
@@ -883,22 +1042,22 @@ async function acquireLock(lockfilePath, timeoutMs = 5e3) {
883
1042
  const pidStr = String(process.pid);
884
1043
  const hostStr = os2.hostname();
885
1044
  try {
886
- await fs.mkdir(path2.dirname(lockfilePath), { recursive: true });
1045
+ await fs2.mkdir(path3.dirname(lockfilePath), { recursive: true });
887
1046
  } catch {
888
1047
  }
889
1048
  while (Date.now() - start < timeoutMs) {
890
1049
  try {
891
- await fs.writeFile(lockfilePath, `${pidStr}:${hostStr}:${Date.now()}`, { flag: "wx" });
1050
+ await fs2.writeFile(lockfilePath, `${pidStr}:${hostStr}:${Date.now()}`, { flag: "wx" });
892
1051
  return async () => {
893
1052
  try {
894
- await fs.unlink(lockfilePath);
1053
+ await fs2.unlink(lockfilePath);
895
1054
  } catch {
896
1055
  }
897
1056
  };
898
1057
  } catch (err) {
899
1058
  if (isNodeError(err) && err.code === "EEXIST") {
900
1059
  try {
901
- const content = await fs.readFile(lockfilePath, "utf-8");
1060
+ const content = await fs2.readFile(lockfilePath, "utf-8");
902
1061
  const parts = content.split(":");
903
1062
  const lockPid = parseInt(parts[0] ?? "0", 10);
904
1063
  const lockTs = Number(parts[parts.length - 1]);
@@ -912,12 +1071,12 @@ async function acquireLock(lockfilePath, timeoutMs = 5e3) {
912
1071
  }
913
1072
  }
914
1073
  if (holderDead || staleByAge) {
915
- await fs.unlink(lockfilePath).catch(() => {
1074
+ await fs2.unlink(lockfilePath).catch(() => {
916
1075
  });
917
1076
  continue;
918
1077
  }
919
1078
  } catch {
920
- await fs.unlink(lockfilePath).catch(() => {
1079
+ await fs2.unlink(lockfilePath).catch(() => {
921
1080
  });
922
1081
  continue;
923
1082
  }
@@ -940,7 +1099,7 @@ function freshRegistryData() {
940
1099
  async function readRegistryFile(filePath) {
941
1100
  let content;
942
1101
  try {
943
- content = await fs.readFile(filePath, "utf-8");
1102
+ content = await fs2.readFile(filePath, "utf-8");
944
1103
  } catch (err) {
945
1104
  if (isNodeError(err) && err.code === "ENOENT") return freshRegistryData();
946
1105
  throw err;
@@ -971,8 +1130,8 @@ async function writeRegistryFile(filePath, data) {
971
1130
  },
972
1131
  2
973
1132
  );
974
- await fs.writeFile(tmpPath, content, "utf-8");
975
- await fs.rename(tmpPath, filePath);
1133
+ await fs2.writeFile(tmpPath, content, "utf-8");
1134
+ await fs2.rename(tmpPath, filePath);
976
1135
  }
977
1136
  var PersistentProcessRegistry = class {
978
1137
  instanceId;
@@ -994,8 +1153,8 @@ var PersistentProcessRegistry = class {
994
1153
  constructor(baseRegistry) {
995
1154
  this.instanceId = generateInstanceId();
996
1155
  const globalRoot = wstackGlobalRoot3();
997
- this.registryPath = path2.join(globalRoot, REGISTRY_FILE);
998
- this.lockPath = path2.join(globalRoot, LOCKFILE);
1156
+ this.registryPath = path3.join(globalRoot, REGISTRY_FILE);
1157
+ this.lockPath = path3.join(globalRoot, LOCKFILE);
999
1158
  this.baseRegistry = baseRegistry ?? getProcessRegistry();
1000
1159
  this.ensureDirectory().catch((err) => {
1001
1160
  emitStructuredLog(
@@ -1007,9 +1166,9 @@ var PersistentProcessRegistry = class {
1007
1166
  });
1008
1167
  }
1009
1168
  async ensureDirectory() {
1010
- const dir = path2.dirname(this.registryPath);
1169
+ const dir = path3.dirname(this.registryPath);
1011
1170
  try {
1012
- await fs.mkdir(dir, { recursive: true });
1171
+ await fs2.mkdir(dir, { recursive: true });
1013
1172
  } catch (err) {
1014
1173
  if (!isNodeError(err) || err.code !== "EEXIST") throw err;
1015
1174
  }
@@ -1679,163 +1838,6 @@ async function checkAndBlockKillCommand(command) {
1679
1838
  return { blocked: false };
1680
1839
  }
1681
1840
 
1682
- // src/_shell-pick.ts
1683
- var POSIX_DEFAULT = "cmd";
1684
- function pickShell(platform4, command, env) {
1685
- if (platform4 !== "win32") return POSIX_DEFAULT;
1686
- const override = env.get("WRONGSTACK_SHELL")?.trim().toLowerCase();
1687
- if (override === "cmd" || override === "cmd.exe") return "cmd";
1688
- if (override === "powershell" || override === "powershell.exe") return "powershell";
1689
- if (override === "pwsh" || override === "pwsh.exe") return "pwsh";
1690
- if (looksLikePowerShell(command)) return "pwsh";
1691
- return "cmd";
1692
- }
1693
- function looksLikePowerShell(command) {
1694
- if (!command) return false;
1695
- const trimmed = command.trimStart();
1696
- if (/\.ps1\b/i.test(trimmed)) return true;
1697
- if (/^\s*#requires\s/i.test(trimmed)) return true;
1698
- if (/^\s*param\s*\(/i.test(trimmed)) return true;
1699
- if (/\$[\w:{]/i.test(trimmed)) return true;
1700
- if (/\$\(/.test(trimmed)) return true;
1701
- if (/@\s*['"]/.test(trimmed)) return true;
1702
- if (/&\s+\$/.test(trimmed)) return true;
1703
- if (/(^|\s)@\s*\(/.test(trimmed)) return true;
1704
- if (/(^|\s)@\{/.test(trimmed)) return true;
1705
- if (/(?:^|[\s[({,;])(?:-eq|-ne|-lt|-gt|-le|-ge|-like|-notlike|-match|-notmatch|-contains|-notcontains|-in|-notin|-and|-or|-not|-band|-bor|-bxor|-replace|-isplit|-csplit|-osplit|-join|-is|-as|-f)(?:$|[\s\])},;])/i.test(
1706
- trimmed
1707
- )) {
1708
- return true;
1709
- }
1710
- if (PS_VERB_RE.test(trimmed)) return true;
1711
- if (/(?:^|[\s;&|])(gci|gi|gp|gcm|gps)\b/i.test(trimmed)) {
1712
- return true;
1713
- }
1714
- if (looksLikePowerShellExtended(command)) return true;
1715
- return false;
1716
- }
1717
- function looksLikePowerShellExtended(command) {
1718
- if (!command) return false;
1719
- const trimmed = command.trimStart();
1720
- if (/(?:^|\s)[-/](?:WhatIf|Confirm|ErrorAction)(?::[^\s]+|\s|=|$)/i.test(trimmed)) {
1721
- return true;
1722
- }
1723
- if (/(?:^|[\s;&|])(Where-Object|ForEach-Object|Select-Object|Sort-Object|Group-Object|Measure-Object|Compare-Object|Tee-Object)(?:\s|$)/i.test(
1724
- trimmed
1725
- )) {
1726
- return true;
1727
- }
1728
- if (/\bWrite-(?:Host|Output|Error|Warning|Verbose|Debug|Information)(?:\s|$)/i.test(trimmed)) {
1729
- return true;
1730
- }
1731
- if (/HK(?:LM|CU|CR|U|CC|DD|PD):\\/i.test(trimmed)) return true;
1732
- if (/\[(?:string|int|bool|xml|double|float|decimal|char|byte|long|System\.)/i.test(trimmed)) {
1733
- return true;
1734
- }
1735
- if (/^\s*<#|#>\s*$/m.test(trimmed)) return true;
1736
- if (/(?:^|\s)[-//](?:AsPlainText|PipelineVariable|pv|FilterHashtable|OutVariable|ov)(?:\s|=|$)/i.test(
1737
- trimmed
1738
- )) {
1739
- return true;
1740
- }
1741
- return false;
1742
- }
1743
- function wrapPowerShellScript(command) {
1744
- const bootstrap = "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8;$ConfirmPreference='None';$WhatIfPreference=$false";
1745
- return bootstrap + "\n$ErrorActionPreference='Stop'\n" + command + "\nif ($LASTEXITCODE -is [int]) { exit $LASTEXITCODE }";
1746
- }
1747
- var PS_VERB_RE = new RegExp(
1748
- // Boundaries: start-of-string, whitespace, `;`, `&`, `|`, `(`, `{`, `,`.
1749
- "(?:^|[\\s;&|\\(\\{,])(?:Get|Set|New|Remove|Add|Clear|Copy|Move|Rename|Test|Update|Write|Read|Push|Pop|Invoke|Start|Stop|Wait|Out|Format|Group|Measure|Compare|Resolve|ConvertTo|ConvertFrom|Convert|Import|Export|Select|Where|ForEach|Sort|Tee|Split|Join|Limit|Skip|Step|Trace|Debug|Register|Unregister|Enable|Disable|Restart|Suspend|Resume|Save|Open|Close|Lock|Unlock|Mount|Dismount|Enter|Exit|Use|Show|Hide|Find|Search|Watch|Initialize|Optimize|Compress|Expand|Merge|Checkpoint|Undo|Redo|Approve|Deny|Block|Grant|Revoke|Assert|Confirm|Receive|Send|Connect|Disconnect|Reset|Backup|Restore|Publish|Unpublish|Install|Uninstall|Build|Rebuild|Deploy|Submit|Process|Complete|Approve|Revoke|Pay|Refund|Decline|Receive|Send)-[A-Za-z][A-Za-z0-9]+(?:[\\-\\+][A-Za-z][A-Za-z0-9]+)*(?:$|[\\s\\-\\;\\&\\|\\(\\)\\{\\},])",
1750
- "i"
1751
- );
1752
- function shellArgs(shell) {
1753
- if (shell === "powershell" || shell === "pwsh") {
1754
- return ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", "-"];
1755
- }
1756
- return ["/c"];
1757
- }
1758
- function diagnoseBashism(command, shell) {
1759
- if (!command) return void 0;
1760
- const isCmd = shell === "cmd";
1761
- const hints = [];
1762
- const add = (h) => {
1763
- if (!hints.includes(h)) hints.push(h);
1764
- };
1765
- if (/\/dev\/null/.test(command)) {
1766
- add(
1767
- isCmd ? "use `nul` instead of `/dev/null` (e.g. `2>nul`)" : "use `$null` instead of `/dev/null` (e.g. `2>$null`)"
1768
- );
1769
- }
1770
- if (/(^|[;&|]\s*)export\s+[A-Za-z_]\w*=/.test(command)) {
1771
- add(
1772
- isCmd ? "set env vars with `set NAME=value`, not `export`" : "set env vars with `$env:NAME = 'value'`, not `export`"
1773
- );
1774
- }
1775
- if (/<<-?\s*['"]?[A-Za-z_]\w*/.test(command)) {
1776
- add(
1777
- isCmd ? "cmd has no heredocs \u2014 write the content to a file or use multiple `echo` lines" : "PowerShell has no heredocs \u2014 use a single-quoted here-string `@'\u2026'@` (closing `'@` at column 0)"
1778
- );
1779
- }
1780
- if (shell === "powershell" && /(&&|\|\|)/.test(command)) {
1781
- add(
1782
- "Windows PowerShell 5.1 has no `&&`/`||` \u2014 separate commands with `;` (check `$LASTEXITCODE`)"
1783
- );
1784
- }
1785
- if (/\brm\s+-[A-Za-z]*[rf]/.test(command)) {
1786
- add(
1787
- isCmd ? "`rm` is not a cmd builtin \u2014 use `del` (files) or `rmdir /s /q` (dirs)" : "use `Remove-Item -Recurse -Force` \u2014 the `rm -rf` bash flags don't exist in PowerShell"
1788
- );
1789
- }
1790
- if (/\bwhich\s+\S/.test(command)) {
1791
- add(
1792
- isCmd ? "use `where <cmd>` instead of `which`" : "use `Get-Command <cmd>` instead of `which`"
1793
- );
1794
- }
1795
- if (hints.length === 0) return void 0;
1796
- const label = isCmd ? "cmd.exe" : shell === "pwsh" ? "PowerShell 7" : "Windows PowerShell";
1797
- return `[wrongstack] This command failed and contains bash/POSIX syntax that ${label} does not accept \u2014 ${hints.join("; ")}. Rewrite it in ${isCmd ? "cmd" : "PowerShell"} syntax and retry.`;
1798
- }
1799
-
1800
- // src/_win32-resolve.ts
1801
- import * as fs2 from "node:fs";
1802
- import * as path3 from "node:path";
1803
- function resolveWin32Command(cmd) {
1804
- if (process.platform !== "win32") return cmd;
1805
- if (cmd.includes("/") || cmd.includes("\\") || path3.extname(cmd.replace(/\//g, "\\"))) {
1806
- return cmd;
1807
- }
1808
- const pathext = (process.env["PATHEXT"] ?? ".COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC").toLowerCase().split(";");
1809
- const pathDirs = (process.env["PATH"] ?? "").split(path3.delimiter);
1810
- for (const dir of pathDirs) {
1811
- const base = path3.join(dir, cmd);
1812
- for (const ext of pathext) {
1813
- const full = `${base}${ext}`;
1814
- try {
1815
- fs2.accessSync(full, fs2.constants.X_OK);
1816
- return full;
1817
- } catch {
1818
- }
1819
- }
1820
- }
1821
- return cmd;
1822
- }
1823
- function resolvePowerShell(cmd) {
1824
- if (process.platform !== "win32") return cmd;
1825
- const lower = cmd.toLowerCase();
1826
- if (lower !== "pwsh" && lower !== "powershell" && lower !== "pwsh.exe" && lower !== "powershell.exe") {
1827
- return resolveWin32Command(cmd);
1828
- }
1829
- const primary = lower.startsWith("pwsh") ? "pwsh" : "powershell";
1830
- const fallback = lower.startsWith("pwsh") ? "powershell" : "pwsh";
1831
- const resolved = resolveWin32Command(primary);
1832
- if (resolved === primary) {
1833
- const fb = resolveWin32Command(fallback);
1834
- return fb === fallback ? cmd : fb;
1835
- }
1836
- return resolved;
1837
- }
1838
-
1839
1841
  // src/bash.ts
1840
1842
  var MAX_OUTPUT = 32768;
1841
1843
  var DEFAULT_TIMEOUT_MS = 3e5;
@@ -1944,8 +1946,7 @@ var bashTool = {
1944
1946
  plan = {
1945
1947
  bin,
1946
1948
  argv: shellArgs(shell2),
1947
- useStdin: shell2 === "powershell" || shell2 === "pwsh",
1948
- stdinBody: shell2 === "powershell" || shell2 === "pwsh" ? wrapPowerShellScript(input.command) : void 0
1949
+ commandArg: shell2 === "powershell" || shell2 === "pwsh" ? Buffer.from(wrapPowerShellScript(input.command), "utf16le").toString("base64") : input.command
1949
1950
  };
1950
1951
  } else {
1951
1952
  const explicit = process.env["WRONGSTACK_SHELL"];
@@ -1959,10 +1960,10 @@ var bashTool = {
1959
1960
  else bin = "/bin/bash";
1960
1961
  } else bin = "/bin/bash";
1961
1962
  }
1962
- plan = { bin, argv: ["-c"], useStdin: false, stdinBody: void 0 };
1963
+ plan = { bin, argv: ["-c"], commandArg: input.command };
1963
1964
  }
1964
1965
  const shell = plan.bin;
1965
- const args = plan.useStdin ? [...plan.argv] : [...plan.argv, input.command];
1966
+ const args = [...plan.argv, plan.commandArg];
1966
1967
  const env = buildChildEnv(ctx.session?.id);
1967
1968
  const spawnCwd = ctx.workingDir ?? ctx.projectRoot;
1968
1969
  const detached = !isWin2;
@@ -1971,26 +1972,10 @@ var bashTool = {
1971
1972
  const child2 = spawn2(shell, args, {
1972
1973
  cwd: spawnCwd,
1973
1974
  env,
1974
- // PowerShell takes the script on stdin (no argv quoting); cmd.exe
1975
- // and POSIX shells ignore stdin when given the command inline.
1976
- stdio: [plan.useStdin ? "pipe" : "ignore", "ignore", "ignore"],
1977
- // win32: CreateProcess IGNORES CREATE_NO_WINDOW (windowsHide) when
1978
- // DETACHED_PROCESS (detached: true) is set, so the console-less
1979
- // cmd.exe's grandchildren (node, dev servers) each allocate a fresh
1980
- // VISIBLE console window. detached: false lets CREATE_NO_WINDOW
1981
- // apply: the child gets a hidden console that grandchildren inherit.
1982
- // Windows children survive parent exit either way. POSIX keeps
1983
- // detached for the process-group kill semantics.
1975
+ stdio: ["ignore", "ignore", "ignore"],
1984
1976
  detached: !isWin2,
1985
1977
  windowsHide: true
1986
1978
  });
1987
- if (plan.useStdin) {
1988
- try {
1989
- child2.stdin?.write(plan.stdinBody ?? input.command);
1990
- child2.stdin?.end();
1991
- } catch {
1992
- }
1993
- }
1994
1979
  const pid2 = child2.pid;
1995
1980
  const stdoutBytes2 = 0;
1996
1981
  const stderrBytes2 = 0;
@@ -2065,20 +2050,11 @@ var bashTool = {
2065
2050
  const child = spawn2(shell, args, {
2066
2051
  cwd: spawnCwd,
2067
2052
  env,
2068
- // PowerShell takes the script on stdin (no argv quoting); cmd.exe
2069
- // and POSIX shells ignore stdin when given the command inline.
2070
- stdio: [plan.useStdin ? "pipe" : "ignore", "pipe", "pipe"],
2053
+ stdio: ["ignore", "pipe", "pipe"],
2071
2054
  detached,
2072
2055
  windowsHide: true,
2073
2056
  ...isWin2 ? {} : { signal: opts.signal }
2074
2057
  });
2075
- if (plan.useStdin) {
2076
- try {
2077
- child.stdin?.write(plan.stdinBody ?? input.command);
2078
- child.stdin?.end();
2079
- } catch {
2080
- }
2081
- }
2082
2058
  const pid = child.pid;
2083
2059
  let stdoutBytes = 0;
2084
2060
  let stderrBytes = 0;