rman 1.3.0 → 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 +140 -19
  40. package/core/config.js +258 -74
  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 +137 -92
  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 -44
  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 +725 -202
  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
@@ -1,4 +1,5 @@
1
1
  import path from 'node:path';
2
+ import { RmanApplication } from '../core/application.js';
2
3
  import { assertOneScheme, semverScheme, VersionScheme } from '../core/version-scheme.js';
3
4
  import { GitHelper } from '../utils/git.js';
4
5
  import { filterPackages } from '../utils/package-filter.js';
@@ -118,7 +119,7 @@ export class VersionPlanService {
118
119
  changeByPackage.set(pkg.name, { bump: undefined, reason: `explicit version ${explicitVersion}` });
119
120
  return;
120
121
  }
121
- const since = await this.detectBoundary(git, pkg, options);
122
+ const since = await this.plannerFor(pkg).detectBoundary(git, pkg, options);
122
123
  const commits = since ? await git.listCommits({ hash: since }) : await git.listAllCommits();
123
124
  const belongsToPkg = (c) => c.files.some(f => !path.relative(pkg.dirname, f).startsWith('..'));
124
125
  const real = commits.filter(c => belongsToPkg(c) && !ConventionalCommitsService.isReleaseCommit(c.subject, commitMessage));
@@ -152,6 +153,39 @@ export class VersionPlanService {
152
153
  }
153
154
  return result;
154
155
  }
156
+ /**
157
+ * **Which planner answers for one package: its own technology's.**
158
+ *
159
+ * The two abstract members above are the ecosystem's, and a repository can hold more than one.
160
+ * `app.versionPlanner` is a single slot - last registration wins - so in a polyglot repository
161
+ * both of them used to be answered by whichever plugin happened to register last: a Cargo
162
+ * package's boundary fell back to `npm view`, and its cascade assumed npm's caret ranges. That is
163
+ * the same shape of bug the hardcoded `['npm']` publish default was, and the same fix - ask the
164
+ * technology that read the manifest.
165
+ *
166
+ * Falls back to `this`, which is what a single-technology repository always gets and what a
167
+ * `Plugin` contributing no planner of its own means.
168
+ */
169
+ plannerFor(pkg) {
170
+ return pkg.platform.versionPlanner ?? this;
171
+ }
172
+ /**
173
+ * `cascade` for a whole group, which is the unit it is asked about - one answer applies to every
174
+ * member, because the group releases as one version line.
175
+ *
176
+ * **Members disagreeing take the widest answer**, and the direction is deliberate: a cascade that
177
+ * is too narrow produces a plan that silently *releases too little* (the failure `cascade`'s own
178
+ * doc calls invisible), while one that is too wide releases a package that did not strictly need
179
+ * it - visible, and harmless. A mixed-technology group is unusual; a mixed group quietly skipping
180
+ * a dependent that pins exact versions is a broken install.
181
+ */
182
+ cascadeFor(members, bump) {
183
+ const answers = new Set(members.map(m => this.plannerFor(m).cascade(bump)));
184
+ for (const widest of CASCADE_WIDEST_FIRST)
185
+ if (answers.has(widest))
186
+ return widest;
187
+ return 'changed';
188
+ }
155
189
  /**
156
190
  * The largest bump `commits` ask for, in `scheme`'s own names.
157
191
  *
@@ -246,7 +280,7 @@ export class VersionPlanService {
246
280
  to = scheme.next(current, bump, { preid });
247
281
  }
248
282
  const bumping = new Set(changed);
249
- const cascade = bump ? this.cascade(bump) : 'changed';
283
+ const cascade = bump ? this.cascadeFor(members, bump) : 'changed';
250
284
  if (cascade === 'group') {
251
285
  for (const m of members)
252
286
  bumping.add(m);
@@ -377,15 +411,6 @@ export class VersionPlanService {
377
411
  * repository layering its own policy plugin after `rman-node` could never take effect - which is
378
412
  * the only reason to name two in the first place.
379
413
  */
380
- function setPlanner(planner) {
381
- current = planner;
382
- }
383
- VersionPlanService.setPlanner = setPlanner;
384
- /** For tests, which would otherwise leak a planner into every later case in the process. */
385
- function clearPlanner() {
386
- current = undefined;
387
- }
388
- VersionPlanService.clearPlanner = clearPlanner;
389
414
  /**
390
415
  * The registered planner. **Throws** when there is none, rather than falling back to some
391
416
  * built-in default: `Manifest` and `Workspace` can degrade honestly (a package named after its
@@ -393,16 +418,18 @@ export class VersionPlanService {
393
418
  * merely a diminished one - a wrong cascade or a wrong boundary reports a release that is
394
419
  * plausible and untrue.
395
420
  */
