@dudousxd/nestjs-inertia-codegen 1.3.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,6 +277,16 @@ function insertIntoTree(tree, segments, leaf, fullName) {
287
277
  }
288
278
  }
289
279
  __name(insertIntoTree, "insertIntoTree");
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
290
  function buildResponseType(c, outDir) {
291
291
  if (c.controllerRef) {
292
292
  let relPath = (0, import_node_path3.relative)(outDir, c.controllerRef.filePath).replace(/\.ts$/, "");
@@ -313,9 +313,10 @@ function emitRouterTypeBlock(tree, indent, outDir) {
313
313
  const bodyRef = c.contractSource.bodyRef;
314
314
  const body = method === "GET" ? "never" : bodyRef ? bodyRef.isArray ? `Array<${bodyRef.name}>` : bodyRef.name : c.contractSource.body ?? "never";
315
315
  const response = buildResponseType(c, outDir);
316
+ const params = buildParamsType(c.params);
316
317
  const safeMethod = JSON.stringify(method);
317
318
  const safeUrl = JSON.stringify(c.path);
318
- 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} };`);
319
320
  } else {
320
321
  lines.push(`${pad}${objKey}: {`);
321
322
  lines.push(...emitRouterTypeBlock(node.children, indent + 2, outDir));
@@ -338,21 +339,60 @@ function emitApiObjectBlock(tree, indent) {
338
339
  const fetcherMethod = method.toLowerCase();
339
340
  if (method === "GET") {
340
341
  const typeAccess = buildRouterTypeAccess(c.name);
342
+ const withParams = hasPathParams(c.params);
341
343
  lines.push(`${pad}${objKey}: {`);
342
- lines.push(`${pad} queryKey: (query?: ${typeAccess}['query']) => query !== undefined ? [${flatName}, query] as const : [${flatName}] as const,`);
343
- lines.push(`${pad} queryOptions: (query?: ${typeAccess}['query']) =>`);
344
- lines.push(`${pad} _queryOptions({`);
345
- lines.push(`${pad} queryKey: query !== undefined ? [${flatName}, query] as const : [${flatName}] as const,`);
346
- lines.push(`${pad} queryFn: () => fetcher.get<${typeAccess}['response']>(route(${flatName} as never) || ${safePath}, { query }),`);
347
- 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
+ }
348
383
  lines.push(`${pad}},`);
349
384
  } else {
350
385
  const typeAccess = buildRouterTypeAccess(c.name);
386
+ const withParams = hasPathParams(c.params);
351
387
  lines.push(`${pad}${objKey}: {`);
352
388
  lines.push(`${pad} queryKey: () => [${flatName}] as const,`);
353
389
  lines.push(`${pad} mutationOptions: () =>`);
354
390
  lines.push(`${pad} _mutationOptions({`);
355
- lines.push(`${pad} mutationFn: (body: ${typeAccess}['body']) => fetcher.${fetcherMethod}<${typeAccess}['response']>(route(${flatName} as never) || ${safePath}, { body }),`);
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
+ }
356
396
  lines.push(`${pad} }),`);
357
397
  lines.push(`${pad}},`);
358
398
  }
@@ -459,12 +499,12 @@ function buildApiFile(routes, outDir) {
459
499
  method: r.method,
460
500
  name,
461
501
  path: r.path,
502
+ params: r.params,
462
503
  controllerRef: r.controllerRef,
463
504
  contractSource: c.contractSource
464
505
  };
465
506
  insertIntoTree(tree, segments, leaf, name);
466
507
  }
467
- void detectCollisions;
468
508
  lines.push("export type ApiRouter = {");
469
509
  lines.push(...emitRouterTypeBlock(tree, 2, outDir ?? ""));
470
510
  lines.push("};");
@@ -563,7 +603,8 @@ __name(emitIndex, "emitIndex");
563
603
  // src/emit/emit-pages.ts
564
604
  var import_promises6 = require("fs/promises");
565
605
  var import_node_path6 = require("path");
566
- async function emitPages(pages, outDir) {
606
+ async function emitPages(pages, outDir, options = {}) {
607
+ const propsExport = options.propsExport ?? "ComponentProps";
567
608
  await (0, import_promises6.mkdir)(outDir, {
568
609
  recursive: true
569
610
  });
@@ -572,14 +613,40 @@ async function emitPages(pages, outDir) {
572
613
  const key = needsQuotes(p.name) ? JSON.stringify(p.name) : p.name;
573
614
  return ` ${key}: ${propType};`;
574
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";
575
623
  const content = `// Generated by @dudousxd/nestjs-inertia-codegen. Do not edit.
576
624
  export interface InertiaPages {
577
625
  ${body}
578
626
  }
627
+
628
+ export type InertiaPageName = ${pageNameUnion};
629
+ ` + propsHelper + `
630
+ declare module '@dudousxd/nestjs-inertia' {
631
+ interface InertiaPages {
632
+ ${augBody}
633
+ }
634
+ }
579
635
  `;
580
636
  await (0, import_promises6.writeFile)((0, import_node_path6.join)(outDir, "pages.d.ts"), content, "utf8");
581
637
  }
582
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");
583
650
  function needsQuotes(name) {
584
651
  return !/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name);
585
652
  }
@@ -716,7 +783,9 @@ async function generate(config, routes = []) {
716
783
  propsExport: config.pages.propsExport,
717
784
  componentNameStrategy: config.pages.componentNameStrategy
718
785
  });
719
- await emitPages(pages, config.codegen.outDir);
786
+ await emitPages(pages, config.codegen.outDir, {
787
+ propsExport: config.pages.propsExport
788
+ });
720
789
  await emitCache(pages, config.codegen.outDir);
721
790
  const hasRoutes = routes.length > 0;
722
791
  const hasContracts = routes.some((r) => r.contract);
@@ -731,7 +800,7 @@ async function generate(config, routes = []) {
731
800
  __name(generate, "generate");
732
801
 
733
802
  // src/watch/watcher.ts
734
- var import_promises9 = require("fs/promises");
803
+ var import_promises10 = require("fs/promises");
735
804
  var import_node_path10 = require("path");
736
805
  var import_chokidar = __toESM(require("chokidar"), 1);
737
806
 
@@ -740,8 +809,18 @@ var import_node_fs = require("fs");
740
809
  var import_node_path8 = require("path");
741
810
  var import_fast_glob2 = __toESM(require("fast-glob"), 1);
742
811
  var import_ts_morph = require("ts-morph");
743
- var _projectRoot = "";
744
- var _tsconfigPaths = null;
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");
745
824
  var _debug = process.env.NESTJS_INERTIA_DEBUG === "1";
746
825
  function dbg(...args) {
747
826
  if (_debug) console.log("[codegen:debug]", ...args);
@@ -790,10 +869,17 @@ async function discoverContractsFast(opts) {
790
869
  project.addSourceFileAtPath(f);
791
870
  }
792
871
  const routes = [];
793
- _projectRoot = cwd;
794
- _tsconfigPaths = loadTsconfigPaths(tsconfigPath);
795
- for (const sourceFile of project.getSourceFiles()) {
796
- 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;
797
883
  }
798
884
  return routes;
799
885
  }
@@ -1001,10 +1087,11 @@ function resolveModuleSpecifier(moduleSpecifier, sourceFile, project) {
1001
1087
  (0, import_node_path8.resolve)(dir, moduleSpecifier, "index.ts")
1002
1088
  ];
1003
1089
  }
1004
- const baseUrl = _projectRoot;
1005
- dbg("resolveModuleSpecifier", moduleSpecifier, "paths:", JSON.stringify(_tsconfigPaths), "baseUrl:", baseUrl);
1006
- if (_tsconfigPaths) {
1007
- for (const [pattern, mappings] of Object.entries(_tsconfigPaths)) {
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)) {
1008
1095
  const prefix = pattern.replace("*", "");
1009
1096
  if (moduleSpecifier.startsWith(prefix)) {
1010
1097
  const rest = moduleSpecifier.slice(prefix.length);
@@ -1490,18 +1577,16 @@ function extractFromSourceFile(sourceFile, project) {
1490
1577
  methodName,
1491
1578
  filePath: sourceFile.getFilePath()
1492
1579
  },
1493
- ...dtoContract ? {
1494
- contract: {
1495
- contractSource: {
1496
- query: dtoContract.query,
1497
- body: dtoContract.body,
1498
- response: dtoContract.response,
1499
- queryRef: dtoContract.queryRef,
1500
- bodyRef: dtoContract.bodyRef,
1501
- responseRef: dtoContract.responseRef
1502
- }
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
1503
1588
  }
1504
- } : {}
1589
+ }
1505
1590
  });
1506
1591
  }
1507
1592
  }
@@ -1512,6 +1597,7 @@ __name(extractFromSourceFile, "extractFromSourceFile");
1512
1597
 
1513
1598
  // src/watch/lock-file.ts
1514
1599
  var import_promises8 = require("fs/promises");
1600
+ var import_promises9 = require("fs/promises");
1515
1601
  var import_node_path9 = require("path");
1516
1602
  var LOCK_FILE = ".watcher.lock";
1517
1603
  function isProcessAlive(pid) {
@@ -1524,28 +1610,37 @@ function isProcessAlive(pid) {
1524
1610
  }
1525
1611
  __name(isProcessAlive, "isProcessAlive");
1526
1612
  async function acquireLock(outDir) {
1527
- await (0, import_promises8.mkdir)(outDir, {
1613
+ await (0, import_promises9.mkdir)(outDir, {
1528
1614
  recursive: true
1529
1615
  });
1530
1616
  const lockPath = (0, import_node_path9.join)(outDir, LOCK_FILE);
1531
- try {
1532
- const raw = await (0, import_promises8.readFile)(lockPath, "utf8");
1533
- const existing = JSON.parse(raw);
1534
- if (isProcessAlive(existing.pid)) {
1535
- return null;
1536
- }
1537
- } catch {
1538
- }
1539
1617
  const lockData = {
1540
1618
  pid: process.pid,
1541
1619
  startedAt: (/* @__PURE__ */ new Date()).toISOString()
1542
1620
  };
1543
- 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)}
1544
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
+ }
1545
1640
  return {
1546
1641
  release: /* @__PURE__ */ __name(async () => {
1547
1642
  try {
1548
- await (0, import_promises8.unlink)(lockPath);
1643
+ await (0, import_promises9.unlink)(lockPath);
1549
1644
  } catch {
1550
1645
  }
1551
1646
  }, "release")
@@ -1564,7 +1659,7 @@ async function watch(config, onChange) {
1564
1659
  if (lock === null) {
1565
1660
  let holderPid = "unknown";
1566
1661
  try {
1567
- 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");
1568
1663
  const data = JSON.parse(raw);
1569
1664
  if (data.pid !== void 0) holderPid = String(data.pid);
1570
1665
  } catch {
@@ -1605,7 +1700,8 @@ async function watch(config, onChange) {
1605
1700
  pagesDebounceTimer = void 0;
1606
1701
  try {
1607
1702
  await generate(config);
1608
- } catch {
1703
+ } catch (err) {
1704
+ console.error("[nestjs-inertia-codegen] Pages generation failed:", err instanceof Error ? err.message : err);
1609
1705
  }
1610
1706
  onChange?.();
1611
1707
  }, PAGES_DEBOUNCE_MS);
@@ -1643,7 +1739,8 @@ async function watch(config, onChange) {
1643
1739
  if (hasContracts) {
1644
1740
  await emitApi(routes, config.codegen.outDir);
1645
1741
  }
1646
- } catch {
1742
+ } catch (err) {
1743
+ console.error("[nestjs-inertia-codegen] Contracts generation failed:", err instanceof Error ? err.message : err);
1647
1744
  }
1648
1745
  onChange?.();
1649
1746
  }, config.contracts.debounceMs);
@@ -1671,7 +1768,7 @@ async function watch(config, onChange) {
1671
1768
  __name(watch, "watch");
1672
1769
 
1673
1770
  // src/index.ts
1674
- var VERSION = "1.3.0";
1771
+ var VERSION = "1.4.0";
1675
1772
 
1676
1773
  // src/cli/codegen.ts
1677
1774
  async function runCodegen(opts = {}) {
@@ -1701,11 +1798,163 @@ async function runCodegen(opts = {}) {
1701
1798
  }
1702
1799
  __name(runCodegen, "runCodegen");
1703
1800
 
1704
- // src/cli/init.ts
1705
- var import_node_child_process = require("child_process");
1801
+ // src/cli/doctor.ts
1706
1802
  var import_node_fs2 = require("fs");
1707
- var import_promises10 = require("fs/promises");
1708
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
+
1953
+ // src/cli/init.ts
1954
+ var import_node_child_process = require("child_process");
1955
+ var import_node_fs3 = require("fs");
1956
+ var import_promises11 = require("fs/promises");
1957
+ var import_node_path12 = require("path");
1709
1958
  var import_node_readline = require("readline");
1710
1959
  var GITIGNORE_ENTRY = ".nestjs-inertia/";
1711
1960
  var green = /* @__PURE__ */ __name((s) => `\x1B[32m${s}\x1B[0m`, "green");
@@ -1736,7 +1985,7 @@ ${bold(title)}`);
1736
1985
  __name(logSection, "logSection");
1737
1986
  async function readPackageJson(cwd) {
1738
1987
  try {
1739
- 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");
1740
1989
  return JSON.parse(raw);
1741
1990
  } catch {
1742
1991
  return {};
@@ -1774,7 +2023,7 @@ __name(detectTemplateEngine, "detectTemplateEngine");
1774
2023
  async function detectPackageManager(cwd) {
1775
2024
  async function exists(file) {
1776
2025
  try {
1777
- 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));
1778
2027
  return true;
1779
2028
  } catch {
1780
2029
  return false;
@@ -1805,7 +2054,7 @@ async function promptFramework() {
1805
2054
  __name(promptFramework, "promptFramework");
1806
2055
  async function fileExists2(filePath) {
1807
2056
  try {
1808
- await (0, import_promises10.access)(filePath);
2057
+ await (0, import_promises11.access)(filePath);
1809
2058
  return true;
1810
2059
  } catch {
1811
2060
  return false;
@@ -1819,18 +2068,18 @@ async function writeIfNotExists(filePath, content, label) {
1819
2068
  }
1820
2069
  const dir = filePath.substring(0, filePath.lastIndexOf("/"));
1821
2070
  if (dir) {
1822
- await (0, import_promises10.mkdir)(dir, {
2071
+ await (0, import_promises11.mkdir)(dir, {
1823
2072
  recursive: true
1824
2073
  });
1825
2074
  }
1826
- await (0, import_promises10.writeFile)(filePath, content, "utf8");
2075
+ await (0, import_promises11.writeFile)(filePath, content, "utf8");
1827
2076
  logCreated(label);
1828
2077
  }
1829
2078
  __name(writeIfNotExists, "writeIfNotExists");
1830
2079
  async function handleViteConfig(cwd, framework) {
1831
- const filePath = (0, import_node_path11.join)(cwd, "vite.config.ts");
2080
+ const filePath = (0, import_node_path12.join)(cwd, "vite.config.ts");
1832
2081
  if (await fileExists2(filePath)) {
1833
- const existing = await (0, import_promises10.readFile)(filePath, "utf8");
2082
+ const existing = await (0, import_promises11.readFile)(filePath, "utf8");
1834
2083
  const hasPlugin = existing.includes("nestInertia") || existing.includes("nestjs-inertia-vite/plugin");
1835
2084
  if (!hasPlugin) {
1836
2085
  logSkipped("vite.config.ts");
@@ -1844,18 +2093,18 @@ async function handleViteConfig(cwd, framework) {
1844
2093
  }
1845
2094
  const dir = filePath.substring(0, filePath.lastIndexOf("/"));
1846
2095
  if (dir) {
1847
- await (0, import_promises10.mkdir)(dir, {
2096
+ await (0, import_promises11.mkdir)(dir, {
1848
2097
  recursive: true
1849
2098
  });
1850
2099
  }
1851
- await (0, import_promises10.writeFile)(filePath, viteConfigTemplate(framework), "utf8");
2100
+ await (0, import_promises11.writeFile)(filePath, viteConfigTemplate(framework), "utf8");
1852
2101
  logCreated("vite.config.ts");
1853
2102
  }
1854
2103
  __name(handleViteConfig, "handleViteConfig");
1855
2104
  async function patchGitignore(gitignorePath) {
1856
2105
  let existing = "";
1857
2106
  if (await fileExists2(gitignorePath)) {
1858
- existing = await (0, import_promises10.readFile)(gitignorePath, "utf8");
2107
+ existing = await (0, import_promises11.readFile)(gitignorePath, "utf8");
1859
2108
  }
1860
2109
  if (existing.split("\n").some((line) => line.trim() === GITIGNORE_ENTRY)) {
1861
2110
  console.log(` ${cyan("\u2192")} .gitignore ${dim("(already contains .nestjs-inertia/, skipped)")}`);
@@ -1865,30 +2114,36 @@ async function patchGitignore(gitignorePath) {
1865
2114
  ` : `${existing}
1866
2115
  ${GITIGNORE_ENTRY}
1867
2116
  `;
1868
- await (0, import_promises10.writeFile)(gitignorePath, newContent, "utf8");
2117
+ await (0, import_promises11.writeFile)(gitignorePath, newContent, "utf8");
1869
2118
  logPatched(".gitignore", "added .nestjs-inertia/");
1870
2119
  }
1871
2120
  __name(patchGitignore, "patchGitignore");
1872
2121
  function installDeps(pkgManager, deps, dev) {
1873
2122
  if (deps.length === 0) return;
1874
- const flag = dev ? pkgManager === "npm" ? "--save-dev" : "-D" : "";
1875
- 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);
1876
2132
  logPatched(deps.join(", "), "installed");
1877
2133
  try {
1878
- (0, import_node_child_process.execSync)(cmd, {
2134
+ (0, import_node_child_process.execFileSync)(pkgManager, args, {
1879
2135
  stdio: "inherit"
1880
2136
  });
1881
2137
  } catch {
1882
- logWarning(`Failed to install deps. Run manually:
1883
- ${cmd}`);
2138
+ logWarning(`Failed to install: ${deps.join(", ")}`);
1884
2139
  }
1885
2140
  }
1886
2141
  __name(installDeps, "installDeps");
1887
2142
  async function patchPackageJsonScripts(cwd, scripts) {
1888
- const pkgPath = (0, import_node_path11.join)(cwd, "package.json");
2143
+ const pkgPath = (0, import_node_path12.join)(cwd, "package.json");
1889
2144
  let pkg = {};
1890
2145
  try {
1891
- pkg = JSON.parse(await (0, import_promises10.readFile)(pkgPath, "utf8"));
2146
+ pkg = JSON.parse(await (0, import_promises11.readFile)(pkgPath, "utf8"));
1892
2147
  } catch {
1893
2148
  return;
1894
2149
  }
@@ -1907,7 +2162,7 @@ async function patchPackageJsonScripts(cwd, scripts) {
1907
2162
  return;
1908
2163
  }
1909
2164
  pkg.scripts = existing;
1910
- await (0, import_promises10.writeFile)(pkgPath, `${JSON.stringify(pkg, null, 2)}
2165
+ await (0, import_promises11.writeFile)(pkgPath, `${JSON.stringify(pkg, null, 2)}
1911
2166
  `, "utf8");
1912
2167
  }
1913
2168
  __name(patchPackageJsonScripts, "patchPackageJsonScripts");
@@ -1927,7 +2182,7 @@ __name(findAfterLastImport, "findAfterLastImport");
1927
2182
  function patchAppModule(filePath, rootView) {
1928
2183
  let content;
1929
2184
  try {
1930
- content = (0, import_node_fs2.readFileSync)(filePath, "utf8");
2185
+ content = (0, import_node_fs3.readFileSync)(filePath, "utf8");
1931
2186
  } catch {
1932
2187
  return "skipped";
1933
2188
  }
@@ -1965,14 +2220,14 @@ ${indent}HomeController,${content.slice(bracketPos)}`;
1965
2220
  }
1966
2221
  }
1967
2222
  if (!changed) return "already";
1968
- (0, import_node_fs2.writeFileSync)(filePath, content, "utf8");
2223
+ (0, import_node_fs3.writeFileSync)(filePath, content, "utf8");
1969
2224
  return "patched";
1970
2225
  }
1971
2226
  __name(patchAppModule, "patchAppModule");
1972
2227
  function patchMainTs(filePath) {
1973
2228
  let content;
1974
2229
  try {
1975
- content = (0, import_node_fs2.readFileSync)(filePath, "utf8");
2230
+ content = (0, import_node_fs3.readFileSync)(filePath, "utf8");
1976
2231
  } catch {
1977
2232
  return "skipped";
1978
2233
  }
@@ -1996,7 +2251,7 @@ ${content.slice(insertAt)}`;
1996
2251
  });`;
1997
2252
  content = `${content.slice(0, insertAfterPos)}
1998
2253
  ${viteSetup}${content.slice(insertAfterPos)}`;
1999
- (0, import_node_fs2.writeFileSync)(filePath, content, "utf8");
2254
+ (0, import_node_fs3.writeFileSync)(filePath, content, "utf8");
2000
2255
  return "patched";
2001
2256
  }
2002
2257
  __name(patchMainTs, "patchMainTs");
@@ -2177,16 +2432,16 @@ ${bold("nestjs-inertia init")}`);
2177
2432
  const entryExt = framework === "react" ? "tsx" : "ts";
2178
2433
  const pageExt = framework === "react" ? "tsx" : framework === "vue" ? "vue" : "svelte";
2179
2434
  logSection("Scaffold files");
2180
- await writeIfNotExists((0, import_node_path11.join)(cwd, "nestjs-inertia.config.ts"), configTemplate(framework), "nestjs-inertia.config.ts");
2181
- await writeIfNotExists((0, import_node_path11.join)(cwd, "nestjs-inertia.d.ts"), DTS_TEMPLATE, "nestjs-inertia.d.ts");
2182
- 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}`);
2183
2438
  await handleViteConfig(cwd, framework);
2184
- await writeIfNotExists((0, import_node_path11.join)(cwd, "inertia", `app.${entryExt}`), entryPointTemplate(framework), `inertia/app.${entryExt}`);
2185
- await writeIfNotExists((0, import_node_path11.join)(cwd, "inertia", "pages", `Home.${pageExt}`), samplePageTemplate(framework), `inertia/pages/Home.${pageExt}`);
2186
- 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");
2187
2442
  logSection("Patch existing files");
2188
2443
  const rootView = engine === "html" ? "inertia/index.html" : `inertia/index.${engine === "handlebars" ? "hbs" : engine}`;
2189
- 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");
2190
2445
  const appModuleResult = patchAppModule(appModulePath, rootView);
2191
2446
  if (appModuleResult === "patched") {
2192
2447
  logPatched("src/app.module.ts", "added InertiaModule.forRoot");
@@ -2196,7 +2451,7 @@ ${bold("nestjs-inertia init")}`);
2196
2451
  } else {
2197
2452
  logWarning("src/app.module.ts not found \u2014 add InertiaModule.forRoot() manually");
2198
2453
  }
2199
- const mainTsPath = (0, import_node_path11.join)(cwd, "src", "main.ts");
2454
+ const mainTsPath = (0, import_node_path12.join)(cwd, "src", "main.ts");
2200
2455
  const mainTsResult = patchMainTs(mainTsPath);
2201
2456
  if (mainTsResult === "patched") {
2202
2457
  logPatched("src/main.ts", "added setupInertiaVite after NestFactory.create");
@@ -2205,7 +2460,7 @@ ${bold("nestjs-inertia init")}`);
2205
2460
  } else {
2206
2461
  logWarning("src/main.ts not found \u2014 add setupInertiaVite() manually");
2207
2462
  }
2208
- await patchGitignore((0, import_node_path11.join)(cwd, ".gitignore"));
2463
+ await patchGitignore((0, import_node_path12.join)(cwd, ".gitignore"));
2209
2464
  await patchPackageJsonScripts(cwd, {
2210
2465
  "build:client": "vite build",
2211
2466
  "build:ssr": "VITE_SSR=1 vite build --ssr"
@@ -2282,6 +2537,12 @@ async function run(argv) {
2282
2537
  cwd: process.cwd()
2283
2538
  });
2284
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
+ });
2285
2546
  cli.help();
2286
2547
  cli.version(VERSION);
2287
2548
  try {