@modulify/conventional-release 0.1.0
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 +380 -0
- package/bin/cli.cjs +308 -0
- package/bin/cli.mjs +291 -0
- package/bin/index.cjs +7 -0
- package/bin/index.js +9 -0
- package/dist/config.d.ts +12 -0
- package/dist/constants.d.ts +6 -0
- package/dist/execute.d.ts +9 -0
- package/dist/index.cjs +712 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.mjs +712 -0
- package/dist/plan.d.ts +24 -0
- package/dist/reporter.d.ts +11 -0
- package/dist/runtime.d.ts +36 -0
- package/package.json +98 -0
- package/types/domain.d.ts +124 -0
- package/types/index.d.ts +23 -0
- package/types/release.d.ts +93 -0
package/bin/cli.mjs
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import { GitCommander } from "@modulify/git-toolkit";
|
|
2
|
+
import { Runner } from "@modulify/git-toolkit/shell";
|
|
3
|
+
import { run } from "../dist/index.mjs";
|
|
4
|
+
import yargs from "yargs";
|
|
5
|
+
import { hideBin } from "yargs/helpers";
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
import figures from "figures";
|
|
8
|
+
import * as util from "node:util";
|
|
9
|
+
const DEFAULTS = {
|
|
10
|
+
dry: false,
|
|
11
|
+
verbose: false,
|
|
12
|
+
tags: false
|
|
13
|
+
};
|
|
14
|
+
class CliParseError extends Error {
|
|
15
|
+
help;
|
|
16
|
+
}
|
|
17
|
+
async function parseArgv(argv = process.argv) {
|
|
18
|
+
const parser = yargs(hideBin(argv)).locale("en").scriptName("conventional-release").usage("Usage: $0 [options]").option("release-as", {
|
|
19
|
+
alias: "r",
|
|
20
|
+
describe: "Specify the release type (major|minor|patch)",
|
|
21
|
+
requiresArg: true,
|
|
22
|
+
string: true
|
|
23
|
+
}).option("prerelease", {
|
|
24
|
+
alias: "p",
|
|
25
|
+
describe: "Specify the prerelease type (alpha|beta|rc)",
|
|
26
|
+
requiresArg: true,
|
|
27
|
+
string: true
|
|
28
|
+
}).option("dry", {
|
|
29
|
+
type: "boolean",
|
|
30
|
+
default: DEFAULTS.dry,
|
|
31
|
+
describe: "See the commands that running release would run"
|
|
32
|
+
}).option("verbose", {
|
|
33
|
+
type: "boolean",
|
|
34
|
+
default: DEFAULTS.verbose,
|
|
35
|
+
describe: "Show detailed per-slice progress output"
|
|
36
|
+
}).option("tags", {
|
|
37
|
+
type: "boolean",
|
|
38
|
+
default: DEFAULTS.tags,
|
|
39
|
+
describe: "Show generated tags in the final output"
|
|
40
|
+
}).exitProcess(false).check((options) => {
|
|
41
|
+
if (!["alpha", "beta", "rc", void 0].includes(options.prerelease)) {
|
|
42
|
+
throw new Error("prerelease should be one of alpha, beta, rc or undefined");
|
|
43
|
+
}
|
|
44
|
+
return true;
|
|
45
|
+
}).showHelpOnFail(false).fail((message) => {
|
|
46
|
+
throw new Error(message);
|
|
47
|
+
}).alias("version", "v").alias("help", "h").example("$0", "Update changelog and tag release").example("$0 --dry --verbose", "Show a detailed dry-run release preview").pkgConf("release").wrap(97);
|
|
48
|
+
let parsed;
|
|
49
|
+
try {
|
|
50
|
+
parsed = await parser.parseAsync();
|
|
51
|
+
} catch (error) {
|
|
52
|
+
const failure = new CliParseError(error.message);
|
|
53
|
+
const help = await parser.getHelp();
|
|
54
|
+
failure.help = [help].flat().join("\n");
|
|
55
|
+
throw failure;
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
releaseAs: parsed.releaseAs,
|
|
59
|
+
prerelease: parsed.prerelease,
|
|
60
|
+
dry: parsed.dry,
|
|
61
|
+
verbose: parsed.verbose,
|
|
62
|
+
tags: parsed.tags
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function createReporter({
|
|
66
|
+
output,
|
|
67
|
+
git,
|
|
68
|
+
showTags = false,
|
|
69
|
+
verbosity = "summary"
|
|
70
|
+
}) {
|
|
71
|
+
if (verbosity === "detailed") {
|
|
72
|
+
return new DetailedReporter({
|
|
73
|
+
output,
|
|
74
|
+
git,
|
|
75
|
+
showTags
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return new SummaryReporter({
|
|
79
|
+
output,
|
|
80
|
+
git,
|
|
81
|
+
showTags
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
class SummaryReporter {
|
|
85
|
+
output;
|
|
86
|
+
git;
|
|
87
|
+
showTags;
|
|
88
|
+
scope = null;
|
|
89
|
+
position = /* @__PURE__ */ new Map();
|
|
90
|
+
constructor({
|
|
91
|
+
output,
|
|
92
|
+
git,
|
|
93
|
+
showTags
|
|
94
|
+
}) {
|
|
95
|
+
this.output = output;
|
|
96
|
+
this.git = git;
|
|
97
|
+
this.showTags = showTags;
|
|
98
|
+
}
|
|
99
|
+
async onStart(context) {
|
|
100
|
+
this.output.info(
|
|
101
|
+
context.dry ? "Starting dry release" : "Starting release"
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
async onScope(scope, context) {
|
|
105
|
+
this.scope = scope;
|
|
106
|
+
this.position = new Map(
|
|
107
|
+
scope.slices.map((slice, index) => [slice.id, index + 1])
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
async onSliceStart(slice) {
|
|
111
|
+
this.output.info("Running slice %s", [this.describeProgress(slice)]);
|
|
112
|
+
}
|
|
113
|
+
async onSuccess(result) {
|
|
114
|
+
if (!result.changed) {
|
|
115
|
+
this.output.success("No changes since last release");
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const changed = result.slices.filter((slice) => slice.changed);
|
|
119
|
+
const primary = changed[0];
|
|
120
|
+
const packages = collectPackages(changed);
|
|
121
|
+
this.output.success("Release slices: %s", [String(changed.length)]);
|
|
122
|
+
this.output.success("Updated packages: %s", [String(packages.length)]);
|
|
123
|
+
if (primary) {
|
|
124
|
+
this.output.success("Next version: %s", [primary.nextVersion]);
|
|
125
|
+
}
|
|
126
|
+
if (result.dry) {
|
|
127
|
+
this.output.info("No committing or tagging since this was a dry run");
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
this.output.success("Committed %s staged files", [String(result.files.length)]);
|
|
131
|
+
if (this.showTags) {
|
|
132
|
+
const tags = collectTags(changed);
|
|
133
|
+
if (tags.length) {
|
|
134
|
+
this.output.success("Tags: %s", [tags.join(", ")]);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
this.output.info("Run `%s` to publish", [
|
|
138
|
+
`git push --follow-tags origin ${await this.resolveBranch()}`
|
|
139
|
+
]);
|
|
140
|
+
}
|
|
141
|
+
async onError(error) {
|
|
142
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
143
|
+
this.output.error(message);
|
|
144
|
+
}
|
|
145
|
+
describeProgress(slice) {
|
|
146
|
+
const position = this.position.get(slice.id);
|
|
147
|
+
const total = this.scope?.slices.length;
|
|
148
|
+
const label = describeSlice(slice);
|
|
149
|
+
return position && total ? `${position}/${total}: ${label}` : label;
|
|
150
|
+
}
|
|
151
|
+
async resolveBranch() {
|
|
152
|
+
try {
|
|
153
|
+
return await this.git.revParse("HEAD", { abbrevRef: true });
|
|
154
|
+
} catch {
|
|
155
|
+
return "%branch%";
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
class DetailedReporter extends SummaryReporter {
|
|
160
|
+
async onScope(scope, context) {
|
|
161
|
+
await super.onScope(scope, context);
|
|
162
|
+
this.output.info("%s scope: %s packages, %s affected, %s slices", [
|
|
163
|
+
scope.mode,
|
|
164
|
+
String(scope.packages.length),
|
|
165
|
+
String(scope.affected.length),
|
|
166
|
+
String(scope.slices.length)
|
|
167
|
+
]);
|
|
168
|
+
}
|
|
169
|
+
async onSliceSuccess(slice) {
|
|
170
|
+
if (!slice.changed) {
|
|
171
|
+
this.output.warn("Completed slice %s without version changes (%s)", [
|
|
172
|
+
describeSlice(slice),
|
|
173
|
+
slice.currentVersion
|
|
174
|
+
]);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
this.output.success("Completed slice %s: %s -> %s (%s)", [
|
|
178
|
+
describeSlice(slice),
|
|
179
|
+
slice.currentVersion,
|
|
180
|
+
slice.nextVersion,
|
|
181
|
+
slice.releaseType
|
|
182
|
+
]);
|
|
183
|
+
if (this.showTags && slice.tag) {
|
|
184
|
+
this.output.info("Tag: %s", [slice.tag]);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
async onSuccess(result) {
|
|
188
|
+
await super.onSuccess(result);
|
|
189
|
+
if (!result.changed) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const packages = collectPackages(result.slices.filter((slice) => slice.changed));
|
|
193
|
+
this.output.info("Updated packages: %s", [describePackages(packages)]);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
function describeSlice(slice) {
|
|
197
|
+
if (slice.partition) {
|
|
198
|
+
return `${slice.partition} [${describePackages(slice.packages)}]`;
|
|
199
|
+
}
|
|
200
|
+
return describePackages(slice.packages);
|
|
201
|
+
}
|
|
202
|
+
function describePackages(packages) {
|
|
203
|
+
return packages.map((pkg) => pkg.name ?? pkg.path).join(", ");
|
|
204
|
+
}
|
|
205
|
+
function collectTags(slices) {
|
|
206
|
+
return slices.map((slice) => slice.tag).filter((tag) => !!tag);
|
|
207
|
+
}
|
|
208
|
+
function collectPackages(slices) {
|
|
209
|
+
const packages = [];
|
|
210
|
+
const seen = /* @__PURE__ */ new Set();
|
|
211
|
+
for (const slice of slices) {
|
|
212
|
+
for (const pkg of slice.packages) {
|
|
213
|
+
const identity = pkg.name ?? pkg.path;
|
|
214
|
+
if (seen.has(identity)) {
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
seen.add(identity);
|
|
218
|
+
packages.push(pkg);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return packages;
|
|
222
|
+
}
|
|
223
|
+
class ConsoleOutput {
|
|
224
|
+
write(message) {
|
|
225
|
+
console.info(message);
|
|
226
|
+
}
|
|
227
|
+
writeError(message) {
|
|
228
|
+
console.error(message);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
class Output {
|
|
232
|
+
output;
|
|
233
|
+
theme;
|
|
234
|
+
constructor({
|
|
235
|
+
dry,
|
|
236
|
+
output = new ConsoleOutput(),
|
|
237
|
+
theme = createDefaultTheme(dry)
|
|
238
|
+
}) {
|
|
239
|
+
this.output = output;
|
|
240
|
+
this.theme = theme;
|
|
241
|
+
}
|
|
242
|
+
info(template, context = [], figure = this.theme.info) {
|
|
243
|
+
this.output.write(format(template, context, figure));
|
|
244
|
+
}
|
|
245
|
+
success(template, context = [], figure = this.theme.success) {
|
|
246
|
+
this.output.write(format(template, context, figure));
|
|
247
|
+
}
|
|
248
|
+
warn(template, context = [], figure = this.theme.warning) {
|
|
249
|
+
this.output.write(format(template, context, figure));
|
|
250
|
+
}
|
|
251
|
+
error(template, context = [], figure = this.theme.error) {
|
|
252
|
+
this.output.writeError(format(template, context, figure));
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
function createDefaultTheme(dry) {
|
|
256
|
+
return {
|
|
257
|
+
success: dry ? chalk.yellow(figures.tick) : chalk.green(figures.tick),
|
|
258
|
+
warning: chalk.yellow(figures.warning),
|
|
259
|
+
error: chalk.red(figures.cross),
|
|
260
|
+
info: chalk.blue(figures.info)
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
function format(template, context, figure) {
|
|
264
|
+
const bold = (arg) => chalk.bold(arg);
|
|
265
|
+
const message = util.format(template, ...context.map(bold));
|
|
266
|
+
return `${figure} ${message}`;
|
|
267
|
+
}
|
|
268
|
+
async function main(argv = process.argv) {
|
|
269
|
+
const cwd = process.cwd();
|
|
270
|
+
const options = await parseArgv(argv);
|
|
271
|
+
const output = new Output({
|
|
272
|
+
dry: options.dry,
|
|
273
|
+
output: new ConsoleOutput()
|
|
274
|
+
});
|
|
275
|
+
const git = new GitCommander({ sh: new Runner(cwd) });
|
|
276
|
+
await run({
|
|
277
|
+
cwd,
|
|
278
|
+
dry: options.dry,
|
|
279
|
+
releaseAs: options.releaseAs,
|
|
280
|
+
prerelease: options.prerelease,
|
|
281
|
+
reporter: createReporter({
|
|
282
|
+
output,
|
|
283
|
+
git,
|
|
284
|
+
showTags: options.tags,
|
|
285
|
+
verbosity: options.verbose ? "detailed" : "summary"
|
|
286
|
+
})
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
export {
|
|
290
|
+
main
|
|
291
|
+
};
|
package/bin/index.cjs
ADDED
package/bin/index.js
ADDED
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Options, RunOptions } from '../types/index';
|
|
2
|
+
type ScopeOptions = Omit<Options, 'changelogFile'>;
|
|
3
|
+
/**
|
|
4
|
+
* Resolves repository release configuration from `package.json`, `release.config.*`,
|
|
5
|
+
* and inline overrides using the public precedence rules.
|
|
6
|
+
*/
|
|
7
|
+
export declare function resolveConfig(cwd?: string, inline?: Options): Promise<Options>;
|
|
8
|
+
/** Converts repository configuration to executor-ready release options. */
|
|
9
|
+
export declare function toScopeOptions(config: Options): ScopeOptions;
|
|
10
|
+
/** Extracts inline overrides from high-level run options. */
|
|
11
|
+
export declare function toInlineConfig(options: RunOptions): Options;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const DEFAULT_CHANGELOG_FILE = "CHANGELOG.md";
|
|
2
|
+
export declare const DEFAULT_GIT_REMOTE = "origin";
|
|
3
|
+
export declare const DEFAULT_RELEASE_PREFIX = "chore(release): ";
|
|
4
|
+
export declare const DEFAULT_RELEASE_MODE = "sync";
|
|
5
|
+
export declare const DEFAULT_DEPENDENCY_POLICY_INTERNAL = "preserve";
|
|
6
|
+
export declare const RELEASE_CONFIG_FILENAMES: readonly ["release.config.ts", "release.config.mjs", "release.config.js"];
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Options, Reporter, Result, RunContext } from '../types/index';
|
|
2
|
+
import { DiscoveredScope } from './plan';
|
|
3
|
+
import { Runtime } from './runtime';
|
|
4
|
+
type Reporting = {
|
|
5
|
+
reporter: Reporter;
|
|
6
|
+
context: RunContext;
|
|
7
|
+
};
|
|
8
|
+
export declare function runScope(runtime: Runtime, scope: DiscoveredScope, options: Options, reporting?: Reporting): Promise<Result>;
|
|
9
|
+
export {};
|