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
@@ -1 +1,78 @@
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
+ }
62
+ /**
63
+ * Identity helper for authoring a `.rmanrc.cjs`/`.mjs`/`.js` config with full type-checking and
64
+ * autocomplete - the same `defineConfig` pattern Vite/Vitest use. Returns `config` completely
65
+ * unchanged; this exists purely so TypeScript can infer/check against `RmanConfig`, not for any
66
+ * runtime behavior:
67
+ *
68
+ * // .rmanrc.mjs
69
+ * import { defineConfig } from 'rman';
70
+ * export default defineConfig({ packageManager: 'pnpm' });
71
+ *
72
+ * // .rmanrc.cjs
73
+ * const { defineConfig } = require('rman');
74
+ * module.exports = defineConfig({ packageManager: 'pnpm' });
75
+ */
76
+ export function defineConfig(config) {
77
+ return config;
78
+ }
package/package.json CHANGED
@@ -1,11 +1,13 @@
1
1
  {
2
2
  "name": "rman",
3
3
  "description": "Repository manager",
4
- "version": "1.3.0",
4
+ "version": "2.0.0-beta.3",
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",
10
+ "@jsopen/objects": "^2.3.4",
9
11
  "ansi-colors": "^4.1.3",
10
12
  "cross-dirname": "^0.1.0",
11
13
  "easy-table": "^1.2.0",
@@ -1,4 +1,4 @@
1
- import type { Package } from '../core/package.js';
1
+ import type { Package } from '../core/classes/package.js';
2
2
  import type { GitHelper } from '../utils/git.js';
3
3
  /**
4
4
  * Release boundaries and the tag names that mark them - **the single source for both directions**.
@@ -24,7 +24,7 @@ export declare namespace ChangeHashService {
24
24
  *
25
25
  * The keyword used to be `"npm"`, which named a *source* - and the wrong one: auto-detection is
26
26
  * mostly git, and the registry it may consult is now the ecosystem's business (see
27
- * `ManifestProvider.publishedVersion`). What is being chosen here is a *mode*, so it is spelled
27
+ * `Plugin.publishedVersion`). What is being chosen here is a *mode*, so it is spelled
28
28
  * as one. **A rename, not an alias**: `--from npm` now means a ref literally called `npm`,
29
29
  * which is what it should have meant all along.
30
30
  */
@@ -39,11 +39,33 @@ export declare namespace ChangeHashService {
39
39
  * the file doesn't exist. */
40
40
  catchUpFile?: string;
41
41
  }
42
- /** `.rmanrc changelog.tagPattern` (cascaded, per-package overridable) - a glob for this package's
43
- * release tags. `{name}` (if present) is replaced with the package's own name, e.g. `{name}@*`
44
- * for independent per-package versioning (`@scope/pkg@1.2.3`, the same scheme lerna/changesets
45
- * use - `@`/`/` are both fine in a git tag name). Without `{name}`, it's a single repo-wide tag
46
- * shared by every package (e.g. the default `v*`). */
42
+ /**
43
+ * `.rmanrc changelog.tagPattern` (cascaded, per-package overridable) - a glob for this package's
44
+ * release tags. `{name}` (if present) is replaced with the package's own name, e.g. `{name}@*`
45
+ * for independent per-package versioning (`@scope/pkg@1.2.3`, the same scheme lerna/changesets
46
+ * use - `@`/`/` are both fine in a git tag name). Without `{name}`, it's a single repo-wide tag
47
+ * shared by every package (`v*`).
48
+ *
49
+ * **Undeclared, the default is derived from how many version lines the repository has**, not
50
+ * fixed at `v*`. A repo-wide pattern has no `{name}`, so `findLatestTag` resolves it with `git
51
+ * describe --match` - the nearest tag HEAD descends from, whichever package it belongs to. That
52
+ * is exactly right while every package shares one line and **silently wrong** the moment they do
53
+ * not: measured on a two-line repository, releasing `pkg-a` put `v1.1.0` on HEAD, and `pkg-b` -
54
+ * which had a committed, unreleased `fix:` of its own sitting behind that tag - reported
55
+ * `no-change` and shipped nothing. Under `{name}@*` the same repository answers `bump 1.0.0 ->
56
+ * 1.0.1, changed since pkg-b@1.0.0`.
57
+ *
58
+ * So the choice is not a preference and should never have been one to remember: a repo-wide tag
59
+ * can only name a release that the whole repository shares. rman already decides the root's
60
+ * versioning scheme structurally from the same group count (`usesCalendarVersion`) - this is that
61
+ * rule applied to the other half of a release.
62
+ *
63
+ * **The root keeps `v*`.** It is never a member of any group, so the count says nothing about
64
+ * it, and the one caller that asks (`github-release`'s `releaseTagGlob`) only does so when the
65
+ * root is *not* on a calendar version - which is the single-line case, where `v*` is the answer
66
+ * anyway. With several lines the repository's release carries its own `version.releaseTagPattern`
67
+ * instead.
68
+ */
47
69
  function tagPattern(pkg: Package): string;
48
70
  /** This package's most recent release tag - the `{name}`-bearing pattern looks up that package's
49
71
  * *own* tags directly (newest by version sort); a repo-wide pattern instead finds the nearest tag
@@ -51,6 +73,18 @@ export declare namespace ChangeHashService {
51
73
  * tagged at all (a fresh package, or one that's never been released). Shared by `changelog`
52
74
  * (reading the last-documented version) and `version` (finding the boundary a bump measures
53
75
  * "since"). */
76
+ /**
77
+ * **Every pattern that may name this package's releases**, in precedence order - its own, and,
78
+ * only where the pattern was derived, the shared `v*` a repository used before it grew a second
79
+ * version line. One list, because two questions need it and must not disagree: `findLatestTag`
80
+ * picks the boundary off it, and `changelog`'s `splitByRelease` cuts the range at the tags it
81
+ * matches.
82
+ *
83
+ * They *did* disagree, for one commit: the boundary fell back to the shared tag while the split
84
+ * still looked only for `{name}@*`, which a repository mid-transition has none of - so a backfill
85
+ * reaching across twelve releases found no tag to cut at and rendered all of them as one.
86
+ */
87
+ function releaseTagPatterns(pkg: Package): string[];
54
88
  function findLatestTag(git: GitHelper, pkg: Package): Promise<string | undefined>;
55
89
  /** The forward direction of `findLatestTag`: expands `pkg`'s (cascaded) `.rmanrc
56
90
  * changelog.tagPattern` into the concrete tag name `version` belongs under - `{name}` becomes the
@@ -76,7 +110,7 @@ export declare namespace ChangeHashService {
76
110
  * so all three commands agree on "since when" for any repo whose tags are the ones `rman version`
77
111
  * actually created; (2) failing that (no tag at all yet - e.g. onboarding `rman` onto a repo with
78
112
  * real release history but no `rman`-created tags), whatever this package's **own ecosystem**
79
- * reports as its published version (`ManifestProvider.publishedVersion`), mapped to a git tag via
113
+ * reports as its published version (`Plugin.publishedVersion`), mapped to a git tag via
80
114
  * `.rmanrc changelog.tagPattern` and used only if that tag actually exists. Either way, if
81
115
  * `catchUpFile` is given and exists, the result is widened to also cover anything that file
82
116
  * hasn't caught up on yet (see its doc comment). Returns `undefined` when nothing can be resolved
@@ -1,4 +1,5 @@
1
- import { Manifest } from '../core/manifest.js';
1
+ import { Manifest } from '../core/interfaces/manifest.js';
2
+ import { versionLineCount } from '../utils/version-group.js';
2
3
  /**
3
4
  * Release boundaries and the tag names that mark them - **the single source for both directions**.
4
5
  *
@@ -16,14 +17,35 @@ export var ChangeHashService;
16
17
  /** The one keyword `from` accepts instead of a ref: "work it out per package". Omitting `from`
17
18
  * means the same thing - this exists so a pipeline can say it explicitly. */
18
19
  ChangeHashService.AUTO = 'auto';
19
- /** `.rmanrc changelog.tagPattern` (cascaded, per-package overridable) - a glob for this package's
20
- * release tags. `{name}` (if present) is replaced with the package's own name, e.g. `{name}@*`
21
- * for independent per-package versioning (`@scope/pkg@1.2.3`, the same scheme lerna/changesets
22
- * use - `@`/`/` are both fine in a git tag name). Without `{name}`, it's a single repo-wide tag
23
- * shared by every package (e.g. the default `v*`). */
20
+ /**
21
+ * `.rmanrc changelog.tagPattern` (cascaded, per-package overridable) - a glob for this package's
22
+ * release tags. `{name}` (if present) is replaced with the package's own name, e.g. `{name}@*`
23
+ * for independent per-package versioning (`@scope/pkg@1.2.3`, the same scheme lerna/changesets
24
+ * use - `@`/`/` are both fine in a git tag name). Without `{name}`, it's a single repo-wide tag
25
+ * shared by every package (`v*`).
26
+ *
27
+ * **Undeclared, the default is derived from how many version lines the repository has**, not
28
+ * fixed at `v*`. A repo-wide pattern has no `{name}`, so `findLatestTag` resolves it with `git
29
+ * describe --match` - the nearest tag HEAD descends from, whichever package it belongs to. That
30
+ * is exactly right while every package shares one line and **silently wrong** the moment they do
31
+ * not: measured on a two-line repository, releasing `pkg-a` put `v1.1.0` on HEAD, and `pkg-b` -
32
+ * which had a committed, unreleased `fix:` of its own sitting behind that tag - reported
33
+ * `no-change` and shipped nothing. Under `{name}@*` the same repository answers `bump 1.0.0 ->
34
+ * 1.0.1, changed since pkg-b@1.0.0`.
35
+ *
36
+ * So the choice is not a preference and should never have been one to remember: a repo-wide tag
37
+ * can only name a release that the whole repository shares. rman already decides the root's
38
+ * versioning scheme structurally from the same group count (`usesCalendarVersion`) - this is that
39
+ * rule applied to the other half of a release.
40
+ *
41
+ * **The root keeps `v*`.** It is never a member of any group, so the count says nothing about
42
+ * it, and the one caller that asks (`github-release`'s `releaseTagGlob`) only does so when the
43
+ * root is *not* on a calendar version - which is the single-line case, where `v*` is the answer
44
+ * anyway. With several lines the repository's release carries its own `version.releaseTagPattern`
45
+ * instead.
46
+ */
24
47
  function tagPattern(pkg) {
25
- const cfg = pkg.config?.changelog;
26
- return typeof cfg?.tagPattern === 'string' && cfg.tagPattern ? cfg.tagPattern : DEFAULT_TAG_PATTERN;
48
+ return resolvePattern(pkg).pattern;
27
49
  }
28
50
  ChangeHashService.tagPattern = tagPattern;
29
51
  /** This package's most recent release tag - the `{name}`-bearing pattern looks up that package's
@@ -32,10 +54,49 @@ export var ChangeHashService;
32
54
  * tagged at all (a fresh package, or one that's never been released). Shared by `changelog`
33
55
  * (reading the last-documented version) and `version` (finding the boundary a bump measures
34
56
  * "since"). */
57
+ /**
58
+ * **Every pattern that may name this package's releases**, in precedence order - its own, and,
59
+ * only where the pattern was derived, the shared `v*` a repository used before it grew a second
60
+ * version line. One list, because two questions need it and must not disagree: `findLatestTag`
61
+ * picks the boundary off it, and `changelog`'s `splitByRelease` cuts the range at the tags it
62
+ * matches.
63
+ *
64
+ * They *did* disagree, for one commit: the boundary fell back to the shared tag while the split
65
+ * still looked only for `{name}@*`, which a repository mid-transition has none of - so a backfill
66
+ * reaching across twelve releases found no tag to cut at and rendered all of them as one.
67
+ */
68
+ function releaseTagPatterns(pkg) {
69
+ const { pattern, derived } = resolvePattern(pkg);
70
+ const expanded = pattern.replace('{name}', pkg.name);
71
+ return derived && expanded !== SHARED_TAG_PATTERN ? [expanded, SHARED_TAG_PATTERN] : [expanded];
72
+ }
73
+ ChangeHashService.releaseTagPatterns = releaseTagPatterns;
35
74
  async function findLatestTag(git, pkg) {
36
- const pattern = tagPattern(pkg);
75
+ const { pattern, derived } = resolvePattern(pkg);
37
76
  const expanded = pattern.replace('{name}', pkg.name);
38
- return pattern.includes('{name}') ? (await git.listTags(expanded))[0] : await git.describeTag(expanded);
77
+ if (!pattern.includes('{name}'))
78
+ return git.describeTag(expanded);
79
+ const own = (await git.listTags(expanded))[0];
80
+ if (own || !derived)
81
+ return own;
82
+ /**
83
+ * **The bridge across the default changing.** A repository that released under `v*` and has
84
+ * since grown a second version line has no `{name}` tag for this package yet - and reading the
85
+ * whole history instead would re-propose everything ever committed. Measured on a real
86
+ * four-package repository: every package came back on `unreleased commits` and three of them
87
+ * jumped a major, because some commit in the full history said `feat!:`.
88
+ *
89
+ * Falling back to the repo-wide tag gives exactly the boundary that *was* correct - before the
90
+ * split, every package genuinely shared it - so the first run after the split reads the same
91
+ * commits it would have read yesterday, and writes a `{name}` tag that every run after it
92
+ * finds directly. One transition, no manual tagging, no invented release.
93
+ *
94
+ * **Only when the pattern was derived.** A repository that asked for `{name}@*` in its own
95
+ * `.rmanrc` has said what names its tags; borrowing a `v*` tag it never asked about could hand
96
+ * a package a boundary belonging to something else entirely, and reading too little is the
97
+ * failure that ships nothing and says nothing.
98
+ */
99
+ return git.describeTag(SHARED_TAG_PATTERN);
39
100
  }
40
101
  ChangeHashService.findLatestTag = findLatestTag;
41
102
  /** The forward direction of `findLatestTag`: expands `pkg`'s (cascaded) `.rmanrc
@@ -77,7 +138,7 @@ export var ChangeHashService;
77
138
  * so all three commands agree on "since when" for any repo whose tags are the ones `rman version`
78
139
  * actually created; (2) failing that (no tag at all yet - e.g. onboarding `rman` onto a repo with
79
140
  * real release history but no `rman`-created tags), whatever this package's **own ecosystem**
80
- * reports as its published version (`ManifestProvider.publishedVersion`), mapped to a git tag via
141
+ * reports as its published version (`Plugin.publishedVersion`), mapped to a git tag via
81
142
  * `.rmanrc changelog.tagPattern` and used only if that tag actually exists. Either way, if
82
143
  * `catchUpFile` is given and exists, the result is widened to also cover anything that file
83
144
  * hasn't caught up on yet (see its doc comment). Returns `undefined` when nothing can be resolved
@@ -91,7 +152,7 @@ export var ChangeHashService;
91
152
  let tagHash = await findLatestTag(git, pkg);
92
153
  if (!tagHash) {
93
154
  /** Through the package's own ecosystem, not through npm: which registry (if any) knows about
94
- * this package is the `ManifestProvider`'s answer, and in a polyglot repository it differs
155
+ * this package is the `Plugin`'s manifest members's answer, and in a polyglot repository it differs
95
156
  * per package. A repository naming no plugin gets `undefined` here and git tags decide
96
157
  * alone. */
97
158
  const publishedVersion = await Manifest.publishedVersion(pkg);
@@ -109,4 +170,22 @@ export var ChangeHashService;
109
170
  }
