git-devflow 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,167 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.prCommand = void 0;
7
+ const commander_1 = require("commander");
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const prompts_1 = require("@inquirer/prompts");
10
+ const git_js_1 = require("../services/git.js");
11
+ const github_js_1 = require("../services/github.js");
12
+ const copilot_js_1 = require("../services/copilot.js");
13
+ const spinner_js_1 = require("../utils/spinner.js");
14
+ exports.prCommand = new commander_1.Command('pr')
15
+ .description('Manage pull requests');
16
+ exports.prCommand
17
+ .command('create')
18
+ .description('Generate PR description and create pull request using Copilot CLI')
19
+ .option('-b, --base <branch>', 'Base branch (default: main)')
20
+ .option('-i, --issue <number>', 'Related issue number')
21
+ .action(async (options) => {
22
+ const git = new git_js_1.GitService();
23
+ const github = new github_js_1.GitHubService();
24
+ const copilot = new copilot_js_1.CopilotService();
25
+ try {
26
+ // Check if we're in a git repo
27
+ if (!(await git.isGitRepo())) {
28
+ console.log(chalk_1.default.red('āŒ Not a git repository!'));
29
+ process.exit(1);
30
+ }
31
+ // Get current branch
32
+ const currentBranch = await git.getCurrentBranch();
33
+ const baseBranch = options.base || await git.getBaseBranch();
34
+ if (currentBranch === baseBranch) {
35
+ console.log(chalk_1.default.red(`āŒ You're on ${baseBranch}. Create a feature branch first!`));
36
+ console.log(chalk_1.default.dim('Tip: Use `git checkout -b feature/my-feature`'));
37
+ process.exit(1);
38
+ }
39
+ const spinner = new spinner_js_1.Spinner('Checking if branch is on GitHub...');
40
+ spinner.start();
41
+ const isPushed = await git.isBranchPushed(currentBranch);
42
+ if (!isPushed) {
43
+ spinner.update('Pushing branch to GitHub...');
44
+ try {
45
+ await git.pushBranch(currentBranch);
46
+ spinner.succeed(`Branch pushed to GitHub: ${currentBranch}`);
47
+ }
48
+ catch (error) {
49
+ spinner.fail('Failed to push branch');
50
+ console.log(chalk_1.default.red(`\nāŒ Could not push branch: ${error.message}`));
51
+ console.log(chalk_1.default.yellow(`\nPlease push manually: git push -u origin ${currentBranch}\n`));
52
+ process.exit(1);
53
+ }
54
+ }
55
+ else {
56
+ spinner.succeed('Branch already on GitHub');
57
+ }
58
+ console.log(chalk_1.default.cyan(`\nšŸ” Analyzing branch: ${chalk_1.default.bold(currentBranch)}`));
59
+ console.log(chalk_1.default.dim(` Base branch: ${baseBranch}\n`));
60
+ // Get commits
61
+ const commits = await git.getCommitsSinceBase();
62
+ if (commits.length === 0) {
63
+ spinner.fail('No commits found in this branch');
64
+ process.exit(1);
65
+ }
66
+ spinner.succeed(`Found ${commits.length} commit(s)`);
67
+ // Show commits
68
+ console.log(chalk_1.default.cyan('\nšŸ“ Commits in this branch:\n'));
69
+ commits.forEach((commit, idx) => {
70
+ console.log(chalk_1.default.dim(` ${idx + 1}. ${commit.hash} - ${commit.message}`));
71
+ });
72
+ console.log('');
73
+ // Get issue context if provided
74
+ let issueContext = '';
75
+ if (options.issue) {
76
+ const issueSpinner = new spinner_js_1.Spinner(`Fetching issue #${options.issue}...`);
77
+ issueSpinner.start();
78
+ try {
79
+ const issue = await github.getIssue(parseInt(options.issue));
80
+ issueContext = `\n\nRelated Issue: #${options.issue} - ${issue.title}\n${issue.body || ''}`;
81
+ issueSpinner.succeed(`Fetched issue #${options.issue}`);
82
+ }
83
+ catch (error) {
84
+ issueSpinner.fail(`Could not fetch issue #${options.issue}`);
85
+ }
86
+ }
87
+ // Generate PR description using Copilot CLI
88
+ const prSpinner = new spinner_js_1.Spinner('šŸ¤– Generating PR description with Copilot CLI...');
89
+ prSpinner.start();
90
+ const prDescription = await copilot.generatePRDescription(commits, issueContext);
91
+ prSpinner.succeed('Generated PR description');
92
+ // Extract title and body
93
+ const { title, body } = prDescription;
94
+ // Show preview
95
+ console.log(chalk_1.default.cyan('\nšŸ“„ Generated PR:\n'));
96
+ console.log(chalk_1.default.bold('Title:'));
97
+ console.log(chalk_1.default.white(` ${title}\n`));
98
+ console.log(chalk_1.default.bold('Description:'));
99
+ console.log(chalk_1.default.dim('─'.repeat(60)));
100
+ console.log(body);
101
+ console.log(chalk_1.default.dim('─'.repeat(60)));
102
+ console.log('');
103
+ // Ask if user wants to edit
104
+ const shouldEdit = await (0, prompts_1.confirm)({
105
+ message: 'Edit title or description?',
106
+ default: false
107
+ });
108
+ let finalTitle = title;
109
+ let finalBody = body;
110
+ if (shouldEdit) {
111
+ const editChoice = await (0, prompts_1.select)({
112
+ message: 'What would you like to edit?',
113
+ choices: [
114
+ { name: 'Title', value: 'title' },
115
+ { name: 'Description', value: 'body' },
116
+ { name: 'Both', value: 'both' }
117
+ ]
118
+ });
119
+ if (editChoice === 'title' || editChoice === 'both') {
120
+ finalTitle = await (0, prompts_1.input)({
121
+ message: 'Enter PR title:',
122
+ default: title
123
+ });
124
+ }
125
+ if (editChoice === 'body' || editChoice === 'both') {
126
+ console.log(chalk_1.default.yellow('\nšŸ’” Tip: The description will open in your default editor'));
127
+ finalBody = await (0, prompts_1.input)({
128
+ message: 'Enter PR description (or press Enter to keep current):',
129
+ default: body
130
+ });
131
+ }
132
+ }
133
+ // Confirm creation
134
+ const shouldCreate = await (0, prompts_1.confirm)({
135
+ message: `Create PR: "${finalTitle}"?`,
136
+ default: true
137
+ });
138
+ if (!shouldCreate) {
139
+ console.log(chalk_1.default.yellow('āŒ PR creation cancelled'));
140
+ process.exit(0);
141
+ }
142
+ // Create the PR
143
+ const createSpinner = new spinner_js_1.Spinner('Creating pull request on GitHub...');
144
+ createSpinner.start();
145
+ try {
146
+ const pr = await github.createPullRequest({
147
+ title: finalTitle,
148
+ body: finalBody,
149
+ head: currentBranch,
150
+ base: baseBranch
151
+ });
152
+ createSpinner.succeed(chalk_1.default.green(`✨ Pull request created successfully!`));
153
+ console.log(chalk_1.default.cyan(`\nšŸ”— ${pr.html_url}\n`));
154
+ }
155
+ catch (error) {
156
+ createSpinner.fail('Failed to create pull request');
157
+ const safeMsg = error instanceof Error ? error.message : 'Unknown error';
158
+ console.log(chalk_1.default.red(`\nāŒ ${safeMsg}`));
159
+ process.exit(1);
160
+ }
161
+ }
162
+ catch (error) {
163
+ const safeMsg = error instanceof Error ? error.message : 'Something went wrong';
164
+ console.log(chalk_1.default.red(`\nāŒ ${safeMsg}`));
165
+ process.exit(1);
166
+ }
167
+ });
package/dist/index.js ADDED
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const commander_1 = require("commander");
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const commit_js_1 = require("./commands/commit.js");
10
+ const pr_js_1 = require("./commands/pr.js");
11
+ const config_js_1 = require("./commands/config.js");
12
+ const config_js_2 = require("./services/config.js");
13
+ const branch_js_1 = require("./commands/branch.js");
14
+ const program = new commander_1.Command();
15
+ program
16
+ .name('devflow')
17
+ .description('AI-powered Git workflow automation using GitHub Copilot CLI')
18
+ .version('1.0.0');
19
+ // Check if first run and suggest setup
20
+ const checkFirstRun = () => {
21
+ // Skip if running config command
22
+ if (process.argv[2] === 'config')
23
+ return;
24
+ const config = new config_js_2.ConfigService();
25
+ const currentConfig = config.load();
26
+ // If no config at all, strongly suggest setup
27
+ if (Object.keys(currentConfig).length === 0) {
28
+ console.log(chalk_1.default.red('āŒ No configuration found!\n'));
29
+ console.log(chalk_1.default.yellow('Please run: ') + chalk_1.default.cyan('devflow config setup\n'));
30
+ process.exit(1); // ADD THIS - exit immediately
31
+ }
32
+ };
33
+ // Check before running any command
34
+ checkFirstRun();
35
+ // Register commands
36
+ program.addCommand(commit_js_1.commitCommand);
37
+ program.addCommand(pr_js_1.prCommand);
38
+ program.addCommand(config_js_1.configCommand);
39
+ program.addCommand(branch_js_1.branchCommand);
40
+ // Help message
41
+ if (process.argv.length === 2) {
42
+ console.log(chalk_1.default.cyan('✨ DevFlow - AI-Powered Git Workflow\n'));
43
+ program.help();
44
+ }
45
+ program.parse(process.argv);
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ConfigService = exports.DEFAULT_COPILOT_MODEL = void 0;
7
+ const fs_1 = require("fs");
8
+ const path_1 = require("path");
9
+ const os_1 = require("os");
10
+ const chalk_1 = __importDefault(require("chalk"));
11
+ exports.DEFAULT_COPILOT_MODEL = 'claude-sonnet-4.5';
12
+ class ConfigService {
13
+ constructor() {
14
+ this.configDir = (0, path_1.join)((0, os_1.homedir)(), '.devflow');
15
+ this.configPath = (0, path_1.join)(this.configDir, 'config.json');
16
+ }
17
+ // Ensure config directory exists
18
+ ensureConfigDir() {
19
+ if (!(0, fs_1.existsSync)(this.configDir)) {
20
+ (0, fs_1.mkdirSync)(this.configDir, { recursive: true });
21
+ }
22
+ }
23
+ // Load config
24
+ load() {
25
+ try {
26
+ if ((0, fs_1.existsSync)(this.configPath)) {
27
+ const data = (0, fs_1.readFileSync)(this.configPath, 'utf-8');
28
+ return JSON.parse(data);
29
+ }
30
+ }
31
+ catch (error) {
32
+ console.log(chalk_1.default.yellow('āš ļø Could not read config file'));
33
+ }
34
+ return {};
35
+ }
36
+ // Save config
37
+ save(config) {
38
+ try {
39
+ this.ensureConfigDir();
40
+ (0, fs_1.writeFileSync)(this.configPath, JSON.stringify(config, null, 2));
41
+ // Set restrictive permissions (owner read/write only)
42
+ (0, fs_1.chmodSync)(this.configPath, 0o600);
43
+ console.log(chalk_1.default.green(`āœ“ Config saved to ${this.configPath}`));
44
+ console.log(chalk_1.default.green(`āœ“ Config protected with 600 file permissions`));
45
+ }
46
+ catch (error) {
47
+ console.log(chalk_1.default.red('āŒ Failed to save config'));
48
+ throw error;
49
+ }
50
+ }
51
+ // Get a specific value
52
+ get(key) {
53
+ const config = this.load();
54
+ return config[key];
55
+ }
56
+ // Set a specific value
57
+ set(key, value) {
58
+ const config = this.load();
59
+ config[key] = value;
60
+ this.save(config);
61
+ }
62
+ // Check if token is configured
63
+ hasToken() {
64
+ const token = this.get('githubToken');
65
+ return !!token && token.length > 0;
66
+ }
67
+ // Get token (from config or env)
68
+ getToken() {
69
+ // Priority: 1. Config file, 2. Environment variable, 3. gh CLI
70
+ const configToken = this.get('githubToken');
71
+ if (configToken)
72
+ return configToken;
73
+ const envToken = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;
74
+ if (envToken)
75
+ return envToken;
76
+ // Try gh CLI
77
+ try {
78
+ const { execSync } = require('child_process');
79
+ return execSync('gh auth token', { encoding: 'utf-8' }).trim();
80
+ }
81
+ catch {
82
+ return undefined;
83
+ }
84
+ }
85
+ // Get Copilot model preference
86
+ getCopilotModel() {
87
+ return this.get('copilotModel') || exports.DEFAULT_COPILOT_MODEL;
88
+ }
89
+ // Display current config
90
+ display() {
91
+ const config = this.load();
92
+ console.log(chalk_1.default.cyan('\nšŸ“‹ Current DevFlow Configuration:\n'));
93
+ console.log(chalk_1.default.dim('Config file:'), this.configPath);
94
+ console.log('');
95
+ console.log(chalk_1.default.bold('GitHub Token:'), config.githubToken ?
96
+ chalk_1.default.green('Configured') :
97
+ chalk_1.default.yellow('Not set (using environment or gh CLI)'));
98
+ console.log(chalk_1.default.bold('Copilot Model:'), config.copilotModel ||
99
+ chalk_1.default.dim(`${exports.DEFAULT_COPILOT_MODEL} (default)`));
100
+ console.log(chalk_1.default.bold('Default Base Branch:'), config.defaultBaseBranch ||
101
+ chalk_1.default.dim('main (default)'));
102
+ console.log('');
103
+ }
104
+ }
105
+ exports.ConfigService = ConfigService;