@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.js
CHANGED
|
@@ -126,6 +126,19 @@ function applyDefaults(userConfig, cwd) {
|
|
|
126
126
|
enabled: userConfig.forms?.enabled ?? true,
|
|
127
127
|
watch: userConfig.forms?.watch ?? "src/**/*.dto.ts",
|
|
128
128
|
zodImport: userConfig.forms?.zodImport ?? "zod"
|
|
129
|
+
},
|
|
130
|
+
openapi: {
|
|
131
|
+
enabled: userConfig.openapi?.enabled ?? false,
|
|
132
|
+
fileName: userConfig.openapi?.fileName ?? "openapi.json",
|
|
133
|
+
title: userConfig.openapi?.title ?? "NestJS API",
|
|
134
|
+
version: userConfig.openapi?.version ?? "1.0.0",
|
|
135
|
+
description: userConfig.openapi?.description ?? null
|
|
136
|
+
},
|
|
137
|
+
mocks: {
|
|
138
|
+
enabled: userConfig.mocks?.enabled ?? false,
|
|
139
|
+
fileName: userConfig.mocks?.fileName ?? "mocks.ts",
|
|
140
|
+
seed: userConfig.mocks?.seed ?? 1,
|
|
141
|
+
baseUrl: userConfig.mocks?.baseUrl ?? ""
|
|
129
142
|
}
|
|
130
143
|
};
|
|
131
144
|
}
|
|
@@ -163,8 +176,8 @@ Run \`nestjs-codegen init\` to create a starter config.`
|
|
|
163
176
|
}
|
|
164
177
|
|
|
165
178
|
// src/generate.ts
|
|
166
|
-
import { mkdir as
|
|
167
|
-
import { dirname as dirname2, join as
|
|
179
|
+
import { mkdir as mkdir9, writeFile as writeFile9 } from "fs/promises";
|
|
180
|
+
import { dirname as dirname2, join as join12 } from "path";
|
|
168
181
|
|
|
169
182
|
// src/discovery/pages.ts
|
|
170
183
|
import { readFile } from "fs/promises";
|
|
@@ -693,17 +706,28 @@ function emitFilterQueryType(c) {
|
|
|
693
706
|
return `import('@dudousxd/nestjs-filter-client').TypedFilterQuery<${emitFilterQueryTypeArgs(c)}>`;
|
|
694
707
|
}
|
|
695
708
|
function buildResponseType(c, outDir) {
|
|
709
|
+
const respRef = c.contractSource.responseRef;
|
|
710
|
+
if (c.contractSource.stream) {
|
|
711
|
+
if (respRef) return respRef.isArray ? `Array<${respRef.name}>` : respRef.name;
|
|
712
|
+
return c.contractSource.response;
|
|
713
|
+
}
|
|
696
714
|
if (c.controllerRef) {
|
|
697
715
|
let relPath = relative3(outDir, c.controllerRef.filePath).replace(/\.ts$/, "");
|
|
698
716
|
if (!relPath.startsWith(".")) relPath = `./${relPath}`;
|
|
699
717
|
return `Awaited<ReturnType<import('${relPath}').${c.controllerRef.className}['${c.controllerRef.methodName}']>>`;
|
|
700
718
|
}
|
|
701
|
-
const respRef = c.contractSource.responseRef;
|
|
702
719
|
if (respRef) {
|
|
703
720
|
return respRef.isArray ? `Array<${respRef.name}>` : respRef.name;
|
|
704
721
|
}
|
|
705
722
|
return c.contractSource.response;
|
|
706
723
|
}
|
|
724
|
+
function buildErrorType(c) {
|
|
725
|
+
const errRef = c.contractSource.errorRef;
|
|
726
|
+
if (errRef) {
|
|
727
|
+
return errRef.isArray ? `Array<${errRef.name}>` : errRef.name;
|
|
728
|
+
}
|
|
729
|
+
return c.contractSource.error ?? "unknown";
|
|
730
|
+
}
|
|
707
731
|
function emitRouterTypeBlock(tree, indent, outDir) {
|
|
708
732
|
const pad = " ".repeat(indent);
|
|
709
733
|
const lines = [];
|
|
@@ -718,12 +742,14 @@ function emitRouterTypeBlock(tree, indent, outDir) {
|
|
|
718
742
|
const bodyRef = c.contractSource.bodyRef;
|
|
719
743
|
const body = method === "GET" ? "never" : bodyRef ? bodyRef.isArray ? `Array<${bodyRef.name}>` : bodyRef.name : c.contractSource.body ?? "never";
|
|
720
744
|
const response = buildResponseType(c, outDir);
|
|
745
|
+
const error = buildErrorType(c);
|
|
721
746
|
const params = buildParamsType(c.params);
|
|
722
747
|
const safeMethod = JSON.stringify(method);
|
|
723
748
|
const safeUrl = JSON.stringify(c.path);
|
|
724
749
|
const filterFields = c.contractSource.filterFields?.length ? c.contractSource.filterFields.map((f) => JSON.stringify(f)).join(" | ") : "never";
|
|
750
|
+
const stream = c.contractSource.stream ? "true" : "false";
|
|
725
751
|
lines.push(
|
|
726
|
-
`${pad}${objKey}: { method: ${safeMethod}; url: ${safeUrl}; params: ${params}; query: ${query}; body: ${body}; response: ${response}; filterFields: ${filterFields} };`
|
|
752
|
+
`${pad}${objKey}: { method: ${safeMethod}; url: ${safeUrl}; params: ${params}; query: ${query}; body: ${body}; response: ${response}; error: ${error}; filterFields: ${filterFields}; stream: ${stream} };`
|
|
727
753
|
);
|
|
728
754
|
} else {
|
|
729
755
|
lines.push(`${pad}${objKey}: {`);
|
|
@@ -795,15 +821,21 @@ function emitReqHelper() {
|
|
|
795
821
|
""
|
|
796
822
|
];
|
|
797
823
|
}
|
|
798
|
-
function renderLeaf(pad, objKey, req, requestExpr, members) {
|
|
824
|
+
function renderLeaf(pad, objKey, req, requestExpr, members, streamExpr) {
|
|
799
825
|
const lines = [`${pad}${objKey}: (input?: ${req.inputType}) => ({`];
|
|
800
826
|
lines.push(`${pad} ...__req<${req.responseType}>(() => ${requestExpr}),`);
|
|
827
|
+
if (streamExpr) {
|
|
828
|
+
lines.push(`${pad} stream: () => ${streamExpr},`);
|
|
829
|
+
}
|
|
801
830
|
for (const [name, value] of Object.entries(members)) {
|
|
802
831
|
lines.push(`${pad} ${name}: ${value},`);
|
|
803
832
|
}
|
|
804
833
|
lines.push(`${pad}}),`);
|
|
805
834
|
return lines;
|
|
806
835
|
}
|
|
836
|
+
function renderStreamExpr(req) {
|
|
837
|
+
return `fetcher.sse<${req.responseType}>(${req.urlExpr}, ${req.optsExpr})`;
|
|
838
|
+
}
|
|
807
839
|
function emitApiObjectBlock(tree, indent, p) {
|
|
808
840
|
const pad = " ".repeat(indent);
|
|
809
841
|
const lines = [];
|
|
@@ -838,7 +870,8 @@ function emitApiObjectBlock(tree, indent, p) {
|
|
|
838
870
|
}
|
|
839
871
|
const members = {};
|
|
840
872
|
for (const [name, { value }] of owned) members[name] = value;
|
|
841
|
-
|
|
873
|
+
const streamExpr = node.contractSource.stream ? renderStreamExpr(req) : void 0;
|
|
874
|
+
lines.push(...renderLeaf(pad, objKey, req, leaf.requestExpr, members, streamExpr));
|
|
842
875
|
}
|
|
843
876
|
return lines;
|
|
844
877
|
}
|
|
@@ -876,6 +909,8 @@ var ROUTE_NAMESPACE = [
|
|
|
876
909
|
' export type Params<K extends string> = ResolveByName<K, "params">;',
|
|
877
910
|
' export type Error<K extends string> = ResolveByName<K, "error">;',
|
|
878
911
|
' export type FilterFields<K extends string> = ResolveByName<K, "filterFields">;',
|
|
912
|
+
" /** The streamed element type of an `@Sse()`/streaming route \u2014 the type yielded by its `stream()` AsyncIterable. */",
|
|
913
|
+
' export type Stream<K extends string> = ResolveByName<K, "response">;',
|
|
879
914
|
" export type Request<K extends string> = {",
|
|
880
915
|
" body: Body<K>;",
|
|
881
916
|
" query: Query<K>;",
|
|
@@ -892,6 +927,7 @@ var PATH_NAMESPACE = [
|
|
|
892
927
|
' export type Params<M extends string, U extends string> = ResolveByPath<M, U, "params">;',
|
|
893
928
|
' export type Error<M extends string, U extends string> = ResolveByPath<M, U, "error">;',
|
|
894
929
|
' export type FilterFields<M extends string, U extends string> = ResolveByPath<M, U, "filterFields">;',
|
|
930
|
+
' export type Stream<M extends string, U extends string> = ResolveByPath<M, U, "response">;',
|
|
895
931
|
"}",
|
|
896
932
|
""
|
|
897
933
|
];
|
|
@@ -903,6 +939,7 @@ var EMPTY_ROUTE_NAMESPACE = [
|
|
|
903
939
|
" export type Params<K extends string> = never;",
|
|
904
940
|
" export type Error<K extends string> = never;",
|
|
905
941
|
" export type FilterFields<K extends string> = never;",
|
|
942
|
+
" export type Stream<K extends string> = never;",
|
|
906
943
|
" export type Request<K extends string> = { body: never; query: never; params: never };",
|
|
907
944
|
"}",
|
|
908
945
|
""
|
|
@@ -915,6 +952,7 @@ var EMPTY_PATH_NAMESPACE = [
|
|
|
915
952
|
" export type Params<M extends string, U extends string> = never;",
|
|
916
953
|
" export type Error<M extends string, U extends string> = never;",
|
|
917
954
|
" export type FilterFields<M extends string, U extends string> = never;",
|
|
955
|
+
" export type Stream<M extends string, U extends string> = never;",
|
|
918
956
|
"}",
|
|
919
957
|
""
|
|
920
958
|
];
|
|
@@ -938,7 +976,7 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
938
976
|
for (const r of contracted) {
|
|
939
977
|
const cs = r.contract?.contractSource;
|
|
940
978
|
if (!cs) continue;
|
|
941
|
-
const refs = r.controllerRef ? [cs.queryRef, cs.bodyRef] : [cs.queryRef, cs.bodyRef, cs.responseRef];
|
|
979
|
+
const refs = r.controllerRef && !cs.stream ? [cs.queryRef, cs.bodyRef, cs.errorRef] : [cs.queryRef, cs.bodyRef, cs.responseRef, cs.errorRef];
|
|
942
980
|
for (const ref of refs) {
|
|
943
981
|
if (!ref) continue;
|
|
944
982
|
let names = importsByFile.get(ref.filePath);
|
|
@@ -1371,11 +1409,467 @@ async function emitIndex(outDir, hasContracts = false, hasForms = false) {
|
|
|
1371
1409
|
await writeFile4(join7(outDir, "index.d.ts"), content, "utf8");
|
|
1372
1410
|
}
|
|
1373
1411
|
|
|
1374
|
-
// src/emit/emit-
|
|
1412
|
+
// src/emit/emit-mocks.ts
|
|
1375
1413
|
import { mkdir as mkdir5, writeFile as writeFile5 } from "fs/promises";
|
|
1376
|
-
import { join as join8
|
|
1377
|
-
|
|
1414
|
+
import { join as join8 } from "path";
|
|
1415
|
+
|
|
1416
|
+
// src/ir/schema-node-to-json-schema.ts
|
|
1417
|
+
var DEFAULT_CTX = { refPrefix: "#/components/schemas/" };
|
|
1418
|
+
function parseLiteral(raw) {
|
|
1419
|
+
const t = raw.trim();
|
|
1420
|
+
if (t === "true") return true;
|
|
1421
|
+
if (t === "false") return false;
|
|
1422
|
+
if (t === "null") return null;
|
|
1423
|
+
const q = t[0];
|
|
1424
|
+
if ((q === "'" || q === '"' || q === "`") && t[t.length - 1] === q) {
|
|
1425
|
+
return t.slice(1, -1).replace(/\\'/g, "'").replace(/\\"/g, '"').replace(/\\`/g, "`").replace(/\\\\/g, "\\");
|
|
1426
|
+
}
|
|
1427
|
+
if (/^[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/.test(t)) {
|
|
1428
|
+
return Number(t);
|
|
1429
|
+
}
|
|
1430
|
+
return t;
|
|
1431
|
+
}
|
|
1432
|
+
function literalsType(values) {
|
|
1433
|
+
const types = new Set(values.map((v) => v === null ? "null" : typeof v));
|
|
1434
|
+
if (types.size === 1) {
|
|
1435
|
+
const only = [...types][0];
|
|
1436
|
+
if (only === "string") return "string";
|
|
1437
|
+
if (only === "number") return "number";
|
|
1438
|
+
if (only === "boolean") return "boolean";
|
|
1439
|
+
}
|
|
1440
|
+
return void 0;
|
|
1441
|
+
}
|
|
1442
|
+
function convert(node, ctx) {
|
|
1443
|
+
switch (node.kind) {
|
|
1444
|
+
case "string": {
|
|
1445
|
+
const out = { type: "string" };
|
|
1446
|
+
for (const c of node.checks) {
|
|
1447
|
+
if (c.check === "email") out.format = "email";
|
|
1448
|
+
else if (c.check === "url") out.format = "uri";
|
|
1449
|
+
else if (c.check === "uuid") out.format = "uuid";
|
|
1450
|
+
else if (c.check === "min") out.minLength = Number(c.value);
|
|
1451
|
+
else if (c.check === "max") out.maxLength = Number(c.value);
|
|
1452
|
+
else if (c.check === "regex") {
|
|
1453
|
+
const m = /^\/(.*)\/[a-z]*$/.exec(c.pattern);
|
|
1454
|
+
out.pattern = m ? m[1] : c.pattern;
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
return out;
|
|
1458
|
+
}
|
|
1459
|
+
case "number": {
|
|
1460
|
+
const out = { type: "number" };
|
|
1461
|
+
for (const c of node.checks) {
|
|
1462
|
+
if (c.check === "int") out.type = "integer";
|
|
1463
|
+
else if (c.check === "min") out.minimum = Number(c.value);
|
|
1464
|
+
else if (c.check === "max") out.maximum = Number(c.value);
|
|
1465
|
+
else if (c.check === "positive") out.exclusiveMinimum = 0;
|
|
1466
|
+
else if (c.check === "negative") out.exclusiveMaximum = 0;
|
|
1467
|
+
}
|
|
1468
|
+
return out;
|
|
1469
|
+
}
|
|
1470
|
+
case "boolean":
|
|
1471
|
+
return { type: "boolean" };
|
|
1472
|
+
case "date":
|
|
1473
|
+
return { type: "string", format: "date-time" };
|
|
1474
|
+
case "unknown":
|
|
1475
|
+
return node.note ? { description: node.note } : {};
|
|
1476
|
+
case "instanceof":
|
|
1477
|
+
return { type: "object", description: `instanceof ${node.ctor}` };
|
|
1478
|
+
case "enum": {
|
|
1479
|
+
const values = node.literals.map(parseLiteral);
|
|
1480
|
+
const t = literalsType(values);
|
|
1481
|
+
const out = { enum: values };
|
|
1482
|
+
if (t) out.type = t;
|
|
1483
|
+
return out;
|
|
1484
|
+
}
|
|
1485
|
+
case "literal": {
|
|
1486
|
+
const value = parseLiteral(node.raw);
|
|
1487
|
+
const out = { const: value };
|
|
1488
|
+
const t = literalsType([value]);
|
|
1489
|
+
if (t) out.type = t;
|
|
1490
|
+
return out;
|
|
1491
|
+
}
|
|
1492
|
+
case "union": {
|
|
1493
|
+
const options = node.options.map((o) => convert(o, ctx));
|
|
1494
|
+
const out = { oneOf: options };
|
|
1495
|
+
if (node.discriminator) {
|
|
1496
|
+
out.discriminator = { propertyName: node.discriminator };
|
|
1497
|
+
}
|
|
1498
|
+
return out;
|
|
1499
|
+
}
|
|
1500
|
+
case "object": {
|
|
1501
|
+
const properties = {};
|
|
1502
|
+
const required = [];
|
|
1503
|
+
for (const f of node.fields) {
|
|
1504
|
+
if (f.value.kind === "optional") {
|
|
1505
|
+
properties[f.key] = convert(f.value.inner, ctx);
|
|
1506
|
+
} else {
|
|
1507
|
+
properties[f.key] = convert(f.value, ctx);
|
|
1508
|
+
required.push(f.key);
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
const out = { type: "object", properties };
|
|
1512
|
+
if (required.length > 0) out.required = required;
|
|
1513
|
+
out.additionalProperties = node.passthrough;
|
|
1514
|
+
return out;
|
|
1515
|
+
}
|
|
1516
|
+
case "array":
|
|
1517
|
+
return { type: "array", items: convert(node.element, ctx) };
|
|
1518
|
+
case "optional":
|
|
1519
|
+
return widenNullable(convert(node.inner, ctx));
|
|
1520
|
+
case "ref":
|
|
1521
|
+
case "lazyRef":
|
|
1522
|
+
return { $ref: `${ctx.refPrefix}${node.name}` };
|
|
1523
|
+
case "annotated":
|
|
1524
|
+
return convert(node.inner, ctx);
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
function widenNullable(schema) {
|
|
1528
|
+
if (schema.$ref) {
|
|
1529
|
+
return { anyOf: [schema, { type: "null" }] };
|
|
1530
|
+
}
|
|
1531
|
+
if (typeof schema.type === "string") {
|
|
1532
|
+
return { ...schema, type: [schema.type, "null"] };
|
|
1533
|
+
}
|
|
1534
|
+
if (Array.isArray(schema.type)) {
|
|
1535
|
+
return schema.type.includes("null") ? schema : { ...schema, type: [...schema.type, "null"] };
|
|
1536
|
+
}
|
|
1537
|
+
return { anyOf: [schema, { type: "null" }] };
|
|
1538
|
+
}
|
|
1539
|
+
function schemaModuleToJsonSchema(mod, ctx = DEFAULT_CTX) {
|
|
1540
|
+
const named = {};
|
|
1541
|
+
for (const [name, node] of mod.named) {
|
|
1542
|
+
named[name] = convert(node, ctx);
|
|
1543
|
+
}
|
|
1544
|
+
return { root: convert(mod.root, ctx), named };
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
// src/emit/mock-gen-runtime.ts
|
|
1548
|
+
var MOCK_GEN_RUNTIME = `
|
|
1549
|
+
/** mulberry32 \u2014 a tiny, fast, seedable PRNG. \`next()\` returns a float in [0, 1). */
|
|
1550
|
+
function makeRng(seed) {
|
|
1551
|
+
let a = seed >>> 0;
|
|
1552
|
+
return {
|
|
1553
|
+
next() {
|
|
1554
|
+
a |= 0;
|
|
1555
|
+
a = (a + 0x6d2b79f5) | 0;
|
|
1556
|
+
let t = Math.imul(a ^ (a >>> 15), 1 | a);
|
|
1557
|
+
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
|
1558
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
1559
|
+
},
|
|
1560
|
+
};
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
function __pick(rng, items) {
|
|
1564
|
+
return items[Math.floor(rng.next() * items.length)];
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
function __intBetween(rng, min, max) {
|
|
1568
|
+
return Math.floor(rng.next() * (max - min + 1)) + min;
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
const __WORDS = ['lorem', 'ipsum', 'dolor', 'sit', 'amet', 'consectetur', 'adipiscing', 'elit', 'sed', 'tempor'];
|
|
1572
|
+
const __FIRST_NAMES = ['Ada', 'Alan', 'Grace', 'Linus', 'Margaret', 'Dennis'];
|
|
1573
|
+
const __LAST_NAMES = ['Lovelace', 'Turing', 'Hopper', 'Torvalds', 'Hamilton', 'Ritchie'];
|
|
1574
|
+
|
|
1575
|
+
function __fakeWords(rng, count) {
|
|
1576
|
+
let out = [];
|
|
1577
|
+
for (let i = 0; i < count; i++) out.push(__pick(rng, __WORDS));
|
|
1578
|
+
return out.join(' ');
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
function __hex(rng, len) {
|
|
1582
|
+
let s = '';
|
|
1583
|
+
for (let i = 0; i < len; i++) s += Math.floor(rng.next() * 16).toString(16);
|
|
1584
|
+
return s;
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
function __fakeUuid(rng) {
|
|
1588
|
+
return __hex(rng, 8) + '-' + __hex(rng, 4) + '-4' + __hex(rng, 3) + '-' + __pick(rng, ['8', '9', 'a', 'b']) + __hex(rng, 3) + '-' + __hex(rng, 12);
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
function __fakeString(rng, schema) {
|
|
1592
|
+
switch (schema.format) {
|
|
1593
|
+
case 'email':
|
|
1594
|
+
return __pick(rng, __FIRST_NAMES).toLowerCase() + '.' + __pick(rng, __LAST_NAMES).toLowerCase() + '@example.com';
|
|
1595
|
+
case 'uri':
|
|
1596
|
+
case 'url':
|
|
1597
|
+
return 'https://example.com/' + __pick(rng, __WORDS);
|
|
1598
|
+
case 'uuid':
|
|
1599
|
+
return __fakeUuid(rng);
|
|
1600
|
+
case 'date-time':
|
|
1601
|
+
return new Date(Date.UTC(2020, __intBetween(rng, 0, 11), __intBetween(rng, 1, 28))).toISOString();
|
|
1602
|
+
default:
|
|
1603
|
+
return __fakeWords(rng, __intBetween(rng, 1, 3));
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
/** Generate a mock value for a JSON Schema node (depth-capped recursion via $ref). */
|
|
1608
|
+
function generateMock(schema, rng, defs, depth) {
|
|
1609
|
+
defs = defs || {};
|
|
1610
|
+
depth = depth || 0;
|
|
1611
|
+
if (schema.$ref) {
|
|
1612
|
+
const name = schema.$ref.replace('#/components/schemas/', '');
|
|
1613
|
+
const target = defs[name];
|
|
1614
|
+
if (!target || depth > 4) return null;
|
|
1615
|
+
return generateMock(target, rng, defs, depth + 1);
|
|
1616
|
+
}
|
|
1617
|
+
if ('const' in schema) return schema.const;
|
|
1618
|
+
if (schema.enum && schema.enum.length > 0) return __pick(rng, schema.enum);
|
|
1619
|
+
if (schema.oneOf && schema.oneOf.length > 0) return generateMock(__pick(rng, schema.oneOf), rng, defs, depth);
|
|
1620
|
+
if (schema.anyOf && schema.anyOf.length > 0) return generateMock(__pick(rng, schema.anyOf), rng, defs, depth);
|
|
1621
|
+
let type = Array.isArray(schema.type)
|
|
1622
|
+
? (schema.type.filter((t) => t !== 'null')[0] || 'null')
|
|
1623
|
+
: schema.type;
|
|
1624
|
+
switch (type) {
|
|
1625
|
+
case 'string':
|
|
1626
|
+
return __fakeString(rng, schema);
|
|
1627
|
+
case 'integer':
|
|
1628
|
+
return __intBetween(rng, typeof schema.minimum === 'number' ? schema.minimum : 0, typeof schema.maximum === 'number' ? schema.maximum : 1000);
|
|
1629
|
+
case 'number':
|
|
1630
|
+
return __intBetween(rng, typeof schema.minimum === 'number' ? schema.minimum : 0, typeof schema.maximum === 'number' ? schema.maximum : 1000) + Math.round(rng.next() * 100) / 100;
|
|
1631
|
+
case 'boolean':
|
|
1632
|
+
return rng.next() < 0.5;
|
|
1633
|
+
case 'null':
|
|
1634
|
+
return null;
|
|
1635
|
+
case 'array': {
|
|
1636
|
+
const count = depth > 2 ? 0 : __intBetween(rng, 1, 2);
|
|
1637
|
+
const items = schema.items || {};
|
|
1638
|
+
let arr = [];
|
|
1639
|
+
for (let i = 0; i < count; i++) arr.push(generateMock(items, rng, defs, depth + 1));
|
|
1640
|
+
return arr;
|
|
1641
|
+
}
|
|
1642
|
+
case 'object': {
|
|
1643
|
+
const out = {};
|
|
1644
|
+
const props = schema.properties || {};
|
|
1645
|
+
for (const key of Object.keys(props)) out[key] = generateMock(props[key], rng, defs, depth + 1);
|
|
1646
|
+
return out;
|
|
1647
|
+
}
|
|
1648
|
+
default:
|
|
1649
|
+
return {};
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
`.trim();
|
|
1653
|
+
|
|
1654
|
+
// src/emit/emit-mocks.ts
|
|
1655
|
+
var REF_PREFIX = "#/components/schemas/";
|
|
1656
|
+
function toMswPath(path, baseUrl) {
|
|
1657
|
+
return `${baseUrl}${path}`;
|
|
1658
|
+
}
|
|
1659
|
+
function responseSchemaFor(route, defs) {
|
|
1660
|
+
const cs = route.contract.contractSource;
|
|
1661
|
+
if (cs.responseSchema) {
|
|
1662
|
+
const { root, named } = schemaModuleToJsonSchema(cs.responseSchema, { refPrefix: REF_PREFIX });
|
|
1663
|
+
for (const [name, node] of Object.entries(named)) {
|
|
1664
|
+
if (!(name in defs)) defs[name] = node;
|
|
1665
|
+
}
|
|
1666
|
+
return root;
|
|
1667
|
+
}
|
|
1668
|
+
return {};
|
|
1669
|
+
}
|
|
1670
|
+
function buildMocksFile(routes, opts = {}) {
|
|
1671
|
+
const seed = opts.seed ?? 1;
|
|
1672
|
+
const baseUrl = opts.baseUrl ?? "";
|
|
1673
|
+
const contracted = routes.filter((r) => r.contract);
|
|
1674
|
+
const defs = {};
|
|
1675
|
+
const handlers = [];
|
|
1676
|
+
for (const r of contracted) {
|
|
1677
|
+
const schema = responseSchemaFor(r, defs);
|
|
1678
|
+
const method = r.method.toLowerCase();
|
|
1679
|
+
const mswMethod = method === "get" || method === "post" || method === "put" || method === "patch" || method === "delete" ? method : "all";
|
|
1680
|
+
const path = toMswPath(r.path, baseUrl);
|
|
1681
|
+
const cs = r.contract.contractSource;
|
|
1682
|
+
const schemaLiteral = JSON.stringify(schema);
|
|
1683
|
+
const pathLit = JSON.stringify(path);
|
|
1684
|
+
if (cs.stream) {
|
|
1685
|
+
handlers.push(
|
|
1686
|
+
[
|
|
1687
|
+
` // ${r.name} (stream)`,
|
|
1688
|
+
` http.${mswMethod}(${pathLit}, () => {`,
|
|
1689
|
+
` const value = generateMock(${schemaLiteral}, makeRng(SEED), DEFS);`,
|
|
1690
|
+
" const body = `data: ${JSON.stringify(value)}\\n\\n`;",
|
|
1691
|
+
" return new HttpResponse(body, { headers: { 'Content-Type': 'text/event-stream' } });",
|
|
1692
|
+
" }),"
|
|
1693
|
+
].join("\n")
|
|
1694
|
+
);
|
|
1695
|
+
} else {
|
|
1696
|
+
handlers.push(
|
|
1697
|
+
[
|
|
1698
|
+
` // ${r.name}`,
|
|
1699
|
+
` http.${mswMethod}(${pathLit}, () => {`,
|
|
1700
|
+
` const value = generateMock(${schemaLiteral}, makeRng(SEED), DEFS);`,
|
|
1701
|
+
" return HttpResponse.json(value);",
|
|
1702
|
+
" }),"
|
|
1703
|
+
].join("\n")
|
|
1704
|
+
);
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
const lines = [
|
|
1708
|
+
"// Generated by @dudousxd/nestjs-codegen. Do not edit.",
|
|
1709
|
+
"// MSW handlers returning deterministic, schema-shaped mock data.",
|
|
1710
|
+
"/* eslint-disable */",
|
|
1711
|
+
"// @ts-nocheck",
|
|
1712
|
+
"",
|
|
1713
|
+
"import { http, HttpResponse } from 'msw';",
|
|
1714
|
+
"",
|
|
1715
|
+
`const SEED = ${seed};`,
|
|
1716
|
+
"",
|
|
1717
|
+
"// ---------------------------------------------------------------------------",
|
|
1718
|
+
"// Embedded mock-data runtime (mulberry32 PRNG + JSON-Schema value generator).",
|
|
1719
|
+
"// Dependency-free: no @faker-js/faker. Deterministic for a given SEED.",
|
|
1720
|
+
"// ---------------------------------------------------------------------------",
|
|
1721
|
+
MOCK_GEN_RUNTIME,
|
|
1722
|
+
"",
|
|
1723
|
+
"// Shared component schemas referenced by $ref.",
|
|
1724
|
+
`const DEFS = ${JSON.stringify(defs, null, 2)};`,
|
|
1725
|
+
"",
|
|
1726
|
+
"/** MSW request handlers, one per contracted route. */",
|
|
1727
|
+
"export const handlers = [",
|
|
1728
|
+
...handlers,
|
|
1729
|
+
"];",
|
|
1730
|
+
""
|
|
1731
|
+
];
|
|
1732
|
+
return lines.join("\n");
|
|
1733
|
+
}
|
|
1734
|
+
async function emitMocks(routes, outDir, opts = {}) {
|
|
1378
1735
|
await mkdir5(outDir, { recursive: true });
|
|
1736
|
+
const content = buildMocksFile(routes, opts);
|
|
1737
|
+
const fileName = opts.fileName ?? "mocks.ts";
|
|
1738
|
+
await writeFile5(join8(outDir, fileName), content, "utf8");
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
// src/emit/emit-openapi.ts
|
|
1742
|
+
import { mkdir as mkdir6, writeFile as writeFile6 } from "fs/promises";
|
|
1743
|
+
import { join as join9 } from "path";
|
|
1744
|
+
var REF_PREFIX2 = "#/components/schemas/";
|
|
1745
|
+
function toOpenApiPath(path) {
|
|
1746
|
+
return path.replace(/:([^/]+)/g, "{$1}");
|
|
1747
|
+
}
|
|
1748
|
+
function positionSchema(schema, tsType, components) {
|
|
1749
|
+
if (schema) {
|
|
1750
|
+
const { root, named } = schemaModuleToJsonSchema(schema, { refPrefix: REF_PREFIX2 });
|
|
1751
|
+
for (const [name, node] of Object.entries(named)) {
|
|
1752
|
+
if (!(name in components)) components[name] = node;
|
|
1753
|
+
}
|
|
1754
|
+
return root;
|
|
1755
|
+
}
|
|
1756
|
+
return tsType ? { description: tsType } : {};
|
|
1757
|
+
}
|
|
1758
|
+
function buildParameters(route) {
|
|
1759
|
+
const params = [];
|
|
1760
|
+
for (const p of route.params) {
|
|
1761
|
+
if (p.source === "path") {
|
|
1762
|
+
params.push({
|
|
1763
|
+
name: p.name,
|
|
1764
|
+
in: "path",
|
|
1765
|
+
required: true,
|
|
1766
|
+
schema: { type: "string" }
|
|
1767
|
+
});
|
|
1768
|
+
} else if (p.source === "query") {
|
|
1769
|
+
params.push({
|
|
1770
|
+
name: p.name,
|
|
1771
|
+
in: "query",
|
|
1772
|
+
required: false,
|
|
1773
|
+
schema: { type: "string" }
|
|
1774
|
+
});
|
|
1775
|
+
} else if (p.source === "header") {
|
|
1776
|
+
params.push({
|
|
1777
|
+
name: p.name,
|
|
1778
|
+
in: "header",
|
|
1779
|
+
required: false,
|
|
1780
|
+
schema: { type: "string" }
|
|
1781
|
+
});
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
return params;
|
|
1785
|
+
}
|
|
1786
|
+
function buildResponses(cs, components) {
|
|
1787
|
+
const responses = {};
|
|
1788
|
+
const successSchema = positionSchema(
|
|
1789
|
+
// Prefer rich response IR when present; otherwise fall back to the TS type.
|
|
1790
|
+
cs.responseSchema ?? null,
|
|
1791
|
+
cs.response,
|
|
1792
|
+
components
|
|
1793
|
+
);
|
|
1794
|
+
const successContentType = cs.stream ? "text/event-stream" : "application/json";
|
|
1795
|
+
responses["200"] = {
|
|
1796
|
+
description: cs.stream ? "Server-sent event stream" : "Successful response",
|
|
1797
|
+
content: { [successContentType]: { schema: successSchema } }
|
|
1798
|
+
};
|
|
1799
|
+
const errorSchema = positionSchema(null, cs.error ?? null, components);
|
|
1800
|
+
const errorBody = {
|
|
1801
|
+
description: "Error response",
|
|
1802
|
+
content: { "application/json": { schema: errorSchema } }
|
|
1803
|
+
};
|
|
1804
|
+
if (cs.error || cs.errorRef) {
|
|
1805
|
+
responses["400"] = errorBody;
|
|
1806
|
+
responses.default = errorBody;
|
|
1807
|
+
} else {
|
|
1808
|
+
responses.default = {
|
|
1809
|
+
description: "Error response",
|
|
1810
|
+
content: { "application/json": { schema: {} } }
|
|
1811
|
+
};
|
|
1812
|
+
}
|
|
1813
|
+
return responses;
|
|
1814
|
+
}
|
|
1815
|
+
function buildOperation(route, components) {
|
|
1816
|
+
const cs = route.contract.contractSource;
|
|
1817
|
+
const op = {
|
|
1818
|
+
operationId: route.name,
|
|
1819
|
+
parameters: buildParameters(route),
|
|
1820
|
+
responses: buildResponses(cs, components)
|
|
1821
|
+
};
|
|
1822
|
+
const method = route.method.toUpperCase();
|
|
1823
|
+
const hasBody = method !== "GET" && method !== "HEAD" && method !== "DELETE";
|
|
1824
|
+
if (hasBody && (cs.bodySchema || cs.body)) {
|
|
1825
|
+
const bodySchema = positionSchema(cs.bodySchema, cs.body, components);
|
|
1826
|
+
op.requestBody = {
|
|
1827
|
+
required: true,
|
|
1828
|
+
content: { "application/json": { schema: bodySchema } }
|
|
1829
|
+
};
|
|
1830
|
+
}
|
|
1831
|
+
return op;
|
|
1832
|
+
}
|
|
1833
|
+
function buildOpenApiSpec(routes, opts = {}) {
|
|
1834
|
+
const components = {};
|
|
1835
|
+
const paths = {};
|
|
1836
|
+
for (const route of routes) {
|
|
1837
|
+
if (!route.contract) continue;
|
|
1838
|
+
const oaPath = toOpenApiPath(route.path);
|
|
1839
|
+
const method = route.method.toLowerCase();
|
|
1840
|
+
let pathItem = paths[oaPath];
|
|
1841
|
+
if (!pathItem) {
|
|
1842
|
+
pathItem = {};
|
|
1843
|
+
paths[oaPath] = pathItem;
|
|
1844
|
+
}
|
|
1845
|
+
pathItem[method] = buildOperation(route, components);
|
|
1846
|
+
}
|
|
1847
|
+
const info = opts.info ?? {};
|
|
1848
|
+
const doc = {
|
|
1849
|
+
openapi: "3.1.0",
|
|
1850
|
+
info: {
|
|
1851
|
+
title: info.title ?? "NestJS API",
|
|
1852
|
+
version: info.version ?? "1.0.0",
|
|
1853
|
+
...info.description ? { description: info.description } : {}
|
|
1854
|
+
},
|
|
1855
|
+
paths,
|
|
1856
|
+
components: { schemas: components }
|
|
1857
|
+
};
|
|
1858
|
+
return doc;
|
|
1859
|
+
}
|
|
1860
|
+
async function emitOpenApi(routes, outDir, opts = {}) {
|
|
1861
|
+
await mkdir6(outDir, { recursive: true });
|
|
1862
|
+
const doc = buildOpenApiSpec(routes, opts);
|
|
1863
|
+
const fileName = opts.fileName ?? "openapi.json";
|
|
1864
|
+
await writeFile6(join9(outDir, fileName), `${JSON.stringify(doc, null, 2)}
|
|
1865
|
+
`, "utf8");
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1868
|
+
// src/emit/emit-pages.ts
|
|
1869
|
+
import { mkdir as mkdir7, writeFile as writeFile7 } from "fs/promises";
|
|
1870
|
+
import { join as join10, relative as relative5 } from "path";
|
|
1871
|
+
async function emitPages(pages, outDir, _options = {}) {
|
|
1872
|
+
await mkdir7(outDir, { recursive: true });
|
|
1379
1873
|
const pageNameUnion = pages.length > 0 ? pages.map((p) => JSON.stringify(p.name)).join(" | ") : "never";
|
|
1380
1874
|
const augBody = pages.map((p) => {
|
|
1381
1875
|
const key = needsQuotes(p.name) ? JSON.stringify(p.name) : p.name;
|
|
@@ -1394,7 +1888,7 @@ ${augBody}
|
|
|
1394
1888
|
}
|
|
1395
1889
|
${sharedPropsBlock}}
|
|
1396
1890
|
`;
|
|
1397
|
-
await
|
|
1891
|
+
await writeFile7(join10(outDir, "pages.d.ts"), content, "utf8");
|
|
1398
1892
|
}
|
|
1399
1893
|
function buildSharedPropsBlock(sharedProps) {
|
|
1400
1894
|
if (!sharedProps) return "";
|
|
@@ -1424,12 +1918,12 @@ function needsQuotes(name) {
|
|
|
1424
1918
|
}
|
|
1425
1919
|
|
|
1426
1920
|
// src/emit/emit-routes.ts
|
|
1427
|
-
import { mkdir as
|
|
1428
|
-
import { join as
|
|
1921
|
+
import { mkdir as mkdir8, writeFile as writeFile8 } from "fs/promises";
|
|
1922
|
+
import { join as join11 } from "path";
|
|
1429
1923
|
async function emitRoutes(routes, outDir) {
|
|
1430
|
-
await
|
|
1924
|
+
await mkdir8(outDir, { recursive: true });
|
|
1431
1925
|
const content = buildRoutesFile(routes);
|
|
1432
|
-
await
|
|
1926
|
+
await writeFile8(join11(outDir, "routes.ts"), content, "utf8");
|
|
1433
1927
|
}
|
|
1434
1928
|
function buildRoutesFile(routes) {
|
|
1435
1929
|
if (routes.length === 0) {
|
|
@@ -1577,24 +2071,41 @@ async function generate(config, inputRoutes = []) {
|
|
|
1577
2071
|
});
|
|
1578
2072
|
}
|
|
1579
2073
|
const hasForms = await emitForms(routes, config.codegen.outDir, config.forms, config.validation);
|
|
2074
|
+
if (hasContracts && config.openapi.enabled) {
|
|
2075
|
+
await emitOpenApi(routes, config.codegen.outDir, {
|
|
2076
|
+
fileName: config.openapi.fileName,
|
|
2077
|
+
info: {
|
|
2078
|
+
title: config.openapi.title,
|
|
2079
|
+
version: config.openapi.version,
|
|
2080
|
+
...config.openapi.description ? { description: config.openapi.description } : {}
|
|
2081
|
+
}
|
|
2082
|
+
});
|
|
2083
|
+
}
|
|
2084
|
+
if (hasContracts && config.mocks.enabled) {
|
|
2085
|
+
await emitMocks(routes, config.codegen.outDir, {
|
|
2086
|
+
fileName: config.mocks.fileName,
|
|
2087
|
+
seed: config.mocks.seed,
|
|
2088
|
+
baseUrl: config.mocks.baseUrl
|
|
2089
|
+
});
|
|
2090
|
+
}
|
|
1580
2091
|
await emitIndex(config.codegen.outDir, hasContracts, hasForms);
|
|
1581
2092
|
if (extensions.length > 0) {
|
|
1582
2093
|
const extraFiles = await collectEmittedFiles(extensions, ctx);
|
|
1583
2094
|
for (const file of extraFiles) {
|
|
1584
|
-
const dest =
|
|
1585
|
-
await
|
|
1586
|
-
await
|
|
2095
|
+
const dest = join12(config.codegen.outDir, file.path);
|
|
2096
|
+
await mkdir9(dirname2(dest), { recursive: true });
|
|
2097
|
+
await writeFile9(dest, file.contents, "utf8");
|
|
1587
2098
|
}
|
|
1588
2099
|
}
|
|
1589
2100
|
}
|
|
1590
2101
|
|
|
1591
2102
|
// src/watch/watcher.ts
|
|
1592
2103
|
import { readFile as readFile3 } from "fs/promises";
|
|
1593
|
-
import { join as
|
|
2104
|
+
import { join as join15 } from "path";
|
|
1594
2105
|
import chokidar from "chokidar";
|
|
1595
2106
|
|
|
1596
2107
|
// src/discovery/contracts-fast.ts
|
|
1597
|
-
import { join as
|
|
2108
|
+
import { join as join13, resolve as resolve3 } from "path";
|
|
1598
2109
|
import fg2 from "fast-glob";
|
|
1599
2110
|
import {
|
|
1600
2111
|
Node as Node8,
|
|
@@ -1776,10 +2287,85 @@ function followModuleForType(name, moduleSpecifier, fromFile, project, seen) {
|
|
|
1776
2287
|
}
|
|
1777
2288
|
return null;
|
|
1778
2289
|
}
|
|
2290
|
+
function resolveImportedVariable(name, sourceFile, project) {
|
|
2291
|
+
const local = sourceFile.getVariableDeclaration(name);
|
|
2292
|
+
if (local) return { decl: local, file: sourceFile };
|
|
2293
|
+
return resolveVariableViaImports(name, sourceFile, project, /* @__PURE__ */ new Set());
|
|
2294
|
+
}
|
|
2295
|
+
function resolveVariableViaImports(name, sourceFile, project, seen) {
|
|
2296
|
+
for (const importDecl of sourceFile.getImportDeclarations()) {
|
|
2297
|
+
const namedImport = importDecl.getNamedImports().find((n) => (n.getAliasNode()?.getText() ?? n.getName()) === name);
|
|
2298
|
+
if (!namedImport) continue;
|
|
2299
|
+
const sourceName = namedImport.getName();
|
|
2300
|
+
const moduleSpecifier = importDecl.getModuleSpecifierValue();
|
|
2301
|
+
const found = followModuleForVariable(sourceName, moduleSpecifier, sourceFile, project, seen);
|
|
2302
|
+
if (found) return found;
|
|
2303
|
+
}
|
|
2304
|
+
return null;
|
|
2305
|
+
}
|
|
2306
|
+
function followModuleForVariable(name, moduleSpecifier, fromFile, project, seen) {
|
|
2307
|
+
const candidates = resolveModuleSpecifier(moduleSpecifier, fromFile, project);
|
|
2308
|
+
for (const candidate of candidates) {
|
|
2309
|
+
let importedFile = project.getSourceFile(candidate);
|
|
2310
|
+
if (!importedFile) {
|
|
2311
|
+
try {
|
|
2312
|
+
importedFile = project.addSourceFileAtPath(candidate);
|
|
2313
|
+
} catch {
|
|
2314
|
+
continue;
|
|
2315
|
+
}
|
|
2316
|
+
}
|
|
2317
|
+
const found = resolveVariableInFile(name, importedFile, project, seen);
|
|
2318
|
+
if (found) return found;
|
|
2319
|
+
}
|
|
2320
|
+
return null;
|
|
2321
|
+
}
|
|
2322
|
+
function resolveVariableInFile(name, file, project, seen) {
|
|
2323
|
+
const filePath = file.getFilePath();
|
|
2324
|
+
if (seen.has(filePath)) return null;
|
|
2325
|
+
seen.add(filePath);
|
|
2326
|
+
const local = file.getVariableDeclaration(name);
|
|
2327
|
+
if (local) return { decl: local, file };
|
|
2328
|
+
for (const exportDecl of file.getExportDeclarations()) {
|
|
2329
|
+
const moduleSpecifier = exportDecl.getModuleSpecifierValue();
|
|
2330
|
+
const namedExports = exportDecl.getNamedExports();
|
|
2331
|
+
if (moduleSpecifier) {
|
|
2332
|
+
const hasStar = namedExports.length === 0;
|
|
2333
|
+
const reExport2 = namedExports.find(
|
|
2334
|
+
(n) => (n.getAliasNode()?.getText() ?? n.getName()) === name
|
|
2335
|
+
);
|
|
2336
|
+
if (!hasStar && !reExport2) continue;
|
|
2337
|
+
const sourceName2 = hasStar ? name : reExport2?.getName() ?? name;
|
|
2338
|
+
const found = followModuleForVariable(sourceName2, moduleSpecifier, file, project, seen);
|
|
2339
|
+
if (found) return found;
|
|
2340
|
+
continue;
|
|
2341
|
+
}
|
|
2342
|
+
const reExport = namedExports.find(
|
|
2343
|
+
(n) => (n.getAliasNode()?.getText() ?? n.getName()) === name
|
|
2344
|
+
);
|
|
2345
|
+
if (!reExport) continue;
|
|
2346
|
+
const sourceName = reExport.getName();
|
|
2347
|
+
const viaImports = resolveVariableViaImports(sourceName, file, project, seen);
|
|
2348
|
+
if (viaImports) return viaImports;
|
|
2349
|
+
}
|
|
2350
|
+
return null;
|
|
2351
|
+
}
|
|
2352
|
+
var _findTypeCache = /* @__PURE__ */ new WeakMap();
|
|
2353
|
+
function clearTypeResolutionCaches(project) {
|
|
2354
|
+
_findTypeCache.delete(project);
|
|
2355
|
+
_resolveNamedRefCache.delete(project);
|
|
2356
|
+
}
|
|
1779
2357
|
function findType(name, sourceFile, project) {
|
|
2358
|
+
let byKey = _findTypeCache.get(project);
|
|
2359
|
+
if (byKey === void 0) {
|
|
2360
|
+
byKey = /* @__PURE__ */ new Map();
|
|
2361
|
+
_findTypeCache.set(project, byKey);
|
|
2362
|
+
}
|
|
2363
|
+
const key = `${sourceFile.getFilePath()}\0${name}`;
|
|
2364
|
+
if (byKey.has(key)) return byKey.get(key) ?? null;
|
|
1780
2365
|
const local = findTypeInFile(name, sourceFile);
|
|
1781
|
-
|
|
1782
|
-
|
|
2366
|
+
const result = local ?? resolveImportedType(name, sourceFile, project);
|
|
2367
|
+
byKey.set(key, result);
|
|
2368
|
+
return result;
|
|
1783
2369
|
}
|
|
1784
2370
|
var _NON_REF_NAMES = /* @__PURE__ */ new Set(["string", "number", "boolean", "void", "unknown", "any", "Date"]);
|
|
1785
2371
|
function _localDeclForKinds(name, file, kinds) {
|
|
@@ -1816,6 +2402,26 @@ function resolveTypeRef(nodeOrName, sourceFile, project, opts) {
|
|
|
1816
2402
|
if (_NON_REF_NAMES.has(refName)) return null;
|
|
1817
2403
|
name = refName;
|
|
1818
2404
|
}
|
|
2405
|
+
return _resolveNamedRef(name, sourceFile, project, opts);
|
|
2406
|
+
}
|
|
2407
|
+
var _resolveNamedRefCache = /* @__PURE__ */ new WeakMap();
|
|
2408
|
+
function _resolveNamedRef(name, sourceFile, project, opts) {
|
|
2409
|
+
let byKey = _resolveNamedRefCache.get(project);
|
|
2410
|
+
if (byKey === void 0) {
|
|
2411
|
+
byKey = /* @__PURE__ */ new Map();
|
|
2412
|
+
_resolveNamedRefCache.set(project, byKey);
|
|
2413
|
+
}
|
|
2414
|
+
const kindsKey = [...opts.kinds].sort().join(",");
|
|
2415
|
+
const key = `${sourceFile.getFilePath()}\0${name}\0${kindsKey}\0${opts.allowBareSpecifier ? 1 : 0}`;
|
|
2416
|
+
if (byKey.has(key)) {
|
|
2417
|
+
const cached = byKey.get(key) ?? null;
|
|
2418
|
+
return cached ? { ...cached } : null;
|
|
2419
|
+
}
|
|
2420
|
+
const computed = _computeNamedRef(name, sourceFile, project, opts);
|
|
2421
|
+
byKey.set(key, computed);
|
|
2422
|
+
return computed ? { ...computed } : null;
|
|
2423
|
+
}
|
|
2424
|
+
function _computeNamedRef(name, sourceFile, project, opts) {
|
|
1819
2425
|
if (_localDeclForKinds(name, sourceFile, opts.kinds)) {
|
|
1820
2426
|
return { name, filePath: sourceFile.getFilePath() };
|
|
1821
2427
|
}
|
|
@@ -1890,7 +2496,8 @@ function extractSchemaFromDto(classDecl, sourceFile, project) {
|
|
|
1890
2496
|
emittedClasses: /* @__PURE__ */ new Map(),
|
|
1891
2497
|
visiting: /* @__PURE__ */ new Set(),
|
|
1892
2498
|
recursiveSchemas: /* @__PURE__ */ new Set(),
|
|
1893
|
-
depth: 0
|
|
2499
|
+
depth: 0,
|
|
2500
|
+
typeBindings: /* @__PURE__ */ new Map()
|
|
1894
2501
|
};
|
|
1895
2502
|
const root = buildObject(classDecl, sourceFile, ctx);
|
|
1896
2503
|
return { root, named: ctx.named, warnings: ctx.warnings, recursive: ctx.recursiveSchemas };
|
|
@@ -1914,11 +2521,34 @@ function buildProperty(prop, classFile, ctx) {
|
|
|
1914
2521
|
const typeNode = prop.getTypeNode();
|
|
1915
2522
|
const typeText = typeNode?.getText() ?? "unknown";
|
|
1916
2523
|
const isArrayType = !!typeNode && Node3.isArrayTypeNode(typeNode);
|
|
2524
|
+
const discriminator = resolveDiscriminator(dec("Type"));
|
|
2525
|
+
if (discriminator) {
|
|
2526
|
+
const options = discriminator.subTypes.map(
|
|
2527
|
+
(name) => buildNestedReference(name, classFile, ctx)
|
|
2528
|
+
);
|
|
2529
|
+
const unionNode = {
|
|
2530
|
+
kind: "union",
|
|
2531
|
+
options,
|
|
2532
|
+
discriminator: discriminator.property
|
|
2533
|
+
};
|
|
2534
|
+
const wrapArray = has("IsArray") || isArrayType;
|
|
2535
|
+
const node2 = wrapArray ? { kind: "array", element: unionNode } : unionNode;
|
|
2536
|
+
return applyPresence(node2, decorators);
|
|
2537
|
+
}
|
|
2538
|
+
const propTypeParam = singularClassName(typeText);
|
|
2539
|
+
if (propTypeParam && ctx.typeBindings.has(propTypeParam)) {
|
|
2540
|
+
const bound = ctx.typeBindings.get(propTypeParam);
|
|
2541
|
+
const childNode = buildNestedReference(bound, classFile, ctx);
|
|
2542
|
+
const wrapArray = has("IsArray") || isArrayType;
|
|
2543
|
+
const node2 = wrapArray ? { kind: "array", element: childNode } : childNode;
|
|
2544
|
+
return applyPresence(node2, decorators);
|
|
2545
|
+
}
|
|
1917
2546
|
const typeRefName = resolveTypeFactoryName(dec("Type"));
|
|
1918
2547
|
if (has("ValidateNested") || typeRefName) {
|
|
2548
|
+
const typeArgs = genericTypeArgNames(typeNode);
|
|
1919
2549
|
const childName = typeRefName ?? singularClassName(typeText);
|
|
1920
2550
|
if (childName) {
|
|
1921
|
-
const childNode = buildNestedReference(childName, classFile, ctx);
|
|
2551
|
+
const childNode = buildNestedReference(childName, classFile, ctx, typeArgs);
|
|
1922
2552
|
const wrapArray = has("IsArray") || isArrayType;
|
|
1923
2553
|
const node2 = wrapArray ? { kind: "array", element: childNode } : childNode;
|
|
1924
2554
|
return applyPresence(node2, decorators);
|
|
@@ -2043,10 +2673,12 @@ function baseFromType(typeText, isArrayType) {
|
|
|
2043
2673
|
return { kind: "unknown" };
|
|
2044
2674
|
}
|
|
2045
2675
|
}
|
|
2046
|
-
function buildNestedReference(className, fromFile, ctx) {
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2676
|
+
function buildNestedReference(className, fromFile, ctx, typeArgs = []) {
|
|
2677
|
+
const cacheKey = typeArgs.length > 0 ? `${className}<${typeArgs.join(",")}>` : className;
|
|
2678
|
+
const schemaBase = typeArgs.length > 0 ? `${className}Of${typeArgs.join("")}` : className;
|
|
2679
|
+
if (ctx.visiting.has(cacheKey)) {
|
|
2680
|
+
const reserved = ctx.emittedClasses.get(cacheKey) ?? aliasFor(schemaBase, ctx);
|
|
2681
|
+
ctx.emittedClasses.set(cacheKey, reserved);
|
|
2050
2682
|
ctx.recursiveSchemas.add(reserved);
|
|
2051
2683
|
if (!ctx.warnedDecorators.has(`recursive:${reserved}`)) {
|
|
2052
2684
|
ctx.warnedDecorators.add(`recursive:${reserved}`);
|
|
@@ -2065,19 +2697,27 @@ function buildNestedReference(className, fromFile, ctx) {
|
|
|
2065
2697
|
}
|
|
2066
2698
|
return { kind: "unknown", note: "nesting too deep \u2014 not expanded" };
|
|
2067
2699
|
}
|
|
2068
|
-
const existing = ctx.emittedClasses.get(
|
|
2700
|
+
const existing = ctx.emittedClasses.get(cacheKey);
|
|
2069
2701
|
if (existing) return { kind: "ref", name: existing };
|
|
2070
|
-
const schemaName = aliasFor(
|
|
2702
|
+
const schemaName = aliasFor(schemaBase, ctx);
|
|
2071
2703
|
const resolved = findType(className, fromFile, ctx.project);
|
|
2072
2704
|
if (!resolved || resolved.kind !== "class") {
|
|
2073
2705
|
return { kind: "object", fields: [], passthrough: true };
|
|
2074
2706
|
}
|
|
2075
|
-
|
|
2076
|
-
|
|
2707
|
+
const params = resolved.decl.getTypeParameters().map((p) => p.getName());
|
|
2708
|
+
const newBindings = [];
|
|
2709
|
+
params.forEach((param, i) => {
|
|
2710
|
+
const arg = typeArgs[i];
|
|
2711
|
+
if (arg) newBindings.push([param, arg]);
|
|
2712
|
+
});
|
|
2713
|
+
for (const [k, v] of newBindings) ctx.typeBindings.set(k, v);
|
|
2714
|
+
ctx.emittedClasses.set(cacheKey, schemaName);
|
|
2715
|
+
ctx.visiting.add(cacheKey);
|
|
2077
2716
|
ctx.depth += 1;
|
|
2078
2717
|
const childNode = buildObject(resolved.decl, resolved.file, ctx);
|
|
2079
2718
|
ctx.depth -= 1;
|
|
2080
|
-
ctx.visiting.delete(
|
|
2719
|
+
ctx.visiting.delete(cacheKey);
|
|
2720
|
+
for (const [k] of newBindings) ctx.typeBindings.delete(k);
|
|
2081
2721
|
ctx.named.set(schemaName, childNode);
|
|
2082
2722
|
return { kind: "ref", name: schemaName };
|
|
2083
2723
|
}
|
|
@@ -2124,6 +2764,39 @@ function messageRaw(decorator) {
|
|
|
2124
2764
|
}
|
|
2125
2765
|
return void 0;
|
|
2126
2766
|
}
|
|
2767
|
+
function resolveDiscriminator(decorator) {
|
|
2768
|
+
const optsArg = decorator?.getArguments()[1];
|
|
2769
|
+
if (!optsArg || !Node3.isObjectLiteralExpression(optsArg)) return null;
|
|
2770
|
+
let discProp;
|
|
2771
|
+
for (const prop of optsArg.getProperties()) {
|
|
2772
|
+
if (Node3.isPropertyAssignment(prop) && prop.getName() === "discriminator") {
|
|
2773
|
+
discProp = prop.getInitializer();
|
|
2774
|
+
}
|
|
2775
|
+
}
|
|
2776
|
+
if (!discProp || !Node3.isObjectLiteralExpression(discProp)) return null;
|
|
2777
|
+
let property = null;
|
|
2778
|
+
const subTypes = [];
|
|
2779
|
+
for (const prop of discProp.getProperties()) {
|
|
2780
|
+
if (!Node3.isPropertyAssignment(prop)) continue;
|
|
2781
|
+
const name = prop.getName();
|
|
2782
|
+
const init = prop.getInitializer();
|
|
2783
|
+
if (!init) continue;
|
|
2784
|
+
if (name === "property" && Node3.isStringLiteral(init)) {
|
|
2785
|
+
property = init.getLiteralValue();
|
|
2786
|
+
} else if (name === "subTypes" && Node3.isArrayLiteralExpression(init)) {
|
|
2787
|
+
for (const el of init.getElements()) {
|
|
2788
|
+
if (!Node3.isObjectLiteralExpression(el)) continue;
|
|
2789
|
+
for (const p of el.getProperties()) {
|
|
2790
|
+
if (!Node3.isPropertyAssignment(p) || p.getName() !== "name") continue;
|
|
2791
|
+
const nameInit = p.getInitializer();
|
|
2792
|
+
if (nameInit && Node3.isIdentifier(nameInit)) subTypes.push(nameInit.getText());
|
|
2793
|
+
}
|
|
2794
|
+
}
|
|
2795
|
+
}
|
|
2796
|
+
}
|
|
2797
|
+
if (!property || subTypes.length === 0) return null;
|
|
2798
|
+
return { property, subTypes };
|
|
2799
|
+
}
|
|
2127
2800
|
function resolveTypeFactoryName(decorator) {
|
|
2128
2801
|
const arg = firstArg(decorator);
|
|
2129
2802
|
if (!arg) return null;
|
|
@@ -2137,6 +2810,17 @@ function singularClassName(typeText) {
|
|
|
2137
2810
|
const inner = typeText.endsWith("[]") ? typeText.slice(0, -2).trim() : typeText;
|
|
2138
2811
|
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(inner) ? inner : null;
|
|
2139
2812
|
}
|
|
2813
|
+
function genericTypeArgNames(typeNode) {
|
|
2814
|
+
if (!typeNode || !Node3.isTypeReference(typeNode)) return [];
|
|
2815
|
+
const names = [];
|
|
2816
|
+
for (const arg of typeNode.getTypeArguments()) {
|
|
2817
|
+
if (!Node3.isTypeReference(arg)) return [];
|
|
2818
|
+
const tn = arg.getTypeName();
|
|
2819
|
+
if (!Node3.isIdentifier(tn)) return [];
|
|
2820
|
+
names.push(tn.getText());
|
|
2821
|
+
}
|
|
2822
|
+
return names;
|
|
2823
|
+
}
|
|
2140
2824
|
function enumSchemaFromDecorator(decorator, classFile, ctx) {
|
|
2141
2825
|
const arg = firstArg(decorator);
|
|
2142
2826
|
if (!arg) return null;
|
|
@@ -2195,17 +2879,34 @@ import {
|
|
|
2195
2879
|
} from "ts-morph";
|
|
2196
2880
|
|
|
2197
2881
|
// src/discovery/enum-resolution.ts
|
|
2882
|
+
var _enumCache = /* @__PURE__ */ new WeakMap();
|
|
2883
|
+
function clearEnumCache(project) {
|
|
2884
|
+
_enumCache.delete(project);
|
|
2885
|
+
}
|
|
2198
2886
|
function resolveEnumValues(name, sourceFile, project) {
|
|
2887
|
+
let byKey = _enumCache.get(project);
|
|
2888
|
+
if (byKey === void 0) {
|
|
2889
|
+
byKey = /* @__PURE__ */ new Map();
|
|
2890
|
+
_enumCache.set(project, byKey);
|
|
2891
|
+
}
|
|
2892
|
+
const key = `${sourceFile.getFilePath()}\0${name}`;
|
|
2893
|
+
if (byKey.has(key)) {
|
|
2894
|
+
const cached = byKey.get(key) ?? null;
|
|
2895
|
+
return cached ? { values: [...cached.values], numeric: cached.numeric } : null;
|
|
2896
|
+
}
|
|
2199
2897
|
const resolved = findType(name, sourceFile, project);
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
const
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2898
|
+
let result = null;
|
|
2899
|
+
if (resolved && resolved.kind === "enum") {
|
|
2900
|
+
let numeric = true;
|
|
2901
|
+
const values = resolved.members.map((m) => {
|
|
2902
|
+
const parsed = JSON.parse(m);
|
|
2903
|
+
if (typeof parsed === "string") numeric = false;
|
|
2904
|
+
return String(parsed);
|
|
2905
|
+
});
|
|
2906
|
+
if (values.length > 0) result = { values, numeric };
|
|
2907
|
+
}
|
|
2908
|
+
byKey.set(key, result);
|
|
2909
|
+
return result ? { values: [...result.values], numeric: result.numeric } : null;
|
|
2209
2910
|
}
|
|
2210
2911
|
|
|
2211
2912
|
// src/discovery/filter-field-types.ts
|
|
@@ -2650,24 +3351,26 @@ var PASSTHROUGH_UTILITY = /* @__PURE__ */ new Set([
|
|
|
2650
3351
|
"Map",
|
|
2651
3352
|
"Set"
|
|
2652
3353
|
]);
|
|
2653
|
-
function resolveTypeNodeToString(typeNode, sourceFile, project, depth) {
|
|
3354
|
+
function resolveTypeNodeToString(typeNode, sourceFile, project, depth, subst = /* @__PURE__ */ new Map()) {
|
|
2654
3355
|
if (depth <= 0) return "unknown";
|
|
2655
3356
|
if (Node6.isArrayTypeNode(typeNode)) {
|
|
2656
3357
|
const elementType = typeNode.getElementTypeNode();
|
|
2657
|
-
return `Array<${resolveTypeNodeToString(elementType, sourceFile, project, depth)}>`;
|
|
3358
|
+
return `Array<${resolveTypeNodeToString(elementType, sourceFile, project, depth, subst)}>`;
|
|
2658
3359
|
}
|
|
2659
3360
|
if (Node6.isUnionTypeNode(typeNode)) {
|
|
2660
|
-
return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth)).join(" | ");
|
|
3361
|
+
return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth, subst)).join(" | ");
|
|
2661
3362
|
}
|
|
2662
3363
|
if (Node6.isIntersectionTypeNode(typeNode)) {
|
|
2663
|
-
return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth)).join(" & ");
|
|
3364
|
+
return typeNode.getTypeNodes().map((t) => resolveTypeNodeToString(t, sourceFile, project, depth, subst)).join(" & ");
|
|
2664
3365
|
}
|
|
2665
3366
|
if (Node6.isParenthesizedTypeNode(typeNode)) {
|
|
2666
|
-
return `(${resolveTypeNodeToString(typeNode.getTypeNode(), sourceFile, project, depth)})`;
|
|
3367
|
+
return `(${resolveTypeNodeToString(typeNode.getTypeNode(), sourceFile, project, depth, subst)})`;
|
|
2667
3368
|
}
|
|
2668
3369
|
if (Node6.isTypeReference(typeNode)) {
|
|
2669
3370
|
const typeName = typeNode.getTypeName();
|
|
2670
3371
|
const name = Node6.isIdentifier(typeName) ? typeName.getText() : typeNode.getText();
|
|
3372
|
+
const bound = subst.get(name);
|
|
3373
|
+
if (bound !== void 0) return bound;
|
|
2671
3374
|
if (name === "string" || name === "number" || name === "boolean") return name;
|
|
2672
3375
|
if (name === "Date") return "string";
|
|
2673
3376
|
if (name === "unknown" || name === "any" || name === "void") return "unknown";
|
|
@@ -2675,14 +3378,15 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth) {
|
|
|
2675
3378
|
return "unknown";
|
|
2676
3379
|
const wrapperMode = WRAPPER_TYPES[name];
|
|
2677
3380
|
if (wrapperMode) {
|
|
2678
|
-
return unwrapFirstTypeArg(typeNode, sourceFile, project, depth, wrapperMode);
|
|
3381
|
+
return unwrapFirstTypeArg(typeNode, sourceFile, project, depth, wrapperMode, subst);
|
|
2679
3382
|
}
|
|
2680
3383
|
if (PASSTHROUGH_UTILITY.has(name)) {
|
|
2681
3384
|
return typeNode.getText();
|
|
2682
3385
|
}
|
|
2683
3386
|
const resolved = findType(name, sourceFile, project);
|
|
2684
3387
|
if (resolved) {
|
|
2685
|
-
|
|
3388
|
+
const childSubst = buildSubst(resolved, typeNode, sourceFile, project, depth, subst);
|
|
3389
|
+
return expandTypeDecl(resolved, project, depth - 1, childSubst);
|
|
2686
3390
|
}
|
|
2687
3391
|
dbg("unresolvable type:", name, "in", sourceFile.getFilePath());
|
|
2688
3392
|
return "unknown";
|
|
@@ -2695,32 +3399,45 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth) {
|
|
|
2695
3399
|
if (kind === SyntaxKind3.AnyKeyword) return "unknown";
|
|
2696
3400
|
return typeNode.getText();
|
|
2697
3401
|
}
|
|
2698
|
-
function unwrapFirstTypeArg(typeNode, sourceFile, project, depth, mode) {
|
|
3402
|
+
function unwrapFirstTypeArg(typeNode, sourceFile, project, depth, mode, subst = /* @__PURE__ */ new Map()) {
|
|
2699
3403
|
const typeArgs = typeNode.getTypeArguments();
|
|
2700
3404
|
const firstTypeArg = typeArgs[0];
|
|
2701
3405
|
if (typeArgs.length > 0 && firstTypeArg !== void 0) {
|
|
2702
|
-
const inner = resolveTypeNodeToString(firstTypeArg, sourceFile, project, depth);
|
|
3406
|
+
const inner = resolveTypeNodeToString(firstTypeArg, sourceFile, project, depth, subst);
|
|
2703
3407
|
return mode === "arrayOf" ? `Array<${inner}>` : inner;
|
|
2704
3408
|
}
|
|
2705
3409
|
return mode === "arrayOf" ? "Array<unknown>" : "unknown";
|
|
2706
3410
|
}
|
|
2707
|
-
function
|
|
3411
|
+
function buildSubst(result, typeNode, sourceFile, project, depth, parentSubst) {
|
|
3412
|
+
if (result.kind !== "class" && result.kind !== "interface") return /* @__PURE__ */ new Map();
|
|
3413
|
+
const params = result.decl.getTypeParameters().map((p) => p.getName());
|
|
3414
|
+
if (params.length === 0) return /* @__PURE__ */ new Map();
|
|
3415
|
+
const args = typeNode.getTypeArguments();
|
|
3416
|
+
const subst = /* @__PURE__ */ new Map();
|
|
3417
|
+
params.forEach((param, i) => {
|
|
3418
|
+
const arg = args[i];
|
|
3419
|
+
if (arg)
|
|
3420
|
+
subst.set(param, resolveTypeNodeToString(arg, sourceFile, project, depth, parentSubst));
|
|
3421
|
+
});
|
|
3422
|
+
return subst;
|
|
3423
|
+
}
|
|
3424
|
+
function expandTypeDecl(result, project, depth, subst = /* @__PURE__ */ new Map()) {
|
|
2708
3425
|
if (depth < 0) return "unknown";
|
|
2709
3426
|
switch (result.kind) {
|
|
2710
3427
|
case "class":
|
|
2711
|
-
return resolvePropertied(result.decl, result.file, project, depth);
|
|
3428
|
+
return resolvePropertied(result.decl, result.file, project, depth, subst);
|
|
2712
3429
|
case "interface":
|
|
2713
|
-
return resolvePropertied(result.decl, result.file, project, depth);
|
|
3430
|
+
return resolvePropertied(result.decl, result.file, project, depth, subst);
|
|
2714
3431
|
case "typeAlias":
|
|
2715
3432
|
if (result.typeNode) {
|
|
2716
|
-
return resolveTypeNodeToString(result.typeNode, result.file, project, depth);
|
|
3433
|
+
return resolveTypeNodeToString(result.typeNode, result.file, project, depth, subst);
|
|
2717
3434
|
}
|
|
2718
3435
|
return result.text;
|
|
2719
3436
|
case "enum":
|
|
2720
3437
|
return result.members.join(" | ");
|
|
2721
3438
|
}
|
|
2722
3439
|
}
|
|
2723
|
-
function resolvePropertied(decl, sourceFile, project, depth) {
|
|
3440
|
+
function resolvePropertied(decl, sourceFile, project, depth, subst = /* @__PURE__ */ new Map()) {
|
|
2724
3441
|
if (depth < 0) return "unknown";
|
|
2725
3442
|
const lines = [];
|
|
2726
3443
|
for (const prop of decl.getProperties()) {
|
|
@@ -2729,7 +3446,7 @@ function resolvePropertied(decl, sourceFile, project, depth) {
|
|
|
2729
3446
|
const propTypeNode = prop.getTypeNode();
|
|
2730
3447
|
let propType = "unknown";
|
|
2731
3448
|
if (propTypeNode) {
|
|
2732
|
-
propType = resolveTypeNodeToString(propTypeNode, sourceFile, project, depth);
|
|
3449
|
+
propType = resolveTypeNodeToString(propTypeNode, sourceFile, project, depth, subst);
|
|
2733
3450
|
}
|
|
2734
3451
|
lines.push(`${propName}${isOptional ? "?" : ""}: ${propType}`);
|
|
2735
3452
|
}
|
|
@@ -2778,7 +3495,7 @@ function extractParamsType(method, sourceFile, project) {
|
|
|
2778
3495
|
return entries.length > 0 ? `{ ${entries.join("; ")} }` : null;
|
|
2779
3496
|
}
|
|
2780
3497
|
function extractResponseType(method, sourceFile, project) {
|
|
2781
|
-
const apiResponseDecorator = method.
|
|
3498
|
+
const apiResponseDecorator = method.getDecorators().find((d) => d.getName() === "ApiResponse" && (apiResponseStatus(d) ?? 0) < 400);
|
|
2782
3499
|
if (apiResponseDecorator) {
|
|
2783
3500
|
const args = apiResponseDecorator.getArguments();
|
|
2784
3501
|
const optsArg = args[0];
|
|
@@ -2807,6 +3524,59 @@ function extractResponseType(method, sourceFile, project) {
|
|
|
2807
3524
|
}
|
|
2808
3525
|
return "unknown";
|
|
2809
3526
|
}
|
|
3527
|
+
function apiResponseStatus(decorator) {
|
|
3528
|
+
const optsArg = decorator.getArguments()[0];
|
|
3529
|
+
if (!optsArg || !Node6.isObjectLiteralExpression(optsArg)) return null;
|
|
3530
|
+
for (const prop of optsArg.getProperties()) {
|
|
3531
|
+
if (!Node6.isPropertyAssignment(prop)) continue;
|
|
3532
|
+
if (prop.getName() !== "status") continue;
|
|
3533
|
+
const val = prop.getInitializer();
|
|
3534
|
+
if (val && Node6.isNumericLiteral(val)) return Number(val.getLiteralValue());
|
|
3535
|
+
}
|
|
3536
|
+
return null;
|
|
3537
|
+
}
|
|
3538
|
+
function apiResponseTypeNode(decorator) {
|
|
3539
|
+
const optsArg = decorator.getArguments()[0];
|
|
3540
|
+
if (!optsArg || !Node6.isObjectLiteralExpression(optsArg)) return null;
|
|
3541
|
+
for (const prop of optsArg.getProperties()) {
|
|
3542
|
+
if (!Node6.isPropertyAssignment(prop)) continue;
|
|
3543
|
+
if (prop.getName() !== "type") continue;
|
|
3544
|
+
const val = prop.getInitializer();
|
|
3545
|
+
if (!val) return null;
|
|
3546
|
+
if (Node6.isArrayLiteralExpression(val)) {
|
|
3547
|
+
const first = val.getElements()[0];
|
|
3548
|
+
return first ? { node: first, isArray: true } : null;
|
|
3549
|
+
}
|
|
3550
|
+
return { node: val, isArray: false };
|
|
3551
|
+
}
|
|
3552
|
+
return null;
|
|
3553
|
+
}
|
|
3554
|
+
function extractErrorType(method, sourceFile, project) {
|
|
3555
|
+
for (const decorator of method.getDecorators()) {
|
|
3556
|
+
if (decorator.getName() !== "ApiResponse") continue;
|
|
3557
|
+
const status = apiResponseStatus(decorator);
|
|
3558
|
+
if (status === null || status < 400) continue;
|
|
3559
|
+
const typeInfo = apiResponseTypeNode(decorator);
|
|
3560
|
+
if (!typeInfo) continue;
|
|
3561
|
+
const inner = resolveIdentifierToClassType(typeInfo.node, sourceFile, project, 3);
|
|
3562
|
+
const type = typeInfo.isArray ? `Array<${inner}>` : inner;
|
|
3563
|
+
let ref = null;
|
|
3564
|
+
if (Node6.isIdentifier(typeInfo.node)) {
|
|
3565
|
+
const name = typeInfo.node.getText();
|
|
3566
|
+
const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
|
|
3567
|
+
if (localDecl?.isExported()) {
|
|
3568
|
+
ref = { name, filePath: sourceFile.getFilePath(), isArray: typeInfo.isArray };
|
|
3569
|
+
} else {
|
|
3570
|
+
const resolved = resolveImportedType(name, sourceFile, project);
|
|
3571
|
+
if (resolved && (resolved.kind === "class" || resolved.kind === "interface") && resolved.decl.isExported()) {
|
|
3572
|
+
ref = { name, filePath: resolved.file.getFilePath(), isArray: typeInfo.isArray };
|
|
3573
|
+
}
|
|
3574
|
+
}
|
|
3575
|
+
}
|
|
3576
|
+
return { type, ref };
|
|
3577
|
+
}
|
|
3578
|
+
return null;
|
|
3579
|
+
}
|
|
2810
3580
|
function resolveIdentifierToClassType(node, sourceFile, project, depth) {
|
|
2811
3581
|
if (!Node6.isIdentifier(node)) return "unknown";
|
|
2812
3582
|
const name = node.getText();
|
|
@@ -2822,17 +3592,52 @@ function resolveBodyQueryResponseRef(typeNode, sourceFile, project) {
|
|
|
2822
3592
|
unwrapContainers: true
|
|
2823
3593
|
});
|
|
2824
3594
|
}
|
|
3595
|
+
var STREAM_CONTAINERS = /* @__PURE__ */ new Set(["Observable", "AsyncIterable", "AsyncIterableIterator"]);
|
|
3596
|
+
var STREAM_CONTAINERS_GENERATOR = /* @__PURE__ */ new Set(["AsyncGenerator"]);
|
|
3597
|
+
var STREAM_ENVELOPES = /* @__PURE__ */ new Set(["MessageEvent", "MessageEventLike"]);
|
|
3598
|
+
function detectStreamElement(method) {
|
|
3599
|
+
const hasSse = method.getDecorators().some((d) => d.getName() === "Sse");
|
|
3600
|
+
let node = method.getReturnTypeNode();
|
|
3601
|
+
node = unwrapNamedContainer(node, /* @__PURE__ */ new Set(["Promise"]));
|
|
3602
|
+
const containerEl = streamContainerElement(node);
|
|
3603
|
+
if (containerEl) {
|
|
3604
|
+
return unwrapNamedContainer(containerEl, STREAM_ENVELOPES) ?? containerEl;
|
|
3605
|
+
}
|
|
3606
|
+
if (hasSse) return node ?? null;
|
|
3607
|
+
return null;
|
|
3608
|
+
}
|
|
3609
|
+
function streamContainerElement(node) {
|
|
3610
|
+
if (!node || !Node6.isTypeReference(node)) return null;
|
|
3611
|
+
const typeName = node.getTypeName();
|
|
3612
|
+
const name = Node6.isIdentifier(typeName) ? typeName.getText() : "";
|
|
3613
|
+
if (STREAM_CONTAINERS.has(name) || STREAM_CONTAINERS_GENERATOR.has(name)) {
|
|
3614
|
+
return node.getTypeArguments()[0] ?? null;
|
|
3615
|
+
}
|
|
3616
|
+
return null;
|
|
3617
|
+
}
|
|
3618
|
+
function unwrapNamedContainer(node, names) {
|
|
3619
|
+
if (!node || !Node6.isTypeReference(node)) return node;
|
|
3620
|
+
const typeName = node.getTypeName();
|
|
3621
|
+
const name = Node6.isIdentifier(typeName) ? typeName.getText() : "";
|
|
3622
|
+
if (names.has(name)) {
|
|
3623
|
+
return node.getTypeArguments()[0] ?? node;
|
|
3624
|
+
}
|
|
3625
|
+
return node;
|
|
3626
|
+
}
|
|
2825
3627
|
function extractDtoContract(method, sourceFile, project) {
|
|
2826
3628
|
let body = extractBodyType(method, sourceFile, project);
|
|
2827
3629
|
const filterInfo = extractApplyFilterInfo(method, sourceFile, project);
|
|
2828
3630
|
const query = extractQueryType(method, sourceFile, project);
|
|
3631
|
+
const streamElement = detectStreamElement(method);
|
|
3632
|
+
const isStream = streamElement !== null;
|
|
2829
3633
|
if (filterInfo && filterInfo.source === "body") {
|
|
2830
3634
|
const bodyType = "import('@dudousxd/nestjs-filter-client').FilterQueryResult";
|
|
2831
3635
|
body = body ?? bodyType;
|
|
2832
3636
|
}
|
|
2833
3637
|
const paramsType = extractParamsType(method, sourceFile, project);
|
|
2834
|
-
const response = extractResponseType(method, sourceFile, project);
|
|
2835
|
-
|
|
3638
|
+
const response = isStream ? resolveTypeNodeToString(streamElement, sourceFile, project, 3) : extractResponseType(method, sourceFile, project);
|
|
3639
|
+
const errorInfo = extractErrorType(method, sourceFile, project);
|
|
3640
|
+
if (body === null && query === null && paramsType === null && response === "unknown" && errorInfo === null && filterInfo === null && !isStream) {
|
|
2836
3641
|
return null;
|
|
2837
3642
|
}
|
|
2838
3643
|
let bodyRef = null;
|
|
@@ -2846,12 +3651,12 @@ function extractDtoContract(method, sourceFile, project) {
|
|
|
2846
3651
|
queryRef = resolveBodyQueryResponseRef(param.getTypeNode(), sourceFile, project);
|
|
2847
3652
|
}
|
|
2848
3653
|
}
|
|
2849
|
-
const returnTypeNode = method.getReturnTypeNode();
|
|
3654
|
+
const returnTypeNode = isStream ? streamElement : method.getReturnTypeNode();
|
|
2850
3655
|
if (returnTypeNode) {
|
|
2851
3656
|
responseRef = resolveBodyQueryResponseRef(returnTypeNode, sourceFile, project);
|
|
2852
3657
|
}
|
|
2853
|
-
if (!responseRef) {
|
|
2854
|
-
const apiResp = method.
|
|
3658
|
+
if (!responseRef && !isStream) {
|
|
3659
|
+
const apiResp = method.getDecorators().find((d) => d.getName() === "ApiResponse" && (apiResponseStatus(d) ?? 0) < 400);
|
|
2855
3660
|
if (apiResp) {
|
|
2856
3661
|
const args = apiResp.getArguments();
|
|
2857
3662
|
const optsArg = args[0];
|
|
@@ -2893,16 +3698,19 @@ function extractDtoContract(method, sourceFile, project) {
|
|
|
2893
3698
|
query,
|
|
2894
3699
|
body,
|
|
2895
3700
|
response,
|
|
3701
|
+
error: errorInfo?.type ?? null,
|
|
2896
3702
|
params: paramsType,
|
|
2897
3703
|
queryRef,
|
|
2898
3704
|
bodyRef,
|
|
2899
3705
|
responseRef,
|
|
3706
|
+
errorRef: errorInfo?.ref ?? null,
|
|
2900
3707
|
filterFields: filterInfo?.fieldNames ?? null,
|
|
2901
3708
|
filterFieldTypes: filterInfo?.fieldTypes ?? null,
|
|
2902
3709
|
filterSource: filterInfo?.source ?? null,
|
|
2903
3710
|
formWarnings,
|
|
2904
3711
|
bodySchema,
|
|
2905
|
-
querySchema
|
|
3712
|
+
querySchema,
|
|
3713
|
+
stream: isStream
|
|
2906
3714
|
};
|
|
2907
3715
|
}
|
|
2908
3716
|
function resolveParamClass(method, decoratorName, sourceFile, project) {
|
|
@@ -3020,6 +3828,7 @@ function parseDefineContractCall(callExpr) {
|
|
|
3020
3828
|
let query = null;
|
|
3021
3829
|
let body = null;
|
|
3022
3830
|
let response = "unknown";
|
|
3831
|
+
let error = null;
|
|
3023
3832
|
let bodyZodText = null;
|
|
3024
3833
|
let queryZodText = null;
|
|
3025
3834
|
for (const prop of optsArg.getProperties()) {
|
|
@@ -3035,25 +3844,38 @@ function parseDefineContractCall(callExpr) {
|
|
|
3035
3844
|
bodyZodText = val.getText();
|
|
3036
3845
|
} else if (propName === "response") {
|
|
3037
3846
|
response = zodAstToTs(val);
|
|
3847
|
+
} else if (propName === "error") {
|
|
3848
|
+
error = zodAstToTs(val);
|
|
3038
3849
|
}
|
|
3039
3850
|
}
|
|
3040
|
-
return { query, body, response, bodyZodText, queryZodText };
|
|
3851
|
+
return { query, body, response, error, bodyZodText, queryZodText };
|
|
3041
3852
|
}
|
|
3042
3853
|
|
|
3043
3854
|
// src/discovery/contracts-fast.ts
|
|
3044
3855
|
async function discoverContractsFast(opts) {
|
|
3045
3856
|
const { cwd, glob, tsconfig } = opts;
|
|
3046
|
-
const tsconfigPath =
|
|
3047
|
-
|
|
3857
|
+
const tsconfigPath = resolveTsconfigPath(cwd, tsconfig);
|
|
3858
|
+
const project = createDiscoveryProject(tsconfigPath);
|
|
3859
|
+
const files = await fg2(glob, { cwd, absolute: true, onlyFiles: true });
|
|
3860
|
+
for (const f of files) {
|
|
3861
|
+
project.addSourceFileAtPath(f);
|
|
3862
|
+
}
|
|
3863
|
+
bindDiscoveryContext(project, cwd, tsconfigPath);
|
|
3864
|
+
return extractAllRoutes(project);
|
|
3865
|
+
}
|
|
3866
|
+
function resolveTsconfigPath(cwd, tsconfig) {
|
|
3867
|
+
return tsconfig ? resolve3(tsconfig) : join13(cwd, "tsconfig.json");
|
|
3868
|
+
}
|
|
3869
|
+
function createDiscoveryProject(tsconfigPath) {
|
|
3048
3870
|
try {
|
|
3049
|
-
|
|
3871
|
+
return new Project3({
|
|
3050
3872
|
tsConfigFilePath: tsconfigPath,
|
|
3051
3873
|
skipAddingFilesFromTsConfig: true,
|
|
3052
3874
|
skipLoadingLibFiles: true,
|
|
3053
3875
|
skipFileDependencyResolution: true
|
|
3054
3876
|
});
|
|
3055
3877
|
} catch {
|
|
3056
|
-
|
|
3878
|
+
return new Project3({
|
|
3057
3879
|
skipAddingFilesFromTsConfig: true,
|
|
3058
3880
|
skipLoadingLibFiles: true,
|
|
3059
3881
|
skipFileDependencyResolution: true,
|
|
@@ -3064,20 +3886,105 @@ async function discoverContractsFast(opts) {
|
|
|
3064
3886
|
}
|
|
3065
3887
|
});
|
|
3066
3888
|
}
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
project.addSourceFileAtPath(f);
|
|
3070
|
-
}
|
|
3071
|
-
const routes = [];
|
|
3889
|
+
}
|
|
3890
|
+
function bindDiscoveryContext(project, cwd, tsconfigPath) {
|
|
3072
3891
|
setDiscoveryContext(project, {
|
|
3073
3892
|
projectRoot: cwd,
|
|
3074
3893
|
tsconfigPaths: loadTsconfigPaths(tsconfigPath)
|
|
3075
3894
|
});
|
|
3895
|
+
}
|
|
3896
|
+
function extractRoutesFrom(project, controllerPaths) {
|
|
3897
|
+
const routes = [];
|
|
3898
|
+
for (const path of controllerPaths) {
|
|
3899
|
+
const sourceFile = project.getSourceFile(path);
|
|
3900
|
+
if (sourceFile) routes.push(...extractFromSourceFile(sourceFile, project));
|
|
3901
|
+
}
|
|
3902
|
+
return routes;
|
|
3903
|
+
}
|
|
3904
|
+
function extractAllRoutes(project) {
|
|
3905
|
+
const routes = [];
|
|
3076
3906
|
for (const sourceFile of project.getSourceFiles()) {
|
|
3077
3907
|
routes.push(...extractFromSourceFile(sourceFile, project));
|
|
3078
3908
|
}
|
|
3079
3909
|
return routes;
|
|
3080
3910
|
}
|
|
3911
|
+
var PersistentDiscovery = class _PersistentDiscovery {
|
|
3912
|
+
project;
|
|
3913
|
+
cwd;
|
|
3914
|
+
glob;
|
|
3915
|
+
/** Absolute paths of the controllers currently loaded as extraction roots. */
|
|
3916
|
+
controllerPaths = /* @__PURE__ */ new Set();
|
|
3917
|
+
constructor(project, cwd, glob) {
|
|
3918
|
+
this.project = project;
|
|
3919
|
+
this.cwd = cwd;
|
|
3920
|
+
this.glob = glob;
|
|
3921
|
+
}
|
|
3922
|
+
/**
|
|
3923
|
+
* Build the initial persistent Project: create it, glob + add all controllers,
|
|
3924
|
+
* bind the discovery context. Mirrors {@link discoverContractsFast}'s setup.
|
|
3925
|
+
*/
|
|
3926
|
+
static async create(opts) {
|
|
3927
|
+
const { cwd, glob, tsconfig } = opts;
|
|
3928
|
+
const tsconfigPath = resolveTsconfigPath(cwd, tsconfig);
|
|
3929
|
+
const project = createDiscoveryProject(tsconfigPath);
|
|
3930
|
+
bindDiscoveryContext(project, cwd, tsconfigPath);
|
|
3931
|
+
const instance = new _PersistentDiscovery(project, cwd, glob);
|
|
3932
|
+
const files = await fg2(glob, { cwd, absolute: true, onlyFiles: true });
|
|
3933
|
+
for (const f of files) {
|
|
3934
|
+
project.addSourceFileAtPath(f);
|
|
3935
|
+
instance.controllerPaths.add(f);
|
|
3936
|
+
}
|
|
3937
|
+
return instance;
|
|
3938
|
+
}
|
|
3939
|
+
/** Run the initial extraction (equivalent to a first `discoverContractsFast`). */
|
|
3940
|
+
discover() {
|
|
3941
|
+
return this.runExtraction();
|
|
3942
|
+
}
|
|
3943
|
+
/**
|
|
3944
|
+
* Re-discover after one or more files changed. Refreshes the changed file(s)
|
|
3945
|
+
* from disk (controllers AND any lazily-loaded DTO/imported files), re-globs
|
|
3946
|
+
* to pick up added/removed controllers, clears the per-Project caches, then
|
|
3947
|
+
* re-extracts. `changedPaths` is a hint; correctness does not depend on it
|
|
3948
|
+
* being exhaustive because re-globbing + refresh-on-presence covers the set.
|
|
3949
|
+
*/
|
|
3950
|
+
async rediscover(changedPaths) {
|
|
3951
|
+
if (changedPaths) {
|
|
3952
|
+
for (const p of changedPaths) {
|
|
3953
|
+
const abs = resolve3(p);
|
|
3954
|
+
const sf = this.project.getSourceFile(abs);
|
|
3955
|
+
if (sf) {
|
|
3956
|
+
await sf.refreshFromFileSystem();
|
|
3957
|
+
}
|
|
3958
|
+
}
|
|
3959
|
+
}
|
|
3960
|
+
const globbed = new Set(
|
|
3961
|
+
await fg2(this.glob, { cwd: this.cwd, absolute: true, onlyFiles: true })
|
|
3962
|
+
);
|
|
3963
|
+
for (const f of globbed) {
|
|
3964
|
+
if (!this.controllerPaths.has(f)) {
|
|
3965
|
+
try {
|
|
3966
|
+
this.project.addSourceFileAtPath(f);
|
|
3967
|
+
this.controllerPaths.add(f);
|
|
3968
|
+
} catch {
|
|
3969
|
+
}
|
|
3970
|
+
}
|
|
3971
|
+
}
|
|
3972
|
+
for (const f of this.controllerPaths) {
|
|
3973
|
+
if (!globbed.has(f)) {
|
|
3974
|
+
const sf = this.project.getSourceFile(f);
|
|
3975
|
+
if (sf) this.project.removeSourceFile(sf);
|
|
3976
|
+
this.controllerPaths.delete(f);
|
|
3977
|
+
}
|
|
3978
|
+
}
|
|
3979
|
+
return this.runExtraction();
|
|
3980
|
+
}
|
|
3981
|
+
/** Clear stale per-Project caches, then extract over the controller set. */
|
|
3982
|
+
runExtraction() {
|
|
3983
|
+
clearTypeResolutionCaches(this.project);
|
|
3984
|
+
clearEnumCache(this.project);
|
|
3985
|
+
return extractRoutesFrom(this.project, this.controllerPaths);
|
|
3986
|
+
}
|
|
3987
|
+
};
|
|
3081
3988
|
function decoratorStringArg(decoratorExpr) {
|
|
3082
3989
|
if (!decoratorExpr) return void 0;
|
|
3083
3990
|
if (Node8.isStringLiteral(decoratorExpr)) return decoratorExpr.getLiteralValue();
|
|
@@ -3133,6 +4040,11 @@ function resolveVerb(method) {
|
|
|
3133
4040
|
return { httpMethod: verb, handlerPath: decoratorStringArg(pathArg) ?? "" };
|
|
3134
4041
|
}
|
|
3135
4042
|
}
|
|
4043
|
+
const sseDecorator = method.getDecorator("Sse");
|
|
4044
|
+
if (sseDecorator) {
|
|
4045
|
+
const pathArg = sseDecorator.getArguments()[0];
|
|
4046
|
+
return { httpMethod: "GET", handlerPath: decoratorStringArg(pathArg) ?? "" };
|
|
4047
|
+
}
|
|
3136
4048
|
return null;
|
|
3137
4049
|
}
|
|
3138
4050
|
function readAsDecorator(node, label) {
|
|
@@ -3175,7 +4087,17 @@ function buildRoute(args) {
|
|
|
3175
4087
|
};
|
|
3176
4088
|
}
|
|
3177
4089
|
function extractContractRoute(args) {
|
|
3178
|
-
const {
|
|
4090
|
+
const {
|
|
4091
|
+
cls,
|
|
4092
|
+
method,
|
|
4093
|
+
applyContractDecorator,
|
|
4094
|
+
verb,
|
|
4095
|
+
prefix,
|
|
4096
|
+
className,
|
|
4097
|
+
sourceFile,
|
|
4098
|
+
project,
|
|
4099
|
+
seenNames
|
|
4100
|
+
} = args;
|
|
3179
4101
|
const firstDecoratorArg = applyContractDecorator.getArguments()[0];
|
|
3180
4102
|
if (!firstDecoratorArg) return null;
|
|
3181
4103
|
let contractDef = null;
|
|
@@ -3185,18 +4107,19 @@ function extractContractRoute(args) {
|
|
|
3185
4107
|
contractDef = parseDefineContractCall(firstDecoratorArg);
|
|
3186
4108
|
} else if (Node8.isIdentifier(firstDecoratorArg)) {
|
|
3187
4109
|
const identName = firstDecoratorArg.getText();
|
|
3188
|
-
const
|
|
3189
|
-
if (!
|
|
4110
|
+
const resolvedVar = resolveImportedVariable(identName, sourceFile, project);
|
|
4111
|
+
if (!resolvedVar) {
|
|
3190
4112
|
console.warn(
|
|
3191
|
-
`[nestjs-codegen/fast] Cannot resolve '${identName}' in ${sourceFile.getFilePath()}
|
|
4113
|
+
`[nestjs-codegen/fast] Cannot resolve contract identifier '${identName}' applied in ${sourceFile.getFilePath()} \u2014 the import could not be followed to a declaration; skipping`
|
|
3192
4114
|
);
|
|
3193
4115
|
return null;
|
|
3194
4116
|
}
|
|
4117
|
+
const { decl: varDecl, file: declFile } = resolvedVar;
|
|
3195
4118
|
const initializer = varDecl.getInitializer();
|
|
3196
4119
|
if (!initializer) return null;
|
|
3197
4120
|
contractDef = parseDefineContractCall(initializer);
|
|
3198
4121
|
if (contractDef && varDecl.isExported()) {
|
|
3199
|
-
const filePath =
|
|
4122
|
+
const filePath = declFile.getFilePath();
|
|
3200
4123
|
if (contractDef.body !== null) {
|
|
3201
4124
|
bodyZodRef = { name: `${identName}.body`, filePath };
|
|
3202
4125
|
}
|
|
@@ -3229,6 +4152,7 @@ function extractContractRoute(args) {
|
|
|
3229
4152
|
query: contractDef.query,
|
|
3230
4153
|
body: contractDef.body,
|
|
3231
4154
|
response: contractDef.response,
|
|
4155
|
+
error: contractDef.error,
|
|
3232
4156
|
// Path A: capture both the importable ref and the raw text. The emitter
|
|
3233
4157
|
// prefers inlining the text (client-safe — re-exporting from a controller
|
|
3234
4158
|
// would drag server-only deps into the client bundle).
|
|
@@ -3260,15 +4184,18 @@ function extractDtoRoute(args) {
|
|
|
3260
4184
|
query: dtoContract?.query ?? null,
|
|
3261
4185
|
body: dtoContract?.body ?? null,
|
|
3262
4186
|
response: dtoContract?.response ?? "unknown",
|
|
4187
|
+
error: dtoContract?.error ?? null,
|
|
3263
4188
|
queryRef: dtoContract?.queryRef ?? null,
|
|
3264
4189
|
bodyRef: dtoContract?.bodyRef ?? null,
|
|
3265
4190
|
responseRef: dtoContract?.responseRef ?? null,
|
|
4191
|
+
errorRef: dtoContract?.errorRef ?? null,
|
|
3266
4192
|
filterFields: dtoContract?.filterFields ?? null,
|
|
3267
4193
|
filterFieldTypes: dtoContract?.filterFieldTypes ?? null,
|
|
3268
4194
|
filterSource: dtoContract?.filterSource ?? null,
|
|
3269
4195
|
formWarnings: dtoContract?.formWarnings ?? [],
|
|
3270
4196
|
bodySchema: dtoContract?.bodySchema ?? null,
|
|
3271
|
-
querySchema: dtoContract?.querySchema ?? null
|
|
4197
|
+
querySchema: dtoContract?.querySchema ?? null,
|
|
4198
|
+
stream: dtoContract?.stream ?? false
|
|
3272
4199
|
}
|
|
3273
4200
|
});
|
|
3274
4201
|
}
|
|
@@ -3292,6 +4219,7 @@ function extractFromSourceFile(sourceFile, project) {
|
|
|
3292
4219
|
prefix,
|
|
3293
4220
|
className,
|
|
3294
4221
|
sourceFile,
|
|
4222
|
+
project,
|
|
3295
4223
|
seenNames
|
|
3296
4224
|
}) : extractDtoRoute({
|
|
3297
4225
|
cls,
|
|
@@ -3311,8 +4239,8 @@ function extractFromSourceFile(sourceFile, project) {
|
|
|
3311
4239
|
|
|
3312
4240
|
// src/watch/lock-file.ts
|
|
3313
4241
|
import { open } from "fs/promises";
|
|
3314
|
-
import { mkdir as
|
|
3315
|
-
import { join as
|
|
4242
|
+
import { mkdir as mkdir10, readFile as readFile2, unlink } from "fs/promises";
|
|
4243
|
+
import { join as join14 } from "path";
|
|
3316
4244
|
var LOCK_FILE = ".watcher.lock";
|
|
3317
4245
|
function isProcessAlive(pid) {
|
|
3318
4246
|
try {
|
|
@@ -3323,8 +4251,8 @@ function isProcessAlive(pid) {
|
|
|
3323
4251
|
}
|
|
3324
4252
|
}
|
|
3325
4253
|
async function acquireLock(outDir) {
|
|
3326
|
-
await
|
|
3327
|
-
const lockPath =
|
|
4254
|
+
await mkdir10(outDir, { recursive: true });
|
|
4255
|
+
const lockPath = join14(outDir, LOCK_FILE);
|
|
3328
4256
|
const lockData = { pid: process.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
3329
4257
|
try {
|
|
3330
4258
|
const fd = await open(lockPath, "wx");
|
|
@@ -3364,7 +4292,7 @@ async function watch(config, onChange) {
|
|
|
3364
4292
|
if (lock === null) {
|
|
3365
4293
|
let holderPid = "unknown";
|
|
3366
4294
|
try {
|
|
3367
|
-
const raw = await readFile3(
|
|
4295
|
+
const raw = await readFile3(join15(config.codegen.outDir, ".watcher.lock"), "utf8");
|
|
3368
4296
|
const data = JSON.parse(raw);
|
|
3369
4297
|
if (data.pid !== void 0) holderPid = String(data.pid);
|
|
3370
4298
|
} catch {
|
|
@@ -3374,12 +4302,20 @@ async function watch(config, onChange) {
|
|
|
3374
4302
|
);
|
|
3375
4303
|
return NO_OP_WATCHER;
|
|
3376
4304
|
}
|
|
4305
|
+
let discovery = null;
|
|
4306
|
+
async function getDiscovery() {
|
|
4307
|
+
if (discovery === null) {
|
|
4308
|
+
discovery = await PersistentDiscovery.create({
|
|
4309
|
+
cwd: config.codegen.cwd,
|
|
4310
|
+
glob: config.contracts.glob,
|
|
4311
|
+
...config.app?.tsconfig ? { tsconfig: config.app.tsconfig } : {}
|
|
4312
|
+
});
|
|
4313
|
+
return discovery;
|
|
4314
|
+
}
|
|
4315
|
+
return discovery;
|
|
4316
|
+
}
|
|
3377
4317
|
try {
|
|
3378
|
-
const initialRoutes = await
|
|
3379
|
-
cwd: config.codegen.cwd,
|
|
3380
|
-
glob: config.contracts.glob,
|
|
3381
|
-
...config.app?.tsconfig ? { tsconfig: config.app.tsconfig } : {}
|
|
3382
|
-
});
|
|
4318
|
+
const initialRoutes = (await getDiscovery()).discover();
|
|
3383
4319
|
await generate(config, initialRoutes);
|
|
3384
4320
|
} catch (err) {
|
|
3385
4321
|
console.warn(
|
|
@@ -3392,7 +4328,7 @@ async function watch(config, onChange) {
|
|
|
3392
4328
|
}
|
|
3393
4329
|
let pagesDebounceTimer;
|
|
3394
4330
|
const pagesGlob = config.pages?.glob ?? ".nestjs-codegen-no-pages";
|
|
3395
|
-
const pagesWatcher = chokidar.watch(
|
|
4331
|
+
const pagesWatcher = chokidar.watch(join15(config.codegen.cwd, pagesGlob), {
|
|
3396
4332
|
ignoreInitial: true,
|
|
3397
4333
|
persistent: true,
|
|
3398
4334
|
awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
|
|
@@ -3418,23 +4354,23 @@ async function watch(config, onChange) {
|
|
|
3418
4354
|
pagesWatcher.on("change", schedulePagesRegenerate);
|
|
3419
4355
|
pagesWatcher.on("unlink", schedulePagesRegenerate);
|
|
3420
4356
|
let contractsDebounceTimer;
|
|
3421
|
-
const
|
|
4357
|
+
const pendingChangedPaths = /* @__PURE__ */ new Set();
|
|
4358
|
+
const contractsWatcher = chokidar.watch(join15(config.codegen.cwd, config.contracts.glob), {
|
|
3422
4359
|
ignoreInitial: true,
|
|
3423
4360
|
persistent: true,
|
|
3424
4361
|
awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
|
|
3425
4362
|
});
|
|
3426
|
-
function scheduleContractsRegenerate() {
|
|
4363
|
+
function scheduleContractsRegenerate(changedPath) {
|
|
4364
|
+
if (typeof changedPath === "string") pendingChangedPaths.add(changedPath);
|
|
3427
4365
|
if (contractsDebounceTimer !== void 0) {
|
|
3428
4366
|
clearTimeout(contractsDebounceTimer);
|
|
3429
4367
|
}
|
|
3430
4368
|
contractsDebounceTimer = setTimeout(async () => {
|
|
3431
4369
|
contractsDebounceTimer = void 0;
|
|
4370
|
+
const changed = [...pendingChangedPaths];
|
|
4371
|
+
pendingChangedPaths.clear();
|
|
3432
4372
|
try {
|
|
3433
|
-
const routes = await
|
|
3434
|
-
cwd: config.codegen.cwd,
|
|
3435
|
-
glob: config.contracts.glob,
|
|
3436
|
-
...config.app?.tsconfig ? { tsconfig: config.app.tsconfig } : {}
|
|
3437
|
-
});
|
|
4373
|
+
const routes = await (await getDiscovery()).rediscover(changed);
|
|
3438
4374
|
await generate(config, routes);
|
|
3439
4375
|
} catch (err) {
|
|
3440
4376
|
console.error(
|
|
@@ -3445,17 +4381,17 @@ async function watch(config, onChange) {
|
|
|
3445
4381
|
onChange?.();
|
|
3446
4382
|
}, config.contracts.debounceMs);
|
|
3447
4383
|
}
|
|
3448
|
-
contractsWatcher.on("add", scheduleContractsRegenerate);
|
|
3449
|
-
contractsWatcher.on("change", scheduleContractsRegenerate);
|
|
3450
|
-
contractsWatcher.on("unlink", scheduleContractsRegenerate);
|
|
3451
|
-
const formsWatcher = chokidar.watch(
|
|
4384
|
+
contractsWatcher.on("add", (p) => scheduleContractsRegenerate(p));
|
|
4385
|
+
contractsWatcher.on("change", (p) => scheduleContractsRegenerate(p));
|
|
4386
|
+
contractsWatcher.on("unlink", (p) => scheduleContractsRegenerate(p));
|
|
4387
|
+
const formsWatcher = chokidar.watch(join15(config.codegen.cwd, config.forms.watch), {
|
|
3452
4388
|
ignoreInitial: true,
|
|
3453
4389
|
persistent: true,
|
|
3454
4390
|
awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
|
|
3455
4391
|
});
|
|
3456
|
-
formsWatcher.on("add", scheduleContractsRegenerate);
|
|
3457
|
-
formsWatcher.on("change", scheduleContractsRegenerate);
|
|
3458
|
-
formsWatcher.on("unlink", scheduleContractsRegenerate);
|
|
4392
|
+
formsWatcher.on("add", (p) => scheduleContractsRegenerate(p));
|
|
4393
|
+
formsWatcher.on("change", (p) => scheduleContractsRegenerate(p));
|
|
4394
|
+
formsWatcher.on("unlink", (p) => scheduleContractsRegenerate(p));
|
|
3459
4395
|
return {
|
|
3460
4396
|
close: async () => {
|
|
3461
4397
|
if (pagesDebounceTimer !== void 0) {
|
|
@@ -3475,7 +4411,7 @@ async function watch(config, onChange) {
|
|
|
3475
4411
|
}
|
|
3476
4412
|
|
|
3477
4413
|
// src/index.ts
|
|
3478
|
-
var VERSION = "0.
|
|
4414
|
+
var VERSION = "0.5.0";
|
|
3479
4415
|
|
|
3480
4416
|
// src/cli/codegen.ts
|
|
3481
4417
|
async function runCodegen(opts = {}) {
|
|
@@ -3504,13 +4440,13 @@ async function runCodegen(opts = {}) {
|
|
|
3504
4440
|
// src/cli/doctor.ts
|
|
3505
4441
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
3506
4442
|
import { appendFileSync, existsSync, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
3507
|
-
import { join as
|
|
4443
|
+
import { join as join17 } from "path";
|
|
3508
4444
|
|
|
3509
4445
|
// src/cli/init.ts
|
|
3510
4446
|
import { execFileSync } from "child_process";
|
|
3511
4447
|
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
3512
|
-
import { access as access2, mkdir as
|
|
3513
|
-
import { join as
|
|
4448
|
+
import { access as access2, mkdir as mkdir11, readFile as readFile4, writeFile as writeFile10 } from "fs/promises";
|
|
4449
|
+
import { join as join16 } from "path";
|
|
3514
4450
|
import { createInterface } from "readline";
|
|
3515
4451
|
|
|
3516
4452
|
// src/cli/patch-utils.ts
|
|
@@ -3572,7 +4508,7 @@ ${bold(title)}`);
|
|
|
3572
4508
|
}
|
|
3573
4509
|
async function readPackageJson(cwd) {
|
|
3574
4510
|
try {
|
|
3575
|
-
const raw = await readFile4(
|
|
4511
|
+
const raw = await readFile4(join16(cwd, "package.json"), "utf8");
|
|
3576
4512
|
return JSON.parse(raw);
|
|
3577
4513
|
} catch {
|
|
3578
4514
|
return {};
|
|
@@ -3603,7 +4539,7 @@ async function detectTemplateEngine(cwd) {
|
|
|
3603
4539
|
async function detectPackageManager(cwd) {
|
|
3604
4540
|
async function exists(file) {
|
|
3605
4541
|
try {
|
|
3606
|
-
await access2(
|
|
4542
|
+
await access2(join16(cwd, file));
|
|
3607
4543
|
return true;
|
|
3608
4544
|
} catch {
|
|
3609
4545
|
return false;
|
|
@@ -3644,13 +4580,13 @@ async function writeIfNotExists(filePath, content, label) {
|
|
|
3644
4580
|
}
|
|
3645
4581
|
const dir = filePath.substring(0, filePath.lastIndexOf("/"));
|
|
3646
4582
|
if (dir) {
|
|
3647
|
-
await
|
|
4583
|
+
await mkdir11(dir, { recursive: true });
|
|
3648
4584
|
}
|
|
3649
|
-
await
|
|
4585
|
+
await writeFile10(filePath, content, "utf8");
|
|
3650
4586
|
logCreated(label);
|
|
3651
4587
|
}
|
|
3652
4588
|
async function handleViteConfig(cwd, framework) {
|
|
3653
|
-
const filePath =
|
|
4589
|
+
const filePath = join16(cwd, "vite.config.ts");
|
|
3654
4590
|
if (await fileExists2(filePath)) {
|
|
3655
4591
|
const existing = await readFile4(filePath, "utf8");
|
|
3656
4592
|
const hasPlugin = existing.includes("nestInertia") || existing.includes("nestjs-inertia-vite/plugin");
|
|
@@ -3668,9 +4604,9 @@ async function handleViteConfig(cwd, framework) {
|
|
|
3668
4604
|
}
|
|
3669
4605
|
const dir = filePath.substring(0, filePath.lastIndexOf("/"));
|
|
3670
4606
|
if (dir) {
|
|
3671
|
-
await
|
|
4607
|
+
await mkdir11(dir, { recursive: true });
|
|
3672
4608
|
}
|
|
3673
|
-
await
|
|
4609
|
+
await writeFile10(filePath, viteConfigTemplate(framework), "utf8");
|
|
3674
4610
|
logCreated("vite.config.ts");
|
|
3675
4611
|
}
|
|
3676
4612
|
async function patchGitignore(gitignorePath) {
|
|
@@ -3686,7 +4622,7 @@ async function patchGitignore(gitignorePath) {
|
|
|
3686
4622
|
` : `${existing}
|
|
3687
4623
|
${GITIGNORE_ENTRY}
|
|
3688
4624
|
`;
|
|
3689
|
-
await
|
|
4625
|
+
await writeFile10(gitignorePath, newContent, "utf8");
|
|
3690
4626
|
logPatched(".gitignore", "added .nestjs-inertia/");
|
|
3691
4627
|
}
|
|
3692
4628
|
function installDeps(pkgManager, deps, dev) {
|
|
@@ -3708,7 +4644,7 @@ function installDeps(pkgManager, deps, dev) {
|
|
|
3708
4644
|
}
|
|
3709
4645
|
}
|
|
3710
4646
|
async function patchPackageJsonScripts(cwd, scripts) {
|
|
3711
|
-
const pkgPath =
|
|
4647
|
+
const pkgPath = join16(cwd, "package.json");
|
|
3712
4648
|
let pkg = {};
|
|
3713
4649
|
try {
|
|
3714
4650
|
pkg = JSON.parse(await readFile4(pkgPath, "utf8"));
|
|
@@ -3730,7 +4666,7 @@ async function patchPackageJsonScripts(cwd, scripts) {
|
|
|
3730
4666
|
return;
|
|
3731
4667
|
}
|
|
3732
4668
|
pkg.scripts = existing;
|
|
3733
|
-
await
|
|
4669
|
+
await writeFile10(pkgPath, `${JSON.stringify(pkg, null, 2)}
|
|
3734
4670
|
`, "utf8");
|
|
3735
4671
|
}
|
|
3736
4672
|
function patchAppModule(filePath, rootView) {
|
|
@@ -4025,7 +4961,7 @@ export class HomeController {
|
|
|
4025
4961
|
}
|
|
4026
4962
|
`;
|
|
4027
4963
|
function patchTsconfigExclude(cwd, dir, filename = "tsconfig.json") {
|
|
4028
|
-
const filePath =
|
|
4964
|
+
const filePath = join16(cwd, filename);
|
|
4029
4965
|
return patchJsonFile(
|
|
4030
4966
|
filePath,
|
|
4031
4967
|
(json) => {
|
|
@@ -4040,7 +4976,7 @@ function patchTsconfigExclude(cwd, dir, filename = "tsconfig.json") {
|
|
|
4040
4976
|
);
|
|
4041
4977
|
}
|
|
4042
4978
|
function patchNestCliJson(cwd, shellDir) {
|
|
4043
|
-
const filePath =
|
|
4979
|
+
const filePath = join16(cwd, "nest-cli.json");
|
|
4044
4980
|
return patchJsonFile(filePath, (json) => {
|
|
4045
4981
|
const compiler = json.compilerOptions ?? {};
|
|
4046
4982
|
const assets = compiler.assets ?? [];
|
|
@@ -4063,46 +4999,46 @@ async function scaffoldFiles(ctx) {
|
|
|
4063
4999
|
const { cwd, framework, engine, shellFileName, entryExt, pageExt } = ctx;
|
|
4064
5000
|
logSection("Scaffold files");
|
|
4065
5001
|
await writeIfNotExists(
|
|
4066
|
-
|
|
5002
|
+
join16(cwd, "nestjs-inertia.config.ts"),
|
|
4067
5003
|
configTemplate(framework),
|
|
4068
5004
|
"nestjs-inertia.config.ts"
|
|
4069
5005
|
);
|
|
4070
|
-
await writeIfNotExists(
|
|
5006
|
+
await writeIfNotExists(join16(cwd, "nestjs-inertia.d.ts"), DTS_TEMPLATE, "nestjs-inertia.d.ts");
|
|
4071
5007
|
await writeIfNotExists(
|
|
4072
|
-
|
|
5008
|
+
join16(cwd, "tsconfig.inertia.json"),
|
|
4073
5009
|
TSCONFIG_INERTIA_TEMPLATE,
|
|
4074
5010
|
"tsconfig.inertia.json"
|
|
4075
5011
|
);
|
|
4076
5012
|
await writeIfNotExists(
|
|
4077
|
-
|
|
5013
|
+
join16(cwd, "inertia", "tsconfig.json"),
|
|
4078
5014
|
INERTIA_TSCONFIG_TEMPLATE,
|
|
4079
5015
|
"inertia/tsconfig.json"
|
|
4080
5016
|
);
|
|
4081
5017
|
await writeIfNotExists(
|
|
4082
|
-
|
|
5018
|
+
join16(cwd, "inertia", shellFileName),
|
|
4083
5019
|
htmlShellTemplate(framework, engine),
|
|
4084
5020
|
`inertia/${shellFileName}`
|
|
4085
5021
|
);
|
|
4086
5022
|
await handleViteConfig(cwd, framework);
|
|
4087
5023
|
await writeIfNotExists(
|
|
4088
|
-
|
|
5024
|
+
join16(cwd, "inertia", "app", `client.${entryExt}`),
|
|
4089
5025
|
entryPointTemplate(framework),
|
|
4090
5026
|
`inertia/app/client.${entryExt}`
|
|
4091
5027
|
);
|
|
4092
5028
|
await writeIfNotExists(
|
|
4093
|
-
|
|
5029
|
+
join16(cwd, "inertia", "pages", `Home.${pageExt}`),
|
|
4094
5030
|
samplePageTemplate(framework),
|
|
4095
5031
|
`inertia/pages/Home.${pageExt}`
|
|
4096
5032
|
);
|
|
4097
5033
|
await writeIfNotExists(
|
|
4098
|
-
|
|
5034
|
+
join16(cwd, "src", "home.controller.ts"),
|
|
4099
5035
|
SAMPLE_CONTROLLER,
|
|
4100
5036
|
"src/home.controller.ts"
|
|
4101
5037
|
);
|
|
4102
5038
|
}
|
|
4103
5039
|
function patchServerAppModule(ctx) {
|
|
4104
5040
|
const { cwd, rootView } = ctx;
|
|
4105
|
-
const appModulePath =
|
|
5041
|
+
const appModulePath = join16(cwd, "src", "app.module.ts");
|
|
4106
5042
|
const appModuleResult = patchAppModule(appModulePath, rootView);
|
|
4107
5043
|
if (appModuleResult === "patched") {
|
|
4108
5044
|
logPatched("src/app.module.ts", "added InertiaModule.forRoot");
|
|
@@ -4116,7 +5052,7 @@ function patchServerAppModule(ctx) {
|
|
|
4116
5052
|
}
|
|
4117
5053
|
}
|
|
4118
5054
|
function patchServerMainTs(ctx) {
|
|
4119
|
-
const mainTsPath =
|
|
5055
|
+
const mainTsPath = join16(ctx.cwd, "src", "main.ts");
|
|
4120
5056
|
const mainTsResult = patchMainTs(mainTsPath);
|
|
4121
5057
|
if (mainTsResult === "patched") {
|
|
4122
5058
|
logPatched("src/main.ts", "added setupInertiaVite after NestFactory.create");
|
|
@@ -4151,7 +5087,7 @@ function patchBuildConfigs(ctx) {
|
|
|
4151
5087
|
}
|
|
4152
5088
|
async function patchGitignoreAndDist(ctx) {
|
|
4153
5089
|
const { cwd } = ctx;
|
|
4154
|
-
await patchGitignore(
|
|
5090
|
+
await patchGitignore(join16(cwd, ".gitignore"));
|
|
4155
5091
|
const tsconfigDistResult = patchTsconfigExclude(cwd, "dist", "tsconfig.json");
|
|
4156
5092
|
if (tsconfigDistResult === "patched") {
|
|
4157
5093
|
logPatched("tsconfig.json", "excluded dist/ from server compilation");
|
|
@@ -4257,7 +5193,7 @@ ${green("\u2713")} Setup complete! Run: ${bold("nest start --watch")}
|
|
|
4257
5193
|
|
|
4258
5194
|
// src/cli/doctor.ts
|
|
4259
5195
|
function checkFileExists(cwd, file) {
|
|
4260
|
-
return existsSync(
|
|
5196
|
+
return existsSync(join17(cwd, file));
|
|
4261
5197
|
}
|
|
4262
5198
|
function readJson(path) {
|
|
4263
5199
|
try {
|
|
@@ -4293,15 +5229,15 @@ function writeJsonField(filePath, dotPath, value) {
|
|
|
4293
5229
|
}
|
|
4294
5230
|
function getPackageVersion(cwd, pkg) {
|
|
4295
5231
|
try {
|
|
4296
|
-
const pkgJson = readJson(
|
|
5232
|
+
const pkgJson = readJson(join17(cwd, "node_modules", pkg, "package.json"));
|
|
4297
5233
|
return pkgJson?.version ?? null;
|
|
4298
5234
|
} catch {
|
|
4299
5235
|
return null;
|
|
4300
5236
|
}
|
|
4301
5237
|
}
|
|
4302
5238
|
function detectPkgManager(cwd) {
|
|
4303
|
-
if (existsSync(
|
|
4304
|
-
if (existsSync(
|
|
5239
|
+
if (existsSync(join17(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
5240
|
+
if (existsSync(join17(cwd, "yarn.lock"))) return "yarn";
|
|
4305
5241
|
return "npm";
|
|
4306
5242
|
}
|
|
4307
5243
|
async function runDoctor(opts) {
|
|
@@ -4339,7 +5275,7 @@ async function runDoctor(opts) {
|
|
|
4339
5275
|
autoFix: () => runInit({ cwd })
|
|
4340
5276
|
});
|
|
4341
5277
|
if (foundShellDir) {
|
|
4342
|
-
const nestCliPath =
|
|
5278
|
+
const nestCliPath = join17(cwd, "nest-cli.json");
|
|
4343
5279
|
const nestCli = readJson(nestCliPath);
|
|
4344
5280
|
const compiler = nestCli?.compilerOptions ?? {};
|
|
4345
5281
|
const assets = compiler.assets ?? [];
|
|
@@ -4378,7 +5314,7 @@ async function runDoctor(opts) {
|
|
|
4378
5314
|
fix: "Run: nestjs-codegen codegen",
|
|
4379
5315
|
autoFix: () => runCodegen({ cwd })
|
|
4380
5316
|
});
|
|
4381
|
-
const tsconfigPath =
|
|
5317
|
+
const tsconfigPath = join17(cwd, "tsconfig.json");
|
|
4382
5318
|
const tsconfig = readJson(tsconfigPath);
|
|
4383
5319
|
const paths = tsconfig?.compilerOptions?.paths;
|
|
4384
5320
|
checks.push({
|
|
@@ -4389,7 +5325,7 @@ async function runDoctor(opts) {
|
|
|
4389
5325
|
});
|
|
4390
5326
|
const inertiaDir = foundShellDir ?? "inertia";
|
|
4391
5327
|
for (const tsconfigFile of ["tsconfig.json", "tsconfig.build.json"]) {
|
|
4392
|
-
const tsc = readJson(
|
|
5328
|
+
const tsc = readJson(join17(cwd, tsconfigFile));
|
|
4393
5329
|
if (!tsc) continue;
|
|
4394
5330
|
const excl = tsc.exclude ?? [];
|
|
4395
5331
|
const excludesIt = excl.includes(inertiaDir);
|
|
@@ -4415,7 +5351,7 @@ async function runDoctor(opts) {
|
|
|
4415
5351
|
}
|
|
4416
5352
|
});
|
|
4417
5353
|
}
|
|
4418
|
-
const inertiaTsconfigPath =
|
|
5354
|
+
const inertiaTsconfigPath = join17(cwd, "tsconfig.inertia.json");
|
|
4419
5355
|
const inertiaTsconfig = readJson(inertiaTsconfigPath);
|
|
4420
5356
|
checks.push({
|
|
4421
5357
|
name: "tsconfig.inertia.json exists",
|
|
@@ -4467,7 +5403,7 @@ async function runDoctor(opts) {
|
|
|
4467
5403
|
fix: 'Add "nestjs-inertia.d.ts" to include array (resolves InertiaRegistry augmentation)'
|
|
4468
5404
|
});
|
|
4469
5405
|
}
|
|
4470
|
-
const innerTsconfigPath =
|
|
5406
|
+
const innerTsconfigPath = join17(cwd, "inertia", "tsconfig.json");
|
|
4471
5407
|
checks.push({
|
|
4472
5408
|
name: "inertia/tsconfig.json exists (VSCode picks up ~codegen alias)",
|
|
4473
5409
|
pass: existsSync(innerTsconfigPath),
|
|
@@ -4477,7 +5413,7 @@ async function runDoctor(opts) {
|
|
|
4477
5413
|
}
|
|
4478
5414
|
});
|
|
4479
5415
|
if (checkFileExists(cwd, "vite.config.ts")) {
|
|
4480
|
-
const viteContent = readFileSync4(
|
|
5416
|
+
const viteContent = readFileSync4(join17(cwd, "vite.config.ts"), "utf8");
|
|
4481
5417
|
checks.push({
|
|
4482
5418
|
name: "vite.config.ts has resolve.alias",
|
|
4483
5419
|
pass: viteContent.includes("resolve") && viteContent.includes("alias"),
|
|
@@ -4542,7 +5478,7 @@ async function runDoctor(opts) {
|
|
|
4542
5478
|
});
|
|
4543
5479
|
}
|
|
4544
5480
|
if (checkFileExists(cwd, ".gitignore")) {
|
|
4545
|
-
const gitignorePath =
|
|
5481
|
+
const gitignorePath = join17(cwd, ".gitignore");
|
|
4546
5482
|
const gitignore = readFileSync4(gitignorePath, "utf8");
|
|
4547
5483
|
checks.push({
|
|
4548
5484
|
name: ".gitignore includes .nestjs-inertia/",
|
|
@@ -4551,7 +5487,7 @@ async function runDoctor(opts) {
|
|
|
4551
5487
|
autoFix: () => appendFileSync(gitignorePath, "\n.nestjs-inertia/\n")
|
|
4552
5488
|
});
|
|
4553
5489
|
}
|
|
4554
|
-
const pkgJsonPath =
|
|
5490
|
+
const pkgJsonPath = join17(cwd, "package.json");
|
|
4555
5491
|
const pkgJson = readJson(pkgJsonPath);
|
|
4556
5492
|
const scripts = pkgJson?.scripts ?? {};
|
|
4557
5493
|
checks.push({
|