mirror of
https://github.com/PaulHatch/semantic-version.git
synced 2026-04-14 11:14:45 +00:00
Add debug/replay mode (MINOR)
This commit is contained in:
parent
4f07cfb9e0
commit
d93d2fb887
20 changed files with 480 additions and 56 deletions
|
|
@ -30,4 +30,8 @@ export class ActionConfig {
|
|||
public enablePrereleaseMode: boolean = false;
|
||||
/** If bump_each_commit is also set to true, setting this value will cause the version to increment only if the pattern specified is matched. */
|
||||
public bumpEachCommitPatchPattern: string = "";
|
||||
/** If enabled, diagnostic information will be added to the action output. */
|
||||
public debug: boolean = false;
|
||||
/** Diagnostics to replay */
|
||||
public replay: string = '';
|
||||
}
|
||||
|
|
@ -1,8 +1,15 @@
|
|||
// Using require instead of import to support integration testing
|
||||
import * as exec from '@actions/exec';
|
||||
import * as core from '@actions/core';
|
||||
import { DebugManager } from './DebugManager';
|
||||
|
||||
const debugManager = DebugManager.getInstance();
|
||||
|
||||
export const cmd = async (command: string, ...args: any): Promise<string> => {
|
||||
|
||||
if (debugManager.isReplayMode()) {
|
||||
return debugManager.replayCommand(command, args);
|
||||
}
|
||||
|
||||
let output = '', errors = '';
|
||||
const options = {
|
||||
silent: true,
|
||||
|
|
@ -14,15 +21,14 @@ export const cmd = async (command: string, ...args: any): Promise<string> => {
|
|||
}
|
||||
};
|
||||
|
||||
let caughtError: any = null;
|
||||
try {
|
||||
await exec.exec(command, args, options);
|
||||
} catch (err) {
|
||||
//core.info(`The command cd '${command} ${args.join(' ')}' failed: ${err}`);
|
||||
caughtError = err;
|
||||
}
|
||||
|
||||
if (errors !== '') {
|
||||
//core.info(`stderr: ${errors}`);
|
||||
}
|
||||
debugManager.recordCommand(command, args, output, errors, caughtError);
|
||||
|
||||
return output;
|
||||
};
|
||||
|
|
@ -15,11 +15,13 @@ import { DefaultLastReleaseResolver } from './providers/DefaultLastReleaseResolv
|
|||
import { VersionClassifier } from './providers/VersionClassifier'
|
||||
import { BumpAlwaysVersionClassifier } from './providers/BumpAlwaysVersionClassifier'
|
||||
import { ActionConfig } from './ActionConfig';
|
||||
import { DebugManager } from './DebugManager';
|
||||
|
||||
export class ConfigurationProvider {
|
||||
|
||||
constructor(config: ActionConfig) {
|
||||
this.config = config;
|
||||
DebugManager.getInstance().initializeConfig(config);
|
||||
}
|
||||
|
||||
private config: ActionConfig;
|
||||
|
|
|
|||
111
src/DebugManager.ts
Normal file
111
src/DebugManager.ts
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
import exp from "constants";
|
||||
import { ActionConfig } from "./ActionConfig";
|
||||
|
||||
|
||||
/** Utility class for managing debug mode and diagnostic information */
|
||||
export class DebugManager {
|
||||
|
||||
private constructor() { }
|
||||
|
||||
private static instance: DebugManager;
|
||||
/** Returns the singleton instance of the DebugManager */
|
||||
public static getInstance(): DebugManager {
|
||||
if (!DebugManager.instance) {
|
||||
DebugManager.instance = new DebugManager();
|
||||
}
|
||||
return DebugManager.instance;
|
||||
}
|
||||
|
||||
private debugEnabled: boolean = false;
|
||||
private replayMode: boolean = false;
|
||||
private diagnosticInfo: DiagnosticInfo | null = null;
|
||||
|
||||
/** Returns true if debug mode is enabled */
|
||||
public isDebugEnabled(): boolean {
|
||||
return this.debugEnabled;
|
||||
}
|
||||
|
||||
/** Returns true if replay mode is enabled */
|
||||
public isReplayMode(): boolean {
|
||||
return this.replayMode;
|
||||
}
|
||||
|
||||
initializeConfig(config: ActionConfig) {
|
||||
if (config.debug) {
|
||||
this.setDebugEnabled(true);
|
||||
} else if (config.replay.length > 0) {
|
||||
this.replayFromDiagnostics(config.replay);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Enables or disables debug mode, also clears any existing diagnostics info */
|
||||
public setDebugEnabled(enableDebug: boolean = true): void {
|
||||
this.debugEnabled = enableDebug;
|
||||
this.replayMode = false;
|
||||
this.diagnosticInfo = new DiagnosticInfo();
|
||||
};
|
||||
|
||||
/** Enables replay mode and loads the diagnostic information from the specified string */
|
||||
public replayFromDiagnostics(diagnostics: string): void {
|
||||
this.debugEnabled = false
|
||||
this.replayMode = true;
|
||||
this.diagnosticInfo = JSON.parse(diagnostics);
|
||||
}
|
||||
|
||||
/** Returns a JSON string containing the diagnostic information for this run */
|
||||
public getDebugOutput(emptyRepo: boolean = false): string {
|
||||
return this.isDebugEnabled() ? JSON.stringify(this.diagnosticInfo) : '';
|
||||
}
|
||||
|
||||
/** Records a command and its output for diagnostic purposes */
|
||||
public recordCommand(command: string, args: any[], output: string, stderr: string, error: any): void {
|
||||
if (this.isDebugEnabled()) {
|
||||
this.diagnosticInfo?.recordCommand(command, args, output, stderr, error);
|
||||
}
|
||||
}
|
||||
|
||||
/** Replays the specified command and returns the output */
|
||||
public replayCommand(command: string, args: any[]): string {
|
||||
if (this.diagnosticInfo === null) {
|
||||
throw new Error('No diagnostic information available for replay');
|
||||
}
|
||||
|
||||
const commandResult = this.diagnosticInfo.commands.find(c => c.command === command && JSON.stringify(c.args) === JSON.stringify(args));
|
||||
if (!commandResult) {
|
||||
throw new Error(`No result found in diagnostic for command "${command}"`);
|
||||
}
|
||||
if (commandResult.error) {
|
||||
throw commandResult.error;
|
||||
}
|
||||
if (commandResult.stderr) {
|
||||
console.error(commandResult.stderr);
|
||||
}
|
||||
return commandResult.output;
|
||||
}
|
||||
}
|
||||
|
||||
/** Represents a CLI command result */
|
||||
class CommandResult {
|
||||
public command: string;
|
||||
public args: any[];
|
||||
public output: string;
|
||||
public stderr: string;
|
||||
public error: any;
|
||||
public constructor(command: string, args: any[], output: string, stderr: string, error: any) {
|
||||
this.command = command;
|
||||
this.args = args;
|
||||
this.output = output;
|
||||
this.stderr = stderr;
|
||||
this.error = error;
|
||||
}
|
||||
}
|
||||
|
||||
/** Represents the result of the commands executed for a run */
|
||||
class DiagnosticInfo {
|
||||
public commands: CommandResult[] = [];
|
||||
public empty: boolean = false;
|
||||
public recordCommand(command: string, args: any[], output: string, stderr: string, error: any): void {
|
||||
this.commands.push(new CommandResult(command, args, output, stderr, error));
|
||||
}
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@ export class VersionResult {
|
|||
* @param currentCommit - The current commit hash
|
||||
* @param previousCommit - The previous commit hash
|
||||
* @param previousVersion - The previous version
|
||||
* @param debugOutput - Diagnostic information, if debug is enabled
|
||||
*/
|
||||
constructor(
|
||||
public major: number,
|
||||
|
|
@ -32,5 +33,6 @@ export class VersionResult {
|
|||
public authors: string,
|
||||
public currentCommit: string,
|
||||
public previousCommit: string,
|
||||
public previousVersion: string) { }
|
||||
public previousVersion: string,
|
||||
public debugOutput: string) { }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { VersionResult } from './VersionResult';
|
|||
import { VersionType } from './providers/VersionType';
|
||||
import { UserInfo } from './providers/UserInfo';
|
||||
import { VersionInformation } from './providers/VersionInformation';
|
||||
import { DebugManager } from './DebugManager';
|
||||
|
||||
export async function runAction(configurationProvider: ConfigurationProvider): Promise<VersionResult> {
|
||||
|
||||
|
|
@ -14,6 +15,8 @@ export async function runAction(configurationProvider: ConfigurationProvider): P
|
|||
const tagFormatter = configurationProvider.GetTagFormatter();
|
||||
const userFormatter = configurationProvider.GetUserFormatter();
|
||||
|
||||
const debugManager = DebugManager.getInstance();
|
||||
|
||||
if (await currentCommitResolver.IsEmptyRepoAsync()) {
|
||||
const versionInfo = new VersionInformation(0, 0, 0, 0, VersionType.None, [], false, false);
|
||||
return new VersionResult(
|
||||
|
|
@ -29,7 +32,8 @@ export async function runAction(configurationProvider: ConfigurationProvider): P
|
|||
userFormatter.Format('author', []),
|
||||
'',
|
||||
'',
|
||||
'0.0.0'
|
||||
'0.0.0',
|
||||
debugManager.getDebugOutput(true)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -71,6 +75,7 @@ export async function runAction(configurationProvider: ConfigurationProvider): P
|
|||
userFormatter.Format('author', authors),
|
||||
currentCommit,
|
||||
lastRelease.hash,
|
||||
`${lastRelease.major}.${lastRelease.minor}.${lastRelease.patch}`
|
||||
`${lastRelease.major}.${lastRelease.minor}.${lastRelease.patch}`,
|
||||
debugManager.getDebugOutput()
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { expect, test } from '@jest/globals'
|
|||
import { runAction } from '../src/action';
|
||||
import { ConfigurationProvider } from './ConfigurationProvider';
|
||||
import { ActionConfig } from './ActionConfig';
|
||||
import exp from 'constants';
|
||||
|
||||
const windows = process.platform === "win32";
|
||||
const timeout = 30000;
|
||||
|
|
@ -829,7 +830,7 @@ test('Patch does not increment on bump each commit if a patch pattern is set', a
|
|||
test('Patch pattern increment is correct on empty repo', async () => {
|
||||
const repo = createTestRepo({ tagPrefix: '', versionFormat: "${major}.${minor}.${patch}+${increment}", bumpEachCommit: true, bumpEachCommitPatchPattern: '(PATCH)' });
|
||||
|
||||
|
||||
|
||||
const initialResult = await repo.runAction();
|
||||
repo.makeCommit('Initial Commit');
|
||||
const firstResult = await repo.runAction();
|
||||
|
|
@ -851,7 +852,7 @@ test('Patch pattern increment is correct on empty repo', async () => {
|
|||
test('Patch pattern increment is correct/matches non-bumped on empty repo', async () => {
|
||||
const repo = createTestRepo({ tagPrefix: '', versionFormat: "${major}.${minor}.${patch}+${increment}", bumpEachCommit: true, bumpEachCommitPatchPattern: '(PATCH)' });
|
||||
|
||||
|
||||
|
||||
repo.makeCommit('Initial Commit');
|
||||
const firstResult = await repo.runAction();
|
||||
repo.makeCommit('Second Commit');
|
||||
|
|
@ -871,7 +872,7 @@ test('Patch pattern increment is correct/matches non-bumped on empty repo', asyn
|
|||
test('Patch pattern applied when present', async () => {
|
||||
const repo = createTestRepo({ tagPrefix: '', versionFormat: "${major}.${minor}.${patch}+${increment}", bumpEachCommit: true, bumpEachCommitPatchPattern: '(PATCH)' });
|
||||
|
||||
|
||||
|
||||
repo.makeCommit('Initial Commit');
|
||||
const firstResult = await repo.runAction();
|
||||
repo.makeCommit('Second Commit');
|
||||
|
|
@ -928,11 +929,11 @@ test('Prerelease tags are ignored on current commit', async () => {
|
|||
repo.makeCommit(`Commit ${i++}`);
|
||||
await validate('0.1.0+1');
|
||||
repo.exec('git tag v1.0.0-rc2');
|
||||
await validate('0.1.0+1');
|
||||
await validate('0.1.0+1');
|
||||
repo.makeCommit(`Commit ${i++}`);
|
||||
await validate('0.1.0+2');
|
||||
await validate('0.1.0+2');
|
||||
repo.exec('git tag v1.0.0-rc3');
|
||||
await validate('0.1.0+2');
|
||||
await validate('0.1.0+2');
|
||||
repo.makeCommit(`Commit ${i++}`);
|
||||
await validate('0.1.0+3');
|
||||
repo.exec('git tag v1.0.0');
|
||||
|
|
@ -961,14 +962,14 @@ test('Pre-release mode does not update major version if major version is 0', asy
|
|||
const repo = createTestRepo({ tagPrefix: '', versionFormat: "${major}.${minor}.${patch}", enablePrereleaseMode: true });
|
||||
|
||||
repo.makeCommit('Initial Commit');
|
||||
expect(( await repo.runAction()).formattedVersion).toBe('0.0.1');
|
||||
expect((await repo.runAction()).formattedVersion).toBe('0.0.1');
|
||||
repo.makeCommit('Second Commit (MINOR)');
|
||||
expect(( await repo.runAction()).formattedVersion).toBe('0.0.1');
|
||||
expect((await repo.runAction()).formattedVersion).toBe('0.0.1');
|
||||
repo.makeCommit('Third Commit (MAJOR)');
|
||||
expect(( await repo.runAction()).formattedVersion).toBe('0.1.0');
|
||||
expect((await repo.runAction()).formattedVersion).toBe('0.1.0');
|
||||
repo.exec('git tag 0.1.0');
|
||||
repo.makeCommit('Fourth Commit (MAJOR)');
|
||||
expect(( await repo.runAction()).formattedVersion).toBe('0.2.0');
|
||||
expect((await repo.runAction()).formattedVersion).toBe('0.2.0');
|
||||
}, timeout);
|
||||
|
||||
test('Pre-release mode updates major version if major version is not 0', async () => {
|
||||
|
|
@ -977,14 +978,14 @@ test('Pre-release mode updates major version if major version is not 0', async (
|
|||
repo.makeCommit('Initial Commit');
|
||||
repo.exec('git tag 1.0.0');
|
||||
repo.makeCommit('Second Commit');
|
||||
expect(( await repo.runAction()).formattedVersion).toBe('1.0.1');
|
||||
expect((await repo.runAction()).formattedVersion).toBe('1.0.1');
|
||||
repo.makeCommit('Third Commit (MINOR)');
|
||||
expect(( await repo.runAction()).formattedVersion).toBe('1.1.0');
|
||||
expect((await repo.runAction()).formattedVersion).toBe('1.1.0');
|
||||
repo.makeCommit('Fourth Commit (MAJOR)');
|
||||
expect(( await repo.runAction()).formattedVersion).toBe('2.0.0');
|
||||
expect((await repo.runAction()).formattedVersion).toBe('2.0.0');
|
||||
repo.exec('git tag 2.0.0');
|
||||
repo.makeCommit('Fifth Commit (MAJOR)');
|
||||
expect(( await repo.runAction()).formattedVersion).toBe('3.0.0');
|
||||
expect((await repo.runAction()).formattedVersion).toBe('3.0.0');
|
||||
}, timeout);
|
||||
|
||||
test('Tagged commit is flagged as release', async () => {
|
||||
|
|
@ -1020,7 +1021,7 @@ test('Highest tag is chosen when multiple tags are present', async () => {
|
|||
repo.makeCommit('Initial Commit');
|
||||
repo.exec('git tag v2.0.0');
|
||||
repo.exec('git tag v1.0.0');
|
||||
repo.exec('git tag v1.5.0');
|
||||
repo.exec('git tag v1.5.0');
|
||||
var result = await repo.runAction();
|
||||
expect(result.formattedVersion).toBe('2.0.0+0');
|
||||
|
||||
|
|
@ -1033,4 +1034,27 @@ test('Highest tag is chosen when multiple tags are present', async () => {
|
|||
result = await repo.runAction();
|
||||
expect(result.formattedVersion).toBe('3.0.1+0');
|
||||
|
||||
}, timeout);
|
||||
}, timeout);
|
||||
|
||||
|
||||
test('Debug records and replays expected data', async () => {
|
||||
const repo = createTestRepo();
|
||||
|
||||
repo.makeCommit('Initial Commit');
|
||||
repo.exec('git tag v3.0.0');
|
||||
repo.makeCommit(`Second Commit`);
|
||||
const result = await repo.runAction({ debug: true });
|
||||
|
||||
repo.makeCommit(`Breaking Commit`);
|
||||
repo.exec('git tag v9.9.9');
|
||||
|
||||
var replayResult = await repo.runAction({ replay: result.debugOutput });
|
||||
|
||||
expect(replayResult.major).toBe(result.major);
|
||||
expect(replayResult.minor).toBe(result.minor);
|
||||
expect(replayResult.patch).toBe(result.patch);
|
||||
expect(replayResult.increment).toBe(result.increment);
|
||||
|
||||
}, timeout);
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -51,7 +51,9 @@ export async function run() {
|
|||
searchCommitBody: core.getInput('search_commit_body') === 'true',
|
||||
userFormatType: core.getInput('user_format_type'),
|
||||
enablePrereleaseMode: core.getInput('enable_prerelease_mode') === 'true',
|
||||
bumpEachCommitPatchPattern: core.getInput('bump_each_commit_patch_pattern')
|
||||
bumpEachCommitPatchPattern: core.getInput('bump_each_commit_patch_pattern'),
|
||||
debug: core.getInput('debug') === 'true',
|
||||
replay: ''
|
||||
};
|
||||
|
||||
if (config.versionFormat === '' && core.getInput('format') !== '') {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue