206 lines
4.9 KiB
JavaScript
Executable File
206 lines
4.9 KiB
JavaScript
Executable File
#!/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 <path> Target project path.
|
|
|
|
Optional:
|
|
--mode <dry-run|apply> Default: dry-run
|
|
--frontend-origin <url> Default: http://localhost:5173
|
|
--allowlist-discord-ids <csv> Default: empty
|
|
--scopes <csv> 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();
|