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.
Files changed (153) hide show
  1. package/README.md +63 -19
  2. package/cli.d.ts +5 -0
  3. package/cli.js +214 -88
  4. package/commands/build.command.d.ts +177 -3
  5. package/commands/build.command.js +20 -10
  6. package/commands/changed.command.d.ts +80 -3
  7. package/commands/changed.command.js +19 -12
  8. package/commands/changelog.command.d.ts +192 -3
  9. package/commands/changelog.command.js +87 -43
  10. package/commands/config.command.d.ts +44 -3
  11. package/commands/config.command.js +30 -19
  12. package/commands/diff.command.d.ts +37 -3
  13. package/commands/diff.command.js +25 -16
  14. package/commands/exec.command.d.ts +193 -3
  15. package/commands/exec.command.js +60 -58
  16. package/commands/github-release.command.d.ts +154 -3
  17. package/commands/github-release.command.js +67 -37
  18. package/commands/import.command.d.ts +36 -3
  19. package/commands/import.command.js +28 -20
  20. package/commands/info.command.d.ts +35 -7
  21. package/commands/info.command.js +36 -30
  22. package/commands/list.command.d.ts +163 -3
  23. package/commands/list.command.js +109 -71
  24. package/commands/publish.command.d.ts +231 -0
  25. package/commands/publish.command.js +304 -0
  26. package/commands/run.command.d.ts +186 -6
  27. package/commands/run.command.js +26 -72
  28. package/commands/test.command.d.ts +173 -3
  29. package/commands/test.command.js +16 -10
  30. package/commands/version.command.d.ts +317 -3
  31. package/commands/version.command.js +149 -69
  32. package/commands.d.ts +32 -0
  33. package/commands.js +28 -0
  34. package/constants.js +1 -1
  35. package/core/application.d.ts +116 -0
  36. package/core/application.js +143 -0
  37. package/core/command-builder.d.ts +14 -0
  38. package/core/command-builder.js +78 -0
  39. package/core/config.d.ts +180 -50
  40. package/core/config.js +332 -153
  41. package/core/core-services.d.ts +14 -0
  42. package/core/core-services.js +30 -0
  43. package/core/core-targets.d.ts +14 -0
  44. package/core/core-targets.js +16 -0
  45. package/core/custom-command.d.ts +42 -6
  46. package/core/custom-command.js +44 -17
  47. package/core/extends-config.d.ts +13 -5
  48. package/core/extends-config.js +52 -12
  49. package/core/load-config-module.d.ts +28 -0
  50. package/core/load-config-module.js +42 -0
  51. package/core/manifest.d.ts +47 -25
  52. package/core/manifest.js +51 -69
  53. package/core/merge-config.d.ts +33 -34
  54. package/core/merge-config.js +138 -93
  55. package/core/package.d.ts +145 -17
  56. package/core/package.js +128 -36
  57. package/core/plugin-loader.d.ts +65 -0
  58. package/core/plugin-loader.js +234 -0
  59. package/core/plugin.d.ts +135 -90
  60. package/core/plugin.js +70 -173
  61. package/core/publish-target.d.ts +124 -0
  62. package/core/publish-target.js +30 -0
  63. package/core/registry.d.ts +30 -0
  64. package/core/registry.js +47 -0
  65. package/core/repository.d.ts +72 -9
  66. package/core/repository.js +293 -43
  67. package/core/resolve-target.d.ts +1 -1
  68. package/core/resolve-target.js +1 -1
  69. package/core/service.d.ts +49 -0
  70. package/core/service.js +40 -0
  71. package/core/version-scheme.d.ts +23 -1
  72. package/core/version-scheme.js +29 -1
  73. package/core/workspace.d.ts +84 -33
  74. package/core/workspace.js +63 -22
  75. package/index.d.ts +111 -14
  76. package/index.js +85 -9
  77. package/interfaces/rman-config.interface.d.ts +739 -212
  78. package/interfaces/rman-config.interface.js +61 -1
  79. package/package.json +2 -1
  80. package/plugins/builtins.d.ts +44 -0
  81. package/plugins/builtins.js +33 -0
  82. package/plugins/detect.d.ts +78 -0
  83. package/plugins/detect.js +70 -0
  84. package/plugins/node/augmentation/rman.augmentation.d.ts +84 -0
  85. package/plugins/node/augmentation/rman.augmentation.js +1 -0
  86. package/plugins/node/augmentation/system-info.augmentation.d.ts +26 -0
  87. package/plugins/node/augmentation/system-info.augmentation.js +79 -0
  88. package/plugins/node/commands/ci.command.d.ts +131 -0
  89. package/plugins/node/commands/ci.command.js +59 -0
  90. package/plugins/node/commands/clean.command.d.ts +183 -0
  91. package/plugins/node/commands/clean.command.js +73 -0
  92. package/plugins/node/index.d.ts +29 -0
  93. package/plugins/node/index.js +40 -0
  94. package/plugins/node/node-config.interface.d.ts +77 -0
  95. package/plugins/node/node-config.interface.js +7 -0
  96. package/plugins/node/node-manifest.provider.d.ts +68 -0
  97. package/plugins/node/node-manifest.provider.js +125 -0
  98. package/plugins/node/node.platform.d.ts +53 -0
  99. package/plugins/node/node.platform.js +134 -0
  100. package/plugins/node/npm-publish-target.d.ts +73 -0
  101. package/plugins/node/npm-publish-target.js +96 -0
  102. package/plugins/node/services/ci.service.d.ts +47 -0
  103. package/plugins/node/services/ci.service.js +213 -0
  104. package/plugins/node/services/clean.service.d.ts +53 -0
  105. package/plugins/node/services/clean.service.js +237 -0
  106. package/plugins/node/services/publish.service.d.ts +114 -0
  107. package/plugins/node/services/publish.service.js +371 -0
  108. package/plugins/node/services/version-plan.service.d.ts +44 -0
  109. package/plugins/node/services/version-plan.service.js +58 -0
  110. package/plugins/node/utils/npm-view.d.ts +48 -0
  111. package/plugins/node/utils/npm-view.js +71 -0
  112. package/plugins/node/utils/workspace-range.d.ts +26 -0
  113. package/plugins/node/utils/workspace-range.js +28 -0
  114. package/services/change-hash.service.d.ts +2 -2
  115. package/services/change-hash.service.js +2 -2
  116. package/services/changelog.service.d.ts +62 -51
  117. package/services/changelog.service.js +14 -11
  118. package/services/docker-publish.service.d.ts +50 -29
  119. package/services/docker-publish.service.js +43 -20
  120. package/services/exec.service.d.ts +23 -12
  121. package/services/exec.service.js +14 -9
  122. package/services/github-release.service.d.ts +44 -33
  123. package/services/github-release.service.js +13 -10
  124. package/services/import.service.d.ts +25 -14
  125. package/services/import.service.js +9 -5
  126. package/services/list.service.d.ts +62 -10
  127. package/services/list.service.js +62 -15
  128. package/services/run.service.d.ts +22 -13
  129. package/services/run.service.js +262 -223
  130. package/services/version-plan.service.d.ts +27 -4
  131. package/services/version-plan.service.js +42 -15
  132. package/services/version.service.d.ts +31 -11
  133. package/services/version.service.js +29 -13
  134. package/targets/docker.target.d.ts +53 -0
  135. package/targets/docker.target.js +40 -0
  136. package/utils/bin-path.d.ts +6 -7
  137. package/utils/bin-path.js +7 -18
  138. package/utils/branch-guard.d.ts +29 -0
  139. package/utils/branch-guard.js +31 -0
  140. package/utils/exec.d.ts +10 -0
  141. package/utils/exec.js +1 -1
  142. package/utils/logger.d.ts +1 -1
  143. package/utils/logger.js +1 -1
  144. package/utils/package-filter.d.ts +127 -7
  145. package/utils/package-filter.js +197 -16
  146. package/utils/printable-config.d.ts +1 -1
  147. package/utils/printable-config.js +1 -1
  148. package/utils/run-bin.d.ts +10 -0
  149. package/utils/run-bin.js +1 -1
  150. package/utils/run-options.d.ts +97 -0
  151. package/utils/run-options.js +81 -0
  152. package/utils/version-stamp.d.ts +1 -1
  153. package/utils/version-stamp.js +1 -1
