rman-node 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +149 -0
  3. package/augmentation/manifest.augmentation.d.ts +17 -0
  4. package/augmentation/manifest.augmentation.js +129 -0
  5. package/augmentation/rman.augmentation.d.ts +45 -0
  6. package/augmentation/rman.augmentation.js +1 -0
  7. package/augmentation/run.augmentation.d.ts +15 -0
  8. package/augmentation/run.augmentation.js +61 -0
  9. package/augmentation/system-info.augmentation.d.ts +26 -0
  10. package/augmentation/system-info.augmentation.js +79 -0
  11. package/augmentation/workspace.augmentation.d.ts +18 -0
  12. package/augmentation/workspace.augmentation.js +49 -0
  13. package/commands/ci.command.d.ts +9 -0
  14. package/commands/ci.command.js +35 -0
  15. package/commands/clean.command.d.ts +6 -0
  16. package/commands/clean.command.js +32 -0
  17. package/commands/publish.command.d.ts +9 -0
  18. package/commands/publish.command.js +228 -0
  19. package/index.d.ts +31 -0
  20. package/index.js +100 -0
  21. package/interfaces/rman-config.interface.d.ts +80 -0
  22. package/interfaces/rman-config.interface.js +7 -0
  23. package/package.json +54 -0
  24. package/services/ci.service.d.ts +38 -0
  25. package/services/ci.service.js +201 -0
  26. package/services/clean.service.d.ts +51 -0
  27. package/services/clean.service.js +235 -0
  28. package/services/publish.service.d.ts +77 -0
  29. package/services/publish.service.js +258 -0
  30. package/services/version-plan.service.d.ts +50 -0
  31. package/services/version-plan.service.js +67 -0
  32. package/utils/npm-run-path.d.ts +21 -0
  33. package/utils/npm-run-path.js +36 -0
  34. package/utils/npm-view.d.ts +15 -0
  35. package/utils/npm-view.js +28 -0
  36. package/utils/workspace-range.d.ts +26 -0
  37. package/utils/workspace-range.js +28 -0
