rman 2.0.0-beta.3 → 2.0.0-beta.4
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/builtins/platforms/node/services/publish.service.js +8 -2
- package/commands.d.ts +0 -1
- package/commands.js +0 -1
- package/constants.js +1 -1
- package/core/classes/workspace.d.ts +5 -3
- package/core/classes/workspace.js +58 -10
- package/core/config/config-reader.d.ts +24 -1
- package/core/config/config-reader.js +151 -27
- package/package.json +2 -2
- package/commands/lint.command.d.ts +0 -176
- package/commands/lint.command.js +0 -34
|
@@ -72,8 +72,14 @@ function preparePublishManifest(pkg, publishDir, packagesByName) {
|
|
|
72
72
|
* builds a native module on install. The rest (`build`, `test`, `prepare`, ...) never reach the
|
|
73
73
|
* consumer - `prepare` runs for a git dependency, which builds from the repository, not from this
|
|
74
74
|
* tarball.
|
|
75
|
-
* - `private` -
|
|
76
|
-
*
|
|
75
|
+
* - `private` - it is a statement about the **source tree**, not about the artifact derived from it,
|
|
76
|
+
* and a manifest being published is by definition not private. rman's own gate normally stops a
|
|
77
|
+
* private package before this runs, so the line looks unreachable; it is not, because `private`
|
|
78
|
+
* is also how a repository keeps a stray `npm publish` out of the package directory while
|
|
79
|
+
* publishing `build/` on purpose. That is a real configuration - this repository ran it until
|
|
80
|
+
* `rman publish` became its own release path and the gate started refusing the thing it ships.
|
|
81
|
+
* A narrower guard (`prepublishOnly`, which never reaches a consumer either) says the same thing
|
|
82
|
+
* without claiming the artifact is unpublishable.
|
|
77
83
|
* - `publishConfig.directory` - it pointed *here*; kept, it would point one level deeper again.
|
|
78
84
|
*/
|
|
79
85
|
function derivePublishManifest(pkg, packagesByName) {
|
package/commands.d.ts
CHANGED
|
@@ -20,7 +20,6 @@ import './commands/exec.command.js';
|
|
|
20
20
|
import './commands/github-release.command.js';
|
|
21
21
|
import './commands/import.command.js';
|
|
22
22
|
import './commands/info.command.js';
|
|
23
|
-
import './commands/lint.command.js';
|
|
24
23
|
import './commands/list.command.js';
|
|
25
24
|
import './commands/publish.command.js';
|
|
26
25
|
import './commands/run.command.js';
|
package/commands.js
CHANGED
|
@@ -20,7 +20,6 @@ import './commands/exec.command.js';
|
|
|
20
20
|
import './commands/github-release.command.js';
|
|
21
21
|
import './commands/import.command.js';
|
|
22
22
|
import './commands/info.command.js';
|
|
23
|
-
import './commands/lint.command.js';
|
|
24
23
|
import './commands/list.command.js';
|
|
25
24
|
import './commands/publish.command.js';
|
|
26
25
|
import './commands/run.command.js';
|
package/constants.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '2.0.0-beta.
|
|
1
|
+
export const version = '2.0.0-beta.4';
|
|
@@ -95,13 +95,15 @@ export declare class Workspace {
|
|
|
95
95
|
/** Root, then every directory down to `targetDir`, inclusive. A target outside the root is the
|
|
96
96
|
* root alone. */
|
|
97
97
|
protected _dirChain(targetDir: string): string[];
|
|
98
|
-
/**
|
|
98
|
+
/** One config object's own settings - everything that is not a `"[...]"` block. Shallow: a block
|
|
99
|
+
* is dropped whole, and what is inside it is the caller's business. */
|
|
99
100
|
protected _stripSelectors(config: RmanConfig): RmanConfig;
|
|
100
101
|
/**
|
|
101
|
-
* The
|
|
102
|
+
* The layers in `config` that speak for this package, in the order they are merged.
|
|
102
103
|
*
|
|
103
104
|
* `"[/]"` matches the root and nothing else; a glob matches a package's selector and never the
|
|
104
|
-
* root.
|
|
105
|
+
* root. **A block may hold further selector blocks**, which narrow it: a nested one applies only
|
|
106
|
+
* where its own audience and every audience it sits inside all match.
|
|
105
107
|
*/
|
|
106
108
|
protected _matchingSelectors(config: RmanConfig, audience: Workspace.Audience): RmanConfig[];
|
|
107
109
|
/**
|
|
@@ -290,24 +290,68 @@ export class Workspace {
|
|
|
290
290
|
}
|
|
291
291
|
return dirs;
|
|
292
292
|
}
|
|
293
|
-
/**
|
|
293
|
+
/** One config object's own settings - everything that is not a `"[...]"` block. Shallow: a block
|
|
294
|
+
* is dropped whole, and what is inside it is the caller's business. */
|
|
295
|
+
/* **The symbols travel with it, and leaving them behind was a measured loss.** `mergeConfig` reads
|
|
296
|
+
* a key's origin and its `value` chain off the *source* object under `ORIGINS` / `PREVIOUS_VALUES`,
|
|
297
|
+
* so a copy that carries only string keys arrives with neither. Measured on one repository before
|
|
298
|
+
* this line existed: a bad expression in an unmarked key reported `Invalid expression in "group"`
|
|
299
|
+
* with no file, while the identical expression inside a `"[*]"` block reported
|
|
300
|
+
* `... ("group" (.rmanrc.yml))` - because a selector block was merged as itself and only the
|
|
301
|
+
* unmarked layer came through here. Nesting would have spread that to the blocks too. */
|
|
294
302
|
_stripSelectors(config) {
|
|
295
303
|
const result = {};
|
|
296
304
|
for (const [key, value] of Object.entries(config))
|
|
297
305
|
if (!ConfigReader.isSelectorKey(key))
|
|
298
306
|
result[key] = value;
|
|
307
|
+
for (const symbol of Object.getOwnPropertySymbols(config)) {
|
|
308
|
+
Object.defineProperty(result, symbol, Object.getOwnPropertyDescriptor(config, symbol));
|
|
309
|
+
}
|
|
299
310
|
return result;
|
|
300
311
|
}
|
|
301
312
|
/**
|
|
302
|
-
* The
|
|
313
|
+
* The layers in `config` that speak for this package, in the order they are merged.
|
|
303
314
|
*
|
|
304
315
|
* `"[/]"` matches the root and nothing else; a glob matches a package's selector and never the
|
|
305
|
-
* root.
|
|
316
|
+
* root. **A block may hold further selector blocks**, which narrow it: a nested one applies only
|
|
317
|
+
* where its own audience and every audience it sits inside all match.
|
|
306
318
|
*/
|
|
307
319
|
/* **A glob never matching the root removes two traps at once**: `"[my-*]"` cannot quietly pick up
|
|
308
320
|
* a repository whose root package is called `my-repo`, and `"[*]"` cannot hand a package-shaped
|
|
309
321
|
* setting to a root with no build directory to apply it to. A root block needs no selector, which
|
|
310
|
-
* is what makes `/` structural.
|
|
322
|
+
* is what makes `/` structural.
|
|
323
|
+
*
|
|
324
|
+
* **Nesting is depth-first pre-order, which is the level's own rule applied again rather than a
|
|
325
|
+
* new one**: a block's own keys are its floor, then the blocks written inside it in declaration
|
|
326
|
+
* order, then the next block beside it. So the layer order for
|
|
327
|
+
* `"[platform:node]" { a, "[pkg-*]" { b } }, "[*]" { c }` is `a, b, c` - and the last word is
|
|
328
|
+
* still whatever was written last, *not* whatever is most specific. That is the same cost the
|
|
329
|
+
* dropped specificity ranking already documents (a catch-all written below a narrower block
|
|
330
|
+
* overrides it), and nesting neither adds to it nor asks the tiebreak question again: two blocks
|
|
331
|
+
* that both match are siblings whatever depth they sit at, and declaration order answers them.
|
|
332
|
+
*
|
|
333
|
+
* **A matching block is contributed *stripped*, not whole.** Merging it as it stands put its
|
|
334
|
+
* nested keys into the resolved config as literal `'[pkg-*]'` entries, which is precisely how the
|
|
335
|
+
* old no-op showed up in `rman config` looking as though it had worked.
|
|
336
|
+
*
|
|
337
|
+
* **A platform block speaks for the root too, and only a glob is held off it.** `"[platform:node]"`
|
|
338
|
+
* asks `pkg.platform.name` and the root is a package with a platform, so
|
|
339
|
+
* `"[platform:node]" > "[/]"` is *the root, when the root is a node package* and
|
|
340
|
+
* `"[platform:node]" > "[*]"` is *its packages* - which is the whole of a technology's shared
|
|
341
|
+
* config in one block, `vars` included.
|
|
342
|
+
*
|
|
343
|
+
* A narrower rule was tried first and measured wrong: let a chain reach the root only where it
|
|
344
|
+
* *names* `"[/]"`, keeping a plain `"[platform:node]"` off the root as before. It answers the pair
|
|
345
|
+
* correctly and still breaks the case it was written for - the nested `"[/]"` could not read the
|
|
346
|
+
* `vars` its parent declared (`vars is not defined`), because those are the parent's own keys.
|
|
347
|
+
* Narrowing an audience and hiding the enclosing block's settings from it are different things,
|
|
348
|
+
* and only the first is what nesting means.
|
|
349
|
+
*
|
|
350
|
+
* The cost, stated rather than hidden: a package-shaped setting written **directly** under a
|
|
351
|
+
* platform block now reaches the root as well - the trap that keeps globs off it. It is not the
|
|
352
|
+
* same trap, because neither half of that one applies here: `platform:node` is not a name and
|
|
353
|
+
* cannot match by accident, and it is not a catch-all. And with nesting the spelling for
|
|
354
|
+
* package-shaped settings is `"[platform:node]" > "[*]"`, which says so. */
|
|
311
355
|
_matchingSelectors(config, audience) {
|
|
312
356
|
const matches = [];
|
|
313
357
|
for (const [key, value] of Object.entries(config)) {
|
|
@@ -316,7 +360,9 @@ export class Workspace {
|
|
|
316
360
|
const { scope, test } = ConfigReader.parseSelector(key);
|
|
317
361
|
if (!this._speaksFor(scope, test, audience))
|
|
318
362
|
continue;
|
|
319
|
-
|
|
363
|
+
const block = value;
|
|
364
|
+
matches.push(this._stripSelectors(block));
|
|
365
|
+
matches.push(...this._matchingSelectors(block, audience));
|
|
320
366
|
}
|
|
321
367
|
return matches;
|
|
322
368
|
}
|
|
@@ -328,8 +374,12 @@ export class Workspace {
|
|
|
328
374
|
*/
|
|
329
375
|
/* **A glob never matching the root removes two traps at once**: `"[my-*]"` cannot quietly pick up
|
|
330
376
|
* a repository whose root package is called `my-repo`, and `"[*]"` cannot hand a package-shaped
|
|
331
|
-
* setting to a root with no build directory to apply it to.
|
|
332
|
-
*
|
|
377
|
+
* setting to a root with no build directory to apply it to. Neither trap exists for a platform
|
|
378
|
+
* block - `platform:node` is not a name and cannot match by accident, and it is not a catch-all -
|
|
379
|
+
* so **a platform link answers about the root like any other package**, and what keeps a plain
|
|
380
|
+
* `"[platform:node]"` off the root is `_matchingSelectors`' chain rule rather than a refusal here.
|
|
381
|
+
* Asking `isRoot` first was what made `"[platform:node]" > "[/]"` unanswerable: the pair says *the
|
|
382
|
+
* root, when it is a node package*, and the short-circuit never let the platform question be put.
|
|
333
383
|
*
|
|
334
384
|
* A package no technology claimed carries `basePlatform`, whose name is `''`, so it matches no
|
|
335
385
|
* platform block at all - `parseSelector`'s test refuses an empty name rather than letting an
|
|
@@ -337,11 +387,9 @@ export class Workspace {
|
|
|
337
387
|
_speaksFor(scope, test, audience) {
|
|
338
388
|
if (scope === 'root')
|
|
339
389
|
return audience.isRoot;
|
|
340
|
-
if (audience.isRoot)
|
|
341
|
-
return false;
|
|
342
390
|
if (scope === 'platform')
|
|
343
391
|
return test(audience.platform);
|
|
344
|
-
return audience.selector !== undefined && test(audience.selector);
|
|
392
|
+
return !audience.isRoot && audience.selector !== undefined && test(audience.selector);
|
|
345
393
|
}
|
|
346
394
|
/**
|
|
347
395
|
* The platform that claims a directory - the one its config names, or the first loaded platform
|
|
@@ -13,7 +13,8 @@ export declare class ConfigReader {
|
|
|
13
13
|
*/
|
|
14
14
|
static isSelectorKey(key: string): boolean;
|
|
15
15
|
/**
|
|
16
|
-
* **Which packages a selector key speaks for.**
|
|
16
|
+
* **Which packages a selector key speaks for.** Three audiences, and what each is matched
|
|
17
|
+
* against:
|
|
17
18
|
*
|
|
18
19
|
* | | matches | `test` is given |
|
|
19
20
|
* | --- | --- | --- |
|
|
@@ -58,7 +59,27 @@ export declare class ConfigReader {
|
|
|
58
59
|
* default export when there is one, the module's own exports object otherwise.
|
|
59
60
|
*/
|
|
60
61
|
protected _loadConfigModule(file: string): Promise<any>;
|
|
62
|
+
/**
|
|
63
|
+
* Checks every `"[...]"` key in a config: where it sits, what it carries, and - for a nested one -
|
|
64
|
+
* whether the pair could ever match a package.
|
|
65
|
+
*/
|
|
61
66
|
protected _assertSelectorBlocks(config: RmanConfig, file: string): void;
|
|
67
|
+
/**
|
|
68
|
+
* One walk, refusing a selector key under a *setting* and checking each legal one's contents.
|
|
69
|
+
*
|
|
70
|
+
* @param at the key path to `node`.
|
|
71
|
+
* @param enclosing the selector keys `node` sits inside, outermost first.
|
|
72
|
+
*/
|
|
73
|
+
protected _assertSelectorKeys(node: unknown, file: string, at: string[], enclosing: string[]): void;
|
|
74
|
+
/** What one selector block may carry, and - given `outer` - whether it can match anything. */
|
|
75
|
+
protected _assertSelectorBlock(key: string, value: unknown, file: string, outer: string | undefined): void;
|
|
76
|
+
/**
|
|
77
|
+
* Refuses a nested pair that could never match a package together.
|
|
78
|
+
*
|
|
79
|
+
* @param outer the enclosing selector key.
|
|
80
|
+
* @param inner the key written inside it.
|
|
81
|
+
*/
|
|
82
|
+
protected _assertNestable(outer: string, inner: string, file: string): void;
|
|
62
83
|
protected _resolveExtends(context: ConfigReader.Context, config: RmanConfig, from: string, seen?: string[]): Promise<RmanConfig>;
|
|
63
84
|
/**
|
|
64
85
|
* Lays `options.presets` underneath this level's own config, exactly as an `extends` naming them
|
|
@@ -152,6 +173,8 @@ export declare namespace ConfigReader {
|
|
|
152
173
|
/** Whether a package's selector is one this key speaks for. Always `true` for a root key, which
|
|
153
174
|
* is matched by *being* the root rather than by name. */
|
|
154
175
|
test(name: string): boolean;
|
|
176
|
+
/** The technologies a `"[platform:...]"` key names, and nothing for the other two scopes. */
|
|
177
|
+
names?: readonly string[];
|
|
155
178
|
}
|
|
156
179
|
interface Options {
|
|
157
180
|
/** Plugins to start with - the application's. Optional like `platforms`, which it was not: a
|
|
@@ -6,6 +6,7 @@ import fastGlob from 'fast-glob';
|
|
|
6
6
|
import * as yaml from 'js-yaml';
|
|
7
7
|
import { presetNames, PRESETS_DIR } from '../../builtins/presets/index.js';
|
|
8
8
|
import { declaredKind, isDeclared, isPlatform } from '../interfaces/plugin.js';
|
|
9
|
+
import { CODE_SUBTREES } from './config-paths.js';
|
|
9
10
|
import { mergeConfig } from './merge-config.js';
|
|
10
11
|
const requireConfigModule = createRequire(import.meta.url);
|
|
11
12
|
const IDENTITY_KEYS = ['platform', 'name'];
|
|
@@ -21,6 +22,8 @@ const PRESET_EXTENSIONS = ['.js', '.ts'];
|
|
|
21
22
|
/** What an error about a default preset names itself as - there is no config key to quote, since
|
|
22
23
|
* these are laid down by the caller rather than written by anybody. */
|
|
23
24
|
const PRESETS_LABEL = 'presets';
|
|
25
|
+
/** Free-form by contract - see `_assertSelectorKeys`. */
|
|
26
|
+
const VARS_KEY = 'vars';
|
|
24
27
|
/** Keys that only an rman **config** has, for telling one from a contribution once it has already
|
|
25
28
|
* been refused - see `_assertLoaded`. `name` is not among them: a plugin has one too. */
|
|
26
29
|
const CONFIG_SHAPED = ['extends', PLUGINS_KEY, PLATFORMS_KEY, 'commands', 'publishTargets'];
|
|
@@ -46,7 +49,8 @@ export class ConfigReader {
|
|
|
46
49
|
return key.length > 2 && key.startsWith('[') && key.endsWith(']');
|
|
47
50
|
}
|
|
48
51
|
/**
|
|
49
|
-
* **Which packages a selector key speaks for.**
|
|
52
|
+
* **Which packages a selector key speaks for.** Three audiences, and what each is matched
|
|
53
|
+
* against:
|
|
50
54
|
*
|
|
51
55
|
* | | matches | `test` is given |
|
|
52
56
|
* | --- | --- | --- |
|
|
@@ -82,7 +86,7 @@ export class ConfigReader {
|
|
|
82
86
|
.split(',')
|
|
83
87
|
.map(n => n.trim())
|
|
84
88
|
.filter(Boolean);
|
|
85
|
-
return { scope: 'platform', test: (name) => !!name && names.includes(name) };
|
|
89
|
+
return { scope: 'platform', test: (name) => !!name && names.includes(name), names };
|
|
86
90
|
}
|
|
87
91
|
const re = globToRegExp(stripWorkspacePrefix(inner));
|
|
88
92
|
return { scope: 'package', test: (name) => re.test(name) };
|
|
@@ -292,38 +296,158 @@ export class ConfigReader {
|
|
|
292
296
|
}
|
|
293
297
|
return mod?.default ?? mod;
|
|
294
298
|
}
|
|
299
|
+
/**
|
|
300
|
+
* Checks every `"[...]"` key in a config: where it sits, what it carries, and - for a nested one -
|
|
301
|
+
* whether the pair could ever match a package.
|
|
302
|
+
*/
|
|
295
303
|
_assertSelectorBlocks(config, file) {
|
|
296
|
-
|
|
297
|
-
|
|
304
|
+
this._assertSelectorKeys(config, file, [], []);
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* One walk, refusing a selector key under a *setting* and checking each legal one's contents.
|
|
308
|
+
*
|
|
309
|
+
* @param at the key path to `node`.
|
|
310
|
+
* @param enclosing the selector keys `node` sits inside, outermost first.
|
|
311
|
+
*/
|
|
312
|
+
/* **A selector key is legal at the top level of a config and directly inside another selector
|
|
313
|
+
* key, and nowhere else** - which is the whole of the placement rule, and `at.length >
|
|
314
|
+
* enclosing.length` is the whole of the test: the two arrays stay equal for as long as every
|
|
315
|
+
* ancestor is a selector, and `at` runs ahead the moment one is not.
|
|
316
|
+
*
|
|
317
|
+
* ```yaml
|
|
318
|
+
* "[platform:node]":
|
|
319
|
+
* "[pkg-*]": { group: x } # an intersection
|
|
320
|
+
* run:
|
|
321
|
+
* "[pkg-*]": { ... } # refused - a setting is not an audience
|
|
322
|
+
* ```
|
|
323
|
+
*
|
|
324
|
+
* **The nested form used to be refused outright and that was the wrong call**, on a reason that
|
|
325
|
+
* does not survive being written out: "selectors do not intersect, because specificity ranking was
|
|
326
|
+
* dropped". Ranking answers which of two *siblings* wins, and nesting asks nothing of the sort -
|
|
327
|
+
* a nested block is resolved depth-first in declaration order, which is the rule already in force
|
|
328
|
+
* one level up (`Workspace._matchingSelectors`). What the refusal cost was the only way to say
|
|
329
|
+
* "these packages, but only the ones that are also X" for a whole block: `if:` is per key and
|
|
330
|
+
* reaches neither `vars` nor any key that is not a run step.
|
|
331
|
+
*
|
|
332
|
+
* What the refusal *did* get right was that silence is unacceptable - before it, the inner block
|
|
333
|
+
* reached every package the outer one did, as a literal `'[pkg-*]'` key sitting in the resolved
|
|
334
|
+
* config where `rman config` showed it looking as though it had worked. Both halves are answered
|
|
335
|
+
* now: the shape works, and the shapes that could never work are refused here rather than
|
|
336
|
+
* quietly matching nothing.
|
|
337
|
+
*
|
|
338
|
+
* **`vars` and the contribution keys are skipped**, and both for the same reason: their contents
|
|
339
|
+
* are not config keys. `vars` is free-form by contract and `CODE_SUBTREES` hold plugins, commands
|
|
340
|
+
* and publish targets - arbitrary objects whose key space rman does not own. A bracketed name in
|
|
341
|
+
* either is data. */
|
|
342
|
+
_assertSelectorKeys(node, file, at, enclosing) {
|
|
343
|
+
if (!node || typeof node !== 'object' || Array.isArray(node))
|
|
344
|
+
return;
|
|
345
|
+
for (const [key, value] of Object.entries(node)) {
|
|
346
|
+
if (ConfigReader.isSelectorKey(key)) {
|
|
347
|
+
if (at.length > enclosing.length) {
|
|
348
|
+
throw new Error(`"${key}" in "${file}" is nested under "${at.join('.')}", which is a setting rather ` +
|
|
349
|
+
`than an audience - a selector is read at the top level of a config and inside ` +
|
|
350
|
+
`another selector, so this one would never be applied. Move it out, or use an ` +
|
|
351
|
+
`expression where the value itself depends on the package.`);
|
|
352
|
+
}
|
|
353
|
+
this._assertSelectorBlock(key, value, file, enclosing.at(-1));
|
|
354
|
+
this._assertSelectorKeys(value, file, [...at, key], [...enclosing, key]);
|
|
298
355
|
continue;
|
|
299
|
-
if (EXTENDS_KEY in value) {
|
|
300
|
-
throw new Error(`"${key}" in "${file}" cannot use "extends" - it belongs at the top level, where it is a ` +
|
|
301
|
-
`statement about this config rather than about the packages the selector names.`);
|
|
302
356
|
}
|
|
303
|
-
|
|
304
|
-
* **The two keys a selector cannot carry, because the selector is downstream of them.**
|
|
305
|
-
*
|
|
306
|
-
* A `"[glob]"` matches `Package.selector`, which comes from `name` - and which package a
|
|
307
|
-
* directory even holds comes from `platform`. Both are read from the *unmarked* cascade, while
|
|
308
|
-
* the packages are still being found, so a glob block setting either would need its own answer
|
|
309
|
-
* in order to be matched at all. Typed as a whole `RmanConfig`, both look valid there and both
|
|
310
|
-
* would simply never be read.
|
|
311
|
-
*
|
|
312
|
-
* `"[/]"` is exempt and keeps working: the root is addressed structurally - its directory *is*
|
|
313
|
-
* the repository root - so a root block needs no selector and is applied during the walk.
|
|
314
|
-
*/
|
|
315
|
-
if (ConfigReader.parseSelector(key).scope === 'root')
|
|
357
|
+
if (key === VARS_KEY || CODE_SUBTREES.includes(key))
|
|
316
358
|
continue;
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
359
|
+
this._assertSelectorKeys(value, file, [...at, key], enclosing);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
/** What one selector block may carry, and - given `outer` - whether it can match anything. */
|
|
363
|
+
_assertSelectorBlock(key, value, file, outer) {
|
|
364
|
+
if (!value || typeof value !== 'object')
|
|
365
|
+
return;
|
|
366
|
+
if (outer !== undefined)
|
|
367
|
+
this._assertNestable(outer, key, file);
|
|
368
|
+
if (EXTENDS_KEY in value) {
|
|
369
|
+
throw new Error(`"${key}" in "${file}" cannot use "extends" - it belongs at the top level, where it is a ` +
|
|
370
|
+
`statement about this config rather than about the packages the selector names.`);
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* **The two keys a selector cannot carry, because the selector is downstream of them.**
|
|
374
|
+
*
|
|
375
|
+
* A `"[glob]"` matches `Package.selector`, which comes from `name` - and which package a
|
|
376
|
+
* directory even holds comes from `platform`. Both are read from the *unmarked* cascade, while
|
|
377
|
+
* the packages are still being found, so a glob block setting either would need its own answer
|
|
378
|
+
* in order to be matched at all. Typed as a whole `RmanConfig`, both look valid there and both
|
|
379
|
+
* would simply never be read.
|
|
380
|
+
*
|
|
381
|
+
* `"[/]"` is exempt and keeps working: the root is addressed structurally - its directory *is*
|
|
382
|
+
* the repository root - so a root block needs no selector and is applied during the walk.
|
|
383
|
+
*/
|
|
384
|
+
if (ConfigReader.parseSelector(key).scope === 'root') {
|
|
385
|
+
/** **Except for `platform` in a *nested* `"[/]"`, which is read nowhere.**
|
|
386
|
+
* `Workspace._declaredPlatformName` looks at a level's own `platform` and at a **top-level**
|
|
387
|
+
* `"[/]"`, because which technology claims a directory has to be settled before any block is
|
|
388
|
+
* matched. Inside `"[platform:node]"` it is also circular - the answer decides whether the
|
|
389
|
+
* block that declares it applies at all. */
|
|
390
|
+
if (outer !== undefined && 'platform' in value) {
|
|
391
|
+
throw new Error(`"${key}" in "${file}" is nested inside "${outer}" and cannot set "platform" - which ` +
|
|
392
|
+
`technology claims a directory is settled before any selector is matched, so it is ` +
|
|
393
|
+
`read from the level's own keys or a top-level "[/]" and nowhere else. Write it there.`);
|
|
394
|
+
}
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
for (const identity of IDENTITY_KEYS) {
|
|
398
|
+
if (identity in value) {
|
|
399
|
+
throw new Error(`"${key}" in "${file}" cannot set "${identity}" - a glob matches a package's selector, ` +
|
|
400
|
+
`and "${identity}" is what the selector is derived from, so the block could never be ` +
|
|
401
|
+
`matched in order to apply it. Write it unmarked, in the package's own ".rmanrc", or ` +
|
|
402
|
+
`under "[/]" for the root package.`);
|
|
324
403
|
}
|
|
325
404
|
}
|
|
326
405
|
}
|
|
406
|
+
/**
|
|
407
|
+
* Refuses a nested pair that could never match a package together.
|
|
408
|
+
*
|
|
409
|
+
* @param outer the enclosing selector key.
|
|
410
|
+
* @param inner the key written inside it.
|
|
411
|
+
*/
|
|
412
|
+
/* **Nesting is an AND, so a pair naming disjoint sets is a block that runs for nobody** - which is
|
|
413
|
+
* the silence worth refusing. Two pairs are decidable and both are:
|
|
414
|
+
*
|
|
415
|
+
* - **`"[/]"` paired with a glob**, either way round. A glob matches a package's name and the root
|
|
416
|
+
* is nobody's child, so it never matches the root whatever it is written beside; `"[/]"`
|
|
417
|
+
* matches nothing else. The pair is empty in both directions.
|
|
418
|
+
* - **Two platform blocks naming nothing in common.** A package carries one `platform.name`, so
|
|
419
|
+
* `"[platform:node]" > "[platform:cargo]"` is empty; `"[platform:node,cargo]" > "[platform:node]"`
|
|
420
|
+
* narrows and is fine.
|
|
421
|
+
*
|
|
422
|
+
* **`"[/]"` inside a platform block is the pair this deliberately allows**, and getting that wrong
|
|
423
|
+
* once is why the reasoning is written out. It reads as *the root, when the root is a node
|
|
424
|
+
* package*, which is a perfectly well-defined audience - it was unreachable only because
|
|
425
|
+
* `_speaksFor` asked `isRoot` before it asked the platform question. Refusing it took the natural
|
|
426
|
+
* shape of a technology's shared config away: a preset wanting `vars` over a whole node
|
|
427
|
+
* repository, the root's own settings, and the packages' settings had to hoist `vars` to the top
|
|
428
|
+
* level, where they also reach packages of every other technology.
|
|
429
|
+
*
|
|
430
|
+
* **A glob pair is deliberately not checked**, and the asymmetry is the point rather than an
|
|
431
|
+
* omission: whether two globs intersect is a real computation with a wrong answer available in
|
|
432
|
+
* both directions, while a platform set is `includes`. `"[pkg-*]" > "[lib-*]"` therefore loads and
|
|
433
|
+
* matches nothing - the same thing a top-level `"[lib-*]"` does in a repository with no such
|
|
434
|
+
* package, which nothing reports either. */
|
|
435
|
+
_assertNestable(outer, inner, file) {
|
|
436
|
+
const a = ConfigReader.parseSelector(outer);
|
|
437
|
+
const b = ConfigReader.parseSelector(inner);
|
|
438
|
+
const [root, glob] = a.scope === 'root' ? [outer, b.scope === 'package' && inner] : [inner, a.scope === 'package' && outer];
|
|
439
|
+
if ((a.scope === 'root' || b.scope === 'root') && glob) {
|
|
440
|
+
throw new Error(`"${inner}" in "${file}" is nested inside "${outer}", and "${glob}" can never match the ` +
|
|
441
|
+
`same package "${root}" does - a glob matches a package's name and the root is nobody's ` +
|
|
442
|
+
`child, so the pair is empty. A platform block nests with "[/]" ("the root, when it is ` +
|
|
443
|
+
`of that technology"); a glob does not.`);
|
|
444
|
+
}
|
|
445
|
+
if (a.scope === 'platform' && b.scope === 'platform' && !b.names?.some(name => a.test(name))) {
|
|
446
|
+
throw new Error(`"${inner}" in "${file}" is nested inside "${outer}", which names no technology in common ` +
|
|
447
|
+
`with it - a package carries one platform, so this block could never be applied. Nesting ` +
|
|
448
|
+
`narrows: name a technology the outer block already speaks for.`);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
327
451
|
async _resolveExtends(context, config, from, seen = []) {
|
|
328
452
|
const declared = config[EXTENDS_KEY];
|
|
329
453
|
if (declared === undefined)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rman",
|
|
3
3
|
"description": "Repository manager",
|
|
4
|
-
"version": "2.0.0-beta.
|
|
4
|
+
"version": "2.0.0-beta.4",
|
|
5
5
|
"author": "Panates",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"dependencies": {
|
|
@@ -71,4 +71,4 @@
|
|
|
71
71
|
"publishConfig": {
|
|
72
72
|
"access": "public"
|
|
73
73
|
}
|
|
74
|
-
}
|
|
74
|
+
}
|
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
import { type RmanConfig } from '../interfaces/rman-config.interface.js';
|
|
2
|
-
declare const COMMAND: "lint";
|
|
3
|
-
declare const config: {
|
|
4
|
-
parallel: {
|
|
5
|
-
target: "cli";
|
|
6
|
-
describe: string;
|
|
7
|
-
coerce: (v: unknown) => boolean | number | undefined;
|
|
8
|
-
};
|
|
9
|
-
bail: {
|
|
10
|
-
target: "cli";
|
|
11
|
-
describe: string;
|
|
12
|
-
type: "boolean";
|
|
13
|
-
};
|
|
14
|
-
topo: {
|
|
15
|
-
target: "cli";
|
|
16
|
-
describe: string;
|
|
17
|
-
type: "boolean";
|
|
18
|
-
};
|
|
19
|
-
progress: {
|
|
20
|
-
target: "cli";
|
|
21
|
-
describe: string;
|
|
22
|
-
type: "boolean";
|
|
23
|
-
};
|
|
24
|
-
changed: {
|
|
25
|
-
target: "cli";
|
|
26
|
-
alias: string;
|
|
27
|
-
describe: string;
|
|
28
|
-
type: "boolean";
|
|
29
|
-
};
|
|
30
|
-
changedSince: {
|
|
31
|
-
target: "cli";
|
|
32
|
-
cliName: string;
|
|
33
|
-
describe: string;
|
|
34
|
-
type: "string";
|
|
35
|
-
conflicts: string;
|
|
36
|
-
};
|
|
37
|
-
fromRoot: {
|
|
38
|
-
target: "cli";
|
|
39
|
-
cliName: string;
|
|
40
|
-
alias: string;
|
|
41
|
-
describe: string;
|
|
42
|
-
type: "boolean";
|
|
43
|
-
};
|
|
44
|
-
allowBranch: {
|
|
45
|
-
target: "cli";
|
|
46
|
-
cliName: string;
|
|
47
|
-
describe: string;
|
|
48
|
-
type: "string";
|
|
49
|
-
};
|
|
50
|
-
ignoreBranch: {
|
|
51
|
-
target: "cli";
|
|
52
|
-
cliName: string;
|
|
53
|
-
describe: string;
|
|
54
|
-
type: "string";
|
|
55
|
-
};
|
|
56
|
-
scope: {
|
|
57
|
-
target: "cli";
|
|
58
|
-
describe: string;
|
|
59
|
-
type: "string";
|
|
60
|
-
};
|
|
61
|
-
ignore: {
|
|
62
|
-
target: "cli";
|
|
63
|
-
describe: string;
|
|
64
|
-
type: "string";
|
|
65
|
-
};
|
|
66
|
-
platform: {
|
|
67
|
-
target: "cli";
|
|
68
|
-
describe: string;
|
|
69
|
-
type: "string";
|
|
70
|
-
};
|
|
71
|
-
deps: {
|
|
72
|
-
target: "cli";
|
|
73
|
-
describe: string;
|
|
74
|
-
type: "boolean";
|
|
75
|
-
};
|
|
76
|
-
dependents: {
|
|
77
|
-
target: "cli";
|
|
78
|
-
describe: string;
|
|
79
|
-
type: "boolean";
|
|
80
|
-
};
|
|
81
|
-
};
|
|
82
|
-
type Args = RmanConfig.ArgsOf<typeof config, typeof COMMAND>;
|
|
83
|
-
/**
|
|
84
|
-
* `rman lint` - an alias for `rman run lint`, the third of the trio beside `build` and `test`.
|
|
85
|
-
*/
|
|
86
|
-
declare const lintCommand: (app: import("../index.js").RmanApplication) => {
|
|
87
|
-
command: "lint";
|
|
88
|
-
describe: string;
|
|
89
|
-
/** Read, not owned - see the note above. */
|
|
90
|
-
configKeys: string[];
|
|
91
|
-
config: {
|
|
92
|
-
parallel: {
|
|
93
|
-
target: "cli";
|
|
94
|
-
describe: string;
|
|
95
|
-
coerce: (v: unknown) => boolean | number | undefined;
|
|
96
|
-
};
|
|
97
|
-
bail: {
|
|
98
|
-
target: "cli";
|
|
99
|
-
describe: string;
|
|
100
|
-
type: "boolean";
|
|
101
|
-
};
|
|
102
|
-
topo: {
|
|
103
|
-
target: "cli";
|
|
104
|
-
describe: string;
|
|
105
|
-
type: "boolean";
|
|
106
|
-
};
|
|
107
|
-
progress: {
|
|
108
|
-
target: "cli";
|
|
109
|
-
describe: string;
|
|
110
|
-
type: "boolean";
|
|
111
|
-
};
|
|
112
|
-
changed: {
|
|
113
|
-
target: "cli";
|
|
114
|
-
alias: string;
|
|
115
|
-
describe: string;
|
|
116
|
-
type: "boolean";
|
|
117
|
-
};
|
|
118
|
-
changedSince: {
|
|
119
|
-
target: "cli";
|
|
120
|
-
cliName: string;
|
|
121
|
-
describe: string;
|
|
122
|
-
type: "string";
|
|
123
|
-
conflicts: string;
|
|
124
|
-
};
|
|
125
|
-
fromRoot: {
|
|
126
|
-
target: "cli";
|
|
127
|
-
cliName: string;
|
|
128
|
-
alias: string;
|
|
129
|
-
describe: string;
|
|
130
|
-
type: "boolean";
|
|
131
|
-
};
|
|
132
|
-
allowBranch: {
|
|
133
|
-
target: "cli";
|
|
134
|
-
cliName: string;
|
|
135
|
-
describe: string;
|
|
136
|
-
type: "string";
|
|
137
|
-
};
|
|
138
|
-
ignoreBranch: {
|
|
139
|
-
target: "cli";
|
|
140
|
-
cliName: string;
|
|
141
|
-
describe: string;
|
|
142
|
-
type: "string";
|
|
143
|
-
};
|
|
144
|
-
scope: {
|
|
145
|
-
target: "cli";
|
|
146
|
-
describe: string;
|
|
147
|
-
type: "string";
|
|
148
|
-
};
|
|
149
|
-
ignore: {
|
|
150
|
-
target: "cli";
|
|
151
|
-
describe: string;
|
|
152
|
-
type: "string";
|
|
153
|
-
};
|
|
154
|
-
platform: {
|
|
155
|
-
target: "cli";
|
|
156
|
-
describe: string;
|
|
157
|
-
type: "string";
|
|
158
|
-
};
|
|
159
|
-
deps: {
|
|
160
|
-
target: "cli";
|
|
161
|
-
describe: string;
|
|
162
|
-
type: "boolean";
|
|
163
|
-
};
|
|
164
|
-
dependents: {
|
|
165
|
-
target: "cli";
|
|
166
|
-
describe: string;
|
|
167
|
-
type: "boolean";
|
|
168
|
-
};
|
|
169
|
-
};
|
|
170
|
-
examples: {
|
|
171
|
-
command: string;
|
|
172
|
-
description: string;
|
|
173
|
-
}[];
|
|
174
|
-
handler: (args: Args) => Promise<void>;
|
|
175
|
-
};
|
|
176
|
-
export default lintCommand;
|
package/commands/lint.command.js
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { registerCommand } from '../interfaces/rman-config.interface.js';
|
|
2
|
-
import { assertAllowedBranch, readBranchGuardOptions } from '../utils/branch-guard.js';
|
|
3
|
-
import { readRunOptions, runOptions } from '../utils/run-options.js';
|
|
4
|
-
const COMMAND = 'lint';
|
|
5
|
-
const config = runOptions;
|
|
6
|
-
/**
|
|
7
|
-
* `rman lint` - an alias for `rman run lint`, the third of the trio beside `build` and `test`.
|
|
8
|
-
*/
|
|
9
|
-
/* **Core, not the `node` preset, and the test is what the command *knows*.** It knows a script
|
|
10
|
-
* name and nothing else: a Cargo repository declaring `lint: 'cargo clippy'` and a Go one
|
|
11
|
-
* declaring `golangci-lint run` are served by exactly this file. That is the line `clean` is on the
|
|
12
|
-
* other side of - everything `clean` knows how to delete is a TypeScript fact - and it is why
|
|
13
|
-
* `build` and `test` are here too.
|
|
14
|
-
*
|
|
15
|
-
* **It owns no config key**, for the reason `build` documents: `lint` is `run lint` under another
|
|
16
|
-
* name, so its settings are `run.lint`, which belongs to `run`. Two commands cannot contribute
|
|
17
|
-
* under one top-level key - interface merging is not a deep merge - and these three never needed
|
|
18
|
-
* to. */
|
|
19
|
-
const lintCommand = registerCommand(app => {
|
|
20
|
-
const repository = app.repository;
|
|
21
|
-
return {
|
|
22
|
-
command: COMMAND,
|
|
23
|
-
describe: 'Alias for "run lint"',
|
|
24
|
-
/** Read, not owned - see the note above. */
|
|
25
|
-
configKeys: ['run.lint'],
|
|
26
|
-
config,
|
|
27
|
-
examples: [{ command: '$0 lint', description: '# Lints packages' }],
|
|
28
|
-
handler: async (args) => {
|
|
29
|
-
await assertAllowedBranch(repository, readBranchGuardOptions(args));
|
|
30
|
-
await app.getService('run').runScript('lint', { ...readRunOptions(args), commandName: 'lint' });
|
|
31
|
-
},
|
|
32
|
-
};
|
|
33
|
-
});
|
|
34
|
-
export default lintCommand;
|