opencode-github-release 0.5.0 → 0.6.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 +12 -2
- package/dist/chunk-G33QYB47.js +195 -0
- package/dist/index.d.ts +7 -2
- package/dist/index.js +74 -186
- package/dist/release.d.ts +18 -0
- package/dist/release.js +10 -0
- package/package.json +7 -5
package/README.md
CHANGED
|
@@ -8,7 +8,15 @@ An [OpenCode](https://opencode.ai) plugin that creates git tags and publishes Gi
|
|
|
8
8
|
|
|
9
9
|
## Installation
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
For OpenCode V2, add to your `opencode.json`:
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"plugins": ["opencode-github-release"]
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
For OpenCode V1 (1.18.29 or newer), use its V1 configuration key instead:
|
|
12
20
|
|
|
13
21
|
```json
|
|
14
22
|
{
|
|
@@ -93,12 +101,14 @@ There are commit skills such as (https://github.com/PedroHBO/opencode-config-ski
|
|
|
93
101
|
- [GitHub CLI](https://cli.github.com/) (`gh`) authenticated to your account
|
|
94
102
|
- The current directory must be a git repository with a remote named `origin`
|
|
95
103
|
|
|
104
|
+
Git commands run in the session's working directory. Release creation pushes commits and tags to `origin` and publishes through `gh`; verify the proposed version before proceeding.
|
|
105
|
+
|
|
96
106
|
## Development
|
|
97
107
|
|
|
98
108
|
```bash
|
|
99
109
|
npm install
|
|
100
110
|
npm run typecheck
|
|
101
|
-
npm
|
|
111
|
+
npm test
|
|
102
112
|
npm pack --dry-run
|
|
103
113
|
```
|
|
104
114
|
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
// src/release.ts
|
|
2
|
+
import { execFile } from "child_process";
|
|
3
|
+
import { existsSync } from "fs";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
import { promisify } from "util";
|
|
6
|
+
var exec = promisify(execFile);
|
|
7
|
+
function runner(directory, signal) {
|
|
8
|
+
return async (command, args) => {
|
|
9
|
+
const { stdout } = await exec(command, args, {
|
|
10
|
+
cwd: directory,
|
|
11
|
+
signal,
|
|
12
|
+
maxBuffer: 10 * 1024 * 1024
|
|
13
|
+
});
|
|
14
|
+
return stdout.trim();
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
async function optional(run, command, args, fallback = "") {
|
|
18
|
+
try {
|
|
19
|
+
return await run(command, args);
|
|
20
|
+
} catch {
|
|
21
|
+
return fallback;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function bumpVersion(current, bump) {
|
|
25
|
+
const prefix = current.startsWith("v") ? "v" : "";
|
|
26
|
+
const cleaned = current.replace(/^v/, "");
|
|
27
|
+
const match = cleaned.match(/^(\d+)\.(\d+)\.(\d+)/);
|
|
28
|
+
if (!match) throw new Error(`Cannot parse semver from "${current}"`);
|
|
29
|
+
let major = parseInt(match[1], 10);
|
|
30
|
+
let minor = parseInt(match[2], 10);
|
|
31
|
+
let patch = parseInt(match[3], 10);
|
|
32
|
+
if (bump === "major") {
|
|
33
|
+
major++;
|
|
34
|
+
minor = 0;
|
|
35
|
+
patch = 0;
|
|
36
|
+
}
|
|
37
|
+
if (bump === "minor") {
|
|
38
|
+
minor++;
|
|
39
|
+
patch = 0;
|
|
40
|
+
}
|
|
41
|
+
if (bump === "patch") {
|
|
42
|
+
patch++;
|
|
43
|
+
}
|
|
44
|
+
return `${prefix}${major}.${minor}.${patch}`;
|
|
45
|
+
}
|
|
46
|
+
function classifyCommit(subject, body) {
|
|
47
|
+
const breaking = /^\w+(\([^)]*\))?!:/.test(subject) || /^BREAKING(?: CHANGE|-CHANGE):[ \t]+\S/m.test(body);
|
|
48
|
+
const match = subject.match(/^(\w+)(\([^)]*\))?(!)?\s*:/);
|
|
49
|
+
const type = match?.[1]?.toLowerCase();
|
|
50
|
+
if (type === "feat") return { type: "feat", breaking };
|
|
51
|
+
if (type === "fix") return { type: "fix", breaking };
|
|
52
|
+
return { type: "other", breaking };
|
|
53
|
+
}
|
|
54
|
+
async function latestTag(run) {
|
|
55
|
+
await optional(run, "git", ["fetch", "--tags", "--force"]);
|
|
56
|
+
return optional(run, "git", ["describe", "--tags", "--abbrev=0"], "v0.0.0");
|
|
57
|
+
}
|
|
58
|
+
async function suggestBump(run, progress) {
|
|
59
|
+
await progress("Fetching tags\u2026");
|
|
60
|
+
const tag = await latestTag(run);
|
|
61
|
+
const logText = await optional(run, "git", ["log", `${tag}..HEAD`, "-z", "--format=%h%x00%s%x00%b"]);
|
|
62
|
+
if (!logText) {
|
|
63
|
+
return { title: "No new commits", output: `No new commits since ${tag}. No release needed.` };
|
|
64
|
+
}
|
|
65
|
+
const fields = logText.split("\0");
|
|
66
|
+
fields.pop();
|
|
67
|
+
const entries = [];
|
|
68
|
+
for (let i = 0; i < fields.length; i += 3) {
|
|
69
|
+
const [hash, subject, body] = fields.slice(i, i + 3);
|
|
70
|
+
entries.push({ hash, subject, ...classifyCommit(subject, body) });
|
|
71
|
+
}
|
|
72
|
+
let suggestedBump = "patch";
|
|
73
|
+
for (const entry of entries) {
|
|
74
|
+
if (entry.breaking) {
|
|
75
|
+
suggestedBump = "major";
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
if (entry.type === "feat") {
|
|
79
|
+
suggestedBump = "minor";
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const output = entries.map((e) => {
|
|
83
|
+
const tag2 = e.breaking ? "[BREAKING]" : e.type === "feat" ? "[feat]" : e.type === "fix" ? "[fix]" : " ";
|
|
84
|
+
return ` ${tag2} ${e.hash} ${e.subject}`;
|
|
85
|
+
}).join("\n");
|
|
86
|
+
return {
|
|
87
|
+
title: "Bump suggestion",
|
|
88
|
+
output: [
|
|
89
|
+
`Latest tag: ${tag}`,
|
|
90
|
+
`Commits: ${entries.length}`,
|
|
91
|
+
"",
|
|
92
|
+
output,
|
|
93
|
+
"",
|
|
94
|
+
`Suggested bump: ${suggestedBump} -> ${bumpVersion(tag, suggestedBump)}`
|
|
95
|
+
].join("\n")
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
async function createRelease(args, directory, run, progress) {
|
|
99
|
+
const { bump, version, notes, force } = args;
|
|
100
|
+
if (!bump && !version) {
|
|
101
|
+
throw new Error("Provide either `bump` (patch/minor/major) or an explicit `version` string");
|
|
102
|
+
}
|
|
103
|
+
await progress("Checking working tree\u2026");
|
|
104
|
+
const status = await run("git", ["status", "--porcelain"]);
|
|
105
|
+
if (status && !force) {
|
|
106
|
+
const count = status.split("\n").length;
|
|
107
|
+
return {
|
|
108
|
+
title: "Uncommitted files",
|
|
109
|
+
output: `${count} uncommitted file(s) detected. Call create_release with force: true to proceed anyway, or commit/stash first.`
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
await progress("Fetching tags\u2026");
|
|
113
|
+
const tag = await latestTag(run);
|
|
114
|
+
const repoUsesV = tag.startsWith("v");
|
|
115
|
+
const hasExistingTags = await run("git", ["tag", "-l"]) !== "";
|
|
116
|
+
let newTag;
|
|
117
|
+
if (version) {
|
|
118
|
+
const versionHasV = version.startsWith("v");
|
|
119
|
+
if (hasExistingTags && versionHasV !== repoUsesV) {
|
|
120
|
+
const suggestion = versionHasV ? version.replace(/^v/, "") : `v${version}`;
|
|
121
|
+
return {
|
|
122
|
+
title: "Version prefix mismatch",
|
|
123
|
+
output: [
|
|
124
|
+
`Existing releases use ${repoUsesV ? 'the "v" prefix' : 'no "v" prefix'} (e.g. "${tag}"),`,
|
|
125
|
+
`but you provided "${version}" which ${versionHasV ? "has" : "does not have"} a "v" prefix.`,
|
|
126
|
+
"",
|
|
127
|
+
`Would you like to use "${suggestion}" instead?`,
|
|
128
|
+
"If so, call create_release again with the corrected version."
|
|
129
|
+
].join("\n")
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
newTag = version;
|
|
133
|
+
} else {
|
|
134
|
+
newTag = bumpVersion(tag, bump);
|
|
135
|
+
}
|
|
136
|
+
await progress(`Bumping to ${newTag}\u2026`);
|
|
137
|
+
const branch = await run("git", ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
138
|
+
let unpushedBefore = [];
|
|
139
|
+
if (branch !== "HEAD") {
|
|
140
|
+
const before = await optional(run, "git", ["log", `origin/${branch}..HEAD`, "--oneline"]);
|
|
141
|
+
if (before) {
|
|
142
|
+
unpushedBefore = before.split("\n").map((line) => {
|
|
143
|
+
const hash = line.split(/\s+/)[0];
|
|
144
|
+
return { hash, subject: line.slice(hash.length).trim() };
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (existsSync(join(directory, "package.json"))) {
|
|
149
|
+
const bareVersion = newTag.replace(/^v/, "");
|
|
150
|
+
await run("npm", ["version", bareVersion, "--no-git-tag-version"]);
|
|
151
|
+
await optional(run, "git", ["add", "package.json", "package-lock.json"]);
|
|
152
|
+
await run("git", ["commit", "-m", `chore(release): bump version to ${newTag}`]);
|
|
153
|
+
}
|
|
154
|
+
if (branch !== "HEAD") {
|
|
155
|
+
await progress("Pushing commits\u2026");
|
|
156
|
+
await run("git", ["push", "origin", branch]);
|
|
157
|
+
}
|
|
158
|
+
await progress("Tagging release\u2026");
|
|
159
|
+
await run("git", ["tag", "-a", newTag, "-m", notes || `Release ${newTag}`]);
|
|
160
|
+
await run("git", ["push", "origin", newTag]);
|
|
161
|
+
await progress("Creating GitHub release\u2026");
|
|
162
|
+
if (notes) {
|
|
163
|
+
await run("gh", ["release", "create", newTag, "--title", newTag, "--notes", notes]);
|
|
164
|
+
} else {
|
|
165
|
+
await run("gh", ["release", "create", newTag, "--title", newTag, "--generate-notes"]);
|
|
166
|
+
}
|
|
167
|
+
let result = `Created and published ${newTag} (bumped from ${tag})`;
|
|
168
|
+
if (branch !== "HEAD" && unpushedBefore.length > 0) {
|
|
169
|
+
const remaining = await optional(run, "git", ["log", `origin/${branch}..HEAD`, "--oneline"]);
|
|
170
|
+
const remainingCount = remaining ? remaining.split("\n").length : 0;
|
|
171
|
+
const pushedCount = unpushedBefore.length - remainingCount;
|
|
172
|
+
if (pushedCount > 0) {
|
|
173
|
+
const plural = pushedCount === 1 ? "" : "s";
|
|
174
|
+
result += `
|
|
175
|
+
Pushed ${pushedCount} commit${plural} to ${branch}:`;
|
|
176
|
+
for (let i = 0; i < pushedCount; i++) {
|
|
177
|
+
result += `
|
|
178
|
+
${unpushedBefore[i].hash} ${unpushedBefore[i].subject}`;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (remainingCount > 0) {
|
|
182
|
+
const plural = remainingCount === 1 ? "" : "s";
|
|
183
|
+
const verb = remainingCount === 1 ? "is" : "are";
|
|
184
|
+
result += `
|
|
185
|
+
Note: ${remainingCount} commit${plural} in this release ${verb} not yet pushed to origin/${branch}.`;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return { title: newTag, output: result };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export {
|
|
192
|
+
runner,
|
|
193
|
+
suggestBump,
|
|
194
|
+
createRelease
|
|
195
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
|
+
import { Plugin as Plugin$1 } from '@opencode/plugin';
|
|
1
2
|
import { Plugin } from '@opencode-ai/plugin';
|
|
2
3
|
|
|
3
|
-
declare const
|
|
4
|
+
declare const _default: {
|
|
5
|
+
server: Plugin;
|
|
6
|
+
id: string;
|
|
7
|
+
setup: (context: Plugin$1.Context) => Promise<Plugin$1.Cleanup | void> | Plugin$1.Cleanup | void;
|
|
8
|
+
};
|
|
4
9
|
|
|
5
|
-
export {
|
|
10
|
+
export { _default as default };
|
package/dist/index.js
CHANGED
|
@@ -1,196 +1,84 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createRelease,
|
|
3
|
+
runner,
|
|
4
|
+
suggestBump
|
|
5
|
+
} from "./chunk-G33QYB47.js";
|
|
6
|
+
|
|
1
7
|
// src/index.ts
|
|
8
|
+
import { Plugin } from "@opencode/plugin";
|
|
2
9
|
import { tool } from "@opencode-ai/plugin";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
var suggestDescription = "Analyze git history since the latest tag and suggest a semantic version bump. Call this tool when the user asks to create a release but does NOT specify patch/minor/major or an explicit version string. After receiving the suggestion, present it to the user and ask for confirmation before calling create_release.";
|
|
11
|
+
var releaseDescription = 'Create a git tag and publish a GitHub release with semantic versioning. Provide either `bump` to auto-compute the next version from the latest tag, or an explicit `version` string (e.g. "2.0.0" or "v2.0.0"). If the user only asks to "create a release" without specifying a bump or version, call suggest_bump first instead of this tool.';
|
|
12
|
+
var v1 = async () => ({
|
|
13
|
+
tool: {
|
|
14
|
+
suggest_bump: tool({
|
|
15
|
+
description: suggestDescription,
|
|
16
|
+
args: {},
|
|
17
|
+
async execute(_args, context) {
|
|
18
|
+
return suggestBump(runner(context.directory, context.abort), (title) => context.metadata({ title }));
|
|
19
|
+
}
|
|
20
|
+
}),
|
|
21
|
+
create_release: tool({
|
|
22
|
+
description: releaseDescription,
|
|
23
|
+
args: {
|
|
24
|
+
bump: tool.schema.enum(["patch", "minor", "major"]).optional(),
|
|
25
|
+
version: tool.schema.string().optional(),
|
|
26
|
+
notes: tool.schema.string().optional(),
|
|
27
|
+
force: tool.schema.boolean().optional()
|
|
28
|
+
},
|
|
29
|
+
async execute(args, context) {
|
|
30
|
+
return createRelease(args, context.directory, runner(context.directory, context.abort), (title) => context.metadata({ title }));
|
|
31
|
+
}
|
|
32
|
+
})
|
|
15
33
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
var plugin = async ({ $ }) => {
|
|
34
|
-
return {
|
|
35
|
-
tool: {
|
|
36
|
-
suggest_bump: tool({
|
|
37
|
-
description: "Analyze git history since the latest tag and suggest a semantic version bump. Call this tool when the user asks to create a release but does NOT specify patch/minor/major or an explicit version string. After receiving the suggestion, present it to the user and ask for confirmation before calling create_release.",
|
|
38
|
-
args: {},
|
|
39
|
-
async execute(_args, context) {
|
|
40
|
-
context.metadata({ title: "Fetching tags\u2026" });
|
|
41
|
-
await $`git fetch --tags --force 2>/dev/null || true`.quiet();
|
|
42
|
-
const tagResult = await $`git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0"`.text();
|
|
43
|
-
const latestTag = tagResult.trim();
|
|
44
|
-
const logText = await $`git log ${latestTag}..HEAD --oneline 2>/dev/null || true`.text();
|
|
45
|
-
if (!logText) {
|
|
46
|
-
return {
|
|
47
|
-
title: "No new commits",
|
|
48
|
-
output: `No new commits since ${latestTag}. No release needed.`
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
const lines = logText.split("\n");
|
|
52
|
-
const entries = lines.map((line) => {
|
|
53
|
-
const hash = line.split(/\s+/)[0];
|
|
54
|
-
const subject = line.slice(hash.length).trim();
|
|
55
|
-
const { type, breaking } = classifyCommit(subject);
|
|
56
|
-
return { hash, subject, type, breaking };
|
|
57
|
-
});
|
|
58
|
-
let suggestedBump = "patch";
|
|
59
|
-
for (const entry of entries) {
|
|
60
|
-
if (entry.breaking) {
|
|
61
|
-
suggestedBump = "major";
|
|
62
|
-
break;
|
|
63
|
-
}
|
|
64
|
-
if (entry.type === "feat") {
|
|
65
|
-
suggestedBump = "minor";
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
const output = entries.map((e) => {
|
|
69
|
-
const tag = e.breaking ? "[BREAKING]" : e.type === "feat" ? "[feat]" : e.type === "fix" ? "[fix]" : " ";
|
|
70
|
-
return ` ${tag} ${e.hash} ${e.subject}`;
|
|
71
|
-
}).join("\n");
|
|
72
|
-
return {
|
|
73
|
-
title: "Bump suggestion",
|
|
74
|
-
output: [
|
|
75
|
-
`Latest tag: ${latestTag}`,
|
|
76
|
-
`Commits: ${entries.length}`,
|
|
77
|
-
"",
|
|
78
|
-
output,
|
|
79
|
-
"",
|
|
80
|
-
`Suggested bump: ${suggestedBump} -> ${bumpVersion(latestTag, suggestedBump)}`
|
|
81
|
-
].join("\n")
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
}),
|
|
85
|
-
create_release: tool({
|
|
86
|
-
description: 'Create a git tag and publish a GitHub release with semantic versioning. Provide either `bump` to auto-compute the next version from the latest tag, or an explicit `version` string (e.g. "2.0.0" or "v2.0.0"). If the user only asks to "create a release" without specifying a bump or version, call suggest_bump first instead of this tool.',
|
|
87
|
-
args: {
|
|
88
|
-
bump: tool.schema.enum(["patch", "minor", "major"]).optional(),
|
|
89
|
-
version: tool.schema.string().optional(),
|
|
90
|
-
notes: tool.schema.string().optional(),
|
|
91
|
-
force: tool.schema.boolean().optional()
|
|
92
|
-
},
|
|
93
|
-
async execute(args, context) {
|
|
94
|
-
const { bump, version, notes, force } = args;
|
|
95
|
-
if (!bump && !version) {
|
|
96
|
-
throw new Error("Provide either `bump` (patch/minor/major) or an explicit `version` string");
|
|
97
|
-
}
|
|
98
|
-
context.metadata({ title: "Checking working tree\u2026" });
|
|
99
|
-
const status = (await $`git status --porcelain`.text()).trim();
|
|
100
|
-
if (status && !force) {
|
|
101
|
-
const count = status.split("\n").length;
|
|
102
|
-
return {
|
|
103
|
-
title: "Uncommitted files",
|
|
104
|
-
output: `${count} uncommitted file(s) detected. Call create_release with force: true to proceed anyway, or commit/stash first.`
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
context.metadata({ title: "Fetching tags\u2026" });
|
|
108
|
-
await $`git fetch --tags --force 2>/dev/null || true`.quiet();
|
|
109
|
-
const tagResult = await $`git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0"`.text();
|
|
110
|
-
const latestTag = tagResult.trim();
|
|
111
|
-
const repoUsesV = latestTag.startsWith("v");
|
|
112
|
-
const hasExistingTags = parseInt((await $`git tag -l 2>/dev/null | wc -l`.text()).trim(), 10) > 0;
|
|
113
|
-
let newTag;
|
|
114
|
-
if (version) {
|
|
115
|
-
const versionHasV = version.startsWith("v");
|
|
116
|
-
if (hasExistingTags && versionHasV !== repoUsesV) {
|
|
117
|
-
const suggestion = versionHasV ? version.replace(/^v/, "") : `v${version}`;
|
|
118
|
-
return {
|
|
119
|
-
title: "Version prefix mismatch",
|
|
120
|
-
output: [
|
|
121
|
-
`Existing releases use ${repoUsesV ? 'the "v" prefix' : 'no "v" prefix'} (e.g. "${latestTag}"),`,
|
|
122
|
-
`but you provided "${version}" which ${versionHasV ? "has" : "does not have"} a "v" prefix.`,
|
|
123
|
-
"",
|
|
124
|
-
`Would you like to use "${suggestion}" instead?`,
|
|
125
|
-
"If so, call create_release again with the corrected version."
|
|
126
|
-
].join("\n")
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
newTag = version;
|
|
130
|
-
} else {
|
|
131
|
-
newTag = bumpVersion(latestTag, bump);
|
|
132
|
-
}
|
|
133
|
-
context.metadata({ title: `Bumping to ${newTag}\u2026` });
|
|
134
|
-
const branch = (await $`git rev-parse --abbrev-ref HEAD`.text()).trim();
|
|
135
|
-
let unpushedBefore = [];
|
|
136
|
-
if (branch !== "HEAD") {
|
|
137
|
-
const before = (await $`git log origin/${branch}..HEAD --oneline 2>/dev/null || true`.text()).trim();
|
|
138
|
-
if (before) {
|
|
139
|
-
unpushedBefore = before.split("\n").map((line) => {
|
|
140
|
-
const hash = line.split(/\s+/)[0];
|
|
141
|
-
return { hash, subject: line.slice(hash.length).trim() };
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
const hasPkg = (await $`test -f package.json && echo "yes" || echo "no"`.text()).trim() === "yes";
|
|
146
|
-
if (hasPkg) {
|
|
147
|
-
const bareVersion = newTag.replace(/^v/, "");
|
|
148
|
-
await $`npm version ${bareVersion} --no-git-tag-version`.quiet();
|
|
149
|
-
await $`git add package.json package-lock.json 2>/dev/null || true`.quiet();
|
|
150
|
-
await $`git commit -m ${`chore(release): bump version to ${newTag}`}`.quiet();
|
|
151
|
-
}
|
|
152
|
-
if (branch !== "HEAD") {
|
|
153
|
-
context.metadata({ title: "Pushing commits\u2026" });
|
|
154
|
-
await $`git push origin ${branch}`.quiet();
|
|
155
|
-
}
|
|
156
|
-
context.metadata({ title: "Tagging release\u2026" });
|
|
157
|
-
const message = notes || `Release ${newTag}`;
|
|
158
|
-
await $`git tag -a ${newTag} -m ${message}`.quiet();
|
|
159
|
-
await $`git push origin ${newTag}`.quiet();
|
|
160
|
-
context.metadata({ title: "Creating GitHub release\u2026" });
|
|
161
|
-
if (notes) {
|
|
162
|
-
await $`gh release create ${newTag} --title ${newTag} --notes ${notes}`.quiet();
|
|
163
|
-
} else {
|
|
164
|
-
await $`gh release create ${newTag} --title ${newTag} --generate-notes`.quiet();
|
|
34
|
+
});
|
|
35
|
+
var index_default = {
|
|
36
|
+
...Plugin.define({
|
|
37
|
+
id: "opencode-github-release",
|
|
38
|
+
async setup(ctx) {
|
|
39
|
+
await ctx.tool.transform((editor) => {
|
|
40
|
+
editor.add({
|
|
41
|
+
name: "suggest_bump",
|
|
42
|
+
description: suggestDescription,
|
|
43
|
+
input: { type: "object", properties: {}, additionalProperties: false },
|
|
44
|
+
async execute(_input, context) {
|
|
45
|
+
const session = await ctx.session.get({ sessionID: context.sessionID });
|
|
46
|
+
const result = await suggestBump(
|
|
47
|
+
runner(session.location.directory, context.signal),
|
|
48
|
+
(title) => context.progress({ status: title })
|
|
49
|
+
);
|
|
50
|
+
return { content: result.output, metadata: { title: result.title } };
|
|
165
51
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
52
|
+
});
|
|
53
|
+
editor.add({
|
|
54
|
+
name: "create_release",
|
|
55
|
+
description: releaseDescription,
|
|
56
|
+
input: {
|
|
57
|
+
type: "object",
|
|
58
|
+
properties: {
|
|
59
|
+
bump: { type: "string", enum: ["patch", "minor", "major"] },
|
|
60
|
+
version: { type: "string" },
|
|
61
|
+
notes: { type: "string" },
|
|
62
|
+
force: { type: "boolean" }
|
|
63
|
+
},
|
|
64
|
+
additionalProperties: false
|
|
65
|
+
},
|
|
66
|
+
async execute(input, context) {
|
|
67
|
+
const session = await ctx.session.get({ sessionID: context.sessionID });
|
|
68
|
+
const result = await createRelease(
|
|
69
|
+
input,
|
|
70
|
+
session.location.directory,
|
|
71
|
+
runner(session.location.directory, context.signal),
|
|
72
|
+
(title) => context.progress({ status: title })
|
|
73
|
+
);
|
|
74
|
+
return { content: result.output, metadata: { title: result.title } };
|
|
186
75
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
})
|
|
76
|
+
});
|
|
77
|
+
});
|
|
190
78
|
}
|
|
191
|
-
}
|
|
79
|
+
}),
|
|
80
|
+
server: v1
|
|
192
81
|
};
|
|
193
|
-
var index_default = plugin;
|
|
194
82
|
export {
|
|
195
83
|
index_default as default
|
|
196
84
|
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
type Bump = "patch" | "minor" | "major";
|
|
2
|
+
type Result = {
|
|
3
|
+
title: string;
|
|
4
|
+
output: string;
|
|
5
|
+
};
|
|
6
|
+
type Runner = (command: string, args: string[]) => Promise<string>;
|
|
7
|
+
type Progress = (title: string) => Promise<void> | void;
|
|
8
|
+
declare function runner(directory: string, signal?: AbortSignal): Runner;
|
|
9
|
+
declare function suggestBump(run: Runner, progress: Progress): Promise<Result>;
|
|
10
|
+
type ReleaseArgs = {
|
|
11
|
+
bump?: Bump;
|
|
12
|
+
version?: string;
|
|
13
|
+
notes?: string;
|
|
14
|
+
force?: boolean;
|
|
15
|
+
};
|
|
16
|
+
declare function createRelease(args: ReleaseArgs, directory: string, run: Runner, progress: Progress): Promise<Result>;
|
|
17
|
+
|
|
18
|
+
export { type Bump, type Progress, type ReleaseArgs, type Result, type Runner, createRelease, runner, suggestBump };
|
package/dist/release.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-github-release",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "OpenCode plugin to create git tags and publish GitHub releases with semantic versioning.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -21,10 +21,11 @@
|
|
|
21
21
|
"LICENSE"
|
|
22
22
|
],
|
|
23
23
|
"scripts": {
|
|
24
|
-
"build": "tsup src/index.ts --format esm --dts --clean",
|
|
25
|
-
"dev": "tsup src/index.ts --format esm --dts --watch",
|
|
24
|
+
"build": "tsup src/index.ts src/release.ts --format esm --dts --clean",
|
|
25
|
+
"dev": "tsup src/index.ts src/release.ts --format esm --dts --watch",
|
|
26
26
|
"typecheck": "tsc --noEmit",
|
|
27
|
-
"
|
|
27
|
+
"test": "npm run build && node --test test/*.test.js",
|
|
28
|
+
"check": "npm run typecheck && npm run test && npm pack --dry-run",
|
|
28
29
|
"prepublishOnly": "npm run check"
|
|
29
30
|
},
|
|
30
31
|
"keywords": [
|
|
@@ -38,7 +39,8 @@
|
|
|
38
39
|
"access": "public"
|
|
39
40
|
},
|
|
40
41
|
"dependencies": {
|
|
41
|
-
"@opencode-ai/plugin": "^1.
|
|
42
|
+
"@opencode-ai/plugin": "^1.18.29",
|
|
43
|
+
"@opencode/plugin": "^2.0.24"
|
|
42
44
|
},
|
|
43
45
|
"devDependencies": {
|
|
44
46
|
"@types/node": "^25.0.10",
|