@dudousxd/nestjs-codegen 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +37 -0
- package/dist/cli/main.cjs +1097 -161
- package/dist/cli/main.cjs.map +1 -1
- package/dist/cli/main.js +1080 -144
- 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 +1070 -119
- 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 +1051 -106
- package/dist/index.js.map +1 -1
- package/dist/nest/index.cjs +1032 -114
- 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 +1026 -108
- 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);
|
|
@@ -1405,11 +1443,467 @@ async function emitIndex(outDir, hasContracts = false, hasForms = false) {
|
|
|
1405
1443
|
await (0, import_promises6.writeFile)((0, import_node_path7.join)(outDir, "index.d.ts"), content, "utf8");
|
|
1406
1444
|
}
|
|
1407
1445
|
|
|
1408
|
-
// src/emit/emit-
|
|
1446
|
+
// src/emit/emit-mocks.ts
|
|
1409
1447
|
var import_promises7 = require("fs/promises");
|
|
1410
1448
|
var import_node_path8 = require("path");
|
|
1411
|
-
|
|
1449
|
+
|
|
1450
|
+
// src/ir/schema-node-to-json-schema.ts
|
|
1451
|
+
var DEFAULT_CTX = { refPrefix: "#/components/schemas/" };
|
|
1452
|
+
function parseLiteral(raw) {
|
|
1453
|
+
const t = raw.trim();
|
|
1454
|
+
if (t === "true") return true;
|
|
1455
|
+
if (t === "false") return false;
|
|
1456
|
+
if (t === "null") return null;
|
|
1457
|
+
const q = t[0];
|
|
1458
|
+
if ((q === "'" || q === '"' || q === "`") && t[t.length - 1] === q) {
|
|
1459
|
+
return t.slice(1, -1).replace(/\\'/g, "'").replace(/\\"/g, '"').replace(/\\`/g, "`").replace(/\\\\/g, "\\");
|
|
1460
|
+
}
|
|
1461
|
+
if (/^[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/.test(t)) {
|
|
1462
|
+
return Number(t);
|
|
1463
|
+
}
|
|
1464
|
+
return t;
|
|
1465
|
+
}
|
|
1466
|
+
function literalsType(values) {
|
|
1467
|
+
const types = new Set(values.map((v) => v === null ? "null" : typeof v));
|
|
1468
|
+
if (types.size === 1) {
|
|
1469
|
+
const only = [...types][0];
|
|
1470
|
+
if (only === "string") return "string";
|
|
1471
|
+
if (only === "number") return "number";
|
|
1472
|
+
if (only === "boolean") return "boolean";
|
|
1473
|
+
}
|
|
1474
|
+
return void 0;
|
|
1475
|
+
}
|
|
1476
|
+
function convert(node, ctx) {
|
|
1477
|
+
switch (node.kind) {
|
|
1478
|
+
case "string": {
|
|
1479
|
+
const out = { type: "string" };
|
|
1480
|
+
for (const c of node.checks) {
|
|
1481
|
+
if (c.check === "email") out.format = "email";
|
|
1482
|
+
else if (c.check === "url") out.format = "uri";
|
|
1483
|
+
else if (c.check === "uuid") out.format = "uuid";
|
|
1484
|
+
else if (c.check === "min") out.minLength = Number(c.value);
|
|
1485
|
+
else if (c.check === "max") out.maxLength = Number(c.value);
|
|
1486
|
+
else if (c.check === "regex") {
|
|
1487
|
+
const m = /^\/(.*)\/[a-z]*$/.exec(c.pattern);
|
|
1488
|
+
out.pattern = m ? m[1] : c.pattern;
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
return out;
|
|
1492
|
+
}
|
|
1493
|
+
case "number": {
|
|
1494
|
+
const out = { type: "number" };
|
|
1495
|
+
for (const c of node.checks) {
|
|
1496
|
+
if (c.check === "int") out.type = "integer";
|
|
1497
|
+
else if (c.check === "min") out.minimum = Number(c.value);
|
|
1498
|
+
else if (c.check === "max") out.maximum = Number(c.value);
|
|
1499
|
+
else if (c.check === "positive") out.exclusiveMinimum = 0;
|
|
1500
|
+
else if (c.check === "negative") out.exclusiveMaximum = 0;
|
|
1501
|
+
}
|
|
1502
|
+
return out;
|
|
1503
|
+
}
|
|
1504
|
+
case "boolean":
|
|
1505
|
+
return { type: "boolean" };
|
|
1506
|
+
case "date":
|
|
1507
|
+
return { type: "string", format: "date-time" };
|
|
1508
|
+
case "unknown":
|
|
1509
|
+
return node.note ? { description: node.note } : {};
|
|
1510
|
+
case "instanceof":
|
|
1511
|
+
return { type: "object", description: `instanceof ${node.ctor}` };
|
|
1512
|
+
case "enum": {
|
|
1513
|
+
const values = node.literals.map(parseLiteral);
|
|
1514
|
+
const t = literalsType(values);
|
|
1515
|
+
const out = { enum: values };
|
|
1516
|
+
if (t) out.type = t;
|
|
1517
|
+
return out;
|
|
1518
|
+
}
|
|
1519
|
+
case "literal": {
|
|
1520
|
+
const value = parseLiteral(node.raw);
|
|
1521
|
+
const out = { const: value };
|
|
1522
|
+
const t = literalsType([value]);
|
|
1523
|
+
if (t) out.type = t;
|
|
1524
|
+
return out;
|
|
1525
|
+
}
|
|
1526
|
+
case "union": {
|
|
1527
|
+
const options = node.options.map((o) => convert(o, ctx));
|
|
1528
|
+
const out = { oneOf: options };
|
|
1529
|
+
if (node.discriminator) {
|
|
1530
|
+
out.discriminator = { propertyName: node.discriminator };
|
|
1531
|
+
}
|
|
1532
|
+
return out;
|
|
1533
|
+
}
|
|
1534
|
+
case "object": {
|
|
1535
|
+
const properties = {};
|
|
1536
|
+
const required = [];
|
|
1537
|
+
for (const f of node.fields) {
|
|
1538
|
+
if (f.value.kind === "optional") {
|
|
1539
|
+
properties[f.key] = convert(f.value.inner, ctx);
|
|
1540
|
+
} else {
|
|
1541
|
+
properties[f.key] = convert(f.value, ctx);
|
|
1542
|
+
required.push(f.key);
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
const out = { type: "object", properties };
|
|
1546
|
+
if (required.length > 0) out.required = required;
|
|
1547
|
+
out.additionalProperties = node.passthrough;
|
|
1548
|
+
return out;
|
|
1549
|
+
}
|
|
1550
|
+
case "array":
|
|
1551
|
+
return { type: "array", items: convert(node.element, ctx) };
|
|
1552
|
+
case "optional":
|
|
1553
|
+
return widenNullable(convert(node.inner, ctx));
|
|
1554
|
+
case "ref":
|
|
1555
|
+
case "lazyRef":
|
|
1556
|
+
return { $ref: `${ctx.refPrefix}${node.name}` };
|
|
1557
|
+
case "annotated":
|
|
1558
|
+
return convert(node.inner, ctx);
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
function widenNullable(schema) {
|
|
1562
|
+
if (schema.$ref) {
|
|
1563
|
+
return { anyOf: [schema, { type: "null" }] };
|
|
1564
|
+
}
|
|
1565
|
+
if (typeof schema.type === "string") {
|
|
1566
|
+
return { ...schema, type: [schema.type, "null"] };
|
|
1567
|
+
}
|
|
1568
|
+
if (Array.isArray(schema.type)) {
|
|
1569
|
+
return schema.type.includes("null") ? schema : { ...schema, type: [...schema.type, "null"] };
|
|
1570
|
+
}
|
|
1571
|
+
return { anyOf: [schema, { type: "null" }] };
|
|
1572
|
+
}
|
|
1573
|
+
function schemaModuleToJsonSchema(mod, ctx = DEFAULT_CTX) {
|
|
1574
|
+
const named = {};
|
|
1575
|
+
for (const [name, node] of mod.named) {
|
|
1576
|
+
named[name] = convert(node, ctx);
|
|
1577
|
+
}
|
|
1578
|
+
return { root: convert(mod.root, ctx), named };
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
// src/emit/mock-gen-runtime.ts
|
|
1582
|
+
var MOCK_GEN_RUNTIME = `
|
|
1583
|
+
/** mulberry32 \u2014 a tiny, fast, seedable PRNG. \`next()\` returns a float in [0, 1). */
|
|
1584
|
+
function makeRng(seed) {
|
|
1585
|
+
let a = seed >>> 0;
|
|
1586
|
+
return {
|
|
1587
|
+
next() {
|
|
1588
|
+
a |= 0;
|
|
1589
|
+
a = (a + 0x6d2b79f5) | 0;
|
|
1590
|
+
let t = Math.imul(a ^ (a >>> 15), 1 | a);
|
|
1591
|
+
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
|
1592
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
1593
|
+
},
|
|
1594
|
+
};
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
function __pick(rng, items) {
|
|
1598
|
+
return items[Math.floor(rng.next() * items.length)];
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
function __intBetween(rng, min, max) {
|
|
1602
|
+
return Math.floor(rng.next() * (max - min + 1)) + min;
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
const __WORDS = ['lorem', 'ipsum', 'dolor', 'sit', 'amet', 'consectetur', 'adipiscing', 'elit', 'sed', 'tempor'];
|
|
1606
|
+
const __FIRST_NAMES = ['Ada', 'Alan', 'Grace', 'Linus', 'Margaret', 'Dennis'];
|
|
1607
|
+
const __LAST_NAMES = ['Lovelace', 'Turing', 'Hopper', 'Torvalds', 'Hamilton', 'Ritchie'];
|
|
1608
|
+
|
|
1609
|
+
function __fakeWords(rng, count) {
|
|
1610
|
+
let out = [];
|
|
1611
|
+
for (let i = 0; i < count; i++) out.push(__pick(rng, __WORDS));
|
|
1612
|
+
return out.join(' ');
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
function __hex(rng, len) {
|
|
1616
|
+
let s = '';
|
|
1617
|
+
for (let i = 0; i < len; i++) s += Math.floor(rng.next() * 16).toString(16);
|
|
1618
|
+
return s;
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
function __fakeUuid(rng) {
|
|
1622
|
+
return __hex(rng, 8) + '-' + __hex(rng, 4) + '-4' + __hex(rng, 3) + '-' + __pick(rng, ['8', '9', 'a', 'b']) + __hex(rng, 3) + '-' + __hex(rng, 12);
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
function __fakeString(rng, schema) {
|
|
1626
|
+
switch (schema.format) {
|
|
1627
|
+
case 'email':
|
|
1628
|
+
return __pick(rng, __FIRST_NAMES).toLowerCase() + '.' + __pick(rng, __LAST_NAMES).toLowerCase() + '@example.com';
|
|
1629
|
+
case 'uri':
|
|
1630
|
+
case 'url':
|
|
1631
|
+
return 'https://example.com/' + __pick(rng, __WORDS);
|
|
1632
|
+
case 'uuid':
|
|
1633
|
+
return __fakeUuid(rng);
|
|
1634
|
+
case 'date-time':
|
|
1635
|
+
return new Date(Date.UTC(2020, __intBetween(rng, 0, 11), __intBetween(rng, 1, 28))).toISOString();
|
|
1636
|
+
default:
|
|
1637
|
+
return __fakeWords(rng, __intBetween(rng, 1, 3));
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
/** Generate a mock value for a JSON Schema node (depth-capped recursion via $ref). */
|
|
1642
|
+
function generateMock(schema, rng, defs, depth) {
|
|
1643
|
+
defs = defs || {};
|
|
1644
|
+
depth = depth || 0;
|
|
1645
|
+
if (schema.$ref) {
|
|
1646
|
+
const name = schema.$ref.replace('#/components/schemas/', '');
|
|
1647
|
+
const target = defs[name];
|
|
1648
|
+
if (!target || depth > 4) return null;
|
|
1649
|
+
return generateMock(target, rng, defs, depth + 1);
|
|
1650
|
+
}
|
|
1651
|
+
if ('const' in schema) return schema.const;
|
|
1652
|
+
if (schema.enum && schema.enum.length > 0) return __pick(rng, schema.enum);
|
|
1653
|
+
if (schema.oneOf && schema.oneOf.length > 0) return generateMock(__pick(rng, schema.oneOf), rng, defs, depth);
|
|
1654
|
+
if (schema.anyOf && schema.anyOf.length > 0) return generateMock(__pick(rng, schema.anyOf), rng, defs, depth);
|
|
1655
|
+
let type = Array.isArray(schema.type)
|
|
1656
|
+
? (schema.type.filter((t) => t !== 'null')[0] || 'null')
|
|
1657
|
+
: schema.type;
|
|
1658
|
+
switch (type) {
|
|
1659
|
+
case 'string':
|
|
1660
|
+
return __fakeString(rng, schema);
|
|
1661
|
+
case 'integer':
|
|
1662
|
+
return __intBetween(rng, typeof schema.minimum === 'number' ? schema.minimum : 0, typeof schema.maximum === 'number' ? schema.maximum : 1000);
|
|
1663
|
+
case 'number':
|
|
1664
|
+
return __intBetween(rng, typeof schema.minimum === 'number' ? schema.minimum : 0, typeof schema.maximum === 'number' ? schema.maximum : 1000) + Math.round(rng.next() * 100) / 100;
|
|
1665
|
+
case 'boolean':
|
|
1666
|
+
return rng.next() < 0.5;
|
|
1667
|
+
case 'null':
|
|
1668
|
+
return null;
|
|
1669
|
+
case 'array': {
|
|
1670
|
+
const count = depth > 2 ? 0 : __intBetween(rng, 1, 2);
|
|
1671
|
+
const items = schema.items || {};
|
|
1672
|
+
let arr = [];
|
|
1673
|
+
for (let i = 0; i < count; i++) arr.push(generateMock(items, rng, defs, depth + 1));
|
|
1674
|
+
return arr;
|
|
1675
|
+
}
|
|
1676
|
+
case 'object': {
|
|
1677
|
+
const out = {};
|
|
1678
|
+
const props = schema.properties || {};
|
|
1679
|
+
for (const key of Object.keys(props)) out[key] = generateMock(props[key], rng, defs, depth + 1);
|
|
1680
|
+
return out;
|
|
1681
|
+
}
|
|
1682
|
+
default:
|
|
1683
|
+
return {};
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
`.trim();
|
|
1687
|
+
|
|
1688
|
+
// src/emit/emit-mocks.ts
|
|
1689
|
+
var REF_PREFIX = "#/components/schemas/";
|
|
1690
|
+
function toMswPath(path, baseUrl) {
|
|
1691
|
+
return `${baseUrl}${path}`;
|
|
1692
|
+
}
|
|
1693
|
+
function responseSchemaFor(route, defs) {
|
|
1694
|
+
const cs = route.contract.contractSource;
|
|
1695
|
+
if (cs.responseSchema) {
|
|
1696
|
+
const { root, named } = schemaModuleToJsonSchema(cs.responseSchema, { refPrefix: REF_PREFIX });
|
|
1697
|
+
for (const [name, node] of Object.entries(named)) {
|
|
1698
|
+
if (!(name in defs)) defs[name] = node;
|
|
1699
|
+
}
|
|
1700
|
+
return root;
|
|
1701
|
+
}
|
|
1702
|
+
return {};
|
|
1703
|
+
}
|
|
1704
|
+
function buildMocksFile(routes, opts = {}) {
|
|
1705
|
+
const seed = opts.seed ?? 1;
|
|
1706
|
+
const baseUrl = opts.baseUrl ?? "";
|
|
1707
|
+
const contracted = routes.filter((r) => r.contract);
|
|
1708
|
+
const defs = {};
|
|
1709
|
+
const handlers = [];
|
|
1710
|
+
for (const r of contracted) {
|
|
1711
|
+
const schema = responseSchemaFor(r, defs);
|
|
1712
|
+
const method = r.method.toLowerCase();
|
|
1713
|
+
const mswMethod = method === "get" || method === "post" || method === "put" || method === "patch" || method === "delete" ? method : "all";
|
|
1714
|
+
const path = toMswPath(r.path, baseUrl);
|
|
1715
|
+
const cs = r.contract.contractSource;
|
|
1716
|
+
const schemaLiteral = JSON.stringify(schema);
|
|
1717
|
+
const pathLit = JSON.stringify(path);
|
|
1718
|
+
if (cs.stream) {
|
|
1719
|
+
handlers.push(
|
|
1720
|
+
[
|
|
1721
|
+
` // ${r.name} (stream)`,
|
|
1722
|
+
` http.${mswMethod}(${pathLit}, () => {`,
|
|
1723
|
+
` const value = generateMock(${schemaLiteral}, makeRng(SEED), DEFS);`,
|
|
1724
|
+
" const body = `data: ${JSON.stringify(value)}\\n\\n`;",
|
|
1725
|
+
" return new HttpResponse(body, { headers: { 'Content-Type': 'text/event-stream' } });",
|
|
1726
|
+
" }),"
|
|
1727
|
+
].join("\n")
|
|
1728
|
+
);
|
|
1729
|
+
} else {
|
|
1730
|
+
handlers.push(
|
|
1731
|
+
[
|
|
1732
|
+
` // ${r.name}`,
|
|
1733
|
+
` http.${mswMethod}(${pathLit}, () => {`,
|
|
1734
|
+
` const value = generateMock(${schemaLiteral}, makeRng(SEED), DEFS);`,
|
|
1735
|
+
" return HttpResponse.json(value);",
|
|
1736
|
+
" }),"
|
|
1737
|
+
].join("\n")
|
|
1738
|
+
);
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
const lines = [
|
|
1742
|
+
"// Generated by @dudousxd/nestjs-codegen. Do not edit.",
|
|
1743
|
+
"// MSW handlers returning deterministic, schema-shaped mock data.",
|
|
1744
|
+
"/* eslint-disable */",
|
|
1745
|
+
"// @ts-nocheck",
|
|
1746
|
+
"",
|
|
1747
|
+
"import { http, HttpResponse } from 'msw';",
|
|
1748
|
+
"",
|
|
1749
|
+
`const SEED = ${seed};`,
|
|
1750
|
+
"",
|
|
1751
|
+
"// ---------------------------------------------------------------------------",
|
|
1752
|
+
"// Embedded mock-data runtime (mulberry32 PRNG + JSON-Schema value generator).",
|
|
1753
|
+
"// Dependency-free: no @faker-js/faker. Deterministic for a given SEED.",
|
|
1754
|
+
"// ---------------------------------------------------------------------------",
|
|
1755
|
+
MOCK_GEN_RUNTIME,
|
|
1756
|
+
"",
|
|
1757
|
+
"// Shared component schemas referenced by $ref.",
|
|
1758
|
+
`const DEFS = ${JSON.stringify(defs, null, 2)};`,
|
|
1759
|
+
"",
|
|
1760
|
+
"/** MSW request handlers, one per contracted route. */",
|
|
1761
|
+
"export const handlers = [",
|
|
1762
|
+
...handlers,
|
|
1763
|
+
"];",
|
|
1764
|
+
""
|
|
1765
|
+
];
|
|
1766
|
+
return lines.join("\n");
|
|
1767
|
+
}
|
|
1768
|
+
async function emitMocks(routes, outDir, opts = {}) {
|
|
1412
1769
|
await (0, import_promises7.mkdir)(outDir, { recursive: true });
|
|
1770
|
+
const content = buildMocksFile(routes, opts);
|
|
1771
|
+
const fileName = opts.fileName ?? "mocks.ts";
|
|
1772
|
+
await (0, import_promises7.writeFile)((0, import_node_path8.join)(outDir, fileName), content, "utf8");
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
// src/emit/emit-openapi.ts
|
|
1776
|
+
var import_promises8 = require("fs/promises");
|
|
1777
|
+
var import_node_path9 = require("path");
|
|
1778
|
+
var REF_PREFIX2 = "#/components/schemas/";
|
|
1779
|
+
function toOpenApiPath(path) {
|
|
1780
|
+
return path.replace(/:([^/]+)/g, "{$1}");
|
|
1781
|
+
}
|
|
1782
|
+
function positionSchema(schema, tsType, components) {
|
|
1783
|
+
if (schema) {
|
|
1784
|
+
const { root, named } = schemaModuleToJsonSchema(schema, { refPrefix: REF_PREFIX2 });
|
|
1785
|
+
for (const [name, node] of Object.entries(named)) {
|
|
1786
|
+
if (!(name in components)) components[name] = node;
|
|
1787
|
+
}
|
|
1788
|
+
return root;
|
|
1789
|
+
}
|
|
1790
|
+
return tsType ? { description: tsType } : {};
|
|
1791
|
+
}
|
|
1792
|
+
function buildParameters(route) {
|
|
1793
|
+
const params = [];
|
|
1794
|
+
for (const p of route.params) {
|
|
1795
|
+
if (p.source === "path") {
|
|
1796
|
+
params.push({
|
|
1797
|
+
name: p.name,
|
|
1798
|
+
in: "path",
|
|
1799
|
+
required: true,
|
|
1800
|
+
schema: { type: "string" }
|
|
1801
|
+
});
|
|
1802
|
+
} else if (p.source === "query") {
|
|
1803
|
+
params.push({
|
|
1804
|
+
name: p.name,
|
|
1805
|
+
in: "query",
|
|
1806
|
+
required: false,
|
|
1807
|
+
schema: { type: "string" }
|
|
1808
|
+
});
|
|
1809
|
+
} else if (p.source === "header") {
|
|
1810
|
+
params.push({
|
|
1811
|
+
name: p.name,
|
|
1812
|
+
in: "header",
|
|
1813
|
+
required: false,
|
|
1814
|
+
schema: { type: "string" }
|
|
1815
|
+
});
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
return params;
|
|
1819
|
+
}
|
|
1820
|
+
function buildResponses(cs, components) {
|
|
1821
|
+
const responses = {};
|
|
1822
|
+
const successSchema = positionSchema(
|
|
1823
|
+
// Prefer rich response IR when present; otherwise fall back to the TS type.
|
|
1824
|
+
cs.responseSchema ?? null,
|
|
1825
|
+
cs.response,
|
|
1826
|
+
components
|
|
1827
|
+
);
|
|
1828
|
+
const successContentType = cs.stream ? "text/event-stream" : "application/json";
|
|
1829
|
+
responses["200"] = {
|
|
1830
|
+
description: cs.stream ? "Server-sent event stream" : "Successful response",
|
|
1831
|
+
content: { [successContentType]: { schema: successSchema } }
|
|
1832
|
+
};
|
|
1833
|
+
const errorSchema = positionSchema(null, cs.error ?? null, components);
|
|
1834
|
+
const errorBody = {
|
|
1835
|
+
description: "Error response",
|
|
1836
|
+
content: { "application/json": { schema: errorSchema } }
|
|
1837
|
+
};
|
|
1838
|
+
if (cs.error || cs.errorRef) {
|
|
1839
|
+
responses["400"] = errorBody;
|
|
1840
|
+
responses.default = errorBody;
|
|
1841
|
+
} else {
|
|
1842
|
+
responses.default = {
|
|
1843
|
+
description: "Error response",
|
|
1844
|
+
content: { "application/json": { schema: {} } }
|
|
1845
|
+
};
|
|
1846
|
+
}
|
|
1847
|
+
return responses;
|
|
1848
|
+
}
|
|
1849
|
+
function buildOperation(route, components) {
|
|
1850
|
+
const cs = route.contract.contractSource;
|
|
1851
|
+
const op = {
|
|
1852
|
+
operationId: route.name,
|
|
1853
|
+
parameters: buildParameters(route),
|
|
1854
|
+
responses: buildResponses(cs, components)
|
|
1855
|
+
};
|
|
1856
|
+
const method = route.method.toUpperCase();
|
|
1857
|
+
const hasBody = method !== "GET" && method !== "HEAD" && method !== "DELETE";
|
|
1858
|
+
if (hasBody && (cs.bodySchema || cs.body)) {
|
|
1859
|
+
const bodySchema = positionSchema(cs.bodySchema, cs.body, components);
|
|
1860
|
+
op.requestBody = {
|
|
1861
|
+
required: true,
|
|
1862
|
+
content: { "application/json": { schema: bodySchema } }
|
|
1863
|
+
};
|
|
1864
|
+
}
|
|
1865
|
+
return op;
|
|
1866
|
+
}
|
|
1867
|
+
function buildOpenApiSpec(routes, opts = {}) {
|
|
1868
|
+
const components = {};
|
|
1869
|
+
const paths = {};
|
|
1870
|
+
for (const route of routes) {
|
|
1871
|
+
if (!route.contract) continue;
|
|
1872
|
+
const oaPath = toOpenApiPath(route.path);
|
|
1873
|
+
const method = route.method.toLowerCase();
|
|
1874
|
+
let pathItem = paths[oaPath];
|
|
1875
|
+
if (!pathItem) {
|
|
1876
|
+
pathItem = {};
|
|
1877
|
+
paths[oaPath] = pathItem;
|
|
1878
|
+
}
|
|
1879
|
+
pathItem[method] = buildOperation(route, components);
|
|
1880
|
+
}
|
|
1881
|
+
const info = opts.info ?? {};
|
|
1882
|
+
const doc = {
|
|
1883
|
+
openapi: "3.1.0",
|
|
1884
|
+
info: {
|
|
1885
|
+
title: info.title ?? "NestJS API",
|
|
1886
|
+
version: info.version ?? "1.0.0",
|
|
1887
|
+
...info.description ? { description: info.description } : {}
|
|
1888
|
+
},
|
|
1889
|
+
paths,
|
|
1890
|
+
components: { schemas: components }
|
|
1891
|
+
};
|
|
1892
|
+
return doc;
|
|
1893
|
+
}
|
|
1894
|
+
async function emitOpenApi(routes, outDir, opts = {}) {
|
|
1895
|
+
await (0, import_promises8.mkdir)(outDir, { recursive: true });
|
|
1896
|
+
const doc = buildOpenApiSpec(routes, opts);
|
|
1897
|
+
const fileName = opts.fileName ?? "openapi.json";
|
|
1898
|
+
await (0, import_promises8.writeFile)((0, import_node_path9.join)(outDir, fileName), `${JSON.stringify(doc, null, 2)}
|
|
1899
|
+
`, "utf8");
|
|
1900
|
+
}
|
|
1901
|
+
|
|
1902
|
+
// src/emit/emit-pages.ts
|
|
1903
|
+
var import_promises9 = require("fs/promises");
|
|
1904
|
+
var import_node_path10 = require("path");
|
|
1905
|
+
async function emitPages(pages, outDir, _options = {}) {
|
|
1906
|
+
await (0, import_promises9.mkdir)(outDir, { recursive: true });
|
|
1413
1907
|
const pageNameUnion = pages.length > 0 ? pages.map((p) => JSON.stringify(p.name)).join(" | ") : "never";
|
|
1414
1908
|
const augBody = pages.map((p) => {
|
|
1415
1909
|
const key = needsQuotes(p.name) ? JSON.stringify(p.name) : p.name;
|
|
@@ -1428,7 +1922,7 @@ ${augBody}
|
|
|
1428
1922
|
}
|
|
1429
1923
|
${sharedPropsBlock}}
|
|
1430
1924
|
`;
|
|
1431
|
-
await (0,
|
|
1925
|
+
await (0, import_promises9.writeFile)((0, import_node_path10.join)(outDir, "pages.d.ts"), content, "utf8");
|
|
1432
1926
|
}
|
|
1433
1927
|
function buildSharedPropsBlock(sharedProps) {
|
|
1434
1928
|
if (!sharedProps) return "";
|
|
@@ -1447,7 +1941,7 @@ ${propsBody}
|
|
|
1447
1941
|
`;
|
|
1448
1942
|
}
|
|
1449
1943
|
function buildAugmentationType(page, outDir) {
|
|
1450
|
-
let importPath = (0,
|
|
1944
|
+
let importPath = (0, import_node_path10.relative)(outDir, page.absolutePath).replace(/\.(tsx?|vue|svelte)$/, "");
|
|
1451
1945
|
if (!importPath.startsWith(".")) {
|
|
1452
1946
|
importPath = `./${importPath}`;
|
|
1453
1947
|
}
|
|
@@ -1458,12 +1952,12 @@ function needsQuotes(name) {
|
|
|
1458
1952
|
}
|
|
1459
1953
|
|
|
1460
1954
|
// src/emit/emit-routes.ts
|
|
1461
|
-
var
|
|
1462
|
-
var
|
|
1955
|
+
var import_promises10 = require("fs/promises");
|
|
1956
|
+
var import_node_path11 = require("path");
|
|
1463
1957
|
async function emitRoutes(routes, outDir) {
|
|
1464
|
-
await (0,
|
|
1958
|
+
await (0, import_promises10.mkdir)(outDir, { recursive: true });
|
|
1465
1959
|
const content = buildRoutesFile(routes);
|
|
1466
|
-
await (0,
|
|
1960
|
+
await (0, import_promises10.writeFile)((0, import_node_path11.join)(outDir, "routes.ts"), content, "utf8");
|
|
1467
1961
|
}
|
|
1468
1962
|
function buildRoutesFile(routes) {
|
|
1469
1963
|
if (routes.length === 0) {
|
|
@@ -1611,24 +2105,41 @@ async function generate(config, inputRoutes = []) {
|
|
|
1611
2105
|
});
|
|
1612
2106
|
}
|
|
1613
2107
|
const hasForms = await emitForms(routes, config.codegen.outDir, config.forms, config.validation);
|
|
2108
|
+
if (hasContracts && config.openapi.enabled) {
|
|
2109
|
+
await emitOpenApi(routes, config.codegen.outDir, {
|
|
2110
|
+
fileName: config.openapi.fileName,
|
|
2111
|
+
info: {
|
|
2112
|
+
title: config.openapi.title,
|
|
2113
|
+
version: config.openapi.version,
|
|
2114
|
+
...config.openapi.description ? { description: config.openapi.description } : {}
|
|
2115
|
+
}
|
|
2116
|
+
});
|
|
2117
|
+
}
|
|
2118
|
+
if (hasContracts && config.mocks.enabled) {
|
|
2119
|
+
await emitMocks(routes, config.codegen.outDir, {
|
|
2120
|
+
fileName: config.mocks.fileName,
|
|
2121
|
+
seed: config.mocks.seed,
|
|
2122
|
+
baseUrl: config.mocks.baseUrl
|
|
2123
|
+
});
|
|
2124
|
+
}
|
|
1614
2125
|
await emitIndex(config.codegen.outDir, hasContracts, hasForms);
|
|
1615
2126
|
if (extensions.length > 0) {
|
|
1616
2127
|
const extraFiles = await collectEmittedFiles(extensions, ctx);
|
|
1617
2128
|
for (const file of extraFiles) {
|
|
1618
|
-
const dest = (0,
|
|
1619
|
-
await (0,
|
|
1620
|
-
await (0,
|
|
2129
|
+
const dest = (0, import_node_path12.join)(config.codegen.outDir, file.path);
|
|
2130
|
+
await (0, import_promises11.mkdir)((0, import_node_path12.dirname)(dest), { recursive: true });
|
|
2131
|
+
await (0, import_promises11.writeFile)(dest, file.contents, "utf8");
|
|
1621
2132
|
}
|
|
1622
2133
|
}
|
|
1623
2134
|
}
|
|
1624
2135
|
|
|
1625
2136
|
// src/watch/watcher.ts
|
|
1626
|
-
var
|
|
1627
|
-
var
|
|
2137
|
+
var import_promises14 = require("fs/promises");
|
|
2138
|
+
var import_node_path16 = require("path");
|
|
1628
2139
|
var import_chokidar = __toESM(require("chokidar"), 1);
|
|
1629
2140
|
|
|
1630
2141
|
// src/discovery/contracts-fast.ts
|
|
1631
|
-
var
|
|
2142
|
+
var import_node_path14 = require("path");
|
|
1632
2143
|
var import_fast_glob2 = __toESM(require("fast-glob"), 1);
|
|
1633
2144
|
var import_ts_morph9 = require("ts-morph");
|
|
1634
2145
|
|
|
@@ -1640,7 +2151,7 @@ var import_ts_morph4 = require("ts-morph");
|
|
|
1640
2151
|
|
|
1641
2152
|
// src/discovery/type-ref-resolution.ts
|
|
1642
2153
|
var import_node_fs = require("fs");
|
|
1643
|
-
var
|
|
2154
|
+
var import_node_path13 = require("path");
|
|
1644
2155
|
var import_ts_morph3 = require("ts-morph");
|
|
1645
2156
|
var _EMPTY_CTX = { projectRoot: "", tsconfigPaths: null };
|
|
1646
2157
|
var _ctxByProject = /* @__PURE__ */ new WeakMap();
|
|
@@ -1692,12 +2203,12 @@ function findTypeInFile(name, file) {
|
|
|
1692
2203
|
}
|
|
1693
2204
|
function resolveModuleSpecifier(moduleSpecifier, sourceFile, project) {
|
|
1694
2205
|
if (moduleSpecifier.startsWith(".")) {
|
|
1695
|
-
const dir = (0,
|
|
2206
|
+
const dir = (0, import_node_path13.dirname)(sourceFile.getFilePath());
|
|
1696
2207
|
const noExt = moduleSpecifier.replace(/\.(js|ts)$/, "");
|
|
1697
2208
|
return [
|
|
1698
|
-
(0,
|
|
1699
|
-
(0,
|
|
1700
|
-
(0,
|
|
2209
|
+
(0, import_node_path13.resolve)(dir, `${noExt}.ts`),
|
|
2210
|
+
(0, import_node_path13.resolve)(dir, `${moduleSpecifier}.ts`),
|
|
2211
|
+
(0, import_node_path13.resolve)(dir, moduleSpecifier, "index.ts")
|
|
1701
2212
|
];
|
|
1702
2213
|
}
|
|
1703
2214
|
const ctx = _ctxFor(project);
|
|
@@ -1718,8 +2229,8 @@ function resolveModuleSpecifier(moduleSpecifier, sourceFile, project) {
|
|
|
1718
2229
|
const rest = moduleSpecifier.slice(prefix.length);
|
|
1719
2230
|
const candidates = [];
|
|
1720
2231
|
for (const mapping of mappings) {
|
|
1721
|
-
const resolved = (0,
|
|
1722
|
-
candidates.push(`${resolved}.ts`, (0,
|
|
2232
|
+
const resolved = (0, import_node_path13.resolve)(baseUrl, mapping.replace("*", rest));
|
|
2233
|
+
candidates.push(`${resolved}.ts`, (0, import_node_path13.resolve)(resolved, "index.ts"));
|
|
1723
2234
|
}
|
|
1724
2235
|
dbg(" resolved candidates:", candidates);
|
|
1725
2236
|
return candidates;
|
|
@@ -1800,10 +2311,85 @@ function followModuleForType(name, moduleSpecifier, fromFile, project, seen) {
|
|
|
1800
2311
|
}
|
|
1801
2312
|
return null;
|
|
1802
2313
|
}
|
|
2314
|
+
function resolveImportedVariable(name, sourceFile, project) {
|
|
2315
|
+
const local = sourceFile.getVariableDeclaration(name);
|
|
2316
|
+
if (local) return { decl: local, file: sourceFile };
|
|
2317
|
+
return resolveVariableViaImports(name, sourceFile, project, /* @__PURE__ */ new Set());
|
|
2318
|
+
}
|
|
2319
|
+
function resolveVariableViaImports(name, sourceFile, project, seen) {
|
|
2320
|
+
for (const importDecl of sourceFile.getImportDeclarations()) {
|
|
2321
|
+
const namedImport = importDecl.getNamedImports().find((n) => (n.getAliasNode()?.getText() ?? n.getName()) === name);
|
|
2322
|
+
if (!namedImport) continue;
|
|
2323
|
+
const sourceName = namedImport.getName();
|
|
2324
|
+
const moduleSpecifier = importDecl.getModuleSpecifierValue();
|
|
2325
|
+
const found = followModuleForVariable(sourceName, moduleSpecifier, sourceFile, project, seen);
|
|
2326
|
+
if (found) return found;
|
|
2327
|
+
}
|
|
2328
|
+
return null;
|
|
2329
|
+
}
|
|
2330
|
+
function followModuleForVariable(name, moduleSpecifier, fromFile, project, seen) {
|
|
2331
|
+
const candidates = resolveModuleSpecifier(moduleSpecifier, fromFile, project);
|
|
2332
|
+
for (const candidate of candidates) {
|
|
2333
|
+
let importedFile = project.getSourceFile(candidate);
|
|
2334
|
+
if (!importedFile) {
|
|
2335
|
+
try {
|
|
2336
|
+
importedFile = project.addSourceFileAtPath(candidate);
|
|
2337
|
+
} catch {
|
|
2338
|
+
continue;
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2341
|
+
const found = resolveVariableInFile(name, importedFile, project, seen);
|
|
2342
|
+
if (found) return found;
|
|
2343
|
+
}
|
|
2344
|
+
return null;
|
|
2345
|
+
}
|
|
2346
|
+
function resolveVariableInFile(name, file, project, seen) {
|
|
2347
|
+
const filePath = file.getFilePath();
|
|
2348
|
+
if (seen.has(filePath)) return null;
|
|
2349
|
+
seen.add(filePath);
|
|
2350
|
+
const local = file.getVariableDeclaration(name);
|
|
2351
|
+
if (local) return { decl: local, file };
|
|
2352
|
+
for (const exportDecl of file.getExportDeclarations()) {
|
|
2353
|
+
const moduleSpecifier = exportDecl.getModuleSpecifierValue();
|
|
2354
|
+
const namedExports = exportDecl.getNamedExports();
|
|
2355
|
+
if (moduleSpecifier) {
|
|
2356
|
+
const hasStar = namedExports.length === 0;
|
|
2357
|
+
const reExport2 = namedExports.find(
|
|
2358
|
+
(n) => (n.getAliasNode()?.getText() ?? n.getName()) === name
|
|
2359
|
+
);
|
|
2360
|
+
if (!hasStar && !reExport2) continue;
|
|
2361
|
+
const sourceName2 = hasStar ? name : reExport2?.getName() ?? name;
|
|
2362
|
+
const found = followModuleForVariable(sourceName2, moduleSpecifier, file, project, seen);
|
|
2363
|
+
if (found) return found;
|
|
2364
|
+
continue;
|
|
2365
|
+
}
|
|
2366
|
+
const reExport = namedExports.find(
|
|
2367
|
+
(n) => (n.getAliasNode()?.getText() ?? n.getName()) === name
|
|
2368
|
+
);
|
|
2369
|
+
if (!reExport) continue;
|
|
2370
|
+
const sourceName = reExport.getName();
|
|
2371
|
+
const viaImports = resolveVariableViaImports(sourceName, file, project, seen);
|
|
2372
|
+
if (viaImports) return viaImports;
|
|
2373
|
+
}
|
|
2374
|
+
return null;
|
|
2375
|
+
}
|
|
2376
|
+
var _findTypeCache = /* @__PURE__ */ new WeakMap();
|
|
2377
|
+
function clearTypeResolutionCaches(project) {
|
|
2378
|
+
_findTypeCache.delete(project);
|
|
2379
|
+
_resolveNamedRefCache.delete(project);
|
|
2380
|
+
}
|
|
1803
2381
|
function findType(name, sourceFile, project) {
|
|
2382
|
+
let byKey = _findTypeCache.get(project);
|
|
2383
|
+
if (byKey === void 0) {
|
|
2384
|
+
byKey = /* @__PURE__ */ new Map();
|
|
2385
|
+
_findTypeCache.set(project, byKey);
|
|
2386
|
+
}
|
|
2387
|
+
const key = `${sourceFile.getFilePath()}\0${name}`;
|
|
2388
|
+
if (byKey.has(key)) return byKey.get(key) ?? null;
|
|
1804
2389
|
const local = findTypeInFile(name, sourceFile);
|
|
1805
|
-
|
|
1806
|
-
|
|
2390
|
+
const result = local ?? resolveImportedType(name, sourceFile, project);
|
|
2391
|
+
byKey.set(key, result);
|
|
2392
|
+
return result;
|
|
1807
2393
|
}
|
|
1808
2394
|
var _NON_REF_NAMES = /* @__PURE__ */ new Set(["string", "number", "boolean", "void", "unknown", "any", "Date"]);
|
|
1809
2395
|
function _localDeclForKinds(name, file, kinds) {
|
|
@@ -1840,6 +2426,26 @@ function resolveTypeRef(nodeOrName, sourceFile, project, opts) {
|
|
|
1840
2426
|
if (_NON_REF_NAMES.has(refName)) return null;
|
|
1841
2427
|
name = refName;
|
|
1842
2428
|
}
|
|
2429
|
+
return _resolveNamedRef(name, sourceFile, project, opts);
|
|
2430
|
+
}
|
|
2431
|
+
var _resolveNamedRefCache = /* @__PURE__ */ new WeakMap();
|
|
2432
|
+
function _resolveNamedRef(name, sourceFile, project, opts) {
|
|
2433
|
+
let byKey = _resolveNamedRefCache.get(project);
|
|
2434
|
+
if (byKey === void 0) {
|
|
2435
|
+
byKey = /* @__PURE__ */ new Map();
|
|
2436
|
+
_resolveNamedRefCache.set(project, byKey);
|
|
2437
|
+
}
|
|
2438
|
+
const kindsKey = [...opts.kinds].sort().join(",");
|
|
2439
|
+
const key = `${sourceFile.getFilePath()}\0${name}\0${kindsKey}\0${opts.allowBareSpecifier ? 1 : 0}`;
|
|
2440
|
+
if (byKey.has(key)) {
|
|
2441
|
+
const cached = byKey.get(key) ?? null;
|
|
2442
|
+
return cached ? { ...cached } : null;
|
|
2443
|
+
}
|
|
2444
|
+
const computed = _computeNamedRef(name, sourceFile, project, opts);
|
|
2445
|
+
byKey.set(key, computed);
|
|
2446
|
+
return computed ? { ...computed } : null;
|
|
2447
|
+
}
|
|
2448
|
+
function _computeNamedRef(name, sourceFile, project, opts) {
|
|
1843
2449
|
if (_localDeclForKinds(name, sourceFile, opts.kinds)) {
|
|
1844
2450
|
return { name, filePath: sourceFile.getFilePath() };
|
|
1845
2451
|
}
|
|
@@ -1914,7 +2520,8 @@ function extractSchemaFromDto(classDecl, sourceFile, project) {
|
|
|
1914
2520
|
emittedClasses: /* @__PURE__ */ new Map(),
|
|
1915
2521
|
visiting: /* @__PURE__ */ new Set(),
|
|
1916
2522
|
recursiveSchemas: /* @__PURE__ */ new Set(),
|
|
1917
|
-
depth: 0
|
|
2523
|
+
depth: 0,
|
|
2524
|
+
typeBindings: /* @__PURE__ */ new Map()
|
|
1918
2525
|
};
|
|
1919
2526
|
const root = buildObject(classDecl, sourceFile, ctx);
|
|
1920
2527
|
return { root, named: ctx.named, warnings: ctx.warnings, recursive: ctx.recursiveSchemas };
|
|
@@ -1938,11 +2545,34 @@ function buildProperty(prop, classFile, ctx) {
|
|
|
1938
2545
|
const typeNode = prop.getTypeNode();
|
|
1939
2546
|
const typeText = typeNode?.getText() ?? "unknown";
|
|
1940
2547
|
const isArrayType = !!typeNode && import_ts_morph4.Node.isArrayTypeNode(typeNode);
|
|
2548
|
+
const discriminator = resolveDiscriminator(dec("Type"));
|
|
2549
|
+
if (discriminator) {
|
|
2550
|
+
const options = discriminator.subTypes.map(
|
|
2551
|
+
(name) => buildNestedReference(name, classFile, ctx)
|
|
2552
|
+
);
|
|
2553
|
+
const unionNode = {
|
|
2554
|
+
kind: "union",
|
|
2555
|
+
options,
|
|
2556
|
+
discriminator: discriminator.property
|
|
2557
|
+
};
|
|
2558
|
+
const wrapArray = has("IsArray") || isArrayType;
|
|
2559
|
+
const node2 = wrapArray ? { kind: "array", element: unionNode } : unionNode;
|
|
2560
|
+
return applyPresence(node2, decorators);
|
|
2561
|
+
}
|
|
2562
|
+
const propTypeParam = singularClassName(typeText);
|
|
2563
|
+
if (propTypeParam && ctx.typeBindings.has(propTypeParam)) {
|
|
2564
|
+
const bound = ctx.typeBindings.get(propTypeParam);
|
|
2565
|
+
const childNode = buildNestedReference(bound, classFile, ctx);
|
|
2566
|
+
const wrapArray = has("IsArray") || isArrayType;
|
|
2567
|
+
const node2 = wrapArray ? { kind: "array", element: childNode } : childNode;
|
|
2568
|
+
return applyPresence(node2, decorators);
|
|
2569
|
+
}
|
|
1941
2570
|
const typeRefName = resolveTypeFactoryName(dec("Type"));
|
|
1942
2571
|
if (has("ValidateNested") || typeRefName) {
|
|
2572
|
+
const typeArgs = genericTypeArgNames(typeNode);
|
|
1943
2573
|
const childName = typeRefName ?? singularClassName(typeText);
|
|
1944
2574
|
if (childName) {
|
|
1945
|
-
const childNode = buildNestedReference(childName, classFile, ctx);
|
|
2575
|
+
const childNode = buildNestedReference(childName, classFile, ctx, typeArgs);
|
|
1946
2576
|
const wrapArray = has("IsArray") || isArrayType;
|
|
1947
2577
|
const node2 = wrapArray ? { kind: "array", element: childNode } : childNode;
|
|
1948
2578
|
return applyPresence(node2, decorators);
|
|
@@ -2067,10 +2697,12 @@ function baseFromType(typeText, isArrayType) {
|
|
|
2067
2697
|
return { kind: "unknown" };
|
|
2068
2698
|
}
|
|
2069
2699
|
}
|
|
2070
|
-
function buildNestedReference(className, fromFile, ctx) {
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2700
|
+
function buildNestedReference(className, fromFile, ctx, typeArgs = []) {
|
|
2701
|
+
const cacheKey = typeArgs.length > 0 ? `${className}<${typeArgs.join(",")}>` : className;
|
|
2702
|
+
const schemaBase = typeArgs.length > 0 ? `${className}Of${typeArgs.join("")}` : className;
|
|
2703
|
+
if (ctx.visiting.has(cacheKey)) {
|
|
2704
|
+
const reserved = ctx.emittedClasses.get(cacheKey) ?? aliasFor(schemaBase, ctx);
|
|
2705
|
+
ctx.emittedClasses.set(cacheKey, reserved);
|
|
2074
2706
|
ctx.recursiveSchemas.add(reserved);
|
|
2075
2707
|
if (!ctx.warnedDecorators.has(`recursive:${reserved}`)) {
|
|
2076
2708
|
ctx.warnedDecorators.add(`recursive:${reserved}`);
|
|
@@ -2089,19 +2721,27 @@ function buildNestedReference(className, fromFile, ctx) {
|
|
|
2089
2721
|
}
|
|
2090
2722
|
return { kind: "unknown", note: "nesting too deep \u2014 not expanded" };
|
|
2091
2723
|
}
|
|
2092
|
-
const existing = ctx.emittedClasses.get(
|
|
2724
|
+
const existing = ctx.emittedClasses.get(cacheKey);
|
|
2093
2725
|
if (existing) return { kind: "ref", name: existing };
|
|
2094
|
-
const schemaName = aliasFor(
|
|
2726
|
+
const schemaName = aliasFor(schemaBase, ctx);
|
|
2095
2727
|
const resolved = findType(className, fromFile, ctx.project);
|
|
2096
2728
|
if (!resolved || resolved.kind !== "class") {
|
|
2097
2729
|
return { kind: "object", fields: [], passthrough: true };
|
|
2098
2730
|
}
|
|
2099
|
-
|
|
2100
|
-
|
|
2731
|
+
const params = resolved.decl.getTypeParameters().map((p) => p.getName());
|
|
2732
|
+
const newBindings = [];
|
|
2733
|
+
params.forEach((param, i) => {
|
|
2734
|
+
const arg = typeArgs[i];
|
|
2735
|
+
if (arg) newBindings.push([param, arg]);
|
|
2736
|
+
});
|
|
2737
|
+
for (const [k, v] of newBindings) ctx.typeBindings.set(k, v);
|
|
2738
|
+
ctx.emittedClasses.set(cacheKey, schemaName);
|
|
2739
|
+
ctx.visiting.add(cacheKey);
|
|
2101
2740
|
ctx.depth += 1;
|
|
2102
2741
|
const childNode = buildObject(resolved.decl, resolved.file, ctx);
|
|
2103
2742
|
ctx.depth -= 1;
|
|
2104
|
-
ctx.visiting.delete(
|
|
2743
|
+
ctx.visiting.delete(cacheKey);
|
|
2744
|
+
for (const [k] of newBindings) ctx.typeBindings.delete(k);
|
|
2105
2745
|
ctx.named.set(schemaName, childNode);
|
|
2106
2746
|
return { kind: "ref", name: schemaName };
|
|
2107
2747
|
}
|
|
@@ -2148,6 +2788,39 @@ function messageRaw(decorator) {
|
|
|
2148
2788
|
}
|
|
2149
2789
|
return void 0;
|
|
2150
2790
|
}
|
|
2791
|
+
function resolveDiscriminator(decorator) {
|
|
2792
|
+
const optsArg = decorator?.getArguments()[1];
|
|
2793
|
+
if (!optsArg || !import_ts_morph4.Node.isObjectLiteralExpression(optsArg)) return null;
|
|
2794
|
+
let discProp;
|
|
2795
|
+
for (const prop of optsArg.getProperties()) {
|
|
2796
|
+
if (import_ts_morph4.Node.isPropertyAssignment(prop) && prop.getName() === "discriminator") {
|
|
2797
|
+
discProp = prop.getInitializer();
|
|
2798
|
+
}
|
|
2799
|
+
}
|
|
2800
|
+
if (!discProp || !import_ts_morph4.Node.isObjectLiteralExpression(discProp)) return null;
|
|
2801
|
+
let property = null;
|
|
2802
|
+
const subTypes = [];
|
|
2803
|
+
for (const prop of discProp.getProperties()) {
|
|
2804
|
+
if (!import_ts_morph4.Node.isPropertyAssignment(prop)) continue;
|
|
2805
|
+
const name = prop.getName();
|
|
2806
|
+
const init = prop.getInitializer();
|
|
2807
|
+
if (!init) continue;
|
|
2808
|
+
if (name === "property" && import_ts_morph4.Node.isStringLiteral(init)) {
|
|
2809
|
+
property = init.getLiteralValue();
|
|
2810
|
+
} else if (name === "subTypes" && import_ts_morph4.Node.isArrayLiteralExpression(init)) {
|
|
2811
|
+
for (const el of init.getElements()) {
|
|
2812
|
+
if (!import_ts_morph4.Node.isObjectLiteralExpression(el)) continue;
|
|
2813
|
+
for (const p of el.getProperties()) {
|
|
2814
|
+
if (!import_ts_morph4.Node.isPropertyAssignment(p) || p.getName() !== "name") continue;
|
|
2815
|
+
const nameInit = p.getInitializer();
|
|
2816
|
+
if (nameInit && import_ts_morph4.Node.isIdentifier(nameInit)) subTypes.push(nameInit.getText());
|
|
2817
|
+
}
|
|
2818
|
+
}
|
|
2819
|
+
}
|
|
2820
|
+
}
|
|
2821
|
+
if (!property || subTypes.length === 0) return null;
|
|
2822
|
+
return { property, subTypes };
|
|
2823
|
+
}
|
|
2151
2824
|
function resolveTypeFactoryName(decorator) {
|
|
2152
2825
|
const arg = firstArg(decorator);
|
|
2153
2826
|
if (!arg) return null;
|
|
@@ -2161,6 +2834,17 @@ function singularClassName(typeText) {
|
|
|
2161
2834
|
const inner = typeText.endsWith("[]") ? typeText.slice(0, -2).trim() : typeText;
|
|
2162
2835
|
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(inner) ? inner : null;
|
|
2163
2836
|
}
|
|
2837
|
+
function genericTypeArgNames(typeNode) {
|
|
2838
|
+
if (!typeNode || !import_ts_morph4.Node.isTypeReference(typeNode)) return [];
|
|
2839
|
+
const names = [];
|
|
2840
|
+
for (const arg of typeNode.getTypeArguments()) {
|
|
2841
|
+
if (!import_ts_morph4.Node.isTypeReference(arg)) return [];
|
|
2842
|
+
const tn = arg.getTypeName();
|
|
2843
|
+
if (!import_ts_morph4.Node.isIdentifier(tn)) return [];
|
|
2844
|
+
names.push(tn.getText());
|
|
2845
|
+
}
|
|
2846
|
+
return names;
|
|
2847
|
+
}
|
|
2164
2848
|
function enumSchemaFromDecorator(decorator, classFile, ctx) {
|
|
2165
2849
|
const arg = firstArg(decorator);
|
|
2166
2850
|
if (!arg) return null;
|
|
@@ -2214,17 +2898,34 @@ var import_ts_morph6 = require("ts-morph");
|
|
|
2214
2898
|
var import_ts_morph5 = require("ts-morph");
|
|
2215
2899
|
|
|
2216
2900
|
// src/discovery/enum-resolution.ts
|
|
2901
|
+
var _enumCache = /* @__PURE__ */ new WeakMap();
|
|
2902
|
+
function clearEnumCache(project) {
|
|
2903
|
+
_enumCache.delete(project);
|
|
2904
|
+
}
|
|
2217
2905
|
function resolveEnumValues(name, sourceFile, project) {
|
|
2906
|
+
let byKey = _enumCache.get(project);
|
|
2907
|
+
if (byKey === void 0) {
|
|
2908
|
+
byKey = /* @__PURE__ */ new Map();
|
|
2909
|
+
_enumCache.set(project, byKey);
|
|
2910
|
+
}
|
|
2911
|
+
const key = `${sourceFile.getFilePath()}\0${name}`;
|
|
2912
|
+
if (byKey.has(key)) {
|
|
2913
|
+
const cached = byKey.get(key) ?? null;
|
|
2914
|
+
return cached ? { values: [...cached.values], numeric: cached.numeric } : null;
|
|
2915
|
+
}
|
|
2218
2916
|
const resolved = findType(name, sourceFile, project);
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
const
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2917
|
+
let result = null;
|
|
2918
|
+
if (resolved && resolved.kind === "enum") {
|
|
2919
|
+
let numeric = true;
|
|
2920
|
+
const values = resolved.members.map((m) => {
|
|
2921
|
+
const parsed = JSON.parse(m);
|
|
2922
|
+
if (typeof parsed === "string") numeric = false;
|
|
2923
|
+
return String(parsed);
|
|
2924
|
+
});
|
|
2925
|
+
if (values.length > 0) result = { values, numeric };
|
|
2926
|
+
}
|
|
2927
|
+
byKey.set(key, result);
|
|
2928
|
+
return result ? { values: [...result.values], numeric: result.numeric } : null;
|
|
2228
2929
|
}
|
|
2229
2930
|
|
|
2230
2931
|
// src/discovery/filter-field-types.ts
|
|
@@ -2669,24 +3370,26 @@ var PASSTHROUGH_UTILITY = /* @__PURE__ */ new Set([
|
|
|
2669
3370
|
"Map",
|
|
2670
3371
|
"Set"
|
|
2671
3372
|
]);
|
|
2672
|
-
function resolveTypeNodeToString(typeNode, sourceFile, project, depth) {
|
|
3373
|
+
function resolveTypeNodeToString(typeNode, sourceFile, project, depth, subst = /* @__PURE__ */ new Map()) {
|
|
2673
3374
|
if (depth <= 0) return "unknown";
|
|
2674
3375
|
if (import_ts_morph7.Node.isArrayTypeNode(typeNode)) {
|
|
2675
3376
|
const elementType = typeNode.getElementTypeNode();
|
|
2676
|
-
return `Array<${resolveTypeNodeToString(elementType, sourceFile, project, depth)}>`;
|
|
3377
|
+
return `Array<${resolveTypeNodeToString(elementType, sourceFile, project, depth, subst)}>`;
|
|
2677
3378
|
}
|
|
2678
3379
|
if (import_ts_morph7.Node.isUnionTypeNode(typeNode)) {
|
|
2679
|
-
return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth)).join(" | ");
|
|
3380
|
+
return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth, subst)).join(" | ");
|
|
2680
3381
|
}
|
|
2681
3382
|
if (import_ts_morph7.Node.isIntersectionTypeNode(typeNode)) {
|
|
2682
|
-
return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth)).join(" & ");
|
|
3383
|
+
return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth, subst)).join(" & ");
|
|
2683
3384
|
}
|
|
2684
3385
|
if (import_ts_morph7.Node.isParenthesizedTypeNode(typeNode)) {
|
|
2685
|
-
return `(${resolveTypeNodeToString(typeNode.getTypeNode(), sourceFile, project, depth)})`;
|
|
3386
|
+
return `(${resolveTypeNodeToString(typeNode.getTypeNode(), sourceFile, project, depth, subst)})`;
|
|
2686
3387
|
}
|
|
2687
3388
|
if (import_ts_morph7.Node.isTypeReference(typeNode)) {
|
|
2688
3389
|
const typeName = typeNode.getTypeName();
|
|
2689
3390
|
const name = import_ts_morph7.Node.isIdentifier(typeName) ? typeName.getText() : typeNode.getText();
|
|
3391
|
+
const bound = subst.get(name);
|
|
3392
|
+
if (bound !== void 0) return bound;
|
|
2690
3393
|
if (name === "string" || name === "number" || name === "boolean") return name;
|
|
2691
3394
|
if (name === "Date") return "string";
|
|
2692
3395
|
if (name === "unknown" || name === "any" || name === "void") return "unknown";
|
|
@@ -2694,14 +3397,15 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth) {
|
|
|
2694
3397
|
return "unknown";
|
|
2695
3398
|
const wrapperMode = WRAPPER_TYPES[name];
|
|
2696
3399
|
if (wrapperMode) {
|
|
2697
|
-
return unwrapFirstTypeArg(typeNode, sourceFile, project, depth, wrapperMode);
|
|
3400
|
+
return unwrapFirstTypeArg(typeNode, sourceFile, project, depth, wrapperMode, subst);
|
|
2698
3401
|
}
|
|
2699
3402
|
if (PASSTHROUGH_UTILITY.has(name)) {
|
|
2700
3403
|
return typeNode.getText();
|
|
2701
3404
|
}
|
|
2702
3405
|
const resolved = findType(name, sourceFile, project);
|
|
2703
3406
|
if (resolved) {
|
|
2704
|
-
|
|
3407
|
+
const childSubst = buildSubst(resolved, typeNode, sourceFile, project, depth, subst);
|
|
3408
|
+
return expandTypeDecl(resolved, project, depth - 1, childSubst);
|
|
2705
3409
|
}
|
|
2706
3410
|
dbg("unresolvable type:", name, "in", sourceFile.getFilePath());
|
|
2707
3411
|
return "unknown";
|
|
@@ -2714,32 +3418,45 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth) {
|
|
|
2714
3418
|
if (kind === import_ts_morph7.SyntaxKind.AnyKeyword) return "unknown";
|
|
2715
3419
|
return typeNode.getText();
|
|
2716
3420
|
}
|
|
2717
|
-
function unwrapFirstTypeArg(typeNode, sourceFile, project, depth, mode) {
|
|
3421
|
+
function unwrapFirstTypeArg(typeNode, sourceFile, project, depth, mode, subst = /* @__PURE__ */ new Map()) {
|
|
2718
3422
|
const typeArgs = typeNode.getTypeArguments();
|
|
2719
3423
|
const firstTypeArg = typeArgs[0];
|
|
2720
3424
|
if (typeArgs.length > 0 && firstTypeArg !== void 0) {
|
|
2721
|
-
const inner = resolveTypeNodeToString(firstTypeArg, sourceFile, project, depth);
|
|
3425
|
+
const inner = resolveTypeNodeToString(firstTypeArg, sourceFile, project, depth, subst);
|
|
2722
3426
|
return mode === "arrayOf" ? `Array<${inner}>` : inner;
|
|
2723
3427
|
}
|
|
2724
3428
|
return mode === "arrayOf" ? "Array<unknown>" : "unknown";
|
|
2725
3429
|
}
|
|
2726
|
-
function
|
|
3430
|
+
function buildSubst(result, typeNode, sourceFile, project, depth, parentSubst) {
|
|
3431
|
+
if (result.kind !== "class" && result.kind !== "interface") return /* @__PURE__ */ new Map();
|
|
3432
|
+
const params = result.decl.getTypeParameters().map((p) => p.getName());
|
|
3433
|
+
if (params.length === 0) return /* @__PURE__ */ new Map();
|
|
3434
|
+
const args = typeNode.getTypeArguments();
|
|
3435
|
+
const subst = /* @__PURE__ */ new Map();
|
|
3436
|
+
params.forEach((param, i) => {
|
|
3437
|
+
const arg = args[i];
|
|
3438
|
+
if (arg)
|
|
3439
|
+
subst.set(param, resolveTypeNodeToString(arg, sourceFile, project, depth, parentSubst));
|
|
3440
|
+
});
|
|
3441
|
+
return subst;
|
|
3442
|
+
}
|
|
3443
|
+
function expandTypeDecl(result, project, depth, subst = /* @__PURE__ */ new Map()) {
|
|
2727
3444
|
if (depth < 0) return "unknown";
|
|
2728
3445
|
switch (result.kind) {
|
|
2729
3446
|
case "class":
|
|
2730
|
-
return resolvePropertied(result.decl, result.file, project, depth);
|
|
3447
|
+
return resolvePropertied(result.decl, result.file, project, depth, subst);
|
|
2731
3448
|
case "interface":
|
|
2732
|
-
return resolvePropertied(result.decl, result.file, project, depth);
|
|
3449
|
+
return resolvePropertied(result.decl, result.file, project, depth, subst);
|
|
2733
3450
|
case "typeAlias":
|
|
2734
3451
|
if (result.typeNode) {
|
|
2735
|
-
return resolveTypeNodeToString(result.typeNode, result.file, project, depth);
|
|
3452
|
+
return resolveTypeNodeToString(result.typeNode, result.file, project, depth, subst);
|
|
2736
3453
|
}
|
|
2737
3454
|
return result.text;
|
|
2738
3455
|
case "enum":
|
|
2739
3456
|
return result.members.join(" | ");
|
|
2740
3457
|
}
|
|
2741
3458
|
}
|
|
2742
|
-
function resolvePropertied(decl, sourceFile, project, depth) {
|
|
3459
|
+
function resolvePropertied(decl, sourceFile, project, depth, subst = /* @__PURE__ */ new Map()) {
|
|
2743
3460
|
if (depth < 0) return "unknown";
|
|
2744
3461
|
const lines = [];
|
|
2745
3462
|
for (const prop of decl.getProperties()) {
|
|
@@ -2748,7 +3465,7 @@ function resolvePropertied(decl, sourceFile, project, depth) {
|
|
|
2748
3465
|
const propTypeNode = prop.getTypeNode();
|
|
2749
3466
|
let propType = "unknown";
|
|
2750
3467
|
if (propTypeNode) {
|
|
2751
|
-
propType = resolveTypeNodeToString(propTypeNode, sourceFile, project, depth);
|
|
3468
|
+
propType = resolveTypeNodeToString(propTypeNode, sourceFile, project, depth, subst);
|
|
2752
3469
|
}
|
|
2753
3470
|
lines.push(`${propName}${isOptional ? "?" : ""}: ${propType}`);
|
|
2754
3471
|
}
|
|
@@ -2797,7 +3514,7 @@ function extractParamsType(method, sourceFile, project) {
|
|
|
2797
3514
|
return entries.length > 0 ? `{ ${entries.join("; ")} }` : null;
|
|
2798
3515
|
}
|
|
2799
3516
|
function extractResponseType(method, sourceFile, project) {
|
|
2800
|
-
const apiResponseDecorator = method.
|
|
3517
|
+
const apiResponseDecorator = method.getDecorators().find((d) => d.getName() === "ApiResponse" && (apiResponseStatus(d) ?? 0) < 400);
|
|
2801
3518
|
if (apiResponseDecorator) {
|
|
2802
3519
|
const args = apiResponseDecorator.getArguments();
|
|
2803
3520
|
const optsArg = args[0];
|
|
@@ -2826,6 +3543,59 @@ function extractResponseType(method, sourceFile, project) {
|
|
|
2826
3543
|
}
|
|
2827
3544
|
return "unknown";
|
|
2828
3545
|
}
|
|
3546
|
+
function apiResponseStatus(decorator) {
|
|
3547
|
+
const optsArg = decorator.getArguments()[0];
|
|
3548
|
+
if (!optsArg || !import_ts_morph7.Node.isObjectLiteralExpression(optsArg)) return null;
|
|
3549
|
+
for (const prop of optsArg.getProperties()) {
|
|
3550
|
+
if (!import_ts_morph7.Node.isPropertyAssignment(prop)) continue;
|
|
3551
|
+
if (prop.getName() !== "status") continue;
|
|
3552
|
+
const val = prop.getInitializer();
|
|
3553
|
+
if (val && import_ts_morph7.Node.isNumericLiteral(val)) return Number(val.getLiteralValue());
|
|
3554
|
+
}
|
|
3555
|
+
return null;
|
|
3556
|
+
}
|
|
3557
|
+
function apiResponseTypeNode(decorator) {
|
|
3558
|
+
const optsArg = decorator.getArguments()[0];
|
|
3559
|
+
if (!optsArg || !import_ts_morph7.Node.isObjectLiteralExpression(optsArg)) return null;
|
|
3560
|
+
for (const prop of optsArg.getProperties()) {
|
|
3561
|
+
if (!import_ts_morph7.Node.isPropertyAssignment(prop)) continue;
|
|
3562
|
+
if (prop.getName() !== "type") continue;
|
|
3563
|
+
const val = prop.getInitializer();
|
|
3564
|
+
if (!val) return null;
|
|
3565
|
+
if (import_ts_morph7.Node.isArrayLiteralExpression(val)) {
|
|
3566
|
+
const first = val.getElements()[0];
|
|
3567
|
+
return first ? { node: first, isArray: true } : null;
|
|
3568
|
+
}
|
|
3569
|
+
return { node: val, isArray: false };
|
|
3570
|
+
}
|
|
3571
|
+
return null;
|
|
3572
|
+
}
|
|
3573
|
+
function extractErrorType(method, sourceFile, project) {
|
|
3574
|
+
for (const decorator of method.getDecorators()) {
|
|
3575
|
+
if (decorator.getName() !== "ApiResponse") continue;
|
|
3576
|
+
const status = apiResponseStatus(decorator);
|
|
3577
|
+
if (status === null || status < 400) continue;
|
|
3578
|
+
const typeInfo = apiResponseTypeNode(decorator);
|
|
3579
|
+
if (!typeInfo) continue;
|
|
3580
|
+
const inner = resolveIdentifierToClassType(typeInfo.node, sourceFile, project, 3);
|
|
3581
|
+
const type = typeInfo.isArray ? `Array<${inner}>` : inner;
|
|
3582
|
+
let ref = null;
|
|
3583
|
+
if (import_ts_morph7.Node.isIdentifier(typeInfo.node)) {
|
|
3584
|
+
const name = typeInfo.node.getText();
|
|
3585
|
+
const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
|
|
3586
|
+
if (localDecl?.isExported()) {
|
|
3587
|
+
ref = { name, filePath: sourceFile.getFilePath(), isArray: typeInfo.isArray };
|
|
3588
|
+
} else {
|
|
3589
|
+
const resolved = resolveImportedType(name, sourceFile, project);
|
|
3590
|
+
if (resolved && (resolved.kind === "class" || resolved.kind === "interface") && resolved.decl.isExported()) {
|
|
3591
|
+
ref = { name, filePath: resolved.file.getFilePath(), isArray: typeInfo.isArray };
|
|
3592
|
+
}
|
|
3593
|
+
}
|
|
3594
|
+
}
|
|
3595
|
+
return { type, ref };
|
|
3596
|
+
}
|
|
3597
|
+
return null;
|
|
3598
|
+
}
|
|
2829
3599
|
function resolveIdentifierToClassType(node, sourceFile, project, depth) {
|
|
2830
3600
|
if (!import_ts_morph7.Node.isIdentifier(node)) return "unknown";
|
|
2831
3601
|
const name = node.getText();
|
|
@@ -2841,17 +3611,52 @@ function resolveBodyQueryResponseRef(typeNode, sourceFile, project) {
|
|
|
2841
3611
|
unwrapContainers: true
|
|
2842
3612
|
});
|
|
2843
3613
|
}
|
|
3614
|
+
var STREAM_CONTAINERS = /* @__PURE__ */ new Set(["Observable", "AsyncIterable", "AsyncIterableIterator"]);
|
|
3615
|
+
var STREAM_CONTAINERS_GENERATOR = /* @__PURE__ */ new Set(["AsyncGenerator"]);
|
|
3616
|
+
var STREAM_ENVELOPES = /* @__PURE__ */ new Set(["MessageEvent", "MessageEventLike"]);
|
|
3617
|
+
function detectStreamElement(method) {
|
|
3618
|
+
const hasSse = method.getDecorators().some((d) => d.getName() === "Sse");
|
|
3619
|
+
let node = method.getReturnTypeNode();
|
|
3620
|
+
node = unwrapNamedContainer(node, /* @__PURE__ */ new Set(["Promise"]));
|
|
3621
|
+
const containerEl = streamContainerElement(node);
|
|
3622
|
+
if (containerEl) {
|
|
3623
|
+
return unwrapNamedContainer(containerEl, STREAM_ENVELOPES) ?? containerEl;
|
|
3624
|
+
}
|
|
3625
|
+
if (hasSse) return node ?? null;
|
|
3626
|
+
return null;
|
|
3627
|
+
}
|
|
3628
|
+
function streamContainerElement(node) {
|
|
3629
|
+
if (!node || !import_ts_morph7.Node.isTypeReference(node)) return null;
|
|
3630
|
+
const typeName = node.getTypeName();
|
|
3631
|
+
const name = import_ts_morph7.Node.isIdentifier(typeName) ? typeName.getText() : "";
|
|
3632
|
+
if (STREAM_CONTAINERS.has(name) || STREAM_CONTAINERS_GENERATOR.has(name)) {
|
|
3633
|
+
return node.getTypeArguments()[0] ?? null;
|
|
3634
|
+
}
|
|
3635
|
+
return null;
|
|
3636
|
+
}
|
|
3637
|
+
function unwrapNamedContainer(node, names) {
|
|
3638
|
+
if (!node || !import_ts_morph7.Node.isTypeReference(node)) return node;
|
|
3639
|
+
const typeName = node.getTypeName();
|
|
3640
|
+
const name = import_ts_morph7.Node.isIdentifier(typeName) ? typeName.getText() : "";
|
|
3641
|
+
if (names.has(name)) {
|
|
3642
|
+
return node.getTypeArguments()[0] ?? node;
|
|
3643
|
+
}
|
|
3644
|
+
return node;
|
|
3645
|
+
}
|
|
2844
3646
|
function extractDtoContract(method, sourceFile, project) {
|
|
2845
3647
|
let body = extractBodyType(method, sourceFile, project);
|
|
2846
3648
|
const filterInfo = extractApplyFilterInfo(method, sourceFile, project);
|
|
2847
3649
|
const query = extractQueryType(method, sourceFile, project);
|
|
3650
|
+
const streamElement = detectStreamElement(method);
|
|
3651
|
+
const isStream = streamElement !== null;
|
|
2848
3652
|
if (filterInfo && filterInfo.source === "body") {
|
|
2849
3653
|
const bodyType = "import('@dudousxd/nestjs-filter-client').FilterQueryResult";
|
|
2850
3654
|
body = body ?? bodyType;
|
|
2851
3655
|
}
|
|
2852
3656
|
const paramsType = extractParamsType(method, sourceFile, project);
|
|
2853
|
-
const response = extractResponseType(method, sourceFile, project);
|
|
2854
|
-
|
|
3657
|
+
const response = isStream ? resolveTypeNodeToString(streamElement, sourceFile, project, 3) : extractResponseType(method, sourceFile, project);
|
|
3658
|
+
const errorInfo = extractErrorType(method, sourceFile, project);
|
|
3659
|
+
if (body === null && query === null && paramsType === null && response === "unknown" && errorInfo === null && filterInfo === null && !isStream) {
|
|
2855
3660
|
return null;
|
|
2856
3661
|
}
|
|
2857
3662
|
let bodyRef = null;
|
|
@@ -2865,12 +3670,12 @@ function extractDtoContract(method, sourceFile, project) {
|
|
|
2865
3670
|
queryRef = resolveBodyQueryResponseRef(param.getTypeNode(), sourceFile, project);
|
|
2866
3671
|
}
|
|
2867
3672
|
}
|
|
2868
|
-
const returnTypeNode = method.getReturnTypeNode();
|
|
3673
|
+
const returnTypeNode = isStream ? streamElement : method.getReturnTypeNode();
|
|
2869
3674
|
if (returnTypeNode) {
|
|
2870
3675
|
responseRef = resolveBodyQueryResponseRef(returnTypeNode, sourceFile, project);
|
|
2871
3676
|
}
|
|
2872
|
-
if (!responseRef) {
|
|
2873
|
-
const apiResp = method.
|
|
3677
|
+
if (!responseRef && !isStream) {
|
|
3678
|
+
const apiResp = method.getDecorators().find((d) => d.getName() === "ApiResponse" && (apiResponseStatus(d) ?? 0) < 400);
|
|
2874
3679
|
if (apiResp) {
|
|
2875
3680
|
const args = apiResp.getArguments();
|
|
2876
3681
|
const optsArg = args[0];
|
|
@@ -2912,16 +3717,19 @@ function extractDtoContract(method, sourceFile, project) {
|
|
|
2912
3717
|
query,
|
|
2913
3718
|
body,
|
|
2914
3719
|
response,
|
|
3720
|
+
error: errorInfo?.type ?? null,
|
|
2915
3721
|
params: paramsType,
|
|
2916
3722
|
queryRef,
|
|
2917
3723
|
bodyRef,
|
|
2918
3724
|
responseRef,
|
|
3725
|
+
errorRef: errorInfo?.ref ?? null,
|
|
2919
3726
|
filterFields: filterInfo?.fieldNames ?? null,
|
|
2920
3727
|
filterFieldTypes: filterInfo?.fieldTypes ?? null,
|
|
2921
3728
|
filterSource: filterInfo?.source ?? null,
|
|
2922
3729
|
formWarnings,
|
|
2923
3730
|
bodySchema,
|
|
2924
|
-
querySchema
|
|
3731
|
+
querySchema,
|
|
3732
|
+
stream: isStream
|
|
2925
3733
|
};
|
|
2926
3734
|
}
|
|
2927
3735
|
function resolveParamClass(method, decoratorName, sourceFile, project) {
|
|
@@ -3039,6 +3847,7 @@ function parseDefineContractCall(callExpr) {
|
|
|
3039
3847
|
let query = null;
|
|
3040
3848
|
let body = null;
|
|
3041
3849
|
let response = "unknown";
|
|
3850
|
+
let error = null;
|
|
3042
3851
|
let bodyZodText = null;
|
|
3043
3852
|
let queryZodText = null;
|
|
3044
3853
|
for (const prop of optsArg.getProperties()) {
|
|
@@ -3054,25 +3863,38 @@ function parseDefineContractCall(callExpr) {
|
|
|
3054
3863
|
bodyZodText = val.getText();
|
|
3055
3864
|
} else if (propName === "response") {
|
|
3056
3865
|
response = zodAstToTs(val);
|
|
3866
|
+
} else if (propName === "error") {
|
|
3867
|
+
error = zodAstToTs(val);
|
|
3057
3868
|
}
|
|
3058
3869
|
}
|
|
3059
|
-
return { query, body, response, bodyZodText, queryZodText };
|
|
3870
|
+
return { query, body, response, error, bodyZodText, queryZodText };
|
|
3060
3871
|
}
|
|
3061
3872
|
|
|
3062
3873
|
// src/discovery/contracts-fast.ts
|
|
3063
3874
|
async function discoverContractsFast(opts) {
|
|
3064
3875
|
const { cwd, glob, tsconfig } = opts;
|
|
3065
|
-
const tsconfigPath =
|
|
3066
|
-
|
|
3876
|
+
const tsconfigPath = resolveTsconfigPath(cwd, tsconfig);
|
|
3877
|
+
const project = createDiscoveryProject(tsconfigPath);
|
|
3878
|
+
const files = await (0, import_fast_glob2.default)(glob, { cwd, absolute: true, onlyFiles: true });
|
|
3879
|
+
for (const f of files) {
|
|
3880
|
+
project.addSourceFileAtPath(f);
|
|
3881
|
+
}
|
|
3882
|
+
bindDiscoveryContext(project, cwd, tsconfigPath);
|
|
3883
|
+
return extractAllRoutes(project);
|
|
3884
|
+
}
|
|
3885
|
+
function resolveTsconfigPath(cwd, tsconfig) {
|
|
3886
|
+
return tsconfig ? (0, import_node_path14.resolve)(tsconfig) : (0, import_node_path14.join)(cwd, "tsconfig.json");
|
|
3887
|
+
}
|
|
3888
|
+
function createDiscoveryProject(tsconfigPath) {
|
|
3067
3889
|
try {
|
|
3068
|
-
|
|
3890
|
+
return new import_ts_morph9.Project({
|
|
3069
3891
|
tsConfigFilePath: tsconfigPath,
|
|
3070
3892
|
skipAddingFilesFromTsConfig: true,
|
|
3071
3893
|
skipLoadingLibFiles: true,
|
|
3072
3894
|
skipFileDependencyResolution: true
|
|
3073
3895
|
});
|
|
3074
3896
|
} catch {
|
|
3075
|
-
|
|
3897
|
+
return new import_ts_morph9.Project({
|
|
3076
3898
|
skipAddingFilesFromTsConfig: true,
|
|
3077
3899
|
skipLoadingLibFiles: true,
|
|
3078
3900
|
skipFileDependencyResolution: true,
|
|
@@ -3083,20 +3905,105 @@ async function discoverContractsFast(opts) {
|
|
|
3083
3905
|
}
|
|
3084
3906
|
});
|
|
3085
3907
|
}
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
project.addSourceFileAtPath(f);
|
|
3089
|
-
}
|
|
3090
|
-
const routes = [];
|
|
3908
|
+
}
|
|
3909
|
+
function bindDiscoveryContext(project, cwd, tsconfigPath) {
|
|
3091
3910
|
setDiscoveryContext(project, {
|
|
3092
3911
|
projectRoot: cwd,
|
|
3093
3912
|
tsconfigPaths: loadTsconfigPaths(tsconfigPath)
|
|
3094
3913
|
});
|
|
3914
|
+
}
|
|
3915
|
+
function extractRoutesFrom(project, controllerPaths) {
|
|
3916
|
+
const routes = [];
|
|
3917
|
+
for (const path of controllerPaths) {
|
|
3918
|
+
const sourceFile = project.getSourceFile(path);
|
|
3919
|
+
if (sourceFile) routes.push(...extractFromSourceFile(sourceFile, project));
|
|
3920
|
+
}
|
|
3921
|
+
return routes;
|
|
3922
|
+
}
|
|
3923
|
+
function extractAllRoutes(project) {
|
|
3924
|
+
const routes = [];
|
|
3095
3925
|
for (const sourceFile of project.getSourceFiles()) {
|
|
3096
3926
|
routes.push(...extractFromSourceFile(sourceFile, project));
|
|
3097
3927
|
}
|
|
3098
3928
|
return routes;
|
|
3099
3929
|
}
|
|
3930
|
+
var PersistentDiscovery = class _PersistentDiscovery {
|
|
3931
|
+
project;
|
|
3932
|
+
cwd;
|
|
3933
|
+
glob;
|
|
3934
|
+
/** Absolute paths of the controllers currently loaded as extraction roots. */
|
|
3935
|
+
controllerPaths = /* @__PURE__ */ new Set();
|
|
3936
|
+
constructor(project, cwd, glob) {
|
|
3937
|
+
this.project = project;
|
|
3938
|
+
this.cwd = cwd;
|
|
3939
|
+
this.glob = glob;
|
|
3940
|
+
}
|
|
3941
|
+
/**
|
|
3942
|
+
* Build the initial persistent Project: create it, glob + add all controllers,
|
|
3943
|
+
* bind the discovery context. Mirrors {@link discoverContractsFast}'s setup.
|
|
3944
|
+
*/
|
|
3945
|
+
static async create(opts) {
|
|
3946
|
+
const { cwd, glob, tsconfig } = opts;
|
|
3947
|
+
const tsconfigPath = resolveTsconfigPath(cwd, tsconfig);
|
|
3948
|
+
const project = createDiscoveryProject(tsconfigPath);
|
|
3949
|
+
bindDiscoveryContext(project, cwd, tsconfigPath);
|
|
3950
|
+
const instance = new _PersistentDiscovery(project, cwd, glob);
|
|
3951
|
+
const files = await (0, import_fast_glob2.default)(glob, { cwd, absolute: true, onlyFiles: true });
|
|
3952
|
+
for (const f of files) {
|
|
3953
|
+
project.addSourceFileAtPath(f);
|
|
3954
|
+
instance.controllerPaths.add(f);
|
|
3955
|
+
}
|
|
3956
|
+
return instance;
|
|
3957
|
+
}
|
|
3958
|
+
/** Run the initial extraction (equivalent to a first `discoverContractsFast`). */
|
|
3959
|
+
discover() {
|
|
3960
|
+
return this.runExtraction();
|
|
3961
|
+
}
|
|
3962
|
+
/**
|
|
3963
|
+
* Re-discover after one or more files changed. Refreshes the changed file(s)
|
|
3964
|
+
* from disk (controllers AND any lazily-loaded DTO/imported files), re-globs
|
|
3965
|
+
* to pick up added/removed controllers, clears the per-Project caches, then
|
|
3966
|
+
* re-extracts. `changedPaths` is a hint; correctness does not depend on it
|
|
3967
|
+
* being exhaustive because re-globbing + refresh-on-presence covers the set.
|
|
3968
|
+
*/
|
|
3969
|
+
async rediscover(changedPaths) {
|
|
3970
|
+
if (changedPaths) {
|
|
3971
|
+
for (const p of changedPaths) {
|
|
3972
|
+
const abs = (0, import_node_path14.resolve)(p);
|
|
3973
|
+
const sf = this.project.getSourceFile(abs);
|
|
3974
|
+
if (sf) {
|
|
3975
|
+
await sf.refreshFromFileSystem();
|
|
3976
|
+
}
|
|
3977
|
+
}
|
|
3978
|
+
}
|
|
3979
|
+
const globbed = new Set(
|
|
3980
|
+
await (0, import_fast_glob2.default)(this.glob, { cwd: this.cwd, absolute: true, onlyFiles: true })
|
|
3981
|
+
);
|
|
3982
|
+
for (const f of globbed) {
|
|
3983
|
+
if (!this.controllerPaths.has(f)) {
|
|
3984
|
+
try {
|
|
3985
|
+
this.project.addSourceFileAtPath(f);
|
|
3986
|
+
this.controllerPaths.add(f);
|
|
3987
|
+
} catch {
|
|
3988
|
+
}
|
|
3989
|
+
}
|
|
3990
|
+
}
|
|
3991
|
+
for (const f of this.controllerPaths) {
|
|
3992
|
+
if (!globbed.has(f)) {
|
|
3993
|
+
const sf = this.project.getSourceFile(f);
|
|
3994
|
+
if (sf) this.project.removeSourceFile(sf);
|
|
3995
|
+
this.controllerPaths.delete(f);
|
|
3996
|
+
}
|
|
3997
|
+
}
|
|
3998
|
+
return this.runExtraction();
|
|
3999
|
+
}
|
|
4000
|
+
/** Clear stale per-Project caches, then extract over the controller set. */
|
|
4001
|
+
runExtraction() {
|
|
4002
|
+
clearTypeResolutionCaches(this.project);
|
|
4003
|
+
clearEnumCache(this.project);
|
|
4004
|
+
return extractRoutesFrom(this.project, this.controllerPaths);
|
|
4005
|
+
}
|
|
4006
|
+
};
|
|
3100
4007
|
function decoratorStringArg(decoratorExpr) {
|
|
3101
4008
|
if (!decoratorExpr) return void 0;
|
|
3102
4009
|
if (import_ts_morph9.Node.isStringLiteral(decoratorExpr)) return decoratorExpr.getLiteralValue();
|
|
@@ -3152,6 +4059,11 @@ function resolveVerb(method) {
|
|
|
3152
4059
|
return { httpMethod: verb, handlerPath: decoratorStringArg(pathArg) ?? "" };
|
|
3153
4060
|
}
|
|
3154
4061
|
}
|
|
4062
|
+
const sseDecorator = method.getDecorator("Sse");
|
|
4063
|
+
if (sseDecorator) {
|
|
4064
|
+
const pathArg = sseDecorator.getArguments()[0];
|
|
4065
|
+
return { httpMethod: "GET", handlerPath: decoratorStringArg(pathArg) ?? "" };
|
|
4066
|
+
}
|
|
3155
4067
|
return null;
|
|
3156
4068
|
}
|
|
3157
4069
|
function readAsDecorator(node, label) {
|
|
@@ -3194,7 +4106,17 @@ function buildRoute(args) {
|
|
|
3194
4106
|
};
|
|
3195
4107
|
}
|
|
3196
4108
|
function extractContractRoute(args) {
|
|
3197
|
-
const {
|
|
4109
|
+
const {
|
|
4110
|
+
cls,
|
|
4111
|
+
method,
|
|
4112
|
+
applyContractDecorator,
|
|
4113
|
+
verb,
|
|
4114
|
+
prefix,
|
|
4115
|
+
className,
|
|
4116
|
+
sourceFile,
|
|
4117
|
+
project,
|
|
4118
|
+
seenNames
|
|
4119
|
+
} = args;
|
|
3198
4120
|
const firstDecoratorArg = applyContractDecorator.getArguments()[0];
|
|
3199
4121
|
if (!firstDecoratorArg) return null;
|
|
3200
4122
|
let contractDef = null;
|
|
@@ -3204,18 +4126,19 @@ function extractContractRoute(args) {
|
|
|
3204
4126
|
contractDef = parseDefineContractCall(firstDecoratorArg);
|
|
3205
4127
|
} else if (import_ts_morph9.Node.isIdentifier(firstDecoratorArg)) {
|
|
3206
4128
|
const identName = firstDecoratorArg.getText();
|
|
3207
|
-
const
|
|
3208
|
-
if (!
|
|
4129
|
+
const resolvedVar = resolveImportedVariable(identName, sourceFile, project);
|
|
4130
|
+
if (!resolvedVar) {
|
|
3209
4131
|
console.warn(
|
|
3210
|
-
`[nestjs-codegen/fast] Cannot resolve '${identName}' in ${sourceFile.getFilePath()}
|
|
4132
|
+
`[nestjs-codegen/fast] Cannot resolve contract identifier '${identName}' applied in ${sourceFile.getFilePath()} \u2014 the import could not be followed to a declaration; skipping`
|
|
3211
4133
|
);
|
|
3212
4134
|
return null;
|
|
3213
4135
|
}
|
|
4136
|
+
const { decl: varDecl, file: declFile } = resolvedVar;
|
|
3214
4137
|
const initializer = varDecl.getInitializer();
|
|
3215
4138
|
if (!initializer) return null;
|
|
3216
4139
|
contractDef = parseDefineContractCall(initializer);
|
|
3217
4140
|
if (contractDef && varDecl.isExported()) {
|
|
3218
|
-
const filePath =
|
|
4141
|
+
const filePath = declFile.getFilePath();
|
|
3219
4142
|
if (contractDef.body !== null) {
|
|
3220
4143
|
bodyZodRef = { name: `${identName}.body`, filePath };
|
|
3221
4144
|
}
|
|
@@ -3248,6 +4171,7 @@ function extractContractRoute(args) {
|
|
|
3248
4171
|
query: contractDef.query,
|
|
3249
4172
|
body: contractDef.body,
|
|
3250
4173
|
response: contractDef.response,
|
|
4174
|
+
error: contractDef.error,
|
|
3251
4175
|
// Path A: capture both the importable ref and the raw text. The emitter
|
|
3252
4176
|
// prefers inlining the text (client-safe — re-exporting from a controller
|
|
3253
4177
|
// would drag server-only deps into the client bundle).
|
|
@@ -3279,15 +4203,18 @@ function extractDtoRoute(args) {
|
|
|
3279
4203
|
query: dtoContract?.query ?? null,
|
|
3280
4204
|
body: dtoContract?.body ?? null,
|
|
3281
4205
|
response: dtoContract?.response ?? "unknown",
|
|
4206
|
+
error: dtoContract?.error ?? null,
|
|
3282
4207
|
queryRef: dtoContract?.queryRef ?? null,
|
|
3283
4208
|
bodyRef: dtoContract?.bodyRef ?? null,
|
|
3284
4209
|
responseRef: dtoContract?.responseRef ?? null,
|
|
4210
|
+
errorRef: dtoContract?.errorRef ?? null,
|
|
3285
4211
|
filterFields: dtoContract?.filterFields ?? null,
|
|
3286
4212
|
filterFieldTypes: dtoContract?.filterFieldTypes ?? null,
|
|
3287
4213
|
filterSource: dtoContract?.filterSource ?? null,
|
|
3288
4214
|
formWarnings: dtoContract?.formWarnings ?? [],
|
|
3289
4215
|
bodySchema: dtoContract?.bodySchema ?? null,
|
|
3290
|
-
querySchema: dtoContract?.querySchema ?? null
|
|
4216
|
+
querySchema: dtoContract?.querySchema ?? null,
|
|
4217
|
+
stream: dtoContract?.stream ?? false
|
|
3291
4218
|
}
|
|
3292
4219
|
});
|
|
3293
4220
|
}
|
|
@@ -3311,6 +4238,7 @@ function extractFromSourceFile(sourceFile, project) {
|
|
|
3311
4238
|
prefix,
|
|
3312
4239
|
className,
|
|
3313
4240
|
sourceFile,
|
|
4241
|
+
project,
|
|
3314
4242
|
seenNames
|
|
3315
4243
|
}) : extractDtoRoute({
|
|
3316
4244
|
cls,
|
|
@@ -3329,9 +4257,9 @@ function extractFromSourceFile(sourceFile, project) {
|
|
|
3329
4257
|
}
|
|
3330
4258
|
|
|
3331
4259
|
// src/watch/lock-file.ts
|
|
3332
|
-
var
|
|
3333
|
-
var
|
|
3334
|
-
var
|
|
4260
|
+
var import_promises12 = require("fs/promises");
|
|
4261
|
+
var import_promises13 = require("fs/promises");
|
|
4262
|
+
var import_node_path15 = require("path");
|
|
3335
4263
|
var LOCK_FILE = ".watcher.lock";
|
|
3336
4264
|
function isProcessAlive(pid) {
|
|
3337
4265
|
try {
|
|
@@ -3342,21 +4270,21 @@ function isProcessAlive(pid) {
|
|
|
3342
4270
|
}
|
|
3343
4271
|
}
|
|
3344
4272
|
async function acquireLock(outDir) {
|
|
3345
|
-
await (0,
|
|
3346
|
-
const lockPath = (0,
|
|
4273
|
+
await (0, import_promises13.mkdir)(outDir, { recursive: true });
|
|
4274
|
+
const lockPath = (0, import_node_path15.join)(outDir, LOCK_FILE);
|
|
3347
4275
|
const lockData = { pid: process.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
3348
4276
|
try {
|
|
3349
|
-
const fd = await (0,
|
|
4277
|
+
const fd = await (0, import_promises12.open)(lockPath, "wx");
|
|
3350
4278
|
await fd.writeFile(`${JSON.stringify(lockData, null, 2)}
|
|
3351
4279
|
`, "utf8");
|
|
3352
4280
|
await fd.close();
|
|
3353
4281
|
} catch (err) {
|
|
3354
4282
|
if (err.code === "EEXIST") {
|
|
3355
4283
|
try {
|
|
3356
|
-
const raw = await (0,
|
|
4284
|
+
const raw = await (0, import_promises13.readFile)(lockPath, "utf8");
|
|
3357
4285
|
const existing = JSON.parse(raw);
|
|
3358
4286
|
if (isProcessAlive(existing.pid)) return null;
|
|
3359
|
-
await (0,
|
|
4287
|
+
await (0, import_promises13.unlink)(lockPath);
|
|
3360
4288
|
return acquireLock(outDir);
|
|
3361
4289
|
} catch {
|
|
3362
4290
|
return null;
|
|
@@ -3367,7 +4295,7 @@ async function acquireLock(outDir) {
|
|
|
3367
4295
|
return {
|
|
3368
4296
|
release: async () => {
|
|
3369
4297
|
try {
|
|
3370
|
-
await (0,
|
|
4298
|
+
await (0, import_promises13.unlink)(lockPath);
|
|
3371
4299
|
} catch {
|
|
3372
4300
|
}
|
|
3373
4301
|
}
|
|
@@ -3383,7 +4311,7 @@ async function watch(config, onChange) {
|
|
|
3383
4311
|
if (lock === null) {
|
|
3384
4312
|
let holderPid = "unknown";
|
|
3385
4313
|
try {
|
|
3386
|
-
const raw = await (0,
|
|
4314
|
+
const raw = await (0, import_promises14.readFile)((0, import_node_path16.join)(config.codegen.outDir, ".watcher.lock"), "utf8");
|
|
3387
4315
|
const data = JSON.parse(raw);
|
|
3388
4316
|
if (data.pid !== void 0) holderPid = String(data.pid);
|
|
3389
4317
|
} catch {
|
|
@@ -3393,12 +4321,20 @@ async function watch(config, onChange) {
|
|
|
3393
4321
|
);
|
|
3394
4322
|
return NO_OP_WATCHER;
|
|
3395
4323
|
}
|
|
4324
|
+
let discovery = null;
|
|
4325
|
+
async function getDiscovery() {
|
|
4326
|
+
if (discovery === null) {
|
|
4327
|
+
discovery = await PersistentDiscovery.create({
|
|
4328
|
+
cwd: config.codegen.cwd,
|
|
4329
|
+
glob: config.contracts.glob,
|
|
4330
|
+
...config.app?.tsconfig ? { tsconfig: config.app.tsconfig } : {}
|
|
4331
|
+
});
|
|
4332
|
+
return discovery;
|
|
4333
|
+
}
|
|
4334
|
+
return discovery;
|
|
4335
|
+
}
|
|
3396
4336
|
try {
|
|
3397
|
-
const initialRoutes = await
|
|
3398
|
-
cwd: config.codegen.cwd,
|
|
3399
|
-
glob: config.contracts.glob,
|
|
3400
|
-
...config.app?.tsconfig ? { tsconfig: config.app.tsconfig } : {}
|
|
3401
|
-
});
|
|
4337
|
+
const initialRoutes = (await getDiscovery()).discover();
|
|
3402
4338
|
await generate(config, initialRoutes);
|
|
3403
4339
|
} catch (err) {
|
|
3404
4340
|
console.warn(
|
|
@@ -3411,7 +4347,7 @@ async function watch(config, onChange) {
|
|
|
3411
4347
|
}
|
|
3412
4348
|
let pagesDebounceTimer;
|
|
3413
4349
|
const pagesGlob = config.pages?.glob ?? ".nestjs-codegen-no-pages";
|
|
3414
|
-
const pagesWatcher = import_chokidar.default.watch((0,
|
|
4350
|
+
const pagesWatcher = import_chokidar.default.watch((0, import_node_path16.join)(config.codegen.cwd, pagesGlob), {
|
|
3415
4351
|
ignoreInitial: true,
|
|
3416
4352
|
persistent: true,
|
|
3417
4353
|
awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
|
|
@@ -3437,23 +4373,23 @@ async function watch(config, onChange) {
|
|
|
3437
4373
|
pagesWatcher.on("change", schedulePagesRegenerate);
|
|
3438
4374
|
pagesWatcher.on("unlink", schedulePagesRegenerate);
|
|
3439
4375
|
let contractsDebounceTimer;
|
|
3440
|
-
const
|
|
4376
|
+
const pendingChangedPaths = /* @__PURE__ */ new Set();
|
|
4377
|
+
const contractsWatcher = import_chokidar.default.watch((0, import_node_path16.join)(config.codegen.cwd, config.contracts.glob), {
|
|
3441
4378
|
ignoreInitial: true,
|
|
3442
4379
|
persistent: true,
|
|
3443
4380
|
awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
|
|
3444
4381
|
});
|
|
3445
|
-
function scheduleContractsRegenerate() {
|
|
4382
|
+
function scheduleContractsRegenerate(changedPath) {
|
|
4383
|
+
if (typeof changedPath === "string") pendingChangedPaths.add(changedPath);
|
|
3446
4384
|
if (contractsDebounceTimer !== void 0) {
|
|
3447
4385
|
clearTimeout(contractsDebounceTimer);
|
|
3448
4386
|
}
|
|
3449
4387
|
contractsDebounceTimer = setTimeout(async () => {
|
|
3450
4388
|
contractsDebounceTimer = void 0;
|
|
4389
|
+
const changed = [...pendingChangedPaths];
|
|
4390
|
+
pendingChangedPaths.clear();
|
|
3451
4391
|
try {
|
|
3452
|
-
const routes = await
|
|
3453
|
-
cwd: config.codegen.cwd,
|
|
3454
|
-
glob: config.contracts.glob,
|
|
3455
|
-
...config.app?.tsconfig ? { tsconfig: config.app.tsconfig } : {}
|
|
3456
|
-
});
|
|
4392
|
+
const routes = await (await getDiscovery()).rediscover(changed);
|
|
3457
4393
|
await generate(config, routes);
|
|
3458
4394
|
} catch (err) {
|
|
3459
4395
|
console.error(
|
|
@@ -3464,17 +4400,17 @@ async function watch(config, onChange) {
|
|
|
3464
4400
|
onChange?.();
|
|
3465
4401
|
}, config.contracts.debounceMs);
|
|
3466
4402
|
}
|
|
3467
|
-
contractsWatcher.on("add", scheduleContractsRegenerate);
|
|
3468
|
-
contractsWatcher.on("change", scheduleContractsRegenerate);
|
|
3469
|
-
contractsWatcher.on("unlink", scheduleContractsRegenerate);
|
|
3470
|
-
const formsWatcher = import_chokidar.default.watch((0,
|
|
4403
|
+
contractsWatcher.on("add", (p) => scheduleContractsRegenerate(p));
|
|
4404
|
+
contractsWatcher.on("change", (p) => scheduleContractsRegenerate(p));
|
|
4405
|
+
contractsWatcher.on("unlink", (p) => scheduleContractsRegenerate(p));
|
|
4406
|
+
const formsWatcher = import_chokidar.default.watch((0, import_node_path16.join)(config.codegen.cwd, config.forms.watch), {
|
|
3471
4407
|
ignoreInitial: true,
|
|
3472
4408
|
persistent: true,
|
|
3473
4409
|
awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
|
|
3474
4410
|
});
|
|
3475
|
-
formsWatcher.on("add", scheduleContractsRegenerate);
|
|
3476
|
-
formsWatcher.on("change", scheduleContractsRegenerate);
|
|
3477
|
-
formsWatcher.on("unlink", scheduleContractsRegenerate);
|
|
4411
|
+
formsWatcher.on("add", (p) => scheduleContractsRegenerate(p));
|
|
4412
|
+
formsWatcher.on("change", (p) => scheduleContractsRegenerate(p));
|
|
4413
|
+
formsWatcher.on("unlink", (p) => scheduleContractsRegenerate(p));
|
|
3478
4414
|
return {
|
|
3479
4415
|
close: async () => {
|
|
3480
4416
|
if (pagesDebounceTimer !== void 0) {
|
|
@@ -3494,7 +4430,7 @@ async function watch(config, onChange) {
|
|
|
3494
4430
|
}
|
|
3495
4431
|
|
|
3496
4432
|
// src/index.ts
|
|
3497
|
-
var VERSION = "0.
|
|
4433
|
+
var VERSION = "0.5.0";
|
|
3498
4434
|
|
|
3499
4435
|
// src/cli/codegen.ts
|
|
3500
4436
|
async function runCodegen(opts = {}) {
|
|
@@ -3523,13 +4459,13 @@ async function runCodegen(opts = {}) {
|
|
|
3523
4459
|
// src/cli/doctor.ts
|
|
3524
4460
|
var import_node_child_process2 = require("child_process");
|
|
3525
4461
|
var import_node_fs4 = require("fs");
|
|
3526
|
-
var
|
|
4462
|
+
var import_node_path18 = require("path");
|
|
3527
4463
|
|
|
3528
4464
|
// src/cli/init.ts
|
|
3529
4465
|
var import_node_child_process = require("child_process");
|
|
3530
4466
|
var import_node_fs3 = require("fs");
|
|
3531
|
-
var
|
|
3532
|
-
var
|
|
4467
|
+
var import_promises15 = require("fs/promises");
|
|
4468
|
+
var import_node_path17 = require("path");
|
|
3533
4469
|
var import_node_readline = require("readline");
|
|
3534
4470
|
|
|
3535
4471
|
// src/cli/patch-utils.ts
|
|
@@ -3591,7 +4527,7 @@ ${bold(title)}`);
|
|
|
3591
4527
|
}
|
|
3592
4528
|
async function readPackageJson(cwd) {
|
|
3593
4529
|
try {
|
|
3594
|
-
const raw = await (0,
|
|
4530
|
+
const raw = await (0, import_promises15.readFile)((0, import_node_path17.join)(cwd, "package.json"), "utf8");
|
|
3595
4531
|
return JSON.parse(raw);
|
|
3596
4532
|
} catch {
|
|
3597
4533
|
return {};
|
|
@@ -3622,7 +4558,7 @@ async function detectTemplateEngine(cwd) {
|
|
|
3622
4558
|
async function detectPackageManager(cwd) {
|
|
3623
4559
|
async function exists(file) {
|
|
3624
4560
|
try {
|
|
3625
|
-
await (0,
|
|
4561
|
+
await (0, import_promises15.access)((0, import_node_path17.join)(cwd, file));
|
|
3626
4562
|
return true;
|
|
3627
4563
|
} catch {
|
|
3628
4564
|
return false;
|
|
@@ -3650,7 +4586,7 @@ async function promptFramework() {
|
|
|
3650
4586
|
}
|
|
3651
4587
|
async function fileExists2(filePath) {
|
|
3652
4588
|
try {
|
|
3653
|
-
await (0,
|
|
4589
|
+
await (0, import_promises15.access)(filePath);
|
|
3654
4590
|
return true;
|
|
3655
4591
|
} catch {
|
|
3656
4592
|
return false;
|
|
@@ -3663,15 +4599,15 @@ async function writeIfNotExists(filePath, content, label) {
|
|
|
3663
4599
|
}
|
|
3664
4600
|
const dir = filePath.substring(0, filePath.lastIndexOf("/"));
|
|
3665
4601
|
if (dir) {
|
|
3666
|
-
await (0,
|
|
4602
|
+
await (0, import_promises15.mkdir)(dir, { recursive: true });
|
|
3667
4603
|
}
|
|
3668
|
-
await (0,
|
|
4604
|
+
await (0, import_promises15.writeFile)(filePath, content, "utf8");
|
|
3669
4605
|
logCreated(label);
|
|
3670
4606
|
}
|
|
3671
4607
|
async function handleViteConfig(cwd, framework) {
|
|
3672
|
-
const filePath = (0,
|
|
4608
|
+
const filePath = (0, import_node_path17.join)(cwd, "vite.config.ts");
|
|
3673
4609
|
if (await fileExists2(filePath)) {
|
|
3674
|
-
const existing = await (0,
|
|
4610
|
+
const existing = await (0, import_promises15.readFile)(filePath, "utf8");
|
|
3675
4611
|
const hasPlugin = existing.includes("nestInertia") || existing.includes("nestjs-inertia-vite/plugin");
|
|
3676
4612
|
if (!hasPlugin) {
|
|
3677
4613
|
logSkipped("vite.config.ts");
|
|
@@ -3687,15 +4623,15 @@ async function handleViteConfig(cwd, framework) {
|
|
|
3687
4623
|
}
|
|
3688
4624
|
const dir = filePath.substring(0, filePath.lastIndexOf("/"));
|
|
3689
4625
|
if (dir) {
|
|
3690
|
-
await (0,
|
|
4626
|
+
await (0, import_promises15.mkdir)(dir, { recursive: true });
|
|
3691
4627
|
}
|
|
3692
|
-
await (0,
|
|
4628
|
+
await (0, import_promises15.writeFile)(filePath, viteConfigTemplate(framework), "utf8");
|
|
3693
4629
|
logCreated("vite.config.ts");
|
|
3694
4630
|
}
|
|
3695
4631
|
async function patchGitignore(gitignorePath) {
|
|
3696
4632
|
let existing = "";
|
|
3697
4633
|
if (await fileExists2(gitignorePath)) {
|
|
3698
|
-
existing = await (0,
|
|
4634
|
+
existing = await (0, import_promises15.readFile)(gitignorePath, "utf8");
|
|
3699
4635
|
}
|
|
3700
4636
|
if (existing.split("\n").some((line) => line.trim() === GITIGNORE_ENTRY)) {
|
|
3701
4637
|
console.log(` ${cyan("\u2192")} .gitignore ${dim("(already contains .nestjs-inertia/, skipped)")}`);
|
|
@@ -3705,7 +4641,7 @@ async function patchGitignore(gitignorePath) {
|
|
|
3705
4641
|
` : `${existing}
|
|
3706
4642
|
${GITIGNORE_ENTRY}
|
|
3707
4643
|
`;
|
|
3708
|
-
await (0,
|
|
4644
|
+
await (0, import_promises15.writeFile)(gitignorePath, newContent, "utf8");
|
|
3709
4645
|
logPatched(".gitignore", "added .nestjs-inertia/");
|
|
3710
4646
|
}
|
|
3711
4647
|
function installDeps(pkgManager, deps, dev) {
|
|
@@ -3727,10 +4663,10 @@ function installDeps(pkgManager, deps, dev) {
|
|
|
3727
4663
|
}
|
|
3728
4664
|
}
|
|
3729
4665
|
async function patchPackageJsonScripts(cwd, scripts) {
|
|
3730
|
-
const pkgPath = (0,
|
|
4666
|
+
const pkgPath = (0, import_node_path17.join)(cwd, "package.json");
|
|
3731
4667
|
let pkg = {};
|
|
3732
4668
|
try {
|
|
3733
|
-
pkg = JSON.parse(await (0,
|
|
4669
|
+
pkg = JSON.parse(await (0, import_promises15.readFile)(pkgPath, "utf8"));
|
|
3734
4670
|
} catch {
|
|
3735
4671
|
return;
|
|
3736
4672
|
}
|
|
@@ -3749,7 +4685,7 @@ async function patchPackageJsonScripts(cwd, scripts) {
|
|
|
3749
4685
|
return;
|
|
3750
4686
|
}
|
|
3751
4687
|
pkg.scripts = existing;
|
|
3752
|
-
await (0,
|
|
4688
|
+
await (0, import_promises15.writeFile)(pkgPath, `${JSON.stringify(pkg, null, 2)}
|
|
3753
4689
|
`, "utf8");
|
|
3754
4690
|
}
|
|
3755
4691
|
function patchAppModule(filePath, rootView) {
|
|
@@ -4044,7 +4980,7 @@ export class HomeController {
|
|
|
4044
4980
|
}
|
|
4045
4981
|
`;
|
|
4046
4982
|
function patchTsconfigExclude(cwd, dir, filename = "tsconfig.json") {
|
|
4047
|
-
const filePath = (0,
|
|
4983
|
+
const filePath = (0, import_node_path17.join)(cwd, filename);
|
|
4048
4984
|
return patchJsonFile(
|
|
4049
4985
|
filePath,
|
|
4050
4986
|
(json) => {
|
|
@@ -4059,7 +4995,7 @@ function patchTsconfigExclude(cwd, dir, filename = "tsconfig.json") {
|
|
|
4059
4995
|
);
|
|
4060
4996
|
}
|
|
4061
4997
|
function patchNestCliJson(cwd, shellDir) {
|
|
4062
|
-
const filePath = (0,
|
|
4998
|
+
const filePath = (0, import_node_path17.join)(cwd, "nest-cli.json");
|
|
4063
4999
|
return patchJsonFile(filePath, (json) => {
|
|
4064
5000
|
const compiler = json.compilerOptions ?? {};
|
|
4065
5001
|
const assets = compiler.assets ?? [];
|
|
@@ -4082,46 +5018,46 @@ async function scaffoldFiles(ctx) {
|
|
|
4082
5018
|
const { cwd, framework, engine, shellFileName, entryExt, pageExt } = ctx;
|
|
4083
5019
|
logSection("Scaffold files");
|
|
4084
5020
|
await writeIfNotExists(
|
|
4085
|
-
(0,
|
|
5021
|
+
(0, import_node_path17.join)(cwd, "nestjs-inertia.config.ts"),
|
|
4086
5022
|
configTemplate(framework),
|
|
4087
5023
|
"nestjs-inertia.config.ts"
|
|
4088
5024
|
);
|
|
4089
|
-
await writeIfNotExists((0,
|
|
5025
|
+
await writeIfNotExists((0, import_node_path17.join)(cwd, "nestjs-inertia.d.ts"), DTS_TEMPLATE, "nestjs-inertia.d.ts");
|
|
4090
5026
|
await writeIfNotExists(
|
|
4091
|
-
(0,
|
|
5027
|
+
(0, import_node_path17.join)(cwd, "tsconfig.inertia.json"),
|
|
4092
5028
|
TSCONFIG_INERTIA_TEMPLATE,
|
|
4093
5029
|
"tsconfig.inertia.json"
|
|
4094
5030
|
);
|
|
4095
5031
|
await writeIfNotExists(
|
|
4096
|
-
(0,
|
|
5032
|
+
(0, import_node_path17.join)(cwd, "inertia", "tsconfig.json"),
|
|
4097
5033
|
INERTIA_TSCONFIG_TEMPLATE,
|
|
4098
5034
|
"inertia/tsconfig.json"
|
|
4099
5035
|
);
|
|
4100
5036
|
await writeIfNotExists(
|
|
4101
|
-
(0,
|
|
5037
|
+
(0, import_node_path17.join)(cwd, "inertia", shellFileName),
|
|
4102
5038
|
htmlShellTemplate(framework, engine),
|
|
4103
5039
|
`inertia/${shellFileName}`
|
|
4104
5040
|
);
|
|
4105
5041
|
await handleViteConfig(cwd, framework);
|
|
4106
5042
|
await writeIfNotExists(
|
|
4107
|
-
(0,
|
|
5043
|
+
(0, import_node_path17.join)(cwd, "inertia", "app", `client.${entryExt}`),
|
|
4108
5044
|
entryPointTemplate(framework),
|
|
4109
5045
|
`inertia/app/client.${entryExt}`
|
|
4110
5046
|
);
|
|
4111
5047
|
await writeIfNotExists(
|
|
4112
|
-
(0,
|
|
5048
|
+
(0, import_node_path17.join)(cwd, "inertia", "pages", `Home.${pageExt}`),
|
|
4113
5049
|
samplePageTemplate(framework),
|
|
4114
5050
|
`inertia/pages/Home.${pageExt}`
|
|
4115
5051
|
);
|
|
4116
5052
|
await writeIfNotExists(
|
|
4117
|
-
(0,
|
|
5053
|
+
(0, import_node_path17.join)(cwd, "src", "home.controller.ts"),
|
|
4118
5054
|
SAMPLE_CONTROLLER,
|
|
4119
5055
|
"src/home.controller.ts"
|
|
4120
5056
|
);
|
|
4121
5057
|
}
|
|
4122
5058
|
function patchServerAppModule(ctx) {
|
|
4123
5059
|
const { cwd, rootView } = ctx;
|
|
4124
|
-
const appModulePath = (0,
|
|
5060
|
+
const appModulePath = (0, import_node_path17.join)(cwd, "src", "app.module.ts");
|
|
4125
5061
|
const appModuleResult = patchAppModule(appModulePath, rootView);
|
|
4126
5062
|
if (appModuleResult === "patched") {
|
|
4127
5063
|
logPatched("src/app.module.ts", "added InertiaModule.forRoot");
|
|
@@ -4135,7 +5071,7 @@ function patchServerAppModule(ctx) {
|
|
|
4135
5071
|
}
|
|
4136
5072
|
}
|
|
4137
5073
|
function patchServerMainTs(ctx) {
|
|
4138
|
-
const mainTsPath = (0,
|
|
5074
|
+
const mainTsPath = (0, import_node_path17.join)(ctx.cwd, "src", "main.ts");
|
|
4139
5075
|
const mainTsResult = patchMainTs(mainTsPath);
|
|
4140
5076
|
if (mainTsResult === "patched") {
|
|
4141
5077
|
logPatched("src/main.ts", "added setupInertiaVite after NestFactory.create");
|
|
@@ -4170,7 +5106,7 @@ function patchBuildConfigs(ctx) {
|
|
|
4170
5106
|
}
|
|
4171
5107
|
async function patchGitignoreAndDist(ctx) {
|
|
4172
5108
|
const { cwd } = ctx;
|
|
4173
|
-
await patchGitignore((0,
|
|
5109
|
+
await patchGitignore((0, import_node_path17.join)(cwd, ".gitignore"));
|
|
4174
5110
|
const tsconfigDistResult = patchTsconfigExclude(cwd, "dist", "tsconfig.json");
|
|
4175
5111
|
if (tsconfigDistResult === "patched") {
|
|
4176
5112
|
logPatched("tsconfig.json", "excluded dist/ from server compilation");
|
|
@@ -4276,7 +5212,7 @@ ${green("\u2713")} Setup complete! Run: ${bold("nest start --watch")}
|
|
|
4276
5212
|
|
|
4277
5213
|
// src/cli/doctor.ts
|
|
4278
5214
|
function checkFileExists(cwd, file) {
|
|
4279
|
-
return (0, import_node_fs4.existsSync)((0,
|
|
5215
|
+
return (0, import_node_fs4.existsSync)((0, import_node_path18.join)(cwd, file));
|
|
4280
5216
|
}
|
|
4281
5217
|
function readJson(path) {
|
|
4282
5218
|
try {
|
|
@@ -4312,15 +5248,15 @@ function writeJsonField(filePath, dotPath, value) {
|
|
|
4312
5248
|
}
|
|
4313
5249
|
function getPackageVersion(cwd, pkg) {
|
|
4314
5250
|
try {
|
|
4315
|
-
const pkgJson = readJson((0,
|
|
5251
|
+
const pkgJson = readJson((0, import_node_path18.join)(cwd, "node_modules", pkg, "package.json"));
|
|
4316
5252
|
return pkgJson?.version ?? null;
|
|
4317
5253
|
} catch {
|
|
4318
5254
|
return null;
|
|
4319
5255
|
}
|
|
4320
5256
|
}
|
|
4321
5257
|
function detectPkgManager(cwd) {
|
|
4322
|
-
if ((0, import_node_fs4.existsSync)((0,
|
|
4323
|
-
if ((0, import_node_fs4.existsSync)((0,
|
|
5258
|
+
if ((0, import_node_fs4.existsSync)((0, import_node_path18.join)(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
5259
|
+
if ((0, import_node_fs4.existsSync)((0, import_node_path18.join)(cwd, "yarn.lock"))) return "yarn";
|
|
4324
5260
|
return "npm";
|
|
4325
5261
|
}
|
|
4326
5262
|
async function runDoctor(opts) {
|
|
@@ -4358,7 +5294,7 @@ async function runDoctor(opts) {
|
|
|
4358
5294
|
autoFix: () => runInit({ cwd })
|
|
4359
5295
|
});
|
|
4360
5296
|
if (foundShellDir) {
|
|
4361
|
-
const nestCliPath = (0,
|
|
5297
|
+
const nestCliPath = (0, import_node_path18.join)(cwd, "nest-cli.json");
|
|
4362
5298
|
const nestCli = readJson(nestCliPath);
|
|
4363
5299
|
const compiler = nestCli?.compilerOptions ?? {};
|
|
4364
5300
|
const assets = compiler.assets ?? [];
|
|
@@ -4397,7 +5333,7 @@ async function runDoctor(opts) {
|
|
|
4397
5333
|
fix: "Run: nestjs-codegen codegen",
|
|
4398
5334
|
autoFix: () => runCodegen({ cwd })
|
|
4399
5335
|
});
|
|
4400
|
-
const tsconfigPath = (0,
|
|
5336
|
+
const tsconfigPath = (0, import_node_path18.join)(cwd, "tsconfig.json");
|
|
4401
5337
|
const tsconfig = readJson(tsconfigPath);
|
|
4402
5338
|
const paths = tsconfig?.compilerOptions?.paths;
|
|
4403
5339
|
checks.push({
|
|
@@ -4408,7 +5344,7 @@ async function runDoctor(opts) {
|
|
|
4408
5344
|
});
|
|
4409
5345
|
const inertiaDir = foundShellDir ?? "inertia";
|
|
4410
5346
|
for (const tsconfigFile of ["tsconfig.json", "tsconfig.build.json"]) {
|
|
4411
|
-
const tsc = readJson((0,
|
|
5347
|
+
const tsc = readJson((0, import_node_path18.join)(cwd, tsconfigFile));
|
|
4412
5348
|
if (!tsc) continue;
|
|
4413
5349
|
const excl = tsc.exclude ?? [];
|
|
4414
5350
|
const excludesIt = excl.includes(inertiaDir);
|
|
@@ -4434,7 +5370,7 @@ async function runDoctor(opts) {
|
|
|
4434
5370
|
}
|
|
4435
5371
|
});
|
|
4436
5372
|
}
|
|
4437
|
-
const inertiaTsconfigPath = (0,
|
|
5373
|
+
const inertiaTsconfigPath = (0, import_node_path18.join)(cwd, "tsconfig.inertia.json");
|
|
4438
5374
|
const inertiaTsconfig = readJson(inertiaTsconfigPath);
|
|
4439
5375
|
checks.push({
|
|
4440
5376
|
name: "tsconfig.inertia.json exists",
|
|
@@ -4486,7 +5422,7 @@ async function runDoctor(opts) {
|
|
|
4486
5422
|
fix: 'Add "nestjs-inertia.d.ts" to include array (resolves InertiaRegistry augmentation)'
|
|
4487
5423
|
});
|
|
4488
5424
|
}
|
|
4489
|
-
const innerTsconfigPath = (0,
|
|
5425
|
+
const innerTsconfigPath = (0, import_node_path18.join)(cwd, "inertia", "tsconfig.json");
|
|
4490
5426
|
checks.push({
|
|
4491
5427
|
name: "inertia/tsconfig.json exists (VSCode picks up ~codegen alias)",
|
|
4492
5428
|
pass: (0, import_node_fs4.existsSync)(innerTsconfigPath),
|
|
@@ -4496,7 +5432,7 @@ async function runDoctor(opts) {
|
|
|
4496
5432
|
}
|
|
4497
5433
|
});
|
|
4498
5434
|
if (checkFileExists(cwd, "vite.config.ts")) {
|
|
4499
|
-
const viteContent = (0, import_node_fs4.readFileSync)((0,
|
|
5435
|
+
const viteContent = (0, import_node_fs4.readFileSync)((0, import_node_path18.join)(cwd, "vite.config.ts"), "utf8");
|
|
4500
5436
|
checks.push({
|
|
4501
5437
|
name: "vite.config.ts has resolve.alias",
|
|
4502
5438
|
pass: viteContent.includes("resolve") && viteContent.includes("alias"),
|
|
@@ -4561,7 +5497,7 @@ async function runDoctor(opts) {
|
|
|
4561
5497
|
});
|
|
4562
5498
|
}
|
|
4563
5499
|
if (checkFileExists(cwd, ".gitignore")) {
|
|
4564
|
-
const gitignorePath = (0,
|
|
5500
|
+
const gitignorePath = (0, import_node_path18.join)(cwd, ".gitignore");
|
|
4565
5501
|
const gitignore = (0, import_node_fs4.readFileSync)(gitignorePath, "utf8");
|
|
4566
5502
|
checks.push({
|
|
4567
5503
|
name: ".gitignore includes .nestjs-inertia/",
|
|
@@ -4570,7 +5506,7 @@ async function runDoctor(opts) {
|
|
|
4570
5506
|
autoFix: () => (0, import_node_fs4.appendFileSync)(gitignorePath, "\n.nestjs-inertia/\n")
|
|
4571
5507
|
});
|
|
4572
5508
|
}
|
|
4573
|
-
const pkgJsonPath = (0,
|
|
5509
|
+
const pkgJsonPath = (0, import_node_path18.join)(cwd, "package.json");
|
|
4574
5510
|
const pkgJson = readJson(pkgJsonPath);
|
|
4575
5511
|
const scripts = pkgJson?.scripts ?? {};
|
|
4576
5512
|
checks.push({
|