Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
195 changes: 195 additions & 0 deletions setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
#!/usr/bin/env node

const fs = require('fs');
const readline = require('readline');
const path = require('path');

const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});

const FILES_TO_PROCESS = ['README.md', 'CONTRIBUTING.md'];
const isDryRun = process.argv.includes('--dry-run');

const log = {
info: (msg) => console.log(`\x1b[36m[INFO]\x1b[0m ${msg}`),
success: (msg) => console.log(`\x1b[32m[SUCCESS]\x1b[0m ${msg}`),
warning: (msg) => console.log(`\x1b[33m[WARNING]\x1b[0m ${msg}`),
error: (msg) => console.log(`\x1b[31m[ERROR]\x1b[0m ${msg}`)
};
Comment on lines +15 to +20
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Use console.error for error logging.

Error messages should be written to stderr, not stdout. This follows Node.js conventions and allows proper stream separation when piping output.

♻️ Suggested fix
 const log = {
     info: (msg) => console.log(`\x1b[36m[INFO]\x1b[0m ${msg}`),
     success: (msg) => console.log(`\x1b[32m[SUCCESS]\x1b[0m ${msg}`),
-    warning: (msg) => console.log(`\x1b[33m[WARNING]\x1b[0m ${msg}`),
-    error: (msg) => console.log(`\x1b[31m[ERROR]\x1b[0m ${msg}`)
+    warning: (msg) => console.warn(`\x1b[33m[WARNING]\x1b[0m ${msg}`),
+    error: (msg) => console.error(`\x1b[31m[ERROR]\x1b[0m ${msg}`)
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const log = {
info: (msg) => console.log(`\x1b[36m[INFO]\x1b[0m ${msg}`),
success: (msg) => console.log(`\x1b[32m[SUCCESS]\x1b[0m ${msg}`),
warning: (msg) => console.log(`\x1b[33m[WARNING]\x1b[0m ${msg}`),
error: (msg) => console.log(`\x1b[31m[ERROR]\x1b[0m ${msg}`)
};
const log = {
info: (msg) => console.log(`\x1b[36m[INFO]\x1b[0m ${msg}`),
success: (msg) => console.log(`\x1b[32m[SUCCESS]\x1b[0m ${msg}`),
warning: (msg) => console.warn(`\x1b[33m[WARNING]\x1b[0m ${msg}`),
error: (msg) => console.error(`\x1b[31m[ERROR]\x1b[0m ${msg}`)
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@setup.js` around lines 15 - 20, Replace the current log.error implementation
so errors are written to stderr instead of stdout: update the log object
(symbol: log) and specifically the error method (log.error) to call
console.error with the same formatted message rather than console.log; keep the
ANSI coloring and message format unchanged so other methods (info, success,
warning) remain intact.


const promptUser = (question) => {
return new Promise((resolve) => {
const ask = () => {
rl.question(`\x1b[36m?\x1b[0m ${question}: `, (answer) => {
const trimmed = answer.trim();
if (trimmed) {
resolve(trimmed);
} else {
log.error('Input cannot be empty. Please try again.');
ask();
}
});
};
ask();
});
};

const confirmAction = (question) => {
return new Promise((resolve) => {
const ask = () => {
rl.question(`\x1b[36m?\x1b[0m ${question} (y/n): `, (answer) => {
const trimmed = answer.trim().toLowerCase();
if (trimmed === 'y' || trimmed === 'n') {
resolve(trimmed === 'y');
} else {
log.error('Invalid input. Please enter "y" or "n".');
ask();
}
});
};
ask();
});
};

const hasPlaceholders = (content) => {
return /TODO:\s*Project\s*Name/gi.test(content) ||
/TODO:\s*Project\s*Description/gi.test(content);
};

const replacePlaceholders = (content, data) => {
let newContent = content;
newContent = newContent.replace(/TODO:\s*Project\s*Name/gi, data.projectName);
newContent = newContent.replace(/TODO:\s*Project\s*Description/gi, data.projectDescription);
return newContent;
};
Comment on lines +61 to +66
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

User input may contain special replacement patterns causing unexpected output.

In JavaScript's String.replace(), the replacement string interprets special patterns like $& (matched substring), $' (portion after match), $` (portion before match), and $$ (literal $). If a user enters a project name or description containing these patterns, the replacement will produce incorrect results.

Example: If user enters "My $& Project" as the name, the output would be "My TODO: Project Name Project".

🐛 Proposed fix using replacement function
 const replacePlaceholders = (content, data) => {
     let newContent = content;
-    newContent = newContent.replace(/TODO:\s*Project\s*Name/gi, data.projectName);
-    newContent = newContent.replace(/TODO:\s*Project\s*Description/gi, data.projectDescription);
+    newContent = newContent.replace(/TODO:\s*Project\s*Name/gi, () => data.projectName);
+    newContent = newContent.replace(/TODO:\s*Project\s*Description/gi, () => data.projectDescription);
     return newContent;
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@setup.js` around lines 61 - 66, The replacePlaceholders function is
vulnerable because String.replace treats $-patterns in the replacement string;
change both .replace calls to use a replacement function (e.g.,
.replace(/TODO:\s*Project\s*Name/gi, () => data.projectName) and
.replace(/TODO:\s*Project\s*Description/gi, () => data.projectDescription)) so
user-provided projectName/projectDescription are inserted literally and special
$ sequences are not interpreted.


const processFile = (filePath, data) => {
const absolutePath = path.join(process.cwd(), filePath);

if (!fs.existsSync(absolutePath)) {
log.warning(`File not found, skipping: ${filePath}`);
return false;
}

try {
const content = fs.readFileSync(absolutePath, 'utf8');

if (!hasPlaceholders(content)) {
log.info('File already initialized, skipping');
return false;
}

const newContent = replacePlaceholders(content, data);

if (content !== newContent) {
if (!isDryRun) {
fs.writeFileSync(`${absolutePath}.bak`, content, 'utf8');
log.info(`Created backup: ${filePath}.bak`);
fs.writeFileSync(absolutePath, newContent, 'utf8');
}
return true;
}
return false;
} catch (error) {
log.error(`Failed to process ${filePath}: ${error.message}`);
return false;
}
};

const validateCompletion = () => {
let hasRemainingTodos = false;

FILES_TO_PROCESS.forEach((filePath) => {
const absolutePath = path.join(process.cwd(), filePath);
if (!fs.existsSync(absolutePath)) return;

try {
const content = fs.readFileSync(absolutePath, 'utf8');
if (hasPlaceholders(content)) {
log.warning(`Remaining placeholder found in ${filePath}`);
hasRemainingTodos = true;
}
} catch (error) {
log.error(`Failed to validate ${filePath}: ${error.message}`);
hasRemainingTodos = true;
}
});

if (!hasRemainingTodos) {
log.success('No remaining TODOs detected in processed files.');
}

return !hasRemainingTodos;
};

const main = async () => {
try {
log.info('Template Initialization System Started');
if (isDryRun) {
log.info('Running in DRY RUN mode. No files will be modified.');
}

const projectName = await promptUser('Enter project name');
const projectDescription = await promptUser('Enter project description');

let techStack;
while (true) {
techStack = (await promptUser('Enter tech stack (node/python)')).toLowerCase();
if (techStack === 'node' || techStack === 'python') break;
log.error('Invalid choice. Please enter either "node" or "python".');
}
Comment on lines +137 to +142
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if template files contain any tech stack related placeholders
rg -i "TODO.*tech|TODO.*stack" README.md CONTRIBUTING.md 2>/dev/null || echo "No tech stack placeholders found"

Repository: AOSSIE-Org/Template-Repo

Length of output: 100


🏁 Script executed:

# Find all template files and search for placeholders more broadly
find . -type f \( -name "*.template" -o -name "*.tmpl" -o -name "*.hbs" \) | head -20

Repository: AOSSIE-Org/Template-Repo

Length of output: 50


🏁 Script executed:

# Search for placeholder patterns across the entire codebase
rg -E "{{|TODO|FIXME|\$\{" --type-list | head -5 && rg -E "{{|TODO|FIXME" . --max-count=20 2>/dev/null | head -30

Repository: AOSSIE-Org/Template-Repo

Length of output: 177


🏁 Script executed:

# Read setup.js around the techStack usage lines to understand its purpose
cat -n setup.js | sed -n '130,180p'

Repository: AOSSIE-Org/Template-Repo

Length of output: 2409


🏁 Script executed:

# Find and read the processFile function
rg -n "function processFile|const processFile|processFile\s*=" setup.js -A 30

Repository: AOSSIE-Org/Template-Repo

Length of output: 1132


🏁 Script executed:

# Also search for any placeholder patterns in actual template files or content files
find . -type f \( -name "*.md" -o -name "*.json" -o -name "*.txt" \) -exec grep -l "projectName\|projectDescription\|techStack" {} \; 2>/dev/null | head -20

Repository: AOSSIE-Org/Template-Repo

Length of output: 50


🏁 Script executed:

# Find and read the replacePlaceholders function
rg -n "function replacePlaceholders|const replacePlaceholders|replacePlaceholders\s*=" setup.js -A 30

Repository: AOSSIE-Org/Template-Repo

Length of output: 1241


🏁 Script executed:

# Check FILES_TO_PROCESS to see which files are actually being modified
rg -n "FILES_TO_PROCESS" setup.js -B 5 -A 5

Repository: AOSSIE-Org/Template-Repo

Length of output: 1711


Tech stack input is collected but not used in placeholder replacement.

The techStack value is passed to processFile() but the replacePlaceholders() function only processes projectName and projectDescription placeholders. It ignores the techStack parameter entirely, using the value only for conditional guidance messages (lines 170-174). Either remove the unnecessary techStack collection, or extend replacePlaceholders() to support tech-stack-specific placeholders if that's intended.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@setup.js` around lines 137 - 142, The techStack value collected by promptUser
is passed into processFile but never used by replacePlaceholders, so update
replacePlaceholders to accept and apply tech-stack-specific replacements (e.g.,
replace tokens like {{techStack}} or {{isNode}}/{{isPython}}) or remove passing
techStack from processFile and the prompt; specifically, modify
replacePlaceholders(signature and implementation) to take a third parameter
techStack and perform substitution logic for {{techStack}} (and any conditional
tokens used for guidance), then ensure processFile calls
replacePlaceholders(projectName, projectDescription, techStack) and that any
template placeholders referenced in the repo match these token names.


const data = { projectName, projectDescription, techStack };

log.info('--- SUMMARY OF CHANGES ---');
log.info(`Project Name : ${data.projectName}`);
log.info(`Project Description : ${data.projectDescription}`);
log.info(`Tech Stack : ${data.techStack}`);
log.info(`Target Files : ${FILES_TO_PROCESS.join(', ')}`);

const proceed = await confirmAction('Proceed with these changes?');
if (!proceed) {
log.info('Operation cancelled by user.');
rl.close();
process.exit(0);
}

let modifiedCount = 0;
FILES_TO_PROCESS.forEach((file) => {
if (processFile(file, data)) {
log.success(`${isDryRun ? '[DRY RUN] ' : ''}Successfully updated: ${file}`);
modifiedCount++;
}
});

if (modifiedCount === 0) {
log.info('No files required modifications.');
} else {
if (data.techStack === 'node') {
log.info('Node.js stack selected. Run "npm install" to configure dependencies.');
} else {
log.info('Python stack selected. Run "pip install -r requirements.txt" to configure dependencies.');
}
}

const isSuccess = validateCompletion();

if (isSuccess) {
log.success('Initialization complete!');
rl.close();
process.exit(0);
} else {
log.error('Initialization completed with warnings.');
rl.close();
process.exit(1);
}
} catch (error) {
log.error(`An unexpected error occurred: ${error.message}`);
rl.close();
process.exit(1);
}
};

main();
Loading