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,9 +1,9 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
- import { detectChangeHash, extractVersion, findLatestTag, tagPattern } from '../utils/change-hash.js';
4
- import { isReleaseCommit, parseConventionalCommit } from '../utils/conventional-commits.js';
5
3
  import { GitHelper } from '../utils/git.js';
6
4
  import { filterPackages } from '../utils/package-filter.js';
5
+ import { ChangeHashService } from './change-hash.service.js';
6
+ import { ConventionalCommitsService } from './conventional-commits.service.js';
7
7
  export var ChangelogService;
8
8
  (function (ChangelogService) {
9
9
  /**
@@ -13,8 +13,8 @@ export var ChangelogService;
13
13
  * sake side effect (a "save this" request), not presentation, so it stays here rather than in
14
14
  * the CLI command - printing what happened is the command's job.
15
15
  */
16
- async function generateToFile(repository, options = {}, deps = {}) {
17
- const entries = await getEntries(repository, options, deps);
16
+ async function generateToFile(repository, options = {}) {
17
+ const entries = await getEntries(repository, options);
18
18
  for (const entry of entries)
19
19
  prependToChangelogFile(entry.package, entry.filePath, entry.content);
20
20
  return entries;
@@ -59,7 +59,7 @@ export var ChangelogService;
59
59
  * Run from inside a single package's own directory, it only covers that package unless
60
60
  * `options.root` says otherwise (see `Repository.currentPackage`).
61
61
  */
62
- async function getEntries(repository, options = {}, deps = {}) {
62
+ async function getEntries(repository, options = {}) {
63
63
  const cwdScope = options.root ? undefined : repository.currentPackage;
64
64
  const packages = repository.getPackages().filter(p => p !== repository.rootPackage);
65
65
  const targets = (cwdScope ? [cwdScope] : filterPackages([repository.rootPackage, ...packages], options)).filter(pkg => options.includeSkipped || !pkg.config.publish?.skip);
@@ -67,7 +67,7 @@ export var ChangelogService;
67
67
  const commitMessage = repository.rootPackage.config?.version?.commitMessage;
68
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 => !isReleaseCommit(c.subject, commitMessage));
70
+ const dropVersionBumps = (commits) => commits.filter(c => !ConventionalCommitsService.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.
@@ -86,9 +86,8 @@ export var ChangelogService;
86
86
  };
87
87
  const commitsByTarget = await Promise.all(targets.map(async (pkg) => {
88
88
  const catchUpFile = path.join(pkg.dirname, resolveFilePath(pkg, options.filePath));
89
- const from = await detectChangeHash(git, pkg, {
89
+ const from = await ChangeHashService.detect(git, pkg, {
90
90
  from: options.from,
91
- npmViewVersion: deps.npmViewVersion,
92
91
  catchUpFile: fs.existsSync(catchUpFile) ? catchUpFile : undefined,
93
92
  });
94
93
  return listCommitsCached(from);
@@ -132,7 +131,7 @@ function ignoreTypesConfig(pkg) {
132
131
  function groupCommits(subjects, ignoreTypes = new Set()) {
133
132
  const grouped = { features: [], fixes: [], other: [] };
134
133
  for (const subject of subjects) {
135
- const parsed = parseConventionalCommit(subject);
134
+ const parsed = ConventionalCommitsService.parseSubject(subject);
136
135
  if (!parsed) {
137
136
  grouped.other.push(subject);
138
137
  continue;
@@ -198,8 +197,10 @@ function resolveFilePath(pkg, optionsFilePath) {
198
197
  * if no matching tag exists at all (never tagged, or a fresh package) - see `findLatestTag`.
199
198
  */
200
199
  async function resolveVersion(git, pkg) {
201
- const tag = await findLatestTag(git, pkg);
202
- return tag ? extractVersion(tag, tagPattern(pkg).replace('{name}', pkg.name)) : pkg.version || '';
200
+ const tag = await ChangeHashService.findLatestTag(git, pkg);
201
+ return tag
202
+ ? ChangeHashService.extractVersion(tag, ChangeHashService.tagPattern(pkg).replace('{name}', pkg.name))
203
+ : pkg.version || '';
203
204
  }
204
205
  /** The most specific package whose directory contains `file` - the repository root itself as the
205
206
  * fallback for anything outside every package (e.g. root-level config files). Mirrors
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Conventional Commits, as rman reads them: the subject-line shape, the breaking-change markers,
3
+ * the `Release-As:` escape hatch, and the counter-question of whether a commit is a *release
4
+ * marker* rather than a change at all.
5
+ *
6
+ * A namespace for the reason `ChangeHashService` is one - one subject, several operations, named for
7
+ * what they do rather than repeating the subject (`parseSubject`, not `parseConventionalCommit`).
8
+ *
9
+ * **Nothing here knows about versions.** A commit says what happened; `VersionScheme.bumpFor` turns
10
+ * that into how a number moves. `parseReleaseAs` is the one place the two meet, and it deliberately
11
+ * returns the word as written rather than judging it - see its own doc.
12
+ */
13
+ export declare namespace ConventionalCommitsService {
14
+ /**
15
+ * `type(scope): description`, optionally with a `!` breaking-change marker - Conventional
16
+ * Commits' subject-line shape. Anything that doesn't match falls into "Other Changes" as-is (for
17
+ * changelog entries) or defaults to a patch-level change (for version bump severity) - see
18
+ * `parseSubject`.
19
+ */
20
+ const SUBJECT_PATTERN: RegExp;
21
+ /** A bare version-bump commit (`"6.0.1"`, `"v2.3.0-beta.1"`, ...) - many release tools commit the
22
+ * version bump itself with just the new version number as the message. That's a release marker,
23
+ * not a real change worth describing (or worth bumping a version over on its own), so it's
24
+ * dropped everywhere a real change is being looked for. */
25
+ const VERSION_BUMP_PATTERN: RegExp;
26
+ /**
27
+ * Whether `subject` is a release marker rather than a real change - dropped everywhere real
28
+ * changes are looked for (changelog entries, and what counts as "changed" for a version bump).
29
+ *
30
+ * Covers the bare-version form other tools use (`VERSION_BUMP_PATTERN`) plus every message shape
31
+ * `version` itself writes: its commit message (`commitMessageTemplate`, or the built-in
32
+ * `"chore(release): v{version}"` when a repo doesn't override it), the multi-version form that
33
+ * template falls back to when one commit spans several versions (`chore(release): a@1.2.0,
34
+ * b@1.3.0`), and the monorepo root's own version-sync commit. Without this, rman's own release
35
+ * commits show up in the changelogs it generates - visible whenever the boundary reaches back past
36
+ * a previous release (see `ChangeHashService.detect`'s `catchUpFile`).
37
+ */
38
+ function isReleaseCommit(subject: string, commitMessageTemplate?: string): boolean;
39
+ interface ParsedSubject {
40
+ type: string;
41
+ scope?: string;
42
+ /** A `!` right before the `:` (e.g. `feat!:`) - Conventional Commits' inline breaking-change
43
+ * marker. Doesn't cover a `BREAKING CHANGE:` footer, since only the subject line is available. */
44
+ breaking: boolean;
45
+ description: string;
46
+ }
47
+ /** Parses a commit subject as Conventional Commits, or `undefined` if it doesn't match at all
48
+ * (a non-conventional message - still a real change, just with no `type` to key off of). */
49
+ function parseSubject(subject: string): ParsedSubject | undefined;
50
+ /** Whether a commit `body` carries a Conventional Commits `BREAKING CHANGE:` (or
51
+ * `BREAKING-CHANGE:`) footer - the other, footer-based way to mark a breaking change, alongside
52
+ * the inline `!` the subject line alone can carry (see `parseSubject`, whose own
53
+ * `breaking` only ever reflects that marker, never a footer, since it only sees the subject). */
54
+ function hasBreakingChangeFooter(body: string): boolean;
55
+ /**
56
+ * A `Release-As: <bump>` footer in a commit `body` - lets that one commit's own contribution to the
57
+ * detected bump be overridden by hand, regardless of what its subject line (or a `BREAKING CHANGE:`
58
+ * footer) would otherwise imply. The motivating case: a `feat:` commit that needs to ship right now
59
+ * as a patch, without waiting for the rest of a minor's worth of work to land - `Release-As: patch`
60
+ * on just that commit ships it alone, at patch size, while a later genuine `feat:` (with no
61
+ * override) still correctly triggers a minor of its own. Case-insensitive; the last match wins if a
62
+ * body somehow has more than one, matching how multiple git trailers of the same key are
63
+ * conventionally read (later overrides earlier).
64
+ *
65
+ * **Returns the word as written, and does not judge it** - which bumps exist is the package's
66
+ * `VersionScheme.bumpNames`, and this module is about commit messages. The caller checks it against
67
+ * those and treats anything else as *no override at all*, falling back to what the commit's own
68
+ * subject said (see `VersionPlanService.detectBump`). That keeps two cases harmless: a typo, and release-please's own
69
+ * `Release-As: 1.2.3`, which uses the same trailer for a literal version and appears in real
70
+ * repositories rman gets adopted onto.
71
+ */
72
+ function parseReleaseAs(body: string): string | undefined;
73
+ }
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Conventional Commits, as rman reads them: the subject-line shape, the breaking-change markers,
3
+ * the `Release-As:` escape hatch, and the counter-question of whether a commit is a *release
4
+ * marker* rather than a change at all.
5
+ *
6
+ * A namespace for the reason `ChangeHashService` is one - one subject, several operations, named for
7
+ * what they do rather than repeating the subject (`parseSubject`, not `parseConventionalCommit`).
8
+ *
9
+ * **Nothing here knows about versions.** A commit says what happened; `VersionScheme.bumpFor` turns
10
+ * that into how a number moves. `parseReleaseAs` is the one place the two meet, and it deliberately
11
+ * returns the word as written rather than judging it - see its own doc.
12
+ */
13
+ export var ConventionalCommitsService;
14
+ (function (ConventionalCommitsService) {
15
+ /**
16
+ * `type(scope): description`, optionally with a `!` breaking-change marker - Conventional
17
+ * Commits' subject-line shape. Anything that doesn't match falls into "Other Changes" as-is (for
18
+ * changelog entries) or defaults to a patch-level change (for version bump severity) - see
19
+ * `parseSubject`.
20
+ */
21
+ ConventionalCommitsService.SUBJECT_PATTERN = /^(\w+)(\(([^)]+)\))?(!)?:\s*(.+)$/;
22
+ /** A bare version-bump commit (`"6.0.1"`, `"v2.3.0-beta.1"`, ...) - many release tools commit the
23
+ * version bump itself with just the new version number as the message. That's a release marker,
24
+ * not a real change worth describing (or worth bumping a version over on its own), so it's
25
+ * dropped everywhere a real change is being looked for. */
26
+ ConventionalCommitsService.VERSION_BUMP_PATTERN = /^v?\d+\.\d+\.\d+(?:[-+][\w.]+)?$/;
27
+ /**
28
+ * Whether `subject` is a release marker rather than a real change - dropped everywhere real
29
+ * changes are looked for (changelog entries, and what counts as "changed" for a version bump).
30
+ *
31
+ * Covers the bare-version form other tools use (`VERSION_BUMP_PATTERN`) plus every message shape
32
+ * `version` itself writes: its commit message (`commitMessageTemplate`, or the built-in
33
+ * `"chore(release): v{version}"` when a repo doesn't override it), the multi-version form that
34
+ * template falls back to when one commit spans several versions (`chore(release): a@1.2.0,
35
+ * b@1.3.0`), and the monorepo root's own version-sync commit. Without this, rman's own release
36
+ * commits show up in the changelogs it generates - visible whenever the boundary reaches back past
37
+ * a previous release (see `ChangeHashService.detect`'s `catchUpFile`).
38
+ */
39
+ function isReleaseCommit(subject, commitMessageTemplate) {
40
+ if (ConventionalCommitsService.VERSION_BUMP_PATTERN.test(subject))
41
+ return true;
42
+ if (ROOT_SYNC_PATTERN.test(subject))
43
+ return true;
44
+ if (MULTI_VERSION_RELEASE_PATTERN.test(subject))
45
+ return true;
46
+ // The built-in message is checked even when a repo overrides it: the override only applies to
47
+ // commits spanning a single version (see `buildCommitMessage`), and a repo that adopted one later
48
+ // still has older releases committed under the default.
49
+ if (templatePattern(DEFAULT_COMMIT_MESSAGE).test(subject))
50
+ return true;
51
+ return !!commitMessageTemplate && templatePattern(commitMessageTemplate).test(subject);
52
+ }
53
+ ConventionalCommitsService.isReleaseCommit = isReleaseCommit;
54
+ /** Parses a commit subject as Conventional Commits, or `undefined` if it doesn't match at all
55
+ * (a non-conventional message - still a real change, just with no `type` to key off of). */
56
+ function parseSubject(subject) {
57
+ const m = ConventionalCommitsService.SUBJECT_PATTERN.exec(subject);
58
+ if (!m)
59
+ return undefined;
60
+ const [, type, , scope, breakingMark, description] = m;
61
+ return { type: type.toLowerCase(), scope, breaking: !!breakingMark, description };
62
+ }
63
+ ConventionalCommitsService.parseSubject = parseSubject;
64
+ /** Whether a commit `body` carries a Conventional Commits `BREAKING CHANGE:` (or
65
+ * `BREAKING-CHANGE:`) footer - the other, footer-based way to mark a breaking change, alongside
66
+ * the inline `!` the subject line alone can carry (see `parseSubject`, whose own
67
+ * `breaking` only ever reflects that marker, never a footer, since it only sees the subject). */
68
+ function hasBreakingChangeFooter(body) {
69
+ return /^BREAKING[ -]CHANGE:/im.test(body);
70
+ }
71
+ ConventionalCommitsService.hasBreakingChangeFooter = hasBreakingChangeFooter;
72
+ /**
73
+ * A `Release-As: <bump>` footer in a commit `body` - lets that one commit's own contribution to the
74
+ * detected bump be overridden by hand, regardless of what its subject line (or a `BREAKING CHANGE:`
75
+ * footer) would otherwise imply. The motivating case: a `feat:` commit that needs to ship right now
76
+ * as a patch, without waiting for the rest of a minor's worth of work to land - `Release-As: patch`
77
+ * on just that commit ships it alone, at patch size, while a later genuine `feat:` (with no
78
+ * override) still correctly triggers a minor of its own. Case-insensitive; the last match wins if a
79
+ * body somehow has more than one, matching how multiple git trailers of the same key are
80
+ * conventionally read (later overrides earlier).
81
+ *
82
+ * **Returns the word as written, and does not judge it** - which bumps exist is the package's
83
+ * `VersionScheme.bumpNames`, and this module is about commit messages. The caller checks it against
84
+ * those and treats anything else as *no override at all*, falling back to what the commit's own
85
+ * subject said (see `VersionPlanService.detectBump`). That keeps two cases harmless: a typo, and release-please's own
86
+ * `Release-As: 1.2.3`, which uses the same trailer for a literal version and appears in real
87
+ * repositories rman gets adopted onto.
88
+ */
89
+ function parseReleaseAs(body) {
90
+ const matches = [...body.matchAll(/^release-as:\s*([\w.+-]+)\s*$/gim)];
91
+ const last = matches.at(-1);
92
+ return last ? last[1].toLowerCase() : undefined;
93
+ }
94
+ ConventionalCommitsService.parseReleaseAs = parseReleaseAs;
95
+ })(ConventionalCommitsService || (ConventionalCommitsService = {}));
96
+ /** A semver version, as it appears inside a commit subject - the `\d+\.\d+\.\d+` core of
97
+ * `VERSION_BUMP_PATTERN`, reusable inside the larger patterns below. */
98
+ const SEMVER_SOURCE = String.raw `\d+\.\d+\.\d+(?:[-+][\w.]+)?`;
99
+ /** Mirrors `VersionService`'s own default `version.commitMessage` - kept in sync by
100
+ * `version.command.ts`'s documented default, not imported, to keep this module dependency-free. */
101
+ const DEFAULT_COMMIT_MESSAGE = 'chore(release): v{version}';
102
+ /** `VersionService.applyPlan`'s trailing commit for a monorepo root's informational version. */
103
+ const ROOT_SYNC_PATTERN = new RegExp(String.raw `^chore: sync root version to ${SEMVER_SOURCE}$`);
104
+ /** What the commit-message template falls back to when one commit covers several versions at once
105
+ * (a cross-group ripple) - `{version}` has nothing single to substitute, so each bumped package is
106
+ * listed by name instead. */
107
+ const MULTI_VERSION_RELEASE_PATTERN = new RegExp(String.raw `^chore\(release\): \S+@${SEMVER_SOURCE}(?:, \S+@${SEMVER_SOURCE})*$`);
108
+ function escapeRegExp(value) {
109
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
110
+ }
111
+ /** Turns a `version.commitMessage` template into a matcher for the commits it produces: every
112
+ * literal part escaped, each `{version}` placeholder standing in for any semver. */
113
+ function templatePattern(template) {
114
+ const source = template.split('{version}').map(escapeRegExp).join(SEMVER_SOURCE);
115
+ return new RegExp(`^${source}$`);
116
+ }
@@ -185,7 +185,7 @@ async function updateDescription(entry) {
185
185
  const res = await fetch(`https://hub.docker.com/v2/repositories/${namespace}/${imageName}/`, {
186
186
  method: 'PATCH',
187
187
  headers: { Authorization: `JWT ${token}`, 'Content-Type': 'application/json' },
188
- body: JSON.stringify({ description: pkg.json.description, full_description: readme }),
188
+ body: JSON.stringify({ description: pkg.manifest.raw.description, full_description: readme }),
189
189
  });
190
190
  if (!res.ok)
191
191
  throw new Error(`DockerHub description update failed: ${res.status}`);
@@ -47,7 +47,7 @@ export var ExecService;
47
47
  const names = new Set(packages.map(p => p.name));
48
48
  const children = packages.map(pkg => {
49
49
  const ctx = panel.addItem(pkg.name);
50
- const dependencies = topo ? pkg.dependencies.filter(d => names.has(d)) : [];
50
+ const dependencies = topo ? pkg.dependencies.filter(d => names.has(d.name)).map(d => d.name) : [];
51
51
  return new Task(() => execForPackage(ctx, panel.enabled, pkg.dirname, command, logger, bail ? () => rootTask?.abort() : () => { }), { name: pkg.name, dependencies });
52
52
  });
53
53
  panel.start();
@@ -1,5 +1,5 @@
1
- import type { Package } from '../core/package.js';
2
- import type { Repository } from '../core/repository.js';
1
+ import { Package } from '../core/package.js';
2
+ import { Repository } from '../core/repository.js';
3
3
  export declare namespace GithubReleaseService {
4
4
  /** Injectable "does this release already exist" check - mainly for tests, so they don't depend
5
5
  * on network access or a real GitHub token. Same shape as `DockerPublishService.Deps`' own
@@ -1,10 +1,11 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import fastGlob from 'fast-glob';
4
- import semver from 'semver';
5
- import { expandTag, tagPattern } from '../utils/change-hash.js';
4
+ import { Package } from '../core/package.js';
5
+ import { Repository } from '../core/repository.js';
6
6
  import { GitHelper } from '../utils/git.js';
7
7
  import { expandReleaseTag, isCalendarVersion, releaseTagPattern } from '../utils/release-version.js';
8
+ import { ChangeHashService } from './change-hash.service.js';
8
9
  import { ChangelogService } from './changelog.service.js';
9
10
  export var GithubReleaseService;
10
11
  (function (GithubReleaseService) {
@@ -129,18 +130,22 @@ const GITHUB_UPLOADS = 'https://uploads.github.com';
129
130
  * so the release needs a name of its own (`release-*`); a plain one means every package shares it,
130
131
  * and that shared version's tag already *is* the release. */
131
132
  function releaseTagFor(root) {
132
- return isCalendarVersion(root.version) ? expandReleaseTag(root, root.version) : expandTag(root, root.version);
133
+ return isCalendarVersion(root.version)
134
+ ? expandReleaseTag(root, root.version)
135
+ : ChangeHashService.expandTag(root, root.version);
133
136
  }
134
137
  /** The glob matching the tags `releaseTagFor` produces - for stepping back to the previous one. */
135
138
  function releaseTagGlob(root) {
136
- const pattern = isCalendarVersion(root.version) ? releaseTagPattern(root) : tagPattern(root);
139
+ const pattern = isCalendarVersion(root.version) ? releaseTagPattern(root) : ChangeHashService.tagPattern(root);
137
140
  return pattern.replace('{name}', root.name);
138
141
  }
139
142
  function resolvePrerelease(root, version) {
140
143
  const configured = root.config?.githubRelease?.prerelease;
141
144
  // A calendar version's time part is a semver prerelease identifier by construction - it says
142
145
  // nothing about the release being a preview, so it must not be read as one.
143
- return configured ?? (!isCalendarVersion(version) && !!semver.prerelease(version));
146
+ /** The root's own scheme decides what a preview looks like - `semver.prerelease` was that
147
+ * question with semver's answer baked in. */
148
+ return configured ?? (!isCalendarVersion(version) && root.versionScheme.isPrerelease(version));
144
149
  }
145
150
  /** `owner/repo` out of either remote URL form git hands back - `git@github.com:owner/repo.git`
146
151
  * (SSH) or `https://github.com/owner/repo.git` (HTTPS, credentials and all). `undefined` for
@@ -7,7 +7,10 @@ export var ListService;
7
7
  * status relative to upstream. Pure data - no console output; `rman list`'s own command
8
8
  * decides how to present it (table, JSON, parseable, names only, or a dependency graph). */
9
9
  async function getPackages(repository, options = {}) {
10
- const packages = filterPackages(repository.getPackages({ toposort: options.toposort }), options);
10
+ /** `false`: `list` is the inventory, so a package its own `.rmanrc "skip"` excludes is still
11
+ * *in* the repository and still listed - hiding it would answer a different question. Every
12
+ * other caller honours it, being a command that acts rather than reports. */
13
+ const packages = filterPackages(repository.getPackages({ toposort: options.toposort }), options, false);
11
14
  const status = await repository.listStatus({ hash: options.changedSince });
12
15
  let items = packages.map(p => {
13
16
  const target = p.config.publish?.target;
@@ -18,7 +21,7 @@ export var ListService;
18
21
  location: path.relative(repository.dirname, p.dirname) || '.',
19
22
  private: p.isPrivate,
20
23
  status: status[p.name],
21
- dependencies: [...p.dependencies],
24
+ dependencies: p.dependencies.map(d => d.name),
22
25
  publishTargets: [...publishTargets],
23
26
  docker: publishTargets.includes('docker') ? p.config.publish?.docker : undefined,
24
27
  };
@@ -52,6 +52,66 @@ export declare namespace RunService {
52
52
  * run:
53
53
  * test: mocha # same as test: { exec: mocha }
54
54
  */
55
+ /** One command of a script, with the label the progress panel and the per-step log show. */
56
+ interface ScriptStep {
57
+ name: string;
58
+ command: string;
59
+ }
60
+ /** The three slots a script is made of, each one command or several run in sequence. The same
61
+ * three names a `.rmanrc "run.<script>"` block uses, because they are the same three things. */
62
+ interface ScriptSlots {
63
+ before?: string[];
64
+ exec?: string[];
65
+ after?: string[];
66
+ }
67
+ /**
68
+ * Where a package's steps can come from besides its `.rmanrc`.
69
+ *
70
+ * The core knows one source: the config. **`package.json#scripts` is not a source the core has**,
71
+ * because "a script lives in package.json" is true of a Node repository and of nothing else -
72
+ * `rman-node` contributes that one (with npm's `pre<script>`/`post<script>` convention and its
73
+ * `&&` splitting), and a plugin for another ecosystem would contribute its own.
74
+ *
75
+ * Returns `undefined` for "this package declares nothing", not empty slots - the difference
76
+ * decides whether the config's value applies.
77
+ */
78
+ type StepSource = (pkg: Package, script: string) => ScriptSlots | undefined;
79
+ /**
80
+ * Registers a source. Called by `loadPlugins` for each plugin's `runSteps`, in `plugins`
81
+ * declaration order - never as an import side effect, so what is registered is exactly what the
82
+ * repository's `.rmanrc` asked for.
83
+ */
84
+ function addStepSource(source: StepSource): void;
85
+ /** For tests, which would otherwise leak a source into every later case in the process. */
86
+ function clearStepSources(): void;
87
+ /**
88
+ * What the *package itself* declares for the lifecycle `script`, from the contributed sources
89
+ * alone - no `.rmanrc` involved. `undefined` when it declares nothing.
90
+ *
91
+ * Exported because `run` is not the only lifecycle rman wraps: `version` runs hooks around the
92
+ * version write, and npm spells those `preversion`/`version`/`postversion` in `package.json` -
93
+ * which is the same `pre<script>`/`<script>`/`post<script>` shape a step source already answers.
94
+ * So `VersionService` asks here for `'version'` instead of reading `manifest.raw.scripts` itself,
95
+ * and npm's version lifecycle keeps working with no second seam and no extra line in any plugin.
96
+ * A plugin for another ecosystem gets its own lifecycle hooks the moment it contributes steps.
97
+ */
98
+ function contributedSlots(pkg: Package, script: string): ScriptSlots | undefined;
99
+ /**
100
+ * Runs one slot of a lifecycle belonging to some operation other than `run` itself - `version`'s
101
+ * hooks around the version write are the only one so far.
102
+ *
103
+ * **Here rather than in `VersionService`, because the rule it applies is this service's**: the
104
+ * package's own declaration for `script` wins over the caller's `fallback`, slot by slot, exactly
105
+ * as `getScriptSteps` decides it for `run`. Kept in two places that rule would drift, and one of
106
+ * the copies would sit in the file that writes versions - which now runs no command of its own at
107
+ * all.
108
+ *
109
+ * `fallback` is the caller's own configured command, **already evaluated**: `version`'s three
110
+ * paths are in `DEFERRED_PATHS` precisely because only the caller can bind
111
+ * `${{ pkg.targetVersion }}`, so interpolating here would either be too early or need a scope this
112
+ * service has no business holding.
113
+ */
114
+ function runLifecycleSlot(pkg: Package, script: string, slot: keyof ScriptSlots, fallback?: string): Promise<void>;
55
115
  function getConfig(pkg: Package, script: string): Record<string, unknown>;
56
116
  /**
57
117
  * `.rmanrc` conditional execution, GitHub Actions-`if`-flavored but a small closed grammar