@@ -0,0 +1,35 @@
1
+ import { applyBranchGuardOptions, applyPackageFilterOptions, assertAllowedBranch, readBranchGuardOptions, readPackageFilterOptions, } from 'rman';
2
+ import { CiService } from '../services/ci.service.js';
3
+ /**
4
+ * `rman ci` - contributed by the `rman-node` plugin rather than built into rman.
5
+ *
6
+ * A plugin's command is a `CustomCommand`, so the repository arrives through `context` instead of
7
+ * being captured when the command is registered - the same shape a repository's own `.rman/*.mjs`
8
+ * command has, and the reason both can be registered through one code path.
9
+ */
10
+ export const command = {
11
+ command: 'ci',
12
+ configKeys: ['packageManager'],
13
+ describe: 'Deletes node_modules and lockfiles in every package, then reinstalls from scratch',
14
+ builder: cmd => applyBranchGuardOptions(applyPackageFilterOptions(cmd))
15
+ .example('$0 ci', '')
16
+ .option('package-manager', {
17
+ describe: 'Package manager to install with (default: npm, or .rmanrc "packageManager")',
18
+ choices: PACKAGE_MANAGERS,
19
+ })
20
+ .option('progress', {
21
+ describe: 'Show a live progress panel while running (default: true; auto-disabled when not a TTY). ' +
22
+ 'Unlike run/build, completion is not reported as a per-package tally - only failures are called out.',
23
+ type: 'boolean',
24
+ }),
25
+ handler: async ({ repository }, args) => {
26
+ await assertAllowedBranch(repository, readBranchGuardOptions(args));
27
+ await CiService.reinstall(repository, {
28
+ ...readPackageFilterOptions(args),
29
+ packageManager: args.packageManager,
30
+ progress: args.progress,
31
+ logLevel: args.logLevel,
32
+ });
33
+ },
34
+ };
35
+ const PACKAGE_MANAGERS = ['npm', 'yarn', 'pnpm', 'bun'];
@@ -0,0 +1,6 @@
1
+ import { type CustomCommand } from 'rman';
2
+ /**
3
+ * `rman clean` - contributed by the `rman-node` plugin rather than built into rman, because what
4
+ * it deletes is TypeScript's output. See `CleanService.clean`.
5
+ */
6
+ export declare const command: CustomCommand;
@@ -0,0 +1,32 @@
1
+ import { applyBranchGuardOptions, applyPackageFilterOptions, applyRootOption, assertAllowedBranch, readBranchGuardOptions, readPackageFilterOptions, } from 'rman';
2
+ import { CleanService } from '../services/clean.service.js';
3
+ /**
4
+ * `rman clean` - contributed by the `rman-node` plugin rather than built into rman, because what
5
+ * it deletes is TypeScript's output. See `CleanService.clean`.
6
+ */
7
+ export const command = {
8
+ command: 'clean',
9
+ configKeys: ['clean'],
10
+ describe: 'Removes compiled TypeScript output and any extra files/dirs configured via .rmanrc "clean"',
11
+ builder: cmd => applyRootOption(applyBranchGuardOptions(applyPackageFilterOptions(cmd)), 'Clean')
12
+ .example('$0 clean', '')
13
+ .example('$0 clean --dry-run', '# Preview what would be removed')
14
+ .option('progress', {
15
+ describe: 'Show a live progress panel (default: true; auto-disabled when not a TTY)',
16
+ type: 'boolean',
17
+ })
18
+ .option('dry-run', {
19
+ describe: 'Report what would be removed without actually removing anything',
20
+ type: 'boolean',
21
+ }),
22
+ handler: async ({ repository }, args) => {
23
+ await assertAllowedBranch(repository, readBranchGuardOptions(args));
24
+ await CleanService.clean(repository, {
25
+ ...readPackageFilterOptions(args),
26
+ progress: args.progress,
27
+ dryRun: args.dryRun,
28
+ root: args.root,
29
+ logLevel: args.logLevel,
30
+ });
31
+ },
32
+ };
@@ -0,0 +1,9 @@
1
+ import { type CustomCommand } from 'rman';
2
+ /**
3
+ * `rman publish` - contributed by the `rman-node` plugin.
4
+ *
5
+ * Question B at the package level: each target asks its *own* registry whether this version is
6
+ * already out there (npm via `npm view`, Docker via `docker manifest inspect`). Never whether
7
+ * `version` ran - it only inspects what is on disk and on the registry, so re-running is safe.
8
+ */
9
+ export declare const command: CustomCommand;
@@ -0,0 +1,228 @@
1
+ import readline from 'node:readline/promises';
2
+ import colors from 'ansi-colors';
3
+ import { applyBranchGuardOptions, applyPackageFilterOptions, assertAllowedBranch, DockerPublishService, readBranchGuardOptions, readPackageFilterOptions, } from 'rman';
4
+ import { CiService } from '../services/ci.service.js';
5
+ import { PublishService } from '../services/publish.service.js';
6
+ /**
7
+ * `rman publish` - contributed by the `rman-node` plugin.
8
+ *
9
+ * Question B at the package level: each target asks its *own* registry whether this version is
10
+ * already out there (npm via `npm view`, Docker via `docker manifest inspect`). Never whether
11
+ * `version` ran - it only inspects what is on disk and on the registry, so re-running is safe.
12
+ */
13
+ export const command = {
14
+ command: 'publish',
15
+ configKeys: ['publish'],
16
+ describe: 'Publishes every package to its configured target(s) (npm by default, or .rmanrc "publish.target")',
17
+ builder: cmd => applyBranchGuardOptions(applyPackageFilterOptions(cmd))
18
+ .example('$0 publish', '# Show the plan, then ask for confirmation')
19
+ .example('$0 publish --yes', '# Publish immediately, no confirmation')
20
+ .example('$0 publish --dry-run', '# Only show the plan, never publish')
21
+ .example('$0 publish --target docker', '# Only the packages configured for the "docker" target')
22
+ .option('yes', {
23
+ alias: 'y',
24
+ describe: 'Skip the confirmation prompt and publish immediately',
25
+ type: 'boolean',
26
+ })
27
+ .option('dry-run', {
28
+ describe: 'Only show the plan - never publishes, regardless of --yes',
29
+ type: 'boolean',
30
+ })
31
+ .option('json', {
32
+ alias: 'j',
33
+ describe: 'Print the plan as JSON instead of text - one entry per package and target. Combine with ' +
34
+ '--dry-run to ask "is there anything to publish?" without publishing (e.g. a CI release gate).',
35
+ type: 'boolean',
36
+ })
37
+ .option('target', {
38
+ describe: 'Restrict this run to just these publish target(s) ("npm"/"docker", repeatable) - default: ' +
39
+ 'every target each package itself is configured for (.rmanrc "publish.target", "npm" when unset). ' +
40
+ 'A package that opts into "docker" but has no "publish.docker" config errors clearly instead of ' +
41
+ 'being silently skipped.',
42
+ type: 'array',
43
+ choices: ['npm', 'docker'],
44
+ })
45
+ .option('ignore-dirty', {
46
+ describe: 'Exclude a package with uncommitted local changes instead of aborting the whole run',
47
+ type: 'boolean',
48
+ })
49
+ .option('package-manager', {
50
+ describe: 'Package manager to publish with (default: npm, or .rmanrc "packageManager")',
51
+ choices: PACKAGE_MANAGERS,
52
+ })
53
+ .option('access', {
54
+ describe: 'npm publish --access <public|restricted> - required by the registry for a new scoped package',
55
+ choices: ['public', 'restricted'],
56
+ })
57
+ .option('tag', {
58
+ describe: 'npm publish --tag <tag> - the dist-tag this version is published under (default "latest")',
59
+ type: 'string',
60
+ })
61
+ .option('otp', {
62
+ describe: 'npm publish --otp <otp> - a 2FA one-time password, for registries that require it',
63
+ type: 'string',
64
+ })
65
+ .option('registry', {
66
+ describe: 'Registry to check against and publish to (default: whatever .npmrc already configures)',
67
+ type: 'string',
68
+ })
69
+ .option('userconfig', {
70
+ describe: 'Path to a custom .npmrc to use for both the registry check and the actual publish',
71
+ type: 'string',
72
+ })
73
+ .option('contents', {
74
+ describe: "Subdirectory to publish from, relative to each package's own directory - only consulted when a " +
75
+ 'package has no "publishConfig.directory" of its own (that always wins when present)',
76
+ type: 'string',
77
+ })
78
+ .option('docker-namespace', {
79
+ describe: 'Prefixed onto a bare (no "/") "publish.docker.image" - default: the DOCKERHUB_NAMESPACE ' +
80
+ 'environment variable.',
81
+ type: 'string',
82
+ }),
83
+ handler: async ({ repository }, args) => {
84
+ await assertAllowedBranch(repository, readBranchGuardOptions(args));
85
+ const targets = resolveTargets(args.target);
86
+ const explicitTargets = !!args.target?.length;
87
+ const explicitDockerTarget = explicitTargets && targets.has('docker');
88
+ const ignoreDirty = args.ignoreDirty;
89
+ const npmOptions = {
90
+ ...readPackageFilterOptions(args),
91
+ ignoreDirty,
92
+ registry: args.registry,
93
+ userconfig: args.userconfig,
94
+ };
95
+ const dockerOptions = {
96
+ ...readPackageFilterOptions(args),
97
+ ignoreDirty,
98
+ namespace: args.dockerNamespace,
99
+ };
100
+ const npmPlan = targets.has('npm') ? await PublishService.getPlan(repository, npmOptions) : [];
101
+ const dockerPlan = targets.has('docker') ? await DockerPublishService.getPlan(repository, dockerOptions) : [];
102
+ if (args.json) {
103
+ console.log(JSON.stringify([...npmPlan.map(e => jsonEntry(e, 'npm')), ...dockerPlan.map(e => jsonEntry(e, 'docker'))], undefined, 2));
104
+ }
105
+ else {
106
+ printPlan(npmPlan);
107
+ printPlan(dockerPlan, 'docker');
108
+ }
109
+ if (explicitDockerTarget && !dockerPlan.length) {
110
+ const message = '--target docker was given, but no package\'s .rmanrc configures "publish.docker".';
111
+ console.log(colors.red(message));
112
+ const err = new Error(message);
113
+ err.logged = true;
114
+ throw err;
115
+ }
116
+ const errors = [...npmPlan, ...dockerPlan].filter(e => e.status === 'error');
117
+ if (errors.length) {
118
+ const allDirty = errors.every(e => e.reason === 'uncommitted local changes');
119
+ const message = allDirty
120
+ ? `${errors.length} package(s) have uncommitted local changes ` +
121
+ '(pass --ignore-dirty to exclude them instead of aborting)'
122
+ : `${errors.length} package(s) failed to prepare for publish - see the errors above`;
123
+ console.log(colors.red(message));
124
+ const err = new Error(message);
125
+ err.logged = true;
126
+ throw err;
127
+ }
128
+ if (![...npmPlan, ...dockerPlan].some(e => e.status === 'publish')) {
129
+ if (!args.json)
130
+ console.log(colors.gray('Nothing to publish.'));
131
+ return;
132
+ }
133
+ if (args.dryRun)
134
+ return;
135
+ let proceed = !!args.yes;
136
+ if (!proceed) {
137
+ if (!process.stdout.isTTY) {
138
+ console.log(colors.gray('Not a TTY - refusing to prompt. Pass --yes to publish non-interactively.'));
139
+ return;
140
+ }
141
+ proceed = await confirm('Publish these packages?');
142
+ }
143
+ if (!proceed)
144
+ return;
145
+ const appliedNpm = targets.has('npm')
146
+ ? await PublishService.applyPlan(repository, npmPlan, {
147
+ ...npmOptions,
148
+ packageManager: args.packageManager,
149
+ access: args.access,
150
+ tag: args.tag,
151
+ otp: args.otp,
152
+ contents: args.contents,
153
+ })
154
+ : [];
155
+ const appliedDocker = targets.has('docker') ? await DockerPublishService.applyPlan(repository, dockerPlan) : [];
156
+ let failed = false;
157
+ for (const entry of appliedNpm) {
158
+ if (entry.status === 'publish') {
159
+ console.log(colors.green('published'), colors.cyan(entry.package.name), entry.version);
160
+ }
161
+ else if (entry.status === 'error' && npmPlan.find(e => e.package === entry.package)?.status === 'publish') {
162
+ failed = true;
163
+ console.log(colors.red('failed'), colors.cyan(entry.package.name), colors.red(entry.reason ?? ''));
164
+ }
165
+ }
166
+ for (const entry of appliedDocker) {
167
+ if (entry.status === 'publish') {
168
+ console.log(colors.green('published'), colors.gray('[docker]'), colors.cyan(entry.package.name), entry.image);
169
+ }
170
+ else if (entry.status === 'error' && dockerPlan.find(e => e.package === entry.package)?.status === 'publish') {
171
+ failed = true;
172
+ console.log(colors.red('failed'), colors.gray('[docker]'), colors.cyan(entry.package.name), colors.red(entry.reason ?? ''));
173
+ }
174
+ }
175
+ if (failed) {
176
+ const err = new Error('"publish" failed');
177
+ err.logged = true;
178
+ throw err;
179
+ }
180
+ },
181
+ };
182
+ function resolveTargets(input) {
183
+ if (!input?.length)
184
+ return new Set(['npm', 'docker']);
185
+ return new Set(input);
186
+ }
187
+ /** One `--json` row. `target` is what distinguishes otherwise-identical rows for a package that
188
+ * ships to several targets at once, so a consumer can tell which one still needs publishing. */
189
+ function jsonEntry(entry, target) {
190
+ return {
191
+ name: entry.package.name,
192
+ target,
193
+ status: entry.status,
194
+ version: entry.version,
195
+ reason: entry.reason,
196
+ };
197
+ }
198
+ function printPlan(entries, label) {
199
+ const prefix = label ? colors.gray(`[${label}] `) : '';
200
+ for (const e of entries) {
201
+ const name = prefix + colors.cyan(e.package.name);
202
+ switch (e.status) {
203
+ case 'publish':
204
+ console.log(colors.green('publish'), name, e.version, colors.gray(e.reason ?? ''));
205
+ break;
206
+ case 'up-to-date':
207
+ console.log(colors.gray('up-to-date'), name, e.version);
208
+ break;
209
+ case 'skip':
210
+ console.log(colors.cyan('skip'), name, colors.gray(e.reason ?? ''));
211
+ break;
212
+ case 'error':
213
+ console.log(colors.red('error'), name, colors.red(e.reason ?? ''));
214
+ break;
215
+ }
216
+ }
217
+ }
218
+ async function confirm(question) {
219
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
220
+ try {
221
+ const answer = await rl.question(`${question} (y/N) `);
222
+ return /^y(es)?$/i.test(answer.trim());
223
+ }
224
+ finally {
225
+ rl.close();
226
+ }
227
+ }
228
+ const PACKAGE_MANAGERS = ['npm', 'yarn', 'pnpm', 'bun'];
package/index.d.ts ADDED
@@ -0,0 +1,31 @@
1
+ import './augmentation/rman.augmentation.js';
2
+ export { augmentManifest, DEPENDENCY_KEYS, packageJsonManifest } from './augmentation/manifest.augmentation.js';
3
+ export { augmentRun, packageJsonSteps } from './augmentation/run.augmentation.js';
4
+ export { augmentSystemInfo } from './augmentation/system-info.augmentation.js';
5
+ export { augmentWorkspace, npmWorkspace } from './augmentation/workspace.augmentation.js';
6
+ export type { NodeConfigKeys, RmanNodeConfig } from './interfaces/rman-config.interface.js';
7
+ export { defineConfig } from './interfaces/rman-config.interface.js';
8
+ export { CiService } from './services/ci.service.js';
9
+ export { CleanService } from './services/clean.service.js';
10
+ export { PublishService } from './services/publish.service.js';
11
+ export { augmentVersionPlan, nodeVersionPlanner, NodeVersionPlanService } from './services/version-plan.service.js';
12
+ export { augmentBinPath, npmBinPaths } from './utils/npm-run-path.js';
13
+ export type { ParsedWorkspaceRange } from './utils/workspace-range.js';
14
+ export { parseWorkspaceRange, resolveWorkspaceRange } from './utils/workspace-range.js';
15
+ /** The version this package reports. Rewritten into `build/index.js` by `support/postbuild.cjs`. */
16
+ export declare const version = "1";
17
+ /** Everything this package contributes to rman, as one plugin. Exported by name as well, for code
18
+ * registering it directly instead of through a config. */
19
+ export declare const nodePlugin: import("rman").RmanPlugin;
20
+ /**
21
+ * **An `.rmanrc` config, not a plugin** - which is what a package naming itself in `plugins` should
22
+ * hand over. A package exposing exactly one plugin was the shape of the plugin it happens to
23
+ * contain today: adding a second, or anything else a config can say, would have changed what every
24
+ * repository importing it receives. As a config it is the same kind of thing as the file that names
25
+ * it, and `plugins` accepts an object entry precisely so this works.
26
+ *
27
+ * Only `plugins` is read out of it by `loadPlugins` - a config's other keys reach a repository
28
+ * through `extends`, the key that means "merge this underneath mine".
29
+ */
30
+ declare const _default: import("./interfaces/rman-config.interface.js").RmanNodeConfig;
31
+ export default _default;
package/index.js ADDED
@@ -0,0 +1,100 @@
1
+ import './augmentation/rman.augmentation.js';
2
+ import { definePlugin } from 'rman';
3
+ import { augmentManifest, packageJsonManifest } from './augmentation/manifest.augmentation.js';
4
+ import { augmentRun, packageJsonSteps } from './augmentation/run.augmentation.js';
5
+ import { augmentSystemInfo } from './augmentation/system-info.augmentation.js';
6
+ import { augmentWorkspace, npmWorkspace } from './augmentation/workspace.augmentation.js';
7
+ import * as ciCommand from './commands/ci.command.js';
8
+ import * as cleanCommand from './commands/clean.command.js';
9
+ import * as publishCommand from './commands/publish.command.js';
10
+ import { defineConfig } from './interfaces/rman-config.interface.js';
11
+ import { augmentVersionPlan, nodeVersionPlanner } from './services/version-plan.service.js';
12
+ import { augmentBinPath, npmBinPaths } from './utils/npm-run-path.js';
13
+ export { augmentManifest, DEPENDENCY_KEYS, packageJsonManifest } from './augmentation/manifest.augmentation.js';
14
+ export { augmentRun, packageJsonSteps } from './augmentation/run.augmentation.js';
15
+ export { augmentSystemInfo } from './augmentation/system-info.augmentation.js';
16
+ export { augmentWorkspace, npmWorkspace } from './augmentation/workspace.augmentation.js';
17
+ export { defineConfig } from './interfaces/rman-config.interface.js';
18
+ export { CiService } from './services/ci.service.js';
19
+ export { CleanService } from './services/clean.service.js';
20
+ export { PublishService } from './services/publish.service.js';
21
+ export { augmentVersionPlan, nodeVersionPlanner, NodeVersionPlanService } from './services/version-plan.service.js';
22
+ export { augmentBinPath, npmBinPaths } from './utils/npm-run-path.js';
23
+ export { parseWorkspaceRange, resolveWorkspaceRange } from './utils/workspace-range.js';
24
+ /** The version this package reports. Rewritten into `build/index.js` by `support/postbuild.cjs`. */
25
+ export const version = '1.1.1';
26
+ /**
27
+ * Node.js support for rman, as a plugin:
28
+ *
29
+ * ```yaml
30
+ * # .rmanrc.yml
31
+ * plugins: ['rman-node']
32
+ * ```
33
+ *
34
+ * rman's core is about repositories - packages, versions, changelogs, releases, branches. These
35
+ * three commands are about *npm*, which is a different thing that happens to be true of most
36
+ * repositories rman has been used on so far:
37
+ *
38
+ * - **`publish`** asks an npm registry whether a version is already out there, and pushes it -
39
+ * including the manifest it generates in a build directory, the `"workspace:"` ranges it
40
+ * resolves, and the `devDependencies` it strips.
41
+ * - **`ci`** deletes `node_modules` and a lockfile, and reinstalls with npm/yarn/pnpm/bun.
42
+ * - **`clean`** deletes TypeScript's output - a compiled `.js`/`.js.map`/`.d.ts` beside its `.ts`
43
+ * source, a `*.tsbuildinfo`, skipping `node_modules` while it looks. Every one of those is a
44
+ * TypeScript fact, so a core `clean` was a command that only looked general: nothing in it would
45
+ * fire for a Cargo or Go repository, which have `cargo clean` and `go clean` of their own. The
46
+ * `clean.include`/`clean.exclude` globs came along because splitting them off would leave two
47
+ * commands with one name.
48
+ *
49
+ * `.rmanrc "packageManager"` is **this package's** config key, declared in `NodeConfigKeys` and
50
+ * merged into `RmanConfig` by declaration - `ci`/`publish` read it to decide which one to shell out
51
+ * to, and the `SystemInfo` augmentation reads it to decide which version to report. It was core
52
+ * "because `info` reads it", and that stopped being true when `SystemInfo`'s npm half moved here:
53
+ * measured, nothing in the core read it at all, only the declaration was left behind.
54
+ *
55
+ * **Docker stayed in the core**, where it belongs - any language's project can publish an image.
56
+ * What is still wrong is that this command *drives* it: `publish --target docker` in a repository
57
+ * that is not a Node one would have to install this plugin to reach it. Fixing that means making a
58
+ * publish target something a plugin contributes to a core `publish`, which is the next step rather
59
+ * than this one.
60
+ */
61
+ /** Applied as the plugin module loads - before any command runs, since `loadPlugins` imports this
62
+ * during CLI startup. Augmentations go here rather than inside a command so that `rman info`,
63
+ * which is a *core* command, is affected too. */
64
+ augmentManifest();
65
+ augmentSystemInfo();
66
+ augmentRun();
67
+ augmentWorkspace();
68
+ augmentVersionPlan();
69
+ augmentBinPath();
70
+ /** Everything this package contributes to rman, as one plugin. Exported by name as well, for code
71
+ * registering it directly instead of through a config. */
72
+ export const nodePlugin = definePlugin({
73
+ name: 'rman-node',
74
+ commands: [publishCommand.command, ciCommand.command, cleanCommand.command],
75
+ /** Declared as well as registered by `augmentRun()` above - `addStepSource` is idempotent per
76
+ * source, and a plugin loaded through `plugins` should not need an import side effect to work. */
77
+ runSteps: packageJsonSteps,
78
+ /** What finds the packages at all - see `Repository.create` for why this has to be declared
79
+ * rather than only registered by an import. */
80
+ workspace: npmWorkspace,
81
+ /** What a package's name and version even are - read before anything else. */
82
+ manifest: packageJsonManifest,
83
+ /** What `version`/`changed` compute a release with. `VersionPlanService` is abstract, so without
84
+ * this the two commands have nothing to ask - see `NodeVersionPlanService`. */
85
+ versionPlanner: nodeVersionPlanner,
86
+ /** `node_modules/.bin` on PATH for every `exec`/`runBin`, so a repository's pinned `eslint`/`tsc`
87
+ * is the one that runs. */
88
+ binPaths: npmBinPaths,
89
+ });
90
+ /**
91
+ * **An `.rmanrc` config, not a plugin** - which is what a package naming itself in `plugins` should
92
+ * hand over. A package exposing exactly one plugin was the shape of the plugin it happens to
93
+ * contain today: adding a second, or anything else a config can say, would have changed what every
94
+ * repository importing it receives. As a config it is the same kind of thing as the file that names
95
+ * it, and `plugins` accepts an object entry precisely so this works.
96
+ *
97
+ * Only `plugins` is read out of it by `loadPlugins` - a config's other keys reach a repository
98
+ * through `extends`, the key that means "merge this underneath mine".
99
+ */
100
+ export default defineConfig({ plugins: [nodePlugin] });
@@ -0,0 +1,80 @@
1
+ import type { RmanConfig, WithAppend } from 'rman';
2
+ import type { CiService } from '../services/ci.service.js';
3
+ /**
4
+ * The `.rmanrc` keys that only mean something because the repository is a Node one.
5
+ *
6
+ * Declared here rather than in rman's core for the reason the commands themselves are: `clean`
7
+ * describes TypeScript's output, and `publish.directory` a `package.json` generated at publish
8
+ * time. A Cargo or Go repository has neither, and a core interface offering them was a core
9
+ * interface claiming to know npm.
10
+ *
11
+ * **Two surfaces, and they are not alternatives:**
12
+ *
13
+ * - the `declare module 'rman'` block in [`../augmentation/rman.augmentation.ts`] merges these into
14
+ * `RmanConfigKeys`, so `pkg.config.clean` stays typed wherever it is read - `CleanService`
15
+ * included - with no casts. It lives there because one such block per package is the limit;
16
+ * - `RmanNodeConfig` is the name a *config author* annotates with, which is what makes the import
17
+ * carrying that augmentation explicit instead of a side effect someone has to remember.
18
+ */
19
+ export interface NodeConfigKeys {
20
+ /**
21
+ * Which package manager `ci`/`publish` shell out to, and whose version `info` reports under
22
+ * `Binaries`. Root level only. Default `npm`.
23
+ *
24
+ * It used to be a core key, on the grounds that `info` read it - and that stopped being true the
25
+ * moment `SystemInfo`'s npm half moved here: measured, **nothing in rman's core reads it at
26
+ * all**, only the declaration was left behind. The value set was npm's tooling the whole time.
27
+ */
28
+ packageManager?: CiService.PackageManager;
29
+ /** Extra files and directories `clean` removes, beyond TypeScript's own output - globs relative
30
+ * to each package's own directory. Per-package cascaded; a package declaring its own `clean`
31
+ * block replaces the root's entirely for itself, rather than combining with it. */
32
+ clean?: RmanNodeConfig.CleanOptions;
33
+ }
34
+ export declare namespace RmanNodeConfig {
35
+ interface CleanOptions extends CleanOptionsKeys, WithAppend<CleanOptionsKeys> {
36
+ }
37
+ interface CleanOptionsKeys {
38
+ include?: string | string[];
39
+ exclude?: string | string[];
40
+ /** Excludes this package from `clean` entirely. */
41
+ skip?: boolean;
42
+ }
43
+ /** Added to the core's `publish` block - the npm-only half of it. `target`, `skip` and `docker`
44
+ * stay in the core: the first two are read by `list` and by every target's own plan, and Docker
45
+ * publishing is not a Node concern at all. */
46
+ interface PublishOptions {
47
+ /** Where this package's publishable output lives, relative to its own directory (e.g.
48
+ * `"build"`). Per-package cascaded, so a root `"[*]"` block can say it once for the whole
49
+ * repository instead of repeating `publishConfig.directory` in every `package.json` - which
50
+ * still wins when a package declares it, being the more specific statement.
51
+ *
52
+ * Publishing from such a directory means the manifest there is **generated by `publish`**,
53
+ * from the package's own - see `PublishService`. There is nothing to configure about it. */
54
+ directory?: string;
55
+ }
56
+ }
57
+ /**
58
+ * `.rmanrc` for a repository using this plugin: rman's own keys plus the ones above.
59
+ *
60
+ * ```js
61
+ * // .rmanrc.mjs
62
+ * import { defineConfig } from 'rman-node';
63
+ *
64
+ * export default defineConfig({
65
+ * plugins: ['rman-node'],
66
+ * '[ws:*]': { clean: { include: 'build' }, publish: { directory: 'build' } },
67
+ * });
68
+ * ```
69
+ *
70
+ * `.mjs`, not `.ts`: rman loads `.rmanrc.cjs`/`.mjs`/`.js` and no TypeScript form, so the type
71
+ * reaches a config file through the editor rather than through a compiler. The JSON and YAML forms
72
+ * carry no type at all - since the JSON Schema was removed, they are unchecked.
73
+ */
74
+ export interface RmanNodeConfig extends RmanConfig {
75
+ }
76
+ /**
77
+ * Identity helper for authoring a `.rmanrc.mjs`/`.cjs`/`.js` config with this plugin's keys checked
78
+ * - rman's own `defineConfig` with a narrower parameter, nothing more. Returns `config` unchanged.
79
+ */
80
+ export declare function defineConfig(config: RmanNodeConfig): RmanNodeConfig;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Identity helper for authoring a `.rmanrc.mjs`/`.cjs`/`.js` config with this plugin's keys checked
3
+ * - rman's own `defineConfig` with a narrower parameter, nothing more. Returns `config` unchanged.
4
+ */
5
+ export function defineConfig(config) {
6
+ return config;
7
+ }
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "rman-node",
3
+ "description": "Node.js support for rman - the commands and conventions that only mean something in a Node repository",
4
+ "version": "1.1.1",
5
+ "author": "Panates",
6
+ "license": "MIT",
7
+ "dependencies": {
8
+ "@netlify/parse-npm-script": "^0.1.2",
9
+ "ansi-colors": "^4.1.3",
10
+ "envinfo": "^7.21.0",
11
+ "fast-glob": "^3.3.3",
12
+ "semver": "^7.8.5",
13
+ "yargs": "^18.1.0"
14
+ },
15
+ "peerDependencies": {
16
+ "rman": "^1.1.1"
17
+ },
18
+ "type": "module",
19
+ "module": "./index.js",
20
+ "types": "./index.d.ts",
21
+ "exports": {
22
+ ".": {
23
+ "types": "./index.d.ts",
24
+ "default": "./index.js"
25
+ },
26
+ "./package.json": "./package.json"
27
+ },
28
+ "engines": {
29
+ "node": ">=20.0"
30
+ },
31
+ "contributors": [
32
+ "Eray Hanoglu <e.hanoglu@panates.com>",
33
+ "Ilker Gurelli <i.gurelli@panates.com>",
34
+ "Onur Tokel <o.tokel@panates.com>",
35
+ "Bircan Yuruk <b.yuruk@panates.com>"
36
+ ],
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "git+https://github.com/panates/rman.git",
40
+ "directory": "./packages/node"
41
+ },
42
+ "keywords": [
43
+ "rman",
44
+ "node",
45
+ "nodejs",
46
+ "npm",
47
+ "monorepo",
48
+ "repository",
49
+ "release"
50
+ ],
51
+ "publishConfig": {
52
+ "access": "public"
53
+ }
54
+ }
@@ -0,0 +1,38 @@
1
+ import { type LogLevel, type PackageFilterOptions, type Repository } from 'rman';
2
+ export declare namespace CiService {
3
+ type PackageManager = (typeof PACKAGE_MANAGERS)[number];
4
+ interface Options extends PackageFilterOptions {
5
+ packageManager?: PackageManager;
6
+ /** Show the live progress panel while running. Default true, same as `run`/`build`; auto-disabled
7
+ * when stdout isn't a TTY. Doesn't affect what's printed once done - see `run`. */
8
+ progress?: boolean;
9
+ /** Verbosity of the classic per-step log (only applies when the live panel is off). Falls back to
10
+ * the root's `.rmanrc logLevel`, then 'info' - see `resolveRootLogLevel`. */
11
+ logLevel?: LogLevel;
12
+ }
13
+ /** `.rmanrc packageManager` (root only) picks the package manager used for the final install;
14
+ * explicit CLI value wins over it. Defaults to 'npm'. */
15
+ function resolvePackageManager(repository: Repository, cliValue?: PackageManager): PackageManager;
16
+ /** Deletes `node_modules` and any known lockfile directly under `dirname`. Returns the names
17
+ * that actually existed (and were removed), so the caller can log only those. */
18
+ function wipe(dirname: string): Promise<string[]>;
19
+ /**
20
+ * `ci`: a from-scratch, reproducible install for CI pipelines. For every package (root
21
+ * included), deletes `node_modules` and any lockfile - or, if the package defines its own
22
+ * `"ci"` script, runs that instead. Once every package is clean, installs once at the root
23
+ * with the configured package manager (`npm`/`yarn`/`pnpm`/`bun`).
24
+ *
25
+ * Uses the same live progress panel as `run`/`build` (see `../utils/progress-panel.ts`) while it
26
+ * runs, falling back to a plain rmdir/clean/run/install log line per step when the panel is off.
27
+ *
28
+ * Unlike `run`/`build`, it does *not* end with a per-package success tally: `ci`'s packages don't
29
+ * have independently meaningful outcomes the way a build or test run does - wiping a package is
30
+ * trivial and the one step that can genuinely fail, the install, is a single operation for the
31
+ * whole repository. Counting "N succeeded" across packages would just be noise, so only actual
32
+ * failures get called out (by name, with whatever output they produced), followed by one plain
33
+ * completed/failed line.
34
+ */
35
+ function reinstall(repository: Repository, options?: Options): Promise<void>;
36
+ }
37
+ declare const PACKAGE_MANAGERS: readonly ["npm", "yarn", "pnpm", "bun"];
38
+ export {};