@aventara/client 0.1.0-pilot.3 → 1.0.0-rc.0
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 +222 -0
- package/README.md +29 -14
- package/dist/cli/command.parser.d.ts +4 -14
- package/dist/cli/command.parser.js +12 -1
- package/dist/cli/generate.command.d.ts +6 -1
- package/dist/cli/generate.command.js +12 -1
- package/dist/cli/generation-success.renderer.d.ts +0 -4
- package/dist/cli/step.reporter.d.ts +31 -0
- package/dist/cli/step.reporter.js +87 -0
- package/dist/cli/terminal.prompter.d.ts +0 -6
- package/dist/cli/warning.renderer.d.ts +0 -6
- package/dist/cli.d.ts +0 -16
- package/dist/cli.js +2 -0
- package/dist/config/client-config.interface.d.ts +10 -20
- package/dist/config/config.loader.d.ts +0 -21
- package/dist/config/config.resolver.d.ts +2 -17
- package/dist/config/env.cascade.d.ts +0 -30
- package/dist/config/module-style.resolver.d.ts +0 -25
- package/dist/config/tsconfig.locator.d.ts +0 -24
- package/dist/contract/contract.acceptance.d.ts +1 -40
- package/dist/contract/contract.acceptance.js +1 -1
- package/dist/contract/contract.fetcher.d.ts +0 -29
- package/dist/contract/contract.loader.d.ts +0 -7
- package/dist/emit/banner.emitter.d.ts +0 -18
- package/dist/emit/banner.emitter.js +1 -1
- package/dist/emit/client-surface.emitter.d.ts +0 -23
- package/dist/emit/client-surface.emitter.js +17 -6
- package/dist/emit/client-tree.emitter.d.ts +0 -20
- package/dist/emit/contract-carrier.emitter.d.ts +1 -8
- package/dist/emit/contract-carrier.emitter.js +1 -1
- package/dist/emit/derivation.emitter.d.ts +0 -28
- package/dist/emit/descriptor.emitter.d.ts +1 -1
- package/dist/emit/descriptor.emitter.js +1 -1
- package/dist/emit/emitted-tree.interface.d.ts +0 -53
- package/dist/emit/enum.emitter.d.ts +0 -20
- package/dist/emit/json-shape.emitter.d.ts +16 -0
- package/dist/emit/json-shape.emitter.js +88 -0
- package/dist/emit/module-specifier.scanner.d.ts +0 -15
- package/dist/emit/module-style.interface.d.ts +0 -15
- package/dist/emit/name.deriver.d.ts +1 -72
- package/dist/emit/name.deriver.js +1 -1
- package/dist/emit/named-type.emitter.d.ts +0 -20
- package/dist/emit/runtime.emitter.d.ts +1 -51
- package/dist/emit/runtime.emitter.js +15 -22
- package/dist/emit/scalar.codec.d.ts +0 -39
- package/dist/emit/scalar.codec.js +56 -13
- package/dist/emit/transaction.emitter.d.ts +0 -6
- package/dist/emit/transaction.emitter.js +112 -78
- package/dist/generate.d.ts +8 -36
- package/dist/generate.js +38 -24
- package/dist/index.d.ts +1 -5
- package/dist/init/client-config.template.d.ts +0 -8
- package/dist/init/client-init.orchestrator.js +31 -16
- package/dist/init/client-init.planner.d.ts +0 -1
- package/dist/init/client-init.questions.d.ts +0 -8
- package/dist/init/command.runner.d.ts +2 -0
- package/dist/init/command.runner.js +4 -1
- package/dist/output/output.validator.d.ts +0 -39
- package/dist/output/output.writer.d.ts +1 -110
- package/package.json +6 -5
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# @aventara/client
|
|
2
|
+
|
|
3
|
+
## 1.0.0-rc.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- First release candidate of Aventara 1.0. The public API is now considered stable; changes before 1.0.0 are possible
|
|
8
|
+
based on feedback.
|
|
9
|
+
|
|
10
|
+
### Minor Changes
|
|
11
|
+
|
|
12
|
+
- 24ffbab: A `Json` field can now have a type. Build its shape with `AvZ` from the new entry `@aventara/core/json` and declare it
|
|
13
|
+
on the field: `fields: { Post: { meta: { json: AvZ.object({ title: AvZ.string() }) } } }`. `framework.application`,
|
|
14
|
+
`framework.client`, `appTx`/`clientTx` and the generated client then read and write the field as that type instead of
|
|
15
|
+
generic JSON, and every write to it — create, update, upsert, nested writes and transaction steps, a value taken from
|
|
16
|
+
an earlier step included — is checked before the database is asked: a value out of shape is refused with `A2004` and
|
|
17
|
+
one issue per problem at its exact path (`V1000` missing key, `V1001` wrong type, `V1002` outside an enum or literal,
|
|
18
|
+
`V1004` integer out of range, `V1005` unknown key). Shapes are types only and their objects are closed; reads and
|
|
19
|
+
filters are never checked. Only a field the adapter discovered as `Json` takes a shape, and a Zod schema is not
|
|
20
|
+
accepted. The shape is part of the ClientContract (as JSON Schema), so declaring or changing one changes its hash:
|
|
21
|
+
regenerate the client. `@aventara/core` now depends on `zod` (`^4.6.5`), which only `@aventara/core/json` loads. The
|
|
22
|
+
shape types `AvZShape`, `AvZObject`, `AvZOptional` and `AvZNullable` are exported too, so a project that emits
|
|
23
|
+
declarations can export a shape or a configuration holding one.
|
|
24
|
+
- 24ffbab: Transaction steps can now reference earlier results in `$connect`, `$disconnect`, `$set`, `$connectOrCreate`'s `where`,
|
|
25
|
+
a `cursor`, a unique `where`, anywhere in a filter, and at any depth of nested relation writes. The generated client binds a `$ref` wherever a step's arguments
|
|
26
|
+
hold one, and its types accept one exactly where the server does: of the field's own type, nullable only for a filter's
|
|
27
|
+
equality on a nullable field, a list field for `in`, `notIn`, `hasEvery` and `hasSome`. Regenerate the client to use them.
|
|
28
|
+
- 24ffbab: `aventara new`, `aventara init`, `avclient init` and `avclient generate` show their steps while they work, so a long
|
|
29
|
+
install never looks hung: on a terminal, `[3/4] Installing dependencies (npm install)` with a spinner and the seconds so
|
|
30
|
+
far, then `✓` or `✗` and its time; in a pipe, in CI or with `NO_COLOR`, one plain line per step (`… done (12.4s)`),
|
|
31
|
+
with no control sequence. A closing line gives the total time. The steps go to stderr; stdout keeps the results and the
|
|
32
|
+
next steps. The output of `nest new` and of the install is shown only when it fails, as before, or as it runs with the
|
|
33
|
+
new `--verbose`.
|
|
34
|
+
- 24ffbab: A nested `$update` or `$delete` through a to-many relation filters plain fields only on Prisma 7, and Aventara now says
|
|
35
|
+
so up front: a relation filter in its `where` — `{ posts: { $update: { where: { category: { name: "x" } }, … } } }`, at
|
|
36
|
+
any depth, under `AND`/`OR`/`NOT` too — is refused with `A2004` / `V1006` before the database is asked, and is a type
|
|
37
|
+
error in `framework.application`, `framework.client`, `appTx`/`clientTx` and the generated client. Before, it reached
|
|
38
|
+
Prisma and fail as `A3001`. A to-one relation's nested filter keeps relation filters. The limit is stated by the adapter
|
|
39
|
+
in the Contract (`nestedWriteFilter: "scalar"`), so the ClientContract hash changes: regenerate the client.
|
|
40
|
+
|
|
41
|
+
### Patch Changes
|
|
42
|
+
|
|
43
|
+
- 24ffbab: Each package ships its `CHANGELOG.md`, so the release notes are in the installed package beside the README.
|
|
44
|
+
- 24ffbab: **Behaviour changes:** server-side calls now copy what you pass them. `framework.application`, `framework.client`,
|
|
45
|
+
`framework.execute`, `framework.executeTransaction` and the `appTx`/`clientTx` builders read your arguments once, when
|
|
46
|
+
you call them, into plain data the framework owns; validation, guards, pipes, computed values and the adapter see only
|
|
47
|
+
that copy, so changing or revoking your object afterwards (an Immer draft after `produce`) no longer affects the
|
|
48
|
+
operation. A live Proxy (Vue `reactive`, MobX) still works.
|
|
49
|
+
|
|
50
|
+
A value that cannot be read — a revoked Proxy, a getter that throws — or that is not plain data — a class instance or a
|
|
51
|
+
`Map`; `Date`, `Decimal` and `Uint8Array` are fine — is now refused as your request's error: `A2004` with one `V1001` at
|
|
52
|
+
the value's path (`["arguments", …]`, or `["operations", i, "args", …]` with `cause.operation` in a transaction), before
|
|
53
|
+
any guard, pipe or adapter runs. Before, it was `A3000` (an internal error) or, from `appTx`/`clientTx`, a `TypeError`
|
|
54
|
+
thrown at the builder call; a builder now never throws, and `framework.transaction([...])` answers the refusal. The same
|
|
55
|
+
holds for a `framework.execute` / `executeTransaction` request object that cannot be read. Two answers change for
|
|
56
|
+
inputs that were never supported: a class instance where an object of arguments belongs (`where`, `data`, …) was
|
|
57
|
+
accepted or failed as `A3000`, and is now refused; inherited, non-enumerable and symbol keys are dropped, as JSON drops
|
|
58
|
+
them.
|
|
59
|
+
|
|
60
|
+
New optional root setting `misuse` (types `FrameworkMisuseSink`, `FrameworkMisuseEvent`), beside `diagnostics`: it
|
|
61
|
+
receives one event per refused value — its `kind` (`"unreadable-value"` or `"non-plain-value"`), the `path` the caller
|
|
62
|
+
received, and the operation's `origin`, `resource`, `family`, `variant` and transaction step (`operation`) when they
|
|
63
|
+
could be read — never the value itself. It cannot change a response; an error it throws or a promise it rejects is
|
|
64
|
+
ignored. `diagnostics` keeps receiving only unexpected failures.
|
|
65
|
+
|
|
66
|
+
HTTP requests are not copied again: a body the protocol parses from raw content is already plain data.
|
|
67
|
+
|
|
68
|
+
The generated client reads your arguments through one guard too: a value that cannot be read is refused with a
|
|
69
|
+
`TypeError` naming where (`… the value at /where/id could not be read.`) before anything is sent, and a `tx` builder
|
|
70
|
+
never throws — `transaction([...])` rejects with that error instead. Regenerate the client to pick this up.
|
|
71
|
+
|
|
72
|
+
- 032f792: The generated client's `transaction([...])` refuses an entry that is not one of its `tx` handles, or one handle
|
|
73
|
+
placed twice, with the path core answers for it: `["operations", i]`, the entry itself (was `["operation"]`).
|
|
74
|
+
Regenerate the client to pick this up.
|
|
75
|
+
- Updated dependencies [24ffbab]
|
|
76
|
+
- Updated dependencies [24ffbab]
|
|
77
|
+
- Updated dependencies [24ffbab]
|
|
78
|
+
- Updated dependencies [24ffbab]
|
|
79
|
+
- Updated dependencies [24ffbab]
|
|
80
|
+
- Updated dependencies [24ffbab]
|
|
81
|
+
- Updated dependencies [24ffbab]
|
|
82
|
+
- Updated dependencies [24ffbab]
|
|
83
|
+
- Updated dependencies [24ffbab]
|
|
84
|
+
- Updated dependencies [24ffbab]
|
|
85
|
+
- Updated dependencies [24ffbab]
|
|
86
|
+
- Updated dependencies [24ffbab]
|
|
87
|
+
- Updated dependencies [a973e89]
|
|
88
|
+
- Updated dependencies [1e65e9a]
|
|
89
|
+
- Updated dependencies
|
|
90
|
+
- Updated dependencies [a973e89]
|
|
91
|
+
- Updated dependencies [a973e89]
|
|
92
|
+
- Updated dependencies [1e65e9a]
|
|
93
|
+
- Updated dependencies [1e65e9a]
|
|
94
|
+
- Updated dependencies [24ffbab]
|
|
95
|
+
- Updated dependencies [2d00ad7]
|
|
96
|
+
- Updated dependencies [384a50c]
|
|
97
|
+
- Updated dependencies [d43d1cc]
|
|
98
|
+
- Updated dependencies [3f98749]
|
|
99
|
+
- Updated dependencies [032f792]
|
|
100
|
+
- Updated dependencies [dbe6961]
|
|
101
|
+
- @aventara/core@1.0.0-rc.0
|
|
102
|
+
|
|
103
|
+
## 0.1.0-pilot.4
|
|
104
|
+
|
|
105
|
+
### Patch Changes
|
|
106
|
+
|
|
107
|
+
- f37dcd6: The documentation shipped with the package — the comments an editor shows on hover, and the README — now describes
|
|
108
|
+
what each export does, how to use it, what it returns and what it throws. Notes about how the package was built and
|
|
109
|
+
why are no longer part of what is published.
|
|
110
|
+
- Updated dependencies [f37dcd6]
|
|
111
|
+
- @aventara/core@0.1.0-pilot.4
|
|
112
|
+
|
|
113
|
+
## 0.1.0-pilot.3
|
|
114
|
+
|
|
115
|
+
### Patch Changes
|
|
116
|
+
|
|
117
|
+
- 0c4b352: `avclient init` adds `.env` to the frontend's `.gitignore` whenever it puts the entrypoint variable in `.env`: Vite's
|
|
118
|
+
`.gitignore` lists only `*.local`, so the `.env` init wrote was committed. The file is only appended to — every line
|
|
119
|
+
kept, nothing added when `.env` is already listed — and created where the project has none, as `aventara init` does on
|
|
120
|
+
the server. The generated client itself is still committed.
|
|
121
|
+
- ac1ae3a: `avclient init` refuses a project with no `tsconfig.json` before it writes or installs anything. 0.1.0-pilot.2 wrote
|
|
122
|
+
`framework.client.ts`, the `.env` entry, the script and the devDependency, ran the install, and only then had the first
|
|
123
|
+
generation refuse the JavaScript project. The check is the one `avclient generate` makes — the same search from
|
|
124
|
+
`generateAt` up, the same sentence — and it runs once the questions are answered, before anything is written.
|
|
125
|
+
- c34e65d: `avclient init` and `avclient generate` no longer print Node's `ExperimentalWarning: stripTypeScriptTypes is an
|
|
126
|
+
experimental feature`. With your project's `typescript` the output is type-checked and Node's parser is never called;
|
|
127
|
+
without it, the degraded check still parses with Node's parser and says so in its own warning, and only that one Node
|
|
128
|
+
warning is held back while it parses.
|
|
129
|
+
- 5ab40b2: The generated client is type-checked with your project's own `typescript`, resolved from `generateAt`, not with one
|
|
130
|
+
installed beside `@aventara/client`. Run through `npx @aventara/client init`, the generator lives in npm's cache, where
|
|
131
|
+
its optional peer is never installed, so 0.1.0-pilot.2 warned "`typescript` could not be resolved … NOT type-checked"
|
|
132
|
+
and reported `(checked: syntax)` in a project that had TypeScript. It now reports `(checked: types)`. The degraded check
|
|
133
|
+
runs only when the project has no `typescript`, or one it cannot check with: TypeScript 7 (no classic compiler API), or
|
|
134
|
+
one older than 5.5, the peer range's floor — each said in its own warning.
|
|
135
|
+
- @aventara/core@0.1.0-pilot.3
|
|
136
|
+
|
|
137
|
+
## 0.1.0-pilot.2
|
|
138
|
+
|
|
139
|
+
### Patch Changes
|
|
140
|
+
|
|
141
|
+
- 80527cf: The generated client is still TypeScript source — `AvClient.ts` and `generated/` — compiled by your own toolchain, but
|
|
142
|
+
its internal imports are now written the way your project imports its own files, read from your `tsconfig.json` by
|
|
143
|
+
Prisma 7's rules: extensionless under `moduleResolution: "bundler"` (Next.js with Turbopack or webpack) or `module: "commonjs"`,
|
|
144
|
+
`.js` under `nodenext` (NestJS 12), `.ts` with `allowImportingTsExtensions` (Vite's react-ts template, Node's type
|
|
145
|
+
stripping). So
|
|
146
|
+
Next.js 16's Turbopack, which could not resolve 0.1.0-pilot.1's client, builds it, and you import it as you import your
|
|
147
|
+
own files: `./api/AvClient`, `./api/AvClient.js` in a `nodenext` ES module project, `./api/AvClient.ts` where your
|
|
148
|
+
imports name `.ts` files. A project with no
|
|
149
|
+
`tsconfig.json` is refused with a sentence saying so: JavaScript projects are not supported yet. The client also
|
|
150
|
+
type-checks under `"strict": false` (the tsconfig Next.js writes itself), where 0.1.0-pilot.1's transport did not.
|
|
151
|
+
|
|
152
|
+
The config is loaded the way Prisma 7 loads `prisma.config.ts` (c12 over jiti): `framework.client.ts` — or `.mts`,
|
|
153
|
+
`.cts`, `.js`, `.mjs`, `.cjs`, in ES module or CommonJS syntax, in any project. `avclient init` writes
|
|
154
|
+
`framework.client.ts`; a 0.1.0-pilot.1 `framework.client.mts` keeps working and is reused. Two config files are refused.
|
|
155
|
+
|
|
156
|
+
- e69e88d: The published packages are written for the people who use them. The JavaScript ships without comments; the type
|
|
157
|
+
declarations keep their documentation, and it, the error messages, the protocol fixtures' labels and the comments in a
|
|
158
|
+
generated client no longer carry the project's internal development notes.
|
|
159
|
+
- ab58424: A pipe's output is checked against the server's own rules before it reaches the adapter. The request is validated
|
|
160
|
+
against the caller's Contract; after each pipe, the arguments are validated against the Core Contract (plus fields the
|
|
161
|
+
caller's scope adds), so a pipe may still write what the caller cannot — a field the client Contract hides, such as a
|
|
162
|
+
password hash — but not what the server cannot store. A refusal is the new internal code `A3004 PIPE_OUTPUT_INVALID` (HTTP
|
|
163
|
+
500). The caller receives only the generic `A server pipeline produced invalid arguments.`, like every internal
|
|
164
|
+
failure; the pipe (`pipes[i]` and its function name), the operation, the offending field and its V-code go to the
|
|
165
|
+
diagnostics sink as a `PipeOutputError`, and `@aventara/nest` writes them into its error log line. Nothing reaches the
|
|
166
|
+
adapter. This holds for operations and transaction steps alike (a plan answers with `cause.operation`). Generated
|
|
167
|
+
clients map `A3004` to `InternalError`; the protocol fixtures in `@aventara/testing` include an `A3004` row.
|
|
168
|
+
- 0ec37d4: `avclient --version` (and `-v`) prints the generator's version and exits 0, and `aventara` now accepts `-v` beside
|
|
169
|
+
`--version`, so both bins answer the same two flags.
|
|
170
|
+
- Updated dependencies [c2a3b7c]
|
|
171
|
+
- Updated dependencies [e69e88d]
|
|
172
|
+
- Updated dependencies [ab58424]
|
|
173
|
+
- Updated dependencies [23937f8]
|
|
174
|
+
- @aventara/core@0.1.0-pilot.2
|
|
175
|
+
|
|
176
|
+
## 0.1.0-pilot.1
|
|
177
|
+
|
|
178
|
+
### Patch Changes
|
|
179
|
+
|
|
180
|
+
- d5bd6eb: A CommonJS frontend works. `npm init -y` writes `"type": "commonjs"`, under which Node read `framework.client.ts` as
|
|
181
|
+
CommonJS and `avclient generate` stopped with "Cannot use import statement outside a module". `avclient init` now writes
|
|
182
|
+
`framework.client.mts`, an ES module in every project, and moves an existing `framework.client.ts` to it (asking first if
|
|
183
|
+
it was edited). `avclient generate` reads `framework.client.mts`, still reads `framework.client.ts`, refuses in one
|
|
184
|
+
sentence when both are present, and tells a `.ts` that fails to evaluate to rename itself.
|
|
185
|
+
- 4a12683: TypeScript 7 no longer blocks a frontend. The optional `typescript` peer is `>=5.5.0` with no upper bound, so `npm i`
|
|
186
|
+
in a project on `typescript` 7.x no longer fails with `ERESOLVE`. Under a `typescript` without the classic compiler API
|
|
187
|
+
(`createProgram`), `avclient generate` no longer refuses: it degrades to the syntax check with one warning naming the
|
|
188
|
+
version, and writes the client — which then type-checks under the project's own `tsc` 7.
|
|
189
|
+
- 799346e: The package READMEs (each package's npm page) are rewritten for the people who use the packages. The Prisma 7
|
|
190
|
+
adapter's README now states the actual behaviour of a to-many relation or `$count` projected on
|
|
191
|
+
`update.many`/`create.many`: a validation refusal, `A2004` with `V1008`, before any statement runs (it said `A3001`).
|
|
192
|
+
Every install or run command names the `@pilot` dist-tag.
|
|
193
|
+
- 88408be: `--help` (and `-h`) works after a command: `aventara new --help`, `aventara init --help`, `avclient init --help` and
|
|
194
|
+
`avclient generate --help` print that command's usage and exit 0, instead of refusing "unknown flag --help". Nothing
|
|
195
|
+
else on the line is read or run.
|
|
196
|
+
- Updated dependencies [a45f8a8]
|
|
197
|
+
- Updated dependencies [799346e]
|
|
198
|
+
- @aventara/core@0.1.0-pilot.1
|
|
199
|
+
|
|
200
|
+
## 0.1.0-pilot.0
|
|
201
|
+
|
|
202
|
+
### Minor Changes
|
|
203
|
+
|
|
204
|
+
- First pilot release of `@aventara/client`, the development-time client generator, bin `avclient`.
|
|
205
|
+
|
|
206
|
+
- `avclient init` sets up a frontend — `framework.client.ts`, the `.env` entry, the `avclient:generate` script and an
|
|
207
|
+
exact `@aventara/client` devDependency — then installs and generates.
|
|
208
|
+
- `avclient generate` reads the server's ClientContract and writes a standalone typed client that your application
|
|
209
|
+
imports; the generated code needs no `@aventara/client` at runtime. A client generated for an older contract fails
|
|
210
|
+
with a message telling you to run `avclient generate`. Messages are prefixed `avclient:`.
|
|
211
|
+
- The client's entrypoint follows the server's rule: `"api"`, `"/api/"` and `"//api//"` all mean `"/api"`, and a mount
|
|
212
|
+
path holding a backslash, a `.` segment, a non-ASCII character, a `%` or another character not allowed in a URL path
|
|
213
|
+
segment is refused rather than silently rewritten.
|
|
214
|
+
- `bigint`, `decimal`, `datetime` and `bytes` travel in the server's wire forms, and only canonical base64 is accepted
|
|
215
|
+
for `bytes`.
|
|
216
|
+
- At this release the `typescript` peer is `>=5.5.0 <7`, and TypeScript 7 is refused in one sentence (0.1.0-pilot.1
|
|
217
|
+
lifts this).
|
|
218
|
+
|
|
219
|
+
### Patch Changes
|
|
220
|
+
|
|
221
|
+
- Updated dependencies
|
|
222
|
+
- @aventara/core@0.1.0-pilot.0
|
package/README.md
CHANGED
|
@@ -6,12 +6,12 @@ client is the product: it imports nothing from this package, or from `@aventara/
|
|
|
6
6
|
|
|
7
7
|
Every argument and result type in the generated client is core's own derivation — `@aventara/core`'s published
|
|
8
8
|
declarations, copied into the tree and instantiated over the deployment's ClientContract — so the client agrees with the
|
|
9
|
-
server by construction
|
|
9
|
+
server by construction.
|
|
10
10
|
|
|
11
11
|
## Setting up a frontend: `avclient init`
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
npx @aventara/client@
|
|
14
|
+
npx @aventara/client@rc init # in your frontend project; pnpm: pnpm dlx @aventara/client@rc init
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
It asks — or takes from flags, or with `--yes` takes every default — four things, and writes the setup:
|
|
@@ -26,7 +26,9 @@ It asks — or takes from flags, or with `--yes` takes every default — four th
|
|
|
26
26
|
It writes `framework.client.ts` (or reuses the config the project already has), the variable into `.env` (only with a variable), `"avclient:generate": "avclient
|
|
27
27
|
generate"` into the scripts and `@aventara/client` as an **exact** devDependency at its own version; runs the install
|
|
28
28
|
(`--skip-install` prints it instead); and generates the client right away (`--skip-generate` to skip; a server that does
|
|
29
|
-
not answer leaves the files and names `avclient generate`
|
|
29
|
+
not answer leaves the files and names `avclient generate`; `--verbose` shows the install's output as it runs). Each step
|
|
30
|
+
— the install, loading the config, fetching the ClientContract, emitting, checking and writing — is shown on stderr
|
|
31
|
+
while it runs: live on a terminal, one plain line per step in a pipe, in CI or with `NO_COLOR`. Existing content that differs is listed and replaced only
|
|
30
32
|
when you confirm, or with `--yes`; where nobody can be asked, nothing is touched. The generated tree is meant to be
|
|
31
33
|
committed: regenerating it needs a running server.
|
|
32
34
|
|
|
@@ -92,7 +94,7 @@ supported yet.
|
|
|
92
94
|
`generateAt`, validated there as one program, and only then moved into place. A run killed part-way is repaired by the
|
|
93
95
|
next run that writes, before it writes anything. A first run that fails removes the directories it created.
|
|
94
96
|
- **The validate step is a real type check** when your project has the optional peer `typescript` (5.5 to 6) — it is
|
|
95
|
-
resolved from your project, so `npx @aventara/client@
|
|
97
|
+
resolved from your project, so `npx @aventara/client@rc init` type-checks too — under a consumer's strictest plausible
|
|
96
98
|
settings and with nothing outside the tree resolvable. Without it — or under
|
|
97
99
|
TypeScript 7, which has no classic compiler API to check with — the check degrades to a parse with Node's own
|
|
98
100
|
TypeScript parser, and says so loudly in one warning; it is never skipped, and the client is still written. The peer
|
|
@@ -143,6 +145,22 @@ const profile = avClient.tx.Profile.create.one({ data: { bio: "…", userId: cre
|
|
|
143
145
|
const [user, row] = await avClient.transaction([created, profile]);
|
|
144
146
|
```
|
|
145
147
|
|
|
148
|
+
A `$ref` stands for one field of an earlier step's result, and may be written in a step's data at any depth (a field's
|
|
149
|
+
value, the key a relation's `$connect`, `$disconnect`, `$set`, `$connectOrCreate` or `$upsert` names, the `where` of a
|
|
150
|
+
nested `$update` or `$delete`, and the data `$create`, `$update` and `$upsert` nest), its `cursor`, and anywhere in its
|
|
151
|
+
`where` — `AND`/`OR`/`NOT` and relation filters included. A projection's nested `where` and `orderBy` take none:
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
const post = avClient.tx.Post.create.one({ data: { title: "Hi", author: { $connect: { id: created.$ref("id") } } } });
|
|
155
|
+
const mine = avClient.tx.Post.find.many({ where: { OR: [{ authorId: created.$ref("id") }, { title: { startsWith: created.$ref("email") } }] } });
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Its source field must have the same type as where it is written: for a filter's value, `equals` and `not`, a nullable
|
|
159
|
+
source only where the field is nullable, and a `null` then matches the rows where the field is unset, as a literal `null`
|
|
160
|
+
does; for a unique key, a `cursor`, a comparison or a text operator, a non-null source; for `in`, `notIn`, `hasEvery` and
|
|
161
|
+
`hasSome`, a list field; for `has`, one value. A mismatch is a type error, and the server refuses it before anything runs
|
|
162
|
+
(`A2007`/`V1011`).
|
|
163
|
+
|
|
146
164
|
`avClient.tx.…` builds a handle and sends nothing. `avClient.transaction([...])` sends the plan once and resolves one
|
|
147
165
|
result per handle, typed, in order; a failing step throws its error with `cause.operation` naming the step. `$ref` binds
|
|
148
166
|
to the handle's position in the list it runs with. Two mistakes are refused before anything is sent, as core refuses
|
|
@@ -210,11 +228,9 @@ chose. The `.d.ts` files are core's copied declarations under `generated/derivat
|
|
|
210
228
|
`UserUniqueWhere`, `UserOrderBy`, `UserCreateData`, `UserUpdateData`, `UserSelect`, `UserInclude`), each only when
|
|
211
229
|
its operation is advertised.
|
|
212
230
|
|
|
213
|
-
The tree is self-contained: every import in it names a file in it
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
reserved word, a name the runtime owns, an enum sharing a Resource's name) are renamed, with one warning each; the wire
|
|
217
|
-
name is kept.
|
|
231
|
+
The tree is self-contained: every import in it names a file in it, so it compiles with nothing else installed. Names a TypeScript
|
|
232
|
+
declaration cannot carry as-is (a reserved word, a name the runtime owns, an enum sharing a Resource's name) are
|
|
233
|
+
renamed, with one warning each; the wire name is kept.
|
|
218
234
|
|
|
219
235
|
## What a generated client is bound to
|
|
220
236
|
|
|
@@ -237,11 +253,10 @@ doing so re-binds nothing — the ClientContract hash still decides whether that
|
|
|
237
253
|
|
|
238
254
|
## Type-checking cost in a consumer
|
|
239
255
|
|
|
240
|
-
A consumer pays for the calls it type-checks, not for the schema
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
program, the 500,000 line near 150 Resources.
|
|
256
|
+
A consumer pays for the calls it type-checks, not for the schema, under `skipLibCheck: true` (the common default):
|
|
257
|
+
the named types (`generated/types.d.ts`) and core's derivation are declaration files checked only where read. Under
|
|
258
|
+
`skipLibCheck: false` every named type is resolved where it is declared, so the cost grows with the number of
|
|
259
|
+
Resources.
|
|
245
260
|
|
|
246
261
|
## Before 1.0
|
|
247
262
|
|
|
@@ -1,16 +1,6 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The `avclient` command line: one command, its one flag, and help. Everything the
|
|
3
|
-
* generator needs is in `framework.client.ts` and the `.env` cascade, so
|
|
4
|
-
* `generate` takes no flag that would duplicate a config member — a second source
|
|
5
|
-
* of the same fact. `--yes` is not one: it answers the one question the generator
|
|
6
|
-
* asks.
|
|
7
|
-
*/
|
|
8
1
|
export declare const USAGE = "avclient \u2014 generate a typed Aventara client from a deployed ClientContract.\n\nUsage:\n avclient generate [--yes] Read framework.client.ts (or .js, .mjs, .cjs, .mts,\n .cts) in the current directory, fetch\n <entrypoint>/_contract, and write AvClient.ts and\n generated/ into its generateAt directory, its imports\n spelled as the project's tsconfig.json says.\n avclient init [options] Set up this frontend: write framework.client.ts, the\n .env entry, the avclient:generate script and the\n @aventara/client devDependency; install; generate.\n --entrypoint <url> The server's entrypoint [http://localhost:3000/api].\n --env-var <NAME> Read it from this variable [AVENTARA_API_URL].\n --no-env-var Write it into framework.client.ts as a literal.\n --generate-at <dir> Where the client goes [./src/api].\n --package-manager <npm|pnpm> [the lockfile's, else the launching one, else npm]\n --skip-install Print the install instead of running it.\n --skip-generate Do not generate now.\n -y, --yes Accept every default, and replace differing content.\n avclient --help Print this and exit 0.\n avclient <command> --help Print that command's usage and exit 0.\n avclient --version, -v Print this generator's version and exit 0.\n\nOptions:\n -y, --yes Overwrite or remove content in AvClient.ts and generated/ that the\n generator did not produce, without asking. Without it, such content\n is listed and you are asked; where nobody can answer (stdin is not a\n terminal, as in CI), the run is refused and nothing is touched.\n\nThe generator owns AvClient.ts and generated/ in generateAt, and nothing else\nthere: your own files beside them are never read, moved or removed. Generated\nfiles are replaced on every run; do not edit them.\n\nBefore framework.client.ts is evaluated, the .env cascade is read from the\ncurrent directory, highest precedence first: the process environment,\n.env.<mode>.local, .env.<mode>, .env.local, .env \u2014 where mode is NODE_ENV, or\n\"development\".\n";
|
|
9
|
-
/** `avclient generate --help` (pilot.1): that command's usage alone. */
|
|
10
2
|
export declare const GENERATE_USAGE = "Usage: avclient generate [--yes]\n\nRead framework.client.ts (or .js, .mjs, .cjs, .mts, .cts) in the current directory,\nfetch <entrypoint>/_contract, and write AvClient.ts and generated/ into its\ngenerateAt directory, its imports spelled as the project's tsconfig.json says.\n\nOptions:\n -y, --yes Overwrite or remove content in AvClient.ts and generated/ that the\n generator did not produce, without asking. Without it, such content\n is listed and you are asked; where nobody can answer (stdin is not a\n terminal, as in CI), the run is refused and nothing is touched.\n\nThe generator owns AvClient.ts and generated/ in generateAt, and nothing else\nthere: your own files beside them are never read, moved or removed.\n\nBefore the config is evaluated, the .env cascade is read from the current\ndirectory, highest precedence first: the process environment, .env.<mode>.local,\n.env.<mode>, .env.local, .env \u2014 where mode is NODE_ENV, or \"development\".\n";
|
|
11
|
-
|
|
12
|
-
export declare const INIT_USAGE = "Usage: avclient init [options]\n\nSet up this frontend: write framework.client.ts, the .env entry, the avclient:generate\nscript and the @aventara/client devDependency; install; generate.\n\nOptions:\n --entrypoint <url> The server's entrypoint [http://localhost:3000/api].\n --env-var <NAME> Read it from this variable [AVENTARA_API_URL].\n --no-env-var Write it into framework.client.ts as a literal.\n --generate-at <dir> Where the client goes [./src/api].\n --package-manager <npm|pnpm> [the lockfile's, else the launching one, else npm]\n --skip-install Print the install instead of running it.\n --skip-generate Do not generate now.\n -y, --yes Accept every default, and replace differing content.\n\nOn a terminal every unanswered question is asked; anywhere else, pass its flag\nor --yes, or the run stops before writing anything.\n";
|
|
13
|
-
/** `avclient init`'s command line. */
|
|
3
|
+
export declare const INIT_USAGE = "Usage: avclient init [options]\n\nSet up this frontend: write framework.client.ts, the .env entry, the avclient:generate\nscript and the @aventara/client devDependency; install; generate.\n\nOptions:\n --entrypoint <url> The server's entrypoint [http://localhost:3000/api].\n --env-var <NAME> Read it from this variable [AVENTARA_API_URL].\n --no-env-var Write it into framework.client.ts as a literal.\n --generate-at <dir> Where the client goes [./src/api].\n --package-manager <npm|pnpm> [the lockfile's, else the launching one, else npm]\n --skip-install Print the install instead of running it.\n --skip-generate Do not generate now.\n --verbose Show the install's output as it runs.\n -y, --yes Accept every default, and replace differing content.\n\nOn a terminal every unanswered question is asked; anywhere else, pass its flag\nor --yes, or the run stops before writing anything.\n";
|
|
14
4
|
export type ClientInitCommand = {
|
|
15
5
|
readonly command: "init";
|
|
16
6
|
readonly given: Readonly<Partial<Record<"entrypoint" | "envVar" | "generateAt" | "packageManager", string>>>;
|
|
@@ -18,11 +8,11 @@ export type ClientInitCommand = {
|
|
|
18
8
|
readonly skipInstall: boolean;
|
|
19
9
|
readonly skipGenerate: boolean;
|
|
20
10
|
readonly yes: boolean;
|
|
11
|
+
/** Streams the install's output. */
|
|
12
|
+
readonly verbose?: boolean;
|
|
21
13
|
};
|
|
22
14
|
/** What the command line asked for. */
|
|
23
|
-
export type CliCommand =
|
|
24
|
-
/** `--help`; with `topic`, `avclient <topic> --help` (pilot.1). */
|
|
25
|
-
{
|
|
15
|
+
export type CliCommand = {
|
|
26
16
|
readonly command: "help";
|
|
27
17
|
readonly topic?: "generate" | "init";
|
|
28
18
|
} | {
|
|
@@ -69,6 +69,7 @@ Options:
|
|
|
69
69
|
--package-manager <npm|pnpm> [the lockfile's, else the launching one, else npm]
|
|
70
70
|
--skip-install Print the install instead of running it.
|
|
71
71
|
--skip-generate Do not generate now.
|
|
72
|
+
--verbose Show the install's output as it runs.
|
|
72
73
|
-y, --yes Accept every default, and replace differing content.
|
|
73
74
|
|
|
74
75
|
On a terminal every unanswered question is asked; anywhere else, pass its flag
|
|
@@ -132,8 +133,13 @@ function parseInit(argv, help) {
|
|
|
132
133
|
yes: false,
|
|
133
134
|
};
|
|
134
135
|
const seen = new Set();
|
|
136
|
+
let verbose = false;
|
|
135
137
|
for (let index = 0; index < argv.length; index += 1) {
|
|
136
138
|
const argument = argv[index];
|
|
139
|
+
if (argument === "--verbose" && !verbose) {
|
|
140
|
+
verbose = true;
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
137
143
|
const equals = argument.indexOf("=");
|
|
138
144
|
const flag = equals === -1 ? argument : argument.slice(0, equals);
|
|
139
145
|
const inline = equals === -1 ? undefined : argument.slice(equals + 1);
|
|
@@ -167,5 +173,10 @@ function parseInit(argv, help) {
|
|
|
167
173
|
if (switches.noEnvVar && given.envVar !== undefined) {
|
|
168
174
|
throw new CliCommandError(`--env-var and --no-env-var contradict each other; ${help}`);
|
|
169
175
|
}
|
|
170
|
-
return {
|
|
176
|
+
return {
|
|
177
|
+
command: "init",
|
|
178
|
+
given,
|
|
179
|
+
...switches,
|
|
180
|
+
...(verbose ? { verbose: true } : {}),
|
|
181
|
+
};
|
|
171
182
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ProcessEnvInput } from "../config/env.cascade.js";
|
|
2
2
|
import type { CommandRunner } from "../init/command.runner.js";
|
|
3
|
+
import { type ProgressMode, type StepReporter } from "./step.reporter.js";
|
|
3
4
|
/** What `runCli` reads and writes instead of the process globals. */
|
|
4
5
|
export interface CliIo {
|
|
5
6
|
readonly cwd: string;
|
|
@@ -14,11 +15,15 @@ export interface CliIo {
|
|
|
14
15
|
readonly ask?: (prompt: string) => Promise<string>;
|
|
15
16
|
/** Runs a command with stdout piped (`init`'s install); a child process by default. */
|
|
16
17
|
readonly run?: CommandRunner;
|
|
18
|
+
/** How the steps are shown on stderr; none when omitted. */
|
|
19
|
+
readonly progress?: ProgressMode;
|
|
17
20
|
}
|
|
21
|
+
/** How many steps a generation shows: the config, the contract, the emission, the write. */
|
|
22
|
+
export declare const GENERATION_STEPS = 4;
|
|
18
23
|
/**
|
|
19
24
|
* `avclient generate` — the generation, the generateAt rule's question, and the
|
|
20
25
|
* report. Shared by `init`, which generates once the project is set up.
|
|
21
26
|
*
|
|
22
27
|
* @throws whatever the generation raises; the caller renders it.
|
|
23
28
|
*/
|
|
24
|
-
export declare function runGenerate(io: CliIo, yes: boolean): Promise<number>;
|
|
29
|
+
export declare function runGenerate(io: CliIo, yes: boolean, reporter?: StepReporter): Promise<number>;
|
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
import { generateClient } from "../generate.js";
|
|
2
2
|
import { FOREIGN_CONTENT_QUESTION, renderForeignContentCancellation, renderForeignContentWarning, renderGenerationSuccess, renderUpToDate, } from "./generation-success.renderer.js";
|
|
3
|
-
|
|
3
|
+
import { createStepReporter, } from "./step.reporter.js";
|
|
4
|
+
export const GENERATION_STEPS = 4;
|
|
5
|
+
export async function runGenerate(io, yes, reporter) {
|
|
6
|
+
const progress = reporter ??
|
|
7
|
+
createStepReporter({
|
|
8
|
+
write: io.stderr,
|
|
9
|
+
mode: io.progress ?? "off",
|
|
10
|
+
total: GENERATION_STEPS,
|
|
11
|
+
});
|
|
4
12
|
const outcome = await generateClient({
|
|
5
13
|
directory: io.cwd,
|
|
6
14
|
processEnv: io.env,
|
|
7
15
|
overrideForeign: yes,
|
|
16
|
+
step: progress.step,
|
|
8
17
|
});
|
|
9
18
|
if (outcome.kind === "up-to-date") {
|
|
10
19
|
const report = renderUpToDate(outcome);
|
|
11
20
|
io.stderr(report.stderr);
|
|
21
|
+
progress.finish("avclient", "done");
|
|
12
22
|
io.stdout(report.stdout);
|
|
13
23
|
return 0;
|
|
14
24
|
}
|
|
@@ -30,6 +40,7 @@ export async function runGenerate(io, yes) {
|
|
|
30
40
|
}
|
|
31
41
|
const report = renderGenerationSuccess(result);
|
|
32
42
|
io.stderr(report.stderr);
|
|
43
|
+
progress.finish("avclient", "done");
|
|
33
44
|
io.stdout(report.stdout);
|
|
34
45
|
return 0;
|
|
35
46
|
}
|
|
@@ -13,10 +13,6 @@ export interface GenerationSuccessReport {
|
|
|
13
13
|
readonly stderr: string;
|
|
14
14
|
}
|
|
15
15
|
export declare function renderGenerationSuccess(result: ClientGenerated): GenerationSuccessReport;
|
|
16
|
-
/**
|
|
17
|
-
* Nothing written: the deployment's ClientContract and these bytes are what is
|
|
18
|
-
* there.
|
|
19
|
-
*/
|
|
20
16
|
export declare function renderUpToDate(result: ClientUpToDate): GenerationSuccessReport;
|
|
21
17
|
/**
|
|
22
18
|
* What the person running the generator is told before being asked: every path
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type ProgressMode = "live" | "plain" | "off";
|
|
2
|
+
/** `live` only on a terminal that is not CI and accepts control sequences. */
|
|
3
|
+
export declare function progressModeOf(isTTY: boolean | undefined, env: Readonly<Record<string, string | undefined>>): Exclude<ProgressMode, "off">;
|
|
4
|
+
export type StepReporterOptions = {
|
|
5
|
+
/** Where the lines go: the tool's stderr, so its stdout stays its result. */
|
|
6
|
+
readonly write: (text: string) => void;
|
|
7
|
+
readonly mode: ProgressMode;
|
|
8
|
+
/** How many steps this run takes, for `[n/total]`. */
|
|
9
|
+
readonly total: number;
|
|
10
|
+
/** Each child's output streams between a step's start and end lines. */
|
|
11
|
+
readonly verbose?: boolean;
|
|
12
|
+
/** Milliseconds now; `Date.now` by default. */
|
|
13
|
+
readonly now?: () => number;
|
|
14
|
+
/** Redraw interval of the live line, in milliseconds. */
|
|
15
|
+
readonly interval?: number;
|
|
16
|
+
};
|
|
17
|
+
export type StepReporter = {
|
|
18
|
+
/**
|
|
19
|
+
* Runs one step under its numbered line. Its outcome — `done`, or `failed`
|
|
20
|
+
* when `body` throws — ends the line; the error is rethrown unchanged.
|
|
21
|
+
*/
|
|
22
|
+
readonly step: <T>(title: string, body: () => Promise<T> | T) => Promise<T>;
|
|
23
|
+
/** Streams a child's output, in `verbose` mode only. */
|
|
24
|
+
readonly output: (chunk: string) => void;
|
|
25
|
+
/** The closing line: `<prefix>: <what> in <time>.` */
|
|
26
|
+
readonly finish: (prefix: string, what: string) => void;
|
|
27
|
+
readonly verbose: boolean;
|
|
28
|
+
};
|
|
29
|
+
/** `12.4s`, or `1m 02s` from a minute. */
|
|
30
|
+
export declare function formatElapsed(milliseconds: number): string;
|
|
31
|
+
export declare function createStepReporter(options: StepReporterOptions): StepReporter;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
export function progressModeOf(isTTY, env) {
|
|
2
|
+
const ci = env.CI;
|
|
3
|
+
if (isTTY !== true ||
|
|
4
|
+
(ci !== undefined && ci !== "" && ci !== "0" && ci !== "false") ||
|
|
5
|
+
(env.NO_COLOR !== undefined && env.NO_COLOR !== "") ||
|
|
6
|
+
env.TERM === "dumb") {
|
|
7
|
+
return "plain";
|
|
8
|
+
}
|
|
9
|
+
return "live";
|
|
10
|
+
}
|
|
11
|
+
const SPINNER = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
12
|
+
const CLEAR_LINE = "\r\u001b[2K";
|
|
13
|
+
const GREEN = (text) => `\u001b[32m${text}\u001b[39m`;
|
|
14
|
+
const RED = (text) => `\u001b[31m${text}\u001b[39m`;
|
|
15
|
+
const DIM = (text) => `\u001b[2m${text}\u001b[22m`;
|
|
16
|
+
export function formatElapsed(milliseconds) {
|
|
17
|
+
const seconds = milliseconds / 1000;
|
|
18
|
+
if (seconds < 60) {
|
|
19
|
+
return `${seconds.toFixed(1)}s`;
|
|
20
|
+
}
|
|
21
|
+
const whole = Math.round(seconds);
|
|
22
|
+
return `${Math.floor(whole / 60)}m ${String(whole % 60).padStart(2, "0")}s`;
|
|
23
|
+
}
|
|
24
|
+
export function createStepReporter(options) {
|
|
25
|
+
const now = options.now ?? Date.now;
|
|
26
|
+
const started = now();
|
|
27
|
+
const verbose = options.verbose === true && options.mode !== "off";
|
|
28
|
+
const mode = verbose ? "plain" : options.mode;
|
|
29
|
+
let index = 0;
|
|
30
|
+
const step = async (title, body) => {
|
|
31
|
+
index += 1;
|
|
32
|
+
const label = `[${index}/${options.total}] ${title}`;
|
|
33
|
+
const at = now();
|
|
34
|
+
const elapsed = () => formatElapsed(now() - at);
|
|
35
|
+
if (mode === "off") {
|
|
36
|
+
return await body();
|
|
37
|
+
}
|
|
38
|
+
if (mode === "plain") {
|
|
39
|
+
options.write(verbose ? `${label}…\n` : `${label}… `);
|
|
40
|
+
try {
|
|
41
|
+
const value = await body();
|
|
42
|
+
options.write(verbose ? `${label}… done (${elapsed()})\n` : `done (${elapsed()})\n`);
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
options.write(verbose
|
|
47
|
+
? `${label}… failed (${elapsed()})\n`
|
|
48
|
+
: `failed (${elapsed()})\n`);
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
let frame = 0;
|
|
53
|
+
const draw = () => {
|
|
54
|
+
const seconds = Math.floor((now() - at) / 1000);
|
|
55
|
+
options.write(`${CLEAR_LINE}${SPINNER[frame % SPINNER.length]} ${label}…${seconds > 0 ? DIM(` ${seconds}s`) : ""}`);
|
|
56
|
+
frame += 1;
|
|
57
|
+
};
|
|
58
|
+
draw();
|
|
59
|
+
const timer = setInterval(draw, options.interval ?? 80);
|
|
60
|
+
timer.unref?.();
|
|
61
|
+
try {
|
|
62
|
+
const value = await body();
|
|
63
|
+
clearInterval(timer);
|
|
64
|
+
options.write(`${CLEAR_LINE}${GREEN("✓")} ${label} ${DIM(elapsed())}\n`);
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
clearInterval(timer);
|
|
69
|
+
options.write(`${CLEAR_LINE}${RED("✗")} ${label} ${DIM(elapsed())}\n`);
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
return {
|
|
74
|
+
step,
|
|
75
|
+
output: (chunk) => {
|
|
76
|
+
if (verbose) {
|
|
77
|
+
options.write(chunk);
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
finish: (prefix, what) => {
|
|
81
|
+
if (mode !== "off") {
|
|
82
|
+
options.write(`${prefix}: ${what} in ${formatElapsed(now() - started)}.\n`);
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
verbose,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The terminal's questions — `generate`'s yes/no and `init`'s wizard — over one
|
|
3
|
-
* `node:readline` interface whose lines are **queued**: a line typed (or piped
|
|
4
|
-
* into a pseudo-terminal) before its question is kept for it, where a fresh
|
|
5
|
-
* interface per question, or `readline/promises`' `question`, drops it.
|
|
6
|
-
*/
|
|
7
1
|
export type TerminalPrompter = {
|
|
8
2
|
readonly ask: (prompt: string) => Promise<string>;
|
|
9
3
|
readonly confirm: (question: string) => Promise<boolean>;
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* How one warning reaches the person running the generator: its own stderr line,
|
|
3
|
-
* under the CLI's one prefix. Warnings carry no prefix of their own, so the line
|
|
4
|
-
* says "warning" once. Shared by a success and a refusal, so a warning reads the
|
|
5
|
-
* same either way.
|
|
6
|
-
*/
|
|
7
1
|
/** The prefix every warning line opens with. */
|
|
8
2
|
export declare const WARNING_LINE_PREFIX = "avclient: warning: ";
|
|
9
3
|
/** One newline-terminated line per warning, in the order given; empty for none. */
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,21 +1,5 @@
|
|
|
1
1
|
import { type CliIo } from "./cli/generate.command.js";
|
|
2
2
|
export type { CliIo } from "./cli/generate.command.js";
|
|
3
|
-
/**
|
|
4
|
-
* It is also the one place that ASKS: when content the generator did not produce
|
|
5
|
-
* stands in `AvClient.ts` or `generated/`, the paths are listed and the person is
|
|
6
|
-
* asked whether to overwrite them. `--yes` answers for them. Where nobody can
|
|
7
|
-
* answer — stdin is not a terminal — it never asks and never overrides: it
|
|
8
|
-
* refuses, naming `--yes`. Cancelled or refused, nothing was touched, the run's
|
|
9
|
-
* warnings are printed before the sentence, and the exit code is 1. A killed run's
|
|
10
|
-
* leftovers are looked at before the question, so the answer — and `--yes` —
|
|
11
|
-
* covers what it hid as well.
|
|
12
|
-
*/
|
|
13
3
|
/** Runs one command line; resolves to the exit code. Never throws. */
|
|
14
4
|
export declare function runCli(argv: readonly string[], io: CliIo): Promise<number>;
|
|
15
|
-
/**
|
|
16
|
-
* Runs `avclient` over this process — its arguments, its terminal, its exit
|
|
17
|
-
* code. Called by the bin's entry (`avclient.bin.ts`) once the Node guard has
|
|
18
|
-
* admitted this Node; importing this module runs nothing, which is what lets
|
|
19
|
-
* `runCli` be tested in process.
|
|
20
|
-
*/
|
|
21
5
|
export declare function runFromProcess(): Promise<void>;
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { GENERATE_USAGE, INIT_USAGE, parseCliCommand, USAGE, } from "./cli/command.parser.js";
|
|
2
2
|
import { runGenerate } from "./cli/generate.command.js";
|
|
3
3
|
import { renderGenerationFailure } from "./cli/generation-failure.renderer.js";
|
|
4
|
+
import { progressModeOf } from "./cli/step.reporter.js";
|
|
4
5
|
import { createTerminalPrompter } from "./cli/terminal.prompter.js";
|
|
5
6
|
import { AVENTARA_CLIENT_GENERATOR_VERSION } from "./index.js";
|
|
6
7
|
import { runClientInit } from "./init/client-init.orchestrator.js";
|
|
@@ -47,6 +48,7 @@ export async function runFromProcess() {
|
|
|
47
48
|
confirm: terminal.confirm,
|
|
48
49
|
ask: terminal.ask,
|
|
49
50
|
run: runCommand,
|
|
51
|
+
progress: progressModeOf(process.stderr.isTTY, process.env),
|
|
50
52
|
});
|
|
51
53
|
}
|
|
52
54
|
finally {
|