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

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,236 @@
1
1
  # @aventara/cli
2
2
 
3
+ ## 1.0.0-rc.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 7e3d06f: Breaking: the configuration has a new shape. Every layer (the root, `application` and `client`) now holds the
8
+ same six keys: `exposed`, `transactionsStyle`, `limits`, `pipelines`, `resources` and `exemptions`. Everything about
9
+ one Resource lives under `resources.<Resource>`: its fields, and each field's `definition` and `pipelines`. The old
10
+ keys are refused when the framework starts, with a message that names where each one moved:
11
+
12
+ | Before (rc.0) | Now (rc.1) |
13
+ | ------------------------------------------------------- | ----------------------------------------------------------------------------------- |
14
+ | `fields: { Post: { url: { type, nullable, … } } }` | `resources: { Post: { fields: { url: { definition: { type, nullable, … } } } } }` |
15
+ | `behaviors: { Post: { fields: { slug: { create } } } }` | `resources: { Post: { fields: { slug: { pipelines: { compute: { create } } } } } }` |
16
+ | `transactions: "none"` | `transactionsStyle: "none"` |
17
+ | `restrictions` | `exposed` + `exemptions` (see below) |
18
+ | `@aventara/core/json` | `@aventara/core/avz` |
19
+
20
+ Before:
21
+
22
+ ```ts
23
+ createFramework({
24
+ api: "/api",
25
+ adapter,
26
+ transactions: "interactive",
27
+ fields: {
28
+ Post: {
29
+ url: {
30
+ type: { scalar: "string" },
31
+ nullable: false,
32
+ list: false,
33
+ lifecycle: ["VIRTUAL"],
34
+ },
35
+ },
36
+ },
37
+ behaviors: {
38
+ Post: {
39
+ fields: {
40
+ url: {
41
+ read: {
42
+ dependsOn: ["slug"],
43
+ compute: ({ record }) => `/p/${record.slug}`,
44
+ },
45
+ },
46
+ },
47
+ },
48
+ },
49
+ });
50
+ ```
51
+
52
+ After:
53
+
54
+ ```ts
55
+ createFramework({
56
+ api: "/api",
57
+ adapter,
58
+ transactionsStyle: "interactive",
59
+ resources: {
60
+ Post: {
61
+ fields: {
62
+ url: {
63
+ definition: {
64
+ type: { scalar: "string" },
65
+ nullable: false,
66
+ list: false,
67
+ lifecycle: ["VIRTUAL"],
68
+ format: "url",
69
+ },
70
+ pipelines: {
71
+ compute: {
72
+ read: {
73
+ dependsOn: ["slug"],
74
+ compute: ({ record }) => `/p/${record.slug}`,
75
+ },
76
+ },
77
+ },
78
+ },
79
+ },
80
+ },
81
+ },
82
+ });
83
+ ```
84
+
85
+ What else changed:
86
+
87
+ - A field is defined at the root only. A `definition` in `application` or `client` is refused
88
+ (`CONFIG_FORBIDDEN_IN_OVERLAY`). Define the field
89
+ at the root; `application` and `client` can still add compute stages to it.
90
+ - Computed values chain. When the root and an overlay both compute a field, the root's stage runs first and the
91
+ overlay's receives its result as `value`. A stage can no longer be turned off with `false`. Returning `undefined`
92
+ keeps the current value. An upsert computes its create branch with `compute.create` and its update branch with
93
+ `compute.update`.
94
+ - A discovered field keeps the adapter's facts. On a field the adapter discovered you may restate its own `type`,
95
+ `nullable` and `list`, and give a Json field its shape (`definition.json`). Setting a different type, any `format`
96
+ or any `capabilities` on it is refused. `format` is for fields you add, and it must be one of zod's named string
97
+ formats (`"email"`, `"url"`, `"uuid"`, …); any other name is a type error and refused at startup.
98
+ - A misplaced key is a type error. In a configuration written inline in `createFramework({ ... })` or
99
+ `AventaraModule.forRoot`, a key that has no place there (a typo, an old key, `compute` on a relation field, a field
100
+ name neither your schema nor the root has) is now a type error at that key, and still refused at startup.
101
+ - A Resource whose name starts with `$` is refused at startup (`$` names are the framework's).
102
+ - The Json shape builder moved from `@aventara/core/json` to `@aventara/core/avz`. Change the import; `AvZ` is
103
+ the same.
104
+
105
+ `exposed` and `exemptions`, which replace `restrictions`, have their own entry.
106
+
107
+ - 6317811: Breaking: `exposed` and `exemptions` replace `restrictions`. Every layer (the root, `application` and `client`)
108
+ says what it exposes with two keys: `exposed`, the layer's default, and `exemptions`, the entries that are the
109
+ opposite of it.
110
+
111
+ - `exposed: true` (the root's default) exposes everything the layer above offers, and `exemptions` lists what to
112
+ hide. `exposed: false` exposes nothing, and `exemptions` lists the only things to expose.
113
+ - In `exemptions`, `true` exempts an entry whole, `false` and `[]` exempt nothing, a list exempts the listed
114
+ entries one level down, and an object goes one level deeper. A Resource takes `true`, `false` or
115
+ `{ operations, fields }`. Operations are nested: `operations: { find: ["many"], update: { unique: true } }`.
116
+ A field takes a list of capability names, or an object per capability whose token lists name filter operators,
117
+ directives, reducers or pagination kinds.
118
+ - A list now always means "exempt these". rc.0's `filter: ["equals"]` meant "keep only `equals`"; under
119
+ `exposed: true` the same intent is the list of the operators to remove.
120
+ - Overlays only narrow. `application` and `client` start from the root's view. An overlay can no longer bring
121
+ back what the root hid (rc.0's `hidden: false`); that is now refused at startup, and the message names the other
122
+ overlay to hide it in instead. Expose an entry at the root and hide it in the overlay that must not see it.
123
+ - An overlay that sets `exemptions` must also set its own `exposed`.
124
+ - Relation positions are the adapter's: an exemption can no longer narrow where a relation is projected or written.
125
+ - A unique operation (`find.unique`, `update.unique`, `delete.unique`, `upsert.unique`) needs an identifier. Picking
126
+ one without the identifier's fields, or hiding every identifier while one stays exposed, is refused at startup
127
+ with `COMPILER_MISSING_IDENTIFIER` instead of the operation silently disappearing.
128
+
129
+ Every name in `exemptions` must exist, in both styles: a Resource, field, family, variant, capability or token the
130
+ adapter does not offer is refused at startup with its path (`COMPILER_UNKNOWN_RESOURCE`, `COMPILER_UNKNOWN_FIELD`,
131
+ `COMPILER_UNAVAILABLE_OPERATION`, `COMPILER_UNAVAILABLE_CAPABILITY`), and in a configuration written inline in
132
+ `createFramework({ ... })` it is a type error first. Under `exposed: true`, hiding in an overlay what the root
133
+ already hid does nothing.
134
+
135
+ Before (rc.0):
136
+
137
+ ```ts
138
+ createFramework({
139
+ api: "/api",
140
+ adapter,
141
+ restrictions: {
142
+ User: {
143
+ fields: {
144
+ passwordHash: { hidden: true },
145
+ email: { capabilities: { filter: ["equals"] } },
146
+ },
147
+ },
148
+ },
149
+ client: {
150
+ restrictions: {
151
+ AuditLog: { hidden: true },
152
+ Post: { operations: { delete: { unique: false } } },
153
+ },
154
+ },
155
+ });
156
+ ```
157
+
158
+ After (rc.1):
159
+
160
+ ```ts
161
+ createFramework({
162
+ api: "/api",
163
+ adapter,
164
+ // The root: `exposed` defaults to true, so these are hidden from every origin.
165
+ exemptions: {
166
+ resources: {
167
+ User: {
168
+ fields: {
169
+ passwordHash: true,
170
+ email: { filter: ["contains", "startsWith"] }, // remove every operator but `equals`
171
+ },
172
+ },
173
+ },
174
+ },
175
+ client: {
176
+ exposed: true,
177
+ exemptions: {
178
+ resources: {
179
+ AuditLog: true,
180
+ Post: { operations: { delete: ["unique"] } },
181
+ },
182
+ },
183
+ },
184
+ });
185
+ ```
186
+
187
+ Or expose only what a frontend calls:
188
+
189
+ ```ts
190
+ client: {
191
+ exposed: false,
192
+ exemptions: {
193
+ resources: {
194
+ Post: {
195
+ operations: { find: ["many", "unique"], create: ["one"] },
196
+ fields: { id: ["select"], title: ["select", "create"], body: ["select", "create"] },
197
+ },
198
+ },
199
+ },
200
+ },
201
+ ```
202
+
203
+ `aventara init` and `aventara new` now write `exposed: true` into `src/aventara.config.ts`, with a comment that
204
+ links the exposure documentation.
205
+
206
+ - 9c52482: A `$connect` to a row that does not exist now answers `404 A2003` (not found), the same as reading a missing row.
207
+ Before, it answered `422 A2004`. This covers a to-one `$connect` in `create`, `update` and `upsert`, a to-many
208
+ `$connect`, and the same writes inside a transaction plan, where `cause.operation` names the step. The message is
209
+ unchanged: "The operation's arguments referenced a record that does not exist." On `update.first`, the same mistake
210
+ answered `A2014` before, as if the row had changed underneath; it now answers `A2003` too, and a duplicate unique value
211
+ written through `update.first` answers `409 A2008` instead of `A3001`.
212
+
213
+ Clearer top-level messages for an unknown Resource or operation. `A2001` now says "The requested Resource is not
214
+ available." and `A2002` says "The requested operation is not available on this Resource.", instead of "Operation
215
+ arguments failed framework validation.", which stays the message of `A2004`, `A2007` and `A2009`. A transaction step
216
+ whose operation is outside the standard grammar now carries the same issue message as a single call, naming the
217
+ operation and the Resource (for example `Operation "dance.one" is not available on Resource "posts".`), instead of
218
+ "Operation is not part of the standard operation grammar."
219
+
220
+ A `$ref` with no `fingerprint` is `V1000` (required value missing) and answers `422 A2004`. Before, it was `V1017`
221
+ (fingerprint mismatch) under `A2007`. A fingerprint that is present but wrong is still `V1017`.
222
+
223
+ `aventara init` and `aventara new` link to the documentation at https://aventara-framework.vercel.app. The old
224
+ address redirects, so links already written into projects keep working.
225
+
226
+ - 8db76e7: `aventara new` and `aventara init` write a `diagnostics` sink into `src/aventara.config.ts`. When something fails
227
+ inside the server, the caller gets a plain 500 and the Nest log said only which request failed; the raw error went to
228
+ a sink the scaffold did not set. Now the sink logs the raw error with its stack through Nest's `Logger` (context
229
+ `Aventara`), at error level, right after that line. Replace it with your own reporter when you have one.
230
+
231
+ - `aventara new` and `aventara init` write `api: '/api'` into `src/aventara.config.ts` (the root key was renamed from
232
+ `entrypoint`).
233
+
3
234
  ## 1.0.0-rc.0
4
235
 
5
236
  ### Major Changes
package/README.md CHANGED
@@ -8,8 +8,8 @@ aventara new my-api # a new NestJS project with Aventara
8
8
  aventara init # Aventara added to the NestJS 12 project in this directory
9
9
  ```
10
10
 
11
- It is run once, globally or through `npx`; it is not a dependency of the projects it writes. The frontend's side —
12
- `framework.client.ts` and the generated client — is `@aventara/client`'s (`npx @aventara/client@rc init`).
11
+ It is run once, globally or through `npx`; it is not a dependency of the projects it writes. The frontend's side
12
+ (`framework.client.ts` and the generated client) belongs to `@aventara/client` (`npx @aventara/client@rc init`).
13
13
 
14
14
  ## `aventara new <name>`
15
15
 
@@ -17,22 +17,24 @@ Runs the pinned `nest new` (`npx -y @nestjs/cli@12.0.8 new …`, or `pnpm dlx
17
17
  `src/app.module.ts` is what that version writes, then runs exactly `aventara init`'s pipeline over the new project and
18
18
  installs it. A directory that exists and is not empty is refused before anything runs.
19
19
 
20
- Once installed, the project gets its first git commit, `Initial Aventara Scaffold` — the lockfile in it, `.env` not
21
- (it is in `.gitignore`). No commit is made, and one line says why, when git is not installed, when git has no
20
+ Once installed, the project gets its first git commit, `Initial Aventara Scaffold`, with the lockfile in it and
21
+ without `.env` (it is in `.gitignore`). No commit is made, and one line says why, when git is not installed, when git has no
22
22
  `user.name`/`user.email`, or when the directory is already inside a git repository (a monorepo: commit it there).
23
23
  `--skip-git` makes no repository and no commit.
24
24
 
25
25
  ## `aventara init`
26
26
 
27
- Runs inside an existing **NestJS 12** project (refused: no `package.json`, another Nest major, TypeScript 7, a yarn-only
28
- lockfile, a project already initialized).
27
+ Runs inside an existing NestJS 12 project. It refuses a directory with no `package.json`, another Nest major,
28
+ TypeScript 7, a yarn-only lockfile, or a project already initialized.
29
29
 
30
- - **No ORM yet:** the ORM and the database are asked (or taken from the flags), and everything below is written.
31
- - **Prisma already there:** the installed Prisma must be one an Aventara adapter supports — today Prisma 7, `^7.10.0`,
32
- SQLite or PostgreSQL, a driver adapter, the `prisma-client` generator — or the run is refused in one sentence, with
33
- nothing written. Your schema, Prisma config and service are **reused and never written**, and your `.env` only gains
34
- a `CORS_ORIGINS` line at its end when it has none; init finds the class extending `PrismaClient` and the module
35
- exporting it, or asks.
30
+ In a project with no ORM yet, the ORM and the database are asked (or taken from the flags), and everything below is
31
+ written.
32
+
33
+ In a project that already uses Prisma, the installed Prisma must be one an Aventara adapter supports, or the run is
34
+ refused in one sentence with nothing written. Today that is Prisma 7 (`^7.10.0`) on SQLite or PostgreSQL, with a driver
35
+ adapter and the `prisma-client` generator. Your schema, Prisma config and service are reused and never written, and
36
+ your `.env` only gains a `CORS_ORIGINS` line at its end when it has none. Init finds the class extending `PrismaClient`
37
+ and the module exporting it, or asks.
36
38
 
37
39
  ### Questions and flags
38
40
 
@@ -42,20 +44,20 @@ possible answer is shown, not asked.
42
44
 
43
45
  | Flag | Question | Default |
44
46
  |---|---|---|
45
- | `<name>` (`new` only) | The project directory. | — (required) |
47
+ | `<name>` (`new` only) | The project directory. | required |
46
48
  | `--orm <alias>` | The ORM, as an adapter's alias: `prisma7` (Prisma 7, `@aventara/prisma7-adapter`). `--help` lists the aliases that exist. | the only one there is |
47
49
  | `--db <provider>` | The database: the adapter's providers (`sqlite`, `postgresql`). | `sqlite` |
48
- | `--package-manager <npm\|pnpm>` | — | the lockfile's, else the one that launched the CLI, else npm |
50
+ | `--package-manager <npm\|pnpm>` | The package manager. | the lockfile's, else the one that launched the CLI, else npm |
49
51
  | `--prisma-service <path#Export>` (`init`) | Your Prisma service, when more than one, or none, is found. | the one found; none → write `src/prisma.service.ts` |
50
52
  | `--prisma-module <path#Export>` (`init`) | The Nest module that provides and exports it. | the one found |
51
53
  | `--skip-install` | Write the files; print the install instead of running it. | |
52
54
  | `--skip-git` (`new`) | Passed to `nest new`. | |
53
- | `-y`, `--yes` | Accept every unanswered default **and** replace existing content that differs. | |
55
+ | `-y`, `--yes` | Accept every unanswered default, and replace existing content that differs. | |
54
56
  | `--verbose` | Show the output of `nest new` and the install as they run. | |
55
57
 
56
- While it works, each step is shown on stderr — `[3/4] Installing dependencies (npm install)` — with a spinner and the
57
- seconds so far on a terminal, or as one plain line per step (`… done (12.4s)`) in a pipe, in CI (`CI`) or with
58
- `NO_COLOR`; the output of `nest new` and the install is shown only if it fails, or with `--verbose`.
58
+ While it works, each step is shown on stderr, such as `[3/4] Installing dependencies (npm install)`. On a terminal it
59
+ has a spinner and the seconds so far; in a pipe, in CI (`CI`) or with `NO_COLOR` it is one plain line per step
60
+ (`… done (12.4s)`). The output of `nest new` and the install is shown only if it fails, or with `--verbose`.
59
61
 
60
62
  Existing content that differs from what would be written (a `.env` key, a script, a file) is listed, and replaced only
61
63
  when you confirm, or with `--yes`; where nobody can be asked, the run stops and touches nothing.
@@ -64,17 +66,17 @@ when you confirm, or with `--yes`; where nobody can be asked, the run stops and
64
66
 
65
67
  | Path | Owner afterwards |
66
68
  |---|---|
67
- | `prisma/schema.prisma` — `prisma-client` generating into `src/generated/prisma`, a `User` model | you |
68
- | `prisma.config.ts` — loads `.env` itself (Prisma does not) | you |
69
- | `src/prisma.service.ts` — `PrismaService` extending the client with the driver adapter, refusing a missing `DATABASE_URL` before the server listens; `PrismaModule` | you |
70
- | `src/aventara.config.ts` — `aventaraConfig(prisma)`: the entrypoint (`/api`), and the restrictions and pipelines you add | you |
71
- | `src/app.module.ts` — one edit: the imports, `PrismaModule` and `AventaraModule.forRootAsync({ … })`; printed instead when the file is not the shape expected | you |
72
- | `test/app.e2e-spec.ts` — one edit: a `GET /api/_contract` → 200 test after Nest's `GET /` test; left alone when the file is not Nest's | you |
73
- | `src/main.ts` — one edit after `NestFactory.create`: `app.enableCors(…)` for the comma-separated origins in `CORS_ORIGINS`, and CORS off when it is unset or empty, then a commented-out platform-level rate limiter (`express-rate-limit`; Nest's `@nestjs/throttler` is a guard, which does not run on Aventara's routes); left alone when the file already configures CORS, printed when it has no such line | you |
69
+ | `prisma/schema.prisma`: `prisma-client` generating into `src/generated/prisma`, a `User` model | you |
70
+ | `prisma.config.ts`: loads `.env` itself (Prisma does not) | you |
71
+ | `src/prisma.service.ts`: `PrismaService` extending the client with the driver adapter, refusing a missing `DATABASE_URL` before the server listens; `PrismaModule` | you |
72
+ | `src/aventara.config.ts`: `aventaraConfig(prisma)`: the `api` mount path (`/api`), a `diagnostics` sink that logs the raw error of a server failure, with its stack, through Nest's `Logger`, and the `exposed`/`exemptions`, `pipelines` and `resources` you add | you |
73
+ | `src/app.module.ts`: one edit that adds the imports, `PrismaModule` and `AventaraModule.forRootAsync({ … })`; printed instead when the file is not the shape expected | you |
74
+ | `test/app.e2e-spec.ts`: one edit that adds a `GET /api/_contract` → 200 test after Nest's `GET /` test; left alone when the file is not Nest's | you |
75
+ | `src/main.ts`: one edit after `NestFactory.create`: `app.enableCors(…)` for the comma-separated origins in `CORS_ORIGINS`, and CORS off when it is unset or empty, then a commented-out platform-level rate limiter (`express-rate-limit`; Nest's `@nestjs/throttler` is a guard, which does not run on Aventara's routes); left alone when the file already configures CORS, printed when it has no such line | you |
74
76
  | `.env` (`DATABASE_URL`, and `CORS_ORIGINS="http://localhost:5173,http://localhost:3001"` appended unless a line sets it), `.gitignore` (`/src/generated/`, `/dev.db*`, `.env`) | you |
75
- | `package.json` — `@aventara/*` at this CLI's version, `prisma`, `@prisma/client` and the driver at `7.10.0`; `aventara:prepare`, run by `postinstall` and by `prebuild`, `prestart`, `prestart:dev` and `prestart:debug`, and the `.env` on the start scripts and `test:e2e` (`--env-file`, `--env-file-if-exists`) | you |
76
- | `pnpm-workspace.yaml` (pnpm only) — `allowBuilds` for Prisma's and SQLite's install scripts | you |
77
- | `src/generated/prisma/**`, `src/generated/aventara/discovery.artifact.ts` | **generated** by `aventara:prepare` (run on every install, build and start, so a schema change needs only a restart); never edit |
77
+ | `package.json`: `@aventara/*` at this CLI's version, `prisma`, `@prisma/client` and the driver at `7.10.0`; `aventara:prepare`, run by `postinstall` and by `prebuild`, `prestart`, `prestart:dev` and `prestart:debug`, and the `.env` on the start scripts and `test:e2e` (`--env-file`, `--env-file-if-exists`) | you |
78
+ | `pnpm-workspace.yaml` (pnpm only): `allowBuilds` for Prisma's and SQLite's install scripts | you |
79
+ | `src/generated/prisma/**`, `src/generated/aventara/discovery.artifact.ts` | generated by `aventara:prepare` (run on every install, build and start, so a schema change needs only a restart); never edit |
78
80
 
79
81
  ### Next steps it prints
80
82
 
@@ -111,23 +113,29 @@ curl -s -X POST http://localhost:3000/api/_resources/User/find/many \
111
113
  Leave out an identity header and the answer is `400 A2000`, naming the header that is missing. The hash changes
112
114
  whenever the schema or the configuration does: fetch it again, and regenerate the client.
113
115
 
114
- - **Resource keys are the model names, as written.** `model User` is `User` everywhere: on the wire
115
- (`/_resources/User/find/many`) and in the generated client (`avClient.User.find.many({})`). Nothing is renamed,
116
- lower-cased or pluralized.
117
- - **Results are read-only.** A list result is a `readonly` array: type it `readonly User[]`, not `User[]` —
118
- `const users: readonly User[] = await avClient.User.find.many({});`.
116
+ Resource keys are the model names, as written. `model User` is `User` everywhere: on the wire
117
+ (`/_resources/User/find/many`) and in the generated client (`avClient.User.find.many({})`). Nothing is renamed,
118
+ lower-cased or pluralized.
119
+
120
+ Results are read-only. A list result is a `readonly` array, so type it `readonly User[]`:
121
+ `const users: readonly User[] = await avClient.User.find.many({});`.
119
122
 
120
123
  ## Supported
121
124
 
122
- NestJS 12; Node `^22.18.0 || >=24.2.0` (on any other Node the bin refuses in one sentence naming that range;
123
- on Node 22 use npm ≥ 11 — Node 22's bundled npm 10 cannot install Nest 12's own scaffold; a CommonJS project's jest
124
- e2e needs Node ≥ 24.9, Nest's and Jest's limit); npm and pnpm (yarn is not supported); ESM and
125
- CommonJS projects; Prisma 7 (`^7.10.0`) on SQLite and PostgreSQL. The ORMs, majors, databases and drivers on offer are
126
- **generated** from the Aventara adapter packages that exist (`src/catalog/adapter.catalog.generated.ts`), never listed
127
- by hand: a future
128
- `@aventara/prisma8-adapter` appears by existing.
125
+ - NestJS 12.
126
+ - Node `^22.18.0 || >=24.2.0`. On any other Node the bin refuses in one sentence naming that range. On Node 22 use
127
+ npm ≥ 11, because Node 22's bundled npm 10 cannot install Nest 12's own scaffold. A CommonJS project's jest e2e needs
128
+ Node ≥ 24.9, which is Nest's and Jest's limit.
129
+ - npm and pnpm. Yarn is not supported.
130
+ - ESM and CommonJS projects.
131
+ - Prisma 7 (`^7.10.0`) on SQLite and PostgreSQL.
132
+
133
+ The ORMs, majors, databases and drivers on offer are generated from the Aventara adapter packages that exist
134
+ (`src/catalog/adapter.catalog.generated.ts`), never listed by hand, so a future `@aventara/prisma8-adapter` appears
135
+ once it exists.
129
136
 
130
137
  ## License
131
138
 
132
- PolyForm Shield 1.0.0 with an additional permission — free to use, including in commercial applications; you may not
133
- use it to build a product that competes with Aventara itself. See `LICENSE` and `LICENSE-ADDITIONAL-PERMISSION.md`.
139
+ PolyForm Shield 1.0.0 with an additional permission. It is free to use, including in commercial applications, but you
140
+ may not use it to build a product that competes with Aventara itself. See `LICENSE` and
141
+ `LICENSE-ADDITIONAL-PERMISSION.md`.
@@ -1,5 +1,5 @@
1
- export class ConflictsNotConfirmedError extends Error {
2
- name = "ConflictsNotConfirmedError";
1
+ export class AvConflictsNotConfirmedError extends Error {
2
+ name = "AvConflictsNotConfirmedError";
3
3
  }
4
4
  export function conflictWarning(conflicts) {
5
5
  return `${conflicts.join(", ")} already ${conflicts.length === 1 ? "has" : "have"} other content, and initializing will replace ${conflicts.length === 1 ? "it" : "them"}`;
@@ -10,7 +10,7 @@ export async function confirmConflicts(conflicts, consent) {
10
10
  }
11
11
  const them = conflicts.length === 1 ? "it" : "them";
12
12
  if (!consent.prompter.interactive) {
13
- throw new ConflictsNotConfirmedError(`not initializing, and nothing was touched: ${conflicts.join(", ")} already ${conflicts.length === 1 ? "has" : "have"} other content and nobody can be asked (stdin is not a terminal); run \`aventara init --yes\` to replace ${them}`);
13
+ throw new AvConflictsNotConfirmedError(`not initializing, and nothing was touched: ${conflicts.join(", ")} already ${conflicts.length === 1 ? "has" : "have"} other content and nobody can be asked (stdin is not a terminal); run \`aventara init --yes\` to replace ${them}`);
14
14
  }
15
15
  consent.prompter.say(`aventara: warning: ${conflictWarning(conflicts)}.`);
16
16
  const answer = await consent.prompter.ask({
@@ -19,6 +19,6 @@ export async function confirmConflicts(conflicts, consent) {
19
19
  defaultValue: "N",
20
20
  });
21
21
  if (!/^y(?:es)?$/i.test(answer.trim())) {
22
- throw new ConflictsNotConfirmedError(`cancelled, and nothing was touched; run again and answer yes to replace ${them}`);
22
+ throw new AvConflictsNotConfirmedError(`cancelled, and nothing was touched; run again and answer yes to replace ${them}`);
23
23
  }
24
24
  }
package/dist/cli.js CHANGED
@@ -1,23 +1,23 @@
1
1
  import { readFileSync } from "node:fs";
2
- import { ConflictsNotConfirmedError } from "./apply/conflict.confirmer.js";
2
+ import { AvConflictsNotConfirmedError } from "./apply/conflict.confirmer.js";
3
3
  import { ADAPTER_CATALOG } from "./catalog/adapter.catalog.generated.js";
4
- import { CliCommandError, commandUsage, parseCliCommand, usage, } from "./command/command.parser.js";
5
- import { ProjectRefusedError } from "./project/project.inspector.js";
4
+ import { AvCliCommandError, commandUsage, parseCliCommand, usage, } from "./command/command.parser.js";
5
+ import { AvProjectRefusedError } from "./project/project.inspector.js";
6
6
  import { runCommand } from "./run/command.runner.js";
7
- import { InstallFailedError, runInit } from "./run/init.orchestrator.js";
7
+ import { AvInstallFailedError, runInit } from "./run/init.orchestrator.js";
8
8
  import { runNew } from "./run/new.orchestrator.js";
9
9
  import { progressModeOf } from "./run/step.reporter.js";
10
- import { UnansweredQuestionsError, } from "./wizard/answer.resolver.js";
11
- import { createReadlinePrompter, WizardCancelledError, } from "./wizard/readline.prompter.js";
12
- import { WizardAnswerError } from "./wizard/wizard.questions.js";
10
+ import { AvUnansweredQuestionsError, } from "./wizard/answer.resolver.js";
11
+ import { AvWizardCancelledError, createReadlinePrompter, } from "./wizard/readline.prompter.js";
12
+ import { AvWizardAnswerError } from "./wizard/wizard.questions.js";
13
13
  const REFUSALS = [
14
- CliCommandError,
15
- WizardAnswerError,
16
- UnansweredQuestionsError,
17
- WizardCancelledError,
18
- ProjectRefusedError,
19
- ConflictsNotConfirmedError,
20
- InstallFailedError,
14
+ AvCliCommandError,
15
+ AvWizardAnswerError,
16
+ AvUnansweredQuestionsError,
17
+ AvWizardCancelledError,
18
+ AvProjectRefusedError,
19
+ AvConflictsNotConfirmedError,
20
+ AvInstallFailedError,
21
21
  ];
22
22
  const MANIFEST = new URL("../package.json", import.meta.url);
23
23
  function ownVersion() {
@@ -1,6 +1,6 @@
1
1
  import { INIT_FLAG_QUESTIONS, NEW_QUESTIONS, ormAliasesSentence, } from "../wizard/wizard.questions.js";
2
- export class CliCommandError extends Error {
3
- name = "CliCommandError";
2
+ export class AvCliCommandError extends Error {
3
+ name = "AvCliCommandError";
4
4
  }
5
5
  const OPTIONS = [
6
6
  {
@@ -101,7 +101,7 @@ ${asked(catalog)}`;
101
101
  export function parseCliCommand(argv) {
102
102
  const [command, ...rest] = argv;
103
103
  if (command === undefined) {
104
- throw new CliCommandError(`a command is required: new or init; ${HELP}`);
104
+ throw new AvCliCommandError(`a command is required: new or init; ${HELP}`);
105
105
  }
106
106
  if (command === "--help" || command === "-h") {
107
107
  return { command: "help" };
@@ -110,7 +110,7 @@ export function parseCliCommand(argv) {
110
110
  return { command: "version" };
111
111
  }
112
112
  if (command !== "new" && command !== "init") {
113
- throw new CliCommandError(`unknown command ${JSON.stringify(command)}: the commands are new and init; ${HELP}`);
113
+ throw new AvCliCommandError(`unknown command ${JSON.stringify(command)}: the commands are new and init; ${HELP}`);
114
114
  }
115
115
  if (rest.includes("--help") || rest.includes("-h")) {
116
116
  return { command: "help", topic: command };
@@ -124,7 +124,7 @@ export function parseCliCommand(argv) {
124
124
  const argument = rest[index];
125
125
  if (!argument.startsWith("-")) {
126
126
  if (positional === undefined || given[positional.id] !== undefined) {
127
- throw new CliCommandError(`unexpected argument ${JSON.stringify(argument)}: aventara ${command} takes ${positional === undefined ? "no" : "one"} positional argument; ${HELP}`);
127
+ throw new AvCliCommandError(`unexpected argument ${JSON.stringify(argument)}: aventara ${command} takes ${positional === undefined ? "no" : "one"} positional argument; ${HELP}`);
128
128
  }
129
129
  given[positional.id] = argument;
130
130
  continue;
@@ -137,20 +137,20 @@ export function parseCliCommand(argv) {
137
137
  const canonical = question?.flag ?? option?.flag ?? flag;
138
138
  if (question === undefined && option === undefined) {
139
139
  const elsewhere = ["new", "init"].filter((other) => QUESTIONS[other].some((candidate) => candidate.flag === flag));
140
- throw new CliCommandError(elsewhere.length === 0
140
+ throw new AvCliCommandError(elsewhere.length === 0
141
141
  ? `unknown flag ${flag}; ${HELP}`
142
142
  : `${flag} is a flag of aventara ${elsewhere.join(" and ")}, not aventara ${command}; ${HELP}`);
143
143
  }
144
144
  if (option !== undefined && !option.commands.includes(command)) {
145
- throw new CliCommandError(`${option.flag} is a flag of aventara ${option.commands.join(" and ")}, not aventara ${command}; ${HELP}`);
145
+ throw new AvCliCommandError(`${option.flag} is a flag of aventara ${option.commands.join(" and ")}, not aventara ${command}; ${HELP}`);
146
146
  }
147
147
  if (seen.has(canonical)) {
148
- throw new CliCommandError(`${canonical} was given twice; ${HELP}`);
148
+ throw new AvCliCommandError(`${canonical} was given twice; ${HELP}`);
149
149
  }
150
150
  seen.add(canonical);
151
151
  if (question === undefined) {
152
152
  if (inline !== undefined) {
153
- throw new CliCommandError(`${canonical} takes no value; ${HELP}`);
153
+ throw new AvCliCommandError(`${canonical} takes no value; ${HELP}`);
154
154
  }
155
155
  options[option.key] = true;
156
156
  continue;
@@ -158,7 +158,7 @@ export function parseCliCommand(argv) {
158
158
  const value = inline ?? rest[index + 1];
159
159
  if (value === undefined ||
160
160
  (inline === undefined && value.startsWith("-"))) {
161
- throw new CliCommandError(`${canonical} takes a value; ${HELP}`);
161
+ throw new AvCliCommandError(`${canonical} takes a value; ${HELP}`);
162
162
  }
163
163
  if (inline === undefined) {
164
164
  index += 1;
@@ -3,7 +3,7 @@ import { E2E_SPEC, spliceContractTest } from "../apply/e2e-spec.anchor.js";
3
3
  import { CORS_ORIGINS_VARIABLE, MAIN_FILE, mainCorsInstructions, SCAFFOLD_CORS_ORIGINS, spliceMainCors, } from "../apply/main-cors.anchor.js";
4
4
  import { appendEnvEntryIfAbsent, mergeEnvFile, mergeGitignore, mergePackageManifest, mergePnpmWorkspace, } from "../apply/manifest.merger.js";
5
5
  import { APP_MODULE, } from "../project/project.inspector.js";
6
- import { SCAFFOLD_ENTRYPOINT, } from "../templates/template.registry.js";
6
+ import { SCAFFOLD_API, } from "../templates/template.registry.js";
7
7
  export const PREPARE_SCRIPT = "aventara:prepare";
8
8
  const PREPARED_SCRIPTS = [
9
9
  "build",
@@ -113,9 +113,7 @@ export function planInit(input) {
113
113
  keeping.push(...writeOf(APP_MODULE, appModule, edit.text));
114
114
  replacing.push(...writeOf(APP_MODULE, appModule, edit.text));
115
115
  const spec = input.read(E2E_SPEC);
116
- const tested = spec === undefined
117
- ? undefined
118
- : spliceContractTest(spec, SCAFFOLD_ENTRYPOINT);
116
+ const tested = spec === undefined ? undefined : spliceContractTest(spec, SCAFFOLD_API);
119
117
  if (tested !== undefined) {
120
118
  keeping.push(...writeOf(E2E_SPEC, spec, tested));
121
119
  replacing.push(...writeOf(E2E_SPEC, spec, tested));
@@ -1,8 +1,8 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import path from "node:path";
3
3
  import { lowestMajorOf } from "../catalog/range.reader.js";
4
- export class ProjectRefusedError extends Error {
5
- name = "ProjectRefusedError";
4
+ export class AvProjectRefusedError extends Error {
5
+ name = "AvProjectRefusedError";
6
6
  }
7
7
  const NEST_MAJOR = 12;
8
8
  const FIRST_UNSUPPORTED_TYPESCRIPT = 7;
@@ -23,32 +23,32 @@ function matchesFamily(pattern, name) {
23
23
  export function inspectProject(directory, catalog) {
24
24
  const manifestPath = path.join(directory, "package.json");
25
25
  if (!existsSync(manifestPath)) {
26
- throw new ProjectRefusedError(`aventara init runs in a NestJS ${NEST_MAJOR} project, and there is no package.json in ${directory}`);
26
+ throw new AvProjectRefusedError(`aventara init runs in a NestJS ${NEST_MAJOR} project, and there is no package.json in ${directory}`);
27
27
  }
28
28
  const manifestText = readFileSync(manifestPath, "utf8");
29
29
  const manifest = JSON.parse(manifestText);
30
30
  const nest = dependencyRange(manifest, "@nestjs/core");
31
31
  if (nest === undefined) {
32
- throw new ProjectRefusedError(`aventara init runs in a NestJS ${NEST_MAJOR} project, and this package.json has no @nestjs/core dependency`);
32
+ throw new AvProjectRefusedError(`aventara init runs in a NestJS ${NEST_MAJOR} project, and this package.json has no @nestjs/core dependency`);
33
33
  }
34
34
  if (lowestMajorOf(nest) !== NEST_MAJOR) {
35
- throw new ProjectRefusedError(`aventara init supports NestJS ${NEST_MAJOR} only, and this project depends on @nestjs/core ${nest}`);
35
+ throw new AvProjectRefusedError(`aventara init supports NestJS ${NEST_MAJOR} only, and this project depends on @nestjs/core ${nest}`);
36
36
  }
37
37
  const typescript = dependencyRange(manifest, "typescript");
38
38
  const typescriptMajor = typescript === undefined ? undefined : lowestMajorOf(typescript);
39
39
  if (typescriptMajor !== undefined &&
40
40
  typescriptMajor >= FIRST_UNSUPPORTED_TYPESCRIPT) {
41
- throw new ProjectRefusedError(`this project declares typescript ${typescript}, and TypeScript ${FIRST_UNSUPPORTED_TYPESCRIPT} has no classic compiler API for Aventara's generate steps to read types with; use TypeScript 6 or 5`);
41
+ throw new AvProjectRefusedError(`this project declares typescript ${typescript}, and TypeScript ${FIRST_UNSUPPORTED_TYPESCRIPT} has no classic compiler API for Aventara's generate steps to read types with; use TypeScript 6 or 5`);
42
42
  }
43
43
  const lockfiles = LOCKFILES.filter(([file]) => existsSync(path.join(directory, file))).map(([, manager]) => manager);
44
44
  const known = lockfiles.filter((manager) => manager !== "yarn");
45
45
  if (lockfiles.includes("yarn") && known.length === 0) {
46
- throw new ProjectRefusedError("this project has only a yarn.lock, and aventara init supports npm and pnpm");
46
+ throw new AvProjectRefusedError("this project has only a yarn.lock, and aventara init supports npm and pnpm");
47
47
  }
48
48
  const appModule = path.join(directory, APP_MODULE);
49
49
  if (existsSync(appModule) &&
50
50
  /\bAventaraModule\b/.test(readFileSync(appModule, "utf8"))) {
51
- throw new ProjectRefusedError(`this project is already initialized: ${APP_MODULE} uses AventaraModule`);
51
+ throw new AvProjectRefusedError(`this project is already initialized: ${APP_MODULE} uses AventaraModule`);
52
52
  }
53
53
  const dependencies = [
54
54
  ...Object.keys(manifest.dependencies ?? {}),
@@ -4,7 +4,7 @@ import { confirmConflicts } from "../apply/conflict.confirmer.js";
4
4
  import { matchInstalledOrm, matchOrmSetup, } from "../catalog/catalog.matcher.js";
5
5
  import { planInit } from "../plan/project.planner.js";
6
6
  import { detectPackageManager } from "../project/package-manager.detector.js";
7
- import { inspectProject, ProjectRefusedError, } from "../project/project.inspector.js";
7
+ import { AvProjectRefusedError, inspectProject, } from "../project/project.inspector.js";
8
8
  import { scanServices } from "../project/service.detector.js";
9
9
  import { templatesFor, } from "../templates/template.registry.js";
10
10
  import { resolveAnswers } from "../wizard/answer.resolver.js";
@@ -13,8 +13,8 @@ import { createStepReporter, } from "./step.reporter.js";
13
13
  export function printNextSteps(stdout, steps) {
14
14
  stdout(`Next:\n${steps.map((step, index) => ` ${index + 1}. ${step}`).join("\n")}\n`);
15
15
  }
16
- export class InstallFailedError extends Error {
17
- name = "InstallFailedError";
16
+ export class AvInstallFailedError extends Error {
17
+ name = "AvInstallFailedError";
18
18
  }
19
19
  export async function runInit(command, io) {
20
20
  const { progress, nextSteps } = await initProject(command, io);
@@ -88,7 +88,7 @@ export async function initProject(command, io) {
88
88
  onOutput: progress.output,
89
89
  });
90
90
  if (installed.code !== 0) {
91
- throw new InstallFailedError(`\`${packageManager} install\` exited with code ${installed.code} after the files were written; fix what it reports and run \`${packageManager} install\` again:\n${installed.stderr.trimEnd()}`);
91
+ throw new AvInstallFailedError(`\`${packageManager} install\` exited with code ${installed.code} after the files were written; fix what it reports and run \`${packageManager} install\` again:\n${installed.stderr.trimEnd()}`);
92
92
  }
93
93
  });
94
94
  }
@@ -144,25 +144,25 @@ async function prepareReuse(command, io, inspection, detectedPackageManager) {
144
144
  installCommand: `${detectedPackageManager} install`,
145
145
  });
146
146
  if (verdict.kind === "refused") {
147
- throw new ProjectRefusedError(verdict.sentence);
147
+ throw new AvProjectRefusedError(verdict.sentence);
148
148
  }
149
149
  const { entry } = verdict;
150
150
  const templates = templatesFor(entry);
151
151
  const facts = templates.readProject(io.directory, inspection.manifest);
152
152
  const problem = matchOrmSetup(entry, facts);
153
153
  if (problem !== undefined) {
154
- throw new ProjectRefusedError(problem);
154
+ throw new AvProjectRefusedError(problem);
155
155
  }
156
156
  const provider = facts.provider;
157
157
  if (facts.clientOutput === undefined) {
158
- throw new ProjectRefusedError(`this project's ${facts.generator} generator block in ${facts.schema} has no output, and the generate step reads the client from it; give it one, such as ../src/generated/${entry.orm.family}`);
158
+ throw new AvProjectRefusedError(`this project's ${facts.generator} generator block in ${facts.schema} has no output, and the generate step reads the client from it; give it one, such as ../src/generated/${entry.orm.family}`);
159
159
  }
160
160
  for (const [flag, given, found] of [
161
161
  ["--orm", command.given.orm, entry.alias],
162
162
  ["--db", command.given.db, provider],
163
163
  ]) {
164
164
  if (given !== undefined && given.trim().toLowerCase() !== found) {
165
- throw new ProjectRefusedError(`${flag} ${given} does not match this project, which already uses ${found}`);
165
+ throw new AvProjectRefusedError(`${flag} ${given} does not match this project, which already uses ${found}`);
166
166
  }
167
167
  }
168
168
  const scan = scanServices(io.directory, templates.clientShape(facts));
@@ -2,10 +2,10 @@ import { createHash } from "node:crypto";
2
2
  import { existsSync, readdirSync, readFileSync } from "node:fs";
3
3
  import path from "node:path";
4
4
  import { detectPackageManager } from "../project/package-manager.detector.js";
5
- import { ProjectRefusedError } from "../project/project.inspector.js";
5
+ import { AvProjectRefusedError } from "../project/project.inspector.js";
6
6
  import { resolveAnswers } from "../wizard/answer.resolver.js";
7
7
  import { NEW_QUESTIONS } from "../wizard/wizard.questions.js";
8
- import { InstallFailedError, initProject, printNextSteps, } from "./init.orchestrator.js";
8
+ import { AvInstallFailedError, initProject, printNextSteps, } from "./init.orchestrator.js";
9
9
  import { commitScaffold, gitSurroundings } from "./scaffold.committer.js";
10
10
  import { createStepReporter } from "./step.reporter.js";
11
11
  export const NEST_CLI = "@nestjs/cli@12.0.8";
@@ -34,7 +34,7 @@ export async function runNew(command, io) {
34
34
  }
35
35
  const directory = path.join(io.cwd, name);
36
36
  if (existsSync(directory) && readdirSync(directory).length > 0) {
37
- throw new ProjectRefusedError(`aventara new creates ${directory}, which already exists and is not empty; choose another name, or run aventara init inside an existing project`);
37
+ throw new AvProjectRefusedError(`aventara new creates ${directory}, which already exists and is not empty; choose another name, or run aventara init inside an existing project`);
38
38
  }
39
39
  const surroundings = command.skipGit
40
40
  ? undefined
@@ -63,7 +63,7 @@ export async function runNew(command, io) {
63
63
  onOutput: progress.output,
64
64
  });
65
65
  if (created.code !== 0) {
66
- throw new InstallFailedError(`\`${runner} ${args.join(" ")}\` exited with code ${created.code}, and nothing of Aventara's was written; fix what it reports and run aventara new again:\n${created.stderr.trimEnd()}`);
66
+ throw new AvInstallFailedError(`\`${runner} ${args.join(" ")}\` exited with code ${created.code}, and nothing of Aventara's was written; fix what it reports and run aventara new again:\n${created.stderr.trimEnd()}`);
67
67
  }
68
68
  });
69
69
  const appModule = path.join(directory, "src", "app.module.ts");
@@ -71,7 +71,7 @@ export async function runNew(command, io) {
71
71
  ? createHash("sha256").update(readFileSync(appModule)).digest("hex")
72
72
  : "(absent)";
73
73
  if (digest !== NEST_APP_MODULE_SHA256) {
74
- throw new InstallFailedError(`nest new's output changed: ${appModule} is not what ${NEST_CLI} writes (sha256 ${digest}), so nothing of Aventara's was written over it; run aventara init inside ${directory} to add Aventara to it`);
74
+ throw new AvInstallFailedError(`nest new's output changed: ${appModule} is not what ${NEST_CLI} writes (sha256 ${digest}), so nothing of Aventara's was written over it; run aventara init inside ${directory} to add Aventara to it`);
75
75
  }
76
76
  const { nextSteps } = await initProject({
77
77
  command: "init",
@@ -1,2 +1,3 @@
1
- export const DOCS_BASE_URL = "https://aventara-docs.vercel.app";
1
+ export const DOCS_BASE_URL = "https://aventara-framework.vercel.app";
2
+ export const EXPOSURE_DOCS = `${DOCS_BASE_URL}/docs/exposure`;
2
3
  export const RATE_LIMITING_DOCS = `${DOCS_BASE_URL}/docs/limits-and-safety#rate-limiting`;
@@ -1,7 +1,8 @@
1
1
  import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
2
2
  import path from "node:path";
3
3
  import { lowestVersionOf } from "../../catalog/range.reader.js";
4
- import { SCAFFOLD_ENTRYPOINT, } from "../template.registry.js";
4
+ import { EXPOSURE_DOCS } from "../docs.links.js";
5
+ import { SCAFFOLD_API, } from "../template.registry.js";
5
6
  const CLIENT_OUTPUT = "src/generated/prisma";
6
7
  const SCHEMA = "prisma/schema.prisma";
7
8
  const CONFIG_FILES = ["prisma7.config.ts", "prisma.config.ts"];
@@ -111,18 +112,38 @@ function aventaraConfig(input) {
111
112
  const file = "src/aventara.config.ts";
112
113
  const service = input.service.exportName;
113
114
  return [
115
+ "import type { AvDiagnostic } from '@aventara/core';",
114
116
  `import { createPrismaAdapter } from '${input.entry.adapterPackage}';`,
117
+ "import { Logger } from '@nestjs/common';",
115
118
  `import { discovery } from '${specifier(file, input.artifact.replace(/\.ts$/, ""), input.js)}';`,
116
119
  `import type { ${service} } from '${specifier(file, input.service.file.replace(/\.ts$/, ""), input.js)}';`,
117
120
  "",
121
+ "const logger = new Logger('Aventara');",
122
+ "",
118
123
  "/**",
119
- " * Aventara's configuration: the entrypoint, and the restrictions and",
120
- " * pipelines you add. AventaraModule (app.module.ts) builds the framework from",
121
- " * it; `discovery` is regenerated by the aventara:prepare script.",
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.",
122
129
  " */",
123
130
  `export async function aventaraConfig(prisma: ${service}) {`,
124
131
  " return {",
125
- ` entrypoint: '${SCAFFOLD_ENTRYPOINT}',`,
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
+ " },",
126
147
  " adapter: await createPrismaAdapter({",
127
148
  input.service.clientMember === undefined
128
149
  ? " client: prisma,"
@@ -1,5 +1,5 @@
1
1
  import { PRISMA7_TEMPLATES } from "./prisma7/prisma7.templates.js";
2
- export const SCAFFOLD_ENTRYPOINT = "/api";
2
+ export const SCAFFOLD_API = "/api";
3
3
  export const TEMPLATE_REGISTRY = {
4
4
  prisma7: PRISMA7_TEMPLATES,
5
5
  };
@@ -1,6 +1,6 @@
1
- import { WizardAnswerError } from "./wizard.questions.js";
2
- export class UnansweredQuestionsError extends Error {
3
- name = "UnansweredQuestionsError";
1
+ import { AvWizardAnswerError } from "./wizard.questions.js";
2
+ export class AvUnansweredQuestionsError extends Error {
3
+ name = "AvUnansweredQuestionsError";
4
4
  }
5
5
  function flagSpelling(question, context) {
6
6
  if (!question.flag.startsWith("--")) {
@@ -41,7 +41,7 @@ async function prompt(question, context, prompter) {
41
41
  return question.parse(raw, context);
42
42
  }
43
43
  catch (error) {
44
- if (!(error instanceof WizardAnswerError)) {
44
+ if (!(error instanceof AvWizardAnswerError)) {
45
45
  throw error;
46
46
  }
47
47
  prompter.say(`${error.message}.`);
@@ -90,7 +90,7 @@ export async function resolveAnswers(questions, sources) {
90
90
  });
91
91
  }
92
92
  if (missing.length > 0) {
93
- throw new UnansweredQuestionsError(unansweredSentence(missing, sources.yes));
93
+ throw new AvUnansweredQuestionsError(unansweredSentence(missing, sources.yes));
94
94
  }
95
95
  return answered;
96
96
  }
@@ -1,13 +1,13 @@
1
1
  import { createInterface } from "node:readline";
2
- export class WizardCancelledError extends Error {
3
- name = "WizardCancelledError";
2
+ export class AvWizardCancelledError extends Error {
3
+ name = "AvWizardCancelledError";
4
4
  }
5
5
  export function createReadlinePrompter(streams) {
6
6
  let session;
7
7
  let ended = false;
8
8
  const lines = [];
9
9
  const waiting = [];
10
- const cancelled = () => new WizardCancelledError("the wizard was closed before it was answered");
10
+ const cancelled = () => new AvWizardCancelledError("the wizard was closed before it was answered");
11
11
  const open = () => {
12
12
  if (session === undefined) {
13
13
  session = createInterface({
@@ -1,7 +1,7 @@
1
1
  import { modulesProviding, } from "../project/service.detector.js";
2
2
  export const PACKAGE_MANAGERS = ["npm", "pnpm"];
3
- export class WizardAnswerError extends Error {
4
- name = "WizardAnswerError";
3
+ export class AvWizardAnswerError extends Error {
4
+ name = "AvWizardAnswerError";
5
5
  }
6
6
  function ormLabel(entry) {
7
7
  return `${entry.orm.label} ${entry.orm.major}`;
@@ -32,7 +32,7 @@ export const NAME_QUESTION = {
32
32
  parse: (raw) => {
33
33
  const name = raw.trim();
34
34
  if (!NAME.test(name)) {
35
- throw new WizardAnswerError(`${JSON.stringify(raw)} is not a project name: use letters, digits, ".", "_" and "-", starting with a letter or digit`);
35
+ throw new AvWizardAnswerError(`${JSON.stringify(raw)} is not a project name: use letters, digits, ".", "_" and "-", starting with a letter or digit`);
36
36
  }
37
37
  return name;
38
38
  },
@@ -52,7 +52,7 @@ export const ORM_QUESTION = {
52
52
  const alias = raw.trim().toLowerCase();
53
53
  const entry = context.catalog.find((candidate) => candidate.alias === alias);
54
54
  if (entry === undefined) {
55
- throw new WizardAnswerError(`there is no Aventara adapter for the ORM ${JSON.stringify(raw)}; ${ormAliasesSentence(context.catalog)}`);
55
+ throw new AvWizardAnswerError(`there is no Aventara adapter for the ORM ${JSON.stringify(raw)}; ${ormAliasesSentence(context.catalog)}`);
56
56
  }
57
57
  return entry;
58
58
  },
@@ -82,7 +82,7 @@ export const DB_QUESTION = {
82
82
  const orm = requireOrm(context);
83
83
  const db = raw.trim().toLowerCase();
84
84
  if (!orm.providers.includes(db)) {
85
- throw new WizardAnswerError(`the ${ormLabel(orm)} adapter does not support the database ${JSON.stringify(raw)}; choose ${orm.providers.join(" or ")}`);
85
+ throw new AvWizardAnswerError(`the ${ormLabel(orm)} adapter does not support the database ${JSON.stringify(raw)}; choose ${orm.providers.join(" or ")}`);
86
86
  }
87
87
  return db;
88
88
  },
@@ -98,7 +98,7 @@ export const PACKAGE_MANAGER_QUESTION = {
98
98
  parse: (raw) => {
99
99
  const manager = PACKAGE_MANAGERS.find((candidate) => candidate === raw.trim().toLowerCase());
100
100
  if (manager === undefined) {
101
- throw new WizardAnswerError(`${JSON.stringify(raw)} is not a supported package manager; choose ${PACKAGE_MANAGERS.join(" or ")}`);
101
+ throw new AvWizardAnswerError(`${JSON.stringify(raw)} is not a supported package manager; choose ${PACKAGE_MANAGERS.join(" or ")}`);
102
102
  }
103
103
  return manager;
104
104
  },
@@ -109,7 +109,7 @@ function reference(raw, flag) {
109
109
  const exportName = raw.slice(hash + 1).trim();
110
110
  const file = raw.slice(0, hash).trim().replace(/^\.\//, "");
111
111
  if (hash <= 0 || !/^[A-Za-z_$][\w$]*$/.test(exportName)) {
112
- throw new WizardAnswerError(`${flag} takes <path#Export>, a file and the name it exports, such as src/prisma.service.ts#PrismaService; got ${JSON.stringify(raw)}`);
112
+ throw new AvWizardAnswerError(`${flag} takes <path#Export>, a file and the name it exports, such as src/prisma.service.ts#PrismaService; got ${JSON.stringify(raw)}`);
113
113
  }
114
114
  return { file, exportName };
115
115
  }
@@ -166,7 +166,7 @@ export const SERVICE_QUESTION = {
166
166
  const options = serviceOptions(services);
167
167
  const chosen = options.find((option) => option.file === wanted.file && option.exportName === wanted.exportName);
168
168
  if (chosen === undefined) {
169
- throw new WizardAnswerError(`${spelled(wanted)} is not a service found in src/ (a class extending ${services.clientClass}, or one holding it); found: ${options
169
+ throw new AvWizardAnswerError(`${spelled(wanted)} is not a service found in src/ (a class extending ${services.clientClass}, or one holding it); found: ${options
170
170
  .filter((option) => !option.isNew)
171
171
  .map(spelled)
172
172
  .join(", ") || "none"}`);
@@ -202,7 +202,7 @@ export const MODULE_QUESTION = {
202
202
  : modulesProviding(services.scan, service);
203
203
  const chosen = modules.find((module) => module.file === wanted.file && module.exportName === wanted.exportName);
204
204
  if (chosen === undefined) {
205
- throw new WizardAnswerError(`${spelled(wanted)} is not a module that provides and exports ${service.exportName}; found: ${modules.map(spelled).join(", ") || "none"}`);
205
+ throw new AvWizardAnswerError(`${spelled(wanted)} is not a module that provides and exports ${service.exportName}; found: ${modules.map(spelled).join(", ") || "none"}`);
206
206
  }
207
207
  return { file: chosen.file, exportName: chosen.exportName };
208
208
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aventara/cli",
3
- "version": "1.0.0-rc.0",
3
+ "version": "1.0.0-rc.1",
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",