rman 0.37.4 → 1.0.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.
- package/README.md +364 -21
- package/cli.d.ts +1 -0
- package/cli.js +52 -28
- 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 +11 -8
- 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 +46 -60
- 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/bin/rman.mjs +0 -4
- package/commands/build-command.d.ts +0 -11
- package/commands/build-command.js +0 -24
- package/commands/changed-command.d.ts +0 -16
- package/commands/changed-command.js +0 -32
- package/commands/ci-command.d.ts +0 -24
- package/commands/ci-command.js +0 -68
- package/commands/execute-command.d.ts +0 -19
- package/commands/execute-command.js +0 -72
- package/commands/info-command.d.ts +0 -10
- package/commands/info-command.js +0 -55
- package/commands/list-command.d.ts +0 -38
- package/commands/list-command.js +0 -134
- package/commands/multi-task-command.d.ts +0 -22
- package/commands/multi-task-command.js +0 -65
- package/commands/publish-command.d.ts +0 -24
- package/commands/publish-command.js +0 -131
- package/commands/run-command.d.ts +0 -28
- package/commands/run-command.js +0 -131
- package/commands/version-command.d.ts +0 -25
- package/commands/version-command.js +0 -195
- package/core/command.d.ts +0 -35
- package/core/command.js +0 -116
- 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 -28
- 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,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 {};
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import colors from 'ansi-colors';
|
|
4
|
+
import { exec } from '../utils/exec.js';
|
|
5
|
+
import { Logger, resolveRootLogLevel } from '../utils/logger.js';
|
|
6
|
+
import { filterPackages } from '../utils/package-filter.js';
|
|
7
|
+
import { formatDuration, ProgressPanel } from '../utils/progress-panel.js';
|
|
8
|
+
export var CiService;
|
|
9
|
+
(function (CiService) {
|
|
10
|
+
/** `.rmanrc packageManager` (root only) picks the package manager used for the final install;
|
|
11
|
+
* explicit CLI value wins over it. Defaults to 'npm'. */
|
|
12
|
+
function resolvePackageManager(repository, cliValue) {
|
|
13
|
+
if (cliValue)
|
|
14
|
+
return cliValue;
|
|
15
|
+
const configured = repository.config?.packageManager;
|
|
16
|
+
if (configured === undefined)
|
|
17
|
+
return 'npm';
|
|
18
|
+
if (PACKAGE_MANAGERS.includes(configured))
|
|
19
|
+
return configured;
|
|
20
|
+
throw new Error(`Invalid "packageManager" in .rmanrc: "${configured}" (expected one of: ${PACKAGE_MANAGERS.join(', ')})`);
|
|
21
|
+
}
|
|
22
|
+
CiService.resolvePackageManager = resolvePackageManager;
|
|
23
|
+
/** Deletes `node_modules` and any known lockfile directly under `dirname`. Returns the names
|
|
24
|
+
* that actually existed (and were removed), so the caller can log only those. */
|
|
25
|
+
async function wipe(dirname) {
|
|
26
|
+
const removed = [];
|
|
27
|
+
for (const name of ['node_modules', ...LOCK_FILES]) {
|
|
28
|
+
const target = path.join(dirname, name);
|
|
29
|
+
const existed = await fs
|
|
30
|
+
.access(target)
|
|
31
|
+
.then(() => true)
|
|
32
|
+
.catch(() => false);
|
|
33
|
+
if (!existed)
|
|
34
|
+
continue;
|
|
35
|
+
await fs.rm(target, { recursive: true, force: true });
|
|
36
|
+
removed.push(name);
|
|
37
|
+
}
|
|
38
|
+
return removed;
|
|
39
|
+
}
|
|
40
|
+
CiService.wipe = wipe;
|
|
41
|
+
/**
|
|
42
|
+
* `ci`: a from-scratch, reproducible install for CI pipelines. For every package (root
|
|
43
|
+
* included), deletes `node_modules` and any lockfile - or, if the package defines its own
|
|
44
|
+
* `"ci"` script, runs that instead. Once every package is clean, installs once at the root
|
|
45
|
+
* with the configured package manager (`npm`/`yarn`/`pnpm`/`bun`).
|
|
46
|
+
*
|
|
47
|
+
* Uses the same live progress panel as `run`/`build` (see `../utils/progress-panel.ts`) while it
|
|
48
|
+
* runs, falling back to a plain rmdir/clean/run/install log line per step when the panel is off.
|
|
49
|
+
*
|
|
50
|
+
* Unlike `run`/`build`, it does *not* end with a per-package success tally: `ci`'s packages don't
|
|
51
|
+
* have independently meaningful outcomes the way a build or test run does - wiping a package is
|
|
52
|
+
* trivial and the one step that can genuinely fail, the install, is a single operation for the
|
|
53
|
+
* whole repository. Counting "N succeeded" across packages would just be noise, so only actual
|
|
54
|
+
* failures get called out (by name, with whatever output they produced), followed by one plain
|
|
55
|
+
* completed/failed line.
|
|
56
|
+
*/
|
|
57
|
+
async function reinstall(repository, options = {}) {
|
|
58
|
+
const packageManager = resolvePackageManager(repository, options.packageManager);
|
|
59
|
+
const logger = new Logger(options.logLevel ?? resolveRootLogLevel(repository));
|
|
60
|
+
// root is handled separately below - for a non-monorepo, getPackages() would otherwise
|
|
61
|
+
// include it a second time (it doubles as "the" package).
|
|
62
|
+
const packages = filterPackages(repository.getPackages().filter(p => p !== repository.rootPackage), options);
|
|
63
|
+
const progress = options.progress ?? true;
|
|
64
|
+
const panel = new ProgressPanel('CI', !!process.stdout.isTTY && progress);
|
|
65
|
+
const runStartedAt = Date.now();
|
|
66
|
+
panel.start();
|
|
67
|
+
const items = [];
|
|
68
|
+
let failed = false;
|
|
69
|
+
try {
|
|
70
|
+
const results = await Promise.allSettled(packages.map(pkg => {
|
|
71
|
+
const item = panel.addItem(pkg.name);
|
|
72
|
+
items.push(item);
|
|
73
|
+
return ciForPackage(pkg, item, panel.enabled, logger);
|
|
74
|
+
}));
|
|
75
|
+
if (results.some(r => r.status === 'rejected'))
|
|
76
|
+
failed = true;
|
|
77
|
+
const rootItem = panel.addItem('root');
|
|
78
|
+
items.push(rootItem);
|
|
79
|
+
try {
|
|
80
|
+
await ciForRoot(repository, rootItem, panel.enabled, packageManager, logger);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
failed = true;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
panel.stop();
|
|
88
|
+
}
|
|
89
|
+
for (const item of items) {
|
|
90
|
+
if (item.status !== 'failed')
|
|
91
|
+
continue;
|
|
92
|
+
logger.error(colors.red.bold('X'), item.name);
|
|
93
|
+
if (item.log.length)
|
|
94
|
+
logger.error(colors.red(item.log.join('\n')));
|
|
95
|
+
}
|
|
96
|
+
const totalElapsed = formatDuration(Date.now() - runStartedAt);
|
|
97
|
+
const summary = [failed ? colors.red('ci failed') : colors.green('ci completed'), colors.gray(`(${totalElapsed})`)];
|
|
98
|
+
if (failed)
|
|
99
|
+
logger.error(...summary);
|
|
100
|
+
else
|
|
101
|
+
logger.info(...summary);
|
|
102
|
+
if (failed) {
|
|
103
|
+
const err = new Error('"ci" failed');
|
|
104
|
+
err.logged = true;
|
|
105
|
+
throw err;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
CiService.reinstall = reinstall;
|
|
109
|
+
})(CiService || (CiService = {}));
|
|
110
|
+
const PACKAGE_MANAGERS = ['npm', 'yarn', 'pnpm', 'bun'];
|
|
111
|
+
const LOCK_FILES = ['package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'bun.lock', 'bun.lockb'];
|
|
112
|
+
/** Runs a shell `command` in `cwd`, driving `item` for the live panel when it's on, or falling
|
|
113
|
+
* back to the plain "classic" log line `logLine` prints first - the same split `run`'s classic
|
|
114
|
+
* log uses. `logLine` is expected to go through a `Logger` (so it respects `--log-level`), not a
|
|
115
|
+
* raw `console.log`. */
|
|
116
|
+
async function runStep(item, panelEnabled, cwd, command, logLine) {
|
|
117
|
+
if (panelEnabled) {
|
|
118
|
+
await exec(command, {
|
|
119
|
+
cwd,
|
|
120
|
+
stdio: 'pipe',
|
|
121
|
+
onLine: line => {
|
|
122
|
+
item.log.push(line);
|
|
123
|
+
item.lastLine = line;
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
logLine();
|
|
129
|
+
await exec(command, { cwd, stdio: 'inherit' });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/** Logs (or reflects onto the live panel) the outcome of a `wipe()` call - including the "nothing
|
|
133
|
+
* to remove" case, which otherwise prints nothing at all in the classic log and can look like the
|
|
134
|
+
* package was never processed (most workspace layouts hoist deps to the root's `node_modules`,
|
|
135
|
+
* so an individual package legitimately has nothing of its own to wipe most of the time). */
|
|
136
|
+
function logWipeResult(item, panelEnabled, logger, label, removed) {
|
|
137
|
+
if (panelEnabled) {
|
|
138
|
+
item.lastLine = removed.length ? `removed ${removed.join(', ')}` : 'already clean';
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (!removed.length) {
|
|
142
|
+
logger.info(colors.gray('clean'), colors.cyan(label));
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
for (const name of removed)
|
|
146
|
+
logger.info(colors.yellow('rmdir'), colors.cyan(label), name);
|
|
147
|
+
}
|
|
148
|
+
/** A package can opt out of the default wipe by defining its own `"ci"` npm script - if present,
|
|
149
|
+
* that runs instead of the wipe, same as any other rman script. */
|
|
150
|
+
async function ciForPackage(pkg, item, panelEnabled, logger) {
|
|
151
|
+
item.status = 'running';
|
|
152
|
+
item.startedAt = Date.now();
|
|
153
|
+
try {
|
|
154
|
+
const script = pkg.json.scripts?.ci;
|
|
155
|
+
if (typeof script === 'string' && script) {
|
|
156
|
+
item.currentStep = 'ci';
|
|
157
|
+
await runStep(item, panelEnabled, pkg.dirname, script, () => logger.info(colors.cyan('run'), colors.cyan(item.name), script));
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
item.currentStep = 'wipe';
|
|
161
|
+
const removed = await CiService.wipe(pkg.dirname);
|
|
162
|
+
logWipeResult(item, panelEnabled, logger, item.name, removed);
|
|
163
|
+
}
|
|
164
|
+
item.status = 'success';
|
|
165
|
+
}
|
|
166
|
+
catch (e) {
|
|
167
|
+
item.status = 'failed';
|
|
168
|
+
throw e;
|
|
169
|
+
}
|
|
170
|
+
finally {
|
|
171
|
+
item.finishedAt = Date.now();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/** The root gets one extra step over a regular package once it isn't opting out with its own
|
|
175
|
+
* `"ci"` script: after its own wipe, it installs once for the whole repository. */
|
|
176
|
+
async function ciForRoot(repository, item, panelEnabled, packageManager, logger) {
|
|
177
|
+
item.status = 'running';
|
|
178
|
+
item.startedAt = Date.now();
|
|
179
|
+
try {
|
|
180
|
+
const script = repository.rootPackage.json.scripts?.ci;
|
|
181
|
+
if (typeof script === 'string' && script) {
|
|
182
|
+
item.currentStep = 'ci';
|
|
183
|
+
await runStep(item, panelEnabled, repository.dirname, script, () => logger.info(colors.cyan('run'), colors.cyan('root'), script));
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
item.stepsTotal = 2;
|
|
187
|
+
item.stepIndex = 0;
|
|
188
|
+
item.currentStep = 'wipe';
|
|
189
|
+
const removed = await CiService.wipe(repository.dirname);
|
|
190
|
+
logWipeResult(item, panelEnabled, logger, 'root', removed);
|
|
191
|
+
item.stepIndex = 1;
|
|
192
|
+
item.currentStep = 'install';
|
|
193
|
+
await runStep(item, panelEnabled, repository.dirname, `${packageManager} install`, () => logger.info(colors.cyan('install'), `Running "${packageManager} install"`));
|
|
194
|
+
}
|
|
195
|
+
item.status = 'success';
|
|
196
|
+
}
|
|
197
|
+
catch (e) {
|
|
198
|
+
item.status = 'failed';
|
|
199
|
+
throw e;
|
|
200
|
+
}
|
|
201
|
+
finally {
|
|
202
|
+
item.finishedAt = Date.now();
|
|
203
|
+
}
|
|
204
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
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 CleanService {
|
|
5
|
+
interface Options extends PackageFilterOptions {
|
|
6
|
+
/** Show the live progress panel. Default true; auto-disabled when stdout isn't a TTY. */
|
|
7
|
+
progress?: boolean;
|
|
8
|
+
/** Report what would be removed without actually removing anything. Default false. */
|
|
9
|
+
dryRun?: boolean;
|
|
10
|
+
/** Clean the whole repository even when the current directory is inside a single package
|
|
11
|
+
* (which otherwise scopes cleaning to just that package - see `Repository.currentPackage`).
|
|
12
|
+
* Has no effect when already at the repository root, or outside any known package. */
|
|
13
|
+
root?: boolean;
|
|
14
|
+
/** Verbosity of the classic per-item log (only applies when the live panel is off). Falls back
|
|
15
|
+
* to the root's `.rmanrc logLevel`, then 'info' - see `resolveRootLogLevel`. */
|
|
16
|
+
logLevel?: LogLevel;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* `clean`: removes build output across every package (root included) - the built-in replacement
|
|
20
|
+
* for `ts-cleanup`, plus whatever else `.rmanrc clean.include`/`clean.exclude` says to remove.
|
|
21
|
+
*
|
|
22
|
+
* For every package not opted out via its own (cascaded) `clean.skip: true`:
|
|
23
|
+
* - deletes compiled `.js`/`.js.map`/`.d.ts` files under its `src`/`test` (see `cleanTsArtifacts`);
|
|
24
|
+
* - deletes any `*.tsbuildinfo` incremental-build cache file anywhere in it;
|
|
25
|
+
* - deletes anything matching its own (cascaded) `clean.include` glob(s), minus `clean.exclude`.
|
|
26
|
+
*
|
|
27
|
+
* `include`/`exclude` are resolved relative to *that* package's own directory - a root-level
|
|
28
|
+
* `.rmanrc` pattern like `packages/*\/build` is naturally evaluated from the repository root
|
|
29
|
+
* (spanning every package in one pass), while a package's own override in its own `.rmanrc`
|
|
30
|
+
* (e.g. `include: ./cache`) only ever reaches that one package, since a package normally
|
|
31
|
+
* overrides rather than merges with the root's value for the same key.
|
|
32
|
+
*
|
|
33
|
+
* Never touches `node_modules` - that's `ci`'s job, not this one.
|
|
34
|
+
*
|
|
35
|
+
* Run from inside a single package's own directory, it only cleans that package unless
|
|
36
|
+
* `options.root` says otherwise (see `Repository.currentPackage`).
|
|
37
|
+
*
|
|
38
|
+
* Uses the same live progress panel as `run`/`build` (see `../utils/progress-panel.ts`) - unlike
|
|
39
|
+
* `ci`, cleaning genuinely is independent per-package work, so the final per-package tally stays.
|
|
40
|
+
*/
|
|
41
|
+
function clean(repository: Repository, options?: Options): Promise<void>;
|
|
42
|
+
}
|