|
| 1 | +// setup.js |
| 2 | + |
| 3 | +const readline = require('readline'); |
| 4 | +const fs = require('fs'); |
| 5 | +const path = require('path'); |
| 6 | + |
| 7 | +// Configure readline interface for user input |
| 8 | +const rl = readline.createInterface({ |
| 9 | + input: process.stdin, |
| 10 | + output: process.stdout |
| 11 | +}); |
| 12 | + |
| 13 | +// List of files that need to be modified by the script |
| 14 | +const filesToModify = [ |
| 15 | + 'project.json', |
| 16 | + 'CONTRIBUTING.md', |
| 17 | + 'LICENSE', |
| 18 | + 'CODE_OF_CONDUCT.md', |
| 19 | + 'README.md' |
| 20 | +]; |
| 21 | + |
| 22 | +/** |
| 23 | + * Prompts the user with a question and returns their answer. |
| 24 | + * @param {string} query The question to ask the user. |
| 25 | + * @returns {Promise<string>} A promise that resolves with the user's answer. |
| 26 | + */ |
| 27 | +function askQuestion(query) { |
| 28 | + return new Promise(resolve => rl.question(query, resolve)); |
| 29 | +} |
| 30 | + |
| 31 | +/** |
| 32 | + * Collects all necessary inputs from the user. |
| 33 | + * @returns {Promise<Object>} An object containing all user inputs. |
| 34 | + */ |
| 35 | +async function getUserInputs() { |
| 36 | + console.log('\n--- Project Setup ---'); |
| 37 | + console.log('Let\'s personalize your new repository.'); |
| 38 | + console.log('Please provide the following information:'); |
| 39 | + |
| 40 | + const inputs = {}; |
| 41 | + |
| 42 | + inputs.projectName = await askQuestion('1. Enter the name for your new project (e.g., my-awesome-app): '); |
| 43 | + inputs.projectDescription = await askQuestion('2. Enter a short description for your project: '); |
| 44 | + inputs.projectKeywords = await askQuestion('3. Enter keywords for your project, separated by commas (e.g., javascript, web, utility): '); |
| 45 | + inputs.authorName = await askQuestion('4. Enter the author\'s name (e.g., John Doe or My Company Inc.): '); |
| 46 | + inputs.contactEmail = await askQuestion('5. Enter a contact email for your project (e.g., contact@yourproject.com): '); |
| 47 | + |
| 48 | + const currentYear = new Date().getFullYear(); |
| 49 | + inputs.licenseYear = (await askQuestion(`6. Enter the copyright year (default: ${currentYear}): `)) || currentYear.toString(); // Ensure string for replacement |
| 50 | + |
| 51 | + inputs.githubUsername = await askQuestion('7. Enter your GitHub username or organization name (e.g., octocat): '); |
| 52 | + inputs.codecovToken = await askQuestion('8. Enter your Codecov token (optional, leave blank if not using): '); |
| 53 | + |
| 54 | + rl.close(); // Close the readline interface after all questions are asked |
| 55 | + return inputs; |
| 56 | +} |
| 57 | + |
| 58 | +/** |
| 59 | + * Processes each file, replacing placeholders with user inputs. |
| 60 | + * @param {Object} inputs An object containing all user inputs. |
| 61 | + */ |
| 62 | +async function processFiles(inputs) { |
| 63 | + for (const file of filesToModify) { |
| 64 | + const filePath = path.join(process.cwd(), file); // Assumes script is run from repo root |
| 65 | + console.log(`\nProcessing ${file}...`); |
| 66 | + |
| 67 | + try { |
| 68 | + let content = fs.readFileSync(filePath, 'utf8'); |
| 69 | + |
| 70 | + // --- Generic Replacements (order matters if placeholders overlap) --- |
| 71 | + content = content.replace(new RegExp('{{PROJECT_NAME}}', 'g'), inputs.projectName); |
| 72 | + content = content.replace(new RegExp('{{PROJECT_DESCRIPTION}}', 'g'), inputs.projectDescription); |
| 73 | + content = content.replace(new RegExp('{{AUTHOR_NAME}}', 'g'), inputs.authorName); |
| 74 | + content = content.replace(new RegExp('{{CONTACT_EMAIL}}', 'g'), inputs.contactEmail); |
| 75 | + content = content.replace(new RegExp('{{LICENSE_YEAR}}', 'g'), inputs.licenseYear); |
| 76 | + content = content.replace(new RegExp('{{GITHUB_USERNAME}}', 'g'), inputs.githubUsername); |
| 77 | + // Handle optional Codecov token |
| 78 | + content = content.replace(new RegExp('{{CODECOV_TOKEN}}', 'g'), inputs.codecovToken || ''); |
| 79 | + |
| 80 | + |
| 81 | + // --- Specific handling for project.json --- |
| 82 | + if (file === 'project.json') { |
| 83 | + let projectJson = JSON.parse(content); |
| 84 | + |
| 85 | + // Update specific fields that might have been generically replaced but need JSON structure |
| 86 | + projectJson.name = inputs.projectName; |
| 87 | + projectJson.description = inputs.projectDescription; |
| 88 | + projectJson.author = inputs.authorName; |
| 89 | + |
| 90 | + // Handle keywords array: convert comma-separated string to array |
| 91 | + if (inputs.projectKeywords) { |
| 92 | + projectJson.keywords = inputs.projectKeywords.split(',').map(kw => kw.trim()).filter(kw => kw.length > 0); |
| 93 | + } else { |
| 94 | + projectJson.keywords = []; // Set to empty array if no keywords provided |
| 95 | + } |
| 96 | + |
| 97 | + content = JSON.stringify(projectJson, null, 2); // Pretty print JSON with 2 spaces |
| 98 | + } |
| 99 | + |
| 100 | + // --- Specific handling for README.md --- |
| 101 | + if (file === 'README.md') { |
| 102 | + // Correct the LICENSE link from LICENSE.md to LICENSE |
| 103 | + content = content.replace(/\[LICENSE\]\(LICENSE\.md\)/g, '[LICENSE](LICENSE)'); |
| 104 | + |
| 105 | + // Update the 'Post-Template Setup' section |
| 106 | + // This regex captures the section content up to the next '##' heading or end of file |
| 107 | + const postTemplateSetupRegex = /(## Post-Template Setup[\s\S]*?)(?=##|$)/; |
| 108 | + const newPostTemplateSetupContent = `## Post-Template Setup\n\nAfter creating your repository from this template, you've successfully run this setup script to personalize your project. You're ready to start building!\n\n*(This section was updated by the setup script.)*`; |
| 109 | + |
| 110 | + if (content.match(postTemplateSetupRegex)) { |
| 111 | + content = content.replace(postTemplateSetupRegex, newPostTemplateSetupContent); |
| 112 | + } else { |
| 113 | + // If the section isn't found (e.g., file was heavily modified), append it |
| 114 | + console.warn('Warning: "Post-Template Setup" section not found in README.md. Appending new content.'); |
| 115 | + content += `\n${newPostTemplateSetupContent}`; |
| 116 | + } |
| 117 | + } |
| 118 | + |
| 119 | + fs.writeFileSync(filePath, content, 'utf8'); |
| 120 | + console.log(`Successfully updated ${file}.`); |
| 121 | + |
| 122 | + } catch (error) { |
| 123 | + console.error(`Error processing ${file}: ${error.message}`); |
| 124 | + if (file === 'project.json' && error instanceof SyntaxError) { |
| 125 | + console.error('Make sure project.json is valid JSON before running the script.'); |
| 126 | + } |
| 127 | + } |
| 128 | + } |
| 129 | +} |
| 130 | + |
| 131 | +/** |
| 132 | + * Main function to run the setup script. |
| 133 | + */ |
| 134 | +async function runSetup() { |
| 135 | + try { |
| 136 | + const inputs = await getUserInputs(); |
| 137 | + await processFiles(inputs); |
| 138 | + |
| 139 | + console.log('\n--- Setup Complete! ---'); |
| 140 | + console.log('Your repository has been successfully personalized.'); |
| 141 | + console.log('Here are some quick reminders:'); |
| 142 | + console.log(`- Project Name: ${inputs.projectName}`); |
| 143 | + console.log(`- Author: ${inputs.authorName}`); |
| 144 | + console.log(`- GitHub Repository: https://github.com/${inputs.githubUsername}/${inputs.projectName}`); |
| 145 | + if (inputs.codecovToken) { |
| 146 | + console.log(`- Remember to add your Codecov token as a secret named 'CODECOV_TOKEN' in your GitHub repository settings for CI integration.`); |
| 147 | + } |
| 148 | + console.log('\n- Please review the modified files to ensure everything looks correct and fine-tune as needed.'); |
| 149 | + console.log('- Don\'t forget to initialize your Git repository if you haven\'t already!'); |
| 150 | + console.log('\nHappy coding!'); |
| 151 | + } catch (error) { |
| 152 | + console.error('\nAn unexpected error occurred during setup:', error); |
| 153 | + process.exit(1); // Exit with an error code |
| 154 | + } finally { |
| 155 | + if (!rl.closed) { |
| 156 | + rl.close(); // Ensure readline interface is closed even on error |
| 157 | + } |
| 158 | + } |
| 159 | +} |
| 160 | + |
| 161 | +// Execute the setup script |
| 162 | +runSetup(); |
0 commit comments