279 lines
7.5 KiB
JavaScript
279 lines
7.5 KiB
JavaScript
import chalk from 'chalk';
|
|
import { resolve, join, basename } from 'path';
|
|
import { existsSync, readdirSync, statSync } from 'fs';
|
|
import { detectProject, isValidProject } from '../detectors/index.js';
|
|
import { initCommand } from './init.js';
|
|
|
|
/**
|
|
* Batch command - operations across multiple projects
|
|
*/
|
|
export async function batchCommand(action, options) {
|
|
const rootPath = resolve(options.root);
|
|
|
|
if (!existsSync(rootPath)) {
|
|
throw new Error(`Root path does not exist: ${rootPath}`);
|
|
}
|
|
|
|
console.log(chalk.blue('Batch operation:'), action);
|
|
console.log(chalk.gray('Root directory:'), rootPath);
|
|
console.log();
|
|
|
|
// Get all project directories
|
|
const projects = getProjectDirectories(rootPath, options);
|
|
console.log(chalk.gray(`Found ${projects.length} projects`));
|
|
console.log();
|
|
|
|
switch (action) {
|
|
case 'detect':
|
|
await batchDetect(projects, options);
|
|
break;
|
|
case 'init':
|
|
await batchInit(projects, options);
|
|
break;
|
|
default:
|
|
throw new Error(`Unknown batch action: ${action}. Supported: detect, init`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get all project directories from root
|
|
*/
|
|
function getProjectDirectories(rootPath, options) {
|
|
const entries = readdirSync(rootPath);
|
|
let projects = [];
|
|
|
|
for (const entry of entries) {
|
|
const fullPath = join(rootPath, entry);
|
|
|
|
// Skip if not a directory
|
|
try {
|
|
if (!statSync(fullPath).isDirectory()) continue;
|
|
} catch {
|
|
continue;
|
|
}
|
|
|
|
// Skip invalid projects
|
|
if (!isValidProject(fullPath)) continue;
|
|
|
|
// Apply filter pattern
|
|
if (options.filter && !entry.includes(options.filter)) continue;
|
|
|
|
// Apply exclusions
|
|
if (options.exclude) {
|
|
const excludeList = options.exclude.split(',').map(e => e.trim());
|
|
if (excludeList.some(exc => entry.includes(exc))) continue;
|
|
}
|
|
|
|
projects.push({
|
|
name: entry,
|
|
path: fullPath
|
|
});
|
|
}
|
|
|
|
return projects.sort((a, b) => a.name.localeCompare(b.name));
|
|
}
|
|
|
|
/**
|
|
* Batch detect - scan all projects and report types
|
|
*/
|
|
async function batchDetect(projects, options) {
|
|
const results = {
|
|
dockerizable: [],
|
|
notDockerizable: [],
|
|
byType: {}
|
|
};
|
|
|
|
console.log(chalk.blue('Scanning projects...'));
|
|
console.log();
|
|
|
|
for (const project of projects) {
|
|
process.stdout.write(chalk.gray(` ${project.name}... `));
|
|
|
|
try {
|
|
const detection = await detectProject(project.path);
|
|
|
|
if (detection.dockerizable) {
|
|
results.dockerizable.push({
|
|
...project,
|
|
...detection
|
|
});
|
|
|
|
// Group by type
|
|
if (!results.byType[detection.type]) {
|
|
results.byType[detection.type] = [];
|
|
}
|
|
results.byType[detection.type].push(project.name);
|
|
|
|
console.log(chalk.green(`✓ ${detection.type}`));
|
|
} else {
|
|
results.notDockerizable.push({
|
|
...project,
|
|
...detection
|
|
});
|
|
console.log(chalk.yellow(`✗ ${detection.type} - ${detection.reason}`));
|
|
}
|
|
} catch (error) {
|
|
results.notDockerizable.push({
|
|
...project,
|
|
type: 'error',
|
|
reason: error.message
|
|
});
|
|
console.log(chalk.red(`✗ Error: ${error.message}`));
|
|
}
|
|
}
|
|
|
|
// Print report
|
|
if (options.report) {
|
|
printReport(results, projects.length);
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
/**
|
|
* Print batch detect report
|
|
*/
|
|
function printReport(results, totalCount) {
|
|
console.log();
|
|
console.log(chalk.blue('═'.repeat(60)));
|
|
console.log(chalk.blue.bold(' BATCH DETECTION REPORT'));
|
|
console.log(chalk.blue('═'.repeat(60)));
|
|
console.log();
|
|
|
|
// Summary
|
|
console.log(chalk.white.bold('Summary'));
|
|
console.log(chalk.gray('─'.repeat(40)));
|
|
console.log(` Total projects: ${totalCount}`);
|
|
console.log(` Dockerizable: ${chalk.green(results.dockerizable.length)}`);
|
|
console.log(` Not dockerizable: ${chalk.yellow(results.notDockerizable.length)}`);
|
|
console.log();
|
|
|
|
// By type
|
|
console.log(chalk.white.bold('Projects by Type'));
|
|
console.log(chalk.gray('─'.repeat(40)));
|
|
|
|
const typeOrder = [
|
|
'nodejs-express',
|
|
'nodejs-vite-react',
|
|
'nodejs-vite-react-ssr',
|
|
'nodejs-generic',
|
|
'python-standard',
|
|
'python-ml-pytorch',
|
|
'dotnet-blazor',
|
|
'dotnet-webapi',
|
|
'static-nginx',
|
|
'flutter-web'
|
|
];
|
|
|
|
for (const type of typeOrder) {
|
|
if (results.byType[type] && results.byType[type].length > 0) {
|
|
console.log();
|
|
console.log(chalk.cyan(` ${type} (${results.byType[type].length}):`));
|
|
results.byType[type].forEach(name => {
|
|
console.log(chalk.gray(` - ${name}`));
|
|
});
|
|
}
|
|
}
|
|
|
|
// Other types not in the order list
|
|
for (const [type, projects] of Object.entries(results.byType)) {
|
|
if (!typeOrder.includes(type) && projects.length > 0) {
|
|
console.log();
|
|
console.log(chalk.cyan(` ${type} (${projects.length}):`));
|
|
projects.forEach(name => {
|
|
console.log(chalk.gray(` - ${name}`));
|
|
});
|
|
}
|
|
}
|
|
|
|
// Not dockerizable
|
|
if (results.notDockerizable.length > 0) {
|
|
console.log();
|
|
console.log(chalk.white.bold('Not Dockerizable'));
|
|
console.log(chalk.gray('─'.repeat(40)));
|
|
results.notDockerizable.forEach(p => {
|
|
console.log(chalk.yellow(` - ${p.name}`), chalk.gray(`(${p.reason || p.type})`));
|
|
});
|
|
}
|
|
|
|
console.log();
|
|
console.log(chalk.blue('═'.repeat(60)));
|
|
|
|
// Suggested ports
|
|
if (results.dockerizable.length > 0) {
|
|
console.log();
|
|
console.log(chalk.white.bold('Suggested Port Mapping'));
|
|
console.log(chalk.gray('─'.repeat(40)));
|
|
|
|
let port = 3000;
|
|
results.dockerizable.forEach(p => {
|
|
const suggestedPort = p.port || port;
|
|
console.log(chalk.gray(` ${p.name}:`), `${suggestedPort}`);
|
|
port = Math.max(port, suggestedPort) + 1;
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Batch init - initialize Docker config for multiple projects
|
|
*/
|
|
async function batchInit(projects, options) {
|
|
console.log(chalk.blue('Initializing Docker configuration...'));
|
|
console.log();
|
|
|
|
const parallel = options.parallel || 4;
|
|
let completed = 0;
|
|
let failed = 0;
|
|
let skipped = 0;
|
|
|
|
// Process in parallel batches
|
|
for (let i = 0; i < projects.length; i += parallel) {
|
|
const batch = projects.slice(i, i + parallel);
|
|
|
|
const promises = batch.map(async (project) => {
|
|
try {
|
|
// Check if already initialized
|
|
const hasDockerfile = existsSync(join(project.path, 'Dockerfile'));
|
|
const hasConfig = existsSync(join(project.path, 'docker-deployment.json'));
|
|
|
|
if ((hasDockerfile || hasConfig) && !options.force) {
|
|
console.log(chalk.yellow(` ${project.name}: Skipped (already initialized)`));
|
|
skipped++;
|
|
return;
|
|
}
|
|
|
|
// Detect first
|
|
const detection = await detectProject(project.path);
|
|
|
|
if (!detection.dockerizable) {
|
|
console.log(chalk.yellow(` ${project.name}: Skipped (${detection.reason})`));
|
|
skipped++;
|
|
return;
|
|
}
|
|
|
|
// Initialize with non-interactive mode
|
|
await initCommand(project.path, {
|
|
interactive: false,
|
|
overwrite: options.force
|
|
});
|
|
|
|
console.log(chalk.green(` ${project.name}: ✓ Initialized`));
|
|
completed++;
|
|
|
|
} catch (error) {
|
|
console.log(chalk.red(` ${project.name}: ✗ Failed - ${error.message}`));
|
|
failed++;
|
|
}
|
|
});
|
|
|
|
await Promise.all(promises);
|
|
}
|
|
|
|
// Summary
|
|
console.log();
|
|
console.log(chalk.blue('Batch init complete:'));
|
|
console.log(chalk.green(` Initialized: ${completed}`));
|
|
console.log(chalk.yellow(` Skipped: ${skipped}`));
|
|
console.log(chalk.red(` Failed: ${failed}`));
|
|
}
|