@deriv-com/shiftai-cli
Version:
A comprehensive AI code detection and analysis CLI tool for tracking AI-generated code in projects
244 lines (203 loc) ⢠7.67 kB
JavaScript
const { spawn } = require('child_process');
const chalk = require('chalk');
const fs = require('fs-extra');
const AIStorage = require('../core/ai-storage');
const PRManager = require('../core/pr-manager');
const { getPRTargetInfo, getCurrentBranch, getCurrentCommitHash, getGitAuthor } = require('../utils/git-utils');
/**
* Enhanced git push command with automatic PR creation
*/
async function pushCommand(args, options = {}) {
try {
console.log(chalk.blue('š ShiftAI Enhanced Push'));
// Get current branch before push
const branchName = getCurrentBranch();
console.log(chalk.gray(`š” Pushing branch: ${branchName}`));
// Check if automatic PR creation is enabled
const configPath = AIStorage.getConfigPath();
const config = fs.pathExistsSync(configPath) ? require(configPath) : {};
const createPR = config.upload?.createPR ?? true;
if (createPR) {
console.log(chalk.blue('āļø Automatic PR creation enabled'));
} else {
console.log(chalk.gray('āļø Automatic PR creation disabled'));
}
// Execute git push and monitor its completion
const pushSuccess = await executeGitPush(args);
if (!pushSuccess) {
console.log(chalk.red('ā Git push failed - skipping PR creation'));
process.exit(1);
}
console.log(chalk.green('ā
Push completed successfully'));
// If PR creation is enabled, trigger it now
if (createPR) {
await handlePostPushPRCreation(branchName);
} else {
console.log(chalk.yellow('š” Run "shai pr" to analyze and create PR manually'));
}
} catch (error) {
console.error(chalk.red(`ā Push command failed: ${error.message}`));
process.exit(1);
}
}
/**
* Execute git push and monitor completion
*/
function executeGitPush(args) {
return new Promise((resolve, reject) => {
console.log(chalk.blue('š¤ Running git push...'));
// Build git push command
const gitArgs = ['push', ...args];
console.log(chalk.gray(` Command: git ${gitArgs.join(' ')}`));
// Spawn git push process
const gitProcess = spawn('git', gitArgs, {
stdio: ['inherit', 'pipe', 'pipe'],
cwd: process.cwd()
});
let stdout = '';
let stderr = '';
// Capture output
gitProcess.stdout.on('data', (data) => {
const output = data.toString();
stdout += output;
// Show real-time output
process.stdout.write(output);
});
gitProcess.stderr.on('data', (data) => {
const output = data.toString();
stderr += output;
// Show real-time output
process.stderr.write(output);
});
gitProcess.on('close', (code) => {
if (code === 0) {
// Check for successful push indicators
const output = stdout + stderr;
const successIndicators = [
'Total ',
'To https://github.com/',
'To git@github.com:',
'-> ',
'Everything up-to-date'
];
const hasSuccessIndicator = successIndicators.some(indicator =>
output.includes(indicator)
);
if (hasSuccessIndicator || code === 0) {
resolve(true);
} else {
resolve(false);
}
} else {
resolve(false);
}
});
gitProcess.on('error', (error) => {
reject(error);
});
});
}
/**
* Handle PR creation after successful push
*/
async function handlePostPushPRCreation(branchName) {
try {
console.log(chalk.blue('\nš Starting automatic PR creation...'));
// Load .env file if it exists
try {
const path = require('path');
const envPath = path.join(process.cwd(), '.env');
if (fs.pathExistsSync(envPath)) {
require('dotenv').config({ path: envPath });
}
} catch (error) {
// Continue without .env
}
// Get GitHub token
const githubToken = process.env.GITHUB_TOKEN;
if (!githubToken) {
console.log(chalk.yellow('ā ļø GITHUB_TOKEN not found - skipping PR creation'));
console.log(chalk.gray(' Set GITHUB_TOKEN environment variable or add to .env file'));
return;
}
// Get PR target info
const prTargetInfo = getPRTargetInfo();
// Wait a moment for GitHub to sync the push
console.log(chalk.blue('ā³ Waiting for GitHub to sync...'));
await new Promise(resolve => setTimeout(resolve, 3000));
// Step 1: Initialize and analyze
console.log(chalk.blue('š Analyzing AI content in your changes...'));
const aiStorage = new AIStorage({
organization: prTargetInfo.organization,
repository: prTargetInfo.repository,
projectRoot: process.cwd()
});
await aiStorage.initialize();
const currentCommit = getCurrentCommitHash();
const metadata = {
branch: branchName,
commit: currentCommit,
author: getGitAuthor(),
timestamp: new Date().toISOString()
};
const aiData = await aiStorage.analyzeBranchChanges(metadata);
// Step 2: Setup PR management
const prManager = new PRManager({
organization: prTargetInfo.organization,
repository: prTargetInfo.repository,
token: githubToken
});
// Step 3: Check for existing PR
console.log(chalk.blue('š Checking for existing PR...'));
let pr = await prManager.findPR(branchName, prTargetInfo);
if (!pr) {
// Step 4: Create new PR
console.log(chalk.blue('š Creating pull request...'));
if (prTargetInfo.isCrossRepo) {
console.log(chalk.gray(` Cross-repo: ${prTargetInfo.forkOwner} ā ${prTargetInfo.organization}`));
}
pr = await prManager.createPR(branchName, 'master', prTargetInfo);
if (!pr) {
console.error(chalk.red('ā Failed to create PR'));
return;
}
console.log(chalk.green(`ā
Created PR #${pr.number}: "${pr.title}"`));
} else {
console.log(chalk.yellow(`š Found existing PR #${pr.number}: "${pr.title}"`));
}
// Step 5: Get PR URL
const prUrl = prManager.getPRUrl(pr.number);
console.log(chalk.cyan(`š ${prUrl}`));
// Step 6: Post analysis comment
console.log(chalk.blue('š¬ Adding AI analysis comment...'));
const commentResult = await prManager.postAnalysisComment(pr.number, aiData);
if (commentResult) {
if (commentResult.updated) {
console.log(chalk.green('ā
Updated AI analysis comment'));
} else {
console.log(chalk.green('ā
Posted AI analysis comment'));
}
} else {
console.log(chalk.yellow('ā ļø Comment posting failed - check token permissions'));
}
// Step 7: Auto-open PR in browser
console.log(chalk.blue('š Opening PR in browser...'));
try {
const { execSync } = require('child_process');
const platform = process.platform;
let openCommand;
if (platform === 'darwin') openCommand = 'open';
else if (platform === 'win32') openCommand = 'start';
else openCommand = 'xdg-open';
execSync(`${openCommand} "${prUrl}"`, { stdio: 'ignore' });
console.log(chalk.green('ā
Opened in browser'));
} catch (error) {
console.log(chalk.gray('š” Copy the URL above to open manually'));
}
console.log(chalk.green('š PR creation completed successfully!'));
} catch (error) {
console.error(chalk.red(`ā PR creation failed: ${error.message}`));
console.log(chalk.yellow('š” Run "shai pr" manually to create the PR'));
}
}
module.exports = pushCommand;