opencode-github-release 0.4.0 → 0.4.2

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 (3) hide show
  1. package/README.md +15 -1
  2. package/dist/index.js +54 -11
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -44,13 +44,25 @@ Create a patch release with notes "Fixed login bug"
44
44
  Create a release
45
45
  ```
46
46
 
47
+ **Suggest a bump (without releasing):**
48
+ ```
49
+ What version should I use?
50
+ ```
51
+
52
+ **Override dirty tree check:**
53
+ ```
54
+ Create a major release with force: true
55
+ ```
56
+
47
57
  ## How it works
48
58
 
49
59
  When you request a release, the plugin fetches the latest git tag from the repository and computes the next version from it. If you specify a bump (`patch`/`minor`/`major`) or an explicit version, it creates the tag and release immediately. If you don't specify either (e.g., just "create a release"), the plugin instead runs `suggest_bump` — it analyzes commits since the latest tag using conventional commit conventions: `fix` → patch, `feat` → minor, `BREAKING CHANGE` → major — and presents the suggestion for your confirmation before proceeding.
50
60
 
51
61
  If you don't provide release notes, the plugin automatically generates them from the commit history via `gh release create --generate-notes`.
52
62
 
53
- `create_release` expects conventional commit messages (`feat:`, `fix:`, `BREAKING CHANGE:`) for accurate auto-suggestion. Commits that follow this convention are classified as:
63
+ `create_release` checks for uncommitted changes before proceeding. If the working tree is dirty, it returns a warning and asks you to commit, stash, or pass `force: true`.
64
+
65
+ It expects conventional commit messages (`feat:`, `fix:`, `BREAKING CHANGE:`) for accurate auto-suggestion. Commits that follow this convention are classified as:
54
66
 
55
67
  - `fix:` → patch bump
56
68
  - `feat:` → minor bump
@@ -69,6 +81,8 @@ Commits: 3
69
81
  Suggested bump: major -> v1.0.0
70
82
  ```
71
83
 
84
+ You can invoke `suggest_bump` directly at any time ("Suggest a bump") — it analyzes commits since the latest tag and recommends a bump without creating anything.
85
+
72
86
  ## Prerequisites
73
87
 
