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.
- package/LICENSE +21 -0
- package/README.md +149 -0
- package/augmentation/manifest.augmentation.d.ts +17 -0
- package/augmentation/manifest.augmentation.js +129 -0
- package/augmentation/rman.augmentation.d.ts +45 -0
- package/augmentation/rman.augmentation.js +1 -0
- package/augmentation/run.augmentation.d.ts +15 -0
- package/augmentation/run.augmentation.js +61 -0
- package/augmentation/system-info.augmentation.d.ts +26 -0
- package/augmentation/system-info.augmentation.js +79 -0
- package/augmentation/workspace.augmentation.d.ts +18 -0
- package/augmentation/workspace.augmentation.js +49 -0
- package/commands/ci.command.d.ts +9 -0
- package/commands/ci.command.js +35 -0
- package/commands/clean.command.d.ts +6 -0
- package/commands/clean.command.js +32 -0
- package/commands/publish.command.d.ts +9 -0
- package/commands/publish.command.js +228 -0
- package/index.d.ts +31 -0
- package/index.js +100 -0
- package/interfaces/rman-config.interface.d.ts +80 -0
- package/interfaces/rman-config.interface.js +7 -0
- package/package.json +54 -0
- package/services/ci.service.d.ts +38 -0
- package/services/ci.service.js +201 -0
- package/services/clean.service.d.ts +51 -0
- package/services/clean.service.js +235 -0
- package/services/publish.service.d.ts +77 -0
- package/services/publish.service.js +258 -0
- package/services/version-plan.service.d.ts +50 -0
- package/services/version-plan.service.js +67 -0
- package/utils/npm-run-path.d.ts +21 -0
- package/utils/npm-run-path.js +36 -0
- package/utils/npm-view.d.ts +15 -0
- package/utils/npm-view.js +28 -0
- package/utils/workspace-range.d.ts +26 -0
- 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,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 {};
|