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,57 @@
1
+ import { definePlatform } from '../../core/interfaces/plugin.js';
2
+ import { defineConfig } from '../../interfaces/rman-config.interface.js';
3
+ import { augmentSystemInfo, ciCommand, cleanCommand, NodePlatform } from '../platforms/node/index.js';
4
+ import { NpmPublishTarget } from '../publish-targets/npm/index.js';
5
+ /**
6
+ * **The `node` preset** - everything rman knows about the npm ecosystem, as a config.
7
+ *
8
+ * ```yaml
9
+ * # .rmanrc.yml
10
+ * extends: node
11
+ * ```
12
+ *
13
+ * It contributes the technology (how a `package.json` is read, where `workspaces` point, how
14
+ * versions are planned), the two commands that are npm's alone (`clean`, `ci`), and the `npm`
15
+ * publish target. A polyglot repository names several: `extends: ['node', 'cargo']` holds both,
16
+ * because every key here appends.
17
+ */
18
+ /* **A config rather than a plugin, and that is the whole shape of a preset.** `commands` and
19
+ * `publishTargets` were already config keys; `platforms` is one now, so a technology arrives the
20
+ * same way everything else does. What used to need a `Builtin` type, a name that looked like a glob
21
+ * and an expansion step is `extends` and nothing else.
22
+ *
23
+ * **A function, so nothing here happens until a repository asks for it.** `augmentSystemInfo()`
24
+ * mutates the core's own `SystemInfo` in place, so running it at import time would have `rman info`
25
+ * report npm's tooling in a Cargo repository that never named this preset. `extends` imports the
26
+ * module it names and calls this; a repository that names something else never reaches it. */
27
+ export function nodePreset() {
28
+ augmentSystemInfo();
29
+ return defineConfig({
30
+ platforms: [(nodePlatform ??= definePlatform(new NodePlatform()))],
31
+ commands: [ciCommand, cleanCommand],
32
+ publishTargets: [(npmTarget ??= new NpmPublishTarget())],
33
+ });
34
+ }
35
+ export default nodePreset;
36
+ /**
37
+ * **One instance of each across every call, so calling this preset twice contributes one of each.**
38
+ *
39
+ * A repository writing `extends: 'rman:node'` is read *and* gets the same preset laid under it by
40
+ * default, which is ordinary rather than a mistake - the contribution keys append and drop a
41
+ * duplicate **by identity** (`appendList`). The two commands are module consts, so they dedup on
42
+ * their own; anything this factory `new`s does not, and both of these have been that at some point.
43
+ *
44
+ * - **The target announced itself loudly.** Two of them reached `publish`, which refuses two
45
+ * targets declaring one option name, and it died with `Publish targets "npm" and "npm" both
46
+ * declare an option named "packageManager"` - the guard working exactly as intended, on a
47
+ * collision with itself.
48
+ * - **The platform does not, and that is the one to watch.** `_addPlatform` dedups by *name*, so a
49
+ * second `node` platform never reaches `context.platforms` and nothing resolves differently. What
50
+ * it does is put the same technology in `config.platforms` twice - visible only in `rman config`
51
+ * and to whoever iterates the key - plus an `augmentSystemInfo()` and a `new NodePlatform()` per
52
+ * read. Caught by the identity spec in `docs-api.spec.ts` rather than by anything failing.
53
+ *
54
+ * **A new preset has to do the same for anything it constructs.**
55
+ */
56
+ let npmTarget;
57
+ let nodePlatform;
@@ -1,31 +1,19 @@
1
- import type { Package } from '../core/package.js';
2
- import type { Repository } from '../core/repository.js';
3
- import { type PackageFilterOptions } from '../utils/package-filter.js';
4
- export declare namespace DockerPublishService {
5
- /** Injectable "does this tag already exist" check - mainly for tests, so they don't depend on
6
- * network access or a real Docker daemon. Same shape as `PublishService.Deps.npmViewVersion`. */
7
- interface Deps {
8
- imageExists?: (image: string, tag: string) => Promise<boolean>;
9
- }
10
- interface Options extends PackageFilterOptions {
11
- /** A package with uncommitted local changes is excluded (status `'skip'`) instead of aborting
12
- * the whole plan (status `'error'`) - same as `version`/`publish --target npm`'s own option. */
13
- ignoreDirty?: boolean;
14
- /** Prefixed onto a bare (no `/`) `publish.docker.image` - falls back to the
15
- * `DOCKERHUB_NAMESPACE` environment variable. */
16
- namespace?: string;
17
- }
18
- type ApplyOptions = Options;
19
- /** One package's outcome in a docker-publish plan - see `getPlan`. */
20
- interface Entry {
21
- package: Package;
22
- version: string;
23
- status: 'publish' | 'skip' | 'up-to-date' | 'error';
24
- /** The fully-qualified `<namespace>/<image>` this entry publishes to - unset only when the
25
- * package's own `publish.docker.image` config is missing entirely (an `'error'` entry). */
26
- image?: string;
27
- reason?: string;
28
- }
1
+ import { Service } from '../../../core/classes/service.js';
2
+ import { type PublishTarget } from '../../../core/interfaces/publish-target.js';
3
+ import { type PackageFilterOptions } from '../../../utils/package-filter.js';
4
+ /**
5
+ * The name this target answers to in `publish.target` and `--target`.
6
+ *
7
+ * Declared beside the implementation rather than in `publish-target.ts`: the seam is general and
8
+ * must not know any one target's name, and the adapter in `builtins/publish-targets/docker/docker.target.ts` reads it from
9
+ * here, which keeps the dependency pointing one way.
10
+ */
11
+ export declare const DOCKER_TARGET = "docker";
12
+ /**
13
+ * A service class - see `ListService` for the shape and `Service` for the three measured
14
+ * consequences a namespace had. `repository` left the signature because the application carries it.
15
+ */
16
+ export declare class DockerPublishService extends Service {
29
17
  /**
30
18
  * Computes what `publish --target docker` *would* do. Unlike the npm side (opt-out via
31
19
  * `"private"`), the docker target is opt-in: only packages whose own (cascaded) `.rmanrc
@@ -39,14 +27,47 @@ export declare namespace DockerPublishService {
39
27
  * already exists on the registry (via `docker manifest inspect`, queried concurrently) decides
40
28
  * the rest: `'up-to-date'` if so, `'publish'` if not.
41
29
  */
42
- function getPlan(repository: Repository, options?: Options, deps?: Deps): Promise<Entry[]>;
30
+ getPlan(options?: DockerPublishService.Options, deps?: DockerPublishService.Deps): Promise<DockerPublishService.Entry[]>;
43
31
  /**
44
32
  * Publishes every `'publish'` entry in `plan`: one `docker login` and one `docker buildx create`
45
33
  * up front (each package's own build reuses them), then per package a single `docker buildx
46
- * build --push` using that package's `publish.docker` config (platforms, named build-contexts,
34
+ * build --push` using that package's `publish.docker` config (architectures, named build-contexts,
47
35
  * build-args, an optional `cwd` override). A package's `publish.docker.readme` file (default
48
36
  * `DOCKER_README.md`), if present, updates the DockerHub repo description afterward. A package's
49
37
  * own failure doesn't stop unrelated packages elsewhere in the plan.
50
38
  */
51
- function applyPlan(repository: Repository, plan: Entry[]): Promise<Entry[]>;
39
+ applyPlan(plan: DockerPublishService.Entry[]): Promise<DockerPublishService.Entry[]>;
40
+ }
41
+ export declare namespace DockerPublishService {
42
+ /** Injectable "does this tag already exist" check - mainly for tests, so they don't depend on
43
+ * network access or a real Docker daemon. Same shape as `PublishService.Deps.npmViewVersion`. */
44
+ interface Deps {
45
+ imageExists?: (image: string, tag: string) => Promise<boolean>;
46
+ }
47
+ interface Options extends PackageFilterOptions {
48
+ /** A package with uncommitted local changes is excluded (status `'skip'`) instead of aborting
49
+ * the whole plan (status `'error'`) - same as `version`/`publish --target npm`'s own option. */
50
+ ignoreDirty?: boolean;
51
+ /** Prefixed onto a bare (no `/`) `publish.docker.image` - falls back to the
52
+ * `DOCKERHUB_NAMESPACE` environment variable. */
53
+ namespace?: string;
54
+ }
55
+ type ApplyOptions = Options;
56
+ /**
57
+ * One package's outcome in a docker-publish plan - see `getPlan`.
58
+ *
59
+ * A `PublishTarget.Entry` with one field of its own, which is the shape that interface expects: a
60
+ * target knows things about its own registry that no other target has a word for, and `detail` is
61
+ * where it puts whatever the command should print beside the package.
62
+ */
63
+ interface Entry extends PublishTarget.Entry {
64
+ /** The fully-qualified `<namespace>/<image>` this entry publishes to - unset only when the
65
+ * package's own `publish.docker.image` config is missing entirely (an `'error'` entry). */
66
+ image?: string;
67
+ }
68
+ }
69
+ declare module '../../../core/classes/service.js' {
70
+ interface ServiceMap {
71
+ dockerPublish: DockerPublishService;
72
+ }
52
73
  }
@@ -2,11 +2,24 @@ import { execFile } from 'node:child_process';
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { promisify } from 'node:util';
5
- import { exec } from '../utils/exec.js';
6
- import { GitHelper } from '../utils/git.js';
7
- import { filterPackages } from '../utils/package-filter.js';
8
- export var DockerPublishService;
9
- (function (DockerPublishService) {
5
+ import { Service } from '../../../core/classes/service.js';
6
+ import { targetsOf } from '../../../core/interfaces/publish-target.js';
7
+ import { exec } from '../../../utils/exec.js';
8
+ import { GitHelper } from '../../../utils/git.js';
9
+ import { filterPackages } from '../../../utils/package-filter.js';
10
+ /**
11
+ * The name this target answers to in `publish.target` and `--target`.
12
+ *
13
+ * Declared beside the implementation rather than in `publish-target.ts`: the seam is general and
14
+ * must not know any one target's name, and the adapter in `builtins/publish-targets/docker/docker.target.ts` reads it from
15
+ * here, which keeps the dependency pointing one way.
16
+ */
17
+ export const DOCKER_TARGET = 'docker';
18
+ /**
19
+ * A service class - see `ListService` for the shape and `Service` for the three measured
20
+ * consequences a namespace had. `repository` left the signature because the application carries it.
21
+ */
22
+ export class DockerPublishService extends Service {
10
23
  /**
11
24
  * Computes what `publish --target docker` *would* do. Unlike the npm side (opt-out via
12
25
  * `"private"`), the docker target is opt-in: only packages whose own (cascaded) `.rmanrc
@@ -20,9 +33,14 @@ export var DockerPublishService;
20
33
  * already exists on the registry (via `docker manifest inspect`, queried concurrently) decides
21
34
  * the rest: `'up-to-date'` if so, `'publish'` if not.
22
35
  */
23
- async function getPlan(repository, options = {}, deps = {}) {
36
+ async getPlan(options = {}, deps = {}) {
37
+ const repository = this.repository;
24
38
  const git = new GitHelper({ cwd: repository.dirname });
25
- const packages = filterPackages(repository.getPackages({ toposort: true }), options).filter(pkg => targetsDocker(pkg) && !pkg.config.publish?.skip);
39
+ /** Which packages are the docker target's is the one question `PublishTarget` answers for every
40
+ * target alike (`publish.target`, or `claims` when a package declares none) - asked through
41
+ * `targetsOf` rather than re-read here, so `publish` and `list --json` cannot disagree about
42
+ * where a package ships. */
43
+ const packages = filterPackages(repository.getPackages({ toposort: true }), options).filter(pkg => targetsOf(this.app, pkg).some(t => t.name === DOCKER_TARGET) && !pkg.config.publish?.skip);
26
44
  const dirtyFiles = await git.listDirtyFiles({ absolute: true });
27
45
  const isDirty = (pkg) => dirtyFiles.some(f => !path.relative(pkg.dirname, f).startsWith('..'));
28
46
  const imageExists = deps.imageExists ?? defaultImageExists;
@@ -52,6 +70,7 @@ export var DockerPublishService;
52
70
  package: pkg,
53
71
  version: pkg.version,
54
72
  image,
73
+ detail: image,
55
74
  status: options.ignoreDirty ? 'skip' : 'error',
56
75
  reason: 'uncommitted local changes',
57
76
  });
@@ -65,27 +84,33 @@ export var DockerPublishService;
65
84
  package: pkg,
66
85
  version: pkg.version,
67
86
  image,
87
+ detail: image,
68
88
  status: exists ? 'up-to-date' : 'publish',
69
89
  reason: exists ? `registry already has ${image}:${pkg.version}` : 'never published',
70
90
  });
71
91
  }));
