versionary 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Johan Larsson
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,34 @@
1
+ # versionary
2
+
3
+ Versionary is a software-agnostic automated release tool focused on SemVer, conventional commits, release PR workflows, and extensibility.
4
+
5
+ Configuration is loaded from `versionary.jsonc` by default.
6
+
7
+ ## Simple mode (MVP)
8
+
9
+ For a quick trial, use:
10
+
11
+ - `versionary.jsonc` with `"mode": "simple"`
12
+ - `version.txt` as the version source
13
+ - `CHANGELOG.md` as release notes output
14
+
15
+ Commands:
16
+
17
+ - `pnpm verify`
18
+ - `pnpm plan`
19
+ - `pnpm changelog -- --write`
20
+ - `pnpm pr`
21
+
22
+ ## Install from GitHub
23
+
24
+ You can install directly from a git ref:
25
+
26
+ ```json
27
+ {
28
+ "devDependencies": {
29
+ "versionary": "github:jolars/versionary#<commit-or-tag>"
30
+ }
31
+ }
32
+ ```
33
+
34
+ The package runs a `prepare` build during git installation so the `versionary` CLI binary is available after `pnpm install`.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const plan_js_1 = require("../simple/plan.js");
10
+ const changelog_js_1 = require("../simple/changelog.js");
11
+ const pr_js_1 = require("../simple/pr.js");
12
+ const verify_project_js_1 = require("../verify/verify-project.js");
13
+ function printVerifyResult() {
14
+ const result = (0, verify_project_js_1.verifyProject)();
15
+ for (const check of result.checks) {
16
+ const status = check.ok ? "OK" : "FAIL";
17
+ console.log(`[${status}] ${check.name} - ${check.details}`);
18
+ }
19
+ return result.ok ? 0 : 1;
20
+ }
21
+ function main() {
22
+ const [, , command, ...args] = process.argv;
23
+ if (command === "verify") {
24
+ return printVerifyResult();
25
+ }
26
+ if (command === "plan") {
27
+ const plan = (0, plan_js_1.createSimplePlan)();
28
+ console.log(JSON.stringify(plan, null, 2));
29
+ return 0;
30
+ }
31
+ if (command === "changelog") {
32
+ const write = args.includes("--write");
33
+ const plan = (0, plan_js_1.createSimplePlan)();
34
+ if (!plan.nextVersion) {
35
+ console.log("No releasable commits found.");
36
+ return 0;
37
+ }
38
+ const section = (0, changelog_js_1.renderSimpleChangelog)(plan);
39
+ if (!write) {
40
+ console.log(section);
41
+ return 0;
42
+ }
43
+ const changelogPath = node_path_1.default.join(process.cwd(), plan.changelogFile);
44
+ const existing = node_fs_1.default.existsSync(changelogPath) ? node_fs_1.default.readFileSync(changelogPath, "utf8") : "";
45
+ const heading = "# Changelog\n\n";
46
+ const body = existing.replace(/^# Changelog\s*/u, "");
47
+ node_fs_1.default.writeFileSync(changelogPath, `${heading}${section}\n${body}`.trimEnd() + "\n", "utf8");
48
+ console.log(`Updated ${plan.changelogFile}`);
49
+ return 0;
50
+ }
51
+ if (command === "pr") {
52
+ const pr = (0, pr_js_1.prepareSimpleReleasePr)();
53
+ console.log(`Prepared release PR branch ${pr.branch}`);
54
+ console.log(`Title: ${pr.title}`);
55
+ return 0;
56
+ }
57
+ console.log("Usage: versionary <command>");
58
+ console.log("Commands:");
59
+ console.log(" verify Validate config and basic repository shape");
60
+ console.log(" plan Print release plan (simple mode)");
61
+ console.log(" changelog [--write] Print or write changelog section");
62
+ console.log(" pr Prepare release PR commit and branch");
63
+ return 1;
64
+ }
65
+ process.exit(main());
@@ -0,0 +1,6 @@
1
+ import type { ConfigFileFormat, LoadedConfig } from "../types/config.js";
2
+ export declare function findConfigFile(cwd: string): {
3
+ path: string;
4
+ format: ConfigFileFormat;
5
+ } | null;
6
+ export declare function loadConfig(cwd?: string): LoadedConfig;
@@ -0,0 +1,54 @@
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.findConfigFile = findConfigFile;
7
+ exports.loadConfig = loadConfig;
8
+ const node_fs_1 = __importDefault(require("node:fs"));
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ const toml_1 = require("@iarna/toml");
11
+ const jsonc_parser_1 = require("jsonc-parser");
12
+ const schema_js_1 = require("./schema.js");
13
+ const SUPPORTED_FILES = [
14
+ { file: "versionary.jsonc", format: "jsonc" },
15
+ { 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
+ ];
23
+ function parseConfig(raw, format) {
24
+ if (format === "json" || format === "jsonc") {
25
+ return (0, jsonc_parser_1.parse)(raw);
26
+ }
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.");
31
+ }
32
+ function findConfigFile(cwd) {
33
+ for (const candidate of SUPPORTED_FILES) {
34
+ const candidatePath = node_path_1.default.join(cwd, candidate.file);
35
+ if (node_fs_1.default.existsSync(candidatePath)) {
36
+ return { path: candidatePath, format: candidate.format };
37
+ }
38
+ }
39
+ return null;
40
+ }
41
+ function loadConfig(cwd = process.cwd()) {
42
+ const found = findConfigFile(cwd);
43
+ if (!found) {
44
+ throw new Error("No Versionary config found. Create versionary.jsonc (preferred), .json, or .toml.");
45
+ }
46
+ const raw = node_fs_1.default.readFileSync(found.path, "utf8");
47
+ const parsed = parseConfig(raw, found.format);
48
+ const validated = schema_js_1.configSchema.parse(parsed);
49
+ return {
50
+ path: found.path,
51
+ format: found.format,
52
+ config: validated,
53
+ };
54
+ }
@@ -0,0 +1,99 @@
1
+ import { z } from "zod";
2
+ export declare const configSchema: z.ZodObject<{
3
+ version: z.ZodLiteral<1>;
4
+ mode: z.ZodOptional<z.ZodEnum<{
5
+ simple: "simple";
6
+ standard: "standard";
7
+ }>>;
8
+ history: z.ZodOptional<z.ZodObject<{
9
+ bootstrap: z.ZodOptional<z.ZodObject<{
10
+ sha: z.ZodOptional<z.ZodString>;
11
+ tag: z.ZodOptional<z.ZodString>;
12
+ }, z.core.$strip>>;
13
+ }, z.core.$strip>>;
14
+ monorepo: z.ZodOptional<z.ZodObject<{
15
+ mode: z.ZodDefault<z.ZodEnum<{
16
+ independent: "independent";
17
+ fixed: "fixed";
18
+ }>>;
19
+ }, z.core.$strip>>;
20
+ defaults: z.ZodOptional<z.ZodObject<{
21
+ strategy: z.ZodOptional<z.ZodString>;
22
+ versioning: z.ZodOptional<z.ZodObject<{
23
+ bumpMinorPreMajor: z.ZodOptional<z.ZodBoolean>;
24
+ }, z.core.$strip>>;
25
+ changelog: z.ZodOptional<z.ZodObject<{
26
+ includeAuthors: z.ZodOptional<z.ZodBoolean>;
27
+ }, z.core.$strip>>;
28
+ commitConventions: z.ZodOptional<z.ZodObject<{
29
+ preset: z.ZodDefault<z.ZodEnum<{
30
+ conventional: "conventional";
31
+ angular: "angular";
32
+ custom: "custom";
33
+ }>>;
34
+ }, z.core.$strip>>;
35
+ }, z.core.$strip>>;
36
+ packages: z.ZodOptional<z.ZodArray<z.ZodObject<{
37
+ path: z.ZodString;
38
+ strategy: z.ZodOptional<z.ZodString>;
39
+ packageName: z.ZodOptional<z.ZodString>;
40
+ excludePaths: z.ZodOptional<z.ZodArray<z.ZodString>>;
41
+ artifacts: z.ZodOptional<z.ZodArray<z.ZodObject<{
42
+ file: z.ZodString;
43
+ format: z.ZodEnum<{
44
+ json: "json";
45
+ toml: "toml";
46
+ yaml: "yaml";
47
+ regex: "regex";
48
+ }>;
49
+ path: z.ZodOptional<z.ZodString>;
50
+ pattern: z.ZodOptional<z.ZodString>;
51
+ }, z.core.$strip>>>;
52
+ }, z.core.$strip>>>;
53
+ pluginConfig: z.ZodOptional<z.ZodObject<{
54
+ extends: z.ZodOptional<z.ZodArray<z.ZodObject<{
55
+ name: z.ZodString;
56
+ }, z.core.$strip>>>;
57
+ globalOptions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
58
+ execution: z.ZodOptional<z.ZodArray<z.ZodObject<{
59
+ step: z.ZodEnum<{
60
+ verifyConditions: "verifyConditions";
61
+ analyzeCommits: "analyzeCommits";
62
+ resolveReverts: "resolveReverts";
63
+ verifyRelease: "verifyRelease";
64
+ generateNotes: "generateNotes";
65
+ updateArtifacts: "updateArtifacts";
66
+ preparePr: "preparePr";
67
+ publish: "publish";
68
+ postRelease: "postRelease";
69
+ success: "success";
70
+ fail: "fail";
71
+ }>;
72
+ lifecycle: z.ZodOptional<z.ZodArray<z.ZodEnum<{
73
+ plan: "plan";
74
+ pr: "pr";
75
+ release: "release";
76
+ }>>>;
77
+ merge: z.ZodOptional<z.ZodEnum<{
78
+ highest: "highest";
79
+ concat: "concat";
80
+ override: "override";
81
+ reduce: "reduce";
82
+ }>>;
83
+ }, z.core.$strip>>>;
84
+ plugins: z.ZodOptional<z.ZodArray<z.ZodObject<{
85
+ name: z.ZodString;
86
+ options: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
87
+ }, z.core.$strip>>>;
88
+ }, z.core.$strip>>;
89
+ plugins: z.ZodOptional<z.ZodArray<z.ZodObject<{
90
+ name: z.ZodString;
91
+ options: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
92
+ }, z.core.$strip>>>;
93
+ simple: z.ZodOptional<z.ZodObject<{
94
+ versionFile: z.ZodOptional<z.ZodString>;
95
+ changelogFile: z.ZodOptional<z.ZodString>;
96
+ releaseBranchPrefix: z.ZodOptional<z.ZodString>;
97
+ }, z.core.$strip>>;
98
+ }, z.core.$strip>;
99
+ export type ConfigSchema = z.infer<typeof configSchema>;
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.configSchema = void 0;
4
+ const zod_1 = require("zod");
5
+ const artifactRuleSchema = zod_1.z.object({
6
+ file: zod_1.z.string().min(1),
7
+ format: zod_1.z.enum(["json", "toml", "yaml", "regex"]),
8
+ path: zod_1.z.string().optional(),
9
+ pattern: zod_1.z.string().optional(),
10
+ });
11
+ const packageSchema = zod_1.z.object({
12
+ path: zod_1.z.string().min(1),
13
+ strategy: zod_1.z.string().optional(),
14
+ packageName: zod_1.z.string().optional(),
15
+ excludePaths: zod_1.z.array(zod_1.z.string()).optional(),
16
+ artifacts: zod_1.z.array(artifactRuleSchema).optional(),
17
+ });
18
+ const pluginSchema = zod_1.z.object({
19
+ name: zod_1.z.string().min(1),
20
+ options: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).optional(),
21
+ });
22
+ const pluginExecutionSchema = zod_1.z.object({
23
+ step: zod_1.z.enum([
24
+ "verifyConditions",
25
+ "analyzeCommits",
26
+ "resolveReverts",
27
+ "verifyRelease",
28
+ "generateNotes",
29
+ "updateArtifacts",
30
+ "preparePr",
31
+ "publish",
32
+ "postRelease",
33
+ "success",
34
+ "fail",
35
+ ]),
36
+ lifecycle: zod_1.z.array(zod_1.z.enum(["plan", "pr", "release"])).optional(),
37
+ merge: zod_1.z.enum(["highest", "concat", "override", "reduce"]).optional(),
38
+ });
39
+ const pluginConfigSchema = zod_1.z.object({
40
+ extends: zod_1.z.array(zod_1.z.object({ name: zod_1.z.string().min(1) })).optional(),
41
+ globalOptions: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).optional(),
42
+ execution: zod_1.z.array(pluginExecutionSchema).optional(),
43
+ plugins: zod_1.z.array(pluginSchema).optional(),
44
+ });
45
+ exports.configSchema = zod_1.z.object({
46
+ version: zod_1.z.literal(1),
47
+ mode: zod_1.z.enum(["simple", "standard"]).optional(),
48
+ history: zod_1.z
49
+ .object({
50
+ bootstrap: zod_1.z
51
+ .object({
52
+ sha: zod_1.z.string().optional(),
53
+ tag: zod_1.z.string().optional(),
54
+ })
55
+ .optional(),
56
+ })
57
+ .optional(),
58
+ monorepo: zod_1.z
59
+ .object({
60
+ mode: zod_1.z.enum(["independent", "fixed"]).default("independent"),
61
+ })
62
+ .optional(),
63
+ defaults: zod_1.z
64
+ .object({
65
+ strategy: zod_1.z.string().optional(),
66
+ versioning: zod_1.z
67
+ .object({
68
+ bumpMinorPreMajor: zod_1.z.boolean().optional(),
69
+ })
70
+ .optional(),
71
+ changelog: zod_1.z
72
+ .object({
73
+ includeAuthors: zod_1.z.boolean().optional(),
74
+ })
75
+ .optional(),
76
+ commitConventions: zod_1.z
77
+ .object({
78
+ preset: zod_1.z.enum(["conventional", "angular", "custom"]).default("conventional"),
79
+ })
80
+ .optional(),
81
+ })
82
+ .optional(),
83
+ packages: zod_1.z.array(packageSchema).optional(),
84
+ pluginConfig: pluginConfigSchema.optional(),
85
+ plugins: zod_1.z.array(pluginSchema).optional(),
86
+ simple: zod_1.z
87
+ .object({
88
+ versionFile: zod_1.z.string().optional(),
89
+ changelogFile: zod_1.z.string().optional(),
90
+ releaseBranchPrefix: zod_1.z.string().optional(),
91
+ })
92
+ .optional(),
93
+ });
@@ -0,0 +1,3 @@
1
+ export type { VersionaryConfig, VersionaryPackage, VersionaryPluginRef, VersionaryArtifactRule, } from "./types/config.js";
2
+ export { loadConfig } from "./config/load-config.js";
3
+ export { verifyProject } from "./verify/verify-project.js";
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.verifyProject = exports.loadConfig = void 0;
4
+ var load_config_js_1 = require("./config/load-config.js");
5
+ Object.defineProperty(exports, "loadConfig", { enumerable: true, get: function () { return load_config_js_1.loadConfig; } });
6
+ var verify_project_js_1 = require("./verify/verify-project.js");
7
+ Object.defineProperty(exports, "verifyProject", { enumerable: true, get: function () { return verify_project_js_1.verifyProject; } });
@@ -0,0 +1,3 @@
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;
@@ -0,0 +1,32 @@
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
+ }
@@ -0,0 +1,7 @@
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;
@@ -0,0 +1,61 @@
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
+ }
@@ -0,0 +1,12 @@
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;
@@ -0,0 +1,37 @@
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
+ }
@@ -0,0 +1,5 @@
1
+ export declare function prepareSimpleReleasePr(cwd?: string): {
2
+ branch: string;
3
+ title: string;
4
+ version: string;
5
+ };
@@ -0,0 +1,45 @@
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.prepareSimpleReleasePr = prepareSimpleReleasePr;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const node_child_process_1 = require("node:child_process");
10
+ const plan_js_1 = require("./plan.js");
11
+ const changelog_js_1 = require("./changelog.js");
12
+ function ensureCleanWorktree(cwd) {
13
+ const status = (0, node_child_process_1.execFileSync)("git", ["status", "--porcelain"], {
14
+ cwd,
15
+ encoding: "utf8",
16
+ stdio: ["ignore", "pipe", "ignore"],
17
+ }).trim();
18
+ if (status.length > 0) {
19
+ throw new Error("Working tree is not clean. Commit or stash changes before `versionary pr`.");
20
+ }
21
+ }
22
+ function prepareSimpleReleasePr(cwd = process.cwd()) {
23
+ const plan = (0, plan_js_1.createSimplePlan)(cwd);
24
+ if (!plan.nextVersion) {
25
+ throw new Error("No releasable commits found. Nothing to open a release PR for.");
26
+ }
27
+ ensureCleanWorktree(cwd);
28
+ const versionPath = node_path_1.default.join(cwd, plan.versionFile);
29
+ node_fs_1.default.writeFileSync(versionPath, `${plan.nextVersion}\n`, "utf8");
30
+ const section = (0, changelog_js_1.renderSimpleChangelog)(plan);
31
+ (0, changelog_js_1.prependChangelog)(cwd, plan.changelogFile, section);
32
+ const branch = `versionary/release-v${plan.nextVersion}`;
33
+ const title = `chore(release): v${plan.nextVersion}`;
34
+ (0, node_child_process_1.execFileSync)("git", ["checkout", "-B", branch], { cwd, stdio: ["ignore", "pipe", "ignore"] });
35
+ (0, node_child_process_1.execFileSync)("git", ["add", plan.versionFile, plan.changelogFile], {
36
+ cwd,
37
+ stdio: ["ignore", "pipe", "ignore"],
38
+ });
39
+ (0, node_child_process_1.execFileSync)("git", ["commit", "-m", title], { cwd, stdio: ["ignore", "pipe", "ignore"] });
40
+ return {
41
+ branch,
42
+ title,
43
+ version: plan.nextVersion,
44
+ };
45
+ }
@@ -0,0 +1,8 @@
1
+ export type ReleaseType = "major" | "minor" | "patch" | null;
2
+ export interface ParsedVersion {
3
+ major: number;
4
+ minor: number;
5
+ patch: number;
6
+ }
7
+ export declare function parseVersion(version: string): ParsedVersion;
8
+ export declare function bumpVersion(current: string, releaseType: Exclude<ReleaseType, null>): string;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseVersion = parseVersion;
4
+ exports.bumpVersion = bumpVersion;
5
+ function parseVersion(version) {
6
+ const match = version.trim().match(/^(\d+)\.(\d+)\.(\d+)$/);
7
+ if (!match) {
8
+ throw new Error(`Invalid version in version file: "${version}". Expected x.y.z`);
9
+ }
10
+ return {
11
+ major: Number(match[1]),
12
+ minor: Number(match[2]),
13
+ patch: Number(match[3]),
14
+ };
15
+ }
16
+ function bumpVersion(current, releaseType) {
17
+ const parsed = parseVersion(current);
18
+ if (releaseType === "major") {
19
+ return `${parsed.major + 1}.0.0`;
20
+ }
21
+ if (releaseType === "minor") {
22
+ return `${parsed.major}.${parsed.minor + 1}.0`;
23
+ }
24
+ return `${parsed.major}.${parsed.minor}.${parsed.patch + 1}`;
25
+ }
@@ -0,0 +1,80 @@
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
+ export interface VersionaryArtifactRule {
28
+ file: string;
29
+ format: "json" | "toml" | "yaml" | "regex";
30
+ path?: string;
31
+ pattern?: string;
32
+ }
33
+ 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[];
60
+ }
61
+ export interface VersionaryConfig {
62
+ 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
+ };
75
+ }
76
+ export interface LoadedConfig {
77
+ path: string;
78
+ format: ConfigFileFormat;
79
+ config: VersionaryConfig;
80
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,9 @@
1
+ export interface VerifyResult {
2
+ ok: boolean;
3
+ checks: Array<{
4
+ name: string;
5
+ ok: boolean;
6
+ details: string;
7
+ }>;
8
+ }
9
+ export declare function verifyProject(cwd?: string): VerifyResult;
@@ -0,0 +1,40 @@
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.verifyProject = verifyProject;
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
+ function verifyProject(cwd = process.cwd()) {
11
+ const checks = [];
12
+ const config = (0, load_config_js_1.loadConfig)(cwd);
13
+ checks.push({
14
+ name: "config-load",
15
+ ok: true,
16
+ details: `Loaded ${node_path_1.default.basename(config.path)} (${config.format})`,
17
+ });
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
+ }
27
+ if (config.config.packages) {
28
+ for (const pkg of config.config.packages) {
29
+ const pkgPath = node_path_1.default.join(cwd, pkg.path);
30
+ const exists = node_fs_1.default.existsSync(pkgPath);
31
+ checks.push({
32
+ name: `package-path:${pkg.path}`,
33
+ ok: exists,
34
+ details: exists ? "Path exists" : `Missing path: ${pkg.path}`,
35
+ });
36
+ }
37
+ }
38
+ const ok = checks.every((c) => c.ok);
39
+ return { ok, checks };
40
+ }
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "versionary",
3
+ "version": "0.1.0",
4
+ "description": "Automatic release framework based on conventional commits and semantic versioning",
5
+ "keywords": [
6
+ "releasing",
7
+ "semantic versioning",
8
+ "conventional commits"
9
+ ],
10
+ "homepage": "https://github.com/jolars/versionary",
11
+ "bugs": {
12
+ "url": "https://github.com/jolars/versionary/issues"
13
+ },
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/jolars/versionary.git"
17
+ },
18
+ "license": "MIT",
19
+ "author": "Johan Larsson",
20
+ "type": "commonjs",
21
+ "packageManager": "pnpm@10.33.0",
22
+ "bin": {
23
+ "versionary": "dist/cli/index.js"
24
+ },
25
+ "files": [
26
+ "dist"
27
+ ],
28
+ "main": "dist/index.js",
29
+ "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
+ "dependencies": {
41
+ "@iarna/toml": "^2.2.5",
42
+ "jsonc-parser": "^3.3.1",
43
+ "zod": "^4.1.12"
44
+ },
45
+ "devDependencies": {
46
+ "@types/node": "^24.7.2",
47
+ "tsx": "^4.20.6",
48
+ "typescript": "^5.9.3",
49
+ "vitest": "^3.2.4"
50
+ }
51
+ }