74
88
  - [Git](https://git-scm.com/)
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  // src/index.ts
2
2
  import { tool } from "@opencode-ai/plugin";
3
3
  function bumpVersion(current, bump) {
4
+ const prefix = current.startsWith("v") ? "v" : "";
4
5
  const cleaned = current.replace(/^v/, "");
5
6
  const match = cleaned.match(/^(\d+)\.(\d+)\.(\d+)/);
6
7
  if (!match) throw new Error(`Cannot parse semver from "${current}"`);
@@ -19,7 +20,7 @@ function bumpVersion(current, bump) {
19
20
  if (bump === "patch") {
20
21
  patch++;
21
22
  }
22
- return `v${major}.${minor}.${patch}`;
23
+ return `${prefix}${major}.${minor}.${patch}`;
23
24
  }
24
25
  function classifyCommit(subject) {
25
26
  const breaking = /^BREAKING CHANGE:/im.test(subject) || /^\w+(\([^)]*\))?!:/m.test(subject);
@@ -104,13 +105,37 @@ var plugin = async ({ $ }) => {
104
105
  await $`git fetch --tags --force 2>/dev/null || true`.quiet();
105
106
  const tagResult = await $`git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0"`.text();
106
107
  const latestTag = tagResult.trim();
107
- const newTag = version ? version.startsWith("v") ? version : `v${version}` : bumpVersion(latestTag, bump);
108
+ const repoUsesV = latestTag.startsWith("v");
109
+ const hasExistingTags = parseInt((await $`git tag -l 2>/dev/null | wc -l`.text()).trim(), 10) > 0;
110
+ let newTag;
111
+ if (version) {
112
+ const versionHasV = version.startsWith("v");
113
+ if (hasExistingTags && versionHasV !== repoUsesV) {
114
+ const suggestion = versionHasV ? version.replace(/^v/, "") : `v${version}`;
115
+ return {
116
+ title: "Version prefix mismatch",
117
+ output: [
118
+ `Existing releases use ${repoUsesV ? 'the "v" prefix' : 'no "v" prefix'} (e.g. "${latestTag}"),`,
119
+ `but you provided "${version}" which ${versionHasV ? "has" : "does not have"} a "v" prefix.`,
120
+ "",
121
+ `Would you like to use "${suggestion}" instead?`,
122
+ "If so, call create_release again with the corrected version."
123
+ ].join("\n")
124
+ };
125
+ }
126
+ newTag = version;
127
+ } else {
128
+ newTag = bumpVersion(latestTag, bump);
129
+ }
108
130
  const branch = (await $`git rev-parse --abbrev-ref HEAD`.text()).trim();
109
- let unpushedInfo = "";
131
+ let unpushedBefore = [];
110
132
  if (branch !== "HEAD") {
111
- const unpushed = (await $`git log origin/${branch}..HEAD --oneline 2>/dev/null || true`.text()).trim();
112
- if (unpushed) {
113
- unpushedInfo = `${unpushed.split("\n").length} unpushed commit(s) will be included in this release.`;
133
+ const before = (await $`git log origin/${branch}..HEAD --oneline 2>/dev/null || true`.text()).trim();
134
+ if (before) {
135
+ unpushedBefore = before.split("\n").map((line) => {
136
+ const hash = line.split(/\s+/)[0];
137
+ return { hash, subject: line.slice(hash.length).trim() };
138
+ });
114
139
  }
115
140
  }
116
141
  const hasPkg = (await $`test -f package.json && echo "yes" || echo "no"`.text()).trim() === "yes";
@@ -119,9 +144,9 @@ var plugin = async ({ $ }) => {
119
144
  await $`npm version ${bareVersion} --no-git-tag-version`.quiet();
120
145
  await $`git add package.json package-lock.json 2>/dev/null || true`.quiet();
121
146
  await $`git commit -m ${`chore(release): bump version to ${newTag}`}`.quiet();
122
- if (branch !== "HEAD") {
123
- await $`git push origin ${branch}`.quiet();
124
- }
147
+ }
148
+ if (branch !== "HEAD") {
149
+ await $`git push origin ${branch}`.quiet();
125
150
  }
126
151
  const message = notes || `Release ${newTag}`;
127
152
  await $`git tag -a ${newTag} -m ${message}`.quiet();
@@ -132,8 +157,26 @@ var plugin = async ({ $ }) => {
132
157
  await $`gh release create ${newTag} --title ${newTag} --generate-notes`.quiet();
133
158
  }
134
159
  let result = `Created and published ${newTag} (bumped from ${latestTag})`;
135
- if (unpushedInfo) result += `
136
- ${unpushedInfo}`;
160
+ if (branch !== "HEAD" && unpushedBefore.length > 0) {
161
+ const remaining = (await $`git log origin/${branch}..HEAD --oneline 2>/dev/null || true`.text()).trim();
162
+ const remainingCount = remaining ? remaining.split("\n").length : 0;
163
+ const pushedCount = unpushedBefore.length - remainingCount;
164
+ if (pushedCount > 0) {
165
+ const plural = pushedCount === 1 ? "" : "s";
166
+ result += `
167
+ Pushed ${pushedCount} commit${plural} to ${branch}:`;
168
+ for (let i = 0; i < pushedCount; i++) {
169
+ result += `
170
+ ${unpushedBefore[i].hash} ${unpushedBefore[i].subject}`;
171
+ }
172
+ }
173
+ if (remainingCount > 0) {
174
+ const plural = remainingCount === 1 ? "" : "s";
175
+ const verb = remainingCount === 1 ? "is" : "are";
176
+ result += `
177
+ Note: ${remainingCount} commit${plural} in this release ${verb} not yet pushed to origin/${branch}.`;
178
+ }
179
+ }
137
180
  return { title: newTag, output: result };
138
181
  }
139
182
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-github-release",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "OpenCode plugin to create git tags and publish GitHub releases with semantic versioning.",
5
5
  "repository": {
6
6
  "type": "git",