versionary 0.1.0 → 0.3.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 (70) hide show
  1. package/README.md +217 -8
  2. package/dist/app/release/pr.d.ts +18 -0
  3. package/dist/app/release/pr.js +209 -0
  4. package/dist/app/release/recovery.d.ts +17 -0
  5. package/dist/app/release/recovery.js +101 -0
  6. package/dist/app/release/release.d.ts +1 -0
  7. package/dist/app/release/release.js +90 -0
  8. package/dist/app/release/state.d.ts +10 -0
  9. package/dist/app/release/state.js +59 -0
  10. package/dist/app/release/verify.d.ts +2 -0
  11. package/dist/app/release/verify.js +5 -0
  12. package/dist/cli/index.js +51 -8
  13. package/dist/config/load-config.js +8 -12
  14. package/dist/config/schema.d.ts +25 -85
  15. package/dist/config/schema.js +20 -81
  16. package/dist/domain/release/changelog.d.ts +12 -0
  17. package/dist/domain/release/changelog.js +123 -0
  18. package/dist/domain/release/plan.d.ts +21 -0
  19. package/dist/domain/release/plan.js +114 -0
  20. package/dist/domain/release/semver.d.ts +15 -0
  21. package/dist/domain/release/semver.js +111 -0
  22. package/dist/domain/strategy/node.d.ts +2 -0
  23. package/dist/domain/strategy/node.js +37 -0
  24. package/dist/domain/strategy/resolve.d.ts +3 -0
  25. package/dist/domain/strategy/resolve.js +11 -0
  26. package/dist/domain/strategy/simple.d.ts +2 -0
  27. package/dist/domain/strategy/simple.js +28 -0
  28. package/dist/domain/strategy/types.d.ts +7 -0
  29. package/dist/domain/strategy/types.js +2 -0
  30. package/dist/index.d.ts +6 -1
  31. package/dist/index.js +10 -1
  32. package/dist/infra/git/commits.d.ts +65 -0
  33. package/dist/infra/git/commits.js +436 -0
  34. package/dist/infra/git/repo-url.d.ts +1 -0
  35. package/dist/infra/git/repo-url.js +27 -0
  36. package/dist/infra/scm/github/plugin.d.ts +1 -0
  37. package/dist/infra/scm/github/plugin.js +5 -0
  38. package/dist/infra/scm/runtime.d.ts +2 -0
  39. package/dist/infra/scm/runtime.js +24 -0
  40. package/dist/infra/scm/types.d.ts +1 -0
  41. package/dist/infra/scm/types.js +2 -0
  42. package/dist/plugins/capabilities.d.ts +3 -0
  43. package/dist/plugins/capabilities.js +10 -0
  44. package/dist/plugins/runtime.d.ts +1 -0
  45. package/dist/plugins/runtime.js +5 -0
  46. package/dist/scm/github-plugin.d.ts +2 -0
  47. package/dist/scm/github-plugin.js +207 -0
  48. package/dist/strategies/node.d.ts +1 -0
  49. package/dist/strategies/node.js +5 -0
  50. package/dist/strategies/resolve.d.ts +1 -0
  51. package/dist/strategies/resolve.js +5 -0
  52. package/dist/strategies/simple.d.ts +1 -0
  53. package/dist/strategies/simple.js +5 -0
  54. package/dist/strategies/types.d.ts +1 -0
  55. package/dist/strategies/types.js +2 -0
  56. package/dist/types/config.d.ts +20 -66
  57. package/dist/types/plugins.d.ts +37 -0
  58. package/dist/types/plugins.js +2 -0
  59. package/dist/verify/verify-project.js +13 -13
  60. package/package.json +15 -14
  61. package/dist/simple/changelog.d.ts +0 -3
  62. package/dist/simple/changelog.js +0 -32
  63. package/dist/simple/git.d.ts +0 -7
  64. package/dist/simple/git.js +0 -61
  65. package/dist/simple/plan.d.ts +0 -12
  66. package/dist/simple/plan.js +0 -37
  67. package/dist/simple/pr.d.ts +0 -5
  68. package/dist/simple/pr.js +0 -45
  69. package/dist/simple/semver.d.ts +0 -8
  70. package/dist/simple/semver.js +0 -25
