Add AID export: OpenAPI from api-format and app generator bundle

Nest: POST /aid/export/openapi, POST /aid/export/app. Tools: api-format-to-openapi CLI. Generator: --print-bundle-json. Optional env: AID_EXPORT_API_KEY, AID_GENERATOR_ALLOW_APPLY.
This commit is contained in:
time_
2026-03-19 16:49:27 +03:00
parent 5b8d8a85c4
commit 2bc1aea56a
14 changed files with 895 additions and 0 deletions

View File

@@ -95,6 +95,19 @@ Open the Vite URL in a browser; the React Admin app should load and use the back
---
## Optional: api-format → OpenAPI 3.0
Experimental tooling lives in `tools/api-format-to-openapi/`: a sample **domain api-format** JSON, a **prompt** for LLM conversion, and `convert.mjs` (**deterministic** mapping for `apiFormatVersion: "1"` or **`--mode llm`** via OpenAI). See `tools/api-format-to-openapi/README.md`.
```bash
cd tools/api-format-to-openapi
node convert.mjs --in examples/api-format.example.json --out ../../openapi.generated.json
```
**AID / HTTP:** Nest exposes `POST /aid/export/openapi` and `POST /aid/export/app` (see `server/src/aid-export/README.md`). CLI bundle: `node generation/generate.mjs --print-bundle-json --dsl <file.dsl>`.
---
# Summary
| Step | Command / location |

View File

@@ -519,9 +519,41 @@ function ensureClientApp(apply, frontendResources) {
});
}
/** Собирает файлы как при --apply, без записи. Учитывает текущие app.module.ts и App.tsx на диске. */
function collectGeneratedBundle(parsed) {
const files = {};
const prismaPath = path.join(ROOT, 'server/prisma/schema.prisma');
const pr = ensurePrismaSchema(parsed, prismaPath, false);
files['server/prisma/schema.prisma'] = pr.content;
const backendModules = [];
const frontendResources = [];
for (const [entityName, ent] of Object.entries(parsed.entities)) {
const pk = ent.primaryKey;
const resource = pluralize(toKebab(entityName));
const be = renderBackendModule(entityName, ent, resource, pk);
const fe = renderFrontendResource(entityName, ent, resource, pk, parsed.enums);
backendModules.push(be);
frontendResources.push(fe);
Object.assign(files, be.files, fe.files);
}
const appMod = ensureAppModule(false, backendModules);
files['server/src/app.module.ts'] = appMod.content;
const clientApp = ensureClientApp(false, frontendResources);
files['client/src/App.tsx'] = clientApp.content;
return {
entityCount: Object.keys(parsed.entities).length,
enumCount: Object.keys(parsed.enums).length,
files,
};
}
function main() {
const args = process.argv.slice(2);
const apply = args.includes('--apply');
const printBundleJson = args.includes('--print-bundle-json');
const dslArgIdx = args.indexOf('--dsl');
const dslPath = dslArgIdx >= 0 ? args[dslArgIdx + 1] : 'examples/TOiR.domain.dsl';
@@ -529,6 +561,12 @@ function main() {
const dslText = readFile(absDsl);
const parsed = parseDomainDSL(dslText);
if (printBundleJson) {
const bundle = collectGeneratedBundle(parsed);
process.stdout.write(JSON.stringify(bundle));
return;
}
// Prisma schema
const prismaPath = path.join(ROOT, 'server/prisma/schema.prisma');
ensurePrismaSchema(parsed, prismaPath, apply);