fyn 3.2.1 → 3.2.2

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.
Files changed (3) hide show
  1. package/README.md +2 -0
  2. package/dist/fyn.mjs +81 -16
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -75,6 +75,7 @@ progress: normal
75
75
  logLevel: info
76
76
  production: false
77
77
  centralStore: false
78
+ hardlink: true
78
79
  ```
79
80
 
80
81
  Or as an ini:
@@ -89,6 +90,7 @@ progress=normal
89
90
  logLevel=info
90
91
  production=false
91
92
  centralStore=false
93
+ hardlink=true
92
94
  ```
93
95
 
94
96
  ### Local source exports (`fyn.localExports`)
package/dist/fyn.mjs CHANGED
@@ -92008,6 +92008,25 @@ async function unlinkDest(destFp) {
92008
92008
  }
92009
92009
  }
92010
92010
  /**
92011
+ * Copy with COPYFILE_EXCL, so a new destination takes one call instead of an unlink and a copy.
92012
+ * An existing destination is unlinked and copied again, never written through - see
92013
+ * {@link unlinkDest}. replicate writes into a cleared dir, so the retry is rare.
92014
+ *
92015
+ * @param {*} srcFp
92016
+ * @param {*} destFp
92017
+ * @param {*} mode - extra copyFile flags, such as COPYFILE_FICLONE
92018
+ * @returns
92019
+ */
92020
+ async function copyNew(srcFp, destFp, mode = 0) {
92021
+ try {
92022
+ return await fs.promises.copyFile(srcFp, destFp, mode | fs.constants.COPYFILE_EXCL);
92023
+ } catch (err) {
92024
+ if (err.code !== "EEXIST") throw err;
92025
+ }
92026
+ await unlinkDest(destFp);
92027
+ return fs.promises.copyFile(srcFp, destFp, mode | fs.constants.COPYFILE_EXCL);
92028
+ }
92029
+ /**
92011
92030
  * Copy a file, replacing the destination rather than writing through it.
92012
92031
  *
92013
92032
  * @param {*} srcFp
@@ -92015,16 +92034,17 @@ async function unlinkDest(destFp) {
92015
92034
  * @returns
92016
92035
  */
92017
92036
  async function copyFile(srcFp, destFp) {
92018
- await unlinkDest(destFp);
92019
- if (file_ops_default.copyFile) return file_ops_default.copyFile(srcFp, destFp);
92037
+ if (file_ops_default.copyFile) return copyNew(srcFp, destFp);
92020
92038
  else {
92039
+ await unlinkDest(destFp);
92021
92040
  const srcData = await file_ops_default.readFile(srcFp);
92022
92041
  return file_ops_default.writeFile(destFp, srcData);
92023
92042
  }
92024
92043
  }
92025
92044
  /**
92026
- * Clone a file using copy-on-write if supported (macOS APFS, Linux btrfs/xfs).
92027
- * Falls back to regular copy if CoW is not available.
92045
+ * Clone a file using copy-on-write if supported (Linux btrfs/xfs).
92046
+ * Falls back to regular copy if CoW is not available. libuv has no clonefile on macOS, so
92047
+ * there it is always a full copy.
92028
92048
  *
92029
92049
  * Replaces the destination rather than writing through it - see {@link unlinkDest}.
92030
92050
  *
@@ -92033,8 +92053,7 @@ async function copyFile(srcFp, destFp) {
92033
92053
  * @returns
92034
92054
  */
92035
92055
  async function cloneFile(srcFp, destFp) {
92036
- await unlinkDest(destFp);
92037
- return fs.promises.copyFile(srcFp, destFp, fs.constants.COPYFILE_FICLONE);
92056
+ return copyNew(srcFp, destFp, fs.constants.COPYFILE_FICLONE);
92038
92057
  }
92039
92058
  async function prepDestDir(dest) {
92040
92059
  const statDest = await tryCatch(() => file_ops_default.lstat(dest));
@@ -94791,12 +94810,8 @@ var PkgSrcManager = class {
94791
94810
  if (hasCentral) {
94792
94811
  if (!await central.validate(integrity)) {
94793
94812
  const contentPath = await central.getContentPath(integrity);
94794
- logger$1.error(`Corrupted central store package detected
94795
- -- CORRUPTED CENTRAL STORE PACKAGE DETECTED, removing --
94796
- ID: ${verId}
94797
- integrity: ${integrity}
94798
- path: '${contentPath}'
94799
- `);
94813
+ logger$1.info(`central store copy of ${dispId} has changed, likely from an edit under a project's node_modules. fyn is replacing it with a clean copy. Projects installed earlier keep their edited files.`);
94814
+ logger$1.debug(`replacing central store package ${verId} integrity ${integrity} at '${contentPath}'`);
94800
94815
  await central.delete(integrity);
94801
94816
  hasCentral = false;
94802
94817
  }
@@ -96917,6 +96932,14 @@ const { missPipe } = fyntil;
96917
96932
  function isTreeFileContent(obj) {
96918
96933
  return typeof obj === "object" && obj !== null && "_" in obj && typeof obj._ === "number";
96919
96934
  }
96935
+ /** link errors that mean the filesystem can't hardlink from the store at all */
96936
+ const NO_LINK_CODES = [
96937
+ "EXDEV",
96938
+ "EPERM",
96939
+ "ENOTSUP",
96940
+ "EOPNOTSUPP",
96941
+ "ENOSYS"
96942
+ ];
96920
96943
  /**
96921
96944
  * Convert a directory tree structure to a flatten one like:
96922
96945
  * ```
@@ -96954,9 +96977,11 @@ function flattenTree(tree, output, baseDir) {
96954
96977
  var FynCentral = class {
96955
96978
  _centralDir;
96956
96979
  _map;
96957
- constructor({ centralDir = ".fyn/_central-storage" } = {}) {
96980
+ _hardlink;
96981
+ constructor({ centralDir = ".fyn/_central-storage", hardlink = true } = {}) {
96958
96982
  this._centralDir = Path.resolve(centralDir);
96959
96983
  this._map = /* @__PURE__ */ new Map();
96984
+ this._hardlink = hardlink;
96960
96985
  }
96961
96986
  _analyze(integrity) {
96962
96987
  const sri = import_lib$1.default.parse(integrity, { single: true });
@@ -97129,13 +97154,32 @@ var FynCentral = class {
97129
97154
  const src = Path.join(info.contentPath, "package", file);
97130
97155
  const dest = Path.join(destDir, file);
97131
97156
  if (file === "package.json") return copyFile(src, dest);
97132
- return cloneFile(src, dest);
97157
+ return this._hardlink ? this._linkFile(src, dest) : cloneFile(src, dest);
97133
97158
  }, { concurrency: 5 });
97134
97159
  } catch (err) {
97135
97160
  const msg = `fyn-central can't replicate package at ${destDir} for integrity ${integrity}`;
97136
97161
  throw new AggregateError$1([err], msg);
97137
97162
  }
97138
97163
  }
