@dudousxd/nestjs-codegen 0.26.0 → 0.27.1
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 +38 -0
- package/README.md +2 -2
- package/dist/cli/main.cjs +212 -73
- package/dist/cli/main.cjs.map +1 -1
- package/dist/cli/main.js +213 -74
- package/dist/cli/main.js.map +1 -1
- package/dist/extension/index.d.cts +1 -1
- package/dist/extension/index.d.ts +1 -1
- package/dist/{index-Bbw73Kkb.d.cts → index-Dc4s883u.d.cts} +41 -4
- package/dist/{index-Bbw73Kkb.d.ts → index-Dc4s883u.d.ts} +41 -4
- package/dist/index.cjs +212 -73
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +213 -74
- package/dist/index.js.map +1 -1
- package/dist/nest/index.cjs +216 -77
- package/dist/nest/index.cjs.map +1 -1
- package/dist/nest/index.d.cts +1 -1
- package/dist/nest/index.d.ts +1 -1
- package/dist/nest/index.js +217 -78
- package/dist/nest/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { m as ApiClientLayer, n as ApiHeaderContribution, o as ApiModuleDeps, C as CodegenExtension, p as EmittedFile, E as ExtensionContext, L as LeafModel, q as RequestModel, s as RequestShape, t as defineExtension, u as requestShape } from '../index-
|
|
1
|
+
export { m as ApiClientLayer, n as ApiHeaderContribution, o as ApiModuleDeps, C as CodegenExtension, p as EmittedFile, E as ExtensionContext, L as LeafModel, q as RequestModel, s as RequestShape, t as defineExtension, u as requestShape } from '../index-Dc4s883u.cjs';
|
|
2
2
|
import 'ts-morph';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { m as ApiClientLayer, n as ApiHeaderContribution, o as ApiModuleDeps, C as CodegenExtension, p as EmittedFile, E as ExtensionContext, L as LeafModel, q as RequestModel, s as RequestShape, t as defineExtension, u as requestShape } from '../index-
|
|
1
|
+
export { m as ApiClientLayer, n as ApiHeaderContribution, o as ApiModuleDeps, C as CodegenExtension, p as EmittedFile, E as ExtensionContext, L as LeafModel, q as RequestModel, s as RequestShape, t as defineExtension, u as requestShape } from '../index-Dc4s883u.js';
|
|
2
2
|
import 'ts-morph';
|
|
@@ -238,9 +238,22 @@ interface UserConfig {
|
|
|
238
238
|
validation: ValidationOption;
|
|
239
239
|
/** Inertia page discovery. Omit when you don't use Inertia. */
|
|
240
240
|
pages?: {
|
|
241
|
-
|
|
241
|
+
/**
|
|
242
|
+
* Page files of the default Inertia app (`InertiaModule.forRoot`), relative to `cwd`. One
|
|
243
|
+
* glob or several; a page is named relative to the static base of the glob that matched it
|
|
244
|
+
* (`inertia/pages/users/Show.tsx` under `inertia/pages/**\/*.tsx` → `users/Show`).
|
|
245
|
+
*/
|
|
246
|
+
glob: string | string[];
|
|
242
247
|
propsExport?: string;
|
|
243
|
-
componentNameStrategy?:
|
|
248
|
+
componentNameStrategy?: PageNameStrategy;
|
|
249
|
+
/**
|
|
250
|
+
* Further Inertia apps, one per `InertiaModule.forFeature({ scope })`, keyed by scope name.
|
|
251
|
+
* Each has its own page glob(s); its page names get a prefix (default `'<scope>/'`, so
|
|
252
|
+
* `minimal/pages/Home.tsx` → `minimal/Home`, the name its `@Inertia('minimal/Home')`
|
|
253
|
+
* handlers render). Every page of every scope lands in `InertiaPages`/`InertiaPageName`;
|
|
254
|
+
* `InertiaScopePages` lists each scope's page names.
|
|
255
|
+
*/
|
|
256
|
+
scopes?: Record<string, PageScopeConfig>;
|
|
244
257
|
};
|
|
245
258
|
contracts?: {
|
|
246
259
|
/** Glob pattern (relative to cwd) for controller files. Default: `'src/**\/\*.controller.ts'` */
|
|
@@ -248,6 +261,11 @@ interface UserConfig {
|
|
|
248
261
|
/** Debounce delay in ms before re-running route discovery. Default: `500` */
|
|
249
262
|
debounceMs?: number;
|
|
250
263
|
};
|
|
264
|
+
/**
|
|
265
|
+
* @deprecated Has no effect: discovery never read it. For several Inertia apps use
|
|
266
|
+
* `pages.scopes`; for several controller sets use one `contracts.glob` (brace/extglob).
|
|
267
|
+
* A config that sets it gets a warning.
|
|
268
|
+
*/
|
|
251
269
|
scopes?: Record<string, ScopeConfig>;
|
|
252
270
|
codegen?: {
|
|
253
271
|
outDir?: string;
|
|
@@ -344,14 +362,33 @@ interface UserConfig {
|
|
|
344
362
|
*/
|
|
345
363
|
driftGuard?: boolean;
|
|
346
364
|
}
|
|
365
|
+
/** @deprecated See `UserConfig.scopes`. */
|
|
347
366
|
interface ScopeConfig {
|
|
348
367
|
glob: string;
|
|
349
368
|
prefix?: string;
|
|
350
369
|
}
|
|
370
|
+
/** How a page file's path (relative to its glob's static base) becomes its component name. */
|
|
371
|
+
type PageNameStrategy = 'relative-no-ext' | 'kebab' | ((path: string) => string);
|
|
372
|
+
/** One more Inertia app (an `InertiaModule.forFeature({ scope })`) — see `pages.scopes`. */
|
|
373
|
+
interface PageScopeConfig {
|
|
374
|
+
/** Page files of this app, relative to `cwd`. */
|
|
375
|
+
glob: string | string[];
|
|
376
|
+
/** Prepended to every page name of this scope. Default `'<scope>/'`; `''` for none. */
|
|
377
|
+
prefix?: string;
|
|
378
|
+
/** Naming strategy for this scope's pages. Default: `pages.componentNameStrategy`. */
|
|
379
|
+
componentNameStrategy?: PageNameStrategy;
|
|
380
|
+
}
|
|
381
|
+
interface ResolvedPageScopeConfig {
|
|
382
|
+
glob: string | string[];
|
|
383
|
+
prefix: string;
|
|
384
|
+
componentNameStrategy: PageNameStrategy;
|
|
385
|
+
}
|
|
351
386
|
interface ResolvedPagesConfig {
|
|
352
|
-
glob: string;
|
|
387
|
+
glob: string | string[];
|
|
353
388
|
propsExport: string;
|
|
354
|
-
componentNameStrategy:
|
|
389
|
+
componentNameStrategy: PageNameStrategy;
|
|
390
|
+
/** Feature scopes (not including the default app), with defaults applied. */
|
|
391
|
+
scopes: Record<string, ResolvedPageScopeConfig>;
|
|
355
392
|
}
|
|
356
393
|
interface ResolvedContractsConfig {
|
|
357
394
|
/** Glob pattern relative to `codegen.cwd` for watching controller files. */
|
|
@@ -238,9 +238,22 @@ interface UserConfig {
|
|
|
238
238
|
validation: ValidationOption;
|
|
239
239
|
/** Inertia page discovery. Omit when you don't use Inertia. */
|
|
240
240
|
pages?: {
|
|
241
|
-
|
|
241
|
+
/**
|
|
242
|
+
* Page files of the default Inertia app (`InertiaModule.forRoot`), relative to `cwd`. One
|
|
243
|
+
* glob or several; a page is named relative to the static base of the glob that matched it
|
|
244
|
+
* (`inertia/pages/users/Show.tsx` under `inertia/pages/**\/*.tsx` → `users/Show`).
|
|
245
|
+
*/
|
|
246
|
+
glob: string | string[];
|
|
242
247
|
propsExport?: string;
|
|
243
|
-
componentNameStrategy?:
|
|
248
|
+
componentNameStrategy?: PageNameStrategy;
|
|
249
|
+
/**
|
|
250
|
+
* Further Inertia apps, one per `InertiaModule.forFeature({ scope })`, keyed by scope name.
|
|
251
|
+
* Each has its own page glob(s); its page names get a prefix (default `'<scope>/'`, so
|
|
252
|
+
* `minimal/pages/Home.tsx` → `minimal/Home`, the name its `@Inertia('minimal/Home')`
|
|
253
|
+
* handlers render). Every page of every scope lands in `InertiaPages`/`InertiaPageName`;
|
|
254
|
+
* `InertiaScopePages` lists each scope's page names.
|
|
255
|
+
*/
|
|
256
|
+
scopes?: Record<string, PageScopeConfig>;
|
|
244
257
|
};
|
|
245
258
|
contracts?: {
|
|
246
259
|
/** Glob pattern (relative to cwd) for controller files. Default: `'src/**\/\*.controller.ts'` */
|
|
@@ -248,6 +261,11 @@ interface UserConfig {
|
|
|
248
261
|
/** Debounce delay in ms before re-running route discovery. Default: `500` */
|
|
249
262
|
debounceMs?: number;
|
|
250
263
|
};
|
|
264
|
+
/**
|
|
265
|
+
* @deprecated Has no effect: discovery never read it. For several Inertia apps use
|
|
266
|
+
* `pages.scopes`; for several controller sets use one `contracts.glob` (brace/extglob).
|
|
267
|
+
* A config that sets it gets a warning.
|
|
268
|
+
*/
|
|
251
269
|
scopes?: Record<string, ScopeConfig>;
|
|
252
270
|
codegen?: {
|
|
253
271
|
outDir?: string;
|
|
@@ -344,14 +362,33 @@ interface UserConfig {
|
|
|
344
362
|
*/
|
|
345
363
|
driftGuard?: boolean;
|
|
346
364
|
}
|
|
365
|
+
/** @deprecated See `UserConfig.scopes`. */
|
|
347
366
|
interface ScopeConfig {
|
|
348
367
|
glob: string;
|
|
349
368
|
prefix?: string;
|
|
350
369
|
}
|
|
370
|
+
/** How a page file's path (relative to its glob's static base) becomes its component name. */
|
|
371
|
+
type PageNameStrategy = 'relative-no-ext' | 'kebab' | ((path: string) => string);
|
|
372
|
+
/** One more Inertia app (an `InertiaModule.forFeature({ scope })`) — see `pages.scopes`. */
|
|
373
|
+
interface PageScopeConfig {
|
|
374
|
+
/** Page files of this app, relative to `cwd`. */
|
|
375
|
+
glob: string | string[];
|
|
376
|
+
/** Prepended to every page name of this scope. Default `'<scope>/'`; `''` for none. */
|
|
377
|
+
prefix?: string;
|
|
378
|
+
/** Naming strategy for this scope's pages. Default: `pages.componentNameStrategy`. */
|
|
379
|
+
componentNameStrategy?: PageNameStrategy;
|
|
380
|
+
}
|
|
381
|
+
interface ResolvedPageScopeConfig {
|
|
382
|
+
glob: string | string[];
|
|
383
|
+
prefix: string;
|
|
384
|
+
componentNameStrategy: PageNameStrategy;
|
|
385
|
+
}
|
|
351
386
|
interface ResolvedPagesConfig {
|
|
352
|
-
glob: string;
|
|
387
|
+
glob: string | string[];
|
|
353
388
|
propsExport: string;
|
|
354
|
-
componentNameStrategy:
|
|
389
|
+
componentNameStrategy: PageNameStrategy;
|
|
390
|
+
/** Feature scopes (not including the default app), with defaults applied. */
|
|
391
|
+
scopes: Record<string, ResolvedPageScopeConfig>;
|
|
355
392
|
}
|
|
356
393
|
interface ResolvedContractsConfig {
|
|
357
394
|
/** Glob pattern relative to `codegen.cwd` for watching controller files. */
|
package/dist/index.cjs
CHANGED
|
@@ -149,12 +149,33 @@ function validateUserConfig(userConfig) {
|
|
|
149
149
|
"validation adapter is required \u2014 install @dudousxd/nestjs-codegen-zod and pass zodAdapter, or use @dudousxd/nestjs-codegen-valibot / -arktype"
|
|
150
150
|
);
|
|
151
151
|
}
|
|
152
|
-
if (userConfig.pages &&
|
|
152
|
+
if (userConfig.pages && !isGlobInput(userConfig.pages.glob)) {
|
|
153
153
|
throw new ConfigError(
|
|
154
|
-
"Config validation failed: `pages.glob` must be a string when `pages` is set"
|
|
154
|
+
"Config validation failed: `pages.glob` must be a string or a non-empty array of strings when `pages` is set"
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
for (const [scope, scopeConfig] of Object.entries(userConfig.pages?.scopes ?? {})) {
|
|
158
|
+
if (scope === "default") {
|
|
159
|
+
throw new ConfigError(
|
|
160
|
+
'Config validation failed: `pages.scopes` cannot declare "default" \u2014 the default app is `pages.glob` (InertiaModule.forRoot).'
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
if (!isGlobInput(scopeConfig?.glob)) {
|
|
164
|
+
throw new ConfigError(
|
|
165
|
+
`Config validation failed: \`pages.scopes.${scope}.glob\` must be a string or a non-empty array of strings`
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (userConfig.scopes && Object.keys(userConfig.scopes).length > 0) {
|
|
170
|
+
console.warn(
|
|
171
|
+
"[nestjs-codegen] The top-level `scopes` option has no effect and is deprecated. For several Inertia apps use `pages.scopes` (one entry per InertiaModule.forFeature scope); for several controller sets use one `contracts.glob` (e.g. a brace glob)."
|
|
155
172
|
);
|
|
156
173
|
}
|
|
157
174
|
}
|
|
175
|
+
function isGlobInput(value) {
|
|
176
|
+
if (typeof value === "string") return true;
|
|
177
|
+
return Array.isArray(value) && value.length > 0 && value.every((glob) => typeof glob === "string");
|
|
178
|
+
}
|
|
158
179
|
function applyDefaults(userConfig, cwd) {
|
|
159
180
|
validateUserConfig(userConfig);
|
|
160
181
|
const outDir = userConfig.codegen?.outDir ? resolveAbsolute(cwd, userConfig.codegen.outDir) : (0, import_node_path.join)(cwd, ".nestjs-codegen");
|
|
@@ -181,7 +202,17 @@ function applyDefaults(userConfig, cwd) {
|
|
|
181
202
|
pages: userConfig.pages ? {
|
|
182
203
|
glob: userConfig.pages.glob,
|
|
183
204
|
propsExport: userConfig.pages.propsExport ?? "ComponentProps",
|
|
184
|
-
componentNameStrategy: userConfig.pages.componentNameStrategy ?? "relative-no-ext"
|
|
205
|
+
componentNameStrategy: userConfig.pages.componentNameStrategy ?? "relative-no-ext",
|
|
206
|
+
scopes: Object.fromEntries(
|
|
207
|
+
Object.entries(userConfig.pages.scopes ?? {}).map(([scope, scopeConfig]) => [
|
|
208
|
+
scope,
|
|
209
|
+
{
|
|
210
|
+
glob: scopeConfig.glob,
|
|
211
|
+
prefix: scopeConfig.prefix ?? `${scope}/`,
|
|
212
|
+
componentNameStrategy: scopeConfig.componentNameStrategy ?? userConfig.pages?.componentNameStrategy ?? "relative-no-ext"
|
|
213
|
+
}
|
|
214
|
+
])
|
|
215
|
+
)
|
|
185
216
|
} : null,
|
|
186
217
|
contracts: {
|
|
187
218
|
glob: userConfig.contracts?.glob ?? "src/**/*.controller.ts",
|
|
@@ -258,23 +289,81 @@ var import_promises2 = require("fs/promises");
|
|
|
258
289
|
var import_node_path2 = require("path");
|
|
259
290
|
var import_fast_glob = __toESM(require("fast-glob"), 1);
|
|
260
291
|
var NON_PAGE_FILE_RE = /\.(?:test|spec|stories|story)\.(?:tsx?|jsx?|vue|svelte)$/i;
|
|
292
|
+
var MAGIC_SEGMENT_RE = /[*?{}[\]()!]/;
|
|
293
|
+
function globStaticBase(glob) {
|
|
294
|
+
const segments = glob.split("/");
|
|
295
|
+
const staticSegments = [];
|
|
296
|
+
for (const segment of segments.slice(0, -1)) {
|
|
297
|
+
if (MAGIC_SEGMENT_RE.test(segment)) break;
|
|
298
|
+
staticSegments.push(segment);
|
|
299
|
+
}
|
|
300
|
+
return staticSegments.join("/");
|
|
301
|
+
}
|
|
261
302
|
async function discoverPages(opts) {
|
|
262
|
-
const
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
const
|
|
266
|
-
const pagesBase = (0, import_node_path2.join)(opts.cwd, globStatic);
|
|
303
|
+
const globs = typeof opts.glob === "string" ? [opts.glob] : opts.glob;
|
|
304
|
+
const prefix = opts.prefix ?? "";
|
|
305
|
+
const scope = opts.scope ?? "default";
|
|
306
|
+
const seenFiles = /* @__PURE__ */ new Set();
|
|
267
307
|
const out = [];
|
|
268
|
-
for (const
|
|
269
|
-
const
|
|
270
|
-
const
|
|
271
|
-
const
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
308
|
+
for (const glob of globs) {
|
|
309
|
+
const files = (await (0, import_fast_glob.default)(glob, { cwd: opts.cwd, absolute: true })).filter((f) => !NON_PAGE_FILE_RE.test(f) && !seenFiles.has(f)).sort();
|
|
310
|
+
const pagesBase = (0, import_node_path2.join)(opts.cwd, globStaticBase(glob));
|
|
311
|
+
for (const file of files) {
|
|
312
|
+
seenFiles.add(file);
|
|
313
|
+
const rel = (0, import_node_path2.relative)(opts.cwd, file);
|
|
314
|
+
const nameRel = (0, import_node_path2.relative)(pagesBase, file).split(import_node_path2.sep).join("/");
|
|
315
|
+
const name = `${prefix}${computeName(nameRel, opts.componentNameStrategy)}`;
|
|
316
|
+
const source = await (0, import_promises2.readFile)(file, "utf8");
|
|
317
|
+
const propsSource = extractPropsSource(source, opts.propsExport);
|
|
318
|
+
out.push({ name, absolutePath: file, relativePath: rel, propsSource, scope });
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
assertUniqueNames(out);
|
|
322
|
+
return out;
|
|
323
|
+
}
|
|
324
|
+
async function discoverPageScopes(config) {
|
|
325
|
+
const pages = config.pages;
|
|
326
|
+
if (!pages) return [];
|
|
327
|
+
const common = { cwd: config.codegen.cwd, propsExport: pages.propsExport };
|
|
328
|
+
const out = await discoverPages({
|
|
329
|
+
...common,
|
|
330
|
+
glob: pages.glob,
|
|
331
|
+
componentNameStrategy: pages.componentNameStrategy
|
|
332
|
+
});
|
|
333
|
+
for (const [scope, scopeConfig] of Object.entries(pages.scopes ?? {})) {
|
|
334
|
+
out.push(
|
|
335
|
+
...await discoverPages({
|
|
336
|
+
...common,
|
|
337
|
+
glob: scopeConfig.glob,
|
|
338
|
+
componentNameStrategy: scopeConfig.componentNameStrategy,
|
|
339
|
+
prefix: scopeConfig.prefix,
|
|
340
|
+
scope
|
|
341
|
+
})
|
|
342
|
+
);
|
|
275
343
|
}
|
|
344
|
+
assertUniqueNames(out);
|
|
276
345
|
return out;
|
|
277
346
|
}
|
|
347
|
+
function allPageGlobs(config) {
|
|
348
|
+
if (!config.pages) return [];
|
|
349
|
+
const asList = (glob) => typeof glob === "string" ? [glob] : glob;
|
|
350
|
+
return [
|
|
351
|
+
...asList(config.pages.glob),
|
|
352
|
+
...Object.values(config.pages.scopes ?? {}).flatMap((scope) => asList(scope.glob))
|
|
353
|
+
];
|
|
354
|
+
}
|
|
355
|
+
function assertUniqueNames(pages) {
|
|
356
|
+
const byName = /* @__PURE__ */ new Map();
|
|
357
|
+
for (const page of pages) {
|
|
358
|
+
const previous = byName.get(page.name);
|
|
359
|
+
if (previous) {
|
|
360
|
+
throw new Error(
|
|
361
|
+
`Two Inertia pages resolve to the component name "${page.name}": ${previous.relativePath} (scope "${previous.scope}") and ${page.relativePath} (scope "${page.scope}"). Give one of them another name, directory or scope prefix.`
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
byName.set(page.name, page);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
278
367
|
function computeName(rel, strat) {
|
|
279
368
|
if (typeof strat === "function") return strat(rel);
|
|
280
369
|
const noExt = rel.replace(/\.(tsx?|vue|svelte)$/, "");
|
|
@@ -686,10 +775,11 @@ function resolveModuleSpecifier(moduleSpecifier, sourceFile, project) {
|
|
|
686
775
|
}
|
|
687
776
|
return [];
|
|
688
777
|
}
|
|
689
|
-
function resolveImportedType(name, sourceFile, project) {
|
|
778
|
+
function resolveImportedType(name, sourceFile, project, seen = /* @__PURE__ */ new Set()) {
|
|
690
779
|
for (const importDecl of sourceFile.getImportDeclarations()) {
|
|
691
|
-
const namedImport = importDecl.getNamedImports().find((n) => n.getName() === name);
|
|
780
|
+
const namedImport = importDecl.getNamedImports().find((n) => (n.getAliasNode()?.getText() ?? n.getName()) === name);
|
|
692
781
|
if (!namedImport) continue;
|
|
782
|
+
const sourceName = namedImport.getName();
|
|
693
783
|
const moduleSpecifier = importDecl.getModuleSpecifierValue();
|
|
694
784
|
const candidates = resolveModuleSpecifier(moduleSpecifier, sourceFile, project);
|
|
695
785
|
for (const candidate of candidates) {
|
|
@@ -701,17 +791,17 @@ function resolveImportedType(name, sourceFile, project) {
|
|
|
701
791
|
continue;
|
|
702
792
|
}
|
|
703
793
|
}
|
|
704
|
-
const result = findTypeInFile(
|
|
794
|
+
const result = findTypeInFile(sourceName, importedFile);
|
|
705
795
|
if (result) return result;
|
|
706
|
-
const viaReExport = resolveReExportedType(
|
|
796
|
+
const viaReExport = resolveReExportedType(sourceName, importedFile, project, /* @__PURE__ */ new Set());
|
|
707
797
|
if (viaReExport) return viaReExport;
|
|
708
798
|
}
|
|
709
799
|
if (candidates.length === 0) {
|
|
710
|
-
const viaCompiler = resolveBareSpecifierType(
|
|
800
|
+
const viaCompiler = resolveBareSpecifierType(sourceName, importDecl, project);
|
|
711
801
|
if (viaCompiler) return viaCompiler;
|
|
712
802
|
}
|
|
713
803
|
}
|
|
714
|
-
return resolveReExportedType(name, sourceFile, project,
|
|
804
|
+
return resolveReExportedType(name, sourceFile, project, seen);
|
|
715
805
|
}
|
|
716
806
|
function resolveBareSpecifierType(name, importDecl, project) {
|
|
717
807
|
let target;
|
|
@@ -726,9 +816,9 @@ function resolveBareSpecifierType(name, importDecl, project) {
|
|
|
726
816
|
return resolveReExportedType(name, target, project, /* @__PURE__ */ new Set());
|
|
727
817
|
}
|
|
728
818
|
function resolveReExportedType(name, file, project, seen) {
|
|
729
|
-
const
|
|
730
|
-
if (seen.has(
|
|
731
|
-
seen.add(
|
|
819
|
+
const visit = `${file.getFilePath()}#${name}`;
|
|
820
|
+
if (seen.has(visit)) return null;
|
|
821
|
+
seen.add(visit);
|
|
732
822
|
for (const exportDecl of file.getExportDeclarations()) {
|
|
733
823
|
const moduleSpecifier = exportDecl.getModuleSpecifierValue();
|
|
734
824
|
const namedExports = exportDecl.getNamedExports();
|
|
@@ -750,7 +840,7 @@ function resolveReExportedType(name, file, project, seen) {
|
|
|
750
840
|
const sourceName = namedExports.find((n) => (n.getAliasNode()?.getText() ?? n.getName()) === name)?.getName() ?? name;
|
|
751
841
|
const local = findTypeInFile(sourceName, file);
|
|
752
842
|
if (local) return local;
|
|
753
|
-
const imported = resolveImportedType(sourceName, file, project);
|
|
843
|
+
const imported = resolveImportedType(sourceName, file, project, seen);
|
|
754
844
|
if (imported) return imported;
|
|
755
845
|
}
|
|
756
846
|
return null;
|
|
@@ -4145,28 +4235,28 @@ function renderFetcherRequest(req, binaryResponse) {
|
|
|
4145
4235
|
}
|
|
4146
4236
|
function emitReqHelper() {
|
|
4147
4237
|
return [
|
|
4148
|
-
"/**
|
|
4149
|
-
"type
|
|
4150
|
-
"
|
|
4238
|
+
"/** An awaitable request handle: a lazy, memoized `Promise` that also carries `fetch()` and the members extensions add. */",
|
|
4239
|
+
"export type RequestHandle<R, M = {}> = Promise<R> & { fetch(): Promise<R> } & M;",
|
|
4240
|
+
"class __Request<R> extends Promise<R> {",
|
|
4241
|
+
" static override get [Symbol.species](): PromiseConstructor {",
|
|
4242
|
+
" return Promise;",
|
|
4243
|
+
" }",
|
|
4244
|
+
" private __run: () => Promise<R>;",
|
|
4245
|
+
" private __p: Promise<R> | undefined;",
|
|
4246
|
+
" constructor(run: () => Promise<R>) {",
|
|
4247
|
+
" super(() => {});",
|
|
4248
|
+
" this.__run = run;",
|
|
4249
|
+
" }",
|
|
4250
|
+
" override then<T1 = R, T2 = never>(",
|
|
4151
4251
|
" onfulfilled?: ((value: R) => T1 | PromiseLike<T1>) | null,",
|
|
4152
4252
|
" onrejected?: ((reason: unknown) => T2 | PromiseLike<T2>) | null,",
|
|
4153
|
-
" ): Promise<T1 | T2
|
|
4154
|
-
"
|
|
4155
|
-
"
|
|
4156
|
-
"
|
|
4157
|
-
"}
|
|
4158
|
-
"function __req<R>(run: () => Promise<R
|
|
4159
|
-
"
|
|
4160
|
-
" const __promise = () => {",
|
|
4161
|
-
" __p ??= run();",
|
|
4162
|
-
" return __p;",
|
|
4163
|
-
" };",
|
|
4164
|
-
" return {",
|
|
4165
|
-
" then: (onfulfilled, onrejected) => __promise().then(onfulfilled, onrejected),",
|
|
4166
|
-
" catch: (onrejected) => __promise().catch(onrejected),",
|
|
4167
|
-
" finally: (onfinally) => __promise().finally(onfinally),",
|
|
4168
|
-
" fetch: run,",
|
|
4169
|
-
" };",
|
|
4253
|
+
" ): Promise<T1 | T2> {",
|
|
4254
|
+
" this.__p ??= this.__run();",
|
|
4255
|
+
" return this.__p.then(onfulfilled, onrejected);",
|
|
4256
|
+
" }",
|
|
4257
|
+
"}",
|
|
4258
|
+
"function __req<R, M extends object = {}>(run: () => Promise<R>, members?: M): RequestHandle<R, M> {",
|
|
4259
|
+
" return Object.assign(new __Request<R>(run), { fetch: run }, members) as RequestHandle<R, M>;",
|
|
4170
4260
|
"}",
|
|
4171
4261
|
""
|
|
4172
4262
|
];
|
|
@@ -4184,18 +4274,26 @@ function emitFilterFieldGuard() {
|
|
|
4184
4274
|
];
|
|
4185
4275
|
}
|
|
4186
4276
|
function renderLeaf(pad, objKey, req, requestExpr, members, streamExpr) {
|
|
4187
|
-
const
|
|
4188
|
-
lines.push(`${pad} ...__req<${req.responseType}>(() => ${requestExpr}),`);
|
|
4277
|
+
const memberLines = [];
|
|
4189
4278
|
if (req.filterFieldsExpr) {
|
|
4190
|
-
|
|
4279
|
+
memberLines.push(`${pad} filterFields: ${req.filterFieldsExpr},`);
|
|
4191
4280
|
}
|
|
4192
4281
|
if (streamExpr) {
|
|
4193
|
-
|
|
4282
|
+
memberLines.push(`${pad} stream: () => ${streamExpr},`);
|
|
4194
4283
|
}
|
|
4195
4284
|
for (const [name, value] of Object.entries(members)) {
|
|
4196
|
-
|
|
4197
|
-
}
|
|
4198
|
-
|
|
4285
|
+
memberLines.push(`${pad} ${name}: ${value},`);
|
|
4286
|
+
}
|
|
4287
|
+
const body = [requestExpr, ...memberLines].join("\n");
|
|
4288
|
+
const param = /\binput\b/.test(body) ? `input?: ${req.inputType}` : "";
|
|
4289
|
+
const lines = [`${pad}${objKey}: (${param}) =>`];
|
|
4290
|
+
if (memberLines.length === 0) {
|
|
4291
|
+
lines.push(`${pad} __req<${req.responseType}>(() => ${requestExpr}),`);
|
|
4292
|
+
return lines;
|
|
4293
|
+
}
|
|
4294
|
+
lines.push(`${pad} __req(() => ${requestExpr}, {`);
|
|
4295
|
+
lines.push(...memberLines);
|
|
4296
|
+
lines.push(`${pad} }),`);
|
|
4199
4297
|
return lines;
|
|
4200
4298
|
}
|
|
4201
4299
|
function renderStreamExpr(req) {
|
|
@@ -4329,6 +4427,23 @@ var EMPTY_PATH_NAMESPACE = [
|
|
|
4329
4427
|
function isClientCallable(route) {
|
|
4330
4428
|
return route.method.toUpperCase() !== "ALL";
|
|
4331
4429
|
}
|
|
4430
|
+
var ROUTES_IMPORT_PLACEHOLDER = "/* @@routes-import@@ */";
|
|
4431
|
+
var ROUTES_EXPORTS = [
|
|
4432
|
+
// Called, never mentioned in prose: match the call so a comment saying "route" doesn't count.
|
|
4433
|
+
{ name: "route", typeOnly: false },
|
|
4434
|
+
{ name: "ROUTES", typeOnly: false },
|
|
4435
|
+
{ name: "RouteName", typeOnly: true },
|
|
4436
|
+
{ name: "ExtractParams", typeOnly: true },
|
|
4437
|
+
{ name: "RouteParams", typeOnly: true }
|
|
4438
|
+
];
|
|
4439
|
+
function finishRoutesImport(lines) {
|
|
4440
|
+
const body = lines.filter((l) => l !== ROUTES_IMPORT_PLACEHOLDER).join("\n");
|
|
4441
|
+
const used = ROUTES_EXPORTS.filter(
|
|
4442
|
+
({ name }) => new RegExp(name === "route" ? "\\broute\\(" : `\\b${name}\\b`).test(body)
|
|
4443
|
+
).map(({ name, typeOnly }) => typeOnly ? `type ${name}` : name);
|
|
4444
|
+
const importLine = used.length ? `import { ${used.join(", ")} } from './routes.js';` : null;
|
|
4445
|
+
return lines.flatMap((l) => l === ROUTES_IMPORT_PLACEHOLDER ? importLine ? [importLine] : [] : [l]).join("\n");
|
|
4446
|
+
}
|
|
4332
4447
|
function buildApiFile(routes, outDir, opts = {}) {
|
|
4333
4448
|
const fetcherImportPath = opts.fetcherImportPath;
|
|
4334
4449
|
const serialization = opts.serialization ?? "json";
|
|
@@ -4388,9 +4503,7 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
4388
4503
|
for (const imp of ext.apiHeader?.(ctx)?.imports ?? []) pushImport(imp);
|
|
4389
4504
|
}
|
|
4390
4505
|
lines.push(...extImports);
|
|
4391
|
-
lines.push(
|
|
4392
|
-
"import { route, ROUTES, type RouteName, type ExtractParams, type RouteParams } from './routes.js';"
|
|
4393
|
-
);
|
|
4506
|
+
lines.push(ROUTES_IMPORT_PLACEHOLDER);
|
|
4394
4507
|
const runtimeImport = fetcherImportPath ?? "@dudousxd/nestjs-client";
|
|
4395
4508
|
lines.push(`import type { Fetcher } from '${runtimeImport}';`);
|
|
4396
4509
|
if (serialization === "json" && contracted.length > 0) {
|
|
@@ -4441,7 +4554,7 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
4441
4554
|
lines.push(...statements, "");
|
|
4442
4555
|
}
|
|
4443
4556
|
}
|
|
4444
|
-
return lines
|
|
4557
|
+
return finishRoutesImport(lines);
|
|
4445
4558
|
}
|
|
4446
4559
|
const tree = /* @__PURE__ */ new Map();
|
|
4447
4560
|
for (const r of contracted) {
|
|
@@ -4494,7 +4607,7 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
4494
4607
|
lines.push(...statements, "");
|
|
4495
4608
|
}
|
|
4496
4609
|
}
|
|
4497
|
-
return lines
|
|
4610
|
+
return finishRoutesImport(lines);
|
|
4498
4611
|
}
|
|
4499
4612
|
|
|
4500
4613
|
// src/emit/emit-cache.ts
|
|
@@ -4507,6 +4620,7 @@ async function emitCache(pages, outDir) {
|
|
|
4507
4620
|
const s = await (0, import_promises4.stat)(p.absolutePath);
|
|
4508
4621
|
return {
|
|
4509
4622
|
name: p.name,
|
|
4623
|
+
scope: p.scope ?? "default",
|
|
4510
4624
|
relativePath: p.relativePath,
|
|
4511
4625
|
mtime: s.mtime.toISOString()
|
|
4512
4626
|
};
|
|
@@ -5301,10 +5415,11 @@ async function emitPages(pages, outDir, _options = {}) {
|
|
|
5301
5415
|
}).join("\n");
|
|
5302
5416
|
const propsHelper = "\nexport type InertiaProps<K extends InertiaPageName> = import('@dudousxd/nestjs-inertia').InertiaPages[K];\n";
|
|
5303
5417
|
const sharedPropsBlock = buildSharedPropsBlock(_options.sharedProps ?? null);
|
|
5418
|
+
const scopesBlock = buildScopesBlock(pages);
|
|
5304
5419
|
const content = `// Generated by @dudousxd/nestjs-codegen. Do not edit.
|
|
5305
5420
|
|
|
5306
5421
|
export type InertiaPageName = ${pageNameUnion};
|
|
5307
|
-
${propsHelper}
|
|
5422
|
+
${propsHelper}${scopesBlock}
|
|
5308
5423
|
declare module '@dudousxd/nestjs-inertia' {
|
|
5309
5424
|
interface InertiaPages {
|
|
5310
5425
|
${augBody}
|
|
@@ -5313,6 +5428,31 @@ ${sharedPropsBlock}}
|
|
|
5313
5428
|
`;
|
|
5314
5429
|
await (0, import_promises9.writeFile)((0, import_node_path13.join)(outDir, "pages.d.ts"), content, "utf8");
|
|
5315
5430
|
}
|
|
5431
|
+
function buildScopesBlock(pages) {
|
|
5432
|
+
const byScope = /* @__PURE__ */ new Map();
|
|
5433
|
+
for (const page of pages) {
|
|
5434
|
+
const scope = page.scope ?? "default";
|
|
5435
|
+
const names = byScope.get(scope) ?? [];
|
|
5436
|
+
names.push(JSON.stringify(page.name));
|
|
5437
|
+
byScope.set(scope, names);
|
|
5438
|
+
}
|
|
5439
|
+
if (![...byScope.keys()].some((scope) => scope !== "default")) return "";
|
|
5440
|
+
if (!byScope.has("default")) byScope.set("default", []);
|
|
5441
|
+
const scopes = ["default", ...[...byScope.keys()].filter((s) => s !== "default")];
|
|
5442
|
+
const body = scopes.map((scope) => {
|
|
5443
|
+
const key = needsQuotes(scope) ? JSON.stringify(scope) : scope;
|
|
5444
|
+
const names = byScope.get(scope) ?? [];
|
|
5445
|
+
return ` ${key}: ${names.length ? names.join(" | ") : "never"};`;
|
|
5446
|
+
}).join("\n");
|
|
5447
|
+
return `
|
|
5448
|
+
/** The page names of each Inertia app: \`default\` (forRoot) and every \`pages.scopes\` entry. */
|
|
5449
|
+
export interface InertiaScopePages {
|
|
5450
|
+
${body}
|
|
5451
|
+
}
|
|
5452
|
+
|
|
5453
|
+
export type InertiaScopePageName<S extends keyof InertiaScopePages> = InertiaScopePages[S];
|
|
5454
|
+
`;
|
|
5455
|
+
}
|
|
5316
5456
|
function buildSharedPropsBlock(sharedProps) {
|
|
5317
5457
|
if (!sharedProps) return "";
|
|
5318
5458
|
if (sharedProps.isImportRef) {
|
|
@@ -5520,8 +5660,7 @@ function diffConfigKeyHashes(previous, current) {
|
|
|
5520
5660
|
return [...keys].filter((key) => previous[key] !== current[key]).sort();
|
|
5521
5661
|
}
|
|
5522
5662
|
async function discoverInputFiles(config) {
|
|
5523
|
-
const globs = [config.contracts.glob, config.forms.watch];
|
|
5524
|
-
if (config.pages) globs.push(config.pages.glob);
|
|
5663
|
+
const globs = [config.contracts.glob, config.forms.watch, ...allPageGlobs(config)];
|
|
5525
5664
|
const cwd = config.codegen.cwd;
|
|
5526
5665
|
const matched = await (0, import_fast_glob3.default)(globs, { cwd, absolute: true, onlyFiles: true });
|
|
5527
5666
|
return [...new Set(matched)].sort();
|
|
@@ -5652,12 +5791,7 @@ async function generate(config, inputRoutes = [], entryPoint = "cli") {
|
|
|
5652
5791
|
}
|
|
5653
5792
|
if (config.pages) {
|
|
5654
5793
|
const pagesConfig = config.pages;
|
|
5655
|
-
const pages = await
|
|
5656
|
-
glob: pagesConfig.glob,
|
|
5657
|
-
cwd: config.codegen.cwd,
|
|
5658
|
-
propsExport: pagesConfig.propsExport,
|
|
5659
|
-
componentNameStrategy: pagesConfig.componentNameStrategy
|
|
5660
|
-
});
|
|
5794
|
+
const pages = await discoverPageScopes(config);
|
|
5661
5795
|
const sharedProps = discoverSharedPropsFromConfig(config);
|
|
5662
5796
|
await emitPages(pages, config.codegen.outDir, {
|
|
5663
5797
|
propsExport: pagesConfig.propsExport,
|
|
@@ -5832,12 +5966,17 @@ async function watch(config, onChange, options = {}) {
|
|
|
5832
5966
|
await runInitialPass();
|
|
5833
5967
|
}
|
|
5834
5968
|
let pagesDebounceTimer;
|
|
5835
|
-
const
|
|
5836
|
-
const pagesWatcher = import_chokidar.default.watch(
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
|
|
5840
|
-
|
|
5969
|
+
const pageGlobs = allPageGlobs(config);
|
|
5970
|
+
const pagesWatcher = import_chokidar.default.watch(
|
|
5971
|
+
(pageGlobs.length ? pageGlobs : [".nestjs-codegen-no-pages"]).map(
|
|
5972
|
+
(glob) => (0, import_node_path18.join)(config.codegen.cwd, glob)
|
|
5973
|
+
),
|
|
5974
|
+
{
|
|
5975
|
+
ignoreInitial: true,
|
|
5976
|
+
persistent: true,
|
|
5977
|
+
awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
|
|
5978
|
+
}
|
|
5979
|
+
);
|
|
5841
5980
|
function schedulePagesRegenerate() {
|
|
5842
5981
|
if (pagesDebounceTimer !== void 0) {
|
|
5843
5982
|
clearTimeout(pagesDebounceTimer);
|
|
@@ -6000,7 +6139,7 @@ function createChainModuleRenderer(opts) {
|
|
|
6000
6139
|
}
|
|
6001
6140
|
|
|
6002
6141
|
// src/index.ts
|
|
6003
|
-
var VERSION = "0.
|
|
6142
|
+
var VERSION = "0.27.1";
|
|
6004
6143
|
// Annotate the CommonJS export names for ESM import in node:
|
|
6005
6144
|
0 && (module.exports = {
|
|
6006
6145
|
CodegenError,
|