fix(mod_commit): Improve commit workflow: detect project type and current branch; add robust version bump helpers for npm/deno
This commit is contained in:
		| @@ -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 | ||||
|  | ||||
|   | ||||
| @@ -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.' | ||||
| } | ||||
|   | ||||
| @@ -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`); | ||||
|   } | ||||
| }; | ||||
|  | ||||
|   | ||||
							
								
								
									
										204
									
								
								ts/mod_commit/mod.helpers.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										204
									
								
								ts/mod_commit/mod.helpers.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -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<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 | ||||
|  * @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'); | ||||
|  | ||||
|   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<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 | ||||
|  * @param version The version to sync | ||||
|  */ | ||||
| async function syncVersionToDenoJson(version: string): Promise<void> { | ||||
|   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<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}`); | ||||
|   } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user