rman 1.2.5 → 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.
Files changed (153) hide show
  1. package/README.md +63 -19
  2. package/cli.d.ts +5 -0
  3. package/cli.js +214 -88
  4. package/commands/build.command.d.ts +177 -3
  5. package/commands/build.command.js +20 -10
  6. package/commands/changed.command.d.ts +80 -3
  7. package/commands/changed.command.js +19 -12
  8. package/commands/changelog.command.d.ts +192 -3
  9. package/commands/changelog.command.js +87 -43
  10. package/commands/config.command.d.ts +44 -3
  11. package/commands/config.command.js +30 -19
  12. package/commands/diff.command.d.ts +37 -3
  13. package/commands/diff.command.js +25 -16
  14. package/commands/exec.command.d.ts +193 -3
  15. package/commands/exec.command.js +60 -58
  16. package/commands/github-release.command.d.ts +154 -3
  17. package/commands/github-release.command.js +67 -37
  18. package/commands/import.command.d.ts +36 -3
  19. package/commands/import.command.js +28 -20
  20. package/commands/info.command.d.ts +35 -7
  21. package/commands/info.command.js +36 -30
  22. package/commands/list.command.d.ts +163 -3
  23. package/commands/list.command.js +109 -71
  24. package/commands/publish.command.d.ts +231 -0
  25. package/commands/publish.command.js +304 -0
  26. package/commands/run.command.d.ts +186 -6
  27. package/commands/run.command.js +26 -72
  28. package/commands/test.command.d.ts +173 -3
  29. package/commands/test.command.js +16 -10
  30. package/commands/version.command.d.ts +317 -3
  31. package/commands/version.command.js +149 -69
  32. package/commands.d.ts +32 -0
  33. package/commands.js +28 -0
  34. package/constants.js +1 -1
  35. package/core/application.d.ts +116 -0
  36. package/core/application.js +143 -0
  37. package/core/command-builder.d.ts +14 -0
  38. package/core/command-builder.js +78 -0
  39. package/core/config.d.ts +180 -50
  40. package/core/config.js +332 -153
  41. package/core/core-services.d.ts +14 -0
  42. package/core/core-services.js +30 -0
  43. package/core/core-targets.d.ts +14 -0
  44. package/core/core-targets.js +16 -0
  45. package/core/custom-command.d.ts +42 -6
  46. package/core/custom-command.js +44 -17
  47. package/core/extends-config.d.ts +13 -5
  48. package/core/extends-config.js +52 -12
  49. package/core/load-config-module.d.ts +28 -0
  50. package/core/load-config-module.js +42 -0
  51. package/core/manifest.d.ts +47 -25
  52. package/core/manifest.js +51 -69
  53. package/core/merge-config.d.ts +33 -34
  54. package/core/merge-config.js +138 -93
  55. package/core/package.d.ts +145 -17
  56. package/core/package.js +128 -36
  57. package/core/plugin-loader.d.ts +65 -0
  58. package/core/plugin-loader.js +234 -0
  59. package/core/plugin.d.ts +135 -90
  60. package/core/plugin.js +70 -173
  61. package/core/publish-target.d.ts +124 -0
  62. package/core/publish-target.js +30 -0
  63. package/core/registry.d.ts +30 -0
  64. package/core/registry.js +47 -0
  65. package/core/repository.d.ts +72 -9
  66. package/core/repository.js +293 -43
  67. package/core/resolve-target.d.ts +1 -1
  68. package/core/resolve-target.js +1 -1
  69. package/core/service.d.ts +49 -0
  70. package/core/service.js +40 -0
  71. package/core/version-scheme.d.ts +23 -1
  72. package/core/version-scheme.js +29 -1
  73. package/core/workspace.d.ts +84 -33
  74. package/core/workspace.js +63 -22
  75. package/index.d.ts +111 -14
  76. package/index.js +85 -9
  77. package/interfaces/rman-config.interface.d.ts +739 -212
  78. package/interfaces/rman-config.interface.js +61 -1
  79. package/package.json +2 -1
  80. package/plugins/builtins.d.ts +44 -0
  81. package/plugins/builtins.js +33 -0
  82. package/plugins/detect.d.ts +78 -0
  83. package/plugins/detect.js +70 -0
  84. package/plugins/node/augmentation/rman.augmentation.d.ts +84 -0
  85. package/plugins/node/augmentation/rman.augmentation.js +1 -0
  86. package/plugins/node/augmentation/system-info.augmentation.d.ts +26 -0
  87. package/plugins/node/augmentation/system-info.augmentation.js +79 -0
  88. package/plugins/node/commands/ci.command.d.ts +131 -0
  89. package/plugins/node/commands/ci.command.js +59 -0
  90. package/plugins/node/commands/clean.command.d.ts +183 -0
  91. package/plugins/node/commands/clean.command.js +73 -0
  92. package/plugins/node/index.d.ts +29 -0
  93. package/plugins/node/index.js +40 -0
  94. package/plugins/node/node-config.interface.d.ts +77 -0
  95. package/plugins/node/node-config.interface.js +7 -0
  96. package/plugins/node/node-manifest.provider.d.ts +68 -0
  97. package/plugins/node/node-manifest.provider.js +125 -0
  98. package/plugins/node/node.platform.d.ts +53 -0
  99. package/plugins/node/node.platform.js +134 -0
  100. package/plugins/node/npm-publish-target.d.ts +73 -0
  101. package/plugins/node/npm-publish-target.js +96 -0
  102. package/plugins/node/services/ci.service.d.ts +47 -0
  103. package/plugins/node/services/ci.service.js +213 -0
  104. package/plugins/node/services/clean.service.d.ts +53 -0
  105. package/plugins/node/services/clean.service.js +237 -0
  106. package/plugins/node/services/publish.service.d.ts +114 -0
  107. package/plugins/node/services/publish.service.js +371 -0
  108. package/plugins/node/services/version-plan.service.d.ts +44 -0
  109. package/plugins/node/services/version-plan.service.js +58 -0
  110. package/plugins/node/utils/npm-view.d.ts +48 -0
  111. package/plugins/node/utils/npm-view.js +71 -0
  112. package/plugins/node/utils/workspace-range.d.ts +26 -0
  113. package/plugins/node/utils/workspace-range.js +28 -0
  114. package/services/change-hash.service.d.ts +2 -2
  115. package/services/change-hash.service.js +2 -2
  116. package/services/changelog.service.d.ts +62 -51
  117. package/services/changelog.service.js +14 -11
  118. package/services/docker-publish.service.d.ts +50 -29
  119. package/services/docker-publish.service.js +43 -20
  120. package/services/exec.service.d.ts +23 -12
  121. package/services/exec.service.js +14 -9
  122. package/services/github-release.service.d.ts +44 -33
  123. package/services/github-release.service.js +13 -10
  124. package/services/import.service.d.ts +25 -14
  125. package/services/import.service.js +9 -5
  126. package/services/list.service.d.ts +62 -10
  127. package/services/list.service.js +62 -15
  128. package/services/run.service.d.ts +22 -13
  129. package/services/run.service.js +262 -223
  130. package/services/version-plan.service.d.ts +27 -4
  131. package/services/version-plan.service.js +42 -15
  132. package/services/version.service.d.ts +31 -11
  133. package/services/version.service.js +29 -13
  134. package/targets/docker.target.d.ts +53 -0
  135. package/targets/docker.target.js +40 -0
  136. package/utils/bin-path.d.ts +6 -7
  137. package/utils/bin-path.js +7 -18
  138. package/utils/branch-guard.d.ts +29 -0
  139. package/utils/branch-guard.js +31 -0
  140. package/utils/exec.d.ts +10 -0
  141. package/utils/exec.js +1 -1
  142. package/utils/logger.d.ts +1 -1
  143. package/utils/logger.js +1 -1
  144. package/utils/package-filter.d.ts +127 -7
  145. package/utils/package-filter.js +197 -16
  146. package/utils/printable-config.d.ts +1 -1
  147. package/utils/printable-config.js +1 -1
  148. package/utils/run-bin.d.ts +10 -0
  149. package/utils/run-bin.js +1 -1
  150. package/utils/run-options.d.ts +97 -0
  151. package/utils/run-options.js +81 -0
  152. package/utils/version-stamp.d.ts +1 -1
  153. 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
+ }