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
package/dist/cli/index.js CHANGED
@@ -4,43 +4,97 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  return (mod && mod.__esModule) ? mod : { "default": mod };
5
5
  };
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
+ const node_child_process_1 = require("node:child_process");
7
8
  const node_fs_1 = __importDefault(require("node:fs"));
8
9
  const node_path_1 = __importDefault(require("node:path"));
9
- const node_child_process_1 = require("node:child_process");
10
- const plan_js_1 = require("../simple/plan.js");
11
- const changelog_js_1 = require("../simple/changelog.js");
12
- const pr_js_1 = require("../simple/pr.js");
13
- const release_js_1 = require("../simple/release.js");
14
- const verify_project_js_1 = require("../verify/verify-project.js");
10
+ const pr_js_1 = require("../app/release/pr.js");
11
+ const release_js_1 = require("../app/release/release.js");
12
+ const verify_js_1 = require("../app/release/verify.js");
13
+ const changelog_js_1 = require("../domain/release/changelog.js");
14
+ const plan_js_1 = require("../domain/release/plan.js");
15
15
  function printVerifyResult() {
16
- const result = (0, verify_project_js_1.verifyProject)();
16
+ const result = (0, verify_js_1.verifyProject)();
17
17
  for (const check of result.checks) {
18
18
  const status = check.ok ? "OK" : "FAIL";
19
19
  console.log(`[${status}] ${check.name} - ${check.details}`);
20
20
  }
21
21
  return result.ok ? 0 : 1;
22
22
  }
