@aventara/cli 1.0.0-rc.1 → 1.0.0-rc.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,391 +1,12 @@
1
- import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
2
- import path from "node:path";
3
- import { lowestVersionOf } from "../../catalog/range.reader.js";
4
- import { EXPOSURE_DOCS } from "../docs.links.js";
5
- import { SCAFFOLD_API, } from "../template.registry.js";
6
- const CLIENT_OUTPUT = "src/generated/prisma";
7
- const SCHEMA = "prisma/schema.prisma";
8
- const CONFIG_FILES = ["prisma7.config.ts", "prisma.config.ts"];
9
- const DRIVER_PREFIX = "@prisma/adapter-";
10
- const NEW_SERVICE = {
11
- file: "src/prisma.service.ts",
12
- exportName: "PrismaService",
13
- };
14
- const NEW_MODULE = {
15
- file: "src/prisma.service.ts",
16
- exportName: "PrismaModule",
17
- };
18
- const PROVIDERS = {
19
- sqlite: {
20
- driverClass: "PrismaBetterSqlite3",
21
- driverOptions: "{ url }",
22
- databaseUrl: "file:./dev.db",
23
- gitignore: ["/dev.db*"],
24
- nativeBuilds: ["@prisma/engines", "better-sqlite3", "prisma"],
25
- },
26
- postgresql: {
27
- driverClass: "PrismaPg",
28
- driverOptions: "{ connectionString: url }",
29
- databaseUrl: "postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public",
30
- gitignore: [],
31
- nativeBuilds: ["@prisma/engines", "prisma"],
32
- },
33
- };
34
- function specifier(from, to, js) {
35
- const relative = path.posix.relative(path.posix.dirname(from), to);
36
- return `${relative.startsWith(".") ? relative : `./${relative}`}${js}`;
37
- }
38
- function schema(input) {
39
- return [
40
- "generator client {",
41
- ` provider = "${input.entry.generators[0]}"`,
42
- ` output = "../${CLIENT_OUTPUT}"`,
43
- ...(input.moduleKind === "cjs" ? [' moduleFormat = "cjs"'] : []),
44
- "}",
45
- "",
46
- "datasource db {",
47
- ` provider = "${input.provider}"`,
48
- "}",
49
- "",
50
- "model User {",
51
- " id Int @id @default(autoincrement())",
52
- " email String @unique",
53
- " name String?",
54
- "}",
55
- "",
56
- ].join("\n");
57
- }
58
- function prismaConfig() {
59
- return [
60
- "import { existsSync } from 'node:fs';",
61
- "import { defineConfig } from 'prisma/config';",
62
- "",
63
- "// Prisma reads DATABASE_URL from the environment and does not load .env itself.",
64
- "if (existsSync('.env')) {",
65
- " process.loadEnvFile('.env');",
66
- "}",
67
- "",
68
- "export default defineConfig({",
69
- ` schema: '${SCHEMA}',`,
70
- " migrations: {",
71
- " path: 'prisma/migrations',",
72
- " },",
73
- " datasource: {",
74
- " url: process.env.DATABASE_URL,",
75
- " },",
76
- "});",
77
- "",
78
- ].join("\n");
79
- }
80
- function prismaService(input) {
81
- const provider = PROVIDERS[input.provider];
82
- const driver = input.entry.drivers[input.provider];
83
- return [
84
- "import { Injectable, Module, type OnModuleDestroy } from '@nestjs/common';",
85
- `import { ${provider.driverClass} } from '${driver}';`,
86
- `import { PrismaClient } from '${specifier(NEW_SERVICE.file, `${input.clientOutput}/client`, input.js)}';`,
87
- "",
88
- "@Injectable()",
89
- "export class PrismaService extends PrismaClient implements OnModuleDestroy {",
90
- " constructor() {",
91
- " const url = process.env.DATABASE_URL;",
92
- " if (!url) {",
93
- " // Thrown while Nest builds the application, before it listens.",
94
- " throw new Error(",
95
- " 'DATABASE_URL is not set: put it in .env (the start scripts load it) or in the environment.',",
96
- " );",
97
- " }",
98
- ` super({ adapter: new ${provider.driverClass}(${provider.driverOptions}) });`,
99
- " }",
100
- "",
101
- " async onModuleDestroy(): Promise<void> {",
102
- " await this.$disconnect();",
103
- " }",
104
- "}",
105
- "",
106
- "@Module({ providers: [PrismaService], exports: [PrismaService] })",
107
- "export class PrismaModule {}",
108
- "",
109
- ].join("\n");
110
- }
111
- function aventaraConfig(input) {
112
- const file = "src/aventara.config.ts";
113
- const service = input.service.exportName;
114
- return [
115
- "import type { AvDiagnostic } from '@aventara/core';",
116
- `import { createPrismaAdapter } from '${input.entry.adapterPackage}';`,
117
- "import { Logger } from '@nestjs/common';",
118
- `import { discovery } from '${specifier(file, input.artifact.replace(/\.ts$/, ""), input.js)}';`,
119
- `import type { ${service} } from '${specifier(file, input.service.file.replace(/\.ts$/, ""), input.js)}';`,
120
- "",
121
- "const logger = new Logger('Aventara');",
122
- "",
123
- "/**",
124
- " * Aventara's configuration: where the API is mounted (`api`), and what you add",
125
- " * to it (`exposed` and `exemptions`, `pipelines`, and the per-Resource",
126
- " * `resources` tree).",
127
- " * AventaraModule (app.module.ts) builds the framework from it; `discovery` is",
128
- " * regenerated by the aventara:prepare script.",
129
- " */",
130
- `export async function aventaraConfig(prisma: ${service}) {`,
131
- " return {",
132
- ` api: '${SCAFFOLD_API}',`,
133
- " // Everything the adapter offers is exposed. List what to hide under",
134
- " // `exemptions`, or set `exposed: false` and list what to expose instead.",
135
- ` // See: ${EXPOSURE_DOCS}`,
136
- " exposed: true,",
137
- " // When something fails inside the server, the caller gets a plain 500 and",
138
- " // Aventara logs one line naming the request. This logs the error itself,",
139
- " // with its stack, through Nest's logger.",
140
- " diagnostics: (diagnostic: AvDiagnostic) => {",
141
- " const { error } = diagnostic;",
142
- " logger.error(",
143
- ` \`\${diagnostic.code} (request \${diagnostic.requestId}): raw error\`,`,
144
- " error instanceof Error ? error.stack : String(error),",
145
- " );",
146
- " },",
147
- " adapter: await createPrismaAdapter({",
148
- input.service.clientMember === undefined
149
- ? " client: prisma,"
150
- : ` client: prisma.${input.service.clientMember},`,
151
- " discovery,",
152
- ` provider: '${input.provider}',`,
153
- ` driver: '${input.entry.drivers[input.provider]}',`,
154
- " }),",
155
- " } as const;",
156
- "}",
157
- "",
158
- ].join("\n");
159
- }
160
- function prepare(input) {
161
- return [
162
- input.entry.bin,
163
- `--schema ${input.schema}`,
164
- `--client ${input.clientOutput}`,
165
- `--provider ${input.provider}`,
166
- `--driver ${input.entry.drivers[input.provider]}`,
167
- `--out ${input.artifact}`,
168
- ].join(" ");
169
- }
170
- function appModule(service, module, js) {
171
- const from = "src/app.module.ts";
172
- const sameFile = service.file === module.file;
173
- return {
174
- imports: [
175
- `import { aventaraConfig } from '${specifier(from, "src/aventara.config", js)}';`,
176
- ...(sameFile
177
- ? [
178
- `import { ${module.exportName}, ${service.exportName} } from '${specifier(from, service.file.replace(/\.ts$/, ""), js)}';`,
179
- ]
180
- : [
181
- `import { ${module.exportName} } from '${specifier(from, module.file.replace(/\.ts$/, ""), js)}';`,
182
- `import { ${service.exportName} } from '${specifier(from, service.file.replace(/\.ts$/, ""), js)}';`,
183
- ]),
184
- ],
185
- entries: [
186
- `${module.exportName},`,
187
- [
188
- "AventaraModule.forRootAsync({",
189
- ` imports: [${module.exportName}],`,
190
- ` inject: [${service.exportName}],`,
191
- ` useFactory: async (prisma: ${service.exportName}) => ({`,
192
- " config: await aventaraConfig(prisma),",
193
- " }),",
194
- "}),",
195
- ].join("\n"),
196
- ],
197
- };
198
- }
199
- function scaffold(input) {
200
- const js = input.moduleKind === "esm" ? ".js" : "";
201
- const floor = lowestVersionOf(input.entry.range);
202
- const provider = PROVIDERS[input.provider];
203
- const driver = input.entry.drivers[input.provider];
204
- const run = input.packageManager === "pnpm" ? "pnpm exec" : "npx";
205
- const artifact = "src/generated/aventara/discovery.artifact.ts";
206
- return {
207
- files: [
208
- { path: SCHEMA, content: schema(input) },
209
- { path: "prisma.config.ts", content: prismaConfig() },
210
- {
211
- path: NEW_SERVICE.file,
212
- content: prismaService({ ...input, clientOutput: CLIENT_OUTPUT, js }),
213
- },
214
- {
215
- path: "src/aventara.config.ts",
216
- content: aventaraConfig({
217
- ...input,
218
- service: NEW_SERVICE,
219
- artifact,
220
- js,
221
- }),
222
- },
223
- ],
224
- env: { DATABASE_URL: provider.databaseUrl },
225
- gitignore: ["/src/generated/", ...provider.gitignore],
226
- dependencies: { [input.entry.orm.package]: floor, [driver]: floor },
227
- devDependencies: { prisma: floor },
228
- prepare: prepare({
229
- ...input,
230
- schema: SCHEMA,
231
- clientOutput: CLIENT_OUTPUT,
232
- artifact,
233
- }),
234
- appModule: appModule(NEW_SERVICE, NEW_MODULE, js),
235
- nativeBuilds: provider.nativeBuilds,
236
- nextSteps: [
237
- ...(input.provider === "postgresql"
238
- ? ["Set DATABASE_URL in .env to your PostgreSQL database."]
239
- : []),
240
- `Create the database tables: ${run} prisma db push`,
241
- ],
242
- };
243
- }
244
- function reuse(input) {
245
- const js = input.moduleKind === "esm" ? ".js" : "";
246
- const { facts } = input;
247
- return {
248
- files: [
249
- ...(input.writeService
250
- ? [
251
- {
252
- path: NEW_SERVICE.file,
253
- content: prismaService({
254
- entry: input.entry,
255
- provider: facts.provider,
256
- clientOutput: facts.clientOutput,
257
- js,
258
- }),
259
- },
260
- ]
261
- : []),
262
- {
263
- path: "src/aventara.config.ts",
264
- content: aventaraConfig({
265
- entry: input.entry,
266
- provider: facts.provider,
267
- service: input.service,
268
- artifact: input.artifact,
269
- js,
270
- }),
271
- },
272
- ],
273
- env: {},
274
- gitignore: [`/${path.posix.dirname(input.artifact)}/`],
275
- dependencies: {},
276
- devDependencies: {},
277
- prepare: prepare({
278
- entry: input.entry,
279
- schema: facts.schema,
280
- clientOutput: facts.clientOutput,
281
- provider: facts.provider,
282
- artifact: input.artifact,
283
- }),
284
- appModule: appModule(input.service, input.module, js),
285
- nativeBuilds: [],
286
- nextSteps: [],
287
- };
288
- }
289
- function schemaLocation(directory, manifest) {
290
- for (const config of CONFIG_FILES) {
291
- const file = path.join(directory, config);
292
- if (existsSync(file)) {
293
- const declared = /\bschema\s*:\s*['"]([^'"]+)['"]/.exec(readFileSync(file, "utf8"))?.[1];
294
- if (declared !== undefined) {
295
- return path.posix.normalize(declared);
296
- }
297
- }
298
- }
299
- const prisma = manifest.prisma;
300
- return typeof prisma?.schema === "string"
301
- ? path.posix.normalize(prisma.schema)
302
- : SCHEMA;
303
- }
304
- function schemaFiles(directory, schemaPath) {
305
- const full = path.join(directory, schemaPath);
306
- if (!existsSync(full)) {
307
- return [];
308
- }
309
- if (!statSync(full).isDirectory()) {
310
- return [schemaPath];
311
- }
312
- return readdirSync(full, { recursive: true, withFileTypes: true })
313
- .filter((entry) => entry.isFile() && entry.name.endsWith(".prisma"))
314
- .map((entry) => path
315
- .relative(directory, path.join(entry.parentPath, entry.name))
316
- .split(path.sep)
317
- .join("/"))
318
- .sort();
319
- }
320
- function blocks(text, kind) {
321
- return [
322
- ...text.matchAll(new RegExp(`^\\s*${kind}\\s+\\w+\\s*\\{([^}]*)\\}`, "gm")),
323
- ].map((match) => match[1]);
324
- }
325
- function setting(block, name) {
326
- return new RegExp(`^\\s*${name}\\s*=\\s*"([^"]*)"`, "m").exec(block)?.[1];
327
- }
328
- function readProject(directory, manifest) {
329
- const schemaPath = schemaLocation(directory, manifest);
330
- let provider;
331
- let generator;
332
- let clientOutput;
333
- for (const file of schemaFiles(directory, schemaPath)) {
334
- const text = readFileSync(path.join(directory, file), "utf8").replace(/\/\/.*$/gm, "");
335
- provider ??= blocks(text, "datasource")
336
- .map((block) => setting(block, "provider"))
337
- .find((value) => value !== undefined);
338
- for (const block of blocks(text, "generator")) {
339
- const declared = setting(block, "provider");
340
- const output = setting(block, "output");
341
- if (generator === undefined || /^prisma-client/.test(declared ?? "")) {
342
- generator = declared;
343
- clientOutput =
344
- output === undefined
345
- ? undefined
346
- : path.posix.normalize(path.posix.join(path.posix.dirname(file), output));
347
- }
348
- }
349
- }
350
- const dependencies = [
351
- ...Object.keys(manifest.dependencies ?? {}),
352
- ...Object.keys(manifest.devDependencies ?? {}),
353
- ];
354
- return {
355
- schema: schemaPath,
356
- provider,
357
- generator,
358
- clientOutput,
359
- drivers: dependencies.filter((name) => name.startsWith(DRIVER_PREFIX)),
360
- };
361
- }
362
- function clientShape(facts) {
363
- return {
364
- clientClass: "PrismaClient",
365
- importsClient: (imported, file) => {
366
- if (!imported.startsWith(".")) {
367
- return imported === "@prisma/client";
368
- }
369
- if (facts.clientOutput === undefined) {
370
- return false;
371
- }
372
- const resolved = path.posix
373
- .join(path.posix.dirname(file), imported)
374
- .replace(/\.(js|ts)$/, "");
375
- return (resolved === facts.clientOutput ||
376
- resolved === `${facts.clientOutput}/client` ||
377
- resolved === `${facts.clientOutput}/index`);
378
- },
379
- skip: facts.clientOutput === undefined
380
- ? ["src/generated"]
381
- : ["src/generated", facts.clientOutput],
382
- };
383
- }
1
+ import { PRISMA7_NEST_TEMPLATES } from "./prisma7-nest.templates.js";
2
+ import { PRISMA7_NEXT_TEMPLATES } from "./prisma7-next.templates.js";
3
+ import { clientShape, PROVIDERS, readProject, } from "./prisma7-orm.templates.js";
384
4
  export const PRISMA7_TEMPLATES = {
385
5
  providers: PROVIDERS,
386
- scaffold,
387
6
  readProject,
388
7
  clientShape,
389
- newService: { service: NEW_SERVICE, module: NEW_MODULE },
390
- reuse,
8
+ hosts: {
9
+ nest: PRISMA7_NEST_TEMPLATES,
10
+ next: PRISMA7_NEXT_TEMPLATES,
11
+ },
391
12
  };
