@poe-platform/safe-bash 0.1.821 → 0.1.823

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.
Files changed (93) hide show
  1. package/dist/safe-bash/chunks/{chunk-HNT2MR4W.js → chunk-363QOIDR.js} +2 -1
  2. package/dist/safe-bash/chunks/chunk-AGHV53VF.js +42 -0
  3. package/dist/safe-bash/chunks/{chunk-VF3Q2V3E.js → chunk-RAFD5ZAH.js} +1 -1
  4. package/dist/safe-bash/chunks/{chunk-67QE3JLU.js → chunk-TZRVK3NU.js} +1 -1
  5. package/dist/safe-bash/chunks/{chunk-IRJS54AA.js → chunk-VWLQDZFF.js} +20 -12
  6. package/dist/safe-bash/chunks/{chunk-NAETSXBW.js → chunk-ZJAXMB5M.js} +1 -1
  7. package/dist/safe-bash/command-chunks/{chunk-QX3WKBLI.js → chunk-36FV6BAG.js} +5 -3
  8. package/dist/safe-bash/command-chunks/{chunk-ZSHXZ3XG.js → chunk-3EAZRKI4.js} +7 -7
  9. package/dist/safe-bash/command-chunks/chunk-64HIOP7Q.js +993 -0
  10. package/dist/safe-bash/command-chunks/{chunk-WOT2YXBT.js → chunk-7ZQVMPSB.js} +213 -42
  11. package/dist/safe-bash/command-chunks/{chunk-GRFJKJFN.js → chunk-AVSINNZ6.js} +233 -3
  12. package/dist/safe-bash/command-chunks/{chunk-BZLLQWRK.js → chunk-E5CDHXFA.js} +2 -2
  13. package/dist/safe-bash/command-chunks/{chunk-RHXV2KG5.js → chunk-EO3RYGUZ.js} +1010 -58
  14. package/dist/safe-bash/command-chunks/{chunk-MCUDSE4H.js → chunk-EWECMOQ5.js} +1 -1
  15. package/dist/safe-bash/command-chunks/{chunk-RTUDDAAY.js → chunk-O3KFOFJM.js} +1 -1
  16. package/dist/safe-bash/command-chunks/{chunk-SLD5A7IL.js → chunk-OU5UEUZN.js} +1 -1
  17. package/dist/safe-bash/command-chunks/{chunk-RKAL5VFP.js → chunk-PLVFJ6FF.js} +255 -616
  18. package/dist/safe-bash/command-chunks/{chunk-CJIFJ45R.js → chunk-QBCEAYQT.js} +1 -1
  19. package/dist/safe-bash/command-chunks/{chunk-4Q3HP33O.js → chunk-RGKHTSUY.js} +146 -123
  20. package/dist/safe-bash/command-chunks/{chunk-I7MEH76L.js → chunk-UYNJF5QN.js} +1 -1
  21. package/dist/safe-bash/command-chunks/{chunk-IAHYG67O.js → chunk-V5ZBRM5R.js} +3 -3
  22. package/dist/safe-bash/command-chunks/{chunk-R4NPYXQX.js → chunk-WDTWJFPK.js} +320 -67
  23. package/dist/safe-bash/command-chunks/{chunk-GQV5RWMN.js → chunk-ZFXHSFTU.js} +1 -1
  24. package/dist/safe-bash/command-chunks/{prompt-tool-debug-ERJKK3XZ.js → prompt-tool-debug-3POFNRYP.js} +5 -5
  25. package/dist/safe-bash/commands/bc/index.browser.js +1 -1
  26. package/dist/safe-bash/commands/llm/index.browser.js +3 -1
  27. package/dist/safe-bash/commands/llm/index.js +1 -1
  28. package/dist/safe-bash/commands/python/executor.browser.js +12 -1
  29. package/dist/safe-bash/commands/python/executor.js +1 -1
  30. package/dist/safe-bash/commands/python/index.browser.js +23 -1
  31. package/dist/safe-bash/commands/python/index.js +1 -1
  32. package/dist/safe-bash/core.browser.js +2 -2
  33. package/dist/safe-bash/full-core.browser.js +30 -6
  34. package/dist/safe-bash/plugins/index.browser.js +3 -3
  35. package/dist/safe-bash/registry-entry.browser.js +2 -2
  36. package/dist/safe-bash/search.browser.js +1 -1
  37. package/dist/safe-bash/shell/runtime.d.ts +2 -2
  38. package/dist/safe-bash/shell/runtime.js +11 -2
  39. package/dist/safe-bash/shell/sync-extra-evaluators.js +12 -12
  40. package/dist/safe-bash/shell-entry.browser.js +2 -2
  41. package/dist/safe-bash-command-llm/collections.js +24 -65
  42. package/dist/safe-bash-command-llm/command.js +6 -6
  43. package/dist/safe-bash-command-llm/fragment-loaders.d.ts +3 -2
  44. package/dist/safe-bash-command-llm/index.d.ts +3 -2
  45. package/dist/safe-bash-command-llm/index.js +10 -8
  46. package/dist/safe-bash-command-llm/loader-provider.d.ts +25 -0
  47. package/dist/safe-bash-command-llm/openai-sse.js +2 -2
  48. package/dist/safe-bash-command-llm/openai.js +4 -4
  49. package/dist/safe-bash-command-llm/providers/index.js +4 -4
  50. package/dist/safe-bash-command-llm/providers/openai.js +4 -4
  51. package/dist/safe-bash-command-llm/python-unicode.d.ts +2 -0
  52. package/dist/safe-bash-command-llm/templates.d.ts +7 -2
  53. package/dist/safe-bash-command-llm/types.d.ts +7 -1
  54. package/dist/safe-bash-command-mcp/index.browser.js +3 -0
  55. package/dist/safe-bash-command-mcp/index.js +3 -0
  56. package/dist/safe-bash-command-patch/index.js +2 -2
  57. package/dist/safe-bash-command-patch/patch-publication.js +1 -1
  58. package/dist/safe-bash-command-patch/patch.js +2 -2
  59. package/dist/safe-bash-command-python/build-backend.d.ts +51 -0
  60. package/dist/safe-bash-command-python/build-dependencies.d.ts +12 -0
  61. package/dist/safe-bash-command-python/build-environment.d.ts +3 -0
  62. package/dist/safe-bash-command-python/build-wheel.d.ts +8 -0
  63. package/dist/safe-bash-command-python/executor.d.ts +12 -2
  64. package/dist/safe-bash-command-python/executor.js +26 -4
  65. package/dist/safe-bash-command-python/index.js +26 -4
  66. package/dist/safe-bash-command-python/jspi.js +2 -2
  67. package/dist/safe-bash-command-python/llm-assets-node.js +1 -1
  68. package/dist/safe-bash-command-python/llm-fragment-loader.d.ts +6 -0
  69. package/dist/safe-bash-command-python/llm-functions-loader.d.ts +5 -1
  70. package/dist/safe-bash-command-python/llm-loader-discovery.d.ts +13 -0
  71. package/dist/safe-bash-command-python/llm-package-manager.d.ts +7 -0
  72. package/dist/safe-bash-command-python/llm-template-loader.d.ts +6 -0
  73. package/dist/safe-bash-command-python/manifest.d.ts +13 -0
  74. package/dist/safe-bash-command-python/provisioning.d.ts +33 -5
  75. package/dist/safe-bash-command-python/provisioning.js +1 -1
  76. package/dist/safe-bash-command-python/source-environment.d.ts +9 -0
  77. package/dist/safe-bash-command-python/source-snapshot.d.ts +8 -0
  78. package/dist/safe-bash-command-python/worker.js +3 -185
  79. package/dist/safe-bash-csv-engine/index.js +1 -1
  80. package/dist/safe-bash-csv-engine/python-iso2022.d.ts +21 -0
  81. package/dist/safe-bash-csv-engine/python-utf7.d.ts +18 -0
  82. package/dist/safe-bash-csv-engine/text-decoder.d.ts +2 -0
  83. package/dist/safe-bash-csv-engine/text-decoder.js +7 -3
  84. package/dist/safe-bash-zip-engine/index.js +1 -1
  85. package/dist/safe-bash-zip-engine/zip/repair.js +1 -1
  86. package/dist/safe-bash-zip-engine/zip/sfx.js +1 -1
  87. package/dist/safe-bash-zip-engine/zip-format.d.ts +3 -1
  88. package/dist/safe-bash-zip-engine/zip-format.js +1 -1
  89. package/dist/safe-playwright-cloudflare/shell-browser-resource.d.ts +1 -1
  90. package/dist/safe-playwright-cloudflare/shell-browser-resource.js +35 -20
  91. package/package.json +3 -3
  92. package/dist/safe-bash/chunks/chunk-PSD2RSOS.js +0 -31
  93. package/dist/safe-bash/command-chunks/chunk-5EFNQ6FY.js +0 -458