396
- function getPlanner() {
397
- if (!current) {
421
+ function getPlanner(app) {
422
+ const planner = app.versionPlanner;
423
+ if (!planner) {
398
424
  throw new Error('No version planner is registered, so no version plan can be computed. Name a plugin that ' +
399
425
  'contributes one in .rmanrc "plugins" - "rman-node" for a Node repository.');
400
426
  }
401
- return current;
427
+ return planner;
402
428
  }
403
429
  VersionPlanService.getPlanner = getPlanner;
404
- let current;
405
430
  })(VersionPlanService || (VersionPlanService = {}));
431
+ /** Widest reach first - what `cascadeFor` walks when a group's members answer differently. */
432
+ const CASCADE_WIDEST_FIRST = ['group', 'dependents', 'changed'];
406
433
  /** What one commit says happened, with no reference to any version format - `VersionScheme.bumpFor`
407
434
  * turns it into a number's movement. Everything unrecognized is a `'fix'`: something changed, so
408
435
  * the smallest release is still warranted. */
@@ -1,17 +1,41 @@
1
1
  import type { Package } from '../core/package.js';
2
2
  import type { Repository } from '../core/repository.js';
3
3
  import type { RunStepValue } from '../core/run-step.js';
4
+ import { Service } from '../core/service.js';
4
5
  import { VersionPlanService } from './version-plan.service.js';
5
6
  /**
6
7
  * Applying a version plan: the writes. Every manifest edit, dependency-range refresh, stamp,
7
8
  * commit and tag lives here; **what** to write is `VersionPlanService`'s answer.
8
9
  *
9
10
  * Nothing here knows what a `package.json` is, names an npm script, or runs a command: the version
10
- * goes through `Manifest`/`ManifestProvider`, refreshing a sibling's dependency range goes through
11
+ * goes through `Manifest`/`Plugin`'s manifest members, refreshing a sibling's dependency range goes through
11
12
  * the same provider, and the lifecycle hooks around the write go through
12
13
  * `RunService.runLifecycleSlot` - this module only supplies the `.rmanrc version.<slot>` fallback,
13
14
  * which is its own config. What is left is git, that config, and the version stamps.
14
15
  */
16
+ /**
17
+ * A service class - see `ListService` for the shape and `Service` for the three measured
18
+ * consequences a namespace had.
19
+ *
20
+ * **Only `applyPlan` became a method**, because only it takes a repository. `buildCommitMessage`,
21
+ * `stampDockerfile`, `stampSourceFiles` and `normalizeScriptValue` take a `Package` or nothing and
22
+ * stay functions on the namespace below - the same rule that leaves `ChangeHashService` a namespace
23
+ * entirely.
24
+ *
25
+ * **Declared before the namespace**, which TypeScript requires: the other order is
26
+ * `A namespace declaration cannot be located prior to a class with which it is merged`.
27
+ */
28
+ export declare class VersionService extends Service {
29
+ /**
30
+ * Writes every `'bump'` entry's new version into its own manifest (and refreshes any other bumped
31
+ * package's dependency range on it), runs that package's own version-lifecycle hooks or its
32
+ * `.rmanrc version.before`/`.exec`/`.after` around the write - see the `hook` closure below -
33
+ * then commits and tags **once per group** - so independently-versioned groups each get their own clean commit/tag rather than one entangled
34
+ * commit spanning unrelated version lines. Pushes only when `options.push` is set - same as a
35
+ * plain `npm version`, this never reaches the network on its own otherwise.
36
+ */
37
+ applyPlan(plan: VersionPlanService.Entry[], options?: VersionService.ApplyOptions): Promise<VersionService.ApplyResult>;
38
+ }
15
39
  export declare namespace VersionService {
16
40
  interface ApplyOptions {
17
41
  /** Push the resulting commit(s) and tag(s) to the remote once applied. Default false - same
@@ -61,15 +85,6 @@ export declare namespace VersionService {
61
85
  /** True for the repository's own release tag, which belongs to no single package. */
62
86
  release?: boolean;
63
87
  }
64
- /**
65
- * Writes every `'bump'` entry's new version into its own manifest (and refreshes any other bumped
66
- * package's dependency range on it), runs that package's own version-lifecycle hooks or its
67
- * `.rmanrc version.before`/`.exec`/`.after` around the write - see the `hook` closure below -
68
- * then commits and tags **once per group** - so independently-versioned groups each get their own clean commit/tag rather than one entangled
69
- * commit spanning unrelated version lines. Pushes only when `options.push` is set - same as a
70
- * plain `npm version`, this never reaches the network on its own otherwise.
71
- */
72
- function applyPlan(repository: Repository, plan: VersionPlanService.Entry[], options?: ApplyOptions): Promise<ApplyResult>;
73
88
  /** `.rmanrc version.commitMessage` (root-level; `{version}` is replaced when every bumped package
74
89
  * in this commit shares one version) - defaults to `"chore(release): v{version}"`, or a plain
75
90
  * listing of `name@version` pairs when this particular commit spans different versions (a
@@ -119,8 +134,13 @@ export declare namespace VersionService {
119
134
  * pattern missed it) released a tagged commit with a stale constant and said nothing. The error
120
135
  * names the file and, when the package's ecosystem declared none, says so.
121
136
  *
122
- * *How* a version is declared is the provider's (`ManifestProvider.stampVersion`); *which* files
137
+ * *How* a version is declared is the provider's (`Plugin.stampVersion`); *which* files
123
138
  * hold one is the repository's, which is why the list is config and the rewrite is a seam.
124
139
  */
125
140
  function stampSourceFiles(pkg: Package, version: string): string[];
126
141
  }
