@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.js CHANGED
@@ -216,16 +216,6 @@ function validateNameSegment(seg, fullName) {
216
216
  }
217
217
  }
218
218
  __name(validateNameSegment, "validateNameSegment");
219
- function detectCollisions(tree, name) {
220
- for (const [key, node] of tree) {
221
- if (node.kind === "leaf") {
222
- } else {
223
- void key;
224
- }
225
- }
226
- void name;
227
- }
228
- __name(detectCollisions, "detectCollisions");
229
219
  function insertIntoTree(tree, segments, leaf, fullName) {
230
220
  const head = segments[0];
231
221
  const rest = segments.slice(1);
@@ -255,6 +245,16 @@ function insertIntoTree(tree, segments, leaf, fullName) {
255
245
  }
256
246
  }
257
247
  __name(insertIntoTree, "insertIntoTree");
248
+ function buildParamsType(params) {
249
+ const pathParams = params.filter((p) => p.source === "path");
250
+ if (pathParams.length === 0) return "never";
251
+ return `{ ${pathParams.map((p) => `${p.name}: string`).join("; ")} }`;
252
+ }
253
+ __name(buildParamsType, "buildParamsType");
254
+ function hasPathParams(params) {
255
+ return params.some((p) => p.source === "path");
256
+ }
257
+ __name(hasPathParams, "hasPathParams");
258
258
  function buildResponseType(c, outDir) {
259
259
  if (c.controllerRef) {
260
260
  let relPath = relative3(outDir, c.controllerRef.filePath).replace(/\.ts$/, "");
@@ -281,9 +281,10 @@ function emitRouterTypeBlock(tree, indent, outDir) {
281
281
  const bodyRef = c.contractSource.bodyRef;
282
282
  const body = method === "GET" ? "never" : bodyRef ? bodyRef.isArray ? `Array<${bodyRef.name}>` : bodyRef.name : c.contractSource.body ?? "never";
283
283
  const response = buildResponseType(c, outDir);
284
+ const params = buildParamsType(c.params);
284
285
  const safeMethod = JSON.stringify(method);
285
286
  const safeUrl = JSON.stringify(c.path);
286
- lines.push(`${pad}${objKey}: { method: ${safeMethod}; url: ${safeUrl}; query: ${query}; body: ${body}; response: ${response} };`);
287
+ lines.push(`${pad}${objKey}: { method: ${safeMethod}; url: ${safeUrl}; params: ${params}; query: ${query}; body: ${body}; response: ${response} };`);
287
288
  } else {
288
289
  lines.push(`${pad}${objKey}: {`);
289
290
  lines.push(...emitRouterTypeBlock(node.children, indent + 2, outDir));
@@ -306,21 +307,60 @@ function emitApiObjectBlock(tree, indent) {
306
307
  const fetcherMethod = method.toLowerCase();
307
308
  if (method === "GET") {
308
309
  const typeAccess = buildRouterTypeAccess(c.name);
310
+ const withParams = hasPathParams(c.params);
309
311
  lines.push(`${pad}${objKey}: {`);
310
- lines.push(`${pad} queryKey: (query?: ${typeAccess}['query']) => query !== undefined ? [${flatName}, query] as const : [${flatName}] as const,`);
311
- lines.push(`${pad} queryOptions: (query?: ${typeAccess}['query']) =>`);
312
- lines.push(`${pad} _queryOptions({`);
313
- lines.push(`${pad} queryKey: query !== undefined ? [${flatName}, query] as const : [${flatName}] as const,`);
314
- lines.push(`${pad} queryFn: () => fetcher.get<${typeAccess}['response']>(route(${flatName} as never) || ${safePath}, { query }),`);
315
- lines.push(`${pad} }),`);
312
+ if (withParams) {
313
+ lines.push(`${pad} queryKey: (params: ${typeAccess}['params'], query?: ${typeAccess}['query']) => query !== undefined ? [${flatName}, params, query] as const : [${flatName}, params] as const,`);
314
+ lines.push(`${pad} queryOptions: (params: ${typeAccess}['params'], query?: ${typeAccess}['query']) =>`);
315
+ lines.push(`${pad} _queryOptions({`);
316
+ lines.push(`${pad} queryKey: query !== undefined ? [${flatName}, params, query] as const : [${flatName}, params] as const,`);
317
+ lines.push(`${pad} queryFn: () => fetcher.get<${typeAccess}['response']>(route(${flatName} as never, params as never) || ${safePath}, { query }),`);
318
+ lines.push(`${pad} }),`);
319
+ lines.push(`${pad} infiniteQueryOptions: (params: ${typeAccess}['params'], query?: ${typeAccess}['query']) => ({`);
320
+ lines.push(`${pad} queryKey: query !== undefined ? [${flatName}, params, query] as const : [${flatName}, params] as const,`);
321
+ lines.push(`${pad} queryFn: ({ pageParam }: { pageParam: number }) => fetcher.get<${typeAccess}['response']>(route(${flatName} as never, params as never) || ${safePath}, { query: { ...query, page: pageParam } }),`);
322
+ lines.push(`${pad} initialPageParam: 1,`);
323
+ lines.push(`${pad} getNextPageParam: (lastPage: ${typeAccess}['response']) => {`);
324
+ lines.push(`${pad} const meta = (lastPage as any)?.meta;`);
325
+ lines.push(`${pad} if (meta?.page != null && meta?.lastPage != null) {`);
326
+ lines.push(`${pad} return meta.page < meta.lastPage ? meta.page + 1 : undefined;`);
327
+ lines.push(`${pad} }`);
328
+ lines.push(`${pad} return undefined;`);
329
+ lines.push(`${pad} },`);
330
+ lines.push(`${pad} }),`);
331
+ } else {
332
+ lines.push(`${pad} queryKey: (query?: ${typeAccess}['query']) => query !== undefined ? [${flatName}, query] as const : [${flatName}] as const,`);
333
+ lines.push(`${pad} queryOptions: (query?: ${typeAccess}['query']) =>`);
334
+ lines.push(`${pad} _queryOptions({`);
335
+ lines.push(`${pad} queryKey: query !== undefined ? [${flatName}, query] as const : [${flatName}] as const,`);
336
+ lines.push(`${pad} queryFn: () => fetcher.get<${typeAccess}['response']>(route(${flatName} as never) || ${safePath}, { query }),`);
337
+ lines.push(`${pad} }),`);
338
+ lines.push(`${pad} infiniteQueryOptions: (query?: ${typeAccess}['query']) => ({`);
339
+ lines.push(`${pad} queryKey: query !== undefined ? [${flatName}, query] as const : [${flatName}] as const,`);
340
+ lines.push(`${pad} queryFn: ({ pageParam }: { pageParam: number }) => fetcher.get<${typeAccess}['response']>(route(${flatName} as never) || ${safePath}, { query: { ...query, page: pageParam } }),`);
341
+ lines.push(`${pad} initialPageParam: 1,`);
342
+ lines.push(`${pad} getNextPageParam: (lastPage: ${typeAccess}['response']) => {`);
343
+ lines.push(`${pad} const meta = (lastPage as any)?.meta;`);
344
+ lines.push(`${pad} if (meta?.page != null && meta?.lastPage != null) {`);
345
+ lines.push(`${pad} return meta.page < meta.lastPage ? meta.page + 1 : undefined;`);
346
+ lines.push(`${pad} }`);
347
+ lines.push(`${pad} return undefined;`);
348
+ lines.push(`${pad} },`);
349
+ lines.push(`${pad} }),`);
350
+ }
316
351
  lines.push(`${pad}},`);
317
352
  } else {
318
353
  const typeAccess = buildRouterTypeAccess(c.name);
354
+ const withParams = hasPathParams(c.params);
319
355
  lines.push(`${pad}${objKey}: {`);
320
356
  lines.push(`${pad} queryKey: () => [${flatName}] as const,`);
321
357
  lines.push(`${pad} mutationOptions: () =>`);
322
358
  lines.push(`${pad} _mutationOptions({`);
323
- lines.push(`${pad} mutationFn: (body: ${typeAccess}['body']) => fetcher.${fetcherMethod}<${typeAccess}['response']>(route(${flatName} as never) || ${safePath}, { body }),`);
359
+ if (withParams) {
360
+ 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 }),`);
361
+ } else {
362
+ lines.push(`${pad} mutationFn: (body: ${typeAccess}['body']) => fetcher.${fetcherMethod}<${typeAccess}['response']>(route(${flatName} as never) || ${safePath}, { body }),`);
363
+ }
324
364
  lines.push(`${pad} }),`);
325
365
  lines.push(`${pad}},`);
326
366
  }
@@ -427,12 +467,12 @@ function buildApiFile(routes, outDir) {
427
467
  method: r.method,
428
468
  name,
429
469
  path: r.path,
470
+ params: r.params,
430
471
  controllerRef: r.controllerRef,
431
472
  contractSource: c.contractSource
432
473
  };
433
474
  insertIntoTree(tree, segments, leaf, name);
434
475
  }
435
- void detectCollisions;
436
476
  lines.push("export type ApiRouter = {");
437
477
  lines.push(...emitRouterTypeBlock(tree, 2, outDir ?? ""));
438
478
  lines.push("};");
@@ -530,8 +570,9 @@ __name(emitIndex, "emitIndex");
530
570
 
531
571
  // src/emit/emit-pages.ts
532
572
  import { mkdir as mkdir4, writeFile as writeFile4 } from "fs/promises";
533
- import { join as join5 } from "path";
534
- async function emitPages(pages, outDir) {
573
+ import { join as join5, relative as relative4 } from "path";
574
+ async function emitPages(pages, outDir, options = {}) {
575
+ const propsExport = options.propsExport ?? "ComponentProps";
535
576
  await mkdir4(outDir, {
536
577
  recursive: true
537
578
  });
@@ -540,14 +581,40 @@ async function emitPages(pages, outDir) {
540
581
  const key = needsQuotes(p.name) ? JSON.stringify(p.name) : p.name;
541
582
  return ` ${key}: ${propType};`;
542
583
  }).join("\n");
584
+ const pageNameUnion = pages.length > 0 ? pages.map((p) => JSON.stringify(p.name)).join(" | ") : "never";
585
+ const augBody = pages.map((p) => {
586
+ const key = needsQuotes(p.name) ? JSON.stringify(p.name) : p.name;
587
+ const valueType = buildAugmentationType(p, outDir, propsExport);
588
+ return ` ${key}: ${valueType};`;
589
+ }).join("\n");
590
+ const propsHelper = "\nexport type InertiaProps<K extends InertiaPageName> = import('@dudousxd/nestjs-inertia').InertiaPages[K];\n";
543
591
  const content = `// Generated by @dudousxd/nestjs-inertia-codegen. Do not edit.
544
592
  export interface InertiaPages {
545
593
  ${body}
546
594
  }
595
+
596
+ export type InertiaPageName = ${pageNameUnion};
597
+ ` + propsHelper + `
598
+ declare module '@dudousxd/nestjs-inertia' {
599
+ interface InertiaPages {
600
+ ${augBody}
601
+ }
602
+ }
547
603
  `;
548
604
  await writeFile4(join5(outDir, "pages.d.ts"), content, "utf8");
549
605
  }
550
606
  __name(emitPages, "emitPages");
607
+ function buildAugmentationType(page, outDir, propsExport) {
608
+ if (!page.propsSource) {
609
+ return "Record<string, unknown>";
610
+ }
611
+ let importPath = relative4(outDir, page.absolutePath).replace(/\.(tsx?|vue|svelte)$/, "");
612
+ if (!importPath.startsWith(".")) {
613
+ importPath = `./${importPath}`;
614
+ }
615
+ return `import('${importPath}').${propsExport}`;
616
+ }
617
+ __name(buildAugmentationType, "buildAugmentationType");
551
618
  function needsQuotes(name) {
552
619
  return !/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name);
553
620
  }
@@ -684,7 +751,9 @@ async function generate(config, routes = []) {
684
751
  propsExport: config.pages.propsExport,
685
752
  componentNameStrategy: config.pages.componentNameStrategy
686
753
  });
687
- await emitPages(pages, config.codegen.outDir);
754
+ await emitPages(pages, config.codegen.outDir, {
755
+ propsExport: config.pages.propsExport
756
+ });
688
757
  await emitCache(pages, config.codegen.outDir);
689
758
  const hasRoutes = routes.length > 0;
690
759
  const hasContracts = routes.some((r) => r.contract);
@@ -708,8 +777,18 @@ import { readFileSync } from "fs";
708
777
  import { dirname, join as join7, resolve as resolve2 } from "path";
709
778
  import fg2 from "fast-glob";
710
779
  import { Node, Project, SyntaxKind } from "ts-morph";
711
- var _projectRoot = "";
712
- var _tsconfigPaths = null;
780
+ var _ctx = {
781
+ projectRoot: "",
782
+ tsconfigPaths: null
783
+ };
784
+ function _projectRoot() {
785
+ return _ctx.projectRoot;
786
+ }
787
+ __name(_projectRoot, "_projectRoot");
788
+ function _tsconfigPaths() {
789
+ return _ctx.tsconfigPaths;
790
+ }
791
+ __name(_tsconfigPaths, "_tsconfigPaths");
713
792
  var _debug = process.env.NESTJS_INERTIA_DEBUG === "1";
714
793
  function dbg(...args) {
715
794
  if (_debug) console.log("[codegen:debug]", ...args);
@@ -758,10 +837,17 @@ async function discoverContractsFast(opts) {
758
837
  project.addSourceFileAtPath(f);
759
838
  }
760
839
  const routes = [];
761
- _projectRoot = cwd;
762
- _tsconfigPaths = loadTsconfigPaths(tsconfigPath);
763
- for (const sourceFile of project.getSourceFiles()) {
764
- routes.push(...extractFromSourceFile(sourceFile, project));
840
+ const prevCtx = _ctx;
841
+ _ctx = {
842
+ projectRoot: cwd,
843
+ tsconfigPaths: loadTsconfigPaths(tsconfigPath)
844
+ };
845
+ try {
846
+ for (const sourceFile of project.getSourceFiles()) {
847
+ routes.push(...extractFromSourceFile(sourceFile, project));
848
+ }
849
+ } finally {
850
+ _ctx = prevCtx;
765
851
  }
766
852
  return routes;
767
853
  }
@@ -969,10 +1055,11 @@ function resolveModuleSpecifier(moduleSpecifier, sourceFile, project) {
969
1055
  resolve2(dir, moduleSpecifier, "index.ts")
970
1056
  ];
971
1057
  }
972
- const baseUrl = _projectRoot;
973
- dbg("resolveModuleSpecifier", moduleSpecifier, "paths:", JSON.stringify(_tsconfigPaths), "baseUrl:", baseUrl);
974
- if (_tsconfigPaths) {
975
- for (const [pattern, mappings] of Object.entries(_tsconfigPaths)) {
1058
+ const baseUrl = _projectRoot();
1059
+ const tsconfigPaths = _tsconfigPaths();
1060
+ dbg("resolveModuleSpecifier", moduleSpecifier, "paths:", JSON.stringify(tsconfigPaths), "baseUrl:", baseUrl);
1061
+ if (tsconfigPaths) {
1062
+ for (const [pattern, mappings] of Object.entries(tsconfigPaths)) {
976
1063
  const prefix = pattern.replace("*", "");
977
1064
  if (moduleSpecifier.startsWith(prefix)) {
978
1065
  const rest = moduleSpecifier.slice(prefix.length);
@@ -1458,18 +1545,16 @@ function extractFromSourceFile(sourceFile, project) {
1458
1545
  methodName,
1459
1546
  filePath: sourceFile.getFilePath()
1460
1547
  },
1461
- ...dtoContract ? {
1462
- contract: {
1463
- contractSource: {
1464
- query: dtoContract.query,
1465
- body: dtoContract.body,
1466
- response: dtoContract.response,
1467
- queryRef: dtoContract.queryRef,
1468
- bodyRef: dtoContract.bodyRef,
1469
- responseRef: dtoContract.responseRef
1470
- }
1548
+ contract: {
1549
+ contractSource: {
1550
+ query: dtoContract?.query ?? null,
1551
+ body: dtoContract?.body ?? null,
1552
+ response: dtoContract?.response ?? "unknown",
1553
+ queryRef: dtoContract?.queryRef,
1554
+ bodyRef: dtoContract?.bodyRef,
1555
+ responseRef: dtoContract?.responseRef
1471
1556
  }
1472
- } : {}
1557
+ }
1473
1558
  });
1474
1559
  }
1475
1560
  }
@@ -1479,7 +1564,8 @@ function extractFromSourceFile(sourceFile, project) {
1479
1564
  __name(extractFromSourceFile, "extractFromSourceFile");
1480
1565
 
1481
1566
  // src/watch/lock-file.ts
1482
- import { mkdir as mkdir6, readFile as readFile2, unlink, writeFile as writeFile6 } from "fs/promises";
1567
+ import { open } from "fs/promises";
1568
+ import { mkdir as mkdir6, readFile as readFile2, unlink } from "fs/promises";
1483
1569
  import { join as join8 } from "path";
1484
1570
  var LOCK_FILE = ".watcher.lock";
1485
1571
  function isProcessAlive(pid) {
@@ -1496,20 +1582,29 @@ async function acquireLock(outDir) {
1496
1582
  recursive: true
1497
1583
  });
1498
1584
  const lockPath = join8(outDir, LOCK_FILE);
1499
- try {
1500
- const raw = await readFile2(lockPath, "utf8");
1501
- const existing = JSON.parse(raw);
1502
- if (isProcessAlive(existing.pid)) {
1503
- return null;
1504
- }
1505
- } catch {
1506
- }
1507
1585
  const lockData = {
1508
1586
  pid: process.pid,
1509
1587
  startedAt: (/* @__PURE__ */ new Date()).toISOString()
1510
1588
  };
1511
- await writeFile6(lockPath, `${JSON.stringify(lockData, null, 2)}
1589
+ try {
1590
+ const fd = await open(lockPath, "wx");
1591
+ await fd.writeFile(`${JSON.stringify(lockData, null, 2)}
1512
1592
  `, "utf8");
1593
+ await fd.close();
1594
+ } catch (err) {
1595
+ if (err.code === "EEXIST") {
1596
+ try {
1597
+ const raw = await readFile2(lockPath, "utf8");
1598
+ const existing = JSON.parse(raw);
1599
+ if (isProcessAlive(existing.pid)) return null;
1600
+ await unlink(lockPath);
1601
+ return acquireLock(outDir);
1602
+ } catch {
1603
+ return null;
1604
+ }
1605
+ }
1606
+ return null;
1607
+ }
1513
1608
  return {
1514
1609
  release: /* @__PURE__ */ __name(async () => {
1515
1610
  try {
@@ -1573,7 +1668,8 @@ async function watch(config, onChange) {
1573
1668
  pagesDebounceTimer = void 0;
1574
1669
  try {
1575
1670
  await generate(config);
1576
- } catch {
1671
+ } catch (err) {
1672
+ console.error("[nestjs-inertia-codegen] Pages generation failed:", err instanceof Error ? err.message : err);
1577
1673
  }
1578
1674
  onChange?.();
1579
1675
  }, PAGES_DEBOUNCE_MS);
@@ -1611,7 +1707,8 @@ async function watch(config, onChange) {
1611
1707
  if (hasContracts) {
1612
1708
  await emitApi(routes, config.codegen.outDir);
1613
1709
  }
1614
- } catch {
1710
+ } catch (err) {
1711
+ console.error("[nestjs-inertia-codegen] Contracts generation failed:", err instanceof Error ? err.message : err);
1615
1712
  }
1616
1713
  onChange?.();
1617
1714
  }, config.contracts.debounceMs);
@@ -1639,7 +1736,7 @@ async function watch(config, onChange) {
1639
1736
  __name(watch, "watch");
1640
1737
 
1641
1738
  // src/index.ts
1642
- var VERSION = "1.3.0";
1739
+ var VERSION = "1.4.0";
1643
1740
 
1644
1741
  // src/cli/codegen.ts
1645
1742
  async function runCodegen(opts = {}) {
@@ -1669,11 +1766,163 @@ async function runCodegen(opts = {}) {
1669
1766
  }
1670
1767
  __name(runCodegen, "runCodegen");
1671
1768
 
1672
- // src/cli/init.ts
1673
- import { execSync } from "child_process";
1674
- import { readFileSync as readFileSync2, writeFileSync } from "fs";
1675
- import { access as access2, mkdir as mkdir7, readFile as readFile4, writeFile as writeFile7 } from "fs/promises";
1769
+ // src/cli/doctor.ts
1770
+ import { existsSync, readFileSync as readFileSync2 } from "fs";
1676
1771
  import { join as join10 } from "path";
1772
+ function checkFileExists(cwd, file) {
1773
+ return existsSync(join10(cwd, file));
1774
+ }
1775
+ __name(checkFileExists, "checkFileExists");
1776
+ function readJson(path) {
1777
+ try {
1778
+ const raw = readFileSync2(path, "utf8").replace(/\/\/.*$/gm, "");
1779
+ return JSON.parse(raw);
1780
+ } catch {
1781
+ return null;
1782
+ }
1783
+ }
1784
+ __name(readJson, "readJson");
1785
+ function getPackageVersion(cwd, pkg) {
1786
+ try {
1787
+ const pkgJson = readJson(join10(cwd, "node_modules", pkg, "package.json"));
1788
+ return pkgJson?.version ?? null;
1789
+ } catch {
1790
+ return null;
1791
+ }
1792
+ }
1793
+ __name(getPackageVersion, "getPackageVersion");
1794
+ async function runDoctor(opts) {
1795
+ const { cwd } = opts;
1796
+ const checks = [];
1797
+ checks.push({
1798
+ name: "nestjs-inertia.config.ts exists",
1799
+ pass: checkFileExists(cwd, "nestjs-inertia.config.ts"),
1800
+ fix: "Run: pnpm exec nestjs-inertia init"
1801
+ });
1802
+ const hasApi = checkFileExists(cwd, ".nestjs-inertia/api.ts");
1803
+ const hasRoutes = checkFileExists(cwd, ".nestjs-inertia/routes.ts");
1804
+ const hasPages = checkFileExists(cwd, ".nestjs-inertia/pages.d.ts");
1805
+ checks.push({
1806
+ name: ".nestjs-inertia/ codegen output exists",
1807
+ pass: hasApi && hasRoutes && hasPages,
1808
+ fix: "Run: pnpm exec nestjs-inertia codegen"
1809
+ });
1810
+ const tsconfig = readJson(join10(cwd, "tsconfig.json"));
1811
+ const paths = tsconfig?.compilerOptions?.paths;
1812
+ checks.push({
1813
+ name: "tsconfig.json has @/* path alias",
1814
+ pass: !!paths?.["@/*"],
1815
+ fix: 'Add to tsconfig.json compilerOptions.paths: { "@/*": ["./src/*"] }'
1816
+ });
1817
+ const inertiaTsconfig = readJson(join10(cwd, "tsconfig.inertia.json"));
1818
+ if (inertiaTsconfig) {
1819
+ const inertiaPaths = inertiaTsconfig.compilerOptions?.paths;
1820
+ checks.push({
1821
+ name: "tsconfig.inertia.json has ~/* and ~codegen/* aliases",
1822
+ pass: !!inertiaPaths?.["~/*"] && !!inertiaPaths?.["~codegen/*"],
1823
+ fix: 'Add paths: { "~/*": ["inertia/*"], "~codegen/*": [".nestjs-inertia/*"] }'
1824
+ });
1825
+ }
1826
+ if (checkFileExists(cwd, "vite.config.ts")) {
1827
+ const viteContent = readFileSync2(join10(cwd, "vite.config.ts"), "utf8");
1828
+ checks.push({
1829
+ name: "vite.config.ts has resolve.alias",
1830
+ pass: viteContent.includes("resolve") && viteContent.includes("alias"),
1831
+ fix: "Add resolve.alias with @\u2192src, ~\u2192inertia, ~codegen\u2192.nestjs-inertia"
1832
+ });
1833
+ checks.push({
1834
+ name: "vite.config.ts references nestjs-inertia",
1835
+ pass: viteContent.includes("nestInertia") || viteContent.includes("nestjs-inertia") || viteContent.includes("setupInertiaVite"),
1836
+ fix: "Add: import nestInertia from '@dudousxd/nestjs-inertia-vite/plugin'"
1837
+ });
1838
+ }
1839
+ const libPackages = [
1840
+ "@dudousxd/nestjs-inertia",
1841
+ "@dudousxd/nestjs-inertia-codegen",
1842
+ "@dudousxd/nestjs-inertia-client",
1843
+ "@dudousxd/nestjs-inertia-vite",
1844
+ "@dudousxd/nestjs-inertia-testing"
1845
+ ];
1846
+ const versions = libPackages.map((pkg) => ({
1847
+ pkg,
1848
+ version: getPackageVersion(cwd, pkg)
1849
+ }));
1850
+ const installed = versions.filter((v) => v.version !== null);
1851
+ const uniqueVersions = new Set(installed.map((v) => v.version));
1852
+ const requiredPkgs = [
1853
+ "@dudousxd/nestjs-inertia",
1854
+ "@dudousxd/nestjs-inertia-codegen",
1855
+ "@dudousxd/nestjs-inertia-client"
1856
+ ];
1857
+ const missingRequired = requiredPkgs.filter((pkg) => !getPackageVersion(cwd, pkg));
1858
+ checks.push({
1859
+ name: "Core packages installed (core + codegen + client)",
1860
+ pass: missingRequired.length === 0,
1861
+ fix: missingRequired.length > 0 ? `Missing: ${missingRequired.join(", ")}` : void 0
1862
+ });
1863
+ if (installed.length > 1) {
1864
+ checks.push({
1865
+ name: "All packages on same version",
1866
+ pass: uniqueVersions.size === 1,
1867
+ fix: `Versions: ${installed.map((v) => `${v.pkg.replace("@dudousxd/", "")}@${v.version}`).join(", ")}`
1868
+ });
1869
+ }
1870
+ const inertiaReact = getPackageVersion(cwd, "@inertiajs/react");
1871
+ const inertiaVue = getPackageVersion(cwd, "@inertiajs/vue3");
1872
+ const inertiaSvelte = getPackageVersion(cwd, "@inertiajs/svelte");
1873
+ const inertiaVersion = inertiaReact ?? inertiaVue ?? inertiaSvelte;
1874
+ const inertiaFramework = inertiaReact ? "react" : inertiaVue ? "vue" : inertiaSvelte ? "svelte" : null;
1875
+ if (inertiaVersion) {
1876
+ const majorVersion = Number.parseInt(inertiaVersion.split(".")[0] ?? "0", 10);
1877
+ checks.push({
1878
+ name: `@inertiajs/${inertiaFramework} is v3+`,
1879
+ pass: majorVersion >= 3,
1880
+ fix: `Current: v${inertiaVersion}. Run: pnpm add @inertiajs/${inertiaFramework}@^3.0.0`
1881
+ });
1882
+ }
1883
+ if (checkFileExists(cwd, ".gitignore")) {
1884
+ const gitignore = readFileSync2(join10(cwd, ".gitignore"), "utf8");
1885
+ checks.push({
1886
+ name: ".gitignore includes .nestjs-inertia/",
1887
+ pass: gitignore.includes(".nestjs-inertia"),
1888
+ fix: "Add .nestjs-inertia/ to .gitignore"
1889
+ });
1890
+ }
1891
+ const pkgJson = readJson(join10(cwd, "package.json"));
1892
+ const scripts = pkgJson?.scripts ?? {};
1893
+ checks.push({
1894
+ name: "package.json has build:client script",
1895
+ pass: !!scripts["build:client"],
1896
+ fix: 'Add: "build:client": "vite build"'
1897
+ });
1898
+ console.log("");
1899
+ console.log("\x1B[1mnestjs-inertia doctor\x1B[0m");
1900
+ console.log("");
1901
+ let hasFailures = false;
1902
+ for (const check of checks) {
1903
+ const icon = check.pass ? "\x1B[32m\u2713\x1B[0m" : "\x1B[31m\u2717\x1B[0m";
1904
+ console.log(` ${icon} ${check.name}`);
1905
+ if (!check.pass && check.fix) {
1906
+ console.log(` \x1B[2m${check.fix}\x1B[0m`);
1907
+ hasFailures = true;
1908
+ }
1909
+ }
1910
+ console.log("");
1911
+ if (hasFailures) {
1912
+ console.log(`\x1B[33m${checks.filter((c) => !c.pass).length} issue(s) found\x1B[0m`);
1913
+ } else {
1914
+ console.log("\x1B[32mAll checks passed!\x1B[0m");
1915
+ }
1916
+ console.log("");
1917
+ return hasFailures ? 1 : 0;
1918
+ }
1919
+ __name(runDoctor, "runDoctor");
1920
+
1921
+ // src/cli/init.ts
1922
+ import { execFileSync } from "child_process";
1923
+ import { readFileSync as readFileSync3, writeFileSync } from "fs";
1924
+ import { access as access2, mkdir as mkdir7, readFile as readFile4, writeFile as writeFile6 } from "fs/promises";
1925
+ import { join as join11 } from "path";
1677
1926
  import { createInterface } from "readline";
1678
1927
  var GITIGNORE_ENTRY = ".nestjs-inertia/";
1679
1928
  var green = /* @__PURE__ */ __name((s) => `\x1B[32m${s}\x1B[0m`, "green");
@@ -1704,7 +1953,7 @@ ${bold(title)}`);
1704
1953
  __name(logSection, "logSection");
1705
1954
  async function readPackageJson(cwd) {
1706
1955
  try {
1707
- const raw = await readFile4(join10(cwd, "package.json"), "utf8");
1956
+ const raw = await readFile4(join11(cwd, "package.json"), "utf8");
1708
1957
  return JSON.parse(raw);
1709
1958
  } catch {
1710
1959
  return {};
@@ -1742,7 +1991,7 @@ __name(detectTemplateEngine, "detectTemplateEngine");
1742
1991
  async function detectPackageManager(cwd) {
1743
1992
  async function exists(file) {
1744
1993
  try {
1745
- await access2(join10(cwd, file));
1994
+ await access2(join11(cwd, file));
1746
1995
  return true;
1747
1996
  } catch {
1748
1997
  return false;
@@ -1791,12 +2040,12 @@ async function writeIfNotExists(filePath, content, label) {
1791
2040
  recursive: true
1792
2041
  });
1793
2042
  }
1794
- await writeFile7(filePath, content, "utf8");
2043
+ await writeFile6(filePath, content, "utf8");
1795
2044
  logCreated(label);
1796
2045
  }
1797
2046
  __name(writeIfNotExists, "writeIfNotExists");
1798
2047
  async function handleViteConfig(cwd, framework) {
1799
- const filePath = join10(cwd, "vite.config.ts");
2048
+ const filePath = join11(cwd, "vite.config.ts");
1800
2049
  if (await fileExists2(filePath)) {
1801
2050
  const existing = await readFile4(filePath, "utf8");
1802
2051
  const hasPlugin = existing.includes("nestInertia") || existing.includes("nestjs-inertia-vite/plugin");
@@ -1816,7 +2065,7 @@ async function handleViteConfig(cwd, framework) {
1816
2065
  recursive: true
1817
2066
  });
1818
2067
  }
1819
- await writeFile7(filePath, viteConfigTemplate(framework), "utf8");
2068
+ await writeFile6(filePath, viteConfigTemplate(framework), "utf8");
1820
2069
  logCreated("vite.config.ts");
1821
2070
  }
1822
2071
  __name(handleViteConfig, "handleViteConfig");
@@ -1833,27 +2082,33 @@ async function patchGitignore(gitignorePath) {
1833
2082
  ` : `${existing}
1834
2083
  ${GITIGNORE_ENTRY}
1835
2084
  `;
1836
- await writeFile7(gitignorePath, newContent, "utf8");
2085
+ await writeFile6(gitignorePath, newContent, "utf8");
1837
2086
  logPatched(".gitignore", "added .nestjs-inertia/");
1838
2087
  }
1839
2088
  __name(patchGitignore, "patchGitignore");
1840
2089
  function installDeps(pkgManager, deps, dev) {
1841
2090
  if (deps.length === 0) return;
1842
- const flag = dev ? pkgManager === "npm" ? "--save-dev" : "-D" : "";
1843
- const cmd = pkgManager === "npm" ? `npm install ${flag} ${deps.join(" ")}` : pkgManager === "yarn" ? `yarn add ${flag} ${deps.join(" ")}` : `pnpm add ${flag} ${deps.join(" ")}`;
2091
+ const args = [];
2092
+ if (pkgManager === "npm") {
2093
+ args.push("install");
2094
+ if (dev) args.push("--save-dev");
2095
+ } else {
2096
+ args.push("add");
2097
+ if (dev) args.push("-D");
2098
+ }
2099
+ args.push(...deps);
1844
2100
  logPatched(deps.join(", "), "installed");
1845
2101
  try {
1846
- execSync(cmd, {
2102
+ execFileSync(pkgManager, args, {
1847
2103
  stdio: "inherit"
1848
2104
  });
1849
2105
  } catch {
1850
- logWarning(`Failed to install deps. Run manually:
1851
- ${cmd}`);
2106
+ logWarning(`Failed to install: ${deps.join(", ")}`);
1852
2107
  }
1853
2108
  }
1854
2109
  __name(installDeps, "installDeps");
1855
2110
  async function patchPackageJsonScripts(cwd, scripts) {
1856
- const pkgPath = join10(cwd, "package.json");
2111
+ const pkgPath = join11(cwd, "package.json");
1857
2112
  let pkg = {};
1858
2113
  try {
1859
2114
  pkg = JSON.parse(await readFile4(pkgPath, "utf8"));
@@ -1875,7 +2130,7 @@ async function patchPackageJsonScripts(cwd, scripts) {
1875
2130
  return;
1876
2131
  }
1877
2132
  pkg.scripts = existing;
1878
- await writeFile7(pkgPath, `${JSON.stringify(pkg, null, 2)}
2133
+ await writeFile6(pkgPath, `${JSON.stringify(pkg, null, 2)}
1879
2134
  `, "utf8");
1880
2135
  }
1881
2136
  __name(patchPackageJsonScripts, "patchPackageJsonScripts");
@@ -1895,7 +2150,7 @@ __name(findAfterLastImport, "findAfterLastImport");
1895
2150
  function patchAppModule(filePath, rootView) {
1896
2151
  let content;
1897
2152
  try {
1898
- content = readFileSync2(filePath, "utf8");
2153
+ content = readFileSync3(filePath, "utf8");
1899
2154
  } catch {
1900
2155
  return "skipped";
1901
2156
  }
@@ -1940,7 +2195,7 @@ __name(patchAppModule, "patchAppModule");
1940
2195
  function patchMainTs(filePath) {
1941
2196
  let content;
1942
2197
  try {
1943
- content = readFileSync2(filePath, "utf8");
2198
+ content = readFileSync3(filePath, "utf8");
1944
2199
  } catch {
1945
2200
  return "skipped";
1946
2201
  }
@@ -2145,16 +2400,16 @@ ${bold("nestjs-inertia init")}`);
2145
2400
  const entryExt = framework === "react" ? "tsx" : "ts";
2146
2401
  const pageExt = framework === "react" ? "tsx" : framework === "vue" ? "vue" : "svelte";
2147
2402
  logSection("Scaffold files");
2148
- await writeIfNotExists(join10(cwd, "nestjs-inertia.config.ts"), configTemplate(framework), "nestjs-inertia.config.ts");
2149
- await writeIfNotExists(join10(cwd, "nestjs-inertia.d.ts"), DTS_TEMPLATE, "nestjs-inertia.d.ts");
2150
- await writeIfNotExists(join10(cwd, "inertia", shellFileName), htmlShellTemplate(framework, engine), `inertia/${shellFileName}`);
2403
+ await writeIfNotExists(join11(cwd, "nestjs-inertia.config.ts"), configTemplate(framework), "nestjs-inertia.config.ts");
2404
+ await writeIfNotExists(join11(cwd, "nestjs-inertia.d.ts"), DTS_TEMPLATE, "nestjs-inertia.d.ts");
2405
+ await writeIfNotExists(join11(cwd, "inertia", shellFileName), htmlShellTemplate(framework, engine), `inertia/${shellFileName}`);
2151
2406
  await handleViteConfig(cwd, framework);
2152
- await writeIfNotExists(join10(cwd, "inertia", `app.${entryExt}`), entryPointTemplate(framework), `inertia/app.${entryExt}`);
2153
- await writeIfNotExists(join10(cwd, "inertia", "pages", `Home.${pageExt}`), samplePageTemplate(framework), `inertia/pages/Home.${pageExt}`);
2154
- await writeIfNotExists(join10(cwd, "src", "home.controller.ts"), SAMPLE_CONTROLLER, "src/home.controller.ts");
2407
+ await writeIfNotExists(join11(cwd, "inertia", `app.${entryExt}`), entryPointTemplate(framework), `inertia/app.${entryExt}`);
2408
+ await writeIfNotExists(join11(cwd, "inertia", "pages", `Home.${pageExt}`), samplePageTemplate(framework), `inertia/pages/Home.${pageExt}`);
2409
+ await writeIfNotExists(join11(cwd, "src", "home.controller.ts"), SAMPLE_CONTROLLER, "src/home.controller.ts");
2155
2410
  logSection("Patch existing files");
2156
2411
  const rootView = engine === "html" ? "inertia/index.html" : `inertia/index.${engine === "handlebars" ? "hbs" : engine}`;
2157
- const appModulePath = join10(cwd, "src", "app.module.ts");
2412
+ const appModulePath = join11(cwd, "src", "app.module.ts");
2158
2413
  const appModuleResult = patchAppModule(appModulePath, rootView);
2159
2414
  if (appModuleResult === "patched") {
2160
2415
  logPatched("src/app.module.ts", "added InertiaModule.forRoot");
@@ -2164,7 +2419,7 @@ ${bold("nestjs-inertia init")}`);
2164
2419
  } else {
2165
2420
  logWarning("src/app.module.ts not found \u2014 add InertiaModule.forRoot() manually");
2166
2421
  }
2167
- const mainTsPath = join10(cwd, "src", "main.ts");
2422
+ const mainTsPath = join11(cwd, "src", "main.ts");
2168
2423
  const mainTsResult = patchMainTs(mainTsPath);
2169
2424
  if (mainTsResult === "patched") {
2170
2425
  logPatched("src/main.ts", "added setupInertiaVite after NestFactory.create");
@@ -2173,7 +2428,7 @@ ${bold("nestjs-inertia init")}`);
2173
2428
  } else {
2174
2429
  logWarning("src/main.ts not found \u2014 add setupInertiaVite() manually");
2175
2430
  }
2176
- await patchGitignore(join10(cwd, ".gitignore"));
2431
+ await patchGitignore(join11(cwd, ".gitignore"));
2177
2432
  await patchPackageJsonScripts(cwd, {
2178
2433
  "build:client": "vite build",
2179
2434
  "build:ssr": "VITE_SSR=1 vite build --ssr"
@@ -2250,6 +2505,12 @@ async function run(argv) {
2250
2505
  cwd: process.cwd()
2251
2506
  });
2252
2507
  });
2508
+ cli.command("doctor", "Diagnose your nestjs-inertia setup").action(async () => {
2509
+ const code = await runDoctor({
2510
+ cwd: process.cwd()
2511
+ });
2512
+ process.exitCode = code;
2513
+ });
2253
2514
  cli.help();
2254
2515
  cli.version(VERSION);
2255
2516
  try {