@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.
- package/dist/_shell-pick.d.ts +5 -10
- package/dist/bash.js +184 -208
- package/dist/builtin.js +136 -172
- package/dist/index.js +136 -172
- package/dist/pack.js +136 -172
- package/dist/tool-tier.js +136 -172
- package/package.json +5 -5
package/dist/_shell-pick.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
125
|
-
* not
|
|
126
|
-
*
|
|
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 `-
|
|
134
|
-
*
|
|
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
|
|
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
|
|
1050
|
+
await fs2.writeFile(lockfilePath, `${pidStr}:${hostStr}:${Date.now()}`, { flag: "wx" });
|
|
892
1051
|
return async () => {
|
|
893
1052
|
try {
|
|
894
|
-
await
|
|
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
|
|
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
|
|
1074
|
+
await fs2.unlink(lockfilePath).catch(() => {
|
|
916
1075
|
});
|
|
917
1076
|
continue;
|
|
918
1077
|
}
|
|
919
1078
|
} catch {
|
|
920
|
-
await
|
|
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
|
|
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
|
|
975
|
-
await
|
|
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 =
|
|
998
|
-
this.lockPath =
|
|
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 =
|
|
1169
|
+
const dir = path3.dirname(this.registryPath);
|
|
1011
1170
|
try {
|
|
1012
|
-
await
|
|
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
|
-
|
|
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"],
|
|
1963
|
+
plan = { bin, argv: ["-c"], commandArg: input.command };
|
|
1963
1964
|
}
|
|
1964
1965
|
const shell = plan.bin;
|
|
1965
|
-
const args =
|
|
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
|
-
|
|
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
|
-
|
|
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;
|