142
+ declare module '../core/service.js' {
143
+ interface ServiceMap {
144
+ version: VersionService;
145
+ }
146
+ }
@@ -2,11 +2,11 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { interpolateConfig } from '../core/config.js';
4
4
  import { Manifest } from '../core/manifest.js';
5
+ import { Service } from '../core/service.js';
5
6
  import { GitHelper } from '../utils/git.js';
6
7
  import { expandReleaseTag, isCalendarVersion } from '../utils/release-version.js';
7
8
  import { stampVersionLabel } from '../utils/version-stamp.js';
8
9
  import { ChangeHashService } from './change-hash.service.js';
9
- import { ChangelogService } from './changelog.service.js';
10
10
  import { RunService } from './run.service.js';
11
11
  import { VersionPlanService } from './version-plan.service.js';
12
12
  /**
@@ -14,13 +14,24 @@ import { VersionPlanService } from './version-plan.service.js';
14
14
  * commit and tag lives here; **what** to write is `VersionPlanService`'s answer.
15
15
  *
16
16
  * Nothing here knows what a `package.json` is, names an npm script, or runs a command: the version
17
- * goes through `Manifest`/`ManifestProvider`, refreshing a sibling's dependency range goes through
17
+ * goes through `Manifest`/`Plugin`'s manifest members, refreshing a sibling's dependency range goes through
18
18
  * the same provider, and the lifecycle hooks around the write go through
19
19
  * `RunService.runLifecycleSlot` - this module only supplies the `.rmanrc version.<slot>` fallback,
20
20
  * which is its own config. What is left is git, that config, and the version stamps.
21
21
  */
