versionary 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -6
- package/dist/cli/index.js +43 -2
- package/dist/config/load-config.js +6 -0
- package/dist/config/schema.d.ts +24 -85
- package/dist/config/schema.js +19 -81
- package/dist/index.d.ts +4 -1
- package/dist/index.js +6 -1
- package/dist/plugins/capabilities.d.ts +3 -0
- package/dist/plugins/capabilities.js +10 -0
- package/dist/plugins/runtime.d.ts +2 -0
- package/dist/plugins/runtime.js +24 -0
- package/dist/scm/github-plugin.d.ts +2 -0
- package/dist/scm/github-plugin.js +114 -0
- package/dist/simple/changelog.js +16 -2
- package/dist/simple/git.d.ts +4 -1
- package/dist/simple/git.js +59 -6
- package/dist/simple/plan.d.ts +8 -0
- package/dist/simple/plan.js +73 -13
- package/dist/simple/pr.d.ts +10 -0
- package/dist/simple/pr.js +155 -6
- package/dist/simple/release.d.ts +1 -0
- package/dist/simple/release.js +77 -0
- package/dist/simple/repo-url.d.ts +1 -0
- package/dist/simple/repo-url.js +27 -0
- package/dist/simple/state.d.ts +3 -0
- package/dist/simple/state.js +47 -0
- package/dist/types/config.d.ts +19 -66
- package/dist/types/plugins.d.ts +36 -0
- package/dist/types/plugins.js +2 -0
- package/dist/verify/verify-project.js +11 -13
- package/package.json +15 -14
package/dist/simple/git.js
CHANGED
|
@@ -1,17 +1,47 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.getCommitsSinceLastTag = getCommitsSinceLastTag;
|
|
7
|
+
exports.getCommitsForPath = getCommitsForPath;
|
|
8
|
+
exports.inferReleaseTypeFromSubject = inferReleaseTypeFromSubject;
|
|
9
|
+
exports.isReleasableCommit = isReleasableCommit;
|
|
4
10
|
exports.analyzeCommits = analyzeCommits;
|
|
11
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
5
12
|
const node_child_process_1 = require("node:child_process");
|
|
6
|
-
function
|
|
7
|
-
const
|
|
13
|
+
function getReleaseBranchExcludeArgs(cwd) {
|
|
14
|
+
const releaseBranchesRaw = (0, node_child_process_1.execFileSync)("git", ["branch", "--list", "--format", "%(refname:short)"], {
|
|
8
15
|
cwd,
|
|
9
16
|
encoding: "utf8",
|
|
10
17
|
stdio: ["ignore", "pipe", "ignore"],
|
|
11
18
|
});
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
19
|
+
const releaseBranches = releaseBranchesRaw
|
|
20
|
+
.split("\n")
|
|
21
|
+
.map((line) => line.trim())
|
|
22
|
+
.filter((line) => line.startsWith("versionary/release"));
|
|
23
|
+
return releaseBranches.flatMap((branch) => ["--exclude", branch]);
|
|
24
|
+
}
|
|
25
|
+
function resolveBaseRef(cwd, baselineSha) {
|
|
26
|
+
const excludeArgs = getReleaseBranchExcludeArgs(cwd);
|
|
27
|
+
let baseRef = baselineSha ?? "";
|
|
28
|
+
if (!baseRef) {
|
|
29
|
+
try {
|
|
30
|
+
const cmd = ["describe", "--tags", "--abbrev=0", "--match", "v[0-9]*", ...excludeArgs];
|
|
31
|
+
baseRef = (0, node_child_process_1.execFileSync)("git", cmd, {
|
|
32
|
+
cwd,
|
|
33
|
+
encoding: "utf8",
|
|
34
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
35
|
+
}).trim();
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
baseRef = "";
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return baseRef;
|
|
42
|
+
}
|
|
43
|
+
function readGitLog(cwd, range, pathspecs = []) {
|
|
44
|
+
const output = (0, node_child_process_1.execFileSync)("git", ["log", range, "--pretty=format:%H%x09%s", "--", ...pathspecs], {
|
|
15
45
|
cwd,
|
|
16
46
|
encoding: "utf8",
|
|
17
47
|
stdio: ["ignore", "pipe", "ignore"],
|
|
@@ -27,21 +57,44 @@ function getCommitsSinceLastTag(cwd = process.cwd()) {
|
|
|
27
57
|
return { hash, subject };
|
|
28
58
|
});
|
|
29
59
|
}
|
|
60
|
+
function getCommitsSinceLastTag(cwd = process.cwd(), baselineSha) {
|
|
61
|
+
const baseRef = resolveBaseRef(cwd, baselineSha);
|
|
62
|
+
const range = baseRef ? `${baseRef}..HEAD` : "HEAD";
|
|
63
|
+
return readGitLog(cwd, range);
|
|
64
|
+
}
|
|
65
|
+
function getCommitsForPath(cwd = process.cwd(), baselineSha, packagePath = ".", excludePaths = []) {
|
|
66
|
+
const baseRef = resolveBaseRef(cwd, baselineSha);
|
|
67
|
+
const range = baseRef ? `${baseRef}..HEAD` : "HEAD";
|
|
68
|
+
const normalizedPackagePath = packagePath === "." ? "." : packagePath;
|
|
69
|
+
const excludes = excludePaths.map((excludePath) => {
|
|
70
|
+
const combined = normalizedPackagePath === "."
|
|
71
|
+
? excludePath
|
|
72
|
+
: node_path_1.default.posix.join(normalizedPackagePath, excludePath);
|
|
73
|
+
return `:(exclude)${combined}`;
|
|
74
|
+
});
|
|
75
|
+
return readGitLog(cwd, range, [normalizedPackagePath, ...excludes]);
|
|
76
|
+
}
|
|
30
77
|
function inferReleaseTypeFromSubject(subject) {
|
|
31
78
|
if (/^revert:\s/i.test(subject)) {
|
|
32
79
|
return null;
|
|
33
80
|
}
|
|
81
|
+
if (/^chore(\(.+\))?:\s/i.test(subject)) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
34
84
|
if (/!:/u.test(subject) || /BREAKING CHANGE/u.test(subject)) {
|
|
35
85
|
return "major";
|
|
36
86
|
}
|
|
37
87
|
if (/^feat(\(.+\))?:\s/i.test(subject)) {
|
|
38
88
|
return "minor";
|
|
39
89
|
}
|
|
40
|
-
if (/^(fix|perf
|
|
90
|
+
if (/^(fix|perf)(\(.+\))?:\s/i.test(subject)) {
|
|
41
91
|
return "patch";
|
|
42
92
|
}
|
|
43
93
|
return null;
|
|
44
94
|
}
|
|
95
|
+
function isReleasableCommit(subject) {
|
|
96
|
+
return inferReleaseTypeFromSubject(subject) !== null;
|
|
97
|
+
}
|
|
45
98
|
function analyzeCommits(commits) {
|
|
46
99
|
let result = null;
|
|
47
100
|
for (const commit of commits) {
|
package/dist/simple/plan.d.ts
CHANGED
|
@@ -7,6 +7,14 @@ export interface SimplePlan {
|
|
|
7
7
|
nextVersion: string | null;
|
|
8
8
|
versionFile: string;
|
|
9
9
|
changelogFile: string;
|
|
10
|
+
releaseBranchPrefix: string;
|
|
11
|
+
baselineSha: string | null;
|
|
10
12
|
commits: CommitInfo[];
|
|
13
|
+
packages?: Array<{
|
|
14
|
+
path: string;
|
|
15
|
+
releaseType: ReleaseType;
|
|
16
|
+
nextVersion: string | null;
|
|
17
|
+
commits: CommitInfo[];
|
|
18
|
+
}>;
|
|
11
19
|
}
|
|
12
20
|
export declare function createSimplePlan(cwd?: string): SimplePlan;
|
package/dist/simple/plan.js
CHANGED
|
@@ -9,29 +9,89 @@ const node_path_1 = __importDefault(require("node:path"));
|
|
|
9
9
|
const load_config_js_1 = require("../config/load-config.js");
|
|
10
10
|
const git_js_1 = require("./git.js");
|
|
11
11
|
const semver_js_1 = require("./semver.js");
|
|
12
|
+
const state_js_1 = require("./state.js");
|
|
13
|
+
function getMode(configMode) {
|
|
14
|
+
return configMode ?? "independent";
|
|
15
|
+
}
|
|
12
16
|
function createSimplePlan(cwd = process.cwd()) {
|
|
13
17
|
const loaded = (0, load_config_js_1.loadConfig)(cwd);
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const versionFile = loaded.config.simple?.versionFile ?? "version.txt";
|
|
19
|
-
const changelogFile = loaded.config.simple?.changelogFile ?? "CHANGELOG.md";
|
|
18
|
+
const versionFile = loaded.config["version-file"] ?? "version.txt";
|
|
19
|
+
const changelogFile = loaded.config["changelog-file"] ?? "CHANGELOG.md";
|
|
20
|
+
const releaseBranchPrefix = loaded.config["release-branch"] ?? "versionary/release";
|
|
21
|
+
const baselineSha = (0, state_js_1.readBaselineSha)(cwd) ?? loaded.config["bootstrap-sha"] ?? null;
|
|
20
22
|
const versionPath = node_path_1.default.join(cwd, versionFile);
|
|
21
23
|
if (!node_fs_1.default.existsSync(versionPath)) {
|
|
22
|
-
throw new Error(`
|
|
24
|
+
throw new Error(`Versionary requires ${versionFile} to exist.`);
|
|
23
25
|
}
|
|
24
26
|
const currentVersion = node_fs_1.default.readFileSync(versionPath, "utf8").trim();
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
const configuredPackages = Object.entries(loaded.config.packages ?? {}).map(([pkgPath, cfg]) => ({
|
|
28
|
+
path: pkgPath,
|
|
29
|
+
...cfg,
|
|
30
|
+
}));
|
|
31
|
+
const monorepoMode = getMode(loaded.config["monorepo-mode"]);
|
|
32
|
+
const hasPackages = configuredPackages.length > 0;
|
|
33
|
+
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
|
+
return {
|
|
38
|
+
mode: "simple",
|
|
39
|
+
releaseType,
|
|
40
|
+
currentVersion,
|
|
41
|
+
nextVersion,
|
|
42
|
+
versionFile,
|
|
43
|
+
changelogFile,
|
|
44
|
+
releaseBranchPrefix,
|
|
45
|
+
baselineSha,
|
|
46
|
+
commits,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
const packagePlans = configuredPackages
|
|
50
|
+
.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;
|
|
54
|
+
return {
|
|
55
|
+
path: pkg.path,
|
|
56
|
+
releaseType,
|
|
57
|
+
nextVersion,
|
|
58
|
+
commits,
|
|
59
|
+
};
|
|
60
|
+
})
|
|
61
|
+
.sort((a, b) => a.path.localeCompare(b.path));
|
|
62
|
+
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;
|
|
65
|
+
const adjusted = packagePlans.map((pkgPlan) => ({
|
|
66
|
+
...pkgPlan,
|
|
67
|
+
releaseType: fixedType,
|
|
68
|
+
nextVersion: fixedNextVersion,
|
|
69
|
+
}));
|
|
70
|
+
return {
|
|
71
|
+
mode: "simple",
|
|
72
|
+
releaseType: fixedType,
|
|
73
|
+
currentVersion,
|
|
74
|
+
nextVersion: fixedNextVersion,
|
|
75
|
+
versionFile,
|
|
76
|
+
changelogFile,
|
|
77
|
+
releaseBranchPrefix,
|
|
78
|
+
baselineSha,
|
|
79
|
+
commits: adjusted.flatMap((pkgPlan) => pkgPlan.commits),
|
|
80
|
+
packages: adjusted,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
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;
|
|
28
85
|
return {
|
|
29
86
|
mode: "simple",
|
|
30
|
-
releaseType,
|
|
87
|
+
releaseType: overallType,
|
|
31
88
|
currentVersion,
|
|
32
|
-
nextVersion,
|
|
89
|
+
nextVersion: overallNextVersion,
|
|
33
90
|
versionFile,
|
|
34
91
|
changelogFile,
|
|
35
|
-
|
|
92
|
+
releaseBranchPrefix,
|
|
93
|
+
baselineSha,
|
|
94
|
+
commits: packagePlans.flatMap((pkgPlan) => pkgPlan.commits),
|
|
95
|
+
packages: packagePlans,
|
|
36
96
|
};
|
|
37
97
|
}
|
package/dist/simple/pr.d.ts
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
|
+
import type { CommitInfo } from "./git.js";
|
|
2
|
+
export declare function splitSafeDirtyFiles(files: string[]): {
|
|
3
|
+
ignored: string[];
|
|
4
|
+
blocking: string[];
|
|
5
|
+
};
|
|
1
6
|
export declare function prepareSimpleReleasePr(cwd?: string): {
|
|
2
7
|
branch: string;
|
|
3
8
|
title: string;
|
|
4
9
|
version: string;
|
|
10
|
+
commits: CommitInfo[];
|
|
5
11
|
};
|
|
12
|
+
export declare function renderSimpleReviewRequestBody(version: string, commits: CommitInfo[], cwd?: string): string;
|
|
13
|
+
export declare function openOrUpdateSimpleReviewRequest(cwd: string, branch: string, title: string, version: string, commits: CommitInfo[]): Promise<string>;
|
|
14
|
+
export declare function pushReleaseBranch(cwd: string, branch: string): void;
|
|
15
|
+
export declare function isReleaseCommitMessage(subject: string): boolean;
|
package/dist/simple/pr.js
CHANGED
|
@@ -3,20 +3,68 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.splitSafeDirtyFiles = splitSafeDirtyFiles;
|
|
6
7
|
exports.prepareSimpleReleasePr = prepareSimpleReleasePr;
|
|
8
|
+
exports.renderSimpleReviewRequestBody = renderSimpleReviewRequestBody;
|
|
9
|
+
exports.openOrUpdateSimpleReviewRequest = openOrUpdateSimpleReviewRequest;
|
|
10
|
+
exports.pushReleaseBranch = pushReleaseBranch;
|
|
11
|
+
exports.isReleaseCommitMessage = isReleaseCommitMessage;
|
|
7
12
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
13
|
const node_path_1 = __importDefault(require("node:path"));
|
|
9
14
|
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");
|
|
10
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");
|
|
11
21
|
const changelog_js_1 = require("./changelog.js");
|
|
12
|
-
|
|
13
|
-
|
|
22
|
+
const state_js_1 = require("./state.js");
|
|
23
|
+
const SAFE_DIRTY_FILES = new Set([
|
|
24
|
+
"pnpm-lock.yaml",
|
|
25
|
+
"package-lock.json",
|
|
26
|
+
"yarn.lock",
|
|
27
|
+
"bun.lockb",
|
|
28
|
+
"npm-shrinkwrap.json",
|
|
29
|
+
]);
|
|
30
|
+
function listTrackedDirtyFiles(cwd) {
|
|
31
|
+
const status = (0, node_child_process_1.execFileSync)("git", ["status", "--porcelain", "--untracked-files=no"], {
|
|
14
32
|
cwd,
|
|
15
33
|
encoding: "utf8",
|
|
16
34
|
stdio: ["ignore", "pipe", "ignore"],
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
|
|
35
|
+
});
|
|
36
|
+
return status
|
|
37
|
+
.split("\n")
|
|
38
|
+
.filter((line) => line.length > 0)
|
|
39
|
+
.map((line) => line.slice(3))
|
|
40
|
+
.map((pathPart) => {
|
|
41
|
+
const renameParts = pathPart.split(" -> ");
|
|
42
|
+
return renameParts.at(-1) ?? pathPart;
|
|
43
|
+
})
|
|
44
|
+
.map((filePath) => filePath.trim())
|
|
45
|
+
.filter((filePath) => filePath.length > 0);
|
|
46
|
+
}
|
|
47
|
+
function splitSafeDirtyFiles(files) {
|
|
48
|
+
const ignored = [];
|
|
49
|
+
const blocking = [];
|
|
50
|
+
for (const file of files) {
|
|
51
|
+
const basename = node_path_1.default.basename(file);
|
|
52
|
+
if (SAFE_DIRTY_FILES.has(basename)) {
|
|
53
|
+
ignored.push(file);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
blocking.push(file);
|
|
57
|
+
}
|
|
58
|
+
return { ignored, blocking };
|
|
59
|
+
}
|
|
60
|
+
function ensureCleanWorktree(cwd) {
|
|
61
|
+
const dirtyFiles = listTrackedDirtyFiles(cwd);
|
|
62
|
+
const { ignored, blocking } = splitSafeDirtyFiles(dirtyFiles);
|
|
63
|
+
if (blocking.length > 0) {
|
|
64
|
+
throw new Error(`Working tree has tracked modifications before versionary pr:\n${blocking.join("\n")}\nCommit/stash tracked changes first.`);
|
|
65
|
+
}
|
|
66
|
+
if (ignored.length > 0) {
|
|
67
|
+
console.warn(`Ignoring safe tracked changes before versionary pr:\n${ignored.join("\n")}`);
|
|
20
68
|
}
|
|
21
69
|
}
|
|
22
70
|
function prepareSimpleReleasePr(cwd = process.cwd()) {
|
|
@@ -29,7 +77,7 @@ function prepareSimpleReleasePr(cwd = process.cwd()) {
|
|
|
29
77
|
node_fs_1.default.writeFileSync(versionPath, `${plan.nextVersion}\n`, "utf8");
|
|
30
78
|
const section = (0, changelog_js_1.renderSimpleChangelog)(plan);
|
|
31
79
|
(0, changelog_js_1.prependChangelog)(cwd, plan.changelogFile, section);
|
|
32
|
-
const branch =
|
|
80
|
+
const branch = plan.releaseBranchPrefix;
|
|
33
81
|
const title = `chore(release): v${plan.nextVersion}`;
|
|
34
82
|
(0, node_child_process_1.execFileSync)("git", ["checkout", "-B", branch], { cwd, stdio: ["ignore", "pipe", "ignore"] });
|
|
35
83
|
(0, node_child_process_1.execFileSync)("git", ["add", plan.versionFile, plan.changelogFile], {
|
|
@@ -37,9 +85,110 @@ function prepareSimpleReleasePr(cwd = process.cwd()) {
|
|
|
37
85
|
stdio: ["ignore", "pipe", "ignore"],
|
|
38
86
|
});
|
|
39
87
|
(0, node_child_process_1.execFileSync)("git", ["commit", "-m", title], { cwd, stdio: ["ignore", "pipe", "ignore"] });
|
|
88
|
+
(0, state_js_1.writeBaselineSha)(cwd);
|
|
89
|
+
(0, node_child_process_1.execFileSync)("git", ["add", (0, state_js_1.getBaselineStatePath)(cwd)], {
|
|
90
|
+
cwd,
|
|
91
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
92
|
+
});
|
|
93
|
+
(0, node_child_process_1.execFileSync)("git", ["commit", "--amend", "--no-edit"], { cwd, stdio: ["ignore", "pipe", "ignore"] });
|
|
40
94
|
return {
|
|
41
95
|
branch,
|
|
42
96
|
title,
|
|
43
97
|
version: plan.nextVersion,
|
|
98
|
+
commits: plan.commits,
|
|
44
99
|
};
|
|
45
100
|
}
|
|
101
|
+
function formatCommitMessage(subject) {
|
|
102
|
+
const conventional = subject.match(/^[a-z]+(?:\(([^)]+)\))?!?:\s+(.+)$/iu);
|
|
103
|
+
if (!conventional) {
|
|
104
|
+
return { label: "", message: subject };
|
|
105
|
+
}
|
|
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");
|
|
158
|
+
}
|
|
159
|
+
async function openOrUpdateSimpleReviewRequest(cwd, branch, title, version, commits) {
|
|
160
|
+
const loaded = (0, load_config_js_1.loadConfig)(cwd);
|
|
161
|
+
const releaseFlow = loaded.config["review-mode"] ?? "direct";
|
|
162
|
+
if (releaseFlow !== "review") {
|
|
163
|
+
return "Release flow mode is direct; skipping review request creation.";
|
|
164
|
+
}
|
|
165
|
+
const plugins = (0, runtime_js_1.loadRuntimePlugins)();
|
|
166
|
+
const scmPlugins = (0, capabilities_js_1.findPluginsByCapability)(plugins, "scm.reviewRequest");
|
|
167
|
+
if (scmPlugins.length === 0) {
|
|
168
|
+
throw new Error("review-mode is review but no scm.reviewRequest plugin is available.");
|
|
169
|
+
}
|
|
170
|
+
const plugin = scmPlugins[0];
|
|
171
|
+
if (!plugin?.createOrUpdateReviewRequest) {
|
|
172
|
+
throw new Error(`Plugin "${plugin?.name ?? "unknown"}" does not implement createOrUpdateReviewRequest.`);
|
|
173
|
+
}
|
|
174
|
+
const result = await plugin.createOrUpdateReviewRequest({
|
|
175
|
+
baseBranch: process.env.VERSIONARY_BASE_BRANCH ?? "main",
|
|
176
|
+
headBranch: branch,
|
|
177
|
+
title,
|
|
178
|
+
body: renderSimpleReviewRequestBody(version, commits, cwd),
|
|
179
|
+
labels: ["release"],
|
|
180
|
+
}, {
|
|
181
|
+
cwd,
|
|
182
|
+
logger: console,
|
|
183
|
+
});
|
|
184
|
+
return result.url;
|
|
185
|
+
}
|
|
186
|
+
function pushReleaseBranch(cwd, branch) {
|
|
187
|
+
(0, node_child_process_1.execFileSync)("git", ["push", "--force-with-lease", "origin", branch], {
|
|
188
|
+
cwd,
|
|
189
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
function isReleaseCommitMessage(subject) {
|
|
193
|
+
return /^chore\(release\):\sv\d+\.\d+\.\d+/u.test(subject);
|
|
194
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runSimpleRelease(cwd?: string): Promise<string>;
|
|
@@ -0,0 +1,77 @@
|
|
|
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
|
+
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 load_config_js_1 = require("../config/load-config.js");
|
|
11
|
+
const capabilities_js_1 = require("../plugins/capabilities.js");
|
|
12
|
+
const runtime_js_1 = require("../plugins/runtime.js");
|
|
13
|
+
const pr_js_1 = require("./pr.js");
|
|
14
|
+
function getHeadCommitSubject(cwd) {
|
|
15
|
+
return (0, node_child_process_1.execFileSync)("git", ["log", "-1", "--pretty=%s"], {
|
|
16
|
+
cwd,
|
|
17
|
+
encoding: "utf8",
|
|
18
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
19
|
+
}).trim();
|
|
20
|
+
}
|
|
21
|
+
function createTagIfMissing(cwd, tag) {
|
|
22
|
+
const tagExists = (0, node_child_process_1.execFileSync)("git", ["tag", "--list", tag], {
|
|
23
|
+
cwd,
|
|
24
|
+
encoding: "utf8",
|
|
25
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
26
|
+
}).trim();
|
|
27
|
+
if (tagExists.length > 0) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
(0, node_child_process_1.execFileSync)("git", ["tag", tag], { cwd, stdio: ["ignore", "pipe", "ignore"] });
|
|
31
|
+
(0, node_child_process_1.execFileSync)("git", ["push", "origin", tag], { cwd, stdio: ["ignore", "pipe", "ignore"] });
|
|
32
|
+
}
|
|
33
|
+
function readReleaseNotes(cwd, version, changelogFile) {
|
|
34
|
+
const changelogPath = node_path_1.default.join(cwd, changelogFile);
|
|
35
|
+
if (!node_fs_1.default.existsSync(changelogPath)) {
|
|
36
|
+
return `Automated release for v${version}`;
|
|
37
|
+
}
|
|
38
|
+
const content = node_fs_1.default.readFileSync(changelogPath, "utf8");
|
|
39
|
+
const lines = content.split("\n");
|
|
40
|
+
const start = lines.findIndex((line) => line.startsWith(`## ${version} -`) || line.startsWith(`## [${version}](`));
|
|
41
|
+
if (start < 0) {
|
|
42
|
+
return `Automated release for v${version}`;
|
|
43
|
+
}
|
|
44
|
+
let end = lines.length;
|
|
45
|
+
for (let idx = start + 1; idx < lines.length; idx += 1) {
|
|
46
|
+
if (lines[idx]?.startsWith("## ")) {
|
|
47
|
+
end = idx;
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const notes = lines.slice(start + 1, end).join("\n").trim();
|
|
52
|
+
return notes.length > 0 ? notes : `Automated release for v${version}`;
|
|
53
|
+
}
|
|
54
|
+
async function runSimpleRelease(cwd = process.cwd()) {
|
|
55
|
+
const subject = getHeadCommitSubject(cwd);
|
|
56
|
+
if (!(0, pr_js_1.isReleaseCommitMessage)(subject)) {
|
|
57
|
+
return "No release commit context detected; skipping release stage.";
|
|
58
|
+
}
|
|
59
|
+
const loaded = (0, load_config_js_1.loadConfig)(cwd);
|
|
60
|
+
const versionFile = loaded.config["version-file"] ?? "version.txt";
|
|
61
|
+
const changelogFile = loaded.config["changelog-file"] ?? "CHANGELOG.md";
|
|
62
|
+
const version = node_fs_1.default.readFileSync(node_path_1.default.join(cwd, versionFile), "utf8").trim();
|
|
63
|
+
const tag = `v${version}`;
|
|
64
|
+
createTagIfMissing(cwd, tag);
|
|
65
|
+
const plugins = (0, runtime_js_1.loadRuntimePlugins)();
|
|
66
|
+
const scmPlugins = (0, capabilities_js_1.findPluginsByCapability)(plugins, "scm.releaseMetadata");
|
|
67
|
+
if (scmPlugins.length === 0) {
|
|
68
|
+
throw new Error("No scm.releaseMetadata plugin is available.");
|
|
69
|
+
}
|
|
70
|
+
const plugin = scmPlugins[0];
|
|
71
|
+
if (!plugin?.createReleaseMetadata) {
|
|
72
|
+
throw new Error(`Plugin "${plugin?.name ?? "unknown"}" does not implement createReleaseMetadata.`);
|
|
73
|
+
}
|
|
74
|
+
const notes = readReleaseNotes(cwd, version, changelogFile);
|
|
75
|
+
const result = await plugin.createReleaseMetadata({ tag, version, notes }, { cwd, logger: console });
|
|
76
|
+
return `Published release ${tag}: ${result.url}`;
|
|
77
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function resolveRepositoryWebBaseUrl(cwd: string): string | null;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveRepositoryWebBaseUrl = resolveRepositoryWebBaseUrl;
|
|
4
|
+
const node_child_process_1 = require("node:child_process");
|
|
5
|
+
function resolveRepositoryWebBaseUrl(cwd) {
|
|
6
|
+
const server = process.env.GITHUB_SERVER_URL;
|
|
7
|
+
const slug = process.env.GITHUB_REPOSITORY;
|
|
8
|
+
if (server && slug) {
|
|
9
|
+
return `${server.replace(/\/+$/u, "")}/${slug}`;
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
const remote = (0, node_child_process_1.execFileSync)("git", ["remote", "get-url", "origin"], {
|
|
13
|
+
cwd,
|
|
14
|
+
encoding: "utf8",
|
|
15
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
16
|
+
}).trim();
|
|
17
|
+
const httpsMatch = remote.match(/^(?:https?:\/\/|git@)([^:/]+)[:/]([^/]+\/[^/]+?)(?:\.git)?$/u);
|
|
18
|
+
if (!httpsMatch) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
const [, host, repoPath] = httpsMatch;
|
|
22
|
+
return `https://${host}/${repoPath}`;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
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.writeBaselineSha = writeBaselineSha;
|
|
9
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
10
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
const node_child_process_1 = require("node:child_process");
|
|
12
|
+
const load_config_js_1 = require("../config/load-config.js");
|
|
13
|
+
function getBaselineStatePath(cwd) {
|
|
14
|
+
const loaded = (0, load_config_js_1.loadConfig)(cwd);
|
|
15
|
+
const configured = loaded.config["baseline-file"];
|
|
16
|
+
if (configured) {
|
|
17
|
+
return node_path_1.default.join(cwd, configured);
|
|
18
|
+
}
|
|
19
|
+
const preferred = node_path_1.default.join(cwd, ".versionary-manifest.json");
|
|
20
|
+
if (node_fs_1.default.existsSync(preferred)) {
|
|
21
|
+
return preferred;
|
|
22
|
+
}
|
|
23
|
+
const legacy = node_path_1.default.join(cwd, "versionary.versions.json");
|
|
24
|
+
if (node_fs_1.default.existsSync(legacy)) {
|
|
25
|
+
return legacy;
|
|
26
|
+
}
|
|
27
|
+
return preferred;
|
|
28
|
+
}
|
|
29
|
+
function readBaselineSha(cwd = process.cwd()) {
|
|
30
|
+
const filePath = getBaselineStatePath(cwd);
|
|
31
|
+
if (!node_fs_1.default.existsSync(filePath)) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
const parsed = JSON.parse(node_fs_1.default.readFileSync(filePath, "utf8"));
|
|
35
|
+
return parsed.baselineSha ?? null;
|
|
36
|
+
}
|
|
37
|
+
function writeBaselineSha(cwd = process.cwd(), sha) {
|
|
38
|
+
const baselineSha = sha ??
|
|
39
|
+
(0, node_child_process_1.execFileSync)("git", ["rev-parse", "HEAD"], {
|
|
40
|
+
cwd,
|
|
41
|
+
encoding: "utf8",
|
|
42
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
43
|
+
}).trim();
|
|
44
|
+
const filePath = getBaselineStatePath(cwd);
|
|
45
|
+
const next = { baselineSha };
|
|
46
|
+
node_fs_1.default.writeFileSync(filePath, `${JSON.stringify(next, null, 2)}\n`, "utf8");
|
|
47
|
+
}
|