@@ -0,0 +1,207 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createGitHubPlugin = createGitHubPlugin;
4
+ const rest_1 = require("@octokit/rest");
5
+ function parseRepoSlug(input) {
6
+ const [owner, repo] = input.split("/");
7
+ if (!owner || !repo) {
8
+ throw new Error(`Invalid repo slug "${input}". Expected "owner/repo".`);
9
+ }
10
+ return { owner, repo };
11
+ }
12
+ function getRepoFromEnv() {
13
+ const slug = process.env.GITHUB_REPOSITORY;
14
+ if (!slug) {
15
+ throw new Error("Missing GITHUB_REPOSITORY environment variable.");
16
+ }
17
+ return parseRepoSlug(slug);
18
+ }
19
+ function getGitHubToken() {
20
+ const token = process.env.VERSIONARY_PR_TOKEN ??
21
+ process.env.GH_TOKEN ??
22
+ process.env.GITHUB_TOKEN;
23
+ if (!token) {
24
+ throw new Error("Missing GitHub token. Set VERSIONARY_PR_TOKEN, GH_TOKEN, or GITHUB_TOKEN.");
25
+ }
26
+ return token;
27
+ }
28
+ function parseGitHubError(error) {
29
+ const status = typeof error === "object" && error !== null && "status" in error
30
+ ? Number(error.status)
31
+ : undefined;
32
+ const message = error instanceof Error ? error.message : "Unknown GitHub API error";
33
+ return { status, message };
34
+ }
35
+ function repoRef(repo) {
36
+ return `${repo.owner}/${repo.repo}`;
37
+ }
38
+ function resolveHeadForList(repo, headBranch) {
39
+ const separator = headBranch.indexOf(":");
40
+ if (separator === -1) {
41
+ return `${repo.owner}:${headBranch}`;
42
+ }
43
+ const owner = headBranch.slice(0, separator);
44
+ const branch = headBranch.slice(separator + 1);
45
+ if (!owner || !branch || branch.includes(":")) {
46
+ throw new Error(`Invalid head branch "${headBranch}". Expected "branch" or "owner:branch".`);
47
+ }
48
+ return `${owner}:${branch}`;
49
+ }
50
+ function toReviewRequestState(pull, context) {
51
+ if (pull.state === "open") {
52
+ return "open";
53
+ }
54
+ if (pull.merged_at) {
55
+ return "merged";
56
+ }
57
+ if (pull.state === "closed") {
58
+ return "closed";
59
+ }
60
+ throw new Error(`Unexpected pull request state "${String(pull.state)}" for ${context}.`);
61
+ }
62
+ /**
63
+ * GitHub hardening contract (see tests/github-plugin-hardening-matrix.test.ts):
64
+ * - GITHUB_REPOSITORY must exist and use owner/repo format.
65
+ * - Token precedence: VERSIONARY_PR_TOKEN -> GH_TOKEN -> GITHUB_TOKEN.
66
+ * - Label application is best-effort for 404/422, but fails for other statuses.
67
+ * - Release metadata lookup treats 404 as "missing, create release"; non-404 fails.
68
+ */
69
+ async function ensureLabels(octokit, repo, pullNumber, labels, branchContext) {
70
+ if (labels.length === 0) {
71
+ return;
72
+ }
73
+ try {
74
+ await octokit.issues.addLabels({
75
+ owner: repo.owner,
76
+ repo: repo.repo,
77
+ issue_number: pullNumber,
78
+ labels,
79
+ });
80
+ }
81
+ catch (error) {
82
+ const { status, message } = parseGitHubError(error);
83
+ if (status === 404 || status === 422) {
84
+ return;
85
+ }
86
+ throw new Error(`Failed applying labels to pull request #${pullNumber}: [${repoRef(repo)} base=${branchContext.baseBranch} head=${branchContext.headBranch}] ${message}`);
87
+ }
88
+ }
89
+ function createGitHubPlugin() {
90
+ return {
91
+ name: "github",
92
+ capabilities: ["scm.reviewRequest", "scm.releaseMetadata"],
93
+ async createOrUpdateReviewRequest(input, _context) {
94
+ const repo = getRepoFromEnv();
95
+ const octokit = new rest_1.Octokit({ auth: getGitHubToken() });
96
+ const listHead = resolveHeadForList(repo, input.headBranch);
97
+ let existing;
98
+ try {
99
+ const response = await octokit.pulls.list({
100
+ owner: repo.owner,
101
+ repo: repo.repo,
102
+ state: "open",
103
+ head: listHead,
104
+ base: input.baseBranch,
105
+ per_page: 100,
106
+ });
107
+ existing = response.data;
108
+ }
109
+ catch (error) {
110
+ const { message } = parseGitHubError(error);
111
+ throw new Error(`Failed listing open pull requests for branch "${input.headBranch}" into "${input.baseBranch}": [${repoRef(repo)}] ${message}`);
112
+ }
113
+ if (existing.length > 1) {
114
+ const matches = existing.map((item) => `#${item.number}`).join(", ");
115
+ throw new Error(`Ambiguous open pull request matches for "${input.headBranch}" into "${input.baseBranch}": [${repoRef(repo)}] ${matches}`);
116
+ }
117
+ if (existing.length > 0) {
118
+ const pr = existing[0];
119
+ let updated;
120
+ try {
121
+ const response = await octokit.pulls.update({
122
+ owner: repo.owner,
123
+ repo: repo.repo,
124
+ pull_number: pr.number,
125
+ title: input.title,
126
+ body: input.body,
127
+ });
128
+ updated = response.data;
129
+ }
130
+ catch (error) {
131
+ const { message } = parseGitHubError(error);
132
+ throw new Error(`Failed updating pull request #${pr.number}: [${repoRef(repo)} base=${input.baseBranch} head=${input.headBranch}] ${message}`);
133
+ }
134
+ await ensureLabels(octokit, repo, updated.number, input.labels ?? [], {
135
+ baseBranch: input.baseBranch,
136
+ headBranch: input.headBranch,
137
+ });
138
+ return {
139
+ id: String(updated.id),
140
+ number: updated.number,
141
+ url: updated.html_url,
142
+ state: toReviewRequestState(updated, `pull request #${updated.number} in ${repoRef(repo)}`),
143
+ };
144
+ }
145
+ let created;
146
+ try {
147
+ const response = await octokit.pulls.create({
148
+ owner: repo.owner,
149
+ repo: repo.repo,
150
+ title: input.title,
151
+ head: input.headBranch,
152
+ base: input.baseBranch,
153
+ body: input.body,
154
+ });
155
+ created = response.data;
156
+ }
157
+ catch (error) {
158
+ const { message } = parseGitHubError(error);
159
+ throw new Error(`Failed creating pull request from "${input.headBranch}" into "${input.baseBranch}": [${repoRef(repo)}] ${message}`);
160
+ }
161
+ await ensureLabels(octokit, repo, created.number, input.labels ?? [], {
162
+ baseBranch: input.baseBranch,
163
+ headBranch: input.headBranch,
164
+ });
165
+ return {
166
+ id: String(created.id),
167
+ number: created.number,
168
+ url: created.html_url,
169
+ state: toReviewRequestState(created, `pull request #${created.number} in ${repoRef(repo)}`),
170
+ };
171
+ },
172
+ async createReleaseMetadata(input, _context) {
173
+ const repo = getRepoFromEnv();
174
+ const octokit = new rest_1.Octokit({ auth: getGitHubToken() });
175
+ try {
176
+ const existing = await octokit.repos.getReleaseByTag({
177
+ owner: repo.owner,
178
+ repo: repo.repo,
179
+ tag: input.tag,
180
+ });
181
+ return { url: existing.data.html_url, status: "exists" };
182
+ }
183
+ catch (error) {
184
+ const { status, message } = parseGitHubError(error);
185
+ if (status !== 404) {
186
+ throw new Error(`Failed checking existing GitHub release for tag "${input.tag}": [${repoRef(repo)}] ${message}`);
187
+ }
188
+ }
189
+ let data;
190
+ try {
191
+ const response = await octokit.repos.createRelease({
192
+ owner: repo.owner,
193
+ repo: repo.repo,
194
+ tag_name: input.tag,
195
+ name: input.tag,
196
+ body: input.notes,
197
+ });
198
+ data = response.data;
199
+ }
200
+ catch (error) {
201
+ const { message } = parseGitHubError(error);
202
+ throw new Error(`Failed creating GitHub release for tag "${input.tag}": [${repoRef(repo)}] ${message}`);
203
+ }
204
+ return { url: data.html_url, status: "created" };
205
+ },
206
+ };
207
+ }
@@ -0,0 +1 @@
1
+ export { nodeVersionStrategy } from "../domain/strategy/node.js";
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.nodeVersionStrategy = void 0;
4
+ var node_js_1 = require("../domain/strategy/node.js");
5
+ Object.defineProperty(exports, "nodeVersionStrategy", { enumerable: true, get: function () { return node_js_1.nodeVersionStrategy; } });
@@ -0,0 +1 @@
1
+ export { resolveVersionStrategy } from "../domain/strategy/resolve.js";
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveVersionStrategy = void 0;
4
+ var resolve_js_1 = require("../domain/strategy/resolve.js");
5
+ Object.defineProperty(exports, "resolveVersionStrategy", { enumerable: true, get: function () { return resolve_js_1.resolveVersionStrategy; } });
@@ -0,0 +1 @@
1
+ export { simpleVersionStrategy } from "../domain/strategy/simple.js";
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.simpleVersionStrategy = void 0;
4
+ var simple_js_1 = require("../domain/strategy/simple.js");
5
+ Object.defineProperty(exports, "simpleVersionStrategy", { enumerable: true, get: function () { return simple_js_1.simpleVersionStrategy; } });
@@ -0,0 +1 @@
1
+ export type { VersionStrategy } from "../domain/strategy/types.js";
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,77 +1,31 @@
1
1
  export type ConfigFileFormat = "jsonc" | "json" | "toml" | "js";
