From d99fa00980fc61695195949b33dcda7781006f90 Mon Sep 17 00:00:00 2001 From: Ralph Khreish <35776126+Crunchyman-ralph@users.noreply.github.com> Date: Thu, 17 Apr 2025 19:32:30 +0200 Subject: [PATCH] feat: improve `task-master init` (#248) * chore: fix weird bug where package.json is not upgrading its version based on current package version * feat: improve `tm init` --- .changeset/moody-pugs-grab.md | 6 + scripts/init.js | 249 +++++----------------------------- 2 files changed, 38 insertions(+), 217 deletions(-) create mode 100644 .changeset/moody-pugs-grab.md diff --git a/.changeset/moody-pugs-grab.md b/.changeset/moody-pugs-grab.md new file mode 100644 index 00000000..798134d7 --- /dev/null +++ b/.changeset/moody-pugs-grab.md @@ -0,0 +1,6 @@ +--- +'task-master-ai': patch +--- + +- Fix `task-master init` polluting codebase with new packages inside `package.json` and modifying project `README` + - Now only initializes with cursor rules, windsurf rules, mcp.json, scripts/example_prd.txt, .gitignore modifications, and `README-task-master.md` diff --git a/scripts/init.js b/scripts/init.js index 5b88f56e..1b9f4643 100755 --- a/scripts/init.js +++ b/scripts/init.js @@ -15,7 +15,6 @@ import fs from 'fs'; import path from 'path'; -import { execSync } from 'child_process'; import readline from 'readline'; import { fileURLToPath } from 'url'; import { dirname } from 'path'; @@ -179,9 +178,6 @@ function copyTemplateFile(templateName, targetPath, replacements = {}) { // Map template names to their actual source paths switch (templateName) { - case 'dev.js': - sourcePath = path.join(__dirname, 'dev.js'); - break; case 'scripts_README.md': sourcePath = path.join(__dirname, '..', 'assets', 'scripts_README.md'); break; @@ -297,61 +293,8 @@ function copyTemplateFile(templateName, targetPath, replacements = {}) { return; } - // Handle package.json - merge dependencies - if (filename === 'package.json') { - log('info', `${targetPath} already exists, merging dependencies...`); - try { - const existingPackageJson = JSON.parse( - fs.readFileSync(targetPath, 'utf8') - ); - const newPackageJson = JSON.parse(content); - - // Merge dependencies, preferring existing versions in case of conflicts - existingPackageJson.dependencies = { - ...newPackageJson.dependencies, - ...existingPackageJson.dependencies - }; - - // Add our scripts if they don't already exist - existingPackageJson.scripts = { - ...existingPackageJson.scripts, - ...Object.fromEntries( - Object.entries(newPackageJson.scripts).filter( - ([key]) => !existingPackageJson.scripts[key] - ) - ) - }; - - // Preserve existing type if present - if (!existingPackageJson.type && newPackageJson.type) { - existingPackageJson.type = newPackageJson.type; - } - - fs.writeFileSync( - targetPath, - JSON.stringify(existingPackageJson, null, 2) - ); - log( - 'success', - `Updated ${targetPath} with required dependencies and scripts` - ); - } catch (error) { - log('error', `Failed to merge package.json: ${error.message}`); - // Fallback to writing a backup of the existing file and creating a new one - const backupPath = `${targetPath}.backup-${Date.now()}`; - fs.copyFileSync(targetPath, backupPath); - log('info', `Created backup of existing package.json at ${backupPath}`); - fs.writeFileSync(targetPath, content); - log( - 'warn', - `Replaced ${targetPath} with new content (due to JSON parsing error)` - ); - } - return; - } - // Handle README.md - offer to preserve or create a different file - if (filename === 'README.md') { + if (filename === 'README-task-master.md') { log('info', `${targetPath} already exists`); // Create a separate README file specifically for this project const taskMasterReadmePath = path.join( @@ -361,7 +304,7 @@ function copyTemplateFile(templateName, targetPath, replacements = {}) { fs.writeFileSync(taskMasterReadmePath, content); log( 'success', - `Created ${taskMasterReadmePath} (preserved original README.md)` + `Created ${taskMasterReadmePath} (preserved original README-task-master.md)` ); return; } @@ -396,6 +339,30 @@ async function initializeProject(options = {}) { console.log('=================================================='); } + // Try to get project name from package.json if not provided + if (!options.name) { + const packageJsonPath = path.join(process.cwd(), 'package.json'); + try { + if (fs.existsSync(packageJsonPath)) { + const packageJson = JSON.parse( + fs.readFileSync(packageJsonPath, 'utf8') + ); + if (packageJson.name) { + log( + 'info', + `Found project name '${packageJson.name}' in package.json` + ); + options.name = packageJson.name; + } + } + } catch (error) { + log( + 'debug', + `Could not read project name from package.json: ${error.message}` + ); + } + } + // Determine if we should skip prompts based on the passed options const skipPrompts = options.yes || (options.name && options.description); if (!isSilentMode()) { @@ -414,7 +381,6 @@ async function initializeProject(options = {}) { const projectVersion = options.version || '0.1.0'; // Default from commands.js or here const authorName = options.author || 'Vibe coder'; // Default if not provided const dryRun = options.dryRun || false; - const skipInstall = options.skipInstall || false; const addAliases = options.aliases || false; if (dryRun) { @@ -429,9 +395,6 @@ async function initializeProject(options = {}) { if (addAliases) { log('info', 'Would add shell aliases for task-master'); } - if (!skipInstall) { - log('info', 'Would install dependencies'); - } return { projectName, projectDescription, @@ -447,7 +410,6 @@ async function initializeProject(options = {}) { projectDescription, projectVersion, authorName, - skipInstall, addAliases ); } else { @@ -514,9 +476,8 @@ async function initializeProject(options = {}) { return; // Added return for clarity } - // Still respect dryRun/skipInstall if passed initially even when prompting + // Still respect dryRun if passed initially even when prompting const dryRun = options.dryRun || false; - const skipInstall = options.skipInstall || false; if (dryRun) { log('info', 'DRY RUN MODE: No files will be modified'); @@ -530,9 +491,6 @@ async function initializeProject(options = {}) { if (addAliasesPrompted) { log('info', 'Would add shell aliases for task-master'); } - if (!skipInstall) { - log('info', 'Would install dependencies'); - } return { projectName, projectDescription, @@ -548,7 +506,6 @@ async function initializeProject(options = {}) { projectDescription, projectVersion, authorName, - skipInstall, // Use value from initial options addAliasesPrompted // Use value from prompt ); } catch (error) { @@ -574,7 +531,6 @@ function createProjectStructure( projectDescription, projectVersion, authorName, - skipInstall, addAliases ) { const targetDir = process.cwd(); @@ -585,105 +541,8 @@ function createProjectStructure( ensureDirectoryExists(path.join(targetDir, 'scripts')); ensureDirectoryExists(path.join(targetDir, 'tasks')); - // Define our package.json content - const packageJson = { - name: projectName.toLowerCase().replace(/\s+/g, '-'), - version: projectVersion, - description: projectDescription, - author: authorName, - type: 'module', - scripts: { - dev: 'node scripts/dev.js', - list: 'node scripts/dev.js list', - generate: 'node scripts/dev.js generate', - 'parse-prd': 'node scripts/dev.js parse-prd' - }, - dependencies: { - '@anthropic-ai/sdk': '^0.39.0', - boxen: '^8.0.1', - chalk: '^4.1.2', - commander: '^11.1.0', - 'cli-table3': '^0.6.5', - cors: '^2.8.5', - dotenv: '^16.3.1', - express: '^4.21.2', - fastmcp: '^1.20.5', - figlet: '^1.8.0', - 'fuse.js': '^7.0.0', - 'gradient-string': '^3.0.0', - helmet: '^8.1.0', - inquirer: '^12.5.0', - jsonwebtoken: '^9.0.2', - 'lru-cache': '^10.2.0', - openai: '^4.89.0', - ora: '^8.2.0' - } - }; - - // Check if package.json exists and merge if it does - const packageJsonPath = path.join(targetDir, 'package.json'); - if (fs.existsSync(packageJsonPath)) { - log('info', 'package.json already exists, merging content...'); - try { - const existingPackageJson = JSON.parse( - fs.readFileSync(packageJsonPath, 'utf8') - ); - - // Preserve existing fields but add our required ones - const mergedPackageJson = { - ...existingPackageJson, - scripts: { - ...existingPackageJson.scripts, - ...Object.fromEntries( - Object.entries(packageJson.scripts).filter( - ([key]) => - !existingPackageJson.scripts || - !existingPackageJson.scripts[key] - ) - ) - }, - dependencies: { - ...(existingPackageJson.dependencies || {}), - ...Object.fromEntries( - Object.entries(packageJson.dependencies).filter( - ([key]) => - !existingPackageJson.dependencies || - !existingPackageJson.dependencies[key] - ) - ) - } - }; - - // Ensure type is set if not already present - if (!mergedPackageJson.type && packageJson.type) { - mergedPackageJson.type = packageJson.type; - } - - fs.writeFileSync( - packageJsonPath, - JSON.stringify(mergedPackageJson, null, 2) - ); - log('success', 'Updated package.json with required fields'); - } catch (error) { - log('error', `Failed to merge package.json: ${error.message}`); - // Create a backup before potentially modifying - const backupPath = `${packageJsonPath}.backup-${Date.now()}`; - fs.copyFileSync(packageJsonPath, backupPath); - log('info', `Created backup of existing package.json at ${backupPath}`); - fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2)); - log( - 'warn', - 'Created new package.json (backup of original file was created)' - ); - } - } else { - // If package.json doesn't exist, create it - fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2)); - log('success', 'Created package.json'); - } - // Setup MCP configuration for integration with Cursor - setupMCPConfiguration(targetDir, packageJson.name); + setupMCPConfiguration(targetDir, projectName); // Copy template files with replacements const replacements = { @@ -731,15 +590,6 @@ function createProjectStructure( // Copy .windsurfrules copyTemplateFile('windsurfrules', path.join(targetDir, '.windsurfrules')); - // Copy scripts/dev.js - copyTemplateFile('dev.js', path.join(targetDir, 'scripts', 'dev.js')); - - // Copy scripts/README.md - copyTemplateFile( - 'scripts_README.md', - path.join(targetDir, 'scripts', 'README.md') - ); - // Copy example_prd.txt copyTemplateFile( 'example_prd.txt', @@ -749,43 +599,13 @@ function createProjectStructure( // Create main README.md copyTemplateFile( 'README-task-master.md', - path.join(targetDir, 'README.md'), + path.join(targetDir, 'README-task-master.md'), replacements ); - // Initialize git repository if git is available - try { - if (!fs.existsSync(path.join(targetDir, '.git'))) { - log('info', 'Initializing git repository...'); - execSync('git init', { stdio: 'ignore' }); - log('success', 'Git repository initialized'); - } - } catch (error) { - log('warn', 'Git not available, skipping repository initialization'); - } - - // Run npm install automatically - if (!isSilentMode()) { - console.log( - boxen(chalk.cyan('Installing dependencies...'), { - padding: 0.5, - margin: 0.5, - borderStyle: 'round', - borderColor: 'blue' - }) - ); - } - - try { - if (!skipInstall) { - execSync('npm install', { stdio: 'inherit', cwd: targetDir }); - log('success', 'Dependencies installed successfully!'); - } else { - log('info', 'Dependencies installation skipped'); - } - } catch (error) { - log('error', 'Failed to install dependencies:', error.message); - log('error', 'Please run npm install manually'); + // Add shell aliases if requested + if (addAliases) { + addShellAliases(); } // Display success message @@ -807,11 +627,6 @@ function createProjectStructure( ); } - // Add shell aliases if requested - if (addAliases) { - addShellAliases(); - } - // Display next steps in a nice box if (!isSilentMode()) { console.log(