genoc 0.3.0 → 0.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.
Files changed (65) hide show
  1. package/README.md +30 -186
  2. package/dist/analyzer/analyze.d.ts +18 -0
  3. package/dist/analyzer/analyze.js +534 -0
  4. package/dist/analyzer/path-analyzer.d.ts +43 -6
  5. package/dist/analyzer/path-analyzer.js +57 -84
  6. package/dist/analyzer/schema-mapper.d.ts +3 -2
  7. package/dist/analyzer/schema-mapper.js +11 -7
  8. package/dist/analyzer/types.d.ts +119 -0
  9. package/dist/cli/app.d.ts +4 -2
  10. package/dist/cli/app.js +23 -4
  11. package/dist/cli/config-loader.d.ts +20 -0
  12. package/dist/cli/config-loader.js +186 -0
  13. package/dist/cli/config-schema.d.ts +105 -0
  14. package/dist/cli/config-schema.js +183 -0
  15. package/dist/cli/impl.d.ts +9 -1
  16. package/dist/cli/impl.js +186 -36
  17. package/dist/generator/client-generator.d.ts +13 -14
  18. package/dist/generator/client-generator.js +45 -77
  19. package/dist/generator/contracts-generator.d.ts +11 -9
  20. package/dist/generator/contracts-generator.js +45 -491
  21. package/dist/generator/method-generator.d.ts +1 -1
  22. package/dist/generator/method-generator.js +10 -150
  23. package/dist/parser/ref-resolver.d.ts +1 -1
  24. package/dist/parser/ref-resolver.js +1 -2
  25. package/dist/parser/version/index.d.ts +4 -16
  26. package/dist/parser/version/index.js +3 -14
  27. package/dist/parser/version/v3.0/validator.d.ts +1 -1
  28. package/dist/parser/version/v3.1/validator.d.ts +17 -0
  29. package/dist/parser/version/v3.1/validator.js +126 -0
  30. package/dist/parser/version/validate.d.ts +9 -0
  31. package/dist/parser/version/validate.js +12 -0
  32. package/dist/parser/version/version-detector.d.ts +21 -0
  33. package/dist/parser/version/version-detector.js +19 -0
  34. package/dist/pipeline.d.ts +13 -0
  35. package/dist/pipeline.js +24 -0
  36. package/dist/types/client.d.ts +0 -12
  37. package/dist/utils/generator-helpers.d.ts +1 -24
  38. package/dist/utils/generator-helpers.js +11 -78
  39. package/dist/utils/operation-naming.d.ts +84 -0
  40. package/dist/utils/operation-naming.js +178 -0
  41. package/package.json +4 -8
  42. package/dist/index.d.ts +0 -9
  43. package/dist/index.js +0 -11
  44. package/dist/parser/validators.d.ts +0 -7
  45. package/dist/parser/validators.js +0 -79
  46. package/dist/parser/version/normalized-spec.d.ts +0 -199
  47. package/dist/parser/version/registry.d.ts +0 -28
  48. package/dist/parser/version/registry.js +0 -44
  49. package/dist/parser/version/v3.0/index.d.ts +0 -3
  50. package/dist/parser/version/v3.0/index.js +0 -3
  51. package/dist/parser/version/v3.0/normalizer.d.ts +0 -15
  52. package/dist/parser/version/v3.0/normalizer.js +0 -389
  53. package/dist/parser/version/v3.0/strategy.d.ts +0 -27
  54. package/dist/parser/version/v3.0/strategy.js +0 -96
  55. package/dist/parser/version/v3.1/index.d.ts +0 -1
  56. package/dist/parser/version/v3.1/index.js +0 -1
  57. package/dist/parser/version/v3.1/strategy.d.ts +0 -42
  58. package/dist/parser/version/v3.1/strategy.js +0 -513
  59. package/dist/parser/version/v3.2/index.d.ts +0 -4
  60. package/dist/parser/version/v3.2/index.js +0 -4
  61. package/dist/parser/version/v3.2/strategy.d.ts +0 -39
  62. package/dist/parser/version/v3.2/strategy.js +0 -57
  63. package/dist/parser/version/version-strategy.d.ts +0 -31
  64. package/dist/parser/version/version-strategy.js +0 -1
  65. /package/dist/{parser/version/normalized-spec.js → analyzer/types.js} +0 -0
