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.
- package/README.md +179 -0
- package/dist/commands/branch.js +113 -0
- package/dist/commands/commit.js +95 -0
- package/dist/commands/config.js +135 -0
- package/dist/commands/pr.js +167 -0
- package/dist/index.js +45 -0
- package/dist/services/config.js +105 -0
- package/dist/services/copilot.js +456 -0
- package/dist/services/git.js +133 -0
- package/dist/services/github.js +160 -0
- package/dist/utils/spinner.js +42 -0
- package/package.json +53 -0
|
@@ -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;
|