207 lines
6.6 KiB
TypeScript
207 lines
6.6 KiB
TypeScript
import * as plugins from './mod.plugins.js';
|
|
import * as paths from '../paths.js';
|
|
import { logger } from '../gitzone.logging.js';
|
|
import * as ui from './mod.ui.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}`;
|
|
}
|
|
|
|
/**
|
|
* Reads the current version from package.json or deno.json
|
|
* @param projectType The project type to determine which file to read
|
|
* @returns The current version string
|
|
*/
|
|
function readCurrentVersion(projectType: ProjectType): string {
|
|
if (projectType === 'npm' || projectType === 'both') {
|
|
const packageJsonPath = plugins.path.join(paths.cwd, 'package.json');
|
|
const packageJson = plugins.smartfile.fs.toObjectSync(packageJsonPath) as { version?: string };
|
|
|
|
if (!packageJson.version) {
|
|
throw new Error('package.json does not contain a version field');
|
|
}
|
|
return packageJson.version;
|
|
} else {
|
|
const denoJsonPath = plugins.path.join(paths.cwd, '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');
|
|
}
|
|
return denoConfig.version;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Updates the version field in a JSON file (package.json or deno.json)
|
|
* @param filePath Path to the JSON file
|
|
* @param newVersion The new version to write
|
|
*/
|
|
async function updateVersionFile(filePath: string, newVersion: string): Promise<void> {
|
|
const config = plugins.smartfile.fs.toObjectSync(filePath) as { version?: string };
|
|
config.version = newVersion;
|
|
await plugins.smartfile.memory.toFs(
|
|
JSON.stringify(config, null, 2) + '\n',
|
|
filePath
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Bumps the project version based on project type
|
|
* Handles npm-only, deno-only, and dual projects with unified logic
|
|
* @param projectType The detected project type
|
|
* @param versionType The type of version bump
|
|
* @param currentStep The current step number for progress display
|
|
* @param totalSteps The total number of steps for progress display
|
|
* @returns The new version string
|
|
*/
|
|
export async function bumpProjectVersion(
|
|
projectType: ProjectType,
|
|
versionType: VersionType,
|
|
currentStep?: number,
|
|
totalSteps?: number
|
|
): Promise<string> {
|
|
if (projectType === 'none') {
|
|
throw new Error('Cannot bump version: no package.json or deno.json found');
|
|
}
|
|
|
|
const projectEmoji = projectType === 'npm' ? '📦' : projectType === 'deno' ? '🦕' : '🔀';
|
|
const description = `🏷️ Bumping version (${projectEmoji} ${projectType})`;
|
|
|
|
if (currentStep && totalSteps) {
|
|
ui.printStep(currentStep, totalSteps, description, 'in-progress');
|
|
}
|
|
|
|
const smartshellInstance = new plugins.smartshell.Smartshell({
|
|
executor: 'bash',
|
|
sourceFilePaths: [],
|
|
});
|
|
|
|
try {
|
|
// 1. Read current version
|
|
const currentVersion = readCurrentVersion(projectType);
|
|
|
|
// 2. Calculate new version (reuse existing function!)
|
|
const newVersion = calculateNewVersion(currentVersion, versionType);
|
|
|
|
logger.log('info', `Bumping version: ${currentVersion} → ${newVersion}`);
|
|
|
|
// 3. Determine which files to update
|
|
const filesToUpdate: string[] = [];
|
|
const packageJsonPath = plugins.path.join(paths.cwd, 'package.json');
|
|
const denoJsonPath = plugins.path.join(paths.cwd, 'deno.json');
|
|
|
|
if (projectType === 'npm' || projectType === 'both') {
|
|
await updateVersionFile(packageJsonPath, newVersion);
|
|
filesToUpdate.push('package.json');
|
|
}
|
|
|
|
if (projectType === 'deno' || projectType === 'both') {
|
|
await updateVersionFile(denoJsonPath, newVersion);
|
|
filesToUpdate.push('deno.json');
|
|
}
|
|
|
|
// 4. Stage all updated files
|
|
await smartshellInstance.exec(`git add ${filesToUpdate.join(' ')}`);
|
|
|
|
// 5. Create version commit
|
|
await smartshellInstance.exec(`git commit -m "v${newVersion}"`);
|
|
|
|
// 6. Create version tag
|
|
await smartshellInstance.exec(`git tag v${newVersion} -m "v${newVersion}"`);
|
|
|
|
logger.log('info', `Created commit and tag v${newVersion}`);
|
|
|
|
if (currentStep && totalSteps) {
|
|
ui.printStep(currentStep, totalSteps, description, 'done');
|
|
}
|
|
|
|
return newVersion;
|
|
} catch (error) {
|
|
throw new Error(`Failed to bump project version: ${error.message}`);
|
|
}
|
|
}
|