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 +1,61 @@
1
- export {};
1
+ export const commandRegistry = [];
2
+ /**
3
+ * Registers a command and hands the register function straight back.
4
+ *
5
+ * **It returns the function, not the command's config, and that is the point.** The function has
6
+ * not run yet - it runs during init, once there is a `Repository` - so at registration time no
7
+ * config object exists to return. Declaring one anyway is what made the old body
8
+ * (`return def['config']`) uncompilable: `def` is a function, and `config` belongs to what it
9
+ * *returns*.
10
+ *
11
+ * Nothing is lost by being honest, because the metadata type rides along on the return type:
12
+ * `ReturnType<typeof someCommand>` is the `CommandMetadata`, and `CommandConfigFromMetadata` maps
13
+ * it to the config object the command contributes. A phantom property carrying the config type was
14
+ * tried and is unnecessary for exactly this reason - and it would not have survived being stored in
15
+ * `commandRegistry`, whose element type erases it.
16
+ *
17
+ * **`M` is inferred from the metadata rather than from the function, so that the same position can
18
+ * check it.** With the plainer `<T extends CommandRegisterFunction>(def: T)`, `T` is inferred from
19
+ * the literal itself, so the constraint is a subtype check - and a subtype check does no
20
+ * excess-property checking. Measured: `anotherBogusTopLevelKey: true` and a `totallyBogusKey`
21
+ * inside an option both compiled silently, which is how `cliName` and `examples` went unnoticed in
22
+ * `version.command.ts`. `M & ValidMeta<M>` keeps the bare `M` as the inference site and puts the
23
+ * check beside it, so a misspelled key now errors on its own line and names itself.
24
+ *
25
+ * **A command's `command` string needs `as const`** for the config key to be derivable from it
26
+ * (`'version [bump]' as const` -> `'version'`). `type: 'string'` survives inference on its own,
27
+ * since `yargs.Options['type']` is a union of literals and a union suppresses widening; `command`
28
+ * is a plain `string`, so it widens without the assertion. Spelling it `` `${string}` `` does not
29
+ * help - measured; that trick only applies where the constraint sits on the generic parameter.
30
+ */
31
+ export function registerCommand(def) {
32
+ /** The same cast `declareCommand` makes, rather than a call to it: passing `def` through would
33
+ * re-infer `M` from a type that already carries `ValidMeta<M>`, and the check then compounds
34
+ * onto itself (`Exact<M & Exact<M, …>, …>`) and fails on every command. Two lines, one cast
35
+ * each, is the honest shape. */
36
+ const fn = def;
37
+ commandRegistry.push(fn);
38
+ return fn;
39
+ }
40
+ /**
41
+ * The same declaration, **without** the registration - for a command that must exist only when
42
+ * something asks for it.
43
+ *
44
+ * That is exactly a contributed command's situation: `commandRegistry` is a module-level array
45
+ * walked by every `runCli`, so a package pushing onto it would give its commands to repositories
46
+ * that never named it - the module is imported as soon as anything imports the package. A package
47
+ * puts the function in its config's `commands` instead, and `cli.ts` calls it once the repository
48
+ * exists.
49
+ *
50
+ * **Why a factory rather than the metadata itself**: the config is resolved *inside*
51
+ * `Repository.create`, before the packages are known (plugins are what find them), so
52
+ * `app.repository` throws at that point. The function is stored unrun and called later, in
53
+ * `cli.ts`, where the built-ins' own factories run.
54
+ *
55
+ * Everything `registerCommand` documents about inference - `M & ValidMeta<M>`, the `as const` on
56
+ * `command`, the metadata riding on the return type - applies here unchanged; the two differ in one
57
+ * line.
58
+ */
59
+ export function declareCommand(def) {
60
+ return def;
61
+ }
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "rman",
3
3
  "description": "Repository manager",
4
- "version": "1.3.0",
4
+ "version": "2.0.0-beta.2",
5
5
  "author": "Panates",
6
6
  "license": "MIT",
