fyn 3.0.4 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/fyn.mjs CHANGED
@@ -2098,7 +2098,7 @@ var require_signal_exit = /* @__PURE__ */ __commonJSMin(((exports, module) => {
2098
2098
  return function() {};
2099
2099
  };
2100
2100
  else {
2101
- var assert$2 = __require("assert");
2101
+ var assert$3 = __require("assert");
2102
2102
  var signals$1 = require_signals$3();
2103
2103
  var isWin = /^win/i.test(process.platform);
2104
2104
  var EE$7 = __require("events");
@@ -2118,7 +2118,7 @@ var require_signal_exit = /* @__PURE__ */ __commonJSMin(((exports, module) => {
2118
2118
  module.exports = function(cb, opts) {
2119
2119
  /* istanbul ignore if */
2120
2120
  if (!processOk(global.process)) return function() {};
2121
- assert$2.equal(typeof cb, "function", "a callback must be provided for exit handler");
2121
+ assert$3.equal(typeof cb, "function", "a callback must be provided for exit handler");
2122
2122
  if (loaded === false) load$1();
2123
2123
  var ev = "exit";
2124
2124
  if (opts && opts.alwaysLast) ev = "afterexit";
@@ -5782,7 +5782,7 @@ var file_ops_default = wrapWin32Fs(fileOps);
5782
5782
 
5783
5783
  //#endregion
5784
5784
  //#region node_modules/semver/internal/constants.js
5785
- var require_constants$8 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
5785
+ var require_constants$7 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
5786
5786
  const SEMVER_SPEC_VERSION = "2.0.0";
5787
5787
  const MAX_LENGTH = 256;
5788
5788
  const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER ||
@@ -5820,7 +5820,7 @@ var require_debug$2 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
5820
5820
  //#endregion
5821
5821
  //#region node_modules/semver/internal/re.js
5822
5822
  var require_re = /* @__PURE__ */ __commonJSMin(((exports, module) => {
5823
- const { MAX_SAFE_COMPONENT_LENGTH, MAX_SAFE_BUILD_LENGTH, MAX_LENGTH } = require_constants$8();
5823
+ const { MAX_SAFE_COMPONENT_LENGTH, MAX_SAFE_BUILD_LENGTH, MAX_LENGTH } = require_constants$7();
5824
5824
  const debug = require_debug$2();
5825
5825
  exports = module.exports = {};
5826
5826
  const re = exports.re = [];
@@ -5935,7 +5935,7 @@ var require_identifiers = /* @__PURE__ */ __commonJSMin(((exports, module) => {
5935
5935
  //#region node_modules/semver/classes/semver.js
5936
5936
  var require_semver$1 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
5937
5937
  const debug = require_debug$2();
5938
- const { MAX_LENGTH, MAX_SAFE_INTEGER } = require_constants$8();
5938
+ const { MAX_LENGTH, MAX_SAFE_INTEGER } = require_constants$7();
5939
5939
  const { safeRe: re, t } = require_re();
5940
5940
  const parseOptions = require_parse_options();
5941
5941
  const { compareIdentifiers } = require_identifiers();
@@ -6121,7 +6121,7 @@ var require_semver$1 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
6121
6121
 
6122
6122
  //#endregion
6123
6123
  //#region node_modules/semver/functions/parse.js
6124
- var require_parse$7 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
6124
+ var require_parse$6 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
6125
6125
  const SemVer = require_semver$1();
6126
6126
  const parse = (version, options, throwErrors = false) => {
6127
6127
  if (version instanceof SemVer) return version;
@@ -6138,7 +6138,7 @@ var require_parse$7 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
6138
6138
  //#endregion
6139
6139
  //#region node_modules/semver/functions/valid.js
6140
6140
  var require_valid$1 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
6141
- const parse = require_parse$7();
6141
+ const parse = require_parse$6();
6142
6142
  const valid = (version, options) => {
6143
6143
  const v = parse(version, options);
6144
6144
  return v ? v.version : null;
@@ -6149,7 +6149,7 @@ var require_valid$1 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
6149
6149
  //#endregion
6150
6150
  //#region node_modules/semver/functions/clean.js
6151
6151
  var require_clean = /* @__PURE__ */ __commonJSMin(((exports, module) => {
6152
- const parse = require_parse$7();
6152
+ const parse = require_parse$6();
6153
6153
  const clean = (version, options) => {
6154
6154
  const s = parse(version.trim().replace(/^[=v]+/, ""), options);
6155
6155
  return s ? s.version : null;
@@ -6179,7 +6179,7 @@ var require_inc = /* @__PURE__ */ __commonJSMin(((exports, module) => {
6179
6179
  //#endregion
6180
6180
  //#region node_modules/semver/functions/diff.js
6181
6181
  var require_diff$1 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
6182
- const parse = require_parse$7();
6182
+ const parse = require_parse$6();
6183
6183
  const diff = (version1, version2) => {
6184
6184
  const v1 = parse(version1, null, true);
6185
6185
  const v2 = parse(version2, null, true);
@@ -6232,7 +6232,7 @@ var require_patch$1 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
6232
6232
  //#endregion
6233
6233
  //#region node_modules/semver/functions/prerelease.js
6234
6234
  var require_prerelease = /* @__PURE__ */ __commonJSMin(((exports, module) => {
6235
- const parse = require_parse$7();
6235
+ const parse = require_parse$6();
6236
6236
  const prerelease = (version, options) => {
6237
6237
  const parsed = parse(version, options);
6238
6238
  return parsed && parsed.prerelease.length ? parsed.prerelease : null;
@@ -6377,7 +6377,7 @@ var require_cmp = /* @__PURE__ */ __commonJSMin(((exports, module) => {
6377
6377
  //#region node_modules/semver/functions/coerce.js
6378
6378
  var require_coerce = /* @__PURE__ */ __commonJSMin(((exports, module) => {
6379
6379
  const SemVer = require_semver$1();
6380
- const parse = require_parse$7();
6380
+ const parse = require_parse$6();
6381
6381
  const { safeRe: re, t } = require_re();
6382
6382
  const coerce = (version, options) => {
6383
6383
  if (version instanceof SemVer) return version;
@@ -6409,8 +6409,8 @@ var require_coerce = /* @__PURE__ */ __commonJSMin(((exports, module) => {
6409
6409
  //#endregion
6410
6410
  //#region node_modules/semver/functions/truncate.js
6411
6411
  var require_truncate = /* @__PURE__ */ __commonJSMin(((exports, module) => {
6412
- const parse = require_parse$7();
6413
- const constants = require_constants$8();
6412
+ const parse = require_parse$6();
6413
+ const constants = require_constants$7();
6414
6414
  const SemVer = require_semver$1();
6415
6415
  const truncate = (version, truncation, options) => {
6416
6416
  if (!constants.RELEASE_TYPES.includes(truncation)) return null;
@@ -6591,7 +6591,7 @@ var require_range = /* @__PURE__ */ __commonJSMin(((exports, module) => {
6591
6591
  const debug = require_debug$2();
6592
6592
  const SemVer = require_semver$1();
6593
6593
  const { safeRe: re, src, t, comparatorTrimReplace, tildeTrimReplace, caretTrimReplace } = require_re();
6594
- const { FLAG_INCLUDE_PRERELEASE, FLAG_LOOSE } = require_constants$8();
6594
+ const { FLAG_INCLUDE_PRERELEASE, FLAG_LOOSE } = require_constants$7();
6595
6595
  const BUILDSTRIPRE = new RegExp(src[t.BUILD], "g");
6596
6596
  const isNullSet = (c) => c.value === "<0.0.0-0";
6597
6597
  const isAny = (c) => c.value === "";
@@ -7181,10 +7181,10 @@ var require_subset = /* @__PURE__ */ __commonJSMin(((exports, module) => {
7181
7181
  //#region node_modules/semver/index.js
7182
7182
  var require_semver = /* @__PURE__ */ __commonJSMin(((exports, module) => {
7183
7183
  const internalRe = require_re();
7184
- const constants = require_constants$8();
7184
+ const constants = require_constants$7();
7185
7185
  const SemVer = require_semver$1();
7186
7186
  const identifiers = require_identifiers();
7187
- const parse = require_parse$7();
7187
+ const parse = require_parse$6();
7188
7188
  const valid = require_valid$1();
7189
7189
  const clean = require_clean();
7190
7190
  const inc = require_inc();
@@ -10410,6 +10410,9 @@ const FYN_LOCAL_HARD_TAG = "-fynlocal_h";
10410
10410
  function isLocal(v) {
10411
10411
  return v.indexOf(FYN_LOCAL_TAG) > 0;
10412
10412
  }
10413
+ function isLocalHard(v) {
10414
+ return v.indexOf(FYN_LOCAL_HARD_TAG) > 0;
10415
+ }
10413
10416
  function localify(v, localType, hash = "") {
10414
10417
  return `${v}${localType === "hard" ? FYN_LOCAL_HARD_TAG : FYN_LOCAL_TAG}${hash}`;
10415
10418
  }
@@ -10497,12 +10500,13 @@ function equal(v1, v2) {
10497
10500
  //#region lib/dep-item.ts
10498
10501
  var DepItem = class {
10499
10502
  name;
10500
- /** Optional dependency failure flag (truthy = failed, number for failure code) */
10503
+ /** Optional dependency failure code - truthy means failed; never assigned a boolean */
10501
10504
  optFailed;
10502
10505
  /** Version string (may be set during resolution) */
10503
10506
  version;
10504
10507
  /** Was optional check performed */
10505
10508
  optChecked;
10509
+ /** read by the dep locker's `makeDep`, so not private (FJM-154) */
10506
10510
  _semver;
10507
10511
  /** Original top level package.json dep section (dep, dev, per, opt) */
10508
10512
  src;
@@ -10676,137 +10680,108 @@ var DepItem = class {
10676
10680
  };
10677
10681
 
10678
10682
  //#endregion
10679
- //#region node_modules/item-queue/dist/inflight.js
10683
+ //#region node_modules/xflight/dist/index.js
10684
+ /** Simple assert that works in browser and Node.js */
10685
+ function assert$2(condition, message) {
10686
+ if (!condition) throw new Error(message);
10687
+ }
10680
10688
  /**
10681
- * async inflight operation memoizer
10689
+ * Keyed store of in-progress work, with start-time and last-check-time tracking.
10690
+ *
10691
+ * This is the generic core: it stores any value type `V` under any {@link RecordKey} and
10692
+ * knows nothing about promises. {@link Inflight} builds promise deduplication on top of it.
10693
+ *
10694
+ * `item-queue` uses this directly - it tracks `{ item, promise }` records keyed by an
10695
+ * incrementing number, which is neither a promise nor a string, so it needs the core rather
10696
+ * than {@link Inflight} (FJM-26).
10697
+ *
10698
+ * @template V The type of the tracked value.
10682
10699
  */
10683
- var Inflight = class {
10684
- constructor() {
10685
- this._count = 0;
10686
- this._inflights = {};
10687
- }
10700
+ var InflightStore = class {
10701
+ /** Map of inflight items by key */
10702
+ _inflights = /* @__PURE__ */ new Map();
10703
+ /**
10704
+ * Track a value under a key.
10705
+ *
10706
+ * @throws If key already exists.
10707
+ */
10688
10708
  add(key, value, now) {
10689
- assert(this._inflights[key] === void 0, `Already has inflight item ${String(key)}`);
10690
- this._count++;
10691
- now = now || Date.now();
10692
- this._inflights[key] = {
10693
- start: now,
10694
- lastXTime: now,
10709
+ assert$2(!this._inflights.has(key), `xflight: item ${String(key)} already exist`);
10710
+ const timestamp = now ?? Date.now();
10711
+ this._inflights.set(key, {
10712
+ start: timestamp,
10713
+ lastXTime: timestamp,
10695
10714
  value
10696
- };
10715
+ });
10697
10716
  return value;
10698
10717
  }
10699
- /**
10700
- * return internal inflights records object
10701
- */
10702
- get inflights() {
10703
- return this._inflights;
10704
- }
10705
- /**
10706
- * get an inflight record
10707
- *
10708
- * @param key
10709
- * @returns
10710
- */
10718
+ /** Get the tracked value for a key, or undefined. */
10711
10719
  get(key) {
10712
- const x = this._inflights[key];
10713
- return x && x.value;
10720
+ return this._inflights.get(key)?.value;
10714
10721
  }
10715
10722
  /**
10716
- * remove an inflight record
10723
+ * Remove the inflight item for a key.
10717
10724
  *
10718
- * @param key
10725
+ * @throws If key does not exist.
10719
10726
  */
10720
10727
  remove(key) {
10721
- assert(this._inflights[key] !== void 0, `Removing non-existing inflight item ${String(key)}`);
10722
- assert(this._count > 0, `Removing inflight item ${String(key)} but count is ${this._count}`);
10723
- this._count--;
10724
- if (this._count === 0) this._inflights = {};
10725
- else this._inflights[key] = void 0;
10728
+ assert$2(this._inflights.has(key), `xflight: removing non-existing item ${String(key)}`);
10729
+ this._inflights.delete(key);
10726
10730
  }
10727
10731
  /**
10728
- * Check if inflight record is empty (ie: no inflight operations)
10732
+ * Iterate the tracked items as `[key, record]` pairs.
10733
+ *
10734
+ * Prefer this over reaching for the internal map - it is what lets a caller sweep every
10735
+ * in-progress entry, which is what item-queue's overdue-work watcher does.
10729
10736
  */
10737
+ entries() {
10738
+ return this._inflights.entries();
10739
+ }
10740
+ /** Whether there are no inflight items. */
10730
10741
  get isEmpty() {
10731
- return this._count === 0;
10742
+ return this._inflights.size === 0;
10732
10743
  }
10733
- /**
10734
- * get number of operations inflight
10735
- */
10744
+ /** The number of inflight items. */
10736
10745
  get count() {
10737
- return this._count;
10746
+ return this._inflights.size;
10738
10747
  }
10739
- /**
10740
- * Get the start time of a inflight operation
10741
- *
10742
- * @param key
10743
- * @returns
10744
- */
10748
+ /** Get the start time (ms since epoch) for a key, or undefined. */
10745
10749
  getStartTime(key) {
10746
- const x = this._inflights[key];
10747
- return x && x.start;
10750
+ return this._inflights.get(key)?.start;
10748
10751
  }
10749
- /**
10750
- * Get the time an inflight operation has elapsed
10751
- * @param key
10752
- * @param now
10753
- * @returns
10754
- */
10752
+ /** Elapsed ms since the start for a key, or -1 if not found. */
10755
10753
  time(key, now) {
10756
- const x = this._inflights[key];
10757
- if (x) return (now || Date.now()) - x.start;
10758
- return -1;
10754
+ const item = this._inflights.get(key);
10755
+ return item ? (now ?? Date.now()) - item.start : -1;
10759
10756
  }
10760
- /**
10761
- * Get the time an inflight operation has elapsed
10762
- * @param key
10763
- * @param now
10764
- * @returns
10765
- */
10757
+ /** Alias for {@link time}. */
10766
10758
  elapseTime(key, now) {
10767
10759
  return this.time(key, now);
10768
10760
  }
10769
- /**
10770
- * Get the time the inflight operation was last checked
10771
- * @param key
10772
- * @returns
10773
- */
10761
+ /** Get the last check time (ms since epoch) for a key, or undefined. */
10774
10762
  getCheckTime(key) {
10775
- const x = this._inflights[key];
10776
- return x && x.lastXTime;
10763
+ return this._inflights.get(key)?.lastXTime;
10777
10764
  }
10778
- /**
10779
- * Get the time elapsed since last check
10780
- *
10781
- * @param key
10782
- * @param now
10783
- * @returns
10784
- */
10765
+ /** Elapsed ms since the last check for a key, or -1 if not found. */
10785
10766
  lastCheckTime(key, now) {
10786
- const x = this._inflights[key];
10787
- if (x) return (now || Date.now()) - x.lastXTime;
10788
- return -1;
10767
+ const item = this._inflights.get(key);
10768
+ return item ? (now ?? Date.now()) - item.lastXTime : -1;
10789
10769
  }
10790
- /**
10791
- * Get the time elapsed since last check
10792
- *
10793
- * @param key
10794
- * @param now
10795
- * @returns
10796
- */
10770
+ /** Alias for {@link lastCheckTime}. */
10797
10771
  elapseCheckTime(key, now) {
10798
10772
  return this.lastCheckTime(key, now);
10799
10773
  }
10800
10774
  /**
10801
- * Reset last check time to `now` or `Date.now()`
10775
+ * Reset the last check time for a key, or for all inflight items if no key is given.
10802
10776
  *
10803
- * @param key
10804
- * @param now
10805
- * @returns
10777
+ * @returns This instance for chaining.
10806
10778
  */
10807
10779
  resetCheckTime(key, now) {
10808
- const x = this._inflights[key];
10809
- if (x) x.lastXTime = now || Date.now();
10780
+ const timestamp = now ?? Date.now();
10781
+ if (key !== void 0) {
10782
+ const item = this._inflights.get(key);
10783
+ if (item) item.lastXTime = timestamp;
10784
+ } else for (const item of this._inflights.values()) item.lastXTime = timestamp;
10810
10785
  return this;
10811
10786
  }
10812
10787
  };
@@ -10826,7 +10801,7 @@ var ItemQueue = class ItemQueue extends EventEmitter$1 {
10826
10801
  super();
10827
10802
  this.Promise = options.Promise || ItemQueue.Promise;
10828
10803
  assert(this.Promise, "ItemQueue: No Promise implementation available");
10829
- this._pending = new Inflight();
10804
+ this._pending = new InflightStore();
10830
10805
  this._itemQ = [];
10831
10806
  if (options.itemQ) this.addItems(options.itemQ, true);
10832
10807
  this._concurrency = options.concurrency || 15;
@@ -11046,7 +11021,7 @@ var ItemQueue = class ItemQueue extends EventEmitter$1 {
11046
11021
  const watched = [];
11047
11022
  const still = [];
11048
11023
  const now = Date.now();
11049
- for (const [id, v] of Object.entries(this._pending.inflights)) if (v) {
11024
+ for (const [id, v] of this._pending.entries()) {
11050
11025
  const lastXTime = this._pending.lastCheckTime(id, now);
11051
11026
  const time = this._pending.time(id, now);
11052
11027
  const overdue = time >= this._watchTime;
@@ -12983,6 +12958,13 @@ function groupMM(mms, groups) {
12983
12958
  return groups;
12984
12959
  }
12985
12960
 
12961
+ //#endregion
12962
+ //#region node_modules/@fynpo/base/dist/util.js
12963
+ /**
12964
+ * ensure a path uses `/` for separator
12965
+ */
12966
+ const posixify$4 = Path.sep === "/" ? (x) => x : (path) => path.replace(/\\/g, "/");
12967
+
12986
12968
  //#endregion
12987
12969
  //#region node_modules/@fynpo/base/dist/packages-config.js
12988
12970
  const DEFAULT_INCLUDE = ["packages/*"];
@@ -13566,13 +13548,6 @@ function makeGitignoreMatcher(cwd) {
13566
13548
  };
13567
13549
  }
13568
13550
 
13569
- //#endregion
13570
- //#region node_modules/@fynpo/base/dist/util.js
13571
- /**
13572
- * ensure a path uses `/` for separator
13573
- */
13574
- const posixify$4 = Path.sep === "/" ? (x) => x : (path) => path.replace(/\\/g, "/");
13575
-
13576
13551
  //#endregion
13577
13552
  //#region node_modules/@fynpo/base/dist/fynpo-dep-graph.js
13578
13553
  /**
@@ -14762,6 +14737,161 @@ const spinner = VisualLogger.spinners[1];
14762
14737
  //#endregion
14763
14738
  //#region lib/util/lifecycle-script-policy.ts
14764
14739
  const TRUSTED_URL_TYPES = /* @__PURE__ */ new Set(["npm"]);
14740
+ /** The install-time lifecycle scripts this policy gates. */
14741
+ const LIFECYCLE_SCRIPTS = [
14742
+ "preinstall",
14743
+ "install",
14744
+ "postinstall"
14745
+ ];
14746
+ /** Valid `fyn.scriptPolicy` modes, loosest to strictest. */
14747
+ const SCRIPT_POLICY_MODES = [
14748
+ "all",
14749
+ "source",
14750
+ "review",
14751
+ "off"
14752
+ ];
14753
+ /**
14754
+ * The default. Nothing runs its install scripts without an approval, workspace
14755
+ * packages excepted - npm 12's model. `--script-policy=source` is the opt-out
14756
+ * back to trusting a package because of where it came from.
14757
+ */
14758
+ const DEFAULT_SCRIPT_POLICY = "review";
14759
+ /**
14760
+ * Split a range union into its parts. Accepts npm's `||` and the single `|` a
14761
+ * hand-written config may use.
14762
+ *
14763
+ * @param {string} range a semver range, possibly a union
14764
+ * @returns {string[]} the parts, trimmed
14765
+ */
14766
+ function splitRange(range) {
14767
+ return String(range).split(/\s*\|\|?\s*/).map((part) => part.trim()).filter(Boolean);
14768
+ }
14769
+ /**
14770
+ * Read an allowScripts key's spec as a semver range.
14771
+ *
14772
+ * A key's spec is a range (`^1.2.3`, `1.2.3`, `^1.2.3 || ^2.0.0`) or it is not
14773
+ * a range at all - a git/URL spec such as `github:user/repo#v1`, which matches
14774
+ * the requested spec literally instead.
14775
+ *
14776
+ * @param {string} spec the part of the key after the package name
14777
+ * @returns {(string|undefined)} the range in `||` form, or undefined
14778
+ */
14779
+ function asRange(spec) {
14780
+ if (!spec) return;
14781
+ const normalized = splitRange(spec).join(" || ");
14782
+ return import_semver.default.validRange(normalized) ? normalized : void 0;
14783
+ }
14784
+ /**
14785
+ * Normalize a `fyn.scriptPolicy` value.
14786
+ *
14787
+ * @param {*} mode the configured value
14788
+ * @param {string} [dflt] mode to use when unset
14789
+ * @returns {string} one of {@link SCRIPT_POLICY_MODES}
14790
+ * @throws {Error} when the value is set but not a known mode
14791
+ */
14792
+ function normalizeScriptPolicy(mode, dflt = DEFAULT_SCRIPT_POLICY) {
14793
+ if (mode === void 0 || mode === null || mode === "") return dflt;
14794
+ const normalized = String(mode).toLowerCase();
14795
+ if (!SCRIPT_POLICY_MODES.includes(normalized)) throw new Error(`fyn scriptPolicy "${mode}" is not valid - expected one of ${SCRIPT_POLICY_MODES.join(", ")}`);
14796
+ return normalized;
14797
+ }
14798
+ /**
14799
+ * Normalize a `fyn.scriptPolicy` value, keeping "not configured" distinct from
14800
+ * the default so scopes can be merged before the default is applied.
14801
+ *
14802
+ * @param {*} mode the configured value
14803
+ * @returns {(string|undefined)} the mode, or undefined when unset
14804
+ * @throws {Error} when the value is set but not a known mode
14805
+ */
14806
+ function normalizeScriptPolicyIfSet(mode) {
14807
+ if (mode === void 0 || mode === null || mode === "") return;
14808
+ return normalizeScriptPolicy(mode);
14809
+ }
14810
+ /**
14811
+ * Pick the stricter of two policy modes. Used to merge a package's setting with
14812
+ * the monorepo's: a package may tighten what the repo asked for, never loosen
14813
+ * it.
14814
+ *
14815
+ * @param {...string} modes modes to compare, unset values ignored
14816
+ * @returns {string} the strictest mode given, or the default when none are
14817
+ */
14818
+ function strictestScriptPolicy(...modes) {
14819
+ const given = modes.filter((mode) => mode !== void 0);
14820
+ if (given.length === 0) return DEFAULT_SCRIPT_POLICY;
14821
+ return given.reduce((strictest, mode) => SCRIPT_POLICY_MODES.indexOf(mode) > SCRIPT_POLICY_MODES.indexOf(strictest) ? mode : strictest);
14822
+ }
14823
+ /**
14824
+ * Normalize a configured allowScripts value into a map.
14825
+ *
14826
+ * A map is used as-is. A list of package names - the `--allow-scripts=a,b` CLI
14827
+ * form - becomes a blanket approval for each name, matching npm's flag.
14828
+ *
14829
+ * @param {(object|string[]|string)} value the configured value
14830
+ * @returns {object} an allowScripts map
14831
+ */
14832
+ function normalizeAllowScriptsConfig(value) {
14833
+ if (!value) return {};
14834
+ const list = Array.isArray(value) ? value : typeof value === "string" ? [value] : void 0;
14835
+ if (list === void 0) return value;
14836
+ return list.flatMap((name) => String(name).split(",")).reduce((map, name) => {
14837
+ const key = name.trim();
14838
+ if (key) map[key] = true;
14839
+ return map;
14840
+ }, {});
14841
+ }
14842
+ /**
14843
+ * Merge allowScripts maps from loosest to tightest scope - fynpo config, then
14844
+ * package.json, then CLI.
14845
+ *
14846
+ * Approvals accumulate, but a `false` at any level is final: a package cannot
14847
+ * approve what the monorepo denied. Cross-key denials (a bare-name `false`
14848
+ * against a version-pinned approval) are resolved at evaluation time, where
14849
+ * every matching key is folded together.
14850
+ *
14851
+ * @param {...object} maps allowScripts maps, loosest scope first
14852
+ * @returns {object} the merged map
14853
+ */
14854
+ function mergeAllowScripts(...maps) {
14855
+ const merged = {};
14856
+ for (const map of maps) {
14857
+ const normalized = normalizeAllowScriptsConfig(map);
14858
+ for (const key of Object.keys(normalized)) {
14859
+ if (merged[key] === false) continue;
14860
+ merged[key] = normalized[key];
14861
+ }
14862
+ }
14863
+ return merged;
14864
+ }
14865
+ /**
14866
+ * Fold the `fyn.denyScripts` map for a package.
14867
+ *
14868
+ * The map has the same shape and key grammar as `fyn.allowScripts` -
14869
+ * `{ semver, scripts }` with both fields optional, plus the shorthand forms -
14870
+ * because it answers the same two questions. The only difference is that a
14871
+ * match denies: an absent `semver` denies every version, an absent `scripts`
14872
+ * denies every install script.
14873
+ *
14874
+ * Entries need no `!` markers; every entry in this map is already negative.
14875
+ *
14876
+ * @param {object} depInfo resolved package data
14877
+ * @param {object} denyScripts the effective `fyn.denyScripts` map
14878
+ * @returns {{denyAll:boolean, scripts:Set<string>, key:(string|undefined)}} what
14879
+ * this map denies for this package
14880
+ */
14881
+ function foldDenyScripts(depInfo, denyScripts) {
14882
+ const acc = makeAcc();
14883
+ if (!denyScripts) return {
14884
+ denyAll: false,
14885
+ scripts: acc.deny,
14886
+ key: void 0
14887
+ };
14888
+ const key = foldAllowScripts(depInfo, denyScripts, acc, { version: depInfo.version });
14889
+ return {
14890
+ denyAll: acc.allowAll || acc.denyAll || acc.denied,
14891
+ scripts: /* @__PURE__ */ new Set([...acc.scripts, ...acc.deny]),
14892
+ key
14893
+ };
14894
+ }
14765
14895
  /**
14766
14896
  * Derive the effective source urlType for a dependency item.
14767
14897
  *
@@ -14799,9 +14929,28 @@ function getUrlType(depInfo) {
14799
14929
  if (spec) return analyze(spec).urlType;
14800
14930
  }
14801
14931
  /**
14932
+ * Whether a resolved package came from the workspace itself - a file:/link:
14933
+ * dependency or a fynpo sibling - rather than from a registry or a URL.
14934
+ *
14935
+ * Under `"review"` these are exempt: an allowlist is a review gate on code you
14936
+ * did not write, and monorepo source is reviewed by the PR that changed it.
14937
+ *
14938
+ * @param {object} depInfo resolved package data
14939
+ * @returns {boolean} true if the package is a workspace-local dependency
14940
+ */
14941
+ function isLocalSource(depInfo) {
14942
+ if (!depInfo) return false;
14943
+ if (depInfo.local) return true;
14944
+ const depItem = depInfo[DEP_ITEM];
14945
+ if (depItem && depItem.localType) return true;
14946
+ const spec = depItem && depItem.semver || depInfo[SEMVER];
14947
+ return Boolean(spec && analyze(spec).localType);
14948
+ }
14949
+ /**
14802
14950
  * Build the candidate whitelist keys for a package. Matching accepts BOTH the
14803
14951
  * original requested spec and the resolved version, e.g. `foo@github:user/repo`
14804
- * and `foo@2.3.0`.
14952
+ * and `foo@2.3.0`, plus the bare package name - npm's key form, where the
14953
+ * version it approved lives on the value side.
14805
14954
  *
14806
14955
  * @param {object} depInfo resolved package data
14807
14956
  * @returns {string[]} candidate keys, spec form first
@@ -14812,46 +14961,153 @@ function makeAllowKeys(depInfo) {
14812
14961
  const keys = [];
14813
14962
  if (spec) keys.push(`${depInfo.name}@${spec}`);
14814
14963
  if (depInfo.version && depInfo.version !== spec) keys.push(`${depInfo.name}@${depInfo.version}`);
14964
+ keys.push(depInfo.name);
14815
14965
  return keys;
14816
14966
  }
14817
14967
  /**
14818
- * Fold a single allowScripts entry value into an accumulator. Accepts an array
14819
- * of script names, a single script name, or the wildcard `true`/`"*"` to allow
14820
- * all scripts for the package. Script names are normalized to lowercase.
14968
+ * Classify a string allowScripts value. A lifecycle script name allows just
14969
+ * that script (fyn's form); anything that parses as a semver range is npm's
14970
+ * version pin, allowing all scripts for the matching version only.
14971
+ *
14972
+ * @param {string} value the string value
14973
+ * @returns {string} "script" or "version"
14974
+ */
14975
+ function classifyStringEntry(value) {
14976
+ if (LIFECYCLE_SCRIPTS.includes(value.toLowerCase())) return "script";
14977
+ return import_semver.default.validRange(value) ? "version" : "script";
14978
+ }
14979
+ /**
14980
+ * Fold a single allowScripts entry value into an accumulator.
14981
+ *
14982
+ * Every form is accepted, though fyn only ever writes the object one:
14983
+ *
14984
+ * | value | meaning |
14985
+ * |---|---|
14986
+ * | `{ semver, scripts }` | fyn's form - either field absent means "all" |
14987
+ * | `true` / `"*"` | all install scripts |
14988
+ * | `["install"]` / `"install"` | only those scripts |
14989
+ * | `"1.2.3"` / `"^1.2.3"` | npm's form - all scripts, only matching versions |
14990
+ * | `false` | denied outright |
14821
14991
  *
14822
14992
  * @param {(string[]|string|boolean)} value the allowScripts entry value
14823
- * @param {{allowAll:boolean, scripts:Set<string>}} acc accumulator to fold into
14824
- * @returns {{allowAll:boolean, scripts:Set<string>}} the accumulator
14993
+ * @param {{allowAll:boolean, scripts:Set<string>, denied:boolean}} acc
14994
+ * accumulator to fold into
14995
+ * @param {object} [ctx] matching context
14996
+ * @param {string} [ctx.version] the package's resolved version, for version pins
14997
+ * @returns {{allowAll:boolean, scripts:Set<string>, denied:boolean}} the accumulator
14825
14998
  */
14826
- function normalizeAllowEntry(value, acc) {
14999
+ function normalizeAllowEntry(value, acc, ctx = {}) {
15000
+ if (value === false) {
15001
+ acc.denied = true;
15002
+ return acc;
15003
+ }
14827
15004
  if (value === true || value === "*") {
14828
15005
  acc.allowAll = true;
14829
15006
  return acc;
14830
15007
  }
15008
+ if (value && typeof value === "object" && !Array.isArray(value)) {
15009
+ const range = asRange(value.semver);
15010
+ if (value.semver && !(range && ctx.version && satisfies(ctx.version, range))) return acc;
15011
+ if (value.scripts === void 0) {
15012
+ acc.allowAll = true;
15013
+ return acc;
15014
+ }
15015
+ return normalizeAllowEntry(value.scripts, acc, ctx);
15016
+ }
14831
15017
  let list = [];
14832
15018
  if (Array.isArray(value)) list = value;
14833
15019
  else if (value !== void 0) list = [value];
14834
- for (const s of list) if (s === true || s === "*") acc.allowAll = true;
14835
- else if (typeof s === "string") acc.scripts.add(s.toLowerCase());
15020
+ for (const s of list) {
15021
+ if (s === false) {
15022
+ acc.denied = true;
15023
+ continue;
15024
+ }
15025
+ if (s === true || s === "*") {
15026
+ acc.allowAll = true;
15027
+ continue;
15028
+ }
15029
+ if (typeof s !== "string") continue;
15030
+ const negated = s.startsWith("!");
15031
+ const name = negated || s.startsWith("+") ? s.slice(1) : s;
15032
+ if (negated) {
15033
+ if (name === "*") acc.denyAll = true;
15034
+ else acc.deny.add(name.toLowerCase());
15035
+ continue;
15036
+ }
15037
+ if (classifyStringEntry(name) === "version") {
15038
+ if (ctx.version && satisfies(ctx.version, name)) acc.allowAll = true;
15039
+ } else acc.scripts.add(name.toLowerCase());
15040
+ }
14836
15041
  return acc;
14837
15042
  }
14838
15043
  /**
14839
- * Fold any matching `fyn.allowScripts` entries (by spec or resolved-version key)
14840
- * into the accumulator.
15044
+ * A fresh accumulator for folding allow or deny entries.
15045
+ *
15046
+ * @returns {{allowAll:boolean, scripts:Set<string>, deny:Set<string>,
15047
+ * denyAll:boolean, denied:boolean}} the accumulator
15048
+ */
15049
+ function makeAcc() {
15050
+ return {
15051
+ allowAll: false,
15052
+ scripts: /* @__PURE__ */ new Set(),
15053
+ deny: /* @__PURE__ */ new Set(),
15054
+ denyAll: false,
15055
+ denied: false
15056
+ };
15057
+ }
15058
+ const allowIndexCache = /* @__PURE__ */ new WeakMap();
15059
+ /**
15060
+ * Group an allowScripts map by package name, parsing each key once.
14841
15061
  *
14842
- * @param {string[]} keys candidate keys from {@link makeAllowKeys}
15062
+ * @param {object} allowScripts the map
15063
+ * @returns {Map<string, object[]>} entries by package name
15064
+ */
15065
+ function indexAllowScripts(allowScripts) {
15066
+ if (!allowScripts) return /* @__PURE__ */ new Map();
15067
+ const cached = allowIndexCache.get(allowScripts);
15068
+ if (cached) return cached;
15069
+ const index = /* @__PURE__ */ new Map();
15070
+ for (const key of Object.keys(allowScripts)) {
15071
+ const at = key.indexOf("@", key.startsWith("@") ? 1 : 0);
15072
+ const name = at < 0 ? key : key.slice(0, at);
15073
+ const spec = at < 0 ? void 0 : key.slice(at + 1);
15074
+ const entries = index.get(name) || [];
15075
+ entries.push({
15076
+ key,
15077
+ spec,
15078
+ range: asRange(spec),
15079
+ value: allowScripts[key]
15080
+ });
15081
+ index.set(name, entries);
15082
+ }
15083
+ allowIndexCache.set(allowScripts, index);
15084
+ return index;
15085
+ }
15086
+ /**
15087
+ * Fold every `fyn.allowScripts` entry that matches this package into the
15088
+ * accumulator.
15089
+ *
15090
+ * A key matches when it is the bare package name (every version), when its
15091
+ * spec is a semver range the resolved version satisfies (`sharp@^0.34.0`,
15092
+ * `sharp@^0.34.0 || ^0.35.0`), or - for a git/URL dependency, where there is no
15093
+ * version to range over - when its spec is literally the requested spec.
15094
+ *
15095
+ * @param {object} depInfo resolved package data
14843
15096
  * @param {object} allowScripts the project's `fyn.allowScripts` map
14844
- * @param {{allowAll:boolean, scripts:Set<string>}} acc accumulator to fold into
15097
+ * @param {{allowAll:boolean, scripts:Set<string>, denied:boolean}} acc
15098
+ * accumulator to fold into
15099
+ * @param {object} [ctx] matching context passed to {@link normalizeAllowEntry}
14845
15100
  * @returns {(string|undefined)} the first matched key, if any
14846
15101
  */
14847
- function foldAllowScripts(keys, allowScripts, acc) {
15102
+ function foldAllowScripts(depInfo, allowScripts, acc, ctx) {
15103
+ const entries = indexAllowScripts(allowScripts).get(depInfo.name);
15104
+ if (!entries) return;
15105
+ const depItem = depInfo[DEP_ITEM];
15106
+ const requestedSpec = depItem && depItem.semver || depInfo[SEMVER];
14848
15107
  let matchedKey;
14849
- for (const key of keys) {
14850
- const value = allowScripts && allowScripts[key];
14851
- if (value !== void 0) {
14852
- if (!matchedKey) matchedKey = key;
14853
- normalizeAllowEntry(value, acc);
14854
- }
15108
+ for (const entry of entries) if (entry.spec === void 0 || entry.spec === requestedSpec || entry.range !== void 0 && Boolean(depInfo.version) && satisfies(depInfo.version, entry.range)) {
15109
+ if (!matchedKey) matchedKey = entry.key;
15110
+ normalizeAllowEntry(entry.value, acc, ctx);
14855
15111
  }
14856
15112
  return matchedKey;
14857
15113
  }
@@ -14870,42 +15126,104 @@ function isTopLevelDep(depInfo) {
14870
15126
  * Evaluate the lifecycle-script policy for a resolved package.
14871
15127
  *
14872
15128
  * @param {object} depInfo resolved package data
14873
- * @param {object} allowScripts the project's package.json `fyn.allowScripts` map
15129
+ * @param {object} allowScripts the effective `fyn.allowScripts` map
14874
15130
  * @param {object} [options] additional policy options
14875
15131
  * @param {(boolean|string|string[])} [options.allowTopLevel] the project's
14876
- * `fyn.allowTopLevelScripts` config. When truthy, non-registry packages that
14877
- * are declared directly in the top-level package.json are allowed to run the
14878
- * given lifecycle scripts (`true`/`"*"` = all, or a list of script names).
14879
- * @returns {{trusted:boolean, urlType:(string|undefined), allowAll:boolean,
15132
+ * `fyn.allowTopLevelScripts` config. When truthy, packages that are declared
15133
+ * directly in the top-level package.json are allowed to run the given
15134
+ * lifecycle scripts (`true`/`"*"` = all, or a list of script names).
15135
+ * @param {string} [options.mode] the `fyn.scriptPolicy` mode in effect
15136
+ * @param {boolean} [options.reviewLocalPackages] when true, workspace-local
15137
+ * packages lose their exemption and need an allowlist entry like any other
15138
+ * @returns {{trusted:boolean, denied:boolean, urlType:(string|undefined),
15139
+ * local:boolean, mode:string, reason:string, allowAll:boolean,
14880
15140
  * allowed:Set<string>, key:(string|undefined), topLevel:boolean}} the
14881
15141
  * resolved script policy
14882
15142
  */
14883
15143
  function evaluateScriptPolicy(depInfo, allowScripts, options = {}) {
15144
+ const { allowTopLevel, reviewLocalPackages = false, denyScripts } = options;
15145
+ const mode = normalizeScriptPolicy(options.mode);
14884
15146
  const urlType = getUrlType(depInfo);
14885
15147
  const keys = makeAllowKeys(depInfo);
14886
15148
  const topLevel = isTopLevelDep(depInfo);
14887
- if (!urlType || TRUSTED_URL_TYPES.has(urlType)) return {
14888
- trusted: true,
15149
+ const local = isLocalSource(depInfo);
15150
+ const base = {
14889
15151
  urlType,
15152
+ local,
15153
+ mode,
15154
+ topLevel,
15155
+ key: keys[0]
15156
+ };
15157
+ const nothing = {
15158
+ trusted: false,
15159
+ allowAll: false,
15160
+ allowed: /* @__PURE__ */ new Set(),
15161
+ deniedScripts: /* @__PURE__ */ new Set()
15162
+ };
15163
+ const deny = foldDenyScripts(depInfo, denyScripts);
15164
+ if (mode === "off") return {
15165
+ ...base,
15166
+ ...nothing,
15167
+ denied: true,
15168
+ reason: "off"
15169
+ };
15170
+ if (deny.denyAll) return {
15171
+ ...base,
15172
+ ...nothing,
15173
+ key: deny.key || keys[0],
15174
+ denied: true,
15175
+ reason: "denied"
15176
+ };
15177
+ const acc = makeAcc();
15178
+ const key = foldAllowScripts(depInfo, allowScripts, acc, { version: depInfo.version }) || keys[0];
15179
+ if (acc.denied || acc.denyAll) return {
15180
+ ...base,
15181
+ ...nothing,
15182
+ key,
15183
+ denied: true,
15184
+ reason: "denied"
15185
+ };
15186
+ const denied = /* @__PURE__ */ new Set([...deny.scripts, ...acc.deny]);
15187
+ if (mode === "all") return {
15188
+ ...base,
15189
+ key,
15190
+ trusted: true,
15191
+ denied: false,
14890
15192
  allowAll: true,
14891
15193
  allowed: /* @__PURE__ */ new Set(),
14892
- key: keys[0],
14893
- topLevel
15194
+ deniedScripts: denied,
15195
+ reason: "all"
14894
15196
  };
14895
- const acc = {
14896
- allowAll: false,
14897
- scripts: /* @__PURE__ */ new Set()
15197
+ if (local && !urlType && !reviewLocalPackages) return {
15198
+ ...base,
15199
+ key,
15200
+ trusted: true,
15201
+ denied: false,
15202
+ allowAll: true,
15203
+ allowed: /* @__PURE__ */ new Set(),
15204
+ deniedScripts: denied,
15205
+ reason: "local"
15206
+ };
15207
+ if (mode === "source" && (!urlType || TRUSTED_URL_TYPES.has(urlType))) return {
15208
+ ...base,
15209
+ key,
15210
+ trusted: true,
15211
+ denied: false,
15212
+ allowAll: true,
15213
+ allowed: /* @__PURE__ */ new Set(),
15214
+ deniedScripts: denied,
15215
+ reason: "registry"
14898
15216
  };
14899
- const matchedKey = foldAllowScripts(keys, allowScripts, acc);
14900
- const { allowTopLevel } = options;
14901
- if (topLevel && allowTopLevel !== void 0 && allowTopLevel !== false) normalizeAllowEntry(allowTopLevel, acc);
15217
+ if (mode === "source" && topLevel && allowTopLevel !== void 0 && allowTopLevel !== false) normalizeAllowEntry(allowTopLevel, acc, { version: depInfo.version });
14902
15218
  return {
15219
+ ...base,
14903
15220
  trusted: false,
14904
- urlType,
15221
+ denied: acc.denied,
14905
15222
  allowAll: acc.allowAll,
14906
15223
  allowed: acc.scripts,
14907
- key: matchedKey || keys[0],
14908
- topLevel
15224
+ deniedScripts: /* @__PURE__ */ new Set([...denied, ...acc.deny]),
15225
+ key,
15226
+ reason: mode === "review" ? "review" : "untrusted-source"
14909
15227
  };
14910
15228
  }
14911
15229
  /**
@@ -14914,8 +15232,11 @@ function evaluateScriptPolicy(depInfo, allowScripts, options = {}) {
14914
15232
  * @returns {boolean} whether the script is allowed to run
14915
15233
  */
14916
15234
  function isScriptAllowed(policy, scriptName) {
15235
+ if (policy.denied) return false;
15236
+ const name = String(scriptName).toLowerCase();
15237
+ if (policy.deniedScripts && policy.deniedScripts.has(name)) return false;
14917
15238
  if (policy.trusted || policy.allowAll) return true;
14918
- return policy.allowed.has(String(scriptName).toLowerCase());
15239
+ return policy.allowed.has(name);
14919
15240
  }
14920
15241
 
14921
15242
  //#endregion
@@ -14995,15 +15316,38 @@ function getPackageRawInfo(obj) {
14995
15316
  //#region lib/pkg-dep-resolver.ts
14996
15317
  const simpleSemverCompare = simpleCompare;
14997
15318
  const { checkPkgOsCpu, relativePath, unSlashNpmScope } = fyntil;
15319
+ /**
15320
+ * Decide whether a lock entry that records no package.json still needs a real meta fetch.
15321
+ *
15322
+ * `_missingJson` says the lock never captured the package's json, so its dependencies are
15323
+ * unknown rather than absent. Normally that has to be fetched for real before the package can be
15324
+ * resolved into the tree.
15325
+ *
15326
+ * The exception is a package this platform cannot use. An optional dep skipped on os/cpu is
15327
+ * recorded from its registry meta alone (pkg-dep-resolver synthesizes an os/cpu-only json for
15328
+ * it), so every such entry is `_missingJson` by construction - and on a machine that will never
15329
+ * install it, its dependencies do not matter. Fetching them anyway would cost a packument for
15330
+ * every platform variant in the tree on every install, which is the whole reason those entries
15331
+ * are in the lock (FPM-63, FPM-93).
15332
+ *
15333
+ * @param metaJson locked version metadata
15334
+ * @returns true when the real meta has to be fetched before this item can resolve
15335
+ */
15336
+ function lockedMetaNeedsFetch(metaJson) {
15337
+ if (!metaJson || !metaJson._missingJson) return false;
15338
+ return checkPkgOsCpu(metaJson) === true;
15339
+ }
14998
15340
  const failMetaMsg = (name) => `Unable to retrieve meta for package ${name} - If you've updated its version recently, try to run fyn with '--refresh-meta' again`;
14999
15341
  var PkgDepResolver = class {
15000
15342
  _options;
15001
15343
  _fyn;
15002
15344
  _pkgSrcMgr;
15345
+ /** read by `Fyn` when it starts the dist fetcher, so not private (FJM-154) */
15003
15346
  _data;
15004
15347
  _promiseQ;
15005
15348
  _defer;
15006
15349
  _optResolver;
15350
+ /** `Fyn.lockOnly` - the mode string "lock-only", or false; only read for truth */
15007
15351
  _lockOnly;
15008
15352
  _depthResolving;
15009
15353
  _localsByDepth;
@@ -15093,6 +15437,11 @@ var PkgDepResolver = class {
15093
15437
  this._defer.resolve();
15094
15438
  }
15095
15439
  }
15440
+ /**
15441
+ * @param depInfo where the peer resolutions are stashed - anything carrying a `res` bag.
15442
+ * The installer passes the whole `DepData` for the app's own peer deps ("your app"),
15443
+ * `resolvePeerDep` passes one package's info (FJM-154).
15444
+ */
15096
15445
  resolvePkgPeerDep(json, pkgId, depInfo) {
15097
15446
  const peerDepMeta = json.peerDependenciesMeta || {};
15098
15447
  import_lodash_min.default.each(json.peerDependencies || json.peerDepenencies, (semver, name) => {
@@ -15414,7 +15763,8 @@ var PkgDepResolver = class {
15414
15763
  findVersionFromDistTag(meta, semver) {
15415
15764
  if (import_semver.default.validRange(semver) === null) {
15416
15765
  const lockRsv = meta[LOCK_RSEMVERS];
15417
- if (lockRsv && lockRsv[semver]) return lockRsv[semver];
15766
+ const locked = lockRsv && lockRsv[semver];
15767
+ if (locked) return Array.isArray(locked) ? locked[0] : locked;
15418
15768
  const dtags = meta["dist-tags"];
15419
15769
  if (dtags && dtags.hasOwnProperty(semver)) return dtags[semver];
15420
15770
  }
@@ -15431,7 +15781,6 @@ var PkgDepResolver = class {
15431
15781
  if (!item.localType) item.localType = localFromMeta;
15432
15782
  if (this._fyn.enforceRegistryDeps && item.parent.depth >= 1) this._enforceRegistryDep(item, item.parent);
15433
15783
  }
15434
- const OPT_FAILED_PLATFORM = 3;
15435
15784
  const platformCheck = () => {
15436
15785
  const sysCheck = checkPkgOsCpu(metaJson);
15437
15786
  if (sysCheck !== true) return `package ${log_format_default.pkgId(item)} platform check failed: ${sysCheck}`;
@@ -15441,7 +15790,7 @@ var PkgDepResolver = class {
15441
15790
  if (item.dsrc && item.dsrc.includes("opt") && !item.optChecked) {
15442
15791
  if (sysCheck !== true) {
15443
15792
  logger$1.verbose(`optional dependencies ${sysCheck}`);
15444
- if (!item.optFailed) item.optFailed = OPT_FAILED_PLATFORM;
15793
+ if (!item.optFailed) item.optFailed = 3;
15445
15794
  item.optChecked = true;
15446
15795
  } else {
15447
15796
  logger$1.verbose("adding package to opt check:", item.name, item.semver, item.resolved);
@@ -15456,7 +15805,7 @@ var PkgDepResolver = class {
15456
15805
  logger$1.error(sysCheck);
15457
15806
  throw new Error(sysCheck);
15458
15807
  }
15459
- const pkgsData = this._data.getPkgsData(item.optFailed);
15808
+ const pkgsData = this._data.getPkgsData(Boolean(item.optFailed));
15460
15809
  let pkgV;
15461
15810
  let kpkg = pkgsData[item.name];
15462
15811
  if (kpkg) {
@@ -15501,10 +15850,17 @@ var PkgDepResolver = class {
15501
15850
  if (metaJson.hasPI || scripts.preinstall || scripts.preInstall) pkgV.hasPI = 1;
15502
15851
  if (metaJson.hasI || scripts.install || scripts.postinstall || scripts.postInstall) pkgV.hasI = 1;
15503
15852
  }
15504
- if (item.optFailed === OPT_FAILED_PLATFORM && !pkgV.json && (metaJson.os || metaJson.cpu)) pkgV.json = {
15505
- os: metaJson.os,
15506
- cpu: metaJson.cpu
15507
- };
15853
+ if (item.optFailed === 3 && !pkgV.json && (metaJson.os || metaJson.cpu)) pkgV.json = import_lodash_min.default.pick(metaJson, [
15854
+ "os",
15855
+ "cpu",
15856
+ "dependencies",
15857
+ "optionalDependencies",
15858
+ "peerDependencies",
15859
+ "bundleDependencies",
15860
+ "bundledDependencies",
15861
+ "_hasShrinkwrap",
15862
+ "_missingJson"
15863
+ ]);
15508
15864
  if (localFromMeta) {
15509
15865
  pkgV.local = item.localType;
15510
15866
  item.fullPath = pkgV.dir = pkgV.dist.fullPath;
@@ -15530,8 +15886,8 @@ var PkgDepResolver = class {
15530
15886
  }
15531
15887
  }
15532
15888
  }
15889
+ if (metaJson.deprecated) pkgV.deprecated = metaJson.deprecated;
15533
15890
  if (!optFailed) {
15534
- if (metaJson.deprecated) pkgV.deprecated = metaJson.deprecated;
15535
15891
  let deepRes = false;
15536
15892
  if (firstSeenVersion || (deepRes = this._shouldDeepResolve(pkgV))) {
15537
15893
  const pkgDepth = this._depthResolving[item.depth][item.name];
@@ -15592,7 +15948,7 @@ var PkgDepResolver = class {
15592
15948
  const getKnownSemver = () => {
15593
15949
  const find = (rsv) => {
15594
15950
  let x = rsv && rsv[item.semver];
15595
- if (!x) return x;
15951
+ if (!x) return void 0;
15596
15952
  if (Array.isArray(x)) x = x[0];
15597
15953
  if (noLocal && isLocal(x)) return false;
15598
15954
  return x;
@@ -15860,13 +16216,12 @@ ${item.depPath.join(" > ")}`);
15860
16216
  this._applyOverrides(item);
15861
16217
  this._replaceWithResolutionsData(item);
15862
16218
  return (!item.semverPath || this._fyn.preferLock ? tryLock().then((r) => r || item.semverPath && tryLocal() || false) : tryLocal().then((r) => r || tryLock())).then((r) => {
15863
- if (r && !import_lodash_min.default.get(r, [
16219
+ if (r && !lockedMetaNeedsFetch(import_lodash_min.default.get(r, [
15864
16220
  "meta",
15865
16221
  "versions",
15866
- r.resolved,
15867
- "_missingJson"
15868
- ])) return r;
15869
- if (this._lockOnly || item.localType) return;
16222
+ r.resolved
16223
+ ]))) return r;
16224
+ if (this._lockOnly || item.localType) return r || void 0;
15870
16225
  return this._pkgSrcMgr.fetchMeta(item).then((meta) => {
15871
16226
  if (!meta) throw new Error(failMetaMsg(item.name));
15872
16227
  const updated = this._fyn.depLocker.update(item, meta);
@@ -15947,7 +16302,7 @@ var Qt = Symbol("paused");
15947
16302
  var Bt = Symbol("resume");
15948
16303
  var b = Symbol("buffer");
15949
16304
  var N = Symbol("pipes");
15950
- var _$22 = Symbol("bufferLength");
16305
+ var _$23 = Symbol("bufferLength");
15951
16306
  var bi = Symbol("bufferPush");
15952
16307
  var Ie = Symbol("bufferShift");
15953
16308
  var L = Symbol("objectMode");
@@ -16007,7 +16362,7 @@ var A = class extends EventEmitter$2 {
16007
16362
  [De] = !1;
16008
16363
  [Ne] = !1;
16009
16364
  [qt] = null;
16010
- [_$22] = 0;
16365
+ [_$23] = 0;
16011
16366
  [S] = !1;
16012
16367
  [Jt];
16013
16368
  [Ce] = !1;
@@ -16023,7 +16378,7 @@ var A = class extends EventEmitter$2 {
16023
16378
  i && (this[Jt] = i, i.aborted ? this[xi]() : i.addEventListener("abort", () => this[xi]()));
16024
16379
  }
16025
16380
  get bufferLength() {
16026
- return this[_$22];
16381
+ return this[_$23];
16027
16382
  }
16028
16383
  get encoding() {
16029
16384
  return this[z];
@@ -16064,12 +16419,12 @@ var A = class extends EventEmitter$2 {
16064
16419
  else if (Vr(t)) t = Buffer.from(t);
16065
16420
  else if (typeof t != "string") throw new Error("Non-contiguous data written to non-objectMode stream");
16066
16421
  }
16067
- return this[L] ? (this[g$1] && this[_$22] !== 0 && this[Ae](!0), this[g$1] ? this.emit("data", t) : this[bi](t), this[_$22] !== 0 && this.emit("readable"), i && r(i), this[g$1]) : t.length ? (typeof t == "string" && !(e === this[z] && !this[Mt]?.lastNeed) && (t = Buffer.from(t, e)), Buffer.isBuffer(t) && this[z] && (t = this[Mt].write(t)), this[g$1] && this[_$22] !== 0 && this[Ae](!0), this[g$1] ? this.emit("data", t) : this[bi](t), this[_$22] !== 0 && this.emit("readable"), i && r(i), this[g$1]) : (this[_$22] !== 0 && this.emit("readable"), i && r(i), this[g$1]);
16422
+ return this[L] ? (this[g$1] && this[_$23] !== 0 && this[Ae](!0), this[g$1] ? this.emit("data", t) : this[bi](t), this[_$23] !== 0 && this.emit("readable"), i && r(i), this[g$1]) : t.length ? (typeof t == "string" && !(e === this[z] && !this[Mt]?.lastNeed) && (t = Buffer.from(t, e)), Buffer.isBuffer(t) && this[z] && (t = this[Mt].write(t)), this[g$1] && this[_$23] !== 0 && this[Ae](!0), this[g$1] ? this.emit("data", t) : this[bi](t), this[_$23] !== 0 && this.emit("readable"), i && r(i), this[g$1]) : (this[_$23] !== 0 && this.emit("readable"), i && r(i), this[g$1]);
16068
16423
  }
16069
16424
  read(t) {
16070
16425
  if (this[S]) return null;
16071
- if (this[C] = !1, this[_$22] === 0 || t === 0 || t && t > this[_$22]) return this[J](), null;
16072
- this[L] && (t = null), this[b].length > 1 && !this[L] && (this[b] = [this[z] ? this[b].join("") : Buffer.concat(this[b], this[_$22])]);
16426
+ if (this[C] = !1, this[_$23] === 0 || t === 0 || t && t > this[_$23]) return this[J](), null;
16427
+ this[L] && (t = null), this[b].length > 1 && !this[L] && (this[b] = [this[z] ? this[b].join("") : Buffer.concat(this[b], this[_$23])]);
16073
16428
  let e = this[Ns](t || null, this[b][0]);
16074
16429
  return this[J](), e;
16075
16430
  }
@@ -16077,7 +16432,7 @@ var A = class extends EventEmitter$2 {
16077
16432
  if (this[L]) this[Ie]();
16078
16433
  else {
16079
16434
  let i = e;
16080
- t === i.length || t === null ? this[Ie]() : typeof i == "string" ? (this[b][0] = i.slice(t), e = i.slice(0, t), this[_$22] -= t) : (this[b][0] = i.subarray(t), e = i.subarray(0, t), this[_$22] -= t);
16435
+ t === i.length || t === null ? this[Ie]() : typeof i == "string" ? (this[b][0] = i.slice(t), e = i.slice(0, t), this[_$23] -= t) : (this[b][0] = i.subarray(t), e = i.subarray(0, t), this[_$23] -= t);
16081
16436
  }
16082
16437
  return this.emit("data", e), !this[b].length && !this[Q] && this.emit("drain"), e;
16083
16438
  }
@@ -16103,10 +16458,10 @@ var A = class extends EventEmitter$2 {
16103
16458
  return this[Qt];
16104
16459
  }
16105
16460
  [bi](t) {
16106
- this[L] ? this[_$22] += 1 : this[_$22] += t.length, this[b].push(t);
16461
+ this[L] ? this[_$23] += 1 : this[_$23] += t.length, this[b].push(t);
16107
16462
  }
16108
16463
  [Ie]() {
16109
- return this[L] ? this[_$22] -= 1 : this[_$22] -= this[b][0].length, this[b].shift();
16464
+ return this[L] ? this[_$23] -= 1 : this[_$23] -= this[b][0].length, this[b].shift();
16110
16465
  }
16111
16466
  [Ae](t = !1) {
16112
16467
  do ;
@@ -16132,7 +16487,7 @@ while (this[As](this[Ie]()) && this[b].length);
16132
16487
  on(t, e) {
16133
16488
  let i = super.on(t, e);
16134
16489
  if (t === "data") this[C] = !1, this[Rt]++, !this[N].length && !this[g$1] && this[Bt]();
16135
- else if (t === "readable" && this[_$22] !== 0) super.emit("readable");
16490
+ else if (t === "readable" && this[_$23] !== 0) super.emit("readable");
16136
16491
  else if (Kr(t) && this[nt]) super.emit(t), this.removeAllListeners(t);
16137
16492
  else if (t === "error" && this[qt]) {
16138
16493
  let r = e;
@@ -16283,7 +16638,7 @@ while (this[As](this[Ie]()) && this[b].length);
16283
16638
  }
16284
16639
  destroy(t) {
16285
16640
  if (this[S]) return t ? this.emit("error", t) : this.emit(S), this;
16286
- this[S] = !0, this[C] = !0, this[b].length = 0, this[_$22] = 0;
16641
+ this[S] = !0, this[C] = !0, this[b].length = 0, this[_$23] = 0;
16287
16642
  let e = this;
16288
16643
  return typeof e.close == "function" && !this[Ne] && e.close(), t ? this.emit("error", t) : this.emit(S), this;
16289
16644
  }
@@ -18102,7 +18457,7 @@ var Ee = Symbol("processing");
18102
18457
  var ai = Symbol("processJob");
18103
18458
  var G = Symbol("jobs");
18104
18459
  var ls = Symbol("jobDone");
18105
- var ci$2 = Symbol("addFSEntry");
18460
+ var ci$3 = Symbol("addFSEntry");
18106
18461
  var or = Symbol("addTarEntry");
18107
18462
  var ds$1 = Symbol("stat");
18108
18463
  var us = Symbol("readdir");
@@ -18161,7 +18516,7 @@ var wt = class extends A {
18161
18516
  }
18162
18517
  write(t) {
18163
18518
  if (this[me]) throw new Error("write after end");
18164
- return typeof t == "string" ? this[ci$2](t) : this[or](t), this.flowing;
18519
+ return typeof t == "string" ? this[ci$3](t) : this[or](t), this.flowing;
18165
18520
  }
18166
18521
  [or](t) {
18167
18522
  let e = f(Path.resolve(this.cwd, t.path));
@@ -18172,7 +18527,7 @@ var wt = class extends A {
18172
18527
  }
18173
18528
  this[Ft]();
18174
18529
  }
18175
- [ci$2](t) {
18530
+ [ci$3](t) {
18176
18531
  let e = f(Path.resolve(this.cwd, t));
18177
18532
  this[W].push(new pi(t, e)), this[Ft]();
18178
18533
  }
@@ -18281,7 +18636,7 @@ var wt = class extends A {
18281
18636
  [di](t) {
18282
18637
  t.piped = !0, t.readdir && t.readdir.forEach((r) => {
18283
18638
  let n = t.path, o = n === "./" ? "" : n.replace(/\/*$/, "/");
18284
- this[ci$2](o + r);
18639
+ this[ci$3](o + r);
18285
18640
  });
18286
18641
  let e = t.entry, i = this.zip;
18287
18642
  if (!e) throw new Error("cannot pipe without source");
@@ -18316,7 +18671,7 @@ var kt = class extends wt {
18316
18671
  let e = t.entry, i = this.zip;
18317
18672
  if (t.readdir && t.readdir.forEach((r) => {
18318
18673
  let n = t.path, o = n === "./" ? "" : n.replace(/\/*$/, "/");
18319
- this[ci$2](o + r);
18674
+ this[ci$3](o + r);
18320
18675
  }), !e) throw new Error("Cannot pipe without source");
18321
18676
  i ? e.on("data", (r) => {
18322
18677
  i.write(r);
@@ -27372,7 +27727,7 @@ var require_scan$1 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
27372
27727
 
27373
27728
  //#endregion
27374
27729
  //#region node_modules/spdx-expression-parse/parse.js
27375
- var require_parse$6 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
27730
+ var require_parse$5 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
27376
27731
  module.exports = function(tokens) {
27377
27732
  var index = 0;
27378
27733
  function hasMore() {
@@ -27464,7 +27819,7 @@ var require_parse$6 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
27464
27819
  //#region node_modules/spdx-expression-parse/index.js
27465
27820
  var require_spdx_expression_parse = /* @__PURE__ */ __commonJSMin(((exports, module) => {
27466
27821
  var scan = require_scan$1();
27467
- var parse = require_parse$6();
27822
+ var parse = require_parse$5();
27468
27823
  module.exports = function(source) {
27469
27824
  return parse(scan(source));
27470
27825
  };
@@ -27659,7 +28014,7 @@ var require_normalize_data = /* @__PURE__ */ __commonJSMin(((exports, module) =>
27659
28014
  //#region node_modules/isexe/dist/commonjs/index.min.js
27660
28015
  var require_index_min$1 = /* @__PURE__ */ __commonJSMin(((exports) => {
27661
28016
  var a = (t, e) => () => (e || t((e = { exports: {} }).exports, e), e.exports);
27662
- var _$19 = a((i) => {
28017
+ var _$20 = a((i) => {
27663
28018
  "use strict";
27664
28019
  Object.defineProperty(i, "__esModule", { value: !0 });
27665
28020
  i.sync = i.isexe = void 0;
@@ -27772,7 +28127,7 @@ var require_index_min$1 = /* @__PURE__ */ __commonJSMin(((exports) => {
27772
28127
  };
27773
28128
  Object.defineProperty(exports, "__esModule", { value: !0 });
27774
28129
  exports.sync = exports.isexe = exports.posix = exports.win32 = void 0;
27775
- var E = w(_$19());
28130
+ var E = w(_$20());
27776
28131
  exports.posix = E;
27777
28132
  var O = w(g());
27778
28133
  exports.win32 = O;
@@ -28188,7 +28543,7 @@ var require_lib$31 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
28188
28543
 
28189
28544
  //#endregion
28190
28545
  //#region node_modules/@npmcli/git/lib/errors.js
28191
- var require_errors$5 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
28546
+ var require_errors$4 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
28192
28547
  const maxRetry = 3;
28193
28548
  var GitError = class extends Error {
28194
28549
  shouldRetry() {
@@ -28223,7 +28578,7 @@ var require_errors$5 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
28223
28578
  //#endregion
28224
28579
  //#region node_modules/@npmcli/git/lib/make-error.js
28225
28580
  var require_make_error = /* @__PURE__ */ __commonJSMin(((exports, module) => {
28226
- const { GitConnectionError, GitPathspecError, GitUnknownError } = require_errors$5();
28581
+ const { GitConnectionError, GitPathspecError, GitUnknownError } = require_errors$4();
28227
28582
  const connectionErrorRe = new RegExp([
28228
28583
  "remote error: Internal Server Error",
28229
28584
  "The remote end hung up unexpectedly",
@@ -29478,7 +29833,7 @@ var require_clone = /* @__PURE__ */ __commonJSMin(((exports, module) => {
29478
29833
  "bitbucket.com",
29479
29834
  "bitbucket.org"
29480
29835
  ]);
29481
- const { parse: parse$2 } = __require("url");
29836
+ const { parse: parse$3 } = __require("url");
29482
29837
  const path$44 = __require("path");
29483
29838
  const getRevs = require_revs();
29484
29839
  const spawn = require_spawn();
@@ -29488,7 +29843,7 @@ var require_clone = /* @__PURE__ */ __commonJSMin(((exports, module) => {
29488
29843
  module.exports = (repo, ref = "HEAD", target = null, opts = {}) => getRevs(repo, opts).then((revs) => clone(repo, revs, ref, resolveRef(revs, ref, opts), target || defaultTarget(repo, opts.cwd), opts));
29489
29844
  const maybeShallow = (repo, opts) => {
29490
29845
  if (opts.gitShallow === false || opts.gitShallow) return opts.gitShallow;
29491
- return shallowHosts.has(parse$2(repo).host);
29846
+ return shallowHosts.has(parse$3(repo).host);
29492
29847
  };
29493
29848
  const defaultTarget = (repo, cwd = process.cwd()) => path$44.resolve(cwd, path$44.basename(repo.replace(/[/\\]?\.git$/, "")));
29494
29849
  const clone = (repo, revs, ref, revDoc, target, opts) => {
@@ -29633,7 +29988,7 @@ var require_lib$26 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
29633
29988
  is: require_is(),
29634
29989
  find: require_find$1(),
29635
29990
  isClean: require_is_clean(),
29636
- errors: require_errors$5()
29991
+ errors: require_errors$4()
29637
29992
  };
29638
29993
  }));
29639
29994
 
@@ -32547,7 +32902,7 @@ var require_node$3 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
32547
32902
 
32548
32903
  //#endregion
32549
32904
  //#region node_modules/@npmcli/fs/lib/cp/errors.js
32550
- var require_errors$4 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
32905
+ var require_errors$3 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
32551
32906
  const { inspect } = __require("util");
32552
32907
  var SystemError = class {
32553
32908
  constructor(code, prefix, context) {
@@ -32655,10 +33010,10 @@ var require_errors$4 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
32655
33010
  //#endregion
32656
33011
  //#region node_modules/@npmcli/fs/lib/cp/polyfill.js
32657
33012
  var require_polyfill = /* @__PURE__ */ __commonJSMin(((exports, module) => {
32658
- const { ERR_FS_CP_DIR_TO_NON_DIR, ERR_FS_CP_EEXIST, ERR_FS_CP_EINVAL, ERR_FS_CP_FIFO_PIPE, ERR_FS_CP_NON_DIR_TO_DIR, ERR_FS_CP_SOCKET, ERR_FS_CP_SYMLINK_TO_SUBDIRECTORY, ERR_FS_CP_UNKNOWN, ERR_FS_EISDIR, ERR_INVALID_ARG_TYPE } = require_errors$4();
33013
+ const { ERR_FS_CP_DIR_TO_NON_DIR, ERR_FS_CP_EEXIST, ERR_FS_CP_EINVAL, ERR_FS_CP_FIFO_PIPE, ERR_FS_CP_NON_DIR_TO_DIR, ERR_FS_CP_SOCKET, ERR_FS_CP_SYMLINK_TO_SUBDIRECTORY, ERR_FS_CP_UNKNOWN, ERR_FS_EISDIR, ERR_INVALID_ARG_TYPE } = require_errors$3();
32659
33014
  const { constants: { errno: { EEXIST, EISDIR, EINVAL, ENOTDIR } } } = __require("os");
32660
33015
  const { chmod: chmod$3, copyFile: copyFile$1, lstat: lstat$5, mkdir: mkdir$6, readdir: readdir$5, readlink: readlink$6, stat: stat$6, symlink: symlink$2, unlink: unlink$1, utimes } = __require("fs/promises");
32661
- const { dirname: dirname$24, isAbsolute: isAbsolute$3, join: join$8, parse: parse$1, resolve: resolve$27, sep: sep$8, toNamespacedPath } = __require("path");
33016
+ const { dirname: dirname$24, isAbsolute: isAbsolute$3, join: join$8, parse: parse$2, resolve: resolve$27, sep: sep$8, toNamespacedPath } = __require("path");
32662
33017
  const { fileURLToPath: fileURLToPath$2 } = __require("url");
32663
33018
  const defaultOptions = {
32664
33019
  dereference: false,
@@ -32747,7 +33102,7 @@ var require_polyfill = /* @__PURE__ */ __commonJSMin(((exports, module) => {
32747
33102
  async function checkParentPaths(src, srcStat, dest) {
32748
33103
  const srcParent = resolve$27(dirname$24(src));
32749
33104
  const destParent = resolve$27(dirname$24(dest));
32750
- if (destParent === srcParent || destParent === parse$1(destParent).root) return;
33105
+ if (destParent === srcParent || destParent === parse$2(destParent).root) return;
32751
33106
  let destStat;
32752
33107
  try {
32753
33108
  destStat = await stat$6(destParent, { bigint: true });
@@ -39236,7 +39591,7 @@ var require_dir = /* @__PURE__ */ __commonJSMin(((exports, module) => {
39236
39591
 
39237
39592
  //#endregion
39238
39593
  //#region node_modules/npm-registry-fetch/lib/errors.js
39239
- var require_errors$3 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
39594
+ var require_errors$2 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
39240
39595
  const { URL: URL$8 } = __require("node:url");
39241
39596
  function packageName(href) {
39242
39597
  try {
@@ -39306,7 +39661,7 @@ var require_errors$3 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
39306
39661
 
39307
39662
  //#endregion
39308
39663
  //#region node_modules/minizlib/dist/commonjs/constants.js
39309
- var require_constants$7 = /* @__PURE__ */ __commonJSMin(((exports) => {
39664
+ var require_constants$6 = /* @__PURE__ */ __commonJSMin(((exports) => {
39310
39665
  var __importDefault = exports && exports.__importDefault || function(mod) {
39311
39666
  return mod && mod.__esModule ? mod : { "default": mod };
39312
39667
  };
@@ -39478,8 +39833,8 @@ var require_commonjs$4 = /* @__PURE__ */ __commonJSMin(((exports) => {
39478
39833
  const buffer_1$1 = __require("buffer");
39479
39834
  const minipass_1 = require_commonjs$5();
39480
39835
  const realZlib = __importStar(__require("zlib"));
39481
- const constants_js_1 = require_constants$7();
39482
- var constants_js_2 = require_constants$7();
39836
+ const constants_js_1 = require_constants$6();
39837
+ var constants_js_2 = require_constants$6();
39483
39838
  Object.defineProperty(exports, "constants", {
39484
39839
  enumerable: true,
39485
39840
  get: function() {
@@ -41187,7 +41542,7 @@ var require_lib$15 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
41187
41542
  //#endregion
41188
41543
  //#region node_modules/npm-registry-fetch/lib/check-response.js
41189
41544
  var require_check_response = /* @__PURE__ */ __commonJSMin(((exports, module) => {
41190
- const errors = require_errors$3();
41545
+ const errors = require_errors$2();
41191
41546
  const { Response } = require_lib$16();
41192
41547
  const defaultOpts = require_default_opts();
41193
41548
  const { log } = require_lib$39();
@@ -42862,7 +43217,7 @@ var require_policy$1 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
42862
43217
 
42863
43218
  //#endregion
42864
43219
  //#region node_modules/make-fetch-happen/lib/cache/errors.js
42865
- var require_errors$2 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
43220
+ var require_errors$1 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
42866
43221
  var NotCachedError = class extends Error {
42867
43222
  constructor(url) {
42868
43223
  super(`request to ${url} failed: cache mode is 'only-if-cached' but no cached response is available.`);
@@ -44688,7 +45043,7 @@ var require_smartbuffer = /* @__PURE__ */ __commonJSMin(((exports) => {
44688
45043
 
44689
45044
  //#endregion
44690
45045
  //#region node_modules/socks/build/common/constants.js
44691
- var require_constants$6 = /* @__PURE__ */ __commonJSMin(((exports) => {
45046
+ var require_constants$5 = /* @__PURE__ */ __commonJSMin(((exports) => {
44692
45047
  Object.defineProperty(exports, "__esModule", { value: true });
44693
45048
  exports.SOCKS5_NO_ACCEPTABLE_AUTH = exports.SOCKS5_CUSTOM_AUTH_END = exports.SOCKS5_CUSTOM_AUTH_START = exports.SOCKS_INCOMING_PACKET_SIZES = exports.SocksClientState = exports.Socks5Response = exports.Socks5HostType = exports.Socks5Auth = exports.Socks4Response = exports.SocksCommand = exports.ERRORS = exports.DEFAULT_TIMEOUT = void 0;
44694
45049
  const DEFAULT_TIMEOUT = 3e4;
@@ -44954,7 +45309,7 @@ var require_common$4 = /* @__PURE__ */ __commonJSMin(((exports) => {
44954
45309
 
44955
45310
  //#endregion
44956
45311
  //#region node_modules/ip-address/dist/v4/constants.js
44957
- var require_constants$5 = /* @__PURE__ */ __commonJSMin(((exports) => {
45312
+ var require_constants$4 = /* @__PURE__ */ __commonJSMin(((exports) => {
44958
45313
  Object.defineProperty(exports, "__esModule", { value: true });
44959
45314
  exports.SPECIAL_PURPOSE = exports.RE_SUBNET_STRING = exports.RE_ADDRESS = exports.GROUPS = exports.BITS = void 0;
44960
45315
  exports.BITS = 32;
@@ -45143,7 +45498,7 @@ var require_ipv4 = /* @__PURE__ */ __commonJSMin(((exports) => {
45143
45498
  Object.defineProperty(exports, "__esModule", { value: true });
45144
45499
  exports.Address4 = void 0;
45145
45500
  const common = __importStar(require_common$4());
45146
- const constants = __importStar(require_constants$5());
45501
+ const constants = __importStar(require_constants$4());
45147
45502
  const address_error_1 = require_address_error();
45148
45503
  const isCorrect4 = common.isCorrect(constants.BITS);
45149
45504
  /**
@@ -45651,7 +46006,7 @@ var require_ipv4 = /* @__PURE__ */ __commonJSMin(((exports) => {
45651
46006
 
45652
46007
  //#endregion
45653
46008
  //#region node_modules/ip-address/dist/v6/constants.js
45654
- var require_constants$4 = /* @__PURE__ */ __commonJSMin(((exports) => {
46009
+ var require_constants$3 = /* @__PURE__ */ __commonJSMin(((exports) => {
45655
46010
  Object.defineProperty(exports, "__esModule", { value: true });
45656
46011
  exports.SPECIAL_PURPOSE = exports.RE_URL_WITH_PORT = exports.RE_URL = exports.RE_ZONE_STRING = exports.RE_SUBNET_STRING = exports.RE_BAD_ADDRESS = exports.RE_BAD_CHARACTERS = exports.TYPES = exports.SCOPES = exports.GROUPS = exports.BITS = void 0;
45657
46012
  exports.BITS = 128;
@@ -45965,7 +46320,7 @@ var require_regular_expressions = /* @__PURE__ */ __commonJSMin(((exports) => {
45965
46320
  exports.padGroup = padGroup;
45966
46321
  exports.simpleRegularExpression = simpleRegularExpression;
45967
46322
  exports.possibleElisions = possibleElisions;
45968
- const v6 = __importStar(require_constants$4());
46323
+ const v6 = __importStar(require_constants$3());
45969
46324
  function groupPossibilities(possibilities) {
45970
46325
  return `(${possibilities.join("|")})`;
45971
46326
  }
@@ -46041,8 +46396,8 @@ var require_ipv6 = /* @__PURE__ */ __commonJSMin(((exports) => {
46041
46396
  Object.defineProperty(exports, "__esModule", { value: true });
46042
46397
  exports.Address6 = void 0;
46043
46398
  const common = __importStar(require_common$4());
46044
- const constants4 = __importStar(require_constants$5());
46045
- const constants6 = __importStar(require_constants$4());
46399
+ const constants4 = __importStar(require_constants$4());
46400
+ const constants6 = __importStar(require_constants$3());
46046
46401
  const helpers = __importStar(require_helpers$1());
46047
46402
  const ipv4_1 = require_ipv4();
46048
46403
  const regular_expressions_1 = require_regular_expressions();
@@ -47254,7 +47609,7 @@ var require_helpers = /* @__PURE__ */ __commonJSMin(((exports) => {
47254
47609
  Object.defineProperty(exports, "__esModule", { value: true });
47255
47610
  exports.ipToBuffer = exports.int32ToIpv4 = exports.ipv4ToInt32 = exports.validateSocksClientChainOptions = exports.validateSocksClientOptions = void 0;
47256
47611
  const util_1 = require_util$3();
47257
- const constants_1 = require_constants$6();
47612
+ const constants_1 = require_constants$5();
47258
47613
  const stream$1 = __require("stream");
47259
47614
  const ip_address_1 = require_ip_address();
47260
47615
  const net$2 = __require("net");
@@ -47421,7 +47776,7 @@ var require_socksclient = /* @__PURE__ */ __commonJSMin(((exports) => {
47421
47776
  const events_1$1 = __require("events");
47422
47777
  const net$1 = __require("net");
47423
47778
  const smart_buffer_1 = require_smartbuffer();
47424
- const constants_1 = require_constants$6();
47779
+ const constants_1 = require_constants$5();
47425
47780
  const helpers_1 = require_helpers();
47426
47781
  const receivebuffer_1 = require_receivebuffer();
47427
47782
  const util_1 = require_util$3();
@@ -48176,7 +48531,7 @@ var init_dist = __esmMin((() => {
48176
48531
 
48177
48532
  //#endregion
48178
48533
  //#region node_modules/@npmcli/agent/lib/errors.js
48179
- var require_errors$1 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
48534
+ var require_errors = /* @__PURE__ */ __commonJSMin(((exports, module) => {
48180
48535
  var InvalidProxyProtocolError = class extends Error {
48181
48536
  constructor(url) {
48182
48537
  super(`Invalid protocol \`${url.protocol}\` connecting to proxy \`${url.host}\``);
@@ -48236,7 +48591,7 @@ var require_proxy = /* @__PURE__ */ __commonJSMin(((exports, module) => {
48236
48591
  const { HttpsProxyAgent } = (init_dist$1(), __toCommonJS(dist_exports$1));
48237
48592
  const { SocksProxyAgent } = (init_dist(), __toCommonJS(dist_exports));
48238
48593
  const { LRUCache } = require_index_min$2();
48239
- const { InvalidProxyProtocolError } = require_errors$1();
48594
+ const { InvalidProxyProtocolError } = require_errors();
48240
48595
  const PROXY_CACHE = new LRUCache({ max: 20 });
48241
48596
  const SOCKS_PROTOCOLS = new Set(SocksProxyAgent.protocols);
48242
48597
  const PROXY_ENV_KEYS = /* @__PURE__ */ new Set([
@@ -48291,7 +48646,7 @@ var require_agents = /* @__PURE__ */ __commonJSMin(((exports, module) => {
48291
48646
  const timers = __require("timers/promises");
48292
48647
  const { normalizeOptions, cacheOptions } = require_options();
48293
48648
  const { getProxy, getProxyAgent, proxyCache } = require_proxy();
48294
- const Errors = require_errors$1();
48649
+ const Errors = require_errors();
48295
48650
  const { Agent: AgentBase } = (init_dist$4(), __toCommonJS(dist_exports$3));
48296
48651
  module.exports = class Agent extends AgentBase {
48297
48652
  #options;
@@ -48926,7 +49281,7 @@ var require_entry$4 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
48926
49281
  //#endregion
48927
49282
  //#region node_modules/make-fetch-happen/lib/cache/index.js
48928
49283
  var require_cache = /* @__PURE__ */ __commonJSMin(((exports, module) => {
48929
- const { NotCachedError } = require_errors$2();
49284
+ const { NotCachedError } = require_errors$1();
48930
49285
  const CacheEntry = require_entry$4();
48931
49286
  const remote = require_remote$1();
48932
49287
  const cacheFetch = async (request, options) => {
@@ -49564,7 +49919,7 @@ var require_json_stream = /* @__PURE__ */ __commonJSMin(((exports, module) => {
49564
49919
  //#endregion
49565
49920
  //#region node_modules/npm-registry-fetch/lib/index.js
49566
49921
  var require_lib$12 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
49567
- const { HttpErrorAuthOTP } = require_errors$3();
49922
+ const { HttpErrorAuthOTP } = require_errors$2();
49568
49923
  const checkResponse = require_check_response();
49569
49924
  const getAuth = require_auth$1();
49570
49925
  const fetch = require_lib$13();
@@ -51746,7 +52101,7 @@ var require_length = /* @__PURE__ */ __commonJSMin(((exports) => {
51746
52101
 
51747
52102
  //#endregion
51748
52103
  //#region node_modules/@sigstore/core/dist/asn1/parse.js
51749
- var require_parse$5 = /* @__PURE__ */ __commonJSMin(((exports) => {
52104
+ var require_parse$4 = /* @__PURE__ */ __commonJSMin(((exports) => {
51750
52105
  Object.defineProperty(exports, "__esModule", { value: true });
51751
52106
  exports.parseInteger = parseInteger;
51752
52107
  exports.parseStringASCII = parseStringASCII;
@@ -51901,7 +52256,7 @@ var require_obj = /* @__PURE__ */ __commonJSMin(((exports) => {
51901
52256
  const stream_1 = require_stream$5();
51902
52257
  const error_1 = require_error$10();
51903
52258
  const length_1 = require_length();
51904
- const parse_1 = require_parse$5();
52259
+ const parse_1 = require_parse$4();
51905
52260
  const tag_1 = require_tag$1();
51906
52261
  var ASN1Obj = class {
51907
52262
  tag;
@@ -68295,7 +68650,7 @@ var require_line_endings = /* @__PURE__ */ __commonJSMin(((exports) => {
68295
68650
 
68296
68651
  //#endregion
68297
68652
  //#region node_modules/diff/libcjs/patch/parse.js
68298
- var require_parse$4 = /* @__PURE__ */ __commonJSMin(((exports) => {
68653
+ var require_parse$3 = /* @__PURE__ */ __commonJSMin(((exports) => {
68299
68654
  Object.defineProperty(exports, "__esModule", { value: true });
68300
68655
  exports.parsePatch = parsePatch;
68301
68656
  /**
@@ -68411,7 +68766,7 @@ var require_apply = /* @__PURE__ */ __commonJSMin(((exports) => {
68411
68766
  exports.applyPatches = applyPatches;
68412
68767
  var string_js_1 = require_string$2();
68413
68768
  var line_endings_js_1 = require_line_endings();
68414
- var parse_js_1 = require_parse$4();
68769
+ var parse_js_1 = require_parse$3();
68415
68770
  var distance_iterator_js_1 = require_distance_iterator();
68416
68771
  /**
68417
68772
  * attempts to apply a unified diff patch.
@@ -69012,7 +69367,7 @@ var require_libcjs = /* @__PURE__ */ __commonJSMin(((exports) => {
69012
69367
  return apply_js_1.applyPatches;
69013
69368
  }
69014
69369
  });
69015
- var parse_js_1 = require_parse$4();
69370
+ var parse_js_1 = require_parse$3();
69016
69371
  Object.defineProperty(exports, "parsePatch", {
69017
69372
  enumerable: true,
69018
69373
  get: function() {
@@ -77598,7 +77953,7 @@ async function getCacheInfoWithRefreshTime(cache, key) {
77598
77953
  //#endregion
77599
77954
  //#region lib/util/inflight.ts
77600
77955
  var import_lib$2 = /* @__PURE__ */ __toESM(require_lib$10());
77601
- var inflight_default = Inflight;
77956
+ var inflight_default = InflightStore;
77602
77957
 
77603
77958
  //#endregion
77604
77959
  //#region node_modules/pkg-preper/dist/index.js
@@ -78543,7 +78898,7 @@ var require_expand = /* @__PURE__ */ __commonJSMin(((exports, module) => {
78543
78898
 
78544
78899
  //#endregion
78545
78900
  //#region node_modules/braces/lib/constants.js
78546
- var require_constants$3 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
78901
+ var require_constants$2 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
78547
78902
  module.exports = {
78548
78903
  MAX_LENGTH: 1e4,
78549
78904
  CHAR_0: "0",
@@ -78595,12 +78950,12 @@ var require_constants$3 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
78595
78950
 
78596
78951
  //#endregion
78597
78952
  //#region node_modules/braces/lib/parse.js
78598
- var require_parse$3 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
78953
+ var require_parse$2 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
78599
78954
  const stringify = require_stringify();
78600
78955
  /**
78601
78956
  * Constants
78602
78957
  */
78603
- const { MAX_LENGTH, CHAR_BACKSLASH, CHAR_BACKTICK, CHAR_COMMA, CHAR_DOT, CHAR_LEFT_PARENTHESES, CHAR_RIGHT_PARENTHESES, CHAR_LEFT_CURLY_BRACE, CHAR_RIGHT_CURLY_BRACE, CHAR_LEFT_SQUARE_BRACKET, CHAR_RIGHT_SQUARE_BRACKET, CHAR_DOUBLE_QUOTE, CHAR_SINGLE_QUOTE, CHAR_NO_BREAK_SPACE, CHAR_ZERO_WIDTH_NOBREAK_SPACE } = require_constants$3();
78958
+ const { MAX_LENGTH, CHAR_BACKSLASH, CHAR_BACKTICK, CHAR_COMMA, CHAR_DOT, CHAR_LEFT_PARENTHESES, CHAR_RIGHT_PARENTHESES, CHAR_LEFT_CURLY_BRACE, CHAR_RIGHT_CURLY_BRACE, CHAR_LEFT_SQUARE_BRACKET, CHAR_RIGHT_SQUARE_BRACKET, CHAR_DOUBLE_QUOTE, CHAR_SINGLE_QUOTE, CHAR_NO_BREAK_SPACE, CHAR_ZERO_WIDTH_NOBREAK_SPACE } = require_constants$2();
78604
78959
  /**
78605
78960
  * parse
78606
78961
  */
@@ -78887,7 +79242,7 @@ var require_braces = /* @__PURE__ */ __commonJSMin(((exports, module) => {
78887
79242
  const stringify = require_stringify();
78888
79243
  const compile = require_compile();
78889
79244
  const expand = require_expand();
78890
- const parse = require_parse$3();
79245
+ const parse = require_parse$2();
78891
79246
  /**
78892
79247
  * Expand the given pattern or create a regex-compatible string.
78893
79248
  *
@@ -79011,7 +79366,7 @@ var require_braces = /* @__PURE__ */ __commonJSMin(((exports, module) => {
79011
79366
 
79012
79367
  //#endregion
79013
79368
  //#region node_modules/picomatch/lib/constants.js
79014
- var require_constants$2 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
79369
+ var require_constants$1 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
79015
79370
  const path$28 = __require("path");
79016
79371
  const WIN_SLASH = "\\\\/";
79017
79372
  const WIN_NO_SLASH = `[^${WIN_SLASH}]`;
@@ -79188,7 +79543,7 @@ var require_constants$2 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
79188
79543
  var require_utils$3 = /* @__PURE__ */ __commonJSMin(((exports) => {
79189
79544
  const path$27 = __require("path");
79190
79545
  const win32 = process.platform === "win32";
79191
- const { REGEX_BACKSLASH, REGEX_REMOVE_BACKSLASH, REGEX_SPECIAL_CHARS, REGEX_SPECIAL_CHARS_GLOBAL } = require_constants$2();
79546
+ const { REGEX_BACKSLASH, REGEX_REMOVE_BACKSLASH, REGEX_SPECIAL_CHARS, REGEX_SPECIAL_CHARS_GLOBAL } = require_constants$1();
79192
79547
  exports.isObject = (val) => val !== null && typeof val === "object" && !Array.isArray(val);
79193
79548
  exports.hasRegexChars = (str) => REGEX_SPECIAL_CHARS.test(str);
79194
79549
  exports.isRegexChar = (str) => str.length === 1 && exports.hasRegexChars(str);
@@ -79233,7 +79588,7 @@ var require_utils$3 = /* @__PURE__ */ __commonJSMin(((exports) => {
79233
79588
  //#region node_modules/picomatch/lib/scan.js
79234
79589
  var require_scan = /* @__PURE__ */ __commonJSMin(((exports, module) => {
79235
79590
  const utils = require_utils$3();
79236
- const { CHAR_ASTERISK, CHAR_AT, CHAR_BACKWARD_SLASH, CHAR_COMMA, CHAR_DOT, CHAR_EXCLAMATION_MARK, CHAR_FORWARD_SLASH, CHAR_LEFT_CURLY_BRACE, CHAR_LEFT_PARENTHESES, CHAR_LEFT_SQUARE_BRACKET, CHAR_PLUS, CHAR_QUESTION_MARK, CHAR_RIGHT_CURLY_BRACE, CHAR_RIGHT_PARENTHESES, CHAR_RIGHT_SQUARE_BRACKET } = require_constants$2();
79591
+ const { CHAR_ASTERISK, CHAR_AT, CHAR_BACKWARD_SLASH, CHAR_COMMA, CHAR_DOT, CHAR_EXCLAMATION_MARK, CHAR_FORWARD_SLASH, CHAR_LEFT_CURLY_BRACE, CHAR_LEFT_PARENTHESES, CHAR_LEFT_SQUARE_BRACKET, CHAR_PLUS, CHAR_QUESTION_MARK, CHAR_RIGHT_CURLY_BRACE, CHAR_RIGHT_PARENTHESES, CHAR_RIGHT_SQUARE_BRACKET } = require_constants$1();
79237
79592
  const isPathSeparator = (code) => {
79238
79593
  return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH;
79239
79594
  };
@@ -79519,8 +79874,8 @@ var require_scan = /* @__PURE__ */ __commonJSMin(((exports, module) => {
79519
79874
 
79520
79875
  //#endregion
79521
79876
  //#region node_modules/picomatch/lib/parse.js
79522
- var require_parse$2 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
79523
- const constants = require_constants$2();
79877
+ var require_parse$1 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
79878
+ const constants = require_constants$1();
79524
79879
  const utils = require_utils$3();
79525
79880
  /**
79526
79881
  * Constants
@@ -80585,9 +80940,9 @@ var require_parse$2 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
80585
80940
  var require_picomatch$1 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
80586
80941
  const path$26 = __require("path");
80587
80942
  const scan = require_scan();
80588
- const parse = require_parse$2();
80943
+ const parse = require_parse$1();
80589
80944
  const utils = require_utils$3();
80590
- const constants = require_constants$2();
80945
+ const constants = require_constants$1();
80591
80946
  const isObject = (val) => val && typeof val === "object" && !Array.isArray(val);
80592
80947
  /**
80593
80948
  * Creates a matcher function from one or more glob patterns. The
@@ -81883,7 +82238,7 @@ var require_run_parallel = /* @__PURE__ */ __commonJSMin(((exports, module) => {
81883
82238
 
81884
82239
  //#endregion
81885
82240
  //#region node_modules/@nodelib/fs.scandir/out/constants.js
81886
- var require_constants$1 = /* @__PURE__ */ __commonJSMin(((exports) => {
82241
+ var require_constants = /* @__PURE__ */ __commonJSMin(((exports) => {
81887
82242
  Object.defineProperty(exports, "__esModule", { value: true });
81888
82243
  exports.IS_SUPPORT_READDIR_WITH_FILE_TYPES = void 0;
81889
82244
  const NODE_PROCESS_VERSION_PARTS = process.versions.node.split(".");
@@ -81954,7 +82309,7 @@ var require_async$4 = /* @__PURE__ */ __commonJSMin(((exports) => {
81954
82309
  exports.readdir = exports.readdirWithFileTypes = exports.read = void 0;
81955
82310
  const fsStat = require_out$3();
81956
82311
  const rpl = require_run_parallel();
81957
- const constants_1 = require_constants$1();
82312
+ const constants_1 = require_constants();
81958
82313
  const utils = require_utils$1();
81959
82314
  const common = require_common$3();
81960
82315
  function read(directory, settings, callback) {
@@ -82059,7 +82414,7 @@ var require_sync$4 = /* @__PURE__ */ __commonJSMin(((exports) => {
82059
82414
  Object.defineProperty(exports, "__esModule", { value: true });
82060
82415
  exports.readdir = exports.readdirWithFileTypes = exports.read = void 0;
82061
82416
  const fsStat = require_out$3();
82062
- const constants_1 = require_constants$1();
82417
+ const constants_1 = require_constants();
82063
82418
  const utils = require_utils$1();
82064
82419
  const common = require_common$3();
82065
82420
  function read(directory, settings) {
@@ -84270,7 +84625,7 @@ var require_readShebang = /* @__PURE__ */ __commonJSMin(((exports, module) => {
84270
84625
 
84271
84626
  //#endregion
84272
84627
  //#region node_modules/cross-spawn/lib/parse.js
84273
- var require_parse$1 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
84628
+ var require_parse = /* @__PURE__ */ __commonJSMin(((exports, module) => {
84274
84629
  const path$17 = __require("path");
84275
84630
  const resolveCommand = require_resolveCommand();
84276
84631
  const escape = require_escape();
@@ -84374,7 +84729,7 @@ var require_enoent = /* @__PURE__ */ __commonJSMin(((exports, module) => {
84374
84729
  //#region node_modules/cross-spawn/index.js
84375
84730
  var require_cross_spawn = /* @__PURE__ */ __commonJSMin(((exports, module) => {
84376
84731
  const cp = __require("child_process");
84377
- const parse = require_parse$1();
84732
+ const parse = require_parse();
84378
84733
  const enoent = require_enoent();
84379
84734
  function spawn(command, args, options) {
84380
84735
  const parsed = parse(command, args, options);
@@ -87772,6 +88127,13 @@ var VisualExec = class {
87772
88127
  kill(signal) {
87773
88128
  this.abort(signal);
87774
88129
  }
88130
+ /**
88131
+ * Attach to an already-started child and render its output.
88132
+ *
88133
+ * Resolves with the command's `ExecOutput`, unless an `onComplete` callback returned a
88134
+ * value - that value is passed through instead. `T` names what the caller expects back;
88135
+ * it defaults to `ExecOutput`, which is what a run without an `onComplete` produces.
88136
+ */
87775
88137
  show(child) {
87776
88138
  this._stdoutKey = Symbol("visual-exec-stdout");
87777
88139
  this._stderrKey = Symbol("visual-exec-stderr");
@@ -87815,13 +88177,14 @@ var VisualExec = class {
87815
88177
  let timeoutId;
87816
88178
  const startTime = Date.now();
87817
88179
  let execPromise = child.promise.catch((err) => {
87818
- var _a, _b, _c, _d;
87819
- const output = (_a = err.output) !== null && _a !== void 0 ? _a : {
88180
+ var _a;
88181
+ var _b, _c, _d;
88182
+ const output = (_b = err.output) !== null && _b !== void 0 ? _b : {
87820
88183
  stdout: "",
87821
88184
  stderr: ""
87822
88185
  };
87823
- const exitCode = (_c = (_b = err.exitCode) !== null && _b !== void 0 ? _b : err.code) !== null && _c !== void 0 ? _c : 1;
87824
- (_d = this._onComplete) === null || _d === void 0 || _d.call(this, output, exitCode);
88186
+ const exitCode = (_d = (_c = err.exitCode) !== null && _c !== void 0 ? _c : err.code) !== null && _d !== void 0 ? _d : 1;
88187
+ (_a = this._onComplete) === null || _a === void 0 || _a.call(this, output, exitCode);
87825
88188
  this.logResult(err);
87826
88189
  throw err;
87827
88190
  }).then((output) => {
@@ -87922,6 +88285,12 @@ var VisualExec = class {
87922
88285
  logs.push(chalk.blue("\n<<<"), `End of output from ${this._outputLabel} ---`);
87923
88286
  this._logger.prefix(false)[level](...logs);
87924
88287
  }
88288
+ /**
88289
+ * Run the command and render its output.
88290
+ *
88291
+ * Resolves with `ExecOutput`, or with whatever an `onComplete` callback returned in its
88292
+ * place. `T` defaults to `ExecOutput` so the common case needs no cast at the call site.
88293
+ */
87925
88294
  execute(command) {
87926
88295
  this._startTime = Date.now();
87927
88296
  this._aborted = false;
@@ -87951,13 +88320,14 @@ var VisualExec = class {
87951
88320
  };
87952
88321
  };
87953
88322
  child.promise = child.promise.catch((err) => {
87954
- var _a, _b, _c, _d, _e;
87955
- const output = (_a = err.output) !== null && _a !== void 0 ? _a : {
88323
+ var _a;
88324
+ var _b, _c, _d, _e;
88325
+ const output = (_b = err.output) !== null && _b !== void 0 ? _b : {
87956
88326
  stdout: "",
87957
88327
  stderr: ""
87958
88328
  };
87959
- const exitCode = (_b = err.code) !== null && _b !== void 0 ? _b : 1;
87960
- const signal = (_e = (_c = err.signal) !== null && _c !== void 0 ? _c : ((_d = result.child) === null || _d === void 0 ? void 0 : _d.killed) ? "SIGTERM" : null) !== null && _e !== void 0 ? _e : null;
88329
+ const exitCode = (_c = err.code) !== null && _c !== void 0 ? _c : 1;
88330
+ const signal = (_e = (_d = err.signal) !== null && _d !== void 0 ? _d : ((_a = result.child) === null || _a === void 0 ? void 0 : _a.killed) ? "SIGTERM" : null) !== null && _e !== void 0 ? _e : null;
87961
88331
  const context = {
87962
88332
  ...baseContext(output),
87963
88333
  exitCode,
@@ -89509,7 +89879,7 @@ var PkgSrcManager = class {
89509
89879
  }
89510
89880
  pkgPreperInstallDep(dir, displayTitle) {
89511
89881
  const node = process.env.NODE || process.execPath;
89512
- const fyn = Path.join(__dirname, "../bin/fyn.js");
89882
+ const fyn = Path.join(__dirname, "../bin/fyn.mjs");
89513
89883
  return new VisualExec({
89514
89884
  displayTitle,
89515
89885
  cwd: dir,
@@ -91393,9 +91763,9 @@ var PkgDepLocker = class {
91393
91763
  const dist = vpkg.dist || {};
91394
91764
  if (vpkg.top) meta.top = 1;
91395
91765
  const scripts = json.scripts || {};
91396
- if (vpkg.optFailed) meta.optFailed = vpkg.optFailed;
91766
+ if (vpkg.optFailed && vpkg.optFailed !== 3) meta.optFailed = vpkg.optFailed;
91397
91767
  else if (scripts.preinstall || vpkg.hasPI) meta.hasPI = 1;
91398
- if (scripts.install || scripts.postinstall || scripts.postInstall) meta.hasI = 1;
91768
+ if (scripts.install || scripts.postinstall || scripts.postInstall || vpkg.hasI) meta.hasI = 1;
91399
91769
  if (vpkg.local) {
91400
91770
  meta.$ = "local";
91401
91771
  meta._ = dist.fullPath;
@@ -91404,7 +91774,7 @@ var PkgDepLocker = class {
91404
91774
  meta._ = dist.tarball;
91405
91775
  }
91406
91776
  if (!meta.optFailed) {
91407
- if (import_lodash_min.default.isEmpty(json)) meta._missingJson = true;
91777
+ if (import_lodash_min.default.isEmpty(json) || json._missingJson) meta._missingJson = true;
91408
91778
  else {
91409
91779
  if (!import_lodash_min.default.isEmpty(json.dependencies)) meta.dependencies = json.dependencies;
91410
91780
  if (!import_lodash_min.default.isEmpty(json.optionalDependencies)) meta.optionalDependencies = json.optionalDependencies;
@@ -91501,26 +91871,28 @@ var PkgDepLocker = class {
91501
91871
  const badVersionKey = !(vpkg && vpkg.$ === "local") && !import_semver.default.valid(version);
91502
91872
  const badDeps = vpkg && vpkg.dependencies && !this._depsResolvable(vpkg.dependencies);
91503
91873
  if (!import_lodash_min.default.isEmpty(vpkg) && vpkg._valid !== false && !badVersionKey && !badDeps) {
91874
+ const { $: _dollar, _: _under, top: _top, _valid: _v, ...carried } = vpkg;
91875
+ const expanded = {
91876
+ ...carried,
91877
+ fromLocked: true,
91878
+ name: item.name,
91879
+ version,
91880
+ _hasShrinkwrap: Boolean(vpkg._hasShrinkwrap)
91881
+ };
91504
91882
  if (vpkg.$ === "local") {
91505
- vpkg.local = true;
91506
- vpkg.dist = {
91883
+ expanded.local = isLocalHard(version) ? "hard" : "sym";
91884
+ expanded.dist = {
91507
91885
  integrity: "local",
91508
91886
  fullPath: vpkg._
91509
91887
  };
91510
91888
  } else {
91511
91889
  const tarballUrl = this.normalizeTarballUrl(item, vpkg._);
91512
- vpkg.dist = {
91890
+ expanded.dist = {
91513
91891
  integrity: fyntil.shaToIntegrity(vpkg.$),
91514
91892
  tarball: tarballUrl
91515
91893
  };
91516
91894
  }
91517
- vpkg.$ = void 0;
91518
- vpkg._ = void 0;
91519
- vpkg.fromLocked = true;
91520
- vpkg.name = item.name;
91521
- vpkg.version = version;
91522
- if (vpkg._hasShrinkwrap) vpkg._hasShrinkwrap = true;
91523
- versions[version] = vpkg;
91895
+ versions[version] = expanded;
91524
91896
  } else {
91525
91897
  if (badVersionKey) logger$1.error(`lockfile entry for ${item.name} has invalid version key "${version}" - ignoring and re-resolving from registry`);
91526
91898
  else if (badDeps) logger$1.error(`lockfile entry ${item.name}@${version} has dependencies not satisfiable within the lock (corrupt lock) - ignoring and re-resolving from registry`);
@@ -91558,7 +91930,10 @@ var PkgDepLocker = class {
91558
91930
  }
91559
91931
  if (Object.prototype.hasOwnProperty.call(rangeMap, spec)) return true;
91560
91932
  if (!import_semver.default.validRange(spec)) return true;
91561
- return versions.some((v) => import_semver.default.valid(v) && import_semver.default.satisfies(v, spec));
91933
+ return versions.some((v) => {
91934
+ const base = unlocalify(v);
91935
+ return import_semver.default.valid(base) && import_semver.default.satisfies(base, spec);
91936
+ });
91562
91937
  }
91563
91938
  /**
91564
91939
  * Set the package.json's dependencies items and check if they changed from
@@ -91778,7 +92153,7 @@ var DepData = class {
91778
92153
  return bad ? this.badPkgs : this.pkgs;
91779
92154
  }
91780
92155
  getPkg(item) {
91781
- return this.getPkgsData(item.optFailed)[item.name];
92156
+ return this.getPkgsData(Boolean(item.optFailed))[item.name];
91782
92157
  }
91783
92158
  getPkgById(id) {
91784
92159
  const lastAt = id.lastIndexOf("@");
@@ -94267,434 +94642,333 @@ var require_invariant = /* @__PURE__ */ __commonJSMin(((exports, module) => {
94267
94642
 
94268
94643
  //#endregion
94269
94644
  //#region node_modules/strip-bom/index.js
94270
- var strip_bom_exports = /* @__PURE__ */ __exportAll({ default: () => stripBom });
94645
+ var import_invariant = /* @__PURE__ */ __toESM(require_invariant());
94271
94646
  function stripBom(string) {
94272
94647
  if (typeof string !== "string") throw new TypeError(`Expected a string, got ${typeof string}`);
94273
94648
  if (string.charCodeAt(0) === 65279) return string.slice(1);
94274
94649
  return string;
94275
94650
  }
94276
- var init_strip_bom = __esmMin((() => {}));
94277
94651
 
94278
94652
  //#endregion
94279
- //#region yarn/lib/constants.js
94280
- var require_constants = /* @__PURE__ */ __commonJSMin(((exports) => {
94281
- Object.defineProperty(exports, "__esModule", { value: true });
94282
- exports.LOCKFILE_VERSION = void 0;
94283
- exports.LOCKFILE_VERSION = 1;
94284
- }));
94285
-
94286
- //#endregion
94287
- //#region yarn/lib/errors.js
94288
- var require_errors = /* @__PURE__ */ __commonJSMin(((exports) => {
94289
- Object.defineProperty(exports, "__esModule", { value: true });
94290
- exports.SecurityError = exports.ResponseError = exports.ProcessTermError = exports.ProcessSpawnError = exports.OneTimePasswordError = exports.MessageError = void 0;
94291
- function _defineProperty(e, r, t) {
94292
- return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
94293
- value: t,
94294
- enumerable: !0,
94295
- configurable: !0,
94296
- writable: !0
94297
- }) : e[r] = t, e;
94298
- }
94299
- function _toPropertyKey(t) {
94300
- var i = _toPrimitive(t, "string");
94301
- return "symbol" == typeof i ? i : i + "";
94302
- }
94303
- function _toPrimitive(t, r) {
94304
- if ("object" != typeof t || !t) return t;
94305
- var e = t[Symbol.toPrimitive];
94306
- if (void 0 !== e) {
94307
- var i = e.call(t, r || "default");
94308
- if ("object" != typeof i) return i;
94309
- throw new TypeError("@@toPrimitive must return a primitive value.");
94310
- }
94311
- return ("string" === r ? String : Number)(t);
94312
- }
94313
- var MessageError = class extends Error {
94314
- constructor(msg, code) {
94315
- super(msg);
94316
- _defineProperty(this, "code", void 0);
94317
- this.code = code;
94318
- }
94319
- };
94320
- exports.MessageError = MessageError;
94321
- var ProcessSpawnError = class extends MessageError {
94322
- constructor(msg, code, process) {
94323
- super(msg, code);
94324
- _defineProperty(this, "process", void 0);
94325
- this.process = process;
94326
- }
94327
- };
94328
- exports.ProcessSpawnError = ProcessSpawnError;
94329
- var SecurityError = class extends MessageError {};
94330
- exports.SecurityError = SecurityError;
94331
- var ProcessTermError = class extends MessageError {
94332
- constructor(...args) {
94333
- super(...args);
94334
- _defineProperty(this, "EXIT_CODE", void 0);
94335
- _defineProperty(this, "EXIT_SIGNAL", void 0);
94336
- }
94337
- };
94338
- exports.ProcessTermError = ProcessTermError;
94339
- var ResponseError = class extends Error {
94340
- constructor(msg, responseCode) {
94341
- super(msg);
94342
- _defineProperty(this, "responseCode", void 0);
94343
- this.responseCode = responseCode;
94344
- }
94345
- };
94346
- exports.ResponseError = ResponseError;
94347
- var OneTimePasswordError = class extends Error {};
94348
- exports.OneTimePasswordError = OneTimePasswordError;
94349
- }));
94653
+ //#region yarn/src/errors.ts
94654
+ var MessageError = class extends Error {
94655
+ constructor(msg, code) {
94656
+ super(msg);
94657
+ this.code = code;
94658
+ }
94659
+ code;
94660
+ };
94350
94661
 
94351
94662
  //#endregion
94352
- //#region yarn/lib/util/map.js
94353
- var require_map = /* @__PURE__ */ __commonJSMin(((exports) => {
94354
- Object.defineProperty(exports, "__esModule", { value: true });
94355
- exports.default = nullify;
94356
- function nullify(obj = {}) {
94357
- if (Array.isArray(obj)) for (const item of obj) nullify(item);
94358
- else if (obj !== null && typeof obj === "object" || typeof obj === "function") {
94359
- Object.setPrototypeOf(obj, null);
94360
- if (typeof obj === "object") for (const key in obj) nullify(obj[key]);
94361
- }
94362
- return obj;
94663
+ //#region yarn/src/util/map.ts
94664
+ function nullify(obj = {}) {
94665
+ if (Array.isArray(obj)) for (const item of obj) nullify(item);
94666
+ else if (obj !== null && typeof obj === "object" || typeof obj === "function") {
94667
+ Object.setPrototypeOf(obj, null);
94668
+ if (typeof obj === "object") for (const key in obj) nullify(obj[key]);
94363
94669
  }
94364
- }));
94670
+ return obj;
94671
+ }
94365
94672
 
94366
94673
  //#endregion
94367
- //#region yarn/lib/lockfile/parse.js
94368
- var require_parse = /* @__PURE__ */ __commonJSMin(((exports) => {
94369
- Object.defineProperty(exports, "__esModule", { value: true });
94370
- exports.default = _default;
94371
- var _util = _interopRequireDefault(__require("util"));
94372
- var _invariant = _interopRequireDefault(require_invariant());
94373
- var _stripBom = _interopRequireDefault((init_strip_bom(), __toCommonJS(strip_bom_exports)));
94374
- var _constants = require_constants();
94375
- var _errors = require_errors();
94376
- var _map = _interopRequireDefault(require_map());
94377
- var _yamljs = _interopRequireDefault(require_Yaml());
94378
- function _interopRequireDefault(e) {
94379
- return e && e.__esModule ? e : { default: e };
94380
- }
94381
- function _defineProperty(e, r, t) {
94382
- return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
94383
- value: t,
94384
- enumerable: !0,
94385
- configurable: !0,
94386
- writable: !0
94387
- }) : e[r] = t, e;
94388
- }
94389
- function _toPropertyKey(t) {
94390
- var i = _toPrimitive(t, "string");
94391
- return "symbol" == typeof i ? i : i + "";
94392
- }
94393
- function _toPrimitive(t, r) {
94394
- if ("object" != typeof t || !t) return t;
94395
- var e = t[Symbol.toPrimitive];
94396
- if (void 0 !== e) {
94397
- var i = e.call(t, r || "default");
94398
- if ("object" != typeof i) return i;
94399
- throw new TypeError("@@toPrimitive must return a primitive value.");
94400
- }
94401
- return ("string" === r ? String : Number)(t);
94402
- }
94403
- const VERSION_REGEX = /^yarn lockfile v(\d+)$/;
94404
- const TOKEN_TYPES = {
94405
- boolean: "BOOLEAN",
94406
- string: "STRING",
94407
- identifier: "IDENTIFIER",
94408
- eof: "EOF",
94409
- colon: "COLON",
94410
- newline: "NEWLINE",
94411
- comment: "COMMENT",
94412
- indent: "INDENT",
94413
- invalid: "INVALID",
94414
- number: "NUMBER",
94415
- comma: "COMMA"
94416
- };
94417
- const VALID_PROP_VALUE_TOKENS = [
94418
- TOKEN_TYPES.boolean,
94419
- TOKEN_TYPES.string,
94420
- TOKEN_TYPES.number
94421
- ];
94422
- function isValidPropValueToken(token) {
94423
- return VALID_PROP_VALUE_TOKENS.indexOf(token.type) >= 0;
94674
+ //#region yarn/src/lockfile/parse.ts
94675
+ const VERSION_REGEX = /^yarn lockfile v(\d+)$/;
94676
+ const TOKEN_TYPES = {
94677
+ boolean: "BOOLEAN",
94678
+ string: "STRING",
94679
+ identifier: "IDENTIFIER",
94680
+ eof: "EOF",
94681
+ colon: "COLON",
94682
+ newline: "NEWLINE",
94683
+ comment: "COMMENT",
94684
+ indent: "INDENT",
94685
+ invalid: "INVALID",
94686
+ number: "NUMBER",
94687
+ comma: "COMMA"
94688
+ };
94689
+ const VALID_PROP_VALUE_TOKENS = [
94690
+ TOKEN_TYPES.boolean,
94691
+ TOKEN_TYPES.string,
94692
+ TOKEN_TYPES.number
94693
+ ];
94694
+ function isValidPropValueToken(token) {
94695
+ return VALID_PROP_VALUE_TOKENS.indexOf(token.type) >= 0;
94696
+ }
94697
+ function* tokenise(input) {
94698
+ let lastNewline = false;
94699
+ let line = 1;
94700
+ let col = 0;
94701
+ function buildToken(type, value) {
94702
+ return {
94703
+ line,
94704
+ col,
94705
+ type,
94706
+ value
94707
+ };
94424
94708
  }
94425
- function* tokenise(input) {
94426
- let lastNewline = false;
94427
- let line = 1;
94428
- let col = 0;
94429
- function buildToken(type, value) {
94430
- return {
94431
- line,
94432
- col,
94433
- type,
94434
- value
94435
- };
94436
- }
94437
- while (input.length) {
94438
- let chop = 0;
94439
- if (input[0] === "\n" || input[0] === "\r") {
94440
- chop++;
94441
- if (input[1] === "\n") chop++;
94442
- line++;
94443
- col = 0;
94444
- yield buildToken(TOKEN_TYPES.newline);
94445
- } else if (input[0] === "#") {
94446
- chop++;
94447
- let nextNewline = input.indexOf("\n", chop);
94448
- if (nextNewline === -1) nextNewline = input.length;
94449
- const val = input.substring(chop, nextNewline);
94450
- chop = nextNewline;
94451
- yield buildToken(TOKEN_TYPES.comment, val);
94452
- } else if (input[0] === " ") {
94453
- if (lastNewline) {
94454
- let indentSize = 1;
94455
- for (let i = 1; input[i] === " "; i++) indentSize++;
94456
- if (indentSize % 2) throw new TypeError("Invalid number of spaces");
94457
- else {
94458
- chop = indentSize;
94459
- yield buildToken(TOKEN_TYPES.indent, indentSize / 2);
94460
- }
94461
- } else chop++;
94462
- } else if (input[0] === "\"") {
94463
- let i = 1;
94464
- for (; i < input.length; i++) if (input[i] === "\"") {
94465
- if (!(input[i - 1] === "\\" && input[i - 2] !== "\\")) {
94466
- i++;
94467
- break;
94468
- }
94709
+ while (input.length) {
94710
+ let chop = 0;
94711
+ if (input[0] === "\n" || input[0] === "\r") {
94712
+ chop++;
94713
+ if (input[1] === "\n") chop++;
94714
+ line++;
94715
+ col = 0;
94716
+ yield buildToken(TOKEN_TYPES.newline);
94717
+ } else if (input[0] === "#") {
94718
+ chop++;
94719
+ let nextNewline = input.indexOf("\n", chop);
94720
+ if (nextNewline === -1) nextNewline = input.length;
94721
+ const val = input.substring(chop, nextNewline);
94722
+ chop = nextNewline;
94723
+ yield buildToken(TOKEN_TYPES.comment, val);
94724
+ } else if (input[0] === " ") {
94725
+ if (lastNewline) {
94726
+ let indentSize = 1;
94727
+ for (let i = 1; input[i] === " "; i++) indentSize++;
94728
+ if (indentSize % 2) throw new TypeError("Invalid number of spaces");
94729
+ else {
94730
+ chop = indentSize;
94731
+ yield buildToken(TOKEN_TYPES.indent, indentSize / 2);
94732
+ }
94733
+ } else chop++;
94734
+ } else if (input[0] === "\"") {
94735
+ let i = 1;
94736
+ for (; i < input.length; i++) if (input[i] === "\"") {
94737
+ if (!(input[i - 1] === "\\" && input[i - 2] !== "\\")) {
94738
+ i++;
94739
+ break;
94469
94740
  }
94470
- const val = input.substring(0, i);
94471
- chop = i;
94472
- try {
94473
- yield buildToken(TOKEN_TYPES.string, JSON.parse(val));
94474
- } catch (err) {
94475
- if (err instanceof SyntaxError) yield buildToken(TOKEN_TYPES.invalid);
94476
- else throw err;
94477
- }
94478
- } else if (/^[0-9]/.test(input)) {
94479
- const val = /^[0-9]+/.exec(input)[0];
94480
- chop = val.length;
94481
- yield buildToken(TOKEN_TYPES.number, +val);
94482
- } else if (/^true/.test(input)) {
94483
- yield buildToken(TOKEN_TYPES.boolean, true);
94484
- chop = 4;
94485
- } else if (/^false/.test(input)) {
94486
- yield buildToken(TOKEN_TYPES.boolean, false);
94487
- chop = 5;
94488
- } else if (input[0] === ":") {
94489
- yield buildToken(TOKEN_TYPES.colon);
94490
- chop++;
94491
- } else if (input[0] === ",") {
94492
- yield buildToken(TOKEN_TYPES.comma);
94493
- chop++;
94494
- } else if (/^[a-zA-Z\/.-]/g.test(input)) {
94495
- let i = 0;
94496
- for (; i < input.length; i++) {
94497
- const char = input[i];
94498
- if (char === ":" || char === " " || char === "\n" || char === "\r" || char === ",") break;
94499
- }
94500
- const name = input.substring(0, i);
94501
- chop = i;
94502
- yield buildToken(TOKEN_TYPES.string, name);
94503
- } else yield buildToken(TOKEN_TYPES.invalid);
94504
- if (!chop) yield buildToken(TOKEN_TYPES.invalid);
94505
- col += chop;
94506
- lastNewline = input[0] === "\n" || input[0] === "\r" && input[1] === "\n";
94507
- input = input.slice(chop);
94508
- }
94509
- yield buildToken(TOKEN_TYPES.eof);
94510
- }
94511
- var Parser = class {
94512
- constructor(input, fileLoc = "lockfile") {
94513
- _defineProperty(this, "fileLoc", void 0);
94514
- _defineProperty(this, "token", void 0);
94515
- _defineProperty(this, "tokens", void 0);
94516
- _defineProperty(this, "comments", void 0);
94517
- this.comments = [];
94518
- this.tokens = tokenise(input);
94519
- this.fileLoc = fileLoc;
94520
- }
94521
- onComment(token) {
94522
- const value = token.value;
94523
- (0, _invariant.default)(typeof value === "string", "expected token value to be a string");
94524
- const comment = value.trim();
94525
- const versionMatch = comment.match(VERSION_REGEX);
94526
- if (versionMatch) {
94527
- const version = +versionMatch[1];
94528
- if (version > _constants.LOCKFILE_VERSION) throw new _errors.MessageError(`Can't install from a lockfile of version ${version} as you're on an old yarn version that only supports versions up to ${_constants.LOCKFILE_VERSION}. Run \`$ yarn self-update\` to upgrade to the latest version.`);
94529
- }
94530
- this.comments.push(comment);
94531
- }
94532
- next() {
94533
- const item = this.tokens.next();
94534
- (0, _invariant.default)(item, "expected a token");
94535
- const { done, value } = item;
94536
- if (done || !value) throw new Error("No more tokens");
94537
- else if (value.type === TOKEN_TYPES.comment) {
94538
- this.onComment(value);
94539
- return this.next();
94540
- } else return this.token = value;
94541
- }
94542
- unexpected(msg = "Unexpected token") {
94543
- throw new SyntaxError(`${msg} ${this.token.line}:${this.token.col} in ${this.fileLoc}`);
94544
- }
94545
- expect(tokType) {
94546
- if (this.token.type === tokType) this.next();
94547
- else this.unexpected();
94548
- }
94549
- eat(tokType) {
94550
- if (this.token.type === tokType) {
94741
+ }
94742
+ const val = input.substring(0, i);
94743
+ chop = i;
94744
+ try {
94745
+ yield buildToken(TOKEN_TYPES.string, JSON.parse(val));
94746
+ } catch (err) {
94747
+ if (err instanceof SyntaxError) yield buildToken(TOKEN_TYPES.invalid);
94748
+ else throw err;
94749
+ }
94750
+ } else if (/^[0-9]/.test(input)) {
94751
+ const val = /^[0-9]+/.exec(input)[0];
94752
+ chop = val.length;
94753
+ yield buildToken(TOKEN_TYPES.number, +val);
94754
+ } else if (/^true/.test(input)) {
94755
+ yield buildToken(TOKEN_TYPES.boolean, true);
94756
+ chop = 4;
94757
+ } else if (/^false/.test(input)) {
94758
+ yield buildToken(TOKEN_TYPES.boolean, false);
94759
+ chop = 5;
94760
+ } else if (input[0] === ":") {
94761
+ yield buildToken(TOKEN_TYPES.colon);
94762
+ chop++;
94763
+ } else if (input[0] === ",") {
94764
+ yield buildToken(TOKEN_TYPES.comma);
94765
+ chop++;
94766
+ } else if (/^[a-zA-Z\/.-]/g.test(input)) {
94767
+ let i = 0;
94768
+ for (; i < input.length; i++) {
94769
+ const char = input[i];
94770
+ if (char === ":" || char === " " || char === "\n" || char === "\r" || char === ",") break;
94771
+ }
94772
+ const name = input.substring(0, i);
94773
+ chop = i;
94774
+ yield buildToken(TOKEN_TYPES.string, name);
94775
+ } else yield buildToken(TOKEN_TYPES.invalid);
94776
+ if (!chop) yield buildToken(TOKEN_TYPES.invalid);
94777
+ col += chop;
94778
+ lastNewline = input[0] === "\n" || input[0] === "\r" && input[1] === "\n";
94779
+ input = input.slice(chop);
94780
+ }
94781
+ yield buildToken(TOKEN_TYPES.eof);
94782
+ }
94783
+ var Parser$1 = class {
94784
+ constructor(input, fileLoc = "lockfile") {
94785
+ this.comments = [];
94786
+ this.tokens = tokenise(input);
94787
+ this.fileLoc = fileLoc;
94788
+ }
94789
+ fileLoc;
94790
+ token;
94791
+ tokens;
94792
+ comments;
94793
+ onComment(token) {
94794
+ const value = token.value;
94795
+ (0, import_invariant.default)(typeof value === "string", "expected token value to be a string");
94796
+ const comment = value.trim();
94797
+ const versionMatch = comment.match(VERSION_REGEX);
94798
+ if (versionMatch) {
94799
+ const version = +versionMatch[1];
94800
+ if (version > 1) throw new MessageError(`Can't install from a lockfile of version ${version} as you're on an old yarn version that only supports versions up to ${1}. Run \`$ yarn self-update\` to upgrade to the latest version.`);
94801
+ }
94802
+ this.comments.push(comment);
94803
+ }
94804
+ next() {
94805
+ const item = this.tokens.next();
94806
+ (0, import_invariant.default)(item, "expected a token");
94807
+ const { done, value } = item;
94808
+ if (done || !value) throw new Error("No more tokens");
94809
+ else if (value.type === TOKEN_TYPES.comment) {
94810
+ this.onComment(value);
94811
+ return this.next();
94812
+ } else return this.token = value;
94813
+ }
94814
+ unexpected(msg = "Unexpected token") {
94815
+ throw new SyntaxError(`${msg} ${this.token.line}:${this.token.col} in ${this.fileLoc}`);
94816
+ }
94817
+ expect(tokType) {
94818
+ if (this.token.type === tokType) this.next();
94819
+ else this.unexpected();
94820
+ }
94821
+ eat(tokType) {
94822
+ if (this.token.type === tokType) {
94823
+ this.next();
94824
+ return true;
94825
+ } else return false;
94826
+ }
94827
+ parse(indent = 0) {
94828
+ const obj = nullify();
94829
+ while (true) {
94830
+ const propToken = this.token;
94831
+ if (propToken.type === TOKEN_TYPES.newline) {
94832
+ const nextToken = this.next();
94833
+ if (!indent) continue;
94834
+ if (nextToken.type !== TOKEN_TYPES.indent) break;
94835
+ if (nextToken.value === indent) this.next();
94836
+ else break;
94837
+ } else if (propToken.type === TOKEN_TYPES.indent) {
94838
+ if (propToken.value === indent) this.next();
94839
+ else break;
94840
+ } else if (propToken.type === TOKEN_TYPES.eof) break;
94841
+ else if (propToken.type === TOKEN_TYPES.string) {
94842
+ const key = propToken.value;
94843
+ (0, import_invariant.default)(key, "Expected a key");
94844
+ const keys = [key];
94551
94845
  this.next();
94552
- return true;
94553
- } else return false;
94554
- }
94555
- parse(indent = 0) {
94556
- const obj = (0, _map.default)();
94557
- while (true) {
94558
- const propToken = this.token;
94559
- if (propToken.type === TOKEN_TYPES.newline) {
94560
- const nextToken = this.next();
94561
- if (!indent) continue;
94562
- if (nextToken.type !== TOKEN_TYPES.indent) break;
94563
- if (nextToken.value === indent) this.next();
94564
- else break;
94565
- } else if (propToken.type === TOKEN_TYPES.indent) {
94566
- if (propToken.value === indent) this.next();
94567
- else break;
94568
- } else if (propToken.type === TOKEN_TYPES.eof) break;
94569
- else if (propToken.type === TOKEN_TYPES.string) {
94570
- const key = propToken.value;
94571
- (0, _invariant.default)(key, "Expected a key");
94572
- const keys = [key];
94846
+ while (this.token.type === TOKEN_TYPES.comma) {
94847
+ this.next();
94848
+ const keyToken = this.token;
94849
+ if (keyToken.type !== TOKEN_TYPES.string) this.unexpected("Expected string");
94850
+ const key = keyToken.value;
94851
+ (0, import_invariant.default)(key, "Expected a key");
94852
+ keys.push(key);
94573
94853
  this.next();
94574
- while (this.token.type === TOKEN_TYPES.comma) {
94575
- this.next();
94576
- const keyToken = this.token;
94577
- if (keyToken.type !== TOKEN_TYPES.string) this.unexpected("Expected string");
94578
- const key = keyToken.value;
94579
- (0, _invariant.default)(key, "Expected a key");
94580
- keys.push(key);
94581
- this.next();
94582
- }
94583
- const wasColon = this.token.type === TOKEN_TYPES.colon;
94584
- if (wasColon) this.next();
94585
- if (isValidPropValueToken(this.token)) {
94586
- for (const key of keys) obj[key] = this.token.value;
94587
- this.next();
94588
- } else if (wasColon) {
94589
- const val = this.parse(indent + 1);
94590
- for (const key of keys) obj[key] = val;
94591
- if (indent && this.token.type !== TOKEN_TYPES.indent) break;
94592
- } else this.unexpected("Invalid value type");
94593
- } else this.unexpected(`Unknown token: ${_util.default.inspect(propToken)}`);
94594
- }
94595
- return obj;
94596
- }
94597
- };
94598
- const MERGE_CONFLICT_ANCESTOR = "|||||||";
94599
- const MERGE_CONFLICT_END = ">>>>>>>";
94600
- const MERGE_CONFLICT_SEP = "=======";
94601
- const MERGE_CONFLICT_START = "<<<<<<<";
94602
- /**
94603
- * Extract the two versions of the lockfile from a merge conflict.
94604
- */
94605
- function extractConflictVariants(str) {
94606
- const variants = [[], []];
94607
- const lines = str.split(/\r?\n/g);
94608
- let skip = false;
94609
- while (lines.length) {
94610
- const line = lines.shift();
94611
- if (line.startsWith(MERGE_CONFLICT_START)) {
94612
- while (lines.length) {
94613
- const conflictLine = lines.shift();
94614
- if (conflictLine === MERGE_CONFLICT_SEP) {
94615
- skip = false;
94616
- break;
94617
- } else if (skip || conflictLine.startsWith(MERGE_CONFLICT_ANCESTOR)) {
94618
- skip = true;
94619
- continue;
94620
- } else variants[0].push(conflictLine);
94621
- }
94622
- while (lines.length) {
94623
- const conflictLine = lines.shift();
94624
- if (conflictLine.startsWith(MERGE_CONFLICT_END)) break;
94625
- else variants[1].push(conflictLine);
94626
94854
  }
94627
- } else {
94628
- variants[0].push(line);
94629
- variants[1].push(line);
94630
- }
94855
+ const wasColon = this.token.type === TOKEN_TYPES.colon;
94856
+ if (wasColon) this.next();
94857
+ if (isValidPropValueToken(this.token)) {
94858
+ for (const key of keys) obj[key] = this.token.value;
94859
+ this.next();
94860
+ } else if (wasColon) {
94861
+ const val = this.parse(indent + 1);
94862
+ for (const key of keys) obj[key] = val;
94863
+ if (indent && this.token.type !== TOKEN_TYPES.indent) break;
94864
+ } else this.unexpected("Invalid value type");
94865
+ } else this.unexpected(`Unknown token: ${Util.inspect(propToken)}`);
94631
94866
  }
94632
- return [variants[0].join("\n"), variants[1].join("\n")];
94633
- }
94634
- /**
94635
- * Check if a lockfile has merge conflicts.
94636
- */
94637
- function hasMergeConflicts(str) {
94638
- return str.includes(MERGE_CONFLICT_START) && str.includes(MERGE_CONFLICT_SEP) && str.includes(MERGE_CONFLICT_END);
94867
+ return obj;
94639
94868
  }
94640
- /**
94641
- * Parse the lockfile.
94642
- */
94643
- function parse(str, fileLoc) {
94644
- const parser = new Parser(str, fileLoc);
94645
- parser.next();
94646
- if (!fileLoc.endsWith(`.yml`)) try {
94647
- return parser.parse();
94648
- } catch (error1) {
94649
- try {
94650
- return _yamljs.default.parse(str);
94651
- } catch (error2) {
94652
- throw error1;
94869
+ };
94870
+ const MERGE_CONFLICT_ANCESTOR = "|||||||";
94871
+ const MERGE_CONFLICT_END = ">>>>>>>";
94872
+ const MERGE_CONFLICT_SEP = "=======";
94873
+ const MERGE_CONFLICT_START = "<<<<<<<";
94874
+ /**
94875
+ * Extract the two versions of the lockfile from a merge conflict.
94876
+ */
94877
+ function extractConflictVariants(str) {
94878
+ const variants = [[], []];
94879
+ const lines = str.split(/\r?\n/g);
94880
+ let skip = false;
94881
+ while (lines.length) {
94882
+ const line = lines.shift();
94883
+ if (line.startsWith(MERGE_CONFLICT_START)) {
94884
+ while (lines.length) {
94885
+ const conflictLine = lines.shift();
94886
+ if (conflictLine === MERGE_CONFLICT_SEP) {
94887
+ skip = false;
94888
+ break;
94889
+ } else if (skip || conflictLine.startsWith(MERGE_CONFLICT_ANCESTOR)) {
94890
+ skip = true;
94891
+ continue;
94892
+ } else variants[0].push(conflictLine);
94653
94893
  }
94654
- }
94655
- else {
94656
- const result = _yamljs.default.parse(str);
94657
- if (typeof result === "object") return result;
94658
- else return {};
94894
+ while (lines.length) {
94895
+ const conflictLine = lines.shift();
94896
+ if (conflictLine.startsWith(MERGE_CONFLICT_END)) break;
94897
+ else variants[1].push(conflictLine);
94898
+ }
94899
+ } else {
94900
+ variants[0].push(line);
94901
+ variants[1].push(line);
94659
94902
  }
94660
94903
  }
94661
- /**
94662
- * Parse and merge the two variants in a conflicted lockfile.
94663
- */
94664
- function parseWithConflict(str, fileLoc) {
94665
- const variants = extractConflictVariants(str);
94904
+ return [variants[0].join("\n"), variants[1].join("\n")];
94905
+ }
94906
+ /**
94907
+ * Check if a lockfile has merge conflicts.
94908
+ */
94909
+ function hasMergeConflicts(str) {
94910
+ return str.includes(MERGE_CONFLICT_START) && str.includes(MERGE_CONFLICT_SEP) && str.includes(MERGE_CONFLICT_END);
94911
+ }
94912
+ /**
94913
+ * Parse the lockfile.
94914
+ */
94915
+ function parse$1(str, fileLoc) {
94916
+ const parser = new Parser$1(str, fileLoc);
94917
+ parser.next();
94918
+ if (!fileLoc.endsWith(`.yml`)) try {
94919
+ return parser.parse();
94920
+ } catch (error1) {
94666
94921
  try {
94667
- return {
94668
- type: "merge",
94669
- object: Object.assign({}, parse(variants[0], fileLoc), parse(variants[1], fileLoc))
94670
- };
94671
- } catch (err) {
94672
- if (err instanceof SyntaxError) return {
94673
- type: "conflict",
94674
- object: {}
94675
- };
94676
- else throw err;
94922
+ return import_Yaml.default.parse(str);
94923
+ } catch (error2) {
94924
+ throw error1;
94677
94925
  }
94678
94926
  }
94679
- function _default(str, fileLoc = "lockfile") {
94680
- str = (0, _stripBom.default)(str);
94681
- return hasMergeConflicts(str) ? parseWithConflict(str, fileLoc) : {
94682
- type: "success",
94683
- object: parse(str, fileLoc)
94927
+ else {
94928
+ const result = import_Yaml.default.parse(str);
94929
+ if (typeof result === "object") return result;
94930
+ else return {};
94931
+ }
94932
+ }
94933
+ /**
94934
+ * Parse and merge the two variants in a conflicted lockfile.
94935
+ */
94936
+ function parseWithConflict(str, fileLoc) {
94937
+ const variants = extractConflictVariants(str);
94938
+ try {
94939
+ return {
94940
+ type: "merge",
94941
+ object: Object.assign({}, parse$1(variants[0], fileLoc), parse$1(variants[1], fileLoc))
94684
94942
  };
94943
+ } catch (err) {
94944
+ if (err instanceof SyntaxError) return {
94945
+ type: "conflict",
94946
+ object: {}
94947
+ };
94948
+ else throw err;
94685
94949
  }
94686
- }));
94950
+ }
94951
+ function parse_default(str, fileLoc = "lockfile") {
94952
+ str = stripBom(str);
94953
+ return hasMergeConflicts(str) ? parseWithConflict(str, fileLoc) : {
94954
+ type: "success",
94955
+ object: parse$1(str, fileLoc)
94956
+ };
94957
+ }
94687
94958
 
94688
94959
  //#endregion
94689
- //#region yarn/index.js
94690
- var require_yarn = /* @__PURE__ */ __commonJSMin(((exports, module) => {
94691
- const parse = require_parse().default;
94692
- module.exports = { parseYarnLock: (str, filename) => parse(str, filename).object };
94693
- }));
94960
+ //#region yarn/index.ts
94961
+ /**
94962
+ * @param {string} str the yarn.lock contents
94963
+ * @param {string} [filename] name to report in parse errors
94964
+ * @returns {object} the lockfile as a map of `name@spec` to its resolution
94965
+ */
94966
+ function parseYarnLock(str, filename) {
94967
+ return parse_default(str, filename).object;
94968
+ }
94694
94969
 
94695
94970
  //#endregion
94696
94971
  //#region lib/util/make-npm-env.ts
94697
- var import_yarn = require_yarn();
94698
94972
  /**
94699
94973
  * Helper functions for creating npm environment variables.
94700
94974
  * Matches npm 11+ minimal approach.
@@ -94715,8 +94989,9 @@ function initEnv(fromEnv, production) {
94715
94989
  }
94716
94990
  /**
94717
94991
  * Create npm_package_* and npm_config_* environment variables
94718
- * Matches npm 11's minimal approach - only adds npm_package_name, npm_package_version, npm_package_json
94719
- * and minimal npm_config_* variables
94992
+ * Matches npm 11's minimal approach - only adds npm_package_name and npm_package_version
94993
+ * (npm_package_json is added by the caller, which knows the manifest path) and minimal
94994
+ * npm_config_* variables
94720
94995
  *
94721
94996
  * @param {Object} data - Package.json or config object
94722
94997
  * @param {Object} opts - Options object
@@ -95114,8 +95389,31 @@ const readPkgJson$1 = (dir) => {
95114
95389
  return {};
95115
95390
  });
95116
95391
  };
95117
- const fynInstalledDir = Path.dirname(optionalRequire.resolve("../package.json"));
95118
- const fynCli = createRequire(Path.join(fynInstalledDir, "_")).resolve("./bin/fyn.js");
95392
+ let fynCliPath;
95393
+ const findFynCli = () => {
95394
+ if (fynCliPath) return fynCliPath;
95395
+ const candidates = [];
95396
+ const resolveFrom = (dir, spec) => {
95397
+ try {
95398
+ return createRequire(Path.join(dir, "_")).resolve(spec);
95399
+ } catch {
95400
+ return;
95401
+ }
95402
+ };
95403
+ const ownDir = optionalRequire.resolve("../package.json");
95404
+ if (ownDir) candidates.push(Path.dirname(ownDir));
95405
+ const asDep = resolveFrom(Path.dirname(fileURLToPath(import.meta.url)), "fyn/package.json");
95406
+ if (asDep) candidates.push(Path.dirname(asDep));
95407
+ for (const dir of candidates) {
95408
+ const cli = resolveFrom(dir, "./bin/fyn.mjs");
95409
+ if (cli) {
95410
+ fynCliPath = cli;
95411
+ return cli;
95412
+ }
95413
+ }
95414
+ fynCliPath = Path.join(candidates[0] || process.cwd(), "bin", "fyn.mjs");
95415
+ return fynCliPath;
95416
+ };
95119
95417
  const ONE_MB = 1048576;
95120
95418
  var LifecycleScripts = class {
95121
95419
  _fyn;
@@ -95139,7 +95437,8 @@ var LifecycleScripts = class {
95139
95437
  if (this._appDir) xsh.envPath.addToFront(Path.join(this._appDir, "node_modules/.bin"), env);
95140
95438
  xsh.envPath.addToFront(Path.join(this._pkgDir, "node_modules/.bin"), env);
95141
95439
  env.npm_node_execpath = env.NODE = env.NODE || process.execPath;
95142
- env.npm_execpath = fynCli;
95440
+ env.npm_execpath = findFynCli();
95441
+ env.npm_package_json = Path.join(this._pkgDir, "package.json");
95143
95442
  env.INIT_CWD = this._fyn.initCwd || this._fyn.cwd || process.cwd();
95144
95443
  return env;
95145
95444
  }
@@ -95170,7 +95469,11 @@ var LifecycleScripts = class {
95170
95469
  const scriptName = chalk.magenta(name);
95171
95470
  const script = `"${chalk.cyan(scriptCommand)}"`;
95172
95471
  const pkgDir = log_format_default.pkgPath(this._pkg.name, this._pkgDir.replace(this._fyn.cwd || "", "."));
95173
- const env = this.makeEnv({ PWD: this._pkgDir });
95472
+ const env = this.makeEnv({
95473
+ PWD: this._pkgDir,
95474
+ npm_lifecycle_event: name,
95475
+ npm_lifecycle_script: scriptCommand
95476
+ });
95174
95477
  logger$1.verbose(`running npm script '${scriptName}' of ${dimPkgName}: ${script} - at dir ${pkgDir}`);
95175
95478
  const child = xsh.exec({
95176
95479
  silent,
@@ -95416,8 +95719,6 @@ var PkgDepLinker = class {
95416
95719
  //#endregion
95417
95720
  //#region lib/pkg-opt-resolver.ts
95418
95721
  const { readPkgJson } = fyntil;
95419
- /** `optFailed` value meaning the package's os/cpu did not match the machine that checked it */
95420
- const OPT_FAILED_PLATFORM = 3;
95421
95722
  /**
95422
95723
  * Decide what a lockfile's `optFailed` record means for the machine running *this* install.
95423
95724
  *
@@ -95446,6 +95747,7 @@ var PkgOptResolver = class {
95446
95747
  _resolving;
95447
95748
  _failedChecks;
95448
95749
  _failedPkgs;
95750
+ /** assigned by `PkgDepResolver` right after it is constructed, so not private (FJM-154) */
95449
95751
  _depResolver;
95450
95752
  _inflights;
95451
95753
  _fyn;
@@ -95512,7 +95814,7 @@ var PkgOptResolver = class {
95512
95814
  version,
95513
95815
  top: !import_lodash_min.default.get(item, ["parent", "depth"])
95514
95816
  };
95515
- const policy = evaluateScriptPolicy(optDepInfo, this._fyn.allowScripts, { allowTopLevel: this._fyn.allowTopLevelScripts });
95817
+ const policy = evaluateScriptPolicy(optDepInfo, this._fyn.allowScripts, this._fyn.scriptPolicyOptions);
95516
95818
  return {
95517
95819
  allowed: isScriptAllowed(policy, "preinstall"),
95518
95820
  policy
@@ -95798,7 +96100,7 @@ var LocalPkgBuilder = class {
95798
96100
  });
95799
96101
  const cliFynJs = Path.join(__dirname, "../cli/fyn.js");
95800
96102
  if (await file_ops_default.exists(cliFynJs)) this._fynJs = cliFynJs;
95801
- else this._fynJs = Path.join(__dirname, "../bin/fyn.js");
96103
+ else this._fynJs = Path.join(__dirname, "../bin/fyn.mjs");
95802
96104
  const { localsByDepth } = this._options;
95803
96105
  this._promiseQ.on("doneItem", (data) => {
95804
96106
  this._waitItems[data.item.fullPath].resolve({});
@@ -96341,7 +96643,7 @@ async function syncLocalExports(options) {
96341
96643
  const createLock$1 = Util.promisify(import_lockfile.lock);
96342
96644
  const unlock$1 = Util.promisify(import_lockfile.unlock);
96343
96645
  const { posixify } = fyntil;
96344
- var Fyn = class {
96646
+ var Fyn = class Fyn {
96345
96647
  _cliSource;
96346
96648
  _shownMissingFiles;
96347
96649
  _options;
@@ -96356,9 +96658,12 @@ var Fyn = class {
96356
96658
  _pkgFyn;
96357
96659
  _pkgSrcMgr;
96358
96660
  _depLocker;
96661
+ /** set in `resolveDependencies`, before the collaborators that read it exist */
96359
96662
  _distFetcher;
96663
+ /** set in `resolveDependencies`, before the collaborators that read it exist */
96360
96664
  _depResolver;
96361
96665
  _optResolver;
96666
+ /** set in `resolveDependencies`, before any collaborator that reads it exists */
96362
96667
  _data;
96363
96668
  _central;
96364
96669
  _npmLockData;
@@ -96375,9 +96680,14 @@ var Fyn = class {
96375
96680
  _npmConfigEnv;
96376
96681
  _shortPkgDir;
96377
96682
  _allowScripts;
96683
+ _denyScripts;
96378
96684
  _allowTopLevelScripts;
96379
96685
  _enforceRegistryDeps;
96380
- /** Local packages with nested dependencies */
96686
+ _scriptPolicy;
96687
+ _reviewLocalPackages;
96688
+ _blockedScripts;
96689
+ _pendingScripts;
96690
+ /** Local packages with nested dependencies - written by the dep linker, never read back */
96381
96691
  localPkgWithNestedDep;
96382
96692
  constructor({ opts = {}, _cliSource = {}, _fynpo = true }) {
96383
96693
  this._cliSource = { ..._cliSource };
@@ -96444,7 +96754,7 @@ var Fyn = class {
96444
96754
  if (file_ops_default.existsSync(yarnLockName)) {
96445
96755
  logger$1.info("Reading yarn.lock");
96446
96756
  const yarnLockData = await file_ops_default.readFile(yarnLockName, "utf-8");
96447
- this._yarnLock = (0, import_yarn.parseYarnLock)(yarnLockData);
96757
+ this._yarnLock = parseYarnLock(yarnLockData);
96448
96758
  }
96449
96759
  return false;
96450
96760
  }
@@ -96529,15 +96839,60 @@ var Fyn = class {
96529
96839
  if (this._fynpo?.config) return this._fynpo.dir === this._cwd;
96530
96840
  return false;
96531
96841
  }
96842
+ /**
96843
+ * Options a fynpo config must never supply. `cwd`/`initCwd` are where fyn was
96844
+ * invoked; a monorepo config that relocated the install would be changing
96845
+ * which package.json is being installed.
96846
+ */
96847
+ static FYNPO_OPTIONS_DENY = ["cwd", "initCwd"];
96848
+ /**
96849
+ * Options with precedence rules of their own - allowlists union across scopes
96850
+ * with a denial winning at any level, the blacklist only ever unions, and the
96851
+ * policy mode may only be tightened by a package. The `allowScripts` /
96852
+ * `denyScripts` / `scriptPolicy` / `reviewLocalPackages` getters read the
96853
+ * fynpo config directly, so the generic merge must leave these alone rather
96854
+ * than flattening them here.
96855
+ */
96856
+ static FYNPO_OPTIONS_OWN_MERGE = [
96857
+ "allowScripts",
96858
+ "denyScripts",
96859
+ "allowTopLevelScripts",
96860
+ "scriptPolicy",
96861
+ "reviewLocalPackages"
96862
+ ];
96863
+ /**
96864
+ * Merge the monorepo's `fyn.options` (from fynpo.json / fynpo.config.js) into
96865
+ * this instance's options.
96866
+ *
96867
+ * Precedence is CLI > rc > fynpo config: `_cliSource[key]` records where each
96868
+ * option came from, and anything other than `"default"` means the user asked
96869
+ * for that value, so the monorepo config must not override it.
96870
+ */
96871
+ mergeFynpoOptions() {
96872
+ const fynpoOptions = import_lodash_min.default.get(this, [
96873
+ "_fynpo",
96874
+ "config",
96875
+ "fyn",
96876
+ "options"
96877
+ ]);
96878
+ if (import_lodash_min.default.isEmpty(fynpoOptions)) return;
96879
+ for (const key of Object.keys(fynpoOptions)) {
96880
+ if (Fyn.FYNPO_OPTIONS_DENY.includes(key) || Fyn.FYNPO_OPTIONS_OWN_MERGE.includes(key)) continue;
96881
+ const source = this._cliSource[key];
96882
+ if (source && source !== "default") {
96883
+ logger$1.debug(`fynpo config option ${key} ignored - already set by ${source}`);
96884
+ continue;
96885
+ }
96886
+ const value = fynpoOptions[key];
96887
+ const existing = this._options[key];
96888
+ this._options[key] = import_lodash_min.default.isPlainObject(value) && import_lodash_min.default.isPlainObject(existing) ? import_lodash_min.default.merge({}, value, existing) : value;
96889
+ }
96890
+ logger$1.debug("applied fynpo fyn.options", JSON.stringify(fynpoOptions));
96891
+ }
96532
96892
  async _initializePkg() {
96533
96893
  if (!this._fynpo) {
96534
96894
  this._fynpo = await fyntil.loadFynpo(this._cwd);
96535
- const savedCwd = this._options.cwd;
96536
- import_lodash_min.default.merge(this._options, import_lodash_min.default.get(this, "_fynpo.fyn.options"));
96537
- if (savedCwd) {
96538
- this._options.cwd = savedCwd;
96539
- this._cwd = savedCwd;
96540
- }
96895
+ this.mergeFynpoOptions();
96541
96896
  }
96542
96897
  this.checkLayoutOption();
96543
96898
  if (!this._pkg) {
@@ -96571,6 +96926,7 @@ var Fyn = class {
96571
96926
  }
96572
96927
  }
96573
96928
  this.checkProductionMode();
96929
+ logger$1.debug("install script policy", this.scriptPolicy);
96574
96930
  let fynpoNpmRun;
96575
96931
  if (this._fynpo?.config) {
96576
96932
  if (this._fynpo.graph?.getPackageAtDir(this._cwd)) {
@@ -96755,7 +97111,9 @@ var Fyn = class {
96755
97111
  centralDir,
96756
97112
  production: this.production,
96757
97113
  layout: this._options.layout,
96758
- shortPkgDir: this._shortPkgDir
97114
+ shortPkgDir: this._shortPkgDir,
97115
+ blockedScripts: this._blockedScripts || this._installConfig.blockedScripts || [],
97116
+ pendingScripts: this._pendingScripts || this._installConfig.pendingScripts || []
96759
97117
  };
96760
97118
  await file_ops_default.writeFile(filename, `${JSON.stringify(outputConfig, null, 2)}\n`);
96761
97119
  } catch (err) {
@@ -96774,7 +97132,8 @@ var Fyn = class {
96774
97132
  const options = _options || this._options;
96775
97133
  if (options.pkgFile) {
96776
97134
  let pkgFile;
96777
- if (options.pkgFile === "package.json") {
97135
+ const cwdIsExplicit = Boolean(this._cliSource.cwd) && this._cliSource.cwd !== "default";
97136
+ if (options.pkgFile === "package.json" && !cwdIsExplicit) {
96778
97137
  let foundDir = null;
96779
97138
  pathUpEach(this._cwd, (path) => {
96780
97139
  const testPath = Path.join(path, "package.json");
@@ -96796,6 +97155,7 @@ var Fyn = class {
96796
97155
  } catch (err) {
96797
97156
  logger$1.error("failed to read package.json file", pkgFile);
96798
97157
  logger$1.error(err.message);
97158
+ if (cwdIsExplicit) logger$1.error(`cwd was set explicitly by ${this._cliSource.cwd}, so fyn did not search parent directories for a package.json`);
96799
97159
  fyntil.exit(err);
96800
97160
  }
96801
97161
  const pkgFyn = await this.loadPkgFyn();
@@ -96857,14 +97217,112 @@ var Fyn = class {
96857
97217
  get showDeprecated() {
96858
97218
  return this._options.showDeprecated && "show-deprecated";
96859
97219
  }
97220
+ /**
97221
+ * The monorepo's `fyn.options` block, when installing inside a fynpo repo.
97222
+ * This is the repo-wide baseline for the script policy - one allowlist,
97223
+ * reviewed once, instead of a copy in every package.
97224
+ *
97225
+ * @returns {object} the fynpo `fyn.options`, or an empty object
97226
+ */
97227
+ get fynpoFynOptions() {
97228
+ return import_lodash_min.default.get(this, [
97229
+ "_fynpo",
97230
+ "config",
97231
+ "fyn",
97232
+ "options"
97233
+ ]) || {};
97234
+ }
96860
97235
  get allowScripts() {
96861
- if (this._allowScripts === void 0 && this._pkg) this._allowScripts = import_lodash_min.default.get(this._pkg, ["fyn", "allowScripts"]) || {};
97236
+ if (this._allowScripts === void 0 && this._pkg) this._allowScripts = mergeAllowScripts(this.fynpoFynOptions.allowScripts, import_lodash_min.default.get(this._pkg, ["fyn", "allowScripts"]), this._options.allowScripts);
96862
97237
  return this._allowScripts || {};
96863
97238
  }
97239
+ get denyScripts() {
97240
+ if (this._denyScripts === void 0 && this._pkg) this._denyScripts = mergeAllowScripts(this.fynpoFynOptions.denyScripts, import_lodash_min.default.get(this._pkg, ["fyn", "denyScripts"]), this._options.denyScripts);
97241
+ return this._denyScripts || {};
97242
+ }
96864
97243
  get allowTopLevelScripts() {
96865
- if (this._allowTopLevelScripts === void 0 && this._pkg) this._allowTopLevelScripts = import_lodash_min.default.get(this._pkg, ["fyn", "allowTopLevelScripts"]) || false;
97244
+ if (this._allowTopLevelScripts === void 0 && this._pkg) {
97245
+ const values = [
97246
+ this._options.allowTopLevelScripts,
97247
+ import_lodash_min.default.get(this._pkg, ["fyn", "allowTopLevelScripts"]),
97248
+ this.fynpoFynOptions.allowTopLevelScripts
97249
+ ];
97250
+ this._allowTopLevelScripts = values.find((v) => v !== void 0) || false;
97251
+ }
96866
97252
  return this._allowTopLevelScripts || false;
96867
97253
  }
97254
+ get scriptPolicy() {
97255
+ if (this._scriptPolicy === void 0) {
97256
+ const cliOpt = this._options.scriptPolicy;
97257
+ if (cliOpt) this._scriptPolicy = normalizeScriptPolicy(cliOpt);
97258
+ else if (this._pkg) this._scriptPolicy = strictestScriptPolicy(normalizeScriptPolicyIfSet(this.fynpoFynOptions.scriptPolicy), normalizeScriptPolicyIfSet(import_lodash_min.default.get(this._pkg, ["fyn", "scriptPolicy"])));
97259
+ }
97260
+ return this._scriptPolicy || "review";
97261
+ }
97262
+ get reviewLocalPackages() {
97263
+ if (this._reviewLocalPackages === void 0 && this._pkg) this._reviewLocalPackages = Boolean(this._options.reviewLocalPackages || import_lodash_min.default.get(this._pkg, ["fyn", "reviewLocalPackages"]) || this.fynpoFynOptions.reviewLocalPackages);
97264
+ return Boolean(this._reviewLocalPackages);
97265
+ }
97266
+ /**
97267
+ * The options every lifecycle-script policy evaluation needs, so the two
97268
+ * enforcement points (installer and optional-dep probe) cannot drift.
97269
+ *
97270
+ * @returns {object} options for `evaluateScriptPolicy`
97271
+ */
97272
+ get scriptPolicyOptions() {
97273
+ return {
97274
+ mode: this.scriptPolicy,
97275
+ allowTopLevel: this.allowTopLevelScripts,
97276
+ reviewLocalPackages: this.reviewLocalPackages,
97277
+ denyScripts: this.denyScripts
97278
+ };
97279
+ }
97280
+ get allowScriptsPin() {
97281
+ const opt = this._options.allowScriptsPin;
97282
+ return opt === void 0 ? true : Boolean(opt);
97283
+ }
97284
+ /**
97285
+ * Forget the merged allowlist, so it is rebuilt from disk. Used after the
97286
+ * install-time review prompt writes new approvals.
97287
+ *
97288
+ * @returns {void}
97289
+ */
97290
+ resetAllowScripts() {
97291
+ this._allowScripts = void 0;
97292
+ this._denyScripts = void 0;
97293
+ this._pkg = this._pkg && { ...this._pkg };
97294
+ }
97295
+ get allowScriptsPending() {
97296
+ return Boolean(this._options.allowScriptsPending);
97297
+ }
97298
+ /**
97299
+ * Record the packages whose install scripts were blocked, and those that
97300
+ * would need approval under `"review"`, so they are saved with the install
97301
+ * config and `fyn install-scripts ls` can list them without re-resolving the
97302
+ * tree.
97303
+ *
97304
+ * @param {object[]} blocked blocked-scripts records
97305
+ * @param {object[]} [pending] would-be-blocked-under-review records
97306
+ * @returns {void}
97307
+ */
97308
+ setBlockedScripts(blocked, pending) {
97309
+ this._blockedScripts = blocked || [];
97310
+ if (pending !== void 0) this._pendingScripts = pending;
97311
+ }
97312
+ /**
97313
+ * @returns {object[]} packages whose install scripts were blocked - from this
97314
+ * run when there was one, else what the last install recorded
97315
+ */
97316
+ get blockedScripts() {
97317
+ return this._blockedScripts || this._installConfig.blockedScripts || [];
97318
+ }
97319
+ /**
97320
+ * @returns {object[]} packages that would need approval under `"review"` -
97321
+ * only populated when the install ran with `--allow-scripts-pending`
97322
+ */
97323
+ get pendingScripts() {
97324
+ return this._pendingScripts || this._installConfig.pendingScripts || [];
97325
+ }
96868
97326
  get enforceRegistryDeps() {
96869
97327
  const cliOpt = this._options.enforceRegistryDeps;
96870
97328
  if (cliOpt !== void 0) return Boolean(cliOpt);
@@ -97517,6 +97975,696 @@ var PkgBinLinker$1 = class extends PkgBinLinkerBase {
97517
97975
  /* istanbul ignore next */
97518
97976
  const PkgBinLinker = process.platform === "win32" ? PkgBinLinkerWin32 : PkgBinLinker$1;
97519
97977
 
97978
+ //#endregion
97979
+ //#region lib/util/script-policy-report.ts
97980
+ /**
97981
+ * Build the record of one package whose install scripts were blocked.
97982
+ *
97983
+ * @param {object} depInfo resolved package data
97984
+ * @param {object} policy result of `evaluateScriptPolicy`
97985
+ * @param {string[]} blocked the lifecycle scripts that were skipped
97986
+ * @returns {object} a blocked-scripts record
97987
+ */
97988
+ function makeBlockedRecord(depInfo, policy, blocked) {
97989
+ return {
97990
+ name: depInfo.name,
97991
+ version: depInfo.version,
97992
+ key: policy.key,
97993
+ scripts: [...blocked],
97994
+ urlType: policy.urlType,
97995
+ reason: policy.reason,
97996
+ topLevel: Boolean(policy.topLevel),
97997
+ local: Boolean(policy.local)
97998
+ };
97999
+ }
98000
+ /**
98001
+ * The allowScripts key to write for a record: always the bare package name.
98002
+ *
98003
+ * The constraints live in the value, so one package is one entry however many
98004
+ * versions and scripts it accumulates - see {@link makeAllowEntry}.
98005
+ *
98006
+ * @param {object} record a blocked-scripts record
98007
+ * @returns {string} the allowScripts key
98008
+ */
98009
+ function allowScriptsKey(record) {
98010
+ return record.name;
98011
+ }
98012
+ /**
98013
+ * The allowScripts value fyn writes: `{ semver, scripts }`, with either field
98014
+ * omitted when it would not narrow anything.
98015
+ *
98016
+ * Pinned (the default) `semver` is a caret range of the reviewed version, so
98017
+ * the approval follows that release line and a jump past it comes back for
98018
+ * review. Unpinned there is no `semver`, which covers every version. `scripts`
98019
+ * is omitted when every install script is approved.
98020
+ *
98021
+ * @param {object} record a blocked-scripts record
98022
+ * @param {boolean} [pin] whether to scope the approval to the reviewed version
98023
+ * @returns {object} the allowScripts value
98024
+ */
98025
+ function makeAllowEntry(record, pin = true) {
98026
+ const entry = {};
98027
+ if (pin && record.version) entry.semver = caretRange(record.version);
98028
+ const scripts = normalizeScriptsValue(record.scripts);
98029
+ if (scripts.length > 0 && scripts[0] !== "*") entry.scripts = scripts;
98030
+ return entry;
98031
+ }
98032
+ /**
98033
+ * Fold a record into an entry a previous approval already wrote.
98034
+ *
98035
+ * One key per package means a second version widens `semver` rather than adding
98036
+ * a near-duplicate key. When the two approvals cover different scripts the
98037
+ * union is taken - with a single entry there is nowhere to record "these
98038
+ * scripts for that range, those for this one".
98039
+ *
98040
+ * @param {*} existing the value already in the map, any accepted form
98041
+ * @param {object} record a blocked-scripts record
98042
+ * @param {boolean} [pin] whether to scope the approval to the reviewed version
98043
+ * @returns {object} the merged entry
98044
+ */
98045
+ function mergeAllowEntry(existing, record, pin = true) {
98046
+ const added = makeAllowEntry(record, pin);
98047
+ if (existing === void 0) return added;
98048
+ const prior = toAllowEntry(existing);
98049
+ const entry = {};
98050
+ if (prior.semver !== void 0 && added.semver !== void 0) entry.semver = addVersionToRange(prior.semver, record.version);
98051
+ if (prior.scripts !== void 0 && added.scripts !== void 0) {
98052
+ const scripts = normalizeScriptsValue([...prior.scripts, ...added.scripts]);
98053
+ if (scripts[0] !== "*") entry.scripts = scripts;
98054
+ }
98055
+ return entry;
98056
+ }
98057
+ /**
98058
+ * Read any accepted allowScripts value as the object form, so a map written by
98059
+ * hand - or by npm - can be merged into.
98060
+ *
98061
+ * @param {*} value an allowScripts value
98062
+ * @returns {object} the value as `{ semver?, scripts? }`
98063
+ */
98064
+ function toAllowEntry(value) {
98065
+ if (value && typeof value === "object" && !Array.isArray(value)) return value;
98066
+ if (value === true || value === "*" || value === void 0) return {};
98067
+ const strings = (Array.isArray(value) ? value : [value]).filter((item) => typeof item === "string");
98068
+ if (strings.length === 1 && !LIFECYCLE_SCRIPTS.includes(strings[0].toLowerCase())) {
98069
+ const range = asRange(strings[0]);
98070
+ if (range) return { semver: range };
98071
+ }
98072
+ const scripts = normalizeScriptsValue(strings);
98073
+ return scripts[0] === "*" ? {} : { scripts };
98074
+ }
98075
+ /**
98076
+ * The range an approval of one reviewed version covers.
98077
+ *
98078
+ * @param {string} version the resolved version
98079
+ * @returns {string} a caret range, or the version itself when it is not semver
98080
+ */
98081
+ function caretRange(version) {
98082
+ const clean = unlocalify(String(version));
98083
+ return asRange(`^${clean}`) ? `^${clean}` : clean;
98084
+ }
98085
+ /**
98086
+ * Widen a range so it also covers a version, leaving it alone when it already
98087
+ * does. This is how a second approval of the same package becomes
98088
+ * `pkg@^1.2.3 || ^2.0.0` instead of a second key.
98089
+ *
98090
+ * @param {string} range the existing range
98091
+ * @param {string} version the version to cover
98092
+ * @returns {string} the range
98093
+ */
98094
+ function addVersionToRange(range, version) {
98095
+ const parts = splitRange(range);
98096
+ const added = caretRange(version);
98097
+ if (parts.includes(added) || parts.some((part) => satisfies(version, part))) return parts.join(" || ");
98098
+ return [...parts, added].join(" || ");
98099
+ }
98100
+ /**
98101
+ * Normalize a list of script names into the value fyn writes: `["*"]` when it
98102
+ * covers every install script, otherwise the names in lifecycle order.
98103
+ *
98104
+ * @param {(string[]|string|boolean)} scripts script names, or a wildcard
98105
+ * @returns {string[]} the value to write
98106
+ */
98107
+ function normalizeScriptsValue(scripts) {
98108
+ const names = new Set((Array.isArray(scripts) ? scripts : scripts === void 0 ? [] : [scripts]).map((name) => String(name).toLowerCase()));
98109
+ if (names.has("*") || names.has("true") || LIFECYCLE_SCRIPTS.every((name) => names.has(name))) return ["*"];
98110
+ const known = LIFECYCLE_SCRIPTS.filter((name) => names.has(name));
98111
+ const extra = [...names].filter((name) => !LIFECYCLE_SCRIPTS.includes(name));
98112
+ return [...known, ...extra];
98113
+ }
98114
+ /**
98115
+ * Build the `fyn.allowScripts` fragment that would let these packages run the
98116
+ * scripts they were blocked from.
98117
+ *
98118
+ * fyn's per-script form is kept rather than npm's blanket `"pkg": "1.2.3"`:
98119
+ * it approves the same version but only the scripts the package actually has,
98120
+ * so a later release that adds a `preinstall` still comes back for review.
98121
+ *
98122
+ * @param {object[]} records blocked-scripts records
98123
+ * @param {object} [options] options
98124
+ * @param {boolean} [options.pin] pin each approval to the resolved version
98125
+ * @returns {object} an allowScripts map
98126
+ */
98127
+ function buildAllowScriptsPatch(records, { pin = true } = {}) {
98128
+ const patch = {};
98129
+ for (const record of records) {
98130
+ const key = allowScriptsKey(record);
98131
+ patch[key] = mergeAllowEntry(patch[key], record, pin);
98132
+ }
98133
+ return patch;
98134
+ }
98135
+ /**
98136
+ * @param {object[]} records blocked-scripts records
98137
+ * @returns {object[]} the records, deduplicated by name@version and with their
98138
+ * script lists merged
98139
+ */
98140
+ function dedupeBlockedRecords(records) {
98141
+ const byId = /* @__PURE__ */ new Map();
98142
+ for (const record of records) {
98143
+ const id = `${record.name}@${record.version}`;
98144
+ const existing = byId.get(id);
98145
+ if (existing) existing.scripts = [.../* @__PURE__ */ new Set([...existing.scripts, ...record.scripts])];
98146
+ else byId.set(id, {
98147
+ ...record,
98148
+ scripts: [...record.scripts]
98149
+ });
98150
+ }
98151
+ return [...byId.values()].sort((a, b) => a.name.localeCompare(b.name));
98152
+ }
98153
+ /**
98154
+ * Why a package was blocked, in a few words for the summary table.
98155
+ *
98156
+ * @param {object} record a blocked-scripts record
98157
+ * @returns {string} the reason
98158
+ */
98159
+ function blockedReasonText(record) {
98160
+ switch (record.reason) {
98161
+ case "off": return "scriptPolicy is off";
98162
+ case "denied": return "denied (denyScripts / allowScripts false)";
98163
+ case "review": return "not reviewed";
98164
+ default: return `non-registry (${record.urlType})`;
98165
+ }
98166
+ }
98167
+ /**
98168
+ * Render the end-of-install summary for the packages that were blocked.
98169
+ *
98170
+ * @param {object[]} records blocked-scripts records
98171
+ * @param {object} [options] options
98172
+ * @param {string} [options.mode] the scriptPolicy mode in effect
98173
+ * @param {boolean} [options.pin] pin suggested approvals to the resolved version
98174
+ * @param {boolean} [options.anyTopLevel] whether any record is a direct dep
98175
+ * @returns {string[]} lines to log, empty when nothing was blocked
98176
+ */
98177
+ function formatBlockedScriptsSummary(records, { mode = "source", pin = true } = {}) {
98178
+ const blocked = dedupeBlockedRecords(records);
98179
+ if (blocked.length === 0) return [];
98180
+ const lines = [`${chalk.black.bgYellow("WARN")} ${chalk.magenta("install scripts blocked")} ` + chalk.yellow(`${blocked.length} package${blocked.length > 1 ? "s" : ""} did not run their install scripts`)];
98181
+ const width = blocked.reduce((w, r) => Math.max(w, `${r.name}@${r.version}`.length), 0);
98182
+ for (const record of blocked) {
98183
+ const id = `${record.name}@${record.version}`.padEnd(width);
98184
+ lines.push(` ${chalk.cyan(id)} ${chalk.yellow(record.scripts.join(", "))} ${chalk.gray(blockedReasonText(record))}`);
98185
+ }
98186
+ if (mode !== "off") {
98187
+ const patch = buildAllowScriptsPatch(blocked.filter((r) => r.reason !== "denied"), { pin });
98188
+ if (Object.keys(patch).length > 0) lines.push(chalk.blue(" To allow them, add to package.json:"), ` ${chalk.cyan(JSON.stringify({ fyn: { allowScripts: patch } }))}`, chalk.blue(` Or run: ${chalk.cyan("fyn install-scripts approve <package>")}`));
98189
+ if (blocked.some((r) => r.topLevel && r.reason !== "denied")) lines.push(chalk.blue(" Direct dependencies can also be trusted as a group with:"), ` ${chalk.cyan(`{ "fyn": { "allowTopLevelScripts": true } }`)}`);
98190
+ }
98191
+ return lines;
98192
+ }
98193
+ /**
98194
+ * Render the summary for packages that would need approval under `"review"`,
98195
+ * shown with `--allow-scripts-pending` while the install still runs under the
98196
+ * mode in effect. This is what switching to `"review"` would cost.
98197
+ *
98198
+ * @param {object[]} records pending-review records
98199
+ * @param {object} [options] options
98200
+ * @param {string} [options.mode] the scriptPolicy mode in effect
98201
+ * @param {boolean} [options.pin] pin suggested approvals to the resolved version
98202
+ * @returns {string[]} lines to log, empty when nothing is pending
98203
+ */
98204
+ function formatPendingScriptsSummary(records, { mode = "source", pin = true } = {}) {
98205
+ const pending = dedupeBlockedRecords(records);
98206
+ if (pending.length === 0) return [];
98207
+ const lines = [chalk.blue(`${pending.length} package${pending.length > 1 ? "s" : ""} would need approval under ${chalk.cyan("scriptPolicy: \"review\"")} (currently ${chalk.cyan(mode)})`)];
98208
+ const width = pending.reduce((w, r) => Math.max(w, `${r.name}@${r.version}`.length), 0);
98209
+ for (const record of pending) lines.push(` ${chalk.cyan(`${record.name}@${record.version}`.padEnd(width))} ` + chalk.yellow(record.scripts.join(", ")));
98210
+ const patch = buildAllowScriptsPatch(pending.filter((r) => r.reason !== "denied"), { pin });
98211
+ if (Object.keys(patch).length > 0) lines.push(chalk.blue(" To approve them now:"), ` ${chalk.cyan(JSON.stringify({ fyn: { allowScripts: patch } }))}`);
98212
+ return lines;
98213
+ }
98214
+
98215
+ //#endregion
98216
+ //#region lib/install-scripts.ts
98217
+ /**
98218
+ * Split an allowScripts key into its package name and optional spec.
98219
+ *
98220
+ * Keys are `name`, `name@spec` or `@scope/name@spec`, so the separator is the
98221
+ * `@` after the scope, not the first one.
98222
+ *
98223
+ * @param {string} key an allowScripts key
98224
+ * @returns {{name:string, spec:(string|undefined)}} the parts
98225
+ */
98226
+ function parseAllowKey(key) {
98227
+ const at = key.indexOf("@", key.startsWith("@") ? 1 : 0);
98228
+ if (at < 0) return {
98229
+ name: key,
98230
+ spec: void 0
98231
+ };
98232
+ return {
98233
+ name: key.slice(0, at),
98234
+ spec: key.slice(at + 1)
98235
+ };
98236
+ }
98237
+ /**
98238
+ * Add approvals to an allowScripts map.
98239
+ *
98240
+ * A package already denied is left alone: a denial is a decision someone made,
98241
+ * and an approve - `--all` included - must not quietly undo it. Both spellings
98242
+ * of a denial count, the `denyScripts` list and a `false` in the map.
98243
+ *
98244
+ * One package is one entry, keyed by its bare name, so approving a second
98245
+ * version widens the entry's `semver` instead of adding a near-duplicate key.
98246
+ *
98247
+ * @param {object} allowScripts the current map
98248
+ * @param {object[]} records packages to approve, as blocked-scripts records
98249
+ * @param {object} [options] options
98250
+ * @param {boolean} [options.pin] scope each approval to the reviewed version
98251
+ * @param {string[]} [options.denyScripts] the configured deny list
98252
+ * @returns {{allowScripts:object, approved:string[], skipped:string[]}} result
98253
+ */
98254
+ function approveEntries(allowScripts, records, { pin = true, denyScripts = {} } = {}) {
98255
+ const updated = { ...allowScripts };
98256
+ const approved = [];
98257
+ const skipped = [];
98258
+ const denyNames = new Set(Object.keys(denyScripts || {}).map((key) => parseAllowKey(key).name));
98259
+ for (const record of records) {
98260
+ const key = allowScriptsKey(record);
98261
+ if (denyNames.has(record.name) || Object.keys(updated).some((k) => updated[k] === false && parseAllowKey(k).name === record.name)) {
98262
+ skipped.push(key);
98263
+ continue;
98264
+ }
98265
+ updated[key] = mergeAllowEntry(updated[key], record, pin);
98266
+ approved.push(key);
98267
+ }
98268
+ return {
98269
+ allowScripts: updated,
98270
+ approved,
98271
+ skipped
98272
+ };
98273
+ }
98274
+ /**
98275
+ * Record explicit denials.
98276
+ *
98277
+ * A denial is written against the bare package name so it covers every version,
98278
+ * with an empty entry - `{}` - meaning "every version, every script", the same
98279
+ * defaults the allowlist uses for an absent `semver` / `scripts`. A hand-edited
98280
+ * entry can narrow either.
98281
+ *
98282
+ * @param {object} denyScripts the current deny map
98283
+ * @param {string[]} names package names to deny
98284
+ * @returns {{denyScripts:object, denied:string[], already:string[]}} result
98285
+ */
98286
+ function denyEntries(denyScripts, names) {
98287
+ const updated = { ...denyScripts || {} };
98288
+ const denied = [];
98289
+ const already = [];
98290
+ for (const name of names) {
98291
+ const { name: pkgName } = parseAllowKey(name);
98292
+ if (updated[pkgName] !== void 0) {
98293
+ already.push(pkgName);
98294
+ continue;
98295
+ }
98296
+ updated[pkgName] = {};
98297
+ denied.push(pkgName);
98298
+ }
98299
+ return {
98300
+ denyScripts: updated,
98301
+ denied,
98302
+ already
98303
+ };
98304
+ }
98305
+ /**
98306
+ * Drop entries for packages that are no longer installed.
98307
+ *
98308
+ * @param {object} allowScripts the current map
98309
+ * @param {string[]} installedNames names of the installed packages
98310
+ * @returns {{allowScripts:object, removed:string[]}} result
98311
+ */
98312
+ function pruneEntries(allowScripts, installedNames) {
98313
+ const installed = new Set(installedNames);
98314
+ const updated = {};
98315
+ const removed = [];
98316
+ for (const key of Object.keys(allowScripts)) if (installed.has(parseAllowKey(key).name)) updated[key] = allowScripts[key];
98317
+ else removed.push(key);
98318
+ return {
98319
+ allowScripts: updated,
98320
+ removed
98321
+ };
98322
+ }
98323
+ /**
98324
+ * Match the packages named on the command line against the records an install
98325
+ * recorded, so an approval can be pinned to the version that was reviewed.
98326
+ *
98327
+ * @param {string[]} names package names, optionally `name@version`
98328
+ * @param {object[]} records blocked / pending records from the last install
98329
+ * @returns {{matched:object[], unknown:string[]}} the records to act on
98330
+ */
98331
+ function selectRecords(names, records) {
98332
+ const byName = /* @__PURE__ */ new Map();
98333
+ for (const record of records) if (!byName.has(record.name)) byName.set(record.name, record);
98334
+ const matched = [];
98335
+ const unknown = [];
98336
+ for (const arg of names) {
98337
+ const { name, spec } = parseAllowKey(arg);
98338
+ const record = byName.get(name);
98339
+ if (spec) matched.push({
98340
+ name,
98341
+ version: spec,
98342
+ scripts: record ? record.scripts : [
98343
+ "preinstall",
98344
+ "install",
98345
+ "postinstall"
98346
+ ]
98347
+ });
98348
+ else if (record) matched.push(record);
98349
+ else unknown.push(arg);
98350
+ }
98351
+ return {
98352
+ matched,
98353
+ unknown
98354
+ };
98355
+ }
98356
+ /**
98357
+ * Where an allowlist change should be written.
98358
+ *
98359
+ * In a fynpo repo the monorepo root is the default: one allowlist, reviewed
98360
+ * once. `--local` writes to the package's own package.json instead.
98361
+ *
98362
+ * @param {object} fyn the Fyn instance
98363
+ * @param {boolean} [local] force the package's own package.json
98364
+ * @returns {{file:string, fynpo:boolean}} the target
98365
+ */
98366
+ function resolveTarget(fyn, local = false) {
98367
+ const fynpoDir = import_lodash_min.default.get(fyn, ["_fynpo", "dir"]);
98368
+ if (!local && fynpoDir) return {
98369
+ file: Path.join(fynpoDir, "fynpo.json"),
98370
+ fynpo: true
98371
+ };
98372
+ return {
98373
+ file: Path.join(fyn.cwd, "package.json"),
98374
+ fynpo: false
98375
+ };
98376
+ }
98377
+ /**
98378
+ * Where one fyn option lives in a target file. A fynpo.json keeps fyn's options
98379
+ * under `fyn.options`, a package.json directly under `fyn`.
98380
+ *
98381
+ * @param {object} target from {@link resolveTarget}
98382
+ * @param {string} key the option name
98383
+ * @returns {string[]} the path into the file's JSON
98384
+ */
98385
+ function targetOptionPath(target, key) {
98386
+ return target.fynpo ? [
98387
+ "fyn",
98388
+ "options",
98389
+ key
98390
+ ] : ["fyn", key];
98391
+ }
98392
+ /**
98393
+ * Whether this run can stop and ask a person.
98394
+ *
98395
+ * @returns {boolean} true when there is a terminal and no CI environment
98396
+ */
98397
+ function canPrompt() {
98398
+ return Boolean(!import_ci_info.isCI && process.stdin.isTTY && process.stdout.isTTY);
98399
+ }
98400
+ /**
98401
+ * Ask a question on the terminal.
98402
+ *
98403
+ * @param {string} question the prompt
98404
+ * @returns {Promise<string>} the trimmed, lowercased answer
98405
+ */
98406
+ function ask(question) {
98407
+ const rl = readline.createInterface({
98408
+ input: process.stdin,
98409
+ output: process.stdout
98410
+ });
98411
+ return new Promise((resolve) => {
98412
+ rl.question(question, (answer) => {
98413
+ rl.close();
98414
+ resolve(String(answer).trim().toLowerCase());
98415
+ });
98416
+ });
98417
+ }
98418
+ /**
98419
+ * `fyn install-scripts` - list, approve, deny and prune install-script
98420
+ * approvals.
98421
+ */
98422
+ var InstallScripts = class {
98423
+ /**
98424
+ * @param {object} options options
98425
+ * @param {object} options.fyn the Fyn instance
98426
+ */
98427
+ constructor({ fyn }) {
98428
+ this._fyn = fyn;
98429
+ }
98430
+ /**
98431
+ * The packages awaiting review: what the last install blocked, plus what it
98432
+ * recorded as pending when run with `--allow-scripts-pending`.
98433
+ *
98434
+ * @returns {object[]} deduplicated records
98435
+ */
98436
+ get records() {
98437
+ return dedupeBlockedRecords([...this._fyn.blockedScripts, ...this._fyn.pendingScripts]);
98438
+ }
98439
+ /**
98440
+ * Read the script-policy lists from the file a change would be written to.
98441
+ *
98442
+ * The deny list is normalized on the way in, so an entry written by hand in
98443
+ * any of the accepted forms is still recognized as already denied.
98444
+ *
98445
+ * @param {object} target from {@link resolveTarget}
98446
+ * @returns {Promise<{json:object, allowScripts:object, denyScripts:object}>} the file contents
98447
+ */
98448
+ async readTarget(target) {
98449
+ let json = {};
98450
+ try {
98451
+ json = JSON.parse(await file_ops_default.readFile(target.file));
98452
+ } catch (err) {
98453
+ if (target.fynpo) throw new Error(`cannot read ${target.file} - the monorepo allowlist needs a fynpo.json. Use --local to write to this package's package.json instead.`);
98454
+ throw err;
98455
+ }
98456
+ return {
98457
+ json,
98458
+ allowScripts: import_lodash_min.default.get(json, targetOptionPath(target, "allowScripts")) || {},
98459
+ denyScripts: import_lodash_min.default.get(json, targetOptionPath(target, "denyScripts")) || {}
98460
+ };
98461
+ }
98462
+ /**
98463
+ * Write script-policy lists back to their file. Only the keys named in
98464
+ * `values` are touched, so approving does not rewrite the deny list.
98465
+ *
98466
+ * @param {object} target from {@link resolveTarget}
98467
+ * @param {object} read result of {@link readTarget}
98468
+ * @param {object} values the option values to write, keyed by option name
98469
+ * @returns {Promise<void>} nothing
98470
+ */
98471
+ async writeTarget(target, read, values) {
98472
+ for (const key of Object.keys(values)) import_lodash_min.default.set(read.json, targetOptionPath(target, key), values[key]);
98473
+ await file_ops_default.writeFile(target.file, `${JSON.stringify(read.json, null, 2)}\n`);
98474
+ logger$1.info(`updated ${chalk.cyan(target.file)}`);
98475
+ }
98476
+ /**
98477
+ * List the packages awaiting review.
98478
+ *
98479
+ * @param {object} [options] options
98480
+ * @param {boolean} [options.json] emit JSON instead of a table
98481
+ * @returns {Promise<object[]>} the records listed
98482
+ */
98483
+ async ls({ json = false } = {}) {
98484
+ const records = this.records;
98485
+ if (json) {
98486
+ process.stdout.write(`${JSON.stringify(records, null, 2)}\n`);
98487
+ return records;
98488
+ }
98489
+ if (records.length === 0) {
98490
+ logger$1.info(chalk.green("No packages are awaiting install-script review."));
98491
+ if (this._fyn.scriptPolicy !== "review") logger$1.info(`Run ${chalk.cyan("fyn install --allow-scripts-pending")} to see what ${chalk.cyan("scriptPolicy: \"review\"")} would ask you to approve.`);
98492
+ return records;
98493
+ }
98494
+ logger$1.info(`${records.length} package${records.length > 1 ? "s" : ""} awaiting install-script review:`);
98495
+ const width = records.reduce((w, r) => Math.max(w, `${r.name}@${r.version}`.length), 0);
98496
+ for (const record of records) logger$1.info(` ${chalk.cyan(`${record.name}@${record.version}`.padEnd(width))} ${chalk.yellow(record.scripts.join(", "))} ${chalk.gray(blockedReasonText(record))}`);
98497
+ logger$1.info(`Approve with ${chalk.cyan("fyn install-scripts approve <package>")}`);
98498
+ return records;
98499
+ }
98500
+ /**
98501
+ * Approve packages to run their install scripts.
98502
+ *
98503
+ * @param {string[]} names packages to approve, optionally `name@version`
98504
+ * @param {object} [options] options
98505
+ * @param {boolean} [options.all] approve everything awaiting review
98506
+ * @param {boolean} [options.local] write to this package's package.json
98507
+ * @returns {Promise<string[]>} the keys written
98508
+ */
98509
+ async approve(names = [], { all = false, local = false } = {}) {
98510
+ const pin = this._fyn.allowScriptsPin;
98511
+ let toApprove;
98512
+ if (all) toApprove = this.records;
98513
+ else {
98514
+ const { matched, unknown } = selectRecords(names, this.records);
98515
+ if (unknown.length > 0) {
98516
+ logger$1.error(`not awaiting review: ${unknown.join(", ")} - run ${chalk.cyan("fyn install-scripts ls")} to see what is, or name an explicit version`);
98517
+ return [];
98518
+ }
98519
+ toApprove = matched;
98520
+ }
98521
+ if (toApprove.length === 0) {
98522
+ logger$1.info("nothing to approve");
98523
+ return [];
98524
+ }
98525
+ const target = resolveTarget(this._fyn, local);
98526
+ const read = await this.readTarget(target);
98527
+ const { allowScripts, approved, skipped } = approveEntries(read.allowScripts, toApprove, {
98528
+ pin,
98529
+ denyScripts: {
98530
+ ...read.denyScripts || {},
98531
+ ...this._fyn.denyScripts || {}
98532
+ }
98533
+ });
98534
+ if (skipped.length > 0) logger$1.warn(`${chalk.magenta("denied, not approved")}: ${skipped.join(", ")} - remove the ${chalk.cyan("denyScripts")} entry, or the ${chalk.cyan("allowScripts")} ${chalk.cyan("false")}, first if that was not intended`);
98535
+ if (approved.length === 0) return [];
98536
+ await this.writeTarget(target, read, { allowScripts });
98537
+ logger$1.info(`approved ${chalk.cyan(approved.join(", "))}`);
98538
+ logger$1.info(`Install scripts do not run retroactively - run ${chalk.cyan("fyn install")} to run them.`);
98539
+ return approved;
98540
+ }
98541
+ /**
98542
+ * Deny packages outright, on the `denyScripts` blacklist. The denial wins
98543
+ * over any approval, at any scope.
98544
+ *
98545
+ * @param {string[]} names packages to deny
98546
+ * @param {object} [options] options
98547
+ * @param {boolean} [options.local] write to this package's package.json
98548
+ * @returns {Promise<string[]>} the names newly denied
98549
+ */
98550
+ async deny(names = [], { local = false } = {}) {
98551
+ if (names.length === 0) {
98552
+ logger$1.error("no packages named to deny");
98553
+ return [];
98554
+ }
98555
+ const target = resolveTarget(this._fyn, local);
98556
+ const read = await this.readTarget(target);
98557
+ const { denyScripts, denied, already } = denyEntries(read.denyScripts, names);
98558
+ if (already.length > 0) logger$1.info(`already denied ${chalk.cyan(already.join(", "))}`);
98559
+ if (denied.length === 0) return [];
98560
+ await this.writeTarget(target, read, { denyScripts });
98561
+ logger$1.info(`denied ${chalk.cyan(denied.join(", "))}`);
98562
+ return denied;
98563
+ }
98564
+ /**
98565
+ * Drop approvals for packages that are no longer installed.
98566
+ *
98567
+ * @param {object} [options] options
98568
+ * @param {boolean} [options.local] write to this package's package.json
98569
+ * @returns {Promise<string[]>} the keys removed
98570
+ */
98571
+ /**
98572
+ * Every package this project actually has installed.
98573
+ *
98574
+ * Not `loadFvVersions()` alone, which reads `<node_modules>/.f/_` - the store
98575
+ * for versions that had to be isolated. Under the default hoisted layout that
98576
+ * is a small slice of the tree, and pruning against it deleted the approvals
98577
+ * of every package sitting in `node_modules` proper.
98578
+ *
98579
+ * @returns {Promise<Set<string>>} installed package names, scoped names included
98580
+ */
98581
+ async installedPackageNames() {
98582
+ const names = new Set(Object.keys(await this._fyn.loadFvVersions()));
98583
+ const outputDir = this._fyn.getOutputDir();
98584
+ const readDir = async (dir) => {
98585
+ try {
98586
+ return await file_ops_default.readdir(dir);
98587
+ } catch {
98588
+ return [];
98589
+ }
98590
+ };
98591
+ for (const entry of await readDir(outputDir)) {
98592
+ if (entry.startsWith(".")) continue;
98593
+ if (entry.startsWith("@")) {
98594
+ for (const scoped of await readDir(Path.join(outputDir, entry))) if (!scoped.startsWith(".")) names.add(`${entry}/${scoped}`);
98595
+ } else names.add(entry);
98596
+ }
98597
+ return names;
98598
+ }
98599
+ async prune({ local = false } = {}) {
98600
+ const installed = [...await this.installedPackageNames()];
98601
+ if (installed.length === 0) {
98602
+ logger$1.error(`no installed packages found - run ${chalk.cyan("fyn install")} before pruning, otherwise every approval would look unused`);
98603
+ return [];
98604
+ }
98605
+ const target = resolveTarget(this._fyn, local);
98606
+ const read = await this.readTarget(target);
98607
+ const { allowScripts, removed } = pruneEntries(read.allowScripts, installed);
98608
+ if (removed.length === 0) {
98609
+ logger$1.info(chalk.green("nothing to prune"));
98610
+ return [];
98611
+ }
98612
+ await this.writeTarget(target, read, { allowScripts });
98613
+ logger$1.info(`pruned ${chalk.cyan(removed.join(", "))}`);
98614
+ return removed;
98615
+ }
98616
+ /**
98617
+ * Stop an install that wants to run install scripts nobody has approved.
98618
+ *
98619
+ * On a terminal this asks; anywhere else - CI, a pipe, a hook - there is
98620
+ * nobody to ask, so the install fails rather than quietly producing a tree
98621
+ * whose native packages were never built. `--script-policy=source` is the
98622
+ * documented way out.
98623
+ *
98624
+ * @param {object[]} records blocked-scripts records from this install
98625
+ * @returns {Promise<object[]>} the records approved, empty when none were
98626
+ * @throws {Error} when there is no terminal to ask on
98627
+ */
98628
+ async review(records) {
98629
+ const pending = dedupeBlockedRecords(records);
98630
+ if (pending.length === 0) return [];
98631
+ if (!canPrompt()) {
98632
+ const err = /* @__PURE__ */ new Error(`${pending.length} package(s) need approval to run their install scripts, and there is no terminal to ask on: ${pending.map((r) => `${r.name}@${r.version}`).join(", ")}.\n Approve them where you can review the code:\n ${chalk.cyan("fyn install-scripts approve <package>")}\n Or record the approvals in your package.json / fynpo.json before installing.\n To go back to trusting packages by where they came from, install with ${chalk.cyan("--script-policy=source")}.`);
98633
+ err._fynAlreadyLogged = true;
98634
+ logger$1.error(err.message);
98635
+ throw err;
98636
+ }
98637
+ logger$1.info(`${pending.length} package${pending.length > 1 ? "s" : ""} want to run install scripts that have not been approved:`);
98638
+ const width = pending.reduce((w, r) => Math.max(w, `${r.name}@${r.version}`.length), 0);
98639
+ for (const record of pending) logger$1.info(` ${chalk.cyan(`${record.name}@${record.version}`.padEnd(width))} ` + chalk.yellow(record.scripts.join(", ")));
98640
+ const answer = await ask(`Approve? ${chalk.cyan("[a]")}ll / ${chalk.cyan("[s]")}elect / ${chalk.cyan("[n]")}one (default) `);
98641
+ let approve = [];
98642
+ if (answer === "a" || answer === "all" || answer === "y" || answer === "yes") approve = pending;
98643
+ else if (answer === "s" || answer === "select") for (const record of pending) {
98644
+ const yn = await ask(` ${record.name}@${record.version} (${record.scripts.join(", ")})? [y/N] `);
98645
+ if (yn === "y" || yn === "yes") approve.push(record);
98646
+ }
98647
+ if (approve.length === 0) {
98648
+ logger$1.info("no approvals recorded - install scripts will not run");
98649
+ return [];
98650
+ }
98651
+ const target = resolveTarget(this._fyn, false);
98652
+ const read = await this.readTarget(target);
98653
+ const { allowScripts, approved, skipped } = approveEntries(read.allowScripts, approve, {
98654
+ pin: this._fyn.allowScriptsPin,
98655
+ denyScripts: {
98656
+ ...read.denyScripts || {},
98657
+ ...this._fyn.denyScripts || {}
98658
+ }
98659
+ });
98660
+ if (skipped.length > 0) logger$1.warn(`${chalk.magenta("denied, not approved")}: ${skipped.join(", ")} - remove the ${chalk.cyan("denyScripts")} entry, or the ${chalk.cyan("allowScripts")} ${chalk.cyan("false")}, first if that was not intended`);
98661
+ if (approved.length === 0) return [];
98662
+ await this.writeTarget(target, read, { allowScripts });
98663
+ logger$1.info(`approved ${chalk.cyan(approved.join(", "))}`);
98664
+ return approve.filter((record) => approved.includes(record.name));
98665
+ }
98666
+ };
98667
+
97520
98668
  //#endregion
97521
98669
  //#region lib/pkg-installer.ts
97522
98670
  var PkgInstaller = class {
@@ -97531,6 +98679,12 @@ var PkgInstaller = class {
97531
98679
  preInstall;
97532
98680
  postInstall;
97533
98681
  toLink;
98682
+ /** packages whose install scripts the policy blocked, reported once at the end */
98683
+ blockedScripts = [];
98684
+ /** packages that would need approval under "review" - only with --allow-scripts-pending */
98685
+ pendingScripts = [];
98686
+ /** the blocked packages themselves, so an approval at the prompt can queue their scripts */
98687
+ _blockedDeps = [];
97534
98688
  constructor(options) {
97535
98689
  this._fyn = options.fyn;
97536
98690
  this._data = this._fyn._data;
@@ -97548,6 +98702,9 @@ var PkgInstaller = class {
97548
98702
  this.preInstall = [];
97549
98703
  this.postInstall = [];
97550
98704
  this.toLink = [];
98705
+ this.blockedScripts = [];
98706
+ this.pendingScripts = [];
98707
+ this._blockedDeps = [];
97551
98708
  this._data.cleanLinked();
97552
98709
  this._fyn._depResolver.resolvePkgPeerDep(this._fyn._pkg, "your app", this._data);
97553
98710
  const pkgsData = this._data.getPkgsData();
@@ -97557,6 +98714,7 @@ var PkgInstaller = class {
97557
98714
  logger$1.debug("queuing", depInfo.name, depInfo.version, "for install");
97558
98715
  await this._gatherPkg(depInfo);
97559
98716
  }
98717
+ await this._reviewBlockedScripts();
97560
98718
  return this._doInstall().finally(() => {
97561
98719
  this.preInstall = void 0;
97562
98720
  this.postInstall = void 0;
@@ -97587,11 +98745,14 @@ var PkgInstaller = class {
97587
98745
  const pkgJson = depInfo.json;
97588
98746
  logger$1.debug("linked dependencies for", pkgJson.name, pkgJson.version);
97589
98747
  }
97590
- if (depInfo.str.trim() === outputStr.trim()) continue;
98748
+ const hardLocalPkgJson = depInfo.local === "hard" ? Path.join(this._fyn.getInstalledPkgDir(depInfo.name, depInfo.version, depInfo), "package.json") : void 0;
98749
+ if (depInfo.str.trim() === outputStr.trim()) {
98750
+ if (hardLocalPkgJson) await this._detachPkgJsonLink(hardLocalPkgJson);
98751
+ continue;
98752
+ }
97591
98753
  let pkgJsonFp;
97592
- if (depInfo.local === "hard") {
97593
- const vdir = this._fyn.getInstalledPkgDir(depInfo.name, depInfo.version, depInfo);
97594
- pkgJsonFp = Path.join(vdir, "package.json");
98754
+ if (hardLocalPkgJson) {
98755
+ pkgJsonFp = hardLocalPkgJson;
97595
98756
  await xaa_default.try(() => file_ops_default.unlink(pkgJsonFp));
97596
98757
  } else pkgJsonFp = Path.join(depInfo.dir, "package.json");
97597
98758
  depInfo.str = outputStr;
@@ -97606,6 +98767,24 @@ var PkgInstaller = class {
97606
98767
  }
97607
98768
  }
97608
98769
  }
98770
+ /**
98771
+ * Give an installed package.json its own inode if it is still hardlinked to the local
98772
+ * package it was replicated from.
98773
+ *
98774
+ * Same content, new physical file: unlink the directory entry and write the bytes back.
98775
+ * Cheap - it only touches manifests that actually still share an inode.
98776
+ *
98777
+ * @param pkgJsonFp - installed package.json to detach
98778
+ */
98779
+ async _detachPkgJsonLink(pkgJsonFp) {
98780
+ const stat = await xaa_default.try(() => file_ops_default.stat(pkgJsonFp));
98781
+ if (!stat || stat.nlink < 2) return;
98782
+ const data = await xaa_default.try(() => file_ops_default.readFile(pkgJsonFp));
98783
+ if (data === void 0) return;
98784
+ logger$1.debug("detaching hardlinked package.json from its local source", pkgJsonFp);
98785
+ await xaa_default.try(() => file_ops_default.unlink(pkgJsonFp));
98786
+ await file_ops_default.writeFile(pkgJsonFp, data);
98787
+ }
97609
98788
  _isDepSrcOptionalOnly(depInfo) {
97610
98789
  if ((!depInfo.src || depInfo.src === "opt") && (!depInfo.dsrc || depInfo.dsrc === "opt")) return true;
97611
98790
  return depInfo.requests.filter((req) => req.find((r) => r.startsWith("opt"))).length === depInfo.requests.length;
@@ -97748,6 +98927,7 @@ var PkgInstaller = class {
97748
98927
  if (this._fyn.showDeprecated && import_lodash_min.default.isEmpty(warned)) logger$1.info(chalk.green("HOORAY!!! None of your dependencies are marked deprecated."));
97749
98928
  }).then(() => this._installLocalExports()).then(() => this._saveLockData()).then(() => {
97750
98929
  this._fyn._depResolver._logConsolidatedPeerDepWarnings();
98930
+ this._reportBlockedScripts();
97751
98931
  logger$1.info(`${chalk.green("done install")} ${log_format_default.time(Date.now() - start)}`);
97752
98932
  }).finally(() => {
97753
98933
  logger$1.removeItem(INSTALL_PACKAGE);
@@ -97805,37 +98985,157 @@ var PkgInstaller = class {
97805
98985
  }
97806
98986
  json._fyn = {};
97807
98987
  const scripts = json.scripts || {};
97808
- const scriptPolicy = evaluateScriptPolicy(depInfo, this._fyn.allowScripts, { allowTopLevel: this._fyn.allowTopLevelScripts });
97809
- const blockedScripts = [];
97810
- const isAllowed = (scriptName) => {
97811
- if (isScriptAllowed(scriptPolicy, scriptName)) return true;
97812
- blockedScripts.push(scriptName);
97813
- return false;
97814
- };
98988
+ const scriptPolicy = evaluateScriptPolicy(depInfo, this._fyn.allowScripts, this._fyn.scriptPolicyOptions);
97815
98989
  const hasPI = json.hasPI || Boolean(scripts.preinstall);
97816
- if (!Boolean(depInfo.preinstall) && hasPI) {
98990
+ const piExed = Boolean(depInfo.preinstall);
98991
+ const candidates = [];
98992
+ if (!piExed && hasPI) {
97817
98993
  if (depInfo.preInstalled) json._fyn.preinstall = true;
97818
- else if (isAllowed("preinstall")) {
97819
- logger$1.debug("adding preinstall step for", depInfo.dir);
97820
- this.preInstall.push(depInfo);
97821
- }
98994
+ else candidates.push("preinstall");
97822
98995
  }
98996
+ for (const name of ["install", "postinstall"]) if (Boolean(scripts[name]) && !json._fyn[name]) candidates.push(name);
97823
98997
  this.toLink.push(depInfo);
97824
- const install = ["install", "postinstall"].filter((x) => {
97825
- return Boolean(scripts[x]) && !json._fyn[x] && isAllowed(x);
98998
+ const blockedScripts = candidates.filter((name) => !isScriptAllowed(scriptPolicy, name));
98999
+ this._queueScripts(depInfo, candidates.filter((name) => isScriptAllowed(scriptPolicy, name)));
99000
+ if (blockedScripts.length > 0) {
99001
+ const byName = scriptPolicy.deniedScripts || /* @__PURE__ */ new Set();
99002
+ const denied = blockedScripts.filter((name) => byName.has(name));
99003
+ const pending = blockedScripts.filter((name) => !byName.has(name));
99004
+ if (denied.length > 0) this._recordBlockedScripts(depInfo, {
99005
+ ...scriptPolicy,
99006
+ reason: "denied"
99007
+ }, denied);
99008
+ if (pending.length > 0) {
99009
+ this._recordBlockedScripts(depInfo, scriptPolicy, pending);
99010
+ this._blockedDeps.push({
99011
+ depInfo,
99012
+ candidates: pending
99013
+ });
99014
+ }
99015
+ }
99016
+ this._recordPendingReview(depInfo, {
99017
+ hasPI,
99018
+ ...scripts
97826
99019
  });
99020
+ }
99021
+ /**
99022
+ * Queue a package's allowed install scripts.
99023
+ *
99024
+ * Idempotent per package, because the review prompt re-queues after an
99025
+ * approval and a package may already have had some of its scripts allowed.
99026
+ *
99027
+ * @param {object} depInfo resolved package data
99028
+ * @param {string[]} names the lifecycle scripts to run
99029
+ * @returns {void}
99030
+ */
99031
+ _queueScripts(depInfo, names) {
99032
+ if (names.includes("preinstall") && !this.preInstall.includes(depInfo)) {
99033
+ logger$1.debug("adding preinstall step for", depInfo.dir);
99034
+ this.preInstall.push(depInfo);
99035
+ }
99036
+ const install = ["install", "postinstall"].filter((name) => names.includes(name));
97827
99037
  if (install.length > 0) {
97828
99038
  logger$1.debug("adding install step for", depInfo.dir, install);
97829
- depInfo.install = install;
97830
- this.postInstall.push(depInfo);
99039
+ depInfo.install = import_lodash_min.default.union(depInfo.install || [], install);
99040
+ if (!this.postInstall.includes(depInfo)) this.postInstall.push(depInfo);
97831
99041
  }
97832
- if (blockedScripts.length > 0) this._warnBlockedScripts(depInfo, scriptPolicy, blockedScripts);
97833
99042
  }
97834
- _warnBlockedScripts(depInfo, policy, blocked) {
97835
- const id = log_format_default.pkgId(depInfo);
97836
- logger$1.warn(`${chalk.black.bgYellow("WARN")} ${chalk.magenta("scripts blocked")} ${id}`, chalk.yellow(`skipped lifecycle [${blocked.join(", ")}] for non-registry (${policy.urlType}) package - not in fyn.allowScripts`));
97837
- logger$1.verbose(chalk.blue(" To allow, add to package.json:"), chalk.cyan(`"fyn": { "allowScripts": { "${policy.key}": [${blocked.map((s) => `"${s}"`).join(", ")}] } }`));
97838
- if (policy.topLevel) logger$1.verbose(chalk.blue(" Or trust all direct deps' scripts with:"), chalk.cyan(`"fyn": { "allowTopLevelScripts": true }`));
99043
+ /**
99044
+ * Stop before running anything when packages want to run install scripts
99045
+ * nobody has approved. On a terminal this asks and re-queues whatever the
99046
+ * person approves; anywhere else it throws.
99047
+ *
99048
+ * Only under `"review"`: `"source"` is the documented opt-out and keeps
99049
+ * warning-and-continuing, and under `"off"` nothing running is the point.
99050
+ *
99051
+ * @returns {Promise<void>} nothing
99052
+ */
99053
+ async _reviewBlockedScripts() {
99054
+ if (this._fyn.scriptPolicy !== "review" || this._blockedDeps.length === 0) return;
99055
+ const pending = this.blockedScripts.filter((record) => record.reason !== "denied");
99056
+ if (pending.length === 0) return;
99057
+ if ((await new InstallScripts({ fyn: this._fyn }).review(pending)).length === 0) return;
99058
+ this._fyn.resetAllowScripts();
99059
+ this._requeueApproved();
99060
+ }
99061
+ /**
99062
+ * Re-evaluate the packages that were blocked, after the allowlist on disk
99063
+ * changed, and queue whatever is now allowed.
99064
+ *
99065
+ * @returns {void}
99066
+ */
99067
+ _requeueApproved() {
99068
+ const blockedDeps = this._blockedDeps;
99069
+ const stillBlocked = [];
99070
+ this._blockedDeps = [];
99071
+ this.blockedScripts = [];
99072
+ for (const { depInfo, candidates } of blockedDeps) {
99073
+ const policy = evaluateScriptPolicy(depInfo, this._fyn.allowScripts, this._fyn.scriptPolicyOptions);
99074
+ const blocked = candidates.filter((name) => !isScriptAllowed(policy, name));
99075
+ this._queueScripts(depInfo, candidates.filter((name) => isScriptAllowed(policy, name)));
99076
+ if (blocked.length > 0) {
99077
+ this._recordBlockedScripts(depInfo, policy, blocked);
99078
+ stillBlocked.push({
99079
+ depInfo,
99080
+ candidates
99081
+ });
99082
+ }
99083
+ }
99084
+ this._blockedDeps = stillBlocked;
99085
+ }
99086
+ /**
99087
+ * With `--allow-scripts-pending`, also answer the question `"review"` would
99088
+ * ask - which packages have install scripts that nobody has approved - while
99089
+ * still running the install under the mode in effect. That is how a project
99090
+ * sees what switching to `"review"` would cost before switching.
99091
+ *
99092
+ * @param {object} depInfo resolved package data
99093
+ * @param {object} scripts the package's scripts, plus `hasPI`
99094
+ * @returns {void}
99095
+ */
99096
+ _recordPendingReview(depInfo, scripts) {
99097
+ if (!this._fyn.allowScriptsPending) return;
99098
+ const has = LIFECYCLE_SCRIPTS.filter((name) => Boolean(scripts[name]) || name === "preinstall" && scripts.hasPI);
99099
+ if (has.length === 0) return;
99100
+ const policy = evaluateScriptPolicy(depInfo, this._fyn.allowScripts, {
99101
+ ...this._fyn.scriptPolicyOptions,
99102
+ mode: strictestScriptPolicy(this._fyn.scriptPolicy, "review")
99103
+ });
99104
+ const pending = has.filter((name) => !isScriptAllowed(policy, name));
99105
+ if (pending.length > 0) this.pendingScripts.push(makeBlockedRecord(depInfo, policy, pending));
99106
+ }
99107
+ /**
99108
+ * Record a package whose install scripts the policy blocked. Reported as one
99109
+ * summary at the end of the install rather than a warning per package - under
99110
+ * "review" mode every native dependency blocks on a first install.
99111
+ *
99112
+ * @param {object} depInfo resolved package data
99113
+ * @param {object} policy result of `evaluateScriptPolicy`
99114
+ * @param {string[]} blocked the lifecycle scripts that were skipped
99115
+ * @returns {void}
99116
+ */
99117
+ _recordBlockedScripts(depInfo, policy, blocked) {
99118
+ logger$1.debug("scripts blocked", log_format_default.pkgId(depInfo), `[${blocked.join(", ")}]`, policy.reason);
99119
+ this.blockedScripts.push(makeBlockedRecord(depInfo, policy, blocked));
99120
+ }
99121
+ /**
99122
+ * Report every package whose install scripts were blocked, with the exact
99123
+ * config to paste to allow them, and hand the same records to fyn so they are
99124
+ * saved for `fyn install-scripts ls`.
99125
+ *
99126
+ * @returns {void}
99127
+ */
99128
+ _reportBlockedScripts() {
99129
+ const records = this.blockedScripts;
99130
+ this._fyn.setBlockedScripts(records, this.pendingScripts);
99131
+ for (const line of formatBlockedScriptsSummary(records, {
99132
+ mode: this._fyn.scriptPolicy,
99133
+ pin: this._fyn.allowScriptsPin
99134
+ })) logger$1.warn(line);
99135
+ for (const line of formatPendingScriptsSummary(this.pendingScripts, {
99136
+ mode: this._fyn.scriptPolicy,
99137
+ pin: this._fyn.allowScriptsPin
99138
+ })) logger$1.info(line);
97839
99139
  }
97840
99140
  _cleanBin() {
97841
99141
  logger$1.updateItem(INSTALL_PACKAGE, "cleaning node_modules/.bin");
@@ -98475,6 +99775,18 @@ const OMIT_SRC = {
98475
99775
  optional: "opt",
98476
99776
  peer: "per"
98477
99777
  };
99778
+ /**
99779
+ * How long to wait on npm's bulk advisories endpoint before giving up, in ms.
99780
+ *
99781
+ * That endpoint intermittently accepts the connection and never answers. npm-registry-fetch
99782
+ * would apply its own defaults here - a 5 minute timeout over 3 attempts - which turns a
99783
+ * stalled endpoint into a ~15 minute silent install hang. Audit is advisory and its caller
99784
+ * already degrades to cached data or a warning, so bound it tightly instead: one attempt,
99785
+ * ten seconds, then fall through.
99786
+ */
99787
+ const AUDIT_FETCH_TIMEOUT = 1e4;
99788
+ /** No retries on the audit fetch - an endpoint that just stalled is unlikely to answer the retry. */
99789
+ const AUDIT_FETCH_RETRY = { retries: 0 };
98478
99790
  var AuditReport = class {
98479
99791
  _fyn;
98480
99792
  _depData;
@@ -98574,7 +99886,9 @@ var AuditReport = class {
98574
99886
  ...fetchOpts,
98575
99887
  method: "POST",
98576
99888
  body: payload,
98577
- gzip: true
99889
+ gzip: true,
99890
+ timeout: AUDIT_FETCH_TIMEOUT,
99891
+ retry: AUDIT_FETCH_RETRY
98578
99892
  })).json();
98579
99893
  const result = {
98580
99894
  advisories,
@@ -99740,6 +101054,38 @@ var FynCli = class {
99740
101054
  return this._opts.saveLogs && this.saveLogs(this._opts.saveLogs);
99741
101055
  });
99742
101056
  }
101057
+ /**
101058
+ * `fyn install-scripts <action>` - review and record which packages may run
101059
+ * their install scripts.
101060
+ *
101061
+ * @param {string} action one of ls / approve / deny / prune
101062
+ * @param {object} argv parsed command args and options
101063
+ * @returns {Promise<unknown>} the action's result
101064
+ */
101065
+ async installScripts(action, argv = {}) {
101066
+ const opts = argv.opts || {};
101067
+ const packages = (argv.args || {}).packages || [];
101068
+ await this.fyn._initializePkg();
101069
+ const cmd = new InstallScripts({ fyn: this.fyn });
101070
+ try {
101071
+ switch (action) {
101072
+ case "ls": return await cmd.ls({ json: Boolean(opts.json) });
101073
+ case "approve": return await cmd.approve(packages, {
101074
+ all: Boolean(opts.all),
101075
+ local: Boolean(opts.local)
101076
+ });
101077
+ case "deny": return await cmd.deny(packages, { local: Boolean(opts.local) });
101078
+ case "prune": return await cmd.prune({ local: Boolean(opts.local) });
101079
+ default: throw new Error(`unknown install-scripts action "${action}"`);
101080
+ }
101081
+ } catch (err) {
101082
+ logger$1.error(err.message);
101083
+ fyntil.exit(1);
101084
+ return;
101085
+ } finally {
101086
+ if (this._opts.saveLogs) await this.saveLogs(this._opts.saveLogs);
101087
+ }
101088
+ }
99743
101089
  audit(argv) {
99744
101090
  const opts = {
99745
101091
  json: argv.opts?.json || false,
@@ -99784,7 +101130,7 @@ var FynCli = class {
99784
101130
  if (this._config._fynpo.config) xsh.envPath.addToFront(Path.join(this._config._fynpo.dir, "node_modules/.bin"), env);
99785
101131
  setupNodeGypEnv(env);
99786
101132
  env.npm_node_execpath = env.NODE = env.NODE || process.execPath;
99787
- env.npm_execpath = fileURLToPath$1(new URL("../bin/fyn.js", import.meta.url));
101133
+ env.npm_execpath = findFynCli();
99788
101134
  env.INIT_CWD = this.fyn.cwd;
99789
101135
  const mainScript = scripts[scripts.length - 1];
99790
101136
  return dist_default.each(_scripts, (s) => {
@@ -100053,6 +101399,85 @@ function getTerminalWidth() {
100053
101399
  return process.stdout.columns || 80;
100054
101400
  }
100055
101401
 
101402
+ //#endregion
101403
+ //#region node_modules/@fynjs/cli-args/dist/symbols.js
101404
+ const _PARENT = Symbol("parent");
101405
+ const _PREV = Symbol("prev");
101406
+ const _NEXT = Symbol("next");
101407
+ /** commands whose sync `exec` returned a promise - see NixClap.runExec */
101408
+ const _ASYNC_EXEC = Symbol("asyncExec");
101409
+
101410
+ //#endregion
101411
+ //#region node_modules/@fynjs/cli-args/dist/clap-node.js
101412
+ /**
101413
+ * Represents a node in a command-line application parser.
101414
+ *
101415
+ * @remarks
101416
+ * This class is used to represent commands or options in a CLI application. Each node can have a parent node,
101417
+ * which represents a hierarchical relationship (e.g., command -> sub-command, option).
101418
+ *
101419
+ * @example
101420
+ * ```typescript
101421
+ * const rootNode = new ClapNode('root', 'rootAlias');
101422
+ * const childNode = new ClapNode('child', 'childAlias', rootNode);
101423
+ * rootNode.addArg('arg1');
101424
+ * childNode.addError(new Error('An error occurred'));
101425
+ * ```
101426
+ *
101427
+ * @public
101428
+ */
101429
+ var ClapNode = class {
101430
+ /**
101431
+ *
101432
+ * @param name - idiomatic name of the command
101433
+ * @param inputName - name that was specified by user to trigger this command
101434
+ * @param parent
101435
+ */
101436
+ constructor(name, alias, parent) {
101437
+ this.name = name;
101438
+ this.alias = alias;
101439
+ this.argsList = [];
101440
+ this.argv = [];
101441
+ this.argsMap = {};
101442
+ if (parent) Object.defineProperty(this, _PARENT, {
101443
+ value: parent,
101444
+ configurable: true,
101445
+ enumerable: false,
101446
+ writable: true
101447
+ });
101448
+ this._errors = [];
101449
+ }
101450
+ /**
101451
+ * Add an arg to this node
101452
+ * @param arg
101453
+ * @returns
101454
+ */
101455
+ addArg(arg) {
101456
+ this.argsList.push(arg);
101457
+ }
101458
+ addVerbatimArg(arg) {
101459
+ this.argv.push(arg);
101460
+ }
101461
+ addError(e) {
101462
+ this._errors.push(e);
101463
+ }
101464
+ get hasErrors() {
101465
+ return this._errors.length > 0;
101466
+ }
101467
+ get error() {
101468
+ return this._errors[0];
101469
+ }
101470
+ get errors() {
101471
+ return this._errors;
101472
+ }
101473
+ get parent() {
101474
+ return this[_PARENT];
101475
+ }
101476
+ getParent() {
101477
+ return this.parent;
101478
+ }
101479
+ };
101480
+
100056
101481
  //#endregion
100057
101482
  //#region node_modules/@fynjs/cli-args/dist/base.js
100058
101483
  const SUPPORT_TYPES = [
@@ -100183,336 +101608,6 @@ var CliBase = class {
100183
101608
  }
100184
101609
  };
100185
101610
 
100186
- //#endregion
100187
- //#region node_modules/@fynjs/cli-args/dist/symbols.js
100188
- const _PARENT = Symbol("parent");
100189
- const _PREV = Symbol("prev");
100190
- const _NEXT = Symbol("next");
100191
- /** commands whose sync `exec` returned a promise - see NixClap.runExec */
100192
- const _ASYNC_EXEC = Symbol("asyncExec");
100193
-
100194
- //#endregion
100195
- //#region node_modules/@fynjs/cli-args/dist/clap-node.js
100196
- /**
100197
- * Represents a node in a command-line application parser.
100198
- *
100199
- * @remarks
100200
- * This class is used to represent commands or options in a CLI application. Each node can have a parent node,
100201
- * which represents a hierarchical relationship (e.g., command -> sub-command, option).
100202
- *
100203
- * @example
100204
- * ```typescript
100205
- * const rootNode = new ClapNode('root', 'rootAlias');
100206
- * const childNode = new ClapNode('child', 'childAlias', rootNode);
100207
- * rootNode.addArg('arg1');
100208
- * childNode.addError(new Error('An error occurred'));
100209
- * ```
100210
- *
100211
- * @public
100212
- */
100213
- var ClapNode = class {
100214
- /**
100215
- *
100216
- * @param name - idiomatic name of the command
100217
- * @param inputName - name that was specified by user to trigger this command
100218
- * @param parent
100219
- */
100220
- constructor(name, alias, parent) {
100221
- this.name = name;
100222
- this.alias = alias;
100223
- this.argsList = [];
100224
- this.argv = [];
100225
- this.argsMap = {};
100226
- if (parent) Object.defineProperty(this, _PARENT, {
100227
- value: parent,
100228
- configurable: true,
100229
- enumerable: false,
100230
- writable: true
100231
- });
100232
- this._errors = [];
100233
- }
100234
- /**
100235
- * Add an arg to this node
100236
- * @param arg
100237
- * @returns
100238
- */
100239
- addArg(arg) {
100240
- this.argsList.push(arg);
100241
- }
100242
- addVerbatimArg(arg) {
100243
- this.argv.push(arg);
100244
- }
100245
- addError(e) {
100246
- this._errors.push(e);
100247
- }
100248
- get hasErrors() {
100249
- return this._errors.length > 0;
100250
- }
100251
- get error() {
100252
- return this._errors[0];
100253
- }
100254
- get errors() {
100255
- return this._errors;
100256
- }
100257
- get parent() {
100258
- return this[_PARENT];
100259
- }
100260
- getParent() {
100261
- return this.parent;
100262
- }
100263
- };
100264
-
100265
- //#endregion
100266
- //#region node_modules/@fynjs/cli-args/dist/command-node.js
100267
- /**
100268
- * Object representation for an instance of a command on the CLI
100269
- */
100270
- var CommandNode = class extends ClapNode {
100271
- constructor(name, alias, cmdBase) {
100272
- super(name, alias);
100273
- this.argv.push(name);
100274
- this.cmdBase = cmdBase;
100275
- this.subCmdNodes = {};
100276
- this.optNodes = {};
100277
- this.optCount = {};
100278
- this.isGreedy = false;
100279
- }
100280
- /** get the options with full values for this command */
100281
- get options() {
100282
- return this.jsonMeta.optsFull;
100283
- }
100284
- /** get the options for this command */
100285
- get opts() {
100286
- return this.jsonMeta.opts;
100287
- }
100288
- /** get the arguments for this command */
100289
- get args() {
100290
- return this.jsonMeta.args;
100291
- }
100292
- get source() {
100293
- return this.jsonMeta.source;
100294
- }
100295
- get optsFull() {
100296
- return this.jsonMeta.optsFull;
100297
- }
100298
- get subCommands() {
100299
- return this.jsonMeta.subCommands;
100300
- }
100301
- getParent() {
100302
- return super.getParent();
100303
- }
100304
- /**
100305
- * Get the root command by traversing up the parent chain until no more parent exists.
100306
- * @returns The root CommandNode
100307
- */
100308
- get rootCmd() {
100309
- let current = this;
100310
- while (current.getParent()) current = current.getParent();
100311
- return current;
100312
- }
100313
- /**
100314
- * Get array of all commands that have exec callback
100315
- *
100316
- * Root command execution is handled specially:
100317
- * - This method filters out the root command (returns false for isRootCommand)
100318
- * - Root command execution is determined by _shouldExecuteRootCommand() in nix-clap.ts
100319
- * - Root command is executed in runExec/runExecAsync methods when conditions are met
100320
- *
100321
- * @param cmds - Array to accumulate command nodes with exec handlers
100322
- * @param includeSubCommands - Whether to recursively include sub-commands
100323
- * @returns Array of command nodes that have exec handlers (excluding root command)
100324
- */
100325
- getExecCommands(cmds, includeSubCommands) {
100326
- if (this.cmdBase.exec && !isRootCommand(this.alias)) cmds.push(this);
100327
- if (includeSubCommands) for (const _key in this.subCmdNodes) this.subCmdNodes[_key].getExecCommands(cmds, includeSubCommands);
100328
- return cmds;
100329
- }
100330
- /**
100331
- * Get the command chain (sequence from root to this command, including itself)
100332
- * @returns Array of CommandNodes from root to this command
100333
- */
100334
- get cmdChain() {
100335
- let parent = this;
100336
- const nodes = [];
100337
- while (parent && (parent = parent.getParent())) nodes.push(parent);
100338
- return nodes.reverse().concat(this);
100339
- }
100340
- /**
100341
- * Invoke the command's exec handler
100342
- *
100343
- * @param includeSubCommands - if true, then also do sub commands
100344
- * @param parsed - The parsed result containing remaining args after --
100345
- */
100346
- invokeExec(includeSubCommands, parsed) {
100347
- let count = 0;
100348
- const cmds = this.getExecCommands([], includeSubCommands);
100349
- for (const cmd of cmds) {
100350
- const result = cmd.cmdBase.exec(cmd, parsed);
100351
- if (parsed) {
100352
- if (isThenable(result)) (parsed[_ASYNC_EXEC] || (parsed[_ASYNC_EXEC] = [])).push(cmd.name);
100353
- if (!parsed.execCmd) parsed.execCmd = cmd;
100354
- }
100355
- count++;
100356
- }
100357
- return count;
100358
- }
100359
- /**
100360
- *
100361
- * @param includeSubCommands
100362
- * @param parsed - The parsed result containing remaining args after --
100363
- * @returns
100364
- */
100365
- async invokeExecAsync(includeSubCommands, parsed) {
100366
- let count = 0;
100367
- const cmds = this.getExecCommands([], includeSubCommands);
100368
- for (const cmd of cmds) {
100369
- await cmd.cmdBase.exec(cmd, parsed);
100370
- if (parsed && !parsed.execCmd) parsed.execCmd = cmd;
100371
- count++;
100372
- }
100373
- return count;
100374
- }
100375
- /**
100376
- * Add a sub command to this node
100377
- * @param name
100378
- * @returns node for the new command
100379
- */
100380
- addCommandNode(node) {
100381
- node[_PARENT] = this;
100382
- this.subCmdNodes[node.name] = node;
100383
- return node;
100384
- }
100385
- getErrorNodes(errorNodes = []) {
100386
- if (this.hasErrors) errorNodes.push(this);
100387
- for (const _key in this.optNodes) {
100388
- const _optNode = this.optNodes[_key];
100389
- if (_optNode.hasErrors) errorNodes.push(_optNode);
100390
- }
100391
- for (const _key in this.subCmdNodes) this.subCmdNodes[_key].getErrorNodes(errorNodes);
100392
- return errorNodes;
100393
- }
100394
- /**
100395
- * Add a option to this node
100396
- * @param name
100397
- * @returns node for the new option
100398
- */
100399
- addOptionNode(node) {
100400
- node[_PARENT] = this;
100401
- const name = node.option.name || node.name;
100402
- this.optNodes[name] = node;
100403
- if (!this.optCount[name]) this.optCount[name] = 0;
100404
- this.optCount[name]++;
100405
- return node;
100406
- }
100407
- removeOptionNode(name) {
100408
- if (this.optNodes[name]) {
100409
- delete this.optNodes[name];
100410
- delete this.optCount[name];
100411
- }
100412
- }
100413
- /**
100414
- *
100415
- */
100416
- applyDefaults() {
100417
- for (const optName in this.optNodes) this.optNodes[optName].applyDefaults();
100418
- const options = this.cmdBase.options._options;
100419
- for (const optName in options) {
100420
- const opt = options[optName];
100421
- if (opt.spec.hasOwnProperty("argDefault") && !this.optNodes[optName]) new ClapNodeGenerator(this).addOptionWithArgs(optName, [].concat(opt.spec.argDefault), opt);
100422
- }
100423
- for (const subCmdName in this.subCmdNodes) this.subCmdNodes[subCmdName].applyDefaults();
100424
- }
100425
- /**
100426
- * Allow you to apply extra config to the parsed object, overriding any `opts` with `source` not start with `cli`.
100427
- *
100428
- * For example, you can allow user to specify options in their `package.json` file, and apply those after the command line is parsed.
100429
- * @param config - Config object containing user options config
100430
- * @param src - Name of the source that provided the config. Default to `user`
100431
- * @returns
100432
- */
100433
- applyConfig(config, src = "user") {
100434
- for (const key in config) {
100435
- const data = {
100436
- name: key,
100437
- value: config[key],
100438
- verbatim: config[key],
100439
- arg: config[key],
100440
- dashes: 0
100441
- };
100442
- const matchOpt = this.cmdBase.options.match(data);
100443
- if (matchOpt) {
100444
- const optNode = this.optNodes[matchOpt.name];
100445
- if (!optNode || !optNode.source.startsWith("cli")) {
100446
- this.removeOptionNode(matchOpt.name);
100447
- new ClapNodeGenerator(this).addOptionWithArgs(matchOpt.name, [].concat(data.arg), matchOpt.option, src);
100448
- }
100449
- } else if (!this.optNodes[key]) new ClapNodeGenerator(this).addOptionWithArgs(key, [].concat(data.arg), void 0, src);
100450
- }
100451
- }
100452
- /**
100453
- * For options that has names with - in them, add it using its name converted to camelCase.
100454
- */
100455
- makeCamelCaseOptions() {
100456
- for (const key in this.optNodes) {
100457
- const camelCaseKey = camelCase(key);
100458
- if (camelCaseKey !== key && !this.optNodes[camelCaseKey]) {
100459
- this.optNodes[camelCaseKey] = this.optNodes[key];
100460
- this.optCount[camelCaseKey] = this.optCount[key];
100461
- }
100462
- }
100463
- for (const key in this.subCmdNodes) this.subCmdNodes[key].makeCamelCaseOptions();
100464
- }
100465
- /**
100466
- * check for missing options that are required
100467
- *
100468
- * @param missing
100469
- * @returns
100470
- */
100471
- checkRequiredOptions(missing = []) {
100472
- const _opts = this.cmdBase.options._options;
100473
- for (const name in _opts) if (_opts[name].spec.required) {
100474
- if (!this.optNodes[name]) missing.push(name);
100475
- }
100476
- for (const kCmd in this.subCmdNodes) this.subCmdNodes[kCmd].checkRequiredOptions(missing);
100477
- return missing;
100478
- }
100479
- get jsonMeta() {
100480
- if (this._jsonMeta) return this._jsonMeta;
100481
- const opts = {};
100482
- const optsFull = {};
100483
- const source = {};
100484
- for (const name in this.optNodes) {
100485
- const variants = [name];
100486
- const node = this.optNodes[name];
100487
- if (node.alias && name !== node.alias) variants.push(node.alias);
100488
- for (const _name of variants) {
100489
- if (node.option.isSingleArg) opts[_name] = node.argsMap[0];
100490
- else if (node.option.isCounting) opts[_name] = this.optCount[node.option.name];
100491
- else if (!node.option.hasArgs) opts[_name] = node.argsMap[0];
100492
- else opts[_name] = node.argsMap;
100493
- optsFull[_name] = node.argsMap;
100494
- source[_name] = this.optNodes[name].source;
100495
- }
100496
- }
100497
- const subCommands = {};
100498
- for (const name in this.subCmdNodes) subCommands[name] = this.subCmdNodes[name].jsonMeta;
100499
- const meta = {
100500
- name: this.name,
100501
- alias: this.alias,
100502
- argList: this.argsList,
100503
- args: this.argsMap,
100504
- opts,
100505
- optsFull,
100506
- optsCount: this.optCount,
100507
- source,
100508
- verbatim: {},
100509
- subCommands
100510
- };
100511
- this._jsonMeta = meta;
100512
- return meta;
100513
- }
100514
- };
100515
-
100516
101611
  //#endregion
100517
101612
  //#region node_modules/@fynjs/cli-args/dist/option-base.js
100518
101613
  /**
@@ -100568,35 +101663,6 @@ var UnknownOption = class extends OptionBase {
100568
101663
  };
100569
101664
  const optUnknown = new UnknownOption("", {});
100570
101665
 
100571
- //#endregion
100572
- //#region node_modules/@fynjs/cli-args/dist/option-node.js
100573
- /**
100574
- * Represents an option node in the command-line argument parser.
100575
- * Extends the `ClapNode` class to include specific option-related data.
100576
- *
100577
- * @remarks
100578
- * This class is used to handle options parsed from the command-line input.
100579
- * It includes the option itself and its source.
100580
- *
100581
- * @example
100582
- * ```typescript
100583
- * const optionMatch: OptionMatch = { name: 'verbose', alias: 'v', value: true, option: someOption };
100584
- * const optionNode = new OptionNode(optionMatch);
100585
- * ```
100586
- *
100587
- */
100588
- var OptionNode = class extends ClapNode {
100589
- constructor(data, parent) {
100590
- super(data.name, data.alias, parent);
100591
- this.argv.push(data.arg);
100592
- if (data.value !== void 0) this.addArg(data.value);
100593
- this.option = data.option || optUnknown;
100594
- }
100595
- applyDefaults() {
100596
- if (!this.option.spec.argDefault) return;
100597
- }
100598
- };
100599
-
100600
101666
  //#endregion
100601
101667
  //#region node_modules/@fynjs/cli-args/dist/options.js
100602
101668
  /**
@@ -100944,6 +102010,286 @@ const unknownCommandBaseNoOptions = new CommandBase("unknown no unknown options"
100944
102010
  alias: []
100945
102011
  });
100946
102012
 
102013
+ //#endregion
102014
+ //#region node_modules/@fynjs/cli-args/dist/option-node.js
102015
+ /**
102016
+ * Represents an option node in the command-line argument parser.
102017
+ * Extends the `ClapNode` class to include specific option-related data.
102018
+ *
102019
+ * @remarks
102020
+ * This class is used to handle options parsed from the command-line input.
102021
+ * It includes the option itself and its source.
102022
+ *
102023
+ * @example
102024
+ * ```typescript
102025
+ * const optionMatch: OptionMatch = { name: 'verbose', alias: 'v', value: true, option: someOption };
102026
+ * const optionNode = new OptionNode(optionMatch);
102027
+ * ```
102028
+ *
102029
+ */
102030
+ var OptionNode = class extends ClapNode {
102031
+ constructor(data, parent) {
102032
+ super(data.name, data.alias, parent);
102033
+ this.argv.push(data.arg);
102034
+ if (data.value !== void 0) this.addArg(data.value);
102035
+ this.option = data.option || optUnknown;
102036
+ }
102037
+ applyDefaults() {
102038
+ if (!this.option.spec.argDefault) return;
102039
+ }
102040
+ };
102041
+
102042
+ //#endregion
102043
+ //#region node_modules/@fynjs/cli-args/dist/command-node.js
102044
+ /**
102045
+ * Object representation for an instance of a command on the CLI
102046
+ */
102047
+ var CommandNode = class extends ClapNode {
102048
+ constructor(name, alias, cmdBase) {
102049
+ super(name, alias);
102050
+ this.argv.push(name);
102051
+ this.cmdBase = cmdBase;
102052
+ this.subCmdNodes = {};
102053
+ this.optNodes = {};
102054
+ this.optCount = {};
102055
+ this.isGreedy = false;
102056
+ }
102057
+ /** get the options with full values for this command */
102058
+ get options() {
102059
+ return this.jsonMeta.optsFull;
102060
+ }
102061
+ /** get the options for this command */
102062
+ get opts() {
102063
+ return this.jsonMeta.opts;
102064
+ }
102065
+ /** get the arguments for this command */
102066
+ get args() {
102067
+ return this.jsonMeta.args;
102068
+ }
102069
+ get source() {
102070
+ return this.jsonMeta.source;
102071
+ }
102072
+ get optsFull() {
102073
+ return this.jsonMeta.optsFull;
102074
+ }
102075
+ get subCommands() {
102076
+ return this.jsonMeta.subCommands;
102077
+ }
102078
+ getParent() {
102079
+ return super.getParent();
102080
+ }
102081
+ /**
102082
+ * Get the root command by traversing up the parent chain until no more parent exists.
102083
+ * @returns The root CommandNode
102084
+ */
102085
+ get rootCmd() {
102086
+ let current = this;
102087
+ while (current.getParent()) current = current.getParent();
102088
+ return current;
102089
+ }
102090
+ /**
102091
+ * Get array of all commands that have exec callback
102092
+ *
102093
+ * Root command execution is handled specially:
102094
+ * - This method filters out the root command (returns false for isRootCommand)
102095
+ * - Root command execution is determined by _shouldExecuteRootCommand() in nix-clap.ts
102096
+ * - Root command is executed in runExec/runExecAsync methods when conditions are met
102097
+ *
102098
+ * @param cmds - Array to accumulate command nodes with exec handlers
102099
+ * @param includeSubCommands - Whether to recursively include sub-commands
102100
+ * @returns Array of command nodes that have exec handlers (excluding root command)
102101
+ */
102102
+ getExecCommands(cmds, includeSubCommands) {
102103
+ if (this.cmdBase.exec && !isRootCommand(this.alias)) cmds.push(this);
102104
+ if (includeSubCommands) for (const _key in this.subCmdNodes) this.subCmdNodes[_key].getExecCommands(cmds, includeSubCommands);
102105
+ return cmds;
102106
+ }
102107
+ /**
102108
+ * Get the command chain (sequence from root to this command, including itself)
102109
+ * @returns Array of CommandNodes from root to this command
102110
+ */
102111
+ get cmdChain() {
102112
+ let parent = this;
102113
+ const nodes = [];
102114
+ while (parent && (parent = parent.getParent())) nodes.push(parent);
102115
+ return nodes.reverse().concat(this);
102116
+ }
102117
+ /**
102118
+ * Invoke the command's exec handler
102119
+ *
102120
+ * @param includeSubCommands - if true, then also do sub commands
102121
+ * @param parsed - The parsed result containing remaining args after --
102122
+ */
102123
+ invokeExec(includeSubCommands, parsed) {
102124
+ let count = 0;
102125
+ const cmds = this.getExecCommands([], includeSubCommands);
102126
+ for (const cmd of cmds) {
102127
+ const result = cmd.cmdBase.exec(cmd, parsed);
102128
+ if (parsed) {
102129
+ if (isThenable(result)) (parsed[_ASYNC_EXEC] || (parsed[_ASYNC_EXEC] = [])).push(cmd.name);
102130
+ if (!parsed.execCmd) parsed.execCmd = cmd;
102131
+ }
102132
+ count++;
102133
+ }
102134
+ return count;
102135
+ }
102136
+ /**
102137
+ *
102138
+ * @param includeSubCommands
102139
+ * @param parsed - The parsed result containing remaining args after --
102140
+ * @returns
102141
+ */
102142
+ async invokeExecAsync(includeSubCommands, parsed) {
102143
+ let count = 0;
102144
+ const cmds = this.getExecCommands([], includeSubCommands);
102145
+ for (const cmd of cmds) {
102146
+ await cmd.cmdBase.exec(cmd, parsed);
102147
+ if (parsed && !parsed.execCmd) parsed.execCmd = cmd;
102148
+ count++;
102149
+ }
102150
+ return count;
102151
+ }
102152
+ /**
102153
+ * Add a sub command to this node
102154
+ * @param name
102155
+ * @returns node for the new command
102156
+ */
102157
+ addCommandNode(node) {
102158
+ node[_PARENT] = this;
102159
+ this.subCmdNodes[node.name] = node;
102160
+ return node;
102161
+ }
102162
+ getErrorNodes(errorNodes = []) {
102163
+ if (this.hasErrors) errorNodes.push(this);
102164
+ for (const _key in this.optNodes) {
102165
+ const _optNode = this.optNodes[_key];
102166
+ if (_optNode.hasErrors) errorNodes.push(_optNode);
102167
+ }
102168
+ for (const _key in this.subCmdNodes) this.subCmdNodes[_key].getErrorNodes(errorNodes);
102169
+ return errorNodes;
102170
+ }
102171
+ /**
102172
+ * Add a option to this node
102173
+ * @param name
102174
+ * @returns node for the new option
102175
+ */
102176
+ addOptionNode(node) {
102177
+ node[_PARENT] = this;
102178
+ const name = node.option.name || node.name;
102179
+ this.optNodes[name] = node;
102180
+ if (!this.optCount[name]) this.optCount[name] = 0;
102181
+ this.optCount[name]++;
102182
+ return node;
102183
+ }
102184
+ removeOptionNode(name) {
102185
+ if (this.optNodes[name]) {
102186
+ delete this.optNodes[name];
102187
+ delete this.optCount[name];
102188
+ }
102189
+ }
102190
+ /**
102191
+ *
102192
+ */
102193
+ applyDefaults() {
102194
+ for (const optName in this.optNodes) this.optNodes[optName].applyDefaults();
102195
+ const options = this.cmdBase.options._options;
102196
+ for (const optName in options) {
102197
+ const opt = options[optName];
102198
+ if (opt.spec.hasOwnProperty("argDefault") && !this.optNodes[optName]) new ClapNodeGenerator(this).addOptionWithArgs(optName, [].concat(opt.spec.argDefault), opt);
102199
+ }
102200
+ for (const subCmdName in this.subCmdNodes) this.subCmdNodes[subCmdName].applyDefaults();
102201
+ }
102202
+ /**
102203
+ * Allow you to apply extra config to the parsed object, overriding any `opts` with `source` not start with `cli`.
102204
+ *
102205
+ * For example, you can allow user to specify options in their `package.json` file, and apply those after the command line is parsed.
102206
+ * @param config - Config object containing user options config
102207
+ * @param src - Name of the source that provided the config. Default to `user`
102208
+ * @returns
102209
+ */
102210
+ applyConfig(config, src = "user") {
102211
+ for (const key in config) {
102212
+ const data = {
102213
+ name: key,
102214
+ value: config[key],
102215
+ verbatim: config[key],
102216
+ arg: config[key],
102217
+ dashes: 0
102218
+ };
102219
+ const matchOpt = this.cmdBase.options.match(data);
102220
+ if (matchOpt) {
102221
+ const optNode = this.optNodes[matchOpt.name];
102222
+ if (!optNode || !optNode.source.startsWith("cli")) {
102223
+ this.removeOptionNode(matchOpt.name);
102224
+ new ClapNodeGenerator(this).addOptionWithArgs(matchOpt.name, [].concat(data.arg), matchOpt.option, src);
102225
+ }
102226
+ } else if (!this.optNodes[key]) new ClapNodeGenerator(this).addOptionWithArgs(key, [].concat(data.arg), void 0, src);
102227
+ }
102228
+ }
102229
+ /**
102230
+ * For options that has names with - in them, add it using its name converted to camelCase.
102231
+ */
102232
+ makeCamelCaseOptions() {
102233
+ for (const key in this.optNodes) {
102234
+ const camelCaseKey = camelCase(key);
102235
+ if (camelCaseKey !== key && !this.optNodes[camelCaseKey]) {
102236
+ this.optNodes[camelCaseKey] = this.optNodes[key];
102237
+ this.optCount[camelCaseKey] = this.optCount[key];
102238
+ }
102239
+ }
102240
+ for (const key in this.subCmdNodes) this.subCmdNodes[key].makeCamelCaseOptions();
102241
+ }
102242
+ /**
102243
+ * check for missing options that are required
102244
+ *
102245
+ * @param missing
102246
+ * @returns
102247
+ */
102248
+ checkRequiredOptions(missing = []) {
102249
+ const _opts = this.cmdBase.options._options;
102250
+ for (const name in _opts) if (_opts[name].spec.required) {
102251
+ if (!this.optNodes[name]) missing.push(name);
102252
+ }
102253
+ for (const kCmd in this.subCmdNodes) this.subCmdNodes[kCmd].checkRequiredOptions(missing);
102254
+ return missing;
102255
+ }
102256
+ get jsonMeta() {
102257
+ if (this._jsonMeta) return this._jsonMeta;
102258
+ const opts = {};
102259
+ const optsFull = {};
102260
+ const source = {};
102261
+ for (const name in this.optNodes) {
102262
+ const variants = [name];
102263
+ const node = this.optNodes[name];
102264
+ if (node.alias && name !== node.alias) variants.push(node.alias);
102265
+ for (const _name of variants) {
102266
+ if (node.option.isSingleArg) opts[_name] = node.argsMap[0];
102267
+ else if (node.option.isCounting) opts[_name] = this.optCount[node.option.name];
102268
+ else if (!node.option.hasArgs) opts[_name] = node.argsMap[0];
102269
+ else opts[_name] = node.argsMap;
102270
+ optsFull[_name] = node.argsMap;
102271
+ source[_name] = this.optNodes[name].source;
102272
+ }
102273
+ }
102274
+ const subCommands = {};
102275
+ for (const name in this.subCmdNodes) subCommands[name] = this.subCmdNodes[name].jsonMeta;
102276
+ const meta = {
102277
+ name: this.name,
102278
+ alias: this.alias,
102279
+ argList: this.argsList,
102280
+ args: this.argsMap,
102281
+ opts,
102282
+ optsFull,
102283
+ optsCount: this.optCount,
102284
+ source,
102285
+ verbatim: {},
102286
+ subCommands
102287
+ };
102288
+ this._jsonMeta = meta;
102289
+ return meta;
102290
+ }
102291
+ };
102292
+
100947
102293
  //#endregion
100948
102294
  //#region node_modules/@fynjs/cli-args/dist/node-generator.js
100949
102295
  const BUILDER_STATUS_GATHER_END = 1;
@@ -107931,8 +109277,9 @@ var FynGlobal = class {
107931
109277
  /**
107932
109278
  * Show PATH setup instructions
107933
109279
  */
107934
- showPathSetup() {
107935
- const binPath = Path.join(this.globalRoot, "current", "bin");
109280
+ async showPathSetup() {
109281
+ const stableBin = Path.join(this.globalRoot, "bin");
109282
+ const binPath = await file_ops_default.exists(stableBin) ? stableBin : this.globalBinDir;
107936
109283
  const isWindows = process.platform === "win32";
107937
109284
  console.log("\nTo use globally installed packages, add the bin directory to your PATH:\n");
107938
109285
  if (isWindows) {
@@ -109027,6 +110374,23 @@ const options = {
109027
110374
  desc: "fetch package dist tarball during dep resolving",
109028
110375
  argDefault: "false"
109029
110376
  },
110377
+ "script-policy": {
110378
+ args: "<mode string>",
110379
+ desc: "install script policy: all, source, review (default), or off"
110380
+ },
110381
+ "allow-scripts": {
110382
+ args: "[packages string..]",
110383
+ desc: "let these packages run install scripts, for this run only"
110384
+ },
110385
+ "deny-scripts": {
110386
+ args: "[packages string..]",
110387
+ desc: "block these packages from running install scripts, whatever approved them"
110388
+ },
110389
+ "allow-scripts-pin": {
110390
+ args: "<flag boolean>",
110391
+ desc: "pin script approvals to the reviewed version",
110392
+ argDefault: "true"
110393
+ },
109030
110394
  "central-store": {
109031
110395
  args: "<flag boolean>",
109032
110396
  alias: ["central", "cs"],
@@ -109214,6 +110578,10 @@ const commands = {
109214
110578
  "audit-file": {
109215
110579
  desc: "write audit report JSON to a file after install",
109216
110580
  args: "<path string>"
110581
+ },
110582
+ "allow-scripts-pending": {
110583
+ desc: "also report which packages would need approval under script policy review",
110584
+ args: "<flag boolean>"
109217
110585
  }
109218
110586
  }
109219
110587
  },
@@ -109262,6 +110630,11 @@ const commands = {
109262
110630
  args: "<flag boolean>",
109263
110631
  desc: "save fyn section to package-fyn.json",
109264
110632
  argDefault: "false"
110633
+ },
110634
+ audit: {
110635
+ desc: "run security audit after install (use --no-audit to skip)",
110636
+ args: "<flag boolean>",
110637
+ default: true
109265
110638
  }
109266
110639
  }
109267
110640
  },
@@ -109282,14 +110655,21 @@ const commands = {
109282
110655
  pickOpts.noStartupInfo = true;
109283
110656
  fyntil.resetFynpo();
109284
110657
  logger$1.info("installing...");
109285
- return await new FynCli(pickOpts).install();
110658
+ return await new FynCli(pickOpts).install({ opts: { audit: meta.opts.audit } });
109286
110659
  }
109287
110660
  },
109288
- options: { install: {
109289
- args: "<flag boolean>",
109290
- argDefault: "true",
109291
- desc: "Run install after removed"
109292
- } }
110661
+ options: {
110662
+ install: {
110663
+ args: "<flag boolean>",
110664
+ argDefault: "true",
110665
+ desc: "Run install after removed"
110666
+ },
110667
+ audit: {
110668
+ desc: "run security audit after install (use --no-audit to skip)",
110669
+ args: "<flag boolean>",
110670
+ default: true
110671
+ }
110672
+ }
109293
110673
  },
109294
110674
  stat: {
109295
110675
  desc: "Show stats of installed packages",
@@ -109375,6 +110755,60 @@ const commands = {
109375
110755
  args: "<flag boolean>"
109376
110756
  } }
109377
110757
  },
110758
+ "install-scripts": {
110759
+ desc: "Review which packages may run install scripts",
110760
+ subCommands: {
110761
+ ls: {
110762
+ desc: "List packages awaiting install-script review",
110763
+ alias: "list",
110764
+ options: { json: {
110765
+ args: "<flag boolean>",
110766
+ desc: "emit the list as JSON"
110767
+ } },
110768
+ async exec(cmd) {
110769
+ return new FynCli(await pickOptions(cmd)).installScripts("ls", cmd.jsonMeta);
110770
+ }
110771
+ },
110772
+ approve: {
110773
+ desc: "Allow packages to run their install scripts",
110774
+ args: "[packages string..]",
110775
+ options: {
110776
+ all: {
110777
+ args: "<flag boolean>",
110778
+ desc: "approve every package awaiting review"
110779
+ },
110780
+ local: {
110781
+ args: "<flag boolean>",
110782
+ desc: "write to this package's package.json instead of the monorepo's fynpo.json"
110783
+ }
110784
+ },
110785
+ async exec(cmd) {
110786
+ return new FynCli(await pickOptions(cmd)).installScripts("approve", cmd.jsonMeta);
110787
+ }
110788
+ },
110789
+ deny: {
110790
+ desc: "Deny packages outright - wins over any approval",
110791
+ args: "[packages string..]",
110792
+ options: { local: {
110793
+ args: "<flag boolean>",
110794
+ desc: "write to this package's package.json instead of the monorepo's fynpo.json"
110795
+ } },
110796
+ async exec(cmd) {
110797
+ return new FynCli(await pickOptions(cmd)).installScripts("deny", cmd.jsonMeta);
110798
+ }
110799
+ },
110800
+ prune: {
110801
+ desc: "Drop approvals for packages that are no longer installed",
110802
+ options: { local: {
110803
+ args: "<flag boolean>",
110804
+ desc: "write to this package's package.json instead of the monorepo's fynpo.json"
110805
+ } },
110806
+ async exec(cmd) {
110807
+ return new FynCli(await pickOptions(cmd)).installScripts("prune", cmd.jsonMeta);
110808
+ }
110809
+ }
110810
+ }
110811
+ },
109378
110812
  "sync-local": {
109379
110813
  desc: "Refresh locally linked package files",
109380
110814
  alias: "sl",
@@ -109529,7 +110963,7 @@ const commands = {
109529
110963
  desc: "Directory for global packages (default: ~/.fyn/global)"
109530
110964
  } },
109531
110965
  async exec(cmd) {
109532
- (await makeFynGlobal(cmd)).showPathSetup();
110966
+ await (await makeFynGlobal(cmd)).showPathSetup();
109533
110967
  }
109534
110968
  }
109535
110969
  }