versionary 0.2.0 → 0.4.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.
Files changed (73) hide show
  1. package/README.md +231 -12
  2. package/dist/app/release/artifact-rules.d.ts +3 -0
  3. package/dist/app/release/artifact-rules.js +186 -0
  4. package/dist/app/release/pr.d.ts +23 -0
  5. package/dist/{simple → app/release}/pr.js +90 -79
  6. package/dist/app/release/recovery.d.ts +20 -0
  7. package/dist/app/release/recovery.js +101 -0
  8. package/dist/app/release/release.d.ts +19 -0
  9. package/dist/app/release/release.js +111 -0
  10. package/dist/app/release/state.d.ts +9 -0
  11. package/dist/app/release/state.js +93 -0
  12. package/dist/app/release/verify.d.ts +2 -0
  13. package/dist/app/release/verify.js +5 -0
  14. package/dist/cli/index.js +71 -15
  15. package/dist/config/load-config.js +2 -12
  16. package/dist/config/schema.d.ts +1 -0
  17. package/dist/config/schema.js +34 -1
  18. package/dist/domain/release/changelog.d.ts +12 -0
  19. package/dist/domain/release/changelog.js +123 -0
  20. package/dist/{simple → domain/release}/plan.d.ts +4 -3
  21. package/dist/{simple → domain/release}/plan.js +32 -15
  22. package/dist/domain/release/semver.d.ts +15 -0
  23. package/dist/domain/release/semver.js +111 -0
  24. package/dist/domain/strategy/node.d.ts +2 -0
  25. package/dist/domain/strategy/node.js +67 -0
  26. package/dist/domain/strategy/r.d.ts +2 -0
  27. package/dist/domain/strategy/r.js +46 -0
  28. package/dist/domain/strategy/resolve.d.ts +3 -0
  29. package/dist/domain/strategy/resolve.js +19 -0
  30. package/dist/domain/strategy/rust.d.ts +2 -0
  31. package/dist/domain/strategy/rust.js +369 -0
  32. package/dist/domain/strategy/simple.d.ts +2 -0
  33. package/dist/domain/strategy/simple.js +28 -0
  34. package/dist/domain/strategy/types.d.ts +7 -0
  35. package/dist/domain/strategy/types.js +2 -0
  36. package/dist/index.d.ts +5 -3
  37. package/dist/index.js +5 -1
  38. package/dist/infra/git/commits.d.ts +65 -0
  39. package/dist/infra/git/commits.js +436 -0
  40. package/dist/infra/scm/github/plugin.d.ts +1 -0
  41. package/dist/infra/scm/github/plugin.js +5 -0
  42. package/dist/infra/scm/runtime.d.ts +2 -0
  43. package/dist/infra/scm/runtime.js +24 -0
  44. package/dist/infra/scm/types.d.ts +1 -0
  45. package/dist/infra/scm/types.js +2 -0
  46. package/dist/plugins/runtime.d.ts +1 -2
  47. package/dist/plugins/runtime.js +3 -22
  48. package/dist/scm/github-plugin.js +133 -40
  49. package/dist/strategies/node.d.ts +1 -0
  50. package/dist/strategies/node.js +5 -0
  51. package/dist/strategies/resolve.d.ts +1 -0
  52. package/dist/strategies/resolve.js +5 -0
  53. package/dist/strategies/simple.d.ts +1 -0
  54. package/dist/strategies/simple.js +5 -0
  55. package/dist/strategies/types.d.ts +1 -0
  56. package/dist/strategies/types.js +2 -0
  57. package/dist/types/config.d.ts +1 -0
  58. package/dist/types/plugins.d.ts +1 -0
  59. package/dist/verify/verify-project.js +3 -1
  60. package/package.json +3 -2
  61. package/dist/simple/changelog.d.ts +0 -3
  62. package/dist/simple/changelog.js +0 -46
  63. package/dist/simple/git.d.ts +0 -10
  64. package/dist/simple/git.js +0 -114
  65. package/dist/simple/pr.d.ts +0 -15
  66. package/dist/simple/release.d.ts +0 -1
  67. package/dist/simple/release.js +0 -77
  68. package/dist/simple/semver.d.ts +0 -8
  69. package/dist/simple/semver.js +0 -25
  70. package/dist/simple/state.d.ts +0 -3
  71. package/dist/simple/state.js +0 -47
  72. /package/dist/{simple → infra/git}/repo-url.d.ts +0 -0
  73. /package/dist/{simple → infra/git}/repo-url.js +0 -0