@@ -1,6 +1,6 @@
1
1
  import { PRISMA7_TEMPLATES } from "./prisma7/prisma7.templates.js";
2
2
  export const SCAFFOLD_API = "/api";
3
- export const TEMPLATE_REGISTRY = {
3
+ const TEMPLATE_REGISTRY = {
4
4
  prisma7: PRISMA7_TEMPLATES,
5
5
  };
6
6
  export function templatesFor(entry) {
@@ -136,7 +136,7 @@ function serviceOptions(services) {
136
136
  ? found
137
137
  : [{ ...services.newService, isNew: true }];
138
138
  }
139
- export const SERVICE_QUESTION = {
139
+ const SERVICE_QUESTION = {
140
140
  id: "service",
141
141
  flag: "--prisma-service",
142
142
  placeholder: "<path#Export>",
@@ -175,7 +175,7 @@ export const SERVICE_QUESTION = {
175
175
  },
176
176
  describe: spelled,
177
177
  };
178
- export const MODULE_QUESTION = {
178
+ const MODULE_QUESTION = {
179
179
  id: "module",
180
180
  flag: "--prisma-module",
181
181
  placeholder: "<path#Export>",
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@aventara/cli",
3
- "version": "1.0.0-rc.1",
3
+ "version": "1.0.0-rc.10",
4
4
  "license": "SEE LICENSE IN LICENSE",
5
5
  "description": "Scaffolds an Aventara server: `aventara new` for a new NestJS project, `aventara init` for an existing one.",
6
6
  "type": "module",
7
7
  "engines": {
8
- "node": "^22.18.0 || >=24.2.0"
8
+ "node": "^22.23.4 || >=24.22.1"
9
9
  },
10
10
  "bin": {
11
11
  "aventara": "./dist/aventara.bin.js"
@@ -23,7 +23,7 @@
23
23
  "typescript": "^5.9.2"
24
24
  },
25
25
  "scripts": {
26
- "prebuild": "node scripts/adapter-catalog.generator.ts",
26
+ "prebuild": "node scripts/adapter-catalog.generator.ts && node ../../scripts/build/host-catalog.generator.ts --out src/catalog/host.catalog.generated.ts && node scripts/agent-skill.generator.ts",
27
27
  "build": "node ../../scripts/build/build-output.cleaner.ts dist && tsc -p tsconfig.json --removeComments",
28
28
  "typecheck": "tsc -p tsconfig.typecheck.json --noEmit",
29
29
  "test": "vitest run --typecheck --config ../../vitest.config.mts --root .",