vibecode-party-starter
Version:
A Next.js starter project for vibecoding Saas apps with auth, payments, email, and more
225 lines (199 loc) • 7.96 kB
text/typescript
import fs from 'fs/promises';
import path from 'path';
import ignore from 'ignore';
import type { Dirent } from 'fs'; // Import Dirent type
// --- Configuration ---
// Directories to exclude entirely
const EXCLUDED_DIRS: Set<string> = new Set([
'node_modules',
'.git',
'.next',
'dist',
'build',
'out',
'coverage',
'.vscode',
'.idea',
'public', // Often contains large assets, adjust if needed
// Add any other directories you want to skip
]);
// Specific files or patterns to exclude
const EXCLUDED_FILES_PATTERNS: string[] = [
'package-lock.json',
'yarn.lock',
'pnpm-lock.yaml',
'.DS_Store',
// Add any specific files or patterns
];
// File extensions or specific filenames to include (add more as needed)
const INCLUDED_EXTENSIONS: Set<string> = new Set([
// Extensions
'.js',
'.jsx',
'.ts',
'.tsx',
'.mjs',
'.cjs',
'.css',
'.scss',
'.sass',
'.less',
'.html',
'.md',
'.json',
'.yaml',
'.yml',
'.sh',
'.env',
'.env.local',
'.env.development',
'.env.production',
'.env.example', // Often useful for context
'.gitignore', // Useful for context
'.npmrc',
// Specific filenames (often config files)
'next.config.js',
'next.config.mjs',
'postcss.config.js',
'tailwind.config.js',
'tailwind.config.ts',
'tsconfig.json',
'jsconfig.json',
'.eslintrc.json',
'.prettierrc',
'Dockerfile',
// Add specific filenames relevant to your project
]);
// Initialize gitignore
let ig = ignore();
async function loadGitignore(projectRoot: string): Promise<void> {
try {
const gitignorePath = path.join(projectRoot, '.gitignore');
const gitignoreContent = await fs.readFile(gitignorePath, 'utf-8');
ig = ignore().add(gitignoreContent);
} catch (err: unknown) {
if (err && typeof err === 'object' && 'code' in err && err.code === 'ENOENT') {
console.log('No .gitignore file found, continuing without it');
} else if (err instanceof Error) {
console.error('Error reading .gitignore:', err.message);
}
}
}
// --- Helper Functions ---
function isExcluded(entryPath: string, entryName: string, isDirectory: boolean): boolean {
// Check gitignore patterns first
const relativePath = entryPath.replace(/\\/g, '/'); // Normalize path separators
if (ig.ignores(relativePath)) {
return true;
}
// Then check our manual exclusions
if (isDirectory && EXCLUDED_DIRS.has(entryName)) {
return true;
}
if (!isDirectory && EXCLUDED_FILES_PATTERNS.some(pattern => entryName === pattern)) {
// Add more complex pattern matching here if needed (e.g., regex)
return true;
}
// Check if the path contains an excluded directory component
const pathParts = entryPath.split(path.sep);
if (pathParts.some(part => EXCLUDED_DIRS.has(part))) {
return true;
}
return false;
}
function isIncluded(entryName: string): boolean {
const ext = path.extname(entryName).toLowerCase();
// Check by specific name first, then by extension
return INCLUDED_EXTENSIONS.has(entryName) || (ext !== '' && INCLUDED_EXTENSIONS.has(ext));
}
async function walkDir(dir: string, projectRoot: string, allContents: string[]): Promise<void> {
let entries: Dirent[];
try {
entries = await fs.readdir(dir, { withFileTypes: true });
} catch (err: unknown) {
if (err instanceof Error) {
console.error(`Error reading directory ${dir}: ${err.message}`);
}
return; // Skip directories we can't read
}
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
const relativePath = path.relative(projectRoot, fullPath);
if (isExcluded(relativePath, entry.name, entry.isDirectory())) {
console.log(`(-) Excluding: ${relativePath}`);
continue;
}
if (entry.isDirectory()) {
await walkDir(fullPath, projectRoot, allContents);
} else if (entry.isFile() && isIncluded(entry.name)) {
try {
console.log(`(+) Including: ${relativePath}`);
const content = await fs.readFile(fullPath, 'utf-8');
// Normalize line endings to prevent excessive diffs if files have mixed endings
const normalizedContent = content.replace(/\r\n/g, '\n');
// Directory structure (relative, split by path.sep, joined by /)
const dirStructure = path.dirname(relativePath).split(path.sep).filter(Boolean).join('/');
// Reference comment block
const referenceBlock = `/*\n File: ${relativePath}\n Absolute: ${fullPath}\n Directory: ${dirStructure || '.'}\n*/\n`;
allContents.push(`${referenceBlock}\n--- File: ${relativePath} ---\n\n${normalizedContent}\n\n`);
} catch (err: unknown) {
if (err instanceof Error) {
console.error(`Error reading file ${fullPath}: ${err.message}`);
// Directory structure for error case
const dirStructure = path.dirname(relativePath).split(path.sep).filter(Boolean).join('/');
const referenceBlock = `/*\n File: ${relativePath}\n Absolute: ${fullPath}\n Directory: ${dirStructure || '.'}\n*/\n`;
allContents.push(`${referenceBlock}\n--- File: ${relativePath} ---\n\n!!! Error reading file: ${err.message} !!!\n\n`);
}
}
} else {
// Optional: Log files that are neither excluded nor included
// console.log(`(?) Skipping (not included): ${relativePath}`);
}
}
}
// --- Main Execution ---
async function main(): Promise<void> {
// Basic argument parsing: tsx scripts/bundle-code.ts [targetDir] [outputFile]
const args: string[] = process.argv.slice(2);
const targetDirArg: string | undefined = args[0];
const outputFileArg: string | undefined = args[1];
const targetDir: string = path.resolve(targetDirArg || '.'); // Default to current directory
const outputFilePath: string | null = outputFileArg ? path.resolve(outputFileArg) : null; // Default to console output
// Load .gitignore before processing
await loadGitignore(targetDir);
console.log(`Scanning directory: ${targetDir}`);
if (outputFilePath) {
console.log(`Output will be written to: ${outputFilePath}`);
} else {
console.log(`Output will be printed to console.`);
}
const allContents: string[] = [];
try {
// Check if target directory exists
const stats = await fs.stat(targetDir);
if (!stats.isDirectory()) {
throw new Error(`Target path is not a directory: ${targetDir}`);
}
await walkDir(targetDir, targetDir, allContents);
const combinedOutput: string = allContents.join('');
if (outputFilePath) {
await fs.writeFile(outputFilePath, combinedOutput);
console.log(`\n✅ Successfully wrote bundled code to ${outputFilePath}`);
} else {
console.log("\n--- BUNDLED CODE OUTPUT ---");
console.log(combinedOutput);
console.log("--- END BUNDLED CODE OUTPUT ---");
console.log(`\n✅ Successfully generated bundled code.`);
}
} catch (err: unknown) {
if (err && typeof err === 'object' && 'code' in err && err.code === 'ENOENT') {
console.error(`\n❌ Error: Target directory not found: ${targetDir}`);
} else if (err instanceof Error) {
console.error(`\n❌ An error occurred: ${err.message}`);
console.error(err.stack); // Print stack trace for debugging
}
process.exit(1); // Exit with error code
}
}
main();