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 | # 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) | ## 2025-09-07 - 1.18.7 - fix(claude) | ||||||
| Add .claude local settings to whitelist dev tool permissions | Add .claude local settings to whitelist dev tool permissions | ||||||
|  |  | ||||||
|   | |||||||
| @@ -3,6 +3,6 @@ | |||||||
|  */ |  */ | ||||||
| export const commitinfo = { | export const commitinfo = { | ||||||
|   name: '@git.zone/cli', |   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.' |   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 plugins from './mod.plugins.js'; | ||||||
| import * as paths from '../paths.js'; | import * as paths from '../paths.js'; | ||||||
| import { logger } from '../gitzone.logging.js'; | import { logger } from '../gitzone.logging.js'; | ||||||
|  | import * as helpers from './mod.helpers.js'; | ||||||
|  |  | ||||||
| export const run = async (argvArg: any) => { | export const run = async (argvArg: any) => { | ||||||
|   if (argvArg.format) { |   if (argvArg.format) { | ||||||
| @@ -113,12 +114,18 @@ export const run = async (argvArg: any) => { | |||||||
|   logger.log('info', `Staging files for commit:`); |   logger.log('info', `Staging files for commit:`); | ||||||
|   await smartshellInstance.exec(`git add -A`); |   await smartshellInstance.exec(`git add -A`); | ||||||
|   await smartshellInstance.exec(`git commit -m "${commitString}"`); |   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 ( |   if ( | ||||||
|     answerBucket.getAnswerFor('pushToOrigin') && |     answerBucket.getAnswerFor('pushToOrigin') && | ||||||
|     !(process.env.CI === 'true') |     !(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