From 839ac374aae3c2db75e514f04dc3016604d8a068 Mon Sep 17 00:00:00 2001 From: GDS K S Date: Sun, 20 Jul 2025 17:05:37 -0500 Subject: [PATCH 1/7] fix: update CI to run on all branches for better testing --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 764e6ee..6299386 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,9 @@ name: CI on: push: - branches: [main] + branches: ["*"] # Run on all branches (including develop, feature branches) pull_request: - branches: [main] + branches: [main] # Run on PRs to main jobs: test: From d8ed4f698a8e2a9dbd567002924ec8e73c4517fd Mon Sep 17 00:00:00 2001 From: GDS K S Date: Sun, 20 Jul 2025 17:09:59 -0500 Subject: [PATCH 2/7] fix: update release workflow to use GitHub CLI instead of deprecated action --- .github/workflows/release.yml | 45 +++++++++++++++++------------------ 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e85930f..e2b86c7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,8 +19,8 @@ on: jobs: create-release: runs-on: ubuntu-latest - outputs: - upload_url: ${{ steps.create_release.outputs.upload_url }} + permissions: + contents: write steps: - name: Checkout code @@ -31,23 +31,21 @@ jobs: run: | TAG_NAME="${{ github.ref_name || format('v{0}', github.event.inputs.version) }}" if [[ "$TAG_NAME" == *"beta"* ]] || [[ "$TAG_NAME" == *"alpha"* ]] || [[ "${{ github.event.inputs.prerelease }}" == "true" ]]; then - echo "prerelease=true" >> $GITHUB_OUTPUT + echo "prerelease=--prerelease" >> $GITHUB_OUTPUT echo "release_type=Pre-release" >> $GITHUB_OUTPUT else - echo "prerelease=false" >> $GITHUB_OUTPUT + echo "prerelease=" >> $GITHUB_OUTPUT echo "release_type=Release" >> $GITHUB_OUTPUT fi - - name: Create Release - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ github.ref_name || format('v{0}', github.event.inputs.version) }} - release_name: ${{ steps.release-type.outputs.release_type }} ${{ github.ref_name || format('v{0}', github.event.inputs.version) }} - body: | - ## What's Changed + - name: Create Release with GitHub CLI + run: | + TAG_NAME="${{ github.ref_name || format('v{0}', github.event.inputs.version) }}" + RELEASE_TITLE="${{ steps.release-type.outputs.release_type }} $TAG_NAME" + + gh release create "$TAG_NAME" \ + --title "$RELEASE_TITLE" \ + --notes "## What's Changed - Automated release of commitweave CLI tool - Enhanced commit message creation with emoji support @@ -57,25 +55,26 @@ jobs: ## Installation ### Stable Release - ```bash + \`\`\`bash npm install -g @typeweaver/commitweave - ``` + \`\`\` ### Beta Release - ```bash + \`\`\`bash npm install -g @typeweaver/commitweave@beta - ``` + \`\`\` ## Usage - ```bash + \`\`\`bash commitweave # Start interactive commit creation commitweave init # Initialize configuration - ``` + \`\`\` - Full changelog: https://github.com/GLINCKER/commitweave/compare/v0.1.0-beta.1...${{ github.ref_name || format('v{0}', github.event.inputs.version) }} - draft: false - prerelease: ${{ steps.release-type.outputs.prerelease }} + Full changelog: https://github.com/GLINCKER/commitweave/commits/$TAG_NAME" \ + ${{ steps.release-type.outputs.prerelease }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} publish-npm: needs: create-release From 83f15b2af66ff401eb8267128332d0331bc2ab7c Mon Sep 17 00:00:00 2001 From: GDS K S Date: Sun, 20 Jul 2025 17:18:24 -0500 Subject: [PATCH 3/7] docs: update README and bump version to 0.1.0-beta.3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Complete rewrite of README with comprehensive documentation - Added new commands section and usage examples - Updated all version references to beta.3 - Enhanced documentation with proper badges and structure ๐Ÿค– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- PUBLISHING.md | 12 +- README.md | 551 ++++++++++++------------------------------- RELEASE_CHECKLIST.md | 16 +- package.json | 2 +- 4 files changed, 162 insertions(+), 419 deletions(-) diff --git a/PUBLISHING.md b/PUBLISHING.md index 7ccfa03..5b450b8 100644 --- a/PUBLISHING.md +++ b/PUBLISHING.md @@ -3,7 +3,7 @@ ## Versioning Strategy ### Beta Phase (Current) -- **Current version**: `0.1.0-beta.1` +- **Current version**: `0.1.0-beta.3` - **NPM tag**: `beta` - **Installation**: `npm install -g @typeweaver/commitweave@beta` @@ -30,15 +30,15 @@ #### Beta Releases (Recommended for now) 1. Update version in `package.json`: ```bash - npm version 0.1.0-beta.2 --no-git-tag-version + npm version 0.1.0-beta.3 --no-git-tag-version ``` 2. Create and push beta tag: ```bash git add package.json - git commit -m "chore: bump version to 0.1.0-beta.2" - git tag v0.1.0-beta.2 + git commit -m "chore: bump version to 0.1.0-beta.3" + git tag v0.1.0-beta.3 git push origin main - git push origin v0.1.0-beta.2 + git push origin v0.1.0-beta.3 ``` #### Stable Releases (Future) @@ -81,7 +81,7 @@ npm install -g @typeweaver/commitweave@beta npm install -g @typeweaver/commitweave # Specific version -npm install -g @typeweaver/commitweave@0.1.0-beta.1 +npm install -g @typeweaver/commitweave@0.1.0-beta.3 ``` ### 5. Pre-publish Checklist diff --git a/README.md b/README.md index 9e43836..1203f80 100644 --- a/README.md +++ b/README.md @@ -1,160 +1,91 @@ -# Commitweave ๐Ÿงถ +# CommitWeave ๐Ÿงถ [![npm version](https://badge.fury.io/js/@typeweaver%2Fcommitweave.svg)](https://www.npmjs.com/package/@typeweaver/commitweave) -[![TypeScript](https://img.shields.io/badge/TypeScript-100%25-blue.svg)](https://www.typescriptlang.org/) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -[![Node.js CI](https://img.shields.io/badge/Node.js-%E2%89%A518.0.0-green.svg)](https://nodejs.org/) +[![TypeScript](https://img.shields.io/badge/TypeScript-100%25-blue.svg)](https://www.typescriptlang.org/) +[![Node.js](https://img.shields.io/badge/Node.js-%E2%89%A518.0.0-green.svg)](https://nodejs.org/) -A modern, battle-tested CLI tool for creating smart, structured, and beautiful git commit messages with emoji support, conventional commit rules, AI-powered summaries, and comprehensive TypeScript integration. +> A modern CLI tool for creating smart, structured, and beautiful git commit messages with emoji support, conventional commit standards, and interactive Git integration. ## โœจ Features -- ๐ŸŽจ **Beautiful Interactive CLI** - Intuitive prompts with colorful output using chalk and enquirer -- ๐Ÿ“ **Conventional Commits** - Full support for conventional commit standards +- ๐ŸŽจ **Interactive CLI Experience** - Beautiful prompts with colorful output +- ๐Ÿ“ **Conventional Commits** - Full support for conventional commit standards - ๐ŸŽญ **Smart Emoji Integration** - Contextual emojis for different commit types -- ๐Ÿค– **AI-Powered Summaries** - Optional AI-generated commit messages (OpenAI, Anthropic, Mock) - โš™๏ธ **Highly Configurable** - Customize commit types, emojis, and validation rules -- ๐Ÿช **Git Hooks Ready** - Built-in pre-commit and post-commit hook support -- ๐Ÿ”ง **Full TypeScript Support** - Complete type definitions and IntelliSense -- ๐Ÿ“ฆ **Dual Module Support** - Works with both CommonJS and ESM projects -- ๐Ÿ›ก๏ธ **Cross-Platform** - Windows, macOS, and Linux support -- ๐ŸŽฏ **Zero Dependencies in Production** - Lightweight and fast +- ๐Ÿ”ง **Git Integration** - Stage files and commit in one seamless workflow +- ๐Ÿ“ฆ **TypeScript First** - Complete type definitions and IntelliSense support +- ๐Ÿ›ก๏ธ **Cross-Platform** - Works on Windows, macOS, and Linux +- ๐Ÿš€ **Zero Dependencies Bloat** - Minimal, focused dependencies -## ๐Ÿš€ Installation +## ๐Ÿš€ Quick Start -### Global Installation (Recommended) -```bash -npm install -g @typeweaver/commitweave -``` +### Installation -### Project Installation +#### Beta Release (Recommended) ```bash -# Using npm -npm install --save-dev @typeweaver/commitweave - -# Using yarn -yarn add --dev @typeweaver/commitweave - -# Using pnpm -pnpm add --save-dev @typeweaver/commitweave +npm install -g @typeweaver/commitweave@beta ``` -## ๐ŸŽฏ Quick Start - +#### Specific Version ```bash -# Navigate to your git repository -cd your-project +npm install -g @typeweaver/commitweave@0.1.0-beta.3 +``` -# Initialize configuration (optional) -commitweave init +### Basic Usage -# Create a commit interactively -commitweave -``` +1. **Initialize configuration** (first time): + ```bash + commitweave init + ``` -## ๐Ÿ“– Usage +2. **Create commits interactively**: + ```bash + commitweave + ``` -### CLI Usage +That's it! CommitWeave will guide you through creating perfect commits. -```bash -# Interactive commit creation -commitweave +## ๐Ÿ“– Commands -# Initialize configuration file -commitweave init +### `commitweave` +Start the interactive commit creation process. -# Show help information -commitweave --help -``` +**Features:** +- Select commit type with emoji and description +- Add optional scope for better organization +- Write clear, concise commit subjects +- Add detailed body descriptions +- Mark breaking changes appropriately +- Preview your commit message before confirmation +- Automatically stage all files and commit -### Programmatic Usage +### `commitweave init` +Initialize or update your project's commit configuration. -#### CommonJS -```javascript -const { CommitBuilder, defaultConfig, GitUtils } = require('@typeweaver/commitweave'); +**What it does:** +- Creates `glinr-commit.json` in your project root +- Sets up default commit types with emojis +- Configures conventional commit standards +- Warns before overwriting existing configuration -const builder = new CommitBuilder(defaultConfig); -const message = builder - .setType('feat') - .setSubject('add user authentication') - .setBody('Implement JWT-based authentication system') - .build(); +### Development Commands -console.log(message); -// Output: feat: โœจ add user authentication -``` +For development and testing: +```bash +# Development mode (full functionality) +npx tsx bin/index.ts -#### ES Modules -```javascript -import { - CommitBuilder, - defaultConfig, - GitUtils, - createCommitMessage -} from '@typeweaver/commitweave'; - -// Using the builder pattern -const builder = new CommitBuilder(defaultConfig); -const message = builder - .setType('fix') - .setScope('auth') - .setSubject('resolve token expiration issue') - .setBreakingChange(false) - .build(); - -// Using the helper function -const quickMessage = createCommitMessage( - 'docs', - 'update API documentation', - { config: defaultConfig } -); -``` +# Run tests +npm test -#### TypeScript -```typescript -import type { - Config, - CommitType, - CommitMessage, - AIProvider, - AISummaryOptions -} from '@typeweaver/commitweave'; - -import { - CommitBuilder, - GitUtils, - createAIProvider -} from '@typeweaver/commitweave'; - -const config: Config = { - commitTypes: [ - { - type: 'feat', - emoji: 'โœจ', - description: 'A new feature', - aliases: ['feature'] - } - ], - emojiEnabled: true, - conventionalCommits: true, - aiSummary: false, - maxSubjectLength: 50, - maxBodyLength: 72 -}; - -const gitUtils = new GitUtils(); -const aiProvider = createAIProvider({ provider: 'mock' }); +# Build the package +npm run build ``` ## โš™๏ธ Configuration -Commitweave uses `glinr-commit.json` for configuration. Create one with: - -```bash -commitweave init -``` - -### Configuration Schema +CommitWeave uses a `glinr-commit.json` file for configuration: ```json { @@ -166,204 +97,77 @@ commitweave init "aliases": ["feature", "new"] }, { - "type": "fix", - "emoji": "๐Ÿ›", + "type": "fix", + "emoji": "๐Ÿ›", "description": "A bug fix", "aliases": ["bugfix", "hotfix"] - }, - { - "type": "docs", - "emoji": "๐Ÿ“š", - "description": "Documentation changes", - "aliases": ["documentation"] - }, - { - "type": "style", - "emoji": "๐Ÿ’Ž", - "description": "Code style changes", - "aliases": ["formatting"] - }, - { - "type": "refactor", - "emoji": "๐Ÿ“ฆ", - "description": "Code refactoring", - "aliases": ["refactoring"] - }, - { - "type": "perf", - "emoji": "๐Ÿš€", - "description": "Performance improvements", - "aliases": ["performance", "optimization"] - }, - { - "type": "test", - "emoji": "๐Ÿšจ", - "description": "Adding or updating tests", - "aliases": ["testing"] - }, - { - "type": "build", - "emoji": "๐Ÿ› ", - "description": "Build system changes", - "aliases": ["ci", "deps"] - }, - { - "type": "ci", - "emoji": "โš™๏ธ", - "description": "CI configuration changes", - "aliases": ["continuous-integration"] - }, - { - "type": "chore", - "emoji": "โ™ป๏ธ", - "description": "Other maintenance changes", - "aliases": ["maintenance"] - }, - { - "type": "revert", - "emoji": "๐Ÿ—‘", - "description": "Revert previous changes", - "aliases": ["rollback"] } ], "emojiEnabled": true, "conventionalCommits": true, - "aiSummary": false, "maxSubjectLength": 50, - "maxBodyLength": 72, - "ai": { - "provider": "mock", - "apiKey": "", - "model": "gpt-3.5-turbo", - "maxTokens": 150, - "temperature": 0.7 - }, - "hooks": { - "preCommit": [], - "postCommit": [] - } + "maxBodyLength": 72 } ``` -### Configuration Options +### Default Commit Types -| Option | Type | Default | Description | -|--------|------|---------|-------------| -| `commitTypes` | `CommitType[]` | [Default types] | Available commit types with emojis and aliases | -| `emojiEnabled` | `boolean` | `true` | Include emojis in commit messages | -| `conventionalCommits` | `boolean` | `true` | Follow conventional commit format | -| `aiSummary` | `boolean` | `false` | Enable AI-powered commit generation | -| `maxSubjectLength` | `number` | `50` | Maximum commit subject length | -| `maxBodyLength` | `number` | `72` | Maximum line length for commit body | -| `ai.provider` | `string` | `"mock"` | AI provider: `openai`, `anthropic`, or `mock` | -| `ai.apiKey` | `string` | `""` | API key for the AI provider | -| `ai.model` | `string` | Provider default | Model to use for AI generation | -| `hooks.preCommit` | `string[]` | `[]` | Commands to run before commit | -| `hooks.postCommit` | `string[]` | `[]` | Commands to run after commit | +| Type | Emoji | Description | Aliases | +|------|-------|-------------|---------| +| `feat` | โœจ | A new feature | feature, new | +| `fix` | ๐Ÿ› | A bug fix | bugfix, hotfix | +| `docs` | ๐Ÿ“š | Documentation changes | documentation | +| `style` | ๐Ÿ’Ž | Code style changes | formatting | +| `refactor` | ๐Ÿ“ฆ | Code refactoring | refactoring | +| `perf` | ๐Ÿš€ | Performance improvements | performance, optimization | +| `test` | ๐Ÿšจ | Testing | testing | +| `build` | ๐Ÿ›  | Build system changes | ci, deps | +| `ci` | โš™๏ธ | CI configuration | continuous-integration | +| `chore` | โ™ป๏ธ | Maintenance tasks | maintenance | +| `revert` | ๐Ÿ—‘ | Revert previous commit | rollback | -## ๐Ÿค– AI Integration +## ๐Ÿ“ Commit Message Format -Commitweave supports multiple AI providers for intelligent commit message generation: +CommitWeave follows the [Conventional Commits](https://www.conventionalcommits.org/) specification: -### OpenAI Integration -```json -{ - "ai": { - "provider": "openai", - "apiKey": "your-openai-api-key", - "model": "gpt-3.5-turbo", - "maxTokens": 150, - "temperature": 0.7 - } -} ``` +[optional scope]: -### Anthropic Integration -```json -{ - "ai": { - "provider": "anthropic", - "apiKey": "your-anthropic-api-key", - "model": "claude-3-haiku-20240307", - "maxTokens": 150, - "temperature": 0.7 - } -} -``` +[optional body] -### Mock Provider (Default) -Perfect for testing and development without API costs: -```json -{ - "ai": { - "provider": "mock" - } -} +[optional footer(s)] ``` -## ๐Ÿ› ๏ธ API Reference - -### CommitBuilder - -The main class for building commit messages: - -```typescript -class CommitBuilder { - constructor(config: Config) - - setType(type: string): this - setScope(scope: string): this - setSubject(subject: string): this - setBody(body: string): this - setFooter(footer: string): this - setBreakingChange(isBreaking: boolean): this - setEmoji(emoji: string): this - - build(): string - validate(): { valid: boolean; errors: string[] } - reset(): this -} +### Examples + +**Simple commit:** +``` +feat: โœจ add user authentication ``` -### GitUtils - -Git operations wrapper with enhanced functionality: - -```typescript -class GitUtils { - constructor(workingDir?: string) - - async isGitRepository(): Promise - async getStatus(): Promise - async getStagedChanges(): Promise - async getDiff(staged?: boolean): Promise - async stageAll(): Promise - async stageFiles(files: string[]): Promise - async commit(message: string, options?: { dryRun?: boolean }): Promise - async getCurrentBranch(): Promise - async getLastCommitMessage(): Promise - async hasUncommittedChanges(): Promise -} +**With scope:** +``` +feat(auth): โœจ add JWT token validation ``` -### AI Providers +**With body and breaking change:** +``` +feat(api)!: โœจ implement new user API -```typescript -interface AIProvider { - generateCommitMessage(diff: string, options?: AISummaryOptions): Promise - isConfigured(): boolean -} +This introduces a new user management API that replaces +the legacy user system. -// Available providers -const openaiProvider = new OpenAIProvider(apiKey, model); -const anthropicProvider = new AnthropicProvider(apiKey, model); -const mockProvider = new MockAIProvider(); +BREAKING CHANGE: Legacy user endpoints have been removed ``` -## ๐Ÿ—๏ธ Development +## ๐Ÿ› ๏ธ Development -### Building from Source +### Prerequisites +- Node.js >= 18.0.0 +- npm or yarn +- Git +### Setup ```bash # Clone the repository git clone https://github.com/GLINCKER/commitweave.git @@ -372,150 +176,89 @@ cd commitweave # Install dependencies npm install -# Build the project (dual CJS/ESM output) +# Build the project npm run build -# Run in development mode -npm run dev +# Run tests +npm test -# Test the CLI locally -npm start +# Development mode +npm run dev ``` -### Build System - -Commitweave uses a sophisticated dual-build system: - -- **CommonJS Build** (`dist/index.js`) - For Node.js and older tools -- **ESM Build** (`dist/index.mjs`) - For modern ES modules -- **TypeScript Declarations** (`dist/index.d.ts`) - For full type support -- **CLI Binary** (`dist/bin.js`) - Executable CLI tool - ### Project Structure - ``` commitweave/ -โ”œโ”€โ”€ bin/ -โ”‚ โ”œโ”€โ”€ index.ts # ESM CLI entrypoint -โ”‚ โ””โ”€โ”€ index.cjs.ts # CommonJS CLI entrypoint โ”œโ”€โ”€ src/ +โ”‚ โ”œโ”€โ”€ cli/ # CLI interface logic +โ”‚ โ”œโ”€โ”€ core/ # Core commit building logic โ”‚ โ”œโ”€โ”€ types/ # TypeScript type definitions -โ”‚ โ”‚ โ”œโ”€โ”€ config.ts # Configuration types -โ”‚ โ”‚ โ”œโ”€โ”€ commit.ts # Commit message types -โ”‚ โ”‚ โ”œโ”€โ”€ git.ts # Git operation types -โ”‚ โ”‚ โ”œโ”€โ”€ ai.ts # AI provider types -โ”‚ โ”‚ โ””โ”€โ”€ index.ts # Unified type exports -โ”‚ โ”œโ”€โ”€ config/ -โ”‚ โ”‚ โ””โ”€โ”€ defaultConfig.ts # Default configuration -โ”‚ โ”œโ”€โ”€ core/ -โ”‚ โ”‚ โ””โ”€โ”€ commitBuilder.ts # Commit message builder -โ”‚ โ”œโ”€โ”€ utils/ -โ”‚ โ”‚ โ”œโ”€โ”€ git.ts # Git operations -โ”‚ โ”‚ โ””โ”€โ”€ ai.ts # AI integrations -โ”‚ โ””โ”€โ”€ index.ts # Main library exports -โ”œโ”€โ”€ scripts/ -โ”‚ โ””โ”€โ”€ prepare-dist.js # Build preparation script -โ”œโ”€โ”€ dist/ # Build output -โ”‚ โ”œโ”€โ”€ index.js # CommonJS entry -โ”‚ โ”œโ”€โ”€ index.mjs # ESM entry -โ”‚ โ”œโ”€โ”€ index.d.ts # Type definitions -โ”‚ โ”œโ”€โ”€ bin.js # CLI executable -โ”‚ โ””โ”€โ”€ lib/ # Internal modules -โ”œโ”€โ”€ tsconfig*.json # TypeScript configurations -โ”œโ”€โ”€ glinr-commit.json # Default config template -โ””โ”€โ”€ README.md +โ”‚ โ”œโ”€โ”€ utils/ # Utility functions +โ”‚ โ””โ”€โ”€ config/ # Configuration management +โ”œโ”€โ”€ bin/ # CLI entry points +โ”œโ”€โ”€ scripts/ # Build and utility scripts +โ””โ”€โ”€ .github/workflows/ # CI/CD workflows ``` ## ๐Ÿงช Testing ```bash -# Run tests (when available) +# Run all tests npm test -# Test package imports -node -e "console.log(Object.keys(require('./dist/index.js')))" +# Test commit builder +npx tsx scripts/test-local.ts -# Test CLI functionality -./dist/bin.js --help +# Test CLI functions +npx tsx scripts/test-cli-functions.ts ``` -## ๐Ÿ“ฆ Package Distribution - -The package is optimized for npm distribution with: +## ๐Ÿ“ฆ Publishing -- โœ… **Dual Module Support** - Both CJS and ESM -- โœ… **Complete TypeScript Definitions** - Full IntelliSense support -- โœ… **Tree-shakable Exports** - Import only what you need -- โœ… **Zero Runtime Dependencies** - Production builds are dependency-free -- โœ… **Cross-platform Binary** - Works on Windows, macOS, and Linux +CommitWeave uses automated publishing via GitHub Actions: -## ๐Ÿค Contributing +```bash +# Create a new beta release +git tag v0.1.0-beta.3 +git push origin v0.1.0-beta.3 +``` -We welcome contributions! Here's how to get started: +This triggers: +- GitHub release creation +- NPM package publishing +- Beta tag distribution -1. **Fork the repository** -2. **Create a feature branch**: `git checkout -b feature/amazing-feature` -3. **Make your changes** with proper TypeScript types -4. **Test your changes**: `npm run build && npm test` -5. **Commit using commitweave**: `commitweave` -6. **Push to your branch**: `git push origin feature/amazing-feature` -7. **Open a Pull Request** +## ๐Ÿค Contributing -### Development Guidelines +We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details. -- Use TypeScript for all new code -- Follow the existing code style and patterns -- Add type definitions for all new APIs -- Update documentation for new features -- Test both CommonJS and ESM imports -- Ensure cross-platform compatibility +### Quick Contribution Steps +1. Fork the repository +2. Create a feature branch: `git checkout -b feature/amazing-feature` +3. Make your changes +4. Add tests if applicable +5. Run `npm test` to ensure everything works +6. Commit using CommitWeave: `commitweave` +7. Push and create a Pull Request ## ๐Ÿ“„ License -MIT License - see [LICENSE](LICENSE) for details. - -## ๐Ÿ—บ๏ธ Roadmap - -### v1.0 - Core Features -- [x] Interactive CLI with beautiful prompts -- [x] Conventional commit support -- [x] Emoji integration -- [x] Full TypeScript support -- [x] Dual module system (CJS/ESM) -- [x] AI provider infrastructure - -### v1.1 - Enhanced AI -- [ ] Full OpenAI integration -- [ ] Full Anthropic integration -- [ ] Smart diff analysis -- [ ] Context-aware suggestions - -### v1.2 - Advanced Features -- [ ] Git hooks implementation -- [ ] Configuration validation -- [ ] Template system for custom formats -- [ ] Plugin architecture - -### v1.3 - Developer Experience -- [ ] Interactive configuration wizard -- [ ] VS Code extension -- [ ] GitHub Actions integration -- [ ] Commit message templates +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. ## ๐Ÿ™ Acknowledgments -- **Conventional Commits** - For the commit message standard -- **Inquirer.js/Enquirer** - For beautiful CLI prompts -- **Chalk** - For colorful terminal output -- **Simple Git** - For Git operations -- **Zod** - For runtime type validation - ---- +- [Conventional Commits](https://www.conventionalcommits.org/) for the commit format specification +- [Enquirer](https://github.com/enquirejs/enquirer) for beautiful CLI prompts +- [Chalk](https://github.com/chalk/chalk) for terminal string styling +- [Simple Git](https://github.com/steveukx/git-js) for Git integration -
+## ๐Ÿ”— Links -**Made with โค๏ธ by [TypeWeaver](https://typeweaver.com/)** +- [NPM Package](https://www.npmjs.com/package/@typeweaver/commitweave) +- [GitHub Repository](https://github.com/GLINCKER/commitweave) +- [Issue Tracker](https://github.com/GLINCKER/commitweave/issues) +- [TypeWeaver Organization](https://github.com/GLINCKER) -[npm](https://www.npmjs.com/package/@typeweaver/commitweave) โ€ข [GitHub](https://github.com/GLINCKER/commitweave) โ€ข [Issues](https://github.com/GLINCKER/commitweave/issues) +--- -
\ No newline at end of file +**Made with โค๏ธ by the TypeWeaver team** \ No newline at end of file diff --git a/RELEASE_CHECKLIST.md b/RELEASE_CHECKLIST.md index cf84138..568f82c 100644 --- a/RELEASE_CHECKLIST.md +++ b/RELEASE_CHECKLIST.md @@ -3,7 +3,7 @@ ## ๐ŸŽฏ **Current Status: Ready for Beta Release** ### โœ… **Versioning Setup Complete** -- **Current version**: `0.1.0-beta.1` +- **Current version**: `0.1.0-beta.3` - **NPM Strategy**: Beta releases on `@beta` tag, stable releases on `@latest` - **Single main branch**: All development and releases from `main` @@ -12,14 +12,14 @@ ### Beta Release (Recommended First Release) ```bash # Quick release using script -./scripts/release.sh beta 0.1.0-beta.1 +./scripts/release.sh beta 0.1.0-beta.3 # Manual steps (alternative) git add . git commit -m "feat: initial beta release" -git tag v0.1.0-beta.1 +git tag v0.1.0-beta.3 git push origin main -git push origin v0.1.0-beta.1 +git push origin v0.1.0-beta.3 ``` ### Installation Commands After Release @@ -28,7 +28,7 @@ git push origin v0.1.0-beta.1 npm install -g @typeweaver/commitweave@beta # Specific version -npm install -g @typeweaver/commitweave@0.1.0-beta.1 +npm install -g @typeweaver/commitweave@0.1.0-beta.3 ``` ## ๐Ÿ“‹ **Pre-Release Checklist** @@ -45,8 +45,8 @@ npm install -g @typeweaver/commitweave@0.1.0-beta.1 - [ ] Package structure is correct โœ… ### Versioning -- [ ] Version follows beta format: `0.1.0-beta.X` โœ… -- [ ] Tag will follow format: `v0.1.0-beta.X` โœ… +- [ ] Version follows beta format: `0.1.0-beta.3` โœ… +- [ ] Tag will follow format: `v0.1.0-beta.3` โœ… - [ ] Workflows configured for beta releases โœ… ## ๐Ÿ”„ **Automated Workflow** @@ -99,4 +99,4 @@ commitweave init Your CommitWeave package is ready for beta release. The versioning strategy is sound, workflows are configured, and the package builds successfully. -**To release**: Just run `./scripts/release.sh beta 0.1.0-beta.1` when ready! \ No newline at end of file +**To release**: Just run `./scripts/release.sh beta 0.1.0-beta.3` when ready! \ No newline at end of file diff --git a/package.json b/package.json index 5bf3077..61c9f8a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@typeweaver/commitweave", - "version": "0.1.0-beta.1", + "version": "0.1.0-beta.3", "description": "A modern CLI to write smart, structured, and beautiful git commit messages with emoji support, conventional commit rules, AI-powered summaries (optional), and built-in hooks.", "main": "./dist/index.js", "module": "./dist/index.mjs", From 57a18c45f20cd8586c9e5640bf5fcdf1b7f1d2f7 Mon Sep 17 00:00:00 2001 From: GDS K S Date: Sun, 20 Jul 2025 18:04:30 -0500 Subject: [PATCH 4/7] Branding and Integration --- .github/workflows/commit-lint.yml | 43 +++ bin/index.cjs.ts | 485 ++++++++++++++++++++++++++++-- bin/index.ts | 347 +++++++++++++++++++-- package.json | 3 +- scripts/check-commit.ts | 200 ++++++++++++ src/types/config.ts | 10 + src/ui/banner.ts | 136 +++++++++ src/utils/ai.ts | 218 +++++++++++++- 8 files changed, 1401 insertions(+), 41 deletions(-) create mode 100644 .github/workflows/commit-lint.yml create mode 100644 scripts/check-commit.ts create mode 100644 src/ui/banner.ts diff --git a/.github/workflows/commit-lint.yml b/.github/workflows/commit-lint.yml new file mode 100644 index 0000000..058d92f --- /dev/null +++ b/.github/workflows/commit-lint.yml @@ -0,0 +1,43 @@ +name: Commit Message Validation + +on: + push: + branches: [ main, develop ] + pull_request: + branches: [ main, develop ] + +jobs: + commit-lint: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Fetch all history to check commits + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Build project + run: npm run build + + - name: Validate commit message + run: npm run check + # Alternative: use the CLI directly + # run: npx commitweave check + + - name: Validate all commits in PR (for pull requests) + if: github.event_name == 'pull_request' + run: | + # Check all commits in the PR + for commit in $(git rev-list ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }}); do + echo "Checking commit: $commit" + git log -1 --pretty=%B $commit | npx tsx scripts/check-commit.ts || exit 1 + done \ No newline at end of file diff --git a/bin/index.cjs.ts b/bin/index.cjs.ts index bb3af0a..f881ab6 100644 --- a/bin/index.cjs.ts +++ b/bin/index.cjs.ts @@ -1,24 +1,123 @@ #!/usr/bin/env node // CommonJS version of the CLI entrypoint -const chalk = require('chalk'); const { prompt } = require('enquirer'); -const { writeFile, access } = require('fs/promises'); +const { writeFile, access, readFile } = require('fs/promises'); const { join } = require('path'); +// Dynamic import for ESM-only chalk +let chalk: any; +let createCommitFlow: any; +let stageAllAndCommit: any; +let generateAISummary: any; +let simpleGit: any; +let ConfigSchema: any; +let defaultConfig: any; +let bannerModule: any; + +async function loadConfig() { + try { + if (!ConfigSchema || !defaultConfig) { + const configModule = require('../dist/lib/types/config.js'); + const defaultConfigModule = require('../dist/lib/config/defaultConfig.js'); + ConfigSchema = configModule.ConfigSchema; + defaultConfig = defaultConfigModule.defaultConfig; + } + + const configPath = join(process.cwd(), 'glinr-commit.json'); + const configFile = await readFile(configPath, 'utf-8'); + const configData = JSON.parse(configFile); + return ConfigSchema.parse(configData); + } catch (error) { + return defaultConfig; + } +} + async function main() { - console.log(chalk.cyan.bold('Welcome to Commitweave ๐Ÿงถ')); - console.log(chalk.gray('A modern CLI for smart, structured, and beautiful git commits\n')); + const args = process.argv.slice(2); + const aiFlag = args.includes('--ai'); + const isInteractiveMode = !aiFlag && !args.includes('init') && !args.includes('check') && !args.includes('--help') && !args.includes('-h'); + + // Load dynamic dependencies + if (!chalk) { + chalk = await import('chalk'); + chalk = chalk.default; + } + + if (!bannerModule) { + bannerModule = require('../dist/lib/ui/banner.js'); + } + + // Show beautiful banner for interactive mode + if (isInteractiveMode) { + bannerModule.printBanner(); + await bannerModule.showLoadingAnimation('Initializing Commitweave', 1500); + console.log(''); + bannerModule.printFeatureHighlight(); + } else { + // Show compact banner for direct commands + console.log(chalk.cyan.bold('๐Ÿงถ Commitweave')); + console.log(chalk.gray('Smart, structured, and beautiful git commits\n')); + } try { + // Handle AI flag directly without prompting + if (aiFlag) { + console.log(''); // Add some spacing after the compact banner + await handleAICommitCommand(); + return; + } + + // Handle other direct commands + if (args.includes('init')) { + await handleInitCommand(); + return; + } + + if (args.includes('check')) { + await handleCheckCommand(); + return; + } + + if (args.includes('--help') || args.includes('-h')) { + await showHelp(); + return; + } + + // Default interactive mode with enhanced UI + console.log(chalk.cyan.bold('๐Ÿš€ What would you like to do today?')); + console.log(''); + const response = await prompt({ type: 'select', name: 'action', - message: 'What would you like to do?', + message: 'Choose an action:', choices: [ - { name: 'create', message: '๐Ÿ“ Create a new commit' }, - { name: 'init', message: 'โš™๏ธ Initialize configuration' }, - { name: 'help', message: 'โ“ Show help' } + { + name: 'create', + message: '๐Ÿ“ Create a new commit', + hint: 'Interactive commit message builder' + }, + { + name: 'ai', + message: '๐Ÿค– AI-powered commit', + hint: 'Let AI analyze your changes and suggest a commit message' + }, + { + name: 'init', + message: 'โš™๏ธ Setup configuration', + hint: 'Initialize or update commitweave settings' + }, + { + name: 'check', + message: '๐Ÿ” Validate commit', + hint: 'Check if your latest commit follows conventions' + }, + { + name: 'help', + message: 'โ“ Show help', + hint: 'View all available commands and options' + } ] }); @@ -26,24 +125,37 @@ async function main() { case 'create': await handleCreateCommand(); break; + case 'ai': + await handleAICommitCommand(); + break; case 'init': await handleInitCommand(); break; + case 'check': + await handleCheckCommand(); + break; case 'help': - showHelp(); + await showHelp(); break; } } catch (error) { if (error instanceof Error && error.name === 'ExitPromptError') { - console.log(chalk.gray('\nGoodbye! ๐Ÿ‘‹')); + console.log(chalk.gray('\n๐Ÿ‘‹ Thanks for using Commitweave!')); + console.log(chalk.cyan(' Happy committing! ๐Ÿงถโœจ')); process.exit(0); } - console.error(chalk.red('An error occurred:'), error); + console.error(chalk.red('๐Ÿ’ฅ An error occurred:'), error); process.exit(1); } } async function handleInitCommand() { + // Load chalk dynamically since it's ESM-only + if (!chalk) { + chalk = await import('chalk'); + chalk = chalk.default; + } + try { const configPath = join(process.cwd(), 'glinr-commit.json'); @@ -113,16 +225,355 @@ async function handleInitCommand() { } async function handleCreateCommand() { - console.log(chalk.yellow('๐Ÿšง Commit creation functionality will be available after build!')); - console.log(chalk.gray(' Use "npx tsx bin/index.ts" for development testing')); + // Load dependencies dynamically + if (!chalk) { + chalk = await import('chalk'); + chalk = chalk.default; + } + if (!createCommitFlow) { + const module = require('../dist/lib/cli/createCommitFlow.js'); + createCommitFlow = module.createCommitFlow; + } + if (!stageAllAndCommit) { + const module = require('../dist/lib/utils/git.js'); + stageAllAndCommit = module.stageAllAndCommit; + } + + try { + const result = await createCommitFlow(); + + if (result.cancelled) { + console.log(chalk.yellow('โœจ Commit creation cancelled')); + return; + } + + console.log(chalk.blue('\n๐Ÿ“ฆ Staging files and creating commit...\n')); + + const commitResult = await stageAllAndCommit(result.message); + console.log(chalk.green('โœ… ' + commitResult)); + + } catch (error) { + console.error(chalk.red('โŒ Failed to create commit:')); + + if (error instanceof Error) { + console.error(chalk.red(' ' + error.message)); + + if (error.message.includes('Not a git repository')) { + console.log(chalk.yellow('\n๐Ÿ’ก Tip: Initialize a git repository with "git init"')); + } else if (error.message.includes('No staged changes')) { + console.log(chalk.yellow('\n๐Ÿ’ก Tip: Make some changes to your files first')); + } + } else { + console.error(chalk.red(' Unknown error occurred')); + } + + process.exit(1); + } } -function showHelp() { +async function handleAICommitCommand() { + try { + console.log(chalk.magenta('โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ')); + console.log(chalk.magenta('โ”‚') + chalk.bold.white(' ๐Ÿค– AI Commit Assistant ') + chalk.magenta('โ”‚')); + console.log(chalk.magenta('โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ')); + console.log(''); + + await bannerModule.showLoadingAnimation('Loading AI configuration', 600); + + // Load configuration + const config = await loadConfig(); + if (!config.ai) { + console.log(chalk.yellow('โš ๏ธ AI configuration not found')); + console.log(chalk.gray(' Let\'s set that up for you!')); + console.log(chalk.cyan(' Run: ') + chalk.white('commitweave init') + chalk.gray(' to configure AI')); + return; + } + + await bannerModule.showLoadingAnimation('Connecting to repository', 400); + + // Load dependencies + if (!simpleGit) { + const gitModule = require('simple-git'); + simpleGit = gitModule.simpleGit; + } + if (!generateAISummary) { + const aiModule = require('../dist/lib/utils/ai.js'); + generateAISummary = aiModule.generateAISummary; + } + + // Initialize git + const git = simpleGit(); + + // Check if we're in a git repository + const isRepo = await git.checkIsRepo(); + if (!isRepo) { + console.error(chalk.red('โŒ Not a git repository')); + console.log(chalk.yellow('๐Ÿ’ก Pro tip: Initialize a git repository first')); + console.log(chalk.gray(' Run: ') + chalk.cyan('git init')); + return; + } + + await bannerModule.showLoadingAnimation('Analyzing staged changes', 800); + + // Get staged diff + const diff = await git.diff(['--cached']); + + if (!diff || diff.trim().length === 0) { + console.error(chalk.red('โŒ No staged changes found')); + console.log(chalk.yellow('๐Ÿ’ก Pro tip: Stage some changes first')); + console.log(chalk.gray(' Run: ') + chalk.cyan('git add .') + chalk.gray(' or ') + chalk.cyan('git add ')); + return; + } + + console.log(chalk.green(`โœจ Detected ${diff.split('\n').length} lines of changes`)); + console.log(''); + + // Generate AI summary + await bannerModule.showLoadingAnimation('AI is analyzing your code', 2000); + const { subject, body } = await generateAISummary(diff, config.ai); + + // Show preview + console.log(chalk.green('\nโœจ AI-generated commit message:')); + console.log(chalk.cyan('โ”Œโ”€ Subject:')); + console.log(chalk.white(`โ”‚ ${subject}`)); + if (body && body.trim()) { + console.log(chalk.cyan('โ”œโ”€ Body:')); + body.split('\n').forEach((line: string) => { + console.log(chalk.white(`โ”‚ ${line}`)); + }); + } + console.log(chalk.cyan('โ””โ”€')); + + // Ask for confirmation or editing + const action = await prompt({ + type: 'select', + name: 'choice', + message: 'What would you like to do with this AI-generated message?', + choices: [ + { name: 'commit', message: 'โœ… Use this message and commit' }, + { name: 'edit', message: 'โœ๏ธ Edit this message' }, + { name: 'regenerate', message: '๐Ÿ”„ Generate a new message' }, + { name: 'cancel', message: 'โŒ Cancel' } + ] + }) as { choice: string }; + + switch (action.choice) { + case 'commit': + await commitWithMessage(subject, body); + break; + case 'edit': + await editAndCommit(subject, body); + break; + case 'regenerate': + console.log(chalk.blue('\n๐Ÿ”„ Regenerating AI message...')); + await handleAICommitCommand(); // Recursive call + break; + case 'cancel': + console.log(chalk.yellow('โœจ AI commit cancelled')); + break; + } + + } catch (error) { + console.error(chalk.red('โŒ Failed to create AI commit:')); + + if (error instanceof Error) { + console.error(chalk.red(' ' + error.message)); + + if (error.message.includes('API key')) { + console.log(chalk.yellow('\n๐Ÿ’ก Tip: Configure your AI API key in glinr-commit.json')); + } + } else { + console.error(chalk.red(' Unknown error occurred')); + } + + process.exit(1); + } +} + +async function commitWithMessage(subject: string, body: string) { + const fullMessage = body && body.trim() ? `${subject}\n\n${body}` : subject; + + console.log(chalk.blue('\n๐Ÿ“ฆ Creating commit...\n')); + const commitResult = await stageAllAndCommit(fullMessage); + console.log(chalk.green('โœ… ' + commitResult)); +} + +async function editAndCommit(subject: string, body: string) { + const editedSubject = await prompt({ + type: 'input', + name: 'subject', + message: 'Edit commit subject:', + initial: subject + }) as { subject: string }; + + const editedBody = await prompt({ + type: 'input', + name: 'body', + message: 'Edit commit body (optional):', + initial: body || '' + }) as { body: string }; + + await commitWithMessage(editedSubject.subject, editedBody.body); +} + +async function handleCheckCommand() { + try { + // Load dependencies + const { execSync } = require('child_process'); + + console.log('Checking commit message...'); + + // Load configuration + const config = await loadConfig(); + console.log('Configuration loaded'); + + // Get commit message + let commitMessage: string; + try { + commitMessage = execSync('git log -1 --pretty=%B', { encoding: 'utf-8' }).trim(); + } catch (error) { + console.error('Error: Failed to read commit message from git'); + console.error('Make sure you are in a git repository with at least one commit'); + process.exit(1); + } + + console.log('Latest commit message:'); + console.log(commitMessage); + console.log(''); + + // Use inline validation logic for CommonJS compatibility + const validation = validateCommitMessage(commitMessage, config); + + if (validation.valid) { + console.log('โœ“ Commit message is valid'); + process.exit(0); + } else { + console.log('โœ— Commit message validation failed:'); + for (const error of validation.errors) { + console.log(` - ${error}`); + } + console.log(''); + console.log('Please fix the commit message and try again.'); + + // Show helpful information + if (config.conventionalCommits) { + console.log(''); + console.log('Conventional commit format: type(scope): subject'); + console.log('Example: feat(auth): add user login functionality'); + console.log(''); + console.log('Available types:'); + for (const type of config.commitTypes) { + console.log(` ${type.type}: ${type.description}`); + } + } + + process.exit(1); + } + } catch (error) { + console.error('Error:', error instanceof Error ? error.message : 'Unknown error'); + process.exit(1); + } +} + +function parseCommitMessage(message: string) { + const lines = message.split('\n'); + const header = lines[0] || ''; + + // Match conventional commit format: type(scope)!: subject + const conventionalPattern = /^(\w+)(\([^)]+\))?(!)?\s*:\s*(.+)$/; + const match = header.match(conventionalPattern); + + if (match) { + const [, type, scopeWithParens, breaking, subject] = match; + const scope = scopeWithParens ? scopeWithParens.slice(1, -1) : undefined; + + return { + type, + scope, + breaking: !!breaking, + subject: subject?.trim(), + body: lines.slice(2).join('\n').trim() || undefined, + footer: undefined + }; + } + + // If not conventional format, treat entire header as subject + return { + subject: header.trim(), + body: lines.slice(2).join('\n').trim() || undefined + }; +} + +function validateCommitMessage(message: string, config: any) { + const errors: string[] = []; + const parsed = parseCommitMessage(message); + + // Check if conventional commits are required + if (config.conventionalCommits) { + if (!parsed.type) { + errors.push('Conventional commit format required: type(scope): subject'); + errors.push('Valid types: ' + config.commitTypes.map((ct: any) => ct.type).join(', ')); + } else { + // Validate commit type + const validTypes = config.commitTypes.map((ct: any) => ct.type); + if (!validTypes.includes(parsed.type)) { + errors.push(`Invalid commit type: ${parsed.type}`); + errors.push('Valid types: ' + validTypes.join(', ')); + } + } + } + + // Check subject requirements + if (!parsed.subject || parsed.subject.length === 0) { + errors.push('Commit subject is required'); + } else { + // Check subject length + if (parsed.subject.length > config.maxSubjectLength) { + errors.push(`Subject too long: ${parsed.subject.length} characters (max: ${config.maxSubjectLength})`); + } + + // Check subject format (should not end with period) + if (parsed.subject.endsWith('.')) { + errors.push('Subject should not end with a period'); + } + + // Check subject case (should start with lowercase unless it's a proper noun) + if (config.conventionalCommits && parsed.subject && parsed.subject[0] !== parsed.subject[0].toLowerCase()) { + errors.push('Subject should start with lowercase letter'); + } + } + + // Check body length if present + if (parsed.body && config.maxBodyLength) { + const bodyLines = parsed.body.split('\n'); + for (const line of bodyLines) { + if (line.length > config.maxBodyLength) { + errors.push(`Body line too long: ${line.length} characters (max: ${config.maxBodyLength})`); + break; + } + } + } + + return { + valid: errors.length === 0, + errors + }; +} + +async function showHelp() { + // Load chalk dynamically since it's ESM-only + if (!chalk) { + chalk = await import('chalk'); + chalk = chalk.default; + } + console.log(chalk.bold('\n๐Ÿ“– Commitweave Help\n')); console.log('Available commands:'); - console.log(chalk.cyan(' commitweave') + ' Start interactive commit creation'); - console.log(chalk.cyan(' commitweave init') + ' Initialize configuration file'); - console.log(chalk.cyan(' commitweave --help') + ' Show this help message'); + console.log(chalk.cyan(' commitweave') + ' Start interactive commit creation'); + console.log(chalk.cyan(' commitweave --ai') + ' Generate AI-powered commit message'); + console.log(chalk.cyan(' commitweave check') + ' Validate latest commit message'); + console.log(chalk.cyan(' commitweave init') + ' Initialize configuration file'); + console.log(chalk.cyan(' commitweave --help') + ' Show this help message'); console.log('\nFor more information, visit: https://github.com/typeweaver/commitweave'); } diff --git a/bin/index.ts b/bin/index.ts index e03431b..cb66489 100644 --- a/bin/index.ts +++ b/bin/index.ts @@ -2,24 +2,106 @@ import chalk from 'chalk'; import { prompt } from 'enquirer'; -import { writeFile, access } from 'fs/promises'; +import { writeFile, access, readFile } from 'fs/promises'; import { join } from 'path'; +import { simpleGit } from 'simple-git'; import { createCommitFlow } from '../src/cli/createCommitFlow.js'; import { stageAllAndCommit } from '../src/utils/git.js'; +import { generateAISummary } from '../src/utils/ai.js'; +import { ConfigSchema, type Config } from '../src/types/config.js'; +import { defaultConfig } from '../src/config/defaultConfig.js'; +import { printBanner, showLoadingAnimation, printWelcome, printFeatureHighlight, BRAND_COLORS } from '../src/ui/banner.js'; + +async function loadConfig(): Promise { + try { + const configPath = join(process.cwd(), 'glinr-commit.json'); + const configFile = await readFile(configPath, 'utf-8'); + const configData = JSON.parse(configFile); + return ConfigSchema.parse(configData); + } catch (error) { + return defaultConfig; + } +} async function main() { - console.log(chalk.cyan.bold('Welcome to Commitweave ๐Ÿงถ')); - console.log(chalk.gray('A modern CLI for smart, structured, and beautiful git commits\n')); + const args = process.argv.slice(2); + const aiFlag = args.includes('--ai'); + const isInteractiveMode = !aiFlag && !args.includes('init') && !args.includes('check') && !args.includes('--help') && !args.includes('-h'); + + // Show beautiful banner for interactive mode + if (isInteractiveMode) { + printBanner(); + await showLoadingAnimation('Initializing Commitweave', 1500); + console.log(''); + printFeatureHighlight(); + } else { + // Show compact banner for direct commands + console.log(chalk.hex(BRAND_COLORS.primary).bold('๐Ÿงถ Commitweave')); + console.log(chalk.hex(BRAND_COLORS.muted)('Smart, structured, and beautiful git commits')); + console.log(chalk.hex(BRAND_COLORS.muted)('Powered by ') + + chalk.hex(BRAND_COLORS.accent).bold('GLINR STUDIOS') + + chalk.hex(BRAND_COLORS.muted)(' โ€ข ') + + chalk.hex(BRAND_COLORS.primary)('@typeweaver\n')); + } try { + // Handle AI flag directly without prompting + if (aiFlag) { + console.log(''); // Add some spacing after the compact banner + await handleAICommitCommand(); + return; + } + + // Handle other direct commands + if (args.includes('init')) { + await handleInitCommand(); + return; + } + + if (args.includes('check')) { + await handleCheckCommand(); + return; + } + + if (args.includes('--help') || args.includes('-h')) { + showHelp(); + return; + } + + // Default interactive mode with enhanced UI + console.log(chalk.hex(BRAND_COLORS.accent).bold('๐Ÿš€ What would you like to do today?')); + console.log(''); + const response = await prompt({ type: 'select', name: 'action', - message: 'What would you like to do?', + message: 'Choose an action:', choices: [ - { name: 'create', message: '๐Ÿ“ Create a new commit' }, - { name: 'init', message: 'โš™๏ธ Initialize configuration' }, - { name: 'help', message: 'โ“ Show help' } + { + name: 'create', + message: '๐Ÿ“ Create a new commit', + hint: 'Interactive commit message builder' + }, + { + name: 'ai', + message: '๐Ÿค– AI-powered commit', + hint: 'Let AI analyze your changes and suggest a commit message' + }, + { + name: 'init', + message: 'โš™๏ธ Setup configuration', + hint: 'Initialize or update commitweave settings' + }, + { + name: 'check', + message: '๐Ÿ” Validate commit', + hint: 'Check if your latest commit follows conventions' + }, + { + name: 'help', + message: 'โ“ Show help', + hint: 'View all available commands and options' + } ] }); @@ -27,19 +109,30 @@ async function main() { case 'create': await handleCreateCommand(); break; + case 'ai': + await handleAICommitCommand(); + break; case 'init': await handleInitCommand(); break; + case 'check': + await handleCheckCommand(); + break; case 'help': showHelp(); break; } } catch (error) { if (error instanceof Error && error.name === 'ExitPromptError') { - console.log(chalk.gray('\nGoodbye! ๐Ÿ‘‹')); + console.log(chalk.hex(BRAND_COLORS.muted)('\n๐Ÿ‘‹ Thanks for using Commitweave!')); + console.log(chalk.hex(BRAND_COLORS.primary)(' Happy committing! ๐Ÿงถโœจ')); + console.log(chalk.hex(BRAND_COLORS.muted)(' ') + + chalk.hex(BRAND_COLORS.accent).bold('GLINR STUDIOS') + + chalk.hex(BRAND_COLORS.muted)(' โ€ข ') + + chalk.hex(BRAND_COLORS.primary)('@typeweaver')); globalThis.process?.exit?.(0); } - console.error(chalk.red('An error occurred:'), error); + console.error(chalk.hex(BRAND_COLORS.error)('๐Ÿ’ฅ An error occurred:'), error); globalThis.process?.exit?.(1); } } @@ -115,30 +208,48 @@ async function handleInitCommand() { async function handleCreateCommand() { try { - console.log(chalk.blue('\n๐Ÿš€ Starting commit creation...\n')); + console.log(chalk.hex(BRAND_COLORS.primary)('โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ')); + console.log(chalk.hex(BRAND_COLORS.primary)('โ”‚') + chalk.bold.white(' ๐Ÿš€ Commit Creation Wizard ') + chalk.hex(BRAND_COLORS.primary)('โ”‚')); + console.log(chalk.hex(BRAND_COLORS.primary)('โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ')); + console.log(''); + + await showLoadingAnimation('Preparing commit builder', 800); + console.log(''); const result = await createCommitFlow(); if (result.cancelled) { - console.log(chalk.yellow('โœจ Commit creation cancelled')); + console.log(chalk.hex(BRAND_COLORS.warning)('โœจ Commit creation cancelled')); + console.log(chalk.hex(BRAND_COLORS.muted)(' No worries, come back anytime! ๐Ÿงถ')); return; } - console.log(chalk.blue('\n๐Ÿ“ฆ Staging files and creating commit...\n')); + console.log(chalk.hex(BRAND_COLORS.accent)('\nโ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ')); + console.log(chalk.hex(BRAND_COLORS.accent)('โ”‚') + chalk.bold.white(' ๐Ÿ“ฆ Finalizing Your Commit ') + chalk.hex(BRAND_COLORS.accent)('โ”‚')); + console.log(chalk.hex(BRAND_COLORS.accent)('โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ')); + + await showLoadingAnimation('Staging files and creating commit', 1200); const commitResult = await stageAllAndCommit(result.message); - console.log(chalk.green('โœ… ' + commitResult)); + console.log(chalk.hex(BRAND_COLORS.success).bold('\n๐ŸŽ‰ Success! ') + chalk.hex(BRAND_COLORS.success)(commitResult)); + console.log(chalk.hex(BRAND_COLORS.muted)(' Your commit has been created with style! โœจ')); + console.log(chalk.hex(BRAND_COLORS.muted)(' ') + + chalk.hex(BRAND_COLORS.accent).bold('GLINR STUDIOS') + + chalk.hex(BRAND_COLORS.muted)(' โ€ข ') + + chalk.hex(BRAND_COLORS.primary)('@typeweaver')); } catch (error) { - console.error(chalk.red('โŒ Failed to create commit:')); + console.log(chalk.red('\n๐Ÿ’ฅ Oops! Something went wrong:')); if (error instanceof Error) { console.error(chalk.red(' ' + error.message)); if (error.message.includes('Not a git repository')) { - console.log(chalk.yellow('\n๐Ÿ’ก Tip: Initialize a git repository with "git init"')); + console.log(chalk.yellow('\n๐Ÿ’ก Pro tip: Initialize a git repository first')); + console.log(chalk.gray(' Run: ') + chalk.cyan('git init')); } else if (error.message.includes('No staged changes')) { - console.log(chalk.yellow('\n๐Ÿ’ก Tip: Make some changes to your files first')); + console.log(chalk.yellow('\n๐Ÿ’ก Pro tip: Stage some changes first')); + console.log(chalk.gray(' Run: ') + chalk.cyan('git add .')); } } else { console.error(chalk.red(' Unknown error occurred')); @@ -148,12 +259,210 @@ async function handleCreateCommand() { } } +async function handleAICommitCommand() { + try { + console.log(chalk.hex(BRAND_COLORS.accent)('โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ')); + console.log(chalk.hex(BRAND_COLORS.accent)('โ”‚') + chalk.bold.white(' ๐Ÿค– AI Commit Assistant ') + chalk.hex(BRAND_COLORS.accent)('โ”‚')); + console.log(chalk.hex(BRAND_COLORS.accent)('โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ')); + console.log(''); + + await showLoadingAnimation('Loading AI configuration', 600); + + // Load configuration + const config = await loadConfig(); + if (!config.ai) { + console.log(chalk.yellow('โš ๏ธ AI configuration not found')); + console.log(chalk.gray(' Let\'s set that up for you!')); + console.log(chalk.cyan(' Run: ') + chalk.white('commitweave init') + chalk.gray(' to configure AI')); + return; + } + + await showLoadingAnimation('Connecting to repository', 400); + + // Initialize git + const git = simpleGit(); + + // Check if we're in a git repository + const isRepo = await git.checkIsRepo(); + if (!isRepo) { + console.error(chalk.red('โŒ Not a git repository')); + console.log(chalk.yellow('๐Ÿ’ก Pro tip: Initialize a git repository first')); + console.log(chalk.gray(' Run: ') + chalk.cyan('git init')); + return; + } + + await showLoadingAnimation('Analyzing staged changes', 800); + + // Get staged diff + const diff = await git.diff(['--cached']); + + if (!diff || diff.trim().length === 0) { + console.error(chalk.red('โŒ No staged changes found')); + console.log(chalk.yellow('๐Ÿ’ก Pro tip: Stage some changes first')); + console.log(chalk.gray(' Run: ') + chalk.cyan('git add .') + chalk.gray(' or ') + chalk.cyan('git add ')); + return; + } + + console.log(chalk.green(`โœจ Detected ${diff.split('\n').length} lines of changes`)); + console.log(''); + + // Generate AI summary + await showLoadingAnimation('AI is analyzing your code', 2000); + const { subject, body } = await generateAISummary(diff, config.ai); + + // Show preview + console.log(chalk.green('\nโœจ AI-generated commit message:')); + console.log(chalk.cyan('โ”Œโ”€ Subject:')); + console.log(chalk.white(`โ”‚ ${subject}`)); + if (body && body.trim()) { + console.log(chalk.cyan('โ”œโ”€ Body:')); + body.split('\n').forEach(line => { + console.log(chalk.white(`โ”‚ ${line}`)); + }); + } + console.log(chalk.cyan('โ””โ”€')); + + // Ask for confirmation or editing + const action = await prompt({ + type: 'select', + name: 'choice', + message: 'What would you like to do with this AI-generated message?', + choices: [ + { name: 'commit', message: 'โœ… Use this message and commit' }, + { name: 'edit', message: 'โœ๏ธ Edit this message' }, + { name: 'regenerate', message: '๐Ÿ”„ Generate a new message' }, + { name: 'cancel', message: 'โŒ Cancel' } + ] + }) as { choice: string }; + + switch (action.choice) { + case 'commit': + await commitWithMessage(subject, body); + break; + case 'edit': + await editAndCommit(subject, body); + break; + case 'regenerate': + console.log(chalk.blue('\n๐Ÿ”„ Regenerating AI message...')); + await handleAICommitCommand(); // Recursive call + break; + case 'cancel': + console.log(chalk.yellow('โœจ AI commit cancelled')); + break; + } + + } catch (error) { + console.error(chalk.red('โŒ Failed to create AI commit:')); + + if (error instanceof Error) { + console.error(chalk.red(' ' + error.message)); + + if (error.message.includes('API key')) { + console.log(chalk.yellow('\n๐Ÿ’ก Tip: Configure your AI API key in glinr-commit.json')); + } + } else { + console.error(chalk.red(' Unknown error occurred')); + } + + globalThis.process?.exit?.(1); + } +} + +async function commitWithMessage(subject: string, body: string) { + const fullMessage = body && body.trim() ? `${subject}\n\n${body}` : subject; + + console.log(chalk.blue('\n๐Ÿ“ฆ Creating commit...\n')); + const commitResult = await stageAllAndCommit(fullMessage); + console.log(chalk.green('โœ… ' + commitResult)); +} + +async function editAndCommit(subject: string, body: string) { + const editedSubject = await prompt({ + type: 'input', + name: 'subject', + message: 'Edit commit subject:', + initial: subject + }) as { subject: string }; + + const editedBody = await prompt({ + type: 'input', + name: 'body', + message: 'Edit commit body (optional):', + initial: body || '' + }) as { body: string }; + + await commitWithMessage(editedSubject.subject, editedBody.body); +} + +async function handleCheckCommand() { + try { + // Import the validation functions directly + const { validateCommit, parseCommitMessage, loadConfig } = await import('../scripts/check-commit.js'); + const { execSync } = await import('child_process'); + + console.log('Checking commit message...'); + + // Load configuration + const config = await loadConfig(); + console.log('Configuration loaded'); + + // Get commit message + let commitMessage: string; + try { + commitMessage = execSync('git log -1 --pretty=%B', { encoding: 'utf-8' }).trim(); + } catch (error) { + console.error('Error: Failed to read commit message from git'); + console.error('Make sure you are in a git repository with at least one commit'); + process.exit(1); + } + + console.log('Latest commit message:'); + console.log(commitMessage); + console.log(''); + + // Parse and validate commit message + const parsed = parseCommitMessage(commitMessage); + const validation = validateCommit(parsed, config); + + if (validation.valid) { + console.log('โœ“ Commit message is valid'); + process.exit(0); + } else { + console.log('โœ— Commit message validation failed:'); + for (const error of validation.errors) { + console.log(` - ${error}`); + } + console.log(''); + console.log('Please fix the commit message and try again.'); + + // Show helpful information + if (config.conventionalCommits) { + console.log(''); + console.log('Conventional commit format: type(scope): subject'); + console.log('Example: feat(auth): add user login functionality'); + console.log(''); + console.log('Available types:'); + for (const type of config.commitTypes) { + console.log(` ${type.type}: ${type.description}`); + } + } + + process.exit(1); + } + } catch (error) { + console.error('Error:', error instanceof Error ? error.message : 'Unknown error'); + process.exit(1); + } +} + function showHelp() { console.log(chalk.bold('\n๐Ÿ“– Commitweave Help\n')); console.log('Available commands:'); - console.log(chalk.cyan(' commitweave') + ' Start interactive commit creation'); - console.log(chalk.cyan(' commitweave init') + ' Initialize configuration file'); - console.log(chalk.cyan(' commitweave --help') + ' Show this help message'); + console.log(chalk.cyan(' commitweave') + ' Start interactive commit creation'); + console.log(chalk.cyan(' commitweave --ai') + ' Generate AI-powered commit message'); + console.log(chalk.cyan(' commitweave check') + ' Validate latest commit message'); + console.log(chalk.cyan(' commitweave init') + ' Initialize configuration file'); + console.log(chalk.cyan(' commitweave --help') + ' Show this help message'); console.log('\nFor more information, visit: https://github.com/GLINCKER/commitweave'); } diff --git a/package.json b/package.json index 61c9f8a..64a0370 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@typeweaver/commitweave", - "version": "0.1.0-beta.3", + "version": "0.1.0-beta.4", "description": "A modern CLI to write smart, structured, and beautiful git commit messages with emoji support, conventional commit rules, AI-powered summaries (optional), and built-in hooks.", "main": "./dist/index.js", "module": "./dist/index.mjs", @@ -28,6 +28,7 @@ "clean": "rm -rf dist", "build:post": "npm run build:prepare-dist", "build:prepare-dist": "node scripts/prepare-dist.js", + "check": "tsx scripts/check-commit.ts", "dev": "tsx watch bin/index.ts", "prepublishOnly": "npm run build", "start": "node ./dist/bin.js", diff --git a/scripts/check-commit.ts b/scripts/check-commit.ts new file mode 100644 index 0000000..578824e --- /dev/null +++ b/scripts/check-commit.ts @@ -0,0 +1,200 @@ +#!/usr/bin/env tsx + +import { execSync } from 'child_process'; +import { readFile } from 'fs/promises'; +import { join } from 'path'; +import { ConfigSchema, type Config } from '../src/types/config.js'; +import { defaultConfig } from '../src/config/defaultConfig.js'; + +interface ValidationResult { + valid: boolean; + errors: string[]; +} + +interface ParsedCommit { + type?: string; + scope?: string; + breaking?: boolean; + subject?: string; + body?: string; + footer?: string; +} + +/** + * Load configuration from glinr-commit.json or use defaults + */ +async function loadConfig(): Promise { + try { + const configPath = join(process.cwd(), 'glinr-commit.json'); + const configFile = await readFile(configPath, 'utf-8'); + const configData = JSON.parse(configFile); + return ConfigSchema.parse(configData); + } catch (error) { + return defaultConfig; + } +} + +/** + * Get the latest commit message using git + */ +function getLatestCommitMessage(): string { + try { + const message = execSync('git log -1 --pretty=%B', { encoding: 'utf-8' }); + return message.trim(); + } catch (error) { + console.error('Error: Failed to read commit message from git'); + console.error('Make sure you are in a git repository with at least one commit'); + process.exit(1); + } +} + +/** + * Parse a conventional commit message + */ +function parseCommitMessage(message: string): ParsedCommit { + const lines = message.split('\n'); + const header = lines[0] || ''; + + // Match conventional commit format: type(scope)!: subject + const conventionalPattern = /^(\w+)(\([^)]+\))?(!)?\s*:\s*(.+)$/; + const match = header.match(conventionalPattern); + + if (match) { + const [, type, scopeWithParens, breaking, subject] = match; + const scope = scopeWithParens ? scopeWithParens.slice(1, -1) : undefined; + + return { + type, + scope, + breaking: !!breaking, + subject: subject?.trim(), + body: lines.slice(2).join('\n').trim() || undefined, + footer: undefined // Could be parsed more thoroughly if needed + }; + } + + // If not conventional format, treat entire header as subject + return { + subject: header.trim(), + body: lines.slice(2).join('\n').trim() || undefined + }; +} + +/** + * Validate a parsed commit against configuration + */ +function validateCommit(parsed: ParsedCommit, config: Config): ValidationResult { + const errors: string[] = []; + + // Check if conventional commits are required + if (config.conventionalCommits) { + if (!parsed.type) { + errors.push('Conventional commit format required: type(scope): subject'); + errors.push('Valid types: ' + config.commitTypes.map(ct => ct.type).join(', ')); + } else { + // Validate commit type + const validTypes = config.commitTypes.map(ct => ct.type); + if (!validTypes.includes(parsed.type)) { + errors.push(`Invalid commit type: ${parsed.type}`); + errors.push('Valid types: ' + validTypes.join(', ')); + } + } + } + + // Check subject requirements + if (!parsed.subject || parsed.subject.length === 0) { + errors.push('Commit subject is required'); + } else { + // Check subject length + if (parsed.subject.length > config.maxSubjectLength) { + errors.push(`Subject too long: ${parsed.subject.length} characters (max: ${config.maxSubjectLength})`); + } + + // Check subject format (should not end with period) + if (parsed.subject.endsWith('.')) { + errors.push('Subject should not end with a period'); + } + + // Check subject case (should start with lowercase unless it's a proper noun) + if (config.conventionalCommits && parsed.subject && parsed.subject[0] !== parsed.subject[0].toLowerCase()) { + errors.push('Subject should start with lowercase letter'); + } + } + + // Check body length if present + if (parsed.body && config.maxBodyLength) { + const bodyLines = parsed.body.split('\n'); + for (const line of bodyLines) { + if (line.length > config.maxBodyLength) { + errors.push(`Body line too long: ${line.length} characters (max: ${config.maxBodyLength})`); + break; + } + } + } + + return { + valid: errors.length === 0, + errors + }; +} + +/** + * Main validation function + */ +async function main() { + try { + console.log('Checking commit message...'); + + // Load configuration + const config = await loadConfig(); + console.log('Configuration loaded'); + + // Get commit message + const commitMessage = getLatestCommitMessage(); + console.log('Latest commit message:'); + console.log(commitMessage); + console.log(''); + + // Parse commit message + const parsed = parseCommitMessage(commitMessage); + + // Validate commit + const validation = validateCommit(parsed, config); + + if (validation.valid) { + console.log('โœ“ Commit message is valid'); + process.exit(0); + } else { + console.log('โœ— Commit message validation failed:'); + for (const error of validation.errors) { + console.log(` - ${error}`); + } + console.log(''); + console.log('Please fix the commit message and try again.'); + + // Show helpful information + if (config.conventionalCommits) { + console.log(''); + console.log('Conventional commit format: type(scope): subject'); + console.log('Example: feat(auth): add user login functionality'); + console.log(''); + console.log('Available types:'); + for (const type of config.commitTypes) { + console.log(` ${type.type}: ${type.description}`); + } + } + + process.exit(1); + } + } catch (error) { + console.error('Error:', error instanceof Error ? error.message : 'Unknown error'); + process.exit(1); + } +} + +// Run the script +if (import.meta.url === `file://${process.argv[1]}`) { + main(); +} + +export { validateCommit, parseCommitMessage, loadConfig }; \ No newline at end of file diff --git a/src/types/config.ts b/src/types/config.ts index c3c8cbe..01dc325 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -7,11 +7,20 @@ export const CommitTypeSchema = z.object({ aliases: z.array(z.string()).optional() }); +export const AIConfigSchema = z.object({ + provider: z.enum(['openai', 'anthropic', 'mock']).default('mock'), + apiKey: z.string().optional(), + model: z.string().optional(), + temperature: z.number().min(0).max(2).default(0.7), + maxTokens: z.number().positive().default(150) +}); + export const ConfigSchema = z.object({ commitTypes: z.array(CommitTypeSchema), emojiEnabled: z.boolean().default(true), conventionalCommits: z.boolean().default(true), aiSummary: z.boolean().default(false), + ai: AIConfigSchema.optional(), maxSubjectLength: z.number().default(50), maxBodyLength: z.number().default(72), hooks: z.object({ @@ -21,4 +30,5 @@ export const ConfigSchema = z.object({ }); export type CommitType = z.infer; +export type AIConfig = z.infer; export type Config = z.infer; \ No newline at end of file diff --git a/src/ui/banner.ts b/src/ui/banner.ts new file mode 100644 index 0000000..9b07ce1 --- /dev/null +++ b/src/ui/banner.ts @@ -0,0 +1,136 @@ +import chalk from 'chalk'; + +export const BANNER = ` + โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— +โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ–ˆโ–ˆโ•”โ•โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘โ•šโ•โ•โ–ˆโ–ˆโ•”โ•โ•โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ• +โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ–ˆโ–ˆโ–ˆโ–ˆโ•”โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ–ˆโ–ˆโ–ˆโ–ˆโ•”โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ•— โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— +โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ• โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•”โ•โ•โ• +โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•‘ โ•šโ•โ• โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ•šโ•โ• โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ•šโ–ˆโ–ˆโ–ˆโ•”โ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ• โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— + โ•šโ•โ•โ•โ•โ•โ• โ•šโ•โ•โ•โ•โ•โ• โ•šโ•โ• โ•šโ•โ•โ•šโ•โ• โ•šโ•โ•โ•šโ•โ• โ•šโ•โ• โ•šโ•โ•โ•โ•šโ•โ•โ• โ•šโ•โ•โ•โ•โ•โ•โ•โ•šโ•โ• โ•šโ•โ• โ•šโ•โ•โ•โ• โ•šโ•โ•โ•โ•โ•โ•โ• +`; + +export const COMPACT_BANNER = ` + โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— +โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ–ˆโ–ˆโ•”โ•โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘โ•šโ•โ•โ–ˆโ–ˆโ•”โ•โ•โ•โ–ˆโ–ˆโ•‘ โ–ˆโ•— โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ• +โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ–ˆโ–ˆโ–ˆโ–ˆโ•”โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ–ˆโ–ˆโ–ˆโ–ˆโ•”โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— +โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•‘ โ•šโ•โ• โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ•šโ•โ• โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ•šโ–ˆโ–ˆโ–ˆโ•”โ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ• โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— + โ•šโ•โ•โ•โ•โ•โ• โ•šโ•โ•โ•โ•โ•โ• โ•šโ•โ• โ•šโ•โ•โ•šโ•โ• โ•šโ•โ•โ•šโ•โ• โ•šโ•โ• โ•šโ•โ•โ•โ•šโ•โ•โ• โ•šโ•โ•โ•โ•โ•โ•โ•โ•šโ•โ• โ•šโ•โ• โ•šโ•โ•โ•โ• โ•šโ•โ•โ•โ•โ•โ•โ• +`; + +export const MINI_BANNER = ` + โ–„โ–ˆโ–ˆโ–ˆโ–ˆโ–„ โ–’โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ โ–ˆโ–ˆโ–ˆโ–„ โ–„โ–ˆโ–ˆโ–ˆโ–“ โ–ˆโ–ˆโ–ˆโ–„ โ–„โ–ˆโ–ˆโ–ˆโ–“ โ–ˆโ–ˆโ–“โ–„โ–„โ–„โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–“ โ–ˆ โ–ˆโ–‘โ–“โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ โ–„โ–„โ–„ โ–ˆโ–ˆโ–’ โ–ˆโ–“โ–“โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ +โ–’โ–ˆโ–ˆโ–€ โ–€โ–ˆ โ–’โ–ˆโ–ˆโ–’ โ–ˆโ–ˆโ–’โ–“โ–ˆโ–ˆโ–’โ–€โ–ˆโ–€ โ–ˆโ–ˆโ–’โ–“โ–ˆโ–ˆโ–’โ–€โ–ˆโ–€ โ–ˆโ–ˆโ–’โ–“โ–ˆโ–ˆโ–’โ–“ โ–ˆโ–ˆโ–’ โ–“โ–’โ–“โ–ˆโ–‘ โ–ˆ โ–‘โ–ˆโ–‘โ–“โ–ˆ โ–€โ–’โ–ˆโ–ˆโ–ˆโ–ˆโ–„ โ–“โ–ˆโ–ˆโ–‘ โ–ˆโ–’โ–“โ–ˆ โ–€ +โ–’โ–“โ–ˆ โ–„ โ–’โ–ˆโ–ˆโ–‘ โ–ˆโ–ˆโ–’โ–“โ–ˆโ–ˆ โ–“โ–ˆโ–ˆโ–‘โ–“โ–ˆโ–ˆ โ–“โ–ˆโ–ˆโ–‘โ–’โ–ˆโ–ˆโ–’โ–’ โ–“โ–ˆโ–ˆโ–‘ โ–’โ–‘โ–’โ–ˆโ–‘ โ–ˆ โ–‘โ–ˆ โ–’โ–ˆโ–ˆโ–ˆ โ–’โ–ˆโ–ˆ โ–€โ–ˆโ–„โ–“โ–ˆโ–ˆ โ–ˆโ–’โ–‘โ–’โ–ˆโ–ˆโ–ˆ +โ–’โ–“โ–“โ–„ โ–„โ–ˆโ–ˆโ–’โ–’โ–ˆโ–ˆ โ–ˆโ–ˆโ–‘โ–’โ–ˆโ–ˆ โ–’โ–ˆโ–ˆ โ–’โ–ˆโ–ˆ โ–’โ–ˆโ–ˆ โ–‘โ–ˆโ–ˆโ–‘โ–‘ โ–“โ–ˆโ–ˆโ–“ โ–‘ โ–‘โ–ˆโ–‘ โ–ˆ โ–‘โ–ˆ โ–’โ–“โ–ˆ โ–„โ–‘โ–ˆโ–ˆโ–„โ–„โ–„โ–„โ–ˆโ–ˆโ–’โ–ˆโ–ˆ โ–ˆโ–‘โ–‘โ–’โ–“โ–ˆ โ–„ +โ–’ โ–“โ–ˆโ–ˆโ–ˆโ–€ โ–‘โ–‘ โ–ˆโ–ˆโ–ˆโ–ˆโ–“โ–’โ–‘โ–’โ–ˆโ–ˆโ–’ โ–‘โ–ˆโ–ˆโ–’โ–’โ–ˆโ–ˆโ–’ โ–‘โ–ˆโ–ˆโ–’โ–‘โ–ˆโ–ˆโ–‘ โ–’โ–ˆโ–ˆโ–’ โ–‘ โ–‘โ–‘โ–ˆโ–ˆโ–’โ–ˆโ–ˆโ–“ โ–‘โ–’โ–ˆโ–ˆโ–ˆโ–ˆโ–’โ–“โ–ˆ โ–“โ–ˆโ–ˆโ–’โ–’โ–€โ–ˆโ–‘ โ–‘โ–’โ–ˆโ–ˆโ–ˆโ–ˆโ–’ +โ–‘ โ–‘โ–’ โ–’ โ–‘โ–‘ โ–’โ–‘โ–’โ–‘โ–’โ–‘ โ–‘ โ–’โ–‘ โ–‘ โ–‘โ–‘ โ–’โ–‘ โ–‘ โ–‘โ–‘โ–“ โ–’ โ–‘โ–‘ โ–‘ โ–“โ–‘โ–’ โ–’ โ–‘โ–‘ โ–’โ–‘ โ–‘โ–’โ–’ โ–“โ–’โ–ˆโ–‘โ–‘ โ–โ–‘ โ–‘โ–‘ โ–’โ–‘ โ–‘ + โ–‘ โ–’ โ–‘ โ–’ โ–’โ–‘ โ–‘ โ–‘ โ–‘โ–‘ โ–‘ โ–‘ โ–’ โ–‘ โ–‘ โ–’ โ–‘ โ–‘ โ–‘ โ–‘ โ–‘ โ–’ โ–’โ–’ โ–‘โ–‘ โ–‘โ–‘ โ–‘ โ–‘ โ–‘ +โ–‘ โ–‘ โ–‘ โ–‘ โ–’ โ–‘ โ–‘ โ–‘ โ–‘ โ–’ โ–‘ โ–‘ โ–‘ โ–‘ โ–‘ โ–‘ โ–’ โ–‘โ–‘ โ–‘ +โ–‘ โ–‘ โ–‘ โ–‘ โ–‘ โ–‘ โ–‘ โ–‘ โ–‘ โ–‘ โ–‘ โ–‘ โ–‘ โ–‘ โ–‘ +โ–‘ โ–‘ +`; + +export const LOADING_FRAMES = [ + 'โ ‹', 'โ ™', 'โ น', 'โ ธ', 'โ ผ', 'โ ด', 'โ ฆ', 'โ ง', 'โ ‡', 'โ ' +]; + +export const TAGLINES = [ + "๐Ÿงถ Weaving beautiful commits, one thread at a time", + "๐ŸŽจ Crafting perfect commits with style and intelligence", + "โšก Smart, structured, and stunning git commits", + "๐Ÿš€ Elevating your commit game to the next level", + "๐Ÿ’Ž Where conventional meets exceptional", + "๐ŸŽฏ Precision-crafted commits for modern developers" +]; + +export const BRAND_COLORS = { + primary: '#8b008b', // Dark magenta - main brand color + accent: '#e94057', // Red-pink - prompts and highlights + success: '#00ff87', // Bright green for success + warning: '#ffb347', // Orange for warnings + error: '#ff6b6b', // Red for errors + muted: '#6c757d' // Gray for muted text +}; + +export function getRandomTagline(): string { + return TAGLINES[Math.floor(Math.random() * TAGLINES.length)]; +} + +export function printBanner(compact: boolean = false): void { + console.clear(); + + if (compact) { + console.log(chalk.hex(BRAND_COLORS.primary).bold(COMPACT_BANNER)); + } else { + console.log(chalk.hex(BRAND_COLORS.primary).bold(BANNER)); + } + + console.log(chalk.hex(BRAND_COLORS.muted).italic(getRandomTagline())); + console.log(''); + + // Add branding footer + console.log(chalk.hex(BRAND_COLORS.muted)(' ') + + chalk.hex(BRAND_COLORS.accent).bold('Powered by GLINR STUDIOS') + + chalk.hex(BRAND_COLORS.muted)(' โ€ข ') + + chalk.hex(BRAND_COLORS.primary)('Published by @typeweaver')); + console.log(''); +} + +export function printMiniBanner(): void { + console.log(chalk.hex(BRAND_COLORS.primary).bold(MINI_BANNER)); + console.log(chalk.hex(BRAND_COLORS.muted).italic(getRandomTagline())); + console.log(chalk.hex(BRAND_COLORS.muted)('Powered by ') + + chalk.hex(BRAND_COLORS.accent).bold('GLINR STUDIOS') + + chalk.hex(BRAND_COLORS.muted)(' โ€ข ') + + chalk.hex(BRAND_COLORS.primary)('@typeweaver')); + console.log(''); +} + +export async function showLoadingAnimation(message: string, duration: number = 2000): Promise { + return new Promise((resolve) => { + let frameIndex = 0; + const startTime = Date.now(); + + const interval = setInterval(() => { + const elapsed = Date.now() - startTime; + const frame = LOADING_FRAMES[frameIndex % LOADING_FRAMES.length]; + + process.stdout.write(`\r${chalk.hex(BRAND_COLORS.accent)(frame)} ${chalk.hex(BRAND_COLORS.muted)(message)}...`); + + frameIndex++; + + if (elapsed >= duration) { + clearInterval(interval); + process.stdout.write(`\r${chalk.hex(BRAND_COLORS.success)('โœ“')} ${chalk.hex(BRAND_COLORS.muted)(message)} complete!\n`); + resolve(); + } + }, 80); + }); +} + +export async function typeWriter(text: string, delay: number = 50): Promise { + for (let i = 0; i < text.length; i++) { + process.stdout.write(text[i]); + await new Promise(resolve => setTimeout(resolve, delay)); + } + console.log(''); +} + +export function printWelcome(): void { + console.log(chalk.hex(BRAND_COLORS.muted)('โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”')); + console.log(chalk.hex(BRAND_COLORS.muted)('โ”‚') + chalk.hex(BRAND_COLORS.primary).bold(' Welcome to Commitweave! ๐Ÿงถ ') + chalk.hex(BRAND_COLORS.muted)('โ”‚')); + console.log(chalk.hex(BRAND_COLORS.muted)('โ”‚') + chalk.white(' A modern CLI for smart, structured git commits ') + chalk.hex(BRAND_COLORS.muted)('โ”‚')); + console.log(chalk.hex(BRAND_COLORS.muted)('โ”‚') + chalk.hex(BRAND_COLORS.accent)(' Powered by GLINR STUDIOS ') + chalk.hex(BRAND_COLORS.muted)('โ”‚')); + console.log(chalk.hex(BRAND_COLORS.muted)('โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜')); + console.log(''); +} + +export function printFeatureHighlight(): void { + console.log(chalk.hex(BRAND_COLORS.accent)('โœจ Features:')); + console.log(' ' + chalk.hex(BRAND_COLORS.primary)('๐ŸŽฏ') + ' ' + chalk.white('Conventional Commits')); + console.log(' ' + chalk.hex(BRAND_COLORS.primary)('๐Ÿค–') + ' ' + chalk.white('AI-Powered Suggestions')); + console.log(' ' + chalk.hex(BRAND_COLORS.primary)('๐ŸŽจ') + ' ' + chalk.white('Beautiful Emoji Support')); + console.log(' ' + chalk.hex(BRAND_COLORS.primary)('โšก') + ' ' + chalk.white('Lightning Fast & Interactive')); + console.log(' ' + chalk.hex(BRAND_COLORS.primary)('๐Ÿ”ง') + ' ' + chalk.white('Highly Configurable')); + console.log(''); +} \ No newline at end of file diff --git a/src/utils/ai.ts b/src/utils/ai.ts index 4b39a7b..7e6815d 100644 --- a/src/utils/ai.ts +++ b/src/utils/ai.ts @@ -1,4 +1,5 @@ import type { AISummaryOptions, CommitSuggestion, AIProvider } from '../types/ai.js'; +import type { AIConfig } from '../types/config.js'; export type { AISummaryOptions, CommitSuggestion, AIProvider }; @@ -37,12 +38,83 @@ export class OpenAIProvider implements AIProvider { return !!this.apiKey; } - async generateCommitMessage(_diff: string, _options?: AISummaryOptions): Promise { + async generateCommitMessage(diff: string, options?: AISummaryOptions): Promise { if (!this.isConfigured()) { throw new Error('OpenAI API key not configured'); } - throw new Error('OpenAI integration not yet implemented - this is a placeholder'); + const model = options?.model || this._model; + const temperature = options?.temperature ?? 0.7; + const maxTokens = options?.maxTokens ?? 150; + + const prompt = `Analyze this git diff and generate a conventional commit message. + +Rules: +- Subject line should be โ‰ค 50 characters +- Use conventional commit format: type(scope): subject +- Choose appropriate type: feat, fix, docs, style, refactor, test, chore, etc. +- Be concise and descriptive +- Optional body for more details + +Git diff: +${diff} + +Respond with JSON in this format: +{ + "type": "feat", + "scope": "auth", + "subject": "add user authentication", + "body": "Implement JWT-based authentication system with login/logout functionality", + "confidence": 0.9, + "reasoning": "Added new authentication functionality" +}`; + + try { + const response = await fetch('https://api.openai.com/v1/chat/completions', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${this.apiKey}` + }, + body: JSON.stringify({ + model, + messages: [ + { role: 'system', content: 'You are a helpful assistant that generates conventional git commit messages.' }, + { role: 'user', content: prompt } + ], + temperature, + max_tokens: maxTokens, + response_format: { type: 'json_object' } + }) + }); + + if (!response.ok) { + throw new Error(`OpenAI API error: ${response.status} ${response.statusText}`); + } + + const data = await response.json() as any; + const content = data.choices?.[0]?.message?.content; + + if (!content) { + throw new Error('No response from OpenAI API'); + } + + const parsed = JSON.parse(content); + + return { + type: parsed.type || 'feat', + scope: parsed.scope, + subject: parsed.subject || 'update code', + body: parsed.body, + confidence: parsed.confidence || 0.7, + reasoning: parsed.reasoning || 'Generated by OpenAI' + }; + } catch (error) { + if (error instanceof Error) { + throw new Error(`OpenAI API error: ${error.message}`); + } + throw error; + } } } @@ -63,12 +135,91 @@ export class AnthropicProvider implements AIProvider { return !!this.apiKey; } - async generateCommitMessage(_diff: string, _options?: AISummaryOptions): Promise { + async generateCommitMessage(diff: string, options?: AISummaryOptions): Promise { if (!this.isConfigured()) { throw new Error('Anthropic API key not configured'); } - throw new Error('Anthropic integration not yet implemented - this is a placeholder'); + const model = options?.model || this._model; + const temperature = options?.temperature ?? 0.7; + const maxTokens = options?.maxTokens ?? 150; + + const prompt = `Analyze this git diff and generate a conventional commit message. + +Rules: +- Subject line should be โ‰ค 50 characters +- Use conventional commit format: type(scope): subject +- Choose appropriate type: feat, fix, docs, style, refactor, test, chore, etc. +- Be concise and descriptive +- Optional body for more details + +Git diff: +${diff} + +Respond with JSON in this format: +{ + "type": "feat", + "scope": "auth", + "subject": "add user authentication", + "body": "Implement JWT-based authentication system with login/logout functionality", + "confidence": 0.9, + "reasoning": "Added new authentication functionality" +}`; + + try { + const response = await fetch('https://api.anthropic.com/v1/messages', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'x-api-key': this.apiKey!, + 'anthropic-version': '2023-06-01' + }, + body: JSON.stringify({ + model, + max_tokens: maxTokens, + temperature, + messages: [ + { + role: 'user', + content: prompt + } + ] + }) + }); + + if (!response.ok) { + throw new Error(`Anthropic API error: ${response.status} ${response.statusText}`); + } + + const data = await response.json() as any; + const content = data.content?.[0]?.text; + + if (!content) { + throw new Error('No response from Anthropic API'); + } + + // Try to extract JSON from the response + const jsonMatch = content.match(/\{[\s\S]*\}/); + if (!jsonMatch) { + throw new Error('No valid JSON found in Anthropic response'); + } + + const parsed = JSON.parse(jsonMatch[0]); + + return { + type: parsed.type || 'feat', + scope: parsed.scope, + subject: parsed.subject || 'update code', + body: parsed.body, + confidence: parsed.confidence || 0.7, + reasoning: parsed.reasoning || 'Generated by Claude' + }; + } catch (error) { + if (error instanceof Error) { + throw new Error(`Anthropic API error: ${error.message}`); + } + throw error; + } } } @@ -102,4 +253,63 @@ export async function generateCommitSuggestion( export function isAIConfigured(options?: AISummaryOptions): boolean { const provider = createAIProvider(options); return provider.isConfigured(); +} + +/** + * Generate AI-powered commit summary from git diff + * @param diff - Git diff string + * @param config - AI configuration + * @returns Promise resolving to subject and body + */ +export async function generateAISummary( + diff: string, + config: AIConfig +): Promise<{ subject: string; body: string }> { + // Convert AIConfig to AISummaryOptions + const options: AISummaryOptions = { + provider: config.provider || 'mock' + }; + + // Only set optional properties if they exist + if (config.apiKey) options.apiKey = config.apiKey; + if (config.model) options.model = config.model; + if (config.temperature !== undefined) options.temperature = config.temperature; + if (config.maxTokens !== undefined) options.maxTokens = config.maxTokens; + + // If no API key provided for a real provider, fall back to mock + if (!config.apiKey && config.provider && config.provider !== 'mock') { + options.provider = 'mock'; + } + + try { + const suggestion = await generateCommitSuggestion(diff, options); + + // Format the subject line with conventional commit format + let subject = suggestion.subject; + if (suggestion.type) { + const scope = suggestion.scope ? `(${suggestion.scope})` : ''; + subject = `${suggestion.type}${scope}: ${suggestion.subject}`; + } + + // Ensure subject line is within limit (โ‰ค 50 chars ideally) + if (subject.length > 50) { + subject = subject.substring(0, 47) + '...'; + } + + return { + subject, + body: suggestion.body || '' + }; + } catch (error) { + // Fallback to mock if AI provider fails + console.warn('AI provider failed, falling back to mock:', error); + + const mockProvider = new MockAIProvider(); + const suggestion = await mockProvider.generateCommitMessage(diff); + + return { + subject: suggestion.subject, + body: suggestion.body || '' + }; + } } \ No newline at end of file From 4f68eafd68c0760598d8348eaf4d61da363ae245 Mon Sep 17 00:00:00 2001 From: GDS K S Date: Sun, 20 Jul 2025 18:08:17 -0500 Subject: [PATCH 5/7] Merge branch 'feature/test' into main --- test-merge.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 test-merge.txt diff --git a/test-merge.txt b/test-merge.txt new file mode 100644 index 0000000..83112f6 --- /dev/null +++ b/test-merge.txt @@ -0,0 +1 @@ +test merge commit From c67a8ad4abac36ddf5b28adab6425091f9833d75 Mon Sep 17 00:00:00 2001 From: GDS K S Date: Sun, 20 Jul 2025 18:08:31 -0500 Subject: [PATCH 6/7] fix: add merge commit support to validation script - Add isSpecialCommit function to detect merge/revert/fixup commits - Skip validation for special Git commit types - Update both ESM and CommonJS versions - Remove unused printWelcome function from banner.ts --- bin/index.cjs.ts | 32 ++++++++++++++++++++++++++++++++ scripts/check-commit.ts | 37 ++++++++++++++++++++++++++++++++++++- src/ui/banner.ts | 8 -------- test-merge.txt | 1 - 4 files changed, 68 insertions(+), 10 deletions(-) delete mode 100644 test-merge.txt diff --git a/bin/index.cjs.ts b/bin/index.cjs.ts index f881ab6..657e72c 100644 --- a/bin/index.cjs.ts +++ b/bin/index.cjs.ts @@ -441,6 +441,12 @@ async function handleCheckCommand() { console.log(commitMessage); console.log(''); + // Check if this is a special commit that should be excluded from validation + if (isSpecialCommit(commitMessage)) { + console.log('โœ“ Special commit detected (merge/revert/fixup) - skipping validation'); + process.exit(0); + } + // Use inline validation logic for CommonJS compatibility const validation = validateCommitMessage(commitMessage, config); @@ -475,6 +481,32 @@ async function handleCheckCommand() { } } +function isSpecialCommit(message: string): boolean { + const header = message.split('\n')[0] || ''; + + // Check for merge commits + if (header.startsWith('Merge ')) { + return true; + } + + // Check for revert commits + if (header.startsWith('Revert ')) { + return true; + } + + // Check for fixup commits + if (header.startsWith('fixup! ') || header.startsWith('squash! ')) { + return true; + } + + // Check for initial commits + if (header.toLowerCase().includes('initial commit')) { + return true; + } + + return false; +} + function parseCommitMessage(message: string) { const lines = message.split('\n'); const header = lines[0] || ''; diff --git a/scripts/check-commit.ts b/scripts/check-commit.ts index 578824e..7491b2d 100644 --- a/scripts/check-commit.ts +++ b/scripts/check-commit.ts @@ -48,6 +48,35 @@ function getLatestCommitMessage(): string { } } +/** + * Check if commit message is a special Git commit type that should be excluded from validation + */ +function isSpecialCommit(message: string): boolean { + const header = message.split('\n')[0] || ''; + + // Check for merge commits + if (header.startsWith('Merge ')) { + return true; + } + + // Check for revert commits + if (header.startsWith('Revert ')) { + return true; + } + + // Check for fixup commits + if (header.startsWith('fixup! ') || header.startsWith('squash! ')) { + return true; + } + + // Check for initial commits + if (header.toLowerCase().includes('initial commit')) { + return true; + } + + return false; +} + /** * Parse a conventional commit message */ @@ -155,6 +184,12 @@ async function main() { console.log(commitMessage); console.log(''); + // Check if this is a special commit that should be excluded from validation + if (isSpecialCommit(commitMessage)) { + console.log('โœ“ Special commit detected (merge/revert/fixup) - skipping validation'); + process.exit(0); + } + // Parse commit message const parsed = parseCommitMessage(commitMessage); @@ -197,4 +232,4 @@ if (import.meta.url === `file://${process.argv[1]}`) { main(); } -export { validateCommit, parseCommitMessage, loadConfig }; \ No newline at end of file +export { validateCommit, parseCommitMessage, loadConfig, isSpecialCommit }; \ No newline at end of file diff --git a/src/ui/banner.ts b/src/ui/banner.ts index 9b07ce1..4885c17 100644 --- a/src/ui/banner.ts +++ b/src/ui/banner.ts @@ -116,14 +116,6 @@ export async function typeWriter(text: string, delay: number = 50): Promise Date: Sun, 20 Jul 2025 18:12:03 -0500 Subject: [PATCH 7/7] fix: update CI to fix ts issues --- bin/index.ts | 2 +- scripts/check-commit.ts | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/bin/index.ts b/bin/index.ts index cb66489..39c3540 100644 --- a/bin/index.ts +++ b/bin/index.ts @@ -10,7 +10,7 @@ import { stageAllAndCommit } from '../src/utils/git.js'; import { generateAISummary } from '../src/utils/ai.js'; import { ConfigSchema, type Config } from '../src/types/config.js'; import { defaultConfig } from '../src/config/defaultConfig.js'; -import { printBanner, showLoadingAnimation, printWelcome, printFeatureHighlight, BRAND_COLORS } from '../src/ui/banner.js'; +import { printBanner, showLoadingAnimation, printFeatureHighlight, BRAND_COLORS } from '../src/ui/banner.js'; async function loadConfig(): Promise { try { diff --git a/scripts/check-commit.ts b/scripts/check-commit.ts index 7491b2d..ec0e213 100644 --- a/scripts/check-commit.ts +++ b/scripts/check-commit.ts @@ -12,12 +12,12 @@ interface ValidationResult { } interface ParsedCommit { - type?: string; - scope?: string; - breaking?: boolean; - subject?: string; - body?: string; - footer?: string; + type?: string | undefined; + scope?: string | undefined; + breaking?: boolean | undefined; + subject?: string | undefined; + body?: string | undefined; + footer?: string | undefined; } /** @@ -91,21 +91,23 @@ function parseCommitMessage(message: string): ParsedCommit { if (match) { const [, type, scopeWithParens, breaking, subject] = match; const scope = scopeWithParens ? scopeWithParens.slice(1, -1) : undefined; + const bodyText = lines.slice(2).join('\n').trim(); return { type, scope, breaking: !!breaking, subject: subject?.trim(), - body: lines.slice(2).join('\n').trim() || undefined, + body: bodyText || undefined, footer: undefined // Could be parsed more thoroughly if needed }; } // If not conventional format, treat entire header as subject + const bodyText = lines.slice(2).join('\n').trim(); return { subject: header.trim(), - body: lines.slice(2).join('\n').trim() || undefined + body: bodyText || undefined }; }