22
- export var VersionService;
23
- (function (VersionService) {
22
+ /**
23
+ * A service class - see `ListService` for the shape and `Service` for the three measured
24
+ * consequences a namespace had.
25
+ *
26
+ * **Only `applyPlan` became a method**, because only it takes a repository. `buildCommitMessage`,
27
+ * `stampDockerfile`, `stampSourceFiles` and `normalizeScriptValue` take a `Package` or nothing and
28
+ * stay functions on the namespace below - the same rule that leaves `ChangeHashService` a namespace
29
+ * entirely.
30
+ *
31
+ * **Declared before the namespace**, which TypeScript requires: the other order is
32
+ * `A namespace declaration cannot be located prior to a class with which it is merged`.
33
+ */
34
+ export class VersionService extends Service {
24
35
  /**
25
36
  * Writes every `'bump'` entry's new version into its own manifest (and refreshes any other bumped
26
37
  * package's dependency range on it), runs that package's own version-lifecycle hooks or its
@@ -29,7 +40,8 @@ export var VersionService;
29
40
  * commit spanning unrelated version lines. Pushes only when `options.push` is set - same as a
30
41
  * plain `npm version`, this never reaches the network on its own otherwise.
31
42
  */
32
- async function applyPlan(repository, plan, options = {}) {
43
+ async applyPlan(plan, options = {}) {
44
+ const repository = this.repository;
33
45
  const git = new GitHelper({ cwd: repository.dirname });
34
46
  // The root's own entry is only ever a real package to write/commit like any other when this
35
47
  // *isn't* a monorepo (see `getPlan`) - in a monorepo it's the separate, purely informational
@@ -71,17 +83,20 @@ export var VersionService;
71
83
  interpolateConfig(pkg.config?.version?.[slot], scope, { at: ['version', slot] }), `version.${slot}`));
72
84
  await hook('before');
73
85
  /** Through the manifest, not through a `package.json` field: where a version is written is
74
- * the provider's business (see `ManifestProvider`), and this is the one place rman changes
86
+ * the provider's business (see `Plugin`'s manifest members), and this is the one place rman changes
75
87
  * it. */
76
88
  pkg.manifest.version = entry.to;
77
89
  /** Which fields hold a sibling reference, and what a reference even looks like, is the
78
90
  * ecosystem's - npm's four fields and its `"workspace:"` protocol used to be spelled out
79
- * here. See `ManifestProvider.updateDependencyVersions`. */
91
+ * here. See `Plugin.updateDependencyVersions`. */
80
92
  Manifest.updateDependencyVersions(pkg, bumpedVersions);
81
93
  await hook('exec');
82
94
  pkg.writeManifest();
83
95
  // Before the `after` hook, so a script reacting to the bump sees the whole new state.
84
- const stamped = [stampDockerfile(pkg, entry.to), ...stampSourceFiles(pkg, entry.to)].filter((f) => !!f);
96
+ const stamped = [
97
+ VersionService.stampDockerfile(pkg, entry.to),
98
+ ...VersionService.stampSourceFiles(pkg, entry.to),
99
+ ].filter((f) => !!f);
85
100
  if (stamped.length) {
86
101
  stampedByPackage.set(pkg.name, stamped.map(f => path.relative(repository.dirname, f)));
87
102
  }
@@ -105,9 +120,9 @@ export var VersionService;
105
120
  for (const entry of bumped) {
106
121
  const fromTag = ChangeHashService.expandTag(entry.package, entry.from);
107
122
  const from = (await git.tagExists(fromTag)) ? fromTag : undefined;
108
- const changelogEntries = await ChangelogService.generateToFile(repository, {
123
+ const changelogEntries = await this.app.getService('changelog').generateToFile({
109
124
  scope: entry.package.name,
110
- root: true,
125
+ fromRoot: true,
111
126
  from,
112
127
  // The tag for this release doesn't exist yet (it's created below), so changelog's own
113
128
  // tag-derived version would resolve to the *previous* release and label the entry with it.
@@ -152,7 +167,7 @@ export var VersionService;
152
167
  files.push(changelogFile);
153
168
  files.push(...(stampedByPackage.get(e.package.name) ?? []));
154
169
  }
155
- const message = buildCommitMessage(repository, groupEntries, options.message);
170
+ const message = VersionService.buildCommitMessage(repository, groupEntries, options.message);
156
171
  const sha = await git.commit(files, message);
157
172
  commits.push({ sha, message, packages: groupEntries.map(e => e.package.name) });
158
173
  const tags = new Set(groupEntries.map(e => ChangeHashService.expandTag(e.package, e.to)));
@@ -182,7 +197,8 @@ export var VersionService;
182
197
  await git.push();
183
198
  return { entries: plan, updated: bumped, commits, tags: tagged, pushed };
184
199
  }
185
- VersionService.applyPlan = applyPlan;
200
+ }
201
+ (function (VersionService) {
186
202
  /** `.rmanrc version.commitMessage` (root-level; `{version}` is replaced when every bumped package
187
203
  * in this commit shares one version) - defaults to `"chore(release): v{version}"`, or a plain
188
204
  * listing of `name@version` pairs when this particular commit spans different versions (a
@@ -260,7 +276,7 @@ export var VersionService;
260
276
  * pattern missed it) released a tagged commit with a stale constant and said nothing. The error
261
277
  * names the file and, when the package's ecosystem declared none, says so.
262
278
  *
263
- * *How* a version is declared is the provider's (`ManifestProvider.stampVersion`); *which* files
279
+ * *How* a version is declared is the provider's (`Plugin.stampVersion`); *which* files
264
280
  * hold one is the repository's, which is why the list is config and the rewrite is a seam.
265
281
  */
266
282
  function stampSourceFiles(pkg, version) {
@@ -0,0 +1,53 @@
1
+ import type { ConfigValue } from '../core/config.js';
2
+ import type { PublishTarget } from '../core/publish-target.js';
3
+ import type { ScopedVars } from '../interfaces/rman-config.interface.js';
4
+ /**
5
+ * **`publish.docker.*`** - this target's own config block, declared here rather than centrally.
6
+ *
7
+ * Reaches `RmanConfig` through the `PublishTargetConfigs` slot `publish.command.ts` exports, which
8
+ * is the same slot the `node` built-in declares `publish.npm.*` in. Whoever reads a key declares it: the
9
+ * only thing that reads these is `DockerPublishService`, two files away.
10
+ *
11
+ * Required once `"docker"` is one of a package's `publish.target`s - `publish --target docker`
12
+ * errors clearly on a package that opts in and leaves this out.
13
+ */
14
+ export interface DockerPublishOptions extends DockerPublishOptionsKeys, ScopedVars {
15
+ }
16
+ /** Every key here is a **value**, so every one is a `ConfigValue` - this target declares no step,
17
+ * which is what makes the whole block uniform (see `VersionExtraKeys` for the interface where it
18
+ * is not). */
19
+ export interface DockerPublishOptionsKeys {
20
+ /** DockerHub image name/repository - bare (e.g. `"my-app"`) to be prefixed with
21
+ * `--docker-namespace`/`DOCKERHUB_NAMESPACE`, or already-namespaced (contains a `/`) to use
22
+ * verbatim. */
23
+ image: ConfigValue<string>;
24
+ /** Relative to the package's own directory. Default `"Dockerfile"`. */
25
+ dockerfile?: ConfigValue<string>;
26
+ /** Default `["linux/amd64"]`. */
27
+ platforms?: ConfigValue<string[]>;
28
+ /** Build `cwd` override, relative to the repository root - only needed when the Dockerfile's
29
+ * own `COPY`/`ADD` paths expect something other than the package's own directory (rare). */
30
+ cwd?: ConfigValue<string>;
31
+ /** Named `docker buildx build --build-context <name>=<path>` entries, keyed by name - each
32
+ * path is relative to the package's own directory (or absolute). */
33
+ buildContexts?: ConfigValue<Record<string, string>>;
34
+ /** `docker buildx build --build-arg <name>=<value>` entries - a value of exactly `"$NAME"`
35
+ * expands to `process.env.NAME` at build time (e.g. to pass a CI secret through). */
36
+ buildArgs?: ConfigValue<Record<string, string>>;
37
+ /** A file (relative to the package's own directory) whose contents become the DockerHub repo's
38
+ * full description, if present. Default `"DOCKER_README.md"`. */
39
+ readme?: ConfigValue<string>;
40
+ }
41
+ /**
42
+ * **Docker, as a publish target** - the core's own, and the reason `PublishTarget` is in the core
43
+ * rather than in a plugin.
44
+ *
45
+ * Any language's project can ship an image; nothing about `docker buildx build --push` is npm's, or
46
+ * Cargo's, or Maven's. It was already a core *service*, and could still only be reached through
47
+ * `rman-node`'s `publish` command - so a repository with no JavaScript in it had to install a Node
48
+ * plugin to push a container. This file is the ten lines that fix that.
49
+ *
50
+ * Opt-in, and therefore no `claims`: most packages in a repository are not images, so a package
51
+ * ships here only by naming `"docker"` in its own `publish.target`.
52
+ */
53
+ export declare const dockerPublishTarget: PublishTarget;
@@ -0,0 +1,40 @@
1
+ import { DOCKER_TARGET } from '../services/docker-publish.service.js';
2
+ /**
3
+ * **Docker, as a publish target** - the core's own, and the reason `PublishTarget` is in the core
4
+ * rather than in a plugin.
5
+ *
6
+ * Any language's project can ship an image; nothing about `docker buildx build --push` is npm's, or
7
+ * Cargo's, or Maven's. It was already a core *service*, and could still only be reached through
8
+ * `rman-node`'s `publish` command - so a repository with no JavaScript in it had to install a Node
9
+ * plugin to push a container. This file is the ten lines that fix that.
10
+ *
11
+ * Opt-in, and therefore no `claims`: most packages in a repository are not images, so a package
12
+ * ships here only by naming `"docker"` in its own `publish.target`.
13
+ */
14
+ export const dockerPublishTarget = {
15
+ name: DOCKER_TARGET,
16
+ describe: 'Build and push a container image (docker buildx build --push)',
17
+ options: {
18
+ dockerNamespace: {
19
+ target: 'cli',
20
+ cliName: 'docker-namespace',
21
+ describe: 'Prefixed onto a bare (no "/") "publish.docker.image" - default: the DOCKERHUB_NAMESPACE ' +
22
+ 'environment variable.',
23
+ type: 'string',
24
+ },
25
+ },
26
+ getPlan(ctx) {
27
+ return ctx.app.getService('dockerPublish').getPlan(optionsOf(ctx));
28
+ },
29
+ applyPlan(ctx, plan) {
30
+ /** The entries are this target's own - `getPlan` produced them - so the cast reads back what
31
+ * was put in rather than claiming anything new. The command only ever passes a plan back to
32
+ * the target that made it. */
33
+ return ctx.app.getService('dockerPublish').applyPlan(plan);
34
+ },
35
+ };
36
+ /** The shared filters plus the one flag this target declared - read here rather than in the service,
37
+ * so the service keeps taking a plain options object and stays callable without a CLI. */
38
+ function optionsOf(ctx) {
39
+ return { ...ctx.options, namespace: ctx.args.dockerNamespace };
40
+ }
@@ -1,3 +1,4 @@
1
+ import { RmanApplication } from '../core/application.js';
1
2
  /**
2
3
  * Where a repository's **locally installed executables** live, so a command an author wrote
3
4
  * (`eslint .`, `cargo build`) is found the way it would be in their own shell.
@@ -6,7 +7,7 @@
6
7
  *
7
8
  * - **Which directories** is the *ecosystem's* answer, and the core has none - `node_modules/.bin`
8
9
  * walked up the directory chain is npm's layout and nothing else's (a Python venv says
9
- * `.venv/bin`, a Ruby project `bin`). `rman-node` contributes it; see `RmanPlugin.binPaths`.
10
+ * `.venv/bin`, a Ruby project `bin`). `rman-node` contributes it; see `Plugin.getBinPaths`.
10
11
  * - **How a PATH is spelled** is the *operating system's*, and that stays here: the variable is
11
12
  * `PATH` everywhere except Windows, where its case is whatever the environment happens to use.
12
13
  * That has nothing to do with any ecosystem, and every provider would otherwise get it wrong
@@ -22,20 +23,18 @@ export declare namespace BinPath {
22
23
  * Return them most-specific-first; the core concatenates providers without reordering. */
23
24
  type Provider = (cwd: string) => string[];
24
25
  interface EnvOptions {
26
+ /** The application whose technologies contribute directories. Omitted where a caller genuinely
27
+ * has none - `exec` outside a repository - which leaves the inherited PATH untouched. */
28
+ readonly app?: RmanApplication;
25
29
  /** The directory the command will run in. Default `process.cwd()`. */
26
30
  readonly cwd?: string;
27
31
  /** The environment to derive from, like `process.env`. Default `process.env`. */
28
32
  readonly env?: ProcessEnv;
29
33
  }
30
- /** Registers a provider. Called by `loadPlugins` for each plugin's `binPaths`, in `plugins`
31
- * declaration order - so what is on PATH is a function of the repository's own config. */
32
- function addProvider(provider: Provider): void;
33
- /** For tests, which would otherwise leak a provider into every later case in the process. */
34
- function clearProviders(): void;
35
34
  /** Every provider's directories for `cwd`, concatenated in declaration order. Empty for a
36
35
  * repository that names no plugin - the inherited PATH then stands on its own, which is the
37
36
  * honest answer rather than a guess at some ecosystem's layout. */
38
- function resolve(cwd: string): string[];
37
+ function resolve(app: RmanApplication, cwd: string): string[];
39
38
  /**
40
39
  * `env` with the contributed directories prepended to its PATH - what `exec` and `runBin` hand to
41
40
  * a child process.
package/utils/bin-path.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import path from 'node:path';
2
2
  import process from 'node:process';
3
+ import { RmanApplication } from '../core/application.js';
3
4
  /**
4
5
  * Where a repository's **locally installed executables** live, so a command an author wrote
5
6
  * (`eslint .`, `cargo build`) is found the way it would be in their own shell.
@@ -8,7 +9,7 @@ import process from 'node:process';
8
9
  *
9
10
  * - **Which directories** is the *ecosystem's* answer, and the core has none - `node_modules/.bin`
10
11
  * walked up the directory chain is npm's layout and nothing else's (a Python venv says
11
- * `.venv/bin`, a Ruby project `bin`). `rman-node` contributes it; see `RmanPlugin.binPaths`.
12
+ * `.venv/bin`, a Ruby project `bin`). `rman-node` contributes it; see `Plugin.getBinPaths`.
12
13
  * - **How a PATH is spelled** is the *operating system's*, and that stays here: the variable is
13
14
  * `PATH` everywhere except Windows, where its case is whatever the environment happens to use.
14
15
  * That has nothing to do with any ecosystem, and every provider would otherwise get it wrong
@@ -20,25 +21,12 @@ import process from 'node:process';
20
21
  */
21
22
  export var BinPath;
22
23
  (function (BinPath) {
23
- /** Registers a provider. Called by `loadPlugins` for each plugin's `binPaths`, in `plugins`
24
- * declaration order - so what is on PATH is a function of the repository's own config. */
25
- function addProvider(provider) {
26
- if (providers.includes(provider))
27
- return;
28
- providers.push(provider);
29
- }
30
- BinPath.addProvider = addProvider;
31
- /** For tests, which would otherwise leak a provider into every later case in the process. */
32
- function clearProviders() {
33
- providers.length = 0;
34
- }
35
- BinPath.clearProviders = clearProviders;
36
24
  /** Every provider's directories for `cwd`, concatenated in declaration order. Empty for a
37
25
  * repository that names no plugin - the inherited PATH then stands on its own, which is the
38
26
  * honest answer rather than a guess at some ecosystem's layout. */
39
- function resolve(cwd) {
27
+ function resolve(app, cwd) {
40
28
  const dir = path.resolve(cwd);
41
- return providers.flatMap(provider => provider(dir));
29
+ return [...app.platforms].flatMap(stack => stack.getBinPaths?.(dir) ?? []);
42
30
  }
43
31
  BinPath.resolve = resolve;
44
32
  /**
@@ -52,7 +40,9 @@ export var BinPath;
52
40
  const cwd = options.cwd || process.cwd();
53
41
  const result = { ...(options.env || process.env) };
54
42
  const key = pathKey({ env: result });
55
- const entries = resolve(cwd);
43
+ /** No application means no technologies, so nothing is prepended and the inherited PATH stands
44
+ * on its own - the same answer a repository naming no plugin has always got. */
45
+ const entries = options.app ? resolve(options.app, cwd) : [];
56
46
  if (!entries.length)
57
47
  return result;
58
48
  const inherited = result[key];
@@ -78,5 +68,4 @@ export var BinPath;
78
68
  .find(key => key.toUpperCase() === 'PATH') || 'Path');
79
69
  }
80
70
  BinPath.pathKey = pathKey;
81
- const providers = [];
82
71
  })(BinPath || (BinPath = {}));
@@ -6,6 +6,35 @@ export interface BranchGuardOptions {
6
6
  allowBranch?: string | string[];
7
7
  ignoreBranch?: string | string[];
8
8
  }
9
+ /**
10
+ * `--allow-branch`/`--ignore-branch` as a declaration, to spread into a command's `config` block.
11
+ * See `packageFilterOptions` for why this is `satisfies` rather than an annotation.
12
+ *
13
+ * **The key is the camelCase one and `cliName` carries the flag's spelling.** `allowBranch` is what
14
+ * a `.rmanrc` writes and what `assertAllowedBranch` reads off the config; `--allow-branch` is what
15
+ * the shell sees. The `Argv` version relied on yargs converting one into the other silently, which
16
+ * is why `applyBranchGuardOptions` declares `'allow-branch'` and `readBranchGuardOptions` reads
17
+ * `args.allowBranch`.
18
+ *
19
+ * **`target: 'cli'`, even though `allowBranch` *is* a config key.** It is a repo-wide core key read
20
+ * off the root, not any command's - so it belongs on `RmanConfig` directly, and a command that
21
+ * reads it names it in `configKeys`. Declaring it `'both'` here would contribute `version.allowBranch`,
22
+ * which nothing reads.
23
+ */
24
+ export declare const branchGuardOptions: {
25
+ allowBranch: {
26
+ target: "cli";
27
+ cliName: string;
28
+ describe: string;
29
+ type: "string";
30
+ };
31
+ ignoreBranch: {
32
+ target: "cli";
33
+ cliName: string;
34
+ describe: string;
35
+ type: "string";
36
+ };
37
+ };
9
38
  /** `--allow-branch`/`--ignore-branch`, the same shape and describe text in every command that
10
39
  * supports them - mirrors `package-filter.ts`'s own `applyPackageFilterOptions`. */
11
40
  export declare function applyBranchGuardOptions<T>(cmd: Argv<T>): Argv<T>;
@@ -1,6 +1,37 @@
1
1
  import colors from 'ansi-colors';
2
2
  import micromatch from 'micromatch';
3
3
  import { GitHelper } from './git.js';
4
+ /**
5
+ * `--allow-branch`/`--ignore-branch` as a declaration, to spread into a command's `config` block.
6
+ * See `packageFilterOptions` for why this is `satisfies` rather than an annotation.
7
+ *
8
+ * **The key is the camelCase one and `cliName` carries the flag's spelling.** `allowBranch` is what
9
+ * a `.rmanrc` writes and what `assertAllowedBranch` reads off the config; `--allow-branch` is what
10
+ * the shell sees. The `Argv` version relied on yargs converting one into the other silently, which
11
+ * is why `applyBranchGuardOptions` declares `'allow-branch'` and `readBranchGuardOptions` reads
12
+ * `args.allowBranch`.
13
+ *
14
+ * **`target: 'cli'`, even though `allowBranch` *is* a config key.** It is a repo-wide core key read
15
+ * off the root, not any command's - so it belongs on `RmanConfig` directly, and a command that
16
+ * reads it names it in `configKeys`. Declaring it `'both'` here would contribute `version.allowBranch`,
17
+ * which nothing reads.
18
+ */
19
+ export const branchGuardOptions = {
20
+ allowBranch: {
21
+ target: 'cli',
22
+ cliName: 'allow-branch',
23
+ describe: 'Refuse to run unless the current branch matches this glob (repeatable) - default: .rmanrc ' +
24
+ '"allowBranch", or no restriction at all',
25
+ type: 'string',
26
+ },
27
+ ignoreBranch: {
28
+ target: 'cli',
29
+ cliName: 'ignore-branch',
30
+ describe: 'Refuse to run if the current branch matches this glob (repeatable) - default: .rmanrc ' +
31
+ '"ignoreBranch", or no restriction at all',
32
+ type: 'string',
33
+ },
34
+ };
4
35
  /** `--allow-branch`/`--ignore-branch`, the same shape and describe text in every command that
5
36
  * supports them - mirrors `package-filter.ts`'s own `applyPackageFilterOptions`. */
6
37
  export function applyBranchGuardOptions(cmd) {
package/utils/exec.d.ts CHANGED
@@ -1,4 +1,14 @@
1
+ import type { RmanApplication } from '../core/application.js';
1
2
  export interface ExecOptions {
3
+ /**
4
+ * The application whose technologies put a repository's locally installed binaries on PATH -
5
+ * `node_modules/.bin` for a Node repository, whatever another technology uses.
6
+ *
7
+ * Passed rather than looked up, so a command run against one repository can never pick up the
8
+ * binaries of another in the same process. Omitted (a caller outside any repository) leaves the
9
+ * inherited PATH exactly as it was, which is also what a repository naming no plugin gets.
10
+ */
11
+ app?: RmanApplication;
2
12
  /** 'inherit' streams the child directly to our stdio (used for non-TTY/CI passthrough).
3
13
  * 'pipe' (default) captures output so the caller can drive a live view via onLine. */
4
14
  stdio?: 'inherit' | 'pipe';
package/utils/exec.js CHANGED
@@ -17,7 +17,7 @@ export async function exec(command, options) {
17
17
  ...options,
18
18
  };
19
19
  opts.env = {
20
- ...BinPath.env({ cwd: opts.cwd }),
20
+ ...BinPath.env({ cwd: opts.cwd, app: opts.app }),
21
21
  ...opts.env,
22
22
  };
23
23
  opts.cwd = opts.cwd || process.cwd();
package/utils/logger.d.ts CHANGED
@@ -4,7 +4,7 @@ export declare const LOG_LEVELS: LogLevel[];
4
4
  /**
5
5
  * Gates console output by verbosity (`--log-level`/`.rmanrc logLevel`) so a command's routine
6
6
  * narration can be turned down without losing failure output - the same semantics `run`'s own
7
- * classic per-step log already used (see `resolveLogLevel` in `commands/run.ts`), centralized here
7
+ * classic per-step log already used (see `resolveLogLevel` in `commands/run.command.ts`), centralized here
8
8
  * so every command can respect `--log-level` instead of reimplementing the checks, or ignoring it
9
9
  * altogether via plain `console.log`.
10
10
  */
package/utils/logger.js CHANGED
@@ -2,7 +2,7 @@ export const LOG_LEVELS = ['silent', 'error', 'info', 'verbose'];
2
2
  /**
3
3
  * Gates console output by verbosity (`--log-level`/`.rmanrc logLevel`) so a command's routine
4
4
  * narration can be turned down without losing failure output - the same semantics `run`'s own
5
- * classic per-step log already used (see `resolveLogLevel` in `commands/run.ts`), centralized here
5
+ * classic per-step log already used (see `resolveLogLevel` in `commands/run.command.ts`), centralized here
6
6
  * so every command can respect `--log-level` instead of reimplementing the checks, or ignoring it
7
7
  * altogether via plain `console.log`.
8
8
  */