initial commit
All checks were successful
CI / Check Dist (pull_request) Successful in 11s
CI / Test (pull_request) Successful in 56s
CI / Dry-Run (pull_request) Successful in 11s

This commit is contained in:
2025-08-17 21:28:44 +02:00
parent 4eefc46a2f
commit 17c1cd8a41
16 changed files with 6804 additions and 1 deletions

26
.gitea/workflows/cd.yaml Normal file
View File

@@ -0,0 +1,26 @@
name: CD
on:
push:
branches:
- main
jobs:
release:
name: Release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
- run: npm ci
- name: Increment tag
id: increment-tag
uses: ./
with:
token: ${{ secrets.GITEA_TOKEN }}
- name: Push tag
run: |
git tag ${{ steps.increment-tag.outputs.new-tag }}
git push origin ${{ steps.increment-tag.outputs.new-tag }}

63
.gitea/workflows/ci.yaml Normal file
View File

@@ -0,0 +1,63 @@
name: CI
on:
pull_request:
jobs:
test:
name: Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
- run: npm ci
- name: Format code
run: npm run format
- name: Typecheck
run: npm run typecheck
- name: Run test
run: npm run test
check-dist:
name: Check Dist
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
- run: npm ci
- run: mv dist dist_orig
- run: npm run build
- run: |
original_hash=$(sha256sum dist_orig/index.js | cut -d' ' -f1)
new_hash=$(sha256sum dist/index.js | cut -d' ' -f1)
if [ "$original_hash" != "$new_hash" ]; then
echo "Build is not up to date. Original hash: $original_hash, new hash: $new_hash"
exit 1
fi
dry-run:
name: Dry-Run
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
- run: npm ci
- name: Increment tag
id: increment-tag
uses: ./
with:
token: ${{ secrets.GITEA_TOKEN }}
- name: Print new tag
run: |
if [ -z "${{ steps.increment-tag.outputs.new-tag }}" ]; then
echo "No new tag found"
exit 1
fi
echo "New tag: ${{ steps.increment-tag.outputs.new-tag }}"

54
.gitignore vendored Normal file
View File

@@ -0,0 +1,54 @@
# Dependencies
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Build outputs
build/
*.tsbuildinfo
# Coverage reports
coverage/
*.lcov
# Environment variables
.env
.env.local
.env.*.local
# IDE files
.vscode/
.idea/
*.swp
*.swo
*~
# OS files
.DS_Store
Thumbs.db
# Logs
logs/
*.log
# Runtime data
pids/
*.pid
*.seed
*.pid.lock
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity

1
.nvmrc Normal file
View File

@@ -0,0 +1 @@
v20

8
.prettierrc Normal file
View File

@@ -0,0 +1,8 @@
{
"semi": true,
"trailingComma": "es5",
"singleQuote": false,
"printWidth": 100,
"tabWidth": 2,
"useTabs": false
}

View File

@@ -1,2 +1,62 @@
# conventional-semantic-git-tag-increment
# Conventional Semantic Git Tag Increment
A GitHub Action that automatically increments semantic version tags based on conventional commit messages.
Doesn't assume that you are using any special packaging software, etc. It just relies on Git having tags and commit messages.
## Usage
### Basic workflow
```yaml
name: Auto-tag
on:
push:
branches: [main]
jobs:
tag:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: tbehrendt/conventional-semantic-git-tag-increment@v1
id: tag
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Create and push tag
run: |
git tag ${{ steps.tag.outputs.new-tag }}
git push origin ${{ steps.tag.outputs.new-tag }}
```
### With custom last tag
```yaml
- uses: tbehrendt/conventional-semantic-git-tag-increment@v1
with:
last-tag: "v2.1.0"
token: ${{ secrets.GITHUB_TOKEN }}
```
## Examples
| Commit Message | Current Tag | New Tag | Reason |
| ----------------------------------- | ----------- | -------- | -------------------- |
| `feat: add user authentication` | `v1.0.0` | `v1.1.0` | New feature |
| `fix: resolve login bug` | `v1.1.0` | `v1.1.1` | Bug fix |
| `feat!: change API response format` | `v1.1.1` | `v2.0.0` | Breaking change |
| `docs: update README` | `v2.0.0` | `v2.0.1` | Documentation update |
## Inputs
- `last-tag` (optional): Starting tag to increment from. If not provided, uses the latest tag in the repository.
- `token` (required): GitHub token for repository access. Use `${{ github.token }}` for public repos or a PAT for private repos.
## Outputs
- `new-tag`: The incremented semantic version tag (e.g., `1.2.3`)