7
7
  "dependencies": {
8
+ "@netlify/parse-npm-script": "^0.1.2",
8
9
  "@xmldom/xmldom": "^0.9.12",
9
10
  "ansi-colors": "^4.1.3",
10
11
  "cross-dirname": "^0.1.0",
@@ -0,0 +1,44 @@
1
+ import type { Platform } from '../core/plugin.js';
2
+ import type { RmanConfig } from '../interfaces/rman-config.interface.js';
3
+ /**
4
+ * **The plugins rman ships in the box, by the name a config calls them.**
5
+ *
6
+ * `plugins: ['node']` is the whole of it - no new key, because there is no new concept. A
7
+ * repository was always able to say which technologies it holds; this is that same statement with
8
+ * a name instead of an instance, which is the form a YAML config and a zero-config repository both
9
+ * need.
10
+ *
11
+ * **Bundled is not the same as always on, and keeping those apart is the point.** Nothing an entry
12
+ * contributes exists until a repository names it or detection finds it, so the core still assumes
13
+ * no ecosystem and `rman clean` is still `Unknown argument` in a repository that is not a Node one.
14
+ * What changed is only where the code is shipped from - one install instead of two, which is what
15
+ * `extends: 'rman-node'` was costing every Node repository for a benefit that belonged to polyglot
16
+ * ones.
17
+ *
18
+ * The key is `Plugin.name`, so what a config writes is what `${{ pkg.provider }}` reads back.
19
+ *
20
+ * **A published package is still not one of these.** `plugins: ['rman-node']` names a package, whose
21
+ * config reaches a repository through `extends`; it is refused, saying so. The two are different
22
+ * statements and a built-in name is neither of them - it is the plugin itself, called by name.
23
+ */
24
+ export declare const BUILTIN_PLUGINS: Record<string, Builtin>;
25
+ /**
26
+ * One built-in, in the two halves that must not be the same call.
27
+ *
28
+ * **`plugin` answers questions; `contribute` changes the run.** Detection asks a platform whether a
29
+ * directory is its own, which has to be possible without registering anything - and registering is
30
+ * what `contribute` does: it hands over commands, publish targets and (for `node`) an augmentation
31
+ * that rewrites the core's `SystemInfo` in place. One call for both would have "is this a Node
32
+ * repository?" print npm's tooling in a Cargo one, which is exactly the line between *bundled* and
33
+ * *on*.
34
+ */
35
+ export interface Builtin {
36
+ /** The platform itself, for asking - constructed, registered nowhere, with no side effect. */
37
+ platform: () => Platform;
38
+ /** Everything this built-in adds to a repository that asked for it. */
39
+ contribute: () => RmanConfig;
40
+ }
41
+ /** Whether `name` is one rman ships - the check `plugins` makes before treating a string as a glob. */
42
+ export declare function isBuiltinPlugin(name: string): boolean;
43
+ /** The built-in names, for an error message that has to list what a repository could have meant. */
44
+ export declare function builtinPluginNames(): string[];
@@ -0,0 +1,33 @@
1
+ import { nodeBuiltin, nodePlatform } from './node/index.js';
2
+ /**
3
+ * **The plugins rman ships in the box, by the name a config calls them.**
4
+ *
5
+ * `plugins: ['node']` is the whole of it - no new key, because there is no new concept. A
6
+ * repository was always able to say which technologies it holds; this is that same statement with
7
+ * a name instead of an instance, which is the form a YAML config and a zero-config repository both
8
+ * need.
9
+ *
10
+ * **Bundled is not the same as always on, and keeping those apart is the point.** Nothing an entry
11
+ * contributes exists until a repository names it or detection finds it, so the core still assumes
12
+ * no ecosystem and `rman clean` is still `Unknown argument` in a repository that is not a Node one.
13
+ * What changed is only where the code is shipped from - one install instead of two, which is what
14
+ * `extends: 'rman-node'` was costing every Node repository for a benefit that belonged to polyglot
15
+ * ones.
16
+ *
17
+ * The key is `Plugin.name`, so what a config writes is what `${{ pkg.provider }}` reads back.
18
+ *
19
+ * **A published package is still not one of these.** `plugins: ['rman-node']` names a package, whose
20
+ * config reaches a repository through `extends`; it is refused, saying so. The two are different
21
+ * statements and a built-in name is neither of them - it is the plugin itself, called by name.
22
+ */
23
+ export const BUILTIN_PLUGINS = {
24
+ node: { platform: () => nodePlatform, contribute: nodeBuiltin },
25
+ };
26
+ /** Whether `name` is one rman ships - the check `plugins` makes before treating a string as a glob. */
27
+ export function isBuiltinPlugin(name) {
28
+ return Object.hasOwn(BUILTIN_PLUGINS, name);
29
+ }
30
+ /** The built-in names, for an error message that has to list what a repository could have meant. */
31
+ export function builtinPluginNames() {
32
+ return Object.keys(BUILTIN_PLUGINS).sort();
33
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * **Which platform a repository looks like, when it has not said.**
3
+ *
4
+ * The 2.0 split made every Node repository write a config file before `rman clean` would run, and
5
+ * the cost of that landed on exactly the repositories the split was not for. This is the other half
6
+ * of the fix: a repository that declares no technology gets the one its own files already imply.
7
+ *
8
+ * **The core does not know what any repository looks like, and must not.** It asks the *platforms*
9
+ * - `manifestProvider.read(dir)` has always meant "is this directory one of mine?", so there is no
10
+ * new seam and no list of filenames anywhere in `core/`. This file listed `package.json` for one
11
+ * commit, which is the same mistake as the hardcoded `['npm']` publish default that
12
+ * `PublishTarget.claims` exists to have replaced: true of npm, written in a place that speaks for
13
+ * every ecosystem, and invisible while only one platform ships.
14
+ *
15
+ * The platforms it can ask are the ones it has, which are the built-ins - by construction, not by
16
+ * choice: a third-party platform arrives through `plugins` or `extends`, and a repository that
17
+ * wrote either has declared its technology, so detection never runs for it.
18
+ *
19
+ * **Only ever consulted when nothing was declared** - see `Repository.create`, which checks both the
20
+ * config and the application. That boundary is the whole safety of it, because registering a
21
+ * platform changes *which directories are packages at all* (`Workspace.resolve` takes the first
22
+ * provider that answers), so guessing on top of an explicit statement could quietly change what
23
+ * `rman list` reports.
24
+ *
25
+ * **It is announced, not silent.** A guess the reader cannot see is one they cannot correct.
26
+ */
27
+ export interface DetectedBuiltin {
28
+ /** The built-in's name - what `plugins: [...]` would have said. */
29
+ name: string;
30
+ /** The file that gave it away, for the message - the platform's own `manifestFile`. */
31
+ because: string;
32
+ }
33
+ /**
34
+ * Marks a config whose `plugins` was **detected rather than declared**, so the one caller that
35
+ * should say so can tell.
36
+ *
37
+ * A non-enumerable symbol, the way `ORIGINS` and `PREVIOUS_VALUES` already carry bookkeeping: it
38
+ * travels with the config through `mergeConfig`, `JSON.stringify` and `rman config` without any of
39
+ * them knowing it is there, and `expect`'s `toEqual` compares symbol properties - so a plain
40
+ * property would turn every config-shape spec into a diff about this.
41
+ *
42
+ * The alternative was re-deriving the answer where the message is printed, which means reading the
43
+ * config twice and two places that can disagree about what "declared" means.
44
+ */
45
+ export declare const DETECTED_BUILTIN: unique symbol;
46
+ /** What detection put on `config`, if anything. */
47
+ export declare function detectedBuiltinOf(config: object): DetectedBuiltin | undefined;
48
+ /**
49
+ * The first built-in platform that claims `dir`, or `undefined` for a repository none of them
50
+ * recognizes - which is the honest answer, not a default.
51
+ *
52
+ * **First match, in declaration order**, the same rule `Manifest.read` and `Workspace.resolve`
53
+ * already follow, so a repository two platforms both recognize resolves the same way here as it
54
+ * will once they are loaded.
55
+ *
56
+ * Memoized per directory: this is asked once per directory per package and each ask reads the disk.
57
+ * Nothing here writes, so the answer cannot change under a run in any way that matters.
58
+ *
59
+ * **Async only because `builtins.js` is imported dynamically, and that is a cycle rather than a
60
+ * style** - the same one `expandBuiltinPlugins` in `config.ts` already documents, reached from the
61
+ * other side. A built-in pulls in its commands and services; one of those services extends a core
62
+ * service, and the core's own module graph comes back through here:
63
+ *
64
+ * ```
65
+ * services/version-plan.service -> core/application -> core/core-services
66
+ * -> services/github-release.service -> core/repository -> plugins/detect
67
+ * -> plugins/builtins -> plugins/node/index -> plugins/node/node.platform
68
+ * -> plugins/node/services/version-plan.service (extends the first, still initialising)
69
+ * ```
70
+ *
71
+ * **Measured, and the suite could not see it**: the *built* CLI died on every command with
72
+ * `ReferenceError: Cannot access 'VersionPlanService' before initialization`, while `npm test`
73
+ * passed - mocha resolves `src` through `tsconfig-test.json`'s `paths`, which is a different module
74
+ * graph and happens to reach the two in the other order. `npm run smoke` is what looks now.
75
+ */
76
+ export declare function detectBuiltin(dir: string): Promise<DetectedBuiltin | undefined>;
77
+ /** Forgets what was detected - for a spec that writes a fixture, detects, then writes another. */
78
+ export declare function clearDetectionCache(): void;
@@ -0,0 +1,70 @@
1
+ import path from 'node:path';
2
+ /**
3
+ * Marks a config whose `plugins` was **detected rather than declared**, so the one caller that
4
+ * should say so can tell.
5
+ *
6
+ * A non-enumerable symbol, the way `ORIGINS` and `PREVIOUS_VALUES` already carry bookkeeping: it
7
+ * travels with the config through `mergeConfig`, `JSON.stringify` and `rman config` without any of
8
+ * them knowing it is there, and `expect`'s `toEqual` compares symbol properties - so a plain
9
+ * property would turn every config-shape spec into a diff about this.
10
+ *
11
+ * The alternative was re-deriving the answer where the message is printed, which means reading the
12
+ * config twice and two places that can disagree about what "declared" means.
13
+ */
14
+ export const DETECTED_BUILTIN = Symbol('rman.detectedBuiltin');
15
+ /** What detection put on `config`, if anything. */
16
+ export function detectedBuiltinOf(config) {
17
+ return config[DETECTED_BUILTIN];
18
+ }
19
+ /**
20
+ * The first built-in platform that claims `dir`, or `undefined` for a repository none of them
21
+ * recognizes - which is the honest answer, not a default.
22
+ *
23
+ * **First match, in declaration order**, the same rule `Manifest.read` and `Workspace.resolve`
24
+ * already follow, so a repository two platforms both recognize resolves the same way here as it
25
+ * will once they are loaded.
26
+ *
27
+ * Memoized per directory: this is asked once per directory per package and each ask reads the disk.
28
+ * Nothing here writes, so the answer cannot change under a run in any way that matters.
29
+ *
30
+ * **Async only because `builtins.js` is imported dynamically, and that is a cycle rather than a
31
+ * style** - the same one `expandBuiltinPlugins` in `config.ts` already documents, reached from the
32
+ * other side. A built-in pulls in its commands and services; one of those services extends a core
33
+ * service, and the core's own module graph comes back through here:
34
+ *
35
+ * ```
36
+ * services/version-plan.service -> core/application -> core/core-services
37
+ * -> services/github-release.service -> core/repository -> plugins/detect
38
+ * -> plugins/builtins -> plugins/node/index -> plugins/node/node.platform
39
+ * -> plugins/node/services/version-plan.service (extends the first, still initialising)
40
+ * ```
41
+ *
42
+ * **Measured, and the suite could not see it**: the *built* CLI died on every command with
43
+ * `ReferenceError: Cannot access 'VersionPlanService' before initialization`, while `npm test`
44
+ * passed - mocha resolves `src` through `tsconfig-test.json`'s `paths`, which is a different module
45
+ * graph and happens to reach the two in the other order. `npm run smoke` is what looks now.
46
+ */
47
+ export async function detectBuiltin(dir) {
48
+ const resolved = path.resolve(dir);
49
+ if (cache.has(resolved))
50
+ return cache.get(resolved);
51
+ const { BUILTIN_PLUGINS } = await import('./builtins.js');
52
+ let found;
53
+ for (const [name, builtin] of Object.entries(BUILTIN_PLUGINS)) {
54
+ const platform = builtin.platform();
55
+ /** `read` is the question. Its answer is thrown away - the manifest is read again for real once
56
+ * the platform is registered, and reading it twice is cheaper than keeping a half-built
57
+ * package around to decide whether it should exist. */
58
+ if (platform.manifestProvider.read(resolved)) {
59
+ found = { name, because: platform.manifestProvider.fileName };
60
+ break;
61
+ }
62
+ }
63
+ cache.set(resolved, found);
64
+ return found;
65
+ }
66
+ /** Forgets what was detected - for a spec that writes a fixture, detects, then writes another. */
67
+ export function clearDetectionCache() {
68
+ cache.clear();
69
+ }
70
+ const cache = new Map();
@@ -0,0 +1,84 @@
1
+ import type cleanCommand from '../commands/clean.command.js';
2
+ import type { CleanExtraKeys } from '../commands/clean.command.js';
3
+ import type { NodeConfigKeys, RmanNodeConfig } from '../node-config.interface.js';
4
+ import type { CiService } from '../services/ci.service.js';
5
+ /**
6
+ * **Every type this plugin adds to rman, in one `declare module` block.**
7
+ *
8
+ * **One block was once forced and is now only convenient.** While this shipped as its own package
9
+ * it augmented the *package name* `'rman'`, and a second such block anywhere silently disabled the
10
+ * first - measured, `SystemInfo.PackageManager` went unresolved at four call sites with nothing
11
+ * pointing at the cause. Bundled into rman, these augment a **module path** like every built-in
12
+ * command's own contribution does, and that limit is gone; they stay together because the plugin's
13
+ * types read better in one place, not because they have to.
14
+ *
15
+ * Type-only. The runtime half of each augmentation lives with its own subject
16
+ * (`augmentSystemInfo()`, `augmentManifest()`, ...); this file is imported by the plugin's entry
17
+ * point purely so a consumer's compiler loads it.
18
+ */
19
+ declare module '../../../interfaces/rman-config.interface.js' {
20
+ /**
21
+ * The `.rmanrc` keys that only mean something in a Node repository - merged into the core's own
22
+ * key list, so `pkg.config.clean` is typed wherever it is read (`CleanService` included) without
23
+ * a cast, and without the core declaring a key it knows nothing about.
24
+ *
25
+ * The augmentation is evaluated where it is used, so `clean` is typed at the place it is read.
26
+ */
27
+ interface RmanConfigKeys extends NodeConfigKeys {
28
+ }
29
+ /**
30
+ * **`clean.*`, contributed by the command that reads it**, the way every built-in contributes its
31
+ * own key - `skip` derived from the command's `config` block, `include`/`exclude` hand-written in
32
+ * `CleanExtraKeys` because a `CommandOption` cannot say "a glob *or* a list of them".
33
+ *
34
+ * It is declared here rather than beside the command only because of the one-block rule above.
35
+ * rman's own commands put theirs next to themselves, augmenting a module path instead of a
36
+ * package name, which has no such limit.
37
+ */
38
+ namespace RmanConfig {
39
+ interface CommandConfigs extends RmanConfig.CommandContribution<ReturnType<typeof cleanCommand>, CleanExtraKeys> {
40
+ }
41
+ }
42
+ }
43
+ /**
44
+ * **The `npm` target's own `publish.*` block** - declared into the slot `publish` exports, exactly
45
+ * where the core's own `docker` target declares `publish.docker`.
46
+ *
47
+ * A *different* module from the block above, and that is the shape the fold made possible: while
48
+ * this shipped as `rman-node` everything had to augment the one package name `'rman'`, so a slot
49
+ * declared in `publish.command.ts` was reachable only by re-declaring it - which now reads as two
50
+ * interfaces of one name and is refused. Each interface is augmented where it lives.
51
+ */
52
+ declare module '../../../commands/publish.command.js' {
53
+ /**
54
+ * The `npm` publish target's own `publish.*` block, declared the way the core's `docker` target
55
+ * declares `publish.docker` - through the slot `publish` contributes, rather than into a central
56
+ * `PublishOptionsKeys` that the core owned.
57
+ *
58
+ * That is the config half of a target being a contribution: this package brings the flags, the
59
+ * registry check, *and* the keys, and none of it is written down in rman.
60
+ */
61
+ interface PublishTargetConfigs {
62
+ /** The `npm` target's block, named after the target exactly as the core's `docker` one is. */
63
+ npm?: RmanNodeConfig.NpmPublishOptions;
64
+ }
65
+ }
66
+ /**
67
+ * **The npm half of `SystemInfo`** - what `rman info` reports once a repository is a Node one. The
68
+ * runtime half is `augmentSystemInfo()` in the file beside this one, called when the built-in is
69
+ * registered rather than at import, so a repository that never named it reports no npm.
70
+ */
71
+ declare module '../../../services/system-info.js' {
72
+ namespace SystemInfo {
73
+ type PackageManager = CiService.PackageManager;
74
+ interface Options {
75
+ /**
76
+ * Report this package manager's version under `Binaries`, plus the `npmPackages` sections.
77
+ *
78
+ * Defaults to `.rmanrc "packageManager"` (read off `Options.repository`), then to `npm` -
79
+ * this package being installed is itself the statement that the repository is a Node one.
80
+ */
81
+ packageManager?: PackageManager;
82
+ }
83
+ }
84
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Adds the npm half of `SystemInfo` to rman's own types.
3
+ *
4
+ * The core's `Options` carries nothing language-specific on purpose, so `packageManager` is not a
5
+ * setting it hid behind a flag - it is a setting it does not have. Declaration merging is what lets
6
+ * this package add it as if it had always been there, `SystemInfo.PackageManager` included, without
7
+ * the core naming npm anywhere.
8
+ *
9
+ * The union itself is `CiService.PackageManager`, not a second copy: `ci` and `publish` already
10
+ * shell out to one of these, and two lists of the same four names drift the moment a fifth appears.
11
+ */
12
+ /** The import above is aliased precisely so this block can use the real name: declaration merging
13
+ * keys off the module specifier, not off any local binding, so `namespace SystemInfo` here *is*
14
+ * rman's - and nothing shadows anything. */
15
+ /** The `SystemInfo` types this file's wrapper relies on are declared in
16
+ * [`rman.augmentation.ts`](rman.augmentation.ts), with the rest of this plugin's - one
17
+ * `declare module` block per package, or the others stop applying. */
18
+ /**
19
+ * Turns the npm half on at runtime.
20
+ *
21
+ * Applied by wrapping rather than by a flag on the core service: "is this a Node repository" is not
22
+ * a question rman's core should be able to ask, and a `nodejs: true` option there would be exactly
23
+ * that question wearing a different hat. The wrapper translates `packageManager` into the `envinfo`
24
+ * categories the core knows nothing about.
25
+ */
26
+ export declare function augmentSystemInfo(): void;
@@ -0,0 +1,79 @@
1
+ import { SystemInfo as OrgSystemInfo } from '../../../services/system-info.js';
2
+ /**
3
+ * Adds the npm half of `SystemInfo` to rman's own types.
4
+ *
5
+ * The core's `Options` carries nothing language-specific on purpose, so `packageManager` is not a
6
+ * setting it hid behind a flag - it is a setting it does not have. Declaration merging is what lets
7
+ * this package add it as if it had always been there, `SystemInfo.PackageManager` included, without
8
+ * the core naming npm anywhere.
9
+ *
10
+ * The union itself is `CiService.PackageManager`, not a second copy: `ci` and `publish` already
11
+ * shell out to one of these, and two lists of the same four names drift the moment a fifth appears.
12
+ */
13
+ /** The import above is aliased precisely so this block can use the real name: declaration merging
14
+ * keys off the module specifier, not off any local binding, so `namespace SystemInfo` here *is*
15
+ * rman's - and nothing shadows anything. */
16
+ /** The `SystemInfo` types this file's wrapper relies on are declared in
17
+ * [`rman.augmentation.ts`](rman.augmentation.ts), with the rest of this plugin's - one
18
+ * `declare module` block per package, or the others stop applying. */
19
+ /**
20
+ * Turns the npm half on at runtime.
21
+ *
22
+ * Applied by wrapping rather than by a flag on the core service: "is this a Node repository" is not
23
+ * a question rman's core should be able to ask, and a `nodejs: true` option there would be exactly
24
+ * that question wearing a different hat. The wrapper translates `packageManager` into the `envinfo`
25
+ * categories the core knows nothing about.
26
+ */
27
+ export function augmentSystemInfo() {
28
+ const base = OrgSystemInfo.getSystemInfo;
29
+ /**
30
+ * Idempotent, and marked on **the function** rather than in a module-level flag: the flag made
31
+ * re-application impossible once anything had replaced `getSystemInfo` - a test substituting a
32
+ * stub, or a second augmentation layering on - so whether the wrapper was actually installed came
33
+ * down to import order. Asking the current implementation whether it is already wrapped cannot go
34
+ * stale that way.
35
+ */
36
+ if (base[AUGMENTED])
37
+ return;
38
+ /** A namespace's exported function is a property of a plain object at runtime, so this is an
39
+ * ordinary assignment - the ESM *binding* is immutable, the object it points at is not. */
40
+ const wrapped = options => {
41
+ /** An explicit argument first, then the repository's own config, then npm. The config step is
42
+ * why the core declares `Options.repository`: `rman info` passes only that, so without it a
43
+ * pnpm repository would silently be reported as an npm one. */
44
+ const packageManager = options?.packageManager ?? asPackageManager(options?.repository?.config?.packageManager) ?? 'npm';
45
+ return base({
46
+ ...options,
47
+ envinfo: {
48
+ /** Replaces the core's `Binaries: ['Node']` - `envinfo` categories merge by key, not by
49
+ * concatenation, so the whole list has to be restated. */
50
+ Binaries: ['Node', PACKAGE_MANAGER_BINARY[packageManager]],
51
+ /** This package's own version alongside rman's - when `info` is being read to work out why
52
+ * a command behaved oddly, which plugin version is installed is half the answer. */
53
+ npmPackages: ['rman', 'typescript'],
54
+ npmGlobalPackages: ['typescript'],
55
+ ...options?.envinfo,
56
+ },
57
+ });
58
+ };
59
+ wrapped[AUGMENTED] = true;
60
+ OrgSystemInfo.getSystemInfo = wrapped;
61
+ }
62
+ /** `.rmanrc "packageManager"` value -> the `Binaries` key `envinfo` recognizes for it (`npm`/
63
+ * `pnpm`/`bun` are lowercase, `Yarn` isn't - envinfo's own naming, not ours). */
64
+ const PACKAGE_MANAGER_BINARY = {
65
+ npm: 'npm',
66
+ yarn: 'Yarn',
67
+ pnpm: 'pnpm',
68
+ bun: 'bun',
69
+ };
70
+ /** A config value is whatever the file said, so an unrecognized one is ignored rather than used to
71
+ * index `PACKAGE_MANAGER_BINARY` and produce `Binaries: ['Node', undefined]`. */
72
+ function asPackageManager(value) {
73
+ return typeof value === 'string' && value in PACKAGE_MANAGER_BINARY
74
+ ? value
75
+ : undefined;
76
+ }
77
+ /** Marks an implementation as already wrapped. A symbol rather than a property name so it cannot
78
+ * collide with anything `envinfo` or a future core option carries. */
79
+ const AUGMENTED = Symbol.for('rman.node.systemInfo.augmented');
@@ -0,0 +1,131 @@
1
+ import type { ArgsOf } from '../../../index.js';
2
+ /** Hoisted out of the metadata literal so the handler can be annotated against them - see
3
+ * `RmanConfig.ArgsOf` for why an inferred `argv` and the metadata's own typo checking cannot both
4
+ * work in one signature. */
5
+ declare const COMMAND: "ci";
6
+ declare const config: {
7
+ packageManager: {
8
+ target: "cli";
9
+ cliName: string;
10
+ describe: string;
11
+ choices: readonly ["npm", "yarn", "pnpm", "bun"];
12
+ };
13
+ progress: {
14
+ target: "cli";
15
+ describe: string;
16
+ type: "boolean";
17
+ };
18
+ allowBranch: {
19
+ target: "cli";
20
+ cliName: string;
21
+ describe: string;
22
+ type: "string";
23
+ };
24
+ ignoreBranch: {
25
+ target: "cli";
26
+ cliName: string;
27
+ describe: string;
28
+ type: "string";
29
+ };
30
+ scope: {
31
+ target: "cli";
32
+ describe: string;
33
+ type: "string";
34
+ };
35
+ ignore: {
36
+ target: "cli";
37
+ describe: string;
38
+ type: "string";
39
+ };
40
+ platform: {
41
+ target: "cli";
42
+ describe: string;
43
+ type: "string";
44
+ };
45
+ deps: {
46
+ target: "cli";
47
+ describe: string;
48
+ type: "boolean";
49
+ };
50
+ dependents: {
51
+ target: "cli";
52
+ describe: string;
53
+ type: "boolean";
54
+ };
55
+ };
56
+ type Args = ArgsOf<typeof config, typeof COMMAND>;
57
+ /**
58
+ * `rman ci` - contributed by the `node` built-in rather than always present.
59
+ *
60
+ * **Declared, not built.** This was a `CustomCommand` with a hand-written `builder` chaining
61
+ * `applyBranchGuardOptions(applyPackageFilterOptions(cmd))`, which is the shape every built-in had
62
+ * before the options became data. A plugin's command uses the identical declaration now -
63
+ * `declareCommand` rather than `registerCommand`, which is the one difference and the reason for
64
+ * it: `registerCommand` pushes onto a module-level registry that `runCli` always walks, so a plugin
65
+ * using it would hand `ci` to repositories that are not Node ones.
66
+ *
67
+ * The repository arrives through `app` when the factory runs (in `cli.ts`, after `Repository.create`
68
+ * has attached one), rather than through a `CommandContext` per invocation.
69
+ */
70
+ declare const ciCommand: (app: import("../../../index.js").RmanApplication) => {
71
+ command: "ci";
72
+ describe: string;
73
+ /** Read, not owned: `packageManager` is a root-level key this package declares in
74
+ * `NodeConfigKeys`, and the `npm` publish target reads it too. */
75
+ configKeys: string[];
76
+ config: {
77
+ packageManager: {
78
+ target: "cli";
79
+ cliName: string;
80
+ describe: string;
81
+ choices: readonly ["npm", "yarn", "pnpm", "bun"];
82
+ };
83
+ progress: {
84
+ target: "cli";
85
+ describe: string;
86
+ type: "boolean";
87
+ };
88
+ allowBranch: {
89
+ target: "cli";
90
+ cliName: string;
91
+ describe: string;
92
+ type: "string";
93
+ };
94
+ ignoreBranch: {
95
+ target: "cli";
96
+ cliName: string;
97
+ describe: string;
98
+ type: "string";
99
+ };
100
+ scope: {
101
+ target: "cli";
102
+ describe: string;
103
+ type: "string";
104
+ };
105
+ ignore: {
106
+ target: "cli";
107
+ describe: string;
108
+ type: "string";
109
+ };
110
+ platform: {
111
+ target: "cli";
112
+ describe: string;
113
+ type: "string";
114
+ };
115
+ deps: {
116
+ target: "cli";
117
+ describe: string;
118
+ type: "boolean";
119
+ };
120
+ dependents: {
121
+ target: "cli";
122
+ describe: string;
123
+ type: "boolean";
124
+ };
125
+ };
126
+ examples: {
127
+ command: string;
128
+ }[];
129
+ handler: (args: Args) => Promise<void>;
130
+ };
131
+ export default ciCommand;