versionary 0.9.0 → 0.11.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 +42 -5
- package/dist/app/release/pr.js +8 -5
- package/dist/app/release/release.d.ts +10 -0
- package/dist/app/release/release.js +34 -11
- package/dist/app/release/state.js +14 -2
- package/dist/cli/index.js +98 -6
- package/dist/config/load-config.js +3 -0
- package/dist/config/schema.d.ts +1 -1
- package/dist/config/schema.js +1 -2
- package/dist/domain/release/changelog.js +12 -1
- package/dist/infra/git/commits.js +3 -3
- package/dist/infra/scm/runtime.d.ts +1 -1
- package/dist/infra/scm/runtime.js +2 -19
- package/dist/types/config.d.ts +1 -2
- package/dist/verify/verify-project.d.ts +2 -0
- package/dist/verify/verify-project.js +13 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ Versionary is being built to:
|
|
|
22
22
|
|
|
23
23
|
- support both direct releases and release-PR-gated releases
|
|
24
24
|
- work across repository types (Node, Rust, docs/LaTeX, etc.)
|
|
25
|
-
- stay SCM-agnostic at the core
|
|
25
|
+
- stay SCM-agnostic at the core with built-in integration adapters
|
|
26
26
|
(GitHub first; GitLab/Codeberg later)
|
|
27
27
|
- keep a small, stable core with explicit extension points
|
|
28
28
|
- handle trunk-based development and monorepo workflows cleanly
|
|
@@ -40,6 +40,7 @@ Out of scope (intentional):
|
|
|
40
40
|
|
|
41
41
|
- publishing artifacts to language registries
|
|
42
42
|
- replacing package-specific publish tooling
|
|
43
|
+
- external/user-provided plugin loading
|
|
43
44
|
|
|
44
45
|
Use your CI/CD platform for registry publishing, triggered from a created
|
|
45
46
|
release/tag.
|
|
@@ -48,7 +49,7 @@ release/tag.
|
|
|
48
49
|
|
|
49
50
|
Current implementation focuses on:
|
|
50
51
|
|
|
51
|
-
- strategy-based version updates (`simple`, `node`)
|
|
52
|
+
- strategy-based version updates (`simple`, `node`, `rust`, `r`)
|
|
52
53
|
- release planning and changelog generation
|
|
53
54
|
- review-mode vs direct-mode release flow
|
|
54
55
|
- built-in GitHub SCM plugin capabilities
|
|
@@ -56,6 +57,40 @@ Current implementation focuses on:
|
|
|
56
57
|
Planned/harder areas include deeper monorepo ergonomics, broader SCM coverage,
|
|
57
58
|
and stronger failure recovery around release steps.
|
|
58
59
|
|
|
60
|
+
## Adding a new release strategy
|
|
61
|
+
|
|
62
|
+
Versionary is set up so new language strategies can be added internally without
|
|
63
|
+
changing release orchestration. A new strategy should implement the
|
|
64
|
+
`VersionStrategy` contract in `src/domain/strategy/types.ts` and be wired in
|
|
65
|
+
`src/domain/strategy/resolve.ts`.
|
|
66
|
+
|
|
67
|
+
Checklist for new strategies (for example `python`):
|
|
68
|
+
|
|
69
|
+
- define strategy `name`
|
|
70
|
+
- define `getVersionFile(config)` defaults and config override behavior
|
|
71
|
+
- implement `readVersion(cwd, config)` with explicit malformed-file errors
|
|
72
|
+
- implement `writeVersion(cwd, config, version)` returning deterministic updated
|
|
73
|
+
file paths
|
|
74
|
+
- add release-name extraction support if package tags should derive from
|
|
75
|
+
language metadata (similar to Node/Rust/R)
|
|
76
|
+
- add focused strategy tests for ecosystem-specific behavior and edge cases
|
|
77
|
+
- add/extend strategy contract tests in `tests/strategy-contract.test.ts`
|
|
78
|
+
- update schema/docs for new `release-type` behavior and defaults
|
|
79
|
+
|
|
80
|
+
Current ecosystem policy defaults:
|
|
81
|
+
|
|
82
|
+
- changelog source for publish:
|
|
83
|
+
- root target uses root `changelog-file`
|
|
84
|
+
- package target uses `packages.<path>.changelog-file` when configured, else
|
|
85
|
+
root `changelog-file`
|
|
86
|
+
- lockfiles:
|
|
87
|
+
- Node strategy updates root `package-lock.json`/`npm-shrinkwrap.json` when
|
|
88
|
+
present
|
|
89
|
+
- Rust release PR prep refreshes all discovered `Cargo.lock` files
|
|
90
|
+
- workspace/inheritance:
|
|
91
|
+
- Rust supports `version.workspace = true` via `[workspace.package].version`
|
|
92
|
+
- other strategies should document equivalent inheritance behavior explicitly
|
|
93
|
+
|
|
59
94
|
## Architecture layout (current migration)
|
|
60
95
|
|
|
61
96
|
The repository is moving to explicit layered modules:
|
|
@@ -95,7 +130,7 @@ For a quick trial, use:
|
|
|
95
130
|
- pre-1.0 policy defaults to conservative major handling: for `0.y.z`, breaking
|
|
96
131
|
changes bump to `0.(y+1).0`; set `allow-stable-major: true` to allow explicit
|
|
97
132
|
auto-transition to `1.0.0` on a breaking release
|
|
98
|
-
- review mode (`review-mode`): `
|
|
133
|
+
- review mode (`review-mode`): `pr` (PR/MR style) or `direct` (no review
|
|
99
134
|
request)
|
|
100
135
|
- optional monorepo planning with `monorepo-mode` and `packages`:
|
|
101
136
|
- `independent` computes package bumps per path
|
|
@@ -176,7 +211,8 @@ Release planning is based on Conventional Commit parsing semantics:
|
|
|
176
211
|
- recognizes breaking changes from `!` and `BREAKING CHANGE` / `BREAKING-CHANGE`
|
|
177
212
|
footers
|
|
178
213
|
- maps release impact as `feat => minor`, `fix|perf => patch`, breaking => major
|
|
179
|
-
- treats `revert:` as
|
|
214
|
+
- treats `revert:` commits as patch-releasable by default (and major if marked
|
|
215
|
+
breaking, e.g. `revert!:` or `BREAKING CHANGE`)
|
|
180
216
|
- suppresses commits that are reverted within the analyzed release window so
|
|
181
217
|
they do not affect bump/changelog output
|
|
182
218
|
- emits parser diagnostics for malformed headers/footers/references and
|
|
@@ -239,7 +275,8 @@ Minimum GitHub token/repo permissions for Versionary-managed metadata:
|
|
|
239
275
|
|
|
240
276
|
`review-mode` behavior:
|
|
241
277
|
|
|
242
|
-
- `
|
|
278
|
+
- `pr` (preferred; `review` is a backward-compatible alias): `pnpm run run`
|
|
279
|
+
prepares/updates the release branch and creates or
|
|
243
280
|
updates a release PR
|
|
244
281
|
- `direct`: `pnpm run run` prepares/updates the release branch and skips review
|
|
245
282
|
request creation
|
package/dist/app/release/pr.js
CHANGED
|
@@ -235,7 +235,10 @@ function formatReleaseCommitTitle(releaseTargets) {
|
|
|
235
235
|
return "chore(release): v0.0.0";
|
|
236
236
|
}
|
|
237
237
|
const tags = releaseTargets.map((target) => target.tag);
|
|
238
|
-
|
|
238
|
+
if (tags.length === 1) {
|
|
239
|
+
return `chore(release): ${tags[0]}`;
|
|
240
|
+
}
|
|
241
|
+
return `chore(release): ${tags[0]} (+${tags.length - 1} more)`;
|
|
239
242
|
}
|
|
240
243
|
function prepareSimpleReleasePr(cwd = process.cwd(), options = {}) {
|
|
241
244
|
const plan = (0, plan_js_1.createSimplePlan)(cwd);
|
|
@@ -374,14 +377,14 @@ function renderSimpleReviewRequestBody(version, previousVersion, commits, plan =
|
|
|
374
377
|
}
|
|
375
378
|
async function openOrUpdateSimpleReviewRequest(cwd, branch, title, version, previousVersion, commits, plan = null, options = {}) {
|
|
376
379
|
const loaded = (0, load_config_js_1.loadConfig)(cwd);
|
|
377
|
-
const releaseFlow = loaded.config["review-mode"] ?? "
|
|
378
|
-
if (releaseFlow
|
|
380
|
+
const releaseFlow = loaded.config["review-mode"] ?? "pr";
|
|
381
|
+
if (releaseFlow === "direct") {
|
|
379
382
|
return "Release flow mode is direct; skipping review request creation.";
|
|
380
383
|
}
|
|
381
384
|
const plugins = (0, runtime_js_1.loadRuntimePlugins)();
|
|
382
385
|
const scmPlugins = (0, capabilities_js_1.findPluginsByCapability)(plugins, "scm.reviewRequest");
|
|
383
386
|
if (scmPlugins.length === 0) {
|
|
384
|
-
throw new Error("review-mode is
|
|
387
|
+
throw new Error("review-mode is pr but no scm.reviewRequest plugin is available.");
|
|
385
388
|
}
|
|
386
389
|
const plugin = scmPlugins[0];
|
|
387
390
|
if (!plugin?.createOrUpdateReviewRequest) {
|
|
@@ -410,5 +413,5 @@ function isReleaseCommitMessage(commitMessage) {
|
|
|
410
413
|
return true;
|
|
411
414
|
}
|
|
412
415
|
const subject = commitMessage.split("\n")[0]?.trim() ?? "";
|
|
413
|
-
return /^chore\(release\):\s+(?:v\d+\.\d+\.\d+|\S+-v\d+\.\d+\.\d+)(?:,\s+(?:v\d+\.\d+\.\d+|\S+-v\d+\.\d+\.\d+))
|
|
416
|
+
return /^chore\(release\):\s+(?:(?:v\d+\.\d+\.\d+|\S+-v\d+\.\d+\.\d+)(?:,\s+(?:v\d+\.\d+\.\d+|\S+-v\d+\.\d+\.\d+))*|(?:v\d+\.\d+\.\d+|\S+-v\d+\.\d+\.\d+)\s+\(\+\d+\s+more\))(?:\s+\(#\d+\))?$/u.test(subject);
|
|
414
417
|
}
|
|
@@ -1,8 +1,17 @@
|
|
|
1
|
+
import type { VersionaryConfig } from "../../types/config.js";
|
|
1
2
|
import type { VersionaryPluginContext } from "../../types/plugins.js";
|
|
3
|
+
export declare function resolveTargetChangelogFile(config: VersionaryConfig, rootChangelogFile: string, targetPath: string): string;
|
|
2
4
|
export declare function runSimpleRelease(cwd?: string): Promise<string>;
|
|
3
5
|
export type SimpleRunReleaseResult = {
|
|
4
6
|
action: "release-skipped";
|
|
5
7
|
reason: string;
|
|
8
|
+
} | {
|
|
9
|
+
action: "release-dry-run";
|
|
10
|
+
message: string;
|
|
11
|
+
targets: {
|
|
12
|
+
tag: string;
|
|
13
|
+
version: string;
|
|
14
|
+
}[];
|
|
6
15
|
} | {
|
|
7
16
|
action: "release-published";
|
|
8
17
|
message: string;
|
|
@@ -15,5 +24,6 @@ export type SimpleRunReleaseResult = {
|
|
|
15
24
|
};
|
|
16
25
|
export interface RunSimpleReleaseOptions {
|
|
17
26
|
logger?: VersionaryPluginContext["logger"];
|
|
27
|
+
"dry-run"?: boolean;
|
|
18
28
|
}
|
|
19
29
|
export declare function runSimpleReleaseDetailed(cwd?: string, options?: RunSimpleReleaseOptions): Promise<SimpleRunReleaseResult>;
|
|
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.resolveTargetChangelogFile = resolveTargetChangelogFile;
|
|
6
7
|
exports.runSimpleRelease = runSimpleRelease;
|
|
7
8
|
exports.runSimpleReleaseDetailed = runSimpleReleaseDetailed;
|
|
8
9
|
const node_child_process_1 = require("node:child_process");
|
|
@@ -46,6 +47,16 @@ function readReleaseNotes(cwd, version, changelogFile) {
|
|
|
46
47
|
.trim();
|
|
47
48
|
return notes.length > 0 ? notes : `Automated release for v${version}`;
|
|
48
49
|
}
|
|
50
|
+
function resolveTargetChangelogFile(config, rootChangelogFile, targetPath) {
|
|
51
|
+
if (targetPath === ".") {
|
|
52
|
+
return rootChangelogFile;
|
|
53
|
+
}
|
|
54
|
+
const packageChangelogFile = config.packages?.[targetPath]?.["changelog-file"];
|
|
55
|
+
if (!packageChangelogFile) {
|
|
56
|
+
return rootChangelogFile;
|
|
57
|
+
}
|
|
58
|
+
return node_path_1.default.posix.join(targetPath, packageChangelogFile);
|
|
59
|
+
}
|
|
49
60
|
async function runSimpleRelease(cwd = process.cwd()) {
|
|
50
61
|
const result = await runSimpleReleaseDetailed(cwd, { logger: console });
|
|
51
62
|
if (result.action === "release-skipped") {
|
|
@@ -66,16 +77,6 @@ async function runSimpleReleaseDetailed(cwd = process.cwd(), options = {}) {
|
|
|
66
77
|
const changelogFile = loaded.config["changelog-file"] ?? "CHANGELOG.md";
|
|
67
78
|
const version = strategy.readVersion(cwd, loaded.config);
|
|
68
79
|
const defaultTag = `v${version}`;
|
|
69
|
-
const plugins = (0, runtime_js_1.loadRuntimePlugins)();
|
|
70
|
-
const scmPlugins = (0, capabilities_js_1.findPluginsByCapability)(plugins, "scm.releaseMetadata");
|
|
71
|
-
if (scmPlugins.length === 0) {
|
|
72
|
-
throw new Error("No scm.releaseMetadata plugin is available.");
|
|
73
|
-
}
|
|
74
|
-
const plugin = scmPlugins[0];
|
|
75
|
-
if (!plugin?.createReleaseMetadata) {
|
|
76
|
-
throw new Error(`Plugin "${plugin?.name ?? "unknown"}" does not implement createReleaseMetadata.`);
|
|
77
|
-
}
|
|
78
|
-
const createReleaseMetadata = plugin.createReleaseMetadata;
|
|
79
80
|
const releaseTargets = (0, state_js_1.readReleaseTargets)(cwd);
|
|
80
81
|
const targets = releaseTargets.length > 0
|
|
81
82
|
? releaseTargets
|
|
@@ -86,12 +87,34 @@ async function runSimpleReleaseDetailed(cwd = process.cwd(), options = {}) {
|
|
|
86
87
|
tag: defaultTag,
|
|
87
88
|
},
|
|
88
89
|
];
|
|
90
|
+
if (options["dry-run"]) {
|
|
91
|
+
const targetList = targets.map((target) => `${target.tag} (${target.version})`);
|
|
92
|
+
return {
|
|
93
|
+
action: "release-dry-run",
|
|
94
|
+
targets: targets.map((target) => ({
|
|
95
|
+
tag: target.tag,
|
|
96
|
+
version: target.version,
|
|
97
|
+
})),
|
|
98
|
+
message: `Dry run: would publish releases ${targetList.join(", ")}`,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
const plugins = (0, runtime_js_1.loadRuntimePlugins)();
|
|
102
|
+
const scmPlugins = (0, capabilities_js_1.findPluginsByCapability)(plugins, "scm.releaseMetadata");
|
|
103
|
+
if (scmPlugins.length === 0) {
|
|
104
|
+
throw new Error("No scm.releaseMetadata plugin is available.");
|
|
105
|
+
}
|
|
106
|
+
const plugin = scmPlugins[0];
|
|
107
|
+
if (!plugin?.createReleaseMetadata) {
|
|
108
|
+
throw new Error(`Plugin "${plugin?.name ?? "unknown"}" does not implement createReleaseMetadata.`);
|
|
109
|
+
}
|
|
110
|
+
const createReleaseMetadata = plugin.createReleaseMetadata;
|
|
89
111
|
const releases = [];
|
|
90
112
|
for (const target of targets) {
|
|
113
|
+
const targetChangelogFile = resolveTargetChangelogFile(loaded.config, changelogFile, target.path);
|
|
91
114
|
const outcome = await (0, recovery_js_1.executeIdempotentReleaseTarget)(cwd, {
|
|
92
115
|
tag: target.tag,
|
|
93
116
|
version: target.version,
|
|
94
|
-
notes: readReleaseNotes(cwd, target.version,
|
|
117
|
+
notes: readReleaseNotes(cwd, target.version, targetChangelogFile),
|
|
95
118
|
}, {
|
|
96
119
|
createReleaseMetadata: (input) => createReleaseMetadata(input, {
|
|
97
120
|
cwd,
|
|
@@ -79,7 +79,7 @@ function readReleaseTargets(cwd = process.cwd()) {
|
|
|
79
79
|
const parsed = parseStateFile(node_fs_1.default.readFileSync(filePath, "utf8"), filePath);
|
|
80
80
|
return parsed[RELEASE_TARGETS_KEY] ?? [];
|
|
81
81
|
}
|
|
82
|
-
function writeBaselineSha(cwd = process.cwd(), sha, releaseTargets
|
|
82
|
+
function writeBaselineSha(cwd = process.cwd(), sha, releaseTargets) {
|
|
83
83
|
const baselineShaValue = sha ??
|
|
84
84
|
(0, node_child_process_1.execFileSync)("git", ["rev-parse", "HEAD"], {
|
|
85
85
|
cwd,
|
|
@@ -87,10 +87,22 @@ function writeBaselineSha(cwd = process.cwd(), sha, releaseTargets = []) {
|
|
|
87
87
|
stdio: ["ignore", "pipe", "ignore"],
|
|
88
88
|
}).trim();
|
|
89
89
|
const filePath = getBaselineStatePath(cwd);
|
|
90
|
+
const existing = node_fs_1.default.existsSync(filePath)
|
|
91
|
+
? parseStateFile(node_fs_1.default.readFileSync(filePath, "utf8"), filePath)
|
|
92
|
+
: {};
|
|
93
|
+
const existingTargets = existing[RELEASE_TARGETS_KEY] ?? [];
|
|
94
|
+
const nextTargets = releaseTargets === undefined
|
|
95
|
+
? existingTargets
|
|
96
|
+
: [
|
|
97
|
+
...new Map([...existingTargets, ...releaseTargets].map((target) => [
|
|
98
|
+
target.path,
|
|
99
|
+
target,
|
|
100
|
+
])).values(),
|
|
101
|
+
].sort((a, b) => a.path.localeCompare(b.path));
|
|
90
102
|
const next = {
|
|
91
103
|
[MANIFEST_VERSION_KEY]: 1,
|
|
92
104
|
[BASELINE_SHA_KEY]: baselineShaValue,
|
|
93
|
-
[RELEASE_TARGETS_KEY]:
|
|
105
|
+
[RELEASE_TARGETS_KEY]: nextTargets,
|
|
94
106
|
};
|
|
95
107
|
node_fs_1.default.writeFileSync(filePath, `${JSON.stringify(next, null, 2)}\n`, "utf8");
|
|
96
108
|
}
|
package/dist/cli/index.js
CHANGED
|
@@ -14,15 +14,33 @@ const changelog_js_1 = require("../domain/release/changelog.js");
|
|
|
14
14
|
const plan_js_1 = require("../domain/release/plan.js");
|
|
15
15
|
function printVerifyResult() {
|
|
16
16
|
const result = (0, verify_js_1.verifyProject)();
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
const categories = [
|
|
18
|
+
{ key: "config", title: "Config" },
|
|
19
|
+
{ key: "paths", title: "Paths" },
|
|
20
|
+
{ key: "version-files", title: "Version files" },
|
|
21
|
+
];
|
|
22
|
+
for (const category of categories) {
|
|
23
|
+
const checks = result.checks.filter((check) => check.category === category.key);
|
|
24
|
+
if (checks.length === 0) {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
console.log(`${category.title}:`);
|
|
28
|
+
for (const check of checks) {
|
|
29
|
+
const status = check.ok ? "OK" : "FAIL";
|
|
30
|
+
console.log(` [${status}] ${check.name} - ${check.details}`);
|
|
31
|
+
if (!check.ok && check.remediation) {
|
|
32
|
+
console.log(` Fix: ${check.remediation}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
console.log("");
|
|
20
36
|
}
|
|
37
|
+
console.log(result.ok ? "Summary: all checks passed." : "Summary: checks failed.");
|
|
21
38
|
return result.ok ? 0 : 1;
|
|
22
39
|
}
|
|
23
40
|
function parseFlags(args) {
|
|
24
41
|
return {
|
|
25
42
|
json: args.includes("--json"),
|
|
43
|
+
"dry-run": args.includes("--dry-run"),
|
|
26
44
|
};
|
|
27
45
|
}
|
|
28
46
|
function emitJson(payload) {
|
|
@@ -38,9 +56,20 @@ async function main() {
|
|
|
38
56
|
stdio: ["ignore", "pipe", "ignore"],
|
|
39
57
|
}).trim();
|
|
40
58
|
if ((0, pr_js_1.isReleaseCommitMessage)(commitMessage)) {
|
|
59
|
+
if (flags["dry-run"] && !flags.json) {
|
|
60
|
+
const release = await (0, release_js_1.runSimpleReleaseDetailed)(process.cwd(), {
|
|
61
|
+
logger,
|
|
62
|
+
"dry-run": true,
|
|
63
|
+
});
|
|
64
|
+
if (release.action === "release-dry-run") {
|
|
65
|
+
console.log(release.message);
|
|
66
|
+
return 0;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
41
69
|
if (flags.json) {
|
|
42
70
|
const release = await (0, release_js_1.runSimpleReleaseDetailed)(process.cwd(), {
|
|
43
71
|
logger,
|
|
72
|
+
"dry-run": flags["dry-run"],
|
|
44
73
|
});
|
|
45
74
|
if (release.action === "release-skipped") {
|
|
46
75
|
emitJson({
|
|
@@ -51,6 +80,16 @@ async function main() {
|
|
|
51
80
|
});
|
|
52
81
|
return 0;
|
|
53
82
|
}
|
|
83
|
+
if (release.action === "release-dry-run") {
|
|
84
|
+
emitJson({
|
|
85
|
+
action: "release-dry-run",
|
|
86
|
+
message: release.message,
|
|
87
|
+
releaseCreated: false,
|
|
88
|
+
tagNames: release.targets.map((target) => target.tag),
|
|
89
|
+
targets: release.targets,
|
|
90
|
+
});
|
|
91
|
+
return 0;
|
|
92
|
+
}
|
|
54
93
|
emitJson({
|
|
55
94
|
action: "release-published",
|
|
56
95
|
message: release.message,
|
|
@@ -78,6 +117,34 @@ async function main() {
|
|
|
78
117
|
console.log(message);
|
|
79
118
|
return 0;
|
|
80
119
|
}
|
|
120
|
+
if (flags["dry-run"]) {
|
|
121
|
+
const dryRunMessage = `Dry run: would prepare release PR branch ${plan.releaseBranchPrefix} for ${plan.nextVersion}`;
|
|
122
|
+
if (flags.json) {
|
|
123
|
+
emitJson({
|
|
124
|
+
action: "pr-dry-run",
|
|
125
|
+
message: dryRunMessage,
|
|
126
|
+
releaseCreated: false,
|
|
127
|
+
tagNames: [],
|
|
128
|
+
branch: plan.releaseBranchPrefix,
|
|
129
|
+
targets: plan.packages
|
|
130
|
+
?.filter((pkg) => pkg.nextVersion)
|
|
131
|
+
.map((pkg) => ({
|
|
132
|
+
tag: pkg.path === "."
|
|
133
|
+
? `v${pkg.nextVersion ?? ""}`
|
|
134
|
+
: `${pkg.path}-v${pkg.nextVersion ?? ""}`,
|
|
135
|
+
version: pkg.nextVersion ?? "",
|
|
136
|
+
})) ?? [
|
|
137
|
+
{
|
|
138
|
+
tag: `v${plan.nextVersion}`,
|
|
139
|
+
version: plan.nextVersion,
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
|
+
});
|
|
143
|
+
return 0;
|
|
144
|
+
}
|
|
145
|
+
console.log(dryRunMessage);
|
|
146
|
+
return 0;
|
|
147
|
+
}
|
|
81
148
|
const pr = (0, pr_js_1.prepareSimpleReleasePr)(process.cwd(), { logger });
|
|
82
149
|
(0, pr_js_1.pushReleaseBranch)(process.cwd(), pr.branch);
|
|
83
150
|
const reviewResult = await (0, pr_js_1.openOrUpdateSimpleReviewRequest)(process.cwd(), pr.branch, pr.title, pr.version, pr.previousVersion, pr.commits, pr.plan, { logger });
|
|
@@ -130,6 +197,15 @@ async function main() {
|
|
|
130
197
|
return 0;
|
|
131
198
|
}
|
|
132
199
|
if (command === "pr") {
|
|
200
|
+
if (flags["dry-run"]) {
|
|
201
|
+
const plan = (0, plan_js_1.createSimplePlan)();
|
|
202
|
+
if (!plan.nextVersion) {
|
|
203
|
+
console.log("No releasable commits found. Nothing to do.");
|
|
204
|
+
return 0;
|
|
205
|
+
}
|
|
206
|
+
console.log(`Dry run: would prepare release PR branch ${plan.releaseBranchPrefix} for ${plan.nextVersion}`);
|
|
207
|
+
return 0;
|
|
208
|
+
}
|
|
133
209
|
const pr = (0, pr_js_1.prepareSimpleReleasePr)(process.cwd(), { logger: console });
|
|
134
210
|
(0, pr_js_1.pushReleaseBranch)(process.cwd(), pr.branch);
|
|
135
211
|
const reviewResult = await (0, pr_js_1.openOrUpdateSimpleReviewRequest)(process.cwd(), pr.branch, pr.title, pr.version, pr.previousVersion, pr.commits, pr.plan);
|
|
@@ -139,18 +215,34 @@ async function main() {
|
|
|
139
215
|
return 0;
|
|
140
216
|
}
|
|
141
217
|
if (command === "release") {
|
|
218
|
+
if (flags["dry-run"]) {
|
|
219
|
+
const result = await (0, release_js_1.runSimpleReleaseDetailed)(process.cwd(), {
|
|
220
|
+
logger,
|
|
221
|
+
"dry-run": true,
|
|
222
|
+
});
|
|
223
|
+
if (result.action === "release-dry-run") {
|
|
224
|
+
console.log(result.message);
|
|
225
|
+
}
|
|
226
|
+
else if (result.action === "release-skipped") {
|
|
227
|
+
console.log(result.reason);
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
console.log(result.message);
|
|
231
|
+
}
|
|
232
|
+
return 0;
|
|
233
|
+
}
|
|
142
234
|
const message = await (0, release_js_1.runSimpleRelease)(process.cwd());
|
|
143
235
|
console.log(message);
|
|
144
236
|
return 0;
|
|
145
237
|
}
|
|
146
238
|
console.log("Usage: versionary <command>");
|
|
147
239
|
console.log("Commands:");
|
|
148
|
-
console.log(" run [--json] Auto-dispatch release PR/update or release publish by context");
|
|
240
|
+
console.log(" run [--json] [--dry-run] Auto-dispatch release PR/update or release publish by context");
|
|
149
241
|
console.log(" verify Validate config and basic repository shape");
|
|
150
242
|
console.log(" plan Print release plan (simple mode)");
|
|
151
243
|
console.log(" changelog [--write] Print or write changelog section");
|
|
152
|
-
console.log(" pr Prepare release PR commit and branch");
|
|
153
|
-
console.log(" release Publish release metadata for release commit context");
|
|
244
|
+
console.log(" pr [--dry-run] Prepare release PR commit and branch");
|
|
245
|
+
console.log(" release [--dry-run] Publish release metadata for release commit context");
|
|
154
246
|
return 1;
|
|
155
247
|
}
|
|
156
248
|
main()
|
|
@@ -41,6 +41,9 @@ function loadConfig(cwd = process.cwd()) {
|
|
|
41
41
|
if (!isRecord(parsed)) {
|
|
42
42
|
throw new Error("Invalid config: expected an object at the root.");
|
|
43
43
|
}
|
|
44
|
+
if (Object.hasOwn(parsed, "plugins")) {
|
|
45
|
+
throw new Error('The "plugins" config key is no longer supported. Versionary uses built-in integrations only.');
|
|
46
|
+
}
|
|
44
47
|
const validated = schema_js_1.configSchema.parse(parsed);
|
|
45
48
|
return {
|
|
46
49
|
path: found.path,
|
package/dist/config/schema.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export declare const configSchema: z.ZodObject<{
|
|
|
4
4
|
version: z.ZodLiteral<1>;
|
|
5
5
|
"review-mode": z.ZodOptional<z.ZodEnum<{
|
|
6
6
|
direct: "direct";
|
|
7
|
+
pr: "pr";
|
|
7
8
|
review: "review";
|
|
8
9
|
}>>;
|
|
9
10
|
"version-file": z.ZodOptional<z.ZodString>;
|
|
@@ -37,6 +38,5 @@ export declare const configSchema: z.ZodObject<{
|
|
|
37
38
|
pattern: z.ZodOptional<z.ZodString>;
|
|
38
39
|
}, z.core.$strip>>>;
|
|
39
40
|
}, z.core.$strict>>>;
|
|
40
|
-
plugins: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
41
41
|
}, z.core.$strict>;
|
|
42
42
|
export type ConfigSchema = z.infer<typeof configSchema>;
|
package/dist/config/schema.js
CHANGED
|
@@ -62,7 +62,7 @@ exports.configSchema = zod_1.z
|
|
|
62
62
|
.object({
|
|
63
63
|
$schema: zod_1.z.string().optional(),
|
|
64
64
|
version: zod_1.z.literal(1),
|
|
65
|
-
"review-mode": zod_1.z.enum(["direct", "review"]).optional(),
|
|
65
|
+
"review-mode": zod_1.z.enum(["direct", "pr", "review"]).optional(),
|
|
66
66
|
"version-file": zod_1.z.string().optional(),
|
|
67
67
|
"changelog-file": zod_1.z.string().optional(),
|
|
68
68
|
"release-branch": zod_1.z.string().optional(),
|
|
@@ -74,6 +74,5 @@ exports.configSchema = zod_1.z
|
|
|
74
74
|
"include-commit-authors": zod_1.z.boolean().optional(),
|
|
75
75
|
"release-type": zod_1.z.string().optional(),
|
|
76
76
|
packages: zod_1.z.record(zod_1.z.string().min(1), packageSchema).optional(),
|
|
77
|
-
plugins: zod_1.z.array(zod_1.z.string().min(1)).optional(),
|
|
78
77
|
})
|
|
79
78
|
.strict();
|
|
@@ -48,6 +48,7 @@ function groupCommitLines(commits, repoUrl) {
|
|
|
48
48
|
const breaking = [];
|
|
49
49
|
const features = [];
|
|
50
50
|
const fixes = [];
|
|
51
|
+
const reverts = [];
|
|
51
52
|
for (const commit of commits) {
|
|
52
53
|
const type = (0, commits_js_1.inferReleaseTypeFromParsedCommit)(commit);
|
|
53
54
|
if (!type) {
|
|
@@ -63,6 +64,10 @@ function groupCommitLines(commits, repoUrl) {
|
|
|
63
64
|
const line = `- ${label}${message} (${hashLabel})${referencesSuffix}`;
|
|
64
65
|
const commitType = (commit.type ?? "").toLowerCase();
|
|
65
66
|
const isBreaking = type === "major";
|
|
67
|
+
if (commit.isRevert) {
|
|
68
|
+
reverts.push(line);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
66
71
|
if (isBreaking) {
|
|
67
72
|
breaking.push(line);
|
|
68
73
|
}
|
|
@@ -76,7 +81,7 @@ function groupCommitLines(commits, repoUrl) {
|
|
|
76
81
|
fixes.push(line);
|
|
77
82
|
}
|
|
78
83
|
}
|
|
79
|
-
return { breaking, features, fixes };
|
|
84
|
+
return { breaking, features, fixes, reverts };
|
|
80
85
|
}
|
|
81
86
|
function renderSimpleReleaseNotes(input, options = {}) {
|
|
82
87
|
const repoUrl = (0, repo_url_js_1.resolveRepositoryWebBaseUrl)(input.cwd ?? process.cwd());
|
|
@@ -94,6 +99,9 @@ function renderSimpleReleaseNotes(input, options = {}) {
|
|
|
94
99
|
if (grouped.fixes.length > 0) {
|
|
95
100
|
sections.push("### Bug Fixes", ...grouped.fixes, "");
|
|
96
101
|
}
|
|
102
|
+
if (grouped.reverts.length > 0) {
|
|
103
|
+
sections.push("### Reverts", ...grouped.reverts, "");
|
|
104
|
+
}
|
|
97
105
|
const lines = [header, "", ...sections];
|
|
98
106
|
if (options.includeFooter) {
|
|
99
107
|
lines.push("\n---\n\nThis PR was generated by [Versionary](https://github.com/jolars/versionary).");
|
|
@@ -127,6 +135,9 @@ function renderPackageChangelogSection(input) {
|
|
|
127
135
|
if (grouped.fixes.length > 0) {
|
|
128
136
|
sections.push("### Bug Fixes", ...grouped.fixes, "");
|
|
129
137
|
}
|
|
138
|
+
if (grouped.reverts.length > 0) {
|
|
139
|
+
sections.push("### Reverts", ...grouped.reverts, "");
|
|
140
|
+
}
|
|
130
141
|
return [header, "", ...sections].join("\n");
|
|
131
142
|
}
|
|
132
143
|
function prependChangelog(cwd, changelogFile, section) {
|
|
@@ -351,12 +351,12 @@ function getParsedCommitsForPath(cwd = process.cwd(), baselineSha, packagePath =
|
|
|
351
351
|
return readGitLogFull(cwd, range, [normalizedPackagePath, ...excludes]);
|
|
352
352
|
}
|
|
353
353
|
function inferReleaseTypeFromParsedCommit(commit) {
|
|
354
|
-
if (commit.isRevert) {
|
|
355
|
-
return null;
|
|
356
|
-
}
|
|
357
354
|
if (commit.isBreaking) {
|
|
358
355
|
return "major";
|
|
359
356
|
}
|
|
357
|
+
if (commit.isRevert) {
|
|
358
|
+
return "patch";
|
|
359
|
+
}
|
|
360
360
|
const type = commit.type?.toLowerCase() ?? "";
|
|
361
361
|
if (type === "feat") {
|
|
362
362
|
return "minor";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { VersionaryPluginRuntime } from "../../types/plugins.js";
|
|
2
|
-
export declare function loadRuntimePlugins(
|
|
2
|
+
export declare function loadRuntimePlugins(): VersionaryPluginRuntime[];
|
|
@@ -1,24 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.loadRuntimePlugins = loadRuntimePlugins;
|
|
4
|
-
const load_config_js_1 = require("../../config/load-config.js");
|
|
5
4
|
const plugin_js_1 = require("./github/plugin.js");
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
};
|
|
9
|
-
function loadRuntimePlugins(cwd = process.cwd()) {
|
|
10
|
-
const loaded = (0, load_config_js_1.loadConfig)(cwd);
|
|
11
|
-
const configured = loaded.config.plugins ?? [];
|
|
12
|
-
const plugins = [(0, plugin_js_1.createGitHubPlugin)()];
|
|
13
|
-
const seen = new Set(plugins.map((plugin) => plugin.name));
|
|
14
|
-
for (const name of configured) {
|
|
15
|
-
const factory = BUILTIN_PLUGIN_FACTORIES[name];
|
|
16
|
-
if (!factory || seen.has(name)) {
|
|
17
|
-
continue;
|
|
18
|
-
}
|
|
19
|
-
const plugin = factory();
|
|
20
|
-
plugins.push(plugin);
|
|
21
|
-
seen.add(plugin.name);
|
|
22
|
-
}
|
|
23
|
-
return plugins;
|
|
5
|
+
function loadRuntimePlugins() {
|
|
6
|
+
return [(0, plugin_js_1.createGitHubPlugin)()];
|
|
24
7
|
}
|
package/dist/types/config.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ export interface VersionaryPackage {
|
|
|
15
15
|
}
|
|
16
16
|
export interface VersionaryConfig {
|
|
17
17
|
version: 1;
|
|
18
|
-
"review-mode"?: "direct" | "review";
|
|
18
|
+
"review-mode"?: "direct" | "pr" | "review";
|
|
19
19
|
"version-file"?: string;
|
|
20
20
|
"changelog-file"?: string;
|
|
21
21
|
"release-branch"?: string;
|
|
@@ -27,7 +27,6 @@ export interface VersionaryConfig {
|
|
|
27
27
|
"include-commit-authors"?: boolean;
|
|
28
28
|
"release-type"?: string;
|
|
29
29
|
packages?: Record<string, VersionaryPackage>;
|
|
30
|
-
plugins?: string[];
|
|
31
30
|
}
|
|
32
31
|
export interface LoadedConfig {
|
|
33
32
|
path: string;
|
|
@@ -16,6 +16,7 @@ function verifyProject(cwd = process.cwd()) {
|
|
|
16
16
|
name: "config-load",
|
|
17
17
|
ok: true,
|
|
18
18
|
details: `Loaded ${node_path_1.default.basename(config.path)} (${config.format})`,
|
|
19
|
+
category: "config",
|
|
19
20
|
});
|
|
20
21
|
const strategy = (0, resolve_js_1.resolveVersionStrategy)(config.config);
|
|
21
22
|
const versionFile = strategy.getVersionFile(config.config);
|
|
@@ -24,6 +25,10 @@ function verifyProject(cwd = process.cwd()) {
|
|
|
24
25
|
name: `version-file:${versionFile}`,
|
|
25
26
|
ok: exists,
|
|
26
27
|
details: exists ? "Version file exists" : `Missing ${versionFile}`,
|
|
28
|
+
category: "version-files",
|
|
29
|
+
remediation: exists
|
|
30
|
+
? undefined
|
|
31
|
+
: `Create ${versionFile} or set "version-file" to the correct path for your release strategy.`,
|
|
27
32
|
});
|
|
28
33
|
if (config.config.packages) {
|
|
29
34
|
for (const [pkgPathRaw, packageConfig] of Object.entries(config.config.packages)) {
|
|
@@ -33,6 +38,10 @@ function verifyProject(cwd = process.cwd()) {
|
|
|
33
38
|
name: `package-path:${pkgPathRaw}`,
|
|
34
39
|
ok: exists,
|
|
35
40
|
details: exists ? "Path exists" : `Missing path: ${pkgPathRaw}`,
|
|
41
|
+
category: "paths",
|
|
42
|
+
remediation: exists
|
|
43
|
+
? undefined
|
|
44
|
+
: `Create ${pkgPathRaw} or remove/rename this entry under "packages" in versionary config.`,
|
|
36
45
|
});
|
|
37
46
|
if (exists) {
|
|
38
47
|
const packageContext = (0, package_context_js_1.resolvePackageStrategyContext)(config.config, pkgPathRaw, packageConfig);
|
|
@@ -44,6 +53,10 @@ function verifyProject(cwd = process.cwd()) {
|
|
|
44
53
|
details: packageVersionExists
|
|
45
54
|
? "Version file exists"
|
|
46
55
|
: `Missing ${packageVersionFile}`,
|
|
56
|
+
category: "version-files",
|
|
57
|
+
remediation: packageVersionExists
|
|
58
|
+
? undefined
|
|
59
|
+
: `Create ${packageVersionFile} or adjust package release settings ("release-type"/"version-file") for ${pkgPathRaw}.`,
|
|
47
60
|
});
|
|
48
61
|
}
|
|
49
62
|
}
|