257 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			257 lines
		
	
	
		
			7.7 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}`;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * 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
 | 
						|
 * @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> {
 | 
						|
  const projectEmoji = projectType === 'npm' ? '📦' : projectType === 'deno' ? '🦕' : '🔀';
 | 
						|
  const description = `🏷️  Bumping version (${projectEmoji} ${projectType})`;
 | 
						|
 | 
						|
  if (currentStep && totalSteps) {
 | 
						|
    ui.printStep(currentStep, totalSteps, description, 'in-progress');
 | 
						|
  }
 | 
						|
 | 
						|
  let newVersion: string;
 | 
						|
 | 
						|
  switch (projectType) {
 | 
						|
    case 'npm':
 | 
						|
      newVersion = await bumpNpmVersion(versionType);
 | 
						|
      break;
 | 
						|
 | 
						|
    case 'deno':
 | 
						|
      newVersion = await bumpDenoVersion(versionType);
 | 
						|
      break;
 | 
						|
 | 
						|
    case 'both': {
 | 
						|
      // Bump npm version first (it handles git tags)
 | 
						|
      newVersion = await bumpNpmVersion(versionType);
 | 
						|
      // Then sync to deno.json
 | 
						|
      await syncVersionToDenoJson(newVersion);
 | 
						|
      break;
 | 
						|
    }
 | 
						|
 | 
						|
    case 'none':
 | 
						|
      throw new Error('Cannot bump version: no package.json or deno.json found');
 | 
						|
 | 
						|
    default:
 | 
						|
      throw new Error(`Unknown project type: ${projectType}`);
 | 
						|
  }
 | 
						|
 | 
						|
  if (currentStep && totalSteps) {
 | 
						|
    ui.printStep(currentStep, totalSteps, description, 'done');
 | 
						|
  }
 | 
						|
 | 
						|
  return newVersion;
 | 
						|
}
 |