@@ -1,63 +1,16 @@
1
1
  import { RefResolver } from '../parser/ref-resolver.js';
2
- import { sanitizeTypeName, getOperationTypePrefix } from '../utils/generator-helpers.js';
2
+ import { isBinaryContentType } from '../utils/generator-helpers.js';
3
+ import { getOperationTypePrefix } from '../utils/operation-naming.js';
3
4
  import { getMethodName } from './naming.js';
4
5
  const HTTP_METHODS = ['get', 'post', 'put', 'patch', 'delete', 'options', 'head', 'trace'];
5
- function isRef(obj) {
6
- return obj !== null && typeof obj === 'object' && '$ref' in obj;
7
- }
8
- function isBinaryContentType(ct) {
9
- if (ct === 'application/octet-stream')
10
- return true;
11
- if (ct.startsWith('image/'))
12
- return true;
13
- if (ct.startsWith('video/'))
14
- return true;
15
- if (ct.startsWith('audio/'))
16
- return true;
17
- return false;
18
- }
19
- function schemaToTsType(schema, resolver) {
6
+ /**
7
+ * Resolve a possibly-$ref-ed schema and check for an exact top-level `format: binary`
8
+ * match. Top-level only — intentionally no deep-walk into items/allOf/oneOf/anyOf.
9
+ */
10
+ function isBinarySchema(schema, resolver) {
20
11
  if (!schema)
21
- return 'unknown';
22
- if (isRef(schema)) {
23
- const resolved = resolver.resolve(schema);
24
- const refStr = schema.$ref;
25
- const lastSegment = sanitizeTypeName(refStr.split('/').pop());
26
- if (lastSegment && resolved.type) {
27
- return lastSegment;
28
- }
29
- return lastSegment ?? 'unknown';
30
- }
31
- const s = schema;
32
- if (s.type === undefined)
33
- return 'unknown';
34
- if (Array.isArray(s.type)) {
35
- const nonNull = s.type.filter((t) => t !== 'null');
36
- if (nonNull.length === 0)
37
- return 'null';
38
- return schemaToTsType({ ...s, type: nonNull[0] }, resolver);
39
- }
40
- switch (s.type) {
41
- case 'string':
42
- return 'string';
43
- case 'integer':
44
- case 'number':
45
- return 'number';
46
- case 'boolean':
47
- return 'boolean';
48
- case 'array':
49
- if (s.items) {
50
- const itemType = schemaToTsType(s.items, resolver);
51
- return `${itemType}[]`;
52
- }
53
- return 'unknown[]';
54
- case 'object':
55
- return 'object';
56
- case 'null':
57
- return 'null';
58
- default:
59
- return 'unknown';
60
- }
12
+ return false;
13
+ return resolver.resolveSchema(schema).format === 'binary';
61
14
  }
62
15
  function resolveParameter(param, resolver) {
63
16
  return resolver.resolve(param);
@@ -75,7 +28,6 @@ function analyzeParameter(param, resolver) {
75
28
  in: param.in,
76
29
  required: param.required ?? param.in === 'path',
77
30
  schema,
78
- tsType: schemaToTsType(param.schema, resolver),
79
31
  description: param.description,
80
32
  deprecated: param.deprecated,
81
33
  example: param.example,
@@ -100,28 +52,40 @@ function mergeParameters(pathItemParams, operationParams, resolver) {
100
52
  }
101
53
  return merged;
102
54
  }
55
+ /**
56
+ * Analyze one request body. Returns the structural facts plus the RAW
57
+ * (possibly `$ref`) first-content schema as a separate value — the schema is
58
+ * analyzer-internal translation input for `analyze()` and does not travel on
59
+ * the `AnalyzedRequestBody` seam.
60
+ */
103
61
  function analyzeRequestBody(body, resolver) {
104
62
  if (!body)
105
63
  return undefined;
106
64
  const resolved = resolveRequestBody(body, resolver);
107
65
  const contentTypes = Object.keys(resolved.content);
108
66
  let schema;
109
- let tsType = 'unknown';
110
67
  if (contentTypes.length > 0) {
111
68
  const firstContent = resolved.content[contentTypes[0]];
112
69
  if (firstContent?.schema) {
113
70
  schema = firstContent.schema;
114
- tsType = schemaToTsType(schema, resolver);
115
71
  }
116
72
  }
117
73
  const isMultipart = contentTypes.length > 0 && contentTypes[0] === 'multipart/form-data';
74
+ // Contract with the generators: multipart bodies keep the FileInput shape
75
+ // and must never be Blob-ified, so the binary signals are bypassed.
76
+ const isBinary = isMultipart
77
+ ? false
78
+ : contentTypes.some(isBinaryContentType) || isBinarySchema(schema, resolver);
118
79
  return {
119
- required: resolved.required ?? false,
120
- contentTypes,
121
- schema,
122
- tsType,
123
- isMultipart,
124
- description: resolved.description,
80
+ requestBody: {
81
+ required: resolved.required ?? false,
82
+ contentTypes,
83
+ hasSchema: schema !== undefined,
84
+ isMultipart,
85
+ isBinary,
86
+ description: resolved.description,
87
+ },
88
+ rawSchema: schema,
125
89
  };
126
90
  }
127
91
  function analyzeResponses(responses, resolver) {
@@ -129,7 +93,6 @@ function analyzeResponses(responses, resolver) {
129
93
  for (const [statusCode, response] of Object.entries(responses)) {
130
94
  const resolved = resolveResponse(response, resolver);
131
95
  let schema;
132
- let tsType = 'unknown';
133
96
  let contentTypes = [];
134
97
  if (resolved.content) {
135
98
  contentTypes = Object.keys(resolved.content);
@@ -137,24 +100,22 @@ function analyzeResponses(responses, resolver) {
137
100
  const firstContent = resolved.content[contentTypes[0]];
138
101
  if (firstContent?.schema) {
139
102
  schema = firstContent.schema;
140
- tsType = schemaToTsType(schema, resolver);
141
103
  }
142
104
  }
143
105
  }
144
106
  // Empty-body success responses → void
145
- if (tsType === 'unknown' &&
107
+ const isVoid = schema === undefined &&
146
108
  statusCode.startsWith('2') &&
147
- (!resolved.content || contentTypes.length === 0)) {
148
- tsType = 'void';
149
- }
150
- const isBinary = contentTypes.length > 0 && isBinaryContentType(contentTypes[0]);
109
+ (!resolved.content || contentTypes.length === 0);
110
+ const isBinary = contentTypes.length > 0 &&
111
+ (isBinaryContentType(contentTypes[0]) || isBinarySchema(schema, resolver));
151
112
  result.push({
152
113
  statusCode,
153
114
  description: resolved.description,
154
115
  schema,
155
- tsType,
156
116
  isSuccess: statusCode.startsWith('2'),
157
117
  isBinary,
118
+ isVoid,
158
119
  });
159
120
  }
160
121
  return result;
@@ -183,18 +144,19 @@ function categorizeParameters(params) {
183
144
  return { pathParams, queryParams, headerParams, cookieParams };
184
145
  }
185
146
  /**
186
- * Analyze all paths and operations from an OpenAPI document into structured data
187
- * for code generation.
147
+ * Analyze all paths and operations from an OpenAPI document into structured
148
+ * data for code generation, carrying the raw request body schemas alongside.
188
149
  *
189
150
  * @param doc - The parsed and validated OpenAPI document
190
151
  * @param resolver - A RefResolver for resolving $ref pointers
191
152
  * @param strategy - Method naming strategy (defaults to 'path-based')
192
- * @returns Array of AnalyzedOperation objects
153
+ * @returns Operations with parallel raw request body schemas
193
154
  */
194
- export function analyzePaths(doc, resolver, strategy = 'path-based') {
155
+ export function analyzePathsDetailed(doc, resolver, strategy = 'path-based') {
195
156
  const operations = [];
157
+ const requestBodySchemas = [];
196
158
  if (!doc.paths)
197
- return operations;
159
+ return { operations, requestBodySchemas };
198
160
  const usedTypePrefixes = new Set();
199
161
  const usedMethodNames = new Set();
200
162
  for (const [urlPath, pathItem] of Object.entries(doc.paths)) {
@@ -205,7 +167,7 @@ export function analyzePaths(doc, resolver, strategy = 'path-based') {
205
167
  const mergedParams = mergeParameters(pathItem.parameters, operation.parameters, resolver);
206
168
  const analyzedParams = mergedParams.map((p) => analyzeParameter(p, resolver));
207
169
  const categorized = categorizeParameters(analyzedParams);
208
- const requestBody = analyzeRequestBody(operation.requestBody, resolver);
170
+ const requestBodyResult = analyzeRequestBody(operation.requestBody, resolver);
209
171
  const responses = analyzeResponses(operation.responses, resolver);
210
172
  const methodName = getMethodName(method, urlPath, operation.operationId, strategy);
211
173
  operations.push({
@@ -218,17 +180,16 @@ export function analyzePaths(doc, resolver, strategy = 'path-based') {
218
180
  deprecated: operation.deprecated ?? false,
219
181
  tags: operation.tags ?? [],
220
182
  ...categorized,
221
- requestBody,
183
+ requestBody: requestBodyResult?.requestBody,
222
184
  responses,
223
185
  });
186
+ requestBodySchemas.push(requestBodyResult?.rawSchema);
224
187
  }
225
188
  }
226
189
  // Dedupe type prefixes and method names so that distinct routes folding to
227
190
  // the same identifier (e.g. "/weird" and "/weird-", or "/Weird" and
228
191
  // "/weird") cannot produce duplicate exported types or client methods.
229
- // NOTE: analyzePaths runs twice per generation (contracts pass + client
230
- // pass); this assignment must stay deterministic - it relies on insertion
231
- // order and the fixed HTTP_METHODS loop - so both passes agree.
192
+ // Assignment relies on insertion order and the fixed HTTP_METHODS loop.
232
193
  for (const op of operations) {
233
194
  const baseTypePrefix = getOperationTypePrefix(op);
234
195
  let typePrefix = baseTypePrefix;
@@ -249,5 +210,17 @@ export function analyzePaths(doc, resolver, strategy = 'path-based') {
249
210
  usedMethodNames.add(methodName);
250
211
  op.methodName = methodName;
251
212
  }
252
- return operations;
213
+ return { operations, requestBodySchemas };
214
+ }
215
+ /**
216
+ * Analyze all paths and operations from an OpenAPI document into structured
217
+ * data for code generation.
218
+ *
219
+ * @param doc - The parsed and validated OpenAPI document
220
+ * @param resolver - A RefResolver for resolving $ref pointers
221
+ * @param strategy - Method naming strategy (defaults to 'path-based')
222
+ * @returns Array of AnalyzedOperation objects
223
+ */
224
+ export function analyzePaths(doc, resolver, strategy = 'path-based') {
225
+ return analyzePathsDetailed(doc, resolver, strategy).operations;
253
226
  }
@@ -17,11 +17,12 @@ export declare class SchemaMapper {
17
17
  private readonly discriminatorTargets;
18
18
  private readonly reservedNames;
19
19
  private readonly brandedTypes;
20
- private static nullableWarned;
20
+ private nullableWarned;
21
+ private readonly warnSink;
21
22
  constructor(resolver: RefResolver, typeNameGenerator?: TypeNameGenerator, discriminatorTargets?: Map<string, {
22
23
  propertyName: string;
23
24
  literalValue: string;
24
- }>, reservedNames?: Set<string>);
25
+ }>, reservedNames?: Set<string>, warnSink?: (message: string) => void);
25
26
  getBrandedTypes(): Map<string, {
26
27
  name: string;
27
28
  format: string;
@@ -40,8 +40,9 @@ export class SchemaMapper {
40
40
  discriminatorTargets;
41
41
  reservedNames;
42
42
  brandedTypes = new Map();
43
- static nullableWarned = false;
44
- constructor(resolver, typeNameGenerator, discriminatorTargets, reservedNames) {
43
+ nullableWarned = false;
44
+ warnSink;
45
+ constructor(resolver, typeNameGenerator, discriminatorTargets, reservedNames, warnSink) {
45
46
  // NOTE: discriminatorTargets must be keyed by names produced by the SAME
46
47
  // (rename-aware) typeNameGenerator that resolves $refs. Passing targets
47
48
  // keyed by raw schema names means renamed subtypes silently lose their
@@ -50,6 +51,11 @@ export class SchemaMapper {
50
51
  this.typeNameGenerator = typeNameGenerator ?? defaultTypeNameGenerator;
51
52
  this.discriminatorTargets = discriminatorTargets ?? new Map();
52
53
  this.reservedNames = reservedNames ?? new Set();
54
+ this.warnSink =
55
+ warnSink ??
56
+ ((message) => {
57
+ process.stderr.write(message);
58
+ });
53
59
  }
54
60
  getBrandedTypes() {
55
61
  return this.brandedTypes;
@@ -116,11 +122,9 @@ export class SchemaMapper {
116
122
  // TODO: Remove deprecated nullable warning and handling when OpenAPI 3.1 support is complete
117
123
  // The 'nullable' property is deprecated in OpenAPI 3.1 in favor of type arrays like ["string", "null"]
118
124
  // This warning should be removed once full type array support is implemented
119
- if (s.nullable === true && !SchemaMapper.nullableWarned) {
120
- // TODO: Replace with structured logging solution
121
- // oxlint-disable-next-line no-console
122
- console.warn('Warning: \'nullable\' is deprecated in OpenAPI 3.1. Use \'type: ["string", "null"]\' instead.');
123
- SchemaMapper.nullableWarned = true;
125
+ if (s.nullable === true && !this.nullableWarned) {
126
+ this.warnSink('Warning: \'nullable\' is deprecated in OpenAPI 3.1. Use \'type: ["string", "null"]\' instead.');
127
+ this.nullableWarned = true;
124
128
  }
125
129
  if (s.enum !== undefined && s.enum.length > 0) {
126
130
  let tsType = this.mapEnumValues(s.enum);
@@ -0,0 +1,119 @@
1
+ import type { RefResolver } from '../parser/ref-resolver.js';
2
+ import type { MethodNameStrategy } from '../types/client.js';
3
+ import type { AnalyzedOperation } from './path-analyzer.js';
4
+ /**
5
+ * Finished (render-ready) type declaration for the contracts file: the emitted
6
+ * type name, its complete definition statement, and an optional type-level
7
+ * JSDoc block rendered above it.
8
+ */
9
+ export interface TypeDeclaration {
10
+ /** Emitted type name (rename-aware). */
11
+ name: string;
12
+ /** Complete definition statement, e.g. `export type User = { … };`. */
13
+ definition: string;
14
+ /** Type-level JSDoc block, when the source carries renderable metadata. */
15
+ jsDoc?: string;
16
+ }
17
+ /**
18
+ * Finished security scheme type declaration (contracts Section 1b): the
19
+ * collision-deduped `{Name}Auth` type name, the rendered object type text,
20
+ * and the description-only JSDoc block ('' when absent).
21
+ */
22
+ export interface SecuritySchemeTypeDeclaration {
23
+ /** Emitted `{Name}Auth` type name (sanitized, numbered on collision). */
24
+ name: string;
25
+ /** Rendered object type text — the `securitySchemeToTsType` output. */
26
+ tsType: string;
27
+ /** Description JSDoc block; '' when the scheme carries no description. */
28
+ jsDoc: string;
29
+ }
30
+ /**
31
+ * Finished server variable type declaration (contracts Section 1c). Names
32
+ * are index-aware over ALL `doc.servers` — `ServerParams` when the document
33
+ * declares a single server, `Server{i}Params` otherwise — so servers without
34
+ * variables still occupy an index.
35
+ */
36
+ export interface ServerTypeDeclaration {
37
+ name: string;
38
+ /** Server URL, emitted as the `/** Server: url *\/` comment when present. */
39
+ url?: string;
40
+ /** Finished interface body lines — variable JSDoc comments and property lines, indentation included. */
41
+ propertyLines: string[];
42
+ }
43
+ /** Branded format type discovered by the schema mapper during translation. */
44
+ export interface BrandedTypeDeclaration {
45
+ name: string;
46
+ format: string;
47
+ baseType: string;
48
+ }
49
+ /**
50
+ * One multipart `FileInput` property fact from the ref-resolving walk of a
51
+ * multipart request body ($refs followed; a resolved `format: binary`
52
+ * property is a `file`/`file-array`, not a `field`).
53
+ */
54
+ export interface FileUploadPropertyFact {
55
+ /** Property name in the multipart schema. */
56
+ name: string;
57
+ /** Whether the property is listed in the schema's `required` array. */
58
+ required: boolean;
59
+ /** `file` → `FileInput`, `file-array` → `FileInput[]`, `field` → `string`. */
60
+ kind: 'file' | 'file-array' | 'field';
61
+ }
62
+ /**
63
+ * Analyzed operation plus its finished translation results.
64
+ *
65
+ * The structural fields are the plain `AnalyzedOperation`; the added fields
66
+ * carry the real-mapper output the contracts renderer emits verbatim.
67
+ */
68
+ export interface FinishedOperation extends AnalyzedOperation {
69
+ /**
70
+ * Finished contracts-file emission lines for this operation (Sections 2–4:
71
+ * query/header/body/response/error types, with JSDoc attached), in emission
72
+ * order. Assembled by `analyze()`; `renderContracts` spills them unchanged.
73
+ */
74
+ contractsLines: string[];
75
+ /**
76
+ * Multipart `FileInput` property facts from the ref-resolving walk of the
77
+ * request body schema (`$ref`s followed; a resolved `format: binary`
78
+ * property is a `file`/`file-array`, not a `field`). Present exactly when
79
+ * the operation has a multipart body WITH a schema — an empty array means a
80
+ * zero-property body; `undefined` means no multipart schema (no FormData
81
+ * emission). Sole fact source for the client generator's FormData appends.
82
+ */
83
+ fileUploadProperties?: FileUploadPropertyFact[];
84
+ }
85
+ /**
86
+ * Single-pass analysis model of an OpenAPI document — the ONLY input every
87
+ * generator renderer consumes. Renderers get the model plus config; they never
88
+ * touch the raw spec or a `RefResolver`.
89
+ */
90
+ export interface AnalyzedSpec {
91
+ /** `doc.openapi` verbatim (e.g. `"3.1.0"`) — feeds generated file headers. */
92
+ specVersion: string;
93
+ /** Operations with structural facts and finished contracts emission lines. */
94
+ operations: FinishedOperation[];
95
+ /**
96
+ * Section-1 component schema declarations as finished entries, in the
97
+ * pre-refactor emission order: topologically sorted schemas first, then
98
+ * discriminator `{Base}Variant` unions in discovery order.
99
+ */
100
+ schemaTypes: TypeDeclaration[];
101
+ /** Branded format types discovered during mapping, in discovery order. */
102
+ brandedTypes: BrandedTypeDeclaration[];
103
+ /** Any operation has a multipart request body → contracts emits `FileInput`. */
104
+ hasFileUpload: boolean;
105
+ /**
106
+ * Finished security scheme type declarations (`{Name}Auth`), in spec key
107
+ * order. The `SecuritySchemes` union is emitted when more than one exists.
108
+ */
109
+ securitySchemeTypes: SecuritySchemeTypeDeclaration[];
110
+ /** Finished server variable interface declarations, in spec order. */
111
+ serverTypes: ServerTypeDeclaration[];
112
+ }
113
+ /** Options for `analyze()`. */
114
+ export interface AnalyzeOptions {
115
+ /** Resolver for `$ref` pointers; defaults to a fresh `RefResolver(doc)`. */
116
+ resolver?: RefResolver;
117
+ /** Method naming strategy; defaults to `'path-based'`. */
118
+ strategy?: MethodNameStrategy;
119
+ }
package/dist/cli/app.d.ts CHANGED
@@ -1,10 +1,12 @@
1
1
  interface Flags {
2
- outputDir: string;
2
+ outputDir?: string;
3
3
  methodNameStrategy: 'path-based' | 'operationId' | 'operationId-with-fallback';
4
4
  specVersion?: string;
5
- strictVersion: boolean;
5
+ strictVersion?: boolean;
6
6
  runtimeImportPath?: string;
7
7
  proxy?: string;
8
+ config?: string;
9
+ project?: string;
8
10
  }
9
11
  export type AppFlags = Flags;
10
12
  export declare const app: import("@stricli/core").Application<import("@stricli/core").CommandContext>;
package/dist/cli/app.js CHANGED
@@ -8,8 +8,9 @@ const command = buildCommand({
8
8
  outputDir: {
9
9
  kind: 'parsed',
10
10
  parse: String,
11
- brief: 'Directory to write generated files',
11
+ brief: 'Directory to write generated files (or set outputDir in the config file)',
12
12
  placeholder: 'dir',
13
+ optional: true,
13
14
  },
14
15
  methodNameStrategy: {
15
16
  kind: 'enum',
@@ -26,8 +27,8 @@ const command = buildCommand({
26
27
  },
27
28
  strictVersion: {
28
29
  kind: 'boolean',
29
- default: true,
30
- brief: 'Enable strict version checking',
30
+ brief: 'Warn when --spec-version mismatches the detected version (default: true)',
31
+ optional: true,
31
32
  },
32
33
  runtimeImportPath: {
33
34
  kind: 'parsed',
@@ -43,14 +44,32 @@ const command = buildCommand({
43
44
  optional: true,
44
45
  placeholder: 'url',
45
46
  },
47
+ config: {
48
+ kind: 'parsed',
49
+ parse: String,
50
+ brief: 'Path to a genoc config file (.genocrc.yml / .genocrc.json); skips discovery',
51
+ optional: true,
52
+ placeholder: 'path',
53
+ },
54
+ project: {
55
+ kind: 'parsed',
56
+ parse: String,
57
+ brief: 'Run only the named client from a multi-client config',
58
+ optional: true,
59
+ placeholder: 'name',
60
+ },
61
+ },
62
+ aliases: {
63
+ o: 'outputDir',
46
64
  },
47
65
  positional: {
48
66
  kind: 'tuple',
49
67
  parameters: [
50
68
  {
51
- brief: 'Path or URL to OpenAPI 3.0 or 3.1 spec (JSON/YAML)',
69
+ brief: 'Path or URL to OpenAPI 3.0 or 3.1 spec (JSON/YAML); omit to use a config file',
52
70
  parse: String,
53
71
  placeholder: 'spec',
72
+ optional: true,
54
73
  },
55
74
  ],
56
75
  },
@@ -0,0 +1,20 @@
1
+ import type { ParsedConfig } from './config-schema.js';
2
+ export interface LoadConfigFileOptions {
3
+ /** Explicit config file path (`--config`); skips discovery when set. */
4
+ explicitPath?: string;
5
+ /** Directory to start discovery from; defaults to `process.cwd()`. */
6
+ searchDir?: string;
7
+ }
8
+ export interface LoadedConfigFile {
9
+ config: ParsedConfig;
10
+ filepath: string;
11
+ }
12
+ /**
13
+ * Load the genoc config file. With `explicitPath`, loads exactly that file
14
+ * (skipping discovery). Otherwise discovers `.genocrc.yml` / `.genocrc.json`
15
+ * walking up from `searchDir` (default `process.cwd()`), stopping at the git
16
+ * boundary (fs root fallback). Returns `null` when no config file is found;
17
+ * throws `UserError` for parse errors, schema violations, empty documents,
18
+ * and duplicate resolved output dirs.
19
+ */
20
+ export declare function loadConfigFile(options?: LoadConfigFileOptions): Promise<LoadedConfigFile | null>;