Files
cli/ts/mod_commit/mod.helpers.ts

235 lines
7.0 KiB
TypeScript

import * as plugins from './mod.plugins.js';
import * as paths from '../paths.js';
import { logger } from '../gitzone.logging.js';
export type ProjectType = 'npm' | 'deno' | 'both' | 'none';
export type VersionType = 'patch' | 'minor' | 'major';
/**
* Detects the current git branch
* @returns The current branch name, defaults to 'master' if detection fails
*/
export async function detectCurrentBranch(): Promise<string> {
try {
const smartshellInstance = new plugins.smartshell.Smartshell({
executor: 'bash',
sourceFilePaths: [],
});
const result = await smartshellInstance.exec('git branch --show-current');
const branchName = result.stdout.trim();
if (!branchName) {
logger.log('warn', 'Could not detect current branch, falling back to "master"');
return 'master';
}
logger.log('info', `Detected current branch: ${branchName}`);
return branchName;
} catch (error) {
logger.log('warn', `Failed to detect branch: ${error.message}, falling back to "master"`);
return 'master';
}
}
/**
* Detects the project type based on presence of package.json and/or deno.json
* @returns The project type
*/
export async function detectProjectType(): Promise<ProjectType> {
const packageJsonPath = plugins.path.join(paths.cwd, 'package.json');
const denoJsonPath = plugins.path.join(paths.cwd, 'deno.json');
const hasPackageJson = await plugins.smartfile.fs.fileExists(packageJsonPath);
const hasDenoJson = await plugins.smartfile.fs.fileExists(denoJsonPath);
if (hasPackageJson && hasDenoJson) {
logger.log('info', 'Detected dual project (npm + deno)');
return 'both';
} else if (hasPackageJson) {
logger.log('info', 'Detected npm project');
return 'npm';
} else if (hasDenoJson) {
logger.log('info', 'Detected deno project');
return 'deno';
} else {
throw new Error('No package.json or deno.json found in current directory');
}
}
/**
* Parses a semantic version string and bumps it according to the version type
* @param currentVersion Current version string (e.g., "1.2.3")
* @param versionType Type of version bump
* @returns New version string
*/
function calculateNewVersion(currentVersion: string, versionType: VersionType): string {
const versionMatch = currentVersion.match(/^(\d+)\.(\d+)\.(\d+)/);
if (!versionMatch) {
throw new Error(`Invalid version format: ${currentVersion}`);
}
let [, major, minor, patch] = versionMatch.map(Number);
switch (versionType) {
case 'major':
major += 1;
minor = 0;
patch = 0;
break;
case 'minor':
minor += 1;
patch = 0;
break;
case 'patch':
patch += 1;
break;
}
return `${major}.${minor}.${patch}`;
}
/**
* Bumps the version in deno.json, commits the change, and creates a tag
* @param versionType Type of version bump
* @returns The new version string
*/
export async function bumpDenoVersion(versionType: VersionType): Promise<string> {
const denoJsonPath = plugins.path.join(paths.cwd, 'deno.json');
const smartshellInstance = new plugins.smartshell.Smartshell({
executor: 'bash',
sourceFilePaths: [],
});
try {
// Read deno.json
const denoConfig = plugins.smartfile.fs.toObjectSync(
denoJsonPath
) as { version?: string };
if (!denoConfig.version) {
throw new Error('deno.json does not contain a version field');
}
const currentVersion = denoConfig.version;
const newVersion = calculateNewVersion(currentVersion, versionType);
logger.log('info', `Bumping deno.json version: ${currentVersion}${newVersion}`);
// Update version
denoConfig.version = newVersion;
// Write back to disk
await plugins.smartfile.memory.toFs(
JSON.stringify(denoConfig, null, 2) + '\n',
denoJsonPath
);
// Stage the deno.json file
await smartshellInstance.exec('git add deno.json');
// Commit the version bump
await smartshellInstance.exec(`git commit -m "v${newVersion}"`);
// Create the version tag
await smartshellInstance.exec(`git tag v${newVersion} -m "v${newVersion}"`);
logger.log('info', `Created commit and tag v${newVersion}`);
return newVersion;
} catch (error) {
throw new Error(`Failed to bump deno.json version: ${error.message}`);
}
}
/**
* Bumps the version in package.json using npm version command
* @param versionType Type of version bump
* @returns The new version string
*/
async function bumpNpmVersion(versionType: VersionType): Promise<string> {
const smartshellInstance = new plugins.smartshell.Smartshell({
executor: 'bash',
sourceFilePaths: [],
});
logger.log('info', `Bumping package.json version using npm version ${versionType}`);
const result = await smartshellInstance.exec(`npm version ${versionType}`);
// npm version returns the new version with a 'v' prefix, e.g., "v1.2.3"
const newVersion = result.stdout.trim().replace(/^v/, '');
return newVersion;
}
/**
* Syncs the version from package.json to deno.json and amends the npm commit
* @param version The version to sync
*/
async function syncVersionToDenoJson(version: string): Promise<void> {
const denoJsonPath = plugins.path.join(paths.cwd, 'deno.json');
const smartshellInstance = new plugins.smartshell.Smartshell({
executor: 'bash',
sourceFilePaths: [],
});
try {
const denoConfig = plugins.smartfile.fs.toObjectSync(
denoJsonPath
) as { version?: string };
logger.log('info', `Syncing version to deno.json: ${version}`);
denoConfig.version = version;
await plugins.smartfile.memory.toFs(
JSON.stringify(denoConfig, null, 2) + '\n',
denoJsonPath
);
// Stage the deno.json file
await smartshellInstance.exec('git add deno.json');
// Amend the npm version commit to include deno.json
await smartshellInstance.exec('git commit --amend --no-edit');
// Re-create the tag with force to update it
await smartshellInstance.exec(`git tag -fa v${version} -m "v${version}"`);
logger.log('info', `Amended commit to include deno.json and updated tag v${version}`);
} catch (error) {
throw new Error(`Failed to sync version to deno.json: ${error.message}`);
}
}
/**
* Bumps the project version based on project type
* @param projectType The detected project type
* @param versionType The type of version bump
* @returns The new version string
*/
export async function bumpProjectVersion(
projectType: ProjectType,
versionType: VersionType
): Promise<string> {
switch (projectType) {
case 'npm':
return await bumpNpmVersion(versionType);
case 'deno':
return await bumpDenoVersion(versionType);
case 'both': {
// Bump npm version first (it handles git tags)
const newVersion = await bumpNpmVersion(versionType);
// Then sync to deno.json
await syncVersionToDenoJson(newVersion);
return newVersion;
}
case 'none':
throw new Error('Cannot bump version: no package.json or deno.json found');
default:
throw new Error(`Unknown project type: ${projectType}`);
}
}