rman 1.0.8 → 1.0.10

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.
@@ -11,7 +11,12 @@
11
11
  },
12
12
  "packageManager": {
13
13
  "type": "string",
14
- "enum": ["npm", "yarn", "pnpm", "bun"],
14
+ "enum": [
15
+ "npm",
16
+ "yarn",
17
+ "pnpm",
18
+ "bun"
19
+ ],
15
20
  "default": "npm",
16
21
  "description": "Package manager used by \"ci\"/\"publish\". Root-level only - an explicit --package-manager CLI flag wins over this."
17
22
  },
@@ -31,7 +36,15 @@
31
36
  "group": {
32
37
  "description": "Release-versioning group for \"version\"/\"publish\": true (default) puts the package in the implicit repo-wide group; a string joins exactly the other packages sharing that string; false makes it a solo, fully independent group. Per-package cascaded.",
33
38
  "default": true,
34
- "oneOf": [{ "type": "boolean" }, { "type": "string", "minLength": 1 }]
39
+ "oneOf": [
40
+ {
41
+ "type": "boolean"
42
+ },
43
+ {
44
+ "type": "string",
45
+ "minLength": 1
46
+ }
47
+ ]
35
48
  },
36
49
  "version": {
37
50
  "type": "object",
@@ -43,6 +56,16 @@
43
56
  "default": "chore(release): v{version}",
44
57
  "description": "Commit message for every group a \"version\" run commits. \"{version}\" is substituted when every bumped package in that commit shares one version. Root-level only."
45
58
  },
