@fynpo/base 1.1.22 → 2.0.0

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
@@ -1,3 +1,64 @@
1
+ [![NPM version][npm-image]][npm-url] [![Build Status][build-image]][build-url]
2
+
1
3
  # @fynpo/base
2
4
 
3
- A zero setup monorepo manager for node.js - <https://jchip.github.io/fynpo/>
5
+ The package-graph and config layer under [fynpo] and [fyn].
6
+
7
+ It answers the questions a monorepo tool keeps asking: which directories in this repo are packages, how do they depend on each other, what order can they be built in, and what does the repo's `fynpo` config say. Nothing here drives a command - it's the shared model that fynpo's commands and fyn's local-package resolution are both built on.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ npm install @fynpo/base
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ```js
18
+ import { FynpoDepGraph, FynpoConfigManager, readFynpoPackages } from "@fynpo/base";
19
+
20
+ const graph = new FynpoDepGraph({ cwd: process.cwd() });
21
+ await graph.resolve();
22
+
23
+ // packages found, and how they relate
24
+ console.log(Object.keys(graph.packages.byName));
25
+ ```
26
+
27
+ ## What's in it
28
+
29
+ ### `FynpoDepGraph`
30
+
31
+ Reads the repo's packages and builds the dependency graph between them - direct and indirect, across `dependencies`, `devDependencies`, `optionalDependencies` and `peerDependencies`. Gives you topological ordering for running tasks in dependency order, and resolves a `name@<semver>` reference to the local `name@version` that satisfies it.
32
+
33
+ Its `autoSearched` flag records whether packages had to be discovered by searching every directory (because the config declared no `packages` patterns) - worth surfacing, since a repo that keeps packages outside the default location behaves differently in commands that don't auto-search.
34
+
35
+ ### `FynpoConfigManager`
36
+
37
+ Loads and normalizes the repo's `fynpo.json` / `fynpo.config.js`.
38
+
39
+ ### `readFynpoPackages(options)` and `makePkgDeps(packages, opts)`
40
+
41
+ The lower-level pieces if you want the package list or the dep relations without the graph object.
42
+
43
+ ### `PackageRef`, `pkgId`, `getDepSection`, `makeDepStep`
44
+
45
+ Helpers for parsing and formatting package references and dependency sections.
46
+
47
+ ### `caching`
48
+
49
+ Exported as a namespace (`import { caching } from "@fynpo/base"`) - input/output hashing used for fynpo's task caching: `processInput`, `processOutput`, `processLifecycleInput`, `readHashDigest`.
50
+
51
+ ### `gitignore` helpers and `posixify`
52
+
53
+ Small utilities shared by the tools.
54
+
55
+ ## License
56
+
57
+ Licensed under the [Apache License, Version 2.0](./LICENSE).
58
+
59
+ [fynpo]: https://github.com/jchip/fynjs/tree/main/packages/fynpo
60
+ [fyn]: https://github.com/jchip/fynjs/tree/main/packages/fyn
61
+ [npm-image]: https://badge.fury.io/js/%40fynpo%2Fbase.svg
62
+ [npm-url]: https://npmjs.org/package/@fynpo/base
63
+ [build-image]: https://github.com/jchip/fynjs/actions/workflows/ci.yml/badge.svg
64
+ [build-url]: https://github.com/jchip/fynjs/actions/workflows/ci.yml
package/dist/caching.d.ts CHANGED
@@ -60,9 +60,9 @@ export declare function processInput({ cwd, input, packageJson, extra, }?: {
60
60
  }): Promise<{
61
61
  files: string[];
62
62
  data: {
63
- env: any;
64
- versions: any;
65
- npmScripts: any;
63
+ env: Pick<NodeJS.ProcessEnv, string>;
64
+ versions: Pick<NodeJS.ProcessVersions, string>;
65
+ npmScripts: Pick<any, string>;
66
66
  fileHashes: Record<string, string>;
67
67
  extra: any;
68
68
  };
@@ -97,7 +97,7 @@ export declare function processOutput({ cwd, inputHash, calcHash, output, preFil
97
97
  /** list of pre-determined files, will be filtered with output.exclude */
98
98
  preFiles?: string[];
99
99
  }): Promise<{
100
- files: any;
100
+ files: string[];
101
101
  data: {
102
102
  inputHash: string;
103
103
  fileHashes: {};
package/dist/caching.js CHANGED
@@ -1,17 +1,10 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.readHashDigest = void 0;
4
- exports.processInput = processInput;
5
- exports.processLifecycleInput = processLifecycleInput;
6
- exports.processOutput = processOutput;
7
- const tslib_1 = require("tslib");
8
- const filter_scan_dir_1 = require("filter-scan-dir");
9
- const minimatch_1 = tslib_1.__importDefault(require("minimatch"));
10
- const lodash_1 = tslib_1.__importDefault(require("lodash"));
11
- const fs_1 = tslib_1.__importDefault(require("fs"));
12
- const path_1 = tslib_1.__importDefault(require("path"));
13
- const crypto_1 = tslib_1.__importDefault(require("crypto"));
14
- const minimatch_group_1 = require("./minimatch-group");
1
+ import { filterScanDir } from "filter-scan-dir";
2
+ import { Minimatch } from "minimatch";
3
+ import _ from "lodash";
4
+ import Fs from "fs";
5
+ import Path from "path";
6
+ import Crypto from "crypto";
7
+ import { checkMmMatch, deconstructMM, unrollMmMatch } from "./minimatch-group.js";
15
8
  /**
16
9
  * create a sha256 hash for some data
17
10
  *
@@ -19,7 +12,7 @@ const minimatch_group_1 = require("./minimatch-group");
19
12
  * @returns
20
13
  */
21
14
  function hashData1(data, encoding = "base64url") {
22
- return crypto_1.default.createHash("sha256").update(data).digest(encoding);
15
+ return Crypto.createHash("sha256").update(data).digest(encoding);
23
16
  }
24
17
  function makeBase64Url(hash) {
25
18
  return hash.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
@@ -30,9 +23,9 @@ function hashData2(data, encoding = "base64url") {
30
23
  }
31
24
  return hashData1(data, encoding);
32
25
  }
33
- const hasBase64Url = typeof crypto_1.default.createHash("sha256").digest("base64url") === "string";
26
+ const hasBase64Url = typeof Crypto.createHash("sha256").digest("base64url") === "string";
34
27
  const hashData = hasBase64Url ? hashData1 : hashData2;
35
- exports.readHashDigest = hasBase64Url
28
+ export const readHashDigest = hasBase64Url
36
29
  ? (hash, encoding = "base64url") => {
37
30
  hash.setEncoding(encoding);
38
31
  hash.end();
@@ -55,7 +48,7 @@ exports.readHashDigest = hasBase64Url
55
48
  * @returns
56
49
  */
57
50
  async function hashFile(filename, encoding = "base64url") {
58
- const data = await fs_1.default.promises.readFile(filename);
51
+ const data = await Fs.promises.readFile(filename);
59
52
  const hash = hashData(data, encoding);
60
53
  return hash;
61
54
  }
@@ -69,7 +62,7 @@ async function hashFiles(cwd, files, encoding = "base64url") {
69
62
  const hashes = {};
70
63
  const promises = [];
71
64
  for (const file of files) {
72
- promises.push(hashFile(path_1.default.join(cwd, file), encoding).then((h) => (hashes[file] = h)));
65
+ promises.push(hashFile(Path.join(cwd, file), encoding).then((h) => (hashes[file] = h)));
73
66
  }
74
67
  await Promise.all(promises);
75
68
  const result = {};
@@ -84,7 +77,7 @@ async function hashFiles(cwd, files, encoding = "base64url") {
84
77
  * @returns
85
78
  */
86
79
  async function readPackageJson(cwd) {
87
- return JSON.parse(await fs_1.default.promises.readFile(path_1.default.join(cwd, "package.json"), "utf-8"));
80
+ return JSON.parse(await Fs.promises.readFile(Path.join(cwd, "package.json"), "utf-8"));
88
81
  }
89
82
  /**
90
83
  * Convert minimatch string patterns to Minimatch instances
@@ -95,7 +88,7 @@ async function readPackageJson(cwd) {
95
88
  function makeMmPatterns(patterns, options = { dot: true }) {
96
89
  return []
97
90
  .concat(patterns)
98
- .map((x) => x && new minimatch_1.default.Minimatch(x, options))
91
+ .map((x) => x && new Minimatch(x, options))
99
92
  .filter((x) => x);
100
93
  }
101
94
  /**
@@ -108,24 +101,24 @@ function makeMmPatterns(patterns, options = { dot: true }) {
108
101
  async function scanFiles(cwd, includes, excludes) {
109
102
  const filter = (_file, _path, extras) => {
110
103
  // filter-scan-dir now handles filtering symlinks when includeDir: false and includeSymlink is not set
111
- if (!(0, minimatch_group_1.checkMmMatch)(extras.dirFile, includes) || (0, minimatch_group_1.checkMmMatch)(extras.dirFile, excludes)) {
104
+ if (!checkMmMatch(extras.dirFile, includes) || checkMmMatch(extras.dirFile, excludes)) {
112
105
  return false;
113
106
  }
114
107
  return true;
115
108
  };
116
- const dirIncludes = includes.map((mx) => (0, minimatch_group_1.deconstructMM)(mx));
109
+ const dirIncludes = includes.map((mx) => deconstructMM(mx));
117
110
  const filterDir = (_file, _path, extras) => {
118
111
  const dir = `${extras.dirFile}/`; // add extra / to force matching directory
119
- const inc = dirIncludes.find((di) => (0, minimatch_group_1.checkMmMatch)(dir, di.mms));
112
+ const inc = dirIncludes.find((di) => checkMmMatch(dir, di.mms));
120
113
  if (inc) {
121
- const exc = (0, minimatch_group_1.checkMmMatch)(dir, excludes);
114
+ const exc = checkMmMatch(dir, excludes);
122
115
  if (!exc) {
123
116
  return true;
124
117
  }
125
118
  }
126
119
  return false;
127
120
  };
128
- return (await (0, filter_scan_dir_1.filterScanDir)({
121
+ return (await filterScanDir({
129
122
  cwd,
130
123
  filter,
131
124
  filterDir,
@@ -140,13 +133,13 @@ async function scanFiles(cwd, includes, excludes) {
140
133
  * @param param0
141
134
  * @returns result from processing input rules
142
135
  */
143
- async function processInput({ cwd, input, packageJson, extra, } = { input: {} }) {
136
+ export async function processInput({ cwd, input, packageJson, extra, } = { input: {} }) {
144
137
  const files = await scanFiles(cwd, makeMmPatterns(input.include, input.minimatchOptions), makeMmPatterns(input.exclude, input.minimatchOptions));
145
138
  const fileHashes = await hashFiles(cwd, files);
146
139
  const data = {
147
- env: lodash_1.default.pick(process.env, input.includeEnv),
148
- versions: lodash_1.default.pick(process.versions, input.includeVersions),
149
- npmScripts: lodash_1.default.pick(lodash_1.default.get(packageJson || (await readPackageJson(cwd)), "scripts"), input.npmScripts),
140
+ env: _.pick(process.env, input.includeEnv),
141
+ versions: _.pick(process.versions, input.includeVersions),
142
+ npmScripts: _.pick(_.get(packageJson || (await readPackageJson(cwd)), "scripts"), input.npmScripts),
150
143
  fileHashes,
151
144
  extra: extra || {}, // additional data
152
145
  };
@@ -159,10 +152,10 @@ async function processInput({ cwd, input, packageJson, extra, } = { input: {} })
159
152
  * @param param0
160
153
  * @returns
161
154
  */
162
- async function processLifecycleInput({ cwd, input, packageJson, } = { input: {} }) {
155
+ export async function processLifecycleInput({ cwd, input, packageJson, } = { input: {} }) {
163
156
  packageJson = packageJson || (await readPackageJson(cwd));
164
- const npmScripts = lodash_1.default.pick(lodash_1.default.get(packageJson, "scripts"), input.npmScripts);
165
- if (lodash_1.default.isEmpty(npmScripts)) {
157
+ const npmScripts = _.pick(_.get(packageJson, "scripts"), input.npmScripts);
158
+ if (_.isEmpty(npmScripts)) {
166
159
  return {};
167
160
  }
168
161
  return await processInput({ cwd, input, packageJson });
@@ -173,22 +166,22 @@ async function processLifecycleInput({ cwd, input, packageJson, } = { input: {}
173
166
  * @param param0
174
167
  * @returns
175
168
  */
176
- async function processOutput({ cwd, inputHash, calcHash, output, preFiles, } = { output: {} }) {
169
+ export async function processOutput({ cwd, inputHash, calcHash, output, preFiles, } = { output: {} }) {
177
170
  const mmIncludes = makeMmPatterns(output.include, output.minimatchOptions);
178
171
  const mmExcludes = makeMmPatterns(output.exclude, output.minimatchOptions);
179
172
  const files = await scanFiles(cwd, mmIncludes, mmExcludes);
180
173
  preFiles = []
181
174
  .concat(preFiles)
182
175
  .filter((x) => {
183
- if (!x || (0, minimatch_group_1.unrollMmMatch)(x, mmExcludes)) {
176
+ if (!x || unrollMmMatch(x, mmExcludes)) {
184
177
  return false;
185
178
  }
186
179
  return true;
187
180
  })
188
- .map((x) => x.replace(`${cwd}${path_1.default.sep}`, ""));
181
+ .map((x) => x.replace(`${cwd}${Path.sep}`, ""));
189
182
  let hash = "";
190
183
  let fileHashes = {};
191
- const allFiles = lodash_1.default.uniq(files.concat(preFiles)).sort();
184
+ const allFiles = _.uniq(files.concat(preFiles)).sort();
192
185
  let data = { inputHash, fileHashes };
193
186
  //
194
187
  if (calcHash) {
@@ -200,4 +193,3 @@ async function processOutput({ cwd, inputHash, calcHash, output, preFiles, } = {
200
193
  const now = Date.now();
201
194
  return { files: allFiles, data, hash, access: now, create: now };
202
195
  }
203
- //# sourceMappingURL=caching.js.map
@@ -1,14 +1,11 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.FynpoConfigManager = void 0;
4
- const tslib_1 = require("tslib");
5
- const fs_1 = tslib_1.__importDefault(require("fs"));
6
- const path_1 = tslib_1.__importDefault(require("path"));
7
- const optional_require_1 = require("optional-require");
1
+ import Fs from "fs";
2
+ import Path from "path";
3
+ import { makeOptionalImport } from "optional-import";
4
+ const optionalImport = makeOptionalImport(import.meta);
8
5
  /**
9
6
  * manage fynpo config
10
7
  */
11
- class FynpoConfigManager {
8
+ export class FynpoConfigManager {
12
9
  constructor(opts = {}) {
13
10
  this.options = { cwd: process.cwd(), ...opts };
14
11
  this._topDir = undefined;
@@ -49,7 +46,7 @@ class FynpoConfigManager {
49
46
  }
50
47
  async readJson(file) {
51
48
  try {
52
- const data = await fs_1.default.promises.readFile(file, "utf8");
49
+ const data = await Fs.promises.readFile(file, "utf8");
53
50
  return JSON.parse(data);
54
51
  }
55
52
  catch (err) {
@@ -61,23 +58,44 @@ class FynpoConfigManager {
61
58
  }
62
59
  }
63
60
  async search() {
61
+ var _a;
64
62
  let dir = this.options.cwd;
65
63
  let prevDir = dir;
66
64
  let count = 0;
67
65
  do {
68
66
  // allow manually disable fynpo with a file
69
- if (fs_1.default.existsSync(path_1.default.join(dir, ".no-fynpo"))) {
67
+ if (Fs.existsSync(Path.join(dir, ".no-fynpo"))) {
70
68
  break;
71
69
  }
72
- [".js", ".json"].find((ext) => {
73
- return (this._config = (0, optional_require_1.optionalRequire)(path_1.default.join(dir, `fynpo.config${ext}`)));
70
+ //
71
+ // `fynpo.config.js` loads as a module: an absent file falls back, while one that exists
72
+ // but throws still surfaces as an error rather than being mistaken for absent.
73
+ //
74
+ // `fynpo.config.json` is NOT loaded as a module - `import()` of JSON needs an import
75
+ // attribute (`ERR_IMPORT_ATTRIBUTE_MISSING` without one), and reading it as JSON is what
76
+ // it is anyway. `readJson` already reports a malformed file and swallows only ENOENT.
77
+ //
78
+ const configMod = await optionalImport(Path.join(dir, "fynpo.config.js"), {
79
+ default: undefined,
74
80
  });
81
+ if (configMod) {
82
+ // a CJS config's `module.exports`, or an ESM config's `export default`, is on `.default`
83
+ this._config = (_a = configMod.default) !== null && _a !== void 0 ? _a : configMod;
84
+ }
85
+ else {
86
+ try {
87
+ this._config = await this.readJson(Path.join(dir, "fynpo.config.json"));
88
+ }
89
+ catch (_e) {
90
+ //
91
+ }
92
+ }
75
93
  if (this._config) {
76
94
  this._type = "fynpo monorepo";
77
95
  break;
78
96
  }
79
97
  try {
80
- this._config = await this.readJson(path_1.default.join(dir, "fynpo.json"));
98
+ this._config = await this.readJson(Path.join(dir, "fynpo.json"));
81
99
  this._type = "fynpo monorepo";
82
100
  break;
83
101
  }
@@ -85,7 +103,7 @@ class FynpoConfigManager {
85
103
  //
86
104
  }
87
105
  try {
88
- const lerna = await this.readJson(path_1.default.join(dir, "lerna.json"));
106
+ const lerna = await this.readJson(Path.join(dir, "lerna.json"));
89
107
  if (lerna.fynpo) {
90
108
  this._type = "lerna monorepo with fynpo";
91
109
  this._config = lerna;
@@ -96,14 +114,13 @@ class FynpoConfigManager {
96
114
  //
97
115
  }
98
116
  prevDir = dir;
99
- dir = path_1.default.dirname(dir);
117
+ dir = Path.dirname(dir);
100
118
  } while (++count < 50 && dir !== prevDir);
101
- // add alias patterns for packages config
102
- if (this._config && this._config.hasOwnProperty("packages")) {
103
- this._config.patterns = this._config.packages;
104
- }
119
+ //
120
+ // No `patterns` alias any more - see the matching note in fynpo/src/utils.ts loadConfig.
121
+ // `patterns` bypasses auto-search, while `include` is meant to filter what auto-search
122
+ // found. The raw `packages` config is carried through and resolved downstream. FPO-17.
123
+ //
105
124
  this._topDir = dir;
106
125
  }
107
126
  }
108
- exports.FynpoConfigManager = FynpoConfigManager;
109
- //# sourceMappingURL=fynpo-config.js.map
@@ -1,4 +1,4 @@
1
- import mm from "minimatch";
1
+ import { Minimatch } from "minimatch";
2
2
  /**
3
3
  * Basic information about a package: name, version, and its path in the monorepo
4
4
  */
@@ -61,7 +61,7 @@ export declare class PackageRef {
61
61
  * create a RegExp from it.
62
62
  */
63
63
  regex?: RegExp;
64
- mm?: mm.IMinimatch;
64
+ mm?: Minimatch;
65
65
  constructor(ref: string);
66
66
  /**
67
67
  * Parse a package ref in string form
@@ -174,6 +174,13 @@ export type ReadFynpoOptions = {
174
174
  patterns?: string[];
175
175
  /** top dir of the monorepo to start searching */
176
176
  cwd?: string;
177
+ /**
178
+ * List of package names to NOT resolve as local packages.
179
+ * These packages will always be fetched from registry instead of using local versions.
180
+ */
181
+ noFynLocal?: string[];
182
+ /** raw `packages` config from fynpo.json - drives discovery and the publish sets */
183
+ packages?: unknown;
177
184
  };
178
185
  /**
179
186
  * Create a package ID by joining name and version into a single string with `@`
@@ -198,6 +205,13 @@ export declare class FynpoDepGraph {
198
205
  depMapByPath: Record<string, PackageDepData>;
199
206
  /** Remember resolved package for a `name@<semver>` ID to its `name@version` ID */
200
207
  resolvedCache: Record<string, string>;
208
+ /**
209
+ * True when no `packages` patterns were configured and every directory had to be
210
+ * searched for a package.json. Callers can surface this - discovery is then
211
+ * implicit, and a repo that keeps packages outside the default location gets
212
+ * different results from the commands that don't auto-search.
213
+ */
214
+ autoSearched: boolean;
201
215
  _options: ReadFynpoOptions;
202
216
  constructor(options?: ReadFynpoOptions);
203
217
  /**
@@ -278,6 +292,18 @@ export declare class FynpoDepGraph {
278
292
  * @returns fynpo package info
279
293
  */
280
294
  resolvePackage(name: string, semver: string, fallbackToFirst?: boolean): FynpoPackageInfo;
295
+ /**
296
+ * Check if a dependency should NOT be resolved as a local package.
297
+ *
298
+ * Checks both:
299
+ * 1. Monorepo-wide `noFynLocal` option
300
+ * 2. Per-package `fyn.dependencies[name]` setting (false or "no-fyn-local")
301
+ *
302
+ * @param pkgInfo - the package that has the dependency
303
+ * @param depName - name of the dependency to check
304
+ * @returns true if the dependency should NOT be resolved locally
305
+ */
306
+ checkNoFynLocal(pkgInfo: FynpoPackageInfo, depName: string): boolean;
281
307
  /**
282
308
  * Figure out all the packages' direct dependencies on other local packages
283
309
  *