@poe-platform/safe-bash 0.1.521 → 0.1.523

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.
@@ -37479,6 +37479,7 @@ init_platform();
37479
37479
  var assignments = /* @__PURE__ */ new WeakMap();
37480
37480
  var selectors = /* @__PURE__ */ new WeakMap();
37481
37481
  var quoteMarkers = /* @__PURE__ */ new WeakSet();
37482
+ var prefixNameQuoteGroups = /* @__PURE__ */ new WeakMap();
37482
37483
  function setQuoteMarker(part, synthetic) {
37483
37484
  if (synthetic) quoteMarkers.add(part);
37484
37485
  else quoteMarkers.delete(part);
@@ -37514,6 +37515,8 @@ function copyArraySelector(original, copy2) {
37514
37515
  const selector = selectors.get(original);
37515
37516
  if (selector) selectors.set(copy2, selector);
37516
37517
  setQuoteMarker(copy2, isQuoteMarker(original));
37518
+ const group = prefixNameQuoteGroups.get(original);
37519
+ if (group) prefixNameQuoteGroups.set(copy2, group);
37517
37520
  return copy2;
37518
37521
  }
37519
37522
  function setArrayAssignment(word, assignment) {
@@ -38113,14 +38116,18 @@ shell: command substitution: line ${line}: \`${sourceLine}'
38113
38116
  this.position++;
38114
38117
  text3("", true, true, this.position - 1, this.position);
38115
38118
  const quoteParts = parts.length;
38119
+ let nameListing = false;
38116
38120
  while (this.position < this.source.length && this.source[this.position] !== '"') {
38117
38121
  const inner = this.source[this.position];
38118
38122
  const opaque2 = this.braceReplay?.get(this.position);
38119
38123
  if (opaque2 !== void 0) {
38120
38124
  text3(opaque2, true);
38121
38125
  this.position++;
38122
- } else if (!literal && (inner === "$" || inner === "`")) this.expansion(parts, true);
38123
- else if (inner === "\\" && /[$`"\\\n]/u.test(this.source[this.position + 1] ?? "")) {
38126
+ } else if (!literal && (inner === "$" || inner === "`")) {
38127
+ this.expansion(parts, true);
38128
+ const part = parts.at(-1);
38129
+ nameListing ||= part.kind === "variable" && part.prefixNames === "@";
38130
+ } else if (inner === "\\" && /[$`"\\\n]/u.test(this.source[this.position + 1] ?? "")) {
38124
38131
  const escaped3 = this.source[this.position + 1];
38125
38132
  if (escaped3 !== "\n") text3(escaped3, true, false, this.position, this.position + 2);
38126
38133
  else this.printedNewlineReduction++;
@@ -38132,6 +38139,15 @@ shell: command substitution: line ${line}: \`${sourceLine}'
38132
38139
  }
38133
38140
  if (this.source[this.position] !== '"' && !this.braceReplay) this.error("Unterminated double quote", { quote: '"', line: quoteLine });
38134
38141
  if (parts.length === quoteParts) setQuoteMarker(parts.at(-1), false);
38142
+ if (nameListing) {
38143
+ this.budget.admit();
38144
+ const group = {};
38145
+ for (let index = quoteParts - 1; index < parts.length; index++) {
38146
+ this.budget.admit();
38147
+ const part = parts[index];
38148
+ if (index !== quoteParts - 1 || isQuoteMarker(part) || part.kind !== "text" || part.value !== "") prefixNameQuoteGroups.set(part, group);
38149
+ }
38150
+ }
38135
38151
  const spelling2 = expansionSpellings.get(parts.at(-1));
38136
38152
  if (spelling2) spelling2.end = Math.min(this.position + 1, this.source.length);
38137
38153
  if (this.position < this.source.length) this.position++;
@@ -38291,11 +38307,20 @@ shell: command substitution: line ${line}: \`${sourceLine}'
38291
38307
  } else if (this.source[this.position] === "{") {
38292
38308
  this.position++;
38293
38309
  const parameterStart = this.position - 2;
38294
- const length = this.source[this.position] === "#" && /[a-zA-Z_]/u.test(this.source[this.position + 1] ?? "");
38310
+ const listing = this.source[this.position] === "!";
38311
+ if (listing) this.position++;
38312
+ const length = !listing && this.source[this.position] === "#" && /[a-zA-Z_]/u.test(this.source[this.position + 1] ?? "");
38295
38313
  if (length) this.position++;
38296
38314
  const name2 = /^(?:[a-zA-Z_][a-zA-Z_0-9]*|[0-9]+|[?@*#-])/u.exec(this.source.slice(this.position))?.[0];
38297
38315
  if (!name2) this.error("Unsupported parameter expansion");
38298
38316
  this.position += name2.length;
38317
+ let prefixNames;
38318
+ if (listing) {
38319
+ const separator = this.source[this.position];
38320
+ if (!/^[a-zA-Z_][a-zA-Z_0-9]*$/u.test(name2) || separator !== "*" && separator !== "@" || this.source[this.position + 1] !== "}") this.error("Unsupported indirect parameter expansion");
38321
+ prefixNames = separator;
38322
+ this.position++;
38323
+ }
38299
38324
  let selector;
38300
38325
  if (this.source[this.position] === "[") {
38301
38326
  if (!/^[a-zA-Z_][a-zA-Z_0-9]*$/u.test(name2)) this.error("Unsupported indexed-array parameter");
@@ -38348,7 +38373,7 @@ shell: command substitution: line ${line}: \`${sourceLine}'
38348
38373
  parts.push({ kind: "failed-parameter", source: this.source.slice(parameterStart, this.position), line, quoted: quoted2 });
38349
38374
  } else {
38350
38375
  this.position++;
38351
- const part = { kind: "variable", name: name2, quoted: quoted2, line, ...length ? { length } : {}, ...operator ? { operator, alternate } : {}, ...replacement ? { replacement } : {}, ...substring ? { substring } : {} };
38376
+ const part = { kind: "variable", name: name2, quoted: quoted2, line, ...prefixNames ? { prefixNames } : {}, ...length ? { length } : {}, ...operator ? { operator, alternate } : {}, ...replacement ? { replacement } : {}, ...substring ? { substring } : {} };
38352
38377
  if (selector) setArraySelector(part, selector);
38353
38378
  parts.push(part);
38354
38379
  }
@@ -40760,6 +40785,7 @@ var IndexedBinding = class _IndexedBinding {
40760
40785
  generation = 0;
40761
40786
  version = 0;
40762
40787
  references = 1;
40788
+ assigned = false;
40763
40789
  static create(parent) {
40764
40790
  const owner = ArrayOwner.create(parent.ledger, parent);
40765
40791
  try {
@@ -40791,11 +40817,13 @@ var IndexedBinding = class _IndexedBinding {
40791
40817
  text3.release();
40792
40818
  };
40793
40819
  this.values.set(index, element);
40820
+ this.assigned = true;
40794
40821
  if (index > this.maximum) this.maximum = index;
40795
40822
  previous?.slot.release();
40796
40823
  }
40797
40824
  async copy(signal) {
40798
40825
  const copy2 = _IndexedBinding.create(this.owner.parent);
40826
+ copy2.assigned = this.assigned;
40799
40827
  this.retain();
40800
40828
  try {
40801
40829
  for (const [index, element] of this.values) {
@@ -41671,6 +41699,35 @@ function resolveLimits(...limits) {
41671
41699
  return result2;
41672
41700
  }
41673
41701
  var budgetedSinks = /* @__PURE__ */ new WeakMap();
41702
+ async function sortExpansionStrings(values, work) {
41703
+ const compare6 = async (left, right) => {
41704
+ for (let index = 0; index < Math.min(left.length, right.length); index++) {
41705
+ const pending = stringCheckpoint(work);
41706
+ if (pending) await pending;
41707
+ const difference = left.charCodeAt(index) - right.charCodeAt(index);
41708
+ if (difference) return difference;
41709
+ }
41710
+ return left.length - right.length;
41711
+ };
41712
+ const sift = async (root, end) => {
41713
+ while (root * 2 + 1 < end) {
41714
+ let child = root * 2 + 1;
41715
+ if (child + 1 < end && await compare6(values[child], values[child + 1]) < 0) child++;
41716
+ if (await compare6(values[root], values[child]) >= 0) return;
41717
+ const saved = values[root];
41718
+ values[root] = values[child];
41719
+ values[child] = saved;
41720
+ root = child;
41721
+ }
41722
+ };
41723
+ for (let index = Math.floor(values.length / 2) - 1; index >= 0; index--) await sift(index, values.length);
41724
+ for (let end = values.length - 1; end > 0; end--) {
41725
+ const saved = values[0];
41726
+ values[0] = values[end];
41727
+ values[end] = saved;
41728
+ await sift(0, end);
41729
+ }
41730
+ }
41674
41731
  var Budget25 = class {
41675
41732
  constructor(limits, signal, onInternalError) {
41676
41733
  this.limits = limits;
@@ -41691,6 +41748,8 @@ var Budget25 = class {
41691
41748
  iterations = 0;
41692
41749
  bytes = 0;
41693
41750
  sourceBytes = 0;
41751
+ globstarEntries = 0;
41752
+ globstarStates = 0;
41694
41753
  controller = new AbortController();
41695
41754
  signal;
41696
41755
  #wallClockTimer;
@@ -41839,7 +41898,7 @@ var typedSavedVariables = /* @__PURE__ */ new WeakMap();
41839
41898
  var valueScope = /* @__PURE__ */ Symbol("shell value allocation scope");
41840
41899
  var invokedValues = /* @__PURE__ */ new WeakMap();
41841
41900
  function isolateIO(io) {
41842
- return { ...io, ...io.descriptors ? { descriptors: new Map([...io.descriptors].map(([number5, descriptor2]) => [number5, { ...descriptor2 }])) } : {} };
41901
+ return { ...io, nameExpansionContext: void 0, ...io.descriptors ? { descriptors: new Map([...io.descriptors].map(([number5, descriptor2]) => [number5, { ...descriptor2 }])) } : {} };
41843
41902
  }
41844
41903
  function activeIO(io) {
41845
41904
  const input3 = io.descriptors?.get(0);
@@ -43036,6 +43095,7 @@ var Runtime = class _Runtime {
43036
43095
  const current = store.get(name2);
43037
43096
  if (index !== void 0 && current) {
43038
43097
  staged = index === "members" ? IndexedBinding.create(store.owner) : await current.copy(this.signal);
43098
+ staged.assigned = current.assigned;
43039
43099
  let maximum = -1;
43040
43100
  for (const key of staged.values.keys()) {
43041
43101
  operation2.reserve({ work: 2 }).release();
@@ -43236,7 +43296,8 @@ var Runtime = class _Runtime {
43236
43296
  this.signal.throwIfAborted();
43237
43297
  if (state.readonlyVariables?.has(name2)) throw new ArrayFailure("readonly binding");
43238
43298
  if (!watch.valid()) throw new ArrayFailure("stale binding");
43239
- if (!(assignment.kind === "compound" && assignment.append && writes === 0)) {
43299
+ staged.assigned = true;
43300
+ if (!(assignment.kind === "compound" && assignment.append && writes === 0 && current?.assigned)) {
43240
43301
  let released;
43241
43302
  stateMonitor(state).publish(tickets, name2, () => {
43242
43303
  supersede();
@@ -43539,8 +43600,8 @@ var Runtime = class _Runtime {
43539
43600
  signal: this.signal,
43540
43601
  locale: state.variables.LC_ALL || state.variables.LC_COLLATE || state.variables.LANG || "C",
43541
43602
  work: { remaining: this.budget.limits.maxExpansionBytes, signal: this.signal, exhausted: () => this.budget.fail("maxExpansionBytes"), allocation },
43542
- expand: async (word, pattern = false) => (await this.word(word, state, io, false, pattern, false, pattern)).join(""),
43543
- regex: (subject, pattern) => this.ere(subject, pattern, state, io),
43603
+ expand: async (word, pattern = false) => (await this.word(word, state, { ...io, nameExpansionContext: "conditional" }, false, pattern, false, pattern)).join(""),
43604
+ regex: (subject, pattern) => this.ere(subject, pattern, state, { ...io, nameExpansionContext: "conditional" }),
43544
43605
  option: (name2) => name2 === "braceexpand" ? state.braceexpand !== false : name2 === "errexit" ? !!state.errexit : name2 === "nounset" ? !!state.nounset : name2 === "pipefail" ? state.pipefail : false,
43545
43606
  present: (name2) => {
43546
43607
  const match = /^([a-zA-Z_][a-zA-Z_0-9]*)(?:\[(0|[1-9][0-9]*|[@*])\])?$/u.exec(name2);
@@ -43763,7 +43824,7 @@ var Runtime = class _Runtime {
43763
43824
  for (const warning of warnings.splice(0)) await writeDiagnostic(io.stderr, `shell: warning: ${warning}
43764
43825
  `);
43765
43826
  if (++words % 128 === 0) await yieldTurn(this.signal);
43766
- const part = (await this.word(word, state, io, false)).join("");
43827
+ const part = (await this.word(word, state, { ...io, nameExpansionContext: "document" }, false)).join("");
43767
43828
  size += import_buffer.Buffer.byteLength(part);
43768
43829
  if (size > this.budget.limits.maxExpansionBytes) this.budget.fail("maxExpansionBytes");
43769
43830
  value2 += part;
@@ -44606,6 +44667,7 @@ ${functionDisplay(name2, state.functions.get(name2))}`;
44606
44667
  getopts: { cursor: createGetoptsState(), integer: true },
44607
44668
  directoryStack: { entries: [], bytes: 0 },
44608
44669
  dotglob: false,
44670
+ globstar: false,
44609
44671
  positional: [...args],
44610
44672
  arg0,
44611
44673
  profile: context2.command === "sh" ? "sh" : "bash",
@@ -45680,7 +45742,7 @@ ${prefix3} line ${offset + line}: \`${source2.split("\n")[line - 1] ?? ""}'
45680
45742
  else if (flag === "u") unset2 = true;
45681
45743
  else {
45682
45744
  await this.diagnostic(context2, `shopt: ${option.startsWith("--") ? option : `-${flag}`}: unsupported option`);
45683
- await writeDiagnostic(context2.stderr, "shopt: usage: shopt [-pqsu] [--] [dotglob ...]\n");
45745
+ await writeDiagnostic(context2.stderr, "shopt: usage: shopt [-pqsu] [--] [dotglob globstar ...]\n");
45684
45746
  return 2;
45685
45747
  }
45686
45748
  }
@@ -45689,26 +45751,26 @@ ${prefix3} line ${offset + line}: \`${source2.split("\n")[line - 1] ?? ""}'
45689
45751
  await this.diagnostic(context2, "shopt: cannot set and unset shell options simultaneously");
45690
45752
  return 1;
45691
45753
  }
45692
- const emit2 = async () => {
45693
- if (!quiet) await writeText(context2.stdout, print ? `shopt -${state.dotglob ? "s" : "u"} dotglob
45694
- ` : `dotglob ${state.dotglob ? "on" : "off"}
45754
+ const emit2 = async (name2) => {
45755
+ if (!quiet) await writeText(context2.stdout, print ? `shopt -${state[name2] ? "s" : "u"} ${name2}
45756
+ ` : `${name2.padEnd(20)} ${state[name2] ? "on" : "off"}
45695
45757
  `);
45696
45758
  };
45697
45759
  if (index === context2.args.length) {
45698
- if ((!set || state.dotglob) && (!unset2 || !state.dotglob)) await emit2();
45760
+ for (const name2 of ["dotglob", "globstar"]) if ((!set || state[name2]) && (!unset2 || !state[name2])) await emit2(name2);
45699
45761
  return 0;
45700
45762
  }
45701
45763
  let status2 = 0;
45702
45764
  for (; index < context2.args.length; index++) {
45703
45765
  this.signal.throwIfAborted();
45704
45766
  const name2 = context2.args[index];
45705
- if (name2 !== "dotglob") {
45706
- await this.diagnostic(context2, `shopt: ${name2}: unsupported shell option name (only dotglob is supported)`);
45767
+ if (name2 !== "dotglob" && name2 !== "globstar") {
45768
+ await this.diagnostic(context2, `shopt: ${name2}: unsupported shell option name (supported: dotglob, globstar)`);
45707
45769
  status2 = 1;
45708
- } else if (set || unset2) state.dotglob = set;
45770
+ } else if (set || unset2) state[name2] = set;
45709
45771
  else {
45710
- await emit2();
45711
- if (!state.dotglob) status2 = 1;
45772
+ await emit2(name2);
45773
+ if (!state[name2]) status2 = 1;
45712
45774
  }
45713
45775
  }
45714
45776
  return status2;
@@ -46340,7 +46402,66 @@ ${prefix3} line ${offset + line}: \`${source2.split("\n")[line - 1] ?? ""}'
46340
46402
  async part(part, state, io, hereString = false) {
46341
46403
  return shellValueText(await this.valuePart(part, state, io, hereString));
46342
46404
  }
46405
+ async *prefixNames(prefix3, state) {
46406
+ const allocation = this.budget.values.scope();
46407
+ const store = arrayStore(state);
46408
+ const holding = store?.owner.hold();
46409
+ const work = { remaining: Math.min(Number.MAX_SAFE_INTEGER, this.budget.limits.maxExpansionBytes * 4 + 1024), signal: this.signal, exhausted: () => this.budget.fail("maxExpansionBytes") };
46410
+ try {
46411
+ allocation.reserveBytes(128);
46412
+ const names = [];
46413
+ const seen = /* @__PURE__ */ new Set();
46414
+ let bytes3 = 0;
46415
+ const consider = async (name2, assigned) => {
46416
+ const scanned = await scanString(name2, work);
46417
+ const pending = stringCheckpoint(work, name2.length + 1);
46418
+ if (pending) await pending;
46419
+ if (!assigned || !name2.startsWith(prefix3) || seen.has(name2)) return;
46420
+ for (let index = 0; index < name2.length; index++) {
46421
+ const pending2 = stringCheckpoint(work);
46422
+ if (pending2) await pending2;
46423
+ const code = name2.charCodeAt(index);
46424
+ if (!(code === 95 || code >= 65 && code <= 90 || code >= 97 && code <= 122 || index > 0 && code >= 48 && code <= 57)) return;
46425
+ }
46426
+ if (names.length >= this.budget.limits.maxExpansionFields) this.budget.fail("maxExpansionFields");
46427
+ if (scanned.bytes > this.budget.limits.maxExpansionBytes - bytes3) this.budget.fail("maxExpansionBytes");
46428
+ bytes3 += scanned.bytes;
46429
+ allocation.reserveBytes(name2.length * 2 + 64);
46430
+ seen.add(name2);
46431
+ names.push(name2);
46432
+ };
46433
+ for (const name2 in state.variables) if (Object.hasOwn(state.variables, name2)) await consider(name2, state.variables[name2] !== void 0);
46434
+ if (store) for (const [name2, entry] of store.bindings) await consider(name2, entry.binding.assigned);
46435
+ for (const frame of state.locals) for (const [name2, saved] of frame) {
46436
+ await consider(name2, saved.value !== void 0 || typedSavedVariables.get(saved)?.binding?.assigned === true);
46437
+ }
46438
+ await sortExpansionStrings(names, work);
46439
+ for (const name2 of names) {
46440
+ const pending = stringCheckpoint(work);
46441
+ if (pending) await pending;
46442
+ yield name2;
46443
+ }
46444
+ } finally {
46445
+ allocation.close();
46446
+ holding?.release();
46447
+ }
46448
+ }
46343
46449
  async valuePart(part, state, io, hereString = false) {
46450
+ if (part.kind === "variable" && part.prefixNames) {
46451
+ const ifs = state.variables.IFS ?? " ";
46452
+ const separator = io.nameExpansionContext === "document" || io.nameExpansionContext === "conditional" && part.prefixNames === "@" ? " " : ifs.length ? String.fromCodePoint(ifs.codePointAt(0)) : "";
46453
+ const fragments = [];
46454
+ let bytes3 = 0;
46455
+ for await (const name2 of this.prefixNames(part.name, state)) {
46456
+ const join = fragments.length ? separator : "";
46457
+ bytes3 += import_buffer.Buffer.byteLength(name2) + import_buffer.Buffer.byteLength(join);
46458
+ if (bytes3 > this.budget.limits.maxExpansionBytes) this.budget.fail("maxExpansionBytes");
46459
+ io[valueScope]?.reserve((name2.length + join.length) * 2 + 32, 0);
46460
+ fragments.push(join, name2);
46461
+ }
46462
+ io[valueScope]?.reserve(bytes3 * 2, 0);
46463
+ return fragments.join("");
46464
+ }
46344
46465
  const binding = part.kind === "variable" ? arrayStore(state)?.get(part.name) : void 0;
46345
46466
  const holding = binding ? requireArrays(state).owner.hold() : void 0;
46346
46467
  const selector = getArraySelector(part);
@@ -46730,7 +46851,8 @@ ${prefix3} line ${offset + line}: \`${source2.split("\n")[line - 1] ?? ""}'
46730
46851
  }
46731
46852
  return fields3;
46732
46853
  }
46733
- const arrayOwned = word.parts.some((part) => part.kind === "variable" && (getArraySelector(part) !== void 0 || arrayStore(state)?.get(part.name) !== void 0));
46854
+ const arrayOwned = word.parts.some((part) => part.kind === "variable" && !part.prefixNames && (getArraySelector(part) !== void 0 || arrayStore(state)?.get(part.name) !== void 0));
46855
+ const prefixOwned = word.parts.some((part) => part.kind === "variable" && part.prefixNames === "@");
46734
46856
  const owner = arrayOwned ? requireArrays(state).owner : void 0;
46735
46857
  const holding = owner?.hold();
46736
46858
  const scratch = !owner && split4 && state.variables.IFS !== "" && word.parts.some((part) => !part.quoted && part.kind !== "text") ? this.budget.values.scope() : void 0;
@@ -46739,11 +46861,13 @@ ${prefix3} line ${offset + line}: \`${source2.split("\n")[line - 1] ?? ""}'
46739
46861
  owner?.reserve({ metadata: 128 + word.parts.length * 32, allocatedSlots: word.parts.length + 1, work: word.parts.length + 5 });
46740
46862
  scratch?.reserve(word.parts.length * 32, 0);
46741
46863
  const fields3 = [];
46864
+ let emptyNameGroups;
46865
+ let quoteGroup;
46742
46866
  const addField = () => {
46743
46867
  if (fields3.length >= this.budget.limits.maxExpansionFields) this.budget.fail("maxExpansionFields");
46744
46868
  scratch?.reserve(32, 0);
46745
46869
  owner?.reserve({ metadata: 32, allocatedSlots: 1, work: 3 });
46746
- fields3.push({ fragments: [], bytes: false, patterns: void 0, present: false });
46870
+ fields3.push({ fragments: [], bytes: false, patterns: void 0, present: false, independentPresence: false });
46747
46871
  };
46748
46872
  addField();
46749
46873
  let expansionBytes = 0;
@@ -46776,6 +46900,14 @@ ${prefix3} line ${offset + line}: \`${source2.split("\n")[line - 1] ?? ""}'
46776
46900
  regexAppend?.(text3, !glob);
46777
46901
  field2.fragments.push(value2);
46778
46902
  field2.present ||= present;
46903
+ if (present) {
46904
+ if (size > 0 || !quoteGroup) field2.independentPresence = true;
46905
+ else {
46906
+ io[valueScope]?.reserve(32, 0);
46907
+ field2.quoteGroups ??= [];
46908
+ field2.quoteGroups.push(quoteGroup);
46909
+ }
46910
+ }
46779
46911
  };
46780
46912
  const appendSplit = async (value2) => {
46781
46913
  this.budget.cpuCheckpoint();
@@ -46824,7 +46956,8 @@ ${prefix3} line ${offset + line}: \`${source2.split("\n")[line - 1] ?? ""}'
46824
46956
  const parts = word.parts.map((part) => ({ part, splitText: false, io }));
46825
46957
  for (let index = 0; index < parts.length; index++) {
46826
46958
  const { part, splitText, io: partIO } = parts[index];
46827
- const quotedPresence = part.quoted && !(arrayOwned && isQuoteMarker(part));
46959
+ quoteGroup = prefixNameQuoteGroups.get(part);
46960
+ const quotedPresence = part.quoted && !((arrayOwned || prefixOwned) && isQuoteMarker(part));
46828
46961
  if (part.kind === "variable" && ["-", "+", ":-", ":+"].includes(part.operator ?? "") && /^[a-zA-Z_][a-zA-Z_0-9]*$/u.test(part.name)) {
46829
46962
  const value2 = this.variable(state, part.name);
46830
46963
  const missing = value2 === void 0 || part.operator.startsWith(":") && value2 === "";
@@ -46838,7 +46971,18 @@ ${prefix3} line ${offset + line}: \`${source2.split("\n")[line - 1] ?? ""}'
46838
46971
  }
46839
46972
  }
46840
46973
  const selector = getArraySelector(part);
46841
- if (part.kind === "variable" && selector?.kind === "members" && !part.length && split4 && (!part.quoted || selector.separator === "@")) {
46974
+ if (part.kind === "variable" && part.prefixNames === "@" && split4 && (part.quoted || state.variables.IFS === "")) {
46975
+ let position = 0;
46976
+ for await (const name2 of this.prefixNames(part.name, state)) {
46977
+ if (position++) addField();
46978
+ append(name2, !part.quoted, true);
46979
+ }
46980
+ if (position === 0 && quoteGroup) {
46981
+ io[valueScope]?.reserve(32, 0);
46982
+ emptyNameGroups ??= /* @__PURE__ */ new Set();
46983
+ emptyNameGroups.add(quoteGroup);
46984
+ }
46985
+ } else if (part.kind === "variable" && selector?.kind === "members" && !part.length && split4 && (!part.quoted || selector.separator === "@")) {
46842
46986
  const members = await this.arrayMembers(part.name, state);
46843
46987
  for (let position = 0; position < members.length; position++) {
46844
46988
  if (position > 0) addField();
@@ -46868,6 +47012,19 @@ ${prefix3} line ${offset + line}: \`${source2.split("\n")[line - 1] ?? ""}'
46868
47012
  let resultBytes = 0;
46869
47013
  for (const field2 of fields3) {
46870
47014
  if (!field2.present && split4) continue;
47015
+ if (split4 && !field2.independentPresence && field2.quoteGroups) {
47016
+ const work = { remaining: this.budget.limits.maxExpansionBytes, signal: this.signal, exhausted: () => this.budget.fail("maxExpansionBytes") };
47017
+ let empty2 = true;
47018
+ for (const group of field2.quoteGroups) {
47019
+ const pending = stringCheckpoint(work);
47020
+ if (pending) await pending;
47021
+ if (!emptyNameGroups?.has(group)) {
47022
+ empty2 = false;
47023
+ break;
47024
+ }
47025
+ }
47026
+ if (empty2) continue;
47027
+ }
46871
47028
  if (scratch && field2.fragments.length > 1 && !field2.bytes) scratch.reserve(field2.fragments.reduce((bytes3, value2) => bytes3 + shellValueText(value2).length * 2, 0), 0);
46872
47029
  const assembled = concatShellValues(field2.fragments, io[valueScope]);
46873
47030
  const projection = shellValueText(assembled);
@@ -46994,8 +47151,169 @@ ${prefix3} line ${offset + line}: \`${source2.split("\n")[line - 1] ?? ""}'
46994
47151
  holding.release();
46995
47152
  }
46996
47153
  }
47154
+ async recursiveGlob(value2, pattern, state) {
47155
+ const failWalk = (message3) => {
47156
+ const error = new FsError("EFBIG", { syscall: "glob", message: message3 });
47157
+ this.budget.controller.abort(error);
47158
+ throw error;
47159
+ };
47160
+ const work = { remaining: Math.min(Number.MAX_SAFE_INTEGER, this.budget.limits.maxExpansionBytes * 4 + 1024), signal: this.signal, exhausted: () => this.budget.fail("maxExpansionBytes") };
47161
+ const scratch = this.budget.values.scope();
47162
+ work.allocation = scratch;
47163
+ try {
47164
+ const segments = [];
47165
+ let start = 0;
47166
+ for (let end = 0; end <= pattern.length; end++) {
47167
+ const pending = stringCheckpoint(work);
47168
+ if (pending) await pending;
47169
+ if (end !== pattern.length && pattern[end] !== "/") continue;
47170
+ if (end > start) {
47171
+ const bytes3 = (await scanString(pattern, work, start, end)).bytes;
47172
+ if (++this.budget.globstarStates > 1e5) failWalk("globstar traversal state limit exceeded");
47173
+ scratch.reserveBytes(bytes3 * 2 + 64);
47174
+ segments.push(pattern.slice(start, end));
47175
+ }
47176
+ start = end + 1;
47177
+ }
47178
+ const make = (parent, suffix2, bytes3, descend, depth) => {
47179
+ const separator = parent.path && parent.path !== "/" ? "/" : "";
47180
+ const size = parent.bytes + separator.length + bytes3;
47181
+ if (size > this.budget.limits.maxExpansionBytes) this.budget.fail("maxExpansionBytes");
47182
+ if (++this.budget.globstarStates > 1e5) failWalk("globstar traversal state limit exceeded");
47183
+ if (depth > 128) failWalk("globstar directory depth limit exceeded");
47184
+ scratch.reserveBytes(size * 2 + 128);
47185
+ return { path: parent.path + separator + suffix2, bytes: size, descend, depth };
47186
+ };
47187
+ const empty2 = { path: "", bytes: 0, descend: true, depth: 0 };
47188
+ let candidates = [make(empty2, pattern.startsWith("/") ? "/" : "", pattern.startsWith("/") ? 1 : 0, true, 0)];
47189
+ const ignored = (error) => {
47190
+ this.signal.throwIfAborted();
47191
+ return ["ENOENT", "ENOTDIR", "EACCES"].includes(errorCode(error) ?? "");
47192
+ };
47193
+ const read = async (candidate) => {
47194
+ const maxEntries = 1e5 - this.budget.globstarEntries;
47195
+ let entries2;
47196
+ try {
47197
+ entries2 = await interruptible3(this.fs.readdir(pathOf(state, candidate.path || "."), { signal: this.signal, maxEntries }), this.signal);
47198
+ } catch (error) {
47199
+ this.signal.throwIfAborted();
47200
+ if (errorCode(error) === "EFBIG") this.budget.controller.abort(error);
47201
+ throw error;
47202
+ }
47203
+ this.signal.throwIfAborted();
47204
+ if (entries2.length > maxEntries) failWalk("globstar directory entry limit exceeded");
47205
+ this.budget.globstarEntries += entries2.length;
47206
+ return entries2;
47207
+ };
47208
+ let wildcardPrefix = false;
47209
+ for (let index = 0; index < segments.length; index++) {
47210
+ const segment2 = segments[index];
47211
+ const terminal = index === segments.length - 1;
47212
+ const next = /* @__PURE__ */ new Map();
47213
+ const add3 = (candidate) => {
47214
+ const prior = next.get(candidate.path);
47215
+ if (!prior || candidate.descend && !prior.descend) next.set(candidate.path, candidate);
47216
+ };
47217
+ if (segment2 === "**") {
47218
+ const visited = /* @__PURE__ */ new Set();
47219
+ for (const root of candidates) {
47220
+ this.signal.throwIfAborted();
47221
+ try {
47222
+ if ((await interruptible3(this.fs.stat(pathOf(state, root.path || "."), { signal: this.signal }), this.signal)).type !== "directory") continue;
47223
+ } catch (error) {
47224
+ if (ignored(error)) continue;
47225
+ throw error;
47226
+ }
47227
+ if (!terminal || root.path) {
47228
+ add3(terminal && !wildcardPrefix && root.path !== "/" ? make(root, "", 0, root.descend, root.depth) : root);
47229
+ }
47230
+ const queue = root.descend ? [root] : [];
47231
+ while (queue.length) {
47232
+ const directory2 = queue.pop();
47233
+ if (visited.has(directory2.path)) continue;
47234
+ visited.add(directory2.path);
47235
+ let entries2;
47236
+ try {
47237
+ entries2 = await read(directory2);
47238
+ } catch (error) {
47239
+ if (ignored(error)) continue;
47240
+ throw error;
47241
+ }
47242
+ for (const entry of entries2) {
47243
+ const pending = stringCheckpoint(work);
47244
+ if (pending) await pending;
47245
+ if (entry.name === "." || entry.name === ".." || !state.dotglob && entry.name.startsWith(".")) continue;
47246
+ const bytes3 = (await scanString(entry.name, work)).bytes;
47247
+ const child = make(directory2, entry.name, bytes3, entry.type === "directory", directory2.depth + (entry.type === "directory" ? 1 : 0));
47248
+ if (terminal || entry.type === "directory" || entry.type === "symlink" && root.path !== "") add3(child);
47249
+ if (entry.type === "directory") queue.push(child);
47250
+ }
47251
+ }
47252
+ }
47253
+ wildcardPrefix = true;
47254
+ } else if (!/(?:^|[^\\])[*?[]/u.test(segment2)) {
47255
+ const literal = segment2.replace(/\\(.)/gu, "$1");
47256
+ const bytes3 = (await scanString(literal, work)).bytes;
47257
+ for (const candidate of candidates) add3(make(candidate, literal, bytes3, true, candidate.depth));
47258
+ } else {
47259
+ const matches3 = await compilePattern(segment2, work);
47260
+ for (const candidate of candidates) {
47261
+ let entries2;
47262
+ try {
47263
+ entries2 = await read(candidate);
47264
+ } catch (error) {
47265
+ if (ignored(error)) continue;
47266
+ throw error;
47267
+ }
47268
+ for (const entry of entries2) {
47269
+ const pending = stringCheckpoint(work);
47270
+ if (pending) await pending;
47271
+ if (entry.name !== "." && entry.name !== ".." && (state.dotglob || !entry.name.startsWith(".") || segment2.startsWith(".")) && await matches3(entry.name)) {
47272
+ add3(make(candidate, entry.name, (await scanString(entry.name, work)).bytes, true, candidate.depth));
47273
+ }
47274
+ }
47275
+ }
47276
+ wildcardPrefix = true;
47277
+ }
47278
+ candidates = [...next.values()];
47279
+ }
47280
+ const found = [];
47281
+ let outputBytes = 0;
47282
+ for (const candidate of candidates) {
47283
+ this.signal.throwIfAborted();
47284
+ try {
47285
+ const path = pathOf(state, candidate.path);
47286
+ const stat = await interruptible3(value2.endsWith("/") ? this.fs.stat(path, { signal: this.signal }) : this.fs.lstat(path, { signal: this.signal }), this.signal);
47287
+ if (value2.endsWith("/") && stat.type !== "directory") continue;
47288
+ const slash = value2.endsWith("/") && !candidate.path.endsWith("/") ? "/" : "";
47289
+ if (found.length >= this.budget.limits.maxExpansionFields) this.budget.fail("maxExpansionFields");
47290
+ outputBytes += candidate.bytes + slash.length;
47291
+ if (outputBytes > this.budget.limits.maxExpansionBytes) this.budget.fail("maxExpansionBytes");
47292
+ scratch.reserveBytes(candidate.bytes * 2 + slash.length + 8);
47293
+ found.push(candidate.path + slash);
47294
+ } catch (error) {
47295
+ if (!ignored(error)) throw error;
47296
+ }
47297
+ }
47298
+ await sortExpansionStrings(found, work);
47299
+ return found.length ? found : [value2];
47300
+ } finally {
47301
+ scratch.close();
47302
+ }
47303
+ }
46997
47304
  async glob(value2, pattern, state) {
46998
47305
  if (!/(?:^|[^\\])[*?[]/u.test(pattern)) return [value2];
47306
+ if (state.globstar) {
47307
+ const work2 = { remaining: Math.min(Number.MAX_SAFE_INTEGER, this.budget.limits.maxExpansionBytes * 4 + 1024), signal: this.signal, exhausted: () => this.budget.fail("maxExpansionBytes") };
47308
+ let start = 0;
47309
+ for (let end = 0; end <= pattern.length; end++) {
47310
+ const pending = stringCheckpoint(work2);
47311
+ if (pending) await pending;
47312
+ if (end !== pattern.length && pattern[end] !== "/") continue;
47313
+ if (end - start === 2 && pattern[start] === "*" && pattern[start + 1] === "*") return this.recursiveGlob(value2, pattern, state);
47314
+ start = end + 1;
47315
+ }
47316
+ }
46999
47317
  const absolute2 = pattern.startsWith("/");
47000
47318
  const work = { remaining: Math.min(Number.MAX_SAFE_INTEGER, this.budget.limits.maxExpansionBytes * 4 + 1024), signal: this.signal, exhausted: () => this.budget.fail("maxExpansionBytes") };
47001
47319
  let candidates = [absolute2 ? "/" : ""];
@@ -47838,6 +48156,7 @@ var Shell = class {
47838
48156
  getopts: { cursor: { index: 0 }, integer: true },
47839
48157
  directoryStack: { entries: [], bytes: 0 },
47840
48158
  dotglob: false,
48159
+ globstar: false,
47841
48160
  status: 0,
47842
48161
  substitutionStatus: 0,
47843
48162
  depth: 0,