rman 1.0.12 → 1.1.1

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 (98) hide show
  1. package/README.md +90 -70
  2. package/cli.js +225 -14
  3. package/commands/build.command.js +1 -0
  4. package/commands/changed.command.js +2 -2
  5. package/commands/changelog.command.js +13 -15
  6. package/commands/config.command.js +60 -0
  7. package/commands/diff.command.js +9 -4
  8. package/commands/exec.command.js +2 -8
  9. package/commands/github-release.command.js +1 -0
  10. package/commands/info.command.d.ts +9 -0
  11. package/commands/info.command.js +12 -2
  12. package/commands/run.command.js +5 -8
  13. package/commands/test.command.js +1 -0
  14. package/commands/version.command.js +53 -14
  15. package/constants.js +1 -1
  16. package/core/config.d.ts +142 -13
  17. package/core/config.js +266 -43
  18. package/core/custom-command.d.ts +133 -0
  19. package/core/custom-command.js +99 -0
  20. package/core/extends-config.d.ts +27 -0
  21. package/core/extends-config.js +89 -0
  22. package/core/manifest.d.ts +222 -0
  23. package/core/manifest.js +150 -0
  24. package/core/merge-config.d.ts +57 -0
  25. package/core/merge-config.js +146 -0
  26. package/core/package.d.ts +73 -7
  27. package/core/package.js +86 -24
  28. package/core/plugin.d.ts +112 -0
  29. package/core/plugin.js +189 -0
  30. package/core/repository.d.ts +77 -1
  31. package/core/repository.js +242 -129
  32. package/core/resolve-target.d.ts +12 -0
  33. package/core/resolve-target.js +33 -0
  34. package/core/version-scheme.d.ts +134 -0
  35. package/core/version-scheme.js +148 -0
  36. package/core/workspace.d.ts +68 -0
  37. package/core/workspace.js +83 -0
  38. package/index.d.ts +54 -8
  39. package/index.js +42 -7
  40. package/interfaces/rman-config.interface.d.ts +171 -36
  41. package/package.json +15 -7
  42. package/services/change-hash.service.d.ts +88 -0
  43. package/services/change-hash.service.js +112 -0
  44. package/services/changelog.service.d.ts +8 -13
  45. package/services/changelog.service.js +12 -11
  46. package/services/conventional-commits.service.d.ts +73 -0
  47. package/services/conventional-commits.service.js +116 -0
  48. package/services/docker-publish.service.js +1 -1
  49. package/services/exec.service.js +1 -1
  50. package/services/github-release.service.d.ts +2 -2
  51. package/services/github-release.service.js +10 -5
  52. package/services/list.service.js +5 -2
  53. package/services/run.service.d.ts +60 -0
  54. package/services/run.service.js +109 -65
  55. package/services/system-info.d.ts +22 -7
  56. package/services/system-info.js +8 -23
  57. package/services/version-plan.service.d.ts +244 -0
  58. package/services/version-plan.service.js +414 -0
  59. package/services/version.service.d.ts +92 -82
  60. package/services/version.service.js +219 -433
  61. package/services.d.ts +5 -3
  62. package/services.js +5 -3
  63. package/utils/bin-path.d.ts +59 -0
  64. package/utils/bin-path.js +82 -0
  65. package/utils/child-tracker.d.ts +16 -0
  66. package/utils/child-tracker.js +30 -0
  67. package/utils/exec.d.ts +13 -2
  68. package/utils/exec.js +17 -17
  69. package/utils/git.d.ts +9 -3
  70. package/utils/git.js +10 -2
  71. package/utils/package-filter.d.ts +33 -2
  72. package/utils/package-filter.js +47 -7
  73. package/utils/release-version.js +3 -3
  74. package/utils/run-bin.d.ts +46 -0
  75. package/utils/run-bin.js +63 -0
  76. package/utils/version-stamp.d.ts +14 -6
  77. package/utils/version-stamp.js +25 -13
  78. package/commands/ci.command.js +0 -30
  79. package/commands/clean.command.d.ts +0 -3
  80. package/commands/clean.command.js +0 -36
  81. package/commands/publish.command.d.ts +0 -3
  82. package/commands/publish.command.js +0 -225
  83. package/rmanrc.schema.json +0 -392
  84. package/services/ci.service.d.ts +0 -40
  85. package/services/ci.service.js +0 -204
  86. package/services/clean.service.d.ts +0 -42
  87. package/services/clean.service.js +0 -226
  88. package/services/publish.service.d.ts +0 -79
  89. package/services/publish.service.js +0 -273
  90. package/utils/change-hash.d.ts +0 -68
  91. package/utils/change-hash.js +0 -98
  92. package/utils/conventional-commits.d.ts +0 -52
  93. package/utils/conventional-commits.js +0 -90
  94. package/utils/npm-run-path.d.ts +0 -67
  95. package/utils/npm-run-path.js +0 -63
  96. package/utils/workspace-range.d.ts +0 -17
  97. package/utils/workspace-range.js +0 -28
  98. /package/commands/{ci.command.d.ts → config.command.d.ts} +0 -0
