@dudousxd/nestjs-inertia-codegen 1.0.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.
@@ -0,0 +1,1950 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
+
31
+ // src/cli/main.ts
32
+ var main_exports = {};
33
+ __export(main_exports, {
34
+ run: () => run
35
+ });
36
+ module.exports = __toCommonJS(main_exports);
37
+ var import_cac = require("cac");
38
+
39
+ // src/config/load-config.ts
40
+ var import_promises = require("fs/promises");
41
+ var import_node_path = require("path");
42
+ var import_node_url = require("url");
43
+
44
+ // src/exceptions.ts
45
+ var ConfigError = class extends Error {
46
+ static {
47
+ __name(this, "ConfigError");
48
+ }
49
+ constructor(message, options) {
50
+ super(message, options);
51
+ this.name = "ConfigError";
52
+ }
53
+ };
54
+
55
+ // src/config/load-config.ts
56
+ var CONFIG_FILE = "nestjs-inertia.config.ts";
57
+ async function fileExists(filePath) {
58
+ try {
59
+ await (0, import_promises.access)(filePath);
60
+ return true;
61
+ } catch {
62
+ return false;
63
+ }
64
+ }
65
+ __name(fileExists, "fileExists");
66
+ async function importTs(filePath) {
67
+ let tsImport;
68
+ try {
69
+ const tsxEsm = await import("tsx/esm/api");
70
+ tsImport = tsxEsm.tsImport;
71
+ } catch {
72
+ throw new ConfigError("Failed to load config: `tsx` is required for loading TypeScript config files. Install it as a dev dependency: pnpm add -D tsx");
73
+ }
74
+ const parentURL = (0, import_node_url.pathToFileURL)(`${filePath}__parent__`).href;
75
+ const fileUrl = (0, import_node_url.pathToFileURL)(filePath).href;
76
+ return tsImport(fileUrl, {
77
+ parentURL
78
+ });
79
+ }
80
+ __name(importTs, "importTs");
81
+ function resolveAbsolute(cwd, p) {
82
+ if ((0, import_node_path.isAbsolute)(p)) return p;
83
+ return (0, import_node_path.resolve)(cwd, p);
84
+ }
85
+ __name(resolveAbsolute, "resolveAbsolute");
86
+ function assertInsideCwd(cwd, resolvedPath, fieldName) {
87
+ const rel = (0, import_node_path.relative)(cwd, resolvedPath);
88
+ if (rel.startsWith(`..${import_node_path.sep}`) || rel === ".." || (0, import_node_path.isAbsolute)(rel)) {
89
+ throw new ConfigError(`\`${fieldName}\` must be inside the project cwd.
90
+ Resolved to: ${resolvedPath}
91
+ Project cwd: ${cwd}
92
+ If this is intentional, move the file inside your project directory.`);
93
+ }
94
+ }
95
+ __name(assertInsideCwd, "assertInsideCwd");
96
+ function applyDefaults(userConfig, cwd) {
97
+ const outDir = userConfig.codegen?.outDir ? resolveAbsolute(cwd, userConfig.codegen.outDir) : (0, import_node_path.join)(cwd, ".nestjs-inertia");
98
+ const resolvedCwd = userConfig.codegen?.cwd ? resolveAbsolute(cwd, userConfig.codegen.cwd) : cwd;
99
+ let app = null;
100
+ if (userConfig.app) {
101
+ const resolvedEntry = resolveAbsolute(cwd, userConfig.app.moduleEntry);
102
+ assertInsideCwd(cwd, resolvedEntry, "app.moduleEntry");
103
+ let resolvedTsconfig = null;
104
+ if (userConfig.app.tsconfig) {
105
+ resolvedTsconfig = resolveAbsolute(cwd, userConfig.app.tsconfig);
106
+ assertInsideCwd(cwd, resolvedTsconfig, "app.tsconfig");
107
+ }
108
+ app = {
109
+ moduleEntry: resolvedEntry,
110
+ tsconfig: resolvedTsconfig
111
+ };
112
+ }
113
+ return {
114
+ pages: {
115
+ glob: userConfig.pages.glob,
116
+ propsExport: userConfig.pages.propsExport ?? "ComponentProps",
117
+ componentNameStrategy: userConfig.pages.componentNameStrategy ?? "relative-no-ext"
118
+ },
119
+ contracts: {
120
+ glob: userConfig.contracts?.glob ?? "src/**/*.controller.ts",
121
+ debounceMs: userConfig.contracts?.debounceMs ?? 500
122
+ },
123
+ scopes: userConfig.scopes ?? {},
124
+ codegen: {
125
+ outDir,
126
+ cwd: resolvedCwd
127
+ },
128
+ app
129
+ };
130
+ }
131
+ __name(applyDefaults, "applyDefaults");
132
+ async function loadConfig(cwd) {
133
+ const resolvedCwd = cwd ?? process.cwd();
134
+ const configPath = (0, import_node_path.join)(resolvedCwd, CONFIG_FILE);
135
+ if (!await fileExists(configPath)) {
136
+ throw new ConfigError(`Config file not found: ${configPath}
137
+ Run \`nestjs-inertia init\` to create a starter config.`);
138
+ }
139
+ let mod;
140
+ try {
141
+ mod = await importTs(configPath);
142
+ } catch (err) {
143
+ if (err instanceof ConfigError) throw err;
144
+ throw new ConfigError(`Failed to load config from ${configPath}`, {
145
+ cause: err
146
+ });
147
+ }
148
+ const modNs = mod.default;
149
+ const userConfig = modNs != null && typeof modNs === "object" && "default" in modNs ? modNs.default : modNs;
150
+ if (!userConfig || typeof userConfig !== "object") {
151
+ throw new ConfigError(`Config file must have a default export. Did you forget \`export default defineConfig({...})\`? (${configPath})`);
152
+ }
153
+ if (!userConfig.pages || typeof userConfig.pages.glob !== "string") {
154
+ throw new ConfigError(`Config validation failed: \`pages.glob\` is required (${configPath})`);
155
+ }
156
+ return applyDefaults(userConfig, resolvedCwd);
157
+ }
158
+ __name(loadConfig, "loadConfig");
159
+
160
+ // src/discovery/pages.ts
161
+ var import_promises2 = require("fs/promises");
162
+ var import_node_path2 = require("path");
163
+ var import_fast_glob = __toESM(require("fast-glob"), 1);
164
+ async function discoverPages(opts) {
165
+ const files = await (0, import_fast_glob.default)(opts.glob, {
166
+ cwd: opts.cwd,
167
+ absolute: true
168
+ });
169
+ files.sort();
170
+ const out = [];
171
+ for (const file of files) {
172
+ const rel = (0, import_node_path2.relative)(opts.cwd, file);
173
+ const name = computeName(rel, opts.componentNameStrategy);
174
+ const source = await (0, import_promises2.readFile)(file, "utf8");
175
+ const propsSource = extractPropsSource(source, opts.propsExport);
176
+ out.push({
177
+ name,
178
+ absolutePath: file,
179
+ relativePath: rel,
180
+ propsSource
181
+ });
182
+ }
183
+ return out;
184
+ }
185
+ __name(discoverPages, "discoverPages");
186
+ function computeName(rel, strat) {
187
+ if (typeof strat === "function") return strat(rel);
188
+ const noExt = rel.replace(/\.(tsx?|vue|svelte)$/, "");
189
+ if (strat === "kebab") return noExt.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
190
+ return noExt;
191
+ }
192
+ __name(computeName, "computeName");
193
+ function extractPropsSource(source, exportName) {
194
+ const re = new RegExp(`export\\s+type\\s+${exportName}\\s*=\\s*`, "m");
195
+ const m = source.match(re);
196
+ if (!m) return null;
197
+ const start = m.index + m[0].length;
198
+ let i = start;
199
+ let depth = 0;
200
+ let started = false;
201
+ while (i < source.length) {
202
+ const c = source[i];
203
+ if (c === "{") {
204
+ depth++;
205
+ started = true;
206
+ } else if (c === "}") {
207
+ depth--;
208
+ if (started && depth === 0) {
209
+ return source.slice(start, i + 1);
210
+ }
211
+ } else if (c === ";" && !started) return source.slice(start, i);
212
+ i++;
213
+ }
214
+ return source.slice(start);
215
+ }
216
+ __name(extractPropsSource, "extractPropsSource");
217
+
218
+ // src/emit/emit-api.ts
219
+ var import_promises3 = require("fs/promises");
220
+ var import_node_path3 = require("path");
221
+ async function emitApi(routes, outDir) {
222
+ await (0, import_promises3.mkdir)(outDir, {
223
+ recursive: true
224
+ });
225
+ const content = buildApiFile(routes);
226
+ await (0, import_promises3.writeFile)((0, import_node_path3.join)(outDir, "api.ts"), content, "utf8");
227
+ }
228
+ __name(emitApi, "emitApi");
229
+ function splitName(name) {
230
+ return name.split(".");
231
+ }
232
+ __name(splitName, "splitName");
233
+ function toObjectKey(segment) {
234
+ if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(segment)) {
235
+ return segment;
236
+ }
237
+ return JSON.stringify(segment);
238
+ }
239
+ __name(toObjectKey, "toObjectKey");
240
+ function toCamelCase(s) {
241
+ return s.split(/[^a-zA-Z0-9]+/).filter(Boolean).map((word, i) => i === 0 ? word.charAt(0).toLowerCase() + word.slice(1) : word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
242
+ }
243
+ __name(toCamelCase, "toCamelCase");
244
+ function validateNameSegment(seg, fullName) {
245
+ if (!/^[a-z][a-zA-Z0-9]*$/.test(seg)) {
246
+ const suggested = toCamelCase(seg);
247
+ throw new Error(`Contract name "${fullName}" has invalid segment "${seg}". Use camelCase identifiers only (lowercase letter then alphanumeric). Suggested: "${suggested}"`);
248
+ }
249
+ }
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
+ function insertIntoTree(tree, segments, leaf, fullName) {
262
+ const head = segments[0];
263
+ const rest = segments.slice(1);
264
+ if (rest.length === 0) {
265
+ const existing = tree.get(head);
266
+ if (existing !== void 0 && existing.kind === "branch") {
267
+ throw new Error(`Contract name conflict: "${fullName}" cannot have both a direct entry and child entries`);
268
+ }
269
+ tree.set(head, leaf);
270
+ } else {
271
+ const existing = tree.get(head);
272
+ if (existing !== void 0 && existing.kind === "leaf") {
273
+ const prefixName = fullName.split(".").slice(0, segments.length - rest.length).join(".");
274
+ throw new Error(`Contract name conflict: "${prefixName}" cannot have both a direct entry and child entries`);
275
+ }
276
+ let branch;
277
+ if (existing === void 0) {
278
+ branch = {
279
+ kind: "branch",
280
+ children: /* @__PURE__ */ new Map()
281
+ };
282
+ tree.set(head, branch);
283
+ } else {
284
+ branch = existing;
285
+ }
286
+ insertIntoTree(branch.children, rest, leaf, fullName);
287
+ }
288
+ }
289
+ __name(insertIntoTree, "insertIntoTree");
290
+ function emitRouterTypeBlock(tree, indent) {
291
+ const pad = " ".repeat(indent);
292
+ const lines = [];
293
+ for (const [key, node] of tree) {
294
+ const objKey = toObjectKey(key);
295
+ if (node.kind === "leaf") {
296
+ const c = node;
297
+ const method = c.method.toUpperCase();
298
+ const query = c.contractSource.query ?? "never";
299
+ const body = method === "GET" ? "never" : c.contractSource.body ?? "never";
300
+ const response = c.contractSource.response;
301
+ const safeMethod = JSON.stringify(method);
302
+ const safeUrl = JSON.stringify(c.path);
303
+ lines.push(`${pad}${objKey}: { method: ${safeMethod}; url: ${safeUrl}; query: ${query}; body: ${body}; response: ${response} };`);
304
+ } else {
305
+ lines.push(`${pad}${objKey}: {`);
306
+ lines.push(...emitRouterTypeBlock(node.children, indent + 2));
307
+ lines.push(`${pad}};`);
308
+ }
309
+ }
310
+ return lines;
311
+ }
312
+ __name(emitRouterTypeBlock, "emitRouterTypeBlock");
313
+ function emitApiObjectBlock(tree, indent) {
314
+ const pad = " ".repeat(indent);
315
+ const lines = [];
316
+ for (const [key, node] of tree) {
317
+ const objKey = toObjectKey(key);
318
+ if (node.kind === "leaf") {
319
+ const c = node;
320
+ const method = c.method.toUpperCase();
321
+ const flatName = JSON.stringify(c.name);
322
+ const safePath = JSON.stringify(c.path);
323
+ const fetcherMethod = method.toLowerCase();
324
+ if (method === "GET") {
325
+ const typeAccess = buildRouterTypeAccess(c.name);
326
+ lines.push(`${pad}${objKey}: {`);
327
+ lines.push(`${pad} queryOptions: (query?: ${typeAccess}['query']) =>`);
328
+ lines.push(`${pad} queryOptions({`);
329
+ lines.push(`${pad} queryKey: [${flatName}, query],`);
330
+ lines.push(`${pad} queryFn: () => fetcher.get<${typeAccess}['response']>(route(${flatName} as never) || ${safePath}, { query }),`);
331
+ lines.push(`${pad} }),`);
332
+ lines.push(`${pad}},`);
333
+ } else {
334
+ const typeAccess = buildRouterTypeAccess(c.name);
335
+ lines.push(`${pad}${objKey}: {`);
336
+ lines.push(`${pad} mutationOptions: () => ({`);
337
+ lines.push(`${pad} mutationFn: (body: ${typeAccess}['body']) => fetcher.${fetcherMethod}<${typeAccess}['response']>(route(${flatName} as never) || ${safePath}, { body }),`);
338
+ lines.push(`${pad} }),`);
339
+ lines.push(`${pad}},`);
340
+ }
341
+ } else {
342
+ lines.push(`${pad}${objKey}: {`);
343
+ lines.push(...emitApiObjectBlock(node.children, indent + 2));
344
+ lines.push(`${pad}},`);
345
+ }
346
+ }
347
+ return lines;
348
+ }
349
+ __name(emitApiObjectBlock, "emitApiObjectBlock");
350
+ function buildRouterTypeAccess(name) {
351
+ const segments = splitName(name);
352
+ return `ApiRouter${segments.map((s) => `[${JSON.stringify(s)}]`).join("")}`;
353
+ }
354
+ __name(buildRouterTypeAccess, "buildRouterTypeAccess");
355
+ function buildApiFile(routes) {
356
+ const contracted = routes.filter((r) => r.contract);
357
+ const lines = [
358
+ "// Generated by @dudousxd/nestjs-inertia-codegen. Do not edit.",
359
+ "",
360
+ "import { queryOptions } from '@tanstack/query-core';",
361
+ "import { route } from './routes.js';",
362
+ "import { createFetcher } from '@dudousxd/nestjs-inertia-client';",
363
+ "",
364
+ "export const fetcher = createFetcher();",
365
+ ""
366
+ ];
367
+ if (contracted.length === 0) {
368
+ lines.push("export type ApiRouter = Record<string, never>;");
369
+ lines.push("");
370
+ lines.push("export const api: Record<string, never> = {} as Record<string, never>;");
371
+ lines.push("");
372
+ lines.push("export namespace Route {");
373
+ lines.push(" export type Response<K extends string> = never;");
374
+ lines.push(" export type Body<K extends string> = never;");
375
+ lines.push(" export type Query<K extends string> = never;");
376
+ lines.push(" export type Params<K extends string> = never;");
377
+ lines.push(" export type Error<K extends string> = never;");
378
+ lines.push(" export type Request<K extends string> = { body: never; query: never; params: never };");
379
+ lines.push("}");
380
+ lines.push("");
381
+ lines.push("export namespace Path {");
382
+ lines.push(" export type Response<M extends string, U extends string> = never;");
383
+ lines.push(" export type Body<M extends string, U extends string> = never;");
384
+ lines.push(" export type Query<M extends string, U extends string> = never;");
385
+ lines.push(" export type Params<M extends string, U extends string> = never;");
386
+ lines.push(" export type Error<M extends string, U extends string> = never;");
387
+ lines.push("}");
388
+ lines.push("");
389
+ return lines.join("\n");
390
+ }
391
+ const tree = /* @__PURE__ */ new Map();
392
+ for (const r of contracted) {
393
+ const c = r.contract;
394
+ const name = r.name;
395
+ const segments = splitName(name);
396
+ for (const seg of segments) {
397
+ validateNameSegment(seg, name);
398
+ }
399
+ const leaf = {
400
+ kind: "leaf",
401
+ method: r.method,
402
+ name,
403
+ path: r.path,
404
+ contractSource: c.contractSource
405
+ };
406
+ insertIntoTree(tree, segments, leaf, name);
407
+ }
408
+ void detectCollisions;
409
+ lines.push("export type ApiRouter = {");
410
+ lines.push(...emitRouterTypeBlock(tree, 2));
411
+ lines.push("};");
412
+ lines.push("");
413
+ lines.push("export const api = {");
414
+ lines.push(...emitApiObjectBlock(tree, 2));
415
+ lines.push("};");
416
+ lines.push("");
417
+ lines.push("type _RouterAt<R, P extends string> = P extends `${infer Head}.${infer Tail}`");
418
+ lines.push(" ? Head extends keyof R ? _RouterAt<R[Head], Tail> : never");
419
+ lines.push(" : P extends keyof R ? R[P] : never;");
420
+ lines.push("");
421
+ lines.push("type ResolveByName<K extends string, Field extends string> = _RouterAt<ApiRouter, K> extends infer R ? Field extends keyof R ? R[Field] : never : never;");
422
+ lines.push("");
423
+ lines.push("type _LeafValues<T> = T extends { method: string; url: string }");
424
+ lines.push(" ? T");
425
+ lines.push(" : T extends object ? _LeafValues<T[keyof T]> : never;");
426
+ lines.push("");
427
+ lines.push("type ResolveByPath<M extends string, U extends string, Field extends string> = _LeafValues<ApiRouter> extends infer L");
428
+ lines.push(" ? L extends { method: M; url: U }");
429
+ lines.push(" ? Field extends keyof L ? L[Field] : never");
430
+ lines.push(" : never");
431
+ lines.push(" : never;");
432
+ lines.push("");
433
+ lines.push("export namespace Route {");
434
+ lines.push(' export type Response<K extends string> = ResolveByName<K, "response">;');
435
+ lines.push(' export type Body<K extends string> = ResolveByName<K, "body">;');
436
+ lines.push(' export type Query<K extends string> = ResolveByName<K, "query">;');
437
+ lines.push(' export type Params<K extends string> = ResolveByName<K, "params">;');
438
+ lines.push(' export type Error<K extends string> = ResolveByName<K, "error">;');
439
+ lines.push(" export type Request<K extends string> = {");
440
+ lines.push(" body: Body<K>;");
441
+ lines.push(" query: Query<K>;");
442
+ lines.push(" params: Params<K>;");
443
+ lines.push(" };");
444
+ lines.push("}");
445
+ lines.push("");
446
+ lines.push("export namespace Path {");
447
+ lines.push(' export type Response<M extends string, U extends string> = ResolveByPath<M, U, "response">;');
448
+ lines.push(' export type Body<M extends string, U extends string> = ResolveByPath<M, U, "body">;');
449
+ lines.push(' export type Query<M extends string, U extends string> = ResolveByPath<M, U, "query">;');
450
+ lines.push(' export type Params<M extends string, U extends string> = ResolveByPath<M, U, "params">;');
451
+ lines.push(' export type Error<M extends string, U extends string> = ResolveByPath<M, U, "error">;');
452
+ lines.push("}");
453
+ lines.push("");
454
+ return lines.join("\n");
455
+ }
456
+ __name(buildApiFile, "buildApiFile");
457
+
458
+ // src/emit/emit-cache.ts
459
+ var import_promises4 = require("fs/promises");
460
+ var import_node_path4 = require("path");
461
+ async function emitCache(pages, outDir) {
462
+ await (0, import_promises4.mkdir)(outDir, {
463
+ recursive: true
464
+ });
465
+ const entries = await Promise.all(pages.map(async (p) => {
466
+ const s = await (0, import_promises4.stat)(p.absolutePath);
467
+ return {
468
+ name: p.name,
469
+ relativePath: p.relativePath,
470
+ mtime: s.mtime.toISOString()
471
+ };
472
+ }));
473
+ const cache = {
474
+ pages: entries
475
+ };
476
+ await (0, import_promises4.writeFile)((0, import_node_path4.join)(outDir, "components.json"), `${JSON.stringify(cache, null, 2)}
477
+ `, "utf8");
478
+ }
479
+ __name(emitCache, "emitCache");
480
+
481
+ // src/emit/emit-index.ts
482
+ var import_promises5 = require("fs/promises");
483
+ var import_node_path5 = require("path");
484
+ async function emitIndex(outDir, hasContracts = false) {
485
+ await (0, import_promises5.mkdir)(outDir, {
486
+ recursive: true
487
+ });
488
+ const exports2 = [
489
+ "export * from './pages.js';",
490
+ "export * from './routes.js';"
491
+ ];
492
+ if (hasContracts) {
493
+ exports2.push("export * from './api.js';");
494
+ }
495
+ const content = [
496
+ "// Generated by @dudousxd/nestjs-inertia-codegen. Do not edit.",
497
+ ...exports2,
498
+ ""
499
+ ].join("\n");
500
+ await (0, import_promises5.writeFile)((0, import_node_path5.join)(outDir, "index.d.ts"), content, "utf8");
501
+ }
502
+ __name(emitIndex, "emitIndex");
503
+
504
+ // src/emit/emit-pages.ts
505
+ var import_promises6 = require("fs/promises");
506
+ var import_node_path6 = require("path");
507
+ async function emitPages(pages, outDir) {
508
+ await (0, import_promises6.mkdir)(outDir, {
509
+ recursive: true
510
+ });
511
+ const body = pages.map((p) => {
512
+ const propType = p.propsSource ?? "unknown";
513
+ const key = needsQuotes(p.name) ? JSON.stringify(p.name) : p.name;
514
+ return ` ${key}: ${propType};`;
515
+ }).join("\n");
516
+ const content = `// Generated by @dudousxd/nestjs-inertia-codegen. Do not edit.
517
+ export interface InertiaPages {
518
+ ${body}
519
+ }
520
+ `;
521
+ await (0, import_promises6.writeFile)((0, import_node_path6.join)(outDir, "pages.d.ts"), content, "utf8");
522
+ }
523
+ __name(emitPages, "emitPages");
524
+ function needsQuotes(name) {
525
+ return !/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name);
526
+ }
527
+ __name(needsQuotes, "needsQuotes");
528
+
529
+ // src/emit/emit-routes.ts
530
+ var import_promises7 = require("fs/promises");
531
+ var import_node_path7 = require("path");
532
+ async function emitRoutes(routes, outDir) {
533
+ await (0, import_promises7.mkdir)(outDir, {
534
+ recursive: true
535
+ });
536
+ const content = buildRoutesFile(routes);
537
+ await (0, import_promises7.writeFile)((0, import_node_path7.join)(outDir, "routes.ts"), content, "utf8");
538
+ }
539
+ __name(emitRoutes, "emitRoutes");
540
+ function buildRoutesFile(routes) {
541
+ if (routes.length === 0) {
542
+ return buildEmpty();
543
+ }
544
+ const entries = routes.map((r) => ` ${JSON.stringify(r.name)}: ${JSON.stringify(r.path)},`).join("\n");
545
+ const routeNameUnion = routes.map((r) => ` | ${JSON.stringify(r.name)}`).join("\n");
546
+ const lines = [
547
+ "// Generated by @dudousxd/nestjs-inertia-codegen. Do not edit.",
548
+ "",
549
+ "/** Map of route name \u2192 path pattern. */",
550
+ "export const ROUTES = {",
551
+ entries,
552
+ "} as const;",
553
+ "",
554
+ "/** Union of all known route names. */",
555
+ "export type RouteName =",
556
+ `${routeNameUnion};`,
557
+ "",
558
+ "/**",
559
+ " * Extracts path-parameter names from a route path string.",
560
+ ' * `ExtractParams<"/users/:id">` \u2192 `"id"`',
561
+ " */",
562
+ "export type ExtractParams<Path extends string> =",
563
+ " Path extends `${string}:${infer Param}/${infer Rest}`",
564
+ " ? Param | ExtractParams<`/${Rest}`>",
565
+ " : Path extends `${string}:${infer Param}`",
566
+ " ? Param",
567
+ " : never;",
568
+ "",
569
+ "/**",
570
+ " * Mapped type: given a `RouteName`, returns an object with each path param as `string`.",
571
+ ' * `RouteParams<"UsersController.show">` \u2192 `{ id: string }`',
572
+ ' * `RouteParams<"UsersController.list">` \u2192 `Record<string, never>`',
573
+ " */",
574
+ "export type RouteParams<K extends RouteName> =",
575
+ " ExtractParams<(typeof ROUTES)[K]> extends never",
576
+ " ? Record<string, never>",
577
+ " : { [P in ExtractParams<(typeof ROUTES)[K]>]: string };",
578
+ "",
579
+ "/**",
580
+ " * Map of every route name to its resolved params object.",
581
+ " * Use this for `InertiaRegistry` module augmentation.",
582
+ " */",
583
+ "export type RouteParamsMap = { [K in RouteName]: RouteParams<K> };",
584
+ "",
585
+ "/**",
586
+ " * Build a URL from a named route, interpolating path params and appending query string.",
587
+ " *",
588
+ " * @example",
589
+ " * route('UsersController.show', { id: '42' }) // \u2192 '/users/42'",
590
+ " * route('UsersController.list') // \u2192 '/users'",
591
+ " * route('users.list', undefined, { active: true }) // \u2192 '/api/users?active=true'",
592
+ " */",
593
+ "export function route<K extends RouteName>(",
594
+ " name: K,",
595
+ " ...args: ExtractParams<(typeof ROUTES)[K]> extends never",
596
+ " ? [params?: undefined, query?: Record<string, unknown>]",
597
+ " : [params: RouteParams<K>, query?: Record<string, unknown>]",
598
+ "): string {",
599
+ " const [params, query] = args as [Record<string, string> | undefined, Record<string, unknown> | undefined];",
600
+ " const path: string | undefined = ROUTES[name as RouteName];",
601
+ " if (path === undefined) {",
602
+ " const available = Object.keys(ROUTES).join(', ');",
603
+ " throw new Error(",
604
+ ' `[nestjs-inertia] Route "${String(name)}" does not exist.\\n\\n` +',
605
+ " `Available routes: ${available}\\n\\n` +",
606
+ " `This usually means:\\n` +",
607
+ " ` - The route name has a typo\\n` +",
608
+ " ` - The controller hasn't been scanned by codegen yet (run: nestjs-inertia codegen)\\n` +",
609
+ " ` - The @As() decorator changed the route name\\n`",
610
+ " );",
611
+ " }",
612
+ " let resolvedPath: string = path;",
613
+ " if (params) {",
614
+ " resolvedPath = resolvedPath.replace(/:([^/]+)/g, (_, key) => {",
615
+ " const val = params[key];",
616
+ " if (val === undefined) throw new Error(`Missing route param: ${key}`);",
617
+ " return encodeURIComponent(val);",
618
+ " });",
619
+ " }",
620
+ " if (query && Object.keys(query).length > 0) {",
621
+ " const qs = new URLSearchParams();",
622
+ " for (const [k, v] of Object.entries(query)) {",
623
+ " if (v !== undefined && v !== null) qs.append(k, String(v));",
624
+ " }",
625
+ " const qStr = qs.toString();",
626
+ " if (qStr) {",
627
+ " const sep = resolvedPath.includes('?') ? '&' : '?';",
628
+ " resolvedPath = `${resolvedPath}${sep}${qStr}`;",
629
+ " }",
630
+ " }",
631
+ " return resolvedPath;",
632
+ "}",
633
+ ""
634
+ ];
635
+ return lines.join("\n");
636
+ }
637
+ __name(buildRoutesFile, "buildRoutesFile");
638
+ function buildEmpty() {
639
+ return [
640
+ "// Generated by @dudousxd/nestjs-inertia-codegen. Do not edit.",
641
+ "",
642
+ "export const ROUTES = {} as const;",
643
+ "export type RouteName = never;",
644
+ "export type ExtractParams<_Path extends string> = never;",
645
+ "export type RouteParams<_K extends RouteName> = Record<string, never>;",
646
+ 'export function route(_name: never, _params?: undefined, _query?: Record<string, unknown>): string { return ""; }',
647
+ ""
648
+ ].join("\n");
649
+ }
650
+ __name(buildEmpty, "buildEmpty");
651
+
652
+ // src/generate.ts
653
+ async function generate(config, routes = []) {
654
+ const pages = await discoverPages({
655
+ glob: config.pages.glob,
656
+ cwd: config.codegen.cwd,
657
+ propsExport: config.pages.propsExport,
658
+ componentNameStrategy: config.pages.componentNameStrategy
659
+ });
660
+ await emitPages(pages, config.codegen.outDir);
661
+ await emitCache(pages, config.codegen.outDir);
662
+ const hasRoutes = routes.length > 0;
663
+ const hasContracts = routes.some((r) => r.contract);
664
+ if (hasRoutes) {
665
+ await emitRoutes(routes, config.codegen.outDir);
666
+ }
667
+ await emitIndex(config.codegen.outDir, hasContracts);
668
+ if (hasContracts) {
669
+ await emitApi(routes, config.codegen.outDir);
670
+ }
671
+ }
672
+ __name(generate, "generate");
673
+
674
+ // src/watch/watcher.ts
675
+ var import_promises9 = require("fs/promises");
676
+ var import_node_path10 = require("path");
677
+ var import_chokidar = __toESM(require("chokidar"), 1);
678
+
679
+ // src/discovery/contracts-fast.ts
680
+ var import_node_path8 = require("path");
681
+ var import_fast_glob2 = __toESM(require("fast-glob"), 1);
682
+ var import_ts_morph = require("ts-morph");
683
+ async function discoverContractsFast(opts) {
684
+ const { cwd, glob, tsconfig } = opts;
685
+ const tsconfigPath = tsconfig ? (0, import_node_path8.resolve)(tsconfig) : (0, import_node_path8.join)(cwd, "tsconfig.json");
686
+ let project;
687
+ try {
688
+ project = new import_ts_morph.Project({
689
+ tsConfigFilePath: tsconfigPath,
690
+ skipAddingFilesFromTsConfig: true,
691
+ skipLoadingLibFiles: true,
692
+ skipFileDependencyResolution: true
693
+ });
694
+ } catch {
695
+ project = new import_ts_morph.Project({
696
+ skipAddingFilesFromTsConfig: true,
697
+ skipLoadingLibFiles: true,
698
+ skipFileDependencyResolution: true,
699
+ compilerOptions: {
700
+ allowJs: true,
701
+ resolveJsonModule: false,
702
+ strict: false
703
+ }
704
+ });
705
+ }
706
+ const files = await (0, import_fast_glob2.default)(glob, {
707
+ cwd,
708
+ absolute: true,
709
+ onlyFiles: true
710
+ });
711
+ for (const f of files) {
712
+ project.addSourceFileAtPath(f);
713
+ }
714
+ const routes = [];
715
+ for (const sourceFile of project.getSourceFiles()) {
716
+ routes.push(...extractFromSourceFile(sourceFile));
717
+ }
718
+ return routes;
719
+ }
720
+ __name(discoverContractsFast, "discoverContractsFast");
721
+ function zodAstToTs(node) {
722
+ if (!import_ts_morph.Node.isCallExpression(node)) return "unknown";
723
+ const expr = node.getExpression();
724
+ if (import_ts_morph.Node.isPropertyAccessExpression(expr)) {
725
+ const methodName = expr.getName();
726
+ const receiver = expr.getExpression();
727
+ if (methodName === "optional") {
728
+ return `${zodAstToTs(receiver)} | undefined`;
729
+ }
730
+ if (methodName === "nullable") {
731
+ return `${zodAstToTs(receiver)} | null`;
732
+ }
733
+ const args = node.getArguments();
734
+ switch (methodName) {
735
+ case "string":
736
+ return "string";
737
+ case "number":
738
+ return "number";
739
+ case "boolean":
740
+ return "boolean";
741
+ case "unknown":
742
+ return "unknown";
743
+ case "any":
744
+ return "unknown";
745
+ case "literal": {
746
+ const lit = args[0];
747
+ if (!lit) return "unknown";
748
+ if (import_ts_morph.Node.isStringLiteral(lit)) return JSON.stringify(lit.getLiteralValue());
749
+ if (import_ts_morph.Node.isNumericLiteral(lit)) return lit.getLiteralValue().toString();
750
+ if (lit.getKind() === import_ts_morph.SyntaxKind.TrueKeyword) return "true";
751
+ if (lit.getKind() === import_ts_morph.SyntaxKind.FalseKeyword) return "false";
752
+ return "unknown";
753
+ }
754
+ case "enum": {
755
+ const arrArg = args[0];
756
+ if (!arrArg || !import_ts_morph.Node.isArrayLiteralExpression(arrArg)) return "unknown";
757
+ const members = arrArg.getElements().map((el) => import_ts_morph.Node.isStringLiteral(el) ? JSON.stringify(el.getLiteralValue()) : "unknown");
758
+ return members.join(" | ");
759
+ }
760
+ case "array": {
761
+ const inner = args[0];
762
+ if (!inner) return "unknown";
763
+ return `Array<${zodAstToTs(inner)}>`;
764
+ }
765
+ case "object": {
766
+ const objArg = args[0];
767
+ if (!objArg || !import_ts_morph.Node.isObjectLiteralExpression(objArg)) return "unknown";
768
+ const lines = [];
769
+ for (const prop of objArg.getProperties()) {
770
+ if (!import_ts_morph.Node.isPropertyAssignment(prop)) continue;
771
+ const key = prop.getName();
772
+ const valNode = prop.getInitializer();
773
+ if (!valNode) continue;
774
+ const tsType = zodAstToTs(valNode);
775
+ const isOpt = isOptionalChain(valNode);
776
+ lines.push(`${key}${isOpt ? "?" : ""}: ${tsType}`);
777
+ }
778
+ return `{ ${lines.join("; ")} }`;
779
+ }
780
+ case "union": {
781
+ const arrArg = args[0];
782
+ if (!arrArg || !import_ts_morph.Node.isArrayLiteralExpression(arrArg)) return "unknown";
783
+ return arrArg.getElements().map(zodAstToTs).join(" | ");
784
+ }
785
+ case "record": {
786
+ const valArg = args.length === 1 ? args[0] : args[1];
787
+ if (!valArg) return "unknown";
788
+ return `Record<string, ${zodAstToTs(valArg)}>`;
789
+ }
790
+ case "tuple": {
791
+ const arrArg = args[0];
792
+ if (!arrArg || !import_ts_morph.Node.isArrayLiteralExpression(arrArg)) return "unknown";
793
+ return `[${arrArg.getElements().map(zodAstToTs).join(", ")}]`;
794
+ }
795
+ default:
796
+ return "unknown";
797
+ }
798
+ }
799
+ return "unknown";
800
+ }
801
+ __name(zodAstToTs, "zodAstToTs");
802
+ function isOptionalChain(node) {
803
+ if (!import_ts_morph.Node.isCallExpression(node)) return false;
804
+ const expr = node.getExpression();
805
+ return import_ts_morph.Node.isPropertyAccessExpression(expr) && expr.getName() === "optional";
806
+ }
807
+ __name(isOptionalChain, "isOptionalChain");
808
+ function decoratorStringArg(decoratorExpr) {
809
+ if (!decoratorExpr) return void 0;
810
+ if (import_ts_morph.Node.isStringLiteral(decoratorExpr)) return decoratorExpr.getLiteralValue();
811
+ if (import_ts_morph.Node.isArrayLiteralExpression(decoratorExpr)) {
812
+ const first = decoratorExpr.getElements()[0];
813
+ if (first && import_ts_morph.Node.isStringLiteral(first)) return first.getLiteralValue();
814
+ }
815
+ return void 0;
816
+ }
817
+ __name(decoratorStringArg, "decoratorStringArg");
818
+ function parseDefineContractCall(callExpr) {
819
+ if (!import_ts_morph.Node.isCallExpression(callExpr)) return null;
820
+ const callee = callExpr.getExpression();
821
+ const calleeName = import_ts_morph.Node.isIdentifier(callee) ? callee.getText() : import_ts_morph.Node.isPropertyAccessExpression(callee) ? callee.getName() : "";
822
+ if (calleeName !== "defineContract") return null;
823
+ const args = callExpr.getArguments();
824
+ const optsArg = args[0];
825
+ if (!optsArg || !import_ts_morph.Node.isObjectLiteralExpression(optsArg)) return null;
826
+ let query = null;
827
+ let body = null;
828
+ let response = "unknown";
829
+ for (const prop of optsArg.getProperties()) {
830
+ if (!import_ts_morph.Node.isPropertyAssignment(prop)) continue;
831
+ const propName = prop.getName();
832
+ const val = prop.getInitializer();
833
+ if (!val) continue;
834
+ if (propName === "query") {
835
+ query = zodAstToTs(val);
836
+ } else if (propName === "body") {
837
+ body = zodAstToTs(val);
838
+ } else if (propName === "response") {
839
+ response = zodAstToTs(val);
840
+ }
841
+ }
842
+ return {
843
+ query,
844
+ body,
845
+ response
846
+ };
847
+ }
848
+ __name(parseDefineContractCall, "parseDefineContractCall");
849
+ function deriveClassSegment(className) {
850
+ const noSuffix = className.replace(/Controller$/, "");
851
+ if (!noSuffix) {
852
+ throw new Error(`Controller class name "${className}" derives empty route segment after stripping "Controller". Add an @As(...) override at the class level.`);
853
+ }
854
+ return noSuffix.charAt(0).toLowerCase() + noSuffix.slice(1);
855
+ }
856
+ __name(deriveClassSegment, "deriveClassSegment");
857
+ function resolveRouteName(className, methodName, classAs, methodAs) {
858
+ const classPortion = classAs ?? deriveClassSegment(className);
859
+ const methodPortion = methodAs ?? methodName;
860
+ return `${classPortion}.${methodPortion}`;
861
+ }
862
+ __name(resolveRouteName, "resolveRouteName");
863
+ function joinPaths(prefix, suffix) {
864
+ if (!prefix && !suffix) return "/";
865
+ if (!prefix) return suffix.startsWith("/") ? suffix : `/${suffix}`;
866
+ if (!suffix) return prefix.startsWith("/") ? prefix : `/${prefix}`;
867
+ const p = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
868
+ const s = suffix.startsWith("/") ? suffix : `/${suffix}`;
869
+ const combined = p + s;
870
+ return combined === "" ? "/" : combined;
871
+ }
872
+ __name(joinPaths, "joinPaths");
873
+ function extractParams(path) {
874
+ const matches = path.matchAll(/:(\w+)/g);
875
+ return Array.from(matches).map((m) => ({
876
+ name: m[1],
877
+ source: "path"
878
+ }));
879
+ }
880
+ __name(extractParams, "extractParams");
881
+ function resolveTypeNodeToString(typeNode, sourceFile, depth) {
882
+ if (depth <= 0) return "unknown";
883
+ if (import_ts_morph.Node.isArrayTypeNode(typeNode)) {
884
+ const elementType = typeNode.getElementTypeNode();
885
+ return `Array<${resolveTypeNodeToString(elementType, sourceFile, depth)}>`;
886
+ }
887
+ if (import_ts_morph.Node.isTypeReference(typeNode)) {
888
+ const typeName = typeNode.getTypeName();
889
+ const name = import_ts_morph.Node.isIdentifier(typeName) ? typeName.getText() : typeNode.getText();
890
+ if (name === "string" || name === "number" || name === "boolean") return name;
891
+ if (name === "Date") return "string";
892
+ if (name === "unknown" || name === "any") return "unknown";
893
+ if (name === "Array") {
894
+ const typeArgs = typeNode.getTypeArguments();
895
+ const firstTypeArg = typeArgs[0];
896
+ if (typeArgs.length > 0 && firstTypeArg !== void 0) {
897
+ return `Array<${resolveTypeNodeToString(firstTypeArg, sourceFile, depth)}>`;
898
+ }
899
+ return "Array<unknown>";
900
+ }
901
+ if (name === "Promise") {
902
+ const typeArgs = typeNode.getTypeArguments();
903
+ const firstTypeArg = typeArgs[0];
904
+ if (typeArgs.length > 0 && firstTypeArg !== void 0) {
905
+ return resolveTypeNodeToString(firstTypeArg, sourceFile, depth);
906
+ }
907
+ return "unknown";
908
+ }
909
+ const classDec = sourceFile.getClass(name);
910
+ if (classDec) {
911
+ return resolveClassDeclaration(classDec, sourceFile, depth - 1);
912
+ }
913
+ return name;
914
+ }
915
+ const kind = typeNode.getKind();
916
+ if (kind === import_ts_morph.SyntaxKind.StringKeyword) return "string";
917
+ if (kind === import_ts_morph.SyntaxKind.NumberKeyword) return "number";
918
+ if (kind === import_ts_morph.SyntaxKind.BooleanKeyword) return "boolean";
919
+ if (kind === import_ts_morph.SyntaxKind.UnknownKeyword) return "unknown";
920
+ if (kind === import_ts_morph.SyntaxKind.AnyKeyword) return "unknown";
921
+ return typeNode.getText();
922
+ }
923
+ __name(resolveTypeNodeToString, "resolveTypeNodeToString");
924
+ function resolveClassDeclaration(cls, sourceFile, depth) {
925
+ if (depth < 0) return "unknown";
926
+ const lines = [];
927
+ for (const prop of cls.getProperties()) {
928
+ const propName = prop.getName();
929
+ const isOptional = prop.hasQuestionToken();
930
+ const propTypeNode = prop.getTypeNode();
931
+ let propType = "unknown";
932
+ if (propTypeNode) {
933
+ propType = resolveTypeNodeToString(propTypeNode, sourceFile, depth);
934
+ }
935
+ lines.push(`${propName}${isOptional ? "?" : ""}: ${propType}`);
936
+ }
937
+ return `{ ${lines.join("; ")} }`;
938
+ }
939
+ __name(resolveClassDeclaration, "resolveClassDeclaration");
940
+ function extractBodyType(method, sourceFile) {
941
+ for (const param of method.getParameters()) {
942
+ const bodyDecorator = param.getDecorators().find((d) => d.getName() === "Body");
943
+ if (!bodyDecorator) continue;
944
+ const bodyArgs = bodyDecorator.getArguments();
945
+ if (bodyArgs.length > 0) continue;
946
+ const typeNode = param.getTypeNode();
947
+ if (typeNode) {
948
+ return resolveTypeNodeToString(typeNode, sourceFile, 3);
949
+ }
950
+ }
951
+ return null;
952
+ }
953
+ __name(extractBodyType, "extractBodyType");
954
+ function extractQueryType(method, sourceFile) {
955
+ for (const param of method.getParameters()) {
956
+ const queryDecorator = param.getDecorators().find((d) => d.getName() === "Query");
957
+ if (!queryDecorator) continue;
958
+ const queryArgs = queryDecorator.getArguments();
959
+ if (queryArgs.length > 0) continue;
960
+ const typeNode = param.getTypeNode();
961
+ if (typeNode) {
962
+ return resolveTypeNodeToString(typeNode, sourceFile, 3);
963
+ }
964
+ }
965
+ return null;
966
+ }
967
+ __name(extractQueryType, "extractQueryType");
968
+ function extractParamsType(method, sourceFile) {
969
+ const entries = [];
970
+ for (const param of method.getParameters()) {
971
+ const paramDecorator = param.getDecorators().find((d) => d.getName() === "Param");
972
+ if (!paramDecorator) continue;
973
+ const paramArgs = paramDecorator.getArguments();
974
+ if (paramArgs.length === 0) continue;
975
+ const nameArg = paramArgs[0];
976
+ if (!import_ts_morph.Node.isStringLiteral(nameArg)) continue;
977
+ const paramName = nameArg.getLiteralValue();
978
+ const typeNode = param.getTypeNode();
979
+ const paramType = typeNode ? resolveTypeNodeToString(typeNode, sourceFile, 3) : "string";
980
+ entries.push(`${paramName}: ${paramType}`);
981
+ }
982
+ return entries.length > 0 ? `{ ${entries.join("; ")} }` : null;
983
+ }
984
+ __name(extractParamsType, "extractParamsType");
985
+ function extractResponseType(method, sourceFile) {
986
+ const apiResponseDecorator = method.getDecorator("ApiResponse");
987
+ if (apiResponseDecorator) {
988
+ const args = apiResponseDecorator.getArguments();
989
+ const optsArg = args[0];
990
+ if (optsArg && import_ts_morph.Node.isObjectLiteralExpression(optsArg)) {
991
+ for (const prop of optsArg.getProperties()) {
992
+ if (!import_ts_morph.Node.isPropertyAssignment(prop)) continue;
993
+ if (prop.getName() !== "type") continue;
994
+ const val = prop.getInitializer();
995
+ if (!val) continue;
996
+ if (import_ts_morph.Node.isArrayLiteralExpression(val)) {
997
+ const elements = val.getElements();
998
+ const firstEl = elements[0];
999
+ if (elements.length > 0 && firstEl !== void 0) {
1000
+ const innerType = resolveIdentifierToClassType(firstEl, sourceFile, 3);
1001
+ return `Array<${innerType}>`;
1002
+ }
1003
+ return "Array<unknown>";
1004
+ }
1005
+ return resolveIdentifierToClassType(val, sourceFile, 3);
1006
+ }
1007
+ }
1008
+ }
1009
+ const returnTypeNode = method.getReturnTypeNode();
1010
+ if (returnTypeNode) {
1011
+ return resolveTypeNodeToString(returnTypeNode, sourceFile, 3);
1012
+ }
1013
+ return "unknown";
1014
+ }
1015
+ __name(extractResponseType, "extractResponseType");
1016
+ function resolveIdentifierToClassType(node, sourceFile, depth) {
1017
+ if (!import_ts_morph.Node.isIdentifier(node)) return "unknown";
1018
+ const name = node.getText();
1019
+ const classDec = sourceFile.getClass(name);
1020
+ if (classDec) {
1021
+ return resolveClassDeclaration(classDec, sourceFile, depth - 1);
1022
+ }
1023
+ return name;
1024
+ }
1025
+ __name(resolveIdentifierToClassType, "resolveIdentifierToClassType");
1026
+ function extractDtoContract(method, sourceFile) {
1027
+ const body = extractBodyType(method, sourceFile);
1028
+ const query = extractQueryType(method, sourceFile);
1029
+ const paramsType = extractParamsType(method, sourceFile);
1030
+ const response = extractResponseType(method, sourceFile);
1031
+ if (body === null && query === null && paramsType === null && response === "unknown") {
1032
+ return null;
1033
+ }
1034
+ return {
1035
+ query,
1036
+ body,
1037
+ response,
1038
+ params: paramsType
1039
+ };
1040
+ }
1041
+ __name(extractDtoContract, "extractDtoContract");
1042
+ var HTTP_METHOD_DECORATORS = {
1043
+ Get: "GET",
1044
+ Post: "POST",
1045
+ Put: "PUT",
1046
+ Patch: "PATCH",
1047
+ Delete: "DELETE",
1048
+ Options: "OPTIONS",
1049
+ Head: "HEAD",
1050
+ All: "ALL"
1051
+ };
1052
+ function extractFromSourceFile(sourceFile) {
1053
+ const routes = [];
1054
+ const seenNames = /* @__PURE__ */ new Map();
1055
+ const classes = sourceFile.getClasses();
1056
+ for (const cls of classes) {
1057
+ const controllerDecorator = cls.getDecorator("Controller");
1058
+ if (!controllerDecorator) continue;
1059
+ const controllerArgs = controllerDecorator.getArguments();
1060
+ const firstArg = controllerArgs[0];
1061
+ const prefix = decoratorStringArg(firstArg) ?? "";
1062
+ const className = cls.getName() ?? "Unknown";
1063
+ for (const method of cls.getMethods()) {
1064
+ let httpMethod;
1065
+ let handlerPath = "";
1066
+ for (const [decoratorName, verb] of Object.entries(HTTP_METHOD_DECORATORS)) {
1067
+ const httpDecorator = method.getDecorator(decoratorName);
1068
+ if (httpDecorator) {
1069
+ httpMethod = verb;
1070
+ const httpArgs = httpDecorator.getArguments();
1071
+ const pathArg = httpArgs[0];
1072
+ handlerPath = decoratorStringArg(pathArg) ?? "";
1073
+ break;
1074
+ }
1075
+ }
1076
+ const applyContractDecorator = method.getDecorator("ApplyContract");
1077
+ if (applyContractDecorator) {
1078
+ const decoratorArgs = applyContractDecorator.getArguments();
1079
+ const firstDecoratorArg = decoratorArgs[0];
1080
+ if (!firstDecoratorArg) continue;
1081
+ let contractDef = null;
1082
+ if (import_ts_morph.Node.isCallExpression(firstDecoratorArg)) {
1083
+ contractDef = parseDefineContractCall(firstDecoratorArg);
1084
+ } else if (import_ts_morph.Node.isIdentifier(firstDecoratorArg)) {
1085
+ const identName = firstDecoratorArg.getText();
1086
+ const varDecl = sourceFile.getVariableDeclaration(identName);
1087
+ if (!varDecl) {
1088
+ console.warn(`[nestjs-inertia-codegen/fast] Cannot resolve '${identName}' in ${sourceFile.getFilePath()} (cross-file imports are out-of-scope for v1) \u2014 skipping`);
1089
+ continue;
1090
+ }
1091
+ const initializer = varDecl.getInitializer();
1092
+ if (!initializer) continue;
1093
+ contractDef = parseDefineContractCall(initializer);
1094
+ } else {
1095
+ console.warn(`[nestjs-inertia-codegen/fast] @ApplyContract arg is not an identifier or call expression in ${sourceFile.getFilePath()} \u2014 skipping`);
1096
+ continue;
1097
+ }
1098
+ if (!contractDef) continue;
1099
+ if (!httpMethod) continue;
1100
+ const resolvedMethod = httpMethod;
1101
+ const resolvedPath = joinPaths(prefix, handlerPath);
1102
+ const combined = resolvedPath;
1103
+ const params = extractParams(combined);
1104
+ const methodName = method.getName();
1105
+ const classAsDecorator = cls.getDecorator("As");
1106
+ let classAs;
1107
+ if (classAsDecorator) {
1108
+ const classAsArgs = classAsDecorator.getArguments();
1109
+ const classAsName = decoratorStringArg(classAsArgs[0]);
1110
+ if (!classAsName) {
1111
+ throw new Error(`@As decorator on class ${className} must have a non-empty string argument.`);
1112
+ }
1113
+ classAs = classAsName;
1114
+ }
1115
+ const methodAsDecorator = method.getDecorator("As");
1116
+ let methodAs;
1117
+ if (methodAsDecorator) {
1118
+ const methodAsArgs = methodAsDecorator.getArguments();
1119
+ const methodAsName = decoratorStringArg(methodAsArgs[0]);
1120
+ if (!methodAsName) {
1121
+ throw new Error(`@As decorator on ${className}.${methodName} must have a non-empty string argument.`);
1122
+ }
1123
+ methodAs = methodAsName;
1124
+ }
1125
+ const routeName = resolveRouteName(className, methodName, classAs, methodAs);
1126
+ const qualifiedRef = `${className}.${methodName}`;
1127
+ const existing = seenNames.get(routeName);
1128
+ if (existing !== void 0) {
1129
+ throw new Error(`Route name collision: "${routeName}" is used by both "${existing}" and "${qualifiedRef}". Use @As(...) to give one of them a unique name.`);
1130
+ }
1131
+ seenNames.set(routeName, qualifiedRef);
1132
+ routes.push({
1133
+ method: resolvedMethod,
1134
+ path: combined,
1135
+ name: routeName,
1136
+ params,
1137
+ contract: {
1138
+ contractSource: {
1139
+ query: contractDef.query,
1140
+ body: contractDef.body,
1141
+ response: contractDef.response
1142
+ }
1143
+ }
1144
+ });
1145
+ } else {
1146
+ if (!httpMethod) continue;
1147
+ const combined = joinPaths(prefix, handlerPath);
1148
+ const params = extractParams(combined);
1149
+ const methodName = method.getName();
1150
+ const routeName = `${className}.${methodName}`;
1151
+ const dtoContract = extractDtoContract(method, sourceFile);
1152
+ routes.push({
1153
+ method: httpMethod,
1154
+ path: combined,
1155
+ name: routeName,
1156
+ params,
1157
+ // Attach contract if DTO extraction produced useful type info
1158
+ ...dtoContract ? {
1159
+ contract: {
1160
+ contractSource: {
1161
+ query: dtoContract.query,
1162
+ body: dtoContract.body,
1163
+ response: dtoContract.response
1164
+ }
1165
+ }
1166
+ } : {}
1167
+ });
1168
+ }
1169
+ }
1170
+ }
1171
+ return routes;
1172
+ }
1173
+ __name(extractFromSourceFile, "extractFromSourceFile");
1174
+
1175
+ // src/watch/lock-file.ts
1176
+ var import_promises8 = require("fs/promises");
1177
+ var import_node_path9 = require("path");
1178
+ var LOCK_FILE = ".watcher.lock";
1179
+ function isProcessAlive(pid) {
1180
+ try {
1181
+ process.kill(pid, 0);
1182
+ return true;
1183
+ } catch {
1184
+ return false;
1185
+ }
1186
+ }
1187
+ __name(isProcessAlive, "isProcessAlive");
1188
+ async function acquireLock(outDir) {
1189
+ await (0, import_promises8.mkdir)(outDir, {
1190
+ recursive: true
1191
+ });
1192
+ const lockPath = (0, import_node_path9.join)(outDir, LOCK_FILE);
1193
+ try {
1194
+ const raw = await (0, import_promises8.readFile)(lockPath, "utf8");
1195
+ const existing = JSON.parse(raw);
1196
+ if (isProcessAlive(existing.pid)) {
1197
+ return null;
1198
+ }
1199
+ } catch {
1200
+ }
1201
+ const lockData = {
1202
+ pid: process.pid,
1203
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
1204
+ };
1205
+ await (0, import_promises8.writeFile)(lockPath, `${JSON.stringify(lockData, null, 2)}
1206
+ `, "utf8");
1207
+ return {
1208
+ release: /* @__PURE__ */ __name(async () => {
1209
+ try {
1210
+ await (0, import_promises8.unlink)(lockPath);
1211
+ } catch {
1212
+ }
1213
+ }, "release")
1214
+ };
1215
+ }
1216
+ __name(acquireLock, "acquireLock");
1217
+
1218
+ // src/watch/watcher.ts
1219
+ var PAGES_DEBOUNCE_MS = 150;
1220
+ var NO_OP_WATCHER = {
1221
+ close: /* @__PURE__ */ __name(async () => {
1222
+ }, "close")
1223
+ };
1224
+ async function watch(config, onChange) {
1225
+ const lock = await acquireLock(config.codegen.outDir);
1226
+ if (lock === null) {
1227
+ let holderPid = "unknown";
1228
+ try {
1229
+ const raw = await (0, import_promises9.readFile)((0, import_node_path10.join)(config.codegen.outDir, ".watcher.lock"), "utf8");
1230
+ const data = JSON.parse(raw);
1231
+ if (data.pid !== void 0) holderPid = String(data.pid);
1232
+ } catch {
1233
+ }
1234
+ console.warn(`[nestjs-inertia-codegen] auto-watch skipped \u2014 another process (PID ${holderPid}) is already running the watcher in ${config.codegen.outDir}. Files will continue to regenerate from that process. To take over, stop the other watcher.`);
1235
+ return NO_OP_WATCHER;
1236
+ }
1237
+ try {
1238
+ await generate(config);
1239
+ } catch {
1240
+ }
1241
+ let pagesDebounceTimer;
1242
+ const pagesWatcher = import_chokidar.default.watch((0, import_node_path10.join)(config.codegen.cwd, config.pages.glob), {
1243
+ ignoreInitial: true,
1244
+ persistent: true,
1245
+ awaitWriteFinish: {
1246
+ stabilityThreshold: 80,
1247
+ pollInterval: 20
1248
+ }
1249
+ });
1250
+ function schedulePagesRegenerate() {
1251
+ if (pagesDebounceTimer !== void 0) {
1252
+ clearTimeout(pagesDebounceTimer);
1253
+ }
1254
+ pagesDebounceTimer = setTimeout(async () => {
1255
+ pagesDebounceTimer = void 0;
1256
+ try {
1257
+ await generate(config);
1258
+ } catch {
1259
+ }
1260
+ onChange?.();
1261
+ }, PAGES_DEBOUNCE_MS);
1262
+ }
1263
+ __name(schedulePagesRegenerate, "schedulePagesRegenerate");
1264
+ pagesWatcher.on("add", schedulePagesRegenerate);
1265
+ pagesWatcher.on("change", schedulePagesRegenerate);
1266
+ pagesWatcher.on("unlink", schedulePagesRegenerate);
1267
+ let contractsDebounceTimer;
1268
+ const contractsWatcher = import_chokidar.default.watch((0, import_node_path10.join)(config.codegen.cwd, config.contracts.glob), {
1269
+ ignoreInitial: true,
1270
+ persistent: true,
1271
+ awaitWriteFinish: {
1272
+ stabilityThreshold: 80,
1273
+ pollInterval: 20
1274
+ }
1275
+ });
1276
+ function scheduleContractsRegenerate() {
1277
+ if (contractsDebounceTimer !== void 0) {
1278
+ clearTimeout(contractsDebounceTimer);
1279
+ }
1280
+ contractsDebounceTimer = setTimeout(async () => {
1281
+ contractsDebounceTimer = void 0;
1282
+ try {
1283
+ const routes = await discoverContractsFast({
1284
+ cwd: config.codegen.cwd,
1285
+ glob: config.contracts.glob,
1286
+ ...config.app?.tsconfig ? {
1287
+ tsconfig: config.app.tsconfig
1288
+ } : {}
1289
+ });
1290
+ await emitRoutes(routes, config.codegen.outDir);
1291
+ const hasContracts = routes.some((r) => r.contract);
1292
+ await emitIndex(config.codegen.outDir, hasContracts);
1293
+ if (hasContracts) {
1294
+ await emitApi(routes, config.codegen.outDir);
1295
+ }
1296
+ } catch {
1297
+ }
1298
+ onChange?.();
1299
+ }, config.contracts.debounceMs);
1300
+ }
1301
+ __name(scheduleContractsRegenerate, "scheduleContractsRegenerate");
1302
+ contractsWatcher.on("add", scheduleContractsRegenerate);
1303
+ contractsWatcher.on("change", scheduleContractsRegenerate);
1304
+ contractsWatcher.on("unlink", scheduleContractsRegenerate);
1305
+ return {
1306
+ close: /* @__PURE__ */ __name(async () => {
1307
+ if (pagesDebounceTimer !== void 0) {
1308
+ clearTimeout(pagesDebounceTimer);
1309
+ pagesDebounceTimer = void 0;
1310
+ }
1311
+ if (contractsDebounceTimer !== void 0) {
1312
+ clearTimeout(contractsDebounceTimer);
1313
+ contractsDebounceTimer = void 0;
1314
+ }
1315
+ await pagesWatcher.close();
1316
+ await contractsWatcher.close();
1317
+ await lock.release();
1318
+ }, "close")
1319
+ };
1320
+ }
1321
+ __name(watch, "watch");
1322
+
1323
+ // src/index.ts
1324
+ var VERSION = "1.0.0";
1325
+
1326
+ // src/cli/codegen.ts
1327
+ async function runCodegen(opts = {}) {
1328
+ const cwd = opts.cwd ?? process.cwd();
1329
+ const config = await loadConfig(cwd);
1330
+ if (opts.watch) {
1331
+ const watcher = await watch(config);
1332
+ await new Promise((resolve3) => {
1333
+ function onSignal() {
1334
+ watcher.close().then(resolve3).catch(resolve3);
1335
+ }
1336
+ __name(onSignal, "onSignal");
1337
+ process.once("SIGINT", onSignal);
1338
+ process.once("SIGTERM", onSignal);
1339
+ });
1340
+ return;
1341
+ }
1342
+ const routes = await discoverContractsFast({
1343
+ cwd: config.codegen.cwd,
1344
+ glob: config.contracts.glob,
1345
+ ...config.app?.tsconfig ? {
1346
+ tsconfig: config.app.tsconfig
1347
+ } : {}
1348
+ });
1349
+ await generate(config, routes);
1350
+ console.log("\u2713 Codegen generated artifacts in", config.codegen.outDir);
1351
+ }
1352
+ __name(runCodegen, "runCodegen");
1353
+
1354
+ // src/cli/init.ts
1355
+ var import_node_child_process = require("child_process");
1356
+ var import_node_fs = require("fs");
1357
+ var import_promises10 = require("fs/promises");
1358
+ var import_node_path11 = require("path");
1359
+ var import_node_readline = require("readline");
1360
+ var GITIGNORE_ENTRY = ".nestjs-inertia/";
1361
+ var green = /* @__PURE__ */ __name((s) => `\x1B[32m${s}\x1B[0m`, "green");
1362
+ var yellow = /* @__PURE__ */ __name((s) => `\x1B[33m${s}\x1B[0m`, "yellow");
1363
+ var cyan = /* @__PURE__ */ __name((s) => `\x1B[36m${s}\x1B[0m`, "cyan");
1364
+ var dim = /* @__PURE__ */ __name((s) => `\x1B[2m${s}\x1B[0m`, "dim");
1365
+ var bold = /* @__PURE__ */ __name((s) => `\x1B[1m${s}\x1B[0m`, "bold");
1366
+ function logCreated(path) {
1367
+ console.log(` ${green("\u2713")} ${path} ${dim("(created)")}`);
1368
+ }
1369
+ __name(logCreated, "logCreated");
1370
+ function logPatched(path, detail) {
1371
+ console.log(` ${green("\u2713")} ${path} ${dim(`(${detail})`)}`);
1372
+ }
1373
+ __name(logPatched, "logPatched");
1374
+ function logSkipped(path) {
1375
+ console.log(` ${cyan("\u2192")} ${path} ${dim("(already exists, skipped)")}`);
1376
+ }
1377
+ __name(logSkipped, "logSkipped");
1378
+ function logWarning(msg) {
1379
+ console.log(` ${yellow("\u26A0")} ${msg}`);
1380
+ }
1381
+ __name(logWarning, "logWarning");
1382
+ function logSection(title) {
1383
+ console.log(`
1384
+ ${bold(title)}`);
1385
+ }
1386
+ __name(logSection, "logSection");
1387
+ async function readPackageJson(cwd) {
1388
+ try {
1389
+ const raw = await (0, import_promises10.readFile)((0, import_node_path11.join)(cwd, "package.json"), "utf8");
1390
+ return JSON.parse(raw);
1391
+ } catch {
1392
+ return {};
1393
+ }
1394
+ }
1395
+ __name(readPackageJson, "readPackageJson");
1396
+ function allDeps(pkg) {
1397
+ const deps = pkg.dependencies ?? {};
1398
+ const devDeps = pkg.devDependencies ?? {};
1399
+ return [
1400
+ ...Object.keys(deps),
1401
+ ...Object.keys(devDeps)
1402
+ ];
1403
+ }
1404
+ __name(allDeps, "allDeps");
1405
+ async function detectFramework(cwd) {
1406
+ const pkg = await readPackageJson(cwd);
1407
+ const deps = allDeps(pkg);
1408
+ if (deps.includes("@inertiajs/react") || deps.includes("react")) return "react";
1409
+ if (deps.includes("@inertiajs/vue3") || deps.includes("vue")) return "vue";
1410
+ if (deps.includes("@inertiajs/svelte") || deps.includes("svelte")) return "svelte";
1411
+ return null;
1412
+ }
1413
+ __name(detectFramework, "detectFramework");
1414
+ async function detectTemplateEngine(cwd) {
1415
+ const pkg = await readPackageJson(cwd);
1416
+ const deps = allDeps(pkg);
1417
+ if (deps.includes("handlebars")) return "handlebars";
1418
+ if (deps.includes("ejs")) return "ejs";
1419
+ if (deps.includes("pug")) return "pug";
1420
+ if (deps.includes("liquidjs")) return "liquid";
1421
+ return "html";
1422
+ }
1423
+ __name(detectTemplateEngine, "detectTemplateEngine");
1424
+ async function detectPackageManager(cwd) {
1425
+ async function exists(file) {
1426
+ try {
1427
+ await (0, import_promises10.access)((0, import_node_path11.join)(cwd, file));
1428
+ return true;
1429
+ } catch {
1430
+ return false;
1431
+ }
1432
+ }
1433
+ __name(exists, "exists");
1434
+ if (await exists("pnpm-lock.yaml")) return "pnpm";
1435
+ if (await exists("yarn.lock")) return "yarn";
1436
+ return "npm";
1437
+ }
1438
+ __name(detectPackageManager, "detectPackageManager");
1439
+ async function promptFramework() {
1440
+ if (!process.stdin.isTTY) return "react";
1441
+ return new Promise((resolve3) => {
1442
+ const rl = (0, import_node_readline.createInterface)({
1443
+ input: process.stdin,
1444
+ output: process.stdout
1445
+ });
1446
+ rl.question("[nestjs-inertia] Which frontend framework? (react/vue/svelte) [react]: ", (answer) => {
1447
+ rl.close();
1448
+ const trimmed = answer.trim().toLowerCase();
1449
+ if (trimmed === "vue") resolve3("vue");
1450
+ else if (trimmed === "svelte") resolve3("svelte");
1451
+ else resolve3("react");
1452
+ });
1453
+ });
1454
+ }
1455
+ __name(promptFramework, "promptFramework");
1456
+ async function fileExists2(filePath) {
1457
+ try {
1458
+ await (0, import_promises10.access)(filePath);
1459
+ return true;
1460
+ } catch {
1461
+ return false;
1462
+ }
1463
+ }
1464
+ __name(fileExists2, "fileExists");
1465
+ async function writeIfNotExists(filePath, content, label) {
1466
+ if (await fileExists2(filePath)) {
1467
+ logSkipped(label);
1468
+ return;
1469
+ }
1470
+ const dir = filePath.substring(0, filePath.lastIndexOf("/"));
1471
+ if (dir) {
1472
+ await (0, import_promises10.mkdir)(dir, {
1473
+ recursive: true
1474
+ });
1475
+ }
1476
+ await (0, import_promises10.writeFile)(filePath, content, "utf8");
1477
+ logCreated(label);
1478
+ }
1479
+ __name(writeIfNotExists, "writeIfNotExists");
1480
+ async function handleViteConfig(cwd, framework) {
1481
+ const filePath = (0, import_node_path11.join)(cwd, "vite.config.ts");
1482
+ if (await fileExists2(filePath)) {
1483
+ const existing = await (0, import_promises10.readFile)(filePath, "utf8");
1484
+ const hasPlugin = existing.includes("nestInertia") || existing.includes("nestjs-inertia-vite/plugin");
1485
+ if (!hasPlugin) {
1486
+ logSkipped("vite.config.ts");
1487
+ logWarning(`vite.config.ts exists but nestInertia plugin not detected \u2014 add it manually:
1488
+ import nestInertia from '@dudousxd/nestjs-inertia-vite/plugin';
1489
+ plugins: [nestInertia({ ${framework}: true })]`);
1490
+ } else {
1491
+ logSkipped("vite.config.ts");
1492
+ }
1493
+ return;
1494
+ }
1495
+ const dir = filePath.substring(0, filePath.lastIndexOf("/"));
1496
+ if (dir) {
1497
+ await (0, import_promises10.mkdir)(dir, {
1498
+ recursive: true
1499
+ });
1500
+ }
1501
+ await (0, import_promises10.writeFile)(filePath, viteConfigTemplate(framework), "utf8");
1502
+ logCreated("vite.config.ts");
1503
+ }
1504
+ __name(handleViteConfig, "handleViteConfig");
1505
+ async function patchGitignore(gitignorePath) {
1506
+ let existing = "";
1507
+ if (await fileExists2(gitignorePath)) {
1508
+ existing = await (0, import_promises10.readFile)(gitignorePath, "utf8");
1509
+ }
1510
+ if (existing.split("\n").some((line) => line.trim() === GITIGNORE_ENTRY)) {
1511
+ console.log(` ${cyan("\u2192")} .gitignore ${dim("(already contains .nestjs-inertia/, skipped)")}`);
1512
+ return;
1513
+ }
1514
+ const newContent = existing.endsWith("\n") || existing === "" ? `${existing}${GITIGNORE_ENTRY}
1515
+ ` : `${existing}
1516
+ ${GITIGNORE_ENTRY}
1517
+ `;
1518
+ await (0, import_promises10.writeFile)(gitignorePath, newContent, "utf8");
1519
+ logPatched(".gitignore", "added .nestjs-inertia/");
1520
+ }
1521
+ __name(patchGitignore, "patchGitignore");
1522
+ function installDeps(pkgManager, deps, dev) {
1523
+ if (deps.length === 0) return;
1524
+ const flag = dev ? pkgManager === "npm" ? "--save-dev" : "-D" : "";
1525
+ const cmd = pkgManager === "npm" ? `npm install ${flag} ${deps.join(" ")}` : pkgManager === "yarn" ? `yarn add ${flag} ${deps.join(" ")}` : `pnpm add ${flag} ${deps.join(" ")}`;
1526
+ logPatched(deps.join(", "), "installed");
1527
+ try {
1528
+ (0, import_node_child_process.execSync)(cmd, {
1529
+ stdio: "inherit"
1530
+ });
1531
+ } catch {
1532
+ logWarning(`Failed to install deps. Run manually:
1533
+ ${cmd}`);
1534
+ }
1535
+ }
1536
+ __name(installDeps, "installDeps");
1537
+ async function patchPackageJsonScripts(cwd, scripts) {
1538
+ const pkgPath = (0, import_node_path11.join)(cwd, "package.json");
1539
+ let pkg = {};
1540
+ try {
1541
+ pkg = JSON.parse(await (0, import_promises10.readFile)(pkgPath, "utf8"));
1542
+ } catch {
1543
+ return;
1544
+ }
1545
+ const existing = pkg.scripts ?? {};
1546
+ let changed = false;
1547
+ for (const [key, value] of Object.entries(scripts)) {
1548
+ if (!(key in existing)) {
1549
+ existing[key] = value;
1550
+ changed = true;
1551
+ logPatched("package.json", `added ${key} script`);
1552
+ } else {
1553
+ console.log(` ${cyan("\u2192")} package.json ${dim(`(${key} already defined, skipped)`)}`);
1554
+ }
1555
+ }
1556
+ if (!changed) {
1557
+ return;
1558
+ }
1559
+ pkg.scripts = existing;
1560
+ await (0, import_promises10.writeFile)(pkgPath, `${JSON.stringify(pkg, null, 2)}
1561
+ `, "utf8");
1562
+ }
1563
+ __name(patchPackageJsonScripts, "patchPackageJsonScripts");
1564
+ function findAfterLastImport(content) {
1565
+ const lastImportIndex = content.lastIndexOf("\nimport ");
1566
+ if (lastImportIndex !== -1) {
1567
+ const endOfLine = content.indexOf("\n", lastImportIndex + 1);
1568
+ return endOfLine !== -1 ? endOfLine + 1 : content.length;
1569
+ }
1570
+ if (content.startsWith("import ")) {
1571
+ const endOfLine = content.indexOf("\n");
1572
+ return endOfLine !== -1 ? endOfLine + 1 : content.length;
1573
+ }
1574
+ return 0;
1575
+ }
1576
+ __name(findAfterLastImport, "findAfterLastImport");
1577
+ function patchAppModule(filePath, rootView) {
1578
+ let content;
1579
+ try {
1580
+ content = (0, import_node_fs.readFileSync)(filePath, "utf8");
1581
+ } catch {
1582
+ return "skipped";
1583
+ }
1584
+ let changed = false;
1585
+ if (!content.includes("InertiaModule")) {
1586
+ const insertAt = findAfterLastImport(content);
1587
+ if (insertAt > 0) {
1588
+ content = `${content.slice(0, insertAt)}import { InertiaModule } from '@dudousxd/nestjs-inertia';
1589
+ ${content.slice(insertAt)}`;
1590
+ }
1591
+ const importsMatch = content.match(/imports\s*:\s*\[/);
1592
+ if (importsMatch?.index !== void 0) {
1593
+ const bracketPos = content.indexOf("[", importsMatch.index) + 1;
1594
+ const indent = " ";
1595
+ content = `${content.slice(0, bracketPos)}
1596
+ ${indent}InertiaModule.forRoot({
1597
+ ${indent} rootView: '${rootView}',
1598
+ ${indent}}),${content.slice(bracketPos)}`;
1599
+ changed = true;
1600
+ }
1601
+ }
1602
+ if (!content.includes("HomeController")) {
1603
+ const insertAt = findAfterLastImport(content);
1604
+ if (insertAt > 0) {
1605
+ content = `${content.slice(0, insertAt)}import { HomeController } from './home.controller';
1606
+ ${content.slice(insertAt)}`;
1607
+ }
1608
+ const controllersMatch = content.match(/controllers\s*:\s*\[/);
1609
+ if (controllersMatch?.index !== void 0) {
1610
+ const bracketPos = content.indexOf("[", controllersMatch.index) + 1;
1611
+ const indent = " ";
1612
+ content = `${content.slice(0, bracketPos)}
1613
+ ${indent}HomeController,${content.slice(bracketPos)}`;
1614
+ changed = true;
1615
+ }
1616
+ }
1617
+ if (!changed) return "already";
1618
+ (0, import_node_fs.writeFileSync)(filePath, content, "utf8");
1619
+ return "patched";
1620
+ }
1621
+ __name(patchAppModule, "patchAppModule");
1622
+ function patchMainTs(filePath) {
1623
+ let content;
1624
+ try {
1625
+ content = (0, import_node_fs.readFileSync)(filePath, "utf8");
1626
+ } catch {
1627
+ return "skipped";
1628
+ }
1629
+ if (content.includes("setupInertiaVite")) return "already";
1630
+ const insertAt = findAfterLastImport(content);
1631
+ if (insertAt > 0) {
1632
+ content = `${content.slice(0, insertAt)}import { setupInertiaVite } from '@dudousxd/nestjs-inertia-vite';
1633
+ ${content.slice(insertAt)}`;
1634
+ }
1635
+ const createMatch = content.match(/(?:const|let)\s+(\w+)\s*=\s*await\s+NestFactory\.create[^;]+;/);
1636
+ if (!createMatch || createMatch.index === void 0) return "skipped";
1637
+ const appVarName = createMatch[1];
1638
+ const insertAfterPos = createMatch.index + createMatch[0].length;
1639
+ const viteSetup = `
1640
+ // Inertia + Vite integration (dev: HMR middleware, prod: static assets)
1641
+ await setupInertiaVite(${appVarName}, {
1642
+ mode: process.env.NODE_ENV ?? 'development',
1643
+ root: 'inertia',
1644
+ publicDir: 'dist/inertia/client',
1645
+ outDir: 'dist/inertia',
1646
+ });`;
1647
+ content = `${content.slice(0, insertAfterPos)}
1648
+ ${viteSetup}${content.slice(insertAfterPos)}`;
1649
+ (0, import_node_fs.writeFileSync)(filePath, content, "utf8");
1650
+ return "patched";
1651
+ }
1652
+ __name(patchMainTs, "patchMainTs");
1653
+ function configTemplate(framework) {
1654
+ const glob = framework === "react" ? "inertia/pages/**/*.tsx" : framework === "vue" ? "inertia/pages/**/*.vue" : "inertia/pages/**/*.svelte";
1655
+ return `import { defineConfig } from '@dudousxd/nestjs-inertia-codegen';
1656
+
1657
+ export default defineConfig({
1658
+ pages: {
1659
+ glob: '${glob}',
1660
+ },
1661
+ });
1662
+ `;
1663
+ }
1664
+ __name(configTemplate, "configTemplate");
1665
+ var DTS_TEMPLATE = `// Auto-generated by nestjs-inertia-codegen. Commit this file.
1666
+ // Re-run \`nestjs-inertia codegen\` to refresh after adding/removing pages.
1667
+
1668
+ import '.nestjs-inertia/index.js';
1669
+
1670
+ declare module '@dudousxd/nestjs-inertia' {
1671
+ interface InertiaRegistry {
1672
+ pages: import('.nestjs-inertia/pages.js').InertiaPages;
1673
+ shared: import('.nestjs-inertia/shared.js').InertiaSharedProps;
1674
+ routes: import('.nestjs-inertia/routes.js').RouteParamsMap;
1675
+ }
1676
+ }
1677
+ `;
1678
+ function htmlShellTemplate(framework, _engine) {
1679
+ const ext = framework === "react" ? "tsx" : "ts";
1680
+ return `<!DOCTYPE html>
1681
+ <html lang="en">
1682
+ <head>
1683
+ <meta charset="UTF-8" />
1684
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
1685
+ <title>My App</title>
1686
+ @inertiaHead
1687
+ </head>
1688
+ <body>
1689
+ @inertia
1690
+ @vite('inertia/app.${ext}')
1691
+ </body>
1692
+ </html>
1693
+ `;
1694
+ }
1695
+ __name(htmlShellTemplate, "htmlShellTemplate");
1696
+ function viteConfigTemplate(framework) {
1697
+ const pluginOption = `{ ${framework}: true }`;
1698
+ return `import { defineConfig } from 'vite';
1699
+ import nestInertia from '@dudousxd/nestjs-inertia-vite/plugin';
1700
+
1701
+ export default defineConfig({
1702
+ plugins: [nestInertia(${pluginOption})],
1703
+ });
1704
+ `;
1705
+ }
1706
+ __name(viteConfigTemplate, "viteConfigTemplate");
1707
+ function entryPointTemplate(framework) {
1708
+ if (framework === "react") {
1709
+ return `import { createRoot } from 'react-dom/client';
1710
+ import { createInertiaApp } from '@inertiajs/react';
1711
+
1712
+ createInertiaApp({
1713
+ resolve: (name) => {
1714
+ const pages = import.meta.glob('./pages/*.tsx', { eager: true });
1715
+ return (pages as Record<string, unknown>)[\`./pages/\${name}.tsx\`];
1716
+ },
1717
+ setup({ el, App, props }) {
1718
+ createRoot(el!).render(<App {...props} />);
1719
+ },
1720
+ });
1721
+ `;
1722
+ }
1723
+ if (framework === "vue") {
1724
+ return `import { createApp, h } from 'vue';
1725
+ import { createInertiaApp } from '@inertiajs/vue3';
1726
+
1727
+ createInertiaApp({
1728
+ resolve: (name) => {
1729
+ const pages = import.meta.glob('./pages/*.vue', { eager: true });
1730
+ return (pages as Record<string, unknown>)[\`./pages/\${name}.vue\`];
1731
+ },
1732
+ setup({ el, App, props, plugin }) {
1733
+ createApp({ render: () => h(App, props) }).use(plugin).mount(el!);
1734
+ },
1735
+ });
1736
+ `;
1737
+ }
1738
+ return `import { mount } from 'svelte';
1739
+ import { createInertiaApp } from '@inertiajs/svelte';
1740
+
1741
+ createInertiaApp({
1742
+ resolve: (name) => {
1743
+ const pages = import.meta.glob('./pages/*.svelte', { eager: true });
1744
+ return (pages as Record<string, unknown>)[\`./pages/\${name}.svelte\`];
1745
+ },
1746
+ setup({ el, App, props }) {
1747
+ mount(App, { target: el!, props });
1748
+ },
1749
+ });
1750
+ `;
1751
+ }
1752
+ __name(entryPointTemplate, "entryPointTemplate");
1753
+ function samplePageTemplate(framework) {
1754
+ if (framework === "react") {
1755
+ return `export type ComponentProps = {
1756
+ greeting: string;
1757
+ };
1758
+
1759
+ export default function Home({ greeting }: ComponentProps) {
1760
+ return (
1761
+ <main>
1762
+ <h1>{greeting}</h1>
1763
+ <p>Edit this page at <code>inertia/pages/Home.tsx</code></p>
1764
+ </main>
1765
+ );
1766
+ }
1767
+ `;
1768
+ }
1769
+ if (framework === "vue") {
1770
+ return `<script setup lang="ts">
1771
+ defineProps<{ greeting: string }>();
1772
+ </script>
1773
+
1774
+ <template>
1775
+ <main>
1776
+ <h1>{{ greeting }}</h1>
1777
+ <p>Edit this page at <code>inertia/pages/Home.vue</code></p>
1778
+ </main>
1779
+ </template>
1780
+ `;
1781
+ }
1782
+ return `<script lang="ts">
1783
+ let { greeting } = $props<{ greeting: string }>();
1784
+ </script>
1785
+
1786
+ <main>
1787
+ <h1>{greeting}</h1>
1788
+ <p>Edit this page at <code>inertia/pages/Home.svelte</code></p>
1789
+ </main>
1790
+ `;
1791
+ }
1792
+ __name(samplePageTemplate, "samplePageTemplate");
1793
+ var SAMPLE_CONTROLLER = `import { Controller, Get } from '@nestjs/common';
1794
+ import { Inertia } from '@dudousxd/nestjs-inertia';
1795
+
1796
+ @Controller()
1797
+ export class HomeController {
1798
+ @Get('/')
1799
+ @Inertia('Home')
1800
+ index() {
1801
+ return { greeting: 'Welcome to NestJS + Inertia.js!' };
1802
+ }
1803
+ }
1804
+ `;
1805
+ async function runInit(opts = {}) {
1806
+ const cwd = opts.cwd ?? process.cwd();
1807
+ console.log(`
1808
+ ${bold("nestjs-inertia init")}`);
1809
+ let framework = await detectFramework(cwd);
1810
+ if (!framework) {
1811
+ framework = await promptFramework();
1812
+ }
1813
+ const engine = await detectTemplateEngine(cwd);
1814
+ const engineLabel = engine === "html" ? "plain HTML" : engine;
1815
+ const frameworkLabel = framework.charAt(0).toUpperCase() + framework.slice(1);
1816
+ console.log(`
1817
+ Detected: ${bold(`${frameworkLabel} + ${engineLabel}`)}`);
1818
+ const shellFileName = engine === "html" ? "index.html" : `index.${engine === "handlebars" ? "hbs" : engine}`;
1819
+ const entryExt = framework === "react" ? "tsx" : "ts";
1820
+ const pageExt = framework === "react" ? "tsx" : framework === "vue" ? "vue" : "svelte";
1821
+ logSection("Scaffold files");
1822
+ await writeIfNotExists((0, import_node_path11.join)(cwd, "nestjs-inertia.config.ts"), configTemplate(framework), "nestjs-inertia.config.ts");
1823
+ await writeIfNotExists((0, import_node_path11.join)(cwd, "nestjs-inertia.d.ts"), DTS_TEMPLATE, "nestjs-inertia.d.ts");
1824
+ await writeIfNotExists((0, import_node_path11.join)(cwd, "inertia", shellFileName), htmlShellTemplate(framework, engine), `inertia/${shellFileName}`);
1825
+ await handleViteConfig(cwd, framework);
1826
+ await writeIfNotExists((0, import_node_path11.join)(cwd, "inertia", `app.${entryExt}`), entryPointTemplate(framework), `inertia/app.${entryExt}`);
1827
+ await writeIfNotExists((0, import_node_path11.join)(cwd, "inertia", "pages", `Home.${pageExt}`), samplePageTemplate(framework), `inertia/pages/Home.${pageExt}`);
1828
+ await writeIfNotExists((0, import_node_path11.join)(cwd, "src", "home.controller.ts"), SAMPLE_CONTROLLER, "src/home.controller.ts");
1829
+ logSection("Patch existing files");
1830
+ const rootView = engine === "html" ? "inertia/index.html" : `inertia/index.${engine === "handlebars" ? "hbs" : engine}`;
1831
+ const appModulePath = (0, import_node_path11.join)(cwd, "src", "app.module.ts");
1832
+ const appModuleResult = patchAppModule(appModulePath, rootView);
1833
+ if (appModuleResult === "patched") {
1834
+ logPatched("src/app.module.ts", "added InertiaModule.forRoot");
1835
+ logPatched("src/app.module.ts", "added HomeController to controllers");
1836
+ } else if (appModuleResult === "already") {
1837
+ console.log(` ${cyan("\u2192")} src/app.module.ts ${dim("(InertiaModule already registered, skipped)")}`);
1838
+ } else {
1839
+ logWarning("src/app.module.ts not found \u2014 add InertiaModule.forRoot() manually");
1840
+ }
1841
+ const mainTsPath = (0, import_node_path11.join)(cwd, "src", "main.ts");
1842
+ const mainTsResult = patchMainTs(mainTsPath);
1843
+ if (mainTsResult === "patched") {
1844
+ logPatched("src/main.ts", "added setupInertiaVite after NestFactory.create");
1845
+ } else if (mainTsResult === "already") {
1846
+ console.log(` ${cyan("\u2192")} src/main.ts ${dim("(setupInertiaVite already present, skipped)")}`);
1847
+ } else {
1848
+ logWarning("src/main.ts not found \u2014 add setupInertiaVite() manually");
1849
+ }
1850
+ await patchGitignore((0, import_node_path11.join)(cwd, ".gitignore"));
1851
+ await patchPackageJsonScripts(cwd, {
1852
+ "build:client": "vite build",
1853
+ "build:ssr": "VITE_SSR=1 vite build --ssr"
1854
+ });
1855
+ logSection("Install dependencies");
1856
+ const pkg = await readPackageJson(cwd);
1857
+ const installedDeps = allDeps(pkg);
1858
+ const pkgManager = await detectPackageManager(cwd);
1859
+ const commonDeps = [
1860
+ "vite"
1861
+ ].filter((d) => !installedDeps.includes(d));
1862
+ let frameworkDeps = [];
1863
+ let frameworkDevDeps = [];
1864
+ if (framework === "react") {
1865
+ const needed = [
1866
+ "@inertiajs/react",
1867
+ "react",
1868
+ "react-dom"
1869
+ ].filter((d) => !installedDeps.includes(d));
1870
+ const neededDev = [
1871
+ "@types/react",
1872
+ "@types/react-dom",
1873
+ "@vitejs/plugin-react"
1874
+ ].filter((d) => !installedDeps.includes(d));
1875
+ frameworkDeps = needed;
1876
+ frameworkDevDeps = neededDev;
1877
+ } else if (framework === "vue") {
1878
+ const needed = [
1879
+ "@inertiajs/vue3",
1880
+ "vue"
1881
+ ].filter((d) => !installedDeps.includes(d));
1882
+ const neededDev = [
1883
+ "@vitejs/plugin-vue"
1884
+ ].filter((d) => !installedDeps.includes(d));
1885
+ frameworkDeps = needed;
1886
+ frameworkDevDeps = neededDev;
1887
+ } else {
1888
+ const needed = [
1889
+ "@inertiajs/svelte",
1890
+ "svelte"
1891
+ ].filter((d) => !installedDeps.includes(d));
1892
+ const neededDev = [
1893
+ "@sveltejs/vite-plugin-svelte"
1894
+ ].filter((d) => !installedDeps.includes(d));
1895
+ frameworkDeps = needed;
1896
+ frameworkDevDeps = neededDev;
1897
+ }
1898
+ const depsToInstall = [
1899
+ ...commonDeps,
1900
+ ...frameworkDeps
1901
+ ];
1902
+ const devDepsToInstall = frameworkDevDeps;
1903
+ if (!opts.skipInstall) {
1904
+ installDeps(pkgManager, depsToInstall, false);
1905
+ installDeps(pkgManager, devDepsToInstall, true);
1906
+ }
1907
+ console.log(`
1908
+ ${green("\u2713")} Setup complete! Run: ${bold("nest start --watch")}
1909
+ `);
1910
+ }
1911
+ __name(runInit, "runInit");
1912
+
1913
+ // src/cli/main.ts
1914
+ async function run(argv) {
1915
+ const cli = (0, import_cac.cac)("nestjs-inertia");
1916
+ cli.command("codegen", "Generate typed artifacts from your NestJS + Inertia app").option("--watch", "Watch for file changes and re-generate automatically").action(async (opts) => {
1917
+ await runCodegen({
1918
+ watch: Boolean(opts.watch),
1919
+ cwd: process.cwd()
1920
+ });
1921
+ });
1922
+ cli.command("init", "Initialise nestjs-inertia-codegen in the current project").action(async () => {
1923
+ await runInit({
1924
+ cwd: process.cwd()
1925
+ });
1926
+ });
1927
+ cli.help();
1928
+ cli.version(VERSION);
1929
+ try {
1930
+ cli.parse([
1931
+ "node",
1932
+ "nestjs-inertia",
1933
+ ...argv
1934
+ ], {
1935
+ run: false
1936
+ });
1937
+ await cli.runMatchedCommand();
1938
+ return 0;
1939
+ } catch (err) {
1940
+ const message = err instanceof Error ? err.message : String(err);
1941
+ console.error(`[nestjs-inertia] Error: ${message}`);
1942
+ return 1;
1943
+ }
1944
+ }
1945
+ __name(run, "run");
1946
+ // Annotate the CommonJS export names for ESM import in node:
1947
+ 0 && (module.exports = {
1948
+ run
1949
+ });
1950
+ //# sourceMappingURL=main.cjs.map