scaffold-scripts
Version:
Simple CLI tool for managing and running your own scripts. Add any script, run it anywhere.
341 lines • 15.1 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ScriptExecutor = void 0;
const child_process_1 = require("child_process");
const util_1 = require("util");
const os_1 = require("os");
const scriptTypeDetector_js_1 = require("./scriptTypeDetector.js");
const fs_1 = require("fs");
const path_1 = require("path");
const os_2 = require("os");
const execAsync = (0, util_1.promisify)(child_process_1.exec);
class ScriptExecutor {
constructor() {
this.isWindows = (0, os_1.platform)() === 'win32';
this.typeDetector = new scriptTypeDetector_js_1.ScriptTypeDetector();
}
/**
* Convert script to appropriate platform format
*/
convertScript(script, targetPlatform) {
const shouldConvertToWindows = targetPlatform === 'windows' ||
(targetPlatform === 'all' && this.isWindows);
if (shouldConvertToWindows) {
return this.convertToWindows(script);
}
else {
return this.convertToUnix(script);
}
}
/**
* Convert script to Windows PowerShell format
*/
convertToWindows(script) {
return script
// Convert directory separators
.replace(/\//g, '\\\\')
// Convert mkdir to ni (New-Item)
.replace(/mkdir -p\s+/g, 'ni -ItemType Directory -Force -Path ')
.replace(/mkdir\s+/g, 'ni -ItemType Directory -Force -Path ')
// Convert touch to ni (New-Item)
.replace(/touch\s+/g, 'ni -ItemType File -Force -Path ')
// Convert Python virtual environment activation
.replace(/source\s+([^\\s]+)\/bin\/activate/g, '$1\\\\Scripts\\\\activate.ps1')
.replace(/\.\s+([^\\s]+)\/bin\/activate/g, '$1\\\\Scripts\\\\activate.ps1')
// Convert shell-specific commands
.replace(/export\s+([A-Z_]+)=([^\\s;]+)/g, '$env:$1="$2"')
.replace(/which\s+/g, 'Get-Command ')
// Convert && to semicolons for PowerShell
.replace(/\s+&&\s+/g, '; ')
// Convert environment variables
.replace(/\$([A-Z_]+)/g, '$env:$1')
// Convert common Unix commands
.replace(/\bls\b/g, 'Get-ChildItem')
.replace(/\bcp\s+/g, 'Copy-Item ')
.replace(/\bmv\s+/g, 'Move-Item ')
// Ensure paths are quoted if they contain spaces
.replace(/(ni -ItemType \w+ -Force -Path\s+)([^'"][^;]*)/g, (match, prefix, paths) => {
// Split paths and quote each one
const pathList = paths.split(',').map((p) => p.trim()).map((p) => {
if (!p.startsWith("'") && !p.startsWith('"')) {
return `'${p}'`;
}
return p;
}).join(',');
return prefix + pathList;
});
}
/**
* Convert script to Unix/Linux format
*/
convertToUnix(script) {
return script
// Convert directory separators
.replace(/\\\\/g, '/')
.replace(/\\\\/g, '/')
// Convert ni (New-Item) to mkdir/touch
.replace(/ni -ItemType Directory -Force -Path\s+/g, 'mkdir -p ')
.replace(/ni -ItemType File -Force -Path\s+/g, 'touch ')
// Convert Python virtual environment activation
.replace(/([^\\s]+)\\\\Scripts\\\\activate\.ps1/g, 'source $1/bin/activate')
// Convert PowerShell commands to Unix equivalents
.replace(/\$env:([A-Z_]+)="([^"]+)"/g, 'export $1="$2"')
.replace(/Get-Command\s+/g, 'which ')
.replace(/Get-ChildItem/g, 'ls')
.replace(/Copy-Item\s+/g, 'cp ')
.replace(/Move-Item\s+/g, 'mv ')
// Convert semicolons back to &&
.replace(/;\s+/g, ' && ')
// Convert PowerShell environment variables
.replace(/\$env:([A-Z_]+)/g, '$$$1')
// Remove quotes from simple paths
.replace(/'([^']*?)'/g, '$1')
.replace(/"([^"]*?)"/g, '$1');
}
/**
* Get script type info from string
*/
getScriptTypeInfo(type) {
switch (type) {
case 'nodejs':
return { type: 'nodejs', interpreters: ['node'], extensions: ['.js', '.mjs'] };
case 'python':
return { type: 'python', interpreters: ['python', 'python3'], extensions: ['.py'] };
case 'powershell':
return { type: 'powershell', interpreters: ['powershell', 'pwsh'], extensions: ['.ps1'] };
case 'shell':
return { type: 'shell', interpreters: ['bash'], extensions: ['.sh'] };
case 'batch':
return { type: 'batch', interpreters: ['cmd'], extensions: ['.bat', '.cmd'] };
default:
return { type: 'shell', interpreters: ['bash'], extensions: ['.sh'] };
}
}
/**
* Execute the script in the current directory with real-time output streaming
*/
async executeScript(script, targetPlatform, args = [], knownScriptType) {
const scriptType = knownScriptType ?
this.getScriptTypeInfo(knownScriptType) :
this.typeDetector.detectType(script);
console.log(`Using script type: ${scriptType.type}`);
// Check if required interpreters are available
const availability = await this.typeDetector.checkInterpreterAvailability(scriptType);
if (availability.available.length === 0 && availability.missing.length > 0) {
throw new Error(`Required interpreters not found: ${availability.missing.join(', ')}. Please install them first.`);
}
// For Python, Node.js, PowerShell, or other interpreter-based scripts, create a temporary file
if (['python', 'nodejs', 'powershell'].includes(scriptType.type)) {
return this.executeInterpreterScriptWithStreaming(script, scriptType, args);
}
// For shell/batch scripts, use the converted approach
const convertedScript = this.convertScript(script, targetPlatform);
console.log('\\n--- Executing Script ---');
console.log(convertedScript);
console.log('--- End Script ---\\n');
return this.executeWithStreaming(convertedScript);
}
/**
* Execute a command with real-time output streaming
*/
async executeWithStreaming(command) {
return new Promise(async (resolve, reject) => {
try {
// Detect best available shell
let shell = '/bin/bash';
let shellArgs = ['-c'];
if (this.isWindows) {
// On Windows, try powershell.exe first, then pwsh as fallback
try {
await execAsync('where powershell.exe', { timeout: 1000 });
shell = 'powershell.exe';
shellArgs = ['-Command'];
}
catch {
try {
await execAsync('where pwsh', { timeout: 1000 });
shell = 'pwsh';
shellArgs = ['-Command'];
}
catch {
// If neither PowerShell is available on Windows, use cmd
shell = 'cmd';
shellArgs = ['/c'];
}
}
}
else {
// On Unix-like systems, try pwsh first, then bash as fallback
try {
await execAsync('which pwsh', { timeout: 1000 });
shell = 'pwsh';
shellArgs = ['-Command'];
}
catch {
shell = '/bin/bash';
shellArgs = ['-c'];
}
}
const child = (0, child_process_1.spawn)(shell, [...shellArgs, command], {
cwd: process.cwd(),
stdio: ['inherit', 'pipe', 'pipe']
});
let stdout = '';
let stderr = '';
// Stream stdout in real-time
child.stdout?.on('data', (data) => {
const output = data.toString();
stdout += output;
// Write directly to stdout to preserve formatting
process.stdout.write(output);
});
// Stream stderr in real-time
child.stderr?.on('data', (data) => {
const output = data.toString();
stderr += output;
// Write directly to stderr to preserve formatting
process.stderr.write(output);
});
child.on('close', (code) => {
if (code === 0) {
resolve({ stdout, stderr });
}
else {
reject(new Error(`Script execution failed with exit code ${code}`));
}
});
child.on('error', (error) => {
reject(new Error(`Script execution failed: ${error.message}`));
});
}
catch (error) {
reject(new Error(`Script execution failed: ${error.message}`));
}
});
}
/**
* Execute interpreter-based scripts (Python, Node.js, etc.)
*/
async executeInterpreterScript(script, scriptType, args = []) {
// Create temporary directory
const tempDir = (0, path_1.join)((0, os_2.tmpdir)(), 'scaffold-scripts');
(0, fs_1.mkdirSync)(tempDir, { recursive: true });
// Create temporary script file
const extension = scriptType.extensions[0];
const tempFile = (0, path_1.join)(tempDir, `temp_script${extension}`);
(0, fs_1.writeFileSync)(tempFile, script);
console.log(`\\n--- Executing ${scriptType.type} Script ---`);
console.log(script);
console.log('--- End Script ---\\n');
try {
let command = this.typeDetector.getExecutionCommand(scriptType, tempFile);
// Add script arguments for PowerShell
if (scriptType.type === 'powershell' && args.length > 0) {
command += ' ' + args.join(' ');
}
const result = await execAsync(command, {
cwd: process.cwd(),
maxBuffer: 1024 * 1024 * 10 // 10MB buffer
});
return result;
}
catch (error) {
throw new Error(`${scriptType.type} script execution failed: ${error.message}`);
}
}
/**
* Execute interpreter-based scripts with real-time output streaming
*/
async executeInterpreterScriptWithStreaming(script, scriptType, args = []) {
return new Promise((resolve, reject) => {
// Create temporary directory
const tempDir = (0, path_1.join)((0, os_2.tmpdir)(), 'scaffold-scripts');
(0, fs_1.mkdirSync)(tempDir, { recursive: true });
// Create temporary script file
const extension = scriptType.extensions[0];
const tempFile = (0, path_1.join)(tempDir, `temp_script${extension}`);
(0, fs_1.writeFileSync)(tempFile, script);
console.log(`\\n--- Executing ${scriptType.type} Script ---`);
console.log(script);
console.log('--- End Script ---\\n');
try {
let command = this.typeDetector.getExecutionCommand(scriptType, tempFile);
// Parse command and arguments for spawn, handling quoted paths properly
const parseCommand = (cmd) => {
const parts = [];
let current = '';
let inQuotes = false;
for (let i = 0; i < cmd.length; i++) {
const char = cmd[i];
if (char === '"' && (i === 0 || cmd[i - 1] !== '\\')) {
inQuotes = !inQuotes;
}
else if (char === ' ' && !inQuotes) {
if (current) {
parts.push(current);
current = '';
}
}
else {
current += char;
}
}
if (current) {
parts.push(current);
}
return {
executable: parts[0],
args: parts.slice(1)
};
};
const { executable, args: execArgs } = parseCommand(command);
// Add script arguments for PowerShell
if (scriptType.type === 'powershell' && args.length > 0) {
execArgs.push(...args);
}
const child = (0, child_process_1.spawn)(executable, execArgs, {
cwd: process.cwd(),
stdio: ['inherit', 'pipe', 'pipe']
});
let stdout = '';
let stderr = '';
// Stream stdout in real-time
child.stdout?.on('data', (data) => {
const output = data.toString();
stdout += output;
// Write directly to stdout to preserve formatting
process.stdout.write(output);
});
// Stream stderr in real-time
child.stderr?.on('data', (data) => {
const output = data.toString();
stderr += output;
// Write directly to stderr to preserve formatting
process.stderr.write(output);
});
child.on('close', (code) => {
if (code === 0) {
resolve({ stdout, stderr });
}
else {
reject(new Error(`${scriptType.type} script execution failed with exit code ${code}`));
}
});
child.on('error', (error) => {
reject(new Error(`${scriptType.type} script execution failed: ${error.message}`));
});
}
catch (error) {
reject(new Error(`${scriptType.type} script execution failed: ${error.message}`));
}
});
}
/**
* Preview what the script will look like when converted
*/
previewScript(script, targetPlatform) {
return this.convertScript(script, targetPlatform);
}
}
exports.ScriptExecutor = ScriptExecutor;
//# sourceMappingURL=scriptExecutor.js.map