@dudousxd/nestjs-codegen 0.2.1 → 0.4.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 +57 -0
- package/dist/cli/main.cjs +1229 -1612
- package/dist/cli/main.cjs.map +1 -1
- package/dist/cli/main.js +1211 -1594
- package/dist/cli/main.js.map +1 -1
- package/dist/extension/index.cjs +12 -2
- package/dist/extension/index.cjs.map +1 -1
- package/dist/extension/index.d.cts +1 -1
- package/dist/extension/index.d.ts +1 -1
- package/dist/extension/index.js +10 -1
- package/dist/extension/index.js.map +1 -1
- package/dist/{index-BwIRjOQA.d.cts → index-DA4uySjo.d.cts} +84 -41
- package/dist/{index-BwIRjOQA.d.ts → index-DA4uySjo.d.ts} +84 -41
- package/dist/index.cjs +1073 -1460
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +56 -15
- package/dist/index.d.ts +56 -15
- package/dist/index.js +1043 -1430
- package/dist/index.js.map +1 -1
- package/dist/nest/index.cjs +934 -1365
- package/dist/nest/index.cjs.map +1 -1
- package/dist/nest/index.d.cts +9 -2
- package/dist/nest/index.d.ts +9 -2
- package/dist/nest/index.js +921 -1352
- package/dist/nest/index.js.map +1 -1
- package/package.json +3 -2
package/dist/cli/main.cjs
CHANGED
|
@@ -54,112 +54,9 @@ var CodegenError = class extends Error {
|
|
|
54
54
|
}
|
|
55
55
|
};
|
|
56
56
|
|
|
57
|
-
// src/adapters/zod.ts
|
|
58
|
-
function toObjectKey(name) {
|
|
59
|
-
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);
|
|
60
|
-
}
|
|
61
|
-
function messageSuffix(messageRaw2) {
|
|
62
|
-
return messageRaw2 ? `{ message: ${messageRaw2} }` : "";
|
|
63
|
-
}
|
|
64
|
-
function renderStringChecks(checks) {
|
|
65
|
-
let out = "";
|
|
66
|
-
for (const c of checks) {
|
|
67
|
-
switch (c.check) {
|
|
68
|
-
case "email":
|
|
69
|
-
out += `.email(${messageSuffix(c.messageRaw)})`;
|
|
70
|
-
break;
|
|
71
|
-
case "url":
|
|
72
|
-
out += `.url(${messageSuffix(c.messageRaw)})`;
|
|
73
|
-
break;
|
|
74
|
-
case "uuid":
|
|
75
|
-
out += `.uuid(${messageSuffix(c.messageRaw)})`;
|
|
76
|
-
break;
|
|
77
|
-
case "regex":
|
|
78
|
-
out += `.regex(${c.pattern})`;
|
|
79
|
-
break;
|
|
80
|
-
case "min":
|
|
81
|
-
out += `.min(${c.value})`;
|
|
82
|
-
break;
|
|
83
|
-
case "max":
|
|
84
|
-
out += `.max(${c.value})`;
|
|
85
|
-
break;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
return out;
|
|
89
|
-
}
|
|
90
|
-
function render(node, ctx) {
|
|
91
|
-
switch (node.kind) {
|
|
92
|
-
case "string":
|
|
93
|
-
return `z.string()${renderStringChecks(node.checks)}`;
|
|
94
|
-
case "number": {
|
|
95
|
-
let out = "z.number()";
|
|
96
|
-
for (const c of node.checks) {
|
|
97
|
-
if (c.check === "int") out += ".int()";
|
|
98
|
-
else if (c.check === "positive") out += ".positive()";
|
|
99
|
-
else if (c.check === "negative") out += ".negative()";
|
|
100
|
-
else if (c.check === "min") out += `.min(${c.value})`;
|
|
101
|
-
else if (c.check === "max") out += `.max(${c.value})`;
|
|
102
|
-
}
|
|
103
|
-
return out;
|
|
104
|
-
}
|
|
105
|
-
case "boolean":
|
|
106
|
-
return "z.boolean()";
|
|
107
|
-
case "date":
|
|
108
|
-
return "z.coerce.date()";
|
|
109
|
-
case "unknown":
|
|
110
|
-
return node.note ? `z.unknown() /* ${node.note} */` : "z.unknown()";
|
|
111
|
-
case "instanceof":
|
|
112
|
-
return `z.instanceof(${node.ctor})`;
|
|
113
|
-
case "enum":
|
|
114
|
-
return `z.enum([${node.literals.join(", ")}])`;
|
|
115
|
-
case "literal":
|
|
116
|
-
return `z.literal(${node.raw})`;
|
|
117
|
-
case "union":
|
|
118
|
-
return `z.union([${node.options.map((o) => render(o, ctx)).join(", ")}])`;
|
|
119
|
-
case "object": {
|
|
120
|
-
if (node.fields.length === 0) {
|
|
121
|
-
return node.passthrough ? "z.object({}).passthrough()" : "z.object({})";
|
|
122
|
-
}
|
|
123
|
-
const inner = node.fields.map((f) => `${toObjectKey(f.key)}: ${render(f.value, ctx)}`).join(", ");
|
|
124
|
-
return `z.object({ ${inner} })${node.passthrough ? ".passthrough()" : ""}`;
|
|
125
|
-
}
|
|
126
|
-
case "array":
|
|
127
|
-
return `z.array(${render(node.element, ctx)})`;
|
|
128
|
-
case "optional":
|
|
129
|
-
return `${render(node.inner, ctx)}.optional()`;
|
|
130
|
-
case "ref":
|
|
131
|
-
return node.name;
|
|
132
|
-
case "lazyRef":
|
|
133
|
-
return `z.lazy(() => ${node.name})`;
|
|
134
|
-
case "annotated": {
|
|
135
|
-
const comments = node.unmappable.map((n) => `/* @${n}: not translatable to zod (server-only) */`).join(" ");
|
|
136
|
-
return `${render(node.inner, ctx)} ${comments}`;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
var zodAdapter = {
|
|
141
|
-
name: "zod",
|
|
142
|
-
importStatements(usage) {
|
|
143
|
-
return usage.used ? ["import { z } from 'zod';"] : [];
|
|
144
|
-
},
|
|
145
|
-
render,
|
|
146
|
-
inferType(schemaConst) {
|
|
147
|
-
return `z.infer<typeof ${schemaConst}>`;
|
|
148
|
-
},
|
|
149
|
-
renderModule(mod) {
|
|
150
|
-
const ctx = { named: mod.named };
|
|
151
|
-
const namedNestedSchemas = /* @__PURE__ */ new Map();
|
|
152
|
-
for (const [name, node] of mod.named) {
|
|
153
|
-
namedNestedSchemas.set(name, render(node, ctx));
|
|
154
|
-
}
|
|
155
|
-
return { schemaText: render(mod.root, ctx), namedNestedSchemas, warnings: mod.warnings };
|
|
156
|
-
}
|
|
157
|
-
};
|
|
158
|
-
|
|
159
57
|
// src/adapters/registry.ts
|
|
160
58
|
function resolveAdapter(option) {
|
|
161
59
|
if (typeof option !== "string") return option;
|
|
162
|
-
if (option === "zod") return zodAdapter;
|
|
163
60
|
const pkg = `@dudousxd/nestjs-codegen-${option}`;
|
|
164
61
|
const named = `${option}Adapter`;
|
|
165
62
|
throw new ConfigError(
|
|
@@ -209,8 +106,21 @@ If this is intentional, move the file inside your project directory.`
|
|
|
209
106
|
);
|
|
210
107
|
}
|
|
211
108
|
}
|
|
109
|
+
function validateUserConfig(userConfig) {
|
|
110
|
+
if (userConfig.validation == null) {
|
|
111
|
+
throw new ConfigError(
|
|
112
|
+
"validation adapter is required \u2014 install @dudousxd/nestjs-codegen-zod and pass zodAdapter, or use @dudousxd/nestjs-codegen-valibot / -arktype"
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
if (userConfig.pages && typeof userConfig.pages.glob !== "string") {
|
|
116
|
+
throw new ConfigError(
|
|
117
|
+
"Config validation failed: `pages.glob` must be a string when `pages` is set"
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
212
121
|
function applyDefaults(userConfig, cwd) {
|
|
213
|
-
|
|
122
|
+
validateUserConfig(userConfig);
|
|
123
|
+
const outDir = userConfig.codegen?.outDir ? resolveAbsolute(cwd, userConfig.codegen.outDir) : (0, import_node_path.join)(cwd, ".nestjs-codegen");
|
|
214
124
|
const resolvedCwd = userConfig.codegen?.cwd ? resolveAbsolute(cwd, userConfig.codegen.cwd) : cwd;
|
|
215
125
|
let app = null;
|
|
216
126
|
if (userConfig.app) {
|
|
@@ -228,7 +138,8 @@ function applyDefaults(userConfig, cwd) {
|
|
|
228
138
|
}
|
|
229
139
|
return {
|
|
230
140
|
extensions: userConfig.extensions ?? [],
|
|
231
|
-
|
|
141
|
+
// Non-null: validateUserConfig() above throws when `validation` is absent.
|
|
142
|
+
validation: resolveAdapter(userConfig.validation),
|
|
232
143
|
pages: userConfig.pages ? {
|
|
233
144
|
glob: userConfig.pages.glob,
|
|
234
145
|
propsExport: userConfig.pages.propsExport ?? "ComponentProps",
|
|
@@ -282,18 +193,12 @@ Run \`nestjs-codegen init\` to create a starter config.`
|
|
|
282
193
|
`Config file must have a default export. Did you forget \`export default defineConfig({...})\`? (${configPath})`
|
|
283
194
|
);
|
|
284
195
|
}
|
|
285
|
-
if (userConfig.pages && typeof userConfig.pages.glob !== "string") {
|
|
286
|
-
throw new ConfigError(
|
|
287
|
-
`Config validation failed: \`pages.glob\` must be a string when \`pages\` is set (${configPath})`
|
|
288
|
-
);
|
|
289
|
-
}
|
|
290
196
|
return applyDefaults(userConfig, resolvedCwd);
|
|
291
197
|
}
|
|
292
198
|
|
|
293
199
|
// src/generate.ts
|
|
294
200
|
var import_promises9 = require("fs/promises");
|
|
295
|
-
var
|
|
296
|
-
var import_ts_morph3 = require("ts-morph");
|
|
201
|
+
var import_node_path10 = require("path");
|
|
297
202
|
|
|
298
203
|
// src/discovery/pages.ts
|
|
299
204
|
var import_promises2 = require("fs/promises");
|
|
@@ -348,6 +253,7 @@ function extractPropsSource(source, exportName) {
|
|
|
348
253
|
}
|
|
349
254
|
|
|
350
255
|
// src/discovery/shared-props.ts
|
|
256
|
+
var import_node_path3 = require("path");
|
|
351
257
|
var import_ts_morph = require("ts-morph");
|
|
352
258
|
function discoverSharedProps(project, moduleEntry) {
|
|
353
259
|
try {
|
|
@@ -368,6 +274,31 @@ function discoverSharedProps(project, moduleEntry) {
|
|
|
368
274
|
return null;
|
|
369
275
|
}
|
|
370
276
|
}
|
|
277
|
+
function discoverSharedPropsFromConfig(config) {
|
|
278
|
+
if (!config.app?.moduleEntry) return null;
|
|
279
|
+
try {
|
|
280
|
+
const tsconfigPath = config.app.tsconfig ?? (0, import_node_path3.join)(config.codegen.cwd, "tsconfig.json");
|
|
281
|
+
let project;
|
|
282
|
+
try {
|
|
283
|
+
project = new import_ts_morph.Project({
|
|
284
|
+
tsConfigFilePath: tsconfigPath,
|
|
285
|
+
skipAddingFilesFromTsConfig: true,
|
|
286
|
+
skipLoadingLibFiles: true,
|
|
287
|
+
skipFileDependencyResolution: true
|
|
288
|
+
});
|
|
289
|
+
} catch {
|
|
290
|
+
project = new import_ts_morph.Project({
|
|
291
|
+
skipAddingFilesFromTsConfig: true,
|
|
292
|
+
skipLoadingLibFiles: true,
|
|
293
|
+
skipFileDependencyResolution: true,
|
|
294
|
+
compilerOptions: { allowJs: true, strict: false }
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
return discoverSharedProps(project, config.app.moduleEntry);
|
|
298
|
+
} catch {
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
371
302
|
function findForRootCall(sourceFile) {
|
|
372
303
|
const callExpressions = sourceFile.getDescendantsOfKind(import_ts_morph.SyntaxKind.CallExpression);
|
|
373
304
|
for (const call of callExpressions) {
|
|
@@ -387,9 +318,9 @@ function findForRootCall(sourceFile) {
|
|
|
387
318
|
function findShareInitializer(forRootCall) {
|
|
388
319
|
if (!import_ts_morph.Node.isCallExpression(forRootCall)) return null;
|
|
389
320
|
const args = forRootCall.getArguments();
|
|
390
|
-
const
|
|
391
|
-
if (!
|
|
392
|
-
for (const prop of
|
|
321
|
+
const firstArg2 = args[0];
|
|
322
|
+
if (!firstArg2 || !import_ts_morph.Node.isObjectLiteralExpression(firstArg2)) return null;
|
|
323
|
+
for (const prop of firstArg2.getProperties()) {
|
|
393
324
|
if (import_ts_morph.Node.isPropertyAssignment(prop) && prop.getName() === "share") {
|
|
394
325
|
return prop.getInitializer() ?? null;
|
|
395
326
|
}
|
|
@@ -488,9 +419,9 @@ function extractFromReturnTypeAnnotation(typeNode) {
|
|
|
488
419
|
const typeName = typeNode.getTypeName();
|
|
489
420
|
if (import_ts_morph.Node.isIdentifier(typeName) && typeName.getText() === "Promise") {
|
|
490
421
|
const typeArgs = typeNode.getTypeArguments();
|
|
491
|
-
const
|
|
492
|
-
if (
|
|
493
|
-
return extractFromReturnTypeAnnotation(
|
|
422
|
+
const firstArg2 = typeArgs[0];
|
|
423
|
+
if (firstArg2) {
|
|
424
|
+
return extractFromReturnTypeAnnotation(firstArg2);
|
|
494
425
|
}
|
|
495
426
|
return null;
|
|
496
427
|
}
|
|
@@ -596,25 +527,14 @@ function inferExpressionType(node) {
|
|
|
596
527
|
|
|
597
528
|
// src/emit/emit-api.ts
|
|
598
529
|
var import_promises3 = require("fs/promises");
|
|
599
|
-
var
|
|
530
|
+
var import_node_path4 = require("path");
|
|
600
531
|
|
|
601
532
|
// src/extension/registry.ts
|
|
602
533
|
var import_ts_morph2 = require("ts-morph");
|
|
603
534
|
function resolveApiSlots(extensions) {
|
|
604
|
-
let transport;
|
|
605
|
-
let transportOwner;
|
|
606
535
|
let layer;
|
|
607
536
|
let layerOwner;
|
|
608
537
|
for (const ext of extensions) {
|
|
609
|
-
if (ext.apiTransport) {
|
|
610
|
-
if (transport) {
|
|
611
|
-
throw new CodegenError(
|
|
612
|
-
`api transport claimed by both "${transportOwner}" and "${ext.name}" \u2014 only one extension may set apiTransport.`
|
|
613
|
-
);
|
|
614
|
-
}
|
|
615
|
-
transport = ext.apiTransport;
|
|
616
|
-
transportOwner = ext.name;
|
|
617
|
-
}
|
|
618
538
|
if (ext.apiClientLayer) {
|
|
619
539
|
if (layer) {
|
|
620
540
|
throw new CodegenError(
|
|
@@ -626,11 +546,22 @@ function resolveApiSlots(extensions) {
|
|
|
626
546
|
}
|
|
627
547
|
}
|
|
628
548
|
return {
|
|
629
|
-
...transport ? { transport } : {},
|
|
630
549
|
...layer ? { layer } : {}
|
|
631
550
|
};
|
|
632
551
|
}
|
|
633
552
|
var CORE_FILES = /* @__PURE__ */ new Set(["routes.ts", "api.ts", "forms.ts", "index.d.ts", "pages.d.ts"]);
|
|
553
|
+
function mergeExclusive(target, incoming, {
|
|
554
|
+
owner,
|
|
555
|
+
describe
|
|
556
|
+
}) {
|
|
557
|
+
for (const [key, value] of incoming) {
|
|
558
|
+
const prev = target.get(key);
|
|
559
|
+
if (prev !== void 0) {
|
|
560
|
+
throw new CodegenError(describe(key, prev.owner, owner));
|
|
561
|
+
}
|
|
562
|
+
target.set(key, { value, owner });
|
|
563
|
+
}
|
|
564
|
+
}
|
|
634
565
|
function createExtensionContext(config, getRoutes) {
|
|
635
566
|
let project;
|
|
636
567
|
return {
|
|
@@ -675,29 +606,36 @@ async function collectEmittedFiles(extensions, ctx) {
|
|
|
675
606
|
`Extension "${ext.name}" tried to emit the core-owned file "${file.path}". Core files (${[...CORE_FILES].join(", ")}) cannot be produced by extensions.`
|
|
676
607
|
);
|
|
677
608
|
}
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
);
|
|
683
|
-
}
|
|
684
|
-
owners.set(key, ext.name);
|
|
609
|
+
mergeExclusive(owners, [[key, file]], {
|
|
610
|
+
owner: ext.name,
|
|
611
|
+
describe: (_key, prevOwner, owner) => `Output file "${file.path}" is emitted by both "${prevOwner}" and "${owner}". Two extensions cannot write the same file.`
|
|
612
|
+
});
|
|
685
613
|
files.push(file);
|
|
686
614
|
}
|
|
687
615
|
}
|
|
688
616
|
return files;
|
|
689
617
|
}
|
|
690
618
|
|
|
619
|
+
// src/extension/types.ts
|
|
620
|
+
function requestShape(route) {
|
|
621
|
+
const cs = route.contract?.contractSource;
|
|
622
|
+
const isGet = route.method.toUpperCase() === "GET";
|
|
623
|
+
const isQuery = isGet || !!cs?.filterFields?.length;
|
|
624
|
+
const hasBody = !!cs?.bodyRef || cs?.body != null && cs.body !== "never";
|
|
625
|
+
const hasQuery = isGet || !!cs?.queryRef || cs?.query != null && cs.query !== "never";
|
|
626
|
+
return { isGet, isQuery, hasBody, hasQuery };
|
|
627
|
+
}
|
|
628
|
+
|
|
691
629
|
// src/emit/emit-api.ts
|
|
692
630
|
async function emitApi(routes, outDir, opts = {}) {
|
|
693
631
|
await (0, import_promises3.mkdir)(outDir, { recursive: true });
|
|
694
632
|
const content = buildApiFile(routes, outDir, opts);
|
|
695
|
-
await (0, import_promises3.writeFile)((0,
|
|
633
|
+
await (0, import_promises3.writeFile)((0, import_node_path4.join)(outDir, "api.ts"), content, "utf8");
|
|
696
634
|
}
|
|
697
635
|
function splitName(name) {
|
|
698
636
|
return name.split(".");
|
|
699
637
|
}
|
|
700
|
-
function
|
|
638
|
+
function toObjectKey(segment) {
|
|
701
639
|
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(segment)) {
|
|
702
640
|
return segment;
|
|
703
641
|
}
|
|
@@ -790,7 +728,7 @@ function emitFilterQueryType(c) {
|
|
|
790
728
|
}
|
|
791
729
|
function buildResponseType(c, outDir) {
|
|
792
730
|
if (c.controllerRef) {
|
|
793
|
-
let relPath = (0,
|
|
731
|
+
let relPath = (0, import_node_path4.relative)(outDir, c.controllerRef.filePath).replace(/\.ts$/, "");
|
|
794
732
|
if (!relPath.startsWith(".")) relPath = `./${relPath}`;
|
|
795
733
|
return `Awaited<ReturnType<import('${relPath}').${c.controllerRef.className}['${c.controllerRef.methodName}']>>`;
|
|
796
734
|
}
|
|
@@ -804,7 +742,7 @@ function emitRouterTypeBlock(tree, indent, outDir) {
|
|
|
804
742
|
const pad = " ".repeat(indent);
|
|
805
743
|
const lines = [];
|
|
806
744
|
for (const [key, node] of tree) {
|
|
807
|
-
const objKey =
|
|
745
|
+
const objKey = toObjectKey(key);
|
|
808
746
|
if (node.kind === "leaf") {
|
|
809
747
|
const c = node;
|
|
810
748
|
const method = c.method.toUpperCase();
|
|
@@ -830,15 +768,12 @@ function emitRouterTypeBlock(tree, indent, outDir) {
|
|
|
830
768
|
return lines;
|
|
831
769
|
}
|
|
832
770
|
function buildRequestModel(c) {
|
|
833
|
-
const isGet = c.method.toUpperCase() === "GET";
|
|
834
771
|
const m = c.method.toLowerCase();
|
|
835
772
|
const flat = JSON.stringify(c.name);
|
|
836
773
|
const path = JSON.stringify(c.path);
|
|
837
774
|
const TA = buildRouterTypeAccess(c.name);
|
|
838
775
|
const withParams = hasPathParams(c.params);
|
|
839
|
-
const
|
|
840
|
-
const isQuery = isGet || !!c.contractSource.filterFields?.length;
|
|
841
|
-
const hasQuery = isGet || !!c.contractSource.queryRef || c.contractSource.query != null && c.contractSource.query !== "never";
|
|
776
|
+
const { isGet, isQuery, hasBody, hasQuery } = requestShape(c.route);
|
|
842
777
|
const fields = [];
|
|
843
778
|
if (withParams) fields.push(`params: ${TA}['params']`);
|
|
844
779
|
if (hasQuery) fields.push(`query?: ${TA}['query']`);
|
|
@@ -860,7 +795,6 @@ function buildRequestModel(c) {
|
|
|
860
795
|
urlExpr,
|
|
861
796
|
optsExpr,
|
|
862
797
|
responseType: `${TA}['response']`,
|
|
863
|
-
bodyType: `${TA}['body']`,
|
|
864
798
|
queryKeyExpr: `[${flat}, input] as const`
|
|
865
799
|
};
|
|
866
800
|
}
|
|
@@ -908,7 +842,7 @@ function emitApiObjectBlock(tree, indent, p) {
|
|
|
908
842
|
const pad = " ".repeat(indent);
|
|
909
843
|
const lines = [];
|
|
910
844
|
for (const [key, node] of tree) {
|
|
911
|
-
const objKey =
|
|
845
|
+
const objKey = toObjectKey(key);
|
|
912
846
|
if (node.kind === "branch") {
|
|
913
847
|
lines.push(`${pad}${objKey}: {`);
|
|
914
848
|
lines.push(...emitApiObjectBlock(node.children, indent + 2, p));
|
|
@@ -916,30 +850,28 @@ function emitApiObjectBlock(tree, indent, p) {
|
|
|
916
850
|
continue;
|
|
917
851
|
}
|
|
918
852
|
const req = buildRequestModel(node);
|
|
919
|
-
const
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
params: node.params,
|
|
924
|
-
contract: { contractSource: node.contractSource },
|
|
925
|
-
...node.controllerRef ? { controllerRef: node.controllerRef } : {}
|
|
853
|
+
const leaf = {
|
|
854
|
+
route: node.route,
|
|
855
|
+
request: req,
|
|
856
|
+
requestExpr: renderFetcherRequest(req)
|
|
926
857
|
};
|
|
927
|
-
const
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
858
|
+
const owned = /* @__PURE__ */ new Map();
|
|
859
|
+
if (p.layer) {
|
|
860
|
+
mergeExclusive(owned, Object.entries(p.layer.buildMembers(leaf.requestExpr, leaf, p.ctx)), {
|
|
861
|
+
owner: p.layer.name,
|
|
862
|
+
describe: (name, prevOwner, owner) => `api member "${name}" on route "${req.routeName}" is contributed by more than one extension (conflict between "${prevOwner}" and "${owner}").`
|
|
863
|
+
});
|
|
864
|
+
}
|
|
931
865
|
for (const ext of p.memberExts) {
|
|
932
866
|
const extra = ext.apiMembers?.(leaf, p.ctx);
|
|
933
867
|
if (!extra) continue;
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
);
|
|
939
|
-
}
|
|
940
|
-
members[name] = value;
|
|
941
|
-
}
|
|
868
|
+
mergeExclusive(owned, Object.entries(extra), {
|
|
869
|
+
owner: ext.name,
|
|
870
|
+
describe: (name, prevOwner, owner) => `api member "${name}" on route "${req.routeName}" is contributed by more than one extension (conflict between "${prevOwner}" and "${owner}").`
|
|
871
|
+
});
|
|
942
872
|
}
|
|
873
|
+
const members = {};
|
|
874
|
+
for (const [name, { value }] of owned) members[name] = value;
|
|
943
875
|
lines.push(...renderLeaf(pad, objKey, req, leaf.requestExpr, members));
|
|
944
876
|
}
|
|
945
877
|
return lines;
|
|
@@ -948,10 +880,82 @@ function buildRouterTypeAccess(name) {
|
|
|
948
880
|
const segments = splitName(name);
|
|
949
881
|
return `ApiRouter${segments.map((s) => `[${JSON.stringify(s)}]`).join("")}`;
|
|
950
882
|
}
|
|
883
|
+
var RESOLVER_HELPERS = [
|
|
884
|
+
// --- Recursive helper type _RouterAt: walks nested ApiRouter by dot-path ---
|
|
885
|
+
"type _RouterAt<R, P extends string> = P extends `${infer Head}.${infer Tail}`",
|
|
886
|
+
" ? Head extends keyof R ? _RouterAt<R[Head], Tail> : never",
|
|
887
|
+
" : P extends keyof R ? R[P] : never;",
|
|
888
|
+
"",
|
|
889
|
+
// --- ResolveByName: resolve a field from a dot-path name ---
|
|
890
|
+
"type ResolveByName<K extends string, Field extends string> = _RouterAt<ApiRouter, K> extends infer R ? Field extends keyof R ? R[Field] : never : never;",
|
|
891
|
+
"",
|
|
892
|
+
// --- ResolveByPath: scan all leaves for matching method + url ---
|
|
893
|
+
// Flattens ApiRouter recursively and finds the entry whose method === M and url === U.
|
|
894
|
+
"type _LeafValues<T> = T extends { method: string; url: string }",
|
|
895
|
+
" ? T",
|
|
896
|
+
" : T extends object ? _LeafValues<T[keyof T]> : never;",
|
|
897
|
+
"",
|
|
898
|
+
"type ResolveByPath<M extends string, U extends string, Field extends string> = _LeafValues<ApiRouter> extends infer L",
|
|
899
|
+
" ? L extends { method: M; url: U }",
|
|
900
|
+
" ? Field extends keyof L ? L[Field] : never",
|
|
901
|
+
" : never",
|
|
902
|
+
" : never;",
|
|
903
|
+
""
|
|
904
|
+
];
|
|
905
|
+
var ROUTE_NAMESPACE = [
|
|
906
|
+
"export namespace Route {",
|
|
907
|
+
' export type Response<K extends string> = ResolveByName<K, "response">;',
|
|
908
|
+
' export type Body<K extends string> = ResolveByName<K, "body">;',
|
|
909
|
+
' export type Query<K extends string> = ResolveByName<K, "query">;',
|
|
910
|
+
' export type Params<K extends string> = ResolveByName<K, "params">;',
|
|
911
|
+
' export type Error<K extends string> = ResolveByName<K, "error">;',
|
|
912
|
+
' export type FilterFields<K extends string> = ResolveByName<K, "filterFields">;',
|
|
913
|
+
" export type Request<K extends string> = {",
|
|
914
|
+
" body: Body<K>;",
|
|
915
|
+
" query: Query<K>;",
|
|
916
|
+
" params: Params<K>;",
|
|
917
|
+
" };",
|
|
918
|
+
"}",
|
|
919
|
+
""
|
|
920
|
+
];
|
|
921
|
+
var PATH_NAMESPACE = [
|
|
922
|
+
"export namespace Path {",
|
|
923
|
+
' export type Response<M extends string, U extends string> = ResolveByPath<M, U, "response">;',
|
|
924
|
+
' export type Body<M extends string, U extends string> = ResolveByPath<M, U, "body">;',
|
|
925
|
+
' export type Query<M extends string, U extends string> = ResolveByPath<M, U, "query">;',
|
|
926
|
+
' export type Params<M extends string, U extends string> = ResolveByPath<M, U, "params">;',
|
|
927
|
+
' export type Error<M extends string, U extends string> = ResolveByPath<M, U, "error">;',
|
|
928
|
+
' export type FilterFields<M extends string, U extends string> = ResolveByPath<M, U, "filterFields">;',
|
|
929
|
+
"}",
|
|
930
|
+
""
|
|
931
|
+
];
|
|
932
|
+
var EMPTY_ROUTE_NAMESPACE = [
|
|
933
|
+
"export namespace Route {",
|
|
934
|
+
" export type Response<K extends string> = never;",
|
|
935
|
+
" export type Body<K extends string> = never;",
|
|
936
|
+
" export type Query<K extends string> = never;",
|
|
937
|
+
" export type Params<K extends string> = never;",
|
|
938
|
+
" export type Error<K extends string> = never;",
|
|
939
|
+
" export type FilterFields<K extends string> = never;",
|
|
940
|
+
" export type Request<K extends string> = { body: never; query: never; params: never };",
|
|
941
|
+
"}",
|
|
942
|
+
""
|
|
943
|
+
];
|
|
944
|
+
var EMPTY_PATH_NAMESPACE = [
|
|
945
|
+
"export namespace Path {",
|
|
946
|
+
" export type Response<M extends string, U extends string> = never;",
|
|
947
|
+
" export type Body<M extends string, U extends string> = never;",
|
|
948
|
+
" export type Query<M extends string, U extends string> = never;",
|
|
949
|
+
" export type Params<M extends string, U extends string> = never;",
|
|
950
|
+
" export type Error<M extends string, U extends string> = never;",
|
|
951
|
+
" export type FilterFields<M extends string, U extends string> = never;",
|
|
952
|
+
"}",
|
|
953
|
+
""
|
|
954
|
+
];
|
|
951
955
|
function buildApiFile(routes, outDir, opts = {}) {
|
|
952
956
|
const fetcherImportPath = opts.fetcherImportPath;
|
|
953
957
|
const extensions = opts.extensions ?? [];
|
|
954
|
-
const {
|
|
958
|
+
const { layer } = resolveApiSlots(extensions);
|
|
955
959
|
const memberExts = extensions.filter((e) => e.apiMembers);
|
|
956
960
|
const headerExts = extensions.filter((e) => e.apiHeader);
|
|
957
961
|
const contracted = routes.filter((r) => r.contract);
|
|
@@ -996,7 +1000,6 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
996
1000
|
seenImports.add(imp);
|
|
997
1001
|
extImports.push(imp);
|
|
998
1002
|
};
|
|
999
|
-
for (const imp of transport?.imports?.(ctx) ?? []) pushImport(imp);
|
|
1000
1003
|
for (const imp of layer?.imports?.(ctx) ?? []) pushImport(imp);
|
|
1001
1004
|
for (const ext of headerExts) {
|
|
1002
1005
|
for (const imp of ext.apiHeader?.(ctx)?.imports ?? []) pushImport(imp);
|
|
@@ -1012,8 +1015,8 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
1012
1015
|
const emittedNames = /* @__PURE__ */ new Set();
|
|
1013
1016
|
for (const [filePath, names] of importsByFile) {
|
|
1014
1017
|
let relPath;
|
|
1015
|
-
if ((0,
|
|
1016
|
-
relPath = (0,
|
|
1018
|
+
if ((0, import_node_path4.isAbsolute)(filePath)) {
|
|
1019
|
+
relPath = (0, import_node_path4.relative)(outDir, filePath).replace(/\.ts$/, "");
|
|
1017
1020
|
if (!relPath.startsWith(".")) relPath = `./${relPath}`;
|
|
1018
1021
|
} else {
|
|
1019
1022
|
relPath = filePath;
|
|
@@ -1041,27 +1044,8 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
1041
1044
|
lines.push("}");
|
|
1042
1045
|
lines.push("export type Api = ReturnType<typeof createApi>;");
|
|
1043
1046
|
lines.push("");
|
|
1044
|
-
lines.push(
|
|
1045
|
-
lines.push(
|
|
1046
|
-
lines.push(" export type Body<K extends string> = never;");
|
|
1047
|
-
lines.push(" export type Query<K extends string> = never;");
|
|
1048
|
-
lines.push(" export type Params<K extends string> = never;");
|
|
1049
|
-
lines.push(" export type Error<K extends string> = never;");
|
|
1050
|
-
lines.push(" export type FilterFields<K extends string> = never;");
|
|
1051
|
-
lines.push(
|
|
1052
|
-
" export type Request<K extends string> = { body: never; query: never; params: never };"
|
|
1053
|
-
);
|
|
1054
|
-
lines.push("}");
|
|
1055
|
-
lines.push("");
|
|
1056
|
-
lines.push("export namespace Path {");
|
|
1057
|
-
lines.push(" export type Response<M extends string, U extends string> = never;");
|
|
1058
|
-
lines.push(" export type Body<M extends string, U extends string> = never;");
|
|
1059
|
-
lines.push(" export type Query<M extends string, U extends string> = never;");
|
|
1060
|
-
lines.push(" export type Params<M extends string, U extends string> = never;");
|
|
1061
|
-
lines.push(" export type Error<M extends string, U extends string> = never;");
|
|
1062
|
-
lines.push(" export type FilterFields<M extends string, U extends string> = never;");
|
|
1063
|
-
lines.push("}");
|
|
1064
|
-
lines.push("");
|
|
1047
|
+
lines.push(...EMPTY_ROUTE_NAMESPACE);
|
|
1048
|
+
lines.push(...EMPTY_PATH_NAMESPACE);
|
|
1065
1049
|
return lines.join("\n");
|
|
1066
1050
|
}
|
|
1067
1051
|
const tree = /* @__PURE__ */ new Map();
|
|
@@ -1079,7 +1063,8 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
1079
1063
|
path: r.path,
|
|
1080
1064
|
params: r.params,
|
|
1081
1065
|
controllerRef: r.controllerRef,
|
|
1082
|
-
contractSource: c.contractSource
|
|
1066
|
+
contractSource: c.contractSource,
|
|
1067
|
+
route: r
|
|
1083
1068
|
};
|
|
1084
1069
|
insertIntoTree(tree, segments, leaf, name);
|
|
1085
1070
|
}
|
|
@@ -1092,7 +1077,6 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
1092
1077
|
lines.push(" return {");
|
|
1093
1078
|
lines.push(
|
|
1094
1079
|
...emitApiObjectBlock(tree, 4, {
|
|
1095
|
-
...transport ? { transport } : {},
|
|
1096
1080
|
...layer ? { layer } : {},
|
|
1097
1081
|
memberExts,
|
|
1098
1082
|
ctx
|
|
@@ -1103,61 +1087,9 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
1103
1087
|
lines.push("");
|
|
1104
1088
|
lines.push("export type Api = ReturnType<typeof createApi>;");
|
|
1105
1089
|
lines.push("");
|
|
1106
|
-
lines.push(
|
|
1107
|
-
lines.push(
|
|
1108
|
-
lines.push(
|
|
1109
|
-
lines.push("");
|
|
1110
|
-
lines.push(
|
|
1111
|
-
"type ResolveByName<K extends string, Field extends string> = _RouterAt<ApiRouter, K> extends infer R ? Field extends keyof R ? R[Field] : never : never;"
|
|
1112
|
-
);
|
|
1113
|
-
lines.push("");
|
|
1114
|
-
lines.push("type _LeafValues<T> = T extends { method: string; url: string }");
|
|
1115
|
-
lines.push(" ? T");
|
|
1116
|
-
lines.push(" : T extends object ? _LeafValues<T[keyof T]> : never;");
|
|
1117
|
-
lines.push("");
|
|
1118
|
-
lines.push(
|
|
1119
|
-
"type ResolveByPath<M extends string, U extends string, Field extends string> = _LeafValues<ApiRouter> extends infer L"
|
|
1120
|
-
);
|
|
1121
|
-
lines.push(" ? L extends { method: M; url: U }");
|
|
1122
|
-
lines.push(" ? Field extends keyof L ? L[Field] : never");
|
|
1123
|
-
lines.push(" : never");
|
|
1124
|
-
lines.push(" : never;");
|
|
1125
|
-
lines.push("");
|
|
1126
|
-
lines.push("export namespace Route {");
|
|
1127
|
-
lines.push(' export type Response<K extends string> = ResolveByName<K, "response">;');
|
|
1128
|
-
lines.push(' export type Body<K extends string> = ResolveByName<K, "body">;');
|
|
1129
|
-
lines.push(' export type Query<K extends string> = ResolveByName<K, "query">;');
|
|
1130
|
-
lines.push(' export type Params<K extends string> = ResolveByName<K, "params">;');
|
|
1131
|
-
lines.push(' export type Error<K extends string> = ResolveByName<K, "error">;');
|
|
1132
|
-
lines.push(' export type FilterFields<K extends string> = ResolveByName<K, "filterFields">;');
|
|
1133
|
-
lines.push(" export type Request<K extends string> = {");
|
|
1134
|
-
lines.push(" body: Body<K>;");
|
|
1135
|
-
lines.push(" query: Query<K>;");
|
|
1136
|
-
lines.push(" params: Params<K>;");
|
|
1137
|
-
lines.push(" };");
|
|
1138
|
-
lines.push("}");
|
|
1139
|
-
lines.push("");
|
|
1140
|
-
lines.push("export namespace Path {");
|
|
1141
|
-
lines.push(
|
|
1142
|
-
' export type Response<M extends string, U extends string> = ResolveByPath<M, U, "response">;'
|
|
1143
|
-
);
|
|
1144
|
-
lines.push(
|
|
1145
|
-
' export type Body<M extends string, U extends string> = ResolveByPath<M, U, "body">;'
|
|
1146
|
-
);
|
|
1147
|
-
lines.push(
|
|
1148
|
-
' export type Query<M extends string, U extends string> = ResolveByPath<M, U, "query">;'
|
|
1149
|
-
);
|
|
1150
|
-
lines.push(
|
|
1151
|
-
' export type Params<M extends string, U extends string> = ResolveByPath<M, U, "params">;'
|
|
1152
|
-
);
|
|
1153
|
-
lines.push(
|
|
1154
|
-
' export type Error<M extends string, U extends string> = ResolveByPath<M, U, "error">;'
|
|
1155
|
-
);
|
|
1156
|
-
lines.push(
|
|
1157
|
-
' export type FilterFields<M extends string, U extends string> = ResolveByPath<M, U, "filterFields">;'
|
|
1158
|
-
);
|
|
1159
|
-
lines.push("}");
|
|
1160
|
-
lines.push("");
|
|
1090
|
+
lines.push(...RESOLVER_HELPERS);
|
|
1091
|
+
lines.push(...ROUTE_NAMESPACE);
|
|
1092
|
+
lines.push(...PATH_NAMESPACE);
|
|
1161
1093
|
for (const ext of headerExts) {
|
|
1162
1094
|
const statements = ext.apiHeader?.(ctx)?.statements;
|
|
1163
1095
|
if (statements?.length) {
|
|
@@ -1169,7 +1101,7 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
1169
1101
|
|
|
1170
1102
|
// src/emit/emit-cache.ts
|
|
1171
1103
|
var import_promises4 = require("fs/promises");
|
|
1172
|
-
var
|
|
1104
|
+
var import_node_path5 = require("path");
|
|
1173
1105
|
async function emitCache(pages, outDir) {
|
|
1174
1106
|
await (0, import_promises4.mkdir)(outDir, { recursive: true });
|
|
1175
1107
|
const entries = await Promise.all(
|
|
@@ -1183,95 +1115,21 @@ async function emitCache(pages, outDir) {
|
|
|
1183
1115
|
})
|
|
1184
1116
|
);
|
|
1185
1117
|
const cache = { pages: entries };
|
|
1186
|
-
await (0, import_promises4.writeFile)((0,
|
|
1118
|
+
await (0, import_promises4.writeFile)((0, import_node_path5.join)(outDir, "components.json"), `${JSON.stringify(cache, null, 2)}
|
|
1187
1119
|
`, "utf8");
|
|
1188
1120
|
}
|
|
1189
1121
|
|
|
1190
1122
|
// src/emit/emit-forms.ts
|
|
1191
1123
|
var import_promises5 = require("fs/promises");
|
|
1192
|
-
var
|
|
1124
|
+
var import_node_path6 = require("path");
|
|
1193
1125
|
async function emitForms(routes, outDir, config, adapter) {
|
|
1194
1126
|
if (config && config.enabled === false) return false;
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
if (content2 === null) return false;
|
|
1198
|
-
await (0, import_promises5.mkdir)(outDir, { recursive: true });
|
|
1199
|
-
await (0, import_promises5.writeFile)((0, import_node_path5.join)(outDir, "forms.ts"), content2, "utf8");
|
|
1200
|
-
return true;
|
|
1201
|
-
}
|
|
1202
|
-
const entries = collectFormEntries(routes);
|
|
1203
|
-
if (entries.length === 0) return false;
|
|
1127
|
+
const content = buildFormsFileWithAdapter(routes, outDir, adapter, config);
|
|
1128
|
+
if (content === null) return false;
|
|
1204
1129
|
await (0, import_promises5.mkdir)(outDir, { recursive: true });
|
|
1205
|
-
|
|
1206
|
-
await (0, import_promises5.writeFile)((0, import_node_path5.join)(outDir, "forms.ts"), content, "utf8");
|
|
1130
|
+
await (0, import_promises5.writeFile)((0, import_node_path6.join)(outDir, "forms.ts"), content, "utf8");
|
|
1207
1131
|
return true;
|
|
1208
1132
|
}
|
|
1209
|
-
function buildFormsFileWithAdapter(routes, adapter) {
|
|
1210
|
-
const sorted = [...routes].filter((r) => r.contract).sort((a, b) => a.name.localeCompare(b.name));
|
|
1211
|
-
const methodNameCounts = /* @__PURE__ */ new Map();
|
|
1212
|
-
for (const route of sorted) {
|
|
1213
|
-
const cs = route.contract.contractSource;
|
|
1214
|
-
if (!cs.bodySchema && !cs.querySchema) continue;
|
|
1215
|
-
methodNameCounts.set(
|
|
1216
|
-
deriveBaseName(route.name).method,
|
|
1217
|
-
(methodNameCounts.get(deriveBaseName(route.name).method) ?? 0) + 1
|
|
1218
|
-
);
|
|
1219
|
-
}
|
|
1220
|
-
const named = /* @__PURE__ */ new Map();
|
|
1221
|
-
const decls = [];
|
|
1222
|
-
const mapEntries = [];
|
|
1223
|
-
let used = false;
|
|
1224
|
-
for (const route of sorted) {
|
|
1225
|
-
const cs = route.contract.contractSource;
|
|
1226
|
-
const { method, full } = deriveBaseName(route.name);
|
|
1227
|
-
const base = (methodNameCounts.get(method) ?? 0) > 1 ? full : method;
|
|
1228
|
-
const block = [];
|
|
1229
|
-
if (cs.bodyZodText && !cs.bodySchema) {
|
|
1230
|
-
block.push(
|
|
1231
|
-
`// warning: ${route.name} body is a defineContract (zod) schema; not translated to ${adapter.name} \u2014 use the zod adapter.`
|
|
1232
|
-
);
|
|
1233
|
-
}
|
|
1234
|
-
let bodyConst;
|
|
1235
|
-
if (cs.bodySchema) {
|
|
1236
|
-
used = true;
|
|
1237
|
-
const r = adapter.renderModule(cs.bodySchema);
|
|
1238
|
-
for (const [n, t] of r.namedNestedSchemas) named.set(n, t);
|
|
1239
|
-
bodyConst = `${base}BodySchema`;
|
|
1240
|
-
block.push(`export const ${bodyConst} = ${r.schemaText};`);
|
|
1241
|
-
block.push(`export type ${base}Body = ${adapter.inferType(bodyConst)};`);
|
|
1242
|
-
}
|
|
1243
|
-
if (cs.querySchema) {
|
|
1244
|
-
used = true;
|
|
1245
|
-
const r = adapter.renderModule(cs.querySchema);
|
|
1246
|
-
for (const [n, t] of r.namedNestedSchemas) named.set(n, t);
|
|
1247
|
-
const queryConst = `${base}QuerySchema`;
|
|
1248
|
-
block.push(`export const ${queryConst} = ${r.schemaText};`);
|
|
1249
|
-
block.push(`export type ${base}Query = ${adapter.inferType(queryConst)};`);
|
|
1250
|
-
}
|
|
1251
|
-
if (block.length === 0) continue;
|
|
1252
|
-
decls.push(`// ${route.name}`, ...block, "");
|
|
1253
|
-
if (bodyConst) mapEntries.push(` ${JSON.stringify(route.name)}: ${bodyConst},`);
|
|
1254
|
-
}
|
|
1255
|
-
if (!used) return null;
|
|
1256
|
-
const lines = ["// Generated by @dudousxd/nestjs-codegen. Do not edit."];
|
|
1257
|
-
for (const imp of adapter.importStatements({ used: true })) lines.push(imp);
|
|
1258
|
-
lines.push("");
|
|
1259
|
-
if (named.size > 0) {
|
|
1260
|
-
lines.push("// Hoisted nested schemas (shared across endpoints).");
|
|
1261
|
-
for (const [n, t] of named) lines.push(`const ${n} = ${t};`);
|
|
1262
|
-
lines.push("");
|
|
1263
|
-
}
|
|
1264
|
-
lines.push(...decls);
|
|
1265
|
-
lines.push("/** Route name \u2192 body schema map. */");
|
|
1266
|
-
lines.push("export const formSchemas = {");
|
|
1267
|
-
lines.push(...mapEntries);
|
|
1268
|
-
lines.push("} as const;");
|
|
1269
|
-
lines.push("");
|
|
1270
|
-
return lines.join("\n");
|
|
1271
|
-
}
|
|
1272
|
-
function hasSchema(src) {
|
|
1273
|
-
return !!src && (src.ref !== null || src.text !== null);
|
|
1274
|
-
}
|
|
1275
1133
|
function pascal(segment) {
|
|
1276
1134
|
return segment.split(/[^a-zA-Z0-9]+/).filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
1277
1135
|
}
|
|
@@ -1281,124 +1139,16 @@ function deriveBaseName(routeName) {
|
|
|
1281
1139
|
const full = segments.map(pascal).join("");
|
|
1282
1140
|
return { method, full };
|
|
1283
1141
|
}
|
|
1284
|
-
function collectFormEntries(routes) {
|
|
1285
|
-
const sorted = [...routes].filter((r) => r.contract).sort((a, b) => a.name.localeCompare(b.name));
|
|
1286
|
-
const methodNameCounts = /* @__PURE__ */ new Map();
|
|
1287
|
-
const candidates = [];
|
|
1288
|
-
for (const route of sorted) {
|
|
1289
|
-
const cs = route.contract.contractSource;
|
|
1290
|
-
const body = { ref: cs.bodyZodRef ?? null, text: cs.bodyZodText ?? null };
|
|
1291
|
-
const query = { ref: cs.queryZodRef ?? null, text: cs.queryZodText ?? null };
|
|
1292
|
-
if (!hasSchema(body) && !hasSchema(query)) continue;
|
|
1293
|
-
const { method, full } = deriveBaseName(route.name);
|
|
1294
|
-
methodNameCounts.set(method, (methodNameCounts.get(method) ?? 0) + 1);
|
|
1295
|
-
candidates.push({ route, method, full });
|
|
1296
|
-
}
|
|
1297
|
-
const entries = [];
|
|
1298
|
-
for (const { route, method, full } of candidates) {
|
|
1299
|
-
const cs = route.contract.contractSource;
|
|
1300
|
-
const collision = (methodNameCounts.get(method) ?? 0) > 1;
|
|
1301
|
-
const baseName = collision ? full : method;
|
|
1302
|
-
const body = { ref: cs.bodyZodRef ?? null, text: cs.bodyZodText ?? null };
|
|
1303
|
-
const query = { ref: cs.queryZodRef ?? null, text: cs.queryZodText ?? null };
|
|
1304
|
-
entries.push({
|
|
1305
|
-
routeName: route.name,
|
|
1306
|
-
baseName,
|
|
1307
|
-
body: hasSchema(body) ? body : void 0,
|
|
1308
|
-
query: hasSchema(query) ? query : void 0,
|
|
1309
|
-
nestedSchemas: cs.formNestedSchemas ?? null,
|
|
1310
|
-
warnings: cs.formWarnings ?? []
|
|
1311
|
-
});
|
|
1312
|
-
}
|
|
1313
|
-
return entries;
|
|
1314
|
-
}
|
|
1315
1142
|
function relImport(outDir, filePath) {
|
|
1316
|
-
let relPath = (0,
|
|
1143
|
+
let relPath = (0, import_node_path6.relative)(outDir, filePath).replace(/\.ts$/, "");
|
|
1317
1144
|
if (!relPath.startsWith(".")) relPath = `./${relPath}`;
|
|
1318
1145
|
return relPath;
|
|
1319
1146
|
}
|
|
1320
1147
|
function refRootIdentifier(refName) {
|
|
1321
1148
|
return refName.split(".")[0] ?? refName;
|
|
1322
1149
|
}
|
|
1323
|
-
function
|
|
1324
|
-
|
|
1325
|
-
const lines = [
|
|
1326
|
-
"// Generated by @dudousxd/nestjs-codegen. Do not edit.",
|
|
1327
|
-
`import { z } from '${zodImport}';`
|
|
1328
|
-
];
|
|
1329
|
-
const importsByFile = /* @__PURE__ */ new Map();
|
|
1330
|
-
const refAlias = /* @__PURE__ */ new Map();
|
|
1331
|
-
for (const entry of entries) {
|
|
1332
|
-
for (const src of [entry.body, entry.query]) {
|
|
1333
|
-
if (src?.ref && !src.text) {
|
|
1334
|
-
const root = refRootIdentifier(src.ref.name);
|
|
1335
|
-
const set = importsByFile.get(src.ref.filePath) ?? /* @__PURE__ */ new Set();
|
|
1336
|
-
set.add(root);
|
|
1337
|
-
importsByFile.set(src.ref.filePath, set);
|
|
1338
|
-
}
|
|
1339
|
-
}
|
|
1340
|
-
}
|
|
1341
|
-
if (importsByFile.size > 0) {
|
|
1342
|
-
const emitted = /* @__PURE__ */ new Set();
|
|
1343
|
-
for (const [filePath, roots] of [...importsByFile.entries()].sort()) {
|
|
1344
|
-
const relPath = relImport(outDir, filePath);
|
|
1345
|
-
const specifiers = [];
|
|
1346
|
-
for (const root of [...roots].sort()) {
|
|
1347
|
-
if (emitted.has(root)) {
|
|
1348
|
-
const alias = `${root}_${emitted.size}`;
|
|
1349
|
-
specifiers.push(`${root} as ${alias}`);
|
|
1350
|
-
emitted.add(alias);
|
|
1351
|
-
refAlias.set(`${filePath}\0${root}`, alias);
|
|
1352
|
-
} else {
|
|
1353
|
-
specifiers.push(root);
|
|
1354
|
-
emitted.add(root);
|
|
1355
|
-
refAlias.set(`${filePath}\0${root}`, root);
|
|
1356
|
-
}
|
|
1357
|
-
}
|
|
1358
|
-
lines.push(`import { ${specifiers.join(", ")} } from '${relPath}';`);
|
|
1359
|
-
}
|
|
1360
|
-
}
|
|
1361
|
-
lines.push("");
|
|
1362
|
-
const { globalSchemas, renamesByEntry } = planNestedSchemas(entries);
|
|
1363
|
-
if (globalSchemas.size > 0) {
|
|
1364
|
-
lines.push("// Hoisted nested schemas (shared across endpoints).");
|
|
1365
|
-
for (const [name, text] of globalSchemas) {
|
|
1366
|
-
lines.push(`const ${name} = ${text};`);
|
|
1367
|
-
}
|
|
1368
|
-
lines.push("");
|
|
1369
|
-
}
|
|
1370
|
-
const mapEntries = [];
|
|
1371
|
-
for (const entry of entries) {
|
|
1372
|
-
lines.push(`// ${entry.routeName}`);
|
|
1373
|
-
if (entry.warnings && entry.warnings.length > 0) {
|
|
1374
|
-
for (const w of entry.warnings) {
|
|
1375
|
-
lines.push(`// warning: ${w}`);
|
|
1376
|
-
}
|
|
1377
|
-
}
|
|
1378
|
-
const rename = renamesByEntry.get(entry) ?? null;
|
|
1379
|
-
if (entry.body) {
|
|
1380
|
-
const schemaName = `${entry.baseName}BodySchema`;
|
|
1381
|
-
const typeName = `${entry.baseName}Body`;
|
|
1382
|
-
const text = applyRenames(renderSchema(entry.body, outDir, refAlias), rename);
|
|
1383
|
-
lines.push(`export const ${schemaName} = ${text};`);
|
|
1384
|
-
lines.push(`export type ${typeName} = z.infer<typeof ${schemaName}>;`);
|
|
1385
|
-
mapEntries.push(` ${JSON.stringify(entry.routeName)}: ${schemaName},`);
|
|
1386
|
-
}
|
|
1387
|
-
if (entry.query) {
|
|
1388
|
-
const schemaName = `${entry.baseName}QuerySchema`;
|
|
1389
|
-
const typeName = `${entry.baseName}Query`;
|
|
1390
|
-
const text = applyRenames(renderSchema(entry.query, outDir, refAlias), rename);
|
|
1391
|
-
lines.push(`export const ${schemaName} = ${text};`);
|
|
1392
|
-
lines.push(`export type ${typeName} = z.infer<typeof ${schemaName}>;`);
|
|
1393
|
-
}
|
|
1394
|
-
lines.push("");
|
|
1395
|
-
}
|
|
1396
|
-
lines.push("/** Route name \u2192 body schema map. */");
|
|
1397
|
-
lines.push("export const formSchemas = {");
|
|
1398
|
-
lines.push(...mapEntries);
|
|
1399
|
-
lines.push("} as const;");
|
|
1400
|
-
lines.push("");
|
|
1401
|
-
return lines.join("\n");
|
|
1150
|
+
function hasSource(src) {
|
|
1151
|
+
return !!(src.schema || src.zodText || src.zodRef);
|
|
1402
1152
|
}
|
|
1403
1153
|
function applyRenames(text, renames) {
|
|
1404
1154
|
if (!renames || renames.size === 0) return text;
|
|
@@ -1464,44 +1214,206 @@ function planNestedSchemas(entries) {
|
|
|
1464
1214
|
}
|
|
1465
1215
|
return { globalSchemas, renamesByEntry };
|
|
1466
1216
|
}
|
|
1467
|
-
function
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1217
|
+
function buildFormsFileWithAdapter(routes, outDir, adapter, config) {
|
|
1218
|
+
const acceptsRawZod = adapter.acceptsRawZodSource === true;
|
|
1219
|
+
const sorted = [...routes].filter((r) => r.contract).sort((a, b) => a.name.localeCompare(b.name));
|
|
1220
|
+
const methodNameCounts = /* @__PURE__ */ new Map();
|
|
1221
|
+
const candidates = [];
|
|
1222
|
+
for (const route of sorted) {
|
|
1223
|
+
const cs = route.contract.contractSource;
|
|
1224
|
+
const body = {
|
|
1225
|
+
schema: cs.bodySchema ?? null,
|
|
1226
|
+
zodText: cs.bodyZodText ?? null,
|
|
1227
|
+
zodRef: cs.bodyZodRef ?? null
|
|
1228
|
+
};
|
|
1229
|
+
const query = {
|
|
1230
|
+
schema: cs.querySchema ?? null,
|
|
1231
|
+
zodText: cs.queryZodText ?? null,
|
|
1232
|
+
zodRef: cs.queryZodRef ?? null
|
|
1233
|
+
};
|
|
1234
|
+
if (!hasSource(body) && !hasSource(query)) continue;
|
|
1235
|
+
const { method, full } = deriveBaseName(route.name);
|
|
1236
|
+
methodNameCounts.set(method, (methodNameCounts.get(method) ?? 0) + 1);
|
|
1237
|
+
candidates.push({
|
|
1238
|
+
routeName: route.name,
|
|
1239
|
+
baseName: full,
|
|
1240
|
+
// resolved below
|
|
1241
|
+
body: hasSource(body) ? body : void 0,
|
|
1242
|
+
query: hasSource(query) ? query : void 0,
|
|
1243
|
+
nestedSchemas: cs.formNestedSchemas ?? null,
|
|
1244
|
+
warnings: cs.formWarnings ?? []
|
|
1245
|
+
});
|
|
1486
1246
|
}
|
|
1487
|
-
|
|
1488
|
-
|
|
1247
|
+
const entries = candidates.map((c) => {
|
|
1248
|
+
const { method, full } = deriveBaseName(c.routeName);
|
|
1249
|
+
const collision = (methodNameCounts.get(method) ?? 0) > 1;
|
|
1250
|
+
return { ...c, baseName: collision ? full : method };
|
|
1251
|
+
});
|
|
1252
|
+
if (entries.length === 0) return null;
|
|
1253
|
+
const importsByFile = /* @__PURE__ */ new Map();
|
|
1254
|
+
const refAlias = /* @__PURE__ */ new Map();
|
|
1255
|
+
for (const entry of entries) {
|
|
1256
|
+
for (const src of [entry.body, entry.query]) {
|
|
1257
|
+
if (src?.zodRef && !src.zodText && !src.schema) {
|
|
1258
|
+
const root = refRootIdentifier(src.zodRef.name);
|
|
1259
|
+
const set = importsByFile.get(src.zodRef.filePath) ?? /* @__PURE__ */ new Set();
|
|
1260
|
+
set.add(root);
|
|
1261
|
+
importsByFile.set(src.zodRef.filePath, set);
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1489
1264
|
}
|
|
1490
|
-
const
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1265
|
+
const importLines = [];
|
|
1266
|
+
if (importsByFile.size > 0) {
|
|
1267
|
+
const emitted = /* @__PURE__ */ new Set();
|
|
1268
|
+
for (const [filePath, roots] of [...importsByFile.entries()].sort()) {
|
|
1269
|
+
const relPath = relImport(outDir, filePath);
|
|
1270
|
+
const specifiers = [];
|
|
1271
|
+
for (const root of [...roots].sort()) {
|
|
1272
|
+
if (emitted.has(root)) {
|
|
1273
|
+
const alias = `${root}_${emitted.size}`;
|
|
1274
|
+
specifiers.push(`${root} as ${alias}`);
|
|
1275
|
+
emitted.add(alias);
|
|
1276
|
+
refAlias.set(`${filePath}\0${root}`, alias);
|
|
1277
|
+
} else {
|
|
1278
|
+
specifiers.push(root);
|
|
1279
|
+
emitted.add(root);
|
|
1280
|
+
refAlias.set(`${filePath}\0${root}`, root);
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
importLines.push(`import { ${specifiers.join(", ")} } from '${relPath}';`);
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
const { globalSchemas, renamesByEntry } = planNestedSchemas(entries);
|
|
1287
|
+
const irNamed = /* @__PURE__ */ new Map();
|
|
1288
|
+
const irTypeAliases = /* @__PURE__ */ new Map();
|
|
1289
|
+
const irAnnotations = /* @__PURE__ */ new Map();
|
|
1290
|
+
const decls = [];
|
|
1291
|
+
const mapEntries = [];
|
|
1292
|
+
let used = false;
|
|
1293
|
+
const renderSource = (src, rename) => {
|
|
1294
|
+
if (src.schema) {
|
|
1295
|
+
const r = adapter.renderModule(src.schema);
|
|
1296
|
+
for (const [n, t] of r.namedNestedSchemas) irNamed.set(n, t);
|
|
1297
|
+
if (r.namedTypeAliases) for (const [n, t] of r.namedTypeAliases) irTypeAliases.set(n, t);
|
|
1298
|
+
if (r.namedAnnotations) for (const [n, a] of r.namedAnnotations) irAnnotations.set(n, a);
|
|
1299
|
+
return { text: r.schemaText };
|
|
1300
|
+
}
|
|
1301
|
+
if (src.zodText) {
|
|
1302
|
+
if (!acceptsRawZod) {
|
|
1303
|
+
return {
|
|
1304
|
+
text: "",
|
|
1305
|
+
warn: `is a defineContract (zod) schema; not translated to ${adapter.name} \u2014 use the zod adapter.`
|
|
1306
|
+
};
|
|
1307
|
+
}
|
|
1308
|
+
return { text: applyRenames(src.zodText, rename) };
|
|
1309
|
+
}
|
|
1310
|
+
if (src.zodRef) {
|
|
1311
|
+
if (!acceptsRawZod) {
|
|
1312
|
+
return {
|
|
1313
|
+
text: "",
|
|
1314
|
+
warn: `is a defineContract (zod) schema; not translated to ${adapter.name} \u2014 use the zod adapter.`
|
|
1315
|
+
};
|
|
1316
|
+
}
|
|
1317
|
+
const root = refRootIdentifier(src.zodRef.name);
|
|
1318
|
+
const alias = refAlias.get(`${src.zodRef.filePath}\0${root}`) ?? root;
|
|
1319
|
+
const member = src.zodRef.name.slice(root.length);
|
|
1320
|
+
return { text: `${alias}${member}` };
|
|
1321
|
+
}
|
|
1322
|
+
return null;
|
|
1323
|
+
};
|
|
1324
|
+
for (const entry of entries) {
|
|
1325
|
+
const block = [];
|
|
1326
|
+
const rename = renamesByEntry.get(entry) ?? null;
|
|
1327
|
+
let bodyConst;
|
|
1328
|
+
if (entry.warnings && entry.warnings.length > 0) {
|
|
1329
|
+
for (const w of entry.warnings) block.push(`// warning: ${w}`);
|
|
1330
|
+
}
|
|
1331
|
+
if (entry.body) {
|
|
1332
|
+
const rendered = renderSource(entry.body, rename);
|
|
1333
|
+
if (rendered?.warn) {
|
|
1334
|
+
block.push(`// warning: ${entry.routeName} body ${rendered.warn}`);
|
|
1335
|
+
} else if (rendered) {
|
|
1336
|
+
used = true;
|
|
1337
|
+
bodyConst = `${entry.baseName}BodySchema`;
|
|
1338
|
+
block.push(`export const ${bodyConst} = ${rendered.text};`);
|
|
1339
|
+
block.push(`export type ${entry.baseName}Body = ${adapter.inferType(bodyConst)};`);
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
if (entry.query) {
|
|
1343
|
+
const rendered = renderSource(entry.query, rename);
|
|
1344
|
+
if (rendered?.warn) {
|
|
1345
|
+
block.push(`// warning: ${entry.routeName} query ${rendered.warn}`);
|
|
1346
|
+
} else if (rendered) {
|
|
1347
|
+
used = true;
|
|
1348
|
+
const queryConst = `${entry.baseName}QuerySchema`;
|
|
1349
|
+
block.push(`export const ${queryConst} = ${rendered.text};`);
|
|
1350
|
+
block.push(`export type ${entry.baseName}Query = ${adapter.inferType(queryConst)};`);
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
if (block.length === 0) continue;
|
|
1354
|
+
decls.push(`// ${entry.routeName}`, ...block, "");
|
|
1355
|
+
if (bodyConst) mapEntries.push(` ${JSON.stringify(entry.routeName)}: ${bodyConst},`);
|
|
1356
|
+
}
|
|
1357
|
+
if (!used) return null;
|
|
1358
|
+
const lines = ["// Generated by @dudousxd/nestjs-codegen. Do not edit."];
|
|
1359
|
+
if (acceptsRawZod) {
|
|
1360
|
+
const zodImport = config?.zodImport ?? "zod";
|
|
1361
|
+
lines.push(`import { z } from '${zodImport}';`);
|
|
1362
|
+
} else {
|
|
1363
|
+
for (const imp of adapter.importStatements({ used: true })) lines.push(imp);
|
|
1364
|
+
}
|
|
1365
|
+
lines.push(...importLines);
|
|
1366
|
+
lines.push("");
|
|
1367
|
+
const allNested = /* @__PURE__ */ new Map();
|
|
1368
|
+
for (const [n, t] of globalSchemas) allNested.set(n, t);
|
|
1369
|
+
for (const [n, t] of irNamed) if (!allNested.has(n)) allNested.set(n, t);
|
|
1370
|
+
if (allNested.size > 0) {
|
|
1371
|
+
lines.push("// Hoisted nested schemas (shared across endpoints).");
|
|
1372
|
+
for (const [n, alias] of irTypeAliases) {
|
|
1373
|
+
if (allNested.has(n)) lines.push(`${alias};`);
|
|
1374
|
+
}
|
|
1375
|
+
for (const [n, t] of allNested) {
|
|
1376
|
+
const annotation = irAnnotations.get(n);
|
|
1377
|
+
lines.push(`const ${n}${annotation ? `: ${annotation}` : ""} = ${t};`);
|
|
1378
|
+
}
|
|
1379
|
+
lines.push("");
|
|
1380
|
+
}
|
|
1381
|
+
lines.push(...decls);
|
|
1382
|
+
lines.push("/** Route name \u2192 body schema map. */");
|
|
1383
|
+
lines.push("export const formSchemas = {");
|
|
1384
|
+
lines.push(...mapEntries);
|
|
1385
|
+
lines.push("} as const;");
|
|
1386
|
+
lines.push("");
|
|
1387
|
+
return lines.join("\n");
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
// src/emit/emit-index.ts
|
|
1391
|
+
var import_promises6 = require("fs/promises");
|
|
1392
|
+
var import_node_path7 = require("path");
|
|
1393
|
+
async function emitIndex(outDir, hasContracts = false, hasForms = false) {
|
|
1394
|
+
await (0, import_promises6.mkdir)(outDir, { recursive: true });
|
|
1395
|
+
const exports2 = ["export * from './pages.js';", "export * from './routes.js';"];
|
|
1396
|
+
if (hasContracts) {
|
|
1397
|
+
exports2.push("export * from './api.js';");
|
|
1398
|
+
}
|
|
1399
|
+
if (hasForms) {
|
|
1400
|
+
exports2.push("export * from './forms.js';");
|
|
1401
|
+
}
|
|
1402
|
+
const content = ["// Generated by @dudousxd/nestjs-codegen. Do not edit.", ...exports2, ""].join(
|
|
1403
|
+
"\n"
|
|
1404
|
+
);
|
|
1405
|
+
await (0, import_promises6.writeFile)((0, import_node_path7.join)(outDir, "index.d.ts"), content, "utf8");
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
// src/emit/emit-pages.ts
|
|
1409
|
+
var import_promises7 = require("fs/promises");
|
|
1410
|
+
var import_node_path8 = require("path");
|
|
1411
|
+
async function emitPages(pages, outDir, _options = {}) {
|
|
1412
|
+
await (0, import_promises7.mkdir)(outDir, { recursive: true });
|
|
1413
|
+
const pageNameUnion = pages.length > 0 ? pages.map((p) => JSON.stringify(p.name)).join(" | ") : "never";
|
|
1414
|
+
const augBody = pages.map((p) => {
|
|
1415
|
+
const key = needsQuotes(p.name) ? JSON.stringify(p.name) : p.name;
|
|
1416
|
+
const valueType = buildAugmentationType(p, outDir);
|
|
1505
1417
|
return ` ${key}: ${valueType};`;
|
|
1506
1418
|
}).join("\n");
|
|
1507
1419
|
const propsHelper = "\nexport type InertiaProps<K extends InertiaPageName> = import('@dudousxd/nestjs-inertia').InertiaPages[K];\n";
|
|
@@ -1516,7 +1428,7 @@ ${augBody}
|
|
|
1516
1428
|
}
|
|
1517
1429
|
${sharedPropsBlock}}
|
|
1518
1430
|
`;
|
|
1519
|
-
await (0, import_promises7.writeFile)((0,
|
|
1431
|
+
await (0, import_promises7.writeFile)((0, import_node_path8.join)(outDir, "pages.d.ts"), content, "utf8");
|
|
1520
1432
|
}
|
|
1521
1433
|
function buildSharedPropsBlock(sharedProps) {
|
|
1522
1434
|
if (!sharedProps) return "";
|
|
@@ -1535,7 +1447,7 @@ ${propsBody}
|
|
|
1535
1447
|
`;
|
|
1536
1448
|
}
|
|
1537
1449
|
function buildAugmentationType(page, outDir) {
|
|
1538
|
-
let importPath = (0,
|
|
1450
|
+
let importPath = (0, import_node_path8.relative)(outDir, page.absolutePath).replace(/\.(tsx?|vue|svelte)$/, "");
|
|
1539
1451
|
if (!importPath.startsWith(".")) {
|
|
1540
1452
|
importPath = `./${importPath}`;
|
|
1541
1453
|
}
|
|
@@ -1547,11 +1459,11 @@ function needsQuotes(name) {
|
|
|
1547
1459
|
|
|
1548
1460
|
// src/emit/emit-routes.ts
|
|
1549
1461
|
var import_promises8 = require("fs/promises");
|
|
1550
|
-
var
|
|
1462
|
+
var import_node_path9 = require("path");
|
|
1551
1463
|
async function emitRoutes(routes, outDir) {
|
|
1552
1464
|
await (0, import_promises8.mkdir)(outDir, { recursive: true });
|
|
1553
1465
|
const content = buildRoutesFile(routes);
|
|
1554
|
-
await (0, import_promises8.writeFile)((0,
|
|
1466
|
+
await (0, import_promises8.writeFile)((0, import_node_path9.join)(outDir, "routes.ts"), content, "utf8");
|
|
1555
1467
|
}
|
|
1556
1468
|
function buildRoutesFile(routes) {
|
|
1557
1469
|
if (routes.length === 0) {
|
|
@@ -1679,30 +1591,7 @@ async function generate(config, inputRoutes = []) {
|
|
|
1679
1591
|
propsExport: pagesConfig.propsExport,
|
|
1680
1592
|
componentNameStrategy: pagesConfig.componentNameStrategy
|
|
1681
1593
|
});
|
|
1682
|
-
|
|
1683
|
-
if (config.app?.moduleEntry) {
|
|
1684
|
-
try {
|
|
1685
|
-
const tsconfigPath = config.app.tsconfig ?? (0, import_node_path9.join)(config.codegen.cwd, "tsconfig.json");
|
|
1686
|
-
let project;
|
|
1687
|
-
try {
|
|
1688
|
-
project = new import_ts_morph3.Project({
|
|
1689
|
-
tsConfigFilePath: tsconfigPath,
|
|
1690
|
-
skipAddingFilesFromTsConfig: true,
|
|
1691
|
-
skipLoadingLibFiles: true,
|
|
1692
|
-
skipFileDependencyResolution: true
|
|
1693
|
-
});
|
|
1694
|
-
} catch {
|
|
1695
|
-
project = new import_ts_morph3.Project({
|
|
1696
|
-
skipAddingFilesFromTsConfig: true,
|
|
1697
|
-
skipLoadingLibFiles: true,
|
|
1698
|
-
skipFileDependencyResolution: true,
|
|
1699
|
-
compilerOptions: { allowJs: true, strict: false }
|
|
1700
|
-
});
|
|
1701
|
-
}
|
|
1702
|
-
sharedProps = discoverSharedProps(project, config.app.moduleEntry);
|
|
1703
|
-
} catch {
|
|
1704
|
-
}
|
|
1705
|
-
}
|
|
1594
|
+
const sharedProps = discoverSharedPropsFromConfig(config);
|
|
1706
1595
|
await emitPages(pages, config.codegen.outDir, {
|
|
1707
1596
|
propsExport: pagesConfig.propsExport,
|
|
1708
1597
|
sharedProps
|
|
@@ -1726,8 +1615,8 @@ async function generate(config, inputRoutes = []) {
|
|
|
1726
1615
|
if (extensions.length > 0) {
|
|
1727
1616
|
const extraFiles = await collectEmittedFiles(extensions, ctx);
|
|
1728
1617
|
for (const file of extraFiles) {
|
|
1729
|
-
const dest = (0,
|
|
1730
|
-
await (0, import_promises9.mkdir)((0,
|
|
1618
|
+
const dest = (0, import_node_path10.join)(config.codegen.outDir, file.path);
|
|
1619
|
+
await (0, import_promises9.mkdir)((0, import_node_path10.dirname)(dest), { recursive: true });
|
|
1731
1620
|
await (0, import_promises9.writeFile)(dest, file.contents, "utf8");
|
|
1732
1621
|
}
|
|
1733
1622
|
}
|
|
@@ -1735,35 +1624,31 @@ async function generate(config, inputRoutes = []) {
|
|
|
1735
1624
|
|
|
1736
1625
|
// src/watch/watcher.ts
|
|
1737
1626
|
var import_promises12 = require("fs/promises");
|
|
1738
|
-
var
|
|
1627
|
+
var import_node_path14 = require("path");
|
|
1739
1628
|
var import_chokidar = __toESM(require("chokidar"), 1);
|
|
1740
1629
|
|
|
1741
1630
|
// src/discovery/contracts-fast.ts
|
|
1742
|
-
var
|
|
1631
|
+
var import_node_path12 = require("path");
|
|
1743
1632
|
var import_fast_glob2 = __toESM(require("fast-glob"), 1);
|
|
1744
1633
|
var import_ts_morph9 = require("ts-morph");
|
|
1745
1634
|
|
|
1635
|
+
// src/discovery/dto-type-resolver.ts
|
|
1636
|
+
var import_ts_morph7 = require("ts-morph");
|
|
1637
|
+
|
|
1746
1638
|
// src/discovery/dto-to-ir.ts
|
|
1747
|
-
var
|
|
1639
|
+
var import_ts_morph4 = require("ts-morph");
|
|
1748
1640
|
|
|
1749
1641
|
// src/discovery/type-ref-resolution.ts
|
|
1750
1642
|
var import_node_fs = require("fs");
|
|
1751
|
-
var
|
|
1752
|
-
var
|
|
1753
|
-
var
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
return prev;
|
|
1758
|
-
}
|
|
1759
|
-
function restoreDiscoveryContext(ctx) {
|
|
1760
|
-
_ctx = ctx;
|
|
1761
|
-
}
|
|
1762
|
-
function _projectRoot() {
|
|
1763
|
-
return _ctx.projectRoot;
|
|
1643
|
+
var import_node_path11 = require("path");
|
|
1644
|
+
var import_ts_morph3 = require("ts-morph");
|
|
1645
|
+
var _EMPTY_CTX = { projectRoot: "", tsconfigPaths: null };
|
|
1646
|
+
var _ctxByProject = /* @__PURE__ */ new WeakMap();
|
|
1647
|
+
function setDiscoveryContext(project, ctx) {
|
|
1648
|
+
_ctxByProject.set(project, ctx);
|
|
1764
1649
|
}
|
|
1765
|
-
function
|
|
1766
|
-
return
|
|
1650
|
+
function _ctxFor(project) {
|
|
1651
|
+
return _ctxByProject.get(project) ?? _EMPTY_CTX;
|
|
1767
1652
|
}
|
|
1768
1653
|
var _debug = process.env.NESTJS_INERTIA_DEBUG === "1";
|
|
1769
1654
|
function dbg(...args) {
|
|
@@ -1805,18 +1690,19 @@ function findTypeInFile(name, file) {
|
|
|
1805
1690
|
}
|
|
1806
1691
|
return null;
|
|
1807
1692
|
}
|
|
1808
|
-
function resolveModuleSpecifier(moduleSpecifier, sourceFile,
|
|
1693
|
+
function resolveModuleSpecifier(moduleSpecifier, sourceFile, project) {
|
|
1809
1694
|
if (moduleSpecifier.startsWith(".")) {
|
|
1810
|
-
const dir = (0,
|
|
1695
|
+
const dir = (0, import_node_path11.dirname)(sourceFile.getFilePath());
|
|
1811
1696
|
const noExt = moduleSpecifier.replace(/\.(js|ts)$/, "");
|
|
1812
1697
|
return [
|
|
1813
|
-
(0,
|
|
1814
|
-
(0,
|
|
1815
|
-
(0,
|
|
1698
|
+
(0, import_node_path11.resolve)(dir, `${noExt}.ts`),
|
|
1699
|
+
(0, import_node_path11.resolve)(dir, `${moduleSpecifier}.ts`),
|
|
1700
|
+
(0, import_node_path11.resolve)(dir, moduleSpecifier, "index.ts")
|
|
1816
1701
|
];
|
|
1817
1702
|
}
|
|
1818
|
-
const
|
|
1819
|
-
const
|
|
1703
|
+
const ctx = _ctxFor(project);
|
|
1704
|
+
const baseUrl = ctx.projectRoot;
|
|
1705
|
+
const tsconfigPaths = ctx.tsconfigPaths;
|
|
1820
1706
|
dbg(
|
|
1821
1707
|
"resolveModuleSpecifier",
|
|
1822
1708
|
moduleSpecifier,
|
|
@@ -1832,8 +1718,8 @@ function resolveModuleSpecifier(moduleSpecifier, sourceFile, _project) {
|
|
|
1832
1718
|
const rest = moduleSpecifier.slice(prefix.length);
|
|
1833
1719
|
const candidates = [];
|
|
1834
1720
|
for (const mapping of mappings) {
|
|
1835
|
-
const resolved = (0,
|
|
1836
|
-
candidates.push(`${resolved}.ts`, (0,
|
|
1721
|
+
const resolved = (0, import_node_path11.resolve)(baseUrl, mapping.replace("*", rest));
|
|
1722
|
+
candidates.push(`${resolved}.ts`, (0, import_node_path11.resolve)(resolved, "index.ts"));
|
|
1837
1723
|
}
|
|
1838
1724
|
dbg(" resolved candidates:", candidates);
|
|
1839
1725
|
return candidates;
|
|
@@ -1933,13 +1819,13 @@ function resolveTypeRef(nodeOrName, sourceFile, project, opts) {
|
|
|
1933
1819
|
name = nodeOrName;
|
|
1934
1820
|
} else {
|
|
1935
1821
|
const typeNode = nodeOrName;
|
|
1936
|
-
if (opts.unwrapContainers &&
|
|
1822
|
+
if (opts.unwrapContainers && import_ts_morph3.Node.isArrayTypeNode(typeNode)) {
|
|
1937
1823
|
const inner = resolveTypeRef(typeNode.getElementTypeNode(), sourceFile, project, opts);
|
|
1938
1824
|
return inner ? { ...inner, isArray: true } : null;
|
|
1939
1825
|
}
|
|
1940
|
-
if (!
|
|
1826
|
+
if (!import_ts_morph3.Node.isTypeReference(typeNode)) return null;
|
|
1941
1827
|
const typeName = typeNode.getTypeName();
|
|
1942
|
-
const refName =
|
|
1828
|
+
const refName = import_ts_morph3.Node.isIdentifier(typeName) ? typeName.getText() : null;
|
|
1943
1829
|
if (!refName) return null;
|
|
1944
1830
|
if (opts.unwrapContainers && refName === "Promise") {
|
|
1945
1831
|
const first = typeNode.getTypeArguments()[0];
|
|
@@ -1962,7 +1848,7 @@ function resolveTypeRef(nodeOrName, sourceFile, project, opts) {
|
|
|
1962
1848
|
if (!namedImport) continue;
|
|
1963
1849
|
const moduleSpecifier = importDecl.getModuleSpecifierValue();
|
|
1964
1850
|
if (opts.allowBareSpecifier && !moduleSpecifier.startsWith(".") && !moduleSpecifier.startsWith("/")) {
|
|
1965
|
-
const tsconfigPaths =
|
|
1851
|
+
const tsconfigPaths = _ctxFor(project).tsconfigPaths;
|
|
1966
1852
|
const isAlias = tsconfigPaths != null && Object.keys(tsconfigPaths).some((p) => {
|
|
1967
1853
|
const prefix = p.replace("*", "");
|
|
1968
1854
|
return moduleSpecifier.startsWith(prefix);
|
|
@@ -2031,10 +1917,7 @@ function extractSchemaFromDto(classDecl, sourceFile, project) {
|
|
|
2031
1917
|
depth: 0
|
|
2032
1918
|
};
|
|
2033
1919
|
const root = buildObject(classDecl, sourceFile, ctx);
|
|
2034
|
-
|
|
2035
|
-
ctx.named.set(schemaName, { kind: "unknown", note: "recursive type \u2014 not expanded" });
|
|
2036
|
-
}
|
|
2037
|
-
return { root, named: ctx.named, warnings: ctx.warnings };
|
|
1920
|
+
return { root, named: ctx.named, warnings: ctx.warnings, recursive: ctx.recursiveSchemas };
|
|
2038
1921
|
}
|
|
2039
1922
|
function buildObject(classDecl, classFile, ctx) {
|
|
2040
1923
|
const props = classDecl.getProperties();
|
|
@@ -2054,7 +1937,7 @@ function buildProperty(prop, classFile, ctx) {
|
|
|
2054
1937
|
const dec = (n) => decorators.get(n);
|
|
2055
1938
|
const typeNode = prop.getTypeNode();
|
|
2056
1939
|
const typeText = typeNode?.getText() ?? "unknown";
|
|
2057
|
-
const isArrayType = !!typeNode &&
|
|
1940
|
+
const isArrayType = !!typeNode && import_ts_morph4.Node.isArrayTypeNode(typeNode);
|
|
2058
1941
|
const typeRefName = resolveTypeFactoryName(dec("Type"));
|
|
2059
1942
|
if (has("ValidateNested") || typeRefName) {
|
|
2060
1943
|
const childName = typeRefName ?? singularClassName(typeText);
|
|
@@ -2185,18 +2068,27 @@ function baseFromType(typeText, isArrayType) {
|
|
|
2185
2068
|
}
|
|
2186
2069
|
}
|
|
2187
2070
|
function buildNestedReference(className, fromFile, ctx) {
|
|
2188
|
-
if (ctx.visiting.has(className)
|
|
2071
|
+
if (ctx.visiting.has(className)) {
|
|
2189
2072
|
const reserved = ctx.emittedClasses.get(className) ?? aliasFor(className, ctx);
|
|
2190
2073
|
ctx.emittedClasses.set(className, reserved);
|
|
2191
2074
|
ctx.recursiveSchemas.add(reserved);
|
|
2192
2075
|
if (!ctx.warnedDecorators.has(`recursive:${reserved}`)) {
|
|
2193
2076
|
ctx.warnedDecorators.add(`recursive:${reserved}`);
|
|
2194
|
-
const msg = `${className} is a recursive type
|
|
2077
|
+
const msg = `${className} is a recursive type; the generated schema validates it via a lazy self-reference.`;
|
|
2195
2078
|
ctx.warnings.push(msg);
|
|
2196
2079
|
console.warn(`[nestjs-codegen] ${msg}`);
|
|
2197
2080
|
}
|
|
2198
2081
|
return { kind: "lazyRef", name: reserved };
|
|
2199
2082
|
}
|
|
2083
|
+
if (ctx.depth >= 8) {
|
|
2084
|
+
if (!ctx.warnedDecorators.has(`deep:${className}`)) {
|
|
2085
|
+
ctx.warnedDecorators.add(`deep:${className}`);
|
|
2086
|
+
const msg = `${className} nesting is too deep to expand; the generated schema uses unknown for it.`;
|
|
2087
|
+
ctx.warnings.push(msg);
|
|
2088
|
+
console.warn(`[nestjs-codegen] ${msg}`);
|
|
2089
|
+
}
|
|
2090
|
+
return { kind: "unknown", note: "nesting too deep \u2014 not expanded" };
|
|
2091
|
+
}
|
|
2200
2092
|
const existing = ctx.emittedClasses.get(className);
|
|
2201
2093
|
if (existing) return { kind: "ref", name: existing };
|
|
2202
2094
|
const schemaName = aliasFor(className, ctx);
|
|
@@ -2234,22 +2126,22 @@ function firstArgText(decorator) {
|
|
|
2234
2126
|
}
|
|
2235
2127
|
function numericArg(decorator) {
|
|
2236
2128
|
const arg = firstArg(decorator);
|
|
2237
|
-
if (arg &&
|
|
2129
|
+
if (arg && import_ts_morph4.Node.isNumericLiteral(arg)) return arg.getText();
|
|
2238
2130
|
return null;
|
|
2239
2131
|
}
|
|
2240
2132
|
function numericArgs(decorator) {
|
|
2241
2133
|
const args = decorator?.getArguments() ?? [];
|
|
2242
|
-
const num = (n) => n &&
|
|
2134
|
+
const num = (n) => n && import_ts_morph4.Node.isNumericLiteral(n) ? n.getText() : null;
|
|
2243
2135
|
return [num(args[0]), num(args[1])];
|
|
2244
2136
|
}
|
|
2245
2137
|
function messageRaw(decorator) {
|
|
2246
2138
|
const args = decorator?.getArguments() ?? [];
|
|
2247
2139
|
for (const arg of args) {
|
|
2248
|
-
if (
|
|
2140
|
+
if (import_ts_morph4.Node.isObjectLiteralExpression(arg)) {
|
|
2249
2141
|
for (const prop of arg.getProperties()) {
|
|
2250
|
-
if (
|
|
2142
|
+
if (import_ts_morph4.Node.isPropertyAssignment(prop) && prop.getName() === "message") {
|
|
2251
2143
|
const init = prop.getInitializer();
|
|
2252
|
-
if (init &&
|
|
2144
|
+
if (init && import_ts_morph4.Node.isStringLiteral(init)) return init.getText();
|
|
2253
2145
|
}
|
|
2254
2146
|
}
|
|
2255
2147
|
}
|
|
@@ -2259,9 +2151,9 @@ function messageRaw(decorator) {
|
|
|
2259
2151
|
function resolveTypeFactoryName(decorator) {
|
|
2260
2152
|
const arg = firstArg(decorator);
|
|
2261
2153
|
if (!arg) return null;
|
|
2262
|
-
if (
|
|
2154
|
+
if (import_ts_morph4.Node.isArrowFunction(arg)) {
|
|
2263
2155
|
const body = arg.getBody();
|
|
2264
|
-
if (
|
|
2156
|
+
if (import_ts_morph4.Node.isIdentifier(body)) return body.getText();
|
|
2265
2157
|
}
|
|
2266
2158
|
return null;
|
|
2267
2159
|
}
|
|
@@ -2272,7 +2164,7 @@ function singularClassName(typeText) {
|
|
|
2272
2164
|
function enumSchemaFromDecorator(decorator, classFile, ctx) {
|
|
2273
2165
|
const arg = firstArg(decorator);
|
|
2274
2166
|
if (!arg) return null;
|
|
2275
|
-
if (
|
|
2167
|
+
if (import_ts_morph4.Node.isIdentifier(arg)) {
|
|
2276
2168
|
const name = arg.getText();
|
|
2277
2169
|
const resolved = findType(name, classFile, ctx.project);
|
|
2278
2170
|
if (resolved && resolved.kind === "enum") {
|
|
@@ -2286,12 +2178,12 @@ function enumSchemaFromDecorator(decorator, classFile, ctx) {
|
|
|
2286
2178
|
}
|
|
2287
2179
|
return { kind: "unknown", note: `@IsEnum(${name}): enum not resolvable to literals` };
|
|
2288
2180
|
}
|
|
2289
|
-
if (
|
|
2181
|
+
if (import_ts_morph4.Node.isObjectLiteralExpression(arg)) {
|
|
2290
2182
|
const values = [];
|
|
2291
2183
|
for (const p of arg.getProperties()) {
|
|
2292
|
-
if (!
|
|
2184
|
+
if (!import_ts_morph4.Node.isPropertyAssignment(p)) continue;
|
|
2293
2185
|
const init = p.getInitializer();
|
|
2294
|
-
if (init &&
|
|
2186
|
+
if (init && import_ts_morph4.Node.isStringLiteral(init)) values.push(init.getText());
|
|
2295
2187
|
}
|
|
2296
2188
|
if (values.length > 0) return { kind: "enum", literals: values };
|
|
2297
2189
|
}
|
|
@@ -2299,9 +2191,9 @@ function enumSchemaFromDecorator(decorator, classFile, ctx) {
|
|
|
2299
2191
|
}
|
|
2300
2192
|
function inSchemaFromDecorator(decorator) {
|
|
2301
2193
|
const arg = firstArg(decorator);
|
|
2302
|
-
if (arg &&
|
|
2194
|
+
if (arg && import_ts_morph4.Node.isArrayLiteralExpression(arg)) {
|
|
2303
2195
|
const elements = arg.getElements();
|
|
2304
|
-
const allStrings = elements.every((e) =>
|
|
2196
|
+
const allStrings = elements.every((e) => import_ts_morph4.Node.isStringLiteral(e));
|
|
2305
2197
|
if (allStrings && elements.length > 0) {
|
|
2306
2198
|
return { kind: "enum", literals: elements.map((e) => e.getText()) };
|
|
2307
2199
|
}
|
|
@@ -2315,427 +2207,99 @@ function inSchemaFromDecorator(decorator) {
|
|
|
2315
2207
|
return null;
|
|
2316
2208
|
}
|
|
2317
2209
|
|
|
2318
|
-
// src/discovery/
|
|
2210
|
+
// src/discovery/filter-for.ts
|
|
2319
2211
|
var import_ts_morph6 = require("ts-morph");
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
"
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
"Matches",
|
|
2337
|
-
"IsEnum",
|
|
2338
|
-
"IsIn",
|
|
2339
|
-
"IsOptional",
|
|
2340
|
-
"IsNotEmpty",
|
|
2341
|
-
"IsArray",
|
|
2342
|
-
"ValidateNested",
|
|
2343
|
-
"Type",
|
|
2344
|
-
"IsObject",
|
|
2345
|
-
"Allow",
|
|
2346
|
-
"IsDefined"
|
|
2347
|
-
]);
|
|
2348
|
-
function extractZodFromDto(classDecl, sourceFile, project) {
|
|
2349
|
-
const ctx = {
|
|
2350
|
-
sourceFile,
|
|
2351
|
-
project,
|
|
2352
|
-
namedNestedSchemas: /* @__PURE__ */ new Map(),
|
|
2353
|
-
warnings: [],
|
|
2354
|
-
warnedDecorators: /* @__PURE__ */ new Set(),
|
|
2355
|
-
emittedClasses: /* @__PURE__ */ new Map(),
|
|
2356
|
-
visiting: /* @__PURE__ */ new Set(),
|
|
2357
|
-
recursiveSchemas: /* @__PURE__ */ new Set(),
|
|
2358
|
-
depth: 0
|
|
2359
|
-
};
|
|
2360
|
-
const schemaText = buildObjectSchema(classDecl, sourceFile, ctx);
|
|
2361
|
-
for (const schemaName of ctx.recursiveSchemas) {
|
|
2362
|
-
ctx.namedNestedSchemas.set(schemaName, "z.unknown() /* recursive type \u2014 not expanded */");
|
|
2363
|
-
}
|
|
2364
|
-
return {
|
|
2365
|
-
schemaText,
|
|
2366
|
-
namedNestedSchemas: ctx.namedNestedSchemas,
|
|
2367
|
-
warnings: ctx.warnings
|
|
2368
|
-
};
|
|
2212
|
+
|
|
2213
|
+
// src/discovery/filter-field-types.ts
|
|
2214
|
+
var import_ts_morph5 = require("ts-morph");
|
|
2215
|
+
|
|
2216
|
+
// src/discovery/enum-resolution.ts
|
|
2217
|
+
function resolveEnumValues(name, sourceFile, project) {
|
|
2218
|
+
const resolved = findType(name, sourceFile, project);
|
|
2219
|
+
if (!resolved || resolved.kind !== "enum") return null;
|
|
2220
|
+
let numeric = true;
|
|
2221
|
+
const values = resolved.members.map((m) => {
|
|
2222
|
+
const parsed = JSON.parse(m);
|
|
2223
|
+
if (typeof parsed === "string") numeric = false;
|
|
2224
|
+
return String(parsed);
|
|
2225
|
+
});
|
|
2226
|
+
if (values.length === 0) return null;
|
|
2227
|
+
return { values, numeric };
|
|
2369
2228
|
}
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2229
|
+
|
|
2230
|
+
// src/discovery/filter-field-types.ts
|
|
2231
|
+
var STRING_TYPE_KEYWORDS = ["varchar", "text", "string", "char", "uuid", "enum"];
|
|
2232
|
+
var NUMBER_TYPE_KEYWORDS = ["int", "float", "double", "decimal", "number", "numeric", "real"];
|
|
2233
|
+
var BOOLEAN_TYPE_KEYWORDS = ["bool", "boolean", "bit"];
|
|
2234
|
+
var DATE_TYPE_KEYWORDS = ["date", "time", "timestamp", "datetime"];
|
|
2235
|
+
var JSON_TYPE_KEYWORDS = ["json", "jsonb"];
|
|
2236
|
+
function classifyTypeKeyword(raw) {
|
|
2237
|
+
const t = raw.toLowerCase();
|
|
2238
|
+
if (STRING_TYPE_KEYWORDS.some((s) => t.includes(s))) return "string";
|
|
2239
|
+
if (NUMBER_TYPE_KEYWORDS.some((s) => t.includes(s))) return "number";
|
|
2240
|
+
if (BOOLEAN_TYPE_KEYWORDS.some((s) => t.includes(s))) return "boolean";
|
|
2241
|
+
if (DATE_TYPE_KEYWORDS.some((s) => t.includes(s))) return "date";
|
|
2242
|
+
if (JSON_TYPE_KEYWORDS.some((s) => t.includes(s))) return "json";
|
|
2243
|
+
return null;
|
|
2382
2244
|
}
|
|
2383
|
-
function
|
|
2384
|
-
return
|
|
2245
|
+
function markNullable(r, nullable) {
|
|
2246
|
+
return nullable ? { ...r, nullable: true } : r;
|
|
2385
2247
|
}
|
|
2386
|
-
function
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
base = ensureStringBase(base);
|
|
2425
|
-
refinements.push(`.uuid(${messageArg(dec("IsUUID"))})`);
|
|
2426
|
-
}
|
|
2427
|
-
if (has("Matches")) {
|
|
2428
|
-
const re = firstArgText2(dec("Matches"));
|
|
2429
|
-
if (re) {
|
|
2430
|
-
base = ensureStringBase(base);
|
|
2431
|
-
refinements.push(`.regex(${re})`);
|
|
2248
|
+
function classifyTypeNode(typeNode, sourceFile, project, opts) {
|
|
2249
|
+
if (import_ts_morph5.Node.isUnionTypeNode(typeNode)) {
|
|
2250
|
+
let nullable = false;
|
|
2251
|
+
const stringLits = [];
|
|
2252
|
+
const numberLits = [];
|
|
2253
|
+
const others = [];
|
|
2254
|
+
for (const member of typeNode.getTypeNodes()) {
|
|
2255
|
+
const kind = member.getKind();
|
|
2256
|
+
if (kind === import_ts_morph5.SyntaxKind.NullKeyword || kind === import_ts_morph5.SyntaxKind.UndefinedKeyword) {
|
|
2257
|
+
nullable = true;
|
|
2258
|
+
continue;
|
|
2259
|
+
}
|
|
2260
|
+
if (import_ts_morph5.Node.isLiteralTypeNode(member)) {
|
|
2261
|
+
const lit = member.getLiteral();
|
|
2262
|
+
if (import_ts_morph5.Node.isStringLiteral(lit)) {
|
|
2263
|
+
stringLits.push(lit.getLiteralValue());
|
|
2264
|
+
continue;
|
|
2265
|
+
}
|
|
2266
|
+
if (import_ts_morph5.Node.isNumericLiteral(lit)) {
|
|
2267
|
+
numberLits.push(lit.getText());
|
|
2268
|
+
continue;
|
|
2269
|
+
}
|
|
2270
|
+
if (lit.getKind() === import_ts_morph5.SyntaxKind.NullKeyword) {
|
|
2271
|
+
nullable = true;
|
|
2272
|
+
continue;
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
others.push(member);
|
|
2276
|
+
}
|
|
2277
|
+
if (others.length === 0 && stringLits.length > 0 && numberLits.length === 0) {
|
|
2278
|
+
return markNullable({ kind: "string", enumValues: stringLits }, nullable);
|
|
2279
|
+
}
|
|
2280
|
+
if (others.length === 0 && numberLits.length > 0 && stringLits.length === 0) {
|
|
2281
|
+
return markNullable({ kind: "number", enumValues: numberLits, numericEnum: true }, nullable);
|
|
2282
|
+
}
|
|
2283
|
+
if (others.length === 1) {
|
|
2284
|
+
const inner = classifyTypeNode(others[0], sourceFile, project, opts);
|
|
2285
|
+
return markNullable(inner, nullable || inner.nullable === true);
|
|
2432
2286
|
}
|
|
2287
|
+
return markNullable({ kind: "unknown" }, nullable);
|
|
2433
2288
|
}
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2289
|
+
switch (typeNode.getKind()) {
|
|
2290
|
+
case import_ts_morph5.SyntaxKind.StringKeyword:
|
|
2291
|
+
return { kind: "string" };
|
|
2292
|
+
case import_ts_morph5.SyntaxKind.NumberKeyword:
|
|
2293
|
+
return { kind: "number" };
|
|
2294
|
+
case import_ts_morph5.SyntaxKind.BooleanKeyword:
|
|
2295
|
+
return { kind: "boolean" };
|
|
2296
|
+
case import_ts_morph5.SyntaxKind.AnyKeyword:
|
|
2297
|
+
case import_ts_morph5.SyntaxKind.UnknownKeyword:
|
|
2298
|
+
return { kind: "unknown" };
|
|
2299
|
+
default:
|
|
2300
|
+
break;
|
|
2437
2301
|
}
|
|
2438
|
-
if (
|
|
2439
|
-
const n = numericArg2(dec("MaxLength"));
|
|
2440
|
-
if (n !== null) refinements.push(`.max(${n})`);
|
|
2441
|
-
}
|
|
2442
|
-
if (has("Length")) {
|
|
2443
|
-
const [min, max] = numericArgs2(dec("Length"));
|
|
2444
|
-
if (min !== null) refinements.push(`.min(${min})`);
|
|
2445
|
-
if (max !== null) refinements.push(`.max(${max})`);
|
|
2446
|
-
}
|
|
2447
|
-
if (has("Min")) {
|
|
2448
|
-
const n = numericArg2(dec("Min"));
|
|
2449
|
-
if (n !== null) refinements.push(`.min(${n})`);
|
|
2450
|
-
}
|
|
2451
|
-
if (has("Max")) {
|
|
2452
|
-
const n = numericArg2(dec("Max"));
|
|
2453
|
-
if (n !== null) refinements.push(`.max(${n})`);
|
|
2454
|
-
}
|
|
2455
|
-
if (has("IsPositive")) refinements.push(".positive()");
|
|
2456
|
-
if (has("IsNegative")) refinements.push(".negative()");
|
|
2457
|
-
if (has("IsNotEmpty") && isStringBase(base)) refinements.push(".min(1)");
|
|
2458
|
-
if (has("IsEnum")) {
|
|
2459
|
-
const enumExpr = enumSchemaFromDecorator2(dec("IsEnum"), classFile, ctx);
|
|
2460
|
-
if (enumExpr) base = enumExpr;
|
|
2461
|
-
}
|
|
2462
|
-
if (has("IsIn")) {
|
|
2463
|
-
const inExpr = inSchemaFromDecorator2(dec("IsIn"));
|
|
2464
|
-
if (inExpr) base = inExpr;
|
|
2465
|
-
}
|
|
2466
|
-
for (const name of decorators.keys()) {
|
|
2467
|
-
if (!KNOWN_DECORATORS2.has(name)) {
|
|
2468
|
-
comments.push(`/* @${name}: not translatable to zod (server-only) */`);
|
|
2469
|
-
if (!ctx.warnedDecorators.has(name)) {
|
|
2470
|
-
ctx.warnedDecorators.add(name);
|
|
2471
|
-
const msg = `@${name} is not translatable to zod and was skipped (server-only validation).`;
|
|
2472
|
-
ctx.warnings.push(msg);
|
|
2473
|
-
console.warn(`[nestjs-codegen/forms] ${msg}`);
|
|
2474
|
-
}
|
|
2475
|
-
}
|
|
2476
|
-
}
|
|
2477
|
-
let expr = base + refinements.join("");
|
|
2478
|
-
if (isArrayType && !expr.startsWith("z.array(")) {
|
|
2479
|
-
expr = `z.array(${expr})`;
|
|
2480
|
-
}
|
|
2481
|
-
expr = applyPresence2(expr, decorators);
|
|
2482
|
-
if (comments.length > 0) {
|
|
2483
|
-
expr = `${expr} ${comments.join(" ")}`;
|
|
2484
|
-
}
|
|
2485
|
-
return expr;
|
|
2486
|
-
}
|
|
2487
|
-
function applyPresence2(expr, decorators) {
|
|
2488
|
-
if (decorators.has("IsDefined")) return expr;
|
|
2489
|
-
if (decorators.has("IsOptional")) return `${expr}.optional()`;
|
|
2490
|
-
return expr;
|
|
2491
|
-
}
|
|
2492
|
-
function baseFromType2(typeText, isArrayType, ctx, classFile) {
|
|
2493
|
-
const inner = isArrayType ? typeText.slice(0, -2).trim() : typeText;
|
|
2494
|
-
switch (inner) {
|
|
2495
|
-
case "string":
|
|
2496
|
-
return "z.string()";
|
|
2497
|
-
case "number":
|
|
2498
|
-
return "z.number()";
|
|
2499
|
-
case "boolean":
|
|
2500
|
-
return "z.boolean()";
|
|
2501
|
-
case "Date":
|
|
2502
|
-
return "z.coerce.date()";
|
|
2503
|
-
case "File":
|
|
2504
|
-
case "Express.Multer.File":
|
|
2505
|
-
return "z.instanceof(File)";
|
|
2506
|
-
default:
|
|
2507
|
-
return "z.unknown()";
|
|
2508
|
-
}
|
|
2509
|
-
}
|
|
2510
|
-
function ensureStringBase(base) {
|
|
2511
|
-
return isStringBase(base) ? base : "z.string()";
|
|
2512
|
-
}
|
|
2513
|
-
function isStringBase(base) {
|
|
2514
|
-
return base.startsWith("z.string(");
|
|
2515
|
-
}
|
|
2516
|
-
function buildNestedReference2(className, fromFile, ctx) {
|
|
2517
|
-
if (ctx.visiting.has(className) || ctx.depth >= 8) {
|
|
2518
|
-
const reserved = ctx.emittedClasses.get(className) ?? aliasFor2(className, ctx);
|
|
2519
|
-
ctx.emittedClasses.set(className, reserved);
|
|
2520
|
-
ctx.recursiveSchemas.add(reserved);
|
|
2521
|
-
if (!ctx.warnedDecorators.has(`recursive:${reserved}`)) {
|
|
2522
|
-
ctx.warnedDecorators.add(`recursive:${reserved}`);
|
|
2523
|
-
const msg = `${className} is a recursive type and was not expanded; the generated form schema uses z.unknown() for it.`;
|
|
2524
|
-
ctx.warnings.push(msg);
|
|
2525
|
-
console.warn(`[nestjs-codegen/forms] ${msg}`);
|
|
2526
|
-
}
|
|
2527
|
-
return `z.lazy(() => ${reserved})`;
|
|
2528
|
-
}
|
|
2529
|
-
const existing = ctx.emittedClasses.get(className);
|
|
2530
|
-
if (existing) return existing;
|
|
2531
|
-
const schemaName = aliasFor2(className, ctx);
|
|
2532
|
-
const resolved = findType(className, fromFile, ctx.project);
|
|
2533
|
-
if (!resolved || resolved.kind !== "class") {
|
|
2534
|
-
return "z.object({}).passthrough()";
|
|
2535
|
-
}
|
|
2536
|
-
ctx.emittedClasses.set(className, schemaName);
|
|
2537
|
-
ctx.visiting.add(className);
|
|
2538
|
-
ctx.depth += 1;
|
|
2539
|
-
const childText = buildObjectSchema(resolved.decl, resolved.file, ctx);
|
|
2540
|
-
ctx.depth -= 1;
|
|
2541
|
-
ctx.visiting.delete(className);
|
|
2542
|
-
ctx.namedNestedSchemas.set(schemaName, childText);
|
|
2543
|
-
return schemaName;
|
|
2544
|
-
}
|
|
2545
|
-
function aliasFor2(className, ctx) {
|
|
2546
|
-
const baseName = `${className}Schema`;
|
|
2547
|
-
let candidate = baseName;
|
|
2548
|
-
let i = 1;
|
|
2549
|
-
const used = new Set(ctx.namedNestedSchemas.keys());
|
|
2550
|
-
for (const v of ctx.emittedClasses.values()) used.add(v);
|
|
2551
|
-
while (used.has(candidate)) {
|
|
2552
|
-
candidate = `${baseName}_${i}`;
|
|
2553
|
-
i += 1;
|
|
2554
|
-
}
|
|
2555
|
-
return candidate;
|
|
2556
|
-
}
|
|
2557
|
-
function firstArg2(decorator) {
|
|
2558
|
-
return decorator?.getArguments()[0];
|
|
2559
|
-
}
|
|
2560
|
-
function firstArgText2(decorator) {
|
|
2561
|
-
const arg = firstArg2(decorator);
|
|
2562
|
-
return arg ? arg.getText() : null;
|
|
2563
|
-
}
|
|
2564
|
-
function numericArg2(decorator) {
|
|
2565
|
-
const arg = firstArg2(decorator);
|
|
2566
|
-
if (arg && import_ts_morph6.Node.isNumericLiteral(arg)) return arg.getText();
|
|
2567
|
-
return null;
|
|
2568
|
-
}
|
|
2569
|
-
function numericArgs2(decorator) {
|
|
2570
|
-
const args = decorator?.getArguments() ?? [];
|
|
2571
|
-
const num = (n) => n && import_ts_morph6.Node.isNumericLiteral(n) ? n.getText() : null;
|
|
2572
|
-
return [num(args[0]), num(args[1])];
|
|
2573
|
-
}
|
|
2574
|
-
function messageArg(decorator) {
|
|
2575
|
-
const args = decorator?.getArguments() ?? [];
|
|
2576
|
-
for (const arg of args) {
|
|
2577
|
-
if (import_ts_morph6.Node.isObjectLiteralExpression(arg)) {
|
|
2578
|
-
for (const prop of arg.getProperties()) {
|
|
2579
|
-
if (import_ts_morph6.Node.isPropertyAssignment(prop) && prop.getName() === "message") {
|
|
2580
|
-
const init = prop.getInitializer();
|
|
2581
|
-
if (init && import_ts_morph6.Node.isStringLiteral(init)) {
|
|
2582
|
-
return `{ message: ${init.getText()} }`;
|
|
2583
|
-
}
|
|
2584
|
-
}
|
|
2585
|
-
}
|
|
2586
|
-
}
|
|
2587
|
-
}
|
|
2588
|
-
return "";
|
|
2589
|
-
}
|
|
2590
|
-
function resolveTypeFactoryName2(decorator) {
|
|
2591
|
-
const arg = firstArg2(decorator);
|
|
2592
|
-
if (!arg) return null;
|
|
2593
|
-
if (import_ts_morph6.Node.isArrowFunction(arg)) {
|
|
2594
|
-
const body = arg.getBody();
|
|
2595
|
-
if (import_ts_morph6.Node.isIdentifier(body)) return body.getText();
|
|
2596
|
-
}
|
|
2597
|
-
return null;
|
|
2598
|
-
}
|
|
2599
|
-
function singularClassName2(typeText) {
|
|
2600
|
-
const inner = typeText.endsWith("[]") ? typeText.slice(0, -2).trim() : typeText;
|
|
2601
|
-
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(inner) ? inner : null;
|
|
2602
|
-
}
|
|
2603
|
-
function enumSchemaFromDecorator2(decorator, classFile, ctx) {
|
|
2604
|
-
const arg = firstArg2(decorator);
|
|
2605
|
-
if (!arg) return null;
|
|
2606
|
-
if (import_ts_morph6.Node.isIdentifier(arg)) {
|
|
2607
|
-
const name = arg.getText();
|
|
2608
|
-
const resolved = findType(name, classFile, ctx.project);
|
|
2609
|
-
if (resolved && resolved.kind === "enum") {
|
|
2610
|
-
return `z.enum([${resolved.members.join(", ")}])`;
|
|
2611
|
-
}
|
|
2612
|
-
const msg = `@IsEnum(${name}): enum could not be resolved to literal members and is not importable into the generated form schema; falling back to z.unknown().`;
|
|
2613
|
-
if (!ctx.warnedDecorators.has(`IsEnum:${name}`)) {
|
|
2614
|
-
ctx.warnedDecorators.add(`IsEnum:${name}`);
|
|
2615
|
-
ctx.warnings.push(msg);
|
|
2616
|
-
console.warn(`[nestjs-codegen/forms] ${msg}`);
|
|
2617
|
-
}
|
|
2618
|
-
return `z.unknown() /* @IsEnum(${name}): enum not resolvable to literals */`;
|
|
2619
|
-
}
|
|
2620
|
-
if (import_ts_morph6.Node.isObjectLiteralExpression(arg)) {
|
|
2621
|
-
const values = [];
|
|
2622
|
-
for (const p of arg.getProperties()) {
|
|
2623
|
-
if (!import_ts_morph6.Node.isPropertyAssignment(p)) continue;
|
|
2624
|
-
const init = p.getInitializer();
|
|
2625
|
-
if (init && import_ts_morph6.Node.isStringLiteral(init)) values.push(init.getText());
|
|
2626
|
-
}
|
|
2627
|
-
if (values.length > 0) return `z.enum([${values.join(", ")}])`;
|
|
2628
|
-
}
|
|
2629
|
-
return null;
|
|
2630
|
-
}
|
|
2631
|
-
function inSchemaFromDecorator2(decorator) {
|
|
2632
|
-
const arg = firstArg2(decorator);
|
|
2633
|
-
if (arg && import_ts_morph6.Node.isArrayLiteralExpression(arg)) {
|
|
2634
|
-
const elements = arg.getElements();
|
|
2635
|
-
const allStrings = elements.every((e) => import_ts_morph6.Node.isStringLiteral(e));
|
|
2636
|
-
if (allStrings && elements.length > 0) {
|
|
2637
|
-
return `z.enum([${elements.map((e) => e.getText()).join(", ")}])`;
|
|
2638
|
-
}
|
|
2639
|
-
if (elements.length > 0) {
|
|
2640
|
-
return `z.union([${elements.map((e) => `z.literal(${e.getText()})`).join(", ")}])`;
|
|
2641
|
-
}
|
|
2642
|
-
}
|
|
2643
|
-
return null;
|
|
2644
|
-
}
|
|
2645
|
-
|
|
2646
|
-
// src/discovery/filter-for.ts
|
|
2647
|
-
var import_ts_morph8 = require("ts-morph");
|
|
2648
|
-
|
|
2649
|
-
// src/discovery/filter-field-types.ts
|
|
2650
|
-
var import_ts_morph7 = require("ts-morph");
|
|
2651
|
-
|
|
2652
|
-
// src/discovery/enum-resolution.ts
|
|
2653
|
-
function resolveEnumValues(name, sourceFile, project) {
|
|
2654
|
-
const resolved = findType(name, sourceFile, project);
|
|
2655
|
-
if (!resolved || resolved.kind !== "enum") return null;
|
|
2656
|
-
let numeric = true;
|
|
2657
|
-
const values = resolved.members.map((m) => {
|
|
2658
|
-
const parsed = JSON.parse(m);
|
|
2659
|
-
if (typeof parsed === "string") numeric = false;
|
|
2660
|
-
return String(parsed);
|
|
2661
|
-
});
|
|
2662
|
-
if (values.length === 0) return null;
|
|
2663
|
-
return { values, numeric };
|
|
2664
|
-
}
|
|
2665
|
-
|
|
2666
|
-
// src/discovery/filter-field-types.ts
|
|
2667
|
-
var STRING_TYPE_KEYWORDS = ["varchar", "text", "string", "char", "uuid", "enum"];
|
|
2668
|
-
var NUMBER_TYPE_KEYWORDS = ["int", "float", "double", "decimal", "number", "numeric", "real"];
|
|
2669
|
-
var BOOLEAN_TYPE_KEYWORDS = ["bool", "boolean", "bit"];
|
|
2670
|
-
var DATE_TYPE_KEYWORDS = ["date", "time", "timestamp", "datetime"];
|
|
2671
|
-
var JSON_TYPE_KEYWORDS = ["json", "jsonb"];
|
|
2672
|
-
function classifyTypeKeyword(raw) {
|
|
2673
|
-
const t = raw.toLowerCase();
|
|
2674
|
-
if (STRING_TYPE_KEYWORDS.some((s) => t.includes(s))) return "string";
|
|
2675
|
-
if (NUMBER_TYPE_KEYWORDS.some((s) => t.includes(s))) return "number";
|
|
2676
|
-
if (BOOLEAN_TYPE_KEYWORDS.some((s) => t.includes(s))) return "boolean";
|
|
2677
|
-
if (DATE_TYPE_KEYWORDS.some((s) => t.includes(s))) return "date";
|
|
2678
|
-
if (JSON_TYPE_KEYWORDS.some((s) => t.includes(s))) return "json";
|
|
2679
|
-
return null;
|
|
2680
|
-
}
|
|
2681
|
-
function markNullable(r, nullable) {
|
|
2682
|
-
return nullable ? { ...r, nullable: true } : r;
|
|
2683
|
-
}
|
|
2684
|
-
function classifyTypeNode(typeNode, sourceFile, project, opts) {
|
|
2685
|
-
if (import_ts_morph7.Node.isUnionTypeNode(typeNode)) {
|
|
2686
|
-
let nullable = false;
|
|
2687
|
-
const stringLits = [];
|
|
2688
|
-
const numberLits = [];
|
|
2689
|
-
const others = [];
|
|
2690
|
-
for (const member of typeNode.getTypeNodes()) {
|
|
2691
|
-
const kind = member.getKind();
|
|
2692
|
-
if (kind === import_ts_morph7.SyntaxKind.NullKeyword || kind === import_ts_morph7.SyntaxKind.UndefinedKeyword) {
|
|
2693
|
-
nullable = true;
|
|
2694
|
-
continue;
|
|
2695
|
-
}
|
|
2696
|
-
if (import_ts_morph7.Node.isLiteralTypeNode(member)) {
|
|
2697
|
-
const lit = member.getLiteral();
|
|
2698
|
-
if (import_ts_morph7.Node.isStringLiteral(lit)) {
|
|
2699
|
-
stringLits.push(lit.getLiteralValue());
|
|
2700
|
-
continue;
|
|
2701
|
-
}
|
|
2702
|
-
if (import_ts_morph7.Node.isNumericLiteral(lit)) {
|
|
2703
|
-
numberLits.push(lit.getText());
|
|
2704
|
-
continue;
|
|
2705
|
-
}
|
|
2706
|
-
if (lit.getKind() === import_ts_morph7.SyntaxKind.NullKeyword) {
|
|
2707
|
-
nullable = true;
|
|
2708
|
-
continue;
|
|
2709
|
-
}
|
|
2710
|
-
}
|
|
2711
|
-
others.push(member);
|
|
2712
|
-
}
|
|
2713
|
-
if (others.length === 0 && stringLits.length > 0 && numberLits.length === 0) {
|
|
2714
|
-
return markNullable({ kind: "string", enumValues: stringLits }, nullable);
|
|
2715
|
-
}
|
|
2716
|
-
if (others.length === 0 && numberLits.length > 0 && stringLits.length === 0) {
|
|
2717
|
-
return markNullable({ kind: "number", enumValues: numberLits, numericEnum: true }, nullable);
|
|
2718
|
-
}
|
|
2719
|
-
if (others.length === 1) {
|
|
2720
|
-
const inner = classifyTypeNode(others[0], sourceFile, project, opts);
|
|
2721
|
-
return markNullable(inner, nullable || inner.nullable === true);
|
|
2722
|
-
}
|
|
2723
|
-
return markNullable({ kind: "unknown" }, nullable);
|
|
2724
|
-
}
|
|
2725
|
-
switch (typeNode.getKind()) {
|
|
2726
|
-
case import_ts_morph7.SyntaxKind.StringKeyword:
|
|
2727
|
-
return { kind: "string" };
|
|
2728
|
-
case import_ts_morph7.SyntaxKind.NumberKeyword:
|
|
2729
|
-
return { kind: "number" };
|
|
2730
|
-
case import_ts_morph7.SyntaxKind.BooleanKeyword:
|
|
2731
|
-
return { kind: "boolean" };
|
|
2732
|
-
case import_ts_morph7.SyntaxKind.AnyKeyword:
|
|
2733
|
-
case import_ts_morph7.SyntaxKind.UnknownKeyword:
|
|
2734
|
-
return { kind: "unknown" };
|
|
2735
|
-
default:
|
|
2736
|
-
break;
|
|
2737
|
-
}
|
|
2738
|
-
if (import_ts_morph7.Node.isTypeReference(typeNode)) {
|
|
2302
|
+
if (import_ts_morph5.Node.isTypeReference(typeNode)) {
|
|
2739
2303
|
const refName = typeNode.getTypeName().getText();
|
|
2740
2304
|
if (refName === "Date") return { kind: "date" };
|
|
2741
2305
|
if (refName === "Record" || refName === "Object") return { kind: "json" };
|
|
@@ -2748,25 +2312,25 @@ function classifyTypeNode(typeNode, sourceFile, project, opts) {
|
|
|
2748
2312
|
if (typeRef) return { kind: "unknown", typeRef };
|
|
2749
2313
|
return { kind: "unknown" };
|
|
2750
2314
|
}
|
|
2751
|
-
if (
|
|
2315
|
+
if (import_ts_morph5.Node.isTypeLiteral(typeNode)) return { kind: "json" };
|
|
2752
2316
|
return { kind: "unknown" };
|
|
2753
2317
|
}
|
|
2754
2318
|
function enumFromDecoratorArgs(args, sourceFile, project) {
|
|
2755
2319
|
for (const arg of args) {
|
|
2756
|
-
if (
|
|
2320
|
+
if (import_ts_morph5.Node.isArrowFunction(arg)) {
|
|
2757
2321
|
const body = arg.getBody();
|
|
2758
|
-
if (
|
|
2322
|
+
if (import_ts_morph5.Node.isIdentifier(body)) {
|
|
2759
2323
|
const en = resolveEnumValues(body.getText(), sourceFile, project);
|
|
2760
2324
|
if (en) return en;
|
|
2761
2325
|
}
|
|
2762
2326
|
}
|
|
2763
|
-
if (
|
|
2327
|
+
if (import_ts_morph5.Node.isObjectLiteralExpression(arg)) {
|
|
2764
2328
|
const itemsProp = arg.getProperty("items");
|
|
2765
|
-
if (itemsProp &&
|
|
2329
|
+
if (itemsProp && import_ts_morph5.Node.isPropertyAssignment(itemsProp)) {
|
|
2766
2330
|
const init = itemsProp.getInitializer();
|
|
2767
|
-
if (init &&
|
|
2331
|
+
if (init && import_ts_morph5.Node.isArrowFunction(init)) {
|
|
2768
2332
|
const body = init.getBody();
|
|
2769
|
-
if (
|
|
2333
|
+
if (import_ts_morph5.Node.isIdentifier(body)) {
|
|
2770
2334
|
const en = resolveEnumValues(body.getText(), sourceFile, project);
|
|
2771
2335
|
if (en) return en;
|
|
2772
2336
|
}
|
|
@@ -2789,7 +2353,7 @@ function classifyFromColumnDecorator(prop, sourceFile, project) {
|
|
|
2789
2353
|
return { kind: "string" };
|
|
2790
2354
|
}
|
|
2791
2355
|
for (const arg of args) {
|
|
2792
|
-
if (
|
|
2356
|
+
if (import_ts_morph5.Node.isStringLiteral(arg)) {
|
|
2793
2357
|
const raw = arg.getLiteralValue();
|
|
2794
2358
|
const kind = classifyTypeKeyword(raw);
|
|
2795
2359
|
if (kind) {
|
|
@@ -2797,11 +2361,11 @@ function classifyFromColumnDecorator(prop, sourceFile, project) {
|
|
|
2797
2361
|
return { kind };
|
|
2798
2362
|
}
|
|
2799
2363
|
}
|
|
2800
|
-
if (
|
|
2364
|
+
if (import_ts_morph5.Node.isObjectLiteralExpression(arg)) {
|
|
2801
2365
|
const enumProp = arg.getProperty("enum");
|
|
2802
|
-
if (enumProp &&
|
|
2366
|
+
if (enumProp && import_ts_morph5.Node.isPropertyAssignment(enumProp)) {
|
|
2803
2367
|
const init = enumProp.getInitializer();
|
|
2804
|
-
if (init &&
|
|
2368
|
+
if (init && import_ts_morph5.Node.isIdentifier(init)) {
|
|
2805
2369
|
const en = resolveEnumValues(init.getText(), sourceFile, project);
|
|
2806
2370
|
if (en) {
|
|
2807
2371
|
return en.numeric ? { kind: "number", enumValues: en.values, numericEnum: true } : { kind: "string", enumValues: en.values };
|
|
@@ -2810,9 +2374,9 @@ function classifyFromColumnDecorator(prop, sourceFile, project) {
|
|
|
2810
2374
|
}
|
|
2811
2375
|
}
|
|
2812
2376
|
const typeProp = arg.getProperty("type");
|
|
2813
|
-
if (typeProp &&
|
|
2377
|
+
if (typeProp && import_ts_morph5.Node.isPropertyAssignment(typeProp)) {
|
|
2814
2378
|
const init = typeProp.getInitializer();
|
|
2815
|
-
if (init &&
|
|
2379
|
+
if (init && import_ts_morph5.Node.isStringLiteral(init)) {
|
|
2816
2380
|
const kind = classifyTypeKeyword(init.getLiteralValue());
|
|
2817
2381
|
if (kind) return { kind };
|
|
2818
2382
|
}
|
|
@@ -2848,7 +2412,7 @@ function toFilterFieldType(name, r) {
|
|
|
2848
2412
|
|
|
2849
2413
|
// src/discovery/filter-for.ts
|
|
2850
2414
|
function classifyFilterForHint(typeInit) {
|
|
2851
|
-
if (
|
|
2415
|
+
if (import_ts_morph6.Node.isStringLiteral(typeInit)) {
|
|
2852
2416
|
switch (typeInit.getLiteralValue()) {
|
|
2853
2417
|
case "string":
|
|
2854
2418
|
return { kind: "string" };
|
|
@@ -2862,10 +2426,10 @@ function classifyFilterForHint(typeInit) {
|
|
|
2862
2426
|
return null;
|
|
2863
2427
|
}
|
|
2864
2428
|
}
|
|
2865
|
-
if (
|
|
2429
|
+
if (import_ts_morph6.Node.isArrayLiteralExpression(typeInit)) {
|
|
2866
2430
|
const values = [];
|
|
2867
2431
|
for (const el of typeInit.getElements()) {
|
|
2868
|
-
if (!
|
|
2432
|
+
if (!import_ts_morph6.Node.isStringLiteral(el)) return null;
|
|
2869
2433
|
values.push(el.getLiteralValue());
|
|
2870
2434
|
}
|
|
2871
2435
|
if (values.length === 0) return null;
|
|
@@ -2900,11 +2464,11 @@ function extractFilterForHints(classDecl, project) {
|
|
|
2900
2464
|
if (!filterForDec) continue;
|
|
2901
2465
|
const args = filterForDec.getArguments();
|
|
2902
2466
|
const keyArg = args[0];
|
|
2903
|
-
const inputKey = keyArg &&
|
|
2467
|
+
const inputKey = keyArg && import_ts_morph6.Node.isStringLiteral(keyArg) ? keyArg.getLiteralValue() : method.getName();
|
|
2904
2468
|
const optsArg = args[1];
|
|
2905
|
-
if (optsArg &&
|
|
2469
|
+
if (optsArg && import_ts_morph6.Node.isObjectLiteralExpression(optsArg)) {
|
|
2906
2470
|
const typeProp = optsArg.getProperty("type");
|
|
2907
|
-
if (typeProp &&
|
|
2471
|
+
if (typeProp && import_ts_morph6.Node.isPropertyAssignment(typeProp)) {
|
|
2908
2472
|
const typeInit = typeProp.getInitializer();
|
|
2909
2473
|
if (typeInit) {
|
|
2910
2474
|
const classified = classifyFilterForHint(typeInit);
|
|
@@ -2927,14 +2491,14 @@ function extractApplyFilterInfo(method, sourceFile, project) {
|
|
|
2927
2491
|
const args = filterDecorator.getArguments();
|
|
2928
2492
|
if (args.length === 0) continue;
|
|
2929
2493
|
const filterClassArg = args[0];
|
|
2930
|
-
if (!filterClassArg || !
|
|
2494
|
+
if (!filterClassArg || !import_ts_morph6.Node.isIdentifier(filterClassArg)) continue;
|
|
2931
2495
|
let source = "query";
|
|
2932
2496
|
const optionsArg = args[1];
|
|
2933
|
-
if (optionsArg &&
|
|
2497
|
+
if (optionsArg && import_ts_morph6.Node.isObjectLiteralExpression(optionsArg)) {
|
|
2934
2498
|
const sourceProp = optionsArg.getProperty("source");
|
|
2935
|
-
if (sourceProp &&
|
|
2499
|
+
if (sourceProp && import_ts_morph6.Node.isPropertyAssignment(sourceProp)) {
|
|
2936
2500
|
const init = sourceProp.getInitializer();
|
|
2937
|
-
if (init &&
|
|
2501
|
+
if (init && import_ts_morph6.Node.isStringLiteral(init) && init.getLiteralValue() === "body") {
|
|
2938
2502
|
source = "body";
|
|
2939
2503
|
}
|
|
2940
2504
|
}
|
|
@@ -2997,22 +2561,22 @@ function resolveRelationEntity(prop, sourceFile, project) {
|
|
|
2997
2561
|
const args = dec.getArguments();
|
|
2998
2562
|
if (args.length === 0) continue;
|
|
2999
2563
|
const arg = args[0];
|
|
3000
|
-
if (
|
|
2564
|
+
if (import_ts_morph6.Node.isObjectLiteralExpression(arg)) {
|
|
3001
2565
|
const entityProp = arg.getProperty("entity");
|
|
3002
|
-
if (entityProp &&
|
|
2566
|
+
if (entityProp && import_ts_morph6.Node.isPropertyAssignment(entityProp)) {
|
|
3003
2567
|
const init = entityProp.getInitializer();
|
|
3004
|
-
if (init &&
|
|
2568
|
+
if (init && import_ts_morph6.Node.isArrowFunction(init)) {
|
|
3005
2569
|
const body = init.getBody();
|
|
3006
|
-
if (
|
|
2570
|
+
if (import_ts_morph6.Node.isIdentifier(body)) {
|
|
3007
2571
|
const resolved = findType(body.getText(), prop.getSourceFile(), project);
|
|
3008
2572
|
if (resolved?.kind === "class") return resolved.decl;
|
|
3009
2573
|
}
|
|
3010
2574
|
}
|
|
3011
2575
|
}
|
|
3012
2576
|
}
|
|
3013
|
-
if (
|
|
2577
|
+
if (import_ts_morph6.Node.isArrowFunction(arg)) {
|
|
3014
2578
|
const body = arg.getBody();
|
|
3015
|
-
if (
|
|
2579
|
+
if (import_ts_morph6.Node.isIdentifier(body)) {
|
|
3016
2580
|
const resolved = findType(body.getText(), prop.getSourceFile(), project);
|
|
3017
2581
|
if (resolved?.kind === "class") return resolved.decl;
|
|
3018
2582
|
}
|
|
@@ -3036,11 +2600,11 @@ function extractFilterableEntityFields(filterClass, project) {
|
|
|
3036
2600
|
const args = filterableDecorator.getArguments();
|
|
3037
2601
|
if (args.length === 0) return [];
|
|
3038
2602
|
const optionsArg = args[0];
|
|
3039
|
-
if (!
|
|
2603
|
+
if (!import_ts_morph6.Node.isObjectLiteralExpression(optionsArg)) return [];
|
|
3040
2604
|
const entityProp = optionsArg.getProperty("entity");
|
|
3041
|
-
if (!entityProp || !
|
|
2605
|
+
if (!entityProp || !import_ts_morph6.Node.isPropertyAssignment(entityProp)) return [];
|
|
3042
2606
|
const entityInit = entityProp.getInitializer();
|
|
3043
|
-
if (!entityInit || !
|
|
2607
|
+
if (!entityInit || !import_ts_morph6.Node.isIdentifier(entityInit)) return [];
|
|
3044
2608
|
const entityName = entityInit.getText();
|
|
3045
2609
|
const filterSourceFile = filterClass.getSourceFile();
|
|
3046
2610
|
const resolvedEntity = findType(entityName, filterSourceFile, project);
|
|
@@ -3056,17 +2620,17 @@ function extractFilterableEntityFields(filterClass, project) {
|
|
|
3056
2620
|
const relationsDecorator = filterClass.getDecorators().find((d) => d.getName() === "Relations");
|
|
3057
2621
|
if (relationsDecorator) {
|
|
3058
2622
|
const relArgs = relationsDecorator.getArguments();
|
|
3059
|
-
if (relArgs.length > 0 &&
|
|
2623
|
+
if (relArgs.length > 0 && import_ts_morph6.Node.isObjectLiteralExpression(relArgs[0])) {
|
|
3060
2624
|
for (const relProp of relArgs[0].getProperties()) {
|
|
3061
|
-
if (!
|
|
2625
|
+
if (!import_ts_morph6.Node.isPropertyAssignment(relProp)) continue;
|
|
3062
2626
|
const relInit = relProp.getInitializer();
|
|
3063
|
-
if (!relInit || !
|
|
2627
|
+
if (!relInit || !import_ts_morph6.Node.isObjectLiteralExpression(relInit)) continue;
|
|
3064
2628
|
const keysProp = relInit.getProperty("keys");
|
|
3065
|
-
if (!keysProp || !
|
|
2629
|
+
if (!keysProp || !import_ts_morph6.Node.isPropertyAssignment(keysProp)) continue;
|
|
3066
2630
|
const keysInit = keysProp.getInitializer();
|
|
3067
|
-
if (!keysInit || !
|
|
2631
|
+
if (!keysInit || !import_ts_morph6.Node.isArrayLiteralExpression(keysInit)) continue;
|
|
3068
2632
|
for (const el of keysInit.getElements()) {
|
|
3069
|
-
if (
|
|
2633
|
+
if (import_ts_morph6.Node.isStringLiteral(el)) {
|
|
3070
2634
|
fields.push(toFilterFieldType(el.getLiteralValue(), { kind: "unknown" }));
|
|
3071
2635
|
}
|
|
3072
2636
|
}
|
|
@@ -3076,267 +2640,65 @@ function extractFilterableEntityFields(filterClass, project) {
|
|
|
3076
2640
|
return fields;
|
|
3077
2641
|
}
|
|
3078
2642
|
|
|
3079
|
-
// src/discovery/
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
2643
|
+
// src/discovery/dto-type-resolver.ts
|
|
2644
|
+
var WRAPPER_TYPES = {
|
|
2645
|
+
// MikroORM Ref/Reference/LoadedReference/IdentifiedReference are server-side
|
|
2646
|
+
// wrappers around related entities; the wire shape is just the referenced
|
|
2647
|
+
// entity. Unwrap to the type argument.
|
|
2648
|
+
Ref: "unwrap",
|
|
2649
|
+
Reference: "unwrap",
|
|
2650
|
+
LoadedReference: "unwrap",
|
|
2651
|
+
IdentifiedReference: "unwrap",
|
|
2652
|
+
// MikroORM Opt<T> is a marker, Loaded<T, ...> is a wrapper; both reduce to T.
|
|
2653
|
+
Opt: "unwrap",
|
|
2654
|
+
Loaded: "unwrap",
|
|
2655
|
+
// Promise<T> — unwrap
|
|
2656
|
+
Promise: "unwrap",
|
|
2657
|
+
// MikroORM Collection<T> serializes as an array of T on the wire.
|
|
2658
|
+
Collection: "arrayOf",
|
|
2659
|
+
// Array<T> generic form
|
|
2660
|
+
Array: "arrayOf"
|
|
2661
|
+
};
|
|
2662
|
+
var PASSTHROUGH_UTILITY = /* @__PURE__ */ new Set([
|
|
2663
|
+
"Record",
|
|
2664
|
+
"Omit",
|
|
2665
|
+
"Pick",
|
|
2666
|
+
"Partial",
|
|
2667
|
+
"Required",
|
|
2668
|
+
"Readonly",
|
|
2669
|
+
"Map",
|
|
2670
|
+
"Set"
|
|
2671
|
+
]);
|
|
2672
|
+
function resolveTypeNodeToString(typeNode, sourceFile, project, depth) {
|
|
2673
|
+
if (depth <= 0) return "unknown";
|
|
2674
|
+
if (import_ts_morph7.Node.isArrayTypeNode(typeNode)) {
|
|
2675
|
+
const elementType = typeNode.getElementTypeNode();
|
|
2676
|
+
return `Array<${resolveTypeNodeToString(elementType, sourceFile, project, depth)}>`;
|
|
3102
2677
|
}
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
project.addSourceFileAtPath(f);
|
|
2678
|
+
if (import_ts_morph7.Node.isUnionTypeNode(typeNode)) {
|
|
2679
|
+
return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth)).join(" | ");
|
|
3106
2680
|
}
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
projectRoot: cwd,
|
|
3110
|
-
tsconfigPaths: loadTsconfigPaths(tsconfigPath)
|
|
3111
|
-
});
|
|
3112
|
-
try {
|
|
3113
|
-
for (const sourceFile of project.getSourceFiles()) {
|
|
3114
|
-
routes.push(...extractFromSourceFile(sourceFile, project));
|
|
3115
|
-
}
|
|
3116
|
-
} finally {
|
|
3117
|
-
restoreDiscoveryContext(prevCtx);
|
|
2681
|
+
if (import_ts_morph7.Node.isIntersectionTypeNode(typeNode)) {
|
|
2682
|
+
return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth)).join(" & ");
|
|
3118
2683
|
}
|
|
3119
|
-
|
|
3120
|
-
}
|
|
3121
|
-
function zodAstToTs(node) {
|
|
3122
|
-
if (!import_ts_morph9.Node.isCallExpression(node)) return "unknown";
|
|
3123
|
-
const expr = node.getExpression();
|
|
3124
|
-
if (import_ts_morph9.Node.isPropertyAccessExpression(expr)) {
|
|
3125
|
-
const methodName = expr.getName();
|
|
3126
|
-
const receiver = expr.getExpression();
|
|
3127
|
-
if (methodName === "optional") {
|
|
3128
|
-
return `${zodAstToTs(receiver)} | undefined`;
|
|
3129
|
-
}
|
|
3130
|
-
if (methodName === "nullable") {
|
|
3131
|
-
return `${zodAstToTs(receiver)} | null`;
|
|
3132
|
-
}
|
|
3133
|
-
const args = node.getArguments();
|
|
3134
|
-
switch (methodName) {
|
|
3135
|
-
case "string":
|
|
3136
|
-
return "string";
|
|
3137
|
-
case "number":
|
|
3138
|
-
return "number";
|
|
3139
|
-
case "boolean":
|
|
3140
|
-
return "boolean";
|
|
3141
|
-
case "unknown":
|
|
3142
|
-
return "unknown";
|
|
3143
|
-
case "any":
|
|
3144
|
-
return "unknown";
|
|
3145
|
-
case "literal": {
|
|
3146
|
-
const lit = args[0];
|
|
3147
|
-
if (!lit) return "unknown";
|
|
3148
|
-
if (import_ts_morph9.Node.isStringLiteral(lit)) return JSON.stringify(lit.getLiteralValue());
|
|
3149
|
-
if (import_ts_morph9.Node.isNumericLiteral(lit)) return lit.getLiteralValue().toString();
|
|
3150
|
-
if (lit.getKind() === import_ts_morph9.SyntaxKind.TrueKeyword) return "true";
|
|
3151
|
-
if (lit.getKind() === import_ts_morph9.SyntaxKind.FalseKeyword) return "false";
|
|
3152
|
-
return "unknown";
|
|
3153
|
-
}
|
|
3154
|
-
case "enum": {
|
|
3155
|
-
const arrArg = args[0];
|
|
3156
|
-
if (!arrArg || !import_ts_morph9.Node.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
3157
|
-
const members = arrArg.getElements().map(
|
|
3158
|
-
(el) => import_ts_morph9.Node.isStringLiteral(el) ? JSON.stringify(el.getLiteralValue()) : "unknown"
|
|
3159
|
-
);
|
|
3160
|
-
return members.join(" | ");
|
|
3161
|
-
}
|
|
3162
|
-
case "array": {
|
|
3163
|
-
const inner = args[0];
|
|
3164
|
-
if (!inner) return "unknown";
|
|
3165
|
-
return `Array<${zodAstToTs(inner)}>`;
|
|
3166
|
-
}
|
|
3167
|
-
case "object": {
|
|
3168
|
-
const objArg = args[0];
|
|
3169
|
-
if (!objArg || !import_ts_morph9.Node.isObjectLiteralExpression(objArg)) return "unknown";
|
|
3170
|
-
const lines = [];
|
|
3171
|
-
for (const prop of objArg.getProperties()) {
|
|
3172
|
-
if (!import_ts_morph9.Node.isPropertyAssignment(prop)) continue;
|
|
3173
|
-
const key = prop.getName();
|
|
3174
|
-
const valNode = prop.getInitializer();
|
|
3175
|
-
if (!valNode) continue;
|
|
3176
|
-
const tsType = zodAstToTs(valNode);
|
|
3177
|
-
const isOpt = isOptionalChain(valNode);
|
|
3178
|
-
lines.push(`${key}${isOpt ? "?" : ""}: ${tsType}`);
|
|
3179
|
-
}
|
|
3180
|
-
return `{ ${lines.join("; ")} }`;
|
|
3181
|
-
}
|
|
3182
|
-
case "union": {
|
|
3183
|
-
const arrArg = args[0];
|
|
3184
|
-
if (!arrArg || !import_ts_morph9.Node.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
3185
|
-
return arrArg.getElements().map(zodAstToTs).join(" | ");
|
|
3186
|
-
}
|
|
3187
|
-
case "record": {
|
|
3188
|
-
const valArg = args.length === 1 ? args[0] : args[1];
|
|
3189
|
-
if (!valArg) return "unknown";
|
|
3190
|
-
return `Record<string, ${zodAstToTs(valArg)}>`;
|
|
3191
|
-
}
|
|
3192
|
-
case "tuple": {
|
|
3193
|
-
const arrArg = args[0];
|
|
3194
|
-
if (!arrArg || !import_ts_morph9.Node.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
3195
|
-
return `[${arrArg.getElements().map(zodAstToTs).join(", ")}]`;
|
|
3196
|
-
}
|
|
3197
|
-
default:
|
|
3198
|
-
return "unknown";
|
|
3199
|
-
}
|
|
3200
|
-
}
|
|
3201
|
-
return "unknown";
|
|
3202
|
-
}
|
|
3203
|
-
function isOptionalChain(node) {
|
|
3204
|
-
if (!import_ts_morph9.Node.isCallExpression(node)) return false;
|
|
3205
|
-
const expr = node.getExpression();
|
|
3206
|
-
return import_ts_morph9.Node.isPropertyAccessExpression(expr) && expr.getName() === "optional";
|
|
3207
|
-
}
|
|
3208
|
-
function decoratorStringArg(decoratorExpr) {
|
|
3209
|
-
if (!decoratorExpr) return void 0;
|
|
3210
|
-
if (import_ts_morph9.Node.isStringLiteral(decoratorExpr)) return decoratorExpr.getLiteralValue();
|
|
3211
|
-
if (import_ts_morph9.Node.isArrayLiteralExpression(decoratorExpr)) {
|
|
3212
|
-
const first = decoratorExpr.getElements()[0];
|
|
3213
|
-
if (first && import_ts_morph9.Node.isStringLiteral(first)) return first.getLiteralValue();
|
|
3214
|
-
}
|
|
3215
|
-
return void 0;
|
|
3216
|
-
}
|
|
3217
|
-
function parseDefineContractCall(callExpr) {
|
|
3218
|
-
if (!import_ts_morph9.Node.isCallExpression(callExpr)) return null;
|
|
3219
|
-
const callee = callExpr.getExpression();
|
|
3220
|
-
const calleeName = import_ts_morph9.Node.isIdentifier(callee) ? callee.getText() : import_ts_morph9.Node.isPropertyAccessExpression(callee) ? callee.getName() : "";
|
|
3221
|
-
if (calleeName !== "defineContract") return null;
|
|
3222
|
-
const args = callExpr.getArguments();
|
|
3223
|
-
const optsArg = args[0];
|
|
3224
|
-
if (!optsArg || !import_ts_morph9.Node.isObjectLiteralExpression(optsArg)) return null;
|
|
3225
|
-
let query = null;
|
|
3226
|
-
let body = null;
|
|
3227
|
-
let response = "unknown";
|
|
3228
|
-
let bodyZodText = null;
|
|
3229
|
-
let queryZodText = null;
|
|
3230
|
-
for (const prop of optsArg.getProperties()) {
|
|
3231
|
-
if (!import_ts_morph9.Node.isPropertyAssignment(prop)) continue;
|
|
3232
|
-
const propName = prop.getName();
|
|
3233
|
-
const val = prop.getInitializer();
|
|
3234
|
-
if (!val) continue;
|
|
3235
|
-
if (propName === "query") {
|
|
3236
|
-
query = zodAstToTs(val);
|
|
3237
|
-
queryZodText = val.getText();
|
|
3238
|
-
} else if (propName === "body") {
|
|
3239
|
-
body = zodAstToTs(val);
|
|
3240
|
-
bodyZodText = val.getText();
|
|
3241
|
-
} else if (propName === "response") {
|
|
3242
|
-
response = zodAstToTs(val);
|
|
3243
|
-
}
|
|
3244
|
-
}
|
|
3245
|
-
return { query, body, response, bodyZodText, queryZodText };
|
|
3246
|
-
}
|
|
3247
|
-
function deriveClassSegment(className) {
|
|
3248
|
-
const noSuffix = className.replace(/Controller$/, "");
|
|
3249
|
-
if (!noSuffix) {
|
|
3250
|
-
throw new Error(
|
|
3251
|
-
`Controller class name "${className}" derives empty route segment after stripping "Controller". Add an @As(...) override at the class level.`
|
|
3252
|
-
);
|
|
3253
|
-
}
|
|
3254
|
-
return noSuffix.charAt(0).toLowerCase() + noSuffix.slice(1);
|
|
3255
|
-
}
|
|
3256
|
-
function resolveRouteName(className, methodName, classAs, methodAs) {
|
|
3257
|
-
const classPortion = classAs ?? deriveClassSegment(className);
|
|
3258
|
-
const methodPortion = methodAs ?? methodName;
|
|
3259
|
-
return `${classPortion}.${methodPortion}`;
|
|
3260
|
-
}
|
|
3261
|
-
function joinPaths(prefix, suffix) {
|
|
3262
|
-
if (!prefix && !suffix) return "/";
|
|
3263
|
-
if (!prefix) return suffix.startsWith("/") ? suffix : `/${suffix}`;
|
|
3264
|
-
if (!suffix) return prefix.startsWith("/") ? prefix : `/${prefix}`;
|
|
3265
|
-
const p = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
|
|
3266
|
-
const s = suffix.startsWith("/") ? suffix : `/${suffix}`;
|
|
3267
|
-
const combined = p + s;
|
|
3268
|
-
return combined === "" ? "/" : combined;
|
|
3269
|
-
}
|
|
3270
|
-
function extractParams(path) {
|
|
3271
|
-
const matches = path.matchAll(/:(\w+)/g);
|
|
3272
|
-
return Array.from(matches).map((m) => ({ name: m[1], source: "path" }));
|
|
3273
|
-
}
|
|
3274
|
-
function resolveTypeNodeToString(typeNode, sourceFile, project, depth) {
|
|
3275
|
-
if (depth <= 0) return "unknown";
|
|
3276
|
-
if (import_ts_morph9.Node.isArrayTypeNode(typeNode)) {
|
|
3277
|
-
const elementType = typeNode.getElementTypeNode();
|
|
3278
|
-
return `Array<${resolveTypeNodeToString(elementType, sourceFile, project, depth)}>`;
|
|
3279
|
-
}
|
|
3280
|
-
if (import_ts_morph9.Node.isUnionTypeNode(typeNode)) {
|
|
3281
|
-
return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth)).join(" | ");
|
|
3282
|
-
}
|
|
3283
|
-
if (import_ts_morph9.Node.isIntersectionTypeNode(typeNode)) {
|
|
3284
|
-
return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth)).join(" & ");
|
|
3285
|
-
}
|
|
3286
|
-
if (import_ts_morph9.Node.isParenthesizedTypeNode(typeNode)) {
|
|
2684
|
+
if (import_ts_morph7.Node.isParenthesizedTypeNode(typeNode)) {
|
|
3287
2685
|
return `(${resolveTypeNodeToString(typeNode.getTypeNode(), sourceFile, project, depth)})`;
|
|
3288
2686
|
}
|
|
3289
|
-
if (
|
|
2687
|
+
if (import_ts_morph7.Node.isTypeReference(typeNode)) {
|
|
3290
2688
|
const typeName = typeNode.getTypeName();
|
|
3291
|
-
const name =
|
|
2689
|
+
const name = import_ts_morph7.Node.isIdentifier(typeName) ? typeName.getText() : typeNode.getText();
|
|
3292
2690
|
if (name === "string" || name === "number" || name === "boolean") return name;
|
|
3293
2691
|
if (name === "Date") return "string";
|
|
3294
2692
|
if (name === "unknown" || name === "any" || name === "void") return "unknown";
|
|
3295
2693
|
if (name === "StreamableFile" || name === "Observable" || name === "ReadableStream")
|
|
3296
2694
|
return "unknown";
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
if (typeArgs.length > 0 && firstTypeArg !== void 0) {
|
|
3301
|
-
return resolveTypeNodeToString(firstTypeArg, sourceFile, project, depth);
|
|
3302
|
-
}
|
|
3303
|
-
return "unknown";
|
|
2695
|
+
const wrapperMode = WRAPPER_TYPES[name];
|
|
2696
|
+
if (wrapperMode) {
|
|
2697
|
+
return unwrapFirstTypeArg(typeNode, sourceFile, project, depth, wrapperMode);
|
|
3304
2698
|
}
|
|
3305
|
-
if (name
|
|
3306
|
-
const typeArgs = typeNode.getTypeArguments();
|
|
3307
|
-
const firstTypeArg = typeArgs[0];
|
|
3308
|
-
if (typeArgs.length > 0 && firstTypeArg !== void 0) {
|
|
3309
|
-
return `Array<${resolveTypeNodeToString(firstTypeArg, sourceFile, project, depth)}>`;
|
|
3310
|
-
}
|
|
3311
|
-
return "Array<unknown>";
|
|
3312
|
-
}
|
|
3313
|
-
if (name === "Opt" || name === "Loaded") {
|
|
3314
|
-
const typeArgs = typeNode.getTypeArguments();
|
|
3315
|
-
const firstTypeArg = typeArgs[0];
|
|
3316
|
-
if (typeArgs.length > 0 && firstTypeArg !== void 0) {
|
|
3317
|
-
return resolveTypeNodeToString(firstTypeArg, sourceFile, project, depth);
|
|
3318
|
-
}
|
|
3319
|
-
return "unknown";
|
|
3320
|
-
}
|
|
3321
|
-
if (name === "Array") {
|
|
3322
|
-
const typeArgs = typeNode.getTypeArguments();
|
|
3323
|
-
const firstTypeArg = typeArgs[0];
|
|
3324
|
-
if (typeArgs.length > 0 && firstTypeArg !== void 0) {
|
|
3325
|
-
return `Array<${resolveTypeNodeToString(firstTypeArg, sourceFile, project, depth)}>`;
|
|
3326
|
-
}
|
|
3327
|
-
return "Array<unknown>";
|
|
3328
|
-
}
|
|
3329
|
-
if (["Record", "Omit", "Pick", "Partial", "Required", "Readonly", "Map", "Set"].includes(name)) {
|
|
2699
|
+
if (PASSTHROUGH_UTILITY.has(name)) {
|
|
3330
2700
|
return typeNode.getText();
|
|
3331
2701
|
}
|
|
3332
|
-
if (name === "Promise") {
|
|
3333
|
-
const typeArgs = typeNode.getTypeArguments();
|
|
3334
|
-
const firstTypeArg = typeArgs[0];
|
|
3335
|
-
if (typeArgs.length > 0 && firstTypeArg !== void 0) {
|
|
3336
|
-
return resolveTypeNodeToString(firstTypeArg, sourceFile, project, depth);
|
|
3337
|
-
}
|
|
3338
|
-
return "unknown";
|
|
3339
|
-
}
|
|
3340
2702
|
const resolved = findType(name, sourceFile, project);
|
|
3341
2703
|
if (resolved) {
|
|
3342
2704
|
return expandTypeDecl(resolved, project, depth - 1);
|
|
@@ -3345,13 +2707,22 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth) {
|
|
|
3345
2707
|
return "unknown";
|
|
3346
2708
|
}
|
|
3347
2709
|
const kind = typeNode.getKind();
|
|
3348
|
-
if (kind ===
|
|
3349
|
-
if (kind ===
|
|
3350
|
-
if (kind ===
|
|
3351
|
-
if (kind ===
|
|
3352
|
-
if (kind ===
|
|
2710
|
+
if (kind === import_ts_morph7.SyntaxKind.StringKeyword) return "string";
|
|
2711
|
+
if (kind === import_ts_morph7.SyntaxKind.NumberKeyword) return "number";
|
|
2712
|
+
if (kind === import_ts_morph7.SyntaxKind.BooleanKeyword) return "boolean";
|
|
2713
|
+
if (kind === import_ts_morph7.SyntaxKind.UnknownKeyword) return "unknown";
|
|
2714
|
+
if (kind === import_ts_morph7.SyntaxKind.AnyKeyword) return "unknown";
|
|
3353
2715
|
return typeNode.getText();
|
|
3354
2716
|
}
|
|
2717
|
+
function unwrapFirstTypeArg(typeNode, sourceFile, project, depth, mode) {
|
|
2718
|
+
const typeArgs = typeNode.getTypeArguments();
|
|
2719
|
+
const firstTypeArg = typeArgs[0];
|
|
2720
|
+
if (typeArgs.length > 0 && firstTypeArg !== void 0) {
|
|
2721
|
+
const inner = resolveTypeNodeToString(firstTypeArg, sourceFile, project, depth);
|
|
2722
|
+
return mode === "arrayOf" ? `Array<${inner}>` : inner;
|
|
2723
|
+
}
|
|
2724
|
+
return mode === "arrayOf" ? "Array<unknown>" : "unknown";
|
|
2725
|
+
}
|
|
3355
2726
|
function expandTypeDecl(result, project, depth) {
|
|
3356
2727
|
if (depth < 0) return "unknown";
|
|
3357
2728
|
switch (result.kind) {
|
|
@@ -3417,7 +2788,7 @@ function extractParamsType(method, sourceFile, project) {
|
|
|
3417
2788
|
const paramArgs = paramDecorator.getArguments();
|
|
3418
2789
|
if (paramArgs.length === 0) continue;
|
|
3419
2790
|
const nameArg = paramArgs[0];
|
|
3420
|
-
if (!
|
|
2791
|
+
if (!import_ts_morph7.Node.isStringLiteral(nameArg)) continue;
|
|
3421
2792
|
const paramName = nameArg.getLiteralValue();
|
|
3422
2793
|
const typeNode = param.getTypeNode();
|
|
3423
2794
|
const paramType = typeNode ? resolveTypeNodeToString(typeNode, sourceFile, project, 3) : "string";
|
|
@@ -3430,13 +2801,13 @@ function extractResponseType(method, sourceFile, project) {
|
|
|
3430
2801
|
if (apiResponseDecorator) {
|
|
3431
2802
|
const args = apiResponseDecorator.getArguments();
|
|
3432
2803
|
const optsArg = args[0];
|
|
3433
|
-
if (optsArg &&
|
|
2804
|
+
if (optsArg && import_ts_morph7.Node.isObjectLiteralExpression(optsArg)) {
|
|
3434
2805
|
for (const prop of optsArg.getProperties()) {
|
|
3435
|
-
if (!
|
|
2806
|
+
if (!import_ts_morph7.Node.isPropertyAssignment(prop)) continue;
|
|
3436
2807
|
if (prop.getName() !== "type") continue;
|
|
3437
2808
|
const val = prop.getInitializer();
|
|
3438
2809
|
if (!val) continue;
|
|
3439
|
-
if (
|
|
2810
|
+
if (import_ts_morph7.Node.isArrayLiteralExpression(val)) {
|
|
3440
2811
|
const elements = val.getElements();
|
|
3441
2812
|
const firstEl = elements[0];
|
|
3442
2813
|
if (elements.length > 0 && firstEl !== void 0) {
|
|
@@ -3456,7 +2827,7 @@ function extractResponseType(method, sourceFile, project) {
|
|
|
3456
2827
|
return "unknown";
|
|
3457
2828
|
}
|
|
3458
2829
|
function resolveIdentifierToClassType(node, sourceFile, project, depth) {
|
|
3459
|
-
if (!
|
|
2830
|
+
if (!import_ts_morph7.Node.isIdentifier(node)) return "unknown";
|
|
3460
2831
|
const name = node.getText();
|
|
3461
2832
|
const resolved = findType(name, sourceFile, project);
|
|
3462
2833
|
if (resolved) {
|
|
@@ -3503,11 +2874,11 @@ function extractDtoContract(method, sourceFile, project) {
|
|
|
3503
2874
|
if (apiResp) {
|
|
3504
2875
|
const args = apiResp.getArguments();
|
|
3505
2876
|
const optsArg = args[0];
|
|
3506
|
-
if (optsArg &&
|
|
2877
|
+
if (optsArg && import_ts_morph7.Node.isObjectLiteralExpression(optsArg)) {
|
|
3507
2878
|
for (const prop of optsArg.getProperties()) {
|
|
3508
|
-
if (
|
|
2879
|
+
if (import_ts_morph7.Node.isPropertyAssignment(prop) && prop.getName() === "type") {
|
|
3509
2880
|
const val = prop.getInitializer();
|
|
3510
|
-
if (val &&
|
|
2881
|
+
if (val && import_ts_morph7.Node.isIdentifier(val)) {
|
|
3511
2882
|
const name = val.getText();
|
|
3512
2883
|
const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
|
|
3513
2884
|
if (localDecl?.isExported()) {
|
|
@@ -3524,60 +2895,243 @@ function extractDtoContract(method, sourceFile, project) {
|
|
|
3524
2895
|
}
|
|
3525
2896
|
}
|
|
3526
2897
|
}
|
|
3527
|
-
let bodyZodText = null;
|
|
3528
|
-
let queryZodText = null;
|
|
3529
2898
|
let bodySchema = null;
|
|
3530
2899
|
let querySchema = null;
|
|
3531
|
-
const formNested = {};
|
|
3532
2900
|
const formWarnings = [];
|
|
3533
2901
|
const bodyClass = resolveParamClass(method, "Body", sourceFile, project);
|
|
3534
2902
|
if (bodyClass) {
|
|
3535
|
-
const result = extractZodFromDto(bodyClass.decl, bodyClass.file, project);
|
|
3536
|
-
bodyZodText = result.schemaText;
|
|
3537
|
-
for (const [k, v] of result.namedNestedSchemas) formNested[k] = v;
|
|
3538
|
-
formWarnings.push(...result.warnings);
|
|
3539
2903
|
bodySchema = extractSchemaFromDto(bodyClass.decl, bodyClass.file, project);
|
|
2904
|
+
formWarnings.push(...bodySchema.warnings);
|
|
2905
|
+
}
|
|
2906
|
+
const queryClass = resolveParamClass(method, "Query", sourceFile, project);
|
|
2907
|
+
if (queryClass) {
|
|
2908
|
+
querySchema = extractSchemaFromDto(queryClass.decl, queryClass.file, project);
|
|
2909
|
+
formWarnings.push(...querySchema.warnings);
|
|
2910
|
+
}
|
|
2911
|
+
return {
|
|
2912
|
+
query,
|
|
2913
|
+
body,
|
|
2914
|
+
response,
|
|
2915
|
+
params: paramsType,
|
|
2916
|
+
queryRef,
|
|
2917
|
+
bodyRef,
|
|
2918
|
+
responseRef,
|
|
2919
|
+
filterFields: filterInfo?.fieldNames ?? null,
|
|
2920
|
+
filterFieldTypes: filterInfo?.fieldTypes ?? null,
|
|
2921
|
+
filterSource: filterInfo?.source ?? null,
|
|
2922
|
+
formWarnings,
|
|
2923
|
+
bodySchema,
|
|
2924
|
+
querySchema
|
|
2925
|
+
};
|
|
2926
|
+
}
|
|
2927
|
+
function resolveParamClass(method, decoratorName, sourceFile, project) {
|
|
2928
|
+
for (const param of method.getParameters()) {
|
|
2929
|
+
if (!param.getDecorators().some((d) => d.getName() === decoratorName)) continue;
|
|
2930
|
+
const typeNode = param.getTypeNode();
|
|
2931
|
+
if (!typeNode) continue;
|
|
2932
|
+
const text = typeNode.getText().replace(/\[\]$/, "");
|
|
2933
|
+
if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(text)) continue;
|
|
2934
|
+
const resolved = findType(text, sourceFile, project);
|
|
2935
|
+
if (resolved && resolved.kind === "class") {
|
|
2936
|
+
return { decl: resolved.decl, file: resolved.file };
|
|
2937
|
+
}
|
|
2938
|
+
}
|
|
2939
|
+
return null;
|
|
2940
|
+
}
|
|
2941
|
+
|
|
2942
|
+
// src/discovery/zod-ast-to-ts.ts
|
|
2943
|
+
var import_ts_morph8 = require("ts-morph");
|
|
2944
|
+
function zodAstToTs(node) {
|
|
2945
|
+
if (!import_ts_morph8.Node.isCallExpression(node)) return "unknown";
|
|
2946
|
+
const expr = node.getExpression();
|
|
2947
|
+
if (import_ts_morph8.Node.isPropertyAccessExpression(expr)) {
|
|
2948
|
+
const methodName = expr.getName();
|
|
2949
|
+
const receiver = expr.getExpression();
|
|
2950
|
+
if (methodName === "optional") {
|
|
2951
|
+
return `${zodAstToTs(receiver)} | undefined`;
|
|
2952
|
+
}
|
|
2953
|
+
if (methodName === "nullable") {
|
|
2954
|
+
return `${zodAstToTs(receiver)} | null`;
|
|
2955
|
+
}
|
|
2956
|
+
const args = node.getArguments();
|
|
2957
|
+
switch (methodName) {
|
|
2958
|
+
case "string":
|
|
2959
|
+
return "string";
|
|
2960
|
+
case "number":
|
|
2961
|
+
return "number";
|
|
2962
|
+
case "boolean":
|
|
2963
|
+
return "boolean";
|
|
2964
|
+
case "unknown":
|
|
2965
|
+
return "unknown";
|
|
2966
|
+
case "any":
|
|
2967
|
+
return "unknown";
|
|
2968
|
+
case "literal": {
|
|
2969
|
+
const lit = args[0];
|
|
2970
|
+
if (!lit) return "unknown";
|
|
2971
|
+
if (import_ts_morph8.Node.isStringLiteral(lit)) return JSON.stringify(lit.getLiteralValue());
|
|
2972
|
+
if (import_ts_morph8.Node.isNumericLiteral(lit)) return lit.getLiteralValue().toString();
|
|
2973
|
+
if (lit.getKind() === import_ts_morph8.SyntaxKind.TrueKeyword) return "true";
|
|
2974
|
+
if (lit.getKind() === import_ts_morph8.SyntaxKind.FalseKeyword) return "false";
|
|
2975
|
+
return "unknown";
|
|
2976
|
+
}
|
|
2977
|
+
case "enum": {
|
|
2978
|
+
const arrArg = args[0];
|
|
2979
|
+
if (!arrArg || !import_ts_morph8.Node.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
2980
|
+
const members = arrArg.getElements().map(
|
|
2981
|
+
(el) => import_ts_morph8.Node.isStringLiteral(el) ? JSON.stringify(el.getLiteralValue()) : "unknown"
|
|
2982
|
+
);
|
|
2983
|
+
return members.join(" | ");
|
|
2984
|
+
}
|
|
2985
|
+
case "array": {
|
|
2986
|
+
const inner = args[0];
|
|
2987
|
+
if (!inner) return "unknown";
|
|
2988
|
+
return `Array<${zodAstToTs(inner)}>`;
|
|
2989
|
+
}
|
|
2990
|
+
case "object": {
|
|
2991
|
+
const objArg = args[0];
|
|
2992
|
+
if (!objArg || !import_ts_morph8.Node.isObjectLiteralExpression(objArg)) return "unknown";
|
|
2993
|
+
const lines = [];
|
|
2994
|
+
for (const prop of objArg.getProperties()) {
|
|
2995
|
+
if (!import_ts_morph8.Node.isPropertyAssignment(prop)) continue;
|
|
2996
|
+
const key = prop.getName();
|
|
2997
|
+
const valNode = prop.getInitializer();
|
|
2998
|
+
if (!valNode) continue;
|
|
2999
|
+
const tsType = zodAstToTs(valNode);
|
|
3000
|
+
const isOpt = isOptionalChain(valNode);
|
|
3001
|
+
lines.push(`${key}${isOpt ? "?" : ""}: ${tsType}`);
|
|
3002
|
+
}
|
|
3003
|
+
return `{ ${lines.join("; ")} }`;
|
|
3004
|
+
}
|
|
3005
|
+
case "union": {
|
|
3006
|
+
const arrArg = args[0];
|
|
3007
|
+
if (!arrArg || !import_ts_morph8.Node.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
3008
|
+
return arrArg.getElements().map(zodAstToTs).join(" | ");
|
|
3009
|
+
}
|
|
3010
|
+
case "record": {
|
|
3011
|
+
const valArg = args.length === 1 ? args[0] : args[1];
|
|
3012
|
+
if (!valArg) return "unknown";
|
|
3013
|
+
return `Record<string, ${zodAstToTs(valArg)}>`;
|
|
3014
|
+
}
|
|
3015
|
+
case "tuple": {
|
|
3016
|
+
const arrArg = args[0];
|
|
3017
|
+
if (!arrArg || !import_ts_morph8.Node.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
3018
|
+
return `[${arrArg.getElements().map(zodAstToTs).join(", ")}]`;
|
|
3019
|
+
}
|
|
3020
|
+
default:
|
|
3021
|
+
return "unknown";
|
|
3022
|
+
}
|
|
3023
|
+
}
|
|
3024
|
+
return "unknown";
|
|
3025
|
+
}
|
|
3026
|
+
function isOptionalChain(node) {
|
|
3027
|
+
if (!import_ts_morph8.Node.isCallExpression(node)) return false;
|
|
3028
|
+
const expr = node.getExpression();
|
|
3029
|
+
return import_ts_morph8.Node.isPropertyAccessExpression(expr) && expr.getName() === "optional";
|
|
3030
|
+
}
|
|
3031
|
+
function parseDefineContractCall(callExpr) {
|
|
3032
|
+
if (!import_ts_morph8.Node.isCallExpression(callExpr)) return null;
|
|
3033
|
+
const callee = callExpr.getExpression();
|
|
3034
|
+
const calleeName = import_ts_morph8.Node.isIdentifier(callee) ? callee.getText() : import_ts_morph8.Node.isPropertyAccessExpression(callee) ? callee.getName() : "";
|
|
3035
|
+
if (calleeName !== "defineContract") return null;
|
|
3036
|
+
const args = callExpr.getArguments();
|
|
3037
|
+
const optsArg = args[0];
|
|
3038
|
+
if (!optsArg || !import_ts_morph8.Node.isObjectLiteralExpression(optsArg)) return null;
|
|
3039
|
+
let query = null;
|
|
3040
|
+
let body = null;
|
|
3041
|
+
let response = "unknown";
|
|
3042
|
+
let bodyZodText = null;
|
|
3043
|
+
let queryZodText = null;
|
|
3044
|
+
for (const prop of optsArg.getProperties()) {
|
|
3045
|
+
if (!import_ts_morph8.Node.isPropertyAssignment(prop)) continue;
|
|
3046
|
+
const propName = prop.getName();
|
|
3047
|
+
const val = prop.getInitializer();
|
|
3048
|
+
if (!val) continue;
|
|
3049
|
+
if (propName === "query") {
|
|
3050
|
+
query = zodAstToTs(val);
|
|
3051
|
+
queryZodText = val.getText();
|
|
3052
|
+
} else if (propName === "body") {
|
|
3053
|
+
body = zodAstToTs(val);
|
|
3054
|
+
bodyZodText = val.getText();
|
|
3055
|
+
} else if (propName === "response") {
|
|
3056
|
+
response = zodAstToTs(val);
|
|
3057
|
+
}
|
|
3058
|
+
}
|
|
3059
|
+
return { query, body, response, bodyZodText, queryZodText };
|
|
3060
|
+
}
|
|
3061
|
+
|
|
3062
|
+
// src/discovery/contracts-fast.ts
|
|
3063
|
+
async function discoverContractsFast(opts) {
|
|
3064
|
+
const { cwd, glob, tsconfig } = opts;
|
|
3065
|
+
const tsconfigPath = tsconfig ? (0, import_node_path12.resolve)(tsconfig) : (0, import_node_path12.join)(cwd, "tsconfig.json");
|
|
3066
|
+
let project;
|
|
3067
|
+
try {
|
|
3068
|
+
project = new import_ts_morph9.Project({
|
|
3069
|
+
tsConfigFilePath: tsconfigPath,
|
|
3070
|
+
skipAddingFilesFromTsConfig: true,
|
|
3071
|
+
skipLoadingLibFiles: true,
|
|
3072
|
+
skipFileDependencyResolution: true
|
|
3073
|
+
});
|
|
3074
|
+
} catch {
|
|
3075
|
+
project = new import_ts_morph9.Project({
|
|
3076
|
+
skipAddingFilesFromTsConfig: true,
|
|
3077
|
+
skipLoadingLibFiles: true,
|
|
3078
|
+
skipFileDependencyResolution: true,
|
|
3079
|
+
compilerOptions: {
|
|
3080
|
+
allowJs: true,
|
|
3081
|
+
resolveJsonModule: false,
|
|
3082
|
+
strict: false
|
|
3083
|
+
}
|
|
3084
|
+
});
|
|
3085
|
+
}
|
|
3086
|
+
const files = await (0, import_fast_glob2.default)(glob, { cwd, absolute: true, onlyFiles: true });
|
|
3087
|
+
for (const f of files) {
|
|
3088
|
+
project.addSourceFileAtPath(f);
|
|
3540
3089
|
}
|
|
3541
|
-
const
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3090
|
+
const routes = [];
|
|
3091
|
+
setDiscoveryContext(project, {
|
|
3092
|
+
projectRoot: cwd,
|
|
3093
|
+
tsconfigPaths: loadTsconfigPaths(tsconfigPath)
|
|
3094
|
+
});
|
|
3095
|
+
for (const sourceFile of project.getSourceFiles()) {
|
|
3096
|
+
routes.push(...extractFromSourceFile(sourceFile, project));
|
|
3548
3097
|
}
|
|
3549
|
-
return
|
|
3550
|
-
query,
|
|
3551
|
-
body,
|
|
3552
|
-
response,
|
|
3553
|
-
params: paramsType,
|
|
3554
|
-
queryRef,
|
|
3555
|
-
bodyRef,
|
|
3556
|
-
responseRef,
|
|
3557
|
-
filterFields: filterInfo?.fieldNames ?? null,
|
|
3558
|
-
filterFieldTypes: filterInfo?.fieldTypes ?? null,
|
|
3559
|
-
filterSource: filterInfo?.source ?? null,
|
|
3560
|
-
bodyZodText,
|
|
3561
|
-
queryZodText,
|
|
3562
|
-
formNestedSchemas: Object.keys(formNested).length > 0 ? formNested : null,
|
|
3563
|
-
formWarnings,
|
|
3564
|
-
bodySchema,
|
|
3565
|
-
querySchema
|
|
3566
|
-
};
|
|
3098
|
+
return routes;
|
|
3567
3099
|
}
|
|
3568
|
-
function
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(text)) continue;
|
|
3575
|
-
const resolved = findType(text, sourceFile, project);
|
|
3576
|
-
if (resolved && resolved.kind === "class") {
|
|
3577
|
-
return { decl: resolved.decl, file: resolved.file };
|
|
3578
|
-
}
|
|
3100
|
+
function decoratorStringArg(decoratorExpr) {
|
|
3101
|
+
if (!decoratorExpr) return void 0;
|
|
3102
|
+
if (import_ts_morph9.Node.isStringLiteral(decoratorExpr)) return decoratorExpr.getLiteralValue();
|
|
3103
|
+
if (import_ts_morph9.Node.isArrayLiteralExpression(decoratorExpr)) {
|
|
3104
|
+
const first = decoratorExpr.getElements()[0];
|
|
3105
|
+
if (first && import_ts_morph9.Node.isStringLiteral(first)) return first.getLiteralValue();
|
|
3579
3106
|
}
|
|
3580
|
-
return
|
|
3107
|
+
return void 0;
|
|
3108
|
+
}
|
|
3109
|
+
function deriveClassSegment(className) {
|
|
3110
|
+
const noSuffix = className.replace(/Controller$/, "");
|
|
3111
|
+
if (!noSuffix) {
|
|
3112
|
+
throw new Error(
|
|
3113
|
+
`Controller class name "${className}" derives empty route segment after stripping "Controller". Add an @As(...) override at the class level.`
|
|
3114
|
+
);
|
|
3115
|
+
}
|
|
3116
|
+
return noSuffix.charAt(0).toLowerCase() + noSuffix.slice(1);
|
|
3117
|
+
}
|
|
3118
|
+
function resolveRouteName(className, methodName, classAs, methodAs) {
|
|
3119
|
+
const classPortion = classAs ?? deriveClassSegment(className);
|
|
3120
|
+
const methodPortion = methodAs ?? methodName;
|
|
3121
|
+
return `${classPortion}.${methodPortion}`;
|
|
3122
|
+
}
|
|
3123
|
+
function joinPaths(prefix, suffix) {
|
|
3124
|
+
if (!prefix && !suffix) return "/";
|
|
3125
|
+
if (!prefix) return suffix.startsWith("/") ? suffix : `/${suffix}`;
|
|
3126
|
+
if (!suffix) return prefix.startsWith("/") ? prefix : `/${prefix}`;
|
|
3127
|
+
const p = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
|
|
3128
|
+
const s = suffix.startsWith("/") ? suffix : `/${suffix}`;
|
|
3129
|
+
const combined = p + s;
|
|
3130
|
+
return combined === "" ? "/" : combined;
|
|
3131
|
+
}
|
|
3132
|
+
function extractParams(path) {
|
|
3133
|
+
const matches = path.matchAll(/:(\w+)/g);
|
|
3134
|
+
return Array.from(matches).map((m) => ({ name: m[1], source: "path" }));
|
|
3581
3135
|
}
|
|
3582
3136
|
var HTTP_METHOD_DECORATORS = {
|
|
3583
3137
|
Get: "GET",
|
|
@@ -3589,176 +3143,186 @@ var HTTP_METHOD_DECORATORS = {
|
|
|
3589
3143
|
Head: "HEAD",
|
|
3590
3144
|
All: "ALL"
|
|
3591
3145
|
};
|
|
3146
|
+
function resolveVerb(method) {
|
|
3147
|
+
for (const [decoratorName, verb] of Object.entries(HTTP_METHOD_DECORATORS)) {
|
|
3148
|
+
const httpDecorator = method.getDecorator(decoratorName);
|
|
3149
|
+
if (httpDecorator) {
|
|
3150
|
+
const httpArgs = httpDecorator.getArguments();
|
|
3151
|
+
const pathArg = httpArgs[0];
|
|
3152
|
+
return { httpMethod: verb, handlerPath: decoratorStringArg(pathArg) ?? "" };
|
|
3153
|
+
}
|
|
3154
|
+
}
|
|
3155
|
+
return null;
|
|
3156
|
+
}
|
|
3157
|
+
function readAsDecorator(node, label) {
|
|
3158
|
+
const asDecorator = node.getDecorator("As");
|
|
3159
|
+
if (!asDecorator) return void 0;
|
|
3160
|
+
const asName = decoratorStringArg(asDecorator.getArguments()[0]);
|
|
3161
|
+
if (!asName) {
|
|
3162
|
+
throw new Error(`@As decorator on ${label} must have a non-empty string argument.`);
|
|
3163
|
+
}
|
|
3164
|
+
return asName;
|
|
3165
|
+
}
|
|
3166
|
+
function buildRoute(args) {
|
|
3167
|
+
const {
|
|
3168
|
+
className,
|
|
3169
|
+
methodName,
|
|
3170
|
+
resolvedMethod,
|
|
3171
|
+
combinedPath,
|
|
3172
|
+
classAs,
|
|
3173
|
+
methodAs,
|
|
3174
|
+
sourceFile,
|
|
3175
|
+
seenNames,
|
|
3176
|
+
contractSource
|
|
3177
|
+
} = args;
|
|
3178
|
+
const routeName = resolveRouteName(className, methodName, classAs, methodAs);
|
|
3179
|
+
const qualifiedRef = `${className}.${methodName}`;
|
|
3180
|
+
const existing = seenNames.get(routeName);
|
|
3181
|
+
if (existing !== void 0) {
|
|
3182
|
+
throw new Error(
|
|
3183
|
+
`Route name collision: "${routeName}" is used by both "${existing}" and "${qualifiedRef}". Use @As(...) to give one of them a unique name.`
|
|
3184
|
+
);
|
|
3185
|
+
}
|
|
3186
|
+
seenNames.set(routeName, qualifiedRef);
|
|
3187
|
+
return {
|
|
3188
|
+
method: resolvedMethod,
|
|
3189
|
+
path: combinedPath,
|
|
3190
|
+
name: routeName,
|
|
3191
|
+
params: extractParams(combinedPath),
|
|
3192
|
+
controllerRef: { className, methodName, filePath: sourceFile.getFilePath() },
|
|
3193
|
+
contract: { contractSource }
|
|
3194
|
+
};
|
|
3195
|
+
}
|
|
3196
|
+
function extractContractRoute(args) {
|
|
3197
|
+
const { cls, method, applyContractDecorator, verb, prefix, className, sourceFile, seenNames } = args;
|
|
3198
|
+
const firstDecoratorArg = applyContractDecorator.getArguments()[0];
|
|
3199
|
+
if (!firstDecoratorArg) return null;
|
|
3200
|
+
let contractDef = null;
|
|
3201
|
+
let bodyZodRef = null;
|
|
3202
|
+
let queryZodRef = null;
|
|
3203
|
+
if (import_ts_morph9.Node.isCallExpression(firstDecoratorArg)) {
|
|
3204
|
+
contractDef = parseDefineContractCall(firstDecoratorArg);
|
|
3205
|
+
} else if (import_ts_morph9.Node.isIdentifier(firstDecoratorArg)) {
|
|
3206
|
+
const identName = firstDecoratorArg.getText();
|
|
3207
|
+
const varDecl = sourceFile.getVariableDeclaration(identName);
|
|
3208
|
+
if (!varDecl) {
|
|
3209
|
+
console.warn(
|
|
3210
|
+
`[nestjs-codegen/fast] Cannot resolve '${identName}' in ${sourceFile.getFilePath()} (cross-file imports are out-of-scope for v1) \u2014 skipping`
|
|
3211
|
+
);
|
|
3212
|
+
return null;
|
|
3213
|
+
}
|
|
3214
|
+
const initializer = varDecl.getInitializer();
|
|
3215
|
+
if (!initializer) return null;
|
|
3216
|
+
contractDef = parseDefineContractCall(initializer);
|
|
3217
|
+
if (contractDef && varDecl.isExported()) {
|
|
3218
|
+
const filePath = sourceFile.getFilePath();
|
|
3219
|
+
if (contractDef.body !== null) {
|
|
3220
|
+
bodyZodRef = { name: `${identName}.body`, filePath };
|
|
3221
|
+
}
|
|
3222
|
+
if (contractDef.query !== null) {
|
|
3223
|
+
queryZodRef = { name: `${identName}.query`, filePath };
|
|
3224
|
+
}
|
|
3225
|
+
}
|
|
3226
|
+
} else {
|
|
3227
|
+
console.warn(
|
|
3228
|
+
`[nestjs-codegen/fast] @ApplyContract arg is not an identifier or call expression in ${sourceFile.getFilePath()} \u2014 skipping`
|
|
3229
|
+
);
|
|
3230
|
+
return null;
|
|
3231
|
+
}
|
|
3232
|
+
if (!contractDef) return null;
|
|
3233
|
+
if (!verb) return null;
|
|
3234
|
+
const resolvedPath = joinPaths(prefix, verb.handlerPath);
|
|
3235
|
+
const methodName = method.getName();
|
|
3236
|
+
const classAs = readAsDecorator(cls, `class ${className}`);
|
|
3237
|
+
const methodAs = readAsDecorator(method, `${className}.${methodName}`);
|
|
3238
|
+
return buildRoute({
|
|
3239
|
+
className,
|
|
3240
|
+
methodName,
|
|
3241
|
+
resolvedMethod: verb.httpMethod,
|
|
3242
|
+
combinedPath: resolvedPath,
|
|
3243
|
+
classAs,
|
|
3244
|
+
methodAs,
|
|
3245
|
+
sourceFile,
|
|
3246
|
+
seenNames,
|
|
3247
|
+
contractSource: {
|
|
3248
|
+
query: contractDef.query,
|
|
3249
|
+
body: contractDef.body,
|
|
3250
|
+
response: contractDef.response,
|
|
3251
|
+
// Path A: capture both the importable ref and the raw text. The emitter
|
|
3252
|
+
// prefers inlining the text (client-safe — re-exporting from a controller
|
|
3253
|
+
// would drag server-only deps into the client bundle).
|
|
3254
|
+
bodyZodRef,
|
|
3255
|
+
bodyZodText: contractDef.bodyZodText,
|
|
3256
|
+
queryZodRef,
|
|
3257
|
+
queryZodText: contractDef.queryZodText
|
|
3258
|
+
}
|
|
3259
|
+
});
|
|
3260
|
+
}
|
|
3261
|
+
function extractDtoRoute(args) {
|
|
3262
|
+
const { cls, method, verb, prefix, className, sourceFile, project, seenNames } = args;
|
|
3263
|
+
if (!verb) return null;
|
|
3264
|
+
const combined = joinPaths(prefix, verb.handlerPath);
|
|
3265
|
+
const methodName = method.getName();
|
|
3266
|
+
const classAs = readAsDecorator(cls, `class ${className}`);
|
|
3267
|
+
const methodAs = readAsDecorator(method, `${className}.${methodName}`);
|
|
3268
|
+
const dtoContract = extractDtoContract(method, sourceFile, project);
|
|
3269
|
+
return buildRoute({
|
|
3270
|
+
className,
|
|
3271
|
+
methodName,
|
|
3272
|
+
resolvedMethod: verb.httpMethod,
|
|
3273
|
+
combinedPath: combined,
|
|
3274
|
+
classAs,
|
|
3275
|
+
methodAs,
|
|
3276
|
+
sourceFile,
|
|
3277
|
+
seenNames,
|
|
3278
|
+
contractSource: {
|
|
3279
|
+
query: dtoContract?.query ?? null,
|
|
3280
|
+
body: dtoContract?.body ?? null,
|
|
3281
|
+
response: dtoContract?.response ?? "unknown",
|
|
3282
|
+
queryRef: dtoContract?.queryRef ?? null,
|
|
3283
|
+
bodyRef: dtoContract?.bodyRef ?? null,
|
|
3284
|
+
responseRef: dtoContract?.responseRef ?? null,
|
|
3285
|
+
filterFields: dtoContract?.filterFields ?? null,
|
|
3286
|
+
filterFieldTypes: dtoContract?.filterFieldTypes ?? null,
|
|
3287
|
+
filterSource: dtoContract?.filterSource ?? null,
|
|
3288
|
+
formWarnings: dtoContract?.formWarnings ?? [],
|
|
3289
|
+
bodySchema: dtoContract?.bodySchema ?? null,
|
|
3290
|
+
querySchema: dtoContract?.querySchema ?? null
|
|
3291
|
+
}
|
|
3292
|
+
});
|
|
3293
|
+
}
|
|
3592
3294
|
function extractFromSourceFile(sourceFile, project) {
|
|
3593
3295
|
const routes = [];
|
|
3594
3296
|
const seenNames = /* @__PURE__ */ new Map();
|
|
3595
|
-
const
|
|
3596
|
-
for (const cls of classes) {
|
|
3297
|
+
for (const cls of sourceFile.getClasses()) {
|
|
3597
3298
|
const controllerDecorator = cls.getDecorator("Controller");
|
|
3598
3299
|
if (!controllerDecorator) continue;
|
|
3599
|
-
const
|
|
3600
|
-
const
|
|
3601
|
-
const prefix = decoratorStringArg(firstArg3) ?? "";
|
|
3300
|
+
const firstArg2 = controllerDecorator.getArguments()[0];
|
|
3301
|
+
const prefix = decoratorStringArg(firstArg2) ?? "";
|
|
3602
3302
|
const className = cls.getName() ?? "Unknown";
|
|
3603
3303
|
for (const method of cls.getMethods()) {
|
|
3604
|
-
|
|
3605
|
-
let handlerPath = "";
|
|
3606
|
-
for (const [decoratorName, verb] of Object.entries(HTTP_METHOD_DECORATORS)) {
|
|
3607
|
-
const httpDecorator = method.getDecorator(decoratorName);
|
|
3608
|
-
if (httpDecorator) {
|
|
3609
|
-
httpMethod = verb;
|
|
3610
|
-
const httpArgs = httpDecorator.getArguments();
|
|
3611
|
-
const pathArg = httpArgs[0];
|
|
3612
|
-
handlerPath = decoratorStringArg(pathArg) ?? "";
|
|
3613
|
-
break;
|
|
3614
|
-
}
|
|
3615
|
-
}
|
|
3304
|
+
const verb = resolveVerb(method);
|
|
3616
3305
|
const applyContractDecorator = method.getDecorator("ApplyContract");
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
contractDef = parseDefineContractCall(initializer);
|
|
3638
|
-
if (contractDef && varDecl.isExported()) {
|
|
3639
|
-
const filePath = sourceFile.getFilePath();
|
|
3640
|
-
if (contractDef.body !== null) {
|
|
3641
|
-
bodyZodRef = { name: `${identName}.body`, filePath };
|
|
3642
|
-
}
|
|
3643
|
-
if (contractDef.query !== null) {
|
|
3644
|
-
queryZodRef = { name: `${identName}.query`, filePath };
|
|
3645
|
-
}
|
|
3646
|
-
}
|
|
3647
|
-
} else {
|
|
3648
|
-
console.warn(
|
|
3649
|
-
`[nestjs-codegen/fast] @ApplyContract arg is not an identifier or call expression in ${sourceFile.getFilePath()} \u2014 skipping`
|
|
3650
|
-
);
|
|
3651
|
-
continue;
|
|
3652
|
-
}
|
|
3653
|
-
if (!contractDef) continue;
|
|
3654
|
-
if (!httpMethod) continue;
|
|
3655
|
-
const resolvedMethod = httpMethod;
|
|
3656
|
-
const resolvedPath = joinPaths(prefix, handlerPath);
|
|
3657
|
-
const combined = resolvedPath;
|
|
3658
|
-
const params = extractParams(combined);
|
|
3659
|
-
const methodName = method.getName();
|
|
3660
|
-
const classAsDecorator = cls.getDecorator("As");
|
|
3661
|
-
let classAs;
|
|
3662
|
-
if (classAsDecorator) {
|
|
3663
|
-
const classAsArgs = classAsDecorator.getArguments();
|
|
3664
|
-
const classAsName = decoratorStringArg(classAsArgs[0]);
|
|
3665
|
-
if (!classAsName) {
|
|
3666
|
-
throw new Error(
|
|
3667
|
-
`@As decorator on class ${className} must have a non-empty string argument.`
|
|
3668
|
-
);
|
|
3669
|
-
}
|
|
3670
|
-
classAs = classAsName;
|
|
3671
|
-
}
|
|
3672
|
-
const methodAsDecorator = method.getDecorator("As");
|
|
3673
|
-
let methodAs;
|
|
3674
|
-
if (methodAsDecorator) {
|
|
3675
|
-
const methodAsArgs = methodAsDecorator.getArguments();
|
|
3676
|
-
const methodAsName = decoratorStringArg(methodAsArgs[0]);
|
|
3677
|
-
if (!methodAsName) {
|
|
3678
|
-
throw new Error(
|
|
3679
|
-
`@As decorator on ${className}.${methodName} must have a non-empty string argument.`
|
|
3680
|
-
);
|
|
3681
|
-
}
|
|
3682
|
-
methodAs = methodAsName;
|
|
3683
|
-
}
|
|
3684
|
-
const routeName = resolveRouteName(className, methodName, classAs, methodAs);
|
|
3685
|
-
const qualifiedRef = `${className}.${methodName}`;
|
|
3686
|
-
const existing = seenNames.get(routeName);
|
|
3687
|
-
if (existing !== void 0) {
|
|
3688
|
-
throw new Error(
|
|
3689
|
-
`Route name collision: "${routeName}" is used by both "${existing}" and "${qualifiedRef}". Use @As(...) to give one of them a unique name.`
|
|
3690
|
-
);
|
|
3691
|
-
}
|
|
3692
|
-
seenNames.set(routeName, qualifiedRef);
|
|
3693
|
-
routes.push({
|
|
3694
|
-
method: resolvedMethod,
|
|
3695
|
-
path: combined,
|
|
3696
|
-
name: routeName,
|
|
3697
|
-
params,
|
|
3698
|
-
controllerRef: { className, methodName, filePath: sourceFile.getFilePath() },
|
|
3699
|
-
contract: {
|
|
3700
|
-
contractSource: {
|
|
3701
|
-
query: contractDef.query,
|
|
3702
|
-
body: contractDef.body,
|
|
3703
|
-
response: contractDef.response,
|
|
3704
|
-
// Path A: capture both the importable ref and the raw text. The
|
|
3705
|
-
// emitter prefers inlining the text (client-safe — re-exporting from
|
|
3706
|
-
// a controller would drag server-only deps into the client bundle).
|
|
3707
|
-
bodyZodRef,
|
|
3708
|
-
bodyZodText: contractDef.bodyZodText,
|
|
3709
|
-
queryZodRef,
|
|
3710
|
-
queryZodText: contractDef.queryZodText
|
|
3711
|
-
}
|
|
3712
|
-
}
|
|
3713
|
-
});
|
|
3714
|
-
} else {
|
|
3715
|
-
if (!httpMethod) continue;
|
|
3716
|
-
const combined = joinPaths(prefix, handlerPath);
|
|
3717
|
-
const params = extractParams(combined);
|
|
3718
|
-
const methodName = method.getName();
|
|
3719
|
-
const classAsDecorator = cls.getDecorator("As");
|
|
3720
|
-
let classAs;
|
|
3721
|
-
if (classAsDecorator) {
|
|
3722
|
-
const classAsArgs = classAsDecorator.getArguments();
|
|
3723
|
-
const classAsName = decoratorStringArg(classAsArgs[0]);
|
|
3724
|
-
if (classAsName) classAs = classAsName;
|
|
3725
|
-
}
|
|
3726
|
-
const methodAsDecorator = method.getDecorator("As");
|
|
3727
|
-
let methodAs;
|
|
3728
|
-
if (methodAsDecorator) {
|
|
3729
|
-
const methodAsArgs = methodAsDecorator.getArguments();
|
|
3730
|
-
const methodAsName = decoratorStringArg(methodAsArgs[0]);
|
|
3731
|
-
if (methodAsName) methodAs = methodAsName;
|
|
3732
|
-
}
|
|
3733
|
-
const routeName = resolveRouteName(className, methodName, classAs, methodAs);
|
|
3734
|
-
const dtoContract = extractDtoContract(method, sourceFile, project);
|
|
3735
|
-
routes.push({
|
|
3736
|
-
method: httpMethod,
|
|
3737
|
-
path: combined,
|
|
3738
|
-
name: routeName,
|
|
3739
|
-
params,
|
|
3740
|
-
controllerRef: { className, methodName, filePath: sourceFile.getFilePath() },
|
|
3741
|
-
contract: {
|
|
3742
|
-
contractSource: {
|
|
3743
|
-
query: dtoContract?.query ?? null,
|
|
3744
|
-
body: dtoContract?.body ?? null,
|
|
3745
|
-
response: dtoContract?.response ?? "unknown",
|
|
3746
|
-
queryRef: dtoContract?.queryRef ?? null,
|
|
3747
|
-
bodyRef: dtoContract?.bodyRef ?? null,
|
|
3748
|
-
responseRef: dtoContract?.responseRef ?? null,
|
|
3749
|
-
filterFields: dtoContract?.filterFields ?? null,
|
|
3750
|
-
filterFieldTypes: dtoContract?.filterFieldTypes ?? null,
|
|
3751
|
-
filterSource: dtoContract?.filterSource ?? null,
|
|
3752
|
-
bodyZodText: dtoContract?.bodyZodText ?? null,
|
|
3753
|
-
queryZodText: dtoContract?.queryZodText ?? null,
|
|
3754
|
-
formNestedSchemas: dtoContract?.formNestedSchemas ?? null,
|
|
3755
|
-
formWarnings: dtoContract?.formWarnings ?? [],
|
|
3756
|
-
bodySchema: dtoContract?.bodySchema ?? null,
|
|
3757
|
-
querySchema: dtoContract?.querySchema ?? null
|
|
3758
|
-
}
|
|
3759
|
-
}
|
|
3760
|
-
});
|
|
3761
|
-
}
|
|
3306
|
+
const route = applyContractDecorator ? extractContractRoute({
|
|
3307
|
+
cls,
|
|
3308
|
+
method,
|
|
3309
|
+
applyContractDecorator,
|
|
3310
|
+
verb,
|
|
3311
|
+
prefix,
|
|
3312
|
+
className,
|
|
3313
|
+
sourceFile,
|
|
3314
|
+
seenNames
|
|
3315
|
+
}) : extractDtoRoute({
|
|
3316
|
+
cls,
|
|
3317
|
+
method,
|
|
3318
|
+
verb,
|
|
3319
|
+
prefix,
|
|
3320
|
+
className,
|
|
3321
|
+
sourceFile,
|
|
3322
|
+
project,
|
|
3323
|
+
seenNames
|
|
3324
|
+
});
|
|
3325
|
+
if (route) routes.push(route);
|
|
3762
3326
|
}
|
|
3763
3327
|
}
|
|
3764
3328
|
return routes;
|
|
@@ -3767,7 +3331,7 @@ function extractFromSourceFile(sourceFile, project) {
|
|
|
3767
3331
|
// src/watch/lock-file.ts
|
|
3768
3332
|
var import_promises10 = require("fs/promises");
|
|
3769
3333
|
var import_promises11 = require("fs/promises");
|
|
3770
|
-
var
|
|
3334
|
+
var import_node_path13 = require("path");
|
|
3771
3335
|
var LOCK_FILE = ".watcher.lock";
|
|
3772
3336
|
function isProcessAlive(pid) {
|
|
3773
3337
|
try {
|
|
@@ -3779,7 +3343,7 @@ function isProcessAlive(pid) {
|
|
|
3779
3343
|
}
|
|
3780
3344
|
async function acquireLock(outDir) {
|
|
3781
3345
|
await (0, import_promises11.mkdir)(outDir, { recursive: true });
|
|
3782
|
-
const lockPath = (0,
|
|
3346
|
+
const lockPath = (0, import_node_path13.join)(outDir, LOCK_FILE);
|
|
3783
3347
|
const lockData = { pid: process.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
3784
3348
|
try {
|
|
3785
3349
|
const fd = await (0, import_promises10.open)(lockPath, "wx");
|
|
@@ -3819,7 +3383,7 @@ async function watch(config, onChange) {
|
|
|
3819
3383
|
if (lock === null) {
|
|
3820
3384
|
let holderPid = "unknown";
|
|
3821
3385
|
try {
|
|
3822
|
-
const raw = await (0, import_promises12.readFile)((0,
|
|
3386
|
+
const raw = await (0, import_promises12.readFile)((0, import_node_path14.join)(config.codegen.outDir, ".watcher.lock"), "utf8");
|
|
3823
3387
|
const data = JSON.parse(raw);
|
|
3824
3388
|
if (data.pid !== void 0) holderPid = String(data.pid);
|
|
3825
3389
|
} catch {
|
|
@@ -3847,7 +3411,7 @@ async function watch(config, onChange) {
|
|
|
3847
3411
|
}
|
|
3848
3412
|
let pagesDebounceTimer;
|
|
3849
3413
|
const pagesGlob = config.pages?.glob ?? ".nestjs-codegen-no-pages";
|
|
3850
|
-
const pagesWatcher = import_chokidar.default.watch((0,
|
|
3414
|
+
const pagesWatcher = import_chokidar.default.watch((0, import_node_path14.join)(config.codegen.cwd, pagesGlob), {
|
|
3851
3415
|
ignoreInitial: true,
|
|
3852
3416
|
persistent: true,
|
|
3853
3417
|
awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
|
|
@@ -3873,7 +3437,7 @@ async function watch(config, onChange) {
|
|
|
3873
3437
|
pagesWatcher.on("change", schedulePagesRegenerate);
|
|
3874
3438
|
pagesWatcher.on("unlink", schedulePagesRegenerate);
|
|
3875
3439
|
let contractsDebounceTimer;
|
|
3876
|
-
const contractsWatcher = import_chokidar.default.watch((0,
|
|
3440
|
+
const contractsWatcher = import_chokidar.default.watch((0, import_node_path14.join)(config.codegen.cwd, config.contracts.glob), {
|
|
3877
3441
|
ignoreInitial: true,
|
|
3878
3442
|
persistent: true,
|
|
3879
3443
|
awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
|
|
@@ -3903,7 +3467,7 @@ async function watch(config, onChange) {
|
|
|
3903
3467
|
contractsWatcher.on("add", scheduleContractsRegenerate);
|
|
3904
3468
|
contractsWatcher.on("change", scheduleContractsRegenerate);
|
|
3905
3469
|
contractsWatcher.on("unlink", scheduleContractsRegenerate);
|
|
3906
|
-
const formsWatcher = import_chokidar.default.watch((0,
|
|
3470
|
+
const formsWatcher = import_chokidar.default.watch((0, import_node_path14.join)(config.codegen.cwd, config.forms.watch), {
|
|
3907
3471
|
ignoreInitial: true,
|
|
3908
3472
|
persistent: true,
|
|
3909
3473
|
awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
|
|
@@ -3930,7 +3494,7 @@ async function watch(config, onChange) {
|
|
|
3930
3494
|
}
|
|
3931
3495
|
|
|
3932
3496
|
// src/index.ts
|
|
3933
|
-
var VERSION = "0.
|
|
3497
|
+
var VERSION = "0.4.0";
|
|
3934
3498
|
|
|
3935
3499
|
// src/cli/codegen.ts
|
|
3936
3500
|
async function runCodegen(opts = {}) {
|
|
@@ -3958,15 +3522,51 @@ async function runCodegen(opts = {}) {
|
|
|
3958
3522
|
|
|
3959
3523
|
// src/cli/doctor.ts
|
|
3960
3524
|
var import_node_child_process2 = require("child_process");
|
|
3961
|
-
var
|
|
3962
|
-
var
|
|
3525
|
+
var import_node_fs4 = require("fs");
|
|
3526
|
+
var import_node_path16 = require("path");
|
|
3963
3527
|
|
|
3964
3528
|
// src/cli/init.ts
|
|
3965
3529
|
var import_node_child_process = require("child_process");
|
|
3966
|
-
var
|
|
3530
|
+
var import_node_fs3 = require("fs");
|
|
3967
3531
|
var import_promises13 = require("fs/promises");
|
|
3968
|
-
var
|
|
3532
|
+
var import_node_path15 = require("path");
|
|
3969
3533
|
var import_node_readline = require("readline");
|
|
3534
|
+
|
|
3535
|
+
// src/cli/patch-utils.ts
|
|
3536
|
+
var import_node_fs2 = require("fs");
|
|
3537
|
+
function patchJsonFile(filePath, mutator, parse = (raw) => raw) {
|
|
3538
|
+
let raw;
|
|
3539
|
+
try {
|
|
3540
|
+
raw = (0, import_node_fs2.readFileSync)(filePath, "utf8");
|
|
3541
|
+
} catch {
|
|
3542
|
+
return "skipped";
|
|
3543
|
+
}
|
|
3544
|
+
const json = JSON.parse(parse(raw));
|
|
3545
|
+
if (!mutator(json)) return "already";
|
|
3546
|
+
(0, import_node_fs2.writeFileSync)(filePath, `${JSON.stringify(json, null, 2)}
|
|
3547
|
+
`, "utf8");
|
|
3548
|
+
return "patched";
|
|
3549
|
+
}
|
|
3550
|
+
function findAfterLastImport(content) {
|
|
3551
|
+
const lastImportIndex = content.lastIndexOf("\nimport ");
|
|
3552
|
+
if (lastImportIndex !== -1) {
|
|
3553
|
+
const endOfLine = content.indexOf("\n", lastImportIndex + 1);
|
|
3554
|
+
return endOfLine !== -1 ? endOfLine + 1 : content.length;
|
|
3555
|
+
}
|
|
3556
|
+
if (content.startsWith("import ")) {
|
|
3557
|
+
const endOfLine = content.indexOf("\n");
|
|
3558
|
+
return endOfLine !== -1 ? endOfLine + 1 : content.length;
|
|
3559
|
+
}
|
|
3560
|
+
return 0;
|
|
3561
|
+
}
|
|
3562
|
+
function insertImport(content, stmt) {
|
|
3563
|
+
const insertAt = findAfterLastImport(content);
|
|
3564
|
+
if (insertAt <= 0) return content;
|
|
3565
|
+
return `${content.slice(0, insertAt)}${stmt}
|
|
3566
|
+
${content.slice(insertAt)}`;
|
|
3567
|
+
}
|
|
3568
|
+
|
|
3569
|
+
// src/cli/init.ts
|
|
3970
3570
|
var GITIGNORE_ENTRY = ".nestjs-inertia/";
|
|
3971
3571
|
var green = (s) => `\x1B[32m${s}\x1B[0m`;
|
|
3972
3572
|
var yellow = (s) => `\x1B[33m${s}\x1B[0m`;
|
|
@@ -3991,7 +3591,7 @@ ${bold(title)}`);
|
|
|
3991
3591
|
}
|
|
3992
3592
|
async function readPackageJson(cwd) {
|
|
3993
3593
|
try {
|
|
3994
|
-
const raw = await (0, import_promises13.readFile)((0,
|
|
3594
|
+
const raw = await (0, import_promises13.readFile)((0, import_node_path15.join)(cwd, "package.json"), "utf8");
|
|
3995
3595
|
return JSON.parse(raw);
|
|
3996
3596
|
} catch {
|
|
3997
3597
|
return {};
|
|
@@ -4022,7 +3622,7 @@ async function detectTemplateEngine(cwd) {
|
|
|
4022
3622
|
async function detectPackageManager(cwd) {
|
|
4023
3623
|
async function exists(file) {
|
|
4024
3624
|
try {
|
|
4025
|
-
await (0, import_promises13.access)((0,
|
|
3625
|
+
await (0, import_promises13.access)((0, import_node_path15.join)(cwd, file));
|
|
4026
3626
|
return true;
|
|
4027
3627
|
} catch {
|
|
4028
3628
|
return false;
|
|
@@ -4069,7 +3669,7 @@ async function writeIfNotExists(filePath, content, label) {
|
|
|
4069
3669
|
logCreated(label);
|
|
4070
3670
|
}
|
|
4071
3671
|
async function handleViteConfig(cwd, framework) {
|
|
4072
|
-
const filePath = (0,
|
|
3672
|
+
const filePath = (0, import_node_path15.join)(cwd, "vite.config.ts");
|
|
4073
3673
|
if (await fileExists2(filePath)) {
|
|
4074
3674
|
const existing = await (0, import_promises13.readFile)(filePath, "utf8");
|
|
4075
3675
|
const hasPlugin = existing.includes("nestInertia") || existing.includes("nestjs-inertia-vite/plugin");
|
|
@@ -4127,7 +3727,7 @@ function installDeps(pkgManager, deps, dev) {
|
|
|
4127
3727
|
}
|
|
4128
3728
|
}
|
|
4129
3729
|
async function patchPackageJsonScripts(cwd, scripts) {
|
|
4130
|
-
const pkgPath = (0,
|
|
3730
|
+
const pkgPath = (0, import_node_path15.join)(cwd, "package.json");
|
|
4131
3731
|
let pkg = {};
|
|
4132
3732
|
try {
|
|
4133
3733
|
pkg = JSON.parse(await (0, import_promises13.readFile)(pkgPath, "utf8"));
|
|
@@ -4152,32 +3752,16 @@ async function patchPackageJsonScripts(cwd, scripts) {
|
|
|
4152
3752
|
await (0, import_promises13.writeFile)(pkgPath, `${JSON.stringify(pkg, null, 2)}
|
|
4153
3753
|
`, "utf8");
|
|
4154
3754
|
}
|
|
4155
|
-
function findAfterLastImport(content) {
|
|
4156
|
-
const lastImportIndex = content.lastIndexOf("\nimport ");
|
|
4157
|
-
if (lastImportIndex !== -1) {
|
|
4158
|
-
const endOfLine = content.indexOf("\n", lastImportIndex + 1);
|
|
4159
|
-
return endOfLine !== -1 ? endOfLine + 1 : content.length;
|
|
4160
|
-
}
|
|
4161
|
-
if (content.startsWith("import ")) {
|
|
4162
|
-
const endOfLine = content.indexOf("\n");
|
|
4163
|
-
return endOfLine !== -1 ? endOfLine + 1 : content.length;
|
|
4164
|
-
}
|
|
4165
|
-
return 0;
|
|
4166
|
-
}
|
|
4167
3755
|
function patchAppModule(filePath, rootView) {
|
|
4168
3756
|
let content;
|
|
4169
3757
|
try {
|
|
4170
|
-
content = (0,
|
|
3758
|
+
content = (0, import_node_fs3.readFileSync)(filePath, "utf8");
|
|
4171
3759
|
} catch {
|
|
4172
3760
|
return "skipped";
|
|
4173
3761
|
}
|
|
4174
3762
|
let changed = false;
|
|
4175
3763
|
if (!content.includes("InertiaModule")) {
|
|
4176
|
-
|
|
4177
|
-
if (insertAt > 0) {
|
|
4178
|
-
content = `${content.slice(0, insertAt)}import { InertiaModule } from '@dudousxd/nestjs-inertia';
|
|
4179
|
-
${content.slice(insertAt)}`;
|
|
4180
|
-
}
|
|
3764
|
+
content = insertImport(content, "import { InertiaModule } from '@dudousxd/nestjs-inertia';");
|
|
4181
3765
|
if (!content.includes("from 'node:path'") && !content.includes('from "node:path"')) {
|
|
4182
3766
|
const insertAt2 = findAfterLastImport(content);
|
|
4183
3767
|
content = `${content.slice(0, insertAt2)}import { resolve } from 'node:path';
|
|
@@ -4195,11 +3779,7 @@ ${indent}}),${content.slice(bracketPos)}`;
|
|
|
4195
3779
|
}
|
|
4196
3780
|
}
|
|
4197
3781
|
if (!content.includes("HomeController")) {
|
|
4198
|
-
|
|
4199
|
-
if (insertAt > 0) {
|
|
4200
|
-
content = `${content.slice(0, insertAt)}import { HomeController } from './home.controller';
|
|
4201
|
-
${content.slice(insertAt)}`;
|
|
4202
|
-
}
|
|
3782
|
+
content = insertImport(content, "import { HomeController } from './home.controller';");
|
|
4203
3783
|
const controllersMatch = content.match(/controllers\s*:\s*\[/);
|
|
4204
3784
|
if (controllersMatch?.index !== void 0) {
|
|
4205
3785
|
const bracketPos = content.indexOf("[", controllersMatch.index) + 1;
|
|
@@ -4210,22 +3790,21 @@ ${indent}HomeController,${content.slice(bracketPos)}`;
|
|
|
4210
3790
|
}
|
|
4211
3791
|
}
|
|
4212
3792
|
if (!changed) return "already";
|
|
4213
|
-
(0,
|
|
3793
|
+
(0, import_node_fs3.writeFileSync)(filePath, content, "utf8");
|
|
4214
3794
|
return "patched";
|
|
4215
3795
|
}
|
|
4216
3796
|
function patchMainTs(filePath) {
|
|
4217
3797
|
let content;
|
|
4218
3798
|
try {
|
|
4219
|
-
content = (0,
|
|
3799
|
+
content = (0, import_node_fs3.readFileSync)(filePath, "utf8");
|
|
4220
3800
|
} catch {
|
|
4221
3801
|
return "skipped";
|
|
4222
3802
|
}
|
|
4223
3803
|
if (content.includes("setupInertiaVite")) return "already";
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
}
|
|
3804
|
+
content = insertImport(
|
|
3805
|
+
content,
|
|
3806
|
+
"import { setupInertiaVite } from '@dudousxd/nestjs-inertia-vite';"
|
|
3807
|
+
);
|
|
4229
3808
|
const createMatch = content.match(
|
|
4230
3809
|
/(?:const|let)\s+(\w+)\s*=\s*await\s+NestFactory\.create[^;]+;/
|
|
4231
3810
|
);
|
|
@@ -4242,7 +3821,7 @@ ${content.slice(insertAt)}`;
|
|
|
4242
3821
|
});`;
|
|
4243
3822
|
content = `${content.slice(0, insertAfterPos)}
|
|
4244
3823
|
${viteSetup}${content.slice(insertAfterPos)}`;
|
|
4245
|
-
(0,
|
|
3824
|
+
(0, import_node_fs3.writeFileSync)(filePath, content, "utf8");
|
|
4246
3825
|
return "patched";
|
|
4247
3826
|
}
|
|
4248
3827
|
function configTemplate(framework) {
|
|
@@ -4465,107 +4044,84 @@ export class HomeController {
|
|
|
4465
4044
|
}
|
|
4466
4045
|
`;
|
|
4467
4046
|
function patchTsconfigExclude(cwd, dir, filename = "tsconfig.json") {
|
|
4468
|
-
const filePath = (0,
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
(0, import_node_fs2.writeFileSync)(filePath, `${JSON.stringify(json, null, 2)}
|
|
4482
|
-
`, "utf8");
|
|
4483
|
-
return "patched";
|
|
4047
|
+
const filePath = (0, import_node_path15.join)(cwd, filename);
|
|
4048
|
+
return patchJsonFile(
|
|
4049
|
+
filePath,
|
|
4050
|
+
(json) => {
|
|
4051
|
+
const exclude = json.exclude ?? [];
|
|
4052
|
+
if (exclude.includes(dir)) return false;
|
|
4053
|
+
exclude.push(dir);
|
|
4054
|
+
json.exclude = exclude;
|
|
4055
|
+
return true;
|
|
4056
|
+
},
|
|
4057
|
+
// Strip single-line comments before JSON.parse
|
|
4058
|
+
(raw) => raw.replace(/\/\/.*$/gm, "")
|
|
4059
|
+
);
|
|
4484
4060
|
}
|
|
4485
4061
|
function patchNestCliJson(cwd, shellDir) {
|
|
4486
|
-
const filePath = (0,
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
outDir: `dist/${shellDir}`,
|
|
4504
|
-
watchAssets: true
|
|
4062
|
+
const filePath = (0, import_node_path15.join)(cwd, "nest-cli.json");
|
|
4063
|
+
return patchJsonFile(filePath, (json) => {
|
|
4064
|
+
const compiler = json.compilerOptions ?? {};
|
|
4065
|
+
const assets = compiler.assets ?? [];
|
|
4066
|
+
const alreadyHas = assets.some((a) => {
|
|
4067
|
+
if (typeof a === "string") return a.includes(shellDir);
|
|
4068
|
+
return String(a.include ?? "").includes(shellDir);
|
|
4069
|
+
});
|
|
4070
|
+
if (alreadyHas) return false;
|
|
4071
|
+
assets.push({
|
|
4072
|
+
include: `../${shellDir}/**/*`,
|
|
4073
|
+
outDir: `dist/${shellDir}`,
|
|
4074
|
+
watchAssets: true
|
|
4075
|
+
});
|
|
4076
|
+
compiler.assets = assets;
|
|
4077
|
+
json.compilerOptions = compiler;
|
|
4078
|
+
return true;
|
|
4505
4079
|
});
|
|
4506
|
-
compiler.assets = assets;
|
|
4507
|
-
json.compilerOptions = compiler;
|
|
4508
|
-
(0, import_node_fs2.writeFileSync)(filePath, `${JSON.stringify(json, null, 2)}
|
|
4509
|
-
`, "utf8");
|
|
4510
|
-
return "patched";
|
|
4511
4080
|
}
|
|
4512
|
-
async function
|
|
4513
|
-
const cwd
|
|
4514
|
-
console.log(`
|
|
4515
|
-
${bold("nestjs-inertia init")}`);
|
|
4516
|
-
let framework = await detectFramework(cwd);
|
|
4517
|
-
if (!framework) {
|
|
4518
|
-
framework = await promptFramework();
|
|
4519
|
-
}
|
|
4520
|
-
const engine = await detectTemplateEngine(cwd);
|
|
4521
|
-
const engineLabel = engine === "html" ? "plain HTML" : engine;
|
|
4522
|
-
const frameworkLabel = framework.charAt(0).toUpperCase() + framework.slice(1);
|
|
4523
|
-
console.log(`
|
|
4524
|
-
Detected: ${bold(`${frameworkLabel} + ${engineLabel}`)}`);
|
|
4525
|
-
const shellFileName = engine === "html" ? "index.html" : `index.${engine === "handlebars" ? "hbs" : engine}`;
|
|
4526
|
-
const entryExt = framework === "react" ? "tsx" : "ts";
|
|
4527
|
-
const pageExt = framework === "react" ? "tsx" : framework === "vue" ? "vue" : "svelte";
|
|
4081
|
+
async function scaffoldFiles(ctx) {
|
|
4082
|
+
const { cwd, framework, engine, shellFileName, entryExt, pageExt } = ctx;
|
|
4528
4083
|
logSection("Scaffold files");
|
|
4529
4084
|
await writeIfNotExists(
|
|
4530
|
-
(0,
|
|
4085
|
+
(0, import_node_path15.join)(cwd, "nestjs-inertia.config.ts"),
|
|
4531
4086
|
configTemplate(framework),
|
|
4532
4087
|
"nestjs-inertia.config.ts"
|
|
4533
4088
|
);
|
|
4534
|
-
await writeIfNotExists((0,
|
|
4089
|
+
await writeIfNotExists((0, import_node_path15.join)(cwd, "nestjs-inertia.d.ts"), DTS_TEMPLATE, "nestjs-inertia.d.ts");
|
|
4535
4090
|
await writeIfNotExists(
|
|
4536
|
-
(0,
|
|
4091
|
+
(0, import_node_path15.join)(cwd, "tsconfig.inertia.json"),
|
|
4537
4092
|
TSCONFIG_INERTIA_TEMPLATE,
|
|
4538
4093
|
"tsconfig.inertia.json"
|
|
4539
4094
|
);
|
|
4540
4095
|
await writeIfNotExists(
|
|
4541
|
-
(0,
|
|
4096
|
+
(0, import_node_path15.join)(cwd, "inertia", "tsconfig.json"),
|
|
4542
4097
|
INERTIA_TSCONFIG_TEMPLATE,
|
|
4543
4098
|
"inertia/tsconfig.json"
|
|
4544
4099
|
);
|
|
4545
4100
|
await writeIfNotExists(
|
|
4546
|
-
(0,
|
|
4101
|
+
(0, import_node_path15.join)(cwd, "inertia", shellFileName),
|
|
4547
4102
|
htmlShellTemplate(framework, engine),
|
|
4548
4103
|
`inertia/${shellFileName}`
|
|
4549
4104
|
);
|
|
4550
4105
|
await handleViteConfig(cwd, framework);
|
|
4551
4106
|
await writeIfNotExists(
|
|
4552
|
-
(0,
|
|
4107
|
+
(0, import_node_path15.join)(cwd, "inertia", "app", `client.${entryExt}`),
|
|
4553
4108
|
entryPointTemplate(framework),
|
|
4554
4109
|
`inertia/app/client.${entryExt}`
|
|
4555
4110
|
);
|
|
4556
4111
|
await writeIfNotExists(
|
|
4557
|
-
(0,
|
|
4112
|
+
(0, import_node_path15.join)(cwd, "inertia", "pages", `Home.${pageExt}`),
|
|
4558
4113
|
samplePageTemplate(framework),
|
|
4559
4114
|
`inertia/pages/Home.${pageExt}`
|
|
4560
4115
|
);
|
|
4561
4116
|
await writeIfNotExists(
|
|
4562
|
-
(0,
|
|
4117
|
+
(0, import_node_path15.join)(cwd, "src", "home.controller.ts"),
|
|
4563
4118
|
SAMPLE_CONTROLLER,
|
|
4564
4119
|
"src/home.controller.ts"
|
|
4565
4120
|
);
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
const
|
|
4121
|
+
}
|
|
4122
|
+
function patchServerAppModule(ctx) {
|
|
4123
|
+
const { cwd, rootView } = ctx;
|
|
4124
|
+
const appModulePath = (0, import_node_path15.join)(cwd, "src", "app.module.ts");
|
|
4569
4125
|
const appModuleResult = patchAppModule(appModulePath, rootView);
|
|
4570
4126
|
if (appModuleResult === "patched") {
|
|
4571
4127
|
logPatched("src/app.module.ts", "added InertiaModule.forRoot");
|
|
@@ -4577,7 +4133,9 @@ ${bold("nestjs-inertia init")}`);
|
|
|
4577
4133
|
} else {
|
|
4578
4134
|
logWarning("src/app.module.ts not found \u2014 add InertiaModule.forRoot() manually");
|
|
4579
4135
|
}
|
|
4580
|
-
|
|
4136
|
+
}
|
|
4137
|
+
function patchServerMainTs(ctx) {
|
|
4138
|
+
const mainTsPath = (0, import_node_path15.join)(ctx.cwd, "src", "main.ts");
|
|
4581
4139
|
const mainTsResult = patchMainTs(mainTsPath);
|
|
4582
4140
|
if (mainTsResult === "patched") {
|
|
4583
4141
|
logPatched("src/main.ts", "added setupInertiaVite after NestFactory.create");
|
|
@@ -4586,7 +4144,9 @@ ${bold("nestjs-inertia init")}`);
|
|
|
4586
4144
|
} else {
|
|
4587
4145
|
logWarning("src/main.ts not found \u2014 add setupInertiaVite() manually");
|
|
4588
4146
|
}
|
|
4589
|
-
|
|
4147
|
+
}
|
|
4148
|
+
function patchBuildConfigs(ctx) {
|
|
4149
|
+
const { cwd, shellDir } = ctx;
|
|
4590
4150
|
const nestCliResult = patchNestCliJson(cwd, shellDir);
|
|
4591
4151
|
if (nestCliResult === "patched") {
|
|
4592
4152
|
logPatched("nest-cli.json", `added asset copy for ${shellDir}/ \u2192 dist/${shellDir}/`);
|
|
@@ -4607,18 +4167,26 @@ ${bold("nestjs-inertia init")}`);
|
|
|
4607
4167
|
);
|
|
4608
4168
|
}
|
|
4609
4169
|
}
|
|
4610
|
-
|
|
4170
|
+
}
|
|
4171
|
+
async function patchGitignoreAndDist(ctx) {
|
|
4172
|
+
const { cwd } = ctx;
|
|
4173
|
+
await patchGitignore((0, import_node_path15.join)(cwd, ".gitignore"));
|
|
4611
4174
|
const tsconfigDistResult = patchTsconfigExclude(cwd, "dist", "tsconfig.json");
|
|
4612
4175
|
if (tsconfigDistResult === "patched") {
|
|
4613
4176
|
logPatched("tsconfig.json", "excluded dist/ from server compilation");
|
|
4614
4177
|
} else if (tsconfigDistResult === "already") {
|
|
4615
4178
|
console.log(` ${cyan("\u2192")} tsconfig.json ${dim("(dist/ already excluded, skipped)")}`);
|
|
4616
4179
|
}
|
|
4617
|
-
|
|
4180
|
+
}
|
|
4181
|
+
async function scaffoldPackageScripts(ctx) {
|
|
4182
|
+
await patchPackageJsonScripts(ctx.cwd, {
|
|
4618
4183
|
"build:client": "vite build",
|
|
4619
4184
|
"build:ssr": "VITE_SSR=1 vite build --ssr",
|
|
4620
4185
|
"typecheck:inertia": "tsc --noEmit -p tsconfig.inertia.json"
|
|
4621
4186
|
});
|
|
4187
|
+
}
|
|
4188
|
+
async function installMissingDeps(ctx) {
|
|
4189
|
+
const { cwd, framework, opts } = ctx;
|
|
4622
4190
|
logSection("Install dependencies");
|
|
4623
4191
|
const pkg = await readPackageJson(cwd);
|
|
4624
4192
|
const installedDeps = allDeps(pkg);
|
|
@@ -4652,6 +4220,55 @@ ${bold("nestjs-inertia init")}`);
|
|
|
4652
4220
|
installDeps(pkgManager, depsToInstall, false);
|
|
4653
4221
|
installDeps(pkgManager, devDepsToInstall, true);
|
|
4654
4222
|
}
|
|
4223
|
+
}
|
|
4224
|
+
var INIT_STEPS = [
|
|
4225
|
+
{ label: "scaffold files", run: scaffoldFiles },
|
|
4226
|
+
{
|
|
4227
|
+
label: "patch app.module.ts",
|
|
4228
|
+
run: (ctx) => {
|
|
4229
|
+
logSection("Patch existing files");
|
|
4230
|
+
patchServerAppModule(ctx);
|
|
4231
|
+
}
|
|
4232
|
+
},
|
|
4233
|
+
{ label: "patch main.ts", run: patchServerMainTs },
|
|
4234
|
+
{ label: "patch build configs", run: patchBuildConfigs },
|
|
4235
|
+
{ label: "patch .gitignore + dist exclude", run: patchGitignoreAndDist },
|
|
4236
|
+
{ label: "add package.json scripts", run: scaffoldPackageScripts },
|
|
4237
|
+
{ label: "install dependencies", run: installMissingDeps }
|
|
4238
|
+
];
|
|
4239
|
+
async function runInit(opts = {}) {
|
|
4240
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
4241
|
+
console.log(`
|
|
4242
|
+
${bold("nestjs-inertia init")}`);
|
|
4243
|
+
let framework = await detectFramework(cwd);
|
|
4244
|
+
if (!framework) {
|
|
4245
|
+
framework = await promptFramework();
|
|
4246
|
+
}
|
|
4247
|
+
const engine = await detectTemplateEngine(cwd);
|
|
4248
|
+
const engineLabel = engine === "html" ? "plain HTML" : engine;
|
|
4249
|
+
const frameworkLabel = framework.charAt(0).toUpperCase() + framework.slice(1);
|
|
4250
|
+
console.log(`
|
|
4251
|
+
Detected: ${bold(`${frameworkLabel} + ${engineLabel}`)}`);
|
|
4252
|
+
const shellFileName = engine === "html" ? "index.html" : `index.${engine === "handlebars" ? "hbs" : engine}`;
|
|
4253
|
+
const rootView = engine === "html" ? "inertia/index.html" : `inertia/index.${engine === "handlebars" ? "hbs" : engine}`;
|
|
4254
|
+
const ctx = {
|
|
4255
|
+
opts,
|
|
4256
|
+
cwd,
|
|
4257
|
+
framework,
|
|
4258
|
+
engine,
|
|
4259
|
+
shellFileName,
|
|
4260
|
+
entryExt: framework === "react" ? "tsx" : "ts",
|
|
4261
|
+
pageExt: framework === "react" ? "tsx" : framework === "vue" ? "vue" : "svelte",
|
|
4262
|
+
rootView,
|
|
4263
|
+
shellDir: rootView.split("/")[0]
|
|
4264
|
+
// e.g. "inertia" from "inertia/index.html"
|
|
4265
|
+
};
|
|
4266
|
+
for (const step of INIT_STEPS) {
|
|
4267
|
+
if (process.env.NESTJS_CODEGEN_DEBUG) {
|
|
4268
|
+
console.log(dim(` \xB7 ${step.label}`));
|
|
4269
|
+
}
|
|
4270
|
+
await step.run(ctx);
|
|
4271
|
+
}
|
|
4655
4272
|
console.log(`
|
|
4656
4273
|
${green("\u2713")} Setup complete! Run: ${bold("nest start --watch")}
|
|
4657
4274
|
`);
|
|
@@ -4659,18 +4276,18 @@ ${green("\u2713")} Setup complete! Run: ${bold("nest start --watch")}
|
|
|
4659
4276
|
|
|
4660
4277
|
// src/cli/doctor.ts
|
|
4661
4278
|
function checkFileExists(cwd, file) {
|
|
4662
|
-
return (0,
|
|
4279
|
+
return (0, import_node_fs4.existsSync)((0, import_node_path16.join)(cwd, file));
|
|
4663
4280
|
}
|
|
4664
4281
|
function readJson(path) {
|
|
4665
4282
|
try {
|
|
4666
|
-
const raw = (0,
|
|
4283
|
+
const raw = (0, import_node_fs4.readFileSync)(path, "utf8").replace(/\/\/.*$/gm, "");
|
|
4667
4284
|
return JSON.parse(raw);
|
|
4668
4285
|
} catch {
|
|
4669
4286
|
return null;
|
|
4670
4287
|
}
|
|
4671
4288
|
}
|
|
4672
4289
|
function writeJsonField(filePath, dotPath, value) {
|
|
4673
|
-
const raw = (0,
|
|
4290
|
+
const raw = (0, import_node_fs4.readFileSync)(filePath, "utf8");
|
|
4674
4291
|
const stripped = raw.replace(/\/\/.*$/gm, "");
|
|
4675
4292
|
const obj = JSON.parse(stripped);
|
|
4676
4293
|
let target = obj;
|
|
@@ -4690,20 +4307,20 @@ function writeJsonField(filePath, dotPath, value) {
|
|
|
4690
4307
|
} else {
|
|
4691
4308
|
target[lastKey] = value;
|
|
4692
4309
|
}
|
|
4693
|
-
(0,
|
|
4310
|
+
(0, import_node_fs4.writeFileSync)(filePath, `${JSON.stringify(obj, null, 2)}
|
|
4694
4311
|
`, "utf8");
|
|
4695
4312
|
}
|
|
4696
4313
|
function getPackageVersion(cwd, pkg) {
|
|
4697
4314
|
try {
|
|
4698
|
-
const pkgJson = readJson((0,
|
|
4315
|
+
const pkgJson = readJson((0, import_node_path16.join)(cwd, "node_modules", pkg, "package.json"));
|
|
4699
4316
|
return pkgJson?.version ?? null;
|
|
4700
4317
|
} catch {
|
|
4701
4318
|
return null;
|
|
4702
4319
|
}
|
|
4703
4320
|
}
|
|
4704
4321
|
function detectPkgManager(cwd) {
|
|
4705
|
-
if ((0,
|
|
4706
|
-
if ((0,
|
|
4322
|
+
if ((0, import_node_fs4.existsSync)((0, import_node_path16.join)(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
4323
|
+
if ((0, import_node_fs4.existsSync)((0, import_node_path16.join)(cwd, "yarn.lock"))) return "yarn";
|
|
4707
4324
|
return "npm";
|
|
4708
4325
|
}
|
|
4709
4326
|
async function runDoctor(opts) {
|
|
@@ -4741,7 +4358,7 @@ async function runDoctor(opts) {
|
|
|
4741
4358
|
autoFix: () => runInit({ cwd })
|
|
4742
4359
|
});
|
|
4743
4360
|
if (foundShellDir) {
|
|
4744
|
-
const nestCliPath = (0,
|
|
4361
|
+
const nestCliPath = (0, import_node_path16.join)(cwd, "nest-cli.json");
|
|
4745
4362
|
const nestCli = readJson(nestCliPath);
|
|
4746
4363
|
const compiler = nestCli?.compilerOptions ?? {};
|
|
4747
4364
|
const assets = compiler.assets ?? [];
|
|
@@ -4780,7 +4397,7 @@ async function runDoctor(opts) {
|
|
|
4780
4397
|
fix: "Run: nestjs-codegen codegen",
|
|
4781
4398
|
autoFix: () => runCodegen({ cwd })
|
|
4782
4399
|
});
|
|
4783
|
-
const tsconfigPath = (0,
|
|
4400
|
+
const tsconfigPath = (0, import_node_path16.join)(cwd, "tsconfig.json");
|
|
4784
4401
|
const tsconfig = readJson(tsconfigPath);
|
|
4785
4402
|
const paths = tsconfig?.compilerOptions?.paths;
|
|
4786
4403
|
checks.push({
|
|
@@ -4791,7 +4408,7 @@ async function runDoctor(opts) {
|
|
|
4791
4408
|
});
|
|
4792
4409
|
const inertiaDir = foundShellDir ?? "inertia";
|
|
4793
4410
|
for (const tsconfigFile of ["tsconfig.json", "tsconfig.build.json"]) {
|
|
4794
|
-
const tsc = readJson((0,
|
|
4411
|
+
const tsc = readJson((0, import_node_path16.join)(cwd, tsconfigFile));
|
|
4795
4412
|
if (!tsc) continue;
|
|
4796
4413
|
const excl = tsc.exclude ?? [];
|
|
4797
4414
|
const excludesIt = excl.includes(inertiaDir);
|
|
@@ -4817,14 +4434,14 @@ async function runDoctor(opts) {
|
|
|
4817
4434
|
}
|
|
4818
4435
|
});
|
|
4819
4436
|
}
|
|
4820
|
-
const inertiaTsconfigPath = (0,
|
|
4437
|
+
const inertiaTsconfigPath = (0, import_node_path16.join)(cwd, "tsconfig.inertia.json");
|
|
4821
4438
|
const inertiaTsconfig = readJson(inertiaTsconfigPath);
|
|
4822
4439
|
checks.push({
|
|
4823
4440
|
name: "tsconfig.inertia.json exists",
|
|
4824
4441
|
pass: !!inertiaTsconfig,
|
|
4825
4442
|
fix: "Create tsconfig.inertia.json (typechecks inertia/ + .nestjs-inertia/)",
|
|
4826
4443
|
autoFix: () => {
|
|
4827
|
-
(0,
|
|
4444
|
+
(0, import_node_fs4.writeFileSync)(inertiaTsconfigPath, TSCONFIG_INERTIA_TEMPLATE, "utf8");
|
|
4828
4445
|
}
|
|
4829
4446
|
});
|
|
4830
4447
|
if (inertiaTsconfig) {
|
|
@@ -4869,17 +4486,17 @@ async function runDoctor(opts) {
|
|
|
4869
4486
|
fix: 'Add "nestjs-inertia.d.ts" to include array (resolves InertiaRegistry augmentation)'
|
|
4870
4487
|
});
|
|
4871
4488
|
}
|
|
4872
|
-
const innerTsconfigPath = (0,
|
|
4489
|
+
const innerTsconfigPath = (0, import_node_path16.join)(cwd, "inertia", "tsconfig.json");
|
|
4873
4490
|
checks.push({
|
|
4874
4491
|
name: "inertia/tsconfig.json exists (VSCode picks up ~codegen alias)",
|
|
4875
|
-
pass: (0,
|
|
4492
|
+
pass: (0, import_node_fs4.existsSync)(innerTsconfigPath),
|
|
4876
4493
|
fix: "Create inertia/tsconfig.json that extends ../tsconfig.inertia.json",
|
|
4877
4494
|
autoFix: () => {
|
|
4878
|
-
(0,
|
|
4495
|
+
(0, import_node_fs4.writeFileSync)(innerTsconfigPath, INERTIA_TSCONFIG_TEMPLATE, "utf8");
|
|
4879
4496
|
}
|
|
4880
4497
|
});
|
|
4881
4498
|
if (checkFileExists(cwd, "vite.config.ts")) {
|
|
4882
|
-
const viteContent = (0,
|
|
4499
|
+
const viteContent = (0, import_node_fs4.readFileSync)((0, import_node_path16.join)(cwd, "vite.config.ts"), "utf8");
|
|
4883
4500
|
checks.push({
|
|
4884
4501
|
name: "vite.config.ts has resolve.alias",
|
|
4885
4502
|
pass: viteContent.includes("resolve") && viteContent.includes("alias"),
|
|
@@ -4944,16 +4561,16 @@ async function runDoctor(opts) {
|
|
|
4944
4561
|
});
|
|
4945
4562
|
}
|
|
4946
4563
|
if (checkFileExists(cwd, ".gitignore")) {
|
|
4947
|
-
const gitignorePath = (0,
|
|
4948
|
-
const gitignore = (0,
|
|
4564
|
+
const gitignorePath = (0, import_node_path16.join)(cwd, ".gitignore");
|
|
4565
|
+
const gitignore = (0, import_node_fs4.readFileSync)(gitignorePath, "utf8");
|
|
4949
4566
|
checks.push({
|
|
4950
4567
|
name: ".gitignore includes .nestjs-inertia/",
|
|
4951
4568
|
pass: gitignore.includes(".nestjs-inertia"),
|
|
4952
4569
|
fix: "Add .nestjs-inertia/ to .gitignore",
|
|
4953
|
-
autoFix: () => (0,
|
|
4570
|
+
autoFix: () => (0, import_node_fs4.appendFileSync)(gitignorePath, "\n.nestjs-inertia/\n")
|
|
4954
4571
|
});
|
|
4955
4572
|
}
|
|
4956
|
-
const pkgJsonPath = (0,
|
|
4573
|
+
const pkgJsonPath = (0, import_node_path16.join)(cwd, "package.json");
|
|
4957
4574
|
const pkgJson = readJson(pkgJsonPath);
|
|
4958
4575
|
const scripts = pkgJson?.scripts ?? {};
|
|
4959
4576
|
checks.push({
|