@poe-platform/safe-bash 0.1.70 → 0.1.72

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.
@@ -14,7 +14,8 @@ export { portableSearchCommands, type PortableSearchOptions } from "./commands/s
14
14
  export * from "./commands/regex-execution/public.js";
15
15
  export interface BrowserCommandsOptions {
16
16
  readonly replace?: boolean;
17
+ readonly maxDirectoryEntries?: number;
17
18
  }
18
- export declare function createBrowserCommands(): readonly CommandDefinition[];
19
+ export declare function createBrowserCommands(options?: BrowserCommandsOptions): readonly CommandDefinition[];
19
20
  export declare function browserCommands(options?: BrowserCommandsOptions): VirtualShellPlugin;
20
21
  //# sourceMappingURL=browser.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAOlF,cAAc,sBAAsB,CAAC;AACrC,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,OAAO,EAAE,oBAAoB,EAAE,MAAM,uCAAuC,CAAC;AAC7E,YAAY,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAC;AACnF,OAAO,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAC1E,YAAY,EAAE,eAAe,EAAE,uBAAuB,EAAE,MAAM,kCAAkC,CAAC;AACjG,OAAO,EAAE,6BAA6B,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EAAE,sBAAsB,EAAE,KAAK,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AACnG,cAAc,sCAAsC,CAAC;AAErD,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,wBAAgB,qBAAqB,IAAI,SAAS,iBAAiB,EAAE,CAKpE;AAED,wBAAgB,eAAe,CAAC,OAAO,GAAE,sBAA2B,GAAG,kBAAkB,CAaxF"}