@@ -1,32 +1,152 @@
1
+ import type { RmanPlugin } from '../core/plugin.js';
2
+ /**
3
+ * Adds a `+key` alongside every key of `T`, which **appends** to whatever that key already resolved
4
+ * to instead of replacing it - see `mergeConfig`.
5
+ *
6
+ * Generated by key remapping rather than written out, so a key added to the interface gets its
7
+ * append form automatically and the two can never drift apart.
8
+ */
9
+ export type WithAppend<T> = {
10
+ [K in keyof T as `+${K & string}`]?: T[K];
11
+ };
1
12
  /**
2
13
  * The shape of `.rmanrc`/`.rmanrc.yml`/`.rmanrc.cjs`/`.mjs`/`.js` (and `package.json`'s own
3
- * `"rman"` key) - see docs/api.md#configuration-rmanrc-rmanrcyml for the full reference. Every
14
+ * `"rman"` key) - see docs/rman.md#configuration-rmanrc-rmanrcyml for the full reference. Every
4
15
  * field is optional and cascades from the repository root down to each package's own directory.
5
16
  * Purely a typing aid (used by `defineConfig` below, and importable on its own for a `.rmanrc.ts`/
6
17
  * `.mts` authored config, or a plain `: RmanConfig` annotation) - never read by rman itself, which
7
18
  * only ever sees the plain JS object a JS config file exports.
8
19
  */
