Replaces nginx.conf CSP map with inline policy and updates the policy for development. Adds new dependencies including Mantine, Radix, Tabler, FontAwesome, and others. Removes the fetch-openapi.js script and introduces generate-choice-labels.cjs to auto-generate TypeScript choice label constants from Orval enums, updating the api:gen script to run this generator. Also updates orval and other dev dependencies, and makes minor formatting changes in orval.config.ts.
213 lines
6.1 KiB
JavaScript
213 lines
6.1 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
/**
|
|
* Generates TypeScript file with choice labels from Orval-generated enum files
|
|
*
|
|
* Usage: node scripts/generate-choice-labels.js
|
|
*
|
|
* This script reads the TypeScript enum files generated by Orval and extracts
|
|
* choice labels from JSDoc comments (format: * `value` - Label) and generates
|
|
* a TypeScript file with runtime-accessible label mappings.
|
|
*/
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
// Configuration
|
|
const MODELS_DIRS = [
|
|
path.join(__dirname, '../src/api/generated/private/models'),
|
|
path.join(__dirname, '../src/api/generated/public/models')
|
|
];
|
|
const OUTPUT_PATH = path.join(__dirname, '../src/constants/choiceLabels.ts');
|
|
|
|
// Convert camelCase to UPPER_SNAKE_CASE
|
|
function camelToSnakeCase(str) {
|
|
return str
|
|
.replace(/([A-Z])/g, '_$1')
|
|
.replace(/^_/, '') // Remove leading underscore
|
|
.toUpperCase();
|
|
}
|
|
|
|
// Extract enum labels from TypeScript file comments
|
|
function parseEnumFile(filePath) {
|
|
const content = fs.readFileSync(filePath, 'utf8');
|
|
const filename = path.basename(filePath, '.ts');
|
|
|
|
// Extract all JSDoc comment blocks
|
|
// Match all: /** ... */
|
|
const commentRegex = /\/\*\*\s*\n([\s\S]*?)\*\//g;
|
|
const comments = [];
|
|
let match;
|
|
|
|
while ((match = commentRegex.exec(content)) !== null) {
|
|
comments.push(match[1]);
|
|
}
|
|
|
|
if (comments.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
// Try to find labels in all comment blocks
|
|
// Parse labels from comment lines like: * `value` - Label
|
|
// Note: The asterisk might have 0 or more spaces after it
|
|
const labelPattern = /\*\s*`([^`]+)`\s*-\s*(.+)/g;
|
|
const labels = {};
|
|
|
|
for (const comment of comments) {
|
|
let labelMatch;
|
|
while ((labelMatch = labelPattern.exec(comment)) !== null) {
|
|
const [, value, label] = labelMatch;
|
|
labels[value] = label.trim();
|
|
}
|
|
}
|
|
|
|
// Only return if we found labels
|
|
if (Object.keys(labels).length === 0) {
|
|
return null;
|
|
}
|
|
|
|
// Extract enum name from the type definition
|
|
// Match: export type EnumName = ...
|
|
const typeMatch = content.match(/export\s+type\s+(\w+Enum)\s*=/);
|
|
const enumName = typeMatch ? typeMatch[1] : null;
|
|
|
|
if (!enumName) {
|
|
return null;
|
|
}
|
|
|
|
return { enumName, labels };
|
|
}
|
|
|
|
// Main function
|
|
function generateLabels() {
|
|
console.log('🔍 Scanning Orval-generated enum files...');
|
|
|
|
const enums = {};
|
|
let totalEnumFiles = 0;
|
|
|
|
// Check each models directory
|
|
for (const MODELS_DIR of MODELS_DIRS) {
|
|
const dirName = path.basename(path.dirname(MODELS_DIR));
|
|
|
|
if (!fs.existsSync(MODELS_DIR)) {
|
|
console.warn(`⚠️ Models directory not found: ${MODELS_DIR}`);
|
|
continue;
|
|
}
|
|
|
|
console.log(`📁 Scanning ${dirName}/models...`);
|
|
|
|
// Read all enum files
|
|
const files = fs.readdirSync(MODELS_DIR);
|
|
const enumFiles = files.filter(f => f.endsWith('Enum.ts'));
|
|
|
|
console.log(` Found ${enumFiles.length} enum files`);
|
|
totalEnumFiles += enumFiles.length;
|
|
|
|
// Parse each enum file
|
|
for (const file of enumFiles) {
|
|
const filePath = path.join(MODELS_DIR, file);
|
|
const result = parseEnumFile(filePath);
|
|
|
|
if (result) {
|
|
// Check if enum already exists from another directory
|
|
if (enums[result.enumName]) {
|
|
// Compare labels to see if they're the same
|
|
const existing = JSON.stringify(enums[result.enumName]);
|
|
const current = JSON.stringify(result.labels);
|
|
|
|
if (existing !== current) {
|
|
console.warn(` ⚠️ ${result.enumName}: Different labels found in ${dirName}, keeping first version`);
|
|
}
|
|
} else {
|
|
enums[result.enumName] = result.labels;
|
|
console.log(` ✓ ${result.enumName}: ${Object.keys(result.labels).length} labels`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (totalEnumFiles === 0) {
|
|
console.error('❌ No enum files found in any models directory');
|
|
console.error(' Run "npm run api:gen" first to generate API client');
|
|
process.exit(1);
|
|
}
|
|
|
|
if (Object.keys(enums).length === 0) {
|
|
console.error('❌ No enum labels found in generated files');
|
|
process.exit(1);
|
|
}
|
|
|
|
// Generate TypeScript file
|
|
console.log('📝 Generating TypeScript file...');
|
|
|
|
const tsContent = `/**
|
|
* Auto-generated choice labels from Orval-generated enum files
|
|
* Generated by: scripts/generate-choice-labels.cjs
|
|
*
|
|
* DO NOT EDIT MANUALLY - Run \`npm run api:gen\` to regenerate
|
|
*/
|
|
|
|
${Object.entries(enums).map(([enumName, labels]) => {
|
|
const enumBaseName = enumName.replace(/Enum$/, '');
|
|
const constantName = camelToSnakeCase(enumBaseName) + '_LABELS';
|
|
|
|
return `/**
|
|
* Labels for ${enumName}
|
|
* ${Object.entries(labels).map(([value, label]) => `* ${value}: ${label}`).join('\n * ')}
|
|
*/
|
|
export const ${constantName} = ${JSON.stringify(labels, null, 2)} as const;
|
|
`;
|
|
}).join('\n')}
|
|
/**
|
|
* Type-safe helper to get choice label
|
|
*/
|
|
export function getChoiceLabel<T extends string>(
|
|
labels: Record<string, string>,
|
|
value: T | undefined | null
|
|
): string {
|
|
if (!value) return '';
|
|
return labels[value] || value;
|
|
}
|
|
|
|
/**
|
|
* Get options array for select dropdowns
|
|
*/
|
|
export function getChoiceOptions<T extends string>(
|
|
labels: Record<T, string>
|
|
): Array<{ value: T; label: string }> {
|
|
return Object.entries(labels).map(([value, label]) => ({
|
|
value: value as T,
|
|
label: label as string,
|
|
}));
|
|
}
|
|
|
|
// Auto-generate all OPTIONS exports
|
|
${Object.entries(enums).map(([enumName]) => {
|
|
const enumBaseName = enumName.replace(/Enum$/, '');
|
|
const labelsConstName = camelToSnakeCase(enumBaseName) + '_LABELS';
|
|
const optionsConstName = camelToSnakeCase(enumBaseName) + '_OPTIONS';
|
|
return `export const ${optionsConstName} = getChoiceOptions(${labelsConstName});`;
|
|
}).join('\n')}
|
|
`;
|
|
|
|
// Ensure output directory exists
|
|
const outputDir = path.dirname(OUTPUT_PATH);
|
|
if (!fs.existsSync(outputDir)) {
|
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
}
|
|
|
|
// Write file
|
|
fs.writeFileSync(OUTPUT_PATH, tsContent, 'utf8');
|
|
|
|
console.log(`✅ Generated ${OUTPUT_PATH}`);
|
|
console.log(` Found ${Object.keys(enums).length} enum types with labels`);
|
|
}
|
|
|
|
// Run the script
|
|
try {
|
|
generateLabels();
|
|
} catch (error) {
|
|
console.error('❌ Error generating choice labels:', error.message);
|
|
process.exit(1);
|
|
}
|