gitarsenal-cli
Version:
CLI tool for creating Modal sandboxes with GitHub repositories
174 lines (149 loc) • 5.51 kB
JavaScript
const path = require('path');
const fs = require('fs-extra');
const { spawn } = require('child_process');
const chalk = require('chalk');
const ora = require('ora');
const { promisify } = require('util');
const { exec } = require('child_process');
const os = require('os');
const execAsync = promisify(exec);
/**
* Get the path to the Python script
* @returns {string} - Path to the Python script
*/
function getPythonScriptPath() {
// First check if the script exists in the package directory
const packageScriptPath = path.join(__dirname, '..', 'python', 'test_modalSandboxScript.py');
if (fs.existsSync(packageScriptPath)) {
return packageScriptPath;
}
// If not found, return the path where it will be copied during installation
return packageScriptPath;
}
/**
* Run the container with the given options
* @param {Object} options - Container options
* @param {string} options.repoUrl - GitHub repository URL
* @param {string} options.gpuType - GPU type
* @param {string} options.volumeName - Volume name
* @param {Array<string>} options.setupCommands - Setup commands
* @param {boolean} options.useApi - Whether to use the API to fetch setup commands
* @param {boolean} options.showExamples - Whether to show usage examples
* @returns {Promise<void>}
*/
async function runContainer(options) {
const {
repoUrl,
gpuType,
volumeName,
setupCommands = [],
useApi = true,
showExamples = false,
yes = false,
userId,
userName,
userEmail
} = options;
// Get the path to the Python script
const scriptPath = getPythonScriptPath();
// Check if the script exists
if (!fs.existsSync(scriptPath)) {
throw new Error(`Python script not found at ${scriptPath}. Please reinstall the package.`);
}
// Prepare command arguments
const args = [
scriptPath
];
// If show examples is true, only pass that flag
if (showExamples) {
args.push('--show-examples');
// Log the command being executed
// console.log(chalk.dim(`\nExecuting: python ${args.join(' ')}`));
// Run the Python script with show examples flag
const pythonExecutable = process.env.PYTHON_EXECUTABLE || 'python';
const pythonProcess = spawn(pythonExecutable, ['-u', ...args], {
stdio: 'inherit', // Inherit stdio to show real-time output
env: { ...process.env, PYTHONUNBUFFERED: '1' } // Force unbuffered output
});
return new Promise((resolve, reject) => {
pythonProcess.on('close', (code) => {
if (code === 0) {
resolve();
} else {
reject(new Error(`Process exited with code ${code}`));
}
});
pythonProcess.on('error', (error) => {
reject(error);
});
});
}
// Add normal arguments
if (gpuType) args.push('--gpu', gpuType);
if (repoUrl) args.push('--repo-url', repoUrl);
if (volumeName) {
args.push('--volume-name', volumeName);
}
// Always use the API to fetch setup commands unless explicitly disabled
if (useApi) {
args.push('--use-api');
}
// Add --yes flag to skip confirmation prompts
if (yes) {
args.push('--yes');
console.log(chalk.gray(`🔍 Debug: Adding --yes flag to Python script`));
}
// Add user credentials if provided
if (userId && userEmail && userName) {
args.push('--user-id', userEmail);
args.push('--user-name', userId);
args.push('--display-name', userName);
// console.log(chalk.gray(`🔍 Debug: Passing user credentials to Python script`));
}
// Handle manual setup commands if provided
if (setupCommands.length > 0) {
// Create a temporary file to store setup commands
const tempCommandsFile = path.join(os.tmpdir(), `gitarsenal-commands-${Date.now()}.txt`);
fs.writeFileSync(tempCommandsFile, setupCommands.join('\n'));
args.push('--commands-file', tempCommandsFile);
// Ensure Python skips auto-detection via GitIngest when commands are provided
args.push('--no-gitingest');
}
// Log the command being executed
// console.log(chalk.dim(`\nExecuting: python ${args.join(' ')}`));
// console.log(chalk.gray(`🔍 Debug: yes parameter = ${yes}`));
// Run the Python script without spinner to avoid terminal interference
console.log(chalk.dim('Launching container...'));
try {
// Run the Python script
const pythonExecutable = process.env.PYTHON_EXECUTABLE || 'python';
const pythonProcess = spawn(pythonExecutable, ['-u', ...args], {
stdio: 'inherit', // Inherit stdio to show real-time output
env: { ...process.env, PYTHONUNBUFFERED: '1' } // Force unbuffered output
});
// Handle process completion
return new Promise((resolve, reject) => {
pythonProcess.on('close', (code) => {
if (code === 0) {
console.log(chalk.green('✅ Container launched successfully'));
resolve();
} else {
console.log(chalk.red(`❌ Container launch failed with exit code ${code}`));
reject(new Error(`Process exited with code ${code}`));
}
});
// Handle process errors
pythonProcess.on('error', (error) => {
console.log(chalk.red(`❌ Failed to start Python process: ${error.message}`));
reject(error);
});
});
} catch (error) {
console.log(chalk.red(`❌ Error launching container: ${error.message}`));
throw error;
}
}
module.exports = {
runContainer,
getPythonScriptPath
};