21
action.yml Normal file
View File

@@ -0,0 +1,21 @@
name: "Conventional Semantic Git Tag Increment"
description: "Increments git tags based on conventional commit messages"
author: "Timo Behrendt <t.behrendt@t00n.de>"
inputs:
last-tag:
description: "Last git tag to increment from (optional, will auto-detect if not provided)"
required: false
default: ""
token:
description: "Token for repository access"
required: true
default: "${{ github.token }}"
outputs:
new-tag:
description: "The new incremented git tag"
runs:
using: "node20"
main: "dist/index.js"

104
dist/index.js vendored Normal file

File diff suppressed because one or more lines are too long

12
esbuild.config.mjs Normal file
View File

@@ -0,0 +1,12 @@
import { build } from "esbuild";
await build({
entryPoints: ["src/index.ts"],
bundle: true,
platform: "node",
target: "node20",
outfile: "dist/index.js",
sourcemap: false,
minify: true,
logLevel: "info",
});

10
jest.config.js Normal file
View File

@@ -0,0 +1,10 @@
module.exports = {
preset: "ts-jest",
testEnvironment: "node",
roots: ["<rootDir>/src"],
testMatch: ["**/*.spec.ts"],
transform: {
"^.+\\.ts$": "ts-jest",
},
moduleFileExtensions: ["ts", "js", "json"],
};

5706
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

27
package.json Normal file
View File

@@ -0,0 +1,27 @@
{
"name": "conventional-semantic-git-tag-increment",
"description": "GitHub Action to increment semantic version tag based on conventional commits",
"main": "src/index.ts",
"scripts": {
"typecheck": "tsc --noEmit",
"build": "esbuild src/index.ts --bundle --outfile=dist/index.js --platform=node --target=node20 --minify",
"test": "jest",
"format": "prettier --write src/**/*.ts"
},
"author": "Timo Behrendt <t.behrendt@t00n.de>",
"license": "MIT",
"devDependencies": {
"@types/conventional-commits-parser": "^5.0.1",
"@types/jest": "^30.0.0",
"@types/node": "^24.3.0",
"esbuild": "^0.25.9",
"jest": "^30.0.5",
"prettier": "^3.6.2",
"ts-jest": "^29.4.1",
"typescript": "^5.9.2"
},
"dependencies": {
"@actions/core": "^1.11.1",
"conventional-commits-parser": "^3.2.4"
}
}

3
src/index.ts Normal file
View File

@@ -0,0 +1,3 @@
import { run } from "./main";
run();

404
src/main.spec.ts Normal file
View File