72
92
  return packages.map(pkg => entries.get(pkg.name));
73
93
  }
74
- DockerPublishService.getPlan = getPlan;
75
94
  /**
76
95
  * Publishes every `'publish'` entry in `plan`: one `docker login` and one `docker buildx create`
77
96
  * up front (each package's own build reuses them), then per package a single `docker buildx
78
- * build --push` using that package's `publish.docker` config (platforms, named build-contexts,
97
+ * build --push` using that package's `publish.docker` config (architectures, named build-contexts,
79
98
  * build-args, an optional `cwd` override). A package's `publish.docker.readme` file (default
80
99
  * `DOCKER_README.md`), if present, updates the DockerHub repo description afterward. A package's
81
100
  * own failure doesn't stop unrelated packages elsewhere in the plan.
82
101
  */
83
- async function applyPlan(repository, plan) {
102
+ async applyPlan(plan) {
103
+ const repository = this.repository;
84
104
  const toPublish = plan.filter(e => e.status === 'publish');
85
105
  if (!toPublish.length)
86
106
  return plan;
87
- await dockerLogin(repository.dirname);
88
- await exec('docker buildx create --use', { cwd: repository.dirname, stdio: 'inherit', throwOnError: false });
107
+ await dockerLogin(repository.app, repository.dirname);
108
+ await exec('docker buildx create --use', {
109
+ cwd: repository.dirname,
110
+ app: repository.app,
111
+ stdio: 'inherit',
112
+ throwOnError: false,
113
+ });
89
114
  const result = [];
90
115
  for (const entry of plan) {
91
116
  if (entry.status !== 'publish') {
@@ -103,12 +128,11 @@ export var DockerPublishService;
103
128
  }
104
129
  return result;
105
130
  }
106
- DockerPublishService.applyPlan = applyPlan;
107
- })(DockerPublishService || (DockerPublishService = {}));
131
+ }
108
132
  const execFileAsync = promisify(execFile);
