Add shared port registry workflow and improve scaffold tooling
This commit is contained in:
193
resources/scripts/scaffold-discord-oauth-vue3-vite.mjs
Executable file
193
resources/scripts/scaffold-discord-oauth-vue3-vite.mjs
Executable file
@@ -0,0 +1,193 @@
|
||||
#!/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();
|
||||
Reference in New Issue
Block a user