@aventara/client 0.1.0-pilot.2 → 0.1.0-pilot.3
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 +3 -2
- package/dist/generate.d.ts +4 -1
- package/dist/init/client-init.orchestrator.js +7 -0
- package/dist/init/client-init.planner.js +10 -0
- package/dist/output/output.validator.d.ts +38 -16
- package/dist/output/output.validator.js +68 -16
- package/dist/output/output.writer.d.ts +4 -1
- package/dist/output/output.writer.js +2 -2
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -91,8 +91,9 @@ supported yet.
|
|
|
91
91
|
- **A failed run leaves the previous output exactly as it was.** The whole tree is written to a staging directory inside
|
|
92
92
|
`generateAt`, validated there as one program, and only then moved into place. A run killed part-way is repaired by the
|
|
93
93
|
next run that writes, before it writes anything. A first run that fails removes the directories it created.
|
|
94
|
-
- **The validate step is a real type check** when the optional peer `typescript` (5.5 to 6)
|
|
95
|
-
|
|
94
|
+
- **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@pilot init` type-checks too — under a consumer's strictest plausible
|
|
96
|
+
settings and with nothing outside the tree resolvable. Without it — or under
|
|
96
97
|
TypeScript 7, which has no classic compiler API to check with — the check degrades to a parse with Node's own
|
|
97
98
|
TypeScript parser, and says so loudly in one warning; it is never skipped, and the client is still written. The peer
|
|
98
99
|
range has no upper bound, so a frontend on TypeScript 7 installs it; your own `tsc` checks the tree when it compiles.
|
package/dist/generate.d.ts
CHANGED
|
@@ -32,7 +32,10 @@ export interface ClientGenerationInput {
|
|
|
32
32
|
readonly overrideForeign?: boolean;
|
|
33
33
|
/** Injected for tests; the platform `fetch` otherwise. */
|
|
34
34
|
readonly fetch?: ContractFetch;
|
|
35
|
-
/**
|
|
35
|
+
/**
|
|
36
|
+
* The `typescript` optional peer; by default the project's own, resolved from
|
|
37
|
+
* `generateAt`.
|
|
38
|
+
*/
|
|
36
39
|
readonly resolveTypeScript?: TypeScriptResolver;
|
|
37
40
|
}
|
|
38
41
|
export interface ClientGenerated {
|
|
@@ -2,7 +2,9 @@ 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";
|
|
5
|
+
import { resolveModuleStyle } from "../config/module-style.resolver.js";
|
|
5
6
|
import { ContractTransportError } from "../contract/contract.fetcher.js";
|
|
7
|
+
import { CLIENT_ENTRY_FILE } from "../emit/emitted-tree.interface.js";
|
|
6
8
|
import { ClientInitNotConfirmedError, ClientInitStepError, } from "./client-init.errors.js";
|
|
7
9
|
import { GENERATE_SCRIPT, planClientInit } from "./client-init.planner.js";
|
|
8
10
|
import { resolveClientInitAnswers } from "./client-init.questions.js";
|
|
@@ -26,6 +28,11 @@ export async function runClientInit(command, io) {
|
|
|
26
28
|
detectedPackageManager: project.lockfile ??
|
|
27
29
|
(userAgent?.split("/")[0] === "pnpm" ? "pnpm" : "npm"),
|
|
28
30
|
});
|
|
31
|
+
const generateAt = path.resolve(io.cwd, answers.generateAt);
|
|
32
|
+
resolveModuleStyle({
|
|
33
|
+
generateAt,
|
|
34
|
+
entryFile: path.join(generateAt, CLIENT_ENTRY_FILE),
|
|
35
|
+
});
|
|
29
36
|
const clientVersion = ownVersion();
|
|
30
37
|
const plan = planClientInit({ project, answers, clientVersion });
|
|
31
38
|
if (plan.conflicts.length > 0 && !command.yes) {
|
|
@@ -47,6 +47,16 @@ export function planClientInit(input) {
|
|
|
47
47
|
replaced[at] = line;
|
|
48
48
|
}
|
|
49
49
|
add(".env", before, `${kept.join("\n")}\n`, `${replaced.join("\n")}\n`);
|
|
50
|
+
const ignore = project.read(".gitignore");
|
|
51
|
+
const ignored = (ignore ?? "")
|
|
52
|
+
.split("\n")
|
|
53
|
+
.some((line) => line.trim() === ".env");
|
|
54
|
+
if (!ignored) {
|
|
55
|
+
const appended = ignore === undefined || ignore === ""
|
|
56
|
+
? ".env\n"
|
|
57
|
+
: `${ignore.endsWith("\n") ? ignore : `${ignore}\n`}.env\n`;
|
|
58
|
+
add(".gitignore", ignore, appended, appended);
|
|
59
|
+
}
|
|
50
60
|
}
|
|
51
61
|
const manifest = (replace) => {
|
|
52
62
|
const parsed = JSON.parse(project.manifestText);
|
|
@@ -14,17 +14,19 @@ import type { ClientEmission } from "../emit/emitted-tree.interface.js";
|
|
|
14
14
|
* so a file without it would make the next run refuse. The imports are read
|
|
15
15
|
* lexically (`module-specifier.scanner.ts`), with no compiler — so a wrongly
|
|
16
16
|
* spelled import is refused even where the type check cannot run (TypeScript 7).
|
|
17
|
-
* - **Types**, when the `typescript` optional peer resolves
|
|
18
|
-
* the tree under a consumer's strictest
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* `lib` files, so an import
|
|
22
|
-
* a `node_modules` beside
|
|
17
|
+
* - **Types**, when the `typescript` optional peer resolves FROM THE PROJECT in
|
|
18
|
+
* its peer range: a real program over the tree under a consumer's strictest
|
|
19
|
+
* plausible settings, in the project's module resolution and format
|
|
20
|
+
* (`compilerOptionsFor`). The tree is judged ALONE — the compiler host serves
|
|
21
|
+
* nothing outside the directory but TypeScript's own `lib` files, so an import
|
|
22
|
+
* the tree cannot satisfy itself fails here even when a `node_modules` beside
|
|
23
|
+
* the output could satisfy it.
|
|
23
24
|
* - **Syntax**, when it does not — or when the `typescript` that resolves has no
|
|
24
25
|
* classic compiler API: each file through Node's own TypeScript parser
|
|
25
|
-
* (`node:module`'s `stripTypeScriptTypes
|
|
26
|
-
* was NOT type-checked. Degraded,
|
|
27
|
-
* developer's own `tsc` still checks the
|
|
26
|
+
* (`node:module`'s `stripTypeScriptTypes`, its ExperimentalWarning held back),
|
|
27
|
+
* with a loud warning of our own that the output was NOT type-checked. Degraded,
|
|
28
|
+
* never skipped, and never a refusal: the developer's own `tsc` still checks the
|
|
29
|
+
* tree when their project compiles.
|
|
28
30
|
*/
|
|
29
31
|
/** The `typescript` module, as the validator uses it. */
|
|
30
32
|
export type TypeScriptCompiler = typeof ts;
|
|
@@ -35,12 +37,23 @@ export type TypeScriptCompiler = typeof ts;
|
|
|
35
37
|
*/
|
|
36
38
|
export type TypeScriptResolver = () => Promise<TypeScriptCompiler | undefined>;
|
|
37
39
|
/**
|
|
38
|
-
* A resolver for `specifier`, looked up from
|
|
39
|
-
*
|
|
40
|
+
* A resolver for `specifier`, looked up from `directory` the way Node looks up
|
|
41
|
+
* a package from a file there: its `node_modules`, then each parent's.
|
|
40
42
|
*/
|
|
41
|
-
export declare function typeScriptResolverFor(specifier: string): TypeScriptResolver;
|
|
42
|
-
/**
|
|
43
|
-
|
|
43
|
+
export declare function typeScriptResolverFor(specifier: string, directory: string): TypeScriptResolver;
|
|
44
|
+
/**
|
|
45
|
+
* The `typescript` of the project that compiles the client, resolved from its
|
|
46
|
+
* `generateAt`. Never this package's own: run through `npx`, the generator is
|
|
47
|
+
* installed in npm's cache, where its optional peer never is — and the type check
|
|
48
|
+
* is a stand-in for the project's own `tsc`, so the project's compiler is the one
|
|
49
|
+
* that answers.
|
|
50
|
+
*/
|
|
51
|
+
export declare function projectTypeScriptResolver(generateAt: string): TypeScriptResolver;
|
|
52
|
+
/**
|
|
53
|
+
* The lowest `typescript` the generator type-checks with: this package's
|
|
54
|
+
* optional peer range, `>=5.5.0` (`packaging-gate.spec.ts` holds the two equal).
|
|
55
|
+
*/
|
|
56
|
+
export declare const MINIMUM_TYPESCRIPT = "5.5.0";
|
|
44
57
|
/** How deeply the tree was judged. */
|
|
45
58
|
export type OutputCheck = "types" | "syntax" | "shape";
|
|
46
59
|
export interface OutputAccepted {
|
|
@@ -70,6 +83,15 @@ export declare const SYNTAX_CHECK_UNAVAILABLE_WARNING: string;
|
|
|
70
83
|
*/
|
|
71
84
|
export declare function typeScriptWithoutCompilerApiWarning(version: string, checked: "syntax" | "shape"): string;
|
|
72
85
|
/**
|
|
73
|
-
*
|
|
86
|
+
* The warning a run carries when the project's `typescript` is older than the
|
|
87
|
+
* peer range: its compiler API lacks options the check sets (`Bundler`
|
|
88
|
+
* resolution, `verbatimModuleSyntax`), so its verdict would not be the one a
|
|
89
|
+
* supported compiler gives.
|
|
90
|
+
*/
|
|
91
|
+
export declare function typeScriptBelowRangeWarning(version: string, checked: "syntax" | "shape"): string;
|
|
92
|
+
/**
|
|
93
|
+
* Judges `directory`, which the caller has just filled with `tree`, with the
|
|
94
|
+
* `typescript` `resolveTypeScript` finds — the project's
|
|
95
|
+
* ({@link projectTypeScriptResolver}).
|
|
74
96
|
*/
|
|
75
|
-
export declare function validateOutputTree(directory: string, emission: Pick<ClientEmission, "style" | "tree">, resolveTypeScript
|
|
97
|
+
export declare function validateOutputTree(directory: string, emission: Pick<ClientEmission, "style" | "tree">, resolveTypeScript: TypeScriptResolver): Promise<OutputValidation>;
|
|
@@ -5,11 +5,11 @@ import path from "node:path";
|
|
|
5
5
|
import { pathToFileURL } from "node:url";
|
|
6
6
|
import { carriesGeneratedOwnership } from "../emit/banner.emitter.js";
|
|
7
7
|
import { scanModuleSpecifiers } from "../emit/module-specifier.scanner.js";
|
|
8
|
-
export function typeScriptResolverFor(specifier) {
|
|
8
|
+
export function typeScriptResolverFor(specifier, directory) {
|
|
9
9
|
return async () => {
|
|
10
10
|
let resolved;
|
|
11
11
|
try {
|
|
12
|
-
resolved = createRequire(
|
|
12
|
+
resolved = createRequire(path.join(directory, "package.json")).resolve(specifier);
|
|
13
13
|
}
|
|
14
14
|
catch (error) {
|
|
15
15
|
if (isErrorWithCode(error, "MODULE_NOT_FOUND")) {
|
|
@@ -21,7 +21,25 @@ export function typeScriptResolverFor(specifier) {
|
|
|
21
21
|
return loaded.default ?? loaded;
|
|
22
22
|
};
|
|
23
23
|
}
|
|
24
|
-
export
|
|
24
|
+
export function projectTypeScriptResolver(generateAt) {
|
|
25
|
+
return typeScriptResolverFor("typescript", generateAt);
|
|
26
|
+
}
|
|
27
|
+
export const MINIMUM_TYPESCRIPT = "5.5.0";
|
|
28
|
+
function meetsMinimum(version) {
|
|
29
|
+
const parts = /^(\d+)\.(\d+)\.(\d+)/.exec(version);
|
|
30
|
+
const minimum = MINIMUM_TYPESCRIPT.split(".").map(Number);
|
|
31
|
+
if (parts === null) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
for (let at = 0; at < 3; at += 1) {
|
|
35
|
+
const have = Number(parts[at + 1]);
|
|
36
|
+
const need = minimum[at];
|
|
37
|
+
if (have !== need) {
|
|
38
|
+
return have > need;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
25
43
|
function hasClassicCompilerApi(compiler) {
|
|
26
44
|
return (typeof compiler.createProgram ===
|
|
27
45
|
"function");
|
|
@@ -41,14 +59,34 @@ export function typeScriptWithoutCompilerApiWarning(version, checked) {
|
|
|
41
59
|
`output was NOT type-checked${checked === "shape" ? " or parsed — only its shape was" : " — only its shape and syntax were"}. ` +
|
|
42
60
|
"Your project's own `tsc` checks it when it compiles; a `typescript` 5.5 to 6 restores the generator's own type check.");
|
|
43
61
|
}
|
|
44
|
-
export
|
|
62
|
+
export function typeScriptBelowRangeWarning(version, checked) {
|
|
63
|
+
return (`the installed \`typescript\` ${version} is older than ${MINIMUM_TYPESCRIPT}, the lowest @aventara/client supports, so the generated ` +
|
|
64
|
+
`output was NOT type-checked${checked === "shape" ? " or parsed — only its shape was" : " — only its shape and syntax were"}. ` +
|
|
65
|
+
`Upgrade the project's \`typescript\` to ${MINIMUM_TYPESCRIPT} or later to restore the type check.`);
|
|
66
|
+
}
|
|
67
|
+
function degradedWarning(compiler, checked) {
|
|
68
|
+
if (compiler === undefined) {
|
|
69
|
+
return checked === "syntax"
|
|
70
|
+
? TYPESCRIPT_UNRESOLVED_WARNING
|
|
71
|
+
: SYNTAX_CHECK_UNAVAILABLE_WARNING;
|
|
72
|
+
}
|
|
73
|
+
if (!hasClassicCompilerApi(compiler)) {
|
|
74
|
+
return typeScriptWithoutCompilerApiWarning(versionOf(compiler), checked);
|
|
75
|
+
}
|
|
76
|
+
if (!meetsMinimum(versionOf(compiler))) {
|
|
77
|
+
return typeScriptBelowRangeWarning(versionOf(compiler), checked);
|
|
78
|
+
}
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
export async function validateOutputTree(directory, emission, resolveTypeScript) {
|
|
45
82
|
const { tree, style } = emission;
|
|
46
83
|
const shapeFindings = await shapeFindingsOf(directory, tree, style);
|
|
47
84
|
if (shapeFindings.length > 0) {
|
|
48
85
|
return { accepted: false, checked: "shape", findings: shapeFindings };
|
|
49
86
|
}
|
|
50
87
|
const compiler = await resolveTypeScript();
|
|
51
|
-
if (compiler !== undefined &&
|
|
88
|
+
if (compiler !== undefined &&
|
|
89
|
+
degradedWarning(compiler, "syntax") === undefined) {
|
|
52
90
|
const findings = typeFindingsOf(compiler, directory, tree, style);
|
|
53
91
|
return findings.length > 0
|
|
54
92
|
? { accepted: false, checked: "types", findings }
|
|
@@ -59,11 +97,7 @@ export async function validateOutputTree(directory, emission, resolveTypeScript
|
|
|
59
97
|
return {
|
|
60
98
|
accepted: true,
|
|
61
99
|
checked: "shape",
|
|
62
|
-
warnings: [
|
|
63
|
-
compiler === undefined
|
|
64
|
-
? SYNTAX_CHECK_UNAVAILABLE_WARNING
|
|
65
|
-
: typeScriptWithoutCompilerApiWarning(versionOf(compiler), "shape"),
|
|
66
|
-
],
|
|
100
|
+
warnings: [degradedWarning(compiler, "shape")],
|
|
67
101
|
};
|
|
68
102
|
}
|
|
69
103
|
const findings = syntaxFindingsOf(strip, tree);
|
|
@@ -72,11 +106,7 @@ export async function validateOutputTree(directory, emission, resolveTypeScript
|
|
|
72
106
|
: {
|
|
73
107
|
accepted: true,
|
|
74
108
|
checked: "syntax",
|
|
75
|
-
warnings: [
|
|
76
|
-
compiler === undefined
|
|
77
|
-
? TYPESCRIPT_UNRESOLVED_WARNING
|
|
78
|
-
: typeScriptWithoutCompilerApiWarning(versionOf(compiler), "syntax"),
|
|
79
|
-
],
|
|
109
|
+
warnings: [degradedWarning(compiler, "syntax")],
|
|
80
110
|
};
|
|
81
111
|
}
|
|
82
112
|
async function shapeFindingsOf(directory, tree, style) {
|
|
@@ -225,7 +255,29 @@ function isInside(directory, file) {
|
|
|
225
255
|
}
|
|
226
256
|
function nodeTypeScriptParser() {
|
|
227
257
|
const candidate = nodeModule.stripTypeScriptTypes;
|
|
228
|
-
|
|
258
|
+
if (typeof candidate !== "function") {
|
|
259
|
+
return undefined;
|
|
260
|
+
}
|
|
261
|
+
return (source) => {
|
|
262
|
+
const emitWarning = process.emitWarning;
|
|
263
|
+
process.emitWarning = function (warning, ...rest) {
|
|
264
|
+
const [kind] = rest;
|
|
265
|
+
const type = typeof kind === "string"
|
|
266
|
+
? kind
|
|
267
|
+
: kind?.type;
|
|
268
|
+
if (type === "ExperimentalWarning" &&
|
|
269
|
+
String(warning).startsWith("stripTypeScriptTypes ")) {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
emitWarning.call(this, warning, ...rest);
|
|
273
|
+
};
|
|
274
|
+
try {
|
|
275
|
+
return candidate(source);
|
|
276
|
+
}
|
|
277
|
+
finally {
|
|
278
|
+
process.emitWarning = emitWarning;
|
|
279
|
+
}
|
|
280
|
+
};
|
|
229
281
|
}
|
|
230
282
|
function syntaxFindingsOf(strip, tree) {
|
|
231
283
|
const decoder = new TextDecoder("utf-8");
|
|
@@ -118,7 +118,10 @@ export interface ClientOutputWriteInput {
|
|
|
118
118
|
* refused.
|
|
119
119
|
*/
|
|
120
120
|
readonly overrideForeign?: readonly string[];
|
|
121
|
-
/**
|
|
121
|
+
/**
|
|
122
|
+
* The `typescript` optional peer; by default the project's own, resolved from
|
|
123
|
+
* `generateAt`.
|
|
124
|
+
*/
|
|
122
125
|
readonly resolveTypeScript?: TypeScriptResolver;
|
|
123
126
|
readonly fileSystem?: OutputFileSystem;
|
|
124
127
|
}
|
|
@@ -3,7 +3,7 @@ import * as fsPromises from "node:fs/promises";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { carriesGeneratedOwnership, GENERATED_OWNERSHIP_HEAD_BYTES, } from "../emit/banner.emitter.js";
|
|
5
5
|
import { CLIENT_ENTRY_FILES, GENERATED_DIRECTORY, LEGACY_CLIENT_ENTRY_FILES, } from "../emit/emitted-tree.interface.js";
|
|
6
|
-
import {
|
|
6
|
+
import { projectTypeScriptResolver, validateOutputTree, } from "./output.validator.js";
|
|
7
7
|
export class OutputWriteError extends Error {
|
|
8
8
|
stage;
|
|
9
9
|
name = "OutputWriteError";
|
|
@@ -94,7 +94,7 @@ export async function writeClientOutput(input) {
|
|
|
94
94
|
await fs.writeFile(target, file.bytes);
|
|
95
95
|
}
|
|
96
96
|
});
|
|
97
|
-
const validation = await stage("validate", paths, () => validateOutputTree(staging, input.emission, input.resolveTypeScript ??
|
|
97
|
+
const validation = await stage("validate", paths, () => validateOutputTree(staging, input.emission, input.resolveTypeScript ?? projectTypeScriptResolver(generateAt)));
|
|
98
98
|
if (validation.accepted) {
|
|
99
99
|
validationWarnings = validation.warnings;
|
|
100
100
|
}
|
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.3",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE",
|
|
5
5
|
"description": "Development-time generator for Aventara typed remote clients.",
|
|
6
6
|
"type": "module",
|
|
@@ -23,7 +23,7 @@
|
|
|
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.3",
|
|
27
27
|
"c12": "3.3.4",
|
|
28
28
|
"get-tsconfig": "4.10.0"
|
|
29
29
|
},
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"access": "public"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@aventara/testing": "0.1.0-pilot.
|
|
42
|
+
"@aventara/testing": "0.1.0-pilot.3",
|
|
43
43
|
"@types/node": "24.10.1",
|
|
44
44
|
"typescript": "^5.9.2"
|
|
45
45
|
},
|