59
+ "changelog": {
60
+ "type": "boolean",
61
+ "default": false,
62
+ "description": "Default for \"version --changelog\" when the CLI flag isn't given - an explicit --changelog/--no-changelog still wins. Root-level only."
63
+ },
64
+ "releaseTagPattern": {
65
+ "type": "string",
66
+ "default": "release-*",
67
+ "description": "Root-level. Tag naming the repository's own release, as opposed to the per-package/group tags \"changelog.tagPattern\" names - only created when the root is on a calendar version (a repo with more than one version line). Must NOT match any package's \"changelog.tagPattern\", or that package's changelog boundary resolves to the repository release instead of its own."
68
+ },
46
69
  "script": {
47
70
  "$ref": "#/definitions/stringOrStringArray",
48
71
  "description": "Command(s) to run as this package's own \"version\" npm-lifecycle step, when its package.json does not define one itself."
@@ -64,7 +87,9 @@
64
87
  "properties": {
65
88
  "ignoreTypes": {
66
89
  "type": "array",
67
- "items": { "type": "string" },
90
+ "items": {
91
+ "type": "string"
92
+ },
68
93
  "default": [],
69
94
  "description": "Conventional Commit types (e.g. \"chore\", \"ci\") dropped entirely from changelog output, instead of being folded into \"Other Changes\". Per-package cascaded."
70
95
  },
@@ -110,11 +135,20 @@
110
135
  "additionalProperties": false,
111
136
  "properties": {
112
137
  "target": {
113
- "description": "Which registries \"publish\" targets for this package. Default [\"npm\"]. A package that only ships Docker images (typically also \"private\": true) sets [\"docker\"]; one that publishes both sets [\"npm\", \"docker\"].",
114
- "default": ["npm"],
138
+ "description": "Which registry \"publish\" ships this package to. Default [\"npm\"]. A package that only ships Docker images (typically also \"private\": true) sets [\"docker\"]; both works too. Each target answers \"is this version already out there?\" against its own registry: npm via \"npm view\", docker via \"docker manifest inspect\". This is strictly about package distribution - the repository's GitHub Release is not a target here, see \"githubRelease\" and the \"github-release\" command.",
139
+ "default": [
140
+ "npm"
141
+ ],
115
142
  "oneOf": [
116
- { "$ref": "#/definitions/publishTarget" },
117
- { "type": "array", "items": { "$ref": "#/definitions/publishTarget" } }
143
+ {
144
+ "$ref": "#/definitions/publishTarget"
145
+ },
146
+ {
147
+ "type": "array",
148
+ "items": {
149
+ "$ref": "#/definitions/publishTarget"
150
+ }
151
+ }
118
152
  ]
119
153
  },
120
154
  "docker": {
@@ -124,14 +158,20 @@
124
158
  "skip": {
125
159
  "type": "boolean",
126
160
  "default": false,
127
- "description": "Excludes this package from \"publish\" entirely (npm and docker both), regardless of \"target\"/\"private\" - a single, explicit \"never published\" statement. \"changelog\" also skips it by default (see its own --include-skipped). Independent of \"version\", which never consults this."
161
+ "description": "Excludes this package from \"publish\" entirely (every target), regardless of \"target\"/\"private\" - a single, explicit \"never published\" statement. \"changelog\" also skips it by default (see its own --include-skipped). Independent of \"version\", which never consults this."
128
162
  }
129
163
  }
130
164
  },
165
+ "githubRelease": {
166
+ "$ref": "#/definitions/githubReleaseConfig",
167
+ "description": "Details of the repository's GitHub Release (\"github-release\"/GithubReleaseService). Entirely optional - every required fact already has a sensible default source. Nothing here decides whether a release is cut: a release records that the repository shipped, so it is always cut. \"assets\" is per-package cascaded; the rest is root-level only."
168
+ },
131
169
  "run": {
132
170
  "type": "object",
133
171
  "description": "Per-script options for \"run\"/\"build\"/\"test\"/RunService, keyed by npm script name (e.g. \"build\", \"lint\", \"test\").",
134
- "additionalProperties": { "$ref": "#/definitions/runScriptConfig" }
172
+ "additionalProperties": {
173
+ "$ref": "#/definitions/runScriptConfig"
174
+ }
135
175
  },
136
176
  "packages": {
137
177
  "type": "object",
@@ -143,8 +183,18 @@
143
183
  "dependencies": {
144
184
  "description": "Extra in-repo \"dependencies\" not present in this package's real package.json, purely for rman's own dependency graph (topo-sort, --deps/--dependents, run's task scheduling). An array defaults each entry's range to \"*\"; an object gives an explicit name -> range map.",
145
185
  "oneOf": [
146
- { "type": "array", "items": { "type": "string" } },
147
- { "type": "object", "additionalProperties": { "type": "string" } }
186
+ {
187
+ "type": "array",
188
+ "items": {
189
+ "type": "string"
190
+ }
191
+ },
192
+ {
193
+ "type": "object",
194
+ "additionalProperties": {
195
+ "type": "string"
196
+ }
197
+ }
148
198
  ]
149
199
  }
150
200
  }
@@ -154,22 +204,41 @@
154
204
  "definitions": {
155
205
  "logLevel": {
156
206
  "type": "string",
157
- "enum": ["silent", "error", "info", "verbose"]
207
+ "enum": [
208
+ "silent",
209
+ "error",
210
+ "info",
211
+ "verbose"
212
+ ]
158
213
  },
159
214
  "stringOrStringArray": {
160
215
  "oneOf": [
161
- { "type": "string", "minLength": 1 },
162
- { "type": "array", "items": { "type": "string", "minLength": 1 } }
216
+ {
217
+ "type": "string",
218
+ "minLength": 1
219
+ },
220
+ {
221
+ "type": "array",
222
+ "items": {
223
+ "type": "string",
224
+ "minLength": 1
225
+ }
226
+ }
163
227
  ]
164
228
  },
165
229
  "publishTarget": {
166
230
  "type": "string",
167
- "enum": ["npm", "docker"]
231
+ "enum": [
232
+ "npm",
233
+ "docker"
234
+ ]
168
235
  },
169
236
  "dockerPublishConfig": {
170
237
  "type": "object",
171
238
  "additionalProperties": false,
172
- "required": ["image"],
239
+ "required": [
240
+ "image"
241
+ ],
173
242
  "properties": {
174
243
  "image": {
175
244
  "type": "string",
@@ -183,8 +252,12 @@
183
252
  },
184
253
  "platforms": {
185
254
  "type": "array",
186
- "items": { "type": "string" },
187
- "default": ["linux/amd64"],
255
+ "items": {
256
+ "type": "string"
257
+ },
258
+ "default": [
259
+ "linux/amd64"
260
+ ],
188
261
  "description": "\"docker buildx build --platform\" targets."
189
262
  },
190
263
  "cwd": {
@@ -193,12 +266,16 @@
193
266
  },
194
267
  "buildContexts": {
195
268
  "type": "object",
196
- "additionalProperties": { "type": "string" },
269
+ "additionalProperties": {
270
+ "type": "string"
271
+ },
197
272
  "description": "Named \"docker buildx build --build-context <name>=<path>\" entries, keyed by name - each path relative to the package's own directory (or absolute)."
198
273
  },
199
274
  "buildArgs": {
200
275
  "type": "object",
201
- "additionalProperties": { "type": "string" },
276
+ "additionalProperties": {
277
+ "type": "string"
278
+ },
202
279
  "description": "\"docker buildx build --build-arg <name>=<value>\" entries - a value of exactly \"$NAME\" expands to the NAME environment variable at build time."
203
280
  },
204
281
  "readme": {
@@ -208,6 +285,32 @@
208
285
  }
209
286
  }
210
287
  },
288
+ "githubReleaseConfig": {
289
+ "type": "object",
290
+ "additionalProperties": false,
291
+ "properties": {
292
+ "assets": {
293
+ "type": "array",
294
+ "items": {
295
+ "type": "string"
296
+ },
297
+ "description": "Files to attach to the release, as glob patterns relative to the package's own directory (e.g. [\"dist/*.tar.gz\"]). Read from every package, since one release covers the whole source tree. A release with no assets is still valid."
298
+ },
299
+ "repository": {
300
+ "type": "string",
301
+ "description": "\"owner/repo\". Default: parsed from the \"origin\" remote's URL."
302
+ },
303
+ "draft": {
304
+ "type": "boolean",
305
+ "default": false,
306
+ "description": "Create the release as an unpublished draft."
307
+ },
308
+ "prerelease": {
309
+ "type": "boolean",
310
+ "description": "Default: whether the version being released is itself a semver prerelease."
311
+ }
312
+ }
313
+ },
211
314
  "runScriptConfig": {
212
315
  "type": "object",
213
316
  "additionalProperties": false,
@@ -11,10 +11,11 @@ export declare namespace ChangelogService {
11
11
  interface Options extends PackageFilterOptions {
12
12
  /** Generate the changelog since this commit/hash - applied the same way to every package.
13
13
  * Default (also `"npm"` explicitly): auto-detect it per package instead, from that package's
14
- * currently-published npm version (see `detectChangeHash`); a package this can't be resolved
15
- * for (unpublished, no network, no matching tag) falls back to its own commits not yet
16
- * pushed to the current branch's upstream (same reference point `--changed`/
17
- * `--changed-since` use). */
14
+ * 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. */
18
19
  from?: string;
19
20
  /** Generate for the whole repository even when the current directory is inside a single
20
21
  * package (which otherwise scopes it to just that package) - see `Repository.currentPackage`. */
@@ -31,6 +32,14 @@ export declare namespace ChangelogService {
31
32
  /** A package with `.rmanrc "publish.skip"` is excluded by default - little point changelogging
32
33
  * something that's never actually released. Set true to generate for it anyway. */
33
34
  includeSkipped?: boolean;
35
+ /** The version these entries are being generated *for* - what `{{version}}` renders as.
36
+ * Without it the version is read back from git tags (see `resolveVersion`), which is only
37
+ * correct once the release being described has actually been tagged. A caller generating
38
+ * notes for a release that doesn't exist yet - `version --changelog` writing the entry before
39
+ * it commits and tags, or a CI step producing release notes ahead of the bump - already knows
40
+ * the number and has to say so, otherwise every entry ends up labelled with the *previous*
41
+ * release's version. */
42
+ version?: string;
34
43
  }
35
44
  /** One package's (root included) generated changelog entry - what `getEntries`/`generate`
36
45
  * return. */
@@ -39,7 +48,8 @@ export declare namespace ChangelogService {
39
48
  /** Display name for this entry's heading - `"<repo dir name> repository"` for the root
40
49
  * package, its own name otherwise (see `getEntries`'s doc comment on `{{package}}`). */
41
50
  label: string;
42
- /** Resolved from git tags, not package.json - see `resolveVersion`. */
51
+ /** `options.version` when the caller gave one, otherwise resolved from git tags rather than
52
+ * package.json - see `resolveVersion`. */
43
53
  version: string;
44
54
  features: string[];
45
55
  fixes: string[];
@@ -71,16 +81,15 @@ export declare namespace ChangelogService {
71
81
  * Fixes/🔧 Other Changes on a best-effort Conventional Commits read; anything that doesn't parse
72
82
  * just lands in Other Changes as-is, so a repo that doesn't follow that convention still gets a
73
83
  * usable list. `.rmanrc changelog.ignoreTypes` (cascaded, e.g. `[chore, dev]`) drops commits of
74
- * those types entirely instead - see `ignoreTypesConfig`. A bare version-bump commit
75
- * (`"6.0.1"`) is always dropped outright, regardless of `ignoreTypes` - see
76
- * `VERSION_BUMP_PATTERN`.
84
+ * those types entirely instead - see `ignoreTypesConfig`. A release marker - a bare version-bump
85
+ * commit (`"6.0.1"`), or any of the messages `version` itself writes - is always dropped
86
+ * outright, regardless of `ignoreTypes`; see `isReleaseCommit`.
77
87
  *
78
88
  * By default (or with `--from npm` explicitly), the boundary is auto-detected per package
79
89
  * instead of one shared one - see `detectChangeHash`. A package that can't be resolved this way
80
- * (unpublished, no network, no matching tag) falls back to its own commits not yet pushed to
81
- * the current branch's upstream (the same reference point `--changed`/`--changed-since` use
82
- * elsewhere, via `GitHelper.listCommits`) - so the command still produces something useful even
83
- * for a repo that's never been published or tagged at all. Packages that end up resolving to
90
+ * (unpublished, no network, no matching tag) has never been released at all, so its whole
91
+ * history counts as unreleased - the same view `version` takes, so a first-ever release still
92
+ * produces a real changelog. Packages that end up resolving to
84
93
  * the same hash (an explicit one, or several packages sharing one tag under fixed versioning)
85
94
  * only have their commits fetched once, not once per package.
86
95
  *
@@ -1,7 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { detectChangeHash, extractVersion, findLatestTag, tagPattern } from '../utils/change-hash.js';
4
- import { parseConventionalCommit, VERSION_BUMP_PATTERN } from '../utils/conventional-commits.js';
4
+ import { isReleaseCommit, parseConventionalCommit } from '../utils/conventional-commits.js';
5
5
  import { GitHelper } from '../utils/git.js';
6
6
  import { filterPackages } from '../utils/package-filter.js';
7
7
  export var ChangelogService;
@@ -33,16 +33,15 @@ export var ChangelogService;
33
33
  * Fixes/🔧 Other Changes on a best-effort Conventional Commits read; anything that doesn't parse
34
34
  * just lands in Other Changes as-is, so a repo that doesn't follow that convention still gets a
35
35
  * usable list. `.rmanrc changelog.ignoreTypes` (cascaded, e.g. `[chore, dev]`) drops commits of
36
- * those types entirely instead - see `ignoreTypesConfig`. A bare version-bump commit
37
- * (`"6.0.1"`) is always dropped outright, regardless of `ignoreTypes` - see
38
- * `VERSION_BUMP_PATTERN`.
36
+ * those types entirely instead - see `ignoreTypesConfig`. A release marker - a bare version-bump
37
+ * commit (`"6.0.1"`), or any of the messages `version` itself writes - is always dropped
38
+ * outright, regardless of `ignoreTypes`; see `isReleaseCommit`.
39
39
  *
40
40
  * By default (or with `--from npm` explicitly), the boundary is auto-detected per package
41
41
  * instead of one shared one - see `detectChangeHash`. A package that can't be resolved this way
42
- * (unpublished, no network, no matching tag) falls back to its own commits not yet pushed to
43
- * the current branch's upstream (the same reference point `--changed`/`--changed-since` use
44
- * elsewhere, via `GitHelper.listCommits`) - so the command still produces something useful even
45
- * for a repo that's never been published or tagged at all. Packages that end up resolving to
42
+ * (unpublished, no network, no matching tag) has never been released at all, so its whole
43
+ * history counts as unreleased - the same view `version` takes, so a first-ever release still
44
+ * produces a real changelog. Packages that end up resolving to
46
45
  * the same hash (an explicit one, or several packages sharing one tag under fixed versioning)
47
46
  * only have their commits fetched once, not once per package.
48
47
  *
@@ -65,9 +64,10 @@ export var ChangelogService;
65
64
  const packages = repository.getPackages().filter(p => p !== repository.rootPackage);
66
65
  const targets = (cwdScope ? [cwdScope] : filterPackages([repository.rootPackage, ...packages], options)).filter(pkg => options.includeSkipped || !pkg.config.publish?.skip);
67
66
  const git = new GitHelper({ cwd: repository.dirname });
68
- // dropped up front, not just while grouping - a package whose only commits are version bumps
67
+ const commitMessage = repository.rootPackage.config?.version?.commitMessage;
68
+ // dropped up front, not just while grouping - a package whose only commits are release markers
69
69
  // should get no entry at all, rather than a heading with nothing real underneath it.
70
- const dropVersionBumps = (commits) => commits.filter(c => !VERSION_BUMP_PATTERN.test(c.subject));
70
+ const dropVersionBumps = (commits) => commits.filter(c => !isReleaseCommit(c.subject, commitMessage));
71
71
  // Several packages often resolve to the identical hash (an explicit --from <hash> applies to
72
72
  // all of them the same way; under fixed versioning, npm auto-detection usually does too) - so
73
73
  // the git fetch for a given hash is cached, run once no matter how many packages share it.
@@ -76,7 +76,10 @@ export var ChangelogService;
76
76
  const key = hash ?? '';
77
77
  let promise = commitsByHash.get(key);
78
78
  if (!promise) {
79
- promise = git.listCommits({ hash }).then(dropVersionBumps);
79
+ // No boundary at all means nothing has ever been released, so everything so far is
80
+ // unreleased - the same fallback `VersionService` makes. (Not "not yet pushed": that reads
81
+ // as empty the moment a first release is pushed, and for a repo with no remote at all.)
82
+ promise = (hash ? git.listCommits({ hash }) : git.listAllCommits()).then(dropVersionBumps);
80
83
  commitsByHash.set(key, promise);
81
84
  }
82
85
  return promise;
@@ -102,7 +105,7 @@ export var ChangelogService;
102
105
  if (!grouped.features.length && !grouped.fixes.length && !grouped.other.length)
103
106
  continue;
104
107
  const label = pkg === repository.rootPackage ? `${path.basename(repository.dirname)} repository` : pkg.name;
105
- const { version, content } = await renderEntry(repository, pkg, label, grouped, git);
108
+ const { version, content } = await renderEntry(repository, pkg, label, grouped, git, options.version);
106
109
  entries.push({
107
110
  package: pkg,
108
111
  label,
@@ -129,8 +132,6 @@ function ignoreTypesConfig(pkg) {
129
132
  function groupCommits(subjects, ignoreTypes = new Set()) {
130
133
  const grouped = { features: [], fixes: [], other: [] };
131
134
  for (const subject of subjects) {
132
- if (VERSION_BUMP_PATTERN.test(subject))
133
- continue;
134
135
  const parsed = parseConventionalCommit(subject);
135
136
  if (!parsed) {
136
137
  grouped.other.push(subject);
@@ -244,9 +245,9 @@ function ownersOf(repository, commit) {
244
245
  }
245
246
  return owners;
246
247
  }
247
- async function renderEntry(repository, pkg, label, grouped, git) {
248
+ async function renderEntry(repository, pkg, label, grouped, git, versionOverride) {
248
249
  const template = resolveTemplate(repository, pkg);
249
- const version = await resolveVersion(git, pkg);
250
+ const version = versionOverride ?? (await resolveVersion(git, pkg));
250
251
  const content = render(template, {
251
252
  package: label,
252
253
  version,
@@ -0,0 +1,69 @@
1
+ import type { Package } from '../core/package.js';
2
+ import type { Repository } from '../core/repository.js';
3
+ export declare namespace GithubReleaseService {
4
+ /** Injectable "does this release already exist" check - mainly for tests, so they don't depend
5
+ * on network access or a real GitHub token. Same shape as `DockerPublishService.Deps`' own
6
+ * `imageExists`. */
7
+ interface Deps {
8
+ releaseExists?: (repository: string, tag: string) => Promise<boolean>;
9
+ }
10
+ interface Options {
11
+ /** Uncommitted local changes anywhere in the repository make the release `'skip'` instead of
12
+ * `'error'` - same as `version`/`publish`'s other targets. */
13
+ ignoreDirty?: boolean;
14
+ /** `owner/repo` override - otherwise the root's own `githubRelease.repository`, falling back
15
+ * to the `origin` remote's URL. */
16
+ repository?: string;
17
+ }
18
+ /** The repository's release outcome - see `getPlan`. At most one of these: a GitHub Release
19
+ * belongs to the repository, not to a package. */
20
+ interface Entry {
21
+ /** Always the repository root - a release covers the whole source tree, not one package. */
22
+ package: Package;
23
+ /** The repository's own release version (see `buildRootEntry`). */
24
+ version: string;
25
+ status: 'publish' | 'skip' | 'up-to-date' | 'error';
26
+ /** The tag this release belongs to - the repository release tag on a calendar version, the
27
+ * single shared version's tag otherwise. */
28
+ tag?: string;
29
+ /** `owner/repo` this release lands in - unset only when it couldn't be resolved (an `'error'`). */
30
+ repository?: string;
31
+ reason?: string;
32
+ }
33
+ /**
34
+ * Computes what `github-release` *would* do - **one** release per run.
35
+ *
36
+ * A GitHub Release is a property of the repository, not of a package: the tag covers the whole
37
+ * source tree, so everything that shipped under it belongs in it. There is deliberately **no
38
+ * opt-in**: it isn't a place a package ships to (that's `publish.target`, which is about
39
+ * registries - npm, Docker Hub, GitHub Packages), it's the repository's own record that a
40
+ * release happened, and a repository always wants that record. Re-running is harmless - an
41
+ * existing release for the tag reads `'up-to-date'`.
42
+ *
43
+ * The release is identified by the repository's own version (the root's - see
44
+ * `VersionService`'s `buildRootEntry`): its release tag when that version is a calendar one, and
45
+ * otherwise the tag of the single shared version, which is the group's own tag - so a repo with
46
+ * one version line gets no second name for the release it already has. Whether a release exists
47
+ * for that tag decides `'up-to-date'` vs `'publish'`.
48
+ *
49
+ * Uncommitted changes anywhere make it `'error'` unless `options.ignoreDirty` downgrades it to
50
+ * `'skip'`. An unresolvable `owner/repo`, or a lookup that fails for any reason other than "no
51
+ * such release", is `'error'` too - a blocking misconfiguration rather than a silent "not
52
+ * released yet" that only fails later.
53
+ */
54
+ function getPlan(repository: Repository, options?: Options, deps?: Deps): Promise<Entry[]>;
55
+ /**
56
+ * Creates the repository's GitHub Release, then uploads whatever `githubRelease.assets` globs
57
+ * match, across every package. The body covers **every** package that shipped under this
58
+ * release, since the tag covers all of their code either way.
59
+ *
60
+ * Each package's notes are bounded by the *previous repository release*, and headed with that
61
+ * package's own version, so a repo whose packages sit on different version lines still reads
62
+ * correctly. A package with nothing in that range simply contributes no section, which is also
63
+ * how a package that didn't ship in this release is left out - no ancestry arithmetic needed.
64
+ *
65
+ * An existing release for the tag is updated rather than treated as a failure, so a re-run after
66
+ * a partial failure converges.
67
+ */
68
+ function applyPlan(repository: Repository, plan: Entry[]): Promise<Entry[]>;
69
+ }