@dudousxd/nestjs-inertia-codegen 1.2.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/dist/cli/main.cjs +467 -104
- package/dist/cli/main.cjs.map +1 -1
- package/dist/cli/main.js +458 -95
- package/dist/cli/main.js.map +1 -1
- package/dist/index.cjs +259 -68
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -1
- package/dist/index.d.ts +10 -1
- package/dist/index.js +257 -66
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.d.cts
CHANGED
|
@@ -78,6 +78,11 @@ interface ContractSource {
|
|
|
78
78
|
interface ContractDescriptor {
|
|
79
79
|
contractSource: ContractSource;
|
|
80
80
|
}
|
|
81
|
+
interface ControllerRef {
|
|
82
|
+
className: string;
|
|
83
|
+
methodName: string;
|
|
84
|
+
filePath: string;
|
|
85
|
+
}
|
|
81
86
|
interface RouteDescriptor {
|
|
82
87
|
method: string;
|
|
83
88
|
path: string;
|
|
@@ -87,6 +92,7 @@ interface RouteDescriptor {
|
|
|
87
92
|
source: 'path' | 'query' | 'body' | 'header';
|
|
88
93
|
}>;
|
|
89
94
|
contract?: ContractDescriptor;
|
|
95
|
+
controllerRef?: ControllerRef;
|
|
90
96
|
}
|
|
91
97
|
|
|
92
98
|
/**
|
|
@@ -121,6 +127,9 @@ declare function watch(config: ResolvedConfig, onChange?: () => void): Promise<W
|
|
|
121
127
|
/**
|
|
122
128
|
* Try to acquire an exclusive lock for a watcher in `outDir`.
|
|
123
129
|
*
|
|
130
|
+
* Uses O_CREAT | O_EXCL (via 'wx' flag) for atomic file creation to prevent
|
|
131
|
+
* TOCTOU race conditions between concurrent processes.
|
|
132
|
+
*
|
|
124
133
|
* Returns `{ release }` on success.
|
|
125
134
|
* Returns `null` if another live process already holds the lock.
|
|
126
135
|
*/
|
|
@@ -128,6 +137,6 @@ declare function acquireLock(outDir: string): Promise<{
|
|
|
128
137
|
release: () => Promise<void>;
|
|
129
138
|
} | null>;
|
|
130
139
|
|
|
131
|
-
declare const VERSION = "1.
|
|
140
|
+
declare const VERSION = "1.4.0";
|
|
132
141
|
|
|
133
142
|
export { CodegenError, ConfigError, type ResolvedConfig, type ScopeConfig, type UserConfig, VERSION, type Watcher, acquireLock, defineConfig, generate, loadConfig, watch };
|
package/dist/index.d.ts
CHANGED
|
@@ -78,6 +78,11 @@ interface ContractSource {
|
|
|
78
78
|
interface ContractDescriptor {
|
|
79
79
|
contractSource: ContractSource;
|
|
80
80
|
}
|
|
81
|
+
interface ControllerRef {
|
|
82
|
+
className: string;
|
|
83
|
+
methodName: string;
|
|
84
|
+
filePath: string;
|
|
85
|
+
}
|
|
81
86
|
interface RouteDescriptor {
|
|
82
87
|
method: string;
|
|
83
88
|
path: string;
|
|
@@ -87,6 +92,7 @@ interface RouteDescriptor {
|
|
|
87
92
|
source: 'path' | 'query' | 'body' | 'header';
|
|
88
93
|
}>;
|
|
89
94
|
contract?: ContractDescriptor;
|
|
95
|
+
controllerRef?: ControllerRef;
|
|
90
96
|
}
|
|
91
97
|
|
|
92
98
|
/**
|
|
@@ -121,6 +127,9 @@ declare function watch(config: ResolvedConfig, onChange?: () => void): Promise<W
|
|
|
121
127
|
/**
|
|
122
128
|
* Try to acquire an exclusive lock for a watcher in `outDir`.
|
|
123
129
|
*
|
|
130
|
+
* Uses O_CREAT | O_EXCL (via 'wx' flag) for atomic file creation to prevent
|
|
131
|
+
* TOCTOU race conditions between concurrent processes.
|
|
132
|
+
*
|
|
124
133
|
* Returns `{ release }` on success.
|
|
125
134
|
* Returns `null` if another live process already holds the lock.
|
|
126
135
|
*/
|
|
@@ -128,6 +137,6 @@ declare function acquireLock(outDir: string): Promise<{
|
|
|
128
137
|
release: () => Promise<void>;
|
|
129
138
|
} | null>;
|
|
130
139
|
|
|
131
|
-
declare const VERSION = "1.
|
|
140
|
+
declare const VERSION = "1.4.0";
|
|
132
141
|
|
|
133
142
|
export { CodegenError, ConfigError, type ResolvedConfig, type ScopeConfig, type UserConfig, VERSION, type Watcher, acquireLock, defineConfig, generate, loadConfig, watch };
|
package/dist/index.js
CHANGED
|
@@ -228,16 +228,6 @@ function validateNameSegment(seg, fullName) {
|
|
|
228
228
|
}
|
|
229
229
|
}
|
|
230
230
|
__name(validateNameSegment, "validateNameSegment");
|
|
231
|
-
function detectCollisions(tree, name) {
|
|
232
|
-
for (const [key, node] of tree) {
|
|
233
|
-
if (node.kind === "leaf") {
|
|
234
|
-
} else {
|
|
235
|
-
void key;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
void name;
|
|
239
|
-
}
|
|
240
|
-
__name(detectCollisions, "detectCollisions");
|
|
241
231
|
function insertIntoTree(tree, segments, leaf, fullName) {
|
|
242
232
|
const head = segments[0];
|
|
243
233
|
const rest = segments.slice(1);
|
|
@@ -267,7 +257,30 @@ function insertIntoTree(tree, segments, leaf, fullName) {
|
|
|
267
257
|
}
|
|
268
258
|
}
|
|
269
259
|
__name(insertIntoTree, "insertIntoTree");
|
|
270
|
-
function
|
|
260
|
+
function buildParamsType(params) {
|
|
261
|
+
const pathParams = params.filter((p) => p.source === "path");
|
|
262
|
+
if (pathParams.length === 0) return "never";
|
|
263
|
+
return `{ ${pathParams.map((p) => `${p.name}: string`).join("; ")} }`;
|
|
264
|
+
}
|
|
265
|
+
__name(buildParamsType, "buildParamsType");
|
|
266
|
+
function hasPathParams(params) {
|
|
267
|
+
return params.some((p) => p.source === "path");
|
|
268
|
+
}
|
|
269
|
+
__name(hasPathParams, "hasPathParams");
|
|
270
|
+
function buildResponseType(c, outDir) {
|
|
271
|
+
if (c.controllerRef) {
|
|
272
|
+
let relPath = relative3(outDir, c.controllerRef.filePath).replace(/\.ts$/, "");
|
|
273
|
+
if (!relPath.startsWith(".")) relPath = `./${relPath}`;
|
|
274
|
+
return `Awaited<ReturnType<import('${relPath}').${c.controllerRef.className}['${c.controllerRef.methodName}']>>`;
|
|
275
|
+
}
|
|
276
|
+
const respRef = c.contractSource.responseRef;
|
|
277
|
+
if (respRef) {
|
|
278
|
+
return respRef.isArray ? `Array<${respRef.name}>` : respRef.name;
|
|
279
|
+
}
|
|
280
|
+
return c.contractSource.response;
|
|
281
|
+
}
|
|
282
|
+
__name(buildResponseType, "buildResponseType");
|
|
283
|
+
function emitRouterTypeBlock(tree, indent, outDir) {
|
|
271
284
|
const pad = " ".repeat(indent);
|
|
272
285
|
const lines = [];
|
|
273
286
|
for (const [key, node] of tree) {
|
|
@@ -279,14 +292,14 @@ function emitRouterTypeBlock(tree, indent) {
|
|
|
279
292
|
const query = queryRef ? queryRef.isArray ? `Array<${queryRef.name}>` : queryRef.name : c.contractSource.query ?? "never";
|
|
280
293
|
const bodyRef = c.contractSource.bodyRef;
|
|
281
294
|
const body = method === "GET" ? "never" : bodyRef ? bodyRef.isArray ? `Array<${bodyRef.name}>` : bodyRef.name : c.contractSource.body ?? "never";
|
|
282
|
-
const
|
|
283
|
-
const
|
|
295
|
+
const response = buildResponseType(c, outDir);
|
|
296
|
+
const params = buildParamsType(c.params);
|
|
284
297
|
const safeMethod = JSON.stringify(method);
|
|
285
298
|
const safeUrl = JSON.stringify(c.path);
|
|
286
|
-
lines.push(`${pad}${objKey}: { method: ${safeMethod}; url: ${safeUrl}; query: ${query}; body: ${body}; response: ${response} };`);
|
|
299
|
+
lines.push(`${pad}${objKey}: { method: ${safeMethod}; url: ${safeUrl}; params: ${params}; query: ${query}; body: ${body}; response: ${response} };`);
|
|
287
300
|
} else {
|
|
288
301
|
lines.push(`${pad}${objKey}: {`);
|
|
289
|
-
lines.push(...emitRouterTypeBlock(node.children, indent + 2));
|
|
302
|
+
lines.push(...emitRouterTypeBlock(node.children, indent + 2, outDir));
|
|
290
303
|
lines.push(`${pad}};`);
|
|
291
304
|
}
|
|
292
305
|
}
|
|
@@ -306,20 +319,61 @@ function emitApiObjectBlock(tree, indent) {
|
|
|
306
319
|
const fetcherMethod = method.toLowerCase();
|
|
307
320
|
if (method === "GET") {
|
|
308
321
|
const typeAccess = buildRouterTypeAccess(c.name);
|
|
322
|
+
const withParams = hasPathParams(c.params);
|
|
309
323
|
lines.push(`${pad}${objKey}: {`);
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
324
|
+
if (withParams) {
|
|
325
|
+
lines.push(`${pad} queryKey: (params: ${typeAccess}['params'], query?: ${typeAccess}['query']) => query !== undefined ? [${flatName}, params, query] as const : [${flatName}, params] as const,`);
|
|
326
|
+
lines.push(`${pad} queryOptions: (params: ${typeAccess}['params'], query?: ${typeAccess}['query']) =>`);
|
|
327
|
+
lines.push(`${pad} _queryOptions({`);
|
|
328
|
+
lines.push(`${pad} queryKey: query !== undefined ? [${flatName}, params, query] as const : [${flatName}, params] as const,`);
|
|
329
|
+
lines.push(`${pad} queryFn: () => fetcher.get<${typeAccess}['response']>(route(${flatName} as never, params as never) || ${safePath}, { query }),`);
|
|
330
|
+
lines.push(`${pad} }),`);
|
|
331
|
+
lines.push(`${pad} infiniteQueryOptions: (params: ${typeAccess}['params'], query?: ${typeAccess}['query']) => ({`);
|
|
332
|
+
lines.push(`${pad} queryKey: query !== undefined ? [${flatName}, params, query] as const : [${flatName}, params] as const,`);
|
|
333
|
+
lines.push(`${pad} queryFn: ({ pageParam }: { pageParam: number }) => fetcher.get<${typeAccess}['response']>(route(${flatName} as never, params as never) || ${safePath}, { query: { ...query, page: pageParam } }),`);
|
|
334
|
+
lines.push(`${pad} initialPageParam: 1,`);
|
|
335
|
+
lines.push(`${pad} getNextPageParam: (lastPage: ${typeAccess}['response']) => {`);
|
|
336
|
+
lines.push(`${pad} const meta = (lastPage as any)?.meta;`);
|
|
337
|
+
lines.push(`${pad} if (meta?.page != null && meta?.lastPage != null) {`);
|
|
338
|
+
lines.push(`${pad} return meta.page < meta.lastPage ? meta.page + 1 : undefined;`);
|
|
339
|
+
lines.push(`${pad} }`);
|
|
340
|
+
lines.push(`${pad} return undefined;`);
|
|
341
|
+
lines.push(`${pad} },`);
|
|
342
|
+
lines.push(`${pad} }),`);
|
|
343
|
+
} else {
|
|
344
|
+
lines.push(`${pad} queryKey: (query?: ${typeAccess}['query']) => query !== undefined ? [${flatName}, query] as const : [${flatName}] as const,`);
|
|
345
|
+
lines.push(`${pad} queryOptions: (query?: ${typeAccess}['query']) =>`);
|
|
346
|
+
lines.push(`${pad} _queryOptions({`);
|
|
347
|
+
lines.push(`${pad} queryKey: query !== undefined ? [${flatName}, query] as const : [${flatName}] as const,`);
|
|
348
|
+
lines.push(`${pad} queryFn: () => fetcher.get<${typeAccess}['response']>(route(${flatName} as never) || ${safePath}, { query }),`);
|
|
349
|
+
lines.push(`${pad} }),`);
|
|
350
|
+
lines.push(`${pad} infiniteQueryOptions: (query?: ${typeAccess}['query']) => ({`);
|
|
351
|
+
lines.push(`${pad} queryKey: query !== undefined ? [${flatName}, query] as const : [${flatName}] as const,`);
|
|
352
|
+
lines.push(`${pad} queryFn: ({ pageParam }: { pageParam: number }) => fetcher.get<${typeAccess}['response']>(route(${flatName} as never) || ${safePath}, { query: { ...query, page: pageParam } }),`);
|
|
353
|
+
lines.push(`${pad} initialPageParam: 1,`);
|
|
354
|
+
lines.push(`${pad} getNextPageParam: (lastPage: ${typeAccess}['response']) => {`);
|
|
355
|
+
lines.push(`${pad} const meta = (lastPage as any)?.meta;`);
|
|
356
|
+
lines.push(`${pad} if (meta?.page != null && meta?.lastPage != null) {`);
|
|
357
|
+
lines.push(`${pad} return meta.page < meta.lastPage ? meta.page + 1 : undefined;`);
|
|
358
|
+
lines.push(`${pad} }`);
|
|
359
|
+
lines.push(`${pad} return undefined;`);
|
|
360
|
+
lines.push(`${pad} },`);
|
|
361
|
+
lines.push(`${pad} }),`);
|
|
362
|
+
}
|
|
315
363
|
lines.push(`${pad}},`);
|
|
316
364
|
} else {
|
|
317
365
|
const typeAccess = buildRouterTypeAccess(c.name);
|
|
366
|
+
const withParams = hasPathParams(c.params);
|
|
318
367
|
lines.push(`${pad}${objKey}: {`);
|
|
319
368
|
lines.push(`${pad} queryKey: () => [${flatName}] as const,`);
|
|
320
|
-
lines.push(`${pad} mutationOptions: ()
|
|
321
|
-
lines.push(`${pad}
|
|
322
|
-
|
|
369
|
+
lines.push(`${pad} mutationOptions: () =>`);
|
|
370
|
+
lines.push(`${pad} _mutationOptions({`);
|
|
371
|
+
if (withParams) {
|
|
372
|
+
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 }),`);
|
|
373
|
+
} else {
|
|
374
|
+
lines.push(`${pad} mutationFn: (body: ${typeAccess}['body']) => fetcher.${fetcherMethod}<${typeAccess}['response']>(route(${flatName} as never) || ${safePath}, { body }),`);
|
|
375
|
+
}
|
|
376
|
+
lines.push(`${pad} }),`);
|
|
323
377
|
lines.push(`${pad}},`);
|
|
324
378
|
}
|
|
325
379
|
} else {
|
|
@@ -342,11 +396,15 @@ function buildApiFile(routes, outDir) {
|
|
|
342
396
|
for (const r of contracted) {
|
|
343
397
|
const cs = r.contract?.contractSource;
|
|
344
398
|
if (!cs) continue;
|
|
345
|
-
|
|
399
|
+
const refs = r.controllerRef ? [
|
|
400
|
+
cs.queryRef,
|
|
401
|
+
cs.bodyRef
|
|
402
|
+
] : [
|
|
346
403
|
cs.queryRef,
|
|
347
404
|
cs.bodyRef,
|
|
348
405
|
cs.responseRef
|
|
349
|
-
]
|
|
406
|
+
];
|
|
407
|
+
for (const ref of refs) {
|
|
350
408
|
if (!ref) continue;
|
|
351
409
|
let names = importsByFile.get(ref.filePath);
|
|
352
410
|
if (!names) {
|
|
@@ -356,10 +414,18 @@ function buildApiFile(routes, outDir) {
|
|
|
356
414
|
names.add(ref.name);
|
|
357
415
|
}
|
|
358
416
|
}
|
|
417
|
+
const hasGetRoutes = contracted.some((r) => r.method === "GET");
|
|
418
|
+
const hasMutationRoutes = contracted.some((r) => r.method !== "GET");
|
|
359
419
|
const lines = [
|
|
360
420
|
"// Generated by @dudousxd/nestjs-inertia-codegen. Do not edit.",
|
|
361
421
|
""
|
|
362
422
|
];
|
|
423
|
+
const tqImports = [];
|
|
424
|
+
if (hasGetRoutes) tqImports.push("queryOptions as _queryOptions");
|
|
425
|
+
if (hasMutationRoutes) tqImports.push("mutationOptions as _mutationOptions");
|
|
426
|
+
if (tqImports.length > 0) {
|
|
427
|
+
lines.push(`import { ${tqImports.join(", ")} } from '@tanstack/react-query';`);
|
|
428
|
+
}
|
|
363
429
|
lines.push("import { route } from './routes.js';");
|
|
364
430
|
lines.push("import { createFetcher } from '@dudousxd/nestjs-inertia-client';");
|
|
365
431
|
if (importsByFile.size > 0 && outDir) {
|
|
@@ -413,13 +479,14 @@ function buildApiFile(routes, outDir) {
|
|
|
413
479
|
method: r.method,
|
|
414
480
|
name,
|
|
415
481
|
path: r.path,
|
|
482
|
+
params: r.params,
|
|
483
|
+
controllerRef: r.controllerRef,
|
|
416
484
|
contractSource: c.contractSource
|
|
417
485
|
};
|
|
418
486
|
insertIntoTree(tree, segments, leaf, name);
|
|
419
487
|
}
|
|
420
|
-
void detectCollisions;
|
|
421
488
|
lines.push("export type ApiRouter = {");
|
|
422
|
-
lines.push(...emitRouterTypeBlock(tree, 2));
|
|
489
|
+
lines.push(...emitRouterTypeBlock(tree, 2, outDir ?? ""));
|
|
423
490
|
lines.push("};");
|
|
424
491
|
lines.push("");
|
|
425
492
|
lines.push("export const api = {");
|
|
@@ -515,8 +582,9 @@ __name(emitIndex, "emitIndex");
|
|
|
515
582
|
|
|
516
583
|
// src/emit/emit-pages.ts
|
|
517
584
|
import { mkdir as mkdir4, writeFile as writeFile4 } from "fs/promises";
|
|
518
|
-
import { join as join5 } from "path";
|
|
519
|
-
async function emitPages(pages, outDir) {
|
|
585
|
+
import { join as join5, relative as relative4 } from "path";
|
|
586
|
+
async function emitPages(pages, outDir, options = {}) {
|
|
587
|
+
const propsExport = options.propsExport ?? "ComponentProps";
|
|
520
588
|
await mkdir4(outDir, {
|
|
521
589
|
recursive: true
|
|
522
590
|
});
|
|
@@ -525,14 +593,40 @@ async function emitPages(pages, outDir) {
|
|
|
525
593
|
const key = needsQuotes(p.name) ? JSON.stringify(p.name) : p.name;
|
|
526
594
|
return ` ${key}: ${propType};`;
|
|
527
595
|
}).join("\n");
|
|
596
|
+
const pageNameUnion = pages.length > 0 ? pages.map((p) => JSON.stringify(p.name)).join(" | ") : "never";
|
|
597
|
+
const augBody = pages.map((p) => {
|
|
598
|
+
const key = needsQuotes(p.name) ? JSON.stringify(p.name) : p.name;
|
|
599
|
+
const valueType = buildAugmentationType(p, outDir, propsExport);
|
|
600
|
+
return ` ${key}: ${valueType};`;
|
|
601
|
+
}).join("\n");
|
|
602
|
+
const propsHelper = "\nexport type InertiaProps<K extends InertiaPageName> = import('@dudousxd/nestjs-inertia').InertiaPages[K];\n";
|
|
528
603
|
const content = `// Generated by @dudousxd/nestjs-inertia-codegen. Do not edit.
|
|
529
604
|
export interface InertiaPages {
|
|
530
605
|
${body}
|
|
531
606
|
}
|
|
607
|
+
|
|
608
|
+
export type InertiaPageName = ${pageNameUnion};
|
|
609
|
+
` + propsHelper + `
|
|
610
|
+
declare module '@dudousxd/nestjs-inertia' {
|
|
611
|
+
interface InertiaPages {
|
|
612
|
+
${augBody}
|
|
613
|
+
}
|
|
614
|
+
}
|
|
532
615
|
`;
|
|
533
616
|
await writeFile4(join5(outDir, "pages.d.ts"), content, "utf8");
|
|
534
617
|
}
|
|
535
618
|
__name(emitPages, "emitPages");
|
|
619
|
+
function buildAugmentationType(page, outDir, propsExport) {
|
|
620
|
+
if (!page.propsSource) {
|
|
621
|
+
return "Record<string, unknown>";
|
|
622
|
+
}
|
|
623
|
+
let importPath = relative4(outDir, page.absolutePath).replace(/\.(tsx?|vue|svelte)$/, "");
|
|
624
|
+
if (!importPath.startsWith(".")) {
|
|
625
|
+
importPath = `./${importPath}`;
|
|
626
|
+
}
|
|
627
|
+
return `import('${importPath}').${propsExport}`;
|
|
628
|
+
}
|
|
629
|
+
__name(buildAugmentationType, "buildAugmentationType");
|
|
536
630
|
function needsQuotes(name) {
|
|
537
631
|
return !/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name);
|
|
538
632
|
}
|
|
@@ -669,7 +763,9 @@ async function generate(config, routes = []) {
|
|
|
669
763
|
propsExport: config.pages.propsExport,
|
|
670
764
|
componentNameStrategy: config.pages.componentNameStrategy
|
|
671
765
|
});
|
|
672
|
-
await emitPages(pages, config.codegen.outDir
|
|
766
|
+
await emitPages(pages, config.codegen.outDir, {
|
|
767
|
+
propsExport: config.pages.propsExport
|
|
768
|
+
});
|
|
673
769
|
await emitCache(pages, config.codegen.outDir);
|
|
674
770
|
const hasRoutes = routes.length > 0;
|
|
675
771
|
const hasContracts = routes.some((r) => r.contract);
|
|
@@ -689,9 +785,38 @@ import { join as join9 } from "path";
|
|
|
689
785
|
import chokidar from "chokidar";
|
|
690
786
|
|
|
691
787
|
// src/discovery/contracts-fast.ts
|
|
788
|
+
import { readFileSync } from "fs";
|
|
692
789
|
import { dirname, join as join7, resolve as resolve2 } from "path";
|
|
693
790
|
import fg2 from "fast-glob";
|
|
694
791
|
import { Node, Project, SyntaxKind } from "ts-morph";
|
|
792
|
+
var _ctx = {
|
|
793
|
+
projectRoot: "",
|
|
794
|
+
tsconfigPaths: null
|
|
795
|
+
};
|
|
796
|
+
function _projectRoot() {
|
|
797
|
+
return _ctx.projectRoot;
|
|
798
|
+
}
|
|
799
|
+
__name(_projectRoot, "_projectRoot");
|
|
800
|
+
function _tsconfigPaths() {
|
|
801
|
+
return _ctx.tsconfigPaths;
|
|
802
|
+
}
|
|
803
|
+
__name(_tsconfigPaths, "_tsconfigPaths");
|
|
804
|
+
var _debug = process.env.NESTJS_INERTIA_DEBUG === "1";
|
|
805
|
+
function dbg(...args) {
|
|
806
|
+
if (_debug) console.log("[codegen:debug]", ...args);
|
|
807
|
+
}
|
|
808
|
+
__name(dbg, "dbg");
|
|
809
|
+
function loadTsconfigPaths(tsconfigPath) {
|
|
810
|
+
try {
|
|
811
|
+
const raw = readFileSync(tsconfigPath, "utf8");
|
|
812
|
+
const stripped = raw.replace(/\/\/.*$/gm, "");
|
|
813
|
+
const parsed = JSON.parse(stripped);
|
|
814
|
+
return parsed.compilerOptions?.paths ?? null;
|
|
815
|
+
} catch {
|
|
816
|
+
return null;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
__name(loadTsconfigPaths, "loadTsconfigPaths");
|
|
695
820
|
async function discoverContractsFast(opts) {
|
|
696
821
|
const { cwd, glob, tsconfig } = opts;
|
|
697
822
|
const tsconfigPath = tsconfig ? resolve2(tsconfig) : join7(cwd, "tsconfig.json");
|
|
@@ -724,8 +849,17 @@ async function discoverContractsFast(opts) {
|
|
|
724
849
|
project.addSourceFileAtPath(f);
|
|
725
850
|
}
|
|
726
851
|
const routes = [];
|
|
727
|
-
|
|
728
|
-
|
|
852
|
+
const prevCtx = _ctx;
|
|
853
|
+
_ctx = {
|
|
854
|
+
projectRoot: cwd,
|
|
855
|
+
tsconfigPaths: loadTsconfigPaths(tsconfigPath)
|
|
856
|
+
};
|
|
857
|
+
try {
|
|
858
|
+
for (const sourceFile of project.getSourceFiles()) {
|
|
859
|
+
routes.push(...extractFromSourceFile(sourceFile, project));
|
|
860
|
+
}
|
|
861
|
+
} finally {
|
|
862
|
+
_ctx = prevCtx;
|
|
729
863
|
}
|
|
730
864
|
return routes;
|
|
731
865
|
}
|
|
@@ -925,17 +1059,42 @@ function findTypeInFile(name, file) {
|
|
|
925
1059
|
return null;
|
|
926
1060
|
}
|
|
927
1061
|
__name(findTypeInFile, "findTypeInFile");
|
|
1062
|
+
function resolveModuleSpecifier(moduleSpecifier, sourceFile, project) {
|
|
1063
|
+
if (moduleSpecifier.startsWith(".")) {
|
|
1064
|
+
const dir = dirname(sourceFile.getFilePath());
|
|
1065
|
+
return [
|
|
1066
|
+
resolve2(dir, `${moduleSpecifier}.ts`),
|
|
1067
|
+
resolve2(dir, moduleSpecifier, "index.ts")
|
|
1068
|
+
];
|
|
1069
|
+
}
|
|
1070
|
+
const baseUrl = _projectRoot();
|
|
1071
|
+
const tsconfigPaths = _tsconfigPaths();
|
|
1072
|
+
dbg("resolveModuleSpecifier", moduleSpecifier, "paths:", JSON.stringify(tsconfigPaths), "baseUrl:", baseUrl);
|
|
1073
|
+
if (tsconfigPaths) {
|
|
1074
|
+
for (const [pattern, mappings] of Object.entries(tsconfigPaths)) {
|
|
1075
|
+
const prefix = pattern.replace("*", "");
|
|
1076
|
+
if (moduleSpecifier.startsWith(prefix)) {
|
|
1077
|
+
const rest = moduleSpecifier.slice(prefix.length);
|
|
1078
|
+
const candidates = [];
|
|
1079
|
+
for (const mapping of mappings) {
|
|
1080
|
+
const resolved = resolve2(baseUrl, mapping.replace("*", rest));
|
|
1081
|
+
candidates.push(`${resolved}.ts`, resolve2(resolved, "index.ts"));
|
|
1082
|
+
}
|
|
1083
|
+
dbg(" resolved candidates:", candidates);
|
|
1084
|
+
return candidates;
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
return [];
|
|
1089
|
+
}
|
|
1090
|
+
__name(resolveModuleSpecifier, "resolveModuleSpecifier");
|
|
928
1091
|
function resolveImportedType(name, sourceFile, project) {
|
|
929
1092
|
for (const importDecl of sourceFile.getImportDeclarations()) {
|
|
930
1093
|
const namedImport = importDecl.getNamedImports().find((n) => n.getName() === name);
|
|
931
1094
|
if (!namedImport) continue;
|
|
932
1095
|
const moduleSpecifier = importDecl.getModuleSpecifierValue();
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
const candidates = [
|
|
936
|
-
resolve2(dir, `${moduleSpecifier}.ts`),
|
|
937
|
-
resolve2(dir, moduleSpecifier, "index.ts")
|
|
938
|
-
];
|
|
1096
|
+
const candidates = resolveModuleSpecifier(moduleSpecifier, sourceFile, project);
|
|
1097
|
+
if (candidates.length === 0) continue;
|
|
939
1098
|
for (const candidate of candidates) {
|
|
940
1099
|
let importedFile = project.getSourceFile(candidate);
|
|
941
1100
|
if (!importedFile) {
|
|
@@ -979,6 +1138,18 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth) {
|
|
|
979
1138
|
}
|
|
980
1139
|
return "Array<unknown>";
|
|
981
1140
|
}
|
|
1141
|
+
if ([
|
|
1142
|
+
"Record",
|
|
1143
|
+
"Omit",
|
|
1144
|
+
"Pick",
|
|
1145
|
+
"Partial",
|
|
1146
|
+
"Required",
|
|
1147
|
+
"Readonly",
|
|
1148
|
+
"Map",
|
|
1149
|
+
"Set"
|
|
1150
|
+
].includes(name)) {
|
|
1151
|
+
return typeNode.getText();
|
|
1152
|
+
}
|
|
982
1153
|
if (name === "Promise") {
|
|
983
1154
|
const typeArgs = typeNode.getTypeArguments();
|
|
984
1155
|
const firstTypeArg = typeArgs[0];
|
|
@@ -991,7 +1162,8 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth) {
|
|
|
991
1162
|
if (resolved) {
|
|
992
1163
|
return expandTypeDecl(resolved, project, depth - 1);
|
|
993
1164
|
}
|
|
994
|
-
|
|
1165
|
+
dbg("unresolvable type:", name, "in", sourceFile.getFilePath());
|
|
1166
|
+
return "unknown";
|
|
995
1167
|
}
|
|
996
1168
|
const kind = typeNode.getKind();
|
|
997
1169
|
if (kind === SyntaxKind.StringKeyword) return "string";
|
|
@@ -1153,7 +1325,7 @@ function tryResolveTypeRef(typeNode, sourceFile, project) {
|
|
|
1153
1325
|
return null;
|
|
1154
1326
|
}
|
|
1155
1327
|
const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
|
|
1156
|
-
if (localDecl
|
|
1328
|
+
if (localDecl?.isExported()) {
|
|
1157
1329
|
return {
|
|
1158
1330
|
name,
|
|
1159
1331
|
filePath: sourceFile.getFilePath()
|
|
@@ -1215,7 +1387,7 @@ function extractDtoContract(method, sourceFile, project) {
|
|
|
1215
1387
|
if (val && Node.isIdentifier(val)) {
|
|
1216
1388
|
const name = val.getText();
|
|
1217
1389
|
const localDecl = sourceFile.getInterface(name) || sourceFile.getClass(name) || sourceFile.getTypeAlias(name);
|
|
1218
|
-
if (localDecl
|
|
1390
|
+
if (localDecl?.isExported()) {
|
|
1219
1391
|
responseRef = {
|
|
1220
1392
|
name,
|
|
1221
1393
|
filePath: sourceFile.getFilePath()
|
|
@@ -1341,6 +1513,11 @@ function extractFromSourceFile(sourceFile, project) {
|
|
|
1341
1513
|
path: combined,
|
|
1342
1514
|
name: routeName,
|
|
1343
1515
|
params,
|
|
1516
|
+
controllerRef: {
|
|
1517
|
+
className,
|
|
1518
|
+
methodName,
|
|
1519
|
+
filePath: sourceFile.getFilePath()
|
|
1520
|
+
},
|
|
1344
1521
|
contract: {
|
|
1345
1522
|
contractSource: {
|
|
1346
1523
|
query: contractDef.query,
|
|
@@ -1375,19 +1552,21 @@ function extractFromSourceFile(sourceFile, project) {
|
|
|
1375
1552
|
path: combined,
|
|
1376
1553
|
name: routeName,
|
|
1377
1554
|
params,
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1555
|
+
controllerRef: {
|
|
1556
|
+
className,
|
|
1557
|
+
methodName,
|
|
1558
|
+
filePath: sourceFile.getFilePath()
|
|
1559
|
+
},
|
|
1560
|
+
contract: {
|
|
1561
|
+
contractSource: {
|
|
1562
|
+
query: dtoContract?.query ?? null,
|
|
1563
|
+
body: dtoContract?.body ?? null,
|
|
1564
|
+
response: dtoContract?.response ?? "unknown",
|
|
1565
|
+
queryRef: dtoContract?.queryRef,
|
|
1566
|
+
bodyRef: dtoContract?.bodyRef,
|
|
1567
|
+
responseRef: dtoContract?.responseRef
|
|
1389
1568
|
}
|
|
1390
|
-
}
|
|
1569
|
+
}
|
|
1391
1570
|
});
|
|
1392
1571
|
}
|
|
1393
1572
|
}
|
|
@@ -1397,7 +1576,8 @@ function extractFromSourceFile(sourceFile, project) {
|
|
|
1397
1576
|
__name(extractFromSourceFile, "extractFromSourceFile");
|
|
1398
1577
|
|
|
1399
1578
|
// src/watch/lock-file.ts
|
|
1400
|
-
import {
|
|
1579
|
+
import { open } from "fs/promises";
|
|
1580
|
+
import { mkdir as mkdir6, readFile as readFile2, unlink } from "fs/promises";
|
|
1401
1581
|
import { join as join8 } from "path";
|
|
1402
1582
|
var LOCK_FILE = ".watcher.lock";
|
|
1403
1583
|
function isProcessAlive(pid) {
|
|
@@ -1414,20 +1594,29 @@ async function acquireLock(outDir) {
|
|
|
1414
1594
|
recursive: true
|
|
1415
1595
|
});
|
|
1416
1596
|
const lockPath = join8(outDir, LOCK_FILE);
|
|
1417
|
-
try {
|
|
1418
|
-
const raw = await readFile2(lockPath, "utf8");
|
|
1419
|
-
const existing = JSON.parse(raw);
|
|
1420
|
-
if (isProcessAlive(existing.pid)) {
|
|
1421
|
-
return null;
|
|
1422
|
-
}
|
|
1423
|
-
} catch {
|
|
1424
|
-
}
|
|
1425
1597
|
const lockData = {
|
|
1426
1598
|
pid: process.pid,
|
|
1427
1599
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1428
1600
|
};
|
|
1429
|
-
|
|
1601
|
+
try {
|
|
1602
|
+
const fd = await open(lockPath, "wx");
|
|
1603
|
+
await fd.writeFile(`${JSON.stringify(lockData, null, 2)}
|
|
1430
1604
|
`, "utf8");
|
|
1605
|
+
await fd.close();
|
|
1606
|
+
} catch (err) {
|
|
1607
|
+
if (err.code === "EEXIST") {
|
|
1608
|
+
try {
|
|
1609
|
+
const raw = await readFile2(lockPath, "utf8");
|
|
1610
|
+
const existing = JSON.parse(raw);
|
|
1611
|
+
if (isProcessAlive(existing.pid)) return null;
|
|
1612
|
+
await unlink(lockPath);
|
|
1613
|
+
return acquireLock(outDir);
|
|
1614
|
+
} catch {
|
|
1615
|
+
return null;
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
return null;
|
|
1619
|
+
}
|
|
1431
1620
|
return {
|
|
1432
1621
|
release: /* @__PURE__ */ __name(async () => {
|
|
1433
1622
|
try {
|
|
@@ -1491,7 +1680,8 @@ async function watch(config, onChange) {
|
|
|
1491
1680
|
pagesDebounceTimer = void 0;
|
|
1492
1681
|
try {
|
|
1493
1682
|
await generate(config);
|
|
1494
|
-
} catch {
|
|
1683
|
+
} catch (err) {
|
|
1684
|
+
console.error("[nestjs-inertia-codegen] Pages generation failed:", err instanceof Error ? err.message : err);
|
|
1495
1685
|
}
|
|
1496
1686
|
onChange?.();
|
|
1497
1687
|
}, PAGES_DEBOUNCE_MS);
|
|
@@ -1529,7 +1719,8 @@ async function watch(config, onChange) {
|
|
|
1529
1719
|
if (hasContracts) {
|
|
1530
1720
|
await emitApi(routes, config.codegen.outDir);
|
|
1531
1721
|
}
|
|
1532
|
-
} catch {
|
|
1722
|
+
} catch (err) {
|
|
1723
|
+
console.error("[nestjs-inertia-codegen] Contracts generation failed:", err instanceof Error ? err.message : err);
|
|
1533
1724
|
}
|
|
1534
1725
|
onChange?.();
|
|
1535
1726
|
}, config.contracts.debounceMs);
|
|
@@ -1557,7 +1748,7 @@ async function watch(config, onChange) {
|
|
|
1557
1748
|
__name(watch, "watch");
|
|
1558
1749
|
|
|
1559
1750
|
// src/index.ts
|
|
1560
|
-
var VERSION = "1.
|
|
1751
|
+
var VERSION = "1.4.0";
|
|
1561
1752
|
export {
|
|
1562
1753
|
CodegenError,
|
|
1563
1754
|
ConfigError,
|