@fynpo/base 1.1.23 → 2.0.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/dist/index.js CHANGED
@@ -1,19 +1,17 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.caching = void 0;
4
- exports.readFynpoPackages = readFynpoPackages;
5
- exports.makePkgDeps = makePkgDeps;
6
- const tslib_1 = require("tslib");
7
- const path_1 = tslib_1.__importDefault(require("path"));
8
- const fs_1 = require("fs");
9
- const filter_scan_dir_1 = require("filter-scan-dir");
10
- const minimatch_1 = tslib_1.__importDefault(require("minimatch"));
11
- const lodash_1 = tslib_1.__importDefault(require("lodash"));
12
- const minimatch_group_1 = require("./minimatch-group");
13
- tslib_1.__exportStar(require("./fynpo-dep-graph"), exports);
14
- tslib_1.__exportStar(require("./fynpo-config"), exports);
15
- tslib_1.__exportStar(require("./util"), exports);
16
- exports.caching = tslib_1.__importStar(require("./caching"));
1
+ import Path from "path";
2
+ import { promises as Fs } from "fs";
3
+ import { filterScanDir } from "filter-scan-dir";
4
+ import { Minimatch } from "minimatch";
5
+ import _ from "lodash";
6
+ import { groupMM } from "./minimatch-group.js";
7
+ import { resolvePackagesConfig, scanPatterns, includeFilter, outOfScopePackages, } from "./packages-config.js";
8
+ import { makeGitignoreMatcher } from "./gitignore.js";
9
+ export * from "./fynpo-dep-graph.js";
10
+ export * from "./fynpo-config.js";
11
+ export * from "./packages-config.js";
12
+ export * from "./gitignore.js";
13
+ export * from "./util.js";
14
+ export * as caching from "./caching.js";
17
15
  /**
18
16
  * Take an array of packages and figure out their dependencies on each other
19
17
  *
@@ -22,7 +20,7 @@ exports.caching = tslib_1.__importStar(require("./caching"));
22
20
  function processDirectDeps(packages) {
23
21
  const add = (name, deps, type) => {
24
22
  const depPkg = packages[name];
25
- lodash_1.default.each(deps, (semver, depName) => {
23
+ _.each(deps, (semver, depName) => {
26
24
  if (!packages.hasOwnProperty(depName)) {
27
25
  return;
28
26
  }
@@ -31,7 +29,7 @@ function processDirectDeps(packages) {
31
29
  depPkg.localDepsByType[type].push(depName);
32
30
  });
33
31
  };
34
- lodash_1.default.each(packages, (pkg, name) => {
32
+ _.each(packages, (pkg, name) => {
35
33
  add(name, pkg.dependencies, "dep");
36
34
  add(name, pkg.devDependencies, "dev");
37
35
  add(name, pkg.optionalDependencies, "opt");
@@ -44,31 +42,81 @@ function processDirectDeps(packages) {
44
42
  * @param circulars - array of package pairs that depend on each other
45
43
  */