2
- export interface VersionaryBootstrap {
3
- sha?: string;
4
- tag?: string;
5
- }
6
- export interface VersionaryHistory {
7
- bootstrap?: VersionaryBootstrap;
8
- }
9
- export interface VersionaryMonorepo {
10
- mode: "independent" | "fixed";
11
- }
12
- export interface VersionaryVersioningDefaults {
13
- bumpMinorPreMajor?: boolean;
14
- }
15
- export interface VersionaryChangelogDefaults {
16
- includeAuthors?: boolean;
17
- }
18
- export interface VersionaryCommitConventions {
19
- preset: "conventional" | "angular" | "custom";
20
- }
21
- export interface VersionaryDefaults {
22
- strategy?: string;
23
- versioning?: VersionaryVersioningDefaults;
24
- changelog?: VersionaryChangelogDefaults;
25
- commitConventions?: VersionaryCommitConventions;
26
- }
27
2
  export interface VersionaryArtifactRule {
28
- file: string;
29
- format: "json" | "toml" | "yaml" | "regex";
30
- path?: string;
3
+ type: "json" | "toml" | "yaml" | "regex";
4
+ path: string;
5
+ jsonpath?: string;
31
6
  pattern?: string;
32
7
  }
33
8
  export interface VersionaryPackage {
34
- path: string;
35
- strategy?: string;
36
- packageName?: string;
37
- excludePaths?: string[];
38
- artifacts?: VersionaryArtifactRule[];
39
- }
40
- export interface VersionaryPluginRef {
41
- name: string;
42
- options?: Record<string, unknown>;
43
- }
44
- export type VersionaryLifecycle = "plan" | "pr" | "release";
45
- export type VersionaryPluginStep = "verifyConditions" | "analyzeCommits" | "resolveReverts" | "verifyRelease" | "generateNotes" | "updateArtifacts" | "preparePr" | "publish" | "postRelease" | "success" | "fail";
46
- export type VersionaryPluginMergeStrategy = "highest" | "concat" | "override" | "reduce";
47
- export interface VersionaryPluginExecution {
48
- step: VersionaryPluginStep;
49
- lifecycle?: VersionaryLifecycle[];
50
- merge?: VersionaryPluginMergeStrategy;
51
- }
52
- export interface VersionaryPluginPresetRef {
53
- name: string;
54
- }
55
- export interface VersionaryPluginConfig {
56
- extends?: VersionaryPluginPresetRef[];
57
- globalOptions?: Record<string, unknown>;
58
- execution?: VersionaryPluginExecution[];
59
- plugins?: VersionaryPluginRef[];
9
+ "release-type"?: string;
10
+ "package-name"?: string;
11
+ "exclude-paths"?: string[];
12
+ "extra-files"?: VersionaryArtifactRule[];
60
13
  }
