rman 0.38.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +371 -21
- package/cli.js +51 -29
- package/commands/build.command.d.ts +3 -0
- package/commands/build.command.js +14 -0
- package/commands/changed.command.d.ts +3 -0
- package/commands/changed.command.js +29 -0
- package/commands/changelog.command.d.ts +3 -0
- package/commands/changelog.command.js +63 -0
- package/commands/ci.command.d.ts +3 -0
- package/commands/ci.command.js +30 -0
- package/commands/clean.command.d.ts +3 -0
- package/commands/clean.command.js +36 -0
- package/commands/diff.command.d.ts +3 -0
- package/commands/diff.command.js +51 -0
- package/commands/exec.command.d.ts +3 -0
- package/commands/exec.command.js +83 -0
- package/commands/import.command.d.ts +3 -0
- package/commands/import.command.js +27 -0
- package/commands/info.command.d.ts +3 -0
- package/commands/info.command.js +61 -0
- package/commands/list.command.d.ts +3 -0
- package/commands/list.command.js +113 -0
- package/commands/publish.command.d.ts +3 -0
- package/commands/publish.command.js +147 -0
- package/commands/run.command.d.ts +6 -0
- package/commands/run.command.js +80 -0
- package/commands/test.command.d.ts +3 -0
- package/commands/test.command.js +14 -0
- package/commands/version.command.d.ts +3 -0
- package/commands/version.command.js +125 -0
- package/constants.js +1 -1
- package/core/config.d.ts +13 -0
- package/core/config.js +61 -0
- package/core/package.d.ts +2 -0
- package/core/package.js +2 -0
- package/core/repository.d.ts +36 -3
- package/core/repository.js +90 -45
- package/index.d.ts +17 -1
- package/index.js +15 -1
- package/package.json +8 -4
- package/rmanrc.schema.json +202 -0
- package/services/changelog.service.d.ts +99 -0
- package/services/changelog.service.js +272 -0
- package/services/ci.service.d.ts +40 -0
- package/services/ci.service.js +204 -0
- package/services/clean.service.d.ts +42 -0
- package/services/clean.service.js +226 -0
- package/services/exec.service.d.ts +35 -0
- package/services/exec.service.js +114 -0
- package/services/import.service.d.ts +30 -0
- package/services/import.service.js +84 -0
- package/services/list.service.d.ts +27 -0
- package/services/list.service.js +25 -0
- package/services/publish.service.d.ts +79 -0
- package/services/publish.service.js +199 -0
- package/services/run.service.d.ts +99 -0
- package/services/run.service.js +432 -0
- package/services/system-info.d.ts +16 -0
- package/services/system-info.js +25 -0
- package/services/version.service.d.ts +95 -0
- package/services/version.service.js +450 -0
- package/services.d.ts +10 -0
- package/services.js +10 -0
- package/utils/branch-guard.d.ts +25 -0
- package/utils/branch-guard.js +61 -0
- package/utils/change-hash.d.ts +55 -0
- package/utils/change-hash.js +80 -0
- package/utils/conventional-commits.d.ts +39 -0
- package/utils/conventional-commits.js +43 -0
- package/utils/exec.d.ts +5 -6
- package/utils/exec.js +45 -59
- package/utils/git.d.ts +86 -0
- package/utils/git.js +276 -0
- package/utils/live-region.d.ts +16 -0
- package/utils/live-region.js +59 -0
- package/utils/logger.d.ts +24 -0
- package/utils/logger.js +39 -0
- package/utils/npm-run-path.d.ts +1 -1
- package/utils/npm-run-path.js +10 -10
- package/utils/package-filter.d.ts +32 -0
- package/utils/package-filter.js +73 -0
- package/utils/progress-panel.d.ts +59 -0
- package/utils/progress-panel.js +149 -0
- package/utils/workspace-range.d.ts +17 -0
- package/utils/workspace-range.js +28 -0
- package/commands/build-command.d.ts +0 -11
- package/commands/build-command.js +0 -26
- package/commands/changed-command.d.ts +0 -16
- package/commands/changed-command.js +0 -35
- package/commands/ci-command.d.ts +0 -24
- package/commands/ci-command.js +0 -71
- package/commands/execute-command.d.ts +0 -19
- package/commands/execute-command.js +0 -75
- package/commands/info-command.d.ts +0 -10
- package/commands/info-command.js +0 -57
- package/commands/list-command.d.ts +0 -38
- package/commands/list-command.js +0 -137
- package/commands/multi-task-command.d.ts +0 -22
- package/commands/multi-task-command.js +0 -68
- package/commands/publish-command.d.ts +0 -24
- package/commands/publish-command.js +0 -135
- package/commands/run-command.d.ts +0 -28
- package/commands/run-command.js +0 -134
- package/commands/version-command.d.ts +0 -25
- package/commands/version-command.js +0 -198
- package/core/command.d.ts +0 -35
- package/core/command.js +0 -117
- package/core/constants.d.ts +0 -1
- package/core/constants.js +0 -2
- package/core/logger.d.ts +0 -12
- package/core/logger.js +0 -2
- package/utils/file-utils.d.ts +0 -4
- package/utils/file-utils.js +0 -29
- package/utils/git-utils.d.ts +0 -25
- package/utils/git-utils.js +0 -66
- package/utils/npm-utils.d.ts +0 -11
- package/utils/npm-utils.js +0 -33
- package/utils/package-not-found-error.d.ts +0 -2
- package/utils/package-not-found-error.js +0 -2
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://raw.githubusercontent.com/panates/rman/main/schemas/rmanrc.schema.json",
|
|
4
|
+
"title": "rman configuration",
|
|
5
|
+
"description": "Schema for rman's .rmanrc (JSON) and .rman.yml (YAML) config files, and the \"rman\" key in package.json. See docs/api.md#configuration-rmanrc-rmanyml for the full reference.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"$schema": {
|
|
9
|
+
"type": "string",
|
|
10
|
+
"description": "Path/URL to this schema - not read by rman itself, purely for editor tooling."
|
|
11
|
+
},
|
|
12
|
+
"packageManager": {
|
|
13
|
+
"type": "string",
|
|
14
|
+
"enum": ["npm", "yarn", "pnpm", "bun"],
|
|
15
|
+
"default": "npm",
|
|
16
|
+
"description": "Package manager used by \"ci\"/\"publish\". Root-level only - an explicit --package-manager CLI flag wins over this."
|
|
17
|
+
},
|
|
18
|
+
"logLevel": {
|
|
19
|
+
"$ref": "#/definitions/logLevel",
|
|
20
|
+
"default": "info",
|
|
21
|
+
"description": "Default verbosity of the classic per-step log for run/build/test/ci/clean. Root-level only. An explicit --log-level CLI flag wins over this."
|
|
22
|
+
},
|
|
23
|
+
"allowBranch": {
|
|
24
|
+
"$ref": "#/definitions/stringOrStringArray",
|
|
25
|
+
"description": "Refuse to run a state-changing command unless the current git branch matches one of these globs. Root-level only. An explicit --allow-branch CLI flag replaces this entirely (never merges)."
|
|
26
|
+
},
|
|
27
|
+
"ignoreBranch": {
|
|
28
|
+
"$ref": "#/definitions/stringOrStringArray",
|
|
29
|
+
"description": "Refuse to run a state-changing command if the current git branch matches one of these globs. Root-level only. An explicit --ignore-branch CLI flag replaces this entirely (never merges)."
|
|
30
|
+
},
|
|
31
|
+
"group": {
|
|
32
|
+
"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
|
+
"default": true,
|
|
34
|
+
"oneOf": [{ "type": "boolean" }, { "type": "string", "minLength": 1 }]
|
|
35
|
+
},
|
|
36
|
+
"version": {
|
|
37
|
+
"type": "object",
|
|
38
|
+
"description": "Options for the \"version\" command/VersionService.",
|
|
39
|
+
"additionalProperties": false,
|
|
40
|
+
"properties": {
|
|
41
|
+
"commitMessage": {
|
|
42
|
+
"type": "string",
|
|
43
|
+
"default": "chore(release): v{version}",
|
|
44
|
+
"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
|
+
},
|
|
46
|
+
"script": {
|
|
47
|
+
"$ref": "#/definitions/stringOrStringArray",
|
|
48
|
+
"description": "Command(s) to run as this package's own \"version\" npm-lifecycle step, when its package.json does not define one itself."
|
|
49
|
+
},
|
|
50
|
+
"preScript": {
|
|
51
|
+
"$ref": "#/definitions/stringOrStringArray",
|
|
52
|
+
"description": "Command(s) to run as this package's own \"preversion\" step, when its package.json does not define one itself."
|
|
53
|
+
},
|
|
54
|
+
"postScript": {
|
|
55
|
+
"$ref": "#/definitions/stringOrStringArray",
|
|
56
|
+
"description": "Command(s) to run as this package's own \"postversion\" step, when its package.json does not define one itself."
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"changelog": {
|
|
61
|
+
"type": "object",
|
|
62
|
+
"description": "Options for the \"changelog\" command/ChangelogService, and for \"version --changelog\".",
|
|
63
|
+
"additionalProperties": false,
|
|
64
|
+
"properties": {
|
|
65
|
+
"ignoreTypes": {
|
|
66
|
+
"type": "array",
|
|
67
|
+
"items": { "type": "string" },
|
|
68
|
+
"default": [],
|
|
69
|
+
"description": "Conventional Commit types (e.g. \"chore\", \"ci\") dropped entirely from changelog output, instead of being folded into \"Other Changes\". Per-package cascaded."
|
|
70
|
+
},
|
|
71
|
+
"template": {
|
|
72
|
+
"type": "string",
|
|
73
|
+
"description": "Path (relative to the repository root) to a changelog template file - not the template text itself. Supports {{package}}/{{version}}/{{date}}/{{commits}} and {{features}}/{{fixes}}/{{other}}. Per-package cascaded."
|
|
74
|
+
},
|
|
75
|
+
"filePath": {
|
|
76
|
+
"type": "string",
|
|
77
|
+
"default": "CHANGELOG.md",
|
|
78
|
+
"description": "Where \"--write\" prepends this package's entry, relative to that package's own directory. Per-package cascaded."
|
|
79
|
+
},
|
|
80
|
+
"tagPattern": {
|
|
81
|
+
"type": "string",
|
|
82
|
+
"default": "v*",
|
|
83
|
+
"description": "Glob for this package's release tags. Include \"{name}\" for independent per-package tags (e.g. \"{name}@*\"); omit it for one shared repo-wide tag scheme. Per-package cascaded."
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
"clean": {
|
|
88
|
+
"type": "object",
|
|
89
|
+
"description": "Options for the \"clean\" command/CleanService.",
|
|
90
|
+
"additionalProperties": false,
|
|
91
|
+
"properties": {
|
|
92
|
+
"include": {
|
|
93
|
+
"$ref": "#/definitions/stringOrStringArray",
|
|
94
|
+
"description": "Extra glob(s) to remove, resolved relative to this package's own directory. Per-package cascaded (a package's own value replaces the root's, it does not merge)."
|
|
95
|
+
},
|
|
96
|
+
"exclude": {
|
|
97
|
+
"$ref": "#/definitions/stringOrStringArray",
|
|
98
|
+
"description": "Glob(s) protected from \"include\" (and from the built-in TypeScript-artifact cleanup), relative to this package's own directory. Per-package cascaded."
|
|
99
|
+
},
|
|
100
|
+
"skip": {
|
|
101
|
+
"type": "boolean",
|
|
102
|
+
"default": false,
|
|
103
|
+
"description": "Opts this package out of \"clean\" entirely. Per-package cascaded."
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
"run": {
|
|
108
|
+
"type": "object",
|
|
109
|
+
"description": "Per-script options for \"run\"/\"build\"/\"test\"/RunService, keyed by npm script name (e.g. \"build\", \"lint\", \"test\").",
|
|
110
|
+
"additionalProperties": { "$ref": "#/definitions/runScriptConfig" }
|
|
111
|
+
},
|
|
112
|
+
"packages": {
|
|
113
|
+
"type": "object",
|
|
114
|
+
"description": "Per-package overrides, keyed by the in-repo package's own name.",
|
|
115
|
+
"additionalProperties": {
|
|
116
|
+
"type": "object",
|
|
117
|
+
"additionalProperties": false,
|
|
118
|
+
"properties": {
|
|
119
|
+
"dependencies": {
|
|
120
|
+
"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.",
|
|
121
|
+
"oneOf": [
|
|
122
|
+
{ "type": "array", "items": { "type": "string" } },
|
|
123
|
+
{ "type": "object", "additionalProperties": { "type": "string" } }
|
|
124
|
+
]
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
"definitions": {
|
|
131
|
+
"logLevel": {
|
|
132
|
+
"type": "string",
|
|
133
|
+
"enum": ["silent", "error", "info", "verbose"]
|
|
134
|
+
},
|
|
135
|
+
"stringOrStringArray": {
|
|
136
|
+
"oneOf": [
|
|
137
|
+
{ "type": "string", "minLength": 1 },
|
|
138
|
+
{ "type": "array", "items": { "type": "string", "minLength": 1 } }
|
|
139
|
+
]
|
|
140
|
+
},
|
|
141
|
+
"runScriptConfig": {
|
|
142
|
+
"type": "object",
|
|
143
|
+
"additionalProperties": false,
|
|
144
|
+
"properties": {
|
|
145
|
+
"concurrency": {
|
|
146
|
+
"type": "integer",
|
|
147
|
+
"minimum": 1,
|
|
148
|
+
"description": "Max packages built at once for this script. Default: CPU count."
|
|
149
|
+
},
|
|
150
|
+
"topo": {
|
|
151
|
+
"type": "boolean",
|
|
152
|
+
"default": true,
|
|
153
|
+
"description": "Respect the package dependency graph: a package waits for its dependencies and is skipped if one fails. Set false for independent scripts (lint/test) - alphabetical order, no failure skipping."
|
|
154
|
+
},
|
|
155
|
+
"bail": {
|
|
156
|
+
"type": "boolean",
|
|
157
|
+
"default": true,
|
|
158
|
+
"description": "Stop the whole batch on this package's own failure. Unusual precedence: this package-level value outranks even an explicit CLI --bail/--no-bail flag."
|
|
159
|
+
},
|
|
160
|
+
"progress": {
|
|
161
|
+
"type": "boolean",
|
|
162
|
+
"default": true,
|
|
163
|
+
"description": "Show the live progress panel for this script (auto-disabled off a TTY)."
|
|
164
|
+
},
|
|
165
|
+
"logLevel": {
|
|
166
|
+
"$ref": "#/definitions/logLevel",
|
|
167
|
+
"description": "Verbosity of the classic per-step log for this package/script, when the live panel is off."
|
|
168
|
+
},
|
|
169
|
+
"changedSince": {
|
|
170
|
+
"type": "string",
|
|
171
|
+
"description": "Root-level fallback commit/hash for \"--changed-since\", used only when the CLI flag is not given."
|
|
172
|
+
},
|
|
173
|
+
"skip": {
|
|
174
|
+
"type": "boolean",
|
|
175
|
+
"default": false,
|
|
176
|
+
"description": "Excludes this package (or the repository root's own pre/post hooks) from running this script entirely."
|
|
177
|
+
},
|
|
178
|
+
"if": {
|
|
179
|
+
"type": "string",
|
|
180
|
+
"description": "Conditional-execution expression: atoms \"changed\"/\"dirty\"/\"committed\" (optionally \"= <hash>\" or \"= {ENV_VAR}\"), combined with and/or/not/(...). E.g. \"changed\", \"(changed or dirty) and not committed\"."
|
|
181
|
+
},
|
|
182
|
+
"script": {
|
|
183
|
+
"$ref": "#/definitions/stringOrStringArray",
|
|
184
|
+
"description": "Command(s) to run as this package's own script, when its package.json does not define one for this script name."
|
|
185
|
+
},
|
|
186
|
+
"preScript": {
|
|
187
|
+
"$ref": "#/definitions/stringOrStringArray",
|
|
188
|
+
"description": "Command(s) to run as this package's own \"pre<script>\" hook, when its package.json does not define one."
|
|
189
|
+
},
|
|
190
|
+
"postScript": {
|
|
191
|
+
"$ref": "#/definitions/stringOrStringArray",
|
|
192
|
+
"description": "Command(s) to run as this package's own \"post<script>\" hook, when its package.json does not define one."
|
|
193
|
+
},
|
|
194
|
+
"override": {
|
|
195
|
+
"type": "boolean",
|
|
196
|
+
"default": false,
|
|
197
|
+
"description": "When true, \"script\"/\"preScript\"/\"postScript\" above replace the package's own package.json definition even when it already has one."
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { Package } from '../core/package.js';
|
|
2
|
+
import type { Repository } from '../core/repository.js';
|
|
3
|
+
import { type PackageFilterOptions } from '../utils/package-filter.js';
|
|
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
|
+
interface Options extends PackageFilterOptions {
|
|
12
|
+
/** 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
|
|
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). */
|
|
18
|
+
from?: string;
|
|
19
|
+
/** Generate for the whole repository even when the current directory is inside a single
|
|
20
|
+
* package (which otherwise scopes it to just that package) - see `Repository.currentPackage`. */
|
|
21
|
+
root?: boolean;
|
|
22
|
+
/** Where a package's changelog file lives, relative to *that package's own* directory -
|
|
23
|
+
* default `'CHANGELOG.md'`. Applies the same way to every package; for a package that wants
|
|
24
|
+
* its own filename instead, use `.rmanrc changelog.filePath` (cascaded, per-package
|
|
25
|
+
* overridable) rather than this option - see `resolveFilePath`. Consulted even without
|
|
26
|
+
* `write`: when auto-detecting, if this file already exists its own last-modifying commit
|
|
27
|
+
* also lower-bounds the boundary, so a stale file (last updated for an older version than
|
|
28
|
+
* what's actually published) doesn't get changes silently skipped over - see
|
|
29
|
+
* `detectChangeHash`'s `catchUpFile`. */
|
|
30
|
+
filePath?: string;
|
|
31
|
+
}
|
|
32
|
+
/** One package's (root included) generated changelog entry - what `getEntries`/`generate`
|
|
33
|
+
* return. */
|
|
34
|
+
interface Entry {
|
|
35
|
+
package: Package;
|
|
36
|
+
/** Display name for this entry's heading - `"<repo dir name> repository"` for the root
|
|
37
|
+
* package, its own name otherwise (see `getEntries`'s doc comment on `{{package}}`). */
|
|
38
|
+
label: string;
|
|
39
|
+
/** Resolved from git tags, not package.json - see `resolveVersion`. */
|
|
40
|
+
version: string;
|
|
41
|
+
features: string[];
|
|
42
|
+
fixes: string[];
|
|
43
|
+
other: string[];
|
|
44
|
+
/** The fully rendered entry, via `.rmanrc changelog.template` (or the built-in default). */
|
|
45
|
+
content: string;
|
|
46
|
+
/** Where this entry would be (or, with `options.write`, was) written, relative to the
|
|
47
|
+
* package's own directory - see `GetOptions.filePath`. */
|
|
48
|
+
filePath: string;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Same as `getEntries`, and additionally - for every returned entry, when `options.write` is
|
|
52
|
+
* set - prepends `entry.content` into that package's own changelog file (see `Entry.filePath`).
|
|
53
|
+
* Still pure with respect to console output: writing a file is a real, callable-for-its-own-
|
|
54
|
+
* sake side effect (a "save this" request), not presentation, so it stays here rather than in
|
|
55
|
+
* the CLI command - printing what happened is the command's job.
|
|
56
|
+
*/
|
|
57
|
+
function generateToFile(repository: Repository, options?: Options, deps?: Deps): Promise<Entry[]>;
|
|
58
|
+
/**
|
|
59
|
+
* Computes a changelog entry per package (root included) from real commits only - either
|
|
60
|
+
* everything since a given `--from <hash>` (applied the same way to every package), or, by
|
|
61
|
+
* default, auto-detected per package instead (see `detectChangeHash`). Pure: returns the
|
|
62
|
+
* entries, never prints or touches `CHANGELOG.md` - see `generate` for that.
|
|
63
|
+
*
|
|
64
|
+
* A commit is attributed to every package its files fall under (root included, for anything
|
|
65
|
+
* outside every package) - unless it's broad enough to count as a repo-wide change (see
|
|
66
|
+
* `BROAD_COMMIT_THRESHOLD`/`ownersOf`), in which case it goes to root alone instead of being
|
|
67
|
+
* repeated verbatim across most of the repository. Subject lines are grouped ✨ Features/🐛 Bug
|
|
68
|
+
* Fixes/🔧 Other Changes on a best-effort Conventional Commits read; anything that doesn't parse
|
|
69
|
+
* just lands in Other Changes as-is, so a repo that doesn't follow that convention still gets a
|
|
70
|
+
* usable list. `.rmanrc changelog.ignoreTypes` (cascaded, e.g. `[chore, dev]`) drops commits of
|
|
71
|
+
* those types entirely instead - see `ignoreTypesConfig`. A bare version-bump commit
|
|
72
|
+
* (`"6.0.1"`) is always dropped outright, regardless of `ignoreTypes` - see
|
|
73
|
+
* `VERSION_BUMP_PATTERN`.
|
|
74
|
+
*
|
|
75
|
+
* By default (or with `--from npm` explicitly), the boundary is auto-detected per package
|
|
76
|
+
* instead of one shared one - see `detectChangeHash`. A package that can't be resolved this way
|
|
77
|
+
* (unpublished, no network, no matching tag) falls back to its own commits not yet pushed to
|
|
78
|
+
* the current branch's upstream (the same reference point `--changed`/`--changed-since` use
|
|
79
|
+
* elsewhere, via `GitHelper.listCommits`) - so the command still produces something useful even
|
|
80
|
+
* for a repo that's never been published or tagged at all. Packages that end up resolving to
|
|
81
|
+
* the same hash (an explicit one, or several packages sharing one tag under fixed versioning)
|
|
82
|
+
* only have their commits fetched once, not once per package.
|
|
83
|
+
*
|
|
84
|
+
* Formatting comes from `.rmanrc changelog.template` - a *path* to a template file (not the
|
|
85
|
+
* template text itself, to keep `.rmanrc` readable), with `{{package}}`/`{{version}}`/
|
|
86
|
+
* `{{date}}`/`{{commits}}` (the full grouped block) and `{{features}}`/`{{fixes}}`/`{{other}}`
|
|
87
|
+
* (their bullet lists alone, for templates that want their own headings/order) - see
|
|
88
|
+
* `resolveTemplate`. `{{package}}` for the repository root is `"<repo dir name> repository"`
|
|
89
|
+
* (e.g. "sqb repository"), not its raw package.json name - which is often a private,
|
|
90
|
+
* non-published placeholder (`"sqb.v4"`) that reads like a stray version marker rather than a
|
|
91
|
+
* recognizable label. `{{version}}` comes from git tags, not package.json (which can drift out
|
|
92
|
+
* of sync with what's actually been released) - see `resolveVersion`/`.rmanrc
|
|
93
|
+
* changelog.tagPattern`.
|
|
94
|
+
*
|
|
95
|
+
* Run from inside a single package's own directory, it only covers that package unless
|
|
96
|
+
* `options.root` says otherwise (see `Repository.currentPackage`).
|
|
97
|
+
*/
|
|
98
|
+
function getEntries(repository: Repository, options?: Options, deps?: Deps): Promise<Entry[]>;
|
|
99
|
+
}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { detectChangeHash, extractVersion, findLatestTag, tagPattern } from '../utils/change-hash.js';
|
|
4
|
+
import { parseConventionalCommit, VERSION_BUMP_PATTERN } from '../utils/conventional-commits.js';
|
|
5
|
+
import { GitHelper } from '../utils/git.js';
|
|
6
|
+
import { filterPackages } from '../utils/package-filter.js';
|
|
7
|
+
export var ChangelogService;
|
|
8
|
+
(function (ChangelogService) {
|
|
9
|
+
/**
|
|
10
|
+
* Same as `getEntries`, and additionally - for every returned entry, when `options.write` is
|
|
11
|
+
* set - prepends `entry.content` into that package's own changelog file (see `Entry.filePath`).
|
|
12
|
+
* Still pure with respect to console output: writing a file is a real, callable-for-its-own-
|
|
13
|
+
* sake side effect (a "save this" request), not presentation, so it stays here rather than in
|
|
14
|
+
* the CLI command - printing what happened is the command's job.
|
|
15
|
+
*/
|
|
16
|
+
async function generateToFile(repository, options = {}, deps = {}) {
|
|
17
|
+
const entries = await getEntries(repository, options, deps);
|
|
18
|
+
for (const entry of entries)
|
|
19
|
+
prependToChangelogFile(entry.package, entry.filePath, entry.content);
|
|
20
|
+
return entries;
|
|
21
|
+
}
|
|
22
|
+
ChangelogService.generateToFile = generateToFile;
|
|
23
|
+
/**
|
|
24
|
+
* Computes a changelog entry per package (root included) from real commits only - either
|
|
25
|
+
* everything since a given `--from <hash>` (applied the same way to every package), or, by
|
|
26
|
+
* default, auto-detected per package instead (see `detectChangeHash`). Pure: returns the
|
|
27
|
+
* entries, never prints or touches `CHANGELOG.md` - see `generate` for that.
|
|
28
|
+
*
|
|
29
|
+
* A commit is attributed to every package its files fall under (root included, for anything
|
|
30
|
+
* outside every package) - unless it's broad enough to count as a repo-wide change (see
|
|
31
|
+
* `BROAD_COMMIT_THRESHOLD`/`ownersOf`), in which case it goes to root alone instead of being
|
|
32
|
+
* repeated verbatim across most of the repository. Subject lines are grouped ✨ Features/🐛 Bug
|
|
33
|
+
* Fixes/🔧 Other Changes on a best-effort Conventional Commits read; anything that doesn't parse
|
|
34
|
+
* just lands in Other Changes as-is, so a repo that doesn't follow that convention still gets a
|
|
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`.
|
|
39
|
+
*
|
|
40
|
+
* By default (or with `--from npm` explicitly), the boundary is auto-detected per package
|
|
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
|
|
46
|
+
* the same hash (an explicit one, or several packages sharing one tag under fixed versioning)
|
|
47
|
+
* only have their commits fetched once, not once per package.
|
|
48
|
+
*
|
|
49
|
+
* Formatting comes from `.rmanrc changelog.template` - a *path* to a template file (not the
|
|
50
|
+
* template text itself, to keep `.rmanrc` readable), with `{{package}}`/`{{version}}`/
|
|
51
|
+
* `{{date}}`/`{{commits}}` (the full grouped block) and `{{features}}`/`{{fixes}}`/`{{other}}`
|
|
52
|
+
* (their bullet lists alone, for templates that want their own headings/order) - see
|
|
53
|
+
* `resolveTemplate`. `{{package}}` for the repository root is `"<repo dir name> repository"`
|
|
54
|
+
* (e.g. "sqb repository"), not its raw package.json name - which is often a private,
|
|
55
|
+
* non-published placeholder (`"sqb.v4"`) that reads like a stray version marker rather than a
|
|
56
|
+
* recognizable label. `{{version}}` comes from git tags, not package.json (which can drift out
|
|
57
|
+
* of sync with what's actually been released) - see `resolveVersion`/`.rmanrc
|
|
58
|
+
* changelog.tagPattern`.
|
|
59
|
+
*
|
|
60
|
+
* Run from inside a single package's own directory, it only covers that package unless
|
|
61
|
+
* `options.root` says otherwise (see `Repository.currentPackage`).
|
|
62
|
+
*/
|
|
63
|
+
async function getEntries(repository, options = {}, deps = {}) {
|
|
64
|
+
const cwdScope = options.root ? undefined : repository.currentPackage;
|
|
65
|
+
const packages = repository.getPackages().filter(p => p !== repository.rootPackage);
|
|
66
|
+
const targets = cwdScope ? [cwdScope] : filterPackages([repository.rootPackage, ...packages], options);
|
|
67
|
+
const git = new GitHelper({ cwd: repository.dirname });
|
|
68
|
+
// dropped up front, not just while grouping - a package whose only commits are version bumps
|
|
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));
|
|
71
|
+
// Several packages often resolve to the identical hash (an explicit --from <hash> applies to
|
|
72
|
+
// all of them the same way; under fixed versioning, npm auto-detection usually does too) - so
|
|
73
|
+
// the git fetch for a given hash is cached, run once no matter how many packages share it.
|
|
74
|
+
const commitsByHash = new Map();
|
|
75
|
+
const listCommitsCached = (hash) => {
|
|
76
|
+
const key = hash ?? '';
|
|
77
|
+
let promise = commitsByHash.get(key);
|
|
78
|
+
if (!promise) {
|
|
79
|
+
promise = git.listCommits({ hash }).then(dropVersionBumps);
|
|
80
|
+
commitsByHash.set(key, promise);
|
|
81
|
+
}
|
|
82
|
+
return promise;
|
|
83
|
+
};
|
|
84
|
+
const commitsByTarget = await Promise.all(targets.map(async (pkg) => {
|
|
85
|
+
const catchUpFile = path.join(pkg.dirname, resolveFilePath(pkg, options.filePath));
|
|
86
|
+
const from = await detectChangeHash(git, pkg, {
|
|
87
|
+
from: options.from,
|
|
88
|
+
npmViewVersion: deps.npmViewVersion,
|
|
89
|
+
catchUpFile: fs.existsSync(catchUpFile) ? catchUpFile : undefined,
|
|
90
|
+
});
|
|
91
|
+
return listCommitsCached(from);
|
|
92
|
+
}));
|
|
93
|
+
const entries = [];
|
|
94
|
+
for (let i = 0; i < targets.length; i++) {
|
|
95
|
+
const pkg = targets[i];
|
|
96
|
+
const ownCommits = commitsByTarget[i].filter(c => ownersOf(repository, c).has(pkg));
|
|
97
|
+
if (!ownCommits.length)
|
|
98
|
+
continue;
|
|
99
|
+
const grouped = groupCommits(ownCommits.map(c => c.subject), ignoreTypesConfig(pkg));
|
|
100
|
+
// every commit could have been dropped by ignoreTypes - skip this package's entry entirely
|
|
101
|
+
// rather than rendering a heading with nothing real underneath it.
|
|
102
|
+
if (!grouped.features.length && !grouped.fixes.length && !grouped.other.length)
|
|
103
|
+
continue;
|
|
104
|
+
const label = pkg === repository.rootPackage ? `${path.basename(repository.dirname)} repository` : pkg.name;
|
|
105
|
+
const { version, content } = await renderEntry(repository, pkg, label, grouped, git);
|
|
106
|
+
entries.push({
|
|
107
|
+
package: pkg,
|
|
108
|
+
label,
|
|
109
|
+
version,
|
|
110
|
+
features: grouped.features,
|
|
111
|
+
fixes: grouped.fixes,
|
|
112
|
+
other: grouped.other,
|
|
113
|
+
content,
|
|
114
|
+
filePath: resolveFilePath(pkg, options.filePath),
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
return entries;
|
|
118
|
+
}
|
|
119
|
+
ChangelogService.getEntries = getEntries;
|
|
120
|
+
})(ChangelogService || (ChangelogService = {}));
|
|
121
|
+
/** `.rmanrc changelog.ignoreTypes` (cascaded, per-package overridable) - Conventional Commits
|
|
122
|
+
* `type`s to drop entirely (e.g. `[chore, dev]`), not just fold into "Other Changes". Only
|
|
123
|
+
* applies to commits that actually parse as `type: ...` - a non-conventional message always
|
|
124
|
+
* still lands in Other Changes, since it has no `type` to match against. */
|
|
125
|
+
function ignoreTypesConfig(pkg) {
|
|
126
|
+
const v = pkg.config?.changelog?.ignoreTypes;
|
|
127
|
+
return new Set(Array.isArray(v) ? v.map(t => String(t).toLowerCase()) : []);
|
|
128
|
+
}
|
|
129
|
+
function groupCommits(subjects, ignoreTypes = new Set()) {
|
|
130
|
+
const grouped = { features: [], fixes: [], other: [] };
|
|
131
|
+
for (const subject of subjects) {
|
|
132
|
+
if (VERSION_BUMP_PATTERN.test(subject))
|
|
133
|
+
continue;
|
|
134
|
+
const parsed = parseConventionalCommit(subject);
|
|
135
|
+
if (!parsed) {
|
|
136
|
+
grouped.other.push(subject);
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
const { type, scope, description } = parsed;
|
|
140
|
+
if (ignoreTypes.has(type))
|
|
141
|
+
continue;
|
|
142
|
+
const line = scope ? `**${scope}:** ${description}` : description;
|
|
143
|
+
if (type === 'feat')
|
|
144
|
+
grouped.features.push(line);
|
|
145
|
+
else if (type === 'fix')
|
|
146
|
+
grouped.fixes.push(line);
|
|
147
|
+
else
|
|
148
|
+
grouped.other.push(subject);
|
|
149
|
+
}
|
|
150
|
+
return grouped;
|
|
151
|
+
}
|
|
152
|
+
function bulletList(lines) {
|
|
153
|
+
return lines.map(l => `- ${l}`).join('\n');
|
|
154
|
+
}
|
|
155
|
+
function renderCommitsBlock(grouped) {
|
|
156
|
+
const sections = [];
|
|
157
|
+
if (grouped.features.length)
|
|
158
|
+
sections.push(`### ✨ Features\n\n${bulletList(grouped.features)}`);
|
|
159
|
+
if (grouped.fixes.length)
|
|
160
|
+
sections.push(`### 🐛 Bug Fixes\n\n${bulletList(grouped.fixes)}`);
|
|
161
|
+
if (grouped.other.length)
|
|
162
|
+
sections.push(`### 🔧 Other Changes\n\n${bulletList(grouped.other)}`);
|
|
163
|
+
return sections.join('\n\n');
|
|
164
|
+
}
|
|
165
|
+
const DEFAULT_TEMPLATE = '## {{package}} {{version}} ({{date}})\n\n{{commits}}\n';
|
|
166
|
+
/** `.rmanrc changelog.template` is a *path* to a template file (resolved relative to the
|
|
167
|
+
* repository root, regardless of which level's `.rmanrc` declared it), not the template text
|
|
168
|
+
* itself - keeping multi-line Markdown out of `.rmanrc` so it stays readable. Falls back to a
|
|
169
|
+
* small built-in template when unset. */
|
|
170
|
+
function resolveTemplate(repository, pkg) {
|
|
171
|
+
const templatePath = pkg.config?.changelog?.template;
|
|
172
|
+
if (typeof templatePath !== 'string' || !templatePath)
|
|
173
|
+
return DEFAULT_TEMPLATE;
|
|
174
|
+
const abs = path.resolve(repository.dirname, templatePath);
|
|
175
|
+
if (!fs.existsSync(abs)) {
|
|
176
|
+
throw new Error(`changelog.template not found for "${pkg.name}": "${templatePath}" (resolved to "${abs}")`);
|
|
177
|
+
}
|
|
178
|
+
return fs.readFileSync(abs, 'utf-8');
|
|
179
|
+
}
|
|
180
|
+
function render(template, values) {
|
|
181
|
+
return template.replace(/\{\{(\w+)\}\}/g, (_, key) => values[key] ?? '');
|
|
182
|
+
}
|
|
183
|
+
const DEFAULT_CHANGELOG_FILE = 'CHANGELOG.md';
|
|
184
|
+
/** Where `write` prepends this package's entry, relative to its own directory. An explicit
|
|
185
|
+
* `optionsFilePath` (`Changelog.Options.filePath`, CLI `--file-path`) applies the same way to
|
|
186
|
+
* every package and wins over `.rmanrc changelog.filePath` (cascaded, per-package overridable),
|
|
187
|
+
* which in turn wins over the default `'CHANGELOG.md'`. */
|
|
188
|
+
function resolveFilePath(pkg, optionsFilePath) {
|
|
189
|
+
if (optionsFilePath)
|
|
190
|
+
return optionsFilePath;
|
|
191
|
+
const cfg = pkg.config?.changelog?.filePath;
|
|
192
|
+
return typeof cfg === 'string' && cfg ? cfg : DEFAULT_CHANGELOG_FILE;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* This package's current version, from git tags rather than its (possibly stale - see the
|
|
196
|
+
* `{{version}}` doc on `Changelog.getEntries`) package.json. Falls back to package.json's version
|
|
197
|
+
* if no matching tag exists at all (never tagged, or a fresh package) - see `findLatestTag`.
|
|
198
|
+
*/
|
|
199
|
+
async function resolveVersion(git, pkg) {
|
|
200
|
+
const tag = await findLatestTag(git, pkg);
|
|
201
|
+
return tag ? extractVersion(tag, tagPattern(pkg).replace('{name}', pkg.name)) : pkg.version || '';
|
|
202
|
+
}
|
|
203
|
+
/** The most specific package whose directory contains `file` - the repository root itself as the
|
|
204
|
+
* fallback for anything outside every package (e.g. root-level config files). Mirrors
|
|
205
|
+
* `Repository.currentPackage`'s longest-prefix logic, but always resolves to *something*
|
|
206
|
+
* (root), rather than `undefined`, since every file belongs to some changelog. */
|
|
207
|
+
function owningPackage(repository, file) {
|
|
208
|
+
let best = repository.rootPackage;
|
|
209
|
+
for (const pkg of repository.packages) {
|
|
210
|
+
const rel = path.relative(pkg.dirname, file);
|
|
211
|
+
const isSelfOrDescendant = rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));
|
|
212
|
+
if (isSelfOrDescendant && pkg.dirname.length > best.dirname.length)
|
|
213
|
+
best = pkg;
|
|
214
|
+
}
|
|
215
|
+
return best;
|
|
216
|
+
}
|
|
217
|
+
/** A commit touching more than this fraction of all packages (a repo-wide relicense, a doc
|
|
218
|
+
* update stamped into every package's README, ...) is treated as a repo-wide change rather than
|
|
219
|
+
* attributed to each of them - see `ownersOf`. Without this, one such commit would show up
|
|
220
|
+
* verbatim in every single package's changelog, making them all look identical. */
|
|
221
|
+
const BROAD_COMMIT_THRESHOLD = 0.5;
|
|
222
|
+
/** ...but only once at least this many packages are actually touched - without a floor, a repo
|
|
223
|
+
* with only 1 or 2 packages would have *every* normal commit look "broad" (100% > 50% of a
|
|
224
|
+
* 1-package repo is trivially true), wrongly attributing ordinary changes to root alone. */
|
|
225
|
+
const BROAD_COMMIT_MIN_PACKAGES = 3;
|
|
226
|
+
/**
|
|
227
|
+
* A commit can touch more than one package at once (or a package plus root-level files) - it's
|
|
228
|
+
* attributed to every package its files map to, not just one. The exception is a commit broad
|
|
229
|
+
* enough to touch at least `BROAD_COMMIT_MIN_PACKAGES` packages *and* more than
|
|
230
|
+
* `BROAD_COMMIT_THRESHOLD` of all of them: that's a repo-wide maintenance change (relicensing, a
|
|
231
|
+
* doc pass across every package, ...), not something that belongs in each package's own release
|
|
232
|
+
* notes individually - it's attributed to the root alone.
|
|
233
|
+
*/
|
|
234
|
+
function ownersOf(repository, commit) {
|
|
235
|
+
const owners = new Set();
|
|
236
|
+
for (const f of commit.files)
|
|
237
|
+
owners.add(owningPackage(repository, f));
|
|
238
|
+
const totalPackages = repository.packages.length;
|
|
239
|
+
const nonRootOwners = [...owners].filter(p => p !== repository.rootPackage).length;
|
|
240
|
+
if (totalPackages > 0 &&
|
|
241
|
+
nonRootOwners >= BROAD_COMMIT_MIN_PACKAGES &&
|
|
242
|
+
nonRootOwners > totalPackages * BROAD_COMMIT_THRESHOLD) {
|
|
243
|
+
return new Set([repository.rootPackage]);
|
|
244
|
+
}
|
|
245
|
+
return owners;
|
|
246
|
+
}
|
|
247
|
+
async function renderEntry(repository, pkg, label, grouped, git) {
|
|
248
|
+
const template = resolveTemplate(repository, pkg);
|
|
249
|
+
const version = await resolveVersion(git, pkg);
|
|
250
|
+
const content = render(template, {
|
|
251
|
+
package: label,
|
|
252
|
+
version,
|
|
253
|
+
date: new Date().toISOString().slice(0, 10),
|
|
254
|
+
commits: renderCommitsBlock(grouped),
|
|
255
|
+
features: bulletList(grouped.features),
|
|
256
|
+
fixes: bulletList(grouped.fixes),
|
|
257
|
+
other: bulletList(grouped.other),
|
|
258
|
+
});
|
|
259
|
+
return { version, content };
|
|
260
|
+
}
|
|
261
|
+
/** Prepends `content` right after the top-level "# Changelog" heading if the file already has
|
|
262
|
+
* one, otherwise creates the file (and any missing parent directory - `relFilePath` can nest one,
|
|
263
|
+
* e.g. `'docs/CHANGELOG.md'`) with one. Leaves everything already in the file untouched below it. */
|
|
264
|
+
function prependToChangelogFile(pkg, relFilePath, content) {
|
|
265
|
+
const file = path.join(pkg.dirname, relFilePath);
|
|
266
|
+
const existing = fs.existsSync(file) ? fs.readFileSync(file, 'utf-8') : '';
|
|
267
|
+
const headerMatch = /^# Changelog\r?\n+/.exec(existing);
|
|
268
|
+
const header = headerMatch ? headerMatch[0] : '# Changelog\n\n';
|
|
269
|
+
const rest = headerMatch ? existing.slice(headerMatch[0].length) : existing;
|
|
270
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
271
|
+
fs.writeFileSync(file, header + content.trimEnd() + '\n\n' + rest);
|
|
272
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Repository } from '../core/repository.js';
|
|
2
|
+
import { type LogLevel } from '../utils/logger.js';
|
|
3
|
+
import { type PackageFilterOptions } from '../utils/package-filter.js';
|
|
4
|
+
export declare namespace CiService {
|
|
5
|
+
type PackageManager = (typeof PACKAGE_MANAGERS)[number];
|
|
6
|
+
interface Options extends PackageFilterOptions {
|
|
7
|
+
packageManager?: PackageManager;
|
|
8
|
+
/** Show the live progress panel while running. Default true, same as `run`/`build`; auto-disabled
|
|
9
|
+
* when stdout isn't a TTY. Doesn't affect what's printed once done - see `run`. */
|
|
10
|
+
progress?: boolean;
|
|
11
|
+
/** Verbosity of the classic per-step log (only applies when the live panel is off). Falls back to
|
|
12
|
+
* the root's `.rmanrc logLevel`, then 'info' - see `resolveRootLogLevel`. */
|
|
13
|
+
logLevel?: LogLevel;
|
|
14
|
+
}
|
|
15
|
+
/** `.rmanrc packageManager` (root only) picks the package manager used for the final install;
|
|
16
|
+
* explicit CLI value wins over it. Defaults to 'npm'. */
|
|
17
|
+
function resolvePackageManager(repository: Repository, cliValue?: PackageManager): PackageManager;
|
|
18
|
+
/** Deletes `node_modules` and any known lockfile directly under `dirname`. Returns the names
|
|
19
|
+
* that actually existed (and were removed), so the caller can log only those. */
|
|
20
|
+
function wipe(dirname: string): Promise<string[]>;
|
|
21
|
+
/**
|
|
22
|
+
* `ci`: a from-scratch, reproducible install for CI pipelines. For every package (root
|
|
23
|
+
* included), deletes `node_modules` and any lockfile - or, if the package defines its own
|
|
24
|
+
* `"ci"` script, runs that instead. Once every package is clean, installs once at the root
|
|
25
|
+
* with the configured package manager (`npm`/`yarn`/`pnpm`/`bun`).
|
|
26
|
+
*
|
|
27
|
+
* Uses the same live progress panel as `run`/`build` (see `../utils/progress-panel.ts`) while it
|
|
28
|
+
* runs, falling back to a plain rmdir/clean/run/install log line per step when the panel is off.
|
|
29
|
+
*
|
|
30
|
+
* Unlike `run`/`build`, it does *not* end with a per-package success tally: `ci`'s packages don't
|
|
31
|
+
* have independently meaningful outcomes the way a build or test run does - wiping a package is
|
|
32
|
+
* trivial and the one step that can genuinely fail, the install, is a single operation for the
|
|
33
|
+
* whole repository. Counting "N succeeded" across packages would just be noise, so only actual
|
|
34
|
+
* failures get called out (by name, with whatever output they produced), followed by one plain
|
|
35
|
+
* completed/failed line.
|
|
36
|
+
*/
|
|
37
|
+
function reinstall(repository: Repository, options?: Options): Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
declare const PACKAGE_MANAGERS: readonly ["npm", "yarn", "pnpm", "bun"];
|
|
40
|
+
export {};
|