import fs from "node:fs"; import path from "node:path"; import { type CommandResult, success } from "../../../lib/command-result"; import { MODULE_PATHS } from "../../../lib/paths"; import { generateErrors } from "./generate-errors"; import { generateLifecycle } from "./generate-lifecycle"; import { generatePermissions } from "./generate-permissions"; import { generateCommandShell, generateQueryShell } from "./generate-shells"; import { parseCommandDoc, type ParsedCommandDoc } from "./parse-command-doc"; import { parseModelDoc, type ParsedModelDoc } from "./parse-model-doc"; import { renderCommandStub, renderCommandTestStub, renderDbStub, renderQueryStub, renderQueryTestStub, } from "./stubs"; export function runModuleGenerateCode(modulePath: string, moduleName: string): CommandResult { const libDir = path.join(modulePath, "lib"); let generated = 0; // Parse command docs const commandDocsDir = path.join(modulePath, MODULE_PATHS.docs.command); const parsedCommandDocs: ParsedCommandDoc[] = []; if (fs.existsSync(commandDocsDir)) { const mdFiles = fs.readdirSync(commandDocsDir).filter((f) => f.endsWith(".md")); for (const file of mdFiles) { const content = fs.readFileSync(path.join(commandDocsDir, file), "utf-8"); const name = path.basename(file, ".md"); parsedCommandDocs.push(parseCommandDoc(name, content)); } } // Parse query docs const queryDocsDir = path.join(modulePath, MODULE_PATHS.docs.query); const parsedQueryDocs: ParsedCommandDoc[] = []; if (fs.existsSync(queryDocsDir)) { const queryFiles = fs.readdirSync(queryDocsDir).filter((f) => f.endsWith(".md")); for (const file of queryFiles) { const content = fs.readFileSync(path.join(queryDocsDir, file), "utf-8"); const name = path.basename(file, ".md"); parsedQueryDocs.push(parseCommandDoc(name, content)); } } // Parse model docs const modelDocsDir = path.join(modulePath, MODULE_PATHS.docs.model); const parsedModelDocs: ParsedModelDoc[] = []; if (fs.existsSync(modelDocsDir)) { const modelFiles = fs.readdirSync(modelDocsDir).filter((f) => f.endsWith(".md")); for (const file of modelFiles) { const content = fs.readFileSync(path.join(modelDocsDir, file), "utf-8"); const modelName = path.basename(file, ".md"); parsedModelDocs.push(parseModelDoc(modelName, content)); } } const allDocsForErrors = [...parsedCommandDocs, ...parsedQueryDocs]; // Generate errors and permissions from command/query docs if (allDocsForErrors.length > 0) { const errorsContent = generateErrors(moduleName, allDocsForErrors); if (errorsContent) { fs.mkdirSync(libDir, { recursive: true }); const errorsFile = path.join(libDir, "errors.generated.ts"); fs.writeFileSync(errorsFile, errorsContent); console.log(` wrote ${path.relative(modulePath, errorsFile)}`); generated++; } } // Generate permissions and command files from parsed command docs if (parsedCommandDocs.length > 0) { const commands = parsedCommandDocs.map((d) => ({ name: d.commandName, permissionScope: d.permissionScope, })); fs.mkdirSync(libDir, { recursive: true }); const permissionsContent = generatePermissions(moduleName, commands); const permissionsFile = path.join(libDir, "permissions.generated.ts"); fs.writeFileSync(permissionsFile, permissionsContent); console.log(` wrote ${path.relative(modulePath, permissionsFile)}`); generated++; const commandDir = path.join(modulePath, MODULE_PATHS.commands); fs.mkdirSync(commandDir, { recursive: true }); for (const doc of parsedCommandDocs) { const shellFile = path.join(commandDir, `${doc.commandName}.generated.ts`); fs.writeFileSync(shellFile, generateCommandShell(doc)); console.log(` wrote ${path.relative(modulePath, shellFile)}`); generated++; const implFile = path.join(commandDir, `${doc.commandName}.ts`); if (!fs.existsSync(implFile)) { fs.writeFileSync(implFile, renderCommandStub(doc.commandName)); console.log(` scaffolded ${path.relative(modulePath, implFile)}`); generated++; } const testFile = path.join(commandDir, `${doc.commandName}.test.ts`); if (!fs.existsSync(testFile)) { fs.writeFileSync(testFile, renderCommandTestStub(doc.commandName)); console.log(` scaffolded ${path.relative(modulePath, testFile)}`); generated++; } } } // Generate query files from parsed query docs if (parsedQueryDocs.length > 0) { const queryDir = path.join(modulePath, MODULE_PATHS.queries); fs.mkdirSync(queryDir, { recursive: true }); for (const doc of parsedQueryDocs) { const shellFile = path.join(queryDir, `${doc.commandName}.generated.ts`); fs.writeFileSync(shellFile, generateQueryShell(doc)); console.log(` wrote ${path.relative(modulePath, shellFile)}`); generated++; const implFile = path.join(queryDir, `${doc.commandName}.ts`); if (!fs.existsSync(implFile)) { fs.writeFileSync(implFile, renderQueryStub(doc.commandName)); console.log(` scaffolded ${path.relative(modulePath, implFile)}`); generated++; } const testFile = path.join(queryDir, `${doc.commandName}.test.ts`); if (!fs.existsSync(testFile)) { fs.writeFileSync(testFile, renderQueryTestStub(doc.commandName)); console.log(` scaffolded ${path.relative(modulePath, testFile)}`); generated++; } } } // Generate db stubs and lifecycle from parsed model docs if (parsedModelDocs.length > 0) { const dbDir = path.join(modulePath, MODULE_PATHS.db); fs.mkdirSync(dbDir, { recursive: true }); for (const modelDoc of parsedModelDocs) { const camelName = modelDoc.modelName.charAt(0).toLowerCase() + modelDoc.modelName.slice(1); const dbFile = path.join(dbDir, `${camelName}.ts`); if (!fs.existsSync(dbFile)) { fs.writeFileSync(dbFile, renderDbStub(camelName)); console.log(` scaffolded ${path.relative(modulePath, dbFile)}`); generated++; } if (modelDoc.transitions.length > 0) { const lifecycleContent = generateLifecycle(modelDoc.modelName, modelDoc.transitions); const lifecycleFile = path.join(dbDir, `${camelName}.lifecycle.generated.ts`); fs.writeFileSync(lifecycleFile, lifecycleContent); console.log(` wrote ${path.relative(modulePath, lifecycleFile)}`); generated++; } } } console.log(`Generated ${generated} file(s) for ${moduleName}`); return success(); }