superuser-task-runner
Version:
A powerful task runner designed to manage complex development workflows across both monorepo and single-project configurations. Built as a more flexible alternative to Turborepo with enhanced customization capabilities, this tool focuses on unique deploym
178 lines (143 loc) • 5.49 kB
JavaScript
const fs = require('fs');
const path = require('path');
const { loadEnvConfig } = require('./loadEnvConfig.js');
const colors = require('./colors.js');
const { ensureMethJsonExists, displayVersion } = require('./helpers.js');
// Function to display help
function displayHelp() {
console.log(`${colors.cyan}${colors.bright}STR ADD - Add commands to str.json${colors.reset}\n`);
console.log(`${colors.bright}Usage:${colors.reset}`);
console.log(` ${colors.green}Single Project:${colors.reset}`);
console.log(` str-add <task-name> <command>`);
console.log(` Example: str-add "test" "pnpm test"`);
console.log();
console.log(` ${colors.green}Monorepo Project:${colors.reset}`);
console.log(` str-add <task-name> <directory> <command>`);
console.log(` Example: str-add "i:app" "apps/app" "pnpm i --filter app --prefer-offline --no-cache"`);
console.log();
console.log(`${colors.bright}Options:${colors.reset}`);
console.log(` -h, --help Show this help message`);
console.log(` -v, --version Show version`);
console.log();
console.log(`${colors.dim}The tool automatically detects single vs monorepo based on STR_CONFIG in .env${colors.reset}`);
}
// Function to add a task to str.json
function addTask(taskName, directory, command) {
const methJsonPath = ensureMethJsonExists();
try {
// Read existing str.json
const fileContent = fs.readFileSync(methJsonPath, 'utf-8');
const methData = JSON.parse(fileContent);
// Ensure tasks object exists
if (!methData.tasks) {
methData.tasks = {};
}
// Check if task already exists
if (methData.tasks[taskName]) {
console.log(`${colors.yellow}Task "${taskName}" already exists. Overwriting...${colors.reset}`);
}
// Create task object
const taskObj = {
command: command
};
// Add directory if provided (monorepo style)
if (directory) {
taskObj.directory = directory;
}
// Add task to tasks object
methData.tasks[taskName] = taskObj;
// Write back to file with proper formatting
fs.writeFileSync(methJsonPath, JSON.stringify(methData, null, 4), 'utf-8');
console.log(`${colors.green}${colors.bright}✓ Successfully added task "${taskName}"${colors.reset}`);
// Display what was added
console.log(`${colors.dim}Task details:${colors.reset}`);
console.log(` Name: ${colors.cyan}${taskName}${colors.reset}`);
if (directory) {
console.log(` Directory: ${colors.cyan}${directory}${colors.reset}`);
}
console.log(` Command: ${colors.cyan}${command}${colors.reset}`);
} catch (err) {
console.error(`${colors.red}Failed to add task: ${err.message}${colors.reset}`);
process.exit(1);
}
}
// Function to validate task name
function validateTaskName(taskName) {
if (!taskName || taskName.trim() === '') {
console.error(`${colors.red}Error: Task name cannot be empty${colors.reset}`);
return false;
}
// Check for invalid characters
const invalidChars = /[<>:"/\\|?*\x00-\x1f]/;
if (invalidChars.test(taskName)) {
console.error(`${colors.red}Error: Task name contains invalid characters${colors.reset}`);
return false;
}
return true;
}
// Function to validate command
function validateCommand(command) {
if (!command || command.trim() === '') {
console.error(`${colors.red}Error: Command cannot be empty${colors.reset}`);
return false;
}
return true;
}
// Main function
function main() {
const args = process.argv.slice(2);
// Handle help and version flags
if (args.includes('-h') || args.includes('--help')) {
displayHelp();
return;
}
if (args.includes('-v') || args.includes('--version')) {
displayVersion();
return;
}
// Check if no arguments provided
if (args.length === 0) {
console.error(`${colors.red}Error: No arguments provided${colors.reset}\n`);
displayHelp();
process.exit(1);
}
// Detect project type
const methConfig = loadEnvConfig();
console.log(`${colors.dim}Detected project type: ${methConfig}${colors.reset}`);
let taskName, directory, command;
if (methConfig === 'monorepo') {
// Monorepo: str-add <task-name> <directory> <command>
if (args.length < 3) {
console.error(`${colors.red}Error: Monorepo projects require 3 arguments: <task-name> <directory> <command>${colors.reset}\n`);
displayHelp();
process.exit(1);
}
taskName = args[0];
directory = args[1];
command = args.slice(2).join(' '); // Join remaining args as command
} else {
// Single: str-add <task-name> <command>
if (args.length < 2) {
console.error(`${colors.red}Error: Single projects require 2 arguments: <task-name> <command>${colors.reset}\n`);
displayHelp();
process.exit(1);
}
taskName = args[0];
directory = null;
command = args.slice(1).join(' '); // Join remaining args as command
}
// Validate inputs
if (!validateTaskName(taskName)) {
process.exit(1);
}
if (!validateCommand(command)) {
process.exit(1);
}
// Add the task
addTask(taskName, directory, command);
console.log(`\n${colors.green}${colors.bright}Task added successfully!${colors.reset}`);
console.log(`${colors.dim}You can now run: ${colors.cyan}str ${taskName}${colors.reset} ${colors.dim}or use ${colors.cyan}str-ui${colors.reset} ${colors.dim}to see it in the interactive menu.${colors.reset}`);
}
// Run the main function
main();