Files
gh-jduncan-rva-skill-porter/src/optional-features/pr-generator.js
2025-11-29 18:50:16 +08:00

297 lines
7.8 KiB
JavaScript

/**
* PR Generation Feature
* Creates pull requests to add dual-platform support to repositories
*/
import { execSync } from 'child_process';
import fs from 'fs/promises';
import path from 'path';
export class PRGenerator {
constructor(sourcePath) {
this.sourcePath = sourcePath;
this.branchName = `skill-porter/add-dual-platform-support`;
}
/**
* Generate a pull request for dual-platform support
* @param {object} options - PR generation options
* @returns {Promise<{success: boolean, prUrl: string, errors: array}>}
*/
async generate(options = {}) {
const {
targetPlatform,
remote = 'origin',
baseBranch = 'main',
draft = false
} = options;
const result = {
success: false,
prUrl: null,
errors: [],
branch: this.branchName
};
try {
// Step 1: Check if gh CLI is available
await this._checkGHCLI();
// Step 2: Check if we're in a git repository
await this._checkGitRepo();
// Step 3: Check for uncommitted changes
const hasChanges = await this._hasUncommittedChanges();
if (!hasChanges) {
throw new Error('No uncommitted changes found. Run conversion first.');
}
// Step 4: Create new branch
await this._createBranch();
// Step 5: Commit changes
await this._commitChanges(targetPlatform);
// Step 6: Push branch
await this._pushBranch(remote);
// Step 7: Create PR
const prUrl = await this._createPR(targetPlatform, baseBranch, draft);
result.prUrl = prUrl;
result.success = true;
} catch (error) {
result.errors.push(error.message);
}
return result;
}
/**
* Check if gh CLI is installed
*/
async _checkGHCLI() {
try {
execSync('gh --version', { stdio: 'ignore' });
} catch {
throw new Error('GitHub CLI (gh) not found. Install from https://cli.github.com');
}
// Check if authenticated
try {
execSync('gh auth status', { stdio: 'ignore' });
} catch {
throw new Error('GitHub CLI not authenticated. Run: gh auth login');
}
}
/**
* Check if directory is a git repository
*/
async _checkGitRepo() {
try {
execSync('git rev-parse --git-dir', {
cwd: this.sourcePath,
stdio: 'ignore'
});
} catch {
throw new Error('Not a git repository. Initialize with: git init');
}
}
/**
* Check for uncommitted changes
*/
async _hasUncommittedChanges() {
try {
const status = execSync('git status --porcelain', {
cwd: this.sourcePath,
encoding: 'utf8'
});
return status.trim().length > 0;
} catch {
return false;
}
}
/**
* Create a new branch
*/
async _createBranch() {
try {
// Check if branch already exists
try {
execSync(`git rev-parse --verify ${this.branchName}`, {
cwd: this.sourcePath,
stdio: 'ignore'
});
// Branch exists, check it out
execSync(`git checkout ${this.branchName}`, {
cwd: this.sourcePath,
stdio: 'ignore'
});
} catch {
// Branch doesn't exist, create it
execSync(`git checkout -b ${this.branchName}`, {
cwd: this.sourcePath,
stdio: 'ignore'
});
}
} catch (error) {
throw new Error(`Failed to create branch: ${error.message}`);
}
}
/**
* Commit changes
*/
async _commitChanges(targetPlatform) {
const platformName = targetPlatform === 'gemini' ? 'Gemini CLI' : 'Claude Code';
const otherPlatform = targetPlatform === 'gemini' ? 'Claude Code' : 'Gemini CLI';
const commitMessage = `Add ${platformName} support for cross-platform compatibility
This PR adds ${platformName} support while maintaining existing ${otherPlatform} functionality, making this skill/extension work on both platforms.
## Changes
${targetPlatform === 'gemini' ? `
- Added \`gemini-extension.json\` - Gemini CLI manifest
- Added \`GEMINI.md\` - Gemini context file
- Created \`shared/\` directory for shared documentation
- Transformed MCP server paths for Gemini compatibility
- Converted tool restrictions (allowed-tools → excludeTools)
- Inferred settings schema from environment variables
` : `
- Added \`SKILL.md\` - Claude Code skill definition
- Added \`.claude-plugin/marketplace.json\` - Claude plugin config
- Created \`shared/\` directory for shared documentation
- Transformed MCP server paths for Claude compatibility
- Converted tool restrictions (excludeTools → allowed-tools)
- Documented environment variables from settings
`}
## Benefits
- ✅ Single codebase works on both AI platforms
- ✅ 85%+ code reuse (shared MCP server and docs)
- ✅ Easier maintenance (fix once, works everywhere)
- ✅ Broader user base (Claude + Gemini communities)
## Testing
- [x] Conversion validated with skill-porter
- [x] Files meet ${platformName} requirements
- [ ] Tested installation on ${platformName}
- [ ] Verified functionality on both platforms
## Installation
### ${otherPlatform} (existing)
\`\`\`bash
${otherPlatform === 'Claude Code' ?
'cp -r . ~/.claude/skills/$(basename $PWD)' :
'gemini extensions install .'}
\`\`\`
### ${platformName} (new)
\`\`\`bash
${platformName === 'Gemini CLI' ?
'gemini extensions install .' :
'cp -r . ~/.claude/skills/$(basename $PWD)'}
\`\`\`
---
*Generated with [skill-porter](https://github.com/jduncan-rva/skill-porter) - Universal tool for cross-platform AI skills*`;
try {
// Stage all new/modified files
execSync('git add .', { cwd: this.sourcePath });
// Create commit
execSync(`git commit -m "${commitMessage.replace(/"/g, '\\"')}"`, {
cwd: this.sourcePath,
stdio: 'ignore'
});
} catch (error) {
throw new Error(`Failed to commit changes: ${error.message}`);
}
}
/**
* Push branch to remote
*/
async _pushBranch(remote) {
try {
execSync(`git push -u ${remote} ${this.branchName}`, {
cwd: this.sourcePath,
stdio: 'inherit'
});
} catch (error) {
throw new Error(`Failed to push branch: ${error.message}`);
}
}
/**
* Create pull request
*/
async _createPR(targetPlatform, baseBranch, draft) {
const platformName = targetPlatform === 'gemini' ? 'Gemini CLI' : 'Claude Code';
const title = `Add ${platformName} support for cross-platform compatibility`;
const body = `This PR adds ${platformName} support, making this skill/extension work on both Claude Code and Gemini CLI.
## Overview
Converted using [skill-porter](https://github.com/jduncan-rva/skill-porter) to enable dual-platform deployment with minimal code duplication.
## What Changed
${targetPlatform === 'gemini' ? '✅ Added Gemini CLI support' : '✅ Added Claude Code support'}
- Platform-specific configuration files
- Shared documentation structure
- Converted tool restrictions and settings
## Benefits
- 🌐 Works on both AI platforms
- 🔄 85%+ code reuse
- 📦 Single repository
- 🚀 Easier maintenance
## Testing Checklist
- [x] Conversion validated
- [ ] Tested on ${platformName}
- [ ] Documentation updated
## Questions?
See the [skill-porter documentation](https://github.com/jduncan-rva/skill-porter) for details on universal skills.`;
try {
const draftFlag = draft ? '--draft' : '';
const output = execSync(
`gh pr create --base ${baseBranch} --title "${title}" --body "${body.replace(/"/g, '\\"')}" ${draftFlag}`,
{
cwd: this.sourcePath,
encoding: 'utf8'
}
);
// Extract PR URL from output
const urlMatch = output.match(/https:\/\/github\.com\/[^\s]+/);
if (urlMatch) {
return urlMatch[0];
}
return 'PR created successfully';
} catch (error) {
throw new Error(`Failed to create PR: ${error.message}`);
}
}
}
export default PRGenerator;