61
14
  export interface VersionaryConfig {
62
15
  version: 1;
63
- mode?: "simple" | "standard";
64
- history?: VersionaryHistory;
65
- monorepo?: VersionaryMonorepo;
66
- defaults?: VersionaryDefaults;
67
- packages?: VersionaryPackage[];
68
- pluginConfig?: VersionaryPluginConfig;
69
- plugins?: VersionaryPluginRef[];
70
- simple?: {
71
- versionFile?: string;
72
- changelogFile?: string;
73
- releaseBranchPrefix?: string;
74
- };
16
+ "review-mode"?: "direct" | "review";
17
+ "version-file"?: string;
18
+ "changelog-file"?: string;
19
+ "release-branch"?: string;
20
+ "baseline-file"?: string;
21
+ "bootstrap-sha"?: string;
22
+ "monorepo-mode"?: "independent" | "fixed";
23
+ "bump-minor-pre-major"?: boolean;
24
+ "allow-stable-major"?: boolean;
25
+ "include-commit-authors"?: boolean;
26
+ "release-type"?: string;
27
+ packages?: Record<string, VersionaryPackage>;
28
+ plugins?: string[];
75
29
  }
76
30
  export interface LoadedConfig {
77
31
  path: string;
@@ -0,0 +1,37 @@
1
+ export type VersionaryPluginCapability = "scm.reviewRequest" | "scm.releaseMetadata";
2
+ export interface VersionaryScmReviewRequestInput {
3
+ baseBranch: string;
4
+ headBranch: string;
5
+ title: string;
6
+ body: string;
7
+ labels?: string[];
8
+ }
9
+ export interface VersionaryScmReviewRequestResult {
10
+ id: string;
11
+ number?: number;
12
+ url: string;
13
+ state: "open" | "closed" | "merged";
14
+ }
15
+ export interface VersionaryScmReleaseMetadataInput {
16
+ tag: string;
17
+ version: string;
18
+ notes: string;
19
+ }
20
+ export interface VersionaryScmReleaseMetadataResult {
21
+ url: string;
22
+ status?: "created" | "exists";
23
+ }
24
+ export interface VersionaryPluginContext {
25
+ cwd: string;
26
+ logger?: {
27
+ info: (message: string) => void;
28
+ warn: (message: string) => void;
29
+ error: (message: string) => void;
30
+ };
31
+ }
32
+ export interface VersionaryPluginRuntime {
33
+ name: string;
34
+ capabilities: VersionaryPluginCapability[];
35
+ createOrUpdateReviewRequest?: (input: VersionaryScmReviewRequestInput, context: VersionaryPluginContext) => Promise<VersionaryScmReviewRequestResult>;
36
+ createReleaseMetadata?: (input: VersionaryScmReleaseMetadataInput, context: VersionaryPluginContext) => Promise<VersionaryScmReleaseMetadataResult>;
37
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -7,6 +7,7 @@ exports.verifyProject = verifyProject;
7
7
  const node_fs_1 = __importDefault(require("node:fs"));
8
8
  const node_path_1 = __importDefault(require("node:path"));
9
9
  const load_config_js_1 = require("../config/load-config.js");
10
+ const resolve_js_1 = require("../domain/strategy/resolve.js");
10
11
  function verifyProject(cwd = process.cwd()) {
11
12
  const checks = [];
12
13
  const config = (0, load_config_js_1.loadConfig)(cwd);
@@ -15,23 +16,22 @@ function verifyProject(cwd = process.cwd()) {
15
16
  ok: true,
16
17
  details: `Loaded ${node_path_1.default.basename(config.path)} (${config.format})`,
17
18
  });
18
- if ((config.config.mode ?? "simple") === "simple") {
19
- const versionFile = config.config.simple?.versionFile ?? "version.txt";
20
- const exists = node_fs_1.default.existsSync(node_path_1.default.join(cwd, versionFile));
21
- checks.push({
22
- name: `simple-version-file:${versionFile}`,
23
- ok: exists,
24
- details: exists ? "Version file exists" : `Missing ${versionFile} for simple mode`,
25
- });
26
- }
19
+ const strategy = (0, resolve_js_1.resolveVersionStrategy)(config.config);
20
+ const versionFile = strategy.getVersionFile(config.config);
21
+ const exists = node_fs_1.default.existsSync(node_path_1.default.join(cwd, versionFile));
22
+ checks.push({
23
+ name: `version-file:${versionFile}`,
24
+ ok: exists,
25
+ details: exists ? "Version file exists" : `Missing ${versionFile}`,
26
+ });
27
27
  if (config.config.packages) {
28
- for (const pkg of config.config.packages) {
29
- const pkgPath = node_path_1.default.join(cwd, pkg.path);
28
+ for (const pkgPathRaw of Object.keys(config.config.packages)) {
29
+ const pkgPath = node_path_1.default.join(cwd, pkgPathRaw);
30
30
  const exists = node_fs_1.default.existsSync(pkgPath);
31
31
  checks.push({
32
- name: `package-path:${pkg.path}`,
32
+ name: `package-path:${pkgPathRaw}`,
33
33
  ok: exists,
34
- details: exists ? "Path exists" : `Missing path: ${pkg.path}`,
34
+ details: exists ? "Path exists" : `Missing path: ${pkgPathRaw}`,
35
35
  });
36
36
  }
37
37
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "versionary",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Automatic release framework based on conventional commits and semantic versioning",
5
5
  "keywords": [
6
6
  "releasing",
@@ -18,7 +18,6 @@
18
18
  "license": "MIT",
19
19
  "author": "Johan Larsson",
20
20
  "type": "commonjs",
21
- "packageManager": "pnpm@10.33.0",
22
21
  "bin": {
23
22
  "versionary": "dist/cli/index.js"
24
23
  },
@@ -27,17 +26,8 @@
27
26
  ],
28
27
  "main": "dist/index.js",
29
28
  "types": "dist/index.d.ts",
30
- "scripts": {
31
- "prepare": "pnpm run build",
32
- "build": "tsc -p tsconfig.json",
33
- "typecheck": "tsc -p tsconfig.json --noEmit",
34
- "test": "vitest run",
35
- "verify": "tsx src/cli/index.ts verify",
36
- "plan": "tsx src/cli/index.ts plan",
37
- "changelog": "tsx src/cli/index.ts changelog",
38
- "pr": "tsx src/cli/index.ts pr"
39
- },
40
29
  "dependencies": {
30
+ "@octokit/rest": "^22.0.0",
41
31
  "@iarna/toml": "^2.2.5",
42
32
  "jsonc-parser": "^3.3.1",
43
33
  "zod": "^4.1.12"
@@ -46,6 +36,17 @@
46
36
  "@types/node": "^24.7.2",
47
37
  "tsx": "^4.20.6",
48
38
  "typescript": "^5.9.3",
49
- "vitest": "^3.2.4"
39
+ "vitest": "^4.1.4"
40
+ },
41
+ "scripts": {
42
+ "build": "tsc -p tsconfig.json",
43
+ "typecheck": "tsc -p tsconfig.json --noEmit",
44
+ "test": "vitest run",
45
+ "verify": "tsx src/cli/index.ts verify",
46
+ "run": "tsx src/cli/index.ts run",
47
+ "plan": "tsx src/cli/index.ts plan",
48
+ "changelog": "tsx src/cli/index.ts changelog",
49
+ "pr": "tsx src/cli/index.ts pr",
50
+ "release": "tsx src/cli/index.ts release"
50
51
  }
51
- }
52
+ }
@@ -1,3 +0,0 @@
1
- import type { SimplePlan } from "./plan.js";
2
- export declare function renderSimpleChangelog(plan: SimplePlan): string;
3
- export declare function prependChangelog(cwd: string, changelogFile: string, section: string): void;
@@ -1,32 +0,0 @@
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.renderSimpleChangelog = renderSimpleChangelog;
7
- exports.prependChangelog = prependChangelog;
8
- const node_fs_1 = __importDefault(require("node:fs"));
9
- const node_path_1 = __importDefault(require("node:path"));
10
- function formatDate() {
11
- return new Date().toISOString().slice(0, 10);
12
- }
13
- function renderSimpleChangelog(plan) {
14
- if (!plan.nextVersion) {
15
- return "";
16
- }
17
- const lines = [
18
- `## ${plan.nextVersion} - ${formatDate()}`,
19
- "",
20
- ...plan.commits.map((commit) => `- ${commit.subject} (${commit.hash.slice(0, 7)})`),
21
- "",
22
- ];
23
- return lines.join("\n");
24
- }
25
- function prependChangelog(cwd, changelogFile, section) {
26
- const changelogPath = node_path_1.default.join(cwd, changelogFile);
27
- const existing = node_fs_1.default.existsSync(changelogPath) ? node_fs_1.default.readFileSync(changelogPath, "utf8") : "";
28
- const heading = existing.startsWith("# Changelog") ? "" : "# Changelog\n\n";
29
- const separator = existing.length > 0 ? "\n" : "";
30
- const next = `${heading}${section}${separator}${existing.replace(/^# Changelog\s*/u, "")}`.trimEnd() + "\n";
31
- node_fs_1.default.writeFileSync(changelogPath, next, "utf8");
32
- }
@@ -1,7 +0,0 @@
1
- import type { ReleaseType } from "./semver.js";
2
- export interface CommitInfo {
3
- hash: string;
4
- subject: string;
5
- }
6
- export declare function getCommitsSinceLastTag(cwd?: string): CommitInfo[];
7
- export declare function analyzeCommits(commits: CommitInfo[]): ReleaseType;
@@ -1,61 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getCommitsSinceLastTag = getCommitsSinceLastTag;
4
- exports.analyzeCommits = analyzeCommits;
5
- const node_child_process_1 = require("node:child_process");
6
- function getCommitsSinceLastTag(cwd = process.cwd()) {
7
- const tagsOutput = (0, node_child_process_1.execFileSync)("git", ["tag", "--sort=-v:refname"], {
8
- cwd,
9
- encoding: "utf8",
10
- stdio: ["ignore", "pipe", "ignore"],
11
- });
12
- const baseRef = tagsOutput.trim().split("\n")[0] ?? "";
13
- const range = baseRef ? `${baseRef}..HEAD` : "HEAD";
14
- const output = (0, node_child_process_1.execFileSync)("git", ["log", range, "--pretty=format:%H%x09%s"], {
15
- cwd,
16
- encoding: "utf8",
17
- stdio: ["ignore", "pipe", "ignore"],
18
- });
19
- if (!output.trim()) {
20
- return [];
21
- }
22
- return output
23
- .trim()
24
- .split("\n")
25
- .map((line) => {
26
- const [hash, subject] = line.split("\t");
27
- return { hash, subject };
28
- });
29
- }
30
- function inferReleaseTypeFromSubject(subject) {
31
- if (/^revert:\s/i.test(subject)) {
32
- return null;
33
- }
34
- if (/!:/u.test(subject) || /BREAKING CHANGE/u.test(subject)) {
35
- return "major";
36
- }
37
- if (/^feat(\(.+\))?:\s/i.test(subject)) {
38
- return "minor";
39
- }
40
- if (/^(fix|perf|refactor)(\(.+\))?:\s/i.test(subject)) {
41
- return "patch";
42
- }
43
- return null;
44
- }
45
- function analyzeCommits(commits) {
46
- let result = null;
47
- for (const commit of commits) {
48
- const type = inferReleaseTypeFromSubject(commit.subject);
49
- if (type === "major") {
50
- return "major";
51
- }
52
- if (type === "minor") {
53
- result = "minor";
54
- continue;
55
- }
56
- if (type === "patch" && result === null) {
57
- result = "patch";
58
- }
59
- }
60
- return result;
61
- }
@@ -1,12 +0,0 @@
1
- import { type CommitInfo } from "./git.js";
2
- import { type ReleaseType } from "./semver.js";
3
- export interface SimplePlan {
4
- mode: "simple";
5
- releaseType: ReleaseType;
6
- currentVersion: string;
7
- nextVersion: string | null;
8
- versionFile: string;
9
- changelogFile: string;
10
- commits: CommitInfo[];
11
- }
12
- export declare function createSimplePlan(cwd?: string): SimplePlan;
@@ -1,37 +0,0 @@
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.createSimplePlan = createSimplePlan;
7
- const node_fs_1 = __importDefault(require("node:fs"));
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");
11
- const semver_js_1 = require("./semver.js");
12
- function createSimplePlan(cwd = process.cwd()) {
13
- const loaded = (0, load_config_js_1.loadConfig)(cwd);
14
- const mode = loaded.config.mode ?? "simple";
15
- if (mode !== "simple") {
16
- throw new Error("Simple plan requires mode: simple in config.");
17
- }
18
- const versionFile = loaded.config.simple?.versionFile ?? "version.txt";
19
- const changelogFile = loaded.config.simple?.changelogFile ?? "CHANGELOG.md";
20
- const versionPath = node_path_1.default.join(cwd, versionFile);
21
- if (!node_fs_1.default.existsSync(versionPath)) {
22
- throw new Error(`Simple mode requires ${versionFile} to exist.`);
23
- }
24
- const currentVersion = node_fs_1.default.readFileSync(versionPath, "utf8").trim();
25
- const commits = (0, git_js_1.getCommitsSinceLastTag)(cwd);
26
- const releaseType = (0, git_js_1.analyzeCommits)(commits);
27
- const nextVersion = releaseType ? (0, semver_js_1.bumpVersion)(currentVersion, releaseType) : null;
28
- return {
29
- mode: "simple",
30
- releaseType,
31
- currentVersion,
32
- nextVersion,
33
- versionFile,
34
- changelogFile,
35
- commits,
36
- };
37
- }
@@ -1,5 +0,0 @@
1
- export declare function prepareSimpleReleasePr(cwd?: string): {
2
- branch: string;
3
- title: string;
4
- version: string;
5
- };