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 +16 -0
- package/dist/fs-worker.mjs +2 -2
- package/dist/fyn.mjs +163 -20
- package/docs/reference.md +22 -1
- package/package.json +2 -2
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
|
|
package/dist/fs-worker.mjs
CHANGED
|
@@ -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
|
|
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(
|
|
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)
|
|
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
|
-
|
|
1264
|
+
const stacks = errors && errors.map ? errors.map((e) => {
|
|
1262
1265
|
return (e && (e.stack || e.message) || String(e)).replace(/^/gm, " ");
|
|
1263
|
-
})
|
|
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: {
|
|
1297
|
-
|
|
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
|
-
|
|
97736
|
-
|
|
97737
|
-
|
|
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)
|
|
97779
|
-
|
|
97780
|
-
|
|
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
|
|
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
|
-
|
|
98290
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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",
|