rman 1.0.9 → 1.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -10
- package/cli.js +2 -0
- package/commands/github-release.command.d.ts +3 -0
- package/commands/github-release.command.js +119 -0
- package/commands/publish.command.js +6 -41
- package/constants.js +1 -1
- package/interfaces/rman-config.interface.d.ts +22 -17
- package/package.json +1 -1
- package/rmanrc.schema.json +9 -10
- package/services/github-release.service.d.ts +10 -9
- package/services/github-release.service.js +15 -22
- package/utils/change-hash.d.ts +2 -2
- package/utils/change-hash.js +2 -2
package/README.md
CHANGED
|
@@ -265,11 +265,10 @@ algorithm, prerelease semantics, and `"workspace:"` dependency-range handling.
|
|
|
265
265
|
|
|
266
266
|
### `rman publish`
|
|
267
267
|
|
|
268
|
-
Publishes every package to its configured
|
|
269
|
-
own `.rmanrc "publish.target"` says (`"npm"`, `"docker"`,
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
tag on the github side.
|
|
268
|
+
Publishes every package to its configured registry - `npm` by default, or whatever each package's
|
|
269
|
+
own `.rmanrc "publish.target"` says (`"npm"`, `"docker"`, or both). Each target decides for itself
|
|
270
|
+
whether the current version is already out there: `npm view` on the npm side, `docker manifest
|
|
271
|
+
inspect` on the docker side.
|
|
273
272
|
|
|
274
273
|
```bash
|
|
275
274
|
rman publish # show the plan, then ask for confirmation
|
|
@@ -281,7 +280,6 @@ rman publish --otp 123456
|
|
|
281
280
|
rman publish --registry https://registry.example.com --userconfig ./ci.npmrc
|
|
282
281
|
rman publish --package-manager pnpm
|
|
283
282
|
rman publish --target docker # only the packages configured for the "docker" target
|
|
284
|
-
rman publish --target github # only the GitHub Release side of it
|
|
285
283
|
```
|
|
286
284
|
|
|
287
285
|
A `"workspace:*"`/`"workspace:^"`/`"workspace:~"` dependency range is automatically rewritten to a
|
|
@@ -292,10 +290,19 @@ A package opts into building/pushing a Docker image via `.rmanrc "publish.target
|
|
|
292
290
|
a `"publish.docker"` block (`image`, `platforms`, `buildContexts`, `buildArgs`, ...) - see
|
|
293
291
|
[docs/cli/publish.md#docker-publishing-publishdocker](docs/cli/publish.md#docker-publishing-publishdocker).
|
|
294
292
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
293
|
+
### `rman github-release`
|
|
294
|
+
|
|
295
|
+
Creates the repository's GitHub Release for the version that just shipped - one per run, named after
|
|
296
|
+
the repository's own release tag, with notes covering every package that shipped under it.
|
|
297
|
+
|
|
298
|
+
```bash
|
|
299
|
+
rman github-release --yes
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
It is deliberately neither a `publish.target` nor opt-in: a release isn't a registry a package ships
|
|
303
|
+
to, it's the repository's own record that a version shipped, and every repository wants that record.
|
|
304
|
+
It needs no configuration at all - see
|
|
305
|
+
[docs/cli/github-release.md](docs/cli/github-release.md).
|
|
299
306
|
|
|
300
307
|
### `rman import <path>`
|
|
301
308
|
|
package/cli.js
CHANGED
|
@@ -11,6 +11,7 @@ import * as ciCommand from './commands/ci.command.js';
|
|
|
11
11
|
import * as cleanCommand from './commands/clean.command.js';
|
|
12
12
|
import * as diffCommand from './commands/diff.command.js';
|
|
13
13
|
import * as execCommand from './commands/exec.command.js';
|
|
14
|
+
import * as githubReleaseCommand from './commands/github-release.command.js';
|
|
14
15
|
import * as importCommand from './commands/import.command.js';
|
|
15
16
|
import * as infoCommand from './commands/info.command.js';
|
|
16
17
|
import * as listCommand from './commands/list.command.js';
|
|
@@ -61,6 +62,7 @@ export async function runCli(options) {
|
|
|
61
62
|
testCommand.initCli(repository, program);
|
|
62
63
|
versionCommand.initCli(repository, program);
|
|
63
64
|
publishCommand.initCli(repository, program);
|
|
65
|
+
githubReleaseCommand.initCli(repository, program);
|
|
64
66
|
execCommand.initCli(repository, program);
|
|
65
67
|
changedCommand.initCli(repository, program);
|
|
66
68
|
diffCommand.initCli(repository, program);
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import readline from 'node:readline/promises';
|
|
2
|
+
import colors from 'ansi-colors';
|
|
3
|
+
import { GithubReleaseService } from '../services/github-release.service.js';
|
|
4
|
+
import { applyBranchGuardOptions, assertAllowedBranch, readBranchGuardOptions } from '../utils/branch-guard.js';
|
|
5
|
+
export function initCli(repository, program) {
|
|
6
|
+
program.command({
|
|
7
|
+
command: 'github-release',
|
|
8
|
+
describe: "Creates the repository's GitHub Release for the version that just shipped",
|
|
9
|
+
builder: cmd => applyBranchGuardOptions(cmd)
|
|
10
|
+
.example('$0 github-release', '# Show what would be released, then ask for confirmation')
|
|
11
|
+
.example('$0 github-release --yes', '# Create it immediately, no confirmation (CI)')
|
|
12
|
+
.example('$0 github-release --dry-run', '# Only show the plan')
|
|
13
|
+
.option('yes', {
|
|
14
|
+
alias: 'y',
|
|
15
|
+
describe: 'Skip the confirmation prompt and create the release immediately',
|
|
16
|
+
type: 'boolean',
|
|
17
|
+
})
|
|
18
|
+
.option('dry-run', {
|
|
19
|
+
describe: 'Only show the plan - never creates anything, regardless of --yes',
|
|
20
|
+
type: 'boolean',
|
|
21
|
+
})
|
|
22
|
+
.option('json', {
|
|
23
|
+
alias: 'j',
|
|
24
|
+
describe: 'Print the plan as JSON instead of text',
|
|
25
|
+
type: 'boolean',
|
|
26
|
+
})
|
|
27
|
+
.option('repository', {
|
|
28
|
+
describe: 'The "owner/repo" the release is created in - default: .rmanrc "githubRelease.repository", ' +
|
|
29
|
+
'falling back to the "origin" remote.',
|
|
30
|
+
type: 'string',
|
|
31
|
+
})
|
|
32
|
+
.option('ignore-dirty', {
|
|
33
|
+
describe: 'Release anyway when the working tree has uncommitted changes, instead of aborting',
|
|
34
|
+
type: 'boolean',
|
|
35
|
+
}),
|
|
36
|
+
handler: async (args) => {
|
|
37
|
+
await assertAllowedBranch(repository, readBranchGuardOptions(args));
|
|
38
|
+
// No package filtering: a release belongs to the repository, not to a package, so there is
|
|
39
|
+
// nothing for --scope/--ignore to narrow down.
|
|
40
|
+
const plan = await GithubReleaseService.getPlan(repository, {
|
|
41
|
+
ignoreDirty: args.ignoreDirty,
|
|
42
|
+
repository: args.repository,
|
|
43
|
+
});
|
|
44
|
+
if (args.json) {
|
|
45
|
+
console.log(JSON.stringify(plan.map(e => ({
|
|
46
|
+
tag: e.tag,
|
|
47
|
+
repository: e.repository,
|
|
48
|
+
status: e.status,
|
|
49
|
+
version: e.version,
|
|
50
|
+
reason: e.reason,
|
|
51
|
+
})), undefined, 2));
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
for (const e of plan) {
|
|
55
|
+
const name = colors.cyan(e.tag ?? e.version);
|
|
56
|
+
switch (e.status) {
|
|
57
|
+
case 'publish':
|
|
58
|
+
console.log(colors.green('release'), name, colors.gray(`${e.repository} - ${e.reason ?? ''}`));
|
|
59
|
+
break;
|
|
60
|
+
case 'up-to-date':
|
|
61
|
+
console.log(colors.gray('up-to-date'), name, colors.gray(e.reason ?? ''));
|
|
62
|
+
break;
|
|
63
|
+
case 'skip':
|
|
64
|
+
console.log(colors.cyan('skip'), name, colors.gray(e.reason ?? ''));
|
|
65
|
+
break;
|
|
66
|
+
case 'error':
|
|
67
|
+
console.log(colors.red('error'), name, colors.red(e.reason ?? ''));
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const error = plan.find(e => e.status === 'error');
|
|
73
|
+
if (error) {
|
|
74
|
+
const err = new Error(error.reason ?? 'Unable to prepare the GitHub Release');
|
|
75
|
+
err.logged = true;
|
|
76
|
+
throw err;
|
|
77
|
+
}
|
|
78
|
+
if (!plan.some(e => e.status === 'publish')) {
|
|
79
|
+
if (!args.json)
|
|
80
|
+
console.log(colors.gray('Nothing to release.'));
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (args.dryRun)
|
|
84
|
+
return;
|
|
85
|
+
let proceed = !!args.yes;
|
|
86
|
+
if (!proceed) {
|
|
87
|
+
if (!process.stdout.isTTY) {
|
|
88
|
+
console.log(colors.gray('Not a TTY - refusing to prompt. Pass --yes to release non-interactively.'));
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
proceed = await confirm('Create this release?');
|
|
92
|
+
}
|
|
93
|
+
if (!proceed)
|
|
94
|
+
return;
|
|
95
|
+
const applied = await GithubReleaseService.applyPlan(repository, plan);
|
|
96
|
+
const failed = applied.find(e => e.status === 'error');
|
|
97
|
+
if (failed) {
|
|
98
|
+
console.log(colors.red('failed'), colors.cyan(failed.tag ?? ''), colors.red(failed.reason ?? ''));
|
|
99
|
+
const err = new Error('"github-release" failed');
|
|
100
|
+
err.logged = true;
|
|
101
|
+
throw err;
|
|
102
|
+
}
|
|
103
|
+
for (const e of applied) {
|
|
104
|
+
if (e.status === 'publish')
|
|
105
|
+
console.log(colors.green('released'), colors.cyan(e.tag ?? ''));
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
async function confirm(question) {
|
|
111
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
112
|
+
try {
|
|
113
|
+
const answer = await rl.question(`${question} (y/N) `);
|
|
114
|
+
return /^y(es)?$/i.test(answer.trim());
|
|
115
|
+
}
|
|
116
|
+
finally {
|
|
117
|
+
rl.close();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -2,7 +2,6 @@ import readline from 'node:readline/promises';
|
|
|
2
2
|
import colors from 'ansi-colors';
|
|
3
3
|
import { CiService } from '../services/ci.service.js';
|
|
4
4
|
import { DockerPublishService } from '../services/docker-publish.service.js';
|
|
5
|
-
import { GithubReleaseService } from '../services/github-release.service.js';
|
|
6
5
|
import { PublishService } from '../services/publish.service.js';
|
|
7
6
|
import { applyBranchGuardOptions, assertAllowedBranch, readBranchGuardOptions } from '../utils/branch-guard.js';
|
|
8
7
|
import { applyPackageFilterOptions, readPackageFilterOptions } from '../utils/package-filter.js';
|
|
@@ -15,7 +14,6 @@ export function initCli(repository, program) {
|
|
|
15
14
|
.example('$0 publish --yes', '# Publish immediately, no confirmation')
|
|
16
15
|
.example('$0 publish --dry-run', '# Only show the plan, never publish')
|
|
17
16
|
.example('$0 publish --target docker', '# Only the packages configured for the "docker" target')
|
|
18
|
-
.example('$0 publish --target github', '# Only the GitHub Release side of it')
|
|
19
17
|
.option('yes', {
|
|
20
18
|
alias: 'y',
|
|
21
19
|
describe: 'Skip the confirmation prompt and publish immediately',
|
|
@@ -32,12 +30,12 @@ export function initCli(repository, program) {
|
|
|
32
30
|
type: 'boolean',
|
|
33
31
|
})
|
|
34
32
|
.option('target', {
|
|
35
|
-
describe: 'Restrict this run to just these publish target(s) ("npm"/"docker"
|
|
33
|
+
describe: 'Restrict this run to just these publish target(s) ("npm"/"docker", repeatable) - default: ' +
|
|
36
34
|
'every target each package itself is configured for (.rmanrc "publish.target", "npm" when unset). ' +
|
|
37
35
|
'A package that opts into "docker" but has no "publish.docker" config errors clearly instead of ' +
|
|
38
36
|
'being silently skipped.',
|
|
39
37
|
type: 'array',
|
|
40
|
-
choices: ['npm', 'docker'
|
|
38
|
+
choices: ['npm', 'docker'],
|
|
41
39
|
})
|
|
42
40
|
.option('ignore-dirty', {
|
|
43
41
|
describe: 'Exclude a package with uncommitted local changes instead of aborting the whole run',
|
|
@@ -76,18 +74,12 @@ export function initCli(repository, program) {
|
|
|
76
74
|
describe: 'Prefixed onto a bare (no "/") "publish.docker.image" - default: the DOCKERHUB_NAMESPACE ' +
|
|
77
75
|
'environment variable.',
|
|
78
76
|
type: 'string',
|
|
79
|
-
})
|
|
80
|
-
.option('github-repository', {
|
|
81
|
-
describe: 'The "owner/repo" GitHub Releases are created in - default: each package\'s own ' +
|
|
82
|
-
'"publish.github.repository", falling back to the "origin" remote.',
|
|
83
|
-
type: 'string',
|
|
84
77
|
}),
|
|
85
78
|
handler: async (args) => {
|
|
86
79
|
await assertAllowedBranch(repository, readBranchGuardOptions(args));
|
|
87
80
|
const targets = resolveTargets(args.target);
|
|
88
81
|
const explicitTargets = !!args.target?.length;
|
|
89
82
|
const explicitDockerTarget = explicitTargets && targets.has('docker');
|
|
90
|
-
const explicitGithubTarget = explicitTargets && targets.has('github');
|
|
91
83
|
const ignoreDirty = args.ignoreDirty;
|
|
92
84
|
const npmOptions = {
|
|
93
85
|
...readPackageFilterOptions(args),
|
|
@@ -100,23 +92,14 @@ export function initCli(repository, program) {
|
|
|
100
92
|
ignoreDirty,
|
|
101
93
|
namespace: args.dockerNamespace,
|
|
102
94
|
};
|
|
103
|
-
// No package filtering: a GitHub Release belongs to the repository, not to a package, so
|
|
104
|
-
// there is nothing for --scope/--ignore to narrow down.
|
|
105
|
-
const githubOptions = { ignoreDirty, repository: args.githubRepository };
|
|
106
95
|
const npmPlan = targets.has('npm') ? await PublishService.getPlan(repository, npmOptions) : [];
|
|
107
96
|
const dockerPlan = targets.has('docker') ? await DockerPublishService.getPlan(repository, dockerOptions) : [];
|
|
108
|
-
const githubPlan = targets.has('github') ? await GithubReleaseService.getPlan(repository, githubOptions) : [];
|
|
109
97
|
if (args.json) {
|
|
110
|
-
console.log(JSON.stringify([
|
|
111
|
-
...npmPlan.map(e => jsonEntry(e, 'npm')),
|
|
112
|
-
...dockerPlan.map(e => jsonEntry(e, 'docker')),
|
|
113
|
-
...githubPlan.map(e => jsonEntry(e, 'github')),
|
|
114
|
-
], undefined, 2));
|
|
98
|
+
console.log(JSON.stringify([...npmPlan.map(e => jsonEntry(e, 'npm')), ...dockerPlan.map(e => jsonEntry(e, 'docker'))], undefined, 2));
|
|
115
99
|
}
|
|
116
100
|
else {
|
|
117
101
|
printPlan(npmPlan);
|
|
118
102
|
printPlan(dockerPlan, 'docker');
|
|
119
|
-
printPlan(githubPlan, 'github');
|
|
120
103
|
}
|
|
121
104
|
if (explicitDockerTarget && !dockerPlan.length) {
|
|
122
105
|
const message = '--target docker was given, but no package\'s .rmanrc configures "publish.docker".';
|
|
@@ -125,14 +108,7 @@ export function initCli(repository, program) {
|
|
|
125
108
|
err.logged = true;
|
|
126
109
|
throw err;
|
|
127
110
|
}
|
|
128
|
-
|
|
129
|
-
const message = '--target github was given, but nothing in .rmanrc opts into the "github" target.';
|
|
130
|
-
console.log(colors.red(message));
|
|
131
|
-
const err = new Error(message);
|
|
132
|
-
err.logged = true;
|
|
133
|
-
throw err;
|
|
134
|
-
}
|
|
135
|
-
const errors = [...npmPlan, ...dockerPlan, ...githubPlan].filter(e => e.status === 'error');
|
|
111
|
+
const errors = [...npmPlan, ...dockerPlan].filter(e => e.status === 'error');
|
|
136
112
|
if (errors.length) {
|
|
137
113
|
const allDirty = errors.every(e => e.reason === 'uncommitted local changes');
|
|
138
114
|
const message = allDirty
|
|
@@ -144,7 +120,7 @@ export function initCli(repository, program) {
|
|
|
144
120
|
err.logged = true;
|
|
145
121
|
throw err;
|
|
146
122
|
}
|
|
147
|
-
if (![...npmPlan, ...dockerPlan
|
|
123
|
+
if (![...npmPlan, ...dockerPlan].some(e => e.status === 'publish')) {
|
|
148
124
|
if (!args.json)
|
|
149
125
|
console.log(colors.gray('Nothing to publish.'));
|
|
150
126
|
return;
|
|
@@ -172,7 +148,6 @@ export function initCli(repository, program) {
|
|
|
172
148
|
})
|
|
173
149
|
: [];
|
|
174
150
|
const appliedDocker = targets.has('docker') ? await DockerPublishService.applyPlan(repository, dockerPlan) : [];
|
|
175
|
-
const appliedGithub = targets.has('github') ? await GithubReleaseService.applyPlan(repository, githubPlan) : [];
|
|
176
151
|
let failed = false;
|
|
177
152
|
for (const entry of appliedNpm) {
|
|
178
153
|
if (entry.status === 'publish') {
|
|
@@ -193,16 +168,6 @@ export function initCli(repository, program) {
|
|
|
193
168
|
console.log(colors.red('failed'), colors.gray('[docker]'), colors.cyan(entry.package.name), colors.red(entry.reason ?? ''));
|
|
194
169
|
}
|
|
195
170
|
}
|
|
196
|
-
for (const entry of appliedGithub) {
|
|
197
|
-
if (entry.status === 'publish') {
|
|
198
|
-
console.log(colors.green('released'), colors.gray('[github]'), colors.cyan(entry.tag ?? ''));
|
|
199
|
-
}
|
|
200
|
-
else if (entry.status === 'error' &&
|
|
201
|
-
githubPlan.find(e => e.package === entry.package)?.status === 'publish') {
|
|
202
|
-
failed = true;
|
|
203
|
-
console.log(colors.red('failed'), colors.gray('[github]'), colors.cyan(entry.package.name), colors.red(entry.reason ?? ''));
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
171
|
if (failed) {
|
|
207
172
|
const err = new Error('"publish" failed');
|
|
208
173
|
err.logged = true;
|
|
@@ -213,7 +178,7 @@ export function initCli(repository, program) {
|
|
|
213
178
|
}
|
|
214
179
|
function resolveTargets(input) {
|
|
215
180
|
if (!input?.length)
|
|
216
|
-
return new Set(['npm', 'docker'
|
|
181
|
+
return new Set(['npm', 'docker']);
|
|
217
182
|
return new Set(input);
|
|
218
183
|
}
|
|
219
184
|
/** One `--json` row. `target` is what distinguishes otherwise-identical rows for a package that
|
package/constants.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '1.0.
|
|
1
|
+
export const version = '1.0.10';
|
|
@@ -16,6 +16,7 @@ export interface RmanConfig {
|
|
|
16
16
|
changelog?: RmanConfig.ChangelogOptions;
|
|
17
17
|
clean?: RmanConfig.CleanOptions;
|
|
18
18
|
publish?: RmanConfig.PublishOptions;
|
|
19
|
+
githubRelease?: RmanConfig.GithubReleaseOptions;
|
|
19
20
|
/** Keyed by npm script name (e.g. `"build"`, `"lint"`, `"test"`). */
|
|
20
21
|
run?: Record<string, RmanConfig.RunScriptOptions>;
|
|
21
22
|
/** Keyed by the in-repo package's own name. */
|
|
@@ -68,17 +69,18 @@ export declare namespace RmanConfig {
|
|
|
68
69
|
dependencies?: string[] | Record<string, string>;
|
|
69
70
|
}
|
|
70
71
|
interface PublishOptions {
|
|
71
|
-
/**
|
|
72
|
+
/** Which **registry** `publish` ships this package to - default `['npm']` (every existing repo
|
|
72
73
|
* keeps working unchanged). A package that only ever wants Docker images (typically also
|
|
73
|
-
* `"private": true`, since it's not meant for npm at all) sets `['docker']`;
|
|
74
|
-
* shipped as GitHub Release assets - or deployed elsewhere entirely, with the release only
|
|
75
|
-
* recording that it happened - sets `['github']`; any combination works (`['npm', 'github']`).
|
|
74
|
+
* `"private": true`, since it's not meant for npm at all) sets `['docker']`; both works too.
|
|
76
75
|
* Each target answers "is this version already out there?" against its own registry, so a
|
|
77
|
-
* package is never left without one: npm via `npm view`, docker via `docker manifest inspect
|
|
78
|
-
*
|
|
76
|
+
* package is never left without one: npm via `npm view`, docker via `docker manifest inspect`.
|
|
77
|
+
*
|
|
78
|
+
* Note this is strictly about *package distribution*. The repository's GitHub Release is not
|
|
79
|
+
* a target here - it isn't a place a package ships to, it's the repository's own record that
|
|
80
|
+
* a release happened, and it is never opted into: see `githubRelease` and the
|
|
81
|
+
* `github-release` command. */
|
|
79
82
|
target?: PublishTarget | PublishTarget[];
|
|
80
83
|
docker?: DockerPublishOptions;
|
|
81
|
-
github?: GithubPublishOptions;
|
|
82
84
|
/** Excludes this package from `publish` entirely (every target), regardless of
|
|
83
85
|
* `target`/`"private"` - a single, explicit "never published" statement, e.g. for a package
|
|
84
86
|
* released through some separate, unrelated process. `changelog` also skips it by default
|
|
@@ -87,7 +89,7 @@ export declare namespace RmanConfig {
|
|
|
87
89
|
* package can still be meaningfully versioned without ever being published. */
|
|
88
90
|
skip?: boolean;
|
|
89
91
|
}
|
|
90
|
-
type PublishTarget = 'npm' | 'docker'
|
|
92
|
+
type PublishTarget = 'npm' | 'docker';
|
|
91
93
|
/** Required once `"docker"` is one of this package's `publish.target`s - `publish --target
|
|
92
94
|
* docker` errors clearly on a package that opts in here but leaves this out. */
|
|
93
95
|
interface DockerPublishOptions {
|
|
@@ -112,19 +114,22 @@ export declare namespace RmanConfig {
|
|
|
112
114
|
* full description, if present. Default `"DOCKER_README.md"`. */
|
|
113
115
|
readme?: string;
|
|
114
116
|
}
|
|
115
|
-
/**
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
|
|
117
|
+
/** Entirely optional - `github-release` needs no configuration at all, since every required fact
|
|
118
|
+
* (which tag, which repository, what the notes say) already has a sensible source. Nothing here
|
|
119
|
+
* decides *whether* a release is cut: a release records that the repository shipped, so it is
|
|
120
|
+
* always cut, and these are only details about how. */
|
|
121
|
+
interface GithubReleaseOptions {
|
|
119
122
|
/** Files to attach to the release, as glob patterns relative to the package's own directory
|
|
120
|
-
* (e.g. `["dist/*.tar.gz"]`).
|
|
121
|
-
*
|
|
123
|
+
* (e.g. `["dist/*.tar.gz"]`). Read from **every** package, since one release covers the whole
|
|
124
|
+
* source tree. A release with no assets at all is still perfectly valid - it records that the
|
|
125
|
+
* version shipped, which is all a deploy-elsewhere package needs. */
|
|
122
126
|
assets?: string[];
|
|
123
|
-
/** `owner/repo`. Default: parsed from the `origin` remote's URL. */
|
|
127
|
+
/** `owner/repo`. Default: parsed from the `origin` remote's URL. Root-level only. */
|
|
124
128
|
repository?: string;
|
|
125
|
-
/** Create the release as an unpublished draft. Default `false`. */
|
|
129
|
+
/** Create the release as an unpublished draft. Default `false`. Root-level only. */
|
|
126
130
|
draft?: boolean;
|
|
127
|
-
/** Default: whether the version being released is itself a semver prerelease (`1.3.0-beta.0`).
|
|
131
|
+
/** Default: whether the version being released is itself a semver prerelease (`1.3.0-beta.0`).
|
|
132
|
+
* Root-level only. */
|
|
128
133
|
prerelease?: boolean;
|
|
129
134
|
}
|
|
130
135
|
}
|
package/package.json
CHANGED
package/rmanrc.schema.json
CHANGED
|
@@ -131,11 +131,11 @@
|
|
|
131
131
|
},
|
|
132
132
|
"publish": {
|
|
133
133
|
"type": "object",
|
|
134
|
-
"description": "Options for the \"publish\" command/PublishService
|
|
134
|
+
"description": "Options for the \"publish\" command/PublishService and DockerPublishService. Per-package cascaded.",
|
|
135
135
|
"additionalProperties": false,
|
|
136
136
|
"properties": {
|
|
137
137
|
"target": {
|
|
138
|
-
"description": "
|
|
138
|
+
"description": "Which registry \"publish\" ships this package to. Default [\"npm\"]. A package that only ships Docker images (typically also \"private\": true) sets [\"docker\"]; both works too. Each target answers \"is this version already out there?\" against its own registry: npm via \"npm view\", docker via \"docker manifest inspect\". This is strictly about package distribution - the repository's GitHub Release is not a target here, see \"githubRelease\" and the \"github-release\" command.",
|
|
139
139
|
"default": [
|
|
140
140
|
"npm"
|
|
141
141
|
],
|
|
@@ -155,10 +155,6 @@
|
|
|
155
155
|
"$ref": "#/definitions/dockerPublishConfig",
|
|
156
156
|
"description": "Required once \"docker\" is one of this package's \"publish.target\"s."
|
|
157
157
|
},
|
|
158
|
-
"github": {
|
|
159
|
-
"$ref": "#/definitions/githubPublishConfig",
|
|
160
|
-
"description": "Optional even when \"github\" is one of this package's \"publish.target\"s - every required fact already has a sensible default source."
|
|
161
|
-
},
|
|
162
158
|
"skip": {
|
|
163
159
|
"type": "boolean",
|
|
164
160
|
"default": false,
|
|
@@ -166,6 +162,10 @@
|
|
|
166
162
|
}
|
|
167
163
|
}
|
|
168
164
|
},
|
|
165
|
+
"githubRelease": {
|
|
166
|
+
"$ref": "#/definitions/githubReleaseConfig",
|
|
167
|
+
"description": "Details of the repository's GitHub Release (\"github-release\"/GithubReleaseService). Entirely optional - every required fact already has a sensible default source. Nothing here decides whether a release is cut: a release records that the repository shipped, so it is always cut. \"assets\" is per-package cascaded; the rest is root-level only."
|
|
168
|
+
},
|
|
169
169
|
"run": {
|
|
170
170
|
"type": "object",
|
|
171
171
|
"description": "Per-script options for \"run\"/\"build\"/\"test\"/RunService, keyed by npm script name (e.g. \"build\", \"lint\", \"test\").",
|
|
@@ -230,8 +230,7 @@
|
|
|
230
230
|
"type": "string",
|
|
231
231
|
"enum": [
|
|
232
232
|
"npm",
|
|
233
|
-
"docker"
|
|
234
|
-
"github"
|
|
233
|
+
"docker"
|
|
235
234
|
]
|
|
236
235
|
},
|
|
237
236
|
"dockerPublishConfig": {
|
|
@@ -286,7 +285,7 @@
|
|
|
286
285
|
}
|
|
287
286
|
}
|
|
288
287
|
},
|
|
289
|
-
"
|
|
288
|
+
"githubReleaseConfig": {
|
|
290
289
|
"type": "object",
|
|
291
290
|
"additionalProperties": false,
|
|
292
291
|
"properties": {
|
|
@@ -295,7 +294,7 @@
|
|
|
295
294
|
"items": {
|
|
296
295
|
"type": "string"
|
|
297
296
|
},
|
|
298
|
-
"description": "Files to attach to the release, as glob patterns relative to the package's own directory (e.g. [\"dist/*.tar.gz\"]). A release with no assets is still valid."
|
|
297
|
+
"description": "Files to attach to the release, as glob patterns relative to the package's own directory (e.g. [\"dist/*.tar.gz\"]). Read from every package, since one release covers the whole source tree. A release with no assets is still valid."
|
|
299
298
|
},
|
|
300
299
|
"repository": {
|
|
301
300
|
"type": "string",
|
|
@@ -11,7 +11,7 @@ export declare namespace GithubReleaseService {
|
|
|
11
11
|
/** Uncommitted local changes anywhere in the repository make the release `'skip'` instead of
|
|
12
12
|
* `'error'` - same as `version`/`publish`'s other targets. */
|
|
13
13
|
ignoreDirty?: boolean;
|
|
14
|
-
/** `owner/repo` override - otherwise the root's own `
|
|
14
|
+
/** `owner/repo` override - otherwise the root's own `githubRelease.repository`, falling back
|
|
15
15
|
* to the `origin` remote's URL. */
|
|
16
16
|
repository?: string;
|
|
17
17
|
}
|
|
@@ -31,13 +31,14 @@ export declare namespace GithubReleaseService {
|
|
|
31
31
|
reason?: string;
|
|
32
32
|
}
|
|
33
33
|
/**
|
|
34
|
-
* Computes what `
|
|
34
|
+
* Computes what `github-release` *would* do - **one** release per run.
|
|
35
35
|
*
|
|
36
36
|
* A GitHub Release is a property of the repository, not of a package: the tag covers the whole
|
|
37
|
-
* source tree, so everything that shipped under it belongs in it.
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* a
|
|
37
|
+
* source tree, so everything that shipped under it belongs in it. There is deliberately **no
|
|
38
|
+
* opt-in**: it isn't a place a package ships to (that's `publish.target`, which is about
|
|
39
|
+
* registries - npm, Docker Hub, GitHub Packages), it's the repository's own record that a
|
|
40
|
+
* release happened, and a repository always wants that record. Re-running is harmless - an
|
|
41
|
+
* existing release for the tag reads `'up-to-date'`.
|
|
41
42
|
*
|
|
42
43
|
* The release is identified by the repository's own version (the root's - see
|
|
43
44
|
* `VersionService`'s `buildRootEntry`): its release tag when that version is a calendar one, and
|
|
@@ -52,9 +53,9 @@ export declare namespace GithubReleaseService {
|
|
|
52
53
|
*/
|
|
53
54
|
function getPlan(repository: Repository, options?: Options, deps?: Deps): Promise<Entry[]>;
|
|
54
55
|
/**
|
|
55
|
-
* Creates the repository's GitHub Release, then uploads whatever `
|
|
56
|
-
* match. The body covers **every** package that shipped under this
|
|
57
|
-
*
|
|
56
|
+
* Creates the repository's GitHub Release, then uploads whatever `githubRelease.assets` globs
|
|
57
|
+
* match, across every package. The body covers **every** package that shipped under this
|
|
58
|
+
* release, since the tag covers all of their code either way.
|
|
58
59
|
*
|
|
59
60
|
* Each package's notes are bounded by the *previous repository release*, and headed with that
|
|
60
61
|
* package's own version, so a repo whose packages sit on different version lines still reads
|
|
@@ -9,13 +9,14 @@ import { ChangelogService } from './changelog.service.js';
|
|
|
9
9
|
export var GithubReleaseService;
|
|
10
10
|
(function (GithubReleaseService) {
|
|
11
11
|
/**
|
|
12
|
-
* Computes what `
|
|
12
|
+
* Computes what `github-release` *would* do - **one** release per run.
|
|
13
13
|
*
|
|
14
14
|
* A GitHub Release is a property of the repository, not of a package: the tag covers the whole
|
|
15
|
-
* source tree, so everything that shipped under it belongs in it.
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* a
|
|
15
|
+
* source tree, so everything that shipped under it belongs in it. There is deliberately **no
|
|
16
|
+
* opt-in**: it isn't a place a package ships to (that's `publish.target`, which is about
|
|
17
|
+
* registries - npm, Docker Hub, GitHub Packages), it's the repository's own record that a
|
|
18
|
+
* release happened, and a repository always wants that record. Re-running is harmless - an
|
|
19
|
+
* existing release for the tag reads `'up-to-date'`.
|
|
19
20
|
*
|
|
20
21
|
* The release is identified by the repository's own version (the root's - see
|
|
21
22
|
* `VersionService`'s `buildRootEntry`): its release tag when that version is a calendar one, and
|
|
@@ -30,18 +31,15 @@ export var GithubReleaseService;
|
|
|
30
31
|
*/
|
|
31
32
|
async function getPlan(repository, options = {}, deps = {}) {
|
|
32
33
|
const root = repository.rootPackage;
|
|
33
|
-
const wanted = [root, ...repository.getPackages()].some(pkg => targetsGithub(pkg) && !pkg.config.publish?.skip);
|
|
34
|
-
if (!wanted)
|
|
35
|
-
return [];
|
|
36
34
|
const git = new GitHelper({ cwd: repository.dirname });
|
|
37
35
|
const base = { package: root, version: root.version };
|
|
38
|
-
const repo = options.repository ?? root.config?.
|
|
36
|
+
const repo = options.repository ?? root.config?.githubRelease?.repository ?? repoFromRemoteUrl(await git.remoteUrl());
|
|
39
37
|
if (!repo) {
|
|
40
38
|
return [
|
|
41
39
|
{
|
|
42
40
|
...base,
|
|
43
41
|
status: 'error',
|
|
44
|
-
reason: 'cannot resolve "owner/repo" - set "
|
|
42
|
+
reason: 'cannot resolve "owner/repo" - set "githubRelease.repository" or an "origin" remote',
|
|
45
43
|
},
|
|
46
44
|
];
|
|
47
45
|
}
|
|
@@ -91,9 +89,9 @@ export var GithubReleaseService;
|
|
|
91
89
|
}
|
|
92
90
|
GithubReleaseService.getPlan = getPlan;
|
|
93
91
|
/**
|
|
94
|
-
* Creates the repository's GitHub Release, then uploads whatever `
|
|
95
|
-
* match. The body covers **every** package that shipped under this
|
|
96
|
-
*
|
|
92
|
+
* Creates the repository's GitHub Release, then uploads whatever `githubRelease.assets` globs
|
|
93
|
+
* match, across every package. The body covers **every** package that shipped under this
|
|
94
|
+
* release, since the tag covers all of their code either way.
|
|
97
95
|
*
|
|
98
96
|
* Each package's notes are bounded by the *previous repository release*, and headed with that
|
|
99
97
|
* package's own version, so a repo whose packages sit on different version lines still reads
|
|
@@ -113,7 +111,7 @@ export var GithubReleaseService;
|
|
|
113
111
|
const release = await createOrUpdateRelease(entry.repository, entry.tag, {
|
|
114
112
|
name: entry.tag,
|
|
115
113
|
body,
|
|
116
|
-
draft: !!repository.rootPackage.config?.
|
|
114
|
+
draft: !!repository.rootPackage.config?.githubRelease?.draft,
|
|
117
115
|
prerelease: resolvePrerelease(repository.rootPackage, entry.version),
|
|
118
116
|
});
|
|
119
117
|
await uploadAssets(repository, entry.repository, release.id);
|
|
@@ -127,11 +125,6 @@ export var GithubReleaseService;
|
|
|
127
125
|
})(GithubReleaseService || (GithubReleaseService = {}));
|
|
128
126
|
const GITHUB_API = 'https://api.github.com';
|
|
129
127
|
const GITHUB_UPLOADS = 'https://uploads.github.com';
|
|
130
|
-
function targetsGithub(pkg) {
|
|
131
|
-
const target = pkg.config.publish?.target;
|
|
132
|
-
const targets = Array.isArray(target) ? target : target ? [target] : ['npm'];
|
|
133
|
-
return targets.includes('github');
|
|
134
|
-
}
|
|
135
128
|
/** The tag naming this repository's release. A calendar root version means several version lines,
|
|
136
129
|
* so the release needs a name of its own (`release-*`); a plain one means every package shares it,
|
|
137
130
|
* and that shared version's tag already *is* the release. */
|
|
@@ -144,7 +137,7 @@ function releaseTagGlob(root) {
|
|
|
144
137
|
return pattern.replace('{name}', root.name);
|
|
145
138
|
}
|
|
146
139
|
function resolvePrerelease(root, version) {
|
|
147
|
-
const configured = root.config?.
|
|
140
|
+
const configured = root.config?.githubRelease?.prerelease;
|
|
148
141
|
// A calendar version's time part is a semver prerelease identifier by construction - it says
|
|
149
142
|
// nothing about the release being a preview, so it must not be read as one.
|
|
150
143
|
return configured ?? (!isCalendarVersion(version) && !!semver.prerelease(version));
|
|
@@ -242,12 +235,12 @@ async function createOrUpdateRelease(repository, tag, fields) {
|
|
|
242
235
|
}
|
|
243
236
|
return release;
|
|
244
237
|
}
|
|
245
|
-
/** Every `
|
|
238
|
+
/** Every `githubRelease.assets` glob across the repository, each resolved against its own
|
|
246
239
|
* package's directory - an app ships its artifacts from its own folder, but they all land on the
|
|
247
240
|
* one release the repository cut. */
|
|
248
241
|
async function uploadAssets(repository, repo, releaseId) {
|
|
249
242
|
for (const pkg of [repository.rootPackage, ...repository.getPackages()]) {
|
|
250
|
-
const patterns = pkg.config.
|
|
243
|
+
const patterns = pkg.config.githubRelease?.assets;
|
|
251
244
|
if (!patterns?.length)
|
|
252
245
|
continue;
|
|
253
246
|
const files = await fastGlob(patterns, { cwd: pkg.dirname, absolute: true, onlyFiles: true });
|
package/utils/change-hash.d.ts
CHANGED
|
@@ -15,8 +15,8 @@ export declare function tagPattern(pkg: Package): string;
|
|
|
15
15
|
export declare function findLatestTag(git: GitHelper, pkg: Package): Promise<string | undefined>;
|
|
16
16
|
/** The forward direction of `findLatestTag`: expands `pkg`'s (cascaded) `.rmanrc
|
|
17
17
|
* changelog.tagPattern` into the concrete tag name `version` belongs under - `{name}` becomes the
|
|
18
|
-
* package's own name, `*` becomes `version`. Shared by `version` (creating the tag),
|
|
19
|
-
*
|
|
18
|
+
* package's own name, `*` becomes `version`. Shared by `version` (creating the tag),
|
|
19
|
+
* `github-release` (finding the release that tag belongs to), and `detectChangeHash`'s own npm
|
|
20
20
|
* fallback (mapping a published version back onto a tag), so all three name tags identically. */
|
|
21
21
|
export declare function expandTag(pkg: Package, version: string): string;
|
|
22
22
|
/** The pattern expansion `expandTag` performs, on any pattern - `{name}` becomes `name`, `*` becomes
|
package/utils/change-hash.js
CHANGED
|
@@ -22,8 +22,8 @@ export async function findLatestTag(git, pkg) {
|
|
|
22
22
|
}
|
|
23
23
|
/** The forward direction of `findLatestTag`: expands `pkg`'s (cascaded) `.rmanrc
|
|
24
24
|
* changelog.tagPattern` into the concrete tag name `version` belongs under - `{name}` becomes the
|
|
25
|
-
* package's own name, `*` becomes `version`. Shared by `version` (creating the tag),
|
|
26
|
-
*
|
|
25
|
+
* package's own name, `*` becomes `version`. Shared by `version` (creating the tag),
|
|
26
|
+
* `github-release` (finding the release that tag belongs to), and `detectChangeHash`'s own npm
|
|
27
27
|
* fallback (mapping a published version back onto a tag), so all three name tags identically. */
|
|
28
28
|
export function expandTag(pkg, version) {
|
|
29
29
|
return applyTagPattern(tagPattern(pkg), pkg.name, version);
|