rman 1.3.0 → 2.0.0-beta.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 +63 -19
- package/cli.d.ts +5 -0
- package/cli.js +214 -88
- package/commands/build.command.d.ts +177 -3
- package/commands/build.command.js +20 -10
- package/commands/changed.command.d.ts +80 -3
- package/commands/changed.command.js +19 -12
- package/commands/changelog.command.d.ts +192 -3
- package/commands/changelog.command.js +87 -43
- package/commands/config.command.d.ts +44 -3
- package/commands/config.command.js +30 -19
- 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/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 +317 -3
- package/commands/version.command.js +149 -69
- 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 +143 -0
- package/core/command-builder.d.ts +14 -0
- package/core/command-builder.js +78 -0
- package/core/config.d.ts +140 -19
- package/core/config.js +258 -74
- package/core/core-services.d.ts +14 -0
- package/core/core-services.js +30 -0
- package/core/core-targets.d.ts +14 -0
- package/core/core-targets.js +16 -0
- package/core/custom-command.d.ts +42 -6
- package/core/custom-command.js +44 -17
- package/core/extends-config.d.ts +13 -5
- package/core/extends-config.js +52 -12
- package/core/load-config-module.d.ts +28 -0
- package/core/load-config-module.js +42 -0
- package/core/manifest.d.ts +47 -25
- package/core/manifest.js +51 -69
- package/core/merge-config.d.ts +33 -34
- package/core/merge-config.js +137 -92
- package/core/package.d.ts +145 -17
- package/core/package.js +128 -36
- package/core/plugin-loader.d.ts +65 -0
- package/core/plugin-loader.js +234 -0
- package/core/plugin.d.ts +135 -90
- package/core/plugin.js +70 -173
- package/core/publish-target.d.ts +124 -0
- package/core/publish-target.js +30 -0
- package/core/registry.d.ts +30 -0
- package/core/registry.js +47 -0
- package/core/repository.d.ts +72 -9
- package/core/repository.js +293 -44
- package/core/resolve-target.d.ts +1 -1
- package/core/resolve-target.js +1 -1
- package/core/service.d.ts +49 -0
- package/core/service.js +40 -0
- package/core/version-scheme.d.ts +23 -1
- package/core/version-scheme.js +29 -1
- package/core/workspace.d.ts +84 -33
- package/core/workspace.js +63 -22
- package/index.d.ts +111 -14
- package/index.js +85 -9
- package/interfaces/rman-config.interface.d.ts +725 -202
- package/interfaces/rman-config.interface.js +61 -1
- package/package.json +2 -1
- package/plugins/builtins.d.ts +44 -0
- package/plugins/builtins.js +33 -0
- package/plugins/detect.d.ts +78 -0
- package/plugins/detect.js +70 -0
- package/plugins/node/augmentation/rman.augmentation.d.ts +84 -0
- package/plugins/node/augmentation/rman.augmentation.js +1 -0
- package/plugins/node/augmentation/system-info.augmentation.d.ts +26 -0
- package/plugins/node/augmentation/system-info.augmentation.js +79 -0
- package/plugins/node/commands/ci.command.d.ts +131 -0
- package/plugins/node/commands/ci.command.js +59 -0
- package/plugins/node/commands/clean.command.d.ts +183 -0
- package/plugins/node/commands/clean.command.js +73 -0
- package/plugins/node/index.d.ts +29 -0
- package/plugins/node/index.js +40 -0
- package/plugins/node/node-config.interface.d.ts +77 -0
- package/plugins/node/node-config.interface.js +7 -0
- package/plugins/node/node-manifest.provider.d.ts +68 -0
- package/plugins/node/node-manifest.provider.js +125 -0
- package/plugins/node/node.platform.d.ts +53 -0
- package/plugins/node/node.platform.js +134 -0
- package/plugins/node/npm-publish-target.d.ts +73 -0
- package/plugins/node/npm-publish-target.js +96 -0
- package/plugins/node/services/ci.service.d.ts +47 -0
- package/plugins/node/services/ci.service.js +213 -0
- package/plugins/node/services/clean.service.d.ts +53 -0
- package/plugins/node/services/clean.service.js +237 -0
- package/plugins/node/services/publish.service.d.ts +114 -0
- package/plugins/node/services/publish.service.js +371 -0
- package/plugins/node/services/version-plan.service.d.ts +44 -0
- package/plugins/node/services/version-plan.service.js +58 -0
- package/plugins/node/utils/npm-view.d.ts +48 -0
- package/plugins/node/utils/npm-view.js +71 -0
- package/plugins/node/utils/workspace-range.d.ts +26 -0
- package/plugins/node/utils/workspace-range.js +28 -0
- package/services/change-hash.service.d.ts +2 -2
- package/services/change-hash.service.js +2 -2
- package/services/changelog.service.d.ts +62 -51
- package/services/changelog.service.js +14 -11
- package/services/docker-publish.service.d.ts +50 -29
- package/services/docker-publish.service.js +43 -20
- package/services/exec.service.d.ts +23 -12
- package/services/exec.service.js +14 -9
- package/services/github-release.service.d.ts +44 -33
- package/services/github-release.service.js +13 -10
- package/services/import.service.d.ts +25 -14
- package/services/import.service.js +9 -5
- package/services/list.service.d.ts +62 -10
- package/services/list.service.js +62 -15
- package/services/run.service.d.ts +22 -13
- package/services/run.service.js +262 -223
- package/services/version-plan.service.d.ts +27 -4
- package/services/version-plan.service.js +42 -15
- package/services/version.service.d.ts +31 -11
- package/services/version.service.js +29 -13
- package/targets/docker.target.d.ts +53 -0
- package/targets/docker.target.js +40 -0
- package/utils/bin-path.d.ts +6 -7
- package/utils/bin-path.js +7 -18
- package/utils/branch-guard.d.ts +29 -0
- package/utils/branch-guard.js +31 -0
- package/utils/exec.d.ts +10 -0
- package/utils/exec.js +1 -1
- package/utils/logger.d.ts +1 -1
- package/utils/logger.js +1 -1
- package/utils/package-filter.d.ts +127 -7
- package/utils/package-filter.js +197 -16
- package/utils/printable-config.d.ts +1 -1
- package/utils/printable-config.js +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-stamp.d.ts +1 -1
- package/utils/version-stamp.js +1 -1
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { Package } from '../../core/package.js';
|
|
2
|
+
import type { Platform } from '../../core/plugin.js';
|
|
3
|
+
import type { RunService } from '../../services/run.service.js';
|
|
4
|
+
import { NodeManifestProvider } from './node-manifest.provider.js';
|
|
5
|
+
import { NodeVersionPlanService } from './services/version-plan.service.js';
|
|
6
|
+
export declare class NodePlatform implements Platform {
|
|
7
|
+
/** The ecosystem, not the file - this is what every package it reads reports as
|
|
8
|
+
* `pkg.provider === 'node'`. `manifestProvider.fileName` already says `package.json`. */
|
|
9
|
+
name: string;
|
|
10
|
+
manifestProvider: NodeManifestProvider;
|
|
11
|
+
versionPlanner: NodeVersionPlanService;
|
|
12
|
+
/**
|
|
13
|
+
* npm's `workspaces` globs, resolved against `dir` - the directories directly below it that hold
|
|
14
|
+
* a `package.json`.
|
|
15
|
+
*
|
|
16
|
+
* **Asked of every directory the walk reaches, not only of the repository root**, which is what
|
|
17
|
+
* the seam changing from a layout to a per-directory question bought: a `package.json` naming
|
|
18
|
+
* `workspaces` is a workspace root wherever it sits, so a Node sub-workspace inside a larger
|
|
19
|
+
* repository is now found by the same code that finds the top one. `deep: 0` already made the
|
|
20
|
+
* globs one level, so nothing here had to change to make that true.
|
|
21
|
+
*/
|
|
22
|
+
getWorkspace(dir: string): string[] | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* Teaches `run` about `package.json#scripts`.
|
|
25
|
+
*
|
|
26
|
+
* rman's core resolves a script from `.rmanrc` alone - `before`/`exec`/`after`. That a script might
|
|
27
|
+
* *also* live in `package.json`, that `prebuild` and `postbuild` run around `build`, and that
|
|
28
|
+
* `a && b` is two steps rather than one, are all facts about npm, so they are here.
|
|
29
|
+
*
|
|
30
|
+
* Registered as a `RunService` step source rather than by wrapping anything: the npm part of `run`
|
|
31
|
+
* is inside step resolution, not at its entry point, so there is nothing a wrapper could reach.
|
|
32
|
+
* The core decides what to do with what this returns - see `getScriptSteps` for the precedence.
|
|
33
|
+
*/
|
|
34
|
+
getRunSteps(pkg: Package, script: string): RunService.ScriptSlots | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Where npm puts a repository's locally installed executables: `node_modules/.bin`, at **every**
|
|
37
|
+
* level from `cwd` up to the filesystem root - which is how npm itself resolves a binary, so a
|
|
38
|
+
* package's `eslint` is found whether it was installed in that package or hoisted to the workspace
|
|
39
|
+
* root.
|
|
40
|
+
*
|
|
41
|
+
* Adapted from [npm-run-path](https://github.com/sindresorhus/npm-run-path), and it used to sit in
|
|
42
|
+
* rman's core. It is npm's directory layout from end to end: a Cargo or Go repository has no
|
|
43
|
+
* `node_modules` to walk, and nothing here would ever fire for it.
|
|
44
|
+
*
|
|
45
|
+
* **The running `node`'s own directory goes last**, after the walk, and its position is
|
|
46
|
+
* load-bearing. It is there so a script calling `node` gets the interpreter rman itself runs on
|
|
47
|
+
* rather than whatever the shell would pick. It also puts rman's own bin directory ahead of the
|
|
48
|
+
* inherited PATH, which is a measured trap: a nested `rman` invocation inside a `run` script
|
|
49
|
+
* resolves to the globally installed one, not to the repository's. Shim it in
|
|
50
|
+
* `<root>/node_modules/.bin` when that has to be overridden - the walk above reaches there first.
|
|
51
|
+
*/
|
|
52
|
+
getBinPaths(cwd: string): string[];
|
|
53
|
+
}
|
|
@@ -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,73 @@
|
|
|
1
|
+
import type { Package } from '../../core/package.js';
|
|
2
|
+
import type { PublishTarget } from '../../core/publish-target.js';
|
|
3
|
+
import { PublishService } from './services/publish.service.js';
|
|
4
|
+
/** The name this target answers to in `publish.target` and `--target`. */
|
|
5
|
+
export declare const NPM_TARGET = "npm";
|
|
6
|
+
/**
|
|
7
|
+
* **npm, as a publish target** - what is left of the old `rman-node` `publish` command once the command
|
|
8
|
+
* itself went back to the core where it belonged.
|
|
9
|
+
*
|
|
10
|
+
* The split is worth stating, because it is the whole point of the move: everything about
|
|
11
|
+
* *publishing a repository* - which packages are candidates, dependency order, the plan/confirm/
|
|
12
|
+
* apply shape, `--dry-run`, the JSON a CI gate reads - is the core's and always was. What is npm's
|
|
13
|
+
* is this file: seven flags, one registry question, and one push. A Cargo or Maven target is the
|
|
14
|
+
* same size.
|
|
15
|
+
*
|
|
16
|
+
* **`claims` is the other half, and it fixes a real bug.** The core used to default a package with
|
|
17
|
+
* no `publish.target` to `['npm']`, which is a statement only an ecosystem can make - so a Cargo
|
|
18
|
+
* package beside a Node one was reported by `rman list --json` as shipping to npm, and treated by
|
|
19
|
+
* `publish` as an npm candidate. Asking `pkg.provider === 'node'` puts the answer where the
|
|
20
|
+
* knowledge is: this plugin read that manifest, so this plugin is the one that can say so.
|
|
21
|
+
*
|
|
22
|
+
* A class, like `NodePlugin` and `NodeManifestProvider`: a config declares it as
|
|
23
|
+
* `publishTargets: [new NpmPublishTarget()]`, which is one shape for everything a plugin package
|
|
24
|
+
* contributes rather than an object literal here and a class there.
|
|
25
|
+
*/
|
|
26
|
+
export declare class NpmPublishTarget implements PublishTarget {
|
|
27
|
+
name: string;
|
|
28
|
+
describe: string;
|
|
29
|
+
claims(pkg: Package): boolean;
|
|
30
|
+
options: {
|
|
31
|
+
packageManager: {
|
|
32
|
+
target: "cli";
|
|
33
|
+
cliName: string;
|
|
34
|
+
describe: string;
|
|
35
|
+
choices: readonly ["npm", "yarn", "pnpm", "bun"];
|
|
36
|
+
};
|
|
37
|
+
access: {
|
|
38
|
+
target: "cli";
|
|
39
|
+
describe: string;
|
|
40
|
+
choices: string[];
|
|
41
|
+
};
|
|
42
|
+
tag: {
|
|
43
|
+
target: "cli";
|
|
44
|
+
describe: string;
|
|
45
|
+
type: "string";
|
|
46
|
+
};
|
|
47
|
+
otp: {
|
|
48
|
+
target: "cli";
|
|
49
|
+
describe: string;
|
|
50
|
+
type: "string";
|
|
51
|
+
};
|
|
52
|
+
registry: {
|
|
53
|
+
target: "cli";
|
|
54
|
+
describe: string;
|
|
55
|
+
type: "string";
|
|
56
|
+
};
|
|
57
|
+
userconfig: {
|
|
58
|
+
target: "cli";
|
|
59
|
+
describe: string;
|
|
60
|
+
type: "string";
|
|
61
|
+
};
|
|
62
|
+
contents: {
|
|
63
|
+
target: "cli";
|
|
64
|
+
describe: string;
|
|
65
|
+
type: "string";
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
getPlan(ctx: PublishTarget.Context): Promise<PublishService.Entry[]>;
|
|
69
|
+
applyPlan(ctx: PublishTarget.Context, plan: PublishTarget.Entry[]): Promise<PublishService.Entry[]>;
|
|
70
|
+
/** The shared filters plus the two flags both halves need - read here rather than in the
|
|
71
|
+
* service, which keeps taking a plain options object and stays callable without a CLI. */
|
|
72
|
+
protected planOptions(ctx: PublishTarget.Context): PublishService.Options;
|
|
73
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { CiService } from './services/ci.service.js';
|
|
2
|
+
import { PublishService } from './services/publish.service.js';
|
|
3
|
+
/** The name this target answers to in `publish.target` and `--target`. */
|
|
4
|
+
export const NPM_TARGET = 'npm';
|
|
5
|
+
/**
|
|
6
|
+
* **npm, as a publish target** - what is left of the old `rman-node` `publish` command once the command
|
|
7
|
+
* itself went back to the core where it belonged.
|
|
8
|
+
*
|
|
9
|
+
* The split is worth stating, because it is the whole point of the move: everything about
|
|
10
|
+
* *publishing a repository* - which packages are candidates, dependency order, the plan/confirm/
|
|
11
|
+
* apply shape, `--dry-run`, the JSON a CI gate reads - is the core's and always was. What is npm's
|
|
12
|
+
* is this file: seven flags, one registry question, and one push. A Cargo or Maven target is the
|
|
13
|
+
* same size.
|
|
14
|
+
*
|
|
15
|
+
* **`claims` is the other half, and it fixes a real bug.** The core used to default a package with
|
|
16
|
+
* no `publish.target` to `['npm']`, which is a statement only an ecosystem can make - so a Cargo
|
|
17
|
+
* package beside a Node one was reported by `rman list --json` as shipping to npm, and treated by
|
|
18
|
+
* `publish` as an npm candidate. Asking `pkg.provider === 'node'` puts the answer where the
|
|
19
|
+
* knowledge is: this plugin read that manifest, so this plugin is the one that can say so.
|
|
20
|
+
*
|
|
21
|
+
* A class, like `NodePlugin` and `NodeManifestProvider`: a config declares it as
|
|
22
|
+
* `publishTargets: [new NpmPublishTarget()]`, which is one shape for everything a plugin package
|
|
23
|
+
* contributes rather than an object literal here and a class there.
|
|
24
|
+
*/
|
|
25
|
+
export class NpmPublishTarget {
|
|
26
|
+
name = NPM_TARGET;
|
|
27
|
+
describe = 'Publish to an npm registry (npm/yarn/pnpm/bun publish)';
|
|
28
|
+
claims(pkg) {
|
|
29
|
+
return pkg.provider === 'node';
|
|
30
|
+
}
|
|
31
|
+
options = {
|
|
32
|
+
packageManager: {
|
|
33
|
+
target: 'cli',
|
|
34
|
+
cliName: 'package-manager',
|
|
35
|
+
describe: 'Package manager to publish with (default: npm, or .rmanrc "packageManager")',
|
|
36
|
+
choices: CiService.PACKAGE_MANAGERS,
|
|
37
|
+
},
|
|
38
|
+
access: {
|
|
39
|
+
target: 'cli',
|
|
40
|
+
describe: 'npm publish --access <public|restricted> - required by the registry for a new scoped package',
|
|
41
|
+
choices: ['public', 'restricted'],
|
|
42
|
+
},
|
|
43
|
+
tag: {
|
|
44
|
+
target: 'cli',
|
|
45
|
+
describe: 'npm publish --tag <tag> - overrides the dist-tag. A prerelease already publishes under its ' +
|
|
46
|
+
'own identifier (2.0.0-beta.1 -> "beta") and a release under "latest", so this is rarely needed',
|
|
47
|
+
type: 'string',
|
|
48
|
+
},
|
|
49
|
+
otp: {
|
|
50
|
+
target: 'cli',
|
|
51
|
+
describe: 'npm publish --otp <otp> - a 2FA one-time password, for registries that require it',
|
|
52
|
+
type: 'string',
|
|
53
|
+
},
|
|
54
|
+
registry: {
|
|
55
|
+
target: 'cli',
|
|
56
|
+
describe: 'Registry to check against and publish to (default: whatever .npmrc already configures)',
|
|
57
|
+
type: 'string',
|
|
58
|
+
},
|
|
59
|
+
userconfig: {
|
|
60
|
+
target: 'cli',
|
|
61
|
+
describe: 'Path to a custom .npmrc to use for both the registry check and the actual publish',
|
|
62
|
+
type: 'string',
|
|
63
|
+
},
|
|
64
|
+
contents: {
|
|
65
|
+
target: 'cli',
|
|
66
|
+
describe: "Subdirectory to publish from, relative to each package's own directory - only consulted when a " +
|
|
67
|
+
'package has no "publishConfig.directory" of its own (that always wins when present)',
|
|
68
|
+
type: 'string',
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
getPlan(ctx) {
|
|
72
|
+
return PublishService.getPlan(ctx.repository, this.planOptions(ctx));
|
|
73
|
+
}
|
|
74
|
+
applyPlan(ctx, plan) {
|
|
75
|
+
return PublishService.applyPlan(ctx.repository, plan, {
|
|
76
|
+
...this.planOptions(ctx),
|
|
77
|
+
packageManager: ctx.args.packageManager,
|
|
78
|
+
access: ctx.args.access,
|
|
79
|
+
otp: ctx.args.otp,
|
|
80
|
+
contents: ctx.args.contents,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
/** The shared filters plus the two flags both halves need - read here rather than in the
|
|
84
|
+
* service, which keeps taking a plain options object and stays callable without a CLI. */
|
|
85
|
+
planOptions(ctx) {
|
|
86
|
+
return {
|
|
87
|
+
...ctx.options,
|
|
88
|
+
registry: ctx.args.registry,
|
|
89
|
+
userconfig: ctx.args.userconfig,
|
|
90
|
+
/** Read at *plan* time too, not only where the publish command is built: the plan is what
|
|
91
|
+
* decides the dist-tag and refuses the two cases with nothing to derive, so `--dry-run` and
|
|
92
|
+
* the JSON a pipeline gates on have to see the flag. */
|
|
93
|
+
tag: ctx.args.tag,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
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
|
+
/**
|
|
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
|
+
}
|