fyn 3.1.6 → 3.1.8

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 +6 -0
  2. package/dist/fyn.mjs +186 -37
  3. package/package.json +1 -1
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/dist/fyn.mjs CHANGED
@@ -26965,6 +26965,65 @@ const join2 = (sep, a, b) => {
26965
26965
  else return a + sep + b;
26966
26966
  };
26967
26967
 
26968
+ //#endregion
26969
+ //#region node_modules/filter-scan-dir/dist/gitignore.js
26970
+ function ancestorDirs(dir) {
26971
+ const dirs = [dir];
26972
+ for (let current = dir;;) {
26973
+ if (fs.existsSync(Path.join(current, ".git"))) return dirs.reverse();
26974
+ const parent = Path.dirname(current);
26975
+ if (parent === current) return [dir];
26976
+ dirs.push(parent);
26977
+ current = parent;
26978
+ }
26979
+ }
26980
+ var GitignoreRules = class GitignoreRules {
26981
+ constructor(rules = []) {
26982
+ this.rules = rules;
26983
+ }
26984
+ ignores(file, directory) {
26985
+ let ignored = false;
26986
+ for (const { dir, matcher } of this.rules) {
26987
+ const relative = Path.relative(dir, file).split(Path.sep).join("/");
26988
+ if (!relative) continue;
26989
+ const result = matcher.test(relative + (directory ? "/" : ""));
26990
+ if (result.ignored) ignored = true;
26991
+ else if (result.unignored) ignored = false;
26992
+ }
26993
+ return ignored;
26994
+ }
26995
+ add(dir, source, parse) {
26996
+ return new GitignoreRules([...this.rules, {
26997
+ dir,
26998
+ matcher: parse(source)
26999
+ }]);
27000
+ }
27001
+ static loadSync(dir, parse, parent) {
27002
+ let rules = parent || new GitignoreRules();
27003
+ for (const base of parent ? [dir] : ancestorDirs(dir)) {
27004
+ if (!parent && rules.ignores(base, true)) rules = new GitignoreRules();
27005
+ try {
27006
+ rules = rules.add(base, fs.readFileSync(Path.join(base, ".gitignore"), "utf8"), parse);
27007
+ } catch (error) {
27008
+ if (error.code !== "ENOENT" && error.code !== "ENOTDIR") throw error;
27009
+ }
27010
+ }
27011
+ return rules;
27012
+ }
27013
+ static async load(dir, parse, parent) {
27014
+ let rules = parent || new GitignoreRules();
27015
+ for (const base of parent ? [dir] : ancestorDirs(dir)) {
27016
+ if (!parent && rules.ignores(base, true)) rules = new GitignoreRules();
27017
+ try {
27018
+ rules = rules.add(base, await fs.promises.readFile(Path.join(base, ".gitignore"), "utf8"), parse);
27019
+ } catch (error) {
27020
+ if (error.code !== "ENOENT" && error.code !== "ENOTDIR") throw error;
27021
+ }
27022
+ }
27023
+ return rules;
27024
+ }
27025
+ };
27026
+
26968
27027
  //#endregion
26969
27028
  //#region node_modules/filter-scan-dir/dist/index.js
26970
27029
  /**
@@ -27051,9 +27110,10 @@ function processFile(options, extras) {
27051
27110
  }
27052
27111
  return false;
27053
27112
  }
27054
- function acceptEntry(options, entry, path) {
27113
+ function acceptEntry(options, entry, path, gitignore) {
27055
27114
  const isDirectory = entry.isDirectory();
27056
27115
  if (isDirectory && options._ignoreDirs.has(entry.name)) return false;
27116
+ if (gitignore === null || gitignore === void 0 ? void 0 : gitignore.ignores(Path.resolve(options.dir, path, entry.name), isDirectory)) return false;
27057
27117
  if (options.prefilter && !options.prefilter(entry.name, path, entry)) return false;
27058
27118
  if (isDirectory) return true;
27059
27119
  if (options.ignoreExt.length || options.filterExt.length) {
@@ -27096,7 +27156,7 @@ function releaseDirectory(options) {
27096
27156
  * @param level
27097
27157
  * @returns
27098
27158
  */
