rman 1.3.0 → 2.0.0-beta.3
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 +65 -31
- package/builtins/platforms/node/augmentation/rmanrc.augmentation.d.ts +83 -0
- package/builtins/platforms/node/augmentation/system-info.augmentation.d.ts +26 -0
- package/builtins/platforms/node/augmentation/system-info.augmentation.js +79 -0
- package/builtins/platforms/node/commands/ci.command.d.ts +133 -0
- package/builtins/platforms/node/commands/ci.command.js +60 -0
- package/builtins/platforms/node/commands/clean.command.d.ts +185 -0
- package/builtins/platforms/node/commands/clean.command.js +74 -0
- package/builtins/platforms/node/index.d.ts +8 -0
- package/builtins/platforms/node/index.js +8 -0
- package/builtins/platforms/node/node-config.interface.d.ts +76 -0
- package/builtins/platforms/node/node-config.interface.js +7 -0
- package/builtins/platforms/node/node-manifest.provider.d.ts +68 -0
- package/builtins/platforms/node/node-manifest.provider.js +125 -0
- package/builtins/platforms/node/node.platform.d.ts +53 -0
- package/builtins/platforms/node/node.platform.js +134 -0
- package/builtins/platforms/node/services/ci.service.d.ts +47 -0
- package/builtins/platforms/node/services/ci.service.js +213 -0
- package/builtins/platforms/node/services/clean.service.d.ts +53 -0
- package/builtins/platforms/node/services/clean.service.js +237 -0
- package/builtins/platforms/node/services/publish.service.d.ts +114 -0
- package/builtins/platforms/node/services/publish.service.js +371 -0
- package/builtins/platforms/node/services/version-plan.service.d.ts +44 -0
- package/builtins/platforms/node/services/version-plan.service.js +58 -0
- package/builtins/platforms/node/utils/workspace-range.d.ts +26 -0
- package/builtins/platforms/node/utils/workspace-range.js +28 -0
- package/builtins/presets/index.d.ts +15 -0
- package/builtins/presets/index.js +57 -0
- package/builtins/presets/node.d.ts +16 -0
- package/builtins/presets/node.js +57 -0
- package/{services → builtins/publish-targets/docker}/docker-publish.service.d.ts +52 -31
- package/{services → builtins/publish-targets/docker}/docker-publish.service.js +49 -26
- package/builtins/publish-targets/docker/docker.target.d.ts +54 -0
- package/builtins/publish-targets/docker/docker.target.js +40 -0
- package/builtins/publish-targets/docker/index.d.ts +2 -0
- package/builtins/publish-targets/docker/index.js +2 -0
- package/builtins/publish-targets/npm/index.d.ts +2 -0
- package/builtins/publish-targets/npm/index.js +2 -0
- package/builtins/publish-targets/npm/npm-publish-target.d.ts +73 -0
- package/builtins/publish-targets/npm/npm-publish-target.js +95 -0
- package/builtins/publish-targets/npm/npm-view.d.ts +48 -0
- package/builtins/publish-targets/npm/npm-view.js +71 -0
- package/cli.d.ts +8 -0
- package/cli.js +228 -93
- package/commands/build.command.d.ts +177 -3
- package/commands/build.command.js +20 -10
- package/commands/changelog.command.d.ts +296 -3
- package/commands/changelog.command.js +127 -43
- package/commands/config.command.d.ts +44 -3
- package/commands/config.command.js +53 -24
- package/commands/diff.command.d.ts +37 -3
- package/commands/diff.command.js +25 -16
- package/commands/exec.command.d.ts +193 -3
- package/commands/exec.command.js +60 -58
- package/commands/github-release.command.d.ts +154 -3
- package/commands/github-release.command.js +67 -37
- package/commands/import.command.d.ts +36 -3
- package/commands/import.command.js +28 -20
- package/commands/info.command.d.ts +35 -7
- package/commands/info.command.js +36 -30
- package/commands/lint.command.d.ts +176 -0
- package/commands/lint.command.js +34 -0
- package/commands/list.command.d.ts +163 -3
- package/commands/list.command.js +109 -71
- package/commands/publish.command.d.ts +231 -0
- package/commands/publish.command.js +304 -0
- package/commands/run.command.d.ts +186 -6
- package/commands/run.command.js +26 -72
- package/commands/test.command.d.ts +173 -3
- package/commands/test.command.js +16 -10
- package/commands/version.command.d.ts +330 -3
- package/commands/version.command.js +292 -78
- package/commands.d.ts +32 -0
- package/commands.js +28 -0
- package/constants.js +1 -1
- package/core/application.d.ts +116 -0
- package/core/application.js +160 -0
- package/core/classes/package.d.ts +219 -0
- package/core/classes/package.js +214 -0
- package/core/classes/registry.d.ts +30 -0
- package/core/classes/registry.js +47 -0
- package/core/{repository.d.ts → classes/repository.d.ts} +69 -20
- package/core/{repository.js → classes/repository.js} +195 -75
- package/core/classes/service.d.ts +49 -0
- package/core/classes/service.js +40 -0
- package/core/{version-scheme.d.ts → classes/version-scheme.d.ts} +23 -1
- package/core/{version-scheme.js → classes/version-scheme.js} +29 -1
- package/core/classes/workspace.d.ts +275 -0
- package/core/classes/workspace.js +537 -0
- package/core/command-builder.d.ts +14 -0
- package/core/command-builder.js +94 -0
- package/core/config/config-file-scope.d.ts +107 -0
- package/core/config/config-file-scope.js +226 -0
- package/core/config/config-interpolator.d.ts +164 -0
- package/core/config/config-interpolator.js +577 -0
- package/core/config/config-paths.d.ts +65 -0
- package/core/config/config-paths.js +78 -0
- package/core/config/config-reader.d.ts +192 -0
- package/core/config/config-reader.js +727 -0
- package/core/{merge-config.d.ts → config/merge-config.d.ts} +33 -34
- package/core/config/merge-config.js +260 -0
- package/core/{resolve-target.d.ts → config/resolve-config-target.d.ts} +1 -1
- package/core/{resolve-target.js → config/resolve-config-target.js} +1 -1
- package/core/{custom-command.d.ts → interfaces/custom-command.d.ts} +46 -10
- package/core/{custom-command.js → interfaces/custom-command.js} +44 -17
- package/core/{manifest.d.ts → interfaces/manifest.d.ts} +49 -27
- package/core/interfaces/manifest.js +132 -0
- package/core/interfaces/plugin.d.ts +174 -0
- package/core/interfaces/plugin.js +124 -0
- package/core/interfaces/publish-target.d.ts +136 -0
- package/core/interfaces/publish-target.js +43 -0
- package/core/{run-step.d.ts → interfaces/run-step.d.ts} +4 -4
- package/core/interfaces/run-step.js +1 -0
- package/core/plugin-loader.d.ts +25 -0
- package/core/plugin-loader.js +53 -0
- package/index.d.ts +122 -24
- package/index.js +66 -16
- package/interfaces/config-scope.interface.d.ts +223 -0
- package/interfaces/config-scope.interface.js +1 -0
- package/interfaces/rman-config.interface.d.ts +821 -203
- package/interfaces/rman-config.interface.js +78 -1
- package/package.json +3 -1
- package/services/change-hash.service.d.ts +42 -8
- package/services/change-hash.service.js +92 -13
- package/services/changelog.service.d.ts +91 -52
- package/services/changelog.service.js +460 -74
- package/services/config-service.d.ts +8 -0
- package/services/config-service.js +3 -0
- package/services/exec.service.d.ts +23 -12
- package/services/exec.service.js +14 -9
- package/services/github-release.service.d.ts +45 -34
- package/services/github-release.service.js +15 -12
- package/services/import.service.d.ts +25 -14
- package/services/import.service.js +9 -5
- package/services/list.service.d.ts +67 -13
- package/services/list.service.js +62 -15
- package/services/run.service.d.ts +35 -22
- package/services/run.service.js +316 -233
- package/services/system-info.d.ts +1 -1
- package/services/version-plan.service.d.ts +35 -8
- package/services/version-plan.service.js +96 -36
- package/services/version.service.d.ts +34 -14
- package/services/version.service.js +35 -16
- package/services.d.ts +1 -1
- package/services.js +1 -1
- package/utils/bin-path.d.ts +6 -7
- package/utils/bin-path.js +7 -18
- package/utils/branch-guard.d.ts +30 -1
- package/utils/branch-guard.js +31 -0
- package/utils/color-yaml.d.ts +8 -0
- package/utils/color-yaml.js +86 -0
- package/utils/exec.d.ts +10 -0
- package/utils/exec.js +1 -1
- package/utils/git.d.ts +26 -0
- package/utils/git.js +65 -0
- package/utils/logger.d.ts +2 -2
- package/utils/logger.js +1 -1
- package/utils/package-filter.d.ts +135 -8
- package/utils/package-filter.js +204 -16
- package/utils/printable-config.d.ts +9 -1
- package/utils/printable-config.js +46 -1
- package/utils/progress-panel.d.ts +9 -0
- package/utils/progress-panel.js +23 -1
- package/utils/release-version.d.ts +1 -1
- package/utils/run-bin.d.ts +10 -0
- package/utils/run-bin.js +1 -1
- package/utils/run-options.d.ts +97 -0
- package/utils/run-options.js +81 -0
- package/utils/version-group.d.ts +31 -0
- package/utils/version-group.js +43 -0
- package/utils/version-stamp.d.ts +1 -1
- package/utils/version-stamp.js +1 -1
- package/commands/changed.command.d.ts +0 -3
- package/commands/changed.command.js +0 -29
- package/core/config.d.ts +0 -409
- package/core/config.js +0 -939
- package/core/extends-config.d.ts +0 -27
- package/core/extends-config.js +0 -89
- package/core/manifest.js +0 -150
- package/core/merge-config.js +0 -219
- package/core/package.d.ts +0 -83
- package/core/package.js +0 -106
- package/core/plugin.d.ts +0 -112
- package/core/plugin.js +0 -189
- package/core/workspace.d.ts +0 -68
- package/core/workspace.js +0 -83
- /package/{core/run-step.js → builtins/platforms/node/augmentation/rmanrc.augmentation.js} +0 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import parseNpmScript from '@netlify/parse-npm-script';
|
|
4
|
+
import glob from 'fast-glob';
|
|
5
|
+
import { NodeManifestProvider } from './node-manifest.provider.js';
|
|
6
|
+
import { NodeVersionPlanService } from './services/version-plan.service.js';
|
|
7
|
+
/** Stands in for a script the package does not declare. `#` is a shell comment, so were it ever to
|
|
8
|
+
* reach a shell it would do nothing - but it is filtered out above instead. */
|
|
9
|
+
const PLACEHOLDER = '#';
|
|
10
|
+
export class NodePlatform {
|
|
11
|
+
/** The ecosystem, not the file - this is what every package it reads reports as
|
|
12
|
+
* `pkg.provider === 'node'`. `manifestProvider.fileName` already says `package.json`. */
|
|
13
|
+
name = 'node';
|
|
14
|
+
manifestProvider = new NodeManifestProvider();
|
|
15
|
+
versionPlanner = new NodeVersionPlanService();
|
|
16
|
+
/**
|
|
17
|
+
* npm's `workspaces` globs, resolved against `dir` - the directories directly below it that hold
|
|
18
|
+
* a `package.json`.
|
|
19
|
+
*
|
|
20
|
+
* **Asked of every directory the walk reaches, not only of the repository root**, which is what
|
|
21
|
+
* the seam changing from a layout to a per-directory question bought: a `package.json` naming
|
|
22
|
+
* `workspaces` is a workspace root wherever it sits, so a Node sub-workspace inside a larger
|
|
23
|
+
* repository is now found by the same code that finds the top one. `deep: 0` already made the
|
|
24
|
+
* globs one level, so nothing here had to change to make that true.
|
|
25
|
+
*/
|
|
26
|
+
getWorkspace(dir) {
|
|
27
|
+
const manifest = path.join(dir, 'package.json');
|
|
28
|
+
if (!fs.existsSync(manifest))
|
|
29
|
+
return undefined;
|
|
30
|
+
let patterns;
|
|
31
|
+
try {
|
|
32
|
+
patterns = JSON.parse(fs.readFileSync(manifest, 'utf-8'))?.workspaces;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
/** A malformed `package.json` is not this provider's error to report - `Package` will do it
|
|
36
|
+
* with the file in hand when something actually reads the manifest. */
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
if (!Array.isArray(patterns))
|
|
40
|
+
return undefined;
|
|
41
|
+
const packageDirs = [];
|
|
42
|
+
for (const pattern of patterns) {
|
|
43
|
+
if (typeof pattern !== 'string')
|
|
44
|
+
continue;
|
|
45
|
+
const dirs = glob.sync(pattern, { cwd: dir, absolute: true, deep: 0, onlyDirectories: true });
|
|
46
|
+
for (const d of dirs) {
|
|
47
|
+
if (fs.existsSync(path.join(d, 'package.json')))
|
|
48
|
+
packageDirs.push(d);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return packageDirs;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Teaches `run` about `package.json#scripts`.
|
|
55
|
+
*
|
|
56
|
+
* rman's core resolves a script from `.rmanrc` alone - `before`/`exec`/`after`. That a script might
|
|
57
|
+
* *also* live in `package.json`, that `prebuild` and `postbuild` run around `build`, and that
|
|
58
|
+
* `a && b` is two steps rather than one, are all facts about npm, so they are here.
|
|
59
|
+
*
|
|
60
|
+
* Registered as a `RunService` step source rather than by wrapping anything: the npm part of `run`
|
|
61
|
+
* is inside step resolution, not at its entry point, so there is nothing a wrapper could reach.
|
|
62
|
+
* The core decides what to do with what this returns - see `getScriptSteps` for the precedence.
|
|
63
|
+
*/
|
|
64
|
+
getRunSteps(pkg, script) {
|
|
65
|
+
const scripts = pkg.manifest.raw?.scripts;
|
|
66
|
+
if (!scripts || typeof scripts !== 'object')
|
|
67
|
+
return undefined;
|
|
68
|
+
const declared = (name) => typeof scripts[name] === 'string' && !!scripts[name];
|
|
69
|
+
if (!declared(script) && !declared('pre' + script) && !declared('post' + script))
|
|
70
|
+
return undefined;
|
|
71
|
+
/**
|
|
72
|
+
* `parseNpmScript` expands npm's own lifecycle - `pre<script>`, the script, `post<script>` - and
|
|
73
|
+
* splits each on `&&` into the commands npm would run in sequence. The placeholder matters: it
|
|
74
|
+
* asks for a script the package may not declare itself (it may have only a `prebuild`), and
|
|
75
|
+
* without something there the parser reports the whole chain as absent.
|
|
76
|
+
*/
|
|
77
|
+
const json = { ...pkg.manifest.raw, scripts: { ...scripts } };
|
|
78
|
+
json.scripts[script] = json.scripts[script] || PLACEHOLDER;
|
|
79
|
+
const info = parseNpmScript(json, 'npm run ' + script);
|
|
80
|
+
if (!info?.raw?.length)
|
|
81
|
+
return undefined;
|
|
82
|
+
const slots = { before: [], exec: [], after: [] };
|
|
83
|
+
for (const step of info.steps) {
|
|
84
|
+
const slot = slotOf(step.name, script);
|
|
85
|
+
if (!slot)
|
|
86
|
+
continue;
|
|
87
|
+
for (const command of Array.isArray(step.parsed) ? step.parsed : [step.parsed]) {
|
|
88
|
+
if (command === PLACEHOLDER)
|
|
89
|
+
continue;
|
|
90
|
+
slots[slot].push(command);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return slots;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Where npm puts a repository's locally installed executables: `node_modules/.bin`, at **every**
|
|
97
|
+
* level from `cwd` up to the filesystem root - which is how npm itself resolves a binary, so a
|
|
98
|
+
* package's `eslint` is found whether it was installed in that package or hoisted to the workspace
|
|
99
|
+
* root.
|
|
100
|
+
*
|
|
101
|
+
* Adapted from [npm-run-path](https://github.com/sindresorhus/npm-run-path), and it used to sit in
|
|
102
|
+
* rman's core. It is npm's directory layout from end to end: a Cargo or Go repository has no
|
|
103
|
+
* `node_modules` to walk, and nothing here would ever fire for it.
|
|
104
|
+
*
|
|
105
|
+
* **The running `node`'s own directory goes last**, after the walk, and its position is
|
|
106
|
+
* load-bearing. It is there so a script calling `node` gets the interpreter rman itself runs on
|
|
107
|
+
* rather than whatever the shell would pick. It also puts rman's own bin directory ahead of the
|
|
108
|
+
* inherited PATH, which is a measured trap: a nested `rman` invocation inside a `run` script
|
|
109
|
+
* resolves to the globally installed one, not to the repository's. Shim it in
|
|
110
|
+
* `<root>/node_modules/.bin` when that has to be overridden - the walk above reaches there first.
|
|
111
|
+
*/
|
|
112
|
+
getBinPaths(cwd) {
|
|
113
|
+
const result = [];
|
|
114
|
+
let previous;
|
|
115
|
+
let dir = path.resolve(cwd);
|
|
116
|
+
while (previous !== dir) {
|
|
117
|
+
result.push(path.join(dir, 'node_modules/.bin'));
|
|
118
|
+
previous = dir;
|
|
119
|
+
dir = path.resolve(dir, '..');
|
|
120
|
+
}
|
|
121
|
+
result.push(path.resolve(cwd, process.execPath, '..'));
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/** npm's lifecycle names, mapped onto rman's three slots. */
|
|
126
|
+
function slotOf(stepName, script) {
|
|
127
|
+
if (stepName === 'pre' + script)
|
|
128
|
+
return 'before';
|
|
129
|
+
if (stepName === script)
|
|
130
|
+
return 'exec';
|
|
131
|
+
if (stepName === 'post' + script)
|
|
132
|
+
return 'after';
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Repository } from '../../../../core/classes/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
|
+
/**
|
|
6
|
+
* The package managers this plugin knows how to drive, and the `choices` both `ci --package-manager`
|
|
7
|
+
* and the `npm` publish target's own flag offer.
|
|
8
|
+
*
|
|
9
|
+
* Exported rather than module-private, which it was: the publish target needs the same list, and
|
|
10
|
+
* a second copy of it is the kind of drift that shows up as a flag accepting a value nothing can
|
|
11
|
+
* act on. One list, one place it is written down.
|
|
12
|
+
*/
|
|
13
|
+
const PACKAGE_MANAGERS: readonly ["npm", "yarn", "pnpm", "bun"];
|
|
14
|
+
type PackageManager = (typeof PACKAGE_MANAGERS)[number];
|
|
15
|
+
interface Options extends PackageFilterOptions {
|
|
16
|
+
packageManager?: PackageManager;
|
|
17
|
+
/** Show the live progress panel while running. Default true, same as `run`/`build`; auto-disabled
|
|
18
|
+
* when stdout isn't a TTY. Doesn't affect what's printed once done - see `run`. */
|
|
19
|
+
progress?: boolean;
|
|
20
|
+
/** Verbosity of the classic per-step log (only applies when the live panel is off). Falls back to
|
|
21
|
+
* the root's `.rmanrc logLevel`, then 'info' - see `resolveRootLogLevel`. */
|
|
22
|
+
logLevel?: LogLevel;
|
|
23
|
+
}
|
|
24
|
+
/** `.rmanrc packageManager` (root only) picks the package manager used for the final install;
|
|
25
|
+
* explicit CLI value wins over it. Defaults to 'npm'. */
|
|
26
|
+
function resolvePackageManager(repository: Repository, cliValue?: PackageManager): PackageManager;
|
|
27
|
+
/** Deletes `node_modules` and any known lockfile directly under `dirname`. Returns the names
|
|
28
|
+
* that actually existed (and were removed), so the caller can log only those. */
|
|
29
|
+
function wipe(dirname: string): Promise<string[]>;
|
|
30
|
+
/**
|
|
31
|
+
* `ci`: a from-scratch, reproducible install for CI pipelines. For every package (root
|
|
32
|
+
* included), deletes `node_modules` and any lockfile - or, if the package defines its own
|
|
33
|
+
* `"ci"` script, runs that instead. Once every package is clean, installs once at the root
|
|
34
|
+
* with the configured package manager (`npm`/`yarn`/`pnpm`/`bun`).
|
|
35
|
+
*
|
|
36
|
+
* Uses the same live progress panel as `run`/`build` (see `../utils/progress-panel.ts`) while it
|
|
37
|
+
* runs, falling back to a plain rmdir/clean/run/install log line per step when the panel is off.
|
|
38
|
+
*
|
|
39
|
+
* Unlike `run`/`build`, it does *not* end with a per-package success tally: `ci`'s packages don't
|
|
40
|
+
* have independently meaningful outcomes the way a build or test run does - wiping a package is
|
|
41
|
+
* trivial and the one step that can genuinely fail, the install, is a single operation for the
|
|
42
|
+
* whole repository. Counting "N succeeded" across packages would just be noise, so only actual
|
|
43
|
+
* failures get called out (by name, with whatever output they produced), followed by one plain
|
|
44
|
+
* completed/failed line.
|
|
45
|
+
*/
|
|
46
|
+
function reinstall(repository: Repository, options?: Options): Promise<void>;
|
|
47
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
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
|
+
/**
|
|
11
|
+
* The package managers this plugin knows how to drive, and the `choices` both `ci --package-manager`
|
|
12
|
+
* and the `npm` publish target's own flag offer.
|
|
13
|
+
*
|
|
14
|
+
* Exported rather than module-private, which it was: the publish target needs the same list, and
|
|
15
|
+
* a second copy of it is the kind of drift that shows up as a flag accepting a value nothing can
|
|
16
|
+
* act on. One list, one place it is written down.
|
|
17
|
+
*/
|
|
18
|
+
CiService.PACKAGE_MANAGERS = ['npm', 'yarn', 'pnpm', 'bun'];
|
|
19
|
+
/** `.rmanrc packageManager` (root only) picks the package manager used for the final install;
|
|
20
|
+
* explicit CLI value wins over it. Defaults to 'npm'. */
|
|
21
|
+
function resolvePackageManager(repository, cliValue) {
|
|
22
|
+
if (cliValue)
|
|
23
|
+
return cliValue;
|
|
24
|
+
const configured = repository.config?.packageManager;
|
|
25
|
+
if (configured === undefined)
|
|
26
|
+
return 'npm';
|
|
27
|
+
if (CiService.PACKAGE_MANAGERS.includes(configured))
|
|
28
|
+
return configured;
|
|
29
|
+
throw new Error(`Invalid "packageManager" in .rmanrc: "${configured}" (expected one of: ${CiService.PACKAGE_MANAGERS.join(', ')})`);
|
|
30
|
+
}
|
|
31
|
+
CiService.resolvePackageManager = resolvePackageManager;
|
|
32
|
+
/** Deletes `node_modules` and any known lockfile directly under `dirname`. Returns the names
|
|
33
|
+
* that actually existed (and were removed), so the caller can log only those. */
|
|
34
|
+
async function wipe(dirname) {
|
|
35
|
+
const removed = [];
|
|
36
|
+
for (const name of ['node_modules', ...LOCK_FILES]) {
|
|
37
|
+
const target = path.join(dirname, name);
|
|
38
|
+
const existed = await fs
|
|
39
|
+
.access(target)
|
|
40
|
+
.then(() => true)
|
|
41
|
+
.catch(() => false);
|
|
42
|
+
if (!existed)
|
|
43
|
+
continue;
|
|
44
|
+
await fs.rm(target, { recursive: true, force: true });
|
|
45
|
+
removed.push(name);
|
|
46
|
+
}
|
|
47
|
+
return removed;
|
|
48
|
+
}
|
|
49
|
+
CiService.wipe = wipe;
|
|
50
|
+
/**
|
|
51
|
+
* `ci`: a from-scratch, reproducible install for CI pipelines. For every package (root
|
|
52
|
+
* included), deletes `node_modules` and any lockfile - or, if the package defines its own
|
|
53
|
+
* `"ci"` script, runs that instead. Once every package is clean, installs once at the root
|
|
54
|
+
* with the configured package manager (`npm`/`yarn`/`pnpm`/`bun`).
|
|
55
|
+
*
|
|
56
|
+
* Uses the same live progress panel as `run`/`build` (see `../utils/progress-panel.ts`) while it
|
|
57
|
+
* runs, falling back to a plain rmdir/clean/run/install log line per step when the panel is off.
|
|
58
|
+
*
|
|
59
|
+
* Unlike `run`/`build`, it does *not* end with a per-package success tally: `ci`'s packages don't
|
|
60
|
+
* have independently meaningful outcomes the way a build or test run does - wiping a package is
|
|
61
|
+
* trivial and the one step that can genuinely fail, the install, is a single operation for the
|
|
62
|
+
* whole repository. Counting "N succeeded" across packages would just be noise, so only actual
|
|
63
|
+
* failures get called out (by name, with whatever output they produced), followed by one plain
|
|
64
|
+
* completed/failed line.
|
|
65
|
+
*/
|
|
66
|
+
async function reinstall(repository, options = {}) {
|
|
67
|
+
const packageManager = resolvePackageManager(repository, options.packageManager);
|
|
68
|
+
const logger = new Logger(options.logLevel ?? resolveRootLogLevel(repository));
|
|
69
|
+
// root is handled separately below - for a non-monorepo, getPackages() would otherwise
|
|
70
|
+
// include it a second time (it doubles as "the" package).
|
|
71
|
+
const packages = filterPackages(repository.getPackages().filter(p => p !== repository.rootPackage), options);
|
|
72
|
+
const progress = options.progress ?? true;
|
|
73
|
+
const panel = new ProgressPanel('CI', !!process.stdout.isTTY && progress);
|
|
74
|
+
const runStartedAt = Date.now();
|
|
75
|
+
panel.start();
|
|
76
|
+
const items = [];
|
|
77
|
+
let failed = false;
|
|
78
|
+
try {
|
|
79
|
+
const results = await Promise.allSettled(packages.map(pkg => {
|
|
80
|
+
const item = panel.addItem(pkg.name);
|
|
81
|
+
items.push(item);
|
|
82
|
+
return ciForPackage(pkg, item, panel.enabled, logger);
|
|
83
|
+
}));
|
|
84
|
+
if (results.some(r => r.status === 'rejected'))
|
|
85
|
+
failed = true;
|
|
86
|
+
const rootItem = panel.addItem('root');
|
|
87
|
+
items.push(rootItem);
|
|
88
|
+
try {
|
|
89
|
+
await ciForRoot(repository, rootItem, panel.enabled, packageManager, logger);
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
failed = true;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
panel.stop();
|
|
97
|
+
}
|
|
98
|
+
for (const item of items) {
|
|
99
|
+
if (item.status !== 'failed')
|
|
100
|
+
continue;
|
|
101
|
+
logger.error(colors.red.bold('X'), item.name);
|
|
102
|
+
if (item.log.length)
|
|
103
|
+
logger.error(colors.red(item.log.join('\n')));
|
|
104
|
+
}
|
|
105
|
+
const totalElapsed = formatDuration(Date.now() - runStartedAt);
|
|
106
|
+
const summary = [failed ? colors.red('ci failed') : colors.green('ci completed'), colors.gray(`(${totalElapsed})`)];
|
|
107
|
+
if (failed)
|
|
108
|
+
logger.error(...summary);
|
|
109
|
+
else
|
|
110
|
+
logger.info(...summary);
|
|
111
|
+
if (failed) {
|
|
112
|
+
const err = new Error('"ci" failed');
|
|
113
|
+
err.logged = true;
|
|
114
|
+
throw err;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
CiService.reinstall = reinstall;
|
|
118
|
+
})(CiService || (CiService = {}));
|
|
119
|
+
const LOCK_FILES = ['package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'bun.lock', 'bun.lockb'];
|
|
120
|
+
/** Runs a shell `command` in `cwd`, driving `item` for the live panel when it's on, or falling
|
|
121
|
+
* back to the plain "classic" log line `logLine` prints first - the same split `run`'s classic
|
|
122
|
+
* log uses. `logLine` is expected to go through a `Logger` (so it respects `--log-level`), not a
|
|
123
|
+
* raw `console.log`. */
|
|
124
|
+
async function runStep(app, item, panelEnabled, cwd, command, logLine) {
|
|
125
|
+
if (panelEnabled) {
|
|
126
|
+
await exec(command, {
|
|
127
|
+
app,
|
|
128
|
+
cwd,
|
|
129
|
+
stdio: 'pipe',
|
|
130
|
+
onLine: line => {
|
|
131
|
+
item.log.push(line);
|
|
132
|
+
item.lastLine = line;
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
logLine();
|
|
138
|
+
await exec(command, { cwd, app, stdio: 'inherit' });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/** Logs (or reflects onto the live panel) the outcome of a `wipe()` call - including the "nothing
|
|
142
|
+
* to remove" case, which otherwise prints nothing at all in the classic log and can look like the
|
|
143
|
+
* package was never processed (most workspace layouts hoist deps to the root's `node_modules`,
|
|
144
|
+
* so an individual package legitimately has nothing of its own to wipe most of the time). */
|
|
145
|
+
function logWipeResult(item, panelEnabled, logger, label, removed) {
|
|
146
|
+
if (panelEnabled) {
|
|
147
|
+
item.lastLine = removed.length ? `removed ${removed.join(', ')}` : 'already clean';
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (!removed.length) {
|
|
151
|
+
logger.info(colors.gray('clean'), colors.cyan(label));
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
for (const name of removed)
|
|
155
|
+
logger.info(colors.yellow('rmdir'), colors.cyan(label), name);
|
|
156
|
+
}
|
|
157
|
+
/** A package can opt out of the default wipe by defining its own `"ci"` npm script - if present,
|
|
158
|
+
* that runs instead of the wipe, same as any other rman script. */
|
|
159
|
+
async function ciForPackage(pkg, item, panelEnabled, logger) {
|
|
160
|
+
item.status = 'running';
|
|
161
|
+
item.startedAt = Date.now();
|
|
162
|
+
try {
|
|
163
|
+
const script = pkg.manifest.raw.scripts?.ci;
|
|
164
|
+
if (typeof script === 'string' && script) {
|
|
165
|
+
item.currentStep = 'ci';
|
|
166
|
+
await runStep(pkg.repository.app, item, panelEnabled, pkg.dirname, script, () => logger.info(colors.cyan('run'), colors.cyan(item.name), script));
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
item.currentStep = 'wipe';
|
|
170
|
+
const removed = await CiService.wipe(pkg.dirname);
|
|
171
|
+
logWipeResult(item, panelEnabled, logger, item.name, removed);
|
|
172
|
+
}
|
|
173
|
+
item.status = 'success';
|
|
174
|
+
}
|
|
175
|
+
catch (e) {
|
|
176
|
+
item.status = 'failed';
|
|
177
|
+
throw e;
|
|
178
|
+
}
|
|
179
|
+
finally {
|
|
180
|
+
item.finishedAt = Date.now();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/** The root gets one extra step over a regular package once it isn't opting out with its own
|
|
184
|
+
* `"ci"` script: after its own wipe, it installs once for the whole repository. */
|
|
185
|
+
async function ciForRoot(repository, item, panelEnabled, packageManager, logger) {
|
|
186
|
+
item.status = 'running';
|
|
187
|
+
item.startedAt = Date.now();
|
|
188
|
+
try {
|
|
189
|
+
const script = repository.rootPackage.manifest.raw.scripts?.ci;
|
|
190
|
+
if (typeof script === 'string' && script) {
|
|
191
|
+
item.currentStep = 'ci';
|
|
192
|
+
await runStep(repository.app, item, panelEnabled, repository.dirname, script, () => logger.info(colors.cyan('run'), colors.cyan('root'), script));
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
item.stepsTotal = 2;
|
|
196
|
+
item.stepIndex = 0;
|
|
197
|
+
item.currentStep = 'wipe';
|
|
198
|
+
const removed = await CiService.wipe(repository.dirname);
|
|
199
|
+
logWipeResult(item, panelEnabled, logger, 'root', removed);
|
|
200
|
+
item.stepIndex = 1;
|
|
201
|
+
item.currentStep = 'install';
|
|
202
|
+
await runStep(repository.app, item, panelEnabled, repository.dirname, `${packageManager} install`, () => logger.info(colors.cyan('install'), `Running "${packageManager} install"`));
|
|
203
|
+
}
|
|
204
|
+
item.status = 'success';
|
|
205
|
+
}
|
|
206
|
+
catch (e) {
|
|
207
|
+
item.status = 'failed';
|
|
208
|
+
throw e;
|
|
209
|
+
}
|
|
210
|
+
finally {
|
|
211
|
+
item.finishedAt = Date.now();
|
|
212
|
+
}
|
|
213
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { Repository } from '../../../../core/classes/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
|
+
fromRoot?: 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 replacement for
|
|
20
|
+
* `ts-cleanup`, plus whatever else `.rmanrc clean.include`/`clean.exclude` says to remove.
|
|
21
|
+
*
|
|
22
|
+
* **The `node` built-in's rather than always present, because what it knows how to delete is not
|
|
23
|
+
* repository-shaped knowledge but TypeScript-shaped**: a compiled `.js`/`.js.map`/`.d.ts` beside
|
|
24
|
+
* its `.ts` source, a `*.tsbuildinfo`, and a `node_modules` to skip while looking. A Cargo or Go
|
|
25
|
+
* repository has `cargo clean` and `go clean` and nothing here would fire for it, so a core
|
|
26
|
+
* `clean` was a command that only appeared to be general.
|
|
27
|
+
*
|
|
28
|
+
* The `clean.include`/`clean.exclude` globs move with it, and that is the one cost worth naming:
|
|
29
|
+
* they are ecosystem-neutral, so a repository wanting only those now has to name this plugin (or
|
|
30
|
+
* write the two `rm` lines as a `run` script). Keeping a stub `clean` in core for them would mean
|
|
31
|
+
* two commands with one name and a precedence rule between them - worse than the honest move.
|
|
32
|
+
*
|
|
33
|
+
* For every package not opted out via its own (cascaded) `clean.skip: true`:
|
|
34
|
+
* - deletes compiled `.js`/`.js.map`/`.d.ts` files under its `src`/`test` (see `cleanTsArtifacts`);
|
|
35
|
+
* - deletes any `*.tsbuildinfo` incremental-build cache file anywhere in it;
|
|
36
|
+
* - deletes anything matching its own (cascaded) `clean.include` glob(s), minus `clean.exclude`.
|
|
37
|
+
*
|
|
38
|
+
* `include`/`exclude` are resolved relative to *that* package's own directory - a root-level
|
|
39
|
+
* `.rmanrc` pattern like `packages/*\/build` is naturally evaluated from the repository root
|
|
40
|
+
* (spanning every package in one pass), while a package's own override in its own `.rmanrc`
|
|
41
|
+
* (e.g. `include: ./cache`) only ever reaches that one package, since a package normally
|
|
42
|
+
* overrides rather than merges with the root's value for the same key.
|
|
43
|
+
*
|
|
44
|
+
* Never touches `node_modules` - that's `ci`'s job, not this one.
|
|
45
|
+
*
|
|
46
|
+
* Run from inside a single package's own directory, it only cleans that package unless
|
|
47
|
+
* `options.fromRoot` says otherwise (see `Repository.currentPackage`).
|
|
48
|
+
*
|
|
49
|
+
* Uses the same live progress panel as `run`/`build` (see `../utils/progress-panel.ts`) - unlike
|
|
50
|
+
* `ci`, cleaning genuinely is independent per-package work, so the final per-package tally stays.
|
|
51
|
+
*/
|
|
52
|
+
function clean(repository: Repository, options?: Options): Promise<void>;
|
|
53
|
+
}
|