@dudousxd/nestjs-codegen 0.4.1 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +37 -0
- package/dist/cli/main.cjs +1080 -160
- package/dist/cli/main.cjs.map +1 -1
- package/dist/cli/main.js +1063 -143
- package/dist/cli/main.js.map +1 -1
- package/dist/extension/index.d.cts +1 -1
- package/dist/extension/index.d.ts +1 -1
- package/dist/{index-DA4uySjo.d.cts → index-B0mS84Jj.d.cts} +83 -1
- package/dist/{index-DA4uySjo.d.ts → index-B0mS84Jj.d.ts} +83 -1
- package/dist/index.cjs +1053 -118
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +104 -4
- package/dist/index.d.ts +104 -4
- package/dist/index.js +1034 -105
- package/dist/index.js.map +1 -1
- package/dist/nest/index.cjs +1015 -113
- package/dist/nest/index.cjs.map +1 -1
- package/dist/nest/index.d.cts +1 -1
- package/dist/nest/index.d.ts +1 -1
- package/dist/nest/index.js +1009 -107
- package/dist/nest/index.js.map +1 -1
- package/package.json +30 -11
package/dist/cli/main.cjs
CHANGED
|
@@ -160,6 +160,19 @@ function applyDefaults(userConfig, cwd) {
|
|
|
160
160
|
enabled: userConfig.forms?.enabled ?? true,
|
|
161
161
|
watch: userConfig.forms?.watch ?? "src/**/*.dto.ts",
|
|
162
162
|
zodImport: userConfig.forms?.zodImport ?? "zod"
|
|
163
|
+
},
|
|
164
|
+
openapi: {
|
|
165
|
+
enabled: userConfig.openapi?.enabled ?? false,
|
|
166
|
+
fileName: userConfig.openapi?.fileName ?? "openapi.json",
|
|
167
|
+
title: userConfig.openapi?.title ?? "NestJS API",
|
|
168
|
+
version: userConfig.openapi?.version ?? "1.0.0",
|
|
169
|
+
description: userConfig.openapi?.description ?? null
|
|
170
|
+
},
|
|
171
|
+
mocks: {
|
|
172
|
+
enabled: userConfig.mocks?.enabled ?? false,
|
|
173
|
+
fileName: userConfig.mocks?.fileName ?? "mocks.ts",
|
|
174
|
+
seed: userConfig.mocks?.seed ?? 1,
|
|
175
|
+
baseUrl: userConfig.mocks?.baseUrl ?? ""
|
|
163
176
|
}
|
|
164
177
|
};
|
|
165
178
|
}
|
|
@@ -197,8 +210,8 @@ Run \`nestjs-codegen init\` to create a starter config.`
|
|
|
197
210
|
}
|
|
198
211
|
|
|
199
212
|
// src/generate.ts
|
|
200
|
-
var
|
|
201
|
-
var
|
|
213
|
+
var import_promises11 = require("fs/promises");
|
|
214
|
+
var import_node_path12 = require("path");
|
|
202
215
|
|
|
203
216
|
// src/discovery/pages.ts
|
|
204
217
|
var import_promises2 = require("fs/promises");
|
|
@@ -727,17 +740,28 @@ function emitFilterQueryType(c) {
|
|
|
727
740
|
return `import('@dudousxd/nestjs-filter-client').TypedFilterQuery<${emitFilterQueryTypeArgs(c)}>`;
|
|
728
741
|
}
|
|
729
742
|
function buildResponseType(c, outDir) {
|
|
743
|
+
const respRef = c.contractSource.responseRef;
|
|
744
|
+
if (c.contractSource.stream) {
|
|
745
|
+
if (respRef) return respRef.isArray ? `Array<${respRef.name}>` : respRef.name;
|
|
746
|
+
return c.contractSource.response;
|
|
747
|
+
}
|
|
730
748
|
if (c.controllerRef) {
|
|
731
749
|
let relPath = (0, import_node_path4.relative)(outDir, c.controllerRef.filePath).replace(/\.ts$/, "");
|
|
732
750
|
if (!relPath.startsWith(".")) relPath = `./${relPath}`;
|
|
733
751
|
return `Awaited<ReturnType<import('${relPath}').${c.controllerRef.className}['${c.controllerRef.methodName}']>>`;
|
|
734
752
|
}
|
|
735
|
-
const respRef = c.contractSource.responseRef;
|
|
736
753
|
if (respRef) {
|
|
737
754
|
return respRef.isArray ? `Array<${respRef.name}>` : respRef.name;
|
|
738
755
|
}
|
|
739
756
|
return c.contractSource.response;
|
|
740
757
|
}
|
|
758
|
+
function buildErrorType(c) {
|
|
759
|
+
const errRef = c.contractSource.errorRef;
|
|
760
|
+
if (errRef) {
|
|
761
|
+
return errRef.isArray ? `Array<${errRef.name}>` : errRef.name;
|
|
762
|
+
}
|
|
763
|
+
return c.contractSource.error ?? "unknown";
|
|
764
|
+
}
|
|
741
765
|
function emitRouterTypeBlock(tree, indent, outDir) {
|
|
742
766
|
const pad = " ".repeat(indent);
|
|
743
767
|
const lines = [];
|
|
@@ -752,12 +776,14 @@ function emitRouterTypeBlock(tree, indent, outDir) {
|
|
|
752
776
|
const bodyRef = c.contractSource.bodyRef;
|
|
753
777
|
const body = method === "GET" ? "never" : bodyRef ? bodyRef.isArray ? `Array<${bodyRef.name}>` : bodyRef.name : c.contractSource.body ?? "never";
|
|
754
778
|
const response = buildResponseType(c, outDir);
|
|
779
|
+
const error = buildErrorType(c);
|
|
755
780
|
const params = buildParamsType(c.params);
|
|
756
781
|
const safeMethod = JSON.stringify(method);
|
|
757
782
|
const safeUrl = JSON.stringify(c.path);
|
|
758
783
|
const filterFields = c.contractSource.filterFields?.length ? c.contractSource.filterFields.map((f) => JSON.stringify(f)).join(" | ") : "never";
|
|
784
|
+
const stream = c.contractSource.stream ? "true" : "false";
|
|
759
785
|
lines.push(
|
|
760
|
-
`${pad}${objKey}: { method: ${safeMethod}; url: ${safeUrl}; params: ${params}; query: ${query}; body: ${body}; response: ${response}; filterFields: ${filterFields} };`
|
|
786
|
+
`${pad}${objKey}: { method: ${safeMethod}; url: ${safeUrl}; params: ${params}; query: ${query}; body: ${body}; response: ${response}; error: ${error}; filterFields: ${filterFields}; stream: ${stream} };`
|
|
761
787
|
);
|
|
762
788
|
} else {
|
|
763
789
|
lines.push(`${pad}${objKey}: {`);
|
|
@@ -829,15 +855,21 @@ function emitReqHelper() {
|
|
|
829
855
|
""
|
|
830
856
|
];
|
|
831
857
|
}
|
|
832
|
-
function renderLeaf(pad, objKey, req, requestExpr, members) {
|
|
858
|
+
function renderLeaf(pad, objKey, req, requestExpr, members, streamExpr) {
|
|
833
859
|
const lines = [`${pad}${objKey}: (input?: ${req.inputType}) => ({`];
|
|
834
860
|
lines.push(`${pad} ...__req<${req.responseType}>(() => ${requestExpr}),`);
|
|
861
|
+
if (streamExpr) {
|
|
862
|
+
lines.push(`${pad} stream: () => ${streamExpr},`);
|
|
863
|
+
}
|
|
835
864
|
for (const [name, value] of Object.entries(members)) {
|
|
836
865
|
lines.push(`${pad} ${name}: ${value},`);
|
|
837
866
|
}
|
|
838
867
|
lines.push(`${pad}}),`);
|
|
839
868
|
return lines;
|
|
840
869
|
}
|
|
870
|
+
function renderStreamExpr(req) {
|
|
871
|
+
return `fetcher.sse<${req.responseType}>(${req.urlExpr}, ${req.optsExpr})`;
|
|
872
|
+
}
|
|
841
873
|
function emitApiObjectBlock(tree, indent, p) {
|
|
842
874
|
const pad = " ".repeat(indent);
|
|
843
875
|
const lines = [];
|
|
@@ -872,7 +904,8 @@ function emitApiObjectBlock(tree, indent, p) {
|
|
|
872
904
|
}
|
|
873
905
|
const members = {};
|
|
874
906
|
for (const [name, { value }] of owned) members[name] = value;
|
|
875
|
-
|
|
907
|
+
const streamExpr = node.contractSource.stream ? renderStreamExpr(req) : void 0;
|
|
908
|
+
lines.push(...renderLeaf(pad, objKey, req, leaf.requestExpr, members, streamExpr));
|
|
876
909
|
}
|
|
877
910
|
return lines;
|
|
878
911
|
}
|
|
@@ -910,6 +943,8 @@ var ROUTE_NAMESPACE = [
|
|
|
910
943
|
' export type Params<K extends string> = ResolveByName<K, "params">;',
|
|
911
944
|
' export type Error<K extends string> = ResolveByName<K, "error">;',
|
|
912
945
|
' export type FilterFields<K extends string> = ResolveByName<K, "filterFields">;',
|
|
946
|
+
" /** The streamed element type of an `@Sse()`/streaming route \u2014 the type yielded by its `stream()` AsyncIterable. */",
|
|
947
|
+
' export type Stream<K extends string> = ResolveByName<K, "response">;',
|
|
913
948
|
" export type Request<K extends string> = {",
|
|
914
949
|
" body: Body<K>;",
|
|
915
950
|
" query: Query<K>;",
|
|
@@ -926,6 +961,7 @@ var PATH_NAMESPACE = [
|
|
|
926
961
|
' export type Params<M extends string, U extends string> = ResolveByPath<M, U, "params">;',
|
|
927
962
|
' export type Error<M extends string, U extends string> = ResolveByPath<M, U, "error">;',
|
|
928
963
|
' export type FilterFields<M extends string, U extends string> = ResolveByPath<M, U, "filterFields">;',
|
|
964
|
+
' export type Stream<M extends string, U extends string> = ResolveByPath<M, U, "response">;',
|
|
929
965
|
"}",
|
|
930
966
|
""
|
|
931
967
|
];
|
|
@@ -937,6 +973,7 @@ var EMPTY_ROUTE_NAMESPACE = [
|
|
|
937
973
|
" export type Params<K extends string> = never;",
|
|
938
974
|
" export type Error<K extends string> = never;",
|
|
939
975
|
" export type FilterFields<K extends string> = never;",
|
|
976
|
+
" export type Stream<K extends string> = never;",
|
|
940
977
|
" export type Request<K extends string> = { body: never; query: never; params: never };",
|
|
941
978
|
"}",
|
|
942
979
|
""
|
|
@@ -949,6 +986,7 @@ var EMPTY_PATH_NAMESPACE = [
|
|
|
949
986
|
" export type Params<M extends string, U extends string> = never;",
|
|
950
987
|
" export type Error<M extends string, U extends string> = never;",
|
|
951
988
|
" export type FilterFields<M extends string, U extends string> = never;",
|
|
989
|
+
" export type Stream<M extends string, U extends string> = never;",
|
|
952
990
|
"}",
|
|
953
991
|
""
|
|
954
992
|
];
|
|
@@ -972,7 +1010,7 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
972
1010
|
for (const r of contracted) {
|
|
973
1011
|
const cs = r.contract?.contractSource;
|
|
974
1012
|
if (!cs) continue;
|
|
975
|
-
const refs = r.controllerRef ? [cs.queryRef, cs.bodyRef] : [cs.queryRef, cs.bodyRef, cs.responseRef];
|
|
1013
|
+
const refs = r.controllerRef && !cs.stream ? [cs.queryRef, cs.bodyRef, cs.errorRef] : [cs.queryRef, cs.bodyRef, cs.responseRef, cs.errorRef];
|
|
976
1014
|
for (const ref of refs) {
|
|
977
1015
|
if (!ref) continue;
|
|
978
1016
|
let names = importsByFile.get(ref.filePath);
|
|
@@ -1150,18 +1188,27 @@ function refRootIdentifier(refName) {
|
|
|
1150
1188
|
function hasSource(src) {
|
|
1151
1189
|
return !!(src.schema || src.zodText || src.zodRef);
|
|
1152
1190
|
}
|
|
1191
|
+
function escapeRegExp(s) {
|
|
1192
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1193
|
+
}
|
|
1194
|
+
var wordBoundaryRegexCache = /* @__PURE__ */ new Map();
|
|
1195
|
+
function wordBoundaryRegex(token) {
|
|
1196
|
+
let re = wordBoundaryRegexCache.get(token);
|
|
1197
|
+
if (re === void 0) {
|
|
1198
|
+
re = new RegExp(`\\b${escapeRegExp(token)}\\b`, "g");
|
|
1199
|
+
wordBoundaryRegexCache.set(token, re);
|
|
1200
|
+
}
|
|
1201
|
+
return re;
|
|
1202
|
+
}
|
|
1153
1203
|
function applyRenames(text, renames) {
|
|
1154
1204
|
if (!renames || renames.size === 0) return text;
|
|
1155
1205
|
let out = text;
|
|
1156
1206
|
for (const [from, to] of renames) {
|
|
1157
1207
|
if (from === to) continue;
|
|
1158
|
-
out = out.replace(
|
|
1208
|
+
out = out.replace(wordBoundaryRegex(from), to);
|
|
1159
1209
|
}
|
|
1160
1210
|
return out;
|
|
1161
1211
|
}
|
|
1162
|
-
function escapeRegExp(s) {
|
|
1163
|
-
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1164
|
-
}
|
|
1165
1212
|
function isSelfReferential(name, text) {
|
|
1166
1213
|
return new RegExp(`\\b${escapeRegExp(name)}\\b`).test(text);
|
|
1167
1214
|
}
|
|
@@ -1173,7 +1220,23 @@ function planNestedSchemas(entries) {
|
|
|
1173
1220
|
const local = Object.entries(entry.nestedSchemas);
|
|
1174
1221
|
if (local.length === 0) continue;
|
|
1175
1222
|
const rename = /* @__PURE__ */ new Map();
|
|
1176
|
-
|
|
1223
|
+
const renameValues = /* @__PURE__ */ new Set();
|
|
1224
|
+
const setRename = (key, value) => {
|
|
1225
|
+
const prev = rename.get(key);
|
|
1226
|
+
rename.set(key, value);
|
|
1227
|
+
if (prev !== void 0 && prev !== value) {
|
|
1228
|
+
let stillUsed = false;
|
|
1229
|
+
for (const v of rename.values()) {
|
|
1230
|
+
if (v === prev) {
|
|
1231
|
+
stillUsed = true;
|
|
1232
|
+
break;
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
if (!stillUsed) renameValues.delete(prev);
|
|
1236
|
+
}
|
|
1237
|
+
renameValues.add(value);
|
|
1238
|
+
};
|
|
1239
|
+
for (const [name] of local) setRename(name, name);
|
|
1177
1240
|
const textFor = (name) => {
|
|
1178
1241
|
const raw = entry.nestedSchemas?.[name] ?? "";
|
|
1179
1242
|
return applyRenames(raw, rename);
|
|
@@ -1191,11 +1254,11 @@ function planNestedSchemas(entries) {
|
|
|
1191
1254
|
if (existing === text) continue;
|
|
1192
1255
|
let i = 2;
|
|
1193
1256
|
let candidate = `${name}_${i}`;
|
|
1194
|
-
while (globalSchemas.has(candidate) && globalSchemas.get(candidate) !== textFor(name) ||
|
|
1257
|
+
while (globalSchemas.has(candidate) && globalSchemas.get(candidate) !== textFor(name) || renameValues.has(candidate)) {
|
|
1195
1258
|
i += 1;
|
|
1196
1259
|
candidate = `${name}_${i}`;
|
|
1197
1260
|
}
|
|
1198
|
-
|
|
1261
|
+
setRename(name, candidate);
|
|
1199
1262
|
changed = true;
|
|
1200
1263
|
}
|
|
1201
1264
|
}
|
|
@@ -1405,11 +1468,467 @@ async function emitIndex(outDir, hasContracts = false, hasForms = false) {
|
|
|
1405
1468
|
await (0, import_promises6.writeFile)((0, import_node_path7.join)(outDir, "index.d.ts"), content, "utf8");
|
|
1406
1469
|
}
|
|
1407
1470
|
|
|
1408
|
-
// src/emit/emit-
|
|
1471
|
+
// src/emit/emit-mocks.ts
|
|
1409
1472
|
var import_promises7 = require("fs/promises");
|
|
1410
1473
|
var import_node_path8 = require("path");
|
|
1411
|
-
|
|
1474
|
+
|
|
1475
|
+
// src/ir/schema-node-to-json-schema.ts
|
|
1476
|
+
var DEFAULT_CTX = { refPrefix: "#/components/schemas/" };
|
|
1477
|
+
function parseLiteral(raw) {
|
|
1478
|
+
const t = raw.trim();
|
|
1479
|
+
if (t === "true") return true;
|
|
1480
|
+
if (t === "false") return false;
|
|
1481
|
+
if (t === "null") return null;
|
|
1482
|
+
const q = t[0];
|
|
1483
|
+
if ((q === "'" || q === '"' || q === "`") && t[t.length - 1] === q) {
|
|
1484
|
+
return t.slice(1, -1).replace(/\\'/g, "'").replace(/\\"/g, '"').replace(/\\`/g, "`").replace(/\\\\/g, "\\");
|
|
1485
|
+
}
|
|
1486
|
+
if (/^[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/.test(t)) {
|
|
1487
|
+
return Number(t);
|
|
1488
|
+
}
|
|
1489
|
+
return t;
|
|
1490
|
+
}
|
|
1491
|
+
function literalsType(values) {
|
|
1492
|
+
const types = new Set(values.map((v) => v === null ? "null" : typeof v));
|
|
1493
|
+
if (types.size === 1) {
|
|
1494
|
+
const only = [...types][0];
|
|
1495
|
+
if (only === "string") return "string";
|
|
1496
|
+
if (only === "number") return "number";
|
|
1497
|
+
if (only === "boolean") return "boolean";
|
|
1498
|
+
}
|
|
1499
|
+
return void 0;
|
|
1500
|
+
}
|
|
1501
|
+
function convert(node, ctx) {
|
|
1502
|
+
switch (node.kind) {
|
|
1503
|
+
case "string": {
|
|
1504
|
+
const out = { type: "string" };
|
|
1505
|
+
for (const c of node.checks) {
|
|
1506
|
+
if (c.check === "email") out.format = "email";
|
|
1507
|
+
else if (c.check === "url") out.format = "uri";
|
|
1508
|
+
else if (c.check === "uuid") out.format = "uuid";
|
|
1509
|
+
else if (c.check === "min") out.minLength = Number(c.value);
|
|
1510
|
+
else if (c.check === "max") out.maxLength = Number(c.value);
|
|
1511
|
+
else if (c.check === "regex") {
|
|
1512
|
+
const m = /^\/(.*)\/[a-z]*$/.exec(c.pattern);
|
|
1513
|
+
out.pattern = m ? m[1] : c.pattern;
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
return out;
|
|
1517
|
+
}
|
|
1518
|
+
case "number": {
|
|
1519
|
+
const out = { type: "number" };
|
|
1520
|
+
for (const c of node.checks) {
|
|
1521
|
+
if (c.check === "int") out.type = "integer";
|
|
1522
|
+
else if (c.check === "min") out.minimum = Number(c.value);
|
|
1523
|
+
else if (c.check === "max") out.maximum = Number(c.value);
|
|
1524
|
+
else if (c.check === "positive") out.exclusiveMinimum = 0;
|
|
1525
|
+
else if (c.check === "negative") out.exclusiveMaximum = 0;
|
|
1526
|
+
}
|
|
1527
|
+
return out;
|
|
1528
|
+
}
|
|
1529
|
+
case "boolean":
|
|
1530
|
+
return { type: "boolean" };
|
|
1531
|
+
case "date":
|
|
1532
|
+
return { type: "string", format: "date-time" };
|
|
1533
|
+
case "unknown":
|
|
1534
|
+
return node.note ? { description: node.note } : {};
|
|
1535
|
+
case "instanceof":
|
|
1536
|
+
return { type: "object", description: `instanceof ${node.ctor}` };
|
|
1537
|
+
case "enum": {
|
|
1538
|
+
const values = node.literals.map(parseLiteral);
|
|
1539
|
+
const t = literalsType(values);
|
|
1540
|
+
const out = { enum: values };
|
|
1541
|
+
if (t) out.type = t;
|
|
1542
|
+
return out;
|
|
1543
|
+
}
|
|
1544
|
+
case "literal": {
|
|
1545
|
+
const value = parseLiteral(node.raw);
|
|
1546
|
+
const out = { const: value };
|
|
1547
|
+
const t = literalsType([value]);
|
|
1548
|
+
if (t) out.type = t;
|
|
1549
|
+
return out;
|
|
1550
|
+
}
|
|
1551
|
+
case "union": {
|
|
1552
|
+
const options = node.options.map((o) => convert(o, ctx));
|
|
1553
|
+
const out = { oneOf: options };
|
|
1554
|
+
if (node.discriminator) {
|
|
1555
|
+
out.discriminator = { propertyName: node.discriminator };
|
|
1556
|
+
}
|
|
1557
|
+
return out;
|
|
1558
|
+
}
|
|
1559
|
+
case "object": {
|
|
1560
|
+
const properties = {};
|
|
1561
|
+
const required = [];
|
|
1562
|
+
for (const f of node.fields) {
|
|
1563
|
+
if (f.value.kind === "optional") {
|
|
1564
|
+
properties[f.key] = convert(f.value.inner, ctx);
|
|
1565
|
+
} else {
|
|
1566
|
+
properties[f.key] = convert(f.value, ctx);
|
|
1567
|
+
required.push(f.key);
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
const out = { type: "object", properties };
|
|
1571
|
+
if (required.length > 0) out.required = required;
|
|
1572
|
+
out.additionalProperties = node.passthrough;
|
|
1573
|
+
return out;
|
|
1574
|
+
}
|
|
1575
|
+
case "array":
|
|
1576
|
+
return { type: "array", items: convert(node.element, ctx) };
|
|
1577
|
+
case "optional":
|
|
1578
|
+
return widenNullable(convert(node.inner, ctx));
|
|
1579
|
+
case "ref":
|
|
1580
|
+
case "lazyRef":
|
|
1581
|
+
return { $ref: `${ctx.refPrefix}${node.name}` };
|
|
1582
|
+
case "annotated":
|
|
1583
|
+
return convert(node.inner, ctx);
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
function widenNullable(schema) {
|
|
1587
|
+
if (schema.$ref) {
|
|
1588
|
+
return { anyOf: [schema, { type: "null" }] };
|
|
1589
|
+
}
|
|
1590
|
+
if (typeof schema.type === "string") {
|
|
1591
|
+
return { ...schema, type: [schema.type, "null"] };
|
|
1592
|
+
}
|
|
1593
|
+
if (Array.isArray(schema.type)) {
|
|
1594
|
+
return schema.type.includes("null") ? schema : { ...schema, type: [...schema.type, "null"] };
|
|
1595
|
+
}
|
|
1596
|
+
return { anyOf: [schema, { type: "null" }] };
|
|
1597
|
+
}
|
|
1598
|
+
function schemaModuleToJsonSchema(mod, ctx = DEFAULT_CTX) {
|
|
1599
|
+
const named = {};
|
|
1600
|
+
for (const [name, node] of mod.named) {
|
|
1601
|
+
named[name] = convert(node, ctx);
|
|
1602
|
+
}
|
|
1603
|
+
return { root: convert(mod.root, ctx), named };
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
// src/emit/mock-gen-runtime.ts
|
|
1607
|
+
var MOCK_GEN_RUNTIME = `
|
|
1608
|
+
/** mulberry32 \u2014 a tiny, fast, seedable PRNG. \`next()\` returns a float in [0, 1). */
|
|
1609
|
+
function makeRng(seed) {
|
|
1610
|
+
let a = seed >>> 0;
|
|
1611
|
+
return {
|
|
1612
|
+
next() {
|
|
1613
|
+
a |= 0;
|
|
1614
|
+
a = (a + 0x6d2b79f5) | 0;
|
|
1615
|
+
let t = Math.imul(a ^ (a >>> 15), 1 | a);
|
|
1616
|
+
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
|
1617
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
1618
|
+
},
|
|
1619
|
+
};
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
function __pick(rng, items) {
|
|
1623
|
+
return items[Math.floor(rng.next() * items.length)];
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
function __intBetween(rng, min, max) {
|
|
1627
|
+
return Math.floor(rng.next() * (max - min + 1)) + min;
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
const __WORDS = ['lorem', 'ipsum', 'dolor', 'sit', 'amet', 'consectetur', 'adipiscing', 'elit', 'sed', 'tempor'];
|
|
1631
|
+
const __FIRST_NAMES = ['Ada', 'Alan', 'Grace', 'Linus', 'Margaret', 'Dennis'];
|
|
1632
|
+
const __LAST_NAMES = ['Lovelace', 'Turing', 'Hopper', 'Torvalds', 'Hamilton', 'Ritchie'];
|
|
1633
|
+
|
|
1634
|
+
function __fakeWords(rng, count) {
|
|
1635
|
+
let out = [];
|
|
1636
|
+
for (let i = 0; i < count; i++) out.push(__pick(rng, __WORDS));
|
|
1637
|
+
return out.join(' ');
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
function __hex(rng, len) {
|
|
1641
|
+
let s = '';
|
|
1642
|
+
for (let i = 0; i < len; i++) s += Math.floor(rng.next() * 16).toString(16);
|
|
1643
|
+
return s;
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
function __fakeUuid(rng) {
|
|
1647
|
+
return __hex(rng, 8) + '-' + __hex(rng, 4) + '-4' + __hex(rng, 3) + '-' + __pick(rng, ['8', '9', 'a', 'b']) + __hex(rng, 3) + '-' + __hex(rng, 12);
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
function __fakeString(rng, schema) {
|
|
1651
|
+
switch (schema.format) {
|
|
1652
|
+
case 'email':
|
|
1653
|
+
return __pick(rng, __FIRST_NAMES).toLowerCase() + '.' + __pick(rng, __LAST_NAMES).toLowerCase() + '@example.com';
|
|
1654
|
+
case 'uri':
|
|
1655
|
+
case 'url':
|
|
1656
|
+
return 'https://example.com/' + __pick(rng, __WORDS);
|
|
1657
|
+
case 'uuid':
|
|
1658
|
+
return __fakeUuid(rng);
|
|
1659
|
+
case 'date-time':
|
|
1660
|
+
return new Date(Date.UTC(2020, __intBetween(rng, 0, 11), __intBetween(rng, 1, 28))).toISOString();
|
|
1661
|
+
default:
|
|
1662
|
+
return __fakeWords(rng, __intBetween(rng, 1, 3));
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
/** Generate a mock value for a JSON Schema node (depth-capped recursion via $ref). */
|
|
1667
|
+
function generateMock(schema, rng, defs, depth) {
|
|
1668
|
+
defs = defs || {};
|
|
1669
|
+
depth = depth || 0;
|
|
1670
|
+
if (schema.$ref) {
|
|
1671
|
+
const name = schema.$ref.replace('#/components/schemas/', '');
|
|
1672
|
+
const target = defs[name];
|
|
1673
|
+
if (!target || depth > 4) return null;
|
|
1674
|
+
return generateMock(target, rng, defs, depth + 1);
|
|
1675
|
+
}
|
|
1676
|
+
if ('const' in schema) return schema.const;
|
|
1677
|
+
if (schema.enum && schema.enum.length > 0) return __pick(rng, schema.enum);
|
|
1678
|
+
if (schema.oneOf && schema.oneOf.length > 0) return generateMock(__pick(rng, schema.oneOf), rng, defs, depth);
|
|
1679
|
+
if (schema.anyOf && schema.anyOf.length > 0) return generateMock(__pick(rng, schema.anyOf), rng, defs, depth);
|
|
1680
|
+
let type = Array.isArray(schema.type)
|
|
1681
|
+
? (schema.type.filter((t) => t !== 'null')[0] || 'null')
|
|
1682
|
+
: schema.type;
|
|
1683
|
+
switch (type) {
|
|
1684
|
+
case 'string':
|
|
1685
|
+
return __fakeString(rng, schema);
|
|
1686
|
+
case 'integer':
|
|
1687
|
+
return __intBetween(rng, typeof schema.minimum === 'number' ? schema.minimum : 0, typeof schema.maximum === 'number' ? schema.maximum : 1000);
|
|
1688
|
+
case 'number':
|
|
1689
|
+
return __intBetween(rng, typeof schema.minimum === 'number' ? schema.minimum : 0, typeof schema.maximum === 'number' ? schema.maximum : 1000) + Math.round(rng.next() * 100) / 100;
|
|
1690
|
+
case 'boolean':
|
|
1691
|
+
return rng.next() < 0.5;
|
|
1692
|
+
case 'null':
|
|
1693
|
+
return null;
|
|
1694
|
+
case 'array': {
|
|
1695
|
+
const count = depth > 2 ? 0 : __intBetween(rng, 1, 2);
|
|
1696
|
+
const items = schema.items || {};
|
|
1697
|
+
let arr = [];
|
|
1698
|
+
for (let i = 0; i < count; i++) arr.push(generateMock(items, rng, defs, depth + 1));
|
|
1699
|
+
return arr;
|
|
1700
|
+
}
|
|
1701
|
+
case 'object': {
|
|
1702
|
+
const out = {};
|
|
1703
|
+
const props = schema.properties || {};
|
|
1704
|
+
for (const key of Object.keys(props)) out[key] = generateMock(props[key], rng, defs, depth + 1);
|
|
1705
|
+
return out;
|
|
1706
|
+
}
|
|
1707
|
+
default:
|
|
1708
|
+
return {};
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
`.trim();
|
|
1712
|
+
|
|
1713
|
+
// src/emit/emit-mocks.ts
|
|
1714
|
+
var REF_PREFIX = "#/components/schemas/";
|
|
1715
|
+
function toMswPath(path, baseUrl) {
|
|
1716
|
+
return `${baseUrl}${path}`;
|
|
1717
|
+
}
|
|
1718
|
+
function responseSchemaFor(route, defs) {
|
|
1719
|
+
const cs = route.contract.contractSource;
|
|
1720
|
+
if (cs.responseSchema) {
|
|
1721
|
+
const { root, named } = schemaModuleToJsonSchema(cs.responseSchema, { refPrefix: REF_PREFIX });
|
|
1722
|
+
for (const [name, node] of Object.entries(named)) {
|
|
1723
|
+
if (!(name in defs)) defs[name] = node;
|
|
1724
|
+
}
|
|
1725
|
+
return root;
|
|
1726
|
+
}
|
|
1727
|
+
return {};
|
|
1728
|
+
}
|
|
1729
|
+
function buildMocksFile(routes, opts = {}) {
|
|
1730
|
+
const seed = opts.seed ?? 1;
|
|
1731
|
+
const baseUrl = opts.baseUrl ?? "";
|
|
1732
|
+
const contracted = routes.filter((r) => r.contract);
|
|
1733
|
+
const defs = {};
|
|
1734
|
+
const handlers = [];
|
|
1735
|
+
for (const r of contracted) {
|
|
1736
|
+
const schema = responseSchemaFor(r, defs);
|
|
1737
|
+
const method = r.method.toLowerCase();
|
|
1738
|
+
const mswMethod = method === "get" || method === "post" || method === "put" || method === "patch" || method === "delete" ? method : "all";
|
|
1739
|
+
const path = toMswPath(r.path, baseUrl);
|
|
1740
|
+
const cs = r.contract.contractSource;
|
|
1741
|
+
const schemaLiteral = JSON.stringify(schema);
|
|
1742
|
+
const pathLit = JSON.stringify(path);
|
|
1743
|
+
if (cs.stream) {
|
|
1744
|
+
handlers.push(
|
|
1745
|
+
[
|
|
1746
|
+
` // ${r.name} (stream)`,
|
|
1747
|
+
` http.${mswMethod}(${pathLit}, () => {`,
|
|
1748
|
+
` const value = generateMock(${schemaLiteral}, makeRng(SEED), DEFS);`,
|
|
1749
|
+
" const body = `data: ${JSON.stringify(value)}\\n\\n`;",
|
|
1750
|
+
" return new HttpResponse(body, { headers: { 'Content-Type': 'text/event-stream' } });",
|
|
1751
|
+
" }),"
|
|
1752
|
+
].join("\n")
|
|
1753
|
+
);
|
|
1754
|
+
} else {
|
|
1755
|
+
handlers.push(
|
|
1756
|
+
[
|
|
1757
|
+
` // ${r.name}`,
|
|
1758
|
+
` http.${mswMethod}(${pathLit}, () => {`,
|
|
1759
|
+
` const value = generateMock(${schemaLiteral}, makeRng(SEED), DEFS);`,
|
|
1760
|
+
" return HttpResponse.json(value);",
|
|
1761
|
+
" }),"
|
|
1762
|
+
].join("\n")
|
|
1763
|
+
);
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
const lines = [
|
|
1767
|
+
"// Generated by @dudousxd/nestjs-codegen. Do not edit.",
|
|
1768
|
+
"// MSW handlers returning deterministic, schema-shaped mock data.",
|
|
1769
|
+
"/* eslint-disable */",
|
|
1770
|
+
"// @ts-nocheck",
|
|
1771
|
+
"",
|
|
1772
|
+
"import { http, HttpResponse } from 'msw';",
|
|
1773
|
+
"",
|
|
1774
|
+
`const SEED = ${seed};`,
|
|
1775
|
+
"",
|
|
1776
|
+
"// ---------------------------------------------------------------------------",
|
|
1777
|
+
"// Embedded mock-data runtime (mulberry32 PRNG + JSON-Schema value generator).",
|
|
1778
|
+
"// Dependency-free: no @faker-js/faker. Deterministic for a given SEED.",
|
|
1779
|
+
"// ---------------------------------------------------------------------------",
|
|
1780
|
+
MOCK_GEN_RUNTIME,
|
|
1781
|
+
"",
|
|
1782
|
+
"// Shared component schemas referenced by $ref.",
|
|
1783
|
+
`const DEFS = ${JSON.stringify(defs, null, 2)};`,
|
|
1784
|
+
"",
|
|
1785
|
+
"/** MSW request handlers, one per contracted route. */",
|
|
1786
|
+
"export const handlers = [",
|
|
1787
|
+
...handlers,
|
|
1788
|
+
"];",
|
|
1789
|
+
""
|
|
1790
|
+
];
|
|
1791
|
+
return lines.join("\n");
|
|
1792
|
+
}
|
|
1793
|
+
async function emitMocks(routes, outDir, opts = {}) {
|
|
1412
1794
|
await (0, import_promises7.mkdir)(outDir, { recursive: true });
|
|
1795
|
+
const content = buildMocksFile(routes, opts);
|
|
1796
|
+
const fileName = opts.fileName ?? "mocks.ts";
|
|
1797
|
+
await (0, import_promises7.writeFile)((0, import_node_path8.join)(outDir, fileName), content, "utf8");
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
// src/emit/emit-openapi.ts
|
|
1801
|
+
var import_promises8 = require("fs/promises");
|
|
1802
|
+
var import_node_path9 = require("path");
|
|
1803
|
+
var REF_PREFIX2 = "#/components/schemas/";
|
|
1804
|
+
function toOpenApiPath(path) {
|
|
1805
|
+
return path.replace(/:([^/]+)/g, "{$1}");
|
|
1806
|
+
}
|
|
1807
|
+
function positionSchema(schema, tsType, components) {
|
|
1808
|
+
if (schema) {
|
|
1809
|
+
const { root, named } = schemaModuleToJsonSchema(schema, { refPrefix: REF_PREFIX2 });
|
|
1810
|
+
for (const [name, node] of Object.entries(named)) {
|
|
1811
|
+
if (!(name in components)) components[name] = node;
|
|
1812
|
+
}
|
|
1813
|
+
return root;
|
|
1814
|
+
}
|
|
1815
|
+
return tsType ? { description: tsType } : {};
|
|
1816
|
+
}
|
|
1817
|
+
function buildParameters(route) {
|
|
1818
|
+
const params = [];
|
|
1819
|
+
for (const p of route.params) {
|
|
1820
|
+
if (p.source === "path") {
|
|
1821
|
+
params.push({
|
|
1822
|
+
name: p.name,
|
|
1823
|
+
in: "path",
|
|
1824
|
+
required: true,
|
|
1825
|
+
schema: { type: "string" }
|
|
1826
|
+
});
|
|
1827
|
+
} else if (p.source === "query") {
|
|
1828
|
+
params.push({
|
|
1829
|
+
name: p.name,
|
|
1830
|
+
in: "query",
|
|
1831
|
+
required: false,
|
|
1832
|
+
schema: { type: "string" }
|
|
1833
|
+
});
|
|
1834
|
+
} else if (p.source === "header") {
|
|
1835
|
+
params.push({
|
|
1836
|
+
name: p.name,
|
|
1837
|
+
in: "header",
|
|
1838
|
+
required: false,
|
|
1839
|
+
schema: { type: "string" }
|
|
1840
|
+
});
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
return params;
|
|
1844
|
+
}
|
|
1845
|
+
function buildResponses(cs, components) {
|
|
1846
|
+
const responses = {};
|
|
1847
|
+
const successSchema = positionSchema(
|
|
1848
|
+
// Prefer rich response IR when present; otherwise fall back to the TS type.
|
|
1849
|
+
cs.responseSchema ?? null,
|
|
1850
|
+
cs.response,
|
|
1851
|
+
components
|
|
1852
|
+
);
|
|
1853
|
+
const successContentType = cs.stream ? "text/event-stream" : "application/json";
|
|
1854
|
+
responses["200"] = {
|
|
1855
|
+
description: cs.stream ? "Server-sent event stream" : "Successful response",
|
|
1856
|
+
content: { [successContentType]: { schema: successSchema } }
|
|
1857
|
+
};
|
|
1858
|
+
const errorSchema = positionSchema(null, cs.error ?? null, components);
|
|
1859
|
+
const errorBody = {
|
|
1860
|
+
description: "Error response",
|
|
1861
|
+
content: { "application/json": { schema: errorSchema } }
|
|
1862
|
+
};
|
|
1863
|
+
if (cs.error || cs.errorRef) {
|
|
1864
|
+
responses["400"] = errorBody;
|
|
1865
|
+
responses.default = errorBody;
|
|
1866
|
+
} else {
|
|
1867
|
+
responses.default = {
|
|
1868
|
+
description: "Error response",
|
|
1869
|
+
content: { "application/json": { schema: {} } }
|
|
1870
|
+
};
|
|
1871
|
+
}
|
|
1872
|
+
return responses;
|
|
1873
|
+
}
|
|
1874
|
+
function buildOperation(route, components) {
|
|
1875
|
+
const cs = route.contract.contractSource;
|
|
1876
|
+
const op = {
|
|
1877
|
+
operationId: route.name,
|
|
1878
|
+
parameters: buildParameters(route),
|
|
1879
|
+
responses: buildResponses(cs, components)
|
|
1880
|
+
};
|
|
1881
|
+
const method = route.method.toUpperCase();
|
|
1882
|
+
const hasBody = method !== "GET" && method !== "HEAD" && method !== "DELETE";
|
|
1883
|
+
if (hasBody && (cs.bodySchema || cs.body)) {
|
|
1884
|
+
const bodySchema = positionSchema(cs.bodySchema, cs.body, components);
|
|
1885
|
+
op.requestBody = {
|
|
1886
|
+
required: true,
|
|
1887
|
+
content: { "application/json": { schema: bodySchema } }
|
|
1888
|
+
};
|
|
1889
|
+
}
|
|
1890
|
+
return op;
|
|
1891
|
+
}
|
|
1892
|
+
function buildOpenApiSpec(routes, opts = {}) {
|
|
1893
|
+
const components = {};
|
|
1894
|
+
const paths = {};
|
|
1895
|
+
for (const route of routes) {
|
|
1896
|
+
if (!route.contract) continue;
|
|
1897
|
+
const oaPath = toOpenApiPath(route.path);
|
|
1898
|
+
const method = route.method.toLowerCase();
|
|
1899
|
+
let pathItem = paths[oaPath];
|
|
1900
|
+
if (!pathItem) {
|
|
1901
|
+
pathItem = {};
|
|
1902
|
+
paths[oaPath] = pathItem;
|
|
1903
|
+
}
|
|
1904
|
+
pathItem[method] = buildOperation(route, components);
|
|
1905
|
+
}
|
|
1906
|
+
const info = opts.info ?? {};
|
|
1907
|
+
const doc = {
|
|
1908
|
+
openapi: "3.1.0",
|
|
1909
|
+
info: {
|
|
1910
|
+
title: info.title ?? "NestJS API",
|
|
1911
|
+
version: info.version ?? "1.0.0",
|
|
1912
|
+
...info.description ? { description: info.description } : {}
|
|
1913
|
+
},
|
|
1914
|
+
paths,
|
|
1915
|
+
components: { schemas: components }
|
|
1916
|
+
};
|
|
1917
|
+
return doc;
|
|
1918
|
+
}
|
|
1919
|
+
async function emitOpenApi(routes, outDir, opts = {}) {
|
|
1920
|
+
await (0, import_promises8.mkdir)(outDir, { recursive: true });
|
|
1921
|
+
const doc = buildOpenApiSpec(routes, opts);
|
|
1922
|
+
const fileName = opts.fileName ?? "openapi.json";
|
|
1923
|
+
await (0, import_promises8.writeFile)((0, import_node_path9.join)(outDir, fileName), `${JSON.stringify(doc, null, 2)}
|
|
1924
|
+
`, "utf8");
|
|
1925
|
+
}
|
|
1926
|
+
|
|
1927
|
+
// src/emit/emit-pages.ts
|
|
1928
|
+
var import_promises9 = require("fs/promises");
|
|
1929
|
+
var import_node_path10 = require("path");
|
|
1930
|
+
async function emitPages(pages, outDir, _options = {}) {
|
|
1931
|
+
await (0, import_promises9.mkdir)(outDir, { recursive: true });
|
|
1413
1932
|
const pageNameUnion = pages.length > 0 ? pages.map((p) => JSON.stringify(p.name)).join(" | ") : "never";
|
|
1414
1933
|
const augBody = pages.map((p) => {
|
|
1415
1934
|
const key = needsQuotes(p.name) ? JSON.stringify(p.name) : p.name;
|
|
@@ -1428,7 +1947,7 @@ ${augBody}
|
|
|
1428
1947
|
}
|
|
1429
1948
|
${sharedPropsBlock}}
|
|
1430
1949
|
`;
|
|
1431
|
-
await (0,
|
|
1950
|
+
await (0, import_promises9.writeFile)((0, import_node_path10.join)(outDir, "pages.d.ts"), content, "utf8");
|
|
1432
1951
|
}
|
|
1433
1952
|
function buildSharedPropsBlock(sharedProps) {
|
|
1434
1953
|
if (!sharedProps) return "";
|
|
@@ -1447,7 +1966,7 @@ ${propsBody}
|
|
|
1447
1966
|
`;
|
|
1448
1967
|
}
|
|
1449
1968
|
function buildAugmentationType(page, outDir) {
|
|
1450
|
-
let importPath = (0,
|
|
1969
|
+
let importPath = (0, import_node_path10.relative)(outDir, page.absolutePath).replace(/\.(tsx?|vue|svelte)$/, "");
|
|
1451
1970
|
if (!importPath.startsWith(".")) {
|
|
1452
1971
|
importPath = `./${importPath}`;
|
|
1453
1972
|
}
|
|
@@ -1458,12 +1977,12 @@ function needsQuotes(name) {
|
|
|
1458
1977
|
}
|
|
1459
1978
|
|
|
1460
1979
|
// src/emit/emit-routes.ts
|
|
1461
|
-
var
|
|
1462
|
-
var
|
|
1980
|
+
var import_promises10 = require("fs/promises");
|
|
1981
|
+
var import_node_path11 = require("path");
|
|
1463
1982
|
async function emitRoutes(routes, outDir) {
|
|
1464
|
-
await (0,
|
|
1983
|
+
await (0, import_promises10.mkdir)(outDir, { recursive: true });
|
|
1465
1984
|
const content = buildRoutesFile(routes);
|
|
1466
|
-
await (0,
|
|
1985
|
+
await (0, import_promises10.writeFile)((0, import_node_path11.join)(outDir, "routes.ts"), content, "utf8");
|
|
1467
1986
|
}
|
|
1468
1987
|
function buildRoutesFile(routes) {
|
|
1469
1988
|
if (routes.length === 0) {
|
|
@@ -1611,24 +2130,41 @@ async function generate(config, inputRoutes = []) {
|
|
|
1611
2130
|
});
|
|
1612
2131
|
}
|
|
1613
2132
|
const hasForms = await emitForms(routes, config.codegen.outDir, config.forms, config.validation);
|
|
2133
|
+
if (hasContracts && config.openapi.enabled) {
|
|
2134
|
+
await emitOpenApi(routes, config.codegen.outDir, {
|
|
2135
|
+
fileName: config.openapi.fileName,
|
|
2136
|
+
info: {
|
|
2137
|
+
title: config.openapi.title,
|
|
2138
|
+
version: config.openapi.version,
|
|
2139
|
+
...config.openapi.description ? { description: config.openapi.description } : {}
|
|
2140
|
+
}
|
|
2141
|
+
});
|
|
2142
|
+
}
|
|
2143
|
+
if (hasContracts && config.mocks.enabled) {
|
|
2144
|
+
await emitMocks(routes, config.codegen.outDir, {
|
|
2145
|
+
fileName: config.mocks.fileName,
|
|
2146
|
+
seed: config.mocks.seed,
|
|
2147
|
+
baseUrl: config.mocks.baseUrl
|
|
2148
|
+
});
|
|
2149
|
+
}
|
|
1614
2150
|
await emitIndex(config.codegen.outDir, hasContracts, hasForms);
|
|
1615
2151
|
if (extensions.length > 0) {
|
|
1616
2152
|
const extraFiles = await collectEmittedFiles(extensions, ctx);
|
|
1617
2153
|
for (const file of extraFiles) {
|
|
1618
|
-
const dest = (0,
|
|
1619
|
-
await (0,
|
|
1620
|
-
await (0,
|
|
2154
|
+
const dest = (0, import_node_path12.join)(config.codegen.outDir, file.path);
|
|
2155
|
+
await (0, import_promises11.mkdir)((0, import_node_path12.dirname)(dest), { recursive: true });
|
|
2156
|
+
await (0, import_promises11.writeFile)(dest, file.contents, "utf8");
|
|
1621
2157
|
}
|
|
1622
2158
|
}
|
|
1623
2159
|
}
|
|
1624
2160
|
|
|
1625
2161
|
// src/watch/watcher.ts
|
|
1626
|
-
var
|
|
1627
|
-
var
|
|
2162
|
+
var import_promises14 = require("fs/promises");
|
|
2163
|
+
var import_node_path16 = require("path");
|
|
1628
2164
|
var import_chokidar = __toESM(require("chokidar"), 1);
|
|
1629
2165
|
|
|
1630
2166
|
// src/discovery/contracts-fast.ts
|
|
1631
|
-
var
|
|
2167
|
+
var import_node_path14 = require("path");
|
|
1632
2168
|
var import_fast_glob2 = __toESM(require("fast-glob"), 1);
|
|
1633
2169
|
var import_ts_morph9 = require("ts-morph");
|
|
1634
2170
|
|
|
@@ -1640,7 +2176,7 @@ var import_ts_morph4 = require("ts-morph");
|
|
|
1640
2176
|
|
|
1641
2177
|
// src/discovery/type-ref-resolution.ts
|
|
1642
2178
|
var import_node_fs = require("fs");
|
|
1643
|
-
var
|
|
2179
|
+
var import_node_path13 = require("path");
|
|
1644
2180
|
var import_ts_morph3 = require("ts-morph");
|
|
1645
2181
|
var _EMPTY_CTX = { projectRoot: "", tsconfigPaths: null };
|
|
1646
2182
|
var _ctxByProject = /* @__PURE__ */ new WeakMap();
|
|
@@ -1692,12 +2228,12 @@ function findTypeInFile(name, file) {
|
|
|
1692
2228
|
}
|
|
1693
2229
|
function resolveModuleSpecifier(moduleSpecifier, sourceFile, project) {
|
|
1694
2230
|
if (moduleSpecifier.startsWith(".")) {
|
|
1695
|
-
const dir = (0,
|
|
2231
|
+
const dir = (0, import_node_path13.dirname)(sourceFile.getFilePath());
|
|
1696
2232
|
const noExt = moduleSpecifier.replace(/\.(js|ts)$/, "");
|
|
1697
2233
|
return [
|
|
1698
|
-
(0,
|
|
1699
|
-
(0,
|
|
1700
|
-
(0,
|
|
2234
|
+
(0, import_node_path13.resolve)(dir, `${noExt}.ts`),
|
|
2235
|
+
(0, import_node_path13.resolve)(dir, `${moduleSpecifier}.ts`),
|
|
2236
|
+
(0, import_node_path13.resolve)(dir, moduleSpecifier, "index.ts")
|
|
1701
2237
|
];
|
|
1702
2238
|
}
|
|
1703
2239
|
const ctx = _ctxFor(project);
|
|
@@ -1718,8 +2254,8 @@ function resolveModuleSpecifier(moduleSpecifier, sourceFile, project) {
|
|
|
1718
2254
|
const rest = moduleSpecifier.slice(prefix.length);
|
|
1719
2255
|
const candidates = [];
|
|
1720
2256
|
for (const mapping of mappings) {
|
|
1721
|
-
const resolved = (0,
|
|
1722
|
-
candidates.push(`${resolved}.ts`, (0,
|
|
2257
|
+
const resolved = (0, import_node_path13.resolve)(baseUrl, mapping.replace("*", rest));
|
|
2258
|
+
candidates.push(`${resolved}.ts`, (0, import_node_path13.resolve)(resolved, "index.ts"));
|
|
1723
2259
|
}
|
|
1724
2260
|
dbg(" resolved candidates:", candidates);
|
|
1725
2261
|
return candidates;
|
|
@@ -1800,7 +2336,73 @@ function followModuleForType(name, moduleSpecifier, fromFile, project, seen) {
|
|
|
1800
2336
|
}
|
|
1801
2337
|
return null;
|
|
1802
2338
|
}
|
|
2339
|
+
function resolveImportedVariable(name, sourceFile, project) {
|
|
2340
|
+
const local = sourceFile.getVariableDeclaration(name);
|
|
2341
|
+
if (local) return { decl: local, file: sourceFile };
|
|
2342
|
+
return resolveVariableViaImports(name, sourceFile, project, /* @__PURE__ */ new Set());
|
|
2343
|
+
}
|
|
2344
|
+
function resolveVariableViaImports(name, sourceFile, project, seen) {
|
|
2345
|
+
for (const importDecl of sourceFile.getImportDeclarations()) {
|
|
2346
|
+
const namedImport = importDecl.getNamedImports().find((n) => (n.getAliasNode()?.getText() ?? n.getName()) === name);
|
|
2347
|
+
if (!namedImport) continue;
|
|
2348
|
+
const sourceName = namedImport.getName();
|
|
2349
|
+
const moduleSpecifier = importDecl.getModuleSpecifierValue();
|
|
2350
|
+
const found = followModuleForVariable(sourceName, moduleSpecifier, sourceFile, project, seen);
|
|
2351
|
+
if (found) return found;
|
|
2352
|
+
}
|
|
2353
|
+
return null;
|
|
2354
|
+
}
|
|
2355
|
+
function followModuleForVariable(name, moduleSpecifier, fromFile, project, seen) {
|
|
2356
|
+
const candidates = resolveModuleSpecifier(moduleSpecifier, fromFile, project);
|
|
2357
|
+
for (const candidate of candidates) {
|
|
2358
|
+
let importedFile = project.getSourceFile(candidate);
|
|
2359
|
+
if (!importedFile) {
|
|
2360
|
+
try {
|
|
2361
|
+
importedFile = project.addSourceFileAtPath(candidate);
|
|
2362
|
+
} catch {
|
|
2363
|
+
continue;
|
|
2364
|
+
}
|
|
2365
|
+
}
|
|
2366
|
+
const found = resolveVariableInFile(name, importedFile, project, seen);
|
|
2367
|
+
if (found) return found;
|
|
2368
|
+
}
|
|
2369
|
+
return null;
|
|
2370
|
+
}
|
|
2371
|
+
function resolveVariableInFile(name, file, project, seen) {
|
|
2372
|
+
const filePath = file.getFilePath();
|
|
2373
|
+
if (seen.has(filePath)) return null;
|
|
2374
|
+
seen.add(filePath);
|
|
2375
|
+
const local = file.getVariableDeclaration(name);
|
|
2376
|
+
if (local) return { decl: local, file };
|
|
2377
|
+
for (const exportDecl of file.getExportDeclarations()) {
|
|
2378
|
+
const moduleSpecifier = exportDecl.getModuleSpecifierValue();
|
|
2379
|
+
const namedExports = exportDecl.getNamedExports();
|
|
2380
|
+
if (moduleSpecifier) {
|
|
2381
|
+
const hasStar = namedExports.length === 0;
|
|
2382
|
+
const reExport2 = namedExports.find(
|
|
2383
|
+
(n) => (n.getAliasNode()?.getText() ?? n.getName()) === name
|
|
2384
|
+
);
|
|
2385
|
+
if (!hasStar && !reExport2) continue;
|
|
2386
|
+
const sourceName2 = hasStar ? name : reExport2?.getName() ?? name;
|
|
2387
|
+
const found = followModuleForVariable(sourceName2, moduleSpecifier, file, project, seen);
|
|
2388
|
+
if (found) return found;
|
|
2389
|
+
continue;
|
|
2390
|
+
}
|
|
2391
|
+
const reExport = namedExports.find(
|
|
2392
|
+
(n) => (n.getAliasNode()?.getText() ?? n.getName()) === name
|
|
2393
|
+
);
|
|
2394
|
+
if (!reExport) continue;
|
|
2395
|
+
const sourceName = reExport.getName();
|
|
2396
|
+
const viaImports = resolveVariableViaImports(sourceName, file, project, seen);
|
|
2397
|
+
if (viaImports) return viaImports;
|
|
2398
|
+
}
|
|
2399
|
+
return null;
|
|
2400
|
+
}
|
|
1803
2401
|
var _findTypeCache = /* @__PURE__ */ new WeakMap();
|
|
2402
|
+
function clearTypeResolutionCaches(project) {
|
|
2403
|
+
_findTypeCache.delete(project);
|
|
2404
|
+
_resolveNamedRefCache.delete(project);
|
|
2405
|
+
}
|
|
1804
2406
|
function findType(name, sourceFile, project) {
|
|
1805
2407
|
let byKey = _findTypeCache.get(project);
|
|
1806
2408
|
if (byKey === void 0) {
|
|
@@ -1941,9 +2543,11 @@ function extractSchemaFromDto(classDecl, sourceFile, project) {
|
|
|
1941
2543
|
warnings: [],
|
|
1942
2544
|
warnedDecorators: /* @__PURE__ */ new Set(),
|
|
1943
2545
|
emittedClasses: /* @__PURE__ */ new Map(),
|
|
2546
|
+
usedSchemaNames: /* @__PURE__ */ new Set(),
|
|
1944
2547
|
visiting: /* @__PURE__ */ new Set(),
|
|
1945
2548
|
recursiveSchemas: /* @__PURE__ */ new Set(),
|
|
1946
|
-
depth: 0
|
|
2549
|
+
depth: 0,
|
|
2550
|
+
typeBindings: /* @__PURE__ */ new Map()
|
|
1947
2551
|
};
|
|
1948
2552
|
const root = buildObject(classDecl, sourceFile, ctx);
|
|
1949
2553
|
return { root, named: ctx.named, warnings: ctx.warnings, recursive: ctx.recursiveSchemas };
|
|
@@ -1967,11 +2571,34 @@ function buildProperty(prop, classFile, ctx) {
|
|
|
1967
2571
|
const typeNode = prop.getTypeNode();
|
|
1968
2572
|
const typeText = typeNode?.getText() ?? "unknown";
|
|
1969
2573
|
const isArrayType = !!typeNode && import_ts_morph4.Node.isArrayTypeNode(typeNode);
|
|
2574
|
+
const discriminator = resolveDiscriminator(dec("Type"));
|
|
2575
|
+
if (discriminator) {
|
|
2576
|
+
const options = discriminator.subTypes.map(
|
|
2577
|
+
(name) => buildNestedReference(name, classFile, ctx)
|
|
2578
|
+
);
|
|
2579
|
+
const unionNode = {
|
|
2580
|
+
kind: "union",
|
|
2581
|
+
options,
|
|
2582
|
+
discriminator: discriminator.property
|
|
2583
|
+
};
|
|
2584
|
+
const wrapArray = has("IsArray") || isArrayType;
|
|
2585
|
+
const node2 = wrapArray ? { kind: "array", element: unionNode } : unionNode;
|
|
2586
|
+
return applyPresence(node2, decorators);
|
|
2587
|
+
}
|
|
2588
|
+
const propTypeParam = singularClassName(typeText);
|
|
2589
|
+
if (propTypeParam && ctx.typeBindings.has(propTypeParam)) {
|
|
2590
|
+
const bound = ctx.typeBindings.get(propTypeParam);
|
|
2591
|
+
const childNode = buildNestedReference(bound, classFile, ctx);
|
|
2592
|
+
const wrapArray = has("IsArray") || isArrayType;
|
|
2593
|
+
const node2 = wrapArray ? { kind: "array", element: childNode } : childNode;
|
|
2594
|
+
return applyPresence(node2, decorators);
|
|
2595
|
+
}
|
|
1970
2596
|
const typeRefName = resolveTypeFactoryName(dec("Type"));
|
|
1971
2597
|
if (has("ValidateNested") || typeRefName) {
|
|
2598
|
+
const typeArgs = genericTypeArgNames(typeNode);
|
|
1972
2599
|
const childName = typeRefName ?? singularClassName(typeText);
|
|
1973
2600
|
if (childName) {
|
|
1974
|
-
const childNode = buildNestedReference(childName, classFile, ctx);
|
|
2601
|
+
const childNode = buildNestedReference(childName, classFile, ctx, typeArgs);
|
|
1975
2602
|
const wrapArray = has("IsArray") || isArrayType;
|
|
1976
2603
|
const node2 = wrapArray ? { kind: "array", element: childNode } : childNode;
|
|
1977
2604
|
return applyPresence(node2, decorators);
|
|
@@ -2096,10 +2723,13 @@ function baseFromType(typeText, isArrayType) {
|
|
|
2096
2723
|
return { kind: "unknown" };
|
|
2097
2724
|
}
|
|
2098
2725
|
}
|
|
2099
|
-
function buildNestedReference(className, fromFile, ctx) {
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2726
|
+
function buildNestedReference(className, fromFile, ctx, typeArgs = []) {
|
|
2727
|
+
const cacheKey = typeArgs.length > 0 ? `${className}<${typeArgs.join(",")}>` : className;
|
|
2728
|
+
const schemaBase = typeArgs.length > 0 ? `${className}Of${typeArgs.join("")}` : className;
|
|
2729
|
+
if (ctx.visiting.has(cacheKey)) {
|
|
2730
|
+
const reserved = ctx.emittedClasses.get(cacheKey) ?? aliasFor(schemaBase, ctx);
|
|
2731
|
+
ctx.emittedClasses.set(cacheKey, reserved);
|
|
2732
|
+
ctx.usedSchemaNames.add(reserved);
|
|
2103
2733
|
ctx.recursiveSchemas.add(reserved);
|
|
2104
2734
|
if (!ctx.warnedDecorators.has(`recursive:${reserved}`)) {
|
|
2105
2735
|
ctx.warnedDecorators.add(`recursive:${reserved}`);
|
|
@@ -2118,29 +2748,37 @@ function buildNestedReference(className, fromFile, ctx) {
|
|
|
2118
2748
|
}
|
|
2119
2749
|
return { kind: "unknown", note: "nesting too deep \u2014 not expanded" };
|
|
2120
2750
|
}
|
|
2121
|
-
const existing = ctx.emittedClasses.get(
|
|
2751
|
+
const existing = ctx.emittedClasses.get(cacheKey);
|
|
2122
2752
|
if (existing) return { kind: "ref", name: existing };
|
|
2123
|
-
const schemaName = aliasFor(
|
|
2753
|
+
const schemaName = aliasFor(schemaBase, ctx);
|
|
2124
2754
|
const resolved = findType(className, fromFile, ctx.project);
|
|
2125
2755
|
if (!resolved || resolved.kind !== "class") {
|
|
2126
2756
|
return { kind: "object", fields: [], passthrough: true };
|
|
2127
2757
|
}
|
|
2128
|
-
|
|
2129
|
-
|
|
2758
|
+
const params = resolved.decl.getTypeParameters().map((p) => p.getName());
|
|
2759
|
+
const newBindings = [];
|
|
2760
|
+
params.forEach((param, i) => {
|
|
2761
|
+
const arg = typeArgs[i];
|
|
2762
|
+
if (arg) newBindings.push([param, arg]);
|
|
2763
|
+
});
|
|
2764
|
+
for (const [k, v] of newBindings) ctx.typeBindings.set(k, v);
|
|
2765
|
+
ctx.emittedClasses.set(cacheKey, schemaName);
|
|
2766
|
+
ctx.usedSchemaNames.add(schemaName);
|
|
2767
|
+
ctx.visiting.add(cacheKey);
|
|
2130
2768
|
ctx.depth += 1;
|
|
2131
2769
|
const childNode = buildObject(resolved.decl, resolved.file, ctx);
|
|
2132
2770
|
ctx.depth -= 1;
|
|
2133
|
-
ctx.visiting.delete(
|
|
2771
|
+
ctx.visiting.delete(cacheKey);
|
|
2772
|
+
for (const [k] of newBindings) ctx.typeBindings.delete(k);
|
|
2134
2773
|
ctx.named.set(schemaName, childNode);
|
|
2774
|
+
ctx.usedSchemaNames.add(schemaName);
|
|
2135
2775
|
return { kind: "ref", name: schemaName };
|
|
2136
2776
|
}
|
|
2137
2777
|
function aliasFor(className, ctx) {
|
|
2138
2778
|
const baseName = `${className}Schema`;
|
|
2139
2779
|
let candidate = baseName;
|
|
2140
2780
|
let i = 1;
|
|
2141
|
-
|
|
2142
|
-
for (const v of ctx.emittedClasses.values()) used.add(v);
|
|
2143
|
-
while (used.has(candidate)) {
|
|
2781
|
+
while (ctx.usedSchemaNames.has(candidate)) {
|
|
2144
2782
|
candidate = `${baseName}_${i}`;
|
|
2145
2783
|
i += 1;
|
|
2146
2784
|
}
|
|
@@ -2177,6 +2815,39 @@ function messageRaw(decorator) {
|
|
|
2177
2815
|
}
|
|
2178
2816
|
return void 0;
|
|
2179
2817
|
}
|
|
2818
|
+
function resolveDiscriminator(decorator) {
|
|
2819
|
+
const optsArg = decorator?.getArguments()[1];
|
|
2820
|
+
if (!optsArg || !import_ts_morph4.Node.isObjectLiteralExpression(optsArg)) return null;
|
|
2821
|
+
let discProp;
|
|
2822
|
+
for (const prop of optsArg.getProperties()) {
|
|
2823
|
+
if (import_ts_morph4.Node.isPropertyAssignment(prop) && prop.getName() === "discriminator") {
|
|
2824
|
+
discProp = prop.getInitializer();
|
|
2825
|
+
}
|
|
2826
|
+
}
|
|
2827
|
+
if (!discProp || !import_ts_morph4.Node.isObjectLiteralExpression(discProp)) return null;
|
|
2828
|
+
let property = null;
|
|
2829
|
+
const subTypes = [];
|
|
2830
|
+
for (const prop of discProp.getProperties()) {
|
|
2831
|
+
if (!import_ts_morph4.Node.isPropertyAssignment(prop)) continue;
|
|
2832
|
+
const name = prop.getName();
|
|
2833
|
+
const init = prop.getInitializer();
|
|
2834
|
+
if (!init) continue;
|
|
2835
|
+
if (name === "property" && import_ts_morph4.Node.isStringLiteral(init)) {
|
|
2836
|
+
property = init.getLiteralValue();
|
|
2837
|
+
} else if (name === "subTypes" && import_ts_morph4.Node.isArrayLiteralExpression(init)) {
|
|
2838
|
+
for (const el of init.getElements()) {
|
|
2839
|
+
if (!import_ts_morph4.Node.isObjectLiteralExpression(el)) continue;
|
|
2840
|
+
for (const p of el.getProperties()) {
|
|
2841
|
+
if (!import_ts_morph4.Node.isPropertyAssignment(p) || p.getName() !== "name") continue;
|
|
2842
|
+
const nameInit = p.getInitializer();
|
|
2843
|
+
if (nameInit && import_ts_morph4.Node.isIdentifier(nameInit)) subTypes.push(nameInit.getText());
|
|
2844
|
+
}
|
|
2845
|
+
}
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
if (!property || subTypes.length === 0) return null;
|
|
2849
|
+
return { property, subTypes };
|
|
2850
|
+
}
|
|
2180
2851
|
function resolveTypeFactoryName(decorator) {
|
|
2181
2852
|
const arg = firstArg(decorator);
|
|
2182
2853
|
if (!arg) return null;
|
|
@@ -2190,6 +2861,17 @@ function singularClassName(typeText) {
|
|
|
2190
2861
|
const inner = typeText.endsWith("[]") ? typeText.slice(0, -2).trim() : typeText;
|
|
2191
2862
|
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(inner) ? inner : null;
|
|
2192
2863
|
}
|
|
2864
|
+
function genericTypeArgNames(typeNode) {
|
|
2865
|
+
if (!typeNode || !import_ts_morph4.Node.isTypeReference(typeNode)) return [];
|
|
2866
|
+
const names = [];
|
|
2867
|
+
for (const arg of typeNode.getTypeArguments()) {
|
|
2868
|
+
if (!import_ts_morph4.Node.isTypeReference(arg)) return [];
|
|
2869
|
+
const tn = arg.getTypeName();
|
|
2870
|
+
if (!import_ts_morph4.Node.isIdentifier(tn)) return [];
|
|
2871
|
+
names.push(tn.getText());
|
|
2872
|
+
}
|
|
2873
|
+
return names;
|
|
2874
|
+
}
|
|
2193
2875
|
function enumSchemaFromDecorator(decorator, classFile, ctx) {
|
|
2194
2876
|
const arg = firstArg(decorator);
|
|
2195
2877
|
if (!arg) return null;
|
|
@@ -2244,6 +2926,9 @@ var import_ts_morph5 = require("ts-morph");
|
|
|
2244
2926
|
|
|
2245
2927
|
// src/discovery/enum-resolution.ts
|
|
2246
2928
|
var _enumCache = /* @__PURE__ */ new WeakMap();
|
|
2929
|
+
function clearEnumCache(project) {
|
|
2930
|
+
_enumCache.delete(project);
|
|
2931
|
+
}
|
|
2247
2932
|
function resolveEnumValues(name, sourceFile, project) {
|
|
2248
2933
|
let byKey = _enumCache.get(project);
|
|
2249
2934
|
if (byKey === void 0) {
|
|
@@ -2712,24 +3397,26 @@ var PASSTHROUGH_UTILITY = /* @__PURE__ */ new Set([
|
|
|
2712
3397
|
"Map",
|
|
2713
3398
|
"Set"
|
|
2714
3399
|
]);
|
|
2715
|
-
function resolveTypeNodeToString(typeNode, sourceFile, project, depth) {
|
|
3400
|
+
function resolveTypeNodeToString(typeNode, sourceFile, project, depth, subst = /* @__PURE__ */ new Map()) {
|
|
2716
3401
|
if (depth <= 0) return "unknown";
|
|
2717
3402
|
if (import_ts_morph7.Node.isArrayTypeNode(typeNode)) {
|
|
2718
3403
|
const elementType = typeNode.getElementTypeNode();
|
|
2719
|
-
return `Array<${resolveTypeNodeToString(elementType, sourceFile, project, depth)}>`;
|
|
3404
|
+
return `Array<${resolveTypeNodeToString(elementType, sourceFile, project, depth, subst)}>`;
|
|
2720
3405
|
}
|
|
2721
3406
|
if (import_ts_morph7.Node.isUnionTypeNode(typeNode)) {
|
|
2722
|
-
return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth)).join(" | ");
|
|
3407
|
+
return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth, subst)).join(" | ");
|
|
2723
3408
|
}
|
|
2724
3409
|
if (import_ts_morph7.Node.isIntersectionTypeNode(typeNode)) {
|
|
2725
|
-
return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth)).join(" & ");
|
|
3410
|
+
return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth, subst)).join(" & ");
|
|
2726
3411
|
}
|
|
2727
3412
|
if (import_ts_morph7.Node.isParenthesizedTypeNode(typeNode)) {
|
|
2728
|
-
return `(${resolveTypeNodeToString(typeNode.getTypeNode(), sourceFile, project, depth)})`;
|
|
3413
|
+
return `(${resolveTypeNodeToString(typeNode.getTypeNode(), sourceFile, project, depth, subst)})`;
|
|
2729
3414
|
}
|
|
2730
3415
|
if (import_ts_morph7.Node.isTypeReference(typeNode)) {
|
|
2731
3416
|
const typeName = typeNode.getTypeName();
|
|
2732
3417
|
const name = import_ts_morph7.Node.isIdentifier(typeName) ? typeName.getText() : typeNode.getText();
|
|
3418
|
+
const bound = subst.get(name);
|
|
3419
|
+
if (bound !== void 0) return bound;
|
|
2733
3420
|
if (name === "string" || name === "number" || name === "boolean") return name;
|
|
2734
3421
|
if (name === "Date") return "string";
|
|
2735
3422
|
if (name === "unknown" || name === "any" || name === "void") return "unknown";
|
|
@@ -2737,14 +3424,15 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth) {
|
|
|
2737
3424
|
return "unknown";
|
|
2738
3425
|
const wrapperMode = WRAPPER_TYPES[name];
|
|
2739
3426
|
if (wrapperMode) {
|
|
2740
|
-
return unwrapFirstTypeArg(typeNode, sourceFile, project, depth, wrapperMode);
|
|
3427
|
+
return unwrapFirstTypeArg(typeNode, sourceFile, project, depth, wrapperMode, subst);
|
|
2741
3428
|
}
|
|
2742
3429
|
if (PASSTHROUGH_UTILITY.has(name)) {
|
|
2743
3430
|
return typeNode.getText();
|
|
2744
3431
|
}
|
|
2745
3432
|
const resolved = findType(name, sourceFile, project);
|
|
2746
3433
|
if (resolved) {
|
|
2747
|
-
|
|
3434
|
+
const childSubst = buildSubst(resolved, typeNode, sourceFile, project, depth, subst);
|
|
3435
|
+
return expandTypeDecl(resolved, project, depth - 1, childSubst);
|
|
2748
3436
|
}
|
|
2749
3437
|
dbg("unresolvable type:", name, "in", sourceFile.getFilePath());
|
|
2750
3438
|
return "unknown";
|
|
@@ -2757,32 +3445,45 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth) {
|
|
|
2757
3445
|
if (kind === import_ts_morph7.SyntaxKind.AnyKeyword) return "unknown";
|
|
2758
3446
|
return typeNode.getText();
|
|
2759
3447
|
}
|
|
2760
|
-
function unwrapFirstTypeArg(typeNode, sourceFile, project, depth, mode) {
|
|
3448
|
+
function unwrapFirstTypeArg(typeNode, sourceFile, project, depth, mode, subst = /* @__PURE__ */ new Map()) {
|
|
2761
3449
|
const typeArgs = typeNode.getTypeArguments();
|
|
2762
3450
|
const firstTypeArg = typeArgs[0];
|
|
2763
3451
|
if (typeArgs.length > 0 && firstTypeArg !== void 0) {
|
|
2764
|
-
const inner = resolveTypeNodeToString(firstTypeArg, sourceFile, project, depth);
|
|
3452
|
+
const inner = resolveTypeNodeToString(firstTypeArg, sourceFile, project, depth, subst);
|
|
2765
3453
|
return mode === "arrayOf" ? `Array<${inner}>` : inner;
|
|
2766
3454
|
}
|
|
2767
3455
|
return mode === "arrayOf" ? "Array<unknown>" : "unknown";
|
|
2768
3456
|
}
|
|
2769
|
-
function
|
|
3457
|
+
function buildSubst(result, typeNode, sourceFile, project, depth, parentSubst) {
|
|
3458
|
+
if (result.kind !== "class" && result.kind !== "interface") return /* @__PURE__ */ new Map();
|
|
3459
|
+
const params = result.decl.getTypeParameters().map((p) => p.getName());
|
|
3460
|
+
if (params.length === 0) return /* @__PURE__ */ new Map();
|
|
3461
|
+
const args = typeNode.getTypeArguments();
|
|
3462
|
+
const subst = /* @__PURE__ */ new Map();
|
|
3463
|
+
params.forEach((param, i) => {
|
|
3464
|
+
const arg = args[i];
|
|
3465
|
+
if (arg)
|
|
3466
|
+
subst.set(param, resolveTypeNodeToString(arg, sourceFile, project, depth, parentSubst));
|
|
3467
|
+
});
|
|
3468
|
+
return subst;
|
|
3469
|
+
}
|
|
3470
|
+
function expandTypeDecl(result, project, depth, subst = /* @__PURE__ */ new Map()) {
|
|
2770
3471
|
if (depth < 0) return "unknown";
|
|
2771
3472
|
switch (result.kind) {
|
|
2772
3473
|
case "class":
|
|
2773
|
-
return resolvePropertied(result.decl, result.file, project, depth);
|
|
3474
|
+
return resolvePropertied(result.decl, result.file, project, depth, subst);
|
|
2774
3475
|
case "interface":
|
|
2775
|
-
return resolvePropertied(result.decl, result.file, project, depth);
|
|
3476
|
+
return resolvePropertied(result.decl, result.file, project, depth, subst);
|
|
2776
3477
|
case "typeAlias":
|
|
2777
3478
|
if (result.typeNode) {
|
|
2778
|
-
return resolveTypeNodeToString(result.typeNode, result.file, project, depth);
|
|
3479
|
+
return resolveTypeNodeToString(result.typeNode, result.file, project, depth, subst);
|
|
2779
3480
|
}
|
|
2780
3481
|
return result.text;
|
|
2781
3482
|
case "enum":
|
|
2782
3483
|
return result.members.join(" | ");
|
|
2783
3484
|
}
|
|
2784
3485
|
}
|
|
2785
|
-
function resolvePropertied(decl, sourceFile, project, depth) {
|
|
3486
|
+
function resolvePropertied(decl, sourceFile, project, depth, subst = /* @__PURE__ */ new Map()) {
|
|
2786
3487
|
if (depth < 0) return "unknown";
|
|
2787
3488
|
const lines = [];
|
|
2788
3489
|
for (const prop of decl.getProperties()) {
|
|
@@ -2791,7 +3492,7 @@ function resolvePropertied(decl, sourceFile, project, depth) {
|
|
|
2791
3492
|
const propTypeNode = prop.getTypeNode();
|
|
2792
3493
|
let propType = "unknown";
|
|
2793
3494
|
if (propTypeNode) {
|
|
2794
|
-
propType = resolveTypeNodeToString(propTypeNode, sourceFile, project, depth);
|
|
3495
|
+
propType = resolveTypeNodeToString(propTypeNode, sourceFile, project, depth, subst);
|
|
2795
3496
|
}
|
|
2796
3497
|
lines.push(`${propName}${isOptional ? "?" : ""}: ${propType}`);
|
|
2797
3498
|
}
|
|
@@ -2840,7 +3541,7 @@ function extractParamsType(method, sourceFile, project) {
|
|
|
2840
3541
|
return entries.length > 0 ? `{ ${entries.join("; ")} }` : null;
|
|
2841
3542
|
}
|
|
2842
3543
|
function extractResponseType(method, sourceFile, project) {
|
|
2843
|
-
const apiResponseDecorator = method.
|
|
3544
|
+
const apiResponseDecorator = method.getDecorators().find((d) => d.getName() === "ApiResponse" && (apiResponseStatus(d) ?? 0) < 400);
|
|
2844
3545
|
if (apiResponseDecorator) {
|
|
2845
3546
|
const args = apiResponseDecorator.getArguments();
|
|
2846
3547
|
const optsArg = args[0];
|
|
@@ -2869,6 +3570,59 @@ function extractResponseType(method, sourceFile, project) {
|
|
|
2869
3570
|
}
|
|
2870
3571
|
return "unknown";
|
|
2871
3572
|
}
|
|
3573
|
+
function apiResponseStatus(decorator) {
|
|
3574
|
+
const optsArg = decorator.getArguments()[0];
|
|
3575
|
+
if (!optsArg || !import_ts_morph7.Node.isObjectLiteralExpression(optsArg)) return null;
|
|
3576
|
+
for (const prop of optsArg.getProperties()) {
|
|
3577
|
+
if (!import_ts_morph7.Node.isPropertyAssignment(prop)) continue;
|
|
3578
|
+
if (prop.getName() !== "status") continue;
|
|
3579
|
+
const val = prop.getInitializer();
|
|
3580
|
+
if (val && import_ts_morph7.Node.isNumericLiteral(val)) return Number(val.getLiteralValue());
|
|
3581
|
+
}
|
|
3582
|
+
return null;
|
|
3583
|
+
}
|
|
3584
|
+
function apiResponseTypeNode(decorator) {
|
|
3585
|
+
const optsArg = decorator.getArguments()[0];
|
|
3586
|
+
if (!optsArg || !import_ts_morph7.Node.isObjectLiteralExpression(optsArg)) return null;
|
|
3587
|
+
for (const prop of optsArg.getProperties()) {
|
|
3588
|
+
if (!import_ts_morph7.Node.isPropertyAssignment(prop)) continue;
|
|
3589
|
+
if (prop.getName() !== "type") continue;
|
|
3590
|
+
const val = prop.getInitializer();
|
|
3591
|
+
if (!val) return null;
|
|
3592
|
+
if (import_ts_morph7.Node.isArrayLiteralExpression(val)) {
|
|
3593
|
+
const first = val.getElements()[0];
|
|
3594
|
+
return first ? { node: first, isArray: true } : null;
|
|
3595
|
+
}
|
|
3596
|
+
return { node: val, isArray: false };
|
|
3597
|
+
}
|
|
3598
|
+
return null;
|
|
3599
|
+
}
|
|
3600
|
+
function extractErrorType(method, sourceFile, project) {
|
|
3601
|
+
for (const decorator of method.getDecorators()) {
|
|
3602
|
+
if (decorator.getName() !== "ApiResponse") continue;
|
|
3603
|
+
const status = apiResponseStatus(decorator);
|
|
3604
|
+
if (status === null || status < 400) continue;
|
|
3605
|
+
const typeInfo = apiResponseTypeNode(decorator);
|
|
3606
|
+
if (!typeInfo) continue;
|
|
3607
|
+
const inner = resolveIdentifierToClassType(typeInfo.node, sourceFile, project, 3);
|
|
3608
|
+
const type = typeInfo.isArray ? `Array<${inner}>` : inner;
|
|
3609
|
+
let ref = null;
|
|
3610
|
+
if (import_ts_morph7.Node.isIdentifier(typeInfo.node)) {
|
|
3611
|
+
const name = typeInfo.node.getText();
|
|
3612
|
+
const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
|
|
3613
|
+
if (localDecl?.isExported()) {
|
|
3614
|
+
ref = { name, filePath: sourceFile.getFilePath(), isArray: typeInfo.isArray };
|
|
3615
|
+
} else {
|
|
3616
|
+
const resolved = resolveImportedType(name, sourceFile, project);
|
|
3617
|
+
if (resolved && (resolved.kind === "class" || resolved.kind === "interface") && resolved.decl.isExported()) {
|
|
3618
|
+
ref = { name, filePath: resolved.file.getFilePath(), isArray: typeInfo.isArray };
|
|
3619
|
+
}
|
|
3620
|
+
}
|
|
3621
|
+
}
|
|
3622
|
+
return { type, ref };
|
|
3623
|
+
}
|
|
3624
|
+
return null;
|
|
3625
|
+
}
|
|
2872
3626
|
function resolveIdentifierToClassType(node, sourceFile, project, depth) {
|
|
2873
3627
|
if (!import_ts_morph7.Node.isIdentifier(node)) return "unknown";
|
|
2874
3628
|
const name = node.getText();
|
|
@@ -2884,17 +3638,52 @@ function resolveBodyQueryResponseRef(typeNode, sourceFile, project) {
|
|
|
2884
3638
|
unwrapContainers: true
|
|
2885
3639
|
});
|
|
2886
3640
|
}
|
|
3641
|
+
var STREAM_CONTAINERS = /* @__PURE__ */ new Set(["Observable", "AsyncIterable", "AsyncIterableIterator"]);
|
|
3642
|
+
var STREAM_CONTAINERS_GENERATOR = /* @__PURE__ */ new Set(["AsyncGenerator"]);
|
|
3643
|
+
var STREAM_ENVELOPES = /* @__PURE__ */ new Set(["MessageEvent", "MessageEventLike"]);
|
|
3644
|
+
function detectStreamElement(method) {
|
|
3645
|
+
const hasSse = method.getDecorators().some((d) => d.getName() === "Sse");
|
|
3646
|
+
let node = method.getReturnTypeNode();
|
|
3647
|
+
node = unwrapNamedContainer(node, /* @__PURE__ */ new Set(["Promise"]));
|
|
3648
|
+
const containerEl = streamContainerElement(node);
|
|
3649
|
+
if (containerEl) {
|
|
3650
|
+
return unwrapNamedContainer(containerEl, STREAM_ENVELOPES) ?? containerEl;
|
|
3651
|
+
}
|
|
3652
|
+
if (hasSse) return node ?? null;
|
|
3653
|
+
return null;
|
|
3654
|
+
}
|
|
3655
|
+
function streamContainerElement(node) {
|
|
3656
|
+
if (!node || !import_ts_morph7.Node.isTypeReference(node)) return null;
|
|
3657
|
+
const typeName = node.getTypeName();
|
|
3658
|
+
const name = import_ts_morph7.Node.isIdentifier(typeName) ? typeName.getText() : "";
|
|
3659
|
+
if (STREAM_CONTAINERS.has(name) || STREAM_CONTAINERS_GENERATOR.has(name)) {
|
|
3660
|
+
return node.getTypeArguments()[0] ?? null;
|
|
3661
|
+
}
|
|
3662
|
+
return null;
|
|
3663
|
+
}
|
|
3664
|
+
function unwrapNamedContainer(node, names) {
|
|
3665
|
+
if (!node || !import_ts_morph7.Node.isTypeReference(node)) return node;
|
|
3666
|
+
const typeName = node.getTypeName();
|
|
3667
|
+
const name = import_ts_morph7.Node.isIdentifier(typeName) ? typeName.getText() : "";
|
|
3668
|
+
if (names.has(name)) {
|
|
3669
|
+
return node.getTypeArguments()[0] ?? node;
|
|
3670
|
+
}
|
|
3671
|
+
return node;
|
|
3672
|
+
}
|
|
2887
3673
|
function extractDtoContract(method, sourceFile, project) {
|
|
2888
3674
|
let body = extractBodyType(method, sourceFile, project);
|
|
2889
3675
|
const filterInfo = extractApplyFilterInfo(method, sourceFile, project);
|
|
2890
3676
|
const query = extractQueryType(method, sourceFile, project);
|
|
3677
|
+
const streamElement = detectStreamElement(method);
|
|
3678
|
+
const isStream = streamElement !== null;
|
|
2891
3679
|
if (filterInfo && filterInfo.source === "body") {
|
|
2892
3680
|
const bodyType = "import('@dudousxd/nestjs-filter-client').FilterQueryResult";
|
|
2893
3681
|
body = body ?? bodyType;
|
|
2894
3682
|
}
|
|
2895
3683
|
const paramsType = extractParamsType(method, sourceFile, project);
|
|
2896
|
-
const response = extractResponseType(method, sourceFile, project);
|
|
2897
|
-
|
|
3684
|
+
const response = isStream ? resolveTypeNodeToString(streamElement, sourceFile, project, 3) : extractResponseType(method, sourceFile, project);
|
|
3685
|
+
const errorInfo = extractErrorType(method, sourceFile, project);
|
|
3686
|
+
if (body === null && query === null && paramsType === null && response === "unknown" && errorInfo === null && filterInfo === null && !isStream) {
|
|
2898
3687
|
return null;
|
|
2899
3688
|
}
|
|
2900
3689
|
let bodyRef = null;
|
|
@@ -2908,12 +3697,12 @@ function extractDtoContract(method, sourceFile, project) {
|
|
|
2908
3697
|
queryRef = resolveBodyQueryResponseRef(param.getTypeNode(), sourceFile, project);
|
|
2909
3698
|
}
|
|
2910
3699
|
}
|
|
2911
|
-
const returnTypeNode = method.getReturnTypeNode();
|
|
3700
|
+
const returnTypeNode = isStream ? streamElement : method.getReturnTypeNode();
|
|
2912
3701
|
if (returnTypeNode) {
|
|
2913
3702
|
responseRef = resolveBodyQueryResponseRef(returnTypeNode, sourceFile, project);
|
|
2914
3703
|
}
|
|
2915
|
-
if (!responseRef) {
|
|
2916
|
-
const apiResp = method.
|
|
3704
|
+
if (!responseRef && !isStream) {
|
|
3705
|
+
const apiResp = method.getDecorators().find((d) => d.getName() === "ApiResponse" && (apiResponseStatus(d) ?? 0) < 400);
|
|
2917
3706
|
if (apiResp) {
|
|
2918
3707
|
const args = apiResp.getArguments();
|
|
2919
3708
|
const optsArg = args[0];
|
|
@@ -2955,16 +3744,19 @@ function extractDtoContract(method, sourceFile, project) {
|
|
|
2955
3744
|
query,
|
|
2956
3745
|
body,
|
|
2957
3746
|
response,
|
|
3747
|
+
error: errorInfo?.type ?? null,
|
|
2958
3748
|
params: paramsType,
|
|
2959
3749
|
queryRef,
|
|
2960
3750
|
bodyRef,
|
|
2961
3751
|
responseRef,
|
|
3752
|
+
errorRef: errorInfo?.ref ?? null,
|
|
2962
3753
|
filterFields: filterInfo?.fieldNames ?? null,
|
|
2963
3754
|
filterFieldTypes: filterInfo?.fieldTypes ?? null,
|
|
2964
3755
|
filterSource: filterInfo?.source ?? null,
|
|
2965
3756
|
formWarnings,
|
|
2966
3757
|
bodySchema,
|
|
2967
|
-
querySchema
|
|
3758
|
+
querySchema,
|
|
3759
|
+
stream: isStream
|
|
2968
3760
|
};
|
|
2969
3761
|
}
|
|
2970
3762
|
function resolveParamClass(method, decoratorName, sourceFile, project) {
|
|
@@ -3082,6 +3874,7 @@ function parseDefineContractCall(callExpr) {
|
|
|
3082
3874
|
let query = null;
|
|
3083
3875
|
let body = null;
|
|
3084
3876
|
let response = "unknown";
|
|
3877
|
+
let error = null;
|
|
3085
3878
|
let bodyZodText = null;
|
|
3086
3879
|
let queryZodText = null;
|
|
3087
3880
|
for (const prop of optsArg.getProperties()) {
|
|
@@ -3097,25 +3890,38 @@ function parseDefineContractCall(callExpr) {
|
|
|
3097
3890
|
bodyZodText = val.getText();
|
|
3098
3891
|
} else if (propName === "response") {
|
|
3099
3892
|
response = zodAstToTs(val);
|
|
3893
|
+
} else if (propName === "error") {
|
|
3894
|
+
error = zodAstToTs(val);
|
|
3100
3895
|
}
|
|
3101
3896
|
}
|
|
3102
|
-
return { query, body, response, bodyZodText, queryZodText };
|
|
3897
|
+
return { query, body, response, error, bodyZodText, queryZodText };
|
|
3103
3898
|
}
|
|
3104
3899
|
|
|
3105
3900
|
// src/discovery/contracts-fast.ts
|
|
3106
3901
|
async function discoverContractsFast(opts) {
|
|
3107
3902
|
const { cwd, glob, tsconfig } = opts;
|
|
3108
|
-
const tsconfigPath =
|
|
3109
|
-
|
|
3903
|
+
const tsconfigPath = resolveTsconfigPath(cwd, tsconfig);
|
|
3904
|
+
const project = createDiscoveryProject(tsconfigPath);
|
|
3905
|
+
const files = await (0, import_fast_glob2.default)(glob, { cwd, absolute: true, onlyFiles: true });
|
|
3906
|
+
for (const f of files) {
|
|
3907
|
+
project.addSourceFileAtPath(f);
|
|
3908
|
+
}
|
|
3909
|
+
bindDiscoveryContext(project, cwd, tsconfigPath);
|
|
3910
|
+
return extractAllRoutes(project);
|
|
3911
|
+
}
|
|
3912
|
+
function resolveTsconfigPath(cwd, tsconfig) {
|
|
3913
|
+
return tsconfig ? (0, import_node_path14.resolve)(tsconfig) : (0, import_node_path14.join)(cwd, "tsconfig.json");
|
|
3914
|
+
}
|
|
3915
|
+
function createDiscoveryProject(tsconfigPath) {
|
|
3110
3916
|
try {
|
|
3111
|
-
|
|
3917
|
+
return new import_ts_morph9.Project({
|
|
3112
3918
|
tsConfigFilePath: tsconfigPath,
|
|
3113
3919
|
skipAddingFilesFromTsConfig: true,
|
|
3114
3920
|
skipLoadingLibFiles: true,
|
|
3115
3921
|
skipFileDependencyResolution: true
|
|
3116
3922
|
});
|
|
3117
3923
|
} catch {
|
|
3118
|
-
|
|
3924
|
+
return new import_ts_morph9.Project({
|
|
3119
3925
|
skipAddingFilesFromTsConfig: true,
|
|
3120
3926
|
skipLoadingLibFiles: true,
|
|
3121
3927
|
skipFileDependencyResolution: true,
|
|
@@ -3126,20 +3932,105 @@ async function discoverContractsFast(opts) {
|
|
|
3126
3932
|
}
|
|
3127
3933
|
});
|
|
3128
3934
|
}
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
project.addSourceFileAtPath(f);
|
|
3132
|
-
}
|
|
3133
|
-
const routes = [];
|
|
3935
|
+
}
|
|
3936
|
+
function bindDiscoveryContext(project, cwd, tsconfigPath) {
|
|
3134
3937
|
setDiscoveryContext(project, {
|
|
3135
3938
|
projectRoot: cwd,
|
|
3136
3939
|
tsconfigPaths: loadTsconfigPaths(tsconfigPath)
|
|
3137
3940
|
});
|
|
3941
|
+
}
|
|
3942
|
+
function extractRoutesFrom(project, controllerPaths) {
|
|
3943
|
+
const routes = [];
|
|
3944
|
+
for (const path of controllerPaths) {
|
|
3945
|
+
const sourceFile = project.getSourceFile(path);
|
|
3946
|
+
if (sourceFile) routes.push(...extractFromSourceFile(sourceFile, project));
|
|
3947
|
+
}
|
|
3948
|
+
return routes;
|
|
3949
|
+
}
|
|
3950
|
+
function extractAllRoutes(project) {
|
|
3951
|
+
const routes = [];
|
|
3138
3952
|
for (const sourceFile of project.getSourceFiles()) {
|
|
3139
3953
|
routes.push(...extractFromSourceFile(sourceFile, project));
|
|
3140
3954
|
}
|
|
3141
3955
|
return routes;
|
|
3142
3956
|
}
|
|
3957
|
+
var PersistentDiscovery = class _PersistentDiscovery {
|
|
3958
|
+
project;
|
|
3959
|
+
cwd;
|
|
3960
|
+
glob;
|
|
3961
|
+
/** Absolute paths of the controllers currently loaded as extraction roots. */
|
|
3962
|
+
controllerPaths = /* @__PURE__ */ new Set();
|
|
3963
|
+
constructor(project, cwd, glob) {
|
|
3964
|
+
this.project = project;
|
|
3965
|
+
this.cwd = cwd;
|
|
3966
|
+
this.glob = glob;
|
|
3967
|
+
}
|
|
3968
|
+
/**
|
|
3969
|
+
* Build the initial persistent Project: create it, glob + add all controllers,
|
|
3970
|
+
* bind the discovery context. Mirrors {@link discoverContractsFast}'s setup.
|
|
3971
|
+
*/
|
|
3972
|
+
static async create(opts) {
|
|
3973
|
+
const { cwd, glob, tsconfig } = opts;
|
|
3974
|
+
const tsconfigPath = resolveTsconfigPath(cwd, tsconfig);
|
|
3975
|
+
const project = createDiscoveryProject(tsconfigPath);
|
|
3976
|
+
bindDiscoveryContext(project, cwd, tsconfigPath);
|
|
3977
|
+
const instance = new _PersistentDiscovery(project, cwd, glob);
|
|
3978
|
+
const files = await (0, import_fast_glob2.default)(glob, { cwd, absolute: true, onlyFiles: true });
|
|
3979
|
+
for (const f of files) {
|
|
3980
|
+
project.addSourceFileAtPath(f);
|
|
3981
|
+
instance.controllerPaths.add(f);
|
|
3982
|
+
}
|
|
3983
|
+
return instance;
|
|
3984
|
+
}
|
|
3985
|
+
/** Run the initial extraction (equivalent to a first `discoverContractsFast`). */
|
|
3986
|
+
discover() {
|
|
3987
|
+
return this.runExtraction();
|
|
3988
|
+
}
|
|
3989
|
+
/**
|
|
3990
|
+
* Re-discover after one or more files changed. Refreshes the changed file(s)
|
|
3991
|
+
* from disk (controllers AND any lazily-loaded DTO/imported files), re-globs
|
|
3992
|
+
* to pick up added/removed controllers, clears the per-Project caches, then
|
|
3993
|
+
* re-extracts. `changedPaths` is a hint; correctness does not depend on it
|
|
3994
|
+
* being exhaustive because re-globbing + refresh-on-presence covers the set.
|
|
3995
|
+
*/
|
|
3996
|
+
async rediscover(changedPaths) {
|
|
3997
|
+
if (changedPaths) {
|
|
3998
|
+
for (const p of changedPaths) {
|
|
3999
|
+
const abs = (0, import_node_path14.resolve)(p);
|
|
4000
|
+
const sf = this.project.getSourceFile(abs);
|
|
4001
|
+
if (sf) {
|
|
4002
|
+
await sf.refreshFromFileSystem();
|
|
4003
|
+
}
|
|
4004
|
+
}
|
|
4005
|
+
}
|
|
4006
|
+
const globbed = new Set(
|
|
4007
|
+
await (0, import_fast_glob2.default)(this.glob, { cwd: this.cwd, absolute: true, onlyFiles: true })
|
|
4008
|
+
);
|
|
4009
|
+
for (const f of globbed) {
|
|
4010
|
+
if (!this.controllerPaths.has(f)) {
|
|
4011
|
+
try {
|
|
4012
|
+
this.project.addSourceFileAtPath(f);
|
|
4013
|
+
this.controllerPaths.add(f);
|
|
4014
|
+
} catch {
|
|
4015
|
+
}
|
|
4016
|
+
}
|
|
4017
|
+
}
|
|
4018
|
+
for (const f of this.controllerPaths) {
|
|
4019
|
+
if (!globbed.has(f)) {
|
|
4020
|
+
const sf = this.project.getSourceFile(f);
|
|
4021
|
+
if (sf) this.project.removeSourceFile(sf);
|
|
4022
|
+
this.controllerPaths.delete(f);
|
|
4023
|
+
}
|
|
4024
|
+
}
|
|
4025
|
+
return this.runExtraction();
|
|
4026
|
+
}
|
|
4027
|
+
/** Clear stale per-Project caches, then extract over the controller set. */
|
|
4028
|
+
runExtraction() {
|
|
4029
|
+
clearTypeResolutionCaches(this.project);
|
|
4030
|
+
clearEnumCache(this.project);
|
|
4031
|
+
return extractRoutesFrom(this.project, this.controllerPaths);
|
|
4032
|
+
}
|
|
4033
|
+
};
|
|
3143
4034
|
function decoratorStringArg(decoratorExpr) {
|
|
3144
4035
|
if (!decoratorExpr) return void 0;
|
|
3145
4036
|
if (import_ts_morph9.Node.isStringLiteral(decoratorExpr)) return decoratorExpr.getLiteralValue();
|
|
@@ -3195,6 +4086,11 @@ function resolveVerb(method) {
|
|
|
3195
4086
|
return { httpMethod: verb, handlerPath: decoratorStringArg(pathArg) ?? "" };
|
|
3196
4087
|
}
|
|
3197
4088
|
}
|
|
4089
|
+
const sseDecorator = method.getDecorator("Sse");
|
|
4090
|
+
if (sseDecorator) {
|
|
4091
|
+
const pathArg = sseDecorator.getArguments()[0];
|
|
4092
|
+
return { httpMethod: "GET", handlerPath: decoratorStringArg(pathArg) ?? "" };
|
|
4093
|
+
}
|
|
3198
4094
|
return null;
|
|
3199
4095
|
}
|
|
3200
4096
|
function readAsDecorator(node, label) {
|
|
@@ -3237,7 +4133,17 @@ function buildRoute(args) {
|
|
|
3237
4133
|
};
|
|
3238
4134
|
}
|
|
3239
4135
|
function extractContractRoute(args) {
|
|
3240
|
-
const {
|
|
4136
|
+
const {
|
|
4137
|
+
cls,
|
|
4138
|
+
method,
|
|
4139
|
+
applyContractDecorator,
|
|
4140
|
+
verb,
|
|
4141
|
+
prefix,
|
|
4142
|
+
className,
|
|
4143
|
+
sourceFile,
|
|
4144
|
+
project,
|
|
4145
|
+
seenNames
|
|
4146
|
+
} = args;
|
|
3241
4147
|
const firstDecoratorArg = applyContractDecorator.getArguments()[0];
|
|
3242
4148
|
if (!firstDecoratorArg) return null;
|
|
3243
4149
|
let contractDef = null;
|
|
@@ -3247,18 +4153,19 @@ function extractContractRoute(args) {
|
|
|
3247
4153
|
contractDef = parseDefineContractCall(firstDecoratorArg);
|
|
3248
4154
|
} else if (import_ts_morph9.Node.isIdentifier(firstDecoratorArg)) {
|
|
3249
4155
|
const identName = firstDecoratorArg.getText();
|
|
3250
|
-
const
|
|
3251
|
-
if (!
|
|
4156
|
+
const resolvedVar = resolveImportedVariable(identName, sourceFile, project);
|
|
4157
|
+
if (!resolvedVar) {
|
|
3252
4158
|
console.warn(
|
|
3253
|
-
`[nestjs-codegen/fast] Cannot resolve '${identName}' in ${sourceFile.getFilePath()}
|
|
4159
|
+
`[nestjs-codegen/fast] Cannot resolve contract identifier '${identName}' applied in ${sourceFile.getFilePath()} \u2014 the import could not be followed to a declaration; skipping`
|
|
3254
4160
|
);
|
|
3255
4161
|
return null;
|
|
3256
4162
|
}
|
|
4163
|
+
const { decl: varDecl, file: declFile } = resolvedVar;
|
|
3257
4164
|
const initializer = varDecl.getInitializer();
|
|
3258
4165
|
if (!initializer) return null;
|
|
3259
4166
|
contractDef = parseDefineContractCall(initializer);
|
|
3260
4167
|
if (contractDef && varDecl.isExported()) {
|
|
3261
|
-
const filePath =
|
|
4168
|
+
const filePath = declFile.getFilePath();
|
|
3262
4169
|
if (contractDef.body !== null) {
|
|
3263
4170
|
bodyZodRef = { name: `${identName}.body`, filePath };
|
|
3264
4171
|
}
|
|
@@ -3291,6 +4198,7 @@ function extractContractRoute(args) {
|
|
|
3291
4198
|
query: contractDef.query,
|
|
3292
4199
|
body: contractDef.body,
|
|
3293
4200
|
response: contractDef.response,
|
|
4201
|
+
error: contractDef.error,
|
|
3294
4202
|
// Path A: capture both the importable ref and the raw text. The emitter
|
|
3295
4203
|
// prefers inlining the text (client-safe — re-exporting from a controller
|
|
3296
4204
|
// would drag server-only deps into the client bundle).
|
|
@@ -3322,15 +4230,18 @@ function extractDtoRoute(args) {
|
|
|
3322
4230
|
query: dtoContract?.query ?? null,
|
|
3323
4231
|
body: dtoContract?.body ?? null,
|
|
3324
4232
|
response: dtoContract?.response ?? "unknown",
|
|
4233
|
+
error: dtoContract?.error ?? null,
|
|
3325
4234
|
queryRef: dtoContract?.queryRef ?? null,
|
|
3326
4235
|
bodyRef: dtoContract?.bodyRef ?? null,
|
|
3327
4236
|
responseRef: dtoContract?.responseRef ?? null,
|
|
4237
|
+
errorRef: dtoContract?.errorRef ?? null,
|
|
3328
4238
|
filterFields: dtoContract?.filterFields ?? null,
|
|
3329
4239
|
filterFieldTypes: dtoContract?.filterFieldTypes ?? null,
|
|
3330
4240
|
filterSource: dtoContract?.filterSource ?? null,
|
|
3331
4241
|
formWarnings: dtoContract?.formWarnings ?? [],
|
|
3332
4242
|
bodySchema: dtoContract?.bodySchema ?? null,
|
|
3333
|
-
querySchema: dtoContract?.querySchema ?? null
|
|
4243
|
+
querySchema: dtoContract?.querySchema ?? null,
|
|
4244
|
+
stream: dtoContract?.stream ?? false
|
|
3334
4245
|
}
|
|
3335
4246
|
});
|
|
3336
4247
|
}
|
|
@@ -3354,6 +4265,7 @@ function extractFromSourceFile(sourceFile, project) {
|
|
|
3354
4265
|
prefix,
|
|
3355
4266
|
className,
|
|
3356
4267
|
sourceFile,
|
|
4268
|
+
project,
|
|
3357
4269
|
seenNames
|
|
3358
4270
|
}) : extractDtoRoute({
|
|
3359
4271
|
cls,
|
|
@@ -3372,9 +4284,9 @@ function extractFromSourceFile(sourceFile, project) {
|
|
|
3372
4284
|
}
|
|
3373
4285
|
|
|
3374
4286
|
// src/watch/lock-file.ts
|
|
3375
|
-
var
|
|
3376
|
-
var
|
|
3377
|
-
var
|
|
4287
|
+
var import_promises12 = require("fs/promises");
|
|
4288
|
+
var import_promises13 = require("fs/promises");
|
|
4289
|
+
var import_node_path15 = require("path");
|
|
3378
4290
|
var LOCK_FILE = ".watcher.lock";
|
|
3379
4291
|
function isProcessAlive(pid) {
|
|
3380
4292
|
try {
|
|
@@ -3385,21 +4297,21 @@ function isProcessAlive(pid) {
|
|
|
3385
4297
|
}
|
|
3386
4298
|
}
|
|
3387
4299
|
async function acquireLock(outDir) {
|
|
3388
|
-
await (0,
|
|
3389
|
-
const lockPath = (0,
|
|
4300
|
+
await (0, import_promises13.mkdir)(outDir, { recursive: true });
|
|
4301
|
+
const lockPath = (0, import_node_path15.join)(outDir, LOCK_FILE);
|
|
3390
4302
|
const lockData = { pid: process.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
3391
4303
|
try {
|
|
3392
|
-
const fd = await (0,
|
|
4304
|
+
const fd = await (0, import_promises12.open)(lockPath, "wx");
|
|
3393
4305
|
await fd.writeFile(`${JSON.stringify(lockData, null, 2)}
|
|
3394
4306
|
`, "utf8");
|
|
3395
4307
|
await fd.close();
|
|
3396
4308
|
} catch (err) {
|
|
3397
4309
|
if (err.code === "EEXIST") {
|
|
3398
4310
|
try {
|
|
3399
|
-
const raw = await (0,
|
|
4311
|
+
const raw = await (0, import_promises13.readFile)(lockPath, "utf8");
|
|
3400
4312
|
const existing = JSON.parse(raw);
|
|
3401
4313
|
if (isProcessAlive(existing.pid)) return null;
|
|
3402
|
-
await (0,
|
|
4314
|
+
await (0, import_promises13.unlink)(lockPath);
|
|
3403
4315
|
return acquireLock(outDir);
|
|
3404
4316
|
} catch {
|
|
3405
4317
|
return null;
|
|
@@ -3410,7 +4322,7 @@ async function acquireLock(outDir) {
|
|
|
3410
4322
|
return {
|
|
3411
4323
|
release: async () => {
|
|
3412
4324
|
try {
|
|
3413
|
-
await (0,
|
|
4325
|
+
await (0, import_promises13.unlink)(lockPath);
|
|
3414
4326
|
} catch {
|
|
3415
4327
|
}
|
|
3416
4328
|
}
|
|
@@ -3426,7 +4338,7 @@ async function watch(config, onChange) {
|
|
|
3426
4338
|
if (lock === null) {
|
|
3427
4339
|
let holderPid = "unknown";
|
|
3428
4340
|
try {
|
|
3429
|
-
const raw = await (0,
|
|
4341
|
+
const raw = await (0, import_promises14.readFile)((0, import_node_path16.join)(config.codegen.outDir, ".watcher.lock"), "utf8");
|
|
3430
4342
|
const data = JSON.parse(raw);
|
|
3431
4343
|
if (data.pid !== void 0) holderPid = String(data.pid);
|
|
3432
4344
|
} catch {
|
|
@@ -3436,12 +4348,20 @@ async function watch(config, onChange) {
|
|
|
3436
4348
|
);
|
|
3437
4349
|
return NO_OP_WATCHER;
|
|
3438
4350
|
}
|
|
4351
|
+
let discovery = null;
|
|
4352
|
+
async function getDiscovery() {
|
|
4353
|
+
if (discovery === null) {
|
|
4354
|
+
discovery = await PersistentDiscovery.create({
|
|
4355
|
+
cwd: config.codegen.cwd,
|
|
4356
|
+
glob: config.contracts.glob,
|
|
4357
|
+
...config.app?.tsconfig ? { tsconfig: config.app.tsconfig } : {}
|
|
4358
|
+
});
|
|
4359
|
+
return discovery;
|
|
4360
|
+
}
|
|
4361
|
+
return discovery;
|
|
4362
|
+
}
|
|
3439
4363
|
try {
|
|
3440
|
-
const initialRoutes = await
|
|
3441
|
-
cwd: config.codegen.cwd,
|
|
3442
|
-
glob: config.contracts.glob,
|
|
3443
|
-
...config.app?.tsconfig ? { tsconfig: config.app.tsconfig } : {}
|
|
3444
|
-
});
|
|
4364
|
+
const initialRoutes = (await getDiscovery()).discover();
|
|
3445
4365
|
await generate(config, initialRoutes);
|
|
3446
4366
|
} catch (err) {
|
|
3447
4367
|
console.warn(
|
|
@@ -3454,7 +4374,7 @@ async function watch(config, onChange) {
|
|
|
3454
4374
|
}
|
|
3455
4375
|
let pagesDebounceTimer;
|
|
3456
4376
|
const pagesGlob = config.pages?.glob ?? ".nestjs-codegen-no-pages";
|
|
3457
|
-
const pagesWatcher = import_chokidar.default.watch((0,
|
|
4377
|
+
const pagesWatcher = import_chokidar.default.watch((0, import_node_path16.join)(config.codegen.cwd, pagesGlob), {
|
|
3458
4378
|
ignoreInitial: true,
|
|
3459
4379
|
persistent: true,
|
|
3460
4380
|
awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
|
|
@@ -3480,23 +4400,23 @@ async function watch(config, onChange) {
|
|
|
3480
4400
|
pagesWatcher.on("change", schedulePagesRegenerate);
|
|
3481
4401
|
pagesWatcher.on("unlink", schedulePagesRegenerate);
|
|
3482
4402
|
let contractsDebounceTimer;
|
|
3483
|
-
const
|
|
4403
|
+
const pendingChangedPaths = /* @__PURE__ */ new Set();
|
|
4404
|
+
const contractsWatcher = import_chokidar.default.watch((0, import_node_path16.join)(config.codegen.cwd, config.contracts.glob), {
|
|
3484
4405
|
ignoreInitial: true,
|
|
3485
4406
|
persistent: true,
|
|
3486
4407
|
awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
|
|
3487
4408
|
});
|
|
3488
|
-
function scheduleContractsRegenerate() {
|
|
4409
|
+
function scheduleContractsRegenerate(changedPath) {
|
|
4410
|
+
if (typeof changedPath === "string") pendingChangedPaths.add(changedPath);
|
|
3489
4411
|
if (contractsDebounceTimer !== void 0) {
|
|
3490
4412
|
clearTimeout(contractsDebounceTimer);
|
|
3491
4413
|
}
|
|
3492
4414
|
contractsDebounceTimer = setTimeout(async () => {
|
|
3493
4415
|
contractsDebounceTimer = void 0;
|
|
4416
|
+
const changed = [...pendingChangedPaths];
|
|
4417
|
+
pendingChangedPaths.clear();
|
|
3494
4418
|
try {
|
|
3495
|
-
const routes = await
|
|
3496
|
-
cwd: config.codegen.cwd,
|
|
3497
|
-
glob: config.contracts.glob,
|
|
3498
|
-
...config.app?.tsconfig ? { tsconfig: config.app.tsconfig } : {}
|
|
3499
|
-
});
|
|
4419
|
+
const routes = await (await getDiscovery()).rediscover(changed);
|
|
3500
4420
|
await generate(config, routes);
|
|
3501
4421
|
} catch (err) {
|
|
3502
4422
|
console.error(
|
|
@@ -3507,17 +4427,17 @@ async function watch(config, onChange) {
|
|
|
3507
4427
|
onChange?.();
|
|
3508
4428
|
}, config.contracts.debounceMs);
|
|
3509
4429
|
}
|
|
3510
|
-
contractsWatcher.on("add", scheduleContractsRegenerate);
|
|
3511
|
-
contractsWatcher.on("change", scheduleContractsRegenerate);
|
|
3512
|
-
contractsWatcher.on("unlink", scheduleContractsRegenerate);
|
|
3513
|
-
const formsWatcher = import_chokidar.default.watch((0,
|
|
4430
|
+
contractsWatcher.on("add", (p) => scheduleContractsRegenerate(p));
|
|
4431
|
+
contractsWatcher.on("change", (p) => scheduleContractsRegenerate(p));
|
|
4432
|
+
contractsWatcher.on("unlink", (p) => scheduleContractsRegenerate(p));
|
|
4433
|
+
const formsWatcher = import_chokidar.default.watch((0, import_node_path16.join)(config.codegen.cwd, config.forms.watch), {
|
|
3514
4434
|
ignoreInitial: true,
|
|
3515
4435
|
persistent: true,
|
|
3516
4436
|
awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
|
|
3517
4437
|
});
|
|
3518
|
-
formsWatcher.on("add", scheduleContractsRegenerate);
|
|
3519
|
-
formsWatcher.on("change", scheduleContractsRegenerate);
|
|
3520
|
-
formsWatcher.on("unlink", scheduleContractsRegenerate);
|
|
4438
|
+
formsWatcher.on("add", (p) => scheduleContractsRegenerate(p));
|
|
4439
|
+
formsWatcher.on("change", (p) => scheduleContractsRegenerate(p));
|
|
4440
|
+
formsWatcher.on("unlink", (p) => scheduleContractsRegenerate(p));
|
|
3521
4441
|
return {
|
|
3522
4442
|
close: async () => {
|
|
3523
4443
|
if (pagesDebounceTimer !== void 0) {
|
|
@@ -3537,7 +4457,7 @@ async function watch(config, onChange) {
|
|
|
3537
4457
|
}
|
|
3538
4458
|
|
|
3539
4459
|
// src/index.ts
|
|
3540
|
-
var VERSION = "0.
|
|
4460
|
+
var VERSION = "0.5.1";
|
|
3541
4461
|
|
|
3542
4462
|
// src/cli/codegen.ts
|
|
3543
4463
|
async function runCodegen(opts = {}) {
|
|
@@ -3566,13 +4486,13 @@ async function runCodegen(opts = {}) {
|
|
|
3566
4486
|
// src/cli/doctor.ts
|
|
3567
4487
|
var import_node_child_process2 = require("child_process");
|
|
3568
4488
|
var import_node_fs4 = require("fs");
|
|
3569
|
-
var
|
|
4489
|
+
var import_node_path18 = require("path");
|
|
3570
4490
|
|
|
3571
4491
|
// src/cli/init.ts
|
|
3572
4492
|
var import_node_child_process = require("child_process");
|
|
3573
4493
|
var import_node_fs3 = require("fs");
|
|
3574
|
-
var
|
|
3575
|
-
var
|
|
4494
|
+
var import_promises15 = require("fs/promises");
|
|
4495
|
+
var import_node_path17 = require("path");
|
|
3576
4496
|
var import_node_readline = require("readline");
|
|
3577
4497
|
|
|
3578
4498
|
// src/cli/patch-utils.ts
|
|
@@ -3634,7 +4554,7 @@ ${bold(title)}`);
|
|
|
3634
4554
|
}
|
|
3635
4555
|
async function readPackageJson(cwd) {
|
|
3636
4556
|
try {
|
|
3637
|
-
const raw = await (0,
|
|
4557
|
+
const raw = await (0, import_promises15.readFile)((0, import_node_path17.join)(cwd, "package.json"), "utf8");
|
|
3638
4558
|
return JSON.parse(raw);
|
|
3639
4559
|
} catch {
|
|
3640
4560
|
return {};
|
|
@@ -3665,7 +4585,7 @@ async function detectTemplateEngine(cwd) {
|
|
|
3665
4585
|
async function detectPackageManager(cwd) {
|
|
3666
4586
|
async function exists(file) {
|
|
3667
4587
|
try {
|
|
3668
|
-
await (0,
|
|
4588
|
+
await (0, import_promises15.access)((0, import_node_path17.join)(cwd, file));
|
|
3669
4589
|
return true;
|
|
3670
4590
|
} catch {
|
|
3671
4591
|
return false;
|
|
@@ -3693,7 +4613,7 @@ async function promptFramework() {
|
|
|
3693
4613
|
}
|
|
3694
4614
|
async function fileExists2(filePath) {
|
|
3695
4615
|
try {
|
|
3696
|
-
await (0,
|
|
4616
|
+
await (0, import_promises15.access)(filePath);
|
|
3697
4617
|
return true;
|
|
3698
4618
|
} catch {
|
|
3699
4619
|
return false;
|
|
@@ -3706,15 +4626,15 @@ async function writeIfNotExists(filePath, content, label) {
|
|
|
3706
4626
|
}
|
|
3707
4627
|
const dir = filePath.substring(0, filePath.lastIndexOf("/"));
|
|
3708
4628
|
if (dir) {
|
|
3709
|
-
await (0,
|
|
4629
|
+
await (0, import_promises15.mkdir)(dir, { recursive: true });
|
|
3710
4630
|
}
|
|
3711
|
-
await (0,
|
|
4631
|
+
await (0, import_promises15.writeFile)(filePath, content, "utf8");
|
|
3712
4632
|
logCreated(label);
|
|
3713
4633
|
}
|
|
3714
4634
|
async function handleViteConfig(cwd, framework) {
|
|
3715
|
-
const filePath = (0,
|
|
4635
|
+
const filePath = (0, import_node_path17.join)(cwd, "vite.config.ts");
|
|
3716
4636
|
if (await fileExists2(filePath)) {
|
|
3717
|
-
const existing = await (0,
|
|
4637
|
+
const existing = await (0, import_promises15.readFile)(filePath, "utf8");
|
|
3718
4638
|
const hasPlugin = existing.includes("nestInertia") || existing.includes("nestjs-inertia-vite/plugin");
|
|
3719
4639
|
if (!hasPlugin) {
|
|
3720
4640
|
logSkipped("vite.config.ts");
|
|
@@ -3730,15 +4650,15 @@ async function handleViteConfig(cwd, framework) {
|
|
|
3730
4650
|
}
|
|
3731
4651
|
const dir = filePath.substring(0, filePath.lastIndexOf("/"));
|
|
3732
4652
|
if (dir) {
|
|
3733
|
-
await (0,
|
|
4653
|
+
await (0, import_promises15.mkdir)(dir, { recursive: true });
|
|
3734
4654
|
}
|
|
3735
|
-
await (0,
|
|
4655
|
+
await (0, import_promises15.writeFile)(filePath, viteConfigTemplate(framework), "utf8");
|
|
3736
4656
|
logCreated("vite.config.ts");
|
|
3737
4657
|
}
|
|
3738
4658
|
async function patchGitignore(gitignorePath) {
|
|
3739
4659
|
let existing = "";
|
|
3740
4660
|
if (await fileExists2(gitignorePath)) {
|
|
3741
|
-
existing = await (0,
|
|
4661
|
+
existing = await (0, import_promises15.readFile)(gitignorePath, "utf8");
|
|
3742
4662
|
}
|
|
3743
4663
|
if (existing.split("\n").some((line) => line.trim() === GITIGNORE_ENTRY)) {
|
|
3744
4664
|
console.log(` ${cyan("\u2192")} .gitignore ${dim("(already contains .nestjs-inertia/, skipped)")}`);
|
|
@@ -3748,7 +4668,7 @@ async function patchGitignore(gitignorePath) {
|
|
|
3748
4668
|
` : `${existing}
|
|
3749
4669
|
${GITIGNORE_ENTRY}
|
|
3750
4670
|
`;
|
|
3751
|
-
await (0,
|
|
4671
|
+
await (0, import_promises15.writeFile)(gitignorePath, newContent, "utf8");
|
|
3752
4672
|
logPatched(".gitignore", "added .nestjs-inertia/");
|
|
3753
4673
|
}
|
|
3754
4674
|
function installDeps(pkgManager, deps, dev) {
|
|
@@ -3770,10 +4690,10 @@ function installDeps(pkgManager, deps, dev) {
|
|
|
3770
4690
|
}
|
|
3771
4691
|
}
|
|
3772
4692
|
async function patchPackageJsonScripts(cwd, scripts) {
|
|
3773
|
-
const pkgPath = (0,
|
|
4693
|
+
const pkgPath = (0, import_node_path17.join)(cwd, "package.json");
|
|
3774
4694
|
let pkg = {};
|
|
3775
4695
|
try {
|
|
3776
|
-
pkg = JSON.parse(await (0,
|
|
4696
|
+
pkg = JSON.parse(await (0, import_promises15.readFile)(pkgPath, "utf8"));
|
|
3777
4697
|
} catch {
|
|
3778
4698
|
return;
|
|
3779
4699
|
}
|
|
@@ -3792,7 +4712,7 @@ async function patchPackageJsonScripts(cwd, scripts) {
|
|
|
3792
4712
|
return;
|
|
3793
4713
|
}
|
|
3794
4714
|
pkg.scripts = existing;
|
|
3795
|
-
await (0,
|
|
4715
|
+
await (0, import_promises15.writeFile)(pkgPath, `${JSON.stringify(pkg, null, 2)}
|
|
3796
4716
|
`, "utf8");
|
|
3797
4717
|
}
|
|
3798
4718
|
function patchAppModule(filePath, rootView) {
|
|
@@ -4087,7 +5007,7 @@ export class HomeController {
|
|
|
4087
5007
|
}
|
|
4088
5008
|
`;
|
|
4089
5009
|
function patchTsconfigExclude(cwd, dir, filename = "tsconfig.json") {
|
|
4090
|
-
const filePath = (0,
|
|
5010
|
+
const filePath = (0, import_node_path17.join)(cwd, filename);
|
|
4091
5011
|
return patchJsonFile(
|
|
4092
5012
|
filePath,
|
|
4093
5013
|
(json) => {
|
|
@@ -4102,7 +5022,7 @@ function patchTsconfigExclude(cwd, dir, filename = "tsconfig.json") {
|
|
|
4102
5022
|
);
|
|
4103
5023
|
}
|
|
4104
5024
|
function patchNestCliJson(cwd, shellDir) {
|
|
4105
|
-
const filePath = (0,
|
|
5025
|
+
const filePath = (0, import_node_path17.join)(cwd, "nest-cli.json");
|
|
4106
5026
|
return patchJsonFile(filePath, (json) => {
|
|
4107
5027
|
const compiler = json.compilerOptions ?? {};
|
|
4108
5028
|
const assets = compiler.assets ?? [];
|
|
@@ -4125,46 +5045,46 @@ async function scaffoldFiles(ctx) {
|
|
|
4125
5045
|
const { cwd, framework, engine, shellFileName, entryExt, pageExt } = ctx;
|
|
4126
5046
|
logSection("Scaffold files");
|
|
4127
5047
|
await writeIfNotExists(
|
|
4128
|
-
(0,
|
|
5048
|
+
(0, import_node_path17.join)(cwd, "nestjs-inertia.config.ts"),
|
|
4129
5049
|
configTemplate(framework),
|
|
4130
5050
|
"nestjs-inertia.config.ts"
|
|
4131
5051
|
);
|
|
4132
|
-
await writeIfNotExists((0,
|
|
5052
|
+
await writeIfNotExists((0, import_node_path17.join)(cwd, "nestjs-inertia.d.ts"), DTS_TEMPLATE, "nestjs-inertia.d.ts");
|
|
4133
5053
|
await writeIfNotExists(
|
|
4134
|
-
(0,
|
|
5054
|
+
(0, import_node_path17.join)(cwd, "tsconfig.inertia.json"),
|
|
4135
5055
|
TSCONFIG_INERTIA_TEMPLATE,
|
|
4136
5056
|
"tsconfig.inertia.json"
|
|
4137
5057
|
);
|
|
4138
5058
|
await writeIfNotExists(
|
|
4139
|
-
(0,
|
|
5059
|
+
(0, import_node_path17.join)(cwd, "inertia", "tsconfig.json"),
|
|
4140
5060
|
INERTIA_TSCONFIG_TEMPLATE,
|
|
4141
5061
|
"inertia/tsconfig.json"
|
|
4142
5062
|
);
|
|
4143
5063
|
await writeIfNotExists(
|
|
4144
|
-
(0,
|
|
5064
|
+
(0, import_node_path17.join)(cwd, "inertia", shellFileName),
|
|
4145
5065
|
htmlShellTemplate(framework, engine),
|
|
4146
5066
|
`inertia/${shellFileName}`
|
|
4147
5067
|
);
|
|
4148
5068
|
await handleViteConfig(cwd, framework);
|
|
4149
5069
|
await writeIfNotExists(
|
|
4150
|
-
(0,
|
|
5070
|
+
(0, import_node_path17.join)(cwd, "inertia", "app", `client.${entryExt}`),
|
|
4151
5071
|
entryPointTemplate(framework),
|
|
4152
5072
|
`inertia/app/client.${entryExt}`
|
|
4153
5073
|
);
|
|
4154
5074
|
await writeIfNotExists(
|
|
4155
|
-
(0,
|
|
5075
|
+
(0, import_node_path17.join)(cwd, "inertia", "pages", `Home.${pageExt}`),
|
|
4156
5076
|
samplePageTemplate(framework),
|
|
4157
5077
|
`inertia/pages/Home.${pageExt}`
|
|
4158
5078
|
);
|
|
4159
5079
|
await writeIfNotExists(
|
|
4160
|
-
(0,
|
|
5080
|
+
(0, import_node_path17.join)(cwd, "src", "home.controller.ts"),
|
|
4161
5081
|
SAMPLE_CONTROLLER,
|
|
4162
5082
|
"src/home.controller.ts"
|
|
4163
5083
|
);
|
|
4164
5084
|
}
|
|
4165
5085
|
function patchServerAppModule(ctx) {
|
|
4166
5086
|
const { cwd, rootView } = ctx;
|
|
4167
|
-
const appModulePath = (0,
|
|
5087
|
+
const appModulePath = (0, import_node_path17.join)(cwd, "src", "app.module.ts");
|
|
4168
5088
|
const appModuleResult = patchAppModule(appModulePath, rootView);
|
|
4169
5089
|
if (appModuleResult === "patched") {
|
|
4170
5090
|
logPatched("src/app.module.ts", "added InertiaModule.forRoot");
|
|
@@ -4178,7 +5098,7 @@ function patchServerAppModule(ctx) {
|
|
|
4178
5098
|
}
|
|
4179
5099
|
}
|
|
4180
5100
|
function patchServerMainTs(ctx) {
|
|
4181
|
-
const mainTsPath = (0,
|
|
5101
|
+
const mainTsPath = (0, import_node_path17.join)(ctx.cwd, "src", "main.ts");
|
|
4182
5102
|
const mainTsResult = patchMainTs(mainTsPath);
|
|
4183
5103
|
if (mainTsResult === "patched") {
|
|
4184
5104
|
logPatched("src/main.ts", "added setupInertiaVite after NestFactory.create");
|
|
@@ -4213,7 +5133,7 @@ function patchBuildConfigs(ctx) {
|
|
|
4213
5133
|
}
|
|
4214
5134
|
async function patchGitignoreAndDist(ctx) {
|
|
4215
5135
|
const { cwd } = ctx;
|
|
4216
|
-
await patchGitignore((0,
|
|
5136
|
+
await patchGitignore((0, import_node_path17.join)(cwd, ".gitignore"));
|
|
4217
5137
|
const tsconfigDistResult = patchTsconfigExclude(cwd, "dist", "tsconfig.json");
|
|
4218
5138
|
if (tsconfigDistResult === "patched") {
|
|
4219
5139
|
logPatched("tsconfig.json", "excluded dist/ from server compilation");
|
|
@@ -4319,7 +5239,7 @@ ${green("\u2713")} Setup complete! Run: ${bold("nest start --watch")}
|
|
|
4319
5239
|
|
|
4320
5240
|
// src/cli/doctor.ts
|
|
4321
5241
|
function checkFileExists(cwd, file) {
|
|
4322
|
-
return (0, import_node_fs4.existsSync)((0,
|
|
5242
|
+
return (0, import_node_fs4.existsSync)((0, import_node_path18.join)(cwd, file));
|
|
4323
5243
|
}
|
|
4324
5244
|
function readJson(path) {
|
|
4325
5245
|
try {
|
|
@@ -4355,15 +5275,15 @@ function writeJsonField(filePath, dotPath, value) {
|
|
|
4355
5275
|
}
|
|
4356
5276
|
function getPackageVersion(cwd, pkg) {
|
|
4357
5277
|
try {
|
|
4358
|
-
const pkgJson = readJson((0,
|
|
5278
|
+
const pkgJson = readJson((0, import_node_path18.join)(cwd, "node_modules", pkg, "package.json"));
|
|
4359
5279
|
return pkgJson?.version ?? null;
|
|
4360
5280
|
} catch {
|
|
4361
5281
|
return null;
|
|
4362
5282
|
}
|
|
4363
5283
|
}
|
|
4364
5284
|
function detectPkgManager(cwd) {
|
|
4365
|
-
if ((0, import_node_fs4.existsSync)((0,
|
|
4366
|
-
if ((0, import_node_fs4.existsSync)((0,
|
|
5285
|
+
if ((0, import_node_fs4.existsSync)((0, import_node_path18.join)(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
5286
|
+
if ((0, import_node_fs4.existsSync)((0, import_node_path18.join)(cwd, "yarn.lock"))) return "yarn";
|
|
4367
5287
|
return "npm";
|
|
4368
5288
|
}
|
|
4369
5289
|
async function runDoctor(opts) {
|
|
@@ -4401,7 +5321,7 @@ async function runDoctor(opts) {
|
|
|
4401
5321
|
autoFix: () => runInit({ cwd })
|
|
4402
5322
|
});
|
|
4403
5323
|
if (foundShellDir) {
|
|
4404
|
-
const nestCliPath = (0,
|
|
5324
|
+
const nestCliPath = (0, import_node_path18.join)(cwd, "nest-cli.json");
|
|
4405
5325
|
const nestCli = readJson(nestCliPath);
|
|
4406
5326
|
const compiler = nestCli?.compilerOptions ?? {};
|
|
4407
5327
|
const assets = compiler.assets ?? [];
|
|
@@ -4440,7 +5360,7 @@ async function runDoctor(opts) {
|
|
|
4440
5360
|
fix: "Run: nestjs-codegen codegen",
|
|
4441
5361
|
autoFix: () => runCodegen({ cwd })
|
|
4442
5362
|
});
|
|
4443
|
-
const tsconfigPath = (0,
|
|
5363
|
+
const tsconfigPath = (0, import_node_path18.join)(cwd, "tsconfig.json");
|
|
4444
5364
|
const tsconfig = readJson(tsconfigPath);
|
|
4445
5365
|
const paths = tsconfig?.compilerOptions?.paths;
|
|
4446
5366
|
checks.push({
|
|
@@ -4451,7 +5371,7 @@ async function runDoctor(opts) {
|
|
|
4451
5371
|
});
|
|
4452
5372
|
const inertiaDir = foundShellDir ?? "inertia";
|
|
4453
5373
|
for (const tsconfigFile of ["tsconfig.json", "tsconfig.build.json"]) {
|
|
4454
|
-
const tsc = readJson((0,
|
|
5374
|
+
const tsc = readJson((0, import_node_path18.join)(cwd, tsconfigFile));
|
|
4455
5375
|
if (!tsc) continue;
|
|
4456
5376
|
const excl = tsc.exclude ?? [];
|
|
4457
5377
|
const excludesIt = excl.includes(inertiaDir);
|
|
@@ -4477,7 +5397,7 @@ async function runDoctor(opts) {
|
|
|
4477
5397
|
}
|
|
4478
5398
|
});
|
|
4479
5399
|
}
|
|
4480
|
-
const inertiaTsconfigPath = (0,
|
|
5400
|
+
const inertiaTsconfigPath = (0, import_node_path18.join)(cwd, "tsconfig.inertia.json");
|
|
4481
5401
|
const inertiaTsconfig = readJson(inertiaTsconfigPath);
|
|
4482
5402
|
checks.push({
|
|
4483
5403
|
name: "tsconfig.inertia.json exists",
|
|
@@ -4529,7 +5449,7 @@ async function runDoctor(opts) {
|
|
|
4529
5449
|
fix: 'Add "nestjs-inertia.d.ts" to include array (resolves InertiaRegistry augmentation)'
|
|
4530
5450
|
});
|
|
4531
5451
|
}
|
|
4532
|
-
const innerTsconfigPath = (0,
|
|
5452
|
+
const innerTsconfigPath = (0, import_node_path18.join)(cwd, "inertia", "tsconfig.json");
|
|
4533
5453
|
checks.push({
|
|
4534
5454
|
name: "inertia/tsconfig.json exists (VSCode picks up ~codegen alias)",
|
|
4535
5455
|
pass: (0, import_node_fs4.existsSync)(innerTsconfigPath),
|
|
@@ -4539,7 +5459,7 @@ async function runDoctor(opts) {
|
|
|
4539
5459
|
}
|
|
4540
5460
|
});
|
|
4541
5461
|
if (checkFileExists(cwd, "vite.config.ts")) {
|
|
4542
|
-
const viteContent = (0, import_node_fs4.readFileSync)((0,
|
|
5462
|
+
const viteContent = (0, import_node_fs4.readFileSync)((0, import_node_path18.join)(cwd, "vite.config.ts"), "utf8");
|
|
4543
5463
|
checks.push({
|
|
4544
5464
|
name: "vite.config.ts has resolve.alias",
|
|
4545
5465
|
pass: viteContent.includes("resolve") && viteContent.includes("alias"),
|
|
@@ -4604,7 +5524,7 @@ async function runDoctor(opts) {
|
|
|
4604
5524
|
});
|
|
4605
5525
|
}
|
|
4606
5526
|
if (checkFileExists(cwd, ".gitignore")) {
|
|
4607
|
-
const gitignorePath = (0,
|
|
5527
|
+
const gitignorePath = (0, import_node_path18.join)(cwd, ".gitignore");
|
|
4608
5528
|
const gitignore = (0, import_node_fs4.readFileSync)(gitignorePath, "utf8");
|
|
4609
5529
|
checks.push({
|
|
4610
5530
|
name: ".gitignore includes .nestjs-inertia/",
|
|
@@ -4613,7 +5533,7 @@ async function runDoctor(opts) {
|
|
|
4613
5533
|
autoFix: () => (0, import_node_fs4.appendFileSync)(gitignorePath, "\n.nestjs-inertia/\n")
|
|
4614
5534
|
});
|
|
4615
5535
|
}
|
|
4616
|
-
const pkgJsonPath = (0,
|
|
5536
|
+
const pkgJsonPath = (0, import_node_path18.join)(cwd, "package.json");
|
|
4617
5537
|
const pkgJson = readJson(pkgJsonPath);
|
|
4618
5538
|
const scripts = pkgJson?.scripts ?? {};
|
|
4619
5539
|
checks.push({
|