@dudousxd/nestjs-inertia-codegen 1.2.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/main.cjs CHANGED
@@ -248,16 +248,6 @@ function validateNameSegment(seg, fullName) {
248
248
  }
249
249
  }
250
250
  __name(validateNameSegment, "validateNameSegment");
251
- function detectCollisions(tree, name) {
252
- for (const [key, node] of tree) {
253
- if (node.kind === "leaf") {
254
- } else {
255
- void key;
256
- }
257
- }
258
- void name;
259
- }
260
- __name(detectCollisions, "detectCollisions");
261
251
  function insertIntoTree(tree, segments, leaf, fullName) {
262
252
  const head = segments[0];
263
253
  const rest = segments.slice(1);
@@ -287,7 +277,30 @@ function insertIntoTree(tree, segments, leaf, fullName) {
287
277
  }
288
278
  }
289
279
  __name(insertIntoTree, "insertIntoTree");
290
- function emitRouterTypeBlock(tree, indent) {
280
+ function buildParamsType(params) {
281
+ const pathParams = params.filter((p) => p.source === "path");
282
+ if (pathParams.length === 0) return "never";
283
+ return `{ ${pathParams.map((p) => `${p.name}: string`).join("; ")} }`;
284
+ }
285
+ __name(buildParamsType, "buildParamsType");
286
+ function hasPathParams(params) {
287
+ return params.some((p) => p.source === "path");
288
+ }
289
+ __name(hasPathParams, "hasPathParams");
290
+ function buildResponseType(c, outDir) {
291
+ if (c.controllerRef) {
292
+ let relPath = (0, import_node_path3.relative)(outDir, c.controllerRef.filePath).replace(/\.ts$/, "");
293
+ if (!relPath.startsWith(".")) relPath = `./${relPath}`;
294
+ return `Awaited<ReturnType<import('${relPath}').${c.controllerRef.className}['${c.controllerRef.methodName}']>>`;
295
+ }
296
+ const respRef = c.contractSource.responseRef;
297
+ if (respRef) {
298
+ return respRef.isArray ? `Array<${respRef.name}>` : respRef.name;
299
+ }
300
+ return c.contractSource.response;
301
+ }
302
+ __name(buildResponseType, "buildResponseType");
303
+ function emitRouterTypeBlock(tree, indent, outDir) {
291
304
  const pad = " ".repeat(indent);
292
305
  const lines = [];
293
306
  for (const [key, node] of tree) {
@@ -299,14 +312,14 @@ function emitRouterTypeBlock(tree, indent) {
299
312
  const query = queryRef ? queryRef.isArray ? `Array<${queryRef.name}>` : queryRef.name : c.contractSource.query ?? "never";
300
313
  const bodyRef = c.contractSource.bodyRef;
301
314
  const body = method === "GET" ? "never" : bodyRef ? bodyRef.isArray ? `Array<${bodyRef.name}>` : bodyRef.name : c.contractSource.body ?? "never";
302
- const respRef = c.contractSource.responseRef;
303
- const response = respRef ? respRef.isArray ? `Array<${respRef.name}>` : respRef.name : c.contractSource.response;
315
+ const response = buildResponseType(c, outDir);
316
+ const params = buildParamsType(c.params);
304
317
  const safeMethod = JSON.stringify(method);
305
318
  const safeUrl = JSON.stringify(c.path);
306
- lines.push(`${pad}${objKey}: { method: ${safeMethod}; url: ${safeUrl}; query: ${query}; body: ${body}; response: ${response} };`);
319
+ lines.push(`${pad}${objKey}: { method: ${safeMethod}; url: ${safeUrl}; params: ${params}; query: ${query}; body: ${body}; response: ${response} };`);
307
320
  } else {
308
321
  lines.push(`${pad}${objKey}: {`);
309
- lines.push(...emitRouterTypeBlock(node.children, indent + 2));
322
+ lines.push(...emitRouterTypeBlock(node.children, indent + 2, outDir));
310
323
  lines.push(`${pad}};`);
311
324
  }
312
325
  }
@@ -326,20 +339,61 @@ function emitApiObjectBlock(tree, indent) {
326
339
  const fetcherMethod = method.toLowerCase();
327
340
  if (method === "GET") {
328
341
  const typeAccess = buildRouterTypeAccess(c.name);
342
+ const withParams = hasPathParams(c.params);
329
343
  lines.push(`${pad}${objKey}: {`);
330
- lines.push(`${pad} queryKey: (query?: ${typeAccess}['query']) => query !== undefined ? [${flatName}, query] as const : [${flatName}] as const,`);
331
- lines.push(`${pad} queryOptions: (query?: ${typeAccess}['query']) => ({`);
332
- lines.push(`${pad} queryKey: query !== undefined ? [${flatName}, query] as const : [${flatName}] as const,`);
333
- lines.push(`${pad} queryFn: () => fetcher.get<${typeAccess}['response']>(route(${flatName} as never) || ${safePath}, { query }),`);
334
- lines.push(`${pad} }),`);
344
+ if (withParams) {
345
+ lines.push(`${pad} queryKey: (params: ${typeAccess}['params'], query?: ${typeAccess}['query']) => query !== undefined ? [${flatName}, params, query] as const : [${flatName}, params] as const,`);
346
+ lines.push(`${pad} queryOptions: (params: ${typeAccess}['params'], query?: ${typeAccess}['query']) =>`);
347
+ lines.push(`${pad} _queryOptions({`);
348
+ lines.push(`${pad} queryKey: query !== undefined ? [${flatName}, params, query] as const : [${flatName}, params] as const,`);
349
+ lines.push(`${pad} queryFn: () => fetcher.get<${typeAccess}['response']>(route(${flatName} as never, params as never) || ${safePath}, { query }),`);
350
+ lines.push(`${pad} }),`);
351
+ lines.push(`${pad} infiniteQueryOptions: (params: ${typeAccess}['params'], query?: ${typeAccess}['query']) => ({`);
352
+ lines.push(`${pad} queryKey: query !== undefined ? [${flatName}, params, query] as const : [${flatName}, params] as const,`);
353
+ lines.push(`${pad} queryFn: ({ pageParam }: { pageParam: number }) => fetcher.get<${typeAccess}['response']>(route(${flatName} as never, params as never) || ${safePath}, { query: { ...query, page: pageParam } }),`);
354
+ lines.push(`${pad} initialPageParam: 1,`);
355
+ lines.push(`${pad} getNextPageParam: (lastPage: ${typeAccess}['response']) => {`);
356
+ lines.push(`${pad} const meta = (lastPage as any)?.meta;`);
357
+ lines.push(`${pad} if (meta?.page != null && meta?.lastPage != null) {`);
358
+ lines.push(`${pad} return meta.page < meta.lastPage ? meta.page + 1 : undefined;`);
359
+ lines.push(`${pad} }`);
360
+ lines.push(`${pad} return undefined;`);
361
+ lines.push(`${pad} },`);
362
+ lines.push(`${pad} }),`);
363
+ } else {
364
+ lines.push(`${pad} queryKey: (query?: ${typeAccess}['query']) => query !== undefined ? [${flatName}, query] as const : [${flatName}] as const,`);
365
+ lines.push(`${pad} queryOptions: (query?: ${typeAccess}['query']) =>`);
366
+ lines.push(`${pad} _queryOptions({`);
367
+ lines.push(`${pad} queryKey: query !== undefined ? [${flatName}, query] as const : [${flatName}] as const,`);
368
+ lines.push(`${pad} queryFn: () => fetcher.get<${typeAccess}['response']>(route(${flatName} as never) || ${safePath}, { query }),`);
369
+ lines.push(`${pad} }),`);
370
+ lines.push(`${pad} infiniteQueryOptions: (query?: ${typeAccess}['query']) => ({`);
371
+ lines.push(`${pad} queryKey: query !== undefined ? [${flatName}, query] as const : [${flatName}] as const,`);
372
+ lines.push(`${pad} queryFn: ({ pageParam }: { pageParam: number }) => fetcher.get<${typeAccess}['response']>(route(${flatName} as never) || ${safePath}, { query: { ...query, page: pageParam } }),`);
373
+ lines.push(`${pad} initialPageParam: 1,`);
374
+ lines.push(`${pad} getNextPageParam: (lastPage: ${typeAccess}['response']) => {`);
375
+ lines.push(`${pad} const meta = (lastPage as any)?.meta;`);
376
+ lines.push(`${pad} if (meta?.page != null && meta?.lastPage != null) {`);
377
+ lines.push(`${pad} return meta.page < meta.lastPage ? meta.page + 1 : undefined;`);
378
+ lines.push(`${pad} }`);
379
+ lines.push(`${pad} return undefined;`);
380
+ lines.push(`${pad} },`);
381
+ lines.push(`${pad} }),`);
382
+ }
335
383
  lines.push(`${pad}},`);
336
384
  } else {
337
385
  const typeAccess = buildRouterTypeAccess(c.name);
386
+ const withParams = hasPathParams(c.params);
338
387
  lines.push(`${pad}${objKey}: {`);
339
388
  lines.push(`${pad} queryKey: () => [${flatName}] as const,`);
340
- lines.push(`${pad} mutationOptions: () => ({`);
341
- lines.push(`${pad} mutationFn: (body: ${typeAccess}['body']) => fetcher.${fetcherMethod}<${typeAccess}['response']>(route(${flatName} as never) || ${safePath}, { body }),`);
342
- lines.push(`${pad} }),`);
389
+ lines.push(`${pad} mutationOptions: () =>`);
390
+ lines.push(`${pad} _mutationOptions({`);
391
+ if (withParams) {
392
+ lines.push(`${pad} mutationFn: (input: { params: ${typeAccess}['params']; body: ${typeAccess}['body'] }) => fetcher.${fetcherMethod}<${typeAccess}['response']>(route(${flatName} as never, input.params as never) || ${safePath}, { body: input.body }),`);
393
+ } else {
394
+ lines.push(`${pad} mutationFn: (body: ${typeAccess}['body']) => fetcher.${fetcherMethod}<${typeAccess}['response']>(route(${flatName} as never) || ${safePath}, { body }),`);
395
+ }
396
+ lines.push(`${pad} }),`);
343
397
  lines.push(`${pad}},`);
344
398
  }
345
399
  } else {
@@ -362,11 +416,15 @@ function buildApiFile(routes, outDir) {
362
416
  for (const r of contracted) {
363
417
  const cs = r.contract?.contractSource;
364
418
  if (!cs) continue;
365
- for (const ref of [
419
+ const refs = r.controllerRef ? [
420
+ cs.queryRef,
421
+ cs.bodyRef
422
+ ] : [
366
423
  cs.queryRef,
367
424
  cs.bodyRef,
368
425
  cs.responseRef
369
- ]) {
426
+ ];
427
+ for (const ref of refs) {
370
428
  if (!ref) continue;
371
429
  let names = importsByFile.get(ref.filePath);
372
430
  if (!names) {
@@ -376,10 +434,18 @@ function buildApiFile(routes, outDir) {
376
434
  names.add(ref.name);
377
435
  }
378
436
  }
437
+ const hasGetRoutes = contracted.some((r) => r.method === "GET");
438
+ const hasMutationRoutes = contracted.some((r) => r.method !== "GET");
379
439
  const lines = [
380
440
  "// Generated by @dudousxd/nestjs-inertia-codegen. Do not edit.",
381
441
  ""
382
442
  ];
443
+ const tqImports = [];
444
+ if (hasGetRoutes) tqImports.push("queryOptions as _queryOptions");
445
+ if (hasMutationRoutes) tqImports.push("mutationOptions as _mutationOptions");
446
+ if (tqImports.length > 0) {
447
+ lines.push(`import { ${tqImports.join(", ")} } from '@tanstack/react-query';`);
448
+ }
383
449
  lines.push("import { route } from './routes.js';");
384
450
  lines.push("import { createFetcher } from '@dudousxd/nestjs-inertia-client';");
385
451
  if (importsByFile.size > 0 && outDir) {
@@ -433,13 +499,14 @@ function buildApiFile(routes, outDir) {
433
499
  method: r.method,
434
500
  name,
435
501
  path: r.path,
502
+ params: r.params,
503
+ controllerRef: r.controllerRef,
436
504
  contractSource: c.contractSource
437
505
  };
438
506
  insertIntoTree(tree, segments, leaf, name);
439
507
  }
440
- void detectCollisions;
441
508
  lines.push("export type ApiRouter = {");
442
- lines.push(...emitRouterTypeBlock(tree, 2));
509
+ lines.push(...emitRouterTypeBlock(tree, 2, outDir ?? ""));
443
510
  lines.push("};");
444
511
  lines.push("");
445
512
  lines.push("export const api = {");
@@ -536,7 +603,8 @@ __name(emitIndex, "emitIndex");
536
603
  // src/emit/emit-pages.ts
537
604
  var import_promises6 = require("fs/promises");
538
605
  var import_node_path6 = require("path");
539
- async function emitPages(pages, outDir) {
606
+ async function emitPages(pages, outDir, options = {}) {
607
+ const propsExport = options.propsExport ?? "ComponentProps";
540
608
  await (0, import_promises6.mkdir)(outDir, {
541
609
  recursive: true
542
610
  });
@@ -545,14 +613,40 @@ async function emitPages(pages, outDir) {
545
613
  const key = needsQuotes(p.name) ? JSON.stringify(p.name) : p.name;
546
614
  return ` ${key}: ${propType};`;
547
615
  }).join("\n");
616
+ const pageNameUnion = pages.length > 0 ? pages.map((p) => JSON.stringify(p.name)).join(" | ") : "never";
617
+ const augBody = pages.map((p) => {
618
+ const key = needsQuotes(p.name) ? JSON.stringify(p.name) : p.name;
619
+ const valueType = buildAugmentationType(p, outDir, propsExport);
620
+ return ` ${key}: ${valueType};`;
621
+ }).join("\n");
622
+ const propsHelper = "\nexport type InertiaProps<K extends InertiaPageName> = import('@dudousxd/nestjs-inertia').InertiaPages[K];\n";
548
623
  const content = `// Generated by @dudousxd/nestjs-inertia-codegen. Do not edit.
549
624
  export interface InertiaPages {
550
625
  ${body}
551
626
  }
627
+
628
+ export type InertiaPageName = ${pageNameUnion};
629
+ ` + propsHelper + `
630
+ declare module '@dudousxd/nestjs-inertia' {
631
+ interface InertiaPages {
632
+ ${augBody}
633
+ }
634
+ }
552
635
  `;
553
636
  await (0, import_promises6.writeFile)((0, import_node_path6.join)(outDir, "pages.d.ts"), content, "utf8");
554
637
  }
555
638
  __name(emitPages, "emitPages");
639
+ function buildAugmentationType(page, outDir, propsExport) {
640
+ if (!page.propsSource) {
641
+ return "Record<string, unknown>";
642
+ }
643
+ let importPath = (0, import_node_path6.relative)(outDir, page.absolutePath).replace(/\.(tsx?|vue|svelte)$/, "");
644
+ if (!importPath.startsWith(".")) {
645
+ importPath = `./${importPath}`;
646
+ }
647
+ return `import('${importPath}').${propsExport}`;
648
+ }
649
+ __name(buildAugmentationType, "buildAugmentationType");
556
650
  function needsQuotes(name) {
557
651
  return !/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name);
558
652
  }
@@ -689,7 +783,9 @@ async function generate(config, routes = []) {
689
783
  propsExport: config.pages.propsExport,
690
784
  componentNameStrategy: config.pages.componentNameStrategy
691
785
  });
692
- await emitPages(pages, config.codegen.outDir);
786
+ await emitPages(pages, config.codegen.outDir, {
787
+ propsExport: config.pages.propsExport
788
+ });
693
789
  await emitCache(pages, config.codegen.outDir);
694
790
  const hasRoutes = routes.length > 0;
695
791
  const hasContracts = routes.some((r) => r.contract);
@@ -704,14 +800,43 @@ async function generate(config, routes = []) {
704
800
  __name(generate, "generate");
705
801
 
706
802
  // src/watch/watcher.ts
707
- var import_promises9 = require("fs/promises");
803
+ var import_promises10 = require("fs/promises");
708
804
  var import_node_path10 = require("path");
709
805
  var import_chokidar = __toESM(require("chokidar"), 1);
710
806
 
711
807
  // src/discovery/contracts-fast.ts
808
+ var import_node_fs = require("fs");
712
809
  var import_node_path8 = require("path");
713
810
  var import_fast_glob2 = __toESM(require("fast-glob"), 1);
714
811
  var import_ts_morph = require("ts-morph");
812
+ var _ctx = {
813
+ projectRoot: "",
814
+ tsconfigPaths: null
815
+ };
816
+ function _projectRoot() {
817
+ return _ctx.projectRoot;
818
+ }
819
+ __name(_projectRoot, "_projectRoot");
820
+ function _tsconfigPaths() {
821
+ return _ctx.tsconfigPaths;
822
+ }
823
+ __name(_tsconfigPaths, "_tsconfigPaths");
824
+ var _debug = process.env.NESTJS_INERTIA_DEBUG === "1";
825
+ function dbg(...args) {
826
+ if (_debug) console.log("[codegen:debug]", ...args);
827
+ }
828
+ __name(dbg, "dbg");
829
+ function loadTsconfigPaths(tsconfigPath) {
830
+ try {
831
+ const raw = (0, import_node_fs.readFileSync)(tsconfigPath, "utf8");
832
+ const stripped = raw.replace(/\/\/.*$/gm, "");
833
+ const parsed = JSON.parse(stripped);
834
+ return parsed.compilerOptions?.paths ?? null;
835
+ } catch {
836
+ return null;
837
+ }
838
+ }
839
+ __name(loadTsconfigPaths, "loadTsconfigPaths");
715
840
  async function discoverContractsFast(opts) {
716
841
  const { cwd, glob, tsconfig } = opts;
717
842
  const tsconfigPath = tsconfig ? (0, import_node_path8.resolve)(tsconfig) : (0, import_node_path8.join)(cwd, "tsconfig.json");
@@ -744,8 +869,17 @@ async function discoverContractsFast(opts) {
744
869
  project.addSourceFileAtPath(f);
745
870
  }
746
871
  const routes = [];
747
- for (const sourceFile of project.getSourceFiles()) {
748
- routes.push(...extractFromSourceFile(sourceFile, project));
872
+ const prevCtx = _ctx;
873
+ _ctx = {
874
+ projectRoot: cwd,
875
+ tsconfigPaths: loadTsconfigPaths(tsconfigPath)
876
+ };
877
+ try {
878
+ for (const sourceFile of project.getSourceFiles()) {
879
+ routes.push(...extractFromSourceFile(sourceFile, project));
880
+ }
881
+ } finally {
882
+ _ctx = prevCtx;
749
883
  }
750
884
  return routes;
751
885
  }
@@ -945,17 +1079,42 @@ function findTypeInFile(name, file) {
945
1079
  return null;
946
1080
  }
947
1081
  __name(findTypeInFile, "findTypeInFile");
1082
+ function resolveModuleSpecifier(moduleSpecifier, sourceFile, project) {
1083
+ if (moduleSpecifier.startsWith(".")) {
1084
+ const dir = (0, import_node_path8.dirname)(sourceFile.getFilePath());
1085
+ return [
1086
+ (0, import_node_path8.resolve)(dir, `${moduleSpecifier}.ts`),
1087
+ (0, import_node_path8.resolve)(dir, moduleSpecifier, "index.ts")
1088
+ ];
1089
+ }
1090
+ const baseUrl = _projectRoot();
1091
+ const tsconfigPaths = _tsconfigPaths();
1092
+ dbg("resolveModuleSpecifier", moduleSpecifier, "paths:", JSON.stringify(tsconfigPaths), "baseUrl:", baseUrl);
1093
+ if (tsconfigPaths) {
1094
+ for (const [pattern, mappings] of Object.entries(tsconfigPaths)) {
1095
+ const prefix = pattern.replace("*", "");
1096
+ if (moduleSpecifier.startsWith(prefix)) {
1097
+ const rest = moduleSpecifier.slice(prefix.length);
1098
+ const candidates = [];
1099
+ for (const mapping of mappings) {
1100
+ const resolved = (0, import_node_path8.resolve)(baseUrl, mapping.replace("*", rest));
1101
+ candidates.push(`${resolved}.ts`, (0, import_node_path8.resolve)(resolved, "index.ts"));
1102
+ }
1103
+ dbg(" resolved candidates:", candidates);
1104
+ return candidates;
1105
+ }
1106
+ }
1107
+ }
1108
+ return [];
1109
+ }
1110
+ __name(resolveModuleSpecifier, "resolveModuleSpecifier");
948
1111
  function resolveImportedType(name, sourceFile, project) {
949
1112
  for (const importDecl of sourceFile.getImportDeclarations()) {
950
1113
  const namedImport = importDecl.getNamedImports().find((n) => n.getName() === name);
951
1114
  if (!namedImport) continue;
952
1115
  const moduleSpecifier = importDecl.getModuleSpecifierValue();
953
- if (!moduleSpecifier.startsWith(".")) return null;
954
- const dir = (0, import_node_path8.dirname)(sourceFile.getFilePath());
955
- const candidates = [
956
- (0, import_node_path8.resolve)(dir, `${moduleSpecifier}.ts`),
957
- (0, import_node_path8.resolve)(dir, moduleSpecifier, "index.ts")
958
- ];
1116
+ const candidates = resolveModuleSpecifier(moduleSpecifier, sourceFile, project);
1117
+ if (candidates.length === 0) continue;
959
1118
  for (const candidate of candidates) {
960
1119
  let importedFile = project.getSourceFile(candidate);
961
1120
  if (!importedFile) {
@@ -999,6 +1158,18 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth) {
999
1158
  }
1000
1159
  return "Array<unknown>";
1001
1160
  }
1161
+ if ([
1162
+ "Record",
1163
+ "Omit",
1164
+ "Pick",
1165
+ "Partial",
1166
+ "Required",
1167
+ "Readonly",
1168
+ "Map",
1169
+ "Set"
1170
+ ].includes(name)) {
1171
+ return typeNode.getText();
1172
+ }
1002
1173
  if (name === "Promise") {
1003
1174
  const typeArgs = typeNode.getTypeArguments();
1004
1175
  const firstTypeArg = typeArgs[0];
@@ -1011,7 +1182,8 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth) {
1011
1182
  if (resolved) {
1012
1183
  return expandTypeDecl(resolved, project, depth - 1);
1013
1184
  }
1014
- return name;
1185
+ dbg("unresolvable type:", name, "in", sourceFile.getFilePath());
1186
+ return "unknown";
1015
1187
  }
1016
1188
  const kind = typeNode.getKind();
1017
1189
  if (kind === import_ts_morph.SyntaxKind.StringKeyword) return "string";
@@ -1173,7 +1345,7 @@ function tryResolveTypeRef(typeNode, sourceFile, project) {
1173
1345
  return null;
1174
1346
  }
1175
1347
  const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
1176
- if (localDecl && localDecl.isExported()) {
1348
+ if (localDecl?.isExported()) {
1177
1349
  return {
1178
1350
  name,
1179
1351
  filePath: sourceFile.getFilePath()
@@ -1235,7 +1407,7 @@ function extractDtoContract(method, sourceFile, project) {
1235
1407
  if (val && import_ts_morph.Node.isIdentifier(val)) {
1236
1408
  const name = val.getText();
1237
1409
  const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
1238
- if (localDecl && localDecl.isExported()) {
1410
+ if (localDecl?.isExported()) {
1239
1411
  responseRef = {
1240
1412
  name,
1241
1413
  filePath: sourceFile.getFilePath()
@@ -1361,6 +1533,11 @@ function extractFromSourceFile(sourceFile, project) {
1361
1533
  path: combined,
1362
1534
  name: routeName,
1363
1535
  params,
1536
+ controllerRef: {
1537
+ className,
1538
+ methodName,
1539
+ filePath: sourceFile.getFilePath()
1540
+ },
1364
1541
  contract: {
1365
1542
  contractSource: {
1366
1543
  query: contractDef.query,
@@ -1395,19 +1572,21 @@ function extractFromSourceFile(sourceFile, project) {
1395
1572
  path: combined,
1396
1573
  name: routeName,
1397
1574
  params,
1398
- // Attach contract if DTO extraction produced useful type info
1399
- ...dtoContract ? {
1400
- contract: {
1401
- contractSource: {
1402
- query: dtoContract.query,
1403
- body: dtoContract.body,
1404
- response: dtoContract.response,
1405
- queryRef: dtoContract.queryRef,
1406
- bodyRef: dtoContract.bodyRef,
1407
- responseRef: dtoContract.responseRef
1408
- }
1575
+ controllerRef: {
1576
+ className,
1577
+ methodName,
1578
+ filePath: sourceFile.getFilePath()
1579
+ },
1580
+ contract: {
1581
+ contractSource: {
1582
+ query: dtoContract?.query ?? null,
1583
+ body: dtoContract?.body ?? null,
1584
+ response: dtoContract?.response ?? "unknown",
1585
+ queryRef: dtoContract?.queryRef,
1586
+ bodyRef: dtoContract?.bodyRef,
1587
+ responseRef: dtoContract?.responseRef
1409
1588
  }
1410
- } : {}
1589
+ }
1411
1590
  });
1412
1591
  }
1413
1592
  }
@@ -1418,6 +1597,7 @@ __name(extractFromSourceFile, "extractFromSourceFile");
1418
1597
 
1419
1598
  // src/watch/lock-file.ts
1420
1599
  var import_promises8 = require("fs/promises");
1600
+ var import_promises9 = require("fs/promises");
1421
1601
  var import_node_path9 = require("path");
1422
1602
  var LOCK_FILE = ".watcher.lock";
1423
1603
  function isProcessAlive(pid) {
@@ -1430,28 +1610,37 @@ function isProcessAlive(pid) {
1430
1610
  }
1431
1611
  __name(isProcessAlive, "isProcessAlive");
1432
1612
  async function acquireLock(outDir) {
1433
- await (0, import_promises8.mkdir)(outDir, {
1613
+ await (0, import_promises9.mkdir)(outDir, {
1434
1614
  recursive: true
1435
1615
  });
1436
1616
  const lockPath = (0, import_node_path9.join)(outDir, LOCK_FILE);
1437
- try {
1438
- const raw = await (0, import_promises8.readFile)(lockPath, "utf8");
1439
- const existing = JSON.parse(raw);
1440
- if (isProcessAlive(existing.pid)) {
1441
- return null;
1442
- }
1443
- } catch {
1444
- }
1445
1617
  const lockData = {
1446
1618
  pid: process.pid,
1447
1619
  startedAt: (/* @__PURE__ */ new Date()).toISOString()
1448
1620
  };
1449
- await (0, import_promises8.writeFile)(lockPath, `${JSON.stringify(lockData, null, 2)}
1621
+ try {
1622
+ const fd = await (0, import_promises8.open)(lockPath, "wx");
1623
+ await fd.writeFile(`${JSON.stringify(lockData, null, 2)}
1450
1624
  `, "utf8");
1625
+ await fd.close();
1626
+ } catch (err) {
1627
+ if (err.code === "EEXIST") {
1628
+ try {
1629
+ const raw = await (0, import_promises9.readFile)(lockPath, "utf8");
1630
+ const existing = JSON.parse(raw);
1631
+ if (isProcessAlive(existing.pid)) return null;
1632
+ await (0, import_promises9.unlink)(lockPath);
1633
+ return acquireLock(outDir);
1634
+ } catch {
1635
+ return null;
1636
+ }
1637
+ }
1638
+ return null;
1639
+ }
1451
1640
  return {
1452
1641
  release: /* @__PURE__ */ __name(async () => {
1453
1642
  try {
1454
- await (0, import_promises8.unlink)(lockPath);
1643
+ await (0, import_promises9.unlink)(lockPath);
1455
1644
  } catch {
1456
1645
  }
1457
1646
  }, "release")
@@ -1470,7 +1659,7 @@ async function watch(config, onChange) {
1470
1659
  if (lock === null) {
1471
1660
  let holderPid = "unknown";
1472
1661
  try {
1473
- const raw = await (0, import_promises9.readFile)((0, import_node_path10.join)(config.codegen.outDir, ".watcher.lock"), "utf8");
1662
+ const raw = await (0, import_promises10.readFile)((0, import_node_path10.join)(config.codegen.outDir, ".watcher.lock"), "utf8");
1474
1663
  const data = JSON.parse(raw);
1475
1664
  if (data.pid !== void 0) holderPid = String(data.pid);
1476
1665
  } catch {
@@ -1511,7 +1700,8 @@ async function watch(config, onChange) {
1511
1700
  pagesDebounceTimer = void 0;
1512
1701
  try {
1513
1702
  await generate(config);
1514
- } catch {
1703
+ } catch (err) {
1704
+ console.error("[nestjs-inertia-codegen] Pages generation failed:", err instanceof Error ? err.message : err);
1515
1705
  }
1516
1706
  onChange?.();
1517
1707
  }, PAGES_DEBOUNCE_MS);
@@ -1549,7 +1739,8 @@ async function watch(config, onChange) {
1549
1739
  if (hasContracts) {
1550
1740
  await emitApi(routes, config.codegen.outDir);
1551
1741
  }
1552
- } catch {
1742
+ } catch (err) {
1743
+ console.error("[nestjs-inertia-codegen] Contracts generation failed:", err instanceof Error ? err.message : err);
1553
1744
  }
1554
1745
  onChange?.();
1555
1746
  }, config.contracts.debounceMs);
@@ -1577,7 +1768,7 @@ async function watch(config, onChange) {
1577
1768
  __name(watch, "watch");
1578
1769
 
1579
1770
  // src/index.ts
1580
- var VERSION = "1.2.0";
1771
+ var VERSION = "1.4.0";
1581
1772
 
1582
1773
  // src/cli/codegen.ts
1583
1774
  async function runCodegen(opts = {}) {
@@ -1607,11 +1798,163 @@ async function runCodegen(opts = {}) {
1607
1798
  }
1608
1799
  __name(runCodegen, "runCodegen");
1609
1800
 
1801
+ // src/cli/doctor.ts
1802
+ var import_node_fs2 = require("fs");
1803
+ var import_node_path11 = require("path");
1804
+ function checkFileExists(cwd, file) {
1805
+ return (0, import_node_fs2.existsSync)((0, import_node_path11.join)(cwd, file));
1806
+ }
1807
+ __name(checkFileExists, "checkFileExists");
1808
+ function readJson(path) {
1809
+ try {
1810
+ const raw = (0, import_node_fs2.readFileSync)(path, "utf8").replace(/\/\/.*$/gm, "");
1811
+ return JSON.parse(raw);
1812
+ } catch {
1813
+ return null;
1814
+ }
1815
+ }
1816
+ __name(readJson, "readJson");
1817
+ function getPackageVersion(cwd, pkg) {
1818
+ try {
1819
+ const pkgJson = readJson((0, import_node_path11.join)(cwd, "node_modules", pkg, "package.json"));
1820
+ return pkgJson?.version ?? null;
1821
+ } catch {
1822
+ return null;
1823
+ }
1824
+ }
1825
+ __name(getPackageVersion, "getPackageVersion");
1826
+ async function runDoctor(opts) {
1827
+ const { cwd } = opts;
1828
+ const checks = [];
1829
+ checks.push({
1830
+ name: "nestjs-inertia.config.ts exists",
1831
+ pass: checkFileExists(cwd, "nestjs-inertia.config.ts"),
1832
+ fix: "Run: pnpm exec nestjs-inertia init"
1833
+ });
1834
+ const hasApi = checkFileExists(cwd, ".nestjs-inertia/api.ts");
1835
+ const hasRoutes = checkFileExists(cwd, ".nestjs-inertia/routes.ts");
1836
+ const hasPages = checkFileExists(cwd, ".nestjs-inertia/pages.d.ts");
1837
+ checks.push({
1838
+ name: ".nestjs-inertia/ codegen output exists",
1839
+ pass: hasApi && hasRoutes && hasPages,
1840
+ fix: "Run: pnpm exec nestjs-inertia codegen"
1841
+ });
1842
+ const tsconfig = readJson((0, import_node_path11.join)(cwd, "tsconfig.json"));
1843
+ const paths = tsconfig?.compilerOptions?.paths;
1844
+ checks.push({
1845
+ name: "tsconfig.json has @/* path alias",
1846
+ pass: !!paths?.["@/*"],
1847
+ fix: 'Add to tsconfig.json compilerOptions.paths: { "@/*": ["./src/*"] }'
1848
+ });
1849
+ const inertiaTsconfig = readJson((0, import_node_path11.join)(cwd, "tsconfig.inertia.json"));
1850
+ if (inertiaTsconfig) {
1851
+ const inertiaPaths = inertiaTsconfig.compilerOptions?.paths;
1852
+ checks.push({
1853
+ name: "tsconfig.inertia.json has ~/* and ~codegen/* aliases",
1854
+ pass: !!inertiaPaths?.["~/*"] && !!inertiaPaths?.["~codegen/*"],
1855
+ fix: 'Add paths: { "~/*": ["inertia/*"], "~codegen/*": [".nestjs-inertia/*"] }'
1856
+ });
1857
+ }
1858
+ if (checkFileExists(cwd, "vite.config.ts")) {
1859
+ const viteContent = (0, import_node_fs2.readFileSync)((0, import_node_path11.join)(cwd, "vite.config.ts"), "utf8");
1860
+ checks.push({
1861
+ name: "vite.config.ts has resolve.alias",
1862
+ pass: viteContent.includes("resolve") && viteContent.includes("alias"),
1863
+ fix: "Add resolve.alias with @\u2192src, ~\u2192inertia, ~codegen\u2192.nestjs-inertia"
1864
+ });
1865
+ checks.push({
1866
+ name: "vite.config.ts references nestjs-inertia",
1867
+ pass: viteContent.includes("nestInertia") || viteContent.includes("nestjs-inertia") || viteContent.includes("setupInertiaVite"),
1868
+ fix: "Add: import nestInertia from '@dudousxd/nestjs-inertia-vite/plugin'"
1869
+ });
1870
+ }
1871
+ const libPackages = [
1872
+ "@dudousxd/nestjs-inertia",
1873
+ "@dudousxd/nestjs-inertia-codegen",
1874
+ "@dudousxd/nestjs-inertia-client",
1875
+ "@dudousxd/nestjs-inertia-vite",
1876
+ "@dudousxd/nestjs-inertia-testing"
1877
+ ];
1878
+ const versions = libPackages.map((pkg) => ({
1879
+ pkg,
1880
+ version: getPackageVersion(cwd, pkg)
1881
+ }));
1882
+ const installed = versions.filter((v) => v.version !== null);
1883
+ const uniqueVersions = new Set(installed.map((v) => v.version));
1884
+ const requiredPkgs = [
1885
+ "@dudousxd/nestjs-inertia",
1886
+ "@dudousxd/nestjs-inertia-codegen",
1887
+ "@dudousxd/nestjs-inertia-client"
1888
+ ];
1889
+ const missingRequired = requiredPkgs.filter((pkg) => !getPackageVersion(cwd, pkg));
1890
+ checks.push({
1891
+ name: "Core packages installed (core + codegen + client)",
1892
+ pass: missingRequired.length === 0,
1893
+ fix: missingRequired.length > 0 ? `Missing: ${missingRequired.join(", ")}` : void 0
1894
+ });
1895
+ if (installed.length > 1) {
1896
+ checks.push({
1897
+ name: "All packages on same version",
1898
+ pass: uniqueVersions.size === 1,
1899
+ fix: `Versions: ${installed.map((v) => `${v.pkg.replace("@dudousxd/", "")}@${v.version}`).join(", ")}`
1900
+ });
1901
+ }
1902
+ const inertiaReact = getPackageVersion(cwd, "@inertiajs/react");
1903
+ const inertiaVue = getPackageVersion(cwd, "@inertiajs/vue3");
1904
+ const inertiaSvelte = getPackageVersion(cwd, "@inertiajs/svelte");
1905
+ const inertiaVersion = inertiaReact ?? inertiaVue ?? inertiaSvelte;
1906
+ const inertiaFramework = inertiaReact ? "react" : inertiaVue ? "vue" : inertiaSvelte ? "svelte" : null;
1907
+ if (inertiaVersion) {
1908
+ const majorVersion = Number.parseInt(inertiaVersion.split(".")[0] ?? "0", 10);
1909
+ checks.push({
1910
+ name: `@inertiajs/${inertiaFramework} is v3+`,
1911
+ pass: majorVersion >= 3,
1912
+ fix: `Current: v${inertiaVersion}. Run: pnpm add @inertiajs/${inertiaFramework}@^3.0.0`
1913
+ });
1914
+ }
1915
+ if (checkFileExists(cwd, ".gitignore")) {
1916
+ const gitignore = (0, import_node_fs2.readFileSync)((0, import_node_path11.join)(cwd, ".gitignore"), "utf8");
1917
+ checks.push({
1918
+ name: ".gitignore includes .nestjs-inertia/",
1919
+ pass: gitignore.includes(".nestjs-inertia"),
1920
+ fix: "Add .nestjs-inertia/ to .gitignore"
1921
+ });
1922
+ }
1923
+ const pkgJson = readJson((0, import_node_path11.join)(cwd, "package.json"));
1924
+ const scripts = pkgJson?.scripts ?? {};
1925
+ checks.push({
1926
+ name: "package.json has build:client script",
1927
+ pass: !!scripts["build:client"],
1928
+ fix: 'Add: "build:client": "vite build"'
1929
+ });
1930
+ console.log("");
1931
+ console.log("\x1B[1mnestjs-inertia doctor\x1B[0m");
1932
+ console.log("");
1933
+ let hasFailures = false;
1934
+ for (const check of checks) {
1935
+ const icon = check.pass ? "\x1B[32m\u2713\x1B[0m" : "\x1B[31m\u2717\x1B[0m";
1936
+ console.log(` ${icon} ${check.name}`);
1937
+ if (!check.pass && check.fix) {
1938
+ console.log(` \x1B[2m${check.fix}\x1B[0m`);
1939
+ hasFailures = true;
1940
+ }
1941
+ }
1942
+ console.log("");
1943
+ if (hasFailures) {
1944
+ console.log(`\x1B[33m${checks.filter((c) => !c.pass).length} issue(s) found\x1B[0m`);
1945
+ } else {
1946
+ console.log("\x1B[32mAll checks passed!\x1B[0m");
1947
+ }
1948
+ console.log("");
1949
+ return hasFailures ? 1 : 0;
1950
+ }
1951
+ __name(runDoctor, "runDoctor");
1952
+
1610
1953
  // src/cli/init.ts
1611
1954
  var import_node_child_process = require("child_process");
1612
- var import_node_fs = require("fs");
1613
- var import_promises10 = require("fs/promises");
1614
- var import_node_path11 = require("path");
1955
+ var import_node_fs3 = require("fs");
1956
+ var import_promises11 = require("fs/promises");
1957
+ var import_node_path12 = require("path");
1615
1958
  var import_node_readline = require("readline");
1616
1959
  var GITIGNORE_ENTRY = ".nestjs-inertia/";
1617
1960
  var green = /* @__PURE__ */ __name((s) => `\x1B[32m${s}\x1B[0m`, "green");
@@ -1642,7 +1985,7 @@ ${bold(title)}`);
1642
1985
  __name(logSection, "logSection");
1643
1986
  async function readPackageJson(cwd) {
1644
1987
  try {
1645
- const raw = await (0, import_promises10.readFile)((0, import_node_path11.join)(cwd, "package.json"), "utf8");
1988
+ const raw = await (0, import_promises11.readFile)((0, import_node_path12.join)(cwd, "package.json"), "utf8");
1646
1989
  return JSON.parse(raw);
1647
1990
  } catch {
1648
1991
  return {};
@@ -1680,7 +2023,7 @@ __name(detectTemplateEngine, "detectTemplateEngine");
1680
2023
  async function detectPackageManager(cwd) {
1681
2024
  async function exists(file) {
1682
2025
  try {
1683
- await (0, import_promises10.access)((0, import_node_path11.join)(cwd, file));
2026
+ await (0, import_promises11.access)((0, import_node_path12.join)(cwd, file));
1684
2027
  return true;
1685
2028
  } catch {
1686
2029
  return false;
@@ -1711,7 +2054,7 @@ async function promptFramework() {
1711
2054
  __name(promptFramework, "promptFramework");
1712
2055
  async function fileExists2(filePath) {
1713
2056
  try {
1714
- await (0, import_promises10.access)(filePath);
2057
+ await (0, import_promises11.access)(filePath);
1715
2058
  return true;
1716
2059
  } catch {
1717
2060
  return false;
@@ -1725,18 +2068,18 @@ async function writeIfNotExists(filePath, content, label) {
1725
2068
  }
1726
2069
  const dir = filePath.substring(0, filePath.lastIndexOf("/"));
1727
2070
  if (dir) {
1728
- await (0, import_promises10.mkdir)(dir, {
2071
+ await (0, import_promises11.mkdir)(dir, {
1729
2072
  recursive: true
1730
2073
  });
1731
2074
  }
1732
- await (0, import_promises10.writeFile)(filePath, content, "utf8");
2075
+ await (0, import_promises11.writeFile)(filePath, content, "utf8");
1733
2076
  logCreated(label);
1734
2077
  }
1735
2078
  __name(writeIfNotExists, "writeIfNotExists");
1736
2079
  async function handleViteConfig(cwd, framework) {
1737
- const filePath = (0, import_node_path11.join)(cwd, "vite.config.ts");
2080
+ const filePath = (0, import_node_path12.join)(cwd, "vite.config.ts");
1738
2081
  if (await fileExists2(filePath)) {
1739
- const existing = await (0, import_promises10.readFile)(filePath, "utf8");
2082
+ const existing = await (0, import_promises11.readFile)(filePath, "utf8");
1740
2083
  const hasPlugin = existing.includes("nestInertia") || existing.includes("nestjs-inertia-vite/plugin");
1741
2084
  if (!hasPlugin) {
1742
2085
  logSkipped("vite.config.ts");
@@ -1750,18 +2093,18 @@ async function handleViteConfig(cwd, framework) {
1750
2093
  }
1751
2094
  const dir = filePath.substring(0, filePath.lastIndexOf("/"));
1752
2095
  if (dir) {
1753
- await (0, import_promises10.mkdir)(dir, {
2096
+ await (0, import_promises11.mkdir)(dir, {
1754
2097
  recursive: true
1755
2098
  });
1756
2099
  }
1757
- await (0, import_promises10.writeFile)(filePath, viteConfigTemplate(framework), "utf8");
2100
+ await (0, import_promises11.writeFile)(filePath, viteConfigTemplate(framework), "utf8");
1758
2101
  logCreated("vite.config.ts");
1759
2102
  }
1760
2103
  __name(handleViteConfig, "handleViteConfig");
1761
2104
  async function patchGitignore(gitignorePath) {
1762
2105
  let existing = "";
1763
2106
  if (await fileExists2(gitignorePath)) {
1764
- existing = await (0, import_promises10.readFile)(gitignorePath, "utf8");
2107
+ existing = await (0, import_promises11.readFile)(gitignorePath, "utf8");
1765
2108
  }
1766
2109
  if (existing.split("\n").some((line) => line.trim() === GITIGNORE_ENTRY)) {
1767
2110
  console.log(` ${cyan("\u2192")} .gitignore ${dim("(already contains .nestjs-inertia/, skipped)")}`);
@@ -1771,30 +2114,36 @@ async function patchGitignore(gitignorePath) {
1771
2114
  ` : `${existing}
1772
2115
  ${GITIGNORE_ENTRY}
1773
2116
  `;
1774
- await (0, import_promises10.writeFile)(gitignorePath, newContent, "utf8");
2117
+ await (0, import_promises11.writeFile)(gitignorePath, newContent, "utf8");
1775
2118
  logPatched(".gitignore", "added .nestjs-inertia/");
1776
2119
  }
1777
2120
  __name(patchGitignore, "patchGitignore");
1778
2121
  function installDeps(pkgManager, deps, dev) {
1779
2122
  if (deps.length === 0) return;
1780
- const flag = dev ? pkgManager === "npm" ? "--save-dev" : "-D" : "";
1781
- const cmd = pkgManager === "npm" ? `npm install ${flag} ${deps.join(" ")}` : pkgManager === "yarn" ? `yarn add ${flag} ${deps.join(" ")}` : `pnpm add ${flag} ${deps.join(" ")}`;
2123
+ const args = [];
2124
+ if (pkgManager === "npm") {
2125
+ args.push("install");
2126
+ if (dev) args.push("--save-dev");
2127
+ } else {
2128
+ args.push("add");
2129
+ if (dev) args.push("-D");
2130
+ }
2131
+ args.push(...deps);
1782
2132
  logPatched(deps.join(", "), "installed");
1783
2133
  try {
1784
- (0, import_node_child_process.execSync)(cmd, {
2134
+ (0, import_node_child_process.execFileSync)(pkgManager, args, {
1785
2135
  stdio: "inherit"
1786
2136
  });
1787
2137
  } catch {
1788
- logWarning(`Failed to install deps. Run manually:
1789
- ${cmd}`);
2138
+ logWarning(`Failed to install: ${deps.join(", ")}`);
1790
2139
  }
1791
2140
  }
1792
2141
  __name(installDeps, "installDeps");
1793
2142
  async function patchPackageJsonScripts(cwd, scripts) {
1794
- const pkgPath = (0, import_node_path11.join)(cwd, "package.json");
2143
+ const pkgPath = (0, import_node_path12.join)(cwd, "package.json");
1795
2144
  let pkg = {};
1796
2145
  try {
1797
- pkg = JSON.parse(await (0, import_promises10.readFile)(pkgPath, "utf8"));
2146
+ pkg = JSON.parse(await (0, import_promises11.readFile)(pkgPath, "utf8"));
1798
2147
  } catch {
1799
2148
  return;
1800
2149
  }
@@ -1813,7 +2162,7 @@ async function patchPackageJsonScripts(cwd, scripts) {
1813
2162
  return;
1814
2163
  }
1815
2164
  pkg.scripts = existing;
1816
- await (0, import_promises10.writeFile)(pkgPath, `${JSON.stringify(pkg, null, 2)}
2165
+ await (0, import_promises11.writeFile)(pkgPath, `${JSON.stringify(pkg, null, 2)}
1817
2166
  `, "utf8");
1818
2167
  }
1819
2168
  __name(patchPackageJsonScripts, "patchPackageJsonScripts");
@@ -1833,7 +2182,7 @@ __name(findAfterLastImport, "findAfterLastImport");
1833
2182
  function patchAppModule(filePath, rootView) {
1834
2183
  let content;
1835
2184
  try {
1836
- content = (0, import_node_fs.readFileSync)(filePath, "utf8");
2185
+ content = (0, import_node_fs3.readFileSync)(filePath, "utf8");
1837
2186
  } catch {
1838
2187
  return "skipped";
1839
2188
  }
@@ -1871,14 +2220,14 @@ ${indent}HomeController,${content.slice(bracketPos)}`;
1871
2220
  }
1872
2221
  }
1873
2222
  if (!changed) return "already";
1874
- (0, import_node_fs.writeFileSync)(filePath, content, "utf8");
2223
+ (0, import_node_fs3.writeFileSync)(filePath, content, "utf8");
1875
2224
  return "patched";
1876
2225
  }
1877
2226
  __name(patchAppModule, "patchAppModule");
1878
2227
  function patchMainTs(filePath) {
1879
2228
  let content;
1880
2229
  try {
1881
- content = (0, import_node_fs.readFileSync)(filePath, "utf8");
2230
+ content = (0, import_node_fs3.readFileSync)(filePath, "utf8");
1882
2231
  } catch {
1883
2232
  return "skipped";
1884
2233
  }
@@ -1902,7 +2251,7 @@ ${content.slice(insertAt)}`;
1902
2251
  });`;
1903
2252
  content = `${content.slice(0, insertAfterPos)}
1904
2253
  ${viteSetup}${content.slice(insertAfterPos)}`;
1905
- (0, import_node_fs.writeFileSync)(filePath, content, "utf8");
2254
+ (0, import_node_fs3.writeFileSync)(filePath, content, "utf8");
1906
2255
  return "patched";
1907
2256
  }
1908
2257
  __name(patchMainTs, "patchMainTs");
@@ -1951,11 +2300,19 @@ function htmlShellTemplate(framework, _engine) {
1951
2300
  __name(htmlShellTemplate, "htmlShellTemplate");
1952
2301
  function viteConfigTemplate(framework) {
1953
2302
  const pluginOption = `{ ${framework}: true }`;
1954
- return `import { defineConfig } from 'vite';
2303
+ return `import { resolve } from 'node:path';
2304
+ import { defineConfig } from 'vite';
1955
2305
  import nestInertia from '@dudousxd/nestjs-inertia-vite/plugin';
1956
2306
 
1957
2307
  export default defineConfig({
1958
2308
  plugins: [nestInertia(${pluginOption})],
2309
+ resolve: {
2310
+ alias: {
2311
+ '@': resolve(__dirname, 'src'),
2312
+ '~': resolve(__dirname, 'inertia'),
2313
+ '~codegen': resolve(__dirname, '.nestjs-inertia'),
2314
+ },
2315
+ },
1959
2316
  });
1960
2317
  `;
1961
2318
  }
@@ -2075,16 +2432,16 @@ ${bold("nestjs-inertia init")}`);
2075
2432
  const entryExt = framework === "react" ? "tsx" : "ts";
2076
2433
  const pageExt = framework === "react" ? "tsx" : framework === "vue" ? "vue" : "svelte";
2077
2434
  logSection("Scaffold files");
2078
- await writeIfNotExists((0, import_node_path11.join)(cwd, "nestjs-inertia.config.ts"), configTemplate(framework), "nestjs-inertia.config.ts");
2079
- await writeIfNotExists((0, import_node_path11.join)(cwd, "nestjs-inertia.d.ts"), DTS_TEMPLATE, "nestjs-inertia.d.ts");
2080
- await writeIfNotExists((0, import_node_path11.join)(cwd, "inertia", shellFileName), htmlShellTemplate(framework, engine), `inertia/${shellFileName}`);
2435
+ await writeIfNotExists((0, import_node_path12.join)(cwd, "nestjs-inertia.config.ts"), configTemplate(framework), "nestjs-inertia.config.ts");
2436
+ await writeIfNotExists((0, import_node_path12.join)(cwd, "nestjs-inertia.d.ts"), DTS_TEMPLATE, "nestjs-inertia.d.ts");
2437
+ await writeIfNotExists((0, import_node_path12.join)(cwd, "inertia", shellFileName), htmlShellTemplate(framework, engine), `inertia/${shellFileName}`);
2081
2438
  await handleViteConfig(cwd, framework);
2082
- await writeIfNotExists((0, import_node_path11.join)(cwd, "inertia", `app.${entryExt}`), entryPointTemplate(framework), `inertia/app.${entryExt}`);
2083
- await writeIfNotExists((0, import_node_path11.join)(cwd, "inertia", "pages", `Home.${pageExt}`), samplePageTemplate(framework), `inertia/pages/Home.${pageExt}`);
2084
- await writeIfNotExists((0, import_node_path11.join)(cwd, "src", "home.controller.ts"), SAMPLE_CONTROLLER, "src/home.controller.ts");
2439
+ await writeIfNotExists((0, import_node_path12.join)(cwd, "inertia", `app.${entryExt}`), entryPointTemplate(framework), `inertia/app.${entryExt}`);
2440
+ await writeIfNotExists((0, import_node_path12.join)(cwd, "inertia", "pages", `Home.${pageExt}`), samplePageTemplate(framework), `inertia/pages/Home.${pageExt}`);
2441
+ await writeIfNotExists((0, import_node_path12.join)(cwd, "src", "home.controller.ts"), SAMPLE_CONTROLLER, "src/home.controller.ts");
2085
2442
  logSection("Patch existing files");
2086
2443
  const rootView = engine === "html" ? "inertia/index.html" : `inertia/index.${engine === "handlebars" ? "hbs" : engine}`;
2087
- const appModulePath = (0, import_node_path11.join)(cwd, "src", "app.module.ts");
2444
+ const appModulePath = (0, import_node_path12.join)(cwd, "src", "app.module.ts");
2088
2445
  const appModuleResult = patchAppModule(appModulePath, rootView);
2089
2446
  if (appModuleResult === "patched") {
2090
2447
  logPatched("src/app.module.ts", "added InertiaModule.forRoot");
@@ -2094,7 +2451,7 @@ ${bold("nestjs-inertia init")}`);
2094
2451
  } else {
2095
2452
  logWarning("src/app.module.ts not found \u2014 add InertiaModule.forRoot() manually");
2096
2453
  }
2097
- const mainTsPath = (0, import_node_path11.join)(cwd, "src", "main.ts");
2454
+ const mainTsPath = (0, import_node_path12.join)(cwd, "src", "main.ts");
2098
2455
  const mainTsResult = patchMainTs(mainTsPath);
2099
2456
  if (mainTsResult === "patched") {
2100
2457
  logPatched("src/main.ts", "added setupInertiaVite after NestFactory.create");
@@ -2103,7 +2460,7 @@ ${bold("nestjs-inertia init")}`);
2103
2460
  } else {
2104
2461
  logWarning("src/main.ts not found \u2014 add setupInertiaVite() manually");
2105
2462
  }
2106
- await patchGitignore((0, import_node_path11.join)(cwd, ".gitignore"));
2463
+ await patchGitignore((0, import_node_path12.join)(cwd, ".gitignore"));
2107
2464
  await patchPackageJsonScripts(cwd, {
2108
2465
  "build:client": "vite build",
2109
2466
  "build:ssr": "VITE_SSR=1 vite build --ssr"
@@ -2180,6 +2537,12 @@ async function run(argv) {
2180
2537
  cwd: process.cwd()
2181
2538
  });
2182
2539
  });
2540
+ cli.command("doctor", "Diagnose your nestjs-inertia setup").action(async () => {
2541
+ const code = await runDoctor({
2542
+ cwd: process.cwd()
2543
+ });
2544
+ process.exitCode = code;
2545
+ });
2183
2546
  cli.help();
2184
2547
  cli.version(VERSION);
2185
2548
  try {