@aventara/client 0.1.0-pilot.0 → 0.1.0-pilot.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/README.md +27 -61
- package/dist/cli/command.parser.d.ts +9 -2
- package/dist/cli/command.parser.js +49 -4
- package/dist/cli/generation-failure.renderer.js +0 -2
- package/dist/cli.js +6 -2
- package/dist/config/config.loader.d.ts +19 -11
- package/dist/config/config.loader.js +31 -16
- package/dist/init/client-config.template.d.ts +7 -2
- package/dist/init/client-config.template.js +8 -3
- package/dist/init/client-init.orchestrator.js +6 -2
- package/dist/init/client-init.planner.d.ts +2 -1
- package/dist/init/client-init.planner.js +12 -1
- package/dist/output/output.validator.d.ts +12 -13
- package/dist/output/output.validator.js +35 -27
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ server by construction, and that agreement is gated over both pilot schemas.
|
|
|
11
11
|
## Setting up a frontend: `avclient init`
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
npx @aventara/client init # in your frontend project
|
|
14
|
+
npx @aventara/client@pilot init # in your frontend project; pnpm: pnpm dlx @aventara/client@pilot 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:
|
|
@@ -23,7 +23,7 @@ It asks — or takes from flags, or with `--yes` takes every default — four th
|
|
|
23
23
|
| `--generate-at <dir>` | Where the client goes | `./src/api` |
|
|
24
24
|
| `--package-manager <npm\|pnpm>` | | the lockfile's, else the launching one, else npm |
|
|
25
25
|
|
|
26
|
-
It writes `framework.client.
|
|
26
|
+
It writes `framework.client.mts`, 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
29
|
not answer leaves the files and names `avclient generate`). Existing content that differs is listed and replaced only
|
|
@@ -32,7 +32,7 @@ committed: regenerating it needs a running server.
|
|
|
32
32
|
|
|
33
33
|
## Generating a client
|
|
34
34
|
|
|
35
|
-
Install it as a dev dependency (or let `avclient init` do it), then add `framework.client.
|
|
35
|
+
Install it as a dev dependency (or let `avclient init` do it), then add `framework.client.mts` to the directory you run
|
|
36
36
|
the generator from:
|
|
37
37
|
|
|
38
38
|
```ts
|
|
@@ -50,6 +50,11 @@ and run:
|
|
|
50
50
|
npx avclient generate [--yes]
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
+
**Why `.mts`.** `npm init -y` writes `"type": "commonjs"`, and under it Node reads a `.ts` file as CommonJS — the
|
|
54
|
+
config's `import` would stop the run. A `.mts` file is an ES module in every project. A `framework.client.ts` written
|
|
55
|
+
before 0.1.0-pilot.1 is still read; with both files present the generator refuses, and `avclient init` moves the old one
|
|
56
|
+
to `framework.client.mts` (asking first if you edited it).
|
|
57
|
+
|
|
53
58
|
- **`entrypoint`** is the deployment's framework entrypoint: origin plus mount path, one absolute `http(s)` URL. It
|
|
54
59
|
may not carry credentials — the platform's `fetch` refuses such a URL, and the entrypoint ships inside the client.
|
|
55
60
|
- **`generateAt`** is a directory you may share with your own files. The generator owns exactly two entries in it —
|
|
@@ -67,9 +72,11 @@ npx avclient generate [--yes]
|
|
|
67
72
|
- **A failed run leaves the previous output exactly as it was.** The whole tree is written to a staging directory inside
|
|
68
73
|
`generateAt`, validated there as one program, and only then moved into place. A run killed part-way is repaired by the
|
|
69
74
|
next run that writes, before it writes anything. A first run that fails removes the directories it created.
|
|
70
|
-
- **The validate step is a real type check** when the optional peer `typescript` is installed, under a
|
|
71
|
-
strictest plausible settings and with nothing outside the tree resolvable. Without it
|
|
72
|
-
|
|
75
|
+
- **The validate step is a real type check** when the optional peer `typescript` (5.5 to 6) is installed, under a
|
|
76
|
+
consumer's strictest plausible settings and with nothing outside the tree resolvable. Without it — or under
|
|
77
|
+
TypeScript 7, which has no classic compiler API to check with — the check degrades to a parse with Node's own
|
|
78
|
+
TypeScript parser, and says so loudly in one warning; it is never skipped, and the client is still written. The peer
|
|
79
|
+
range has no upper bound, so a frontend on TypeScript 7 installs it; your own `tsc` checks the tree when it compiles.
|
|
73
80
|
- **Determinism.** Two runs over one ClientContract write the same bytes; deleting the client and regenerating it gives
|
|
74
81
|
the same bytes. Nothing volatile — no timestamp, generator version, host or path — is emitted.
|
|
75
82
|
- **Failure is a sentence.** A refusal is one line and exit code 1, with no stack; anything the run warned about before
|
|
@@ -165,21 +172,21 @@ chose. The `.d.ts` files are core's copied declarations under `generated/derivat
|
|
|
165
172
|
- `generated/enums.ts` — each enum as a union type and a same-named `as const` object.
|
|
166
173
|
- `generated/metadata.ts` — the ClientContract hash and protocol version the client is bound to, and the entrypoint it
|
|
167
174
|
was generated from, its default deployment (an entrypoint carrying credentials is refused at resolution).
|
|
168
|
-
- `generated/runtime/codec.ts` — the nine scalars' wire codecs
|
|
175
|
+
- `generated/runtime/codec.ts` — the nine scalars' wire codecs, and the request-body serializer.
|
|
169
176
|
- `generated/runtime/decimal.ts` — the framework's `Decimal`: a constructor, `toString` and `toJSON`.
|
|
170
177
|
- `generated/runtime/descriptor.ts` — what the runtime reads of the contract: the result fields it revives and the
|
|
171
178
|
relations it follows (the decode table), and the advertised operations.
|
|
172
179
|
- `generated/runtime/errors.ts` — `FrameworkError`, its subclasses by code class (`AuthError`, `ConflictError`,
|
|
173
180
|
`ContractMismatchError`, `InternalError`, `NotFoundError`, `ProtocolError`, `ValidationError`), `TransportError`, and
|
|
174
181
|
the code unions, emitted from core's code arrays rather than retyped.
|
|
175
|
-
- `generated/runtime/fingerprint.ts` — the operation fingerprint
|
|
182
|
+
- `generated/runtime/fingerprint.ts` — the operation fingerprint (RFC 8785 canonical JSON, SHA-256,
|
|
176
183
|
base64url), dependency-free and pinned to core's; emitted only when transactions are `interactive`.
|
|
177
184
|
- `generated/runtime/transaction.ts` — `avClient.tx`'s deferred handles and `avClient.transaction`'s plan assembly,
|
|
178
185
|
refusals and one POST to `/_transactions`; emitted only when transactions are `interactive`.
|
|
179
|
-
- `generated/runtime/transport.ts` — one POST per operation with the identity headers, and the response read by
|
|
180
|
-
rule; arguments encoded by value, results revived by the decode table, the platform's own `fetch` type accepted.
|
|
186
|
+
- `generated/runtime/transport.ts` — one POST per operation with the identity headers, and the response read by the
|
|
187
|
+
protocol's rule; arguments encoded by value, results revived by the decode table, the platform's own `fetch` type accepted.
|
|
181
188
|
Its `execute` primitive is module-private: no public file re-exports it; `AvClient`'s methods wrap it.
|
|
182
|
-
- `generated/types.d.ts` —
|
|
189
|
+
- `generated/types.d.ts` — the named Resource types, as a declaration file (`User`, `UserWhere`,
|
|
183
190
|
`UserUniqueWhere`, `UserOrderBy`, `UserCreateData`, `UserUpdateData`, `UserSelect`, `UserInclude`), each only when
|
|
184
191
|
its operation is advertised.
|
|
185
192
|
|
|
@@ -198,10 +205,9 @@ A deployment whose ClientContract differs refuses the request with `A2005`, whic
|
|
|
198
205
|
|
|
199
206
|
The hash covers everything the deployment advertises, not only its schema: two deployments of one schema can advertise
|
|
200
207
|
different ClientContracts, and a client generated against one is refused by the other. Whatever the cause, the remedy
|
|
201
|
-
is the same — **generate against the deployment the client will call, and regenerate when it changes.**
|
|
202
|
-
the generator's half of follow-up row F-801, by the architect's decision to state the binding rather than its causes.)
|
|
208
|
+
is the same — **generate against the deployment the client will call, and regenerate when it changes.**
|
|
203
209
|
|
|
204
|
-
That holds for an operation the deployment **no longer advertises**, too
|
|
210
|
+
That holds for an operation the deployment **no longer advertises**, too: the server checks the client's
|
|
205
211
|
identity before it routes, so a stale client calling a removed operation also hears `A2005` and throws
|
|
206
212
|
`ContractMismatchError`. A response that is not a framework envelope at all — a host's own 404 for a path it does not
|
|
207
213
|
mount — is a `TransportError`, which names no remedy: nothing in it says the client is stale.
|
|
@@ -209,59 +215,19 @@ mount — is a `TransportError`, which names no remedy: nothing in it says the c
|
|
|
209
215
|
The embedded entrypoint is a **default**: `new AvClient({ entrypoint })` may point a client at another deployment, and
|
|
210
216
|
doing so re-binds nothing — the ClientContract hash still decides whether that deployment accepts the client.
|
|
211
217
|
|
|
212
|
-
##
|
|
213
|
-
|
|
214
|
-
| Phase 12 deliverable | Status |
|
|
215
|
-
|---|---|
|
|
216
|
-
| `avclient generate` CLI (bin `avclient`, R3) | Delivered, with `--yes` and the foreign-content question. |
|
|
217
|
-
| `.env` loading | Delivered: the five-file cascade, no dependency. |
|
|
218
|
-
| Deployed `/_contract` fetch | Delivered: conditional on the output it finds (`If-None-Match`, `304` → "up to date" only when re-emission is byte-identical). |
|
|
219
|
-
| Protocol compatibility validation | Delivered: the supported-version set, and the hash recomputed with core's function and compared — never trusted as delivered. |
|
|
220
|
-
| ClientContract structure validation | Delivered, by core's `validateClientContractStructure`. |
|
|
221
|
-
| Deterministic named type generation | Delivered: §15.6's named types as aliases over core's derivation, the rename ladder keeping every derived name free. |
|
|
222
|
-
| Resource operation methods, the client class and the default singleton | Delivered: `AvClient` and `avClient`, one method per advertised operation (P4: the runtime's list equals the type grammar's keys over both pilots). |
|
|
223
|
-
| Projection inference | Delivered: core's own call grammar — the caller's `select`/`include` literal inferred. Facts the capability model cannot state yet are unstated on both sides alike: **F-809**, **F-812**, **F-813**, **F-818** no longer block the client. |
|
|
224
|
-
| Deferred transaction builder | Delivered, iff the ClientContract advertises `interactive`; core's Contract-derived `Operation<T>` (**F-701**, closed) types each handle. |
|
|
225
|
-
| Transport runtime emitted into output | Delivered, including the stale-client mechanism (**F-716**, closed: server-side). |
|
|
226
|
-
| Scalar codecs | Delivered: all nine scalars of §6.2, encoded by value, revived by a decode table. |
|
|
227
|
-
| Typed framework and transport errors | Delivered. |
|
|
228
|
-
| Contract version and hash embedded into output | Delivered (`generated/metadata.ts`), with the resolved entrypoint as the default (§15.2). |
|
|
229
|
-
| Atomic temp → validate → replace | Delivered. |
|
|
230
|
-
| Generated-file ownership banners | Delivered. |
|
|
231
|
-
| No runtime import from `@aventara/client` | Delivered, and gated: nothing outside the tree, `@aventara/core` included. |
|
|
232
|
-
| Availability readers | Read through core's `isOperationVariantAvailable` (**F-820**, closed). Not hand-rolled here. |
|
|
233
|
-
|
|
234
|
-
### Phase 12's exit gate
|
|
235
|
-
|
|
236
|
-
- *"Delete the generated client, regenerate it from the same contract, and obtain equivalent output"* — **passed, and
|
|
237
|
-
strengthened to byte-identical**, through the packed bin against a real HTTP server, with the callable surface, core's
|
|
238
|
-
derivation and the transaction runtime in the tree.
|
|
239
|
-
- *"A generated test application compiles without depending on server source types"* — **passed, and run**: a
|
|
240
|
-
throwaway project where neither `@aventara/core` nor `@aventara/client` can resolve generates its client from a
|
|
241
|
-
running host, compiles a program against it with `tsc`, and runs it — scalars revived, an enum, a `$count` envelope, a
|
|
242
|
-
transaction with a `$ref`, a `NotFoundError`, an abort, and `ContractMismatchError` after the host's contract changes.
|
|
243
|
-
|
|
244
|
-
### Type-checking cost in a consumer
|
|
218
|
+
## Type-checking cost in a consumer
|
|
245
219
|
|
|
246
220
|
A consumer pays for the calls it type-checks, not for the schema — under `skipLibCheck: true` (the common default),
|
|
247
221
|
where the named types (`generated/types.d.ts`) and core's derivation are declaration files checked only where read:
|
|
248
222
|
about 33,000 instantiations for a 50-Resource schema and seven typed calls. Under `skipLibCheck: false` every named type
|
|
249
|
-
is resolved where it is declared, about 2,900 instantiations per Resource
|
|
223
|
+
is resolved where it is declared, about 2,900 instantiations per Resource: about 204,000 for the same
|
|
250
224
|
program, the 500,000 line near 150 Resources.
|
|
251
225
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
- **The
|
|
255
|
-
`
|
|
256
|
-
|
|
257
|
-
`AvClient`, `CallOptions`), the emitted tree's layout, the forms map, credentials, "up to date".
|
|
258
|
-
- **The end-to-end and ownership gates read a gitignored file by path:** the SQLite pilot's discovery artifact,
|
|
259
|
-
`packages/prisma7-adapter/generated/artifact/sqlite.artifact.ts`, which exists only after the adapter's generate step
|
|
260
|
-
has run (`pnpm typecheck` runs it). This package takes no dependency on the adapter.
|
|
261
|
-
- **The `Aventara` wire prefix** of the identity headers must be frozen before the first external release (§12.4). It is
|
|
262
|
-
one constant in `generated/runtime/transport.ts`, so the freeze is a one-line change.
|
|
263
|
-
- **A tarball from `npm pack` is not a releasable artifact** while `@aventara/core` is spelled `workspace:*`, and does
|
|
264
|
-
not install outside this workspace (F-808, Phase 13). Do not validate a pilot against one.
|
|
226
|
+
## Before 1.0
|
|
227
|
+
|
|
228
|
+
- **The `Aventara` wire prefix** of the protocol's headers (`Aventara-Protocol-Version`, `Aventara-Contract-Hash`,
|
|
229
|
+
`Aventara-Request-Id`) may still change before the first stable release. It is one constant in the generated
|
|
230
|
+
`generated/runtime/transport.ts`; regenerating picks up a change.
|
|
265
231
|
|
|
266
232
|
## License
|
|
267
233
|
|
|
@@ -5,7 +5,11 @@
|
|
|
5
5
|
* source of the same fact. `--yes` is not one: it answers the one question the
|
|
6
6
|
* generator asks (architect, 2026-10-04).
|
|
7
7
|
*/
|
|
8
|
-
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 in the
|
|
8
|
+
export declare const USAGE = "avclient \u2014 generate a typed Aventara client from a deployed ClientContract.\n\nUsage:\n avclient generate [--yes] Read framework.client.mts (or framework.client.ts) in the\n current directory, fetch <entrypoint>/_contract, and\n write AvClient.ts and generated/ into its generateAt\n directory.\n avclient init [options] Set up this frontend: write framework.client.mts, 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.mts 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\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.mts 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
|
+
export declare const GENERATE_USAGE = "Usage: avclient generate [--yes]\n\nRead framework.client.mts (or framework.client.ts) in the current directory, fetch\n<entrypoint>/_contract, and write AvClient.ts and generated/ into its generateAt\ndirectory.\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
|
+
/** `avclient init --help` (pilot.1): that command's usage alone. */
|
|
12
|
+
export declare const INIT_USAGE = "Usage: avclient init [options]\n\nSet up this frontend: write framework.client.mts, 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.mts 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";
|
|
9
13
|
/** `avclient init`'s command line (R4, Q16 rows 12–15). */
|
|
10
14
|
export type ClientInitCommand = {
|
|
11
15
|
readonly command: "init";
|
|
@@ -16,8 +20,11 @@ export type ClientInitCommand = {
|
|
|
16
20
|
readonly yes: boolean;
|
|
17
21
|
};
|
|
18
22
|
/** What the command line asked for. */
|
|
19
|
-
export type CliCommand =
|
|
23
|
+
export type CliCommand =
|
|
24
|
+
/** `--help`; with `topic`, `avclient <topic> --help` (pilot.1). */
|
|
25
|
+
{
|
|
20
26
|
readonly command: "help";
|
|
27
|
+
readonly topic?: "generate" | "init";
|
|
21
28
|
} | {
|
|
22
29
|
readonly command: "generate";
|
|
23
30
|
readonly yes: boolean;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CLIENT_CONFIG_FILE } from "../config/config.loader.js";
|
|
1
|
+
import { CLIENT_CONFIG_FILE, LEGACY_CLIENT_CONFIG_FILE, } from "../config/config.loader.js";
|
|
2
2
|
/**
|
|
3
3
|
* The `avclient` command line: one command, its one flag, and help. Everything
|
|
4
4
|
* the generator needs is in `framework.client.ts` and the `.env` cascade (§15.2),
|
|
@@ -9,9 +9,10 @@ import { CLIENT_CONFIG_FILE } from "../config/config.loader.js";
|
|
|
9
9
|
export const USAGE = `avclient — generate a typed Aventara client from a deployed ClientContract.
|
|
10
10
|
|
|
11
11
|
Usage:
|
|
12
|
-
avclient generate [--yes] Read ${CLIENT_CONFIG_FILE} in the
|
|
13
|
-
fetch <entrypoint>/_contract, and
|
|
14
|
-
and generated/ into its generateAt
|
|
12
|
+
avclient generate [--yes] Read ${CLIENT_CONFIG_FILE} (or ${LEGACY_CLIENT_CONFIG_FILE}) in the
|
|
13
|
+
current directory, fetch <entrypoint>/_contract, and
|
|
14
|
+
write AvClient.ts and generated/ into its generateAt
|
|
15
|
+
directory.
|
|
15
16
|
avclient init [options] Set up this frontend: write ${CLIENT_CONFIG_FILE}, the
|
|
16
17
|
.env entry, the avclient:generate script and the
|
|
17
18
|
@aventara/client devDependency; install; generate.
|
|
@@ -24,6 +25,7 @@ Usage:
|
|
|
24
25
|
--skip-generate Do not generate now.
|
|
25
26
|
-y, --yes Accept every default, and replace differing content.
|
|
26
27
|
avclient --help Print this and exit 0.
|
|
28
|
+
avclient <command> --help Print that command's usage and exit 0.
|
|
27
29
|
|
|
28
30
|
Options:
|
|
29
31
|
-y, --yes Overwrite or remove content in AvClient.ts and generated/ that the
|
|
@@ -40,6 +42,45 @@ current directory, highest precedence first: the process environment,
|
|
|
40
42
|
.env.<mode>.local, .env.<mode>, .env.local, .env — where mode is NODE_ENV, or
|
|
41
43
|
"development".
|
|
42
44
|
`;
|
|
45
|
+
/** `avclient generate --help` (pilot.1): that command's usage alone. */
|
|
46
|
+
export const GENERATE_USAGE = `Usage: avclient generate [--yes]
|
|
47
|
+
|
|
48
|
+
Read ${CLIENT_CONFIG_FILE} (or ${LEGACY_CLIENT_CONFIG_FILE}) in the current directory, fetch
|
|
49
|
+
<entrypoint>/_contract, and write AvClient.ts and generated/ into its generateAt
|
|
50
|
+
directory.
|
|
51
|
+
|
|
52
|
+
Options:
|
|
53
|
+
-y, --yes Overwrite or remove content in AvClient.ts and generated/ that the
|
|
54
|
+
generator did not produce, without asking. Without it, such content
|
|
55
|
+
is listed and you are asked; where nobody can answer (stdin is not a
|
|
56
|
+
terminal, as in CI), the run is refused and nothing is touched.
|
|
57
|
+
|
|
58
|
+
The generator owns AvClient.ts and generated/ in generateAt, and nothing else
|
|
59
|
+
there: your own files beside them are never read, moved or removed.
|
|
60
|
+
|
|
61
|
+
Before the config is evaluated, the .env cascade is read from the current
|
|
62
|
+
directory, highest precedence first: the process environment, .env.<mode>.local,
|
|
63
|
+
.env.<mode>, .env.local, .env — where mode is NODE_ENV, or "development".
|
|
64
|
+
`;
|
|
65
|
+
/** `avclient init --help` (pilot.1): that command's usage alone. */
|
|
66
|
+
export const INIT_USAGE = `Usage: avclient init [options]
|
|
67
|
+
|
|
68
|
+
Set up this frontend: write ${CLIENT_CONFIG_FILE}, the .env entry, the avclient:generate
|
|
69
|
+
script and the @aventara/client devDependency; install; generate.
|
|
70
|
+
|
|
71
|
+
Options:
|
|
72
|
+
--entrypoint <url> The server's entrypoint [http://localhost:3000/api].
|
|
73
|
+
--env-var <NAME> Read it from this variable [AVENTARA_API_URL].
|
|
74
|
+
--no-env-var Write it into ${CLIENT_CONFIG_FILE} as a literal.
|
|
75
|
+
--generate-at <dir> Where the client goes [./src/api].
|
|
76
|
+
--package-manager <npm|pnpm> [the lockfile's, else the launching one, else npm]
|
|
77
|
+
--skip-install Print the install instead of running it.
|
|
78
|
+
--skip-generate Do not generate now.
|
|
79
|
+
-y, --yes Accept every default, and replace differing content.
|
|
80
|
+
|
|
81
|
+
On a terminal every unanswered question is asked; anywhere else, pass its flag
|
|
82
|
+
or --yes, or the run stops before writing anything.
|
|
83
|
+
`;
|
|
43
84
|
/** The command line cannot be understood. A refusal: one sentence, exit 1. */
|
|
44
85
|
export class CliCommandError extends Error {
|
|
45
86
|
name = "CliCommandError";
|
|
@@ -54,6 +95,10 @@ export function parseCliCommand(argv) {
|
|
|
54
95
|
if (command === "--help" || command === "-h") {
|
|
55
96
|
return { command: "help" };
|
|
56
97
|
}
|
|
98
|
+
if ((command === "init" || command === "generate") &&
|
|
99
|
+
(rest.includes("--help") || rest.includes("-h"))) {
|
|
100
|
+
return { command: "help", topic: command };
|
|
101
|
+
}
|
|
57
102
|
if (command === "init") {
|
|
58
103
|
return parseInit(rest, help);
|
|
59
104
|
}
|
|
@@ -6,7 +6,6 @@ import { GeneratedNameError } from "../emit/name.deriver.js";
|
|
|
6
6
|
import { ClientInitNotConfirmedError, ClientInitStepError, } from "../init/client-init.errors.js";
|
|
7
7
|
import { ClientInitAnswerError, ClientInitUnansweredError, } from "../init/client-init.questions.js";
|
|
8
8
|
import { ClientProjectRefusedError } from "../init/client-project.inspector.js";
|
|
9
|
-
import { UnsupportedTypeScriptError } from "../output/output.validator.js";
|
|
10
9
|
import { OutputWriteError, warningsRaisedBeforeDefect, } from "../output/output.writer.js";
|
|
11
10
|
import { CliCommandError } from "./command.parser.js";
|
|
12
11
|
import { renderWarningLines } from "./warning.renderer.js";
|
|
@@ -30,7 +29,6 @@ const REFUSALS = [
|
|
|
30
29
|
ContractProtocolError,
|
|
31
30
|
GeneratedNameError,
|
|
32
31
|
OutputWriteError,
|
|
33
|
-
UnsupportedTypeScriptError,
|
|
34
32
|
ClientInitAnswerError,
|
|
35
33
|
ClientInitUnansweredError,
|
|
36
34
|
ClientProjectRefusedError,
|
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { parseCliCommand, USAGE } from "./cli/command.parser.js";
|
|
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
4
|
import { createTerminalPrompter } from "./cli/terminal.prompter.js";
|
|
@@ -27,7 +27,11 @@ export async function runCli(argv, io) {
|
|
|
27
27
|
try {
|
|
28
28
|
const command = parseCliCommand(argv);
|
|
29
29
|
if (command.command === "help") {
|
|
30
|
-
io.stdout(
|
|
30
|
+
io.stdout(command.topic === undefined
|
|
31
|
+
? USAGE
|
|
32
|
+
: command.topic === "init"
|
|
33
|
+
? INIT_USAGE
|
|
34
|
+
: GENERATE_USAGE);
|
|
31
35
|
return 0;
|
|
32
36
|
}
|
|
33
37
|
if (command.command === "init") {
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import type { ClientConfigInput } from "./client-config.interface.js";
|
|
2
2
|
/**
|
|
3
|
-
* Finds and evaluates the client config: `framework.client.
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* fact.
|
|
3
|
+
* Finds and evaluates the client config: `framework.client.mts` (or, for a setup
|
|
4
|
+
* written before pilot.1, `framework.client.ts`), default-exporting
|
|
5
|
+
* `defineClientConfig({ entrypoint, generateAt })` as §15.2 writes it. One file,
|
|
6
|
+
* looked up in one directory — no search up the tree, no `package.json` key —
|
|
7
|
+
* and when both spellings are present the run refuses rather than choose: two
|
|
8
|
+
* files would be two sources of the same fact.
|
|
9
|
+
*
|
|
10
|
+
* Why `.mts` (pilot.1): `npm init -y` writes `"type": "commonjs"`, under which
|
|
11
|
+
* Node reads a `.ts` file as CommonJS and the config's `import`/`export` stop
|
|
12
|
+
* the run. A `.mts` file is an ES module in every project. A `.ts` config that
|
|
13
|
+
* fails that way is told to rename itself.
|
|
9
14
|
*
|
|
10
15
|
* The file is TypeScript and is evaluated by Node itself (type stripping, on by
|
|
11
16
|
* default in the Node this repository requires), so the generator needs no
|
|
@@ -17,17 +22,20 @@ import type { ClientConfigInput } from "./client-config.interface.js";
|
|
|
17
22
|
* is trusted: the config is the consumer's code, and a typo there should be a
|
|
18
23
|
* sentence naming the member, not a `TypeError` from deep inside resolution.
|
|
19
24
|
*/
|
|
20
|
-
/** The config file
|
|
21
|
-
export declare const CLIENT_CONFIG_FILE = "framework.client.
|
|
25
|
+
/** The config file `avclient init` writes: an ES module whatever the project's `type` (pilot.1). */
|
|
26
|
+
export declare const CLIENT_CONFIG_FILE = "framework.client.mts";
|
|
27
|
+
/** §15.2's spelling, still read for the setups written before pilot.1. */
|
|
28
|
+
export declare const LEGACY_CLIENT_CONFIG_FILE = "framework.client.ts";
|
|
22
29
|
export interface LoadedClientConfig {
|
|
23
30
|
/** The config file's absolute path; `generateAt` resolves against its directory. */
|
|
24
31
|
readonly file: string;
|
|
25
32
|
readonly config: ClientConfigInput;
|
|
26
33
|
}
|
|
27
34
|
/**
|
|
28
|
-
* Evaluates `<directory>/framework.client.ts
|
|
35
|
+
* Evaluates `<directory>/framework.client.mts`, or `framework.client.ts` when
|
|
36
|
+
* only that one is there.
|
|
29
37
|
*
|
|
30
|
-
* @throws ClientConfigError, in one line, when
|
|
31
|
-
* evaluate, or does not default-export a client config.
|
|
38
|
+
* @throws ClientConfigError, in one line, when neither or both are there, or
|
|
39
|
+
* the file does not evaluate, or does not default-export a client config.
|
|
32
40
|
*/
|
|
33
41
|
export declare function loadClientConfigFile(directory: string): Promise<LoadedClientConfig>;
|
|
@@ -3,12 +3,17 @@ import path from "node:path";
|
|
|
3
3
|
import { pathToFileURL } from "node:url";
|
|
4
4
|
import { ClientConfigError } from "./config.resolver.js";
|
|
5
5
|
/**
|
|
6
|
-
* Finds and evaluates the client config: `framework.client.
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* fact.
|
|
6
|
+
* Finds and evaluates the client config: `framework.client.mts` (or, for a setup
|
|
7
|
+
* written before pilot.1, `framework.client.ts`), default-exporting
|
|
8
|
+
* `defineClientConfig({ entrypoint, generateAt })` as §15.2 writes it. One file,
|
|
9
|
+
* looked up in one directory — no search up the tree, no `package.json` key —
|
|
10
|
+
* and when both spellings are present the run refuses rather than choose: two
|
|
11
|
+
* files would be two sources of the same fact.
|
|
12
|
+
*
|
|
13
|
+
* Why `.mts` (pilot.1): `npm init -y` writes `"type": "commonjs"`, under which
|
|
14
|
+
* Node reads a `.ts` file as CommonJS and the config's `import`/`export` stop
|
|
15
|
+
* the run. A `.mts` file is an ES module in every project. A `.ts` config that
|
|
16
|
+
* fails that way is told to rename itself.
|
|
12
17
|
*
|
|
13
18
|
* The file is TypeScript and is evaluated by Node itself (type stripping, on by
|
|
14
19
|
* default in the Node this repository requires), so the generator needs no
|
|
@@ -20,26 +25,36 @@ import { ClientConfigError } from "./config.resolver.js";
|
|
|
20
25
|
* is trusted: the config is the consumer's code, and a typo there should be a
|
|
21
26
|
* sentence naming the member, not a `TypeError` from deep inside resolution.
|
|
22
27
|
*/
|
|
23
|
-
/** The config file
|
|
24
|
-
export const CLIENT_CONFIG_FILE = "framework.client.
|
|
28
|
+
/** The config file `avclient init` writes: an ES module whatever the project's `type` (pilot.1). */
|
|
29
|
+
export const CLIENT_CONFIG_FILE = "framework.client.mts";
|
|
30
|
+
/** §15.2's spelling, still read for the setups written before pilot.1. */
|
|
31
|
+
export const LEGACY_CLIENT_CONFIG_FILE = "framework.client.ts";
|
|
25
32
|
/**
|
|
26
|
-
* Evaluates `<directory>/framework.client.ts
|
|
33
|
+
* Evaluates `<directory>/framework.client.mts`, or `framework.client.ts` when
|
|
34
|
+
* only that one is there.
|
|
27
35
|
*
|
|
28
|
-
* @throws ClientConfigError, in one line, when
|
|
29
|
-
* evaluate, or does not default-export a client config.
|
|
36
|
+
* @throws ClientConfigError, in one line, when neither or both are there, or
|
|
37
|
+
* the file does not evaluate, or does not default-export a client config.
|
|
30
38
|
*/
|
|
31
39
|
export async function loadClientConfigFile(directory) {
|
|
32
|
-
const
|
|
33
|
-
if (
|
|
34
|
-
throw new ClientConfigError(`
|
|
35
|
-
|
|
40
|
+
const present = [CLIENT_CONFIG_FILE, LEGACY_CLIENT_CONFIG_FILE].filter((name) => existsSync(path.resolve(directory, name)));
|
|
41
|
+
if (present.length > 1) {
|
|
42
|
+
throw new ClientConfigError(`both ${CLIENT_CONFIG_FILE} and ${LEGACY_CLIENT_CONFIG_FILE} are in ${path.resolve(directory)}, and only one may configure the generator; keep ${CLIENT_CONFIG_FILE} and delete ${LEGACY_CLIENT_CONFIG_FILE}.`);
|
|
43
|
+
}
|
|
44
|
+
const [name] = present;
|
|
45
|
+
if (name === undefined) {
|
|
46
|
+
throw new ClientConfigError(`no ${CLIENT_CONFIG_FILE} at ${path.resolve(directory, CLIENT_CONFIG_FILE)}; create it with ` +
|
|
47
|
+
"`export default defineClientConfig({ entrypoint, generateAt })` (or run `avclient init`) and run the generator from its directory.");
|
|
36
48
|
}
|
|
49
|
+
const file = path.resolve(directory, name);
|
|
37
50
|
let evaluated;
|
|
38
51
|
try {
|
|
39
52
|
evaluated = (await import(pathToFileURL(file).href));
|
|
40
53
|
}
|
|
41
54
|
catch (error) {
|
|
42
|
-
throw new ClientConfigError(`${file} could not be evaluated: ${firstLineOf(error)}
|
|
55
|
+
throw new ClientConfigError(`${file} could not be evaluated: ${firstLineOf(error)}${name === LEGACY_CLIENT_CONFIG_FILE && error instanceof SyntaxError
|
|
56
|
+
? `; if this project's package.json says "type": "commonjs" (npm init -y writes it), Node reads a .ts file as CommonJS — rename it ${CLIENT_CONFIG_FILE}, which is an ES module in every project`
|
|
57
|
+
: ""}`, { cause: error });
|
|
43
58
|
}
|
|
44
59
|
return { file, config: clientConfigOf(file, evaluated) };
|
|
45
60
|
}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* R4, §15.2 — the config `avclient init` writes: developer-owned, read by
|
|
3
|
+
* `avclient generate`. `file` names it in its own comment line: the
|
|
4
|
+
* `framework.client.mts` it writes, or — to recognize what pilot.0 wrote —
|
|
5
|
+
* `framework.client.ts`.
|
|
6
|
+
*/
|
|
2
7
|
export declare function clientConfigSource(input: {
|
|
3
8
|
readonly entrypoint: string;
|
|
4
9
|
readonly envVar: string | undefined;
|
|
5
10
|
readonly generateAt: string;
|
|
6
|
-
}): string;
|
|
11
|
+
}, file?: string): string;
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { CLIENT_CONFIG_FILE } from "../config/config.loader.js";
|
|
2
2
|
/** The package the written config imports its two functions from. */
|
|
3
3
|
const CLIENT_PACKAGE = "@aventara/client";
|
|
4
|
-
/**
|
|
5
|
-
|
|
4
|
+
/**
|
|
5
|
+
* R4, §15.2 — the config `avclient init` writes: developer-owned, read by
|
|
6
|
+
* `avclient generate`. `file` names it in its own comment line: the
|
|
7
|
+
* `framework.client.mts` it writes, or — to recognize what pilot.0 wrote —
|
|
8
|
+
* `framework.client.ts`.
|
|
9
|
+
*/
|
|
10
|
+
export function clientConfigSource(input, file = CLIENT_CONFIG_FILE) {
|
|
6
11
|
return [
|
|
7
12
|
// The specifier is spliced in, not written after `from`, so this module's own
|
|
8
13
|
// shipped JavaScript does not read as importing the package it names.
|
|
@@ -10,7 +15,7 @@ export function clientConfigSource(input) {
|
|
|
10
15
|
? `import { defineClientConfig } from ${JSON.stringify(CLIENT_PACKAGE)};`
|
|
11
16
|
: `import { defineClientConfig, env } from ${JSON.stringify(CLIENT_PACKAGE)};`,
|
|
12
17
|
"",
|
|
13
|
-
`// ${
|
|
18
|
+
`// ${file}: where the Aventara server is, and where its typed client goes.`,
|
|
14
19
|
"export default defineClientConfig({",
|
|
15
20
|
input.envVar === undefined
|
|
16
21
|
? `\tentrypoint: ${JSON.stringify(input.entrypoint)},`
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { runGenerate } from "../cli/generate.command.js";
|
|
4
4
|
import { CLIENT_CONFIG_FILE } from "../config/config.loader.js";
|
|
@@ -50,10 +50,14 @@ export async function runClientInit(command, io) {
|
|
|
50
50
|
const writes = plan.conflicts.length === 0 ? plan.keeping : plan.replacing;
|
|
51
51
|
for (const write of writes) {
|
|
52
52
|
const target = path.join(io.cwd, write.path);
|
|
53
|
+
if (write.content === undefined) {
|
|
54
|
+
rmSync(target, { force: true });
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
53
57
|
mkdirSync(path.dirname(target), { recursive: true });
|
|
54
58
|
writeFileSync(target, write.content);
|
|
55
59
|
}
|
|
56
|
-
io.stdout(`avclient: initialized ${io.cwd}: ${writes.map((write) => write.path).join(", ") || "nothing to change"}.\n`);
|
|
60
|
+
io.stdout(`avclient: initialized ${io.cwd}: ${writes.map((write) => (write.content === undefined ? `removed ${write.path}` : write.path)).join(", ") || "nothing to change"}.\n`);
|
|
57
61
|
const install = `${answers.packageManager} install`;
|
|
58
62
|
if (command.skipInstall) {
|
|
59
63
|
io.stdout(`Next:\n 1. Install @aventara/client ${clientVersion}: ${install}\n 2. With the server running: ${answers.packageManager} run ${GENERATE_SCRIPT}\n`);
|
|
@@ -9,9 +9,10 @@ import type { ClientProject } from "./client-project.inspector.js";
|
|
|
9
9
|
*/
|
|
10
10
|
/** R4: the script a frontend regenerates its client with. */
|
|
11
11
|
export declare const GENERATE_SCRIPT = "avclient:generate";
|
|
12
|
+
/** One change to a project file: its new content, or `undefined` to remove it. */
|
|
12
13
|
export type ClientInitWrite = {
|
|
13
14
|
readonly path: string;
|
|
14
|
-
readonly content: string;
|
|
15
|
+
readonly content: string | undefined;
|
|
15
16
|
};
|
|
16
17
|
export type ClientInitPlan = {
|
|
17
18
|
readonly keeping: readonly ClientInitWrite[];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CLIENT_CONFIG_FILE } from "../config/config.loader.js";
|
|
1
|
+
import { CLIENT_CONFIG_FILE, LEGACY_CLIENT_CONFIG_FILE, } from "../config/config.loader.js";
|
|
2
2
|
import { clientConfigSource } from "./client-config.template.js";
|
|
3
3
|
/**
|
|
4
4
|
* §4.5 — what `avclient init` writes, decided before anything is: the config
|
|
@@ -31,6 +31,17 @@ export function planClientInit(input) {
|
|
|
31
31
|
};
|
|
32
32
|
const config = clientConfigSource(answers);
|
|
33
33
|
const existingConfig = project.read(CLIENT_CONFIG_FILE);
|
|
34
|
+
// pilot.1: a `framework.client.ts` is moved to `framework.client.mts` — two
|
|
35
|
+
// configs would make `avclient generate` refuse. The one pilot.0 wrote for
|
|
36
|
+
// these answers moves without asking; anything else is a conflict.
|
|
37
|
+
const legacyConfig = project.read(LEGACY_CLIENT_CONFIG_FILE);
|
|
38
|
+
if (legacyConfig !== undefined) {
|
|
39
|
+
if (legacyConfig !== clientConfigSource(answers, LEGACY_CLIENT_CONFIG_FILE)) {
|
|
40
|
+
conflicts.push(`${LEGACY_CLIENT_CONFIG_FILE} (replaced by ${CLIENT_CONFIG_FILE})`);
|
|
41
|
+
}
|
|
42
|
+
keeping.push({ path: LEGACY_CLIENT_CONFIG_FILE, content: undefined });
|
|
43
|
+
replacing.push({ path: LEGACY_CLIENT_CONFIG_FILE, content: undefined });
|
|
44
|
+
}
|
|
34
45
|
if (existingConfig !== undefined && existingConfig !== config) {
|
|
35
46
|
conflicts.push(CLIENT_CONFIG_FILE);
|
|
36
47
|
}
|
|
@@ -19,9 +19,12 @@ import type { EmittedTree } from "../emit/emitted-tree.interface.js";
|
|
|
19
19
|
* but TypeScript's own `lib` files, so an import the tree cannot satisfy itself
|
|
20
20
|
* fails here even when a `node_modules` beside the output could satisfy it
|
|
21
21
|
* (§15.5).
|
|
22
|
-
* - **Syntax**, when it does not
|
|
23
|
-
* (
|
|
24
|
-
*
|
|
22
|
+
* - **Syntax**, when it does not — or when the `typescript` that resolves has no
|
|
23
|
+
* classic compiler API (TypeScript 7, plan B3; pilot.1): each file through
|
|
24
|
+
* Node's own TypeScript parser (`node:module`'s `stripTypeScriptTypes`), with a
|
|
25
|
+
* loud warning that the output was NOT type-checked. Degraded, never skipped
|
|
26
|
+
* (S7), and never a refusal: the developer's own `tsc` still checks the tree
|
|
27
|
+
* when their project compiles.
|
|
25
28
|
*/
|
|
26
29
|
/** The `typescript` module, as the validator uses it. */
|
|
27
30
|
export type TypeScriptCompiler = typeof ts;
|
|
@@ -38,16 +41,6 @@ export type TypeScriptResolver = () => Promise<TypeScriptCompiler | undefined>;
|
|
|
38
41
|
export declare function typeScriptResolverFor(specifier: string): TypeScriptResolver;
|
|
39
42
|
/** The `typescript` this package's optional peer names (Q5). */
|
|
40
43
|
export declare const resolveInstalledTypeScript: TypeScriptResolver;
|
|
41
|
-
/**
|
|
42
|
-
* D4 — the resolved `typescript` has no classic compiler API. TypeScript 7 is
|
|
43
|
-
* such a package (plan B3: it exports its version and nothing else), and before
|
|
44
|
-
* this refusal the type check died on it with a `TypeError` stack. A refusal,
|
|
45
|
-
* not a degraded check: a compiler the developer installed is not "absent", and
|
|
46
|
-
* passing it over silently would weaken the check they chose.
|
|
47
|
-
*/
|
|
48
|
-
export declare class UnsupportedTypeScriptError extends Error {
|
|
49
|
-
readonly name = "UnsupportedTypeScriptError";
|
|
50
|
-
}
|
|
51
44
|
/** How deeply the tree was judged. */
|
|
52
45
|
export type OutputCheck = "types" | "syntax" | "shape";
|
|
53
46
|
export interface OutputAccepted {
|
|
@@ -70,6 +63,12 @@ export type OutputValidation = OutputAccepted | OutputRejected;
|
|
|
70
63
|
export declare const TYPESCRIPT_UNRESOLVED_WARNING: string;
|
|
71
64
|
/** The warning a run with neither `typescript` nor Node's parser carries. */
|
|
72
65
|
export declare const SYNTAX_CHECK_UNAVAILABLE_WARNING: string;
|
|
66
|
+
/**
|
|
67
|
+
* The warning a run carries when the `typescript` that resolves is one the
|
|
68
|
+
* generator cannot type-check with (TypeScript 7): what was checked instead,
|
|
69
|
+
* and where the type check still happens.
|
|
70
|
+
*/
|
|
71
|
+
export declare function typeScriptWithoutCompilerApiWarning(version: string, checked: "syntax" | "shape"): string;
|
|
73
72
|
/**
|
|
74
73
|
* Judges `directory`, which the caller has just filled with `tree`.
|
|
75
74
|
*/
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
1
|
import { readdir, readFile } from "node:fs/promises";
|
|
3
2
|
import * as nodeModule from "node:module";
|
|
4
3
|
import { createRequire } from "node:module";
|
|
@@ -28,30 +27,22 @@ export function typeScriptResolverFor(specifier) {
|
|
|
28
27
|
/** The `typescript` this package's optional peer names (Q5). */
|
|
29
28
|
export const resolveInstalledTypeScript = typeScriptResolverFor("typescript");
|
|
30
29
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
30
|
+
* Whether `compiler` has the classic compiler API {@link typeFindingsOf} uses.
|
|
31
|
+
* TypeScript 7 does not (plan B3: its package exports its version and nothing
|
|
32
|
+
* else); before pilot.1 the generator refused it, and its peer range stopped
|
|
33
|
+
* below it — which made `npm i` fail with ERESOLVE in any frontend whose
|
|
34
|
+
* `typescript` is the current `latest`.
|
|
36
35
|
*/
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
function hasClassicCompilerApi(compiler) {
|
|
37
|
+
// One member, not `Partial<typeof ts>`: a mapped type over the whole
|
|
38
|
+
// compiler namespace costs thousands of instantiations to ask one question.
|
|
39
|
+
return (typeof compiler.createProgram ===
|
|
40
|
+
"function");
|
|
39
41
|
}
|
|
40
|
-
/**
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return manifest.peerDependencies?.typescript ?? "(undeclared)";
|
|
45
|
-
}
|
|
46
|
-
/** Refuses a compiler without the API {@link typeFindingsOf} uses. */
|
|
47
|
-
function assertClassicCompilerApi(compiler) {
|
|
48
|
-
// Two members, not `Partial<typeof ts>`: a mapped type over the whole
|
|
49
|
-
// compiler namespace costs thousands of instantiations to ask two questions.
|
|
50
|
-
const api = compiler;
|
|
51
|
-
if (typeof api.createProgram === "function") {
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
throw new UnsupportedTypeScriptError(`the installed \`typescript\` ${api.version ?? "(no version)"} has no classic compiler API (\`createProgram\`) to type-check the generated output with; install \`typescript\` in the range @aventara/client supports, ${declaredTypeScriptRange()}.`);
|
|
42
|
+
/** The version a resolved `typescript` names, whatever its API. */
|
|
43
|
+
function versionOf(compiler) {
|
|
44
|
+
const { version } = compiler;
|
|
45
|
+
return typeof version === "string" ? version : "(no version)";
|
|
55
46
|
}
|
|
56
47
|
/**
|
|
57
48
|
* The warning a run without `typescript` carries. Loud on purpose, in its words:
|
|
@@ -64,6 +55,16 @@ export const TYPESCRIPT_UNRESOLVED_WARNING = "`typescript` could not be resolved
|
|
|
64
55
|
export const SYNTAX_CHECK_UNAVAILABLE_WARNING = "neither `typescript` nor Node's TypeScript parser is available, so the generated " +
|
|
65
56
|
"output was NOT type-checked or parsed — only its shape was. Install `typescript` (an " +
|
|
66
57
|
"optional peer of @aventara/client) in this project to restore the type check.";
|
|
58
|
+
/**
|
|
59
|
+
* The warning a run carries when the `typescript` that resolves is one the
|
|
60
|
+
* generator cannot type-check with (TypeScript 7): what was checked instead,
|
|
61
|
+
* and where the type check still happens.
|
|
62
|
+
*/
|
|
63
|
+
export function typeScriptWithoutCompilerApiWarning(version, checked) {
|
|
64
|
+
return (`the installed \`typescript\` ${version} has no classic compiler API (\`createProgram\`), so the generated ` +
|
|
65
|
+
`output was NOT type-checked${checked === "shape" ? " or parsed — only its shape was" : " — only its shape and syntax were"}. ` +
|
|
66
|
+
"Your project's own `tsc` checks it when it compiles; a `typescript` 5.5 to 6 restores the generator's own type check.");
|
|
67
|
+
}
|
|
67
68
|
/**
|
|
68
69
|
* Judges `directory`, which the caller has just filled with `tree`.
|
|
69
70
|
*/
|
|
@@ -73,8 +74,7 @@ export async function validateOutputTree(directory, tree, resolveTypeScript = re
|
|
|
73
74
|
return { accepted: false, checked: "shape", findings: shapeFindings };
|
|
74
75
|
}
|
|
75
76
|
const compiler = await resolveTypeScript();
|
|
76
|
-
if (compiler !== undefined) {
|
|
77
|
-
assertClassicCompilerApi(compiler);
|
|
77
|
+
if (compiler !== undefined && hasClassicCompilerApi(compiler)) {
|
|
78
78
|
const findings = typeFindingsOf(compiler, directory, tree);
|
|
79
79
|
return findings.length > 0
|
|
80
80
|
? { accepted: false, checked: "types", findings }
|
|
@@ -85,7 +85,11 @@ export async function validateOutputTree(directory, tree, resolveTypeScript = re
|
|
|
85
85
|
return {
|
|
86
86
|
accepted: true,
|
|
87
87
|
checked: "shape",
|
|
88
|
-
warnings: [
|
|
88
|
+
warnings: [
|
|
89
|
+
compiler === undefined
|
|
90
|
+
? SYNTAX_CHECK_UNAVAILABLE_WARNING
|
|
91
|
+
: typeScriptWithoutCompilerApiWarning(versionOf(compiler), "shape"),
|
|
92
|
+
],
|
|
89
93
|
};
|
|
90
94
|
}
|
|
91
95
|
const findings = syntaxFindingsOf(strip, tree);
|
|
@@ -94,7 +98,11 @@ export async function validateOutputTree(directory, tree, resolveTypeScript = re
|
|
|
94
98
|
: {
|
|
95
99
|
accepted: true,
|
|
96
100
|
checked: "syntax",
|
|
97
|
-
warnings: [
|
|
101
|
+
warnings: [
|
|
102
|
+
compiler === undefined
|
|
103
|
+
? TYPESCRIPT_UNRESOLVED_WARNING
|
|
104
|
+
: typeScriptWithoutCompilerApiWarning(versionOf(compiler), "syntax"),
|
|
105
|
+
],
|
|
98
106
|
};
|
|
99
107
|
}
|
|
100
108
|
/* ------------------------------------------------------------------ *
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aventara/client",
|
|
3
|
-
"version": "0.1.0-pilot.
|
|
3
|
+
"version": "0.1.0-pilot.1",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE",
|
|
5
5
|
"description": "Development-time generator for Aventara typed remote clients.",
|
|
6
6
|
"type": "module",
|
|
@@ -23,10 +23,10 @@
|
|
|
23
23
|
"LICENSE-ADDITIONAL-PERMISSION.md"
|
|
24
24
|
],
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@aventara/core": "0.1.0-pilot.
|
|
26
|
+
"@aventara/core": "0.1.0-pilot.1"
|
|
27
27
|
},
|
|
28
28
|
"peerDependencies": {
|
|
29
|
-
"typescript": ">=5.5.0
|
|
29
|
+
"typescript": ">=5.5.0"
|
|
30
30
|
},
|
|
31
31
|
"peerDependenciesMeta": {
|
|
32
32
|
"typescript": {
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"access": "public"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@aventara/testing": "0.1.0-pilot.
|
|
40
|
+
"@aventara/testing": "0.1.0-pilot.1",
|
|
41
41
|
"@types/node": "24.10.1",
|
|
42
42
|
"typescript": "^5.9.2"
|
|
43
43
|
},
|