110
171
  ChangeHashService.detect = detect;
111
172
  })(ChangeHashService || (ChangeHashService = {}));
112
- const DEFAULT_TAG_PATTERN = 'v*';
173
+ /** One tag for the whole repository - right while every package releases on one line. */
174
+ const SHARED_TAG_PATTERN = 'v*';
175
+ /** One tag per package - the scheme lerna and changesets use, and the only one that can name a
176
+ * release when a repository has more than one version line. */
177
+ const PER_PACKAGE_TAG_PATTERN = '{name}@*';
178
+ /**
179
+ * The pattern plus **where it came from**, which `findLatestTag` needs and callers do not: the
180
+ * bridge to a repo-wide tag applies only where rman changed the answer itself, never over a
181
+ * repository's own declaration.
182
+ */
183
+ function resolvePattern(pkg) {
184
+ const configured = pkg.config?.changelog?.tagPattern;
185
+ if (typeof configured === 'string' && configured)
186
+ return { pattern: configured, derived: false };
187
+ if (pkg.isRoot)
188
+ return { pattern: SHARED_TAG_PATTERN, derived: true };
189
+ const several = versionLineCount(pkg) > 1;
190
+ return { pattern: several ? PER_PACKAGE_TAG_PATTERN : SHARED_TAG_PATTERN, derived: true };
191
+ }
@@ -1,20 +1,74 @@
1
- import type { Package } from '../core/package.js';
2
- import type { Repository } from '../core/repository.js';
1
+ import type { Package } from '../core/classes/package.js';
2
+ import { Service } from '../core/classes/service.js';
3
3
  import { type PackageFilterOptions } from '../utils/package-filter.js';
