@poe-platform/safe-bash 0.1.612 → 0.1.614

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.
@@ -27406,9 +27406,13 @@ async function parse12(scope, limits) {
27406
27406
  for (const value2 of argumentsValue.values) text2(shellValueBytes(value2));
27407
27407
  }
27408
27408
  let archive;
27409
+ let action = "add";
27409
27410
  let recursive = false;
27410
27411
  let quiet = false;
27411
27412
  let junkPaths = false;
27413
+ let omitDirectories = false;
27414
+ let storeLinks = false;
27415
+ let test = false;
27412
27416
  let level = 6;
27413
27417
  let stdinNames = false;
27414
27418
  let literal = false;
@@ -27426,8 +27430,15 @@ async function parse12(scope, limits) {
27426
27430
  for (let offset = 1; offset < argument2.length; offset++) {
27427
27431
  const flag = argument2[offset];
27428
27432
  if (flag === "r") recursive = true;
27429
- else if (flag === "q") quiet = true;
27433
+ else if (flag === "d" || flag === "u" || flag === "f") {
27434
+ const next = flag === "d" ? "delete" : flag === "u" ? "update" : "freshen";
27435
+ if (action !== "add" && action !== next) throw new ZipFailure(16, "Invalid command arguments", "specify just one action");
27436
+ action = next;
27437
+ } else if (flag === "q") quiet = true;
27430
27438
  else if (flag === "j") junkPaths = true;
27439
+ else if (flag === "D") omitDirectories = true;
27440
+ else if (flag === "y") storeLinks = true;
27441
+ else if (flag === "T") test = true;
27431
27442
  else if (flag === "@") stdinNames = true;
27432
27443
  else if (flag !== void 0 && flag >= "0" && flag <= "9") level = Number(flag);
27433
27444
  else if (flag === "i" || flag === "x") {
@@ -27485,7 +27496,7 @@ async function parse12(scope, limits) {
27485
27496
  start = end + 1;
27486
27497
  }
27487
27498
  }
27488
- return { archive, recursive, quiet, junkPaths, includes, excludes, level, operands: [...names, ...operands], firstOperand };
27499
+ return { action, archive, recursive, quiet, junkPaths, omitDirectories, storeLinks, test, includes, excludes, level, operands: [...names, ...operands], firstOperand };
27489
27500
  }
27490
27501
  function memberName(path, limits) {
27491
27502
  if (path.startsWith("//") && !path.startsWith("///")) fail("UNC source paths are unsupported");
@@ -27500,6 +27511,30 @@ function memberName(path, limits) {
27500
27511
  function unchanged4(before, after) {
27501
27512
  return before.type === after.type && before.size === after.size && before.mode === after.mode && before.mtimeMs === after.mtimeMs && before.ctimeMs === after.ctimeMs && before.nlink === after.nlink && (!hasIdentity(before) || sameIdentity2(before, after));
27502
27513
  }
27514
+ async function inspectSource(scope, path, storeLinks) {
27515
+ const { fs, signal } = scope.context;
27516
+ if (storeLinks) {
27517
+ const stat2 = await scope.operation(() => fs.lstat(path, { signal }));
27518
+ if (stat2.type === "symlink") {
27519
+ const parent = await scope.operation(() => fs.realpath(dirname(path), { signal }));
27520
+ const canonical2 = `${parent === "/" ? "" : parent}/${path.slice(path.lastIndexOf("/") + 1)}`;
27521
+ return { canonical: canonical2, stat: stat2 };
27522
+ }
27523
+ }
27524
+ const canonical = await scope.operation(() => fs.realpath(path, { signal }));
27525
+ const stat = await scope.operation(() => fs.stat(path, { signal }));
27526
+ return { canonical, stat };
27527
+ }
27528
+ async function filterName(name2, selection, includeCount) {
27529
+ selection.matched.clear();
27530
+ await selection.matches(name2);
27531
+ let included = includeCount === 0;
27532
+ for (const pattern of selection.matched) {
27533
+ if (pattern >= includeCount) return false;
27534
+ included = true;
27535
+ }
27536
+ return included;
27537
+ }
27503
27538
  async function prepare(scope, parsed, budget) {
27504
27539
  const context2 = scope.context;
27505
27540
  const limits = budget.limits;
@@ -27521,7 +27556,10 @@ async function prepare(scope, parsed, budget) {
27521
27556
  if (!current || !unchanged4(existing2, current)) fail("archive changed while reading");
27522
27557
  }
27523
27558
  const old = new Map(archive.entries.map((entry) => [entry.name, entry]));
27559
+ if (!archive.entries.length && !parsed.quiet && (parsed.action === "update" || parsed.action === "freshen")) await budget.output(` zip warning: ${parsed.archive} not found or empty
27560
+ `);
27524
27561
  const selected = /* @__PURE__ */ new Map();
27562
+ const deleted = /* @__PURE__ */ new Set();
27525
27563
  const selection = new Selection([...parsed.includes, ...parsed.excludes], limits, context2.signal);
27526
27564
  const ancestors = [];
27527
27565
  let visits = 0;
@@ -27538,8 +27576,7 @@ async function prepare(scope, parsed, budget) {
27538
27576
  let canonical;
27539
27577
  let stat;
27540
27578
  try {
27541
- canonical = await scope.operation(() => context2.fs.realpath(path, { signal: context2.signal }));
27542
- stat = await scope.operation(() => context2.fs.stat(path, { signal: context2.signal }));
27579
+ ({ canonical, stat } = await inspectSource(scope, path, parsed.storeLinks));
27543
27580
  } catch (error) {
27544
27581
  context2.signal.throwIfAborted();
27545
27582
  if (typeof error !== "object" || error === null || !("code" in error) || error.code !== "ENOENT") throw error;
@@ -27549,7 +27586,8 @@ async function prepare(scope, parsed, budget) {
27549
27586
  }
27550
27587
  checkPath(canonical, limits);
27551
27588
  if (canonical === output2 || existing2 && sameIdentity2(existing2, stat)) return;
27552
- if (stat.type !== "file" && stat.type !== "directory") {
27589
+ const symlink = parsed.storeLinks && stat.type === "symlink";
27590
+ if (stat.type !== "file" && stat.type !== "directory" && !symlink) {
27553
27591
  if (!parsed.quiet) await budget.output(` zip warning: ignoring special file: ${source2}
27554
27592
  `);
27555
27593
  return;
@@ -27557,17 +27595,12 @@ async function prepare(scope, parsed, budget) {
27557
27595
  if (existing2 && !hasIdentity(stat)) fail("cannot exclude archive aliases when source backing identity is unknown");
27558
27596
  const directory2 = stat.type === "directory";
27559
27597
  if (directory2 && name2 && !name2.endsWith("/")) name2 += "/";
27560
- selection.matched.clear();
27561
- await selection.matches(name2);
27562
- let included = parsed.includes.length === 0;
27563
- let excluded = false;
27564
- for (const pattern of selection.matched) {
27565
- if (pattern < parsed.includes.length) included = true;
27566
- else excluded = true;
27567
- }
27598
+ const included = await filterName(name2, selection, parsed.includes.length);
27568
27599
  const sourceName = name2;
27569
27600
  if (parsed.junkPaths) name2 = directory2 ? "" : name2.slice(name2.lastIndexOf("/") + 1);
27570
- if (name2 && included && !excluded) {
27601
+ const prior = old.get(name2);
27602
+ const eligible = parsed.action !== "update" && parsed.action !== "freshen" || (prior ? Math.floor(stat.mtimeMs / 1e3) > Math.floor(prior.modified.getTime() / 1e3) : parsed.action === "update");
27603
+ if (name2 && included && eligible && !(directory2 && parsed.omitDirectories)) {
27571
27604
  checkPath(name2, limits);
27572
27605
  const previous = selected.get(name2);
27573
27606
  if (previous && previous.source !== source2) {
@@ -27579,14 +27612,19 @@ async function prepare(scope, parsed, budget) {
27579
27612
  }
27580
27613
  if (!previous) {
27581
27614
  await budget.member(directory2 ? 0 : stat.size);
27582
- const bytes4 = directory2 ? new Uint8Array() : await collectBytes(scope.input(path), { maxBytes: stat.size, signal: context2.signal });
27615
+ let bytes4;
27616
+ if (directory2) bytes4 = new Uint8Array();
27617
+ else if (symlink) {
27618
+ if (!context2.fs.readlink) fail("filesystem does not support reading symbolic links");
27619
+ const target = await scope.operation(() => context2.fs.readlink(path, { signal: context2.signal }));
27620
+ if (import_buffer.Buffer.byteLength(target) > stat.size) fail(`source changed while reading: ${source2}`);
27621
+ bytes4 = import_buffer.Buffer.from(target);
27622
+ } else bytes4 = await collectBytes(scope.input(path), { maxBytes: stat.size, signal: context2.signal });
27583
27623
  if (!directory2 && bytes4.length !== stat.size) fail(`source changed while reading: ${source2}`);
27584
- const current = await scope.operation(() => context2.fs.stat(path, { signal: context2.signal }));
27585
- const currentPath = await scope.operation(() => context2.fs.realpath(path, { signal: context2.signal }));
27586
- if (currentPath !== canonical || !unchanged4(stat, current)) fail(`source changed while reading: ${source2}`);
27587
- let entry = await makeZipEntry(name2, bytes4, { modified: new Date(stat.mtimeMs), mode: stat.mode, directory: directory2, symlink: false }, limits, context2.signal, parsed.level);
27624
+ const current = await inspectSource(scope, path, parsed.storeLinks);
27625
+ if (current.canonical !== canonical || !unchanged4(stat, current.stat)) fail(`source changed while reading: ${source2}`);
27626
+ let entry = await makeZipEntry(name2, bytes4, { modified: new Date(stat.mtimeMs), mode: stat.mode, directory: directory2, symlink }, limits, context2.signal, parsed.level);
27588
27627
  if ([".z", ".zip", ".zoo", ".arc", ".lzh", ".arj"].some((suffix2) => name2.toLowerCase().endsWith(suffix2))) entry = { ...entry, method: 0, data: bytes4 };
27589
- const prior = old.get(name2);
27590
27628
  if (prior?.comment) entry = { ...entry, comment: prior.comment };
27591
27629
  if (entry.data.length > limits.maxArchiveBytes - compressedBytes) fail("archive byte limit exceeded");
27592
27630
  compressedBytes += entry.data.length;
@@ -27609,26 +27647,53 @@ async function prepare(scope, parsed, budget) {
27609
27647
  }
27610
27648
  }
27611
27649
  };
27612
- for (const operand of parsed.operands) await visit(operand, memberName(operand, limits), 0);
27613
- if (!selected.size && !parsed.includes.length) {
27614
- const detail = parsed.recursive && parsed.firstOperand >= 0 ? `try: zip ${context2.args.slice(0, parsed.firstOperand).join(" ")} . -i ${context2.args.slice(parsed.firstOperand).join(" ")}` : parsed.archive;
27650
+ if (parsed.action === "delete") {
27651
+ if (!parsed.quiet && parsed.recursive) await budget.output(" zip warning: invalid option(s) used with -d; ignored.\n");
27652
+ if (!parsed.quiet && !archive.entries.length) await budget.output(` zip warning: ${parsed.archive} not found or empty
27653
+ `);
27654
+ const operands = new Selection(parsed.operands, limits, context2.signal);
27655
+ if (parsed.operands.length) {
27656
+ for (const entry of archive.entries) {
27657
+ if (await operands.matches(entry.name) && await filterName(entry.name, selection, parsed.includes.length)) deleted.add(entry.name);
27658
+ }
27659
+ }
27660
+ if (!parsed.quiet) {
27661
+ for (const [index, operand] of parsed.operands.entries()) {
27662
+ if (!operands.matched.has(index)) await budget.output(` zip warning: name not matched: ${operand}
27663
+ `);
27664
+ }
27665
+ }
27666
+ } else {
27667
+ const operands = parsed.operands.length || parsed.action === "add" ? parsed.operands : archive.entries.map((entry) => entry.name);
27668
+ for (const operand of operands) await visit(operand, memberName(operand, limits), 0);
27669
+ }
27670
+ if (!selected.size && (parsed.action === "freshen" || parsed.action === "update" && (existing2 || !parsed.includes.length))) return void 0;
27671
+ if (!selected.size && !deleted.size && (parsed.action === "delete" || !parsed.includes.length)) {
27672
+ const detail = parsed.action !== "delete" && parsed.recursive && parsed.firstOperand >= 0 ? `try: zip ${context2.args.slice(0, parsed.firstOperand).join(" ")} . -i ${context2.args.slice(parsed.firstOperand).join(" ")}` : parsed.archive;
27615
27673
  throw new ZipFailure(12, "Nothing to do!", detail);
27616
27674
  }
27617
27675
  const entries2 = [];
27618
27676
  const progress = [];
27619
27677
  let progressBytes = 0;
27620
- const append = (entry, update) => {
27678
+ const queue = (message3) => {
27621
27679
  if (parsed.quiet) return;
27622
- const percentage = entry.size ? Math.trunc((Math.trunc(200 * (entry.size - entry.data.length) / entry.size) + 1) / 2) : 0;
27623
- const message3 = `${update ? "updating:" : " adding:"} ${entry.name} (${entry.method === 8 ? `deflated ${percentage}%` : "stored 0%"})
27624
- `;
27625
27680
  const size = import_buffer.Buffer.byteLength(message3);
27626
27681
  if (size > limits.maxTextBytes - budget.textBytes - progressBytes) fail("text output limit exceeded");
27627
27682
  progressBytes += size;
27628
27683
  progress.push(message3);
27629
27684
  };
27685
+ const append = (entry, update) => {
27686
+ const percentage = entry.size ? Math.trunc((Math.trunc(200 * (entry.size - entry.data.length) / entry.size) + 1) / 2) : 0;
27687
+ queue(`${update ? parsed.action === "freshen" ? "freshening:" : "updating:" : " adding:"} ${entry.name} (${entry.method === 8 ? `deflated ${percentage}%` : "stored 0%"})
27688
+ `);
27689
+ };
27630
27690
  for (const entry of archive.entries) {
27631
27691
  if (++work > limits.maxPatternSteps) fail("archive work limit exceeded");
27692
+ if (deleted.has(entry.name)) {
27693
+ queue(`deleting: ${entry.name}
27694
+ `);
27695
+ continue;
27696
+ }
27632
27697
  const replacement = selected.get(entry.name);
27633
27698
  if (replacement) {
27634
27699
  entries2.push(replacement.entry);
@@ -27643,8 +27708,33 @@ async function prepare(scope, parsed, budget) {
27643
27708
  entries2.push(entry);
27644
27709
  append(entry, false);
27645
27710
  }
27646
- if (!entries2.length && !parsed.quiet) await budget.output(" zip warning: zip file empty\n");
27711
+ if (!entries2.length) queue(" zip warning: zip file empty\n");
27647
27712
  const bytes3 = await writeZipArchive({ entries: entries2, comment: archive.comment }, limits, context2.signal);
27713
+ if (parsed.test) {
27714
+ for (const message3 of progress) await budget.output(message3);
27715
+ progress.length = 0;
27716
+ progressBytes = 0;
27717
+ try {
27718
+ const tested = await readZipArchive(bytes3, limits, context2.signal);
27719
+ if (!tested.entries.length) fail("empty ZIP archive");
27720
+ let decoded = 0;
27721
+ for (const entry of tested.entries) {
27722
+ for await (const chunk of decodeZipEntry(entry, limits, context2.signal)) {
27723
+ if (chunk.length > limits.maxTotalBytes - decoded) fail("actual decompressed byte limit exceeded");
27724
+ decoded += chunk.length;
27725
+ }
27726
+ }
27727
+ } catch {
27728
+ context2.signal.throwIfAborted();
27729
+ if (!parsed.quiet) await budget.output(`test of ${parsed.archive} FAILED
27730
+ `);
27731
+ throw new ZipFailure(8, "Zip file invalid, could not spawn unzip, or wrong unzip", "original files unmodified");
27732
+ }
27733
+ if (!parsed.quiet) {
27734
+ queue(`test of ${parsed.archive} OK
27735
+ `);
27736
+ }
27737
+ }
27648
27738
  return { output: output2, parentName, parent, parentStat, existing: existing2, bytes: bytes3, progress };
27649
27739
  }
27650
27740
  function createZipCommand(options2 = {}) {
@@ -27657,6 +27747,7 @@ function createZipCommand(options2 = {}) {
27657
27747
  try {
27658
27748
  const parsed = await parse12(scope, limits);
27659
27749
  const prepared = await prepare(scope, parsed, budget);
27750
+ if (!prepared) return { exitCode: 12 };
27660
27751
  await writeFileOutput(context2, prepared.bytes, () => scope.operation(() => publishZip(scope, prepared)));
27661
27752
  for (const message3 of prepared.progress) await budget.output(message3);
27662
27753
  return { exitCode: 0 };