23
+ function parseFlags(args) {
24
+ return {
25
+ json: args.includes("--json"),
26
+ };
27
+ }
28
+ function emitJson(payload) {
29
+ process.stdout.write(`${JSON.stringify(payload)}\n`);
30
+ }
23
31
  async function main() {
24
32
  const [, , command, ...args] = process.argv;
33
+ const flags = parseFlags(args);
34
+ const logger = flags.json ? undefined : console;
25
35
  if (!command || command === "run") {
26
36
  const subject = (0, node_child_process_1.execFileSync)("git", ["log", "-1", "--pretty=%s"], {
27
37
  encoding: "utf8",
28
38
  stdio: ["ignore", "pipe", "ignore"],
29
39
  }).trim();
30
40
  if ((0, pr_js_1.isReleaseCommitMessage)(subject)) {
41
+ if (flags.json) {
42
+ const release = await (0, release_js_1.runSimpleReleaseDetailed)(process.cwd(), {
43
+ logger,
44
+ });
45
+ if (release.action === "release-skipped") {
46
+ emitJson({
47
+ action: "release-skipped",
48
+ message: release.reason,
49
+ releaseCreated: false,
50
+ tagNames: [],
51
+ });
52
+ return 0;
53
+ }
54
+ emitJson({
55
+ action: "release-published",
56
+ message: release.message,
57
+ releaseCreated: release.releases.length > 0,
58
+ tagNames: release.releases.map((target) => target.tag),
59
+ });
60
+ return 0;
61
+ }
31
62
  const message = await (0, release_js_1.runSimpleRelease)(process.cwd());
32
63
  console.log(message);
33
64
  return 0;
34
65
  }
35
66
  const plan = (0, plan_js_1.createSimplePlan)();
36
67
  if (!plan.nextVersion) {
37
- console.log("No releasable commits found. Nothing to do.");
68
+ const message = "No releasable commits found. Nothing to do.";
69
+ if (flags.json) {
70
+ emitJson({
71
+ action: "noop",
72
+ message,
73
+ releaseCreated: false,
74
+ tagNames: [],
75
+ });
76
+ return 0;
77
+ }
78
+ console.log(message);
38
79
  return 0;
39
80
  }
40
- const pr = (0, pr_js_1.prepareSimpleReleasePr)();
81
+ const pr = (0, pr_js_1.prepareSimpleReleasePr)(process.cwd(), { logger });
41
82
  (0, pr_js_1.pushReleaseBranch)(process.cwd(), pr.branch);
42
- const reviewResult = await (0, pr_js_1.openOrUpdateSimpleReviewRequest)(process.cwd(), pr.branch, pr.title, pr.version, pr.commits);
43
- console.log(`Prepared release PR branch ${pr.branch}`);
83
+ const reviewResult = await (0, pr_js_1.openOrUpdateSimpleReviewRequest)(process.cwd(), pr.branch, pr.title, pr.version, pr.previousVersion, pr.commits, pr.plan, { logger });
84
+ const message = `Prepared release PR branch ${pr.branch}`;
85
+ if (flags.json) {
86
+ emitJson({
87
+ action: "pr-prepared",
88
+ message,
89
+ releaseCreated: false,
90
+ tagNames: [],
91
+ reviewUrl: reviewResult,
92
+ branch: pr.branch,
93
+ title: pr.title,
94
+ });
95
+ return 0;
96
+ }
97
+ console.log(message);
44
98
  console.log(`Title: ${pr.title}`);
45
99
  console.log(reviewResult);
46
100
  return 0;
@@ -66,7 +120,9 @@ async function main() {
66
120
  return 0;
67
121
  }
68
122
  const changelogPath = node_path_1.default.join(process.cwd(), plan.changelogFile);
69
- const existing = node_fs_1.default.existsSync(changelogPath) ? node_fs_1.default.readFileSync(changelogPath, "utf8") : "";
123
+ const existing = node_fs_1.default.existsSync(changelogPath)
124
+ ? node_fs_1.default.readFileSync(changelogPath, "utf8")
125
+ : "";
70
126
  const heading = "# Changelog\n\n";
71
127
  const body = existing.replace(/^# Changelog\s*/u, "");
72
128
  node_fs_1.default.writeFileSync(changelogPath, `${heading}${section}\n${body}`.trimEnd() + "\n", "utf8");
@@ -74,9 +130,9 @@ async function main() {
74
130
  return 0;
75
131
  }
76
132
  if (command === "pr") {
77
- const pr = (0, pr_js_1.prepareSimpleReleasePr)();
133
+ const pr = (0, pr_js_1.prepareSimpleReleasePr)(process.cwd(), { logger: console });
78
134
  (0, pr_js_1.pushReleaseBranch)(process.cwd(), pr.branch);
79
- const reviewResult = await (0, pr_js_1.openOrUpdateSimpleReviewRequest)(process.cwd(), pr.branch, pr.title, pr.version, pr.commits);
135
+ const reviewResult = await (0, pr_js_1.openOrUpdateSimpleReviewRequest)(process.cwd(), pr.branch, pr.title, pr.version, pr.previousVersion, pr.commits, pr.plan);
80
136
  console.log(`Prepared release PR branch ${pr.branch}`);
81
137
  console.log(`Title: ${pr.title}`);
82
138
  console.log(reviewResult);
@@ -89,7 +145,7 @@ async function main() {
89
145
  }
90
146
  console.log("Usage: versionary <command>");
91
147
  console.log("Commands:");
92
- console.log(" run Auto-dispatch release PR/update or release publish by context");
148
+ console.log(" run [--json] Auto-dispatch release PR/update or release publish by context");
93
149
  console.log(" verify Validate config and basic repository shape");
94
150
  console.log(" plan Print release plan (simple mode)");
95
151
  console.log(" changelog [--write] Print or write changelog section");
@@ -7,27 +7,17 @@ exports.findConfigFile = findConfigFile;
7
7
  exports.loadConfig = loadConfig;
8
8
  const node_fs_1 = __importDefault(require("node:fs"));
9
9
  const node_path_1 = __importDefault(require("node:path"));
10
- const toml_1 = require("@iarna/toml");
11
10
  const jsonc_parser_1 = require("jsonc-parser");
12
11
  const schema_js_1 = require("./schema.js");
13
12
  const SUPPORTED_FILES = [
14
13
  { file: "versionary.jsonc", format: "jsonc" },
15
14
  { file: "versionary.json", format: "json" },
16
- { file: "versionary.toml", format: "toml" },
17
- { file: "versionary.js", format: "js" },
18
- { file: "versionary.config.jsonc", format: "jsonc" },
19
- { file: "versionary.config.json", format: "json" },
20
- { file: "versionary.config.toml", format: "toml" },
21
- { file: "versionary.config.js", format: "js" },
22
15
  ];
23
16
  function parseConfig(raw, format) {
24
17
  if (format === "json" || format === "jsonc") {
25
18
  return (0, jsonc_parser_1.parse)(raw);
26
19
  }
27
- if (format === "toml") {
28
- return (0, toml_1.parse)(raw);
29
- }
30
- throw new Error("JavaScript config loading is not implemented yet. Use JSONC/JSON/TOML for now.");
20
+ throw new Error("Unsupported config format. Use versionary.jsonc or versionary.json.");
31
21
  }
32
22
  function isRecord(value) {
33
23
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
@@ -44,7 +34,7 @@ function findConfigFile(cwd) {
44
34
  function loadConfig(cwd = process.cwd()) {
45
35
  const found = findConfigFile(cwd);
46
36
  if (!found) {
47
- throw new Error("No Versionary config found. Create versionary.jsonc (preferred), .json, or .toml.");
37
+ throw new Error("No Versionary config found. Create versionary.jsonc (preferred) or versionary.json.");
48
38
  }
49
39
  const raw = node_fs_1.default.readFileSync(found.path, "utf8");
50
40
  const parsed = parseConfig(raw, found.format);
@@ -15,6 +15,7 @@ export declare const configSchema: z.ZodObject<{
15
15
  fixed: "fixed";
16
16
  }>>;
17
17
  "bump-minor-pre-major": z.ZodOptional<z.ZodBoolean>;
18
+ "allow-stable-major": z.ZodOptional<z.ZodBoolean>;
18
19
  "include-commit-authors": z.ZodOptional<z.ZodBoolean>;
19
20
  "release-type": z.ZodOptional<z.ZodString>;
20
21
  packages: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
@@ -2,11 +2,43 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.configSchema = void 0;
4
4
  const zod_1 = require("zod");
5
- const artifactRuleSchema = zod_1.z.object({
5
+ const artifactRuleSchema = zod_1.z
6
+ .object({
6
7
  type: zod_1.z.enum(["json", "toml", "yaml", "regex"]),
7
8
  path: zod_1.z.string().min(1),
8
9
  jsonpath: zod_1.z.string().optional(),
9
10
  pattern: zod_1.z.string().optional(),
11
+ })
12
+ .superRefine((value, ctx) => {
13
+ const needsJsonPath = value.type === "json" || value.type === "toml" || value.type === "yaml";
14
+ if (needsJsonPath && !value.jsonpath) {
15
+ ctx.addIssue({
16
+ code: zod_1.z.ZodIssueCode.custom,
17
+ message: `${value.type} artifact rules require "jsonpath".`,
18
+ path: ["jsonpath"],
19
+ });
20
+ }
21
+ if (needsJsonPath && value.pattern) {
22
+ ctx.addIssue({
23
+ code: zod_1.z.ZodIssueCode.custom,
24
+ message: `${value.type} artifact rules do not support "pattern".`,
25
+ path: ["pattern"],
26
+ });
27
+ }
28
+ if (value.type === "regex" && !value.pattern) {
29
+ ctx.addIssue({
30
+ code: zod_1.z.ZodIssueCode.custom,
31
+ message: 'regex artifact rules require "pattern".',
32
+ path: ["pattern"],
33
+ });
34
+ }
35
+ if (value.type === "regex" && value.jsonpath) {
36
+ ctx.addIssue({
37
+ code: zod_1.z.ZodIssueCode.custom,
38
+ message: 'regex artifact rules do not support "jsonpath".',
39
+ path: ["jsonpath"],
40
+ });
41
+ }
10
42
  });
11
43
  const packageSchema = zod_1.z.object({
12
44
  "release-type": zod_1.z.string().optional(),
@@ -24,6 +56,7 @@ exports.configSchema = zod_1.z.object({
24
56
  "bootstrap-sha": zod_1.z.string().optional(),
25
57
  "monorepo-mode": zod_1.z.enum(["independent", "fixed"]).optional(),
26
58
  "bump-minor-pre-major": zod_1.z.boolean().optional(),
59
+ "allow-stable-major": zod_1.z.boolean().optional(),
27
60
  "include-commit-authors": zod_1.z.boolean().optional(),
28
61
  "release-type": zod_1.z.string().optional(),
29
62
  packages: zod_1.z.record(zod_1.z.string().min(1), packageSchema).optional(),
@@ -0,0 +1,12 @@
1
+ import { type ParsedCommit } from "../../infra/git/commits.js";
2
+ import type { SimplePlan } from "./plan.js";
3
+ export declare function renderSimpleReleaseNotes(input: {
4
+ currentVersion: string;
5
+ nextVersion: string;
6
+ commits: ParsedCommit[];
7
+ cwd?: string;
8
+ }, options?: {
9
+ includeFooter?: boolean;
10
+ }): string;
11
+ export declare function renderSimpleChangelog(plan: SimplePlan): string;
12
+ export declare function prependChangelog(cwd: string, changelogFile: string, section: string): void;
@@ -0,0 +1,123 @@
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.renderSimpleReleaseNotes = renderSimpleReleaseNotes;
7
+ exports.renderSimpleChangelog = renderSimpleChangelog;
8
+ exports.prependChangelog = prependChangelog;
9
+ const node_fs_1 = __importDefault(require("node:fs"));
10
+ const node_path_1 = __importDefault(require("node:path"));
11
+ const commits_js_1 = require("../../infra/git/commits.js");
12
+ const repo_url_js_1 = require("../../infra/git/repo-url.js");
13
+ function formatDate() {
14
+ return new Date().toISOString().slice(0, 10);
15
+ }
16
+ function formatCommitMessage(subject) {
17
+ const conventional = subject.match(/^[a-z]+(?:\(([^)]+)\))?!?:\s+(.+)$/iu);
18
+ if (!conventional) {
19
+ return { label: "", message: subject };
20
+ }
21
+ const scope = conventional[1]?.trim();
22
+ const message = conventional[2]?.trim() ?? subject;
23
+ const label = scope ? `**${scope}:** ` : "";
24
+ return { label, message };
25
+ }
26
+ function formatCommitReferences(commit, repoBaseUrl) {
27
+ const references = commit.references ?? [];
28
+ if (references.length === 0) {
29
+ return "";
30
+ }
31
+ return references
32
+ .map((reference) => {
33
+ const issue = reference.issue;
34
+ if (!issue) {
35
+ return "";
36
+ }
37
+ const action = (reference.action?.trim() || "refs").toLowerCase();
38
+ if (!repoBaseUrl) {
39
+ return `${action} #${issue}`;
40
+ }
41
+ return `${action} [#${issue}](${repoBaseUrl}/issues/${issue})`;
42
+ })
43
+ .filter((entry) => entry.length > 0)
44
+ .join(", ");
45
+ }
46
+ function groupCommitLines(commits, repoUrl) {
47
+ const breaking = [];
48
+ const features = [];
49
+ const fixes = [];
50
+ for (const commit of commits) {
51
+ const type = (0, commits_js_1.inferReleaseTypeFromParsedCommit)(commit);
52
+ if (!type) {
53
+ continue;
54
+ }
55
+ const { label, message } = formatCommitMessage(commit.subject);
56
+ const short = commit.hash.slice(0, 7);
57
+ const hashLabel = repoUrl
58
+ ? `[\`${short}\`](${repoUrl}/commit/${commit.hash})`
59
+ : `\`${short}\``;
60
+ const references = formatCommitReferences(commit, repoUrl);
61
+ const referencesSuffix = references ? `, ${references}` : "";
62
+ const line = `- ${label}${message} (${hashLabel})${referencesSuffix}`;
63
+ const commitType = (commit.type ?? "").toLowerCase();
64
+ const isBreaking = type === "major";
65
+ if (isBreaking) {
66
+ breaking.push(line);
67
+ }
68
+ if (commitType === "feat" || (!isBreaking && type === "minor")) {
69
+ features.push(line);
70
+ continue;
71
+ }
72
+ if (commitType === "fix" ||
73
+ commitType === "perf" ||
74
+ (!isBreaking && type === "patch")) {
75
+ fixes.push(line);
76
+ }
77
+ }
78
+ return { breaking, features, fixes };
79
+ }
80
+ function renderSimpleReleaseNotes(input, options = {}) {
81
+ const repoUrl = (0, repo_url_js_1.resolveRepositoryWebBaseUrl)(input.cwd ?? process.cwd());
82
+ const header = repoUrl
83
+ ? `## [${input.nextVersion}](${repoUrl}/compare/v${input.currentVersion}...v${input.nextVersion}) (${formatDate()})`
84
+ : `## ${input.nextVersion} (${formatDate()})`;
85
+ const grouped = groupCommitLines(input.commits, repoUrl);
86
+ const sections = [];
87
+ if (grouped.breaking.length > 0) {
88
+ sections.push("### Breaking changes", ...grouped.breaking, "");
89
+ }
90
+ if (grouped.features.length > 0) {
91
+ sections.push("### Features", ...grouped.features, "");
92
+ }
93
+ if (grouped.fixes.length > 0) {
94
+ sections.push("### Bug Fixes", ...grouped.fixes, "");
95
+ }
96
+ const lines = [header, "", ...sections];
97
+ if (options.includeFooter) {
98
+ lines.push("\n---\n\nThis PR was generated by [Versionary](https://github.com/jolars/versionary).");
99
+ }
100
+ return lines.join("\n");
101
+ }
102
+ function renderSimpleChangelog(plan) {
103
+ if (!plan.nextVersion) {
104
+ return "";
105
+ }
106
+ return renderSimpleReleaseNotes({
107
+ currentVersion: plan.currentVersion,
108
+ nextVersion: plan.nextVersion,
109
+ commits: plan.commits,
110
+ cwd: process.cwd(),
111
+ });
112
+ }
113
+ function prependChangelog(cwd, changelogFile, section) {
114
+ const changelogPath = node_path_1.default.join(cwd, changelogFile);
115
+ const existing = node_fs_1.default.existsSync(changelogPath)
116
+ ? node_fs_1.default.readFileSync(changelogPath, "utf8")
117
+ : "";
118
+ const heading = existing.startsWith("# Changelog") ? "" : "# Changelog\n\n";
119
+ const separator = existing.length > 0 ? "\n" : "";
120
+ const next = `${heading}${section}${separator}${existing.replace(/^# Changelog\s*/u, "")}`.trimEnd() +
121
+ "\n";
122
+ node_fs_1.default.writeFileSync(changelogPath, next, "utf8");
123
+ }
@@ -1,4 +1,4 @@
1
- import { type CommitInfo } from "./git.js";
1
+ import { type ParsedCommit } from "../../infra/git/commits.js";
2
2
  import { type ReleaseType } from "./semver.js";
3
3
  export interface SimplePlan {
4
4
  mode: "simple";
@@ -9,12 +9,13 @@ export interface SimplePlan {
9
9
  changelogFile: string;
10
10
  releaseBranchPrefix: string;
11
11
  baselineSha: string | null;
12
- commits: CommitInfo[];
12
+ commits: ParsedCommit[];
13
13
  packages?: Array<{
14
14
  path: string;
15
15
  releaseType: ReleaseType;
16
+ currentVersion: string;
16
17
  nextVersion: string | null;
17
- commits: CommitInfo[];
18
+ commits: ParsedCommit[];
18
19
  }>;
19
20
  }
20
21
  export declare function createSimplePlan(cwd?: string): SimplePlan;
@@ -6,16 +6,18 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.createSimplePlan = createSimplePlan;
7
7
  const node_fs_1 = __importDefault(require("node:fs"));
8
8
  const node_path_1 = __importDefault(require("node:path"));
9
- const load_config_js_1 = require("../config/load-config.js");
10
- const git_js_1 = require("./git.js");
9
+ const state_js_1 = require("../../app/release/state.js");
10
+ const load_config_js_1 = require("../../config/load-config.js");
11
+ const commits_js_1 = require("../../infra/git/commits.js");
12
+ const resolve_js_1 = require("../strategy/resolve.js");
11
13
  const semver_js_1 = require("./semver.js");
12
- const state_js_1 = require("./state.js");
13
14
  function getMode(configMode) {
14
15
  return configMode ?? "independent";
15
16
  }
16
17
  function createSimplePlan(cwd = process.cwd()) {
17
18
  const loaded = (0, load_config_js_1.loadConfig)(cwd);
18
- const versionFile = loaded.config["version-file"] ?? "version.txt";
19
+ const strategy = (0, resolve_js_1.resolveVersionStrategy)(loaded.config);
20
+ const versionFile = strategy.getVersionFile(loaded.config);
19
21
  const changelogFile = loaded.config["changelog-file"] ?? "CHANGELOG.md";
20
22
  const releaseBranchPrefix = loaded.config["release-branch"] ?? "versionary/release";
21
23
  const baselineSha = (0, state_js_1.readBaselineSha)(cwd) ?? loaded.config["bootstrap-sha"] ?? null;
@@ -23,7 +25,8 @@ function createSimplePlan(cwd = process.cwd()) {
23
25
  if (!node_fs_1.default.existsSync(versionPath)) {
24
26
  throw new Error(`Versionary requires ${versionFile} to exist.`);
25
27
  }
26
- const currentVersion = node_fs_1.default.readFileSync(versionPath, "utf8").trim();
28
+ const currentVersion = strategy.readVersion(cwd, loaded.config);
29
+ const allowStableMajor = loaded.config["allow-stable-major"] ?? false;
27
30
  const configuredPackages = Object.entries(loaded.config.packages ?? {}).map(([pkgPath, cfg]) => ({
28
31
  path: pkgPath,
29
32
  ...cfg,
@@ -31,9 +34,13 @@ function createSimplePlan(cwd = process.cwd()) {
31
34
  const monorepoMode = getMode(loaded.config["monorepo-mode"]);
32
35
  const hasPackages = configuredPackages.length > 0;
33
36
  if (!hasPackages) {
34
- const commits = (0, git_js_1.getCommitsSinceLastTag)(cwd, baselineSha);
35
- const releaseType = (0, git_js_1.analyzeCommits)(commits);
36
- const nextVersion = releaseType ? (0, semver_js_1.bumpVersion)(currentVersion, releaseType) : null;
37
+ const parsedCommits = (0, commits_js_1.getParsedCommitsSinceLastTag)(cwd, baselineSha);
38
+ const effectiveCommits = (0, commits_js_1.applyRevertSuppression)(parsedCommits);
39
+ const commits = effectiveCommits;
40
+ const releaseType = (0, commits_js_1.analyzeParsedCommits)(parsedCommits);
41
+ const nextVersion = releaseType
42
+ ? (0, semver_js_1.bumpVersion)(currentVersion, releaseType, { allowStableMajor })
43
+ : null;
37
44
  return {
38
45
  mode: "simple",
39
46
  releaseType,
@@ -48,20 +55,28 @@ function createSimplePlan(cwd = process.cwd()) {
48
55
  }
49
56
  const packagePlans = configuredPackages
50
57
  .map((pkg) => {
51
- const commits = (0, git_js_1.getCommitsForPath)(cwd, baselineSha, pkg.path, pkg["exclude-paths"] ?? []);
52
- const releaseType = (0, git_js_1.analyzeCommits)(commits);
53
- const nextVersion = releaseType ? (0, semver_js_1.bumpVersion)(currentVersion, releaseType) : null;
58
+ const parsedCommits = (0, commits_js_1.getParsedCommitsForPath)(cwd, baselineSha, pkg.path, pkg["exclude-paths"] ?? []);
59
+ const effectiveCommits = (0, commits_js_1.applyRevertSuppression)(parsedCommits);
60
+ const commits = effectiveCommits;
61
+ const releaseType = (0, commits_js_1.analyzeParsedCommits)(parsedCommits);
62
+ const nextVersion = releaseType
63
+ ? (0, semver_js_1.bumpVersion)(currentVersion, releaseType, { allowStableMajor })
64
+ : null;
54
65
  return {
55
66
  path: pkg.path,
56
67
  releaseType,
68
+ currentVersion,
57
69
  nextVersion,
58
70
  commits,
71
+ parsedCommits,
59
72
  };
60
73
  })
61
74
  .sort((a, b) => a.path.localeCompare(b.path));
62
75
  if (monorepoMode === "fixed") {
63
- const fixedType = (0, git_js_1.analyzeCommits)(packagePlans.flatMap((pkgPlan) => pkgPlan.commits));
64
- const fixedNextVersion = fixedType ? (0, semver_js_1.bumpVersion)(currentVersion, fixedType) : null;
76
+ const fixedType = (0, commits_js_1.analyzeParsedCommits)(packagePlans.flatMap((pkgPlan) => pkgPlan.parsedCommits));
77
+ const fixedNextVersion = fixedType
78
+ ? (0, semver_js_1.bumpVersion)(currentVersion, fixedType, { allowStableMajor })
79
+ : null;
65
80
  const adjusted = packagePlans.map((pkgPlan) => ({
66
81
  ...pkgPlan,
67
82
  releaseType: fixedType,
@@ -80,8 +95,10 @@ function createSimplePlan(cwd = process.cwd()) {
80
95
  packages: adjusted,
81
96
  };
82
97
  }
83
- const overallType = (0, git_js_1.analyzeCommits)(packagePlans.flatMap((pkgPlan) => pkgPlan.commits));
84
- const overallNextVersion = overallType ? (0, semver_js_1.bumpVersion)(currentVersion, overallType) : null;
98
+ const overallType = (0, commits_js_1.analyzeParsedCommits)(packagePlans.flatMap((pkgPlan) => pkgPlan.parsedCommits));
99
+ const overallNextVersion = overallType
100
+ ? (0, semver_js_1.bumpVersion)(currentVersion, overallType, { allowStableMajor })
101
+ : null;
85
102
  return {
86
103
  mode: "simple",
87
104
  releaseType: overallType,
@@ -0,0 +1,15 @@
1
+ export type ReleaseType = "major" | "minor" | "patch" | null;
2
+ export interface ParsedVersion {
3
+ major: number;
4
+ minor: number;
5
+ patch: number;
6
+ prerelease: string[];
7
+ build: string[];
8
+ }
9
+ export interface BumpVersionOptions {
10
+ allowStableMajor?: boolean;
11
+ }
12
+ export declare function parseVersion(version: string): ParsedVersion;
13
+ export declare function isValidVersion(version: string): boolean;
14
+ export declare function compareVersions(leftRaw: string, rightRaw: string): number;
15
+ export declare function bumpVersion(current: string, releaseType: Exclude<ReleaseType, null>, options?: BumpVersionOptions): string;
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseVersion = parseVersion;
4
+ exports.isValidVersion = isValidVersion;
5
+ exports.compareVersions = compareVersions;
6
+ exports.bumpVersion = bumpVersion;
7
+ const SEMVER_PATTERN = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*))*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/u;
8
+ function parseVersion(version) {
9
+ const match = version.trim().match(SEMVER_PATTERN);
10
+ if (!match) {
11
+ throw new Error(`Invalid version in version file: "${version}". Expected SemVer 2.0.0 format.`);
12
+ }
13
+ return {
14
+ major: Number(match[1]),
15
+ minor: Number(match[2]),
16
+ patch: Number(match[3]),
17
+ prerelease: match[4] ? match[4].split(".") : [],
18
+ build: match[5] ? match[5].split(".") : [],
19
+ };
20
+ }
21
+ function isValidVersion(version) {
22
+ return SEMVER_PATTERN.test(version.trim());
23
+ }
24
+ function isNumericIdentifier(identifier) {
25
+ return /^(0|[1-9]\d*)$/u.test(identifier);
26
+ }
27
+ function comparePreReleaseIdentifiers(left, right) {
28
+ const leftNumeric = isNumericIdentifier(left);
29
+ const rightNumeric = isNumericIdentifier(right);
30
+ if (leftNumeric && rightNumeric) {
31
+ const leftNumber = Number(left);
32
+ const rightNumber = Number(right);
33
+ if (leftNumber < rightNumber) {
34
+ return -1;
35
+ }
36
+ if (leftNumber > rightNumber) {
37
+ return 1;
38
+ }
39
+ return 0;
40
+ }
41
+ if (leftNumeric && !rightNumeric) {
42
+ return -1;
43
+ }
44
+ if (!leftNumeric && rightNumeric) {
45
+ return 1;
46
+ }
47
+ if (left < right) {
48
+ return -1;
49
+ }
50
+ if (left > right) {
51
+ return 1;
52
+ }
53
+ return 0;
54
+ }
55
+ function compareVersions(leftRaw, rightRaw) {
56
+ const left = parseVersion(leftRaw);
57
+ const right = parseVersion(rightRaw);
58
+ if (left.major !== right.major) {
59
+ return left.major < right.major ? -1 : 1;
60
+ }
61
+ if (left.minor !== right.minor) {
62
+ return left.minor < right.minor ? -1 : 1;
63
+ }
64
+ if (left.patch !== right.patch) {
65
+ return left.patch < right.patch ? -1 : 1;
66
+ }
67
+ const leftPre = left.prerelease;
68
+ const rightPre = right.prerelease;
69
+ if (leftPre.length === 0 && rightPre.length === 0) {
70
+ return 0;
71
+ }
72
+ if (leftPre.length === 0) {
73
+ return 1;
74
+ }
75
+ if (rightPre.length === 0) {
76
+ return -1;
77
+ }
78
+ const maxLength = Math.max(leftPre.length, rightPre.length);
79
+ for (let index = 0; index < maxLength; index += 1) {
80
+ const leftIdentifier = leftPre[index];
81
+ const rightIdentifier = rightPre[index];
82
+ if (leftIdentifier === undefined) {
83
+ return -1;
84
+ }
85
+ if (rightIdentifier === undefined) {
86
+ return 1;
87
+ }
88
+ const comparison = comparePreReleaseIdentifiers(leftIdentifier, rightIdentifier);
89
+ if (comparison !== 0) {
90
+ return comparison;
91
+ }
92
+ }
93
+ return 0;
94
+ }
95
+ function bumpVersion(current, releaseType, options = {}) {
96
+ const parsed = parseVersion(current);
97
+ const allowStableMajor = options.allowStableMajor ?? false;
98
+ if (releaseType === "major") {
99
+ if (parsed.major === 0 && !allowStableMajor) {
100
+ return `0.${parsed.minor + 1}.0`;
101
+ }
102
+ if (parsed.major === 0 && allowStableMajor) {
103
+ return "1.0.0";
104
+ }
105
+ return `${parsed.major + 1}.0.0`;
106
+ }
107
+ if (releaseType === "minor") {
108
+ return `${parsed.major}.${parsed.minor + 1}.0`;
109
+ }
110
+ return `${parsed.major}.${parsed.minor}.${parsed.patch + 1}`;
111
+ }
@@ -0,0 +1,2 @@
1
+ import type { VersionStrategy } from "./types.js";
2
+ export declare const nodeVersionStrategy: VersionStrategy;