package/core/package.d.ts CHANGED
@@ -1,15 +1,17 @@
1
- import type { RmanConfig } from '../interfaces/rman-config.interface.js';
1
+ import type { RmanApplication } from './application.js';
2
+ import type { ResolvedConfig } from './config.js';
2
3
  import { Manifest } from './manifest.js';
4
+ import type { Platform } from './plugin.js';
3
5
  import type { Repository } from './repository.js';
4
6
  import { type VersionScheme } from './version-scheme.js';
5
7
  export declare class Package {
6
8
  readonly dirname: string;
7
9
  /**
8
- * This package's identity, read through whichever `ManifestProvider` recognizes its directory.
10
+ * This package's identity, read through whichever `Plugin`'s manifest members recognizes its directory.
9
11
  *
10
12
  * There is no `json` here any more, and that is the point: `package.json` is npm's answer to
11
13
  * "where is a package's name and version written", not rman's. `manifest.raw` is still the whole
12
- * document for a command that knows its own ecosystem - `rman-node` reads `scripts` and
14
+ * document for a command that knows its own ecosystem - the `node` built-in reads `scripts` and
13
15
  * `publishConfig` off it - but the core only ever touches `name`, `version` and `private`.
14
16
  */
15
17
  manifest: Manifest;
@@ -23,9 +25,16 @@ export declare class Package {
23
25
  * whatever the ecosystem, and comparing by identity removes a lookup from every consumer.
24
26
  */
25
27
  dependencies: Package[];
26
- /** Effective rman config for this package, cascaded from the repository root, with every
27
- * `${{ ... }}` expression already evaluated. */
28
- config: RmanConfig;
28
+ /**
29
+ * Effective rman config for this package, cascaded from the repository root, with every
30
+ * `${{ ... }}` expression already evaluated.
31
+ *
32
+ * **`ResolvedConfig`, not `RmanConfig`, and that is the whole two-view split in one line.**
33
+ * `RmanConfig` is what an *author* writes, where a value may be a function; this is what a
34
+ * *reader* gets, where it has already been called. Derived from the one the author writes, so
35
+ * there is no second type to keep in step - see `Resolved`.
36
+ */
37
+ config: ResolvedConfig;
29
38
  /**
30
39
  * The repository this package belongs to - so anything holding a package can reach the whole
31
40
  * picture (its siblings, the root's config, git) without being handed it separately.
@@ -33,14 +42,54 @@ export declare class Package {
33
42
  * Assigned by `Repository.create` rather than taken as a constructor argument, and it has to be:
34
43
  * `Repository extends Package`, so a repository constructing itself runs this constructor before
35
44
  * it exists to be passed in. A repository's own is itself.
45
+ *
46
+ * **Non-enumerable, and `declare` for the same reason `parent` is** - this is the *other* half of
47
+ * why a package could not be serialized, and the older half. A `Repository` holds every package,
48
+ * so one enumerable back-reference makes `JSON.stringify(anyPackage)` a cycle whatever the tree
49
+ * edges do: measured before `parent` was touched at all, and the error even named it
50
+ * (`property 'repository' closes the circle`). Making only `parent` non-enumerable would have
51
+ * delivered a tree that still cannot be dumped, which is the whole reason the asymmetry exists.
52
+ *
53
+ * It is the same rule `Repository.app`, `_repoScope` and `_git` already follow: a back-reference
54
+ * to something that holds everything has no business in a walk of one package.
36
55
  */
37
56
  repository: Repository;
38
57
  /**
39
58
  * The package whose directory contains this one - the repository root for an ordinary member, and
40
59
  * a genuine enclosing package for one nested inside another (which `Repository.currentPackage`
41
60
  * already has to reason about). `undefined` for the root itself, which nothing contains.
61
+ *
62
+ * **Non-enumerable, and `children` is not** - the two halves of one edge, and only one of them
63
+ * can be the one a walk follows. A tree is walked downwards, so `children` has to be visible and
64
+ * `parent` must not be, or a `toEqual` diff climbs back to the root and down again. Assigned
65
+ * through `defineProperty` by `Repository._linkPackages`, the same way `Repository.app` and
66
+ * `ORIGINS` travel.
67
+ *
68
+ * **`declare`, and that is load-bearing.** A plain `readonly parent?: Package` is a *class field*
69
+ * under this target, so TypeScript emits `parent;` and every package gets an **enumerable**
70
+ * `undefined` that the later `defineProperty` only replaces on the packages that have a parent.
71
+ * Measured: `Object.keys(rootPackage)` listed `parent` while `Object.keys(pkg-a)` did not, which
72
+ * is the exact opposite of what either name suggests. `declare` emits nothing, so the property
73
+ * exists only where it is defined.
74
+ *
75
+ * **It does not make a `Package` JSON-serializable, and nothing here claims it does** - see
76
+ * `repository`, which is enumerable and points back at an object holding every package.
77
+ */
78
+ readonly parent?: Package;
79
+ /**
80
+ * The packages whose directories sit directly inside this one - **the tree edge**, in the order
81
+ * the platform that claimed this directory gave them.
82
+ *
83
+ * This is the shape discovery actually has, and it used to be flattened away: a provider was
84
+ * asked once at the repository root and returned one list, so a package nested inside another was
85
+ * only reconstructible by comparing path prefixes (which `_linkPackages` and `currentPackage`
86
+ * both did, separately). `Workspace.walk` descends, so the containment is known as it is found -
87
+ * and `Repository.packages` is now this tree flattened rather than the other way round.
88
+ *
89
+ * Empty for a leaf, and for every package in an ordinary flat monorepo - where the root's
90
+ * `children` is the whole member list.
42
91
  */
43
- parent?: Package;
92
+ readonly children: Package[];
44
93
  /**
45
94
  * How this package's versions are numbered - `pkg.versionScheme.next(pkg.version, 'minor')`.
46
95
  *
@@ -57,27 +106,106 @@ export declare class Package {
57
106
  * changed (a commit's path list). Empty when no provider recognized this directory. */
58
107
  manifestFileName: string;
59
108
  /**
60
- * **Which ecosystem this package belongs to** - `'node'` for one read by `rman-node`, from the
61
- * `ManifestProvider.name` that claimed the directory. Empty when no provider did.
109
+ * **What addresses this package**: what a `.rmanrc "[glob]"` block and `--scope`/`--ignore` match
110
+ * against, and what must be unique within the repository.
111
+ *
112
+ * Three sources, first one that answers:
113
+ *
114
+ * 1. the package's own `.rmanrc "name"` - the repository assigning one;
115
+ * 2. its platform's `manifestProvider.selector`;
116
+ * 3. the manifest's own `name`, which is the default that answer falls back to.
117
+ *
118
+ * **Not `name`, and the split is the point.** `name` is what the package calls itself, read from
119
+ * its manifest, and it is an *ecosystem's* promise that such a thing exists and identifies the
120
+ * package - npm's promise, not rman's. Selectors matched `pkg.name` until this existed, so a
121
+ * repository whose technology has no name concept had packages it could not address at all, and
122
+ * one whose names are not unique (or are import paths) could only address them badly. They
123
+ * coincide for every Node repository, which is why nothing had to change for one.
124
+ *
125
+ * **The root's is nearly unused, deliberately.** A glob never matches the root and `"[/]"` needs
126
+ * no name - the root is addressed structurally, which is the whole reason it is `/`. It still has
127
+ * one so that nothing has to special-case it.
128
+ *
129
+ * Assigned by `Repository.create`, which is what has the config; a bare `new Package(dir, app)`
130
+ * gets the manifest's name, which is sources 2 and 3 with no config to consult.
131
+ */
132
+ selector: string;
133
+ /**
134
+ * **The technology this package belongs to** - the platform whose manifest provider claimed the
135
+ * directory, or `basePlatform` when none did.
136
+ *
137
+ * Per *package*, not per repository: the question is asked per directory, so a polyglot monorepo
138
+ * can hold a `node` package beside a `cargo` one and a command sweeping `getPackages()` can tell
139
+ * them apart. It is also what carries the rest of the technology's answers - where its binaries
140
+ * live, where its scripts come from, how its releases are planned - so anything that used to walk
141
+ * four separate registries asking "is this yours?" now asks the package it already has.
142
+ */
143
+ platform: Platform;
144
+ /**
145
+ * **Which ecosystem this package belongs to** - `'node'` for one the `node` built-in read. Empty when
146
+ * no stack claimed the directory.
62
147
  *
63
148
  * The escape hatch for code that legitimately knows one technology: `if (pkg.provider === 'node')`
64
- * before reaching into `manifest.raw` for something only npm has. Per *package*, not per
65
- * repository, because `Manifest.read` asks per directory - a polyglot monorepo can hold a `node`
66
- * package beside a `cargo` one, and a command sweeping over `getPackages()` has to be able to
67
- * tell.
149
+ * before reaching into `manifest.raw` for something only npm has.
68
150
  *
69
151
  * Not a union type on purpose: the set of ecosystems is whatever the repository's `plugins`
70
152
  * contribute, so narrowing it here would mean the core listing plugins it cannot know about.
71
153
  */
72
- provider: string;
73
- constructor(dirname: string);
154
+ get provider(): string;
155
+ /**
156
+ * **Takes the application but does not keep it.** A package needs it once, to find out which
157
+ * technology claims its directory; afterwards it holds only data, so nothing that has a package
158
+ * can reach a service through it. Data down, behaviour up - the work belongs to services, and a
159
+ * piece of code holding only a package is not doing any.
160
+ *
161
+ * It also keeps the package out of every spread and serialization the application would
162
+ * otherwise be dragged into: `{...pkg}` and `pkg.manifest.raw` are both real, and an `app` field
163
+ * here would carry the whole world into them.
164
+ */
165
+ constructor(dirname: string, app: RmanApplication,
166
+ /**
167
+ * The technology that claimed this directory, when the caller already knows - which
168
+ * `Repository.create` does, because the walk that found the directory is what established it.
169
+ *
170
+ * Optional so a bare `new Package(dir, app)` still works: that is what the fixtures build, and
171
+ * it is also the honest fallback for anyone constructing a package outside a walk. It costs one
172
+ * search of the registry (`app.platformFor`), which is what *every* construction used to do.
173
+ */
174
+ platform?: Platform);
175
+ /** What this package's **platform** says addresses it, before any config is consulted - the
176
+ * manifest's own name when the platform has no opinion, and the directory when it has no name
177
+ * either (which is unique among siblings and is all there is to go on). */
178
+ platformSelector(): string;
74
179
  get basename(): string;
75
180
  get name(): string;
76
181
  get version(): string;
77
182
  get isPrivate(): boolean;
78
- /** Re-reads from disk - for a command that has just written the manifest itself and wants the
79
- * package to agree with the file again. */
183
+ /**
184
+ * Whether this is the repository's **own root package** - what `--scope /` and `.rmanrc`'s
185
+ * `"[/]"` both select.
186
+ *
187
+ * **By directory, and deliberately not by name or by identity.** The documented rule is that the
188
+ * root package is the one whose directory *is* the repository root, because a name can be
189
+ * anything - and identity (`this === repository.rootPackage`) is not equivalent either:
190
+ * `Repository extends Package` while holding a separate `rootPackage` instance for the same
191
+ * directory, so a comparison by reference answers `false` for one of the two objects that are
192
+ * both, truthfully, the root.
193
+ *
194
+ * `false` before `Repository.create` has assigned `repository` - a bare `new Package(dir, app)`
195
+ * (which the test fixtures build) belongs to no repository yet, so there is no root for it to be.
196
+ */
197
+ get isRoot(): boolean;
198
+ /**
199
+ * Re-reads from disk - for a command that has just written the manifest itself and wants the
200
+ * package to agree with the file again.
201
+ *
202
+ * Through **its own** technology's provider, like the constructor: the package already knows
203
+ * which platform claimed it, and a second opinion on a re-read was never wanted.
204
+ */
80
205
  reloadManifest(): Manifest;
81
206
  /** Writes the current manifest back through its provider. */
82
207
  writeManifest(): void;
208
+ /** The three fields a manifest read sets, in one place - so construction and `reloadManifest`
209
+ * cannot disagree about what "reading the manifest" means. */
210
+ private _readManifest;
83
211
  }
package/core/package.js CHANGED
@@ -4,11 +4,11 @@ import { semverScheme } from './version-scheme.js';
4
4
  export class Package {
5
5
  dirname;
6
6
  /**
7
- * This package's identity, read through whichever `ManifestProvider` recognizes its directory.
7
+ * This package's identity, read through whichever `Plugin`'s manifest members recognizes its directory.
8
8
  *
9
9
  * There is no `json` here any more, and that is the point: `package.json` is npm's answer to
10
10
  * "where is a package's name and version written", not rman's. `manifest.raw` is still the whole
11
- * document for a command that knows its own ecosystem - `rman-node` reads `scripts` and
11
+ * document for a command that knows its own ecosystem - the `node` built-in reads `scripts` and
12
12
  * `publishConfig` off it - but the core only ever touches `name`, `version` and `private`.
13
13
  */
14
14
  manifest;
@@ -22,24 +22,30 @@ export class Package {
22
22
  * whatever the ecosystem, and comparing by identity removes a lookup from every consumer.
23
23
  */
24
24
  dependencies = [];
25
- /** Effective rman config for this package, cascaded from the repository root, with every
26
- * `${{ ... }}` expression already evaluated. */
27
- config = {};
28
25
  /**
29
- * The repository this package belongs to - so anything holding a package can reach the whole
30
- * picture (its siblings, the root's config, git) without being handed it separately.
26
+ * Effective rman config for this package, cascaded from the repository root, with every
27
+ * `${{ ... }}` expression already evaluated.
31
28
  *
32
- * Assigned by `Repository.create` rather than taken as a constructor argument, and it has to be:
33
- * `Repository extends Package`, so a repository constructing itself runs this constructor before
34
- * it exists to be passed in. A repository's own is itself.
29
+ * **`ResolvedConfig`, not `RmanConfig`, and that is the whole two-view split in one line.**
30
+ * `RmanConfig` is what an *author* writes, where a value may be a function; this is what a
31
+ * *reader* gets, where it has already been called. Derived from the one the author writes, so
32
+ * there is no second type to keep in step - see `Resolved`.
35
33
  */
36
- repository;
34
+ config = {};
37
35
  /**
38
- * The package whose directory contains this one - the repository root for an ordinary member, and
39
- * a genuine enclosing package for one nested inside another (which `Repository.currentPackage`
40
- * already has to reason about). `undefined` for the root itself, which nothing contains.
36
+ * The packages whose directories sit directly inside this one - **the tree edge**, in the order
37
+ * the platform that claimed this directory gave them.
38
+ *
39
+ * This is the shape discovery actually has, and it used to be flattened away: a provider was
40
+ * asked once at the repository root and returned one list, so a package nested inside another was
41
+ * only reconstructible by comparing path prefixes (which `_linkPackages` and `currentPackage`
42
+ * both did, separately). `Workspace.walk` descends, so the containment is known as it is found -
43
+ * and `Repository.packages` is now this tree flattened rather than the other way round.
44
+ *
45
+ * Empty for a leaf, and for every package in an ordinary flat monorepo - where the root's
46
+ * `children` is the whole member list.
41
47
  */
42
- parent;
48
+ children = [];
43
49
  /**
44
50
  * How this package's versions are numbered - `pkg.versionScheme.next(pkg.version, 'minor')`.
45
51
  *
@@ -56,26 +62,85 @@ export class Package {
56
62
  * changed (a commit's path list). Empty when no provider recognized this directory. */
57
63
  manifestFileName;
58
64
  /**
59
- * **Which ecosystem this package belongs to** - `'node'` for one read by `rman-node`, from the
60
- * `ManifestProvider.name` that claimed the directory. Empty when no provider did.
65
+ * **What addresses this package**: what a `.rmanrc "[glob]"` block and `--scope`/`--ignore` match
66
+ * against, and what must be unique within the repository.
67
+ *
68
+ * Three sources, first one that answers:
69
+ *
70
+ * 1. the package's own `.rmanrc "name"` - the repository assigning one;
71
+ * 2. its platform's `manifestProvider.selector`;
72
+ * 3. the manifest's own `name`, which is the default that answer falls back to.
73
+ *
74
+ * **Not `name`, and the split is the point.** `name` is what the package calls itself, read from
75
+ * its manifest, and it is an *ecosystem's* promise that such a thing exists and identifies the
76
+ * package - npm's promise, not rman's. Selectors matched `pkg.name` until this existed, so a
77
+ * repository whose technology has no name concept had packages it could not address at all, and
78
+ * one whose names are not unique (or are import paths) could only address them badly. They
79
+ * coincide for every Node repository, which is why nothing had to change for one.
80
+ *
81
+ * **The root's is nearly unused, deliberately.** A glob never matches the root and `"[/]"` needs
82
+ * no name - the root is addressed structurally, which is the whole reason it is `/`. It still has
83
+ * one so that nothing has to special-case it.
84
+ *
85
+ * Assigned by `Repository.create`, which is what has the config; a bare `new Package(dir, app)`
86
+ * gets the manifest's name, which is sources 2 and 3 with no config to consult.
87
+ */
88
+ selector;
89
+ /**
90
+ * **The technology this package belongs to** - the platform whose manifest provider claimed the
91
+ * directory, or `basePlatform` when none did.
92
+ *
93
+ * Per *package*, not per repository: the question is asked per directory, so a polyglot monorepo
94
+ * can hold a `node` package beside a `cargo` one and a command sweeping `getPackages()` can tell
95
+ * them apart. It is also what carries the rest of the technology's answers - where its binaries
96
+ * live, where its scripts come from, how its releases are planned - so anything that used to walk
97
+ * four separate registries asking "is this yours?" now asks the package it already has.
98
+ */
99
+ platform;
100
+ /**
101
+ * **Which ecosystem this package belongs to** - `'node'` for one the `node` built-in read. Empty when
102
+ * no stack claimed the directory.
61
103
  *
62
104
  * The escape hatch for code that legitimately knows one technology: `if (pkg.provider === 'node')`
63
- * before reaching into `manifest.raw` for something only npm has. Per *package*, not per
64
- * repository, because `Manifest.read` asks per directory - a polyglot monorepo can hold a `node`
65
- * package beside a `cargo` one, and a command sweeping over `getPackages()` has to be able to
66
- * tell.
105
+ * before reaching into `manifest.raw` for something only npm has.
67
106
  *
68
107
  * Not a union type on purpose: the set of ecosystems is whatever the repository's `plugins`
69
108
  * contribute, so narrowing it here would mean the core listing plugins it cannot know about.
70
109
  */
71
- provider;
72
- constructor(dirname) {
110
+ get provider() {
111
+ return this.platform.name;
112
+ }
113
+ /**
114
+ * **Takes the application but does not keep it.** A package needs it once, to find out which
115
+ * technology claims its directory; afterwards it holds only data, so nothing that has a package
116
+ * can reach a service through it. Data down, behaviour up - the work belongs to services, and a
117
+ * piece of code holding only a package is not doing any.
118
+ *
119
+ * It also keeps the package out of every spread and serialization the application would
120
+ * otherwise be dragged into: `{...pkg}` and `pkg.manifest.raw` are both real, and an `app` field
121
+ * here would carry the whole world into them.
122
+ */
123
+ constructor(dirname, app,
124
+ /**
125
+ * The technology that claimed this directory, when the caller already knows - which
126
+ * `Repository.create` does, because the walk that found the directory is what established it.
127
+ *
128
+ * Optional so a bare `new Package(dir, app)` still works: that is what the fixtures build, and
129
+ * it is also the honest fallback for anyone constructing a package outside a walk. It costs one
130
+ * search of the registry (`app.platformFor`), which is what *every* construction used to do.
131
+ */
132
+ platform) {
73
133
  this.dirname = dirname;
74
- const { manifest, versionScheme, fileName, provider } = Manifest.read(dirname);
75
- this.manifest = manifest;
76
- this.versionScheme = versionScheme;
77
- this.manifestFileName = fileName ? path.join(dirname, fileName) : '';
78
- this.provider = provider;
134
+ this.platform = platform ?? app.platformFor(dirname);
135
+ this._readManifest();
136
+ this.selector = this.platformSelector();
137
+ }
138
+ /** What this package's **platform** says addresses it, before any config is consulted - the
139
+ * manifest's own name when the platform has no opinion, and the directory when it has no name
140
+ * either (which is unique among siblings and is all there is to go on). */
141
+ platformSelector() {
142
+ const asked = this.platform.manifestProvider.selector?.(this.manifest, this.dirname);
143
+ return asked || this.manifest.name || this.basename;
79
144
  }
80
145
  get basename() {
81
146
  return path.basename(this.dirname);
@@ -89,18 +154,45 @@ export class Package {
89
154
  get isPrivate() {
90
155
  return !!this.manifest.private;
91
156
  }
92
- /** Re-reads from disk - for a command that has just written the manifest itself and wants the
93
- * package to agree with the file again. */
157
+ /**
158
+ * Whether this is the repository's **own root package** - what `--scope /` and `.rmanrc`'s
159
+ * `"[/]"` both select.
160
+ *
161
+ * **By directory, and deliberately not by name or by identity.** The documented rule is that the
162
+ * root package is the one whose directory *is* the repository root, because a name can be
163
+ * anything - and identity (`this === repository.rootPackage`) is not equivalent either:
164
+ * `Repository extends Package` while holding a separate `rootPackage` instance for the same
165
+ * directory, so a comparison by reference answers `false` for one of the two objects that are
166
+ * both, truthfully, the root.
167
+ *
168
+ * `false` before `Repository.create` has assigned `repository` - a bare `new Package(dir, app)`
169
+ * (which the test fixtures build) belongs to no repository yet, so there is no root for it to be.
170
+ */
171
+ get isRoot() {
172
+ const repository = this.repository;
173
+ return !!repository && path.resolve(this.dirname) === path.resolve(repository.dirname);
174
+ }
175
+ /**
176
+ * Re-reads from disk - for a command that has just written the manifest itself and wants the
177
+ * package to agree with the file again.
178
+ *
179
+ * Through **its own** technology's provider, like the constructor: the package already knows
180
+ * which platform claimed it, and a second opinion on a re-read was never wanted.
181
+ */
94
182
  reloadManifest() {
95
- const { manifest, versionScheme, fileName, provider } = Manifest.read(this.dirname);
96
- this.manifest = manifest;
97
- this.versionScheme = versionScheme;
98
- this.manifestFileName = fileName ? path.join(this.dirname, fileName) : '';
99
- this.provider = provider;
183
+ this._readManifest();
100
184
  return this.manifest;
101
185
  }
102
186
  /** Writes the current manifest back through its provider. */
103
187
  writeManifest() {
104
- Manifest.write(this.dirname, this.manifest);
188
+ this.platform.manifestProvider.write(this.dirname, this.manifest);
189
+ }
190
+ /** The three fields a manifest read sets, in one place - so construction and `reloadManifest`
191
+ * cannot disagree about what "reading the manifest" means. */
192
+ _readManifest() {
193
+ const { manifest, versionScheme, fileName } = Manifest.read(this.platform, this.dirname);
194
+ this.manifest = manifest;
195
+ this.versionScheme = versionScheme;
196
+ this.manifestFileName = fileName ? path.join(this.dirname, fileName) : '';
105
197
  }
106
198
  }
@@ -0,0 +1,65 @@
1
+ import type { RmanConfig } from '../interfaces/rman-config.interface.js';
2
+ import type { RmanApplication } from './application.js';
3
+ import { type Platform, type Plugin } from './plugin.js';
4
+ /** The `.rmanrc` key naming plugin packages to load. */
5
+ export declare const PLUGINS_KEY = "plugins";
6
+ /**
7
+ * The repository's plugins and publish targets, registered onto `app`.
8
+ *
9
+ * **Three keys share one shape** - `plugins`, `publishTargets` and `commands` each take an
10
+ * instance or a **glob** naming `.js` modules that `export default` one, and each appends rather
11
+ * than replaces. Two of them are read here; `commands` is read by `cli.ts`, and the split is not
12
+ * arbitrary:
13
+ *
14
+ * - **`plugins` and `publishTargets` are root-level, because they have to be.** This runs inside
15
+ * `Repository.create` *before* the packages are known, since a plugin's `getWorkspace` is what
16
+ * finds them - so there are no package configs to read yet. A `plugins` entry in a package's own
17
+ * `.rmanrc` is never seen, which has always been true and is now true for the same reason.
18
+ * - **`commands` is read at any level**, after the packages exist, which is why `cli.ts` owns it:
19
+ * it already holds the whole command pipeline - the `.rman/` default, de-duplication by name,
20
+ * and the built-in shadow check. Reading it here as well would register every config-declared
21
+ * command twice, and yargs does not survive that.
22
+ *
23
+ * **Order within this function is load-bearing**: plugins first, then targets. rman's own
24
+ * `publish` builds its `--target` choices from `app.publishTargets` when its factory runs, and a
25
+ * target contributed by a plugin's `init` has to be there before the list is read.
26
+ *
27
+ * **A contribution that cannot be loaded throws**, unlike a broken `.rman/*.mjs`, which is warned
28
+ * about and skipped. The consequence differs: a skipped local command affects only itself, while a
29
+ * missing plugin silently removes commands the repository is built around - `rman publish` would
30
+ * simply not exist, and "not a known command" sends the reader looking in the wrong place.
31
+ */
32
+ export declare function loadPlugins(app: RmanApplication, rootConfig: RmanConfig): Promise<void>;
33
+ /**
34
+ * **Puts one plugin onto an application** - what `plugins` does with an entry once it has been
35
+ * found and vetted, and the only place that knows how.
36
+ *
37
+ * **A bare `Platform` is sugar**, which is what almost every entry is - a plugin that provides one
38
+ * technology and nothing else. Normalized here, so nothing downstream deals in two shapes.
39
+ *
40
+ * **Exported because a spec must not reimplement it.** The registration is two registries plus a
41
+ * planner assignment, and a fixture writing that out by hand is a second implementation that
42
+ * drifts - which is exactly how a fixture ends up proving the core works when it does not. A spec
43
+ * brings its own technology through this, the same door a config's does.
44
+ *
45
+ * **`init` is not called here, and that is why this returns the normalized plugin.** An `init` runs
46
+ * once, when a *config* brought the plugin in, and it may be asynchronous - so it belongs to the
47
+ * loader above rather than to a function a fixture calls synchronously while building an
48
+ * application.
49
+ */
50
+ export declare function registerPlugin(app: RmanApplication, entry: Plugin | Platform): Plugin;
51
+ /**
52
+ * A plugin's `CustomCommand`, checked the same way a `.rman/*.mjs` one is - the name it answers to
53
+ * comes from its own `command` string, since a plugin has no file name to fall back on.
54
+ *
55
+ * Exported because `cli.ts` runs the identical checks on what a *declarative* command's factory
56
+ * returns, and they must not drift: a plugin written in JavaScript reaches both forms with no type
57
+ * checker in the way.
58
+ */
59
+ export declare function checkCustomCommand<T extends {
60
+ command?: string;
61
+ describe?: unknown;
62
+ handler?: unknown;
63
+ }>(command: T, pluginName: string): T & {
64
+ command: string;
65
+ };