27099
- async function walk(path, options, level = 0) {
27159
+ async function walk(path, options, level = 0, parentRules) {
27100
27160
  let promises = [];
27101
27161
  let hasSlot = false;
27102
27162
  try {
@@ -27107,6 +27167,7 @@ async function walk(path, options, level = 0) {
27107
27167
  }
27108
27168
  if (options._stopped) return void 0;
27109
27169
  const dir = Path.join(options.dir, path);
27170
+ const gitignore = options.gitignore ? await GitignoreRules.load(Path.resolve(dir), options.gitignore, parentRules) : void 0;
27110
27171
  let files = await asyncReaddir(dir, options.readdirOpts);
27111
27172
  if (options.sortFiles) {
27112
27173
  if (!options.readdirOpts) files = files.sort();
@@ -27116,7 +27177,7 @@ async function walk(path, options, level = 0) {
27116
27177
  const extrasFiles = options.fullStat && options._earlyFilter && (options.filter || options.filterDir) ? files.map((entry) => entry.name) : files;
27117
27178
  for (let ix = 0; !options._stopped && ix < files.length; ix++) {
27118
27179
  const file = files[ix];
27119
- if (options._earlyFilter && !acceptEntry(options, file, path)) continue;
27180
+ if (options._earlyFilter && !acceptEntry(options, file, path, gitignore)) continue;
27120
27181
  let extras;
27121
27182
  if (options.fullStat) {
27122
27183
  const name = options._earlyFilter ? file.name : file;
@@ -27143,7 +27204,7 @@ async function walk(path, options, level = 0) {
27143
27204
  break;
27144
27205
  }
27145
27206
  if (!flags.skip && level < options.maxLevel) {
27146
- const walkP = walk(extras.dirFile, options, level + 1);
27207
+ const walkP = walk(extras.dirFile, options, level + 1, gitignore);
27147
27208
  if (options.concurrency > 1) {
27148
27209
  promises.push(walkP);
27149
27210
  if (promises.length >= options.concurrency) {
@@ -27207,7 +27268,7 @@ function makeOptions(opts) {
27207
27268
  _earlyFilter: false,
27208
27269
  _ignoreDirs: new Set([].concat(options.ignoreDirs || []))
27209
27270
  });
27210
- opts2._earlyFilter = !!opts2.prefilter || opts2._ignoreDirs.size > 0 || opts2.ignoreExt.length > 0 || opts2.filterExt.length > 0;
27271
+ opts2._earlyFilter = !!opts2.gitignore || !!opts2.prefilter || opts2._ignoreDirs.size > 0 || opts2.ignoreExt.length > 0 || opts2.filterExt.length > 0;
27211
27272
  if (!opts2.fullStat || opts2._earlyFilter) opts2.readdirOpts = { withFileTypes: true };
27212
27273
  return opts2;
27213
27274
  }
@@ -28835,18 +28896,21 @@ const asPathRef = (ref) => REF_TYPES.some((t) => ref.startsWith(t)) ? ref : `pat
28835
28896
  const resolveAutoSearch = (val) => {
28836
28897
  if (val === false) return {
28837
28898
  enable: false,
28838
- respectGitignore: false
28899
+ respectGitignore: false,
28900
+ stopOnPackageJsonFound: true
28839
28901
  };
28840
28902
  if (val && typeof val === "object") {
28841
28903
  const obj = val;
28842
28904
  return {
28843
28905
  enable: obj.enable !== false,
28844
- respectGitignore: obj.respectGitignore === true
28906
+ respectGitignore: obj.respectGitignore === true,
28907
+ stopOnPackageJsonFound: obj.stopOnPackageJsonFound !== false
28845
28908
  };
28846
28909
  }
28847
28910
  return {
28848
28911
  enable: true,
28849
- respectGitignore: false
28912
+ respectGitignore: false,
28913
+ stopOnPackageJsonFound: true
28850
28914
  };
28851
28915
  };
28852
28916
  /**
@@ -28854,12 +28918,12 @@ const resolveAutoSearch = (val) => {
28854
28918
  *
28855
28919
  * Accepts both shapes:
28856
28920
  *
28857
- * - **array** - the historical form. Treated as `publishInclude`, with auto-search on and
28858
- * `respectGitignore` off. It no longer narrows discovery.
28921
+ * - **array** - the historical form. Treated as additive `include` and `publishInclude`,
28922
+ * with auto-search on and `respectGitignore` off. It no longer narrows discovery.
28859
28923
  * - **object** - `{ autoSearch, include, exclude, publishInclude, publishExclude }`.
28860
28924
  *
28861
- * Defaults: `autoSearch` on, `respectGitignore` off. With auto-search off and no `include`,
28862
- * `include` falls back to `["packages/*"]`.
28925
+ * Defaults: `autoSearch` on, `respectGitignore` off, stop at package.json. With auto-search
28926
+ * off and no `include`, `include` falls back to `["packages/*"]`.
28863
28927
  *
28864
28928
  * @param packages - the raw `packages` value from fynpo.json / fynpo.config.js
28865
28929
  * @returns the resolved config, every field populated
@@ -28870,7 +28934,8 @@ function resolvePackagesConfig(packages) {
28870
28934
  return {
28871
28935
  autoSearch: {
28872
28936
  enable: true,
28873
- respectGitignore: false
28937
+ respectGitignore: false,
28938
+ stopOnPackageJsonFound: true
28874
28939
  },
28875
28940
  include: list,
28876
28941
  exclude: [],
@@ -28894,9 +28959,9 @@ function resolvePackagesConfig(packages) {
28894
28959
  * Decide how to scan for packages.
28895
28960
  *
28896
28961
  * `include` does NOT turn auto-search off - auto-search is on by default and stays on, so it
28897
- * still decides *how the tree is walked*. `include` then filters what the walk found, via
28898
- * {@link includeFilter}. Only with auto-search off does `include` become the scan patterns
28899
- * themselves, falling back to `packages/*`.
28962
+ * still decides *how the tree is walked*. `include` adds explicit paths below package
28963
+ * boundaries. Only with auto-search off does `include` restrict the scan paths,
28964
+ * falling back to `packages/*`.
28900
28965
  *
28901
28966
  * @param config - resolved packages config
28902
28967
  * @returns `null` to auto-search the whole repo, otherwise the patterns to scan
@@ -28906,9 +28971,8 @@ function scanPatterns(config) {
28906
28971
  return config.include.length > 0 ? config.include : [...DEFAULT_INCLUDE];
28907
28972
  }
28908
28973
  /**
28909
- * The patterns a discovered package must match to be kept.
28910
- *
28911
- * Empty means keep everything the scan found.
28974
+ * Explicit inclusion patterns, used to add nested paths and promote them to managed packages.
28975
+ * With auto-search disabled, they restrict discovery to the declared paths.
28912
28976
  *
28913
28977
  * @param config - resolved packages config
28914
28978
  * @returns patterns to match a package path against
@@ -29731,9 +29795,9 @@ var FynpoDepGraph = class {
29731
29795
  const mms = resolved.map((p) => new Minimatch(p));
29732
29796
  groups = groupMM(mms, {});
29733
29797
  }
29734
- const foundInAutoSearch = (path) => {
29735
- if (!autoSearch) return false;
29736
- for (const e of autoSearchFound) if (isPathInside$1(path, e)) return true;
29798
+ const foundInAutoSearch = (path, partial = false) => {
29799
+ if (!autoSearch || !pkgConfig.autoSearch.stopOnPackageJsonFound) return false;
29800
+ for (const e of autoSearchFound) if (isPathInside$1(path, e)) return !includeMms.some((m) => m.match(posixify$2(path), partial));
29737
29801
  return false;
29738
29802
  };
29739
29803
  const files = [];
@@ -29753,7 +29817,7 @@ var FynpoDepGraph = class {
29753
29817
  },
29754
29818
  filterDir: (dir, path, extras) => {
29755
29819
  if (dir !== "node_modules") {
29756
- if (foundInAutoSearch(path) || isExcluded(path) || skipForAutoSearch(path)) return false;
29820
+ if (foundInAutoSearch(extras.dirFile, true) || isExcluded(path) || skipForAutoSearch(path)) return false;
29757
29821
  return prefix === "." && groups[prefix] === null ? !dir.startsWith(".") : Boolean(groups[prefix].find((save) => save.mm.match(extras.dirFile)));
29758
29822
  }
29759
29823
  return false;
@@ -29761,8 +29825,36 @@ var FynpoDepGraph = class {
29761
29825
  });
29762
29826
  files.push(scanned);
29763
29827
  }
29764
- const allFiles = [].concat(...files).filter((f) => isIncluded(Path.dirname(f))).sort();
29765
- for (const pkgFile of allFiles) await this.addPackageByFile(pkgFile);
29828
+ const filesByDepth = [...[].concat(...files)].sort((a, b) => {
29829
+ const depth = (file) => posixify$2(Path.dirname(file)).split("/").length;
29830
+ return depth(a) - depth(b) || a.localeCompare(b);
29831
+ });
29832
+ const packageRoots = [];
29833
+ const accepted = [];
29834
+ for (const pkgFile of filesByDepth) {
29835
+ const pkgPath = posixify$2(Path.dirname(pkgFile));
29836
+ const nested = autoSearch && packageRoots.some((root) => isPathInside$1(pkgPath, root));
29837
+ const included = isIncluded(pkgPath);
29838
+ if (!autoSearch && !included) continue;
29839
+ const managed = !autoSearch || !nested || includeMms.length > 0 && included;
29840
+ const pkgStr = await promises.readFile(Path.join(cwd, pkgFile), "utf-8");
29841
+ const pkgJson = JSON.parse(pkgStr);
29842
+ if (pkgJson.fynpo === false) continue;
29843
+ if (!pkgJson.name) continue;
29844
+ accepted.push({
29845
+ pkgFile,
29846
+ pkgPath,
29847
+ pkgStr,
29848
+ pkgJson,
29849
+ managed,
29850
+ nested
29851
+ });
29852
+ packageRoots.push(pkgPath);
29853
+ }
29854
+ for (const pkg of accepted.sort((a, b) => a.pkgFile.localeCompare(b.pkgFile))) this.addPackage(pkg.pkgJson, pkg.pkgPath, pkg.pkgStr, {
29855
+ managed: pkg.managed,
29856
+ nested: pkg.nested
29857
+ });
29766
29858
  this.updateAuxPackageData();
29767
29859
  return this.packages;
29768
29860
  }
@@ -29771,10 +29863,10 @@ var FynpoDepGraph = class {
29771
29863
  *
29772
29864
  * @param pkgFile - path to the package.json file
29773
29865
  */
29774
- async addPackageByFile(pkgFile) {
29866
+ async addPackageByFile(pkgFile, management = {}) {
29775
29867
  const pkgStr = await promises.readFile(Path.join(this._options.cwd, pkgFile), "utf-8");
29776
29868
  const pkgJson = JSON.parse(pkgStr);
29777
- this.addPackage(pkgJson, Path.dirname(pkgFile), pkgStr);
29869
+ return this.addPackage(pkgJson, posixify$2(Path.dirname(pkgFile)), pkgStr, management);
29778
29870
  }
29779
29871
  /**
29780
29872
  * Add a package to the graph using the data from its package.json
@@ -29784,7 +29876,7 @@ var FynpoDepGraph = class {
29784
29876
  * @param pkgStr - string form of package.json
29785
29877
  * @returns
29786
29878
  */
29787
- addPackage(pkgJson, pkgPath, pkgStr) {
29879
+ addPackage(pkgJson, pkgPath, pkgStr, management = {}) {
29788
29880
  if (pkgJson.fynpo === false) return;
29789
29881
  assert(pkgJson.name, `package at ${pkgPath} doesn't have name`);
29790
29882
  const pkgDir = pkgJson.name[0] === "@" && (pkgPath.endsWith(`/${pkgJson.name}`) || pkgPath === pkgJson.name) ? pkgJson.name : Path.basename(pkgPath);
@@ -29805,11 +29897,20 @@ var FynpoDepGraph = class {
29805
29897
  };
29806
29898
  Object.defineProperties(pkgInfo, {
29807
29899
  pkgStr: { enumerable: false },
29808
- pkgJson: { enumerable: false }
29900
+ pkgJson: { enumerable: false },
29901
+ managed: {
29902
+ value: management.managed !== false,
29903
+ enumerable: false
29904
+ },
29905
+ nested: {
29906
+ value: management.nested === true,
29907
+ enumerable: false
29908
+ }
29809
29909
  });
29810
29910
  const { byName } = this.packages;
29811
29911
  if (byName[pkgJson.name]) byName[pkgJson.name].push(pkgInfo);
29812
29912
  else byName[pkgJson.name] = [pkgInfo];
29913
+ return pkgInfo;
29813
29914
  }
29814
29915
  /**
29815
29916
  * update auxiliary package data
@@ -29823,11 +29924,17 @@ var FynpoDepGraph = class {
29823
29924
  this.packages.byPath = {};
29824
29925
  const { byName, byPath, byId } = this.packages;
29825
29926
  for (const name in byName) {
29927
+ byName[name].sort((a, b) => {
29928
+ const versionOrder = import_semver.default.compare(b.version, a.version);
29929
+ if (versionOrder !== 0) return versionOrder;
29930
+ if (a.managed !== b.managed) return a.managed === false ? 1 : -1;
29931
+ return a.path.localeCompare(b.path);
29932
+ });
29826
29933
  byName[name].forEach((pkg) => {
29827
29934
  byPath[pkg.path] = pkg;
29828
- byId[`${pkg.name}@${pkg.version}`] = pkg;
29935
+ const id = `${pkg.name}@${pkg.version}`;
29936
+ if (!byId[id] || byId[id].managed === false && pkg.managed !== false) byId[id] = pkg;
29829
29937
  });
29830
- if (byName[name].length > 1) byName[name].sort((a, b) => import_semver.default.compare(b.version, a.version));
29831
29938
  }
29832
29939
  }
29833
29940
  /**
@@ -29894,8 +30001,8 @@ var FynpoDepGraph = class {
29894
30001
  localDepsByPath: Object.create(null),
29895
30002
  dependentsByPath: Object.create(null)
29896
30003
  };
29897
- for (const id in byId) {
29898
- const pkgInfo = byId[id];
30004
+ for (const path in byPath) {
30005
+ const pkgInfo = byPath[path];
29899
30006
  const depData = depMapByPath[pkgInfo.path];
29900
30007
  doResolve(depData, pkgInfo.dependencies, "dep");
29901
30008
  doResolve(depData, pkgInfo.devDependencies, "dev");
@@ -30620,7 +30727,7 @@ const fyntil = {
30620
30727
  }
30621
30728
  const graph = new FynpoDepGraph({
30622
30729
  cwd: fcm.topDir,
30623
- patterns: config.packages,
30730
+ packages: config.packages,
30624
30731
  noFynLocal: config.noFynLocal
30625
30732
  });
30626
30733
  await graph.resolve();
@@ -106620,7 +106727,8 @@ var FynCentral = class {
106620
106727
  //#endregion
106621
106728
  //#region lib/util/stat-dir.ts
106622
106729
  async function _scanFileStats(dir, ignores, baseDir = "") {
106623
- const ignore = (fullPath) => ignores.find((pattern) => minimatch(fullPath, pattern, { dot: true }));
106730
+ const patterns = ignores.map((pattern) => new Minimatch(pattern, { dot: true }));
106731
+ const ignore = (fullPath) => patterns.find((pattern) => pattern.match(fullPath));
106624
106732
  let latestMtimeMs = 0;
106625
106733
  let latestFile = "";
106626
106734
  const updateLatest = (mtimeMs, file) => {
@@ -106629,6 +106737,13 @@ async function _scanFileStats(dir, ignores, baseDir = "") {
106629
106737
  latestFile = file;
106630
106738
  }
106631
106739
  };
106740
+ const inspectFile = (file) => {
106741
+ try {
106742
+ updateLatest(file_ops_default.statSync(file).mtimeMs, file);
106743
+ } catch (err) {
106744
+ if (err.code !== "ENOENT" && err.code !== "ENOTDIR") throw err;
106745
+ }
106746
+ };
106632
106747
  const filter = (file, path, extras) => {
106633
106748
  if (ignore(extras.fullFile)) return false;
106634
106749
  updateLatest(extras.stat.mtimeMs, extras.fullFile);
@@ -106636,11 +106751,45 @@ async function _scanFileStats(dir, ignores, baseDir = "") {
106636
106751
  };
106637
106752
  const fullDir = Path.join(baseDir, dir);
106638
106753
  updateLatest((await file_ops_default.stat(fullDir)).mtimeMs, fullDir);
106754
+ for (const file of [
106755
+ "package.json",
106756
+ "package-fyn.json",
106757
+ "fyn-lock.yaml",
106758
+ "package-lock.json",
106759
+ "npm-shrinkwrap.json",
106760
+ "yarn.lock",
106761
+ ".npmrc",
106762
+ ".fynrc",
106763
+ "fynpo.json",
106764
+ "fynpo.config.js",
106765
+ "fynpo.config.json"
106766
+ ]) inspectFile(Path.join(fullDir, file));
106767
+ const ruleDirs = [];
106768
+ let ancestor = Path.resolve(fullDir);
106769
+ while (true) {
106770
+ ruleDirs.push(ancestor);
106771
+ if (file_ops_default.existsSync(Path.join(ancestor, ".git"))) break;
106772
+ const parent = Path.dirname(ancestor);
106773
+ if (parent === ancestor) {
106774
+ ruleDirs.length = 1;
106775
+ break;
106776
+ }
106777
+ ancestor = parent;
106778
+ }
106779
+ for (const ruleDir of ruleDirs) {
106780
+ inspectFile(ruleDir);
106781
+ inspectFile(Path.join(ruleDir, ".gitignore"));
106782
+ }
106639
106783
  await filterScanDir({
106640
106784
  cwd: fullDir,
106641
106785
  prependCwd: false,
106786
+ gitignore: (rules) => (0, import_ignore.default)().add(rules),
106642
106787
  filter,
106643
- filterDir: filter,
106788
+ filterDir: (file, path, extras) => {
106789
+ if (!filter(file, path, extras)) return false;
106790
+ inspectFile(Path.join(extras.fullFile, ".gitignore"));
106791
+ return true;
106792
+ },
106644
106793
  concurrency: 500,
106645
106794
  fullStat: true
106646
106795
  });
@@ -106650,7 +106799,7 @@ async function _scanFileStats(dir, ignores, baseDir = "") {
106650
106799
  };
106651
106800
  }
106652
106801
  function scanFileStats(dir, options = {}) {
106653
- 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), "");
106802
+ 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), "");
106654
106803
  }
106655
106804
 
106656
106805
  //#endregion
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "fyn",
3
3
  "type": "module",
4
- "version": "3.1.6",
4
+ "version": "3.1.8",
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",