@contractkit/plugin-openapi 0.8.12 → 0.8.14
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/.turbo/turbo-build$colon$ci.log +5 -5
- package/.turbo/turbo-test$colon$ci.log +7 -7
- package/CHANGELOG.md +15 -0
- package/dist/codegen-openapi.d.ts +21 -0
- package/dist/codegen-openapi.d.ts.map +1 -1
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/codegen-openapi.ts +33 -1
- package/tests/codegen-openapi.test.ts +17 -1
- package/coverage/base.css +0 -224
- package/coverage/block-navigation.js +0 -87
- package/coverage/clover.xml +0 -403
- package/coverage/coverage-final.json +0 -3
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +0 -131
- package/coverage/prettify.css +0 -1
- package/coverage/prettify.js +0 -2
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +0 -210
- package/coverage/src/codegen-openapi.ts.html +0 -2308
- package/coverage/src/index.html +0 -116
- package/coverage/tests/helpers.ts.html +0 -616
- package/coverage/tests/index.html +0 -116
|
@@ -5,9 +5,9 @@ $ tsup src/index.ts --format esm --sourcemap --dts && tsc --emitDeclarationOnly
|
|
|
5
5
|
[34mCLI[39m tsup v8.5.1
|
|
6
6
|
[34mCLI[39m Target: esnext
|
|
7
7
|
[34mESM[39m Build start
|
|
8
|
-
[32mESM[39m [1mdist/index.js [22m[
|
|
9
|
-
[32mESM[39m [1mdist/index.js.map [22m[
|
|
10
|
-
[32mESM[39m ⚡️ Build success in
|
|
8
|
+
[32mESM[39m [1mdist/index.js [22m[32m20.17 KB[39m
|
|
9
|
+
[32mESM[39m [1mdist/index.js.map [22m[32m46.70 KB[39m
|
|
10
|
+
[32mESM[39m ⚡️ Build success in 155ms
|
|
11
11
|
[34mDTS[39m Build start
|
|
12
|
-
[32mDTS[39m ⚡️ Build success in
|
|
13
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[32m1.
|
|
12
|
+
[32mDTS[39m ⚡️ Build success in 5042ms
|
|
13
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m1.60 KB[39m
|
|
@@ -3,20 +3,20 @@ $ vitest run --coverage
|
|
|
3
3
|
[1m[30m[46m RUN [49m[39m[22m [36mv4.1.5 [39m[90m/home/runner/work/ContractKit/ContractKit/packages/plugin-openapi[39m
|
|
4
4
|
[2mCoverage enabled with [22m[33mv8[39m
|
|
5
5
|
|
|
6
|
-
[32m✓[39m tests/codegen-openapi.test.ts [2m([22m[
|
|
6
|
+
[32m✓[39m tests/codegen-openapi.test.ts [2m([22m[2m56 tests[22m[2m)[22m[32m 259[2mms[22m[39m
|
|
7
7
|
|
|
8
8
|
[2m Test Files [22m [1m[32m1 passed[39m[22m[90m (1)[39m
|
|
9
|
-
[2m Tests [22m [1m[
|
|
10
|
-
[2m Start at [22m
|
|
11
|
-
[2m Duration [22m 5.
|
|
9
|
+
[2m Tests [22m [1m[32m56 passed[39m[22m[90m (56)[39m
|
|
10
|
+
[2m Start at [22m 17:06:40
|
|
11
|
+
[2m Duration [22m 5.47s[2m (transform 1.23s, setup 0ms, import 3.72s, tests 259ms, environment 0ms)[22m
|
|
12
12
|
|
|
13
13
|
[34m % [39m[2mCoverage report from [22m[33mv8[39m
|
|
14
14
|
-------------------|---------|----------|---------|---------|-------------------
|
|
15
15
|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
|
|
16
16
|
-------------------|---------|----------|---------|---------|-------------------
|
|
17
|
-
All files |
|
|
18
|
-
src | 79.
|
|
19
|
-
...en-openapi.ts | 79.
|
|
17
|
+
All files | 79.25 | 74.76 | 82.25 | 81.42 |
|
|
18
|
+
src | 79.9 | 75.51 | 86.11 | 81.92 |
|
|
19
|
+
...en-openapi.ts | 79.9 | 75.51 | 86.11 | 81.92 | ...01-704,728,738
|
|
20
20
|
tests | 73.33 | 67.74 | 76.92 | 76.92 |
|
|
21
21
|
helpers.ts | 73.33 | 67.74 | 76.92 | 76.92 | ...18,138,149-150
|
|
22
22
|
-------------------|---------|----------|---------|---------|-------------------
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# @contractkit/contractkit-plugin-openapi
|
|
2
2
|
|
|
3
|
+
## 0.8.14
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 2bf01f1: Emit explicit schemas for the `time` and `interval` scalars instead of type-less (permissive) schemas, and throw on an unmapped scalar type so a missing mapping fails loudly rather than producing an empty schema.
|
|
8
|
+
- Updated dependencies [2bf01f1]
|
|
9
|
+
- @contractkit/core@0.23.0
|
|
10
|
+
|
|
11
|
+
## 0.8.13
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- Updated dependencies [0d3b8e2]
|
|
16
|
+
- @contractkit/core@0.22.0
|
|
17
|
+
|
|
3
18
|
## 0.8.12
|
|
4
19
|
|
|
5
20
|
### Patch Changes
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { ContractRootNode, OpRootNode } from '@contractkit/core';
|
|
2
|
+
/** A single entry for the OpenAPI `servers` array. */
|
|
2
3
|
export interface OpenApiServerEntry {
|
|
3
4
|
url: string;
|
|
4
5
|
description?: string;
|
|
5
6
|
}
|
|
7
|
+
/** A named entry for `components.securitySchemes` (e.g. an HTTP bearer or API-key scheme). */
|
|
6
8
|
export interface OpenApiSecurityScheme {
|
|
7
9
|
type: string;
|
|
8
10
|
scheme?: string;
|
|
@@ -10,6 +12,7 @@ export interface OpenApiSecurityScheme {
|
|
|
10
12
|
name?: string;
|
|
11
13
|
in?: string;
|
|
12
14
|
}
|
|
15
|
+
/** Plugin configuration controlling the generated OpenAPI document (info block, servers, security, internal-op visibility). */
|
|
13
16
|
export interface OpenApiConfig {
|
|
14
17
|
baseDir?: string;
|
|
15
18
|
output?: string;
|
|
@@ -28,6 +31,7 @@ export interface OpenApiConfig {
|
|
|
28
31
|
*/
|
|
29
32
|
includeInternal?: boolean;
|
|
30
33
|
}
|
|
34
|
+
/** Inputs to {@link generateOpenApi}: the parsed contracts/operations plus the OpenAPI config and shared security schemes. */
|
|
31
35
|
export interface OpenApiCodegenContext {
|
|
32
36
|
contractRoots: ContractRootNode[];
|
|
33
37
|
opRoots: OpRootNode[];
|
|
@@ -35,7 +39,24 @@ export interface OpenApiCodegenContext {
|
|
|
35
39
|
/** Named OpenAPI security scheme definitions to include in components.securitySchemes */
|
|
36
40
|
securitySchemes?: Record<string, OpenApiSecurityScheme>;
|
|
37
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Generate an OpenAPI 3.0 document (YAML) from the parsed contracts and operations.
|
|
44
|
+
*
|
|
45
|
+
* Emits `paths` from the operations and `components.schemas` from the contracts, honoring
|
|
46
|
+
* `config.includeInternal` for `operation(internal)` visibility.
|
|
47
|
+
*
|
|
48
|
+
* @returns The OpenAPI document serialized as a YAML string.
|
|
49
|
+
*/
|
|
38
50
|
export declare function generateOpenApi(ctx: OpenApiCodegenContext): string;
|
|
51
|
+
/**
|
|
52
|
+
* Map a ContractKit scalar type node to its OpenAPI schema object (`{ type, format, ... }`),
|
|
53
|
+
* carrying across constraints like min/max/length.
|
|
54
|
+
*
|
|
55
|
+
* @throws {Error} If the scalar name is not mapped — every member of the closed scalar set must
|
|
56
|
+
* have an explicit case, so an unmapped name signals a scalar was added to core without updating
|
|
57
|
+
* this plugin (rather than silently emitting a type-less, permissive schema).
|
|
58
|
+
*/
|
|
59
|
+
export declare function scalarToSchema(type: import('@contractkit/core').ScalarTypeNode): Record<string, unknown>;
|
|
39
60
|
/**
|
|
40
61
|
* Minimal YAML serializer sufficient for OpenAPI documents.
|
|
41
62
|
* Avoids external dependency while producing clean, readable output.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codegen-openapi.d.ts","sourceRoot":"","sources":["../src/codegen-openapi.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACR,gBAAgB,EAChB,UAAU,EAOb,MAAM,mBAAmB,CAAC;AAG3B,MAAM,WAAW,kBAAkB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,qBAAqB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,aAAa;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE;QACH,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,WAAW,CAAC,EAAE,MAAM,CAAC;KACxB,CAAC;IACF,OAAO,CAAC,EAAE,kBAAkB,EAAE,CAAC;IAC/B,0GAA0G;IAC1G,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;IACtC;;;;OAIG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC7B;AA8FD,MAAM,WAAW,qBAAqB;IAClC,aAAa,EAAE,gBAAgB,EAAE,CAAC;IAClC,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,MAAM,EAAE,aAAa,CAAC;IACtB,yFAAyF;IACzF,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;CAC3D;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,qBAAqB,GAAG,MAAM,CAoFlE;
|
|
1
|
+
{"version":3,"file":"codegen-openapi.d.ts","sourceRoot":"","sources":["../src/codegen-openapi.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACR,gBAAgB,EAChB,UAAU,EAOb,MAAM,mBAAmB,CAAC;AAG3B,sDAAsD;AACtD,MAAM,WAAW,kBAAkB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,8FAA8F;AAC9F,MAAM,WAAW,qBAAqB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;CACf;AAED,+HAA+H;AAC/H,MAAM,WAAW,aAAa;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE;QACH,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,WAAW,CAAC,EAAE,MAAM,CAAC;KACxB,CAAC;IACF,OAAO,CAAC,EAAE,kBAAkB,EAAE,CAAC;IAC/B,0GAA0G;IAC1G,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;IACtC;;;;OAIG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC7B;AA8FD,8HAA8H;AAC9H,MAAM,WAAW,qBAAqB;IAClC,aAAa,EAAE,gBAAgB,EAAE,CAAC;IAClC,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,MAAM,EAAE,aAAa,CAAC;IACtB,yFAAyF;IACzF,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;CAC3D;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,qBAAqB,GAAG,MAAM,CAoFlE;AA0ID;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,OAAO,mBAAmB,EAAE,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAsFxG;AA6LD;;;GAGG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,SAAI,GAAG,MAAM,CA+DzD"}
|
package/dist/index.js
CHANGED
|
@@ -341,6 +341,10 @@ function scalarToSchema(type) {
|
|
|
341
341
|
s.type = "string";
|
|
342
342
|
s.format = "date";
|
|
343
343
|
break;
|
|
344
|
+
case "time":
|
|
345
|
+
s.type = "string";
|
|
346
|
+
s.format = "time";
|
|
347
|
+
break;
|
|
344
348
|
case "datetime":
|
|
345
349
|
s.type = "string";
|
|
346
350
|
s.format = "date-time";
|
|
@@ -349,6 +353,10 @@ function scalarToSchema(type) {
|
|
|
349
353
|
s.type = "string";
|
|
350
354
|
s.format = "duration";
|
|
351
355
|
break;
|
|
356
|
+
case "interval":
|
|
357
|
+
s.type = "string";
|
|
358
|
+
s.format = "interval";
|
|
359
|
+
break;
|
|
352
360
|
case "email":
|
|
353
361
|
s.type = "string";
|
|
354
362
|
s.format = "email";
|
|
@@ -375,6 +383,10 @@ function scalarToSchema(type) {
|
|
|
375
383
|
break;
|
|
376
384
|
case "json":
|
|
377
385
|
break;
|
|
386
|
+
default: {
|
|
387
|
+
const _exhaustive = type.name;
|
|
388
|
+
throw new Error(`plugin-openapi: unmapped scalar '${String(_exhaustive)}' \u2014 add a case`);
|
|
389
|
+
}
|
|
378
390
|
}
|
|
379
391
|
return s;
|
|
380
392
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/codegen-openapi.ts"],"sourcesContent":["import { resolve } from 'node:path';\nimport { generateOpenApi } from './codegen-openapi.js';\nimport type { ContractKitPlugin } from '@contractkit/core';\nimport type { OpenApiConfig, OpenApiSecurityScheme } from './codegen-openapi.js';\nexport type { OpenApiServerEntry, OpenApiConfig, OpenApiSecurityScheme } from './codegen-openapi.js';\n\nexport interface OpenApiPluginOptions extends OpenApiConfig {\n securitySchemes?: Record<string, OpenApiSecurityScheme>;\n}\n\n// ─── Default export: loaded via plugins array, reads config from ctx.options ─\n\nconst plugin: ContractKitPlugin = {\n name: 'openapi',\n cacheKey: 'openapi',\n async generateTargets({ contractRoots, opRoots }, ctx) {\n const { securitySchemes, ...openapiConfig } = ctx.options as OpenApiPluginOptions;\n const base = openapiConfig.baseDir ? resolve(ctx.rootDir, openapiConfig.baseDir) : ctx.rootDir;\n const outPath = resolve(base, openapiConfig.output ?? 'openapi.yaml');\n ctx.emitFile(outPath, generateOpenApi({ contractRoots, opRoots, config: openapiConfig, securitySchemes }));\n },\n};\n\nexport default plugin;\n\n// ─── Factory: for programmatic use with explicit config ────────────────────\n\nexport function createOpenApiPlugin(\n openapiConfig: OpenApiConfig,\n rootDir: string,\n securitySchemes?: Record<string, OpenApiSecurityScheme>,\n): ContractKitPlugin {\n return {\n name: 'openapi',\n cacheKey: `openapi:${JSON.stringify(openapiConfig)}`,\n async generateTargets({ contractRoots, opRoots }, ctx) {\n const base = openapiConfig.baseDir ? resolve(rootDir, openapiConfig.baseDir) : rootDir;\n const outPath = resolve(base, openapiConfig.output ?? 'openapi.yaml');\n const content = generateOpenApi({ contractRoots, opRoots, config: openapiConfig, securitySchemes });\n ctx.emitFile(outPath, content);\n },\n };\n}\n","import type {\n ContractRootNode,\n OpRootNode,\n ContractTypeNode,\n FieldNode,\n ModelNode,\n OpRouteNode,\n OpOperationNode,\n ParamSource,\n} from '@contractkit/core';\nimport { resolveModifiers, resolveSecurity, SECURITY_NONE } from '@contractkit/core';\n\nexport interface OpenApiServerEntry {\n url: string;\n description?: string;\n}\n\nexport interface OpenApiSecurityScheme {\n type: string;\n scheme?: string;\n bearerFormat?: string;\n name?: string;\n in?: string;\n}\n\nexport interface OpenApiConfig {\n baseDir?: string;\n output?: string;\n info?: {\n title?: string;\n version?: string;\n description?: string;\n };\n servers?: OpenApiServerEntry[];\n /** Global OpenAPI security requirements (e.g. [{ bearerAuth: [] }]). Distinct from scheme definitions. */\n security?: Record<string, string[]>[];\n /**\n * Whether to document operations marked `internal`. Defaults to `false` — internal ops\n * are omitted from the spec so external consumers don't see them. Set to `true` for an\n * internal-use spec.\n */\n includeInternal?: boolean;\n}\n\n// ─── Type reachability ────────────────────────────────────────────────────\n\nfunction collectRefsFromType(type: ContractTypeNode, out: Set<string>): void {\n switch (type.kind) {\n case 'ref':\n out.add(type.name);\n break;\n case 'array':\n collectRefsFromType(type.item, out);\n break;\n case 'tuple':\n for (const item of type.items) collectRefsFromType(item, out);\n break;\n case 'record':\n collectRefsFromType(type.value, out);\n break;\n case 'union':\n case 'discriminatedUnion':\n case 'intersection':\n for (const member of type.members) collectRefsFromType(member, out);\n break;\n case 'lazy':\n collectRefsFromType(type.inner, out);\n break;\n case 'inlineObject':\n for (const field of type.fields) collectRefsFromType(field.type, out);\n break;\n }\n}\n\nfunction collectParamSourceRefs(source: ParamSource | undefined, out: Set<string>): void {\n if (!source) return;\n if (source.kind === 'ref') {\n out.add(source.name);\n return;\n }\n if (source.kind === 'params') {\n for (const p of source.nodes) collectRefsFromType(p.type, out);\n return;\n }\n collectRefsFromType(source.node, out);\n}\n\n/** Collect all type names directly referenced by public operations (seed set). */\nfunction collectPublicTypeRefs(opRoots: OpRootNode[], includeInternal = false): Set<string> {\n const refs = new Set<string>();\n for (const opRoot of opRoots) {\n for (const route of opRoot.routes) {\n for (const op of route.operations) {\n if (!includeInternal && resolveModifiers(route, op).includes('internal')) continue;\n if (op.request) {\n for (const body of op.request.bodies) collectRefsFromType(body.bodyType, refs);\n }\n for (const resp of op.responses) {\n if (resp.bodyType) collectRefsFromType(resp.bodyType, refs);\n if (resp.headers) {\n for (const h of resp.headers) collectRefsFromType(h.type, refs);\n }\n }\n collectParamSourceRefs(route.params, refs);\n collectParamSourceRefs(op.query, refs);\n collectParamSourceRefs(op.headers, refs);\n }\n }\n }\n return refs;\n}\n\n/** BFS-expand seed type names through the contract model graph. */\nfunction computeReachableSchemas(seeds: Set<string>, modelMap: Map<string, ModelNode>): Set<string> {\n const reachable = new Set<string>(seeds);\n const frontier = [...seeds];\n while (frontier.length > 0) {\n const name = frontier.pop()!;\n const model = modelMap.get(name);\n if (!model) continue;\n const refs = new Set<string>();\n if (model.type) collectRefsFromType(model.type, refs);\n for (const field of model.fields) collectRefsFromType(field.type, refs);\n if (model.bases) for (const b of model.bases) refs.add(b);\n for (const ref of refs) {\n if (!reachable.has(ref)) {\n reachable.add(ref);\n frontier.push(ref);\n }\n }\n }\n return reachable;\n}\n\n// ─── Public entry point ────────────────────────────────────────────────────\n\nexport interface OpenApiCodegenContext {\n contractRoots: ContractRootNode[];\n opRoots: OpRootNode[];\n config: OpenApiConfig;\n /** Named OpenAPI security scheme definitions to include in components.securitySchemes */\n securitySchemes?: Record<string, OpenApiSecurityScheme>;\n}\n\nexport function generateOpenApi(ctx: OpenApiCodegenContext): string {\n const { contractRoots, opRoots, config, securitySchemes } = ctx;\n const includeInternal = config.includeInternal ?? false;\n\n const doc: Record<string, unknown> = {\n openapi: '3.1.0',\n info: {\n title: config.info?.title ?? 'API',\n version: config.info?.version ?? '0.0.1',\n ...(config.info?.description ? { description: config.info.description } : {}),\n },\n };\n\n if (config.servers && config.servers.length > 0) {\n doc.servers = config.servers;\n }\n\n if (config.security && config.security.length > 0) {\n doc.security = config.security;\n }\n\n // Build component schemas from all contract models\n const allSchemas: Record<string, unknown> = {};\n const modelMap = new Map<string, ModelNode>();\n\n for (const contractRoot of contractRoots) {\n for (const model of contractRoot.models) {\n modelMap.set(model.name, model);\n }\n }\n for (const contractRoot of contractRoots) {\n for (const model of contractRoot.models) {\n allSchemas[model.name] = modelToSchema(model, modelMap);\n }\n }\n\n // Build paths from all operation files\n const paths: Record<string, Record<string, unknown>> = {};\n\n for (const opRoot of opRoots) {\n for (const route of opRoot.routes) {\n const oaPath = convertPath(route.path);\n\n for (const op of route.operations) {\n const mods = resolveModifiers(route, op);\n if (!includeInternal && mods.includes('internal')) continue;\n // Lazily initialize the path object so all-internal routes\n // leave no empty entry in the output\n if (!paths[oaPath]) paths[oaPath] = {};\n const operation = buildOperation(route, op);\n if (mods.includes('deprecated')) (operation as Record<string, unknown>).deprecated = true;\n paths[oaPath][op.method] = operation;\n }\n }\n }\n\n doc.paths = paths;\n\n // Filter schemas to only include types reachable from public operations.\n // When there are no op files, all schemas are included (no filtering).\n const schemas: Record<string, unknown> =\n opRoots.length > 0\n ? (() => {\n const reachable = computeReachableSchemas(collectPublicTypeRefs(opRoots, includeInternal), modelMap);\n const filtered: Record<string, unknown> = {};\n for (const [name, schema] of Object.entries(allSchemas)) {\n if (reachable.has(name)) filtered[name] = schema;\n }\n return filtered;\n })()\n : allSchemas;\n\n const components: Record<string, unknown> = {};\n if (Object.keys(schemas).length > 0) {\n components.schemas = schemas;\n }\n if (securitySchemes && Object.keys(securitySchemes).length > 0) {\n components.securitySchemes = securitySchemes;\n }\n if (Object.keys(components).length > 0) {\n doc.components = components;\n }\n\n return toYaml(doc);\n}\n\n// ─── Path conversion ──────────────────────────────────────────────────────\n\n/** Path is already in OpenAPI `{param}` style — return as-is. */\nfunction convertPath(path: string): string {\n return path;\n}\n\n// ─── Schema conversion ───────────────────────────────────────────────────\n\nfunction modelToSchema(model: ModelNode, modelMap?: Map<string, ModelNode>): Record<string, unknown> {\n // Type alias (no fields)\n if (model.type) {\n const schema = typeToSchema(model.type, modelMap);\n if (model.description) schema.description = model.description;\n return schema;\n }\n\n const properties: Record<string, unknown> = {};\n const required: string[] = [];\n\n for (const field of model.fields) {\n const prop = fieldToSchema(field, modelMap);\n properties[field.name] = prop;\n if (!field.optional) {\n required.push(field.name);\n }\n }\n\n const schema: Record<string, unknown> = {\n type: 'object',\n properties,\n };\n\n if (required.length > 0) {\n schema.required = required;\n }\n\n if (model.bases && model.bases.length > 0) {\n return {\n allOf: [...model.bases.map(b => ({ $ref: `#/components/schemas/${b}` })), schema],\n };\n }\n\n if (model.description) {\n schema.description = model.description;\n }\n if (model.deprecated) {\n schema.deprecated = true;\n }\n\n return schema;\n}\n\nfunction fieldToSchema(field: FieldNode, modelMap?: Map<string, ModelNode>): Record<string, unknown> {\n let schema = typeToSchema(field.type, modelMap);\n\n if (field.nullable) {\n schema = wrapNullable(schema);\n }\n if (field.visibility === 'readonly') {\n schema.readOnly = true;\n } else if (field.visibility === 'writeonly') {\n schema.writeOnly = true;\n }\n if (field.default !== undefined) {\n schema.default = field.default;\n }\n if (field.description) {\n schema.description = field.description;\n }\n if (field.deprecated) {\n schema.deprecated = true;\n }\n\n return schema;\n}\n\nfunction typeToSchema(type: ContractTypeNode, modelMap?: Map<string, ModelNode>): Record<string, unknown> {\n switch (type.kind) {\n case 'scalar':\n return scalarToSchema(type);\n case 'array':\n return arrayToSchema(type, modelMap);\n case 'tuple':\n return { type: 'array', prefixItems: type.items.map(i => typeToSchema(i, modelMap)) };\n case 'record':\n return { type: 'object', additionalProperties: typeToSchema(type.value, modelMap) };\n case 'enum':\n return { type: 'string', enum: type.values };\n case 'literal':\n return { const: type.value };\n case 'union':\n return { oneOf: type.members.map(m => typeToSchema(m, modelMap)) };\n case 'discriminatedUnion': {\n const oneOf = type.members.map(m => typeToSchema(m, modelMap));\n const mapping: Record<string, string> = {};\n for (const member of type.members) {\n if (member.kind !== 'ref') continue;\n const literalValues = resolveDiscriminatorLiterals(member.name, type.discriminator, modelMap);\n if (literalValues.length === 0) continue;\n for (const v of literalValues) {\n mapping[v] = `#/components/schemas/${member.name}`;\n }\n }\n const result: Record<string, unknown> = {\n oneOf,\n discriminator: { propertyName: type.discriminator },\n };\n if (Object.keys(mapping).length > 0) {\n (result.discriminator as Record<string, unknown>).mapping = mapping;\n }\n return result;\n }\n case 'intersection':\n return { allOf: type.members.map(m => typeToSchema(m, modelMap)) };\n case 'ref':\n return { $ref: `#/components/schemas/${type.name}` };\n case 'inlineObject':\n return inlineObjectToSchema(type.fields, modelMap);\n case 'lazy':\n return typeToSchema(type.inner, modelMap);\n }\n}\n\n/** Resolve literal values of a model's discriminator field. Returns [] if not resolvable. */\nfunction resolveDiscriminatorLiterals(modelName: string, discriminator: string, modelMap?: Map<string, ModelNode>): string[] {\n if (!modelMap) return [];\n const model = modelMap.get(modelName);\n if (!model) return [];\n const field = model.fields.find(f => f.name === discriminator);\n if (!field) return [];\n if (field.type.kind === 'literal') return [String(field.type.value)];\n if (field.type.kind === 'enum') return field.type.values;\n return [];\n}\n\nfunction scalarToSchema(type: import('@contractkit/core').ScalarTypeNode): Record<string, unknown> {\n const s: Record<string, unknown> = {};\n\n switch (type.name) {\n case 'string':\n s.type = 'string';\n if (type.min !== undefined) s.minLength = Number(type.min);\n if (type.max !== undefined) s.maxLength = Number(type.max);\n if (type.len !== undefined) {\n s.minLength = type.len;\n s.maxLength = type.len;\n }\n if (type.regex) s.pattern = type.regex;\n break;\n case 'number':\n s.type = 'number';\n if (type.min !== undefined) s.minimum = Number(type.min);\n if (type.max !== undefined) s.maximum = Number(type.max);\n break;\n case 'int':\n s.type = 'integer';\n if (type.min !== undefined) s.minimum = Number(type.min);\n if (type.max !== undefined) s.maximum = Number(type.max);\n break;\n case 'bigint':\n s.type = 'integer';\n s.format = 'int64';\n break;\n case 'boolean':\n s.type = 'boolean';\n break;\n case 'date':\n s.type = 'string';\n s.format = 'date';\n break;\n case 'datetime':\n s.type = 'string';\n s.format = 'date-time';\n break;\n case 'duration':\n s.type = 'string';\n s.format = 'duration';\n break;\n case 'email':\n s.type = 'string';\n s.format = 'email';\n break;\n case 'url':\n s.type = 'string';\n s.format = 'uri';\n break;\n case 'uuid':\n s.type = 'string';\n s.format = 'uuid';\n break;\n case 'unknown':\n // No type constraint\n break;\n case 'null':\n s.type = 'null';\n break;\n case 'object':\n s.type = 'object';\n break;\n case 'binary':\n s.type = 'string';\n s.format = 'binary';\n break;\n case 'json':\n // Any JSON value — no type constraint\n break;\n }\n\n return s;\n}\n\nfunction arrayToSchema(type: import('@contractkit/core').ArrayTypeNode, modelMap?: Map<string, ModelNode>): Record<string, unknown> {\n const s: Record<string, unknown> = { type: 'array', items: typeToSchema(type.item, modelMap) };\n if (type.min !== undefined) s.minItems = type.min;\n if (type.max !== undefined) s.maxItems = type.max;\n return s;\n}\n\nfunction inlineObjectToSchema(fields: FieldNode[], modelMap?: Map<string, ModelNode>): Record<string, unknown> {\n const properties: Record<string, unknown> = {};\n const required: string[] = [];\n\n for (const field of fields) {\n properties[field.name] = fieldToSchema(field, modelMap);\n if (!field.optional) {\n required.push(field.name);\n }\n }\n\n const schema: Record<string, unknown> = { type: 'object', properties };\n if (required.length > 0) schema.required = required;\n return schema;\n}\n\nfunction wrapNullable(schema: Record<string, unknown>): Record<string, unknown> {\n // OpenAPI 3.1 uses JSON Schema nullable via oneOf or type array\n if (schema.$ref) {\n return { oneOf: [schema, { type: 'null' }] };\n }\n if (typeof schema.type === 'string') {\n schema.type = [schema.type, 'null'];\n }\n return schema;\n}\n\n// ─── Operation building ─────────────────────────────────────────────────\n\nfunction buildOperation(route: OpRouteNode, op: OpOperationNode): Record<string, unknown> {\n const operation: Record<string, unknown> = {};\n\n // operationId from service binding or SDK name\n if (op.sdk) {\n operation.operationId = op.sdk;\n } else if (op.service) {\n const methodPart = op.service.split('.').pop();\n if (methodPart) operation.operationId = methodPart;\n }\n\n if (op.description) {\n operation.description = op.description;\n }\n\n // Parameters: path params + query + headers\n const parameters: Record<string, unknown>[] = [];\n\n if (route.params) {\n parameters.push(...paramSourceToParams(route.params, 'path'));\n }\n if (op.query) {\n parameters.push(...paramSourceToParams(op.query, 'query'));\n }\n if (op.headers) {\n parameters.push(...paramSourceToParams(op.headers, 'header'));\n }\n\n if (parameters.length > 0) {\n operation.parameters = parameters;\n }\n\n // Request body\n if (op.request && op.request.bodies.length > 0) {\n const content: Record<string, { schema: ReturnType<typeof typeToSchema> }> = {};\n for (const body of op.request.bodies) {\n content[body.contentType] = { schema: typeToSchema(body.bodyType) };\n }\n operation.requestBody = { required: true, content };\n }\n\n // Effective security (operation-level wins; falls back to route-level)\n // security: none → empty array (explicit public endpoint, overrides global default)\n // security: { fields } → omit operation-level entry (rely on global security from config)\n const effectiveSecurity = resolveSecurity(route, op);\n if (effectiveSecurity === SECURITY_NONE) {\n operation.security = [];\n }\n\n // Responses\n const responses: Record<string, unknown> = {};\n for (const resp of op.responses) {\n const statusKey = String(resp.statusCode);\n const responseObject: Record<string, unknown> = {\n description: statusDescription(resp.statusCode),\n };\n if (resp.bodyType && resp.contentType) {\n responseObject.content = {\n [resp.contentType]: {\n schema: typeToSchema(resp.bodyType),\n },\n };\n }\n if (resp.headers && resp.headers.length > 0) {\n const headers: Record<string, unknown> = {};\n for (const h of resp.headers) {\n const headerObject: Record<string, unknown> = {\n schema: typeToSchema(h.type),\n };\n if (!h.optional) headerObject.required = true;\n if (h.description) headerObject.description = h.description;\n headers[h.name] = headerObject;\n }\n responseObject.headers = headers;\n }\n responses[statusKey] = responseObject;\n }\n\n if (Object.keys(responses).length > 0) {\n operation.responses = responses;\n }\n\n return operation;\n}\n\nfunction paramSourceToParams(source: ParamSource, location: 'path' | 'query' | 'header'): Record<string, unknown>[] {\n if (source.kind === 'ref') {\n // Type reference name used as param source — emit a single $ref\n return [\n {\n name: source.name,\n in: location,\n required: location === 'path',\n schema: { $ref: `#/components/schemas/${source.name}` },\n },\n ];\n }\n\n if (source.kind === 'params') {\n // Inline param declarations\n return source.nodes.map(p => ({\n name: p.name,\n in: location,\n required: location === 'path',\n schema: typeToSchema(p.type),\n }));\n }\n\n // ContractTypeNode (inline object or other type) — if it's an inlineObject, expand fields\n if (source.node.kind === 'inlineObject') {\n return source.node.fields.map(f => ({\n name: f.name,\n in: location,\n required: location === 'path' ? true : !f.optional,\n schema: typeToSchema(f.type),\n }));\n }\n\n // For a ref type used as param source\n if (source.node.kind === 'ref') {\n return [\n {\n name: source.node.name,\n in: location,\n required: location === 'path',\n schema: { $ref: `#/components/schemas/${source.node.name}` },\n },\n ];\n }\n\n return [];\n}\n\nfunction statusDescription(code: number): string {\n const descriptions: Record<number, string> = {\n 200: 'Successful response',\n 201: 'Created',\n 204: 'No content',\n 400: 'Bad request',\n 401: 'Unauthorized',\n 403: 'Forbidden',\n 404: 'Not found',\n 409: 'Conflict',\n 422: 'Unprocessable entity',\n 500: 'Internal server error',\n };\n return descriptions[code] ?? `Response ${code}`;\n}\n\n// ─── YAML serializer ──────────────────────────────────────────────────────\n\n/**\n * Minimal YAML serializer sufficient for OpenAPI documents.\n * Avoids external dependency while producing clean, readable output.\n */\nexport function toYaml(value: unknown, indent = 0): string {\n if (value === null || value === undefined) return 'null';\n if (typeof value === 'boolean') return value ? 'true' : 'false';\n if (typeof value === 'number') return String(value);\n if (typeof value === 'bigint') return String(value);\n\n if (typeof value === 'string') {\n return yamlString(value);\n }\n\n if (Array.isArray(value)) {\n if (value.length === 0) return '[]';\n\n // Check if all items are simple scalars (for inline arrays like enum values, required lists)\n if (value.every(v => typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean')) {\n const items = value.map(v => (typeof v === 'string' ? yamlString(v) : String(v)));\n const inline = `[${items.join(', ')}]`;\n if (inline.length < 80) return inline;\n }\n\n const lines: string[] = [];\n const pad = ' '.repeat(indent);\n for (const item of value) {\n if (isPlainObject(item)) {\n const entries = Object.entries(item as Record<string, unknown>);\n if (entries.length > 0) {\n const [firstKey, firstVal] = entries[0]!;\n const firstValStr = isComplex(firstVal) ? `\\n${toYamlValue(firstVal, indent + 2)}` : ` ${toYaml(firstVal, indent + 2)}`;\n lines.push(`${pad}- ${yamlKey(firstKey)}:${firstValStr}`);\n for (let i = 1; i < entries.length; i++) {\n const [k, v] = entries[i]!;\n const valStr = isComplex(v) ? `\\n${toYamlValue(v, indent + 2)}` : ` ${toYaml(v, indent + 2)}`;\n lines.push(`${pad} ${yamlKey(k)}:${valStr}`);\n }\n } else {\n lines.push(`${pad}- {}`);\n }\n } else {\n lines.push(`${pad}- ${toYaml(item, indent + 1)}`);\n }\n }\n return lines.join('\\n');\n }\n\n if (isPlainObject(value)) {\n const obj = value as Record<string, unknown>;\n const entries = Object.entries(obj);\n if (entries.length === 0) return '{}';\n\n const pad = ' '.repeat(indent);\n const lines: string[] = [];\n for (const [key, val] of entries) {\n if (isComplex(val)) {\n lines.push(`${pad}${yamlKey(key)}:`);\n lines.push(toYamlValue(val, indent + 1));\n } else {\n lines.push(`${pad}${yamlKey(key)}: ${toYaml(val, indent + 1)}`);\n }\n }\n return lines.join('\\n');\n }\n\n return String(value);\n}\n\nfunction toYamlValue(value: unknown, indent: number): string {\n if (Array.isArray(value)) {\n return toYaml(value, indent);\n }\n if (isPlainObject(value)) {\n return toYaml(value, indent);\n }\n return ' '.repeat(indent) + toYaml(value, indent);\n}\n\nfunction isComplex(value: unknown): boolean {\n if (Array.isArray(value)) {\n // Simple scalar arrays can be inlined\n if (value.every(v => typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean')) {\n const items = value.map(v => (typeof v === 'string' ? yamlString(v) : String(v)));\n return `[${items.join(', ')}]`.length >= 80;\n }\n return true;\n }\n return isPlainObject(value);\n}\n\nfunction isPlainObject(value: unknown): boolean {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction yamlString(s: string): string {\n // Use plain style if safe, otherwise single-quoted\n if (s === '') return \"''\";\n if (/^[\\w./-]+$/.test(s) && !/^(true|false|null|yes|no|on|off)$/i.test(s) && !/^\\d/.test(s)) {\n return s;\n }\n // Single-quote, escaping internal single quotes by doubling\n return `'${s.replace(/'/g, \"''\")}'`;\n}\n\nfunction yamlKey(key: string): string {\n // Keys with special chars need quoting\n if (/^[\\w-]+$/.test(key) && !/^(true|false|null|yes|no|on|off)$/i.test(key)) {\n return key;\n }\n return `'${key.replace(/'/g, \"''\")}'`;\n}\n"],"mappings":"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","names":["resolve","resolveModifiers","resolveSecurity","SECURITY_NONE","collectRefsFromType","type","out","kind","add","name","item","items","value","member","members","inner","field","fields","collectParamSourceRefs","source","p","nodes","node","collectPublicTypeRefs","opRoots","includeInternal","refs","Set","opRoot","route","routes","op","operations","resolveModifiers","includes","request","body","bodies","bodyType","resp","responses","headers","h","params","query","computeReachableSchemas","seeds","modelMap","reachable","frontier","length","pop","model","get","bases","b","ref","has","push","generateOpenApi","ctx","contractRoots","config","securitySchemes","doc","openapi","info","title","version","description","servers","security","allSchemas","Map","contractRoot","models","set","modelToSchema","paths","oaPath","convertPath","path","mods","operation","buildOperation","deprecated","method","schemas","filtered","schema","Object","entries","components","keys","toYaml","typeToSchema","properties","required","prop","fieldToSchema","optional","allOf","map","$ref","nullable","wrapNullable","visibility","readOnly","writeOnly","default","undefined","scalarToSchema","arrayToSchema","prefixItems","i","additionalProperties","enum","values","const","oneOf","m","mapping","literalValues","resolveDiscriminatorLiterals","discriminator","v","result","propertyName","inlineObjectToSchema","modelName","find","f","String","s","min","minLength","Number","max","maxLength","len","regex","pattern","minimum","maximum","format","minItems","maxItems","sdk","operationId","service","methodPart","split","parameters","paramSourceToParams","content","contentType","requestBody","effectiveSecurity","resolveSecurity","SECURITY_NONE","statusKey","statusCode","responseObject","statusDescription","headerObject","location","in","code","descriptions","indent","yamlString","Array","isArray","every","inline","join","lines","pad","repeat","isPlainObject","firstKey","firstVal","firstValStr","isComplex","toYamlValue","yamlKey","k","valStr","obj","key","val","test","replace","plugin","name","cacheKey","generateTargets","contractRoots","opRoots","ctx","securitySchemes","openapiConfig","options","base","baseDir","resolve","rootDir","outPath","output","emitFile","generateOpenApi","config","createOpenApiPlugin","JSON","stringify","content"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/codegen-openapi.ts"],"sourcesContent":["import { resolve } from 'node:path';\nimport { generateOpenApi } from './codegen-openapi.js';\nimport type { ContractKitPlugin } from '@contractkit/core';\nimport type { OpenApiConfig, OpenApiSecurityScheme } from './codegen-openapi.js';\nexport type { OpenApiServerEntry, OpenApiConfig, OpenApiSecurityScheme } from './codegen-openapi.js';\n\nexport interface OpenApiPluginOptions extends OpenApiConfig {\n securitySchemes?: Record<string, OpenApiSecurityScheme>;\n}\n\n// ─── Default export: loaded via plugins array, reads config from ctx.options ─\n\nconst plugin: ContractKitPlugin = {\n name: 'openapi',\n cacheKey: 'openapi',\n async generateTargets({ contractRoots, opRoots }, ctx) {\n const { securitySchemes, ...openapiConfig } = ctx.options as OpenApiPluginOptions;\n const base = openapiConfig.baseDir ? resolve(ctx.rootDir, openapiConfig.baseDir) : ctx.rootDir;\n const outPath = resolve(base, openapiConfig.output ?? 'openapi.yaml');\n ctx.emitFile(outPath, generateOpenApi({ contractRoots, opRoots, config: openapiConfig, securitySchemes }));\n },\n};\n\nexport default plugin;\n\n// ─── Factory: for programmatic use with explicit config ────────────────────\n\nexport function createOpenApiPlugin(\n openapiConfig: OpenApiConfig,\n rootDir: string,\n securitySchemes?: Record<string, OpenApiSecurityScheme>,\n): ContractKitPlugin {\n return {\n name: 'openapi',\n cacheKey: `openapi:${JSON.stringify(openapiConfig)}`,\n async generateTargets({ contractRoots, opRoots }, ctx) {\n const base = openapiConfig.baseDir ? resolve(rootDir, openapiConfig.baseDir) : rootDir;\n const outPath = resolve(base, openapiConfig.output ?? 'openapi.yaml');\n const content = generateOpenApi({ contractRoots, opRoots, config: openapiConfig, securitySchemes });\n ctx.emitFile(outPath, content);\n },\n };\n}\n","import type {\n ContractRootNode,\n OpRootNode,\n ContractTypeNode,\n FieldNode,\n ModelNode,\n OpRouteNode,\n OpOperationNode,\n ParamSource,\n} from '@contractkit/core';\nimport { resolveModifiers, resolveSecurity, SECURITY_NONE } from '@contractkit/core';\n\n/** A single entry for the OpenAPI `servers` array. */\nexport interface OpenApiServerEntry {\n url: string;\n description?: string;\n}\n\n/** A named entry for `components.securitySchemes` (e.g. an HTTP bearer or API-key scheme). */\nexport interface OpenApiSecurityScheme {\n type: string;\n scheme?: string;\n bearerFormat?: string;\n name?: string;\n in?: string;\n}\n\n/** Plugin configuration controlling the generated OpenAPI document (info block, servers, security, internal-op visibility). */\nexport interface OpenApiConfig {\n baseDir?: string;\n output?: string;\n info?: {\n title?: string;\n version?: string;\n description?: string;\n };\n servers?: OpenApiServerEntry[];\n /** Global OpenAPI security requirements (e.g. [{ bearerAuth: [] }]). Distinct from scheme definitions. */\n security?: Record<string, string[]>[];\n /**\n * Whether to document operations marked `internal`. Defaults to `false` — internal ops\n * are omitted from the spec so external consumers don't see them. Set to `true` for an\n * internal-use spec.\n */\n includeInternal?: boolean;\n}\n\n// ─── Type reachability ────────────────────────────────────────────────────\n\nfunction collectRefsFromType(type: ContractTypeNode, out: Set<string>): void {\n switch (type.kind) {\n case 'ref':\n out.add(type.name);\n break;\n case 'array':\n collectRefsFromType(type.item, out);\n break;\n case 'tuple':\n for (const item of type.items) collectRefsFromType(item, out);\n break;\n case 'record':\n collectRefsFromType(type.value, out);\n break;\n case 'union':\n case 'discriminatedUnion':\n case 'intersection':\n for (const member of type.members) collectRefsFromType(member, out);\n break;\n case 'lazy':\n collectRefsFromType(type.inner, out);\n break;\n case 'inlineObject':\n for (const field of type.fields) collectRefsFromType(field.type, out);\n break;\n }\n}\n\nfunction collectParamSourceRefs(source: ParamSource | undefined, out: Set<string>): void {\n if (!source) return;\n if (source.kind === 'ref') {\n out.add(source.name);\n return;\n }\n if (source.kind === 'params') {\n for (const p of source.nodes) collectRefsFromType(p.type, out);\n return;\n }\n collectRefsFromType(source.node, out);\n}\n\n/** Collect all type names directly referenced by public operations (seed set). */\nfunction collectPublicTypeRefs(opRoots: OpRootNode[], includeInternal = false): Set<string> {\n const refs = new Set<string>();\n for (const opRoot of opRoots) {\n for (const route of opRoot.routes) {\n for (const op of route.operations) {\n if (!includeInternal && resolveModifiers(route, op).includes('internal')) continue;\n if (op.request) {\n for (const body of op.request.bodies) collectRefsFromType(body.bodyType, refs);\n }\n for (const resp of op.responses) {\n if (resp.bodyType) collectRefsFromType(resp.bodyType, refs);\n if (resp.headers) {\n for (const h of resp.headers) collectRefsFromType(h.type, refs);\n }\n }\n collectParamSourceRefs(route.params, refs);\n collectParamSourceRefs(op.query, refs);\n collectParamSourceRefs(op.headers, refs);\n }\n }\n }\n return refs;\n}\n\n/** BFS-expand seed type names through the contract model graph. */\nfunction computeReachableSchemas(seeds: Set<string>, modelMap: Map<string, ModelNode>): Set<string> {\n const reachable = new Set<string>(seeds);\n const frontier = [...seeds];\n while (frontier.length > 0) {\n const name = frontier.pop()!;\n const model = modelMap.get(name);\n if (!model) continue;\n const refs = new Set<string>();\n if (model.type) collectRefsFromType(model.type, refs);\n for (const field of model.fields) collectRefsFromType(field.type, refs);\n if (model.bases) for (const b of model.bases) refs.add(b);\n for (const ref of refs) {\n if (!reachable.has(ref)) {\n reachable.add(ref);\n frontier.push(ref);\n }\n }\n }\n return reachable;\n}\n\n// ─── Public entry point ────────────────────────────────────────────────────\n\n/** Inputs to {@link generateOpenApi}: the parsed contracts/operations plus the OpenAPI config and shared security schemes. */\nexport interface OpenApiCodegenContext {\n contractRoots: ContractRootNode[];\n opRoots: OpRootNode[];\n config: OpenApiConfig;\n /** Named OpenAPI security scheme definitions to include in components.securitySchemes */\n securitySchemes?: Record<string, OpenApiSecurityScheme>;\n}\n\n/**\n * Generate an OpenAPI 3.0 document (YAML) from the parsed contracts and operations.\n *\n * Emits `paths` from the operations and `components.schemas` from the contracts, honoring\n * `config.includeInternal` for `operation(internal)` visibility.\n *\n * @returns The OpenAPI document serialized as a YAML string.\n */\nexport function generateOpenApi(ctx: OpenApiCodegenContext): string {\n const { contractRoots, opRoots, config, securitySchemes } = ctx;\n const includeInternal = config.includeInternal ?? false;\n\n const doc: Record<string, unknown> = {\n openapi: '3.1.0',\n info: {\n title: config.info?.title ?? 'API',\n version: config.info?.version ?? '0.0.1',\n ...(config.info?.description ? { description: config.info.description } : {}),\n },\n };\n\n if (config.servers && config.servers.length > 0) {\n doc.servers = config.servers;\n }\n\n if (config.security && config.security.length > 0) {\n doc.security = config.security;\n }\n\n // Build component schemas from all contract models\n const allSchemas: Record<string, unknown> = {};\n const modelMap = new Map<string, ModelNode>();\n\n for (const contractRoot of contractRoots) {\n for (const model of contractRoot.models) {\n modelMap.set(model.name, model);\n }\n }\n for (const contractRoot of contractRoots) {\n for (const model of contractRoot.models) {\n allSchemas[model.name] = modelToSchema(model, modelMap);\n }\n }\n\n // Build paths from all operation files\n const paths: Record<string, Record<string, unknown>> = {};\n\n for (const opRoot of opRoots) {\n for (const route of opRoot.routes) {\n const oaPath = convertPath(route.path);\n\n for (const op of route.operations) {\n const mods = resolveModifiers(route, op);\n if (!includeInternal && mods.includes('internal')) continue;\n // Lazily initialize the path object so all-internal routes\n // leave no empty entry in the output\n if (!paths[oaPath]) paths[oaPath] = {};\n const operation = buildOperation(route, op);\n if (mods.includes('deprecated')) (operation as Record<string, unknown>).deprecated = true;\n paths[oaPath][op.method] = operation;\n }\n }\n }\n\n doc.paths = paths;\n\n // Filter schemas to only include types reachable from public operations.\n // When there are no op files, all schemas are included (no filtering).\n const schemas: Record<string, unknown> =\n opRoots.length > 0\n ? (() => {\n const reachable = computeReachableSchemas(collectPublicTypeRefs(opRoots, includeInternal), modelMap);\n const filtered: Record<string, unknown> = {};\n for (const [name, schema] of Object.entries(allSchemas)) {\n if (reachable.has(name)) filtered[name] = schema;\n }\n return filtered;\n })()\n : allSchemas;\n\n const components: Record<string, unknown> = {};\n if (Object.keys(schemas).length > 0) {\n components.schemas = schemas;\n }\n if (securitySchemes && Object.keys(securitySchemes).length > 0) {\n components.securitySchemes = securitySchemes;\n }\n if (Object.keys(components).length > 0) {\n doc.components = components;\n }\n\n return toYaml(doc);\n}\n\n// ─── Path conversion ──────────────────────────────────────────────────────\n\n/** Path is already in OpenAPI `{param}` style — return as-is. */\nfunction convertPath(path: string): string {\n return path;\n}\n\n// ─── Schema conversion ───────────────────────────────────────────────────\n\nfunction modelToSchema(model: ModelNode, modelMap?: Map<string, ModelNode>): Record<string, unknown> {\n // Type alias (no fields)\n if (model.type) {\n const schema = typeToSchema(model.type, modelMap);\n if (model.description) schema.description = model.description;\n return schema;\n }\n\n const properties: Record<string, unknown> = {};\n const required: string[] = [];\n\n for (const field of model.fields) {\n const prop = fieldToSchema(field, modelMap);\n properties[field.name] = prop;\n if (!field.optional) {\n required.push(field.name);\n }\n }\n\n const schema: Record<string, unknown> = {\n type: 'object',\n properties,\n };\n\n if (required.length > 0) {\n schema.required = required;\n }\n\n if (model.bases && model.bases.length > 0) {\n return {\n allOf: [...model.bases.map(b => ({ $ref: `#/components/schemas/${b}` })), schema],\n };\n }\n\n if (model.description) {\n schema.description = model.description;\n }\n if (model.deprecated) {\n schema.deprecated = true;\n }\n\n return schema;\n}\n\nfunction fieldToSchema(field: FieldNode, modelMap?: Map<string, ModelNode>): Record<string, unknown> {\n let schema = typeToSchema(field.type, modelMap);\n\n if (field.nullable) {\n schema = wrapNullable(schema);\n }\n if (field.visibility === 'readonly') {\n schema.readOnly = true;\n } else if (field.visibility === 'writeonly') {\n schema.writeOnly = true;\n }\n if (field.default !== undefined) {\n schema.default = field.default;\n }\n if (field.description) {\n schema.description = field.description;\n }\n if (field.deprecated) {\n schema.deprecated = true;\n }\n\n return schema;\n}\n\nfunction typeToSchema(type: ContractTypeNode, modelMap?: Map<string, ModelNode>): Record<string, unknown> {\n switch (type.kind) {\n case 'scalar':\n return scalarToSchema(type);\n case 'array':\n return arrayToSchema(type, modelMap);\n case 'tuple':\n return { type: 'array', prefixItems: type.items.map(i => typeToSchema(i, modelMap)) };\n case 'record':\n return { type: 'object', additionalProperties: typeToSchema(type.value, modelMap) };\n case 'enum':\n return { type: 'string', enum: type.values };\n case 'literal':\n return { const: type.value };\n case 'union':\n return { oneOf: type.members.map(m => typeToSchema(m, modelMap)) };\n case 'discriminatedUnion': {\n const oneOf = type.members.map(m => typeToSchema(m, modelMap));\n const mapping: Record<string, string> = {};\n for (const member of type.members) {\n if (member.kind !== 'ref') continue;\n const literalValues = resolveDiscriminatorLiterals(member.name, type.discriminator, modelMap);\n if (literalValues.length === 0) continue;\n for (const v of literalValues) {\n mapping[v] = `#/components/schemas/${member.name}`;\n }\n }\n const result: Record<string, unknown> = {\n oneOf,\n discriminator: { propertyName: type.discriminator },\n };\n if (Object.keys(mapping).length > 0) {\n (result.discriminator as Record<string, unknown>).mapping = mapping;\n }\n return result;\n }\n case 'intersection':\n return { allOf: type.members.map(m => typeToSchema(m, modelMap)) };\n case 'ref':\n return { $ref: `#/components/schemas/${type.name}` };\n case 'inlineObject':\n return inlineObjectToSchema(type.fields, modelMap);\n case 'lazy':\n return typeToSchema(type.inner, modelMap);\n }\n}\n\n/** Resolve literal values of a model's discriminator field. Returns [] if not resolvable. */\nfunction resolveDiscriminatorLiterals(modelName: string, discriminator: string, modelMap?: Map<string, ModelNode>): string[] {\n if (!modelMap) return [];\n const model = modelMap.get(modelName);\n if (!model) return [];\n const field = model.fields.find(f => f.name === discriminator);\n if (!field) return [];\n if (field.type.kind === 'literal') return [String(field.type.value)];\n if (field.type.kind === 'enum') return field.type.values;\n return [];\n}\n\n/**\n * Map a ContractKit scalar type node to its OpenAPI schema object (`{ type, format, ... }`),\n * carrying across constraints like min/max/length.\n *\n * @throws {Error} If the scalar name is not mapped — every member of the closed scalar set must\n * have an explicit case, so an unmapped name signals a scalar was added to core without updating\n * this plugin (rather than silently emitting a type-less, permissive schema).\n */\nexport function scalarToSchema(type: import('@contractkit/core').ScalarTypeNode): Record<string, unknown> {\n const s: Record<string, unknown> = {};\n\n switch (type.name) {\n case 'string':\n s.type = 'string';\n if (type.min !== undefined) s.minLength = Number(type.min);\n if (type.max !== undefined) s.maxLength = Number(type.max);\n if (type.len !== undefined) {\n s.minLength = type.len;\n s.maxLength = type.len;\n }\n if (type.regex) s.pattern = type.regex;\n break;\n case 'number':\n s.type = 'number';\n if (type.min !== undefined) s.minimum = Number(type.min);\n if (type.max !== undefined) s.maximum = Number(type.max);\n break;\n case 'int':\n s.type = 'integer';\n if (type.min !== undefined) s.minimum = Number(type.min);\n if (type.max !== undefined) s.maximum = Number(type.max);\n break;\n case 'bigint':\n s.type = 'integer';\n s.format = 'int64';\n break;\n case 'boolean':\n s.type = 'boolean';\n break;\n case 'date':\n s.type = 'string';\n s.format = 'date';\n break;\n case 'time':\n s.type = 'string';\n s.format = 'time';\n break;\n case 'datetime':\n s.type = 'string';\n s.format = 'date-time';\n break;\n case 'duration':\n s.type = 'string';\n s.format = 'duration';\n break;\n case 'interval':\n s.type = 'string';\n s.format = 'interval';\n break;\n case 'email':\n s.type = 'string';\n s.format = 'email';\n break;\n case 'url':\n s.type = 'string';\n s.format = 'uri';\n break;\n case 'uuid':\n s.type = 'string';\n s.format = 'uuid';\n break;\n case 'unknown':\n // No type constraint\n break;\n case 'null':\n s.type = 'null';\n break;\n case 'object':\n s.type = 'object';\n break;\n case 'binary':\n s.type = 'string';\n s.format = 'binary';\n break;\n case 'json':\n // Any JSON value — no type constraint\n break;\n default: {\n const _exhaustive: never = type.name;\n throw new Error(`plugin-openapi: unmapped scalar '${String(_exhaustive)}' — add a case`);\n }\n }\n\n return s;\n}\n\nfunction arrayToSchema(type: import('@contractkit/core').ArrayTypeNode, modelMap?: Map<string, ModelNode>): Record<string, unknown> {\n const s: Record<string, unknown> = { type: 'array', items: typeToSchema(type.item, modelMap) };\n if (type.min !== undefined) s.minItems = type.min;\n if (type.max !== undefined) s.maxItems = type.max;\n return s;\n}\n\nfunction inlineObjectToSchema(fields: FieldNode[], modelMap?: Map<string, ModelNode>): Record<string, unknown> {\n const properties: Record<string, unknown> = {};\n const required: string[] = [];\n\n for (const field of fields) {\n properties[field.name] = fieldToSchema(field, modelMap);\n if (!field.optional) {\n required.push(field.name);\n }\n }\n\n const schema: Record<string, unknown> = { type: 'object', properties };\n if (required.length > 0) schema.required = required;\n return schema;\n}\n\nfunction wrapNullable(schema: Record<string, unknown>): Record<string, unknown> {\n // OpenAPI 3.1 uses JSON Schema nullable via oneOf or type array\n if (schema.$ref) {\n return { oneOf: [schema, { type: 'null' }] };\n }\n if (typeof schema.type === 'string') {\n schema.type = [schema.type, 'null'];\n }\n return schema;\n}\n\n// ─── Operation building ─────────────────────────────────────────────────\n\nfunction buildOperation(route: OpRouteNode, op: OpOperationNode): Record<string, unknown> {\n const operation: Record<string, unknown> = {};\n\n // operationId from service binding or SDK name\n if (op.sdk) {\n operation.operationId = op.sdk;\n } else if (op.service) {\n const methodPart = op.service.split('.').pop();\n if (methodPart) operation.operationId = methodPart;\n }\n\n if (op.description) {\n operation.description = op.description;\n }\n\n // Parameters: path params + query + headers\n const parameters: Record<string, unknown>[] = [];\n\n if (route.params) {\n parameters.push(...paramSourceToParams(route.params, 'path'));\n }\n if (op.query) {\n parameters.push(...paramSourceToParams(op.query, 'query'));\n }\n if (op.headers) {\n parameters.push(...paramSourceToParams(op.headers, 'header'));\n }\n\n if (parameters.length > 0) {\n operation.parameters = parameters;\n }\n\n // Request body\n if (op.request && op.request.bodies.length > 0) {\n const content: Record<string, { schema: ReturnType<typeof typeToSchema> }> = {};\n for (const body of op.request.bodies) {\n content[body.contentType] = { schema: typeToSchema(body.bodyType) };\n }\n operation.requestBody = { required: true, content };\n }\n\n // Effective security (operation-level wins; falls back to route-level)\n // security: none → empty array (explicit public endpoint, overrides global default)\n // security: { fields } → omit operation-level entry (rely on global security from config)\n const effectiveSecurity = resolveSecurity(route, op);\n if (effectiveSecurity === SECURITY_NONE) {\n operation.security = [];\n }\n\n // Responses\n const responses: Record<string, unknown> = {};\n for (const resp of op.responses) {\n const statusKey = String(resp.statusCode);\n const responseObject: Record<string, unknown> = {\n description: statusDescription(resp.statusCode),\n };\n if (resp.bodyType && resp.contentType) {\n responseObject.content = {\n [resp.contentType]: {\n schema: typeToSchema(resp.bodyType),\n },\n };\n }\n if (resp.headers && resp.headers.length > 0) {\n const headers: Record<string, unknown> = {};\n for (const h of resp.headers) {\n const headerObject: Record<string, unknown> = {\n schema: typeToSchema(h.type),\n };\n if (!h.optional) headerObject.required = true;\n if (h.description) headerObject.description = h.description;\n headers[h.name] = headerObject;\n }\n responseObject.headers = headers;\n }\n responses[statusKey] = responseObject;\n }\n\n if (Object.keys(responses).length > 0) {\n operation.responses = responses;\n }\n\n return operation;\n}\n\nfunction paramSourceToParams(source: ParamSource, location: 'path' | 'query' | 'header'): Record<string, unknown>[] {\n if (source.kind === 'ref') {\n // Type reference name used as param source — emit a single $ref\n return [\n {\n name: source.name,\n in: location,\n required: location === 'path',\n schema: { $ref: `#/components/schemas/${source.name}` },\n },\n ];\n }\n\n if (source.kind === 'params') {\n // Inline param declarations\n return source.nodes.map(p => ({\n name: p.name,\n in: location,\n required: location === 'path',\n schema: typeToSchema(p.type),\n }));\n }\n\n // ContractTypeNode (inline object or other type) — if it's an inlineObject, expand fields\n if (source.node.kind === 'inlineObject') {\n return source.node.fields.map(f => ({\n name: f.name,\n in: location,\n required: location === 'path' ? true : !f.optional,\n schema: typeToSchema(f.type),\n }));\n }\n\n // For a ref type used as param source\n if (source.node.kind === 'ref') {\n return [\n {\n name: source.node.name,\n in: location,\n required: location === 'path',\n schema: { $ref: `#/components/schemas/${source.node.name}` },\n },\n ];\n }\n\n return [];\n}\n\nfunction statusDescription(code: number): string {\n const descriptions: Record<number, string> = {\n 200: 'Successful response',\n 201: 'Created',\n 204: 'No content',\n 400: 'Bad request',\n 401: 'Unauthorized',\n 403: 'Forbidden',\n 404: 'Not found',\n 409: 'Conflict',\n 422: 'Unprocessable entity',\n 500: 'Internal server error',\n };\n return descriptions[code] ?? `Response ${code}`;\n}\n\n// ─── YAML serializer ──────────────────────────────────────────────────────\n\n/**\n * Minimal YAML serializer sufficient for OpenAPI documents.\n * Avoids external dependency while producing clean, readable output.\n */\nexport function toYaml(value: unknown, indent = 0): string {\n if (value === null || value === undefined) return 'null';\n if (typeof value === 'boolean') return value ? 'true' : 'false';\n if (typeof value === 'number') return String(value);\n if (typeof value === 'bigint') return String(value);\n\n if (typeof value === 'string') {\n return yamlString(value);\n }\n\n if (Array.isArray(value)) {\n if (value.length === 0) return '[]';\n\n // Check if all items are simple scalars (for inline arrays like enum values, required lists)\n if (value.every(v => typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean')) {\n const items = value.map(v => (typeof v === 'string' ? yamlString(v) : String(v)));\n const inline = `[${items.join(', ')}]`;\n if (inline.length < 80) return inline;\n }\n\n const lines: string[] = [];\n const pad = ' '.repeat(indent);\n for (const item of value) {\n if (isPlainObject(item)) {\n const entries = Object.entries(item as Record<string, unknown>);\n if (entries.length > 0) {\n const [firstKey, firstVal] = entries[0]!;\n const firstValStr = isComplex(firstVal) ? `\\n${toYamlValue(firstVal, indent + 2)}` : ` ${toYaml(firstVal, indent + 2)}`;\n lines.push(`${pad}- ${yamlKey(firstKey)}:${firstValStr}`);\n for (let i = 1; i < entries.length; i++) {\n const [k, v] = entries[i]!;\n const valStr = isComplex(v) ? `\\n${toYamlValue(v, indent + 2)}` : ` ${toYaml(v, indent + 2)}`;\n lines.push(`${pad} ${yamlKey(k)}:${valStr}`);\n }\n } else {\n lines.push(`${pad}- {}`);\n }\n } else {\n lines.push(`${pad}- ${toYaml(item, indent + 1)}`);\n }\n }\n return lines.join('\\n');\n }\n\n if (isPlainObject(value)) {\n const obj = value as Record<string, unknown>;\n const entries = Object.entries(obj);\n if (entries.length === 0) return '{}';\n\n const pad = ' '.repeat(indent);\n const lines: string[] = [];\n for (const [key, val] of entries) {\n if (isComplex(val)) {\n lines.push(`${pad}${yamlKey(key)}:`);\n lines.push(toYamlValue(val, indent + 1));\n } else {\n lines.push(`${pad}${yamlKey(key)}: ${toYaml(val, indent + 1)}`);\n }\n }\n return lines.join('\\n');\n }\n\n return String(value);\n}\n\nfunction toYamlValue(value: unknown, indent: number): string {\n if (Array.isArray(value)) {\n return toYaml(value, indent);\n }\n if (isPlainObject(value)) {\n return toYaml(value, indent);\n }\n return ' '.repeat(indent) + toYaml(value, indent);\n}\n\nfunction isComplex(value: unknown): boolean {\n if (Array.isArray(value)) {\n // Simple scalar arrays can be inlined\n if (value.every(v => typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean')) {\n const items = value.map(v => (typeof v === 'string' ? yamlString(v) : String(v)));\n return `[${items.join(', ')}]`.length >= 80;\n }\n return true;\n }\n return isPlainObject(value);\n}\n\nfunction isPlainObject(value: unknown): boolean {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction yamlString(s: string): string {\n // Use plain style if safe, otherwise single-quoted\n if (s === '') return \"''\";\n if (/^[\\w./-]+$/.test(s) && !/^(true|false|null|yes|no|on|off)$/i.test(s) && !/^\\d/.test(s)) {\n return s;\n }\n // Single-quote, escaping internal single quotes by doubling\n return `'${s.replace(/'/g, \"''\")}'`;\n}\n\nfunction yamlKey(key: string): string {\n // Keys with special chars need quoting\n if (/^[\\w-]+$/.test(key) && !/^(true|false|null|yes|no|on|off)$/i.test(key)) {\n return key;\n }\n return `'${key.replace(/'/g, \"''\")}'`;\n}\n"],"mappings":"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","names":["resolve","resolveModifiers","resolveSecurity","SECURITY_NONE","collectRefsFromType","type","out","kind","add","name","item","items","value","member","members","inner","field","fields","collectParamSourceRefs","source","p","nodes","node","collectPublicTypeRefs","opRoots","includeInternal","refs","Set","opRoot","route","routes","op","operations","resolveModifiers","includes","request","body","bodies","bodyType","resp","responses","headers","h","params","query","computeReachableSchemas","seeds","modelMap","reachable","frontier","length","pop","model","get","bases","b","ref","has","push","generateOpenApi","ctx","contractRoots","config","securitySchemes","doc","openapi","info","title","version","description","servers","security","allSchemas","Map","contractRoot","models","set","modelToSchema","paths","oaPath","convertPath","path","mods","operation","buildOperation","deprecated","method","schemas","filtered","schema","Object","entries","components","keys","toYaml","typeToSchema","properties","required","prop","fieldToSchema","optional","allOf","map","$ref","nullable","wrapNullable","visibility","readOnly","writeOnly","default","undefined","scalarToSchema","arrayToSchema","prefixItems","i","additionalProperties","enum","values","const","oneOf","m","mapping","literalValues","resolveDiscriminatorLiterals","discriminator","v","result","propertyName","inlineObjectToSchema","modelName","find","f","String","s","min","minLength","Number","max","maxLength","len","regex","pattern","minimum","maximum","format","_exhaustive","Error","minItems","maxItems","sdk","operationId","service","methodPart","split","parameters","paramSourceToParams","content","contentType","requestBody","effectiveSecurity","resolveSecurity","SECURITY_NONE","statusKey","statusCode","responseObject","statusDescription","headerObject","location","in","code","descriptions","indent","yamlString","Array","isArray","every","inline","join","lines","pad","repeat","isPlainObject","firstKey","firstVal","firstValStr","isComplex","toYamlValue","yamlKey","k","valStr","obj","key","val","test","replace","plugin","name","cacheKey","generateTargets","contractRoots","opRoots","ctx","securitySchemes","openapiConfig","options","base","baseDir","resolve","rootDir","outPath","output","emitFile","generateOpenApi","config","createOpenApiPlugin","JSON","stringify","content"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contractkit/plugin-openapi",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.14",
|
|
4
4
|
"description": "ContractKit built-in plugin: OpenAPI 3.0 YAML generation",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Marooned Software",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
".": "./dist/index.js"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@contractkit/core": "0.
|
|
29
|
+
"@contractkit/core": "0.23.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@repo/config-eslint": "0.3.1",
|
package/src/codegen-openapi.ts
CHANGED
|
@@ -10,11 +10,13 @@ import type {
|
|
|
10
10
|
} from '@contractkit/core';
|
|
11
11
|
import { resolveModifiers, resolveSecurity, SECURITY_NONE } from '@contractkit/core';
|
|
12
12
|
|
|
13
|
+
/** A single entry for the OpenAPI `servers` array. */
|
|
13
14
|
export interface OpenApiServerEntry {
|
|
14
15
|
url: string;
|
|
15
16
|
description?: string;
|
|
16
17
|
}
|
|
17
18
|
|
|
19
|
+
/** A named entry for `components.securitySchemes` (e.g. an HTTP bearer or API-key scheme). */
|
|
18
20
|
export interface OpenApiSecurityScheme {
|
|
19
21
|
type: string;
|
|
20
22
|
scheme?: string;
|
|
@@ -23,6 +25,7 @@ export interface OpenApiSecurityScheme {
|
|
|
23
25
|
in?: string;
|
|
24
26
|
}
|
|
25
27
|
|
|
28
|
+
/** Plugin configuration controlling the generated OpenAPI document (info block, servers, security, internal-op visibility). */
|
|
26
29
|
export interface OpenApiConfig {
|
|
27
30
|
baseDir?: string;
|
|
28
31
|
output?: string;
|
|
@@ -134,6 +137,7 @@ function computeReachableSchemas(seeds: Set<string>, modelMap: Map<string, Model
|
|
|
134
137
|
|
|
135
138
|
// ─── Public entry point ────────────────────────────────────────────────────
|
|
136
139
|
|
|
140
|
+
/** Inputs to {@link generateOpenApi}: the parsed contracts/operations plus the OpenAPI config and shared security schemes. */
|
|
137
141
|
export interface OpenApiCodegenContext {
|
|
138
142
|
contractRoots: ContractRootNode[];
|
|
139
143
|
opRoots: OpRootNode[];
|
|
@@ -142,6 +146,14 @@ export interface OpenApiCodegenContext {
|
|
|
142
146
|
securitySchemes?: Record<string, OpenApiSecurityScheme>;
|
|
143
147
|
}
|
|
144
148
|
|
|
149
|
+
/**
|
|
150
|
+
* Generate an OpenAPI 3.0 document (YAML) from the parsed contracts and operations.
|
|
151
|
+
*
|
|
152
|
+
* Emits `paths` from the operations and `components.schemas` from the contracts, honoring
|
|
153
|
+
* `config.includeInternal` for `operation(internal)` visibility.
|
|
154
|
+
*
|
|
155
|
+
* @returns The OpenAPI document serialized as a YAML string.
|
|
156
|
+
*/
|
|
145
157
|
export function generateOpenApi(ctx: OpenApiCodegenContext): string {
|
|
146
158
|
const { contractRoots, opRoots, config, securitySchemes } = ctx;
|
|
147
159
|
const includeInternal = config.includeInternal ?? false;
|
|
@@ -364,7 +376,15 @@ function resolveDiscriminatorLiterals(modelName: string, discriminator: string,
|
|
|
364
376
|
return [];
|
|
365
377
|
}
|
|
366
378
|
|
|
367
|
-
|
|
379
|
+
/**
|
|
380
|
+
* Map a ContractKit scalar type node to its OpenAPI schema object (`{ type, format, ... }`),
|
|
381
|
+
* carrying across constraints like min/max/length.
|
|
382
|
+
*
|
|
383
|
+
* @throws {Error} If the scalar name is not mapped — every member of the closed scalar set must
|
|
384
|
+
* have an explicit case, so an unmapped name signals a scalar was added to core without updating
|
|
385
|
+
* this plugin (rather than silently emitting a type-less, permissive schema).
|
|
386
|
+
*/
|
|
387
|
+
export function scalarToSchema(type: import('@contractkit/core').ScalarTypeNode): Record<string, unknown> {
|
|
368
388
|
const s: Record<string, unknown> = {};
|
|
369
389
|
|
|
370
390
|
switch (type.name) {
|
|
@@ -399,6 +419,10 @@ function scalarToSchema(type: import('@contractkit/core').ScalarTypeNode): Recor
|
|
|
399
419
|
s.type = 'string';
|
|
400
420
|
s.format = 'date';
|
|
401
421
|
break;
|
|
422
|
+
case 'time':
|
|
423
|
+
s.type = 'string';
|
|
424
|
+
s.format = 'time';
|
|
425
|
+
break;
|
|
402
426
|
case 'datetime':
|
|
403
427
|
s.type = 'string';
|
|
404
428
|
s.format = 'date-time';
|
|
@@ -407,6 +431,10 @@ function scalarToSchema(type: import('@contractkit/core').ScalarTypeNode): Recor
|
|
|
407
431
|
s.type = 'string';
|
|
408
432
|
s.format = 'duration';
|
|
409
433
|
break;
|
|
434
|
+
case 'interval':
|
|
435
|
+
s.type = 'string';
|
|
436
|
+
s.format = 'interval';
|
|
437
|
+
break;
|
|
410
438
|
case 'email':
|
|
411
439
|
s.type = 'string';
|
|
412
440
|
s.format = 'email';
|
|
@@ -435,6 +463,10 @@ function scalarToSchema(type: import('@contractkit/core').ScalarTypeNode): Recor
|
|
|
435
463
|
case 'json':
|
|
436
464
|
// Any JSON value — no type constraint
|
|
437
465
|
break;
|
|
466
|
+
default: {
|
|
467
|
+
const _exhaustive: never = type.name;
|
|
468
|
+
throw new Error(`plugin-openapi: unmapped scalar '${String(_exhaustive)}' — add a case`);
|
|
469
|
+
}
|
|
438
470
|
}
|
|
439
471
|
|
|
440
472
|
return s;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
2
|
import { parseCk, decomposeCk, applyOptionsDefaults, DiagnosticCollector } from '@contractkit/core';
|
|
3
|
-
import { generateOpenApi, toYaml } from '../src/codegen-openapi.js';
|
|
3
|
+
import { generateOpenApi, toYaml, scalarToSchema } from '../src/codegen-openapi.js';
|
|
4
4
|
import {
|
|
5
5
|
scalarType,
|
|
6
6
|
arrayType,
|
|
@@ -23,6 +23,22 @@ import {
|
|
|
23
23
|
opRoot,
|
|
24
24
|
} from './helpers.js';
|
|
25
25
|
|
|
26
|
+
// ─── scalarToSchema ───────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
describe('scalarToSchema', () => {
|
|
29
|
+
it('maps time to string with format time', () => {
|
|
30
|
+
expect(scalarToSchema({ kind: 'scalar', name: 'time' })).toEqual({ type: 'string', format: 'time' });
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('maps interval to string with format interval', () => {
|
|
34
|
+
expect(scalarToSchema({ kind: 'scalar', name: 'interval' })).toEqual({ type: 'string', format: 'interval' });
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('throws on an unmapped scalar name', () => {
|
|
38
|
+
expect(() => scalarToSchema({ kind: 'scalar', name: 'decimal' } as any)).toThrow(/unmapped scalar 'decimal'/);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
26
42
|
// ─── YAML serializer ──────────────────────────────────────────────────────
|
|
27
43
|
|
|
28
44
|
describe('toYaml', () => {
|