#!/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();