@dudousxd/nestjs-codegen 0.11.0 → 0.13.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 +59 -0
- package/dist/cli/main.cjs +264 -91
- package/dist/cli/main.cjs.map +1 -1
- package/dist/cli/main.js +248 -75
- 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-CxkGbILp.d.cts → index-DvUzPXdh.d.cts} +7 -0
- package/dist/{index-CxkGbILp.d.ts → index-DvUzPXdh.d.ts} +7 -0
- package/dist/index.cjs +222 -49
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +31 -5
- package/dist/index.d.ts +31 -5
- package/dist/index.js +209 -36
- package/dist/index.js.map +1 -1
- package/dist/nest/index.cjs +267 -75
- package/dist/nest/index.cjs.map +1 -1
- package/dist/nest/index.d.cts +16 -5
- package/dist/nest/index.d.ts +16 -5
- package/dist/nest/index.js +267 -75
- package/dist/nest/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { U as UserConfig, R as ResolvedConfig, a as RouteDescriptor, S as SchemaNode, b as RenderContext, c as SchemaModule, d as RenderedModule, e as ResolvedFormsConfig, V as ValidationAdapter, C as CodegenExtension, E as ExtensionContext, f as SerializationMode } from './index-
|
|
2
|
-
export { A as AdapterUsage, g as ContractDescriptor, h as ContractSource, i as ControllerRef, N as NumberCheck, j as ScopeConfig, k as StringCheck, T as TypeRef, l as ValidationOption, r as resolveAdapter } from './index-
|
|
1
|
+
import { U as UserConfig, R as ResolvedConfig, a as RouteDescriptor, S as SchemaNode, b as RenderContext, c as SchemaModule, d as RenderedModule, e as ResolvedFormsConfig, V as ValidationAdapter, C as CodegenExtension, E as ExtensionContext, f as SerializationMode } from './index-DvUzPXdh.cjs';
|
|
2
|
+
export { A as AdapterUsage, g as ContractDescriptor, h as ContractSource, i as ControllerRef, N as NumberCheck, j as ScopeConfig, k as StringCheck, T as TypeRef, l as ValidationOption, r as resolveAdapter } from './index-DvUzPXdh.cjs';
|
|
3
3
|
import { ClassDeclaration, SourceFile, Project } from 'ts-morph';
|
|
4
4
|
|
|
5
5
|
declare function defineConfig(c: UserConfig): UserConfig;
|
|
@@ -45,6 +45,18 @@ declare function generate(config: ResolvedConfig, inputRoutes?: RouteDescriptor[
|
|
|
45
45
|
interface Watcher {
|
|
46
46
|
close(): Promise<void>;
|
|
47
47
|
}
|
|
48
|
+
interface WatchOptions {
|
|
49
|
+
/**
|
|
50
|
+
* Run the initial discover + generate as fire-and-forget instead of awaiting it
|
|
51
|
+
* before returning. The lock is still acquired and the chokidar watchers are
|
|
52
|
+
* still set up synchronously; only the initial generate is backgrounded. Used by
|
|
53
|
+
* the Nest `onApplicationBootstrap` hook so codegen never blocks time-to-ready.
|
|
54
|
+
* The one-shot CLI leaves this `false` so the build step stays synchronous.
|
|
55
|
+
*
|
|
56
|
+
* @default false
|
|
57
|
+
*/
|
|
58
|
+
deferInitialGenerate?: boolean;
|
|
59
|
+
}
|
|
48
60
|
/**
|
|
49
61
|
* Start two chokidar watchers:
|
|
50
62
|
*
|
|
@@ -58,7 +70,7 @@ interface Watcher {
|
|
|
58
70
|
* Both watchers share a single lock file in `config.codegen.outDir`. If another live process
|
|
59
71
|
* already holds the lock, logs a warning and returns a no-op watcher.
|
|
60
72
|
*/
|
|
61
|
-
declare function watch(config: ResolvedConfig, onChange?: () => void): Promise<Watcher>;
|
|
73
|
+
declare function watch(config: ResolvedConfig, onChange?: () => void, options?: WatchOptions): Promise<Watcher>;
|
|
62
74
|
|
|
63
75
|
/**
|
|
64
76
|
* Try to acquire an exclusive lock for a watcher in `outDir`.
|
|
@@ -73,6 +85,20 @@ declare function acquireLock(outDir: string): Promise<{
|
|
|
73
85
|
release: () => Promise<void>;
|
|
74
86
|
} | null>;
|
|
75
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Persisted record of the last successful generate, written to
|
|
90
|
+
* `<outDir>/.codegen-manifest.json`. Used to skip regeneration when nothing
|
|
91
|
+
* relevant changed (see {@link isManifestFresh}).
|
|
92
|
+
*/
|
|
93
|
+
interface CodegenManifest {
|
|
94
|
+
/** Lib version that produced the output. A lib upgrade invalidates the manifest. */
|
|
95
|
+
version: string;
|
|
96
|
+
/** Content hash over all generate inputs (source files + resolved config + version). */
|
|
97
|
+
hash: string;
|
|
98
|
+
/** Generated output files, relative to `outDir`, recorded after the last run. */
|
|
99
|
+
files: string[];
|
|
100
|
+
}
|
|
101
|
+
|
|
76
102
|
/**
|
|
77
103
|
* Renders the neutral {@link SchemaNode} IR to a TypeScript *type* expression
|
|
78
104
|
* (not a validation-lib schema). Used to synthesize the hoisted structural type
|
|
@@ -296,6 +322,6 @@ interface FastDiscoveryOptions {
|
|
|
296
322
|
}
|
|
297
323
|
declare function discoverContractsFast(opts: FastDiscoveryOptions): Promise<RouteDescriptor[]>;
|
|
298
324
|
|
|
299
|
-
declare const VERSION = "0.
|
|
325
|
+
declare const VERSION = "0.13.0";
|
|
300
326
|
|
|
301
|
-
export { type ChainModuleRendererOptions, CodegenError, ConfigError, type FastDiscoveryOptions, type JsonSchema, type MocksEmitOptions, type OpenApiDocument, type OpenApiEmitOptions, type OpenApiInfo, RenderContext, RenderedModule, ResolvedConfig, RouteDescriptor, SchemaModule, SchemaNode, type TsTypeContext, UserConfig, VERSION, ValidationAdapter, type Watcher, acquireLock, buildMocksFile, buildOpenApiSpec, createChainModuleRenderer, defineConfig, discoverContractsFast, emitApi, emitForms, emitMocks, emitOpenApi, emitRoutes, extractSchemaFromDto, generate, loadConfig, renderTsType, resolveConfig, schemaModuleToJsonSchema, schemaNodeToJsonSchema, toObjectKey, typeNameFor, watch };
|
|
327
|
+
export { type ChainModuleRendererOptions, CodegenError, type CodegenManifest, ConfigError, type FastDiscoveryOptions, type JsonSchema, type MocksEmitOptions, type OpenApiDocument, type OpenApiEmitOptions, type OpenApiInfo, RenderContext, RenderedModule, ResolvedConfig, RouteDescriptor, SchemaModule, SchemaNode, type TsTypeContext, UserConfig, VERSION, ValidationAdapter, type WatchOptions, type Watcher, acquireLock, buildMocksFile, buildOpenApiSpec, createChainModuleRenderer, defineConfig, discoverContractsFast, emitApi, emitForms, emitMocks, emitOpenApi, emitRoutes, extractSchemaFromDto, generate, loadConfig, renderTsType, resolveConfig, schemaModuleToJsonSchema, schemaNodeToJsonSchema, toObjectKey, typeNameFor, watch };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { U as UserConfig, R as ResolvedConfig, a as RouteDescriptor, S as SchemaNode, b as RenderContext, c as SchemaModule, d as RenderedModule, e as ResolvedFormsConfig, V as ValidationAdapter, C as CodegenExtension, E as ExtensionContext, f as SerializationMode } from './index-
|
|
2
|
-
export { A as AdapterUsage, g as ContractDescriptor, h as ContractSource, i as ControllerRef, N as NumberCheck, j as ScopeConfig, k as StringCheck, T as TypeRef, l as ValidationOption, r as resolveAdapter } from './index-
|
|
1
|
+
import { U as UserConfig, R as ResolvedConfig, a as RouteDescriptor, S as SchemaNode, b as RenderContext, c as SchemaModule, d as RenderedModule, e as ResolvedFormsConfig, V as ValidationAdapter, C as CodegenExtension, E as ExtensionContext, f as SerializationMode } from './index-DvUzPXdh.js';
|
|
2
|
+
export { A as AdapterUsage, g as ContractDescriptor, h as ContractSource, i as ControllerRef, N as NumberCheck, j as ScopeConfig, k as StringCheck, T as TypeRef, l as ValidationOption, r as resolveAdapter } from './index-DvUzPXdh.js';
|
|
3
3
|
import { ClassDeclaration, SourceFile, Project } from 'ts-morph';
|
|
4
4
|
|
|
5
5
|
declare function defineConfig(c: UserConfig): UserConfig;
|
|
@@ -45,6 +45,18 @@ declare function generate(config: ResolvedConfig, inputRoutes?: RouteDescriptor[
|
|
|
45
45
|
interface Watcher {
|
|
46
46
|
close(): Promise<void>;
|
|
47
47
|
}
|
|
48
|
+
interface WatchOptions {
|
|
49
|
+
/**
|
|
50
|
+
* Run the initial discover + generate as fire-and-forget instead of awaiting it
|
|
51
|
+
* before returning. The lock is still acquired and the chokidar watchers are
|
|
52
|
+
* still set up synchronously; only the initial generate is backgrounded. Used by
|
|
53
|
+
* the Nest `onApplicationBootstrap` hook so codegen never blocks time-to-ready.
|
|
54
|
+
* The one-shot CLI leaves this `false` so the build step stays synchronous.
|
|
55
|
+
*
|
|
56
|
+
* @default false
|
|
57
|
+
*/
|
|
58
|
+
deferInitialGenerate?: boolean;
|
|
59
|
+
}
|
|
48
60
|
/**
|
|
49
61
|
* Start two chokidar watchers:
|
|
50
62
|
*
|
|
@@ -58,7 +70,7 @@ interface Watcher {
|
|
|
58
70
|
* Both watchers share a single lock file in `config.codegen.outDir`. If another live process
|
|
59
71
|
* already holds the lock, logs a warning and returns a no-op watcher.
|
|
60
72
|
*/
|
|
61
|
-
declare function watch(config: ResolvedConfig, onChange?: () => void): Promise<Watcher>;
|
|
73
|
+
declare function watch(config: ResolvedConfig, onChange?: () => void, options?: WatchOptions): Promise<Watcher>;
|
|
62
74
|
|
|
63
75
|
/**
|
|
64
76
|
* Try to acquire an exclusive lock for a watcher in `outDir`.
|
|
@@ -73,6 +85,20 @@ declare function acquireLock(outDir: string): Promise<{
|
|
|
73
85
|
release: () => Promise<void>;
|
|
74
86
|
} | null>;
|
|
75
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Persisted record of the last successful generate, written to
|
|
90
|
+
* `<outDir>/.codegen-manifest.json`. Used to skip regeneration when nothing
|
|
91
|
+
* relevant changed (see {@link isManifestFresh}).
|
|
92
|
+
*/
|
|
93
|
+
interface CodegenManifest {
|
|
94
|
+
/** Lib version that produced the output. A lib upgrade invalidates the manifest. */
|
|
95
|
+
version: string;
|
|
96
|
+
/** Content hash over all generate inputs (source files + resolved config + version). */
|
|
97
|
+
hash: string;
|
|
98
|
+
/** Generated output files, relative to `outDir`, recorded after the last run. */
|
|
99
|
+
files: string[];
|
|
100
|
+
}
|
|
101
|
+
|
|
76
102
|
/**
|
|
77
103
|
* Renders the neutral {@link SchemaNode} IR to a TypeScript *type* expression
|
|
78
104
|
* (not a validation-lib schema). Used to synthesize the hoisted structural type
|
|
@@ -296,6 +322,6 @@ interface FastDiscoveryOptions {
|
|
|
296
322
|
}
|
|
297
323
|
declare function discoverContractsFast(opts: FastDiscoveryOptions): Promise<RouteDescriptor[]>;
|
|
298
324
|
|
|
299
|
-
declare const VERSION = "0.
|
|
325
|
+
declare const VERSION = "0.13.0";
|
|
300
326
|
|
|
301
|
-
export { type ChainModuleRendererOptions, CodegenError, ConfigError, type FastDiscoveryOptions, type JsonSchema, type MocksEmitOptions, type OpenApiDocument, type OpenApiEmitOptions, type OpenApiInfo, RenderContext, RenderedModule, ResolvedConfig, RouteDescriptor, SchemaModule, SchemaNode, type TsTypeContext, UserConfig, VERSION, ValidationAdapter, type Watcher, acquireLock, buildMocksFile, buildOpenApiSpec, createChainModuleRenderer, defineConfig, discoverContractsFast, emitApi, emitForms, emitMocks, emitOpenApi, emitRoutes, extractSchemaFromDto, generate, loadConfig, renderTsType, resolveConfig, schemaModuleToJsonSchema, schemaNodeToJsonSchema, toObjectKey, typeNameFor, watch };
|
|
327
|
+
export { type ChainModuleRendererOptions, CodegenError, type CodegenManifest, ConfigError, type FastDiscoveryOptions, type JsonSchema, type MocksEmitOptions, type OpenApiDocument, type OpenApiEmitOptions, type OpenApiInfo, RenderContext, RenderedModule, ResolvedConfig, RouteDescriptor, SchemaModule, SchemaNode, type TsTypeContext, UserConfig, VERSION, ValidationAdapter, type WatchOptions, type Watcher, acquireLock, buildMocksFile, buildOpenApiSpec, createChainModuleRenderer, defineConfig, discoverContractsFast, emitApi, emitForms, emitMocks, emitOpenApi, emitRoutes, extractSchemaFromDto, generate, loadConfig, renderTsType, resolveConfig, schemaModuleToJsonSchema, schemaNodeToJsonSchema, toObjectKey, typeNameFor, watch };
|
package/dist/index.js
CHANGED
|
@@ -188,8 +188,8 @@ Run \`nestjs-codegen init\` to create a starter config.`
|
|
|
188
188
|
}
|
|
189
189
|
|
|
190
190
|
// src/generate.ts
|
|
191
|
-
import { mkdir as mkdir9, writeFile as
|
|
192
|
-
import { dirname as dirname2, join as
|
|
191
|
+
import { mkdir as mkdir9, writeFile as writeFile10 } from "fs/promises";
|
|
192
|
+
import { dirname as dirname2, join as join13 } from "path";
|
|
193
193
|
|
|
194
194
|
// src/discovery/pages.ts
|
|
195
195
|
import { readFile } from "fs/promises";
|
|
@@ -791,6 +791,7 @@ function buildRequestModel(c) {
|
|
|
791
791
|
const optsParts = [];
|
|
792
792
|
if (hasQuery) optsParts.push("query: input?.query as Record<string, unknown> | undefined");
|
|
793
793
|
if (hasBody) optsParts.push("body: input?.body");
|
|
794
|
+
if (hasBody && c.contractSource.multipart) optsParts.push("multipart: true");
|
|
794
795
|
const optsExpr = optsParts.length ? `{ ${optsParts.join(", ")} }` : "{}";
|
|
795
796
|
return {
|
|
796
797
|
routeName: c.name,
|
|
@@ -2087,6 +2088,99 @@ function buildEmpty() {
|
|
|
2087
2088
|
].join("\n");
|
|
2088
2089
|
}
|
|
2089
2090
|
|
|
2091
|
+
// src/generate-manifest.ts
|
|
2092
|
+
import { createHash } from "crypto";
|
|
2093
|
+
import { readFile as readFile2, readdir, writeFile as writeFile9 } from "fs/promises";
|
|
2094
|
+
import { join as join12, relative as relative6 } from "path";
|
|
2095
|
+
import fg2 from "fast-glob";
|
|
2096
|
+
var MANIFEST_FILE = ".codegen-manifest.json";
|
|
2097
|
+
var LOCK_FILE = ".watcher.lock";
|
|
2098
|
+
function isManifestShape(value) {
|
|
2099
|
+
if (typeof value !== "object" || value === null) return false;
|
|
2100
|
+
const candidate = value;
|
|
2101
|
+
if (typeof candidate.version !== "string") return false;
|
|
2102
|
+
if (typeof candidate.hash !== "string") return false;
|
|
2103
|
+
if (!Array.isArray(candidate.files)) return false;
|
|
2104
|
+
return candidate.files.every((entry) => typeof entry === "string");
|
|
2105
|
+
}
|
|
2106
|
+
function serializeConfig(config) {
|
|
2107
|
+
try {
|
|
2108
|
+
return JSON.stringify(config, (_key, value) => {
|
|
2109
|
+
if (typeof value === "function") return `[fn:${value.name}]${value.toString()}`;
|
|
2110
|
+
return value;
|
|
2111
|
+
});
|
|
2112
|
+
} catch {
|
|
2113
|
+
return `unserializable:${config.codegen.outDir}:${config.contracts.glob}`;
|
|
2114
|
+
}
|
|
2115
|
+
}
|
|
2116
|
+
async function discoverInputFiles(config) {
|
|
2117
|
+
const globs = [config.contracts.glob, config.forms.watch];
|
|
2118
|
+
if (config.pages) globs.push(config.pages.glob);
|
|
2119
|
+
const cwd = config.codegen.cwd;
|
|
2120
|
+
const matched = await fg2(globs, { cwd, absolute: true, onlyFiles: true });
|
|
2121
|
+
return [...new Set(matched)].sort();
|
|
2122
|
+
}
|
|
2123
|
+
async function computeInputsHash(config) {
|
|
2124
|
+
const hash = createHash("sha256");
|
|
2125
|
+
hash.update(`version:${VERSION}
|
|
2126
|
+
`);
|
|
2127
|
+
hash.update(`config:${serializeConfig(config)}
|
|
2128
|
+
`);
|
|
2129
|
+
const inputFiles = await discoverInputFiles(config);
|
|
2130
|
+
const cwd = config.codegen.cwd;
|
|
2131
|
+
for (const file of inputFiles) {
|
|
2132
|
+
const contents = await readFile2(file, "utf8");
|
|
2133
|
+
hash.update(`file:${relative6(cwd, file)}
|
|
2134
|
+
`);
|
|
2135
|
+
hash.update(contents);
|
|
2136
|
+
hash.update("\n");
|
|
2137
|
+
}
|
|
2138
|
+
return hash.digest("hex");
|
|
2139
|
+
}
|
|
2140
|
+
async function readManifest(outDir) {
|
|
2141
|
+
try {
|
|
2142
|
+
const raw = await readFile2(join12(outDir, MANIFEST_FILE), "utf8");
|
|
2143
|
+
const parsed = JSON.parse(raw);
|
|
2144
|
+
if (!isManifestShape(parsed)) return null;
|
|
2145
|
+
return { version: parsed.version, hash: parsed.hash, files: parsed.files };
|
|
2146
|
+
} catch {
|
|
2147
|
+
return null;
|
|
2148
|
+
}
|
|
2149
|
+
}
|
|
2150
|
+
async function writeManifest(outDir, manifest) {
|
|
2151
|
+
await writeFile9(join12(outDir, MANIFEST_FILE), `${JSON.stringify(manifest, null, 2)}
|
|
2152
|
+
`, "utf8");
|
|
2153
|
+
}
|
|
2154
|
+
async function listOutputFiles(outDir) {
|
|
2155
|
+
const found = [];
|
|
2156
|
+
async function walk(dir) {
|
|
2157
|
+
const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
2158
|
+
for (const entry of entries) {
|
|
2159
|
+
const abs = join12(dir, entry.name);
|
|
2160
|
+
if (entry.isDirectory()) {
|
|
2161
|
+
await walk(abs);
|
|
2162
|
+
} else if (entry.isFile()) {
|
|
2163
|
+
const rel = relative6(outDir, abs);
|
|
2164
|
+
if (rel === MANIFEST_FILE || rel === LOCK_FILE) continue;
|
|
2165
|
+
found.push(rel);
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
await walk(outDir);
|
|
2170
|
+
return found.sort();
|
|
2171
|
+
}
|
|
2172
|
+
async function allOutputsExist(outDir, files) {
|
|
2173
|
+
const present = new Set(await listOutputFiles(outDir));
|
|
2174
|
+
return files.every((file) => present.has(file));
|
|
2175
|
+
}
|
|
2176
|
+
async function isManifestFresh(outDir, manifest, inputsHash) {
|
|
2177
|
+
if (manifest === null) return false;
|
|
2178
|
+
if (manifest.version !== VERSION) return false;
|
|
2179
|
+
if (manifest.hash !== inputsHash) return false;
|
|
2180
|
+
if (manifest.files.length === 0) return false;
|
|
2181
|
+
return allOutputsExist(outDir, manifest.files);
|
|
2182
|
+
}
|
|
2183
|
+
|
|
2090
2184
|
// src/util/debug-log.ts
|
|
2091
2185
|
var debugEnabled = false;
|
|
2092
2186
|
function setCodegenDebug(enabled) {
|
|
@@ -2099,6 +2193,12 @@ function debugWarn(message) {
|
|
|
2099
2193
|
// src/generate.ts
|
|
2100
2194
|
async function generate(config, inputRoutes = []) {
|
|
2101
2195
|
setCodegenDebug(config.debug);
|
|
2196
|
+
const inputsHash = await computeInputsHash(config);
|
|
2197
|
+
const manifest = await readManifest(config.codegen.outDir);
|
|
2198
|
+
if (await isManifestFresh(config.codegen.outDir, manifest, inputsHash)) {
|
|
2199
|
+
console.log(`[nestjs-codegen] ${config.codegen.outDir} up to date, skipped`);
|
|
2200
|
+
return;
|
|
2201
|
+
}
|
|
2102
2202
|
const extensions = config.extensions ?? [];
|
|
2103
2203
|
let routes = inputRoutes;
|
|
2104
2204
|
const ctx = createExtensionContext(config, () => routes);
|
|
@@ -2155,21 +2255,27 @@ async function generate(config, inputRoutes = []) {
|
|
|
2155
2255
|
if (extensions.length > 0) {
|
|
2156
2256
|
const extraFiles = await collectEmittedFiles(extensions, ctx);
|
|
2157
2257
|
for (const file of extraFiles) {
|
|
2158
|
-
const dest =
|
|
2258
|
+
const dest = join13(config.codegen.outDir, file.path);
|
|
2159
2259
|
await mkdir9(dirname2(dest), { recursive: true });
|
|
2160
|
-
await
|
|
2260
|
+
await writeFile10(dest, file.contents, "utf8");
|
|
2161
2261
|
}
|
|
2162
2262
|
}
|
|
2263
|
+
const outputFiles = await listOutputFiles(config.codegen.outDir);
|
|
2264
|
+
await writeManifest(config.codegen.outDir, {
|
|
2265
|
+
version: VERSION,
|
|
2266
|
+
hash: inputsHash,
|
|
2267
|
+
files: outputFiles
|
|
2268
|
+
});
|
|
2163
2269
|
}
|
|
2164
2270
|
|
|
2165
2271
|
// src/watch/watcher.ts
|
|
2166
|
-
import { readFile as
|
|
2167
|
-
import { join as
|
|
2272
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
2273
|
+
import { join as join16 } from "path";
|
|
2168
2274
|
import chokidar from "chokidar";
|
|
2169
2275
|
|
|
2170
2276
|
// src/discovery/contracts-fast.ts
|
|
2171
|
-
import { join as
|
|
2172
|
-
import
|
|
2277
|
+
import { join as join14, resolve as resolve3 } from "path";
|
|
2278
|
+
import fg3 from "fast-glob";
|
|
2173
2279
|
import {
|
|
2174
2280
|
Node as Node8,
|
|
2175
2281
|
Project as Project3
|
|
@@ -3584,6 +3690,55 @@ function extractParamsType(method, sourceFile, project) {
|
|
|
3584
3690
|
}
|
|
3585
3691
|
return entries.length > 0 ? `{ ${entries.join("; ")} }` : null;
|
|
3586
3692
|
}
|
|
3693
|
+
function extractUploadedFiles(method) {
|
|
3694
|
+
const FILE = "File | Blob";
|
|
3695
|
+
const entries = [];
|
|
3696
|
+
let multipart = false;
|
|
3697
|
+
const hasUploadedFileParam = method.getParameters().some(
|
|
3698
|
+
(p) => p.getDecorators().some((d) => {
|
|
3699
|
+
const name = d.getName();
|
|
3700
|
+
return name === "UploadedFile" || name === "UploadedFiles";
|
|
3701
|
+
})
|
|
3702
|
+
);
|
|
3703
|
+
for (const decorator of method.getDecorators()) {
|
|
3704
|
+
if (decorator.getName() !== "UseInterceptors") continue;
|
|
3705
|
+
for (const arg of decorator.getArguments()) {
|
|
3706
|
+
if (!Node6.isCallExpression(arg)) continue;
|
|
3707
|
+
const interceptor = arg.getExpression().getText();
|
|
3708
|
+
const callArgs = arg.getArguments();
|
|
3709
|
+
const firstArg2 = callArgs[0];
|
|
3710
|
+
if (interceptor === "FileInterceptor") {
|
|
3711
|
+
if (firstArg2 && Node6.isStringLiteral(firstArg2)) {
|
|
3712
|
+
entries.push(`${firstArg2.getLiteralValue()}: ${FILE}`);
|
|
3713
|
+
multipart = true;
|
|
3714
|
+
}
|
|
3715
|
+
} else if (interceptor === "FilesInterceptor") {
|
|
3716
|
+
if (firstArg2 && Node6.isStringLiteral(firstArg2)) {
|
|
3717
|
+
entries.push(`${firstArg2.getLiteralValue()}: Array<${FILE}>`);
|
|
3718
|
+
multipart = true;
|
|
3719
|
+
}
|
|
3720
|
+
} else if (interceptor === "FileFieldsInterceptor") {
|
|
3721
|
+
if (firstArg2 && Node6.isArrayLiteralExpression(firstArg2)) {
|
|
3722
|
+
for (const el of firstArg2.getElements()) {
|
|
3723
|
+
if (!Node6.isObjectLiteralExpression(el)) continue;
|
|
3724
|
+
const nameProp = el.getProperty("name");
|
|
3725
|
+
if (nameProp && Node6.isPropertyAssignment(nameProp)) {
|
|
3726
|
+
const init = nameProp.getInitializer();
|
|
3727
|
+
if (init && Node6.isStringLiteral(init)) {
|
|
3728
|
+
entries.push(`${init.getLiteralValue()}: Array<${FILE}>`);
|
|
3729
|
+
}
|
|
3730
|
+
}
|
|
3731
|
+
}
|
|
3732
|
+
multipart = true;
|
|
3733
|
+
}
|
|
3734
|
+
} else if (interceptor === "AnyFilesInterceptor") {
|
|
3735
|
+
multipart = true;
|
|
3736
|
+
}
|
|
3737
|
+
}
|
|
3738
|
+
}
|
|
3739
|
+
if (hasUploadedFileParam) multipart = true;
|
|
3740
|
+
return { fields: entries.length > 0 ? entries.join("; ") : null, multipart };
|
|
3741
|
+
}
|
|
3587
3742
|
function extractResponseType(method, sourceFile, project) {
|
|
3588
3743
|
const apiResponseDecorator = method.getDecorators().find((d) => d.getName() === "ApiResponse" && (apiResponseStatus(d) ?? 0) < 400);
|
|
3589
3744
|
if (apiResponseDecorator) {
|
|
@@ -3718,6 +3873,11 @@ function extractDtoContract(method, sourceFile, project) {
|
|
|
3718
3873
|
let body = extractBodyType(method, sourceFile, project);
|
|
3719
3874
|
const filterInfo = extractApplyFilterInfo(method, sourceFile, project);
|
|
3720
3875
|
const query = extractQueryType(method, sourceFile, project);
|
|
3876
|
+
const uploads = extractUploadedFiles(method);
|
|
3877
|
+
if (uploads.fields) {
|
|
3878
|
+
const fileObject = `{ ${uploads.fields} }`;
|
|
3879
|
+
body = body ? `(${body}) & ${fileObject}` : fileObject;
|
|
3880
|
+
}
|
|
3721
3881
|
const streamElement = detectStreamElement(method);
|
|
3722
3882
|
const isStream = streamElement !== null;
|
|
3723
3883
|
if (filterInfo && filterInfo.source === "body") {
|
|
@@ -3727,7 +3887,7 @@ function extractDtoContract(method, sourceFile, project) {
|
|
|
3727
3887
|
const paramsType = extractParamsType(method, sourceFile, project);
|
|
3728
3888
|
const response = isStream ? resolveTypeNodeToString(streamElement, sourceFile, project, 3) : extractResponseType(method, sourceFile, project);
|
|
3729
3889
|
const errorInfo = extractErrorType(method, sourceFile, project);
|
|
3730
|
-
if (body === null && query === null && paramsType === null && response === "unknown" && errorInfo === null && filterInfo === null && !isStream) {
|
|
3890
|
+
if (body === null && query === null && paramsType === null && response === "unknown" && errorInfo === null && filterInfo === null && !isStream && !uploads.multipart) {
|
|
3731
3891
|
return null;
|
|
3732
3892
|
}
|
|
3733
3893
|
let bodyRef = null;
|
|
@@ -3800,7 +3960,8 @@ function extractDtoContract(method, sourceFile, project) {
|
|
|
3800
3960
|
formWarnings,
|
|
3801
3961
|
bodySchema,
|
|
3802
3962
|
querySchema,
|
|
3803
|
-
stream: isStream
|
|
3963
|
+
stream: isStream,
|
|
3964
|
+
multipart: uploads.multipart
|
|
3804
3965
|
};
|
|
3805
3966
|
}
|
|
3806
3967
|
function resolveParamClass(method, decoratorName, sourceFile, project) {
|
|
@@ -3946,7 +4107,7 @@ async function discoverContractsFast(opts) {
|
|
|
3946
4107
|
const { cwd, glob, tsconfig } = opts;
|
|
3947
4108
|
const tsconfigPath = resolveTsconfigPath(cwd, tsconfig);
|
|
3948
4109
|
const project = createDiscoveryProject(tsconfigPath);
|
|
3949
|
-
const files = await
|
|
4110
|
+
const files = await fg3(glob, { cwd, absolute: true, onlyFiles: true });
|
|
3950
4111
|
for (const f of files) {
|
|
3951
4112
|
project.addSourceFileAtPath(f);
|
|
3952
4113
|
}
|
|
@@ -3954,7 +4115,7 @@ async function discoverContractsFast(opts) {
|
|
|
3954
4115
|
return extractAllRoutes(project);
|
|
3955
4116
|
}
|
|
3956
4117
|
function resolveTsconfigPath(cwd, tsconfig) {
|
|
3957
|
-
return tsconfig ? resolve3(tsconfig) :
|
|
4118
|
+
return tsconfig ? resolve3(tsconfig) : join14(cwd, "tsconfig.json");
|
|
3958
4119
|
}
|
|
3959
4120
|
function createDiscoveryProject(tsconfigPath) {
|
|
3960
4121
|
try {
|
|
@@ -4019,7 +4180,7 @@ var PersistentDiscovery = class _PersistentDiscovery {
|
|
|
4019
4180
|
const project = createDiscoveryProject(tsconfigPath);
|
|
4020
4181
|
bindDiscoveryContext(project, cwd, tsconfigPath);
|
|
4021
4182
|
const instance = new _PersistentDiscovery(project, cwd, glob);
|
|
4022
|
-
const files = await
|
|
4183
|
+
const files = await fg3(glob, { cwd, absolute: true, onlyFiles: true });
|
|
4023
4184
|
for (const f of files) {
|
|
4024
4185
|
project.addSourceFileAtPath(f);
|
|
4025
4186
|
instance.controllerPaths.add(f);
|
|
@@ -4048,7 +4209,7 @@ var PersistentDiscovery = class _PersistentDiscovery {
|
|
|
4048
4209
|
}
|
|
4049
4210
|
}
|
|
4050
4211
|
const globbed = new Set(
|
|
4051
|
-
await
|
|
4212
|
+
await fg3(this.glob, { cwd: this.cwd, absolute: true, onlyFiles: true })
|
|
4052
4213
|
);
|
|
4053
4214
|
for (const f of globbed) {
|
|
4054
4215
|
if (!this.controllerPaths.has(f)) {
|
|
@@ -4285,7 +4446,8 @@ function extractDtoRoute(args) {
|
|
|
4285
4446
|
formWarnings: dtoContract?.formWarnings ?? [],
|
|
4286
4447
|
bodySchema: dtoContract?.bodySchema ?? null,
|
|
4287
4448
|
querySchema: dtoContract?.querySchema ?? null,
|
|
4288
|
-
stream: dtoContract?.stream ?? false
|
|
4449
|
+
stream: dtoContract?.stream ?? false,
|
|
4450
|
+
multipart: dtoContract?.multipart ?? false
|
|
4289
4451
|
}
|
|
4290
4452
|
});
|
|
4291
4453
|
}
|
|
@@ -4329,9 +4491,9 @@ function extractFromSourceFile(sourceFile, project) {
|
|
|
4329
4491
|
|
|
4330
4492
|
// src/watch/lock-file.ts
|
|
4331
4493
|
import { open } from "fs/promises";
|
|
4332
|
-
import { mkdir as mkdir10, readFile as
|
|
4333
|
-
import { join as
|
|
4334
|
-
var
|
|
4494
|
+
import { mkdir as mkdir10, readFile as readFile3, unlink } from "fs/promises";
|
|
4495
|
+
import { join as join15 } from "path";
|
|
4496
|
+
var LOCK_FILE2 = ".watcher.lock";
|
|
4335
4497
|
function isProcessAlive(pid) {
|
|
4336
4498
|
try {
|
|
4337
4499
|
process.kill(pid, 0);
|
|
@@ -4342,7 +4504,7 @@ function isProcessAlive(pid) {
|
|
|
4342
4504
|
}
|
|
4343
4505
|
async function acquireLock(outDir) {
|
|
4344
4506
|
await mkdir10(outDir, { recursive: true });
|
|
4345
|
-
const lockPath =
|
|
4507
|
+
const lockPath = join15(outDir, LOCK_FILE2);
|
|
4346
4508
|
const lockData = { pid: process.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
4347
4509
|
try {
|
|
4348
4510
|
const fd = await open(lockPath, "wx");
|
|
@@ -4352,7 +4514,7 @@ async function acquireLock(outDir) {
|
|
|
4352
4514
|
} catch (err) {
|
|
4353
4515
|
if (err.code === "EEXIST") {
|
|
4354
4516
|
try {
|
|
4355
|
-
const raw = await
|
|
4517
|
+
const raw = await readFile3(lockPath, "utf8");
|
|
4356
4518
|
const existing = JSON.parse(raw);
|
|
4357
4519
|
if (isProcessAlive(existing.pid)) return null;
|
|
4358
4520
|
await unlink(lockPath);
|
|
@@ -4377,12 +4539,12 @@ async function acquireLock(outDir) {
|
|
|
4377
4539
|
var PAGES_DEBOUNCE_MS = 150;
|
|
4378
4540
|
var NO_OP_WATCHER = { close: async () => {
|
|
4379
4541
|
} };
|
|
4380
|
-
async function watch(config, onChange) {
|
|
4542
|
+
async function watch(config, onChange, options = {}) {
|
|
4381
4543
|
const lock = await acquireLock(config.codegen.outDir);
|
|
4382
4544
|
if (lock === null) {
|
|
4383
4545
|
let holderPid = "unknown";
|
|
4384
4546
|
try {
|
|
4385
|
-
const raw = await
|
|
4547
|
+
const raw = await readFile4(join16(config.codegen.outDir, ".watcher.lock"), "utf8");
|
|
4386
4548
|
const data = JSON.parse(raw);
|
|
4387
4549
|
if (data.pid !== void 0) holderPid = String(data.pid);
|
|
4388
4550
|
} catch {
|
|
@@ -4405,22 +4567,33 @@ async function watch(config, onChange) {
|
|
|
4405
4567
|
}
|
|
4406
4568
|
return discovery;
|
|
4407
4569
|
}
|
|
4408
|
-
|
|
4409
|
-
const initialRoutes = (await getDiscovery()).discover();
|
|
4410
|
-
lastRoutes = initialRoutes;
|
|
4411
|
-
await generate(config, initialRoutes);
|
|
4412
|
-
} catch (err) {
|
|
4413
|
-
console.warn(
|
|
4414
|
-
`[nestjs-codegen] Initial route discovery failed, falling back to pages-only: ${err instanceof Error ? err.message : String(err)}`
|
|
4415
|
-
);
|
|
4570
|
+
async function runInitialPass() {
|
|
4416
4571
|
try {
|
|
4417
|
-
await
|
|
4418
|
-
|
|
4572
|
+
const initialRoutes = (await getDiscovery()).discover();
|
|
4573
|
+
lastRoutes = initialRoutes;
|
|
4574
|
+
await generate(config, initialRoutes);
|
|
4575
|
+
} catch (err) {
|
|
4576
|
+
console.warn(
|
|
4577
|
+
`[nestjs-codegen] Initial route discovery failed, falling back to pages-only: ${err instanceof Error ? err.message : String(err)}`
|
|
4578
|
+
);
|
|
4579
|
+
try {
|
|
4580
|
+
await generate(config, lastRoutes);
|
|
4581
|
+
} catch {
|
|
4582
|
+
}
|
|
4419
4583
|
}
|
|
4420
4584
|
}
|
|
4585
|
+
if (options.deferInitialGenerate) {
|
|
4586
|
+
void runInitialPass().catch((err) => {
|
|
4587
|
+
console.warn(
|
|
4588
|
+
`[nestjs-codegen] Background initial generate failed: ${err instanceof Error ? err.message : String(err)}`
|
|
4589
|
+
);
|
|
4590
|
+
});
|
|
4591
|
+
} else {
|
|
4592
|
+
await runInitialPass();
|
|
4593
|
+
}
|
|
4421
4594
|
let pagesDebounceTimer;
|
|
4422
4595
|
const pagesGlob = config.pages?.glob ?? ".nestjs-codegen-no-pages";
|
|
4423
|
-
const pagesWatcher = chokidar.watch(
|
|
4596
|
+
const pagesWatcher = chokidar.watch(join16(config.codegen.cwd, pagesGlob), {
|
|
4424
4597
|
ignoreInitial: true,
|
|
4425
4598
|
persistent: true,
|
|
4426
4599
|
awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
|
|
@@ -4447,7 +4620,7 @@ async function watch(config, onChange) {
|
|
|
4447
4620
|
pagesWatcher.on("unlink", schedulePagesRegenerate);
|
|
4448
4621
|
let contractsDebounceTimer;
|
|
4449
4622
|
const pendingChangedPaths = /* @__PURE__ */ new Set();
|
|
4450
|
-
const contractsWatcher = chokidar.watch(
|
|
4623
|
+
const contractsWatcher = chokidar.watch(join16(config.codegen.cwd, config.contracts.glob), {
|
|
4451
4624
|
ignoreInitial: true,
|
|
4452
4625
|
persistent: true,
|
|
4453
4626
|
awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
|
|
@@ -4477,7 +4650,7 @@ async function watch(config, onChange) {
|
|
|
4477
4650
|
contractsWatcher.on("add", (p) => scheduleContractsRegenerate(p));
|
|
4478
4651
|
contractsWatcher.on("change", (p) => scheduleContractsRegenerate(p));
|
|
4479
4652
|
contractsWatcher.on("unlink", (p) => scheduleContractsRegenerate(p));
|
|
4480
|
-
const formsWatcher = chokidar.watch(
|
|
4653
|
+
const formsWatcher = chokidar.watch(join16(config.codegen.cwd, config.forms.watch), {
|
|
4481
4654
|
ignoreInitial: true,
|
|
4482
4655
|
persistent: true,
|
|
4483
4656
|
awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
|
|
@@ -4587,7 +4760,7 @@ function createChainModuleRenderer(opts) {
|
|
|
4587
4760
|
}
|
|
4588
4761
|
|
|
4589
4762
|
// src/index.ts
|
|
4590
|
-
var VERSION = "0.
|
|
4763
|
+
var VERSION = "0.13.0";
|
|
4591
4764
|
export {
|
|
4592
4765
|
CodegenError,
|
|
4593
4766
|
ConfigError,
|