@dudousxd/nestjs-codegen 0.2.0 → 0.3.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 +41 -0
- package/README.md +161 -0
- package/dist/cli/main.cjs +1214 -1613
- package/dist/cli/main.cjs.map +1 -1
- package/dist/cli/main.js +1188 -1587
- 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-oH5t7x4G.d.cts} +56 -41
- package/dist/{index-BwIRjOQA.d.ts → index-oH5t7x4G.d.ts} +56 -41
- package/dist/index.cjs +1003 -1457
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +32 -16
- package/dist/index.d.ts +32 -16
- package/dist/index.js +977 -1430
- package/dist/index.js.map +1 -1
- package/dist/nest/index.cjs +908 -1355
- 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 +893 -1340
- 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,196 @@ 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 decls = [];
|
|
1289
|
+
const mapEntries = [];
|
|
1290
|
+
let used = false;
|
|
1291
|
+
const renderSource = (src, rename) => {
|
|
1292
|
+
if (src.schema) {
|
|
1293
|
+
const r = adapter.renderModule(src.schema);
|
|
1294
|
+
for (const [n, t] of r.namedNestedSchemas) irNamed.set(n, t);
|
|
1295
|
+
return { text: r.schemaText };
|
|
1296
|
+
}
|
|
1297
|
+
if (src.zodText) {
|
|
1298
|
+
if (!acceptsRawZod) {
|
|
1299
|
+
return {
|
|
1300
|
+
text: "",
|
|
1301
|
+
warn: `is a defineContract (zod) schema; not translated to ${adapter.name} \u2014 use the zod adapter.`
|
|
1302
|
+
};
|
|
1303
|
+
}
|
|
1304
|
+
return { text: applyRenames(src.zodText, rename) };
|
|
1305
|
+
}
|
|
1306
|
+
if (src.zodRef) {
|
|
1307
|
+
if (!acceptsRawZod) {
|
|
1308
|
+
return {
|
|
1309
|
+
text: "",
|
|
1310
|
+
warn: `is a defineContract (zod) schema; not translated to ${adapter.name} \u2014 use the zod adapter.`
|
|
1311
|
+
};
|
|
1312
|
+
}
|
|
1313
|
+
const root = refRootIdentifier(src.zodRef.name);
|
|
1314
|
+
const alias = refAlias.get(`${src.zodRef.filePath}\0${root}`) ?? root;
|
|
1315
|
+
const member = src.zodRef.name.slice(root.length);
|
|
1316
|
+
return { text: `${alias}${member}` };
|
|
1317
|
+
}
|
|
1318
|
+
return null;
|
|
1319
|
+
};
|
|
1320
|
+
for (const entry of entries) {
|
|
1321
|
+
const block = [];
|
|
1322
|
+
const rename = renamesByEntry.get(entry) ?? null;
|
|
1323
|
+
let bodyConst;
|
|
1324
|
+
if (entry.warnings && entry.warnings.length > 0) {
|
|
1325
|
+
for (const w of entry.warnings) block.push(`// warning: ${w}`);
|
|
1326
|
+
}
|
|
1327
|
+
if (entry.body) {
|
|
1328
|
+
const rendered = renderSource(entry.body, rename);
|
|
1329
|
+
if (rendered?.warn) {
|
|
1330
|
+
block.push(`// warning: ${entry.routeName} body ${rendered.warn}`);
|
|
1331
|
+
} else if (rendered) {
|
|
1332
|
+
used = true;
|
|
1333
|
+
bodyConst = `${entry.baseName}BodySchema`;
|
|
1334
|
+
block.push(`export const ${bodyConst} = ${rendered.text};`);
|
|
1335
|
+
block.push(`export type ${entry.baseName}Body = ${adapter.inferType(bodyConst)};`);
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
if (entry.query) {
|
|
1339
|
+
const rendered = renderSource(entry.query, rename);
|
|
1340
|
+
if (rendered?.warn) {
|
|
1341
|
+
block.push(`// warning: ${entry.routeName} query ${rendered.warn}`);
|
|
1342
|
+
} else if (rendered) {
|
|
1343
|
+
used = true;
|
|
1344
|
+
const queryConst = `${entry.baseName}QuerySchema`;
|
|
1345
|
+
block.push(`export const ${queryConst} = ${rendered.text};`);
|
|
1346
|
+
block.push(`export type ${entry.baseName}Query = ${adapter.inferType(queryConst)};`);
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
if (block.length === 0) continue;
|
|
1350
|
+
decls.push(`// ${entry.routeName}`, ...block, "");
|
|
1351
|
+
if (bodyConst) mapEntries.push(` ${JSON.stringify(entry.routeName)}: ${bodyConst},`);
|
|
1352
|
+
}
|
|
1353
|
+
if (!used) return null;
|
|
1354
|
+
const lines = ["// Generated by @dudousxd/nestjs-codegen. Do not edit."];
|
|
1355
|
+
if (acceptsRawZod) {
|
|
1356
|
+
const zodImport = config?.zodImport ?? "zod";
|
|
1357
|
+
lines.push(`import { z } from '${zodImport}';`);
|
|
1358
|
+
} else {
|
|
1359
|
+
for (const imp of adapter.importStatements({ used: true })) lines.push(imp);
|
|
1360
|
+
}
|
|
1361
|
+
lines.push(...importLines);
|
|
1362
|
+
lines.push("");
|
|
1363
|
+
const allNested = /* @__PURE__ */ new Map();
|
|
1364
|
+
for (const [n, t] of globalSchemas) allNested.set(n, t);
|
|
1365
|
+
for (const [n, t] of irNamed) if (!allNested.has(n)) allNested.set(n, t);
|
|
1366
|
+
if (allNested.size > 0) {
|
|
1367
|
+
lines.push("// Hoisted nested schemas (shared across endpoints).");
|
|
1368
|
+
for (const [n, t] of allNested) lines.push(`const ${n} = ${t};`);
|
|
1369
|
+
lines.push("");
|
|
1370
|
+
}
|
|
1371
|
+
lines.push(...decls);
|
|
1372
|
+
lines.push("/** Route name \u2192 body schema map. */");
|
|
1373
|
+
lines.push("export const formSchemas = {");
|
|
1374
|
+
lines.push(...mapEntries);
|
|
1375
|
+
lines.push("} as const;");
|
|
1376
|
+
lines.push("");
|
|
1377
|
+
return lines.join("\n");
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
// src/emit/emit-index.ts
|
|
1381
|
+
var import_promises6 = require("fs/promises");
|
|
1382
|
+
var import_node_path7 = require("path");
|
|
1383
|
+
async function emitIndex(outDir, hasContracts = false, hasForms = false) {
|
|
1384
|
+
await (0, import_promises6.mkdir)(outDir, { recursive: true });
|
|
1385
|
+
const exports2 = ["export * from './pages.js';", "export * from './routes.js';"];
|
|
1386
|
+
if (hasContracts) {
|
|
1387
|
+
exports2.push("export * from './api.js';");
|
|
1388
|
+
}
|
|
1389
|
+
if (hasForms) {
|
|
1390
|
+
exports2.push("export * from './forms.js';");
|
|
1391
|
+
}
|
|
1392
|
+
const content = ["// Generated by @dudousxd/nestjs-codegen. Do not edit.", ...exports2, ""].join(
|
|
1393
|
+
"\n"
|
|
1394
|
+
);
|
|
1395
|
+
await (0, import_promises6.writeFile)((0, import_node_path7.join)(outDir, "index.d.ts"), content, "utf8");
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
// src/emit/emit-pages.ts
|
|
1399
|
+
var import_promises7 = require("fs/promises");
|
|
1400
|
+
var import_node_path8 = require("path");
|
|
1401
|
+
async function emitPages(pages, outDir, _options = {}) {
|
|
1402
|
+
await (0, import_promises7.mkdir)(outDir, { recursive: true });
|
|
1403
|
+
const pageNameUnion = pages.length > 0 ? pages.map((p) => JSON.stringify(p.name)).join(" | ") : "never";
|
|
1404
|
+
const augBody = pages.map((p) => {
|
|
1405
|
+
const key = needsQuotes(p.name) ? JSON.stringify(p.name) : p.name;
|
|
1406
|
+
const valueType = buildAugmentationType(p, outDir);
|
|
1505
1407
|
return ` ${key}: ${valueType};`;
|
|
1506
1408
|
}).join("\n");
|
|
1507
1409
|
const propsHelper = "\nexport type InertiaProps<K extends InertiaPageName> = import('@dudousxd/nestjs-inertia').InertiaPages[K];\n";
|
|
@@ -1516,7 +1418,7 @@ ${augBody}
|
|
|
1516
1418
|
}
|
|
1517
1419
|
${sharedPropsBlock}}
|
|
1518
1420
|
`;
|
|
1519
|
-
await (0, import_promises7.writeFile)((0,
|
|
1421
|
+
await (0, import_promises7.writeFile)((0, import_node_path8.join)(outDir, "pages.d.ts"), content, "utf8");
|
|
1520
1422
|
}
|
|
1521
1423
|
function buildSharedPropsBlock(sharedProps) {
|
|
1522
1424
|
if (!sharedProps) return "";
|
|
@@ -1535,7 +1437,7 @@ ${propsBody}
|
|
|
1535
1437
|
`;
|
|
1536
1438
|
}
|
|
1537
1439
|
function buildAugmentationType(page, outDir) {
|
|
1538
|
-
let importPath = (0,
|
|
1440
|
+
let importPath = (0, import_node_path8.relative)(outDir, page.absolutePath).replace(/\.(tsx?|vue|svelte)$/, "");
|
|
1539
1441
|
if (!importPath.startsWith(".")) {
|
|
1540
1442
|
importPath = `./${importPath}`;
|
|
1541
1443
|
}
|
|
@@ -1547,11 +1449,11 @@ function needsQuotes(name) {
|
|
|
1547
1449
|
|
|
1548
1450
|
// src/emit/emit-routes.ts
|
|
1549
1451
|
var import_promises8 = require("fs/promises");
|
|
1550
|
-
var
|
|
1452
|
+
var import_node_path9 = require("path");
|
|
1551
1453
|
async function emitRoutes(routes, outDir) {
|
|
1552
1454
|
await (0, import_promises8.mkdir)(outDir, { recursive: true });
|
|
1553
1455
|
const content = buildRoutesFile(routes);
|
|
1554
|
-
await (0, import_promises8.writeFile)((0,
|
|
1456
|
+
await (0, import_promises8.writeFile)((0, import_node_path9.join)(outDir, "routes.ts"), content, "utf8");
|
|
1555
1457
|
}
|
|
1556
1458
|
function buildRoutesFile(routes) {
|
|
1557
1459
|
if (routes.length === 0) {
|
|
@@ -1679,30 +1581,7 @@ async function generate(config, inputRoutes = []) {
|
|
|
1679
1581
|
propsExport: pagesConfig.propsExport,
|
|
1680
1582
|
componentNameStrategy: pagesConfig.componentNameStrategy
|
|
1681
1583
|
});
|
|
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
|
-
}
|
|
1584
|
+
const sharedProps = discoverSharedPropsFromConfig(config);
|
|
1706
1585
|
await emitPages(pages, config.codegen.outDir, {
|
|
1707
1586
|
propsExport: pagesConfig.propsExport,
|
|
1708
1587
|
sharedProps
|
|
@@ -1726,8 +1605,8 @@ async function generate(config, inputRoutes = []) {
|
|
|
1726
1605
|
if (extensions.length > 0) {
|
|
1727
1606
|
const extraFiles = await collectEmittedFiles(extensions, ctx);
|
|
1728
1607
|
for (const file of extraFiles) {
|
|
1729
|
-
const dest = (0,
|
|
1730
|
-
await (0, import_promises9.mkdir)((0,
|
|
1608
|
+
const dest = (0, import_node_path10.join)(config.codegen.outDir, file.path);
|
|
1609
|
+
await (0, import_promises9.mkdir)((0, import_node_path10.dirname)(dest), { recursive: true });
|
|
1731
1610
|
await (0, import_promises9.writeFile)(dest, file.contents, "utf8");
|
|
1732
1611
|
}
|
|
1733
1612
|
}
|
|
@@ -1735,35 +1614,31 @@ async function generate(config, inputRoutes = []) {
|
|
|
1735
1614
|
|
|
1736
1615
|
// src/watch/watcher.ts
|
|
1737
1616
|
var import_promises12 = require("fs/promises");
|
|
1738
|
-
var
|
|
1617
|
+
var import_node_path14 = require("path");
|
|
1739
1618
|
var import_chokidar = __toESM(require("chokidar"), 1);
|
|
1740
1619
|
|
|
1741
1620
|
// src/discovery/contracts-fast.ts
|
|
1742
|
-
var
|
|
1621
|
+
var import_node_path12 = require("path");
|
|
1743
1622
|
var import_fast_glob2 = __toESM(require("fast-glob"), 1);
|
|
1744
1623
|
var import_ts_morph9 = require("ts-morph");
|
|
1745
1624
|
|
|
1625
|
+
// src/discovery/dto-type-resolver.ts
|
|
1626
|
+
var import_ts_morph7 = require("ts-morph");
|
|
1627
|
+
|
|
1746
1628
|
// src/discovery/dto-to-ir.ts
|
|
1747
|
-
var
|
|
1629
|
+
var import_ts_morph4 = require("ts-morph");
|
|
1748
1630
|
|
|
1749
1631
|
// src/discovery/type-ref-resolution.ts
|
|
1750
1632
|
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;
|
|
1633
|
+
var import_node_path11 = require("path");
|
|
1634
|
+
var import_ts_morph3 = require("ts-morph");
|
|
1635
|
+
var _EMPTY_CTX = { projectRoot: "", tsconfigPaths: null };
|
|
1636
|
+
var _ctxByProject = /* @__PURE__ */ new WeakMap();
|
|
1637
|
+
function setDiscoveryContext(project, ctx) {
|
|
1638
|
+
_ctxByProject.set(project, ctx);
|
|
1764
1639
|
}
|
|
1765
|
-
function
|
|
1766
|
-
return
|
|
1640
|
+
function _ctxFor(project) {
|
|
1641
|
+
return _ctxByProject.get(project) ?? _EMPTY_CTX;
|
|
1767
1642
|
}
|
|
1768
1643
|
var _debug = process.env.NESTJS_INERTIA_DEBUG === "1";
|
|
1769
1644
|
function dbg(...args) {
|
|
@@ -1805,18 +1680,19 @@ function findTypeInFile(name, file) {
|
|
|
1805
1680
|
}
|
|
1806
1681
|
return null;
|
|
1807
1682
|
}
|
|
1808
|
-
function resolveModuleSpecifier(moduleSpecifier, sourceFile,
|
|
1683
|
+
function resolveModuleSpecifier(moduleSpecifier, sourceFile, project) {
|
|
1809
1684
|
if (moduleSpecifier.startsWith(".")) {
|
|
1810
|
-
const dir = (0,
|
|
1685
|
+
const dir = (0, import_node_path11.dirname)(sourceFile.getFilePath());
|
|
1811
1686
|
const noExt = moduleSpecifier.replace(/\.(js|ts)$/, "");
|
|
1812
1687
|
return [
|
|
1813
|
-
(0,
|
|
1814
|
-
(0,
|
|
1815
|
-
(0,
|
|
1688
|
+
(0, import_node_path11.resolve)(dir, `${noExt}.ts`),
|
|
1689
|
+
(0, import_node_path11.resolve)(dir, `${moduleSpecifier}.ts`),
|
|
1690
|
+
(0, import_node_path11.resolve)(dir, moduleSpecifier, "index.ts")
|
|
1816
1691
|
];
|
|
1817
1692
|
}
|
|
1818
|
-
const
|
|
1819
|
-
const
|
|
1693
|
+
const ctx = _ctxFor(project);
|
|
1694
|
+
const baseUrl = ctx.projectRoot;
|
|
1695
|
+
const tsconfigPaths = ctx.tsconfigPaths;
|
|
1820
1696
|
dbg(
|
|
1821
1697
|
"resolveModuleSpecifier",
|
|
1822
1698
|
moduleSpecifier,
|
|
@@ -1832,8 +1708,8 @@ function resolveModuleSpecifier(moduleSpecifier, sourceFile, _project) {
|
|
|
1832
1708
|
const rest = moduleSpecifier.slice(prefix.length);
|
|
1833
1709
|
const candidates = [];
|
|
1834
1710
|
for (const mapping of mappings) {
|
|
1835
|
-
const resolved = (0,
|
|
1836
|
-
candidates.push(`${resolved}.ts`, (0,
|
|
1711
|
+
const resolved = (0, import_node_path11.resolve)(baseUrl, mapping.replace("*", rest));
|
|
1712
|
+
candidates.push(`${resolved}.ts`, (0, import_node_path11.resolve)(resolved, "index.ts"));
|
|
1837
1713
|
}
|
|
1838
1714
|
dbg(" resolved candidates:", candidates);
|
|
1839
1715
|
return candidates;
|
|
@@ -1933,13 +1809,13 @@ function resolveTypeRef(nodeOrName, sourceFile, project, opts) {
|
|
|
1933
1809
|
name = nodeOrName;
|
|
1934
1810
|
} else {
|
|
1935
1811
|
const typeNode = nodeOrName;
|
|
1936
|
-
if (opts.unwrapContainers &&
|
|
1812
|
+
if (opts.unwrapContainers && import_ts_morph3.Node.isArrayTypeNode(typeNode)) {
|
|
1937
1813
|
const inner = resolveTypeRef(typeNode.getElementTypeNode(), sourceFile, project, opts);
|
|
1938
1814
|
return inner ? { ...inner, isArray: true } : null;
|
|
1939
1815
|
}
|
|
1940
|
-
if (!
|
|
1816
|
+
if (!import_ts_morph3.Node.isTypeReference(typeNode)) return null;
|
|
1941
1817
|
const typeName = typeNode.getTypeName();
|
|
1942
|
-
const refName =
|
|
1818
|
+
const refName = import_ts_morph3.Node.isIdentifier(typeName) ? typeName.getText() : null;
|
|
1943
1819
|
if (!refName) return null;
|
|
1944
1820
|
if (opts.unwrapContainers && refName === "Promise") {
|
|
1945
1821
|
const first = typeNode.getTypeArguments()[0];
|
|
@@ -1962,7 +1838,7 @@ function resolveTypeRef(nodeOrName, sourceFile, project, opts) {
|
|
|
1962
1838
|
if (!namedImport) continue;
|
|
1963
1839
|
const moduleSpecifier = importDecl.getModuleSpecifierValue();
|
|
1964
1840
|
if (opts.allowBareSpecifier && !moduleSpecifier.startsWith(".") && !moduleSpecifier.startsWith("/")) {
|
|
1965
|
-
const tsconfigPaths =
|
|
1841
|
+
const tsconfigPaths = _ctxFor(project).tsconfigPaths;
|
|
1966
1842
|
const isAlias = tsconfigPaths != null && Object.keys(tsconfigPaths).some((p) => {
|
|
1967
1843
|
const prefix = p.replace("*", "");
|
|
1968
1844
|
return moduleSpecifier.startsWith(prefix);
|
|
@@ -2234,22 +2110,22 @@ function firstArgText(decorator) {
|
|
|
2234
2110
|
}
|
|
2235
2111
|
function numericArg(decorator) {
|
|
2236
2112
|
const arg = firstArg(decorator);
|
|
2237
|
-
if (arg &&
|
|
2113
|
+
if (arg && import_ts_morph4.Node.isNumericLiteral(arg)) return arg.getText();
|
|
2238
2114
|
return null;
|
|
2239
2115
|
}
|
|
2240
2116
|
function numericArgs(decorator) {
|
|
2241
2117
|
const args = decorator?.getArguments() ?? [];
|
|
2242
|
-
const num = (n) => n &&
|
|
2118
|
+
const num = (n) => n && import_ts_morph4.Node.isNumericLiteral(n) ? n.getText() : null;
|
|
2243
2119
|
return [num(args[0]), num(args[1])];
|
|
2244
2120
|
}
|
|
2245
2121
|
function messageRaw(decorator) {
|
|
2246
2122
|
const args = decorator?.getArguments() ?? [];
|
|
2247
2123
|
for (const arg of args) {
|
|
2248
|
-
if (
|
|
2124
|
+
if (import_ts_morph4.Node.isObjectLiteralExpression(arg)) {
|
|
2249
2125
|
for (const prop of arg.getProperties()) {
|
|
2250
|
-
if (
|
|
2126
|
+
if (import_ts_morph4.Node.isPropertyAssignment(prop) && prop.getName() === "message") {
|
|
2251
2127
|
const init = prop.getInitializer();
|
|
2252
|
-
if (init &&
|
|
2128
|
+
if (init && import_ts_morph4.Node.isStringLiteral(init)) return init.getText();
|
|
2253
2129
|
}
|
|
2254
2130
|
}
|
|
2255
2131
|
}
|
|
@@ -2259,9 +2135,9 @@ function messageRaw(decorator) {
|
|
|
2259
2135
|
function resolveTypeFactoryName(decorator) {
|
|
2260
2136
|
const arg = firstArg(decorator);
|
|
2261
2137
|
if (!arg) return null;
|
|
2262
|
-
if (
|
|
2138
|
+
if (import_ts_morph4.Node.isArrowFunction(arg)) {
|
|
2263
2139
|
const body = arg.getBody();
|
|
2264
|
-
if (
|
|
2140
|
+
if (import_ts_morph4.Node.isIdentifier(body)) return body.getText();
|
|
2265
2141
|
}
|
|
2266
2142
|
return null;
|
|
2267
2143
|
}
|
|
@@ -2272,7 +2148,7 @@ function singularClassName(typeText) {
|
|
|
2272
2148
|
function enumSchemaFromDecorator(decorator, classFile, ctx) {
|
|
2273
2149
|
const arg = firstArg(decorator);
|
|
2274
2150
|
if (!arg) return null;
|
|
2275
|
-
if (
|
|
2151
|
+
if (import_ts_morph4.Node.isIdentifier(arg)) {
|
|
2276
2152
|
const name = arg.getText();
|
|
2277
2153
|
const resolved = findType(name, classFile, ctx.project);
|
|
2278
2154
|
if (resolved && resolved.kind === "enum") {
|
|
@@ -2286,12 +2162,12 @@ function enumSchemaFromDecorator(decorator, classFile, ctx) {
|
|
|
2286
2162
|
}
|
|
2287
2163
|
return { kind: "unknown", note: `@IsEnum(${name}): enum not resolvable to literals` };
|
|
2288
2164
|
}
|
|
2289
|
-
if (
|
|
2165
|
+
if (import_ts_morph4.Node.isObjectLiteralExpression(arg)) {
|
|
2290
2166
|
const values = [];
|
|
2291
2167
|
for (const p of arg.getProperties()) {
|
|
2292
|
-
if (!
|
|
2168
|
+
if (!import_ts_morph4.Node.isPropertyAssignment(p)) continue;
|
|
2293
2169
|
const init = p.getInitializer();
|
|
2294
|
-
if (init &&
|
|
2170
|
+
if (init && import_ts_morph4.Node.isStringLiteral(init)) values.push(init.getText());
|
|
2295
2171
|
}
|
|
2296
2172
|
if (values.length > 0) return { kind: "enum", literals: values };
|
|
2297
2173
|
}
|
|
@@ -2299,9 +2175,9 @@ function enumSchemaFromDecorator(decorator, classFile, ctx) {
|
|
|
2299
2175
|
}
|
|
2300
2176
|
function inSchemaFromDecorator(decorator) {
|
|
2301
2177
|
const arg = firstArg(decorator);
|
|
2302
|
-
if (arg &&
|
|
2178
|
+
if (arg && import_ts_morph4.Node.isArrayLiteralExpression(arg)) {
|
|
2303
2179
|
const elements = arg.getElements();
|
|
2304
|
-
const allStrings = elements.every((e) =>
|
|
2180
|
+
const allStrings = elements.every((e) => import_ts_morph4.Node.isStringLiteral(e));
|
|
2305
2181
|
if (allStrings && elements.length > 0) {
|
|
2306
2182
|
return { kind: "enum", literals: elements.map((e) => e.getText()) };
|
|
2307
2183
|
}
|
|
@@ -2315,427 +2191,99 @@ function inSchemaFromDecorator(decorator) {
|
|
|
2315
2191
|
return null;
|
|
2316
2192
|
}
|
|
2317
2193
|
|
|
2318
|
-
// src/discovery/
|
|
2194
|
+
// src/discovery/filter-for.ts
|
|
2319
2195
|
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
|
-
};
|
|
2196
|
+
|
|
2197
|
+
// src/discovery/filter-field-types.ts
|
|
2198
|
+
var import_ts_morph5 = require("ts-morph");
|
|
2199
|
+
|
|
2200
|
+
// src/discovery/enum-resolution.ts
|
|
2201
|
+
function resolveEnumValues(name, sourceFile, project) {
|
|
2202
|
+
const resolved = findType(name, sourceFile, project);
|
|
2203
|
+
if (!resolved || resolved.kind !== "enum") return null;
|
|
2204
|
+
let numeric = true;
|
|
2205
|
+
const values = resolved.members.map((m) => {
|
|
2206
|
+
const parsed = JSON.parse(m);
|
|
2207
|
+
if (typeof parsed === "string") numeric = false;
|
|
2208
|
+
return String(parsed);
|
|
2209
|
+
});
|
|
2210
|
+
if (values.length === 0) return null;
|
|
2211
|
+
return { values, numeric };
|
|
2369
2212
|
}
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2213
|
+
|
|
2214
|
+
// src/discovery/filter-field-types.ts
|
|
2215
|
+
var STRING_TYPE_KEYWORDS = ["varchar", "text", "string", "char", "uuid", "enum"];
|
|
2216
|
+
var NUMBER_TYPE_KEYWORDS = ["int", "float", "double", "decimal", "number", "numeric", "real"];
|
|
2217
|
+
var BOOLEAN_TYPE_KEYWORDS = ["bool", "boolean", "bit"];
|
|
2218
|
+
var DATE_TYPE_KEYWORDS = ["date", "time", "timestamp", "datetime"];
|
|
2219
|
+
var JSON_TYPE_KEYWORDS = ["json", "jsonb"];
|
|
2220
|
+
function classifyTypeKeyword(raw) {
|
|
2221
|
+
const t = raw.toLowerCase();
|
|
2222
|
+
if (STRING_TYPE_KEYWORDS.some((s) => t.includes(s))) return "string";
|
|
2223
|
+
if (NUMBER_TYPE_KEYWORDS.some((s) => t.includes(s))) return "number";
|
|
2224
|
+
if (BOOLEAN_TYPE_KEYWORDS.some((s) => t.includes(s))) return "boolean";
|
|
2225
|
+
if (DATE_TYPE_KEYWORDS.some((s) => t.includes(s))) return "date";
|
|
2226
|
+
if (JSON_TYPE_KEYWORDS.some((s) => t.includes(s))) return "json";
|
|
2227
|
+
return null;
|
|
2382
2228
|
}
|
|
2383
|
-
function
|
|
2384
|
-
return
|
|
2229
|
+
function markNullable(r, nullable) {
|
|
2230
|
+
return nullable ? { ...r, nullable: true } : r;
|
|
2385
2231
|
}
|
|
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
|
-
if (has("Allow")) base = "z.unknown()";
|
|
2415
|
-
if (has("IsEmail")) {
|
|
2416
|
-
base = ensureStringBase(base);
|
|
2417
|
-
refinements.push(`.email(${messageArg(dec("IsEmail"))})`);
|
|
2418
|
-
}
|
|
2419
|
-
if (has("IsUrl")) {
|
|
2420
|
-
base = ensureStringBase(base);
|
|
2421
|
-
refinements.push(`.url(${messageArg(dec("IsUrl"))})`);
|
|
2422
|
-
}
|
|
2423
|
-
if (has("IsUUID")) {
|
|
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})`);
|
|
2232
|
+
function classifyTypeNode(typeNode, sourceFile, project, opts) {
|
|
2233
|
+
if (import_ts_morph5.Node.isUnionTypeNode(typeNode)) {
|
|
2234
|
+
let nullable = false;
|
|
2235
|
+
const stringLits = [];
|
|
2236
|
+
const numberLits = [];
|
|
2237
|
+
const others = [];
|
|
2238
|
+
for (const member of typeNode.getTypeNodes()) {
|
|
2239
|
+
const kind = member.getKind();
|
|
2240
|
+
if (kind === import_ts_morph5.SyntaxKind.NullKeyword || kind === import_ts_morph5.SyntaxKind.UndefinedKeyword) {
|
|
2241
|
+
nullable = true;
|
|
2242
|
+
continue;
|
|
2243
|
+
}
|
|
2244
|
+
if (import_ts_morph5.Node.isLiteralTypeNode(member)) {
|
|
2245
|
+
const lit = member.getLiteral();
|
|
2246
|
+
if (import_ts_morph5.Node.isStringLiteral(lit)) {
|
|
2247
|
+
stringLits.push(lit.getLiteralValue());
|
|
2248
|
+
continue;
|
|
2249
|
+
}
|
|
2250
|
+
if (import_ts_morph5.Node.isNumericLiteral(lit)) {
|
|
2251
|
+
numberLits.push(lit.getText());
|
|
2252
|
+
continue;
|
|
2253
|
+
}
|
|
2254
|
+
if (lit.getKind() === import_ts_morph5.SyntaxKind.NullKeyword) {
|
|
2255
|
+
nullable = true;
|
|
2256
|
+
continue;
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
others.push(member);
|
|
2432
2260
|
}
|
|
2261
|
+
if (others.length === 0 && stringLits.length > 0 && numberLits.length === 0) {
|
|
2262
|
+
return markNullable({ kind: "string", enumValues: stringLits }, nullable);
|
|
2263
|
+
}
|
|
2264
|
+
if (others.length === 0 && numberLits.length > 0 && stringLits.length === 0) {
|
|
2265
|
+
return markNullable({ kind: "number", enumValues: numberLits, numericEnum: true }, nullable);
|
|
2266
|
+
}
|
|
2267
|
+
if (others.length === 1) {
|
|
2268
|
+
const inner = classifyTypeNode(others[0], sourceFile, project, opts);
|
|
2269
|
+
return markNullable(inner, nullable || inner.nullable === true);
|
|
2270
|
+
}
|
|
2271
|
+
return markNullable({ kind: "unknown" }, nullable);
|
|
2433
2272
|
}
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
}
|
|
2447
|
-
if (has("Min")) {
|
|
2448
|
-
const n = numericArg2(dec("Min"));
|
|
2449
|
-
if (n !== null) refinements.push(`.min(${n})`);
|
|
2273
|
+
switch (typeNode.getKind()) {
|
|
2274
|
+
case import_ts_morph5.SyntaxKind.StringKeyword:
|
|
2275
|
+
return { kind: "string" };
|
|
2276
|
+
case import_ts_morph5.SyntaxKind.NumberKeyword:
|
|
2277
|
+
return { kind: "number" };
|
|
2278
|
+
case import_ts_morph5.SyntaxKind.BooleanKeyword:
|
|
2279
|
+
return { kind: "boolean" };
|
|
2280
|
+
case import_ts_morph5.SyntaxKind.AnyKeyword:
|
|
2281
|
+
case import_ts_morph5.SyntaxKind.UnknownKeyword:
|
|
2282
|
+
return { kind: "unknown" };
|
|
2283
|
+
default:
|
|
2284
|
+
break;
|
|
2450
2285
|
}
|
|
2451
|
-
if (
|
|
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)) {
|
|
2286
|
+
if (import_ts_morph5.Node.isTypeReference(typeNode)) {
|
|
2739
2287
|
const refName = typeNode.getTypeName().getText();
|
|
2740
2288
|
if (refName === "Date") return { kind: "date" };
|
|
2741
2289
|
if (refName === "Record" || refName === "Object") return { kind: "json" };
|
|
@@ -2748,25 +2296,25 @@ function classifyTypeNode(typeNode, sourceFile, project, opts) {
|
|
|
2748
2296
|
if (typeRef) return { kind: "unknown", typeRef };
|
|
2749
2297
|
return { kind: "unknown" };
|
|
2750
2298
|
}
|
|
2751
|
-
if (
|
|
2299
|
+
if (import_ts_morph5.Node.isTypeLiteral(typeNode)) return { kind: "json" };
|
|
2752
2300
|
return { kind: "unknown" };
|
|
2753
2301
|
}
|
|
2754
2302
|
function enumFromDecoratorArgs(args, sourceFile, project) {
|
|
2755
2303
|
for (const arg of args) {
|
|
2756
|
-
if (
|
|
2304
|
+
if (import_ts_morph5.Node.isArrowFunction(arg)) {
|
|
2757
2305
|
const body = arg.getBody();
|
|
2758
|
-
if (
|
|
2306
|
+
if (import_ts_morph5.Node.isIdentifier(body)) {
|
|
2759
2307
|
const en = resolveEnumValues(body.getText(), sourceFile, project);
|
|
2760
2308
|
if (en) return en;
|
|
2761
2309
|
}
|
|
2762
2310
|
}
|
|
2763
|
-
if (
|
|
2311
|
+
if (import_ts_morph5.Node.isObjectLiteralExpression(arg)) {
|
|
2764
2312
|
const itemsProp = arg.getProperty("items");
|
|
2765
|
-
if (itemsProp &&
|
|
2313
|
+
if (itemsProp && import_ts_morph5.Node.isPropertyAssignment(itemsProp)) {
|
|
2766
2314
|
const init = itemsProp.getInitializer();
|
|
2767
|
-
if (init &&
|
|
2315
|
+
if (init && import_ts_morph5.Node.isArrowFunction(init)) {
|
|
2768
2316
|
const body = init.getBody();
|
|
2769
|
-
if (
|
|
2317
|
+
if (import_ts_morph5.Node.isIdentifier(body)) {
|
|
2770
2318
|
const en = resolveEnumValues(body.getText(), sourceFile, project);
|
|
2771
2319
|
if (en) return en;
|
|
2772
2320
|
}
|
|
@@ -2789,7 +2337,7 @@ function classifyFromColumnDecorator(prop, sourceFile, project) {
|
|
|
2789
2337
|
return { kind: "string" };
|
|
2790
2338
|
}
|
|
2791
2339
|
for (const arg of args) {
|
|
2792
|
-
if (
|
|
2340
|
+
if (import_ts_morph5.Node.isStringLiteral(arg)) {
|
|
2793
2341
|
const raw = arg.getLiteralValue();
|
|
2794
2342
|
const kind = classifyTypeKeyword(raw);
|
|
2795
2343
|
if (kind) {
|
|
@@ -2797,11 +2345,11 @@ function classifyFromColumnDecorator(prop, sourceFile, project) {
|
|
|
2797
2345
|
return { kind };
|
|
2798
2346
|
}
|
|
2799
2347
|
}
|
|
2800
|
-
if (
|
|
2348
|
+
if (import_ts_morph5.Node.isObjectLiteralExpression(arg)) {
|
|
2801
2349
|
const enumProp = arg.getProperty("enum");
|
|
2802
|
-
if (enumProp &&
|
|
2350
|
+
if (enumProp && import_ts_morph5.Node.isPropertyAssignment(enumProp)) {
|
|
2803
2351
|
const init = enumProp.getInitializer();
|
|
2804
|
-
if (init &&
|
|
2352
|
+
if (init && import_ts_morph5.Node.isIdentifier(init)) {
|
|
2805
2353
|
const en = resolveEnumValues(init.getText(), sourceFile, project);
|
|
2806
2354
|
if (en) {
|
|
2807
2355
|
return en.numeric ? { kind: "number", enumValues: en.values, numericEnum: true } : { kind: "string", enumValues: en.values };
|
|
@@ -2810,9 +2358,9 @@ function classifyFromColumnDecorator(prop, sourceFile, project) {
|
|
|
2810
2358
|
}
|
|
2811
2359
|
}
|
|
2812
2360
|
const typeProp = arg.getProperty("type");
|
|
2813
|
-
if (typeProp &&
|
|
2361
|
+
if (typeProp && import_ts_morph5.Node.isPropertyAssignment(typeProp)) {
|
|
2814
2362
|
const init = typeProp.getInitializer();
|
|
2815
|
-
if (init &&
|
|
2363
|
+
if (init && import_ts_morph5.Node.isStringLiteral(init)) {
|
|
2816
2364
|
const kind = classifyTypeKeyword(init.getLiteralValue());
|
|
2817
2365
|
if (kind) return { kind };
|
|
2818
2366
|
}
|
|
@@ -2848,7 +2396,7 @@ function toFilterFieldType(name, r) {
|
|
|
2848
2396
|
|
|
2849
2397
|
// src/discovery/filter-for.ts
|
|
2850
2398
|
function classifyFilterForHint(typeInit) {
|
|
2851
|
-
if (
|
|
2399
|
+
if (import_ts_morph6.Node.isStringLiteral(typeInit)) {
|
|
2852
2400
|
switch (typeInit.getLiteralValue()) {
|
|
2853
2401
|
case "string":
|
|
2854
2402
|
return { kind: "string" };
|
|
@@ -2862,10 +2410,10 @@ function classifyFilterForHint(typeInit) {
|
|
|
2862
2410
|
return null;
|
|
2863
2411
|
}
|
|
2864
2412
|
}
|
|
2865
|
-
if (
|
|
2413
|
+
if (import_ts_morph6.Node.isArrayLiteralExpression(typeInit)) {
|
|
2866
2414
|
const values = [];
|
|
2867
2415
|
for (const el of typeInit.getElements()) {
|
|
2868
|
-
if (!
|
|
2416
|
+
if (!import_ts_morph6.Node.isStringLiteral(el)) return null;
|
|
2869
2417
|
values.push(el.getLiteralValue());
|
|
2870
2418
|
}
|
|
2871
2419
|
if (values.length === 0) return null;
|
|
@@ -2900,11 +2448,11 @@ function extractFilterForHints(classDecl, project) {
|
|
|
2900
2448
|
if (!filterForDec) continue;
|
|
2901
2449
|
const args = filterForDec.getArguments();
|
|
2902
2450
|
const keyArg = args[0];
|
|
2903
|
-
const inputKey = keyArg &&
|
|
2451
|
+
const inputKey = keyArg && import_ts_morph6.Node.isStringLiteral(keyArg) ? keyArg.getLiteralValue() : method.getName();
|
|
2904
2452
|
const optsArg = args[1];
|
|
2905
|
-
if (optsArg &&
|
|
2453
|
+
if (optsArg && import_ts_morph6.Node.isObjectLiteralExpression(optsArg)) {
|
|
2906
2454
|
const typeProp = optsArg.getProperty("type");
|
|
2907
|
-
if (typeProp &&
|
|
2455
|
+
if (typeProp && import_ts_morph6.Node.isPropertyAssignment(typeProp)) {
|
|
2908
2456
|
const typeInit = typeProp.getInitializer();
|
|
2909
2457
|
if (typeInit) {
|
|
2910
2458
|
const classified = classifyFilterForHint(typeInit);
|
|
@@ -2927,14 +2475,14 @@ function extractApplyFilterInfo(method, sourceFile, project) {
|
|
|
2927
2475
|
const args = filterDecorator.getArguments();
|
|
2928
2476
|
if (args.length === 0) continue;
|
|
2929
2477
|
const filterClassArg = args[0];
|
|
2930
|
-
if (!filterClassArg || !
|
|
2478
|
+
if (!filterClassArg || !import_ts_morph6.Node.isIdentifier(filterClassArg)) continue;
|
|
2931
2479
|
let source = "query";
|
|
2932
2480
|
const optionsArg = args[1];
|
|
2933
|
-
if (optionsArg &&
|
|
2481
|
+
if (optionsArg && import_ts_morph6.Node.isObjectLiteralExpression(optionsArg)) {
|
|
2934
2482
|
const sourceProp = optionsArg.getProperty("source");
|
|
2935
|
-
if (sourceProp &&
|
|
2483
|
+
if (sourceProp && import_ts_morph6.Node.isPropertyAssignment(sourceProp)) {
|
|
2936
2484
|
const init = sourceProp.getInitializer();
|
|
2937
|
-
if (init &&
|
|
2485
|
+
if (init && import_ts_morph6.Node.isStringLiteral(init) && init.getLiteralValue() === "body") {
|
|
2938
2486
|
source = "body";
|
|
2939
2487
|
}
|
|
2940
2488
|
}
|
|
@@ -2997,22 +2545,22 @@ function resolveRelationEntity(prop, sourceFile, project) {
|
|
|
2997
2545
|
const args = dec.getArguments();
|
|
2998
2546
|
if (args.length === 0) continue;
|
|
2999
2547
|
const arg = args[0];
|
|
3000
|
-
if (
|
|
2548
|
+
if (import_ts_morph6.Node.isObjectLiteralExpression(arg)) {
|
|
3001
2549
|
const entityProp = arg.getProperty("entity");
|
|
3002
|
-
if (entityProp &&
|
|
2550
|
+
if (entityProp && import_ts_morph6.Node.isPropertyAssignment(entityProp)) {
|
|
3003
2551
|
const init = entityProp.getInitializer();
|
|
3004
|
-
if (init &&
|
|
2552
|
+
if (init && import_ts_morph6.Node.isArrowFunction(init)) {
|
|
3005
2553
|
const body = init.getBody();
|
|
3006
|
-
if (
|
|
2554
|
+
if (import_ts_morph6.Node.isIdentifier(body)) {
|
|
3007
2555
|
const resolved = findType(body.getText(), prop.getSourceFile(), project);
|
|
3008
2556
|
if (resolved?.kind === "class") return resolved.decl;
|
|
3009
2557
|
}
|
|
3010
2558
|
}
|
|
3011
2559
|
}
|
|
3012
2560
|
}
|
|
3013
|
-
if (
|
|
2561
|
+
if (import_ts_morph6.Node.isArrowFunction(arg)) {
|
|
3014
2562
|
const body = arg.getBody();
|
|
3015
|
-
if (
|
|
2563
|
+
if (import_ts_morph6.Node.isIdentifier(body)) {
|
|
3016
2564
|
const resolved = findType(body.getText(), prop.getSourceFile(), project);
|
|
3017
2565
|
if (resolved?.kind === "class") return resolved.decl;
|
|
3018
2566
|
}
|
|
@@ -3036,11 +2584,11 @@ function extractFilterableEntityFields(filterClass, project) {
|
|
|
3036
2584
|
const args = filterableDecorator.getArguments();
|
|
3037
2585
|
if (args.length === 0) return [];
|
|
3038
2586
|
const optionsArg = args[0];
|
|
3039
|
-
if (!
|
|
2587
|
+
if (!import_ts_morph6.Node.isObjectLiteralExpression(optionsArg)) return [];
|
|
3040
2588
|
const entityProp = optionsArg.getProperty("entity");
|
|
3041
|
-
if (!entityProp || !
|
|
2589
|
+
if (!entityProp || !import_ts_morph6.Node.isPropertyAssignment(entityProp)) return [];
|
|
3042
2590
|
const entityInit = entityProp.getInitializer();
|
|
3043
|
-
if (!entityInit || !
|
|
2591
|
+
if (!entityInit || !import_ts_morph6.Node.isIdentifier(entityInit)) return [];
|
|
3044
2592
|
const entityName = entityInit.getText();
|
|
3045
2593
|
const filterSourceFile = filterClass.getSourceFile();
|
|
3046
2594
|
const resolvedEntity = findType(entityName, filterSourceFile, project);
|
|
@@ -3056,17 +2604,17 @@ function extractFilterableEntityFields(filterClass, project) {
|
|
|
3056
2604
|
const relationsDecorator = filterClass.getDecorators().find((d) => d.getName() === "Relations");
|
|
3057
2605
|
if (relationsDecorator) {
|
|
3058
2606
|
const relArgs = relationsDecorator.getArguments();
|
|
3059
|
-
if (relArgs.length > 0 &&
|
|
2607
|
+
if (relArgs.length > 0 && import_ts_morph6.Node.isObjectLiteralExpression(relArgs[0])) {
|
|
3060
2608
|
for (const relProp of relArgs[0].getProperties()) {
|
|
3061
|
-
if (!
|
|
2609
|
+
if (!import_ts_morph6.Node.isPropertyAssignment(relProp)) continue;
|
|
3062
2610
|
const relInit = relProp.getInitializer();
|
|
3063
|
-
if (!relInit || !
|
|
2611
|
+
if (!relInit || !import_ts_morph6.Node.isObjectLiteralExpression(relInit)) continue;
|
|
3064
2612
|
const keysProp = relInit.getProperty("keys");
|
|
3065
|
-
if (!keysProp || !
|
|
2613
|
+
if (!keysProp || !import_ts_morph6.Node.isPropertyAssignment(keysProp)) continue;
|
|
3066
2614
|
const keysInit = keysProp.getInitializer();
|
|
3067
|
-
if (!keysInit || !
|
|
2615
|
+
if (!keysInit || !import_ts_morph6.Node.isArrayLiteralExpression(keysInit)) continue;
|
|
3068
2616
|
for (const el of keysInit.getElements()) {
|
|
3069
|
-
if (
|
|
2617
|
+
if (import_ts_morph6.Node.isStringLiteral(el)) {
|
|
3070
2618
|
fields.push(toFilterFieldType(el.getLiteralValue(), { kind: "unknown" }));
|
|
3071
2619
|
}
|
|
3072
2620
|
}
|
|
@@ -3076,267 +2624,65 @@ function extractFilterableEntityFields(filterClass, project) {
|
|
|
3076
2624
|
return fields;
|
|
3077
2625
|
}
|
|
3078
2626
|
|
|
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
|
-
|
|
2627
|
+
// src/discovery/dto-type-resolver.ts
|
|
2628
|
+
var WRAPPER_TYPES = {
|
|
2629
|
+
// MikroORM Ref/Reference/LoadedReference/IdentifiedReference are server-side
|
|
2630
|
+
// wrappers around related entities; the wire shape is just the referenced
|
|
2631
|
+
// entity. Unwrap to the type argument.
|
|
2632
|
+
Ref: "unwrap",
|
|
2633
|
+
Reference: "unwrap",
|
|
2634
|
+
LoadedReference: "unwrap",
|
|
2635
|
+
IdentifiedReference: "unwrap",
|
|
2636
|
+
// MikroORM Opt<T> is a marker, Loaded<T, ...> is a wrapper; both reduce to T.
|
|
2637
|
+
Opt: "unwrap",
|
|
2638
|
+
Loaded: "unwrap",
|
|
2639
|
+
// Promise<T> — unwrap
|
|
2640
|
+
Promise: "unwrap",
|
|
2641
|
+
// MikroORM Collection<T> serializes as an array of T on the wire.
|
|
2642
|
+
Collection: "arrayOf",
|
|
2643
|
+
// Array<T> generic form
|
|
2644
|
+
Array: "arrayOf"
|
|
2645
|
+
};
|
|
2646
|
+
var PASSTHROUGH_UTILITY = /* @__PURE__ */ new Set([
|
|
2647
|
+
"Record",
|
|
2648
|
+
"Omit",
|
|
2649
|
+
"Pick",
|
|
2650
|
+
"Partial",
|
|
2651
|
+
"Required",
|
|
2652
|
+
"Readonly",
|
|
2653
|
+
"Map",
|
|
2654
|
+
"Set"
|
|
2655
|
+
]);
|
|
2656
|
+
function resolveTypeNodeToString(typeNode, sourceFile, project, depth) {
|
|
2657
|
+
if (depth <= 0) return "unknown";
|
|
2658
|
+
if (import_ts_morph7.Node.isArrayTypeNode(typeNode)) {
|
|
2659
|
+
const elementType = typeNode.getElementTypeNode();
|
|
2660
|
+
return `Array<${resolveTypeNodeToString(elementType, sourceFile, project, depth)}>`;
|
|
3102
2661
|
}
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
project.addSourceFileAtPath(f);
|
|
2662
|
+
if (import_ts_morph7.Node.isUnionTypeNode(typeNode)) {
|
|
2663
|
+
return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth)).join(" | ");
|
|
3106
2664
|
}
|
|
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);
|
|
2665
|
+
if (import_ts_morph7.Node.isIntersectionTypeNode(typeNode)) {
|
|
2666
|
+
return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth)).join(" & ");
|
|
3118
2667
|
}
|
|
3119
|
-
|
|
3120
|
-
}
|
|
3121
|
-
|
|
3122
|
-
if (
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
if (
|
|
3128
|
-
|
|
2668
|
+
if (import_ts_morph7.Node.isParenthesizedTypeNode(typeNode)) {
|
|
2669
|
+
return `(${resolveTypeNodeToString(typeNode.getTypeNode(), sourceFile, project, depth)})`;
|
|
2670
|
+
}
|
|
2671
|
+
if (import_ts_morph7.Node.isTypeReference(typeNode)) {
|
|
2672
|
+
const typeName = typeNode.getTypeName();
|
|
2673
|
+
const name = import_ts_morph7.Node.isIdentifier(typeName) ? typeName.getText() : typeNode.getText();
|
|
2674
|
+
if (name === "string" || name === "number" || name === "boolean") return name;
|
|
2675
|
+
if (name === "Date") return "string";
|
|
2676
|
+
if (name === "unknown" || name === "any" || name === "void") return "unknown";
|
|
2677
|
+
if (name === "StreamableFile" || name === "Observable" || name === "ReadableStream")
|
|
2678
|
+
return "unknown";
|
|
2679
|
+
const wrapperMode = WRAPPER_TYPES[name];
|
|
2680
|
+
if (wrapperMode) {
|
|
2681
|
+
return unwrapFirstTypeArg(typeNode, sourceFile, project, depth, wrapperMode);
|
|
3129
2682
|
}
|
|
3130
|
-
if (
|
|
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)) {
|
|
3287
|
-
return `(${resolveTypeNodeToString(typeNode.getTypeNode(), sourceFile, project, depth)})`;
|
|
3288
|
-
}
|
|
3289
|
-
if (import_ts_morph9.Node.isTypeReference(typeNode)) {
|
|
3290
|
-
const typeName = typeNode.getTypeName();
|
|
3291
|
-
const name = import_ts_morph9.Node.isIdentifier(typeName) ? typeName.getText() : typeNode.getText();
|
|
3292
|
-
if (name === "string" || name === "number" || name === "boolean") return name;
|
|
3293
|
-
if (name === "Date") return "string";
|
|
3294
|
-
if (name === "unknown" || name === "any" || name === "void") return "unknown";
|
|
3295
|
-
if (name === "StreamableFile" || name === "Observable" || name === "ReadableStream")
|
|
3296
|
-
return "unknown";
|
|
3297
|
-
if (name === "Ref" || name === "Reference" || name === "LoadedReference" || name === "IdentifiedReference") {
|
|
3298
|
-
const typeArgs = typeNode.getTypeArguments();
|
|
3299
|
-
const firstTypeArg = typeArgs[0];
|
|
3300
|
-
if (typeArgs.length > 0 && firstTypeArg !== void 0) {
|
|
3301
|
-
return resolveTypeNodeToString(firstTypeArg, sourceFile, project, depth);
|
|
3302
|
-
}
|
|
3303
|
-
return "unknown";
|
|
3304
|
-
}
|
|
3305
|
-
if (name === "Collection") {
|
|
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)) {
|
|
2683
|
+
if (PASSTHROUGH_UTILITY.has(name)) {
|
|
3330
2684
|
return typeNode.getText();
|
|
3331
2685
|
}
|
|
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
2686
|
const resolved = findType(name, sourceFile, project);
|
|
3341
2687
|
if (resolved) {
|
|
3342
2688
|
return expandTypeDecl(resolved, project, depth - 1);
|
|
@@ -3345,13 +2691,22 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth) {
|
|
|
3345
2691
|
return "unknown";
|
|
3346
2692
|
}
|
|
3347
2693
|
const kind = typeNode.getKind();
|
|
3348
|
-
if (kind ===
|
|
3349
|
-
if (kind ===
|
|
3350
|
-
if (kind ===
|
|
3351
|
-
if (kind ===
|
|
3352
|
-
if (kind ===
|
|
2694
|
+
if (kind === import_ts_morph7.SyntaxKind.StringKeyword) return "string";
|
|
2695
|
+
if (kind === import_ts_morph7.SyntaxKind.NumberKeyword) return "number";
|
|
2696
|
+
if (kind === import_ts_morph7.SyntaxKind.BooleanKeyword) return "boolean";
|
|
2697
|
+
if (kind === import_ts_morph7.SyntaxKind.UnknownKeyword) return "unknown";
|
|
2698
|
+
if (kind === import_ts_morph7.SyntaxKind.AnyKeyword) return "unknown";
|
|
3353
2699
|
return typeNode.getText();
|
|
3354
2700
|
}
|
|
2701
|
+
function unwrapFirstTypeArg(typeNode, sourceFile, project, depth, mode) {
|
|
2702
|
+
const typeArgs = typeNode.getTypeArguments();
|
|
2703
|
+
const firstTypeArg = typeArgs[0];
|
|
2704
|
+
if (typeArgs.length > 0 && firstTypeArg !== void 0) {
|
|
2705
|
+
const inner = resolveTypeNodeToString(firstTypeArg, sourceFile, project, depth);
|
|
2706
|
+
return mode === "arrayOf" ? `Array<${inner}>` : inner;
|
|
2707
|
+
}
|
|
2708
|
+
return mode === "arrayOf" ? "Array<unknown>" : "unknown";
|
|
2709
|
+
}
|
|
3355
2710
|
function expandTypeDecl(result, project, depth) {
|
|
3356
2711
|
if (depth < 0) return "unknown";
|
|
3357
2712
|
switch (result.kind) {
|
|
@@ -3417,7 +2772,7 @@ function extractParamsType(method, sourceFile, project) {
|
|
|
3417
2772
|
const paramArgs = paramDecorator.getArguments();
|
|
3418
2773
|
if (paramArgs.length === 0) continue;
|
|
3419
2774
|
const nameArg = paramArgs[0];
|
|
3420
|
-
if (!
|
|
2775
|
+
if (!import_ts_morph7.Node.isStringLiteral(nameArg)) continue;
|
|
3421
2776
|
const paramName = nameArg.getLiteralValue();
|
|
3422
2777
|
const typeNode = param.getTypeNode();
|
|
3423
2778
|
const paramType = typeNode ? resolveTypeNodeToString(typeNode, sourceFile, project, 3) : "string";
|
|
@@ -3430,13 +2785,13 @@ function extractResponseType(method, sourceFile, project) {
|
|
|
3430
2785
|
if (apiResponseDecorator) {
|
|
3431
2786
|
const args = apiResponseDecorator.getArguments();
|
|
3432
2787
|
const optsArg = args[0];
|
|
3433
|
-
if (optsArg &&
|
|
2788
|
+
if (optsArg && import_ts_morph7.Node.isObjectLiteralExpression(optsArg)) {
|
|
3434
2789
|
for (const prop of optsArg.getProperties()) {
|
|
3435
|
-
if (!
|
|
2790
|
+
if (!import_ts_morph7.Node.isPropertyAssignment(prop)) continue;
|
|
3436
2791
|
if (prop.getName() !== "type") continue;
|
|
3437
2792
|
const val = prop.getInitializer();
|
|
3438
2793
|
if (!val) continue;
|
|
3439
|
-
if (
|
|
2794
|
+
if (import_ts_morph7.Node.isArrayLiteralExpression(val)) {
|
|
3440
2795
|
const elements = val.getElements();
|
|
3441
2796
|
const firstEl = elements[0];
|
|
3442
2797
|
if (elements.length > 0 && firstEl !== void 0) {
|
|
@@ -3456,7 +2811,7 @@ function extractResponseType(method, sourceFile, project) {
|
|
|
3456
2811
|
return "unknown";
|
|
3457
2812
|
}
|
|
3458
2813
|
function resolveIdentifierToClassType(node, sourceFile, project, depth) {
|
|
3459
|
-
if (!
|
|
2814
|
+
if (!import_ts_morph7.Node.isIdentifier(node)) return "unknown";
|
|
3460
2815
|
const name = node.getText();
|
|
3461
2816
|
const resolved = findType(name, sourceFile, project);
|
|
3462
2817
|
if (resolved) {
|
|
@@ -3503,11 +2858,11 @@ function extractDtoContract(method, sourceFile, project) {
|
|
|
3503
2858
|
if (apiResp) {
|
|
3504
2859
|
const args = apiResp.getArguments();
|
|
3505
2860
|
const optsArg = args[0];
|
|
3506
|
-
if (optsArg &&
|
|
2861
|
+
if (optsArg && import_ts_morph7.Node.isObjectLiteralExpression(optsArg)) {
|
|
3507
2862
|
for (const prop of optsArg.getProperties()) {
|
|
3508
|
-
if (
|
|
2863
|
+
if (import_ts_morph7.Node.isPropertyAssignment(prop) && prop.getName() === "type") {
|
|
3509
2864
|
const val = prop.getInitializer();
|
|
3510
|
-
if (val &&
|
|
2865
|
+
if (val && import_ts_morph7.Node.isIdentifier(val)) {
|
|
3511
2866
|
const name = val.getText();
|
|
3512
2867
|
const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
|
|
3513
2868
|
if (localDecl?.isExported()) {
|
|
@@ -3524,60 +2879,243 @@ function extractDtoContract(method, sourceFile, project) {
|
|
|
3524
2879
|
}
|
|
3525
2880
|
}
|
|
3526
2881
|
}
|
|
3527
|
-
let bodyZodText = null;
|
|
3528
|
-
let queryZodText = null;
|
|
3529
2882
|
let bodySchema = null;
|
|
3530
2883
|
let querySchema = null;
|
|
3531
|
-
const formNested = {};
|
|
3532
2884
|
const formWarnings = [];
|
|
3533
2885
|
const bodyClass = resolveParamClass(method, "Body", sourceFile, project);
|
|
3534
2886
|
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
2887
|
bodySchema = extractSchemaFromDto(bodyClass.decl, bodyClass.file, project);
|
|
2888
|
+
formWarnings.push(...bodySchema.warnings);
|
|
2889
|
+
}
|
|
2890
|
+
const queryClass = resolveParamClass(method, "Query", sourceFile, project);
|
|
2891
|
+
if (queryClass) {
|
|
2892
|
+
querySchema = extractSchemaFromDto(queryClass.decl, queryClass.file, project);
|
|
2893
|
+
formWarnings.push(...querySchema.warnings);
|
|
2894
|
+
}
|
|
2895
|
+
return {
|
|
2896
|
+
query,
|
|
2897
|
+
body,
|
|
2898
|
+
response,
|
|
2899
|
+
params: paramsType,
|
|
2900
|
+
queryRef,
|
|
2901
|
+
bodyRef,
|
|
2902
|
+
responseRef,
|
|
2903
|
+
filterFields: filterInfo?.fieldNames ?? null,
|
|
2904
|
+
filterFieldTypes: filterInfo?.fieldTypes ?? null,
|
|
2905
|
+
filterSource: filterInfo?.source ?? null,
|
|
2906
|
+
formWarnings,
|
|
2907
|
+
bodySchema,
|
|
2908
|
+
querySchema
|
|
2909
|
+
};
|
|
2910
|
+
}
|
|
2911
|
+
function resolveParamClass(method, decoratorName, sourceFile, project) {
|
|
2912
|
+
for (const param of method.getParameters()) {
|
|
2913
|
+
if (!param.getDecorators().some((d) => d.getName() === decoratorName)) continue;
|
|
2914
|
+
const typeNode = param.getTypeNode();
|
|
2915
|
+
if (!typeNode) continue;
|
|
2916
|
+
const text = typeNode.getText().replace(/\[\]$/, "");
|
|
2917
|
+
if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(text)) continue;
|
|
2918
|
+
const resolved = findType(text, sourceFile, project);
|
|
2919
|
+
if (resolved && resolved.kind === "class") {
|
|
2920
|
+
return { decl: resolved.decl, file: resolved.file };
|
|
2921
|
+
}
|
|
2922
|
+
}
|
|
2923
|
+
return null;
|
|
2924
|
+
}
|
|
2925
|
+
|
|
2926
|
+
// src/discovery/zod-ast-to-ts.ts
|
|
2927
|
+
var import_ts_morph8 = require("ts-morph");
|
|
2928
|
+
function zodAstToTs(node) {
|
|
2929
|
+
if (!import_ts_morph8.Node.isCallExpression(node)) return "unknown";
|
|
2930
|
+
const expr = node.getExpression();
|
|
2931
|
+
if (import_ts_morph8.Node.isPropertyAccessExpression(expr)) {
|
|
2932
|
+
const methodName = expr.getName();
|
|
2933
|
+
const receiver = expr.getExpression();
|
|
2934
|
+
if (methodName === "optional") {
|
|
2935
|
+
return `${zodAstToTs(receiver)} | undefined`;
|
|
2936
|
+
}
|
|
2937
|
+
if (methodName === "nullable") {
|
|
2938
|
+
return `${zodAstToTs(receiver)} | null`;
|
|
2939
|
+
}
|
|
2940
|
+
const args = node.getArguments();
|
|
2941
|
+
switch (methodName) {
|
|
2942
|
+
case "string":
|
|
2943
|
+
return "string";
|
|
2944
|
+
case "number":
|
|
2945
|
+
return "number";
|
|
2946
|
+
case "boolean":
|
|
2947
|
+
return "boolean";
|
|
2948
|
+
case "unknown":
|
|
2949
|
+
return "unknown";
|
|
2950
|
+
case "any":
|
|
2951
|
+
return "unknown";
|
|
2952
|
+
case "literal": {
|
|
2953
|
+
const lit = args[0];
|
|
2954
|
+
if (!lit) return "unknown";
|
|
2955
|
+
if (import_ts_morph8.Node.isStringLiteral(lit)) return JSON.stringify(lit.getLiteralValue());
|
|
2956
|
+
if (import_ts_morph8.Node.isNumericLiteral(lit)) return lit.getLiteralValue().toString();
|
|
2957
|
+
if (lit.getKind() === import_ts_morph8.SyntaxKind.TrueKeyword) return "true";
|
|
2958
|
+
if (lit.getKind() === import_ts_morph8.SyntaxKind.FalseKeyword) return "false";
|
|
2959
|
+
return "unknown";
|
|
2960
|
+
}
|
|
2961
|
+
case "enum": {
|
|
2962
|
+
const arrArg = args[0];
|
|
2963
|
+
if (!arrArg || !import_ts_morph8.Node.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
2964
|
+
const members = arrArg.getElements().map(
|
|
2965
|
+
(el) => import_ts_morph8.Node.isStringLiteral(el) ? JSON.stringify(el.getLiteralValue()) : "unknown"
|
|
2966
|
+
);
|
|
2967
|
+
return members.join(" | ");
|
|
2968
|
+
}
|
|
2969
|
+
case "array": {
|
|
2970
|
+
const inner = args[0];
|
|
2971
|
+
if (!inner) return "unknown";
|
|
2972
|
+
return `Array<${zodAstToTs(inner)}>`;
|
|
2973
|
+
}
|
|
2974
|
+
case "object": {
|
|
2975
|
+
const objArg = args[0];
|
|
2976
|
+
if (!objArg || !import_ts_morph8.Node.isObjectLiteralExpression(objArg)) return "unknown";
|
|
2977
|
+
const lines = [];
|
|
2978
|
+
for (const prop of objArg.getProperties()) {
|
|
2979
|
+
if (!import_ts_morph8.Node.isPropertyAssignment(prop)) continue;
|
|
2980
|
+
const key = prop.getName();
|
|
2981
|
+
const valNode = prop.getInitializer();
|
|
2982
|
+
if (!valNode) continue;
|
|
2983
|
+
const tsType = zodAstToTs(valNode);
|
|
2984
|
+
const isOpt = isOptionalChain(valNode);
|
|
2985
|
+
lines.push(`${key}${isOpt ? "?" : ""}: ${tsType}`);
|
|
2986
|
+
}
|
|
2987
|
+
return `{ ${lines.join("; ")} }`;
|
|
2988
|
+
}
|
|
2989
|
+
case "union": {
|
|
2990
|
+
const arrArg = args[0];
|
|
2991
|
+
if (!arrArg || !import_ts_morph8.Node.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
2992
|
+
return arrArg.getElements().map(zodAstToTs).join(" | ");
|
|
2993
|
+
}
|
|
2994
|
+
case "record": {
|
|
2995
|
+
const valArg = args.length === 1 ? args[0] : args[1];
|
|
2996
|
+
if (!valArg) return "unknown";
|
|
2997
|
+
return `Record<string, ${zodAstToTs(valArg)}>`;
|
|
2998
|
+
}
|
|
2999
|
+
case "tuple": {
|
|
3000
|
+
const arrArg = args[0];
|
|
3001
|
+
if (!arrArg || !import_ts_morph8.Node.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
3002
|
+
return `[${arrArg.getElements().map(zodAstToTs).join(", ")}]`;
|
|
3003
|
+
}
|
|
3004
|
+
default:
|
|
3005
|
+
return "unknown";
|
|
3006
|
+
}
|
|
3007
|
+
}
|
|
3008
|
+
return "unknown";
|
|
3009
|
+
}
|
|
3010
|
+
function isOptionalChain(node) {
|
|
3011
|
+
if (!import_ts_morph8.Node.isCallExpression(node)) return false;
|
|
3012
|
+
const expr = node.getExpression();
|
|
3013
|
+
return import_ts_morph8.Node.isPropertyAccessExpression(expr) && expr.getName() === "optional";
|
|
3014
|
+
}
|
|
3015
|
+
function parseDefineContractCall(callExpr) {
|
|
3016
|
+
if (!import_ts_morph8.Node.isCallExpression(callExpr)) return null;
|
|
3017
|
+
const callee = callExpr.getExpression();
|
|
3018
|
+
const calleeName = import_ts_morph8.Node.isIdentifier(callee) ? callee.getText() : import_ts_morph8.Node.isPropertyAccessExpression(callee) ? callee.getName() : "";
|
|
3019
|
+
if (calleeName !== "defineContract") return null;
|
|
3020
|
+
const args = callExpr.getArguments();
|
|
3021
|
+
const optsArg = args[0];
|
|
3022
|
+
if (!optsArg || !import_ts_morph8.Node.isObjectLiteralExpression(optsArg)) return null;
|
|
3023
|
+
let query = null;
|
|
3024
|
+
let body = null;
|
|
3025
|
+
let response = "unknown";
|
|
3026
|
+
let bodyZodText = null;
|
|
3027
|
+
let queryZodText = null;
|
|
3028
|
+
for (const prop of optsArg.getProperties()) {
|
|
3029
|
+
if (!import_ts_morph8.Node.isPropertyAssignment(prop)) continue;
|
|
3030
|
+
const propName = prop.getName();
|
|
3031
|
+
const val = prop.getInitializer();
|
|
3032
|
+
if (!val) continue;
|
|
3033
|
+
if (propName === "query") {
|
|
3034
|
+
query = zodAstToTs(val);
|
|
3035
|
+
queryZodText = val.getText();
|
|
3036
|
+
} else if (propName === "body") {
|
|
3037
|
+
body = zodAstToTs(val);
|
|
3038
|
+
bodyZodText = val.getText();
|
|
3039
|
+
} else if (propName === "response") {
|
|
3040
|
+
response = zodAstToTs(val);
|
|
3041
|
+
}
|
|
3042
|
+
}
|
|
3043
|
+
return { query, body, response, bodyZodText, queryZodText };
|
|
3044
|
+
}
|
|
3045
|
+
|
|
3046
|
+
// src/discovery/contracts-fast.ts
|
|
3047
|
+
async function discoverContractsFast(opts) {
|
|
3048
|
+
const { cwd, glob, tsconfig } = opts;
|
|
3049
|
+
const tsconfigPath = tsconfig ? (0, import_node_path12.resolve)(tsconfig) : (0, import_node_path12.join)(cwd, "tsconfig.json");
|
|
3050
|
+
let project;
|
|
3051
|
+
try {
|
|
3052
|
+
project = new import_ts_morph9.Project({
|
|
3053
|
+
tsConfigFilePath: tsconfigPath,
|
|
3054
|
+
skipAddingFilesFromTsConfig: true,
|
|
3055
|
+
skipLoadingLibFiles: true,
|
|
3056
|
+
skipFileDependencyResolution: true
|
|
3057
|
+
});
|
|
3058
|
+
} catch {
|
|
3059
|
+
project = new import_ts_morph9.Project({
|
|
3060
|
+
skipAddingFilesFromTsConfig: true,
|
|
3061
|
+
skipLoadingLibFiles: true,
|
|
3062
|
+
skipFileDependencyResolution: true,
|
|
3063
|
+
compilerOptions: {
|
|
3064
|
+
allowJs: true,
|
|
3065
|
+
resolveJsonModule: false,
|
|
3066
|
+
strict: false
|
|
3067
|
+
}
|
|
3068
|
+
});
|
|
3069
|
+
}
|
|
3070
|
+
const files = await (0, import_fast_glob2.default)(glob, { cwd, absolute: true, onlyFiles: true });
|
|
3071
|
+
for (const f of files) {
|
|
3072
|
+
project.addSourceFileAtPath(f);
|
|
3540
3073
|
}
|
|
3541
|
-
const
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3074
|
+
const routes = [];
|
|
3075
|
+
setDiscoveryContext(project, {
|
|
3076
|
+
projectRoot: cwd,
|
|
3077
|
+
tsconfigPaths: loadTsconfigPaths(tsconfigPath)
|
|
3078
|
+
});
|
|
3079
|
+
for (const sourceFile of project.getSourceFiles()) {
|
|
3080
|
+
routes.push(...extractFromSourceFile(sourceFile, project));
|
|
3548
3081
|
}
|
|
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
|
-
};
|
|
3082
|
+
return routes;
|
|
3567
3083
|
}
|
|
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
|
-
}
|
|
3084
|
+
function decoratorStringArg(decoratorExpr) {
|
|
3085
|
+
if (!decoratorExpr) return void 0;
|
|
3086
|
+
if (import_ts_morph9.Node.isStringLiteral(decoratorExpr)) return decoratorExpr.getLiteralValue();
|
|
3087
|
+
if (import_ts_morph9.Node.isArrayLiteralExpression(decoratorExpr)) {
|
|
3088
|
+
const first = decoratorExpr.getElements()[0];
|
|
3089
|
+
if (first && import_ts_morph9.Node.isStringLiteral(first)) return first.getLiteralValue();
|
|
3579
3090
|
}
|
|
3580
|
-
return
|
|
3091
|
+
return void 0;
|
|
3092
|
+
}
|
|
3093
|
+
function deriveClassSegment(className) {
|
|
3094
|
+
const noSuffix = className.replace(/Controller$/, "");
|
|
3095
|
+
if (!noSuffix) {
|
|
3096
|
+
throw new Error(
|
|
3097
|
+
`Controller class name "${className}" derives empty route segment after stripping "Controller". Add an @As(...) override at the class level.`
|
|
3098
|
+
);
|
|
3099
|
+
}
|
|
3100
|
+
return noSuffix.charAt(0).toLowerCase() + noSuffix.slice(1);
|
|
3101
|
+
}
|
|
3102
|
+
function resolveRouteName(className, methodName, classAs, methodAs) {
|
|
3103
|
+
const classPortion = classAs ?? deriveClassSegment(className);
|
|
3104
|
+
const methodPortion = methodAs ?? methodName;
|
|
3105
|
+
return `${classPortion}.${methodPortion}`;
|
|
3106
|
+
}
|
|
3107
|
+
function joinPaths(prefix, suffix) {
|
|
3108
|
+
if (!prefix && !suffix) return "/";
|
|
3109
|
+
if (!prefix) return suffix.startsWith("/") ? suffix : `/${suffix}`;
|
|
3110
|
+
if (!suffix) return prefix.startsWith("/") ? prefix : `/${prefix}`;
|
|
3111
|
+
const p = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
|
|
3112
|
+
const s = suffix.startsWith("/") ? suffix : `/${suffix}`;
|
|
3113
|
+
const combined = p + s;
|
|
3114
|
+
return combined === "" ? "/" : combined;
|
|
3115
|
+
}
|
|
3116
|
+
function extractParams(path) {
|
|
3117
|
+
const matches = path.matchAll(/:(\w+)/g);
|
|
3118
|
+
return Array.from(matches).map((m) => ({ name: m[1], source: "path" }));
|
|
3581
3119
|
}
|
|
3582
3120
|
var HTTP_METHOD_DECORATORS = {
|
|
3583
3121
|
Get: "GET",
|
|
@@ -3589,176 +3127,186 @@ var HTTP_METHOD_DECORATORS = {
|
|
|
3589
3127
|
Head: "HEAD",
|
|
3590
3128
|
All: "ALL"
|
|
3591
3129
|
};
|
|
3130
|
+
function resolveVerb(method) {
|
|
3131
|
+
for (const [decoratorName, verb] of Object.entries(HTTP_METHOD_DECORATORS)) {
|
|
3132
|
+
const httpDecorator = method.getDecorator(decoratorName);
|
|
3133
|
+
if (httpDecorator) {
|
|
3134
|
+
const httpArgs = httpDecorator.getArguments();
|
|
3135
|
+
const pathArg = httpArgs[0];
|
|
3136
|
+
return { httpMethod: verb, handlerPath: decoratorStringArg(pathArg) ?? "" };
|
|
3137
|
+
}
|
|
3138
|
+
}
|
|
3139
|
+
return null;
|
|
3140
|
+
}
|
|
3141
|
+
function readAsDecorator(node, label) {
|
|
3142
|
+
const asDecorator = node.getDecorator("As");
|
|
3143
|
+
if (!asDecorator) return void 0;
|
|
3144
|
+
const asName = decoratorStringArg(asDecorator.getArguments()[0]);
|
|
3145
|
+
if (!asName) {
|
|
3146
|
+
throw new Error(`@As decorator on ${label} must have a non-empty string argument.`);
|
|
3147
|
+
}
|
|
3148
|
+
return asName;
|
|
3149
|
+
}
|
|
3150
|
+
function buildRoute(args) {
|
|
3151
|
+
const {
|
|
3152
|
+
className,
|
|
3153
|
+
methodName,
|
|
3154
|
+
resolvedMethod,
|
|
3155
|
+
combinedPath,
|
|
3156
|
+
classAs,
|
|
3157
|
+
methodAs,
|
|
3158
|
+
sourceFile,
|
|
3159
|
+
seenNames,
|
|
3160
|
+
contractSource
|
|
3161
|
+
} = args;
|
|
3162
|
+
const routeName = resolveRouteName(className, methodName, classAs, methodAs);
|
|
3163
|
+
const qualifiedRef = `${className}.${methodName}`;
|
|
3164
|
+
const existing = seenNames.get(routeName);
|
|
3165
|
+
if (existing !== void 0) {
|
|
3166
|
+
throw new Error(
|
|
3167
|
+
`Route name collision: "${routeName}" is used by both "${existing}" and "${qualifiedRef}". Use @As(...) to give one of them a unique name.`
|
|
3168
|
+
);
|
|
3169
|
+
}
|
|
3170
|
+
seenNames.set(routeName, qualifiedRef);
|
|
3171
|
+
return {
|
|
3172
|
+
method: resolvedMethod,
|
|
3173
|
+
path: combinedPath,
|
|
3174
|
+
name: routeName,
|
|
3175
|
+
params: extractParams(combinedPath),
|
|
3176
|
+
controllerRef: { className, methodName, filePath: sourceFile.getFilePath() },
|
|
3177
|
+
contract: { contractSource }
|
|
3178
|
+
};
|
|
3179
|
+
}
|
|
3180
|
+
function extractContractRoute(args) {
|
|
3181
|
+
const { cls, method, applyContractDecorator, verb, prefix, className, sourceFile, seenNames } = args;
|
|
3182
|
+
const firstDecoratorArg = applyContractDecorator.getArguments()[0];
|
|
3183
|
+
if (!firstDecoratorArg) return null;
|
|
3184
|
+
let contractDef = null;
|
|
3185
|
+
let bodyZodRef = null;
|
|
3186
|
+
let queryZodRef = null;
|
|
3187
|
+
if (import_ts_morph9.Node.isCallExpression(firstDecoratorArg)) {
|
|
3188
|
+
contractDef = parseDefineContractCall(firstDecoratorArg);
|
|
3189
|
+
} else if (import_ts_morph9.Node.isIdentifier(firstDecoratorArg)) {
|
|
3190
|
+
const identName = firstDecoratorArg.getText();
|
|
3191
|
+
const varDecl = sourceFile.getVariableDeclaration(identName);
|
|
3192
|
+
if (!varDecl) {
|
|
3193
|
+
console.warn(
|
|
3194
|
+
`[nestjs-codegen/fast] Cannot resolve '${identName}' in ${sourceFile.getFilePath()} (cross-file imports are out-of-scope for v1) \u2014 skipping`
|
|
3195
|
+
);
|
|
3196
|
+
return null;
|
|
3197
|
+
}
|
|
3198
|
+
const initializer = varDecl.getInitializer();
|
|
3199
|
+
if (!initializer) return null;
|
|
3200
|
+
contractDef = parseDefineContractCall(initializer);
|
|
3201
|
+
if (contractDef && varDecl.isExported()) {
|
|
3202
|
+
const filePath = sourceFile.getFilePath();
|
|
3203
|
+
if (contractDef.body !== null) {
|
|
3204
|
+
bodyZodRef = { name: `${identName}.body`, filePath };
|
|
3205
|
+
}
|
|
3206
|
+
if (contractDef.query !== null) {
|
|
3207
|
+
queryZodRef = { name: `${identName}.query`, filePath };
|
|
3208
|
+
}
|
|
3209
|
+
}
|
|
3210
|
+
} else {
|
|
3211
|
+
console.warn(
|
|
3212
|
+
`[nestjs-codegen/fast] @ApplyContract arg is not an identifier or call expression in ${sourceFile.getFilePath()} \u2014 skipping`
|
|
3213
|
+
);
|
|
3214
|
+
return null;
|
|
3215
|
+
}
|
|
3216
|
+
if (!contractDef) return null;
|
|
3217
|
+
if (!verb) return null;
|
|
3218
|
+
const resolvedPath = joinPaths(prefix, verb.handlerPath);
|
|
3219
|
+
const methodName = method.getName();
|
|
3220
|
+
const classAs = readAsDecorator(cls, `class ${className}`);
|
|
3221
|
+
const methodAs = readAsDecorator(method, `${className}.${methodName}`);
|
|
3222
|
+
return buildRoute({
|
|
3223
|
+
className,
|
|
3224
|
+
methodName,
|
|
3225
|
+
resolvedMethod: verb.httpMethod,
|
|
3226
|
+
combinedPath: resolvedPath,
|
|
3227
|
+
classAs,
|
|
3228
|
+
methodAs,
|
|
3229
|
+
sourceFile,
|
|
3230
|
+
seenNames,
|
|
3231
|
+
contractSource: {
|
|
3232
|
+
query: contractDef.query,
|
|
3233
|
+
body: contractDef.body,
|
|
3234
|
+
response: contractDef.response,
|
|
3235
|
+
// Path A: capture both the importable ref and the raw text. The emitter
|
|
3236
|
+
// prefers inlining the text (client-safe — re-exporting from a controller
|
|
3237
|
+
// would drag server-only deps into the client bundle).
|
|
3238
|
+
bodyZodRef,
|
|
3239
|
+
bodyZodText: contractDef.bodyZodText,
|
|
3240
|
+
queryZodRef,
|
|
3241
|
+
queryZodText: contractDef.queryZodText
|
|
3242
|
+
}
|
|
3243
|
+
});
|
|
3244
|
+
}
|
|
3245
|
+
function extractDtoRoute(args) {
|
|
3246
|
+
const { cls, method, verb, prefix, className, sourceFile, project, seenNames } = args;
|
|
3247
|
+
if (!verb) return null;
|
|
3248
|
+
const combined = joinPaths(prefix, verb.handlerPath);
|
|
3249
|
+
const methodName = method.getName();
|
|
3250
|
+
const classAs = readAsDecorator(cls, `class ${className}`);
|
|
3251
|
+
const methodAs = readAsDecorator(method, `${className}.${methodName}`);
|
|
3252
|
+
const dtoContract = extractDtoContract(method, sourceFile, project);
|
|
3253
|
+
return buildRoute({
|
|
3254
|
+
className,
|
|
3255
|
+
methodName,
|
|
3256
|
+
resolvedMethod: verb.httpMethod,
|
|
3257
|
+
combinedPath: combined,
|
|
3258
|
+
classAs,
|
|
3259
|
+
methodAs,
|
|
3260
|
+
sourceFile,
|
|
3261
|
+
seenNames,
|
|
3262
|
+
contractSource: {
|
|
3263
|
+
query: dtoContract?.query ?? null,
|
|
3264
|
+
body: dtoContract?.body ?? null,
|
|
3265
|
+
response: dtoContract?.response ?? "unknown",
|
|
3266
|
+
queryRef: dtoContract?.queryRef ?? null,
|
|
3267
|
+
bodyRef: dtoContract?.bodyRef ?? null,
|
|
3268
|
+
responseRef: dtoContract?.responseRef ?? null,
|
|
3269
|
+
filterFields: dtoContract?.filterFields ?? null,
|
|
3270
|
+
filterFieldTypes: dtoContract?.filterFieldTypes ?? null,
|
|
3271
|
+
filterSource: dtoContract?.filterSource ?? null,
|
|
3272
|
+
formWarnings: dtoContract?.formWarnings ?? [],
|
|
3273
|
+
bodySchema: dtoContract?.bodySchema ?? null,
|
|
3274
|
+
querySchema: dtoContract?.querySchema ?? null
|
|
3275
|
+
}
|
|
3276
|
+
});
|
|
3277
|
+
}
|
|
3592
3278
|
function extractFromSourceFile(sourceFile, project) {
|
|
3593
3279
|
const routes = [];
|
|
3594
3280
|
const seenNames = /* @__PURE__ */ new Map();
|
|
3595
|
-
const
|
|
3596
|
-
for (const cls of classes) {
|
|
3281
|
+
for (const cls of sourceFile.getClasses()) {
|
|
3597
3282
|
const controllerDecorator = cls.getDecorator("Controller");
|
|
3598
3283
|
if (!controllerDecorator) continue;
|
|
3599
|
-
const
|
|
3600
|
-
const
|
|
3601
|
-
const prefix = decoratorStringArg(firstArg3) ?? "";
|
|
3284
|
+
const firstArg2 = controllerDecorator.getArguments()[0];
|
|
3285
|
+
const prefix = decoratorStringArg(firstArg2) ?? "";
|
|
3602
3286
|
const className = cls.getName() ?? "Unknown";
|
|
3603
3287
|
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
|
-
}
|
|
3288
|
+
const verb = resolveVerb(method);
|
|
3616
3289
|
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
|
-
}
|
|
3290
|
+
const route = applyContractDecorator ? extractContractRoute({
|
|
3291
|
+
cls,
|
|
3292
|
+
method,
|
|
3293
|
+
applyContractDecorator,
|
|
3294
|
+
verb,
|
|
3295
|
+
prefix,
|
|
3296
|
+
className,
|
|
3297
|
+
sourceFile,
|
|
3298
|
+
seenNames
|
|
3299
|
+
}) : extractDtoRoute({
|
|
3300
|
+
cls,
|
|
3301
|
+
method,
|
|
3302
|
+
verb,
|
|
3303
|
+
prefix,
|
|
3304
|
+
className,
|
|
3305
|
+
sourceFile,
|
|
3306
|
+
project,
|
|
3307
|
+
seenNames
|
|
3308
|
+
});
|
|
3309
|
+
if (route) routes.push(route);
|
|
3762
3310
|
}
|
|
3763
3311
|
}
|
|
3764
3312
|
return routes;
|
|
@@ -3767,7 +3315,7 @@ function extractFromSourceFile(sourceFile, project) {
|
|
|
3767
3315
|
// src/watch/lock-file.ts
|
|
3768
3316
|
var import_promises10 = require("fs/promises");
|
|
3769
3317
|
var import_promises11 = require("fs/promises");
|
|
3770
|
-
var
|
|
3318
|
+
var import_node_path13 = require("path");
|
|
3771
3319
|
var LOCK_FILE = ".watcher.lock";
|
|
3772
3320
|
function isProcessAlive(pid) {
|
|
3773
3321
|
try {
|
|
@@ -3779,7 +3327,7 @@ function isProcessAlive(pid) {
|
|
|
3779
3327
|
}
|
|
3780
3328
|
async function acquireLock(outDir) {
|
|
3781
3329
|
await (0, import_promises11.mkdir)(outDir, { recursive: true });
|
|
3782
|
-
const lockPath = (0,
|
|
3330
|
+
const lockPath = (0, import_node_path13.join)(outDir, LOCK_FILE);
|
|
3783
3331
|
const lockData = { pid: process.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
3784
3332
|
try {
|
|
3785
3333
|
const fd = await (0, import_promises10.open)(lockPath, "wx");
|
|
@@ -3819,7 +3367,7 @@ async function watch(config, onChange) {
|
|
|
3819
3367
|
if (lock === null) {
|
|
3820
3368
|
let holderPid = "unknown";
|
|
3821
3369
|
try {
|
|
3822
|
-
const raw = await (0, import_promises12.readFile)((0,
|
|
3370
|
+
const raw = await (0, import_promises12.readFile)((0, import_node_path14.join)(config.codegen.outDir, ".watcher.lock"), "utf8");
|
|
3823
3371
|
const data = JSON.parse(raw);
|
|
3824
3372
|
if (data.pid !== void 0) holderPid = String(data.pid);
|
|
3825
3373
|
} catch {
|
|
@@ -3847,7 +3395,7 @@ async function watch(config, onChange) {
|
|
|
3847
3395
|
}
|
|
3848
3396
|
let pagesDebounceTimer;
|
|
3849
3397
|
const pagesGlob = config.pages?.glob ?? ".nestjs-codegen-no-pages";
|
|
3850
|
-
const pagesWatcher = import_chokidar.default.watch((0,
|
|
3398
|
+
const pagesWatcher = import_chokidar.default.watch((0, import_node_path14.join)(config.codegen.cwd, pagesGlob), {
|
|
3851
3399
|
ignoreInitial: true,
|
|
3852
3400
|
persistent: true,
|
|
3853
3401
|
awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
|
|
@@ -3873,7 +3421,7 @@ async function watch(config, onChange) {
|
|
|
3873
3421
|
pagesWatcher.on("change", schedulePagesRegenerate);
|
|
3874
3422
|
pagesWatcher.on("unlink", schedulePagesRegenerate);
|
|
3875
3423
|
let contractsDebounceTimer;
|
|
3876
|
-
const contractsWatcher = import_chokidar.default.watch((0,
|
|
3424
|
+
const contractsWatcher = import_chokidar.default.watch((0, import_node_path14.join)(config.codegen.cwd, config.contracts.glob), {
|
|
3877
3425
|
ignoreInitial: true,
|
|
3878
3426
|
persistent: true,
|
|
3879
3427
|
awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
|
|
@@ -3903,7 +3451,7 @@ async function watch(config, onChange) {
|
|
|
3903
3451
|
contractsWatcher.on("add", scheduleContractsRegenerate);
|
|
3904
3452
|
contractsWatcher.on("change", scheduleContractsRegenerate);
|
|
3905
3453
|
contractsWatcher.on("unlink", scheduleContractsRegenerate);
|
|
3906
|
-
const formsWatcher = import_chokidar.default.watch((0,
|
|
3454
|
+
const formsWatcher = import_chokidar.default.watch((0, import_node_path14.join)(config.codegen.cwd, config.forms.watch), {
|
|
3907
3455
|
ignoreInitial: true,
|
|
3908
3456
|
persistent: true,
|
|
3909
3457
|
awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
|
|
@@ -3930,7 +3478,7 @@ async function watch(config, onChange) {
|
|
|
3930
3478
|
}
|
|
3931
3479
|
|
|
3932
3480
|
// src/index.ts
|
|
3933
|
-
var VERSION = "0.
|
|
3481
|
+
var VERSION = "0.3.0";
|
|
3934
3482
|
|
|
3935
3483
|
// src/cli/codegen.ts
|
|
3936
3484
|
async function runCodegen(opts = {}) {
|
|
@@ -3958,15 +3506,51 @@ async function runCodegen(opts = {}) {
|
|
|
3958
3506
|
|
|
3959
3507
|
// src/cli/doctor.ts
|
|
3960
3508
|
var import_node_child_process2 = require("child_process");
|
|
3961
|
-
var
|
|
3962
|
-
var
|
|
3509
|
+
var import_node_fs4 = require("fs");
|
|
3510
|
+
var import_node_path16 = require("path");
|
|
3963
3511
|
|
|
3964
3512
|
// src/cli/init.ts
|
|
3965
3513
|
var import_node_child_process = require("child_process");
|
|
3966
|
-
var
|
|
3514
|
+
var import_node_fs3 = require("fs");
|
|
3967
3515
|
var import_promises13 = require("fs/promises");
|
|
3968
|
-
var
|
|
3516
|
+
var import_node_path15 = require("path");
|
|
3969
3517
|
var import_node_readline = require("readline");
|
|
3518
|
+
|
|
3519
|
+
// src/cli/patch-utils.ts
|
|
3520
|
+
var import_node_fs2 = require("fs");
|
|
3521
|
+
function patchJsonFile(filePath, mutator, parse = (raw) => raw) {
|
|
3522
|
+
let raw;
|
|
3523
|
+
try {
|
|
3524
|
+
raw = (0, import_node_fs2.readFileSync)(filePath, "utf8");
|
|
3525
|
+
} catch {
|
|
3526
|
+
return "skipped";
|
|
3527
|
+
}
|
|
3528
|
+
const json = JSON.parse(parse(raw));
|
|
3529
|
+
if (!mutator(json)) return "already";
|
|
3530
|
+
(0, import_node_fs2.writeFileSync)(filePath, `${JSON.stringify(json, null, 2)}
|
|
3531
|
+
`, "utf8");
|
|
3532
|
+
return "patched";
|
|
3533
|
+
}
|
|
3534
|
+
function findAfterLastImport(content) {
|
|
3535
|
+
const lastImportIndex = content.lastIndexOf("\nimport ");
|
|
3536
|
+
if (lastImportIndex !== -1) {
|
|
3537
|
+
const endOfLine = content.indexOf("\n", lastImportIndex + 1);
|
|
3538
|
+
return endOfLine !== -1 ? endOfLine + 1 : content.length;
|
|
3539
|
+
}
|
|
3540
|
+
if (content.startsWith("import ")) {
|
|
3541
|
+
const endOfLine = content.indexOf("\n");
|
|
3542
|
+
return endOfLine !== -1 ? endOfLine + 1 : content.length;
|
|
3543
|
+
}
|
|
3544
|
+
return 0;
|
|
3545
|
+
}
|
|
3546
|
+
function insertImport(content, stmt) {
|
|
3547
|
+
const insertAt = findAfterLastImport(content);
|
|
3548
|
+
if (insertAt <= 0) return content;
|
|
3549
|
+
return `${content.slice(0, insertAt)}${stmt}
|
|
3550
|
+
${content.slice(insertAt)}`;
|
|
3551
|
+
}
|
|
3552
|
+
|
|
3553
|
+
// src/cli/init.ts
|
|
3970
3554
|
var GITIGNORE_ENTRY = ".nestjs-inertia/";
|
|
3971
3555
|
var green = (s) => `\x1B[32m${s}\x1B[0m`;
|
|
3972
3556
|
var yellow = (s) => `\x1B[33m${s}\x1B[0m`;
|
|
@@ -3991,7 +3575,7 @@ ${bold(title)}`);
|
|
|
3991
3575
|
}
|
|
3992
3576
|
async function readPackageJson(cwd) {
|
|
3993
3577
|
try {
|
|
3994
|
-
const raw = await (0, import_promises13.readFile)((0,
|
|
3578
|
+
const raw = await (0, import_promises13.readFile)((0, import_node_path15.join)(cwd, "package.json"), "utf8");
|
|
3995
3579
|
return JSON.parse(raw);
|
|
3996
3580
|
} catch {
|
|
3997
3581
|
return {};
|
|
@@ -4022,7 +3606,7 @@ async function detectTemplateEngine(cwd) {
|
|
|
4022
3606
|
async function detectPackageManager(cwd) {
|
|
4023
3607
|
async function exists(file) {
|
|
4024
3608
|
try {
|
|
4025
|
-
await (0, import_promises13.access)((0,
|
|
3609
|
+
await (0, import_promises13.access)((0, import_node_path15.join)(cwd, file));
|
|
4026
3610
|
return true;
|
|
4027
3611
|
} catch {
|
|
4028
3612
|
return false;
|
|
@@ -4069,7 +3653,7 @@ async function writeIfNotExists(filePath, content, label) {
|
|
|
4069
3653
|
logCreated(label);
|
|
4070
3654
|
}
|
|
4071
3655
|
async function handleViteConfig(cwd, framework) {
|
|
4072
|
-
const filePath = (0,
|
|
3656
|
+
const filePath = (0, import_node_path15.join)(cwd, "vite.config.ts");
|
|
4073
3657
|
if (await fileExists2(filePath)) {
|
|
4074
3658
|
const existing = await (0, import_promises13.readFile)(filePath, "utf8");
|
|
4075
3659
|
const hasPlugin = existing.includes("nestInertia") || existing.includes("nestjs-inertia-vite/plugin");
|
|
@@ -4127,7 +3711,7 @@ function installDeps(pkgManager, deps, dev) {
|
|
|
4127
3711
|
}
|
|
4128
3712
|
}
|
|
4129
3713
|
async function patchPackageJsonScripts(cwd, scripts) {
|
|
4130
|
-
const pkgPath = (0,
|
|
3714
|
+
const pkgPath = (0, import_node_path15.join)(cwd, "package.json");
|
|
4131
3715
|
let pkg = {};
|
|
4132
3716
|
try {
|
|
4133
3717
|
pkg = JSON.parse(await (0, import_promises13.readFile)(pkgPath, "utf8"));
|
|
@@ -4152,32 +3736,16 @@ async function patchPackageJsonScripts(cwd, scripts) {
|
|
|
4152
3736
|
await (0, import_promises13.writeFile)(pkgPath, `${JSON.stringify(pkg, null, 2)}
|
|
4153
3737
|
`, "utf8");
|
|
4154
3738
|
}
|
|
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
3739
|
function patchAppModule(filePath, rootView) {
|
|
4168
3740
|
let content;
|
|
4169
3741
|
try {
|
|
4170
|
-
content = (0,
|
|
3742
|
+
content = (0, import_node_fs3.readFileSync)(filePath, "utf8");
|
|
4171
3743
|
} catch {
|
|
4172
3744
|
return "skipped";
|
|
4173
3745
|
}
|
|
4174
3746
|
let changed = false;
|
|
4175
3747
|
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
|
-
}
|
|
3748
|
+
content = insertImport(content, "import { InertiaModule } from '@dudousxd/nestjs-inertia';");
|
|
4181
3749
|
if (!content.includes("from 'node:path'") && !content.includes('from "node:path"')) {
|
|
4182
3750
|
const insertAt2 = findAfterLastImport(content);
|
|
4183
3751
|
content = `${content.slice(0, insertAt2)}import { resolve } from 'node:path';
|
|
@@ -4195,11 +3763,7 @@ ${indent}}),${content.slice(bracketPos)}`;
|
|
|
4195
3763
|
}
|
|
4196
3764
|
}
|
|
4197
3765
|
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
|
-
}
|
|
3766
|
+
content = insertImport(content, "import { HomeController } from './home.controller';");
|
|
4203
3767
|
const controllersMatch = content.match(/controllers\s*:\s*\[/);
|
|
4204
3768
|
if (controllersMatch?.index !== void 0) {
|
|
4205
3769
|
const bracketPos = content.indexOf("[", controllersMatch.index) + 1;
|
|
@@ -4210,22 +3774,21 @@ ${indent}HomeController,${content.slice(bracketPos)}`;
|
|
|
4210
3774
|
}
|
|
4211
3775
|
}
|
|
4212
3776
|
if (!changed) return "already";
|
|
4213
|
-
(0,
|
|
3777
|
+
(0, import_node_fs3.writeFileSync)(filePath, content, "utf8");
|
|
4214
3778
|
return "patched";
|
|
4215
3779
|
}
|
|
4216
3780
|
function patchMainTs(filePath) {
|
|
4217
3781
|
let content;
|
|
4218
3782
|
try {
|
|
4219
|
-
content = (0,
|
|
3783
|
+
content = (0, import_node_fs3.readFileSync)(filePath, "utf8");
|
|
4220
3784
|
} catch {
|
|
4221
3785
|
return "skipped";
|
|
4222
3786
|
}
|
|
4223
3787
|
if (content.includes("setupInertiaVite")) return "already";
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
}
|
|
3788
|
+
content = insertImport(
|
|
3789
|
+
content,
|
|
3790
|
+
"import { setupInertiaVite } from '@dudousxd/nestjs-inertia-vite';"
|
|
3791
|
+
);
|
|
4229
3792
|
const createMatch = content.match(
|
|
4230
3793
|
/(?:const|let)\s+(\w+)\s*=\s*await\s+NestFactory\.create[^;]+;/
|
|
4231
3794
|
);
|
|
@@ -4242,7 +3805,7 @@ ${content.slice(insertAt)}`;
|
|
|
4242
3805
|
});`;
|
|
4243
3806
|
content = `${content.slice(0, insertAfterPos)}
|
|
4244
3807
|
${viteSetup}${content.slice(insertAfterPos)}`;
|
|
4245
|
-
(0,
|
|
3808
|
+
(0, import_node_fs3.writeFileSync)(filePath, content, "utf8");
|
|
4246
3809
|
return "patched";
|
|
4247
3810
|
}
|
|
4248
3811
|
function configTemplate(framework) {
|
|
@@ -4465,107 +4028,84 @@ export class HomeController {
|
|
|
4465
4028
|
}
|
|
4466
4029
|
`;
|
|
4467
4030
|
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";
|
|
4031
|
+
const filePath = (0, import_node_path15.join)(cwd, filename);
|
|
4032
|
+
return patchJsonFile(
|
|
4033
|
+
filePath,
|
|
4034
|
+
(json) => {
|
|
4035
|
+
const exclude = json.exclude ?? [];
|
|
4036
|
+
if (exclude.includes(dir)) return false;
|
|
4037
|
+
exclude.push(dir);
|
|
4038
|
+
json.exclude = exclude;
|
|
4039
|
+
return true;
|
|
4040
|
+
},
|
|
4041
|
+
// Strip single-line comments before JSON.parse
|
|
4042
|
+
(raw) => raw.replace(/\/\/.*$/gm, "")
|
|
4043
|
+
);
|
|
4484
4044
|
}
|
|
4485
4045
|
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
|
|
4046
|
+
const filePath = (0, import_node_path15.join)(cwd, "nest-cli.json");
|
|
4047
|
+
return patchJsonFile(filePath, (json) => {
|
|
4048
|
+
const compiler = json.compilerOptions ?? {};
|
|
4049
|
+
const assets = compiler.assets ?? [];
|
|
4050
|
+
const alreadyHas = assets.some((a) => {
|
|
4051
|
+
if (typeof a === "string") return a.includes(shellDir);
|
|
4052
|
+
return String(a.include ?? "").includes(shellDir);
|
|
4053
|
+
});
|
|
4054
|
+
if (alreadyHas) return false;
|
|
4055
|
+
assets.push({
|
|
4056
|
+
include: `../${shellDir}/**/*`,
|
|
4057
|
+
outDir: `dist/${shellDir}`,
|
|
4058
|
+
watchAssets: true
|
|
4059
|
+
});
|
|
4060
|
+
compiler.assets = assets;
|
|
4061
|
+
json.compilerOptions = compiler;
|
|
4062
|
+
return true;
|
|
4505
4063
|
});
|
|
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
4064
|
}
|
|
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";
|
|
4065
|
+
async function scaffoldFiles(ctx) {
|
|
4066
|
+
const { cwd, framework, engine, shellFileName, entryExt, pageExt } = ctx;
|
|
4528
4067
|
logSection("Scaffold files");
|
|
4529
4068
|
await writeIfNotExists(
|
|
4530
|
-
(0,
|
|
4069
|
+
(0, import_node_path15.join)(cwd, "nestjs-inertia.config.ts"),
|
|
4531
4070
|
configTemplate(framework),
|
|
4532
4071
|
"nestjs-inertia.config.ts"
|
|
4533
4072
|
);
|
|
4534
|
-
await writeIfNotExists((0,
|
|
4073
|
+
await writeIfNotExists((0, import_node_path15.join)(cwd, "nestjs-inertia.d.ts"), DTS_TEMPLATE, "nestjs-inertia.d.ts");
|
|
4535
4074
|
await writeIfNotExists(
|
|
4536
|
-
(0,
|
|
4075
|
+
(0, import_node_path15.join)(cwd, "tsconfig.inertia.json"),
|
|
4537
4076
|
TSCONFIG_INERTIA_TEMPLATE,
|
|
4538
4077
|
"tsconfig.inertia.json"
|
|
4539
4078
|
);
|
|
4540
4079
|
await writeIfNotExists(
|
|
4541
|
-
(0,
|
|
4080
|
+
(0, import_node_path15.join)(cwd, "inertia", "tsconfig.json"),
|
|
4542
4081
|
INERTIA_TSCONFIG_TEMPLATE,
|
|
4543
4082
|
"inertia/tsconfig.json"
|
|
4544
4083
|
);
|
|
4545
4084
|
await writeIfNotExists(
|
|
4546
|
-
(0,
|
|
4085
|
+
(0, import_node_path15.join)(cwd, "inertia", shellFileName),
|
|
4547
4086
|
htmlShellTemplate(framework, engine),
|
|
4548
4087
|
`inertia/${shellFileName}`
|
|
4549
4088
|
);
|
|
4550
4089
|
await handleViteConfig(cwd, framework);
|
|
4551
4090
|
await writeIfNotExists(
|
|
4552
|
-
(0,
|
|
4091
|
+
(0, import_node_path15.join)(cwd, "inertia", "app", `client.${entryExt}`),
|
|
4553
4092
|
entryPointTemplate(framework),
|
|
4554
4093
|
`inertia/app/client.${entryExt}`
|
|
4555
4094
|
);
|
|
4556
4095
|
await writeIfNotExists(
|
|
4557
|
-
(0,
|
|
4096
|
+
(0, import_node_path15.join)(cwd, "inertia", "pages", `Home.${pageExt}`),
|
|
4558
4097
|
samplePageTemplate(framework),
|
|
4559
4098
|
`inertia/pages/Home.${pageExt}`
|
|
4560
4099
|
);
|
|
4561
4100
|
await writeIfNotExists(
|
|
4562
|
-
(0,
|
|
4101
|
+
(0, import_node_path15.join)(cwd, "src", "home.controller.ts"),
|
|
4563
4102
|
SAMPLE_CONTROLLER,
|
|
4564
4103
|
"src/home.controller.ts"
|
|
4565
4104
|
);
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
const
|
|
4105
|
+
}
|
|
4106
|
+
function patchServerAppModule(ctx) {
|
|
4107
|
+
const { cwd, rootView } = ctx;
|
|
4108
|
+
const appModulePath = (0, import_node_path15.join)(cwd, "src", "app.module.ts");
|
|
4569
4109
|
const appModuleResult = patchAppModule(appModulePath, rootView);
|
|
4570
4110
|
if (appModuleResult === "patched") {
|
|
4571
4111
|
logPatched("src/app.module.ts", "added InertiaModule.forRoot");
|
|
@@ -4577,7 +4117,9 @@ ${bold("nestjs-inertia init")}`);
|
|
|
4577
4117
|
} else {
|
|
4578
4118
|
logWarning("src/app.module.ts not found \u2014 add InertiaModule.forRoot() manually");
|
|
4579
4119
|
}
|
|
4580
|
-
|
|
4120
|
+
}
|
|
4121
|
+
function patchServerMainTs(ctx) {
|
|
4122
|
+
const mainTsPath = (0, import_node_path15.join)(ctx.cwd, "src", "main.ts");
|
|
4581
4123
|
const mainTsResult = patchMainTs(mainTsPath);
|
|
4582
4124
|
if (mainTsResult === "patched") {
|
|
4583
4125
|
logPatched("src/main.ts", "added setupInertiaVite after NestFactory.create");
|
|
@@ -4586,7 +4128,9 @@ ${bold("nestjs-inertia init")}`);
|
|
|
4586
4128
|
} else {
|
|
4587
4129
|
logWarning("src/main.ts not found \u2014 add setupInertiaVite() manually");
|
|
4588
4130
|
}
|
|
4589
|
-
|
|
4131
|
+
}
|
|
4132
|
+
function patchBuildConfigs(ctx) {
|
|
4133
|
+
const { cwd, shellDir } = ctx;
|
|
4590
4134
|
const nestCliResult = patchNestCliJson(cwd, shellDir);
|
|
4591
4135
|
if (nestCliResult === "patched") {
|
|
4592
4136
|
logPatched("nest-cli.json", `added asset copy for ${shellDir}/ \u2192 dist/${shellDir}/`);
|
|
@@ -4607,18 +4151,26 @@ ${bold("nestjs-inertia init")}`);
|
|
|
4607
4151
|
);
|
|
4608
4152
|
}
|
|
4609
4153
|
}
|
|
4610
|
-
|
|
4154
|
+
}
|
|
4155
|
+
async function patchGitignoreAndDist(ctx) {
|
|
4156
|
+
const { cwd } = ctx;
|
|
4157
|
+
await patchGitignore((0, import_node_path15.join)(cwd, ".gitignore"));
|
|
4611
4158
|
const tsconfigDistResult = patchTsconfigExclude(cwd, "dist", "tsconfig.json");
|
|
4612
4159
|
if (tsconfigDistResult === "patched") {
|
|
4613
4160
|
logPatched("tsconfig.json", "excluded dist/ from server compilation");
|
|
4614
4161
|
} else if (tsconfigDistResult === "already") {
|
|
4615
4162
|
console.log(` ${cyan("\u2192")} tsconfig.json ${dim("(dist/ already excluded, skipped)")}`);
|
|
4616
4163
|
}
|
|
4617
|
-
|
|
4164
|
+
}
|
|
4165
|
+
async function scaffoldPackageScripts(ctx) {
|
|
4166
|
+
await patchPackageJsonScripts(ctx.cwd, {
|
|
4618
4167
|
"build:client": "vite build",
|
|
4619
4168
|
"build:ssr": "VITE_SSR=1 vite build --ssr",
|
|
4620
4169
|
"typecheck:inertia": "tsc --noEmit -p tsconfig.inertia.json"
|
|
4621
4170
|
});
|
|
4171
|
+
}
|
|
4172
|
+
async function installMissingDeps(ctx) {
|
|
4173
|
+
const { cwd, framework, opts } = ctx;
|
|
4622
4174
|
logSection("Install dependencies");
|
|
4623
4175
|
const pkg = await readPackageJson(cwd);
|
|
4624
4176
|
const installedDeps = allDeps(pkg);
|
|
@@ -4652,6 +4204,55 @@ ${bold("nestjs-inertia init")}`);
|
|
|
4652
4204
|
installDeps(pkgManager, depsToInstall, false);
|
|
4653
4205
|
installDeps(pkgManager, devDepsToInstall, true);
|
|
4654
4206
|
}
|
|
4207
|
+
}
|
|
4208
|
+
var INIT_STEPS = [
|
|
4209
|
+
{ label: "scaffold files", run: scaffoldFiles },
|
|
4210
|
+
{
|
|
4211
|
+
label: "patch app.module.ts",
|
|
4212
|
+
run: (ctx) => {
|
|
4213
|
+
logSection("Patch existing files");
|
|
4214
|
+
patchServerAppModule(ctx);
|
|
4215
|
+
}
|
|
4216
|
+
},
|
|
4217
|
+
{ label: "patch main.ts", run: patchServerMainTs },
|
|
4218
|
+
{ label: "patch build configs", run: patchBuildConfigs },
|
|
4219
|
+
{ label: "patch .gitignore + dist exclude", run: patchGitignoreAndDist },
|
|
4220
|
+
{ label: "add package.json scripts", run: scaffoldPackageScripts },
|
|
4221
|
+
{ label: "install dependencies", run: installMissingDeps }
|
|
4222
|
+
];
|
|
4223
|
+
async function runInit(opts = {}) {
|
|
4224
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
4225
|
+
console.log(`
|
|
4226
|
+
${bold("nestjs-inertia init")}`);
|
|
4227
|
+
let framework = await detectFramework(cwd);
|
|
4228
|
+
if (!framework) {
|
|
4229
|
+
framework = await promptFramework();
|
|
4230
|
+
}
|
|
4231
|
+
const engine = await detectTemplateEngine(cwd);
|
|
4232
|
+
const engineLabel = engine === "html" ? "plain HTML" : engine;
|
|
4233
|
+
const frameworkLabel = framework.charAt(0).toUpperCase() + framework.slice(1);
|
|
4234
|
+
console.log(`
|
|
4235
|
+
Detected: ${bold(`${frameworkLabel} + ${engineLabel}`)}`);
|
|
4236
|
+
const shellFileName = engine === "html" ? "index.html" : `index.${engine === "handlebars" ? "hbs" : engine}`;
|
|
4237
|
+
const rootView = engine === "html" ? "inertia/index.html" : `inertia/index.${engine === "handlebars" ? "hbs" : engine}`;
|
|
4238
|
+
const ctx = {
|
|
4239
|
+
opts,
|
|
4240
|
+
cwd,
|
|
4241
|
+
framework,
|
|
4242
|
+
engine,
|
|
4243
|
+
shellFileName,
|
|
4244
|
+
entryExt: framework === "react" ? "tsx" : "ts",
|
|
4245
|
+
pageExt: framework === "react" ? "tsx" : framework === "vue" ? "vue" : "svelte",
|
|
4246
|
+
rootView,
|
|
4247
|
+
shellDir: rootView.split("/")[0]
|
|
4248
|
+
// e.g. "inertia" from "inertia/index.html"
|
|
4249
|
+
};
|
|
4250
|
+
for (const step of INIT_STEPS) {
|
|
4251
|
+
if (process.env.NESTJS_CODEGEN_DEBUG) {
|
|
4252
|
+
console.log(dim(` \xB7 ${step.label}`));
|
|
4253
|
+
}
|
|
4254
|
+
await step.run(ctx);
|
|
4255
|
+
}
|
|
4655
4256
|
console.log(`
|
|
4656
4257
|
${green("\u2713")} Setup complete! Run: ${bold("nest start --watch")}
|
|
4657
4258
|
`);
|
|
@@ -4659,18 +4260,18 @@ ${green("\u2713")} Setup complete! Run: ${bold("nest start --watch")}
|
|
|
4659
4260
|
|
|
4660
4261
|
// src/cli/doctor.ts
|
|
4661
4262
|
function checkFileExists(cwd, file) {
|
|
4662
|
-
return (0,
|
|
4263
|
+
return (0, import_node_fs4.existsSync)((0, import_node_path16.join)(cwd, file));
|
|
4663
4264
|
}
|
|
4664
4265
|
function readJson(path) {
|
|
4665
4266
|
try {
|
|
4666
|
-
const raw = (0,
|
|
4267
|
+
const raw = (0, import_node_fs4.readFileSync)(path, "utf8").replace(/\/\/.*$/gm, "");
|
|
4667
4268
|
return JSON.parse(raw);
|
|
4668
4269
|
} catch {
|
|
4669
4270
|
return null;
|
|
4670
4271
|
}
|
|
4671
4272
|
}
|
|
4672
4273
|
function writeJsonField(filePath, dotPath, value) {
|
|
4673
|
-
const raw = (0,
|
|
4274
|
+
const raw = (0, import_node_fs4.readFileSync)(filePath, "utf8");
|
|
4674
4275
|
const stripped = raw.replace(/\/\/.*$/gm, "");
|
|
4675
4276
|
const obj = JSON.parse(stripped);
|
|
4676
4277
|
let target = obj;
|
|
@@ -4690,20 +4291,20 @@ function writeJsonField(filePath, dotPath, value) {
|
|
|
4690
4291
|
} else {
|
|
4691
4292
|
target[lastKey] = value;
|
|
4692
4293
|
}
|
|
4693
|
-
(0,
|
|
4294
|
+
(0, import_node_fs4.writeFileSync)(filePath, `${JSON.stringify(obj, null, 2)}
|
|
4694
4295
|
`, "utf8");
|
|
4695
4296
|
}
|
|
4696
4297
|
function getPackageVersion(cwd, pkg) {
|
|
4697
4298
|
try {
|
|
4698
|
-
const pkgJson = readJson((0,
|
|
4299
|
+
const pkgJson = readJson((0, import_node_path16.join)(cwd, "node_modules", pkg, "package.json"));
|
|
4699
4300
|
return pkgJson?.version ?? null;
|
|
4700
4301
|
} catch {
|
|
4701
4302
|
return null;
|
|
4702
4303
|
}
|
|
4703
4304
|
}
|
|
4704
4305
|
function detectPkgManager(cwd) {
|
|
4705
|
-
if ((0,
|
|
4706
|
-
if ((0,
|
|
4306
|
+
if ((0, import_node_fs4.existsSync)((0, import_node_path16.join)(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
4307
|
+
if ((0, import_node_fs4.existsSync)((0, import_node_path16.join)(cwd, "yarn.lock"))) return "yarn";
|
|
4707
4308
|
return "npm";
|
|
4708
4309
|
}
|
|
4709
4310
|
async function runDoctor(opts) {
|
|
@@ -4741,7 +4342,7 @@ async function runDoctor(opts) {
|
|
|
4741
4342
|
autoFix: () => runInit({ cwd })
|
|
4742
4343
|
});
|
|
4743
4344
|
if (foundShellDir) {
|
|
4744
|
-
const nestCliPath = (0,
|
|
4345
|
+
const nestCliPath = (0, import_node_path16.join)(cwd, "nest-cli.json");
|
|
4745
4346
|
const nestCli = readJson(nestCliPath);
|
|
4746
4347
|
const compiler = nestCli?.compilerOptions ?? {};
|
|
4747
4348
|
const assets = compiler.assets ?? [];
|
|
@@ -4780,7 +4381,7 @@ async function runDoctor(opts) {
|
|
|
4780
4381
|
fix: "Run: nestjs-codegen codegen",
|
|
4781
4382
|
autoFix: () => runCodegen({ cwd })
|
|
4782
4383
|
});
|
|
4783
|
-
const tsconfigPath = (0,
|
|
4384
|
+
const tsconfigPath = (0, import_node_path16.join)(cwd, "tsconfig.json");
|
|
4784
4385
|
const tsconfig = readJson(tsconfigPath);
|
|
4785
4386
|
const paths = tsconfig?.compilerOptions?.paths;
|
|
4786
4387
|
checks.push({
|
|
@@ -4791,7 +4392,7 @@ async function runDoctor(opts) {
|
|
|
4791
4392
|
});
|
|
4792
4393
|
const inertiaDir = foundShellDir ?? "inertia";
|
|
4793
4394
|
for (const tsconfigFile of ["tsconfig.json", "tsconfig.build.json"]) {
|
|
4794
|
-
const tsc = readJson((0,
|
|
4395
|
+
const tsc = readJson((0, import_node_path16.join)(cwd, tsconfigFile));
|
|
4795
4396
|
if (!tsc) continue;
|
|
4796
4397
|
const excl = tsc.exclude ?? [];
|
|
4797
4398
|
const excludesIt = excl.includes(inertiaDir);
|
|
@@ -4817,14 +4418,14 @@ async function runDoctor(opts) {
|
|
|
4817
4418
|
}
|
|
4818
4419
|
});
|
|
4819
4420
|
}
|
|
4820
|
-
const inertiaTsconfigPath = (0,
|
|
4421
|
+
const inertiaTsconfigPath = (0, import_node_path16.join)(cwd, "tsconfig.inertia.json");
|
|
4821
4422
|
const inertiaTsconfig = readJson(inertiaTsconfigPath);
|
|
4822
4423
|
checks.push({
|
|
4823
4424
|
name: "tsconfig.inertia.json exists",
|
|
4824
4425
|
pass: !!inertiaTsconfig,
|
|
4825
4426
|
fix: "Create tsconfig.inertia.json (typechecks inertia/ + .nestjs-inertia/)",
|
|
4826
4427
|
autoFix: () => {
|
|
4827
|
-
(0,
|
|
4428
|
+
(0, import_node_fs4.writeFileSync)(inertiaTsconfigPath, TSCONFIG_INERTIA_TEMPLATE, "utf8");
|
|
4828
4429
|
}
|
|
4829
4430
|
});
|
|
4830
4431
|
if (inertiaTsconfig) {
|
|
@@ -4869,17 +4470,17 @@ async function runDoctor(opts) {
|
|
|
4869
4470
|
fix: 'Add "nestjs-inertia.d.ts" to include array (resolves InertiaRegistry augmentation)'
|
|
4870
4471
|
});
|
|
4871
4472
|
}
|
|
4872
|
-
const innerTsconfigPath = (0,
|
|
4473
|
+
const innerTsconfigPath = (0, import_node_path16.join)(cwd, "inertia", "tsconfig.json");
|
|
4873
4474
|
checks.push({
|
|
4874
4475
|
name: "inertia/tsconfig.json exists (VSCode picks up ~codegen alias)",
|
|
4875
|
-
pass: (0,
|
|
4476
|
+
pass: (0, import_node_fs4.existsSync)(innerTsconfigPath),
|
|
4876
4477
|
fix: "Create inertia/tsconfig.json that extends ../tsconfig.inertia.json",
|
|
4877
4478
|
autoFix: () => {
|
|
4878
|
-
(0,
|
|
4479
|
+
(0, import_node_fs4.writeFileSync)(innerTsconfigPath, INERTIA_TSCONFIG_TEMPLATE, "utf8");
|
|
4879
4480
|
}
|
|
4880
4481
|
});
|
|
4881
4482
|
if (checkFileExists(cwd, "vite.config.ts")) {
|
|
4882
|
-
const viteContent = (0,
|
|
4483
|
+
const viteContent = (0, import_node_fs4.readFileSync)((0, import_node_path16.join)(cwd, "vite.config.ts"), "utf8");
|
|
4883
4484
|
checks.push({
|
|
4884
4485
|
name: "vite.config.ts has resolve.alias",
|
|
4885
4486
|
pass: viteContent.includes("resolve") && viteContent.includes("alias"),
|
|
@@ -4944,16 +4545,16 @@ async function runDoctor(opts) {
|
|
|
4944
4545
|
});
|
|
4945
4546
|
}
|
|
4946
4547
|
if (checkFileExists(cwd, ".gitignore")) {
|
|
4947
|
-
const gitignorePath = (0,
|
|
4948
|
-
const gitignore = (0,
|
|
4548
|
+
const gitignorePath = (0, import_node_path16.join)(cwd, ".gitignore");
|
|
4549
|
+
const gitignore = (0, import_node_fs4.readFileSync)(gitignorePath, "utf8");
|
|
4949
4550
|
checks.push({
|
|
4950
4551
|
name: ".gitignore includes .nestjs-inertia/",
|
|
4951
4552
|
pass: gitignore.includes(".nestjs-inertia"),
|
|
4952
4553
|
fix: "Add .nestjs-inertia/ to .gitignore",
|
|
4953
|
-
autoFix: () => (0,
|
|
4554
|
+
autoFix: () => (0, import_node_fs4.appendFileSync)(gitignorePath, "\n.nestjs-inertia/\n")
|
|
4954
4555
|
});
|
|
4955
4556
|
}
|
|
4956
|
-
const pkgJsonPath = (0,
|
|
4557
|
+
const pkgJsonPath = (0, import_node_path16.join)(cwd, "package.json");
|
|
4957
4558
|
const pkgJson = readJson(pkgJsonPath);
|
|
4958
4559
|
const scripts = pkgJson?.scripts ?? {};
|
|
4959
4560
|
checks.push({
|