@@ -0,0 +1,404 @@
import {
IncrementType,
Tag,
GitService,
CoreService,
ConventionalCommitAnalyzer,
TagService,
TagIncrementer,
LocalGitService,
LocalCoreService,
} from "./main";
// Mock child_process.execSync
jest.mock("child_process", () => ({
execSync: jest.fn(),
}));
import { execSync } from "child_process";
const mockExecSync = execSync as jest.MockedFunction<typeof execSync>;
// Mock interfaces for testing
class MockGitService implements GitService {
constructor(
private mockTags: { name: string }[] = [],
private mockCommit: { commit: { message: string } } = { commit: { message: "test commit" } }
) {}
async listTags(perPage: number): Promise<{ name: string }[]> {
return this.mockTags.slice(0, perPage);
}
async getCommit(ref: string): Promise<{ commit: { message: string } }> {
// Use ref parameter to avoid unused parameter warning
if (!ref) {
throw new Error("Ref is required");
}
return this.mockCommit;
}
async testConnection(): Promise<void> {
// Mock successful connection
}
setMockTags(tags: { name: string }[]) {
this.mockTags = tags;
}
setMockCommit(commit: { commit: { message: string } }) {
this.mockCommit = commit;
}
}
class MockCoreService implements CoreService {
private inputs: Record<string, string> = {};
private outputs: Record<string, string> = {};
private logs: string[] = [];
private warnings: string[] = [];
private failures: string[] = [];
setInput(name: string, value: string) {
this.inputs[name] = value;
}
getInput(name: string): string {
return this.inputs[name] || "";
}
info(message: string): void {
this.logs.push(`INFO: ${message}`);
}
warning(message: string): void {
this.warnings.push(`WARNING: ${message}`);
}
setOutput(name: string, value: string): void {
this.outputs[name] = value;
}
setFailed(message: string): void {
this.failures.push(message);
}
getOutputs(): Record<string, string> {
return { ...this.outputs };
}
getLogs(): string[] {
return [...this.logs];
}
getWarnings(): string[] {
return [...this.warnings];
}
getFailures(): string[] {
return [...this.failures];
}
clear() {
this.outputs = {};
this.logs = [];
this.warnings = [];
this.failures = [];
}
}
describe("TagService", () => {
let tagService: TagService;
beforeEach(() => {
tagService = new TagService();
});
describe("bumpTag", () => {
const baseTag: Tag = { major: 1, minor: 2, patch: 3 };
it("should increment major version", () => {
const result = tagService.bumpTag(baseTag, IncrementType.MAJOR);
expect(result).toEqual({ major: 2, minor: 0, patch: 0 });
});
it("should increment minor version", () => {
const result = tagService.bumpTag(baseTag, IncrementType.MINOR);
expect(result).toEqual({ major: 1, minor: 3, patch: 0 });
});
it("should increment patch version", () => {
const result = tagService.bumpTag(baseTag, IncrementType.PATCH);
expect(result).toEqual({ major: 1, minor: 2, patch: 4 });
});
it("should handle zero versions", () => {
const zeroTag: Tag = { major: 0, minor: 0, patch: 0 };
const result = tagService.bumpTag(zeroTag, IncrementType.MINOR);
expect(result).toEqual({ major: 0, minor: 1, patch: 0 });
});
});
describe("renderTag", () => {
const tag: Tag = { major: 1, minor: 2, patch: 3 };
it("should render tag without prefix", () => {
const result = tagService.renderTag(tag);
expect(result).toBe("1.2.3");
});
it("should render tag with prefix", () => {
const result = tagService.renderTag(tag, "v");
expect(result).toBe("v1.2.3");
});
});
describe("parseTag", () => {
it("should parse valid tag without prefix", () => {
const result = tagService.parseTag("1.2.3");
expect(result).toEqual({ major: 1, minor: 2, patch: 3 });
});
it("should parse valid tag with prefix", () => {
const result = tagService.parseTag("v1.2.3");
expect(result).toEqual({ major: 1, minor: 2, patch: 3 });
});
it("should throw error for invalid format", () => {
expect(() => tagService.parseTag("invalid")).toThrow(
"Invalid tag format: invalid. Expected semantic version (e.g., 1.2.3)"
);
});
it("should throw error for non-numeric versions", () => {
expect(() => tagService.parseTag("1.a.3")).toThrow(
"Invalid tag format: 1.a.3. Expected semantic version (e.g., 1.2.3)"
);
});
it("should throw error for incomplete version", () => {
expect(() => tagService.parseTag("1.2")).toThrow(
"Invalid tag format: 1.2. Expected semantic version (e.g., 1.2.3)"
);
});
});
});
describe("TagIncrementer", () => {
let tagIncrementer: TagIncrementer;
let mockGitService: MockGitService;
let mockCoreService: MockCoreService;
let mockCommitAnalyzer: ConventionalCommitAnalyzer;
let mockTagService: TagService;
beforeEach(() => {
mockGitService = new MockGitService();
mockCoreService = new MockCoreService();
mockCommitAnalyzer = new ConventionalCommitAnalyzer();
mockTagService = new TagService();
tagIncrementer = new TagIncrementer(
mockGitService,
mockCoreService,
mockCommitAnalyzer,
mockTagService
);
});
afterEach(() => {
mockCoreService.clear();
});
describe("getBaseTag", () => {
it("should return provided tag when available", async () => {
const result = await tagIncrementer.getBaseTag("v1.2.3");
expect(result).toBe("v1.2.3");
expect(mockCoreService.getLogs()).toContain("INFO: Using provided tag: v1.2.3");
});
it("should fetch latest tag when no tag provided", async () => {
mockGitService.setMockTags([{ name: "v2.0.0" }]);
const result = await tagIncrementer.getBaseTag(undefined);
expect(result).toBe("v2.0.0");
expect(mockCoreService.getLogs()).toContain("INFO: Using latest tag: v2.0.0");
});
it("should default to 0.0.0 when no tags found", async () => {
mockGitService.setMockTags([]);
const result = await tagIncrementer.getBaseTag(undefined);
expect(result).toBe("0.0.0");
expect(mockCoreService.getWarnings()).toContain(
"WARNING: No tags found in repository, defaulting to 0.0.0"
);
});
});
describe("determineIncrementType", () => {
it("should analyze commit and determine increment type", async () => {
mockGitService.setMockCommit({
commit: { message: "feat: new feature" },
});
const result = await tagIncrementer.determineIncrementType("ref");
expect(result).toBe(IncrementType.MINOR);
});
});
describe("incrementTag", () => {
it("should increment tag correctly for patch increment", async () => {
mockGitService.setMockTags([{ name: "v1.2.3" }]);
mockGitService.setMockCommit({
commit: { message: "fix: bug fix" },
});
const result = await tagIncrementer.incrementTag(undefined, "ref");
expect(result).toBe("1.2.4");
expect(mockCoreService.getOutputs()).toEqual({});
});
it("should increment tag correctly for minor increment", async () => {
mockGitService.setMockTags([{ name: "v1.2.3" }]);
mockGitService.setMockCommit({
commit: { message: "feat: new feature" },
});
const result = await tagIncrementer.incrementTag(undefined, "ref");
expect(result).toBe("1.3.0");
});
it("should increment tag correctly for major increment", async () => {
mockGitService.setMockTags([{ name: "v1.2.3" }]);
mockGitService.setMockCommit({
commit: { message: "feat!: breaking change" },
});
const result = await tagIncrementer.incrementTag(undefined, "ref");
expect(result).toBe("2.0.0");
});
it("should use provided tag when available", async () => {
mockGitService.setMockCommit({
commit: { message: "fix: bug fix" },
});
const result = await tagIncrementer.incrementTag("v2.1.0", "ref");
expect(result).toBe("2.1.1");
});
});
});
describe("LocalGitService", () => {
let localGitService: LocalGitService;
beforeEach(() => {
localGitService = new LocalGitService();
mockExecSync.mockClear();
});
describe("listTags", () => {
it("should call git command to list tags", async () => {
mockExecSync.mockReturnValue("v1.0.0\nv1.1.0\n" as any);
const result = await localGitService.listTags(5);
expect(mockExecSync).toHaveBeenCalledWith("git tag --sort=-version:refname", {
encoding: "utf8",
});
expect(result).toEqual([{ name: "v1.0.0" }, { name: "v1.1.0" }]);
});
it("should return empty array when no tags exist", async () => {
mockExecSync.mockReturnValue("" as any);
const result = await localGitService.listTags(5);
expect(result).toEqual([]);
});
});
describe("getCommit", () => {
it("should call git command to get commit", async () => {
mockExecSync.mockReturnValue("test commit message" as any);
const result = await localGitService.getCommit("abc123");
expect(mockExecSync).toHaveBeenCalledWith("git log -1 --pretty=%B abc123", {
encoding: "utf8",
});
expect(result).toEqual({ commit: { message: "test commit message" } });
});
});
describe("testConnection", () => {
it("should call git command to test connection", async () => {
mockExecSync.mockReturnValue(".git" as any);
await localGitService.testConnection();
expect(mockExecSync).toHaveBeenCalledWith("git rev-parse --git-dir", { stdio: "pipe" });
});
it("should throw error when not in git repository", async () => {
mockExecSync.mockImplementation(() => {
throw new Error("fatal: not a git repository");
});
await expect(localGitService.testConnection()).rejects.toThrow(
"Not in a git repository. Please ensure this action runs after checkout."
);
});
});
});
describe("LocalCoreService", () => {
let localCoreService: LocalCoreService;
beforeEach(() => {
localCoreService = new LocalCoreService();
});
// Note: These tests would require mocking the @actions/core module
// In a real implementation, you might want to use jest.mock() to mock the entire module
// For now, we'll just test that the methods exist and can be called
it("should have all required methods", () => {
expect(typeof localCoreService.getInput).toBe("function");
expect(typeof localCoreService.info).toBe("function");
expect(typeof localCoreService.warning).toBe("function");
expect(typeof localCoreService.setOutput).toBe("function");
expect(typeof localCoreService.setFailed).toBe("function");
});
});
// Integration test example
describe("TagIncrementer Integration", () => {
it("should handle complete tag increment flow", async () => {
const mockGitService = new MockGitService([{ name: "v1.2.3" }], {
commit: { message: "feat: new feature" },
});
const mockCoreService = new MockCoreService();
const commitAnalyzer = new ConventionalCommitAnalyzer();
const tagService = new TagService();
const tagIncrementer = new TagIncrementer(
mockGitService,
mockCoreService,
commitAnalyzer,
tagService
);
const result = await tagIncrementer.incrementTag(undefined, "ref");
expect(result).toBe("1.3.0");
expect(mockCoreService.getLogs()).toContain("INFO: Determined increment type: minor");
expect(mockCoreService.getLogs()).toContain("INFO: New tag: 1.3.0");
});
});