109
133
  /** `docker manifest inspect <image>:<tag>` - `false` for any failure (tag doesn't exist yet, no
110
- * network, not logged in, ...), same catch-everything shape as `PublishService`'s own
111
- * `defaultNpmViewVersion`. */
134
+ * network, not logged in, ...), the same catch-everything shape every target's registry check has:
135
+ * "is this version out there" must never fail the run for a reason that is not an answer. */
112
136
  async function defaultImageExists(image, tag) {
113
137
  try {
114
138
  await execFileAsync('docker', ['manifest', 'inspect', `${image}:${tag}`]);
@@ -118,11 +142,6 @@ async function defaultImageExists(image, tag) {
118
142
  return false;
119
143
  }
120
144
  }
121
- function targetsDocker(pkg) {
122
- const target = pkg.config.publish?.target;
123
- const targets = Array.isArray(target) ? target : target ? [target] : ['npm'];
124
- return targets.includes('docker');
125
- }
126
145
  function resolveImageRef(image, namespaceOverride) {
127
146
  if (image.includes('/'))
128
147
  return image;
@@ -138,21 +157,25 @@ function expandEnvValue(value) {
138
157
  const match = /^\$([A-Za-z_][A-Za-z0-9_]*)$/.exec(value);
139
158
  return match ? (process.env[match[1]] ?? '') : value;
140
159
  }
141
- async function dockerLogin(cwd) {
160
+ async function dockerLogin(app, cwd) {
142
161
  const username = process.env.DOCKERHUB_USERNAME;
143
162
  const password = process.env.DOCKERHUB_PASSWORD;
144
163
  if (!username || !password) {
145
164
  throw new Error('DOCKERHUB_USERNAME/DOCKERHUB_PASSWORD environment variables are required to publish to Docker');
146
165
  }
147
- await exec(`echo "${password}" | docker login --username "${username}" --password-stdin`, { cwd, stdio: 'inherit' });
166
+ await exec(`echo "${password}" | docker login --username "${username}" --password-stdin`, {
167
+ cwd,
168
+ app,
169
+ stdio: 'inherit',
170
+ });
148
171
  }
149
172
  async function buildAndPush(repository, entry) {
150
173
  const pkg = entry.package;
151
174
  const docker = pkg.config.publish.docker;
152
- const platforms = docker.platforms?.length ? docker.platforms : ['linux/amd64'];
175
+ const architectures = docker.architectures?.length ? docker.architectures : ['linux/amd64'];
153
176
  const dockerfile = path.resolve(pkg.dirname, docker.dockerfile || 'Dockerfile');
154
177
  const cwd = docker.cwd ? path.resolve(repository.dirname, docker.cwd) : pkg.dirname;
155
- const args = ['buildx', 'build', '--platform', platforms.join(',')];
178
+ const args = ['buildx', 'build', '--platform', architectures.join(',')];
156
179
  for (const [name, dir] of Object.entries(docker.buildContexts ?? {})) {
157
180
  args.push('--build-context', `${name}="${path.resolve(pkg.dirname, dir)}"`);
158
181
  }
@@ -160,7 +183,7 @@ async function buildAndPush(repository, entry) {
160
183
  args.push('--build-arg', `${name}="${expandEnvValue(value)}"`);
161
184
  }
162
185
  args.push('-f', `"${dockerfile}"`, '-t', `"${entry.image}:${entry.version}"`, '-t', `"${entry.image}:latest"`, '--push', '.');
163
- await exec(`docker ${args.join(' ')}`, { cwd, stdio: 'inherit' });
186
+ await exec(`docker ${args.join(' ')}`, { cwd, app: repository.app, stdio: 'inherit' });
164
187
  }
165
188
  async function updateDescription(entry) {
166
189
  const pkg = entry.package;
@@ -0,0 +1,54 @@
1
+ import type { PublishTarget } from '../../../core/interfaces/publish-target.js';
2
+ import type { ConfigValue, ScopedVars } from '../../../interfaces/rman-config.interface.js';
3
+ /**
4
+ * **`publish.docker.*`** - this target's own config block, declared here rather than centrally.
5
+ *
6
+ * Reaches `RmanConfig` through the `PublishTargetConfigs` slot `publish.command.ts` exports, which
7
+ * is the same slot the `node` built-in declares `publish.npm.*` in. Whoever reads a key declares it: the
8
+ * only thing that reads these is `DockerPublishService`, two files away.
9
+ *
10
+ * Required once `"docker"` is one of a package's `publish.target`s - `publish --target docker`
11
+ * errors clearly on a package that opts in and leaves this out.
12
+ */
13
+ export interface DockerPublishOptions extends DockerPublishOptionsKeys, ScopedVars {
14
+ }
15
+ /** Every key here is a **value**, so every one is a `ConfigValue` - this target declares no step,
16
+ * which is what makes the whole block uniform (see `VersionExtraKeys` for the interface where it
17
+ * is not). */
18
+ export interface DockerPublishOptionsKeys {
19
+ /** DockerHub image name/repository - bare (e.g. `"my-app"`) to be prefixed with
20
+ * `--docker-namespace`/`DOCKERHUB_NAMESPACE`, or already-namespaced (contains a `/`) to use
21
+ * verbatim. */
22
+ image: ConfigValue<string>;
23
+ /** Relative to the package's own directory. Default `"Dockerfile"`. */
24
+ dockerfile?: ConfigValue<string>;
25
+ /**
26
+ * The architectures to build for - `docker buildx build --platform`. Default `["linux/amd64"]`.
27
+ */
28
+ architectures?: ConfigValue<string[]>;
29
+ /** Build `cwd` override, relative to the repository root - only needed when the Dockerfile's
30
+ * own `COPY`/`ADD` paths expect something other than the package's own directory (rare). */
31
+ cwd?: ConfigValue<string>;
32
+ /** Named `docker buildx build --build-context <name>=<path>` entries, keyed by name - each
33
+ * path is relative to the package's own directory (or absolute). */
34
+ buildContexts?: ConfigValue<Record<string, string>>;
35
+ /** `docker buildx build --build-arg <name>=<value>` entries - a value of exactly `"$NAME"`
36
+ * expands to `process.env.NAME` at build time (e.g. to pass a CI secret through). */
37
+ buildArgs?: ConfigValue<Record<string, string>>;
38
+ /** A file (relative to the package's own directory) whose contents become the DockerHub repo's
39
+ * full description, if present. Default `"DOCKER_README.md"`. */
40
+ readme?: ConfigValue<string>;
41
+ }
42
+ /**
43
+ * **Docker, as a publish target** - the core's own, and the reason `PublishTarget` is in the core
44
+ * rather than in a plugin.
45
+ *
46
+ * Any language's project can ship an image; nothing about `docker buildx build --push` is npm's, or
47
+ * Cargo's, or Maven's. It was already a core *service*, and could still only be reached through
48
+ * `rman-node`'s `publish` command - so a repository with no JavaScript in it had to install a Node
49
+ * plugin to push a container. This file is the ten lines that fix that.
50
+ *
51
+ * Opt-in, and therefore no `claims`: most packages in a repository are not images, so a package
52
+ * ships here only by naming `"docker"` in its own `publish.target`.
53
+ */
54
+ export declare const dockerPublishTarget: PublishTarget;
@@ -0,0 +1,40 @@
1
+ import { DOCKER_TARGET } from './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
+ }
@@ -0,0 +1,2 @@
1
+ export * from './docker.target.js';
2
+ export * from './docker-publish.service.js';
@@ -0,0 +1,2 @@
1
+ export * from './docker.target.js';
2
+ export * from './docker-publish.service.js';
@@ -0,0 +1,2 @@
1
+ export * from './npm-publish-target.js';
2
+ export * from './npm-view.js';
@@ -0,0 +1,2 @@
1
+ export * from './npm-publish-target.js';
2
+ export * from './npm-view.js';
@@ -0,0 +1,73 @@
1
+ import type { PublishTarget } from '../../../core/interfaces/publish-target.js';
2
+ import { PublishService } from '../../platforms/node/services/publish.service.js';
3
+ /** The name this target answers to in `publish.target` and `--target`. */
4
+ export declare const NPM_TARGET = "npm";
5
+ /**
6
+ * **npm, as a publish target** - what is left of the old `rman-node` `publish` command once the command
7
+ * itself went back to the core where it belonged.
8
+ *
9
+ * The split is worth stating, because it is the whole point of the move: everything about
10
+ * *publishing a repository* - which packages are candidates, dependency order, the plan/confirm/
11
+ * apply shape, `--dry-run`, the JSON a CI gate reads - is the core's and always was. What is npm's
12
+ * is this file: seven flags, one registry question, and one push. A Cargo or Maven target is the
13
+ * same size.
14
+ *
15
+ * **`claims` is the other half, and it fixes a real bug.** The core used to default a package with
16
+ * no `publish.target` to `['npm']`, which is a statement only an ecosystem can make - so a Cargo
17
+ * package beside a Node one was reported by `rman list --json` as shipping to npm, and treated by
18
+ * `publish` as an npm candidate. Asking `pkg.provider === 'node'` puts the answer where the
19
+ * knowledge is: this plugin read that manifest, so this plugin is the one that can say so.
20
+ *
21
+ * A class, like `NodePlugin` and `NodeManifestProvider`: a config declares it as
22
+ * `publishTargets: [new NpmPublishTarget()]`, which is one shape for everything a plugin package
23
+ * contributes rather than an object literal here and a class there.
24
+ */
25
+ export declare class NpmPublishTarget implements PublishTarget {
26
+ name: string;
27
+ describe: string;
28
+ /** npm's alone: a package rman read through the `node` platform. */
29
+ platforms: string[];
30
+ options: {
31
+ packageManager: {
32
+ target: "cli";
33
+ cliName: string;
34
+ describe: string;
35
+ choices: readonly ["npm", "yarn", "pnpm", "bun"];
36
+ };
37
+ access: {
38
+ target: "cli";
39
+ describe: string;
40
+ choices: string[];
41
+ };
42
+ tag: {
43
+ target: "cli";
44
+ describe: string;
45
+ type: "string";
46
+ };
47
+ otp: {
48
+ target: "cli";
49
+ describe: string;
50
+ type: "string";
51
+ };
52
+ registry: {
53
+ target: "cli";
54
+ describe: string;
55
+ type: "string";
56
+ };
57
+ userconfig: {
58
+ target: "cli";
59
+ describe: string;
60
+ type: "string";
61
+ };
62
+ contents: {
63
+ target: "cli";
64
+ describe: string;
65
+ type: "string";
66
+ };
67
+ };
68
+ getPlan(ctx: PublishTarget.Context): Promise<PublishService.Entry[]>;
69
+ applyPlan(ctx: PublishTarget.Context, plan: PublishTarget.Entry[]): Promise<PublishService.Entry[]>;
70
+ /** The shared filters plus the two flags both halves need - read here rather than in the
71
+ * service, which keeps taking a plain options object and stays callable without a CLI. */
72
+ protected planOptions(ctx: PublishTarget.Context): PublishService.Options;
73
+ }
@@ -0,0 +1,95 @@
1
+ import { CiService } from '../../platforms/node/services/ci.service.js';
2
+ import { PublishService } from '../../platforms/node/services/publish.service.js';
3
+ /** The name this target answers to in `publish.target` and `--target`. */
4
+ export const NPM_TARGET = 'npm';
5
+ /**
6
+ * **npm, as a publish target** - what is left of the old `rman-node` `publish` command once the command
7
+ * itself went back to the core where it belonged.
8
+ *
9
+ * The split is worth stating, because it is the whole point of the move: everything about
10
+ * *publishing a repository* - which packages are candidates, dependency order, the plan/confirm/
11
+ * apply shape, `--dry-run`, the JSON a CI gate reads - is the core's and always was. What is npm's
12
+ * is this file: seven flags, one registry question, and one push. A Cargo or Maven target is the
13
+ * same size.
14
+ *
15
+ * **`claims` is the other half, and it fixes a real bug.** The core used to default a package with
16
+ * no `publish.target` to `['npm']`, which is a statement only an ecosystem can make - so a Cargo
17
+ * package beside a Node one was reported by `rman list --json` as shipping to npm, and treated by
18
+ * `publish` as an npm candidate. Asking `pkg.provider === 'node'` puts the answer where the
19
+ * knowledge is: this plugin read that manifest, so this plugin is the one that can say so.
20
+ *
21
+ * A class, like `NodePlugin` and `NodeManifestProvider`: a config declares it as
22
+ * `publishTargets: [new NpmPublishTarget()]`, which is one shape for everything a plugin package
23
+ * contributes rather than an object literal here and a class there.
24
+ */
25
+ export class NpmPublishTarget {
26
+ name = NPM_TARGET;
27
+ describe = 'Publish to an npm registry (npm/yarn/pnpm/bun publish)';
28
+ /** npm's alone: a package rman read through the `node` platform. */
29
+ platforms = ['node'];
30
+ options = {
31
+ packageManager: {
32
+ target: 'cli',
33
+ cliName: 'package-manager',
34
+ describe: 'Package manager to publish with (default: npm, or .rmanrc "packageManager")',
35
+ choices: CiService.PACKAGE_MANAGERS,
36
+ },
37
+ access: {
38
+ target: 'cli',
39
+ describe: 'npm publish --access <public|restricted> - required by the registry for a new scoped package',
40
+ choices: ['public', 'restricted'],
41
+ },
42
+ tag: {
43
+ target: 'cli',
44
+ describe: 'npm publish --tag <tag> - overrides the dist-tag. A prerelease already publishes under its ' +
45
+ 'own identifier (2.0.0-beta.1 -> "beta") and a release under "latest", so this is rarely needed',
46
+ type: 'string',
47
+ },
48
+ otp: {
49
+ target: 'cli',
50
+ describe: 'npm publish --otp <otp> - a 2FA one-time password, for registries that require it',
51
+ type: 'string',
52
+ },
53
+ registry: {
54
+ target: 'cli',
55
+ describe: 'Registry to check against and publish to (default: whatever .npmrc already configures)',
56
+ type: 'string',
57
+ },
58
+ userconfig: {
59
+ target: 'cli',
60
+ describe: 'Path to a custom .npmrc to use for both the registry check and the actual publish',
61
+ type: 'string',
62
+ },
63
+ contents: {
64
+ target: 'cli',
65
+ describe: "Subdirectory to publish from, relative to each package's own directory - only consulted when a " +
66
+ 'package has no "publishConfig.directory" of its own (that always wins when present)',
67
+ type: 'string',
68
+ },
69
+ };
70
+ getPlan(ctx) {
71
+ return PublishService.getPlan(ctx.repository, this.planOptions(ctx));
72
+ }
73
+ applyPlan(ctx, plan) {
74
+ return PublishService.applyPlan(ctx.repository, plan, {
75
+ ...this.planOptions(ctx),
76
+ packageManager: ctx.args.packageManager,
77
+ access: ctx.args.access,
78
+ otp: ctx.args.otp,
79
+ contents: ctx.args.contents,
80
+ });
81
+ }
82
+ /** The shared filters plus the two flags both halves need - read here rather than in the
83
+ * service, which keeps taking a plain options object and stays callable without a CLI. */
84
+ planOptions(ctx) {
85
+ return {
86
+ ...ctx.options,
87
+ registry: ctx.args.registry,
88
+ userconfig: ctx.args.userconfig,
89
+ /** Read at *plan* time too, not only where the publish command is built: the plan is what
90
+ * decides the dist-tag and refuses the two cases with nothing to derive, so `--dry-run` and
91
+ * the JSON a pipeline gates on have to see the flag. */
92
+ tag: ctx.args.tag,
93
+ };
94
+ }
95
+ }
@@ -0,0 +1,48 @@
1
+ /** What the registry knows about one package - the two halves `publish` needs, from one call. */
2
+ export interface NpmPackageView {
3
+ /** The version the **`latest` dist-tag** points at, which is what a bare `npm install <name>`
4
+ * resolves to. Not necessarily the highest: a prerelease published under its own tag, or a
5
+ * patch to an older line, leaves `latest` where it was. */
6
+ latest?: string;
7
+ /** Every version ever published, `latest` included. */
8
+ versions: string[];
9
+ }
10
+ /**
11
+ * `npm view <name> version versions --json` - `undefined` for anything that is not an answer (never
12
+ * published, no network, private or restricted with no access). Catch-everything on purpose: every
13
+ * caller treats "no answer" as a legitimate state rather than a failure.
14
+ *
15
+ * **Two fields in one call, because the two questions are genuinely different and `publish` needs
16
+ * both.** Whether *this* version is already out there decides `up-to-date` vs `publish`, and only
17
+ * `versions` can answer it; what `latest` points at is what the plan *reports*, so a reader can see
18
+ * where the registry stands. Asking `latest` alone was the same approximation in both roles, and it
19
+ * is wrong in both directions: a prerelease on its own dist-tag never moves `latest`, so `publish`
20
+ * kept proposing an already-published version until npm answered 403; and a package whose local
21
+ * version is *behind* `latest` was proposed just as wrongly.
22
+ *
23
+ * **`versions` is normalized to an array defensively, and the reason is what happens if it is
24
+ * not.** `includes` exists on a string too, so a bare `"1.0.0"` would answer `versions.includes()`
25
+ * without any error at all - and answer it by substring, where `"1.0.10".includes("1.0.1")` is
26
+ * `true` (measured). npm's own `--json` output *is* a real array here on this npm (measured: a
27
+ * single-entry `maintainers` comes back as `[...]`, not unwrapped), so this guards a shape
28
+ * difference across npm versions rather than one I reproduced - but a silent wrong answer about
29
+ * whether a version is published is worth two lines.
30
+ */
31
+ export declare function npmViewPackage(name: string, cwd: string, options?: {
32
+ registry?: string;
33
+ userconfig?: string;
34
+ }): Promise<NpmPackageView | undefined>;
35
+ /**
36
+ * `npm view <name> version` - the **`latest` dist-tag's** version, or `undefined` for anything that
37
+ * is not an answer.
38
+ *
39
+ * `packageJsonManifest.publishedVersion` asks this so that `detectChangeHash` can guess a tag name
40
+ * for a package with no release tag at all, and `latest` is the right question for it: it wants the
41
+ * one version the ecosystem considers current, not the set of everything ever published. It passes
42
+ * no options - a bare `npm view` already picks up the repository's own `.npmrc` from `cwd`, which
43
+ * is what that path wants. `publish` asks `npmViewPackage` instead; see there for why.
44
+ */
45
+ export declare function npmViewVersion(name: string, cwd: string, options?: {
46
+ registry?: string;
47
+ userconfig?: string;
48
+ }): Promise<string | undefined>;