274 lines
7.5 KiB
JavaScript
Executable File
274 lines
7.5 KiB
JavaScript
Executable File
/**
|
|
* Workflow Migration Script
|
|
*
|
|
* Updates workflow.yaml files to support the multi-scope system.
|
|
* Primarily updates test_dir and other path variables to use scope-aware paths.
|
|
*
|
|
* Usage:
|
|
* node migrate-workflows.js [--dry-run] [--verbose]
|
|
*
|
|
* Options:
|
|
* --dry-run Show what would be changed without making changes
|
|
* --verbose Show detailed output
|
|
*/
|
|
|
|
const fs = require('fs-extra');
|
|
const path = require('node:path');
|
|
const yaml = require('yaml');
|
|
const chalk = require('chalk');
|
|
|
|
// Configuration
|
|
const SRC_PATH = path.resolve(__dirname, '../../../src');
|
|
const WORKFLOW_PATTERN = '**/workflow.yaml';
|
|
|
|
// Path mappings for migration
|
|
const PATH_MIGRATIONS = [
|
|
// Test directory migrations
|
|
{
|
|
pattern: /\{output_folder\}\/tests/g,
|
|
replacement: '{scope_tests}',
|
|
description: 'test directory to scope_tests',
|
|
},
|
|
{
|
|
pattern: /\{config_source:implementation_artifacts\}\/tests/g,
|
|
replacement: '{config_source:scope_tests}',
|
|
description: 'implementation_artifacts tests to scope_tests',
|
|
},
|
|
// Planning artifacts
|
|
{
|
|
pattern: /\{output_folder\}\/planning-artifacts/g,
|
|
replacement: '{config_source:planning_artifacts}',
|
|
description: 'output_folder planning to config_source',
|
|
},
|
|
// Implementation artifacts
|
|
{
|
|
pattern: /\{output_folder\}\/implementation-artifacts/g,
|
|
replacement: '{config_source:implementation_artifacts}',
|
|
description: 'output_folder implementation to config_source',
|
|
},
|
|
];
|
|
|
|
// Variables that indicate scope requirement
|
|
const SCOPE_INDICATORS = ['{scope}', '{scope_path}', '{scope_tests}', '{scope_planning}', '{scope_implementation}'];
|
|
|
|
/**
|
|
* Find all workflow.yaml files
|
|
*/
|
|
async function findWorkflowFiles(basePath) {
|
|
const files = [];
|
|
|
|
async function walk(dir) {
|
|
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
|
|
for (const entry of entries) {
|
|
const fullPath = path.join(dir, entry.name);
|
|
|
|
if (entry.isDirectory()) {
|
|
// Skip node_modules and hidden directories
|
|
if (!entry.name.startsWith('.') && entry.name !== 'node_modules') {
|
|
await walk(fullPath);
|
|
}
|
|
} else if (entry.name === 'workflow.yaml') {
|
|
files.push(fullPath);
|
|
}
|
|
}
|
|
}
|
|
|
|
await walk(basePath);
|
|
return files;
|
|
}
|
|
|
|
/**
|
|
* Check if a workflow already uses scope variables
|
|
*/
|
|
function usesScope(content) {
|
|
return SCOPE_INDICATORS.some((indicator) => content.includes(indicator));
|
|
}
|
|
|
|
/**
|
|
* Analyze a workflow file and suggest migrations
|
|
*/
|
|
function analyzeWorkflow(content, filePath) {
|
|
const analysis = {
|
|
filePath,
|
|
needsMigration: false,
|
|
alreadyScoped: false,
|
|
suggestions: [],
|
|
currentVariables: [],
|
|
};
|
|
|
|
// Check if already uses scope
|
|
if (usesScope(content)) {
|
|
analysis.alreadyScoped = true;
|
|
return analysis;
|
|
}
|
|
|
|
// Find variables that might need migration
|
|
const variablePattern = /\{[^}]+\}/g;
|
|
const matches = content.match(variablePattern) || [];
|
|
analysis.currentVariables = [...new Set(matches)];
|
|
|
|
// Check each migration pattern
|
|
for (const migration of PATH_MIGRATIONS) {
|
|
if (migration.pattern.test(content)) {
|
|
analysis.needsMigration = true;
|
|
analysis.suggestions.push({
|
|
description: migration.description,
|
|
pattern: migration.pattern.toString(),
|
|
replacement: migration.replacement,
|
|
});
|
|
}
|
|
}
|
|
|
|
// Check for test_dir variable
|
|
if (content.includes('test_dir:') || content.includes('test_dir:')) {
|
|
analysis.needsMigration = true;
|
|
analysis.suggestions.push({
|
|
description: 'Has test_dir variable - may need scope_tests',
|
|
pattern: 'test_dir',
|
|
replacement: 'scope_tests via config_source',
|
|
});
|
|
}
|
|
|
|
return analysis;
|
|
}
|
|
|
|
/**
|
|
* Apply migrations to workflow content
|
|
*/
|
|
function migrateWorkflow(content) {
|
|
let migrated = content;
|
|
let changes = [];
|
|
|
|
for (const migration of PATH_MIGRATIONS) {
|
|
if (migration.pattern.test(migrated)) {
|
|
migrated = migrated.replace(migration.pattern, migration.replacement);
|
|
changes.push(migration.description);
|
|
}
|
|
}
|
|
|
|
return { content: migrated, changes };
|
|
}
|
|
|
|
/**
|
|
* Add scope_required marker to workflow
|
|
*/
|
|
function addScopeMarker(content) {
|
|
try {
|
|
const parsed = yaml.parse(content);
|
|
|
|
// Add scope_required if not present
|
|
if (!parsed.scope_required) {
|
|
parsed.scope_required = false; // Default to false for backward compatibility
|
|
}
|
|
|
|
return yaml.stringify(parsed, { lineWidth: 120 });
|
|
} catch {
|
|
// If YAML parsing fails, return original
|
|
return content;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Main migration function
|
|
*/
|
|
async function main() {
|
|
const args = new Set(process.argv.slice(2));
|
|
const dryRun = args.has('--dry-run');
|
|
const verbose = args.has('--verbose');
|
|
|
|
console.log(chalk.bold('\nWorkflow Migration Script'));
|
|
console.log(chalk.dim('Updating workflow.yaml files for multi-scope support\n'));
|
|
|
|
if (dryRun) {
|
|
console.log(chalk.yellow('DRY RUN MODE - No changes will be made\n'));
|
|
}
|
|
|
|
// Find all workflow files
|
|
console.log(chalk.blue('Scanning for workflow.yaml files...'));
|
|
const files = await findWorkflowFiles(SRC_PATH);
|
|
console.log(chalk.green(`Found ${files.length} workflow.yaml files\n`));
|
|
|
|
// Analysis results
|
|
const results = {
|
|
analyzed: 0,
|
|
alreadyScoped: 0,
|
|
migrated: 0,
|
|
noChanges: 0,
|
|
errors: [],
|
|
};
|
|
|
|
// Process each file
|
|
for (const filePath of files) {
|
|
const relativePath = path.relative(SRC_PATH, filePath);
|
|
results.analyzed++;
|
|
|
|
try {
|
|
const content = await fs.readFile(filePath, 'utf8');
|
|
const analysis = analyzeWorkflow(content, filePath);
|
|
|
|
if (analysis.alreadyScoped) {
|
|
results.alreadyScoped++;
|
|
if (verbose) {
|
|
console.log(chalk.dim(` ○ ${relativePath} - already scope-aware`));
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (!analysis.needsMigration) {
|
|
results.noChanges++;
|
|
if (verbose) {
|
|
console.log(chalk.dim(` ○ ${relativePath} - no changes needed`));
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// Apply migration
|
|
const { content: migrated, changes } = migrateWorkflow(content);
|
|
|
|
if (changes.length > 0) {
|
|
console.log(chalk.cyan(` ● ${relativePath}`));
|
|
for (const change of changes) {
|
|
console.log(chalk.dim(` → ${change}`));
|
|
}
|
|
|
|
if (!dryRun) {
|
|
await fs.writeFile(filePath, migrated, 'utf8');
|
|
}
|
|
results.migrated++;
|
|
} else {
|
|
results.noChanges++;
|
|
}
|
|
} catch (error) {
|
|
results.errors.push({ file: relativePath, error: error.message });
|
|
console.log(chalk.red(` ✗ ${relativePath} - Error: ${error.message}`));
|
|
}
|
|
}
|
|
|
|
// Print summary
|
|
console.log(chalk.bold('\n─────────────────────────────────────'));
|
|
console.log(chalk.bold('Summary'));
|
|
console.log(chalk.dim('─────────────────────────────────────'));
|
|
console.log(` Files analyzed: ${results.analyzed}`);
|
|
console.log(` Already scope-aware: ${results.alreadyScoped}`);
|
|
console.log(` Migrated: ${results.migrated}`);
|
|
console.log(` No changes needed: ${results.noChanges}`);
|
|
if (results.errors.length > 0) {
|
|
console.log(chalk.red(` Errors: ${results.errors.length}`));
|
|
}
|
|
console.log();
|
|
|
|
if (dryRun && results.migrated > 0) {
|
|
console.log(chalk.yellow('Run without --dry-run to apply changes\n'));
|
|
}
|
|
|
|
// Exit with error code if there were errors
|
|
process.exit(results.errors.length > 0 ? 1 : 0);
|
|
}
|
|
|
|
// Run
|
|
main().catch((error) => {
|
|
console.error(chalk.red('Fatal error:'), error.message);
|
|
process.exit(1);
|
|
});
|