281
src/main.ts Normal file
View File

@@ -0,0 +1,281 @@
import * as core from "@actions/core";
import * as ccp from "conventional-commits-parser";
import type { Commit } from "conventional-commits-parser";
import { execSync } from "child_process";
export enum IncrementType {
MAJOR = "major",
MINOR = "minor",
PATCH = "patch",
}
export type Tag = {
major: number;
minor: number;
patch: number;
};
export interface GitService {
listTags(perPage: number): Promise<{ name: string }[]>;
getCommit(ref: string): Promise<{ commit: { message: string } }>;
testConnection(): Promise<void>;
}
export interface CoreService {
getInput(name: string): string;
info(message: string): void;
warning(message: string): void;
setOutput(name: string, value: string): void;
setFailed(message: string): void;
}
export class ConventionalCommitAnalyzer {
analyzeCommit(message: string): Commit {
return ccp.sync(message);
}
determineIncrementType(analyzedCommit: Commit): IncrementType {
if (
analyzedCommit.breaking ||
analyzedCommit.header?.includes("!") ||
analyzedCommit.notes.some((note: any) => note.title === "BREAKING CHANGE")
) {
return IncrementType.MAJOR;
} else if (analyzedCommit.type === "feat") {
return IncrementType.MINOR;
} else {
return IncrementType.PATCH;
}
}
}
export class TagService {
bumpTag(tag: Tag, incrementType: IncrementType): Tag {
switch (incrementType) {
case IncrementType.MAJOR:
return {
major: tag.major + 1,
minor: 0,
patch: 0,
};
case IncrementType.MINOR:
return {
major: tag.major,
minor: tag.minor + 1,
patch: 0,
};
case IncrementType.PATCH:
return {
major: tag.major,
minor: tag.minor,
patch: tag.patch + 1,
};
}
}
renderTag(tag: Tag, prefix: string = ""): string {
return `${prefix}${tag.major}.${tag.minor}.${tag.patch}`;
}
parseTag(tagString: string): Tag {
if (!/^v?\d+\.\d+\.\d+$/.test(tagString)) {
throw new Error(`Invalid tag format: ${tagString}. Expected semantic version (e.g., 1.2.3)`);
}
const versionMatch = tagString.match(/^v?(\d+)\.(\d+)\.(\d+)$/);
if (!versionMatch) {
throw new Error(`Could not parse version from tag: ${tagString}`);
}
return {
major: parseInt(versionMatch[1]),
minor: parseInt(versionMatch[2]),
patch: parseInt(versionMatch[3]),
};
}
}
export class TagIncrementer {
constructor(
private gitService: GitService,
private coreService: CoreService,
private commitAnalyzer: ConventionalCommitAnalyzer,
private tagService: TagService
) {}
async getBaseTag(lastTag: string | undefined): Promise<string> {
if (lastTag) {
this.coreService.info(`Using provided tag: ${lastTag}`);
return lastTag;
}
this.coreService.info("Fetching tags from local repository");
const tags = await this.gitService.listTags(1);
if (tags.length === 0) {
this.coreService.warning("No tags found in repository, defaulting to 0.0.0");
return "0.0.0";
}
const baseTag = tags[0].name;
this.coreService.info(`Using latest tag: ${baseTag}`);
return baseTag;
}
async determineIncrementType(ref: string): Promise<IncrementType> {
this.coreService.info(`Fetching commit ${ref}`);
try {
const { commit } = await this.gitService.getCommit(ref);
this.coreService.info(`Successfully fetched commit: ${commit.message.substring(0, 100)}...`);
const analyzedCommit = this.commitAnalyzer.analyzeCommit(commit.message);
this.coreService.info(
`Analyzed commit - type: ${analyzedCommit.type}, breaking: ${analyzedCommit.breaking}`
);
const incrementType = this.commitAnalyzer.determineIncrementType(analyzedCommit);
this.coreService.info(`Determined increment type: ${incrementType}`);
return incrementType;
} catch (error) {
this.coreService.info(
`Error fetching commit ${ref}: ${error instanceof Error ? error.message : String(error)}`
);
// Additional debugging for commit-related errors
if (error instanceof Error && error.message.includes("404")) {
this.coreService.info(`404 error suggests commit ${ref} was not found in repository`);
this.coreService.info(
`This could indicate: 1) SHA doesn't exist, 2) Repository access issue, 3) API endpoint mismatch`
);
}
throw error;
}
}
async incrementTag(lastTag: string | undefined, ref: string): Promise<string> {
this.coreService.info(`Starting tag increment process at ref ${ref}`);
const baseTag = await this.getBaseTag(lastTag);
const parsedTag = this.tagService.parseTag(baseTag);
this.coreService.info(
`Parsed version: ${parsedTag.major}.${parsedTag.minor}.${parsedTag.patch}`
);
const incrementType = await this.determineIncrementType(ref);
const newTag = this.tagService.bumpTag(parsedTag, incrementType);
const renderedTag = this.tagService.renderTag(newTag);
this.coreService.info(`New tag: ${renderedTag}`);
this.coreService.info(
`Successfully determined new tag: ${renderedTag} (${incrementType} increment)`
);
return renderedTag;
}
}
export class LocalGitService implements GitService {
async listTags(perPage: number): Promise<{ name: string }[]> {
try {
const tags = execSync("git tag --sort=-version:refname", { encoding: "utf8" })
.split("\n")
.filter(Boolean)
.slice(0, perPage);
return tags.map((name) => ({ name }));
} catch (error) {
// If no tags exist, return empty array
return [];
}
}
async getCommit(ref: string): Promise<{ commit: { message: string } }> {
try {
const message = execSync(`git log -1 --pretty=%B ${ref}`, { encoding: "utf8" }).trim();
return { commit: { message } };
} catch (error) {
throw new Error(
`Failed to get commit ${ref}: ${error instanceof Error ? error.message : String(error)}`
);
}
}
async testConnection(): Promise<void> {
try {
// Test if we're in a git repository
execSync("git rev-parse --git-dir", { stdio: "pipe" });
} catch (error) {
throw new Error("Not in a git repository. Please ensure this action runs after checkout.");
}
}
}
export class LocalCoreService implements CoreService {
getInput(name: string): string {
return core.getInput(name);
}
info(message: string): void {
core.info(message);
}
warning(message: string): void {
core.warning(message);
}
setOutput(name: string, value: string): void {
core.setOutput(name, value);
}
setFailed(message: string): void {
core.setFailed(message);
}
}
export async function run(): Promise<void> {
try {
const coreService = new LocalCoreService();
const lastTag = coreService.getInput("last-tag");
const ref = "HEAD";
// Log context information for debugging
coreService.info(`Working with local git repository`);
coreService.info(`Commit ref: ${ref}`);
coreService.info(`Last tag input: ${lastTag || "undefined"}`);
const gitService = new LocalGitService();
const commitAnalyzer = new ConventionalCommitAnalyzer();
const tagService = new TagService();
const tagIncrementer = new TagIncrementer(gitService, coreService, commitAnalyzer, tagService);
coreService.info("Starting tag increment process...");
// Test the connection first
coreService.info("Testing git repository connection...");
await gitService.testConnection();
coreService.info("Git repository connection successful");
const newTag = await tagIncrementer.incrementTag(lastTag || undefined, ref);
coreService.info(`Process completed successfully. New tag: ${newTag}`);
coreService.setOutput("new-tag", newTag);
} catch (error) {
const coreService = new LocalCoreService();
// Enhanced error logging
if (error instanceof Error) {
coreService.info(`Error details: ${error.message}`);
coreService.info(`Error stack: ${error.stack}`);
coreService.setFailed(error.message);
} else {
coreService.info(`Unknown error: ${String(error)}`);
coreService.setFailed("An unknown error occurred");
}
}
}

23
tsconfig.json Normal file
View File

@@ -0,0 +1,23 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "commonjs",
"lib": ["ES2022"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"removeComments": false,
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"exactOptionalPropertyTypes": true,
"noImplicitOverride": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
}