1
+ {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAOlF,cAAc,sBAAsB,CAAC;AACrC,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,OAAO,EAAE,oBAAoB,EAAE,MAAM,uCAAuC,CAAC;AAC7E,YAAY,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAC;AACnF,OAAO,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAC1E,YAAY,EAAE,eAAe,EAAE,uBAAuB,EAAE,MAAM,kCAAkC,CAAC;AACjG,OAAO,EAAE,6BAA6B,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EAAE,sBAAsB,EAAE,KAAK,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AACnG,cAAc,sCAAsC,CAAC;AAErD,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;CACvC;AAED,wBAAgB,qBAAqB,CAAC,OAAO,GAAE,sBAA2B,GAAG,SAAS,iBAAiB,EAAE,CAKxG;AAED,wBAAgB,eAAe,CAAC,OAAO,GAAE,sBAA2B,GAAG,kBAAkB,CAaxF"}
@@ -3405,7 +3405,34 @@ async function moveAcrossDevices(context, source, target, noClobber, budget) {
3405
3405
  return true;
3406
3406
  }
3407
3407
 
3408
+ // packages/safe-bash/src/commands/directory-admission.ts
3409
+ init_platform();
3410
+ function createDirectoryReader(maxEntries = 1e4) {
3411
+ if (!Number.isSafeInteger(maxEntries) || maxEntries < 0) {
3412
+ throw new RangeError("maxDirectoryEntries must be a nonnegative safe integer");
3413
+ }
3414
+ return async (context, path, ordered = false) => {
3415
+ context.signal.throwIfAborted();
3416
+ const entries = await context.fs.readdir(path, { signal: context.signal, maxEntries });
3417
+ context.signal.throwIfAborted();
3418
+ if (entries.length > maxEntries) {
3419
+ throw new FsError("EFBIG", { syscall: "readdir", path, message: "directory entry limit exceeded" });
3420
+ }
3421
+ await yieldTurn(context.signal);
3422
+ if (ordered) {
3423
+ for (let index = 1; index < entries.length; index++) {
3424
+ if (index % 256 === 0) await yieldTurn(context.signal);
3425
+ if (entries[index - 1].name > entries[index].name) {
3426
+ return entries.slice().sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
3427
+ }
3428
+ }
3429
+ }
3430
+ return entries;
3431
+ };
3432
+ }
3433
+
3408
3434
  // packages/safe-bash/src/commands/filesystem.ts
3435
+ var MAX_RECURSIVE_DIRECTORY_DEPTH = 1024;
3409
3436
  async function preflightOperands(context, operands, check) {
3410
3437
  for (const operand of operands) {
3411
3438
  try {
@@ -3441,22 +3468,22 @@ function needCapability(context, capability) {
3441
3468
  if (!context.fs.capabilitiesFor && context.fs.capabilities[declaration] === false) throw new FsError("ENOTSUP", { syscall: capability });
3442
3469
  if (!context.fs[capability]) throw new FsError("ENOTSUP", { syscall: capability });
3443
3470
  }
3444
- async function admitEmptyDirectory(context, path) {
3471
+ async function admitEmptyDirectory(context, path, readDirectory) {
3445
3472
  try {
3446
3473
  await admitFilesystemModes(context, "rmdir", ["directory"], [path]);
3447
3474
  } catch (error) {
3448
3475
  context.signal.throwIfAborted();
3449
3476
  if (codeOf(error) === "ENOTSUP") {
3450
3477
  const capabilities = await context.fs.capabilitiesFor?.(path, { signal: context.signal }) ?? context.fs.capabilities;
3451
- if (capabilities.write !== false && capabilities.readdir !== false && (await context.fs.readdir(path, { signal: context.signal })).length) {
3478
+ if (capabilities.write !== false && capabilities.readdir !== false && (await readDirectory(context, path)).length) {
3452
3479
  throw new FsError("ENOTEMPTY", { syscall: "rmdir", path, cause: error });
3453
3480
  }
3454
3481
  }
3455
3482
  throw error;
3456
3483
  }
3457
3484
  }
3458
- async function removeEmptyDirectory(context, path) {
3459
- await admitEmptyDirectory(context, path);
3485
+ async function removeEmptyDirectory(context, path, readDirectory) {
3486
+ await admitEmptyDirectory(context, path, readDirectory);
3460
3487
  context.signal.throwIfAborted();
3461
3488
  if (!context.fs.rmdir) throw new FsError("ENOTSUP", { syscall: "rmdir", path });
3462
3489
  await context.fs.rmdir(path, { signal: context.signal });
@@ -3468,7 +3495,7 @@ async function destinations(context, operands) {
3468
3495
  if (operands.length > 2 && !directory) throw new FsError("ENOTDIR", { path: target });
3469
3496
  return { target, directory, sources: operands.slice(0, -1) };
3470
3497
  }
3471
- async function copy(context, source, target, flags, top = true, ancestors = /* @__PURE__ */ new Set(), preflight = false) {
3498
+ async function copy(context, source, target, flags, readDirectory, top = true, ancestors = /* @__PURE__ */ new Set(), preflight = false) {
3472
3499
  context.signal.throwIfAborted();
3473
3500
  const link = await context.fs.lstat(source, { signal: context.signal });
3474
3501
  const preserveLink = link.type === "symlink" && !flags.has("L") && (flags.has("P") || !top);
@@ -3488,11 +3515,19 @@ async function copy(context, source, target, flags, top = true, ancestors = /* @
3488
3515
  if (isPathWithin(physicalSource, physicalTarget)) throw new FsError("EINVAL", { path: target, message: "cannot copy a directory into itself" });
3489
3516
  if (ancestors.has(physicalSource)) throw new FsError("ELOOP", { path: source });
3490
3517
  if (targetStat && targetStat.type !== "directory") throw new FsError("ENOTDIR", { path: target });
3518
+ context.signal.throwIfAborted();
3519
+ if (ancestors.size > MAX_RECURSIVE_DIRECTORY_DEPTH) {
3520
+ throw new FsError("ELOOP", { path: source, message: `cp directory depth limit exceeded (${MAX_RECURSIVE_DIRECTORY_DEPTH})` });
3521
+ }
3491
3522
  await admitFilesystemModes(context, "cp", ["recursive"], [target]);
3492
- if (!targetStat && !preflight) await context.fs.mkdir(target, { mode: sourceStat.mode & 511, signal: context.signal });
3493
- const next = new Set(ancestors).add(physicalSource);
3494
- for (const entry of (await context.fs.readdir(source, { signal: context.signal })).sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0)) {
3495
- await copy(context, joinPath(source, entry.name), joinPath(target, entry.name), flags, false, next, preflight);
3523
+ ancestors.add(physicalSource);
3524
+ try {
3525
+ if (!targetStat && !preflight) await context.fs.mkdir(target, { mode: sourceStat.mode & 511, signal: context.signal });
3526
+ for (const entry of await readDirectory(context, source, true)) {
3527
+ await copy(context, joinPath(source, entry.name), joinPath(target, entry.name), flags, readDirectory, false, ancestors, preflight);
3528
+ }
3529
+ } finally {
3530
+ ancestors.delete(physicalSource);
3496
3531
  }
3497
3532
  } else if (preserveLink) {
3498
3533
  await admitFilesystemModes(context, "cp", ["symlink"], [target]);
@@ -3548,7 +3583,8 @@ function modeText(stat) {
3548
3583
  if (stat.mode & 512) text = text.slice(0, 9) + (stat.mode & 1 ? "t" : "T");
3549
3584
  return text;
3550
3585
  }
3551
- function filesystemCommands() {
3586
+ function filesystemCommands(maxDirectoryEntries) {
3587
+ const readDirectory = createDirectoryReader(maxDirectoryEntries);
3552
3588
  return [
3553
3589
  define("mkdir", async (context) => {
3554
3590
  const parsed = options(context.args, "pm:v", { parents: "p", mode: "m", verbose: "v" });
@@ -3613,6 +3649,7 @@ function filesystemCommands() {
3613
3649
  source,
3614
3650
  destination.directory ? joinPath(destination.target, basename(source)) : destination.target,
3615
3651
  parsed.flags,
3652
+ readDirectory,
3616
3653
  true,
3617
3654
  /* @__PURE__ */ new Set(),
3618
3655
  true
@@ -3620,7 +3657,7 @@ function filesystemCommands() {
3620
3657
  });
3621
3658
  return eachOperand(context, destination.sources, async (operand) => {
3622
3659
  const source = pathOf(context, operand);
3623
- await copy(context, source, destination.directory ? joinPath(destination.target, basename(source)) : destination.target, parsed.flags);
3660
+ await copy(context, source, destination.directory ? joinPath(destination.target, basename(source)) : destination.target, parsed.flags, readDirectory);
3624
3661
  });
3625
3662
  }),
3626
3663
  define("mv", async (context) => {
@@ -3659,7 +3696,7 @@ function filesystemCommands() {
3659
3696
  const stat = await maybeStat(context, path, false);
3660
3697
  if (!stat) return;
3661
3698
  const mode = stat.type === "directory" ? parsed.flags.has("r") || parsed.flags.has("R") ? "recursive" : "directory" : "file";
3662
- if (mode === "directory") await admitEmptyDirectory(context, path);
3699
+ if (mode === "directory") await admitEmptyDirectory(context, path, readDirectory);
3663
3700
  else await admitFilesystemModes(context, "rm", [mode], [path]);
3664
3701
  });
3665
3702
  return eachOperand(context, parsed.operands, async (operand) => {
@@ -3674,7 +3711,7 @@ function filesystemCommands() {
3674
3711
  if (stat.type === "directory" && !recursive && !parsed.flags.has("d")) throw new FsError("EISDIR", { path });
3675
3712
  if (stat.type === "directory" && !recursive) {
3676
3713
  try {
3677
- await removeEmptyDirectory(context, path);
3714
+ await removeEmptyDirectory(context, path, readDirectory);
3678
3715
  } catch (error) {
3679
3716
  context.signal.throwIfAborted();
3680
3717
  if (!parsed.flags.has("f") || codeOf(error) !== "ENOENT") throw error;
@@ -3693,7 +3730,7 @@ function filesystemCommands() {
3693
3730
  let path = pathOf(context, operand);
3694
3731
  const stop = dirname(pathOf(context, operand.split("/").find((part) => part && part !== ".") ?? operand));
3695
3732
  do {
3696
- await admitEmptyDirectory(context, path);
3733
+ await admitEmptyDirectory(context, path, readDirectory);
3697
3734
  path = dirname(path);
3698
3735
  } while (parsed.flags.has("p") && path !== "/" && path !== stop);
3699
3736
  });
@@ -3702,7 +3739,7 @@ function filesystemCommands() {
3702
3739
  const stop = dirname(pathOf(context, operand.split("/").find((part) => part && part !== ".") ?? operand));
3703
3740
  do {
3704
3741
  if (path === "/") throw new FsError("EBUSY", { path });
3705
- await removeEmptyDirectory(context, path);
3742
+ await removeEmptyDirectory(context, path, readDirectory);
3706
3743
  if (parsed.flags.has("v")) await output(context, `rmdir: removing directory '${path}'
3707
3744
  `);
3708
3745
  path = dirname(path);
@@ -3828,23 +3865,33 @@ function filesystemCommands() {
3828
3865
  await admitFilesystemModes(context, "ls", ["directory"], [path]);
3829
3866
  const physical = await context.fs.realpath(path, { signal: context.signal });
3830
3867
  if (ancestors.has(physical)) throw new FsError("ELOOP", { path });
3831
- const next = new Set(ancestors).add(physical);
3832
- if (header) {
3833
- await output(context, `${headerWritten ? "\n" : ""}${display}:
3834
- `);
3835
- headerWritten = true;
3868
+ context.signal.throwIfAborted();
3869
+ if (ancestors.size > MAX_RECURSIVE_DIRECTORY_DEPTH) {
3870
+ throw new FsError("ELOOP", { path, message: `ls directory depth limit exceeded (${MAX_RECURSIVE_DIRECTORY_DEPTH})` });
3836
3871
  }
3837
- const entries = await context.fs.readdir(path, { signal: context.signal });
3838
- const names = entries.map((entry) => entry.name).filter((name2) => parsed.flags.has("a") || parsed.flags.has("A") || !name2.startsWith("."));
3839
- if (parsed.flags.has("a")) names.push(".", "..");
3840
- names.sort();
3841
- if (parsed.flags.has("r")) names.reverse();
3842
- for (const name2 of names) await render(joinPath(path, name2), name2);
3843
- if (parsed.flags.has("R")) for (const name2 of names) {
3844
- if (name2 === "." || name2 === "..") continue;
3845
- const child = joinPath(path, name2);
3846
- const childStat = await context.fs[parsed.flags.has("L") ? "stat" : "lstat"](child, { signal: context.signal });
3847
- if (childStat.type === "directory") await list(child, `${display.replace(/\/$/u, "")}/${name2}`, true, next);
3872
+ ancestors.add(physical);
3873
+ try {
3874
+ if (header) {
3875
+ await output(context, `${headerWritten ? "\n" : ""}${display}:
3876
+ `);
3877
+ headerWritten = true;
3878
+ }
3879
+ const entries = await readDirectory(context, path, true);
3880
+ const names = entries.map((entry) => entry.name).filter((name2) => parsed.flags.has("a") || parsed.flags.has("A") || !name2.startsWith("."));
3881
+ if (parsed.flags.has("a")) for (const name2 of [".", ".."]) {
3882
+ const index = names.findIndex((entry) => entry > name2);
3883
+ names.splice(index < 0 ? names.length : index, 0, name2);
3884
+ }
3885
+ if (parsed.flags.has("r")) names.reverse();
3886
+ for (const name2 of names) await render(joinPath(path, name2), name2);
3887
+ if (parsed.flags.has("R")) for (const name2 of names) {
3888
+ if (name2 === "." || name2 === "..") continue;
3889
+ const child = joinPath(path, name2);
3890
+ const childStat = await context.fs[parsed.flags.has("L") ? "stat" : "lstat"](child, { signal: context.signal });
3891
+ if (childStat.type === "directory") await list(child, `${display.replace(/\/$/u, "")}/${name2}`, true, ancestors);
3892
+ }
3893
+ } finally {
3894
+ ancestors.delete(physical);
3848
3895
  }
3849
3896
  };
3850
3897
  return eachOperand(context, operands, (operand) => list(pathOf(context, operand), operand, operands.length > 1 || parsed.flags.has("R")));
@@ -18866,10 +18913,10 @@ function createBoundedRegexProvider(input3 = {}) {
18866
18913
  }
18867
18914
 
18868
18915
  // packages/safe-bash/src/browser.ts
18869
- function createBrowserCommands() {
18916
+ function createBrowserCommands(options3 = {}) {
18870
18917
  return [
18871
18918
  ...basicCommands(),
18872
- ...filesystemCommands(),
18919
+ ...filesystemCommands(options3.maxDirectoryEntries),
18873
18920
  ...predicateCommands(),
18874
18921
  ...streamCommands(),
18875
18922
  ...textCommands()
@@ -18879,7 +18926,7 @@ function browserCommands(options3 = {}) {
18879
18926
  return {
18880
18927
  name: "browser-commands",
18881
18928
  setup(host) {
18882
- const commands = createBrowserCommands();
18929
+ const commands = createBrowserCommands(options3);
18883
18930
  if (!options3.replace) {
18884
18931
  for (const command2 of commands) {
18885
18932
  if (host.commands.has(command2.name)) throw new Error(`Command already registered: ${command2.name}`);