fyn 3.3.0 → 3.3.1

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/README.md CHANGED
@@ -22,6 +22,12 @@ cd <your-project>
22
22
  fyn
23
23
  ```
24
24
 
25
+ Once fyn is installed, it can manage its own global install:
26
+
27
+ ```sh
28
+ fyn global add fyn
29
+ ```
30
+
25
31
  On an existing install, fyn checks file modification times before reinstalling. This scan
26
32
  prunes directories and files matched by repository and nested `.gitignore` rules, alongside
27
33
  its built-in exclusions. The rules apply even to tracked source files; manifests, lockfiles,
@@ -51,6 +57,16 @@ fyn outdated lodash --json
51
57
  and the registry's `latest` tag. Local, Git, and URL dependencies are not compared. Pass
52
58
  `--refresh-meta` to bypass fresh registry metadata caches.
53
59
 
60
+ To move locked versions to the newest their declared ranges allow:
61
+
62
+ ```sh
63
+ fyn update
64
+ fyn update lodash
65
+ ```
66
+
67
+ With no names, `update` ignores every lock pin. With names, it updates those packages and their
68
+ dependencies and keeps the rest pinned. It never edits `package.json`.
69
+
54
70
  - It can read and use some settings from your `.npmrc`.
55
71
  - It can use `npm-shrinkwrap.json` or `package-lock.json` files.
56
72
 
@@ -4770,9 +4770,9 @@ function treeCollector(strip) {
4770
4770
  fromHeaders: true
4771
4771
  };
4772
4772
  const onentry = (entry) => {
4773
- const parts = entry.path.split(/\/|\\/);
4774
4773
  const isDir = entry.type === "Directory";
4775
- const wtree = parts.slice(strip, isDir ? parts.length : parts.length - 1).reduce((wt, dir) => {
4774
+ const parts = entry.path.split(/\/|\\/).slice(strip).filter((p) => p && p !== ".");
4775
+ const wtree = (isDir ? parts : parts.slice(0, -1)).reduce((wt, dir) => {
4776
4776
  return wt[dir] || (wt[dir] = newDirObj());
4777
4777
  }, result.tree);
4778
4778
  if (isDir) return;
package/dist/fyn.mjs CHANGED
@@ -1238,7 +1238,7 @@ const defaultPathFilter = [new RegExp(`/node_modules.*/(pirates/|isomorphic-load
1238
1238
  function cleanErrorStack(error, { replacePath = `${process.cwd()}/`, ignorePathFilter = [] } = {}) {
1239
1239
  const stack = error && (error.stack || error.message);
1240
1240
  if (!stack) return String(stack);
1241
- return stack.split("\n").map((line) => {
1241
+ return stack.split(/\r?\n/).map((line) => {
1242
1242
  if (!line.match(/ {4,}at/)) return line;
1243
1243
  const match = line.match(/( {4,}at )(?:([^(]*\()(.+)(\).*)|(.+))/);
1244
1244
  const [, at, name = "", location = match === null || match === void 0 ? void 0 : match[5], tail = ""] = match || [];
@@ -1246,7 +1246,10 @@ function cleanErrorStack(error, { replacePath = `${process.cwd()}/`, ignorePathF
1246
1246
  const path = location.replace(/\\/g, "/");
1247
1247
  if (defaultPathFilter.concat(ignorePathFilter).find((s) => s && (s instanceof RegExp ? path.match(s) : path.includes(s)))) return false;
1248
1248
  let path2 = path;
1249
- if (replacePath && replacePath.length > 1) path2 = path.startsWith(`file://${replacePath}`) ? path.slice(`file://${replacePath}`.length) : path.replace(replacePath, "");
1249
+ if (replacePath && replacePath.length > 1) {
1250
+ const prefix = path.startsWith(`file://${replacePath}`) ? `file://${replacePath}` : replacePath;
1251
+ if (path.startsWith(prefix)) path2 = path.slice(prefix.length);
1252
+ }
1250
1253
  return `${at}${name}${path2}${tail}`;
1251
1254
  }).filter((x) => x !== false).join("\n");
1252
1255
  }
@@ -1258,9 +1261,10 @@ function cleanErrorStack(error, { replacePath = `${process.cwd()}/`, ignorePathF
1258
1261
  * @returns aggregate stack
1259
1262
  */
1260
1263
  function aggregateStack(stack, errors) {
1261
- return [stack].concat(errors && errors.map && errors.map((e) => {
1264
+ const stacks = errors && errors.map ? errors.map((e) => {
1262
1265
  return (e && (e.stack || e.message) || String(e)).replace(/^/gm, " ");
1263
- })).join("\n");
1266
+ }) : [];
1267
+ return [stack].concat(stacks).join("\n");
1264
1268
  }
1265
1269
  /**
1266
1270
  * build the aggregate stack of an AggregateError
@@ -1291,11 +1295,18 @@ var AggregateError$1 = class extends globalThis.AggregateError {
1291
1295
  },
1292
1296
  __stack: {
1293
1297
  enumerable: false,
1298
+ writable: true,
1294
1299
  value: this.stack
1295
1300
  },
1296
- stack: { get() {
1297
- return aggStack || (aggStack = aggregateErrorStack(this));
1298
- } }
1301
+ stack: {
1302
+ get() {
1303
+ return aggStack || (aggStack = aggregateErrorStack(this));
1304
+ },
1305
+ set(value) {
1306
+ this.__stack = value;
1307
+ aggStack = "";
1308
+ }
1309
+ }
1299
1310
  });
1300
1311
  }
1301
1312
  };
@@ -97525,6 +97536,51 @@ var PkgDepLocker = class {
97525
97536
  if (sorted) remove$3(sorted, (x) => x === item.resolved);
97526
97537
  }
97527
97538
  }
97539
+ /**
97540
+ * Locked versions by package name, newest first, from the serialized lock data
97541
+ */
97542
+ lockedVersions() {
97543
+ assert(this._isFynFormat, "lock data is no longer in fyn format");
97544
+ const result = {};
97545
+ for (const name in this._lockData) {
97546
+ if (name.startsWith("$")) continue;
97547
+ const versions = Object.keys(this._lockData[name]).filter((v) => !v.startsWith("_"));
97548
+ if (versions.length > 0) result[name] = versions.sort(simpleCompare);
97549
+ }
97550
+ return result;
97551
+ }
97552
+ /**
97553
+ * Drop lock data so the resolver picks the newest versions the ranges allow.
97554
+ * With no names, drop every package. With names, drop those packages and
97555
+ * everything they depend on, directly or not.
97556
+ *
97557
+ * @param names - packages to unlock, or none for all
97558
+ * @returns the unlocked package names
97559
+ */
97560
+ unlock(names = []) {
97561
+ assert(this._isFynFormat, "lock data is no longer in fyn format");
97562
+ const data = this._lockData;
97563
+ const pending = names.length > 0 ? [...names] : Object.keys(data).filter((n) => !n.startsWith("$"));
97564
+ const unlocked = /* @__PURE__ */ new Set();
97565
+ while (pending.length > 0) {
97566
+ const name = pending.pop();
97567
+ const pkgLocked = data[name];
97568
+ if (unlocked.has(name) || !pkgLocked) continue;
97569
+ unlocked.add(name);
97570
+ if (names.length === 0) continue;
97571
+ for (const version in pkgLocked) {
97572
+ if (version.startsWith("_")) continue;
97573
+ const vpkg = pkgLocked[version];
97574
+ const deps = {
97575
+ ...vpkg.dependencies,
97576
+ ...vpkg.optionalDependencies
97577
+ };
97578
+ for (const dep in deps) pending.push(parseNpmAlias(deps[dep], dep)?.name || dep);
97579
+ }
97580
+ }
97581
+ for (const name of unlocked) delete data[name];
97582
+ return [...unlocked].sort();
97583
+ }
97528
97584
  shasum(data) {
97529
97585
  return Crypto.createHash("sha1").update(data).digest("hex");
97530
97586
  }
@@ -97709,7 +97765,8 @@ const typeGetters = {
97709
97765
  return lower === "true" || lower === "yes" || lower === "1" || lower === "on";
97710
97766
  },
97711
97767
  truthy: (x) => !!x,
97712
- json: (x) => JSON.parse(x)
97768
+ json: (x) => JSON.parse(x),
97769
+ array: (x) => JSON.parse(x)
97713
97770
  };
97714
97771
  const getEnvName = (optKey, opt, env) => {
97715
97772
  if (!Object.prototype.hasOwnProperty.call(opt, "env")) return void 0;
@@ -97732,9 +97789,12 @@ const xenvConfig = (spec, userConfig, options) => {
97732
97789
  userConfig = userConfig || {};
97733
97790
  const trace = {};
97734
97791
  const getType = (opt) => {
97735
- const tod = Object.prototype.hasOwnProperty.call(opt, "default") && typeof opt.default;
97736
- const type = opt.type || tod !== "function" && tod || "string";
97737
- return type === "object" ? "json" : type;
97792
+ if (opt.type) return opt.type === "object" ? "json" : opt.type;
97793
+ if (!Object.prototype.hasOwnProperty.call(opt, "default")) return "string";
97794
+ if (opt.default === null) return "string";
97795
+ if (Array.isArray(opt.default)) return "array";
97796
+ const tod = typeof opt.default;
97797
+ return tod === "object" ? "json" : tod === "function" ? "string" : tod;
97738
97798
  };
97739
97799
  const getGetter = (opt) => {
97740
97800
  const type = getType(opt);
@@ -97768,17 +97828,26 @@ const xenvConfig = (spec, userConfig, options) => {
97768
97828
  }
97769
97829
  };
97770
97830
  const sources = [...options && options.sources || ["env", "option"], "default"];
97831
+ const badSource = sources.find((s) => !Object.prototype.hasOwnProperty.call(getters, s));
97832
+ if (badSource !== void 0) throw new Error(`xenv-config: unknown source "${badSource}"; valid sources are ${Object.keys(getters).join(", ")}`);
97771
97833
  const config = Object.keys(spec).reduce((cfg, k) => {
97834
+ var _a;
97772
97835
  const opt = spec[k];
97773
97836
  const type = getType(opt);
97774
97837
  let found;
97775
97838
  if (type !== "json") sources.find((s) => found = getters[s](opt, k));
97776
97839
  else {
97777
97840
  const mapped = sources.map((s) => getters[s](opt, k)).filter((x) => Boolean(x));
97778
- if (mapped.length > 0) found = {
97779
- trace: { src: mapped.map((x) => x.trace.src).join(",") },
97780
- value: merge.apply(null, [{}].concat(mapped.map((x) => x.value).reverse()))
97781
- };
97841
+ if (mapped.length > 0) {
97842
+ const name = (_a = mapped.find((x) => x.trace.name)) === null || _a === void 0 ? void 0 : _a.trace.name;
97843
+ found = {
97844
+ trace: {
97845
+ src: mapped.map((x) => x.trace.src).join(","),
97846
+ ...name && { name }
97847
+ },
97848
+ value: merge.apply(null, [{}].concat(mapped.map((x) => x.value).reverse()))
97849
+ };
97850
+ }
97782
97851
  }
97783
97852
  if (!found) return cfg;
97784
97853
  trace[k] = found.trace;
@@ -97874,9 +97943,9 @@ function treeCollector(strip) {
97874
97943
  fromHeaders: true
97875
97944
  };
97876
97945
  const onentry = (entry) => {
97877
- const parts = entry.path.split(/\/|\\/);
97878
97946
  const isDir = entry.type === "Directory";
97879
- const wtree = parts.slice(strip, isDir ? parts.length : parts.length - 1).reduce((wt, dir) => {
97947
+ const parts = entry.path.split(/\/|\\/).slice(strip).filter((p) => p && p !== ".");
97948
+ const wtree = (isDir ? parts : parts.slice(0, -1)).reduce((wt, dir) => {
97880
97949
  return wt[dir] || (wt[dir] = newDirObj());
97881
97950
  }, result.tree);
97882
97951
  if (isDir) return;
@@ -98235,12 +98304,15 @@ var FynCentral = class {
98235
98304
  await prepare();
98236
98305
  }
98237
98306
  };
98307
+ const placing = [];
98238
98308
  try {
98239
98309
  const info = await this.getInfo(integrity);
98240
98310
  const list = flattenTree(info.tree, {
98241
98311
  dirs: [],
98242
98312
  files: []
98243
98313
  }, "");
98314
+ list.dirs = [...new Set(list.dirs)];
98315
+ list.files = [...new Set(list.files)];
98244
98316
  const srcDir = Path.join(info.contentPath, "package");
98245
98317
  const cloneDir = this._reflink && this._cloneDirs && await loadReflinkCloneDir();
98246
98318
  if (cloneDir) {
@@ -98286,10 +98358,14 @@ var FynCentral = class {
98286
98358
  await map$1(list.files, (file) => {
98287
98359
  const src = Path.join(info.contentPath, "package", file);
98288
98360
  const dest = Path.join(destDir, file);
98289
- if (file === "package.json") return this._reflink && !copy ? cloneFile(src, dest, true) : copyFile(src, dest);
98290
- return this._hardlink ? this._linkFile(src, dest) : this._cloneOrCopy(src, dest);
98361
+ let placed;
98362
+ if (file === "package.json") placed = this._reflink && !copy ? cloneFile(src, dest, true) : copyFile(src, dest);
98363
+ else placed = this._hardlink ? this._linkFile(src, dest) : this._cloneOrCopy(src, dest);
98364
+ placing.push(placed);
98365
+ return placed;
98291
98366
  }, { concurrency: 5 });
98292
98367
  } catch (err) {
98368
+ await Promise.allSettled(placing);
98293
98369
  const msg = `fyn-central can't replicate package at ${destDir} for integrity ${integrity}`;
98294
98370
  throw new AggregateError$1([err], msg);
98295
98371
  }
@@ -105982,7 +106058,8 @@ var PkgOutdatedProvider = class {
105982
106058
  if (failed) throw failed.reason;
105983
106059
  return {
105984
106060
  records: settled.map((result) => result.status === "fulfilled" ? result.value : void 0).filter((record) => Boolean(record)),
105985
- skipped: skipped.sort()
106061
+ skipped: skipped.sort(),
106062
+ checked: selected.length - skipped.length
105986
106063
  };
105987
106064
  }
105988
106065
  };
@@ -106029,6 +106106,7 @@ const showOutdated = async (fyn, options = {}) => {
106029
106106
  if (output) await new Promise((resolve, reject) => {
106030
106107
  process.stdout.write(`${output}\n`, (error) => error ? reject(error) : resolve());
106031
106108
  });
106109
+ else if (!options.json) logger$1.info(result.checked === 1 ? "The only direct dependency is up to date" : result.checked > 1 ? `All ${result.checked} direct dependencies are up to date` : "No registry dependencies to check");
106032
106110
  if (!options.json && options.packages?.length) for (const name of result.skipped) logger$1.info(`Skipping non-registry dependency ${chalk.cyan(name)}`);
106033
106111
  return result;
106034
106112
  };
@@ -106608,6 +106686,28 @@ const showAudit = (fyn, opts) => {
106608
106686
  }).runAudit();
106609
106687
  };
106610
106688
 
106689
+ //#endregion
106690
+ //#region cli/show-updates.ts
106691
+ /**
106692
+ * Lines of `name old -> new` for every package whose locked versions changed
106693
+ */
106694
+ const formatUpdates = (before, after) => {
106695
+ const names = [.../* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)])].sort();
106696
+ const lines = [];
106697
+ for (const name of names) {
106698
+ const oldVersions = (before[name] || []).join(", ");
106699
+ const newVersions = (after[name] || []).join(", ");
106700
+ if (oldVersions === newVersions) continue;
106701
+ lines.push(`${chalk.cyan(name)} ${chalk.red(oldVersions || "(none)")} -> ${chalk.green(newVersions || "(removed)")}`);
106702
+ }
106703
+ return lines;
106704
+ };
106705
+ const showUpdates = (before, after) => {
106706
+ const lines = formatUpdates(before, after);
106707
+ if (lines.length === 0) logger$1.info("All locked versions are already the newest their ranges allow");
106708
+ else logger$1.info(`Updated ${lines.length} packages:\n ${lines.join("\n ")}`);
106709
+ };
106710
+
106611
106711
  //#endregion
106612
106712
  //#region node_modules/.f/_/ini/4.1.1/node_modules/ini/lib/ini.js
106613
106713
  var require_ini = /* @__PURE__ */ __commonJSMin(((exports, module) => {
@@ -108148,6 +108248,8 @@ var FynCli = class {
108148
108248
  const start = Date.now();
108149
108249
  const runAudit = argv.opts?.audit !== false;
108150
108250
  const auditFile = argv.opts?.auditFile;
108251
+ const update = argv.opts?.update;
108252
+ let lockedBefore;
108151
108253
  const auditAfterInstall = async () => {
108152
108254
  if (!runAudit) return;
108153
108255
  try {
@@ -108174,6 +108276,7 @@ var FynCli = class {
108174
108276
  }
108175
108277
  installLocked = await this.fyn.createInstallLock();
108176
108278
  await this.fyn.readLockFiles();
108279
+ if (update) lockedBefore = this.unlockForUpdate(update);
108177
108280
  await this.fyn._startInstall();
108178
108281
  const pkg = this.fyn._pkg;
108179
108282
  if (get$1(pkg, "scripts.preinstall")) {
@@ -108274,6 +108377,7 @@ var FynCli = class {
108274
108377
  const end = Date.now();
108275
108378
  await this.fyn.saveFynpoIndirects();
108276
108379
  logger$1.info(chalk.green("complete in total"), chalk.magenta(`${(end - start) / 1e3}`) + "secs");
108380
+ if (lockedBefore) showUpdates(lockedBefore, this.fyn.depLocker.lockedVersions());
108277
108381
  await auditAfterInstall();
108278
108382
  }).catch(async (err) => {
108279
108383
  if (err.message === "No Change") {
@@ -108298,6 +108402,21 @@ var FynCli = class {
108298
108402
  fyntil.exit(0);
108299
108403
  });
108300
108404
  }
108405
+ /**
108406
+ * Drop lock data for `fyn update` so the resolver picks the newest in-range versions
108407
+ *
108408
+ * @param names - packages to update, or empty for all
108409
+ * @returns the locked versions from before
108410
+ */
108411
+ unlockForUpdate(names) {
108412
+ const locker = this.fyn.depLocker;
108413
+ const before = locker.lockedVersions();
108414
+ const missing = names.filter((name) => !before[name]);
108415
+ if (missing.length > 0) throw new Error(`not found in lock data: ${missing.join(", ")}`);
108416
+ const unlocked = locker.unlock(names);
108417
+ logger$1.info(`updating ${unlocked.length} locked packages to the newest versions their ranges allow`);
108418
+ return before;
108419
+ }
108301
108420
  stat(argv) {
108302
108421
  return showStat(this.fyn, argv.args.packages).finally(() => {
108303
108422
  return this._opts.saveLogs && this.saveLogs(this._opts.saveLogs);
@@ -118071,6 +118190,30 @@ const commands = {
118071
118190
  }
118072
118191
  }
118073
118192
  },
118193
+ update: {
118194
+ args: "[packages string..]",
118195
+ usage: "$0 $1 [packages..]",
118196
+ desc: "Update locked versions to the newest their package.json ranges allow",
118197
+ exec: async (cmd) => {
118198
+ const meta = cmd.jsonMeta;
118199
+ const config = await pickOptions(cmd);
118200
+ Object.assign(config.opts, {
118201
+ forceInstall: true,
118202
+ refreshMeta: true,
118203
+ npmLock: false
118204
+ });
118205
+ const update = meta.args.packages || [];
118206
+ return new FynCli(config).install({ opts: {
118207
+ audit: meta.opts.audit !== false,
118208
+ update
118209
+ } });
118210
+ },
118211
+ options: { audit: {
118212
+ desc: "run security audit after update (use --no-audit to skip)",
118213
+ args: "<flag boolean>",
118214
+ argDefault: "true"
118215
+ } }
118216
+ },
118074
118217
  stat: {
118075
118218
  desc: "Show stats of installed packages",
118076
118219
  usage: "$0 $1 <package-name>[@semver] [...]",
package/docs/reference.md CHANGED
@@ -37,6 +37,7 @@ Aliases are in parentheses. Command options are listed with each command. Global
37
37
  | `install` (`i`) | Resolve, fetch and install dependencies. Default command. |
38
38
  | `add` (`a`) | Add packages to `package.json`, then install. |
39
39
  | `remove` (`rm`) | Remove packages from `package.json`, then install. |
40
+ | `update` | Update locked versions to the newest their ranges allow, then install. |
40
41
  | `stat` | Show where an installed package comes from and who depends on it. |
41
42
  | `outdated` | List direct registry dependencies that have newer versions. |
42
43
  | `audit` | Check resolved packages for known vulnerabilities. |
@@ -132,6 +133,25 @@ fyn remove <packages..> [--install] [--audit]
132
133
 
133
134
  Removes each name from `dependencies`, `devDependencies`, `optionalDependencies`, `peerDependencies`, and from the same sections inside the `fyn` block. Empty sections are deleted. Names not found are logged as `These packages don't exist in your package.json`. If nothing was removed it logs `No package was removed`, skips the install and does not set a failing exit code. No names given: `No packages to remove`, exit 1.
134
135
 
136
+ ### `update`
137
+
138
+ ```
139
+ fyn update [packages..] [--audit]
140
+ ```
141
+
142
+ | Option | Type | Default | Behavior |
143
+ | --- | --- | --- | --- |
144
+ | `--audit` | boolean | true | Run a summary audit after the update. |
145
+
146
+ Re-resolves dependencies to the newest versions their declared ranges allow, then installs.
147
+
148
+ - No names: ignores every pin in `fyn-lock.yaml`, or in `<targetDir>/.f/lock.yaml` when there is no lockfile. `package-lock.json`, `npm-shrinkwrap.json` and `yarn.lock` are also ignored.
149
+ - With names: unlocks those packages and every package they depend on, directly or not. Everything else stays pinned. A shared dependency in that subtree updates for all its dependents.
150
+ - A name that is not in the lock data fails with `not found in lock data: <names>`, exit 1.
151
+ - Always fetches fresh registry metadata and skips the "No Change" shortcut.
152
+ - Never edits `package.json`. Use `add` to move past a declared range.
153
+ - After install it prints `name old -> new` for each package whose locked versions changed, then the audit summary.
154
+
135
155
  ### `stat`
136
156
 
137
157
  ```
@@ -160,7 +180,7 @@ Rules:
160
180
  - `current` is the version in `<targetDir>/<name>/package.json`, or `null` (shown as `MISSING`) when it is not installed.
161
181
  - A dependency is reported unless `current === wanted` and (`latest` is missing or `wanted === latest`).
162
182
  - Record fields: `name`, `type` (`prod`, `dev`, `optional`, `devOptional`), `requested`, `current`, `wanted`, `latest`, and `resolvedName` for aliases.
163
- - The table has columns `Package`, `Current`, `Wanted`, `Latest`, `Type`, `Requested`. Empty results print nothing.
183
+ - The table has columns `Package`, `Current`, `Wanted`, `Latest`, `Type`, `Requested`. Empty results log `All N direct dependencies are up to date`, or `No registry dependencies to check` when everything was skipped. `--json` prints `[]`.
164
184
  - Errors (bad spec, missing tag target, no matching version, registry failure, unreadable installed `package.json`) are thrown and make the process exit 1.
165
185
 
166
186
  Exit code: 1 when at least one record is reported, 0 when none. It does not change any file.
@@ -648,6 +668,7 @@ Precedence: an explicit option value (command line, rc, or fynpo `fyn.options.en
648
668
  | Uncaught rejection in the bin wrapper | 1 (prints the error). |
649
669
  | `install` | 1 on failure. 0 on success or "No Change". |
650
670
  | `add`, `remove` | 1 for no package names. Install failures follow `install`. |
671
+ | `update` | 1 for a name that is not locked. Install failures follow `install`. |
651
672
  | `outdated` | 1 when anything is outdated. Errors also 1. |
652
673
  | `audit` | 0 even with findings. Failures logged only. |
653
674
  | `run` | The script's exit code, else 1. Missing script 1 (0 with `--if-present`). |
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "fyn",
3
3
  "type": "module",
4
- "version": "3.3.0",
4
+ "version": "3.3.1",
5
5
  "description": "fyn is a node.js package manager, and supports fynpo -- a zero setup monorepo tool",
6
6
  "main": "./bin/index.mjs",
7
7
  "homepage": "https://fynjs.pages.dev/",
@@ -85,7 +85,7 @@
85
85
  "@fynjs/reflink": "^0.2.0"
86
86
  },
87
87
  "devDependencies": {
88
- "@fynjs/run": "^1.1.10",
88
+ "@fynjs/run": "^1.1.11",
89
89
  "@fynjs/ts-resolve": "^1.0.4",
90
90
  "@types/node": "^26.4.1",
91
91
  "@vitest/coverage-v8": "^5.0.0",