@@ -9,16 +9,15 @@ exports.renderSimpleReviewRequestBody = renderSimpleReviewRequestBody;
9
9
  exports.openOrUpdateSimpleReviewRequest = openOrUpdateSimpleReviewRequest;
10
10
  exports.pushReleaseBranch = pushReleaseBranch;
11
11
  exports.isReleaseCommitMessage = isReleaseCommitMessage;
12
- const node_fs_1 = __importDefault(require("node:fs"));
13
- const node_path_1 = __importDefault(require("node:path"));
14
12
  const node_child_process_1 = require("node:child_process");
15
- const load_config_js_1 = require("../config/load-config.js");
16
- const capabilities_js_1 = require("../plugins/capabilities.js");
17
- const runtime_js_1 = require("../plugins/runtime.js");
18
- const plan_js_1 = require("./plan.js");
19
- const git_js_1 = require("./git.js");
20
- const repo_url_js_1 = require("./repo-url.js");
21
- const changelog_js_1 = require("./changelog.js");
13
+ const node_path_1 = __importDefault(require("node:path"));
14
+ const load_config_js_1 = require("../../config/load-config.js");
15
+ const changelog_js_1 = require("../../domain/release/changelog.js");
16
+ const plan_js_1 = require("../../domain/release/plan.js");
17
+ const resolve_js_1 = require("../../domain/strategy/resolve.js");
18
+ const capabilities_js_1 = require("../../plugins/capabilities.js");
19
+ const runtime_js_1 = require("../../plugins/runtime.js");
20
+ const artifact_rules_js_1 = require("./artifact-rules.js");
22
21
  const state_js_1 = require("./state.js");
