123 lines
4.2 KiB
TypeScript
123 lines
4.2 KiB
TypeScript
/**
|
|
* Arcádia Suite - Module Migrator
|
|
*
|
|
* Executa migrações automáticas quando novos schemas são criados/aprovados.
|
|
* Após aprovação de um schema no pipeline, este utilitário:
|
|
* 1. Registra o schema no shared/schemas/index.ts
|
|
* 2. Gera e aplica a migração no PostgreSQL via Drizzle
|
|
*/
|
|
|
|
import * as fs from "fs";
|
|
import * as path from "path";
|
|
import { exec } from "child_process";
|
|
import { promisify } from "util";
|
|
|
|
const execAsync = promisify(exec);
|
|
|
|
export interface MigrationResult {
|
|
success: boolean;
|
|
moduleName: string;
|
|
schemaRegistered: boolean;
|
|
migrationApplied: boolean;
|
|
error?: string;
|
|
}
|
|
|
|
export async function registerAndMigrate(moduleName: string): Promise<MigrationResult> {
|
|
const result: MigrationResult = {
|
|
success: false,
|
|
moduleName,
|
|
schemaRegistered: false,
|
|
migrationApplied: false,
|
|
};
|
|
|
|
try {
|
|
const schemaPath = path.resolve(process.cwd(), `shared/schemas/${moduleName}.ts`);
|
|
if (!fs.existsSync(schemaPath)) {
|
|
result.error = `Schema não encontrado: shared/schemas/${moduleName}.ts`;
|
|
return result;
|
|
}
|
|
|
|
const indexPath = path.resolve(process.cwd(), "shared/schemas/index.ts");
|
|
let indexContent = fs.readFileSync(indexPath, "utf-8");
|
|
const exportLine = `export * from "./${moduleName}";`;
|
|
|
|
if (!indexContent.includes(exportLine)) {
|
|
const marker = "// === MÓDULOS REGISTRADOS ===";
|
|
if (indexContent.includes(marker)) {
|
|
indexContent = indexContent.replace(
|
|
marker,
|
|
marker + "\n" + exportLine
|
|
);
|
|
} else {
|
|
indexContent += "\n" + exportLine + "\n";
|
|
}
|
|
fs.writeFileSync(indexPath, indexContent, "utf-8");
|
|
console.log(`[Migrator] Schema registrado: ${moduleName}`);
|
|
}
|
|
result.schemaRegistered = true;
|
|
|
|
try {
|
|
console.log(`[Migrator] Aplicando migração para módulo: ${moduleName}...`);
|
|
await execAsync("npx drizzle-kit push", {
|
|
cwd: process.cwd(),
|
|
timeout: 30000,
|
|
env: { ...process.env },
|
|
});
|
|
result.migrationApplied = true;
|
|
console.log(`[Migrator] Migração aplicada com sucesso para: ${moduleName}`);
|
|
} catch (migError: any) {
|
|
console.error(`[Migrator] Erro na migração (não fatal):`, migError.message);
|
|
result.error = `Schema registrado mas migração falhou: ${migError.message}. Execute manualmente: npx drizzle-kit push`;
|
|
result.migrationApplied = false;
|
|
}
|
|
|
|
result.success = result.schemaRegistered;
|
|
return result;
|
|
} catch (error: any) {
|
|
result.error = error.message;
|
|
return result;
|
|
}
|
|
}
|
|
|
|
export async function listModuleSchemas(): Promise<string[]> {
|
|
const schemasDir = path.resolve(process.cwd(), "shared/schemas");
|
|
if (!fs.existsSync(schemasDir)) return [];
|
|
|
|
return fs.readdirSync(schemasDir)
|
|
.filter(f => f.endsWith(".ts") && !f.startsWith("_") && f !== "index.ts" && f !== "loader.ts")
|
|
.map(f => f.replace(".ts", ""));
|
|
}
|
|
|
|
export async function getModuleStatus(): Promise<Record<string, { hasSchema: boolean; hasRoutes: boolean; isRegistered: boolean }>> {
|
|
const schemasDir = path.resolve(process.cwd(), "shared/schemas");
|
|
const modulesDir = path.resolve(process.cwd(), "server/modules");
|
|
const indexPath = path.resolve(process.cwd(), "shared/schemas/index.ts");
|
|
|
|
const indexContent = fs.existsSync(indexPath) ? fs.readFileSync(indexPath, "utf-8") : "";
|
|
|
|
const schemaFiles = fs.existsSync(schemasDir)
|
|
? fs.readdirSync(schemasDir).filter(f => f.endsWith(".ts") && !f.startsWith("_") && f !== "index.ts" && f !== "loader.ts")
|
|
: [];
|
|
|
|
const routeFiles = fs.existsSync(modulesDir)
|
|
? fs.readdirSync(modulesDir).filter(f => f.endsWith(".ts") && !f.startsWith("_") && f !== "loader.ts" && f !== "migrator.ts")
|
|
: [];
|
|
|
|
const allModulesArr = [
|
|
...schemaFiles.map(f => f.replace(".ts", "")),
|
|
...routeFiles.map(f => f.replace(".ts", "")),
|
|
];
|
|
const allModules = allModulesArr.filter((v, i, a) => a.indexOf(v) === i);
|
|
|
|
const status: Record<string, { hasSchema: boolean; hasRoutes: boolean; isRegistered: boolean }> = {};
|
|
for (const mod of allModules) {
|
|
status[mod] = {
|
|
hasSchema: schemaFiles.includes(`${mod}.ts`),
|
|
hasRoutes: routeFiles.includes(`${mod}.ts`),
|
|
isRegistered: indexContent.includes(`export * from "./${mod}"`),
|
|
};
|
|
}
|
|
|
|
return status;
|
|
}
|