From 8dcaf1c631123eb9fdc9ab69f447fee0ddeac57b Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Thu, 23 Oct 2025 18:18:08 +0000 Subject: [PATCH] fix(mod_commit): Improve commit workflow: detect project type and current branch; add robust version bump helpers for npm/deno --- changelog.md | 9 ++ ts/00_commitinfo_data.ts | 2 +- ts/mod_commit/index.ts | 11 +- ts/mod_commit/mod.helpers.ts | 204 +++++++++++++++++++++++++++++++++++ 4 files changed, 223 insertions(+), 3 deletions(-) create mode 100644 ts/mod_commit/mod.helpers.ts diff --git a/changelog.md b/changelog.md index f78a0f2..f2c4806 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,14 @@ # Changelog +## 2025-10-23 - 1.18.8 - fix(mod_commit) +Improve commit workflow: detect project type and current branch; add robust version bump helpers for npm/deno + +- Add mod_commit/mod.helpers.ts with utilities: detectCurrentBranch(), detectProjectType(), bumpProjectVersion(), bumpDenoVersion(), bumpNpmVersion(), syncVersionToDenoJson(), and calculateNewVersion() +- Refactor ts/mod_commit/index.ts to use the new helpers: bumpProjectVersion(projectType, ... ) instead of a hard npm version call and push the actual current branch instead of hardcoding 'master' +- Support bumping versions for npm-only, deno-only, and hybrid (both) projects and synchronize versions from package.json to deno.json when applicable +- Improve branch detection with a fallback to 'master' and informative logging on detection failures +- Add local Claude settings file (.claude/settings.local.json) (editor/CI config) — no code behavior change but included in diff + ## 2025-09-07 - 1.18.7 - fix(claude) Add .claude local settings to whitelist dev tool permissions diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index 58cf814..b7d9475 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@git.zone/cli', - version: '1.18.7', + version: '1.18.8', description: 'A comprehensive CLI tool for enhancing and managing local development workflows with gitzone utilities, focusing on project setup, version control, code formatting, and template management.' } diff --git a/ts/mod_commit/index.ts b/ts/mod_commit/index.ts index 1e50642..740771e 100644 --- a/ts/mod_commit/index.ts +++ b/ts/mod_commit/index.ts @@ -3,6 +3,7 @@ import * as plugins from './mod.plugins.js'; import * as paths from '../paths.js'; import { logger } from '../gitzone.logging.js'; +import * as helpers from './mod.helpers.js'; export const run = async (argvArg: any) => { if (argvArg.format) { @@ -113,12 +114,18 @@ export const run = async (argvArg: any) => { logger.log('info', `Staging files for commit:`); await smartshellInstance.exec(`git add -A`); await smartshellInstance.exec(`git commit -m "${commitString}"`); - await smartshellInstance.exec(`npm version ${commitVersionType}`); + + // Detect project type and bump version accordingly + const projectType = await helpers.detectProjectType(); + await helpers.bumpProjectVersion(projectType, commitVersionType); + if ( answerBucket.getAnswerFor('pushToOrigin') && !(process.env.CI === 'true') ) { - await smartshellInstance.exec(`git push origin master --follow-tags`); + // Detect current branch instead of hardcoding "master" + const currentBranch = await helpers.detectCurrentBranch(); + await smartshellInstance.exec(`git push origin ${currentBranch} --follow-tags`); } }; diff --git a/ts/mod_commit/mod.helpers.ts b/ts/mod_commit/mod.helpers.ts new file mode 100644 index 0000000..ebe573b --- /dev/null +++ b/ts/mod_commit/mod.helpers.ts @@ -0,0 +1,204 @@ +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 { + 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 { + 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 + * @param versionType Type of version bump + * @returns The new version string + */ +export async function bumpDenoVersion(versionType: VersionType): Promise { + const denoJsonPath = plugins.path.join(paths.cwd, 'deno.json'); + + 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 + ); + + 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 { + 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 + * @param version The version to sync + */ +async function syncVersionToDenoJson(version: string): Promise { + const denoJsonPath = plugins.path.join(paths.cwd, 'deno.json'); + + 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 + ); + } 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 { + 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}`); + } +}