aethex-forge/scripts/import-github-issues.js
Claude 7d40abd593
Add GitHub Issues import script
Created automated script to convert markdown issue documentation
into GitHub Issues with proper labels and formatting.

## What's Added

### Script: scripts/import-github-issues.js
- Parses docs/issues/*.md files
- Creates GitHub Issues via Octokit API
- Adds proper labels (P0/P1/P2, bug, feature, etc.)
- Rate-limited to respect GitHub API limits
- Comprehensive error handling

### Documentation: scripts/README.md
- Complete setup instructions
- Troubleshooting guide
- Customization options
- Advanced usage examples

### Dependencies
- Added @octokit/rest for GitHub API access

## How to Use

1. Get GitHub Personal Access Token:
   - Visit https://github.com/settings/tokens
   - Create token with 'repo' scope

2. Set token:
   ```bash
   export GITHUB_TOKEN=your_token_here
   ```

3. Run script:
   ```bash
   node scripts/import-github-issues.js
   ```

## What Gets Created

The script will create 25 GitHub Issues:
- 5 P0 (critical) issues
- 5 P1 (medium priority) issues
- 15 P2 (nice-to-have) issues

Each with:
- Proper title with priority prefix
- Full markdown body
- Appropriate labels
- File references intact

## Labels Created

- P0 (red) - Critical priority
- P1 (orange) - Medium priority
- P2 (yellow) - Low priority
- bug, feature, enhancement, tech-debt, security

See scripts/README.md for full documentation.
2026-01-03 09:11:22 +00:00

210 lines
5.9 KiB
JavaScript
Executable file

#!/usr/bin/env node
/**
* GitHub Issues Import Script
*
* Converts markdown issue documentation into GitHub Issues
* Reads from docs/issues/ and creates issues with proper labels
*
* Usage:
* node scripts/import-github-issues.js
*
* Requirements:
* - GitHub Personal Access Token with repo scope
* - Set GITHUB_TOKEN environment variable
* - npm install @octokit/rest
*/
const fs = require('fs');
const path = require('path');
const { Octokit } = require('@octokit/rest');
// Configuration
const REPO_OWNER = 'AeThex-Corporation';
const REPO_NAME = 'aethex-forge';
const ISSUES_DIR = path.join(__dirname, '../docs/issues');
// Initialize Octokit
const octokit = new Octokit({
auth: process.env.GITHUB_TOKEN,
});
// Label definitions
const LABELS = {
P0: { name: 'P0', color: 'B60205', description: 'Critical priority - fix ASAP' },
P1: { name: 'P1', color: 'D93F0B', description: 'Medium priority' },
P2: { name: 'P2', color: 'FBCA04', description: 'Low priority / nice to have' },
bug: { name: 'bug', color: 'D73A4A', description: 'Something isn\'t working' },
feature: { name: 'feature', color: '0E8A16', description: 'New feature or request' },
enhancement: { name: 'enhancement', color: 'A2EEEF', description: 'Enhancement to existing feature' },
'tech-debt': { name: 'tech-debt', color: 'D876E3', description: 'Technical debt' },
security: { name: 'security', color: 'B60205', description: 'Security issue' },
documentation: { name: 'documentation', color: '0075CA', description: 'Documentation improvements' },
};
/**
* Parse markdown file into individual issues
*/
function parseMarkdownIssues(content, priority) {
const issues = [];
// Split by "## Issue" headings
const sections = content.split(/^## Issue \d+:/m);
// Skip the first section (it's the header before first issue)
for (let i = 1; i < sections.length; i++) {
const section = sections[i].trim();
// Extract title (first line with brackets removed)
const titleMatch = section.match(/^\[.*?\]\s*(.+?)$/m);
if (!titleMatch) continue;
const title = titleMatch[1].trim();
// Extract labels from title
const labels = [priority];
if (title.toLowerCase().includes('fix') || section.includes('**Labels:** `bug`')) {
labels.push('bug');
} else if (section.includes('**Labels:** `feature`')) {
labels.push('feature');
} else if (section.includes('**Labels:** `enhancement`')) {
labels.push('enhancement');
} else if (section.includes('**Labels:** `tech-debt`')) {
labels.push('tech-debt');
} else if (section.includes('**Labels:** `security`')) {
labels.push('security');
}
// Get the body (everything after the title line)
const bodyStartIndex = section.indexOf('\n');
const body = section.substring(bodyStartIndex).trim();
issues.push({
title: `[${priority}] ${title}`,
body,
labels,
});
}
return issues;
}
/**
* Ensure all labels exist in the repository
*/
async function ensureLabels() {
console.log('📝 Ensuring labels exist...');
for (const [key, label] of Object.entries(LABELS)) {
try {
await octokit.rest.issues.getLabel({
owner: REPO_OWNER,
repo: REPO_NAME,
name: label.name,
});
console.log(` ✓ Label "${label.name}" exists`);
} catch (error) {
if (error.status === 404) {
// Label doesn't exist, create it
await octokit.rest.issues.createLabel({
owner: REPO_OWNER,
repo: REPO_NAME,
name: label.name,
color: label.color,
description: label.description,
});
console.log(` ✓ Created label "${label.name}"`);
} else {
throw error;
}
}
}
}
/**
* Create a single GitHub issue
*/
async function createIssue(issue) {
try {
const response = await octokit.rest.issues.create({
owner: REPO_OWNER,
repo: REPO_NAME,
title: issue.title,
body: issue.body,
labels: issue.labels,
});
console.log(` ✓ Created: ${issue.title}`);
console.log(`${response.data.html_url}`);
return response.data;
} catch (error) {
console.error(` ✗ Failed to create: ${issue.title}`);
console.error(` Error: ${error.message}`);
throw error;
}
}
/**
* Main execution
*/
async function main() {
console.log('🚀 GitHub Issues Import Script\n');
// Check for GitHub token
if (!process.env.GITHUB_TOKEN) {
console.error('❌ Error: GITHUB_TOKEN environment variable not set');
console.error('\nTo fix this:');
console.error('1. Go to https://github.com/settings/tokens');
console.error('2. Create a Personal Access Token with "repo" scope');
console.error('3. Run: export GITHUB_TOKEN=your_token_here');
process.exit(1);
}
// Ensure labels exist
await ensureLabels();
console.log('');
// Process each priority file
const files = [
{ file: 'P0-ISSUES.md', priority: 'P0' },
{ file: 'P1-ISSUES.md', priority: 'P1' },
{ file: 'P2-ISSUES.md', priority: 'P2' },
];
let totalCreated = 0;
for (const { file, priority } of files) {
const filePath = path.join(ISSUES_DIR, file);
if (!fs.existsSync(filePath)) {
console.warn(`⚠️ File not found: ${file}`);
continue;
}
console.log(`📄 Processing ${file}...`);
const content = fs.readFileSync(filePath, 'utf8');
const issues = parseMarkdownIssues(content, priority);
console.log(` Found ${issues.length} issues\n`);
for (const issue of issues) {
await createIssue(issue);
totalCreated++;
// Rate limiting: wait 1 second between requests
await new Promise(resolve => setTimeout(resolve, 1000));
}
console.log('');
}
console.log(`✅ Done! Created ${totalCreated} GitHub issues`);
console.log(`\nView issues: https://github.com/${REPO_OWNER}/${REPO_NAME}/issues`);
}
// Run the script
main().catch((error) => {
console.error('❌ Script failed:', error.message);
process.exit(1);
});