rman 1.3.0 → 2.0.0-beta.3

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 (187) hide show
  1. package/README.md +65 -31
  2. package/builtins/platforms/node/augmentation/rmanrc.augmentation.d.ts +83 -0
  3. package/builtins/platforms/node/augmentation/system-info.augmentation.d.ts +26 -0
  4. package/builtins/platforms/node/augmentation/system-info.augmentation.js +79 -0
  5. package/builtins/platforms/node/commands/ci.command.d.ts +133 -0
  6. package/builtins/platforms/node/commands/ci.command.js +60 -0
  7. package/builtins/platforms/node/commands/clean.command.d.ts +185 -0
  8. package/builtins/platforms/node/commands/clean.command.js +74 -0
  9. package/builtins/platforms/node/index.d.ts +8 -0
  10. package/builtins/platforms/node/index.js +8 -0
  11. package/builtins/platforms/node/node-config.interface.d.ts +76 -0
  12. package/builtins/platforms/node/node-config.interface.js +7 -0
  13. package/builtins/platforms/node/node-manifest.provider.d.ts +68 -0
  14. package/builtins/platforms/node/node-manifest.provider.js +125 -0
  15. package/builtins/platforms/node/node.platform.d.ts +53 -0
  16. package/builtins/platforms/node/node.platform.js +134 -0
  17. package/builtins/platforms/node/services/ci.service.d.ts +47 -0
  18. package/builtins/platforms/node/services/ci.service.js +213 -0
  19. package/builtins/platforms/node/services/clean.service.d.ts +53 -0
  20. package/builtins/platforms/node/services/clean.service.js +237 -0
  21. package/builtins/platforms/node/services/publish.service.d.ts +114 -0
  22. package/builtins/platforms/node/services/publish.service.js +371 -0
  23. package/builtins/platforms/node/services/version-plan.service.d.ts +44 -0
  24. package/builtins/platforms/node/services/version-plan.service.js +58 -0
  25. package/builtins/platforms/node/utils/workspace-range.d.ts +26 -0
  26. package/builtins/platforms/node/utils/workspace-range.js +28 -0
  27. package/builtins/presets/index.d.ts +15 -0
  28. package/builtins/presets/index.js +57 -0
  29. package/builtins/presets/node.d.ts +16 -0
  30. package/builtins/presets/node.js +57 -0
  31. package/{services → builtins/publish-targets/docker}/docker-publish.service.d.ts +52 -31
  32. package/{services → builtins/publish-targets/docker}/docker-publish.service.js +49 -26
  33. package/builtins/publish-targets/docker/docker.target.d.ts +54 -0
  34. package/builtins/publish-targets/docker/docker.target.js +40 -0
  35. package/builtins/publish-targets/docker/index.d.ts +2 -0
  36. package/builtins/publish-targets/docker/index.js +2 -0
  37. package/builtins/publish-targets/npm/index.d.ts +2 -0
  38. package/builtins/publish-targets/npm/index.js +2 -0
  39. package/builtins/publish-targets/npm/npm-publish-target.d.ts +73 -0
  40. package/builtins/publish-targets/npm/npm-publish-target.js +95 -0
  41. package/builtins/publish-targets/npm/npm-view.d.ts +48 -0
  42. package/builtins/publish-targets/npm/npm-view.js +71 -0
  43. package/cli.d.ts +8 -0
  44. package/cli.js +228 -93
  45. package/commands/build.command.d.ts +177 -3
  46. package/commands/build.command.js +20 -10
  47. package/commands/changelog.command.d.ts +296 -3
  48. package/commands/changelog.command.js +127 -43
  49. package/commands/config.command.d.ts +44 -3
  50. package/commands/config.command.js +53 -24
  51. package/commands/diff.command.d.ts +37 -3
  52. package/commands/diff.command.js +25 -16
  53. package/commands/exec.command.d.ts +193 -3
  54. package/commands/exec.command.js +60 -58
  55. package/commands/github-release.command.d.ts +154 -3
  56. package/commands/github-release.command.js +67 -37
  57. package/commands/import.command.d.ts +36 -3
  58. package/commands/import.command.js +28 -20
  59. package/commands/info.command.d.ts +35 -7
  60. package/commands/info.command.js +36 -30
  61. package/commands/lint.command.d.ts +176 -0
  62. package/commands/lint.command.js +34 -0
  63. package/commands/list.command.d.ts +163 -3
  64. package/commands/list.command.js +109 -71
  65. package/commands/publish.command.d.ts +231 -0
  66. package/commands/publish.command.js +304 -0
  67. package/commands/run.command.d.ts +186 -6
  68. package/commands/run.command.js +26 -72
  69. package/commands/test.command.d.ts +173 -3
  70. package/commands/test.command.js +16 -10
  71. package/commands/version.command.d.ts +330 -3
  72. package/commands/version.command.js +292 -78
  73. package/commands.d.ts +32 -0
  74. package/commands.js +28 -0
  75. package/constants.js +1 -1
  76. package/core/application.d.ts +116 -0
  77. package/core/application.js +160 -0
  78. package/core/classes/package.d.ts +219 -0
  79. package/core/classes/package.js +214 -0
  80. package/core/classes/registry.d.ts +30 -0
  81. package/core/classes/registry.js +47 -0
  82. package/core/{repository.d.ts → classes/repository.d.ts} +69 -20
  83. package/core/{repository.js → classes/repository.js} +195 -75
  84. package/core/classes/service.d.ts +49 -0
  85. package/core/classes/service.js +40 -0
  86. package/core/{version-scheme.d.ts → classes/version-scheme.d.ts} +23 -1
  87. package/core/{version-scheme.js → classes/version-scheme.js} +29 -1
  88. package/core/classes/workspace.d.ts +275 -0
  89. package/core/classes/workspace.js +537 -0
  90. package/core/command-builder.d.ts +14 -0
  91. package/core/command-builder.js +94 -0
  92. package/core/config/config-file-scope.d.ts +107 -0
  93. package/core/config/config-file-scope.js +226 -0
  94. package/core/config/config-interpolator.d.ts +164 -0
  95. package/core/config/config-interpolator.js +577 -0
  96. package/core/config/config-paths.d.ts +65 -0
  97. package/core/config/config-paths.js +78 -0
  98. package/core/config/config-reader.d.ts +192 -0
  99. package/core/config/config-reader.js +727 -0
  100. package/core/{merge-config.d.ts → config/merge-config.d.ts} +33 -34
  101. package/core/config/merge-config.js +260 -0
  102. package/core/{resolve-target.d.ts → config/resolve-config-target.d.ts} +1 -1
  103. package/core/{resolve-target.js → config/resolve-config-target.js} +1 -1
  104. package/core/{custom-command.d.ts → interfaces/custom-command.d.ts} +46 -10
  105. package/core/{custom-command.js → interfaces/custom-command.js} +44 -17
  106. package/core/{manifest.d.ts → interfaces/manifest.d.ts} +49 -27
  107. package/core/interfaces/manifest.js +132 -0
  108. package/core/interfaces/plugin.d.ts +174 -0
  109. package/core/interfaces/plugin.js +124 -0
  110. package/core/interfaces/publish-target.d.ts +136 -0
  111. package/core/interfaces/publish-target.js +43 -0
  112. package/core/{run-step.d.ts → interfaces/run-step.d.ts} +4 -4
  113. package/core/interfaces/run-step.js +1 -0
  114. package/core/plugin-loader.d.ts +25 -0
  115. package/core/plugin-loader.js +53 -0
  116. package/index.d.ts +122 -24
  117. package/index.js +66 -16
  118. package/interfaces/config-scope.interface.d.ts +223 -0
  119. package/interfaces/config-scope.interface.js +1 -0
  120. package/interfaces/rman-config.interface.d.ts +821 -203
  121. package/interfaces/rman-config.interface.js +78 -1
  122. package/package.json +3 -1
  123. package/services/change-hash.service.d.ts +42 -8
  124. package/services/change-hash.service.js +92 -13
  125. package/services/changelog.service.d.ts +91 -52
  126. package/services/changelog.service.js +460 -74
  127. package/services/config-service.d.ts +8 -0
  128. package/services/config-service.js +3 -0
  129. package/services/exec.service.d.ts +23 -12
  130. package/services/exec.service.js +14 -9
  131. package/services/github-release.service.d.ts +45 -34
  132. package/services/github-release.service.js +15 -12
  133. package/services/import.service.d.ts +25 -14
  134. package/services/import.service.js +9 -5
  135. package/services/list.service.d.ts +67 -13
  136. package/services/list.service.js +62 -15
  137. package/services/run.service.d.ts +35 -22
  138. package/services/run.service.js +316 -233
  139. package/services/system-info.d.ts +1 -1
  140. package/services/version-plan.service.d.ts +35 -8
  141. package/services/version-plan.service.js +96 -36
  142. package/services/version.service.d.ts +34 -14
  143. package/services/version.service.js +35 -16
  144. package/services.d.ts +1 -1
  145. package/services.js +1 -1
  146. package/utils/bin-path.d.ts +6 -7
  147. package/utils/bin-path.js +7 -18
  148. package/utils/branch-guard.d.ts +30 -1
  149. package/utils/branch-guard.js +31 -0
  150. package/utils/color-yaml.d.ts +8 -0
  151. package/utils/color-yaml.js +86 -0
  152. package/utils/exec.d.ts +10 -0
  153. package/utils/exec.js +1 -1
  154. package/utils/git.d.ts +26 -0
  155. package/utils/git.js +65 -0
  156. package/utils/logger.d.ts +2 -2
  157. package/utils/logger.js +1 -1
  158. package/utils/package-filter.d.ts +135 -8
  159. package/utils/package-filter.js +204 -16
  160. package/utils/printable-config.d.ts +9 -1
  161. package/utils/printable-config.js +46 -1
  162. package/utils/progress-panel.d.ts +9 -0
  163. package/utils/progress-panel.js +23 -1
  164. package/utils/release-version.d.ts +1 -1
  165. package/utils/run-bin.d.ts +10 -0
  166. package/utils/run-bin.js +1 -1
  167. package/utils/run-options.d.ts +97 -0
  168. package/utils/run-options.js +81 -0
  169. package/utils/version-group.d.ts +31 -0
  170. package/utils/version-group.js +43 -0
  171. package/utils/version-stamp.d.ts +1 -1
  172. package/utils/version-stamp.js +1 -1
  173. package/commands/changed.command.d.ts +0 -3
  174. package/commands/changed.command.js +0 -29
  175. package/core/config.d.ts +0 -409
  176. package/core/config.js +0 -939
  177. package/core/extends-config.d.ts +0 -27
  178. package/core/extends-config.js +0 -89
  179. package/core/manifest.js +0 -150
  180. package/core/merge-config.js +0 -219
  181. package/core/package.d.ts +0 -83
  182. package/core/package.js +0 -106
  183. package/core/plugin.d.ts +0 -112
  184. package/core/plugin.js +0 -189
  185. package/core/workspace.d.ts +0 -68
  186. package/core/workspace.js +0 -83
  187. /package/{core/run-step.js → builtins/platforms/node/augmentation/rmanrc.augmentation.js} +0 -0
