rman 1.2.5 → 2.0.0-beta.2
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 +63 -19
- package/cli.d.ts +5 -0
- package/cli.js +214 -88
- package/commands/build.command.d.ts +177 -3
- package/commands/build.command.js +20 -10
- package/commands/changed.command.d.ts +80 -3
- package/commands/changed.command.js +19 -12
- package/commands/changelog.command.d.ts +192 -3
- package/commands/changelog.command.js +87 -43
- package/commands/config.command.d.ts +44 -3
- package/commands/config.command.js +30 -19
- package/commands/diff.command.d.ts +37 -3
- package/commands/diff.command.js +25 -16
- package/commands/exec.command.d.ts +193 -3
- package/commands/exec.command.js +60 -58
- package/commands/github-release.command.d.ts +154 -3
- package/commands/github-release.command.js +67 -37
- package/commands/import.command.d.ts +36 -3
- package/commands/import.command.js +28 -20
- package/commands/info.command.d.ts +35 -7
- package/commands/info.command.js +36 -30
- package/commands/list.command.d.ts +163 -3
- package/commands/list.command.js +109 -71
- package/commands/publish.command.d.ts +231 -0
- package/commands/publish.command.js +304 -0
- package/commands/run.command.d.ts +186 -6
- package/commands/run.command.js +26 -72
- package/commands/test.command.d.ts +173 -3
- package/commands/test.command.js +16 -10
- package/commands/version.command.d.ts +317 -3
- package/commands/version.command.js +149 -69
- package/commands.d.ts +32 -0
- package/commands.js +28 -0
- package/constants.js +1 -1
- package/core/application.d.ts +116 -0
- package/core/application.js +143 -0
- package/core/command-builder.d.ts +14 -0
- package/core/command-builder.js +78 -0
- package/core/config.d.ts +180 -50
- package/core/config.js +332 -153
- package/core/core-services.d.ts +14 -0
- package/core/core-services.js +30 -0
- package/core/core-targets.d.ts +14 -0
- package/core/core-targets.js +16 -0
- package/core/custom-command.d.ts +42 -6
- package/core/custom-command.js +44 -17
- package/core/extends-config.d.ts +13 -5
- package/core/extends-config.js +52 -12
- package/core/load-config-module.d.ts +28 -0
- package/core/load-config-module.js +42 -0
- package/core/manifest.d.ts +47 -25
- package/core/manifest.js +51 -69
- package/core/merge-config.d.ts +33 -34
- package/core/merge-config.js +138 -93
- package/core/package.d.ts +145 -17
- package/core/package.js +128 -36
- package/core/plugin-loader.d.ts +65 -0
- package/core/plugin-loader.js +234 -0
- package/core/plugin.d.ts +135 -90
- package/core/plugin.js +70 -173
- package/core/publish-target.d.ts +124 -0
- package/core/publish-target.js +30 -0
- package/core/registry.d.ts +30 -0
- package/core/registry.js +47 -0
- package/core/repository.d.ts +72 -9
- package/core/repository.js +293 -43
- package/core/resolve-target.d.ts +1 -1
- package/core/resolve-target.js +1 -1
- package/core/service.d.ts +49 -0
- package/core/service.js +40 -0
- package/core/version-scheme.d.ts +23 -1
- package/core/version-scheme.js +29 -1
- package/core/workspace.d.ts +84 -33
- package/core/workspace.js +63 -22
- package/index.d.ts +111 -14
- package/index.js +85 -9
- package/interfaces/rman-config.interface.d.ts +739 -212
- package/interfaces/rman-config.interface.js +61 -1
- package/package.json +2 -1
- package/plugins/builtins.d.ts +44 -0
- package/plugins/builtins.js +33 -0
- package/plugins/detect.d.ts +78 -0
- package/plugins/detect.js +70 -0
- package/plugins/node/augmentation/rman.augmentation.d.ts +84 -0
- package/plugins/node/augmentation/rman.augmentation.js +1 -0
- package/plugins/node/augmentation/system-info.augmentation.d.ts +26 -0
- package/plugins/node/augmentation/system-info.augmentation.js +79 -0
- package/plugins/node/commands/ci.command.d.ts +131 -0
- package/plugins/node/commands/ci.command.js +59 -0
- package/plugins/node/commands/clean.command.d.ts +183 -0
- package/plugins/node/commands/clean.command.js +73 -0
- package/plugins/node/index.d.ts +29 -0
- package/plugins/node/index.js +40 -0
- package/plugins/node/node-config.interface.d.ts +77 -0
- package/plugins/node/node-config.interface.js +7 -0
- package/plugins/node/node-manifest.provider.d.ts +68 -0
- package/plugins/node/node-manifest.provider.js +125 -0
- package/plugins/node/node.platform.d.ts +53 -0
- package/plugins/node/node.platform.js +134 -0
- package/plugins/node/npm-publish-target.d.ts +73 -0
- package/plugins/node/npm-publish-target.js +96 -0
- package/plugins/node/services/ci.service.d.ts +47 -0
- package/plugins/node/services/ci.service.js +213 -0
- package/plugins/node/services/clean.service.d.ts +53 -0
- package/plugins/node/services/clean.service.js +237 -0
- package/plugins/node/services/publish.service.d.ts +114 -0
- package/plugins/node/services/publish.service.js +371 -0
- package/plugins/node/services/version-plan.service.d.ts +44 -0
- package/plugins/node/services/version-plan.service.js +58 -0
- package/plugins/node/utils/npm-view.d.ts +48 -0
- package/plugins/node/utils/npm-view.js +71 -0
- package/plugins/node/utils/workspace-range.d.ts +26 -0
- package/plugins/node/utils/workspace-range.js +28 -0
- package/services/change-hash.service.d.ts +2 -2
- package/services/change-hash.service.js +2 -2
- package/services/changelog.service.d.ts +62 -51
- package/services/changelog.service.js +14 -11
- package/services/docker-publish.service.d.ts +50 -29
- package/services/docker-publish.service.js +43 -20
- package/services/exec.service.d.ts +23 -12
- package/services/exec.service.js +14 -9
- package/services/github-release.service.d.ts +44 -33
- package/services/github-release.service.js +13 -10
- package/services/import.service.d.ts +25 -14
- package/services/import.service.js +9 -5
- package/services/list.service.d.ts +62 -10
- package/services/list.service.js +62 -15
- package/services/run.service.d.ts +22 -13
- package/services/run.service.js +262 -223
- package/services/version-plan.service.d.ts +27 -4
- package/services/version-plan.service.js +42 -15
- package/services/version.service.d.ts +31 -11
- package/services/version.service.js +29 -13
- package/targets/docker.target.d.ts +53 -0
- package/targets/docker.target.js +40 -0
- package/utils/bin-path.d.ts +6 -7
- package/utils/bin-path.js +7 -18
- package/utils/branch-guard.d.ts +29 -0
- package/utils/branch-guard.js +31 -0
- package/utils/exec.d.ts +10 -0
- package/utils/exec.js +1 -1
- package/utils/logger.d.ts +1 -1
- package/utils/logger.js +1 -1
- package/utils/package-filter.d.ts +127 -7
- package/utils/package-filter.js +197 -16
- package/utils/printable-config.d.ts +1 -1
- package/utils/printable-config.js +1 -1
- package/utils/run-bin.d.ts +10 -0
- package/utils/run-bin.js +1 -1
- package/utils/run-options.d.ts +97 -0
- package/utils/run-options.js +81 -0
- package/utils/version-stamp.d.ts +1 -1
- package/utils/version-stamp.js +1 -1
package/core/repository.d.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
import type { RmanConfig } from '../interfaces/rman-config.interface.js';
|
|
2
|
+
import { type DetectedBuiltin } from '../plugins/detect.js';
|
|
3
|
+
import { RmanApplication } from './application.js';
|
|
1
4
|
import { type ConfigScope, type GitScope, type PackageScope, type RepositoryScope } from './config.js';
|
|
2
|
-
import type { LoadedCommand } from './custom-command.js';
|
|
3
5
|
import { Package } from './package.js';
|
|
6
|
+
import type { Platform } from './plugin.js';
|
|
7
|
+
import { Workspace } from './workspace.js';
|
|
4
8
|
export declare class Repository extends Package {
|
|
5
9
|
readonly dirname: string;
|
|
6
10
|
readonly monorepo: boolean;
|
|
@@ -10,9 +14,10 @@ export declare class Repository extends Package {
|
|
|
10
14
|
* really was. Used by `currentPackage` to scope commands to "the package I'm standing in". */
|
|
11
15
|
readonly cwd: string;
|
|
12
16
|
readonly rootPackage: Package;
|
|
17
|
+
/** See the `defineProperty` in the constructor - what detection supplied, when it did. */
|
|
18
|
+
readonly detectedBuiltin?: DetectedBuiltin;
|
|
13
19
|
/** Commands the repository's plugins contributed, loaded during `create` because the workspace
|
|
14
20
|
* providers they bring are needed before any package can be found. `cli.ts` registers them. */
|
|
15
|
-
pluginCommands: LoadedCommand[];
|
|
16
21
|
/**
|
|
17
22
|
* Cached repository scope - see `_repositoryScope`.
|
|
18
23
|
*
|
|
@@ -36,11 +41,24 @@ export declare class Repository extends Package {
|
|
|
36
41
|
* for every package that mentions it.
|
|
37
42
|
*/
|
|
38
43
|
private readonly _readCache;
|
|
39
|
-
|
|
44
|
+
/**
|
|
45
|
+
* The application this repository belongs to - its services, its technologies, its logger.
|
|
46
|
+
*
|
|
47
|
+
* **Non-enumerable**, like `_repoScope` and `_git` beside it: the two things that walk a
|
|
48
|
+
* repository are `{...pkg}` spreads and the config scope, and an enumerable back-reference to the
|
|
49
|
+
* whole application would be dragged into both. A `Package` deliberately has no such field at
|
|
50
|
+
* all; a repository is never spread or serialized, which is what makes this one safe - measured,
|
|
51
|
+
* rather than assumed.
|
|
52
|
+
*/
|
|
53
|
+
readonly app: RmanApplication;
|
|
54
|
+
protected constructor(app: RmanApplication, dirname: string, monorepo: boolean, packages: Package[],
|
|
40
55
|
/** The directory `Repository.create()` was actually invoked from - unlike `dirname` (the
|
|
41
56
|
* resolved repository root, possibly several levels up), this is where the user's shell
|
|
42
57
|
* really was. Used by `currentPackage` to scope commands to "the package I'm standing in". */
|
|
43
|
-
cwd?: string
|
|
58
|
+
cwd?: string,
|
|
59
|
+
/** The root directory's own technology, from the same walk that found the packages - so the
|
|
60
|
+
* repository and its `rootPackage` agree without either searching the registry again. */
|
|
61
|
+
platform?: Platform);
|
|
44
62
|
/**
|
|
45
63
|
* The package whose own directory contains `cwd` (the deepest match, so a package nested
|
|
46
64
|
* inside another's directory resolves to the innermost one) - or `undefined` when `cwd` *is*
|
|
@@ -60,9 +78,21 @@ export declare class Repository extends Package {
|
|
|
60
78
|
* dirty, the reference point decides the rest: without `hash`, `committed`
|
|
61
79
|
* means committed but not yet in the upstream branch; with `hash`, `changed`
|
|
62
80
|
* means it differs from that commit. Otherwise a package is `clean`.
|
|
81
|
+
*
|
|
82
|
+
* **Keyed by `Package.selector`**, which is what addresses a package - it was `name`, and the two
|
|
83
|
+
* coincide wherever a technology names its packages. A repository whose does not had every such
|
|
84
|
+
* package answering to `""`, so one entry stood for all of them.
|
|
85
|
+
*
|
|
86
|
+
* **`includeRoot` is opt-in, and the reason is that the root's answer means something different.**
|
|
87
|
+
* Its directory contains every other package, so the same rule - "does a changed file fall under
|
|
88
|
+
* this directory" - reports `dirty` for the root whenever *anything* in the repository is dirty.
|
|
89
|
+
* That is the honest reading of the rule rather than a bug, and it is not what `run --changed`
|
|
90
|
+
* wants, so only a caller that asked for the root gets it (`rman list`'s table, which shows the
|
|
91
|
+
* root as the tree's own row).
|
|
63
92
|
*/
|
|
64
93
|
listStatus(options?: {
|
|
65
94
|
hash?: string;
|
|
95
|
+
includeRoot?: boolean;
|
|
66
96
|
}): Promise<Record<string, Repository.PackageStatus>>;
|
|
67
97
|
/**
|
|
68
98
|
* The scope a `${{ ... }}` expression is evaluated against for `pkg`, optionally with the version
|
|
@@ -89,13 +119,44 @@ export declare class Repository extends Package {
|
|
|
89
119
|
* nothing under `getPackages()` is the root, so only its own unmarked config applies.
|
|
90
120
|
*/
|
|
91
121
|
/**
|
|
92
|
-
* Gives every package its `repository
|
|
93
|
-
*
|
|
122
|
+
* Gives every package its `repository`, and hangs the `parent`/`children` tree off the walk that
|
|
123
|
+
* found them - before any config is resolved, since a config expression or a provider may already
|
|
124
|
+
* want to navigate from a package outwards.
|
|
125
|
+
*
|
|
126
|
+
* **The containment is read from the tree rather than recomputed from paths.** It used to be an
|
|
127
|
+
* O(n²) sweep comparing every package's directory against every other's and keeping the longest
|
|
128
|
+
* prefix - which is the same question `Workspace.walk` answers on the way down, asked again
|
|
129
|
+
* afterwards with the answer thrown away. Two places deriving one relationship is two places to
|
|
130
|
+
* disagree; there is one now.
|
|
131
|
+
*
|
|
132
|
+
* **`parent` is defined non-enumerably, `children` is a plain field**, which is the one asymmetry
|
|
133
|
+
* here and it is deliberate: a tree is serialized downwards, so `children` has to be walkable and
|
|
134
|
+
* `parent` must not be, or every `JSON.stringify` is a cycle. The same way `Repository.app` and
|
|
135
|
+
* `ORIGINS` travel.
|
|
94
136
|
*
|
|
95
137
|
* A repository's own `repository` is itself, which reads oddly and is the honest answer:
|
|
96
138
|
* `Repository extends Package`, so the repository *is* a package of its own repository.
|
|
97
139
|
*/
|
|
98
|
-
protected _linkPackages(): void;
|
|
140
|
+
protected _linkPackages(tree: Workspace.Node, nodes: Workspace.Node[], packages: Package[]): void;
|
|
141
|
+
/**
|
|
142
|
+
* Gives every package the selector a `"[glob]"` block and `--scope` match it by, and refuses two
|
|
143
|
+
* packages that would answer to the same one.
|
|
144
|
+
*
|
|
145
|
+
* **Its own step, before any config is resolved by a selector**, which is the ordering that makes
|
|
146
|
+
* the rest work: `_resolveConfigs` asks `resolveConfig` for each package *by selector*, so an
|
|
147
|
+
* address assigned afterwards would be applied to nothing.
|
|
148
|
+
*
|
|
149
|
+
* **`name` comes from the unmarked cascade only** - the same read `platform` gets, from the same
|
|
150
|
+
* cache, for the same reason. A `"[glob]"` block cannot set it (`assertSelectorBlocks` refuses
|
|
151
|
+
* one) because the glob matches the very thing the block would be setting.
|
|
152
|
+
*
|
|
153
|
+
* **Uniqueness is checked, and the cascade is the mistake it usually catches.** `name` cascades
|
|
154
|
+
* like every unmarked key, so one declaration above two packages gives both the same address -
|
|
155
|
+
* and the failure would otherwise be silent in the worst way: the config reaches both and
|
|
156
|
+
* `getPackage` returns whichever came first. The message names both directories, and says the
|
|
157
|
+
* cascade out loud when the two got it from one declaration.
|
|
158
|
+
*/
|
|
159
|
+
protected _assignSelectors(rootDir: string, cache: Map<string, RmanConfig>): Promise<void>;
|
|
99
160
|
protected _resolveConfigs(): Promise<void>;
|
|
100
161
|
protected _topoSortPackages(packages: Package[]): void;
|
|
101
162
|
protected _packageScope(pkg: Package, targetVersion?: string): PackageScope;
|
|
@@ -131,16 +192,18 @@ export declare class Repository extends Package {
|
|
|
131
192
|
* otherwise: the plugins that know what a package is are named in the config file this step
|
|
132
193
|
* is looking for.
|
|
133
194
|
* 2. **Load the plugins** the root's config names, which registers their workspace providers
|
|
134
|
-
* (
|
|
195
|
+
* (their commands arrive through `.rmanrc "commands"`, which `cli.ts` reads).
|
|
135
196
|
* 3. **Ask the providers** for the layout. None recognizing it means a repository that is itself
|
|
136
197
|
* the one package.
|
|
137
198
|
*
|
|
138
199
|
* **A repository whose `.rmanrc` names no plugin has no packages beyond itself**, and that is the
|
|
139
200
|
* boundary working rather than failing: `workspaces` in a `package.json` is npm's idea, so it
|
|
140
|
-
* takes `plugins: ['
|
|
201
|
+
* takes `plugins: ['node']` - or detection reading the directory as a Node one - for it to be
|
|
202
|
+
* read as a workspace at all.
|
|
141
203
|
*/
|
|
142
204
|
static create(root?: string, options?: {
|
|
143
205
|
deep?: number;
|
|
206
|
+
app?: RmanApplication;
|
|
144
207
|
}): Promise<Repository>;
|
|
145
208
|
/** Finishes constructing `repo` with the async work a constructor can't do itself - resolving
|
|
146
209
|
* `.rmanrc`/`.rmanrc.yml`/`.rmanrc.cjs`/`.mjs`/`.js` config (which may need a dynamic `import()`)
|
package/core/repository.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import colors from 'ansi-colors';
|
|
2
3
|
import path from 'path';
|
|
3
4
|
import semver from 'semver';
|
|
5
|
+
import { detectBuiltin } from '../plugins/detect.js';
|
|
4
6
|
import { GitHelper } from '../utils/git.js';
|
|
7
|
+
import { RmanApplication } from './application.js';
|
|
5
8
|
import { createFileScope, createReadScope, DEFERRED_PATHS, interpolateConfig, readDirConfig, resolveConfig, } from './config.js';
|
|
6
9
|
import { Manifest } from './manifest.js';
|
|
10
|
+
import { ORIGINS } from './merge-config.js';
|
|
7
11
|
import { Package } from './package.js';
|
|
8
|
-
import { loadPlugins } from './plugin.js';
|
|
12
|
+
import { loadPlugins, registerPlugin } from './plugin-loader.js';
|
|
9
13
|
import { Workspace } from './workspace.js';
|
|
10
14
|
export class Repository extends Package {
|
|
11
15
|
dirname;
|
|
@@ -13,9 +17,10 @@ export class Repository extends Package {
|
|
|
13
17
|
packages;
|
|
14
18
|
cwd;
|
|
15
19
|
rootPackage;
|
|
20
|
+
/** See the `defineProperty` in the constructor - what detection supplied, when it did. */
|
|
21
|
+
detectedBuiltin;
|
|
16
22
|
/** Commands the repository's plugins contributed, loaded during `create` because the workspace
|
|
17
23
|
* providers they bring are needed before any package can be found. `cli.ts` registers them. */
|
|
18
|
-
pluginCommands = [];
|
|
19
24
|
/**
|
|
20
25
|
* Cached repository scope - see `_repositoryScope`.
|
|
21
26
|
*
|
|
@@ -39,17 +44,40 @@ export class Repository extends Package {
|
|
|
39
44
|
* for every package that mentions it.
|
|
40
45
|
*/
|
|
41
46
|
_readCache = new Map();
|
|
42
|
-
|
|
47
|
+
/**
|
|
48
|
+
* The application this repository belongs to - its services, its technologies, its logger.
|
|
49
|
+
*
|
|
50
|
+
* **Non-enumerable**, like `_repoScope` and `_git` beside it: the two things that walk a
|
|
51
|
+
* repository are `{...pkg}` spreads and the config scope, and an enumerable back-reference to the
|
|
52
|
+
* whole application would be dragged into both. A `Package` deliberately has no such field at
|
|
53
|
+
* all; a repository is never spread or serialized, which is what makes this one safe - measured,
|
|
54
|
+
* rather than assumed.
|
|
55
|
+
*/
|
|
56
|
+
app;
|
|
57
|
+
constructor(app, dirname, monorepo, packages,
|
|
43
58
|
/** The directory `Repository.create()` was actually invoked from - unlike `dirname` (the
|
|
44
59
|
* resolved repository root, possibly several levels up), this is where the user's shell
|
|
45
60
|
* really was. Used by `currentPackage` to scope commands to "the package I'm standing in". */
|
|
46
|
-
cwd = dirname
|
|
47
|
-
|
|
61
|
+
cwd = dirname,
|
|
62
|
+
/** The root directory's own technology, from the same walk that found the packages - so the
|
|
63
|
+
* repository and its `rootPackage` agree without either searching the registry again. */
|
|
64
|
+
platform) {
|
|
65
|
+
super(dirname, app, platform);
|
|
48
66
|
this.dirname = dirname;
|
|
49
67
|
this.monorepo = monorepo;
|
|
50
68
|
this.packages = packages;
|
|
51
69
|
this.cwd = cwd;
|
|
52
|
-
this
|
|
70
|
+
Object.defineProperty(this, 'app', { value: app, enumerable: false, writable: false });
|
|
71
|
+
/**
|
|
72
|
+
* What detection decided for this repository, or `undefined` when it declared its own
|
|
73
|
+
* technology (or when there was nothing to detect). Carried here because `_resolveConfigs` runs
|
|
74
|
+
* later and every read of the root's config has to agree with the one decision `create` made.
|
|
75
|
+
*
|
|
76
|
+
* Non-enumerable, like `app` and for the same reason: `{...repository}` and `toEqual` both walk
|
|
77
|
+
* a repository, and bookkeeping that shows up there turns spec failures into diffs about it.
|
|
78
|
+
*/
|
|
79
|
+
Object.defineProperty(this, 'detectedBuiltin', { value: undefined, enumerable: false, writable: true });
|
|
80
|
+
this.rootPackage = new Package(dirname, app, platform);
|
|
53
81
|
if (!monorepo)
|
|
54
82
|
this.packages = [this.rootPackage];
|
|
55
83
|
// Config resolution can load a `.rmanrc.cjs`/`.mjs`/`.js` module (dynamic `import()`, always
|
|
@@ -94,11 +122,22 @@ export class Repository extends Package {
|
|
|
94
122
|
* dirty, the reference point decides the rest: without `hash`, `committed`
|
|
95
123
|
* means committed but not yet in the upstream branch; with `hash`, `changed`
|
|
96
124
|
* means it differs from that commit. Otherwise a package is `clean`.
|
|
125
|
+
*
|
|
126
|
+
* **Keyed by `Package.selector`**, which is what addresses a package - it was `name`, and the two
|
|
127
|
+
* coincide wherever a technology names its packages. A repository whose does not had every such
|
|
128
|
+
* package answering to `""`, so one entry stood for all of them.
|
|
129
|
+
*
|
|
130
|
+
* **`includeRoot` is opt-in, and the reason is that the root's answer means something different.**
|
|
131
|
+
* Its directory contains every other package, so the same rule - "does a changed file fall under
|
|
132
|
+
* this directory" - reports `dirty` for the root whenever *anything* in the repository is dirty.
|
|
133
|
+
* That is the honest reading of the rule rather than a bug, and it is not what `run --changed`
|
|
134
|
+
* wants, so only a caller that asked for the root gets it (`rman list`'s table, which shows the
|
|
135
|
+
* root as the tree's own row).
|
|
97
136
|
*/
|
|
98
137
|
async listStatus(options) {
|
|
99
138
|
const hash = options?.hash;
|
|
100
139
|
const git = new GitHelper({ cwd: this.dirname });
|
|
101
|
-
const packages = this.getPackages();
|
|
140
|
+
const packages = options?.includeRoot ? [this.rootPackage, ...this.getPackages()] : this.getPackages();
|
|
102
141
|
const belongsTo = (p, files) => files.some(f => !path.relative(p.dirname, f).startsWith('..'));
|
|
103
142
|
const [dirtyFiles, referenceFiles] = await Promise.all([
|
|
104
143
|
git.listDirtyFiles({ absolute: true }),
|
|
@@ -107,11 +146,11 @@ export class Repository extends Package {
|
|
|
107
146
|
const result = {};
|
|
108
147
|
for (const p of packages) {
|
|
109
148
|
if (belongsTo(p, dirtyFiles))
|
|
110
|
-
result[p.
|
|
149
|
+
result[p.selector] = 'dirty';
|
|
111
150
|
else if (belongsTo(p, referenceFiles))
|
|
112
|
-
result[p.
|
|
151
|
+
result[p.selector] = hash ? 'changed' : 'committed';
|
|
113
152
|
else
|
|
114
|
-
result[p.
|
|
153
|
+
result[p.selector] = 'clean';
|
|
115
154
|
}
|
|
116
155
|
return result;
|
|
117
156
|
}
|
|
@@ -173,30 +212,96 @@ export class Repository extends Package {
|
|
|
173
212
|
* nothing under `getPackages()` is the root, so only its own unmarked config applies.
|
|
174
213
|
*/
|
|
175
214
|
/**
|
|
176
|
-
* Gives every package its `repository
|
|
177
|
-
*
|
|
215
|
+
* Gives every package its `repository`, and hangs the `parent`/`children` tree off the walk that
|
|
216
|
+
* found them - before any config is resolved, since a config expression or a provider may already
|
|
217
|
+
* want to navigate from a package outwards.
|
|
218
|
+
*
|
|
219
|
+
* **The containment is read from the tree rather than recomputed from paths.** It used to be an
|
|
220
|
+
* O(n²) sweep comparing every package's directory against every other's and keeping the longest
|
|
221
|
+
* prefix - which is the same question `Workspace.walk` answers on the way down, asked again
|
|
222
|
+
* afterwards with the answer thrown away. Two places deriving one relationship is two places to
|
|
223
|
+
* disagree; there is one now.
|
|
224
|
+
*
|
|
225
|
+
* **`parent` is defined non-enumerably, `children` is a plain field**, which is the one asymmetry
|
|
226
|
+
* here and it is deliberate: a tree is serialized downwards, so `children` has to be walkable and
|
|
227
|
+
* `parent` must not be, or every `JSON.stringify` is a cycle. The same way `Repository.app` and
|
|
228
|
+
* `ORIGINS` travel.
|
|
178
229
|
*
|
|
179
230
|
* A repository's own `repository` is itself, which reads oddly and is the honest answer:
|
|
180
231
|
* `Repository extends Package`, so the repository *is* a package of its own repository.
|
|
181
232
|
*/
|
|
182
|
-
_linkPackages() {
|
|
183
|
-
|
|
184
|
-
|
|
233
|
+
_linkPackages(tree, nodes, packages) {
|
|
234
|
+
/** Non-enumerable everywhere, including on the repository itself - see `Package.repository`.
|
|
235
|
+
* `writable` because `import` grafts an external repository's packages onto this one. */
|
|
236
|
+
const link = (pkg) => {
|
|
237
|
+
Object.defineProperty(pkg, 'repository', { value: this, enumerable: false, configurable: true, writable: true });
|
|
238
|
+
};
|
|
239
|
+
link(this);
|
|
240
|
+
link(this.rootPackage);
|
|
241
|
+
for (const pkg of this.packages)
|
|
242
|
+
link(pkg);
|
|
243
|
+
/** The walk visits a directory once, so one node is one package and this map is a bijection -
|
|
244
|
+
* which is what lets the edges below be read off the tree instead of guessed from paths. */
|
|
245
|
+
const byNode = new Map(nodes.map((node, i) => [node, packages[i]]));
|
|
246
|
+
byNode.set(tree, this.rootPackage);
|
|
247
|
+
for (const node of [tree, ...nodes]) {
|
|
248
|
+
const pkg = byNode.get(node);
|
|
249
|
+
for (const childNode of node.children) {
|
|
250
|
+
const child = byNode.get(childNode);
|
|
251
|
+
pkg.children.push(child);
|
|
252
|
+
Object.defineProperty(child, 'parent', { value: pkg, enumerable: false, configurable: true });
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
/** `Repository extends Package` while holding a separate `rootPackage` for the same directory,
|
|
256
|
+
* so both are truthfully the root - they share the one array rather than each getting a copy
|
|
257
|
+
* that could drift. */
|
|
258
|
+
Object.defineProperty(this, 'children', { value: this.rootPackage.children, enumerable: true });
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Gives every package the selector a `"[glob]"` block and `--scope` match it by, and refuses two
|
|
262
|
+
* packages that would answer to the same one.
|
|
263
|
+
*
|
|
264
|
+
* **Its own step, before any config is resolved by a selector**, which is the ordering that makes
|
|
265
|
+
* the rest work: `_resolveConfigs` asks `resolveConfig` for each package *by selector*, so an
|
|
266
|
+
* address assigned afterwards would be applied to nothing.
|
|
267
|
+
*
|
|
268
|
+
* **`name` comes from the unmarked cascade only** - the same read `platform` gets, from the same
|
|
269
|
+
* cache, for the same reason. A `"[glob]"` block cannot set it (`assertSelectorBlocks` refuses
|
|
270
|
+
* one) because the glob matches the very thing the block would be setting.
|
|
271
|
+
*
|
|
272
|
+
* **Uniqueness is checked, and the cascade is the mistake it usually catches.** `name` cascades
|
|
273
|
+
* like every unmarked key, so one declaration above two packages gives both the same address -
|
|
274
|
+
* and the failure would otherwise be silent in the worst way: the config reaches both and
|
|
275
|
+
* `getPackage` returns whichever came first. The message names both directories, and says the
|
|
276
|
+
* cascade out loud when the two got it from one declaration.
|
|
277
|
+
*/
|
|
278
|
+
async _assignSelectors(rootDir, cache) {
|
|
279
|
+
const declaredBy = new Map();
|
|
280
|
+
for (const pkg of [this.rootPackage, ...this.packages]) {
|
|
281
|
+
const config = await resolveConfig(rootDir, pkg.dirname, cache, undefined);
|
|
282
|
+
const declared = config.name;
|
|
283
|
+
if (declared !== undefined && (typeof declared !== 'string' || !declared.trim())) {
|
|
284
|
+
throw new Error(`"name" takes the selector this package answers to - "${pkg.dirname}" gave ${typeof declared}.`);
|
|
285
|
+
}
|
|
286
|
+
pkg.selector = declared?.trim() || pkg.platformSelector();
|
|
287
|
+
declaredBy.set(pkg, declared !== undefined);
|
|
288
|
+
}
|
|
289
|
+
/** The root is left out: a glob never matches it and `"[/]"` needs no name, so it shares an
|
|
290
|
+
* address with nobody - see `Package.selector`. */
|
|
291
|
+
const bySelector = new Map();
|
|
185
292
|
for (const pkg of this.packages) {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
if (contains && (!parent || other.dirname.length > parent.dirname.length))
|
|
197
|
-
parent = other;
|
|
293
|
+
const clash = bySelector.get(pkg.selector);
|
|
294
|
+
if (clash) {
|
|
295
|
+
const cascaded = declaredBy.get(pkg) && declaredBy.get(clash);
|
|
296
|
+
throw new Error(`Two packages answer to the selector "${pkg.selector}":\n ${clash.dirname}\n ${pkg.dirname}\n` +
|
|
297
|
+
` A selector has to be unique - "[${pkg.selector}]" and \`--scope ${pkg.selector}\` ` +
|
|
298
|
+
`cannot mean two packages.` +
|
|
299
|
+
(cascaded
|
|
300
|
+
? `\n Both got it from one cascading "name" declaration above them; declare it in ` +
|
|
301
|
+
`each package's own ".rmanrc" instead.`
|
|
302
|
+
: ''));
|
|
198
303
|
}
|
|
199
|
-
pkg.
|
|
304
|
+
bySelector.set(pkg.selector, pkg);
|
|
200
305
|
}
|
|
201
306
|
}
|
|
202
307
|
async _resolveConfigs() {
|
|
@@ -208,14 +313,18 @@ export class Repository extends Package {
|
|
|
208
313
|
* second `rawConfig` copy of every package's config for that one reader; measured identical.
|
|
209
314
|
*/
|
|
210
315
|
/**
|
|
211
|
-
*
|
|
212
|
-
*
|
|
213
|
-
*
|
|
316
|
+
* **By selector, not by name** - `"[glob]"` matches `Package.selector`, which is the package's
|
|
317
|
+
* own `.rmanrc "name"` when it assigned one and its platform's answer otherwise. They coincide
|
|
318
|
+
* for every Node repository; they are not the same question, and `name` was the wrong one to
|
|
319
|
+
* ask, since a package having one at all is an ecosystem's promise rather than rman's.
|
|
320
|
+
*
|
|
321
|
+
* The root passes its own too, although no glob can match it: `"[/]"` is applied on the
|
|
322
|
+
* strength of the target *being* the root and needs no selector at all (see `resolveConfig`).
|
|
214
323
|
*/
|
|
215
|
-
const rootRaw = await resolveConfig(this.dirname, this.dirname, cache, this.rootPackage.
|
|
324
|
+
const rootRaw = await resolveConfig(this.dirname, this.dirname, cache, this.rootPackage.selector, this.detectedBuiltin);
|
|
216
325
|
this.config = interpolateConfig(rootRaw, this.configScope(this.rootPackage), { skip: DEFERRED_PATHS });
|
|
217
326
|
for (const pkg of this.packages) {
|
|
218
|
-
const raw = await resolveConfig(this.dirname, pkg.dirname, cache, pkg.
|
|
327
|
+
const raw = await resolveConfig(this.dirname, pkg.dirname, cache, pkg.selector, this.detectedBuiltin);
|
|
219
328
|
pkg.config = interpolateConfig(raw, this.configScope(pkg), { skip: DEFERRED_PATHS });
|
|
220
329
|
}
|
|
221
330
|
if (this.monorepo)
|
|
@@ -235,7 +344,7 @@ export class Repository extends Package {
|
|
|
235
344
|
// than refusing it - so the scope has to survive one too.
|
|
236
345
|
const name = pkg.name ?? '';
|
|
237
346
|
/** Splitting `@scope/name` is npm's convention, not a universal - the provider decides. */
|
|
238
|
-
const { scope: nameScope, unscopedName } = Manifest.splitName(pkg.dirname, name);
|
|
347
|
+
const { scope: nameScope, unscopedName } = Manifest.splitName(this.app, pkg.dirname, name);
|
|
239
348
|
const scope = {
|
|
240
349
|
name,
|
|
241
350
|
scope: nameScope,
|
|
@@ -383,26 +492,106 @@ export class Repository extends Package {
|
|
|
383
492
|
* otherwise: the plugins that know what a package is are named in the config file this step
|
|
384
493
|
* is looking for.
|
|
385
494
|
* 2. **Load the plugins** the root's config names, which registers their workspace providers
|
|
386
|
-
* (
|
|
495
|
+
* (their commands arrive through `.rmanrc "commands"`, which `cli.ts` reads).
|
|
387
496
|
* 3. **Ask the providers** for the layout. None recognizing it means a repository that is itself
|
|
388
497
|
* the one package.
|
|
389
498
|
*
|
|
390
499
|
* **A repository whose `.rmanrc` names no plugin has no packages beyond itself**, and that is the
|
|
391
500
|
* boundary working rather than failing: `workspaces` in a `package.json` is npm's idea, so it
|
|
392
|
-
* takes `plugins: ['
|
|
501
|
+
* takes `plugins: ['node']` - or detection reading the directory as a Node one - for it to be
|
|
502
|
+
* read as a workspace at all.
|
|
393
503
|
*/
|
|
394
504
|
static async create(root, options) {
|
|
395
505
|
const from = root || process.cwd();
|
|
396
506
|
const rootDir = Workspace.findRoot(from, options?.deep ?? 10);
|
|
507
|
+
/**
|
|
508
|
+
* One application per repository, made here unless the caller brought one.
|
|
509
|
+
*
|
|
510
|
+
* `runCli` passes its own so that `--log-level` reaches the logger; a spec that only wants a
|
|
511
|
+
* repository lets this make one, which is also what keeps two repositories in a single process
|
|
512
|
+
* from sharing anything - the thing that used to need five `clear*()` calls before every test.
|
|
513
|
+
*/
|
|
514
|
+
const app = options?.app ?? new RmanApplication();
|
|
397
515
|
/** The root's own config, raw: `plugins` is a list of package names, so it needs neither the
|
|
398
516
|
* package list (which does not exist yet) nor expression interpolation. */
|
|
399
|
-
const
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
517
|
+
const declared = await readDirConfig(rootDir);
|
|
518
|
+
/**
|
|
519
|
+
* **What this repository looks like, when nothing said** - the other half of shipping the
|
|
520
|
+
* built-ins in the box. See `detectBuiltin`.
|
|
521
|
+
*
|
|
522
|
+
* **Two conditions, and the second is the one that is easy to miss.** The config declaring no
|
|
523
|
+
* `plugins` is not the same as the *repository* having no technology: a programmatic caller -
|
|
524
|
+
* and every spec in this suite - registers one straight onto the application without writing a
|
|
525
|
+
* config at all. Guessing on top of that registers a second technology, and the first provider
|
|
526
|
+
* that recognizes a directory decides whether it holds a package. Measured with only the config
|
|
527
|
+
* condition: ten specs changed answer, seven of them about config cascading and three about
|
|
528
|
+
* `publish`'s flags.
|
|
529
|
+
*
|
|
530
|
+
* **`platforms`, not `plugins`, and the difference is what detection produces.** What would be
|
|
531
|
+
* added here is a *platform*, so what must not already be there is a platform - a plugin that
|
|
532
|
+
* only registers a command says nothing about which directories hold packages, and letting it
|
|
533
|
+
* suppress the guess would leave a Node repository undetected for having added a command.
|
|
534
|
+
*
|
|
535
|
+
* **A root `platform` counts as having said something**, like `plugins: []` does. Detection
|
|
536
|
+
* exists for a repository that stated nothing; one naming its technology has stated the very
|
|
537
|
+
* thing detection would be guessing at, and guessing anyway would register a *second* platform
|
|
538
|
+
* beside the declared one - which then competes for every directory the declaration did not
|
|
539
|
+
* cover.
|
|
540
|
+
*
|
|
541
|
+
* Decided **once**, here, and handed to every read that has to agree - `readDirConfig` has no
|
|
542
|
+
* business knowing about an application.
|
|
543
|
+
*/
|
|
544
|
+
const saidSomething = declared.plugins !== undefined || declared.platform !== undefined;
|
|
545
|
+
const detected = !saidSomething && app.platforms.size === 0 ? await detectBuiltin(rootDir) : undefined;
|
|
546
|
+
const rootConfig = detected ? await readDirConfig(rootDir, { inject: detected }) : declared;
|
|
547
|
+
/**
|
|
548
|
+
* **Said out loud, because a guess the reader cannot see is one they cannot correct.**
|
|
549
|
+
*
|
|
550
|
+
* To **stderr**, not through `app.logger`: that writes with `console.log`, and `rman list
|
|
551
|
+
* --json` has to stay a parseable document on stdout - measured, its output is pure JSON, and
|
|
552
|
+
* one line of prose in front of it breaks every `| jq`. The same reason `--help`'s degradation
|
|
553
|
+
* notice goes to stderr. Not at `silent`, where the caller asked for no narration.
|
|
554
|
+
*/
|
|
555
|
+
if (detected && app.logger.level !== 'silent') {
|
|
556
|
+
const line = `${detected.name} repository detected (${detected.because}) - ` +
|
|
557
|
+
`write \`plugins: ['${detected.name}']\` in .rmanrc to state it, or \`plugins: []\` for none.`;
|
|
558
|
+
console.error(process.stderr.isTTY ? colors.gray(line) : line);
|
|
559
|
+
}
|
|
560
|
+
await loadPlugins(app, rootConfig);
|
|
561
|
+
/**
|
|
562
|
+
* **Its own cache, deliberately not shared with `_resolveConfigs`'.**
|
|
563
|
+
*
|
|
564
|
+
* Both cache `readDirConfig` per directory, and the two reads of the *root* are not the same
|
|
565
|
+
* read: `_resolveConfigs` passes `detectedBuiltin` as `inject` and this one passes nothing,
|
|
566
|
+
* since a built-in's contribution has no bearing on which platform a directory declares. The
|
|
567
|
+
* cache is keyed by directory alone, so one shared map would hand whichever ran first to the
|
|
568
|
+
* other - and for the root that is a config with or without the whole node built-in merged in.
|
|
569
|
+
*
|
|
570
|
+
* The cost is one extra read per directory in the chain, on a repository that is about to read
|
|
571
|
+
* every one of them again per package anyway.
|
|
572
|
+
*/
|
|
573
|
+
const platformCache = new Map();
|
|
574
|
+
/**
|
|
575
|
+
* **The walk**, which is where the package list comes from now - descending from the root,
|
|
576
|
+
* asking each directory's own technology where its children are. `Workspace.resolve` asked the
|
|
577
|
+
* root once, through whichever platform recognized it first; the tree is the shape discovery
|
|
578
|
+
* actually has, and it is what makes a nested package of another technology findable at all.
|
|
579
|
+
*/
|
|
580
|
+
const tree = await Workspace.walk(app, rootDir, {
|
|
581
|
+
deep: options?.deep,
|
|
582
|
+
declared: dir => declaredPlatformAt(app, rootDir, dir, platformCache),
|
|
583
|
+
});
|
|
584
|
+
const nodes = Workspace.flatten(tree);
|
|
585
|
+
const packages = nodes.map(node => new Package(node.dirname, app, node.platform));
|
|
586
|
+
const repo = new Repository(app, tree.dirname, packages.length > 0, packages, from, tree.platform);
|
|
587
|
+
repo._linkPackages(tree, nodes, packages);
|
|
588
|
+
/** Before `_resolveConfigs`, because a `"[glob]"` block is matched against the selector - so
|
|
589
|
+
* the addresses have to be settled before anything is resolved by them. */
|
|
590
|
+
await repo._assignSelectors(rootDir, platformCache);
|
|
591
|
+
/** The application is what the plugins registered into a moment ago; from here on it can hand
|
|
592
|
+
* out services, which need the repository to work on. */
|
|
593
|
+
repo.detectedBuiltin = detected;
|
|
594
|
+
app.attachRepository(repo);
|
|
406
595
|
return Repository._init(repo);
|
|
407
596
|
}
|
|
408
597
|
/** Finishes constructing `repo` with the async work a constructor can't do itself - resolving
|
|
@@ -414,3 +603,64 @@ export class Repository extends Package {
|
|
|
414
603
|
return repo;
|
|
415
604
|
}
|
|
416
605
|
}
|
|
606
|
+
/**
|
|
607
|
+
* The platform `dir` declares in its cascaded `.rmanrc "platform"`, loaded if it has to be -
|
|
608
|
+
* `undefined` when the directory declares none, so the walk falls back to its guess.
|
|
609
|
+
*
|
|
610
|
+
* **The unmarked cascade only**, which is what `resolveConfig` gives with no package name: a
|
|
611
|
+
* selector matches a package *name*, and this runs while the packages are still being found - the
|
|
612
|
+
* name is not known yet, and it is read *from* a manifest whose reader this key decides. So
|
|
613
|
+
* `"[/]"` reaches the root (its directory is the repository root, which needs no name) and a glob
|
|
614
|
+
* block contributes nothing here, which is the honest answer rather than a half-applied one.
|
|
615
|
+
*/
|
|
616
|
+
async function declaredPlatformAt(app, rootDir, dir, cache) {
|
|
617
|
+
const config = await resolveConfig(rootDir, dir, cache);
|
|
618
|
+
const declared = config.platform;
|
|
619
|
+
if (declared === undefined)
|
|
620
|
+
return undefined;
|
|
621
|
+
if (typeof declared !== 'string' || !declared.trim()) {
|
|
622
|
+
throw new Error(`"platform" takes a platform's name - ${originOf(config)} gave ${typeof declared}.`);
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
625
|
+
* **An expression is refused rather than read as a literal.** `platform` is consulted before any
|
|
626
|
+
* package exists - it is what decides what a package *is* - so there is no `pkg` for an
|
|
627
|
+
* expression to be about, and `interpolateConfig` runs long afterwards. Passed through, a
|
|
628
|
+
* `${{ }}` here reached the lookup below as the raw text and failed as an unknown platform name,
|
|
629
|
+
* which sends the reader to check their `plugins`.
|
|
630
|
+
*/
|
|
631
|
+
if (declared.includes('${{')) {
|
|
632
|
+
throw new Error(`"platform" cannot be an expression (${originOf(config)}) - it is read while ` +
|
|
633
|
+
`the packages are still being found, so there is no package for one to be about. Write the ` +
|
|
634
|
+
`name, and use a package's own ".rmanrc" where the answer differs.`);
|
|
635
|
+
}
|
|
636
|
+
const registered = [...app.platforms].find(p => p.name === declared);
|
|
637
|
+
if (registered)
|
|
638
|
+
return registered;
|
|
639
|
+
/**
|
|
640
|
+
* **A built-in is loaded on the strength of being named**, from any level - `platform: 'node'` is
|
|
641
|
+
* enough, and it is what a repository holding one Node package among others writes.
|
|
642
|
+
*
|
|
643
|
+
* `platform()` and not `contribute()`, and that split is why the two halves exist: what arrives
|
|
644
|
+
* is the technology alone. Commands and publish targets come from root `plugins`, because they
|
|
645
|
+
* are repository-wide - a Node package inside a Cargo repository wants npm's manifest read, not
|
|
646
|
+
* an `rman clean` that would sweep the whole tree.
|
|
647
|
+
*/
|
|
648
|
+
const { BUILTIN_PLUGINS, builtinPluginNames } = await import('../plugins/builtins.js');
|
|
649
|
+
const builtin = BUILTIN_PLUGINS[declared];
|
|
650
|
+
if (builtin) {
|
|
651
|
+
const platform = builtin.platform();
|
|
652
|
+
registerPlugin(app, platform);
|
|
653
|
+
return platform;
|
|
654
|
+
}
|
|
655
|
+
const have = [...app.platforms].map(p => p.name).filter(Boolean);
|
|
656
|
+
throw new Error(`"platform" names "${declared}" (${originOf(config)}), which is not a platform ` +
|
|
657
|
+
`this repository has. ${have.length ? `Registered: ${have.join(', ')}. ` : 'None is registered. '}` +
|
|
658
|
+
`rman ships ${builtinPluginNames().join(', ')}; anything else arrives through "plugins".`);
|
|
659
|
+
}
|
|
660
|
+
/** Which file the `platform` key came from, for an error that can be acted on - `mergeConfig`
|
|
661
|
+
* records one per key under `ORIGINS`, and a config is merged from a directory's own four forms,
|
|
662
|
+
* an `extends` base and one layer per directory before anything reads it. */
|
|
663
|
+
function originOf(config) {
|
|
664
|
+
const origins = config[ORIGINS];
|
|
665
|
+
return origins?.platform ? `in "${origins.platform}"` : 'the "platform" key';
|
|
666
|
+
}
|
package/core/resolve-target.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ export declare function resolveConfigTarget(target: string, from: string, label:
|
|
|
15
15
|
* repository cannot resolve it.
|
|
16
16
|
*
|
|
17
17
|
* A globally installed rman's siblings *are* the globally installed packages, so this is what makes
|
|
18
|
-
* `rman ci` work on a fresh clone - the command
|
|
18
|
+
* `rman ci` work on a fresh clone - the command came from `rman-node` then, and `ci` exists to create
|
|
19
19
|
* the very `node_modules` the plugin would otherwise have to be found in. Measured: with both
|
|
20
20
|
* installed globally, a clone answered `"plugins" target "rman-node" could not be resolved ... is
|
|
21
21
|
* it installed in this repository?`, which was true and useless.
|
package/core/resolve-target.js
CHANGED
|
@@ -39,7 +39,7 @@ export function resolveConfigTarget(target, from, label) {
|
|
|
39
39
|
* repository cannot resolve it.
|
|
40
40
|
*
|
|
41
41
|
* A globally installed rman's siblings *are* the globally installed packages, so this is what makes
|
|
42
|
-
* `rman ci` work on a fresh clone - the command
|
|
42
|
+
* `rman ci` work on a fresh clone - the command came from `rman-node` then, and `ci` exists to create
|
|
43
43
|
* the very `node_modules` the plugin would otherwise have to be found in. Measured: with both
|
|
44
44
|
* installed globally, a clone answered `"plugins" target "rman-node" could not be resolved ... is
|
|
45
45
|
* it installed in this repository?`, which was true and useless.
|