#!/usr/bin/env node import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; const TEMPLATE_ROOT = path.resolve( path.dirname(fileURLToPath(import.meta.url)), '..', 'templates', 'discord-oauth-vue3-vite', 'src', 'server', 'discord-oauth', ); function usage() { console.error(`Usage: node resources/scripts/scaffold-discord-oauth-vue3-vite.mjs [options] Required: --project-root Target project path. Optional: --mode Default: dry-run --frontend-origin Default: http://localhost:5173 --allowlist-discord-ids Default: empty --scopes Default: identify,email --force Overwrite generated files. --help Show this help text. `); } function parseArgs(argv) { const options = { mode: 'dry-run', frontendOrigin: 'http://localhost:5173', allowlistDiscordIds: '', scopes: 'identify,email', force: false, }; for (let index = 0; index < argv.length; index += 1) { const arg = argv[index]; if (arg === '--help') { usage(); process.exit(0); } if (arg === '--project-root') { options.projectRoot = argv[index + 1]; index += 1; continue; } if (arg === '--mode') { options.mode = argv[index + 1]; index += 1; continue; } if (arg === '--frontend-origin') { options.frontendOrigin = argv[index + 1]; index += 1; continue; } if (arg === '--allowlist-discord-ids') { options.allowlistDiscordIds = argv[index + 1]; index += 1; continue; } if (arg === '--scopes') { options.scopes = argv[index + 1]; index += 1; continue; } if (arg === '--force') { options.force = true; continue; } throw new Error(`Unknown argument: ${arg}`); } if (!options.projectRoot) { throw new Error('--project-root is required.'); } if (!['dry-run', 'apply'].includes(options.mode)) { throw new Error('--mode must be dry-run or apply.'); } options.projectRoot = path.resolve(options.projectRoot); options.targetRoot = path.join(options.projectRoot, 'src', 'server', 'discord-oauth'); options.allowlistDiscordIdsJson = JSON.stringify( options.allowlistDiscordIds .split(',') .map((entry) => entry.trim()) .filter(Boolean), ); options.scopesJson = JSON.stringify( options.scopes .split(',') .map((entry) => entry.trim()) .filter(Boolean), ); return options; } function ensureDir(dirPath, mode) { if (mode === 'dry-run') { return; } fs.mkdirSync(dirPath, { recursive: true }); } function isTextFile(filePath) { return /\.(?:js|json|md|txt|mjs|sh)$/.test(filePath); } function replacePlaceholders(content, options) { return content .replaceAll('__FRONTEND_ORIGIN__', options.frontendOrigin) .replaceAll('__ALLOWLIST_DISCORD_IDS_JSON__', options.allowlistDiscordIdsJson) .replaceAll('__SCOPES_JSON__', options.scopesJson); } function walkFiles(rootDir, callback) { const entries = fs.readdirSync(rootDir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(rootDir, entry.name); if (entry.isDirectory()) { walkFiles(fullPath, callback); continue; } callback(fullPath); } } function scaffold(options) { if (!fs.existsSync(TEMPLATE_ROOT)) { throw new Error(`Template root not found: ${TEMPLATE_ROOT}`); } const results = []; walkFiles(TEMPLATE_ROOT, (sourcePath) => { const relativePath = path.relative(TEMPLATE_ROOT, sourcePath); const targetPath = path.join(options.targetRoot, relativePath); const exists = fs.existsSync(targetPath); if (exists && !options.force) { results.push({ action: 'skipped', filePath: targetPath }); return; } const action = exists ? 'updated' : 'created'; if (options.mode === 'apply') { ensureDir(path.dirname(targetPath), options.mode); const rawContent = fs.readFileSync(sourcePath); const content = isTextFile(sourcePath) ? replacePlaceholders(rawContent.toString('utf8'), options) : rawContent; fs.writeFileSync(targetPath, content); } results.push({ action, filePath: targetPath }); }); return results; } function main() { const options = parseArgs(process.argv.slice(2)); console.log(`Target bundle: ${path.relative(options.projectRoot, options.targetRoot)}`); console.log(`Mode: ${options.mode}`); const results = scaffold(options); for (const result of results) { console.log(`${result.action.toUpperCase()}: ${path.relative(options.projectRoot, result.filePath)}`); } if (options.mode === 'dry-run') { console.log('Dry-run only. Re-run with --mode apply to write files.'); } } main();