@@ -0,0 +1,160 @@
1
+ import { DockerPublishService, dockerPublishTarget } from '../builtins/publish-targets/docker/index.js';
2
+ import { ChangelogService } from '../services/changelog.service.js';
3
+ import { ConfigService } from '../services/config-service.js';
4
+ import { ExecService } from '../services/exec.service.js';
5
+ import { GithubReleaseService } from '../services/github-release.service.js';
6
+ import { ImportService } from '../services/import.service.js';
7
+ import { ListService } from '../services/list.service.js';
8
+ import { RunService } from '../services/run.service.js';
9
+ import { VersionService } from '../services/version.service.js';
10
+ import { Logger } from '../utils/logger.js';
11
+ import { Registry } from './classes/registry.js';
12
+ import { basePlatform } from './interfaces/plugin.js';
13
+ /**
14
+ * **One rman invocation, and everything it holds.** Created before anything else, handed to every
15
+ * plugin's `init`, and the owner of every registry and service that used to be a module-level
16
+ * variable.
17
+ *
18
+ * The point is that nothing is process-global any more. `Manifest`, `Workspace`, `BinPath`,
19
+ * `RunService` and `VersionPlanService` each kept their contributions in a module-scope array, so
20
+ * two repositories in one process shared them - which the test suite could only survive with a
21
+ * root hook emptying five of them before every test, and the failure that hook prevented was:
22
+ * whichever spec ran first decided the answer for the rest, and the core appeared to work in tests
23
+ * that had registered nothing. An application starts empty and is thrown away whole, so the hook
24
+ * is gone.
25
+ */
26
+ export class RmanApplication {
27
+ /**
28
+ * The **technologies** this invocation knows about, in `plugins` declaration order.
29
+ *
30
+ * One registry rather than the four it replaces (`manifest`, `workspace`, `binPaths`,
31
+ * `runSteps`): a technology is a whole, and declaring part of one was never meaningful - see
32
+ * `Platform`.
33
+ *
34
+ * **Flattened out of the plugins**, because this is what every seam actually iterates - a
35
+ * manifest is read by a platform, a workspace is laid out by a platform, a PATH is contributed by
36
+ * a platform. A plugin providing two of them puts two entries here, and a plugin providing none
37
+ * puts none.
38
+ */
39
+ platforms = new Registry();
40
+ /**
41
+ * The **plugins** themselves, in declaration order - what a config named, before the platforms
42
+ * were taken out of them.
43
+ *
44
+ * Kept apart from `platforms` because the two answer different questions: this is who contributed,
45
+ * that is what they contributed. `init` runs per plugin; nothing else reads this.
46
+ */
47
+ plugins = new Registry();
48
+ /**
49
+ * Where a package's artifact can ship, in registration order.
50
+ *
51
+ * A registry rather than a field because the answer is a *sum*: a package may ship to npm and
52
+ * Docker Hub at once, and `publish` runs every target a package declares. The core contributes
53
+ * `docker` (nobody's ecosystem), the `node` built-in contributes `npm`, and a plugin for any other
54
+ * technology adds its own without either of them changing - see `PublishTarget`.
55
+ */
56
+ publishTargets = new Registry();
57
+ /**
58
+ * **The planner that orchestrates a run**, not the one that answers for each package.
59
+ *
60
+ * One answer, not a sum - so a field rather than a registry, and last registration wins. What it
61
+ * decides is the shape of the whole plan: groups, the commit→size reading, the cross-group ripple
62
+ * and the root's release identity, none of which belongs to any one technology.
63
+ *
64
+ * The two decisions that *are* a technology's - `detectBoundary` and `cascade` - are asked of
65
+ * each package's own `Platform.versionPlanner` instead (`VersionPlanService.plannerFor`), so a
66
+ * polyglot repository no longer resolves both through whichever plugin registered last.
67
+ */
68
+ versionPlanner;
69
+ logger;
70
+ /** `info` until `--log-level` or `.rmanrc "logLevel"` is resolved - which cannot happen here,
71
+ * since reading the config is itself work the application does. */
72
+ constructor(options) {
73
+ this.logger = new Logger(options?.logLevel ?? 'info');
74
+ // Register core services.
75
+ this.setService('config', a => new ConfigService(a));
76
+ this.setService('changelog', a => new ChangelogService(a));
77
+ this.setService('dockerPublish', a => new DockerPublishService(a));
78
+ this.setService('githubRelease', a => new GithubReleaseService(a));
79
+ this.setService('exec', a => new ExecService(a));
80
+ this.setService('import', a => new ImportService(a));
81
+ this.setService('list', a => new ListService(a));
82
+ this.setService('run', a => new RunService(a));
83
+ this.setService('version', a => new VersionService(a));
84
+ // Register core targets.
85
+ this.publishTargets.add(dockerPublishTarget);
86
+ }
87
+ /** Which platform claims a directory - the first whose manifest provider recognizes it, because
88
+ * before a package is read there is nothing else to go on. `basePlatform` when none does, so the
89
+ * caller needs no guard. */
90
+ platformFor(dir) {
91
+ return this.platforms.first(p => (p.manifestProvider.read(dir) ? p : undefined)) ?? basePlatform;
92
+ }
93
+ /**
94
+ * **Not a constructor field, and that is forced by the order things happen in.** Plugins are what
95
+ * *find* the packages - `Repository.create` loads them before it calls `Workspace.resolve` - so
96
+ * the application has to exist, and be handed to every `init`, while there is still no
97
+ * repository to put in it.
98
+ *
99
+ * A throwing getter rather than `undefined`: a plugin reaching for packages during `init` has
100
+ * made a real mistake, and `undefined` would let it write a check that silently does nothing.
101
+ */
102
+ get repository() {
103
+ if (!this._repository) {
104
+ throw new Error('The repository is not available yet - plugins are loaded before the packages they are ' +
105
+ "what finds. Ask for it from a command or a service, not from a plugin's init().");
106
+ }
107
+ return this._repository;
108
+ }
109
+ /**
110
+ * The single instance of a service, built on first use.
111
+ *
112
+ * **Lazy, and for two reasons.** `rman info` has no business constructing the changelog, version
113
+ * and release services, which is the same laziness the config scope's `git` getter was measured
114
+ * to need (0 git reads with property descriptors, 1 with a spread). And services call each other -
115
+ * `VersionService` reaches for `RunService` and `ChangelogService`, which reaches for
116
+ * `ChangeHashService` - so resolving at call time is what keeps that from being a construction
117
+ * cycle.
118
+ */
119
+ getService(name) {
120
+ const existing = this.services.get(name);
121
+ if (existing)
122
+ return existing;
123
+ const factory = this.factories.get(name);
124
+ if (!factory)
125
+ throw new Error(`No service registered under "${String(name)}"`);
126
+ const service = factory(this);
127
+ this.services.set(name, service);
128
+ return service;
129
+ }
130
+ /**
131
+ * Registers how a service is built. A plugin replacing one of the core's - a version planner that
132
+ * knows its own ecosystem's dependency ranges - does it here, which is why that no longer needs a
133
+ * seam of its own on the plugin interface.
134
+ *
135
+ * Refused once the service has been built: something is already holding the old instance, and
136
+ * swapping the factory then would leave two answers to one question in play.
137
+ */
138
+ setService(name, factory) {
139
+ if (this.services.has(name)) {
140
+ throw new Error(`Service "${String(name)}" has already been built and cannot be replaced`);
141
+ }
142
+ this.factories.set(name, factory);
143
+ }
144
+ /**
145
+ * Called by `Repository.create`, after the plugins that find the packages have run.
146
+ *
147
+ * **One application, one repository**, and a second is refused. It was briefly "last one wins",
148
+ * because a single shared application made two repositories in one test collide; each
149
+ * `Repository.create` makes its own now, so the rule holds again and nothing in a process is
150
+ * shared by accident.
151
+ */
152
+ attachRepository(repository) {
153
+ if (this._repository)
154
+ throw new Error('This application already has a repository');
155
+ this._repository = repository;
156
+ }
157
+ _repository;
158
+ services = new Map();
159
+ factories = new Map();
160
+ }
@@ -0,0 +1,219 @@
1
+ import type { ResolvedConfig, RmanConfig } from '../../interfaces/rman-config.interface.js';
2
+ import type { RmanApplication } from '../application.js';
3
+ import { Manifest } from '../interfaces/manifest.js';
4
+ import type { Platform } from '../interfaces/plugin.js';
5
+ import type { Repository } from './repository.js';
6
+ import { type VersionScheme } from './version-scheme.js';
7
+ export declare class Package {
8
+ readonly dirname: string;
9
+ /**
10
+ * This package's identity, read through whichever `Plugin`'s manifest members recognizes its directory.
11
+ *
12
+ * There is no `json` here any more, and that is the point: `package.json` is npm's answer to
13
+ * "where is a package's name and version written", not rman's. `manifest.raw` is still the whole
14
+ * document for a command that knows its own ecosystem - the `node` built-in reads `scripts` and
15
+ * `publishConfig` off it - but the core only ever touches `name`, `version` and `private`.
16
+ */
17
+ manifest: Manifest;
18
+ /**
19
+ * In-repo packages this one depends on - **the full transitive closure**, not just its direct
20
+ * dependencies (see `Repository._updateDependencies`).
21
+ *
22
+ * References, not names: a name is only an identifier if the ecosystem guarantees uniqueness,
23
+ * which npm does and others do not - Go identifies a module by import path, and nothing stops a
24
+ * repository from holding two packages whose short names collide. A `Package` is unambiguous
25
+ * whatever the ecosystem, and comparing by identity removes a lookup from every consumer.
26
+ */
27
+ dependencies: Package[];
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;
38
+ /**
39
+ * The same config **before** interpolation - every `${{ ... }}` and every value function still as
40
+ * the author wrote it.
41
+ *
42
+ * `Workspace` produces this when it cascades the directory chain; `Repository` evaluates it into
43
+ * {@link config}. Read `config` unless you specifically want the unevaluated text.
44
+ */
45
+ rawConfig: RmanConfig;
46
+ /**
47
+ * The repository this package belongs to - so anything holding a package can reach the whole
48
+ * picture (its siblings, the root's config, git) without being handed it separately.
49
+ *
50
+ * Assigned by `Repository.create` rather than taken as a constructor argument, and it has to be:
51
+ * `Repository extends Package`, so a repository constructing itself runs this constructor before
52
+ * it exists to be passed in. A repository's own is itself.
53
+ *
54
+ * **Non-enumerable, and `declare` for the same reason `parent` is** - this is the *other* half of
55
+ * why a package could not be serialized, and the older half. A `Repository` holds every package,
56
+ * so one enumerable back-reference makes `JSON.stringify(anyPackage)` a cycle whatever the tree
57
+ * edges do: measured before `parent` was touched at all, and the error even named it
58
+ * (`property 'repository' closes the circle`). Making only `parent` non-enumerable would have
59
+ * delivered a tree that still cannot be dumped, which is the whole reason the asymmetry exists.
60
+ *
61
+ * It is the same rule `Repository.app`, `_repoScope` and `_git` already follow: a back-reference
62
+ * to something that holds everything has no business in a walk of one package.
63
+ */
64
+ repository: Repository;
65
+ /**
66
+ * The package whose directory contains this one - the repository root for an ordinary member, and
67
+ * a genuine enclosing package for one nested inside another (which `Repository.currentPackage`
68
+ * already has to reason about). `undefined` for the root itself, which nothing contains.
69
+ *
70
+ * **Non-enumerable, and `children` is not** - the two halves of one edge, and only one of them
71
+ * can be the one a walk follows. A tree is walked downwards, so `children` has to be visible and
72
+ * `parent` must not be, or a `toEqual` diff climbs back to the root and down again. Assigned
73
+ * through `defineProperty` by `Repository._linkPackages`, the same way `Repository.app` and
74
+ * `ORIGINS` travel.
75
+ *
76
+ * **`declare`, and that is load-bearing.** A plain `readonly parent?: Package` is a *class field*
77
+ * under this target, so TypeScript emits `parent;` and every package gets an **enumerable**
78
+ * `undefined` that the later `defineProperty` only replaces on the packages that have a parent.
79
+ * Measured: `Object.keys(rootPackage)` listed `parent` while `Object.keys(pkg-a)` did not, which
80
+ * is the exact opposite of what either name suggests. `declare` emits nothing, so the property
81
+ * exists only where it is defined.
82
+ *
83
+ * **It does not make a `Package` JSON-serializable, and nothing here claims it does** - see
84
+ * `repository`, which is enumerable and points back at an object holding every package.
85
+ */
86
+ readonly parent?: Package;
87
+ /**
88
+ * The packages whose directories sit directly inside this one - **the tree edge**, in the order
89
+ * the platform that claimed this directory gave them.
90
+ *
91
+ * This is the shape discovery actually has, and it used to be flattened away: a provider was
92
+ * asked once at the repository root and returned one list, so a package nested inside another was
93
+ * only reconstructible by comparing path prefixes (which `_linkPackages` and `currentPackage`
94
+ * both did, separately). `Workspace.walk` descends, so the containment is known as it is found -
95
+ * and `Repository.packages` is now this tree flattened rather than the other way round.
96
+ *
97
+ * Empty for a leaf, and for every package in an ordinary flat monorepo - where the root's
98
+ * `children` is the whole member list.
99
+ */
100
+ readonly children: Package[];
101
+ /**
102
+ * How this package's versions are numbered - `pkg.versionScheme.next(pkg.version, 'minor')`.
103
+ *
104
+ * Comes from the same provider that read the manifest, because the ecosystem that decides where
105
+ * a version is written is the one that decides how it is numbered - a `pyproject.toml` read with
106
+ * semver's arithmetic would be a pair that never occurs in reality.
107
+ *
108
+ * Defaults to semver, so this seam existing changes nothing. **A group must not mix schemes** -
109
+ * a group is one version line, and comparing across schemes is meaningless; `assertOneScheme`
110
+ * refuses it rather than acting on whatever falls out.
111
+ */
112
+ versionScheme: VersionScheme;
113
+ /** The file the manifest was read from, absolute - for a command that has to say which file it
114
+ * changed (a commit's path list). Empty when no provider recognized this directory. */
115
+ manifestFileName: string;
116
+ /**
117
+ * **What addresses this package**: what a `.rmanrc "[glob]"` block and `--scope`/`--ignore` match
118
+ * against, and what must be unique within the repository.
119
+ *
120
+ * Three sources, first one that answers:
121
+ *
122
+ * 1. the package's own `.rmanrc "name"` - the repository assigning one;
123
+ * 2. its platform's `manifestProvider.selector`;
124
+ * 3. the manifest's own `name`, which is the default that answer falls back to.
125
+ *
126
+ * **Not `name`, and the split is the point.** `name` is what the package calls itself, read from
127
+ * its manifest, and it is an *ecosystem's* promise that such a thing exists and identifies the
128
+ * package - npm's promise, not rman's. Selectors matched `pkg.name` until this existed, so a
129
+ * repository whose technology has no name concept had packages it could not address at all, and
130
+ * one whose names are not unique (or are import paths) could only address them badly. They
131
+ * coincide for every Node repository, which is why nothing had to change for one.
132
+ *
133
+ * **The root's is nearly unused, deliberately.** A glob never matches the root and `"[/]"` needs
134
+ * no name - the root is addressed structurally, which is the whole reason it is `/`. It still has
135
+ * one so that nothing has to special-case it.
136
+ *
137
+ * Assigned by `Repository.create`, which is what has the config; a bare `new Package(dir, app)`
138
+ * gets the manifest's name, which is sources 2 and 3 with no config to consult.
139
+ */
140
+ selector: string;
141
+ /**
142
+ * **The technology this package belongs to** - the platform whose manifest provider claimed the
143
+ * directory, or `basePlatform` when none did.
144
+ *
145
+ * Per *package*, not per repository: the question is asked per directory, so a polyglot monorepo
146
+ * can hold a `node` package beside a `cargo` one and a command sweeping `getPackages()` can tell
147
+ * them apart. It is also what carries the rest of the technology's answers - where its binaries
148
+ * live, where its scripts come from, how its releases are planned - so anything that used to walk
149
+ * four separate registries asking "is this yours?" now asks the package it already has.
150
+ */
151
+ platform: Platform;
152
+ /**
153
+ * **Which ecosystem this package belongs to** - `'node'` for one the `node` built-in read. Empty when
154
+ * no stack claimed the directory.
155
+ *
156
+ * The escape hatch for code that legitimately knows one technology: `if (pkg.provider === 'node')`
157
+ * before reaching into `manifest.raw` for something only npm has.
158
+ *
159
+ * Not a union type on purpose: the set of ecosystems is whatever the repository's `plugins`
160
+ * contribute, so narrowing it here would mean the core listing plugins it cannot know about.
161
+ */
162
+ get provider(): string;
163
+ /**
164
+ * **Takes the application but does not keep it.** A package needs it once, to find out which
165
+ * technology claims its directory; afterwards it holds only data, so nothing that has a package
166
+ * can reach a service through it. Data down, behaviour up - the work belongs to services, and a
167
+ * piece of code holding only a package is not doing any.
168
+ *
169
+ * It also keeps the package out of every spread and serialization the application would
170
+ * otherwise be dragged into: `{...pkg}` and `pkg.manifest.raw` are both real, and an `app` field
171
+ * here would carry the whole world into them.
172
+ */
173
+ constructor(dirname: string, app: RmanApplication,
174
+ /**
175
+ * The technology that claimed this directory, when the caller already knows - which
176
+ * `Repository.create` does, because the walk that found the directory is what established it.
177
+ *
178
+ * Optional so a bare `new Package(dir, app)` still works: that is what the fixtures build, and
179
+ * it is also the honest fallback for anyone constructing a package outside a walk. It costs one
180
+ * search of the registry (`app.platformFor`), which is what *every* construction used to do.
181
+ */
182
+ platform?: Platform);
183
+ /** What this package's **platform** says addresses it, before any config is consulted - the
184
+ * manifest's own name when the platform has no opinion, and the directory when it has no name
185
+ * either (which is unique among siblings and is all there is to go on). */
186
+ platformSelector(): string;
187
+ get basename(): string;
188
+ get name(): string;
189
+ get version(): string;
190
+ get isPrivate(): boolean;
191
+ /**
192
+ * Whether this is the repository's **own root package** - what `--scope /` and `.rmanrc`'s
193
+ * `"[/]"` both select.
194
+ *
195
+ * **By directory, and deliberately not by name or by identity.** The documented rule is that the
196
+ * root package is the one whose directory *is* the repository root, because a name can be
197
+ * anything - and identity (`this === repository.rootPackage`) is not equivalent either:
198
+ * `Repository extends Package` while holding a separate `rootPackage` instance for the same
199
+ * directory, so a comparison by reference answers `false` for one of the two objects that are
200
+ * both, truthfully, the root.
201
+ *
202
+ * `false` before `Repository.create` has assigned `repository` - a bare `new Package(dir, app)`
203
+ * (which the test fixtures build) belongs to no repository yet, so there is no root for it to be.
204
+ */
205
+ get isRoot(): boolean;
206
+ /**
207
+ * Re-reads from disk - for a command that has just written the manifest itself and wants the
208
+ * package to agree with the file again.
209
+ *
210
+ * Through **its own** technology's provider, like the constructor: the package already knows
211
+ * which platform claimed it, and a second opinion on a re-read was never wanted.
212
+ */
213
+ reloadManifest(): Manifest;
214
+ /** Writes the current manifest back through its provider. */
215
+ writeManifest(): void;
216
+ /** The three fields a manifest read sets, in one place - so construction and `reloadManifest`
217
+ * cannot disagree about what "reading the manifest" means. */
218
+ private _readManifest;
219
+ }
@@ -0,0 +1,214 @@
1
+ import path from 'path';
2
+ import { Manifest } from '../interfaces/manifest.js';
3
+ import { semverScheme } from './version-scheme.js';
4
+ export class Package {
5
+ dirname;
6
+ /**
7
+ * This package's identity, read through whichever `Plugin`'s manifest members recognizes its directory.
8
+ *
9
+ * There is no `json` here any more, and that is the point: `package.json` is npm's answer to
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 - the `node` built-in reads `scripts` and
12
+ * `publishConfig` off it - but the core only ever touches `name`, `version` and `private`.
13
+ */
14
+ manifest;
15
+ /**
16
+ * In-repo packages this one depends on - **the full transitive closure**, not just its direct
17
+ * dependencies (see `Repository._updateDependencies`).
18
+ *
19
+ * References, not names: a name is only an identifier if the ecosystem guarantees uniqueness,
20
+ * which npm does and others do not - Go identifies a module by import path, and nothing stops a
21
+ * repository from holding two packages whose short names collide. A `Package` is unambiguous
22
+ * whatever the ecosystem, and comparing by identity removes a lookup from every consumer.
23
+ */
24
+ dependencies = [];
25
+ /**
26
+ * Effective rman config for this package, cascaded from the repository root, with every
27
+ * `${{ ... }}` expression already evaluated.
28
+ *
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`.
33
+ */
34
+ config = {};
35
+ /**
36
+ * The same config **before** interpolation - every `${{ ... }}` and every value function still as
37
+ * the author wrote it.
38
+ *
39
+ * `Workspace` produces this when it cascades the directory chain; `Repository` evaluates it into
40
+ * {@link config}. Read `config` unless you specifically want the unevaluated text.
41
+ */
42
+ /* A second field rather than a stage of the first, because the two have different *types*:
43
+ * `config` is a `ResolvedConfig` by contract and is read in a hundred places, so putting a raw
44
+ * config there for the window between the cascade and the bake would make the type say something
45
+ * untrue everywhere.
46
+ *
47
+ * There was a `rawConfig` once before and it was removed as a duplicate - `version` kept one to
48
+ * re-read its own hooks, which `DEFERRED_PATHS` made unnecessary. This is not that: it is the
49
+ * handoff between two steps that genuinely produce different things. */
50
+ rawConfig = {};
51
+ /**
52
+ * The packages whose directories sit directly inside this one - **the tree edge**, in the order
53
+ * the platform that claimed this directory gave them.
54
+ *
55
+ * This is the shape discovery actually has, and it used to be flattened away: a provider was
56
+ * asked once at the repository root and returned one list, so a package nested inside another was
57
+ * only reconstructible by comparing path prefixes (which `_linkPackages` and `currentPackage`
58
+ * both did, separately). `Workspace.walk` descends, so the containment is known as it is found -
59
+ * and `Repository.packages` is now this tree flattened rather than the other way round.
60
+ *
61
+ * Empty for a leaf, and for every package in an ordinary flat monorepo - where the root's
62
+ * `children` is the whole member list.
63
+ */
64
+ children = [];
65
+ /**
66
+ * How this package's versions are numbered - `pkg.versionScheme.next(pkg.version, 'minor')`.
67
+ *
68
+ * Comes from the same provider that read the manifest, because the ecosystem that decides where
69
+ * a version is written is the one that decides how it is numbered - a `pyproject.toml` read with
70
+ * semver's arithmetic would be a pair that never occurs in reality.
71
+ *
72
+ * Defaults to semver, so this seam existing changes nothing. **A group must not mix schemes** -
73
+ * a group is one version line, and comparing across schemes is meaningless; `assertOneScheme`
74
+ * refuses it rather than acting on whatever falls out.
75
+ */
76
+ versionScheme = semverScheme;
77
+ /** The file the manifest was read from, absolute - for a command that has to say which file it
78
+ * changed (a commit's path list). Empty when no provider recognized this directory. */
79
+ manifestFileName;
80
+ /**
81
+ * **What addresses this package**: what a `.rmanrc "[glob]"` block and `--scope`/`--ignore` match
82
+ * against, and what must be unique within the repository.
83
+ *
84
+ * Three sources, first one that answers:
85
+ *
86
+ * 1. the package's own `.rmanrc "name"` - the repository assigning one;
87
+ * 2. its platform's `manifestProvider.selector`;
88
+ * 3. the manifest's own `name`, which is the default that answer falls back to.
89
+ *
90
+ * **Not `name`, and the split is the point.** `name` is what the package calls itself, read from
91
+ * its manifest, and it is an *ecosystem's* promise that such a thing exists and identifies the
92
+ * package - npm's promise, not rman's. Selectors matched `pkg.name` until this existed, so a
93
+ * repository whose technology has no name concept had packages it could not address at all, and
94
+ * one whose names are not unique (or are import paths) could only address them badly. They
95
+ * coincide for every Node repository, which is why nothing had to change for one.
96
+ *
97
+ * **The root's is nearly unused, deliberately.** A glob never matches the root and `"[/]"` needs
98
+ * no name - the root is addressed structurally, which is the whole reason it is `/`. It still has
99
+ * one so that nothing has to special-case it.
100
+ *
101
+ * Assigned by `Repository.create`, which is what has the config; a bare `new Package(dir, app)`
102
+ * gets the manifest's name, which is sources 2 and 3 with no config to consult.
103
+ */
104
+ selector;
105
+ /**
106
+ * **The technology this package belongs to** - the platform whose manifest provider claimed the
107
+ * directory, or `basePlatform` when none did.
108
+ *
109
+ * Per *package*, not per repository: the question is asked per directory, so a polyglot monorepo
110
+ * can hold a `node` package beside a `cargo` one and a command sweeping `getPackages()` can tell
111
+ * them apart. It is also what carries the rest of the technology's answers - where its binaries
112
+ * live, where its scripts come from, how its releases are planned - so anything that used to walk
113
+ * four separate registries asking "is this yours?" now asks the package it already has.
114
+ */
115
+ platform;
116
+ /**
117
+ * **Which ecosystem this package belongs to** - `'node'` for one the `node` built-in read. Empty when
118
+ * no stack claimed the directory.
119
+ *
120
+ * The escape hatch for code that legitimately knows one technology: `if (pkg.provider === 'node')`
121
+ * before reaching into `manifest.raw` for something only npm has.
122
+ *
123
+ * Not a union type on purpose: the set of ecosystems is whatever the repository's `plugins`
124
+ * contribute, so narrowing it here would mean the core listing plugins it cannot know about.
125
+ */
126
+ get provider() {
127
+ return this.platform.name;
128
+ }
129
+ /**
130
+ * **Takes the application but does not keep it.** A package needs it once, to find out which
131
+ * technology claims its directory; afterwards it holds only data, so nothing that has a package
132
+ * can reach a service through it. Data down, behaviour up - the work belongs to services, and a
133
+ * piece of code holding only a package is not doing any.
134
+ *
135
+ * It also keeps the package out of every spread and serialization the application would
136
+ * otherwise be dragged into: `{...pkg}` and `pkg.manifest.raw` are both real, and an `app` field
137
+ * here would carry the whole world into them.
138
+ */
139
+ constructor(dirname, app,
140
+ /**
141
+ * The technology that claimed this directory, when the caller already knows - which
142
+ * `Repository.create` does, because the walk that found the directory is what established it.
143
+ *
144
+ * Optional so a bare `new Package(dir, app)` still works: that is what the fixtures build, and
145
+ * it is also the honest fallback for anyone constructing a package outside a walk. It costs one
146
+ * search of the registry (`app.platformFor`), which is what *every* construction used to do.
147
+ */
148
+ platform) {
149
+ this.dirname = dirname;
150
+ this.platform = platform ?? app.platformFor(dirname);
151
+ this._readManifest();
152
+ this.selector = this.platformSelector();
153
+ }
154
+ /** What this package's **platform** says addresses it, before any config is consulted - the
155
+ * manifest's own name when the platform has no opinion, and the directory when it has no name
156
+ * either (which is unique among siblings and is all there is to go on). */
157
+ platformSelector() {
158
+ const asked = this.platform.manifestProvider.selector?.(this.manifest, this.dirname);
159
+ return asked || this.manifest.name || this.basename;
160
+ }
161
+ get basename() {
162
+ return path.basename(this.dirname);
163
+ }
164
+ get name() {
165
+ return this.manifest.name;
166
+ }
167
+ get version() {
168
+ return this.manifest.version;
169
+ }
170
+ get isPrivate() {
171
+ return !!this.manifest.private;
172
+ }
173
+ /**
174
+ * Whether this is the repository's **own root package** - what `--scope /` and `.rmanrc`'s
175
+ * `"[/]"` both select.
176
+ *
177
+ * **By directory, and deliberately not by name or by identity.** The documented rule is that the
178
+ * root package is the one whose directory *is* the repository root, because a name can be
179
+ * anything - and identity (`this === repository.rootPackage`) is not equivalent either:
180
+ * `Repository extends Package` while holding a separate `rootPackage` instance for the same
181
+ * directory, so a comparison by reference answers `false` for one of the two objects that are
182
+ * both, truthfully, the root.
183
+ *
184
+ * `false` before `Repository.create` has assigned `repository` - a bare `new Package(dir, app)`
185
+ * (which the test fixtures build) belongs to no repository yet, so there is no root for it to be.
186
+ */
187
+ get isRoot() {
188
+ const repository = this.repository;
189
+ return !!repository && path.resolve(this.dirname) === path.resolve(repository.dirname);
190
+ }
191
+ /**
192
+ * Re-reads from disk - for a command that has just written the manifest itself and wants the
193
+ * package to agree with the file again.
194
+ *
195
+ * Through **its own** technology's provider, like the constructor: the package already knows
196
+ * which platform claimed it, and a second opinion on a re-read was never wanted.
197
+ */
198
+ reloadManifest() {
199
+ this._readManifest();
200
+ return this.manifest;
201
+ }
202
+ /** Writes the current manifest back through its provider. */
203
+ writeManifest() {
204
+ this.platform.manifestProvider.write(this.dirname, this.manifest);
205
+ }
206
+ /** The three fields a manifest read sets, in one place - so construction and `reloadManifest`
207
+ * cannot disagree about what "reading the manifest" means. */
208
+ _readManifest() {
209
+ const { manifest, versionScheme, fileName } = Manifest.read(this.platform, this.dirname);
210
+ this.manifest = manifest;
211
+ this.versionScheme = versionScheme;
212
+ this.manifestFileName = fileName ? path.join(this.dirname, fileName) : '';
213
+ }
214
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * An ordered collection of contributions, held **on the application** rather than in a module.
3
+ *
4
+ * Every registry rman had was a module-level array (`const providers: Provider[] = []` in
5
+ * `manifest.ts`, `workspace.ts`, `bin-path.ts`, `run.service.ts`), which made it process-global:
6
+ * two repositories in one process shared it. The test suite needed a root hook emptying five of
7
+ * them before every single test, and without it whichever spec ran first decided the answer for
8
+ * the rest - so the core appeared to work in tests that had registered nothing.
9
+ *
10
+ * An instance per `RmanApplication` closes the whole class: a new application starts empty, and
11
+ * nothing has to be cleaned up afterwards - the root hook is deleted.
12
+ *
13
+ * **Registry, not service.** The distinction is multiplicity: a registry is for a question whose
14
+ * answer is the *sum* of what was contributed (every provider's bin directories, the first provider
15
+ * that recognizes a directory). A question with exactly one answer is a service, replaceable
16
+ * through `RmanApplication.getService`.
17
+ */
18
+ export declare class Registry<T> implements Iterable<T> {
19
+ private readonly items;
20
+ /** Contributions in registration order, which for a plugin is `plugins` declaration order. */
21
+ get all(): readonly T[];
22
+ get size(): number;
23
+ /** Idempotent by identity: a plugin may both declare a contribution and register it itself, and
24
+ * the same function arriving twice would make the registry lie about what is in it. */
25
+ add(item: T): this;
26
+ /** The first contribution `ask` gives an answer for - "first that recognizes it" resolution,
27
+ * which is how a directory finds the manifest reader and the workspace layout that claim it. */
28
+ first<R>(ask: (item: T) => R | undefined): R | undefined;
29
+ [Symbol.iterator](): Iterator<T>;
30
+ }