23
22
  const SAFE_DIRTY_FILES = new Set([
24
23
  "pnpm-lock.yaml",
@@ -57,106 +56,118 @@ function splitSafeDirtyFiles(files) {
57
56
  }
58
57
  return { ignored, blocking };
59
58
  }
60
- function ensureCleanWorktree(cwd) {
59
+ function ensureCleanWorktree(cwd, logger) {
61
60
  const dirtyFiles = listTrackedDirtyFiles(cwd);
62
61
  const { ignored, blocking } = splitSafeDirtyFiles(dirtyFiles);
63
62
  if (blocking.length > 0) {
64
63
  throw new Error(`Working tree has tracked modifications before versionary pr:\n${blocking.join("\n")}\nCommit/stash tracked changes first.`);
65
64
  }
66
65
  if (ignored.length > 0) {
67
- console.warn(`Ignoring safe tracked changes before versionary pr:\n${ignored.join("\n")}`);
66
+ logger?.warn(`Ignoring safe tracked changes before versionary pr:\n${ignored.join("\n")}`);
68
67
  }
69
68
  }
70
- function prepareSimpleReleasePr(cwd = process.cwd()) {
69
+ function prepareSimpleReleasePr(cwd = process.cwd(), options = {}) {
71
70
  const plan = (0, plan_js_1.createSimplePlan)(cwd);
71
+ const loaded = (0, load_config_js_1.loadConfig)(cwd);
72
+ const strategy = (0, resolve_js_1.resolveVersionStrategy)(loaded.config);
72
73
  if (!plan.nextVersion) {
73
74
  throw new Error("No releasable commits found. Nothing to open a release PR for.");
74
75
  }
75
- ensureCleanWorktree(cwd);
76
- const versionPath = node_path_1.default.join(cwd, plan.versionFile);
77
- node_fs_1.default.writeFileSync(versionPath, `${plan.nextVersion}\n`, "utf8");
76
+ ensureCleanWorktree(cwd, options.logger);
77
+ const updatedVersionFiles = strategy.writeVersion(cwd, loaded.config, plan.nextVersion);
78
+ const updatedArtifactFiles = (0, artifact_rules_js_1.applyConfiguredArtifactRules)(cwd, loaded.config, plan);
78
79
  const section = (0, changelog_js_1.renderSimpleChangelog)(plan);
79
80
  (0, changelog_js_1.prependChangelog)(cwd, plan.changelogFile, section);
80
81
  const branch = plan.releaseBranchPrefix;
81
82
  const title = `chore(release): v${plan.nextVersion}`;
82
- (0, node_child_process_1.execFileSync)("git", ["checkout", "-B", branch], { cwd, stdio: ["ignore", "pipe", "ignore"] });
83
- (0, node_child_process_1.execFileSync)("git", ["add", plan.versionFile, plan.changelogFile], {
83
+ (0, node_child_process_1.execFileSync)("git", ["checkout", "-B", branch], {
84
+ cwd,
85
+ stdio: ["ignore", "pipe", "ignore"],
86
+ });
87
+ const filesToAdd = [
88
+ ...new Set([
89
+ ...updatedVersionFiles,
90
+ ...updatedArtifactFiles,
91
+ plan.changelogFile,
92
+ ]),
93
+ ];
94
+ (0, node_child_process_1.execFileSync)("git", ["add", ...filesToAdd], {
84
95
  cwd,
85
96
  stdio: ["ignore", "pipe", "ignore"],
86
97
  });
87
- (0, node_child_process_1.execFileSync)("git", ["commit", "-m", title], { cwd, stdio: ["ignore", "pipe", "ignore"] });
88
- (0, state_js_1.writeBaselineSha)(cwd);
98
+ (0, node_child_process_1.execFileSync)("git", ["commit", "-m", title], {
99
+ cwd,
100
+ stdio: ["ignore", "pipe", "ignore"],
101
+ });
102
+ const releaseTargets = plan.packages
103
+ ? plan.packages
104
+ .filter((pkg) => pkg.nextVersion)
105
+ .map((pkg) => ({
106
+ path: pkg.path,
107
+ version: pkg.nextVersion ?? "",
108
+ tag: pkg.path === "."
109
+ ? `v${pkg.nextVersion ?? ""}`
110
+ : `${pkg.path.replaceAll("/", "-")}-v${pkg.nextVersion ?? ""}`,
111
+ }))
112
+ : [
113
+ {
114
+ path: ".",
115
+ version: plan.nextVersion,
116
+ tag: `v${plan.nextVersion}`,
117
+ },
118
+ ];
119
+ (0, state_js_1.writeBaselineSha)(cwd, undefined, releaseTargets);
89
120
  (0, node_child_process_1.execFileSync)("git", ["add", (0, state_js_1.getBaselineStatePath)(cwd)], {
90
121
  cwd,
91
122
  stdio: ["ignore", "pipe", "ignore"],
92
123
  });
93
- (0, node_child_process_1.execFileSync)("git", ["commit", "--amend", "--no-edit"], { cwd, stdio: ["ignore", "pipe", "ignore"] });
124
+ (0, node_child_process_1.execFileSync)("git", ["commit", "--amend", "--no-edit"], {
125
+ cwd,
126
+ stdio: ["ignore", "pipe", "ignore"],
127
+ });
94
128
  return {
95
129
  branch,
96
130
  title,
97
131
  version: plan.nextVersion,
132
+ previousVersion: plan.currentVersion,
98
133
  commits: plan.commits,
134
+ plan,
99
135
  };
100
136
  }
101
- function formatCommitMessage(subject) {
102
- const conventional = subject.match(/^[a-z]+(?:\(([^)]+)\))?!?:\s+(.+)$/iu);
103
- if (!conventional) {
104
- return { label: "", message: subject };
137
+ function renderSimpleReviewRequestBody(version, previousVersion, commits, plan = null, cwd = process.cwd()) {
138
+ if (plan?.packages && plan.packages.length > 1) {
139
+ const sections = plan.packages
140
+ .filter((pkg) => pkg.nextVersion)
141
+ .map((pkg) => {
142
+ const notes = (0, changelog_js_1.renderSimpleReleaseNotes)({
143
+ currentVersion: pkg.currentVersion,
144
+ nextVersion: pkg.nextVersion ?? "",
145
+ commits: pkg.commits,
146
+ cwd,
147
+ }, { includeFooter: false });
148
+ const linkedHeader = notes.match(/^##\s+\[([^\]]+)\]\(([^)]+)\)\s+\(([^)]+)\)/u);
149
+ if (linkedHeader) {
150
+ const [, , compareUrl, date] = linkedHeader;
151
+ return notes.replace(/^##\s+\[[^\]]+\]\([^)]+\)\s+\([^)]+\)/u, `## [${pkg.path}: ${pkg.nextVersion ?? ""}](${compareUrl}) (${date})`);
152
+ }
153
+ const plainHeader = notes.match(/^##\s+([^\s]+)\s+\(([^)]+)\)/u);
154
+ if (plainHeader) {
155
+ const [, , date] = plainHeader;
156
+ return notes.replace(/^##\s+[^\s]+\s+\([^)]+\)/u, `## ${pkg.path}: ${pkg.nextVersion ?? ""} (${date})`);
157
+ }
158
+ return notes;
159
+ })
160
+ .join("\n\n");
161
+ return `${sections}\n\nThis PR was generated by Versionary.`;
105
162
  }
106
- const scope = conventional[1]?.trim();
107
- const message = conventional[2]?.trim() ?? subject;
108
- const label = scope ? `**${scope}:** ` : "";
109
- return { label, message };
110
- }
111
- function renderSimpleReviewRequestBody(version, commits, cwd = process.cwd()) {
112
- const breaking = [];
113
- const features = [];
114
- const fixes = [];
115
- const commitBaseUrl = (0, repo_url_js_1.resolveRepositoryWebBaseUrl)(cwd);
116
- for (const commit of commits) {
117
- const subject = commit.subject;
118
- const { label, message } = formatCommitMessage(subject);
119
- const hash = commit.hash.slice(0, 7);
120
- const hashLabel = commitBaseUrl ? `[\`${hash}\`](${commitBaseUrl}/commit/${commit.hash})` : `\`${hash}\``;
121
- const type = (0, git_js_1.inferReleaseTypeFromSubject)(subject);
122
- if (!type) {
123
- continue;
124
- }
125
- const item = `- ${label}${message} (${hashLabel})`;
126
- if (type === "major") {
127
- breaking.push(item);
128
- continue;
129
- }
130
- if (type === "minor") {
131
- features.push(item);
132
- continue;
133
- }
134
- fixes.push(item);
135
- }
136
- const sections = [];
137
- if (breaking.length > 0) {
138
- sections.push("### Breaking changes", ...breaking, "");
139
- }
140
- if (features.length > 0) {
141
- sections.push("### Features", ...features, "");
142
- }
143
- if (fixes.length > 0) {
144
- sections.push("### Fixes", ...fixes, "");
145
- }
146
- return [
147
- ":robot: I have created a release PR for this repository.",
148
- "",
149
- `## Version`,
150
- "",
151
- `This PR prepares **v${version}**.`,
152
- "",
153
- "## Release notes preview",
154
- "",
155
- ...sections,
156
- "This PR was generated by Versionary.",
157
- ].join("\n");
163
+ return (0, changelog_js_1.renderSimpleReleaseNotes)({
164
+ currentVersion: previousVersion,
165
+ nextVersion: version,
166
+ commits,
167
+ cwd,
168
+ }, { includeFooter: true });
158
169
  }
159
- async function openOrUpdateSimpleReviewRequest(cwd, branch, title, version, commits) {
170
+ async function openOrUpdateSimpleReviewRequest(cwd, branch, title, version, previousVersion, commits, plan = null, options = {}) {
160
171
  const loaded = (0, load_config_js_1.loadConfig)(cwd);
161
172
  const releaseFlow = loaded.config["review-mode"] ?? "direct";
162
173
  if (releaseFlow !== "review") {
@@ -175,11 +186,11 @@ async function openOrUpdateSimpleReviewRequest(cwd, branch, title, version, comm
175
186
  baseBranch: process.env.VERSIONARY_BASE_BRANCH ?? "main",
176
187
  headBranch: branch,
177
188
  title,
178
- body: renderSimpleReviewRequestBody(version, commits, cwd),
189
+ body: renderSimpleReviewRequestBody(version, previousVersion, commits, plan, cwd),
179
190
  labels: ["release"],
180
191
  }, {
181
192
  cwd,
182
- logger: console,
193
+ logger: options.logger,
183
194
  });
184
195
  return result.url;
185
196
  }
@@ -0,0 +1,20 @@
1
+ import type { VersionaryScmReleaseMetadataResult } from "../../types/plugins.js";
2
+ export type TagStepResult = "created" | "exists";
3
+ export interface ReleaseTargetInput {
4
+ tag: string;
5
+ version: string;
6
+ notes: string;
7
+ }
8
+ export interface ReleaseExecutionContext {
9
+ createReleaseMetadata: (input: ReleaseTargetInput) => Promise<VersionaryScmReleaseMetadataResult>;
10
+ logger?: {
11
+ info: (message: string) => void;
12
+ };
13
+ }
14
+ export interface ReleaseTargetOutcome {
15
+ tag: string;
16
+ tagStatus: TagStepResult;
17
+ metadataStatus: "created" | "exists";
18
+ url: string;
19
+ }
20
+ export declare function executeIdempotentReleaseTarget(cwd: string, target: ReleaseTargetInput, context: ReleaseExecutionContext): Promise<ReleaseTargetOutcome>;
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.executeIdempotentReleaseTarget = executeIdempotentReleaseTarget;
4
+ const node_child_process_1 = require("node:child_process");
5
+ function hasRemoteTag(cwd, tag) {
6
+ try {
7
+ const output = (0, node_child_process_1.execFileSync)("git", ["ls-remote", "--tags", "origin", `refs/tags/${tag}`], {
8
+ cwd,
9
+ encoding: "utf8",
10
+ stdio: ["ignore", "pipe", "ignore"],
11
+ }).trim();
12
+ return output.length > 0;
13
+ }
14
+ catch {
15
+ return false;
16
+ }
17
+ }
18
+ function readRemoteTagSha(cwd, tag) {
19
+ try {
20
+ const output = (0, node_child_process_1.execFileSync)("git", ["ls-remote", "--tags", "origin", `refs/tags/${tag}`], {
21
+ cwd,
22
+ encoding: "utf8",
23
+ stdio: ["ignore", "pipe", "ignore"],
24
+ }).trim();
25
+ if (!output) {
26
+ return null;
27
+ }
28
+ const [sha] = output.split(/\s+/u);
29
+ return sha ?? null;
30
+ }
31
+ catch {
32
+ return null;
33
+ }
34
+ }
35
+ function createTagWithRecovery(cwd, tag) {
36
+ const localTag = (0, node_child_process_1.execFileSync)("git", ["tag", "--list", tag], {
37
+ cwd,
38
+ encoding: "utf8",
39
+ stdio: ["ignore", "pipe", "ignore"],
40
+ }).trim();
41
+ if (localTag.length > 0) {
42
+ const localSha = (0, node_child_process_1.execFileSync)("git", ["rev-parse", `refs/tags/${tag}`], {
43
+ cwd,
44
+ encoding: "utf8",
45
+ stdio: ["ignore", "pipe", "ignore"],
46
+ }).trim();
47
+ const remoteSha = readRemoteTagSha(cwd, tag);
48
+ if (remoteSha && remoteSha !== localSha) {
49
+ throw new Error(`Tag drift detected for "${tag}": local tag (${localSha.slice(0, 7)}) differs from remote (${remoteSha.slice(0, 7)}). Resolve the tag mismatch before retrying release.`);
50
+ }
51
+ return "exists";
52
+ }
53
+ try {
54
+ (0, node_child_process_1.execFileSync)("git", ["tag", tag], {
55
+ cwd,
56
+ stdio: ["ignore", "pipe", "ignore"],
57
+ });
58
+ }
59
+ catch {
60
+ throw new Error(`Failed creating local tag "${tag}". Ensure repository is writable and retry.`);
61
+ }
62
+ try {
63
+ (0, node_child_process_1.execFileSync)("git", ["push", "origin", tag], {
64
+ cwd,
65
+ stdio: ["ignore", "pipe", "ignore"],
66
+ });
67
+ return "created";
68
+ }
69
+ catch {
70
+ if (hasRemoteTag(cwd, tag)) {
71
+ const localSha = (0, node_child_process_1.execFileSync)("git", ["rev-parse", `refs/tags/${tag}`], {
72
+ cwd,
73
+ encoding: "utf8",
74
+ stdio: ["ignore", "pipe", "ignore"],
75
+ }).trim();
76
+ const remoteSha = readRemoteTagSha(cwd, tag);
77
+ if (remoteSha && remoteSha !== localSha) {
78
+ throw new Error(`Tag drift detected for "${tag}": local tag (${localSha.slice(0, 7)}) differs from remote (${remoteSha.slice(0, 7)}). Resolve the tag mismatch before retrying release.`);
79
+ }
80
+ return "exists";
81
+ }
82
+ throw new Error(`Failed pushing tag "${tag}" to origin. Check push permissions and remote connectivity, then retry.`);
83
+ }
84
+ }
85
+ async function executeIdempotentReleaseTarget(cwd, target, context) {
86
+ const tagStatus = createTagWithRecovery(cwd, target.tag);
87
+ const metadata = await context.createReleaseMetadata(target);
88
+ const metadataStatus = metadata.status ?? "created";
89
+ if (tagStatus === "exists" && metadataStatus === "created") {
90
+ context.logger?.info(`Recovered drift for ${target.tag}: tag already existed and release metadata has now been created.`);
91
+ }
92
+ if (!metadata.url) {
93
+ throw new Error(`Release metadata for "${target.tag}" did not return a URL. Verify SCM API permissions and retry.`);
94
+ }
95
+ return {
96
+ tag: target.tag,
97
+ tagStatus,
98
+ metadataStatus,
99
+ url: metadata.url,
100
+ };
101
+ }
@@ -0,0 +1,19 @@
1
+ import type { VersionaryPluginContext } from "../../types/plugins.js";
2
+ export declare function runSimpleRelease(cwd?: string): Promise<string>;
3
+ export type SimpleRunReleaseResult = {
4
+ action: "release-skipped";
5
+ reason: string;
6
+ } | {
7
+ action: "release-published";
8
+ message: string;
9
+ releases: {
10
+ tag: string;
11
+ url: string;
12
+ tagStatus: "created" | "exists";
13
+ metadataStatus: "created" | "exists";
14
+ }[];
15
+ };
16
+ export interface RunSimpleReleaseOptions {
17
+ logger?: VersionaryPluginContext["logger"];
18
+ }
19
+ export declare function runSimpleReleaseDetailed(cwd?: string, options?: RunSimpleReleaseOptions): Promise<SimpleRunReleaseResult>;
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.runSimpleRelease = runSimpleRelease;
7
+ exports.runSimpleReleaseDetailed = runSimpleReleaseDetailed;
8
+ const node_child_process_1 = require("node:child_process");
9
+ const node_fs_1 = __importDefault(require("node:fs"));
10
+ const node_path_1 = __importDefault(require("node:path"));
11
+ const load_config_js_1 = require("../../config/load-config.js");
12
+ const resolve_js_1 = require("../../domain/strategy/resolve.js");
13
+ const capabilities_js_1 = require("../../plugins/capabilities.js");
14
+ const runtime_js_1 = require("../../plugins/runtime.js");
15
+ const pr_js_1 = require("./pr.js");
16
+ const recovery_js_1 = require("./recovery.js");
17
+ const state_js_1 = require("./state.js");
18
+ function getHeadCommitSubject(cwd) {
19
+ return (0, node_child_process_1.execFileSync)("git", ["log", "-1", "--pretty=%s"], {
20
+ cwd,
21
+ encoding: "utf8",
22
+ stdio: ["ignore", "pipe", "ignore"],
23
+ }).trim();
24
+ }
25
+ function readReleaseNotes(cwd, version, changelogFile) {
26
+ const changelogPath = node_path_1.default.join(cwd, changelogFile);
27
+ if (!node_fs_1.default.existsSync(changelogPath)) {
28
+ return `Automated release for v${version}`;
29
+ }
30
+ const content = node_fs_1.default.readFileSync(changelogPath, "utf8");
31
+ const lines = content.split("\n");
32
+ const start = lines.findIndex((line) => line.startsWith(`## ${version} -`) || line.startsWith(`## [${version}](`));
33
+ if (start < 0) {
34
+ return `Automated release for v${version}`;
35
+ }
36
+ let end = lines.length;
37
+ for (let idx = start + 1; idx < lines.length; idx += 1) {
38
+ if (lines[idx]?.startsWith("## ")) {
39
+ end = idx;
40
+ break;
41
+ }
42
+ }
43
+ const notes = lines
44
+ .slice(start + 1, end)
45
+ .join("\n")
46
+ .trim();
47
+ return notes.length > 0 ? notes : `Automated release for v${version}`;
48
+ }
49
+ async function runSimpleRelease(cwd = process.cwd()) {
50
+ const result = await runSimpleReleaseDetailed(cwd, { logger: console });
51
+ if (result.action === "release-skipped") {
52
+ return result.reason;
53
+ }
54
+ return result.message;
55
+ }
56
+ async function runSimpleReleaseDetailed(cwd = process.cwd(), options = {}) {
57
+ const subject = getHeadCommitSubject(cwd);
58
+ if (!(0, pr_js_1.isReleaseCommitMessage)(subject)) {
59
+ return {
60
+ action: "release-skipped",
61
+ reason: "No release commit context detected; skipping release stage.",
62
+ };
63
+ }
64
+ const loaded = (0, load_config_js_1.loadConfig)(cwd);
65
+ const strategy = (0, resolve_js_1.resolveVersionStrategy)(loaded.config);
66
+ const changelogFile = loaded.config["changelog-file"] ?? "CHANGELOG.md";
67
+ const version = strategy.readVersion(cwd, loaded.config);
68
+ 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 releaseTargets = (0, state_js_1.readReleaseTargets)(cwd);
79
+ const targets = releaseTargets.length > 0
80
+ ? releaseTargets
81
+ : [
82
+ {
83
+ path: ".",
84
+ version,
85
+ tag: defaultTag,
86
+ },
87
+ ];
88
+ const releases = [];
89
+ for (const target of targets) {
90
+ const outcome = await (0, recovery_js_1.executeIdempotentReleaseTarget)(cwd, {
91
+ tag: target.tag,
92
+ version: target.version,
93
+ notes: readReleaseNotes(cwd, target.version, changelogFile),
94
+ }, {
95
+ createReleaseMetadata: (input) => plugin.createReleaseMetadata(input, { cwd, logger: options.logger }),
96
+ logger: options.logger,
97
+ });
98
+ releases.push({
99
+ tag: outcome.tag,
100
+ url: outcome.url,
101
+ tagStatus: outcome.tagStatus,
102
+ metadataStatus: outcome.metadataStatus,
103
+ });
104
+ }
105
+ const published = releases.map((outcome) => `${outcome.tag}: ${outcome.url} (tag=${outcome.tagStatus}, metadata=${outcome.metadataStatus})`);
106
+ return {
107
+ action: "release-published",
108
+ releases,
109
+ message: `Published releases ${published.join(", ")}`,
110
+ };
111
+ }
@@ -0,0 +1,9 @@
1
+ export interface ReleaseTargetState {
2
+ path: string;
3
+ version: string;
4
+ tag: string;
5
+ }
6
+ export declare function getBaselineStatePath(cwd: string): string;
7
+ export declare function readBaselineSha(cwd?: string): string | null;
8
+ export declare function readReleaseTargets(cwd?: string): ReleaseTargetState[];
9
+ export declare function writeBaselineSha(cwd?: string, sha?: string, releaseTargets?: ReleaseTargetState[]): void;
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getBaselineStatePath = getBaselineStatePath;
7
+ exports.readBaselineSha = readBaselineSha;
8
+ exports.readReleaseTargets = readReleaseTargets;
9
+ exports.writeBaselineSha = writeBaselineSha;
10
+ const node_child_process_1 = require("node:child_process");
11
+ const node_fs_1 = __importDefault(require("node:fs"));
12
+ const node_path_1 = __importDefault(require("node:path"));
13
+ const load_config_js_1 = require("../../config/load-config.js");
14
+ function parseStateFile(raw, filePath) {
15
+ const parsed = JSON.parse(raw);
16
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
17
+ throw new Error(`Invalid release manifest at ${filePath}: expected an object.`);
18
+ }
19
+ const manifest = parsed;
20
+ if (manifest.manifestVersion !== undefined &&
21
+ manifest.manifestVersion !== 1) {
22
+ throw new Error(`Unsupported manifestVersion in ${filePath}: ${String(manifest.manifestVersion)}.`);
23
+ }
24
+ if (manifest.baselineSha !== undefined &&
25
+ typeof manifest.baselineSha !== "string") {
26
+ throw new Error(`Invalid release manifest at ${filePath}: baselineSha must be a string.`);
27
+ }
28
+ if (manifest.releaseTargets !== undefined &&
29
+ !Array.isArray(manifest.releaseTargets)) {
30
+ throw new Error(`Invalid release manifest at ${filePath}: releaseTargets must be an array.`);
31
+ }
32
+ if (Array.isArray(manifest.releaseTargets)) {
33
+ for (const target of manifest.releaseTargets) {
34
+ if (!target || typeof target !== "object" || Array.isArray(target)) {
35
+ throw new Error(`Invalid release manifest at ${filePath}: each release target must be an object.`);
36
+ }
37
+ const record = target;
38
+ if (typeof record.path !== "string" ||
39
+ typeof record.version !== "string" ||
40
+ typeof record.tag !== "string") {
41
+ throw new Error(`Invalid release manifest at ${filePath}: releaseTargets must contain string path, version, and tag.`);
42
+ }
43
+ }
44
+ }
45
+ return manifest;
46
+ }
47
+ function getBaselineStatePath(cwd) {
48
+ const loaded = (0, load_config_js_1.loadConfig)(cwd);
49
+ const configured = loaded.config["baseline-file"];
50
+ if (configured) {
51
+ return node_path_1.default.join(cwd, configured);
52
+ }
53
+ const preferred = node_path_1.default.join(cwd, ".versionary-manifest.json");
54
+ if (node_fs_1.default.existsSync(preferred)) {
55
+ return preferred;
56
+ }
57
+ const legacy = node_path_1.default.join(cwd, "versionary.versions.json");
58
+ if (node_fs_1.default.existsSync(legacy)) {
59
+ return legacy;
60
+ }
61
+ return preferred;
62
+ }
63
+ function readBaselineSha(cwd = process.cwd()) {
64
+ const filePath = getBaselineStatePath(cwd);
65
+ if (!node_fs_1.default.existsSync(filePath)) {
66
+ return null;
67
+ }
68
+ const parsed = parseStateFile(node_fs_1.default.readFileSync(filePath, "utf8"), filePath);
69
+ return parsed.baselineSha ?? null;
70
+ }
71
+ function readReleaseTargets(cwd = process.cwd()) {
72
+ const filePath = getBaselineStatePath(cwd);
73
+ if (!node_fs_1.default.existsSync(filePath)) {
74
+ return [];
75
+ }
76
+ const parsed = parseStateFile(node_fs_1.default.readFileSync(filePath, "utf8"), filePath);
77
+ return parsed.releaseTargets ?? [];
78
+ }
79
+ function writeBaselineSha(cwd = process.cwd(), sha, releaseTargets = []) {
80
+ const baselineSha = sha ??
81
+ (0, node_child_process_1.execFileSync)("git", ["rev-parse", "HEAD"], {
82
+ cwd,
83
+ encoding: "utf8",
84
+ stdio: ["ignore", "pipe", "ignore"],
85
+ }).trim();
86
+ const filePath = getBaselineStatePath(cwd);
87
+ const next = {
88
+ manifestVersion: 1,
89
+ baselineSha,
90
+ releaseTargets,
91
+ };
92
+ node_fs_1.default.writeFileSync(filePath, `${JSON.stringify(next, null, 2)}\n`, "utf8");
93
+ }
@@ -0,0 +1,2 @@
1
+ export type { VerifyResult } from "../../verify/verify-project.js";
2
+ export { verifyProject } from "../../verify/verify-project.js";
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.verifyProject = void 0;
4
+ var verify_project_js_1 = require("../../verify/verify-project.js");
5
+ Object.defineProperty(exports, "verifyProject", { enumerable: true, get: function () { return verify_project_js_1.verifyProject; } });