9
- export interface RmanConfig {
10
- packageManager?: 'npm' | 'yarn' | 'pnpm' | 'bun';
20
+ export interface RmanConfig extends RmanConfigKeys, WithAppend<RmanConfigKeys> {
21
+ /**
22
+ * Configs to inherit from, merged **underneath** this one - a shared package
23
+ * (`"@panates/rman-monorepo"`), a relative path, or an array applied in declaration order.
24
+ *
25
+ * A bare name resolves through *this* file's own `node_modules`, so a subpath works too
26
+ * (`"@panates/rman-monorepo/strict"`). The target may be YAML, JSON, or a module exporting a
27
+ * config via `defineConfig`, and may itself `extends` another.
28
+ *
29
+ * Top level only: a `"[selector]"` block naming one is an error rather than a no-op, since
30
+ * inheritance is a statement about this config and not about the packages a selector names.
31
+ */
32
+ extends?: string | string[];
33
+ }
34
+ /** Every setting a config may carry, without the `+key` append forms or `extends` - the shape
35
+ * `RmanConfig` is built from, kept separate only so `WithAppend` has something to map over. */
36
+ export interface RmanConfigKeys {
37
+ /**
38
+ * **A plugin adds its own keys here, by declaration merging** - `rman-node` contributes
39
+ * `clean` and `publish.directory` from its own
40
+ * `interfaces/rman-config.interface.ts`, so `pkg.config.clean` stays typed wherever it is read
41
+ * without the core having to know npm has a `node_modules` or that TypeScript has build output.
42
+ * `WithAppend` is a mapped type evaluated at use, so an augmented key gets its `+key` form too.
43
+ *
44
+ * A config author annotates with the plugin's own name for the union - `RmanNodeConfig` - which
45
+ * is what makes the import that carries the augmentation explicit rather than incidental.
46
+ */
47
+ /**
48
+ * Plugins to load, in declaration order - a *package* contributing commands, where `.rman/*.mjs`
49
+ * contributes one repository's own.
50
+ *
51
+ * Each entry is **either a package name (or path) to import, or a plugin object itself**:
52
+ *
53
+ * ```yaml
54
+ * # .rmanrc.yml - imported by name, resolved through the repository's own node_modules
55
+ * plugins: ['rman-node']
56
+ * ```
57
+ *
58
+ * ```js
59
+ * // .rmanrc.mjs - or handed over directly, which a JS config can do and a YAML one cannot
60
+ * import { defineConfig, definePlugin } from 'rman';
61
+ * export default defineConfig({ plugins: [definePlugin({ name: 'mine', commands: [...] })] });
62
+ * ```
63
+ *
64
+ * The object form is what lets a **plugin package export a config** rather than a single plugin:
65
+ * `rman-node`'s entry point is `export default defineConfig({ plugins: [ ... ] })`, so it is an
66
+ * `.rmanrc` like any other and is free to grow a second plugin without changing its shape. When an
67
+ * imported module exports a config this way, **only its `plugins` are read** - a config's other
68
+ * keys reach a repository through `extends`, which is the key that means "merge this underneath
69
+ * mine".
70
+ *
71
+ * This is how everything that only means something in a Node repository lives outside rman's
72
+ * core. A plugin that cannot be loaded is an error, not a skip: silently losing `rman publish` is
73
+ * worse than not starting.
74
+ *
75
+ * Root level only - which commands exist is a property of the repository, not of a package.
76
+ */
77
+ plugins?: string | RmanPlugin | (string | RmanPlugin)[];
78
+ /**
79
+ * Values for `${{ vars.* }}` to read - a name for something the config would otherwise repeat:
80
+ *
81
+ * ```yaml
82
+ * vars:
83
+ * outDir: build
84
+ * image: 'panates/${{ pkg.basename }}'
85
+ * "[*]":
86
+ * publish:
87
+ * directory: '${{ vars.outDir }}'
88
+ * ```
89
+ *
90
+ * Any shape, and the values may themselves be expressions - they are evaluated for the package
91
+ * reading them, so one `vars.image` gives each package its own.
92
+ *
93
+ * **The one unmarked key that reaches every package**, rather than only the package of the
94
+ * directory declaring it. A package (or a `"[selector]"` block) overrides it **per key**, so
95
+ * redefining one var keeps the rest.
96
+ */
97
+ vars?: Record<string, unknown>;
11
98
  logLevel?: 'silent' | 'error' | 'info' | 'verbose';
12
99
  allowBranch?: string | string[];
13
100
  ignoreBranch?: string | string[];
101
+ /**
102
+ * Leave this package alone: **every command that acts on packages skips it** - `run`/`build`/
103
+ * `test`, `exec`, `clean`, `publish`, `version`, `changelog`. Per-package cascaded, so a root
104
+ * `"[selector]"` block can say it for several at once.
105
+ *
106
+ * One standing statement rather than a `skip` invented per command, which is what it was: three
107
+ * separate keys carried it and none of them meant quite the same thing. The finer-grained ones
108
+ * remain for when only one command should stop - `run.<script>.skip` for a single script,
109
+ * `publish.skip` for "never distributed, by any target" (which `changelog` reuses on purpose).
110
+ *
111
+ * **The commands that *report* deliberately ignore it** - `list` still shows the package, because
112
+ * it is still in the repository and an inventory hiding part of one is answering a different
113
+ * question. `changed` follows `version`, since its whole job is to say what `version` would do.
114
+ */
115
+ skip?: boolean;
14
116
  group?: boolean | string;
15
117
  version?: RmanConfig.VersionOptions;
16
118
  changelog?: RmanConfig.ChangelogOptions;
17
- clean?: RmanConfig.CleanOptions;
18
119
  publish?: RmanConfig.PublishOptions;
19
120
  githubRelease?: RmanConfig.GithubReleaseOptions;
20
121
  /** Keyed by npm script name (e.g. `"build"`, `"lint"`, `"test"`). A bare string (or array of
21
122
  * them) is shorthand for `{ exec: ... }` - `test: "mocha"` and `test: { exec: "mocha" }` mean
22
123
  * exactly the same thing. */
23
124
  run?: Record<string, string | string[] | RmanConfig.RunScriptOptions>;
24
- /** In-repo packages this one depends on beyond what its real `package.json` declares - purely
25
- * for rman's own dependency graph (topo-sort, `--deps`/`--dependents`, `run`'s scheduling). An
26
- * array defaults each entry's range to `"*"`; an object gives an explicit name -> range map.
27
- * Declared from the root via a selector (`"[pkg-a]": { dependencies: [...] }`) or in the
28
- * package's own `.rmanrc`. */
29
- dependencies?: string[] | Record<string, string>;
125
+ /**
126
+ * In-repo packages this one depends on **beyond what its own manifest declares** - purely for
127
+ * rman's own dependency graph (topo-sort, `--deps`/`--dependents`, `run`'s scheduling, the version
128
+ * cascade). Declared from the root via a selector (`"[pkg-a]": { dependencies: [...] }`) or in the
129
+ * package's own `.rmanrc`.
130
+ *
131
+ * Each entry is a package **name, or a repository-relative directory** - tried in that order. The
132
+ * path form is not a convenience: a name identifies a package only where the ecosystem guarantees
133
+ * uniqueness, which npm does and others do not, while a directory is unique by construction. It is
134
+ * the same reason `Workspace.Layout` carries paths and `Package.dependencies` holds references
135
+ * rather than names.
136
+ *
137
+ * **A list, and only a list.** It used to accept a `Record<string, string>` too, documented as "an
138
+ * explicit name -> range map" - and the ranges went nowhere: the one reader took `Object.keys` and
139
+ * dropped the values. Nor could they ever mean anything here, since the cascade works from groups
140
+ * and severities, and `ManifestProvider.updateDependencyVersions` rewrites ranges in the
141
+ * *manifest* - a range declared only in `.rmanrc` has no file to be written to. What this key
142
+ * states is an **edge**, and an edge needs two ends and nothing else.
143
+ *
144
+ * **Core, and it has to be**: it is layered on top of whatever `ManifestProvider.dependencies`
145
+ * read, and it is the *only* way a repository with no provider at all has a graph - a repo whose
146
+ * manifests rman cannot read can still state its edges by hand. Moving it to an ecosystem plugin
147
+ * would take that away from exactly the repositories that need it.
148
+ */
149
+ dependencies?: string[];
30
150
  /**
31
151
  * Config for **other** packages, keyed by a `"[selector]"` naming them - `"[*]"` for every
32
152
  * package in the repository, `"[*-dialect]"` for a glob over package names, `"[pkg-a]"` for one.
@@ -57,7 +177,22 @@ export interface RmanConfig {
57
177
  [selector: `[${string}]`]: RmanConfig;
58
178
  }
59
179
  export declare namespace RmanConfig {
60
- interface VersionOptions {
180
+ /**
181
+ * One `version.stamp` entry: a path, or a path plus the identifier that holds the version.
182
+ *
183
+ * The object form exists because the identifier was unnameable - only the exact lowercase word
184
+ * `version` was ever matched, so a Go `const Version`, a Python `__version__` and a plain
185
+ * `appVersion` were all silently skipped. What the identifier *means* is the ecosystem's
186
+ * (`ManifestProvider.stampVersion`); which file holds one is the repository's, which is why it is
187
+ * here.
188
+ */
189
+ type VersionStampEntry = string | {
190
+ file: string;
191
+ constant?: string;
192
+ };
193
+ interface VersionOptions extends VersionOptionsKeys, WithAppend<VersionOptionsKeys> {
194
+ }
195
+ interface VersionOptionsKeys {
61
196
  commitMessage?: string;
62
197
  /** Default for `version --changelog` when the CLI flag isn't given - a standing "always fold
63
198
  * the changelog into the version-bump commit" policy, rather than something that behaves
@@ -77,7 +212,7 @@ export declare namespace RmanConfig {
77
212
  * already declares (never inserts one), and reads the same path `publish --target docker`
78
213
  * builds from (`publish.docker.dockerfile`), so a package without one is a no-op. */
79
214
  stampDockerfile?: boolean;
80
- /** Source files whose `version` constant is rewritten to the version being written, in the same
215
+ /** Files whose hard-coded version is rewritten to the version being written, in the same
81
216
  * commit as the bump - paths relative to the package's own directory (e.g.
82
217
  * `["src/constants.ts"]`). Per-package cascaded; a listed file a package doesn't have is a
83
218
  * silent no-op, so one `"[*]"` declaration covers a repo where only some packages carry one.
@@ -85,27 +220,29 @@ export declare namespace RmanConfig {
85
220
  * Stamping the source, not the build output: a build-time rewrite leaves the checked-in file
86
221
  * claiming a placeholder, so anything running from source reports that placeholder, git never
87
222
  * records the released version, and the rewrite has to be redone on every build. */
88
- stamp?: string | string[];
89
- /** Command(s) run as this package's own `version` npm-lifecycle step, when its `package.json`
90
- * doesn't define one itself. An array runs them in sequence. */
223
+ stamp?: VersionStampEntry | VersionStampEntry[];
224
+ /** Command(s) run at the version write itself, when the package does not declare a hook for
225
+ * that slot of its own (`version` in a Node repository's `package.json#scripts`, whatever a
226
+ * plugin's step source answers elsewhere - the package's own declaration wins, as in `run`).
227
+ * An array runs them in sequence. `${{ pkg.targetVersion }}` is bound here and in the two
228
+ * below, and nowhere else. */
91
229
  exec?: string | string[];
92
- /** Same, for `preversion`. */
230
+ /** Same, before the write (`preversion` in a Node repository). */
93
231
  before?: string | string[];
94
- /** Same, for `postversion`. */
232
+ /** Same, after it (`postversion` in a Node repository). */
95
233
  after?: string | string[];
96
234
  }
97
- interface ChangelogOptions {
235
+ interface ChangelogOptions extends ChangelogOptionsKeys, WithAppend<ChangelogOptionsKeys> {
236
+ }
237
+ interface ChangelogOptionsKeys {
98
238
  ignoreTypes?: string[];
99
239
  template?: string;
100
240
  filePath?: string;
101
241
  tagPattern?: string;
102
242
  }
103
- interface CleanOptions {
104
- include?: string | string[];
105
- exclude?: string | string[];
106
- skip?: boolean;
243
+ interface RunScriptOptions extends RunScriptOptionsKeys, WithAppend<RunScriptOptionsKeys> {
107
244
  }
108
- interface RunScriptOptions {
245
+ interface RunScriptOptionsKeys {
109
246
  concurrency?: number;
110
247
  topo?: boolean;
111
248
  bail?: boolean;
@@ -123,7 +260,9 @@ export declare namespace RmanConfig {
123
260
  after?: string | string[];
124
261
  override?: boolean;
125
262
  }
126
- interface PublishOptions {
263
+ interface PublishOptions extends PublishOptionsKeys, WithAppend<PublishOptionsKeys> {
264
+ }
265
+ interface PublishOptionsKeys {
127
266
  /** Which **registry** `publish` ships this package to - default `['npm']` (every existing repo
128
267
  * keeps working unchanged). A package that only ever wants Docker images (typically also
129
268
  * `"private": true`, since it's not meant for npm at all) sets `['docker']`; both works too.
@@ -135,14 +274,6 @@ export declare namespace RmanConfig {
135
274
  * a release happened, and it is never opted into: see `githubRelease` and the
136
275
  * `github-release` command. */
137
276
  target?: PublishTarget | PublishTarget[];
138
- /** Where this package's publishable output lives, relative to its own directory (e.g.
139
- * `"build"`). Per-package cascaded, so a root `"[*]"` block can say it once for the whole
140
- * repository instead of repeating `publishConfig.directory` in every `package.json` - which
141
- * still wins when a package declares it, being the more specific statement.
142
- *
143
- * Publishing from such a directory means the manifest there is **generated by `publish`**,
144
- * from the package's own - see `PublishService`. There is nothing to configure about it. */
145
- directory?: string;
146
277
  docker?: DockerPublishOptions;
147
278
  /** Excludes this package from `publish` entirely (every target), regardless of
148
279
  * `target`/`"private"` - a single, explicit "never published" statement, e.g. for a package
@@ -155,7 +286,9 @@ export declare namespace RmanConfig {
155
286
  type PublishTarget = 'npm' | 'docker';
156
287
  /** Required once `"docker"` is one of this package's `publish.target`s - `publish --target
157
288
  * docker` errors clearly on a package that opts in here but leaves this out. */
158
- interface DockerPublishOptions {
289
+ interface DockerPublishOptions extends DockerPublishOptionsKeys, WithAppend<DockerPublishOptionsKeys> {
290
+ }
291
+ interface DockerPublishOptionsKeys {
159
292
  /** DockerHub image name/repository - bare (e.g. `"my-app"`) to be prefixed with
160
293
  * `--docker-namespace`/`DOCKERHUB_NAMESPACE`, or already-namespaced (contains a `/`) to use
161
294
  * verbatim. */
@@ -181,7 +314,9 @@ export declare namespace RmanConfig {
181
314
  * (which tag, which repository, what the notes say) already has a sensible source. Nothing here
182
315
  * decides *whether* a release is cut: a release records that the repository shipped, so it is
183
316
  * always cut, and these are only details about how. */
184
- interface GithubReleaseOptions {
317
+ interface GithubReleaseOptions extends GithubReleaseOptionsKeys, WithAppend<GithubReleaseOptionsKeys> {
318
+ }
319
+ interface GithubReleaseOptionsKeys {
185
320
  /** Files to attach to the release, as glob patterns relative to the package's own directory
186
321
  * (e.g. `["dist/*.tar.gz"]`). Read from **every** package, since one release covers the whole
187
322
  * source tree. A release with no assets at all is still perfectly valid - it records that the
@@ -191,8 +326,8 @@ export declare namespace RmanConfig {
191
326
  repository?: string;
192
327
  /** Create the release as an unpublished draft. Default `false`. Root-level only. */
193
328
  draft?: boolean;
194
- /** Default: whether the version being released is itself a semver prerelease (`1.3.0-beta.0`).
195
- * Root-level only. */
329
+ /** Default: whether the version being released is itself a prerelease by the root's own version
330
+ * scheme (`1.3.0-beta.0` under semver) - see `VersionScheme.isPrerelease`. Root-level only. */
196
331
  prerelease?: boolean;
197
332
  }
198
333
  }
package/package.json CHANGED
@@ -1,11 +1,10 @@
1
1
  {
2
2
  "name": "rman",
3
3
  "description": "Repository manager",
4
- "version": "1.0.12",
4
+ "version": "1.1.1",
5
5
  "author": "Panates",
6
6
  "license": "MIT",
7
7
  "dependencies": {
8
- "@netlify/parse-npm-script": "^0.1.2",
9
8
  "ansi-colors": "^4.1.3",
10
9
  "cross-dirname": "^0.1.0",
11
10
  "easy-table": "^1.2.0",
@@ -35,8 +34,11 @@
35
34
  "types": "./index.d.ts",
36
35
  "default": "./index.js"
37
36
  },
38
- "./package.json": "./package.json",
39
- "./rmanrc.schema.json": "./rmanrc.schema.json"
37
+ "./cli": {
38
+ "types": "./cli.d.ts",
39
+ "default": "./cli.js"
40
+ },
41
+ "./package.json": "./package.json"
40
42
  },
41
43
  "bin": {
42
44
  "rman": "cli.js"
@@ -45,11 +47,15 @@
45
47
  "node": ">=20.0"
46
48
  },
47
49
  "contributors": [
48
- "Eray Hanoglu <e.hanoglu@panates.com>"
50
+ "Eray Hanoglu <e.hanoglu@panates.com>",
51
+ "Ilker Gurelli <i.gurelli@panates.com>",
52
+ "Onur Tokel <o.tokel@panates.com>",
53
+ "Bircan Yuruk <b.yuruk@panates.com>"
49
54
  ],
50
55
  "repository": {
51
56
  "type": "git",
52
- "url": "git+https://github.com/panates/rman.git"
57
+ "url": "git+https://github.com/panates/rman.git",
58
+ "directory": "./packages/rman"
53
59
  },
54
60
  "keywords": [
55
61
  "javascript",
@@ -59,5 +65,7 @@
59
65
  "build",
60
66
  "lerna"
61
67
  ],
62
- "packageManager": "yarn@3.4.1+sha512.a333bf9c2557d71f5ba87dd1446cde24bb9c35301ec29dd2baba577b3f9d1ab61348b66a1324de9088c40a996c5b9203794da8bf14cab9fae6a6eccc00cb8078"
68
+ "publishConfig": {
69
+ "access": "public"
70
+ }
63
71
  }
@@ -0,0 +1,88 @@
1
+ import type { Package } from '../core/package.js';
2
+ import type { GitHelper } from '../utils/git.js';
3
+ /**
4
+ * Release boundaries and the tag names that mark them - **the single source for both directions**.
5
+ *
6
+ * Every command that asks question A ("what changed since this package's last release") comes
7
+ * through `detect`, and every command that needs to *name* a tag comes through `expandTag` /
8
+ * `findLatestTag`. No command builds a tag name of its own; that is what keeps `version`'s tag,
9
+ * `changelog`'s boundary and `github-release`'s lookup from drifting apart.
10
+ *
11
+ * A namespace for the reason `Manifest` and `Workspace` are ones: it is one subject with several
12
+ * operations, so the operations are named for what they do rather than carrying the subject in each
13
+ * name (`ChangeHashService.detect`, not `detectChangeHash`), and a plugin can augment it.
14
+ */
15
+ export declare namespace ChangeHashService {
16
+ /** The one keyword `from` accepts instead of a ref: "work it out per package". Omitting `from`
17
+ * means the same thing - this exists so a pipeline can say it explicitly. */
18
+ const AUTO = "auto";
19
+ interface DetectOptions {
20
+ /**
21
+ * Use this commit/hash directly instead of auto-detecting - applies the same way to every
22
+ * package. `AUTO` (or omitting `from` entirely) asks for auto-detection instead of being
23
+ * treated as a literal ref.
24
+ *
25
+ * The keyword used to be `"npm"`, which named a *source* - and the wrong one: auto-detection is
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
28
+ * as one. **A rename, not an alias**: `--from npm` now means a ref literally called `npm`,
29
+ * which is what it should have meant all along.
30
+ */
31
+ from?: string;
32
+ /** An existing record of what's already been documented (typically a changelog file) - only
33
+ * consulted while auto-detecting (ignored when `from` is an explicit hash). Guards against a
34
+ * gap: if this file's own last-modifying commit is *older* than the tag detected from the
35
+ * registry - e.g. the file was last updated for 1.1.0, but 1.2.0-1.5.0 were released without
36
+ * ever documenting them, and the registry now reports 1.5.0 - starting from the tag alone would
37
+ * silently skip everything the file never recorded. The boundary becomes the merge-base of the
38
+ * two, so the result always covers at least as much as the file is missing. Has no effect when
39
+ * the file doesn't exist. */
40
+ catchUpFile?: string;
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*`). */
47
+ function tagPattern(pkg: Package): string;
48
+ /** This package's most recent release tag - the `{name}`-bearing pattern looks up that package's
49
+ * *own* tags directly (newest by version sort); a repo-wide pattern instead finds the nearest tag
50
+ * HEAD actually descends from, since no single package "owns" that tag. `undefined` if never
51
+ * tagged at all (a fresh package, or one that's never been released). Shared by `changelog`
52
+ * (reading the last-documented version) and `version` (finding the boundary a bump measures
53
+ * "since"). */
54
+ function findLatestTag(git: GitHelper, pkg: Package): Promise<string | undefined>;
55
+ /** The forward direction of `findLatestTag`: expands `pkg`'s (cascaded) `.rmanrc
56
+ * changelog.tagPattern` into the concrete tag name `version` belongs under - `{name}` becomes the
57
+ * package's own name, `*` becomes `version`. Shared by `version` (creating the tag),
58
+ * `github-release` (finding the release that tag belongs to), and `detect`'s own registry
59
+ * fallback (mapping a published version back onto a tag), so all three name tags identically. */
60
+ function expandTag(pkg: Package, version: string): string;
61
+ /** The pattern expansion `expandTag` performs, on any pattern - `{name}` becomes `name`, `*`
62
+ * becomes `version`. Shared with the repository's own release tag, which uses a different pattern
63
+ * (see `releaseTagPattern`) but names tags the same way. */
64
+ function applyTagPattern(pattern: string, name: string, version: string): string;
65
+ /** Strips the pattern's literal prefix (everything before its first `*`) from `tag` to get just
66
+ * the version part - e.g. tag `@sqb/builder@1.2.3` against pattern `@sqb/builder@*` -> `1.2.3`.
67
+ * A pattern with no `*` is returned as its own "version" verbatim (an exact tag, nothing to
68
+ * strip). */
69
+ function extractVersion(tag: string, expandedPattern: string): string;
70
+ /**
71
+ * Resolves the commit/hash a package's changes should be measured "since" - the boundary
72
+ * `changelog --from` uses, but reusable anywhere a command wants to answer "what changed for this
73
+ * package". An explicit `options.from` (anything but `AUTO`) is returned as-is, applying the same
74
+ * way to every package. Otherwise, it's auto-detected in order: (1) this package's own most recent
75
+ * release tag - the same network-free `findLatestTag` lookup `version`/`changed` themselves use,
76
+ * so all three commands agree on "since when" for any repo whose tags are the ones `rman version`
77
+ * actually created; (2) failing that (no tag at all yet - e.g. onboarding `rman` onto a repo with
78
+ * 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
80
+ * `.rmanrc changelog.tagPattern` and used only if that tag actually exists. Either way, if
81
+ * `catchUpFile` is given and exists, the result is widened to also cover anything that file
82
+ * hasn't caught up on yet (see its doc comment). Returns `undefined` when nothing can be resolved
83
+ * at all (never tagged *and* never published, no catch-up file - a genuinely first-ever release) -
84
+ * callers should fall back to their own default in that case (e.g. the whole history, since
85
+ * nothing has ever been released).
86
+ */
87
+ function detect(git: GitHelper, pkg: Package, options?: DetectOptions): Promise<string | undefined>;
88
+ }
@@ -0,0 +1,112 @@
1
+ import { Manifest } from '../core/manifest.js';
2
+ /**
3
+ * Release boundaries and the tag names that mark them - **the single source for both directions**.
4
+ *
5
+ * Every command that asks question A ("what changed since this package's last release") comes
6
+ * through `detect`, and every command that needs to *name* a tag comes through `expandTag` /
7
+ * `findLatestTag`. No command builds a tag name of its own; that is what keeps `version`'s tag,
8
+ * `changelog`'s boundary and `github-release`'s lookup from drifting apart.
9
+ *
10
+ * A namespace for the reason `Manifest` and `Workspace` are ones: it is one subject with several
11
+ * operations, so the operations are named for what they do rather than carrying the subject in each
12
+ * name (`ChangeHashService.detect`, not `detectChangeHash`), and a plugin can augment it.
13
+ */
14
+ export var ChangeHashService;
15
+ (function (ChangeHashService) {
16
+ /** The one keyword `from` accepts instead of a ref: "work it out per package". Omitting `from`
17
+ * means the same thing - this exists so a pipeline can say it explicitly. */
18
+ 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*`). */
24
+ function tagPattern(pkg) {
25
+ const cfg = pkg.config?.changelog;
26
+ return typeof cfg?.tagPattern === 'string' && cfg.tagPattern ? cfg.tagPattern : DEFAULT_TAG_PATTERN;
27
+ }
28
+ ChangeHashService.tagPattern = tagPattern;
29
+ /** This package's most recent release tag - the `{name}`-bearing pattern looks up that package's
30
+ * *own* tags directly (newest by version sort); a repo-wide pattern instead finds the nearest tag
31
+ * HEAD actually descends from, since no single package "owns" that tag. `undefined` if never
32
+ * tagged at all (a fresh package, or one that's never been released). Shared by `changelog`
33
+ * (reading the last-documented version) and `version` (finding the boundary a bump measures
34
+ * "since"). */
35
+ async function findLatestTag(git, pkg) {
36
+ const pattern = tagPattern(pkg);
37
+ const expanded = pattern.replace('{name}', pkg.name);
38
+ return pattern.includes('{name}') ? (await git.listTags(expanded))[0] : await git.describeTag(expanded);
39
+ }
40
+ ChangeHashService.findLatestTag = findLatestTag;
41
+ /** The forward direction of `findLatestTag`: expands `pkg`'s (cascaded) `.rmanrc
42
+ * changelog.tagPattern` into the concrete tag name `version` belongs under - `{name}` becomes the
43
+ * package's own name, `*` becomes `version`. Shared by `version` (creating the tag),
44
+ * `github-release` (finding the release that tag belongs to), and `detect`'s own registry
45
+ * fallback (mapping a published version back onto a tag), so all three name tags identically. */
46
+ function expandTag(pkg, version) {
47
+ return applyTagPattern(tagPattern(pkg), pkg.name, version);
48
+ }
49
+ ChangeHashService.expandTag = expandTag;
50
+ /** The pattern expansion `expandTag` performs, on any pattern - `{name}` becomes `name`, `*`
51
+ * becomes `version`. Shared with the repository's own release tag, which uses a different pattern
52
+ * (see `releaseTagPattern`) but names tags the same way. */
53
+ function applyTagPattern(pattern, name, version) {
54
+ const expanded = pattern.replace('{name}', name);
55
+ const starIdx = expanded.indexOf('*');
56
+ return starIdx === -1 ? expanded : expanded.slice(0, starIdx) + version + expanded.slice(starIdx + 1);
57
+ }
58
+ ChangeHashService.applyTagPattern = applyTagPattern;
59
+ /** Strips the pattern's literal prefix (everything before its first `*`) from `tag` to get just
60
+ * the version part - e.g. tag `@sqb/builder@1.2.3` against pattern `@sqb/builder@*` -> `1.2.3`.
61
+ * A pattern with no `*` is returned as its own "version" verbatim (an exact tag, nothing to
62
+ * strip). */
63
+ function extractVersion(tag, expandedPattern) {
64
+ const starIdx = expandedPattern.indexOf('*');
65
+ if (starIdx === -1)
66
+ return tag;
67
+ const prefix = expandedPattern.slice(0, starIdx);
68
+ return tag.startsWith(prefix) ? tag.slice(prefix.length) : tag;
69
+ }
70
+ ChangeHashService.extractVersion = extractVersion;
71
+ /**
72
+ * Resolves the commit/hash a package's changes should be measured "since" - the boundary
73
+ * `changelog --from` uses, but reusable anywhere a command wants to answer "what changed for this
74
+ * package". An explicit `options.from` (anything but `AUTO`) is returned as-is, applying the same
75
+ * way to every package. Otherwise, it's auto-detected in order: (1) this package's own most recent
76
+ * release tag - the same network-free `findLatestTag` lookup `version`/`changed` themselves use,
77
+ * so all three commands agree on "since when" for any repo whose tags are the ones `rman version`
78
+ * actually created; (2) failing that (no tag at all yet - e.g. onboarding `rman` onto a repo with
79
+ * 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
81
+ * `.rmanrc changelog.tagPattern` and used only if that tag actually exists. Either way, if
82
+ * `catchUpFile` is given and exists, the result is widened to also cover anything that file
83
+ * hasn't caught up on yet (see its doc comment). Returns `undefined` when nothing can be resolved
84
+ * at all (never tagged *and* never published, no catch-up file - a genuinely first-ever release) -
85
+ * callers should fall back to their own default in that case (e.g. the whole history, since
86
+ * nothing has ever been released).
87
+ */
88
+ async function detect(git, pkg, options = {}) {
89
+ if (options.from && options.from !== ChangeHashService.AUTO)
90
+ return options.from;
91
+ let tagHash = await findLatestTag(git, pkg);
92
+ if (!tagHash) {
93
+ /** 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
95
+ * per package. A repository naming no plugin gets `undefined` here and git tags decide
96
+ * alone. */
97
+ const publishedVersion = await Manifest.publishedVersion(pkg);
98
+ if (publishedVersion) {
99
+ const tag = expandTag(pkg, publishedVersion);
100
+ tagHash = (await git.tagExists(tag)) ? tag : undefined;
101
+ }
102
+ }
103
+ const fileHash = options.catchUpFile ? await git.lastCommitTouching(options.catchUpFile) : undefined;
104
+ if (!fileHash)
105
+ return tagHash;
106
+ if (!tagHash)
107
+ return fileHash;
108
+ return (await git.mergeBase(tagHash, fileHash)) ?? tagHash;
109
+ }
110
+ ChangeHashService.detect = detect;
111
+ })(ChangeHashService || (ChangeHashService = {}));
112
+ const DEFAULT_TAG_PATTERN = 'v*';
@@ -2,20 +2,15 @@ import type { Package } from '../core/package.js';
2
2
  import type { Repository } from '../core/repository.js';
3
3
  import { type PackageFilterOptions } from '../utils/package-filter.js';
4
4
  export declare namespace ChangelogService {
5
- /** Injectable dependencies shared by `getEntries`/`generate` - currently just the npm registry lookup
6
- * `detectChangeHash` otherwise makes for itself; overridable so tests (and advanced callers)
7
- * aren't forced through a real network call. */
8
- interface Deps {
9
- npmViewVersion?: (name: string, cwd: string) => Promise<string | undefined>;
10
- }
11
5
  interface Options extends PackageFilterOptions {
12
6
  /** Generate the changelog since this commit/hash - applied the same way to every package.
13
- * Default (also `"npm"` explicitly): auto-detect it per package instead, from that package's
7
+ * Default (also `"auto"` explicitly): auto-detect it per package instead, from that package's
14
8
  * own most recent release tag first - the same lookup `VersionService`/`changed` use, so this
15
- * never disagrees with them - falling back to its currently-published npm version only when
16
- * it has no tag yet (see `detectChangeHash`); a package this can't be resolved for either way
17
- * (never tagged, unpublished, no network) has never been released at all, so its whole
18
- * history counts as unreleased - the same view `version` takes. */
9
+ * 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
11
+ * tag name - see `detectChangeHash`); a package this can't be resolved for either way (never
12
+ * tagged, unpublished, no plugin) has never been released at all, so its whole history counts
13
+ * as unreleased - the same view `version` takes. */
19
14
  from?: string;
20
15
  /** Generate for the whole repository even when the current directory is inside a single
21
16
  * package (which otherwise scopes it to just that package) - see `Repository.currentPackage`. */
@@ -67,7 +62,7 @@ export declare namespace ChangelogService {
67
62
  * sake side effect (a "save this" request), not presentation, so it stays here rather than in
68
63
  * the CLI command - printing what happened is the command's job.
69
64
  */
70
- function generateToFile(repository: Repository, options?: Options, deps?: Deps): Promise<Entry[]>;
65
+ function generateToFile(repository: Repository, options?: Options): Promise<Entry[]>;
71
66
  /**
72
67
  * Computes a changelog entry per package (root included) from real commits only - either
73
68
  * everything since a given `--from <hash>` (applied the same way to every package), or, by
@@ -107,5 +102,5 @@ export declare namespace ChangelogService {
107
102
  * Run from inside a single package's own directory, it only covers that package unless
108
103
  * `options.root` says otherwise (see `Repository.currentPackage`).
109
104
  */
110
- function getEntries(repository: Repository, options?: Options, deps?: Deps): Promise<Entry[]>;
105
+ function getEntries(repository: Repository, options?: Options): Promise<Entry[]>;
111
106
  }