rman 1.2.5 → 1.3.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/constants.js CHANGED
@@ -1 +1 @@
1
- export const version = '1.2.5';
1
+ export const version = '1.3.0';
package/core/config.d.ts CHANGED
@@ -27,52 +27,61 @@ export declare function readDirConfig(dirname: string): Promise<RmanConfig>;
27
27
  * it (inclusive) - each directory level overrides the ones above it, the way tsconfig's `extends`
28
28
  * chain does.
29
29
  *
30
- * Every level contributes in two ways, and the difference is the whole model:
31
- *
32
- * - **Unmarked keys configure the package of the directory that declares them.** The root's own
33
- * `.rmanrc` therefore configures the *root package* - which is where repo-wide settings
34
- * (`packageManager`, `allowBranch`, `version.*`, `githubRelease.*`) are read from anyway - and
35
- * not, silently, every package under it.
36
- * - **A `"[selector]"` block configures the packages it names** - `"[*]"` for all of them (the root
37
- * included), `"[ws:*]"` for every one but the root, `"[/]"` for the root alone, `"[*-dialect]"`
38
- * for a glob over package names. See `parseSelector`. This is the only way a directory speaks
39
- * about anything but its own package.
40
- *
41
- * Splitting the two matters because the same key means different things to the two audiences. The
42
- * clearest case is `run.<script>.postScript`: on a package it's that package's build hook, run in
43
- * its own directory; on the root it's a repo-wide bookend run once at the repository root. A
44
- * cascade that fed one declaration to both ran a package-relative command (`node
45
- * ../../support/postbuild.cjs`) at the root, where it cannot resolve.
30
+ * Every level contributes in two ways:
31
+ *
32
+ * - **An unmarked key configures that directory and every package under it.** What a parent says
33
+ * reaches the children, which is what every directory-scoped config in the ecosystem does and
34
+ * what a reader expects without being told.
35
+ * - **A `"[selector]"` block narrows the audience** - `"[/]"` to the root package alone, `"[*]"` or
36
+ * a glob to the packages below (never the root, which is nobody's child). See `parseSelector`.
37
+ *
38
+ * **The root used to be the one directory whose unmarked config did *not* cascade**, on the
39
+ * reasoning that a setting means different things to a package and to the repository - and the
40
+ * reasoning is sound, but the rule it produced was not readable: an intermediate `packages/`
41
+ * cascaded while the root did not, so what a file meant depended on whether a `package.json` sat
42
+ * beside it. `vars` then had to be carved out as an exception, which is what a rule fighting itself
43
+ * looks like. One sentence now covers both: what is written above reaches below, and `"[/]"` is how
44
+ * a statement stays at the root.
45
+ *
46
+ * **The cost is real and lands on one subtree.** `run.<script>`'s hooks on the root are a repo-wide
47
+ * bookend, run once at the repository root; on a package they are that package's own hook, run in
48
+ * its directory. Cascaded, one declaration is both - once at the root and once per package. A
49
+ * repo-wide bookend therefore belongs under `"[/]"`, where its audience is visible; that is the
50
+ * migration this change asks for, and the only one that is not mechanical.
46
51
  *
47
52
  * `packageName` is what selectors match against; without it, selector blocks contribute nothing at
48
- * all. The root package passes its own, since `"[/]"` and `"[*]"` speak to it.
53
+ * all. The root package passes its own, since `"[/]"` speaks to it.
49
54
  */
50
55
  export declare function resolveConfig(rootDir: string, targetDir: string, cache?: Map<string, RmanConfig>, packageName?: string): Promise<RmanConfig>;
51
- /** A config key naming packages rather than settings: `"[*]"`, `"[/]"`, `"[ws:*]"`, `"[pkg-a]"`. The
56
+ /** A config key naming packages rather than settings: `"[*]"`, `"[/]"`, `"[pkg-a]"`. The
52
57
  * brackets are what keep this space from colliding with real config keys - no setting starts with
53
58
  * one - and in YAML they also mean the key always needs quoting (`"[*]":`), since a bare `[*]`
54
59
  * parses as a flow sequence. */
55
60
  export declare function isSelectorKey(key: string): boolean;
56
61
  /**
57
- * **Which packages a selector speaks for.** Three audiences, because a repository has three:
62
+ * **Which packages a selector speaks for.** Two audiences, and the second is a glob:
58
63
  *
59
64
  * | | |
60
65
  * | --- | --- |
61
- * | `"[/]"` | the **root package** only |
62
- * | `"[*]"`, `"[pkg-a]"`, `"[*-dialect]"` | **every** package the glob matches, root included |
63
- * | `"[ws:*]"`, `"[workspace:pkg-*]"` | every **non-root** package the glob matches |
66
+ * | `"[/]"` | the **root package** alone |
67
+ * | `"[*]"`, `"[pkg-a]"`, `"[*-dialect]"` | the packages **below** this directory that the glob matches |
64
68
  *
65
69
  * `/` for the root because that is what a repository root is called everywhere else, and it cannot
66
- * collide with a package name. `ws:` is a qualifier on the glob rather than a separate spelling of
67
- * `*`, so `"[ws:pkg-*]"` means what it looks like.
68
- *
69
- * **`"[*]"` includes the root, and that is a change from how it used to read.** Before, selectors
70
- * were not applied to the root at all, so `"[*]"` silently meant "the workspace packages" - a
71
- * catch-all with an exception nothing in the syntax mentioned. The three names above say which
72
- * audience is meant; `"[ws:*]"` is the old behaviour, now spelled.
70
+ * collide with a package name.
71
+ *
72
+ * **The root is never selected by name, and that one rule removes two traps.** A glob matches
73
+ * package names, and the root is nobody's child - so `"[my-*]"` cannot quietly pick up a repository
74
+ * whose root package happens to be called `my-repo`, and `"[*]"` cannot hand a package-shaped
75
+ * setting to a root that has no build directory to apply it to. The root is addressed structurally
76
+ * or not at all.
77
+ *
78
+ * **`"[ws:*]"` / `"[workspace:*]"` is accepted and means exactly `"[*]"`.** The qualifier existed to
79
+ * say "not the root" back when a bare glob included it; the shape of the set says that now, so it
80
+ * has nothing left to add. Accepted rather than rejected because the two spellings resolve to the
81
+ * same packages - an error would be friction with no reader to protect.
73
82
  */
74
83
  export declare function parseSelector(key: string): {
75
- scope: 'root' | 'all' | 'workspace';
84
+ scope: 'root' | 'package';
76
85
  test: (name: string) => boolean;
77
86
  };
78
87
  /** The glob inside a selector key, as a `RegExp` anchored at both ends - so `"[*-dialect]"` matches
@@ -330,7 +339,7 @@ export declare const DEFERRED_PATHS: string[];
330
339
  * config:
331
340
  *
332
341
  * ```js
333
- * '[ws:*]': {
342
+ * '[*]': {
334
343
  * clean: { include: ({ vars, value }) => [...value, vars.buildDir] }, // a value: called here
335
344
  * run: { build: { after: ({ pkg }) => copyDocs(pkg) } }, // a step: called by `run`
336
345
  * }
package/core/config.js CHANGED
@@ -117,32 +117,37 @@ export async function readDirConfig(dirname) {
117
117
  * it (inclusive) - each directory level overrides the ones above it, the way tsconfig's `extends`
118
118
  * chain does.
119
119
  *
120
- * Every level contributes in two ways, and the difference is the whole model:
121
- *
122
- * - **Unmarked keys configure the package of the directory that declares them.** The root's own
123
- * `.rmanrc` therefore configures the *root package* - which is where repo-wide settings
124
- * (`packageManager`, `allowBranch`, `version.*`, `githubRelease.*`) are read from anyway - and
125
- * not, silently, every package under it.
126
- * - **A `"[selector]"` block configures the packages it names** - `"[*]"` for all of them (the root
127
- * included), `"[ws:*]"` for every one but the root, `"[/]"` for the root alone, `"[*-dialect]"`
128
- * for a glob over package names. See `parseSelector`. This is the only way a directory speaks
129
- * about anything but its own package.
130
- *
131
- * Splitting the two matters because the same key means different things to the two audiences. The
132
- * clearest case is `run.<script>.postScript`: on a package it's that package's build hook, run in
133
- * its own directory; on the root it's a repo-wide bookend run once at the repository root. A
134
- * cascade that fed one declaration to both ran a package-relative command (`node
135
- * ../../support/postbuild.cjs`) at the root, where it cannot resolve.
120
+ * Every level contributes in two ways:
121
+ *
122
+ * - **An unmarked key configures that directory and every package under it.** What a parent says
123
+ * reaches the children, which is what every directory-scoped config in the ecosystem does and
124
+ * what a reader expects without being told.
125
+ * - **A `"[selector]"` block narrows the audience** - `"[/]"` to the root package alone, `"[*]"` or
126
+ * a glob to the packages below (never the root, which is nobody's child). See `parseSelector`.
127
+ *
128
+ * **The root used to be the one directory whose unmarked config did *not* cascade**, on the
129
+ * reasoning that a setting means different things to a package and to the repository - and the
130
+ * reasoning is sound, but the rule it produced was not readable: an intermediate `packages/`
131
+ * cascaded while the root did not, so what a file meant depended on whether a `package.json` sat
132
+ * beside it. `vars` then had to be carved out as an exception, which is what a rule fighting itself
133
+ * looks like. One sentence now covers both: what is written above reaches below, and `"[/]"` is how
134
+ * a statement stays at the root.
135
+ *
136
+ * **The cost is real and lands on one subtree.** `run.<script>`'s hooks on the root are a repo-wide
137
+ * bookend, run once at the repository root; on a package they are that package's own hook, run in
138
+ * its directory. Cascaded, one declaration is both - once at the root and once per package. A
139
+ * repo-wide bookend therefore belongs under `"[/]"`, where its audience is visible; that is the
140
+ * migration this change asks for, and the only one that is not mechanical.
136
141
  *
137
142
  * `packageName` is what selectors match against; without it, selector blocks contribute nothing at
138
- * all. The root package passes its own, since `"[/]"` and `"[*]"` speak to it.
143
+ * all. The root package passes its own, since `"[/]"` speaks to it.
139
144
  */
140
145
  export async function resolveConfig(rootDir, targetDir, cache = new Map(), packageName) {
141
146
  const result = {};
142
147
  const target = path.resolve(targetDir);
143
148
  /** The root *package* is the one whose directory is the repository root - no other test is
144
149
  * needed, and none would be as reliable: a name can be anything. In a single-package repository
145
- * that is the only package, so `"[/]"` reaches it and `"[ws:*]"` reaches nothing. */
150
+ * that is the only package, so `"[/]"` reaches it and `"[*]"` reaches nothing. */
146
151
  const isRoot = target === path.resolve(rootDir);
147
152
  for (const dir of dirChain(rootDir, targetDir)) {
148
153
  let local = cache.get(dir);
@@ -150,39 +155,30 @@ export async function resolveConfig(rootDir, targetDir, cache = new Map(), packa
150
155
  local = await readDirConfig(dir);
151
156
  cache.set(dir, local);
152
157
  }
153
- // A directory holding a package speaks for that package only - which is what keeps the root's
154
- // own config off every package under it. A directory that holds none (an intermediate
155
- // `packages/`, say) has no package to speak for, so its unmarked config can only mean
156
- // "everything below" and still cascades.
157
- const ownsAPackage = fs.existsSync(path.join(dir, 'package.json'));
158
- const speaksForTarget = !ownsAPackage || path.resolve(dir) === target;
159
158
  /**
160
- * `vars` is the **one** unmarked key that cascades past the package its directory speaks for,
161
- * and it is not a hole in that rule - it is a key the rule was never about. The rule exists
162
- * because a setting means different things to the two audiences (`run.build.after` on the root
163
- * is a repo-wide bookend, on a package its own hook), so one declaration cannot serve both.
164
- * `vars: {x: 1}` means the number 1 to everyone; there is no second audience to be wrong for.
159
+ * **Unmarked first, because it is the widest thing this level says** - and that is an inversion
160
+ * of the order this loop used to run in, where a directory's own plain config beat a selector
161
+ * declared beside it. Under the old reading "unmarked" meant *this package* and so was the
162
+ * narrower of the two; it now means *this package and everything below*, which is the wider.
163
+ * Precedence follows the audience, not the spelling, so it had to move.
165
164
  *
166
- * Merged *before* this directory's selector blocks, so `"[*]": {vars: ...}` - which names the
167
- * packages explicitly - overrides the same directory's plainer statement.
165
+ * Its position in the file is deliberately not consulted: a selector block written above the
166
+ * plain keys still wins. Unmarked is not a fourth selector - it is the level's floor, and the
167
+ * layer that feeds the directories below it.
168
168
  */
169
- if (!speaksForTarget && local.vars !== undefined)
170
- mergeConfig(result, { vars: local.vars });
171
- // Selectors next, so a directory's own unmarked config still wins over a selector declared
172
- // alongside it - "this package" is a more specific statement than "packages matching a glob".
169
+ mergeConfig(result, stripSelectors(local));
170
+ /** Then the selector blocks, **in the order they were written** - see `matchingSelectors`. */
173
171
  if (packageName) {
174
172
  for (const block of matchingSelectors(local, packageName, isRoot))
175
173
  mergeConfig(result, block);
176
174
  }
177
- if (speaksForTarget)
178
- mergeConfig(result, stripSelectors(local));
179
175
  }
180
176
  /** Every layer has had its turn, so an append still outstanding has nothing left to attach to
181
177
  * and becomes the value itself. Done here rather than per layer: until the chain is finished,
182
178
  * the key it appends to may still be coming. */
183
179
  return finalizeConfig(result);
184
180
  }
185
- /** A config key naming packages rather than settings: `"[*]"`, `"[/]"`, `"[ws:*]"`, `"[pkg-a]"`. The
181
+ /** A config key naming packages rather than settings: `"[*]"`, `"[/]"`, `"[pkg-a]"`. The
186
182
  * brackets are what keep this space from colliding with real config keys - no setting starts with
187
183
  * one - and in YAML they also mean the key always needs quoting (`"[*]":`), since a bare `[*]`
188
184
  * parses as a flow sequence. */
@@ -190,50 +186,55 @@ export function isSelectorKey(key) {
190
186
  return key.length > 2 && key.startsWith('[') && key.endsWith(']');
191
187
  }
192
188
  /**
193
- * **Which packages a selector speaks for.** Three audiences, because a repository has three:
189
+ * **Which packages a selector speaks for.** Two audiences, and the second is a glob:
194
190
  *
195
191
  * | | |
196
192
  * | --- | --- |
197
- * | `"[/]"` | the **root package** only |
198
- * | `"[*]"`, `"[pkg-a]"`, `"[*-dialect]"` | **every** package the glob matches, root included |
199
- * | `"[ws:*]"`, `"[workspace:pkg-*]"` | every **non-root** package the glob matches |
193
+ * | `"[/]"` | the **root package** alone |
194
+ * | `"[*]"`, `"[pkg-a]"`, `"[*-dialect]"` | the packages **below** this directory that the glob matches |
200
195
  *
201
196
  * `/` for the root because that is what a repository root is called everywhere else, and it cannot
202
- * collide with a package name. `ws:` is a qualifier on the glob rather than a separate spelling of
203
- * `*`, so `"[ws:pkg-*]"` means what it looks like.
204
- *
205
- * **`"[*]"` includes the root, and that is a change from how it used to read.** Before, selectors
206
- * were not applied to the root at all, so `"[*]"` silently meant "the workspace packages" - a
207
- * catch-all with an exception nothing in the syntax mentioned. The three names above say which
208
- * audience is meant; `"[ws:*]"` is the old behaviour, now spelled.
197
+ * collide with a package name.
198
+ *
199
+ * **The root is never selected by name, and that one rule removes two traps.** A glob matches
200
+ * package names, and the root is nobody's child - so `"[my-*]"` cannot quietly pick up a repository
201
+ * whose root package happens to be called `my-repo`, and `"[*]"` cannot hand a package-shaped
202
+ * setting to a root that has no build directory to apply it to. The root is addressed structurally
203
+ * or not at all.
204
+ *
205
+ * **`"[ws:*]"` / `"[workspace:*]"` is accepted and means exactly `"[*]"`.** The qualifier existed to
206
+ * say "not the root" back when a bare glob included it; the shape of the set says that now, so it
207
+ * has nothing left to add. Accepted rather than rejected because the two spellings resolve to the
208
+ * same packages - an error would be friction with no reader to protect.
209
209
  */
210
210
  export function parseSelector(key) {
211
211
  const inner = key.slice(1, -1);
212
212
  if (inner === ROOT_SELECTOR_INNER)
213
213
  return { scope: 'root', test: () => true };
214
- for (const prefix of WORKSPACE_PREFIXES) {
215
- if (inner.startsWith(prefix)) {
216
- const re = globToRegExp(inner.slice(prefix.length));
217
- return { scope: 'workspace', test: name => re.test(name) };
218
- }
219
- }
220
- const re = globToRegExp(inner);
221
- return { scope: 'all', test: name => re.test(name) };
214
+ const re = globToRegExp(stripWorkspacePrefix(inner));
215
+ return { scope: 'package', test: name => re.test(name) };
222
216
  }
223
217
  /** The glob inside a selector key, as a `RegExp` anchored at both ends - so `"[*-dialect]"` matches
224
218
  * `mysql-dialect` but not `my-dialect-helper`. Glob rather than regex, to match every other
225
219
  * pattern in rman (`allowBranch`, `changelog.tagPattern`, `clean.include`). */
226
220
  export function selectorToRegExp(key) {
227
- return globToRegExp(key.slice(1, -1));
221
+ return globToRegExp(stripWorkspacePrefix(key.slice(1, -1)));
228
222
  }
229
223
  /**
230
- * Every selector block in `config` that speaks for this package, in increasing precedence.
231
- *
232
- * Order, lowest first: **`"[*]"`, then a catch-all `"[ws:*]"`, then the rest in declaration
233
- * order** - so narrowing the audience wins over the widest one, a named package or `"[/]"` wins
234
- * over both, and two equally specific globs resolve by the order they were written in. A catch-all
235
- * is ranked rather than left to declaration order on purpose: where you happen to write "everything"
236
- * should not decide whether it beats a rule about one package.
224
+ * Every selector block in `config` that speaks for this package, **in the order they were written**
225
+ * - later wins, the way `overrides` works in eslint, prettier and babel, and the way a `.gitignore`
226
+ * rule does.
227
+ *
228
+ * **There used to be a ranking** (`"[*]"` lowest, then a catch-all `"[ws:*]"`, then the rest by
229
+ * declaration), so that "everything" could not beat a rule about one package by being written last.
230
+ * It was dropped because the ordering it implies does not exist: specificity only ranks sets that
231
+ * nest, and globs do not. For a package called `pkg-dialect`, neither `"[pkg-*]"` nor
232
+ * `"[*-dialect]"` contains the other, so any answer is an invented tiebreak - and an invented
233
+ * tiebreak is worse than the order the author typed. What was left was already declaration order
234
+ * with one case lifted out of it; this removes the exception rather than generalizing it.
235
+ *
236
+ * The cost, which the docs state rather than hide: a catch-all written *below* a narrower block now
237
+ * overrides it. Writing catch-alls first is a convention, not a rule - the file reads top to bottom.
237
238
  */
238
239
  function matchingSelectors(config, packageName, isRoot) {
239
240
  const matches = [];
@@ -241,23 +242,19 @@ function matchingSelectors(config, packageName, isRoot) {
241
242
  if (!isSelectorKey(key) || !value || typeof value !== 'object')
242
243
  continue;
243
244
  const { scope, test } = parseSelector(key);
244
- if (scope === 'root' && !isRoot)
245
+ if (scope === 'root' ? !isRoot : isRoot || !test(packageName))
245
246
  continue;
246
- if (scope === 'workspace' && isRoot)
247
- continue;
248
- if (!test(packageName))
249
- continue;
250
- matches.push([selectorRank(key), value]);
247
+ matches.push(value);
251
248
  }
252
- return matches.sort((a, b) => a[0] - b[0]).map(([, block]) => block);
249
+ return matches;
253
250
  }
254
- /** 0 for `"[*]"`, 1 for a catch-all workspace selector, 2 for anything that names something. Equal
255
- * ranks keep their declaration order, since `Array.prototype.sort` is stable. */
256
- function selectorRank(key) {
257
- if (key === CATCH_ALL)
258
- return 0;
259
- const inner = key.slice(1, -1);
260
- return WORKSPACE_PREFIXES.some(prefix => inner === `${prefix}*`) ? 1 : 2;
251
+ /** `"[ws:*]"` and `"[workspace:*]"` are the pre-2.x spelling of "not the root", kept working
252
+ * because they now name the same set a bare glob does. Stripped here so one code path serves both. */
253
+ function stripWorkspacePrefix(inner) {
254
+ for (const prefix of WORKSPACE_PREFIXES)
255
+ if (inner.startsWith(prefix))
256
+ return inner.slice(prefix.length);
257
+ return inner;
261
258
  }
262
259
  function globToRegExp(glob) {
263
260
  const source = glob
@@ -273,12 +270,10 @@ function stripSelectors(config) {
273
270
  result[key] = value;
274
271
  return result;
275
272
  }
276
- const CATCH_ALL = '[*]';
277
273
  /** `"[/]"` - the root package, spelled the way a repository root is spelled everywhere else, and
278
274
  * unable to collide with a package name. */
279
275
  const ROOT_SELECTOR_INNER = '/';
280
- /** Both spellings of "the workspace packages, not the root". The long one reads in a config file
281
- * someone else has to understand; the short one is what gets typed. */
276
+ /** Accepted spellings of the retired "not the root" qualifier - see `stripWorkspacePrefix`. */
282
277
  const WORKSPACE_PREFIXES = ['workspace:', 'ws:'];
283
278
  function dirChain(rootDir, targetDir) {
284
279
  const rel = path.relative(rootDir, targetDir);
@@ -399,7 +394,7 @@ export const DEFERRED_PATHS = ['version.before', 'version.exec', 'version.after'
399
394
  * config:
400
395
  *
401
396
  * ```js
402
- * '[ws:*]': {
397
+ * '[*]': {
403
398
  * clean: { include: ({ vars, value }) => [...value, vars.buildDir] }, // a value: called here
404
399
  * run: { build: { after: ({ pkg }) => copyDocs(pkg) } }, // a step: called by `run`
405
400
  * }
@@ -178,7 +178,7 @@ function assignMerged(target, key, value, source, origin) {
178
178
  *
179
179
  * ```js
180
180
  * '[*]': { clean: { include: ({ vars }) => [vars.buildDir] } }
181
- * '[ws:*]': { clean: { include: "${{ [...value, pkg.basename + '.log'] }}" } }
181
+ * '[*]': { clean: { include: "${{ [...value, pkg.basename + '.log'] }}" } }
182
182
  * ```
183
183
  *
184
184
  * Chained here rather than at resolution time because only the merge knows the order of the
@@ -208,9 +208,10 @@ export class Repository extends Package {
208
208
  * second `rawConfig` copy of every package's config for that one reader; measured identical.
209
209
  */
210
210
  /**
211
- * The root is resolved **with its name**, like every other package, because selectors now speak
212
- * to it: `"[/]"` names it and `"[*]"` includes it. It used to be resolved without one, which is
213
- * what made `"[*]"` quietly mean "the workspace packages" - `"[ws:*]"` is that, spelled.
211
+ * The root is resolved **with its name**, like every other package. Not because a glob could
212
+ * match it - `"[*]"` and every other name pattern speak only to the packages below - but because
213
+ * `resolveConfig` needs a name to run `matchingSelectors` at all, and `"[/]"` is a selector.
214
+ * Passing none would silently drop the root's own block.
214
215
  */
215
216
  const rootRaw = await resolveConfig(this.dirname, this.dirname, cache, this.rootPackage.name);
216
217
  this.config = interpolateConfig(rootRaw, this.configScope(this.rootPackage), { skip: DEFERRED_PATHS });
@@ -166,17 +166,21 @@ export interface RmanConfigKeys {
166
166
  */
167
167
  dependencies?: string[];
168
168
  /**
169
- * Config for **other** packages, keyed by a `"[selector]"` naming them - `"[*]"` for every
170
- * package in the repository, `"[*-dialect]"` for a glob over package names, `"[pkg-a]"` for one.
171
- * Everything else in this object configures the package of the directory declaring it, so this
172
- * is the only way a `.rmanrc` speaks about anything but its own package - most usefully the
173
- * repository root's, which otherwise configures the root package alone.
169
+ * Config for a **narrower audience**, keyed by a `"[selector]"` naming it - `"[/]"` for the root
170
+ * package alone, `"[*]"` for the packages below this directory, `"[*-dialect]"` for a glob over
171
+ * their names, `"[pkg-a]"` for one. Everything else in this object reaches this directory *and*
172
+ * every package under it, so a selector is how a statement stops being everyone's.
173
+ *
174
+ * A glob never matches the root, which is nobody's child - so a package-shaped setting cannot
175
+ * reach a root that has no package directory to apply it to, and `"[/]"` is the only way to
176
+ * address the root.
174
177
  *
175
178
  * ```yaml
176
179
  * # the repository root's own .rmanrc.yml
177
- * run:
178
- * build:
179
- * before: node support/generate.cjs # a repo-wide bookend, run once at the root
180
+ * "[/]":
181
+ * run:
182
+ * build:
183
+ * before: node support/generate.cjs # a repo-wide bookend, run once at the root
180
184
  * "[*]":
181
185
  * run:
182
186
  * build:
@@ -184,8 +188,8 @@ export interface RmanConfigKeys {
184
188
  * ```
185
189
  *
186
190
  * In YAML the quotes are **required**: a bare `[*]` parses as a flow sequence, and `*` as an
187
- * alias indicator. Precedence, lowest first: `"[*]"`, then other selectors in declaration order,
188
- * then the package's own unmarked config.
191
+ * alias indicator. Precedence: the unmarked keys first, then these blocks **in the order they
192
+ * were written** - later wins. A directory level closer to the package wins over all of them.
189
193
  *
190
194
  * Recursive, mirroring the schema's own `"$ref": "#"`: whatever a `.rmanrc` may say about its own
191
195
  * package it may say here about the ones it names - nested selectors included. Typed as
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "rman",
3
3
  "description": "Repository manager",
4
- "version": "1.2.5",
4
+ "version": "1.3.0",
5
5
  "author": "Panates",
6
6
  "license": "MIT",
7
7
  "dependencies": {