46
44
  function processIndirectDeps(packages, circulars) {
47
- let change = 0;
48
- const add = (info, deps) => {
49
- lodash_1.default.each(deps, (dep) => {
45
+ //
46
+ // Membership used to be `Array.indexOf` over localDeps / indirectDeps / circulars, and
47
+ // both the walk and the fixpoint were recursive. That is what made a cycle fatal before
48
+ // FPO-19, and it stayed expensive afterwards: a 500-package chain took ~17s to resolve,
49
+ // and a deep enough graph could still exhaust the stack through the walk alone, cycle or
50
+ // no cycle. Sets for membership and explicit stacks for both loops (FPO-43).
51
+ //
52
+ // The arrays are still the output, appended in the same discovery order as before - the
53
+ // Sets only answer "already have it?" without an O(n) scan.
54
+ //
55
+ const localDepsSet = new Map();
56
+ const indirectDepsSet = new Map();
57
+ const circularsSet = new Set(circulars);
58
+ _.each(packages, (pkg, name) => {
59
+ localDepsSet.set(name, new Set(pkg.localDeps));
60
+ indirectDepsSet.set(name, new Set(pkg.indirectDeps));
61
+ });
62
+ /**
63
+ * Accumulate every package reachable from `info` into its indirectDeps.
64
+ *
65
+ * Depth-first over a snapshot of each package's deps, taken as it is reached - the same
66
+ * order the recursive walk produced. `seen` expands each package once per traversal,
67
+ * which is all a transitive closure needs and is what keeps a cycle from looping.
68
+ *
69
+ * @param info the package whose indirect deps we are accumulating
70
+ * @returns how many indirect deps were added
71
+ */
72
+ const add = (info) => {
73
+ const seen = new Set();
74
+ const infoIndirect = indirectDepsSet.get(info.name);
75
+ const infoLocal = localDepsSet.get(info.name);
76
+ let added = 0;
77
+ // each frame is a dep list plus how far into it we have gone
78
+ const stack = [
79
+ { deps: info.localDeps.concat(info.indirectDeps), at: 0 },
80
+ ];
81
+ while (stack.length > 0) {
82
+ const frame = stack[stack.length - 1];
83
+ if (frame.at >= frame.deps.length) {
84
+ stack.pop();
85
+ continue;
86
+ }
87
+ const dep = frame.deps[frame.at++];
50
88
  const depPkg = packages[dep];
51
- if (info.localDeps.indexOf(dep) < 0 && info.indirectDeps.indexOf(dep) < 0) {
52
- change++;
89
+ if (!infoLocal.has(dep) && !infoIndirect.has(dep)) {
90
+ added++;
53
91
  info.indirectDeps.push(dep);
92
+ infoIndirect.add(dep);
54
93
  depPkg.dependents.push(info.name);
55
94
  }
56
- if (depPkg.localDeps.indexOf(info.name) >= 0) {
57
- const x = [info.name, depPkg.name].sort().join(",");
58
- if (circulars.indexOf(x) < 0) {
59
- circulars.push(x);
95
+ // a cycle that comes straight back to `info` - record the pair and stop descending
96
+ if (localDepsSet.get(dep).has(info.name)) {
97
+ const pair = [info.name, depPkg.name].sort().join(",");
98
+ if (!circularsSet.has(pair)) {
99
+ circularsSet.add(pair);
100
+ circulars.push(pair);
60
101
  }
61
- return;
102
+ continue;
62
103
  }
63
- add(info, depPkg.localDeps.concat(depPkg.indirectDeps));
64
- });
104
+ if (seen.has(dep)) {
105
+ continue;
106
+ }
107
+ seen.add(dep);
108
+ stack.push({ deps: depPkg.localDeps.concat(depPkg.indirectDeps), at: 0 });
109
+ }
110
+ return added;
65
111
  };
66
- lodash_1.default.each(packages, (pkg) => {
67
- add(pkg, pkg.localDeps.concat(pkg.indirectDeps));
68
- });
69
- if (change > 0) {
70
- processIndirectDeps(packages, circulars);
71
- }
112
+ // run to a fixpoint: expanding one package can give another package more to reach through
113
+ let change = 0;
114
+ do {
115
+ change = 0;
116
+ _.each(packages, (pkg) => {
117
+ change += add(pkg);
118
+ });
119
+ } while (change > 0);
72
120
  }
73
121
  /**
74
122
  *
@@ -76,7 +124,7 @@ function processIndirectDeps(packages, circulars) {
76
124
  * @param level
77
125
  */
78
126
  function includeDeps(packages, level) {
79
- const localDeps = lodash_1.default.uniq(Object.keys(packages).reduce((acc, p) => {
127
+ const localDeps = _.uniq(Object.keys(packages).reduce((acc, p) => {
80
128
  if (packages[p] && !packages[p].ignore) {
81
129
  return acc.concat(packages[p].localDeps.filter((x) => packages[x] && packages[x].ignore));
82
130
  }
@@ -97,35 +145,72 @@ function includeDeps(packages, level) {
97
145
  /**
98
146
  * Read the packages of a fynpo mono-repo
99
147
  *
100
- * @param patterns - array of minimatch patterns. default: `["packages/*"]`
148
+ * Honors the same `packages` config as {@link FynpoDepGraph}, so both discovery paths agree.
149
+ * Passing `patterns` directly still works and takes precedence, for callers that already know
150
+ * what they want.
151
+ *
152
+ * @param patterns - explicit minimatch patterns. Overrides whatever `packages` config says.
153
+ * @param cwd - repo root
154
+ * @param packages - raw `packages` config, resolved via {@link resolvePackagesConfig}
101
155
  * @returns - packages from the fynpo mono-repo
102
156
  */
103
- async function readFynpoPackages({ patterns = ["packages/*"], cwd = process.cwd(), } = {}) {
104
- const mms = patterns.map((p) => new minimatch_1.default.Minimatch(p));
105
- const groups = (0, minimatch_group_1.groupMM)(mms, {});
157
+ export async function readFynpoPackages({ patterns = undefined, cwd = process.cwd(), packages = undefined, } = {}) {
158
+ const config = resolvePackagesConfig(packages);
159
+ const explicit = _.isEmpty(patterns) ? scanPatterns(config) : patterns;
160
+ const gitignore = makeGitignoreMatcher(cwd);
161
+ const excludeMms = config.exclude.map((p) => new Minimatch(p));
162
+ const isExcluded = (path) => Boolean(path) && excludeMms.some((m) => m.match(path.split(Path.sep).join("/")));
163
+ // `include` filters what the scan found - it does not replace the scan (FPO-17)
164
+ const includeMms = (_.isEmpty(patterns) ? includeFilter(config) : []).map((p) => new Minimatch(p));
165
+ const isIncluded = (path) => includeMms.length === 0 || includeMms.some((m) => m.match(path.split(Path.sep).join("/")));
166
+ // null patterns means auto-search: scan from the root for every package.json
167
+ const autoSearch = explicit === null;
168
+ const groups = autoSearch
169
+ ? { ".": null }
170
+ : groupMM(explicit.map((p) => new Minimatch(p)), {});
171
+ const skipForAutoSearch = (path) => autoSearch && config.autoSearch.respectGitignore && gitignore.ignores(path);
106
172
  const files = [];
107
173
  for (const prefix in groups) {
108
- files.push(await (0, filter_scan_dir_1.filterScanDir)({
174
+ files.push(await filterScanDir({
109
175
  cwd,
110
176
  prefix,
111
177
  concurrency: 500,
112
- filter: (f) => f === "package.json",
113
- filterDir: (dir, _p, extras) => {
114
- if (dir !== "node_modules") {
115
- return Boolean(groups[prefix].find((save) => save.mm.match(extras.dirFile)));
178
+ filter: (f, path, extras) => {
179
+ var _a;
180
+ if (f !== "package.json") {
181
+ return false;
182
+ }
183
+ if (autoSearch && (!path || path === ".")) {
184
+ // the monorepo's own package.json is not a member
185
+ return false;
116
186
  }
117
- return false;
187
+ if (autoSearch && ((_a = extras === null || extras === void 0 ? void 0 : extras.files) === null || _a === void 0 ? void 0 : _a.includes("fynpo.json"))) {
188
+ // a nested fynpo root is its own monorepo
189
+ return false;
190
+ }
191
+ return !isExcluded(path) && !skipForAutoSearch(path);
192
+ },
193
+ filterDir: (dir, path, extras) => {
194
+ if (dir === "node_modules" || isExcluded(path) || skipForAutoSearch(path)) {
195
+ return false;
196
+ }
197
+ if (autoSearch) {
198
+ return !dir.startsWith(".");
199
+ }
200
+ return Boolean(groups[prefix].find((save) => save.mm.match(extras.dirFile)));
118
201
  },
119
202
  }));
120
203
  }
121
- const allFiles = [].concat(...files).sort();
204
+ const allFiles = [].concat(...files)
205
+ .filter((f) => isIncluded(Path.dirname(f)))
206
+ .sort();
122
207
  const allPkgs = {};
123
208
  for (const pkgFile of allFiles) {
124
- const pkgStr = await fs_1.promises.readFile(path_1.default.join(cwd, pkgFile), "utf-8");
209
+ const pkgStr = await Fs.readFile(Path.join(cwd, pkgFile), "utf-8");
125
210
  const pkgJson = JSON.parse(pkgStr);
126
- const path = path_1.default.dirname(pkgFile);
127
- const pkgDir = pkgJson.name[0] === "@" && path.endsWith(pkgJson.name) ? pkgJson.name : path_1.default.basename(path);
128
- allPkgs[pkgJson.name] = Object.assign(lodash_1.default.pick(pkgJson, [
211
+ const path = Path.dirname(pkgFile);
212
+ const pkgDir = pkgJson.name[0] === "@" && path.endsWith(pkgJson.name) ? pkgJson.name : Path.basename(path);
213
+ allPkgs[pkgJson.name] = Object.assign(_.pick(pkgJson, [
129
214
  "name",
130
215
  "version",
131
216
  "dependencies",
@@ -162,7 +247,7 @@ async function readFynpoPackages({ patterns = ["packages/*"], cwd = process.cwd(
162
247
  * @param opts - options
163
248
  * @returns
164
249
  */
165
- function makePkgDeps(packages, opts) {
250
+ export function makePkgDeps(packages, opts) {
166
251
  const cwd = opts.cwd || process.cwd();
167
252
  let circulars = [];
168
253
  let ignores = opts.ignore || [];
@@ -180,14 +265,11 @@ function makePkgDeps(packages, opts) {
180
265
  }
181
266
  }
182
267
  // If options.scope is defined, then ignore packages not in it
183
- if (opts.scope && opts.scope.length > 0) {
184
- Object.keys(packages).forEach((p) => {
185
- const scope = p[0] === "@" ? p.slice(0, p.indexOf("/")) : undefined;
186
- if ((!scope || !opts.scope.includes(scope)) && !ignores[p]) {
187
- ignores.push(p);
188
- }
189
- });
190
- }
268
+ outOfScopePackages(opts.scope, Object.keys(packages)).forEach((p) => {
269
+ if (!ignores[p]) {
270
+ ignores.push(p);
271
+ }
272
+ });
191
273
  if (opts.only && opts.only.length > 0) {
192
274
  opts.only.forEach((x) => {
193
275
  if (!packages[x]) {
@@ -200,17 +282,28 @@ function makePkgDeps(packages, opts) {
200
282
  }
201
283
  });
202
284
  }
203
- const depMap = lodash_1.default.mapValues(packages, (pkg) => {
204
- return lodash_1.default.pick(pkg, ["name", "localDeps", "indirectDeps", "dependents"]);
285
+ const depMap = _.mapValues(packages, (pkg) => {
286
+ return _.pick(pkg, ["name", "localDeps", "indirectDeps", "dependents"]);
205
287
  });
206
- circulars = lodash_1.default.uniq(circulars).map((x) => x.split(","));
207
- ignores = ignores.concat(lodash_1.default.map(circulars, (pair) => {
288
+ circulars = _.uniq(circulars).map((x) => x.split(","));
289
+ // Breaking a cycle costs a package: the less depended-on half of each pair is dropped from
290
+ // the run. That used to happen in silence, so a repo with cycles quietly processed fewer
291
+ // packages than it has and nothing said which ones or why (FPO-43).
292
+ const circularIgnores = _.uniq(_.map(circulars, (pair) => {
208
293
  const depA = packages[pair[0]].dependents.length;
209
294
  const depB = packages[pair[1]].dependents.length;
210
295
  if (depA === depB)
211
296
  return undefined;
212
297
  return depA > depB ? pair[1] : pair[0];
213
298
  }).filter((x) => x));
299
+ if (circulars.length > 0) {
300
+ warnings.push(`Circular local dependencies: ${circulars.map((pair) => pair.join(" <-> ")).join(", ")}`);
301
+ if (circularIgnores.length > 0) {
302
+ warnings.push(`Ignoring ${circularIgnores.join(", ")} to break the circular dependencies above - ` +
303
+ `they are dropped from this run. Break the cycles to include them.`);
304
+ }
305
+ }
306
+ ignores = ignores.concat(circularIgnores);
214
307
  ignores.forEach((x) => {
215
308
  if (packages[x]) {
216
309
  packages[x].ignore = true;
@@ -231,4 +324,3 @@ function makePkgDeps(packages, opts) {
231
324
  focusPkgPath,
232
325
  };
233
326
  }
234
- //# sourceMappingURL=index.js.map
@@ -1,12 +1,12 @@
1
- import mm from "minimatch";
1
+ import { Minimatch, type ParseReturnFiltered } from "minimatch";
2
2
  /**
3
3
  * Remember the minimatch group info from a pattern
4
4
  */
5
5
  type MMGroup = {
6
6
  /** the minimatch object */
7
- mm: mm.IMinimatch;
8
- /** the minimatch sets */
9
- set: any[][];
7
+ mm: Minimatch;
8
+ /** one expanded pattern set (a row of Minimatch.set) */
9
+ set: ParseReturnFiltered[];
10
10
  /** index of the set within the minimatch object */
11
11
  setIx: number;
12
12
  /** index of the first set that's not a literal string */
@@ -22,16 +22,16 @@ export type MMGroups = Record<string, MMGroup[]>;
22
22
  * @param groups - object to group the minimatch objects
23
23
  * @returns object of grouped minimatch objects
24
24
  */
25
- export declare function groupMM(mms: mm.IMinimatch[], groups: MMGroups): MMGroups;
25
+ export declare function groupMM(mms: Minimatch[], groups: MMGroups): MMGroups;
26
26
  /**
27
27
  * process a minimatch pattern to group them for matching directories
28
28
  *
29
29
  * - needs to create a new pattern at every non-string part
30
30
  * @param m0 minimatch pattern
31
31
  */
32
- export declare function deconstructMM(m0: mm.IMinimatch): {
33
- m0: mm.IMinimatch;
34
- mms: mm.IMinimatch[];
32
+ export declare function deconstructMM(m0: Minimatch): {
33
+ m0: Minimatch;
34
+ mms: Minimatch[];
35
35
  };
36
36
  /**
37
37
  * check that a path matches against a list of minimatch patterns
@@ -40,7 +40,7 @@ export declare function deconstructMM(m0: mm.IMinimatch): {
40
40
  * @param patterns
41
41
  * @returns the first pattern that match or false
42
42
  */
43
- export declare function checkMmMatch(fullPath: string, patterns: mm.IMinimatch[]): false | mm.IMinimatch;
43
+ export declare function checkMmMatch(fullPath: string, patterns: Minimatch[]): false | Minimatch;
44
44
  /**
45
45
  * Take a full path and match each level of it to a list of minimatch patterns
46
46
  *
@@ -48,5 +48,5 @@ export declare function checkMmMatch(fullPath: string, patterns: mm.IMinimatch[]
48
48
  * @param mms
49
49
  * @returns
50
50
  */
51
- export declare function unrollMmMatch(path: string, mms: mm.IMinimatch[]): false | mm.IMinimatch;
51
+ export declare function unrollMmMatch(path: string, mms: Minimatch[]): false | Minimatch;
52
52
  export {};
@@ -1,12 +1,5 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.groupMM = groupMM;
4
- exports.deconstructMM = deconstructMM;
5
- exports.checkMmMatch = checkMmMatch;
6
- exports.unrollMmMatch = unrollMmMatch;
7
- const tslib_1 = require("tslib");
8
- const minimatch_1 = tslib_1.__importDefault(require("minimatch"));
9
- const lodash_1 = tslib_1.__importDefault(require("lodash"));
1
+ import { Minimatch, GLOBSTAR } from "minimatch";
2
+ import _ from "lodash";
10
3
  /**
11
4
  * process a list of minimatch objects and group them by the string prefix of their patterns
12
5
  *
@@ -14,7 +7,7 @@ const lodash_1 = tslib_1.__importDefault(require("lodash"));
14
7
  * @param groups - object to group the minimatch objects
15
8
  * @returns object of grouped minimatch objects
16
9
  */
17
- function groupMM(mms, groups) {
10
+ export function groupMM(mms, groups) {
18
11
  mms.forEach((mm) => {
19
12
  mm.set.forEach((set, setIx) => {
20
13
  const ix = set.findIndex((s) => typeof s !== "string");
@@ -43,20 +36,19 @@ function groupMM(mms, groups) {
43
36
  * - needs to create a new pattern at every non-string part
44
37
  * @param m0 minimatch pattern
45
38
  */
46
- function deconstructMM(m0) {
39
+ export function deconstructMM(m0) {
47
40
  const mms = [];
48
41
  const patterns = { m0, mms };
49
42
  const set = m0.set[0];
50
43
  const globParts = m0.globParts[0];
51
44
  const iParts = [];
52
- const { GLOBSTAR } = minimatch_1.default;
53
45
  for (let ix = 0; ix < set.length; ix++) {
54
46
  const s = set[ix];
55
47
  const g = globParts[ix];
56
48
  // if we hit something that's not string, then we need a mm with just the strings
57
49
  // because a dir like "src" will not match "src/*"
58
50
  if (typeof s !== "string" && iParts.length > 0) {
59
- mms.push(new minimatch_1.default.Minimatch(iParts.join("/"), m0.options));
51
+ mms.push(new Minimatch(iParts.join("/"), m0.options));
60
52
  }
61
53
  if (ix === set.length - 1) {
62
54
  mms.push(m0);
@@ -65,7 +57,7 @@ function deconstructMM(m0) {
65
57
  else {
66
58
  iParts.push(g);
67
59
  if (typeof s !== "string") {
68
- mms.push(new minimatch_1.default.Minimatch(iParts.join("/"), m0.options));
60
+ mms.push(new Minimatch(iParts.join("/"), m0.options));
69
61
  }
70
62
  }
71
63
  if (s === GLOBSTAR) {
@@ -82,8 +74,14 @@ function deconstructMM(m0) {
82
74
  * @param patterns
83
75
  * @returns the first pattern that match or false
84
76
  */
85
- function checkMmMatch(fullPath, patterns) {
86
- return !lodash_1.default.isEmpty(patterns) && patterns.find((patternMm) => patternMm.match(fullPath));
77
+ export function checkMmMatch(fullPath, patterns) {
78
+ // Callers append a trailing "/" to force matching a directory. minimatch 3 never let a
79
+ // pattern part that can match the empty string (e.g. "?(a|b)") consume that trailing empty
80
+ // segment, but minimatch 10 does, so "**/?(a|b)" would wrongly match "src/". For every
81
+ // other case v3's match of "dir/" is equivalent to matching "dir", so strip the trailing
82
+ // slash to keep the v3 semantics.
83
+ const path = fullPath.length > 1 && fullPath.endsWith("/") ? fullPath.slice(0, -1) : fullPath;
84
+ return !_.isEmpty(patterns) && patterns.find((patternMm) => patternMm.match(path));
87
85
  }
88
86
  /**
89
87
  * Take a full path and match each level of it to a list of minimatch patterns
@@ -92,7 +90,7 @@ function checkMmMatch(fullPath, patterns) {
92
90
  * @param mms
93
91
  * @returns
94
92
  */
95
- function unrollMmMatch(path, mms) {
93
+ export function unrollMmMatch(path, mms) {
96
94
  const parts = path.split("/");
97
95
  let rp;
98
96
  for (let i = 0; i < parts.length - 1; i++) {
@@ -104,4 +102,3 @@ function unrollMmMatch(path, mms) {
104
102
  }
105
103
  return checkMmMatch(path, mms);
106
104
  }
107
- //# sourceMappingURL=minimatch-group.js.map
@@ -0,0 +1,78 @@
1
+ /** auto-search settings, after defaults are applied */
2
+ export type AutoSearchConfig = {
3
+ /** search the whole repo for package.json when no explicit `include` patterns are given */
4
+ enable: boolean;
5
+ /** when true, auto-search skips gitignored paths. Does NOT affect the publish veto. */
6
+ respectGitignore: boolean;
7
+ };
8
+ /** the `packages` config, after defaults are applied */
9
+ export type PackagesConfig = {
10
+ autoSearch: AutoSearchConfig;
11
+ /** explicit discovery patterns. Empty means "not specified" */
12
+ include: string[];
13
+ /** applies to every package, auto-searched or explicitly matched */
14
+ exclude: string[];
15
+ /** publish allow list. Empty means every discovered package is eligible */
16
+ publishInclude: string[];
17
+ /** publish deny list, applied after the allow list */
18
+ publishExclude: string[];
19
+ };
20
+ /**
21
+ * Normalize `packages` from a fynpo config into a complete {@link PackagesConfig}.
22
+ *
23
+ * Accepts both shapes:
24
+ *
25
+ * - **array** - the historical form. Treated as `publishInclude`, with auto-search on and
26
+ * `respectGitignore` off. It no longer narrows discovery.
27
+ * - **object** - `{ autoSearch, include, exclude, publishInclude, publishExclude }`.
28
+ *
29
+ * Defaults: `autoSearch` on, `respectGitignore` off. With auto-search off and no `include`,
30
+ * `include` falls back to `["packages/*"]`.
31
+ *
32
+ * @param packages - the raw `packages` value from fynpo.json / fynpo.config.js
33
+ * @returns the resolved config, every field populated
34
+ */
35
+ export declare function resolvePackagesConfig(packages?: unknown): PackagesConfig;
36
+ /**
37
+ * The npm scope of a package name, or undefined when it is unscoped.
38
+ *
39
+ * @param name - package name
40
+ * @returns the scope including its `@`, e.g. `@fynjs`
41
+ */
42
+ export declare function packageScope(name: string): string | undefined;
43
+ /**
44
+ * Package names that fall OUTSIDE the given scopes, i.e. the ones `--scope` should exclude.
45
+ *
46
+ * An unscoped package is never in any scope, so it is always excluded once `--scope` is given.
47
+ * Scopes may be written with or without the leading `@`.
48
+ *
49
+ * Shared by both selection paths so they cannot disagree: `makePkgDeps` (used by prepare) and
50
+ * the dep-graph path (used by bootstrap, local and run). `--scope` used to be applied only in
51
+ * the former, so the three commands that actually advertise it silently ignored it. See FPO-37.
52
+ *
53
+ * @param scopes - the requested scopes
54
+ * @param names - all known package names
55
+ * @returns names to exclude; empty when no scope was requested
56
+ */
57
+ export declare function outOfScopePackages(scopes: unknown, names: string[]): string[];
58
+ /**
59
+ * Decide how to scan for packages.
60
+ *
61
+ * `include` does NOT turn auto-search off - auto-search is on by default and stays on, so it
62
+ * still decides *how the tree is walked*. `include` then filters what the walk found, via
63
+ * {@link includeFilter}. Only with auto-search off does `include` become the scan patterns
64
+ * themselves, falling back to `packages/*`.
65
+ *
66
+ * @param config - resolved packages config
67
+ * @returns `null` to auto-search the whole repo, otherwise the patterns to scan
68
+ */
69
+ export declare function scanPatterns(config: PackagesConfig): string[] | null;
70
+ /**
71
+ * The patterns a discovered package must match to be kept.
72
+ *
73
+ * Empty means keep everything the scan found.
74
+ *
75
+ * @param config - resolved packages config
76
+ * @returns patterns to match a package path against
77
+ */
78
+ export declare function includeFilter(config: PackagesConfig): string[];