Skip to content

Commit 76f61db

Browse files
committed
bugfix-324-wrong-tag-names: Implement version validation in CreateReleaseUseCase to ensure semantic versioning compliance and enhance error handling for invalid versions.
1 parent a52be8e commit 76f61db

File tree

4 files changed

+125
-3
lines changed

4 files changed

+125
-3
lines changed

build/cli/index.js

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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';
@@ -53877,7 +53896,18 @@ class CreateReleaseUseCase {
5387753896
}));
5387853897
return result;
5387953898
}
53880-
const releaseVersion = `v${param.singleAction.version}`;
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;
53909+
}
53910+
const releaseVersion = `v${versionCheck.normalized}`;
5388153911
try {
5388253912
const releaseUrl = await this.projectRepository.createRelease(param.owner, param.repo, releaseVersion, param.singleAction.title, param.singleAction.changelog, param.tokens.token);
5388353913
if (releaseUrl) {

build/github_action/index.js

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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';
@@ -48958,7 +48977,18 @@ class CreateReleaseUseCase {
4895848977
}));
4895948978
return result;
4896048979
}
48961-
const releaseVersion = `v${param.singleAction.version}`;
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;
48990+
}
48991+
const releaseVersion = `v${versionCheck.normalized}`;
4896248992
try {
4896348993
const releaseUrl = await this.projectRepository.createRelease(param.owner, param.repo, releaseVersion, param.singleAction.title, param.singleAction.changelog, param.tokens.token);
4896448994
if (releaseUrl) {

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

Lines changed: 26 additions & 0 deletions
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();

src/usecase/actions/create_release_use_case.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,28 @@ import { logError, logInfo, logWarn } from "../../utils/logger";
66
import { getTaskEmoji } from "../../utils/task_emoji";
77
import { ParamUseCase } from "../base/param_usecase";
88

9+
/** Semantic version pattern: x, x.y, or x.y.z (digits only, no leading 'v'). */
10+
const SEMVER_PATTERN = /^\d+(\.\d+){0,2}$/;
11+
12+
function normalizeAndValidateVersion(
13+
version: string
14+
): { valid: true; normalized: string } | { valid: false; error: string } {
15+
const trimmed = version.trim();
16+
const withoutV = trimmed.startsWith("v") ? trimmed.slice(1).trim() : trimmed;
17+
if (withoutV.length === 0) {
18+
return {
19+
valid: false,
20+
error: `${INPUT_KEYS.SINGLE_ACTION_VERSION} must be a semantic version (e.g. 1.0.0).`,
21+
};
22+
}
23+
if (!SEMVER_PATTERN.test(withoutV)) {
24+
return {
25+
valid: false,
26+
error: `${INPUT_KEYS.SINGLE_ACTION_VERSION} must be a semantic version (e.g. 1.0.0). Got: ${version}`,
27+
};
28+
}
29+
return { valid: true, normalized: withoutV };
30+
}
931

1032
export class CreateReleaseUseCase implements ParamUseCase<Execution, Result[]> {
1133
taskId: string = 'CreateReleaseUseCase';
@@ -58,7 +80,21 @@ export class CreateReleaseUseCase implements ParamUseCase<Execution, Result[]>
5880
return result;
5981
}
6082

61-
const releaseVersion = `v${param.singleAction.version}`;
83+
const versionCheck = normalizeAndValidateVersion(param.singleAction.version);
84+
if (!versionCheck.valid) {
85+
logError(versionCheck.error);
86+
result.push(
87+
new Result({
88+
id: this.taskId,
89+
success: false,
90+
executed: true,
91+
errors: [versionCheck.error],
92+
})
93+
);
94+
return result;
95+
}
96+
97+
const releaseVersion = `v${versionCheck.normalized}`;
6298
try {
6399
const releaseUrl = await this.projectRepository.createRelease(
64100
param.owner,

0 commit comments

Comments
 (0)