fyn 3.1.7 → 3.1.9

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
@@ -16,6 +16,12 @@ cd <your-project>
16
16
  fyn
17
17
  ```
18
18
 
19
+ On an existing install, fyn checks file modification times before reinstalling. This scan
20
+ prunes directories and files matched by repository and nested `.gitignore` rules, alongside
21
+ its built-in exclusions. The rules apply even to tracked source files; manifests, lockfiles,
22
+ install configuration and changes to the consulted ignore rules are still checked.
23
+ Use `fyn --fi` to bypass this freshness check and force installation.
24
+
19
25
  Want to add a package on your local disk as a dependency to your project? Do this:
20
26
 
21
27
  ```sh
package/bin/index.mjs CHANGED
@@ -40,4 +40,7 @@ export const run = async (...args) => (await load()).run(...args);
40
40
  */
41
41
  export const fun = async (...args) => (await load()).fun(...args);
42
42
 
43
- export default { run, fun };
43
+ /** Resolve the generated audit report path using fyn's installation configuration. */
44
+ export const getAuditFilePath = async (...args) => (await load()).getAuditFilePath(...args);
45
+
46
+ export default { run, fun, getAuditFilePath };
package/dist/fyn.mjs CHANGED
@@ -1624,7 +1624,7 @@ var AveAzul = class AveAzul extends Promise {
1624
1624
  * @returns Promise that resolves after the delay
1625
1625
  */
1626
1626
  delay(ms) {
1627
- return delay$1(ms);
1627
+ return this.then((value) => AveAzul.delay(ms, value));
1628
1628
  }
1629
1629
  /**
1630
1630
  * Bluebird-style timeout() method
@@ -1662,8 +1662,8 @@ var AveAzul = class AveAzul extends Promise {
1662
1662
  * @returns Promise that maintains the rejection
1663
1663
  */
1664
1664
  tapCatch(fn) {
1665
- return this.catch((err) => {
1666
- fn(err);
1665
+ return this.catch(async (err) => {
1666
+ await fn(err);
1667
1667
  throw err;
1668
1668
  });
1669
1669
  }
@@ -1703,7 +1703,9 @@ var AveAzul = class AveAzul extends Promise {
1703
1703
  * @returns Promise that rejects with the given reason
1704
1704
  */
1705
1705
  throw(reason) {
1706
- return AveAzul.reject(reason);
1706
+ return this.then(() => {
1707
+ throw reason;
1708
+ });
1707
1709
  }
1708
1710
  /**
1709
1711
  * Bluebird-style catchThrow() that catches an error and throws a new one
@@ -1756,6 +1758,8 @@ var AveAzul = class AveAzul extends Promise {
1756
1758
  });
1757
1759
  }
1758
1760
  some(count) {
1761
+ if ((count | 0) !== count || count < 0) return AveAzul.reject(/* @__PURE__ */ new TypeError("expecting a non-negative integer"));
1762
+ if (count === 0) return AveAzul.resolve([]);
1759
1763
  return this.then((args) => {
1760
1764
  const arr = toArray$1(args);
1761
1765
  return new AveAzul((resolve, reject) => {
@@ -1763,6 +1767,10 @@ var AveAzul = class AveAzul extends Promise {
1763
1767
  const results = [];
1764
1768
  const len = arr.length;
1765
1769
  let settled = false;
1770
+ if (count > len) {
1771
+ settled = true;
1772
+ reject(/* @__PURE__ */ new RangeError(`Input array must contain at least ${count} items but contains only ${len} items`));
1773
+ }
1766
1774
  const addDone = (result) => {
1767
1775
  if (settled) return;
1768
1776
  results.push(result);
@@ -2054,7 +2062,7 @@ AveAzul.OperationalError = OperationalError;
2054
2062
  AveAzul.isOperationalError = isOperationalError;
2055
2063
  AveAzul.isProgrammerError = isProgrammerError;
2056
2064
  AveAzul.Disposer = Disposer;
2057
- addStaticAny(AveAzul);
2065
+ addStaticAny(AveAzul, true);
2058
2066
  setupNotImplemented(AveAzul);
2059
2067
 
2060
2068
  //#endregion
@@ -26965,6 +26973,65 @@ const join2 = (sep, a, b) => {
26965
26973
  else return a + sep + b;
26966
26974
  };
26967
26975
 
26976
+ //#endregion
26977
+ //#region node_modules/filter-scan-dir/dist/gitignore.js
26978
+ function ancestorDirs(dir) {
26979
+ const dirs = [dir];
26980
+ for (let current = dir;;) {
26981
+ if (fs.existsSync(Path.join(current, ".git"))) return dirs.reverse();
26982
+ const parent = Path.dirname(current);
26983
+ if (parent === current) return [dir];
26984
+ dirs.push(parent);
26985
+ current = parent;
26986
+ }
26987
+ }
26988
+ var GitignoreRules = class GitignoreRules {
26989
+ constructor(rules = []) {
26990
+ this.rules = rules;
26991
+ }
26992
+ ignores(file, directory) {
26993
+ let ignored = false;
26994
+ for (const { dir, matcher } of this.rules) {
26995
+ const relative = Path.relative(dir, file).split(Path.sep).join("/");
26996
+ if (!relative) continue;
26997
+ const result = matcher.test(relative + (directory ? "/" : ""));
26998
+ if (result.ignored) ignored = true;
26999
+ else if (result.unignored) ignored = false;
27000
+ }
27001
+ return ignored;
27002
+ }
27003
+ add(dir, source, parse) {
27004
+ return new GitignoreRules([...this.rules, {
27005
+ dir,
27006
+ matcher: parse(source)
27007
+ }]);
27008
+ }
27009
+ static loadSync(dir, parse, parent) {
27010
+ let rules = parent || new GitignoreRules();
27011
+ for (const base of parent ? [dir] : ancestorDirs(dir)) {
27012
+ if (!parent && rules.ignores(base, true)) rules = new GitignoreRules();
27013
+ try {
27014
+ rules = rules.add(base, fs.readFileSync(Path.join(base, ".gitignore"), "utf8"), parse);
27015
+ } catch (error) {
27016
+ if (error.code !== "ENOENT" && error.code !== "ENOTDIR") throw error;
27017
+ }
27018
+ }
27019
+ return rules;
27020
+ }
27021
+ static async load(dir, parse, parent) {
27022
+ let rules = parent || new GitignoreRules();
27023
+ for (const base of parent ? [dir] : ancestorDirs(dir)) {
27024
+ if (!parent && rules.ignores(base, true)) rules = new GitignoreRules();
27025
+ try {
27026
+ rules = rules.add(base, await fs.promises.readFile(Path.join(base, ".gitignore"), "utf8"), parse);
27027
+ } catch (error) {
27028
+ if (error.code !== "ENOENT" && error.code !== "ENOTDIR") throw error;
27029
+ }
27030
+ }
27031
+ return rules;
27032
+ }
27033
+ };
27034
+
26968
27035
  //#endregion
26969
27036
  //#region node_modules/filter-scan-dir/dist/index.js
26970
27037
  /**
@@ -27051,9 +27118,10 @@ function processFile(options, extras) {
27051
27118
  }
27052
27119
  return false;
27053
27120
  }
27054
- function acceptEntry(options, entry, path) {
27121
+ function acceptEntry(options, entry, path, gitignore) {
27055
27122
  const isDirectory = entry.isDirectory();
27056
27123
  if (isDirectory && options._ignoreDirs.has(entry.name)) return false;
27124
+ if (gitignore === null || gitignore === void 0 ? void 0 : gitignore.ignores(Path.resolve(options.dir, path, entry.name), isDirectory)) return false;
27057
27125
  if (options.prefilter && !options.prefilter(entry.name, path, entry)) return false;
27058
27126
  if (isDirectory) return true;
27059
27127
  if (options.ignoreExt.length || options.filterExt.length) {
@@ -27096,7 +27164,7 @@ function releaseDirectory(options) {
27096
27164
  * @param level
27097
27165
  * @returns
27098
27166
  */
27099
- async function walk(path, options, level = 0) {
27167
+ async function walk(path, options, level = 0, parentRules) {
27100
27168
  let promises = [];
27101
27169
  let hasSlot = false;
27102
27170
  try {
@@ -27107,6 +27175,7 @@ async function walk(path, options, level = 0) {
27107
27175
  }
27108
27176
  if (options._stopped) return void 0;
27109
27177
  const dir = Path.join(options.dir, path);
27178
+ const gitignore = options.gitignore ? await GitignoreRules.load(Path.resolve(dir), options.gitignore, parentRules) : void 0;
27110
27179
  let files = await asyncReaddir(dir, options.readdirOpts);
27111
27180
  if (options.sortFiles) {
27112
27181
  if (!options.readdirOpts) files = files.sort();
@@ -27116,7 +27185,7 @@ async function walk(path, options, level = 0) {
27116
27185
  const extrasFiles = options.fullStat && options._earlyFilter && (options.filter || options.filterDir) ? files.map((entry) => entry.name) : files;
27117
27186
  for (let ix = 0; !options._stopped && ix < files.length; ix++) {
27118
27187
  const file = files[ix];
27119
- if (options._earlyFilter && !acceptEntry(options, file, path)) continue;
27188
+ if (options._earlyFilter && !acceptEntry(options, file, path, gitignore)) continue;
27120
27189
  let extras;
27121
27190
  if (options.fullStat) {
27122
27191
  const name = options._earlyFilter ? file.name : file;
@@ -27143,7 +27212,7 @@ async function walk(path, options, level = 0) {
27143
27212
  break;
27144
27213
  }
27145
27214
  if (!flags.skip && level < options.maxLevel) {
27146
- const walkP = walk(extras.dirFile, options, level + 1);
27215
+ const walkP = walk(extras.dirFile, options, level + 1, gitignore);
27147
27216
  if (options.concurrency > 1) {
27148
27217
  promises.push(walkP);
27149
27218
  if (promises.length >= options.concurrency) {
@@ -27207,7 +27276,7 @@ function makeOptions(opts) {
27207
27276
  _earlyFilter: false,
27208
27277
  _ignoreDirs: new Set([].concat(options.ignoreDirs || []))
27209
27278
  });
27210
- opts2._earlyFilter = !!opts2.prefilter || opts2._ignoreDirs.size > 0 || opts2.ignoreExt.length > 0 || opts2.filterExt.length > 0;
27279
+ opts2._earlyFilter = !!opts2.gitignore || !!opts2.prefilter || opts2._ignoreDirs.size > 0 || opts2.ignoreExt.length > 0 || opts2.filterExt.length > 0;
27211
27280
  if (!opts2.fullStat || opts2._earlyFilter) opts2.readdirOpts = { withFileTypes: true };
27212
27281
  return opts2;
27213
27282
  }
@@ -28836,20 +28905,20 @@ const resolveAutoSearch = (val) => {
28836
28905
  if (val === false) return {
28837
28906
  enable: false,
28838
28907
  respectGitignore: false,
28839
- stopOnPackageJsonFound: false
28908
+ stopOnPackageJsonFound: true
28840
28909
  };
28841
28910
  if (val && typeof val === "object") {
28842
28911
  const obj = val;
28843
28912
  return {
28844
28913
  enable: obj.enable !== false,
28845
28914
  respectGitignore: obj.respectGitignore === true,
28846
- stopOnPackageJsonFound: obj.stopOnPackageJsonFound === true
28915
+ stopOnPackageJsonFound: obj.stopOnPackageJsonFound !== false
28847
28916
  };
28848
28917
  }
28849
28918
  return {
28850
28919
  enable: true,
28851
28920
  respectGitignore: false,
28852
- stopOnPackageJsonFound: false
28921
+ stopOnPackageJsonFound: true
28853
28922
  };
28854
28923
  };
28855
28924
  /**
@@ -28857,12 +28926,12 @@ const resolveAutoSearch = (val) => {
28857
28926
  *
28858
28927
  * Accepts both shapes:
28859
28928
  *
28860
- * - **array** - the historical form. Treated as `publishInclude`, with auto-search on and
28861
- * `respectGitignore` off. It no longer narrows discovery.
28929
+ * - **array** - the historical form. Treated as additive `include` and `publishInclude`,
28930
+ * with auto-search on and `respectGitignore` off. It no longer narrows discovery.
28862
28931
  * - **object** - `{ autoSearch, include, exclude, publishInclude, publishExclude }`.
28863
28932
  *
28864
- * Defaults: `autoSearch` on, `respectGitignore` off. With auto-search off and no `include`,
28865
- * `include` falls back to `["packages/*"]`.
28933
+ * Defaults: `autoSearch` on, `respectGitignore` off, stop at package.json. With auto-search
28934
+ * off and no `include`, `include` falls back to `["packages/*"]`.
28866
28935
  *
28867
28936
  * @param packages - the raw `packages` value from fynpo.json / fynpo.config.js
28868
28937
  * @returns the resolved config, every field populated
@@ -28874,7 +28943,7 @@ function resolvePackagesConfig(packages) {
28874
28943
  autoSearch: {
28875
28944
  enable: true,
28876
28945
  respectGitignore: false,
28877
- stopOnPackageJsonFound: false
28946
+ stopOnPackageJsonFound: true
28878
28947
  },
28879
28948
  include: list,
28880
28949
  exclude: [],
@@ -28898,9 +28967,9 @@ function resolvePackagesConfig(packages) {
28898
28967
  * Decide how to scan for packages.
28899
28968
  *
28900
28969
  * `include` does NOT turn auto-search off - auto-search is on by default and stays on, so it
28901
- * still decides *how the tree is walked*. `include` then filters what the walk found, via
28902
- * {@link includeFilter}. Only with auto-search off does `include` become the scan patterns
28903
- * themselves, falling back to `packages/*`.
28970
+ * still decides *how the tree is walked*. `include` adds explicit paths below package
28971
+ * boundaries. Only with auto-search off does `include` restrict the scan paths,
28972
+ * falling back to `packages/*`.
28904
28973
  *
28905
28974
  * @param config - resolved packages config
28906
28975
  * @returns `null` to auto-search the whole repo, otherwise the patterns to scan
@@ -28910,9 +28979,8 @@ function scanPatterns(config) {
28910
28979
  return config.include.length > 0 ? config.include : [...DEFAULT_INCLUDE];
28911
28980
  }
28912
28981
  /**
28913
- * The patterns a discovered package must match to be kept.
28914
- *
28915
- * Empty means keep everything the scan found.
28982
+ * Explicit inclusion patterns, used to add nested paths and promote them to managed packages.
28983
+ * With auto-search disabled, they restrict discovery to the declared paths.
28916
28984
  *
28917
28985
  * @param config - resolved packages config
28918
28986
  * @returns patterns to match a package path against
@@ -29735,9 +29803,9 @@ var FynpoDepGraph = class {
29735
29803
  const mms = resolved.map((p) => new Minimatch(p));
29736
29804
  groups = groupMM(mms, {});
29737
29805
  }
29738
- const foundInAutoSearch = (path) => {
29806
+ const foundInAutoSearch = (path, partial = false) => {
29739
29807
  if (!autoSearch || !pkgConfig.autoSearch.stopOnPackageJsonFound) return false;
29740
- for (const e of autoSearchFound) if (isPathInside$1(path, e)) return true;
29808
+ for (const e of autoSearchFound) if (isPathInside$1(path, e)) return !includeMms.some((m) => m.match(posixify$2(path), partial));
29741
29809
  return false;
29742
29810
  };
29743
29811
  const files = [];
@@ -29757,7 +29825,7 @@ var FynpoDepGraph = class {
29757
29825
  },
29758
29826
  filterDir: (dir, path, extras) => {
29759
29827
  if (dir !== "node_modules") {
29760
- if (foundInAutoSearch(path) || isExcluded(path) || skipForAutoSearch(path)) return false;
29828
+ if (foundInAutoSearch(extras.dirFile, true) || isExcluded(path) || skipForAutoSearch(path)) return false;
29761
29829
  return prefix === "." && groups[prefix] === null ? !dir.startsWith(".") : Boolean(groups[prefix].find((save) => save.mm.match(extras.dirFile)));
29762
29830
  }
29763
29831
  return false;
@@ -29775,7 +29843,7 @@ var FynpoDepGraph = class {
29775
29843
  const pkgPath = posixify$2(Path.dirname(pkgFile));
29776
29844
  const nested = autoSearch && packageRoots.some((root) => isPathInside$1(pkgPath, root));
29777
29845
  const included = isIncluded(pkgPath);
29778
- if (!included && !nested) continue;
29846
+ if (!autoSearch && !included) continue;
29779
29847
  const managed = !autoSearch || !nested || includeMms.length > 0 && included;
29780
29848
  const pkgStr = await promises.readFile(Path.join(cwd, pkgFile), "utf-8");
29781
29849
  const pkgJson = JSON.parse(pkgStr);
@@ -30667,7 +30735,7 @@ const fyntil = {
30667
30735
  }
30668
30736
  const graph = new FynpoDepGraph({
30669
30737
  cwd: fcm.topDir,
30670
- patterns: config.packages,
30738
+ packages: config.packages,
30671
30739
  noFynLocal: config.noFynLocal
30672
30740
  });
30673
30741
  await graph.resolve();
@@ -91866,6 +91934,7 @@ var hard_link_dir_exports = /* @__PURE__ */ __exportAll({
91866
91934
  link: () => link$1,
91867
91935
  linkFile: () => linkFile,
91868
91936
  linkSym1: () => linkSym1,
91937
+ localLinkNeedsRefresh: () => localLinkNeedsRefresh,
91869
91938
  unlinkDest: () => unlinkDest
91870
91939
  });
91871
91940
  var import_lib$4 = /* @__PURE__ */ __toESM(require_lib$36(), 1);
@@ -92134,10 +92203,15 @@ async function handleSourceMap({ file, destFiles, src, dest, srcFp, destFp, sour
92134
92203
  * @param {*} dest
92135
92204
  * @param {*} sym1
92136
92205
  */
92137
- async function linkPackTree({ tree, src, dest, sym1, sourceMaps, isRoot = false }) {
92206
+ async function linkPackTree({ tree, src, dest, sym1, sourceMaps, preserveInstall = false, isRoot = false }) {
92138
92207
  const files = tree[SYM_FILES];
92139
92208
  const destFiles = await prepDestDir(dest);
92209
+ if (isRoot && preserveInstall) {
92210
+ destFiles["package.json"] = true;
92211
+ destFiles["node_modules"] = true;
92212
+ }
92140
92213
  for (const file of files) {
92214
+ if (isRoot && preserveInstall && file === "package.json") continue;
92141
92215
  if (!import_ci_info.isCI && file.match(/\.(js|mjs)\.map$/)) continue;
92142
92216
  destFiles[file] = true;
92143
92217
  const srcFp = Path.join(src, file);
@@ -92161,6 +92235,7 @@ async function linkPackTree({ tree, src, dest, sym1, sourceMaps, isRoot = false
92161
92235
  }
92162
92236
  const dirs = Object.keys(tree).sort();
92163
92237
  for (const dir of dirs) {
92238
+ if (isRoot && preserveInstall && dir.split("/")[0] === "node_modules") continue;
92164
92239
  destFiles[dir.split("/")[0]] = true;
92165
92240
  const srcFp = Path.join(src, dir);
92166
92241
  const destFp = Path.join(dest, dir);
@@ -92175,15 +92250,47 @@ async function linkPackTree({ tree, src, dest, sym1, sourceMaps, isRoot = false
92175
92250
  logger$1.debug(`linkPackTree src: ${src} dest: ${dest} - destFiles ${JSON.stringify(destFiles)}`);
92176
92251
  await cleanExtraDest(dest, destFiles, isRoot);
92177
92252
  }
92178
- async function link$1(src, dest, { sourceMaps = true } = {}) {
92253
+ async function link$1(src, dest, { sourceMaps = true, preserveInstall = false } = {}) {
92179
92254
  return await linkPackTree({
92180
92255
  tree: await generatePackTree(src),
92181
92256
  src,
92182
92257
  dest,
92183
92258
  sourceMaps,
92259
+ preserveInstall,
92184
92260
  isRoot: true
92185
92261
  });
92186
92262
  }
92263
+ async function localLinkNeedsRefresh(src, dest, installedAt) {
92264
+ const tree = await generatePackTree(src);
92265
+ const copyMode = fyntil.strToBool(process.env.FYN_LOCAL_COPY_MODE);
92266
+ const inspectTree = async (tree, srcDir, destDir) => {
92267
+ for (const file of tree[SYM_FILES]) {
92268
+ const srcFile = Path.join(srcDir, file);
92269
+ const srcStat = await file_ops_default.lstat(srcFile);
92270
+ if (srcDir === src && file === "package.json" || !import_ci_info.isCI && /\.(js|mjs)\.map$/.test(file)) {
92271
+ if (srcStat.mtimeMs > installedAt) return true;
92272
+ continue;
92273
+ }
92274
+ const destFile = Path.join(destDir, file);
92275
+ let destStat;
92276
+ try {
92277
+ destStat = await file_ops_default.lstat(destFile);
92278
+ } catch (err) {
92279
+ if (err.code === "ENOENT" || err.code === "ENOTDIR") return true;
92280
+ throw err;
92281
+ }
92282
+ if (srcStat.isSymbolicLink()) {
92283
+ if (!destStat.isSymbolicLink() || await file_ops_default.readlink(srcFile) !== await file_ops_default.readlink(destFile)) return true;
92284
+ } else if (!destStat.isFile()) return true;
92285
+ else if (copyMode) {
92286
+ if (srcStat.mtimeMs > installedAt) return true;
92287
+ } else if (srcStat.dev !== destStat.dev || srcStat.ino !== destStat.ino) return true;
92288
+ }
92289
+ for (const dir of Object.keys(tree)) if (await inspectTree(tree[dir], Path.join(srcDir, dir), Path.join(destDir, dir))) return true;
92290
+ return false;
92291
+ };
92292
+ return inspectTree(tree, src, dest);
92293
+ }
92187
92294
  async function linkSym1(src, dest) {
92188
92295
  return await linkPackTree({
92189
92296
  tree: await generatePackTree(src),
@@ -106667,7 +106774,8 @@ var FynCentral = class {
106667
106774
  //#endregion
106668
106775
  //#region lib/util/stat-dir.ts
106669
106776
  async function _scanFileStats(dir, ignores, baseDir = "") {
106670
- const ignore = (fullPath) => ignores.find((pattern) => minimatch(fullPath, pattern, { dot: true }));
106777
+ const patterns = ignores.map((pattern) => new Minimatch(pattern, { dot: true }));
106778
+ const ignore = (fullPath) => patterns.find((pattern) => pattern.match(fullPath));
106671
106779
  let latestMtimeMs = 0;
106672
106780
  let latestFile = "";
106673
106781
  const updateLatest = (mtimeMs, file) => {
@@ -106676,6 +106784,13 @@ async function _scanFileStats(dir, ignores, baseDir = "") {
106676
106784
  latestFile = file;
106677
106785
  }
106678
106786
  };
106787
+ const inspectFile = (file) => {
106788
+ try {
106789
+ updateLatest(file_ops_default.statSync(file).mtimeMs, file);
106790
+ } catch (err) {
106791
+ if (err.code !== "ENOENT" && err.code !== "ENOTDIR") throw err;
106792
+ }
106793
+ };
106679
106794
  const filter = (file, path, extras) => {
106680
106795
  if (ignore(extras.fullFile)) return false;
106681
106796
  updateLatest(extras.stat.mtimeMs, extras.fullFile);
@@ -106683,11 +106798,45 @@ async function _scanFileStats(dir, ignores, baseDir = "") {
106683
106798
  };
106684
106799
  const fullDir = Path.join(baseDir, dir);
106685
106800
  updateLatest((await file_ops_default.stat(fullDir)).mtimeMs, fullDir);
106801
+ for (const file of [
106802
+ "package.json",
106803
+ "package-fyn.json",
106804
+ "fyn-lock.yaml",
106805
+ "package-lock.json",
106806
+ "npm-shrinkwrap.json",
106807
+ "yarn.lock",
106808
+ ".npmrc",
106809
+ ".fynrc",
106810
+ "fynpo.json",
106811
+ "fynpo.config.js",
106812
+ "fynpo.config.json"
106813
+ ]) inspectFile(Path.join(fullDir, file));
106814
+ const ruleDirs = [];
106815
+ let ancestor = Path.resolve(fullDir);
106816
+ while (true) {
106817
+ ruleDirs.push(ancestor);
106818
+ if (file_ops_default.existsSync(Path.join(ancestor, ".git"))) break;
106819
+ const parent = Path.dirname(ancestor);
106820
+ if (parent === ancestor) {
106821
+ ruleDirs.length = 1;
106822
+ break;
106823
+ }
106824
+ ancestor = parent;
106825
+ }
106826
+ for (const ruleDir of ruleDirs) {
106827
+ inspectFile(ruleDir);
106828
+ inspectFile(Path.join(ruleDir, ".gitignore"));
106829
+ }
106686
106830
  await filterScanDir({
106687
106831
  cwd: fullDir,
106688
106832
  prependCwd: false,
106833
+ gitignore: (rules) => (0, import_ignore.default)().add(rules),
106689
106834
  filter,
106690
- filterDir: filter,
106835
+ filterDir: (file, path, extras) => {
106836
+ if (!filter(file, path, extras)) return false;
106837
+ inspectFile(Path.join(extras.fullFile, ".gitignore"));
106838
+ return true;
106839
+ },
106691
106840
  concurrency: 500,
106692
106841
  fullStat: true
106693
106842
  });
@@ -106697,7 +106846,7 @@ async function _scanFileStats(dir, ignores, baseDir = "") {
106697
106846
  };
106698
106847
  }
106699
106848
  function scanFileStats(dir, options = {}) {
106700
- return _scanFileStats(dir, [`**/?(node_modules|_fyn|.vscode|.DS_Store|coverage|.nyc_output|.fynpo|.git|.github|.gitignore)`, "**/*.?(log|md)"].concat(options.ignores || `**/?(docs|docusaurus|packages|tmp|.etmp|samples|dist)`).concat(options.moreIgnores).filter((x) => x), "");
106849
+ return _scanFileStats(dir, [`**/?(node_modules|_fyn|.vscode|.DS_Store|coverage|.nyc_output|.fynpo|.git|.github|.gitignore)`, "**/*.?(log|md)"].concat(options.ignores || `**/?(docs|docusaurus|packages|tmp|.temp|.etmp|samples|dist)`).concat(options.moreIgnores).filter((x) => x), "");
106701
106850
  }
106702
106851
 
106703
106852
  //#endregion
@@ -110331,7 +110480,7 @@ var LocalPkgBuilder = class {
110331
110480
  }
110332
110481
  const checkPkg = await this._fyn.getLocalPkgInstall(item.fullPath);
110333
110482
  this._waitItems[item.fullPath] = false;
110334
- if (!checkPkg.localBuild) {
110483
+ if (!checkPkg.localBuild && !(this._fyn._options.forceInstall && checkPkg.hasScript)) {
110335
110484
  logger$1.debug(`local pkg at ${item.fullPath} doesn't need build`, checkPkg);
110336
110485
  return;
110337
110486
  }
@@ -110370,7 +110519,8 @@ var LocalPkgBuilder = class {
110370
110519
  this._fyn._options.registry && `--reg=${this._fyn._options.registry}`,
110371
110520
  "-q=d --pg=simple --no-build-local --sl=fyn-debug.log",
110372
110521
  !this._fyn._options.sourceMaps && "--no-source-maps",
110373
- "install --no-audit"
110522
+ "install --no-audit",
110523
+ this._fyn._options.forceInstall && "--force-install"
110374
110524
  ].filter((x) => x).join(" ");
110375
110525
  const displayTitle = `building local pkg at ${dispPath}`;
110376
110526
  logger$1.verbose(displayTitle);
@@ -111175,6 +111325,7 @@ var Fyn = class Fyn {
111175
111325
  const localPkg = localPkgs[nmDir];
111176
111326
  const fullPath = Path.join(this._cwd, localPkg.srcDir);
111177
111327
  if ((await this.getLocalPkgInstall(fullPath)).changed) return true;
111328
+ if (await localLinkNeedsRefresh(fullPath, Path.join(this._cwd, nmDir), this._installConfig.time)) return true;
111178
111329
  }
111179
111330
  if (await localExportsNeedInstall({
111180
111331
  cwd: this._cwd,
@@ -111208,6 +111359,7 @@ var Fyn = class Fyn {
111208
111359
  time: Date.now() + 5,
111209
111360
  centralDir,
111210
111361
  production: this.production,
111362
+ fynlocal: Boolean(this.fynlocal),
111211
111363
  layout: this._options.layout,
111212
111364
  shortPkgDir: this._shortPkgDir,
111213
111365
  blockedScripts: this._blockedScripts || this._installConfig.blockedScripts || [],
@@ -115259,7 +115411,7 @@ var FynCli = class {
115259
115411
  const auditFile = argv.opts?.auditFile;
115260
115412
  return dist_default.try(() => this.fyn._initializePkg()).then(async () => {
115261
115413
  checkNewVersion(this.fyn._options);
115262
- if (!this.fyn._changeProdMode && !this.fyn._options.forceInstall && this.fyn._installConfig.time) {
115414
+ if (!this.fyn._changeProdMode && !this.fyn._options.forceInstall && this.fyn._installConfig.fynlocal === Boolean(this.fyn.fynlocal) && this.fyn._installConfig.time) {
115263
115415
  const stats = await scanFileStats(this.fyn.cwd, { moreIgnores: localExportsScanIgnores(this.fyn._pkg) });
115264
115416
  const { latestMtimeMs } = stats;
115265
115417
  logger$1.debug("time check from install config - last install time", this.fyn._installConfig.time, "latest file time", latestMtimeMs);
@@ -115353,6 +115505,16 @@ var FynCli = class {
115353
115505
  dir: this.fyn.cwd
115354
115506
  }
115355
115507
  });
115508
+ if (scripts.length > 0 || this.fyn._options.autoRun && fynpoNpmScripts.length > 0) {
115509
+ const localPkgLinks = this.fyn._installConfig.localPkgLinks || {};
115510
+ for (const vdir in localPkgLinks) {
115511
+ const localPkg = localPkgLinks[vdir];
115512
+ await link$1(Path.join(this.fyn.cwd, localPkg.srcDir), Path.join(this.fyn.cwd, vdir), {
115513
+ sourceMaps: Boolean(localPkg.sourceMaps),
115514
+ preserveInstall: true
115515
+ });
115516
+ }
115517
+ }
115356
115518
  }).then(async () => {
115357
115519
  logger$1.removeItem(INSTALL_PACKAGE);
115358
115520
  const end = Date.now();
@@ -124768,6 +124930,20 @@ const pickOptions = async (cmd, checkFynpo = true) => {
124768
124930
  _fynpo: fynpo
124769
124931
  };
124770
124932
  };
124933
+ /** Resolve bootstrap's generated report without resolving the package graph or installing. */
124934
+ const getAuditFilePath = async (cwd, { rcfile = true } = {}) => {
124935
+ cwd = Path.resolve(cwd);
124936
+ const manager = new FynpoConfigManager({ cwd });
124937
+ await manager.load();
124938
+ const rc = loadRc(rcfile ? cwd : false, manager.topDir);
124939
+ return new Fyn({
124940
+ opts: {
124941
+ ...rc.all || defaultRc,
124942
+ cwd
124943
+ },
124944
+ _fynpo: false
124945
+ }).getFvDir(".fyn-audit.json");
124946
+ };
124771
124947
  const makeFynGlobal = async (cmd, extra = {}) => {
124772
124948
  const { opts } = await pickOptions(cmd, false);
124773
124949
  const tag = cmd.getParent()?.opts?.tag;
@@ -125473,8 +125649,9 @@ var main_default = {
125473
125649
  pickEnvOptions,
125474
125650
  setLockfile,
125475
125651
  hardLinkDir: hard_link_dir_exports,
125476
- reportFynpoLoadError
125652
+ reportFynpoLoadError,
125653
+ getAuditFilePath
125477
125654
  };
125478
125655
 
125479
125656
  //#endregion
125480
- export { main_default as default, fun, getRunExitCode, hard_link_dir_exports as hardLinkDir, nodeGyp, pickEnvOptions, reportFynpoLoadError, run, setLockfile };
125657
+ export { main_default as default, fun, getAuditFilePath, getRunExitCode, hard_link_dir_exports as hardLinkDir, nodeGyp, pickEnvOptions, reportFynpoLoadError, run, setLockfile };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "fyn",
3
3
  "type": "module",
4
- "version": "3.1.7",
4
+ "version": "3.1.9",
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",
@@ -82,7 +82,7 @@
82
82
  "Walmart GTP.js Team and Contributors"
83
83
  ],
84
84
  "devDependencies": {
85
- "@fynjs/run": "^1.1.5",
85
+ "@fynjs/run": "^1.1.6",
86
86
  "@fynjs/ts-resolve": "^1.0.3",
87
87
  "@types/node": "^26.4.1",
88
88
  "@vitest/coverage-v8": "^5.0.0",
@@ -92,7 +92,7 @@
92
92
  "patch-package": "^8.0.0",
93
93
  "prettier": "^3.5.3",
94
94
  "rolldown": "^1.2.6",
95
- "run-verify": "^2.1.5",
95
+ "run-verify": "^2.1.6",
96
96
  "typescript": "^7.0.2",
97
97
  "vite": "^8.2.2",
98
98
  "vitest": "^5.0.0",