Skip to content

Commit 77e8589

Browse files
authored
Merge pull request #325 from vypdev/bugfix/324-wrong-tag-names
[#324] 🐛 - Wrong tag names
2 parents 82cda6c + 76f61db commit 77e8589

File tree

11 files changed

+175
-37
lines changed

11 files changed

+175
-37
lines changed

build/cli/index.js

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -52151,8 +52151,8 @@ class ProjectRepository {
5215152151
const { data: release } = await octokit.rest.repos.createRelease({
5215252152
owner,
5215352153
repo,
52154-
tag_name: `v${version}`,
52155-
name: `v${version} - ${title}`,
52154+
tag_name: version,
52155+
name: `${version} - ${title}`,
5215652156
body: changelog,
5215752157
draft: false,
5215852158
prerelease: false,
@@ -53833,6 +53833,25 @@ const project_repository_1 = __nccwpck_require__(7917);
5383353833
const constants_1 = __nccwpck_require__(8593);
5383453834
const logger_1 = __nccwpck_require__(8836);
5383553835
const task_emoji_1 = __nccwpck_require__(9785);
53836+
/** Semantic version pattern: x, x.y, or x.y.z (digits only, no leading 'v'). */
53837+
const SEMVER_PATTERN = /^\d+(\.\d+){0,2}$/;
53838+
function normalizeAndValidateVersion(version) {
53839+
const trimmed = version.trim();
53840+
const withoutV = trimmed.startsWith("v") ? trimmed.slice(1).trim() : trimmed;
53841+
if (withoutV.length === 0) {
53842+
return {
53843+
valid: false,
53844+
error: `${constants_1.INPUT_KEYS.SINGLE_ACTION_VERSION} must be a semantic version (e.g. 1.0.0).`,
53845+
};
53846+
}
53847+
if (!SEMVER_PATTERN.test(withoutV)) {
53848+
return {
53849+
valid: false,
53850+
error: `${constants_1.INPUT_KEYS.SINGLE_ACTION_VERSION} must be a semantic version (e.g. 1.0.0). Got: ${version}`,
53851+
};
53852+
}
53853+
return { valid: true, normalized: withoutV };
53854+
}
5383653855
class CreateReleaseUseCase {
5383753856
constructor() {
5383853857
this.taskId = 'CreateReleaseUseCase';
@@ -53863,6 +53882,7 @@ class CreateReleaseUseCase {
5386353882
`${constants_1.INPUT_KEYS.SINGLE_ACTION_TITLE} is not set.`
5386453883
],
5386553884
}));
53885+
return result;
5386653886
}
5386753887
else if (param.singleAction.changelog.length === 0) {
5386853888
(0, logger_1.logError)(`Changelog is not set.`);
@@ -53874,9 +53894,22 @@ class CreateReleaseUseCase {
5387453894
`${constants_1.INPUT_KEYS.SINGLE_ACTION_CHANGELOG} is not set.`
5387553895
],
5387653896
}));
53897+
return result;
53898+
}
53899+
const versionCheck = normalizeAndValidateVersion(param.singleAction.version);
53900+
if (!versionCheck.valid) {
53901+
(0, logger_1.logError)(versionCheck.error);
53902+
result.push(new result_1.Result({
53903+
id: this.taskId,
53904+
success: false,
53905+
executed: true,
53906+
errors: [versionCheck.error],
53907+
}));
53908+
return result;
5387753909
}
53910+
const releaseVersion = `v${versionCheck.normalized}`;
5387853911
try {
53879-
const releaseUrl = await this.projectRepository.createRelease(param.owner, param.repo, param.singleAction.version, param.singleAction.title, param.singleAction.changelog, param.tokens.token);
53912+
const releaseUrl = await this.projectRepository.createRelease(param.owner, param.repo, releaseVersion, param.singleAction.title, param.singleAction.changelog, param.tokens.token);
5388053913
if (releaseUrl) {
5388153914
result.push(new result_1.Result({
5388253915
id: this.taskId,
@@ -53886,7 +53919,7 @@ class CreateReleaseUseCase {
5388653919
}));
5388753920
}
5388853921
else {
53889-
(0, logger_1.logWarn)(`CreateRelease: createRelease returned no URL for version ${param.singleAction.version}.`);
53922+
(0, logger_1.logWarn)(`CreateRelease: createRelease returned no URL for version ${releaseVersion}.`);
5389053923
result.push(new result_1.Result({
5389153924
id: this.taskId,
5389253925
success: false,
@@ -53961,24 +53994,25 @@ class CreateTagUseCase {
5396153994
}));
5396253995
return result;
5396353996
}
53997+
const tagName = `v${param.singleAction.version}`;
5396453998
try {
53965-
const sha1Tag = await this.projectRepository.createTag(param.owner, param.repo, param.currentConfiguration.releaseBranch, param.singleAction.version, param.tokens.token);
53999+
const sha1Tag = await this.projectRepository.createTag(param.owner, param.repo, param.currentConfiguration.releaseBranch, tagName, param.tokens.token);
5396654000
if (sha1Tag) {
5396754001
result.push(new result_1.Result({
5396854002
id: this.taskId,
5396954003
success: true,
5397054004
executed: true,
53971-
steps: [`Tag ${param.singleAction.version} is ready: ${sha1Tag}`],
54005+
steps: [`Tag ${tagName} is ready: ${sha1Tag}`],
5397254006
}));
5397354007
}
5397454008
else {
53975-
(0, logger_1.logWarn)(`CreateTag: createTag returned no SHA for version ${param.singleAction.version}.`);
54009+
(0, logger_1.logWarn)(`CreateTag: createTag returned no SHA for version ${tagName}.`);
5397654010
result.push(new result_1.Result({
5397754011
id: this.taskId,
5397854012
success: false,
5397954013
executed: true,
5398054014
errors: [
53981-
`Failed to create tag ${param.singleAction.version}.`
54015+
`Failed to create tag ${tagName}.`
5398254016
],
5398354017
}));
5398454018
}
@@ -53989,7 +54023,7 @@ class CreateTagUseCase {
5398954023
id: this.taskId,
5399054024
success: false,
5399154025
executed: true,
53992-
steps: [`Failed to create tag ${param.singleAction.version}.`],
54026+
steps: [`Failed to create tag ${tagName}.`],
5399354027
errors: [
5399454028
JSON.stringify(error)
5399554029
],

build/github_action/index.js

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47232,8 +47232,8 @@ class ProjectRepository {
4723247232
const { data: release } = await octokit.rest.repos.createRelease({
4723347233
owner,
4723447234
repo,
47235-
tag_name: `v${version}`,
47236-
name: `v${version} - ${title}`,
47235+
tag_name: version,
47236+
name: `${version} - ${title}`,
4723747237
body: changelog,
4723847238
draft: false,
4723947239
prerelease: false,
@@ -48914,6 +48914,25 @@ const project_repository_1 = __nccwpck_require__(7917);
4891448914
const constants_1 = __nccwpck_require__(8593);
4891548915
const logger_1 = __nccwpck_require__(8836);
4891648916
const task_emoji_1 = __nccwpck_require__(9785);
48917+
/** Semantic version pattern: x, x.y, or x.y.z (digits only, no leading 'v'). */
48918+
const SEMVER_PATTERN = /^\d+(\.\d+){0,2}$/;
48919+
function normalizeAndValidateVersion(version) {
48920+
const trimmed = version.trim();
48921+
const withoutV = trimmed.startsWith("v") ? trimmed.slice(1).trim() : trimmed;
48922+
if (withoutV.length === 0) {
48923+
return {
48924+
valid: false,
48925+
error: `${constants_1.INPUT_KEYS.SINGLE_ACTION_VERSION} must be a semantic version (e.g. 1.0.0).`,
48926+
};
48927+
}
48928+
if (!SEMVER_PATTERN.test(withoutV)) {
48929+
return {
48930+
valid: false,
48931+
error: `${constants_1.INPUT_KEYS.SINGLE_ACTION_VERSION} must be a semantic version (e.g. 1.0.0). Got: ${version}`,
48932+
};
48933+
}
48934+
return { valid: true, normalized: withoutV };
48935+
}
4891748936
class CreateReleaseUseCase {
4891848937
constructor() {
4891948938
this.taskId = 'CreateReleaseUseCase';
@@ -48944,6 +48963,7 @@ class CreateReleaseUseCase {
4894448963
`${constants_1.INPUT_KEYS.SINGLE_ACTION_TITLE} is not set.`
4894548964
],
4894648965
}));
48966+
return result;
4894748967
}
4894848968
else if (param.singleAction.changelog.length === 0) {
4894948969
(0, logger_1.logError)(`Changelog is not set.`);
@@ -48955,9 +48975,22 @@ class CreateReleaseUseCase {
4895548975
`${constants_1.INPUT_KEYS.SINGLE_ACTION_CHANGELOG} is not set.`
4895648976
],
4895748977
}));
48978+
return result;
48979+
}
48980+
const versionCheck = normalizeAndValidateVersion(param.singleAction.version);
48981+
if (!versionCheck.valid) {
48982+
(0, logger_1.logError)(versionCheck.error);
48983+
result.push(new result_1.Result({
48984+
id: this.taskId,
48985+
success: false,
48986+
executed: true,
48987+
errors: [versionCheck.error],
48988+
}));
48989+
return result;
4895848990
}
48991+
const releaseVersion = `v${versionCheck.normalized}`;
4895948992
try {
48960-
const releaseUrl = await this.projectRepository.createRelease(param.owner, param.repo, param.singleAction.version, param.singleAction.title, param.singleAction.changelog, param.tokens.token);
48993+
const releaseUrl = await this.projectRepository.createRelease(param.owner, param.repo, releaseVersion, param.singleAction.title, param.singleAction.changelog, param.tokens.token);
4896148994
if (releaseUrl) {
4896248995
result.push(new result_1.Result({
4896348996
id: this.taskId,
@@ -48967,7 +49000,7 @@ class CreateReleaseUseCase {
4896749000
}));
4896849001
}
4896949002
else {
48970-
(0, logger_1.logWarn)(`CreateRelease: createRelease returned no URL for version ${param.singleAction.version}.`);
49003+
(0, logger_1.logWarn)(`CreateRelease: createRelease returned no URL for version ${releaseVersion}.`);
4897149004
result.push(new result_1.Result({
4897249005
id: this.taskId,
4897349006
success: false,
@@ -49042,24 +49075,25 @@ class CreateTagUseCase {
4904249075
}));
4904349076
return result;
4904449077
}
49078+
const tagName = `v${param.singleAction.version}`;
4904549079
try {
49046-
const sha1Tag = await this.projectRepository.createTag(param.owner, param.repo, param.currentConfiguration.releaseBranch, param.singleAction.version, param.tokens.token);
49080+
const sha1Tag = await this.projectRepository.createTag(param.owner, param.repo, param.currentConfiguration.releaseBranch, tagName, param.tokens.token);
4904749081
if (sha1Tag) {
4904849082
result.push(new result_1.Result({
4904949083
id: this.taskId,
4905049084
success: true,
4905149085
executed: true,
49052-
steps: [`Tag ${param.singleAction.version} is ready: ${sha1Tag}`],
49086+
steps: [`Tag ${tagName} is ready: ${sha1Tag}`],
4905349087
}));
4905449088
}
4905549089
else {
49056-
(0, logger_1.logWarn)(`CreateTag: createTag returned no SHA for version ${param.singleAction.version}.`);
49090+
(0, logger_1.logWarn)(`CreateTag: createTag returned no SHA for version ${tagName}.`);
4905749091
result.push(new result_1.Result({
4905849092
id: this.taskId,
4905949093
success: false,
4906049094
executed: true,
4906149095
errors: [
49062-
`Failed to create tag ${param.singleAction.version}.`
49096+
`Failed to create tag ${tagName}.`
4906349097
],
4906449098
}));
4906549099
}
@@ -49070,7 +49104,7 @@ class CreateTagUseCase {
4907049104
id: this.taskId,
4907149105
success: false,
4907249106
executed: true,
49073-
steps: [`Failed to create tag ${param.singleAction.version}.`],
49107+
steps: [`Failed to create tag ${tagName}.`],
4907449108
errors: [
4907549109
JSON.stringify(error)
4907649110
],

docs/features.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ When you set `single-action` (and, when required, `single-action-issue`, `single
8181
| **`think_action`** || Uses OpenCode Plan for deep code analysis and change proposals (reasoning over the codebase). No issue required. |
8282
| **`initial_setup`** || Performs initial setup steps (e.g. for repo or project). No issue required. |
8383
| **`create_release`** | `single-action-version`, `single-action-title`, `single-action-changelog` | Creates a GitHub release with the given version, title, and changelog. |
84-
| **`create_tag`** | `single-action-version` | Creates a Git tag for the given version. |
85-
| **`publish_github_action`** | | Publishes or updates the GitHub Action (e.g. versioning, release). |
84+
| **`create_tag`** | `single-action-version` | Creates a Git tag with prefix `v` (e.g. `v1.2.0`) for the given version from the release branch. |
85+
| **`publish_github_action`** | `single-action-version` | Publishes or updates the GitHub Action: creates/updates the major version tag (e.g. `v2` from `v2.0.4`). Requires `create_tag` to have been run first. |
8686
| **`deployed_action`** | `single-action-issue` | Marks the issue as deployed; updates labels and project state (e.g. "deployed"). |
8787

8888
Single actions that **throw an error** if the last step fails: `publish_github_action`, `create_release`, `deployed_action`, `create_tag`. This lets the workflow fail the job when the action does not succeed.

docs/single-actions/available-actions.mdx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ These actions need **`single-action-issue`** set to the issue number. The workfl
2525
| **`think_action`** || **Deep code analysis** and change proposals (OpenCode Plan). You can pass a question (e.g. from CLI with `-q "..."`). No issue required. | One-off reasoning over the codebase; use from CLI or a workflow that provides context. |
2626
| **`initial_setup`** || Performs **initial setup** steps: creates labels, issue types (if supported), verifies access. No issue required. | First-time repo setup; run once or when you add new labels/types. |
2727
| **`create_release`** | `single-action-version`, `single-action-title`, `single-action-changelog` | Creates a **GitHub release** with the given version, title, and changelog (markdown body). | From a workflow after tests pass; use version and changelog from your build or inputs. |
28-
| **`create_tag`** | `single-action-version` | Creates a **Git tag** for the given version. | When you only need a tag (e.g. for versioning) without a full release. |
29-
| **`publish_github_action`** | | **Publishes or updates** the GitHub Action (e.g. versioning, release to marketplace). No issue required. | In a CI job that builds and publishes the action. |
28+
| **`create_tag`** | `single-action-version` | Creates a **Git tag** with prefix `v` (e.g. `v1.2.3`) for the given version from the release branch. | When you only need a tag (e.g. for versioning) without a full release. The tag is created from the `releaseBranch` stored in issue configuration. |
29+
| **`publish_github_action`** | `single-action-version` | **Publishes or updates** the GitHub Action: creates/updates the major version tag (e.g. `v2` from `v2.0.4`) and the corresponding GitHub Release. Requires that `create_tag` has been run first to create the source tag `v{version}`. | In a CI job that builds and publishes the action, after `create_tag` and `create_release` have run. |
3030

3131
## Actions that fail the job on failure
3232

@@ -55,7 +55,7 @@ The **`copilot`** CLI command (e.g. `giik copilot -p "..."`) uses the OpenCode *
5555
| `initial_setup` |||||
5656
| `create_release` |||||
5757
| `create_tag` |||||
58-
| `publish_github_action` || |||
58+
| `publish_github_action` || |||
5959

6060
## Next steps
6161

docs/single-actions/examples.mdx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,16 +110,19 @@ Changelog can be read from a file or generated in a previous step and passed as
110110

111111
## Workflow: create tag
112112

113-
Create only a tag (no release body):
113+
Create a Git tag with prefix `v` (e.g. `v1.2.0`) from the release branch:
114114

115115
```yaml
116116
- uses: vypdev/copilot@v2
117117
with:
118118
token: ${{ secrets.PAT }}
119119
single-action: create_tag
120120
single-action-version: "1.2.0"
121+
single-action-issue: "100" # Issue with releaseBranch in configuration
121122
```
122123

124+
**Note:** The tag is created from the `releaseBranch` stored in the issue configuration. The version input `1.2.0` will create tag `v1.2.0`.
125+
123126
## Workflow: deployed
124127

125128
Mark issue `100` as deployed (e.g. from your release workflow):

src/data/repository/__tests__/project_repository.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,7 @@ describe("ProjectRepository.createRelease", () => {
429429
const result = await repo.createRelease(
430430
"owner",
431431
"repo",
432-
"1.0",
432+
"v1.0",
433433
"First release",
434434
"Changelog",
435435
"token"

src/data/repository/project_repository.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -758,8 +758,8 @@ export class ProjectRepository {
758758
const { data: release } = await octokit.rest.repos.createRelease({
759759
owner,
760760
repo,
761-
tag_name: `v${version}`,
762-
name: `v${version} - ${title}`,
761+
tag_name: version,
762+
name: `${version} - ${title}`,
763763
body: changelog,
764764
draft: false,
765765
prerelease: false,

src/usecase/actions/__tests__/create_release_use_case.test.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,32 @@ describe('CreateReleaseUseCase', () => {
6565
expect(results.some((r) => r.errors?.some((e) => String(e).includes(`${INPUT_KEYS.SINGLE_ACTION_CHANGELOG} is not set.`)))).toBe(true);
6666
});
6767

68+
it('returns failure when version format is invalid', async () => {
69+
const param = baseParam({
70+
singleAction: { version: 'abc', title: 'Release', changelog: '- Fix' },
71+
});
72+
const results = await useCase.invoke(param);
73+
expect(results).toHaveLength(1);
74+
expect(results[0].success).toBe(false);
75+
expect(results[0].errors?.some((e) => String(e).includes(INPUT_KEYS.SINGLE_ACTION_VERSION))).toBe(true);
76+
expect(mockCreateRelease).not.toHaveBeenCalled();
77+
});
78+
79+
it('accepts version with leading v and produces tag v1.0.0 (no double v)', async () => {
80+
mockCreateRelease.mockResolvedValue('https://github.com/owner/repo/releases/tag/v1.0.0');
81+
const param = baseParam({ singleAction: { version: 'v1.0.0', title: 'Release', changelog: '- Fix' } });
82+
const results = await useCase.invoke(param);
83+
expect(results[0].success).toBe(true);
84+
expect(mockCreateRelease).toHaveBeenCalledWith(
85+
'owner',
86+
'repo',
87+
'v1.0.0',
88+
'Release',
89+
'- Fix',
90+
'token'
91+
);
92+
});
93+
6894
it('returns success with release URL when createRelease succeeds', async () => {
6995
mockCreateRelease.mockResolvedValue('https://github.com/owner/repo/releases/tag/v1.0.0');
7096
const param = baseParam();
@@ -76,7 +102,7 @@ describe('CreateReleaseUseCase', () => {
76102
expect(mockCreateRelease).toHaveBeenCalledWith(
77103
'owner',
78104
'repo',
79-
'1.0.0',
105+
'v1.0.0',
80106
'Release title',
81107
'- Fix bug',
82108
'token'

src/usecase/actions/__tests__/create_tag_use_case.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,12 @@ describe('CreateTagUseCase', () => {
6262
expect(results).toHaveLength(1);
6363
expect(results[0]).toBeInstanceOf(Result);
6464
expect(results[0].success).toBe(true);
65-
expect(results[0].steps?.some((s) => s.includes('1.0.0') && s.includes('abc123'))).toBe(true);
65+
expect(results[0].steps?.some((s) => s.includes('v1.0.0') && s.includes('abc123'))).toBe(true);
6666
expect(mockCreateTag).toHaveBeenCalledWith(
6767
'owner',
6868
'repo',
6969
'release/1.0.0',
70-
'1.0.0',
70+
'v1.0.0',
7171
'token'
7272
);
7373
});

0 commit comments

Comments
 (0)