97164
+ /**
97165
+ * Hardlink a file from the store, or clone it where the filesystem can't link it, such as a
97166
+ * store on another volume. That failure turns linking off for the rest of the run.
97167
+ */
97168
+ async _linkFile(src, dest) {
97169
+ try {
97170
+ await linkFile(src, dest);
97171
+ return;
97172
+ } catch (err) {
97173
+ const code = err.code;
97174
+ if (NO_LINK_CODES.includes(code)) {
97175
+ if (this._hardlink) {
97176
+ this._hardlink = false;
97177
+ logger$1.info(`fyn-central: can't hardlink from ${this._centralDir} (${code}), copying instead`);
97178
+ }
97179
+ } else if (code !== "EMLINK") throw err;
97180
+ }
97181
+ await cloneFile(src, dest);
97182
+ }
97139
97183
  _untarStream(tarStream, targetDir) {
97140
97184
  const newDirObj = () => {
97141
97185
  const n = Object.create(null);
@@ -101514,7 +101558,10 @@ var Fyn = class Fyn {
101514
101558
  }
101515
101559
  logger$1.info(`Enabling central store by fynpo monorepo using dir ${centralDir}`);
101516
101560
  }
101517
- return this._central = new FynCentral({ centralDir });
101561
+ return this._central = new FynCentral({
101562
+ centralDir,
101563
+ hardlink: this.hardlink
101564
+ });
101518
101565
  }
101519
101566
  async _initialize({ noLock = false } = {}) {
101520
101567
  await this._initializePkg();
@@ -101841,6 +101888,7 @@ var Fyn = class Fyn {
101841
101888
  fynlocal: Boolean(this.fynlocal),
101842
101889
  layout: this._options.layout,
101843
101890
  shortPkgDir: this._shortPkgDir,
101891
+ hardlink: this.hardlink,
101844
101892
  blockedScripts: this._blockedScripts || this._installConfig.blockedScripts || [],
101845
101893
  pendingScripts: this._pendingScripts || this._installConfig.pendingScripts || []
101846
101894
  };
@@ -102098,6 +102146,16 @@ var Fyn = class Fyn {
102098
102146
  get production() {
102099
102147
  return this._options.production;
102100
102148
  }
102149
+ /**
102150
+ * Whether the central store hardlinks files into node_modules. --no-hardlink or
102151
+ * FYN_HARDLINK=false turns it off, and .fyn.json keeps the choice for later installs.
102152
+ * Precedence: CLI or rc, then env, then .fyn.json, then on.
102153
+ */
102154
+ get hardlink() {
102155
+ if (this._options.hardlink !== void 0) return this._options.hardlink !== false;
102156
+ if (process.env.FYN_HARDLINK !== void 0) return fyntil.strToBool(process.env.FYN_HARDLINK);
102157
+ return this._installConfig.hardlink !== false;
102158
+ }
102101
102159
  get concurrency() {
102102
102160
  return this._options.concurrency;
102103
102161
  }
@@ -116270,7 +116328,10 @@ const pickOptions = async (cmd, checkFynpo = true) => {
116270
116328
  return {
116271
116329
  opts: allOpts,
116272
116330
  rcData,
116273
- _cliSource: meta.source,
116331
+ _cliSource: {
116332
+ ...cmd.rootCmd?.jsonMeta.source,
116333
+ ...meta.source
116334
+ },
116274
116335
  _fynpo: fynpo
116275
116336
  };
116276
116337
  };
@@ -116332,6 +116393,10 @@ const options = {
116332
116393
  desc: "keep single copy of packages in central store",
116333
116394
  argDefault: "false"
116334
116395
  },
116396
+ hardlink: {
116397
+ args: "<flag boolean>",
116398
+ desc: "hardlink central store files into node_modules (default). --no-hardlink copies them, and is remembered"
116399
+ },
116335
116400
  copy: {
116336
116401
  args: "[packages string..]",
116337
116402
  alias: "cp",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "fyn",
3
3
  "type": "module",
4
- "version": "3.2.1",
4
+ "version": "3.2.2",
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://github.com/jchip/fynjs/tree/main/packages/fyn",