@@ -104,26 +104,34 @@ function createOwnedConnections(binding, sessionId, transportLimits = {}, limits
104
104
  snapshots.stop(interruption);
105
105
  clients.abort(interruption);
106
106
  }
107
- function attempt(operation) {
108
- try {
109
- return Promise.resolve(operation());
110
- }
111
- catch (error) {
112
- return Promise.reject(error);
113
- }
107
+ // Keep successful and uncertain work across caller deadlines. Only a settled
108
+ // failure permits another attempt at that phase.
109
+ const cleanup = new Map();
110
+ function attempt(phase, operation) {
111
+ const existing = cleanup.get(phase);
112
+ if (existing)
113
+ return existing;
114
+ const completion = Promise.resolve().then(operation).catch(error => {
115
+ cleanup.delete(phase);
116
+ throw error;
117
+ });
118
+ cleanup.set(phase, completion);
119
+ return completion;
114
120
  }
115
121
  function release() {
116
122
  if (releasing)
117
123
  return releasing;
118
- released = true;
119
- beginBrowserOwnerShutdown(upstreams);
120
- disconnect();
124
+ if (!released) {
125
+ released = true;
126
+ beginBrowserOwnerShutdown(upstreams);
127
+ disconnect();
128
+ }
121
129
  releasing = finishOwnedBrowserCleanup([
122
- ["snapshot capture", attempt(() => snapshots.settled())],
123
- ["storage control", attempt(() => control?.close())],
124
- ["private transport", attempt(() => privacy.close())],
125
- ["public connection", attempt(() => browser?.close())],
126
- ["provider deletion", attempt(async () => {
130
+ ["snapshot capture", attempt("snapshot capture", () => snapshots.settled())],
131
+ ["storage control", attempt("storage control", () => control?.close())],
132
+ ["private transport", attempt("private transport", () => privacy.close())],
133
+ ["public connection", attempt("public connection", () => browser?.close())],
134
+ ["provider deletion", attempt("provider deletion", async () => {
127
135
  try {
128
136
  await deleteBrowser();
129
137
  finalizeBrowserOwnerTermination(upstreams, true);
@@ -133,7 +141,7 @@ function createOwnedConnections(binding, sessionId, transportLimits = {}, limits
133
141
  throw error;
134
142
  }
135
143
  })],
136
- ["upstream closure", attempt(async () => {
144
+ ["upstream closure", attempt("upstream closure", async () => {
137
145
  const outcomes = await Promise.allSettled([...upstreams].map(waitForBrowserSocketClose));
138
146
  const failures = outcomes
139
147
  .filter((outcome) => outcome.status === "rejected")
@@ -141,7 +149,10 @@ function createOwnedConnections(binding, sessionId, transportLimits = {}, limits
141
149
  if (failures.length)
142
150
  throw new AggregateError(failures, "Owned browser upstream closure failed");
143
151
  })],
144
- ], releaseTimeoutMs === Infinity ? new AbortController().signal : AbortSignal.timeout(releaseTimeoutMs));
152
+ ], releaseTimeoutMs === Infinity ? new AbortController().signal : AbortSignal.timeout(releaseTimeoutMs)).catch(error => {
153
+ releasing = undefined;
154
+ throw error;
155
+ });
145
156
  return releasing;
146
157
  }
147
158
  async function setup(connect, signal) {
@@ -216,12 +227,15 @@ async function finishOwnedBrowserCleanup(operations, signal) {
216
227
  throw new AggregateError(failures, `Owned browser release failed (${details.join("; ")})`);
217
228
  }
218
229
  }
219
- /** Concurrent cleanup paths share one deletion and the same provider outcome. */
230
+ /** Share in-flight deletion and retain success; settled failures permit retry. */
220
231
  export function createCloudflareBrowserRelease(options) {
221
232
  validateDeadline(options.releaseTimeoutMs);
222
233
  let releasing;
223
234
  return () => {
224
- releasing ??= deleteOwnedBrowser(options);
235
+ releasing ??= deleteOwnedBrowser(options).catch(error => {
236
+ releasing = undefined;
237
+ throw error;
238
+ });
225
239
  return releasing;
226
240
  };
227
241
  }
@@ -229,6 +243,7 @@ async function deleteOwnedBrowser(options) {
229
243
  const timeout = options.releaseTimeoutMs ?? Infinity;
230
244
  const response = await options.binding.fetch(`http://fake.host/v1/devtools/browser/${encodeURIComponent(options.sessionId)}`, { method: "DELETE", signal: timeout === Infinity ? new AbortController().signal : AbortSignal.timeout(timeout) });
231
245
  await response.body?.cancel();
232
- if (!response.ok)
246
+ // A previous DELETE may have succeeded despite a lost response.
247
+ if (!response.ok && response.status !== 404)
233
248
  throw new Error(`Owned browser release failed: HTTP ${response.status}`);
234
249
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poe-platform/safe-bash",
3
- "version": "0.1.821",
3
+ "version": "0.1.823",
4
4
  "description": "Extensible virtual shell with pluggable filesystems and streaming commands",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1407,7 +1407,7 @@
1407
1407
  "access": "public"
1408
1408
  },
1409
1409
  "dependencies": {
1410
- "@poe-platform/safe-fs": "0.1.821",
1410
+ "@poe-platform/safe-fs": "0.1.823",
1411
1411
  "pdf-lib": "1.17.1",
1412
1412
  "@types/node": "^25.2.2",
1413
1413
  "@formatjs/intl-numberformat": "9.4.0",
@@ -2470,7 +2470,6 @@
2470
2470
  "./dist/safe-bash/chunks/chunk-A64DHHCT.js",
2471
2471
  "./dist/safe-bash/chunks/chunk-F76MF5GT.js",
2472
2472
  "./dist/safe-bash/chunks/chunk-IDPDAAE2.js",
2473
- "./dist/safe-bash/chunks/chunk-IRJS54AA.js",
2474
2473
  "./dist/safe-bash/chunks/chunk-JQILKYVZ.js",
2475
2474
  "./dist/safe-bash/chunks/chunk-KR4FSXLP.js",
2476
2475
  "./dist/safe-bash/chunks/chunk-KRKRFIK7.js",
@@ -2480,6 +2479,7 @@
2480
2479
  "./dist/safe-bash/chunks/chunk-Q3BXARJD.js",
2481
2480
  "./dist/safe-bash/chunks/chunk-R6ASA7FI.js",
2482
2481
  "./dist/safe-bash/chunks/chunk-RJ7D3WML.js",
2482
+ "./dist/safe-bash/chunks/chunk-VWLQDZFF.js",
2483
2483
  "./dist/safe-bash/chunks/chunk-ZMTSEPTF.js",
2484
2484
  "./dist/safe-bash/commands/apply-patch/index.js",
2485
2485
  "./dist/safe-bash/commands/archive/index.js",
@@ -1,31 +0,0 @@
1
- // packages/safe-bash/src/commands/python/index.ts
2
- import { PythonFailure, PythonPackageConflictError, createPythonCommands, createPythonExecutorCommands, createPythonExecutorPool, createPythonHostBridge, createPythonJspiAssets, createPythonJspiExecutor, createPythonJspiNativeCall, createPythonJspiStatResult, createPythonJspiTrampoline, createPythonLlmCapability, createPythonLlmToolLoader, createPythonPackageCache, createPythonPackageEnvironment, createPythonPackageManifestStore, createPythonShellCapability, inspectPythonCapabilities, installPythonLlmDependencies, installPythonLlmPackages, pythonCommands, pythonDocumentPackages, pythonExecutorCommands, pythonHostFailureCode, pythonLlmDependencies, pythonLlmPackages } from "../../safe-bash-command-python/index.js";
3
-
4
- export {
5
- PythonFailure,
6
- PythonPackageConflictError,
7
- createPythonCommands,
8
- createPythonExecutorCommands,
9
- createPythonExecutorPool,
10
- createPythonHostBridge,
11
- createPythonJspiAssets,
12
- createPythonJspiExecutor,
13
- createPythonJspiNativeCall,
14
- createPythonJspiStatResult,
15
- createPythonJspiTrampoline,
16
- createPythonLlmCapability,
17
- createPythonLlmToolLoader,
18
- createPythonPackageCache,
19
- createPythonPackageEnvironment,
20
- createPythonPackageManifestStore,
21
- createPythonShellCapability,
22
- inspectPythonCapabilities,
23
- installPythonLlmDependencies,
24
- installPythonLlmPackages,
25
- pythonCommands,
26
- pythonDocumentPackages,
27
- pythonExecutorCommands,
28
- pythonHostFailureCode,
29
- pythonLlmDependencies,
30
- pythonLlmPackages
31
- };
@@ -1,458 +0,0 @@
1
- // packages/safe-bash-command-python/src/invocation.ts
2
- var PythonInvocationError = class extends Error {
3
- constructor(message) {
4
- super(message);
5
- this.name = "PythonInvocationError";
6
- }
7
- };
8
- function parsePythonInvocation(args, env) {
9
- const startupArgs = [];
10
- let informational = false;
11
- scan: for (let index = 0; index < args.length; index++) {
12
- const option = args[index];
13
- if (option === "--" || option === "-" || !option.startsWith("-")) break;
14
- if (option === "--help") {
15
- informational = true;
16
- break;
17
- }
18
- if (option === "--version") {
19
- informational = true;
20
- continue;
21
- }
22
- if (option.startsWith("--")) throw new PythonInvocationError("unsupported option " + option);
23
- for (let offset = 1; offset < option.length; offset++) {
24
- const flag = option[offset];
25
- if (flag === "h" || flag === "?") {
26
- informational = true;
27
- break scan;
28
- }
29
- if (flag === "V") {
30
- informational = true;
31
- continue;
32
- }
33
- if ("cmWX".includes(flag)) {
34
- const operand = option.slice(offset + 1) || args[++index];
35
- if (operand === void 0) throw new PythonInvocationError("argument expected for -" + flag);
36
- if (flag === "c" || flag === "m") break scan;
37
- if (flag === "X") {
38
- const separator = operand.indexOf("=");
39
- const name = separator < 0 ? operand : operand.slice(0, separator);
40
- const value = separator < 0 ? void 0 : operand.slice(separator + 1);
41
- let decimal = value ?? "";
42
- while (decimal.length && " \n\r\v\f".includes(decimal[0])) decimal = decimal.slice(1);
43
- if (decimal[0] === "+" || decimal[0] === "-") decimal = decimal.slice(1);
44
- const digits = decimal.length > 0 && [...decimal].every((character) => character >= "0" && character <= "9");
45
- const supported = name === "utf8" && (value === void 0 || value === "0" || value === "1") || (name === "dev" || name === "warn_default_encoding") && value === void 0 || name === "int_max_str_digits" && digits && Number.isSafeInteger(Number(value)) && Number(value) <= 2147483647 && (Number(value) === 0 || Number(value) >= 640);
46
- if (!supported) throw new PythonInvocationError("unsupported -X option " + operand);
47
- startupArgs.push("-X", operand);
48
- }
49
- break;
50
- }
51
- if (!"BEPOusISbdvqR".includes(flag)) throw new PythonInvocationError("unsupported option -" + flag);
52
- startupArgs.push("-" + flag);
53
- }
54
- }
55
- if (!informational && !startupArgs.includes("-E") && !startupArgs.includes("-I")) {
56
- if (env.PYTHONINSPECT) throw new PythonInvocationError("PYTHONINSPECT requires an interactive terminal, which the shell does not expose");
57
- if (env.PYTHONHOME) throw new PythonInvocationError("PYTHONHOME cannot replace the configured Pyodide runtime");
58
- }
59
- const runtimeEnv = { ...env };
60
- delete runtimeEnv.PYTHONPATH;
61
- delete runtimeEnv.PYTHONHOME;
62
- delete runtimeEnv.PYTHONWARNINGS;
63
- runtimeEnv.PYTHONINSPECT = "";
64
- startupArgs.push("-c", "");
65
- return { startupArgs, env: runtimeEnv };
66
- }
67
-
68
- // packages/safe-bash-command-python/src/execution.ts
69
- var pythonExecution = `
70
- import sys, os, json, runpy, traceback, types, warnings, textwrap, io, struct, linecache
71
- _safe_invocation = json.loads(_safe_invocation_json)
72
- os.environ.clear()
73
- os.environ.update(_safe_invocation['env'])
74
- _safe_args = list(_safe_invocation['args'])
75
- sys.orig_argv = [_safe_invocation.get('command', 'python')] + _safe_args
76
- _safe_exit = 0
77
-
78
- class _SafeOptionError(ValueError):
79
- pass
80
-
81
- def _safe_system_exit(_error):
82
- if isinstance(_error.code, int):
83
- _status = int.__index__(_error.code)
84
- _long_bound = 1 << (8 * struct.calcsize('@l') - 1)
85
- status = (_status & 255) if -_long_bound <= _status < _long_bound else 255
86
- else:
87
- status = 0 if _error.code is None else 1
88
- if _error.code is not None and not isinstance(_error.code, int):
89
- print(_error.code, file=sys.stderr)
90
- return status
91
-
92
- def _safe_launch():
93
- args = list(_safe_args)
94
- unbuffered = False
95
- ignore_env = bool(sys.flags.ignore_environment)
96
- safe_path = bool(sys.flags.safe_path)
97
- native_warning_options = list(sys.warnoptions)
98
- # Pyodide adds a bootstrap cwd entry even when CPython safe-path mode is set.
99
- # Each execution mode below supplies its own entry after explicit PYTHONPATH.
100
- sys.path[:] = [entry for entry in sys.path if entry != '']
101
- warning_options = []
102
- version_count = 0
103
- options_terminated = False
104
- while args and args[0].startswith('-') and args[0] != '-':
105
- option = args.pop(0)
106
- if option == '--':
107
- options_terminated = True
108
- break
109
- if option in ('--help', '-h', '-?'):
110
- print('usage: python [-uBEPOsSIbdvqR] [-W filter] [-X option] [-c code | -m module | file | -] [args ...]')
111
- print('Dedicated Pyodide interpreter; stdin is noninteractive.')
112
- print('-u: unbuffered binary output; -B: disable bytecode writes; -E: ignore PYTHON* settings')
113
- print('-P: omit automatic unsafe sys.path entry; -W: Python warning filter')
114
- print('-O/-OO, -S/-s/-I, -b/-bb, -d/-v/-q/-R: CPython startup flags')
115
- print('-X: utf8, dev, warn_default_encoding, int_max_str_digits; interactive flags are unsupported.')
116
- return
117
- if option == '--version':
118
- version_count += 1
119
- continue
120
- if option.startswith('--'):
121
- raise _SafeOptionError('unsupported option ' + option)
122
- index = 1
123
- while index < len(option):
124
- flag = option[index]
125
- if flag in ('h', '?'):
126
- args = ['--help']
127
- break
128
- if flag == 'V':
129
- version_count += 1
130
- index += 1
131
- continue
132
- if flag in ('c', 'm', 'W', 'X'):
133
- operand = option[index + 1:] if index + 1 < len(option) else args.pop(0) if args else None
134
- if operand is None:
135
- raise _SafeOptionError('argument expected for -' + flag)
136
- if flag in ('W', 'X'):
137
- if flag == 'W': warning_options.append(operand)
138
- break
139
- args = ['-' + flag, operand] + args
140
- break
141
- elif flag in ('O', 'S', 's', 'I', 'b', 'd', 'v', 'q', 'R'):
142
- pass # Applied by CPython startup configuration before bootstrap.
143
- elif flag == 'u':
144
- unbuffered = True
145
- elif flag == 'B':
146
- sys.dont_write_bytecode = True
147
- elif flag == 'E':
148
- ignore_env = True
149
- elif flag == 'P':
150
- safe_path = True
151
- else:
152
- raise _SafeOptionError('unsupported option -' + flag)
153
- index += 1
154
- if args and args[0] in ('-c', '-m'):
155
- break
156
- if version_count:
157
- print('Python ' + (sys.version if version_count > 1 else sys.version.split()[0]))
158
- return
159
- if not ignore_env:
160
- unbuffered = unbuffered or bool(os.environ.get('PYTHONUNBUFFERED'))
161
- sys.dont_write_bytecode = sys.dont_write_bytecode or bool(os.environ.get('PYTHONDONTWRITEBYTECODE'))
162
- safe_path = safe_path or bool(os.environ.get('PYTHONSAFEPATH'))
163
- path = os.environ.get('PYTHONPATH')
164
- if path:
165
- sys.path[0:0] = [os.path.abspath(entry) for entry in path.split(os.pathsep)]
166
- if os.environ.get('PYTHONWARNINGS'):
167
- warning_options[0:0] = os.environ['PYTHONWARNINGS'].split(',')
168
- encoding = os.environ.get('PYTHONIOENCODING')
169
- if encoding:
170
- name, separator, errors = encoding.partition(':')
171
- for stream in (sys.stdin, sys.stdout):
172
- stream.reconfigure(encoding=name or None, errors=(errors or 'strict'))
173
- sys.stderr.reconfigure(encoding=name or None, errors='backslashreplace')
174
- # CPython's -b/-bb filter follows environment and command-line -W filters.
175
- byte_warning = ('error' if sys.flags.bytes_warning > 1 else 'default') + '::BytesWarning'
176
- native_before = [value for value in native_warning_options if not (sys.flags.bytes_warning and value == byte_warning)]
177
- warning_options = native_before + warning_options + ([byte_warning] if sys.flags.bytes_warning else [])
178
- warnings._processoptions(warning_options)
179
- sys.warnoptions[:] = warning_options
180
- if unbuffered:
181
- for name in ('stdout', 'stderr'):
182
- stream = getattr(sys, name)
183
- stream.flush()
184
- encoding, errors = stream.encoding, stream.errors
185
- binary = stream.detach()
186
- raw = binary.detach() if hasattr(binary, 'detach') else binary
187
- replacement = io.TextIOWrapper(raw, encoding=encoding, errors=errors, write_through=True)
188
- setattr(sys, name, replacement)
189
- setattr(sys, '__' + name + '__', replacement)
190
- main = types.ModuleType('__main__')
191
- main.__dict__.update(__package__=None, __spec__=None, __loader__=__import__('importlib.machinery', fromlist=['BuiltinImporter']).BuiltinImporter, __builtins__=__builtins__)
192
- sys.modules['__main__'] = main
193
- if not options_terminated and args and args[0] == '-c':
194
- sys.argv = ['-c'] + args[2:]
195
- if not safe_path: sys.path.insert(0, '')
196
- source = textwrap.dedent(args[1])
197
- code = compile(source, '<string>', 'exec')
198
- # CPython 3.14 retains -c source for both top-level and nested tracebacks.
199
- linecache._register_code(code, source, '<string>')
200
- exec(code, main.__dict__)
201
- elif not options_terminated and args and args[0] == '-m':
202
- sys.argv = args[1:]
203
- if not safe_path: sys.path.insert(0, os.getcwd())
204
- runpy._run_module_as_main(args[1], alter_argv=True)
205
- elif not args or args[0] == '-':
206
- sys.argv = (['-'] + args[1:]) if args else ['']
207
- if not safe_path: sys.path.insert(0, '')
208
- main.__file__ = '<stdin>'
209
- exec(compile(sys.stdin.buffer.read(), '<stdin>', 'exec'), main.__dict__)
210
- else:
211
- sys.argv = args
212
- # CPython makes __file__ absolute without collapsing ./ or ../ components.
213
- path = args[0] if os.path.isabs(args[0]) else os.path.join(os.getcwd(), args[0])
214
- importer = __import__('pkgutil').get_importer(path)
215
- if importer is not None:
216
- sys.path.insert(0, path)
217
- runpy._run_module_as_main('__main__', alter_argv=False)
218
- else:
219
- if not safe_path: sys.path.insert(0, os.path.dirname(os.path.realpath(path)))
220
- try:
221
- with open(path, 'rb') as source:
222
- code = source.read()
223
- except OSError as error:
224
- print("python: can't open file " + repr(path) + ': [Errno ' + str(error.errno) + '] ' + error.strerror, file=sys.stderr)
225
- return 2
226
- main.__file__ = path
227
- main.__loader__ = __import__('importlib.machinery', fromlist=['SourceFileLoader']).SourceFileLoader('__main__', path)
228
- exec(compile(code, path, 'exec'), main.__dict__)
229
- return 0
230
-
231
- try:
232
- _safe_exit = _safe_launch() or 0
233
- except SystemExit as _error:
234
- _safe_exit = _safe_system_exit(_error)
235
- except _SafeOptionError as _error:
236
- print('python: ' + str(_error), file=sys.stderr)
237
- _safe_exit = 2
238
- except BaseException as _error:
239
- # Hide bridge frames while preserving actual Python frames and chained errors.
240
- _trace = _error.__traceback__
241
- while _trace is not None and _trace.tb_frame.f_code.co_filename == '<exec>':
242
- _trace = _trace.tb_next
243
- _error.__traceback__ = _trace
244
- sys.last_exc = _error
245
- _safe_exit = 130 if isinstance(_error, KeyboardInterrupt) else 1
246
- try:
247
- sys.excepthook(type(_error), _error, _trace)
248
- except SystemExit as _hook_error:
249
- _safe_exit = _safe_system_exit(_hook_error)
250
- except BaseException as _hook_error:
251
- print('Error in sys.excepthook:', file=sys.stderr)
252
- traceback.print_exception(type(_hook_error), _hook_error, _hook_error.__traceback__.tb_next)
253
- print('\\nOriginal exception was:', file=sys.stderr)
254
- sys.__excepthook__(type(_error), _error, _trace)
255
- finally:
256
- for _stream in (sys.stdout, sys.stderr):
257
- if _stream is not None and not getattr(_stream, 'closed', False):
258
- try:
259
- _stream.flush()
260
- except BaseException:
261
- _safe_exit = 120
262
- _safe_exit
263
- `;
264
-
265
- // packages/safe-bash-command-python/src/runtime-scripts.ts
266
- var pythonRuntimeRelocation = `
267
- import sys, zipimport, importlib.machinery
268
-
269
- def _rebase_runtime_path(value):
270
- return _safe_runtime_mount + value if isinstance(value, str) and (value == '/lib' or value.startswith('/lib/')) else value
271
-
272
- def _rebase_runtime_loader(loader):
273
- if isinstance(loader, zipimport.zipimporter):
274
- archive = _rebase_runtime_path(loader.archive)
275
- if archive != loader.archive:
276
- return zipimport.zipimporter(archive + '/' + loader.prefix)
277
- elif isinstance(loader, (importlib.machinery.SourceFileLoader, importlib.machinery.SourcelessFileLoader, importlib.machinery.ExtensionFileLoader)):
278
- path = _rebase_runtime_path(loader.path)
279
- if path != loader.path:
280
- return type(loader)(loader.name, path)
281
- return loader
282
- sys.path[:] = [_rebase_runtime_path(value) for value in sys.path]
283
- for _module in list(sys.modules.values()):
284
- if _module is None:
285
- continue
286
- for _attribute in ('__file__', '__cached__'):
287
- if hasattr(_module, _attribute):
288
- setattr(_module, _attribute, _rebase_runtime_path(getattr(_module, _attribute)))
289
- if hasattr(_module, '__path__'):
290
- _module.__path__ = [_rebase_runtime_path(value) for value in _module.__path__]
291
- if hasattr(_module, '__loader__'):
292
- _module.__loader__ = _rebase_runtime_loader(_module.__loader__)
293
- _spec = getattr(_module, '__spec__', None)
294
- if _spec is not None:
295
- _spec.origin = _rebase_runtime_path(_spec.origin)
296
- _spec.loader = _rebase_runtime_loader(_spec.loader)
297
- if _spec.submodule_search_locations is not None:
298
- _spec.submodule_search_locations = [_rebase_runtime_path(value) for value in _spec.submodule_search_locations]
299
- sys.path_importer_cache.clear()
300
- `;
301
- var pythonImportMetadata = `
302
- import importlib._bootstrap_external, types, json
303
- _safe_import_stat_callback = _safe_import_stat
304
- _safe_original_import_stat = importlib._bootstrap_external._path_stat
305
- def _safe_import_path_stat(path):
306
- if path == _safe_runtime_mount or path.startswith(_safe_runtime_mount + '/'):
307
- return _safe_original_import_stat(path)
308
- value = json.loads(_safe_import_stat_callback(path))
309
- if 'errno' in value:
310
- raise OSError(value['errno'], 'filesystem import metadata unavailable', path)
311
- return types.SimpleNamespace(st_mode=value['mode'], st_size=value['size'], st_mtime=value['mtimeMs'] / 1000)
312
- importlib._bootstrap_external._path_stat = _safe_import_path_stat
313
- `;
314
- var pythonDirectoryEntries = `
315
- import os, json, errno
316
- _safe_directory_callback = _safe_directory_entries
317
- def _safe_listdir(path='.'):
318
- if isinstance(path, int):
319
- raise OSError(errno.ENOTSUP, 'retained directory descriptors are unsupported')
320
- original = os.fspath(path) if path is not None else '.'
321
- value = json.loads(_safe_directory_callback(os.fsdecode(original)))
322
- if 'errno' in value:
323
- raise OSError(value['errno'], 'directory enumeration failed', path)
324
- return [os.fsencode(name) for name in value['entries']] if isinstance(original, bytes) else value['entries']
325
- os.listdir = _safe_listdir
326
- # The frozen importer holds the posix module separately from os.
327
- importlib._bootstrap_external._os.listdir = _safe_listdir
328
- `;
329
- var pythonStatProjection = `
330
- import os, json, errno
331
- _safe_stat_callback = _safe_stat_projection
332
- _safe_stat_type = os.stat_result
333
- _safe_fspath = os.fspath
334
- _safe_fsdecode = os.fsdecode
335
-
336
- def _safe_stat(path, *, dir_fd=None, follow_symlinks=True):
337
- if dir_fd is not None:
338
- raise OSError(errno.ENOTSUP, 'dir_fd metadata is unsupported')
339
- if isinstance(path, int):
340
- if not follow_symlinks:
341
- raise ValueError('stat: cannot use fd and follow_symlinks together')
342
- else:
343
- path = _safe_fsdecode(_safe_fspath(path))
344
- value = json.loads(_safe_stat_callback(path, follow_symlinks))
345
- if 'errno' in value:
346
- raise OSError(value['errno'], 'filesystem metadata unavailable', path)
347
- keys = ('mode','ino','dev','nlink','uid','gid','size')
348
- if any(key not in value for key in keys):
349
- raise OSError(errno.EOVERFLOW, 'canonical identity or required stat metadata unavailable', path)
350
- times = [value[key] / 1000 for key in ('atimeMs','mtimeMs','ctimeMs')]
351
- extras = dict(zip(('st_atime','st_mtime','st_ctime'), times))
352
- for name, milliseconds in zip(('atime','mtime','ctime'), (value['atimeMs'],value['mtimeMs'],value['ctimeMs'])):
353
- seconds = int(milliseconds // 1000)
354
- extras['st_' + name + '_ns'] = seconds * 1000000000 + round((milliseconds - seconds * 1000) * 1000000)
355
- for key in ('blocks','blksize','rdev'):
356
- if key in value:
357
- extras['st_' + key] = value[key]
358
- return _safe_stat_type(tuple(value[key] for key in keys) + tuple(int(time // 1) for time in times), extras)
359
-
360
- def _safe_lstat(path, *, dir_fd=None):
361
- return _safe_stat(path, dir_fd=dir_fd, follow_symlinks=False)
362
-
363
- def _safe_fstat(fd):
364
- if not isinstance(fd, int):
365
- raise TypeError('file descriptor must be an integer')
366
- return _safe_stat(fd)
367
- os.stat = _safe_stat
368
- os.lstat = _safe_lstat
369
- os.fstat = _safe_fstat
370
- import stat as _safe_stat_module
371
- class _SafeDirEntry:
372
- def __init__(self, root, name):
373
- self.name = name
374
- self.path = os.path.join(root, name)
375
- self._cache = {}
376
- def __fspath__(self):
377
- return self.path
378
- def stat(self, *, follow_symlinks=True):
379
- if follow_symlinks not in self._cache:
380
- self._cache[follow_symlinks] = os.stat(self.path, follow_symlinks=follow_symlinks)
381
- return self._cache[follow_symlinks]
382
- def inode(self):
383
- return self.stat(follow_symlinks=False).st_ino
384
- def is_dir(self, *, follow_symlinks=True):
385
- try:
386
- return _safe_stat_module.S_ISDIR(self.stat(follow_symlinks=follow_symlinks).st_mode)
387
- except FileNotFoundError:
388
- return False
389
- def is_file(self, *, follow_symlinks=True):
390
- try:
391
- return _safe_stat_module.S_ISREG(self.stat(follow_symlinks=follow_symlinks).st_mode)
392
- except FileNotFoundError:
393
- return False
394
- def is_symlink(self):
395
- try:
396
- return _safe_stat_module.S_ISLNK(self.stat(follow_symlinks=False).st_mode)
397
- except FileNotFoundError:
398
- return False
399
- class _SafeScandir:
400
- def __init__(self, path):
401
- self._entries = iter([_SafeDirEntry(path, name) for name in os.listdir(path)])
402
- def __iter__(self):
403
- return self
404
- def __next__(self):
405
- return next(self._entries)
406
- def close(self):
407
- self._entries = iter(())
408
- def __enter__(self):
409
- return self
410
- def __exit__(self, *args):
411
- self.close()
412
- def _safe_scandir(path='.'):
413
- if isinstance(path, int):
414
- raise OSError(errno.ENOTSUP, 'retained directory descriptors are unsupported')
415
- return _SafeScandir(os.fspath(path) if path is not None else '.')
416
- os.scandir = _safe_scandir
417
-
418
- `;
419
- var pythonHardLinks = `
420
- import os, json, errno
421
- _safe_link_callback = _safe_hard_link
422
- def _safe_link(src, dst, *, src_dir_fd=None, dst_dir_fd=None, follow_symlinks=True):
423
- if src_dir_fd is not None or dst_dir_fd is not None:
424
- raise OSError(errno.ENOTSUP, 'dir_fd hard links are unsupported')
425
- source = os.fsdecode(os.fspath(src))
426
- destination = os.fsdecode(os.fspath(dst))
427
- value = json.loads(_safe_link_callback(source, destination, bool(follow_symlinks)))
428
- if 'errno' in value:
429
- raise OSError(value['errno'], 'hard link creation failed', src, None, dst)
430
- os.link = _safe_link
431
- os.supports_follow_symlinks.add(_safe_link)
432
- `;
433
- var pythonTreeCleanup = `
434
- import shutil, os, json
435
- _safe_tree_callback = _safe_tree_cleanup
436
- _safe_original_rmtree_impl = shutil._rmtree_impl
437
- def _safe_rmtree_impl(path, dir_fd, onexc):
438
- if dir_fd is not None:
439
- return _safe_original_rmtree_impl(path, dir_fd, onexc)
440
- value = json.loads(_safe_tree_callback(os.fsdecode(path)))
441
- if not value['supported']:
442
- return _safe_original_rmtree_impl(path, dir_fd, onexc)
443
- if 'errno' in value:
444
- onexc(os.rmdir, path, OSError(value['errno'], 'directory cleanup failed', path))
445
- shutil._rmtree_impl = _safe_rmtree_impl
446
- `;
447
-
448
- export {
449
- PythonInvocationError,
450
- parsePythonInvocation,
451
- pythonExecution,
452
- pythonRuntimeRelocation,
453
- pythonImportMetadata,
454
- pythonDirectoryEntries,
455
- pythonStatProjection,
456
- pythonHardLinks,
457
- pythonTreeCleanup
458
- };