Skip to content

Commit 51d169b

Browse files
authored
Merge pull request #35 from taj54/feature/multi-file-version-update
feat(version-bump): add custom version bump targets
2 parents fdb42be + da2a77c commit 51d169b

8 files changed

Lines changed: 119 additions & 10 deletions

File tree

action.yml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,14 @@ inputs:
2222
description: 'The target path where the version bump should be applied. If not provided, the action will run in the root directory.'
2323
required: false
2424
default: '.'
25-
25+
bump_targets:
26+
description: |
27+
Optional list of version update targets.
28+
Provide the `path` and the `variable` to update, the Action will build regex automatically.
29+
Example:
30+
'[{"path": "setup.py", "variable": "version"}, {"path": "Dockerfile", "variable": "APP_VERSION"}]'
31+
required: false
32+
default: '[]'
2633
outputs:
2734
new_version:
2835
description: 'The new bumped version'

dist/index.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/config/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ export const RELEASE_TYPE = (core.getInput('release_type') || 'patch') as semver
55
export const TARGET_PLATFORM = core.getInput('target_platform');
66
export const GIT_TAG = core.getInput('git_tag') === 'true';
77
export const TARGET_PATH = core.getInput('target_path') || '.';
8+
export const BUMP_TARGETS = JSON.parse(core.getInput('bump_targets') || '[]');

src/index.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import { UpdaterService, GitService, ChangelogService } from './services';
2-
import { FileHandler } from './utils';
2+
import { FileHandler, safeParseJSON } from './utils';
33
import { UpdaterRegistry } from './registry';
44
import {
55
PlatformDetectionError,
66
VersionBumpError,
77
FileNotFoundError,
88
InvalidManifestError,
99
} from './errors';
10-
import { RELEASE_TYPE, TARGET_PLATFORM, GIT_TAG, TARGET_PATH } from './config';
10+
import { RELEASE_TYPE, TARGET_PLATFORM, GIT_TAG, TARGET_PATH, BUMP_TARGETS } from './config';
11+
1112
import * as core from '@actions/core';
1213

1314
async function initializeServices() {
@@ -37,7 +38,8 @@ async function run() {
3738
const platform = updaterService.getPlatform(targetPlatform);
3839
core.info(`Detected platform: ${platform}`);
3940

40-
const version = updaterService.updateVersion(platform, releaseType);
41+
const bumpTargets = safeParseJSON<{ path: string; variable: string }[]>(BUMP_TARGETS);
42+
const version = updaterService.updateVersion(platform, releaseType, bumpTargets);
4143
core.setOutput('new_version', version);
4244

4345
// Generate and update changelog

src/services/updaterService.ts

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import semver from 'semver';
22
import { PlatformDetectionError, VersionBumpError } from '../errors';
33
import { UpdaterRegistry } from '../registry/updaterRegistry';
4+
import { CustomUpdater } from '../updaters/customUpdater';
45

56
/**
67
* Service for managing version updates.
@@ -47,11 +48,27 @@ export class UpdaterService {
4748
* @param releaseType The type of release (major, minor, patch).
4849
* @returns The new version string.
4950
*/
50-
updateVersion(platform: string, releaseType: semver.ReleaseType): string {
51-
const updater = this.updaterRegistry.getUpdater(platform);
52-
if (!updater) {
53-
throw new VersionBumpError(`No updater found for platform: ${platform}`);
51+
updateVersion(
52+
platform: string,
53+
releaseType: semver.ReleaseType,
54+
bumpTargets: Array<{ path: string; variable: string }> = [],
55+
): string {
56+
if (platform === 'custom') {
57+
if (bumpTargets.length === 0) {
58+
throw new VersionBumpError('No bump_targets provided for custom platform.');
59+
}
60+
let lastBumpedVersion: string = '';
61+
for (const target of bumpTargets) {
62+
const customUpdater = new CustomUpdater(target.path, target.variable);
63+
lastBumpedVersion = customUpdater.bumpVersion(releaseType);
64+
}
65+
return lastBumpedVersion;
66+
} else {
67+
const updater = this.updaterRegistry.getUpdater(platform);
68+
if (!updater) {
69+
throw new VersionBumpError(`No updater found for platform: ${platform}`);
70+
}
71+
return updater.bumpVersion(releaseType);
5472
}
55-
return updater.bumpVersion(releaseType);
5673
}
5774
}

src/updaters/customUpdater.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import type { ReleaseType } from 'semver';
2+
import { UpdaterInterface } from '../interface/updaterInterface';
3+
import * as core from '@actions/core';
4+
import { calculateNextVersion, FileHandler, ManifestParser } from '../utils';
5+
6+
export class CustomUpdater implements UpdaterInterface {
7+
platform = 'custom';
8+
private filePath: string;
9+
private variableName: string;
10+
private currentVersion: string | null = null;
11+
private fileHandler: FileHandler;
12+
private manifestParser: ManifestParser;
13+
14+
constructor(filePath: string, variableName: string) {
15+
this.filePath = filePath;
16+
this.variableName = variableName;
17+
this.fileHandler = new FileHandler();
18+
this.manifestParser = new ManifestParser(this.fileHandler);
19+
}
20+
21+
canHandle(): boolean {
22+
// This updater is explicitly called, so it can always handle if constructed.
23+
return true;
24+
}
25+
26+
getCurrentVersion(): string | null {
27+
if (this.currentVersion) {
28+
return this.currentVersion;
29+
}
30+
31+
try {
32+
// eslint-disable-next-line no-useless-escape
33+
const regex = new RegExp(`(${this.variableName}\s*=\s*['"]?)([\\d.]+)(['"]?)`);
34+
this.currentVersion = this.manifestParser.getVersion(this.filePath, 'regex', {
35+
regex: regex,
36+
});
37+
return this.currentVersion;
38+
} catch (error) {
39+
core.debug(`Could not read or parse version from ${this.filePath}: ${error}`);
40+
}
41+
return null;
42+
}
43+
44+
bumpVersion(releaseType: ReleaseType): string {
45+
const oldVersion = this.getCurrentVersion();
46+
if (!oldVersion) {
47+
throw new Error(
48+
`Could not find current version for variable '${this.variableName}' in file '${this.filePath}'`,
49+
);
50+
}
51+
52+
const newVersion = calculateNextVersion(oldVersion, releaseType);
53+
// eslint-disable-next-line no-useless-escape
54+
const regexReplace = new RegExp(`(${this.variableName}\s*=\s*['"]?)${oldVersion}(['"]?)`);
55+
56+
this.manifestParser.updateVersion(this.filePath, newVersion, 'regex', {
57+
regexReplace: regexReplace,
58+
});
59+
60+
core.info(
61+
`Bumped ${this.variableName} in ${this.filePath} from ${oldVersion} to ${newVersion}`,
62+
);
63+
return newVersion;
64+
}
65+
}

src/utils/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from './versionUtil';
22
export * from './fileHandler';
33
export * from './manifestParser';
4+
export * from './jsonUtils';

src/utils/jsonUtils.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/**
2+
* Safely parses a JSON string.
3+
* Returns null (or a fallback) if parsing fails or input is invalid.
4+
*/
5+
/* eslint-disable @typescript-eslint/no-explicit-any */
6+
export function safeParseJSON<T>(str: string | undefined | null): T | undefined {
7+
try {
8+
if (!str || typeof str !== 'string' || str.trim() === '') {
9+
return undefined;
10+
}
11+
return JSON.parse(str) as T;
12+
} catch (e) {
13+
console.error('Invalid JSON:', e);
14+
return undefined;
15+
}
16+
}

0 commit comments

Comments
 (0)