4
+ /**
5
+ * A service class - see `ListService` for the shape and `Service` for the three measured
6
+ * consequences a namespace had. `repository` left the signature because the application carries it.
7
+ */
8
+ export declare class ChangelogService extends Service {
9
+ /**
10
+ * Same as `getEntries`, and additionally - for every returned entry, when `options.write` is
11
+ * set - prepends `entry.content` into that package's own changelog file (see `ChangelogService.Entry.filePath`).
12
+ * Still pure with respect to console output: writing a file is a real, callable-for-its-own-
13
+ * sake side effect (a "save this" request), not presentation, so it stays here rather than in
14
+ * the CLI command - printing what happened is the command's job.
15
+ */
16
+ generateToFile(options?: ChangelogService.Options): Promise<ChangelogService.Entry[]>;
17
+ /**
18
+ * Computes a changelog entry per package (root included) from real commits only - either
19
+ * everything since a given `--from <hash>` (applied the same way to every package), or, by
20
+ * default, auto-detected per package instead (see `detectChangeHash`). Pure: returns the
21
+ * entries, never prints or touches `CHANGELOG.md` - see `generate` for that.
22
+ *
23
+ * A commit is attributed to every package its files fall under (root included, for anything
24
+ * outside every package) - unless it's broad enough to count as a repo-wide change (see
25
+ * `BROAD_COMMIT_THRESHOLD`/`ownersOf`), in which case it goes to root alone instead of being
26
+ * repeated verbatim across most of the repository. Subject lines are grouped ✨ Features/🐛 Bug
27
+ * Fixes/🔧 Other Changes on a best-effort Conventional Commits read; anything that doesn't parse
28
+ * just lands in Other Changes as-is, so a repo that doesn't follow that convention still gets a
29
+ * usable list. `.rmanrc changelog.ignoreTypes` (cascaded, e.g. `[chore, dev]`) drops commits of
30
+ * those types entirely instead - see `ignoreTypesConfig`. A release marker - a bare version-bump
31
+ * commit (`"6.0.1"`), or any of the messages `version` itself writes - is always dropped
32
+ * outright, regardless of `ignoreTypes`; see `isReleaseCommit`.
33
+ *
34
+ * By default (or with `--from npm` explicitly), the boundary is auto-detected per package
35
+ * instead of one shared one - see `detectChangeHash`. A package that can't be resolved this way
36
+ * (unpublished, no network, no matching tag) has never been released at all, so its whole
37
+ * history counts as unreleased - the same view `version` takes, so a first-ever release still
38
+ * produces a real changelog. Packages that end up resolving to
39
+ * the same hash (an explicit one, or several packages sharing one tag under fixed versioning)
40
+ * only have their commits fetched once, not once per package.
41
+ *
42
+ * Formatting comes from `.rmanrc changelog.template` - a *path* to a template file (not the
43
+ * template text itself, to keep `.rmanrc` readable), with `{{package}}`/`{{version}}`/
44
+ * `{{date}}`/`{{commits}}` (the full grouped block) and `{{features}}`/`{{fixes}}`/`{{other}}`
45
+ * (their bullet lists alone, for templates that want their own headings/order) - see
46
+ * `resolveTemplate`. `{{package}}` for the repository root is `"<repo dir name> repository"`
47
+ * (e.g. "sqb repository"), not its raw package.json name - which is often a private,
48
+ * non-published placeholder (`"sqb.v4"`) that reads like a stray version marker rather than a
49
+ * recognizable label. `{{version}}` comes from git tags, not package.json (which can drift out
50
+ * of sync with what's actually been released) - see `resolveVersion`/`.rmanrc
51
+ * changelog.tagPattern`.
52
+ *
53
+ * Run from inside a single package's own directory, it only covers that package unless
54
+ * `options.fromRoot` says otherwise (see `Repository.currentPackage`).
55
+ */
56
+ getEntries(options?: ChangelogService.Options): Promise<ChangelogService.Entry[]>;
57
+ }
4
58
  export declare namespace ChangelogService {
5
59
  interface Options extends PackageFilterOptions {
6
60
  /** Generate the changelog since this commit/hash - applied the same way to every package.
7
61
  * Default (also `"auto"` explicitly): auto-detect it per package instead, from that package's
8
62
  * own most recent release tag first - the same lookup `VersionService`/`changed` use, so this
9
63
  * never disagrees with them - falling back to whatever its own ecosystem's registry reports
10
- * only when it has no tag yet (`ManifestProvider.publishedVersion`, and only ever to guess a
64
+ * only when it has no tag yet (`Plugin.publishedVersion`, and only ever to guess a
11
65
  * tag name - see `detectChangeHash`); a package this can't be resolved for either way (never
12
66
  * tagged, unpublished, no plugin) has never been released at all, so its whole history counts
13
67
  * as unreleased - the same view `version` takes. */
14
68
  from?: string;
15
69
  /** Generate for the whole repository even when the current directory is inside a single
16
70
  * package (which otherwise scopes it to just that package) - see `Repository.currentPackage`. */
17
- root?: boolean;
71
+ fromRoot?: boolean;
18
72
  /** Where a package's changelog file lives, relative to *that package's own* directory -
19
73
  * default `'CHANGELOG.md'`. Applies the same way to every package; for a package that wants
20
74
  * its own filename instead, use `.rmanrc changelog.filePath` (cascaded, per-package
@@ -35,6 +89,21 @@ export declare namespace ChangelogService {
35
89
  * the number and has to say so, otherwise every entry ends up labelled with the *previous*
36
90
  * release's version. */
37
91
  version?: string;
92
+ /**
93
+ * Set by `generateToFile`; there is no reason for a caller to pass it. It tells `getEntries`
94
+ * that the entries are about to be **appended to a file**, which is what makes the file's own
95
+ * record of where it stopped the right boundary - see `resolveBoundary`. A print run asks a
96
+ * different question and deliberately ignores all of it.
97
+ */
98
+ write?: boolean;
99
+ /** Where this package's changelog begins - a version or release tag, a `YYYY-MM-DD` date, or a
100
+ * commit. Releases below it are left out. `.rmanrc changelog.startingAt` when omitted; see
101
+ * `resolveStartingPoint` for how the three forms are told apart. */
102
+ startingAt?: string;
103
+ /** Whether the not-yet-released commits get an entry - `.rmanrc changelog.unreleased` when
104
+ * omitted, and `true` when neither says. A caller passing `version` always gets it, since that
105
+ * names the release being cut; see `resolveUnreleased`. */
106
+ unreleased?: boolean;
38
107
  }
39
108
  /** One package's (root included) generated changelog entry - what `getEntries`/`generate`
40
109
  * return. */
@@ -46,6 +115,19 @@ export declare namespace ChangelogService {
46
115
  /** `options.version` when the caller gave one, otherwise resolved from git tags rather than
47
116
  * package.json - see `resolveVersion`. */
48
117
  version: string;
118
+ /** The last commit this entry covers. `generateToFile` writes it into the file as the marker
119
+ * the next run starts from - see `Entry.filePath`. */
120
+ documentedUpTo: string;
121
+ /** Every section this entry renders, in the order it renders them - the heading a repository
122
+ * chose through `.rmanrc changelog.titles`, and the lines under it. */
123
+ sections: {
124
+ title: string;
125
+ lines: string[];
126
+ }[];
127
+ /** The three buckets `Entry` carried before headings were configurable, derived from
128
+ * `sections` so they keep meaning what they meant: whatever `feat` and `fix` are listed under,
129
+ * and everything else together. A repository that adds a section of its own finds it in
130
+ * `other` - the only honest slot for it in a shape that has three. */
49
131
  features: string[];
50
132
  fixes: string[];
51
133
  other: string[];
@@ -55,52 +137,9 @@ export declare namespace ChangelogService {
55
137
  * package's own directory - see `GetOptions.filePath`. */
56
138
  filePath: string;
57
139
  }
58
- /**
59
- * Same as `getEntries`, and additionally - for every returned entry, when `options.write` is
60
- * set - prepends `entry.content` into that package's own changelog file (see `Entry.filePath`).
61
- * Still pure with respect to console output: writing a file is a real, callable-for-its-own-
62
- * sake side effect (a "save this" request), not presentation, so it stays here rather than in
63
- * the CLI command - printing what happened is the command's job.
64
- */
65
- function generateToFile(repository: Repository, options?: Options): Promise<Entry[]>;
66
- /**
67
- * Computes a changelog entry per package (root included) from real commits only - either
68
- * everything since a given `--from <hash>` (applied the same way to every package), or, by
69
- * default, auto-detected per package instead (see `detectChangeHash`). Pure: returns the
70
- * entries, never prints or touches `CHANGELOG.md` - see `generate` for that.
71
- *
72
- * A commit is attributed to every package its files fall under (root included, for anything
73
- * outside every package) - unless it's broad enough to count as a repo-wide change (see
74
- * `BROAD_COMMIT_THRESHOLD`/`ownersOf`), in which case it goes to root alone instead of being
75
- * repeated verbatim across most of the repository. Subject lines are grouped ✨ Features/🐛 Bug
76
- * Fixes/🔧 Other Changes on a best-effort Conventional Commits read; anything that doesn't parse
77
- * just lands in Other Changes as-is, so a repo that doesn't follow that convention still gets a
78
- * usable list. `.rmanrc changelog.ignoreTypes` (cascaded, e.g. `[chore, dev]`) drops commits of
79
- * those types entirely instead - see `ignoreTypesConfig`. A release marker - a bare version-bump
80
- * commit (`"6.0.1"`), or any of the messages `version` itself writes - is always dropped
81
- * outright, regardless of `ignoreTypes`; see `isReleaseCommit`.
82
- *
83
- * By default (or with `--from npm` explicitly), the boundary is auto-detected per package
84
- * instead of one shared one - see `detectChangeHash`. A package that can't be resolved this way
85
- * (unpublished, no network, no matching tag) has never been released at all, so its whole
86
- * history counts as unreleased - the same view `version` takes, so a first-ever release still
87
- * produces a real changelog. Packages that end up resolving to
88
- * the same hash (an explicit one, or several packages sharing one tag under fixed versioning)
89
- * only have their commits fetched once, not once per package.
90
- *
91
- * Formatting comes from `.rmanrc changelog.template` - a *path* to a template file (not the
92
- * template text itself, to keep `.rmanrc` readable), with `{{package}}`/`{{version}}`/
93
- * `{{date}}`/`{{commits}}` (the full grouped block) and `{{features}}`/`{{fixes}}`/`{{other}}`
94
- * (their bullet lists alone, for templates that want their own headings/order) - see
95
- * `resolveTemplate`. `{{package}}` for the repository root is `"<repo dir name> repository"`
96
- * (e.g. "sqb repository"), not its raw package.json name - which is often a private,
97
- * non-published placeholder (`"sqb.v4"`) that reads like a stray version marker rather than a
98
- * recognizable label. `{{version}}` comes from git tags, not package.json (which can drift out
99
- * of sync with what's actually been released) - see `resolveVersion`/`.rmanrc
100
- * changelog.tagPattern`.
101
- *
102
- * Run from inside a single package's own directory, it only covers that package unless
103
- * `options.root` says otherwise (see `Repository.currentPackage`).
104
- */
105
- function getEntries(repository: Repository, options?: Options): Promise<Entry[]>;
140
+ }
141
+ declare module '../core/classes/service.js' {
142
+ interface ServiceMap {
143
+ changelog: ChangelogService;
144
+ }
106
145
  }