@contractkit/openapi-to-ck
Advanced tools
| var __defProp = Object.defineProperty; | ||
| var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); | ||
| // src/normalize.ts | ||
| function normalize(doc, warnings) { | ||
| const version = detectVersion(doc); | ||
| if (version === "2.0") { | ||
| return normalizeSwagger2(doc, warnings); | ||
| } | ||
| if (version === "3.0") { | ||
| return normalizeOas30(doc, warnings); | ||
| } | ||
| return doc; | ||
| } | ||
| __name(normalize, "normalize"); | ||
| function detectVersion(doc) { | ||
| if (typeof doc.swagger === "string" && doc.swagger.startsWith("2")) return "2.0"; | ||
| if (typeof doc.openapi === "string") { | ||
| if (doc.openapi.startsWith("3.0")) return "3.0"; | ||
| } | ||
| return "3.1"; | ||
| } | ||
| __name(detectVersion, "detectVersion"); | ||
| function normalizeSwagger2(doc, warnings) { | ||
| const info = doc.info ?? { | ||
| title: "Untitled", | ||
| version: "0.0.0" | ||
| }; | ||
| const basePath = doc.basePath ?? ""; | ||
| const schemes = doc.schemes ?? [ | ||
| "https" | ||
| ]; | ||
| const host = doc.host ?? "localhost"; | ||
| const globalConsumes = doc.consumes ?? [ | ||
| "application/json" | ||
| ]; | ||
| const globalProduces = doc.produces ?? [ | ||
| "application/json" | ||
| ]; | ||
| const result = { | ||
| openapi: "3.1.0", | ||
| info: { | ||
| title: info.title ?? "Untitled", | ||
| version: info.version ?? "0.0.0", | ||
| description: info.description | ||
| }, | ||
| servers: [ | ||
| { | ||
| url: `${schemes[0]}://${host}${basePath}` | ||
| } | ||
| ], | ||
| paths: {}, | ||
| components: { | ||
| schemas: {}, | ||
| securitySchemes: {} | ||
| }, | ||
| tags: doc.tags ?? [] | ||
| }; | ||
| const definitions = doc.definitions ?? {}; | ||
| for (const [name, schema] of Object.entries(definitions)) { | ||
| result.components.schemas[name] = normalizeNullable30(schema); | ||
| } | ||
| const secDefs = doc.securityDefinitions ?? {}; | ||
| for (const [name, scheme] of Object.entries(secDefs)) { | ||
| result.components.securitySchemes[name] = convertSecurityScheme2(scheme); | ||
| } | ||
| const paths = doc.paths ?? {}; | ||
| for (const [path, pathItem] of Object.entries(paths)) { | ||
| result.paths[path] = normalizePathItem2(pathItem, globalConsumes, globalProduces, warnings); | ||
| } | ||
| if (doc.security) { | ||
| result.security = doc.security; | ||
| } | ||
| return result; | ||
| } | ||
| __name(normalizeSwagger2, "normalizeSwagger2"); | ||
| function normalizePathItem2(pathItem, globalConsumes, globalProduces, warnings) { | ||
| const methods = [ | ||
| "get", | ||
| "post", | ||
| "put", | ||
| "patch", | ||
| "delete", | ||
| "head", | ||
| "options" | ||
| ]; | ||
| const normalized = {}; | ||
| const pathParams = pathItem.parameters ?? []; | ||
| for (const method of methods) { | ||
| const op = pathItem[method]; | ||
| if (!op) continue; | ||
| const opConsumes = op.consumes ?? globalConsumes; | ||
| const opProduces = op.produces ?? globalProduces; | ||
| const params = [ | ||
| ...pathParams, | ||
| ...op.parameters ?? [] | ||
| ]; | ||
| const nonBodyParams = []; | ||
| let requestBody; | ||
| for (const param of params) { | ||
| if (param.in === "body") { | ||
| const contentType = opConsumes[0] ?? "application/json"; | ||
| requestBody = { | ||
| description: param.description, | ||
| required: param.required ?? true, | ||
| content: { | ||
| [contentType]: { | ||
| schema: normalizeNullable30(param.schema ?? {}) | ||
| } | ||
| } | ||
| }; | ||
| } else if (param.in === "formData") { | ||
| warnings.info(`#/paths/${encodePathSegment(method)}`, "formData parameters converted to multipart/form-data requestBody"); | ||
| if (!requestBody) { | ||
| requestBody = { | ||
| content: { | ||
| "multipart/form-data": { | ||
| schema: { | ||
| type: "object", | ||
| properties: {}, | ||
| required: [] | ||
| } | ||
| } | ||
| } | ||
| }; | ||
| } | ||
| const formSchema = requestBody.content["multipart/form-data"].schema; | ||
| const props = formSchema.properties; | ||
| props[param.name] = normalizeNullable30(param); | ||
| if (param.required) { | ||
| formSchema.required.push(param.name); | ||
| } | ||
| } else { | ||
| const normalizedParam = { | ||
| ...param | ||
| }; | ||
| if (param.type) { | ||
| normalizedParam.schema = normalizeNullable30({ | ||
| type: param.type, | ||
| format: param.format, | ||
| enum: param.enum, | ||
| items: param.items, | ||
| default: param.default, | ||
| minimum: param.minimum, | ||
| maximum: param.maximum, | ||
| minLength: param.minLength, | ||
| maxLength: param.maxLength, | ||
| pattern: param.pattern | ||
| }); | ||
| delete normalizedParam.type; | ||
| delete normalizedParam.format; | ||
| delete normalizedParam.enum; | ||
| delete normalizedParam.items; | ||
| } | ||
| nonBodyParams.push(normalizedParam); | ||
| } | ||
| } | ||
| const responses = {}; | ||
| const opResponses = op.responses ?? {}; | ||
| for (const [code, resp] of Object.entries(opResponses)) { | ||
| const contentType = opProduces[0] ?? "application/json"; | ||
| const headers = convertResponseHeaders2(resp.headers); | ||
| const responseEntry = { | ||
| description: resp.description ?? "" | ||
| }; | ||
| if (resp.schema) { | ||
| responseEntry.content = { | ||
| [contentType]: { | ||
| schema: normalizeNullable30(resp.schema) | ||
| } | ||
| }; | ||
| } | ||
| if (headers) { | ||
| responseEntry.headers = headers; | ||
| } | ||
| responses[code] = responseEntry; | ||
| } | ||
| normalized[method] = { | ||
| operationId: op.operationId, | ||
| summary: op.summary, | ||
| description: op.description, | ||
| tags: op.tags, | ||
| parameters: nonBodyParams.length > 0 ? nonBodyParams : void 0, | ||
| requestBody, | ||
| responses, | ||
| security: op.security, | ||
| deprecated: op.deprecated | ||
| }; | ||
| } | ||
| return normalized; | ||
| } | ||
| __name(normalizePathItem2, "normalizePathItem2"); | ||
| function convertResponseHeaders2(headers) { | ||
| if (!headers) return void 0; | ||
| const out = {}; | ||
| for (const [name, header] of Object.entries(headers)) { | ||
| if (!header || typeof header !== "object") continue; | ||
| const { description, type, format, items, ...rest } = header; | ||
| const schema = { | ||
| ...rest | ||
| }; | ||
| if (type !== void 0) schema.type = type; | ||
| if (format !== void 0) schema.format = format; | ||
| if (items !== void 0) schema.items = items; | ||
| const normalized = {}; | ||
| if (description !== void 0) normalized.description = description; | ||
| if (Object.keys(schema).length > 0) normalized.schema = normalizeNullable30(schema); | ||
| out[name] = normalized; | ||
| } | ||
| return Object.keys(out).length > 0 ? out : void 0; | ||
| } | ||
| __name(convertResponseHeaders2, "convertResponseHeaders2"); | ||
| function convertSecurityScheme2(scheme) { | ||
| const type = scheme.type; | ||
| if (type === "basic") { | ||
| return { | ||
| type: "http", | ||
| scheme: "basic" | ||
| }; | ||
| } | ||
| if (type === "apiKey") { | ||
| return { | ||
| type: "apiKey", | ||
| name: scheme.name, | ||
| in: scheme.in | ||
| }; | ||
| } | ||
| if (type === "oauth2") { | ||
| const flow = scheme.flow; | ||
| const flows = {}; | ||
| if (flow === "implicit") { | ||
| flows.implicit = { | ||
| authorizationUrl: scheme.authorizationUrl, | ||
| scopes: scheme.scopes ?? {} | ||
| }; | ||
| } else if (flow === "password") { | ||
| flows.password = { | ||
| tokenUrl: scheme.tokenUrl, | ||
| scopes: scheme.scopes ?? {} | ||
| }; | ||
| } else if (flow === "application") { | ||
| flows.clientCredentials = { | ||
| tokenUrl: scheme.tokenUrl, | ||
| scopes: scheme.scopes ?? {} | ||
| }; | ||
| } else if (flow === "accessCode") { | ||
| flows.authorizationCode = { | ||
| authorizationUrl: scheme.authorizationUrl, | ||
| tokenUrl: scheme.tokenUrl, | ||
| scopes: scheme.scopes ?? {} | ||
| }; | ||
| } | ||
| return { | ||
| type: "oauth2", | ||
| flows | ||
| }; | ||
| } | ||
| return scheme; | ||
| } | ||
| __name(convertSecurityScheme2, "convertSecurityScheme2"); | ||
| function normalizeOas30(doc, _warnings) { | ||
| if (doc.components?.schemas) { | ||
| for (const [name, schema] of Object.entries(doc.components.schemas)) { | ||
| doc.components.schemas[name] = normalizeNullable30(schema); | ||
| } | ||
| } | ||
| if (doc.paths) { | ||
| for (const pathItem of Object.values(doc.paths)) { | ||
| normalizePathItemSchemas(pathItem); | ||
| } | ||
| } | ||
| doc.openapi = "3.1.0"; | ||
| return doc; | ||
| } | ||
| __name(normalizeOas30, "normalizeOas30"); | ||
| function normalizePathItemSchemas(pathItem) { | ||
| const methods = [ | ||
| "get", | ||
| "post", | ||
| "put", | ||
| "patch", | ||
| "delete", | ||
| "head", | ||
| "options" | ||
| ]; | ||
| for (const method of methods) { | ||
| const op = pathItem[method]; | ||
| if (!op) continue; | ||
| const params = op.parameters ?? []; | ||
| for (const param of params) { | ||
| if (param.schema) { | ||
| param.schema = normalizeNullable30(param.schema); | ||
| } | ||
| } | ||
| const reqBody = op.requestBody; | ||
| if (reqBody?.content) { | ||
| for (const mediaType of Object.values(reqBody.content)) { | ||
| if (mediaType.schema) { | ||
| mediaType.schema = normalizeNullable30(mediaType.schema); | ||
| } | ||
| } | ||
| } | ||
| const responses = op.responses ?? {}; | ||
| for (const resp of Object.values(responses)) { | ||
| if (resp.content) { | ||
| for (const mediaType of Object.values(resp.content)) { | ||
| if (mediaType.schema) { | ||
| mediaType.schema = normalizeNullable30(mediaType.schema); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| __name(normalizePathItemSchemas, "normalizePathItemSchemas"); | ||
| function normalizeNullable30(schema) { | ||
| if (!schema || typeof schema !== "object") return schema; | ||
| const result = { | ||
| ...schema | ||
| }; | ||
| if (result.nullable === true && typeof result.type === "string") { | ||
| result.type = [ | ||
| result.type, | ||
| "null" | ||
| ]; | ||
| delete result.nullable; | ||
| } | ||
| if (result.properties && typeof result.properties === "object") { | ||
| const props = result.properties; | ||
| for (const [key, val] of Object.entries(props)) { | ||
| props[key] = normalizeNullable30(val); | ||
| } | ||
| } | ||
| if (result.items && typeof result.items === "object" && !Array.isArray(result.items)) { | ||
| result.items = normalizeNullable30(result.items); | ||
| } | ||
| if (result.additionalProperties && typeof result.additionalProperties === "object") { | ||
| result.additionalProperties = normalizeNullable30(result.additionalProperties); | ||
| } | ||
| for (const combiner of [ | ||
| "allOf", | ||
| "oneOf", | ||
| "anyOf" | ||
| ]) { | ||
| if (Array.isArray(result[combiner])) { | ||
| result[combiner] = result[combiner].map(normalizeNullable30); | ||
| } | ||
| } | ||
| return result; | ||
| } | ||
| __name(normalizeNullable30, "normalizeNullable30"); | ||
| function encodePathSegment(s) { | ||
| return s.replace(/~/g, "~0").replace(/\//g, "~1"); | ||
| } | ||
| __name(encodePathSegment, "encodePathSegment"); | ||
| // src/circular-refs.ts | ||
| function detectCircularRefs(schemas) { | ||
| const circular = /* @__PURE__ */ new Set(); | ||
| const visiting = /* @__PURE__ */ new Set(); | ||
| const visited = /* @__PURE__ */ new Set(); | ||
| function visit(name) { | ||
| if (visited.has(name)) return; | ||
| if (visiting.has(name)) { | ||
| circular.add(name); | ||
| return; | ||
| } | ||
| visiting.add(name); | ||
| const schema = schemas[name]; | ||
| if (schema && typeof schema === "object") { | ||
| for (const ref of collectRefs(schema)) { | ||
| const refName = extractRefName(ref); | ||
| if (refName && schemas[refName]) { | ||
| visit(refName); | ||
| } | ||
| } | ||
| } | ||
| visiting.delete(name); | ||
| visited.add(name); | ||
| } | ||
| __name(visit, "visit"); | ||
| for (const name of Object.keys(schemas)) { | ||
| visit(name); | ||
| } | ||
| return circular; | ||
| } | ||
| __name(detectCircularRefs, "detectCircularRefs"); | ||
| function collectRefs(obj) { | ||
| const refs = []; | ||
| function walk(val) { | ||
| if (!val || typeof val !== "object") return; | ||
| if (Array.isArray(val)) { | ||
| for (const item of val) walk(item); | ||
| return; | ||
| } | ||
| const record = val; | ||
| if (typeof record.$ref === "string") { | ||
| refs.push(record.$ref); | ||
| } | ||
| for (const v of Object.values(record)) { | ||
| walk(v); | ||
| } | ||
| } | ||
| __name(walk, "walk"); | ||
| walk(obj); | ||
| return refs; | ||
| } | ||
| __name(collectRefs, "collectRefs"); | ||
| function extractRefName(ref) { | ||
| const match = ref.match(/^#\/(?:components\/schemas|definitions)\/(.+)$/); | ||
| return match?.[1]; | ||
| } | ||
| __name(extractRefName, "extractRefName"); | ||
| // src/schema-to-ast.ts | ||
| var LOC = { | ||
| file: "", | ||
| line: 0 | ||
| }; | ||
| var FORMAT_TO_SCALAR = { | ||
| email: "email", | ||
| uri: "url", | ||
| url: "url", | ||
| uuid: "uuid", | ||
| date: "date", | ||
| "date-time": "datetime", | ||
| time: "time", | ||
| binary: "binary", | ||
| int64: "bigint" | ||
| }; | ||
| function schemasToModels(schemas, ctx) { | ||
| const models = []; | ||
| for (const [name, schema] of Object.entries(schemas)) { | ||
| const modelCtx = { | ||
| ...ctx, | ||
| path: `#/components/schemas/${name}` | ||
| }; | ||
| const model = schemaToModel(name, schema, modelCtx); | ||
| if (model) models.push(model); | ||
| } | ||
| models.push(...ctx.extractedModels); | ||
| return models; | ||
| } | ||
| __name(schemasToModels, "schemasToModels"); | ||
| function schemaToModel(name, schema, ctx) { | ||
| warnUnsupported(schema, ctx); | ||
| const description = ctx.includeComments ? schema.description : void 0; | ||
| if (schema.allOf && schema.allOf.length === 2) { | ||
| const [first, second] = schema.allOf; | ||
| const refMember = first?.$ref ? first : second?.$ref ? second : null; | ||
| const objectMember = first?.$ref ? second : first; | ||
| if (refMember?.$ref && objectMember?.properties) { | ||
| const baseName = extractRefName(refMember.$ref); | ||
| if (baseName) { | ||
| const fields = schemaPropertiesToFields(objectMember, ctx); | ||
| return { | ||
| kind: "model", | ||
| name, | ||
| bases: [ | ||
| baseName | ||
| ], | ||
| fields, | ||
| description, | ||
| loc: LOC | ||
| }; | ||
| } | ||
| } | ||
| } | ||
| if (schema.properties || schema.type === "object" && !schema.additionalProperties) { | ||
| const fields = schemaPropertiesToFields(schema, ctx); | ||
| return { | ||
| kind: "model", | ||
| name, | ||
| fields, | ||
| description, | ||
| loc: LOC | ||
| }; | ||
| } | ||
| const typeNode = schemaToTypeNode(schema, ctx); | ||
| return { | ||
| kind: "model", | ||
| name, | ||
| fields: [], | ||
| type: typeNode, | ||
| description, | ||
| loc: LOC | ||
| }; | ||
| } | ||
| __name(schemaToModel, "schemaToModel"); | ||
| function schemaToTypeNode(schema, ctx) { | ||
| if (schema.$ref) { | ||
| const refName = extractRefName(schema.$ref); | ||
| if (refName) { | ||
| if (ctx.circularRefs.has(refName)) { | ||
| return { | ||
| kind: "lazy", | ||
| inner: { | ||
| kind: "ref", | ||
| name: refName | ||
| } | ||
| }; | ||
| } | ||
| return { | ||
| kind: "ref", | ||
| name: refName | ||
| }; | ||
| } | ||
| ctx.warnings.warn(ctx.path, `Unresolvable $ref: ${schema.$ref}`); | ||
| return { | ||
| kind: "scalar", | ||
| name: "unknown" | ||
| }; | ||
| } | ||
| if (schema.const !== void 0) { | ||
| return { | ||
| kind: "literal", | ||
| value: schema.const | ||
| }; | ||
| } | ||
| if (schema.enum) { | ||
| return { | ||
| kind: "enum", | ||
| values: schema.enum.map(String) | ||
| }; | ||
| } | ||
| if (schema.oneOf && schema.oneOf.length > 0) { | ||
| if (schema.discriminator?.propertyName) { | ||
| return toDiscriminatedUnion(schema.oneOf, schema.discriminator.propertyName, ctx); | ||
| } | ||
| return toUnion(schema.oneOf, ctx); | ||
| } | ||
| if (schema.anyOf && schema.anyOf.length > 0) { | ||
| if (schema.discriminator?.propertyName) { | ||
| return toDiscriminatedUnion(schema.anyOf, schema.discriminator.propertyName, ctx); | ||
| } | ||
| return toUnion(schema.anyOf, ctx); | ||
| } | ||
| if (schema.allOf && schema.allOf.length > 0) { | ||
| if (schema.allOf.length === 1) { | ||
| return schemaToTypeNode(schema.allOf[0], ctx); | ||
| } | ||
| return { | ||
| kind: "intersection", | ||
| members: schema.allOf.map((s) => schemaToTypeNode(s, ctx)) | ||
| }; | ||
| } | ||
| const types = normalizeTypeField(schema); | ||
| if (types === null) { | ||
| if (schema.properties) { | ||
| return schemaToInlineObject(schema, ctx); | ||
| } | ||
| return { | ||
| kind: "scalar", | ||
| name: "unknown" | ||
| }; | ||
| } | ||
| const { baseType, nullable } = types; | ||
| let typeNode; | ||
| switch (baseType) { | ||
| case "string": | ||
| typeNode = stringSchemaToType(schema); | ||
| break; | ||
| case "integer": | ||
| typeNode = integerSchemaToType(schema); | ||
| break; | ||
| case "number": | ||
| typeNode = numberSchemaToType(schema); | ||
| break; | ||
| case "boolean": | ||
| typeNode = { | ||
| kind: "scalar", | ||
| name: "boolean" | ||
| }; | ||
| break; | ||
| case "null": | ||
| typeNode = { | ||
| kind: "scalar", | ||
| name: "null" | ||
| }; | ||
| break; | ||
| case "array": | ||
| typeNode = arraySchemaToType(schema, ctx); | ||
| break; | ||
| case "object": | ||
| typeNode = objectSchemaToType(schema, ctx); | ||
| break; | ||
| default: | ||
| ctx.warnings.warn(ctx.path, `Unknown type: ${baseType}`); | ||
| typeNode = { | ||
| kind: "scalar", | ||
| name: "unknown" | ||
| }; | ||
| } | ||
| if (nullable) { | ||
| return { | ||
| kind: "union", | ||
| members: [ | ||
| typeNode, | ||
| { | ||
| kind: "scalar", | ||
| name: "null" | ||
| } | ||
| ] | ||
| }; | ||
| } | ||
| return typeNode; | ||
| } | ||
| __name(schemaToTypeNode, "schemaToTypeNode"); | ||
| function stringSchemaToType(schema) { | ||
| if (schema.format) { | ||
| const scalarName = FORMAT_TO_SCALAR[schema.format]; | ||
| if (scalarName) { | ||
| return { | ||
| kind: "scalar", | ||
| name: scalarName | ||
| }; | ||
| } | ||
| } | ||
| const mods = {}; | ||
| if (schema.minLength !== void 0 && schema.maxLength !== void 0 && schema.minLength === schema.maxLength) { | ||
| mods.len = schema.minLength; | ||
| } else { | ||
| if (schema.minLength !== void 0) mods.min = schema.minLength; | ||
| if (schema.maxLength !== void 0) mods.max = schema.maxLength; | ||
| } | ||
| if (schema.pattern) mods.regex = `/${schema.pattern}/`; | ||
| if (schema.format && !FORMAT_TO_SCALAR[schema.format]) mods.format = schema.format; | ||
| return { | ||
| kind: "scalar", | ||
| name: "string", | ||
| ...mods | ||
| }; | ||
| } | ||
| __name(stringSchemaToType, "stringSchemaToType"); | ||
| function integerSchemaToType(schema) { | ||
| const name = schema.format === "int64" ? "bigint" : "int"; | ||
| const mods = {}; | ||
| if (schema.minimum !== void 0) mods.min = schema.minimum; | ||
| if (schema.maximum !== void 0) mods.max = schema.maximum; | ||
| return { | ||
| kind: "scalar", | ||
| name, | ||
| ...mods | ||
| }; | ||
| } | ||
| __name(integerSchemaToType, "integerSchemaToType"); | ||
| function numberSchemaToType(schema) { | ||
| const mods = {}; | ||
| if (schema.minimum !== void 0) mods.min = schema.minimum; | ||
| if (schema.maximum !== void 0) mods.max = schema.maximum; | ||
| return { | ||
| kind: "scalar", | ||
| name: "number", | ||
| ...mods | ||
| }; | ||
| } | ||
| __name(numberSchemaToType, "numberSchemaToType"); | ||
| function arraySchemaToType(schema, ctx) { | ||
| if (schema.prefixItems && schema.prefixItems.length > 0) { | ||
| return { | ||
| kind: "tuple", | ||
| items: schema.prefixItems.map((s) => schemaToTypeNode(s, ctx)) | ||
| }; | ||
| } | ||
| const item = schema.items ? schemaToTypeNode(schema.items, ctx) : { | ||
| kind: "scalar", | ||
| name: "unknown" | ||
| }; | ||
| const mods = {}; | ||
| if (schema.minItems !== void 0) mods.min = schema.minItems; | ||
| if (schema.maxItems !== void 0) mods.max = schema.maxItems; | ||
| return { | ||
| kind: "array", | ||
| item, | ||
| ...mods | ||
| }; | ||
| } | ||
| __name(arraySchemaToType, "arraySchemaToType"); | ||
| function objectSchemaToType(schema, ctx) { | ||
| if (schema.additionalProperties && typeof schema.additionalProperties === "object" && !schema.properties) { | ||
| return { | ||
| kind: "record", | ||
| key: { | ||
| kind: "scalar", | ||
| name: "string" | ||
| }, | ||
| value: schemaToTypeNode(schema.additionalProperties, ctx) | ||
| }; | ||
| } | ||
| if (schema.properties) { | ||
| return schemaToInlineObject(schema, ctx); | ||
| } | ||
| return { | ||
| kind: "scalar", | ||
| name: "object" | ||
| }; | ||
| } | ||
| __name(objectSchemaToType, "objectSchemaToType"); | ||
| function schemaToInlineObject(schema, ctx) { | ||
| const fields = schemaPropertiesToFields(schema, ctx); | ||
| return { | ||
| kind: "inlineObject", | ||
| fields | ||
| }; | ||
| } | ||
| __name(schemaToInlineObject, "schemaToInlineObject"); | ||
| function schemaPropertiesToFields(schema, ctx) { | ||
| const properties = schema.properties ?? {}; | ||
| const required = new Set(schema.required ?? []); | ||
| const fields = []; | ||
| for (const [name, propSchema] of Object.entries(properties)) { | ||
| const propCtx = { | ||
| ...ctx, | ||
| path: `${ctx.path}/properties/${name}` | ||
| }; | ||
| const fieldType = schemaToTypeNode(propSchema, propCtx); | ||
| let nullable = false; | ||
| let effectiveType = fieldType; | ||
| if (fieldType.kind === "union") { | ||
| const nonNull = fieldType.members.filter((m) => !(m.kind === "scalar" && m.name === "null")); | ||
| if (nonNull.length < fieldType.members.length) { | ||
| nullable = true; | ||
| effectiveType = nonNull.length === 1 ? nonNull[0] : { | ||
| kind: "union", | ||
| members: nonNull | ||
| }; | ||
| } | ||
| } | ||
| const visibility = propSchema.readOnly ? "readonly" : propSchema.writeOnly ? "writeonly" : "normal"; | ||
| fields.push({ | ||
| name, | ||
| optional: !required.has(name), | ||
| nullable, | ||
| visibility, | ||
| type: effectiveType, | ||
| default: propSchema.default, | ||
| deprecated: propSchema.deprecated, | ||
| description: ctx.includeComments ? propSchema.description : void 0, | ||
| loc: LOC | ||
| }); | ||
| } | ||
| return fields; | ||
| } | ||
| __name(schemaPropertiesToFields, "schemaPropertiesToFields"); | ||
| function normalizeTypeField(schema) { | ||
| if (!schema.type) return null; | ||
| if (typeof schema.type === "string") { | ||
| return { | ||
| baseType: schema.type, | ||
| nullable: false | ||
| }; | ||
| } | ||
| if (Array.isArray(schema.type)) { | ||
| const types = schema.type; | ||
| const nonNull = types.filter((t) => t !== "null"); | ||
| const nullable = types.includes("null"); | ||
| if (nonNull.length === 1) { | ||
| return { | ||
| baseType: nonNull[0], | ||
| nullable | ||
| }; | ||
| } | ||
| if (nonNull.length === 0) { | ||
| return { | ||
| baseType: "null", | ||
| nullable: false | ||
| }; | ||
| } | ||
| return { | ||
| baseType: nonNull[0], | ||
| nullable | ||
| }; | ||
| } | ||
| return null; | ||
| } | ||
| __name(normalizeTypeField, "normalizeTypeField"); | ||
| function toUnion(schemas, ctx) { | ||
| const members = schemas.map((s) => schemaToTypeNode(s, ctx)); | ||
| if (members.length === 1) return members[0]; | ||
| return { | ||
| kind: "union", | ||
| members | ||
| }; | ||
| } | ||
| __name(toUnion, "toUnion"); | ||
| function toDiscriminatedUnion(schemas, discriminator, ctx) { | ||
| const members = schemas.map((s) => schemaToTypeNode(s, ctx)); | ||
| if (members.length === 1) return members[0]; | ||
| return { | ||
| kind: "discriminatedUnion", | ||
| discriminator, | ||
| members | ||
| }; | ||
| } | ||
| __name(toDiscriminatedUnion, "toDiscriminatedUnion"); | ||
| function warnUnsupported(schema, ctx) { | ||
| if (schema.xml) ctx.warnings.warn(ctx.path, "xml metadata is not supported, skipping"); | ||
| if (schema.externalDocs) ctx.warnings.info(ctx.path, "externalDocs is not supported, skipping"); | ||
| if (schema.not) ctx.warnings.warn(ctx.path, "not keyword is not supported, skipping"); | ||
| } | ||
| __name(warnUnsupported, "warnUnsupported"); | ||
| function extractInlineModel(schema, suggestedName, ctx) { | ||
| if (schema.$ref) { | ||
| return { | ||
| typeNode: schemaToTypeNode(schema, ctx) | ||
| }; | ||
| } | ||
| if (schema.properties || schema.type === "object" && schema.additionalProperties === void 0) { | ||
| const fields = schemaPropertiesToFields(schema, ctx); | ||
| const model = { | ||
| kind: "model", | ||
| name: suggestedName, | ||
| fields, | ||
| description: ctx.includeComments ? schema.description : void 0, | ||
| loc: LOC | ||
| }; | ||
| return { | ||
| typeNode: { | ||
| kind: "ref", | ||
| name: suggestedName | ||
| }, | ||
| model | ||
| }; | ||
| } | ||
| return { | ||
| typeNode: schemaToTypeNode(schema, ctx) | ||
| }; | ||
| } | ||
| __name(extractInlineModel, "extractInlineModel"); | ||
| function sanitizeName(name, warnings) { | ||
| const cleaned = name.replace(/[^a-zA-Z0-9_$]/g, " ").split(/\s+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(""); | ||
| if (cleaned !== name) { | ||
| warnings.info(`#/components/schemas/${name}`, `Schema name sanitized: "${name}" \u2192 "${cleaned}"`); | ||
| } | ||
| return cleaned || "UnnamedSchema"; | ||
| } | ||
| __name(sanitizeName, "sanitizeName"); | ||
| // src/paths-to-ast.ts | ||
| var LOC2 = { | ||
| file: "", | ||
| line: 0 | ||
| }; | ||
| var HTTP_METHODS = [ | ||
| "get", | ||
| "post", | ||
| "put", | ||
| "patch", | ||
| "delete" | ||
| ]; | ||
| function pathsToRoutes(doc, ctx) { | ||
| const routes = []; | ||
| const routeTags = /* @__PURE__ */ new Map(); | ||
| const paths = doc.paths ?? {}; | ||
| for (const [path, pathItem] of Object.entries(paths)) { | ||
| if (!pathItem) continue; | ||
| const result = pathItemToRoute(path, pathItem, ctx); | ||
| if (result) { | ||
| routes.push(result.route); | ||
| routeTags.set(result.route, result.tag); | ||
| } | ||
| } | ||
| return { | ||
| routes, | ||
| routeTags | ||
| }; | ||
| } | ||
| __name(pathsToRoutes, "pathsToRoutes"); | ||
| function pathItemToRoute(path, pathItem, ctx) { | ||
| const operations = []; | ||
| let primaryTag = "default"; | ||
| const pathParams = (pathItem.parameters ?? []).filter((p) => p.in === "path"); | ||
| for (const method of HTTP_METHODS) { | ||
| const op = pathItem[method]; | ||
| if (!op) continue; | ||
| const opNode = operationToNode(method, op, path, ctx); | ||
| operations.push(opNode); | ||
| if (op.tags && op.tags.length > 0 && primaryTag === "default") { | ||
| primaryTag = op.tags[0]; | ||
| } | ||
| } | ||
| if (operations.length === 0) return null; | ||
| const params = buildPathParams(path, pathParams, pathItem, ctx); | ||
| const route = { | ||
| path, | ||
| operations, | ||
| loc: LOC2 | ||
| }; | ||
| if (params.length > 0) { | ||
| route.params = { | ||
| kind: "params", | ||
| nodes: params | ||
| }; | ||
| } | ||
| if (pathItem.description && ctx.includeComments) { | ||
| route.description = pathItem.description; | ||
| } | ||
| return { | ||
| route, | ||
| tag: primaryTag | ||
| }; | ||
| } | ||
| __name(pathItemToRoute, "pathItemToRoute"); | ||
| function operationToNode(method, op, path, ctx) { | ||
| const pathPrefix = `#/paths/${encodePathSegment2(path)}/${method}`; | ||
| const schemaCtx = makeSchemaCtx(ctx, pathPrefix); | ||
| const node = { | ||
| method, | ||
| responses: [], | ||
| loc: LOC2 | ||
| }; | ||
| if (op.operationId) { | ||
| node.sdk = op.operationId; | ||
| } | ||
| if (op.description && ctx.includeComments) { | ||
| node.description = op.description; | ||
| } | ||
| if (op.deprecated) { | ||
| node.modifiers = [ | ||
| "deprecated" | ||
| ]; | ||
| } | ||
| const queryParams = []; | ||
| const headerParams = []; | ||
| for (const param of op.parameters ?? []) { | ||
| if (param.in === "query") { | ||
| queryParams.push(parameterToNode(param, schemaCtx)); | ||
| } else if (param.in === "header") { | ||
| headerParams.push(parameterToNode(param, schemaCtx)); | ||
| } | ||
| } | ||
| if (queryParams.length > 0) { | ||
| node.query = { | ||
| kind: "params", | ||
| nodes: queryParams | ||
| }; | ||
| } | ||
| if (headerParams.length > 0) { | ||
| node.headers = { | ||
| kind: "params", | ||
| nodes: headerParams | ||
| }; | ||
| } | ||
| if (op.requestBody) { | ||
| node.request = requestBodyToNode(op.requestBody, op.operationId ?? `${method}${toPascalCase(path)}`, schemaCtx, ctx); | ||
| } | ||
| const responses = op.responses ?? {}; | ||
| for (const [code, resp] of Object.entries(responses)) { | ||
| const statusCode = parseInt(code, 10); | ||
| if (isNaN(statusCode)) continue; | ||
| const respNode = responseToNode(statusCode, resp, op.operationId ?? `${method}${toPascalCase(path)}`, schemaCtx, ctx); | ||
| node.responses.push(respNode); | ||
| } | ||
| if (op.security !== void 0) { | ||
| node.security = convertSecurity(op.security); | ||
| } | ||
| return node; | ||
| } | ||
| __name(operationToNode, "operationToNode"); | ||
| function buildPathParams(path, pathLevelParams, pathItem, ctx) { | ||
| const schemaCtx = makeSchemaCtx(ctx, `#/paths/${encodePathSegment2(path)}`); | ||
| const paramMap = /* @__PURE__ */ new Map(); | ||
| for (const p of pathLevelParams) { | ||
| paramMap.set(p.name, p); | ||
| } | ||
| for (const method of HTTP_METHODS) { | ||
| const op = pathItem[method]; | ||
| if (!op?.parameters) continue; | ||
| for (const p of op.parameters) { | ||
| if (p.in === "path" && !paramMap.has(p.name)) { | ||
| paramMap.set(p.name, p); | ||
| } | ||
| } | ||
| } | ||
| const templateNames = [ | ||
| ...path.matchAll(/\{([^}]+)\}/g) | ||
| ].map((m) => m[1]); | ||
| return templateNames.map((name) => { | ||
| const param = paramMap.get(name); | ||
| if (param) { | ||
| return parameterToNode(param, schemaCtx); | ||
| } | ||
| return { | ||
| name, | ||
| optional: false, | ||
| nullable: false, | ||
| type: { | ||
| kind: "scalar", | ||
| name: "string" | ||
| }, | ||
| loc: LOC2 | ||
| }; | ||
| }); | ||
| } | ||
| __name(buildPathParams, "buildPathParams"); | ||
| function parameterToNode(param, ctx) { | ||
| const type = param.schema ? schemaToTypeNode(param.schema, ctx) : { | ||
| kind: "scalar", | ||
| name: "string" | ||
| }; | ||
| return { | ||
| name: param.name, | ||
| optional: param.in !== "path" && !param.required, | ||
| nullable: false, | ||
| type, | ||
| description: ctx.includeComments ? param.description : void 0, | ||
| loc: LOC2 | ||
| }; | ||
| } | ||
| __name(parameterToNode, "parameterToNode"); | ||
| function requestBodyToNode(reqBody, operationName, schemaCtx, ctx) { | ||
| const content = reqBody.content; | ||
| if (!content) return void 0; | ||
| const supported = /* @__PURE__ */ new Set([ | ||
| "application/json", | ||
| "application/x-www-form-urlencoded", | ||
| "multipart/form-data" | ||
| ]); | ||
| const bodies = []; | ||
| for (const [contentType, mediaType] of Object.entries(content)) { | ||
| if (!supported.has(contentType) || !mediaType?.schema) continue; | ||
| const { typeNode, model } = extractInlineModel(mediaType.schema, `${toPascalCase(operationName)}Request`, schemaCtx); | ||
| if (model) { | ||
| ctx.extractedModels.push(model); | ||
| } | ||
| bodies.push({ | ||
| contentType, | ||
| bodyType: typeNode | ||
| }); | ||
| } | ||
| if (bodies.length === 0) return void 0; | ||
| return { | ||
| bodies | ||
| }; | ||
| } | ||
| __name(requestBodyToNode, "requestBodyToNode"); | ||
| function responseToNode(statusCode, resp, operationName, schemaCtx, ctx) { | ||
| const headers = convertResponseHeaders(resp.headers, schemaCtx); | ||
| if (!resp.content) { | ||
| return headers ? { | ||
| statusCode, | ||
| headers | ||
| } : { | ||
| statusCode | ||
| }; | ||
| } | ||
| const [contentType, mediaType] = Object.entries(resp.content)[0] ?? []; | ||
| if (!contentType || !mediaType?.schema) { | ||
| return headers ? { | ||
| statusCode, | ||
| headers | ||
| } : { | ||
| statusCode | ||
| }; | ||
| } | ||
| const { typeNode, model } = extractInlineModel(mediaType.schema, `${toPascalCase(operationName)}Response${statusCode}`, schemaCtx); | ||
| if (model) { | ||
| ctx.extractedModels.push(model); | ||
| } | ||
| return { | ||
| statusCode, | ||
| contentType, | ||
| bodyType: typeNode, | ||
| ...headers ? { | ||
| headers | ||
| } : {} | ||
| }; | ||
| } | ||
| __name(responseToNode, "responseToNode"); | ||
| function convertResponseHeaders(headers, schemaCtx) { | ||
| if (!headers) return void 0; | ||
| const out = []; | ||
| for (const [name, header] of Object.entries(headers)) { | ||
| if (!header) continue; | ||
| const type = header.schema ? schemaToTypeNode(header.schema, schemaCtx) : { | ||
| kind: "scalar", | ||
| name: "string" | ||
| }; | ||
| out.push({ | ||
| name, | ||
| optional: !header.required, | ||
| type, | ||
| description: schemaCtx.includeComments ? header.description : void 0 | ||
| }); | ||
| } | ||
| return out.length > 0 ? out : void 0; | ||
| } | ||
| __name(convertResponseHeaders, "convertResponseHeaders"); | ||
| function convertSecurity(security) { | ||
| if (security.length === 0) { | ||
| return "none"; | ||
| } | ||
| return { | ||
| loc: LOC2 | ||
| }; | ||
| } | ||
| __name(convertSecurity, "convertSecurity"); | ||
| function makeSchemaCtx(ctx, path) { | ||
| return { | ||
| circularRefs: ctx.circularRefs, | ||
| warnings: ctx.warnings, | ||
| path, | ||
| includeComments: ctx.includeComments, | ||
| namedSchemas: ctx.namedSchemas, | ||
| extractedModels: ctx.extractedModels, | ||
| inlineCounter: 0 | ||
| }; | ||
| } | ||
| __name(makeSchemaCtx, "makeSchemaCtx"); | ||
| function toPascalCase(input) { | ||
| return input.replace(/[^a-zA-Z0-9]/g, " ").split(/\s+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(""); | ||
| } | ||
| __name(toPascalCase, "toPascalCase"); | ||
| function encodePathSegment2(s) { | ||
| return s.replace(/~/g, "~0").replace(/\//g, "~1"); | ||
| } | ||
| __name(encodePathSegment2, "encodePathSegment"); | ||
| // src/tag-splitter.ts | ||
| function splitByTag(models, routes, routeTags) { | ||
| const routesByTag = /* @__PURE__ */ new Map(); | ||
| for (const route of routes) { | ||
| const tag = routeTags.get(route) ?? "default"; | ||
| const group = routesByTag.get(tag) ?? []; | ||
| group.push(route); | ||
| routesByTag.set(tag, group); | ||
| } | ||
| const modelsByTag = /* @__PURE__ */ new Map(); | ||
| for (const [tag, tagRoutes] of routesByTag) { | ||
| const refs = /* @__PURE__ */ new Set(); | ||
| for (const route of tagRoutes) { | ||
| collectRouteRefs(route, refs); | ||
| } | ||
| modelsByTag.set(tag, refs); | ||
| } | ||
| const modelNameToModel = new Map(models.map((m) => [ | ||
| m.name, | ||
| m | ||
| ])); | ||
| const modelAssignment = /* @__PURE__ */ new Map(); | ||
| for (const model of models) { | ||
| const tags = []; | ||
| for (const [tag, refs] of modelsByTag) { | ||
| if (refs.has(model.name)) { | ||
| tags.push(tag); | ||
| } | ||
| } | ||
| if (tags.length === 0) { | ||
| modelAssignment.set(model.name, "shared"); | ||
| } else if (tags.length === 1) { | ||
| modelAssignment.set(model.name, tags[0]); | ||
| } else { | ||
| modelAssignment.set(model.name, "shared"); | ||
| } | ||
| } | ||
| for (const model of models) { | ||
| if (modelAssignment.get(model.name) === "shared") { | ||
| const refs = /* @__PURE__ */ new Set(); | ||
| collectModelRefs(model, refs); | ||
| for (const ref of refs) { | ||
| if (modelNameToModel.has(ref)) { | ||
| const currentTag = modelAssignment.get(ref); | ||
| if (currentTag && currentTag !== "shared") { | ||
| const otherTags = [ | ||
| ...modelsByTag.entries() | ||
| ].filter(([t, r]) => t !== currentTag && r.has(ref)).map(([t]) => t); | ||
| if (otherTags.length > 0) { | ||
| modelAssignment.set(ref, "shared"); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| const result = /* @__PURE__ */ new Map(); | ||
| const allTags = /* @__PURE__ */ new Set([ | ||
| ...routesByTag.keys(), | ||
| ...new Set(modelAssignment.values()) | ||
| ]); | ||
| for (const tag of allTags) { | ||
| const tagModels = models.filter((m) => modelAssignment.get(m.name) === tag); | ||
| const tagRoutes = routesByTag.get(tag) ?? []; | ||
| if (tagModels.length === 0 && tagRoutes.length === 0) continue; | ||
| const filename = sanitizeFilename(tag); | ||
| result.set(`${filename}.ck`, { | ||
| kind: "ckRoot", | ||
| meta: tag !== "shared" ? { | ||
| area: tag | ||
| } : {}, | ||
| services: {}, | ||
| models: tagModels, | ||
| routes: tagRoutes, | ||
| file: `${filename}.ck` | ||
| }); | ||
| } | ||
| return result; | ||
| } | ||
| __name(splitByTag, "splitByTag"); | ||
| function mergeIntoSingle(models, routes, filename = "api") { | ||
| return { | ||
| kind: "ckRoot", | ||
| meta: {}, | ||
| services: {}, | ||
| models, | ||
| routes, | ||
| file: `${filename}.ck` | ||
| }; | ||
| } | ||
| __name(mergeIntoSingle, "mergeIntoSingle"); | ||
| function collectRouteRefs(route, refs) { | ||
| if (route.params) { | ||
| collectParamSourceRefs(route.params, refs); | ||
| } | ||
| for (const op of route.operations) { | ||
| if (op.query) collectParamSourceRefs(op.query, refs); | ||
| if (op.headers) collectParamSourceRefs(op.headers, refs); | ||
| if (op.request) { | ||
| for (const body of op.request.bodies) collectTypeRefs(body.bodyType, refs); | ||
| } | ||
| for (const resp of op.responses) { | ||
| if (resp.bodyType) collectTypeRefs(resp.bodyType, refs); | ||
| } | ||
| } | ||
| } | ||
| __name(collectRouteRefs, "collectRouteRefs"); | ||
| function collectParamSourceRefs(source, refs) { | ||
| if (typeof source === "string") { | ||
| refs.add(source); | ||
| return; | ||
| } | ||
| if (Array.isArray(source)) { | ||
| for (const param of source) { | ||
| if (param && typeof param === "object" && "type" in param) { | ||
| collectTypeRefs(param.type, refs); | ||
| } | ||
| } | ||
| return; | ||
| } | ||
| if (source && typeof source === "object" && "kind" in source) { | ||
| collectTypeRefs(source, refs); | ||
| } | ||
| } | ||
| __name(collectParamSourceRefs, "collectParamSourceRefs"); | ||
| function collectTypeRefs(type, refs) { | ||
| switch (type.kind) { | ||
| case "ref": | ||
| refs.add(type.name); | ||
| break; | ||
| case "array": | ||
| collectTypeRefs(type.item, refs); | ||
| break; | ||
| case "tuple": | ||
| for (const item of type.items) collectTypeRefs(item, refs); | ||
| break; | ||
| case "record": | ||
| collectTypeRefs(type.key, refs); | ||
| collectTypeRefs(type.value, refs); | ||
| break; | ||
| case "union": | ||
| case "discriminatedUnion": | ||
| case "intersection": | ||
| for (const member of type.members) collectTypeRefs(member, refs); | ||
| break; | ||
| case "inlineObject": | ||
| for (const field of type.fields) collectTypeRefs(field.type, refs); | ||
| break; | ||
| case "lazy": | ||
| collectTypeRefs(type.inner, refs); | ||
| break; | ||
| } | ||
| } | ||
| __name(collectTypeRefs, "collectTypeRefs"); | ||
| function collectModelRefs(model, refs) { | ||
| if (model.bases) for (const b of model.bases) refs.add(b); | ||
| if (model.type) collectTypeRefs(model.type, refs); | ||
| for (const field of model.fields) { | ||
| collectTypeRefs(field.type, refs); | ||
| } | ||
| } | ||
| __name(collectModelRefs, "collectModelRefs"); | ||
| function sanitizeFilename(tag) { | ||
| return tag.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "") || "default"; | ||
| } | ||
| __name(sanitizeFilename, "sanitizeFilename"); | ||
| // src/ast-to-ck.ts | ||
| var INDENT = " "; | ||
| function astToCk(root, options = {}) { | ||
| const { includeComments = true } = options; | ||
| const ctx = { | ||
| includeComments | ||
| }; | ||
| const parts = []; | ||
| const optionsBlock = serializeOptions(root); | ||
| if (optionsBlock) parts.push(optionsBlock); | ||
| for (const model of root.models) { | ||
| parts.push(serializeModel(model, ctx)); | ||
| } | ||
| for (const route of root.routes) { | ||
| parts.push(serializeRoute(route, ctx)); | ||
| } | ||
| return parts.join("\n\n") + "\n"; | ||
| } | ||
| __name(astToCk, "astToCk"); | ||
| function serializeOptions(root) { | ||
| const hasKeys = Object.keys(root.meta).length > 0; | ||
| const hasServices = root.services && Object.keys(root.services).length > 0; | ||
| const hasSecurity = root.security !== void 0; | ||
| if (!hasKeys && !hasServices && !hasSecurity) return null; | ||
| const lines = [ | ||
| "options {" | ||
| ]; | ||
| if (hasKeys) { | ||
| lines.push(`${INDENT}keys: {`); | ||
| for (const [key, value] of Object.entries(root.meta)) { | ||
| lines.push(`${INDENT}${INDENT}${key}: ${value}`); | ||
| } | ||
| lines.push(`${INDENT}}`); | ||
| } | ||
| if (hasServices) { | ||
| lines.push(`${INDENT}services: {`); | ||
| for (const [name, path] of Object.entries(root.services)) { | ||
| lines.push(`${INDENT}${INDENT}${name}: "${path}"`); | ||
| } | ||
| lines.push(`${INDENT}}`); | ||
| } | ||
| if (hasSecurity) { | ||
| lines.push(`${INDENT}security: {`); | ||
| if (root.security === "none") { | ||
| lines.push(`${INDENT}${INDENT}none`); | ||
| } else { | ||
| const sec = root.security; | ||
| if (sec.policy !== void 0) { | ||
| const value = sec.policy === false ? "none" : sec.policy; | ||
| lines.push(`${INDENT}${INDENT}policy: ${value}`); | ||
| } | ||
| } | ||
| lines.push(`${INDENT}}`); | ||
| } | ||
| lines.push("}"); | ||
| return lines.join("\n"); | ||
| } | ||
| __name(serializeOptions, "serializeOptions"); | ||
| function serializeModel(model, ctx) { | ||
| const parts = []; | ||
| const prefixes = []; | ||
| if (model.inputCase && model.inputCase !== "camel") { | ||
| prefixes.push(`format(input=${model.inputCase})`); | ||
| } | ||
| if (model.mode && model.mode !== "strict") { | ||
| prefixes.push(`mode(${model.mode})`); | ||
| } | ||
| if (model.deprecated) { | ||
| prefixes.push("deprecated"); | ||
| } | ||
| const prefix = prefixes.length > 0 ? prefixes.join(" ") + " " : ""; | ||
| const comment = ctx.includeComments && model.description ? ` # ${model.description}` : ""; | ||
| if (model.type) { | ||
| parts.push(`contract ${prefix}${model.name}: ${serializeType(model.type)}${comment}`); | ||
| return parts.join(""); | ||
| } | ||
| if (model.bases && model.bases.length > 0) { | ||
| parts.push(`contract ${prefix}${model.name}: ${model.bases.join(" & ")} & {${comment}`); | ||
| } else { | ||
| parts.push(`contract ${prefix}${model.name}: {${comment}`); | ||
| } | ||
| for (const field of model.fields) { | ||
| parts.push(serializeField(field, 1, ctx)); | ||
| } | ||
| parts.push("}"); | ||
| return parts.join("\n"); | ||
| } | ||
| __name(serializeModel, "serializeModel"); | ||
| function serializeField(field, depth, ctx) { | ||
| const indent = INDENT.repeat(depth); | ||
| const optional = field.optional ? "?" : ""; | ||
| const visibility = field.visibility !== "normal" ? `${field.visibility} ` : ""; | ||
| const deprecated = field.deprecated ? "deprecated " : ""; | ||
| let typeStr = serializeType(field.type); | ||
| if (field.nullable && !typeContainsNull(field.type)) { | ||
| typeStr = `${typeStr} | null`; | ||
| } | ||
| const defaultVal = field.default !== void 0 ? ` = ${serializeDefault(field.default)}` : ""; | ||
| const comment = ctx.includeComments && field.description ? ` # ${field.description}` : ""; | ||
| return `${indent}${field.name}${optional}: ${deprecated}${visibility}${typeStr}${defaultVal}${comment}`; | ||
| } | ||
| __name(serializeField, "serializeField"); | ||
| function typeContainsNull(type) { | ||
| if (type.kind === "scalar" && type.name === "null") return true; | ||
| if (type.kind === "union") return type.members.some(typeContainsNull); | ||
| return false; | ||
| } | ||
| __name(typeContainsNull, "typeContainsNull"); | ||
| function serializeDefault(value) { | ||
| if (typeof value === "string") { | ||
| if (/^[a-zA-Z_$][a-zA-Z0-9_$\-.]*$/.test(value)) return value; | ||
| return `"${value}"`; | ||
| } | ||
| return String(value); | ||
| } | ||
| __name(serializeDefault, "serializeDefault"); | ||
| function serializeType(type) { | ||
| switch (type.kind) { | ||
| case "scalar": | ||
| return serializeScalar(type); | ||
| case "array": | ||
| return serializeArray(type); | ||
| case "tuple": | ||
| return `tuple(${type.items.map(serializeType).join(", ")})`; | ||
| case "record": | ||
| return `record(${serializeType(type.key)}, ${serializeType(type.value)})`; | ||
| case "enum": | ||
| return `enum(${type.values.join(", ")})`; | ||
| case "literal": | ||
| return serializeLiteral(type); | ||
| case "union": | ||
| return type.members.map(serializeType).join(" | "); | ||
| case "discriminatedUnion": | ||
| return `discriminated(by=${type.discriminator}, ${type.members.map(serializeType).join(" | ")})`; | ||
| case "intersection": | ||
| return type.members.map(serializeType).join(" & "); | ||
| case "ref": | ||
| return type.name; | ||
| case "inlineObject": | ||
| return serializeInlineObject(type); | ||
| case "lazy": | ||
| return `lazy(${serializeType(type.inner)})`; | ||
| } | ||
| } | ||
| __name(serializeType, "serializeType"); | ||
| function serializeScalar(type) { | ||
| const args = []; | ||
| if (type.len !== void 0) args.push(`length=${type.len}`); | ||
| if (type.min !== void 0) args.push(typeof type.min === "string" ? `min="${type.min}"` : `min=${type.min}`); | ||
| if (type.max !== void 0) args.push(typeof type.max === "string" ? `max="${type.max}"` : `max=${type.max}`); | ||
| if (type.regex !== void 0) args.push(`regex=${type.regex}`); | ||
| if (type.format !== void 0) args.push(`format=${type.format}`); | ||
| if (args.length === 0) return type.name; | ||
| return `${type.name}(${args.join(", ")})`; | ||
| } | ||
| __name(serializeScalar, "serializeScalar"); | ||
| function serializeArray(type) { | ||
| const args = [ | ||
| serializeType(type.item) | ||
| ]; | ||
| if (type.min !== void 0) args.push(`min=${type.min}`); | ||
| if (type.max !== void 0) args.push(`max=${type.max}`); | ||
| return `array(${args.join(", ")})`; | ||
| } | ||
| __name(serializeArray, "serializeArray"); | ||
| function serializeLiteral(type) { | ||
| if (typeof type.value === "string") return `literal("${type.value}")`; | ||
| return `literal(${type.value})`; | ||
| } | ||
| __name(serializeLiteral, "serializeLiteral"); | ||
| function serializeInlineObject(type) { | ||
| const modePrefix = type.mode ? `mode(${type.mode}) ` : ""; | ||
| if (type.fields.length === 0) return `${modePrefix}{}`; | ||
| const lines = [ | ||
| `${modePrefix}{` | ||
| ]; | ||
| for (const field of type.fields) { | ||
| lines.push(serializeField(field, 2, { | ||
| includeComments: true | ||
| })); | ||
| } | ||
| lines.push(`${INDENT}}`); | ||
| return lines.join("\n"); | ||
| } | ||
| __name(serializeInlineObject, "serializeInlineObject"); | ||
| function serializeRoute(route, ctx) { | ||
| const lines = []; | ||
| const modStr = serializeModifiers(route.modifiers); | ||
| const comment = ctx.includeComments && route.description ? ` # ${route.description}` : ""; | ||
| lines.push(`operation${modStr} ${route.path}: {${comment}`); | ||
| if (route.params) { | ||
| serializeParamSource(lines, "params", route.params, route.paramsMode, 1, ctx); | ||
| } | ||
| if (route.security !== void 0) { | ||
| serializeSecurityBlock(lines, route.security, 1, ctx); | ||
| } | ||
| for (const op of route.operations) { | ||
| serializeOperation(lines, op, 1, ctx); | ||
| } | ||
| lines.push("}"); | ||
| return lines.join("\n"); | ||
| } | ||
| __name(serializeRoute, "serializeRoute"); | ||
| function serializeOperation(lines, op, depth, ctx) { | ||
| const indent = INDENT.repeat(depth); | ||
| const modStr = serializeModifiers(op.modifiers); | ||
| const comment = ctx.includeComments && op.description ? ` # ${op.description}` : ""; | ||
| lines.push(`${indent}${op.method}${modStr}: {${comment}`); | ||
| const inner = INDENT.repeat(depth + 1); | ||
| if (op.service) { | ||
| lines.push(`${inner}service: ${op.service}`); | ||
| } | ||
| if (op.sdk) { | ||
| lines.push(`${inner}sdk: ${op.sdk}`); | ||
| } | ||
| if (op.signature) { | ||
| const sigComment = ctx.includeComments && op.signatureDescription ? ` # ${op.signatureDescription}` : ""; | ||
| lines.push(`${inner}signature: ${op.signature}${sigComment}`); | ||
| } | ||
| if (op.security !== void 0) { | ||
| serializeSecurityBlock(lines, op.security, depth + 1, ctx); | ||
| } | ||
| if (op.query) { | ||
| serializeParamSource(lines, "query", op.query, op.queryMode, depth + 1, ctx); | ||
| } | ||
| if (op.headers) { | ||
| serializeParamSource(lines, "headers", op.headers, op.headersMode, depth + 1, ctx); | ||
| } | ||
| if (op.request) { | ||
| serializeRequest(lines, op.request, depth + 1); | ||
| } | ||
| if (op.responses.length > 0) { | ||
| serializeResponses(lines, op.responses, depth + 1); | ||
| } | ||
| lines.push(`${indent}}`); | ||
| return lines; | ||
| } | ||
| __name(serializeOperation, "serializeOperation"); | ||
| function serializeModifiers(modifiers) { | ||
| if (!modifiers || modifiers.length === 0) return ""; | ||
| return `(${modifiers.join(", ")})`; | ||
| } | ||
| __name(serializeModifiers, "serializeModifiers"); | ||
| function serializeParamSource(lines, keyword, source, mode, depth, ctx) { | ||
| const indent = INDENT.repeat(depth); | ||
| if (source.kind === "ref") { | ||
| lines.push(`${indent}${keyword}: ${source.name}`); | ||
| return; | ||
| } | ||
| if (source.kind === "type") { | ||
| lines.push(`${indent}${keyword}: ${serializeType(source.node)}`); | ||
| return; | ||
| } | ||
| const modeStr = mode ? `mode(${mode}) ` : ""; | ||
| lines.push(`${indent}${keyword}: ${modeStr}{`); | ||
| for (const param of source.nodes) { | ||
| const optional = param.optional ? "?" : ""; | ||
| let typeStr = serializeType(param.type); | ||
| if (param.nullable && !typeContainsNull(param.type)) { | ||
| typeStr = `${typeStr} | null`; | ||
| } | ||
| const defaultVal = param.default !== void 0 ? ` = ${serializeDefault(param.default)}` : ""; | ||
| const comment = ctx.includeComments && param.description ? ` # ${param.description}` : ""; | ||
| lines.push(`${INDENT.repeat(depth + 1)}${param.name}${optional}: ${typeStr}${defaultVal}${comment}`); | ||
| } | ||
| lines.push(`${indent}}`); | ||
| } | ||
| __name(serializeParamSource, "serializeParamSource"); | ||
| function serializeRequest(lines, request, depth) { | ||
| const indent = INDENT.repeat(depth); | ||
| lines.push(`${indent}request: {`); | ||
| for (const body of request.bodies) { | ||
| lines.push(`${INDENT.repeat(depth + 1)}${body.contentType}: ${serializeType(body.bodyType)}`); | ||
| } | ||
| lines.push(`${indent}}`); | ||
| } | ||
| __name(serializeRequest, "serializeRequest"); | ||
| function serializeResponses(lines, responses, depth) { | ||
| const indent = INDENT.repeat(depth); | ||
| lines.push(`${indent}response: {`); | ||
| for (const resp of responses) { | ||
| const hasBody = resp.bodyType && resp.contentType; | ||
| const hasHeaders = resp.headers && resp.headers.length > 0; | ||
| if (hasBody || hasHeaders) { | ||
| lines.push(`${INDENT.repeat(depth + 1)}${resp.statusCode}: {`); | ||
| if (hasBody) { | ||
| lines.push(`${INDENT.repeat(depth + 2)}${resp.contentType}: ${serializeType(resp.bodyType)}`); | ||
| } | ||
| if (hasHeaders) { | ||
| lines.push(`${INDENT.repeat(depth + 2)}headers: {`); | ||
| for (const h of resp.headers) { | ||
| const opt = h.optional ? "?" : ""; | ||
| const trail = h.description ? ` # ${h.description}` : ""; | ||
| lines.push(`${INDENT.repeat(depth + 3)}${h.name}${opt}: ${serializeType(h.type)}${trail}`); | ||
| } | ||
| lines.push(`${INDENT.repeat(depth + 2)}}`); | ||
| } | ||
| lines.push(`${INDENT.repeat(depth + 1)}}`); | ||
| } else { | ||
| lines.push(`${INDENT.repeat(depth + 1)}${resp.statusCode}:`); | ||
| } | ||
| } | ||
| lines.push(`${indent}}`); | ||
| } | ||
| __name(serializeResponses, "serializeResponses"); | ||
| function serializeSecurityBlock(lines, security, depth, ctx) { | ||
| const indent = INDENT.repeat(depth); | ||
| if (security === "none") { | ||
| lines.push(`${indent}security: none`); | ||
| return; | ||
| } | ||
| const sec = security; | ||
| if (sec.policy !== void 0) { | ||
| const comment = ctx.includeComments && sec.policyDescription ? ` # ${sec.policyDescription}` : ""; | ||
| const value = sec.policy === false ? "none" : sec.policy; | ||
| lines.push(`${indent}security: {`); | ||
| lines.push(`${INDENT.repeat(depth + 1)}policy: ${value}${comment}`); | ||
| lines.push(`${indent}}`); | ||
| } else { | ||
| lines.push(`${indent}security: {}`); | ||
| } | ||
| } | ||
| __name(serializeSecurityBlock, "serializeSecurityBlock"); | ||
| // src/convert.ts | ||
| import { readFileSync } from "fs"; | ||
| import { parse as parseYaml } from "yaml"; | ||
| // src/warnings.ts | ||
| var WarningCollector = class { | ||
| static { | ||
| __name(this, "WarningCollector"); | ||
| } | ||
| warnings = []; | ||
| onWarning; | ||
| constructor(onWarning) { | ||
| this.onWarning = onWarning; | ||
| } | ||
| warn(path, message) { | ||
| this.add({ | ||
| path, | ||
| message, | ||
| severity: "warn" | ||
| }); | ||
| } | ||
| info(path, message) { | ||
| this.add({ | ||
| path, | ||
| message, | ||
| severity: "info" | ||
| }); | ||
| } | ||
| add(warning) { | ||
| this.warnings.push(warning); | ||
| this.onWarning?.(warning); | ||
| } | ||
| }; | ||
| // src/convert.ts | ||
| async function convertOpenApiToCk(options) { | ||
| const { split = "by-tag", includeComments = true } = options; | ||
| const warnings = new WarningCollector(options.onWarning); | ||
| const rawDoc = await parseInput(options.input); | ||
| const doc = normalize(rawDoc, warnings); | ||
| const schemas = sanitizeSchemaNames(doc, warnings); | ||
| const circularRefs = detectCircularRefs(schemas); | ||
| const extractedModels = []; | ||
| const schemaCtx = { | ||
| circularRefs, | ||
| warnings, | ||
| path: "#/components/schemas", | ||
| includeComments, | ||
| namedSchemas: schemas, | ||
| extractedModels, | ||
| inlineCounter: 0 | ||
| }; | ||
| const models = schemasToModels(schemas, schemaCtx); | ||
| const { routes, routeTags } = pathsToRoutes(doc, { | ||
| circularRefs, | ||
| warnings, | ||
| includeComments, | ||
| namedSchemas: schemas, | ||
| extractedModels, | ||
| globalSecurity: doc.security | ||
| }); | ||
| const files = /* @__PURE__ */ new Map(); | ||
| if (split === "by-tag") { | ||
| const ckRoots = splitByTag(models, routes, routeTags); | ||
| for (const [filename, root] of ckRoots) { | ||
| files.set(filename, astToCk(root, { | ||
| includeComments | ||
| })); | ||
| } | ||
| } else { | ||
| const root = mergeIntoSingle(models, routes); | ||
| files.set("api.ck", astToCk(root, { | ||
| includeComments | ||
| })); | ||
| } | ||
| return { | ||
| files, | ||
| warnings: warnings.warnings | ||
| }; | ||
| } | ||
| __name(convertOpenApiToCk, "convertOpenApiToCk"); | ||
| async function parseInput(input) { | ||
| if (typeof input === "object") { | ||
| return input; | ||
| } | ||
| try { | ||
| const content = readFileSync(input, "utf-8"); | ||
| return parseJsonOrYaml(content); | ||
| } catch { | ||
| return parseJsonOrYaml(input); | ||
| } | ||
| } | ||
| __name(parseInput, "parseInput"); | ||
| function parseJsonOrYaml(content) { | ||
| try { | ||
| return JSON.parse(content); | ||
| } catch { | ||
| return parseYaml(content); | ||
| } | ||
| } | ||
| __name(parseJsonOrYaml, "parseJsonOrYaml"); | ||
| function sanitizeSchemaNames(doc, warnings) { | ||
| const original = doc.components?.schemas ?? {}; | ||
| const sanitized = {}; | ||
| const nameMap = /* @__PURE__ */ new Map(); | ||
| for (const name of Object.keys(original)) { | ||
| const clean = sanitizeName(name, warnings); | ||
| if (sanitized[clean]) { | ||
| warnings.warn(`#/components/schemas/${name}`, `Name collision after sanitization: "${name}" and another schema both map to "${clean}"`); | ||
| let i = 2; | ||
| while (sanitized[`${clean}${i}`]) i++; | ||
| nameMap.set(name, `${clean}${i}`); | ||
| sanitized[`${clean}${i}`] = original[name]; | ||
| } else { | ||
| nameMap.set(name, clean); | ||
| sanitized[clean] = original[name]; | ||
| } | ||
| } | ||
| if (nameMap.size > 0) { | ||
| updateRefs(doc, nameMap); | ||
| } | ||
| return sanitized; | ||
| } | ||
| __name(sanitizeSchemaNames, "sanitizeSchemaNames"); | ||
| function updateRefs(obj, nameMap) { | ||
| if (!obj || typeof obj !== "object") return; | ||
| if (Array.isArray(obj)) { | ||
| for (const item of obj) updateRefs(item, nameMap); | ||
| return; | ||
| } | ||
| const record = obj; | ||
| if (typeof record.$ref === "string") { | ||
| const match = record.$ref.match(/^#\/components\/schemas\/(.+)$/); | ||
| if (match?.[1] && nameMap.has(match[1])) { | ||
| record.$ref = `#/components/schemas/${nameMap.get(match[1])}`; | ||
| } | ||
| } | ||
| for (const value of Object.values(record)) { | ||
| updateRefs(value, nameMap); | ||
| } | ||
| } | ||
| __name(updateRefs, "updateRefs"); | ||
| export { | ||
| __name, | ||
| normalize, | ||
| detectCircularRefs, | ||
| extractRefName, | ||
| schemasToModels, | ||
| schemaToTypeNode, | ||
| sanitizeName, | ||
| pathsToRoutes, | ||
| splitByTag, | ||
| mergeIntoSingle, | ||
| astToCk, | ||
| serializeType, | ||
| convertOpenApiToCk | ||
| }; | ||
| //# sourceMappingURL=chunk-YOTHJ2V4.js.map |
Sorry, the diff of this file is too big to display
| > @contractkit/openapi-to-ck@0.7.8 build:ci /home/runner/work/ContractKit/ContractKit/packages/openapi-to-ck | ||
| > @contractkit/openapi-to-ck@0.8.0 build:ci /home/runner/work/ContractKit/ContractKit/packages/openapi-to-ck | ||
| > eslint --max-warnings=0 && pnpm run build | ||
| > @contractkit/openapi-to-ck@0.7.8 build /home/runner/work/ContractKit/ContractKit/packages/openapi-to-ck | ||
| > @contractkit/openapi-to-ck@0.8.0 build /home/runner/work/ContractKit/ContractKit/packages/openapi-to-ck | ||
| > tsup src/index.ts src/plugin.ts --format esm --sourcemap --dts && tsc --emitDeclarationOnly --declaration | ||
@@ -16,10 +16,10 @@ | ||
| [32mESM[39m [1mdist/plugin.js [22m[32m2.51 KB[39m | ||
| [32mESM[39m [1mdist/chunk-T2G2WRZ2.js [22m[32m50.36 KB[39m | ||
| [32mESM[39m [1mdist/chunk-YOTHJ2V4.js [22m[32m50.45 KB[39m | ||
| [32mESM[39m [1mdist/index.js.map [22m[32m71.00 B[39m | ||
| [32mESM[39m [1mdist/plugin.js.map [22m[32m4.95 KB[39m | ||
| [32mESM[39m [1mdist/chunk-T2G2WRZ2.js.map [22m[32m116.56 KB[39m | ||
| [32mESM[39m ⚡️ Build success in 191ms | ||
| [32mESM[39m [1mdist/chunk-YOTHJ2V4.js.map [22m[32m116.74 KB[39m | ||
| [32mESM[39m ⚡️ Build success in 248ms | ||
| [34mDTS[39m Build start | ||
| [32mDTS[39m ⚡️ Build success in 6387ms | ||
| [32mDTS[39m ⚡️ Build success in 7869ms | ||
| [32mDTS[39m [1mdist/index.d.ts [22m[32m8.29 KB[39m | ||
| [32mDTS[39m [1mdist/plugin.d.ts [22m[32m128.00 B[39m |
| > @contractkit/openapi-to-ck@0.7.8 test:ci /home/runner/work/ContractKit/ContractKit/packages/openapi-to-ck | ||
| > @contractkit/openapi-to-ck@0.8.0 test:ci /home/runner/work/ContractKit/ContractKit/packages/openapi-to-ck | ||
| > vitest run --coverage | ||
@@ -9,13 +9,13 @@ | ||
| [32m✓[39m tests/ast-to-ck.test.ts [2m([22m[2m35 tests[22m[2m)[22m[32m 79[2mms[22m[39m | ||
| [32m✓[39m tests/schema-to-ast.test.ts [2m([22m[2m36 tests[22m[2m)[22m[32m 44[2mms[22m[39m | ||
| [32m✓[39m tests/normalize.test.ts [2m([22m[2m6 tests[22m[2m)[22m[32m 91[2mms[22m[39m | ||
| [32m✓[39m tests/tag-splitter.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m 94[2mms[22m[39m | ||
| [32m✓[39m tests/circular-refs.test.ts [2m([22m[2m8 tests[22m[2m)[22m[32m 24[2mms[22m[39m | ||
| [32m✓[39m tests/convert.test.ts [2m([22m[2m16 tests[22m[2m)[22m[32m 142[2mms[22m[39m | ||
| [32m✓[39m tests/ast-to-ck.test.ts [2m([22m[2m35 tests[22m[2m)[22m[32m 61[2mms[22m[39m | ||
| [32m✓[39m tests/schema-to-ast.test.ts [2m([22m[2m36 tests[22m[2m)[22m[32m 47[2mms[22m[39m | ||
| [32m✓[39m tests/normalize.test.ts [2m([22m[2m6 tests[22m[2m)[22m[32m 20[2mms[22m[39m | ||
| [32m✓[39m tests/tag-splitter.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m 79[2mms[22m[39m | ||
| [32m✓[39m tests/convert.test.ts [2m([22m[2m16 tests[22m[2m)[22m[32m 196[2mms[22m[39m | ||
| [32m✓[39m tests/circular-refs.test.ts [2m([22m[2m8 tests[22m[2m)[22m[32m 42[2mms[22m[39m | ||
| [2m Test Files [22m [1m[32m6 passed[39m[22m[90m (6)[39m | ||
| [2m Tests [22m [1m[32m106 passed[39m[22m[90m (106)[39m | ||
| [2m Start at [22m 13:02:53 | ||
| [2m Duration [22m 4.97s[2m (transform 2.41s, setup 0ms, import 5.49s, tests 474ms, environment 1ms)[22m | ||
| [2m Start at [22m 17:08:46 | ||
| [2m Duration [22m 4.23s[2m (transform 2.17s, setup 0ms, import 4.69s, tests 444ms, environment 1ms)[22m | ||
@@ -26,5 +26,5 @@ [34m % [39m[2mCoverage report from [22m[33mv8[39m | ||
| -------------------|---------|----------|---------|---------|------------------------------------------------------------- | ||
| All files | 81.01 | 70.41 | 94.69 | 83.55 | | ||
| src | 80.79 | 70.7 | 93.47 | 83.18 | | ||
| ast-to-ck.ts | 90.65 | 82.7 | 100 | 91.91 | 82-91,168,263,295-296,311,344-345,355,415 | ||
| All files | 80.95 | 70.17 | 94.69 | 83.47 | | ||
| src | 80.72 | 70.45 | 93.47 | 83.1 | | ||
| ast-to-ck.ts | 90.27 | 81.48 | 100 | 91.5 | 82-92,169,264,296-297,312,345-346,356,417 | ||
| circular-refs.ts | 100 | 90 | 100 | 100 | 20-23 | ||
@@ -31,0 +31,0 @@ convert.ts | 77.19 | 87.5 | 80 | 77.77 | 83-98,112-117 |
+42
-0
| # @contractkit/openapi-to-ck | ||
| ## 0.8.0 | ||
| ### Minor Changes | ||
| - dd8197b: **Breaking:** Replace the `requireMfa: boolean` field in `security: { ... }` blocks with `policy: <ident|none>`, and switch the generated Koa router middleware from `requireSecurity` to ServerKit's new `requirePolicy`. | ||
| The `security` declaration on operations, routes, and the file-level `options { security: { ... } }` block no longer accepts a `requireMfa:` line. The new field is `policy:` and takes a bare identifier (the named policy) or the keyword `none` to explicitly bypass policy enforcement. Existing `.ck` files that use `requireMfa:` will fail to parse. | ||
| ```ck | ||
| # Before | ||
| security: { | ||
| requireMfa: true | ||
| } | ||
| # After | ||
| security: { | ||
| policy: paymentsWrite | ||
| } | ||
| # Explicit bypass | ||
| security: { | ||
| policy: none | ||
| } | ||
| ``` | ||
| **`@contractkit/core`** — `SecurityFields` interface drops `requireMfa` / `requireMfaDescription` and adds `policy?: string | false` / `policyDescription?: string`. The grammar's `SecurityRequireMfaLine` is replaced by `SecurityPolicyLine` (`policyKw ":" (noneKw | identifier)`). `security: none` (the route-level public sentinel) is unchanged. | ||
| **`@contractkit/plugin-typescript`** — Generated Koa routers now import `requirePolicy` from `@maroonedsoftware/koa` (previously `requireSecurity`) and emit `requirePolicy({ policy: 'name' })`, `requirePolicy({ policy: false })`, or bare `requirePolicy()`. Consumers must upgrade ServerKit alongside. | ||
| **`@contractkit/prettier-plugin`** — Formats `policy: <name>` and `policy: none` lines inside security blocks. Files containing `requireMfa:` will no longer round-trip and will surface as parse errors. | ||
| **`@contractkit/plugin-markdown`** — The "Security: authenticated" admonition now shows `policy: <name|none>` instead of `requireMfa: <bool>`. | ||
| **`@contractkit/openapi-to-ck`** — Non-empty OpenAPI `security` requirements continue to collapse to an empty `security: {}` (authenticated, default policy); the serializer now emits `policy:` lines when the field is set. | ||
| **`contractkit-vscode-extension`** — TextMate grammar highlights `policy:` inside the security block; LSP completion offers `policy` instead of `requireMfa`. Re-run `pnpm run vscode:install` to pick up the change. | ||
| ### Patch Changes | ||
| - Updated dependencies [dd8197b] | ||
| - @contractkit/core@0.18.0 | ||
| ## 0.7.8 | ||
@@ -4,0 +46,0 @@ |
+148
-146
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <coverage generated="1778245378830" clover="3.2.0"> | ||
| <project timestamp="1778245378831" name="All files"> | ||
| <metrics statements="833" coveredstatements="696" conditionals="747" coveredconditionals="526" methods="113" coveredmethods="107" elements="1693" coveredelements="1329" complexity="0" loc="833" ncloc="833" packages="2" files="9" classes="9"/> | ||
| <coverage generated="1778692131019" clover="3.2.0"> | ||
| <project timestamp="1778692131020" name="All files"> | ||
| <metrics statements="835" coveredstatements="697" conditionals="751" coveredconditionals="527" methods="113" coveredmethods="107" elements="1699" coveredelements="1331" complexity="0" loc="835" ncloc="835" packages="2" files="9" classes="9"/> | ||
| <package name="src"> | ||
| <metrics statements="803" coveredstatements="668" conditionals="727" coveredconditionals="514" methods="92" coveredmethods="86"/> | ||
| <metrics statements="805" coveredstatements="669" conditionals="731" coveredconditionals="515" methods="92" coveredmethods="86"/> | ||
| <file name="ast-to-ck.ts" path="/home/runner/work/ContractKit/ContractKit/packages/openapi-to-ck/src/ast-to-ck.ts"> | ||
| <metrics statements="198" coveredstatements="182" conditionals="185" coveredconditionals="153" methods="18" coveredmethods="18"/> | ||
| <metrics statements="200" coveredstatements="183" conditionals="189" coveredconditionals="154" methods="18" coveredmethods="18"/> | ||
| <line num="17" count="2" type="stmt"/> | ||
@@ -41,168 +41,170 @@ <line num="27" count="47" type="cond" truecount="1" falsecount="0"/> | ||
| <line num="87" count="0" type="cond" truecount="0" falsecount="2"/> | ||
| <line num="88" count="0" type="stmt"/> | ||
| <line num="91" count="0" type="stmt"/> | ||
| <line num="94" count="11" type="stmt"/> | ||
| <line num="88" count="0" type="cond" truecount="0" falsecount="2"/> | ||
| <line num="89" count="0" type="stmt"/> | ||
| <line num="92" count="0" type="stmt"/> | ||
| <line num="95" count="11" type="stmt"/> | ||
| <line num="101" count="46" type="stmt"/> | ||
| <line num="104" count="46" type="stmt"/> | ||
| <line num="105" count="46" type="cond" truecount="4" falsecount="0"/> | ||
| <line num="106" count="1" type="stmt"/> | ||
| <line num="108" count="46" type="cond" truecount="4" falsecount="0"/> | ||
| <line num="109" count="1" type="stmt"/> | ||
| <line num="111" count="46" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="112" count="1" type="stmt"/> | ||
| <line num="115" count="46" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="116" count="46" type="cond" truecount="4" falsecount="0"/> | ||
| <line num="119" count="46" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="120" count="2" type="stmt"/> | ||
| <line num="96" count="11" type="stmt"/> | ||
| <line num="102" count="46" type="stmt"/> | ||
| <line num="105" count="46" type="stmt"/> | ||
| <line num="106" count="46" type="cond" truecount="4" falsecount="0"/> | ||
| <line num="107" count="1" type="stmt"/> | ||
| <line num="109" count="46" type="cond" truecount="4" falsecount="0"/> | ||
| <line num="110" count="1" type="stmt"/> | ||
| <line num="112" count="46" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="113" count="1" type="stmt"/> | ||
| <line num="116" count="46" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="117" count="46" type="cond" truecount="4" falsecount="0"/> | ||
| <line num="120" count="46" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="121" count="2" type="stmt"/> | ||
| <line num="125" count="44" type="cond" truecount="4" falsecount="0"/> | ||
| <line num="126" count="1" type="stmt"/> | ||
| <line num="128" count="43" type="stmt"/> | ||
| <line num="131" count="44" type="stmt"/> | ||
| <line num="132" count="122" type="stmt"/> | ||
| <line num="135" count="44" type="stmt"/> | ||
| <line num="122" count="2" type="stmt"/> | ||
| <line num="126" count="44" type="cond" truecount="4" falsecount="0"/> | ||
| <line num="127" count="1" type="stmt"/> | ||
| <line num="129" count="43" type="stmt"/> | ||
| <line num="132" count="44" type="stmt"/> | ||
| <line num="133" count="122" type="stmt"/> | ||
| <line num="136" count="44" type="stmt"/> | ||
| <line num="140" count="124" type="stmt"/> | ||
| <line num="141" count="124" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="137" count="44" type="stmt"/> | ||
| <line num="141" count="124" type="stmt"/> | ||
| <line num="142" count="124" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="143" count="124" type="cond" truecount="1" falsecount="1"/> | ||
| <line num="145" count="124" type="stmt"/> | ||
| <line num="148" count="124" type="cond" truecount="4" falsecount="0"/> | ||
| <line num="149" count="1" type="stmt"/> | ||
| <line num="152" count="124" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="153" count="124" type="cond" truecount="4" falsecount="0"/> | ||
| <line num="155" count="124" type="stmt"/> | ||
| <line num="159" count="1" type="cond" truecount="3" falsecount="1"/> | ||
| <line num="160" count="1" type="cond" truecount="1" falsecount="1"/> | ||
| <line num="161" count="1" type="stmt"/> | ||
| <line num="165" count="13" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="167" count="11" type="cond" truecount="1" falsecount="1"/> | ||
| <line num="168" count="0" type="stmt"/> | ||
| <line num="170" count="2" type="stmt"/> | ||
| <line num="176" count="294" type="cond" truecount="12" falsecount="0"/> | ||
| <line num="178" count="169" type="stmt"/> | ||
| <line num="180" count="18" type="stmt"/> | ||
| <line num="182" count="1" type="stmt"/> | ||
| <line num="184" count="11" type="stmt"/> | ||
| <line num="186" count="26" type="stmt"/> | ||
| <line num="188" count="3" type="stmt"/> | ||
| <line num="190" count="1" type="stmt"/> | ||
| <line num="192" count="1" type="stmt"/> | ||
| <line num="194" count="1" type="stmt"/> | ||
| <line num="196" count="61" type="stmt"/> | ||
| <line num="198" count="1" type="stmt"/> | ||
| <line num="200" count="1" type="stmt"/> | ||
| <line num="212" count="169" type="stmt"/> | ||
| <line num="213" count="169" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="214" count="169" type="cond" truecount="3" falsecount="1"/> | ||
| <line num="143" count="124" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="144" count="124" type="cond" truecount="1" falsecount="1"/> | ||
| <line num="146" count="124" type="stmt"/> | ||
| <line num="149" count="124" type="cond" truecount="4" falsecount="0"/> | ||
| <line num="150" count="1" type="stmt"/> | ||
| <line num="153" count="124" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="154" count="124" type="cond" truecount="4" falsecount="0"/> | ||
| <line num="156" count="124" type="stmt"/> | ||
| <line num="160" count="1" type="cond" truecount="3" falsecount="1"/> | ||
| <line num="161" count="1" type="cond" truecount="1" falsecount="1"/> | ||
| <line num="162" count="1" type="stmt"/> | ||
| <line num="166" count="13" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="168" count="11" type="cond" truecount="1" falsecount="1"/> | ||
| <line num="169" count="0" type="stmt"/> | ||
| <line num="171" count="2" type="stmt"/> | ||
| <line num="177" count="294" type="cond" truecount="12" falsecount="0"/> | ||
| <line num="179" count="169" type="stmt"/> | ||
| <line num="181" count="18" type="stmt"/> | ||
| <line num="183" count="1" type="stmt"/> | ||
| <line num="185" count="11" type="stmt"/> | ||
| <line num="187" count="26" type="stmt"/> | ||
| <line num="189" count="3" type="stmt"/> | ||
| <line num="191" count="1" type="stmt"/> | ||
| <line num="193" count="1" type="stmt"/> | ||
| <line num="195" count="1" type="stmt"/> | ||
| <line num="197" count="61" type="stmt"/> | ||
| <line num="199" count="1" type="stmt"/> | ||
| <line num="201" count="1" type="stmt"/> | ||
| <line num="213" count="169" type="stmt"/> | ||
| <line num="214" count="169" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="215" count="169" type="cond" truecount="3" falsecount="1"/> | ||
| <line num="216" count="169" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="217" count="169" type="cond" truecount="1" falsecount="1"/> | ||
| <line num="219" count="169" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="220" count="39" type="stmt"/> | ||
| <line num="224" count="18" type="stmt"/> | ||
| <line num="225" count="18" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="216" count="169" type="cond" truecount="3" falsecount="1"/> | ||
| <line num="217" count="169" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="218" count="169" type="cond" truecount="1" falsecount="1"/> | ||
| <line num="220" count="169" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="221" count="39" type="stmt"/> | ||
| <line num="225" count="18" type="stmt"/> | ||
| <line num="226" count="18" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="227" count="18" type="stmt"/> | ||
| <line num="231" count="3" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="232" count="2" type="stmt"/> | ||
| <line num="236" count="1" type="cond" truecount="1" falsecount="1"/> | ||
| <line num="227" count="18" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="228" count="18" type="stmt"/> | ||
| <line num="232" count="3" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="233" count="2" type="stmt"/> | ||
| <line num="237" count="1" type="cond" truecount="1" falsecount="1"/> | ||
| <line num="239" count="1" type="stmt"/> | ||
| <line num="238" count="1" type="cond" truecount="1" falsecount="1"/> | ||
| <line num="240" count="1" type="stmt"/> | ||
| <line num="241" count="2" type="stmt"/> | ||
| <line num="243" count="1" type="stmt"/> | ||
| <line num="241" count="1" type="stmt"/> | ||
| <line num="242" count="2" type="stmt"/> | ||
| <line num="244" count="1" type="stmt"/> | ||
| <line num="250" count="45" type="stmt"/> | ||
| <line num="252" count="45" type="stmt"/> | ||
| <line num="253" count="45" type="cond" truecount="3" falsecount="1"/> | ||
| <line num="254" count="45" type="stmt"/> | ||
| <line num="257" count="45" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="258" count="13" type="stmt"/> | ||
| <line num="262" count="45" type="cond" truecount="1" falsecount="1"/> | ||
| <line num="263" count="0" type="stmt"/> | ||
| <line num="267" count="45" type="stmt"/> | ||
| <line num="268" count="66" type="stmt"/> | ||
| <line num="271" count="45" type="stmt"/> | ||
| <line num="245" count="1" type="stmt"/> | ||
| <line num="251" count="45" type="stmt"/> | ||
| <line num="253" count="45" type="stmt"/> | ||
| <line num="254" count="45" type="cond" truecount="3" falsecount="1"/> | ||
| <line num="255" count="45" type="stmt"/> | ||
| <line num="258" count="45" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="259" count="13" type="stmt"/> | ||
| <line num="263" count="45" type="cond" truecount="1" falsecount="1"/> | ||
| <line num="264" count="0" type="stmt"/> | ||
| <line num="268" count="45" type="stmt"/> | ||
| <line num="269" count="66" type="stmt"/> | ||
| <line num="272" count="45" type="stmt"/> | ||
| <line num="276" count="66" type="stmt"/> | ||
| <line num="273" count="45" type="stmt"/> | ||
| <line num="277" count="66" type="stmt"/> | ||
| <line num="278" count="66" type="cond" truecount="3" falsecount="1"/> | ||
| <line num="279" count="66" type="stmt"/> | ||
| <line num="281" count="66" type="stmt"/> | ||
| <line num="284" count="66" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="285" count="2" type="stmt"/> | ||
| <line num="289" count="66" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="290" count="55" type="stmt"/> | ||
| <line num="294" count="66" type="cond" truecount="1" falsecount="1"/> | ||
| <line num="295" count="0" type="cond" truecount="0" falsecount="4"/> | ||
| <line num="296" count="0" type="stmt"/> | ||
| <line num="300" count="66" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="301" count="12" type="stmt"/> | ||
| <line num="305" count="66" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="306" count="11" type="stmt"/> | ||
| <line num="310" count="66" type="cond" truecount="1" falsecount="1"/> | ||
| <line num="311" count="0" type="stmt"/> | ||
| <line num="315" count="66" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="316" count="12" type="stmt"/> | ||
| <line num="320" count="66" type="cond" truecount="1" falsecount="1"/> | ||
| <line num="321" count="66" type="stmt"/> | ||
| <line num="324" count="66" type="stmt"/> | ||
| <line num="278" count="66" type="stmt"/> | ||
| <line num="279" count="66" type="cond" truecount="3" falsecount="1"/> | ||
| <line num="280" count="66" type="stmt"/> | ||
| <line num="282" count="66" type="stmt"/> | ||
| <line num="285" count="66" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="286" count="2" type="stmt"/> | ||
| <line num="290" count="66" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="291" count="55" type="stmt"/> | ||
| <line num="295" count="66" type="cond" truecount="1" falsecount="1"/> | ||
| <line num="296" count="0" type="cond" truecount="0" falsecount="4"/> | ||
| <line num="297" count="0" type="stmt"/> | ||
| <line num="301" count="66" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="302" count="12" type="stmt"/> | ||
| <line num="306" count="66" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="307" count="11" type="stmt"/> | ||
| <line num="311" count="66" type="cond" truecount="1" falsecount="1"/> | ||
| <line num="312" count="0" type="stmt"/> | ||
| <line num="316" count="66" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="317" count="12" type="stmt"/> | ||
| <line num="321" count="66" type="cond" truecount="1" falsecount="1"/> | ||
| <line num="322" count="66" type="stmt"/> | ||
| <line num="325" count="66" type="stmt"/> | ||
| <line num="329" count="111" type="cond" truecount="4" falsecount="0"/> | ||
| <line num="330" count="1" type="stmt"/> | ||
| <line num="334" count="24" type="stmt"/> | ||
| <line num="337" count="24" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="338" count="1" type="stmt"/> | ||
| <line num="326" count="66" type="stmt"/> | ||
| <line num="330" count="111" type="cond" truecount="4" falsecount="0"/> | ||
| <line num="331" count="1" type="stmt"/> | ||
| <line num="335" count="24" type="stmt"/> | ||
| <line num="338" count="24" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="339" count="1" type="stmt"/> | ||
| <line num="343" count="23" type="cond" truecount="1" falsecount="1"/> | ||
| <line num="344" count="0" type="stmt"/> | ||
| <line num="340" count="1" type="stmt"/> | ||
| <line num="344" count="23" type="cond" truecount="1" falsecount="1"/> | ||
| <line num="345" count="0" type="stmt"/> | ||
| <line num="349" count="23" type="cond" truecount="1" falsecount="1"/> | ||
| <line num="350" count="24" type="stmt"/> | ||
| <line num="346" count="0" type="stmt"/> | ||
| <line num="350" count="23" type="cond" truecount="1" falsecount="1"/> | ||
| <line num="351" count="24" type="stmt"/> | ||
| <line num="352" count="23" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="353" count="23" type="stmt"/> | ||
| <line num="354" count="23" type="cond" truecount="2" falsecount="2"/> | ||
| <line num="355" count="0" type="stmt"/> | ||
| <line num="357" count="23" type="cond" truecount="1" falsecount="1"/> | ||
| <line num="358" count="23" type="cond" truecount="3" falsecount="1"/> | ||
| <line num="359" count="23" type="stmt"/> | ||
| <line num="361" count="23" type="stmt"/> | ||
| <line num="365" count="12" type="stmt"/> | ||
| <line num="352" count="24" type="stmt"/> | ||
| <line num="353" count="23" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="354" count="23" type="stmt"/> | ||
| <line num="355" count="23" type="cond" truecount="2" falsecount="2"/> | ||
| <line num="356" count="0" type="stmt"/> | ||
| <line num="358" count="23" type="cond" truecount="1" falsecount="1"/> | ||
| <line num="359" count="23" type="cond" truecount="3" falsecount="1"/> | ||
| <line num="360" count="23" type="stmt"/> | ||
| <line num="362" count="23" type="stmt"/> | ||
| <line num="366" count="12" type="stmt"/> | ||
| <line num="367" count="12" type="stmt"/> | ||
| <line num="368" count="12" type="stmt"/> | ||
| <line num="370" count="12" type="stmt"/> | ||
| <line num="374" count="66" type="stmt"/> | ||
| <line num="369" count="12" type="stmt"/> | ||
| <line num="371" count="12" type="stmt"/> | ||
| <line num="375" count="66" type="stmt"/> | ||
| <line num="376" count="66" type="stmt"/> | ||
| <line num="377" count="76" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="377" count="66" type="stmt"/> | ||
| <line num="378" count="76" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="379" count="76" type="cond" truecount="4" falsecount="0"/> | ||
| <line num="380" count="54" type="stmt"/> | ||
| <line num="381" count="54" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="382" count="53" type="stmt"/> | ||
| <line num="384" count="54" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="385" count="3" type="stmt"/> | ||
| <line num="379" count="76" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="380" count="76" type="cond" truecount="4" falsecount="0"/> | ||
| <line num="381" count="54" type="stmt"/> | ||
| <line num="382" count="54" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="383" count="53" type="stmt"/> | ||
| <line num="385" count="54" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="386" count="3" type="stmt"/> | ||
| <line num="387" count="4" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="387" count="3" type="stmt"/> | ||
| <line num="388" count="4" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="389" count="4" type="stmt"/> | ||
| <line num="391" count="3" type="stmt"/> | ||
| <line num="393" count="54" type="stmt"/> | ||
| <line num="395" count="22" type="stmt"/> | ||
| <line num="398" count="66" type="stmt"/> | ||
| <line num="402" count="12" type="stmt"/> | ||
| <line num="403" count="12" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="404" count="11" type="stmt"/> | ||
| <line num="389" count="4" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="390" count="4" type="stmt"/> | ||
| <line num="392" count="3" type="stmt"/> | ||
| <line num="394" count="54" type="stmt"/> | ||
| <line num="396" count="22" type="stmt"/> | ||
| <line num="399" count="66" type="stmt"/> | ||
| <line num="403" count="12" type="stmt"/> | ||
| <line num="404" count="12" type="cond" truecount="2" falsecount="0"/> | ||
| <line num="405" count="11" type="stmt"/> | ||
| <line num="408" count="1" type="stmt"/> | ||
| <line num="409" count="1" type="cond" truecount="1" falsecount="1"/> | ||
| <line num="410" count="1" type="cond" truecount="3" falsecount="1"/> | ||
| <line num="411" count="1" type="stmt"/> | ||
| <line num="412" count="1" type="stmt"/> | ||
| <line num="406" count="11" type="stmt"/> | ||
| <line num="409" count="1" type="stmt"/> | ||
| <line num="410" count="1" type="cond" truecount="1" falsecount="1"/> | ||
| <line num="411" count="1" type="cond" truecount="3" falsecount="1"/> | ||
| <line num="412" count="1" type="cond" truecount="1" falsecount="1"/> | ||
| <line num="413" count="1" type="stmt"/> | ||
| <line num="415" count="0" type="stmt"/> | ||
| <line num="414" count="1" type="stmt"/> | ||
| <line num="415" count="1" type="stmt"/> | ||
| <line num="417" count="0" type="stmt"/> | ||
| </file> | ||
@@ -209,0 +211,0 @@ <file name="circular-refs.ts" path="/home/runner/work/ContractKit/ContractKit/packages/openapi-to-ck/src/circular-refs.ts"> |
+14
-14
@@ -26,5 +26,5 @@ | ||
| <div class='fl pad1y space-right2'> | ||
| <span class="strong">81.01% </span> | ||
| <span class="strong">80.95% </span> | ||
| <span class="quiet">Statements</span> | ||
| <span class='fraction'>747/922</span> | ||
| <span class='fraction'>748/924</span> | ||
| </div> | ||
@@ -34,5 +34,5 @@ | ||
| <div class='fl pad1y space-right2'> | ||
| <span class="strong">70.41% </span> | ||
| <span class="strong">70.17% </span> | ||
| <span class="quiet">Branches</span> | ||
| <span class='fraction'>526/747</span> | ||
| <span class='fraction'>527/751</span> | ||
| </div> | ||
@@ -49,5 +49,5 @@ | ||
| <div class='fl pad1y space-right2'> | ||
| <span class="strong">83.55% </span> | ||
| <span class="strong">83.47% </span> | ||
| <span class="quiet">Lines</span> | ||
| <span class='fraction'>696/833</span> | ||
| <span class='fraction'>697/835</span> | ||
| </div> | ||
@@ -86,13 +86,13 @@ | ||
| <td class="file high" data-value="src"><a href="src/index.html">src</a></td> | ||
| <td data-value="80.79" class="pic high"> | ||
| <td data-value="80.72" class="pic high"> | ||
| <div class="chart"><div class="cover-fill" style="width: 80%"></div><div class="cover-empty" style="width: 20%"></div></div> | ||
| </td> | ||
| <td data-value="80.79" class="pct high">80.79%</td> | ||
| <td data-value="885" class="abs high">715/885</td> | ||
| <td data-value="70.7" class="pct medium">70.7%</td> | ||
| <td data-value="727" class="abs medium">514/727</td> | ||
| <td data-value="80.72" class="pct high">80.72%</td> | ||
| <td data-value="887" class="abs high">716/887</td> | ||
| <td data-value="70.45" class="pct medium">70.45%</td> | ||
| <td data-value="731" class="abs medium">515/731</td> | ||
| <td data-value="93.47" class="pct high">93.47%</td> | ||
| <td data-value="92" class="abs high">86/92</td> | ||
| <td data-value="83.18" class="pct high">83.18%</td> | ||
| <td data-value="803" class="abs high">668/803</td> | ||
| <td data-value="83.1" class="pct high">83.1%</td> | ||
| <td data-value="805" class="abs high">669/805</td> | ||
| </tr> | ||
@@ -123,3 +123,3 @@ | ||
| <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> | ||
| at 2026-05-08T13:02:58.721Z | ||
| at 2026-05-13T17:08:50.965Z | ||
| </div> | ||
@@ -126,0 +126,0 @@ <script src="prettify.js"></script> |
@@ -26,5 +26,5 @@ | ||
| <div class='fl pad1y space-right2'> | ||
| <span class="strong">90.65% </span> | ||
| <span class="strong">90.27% </span> | ||
| <span class="quiet">Statements</span> | ||
| <span class='fraction'>194/214</span> | ||
| <span class='fraction'>195/216</span> | ||
| </div> | ||
@@ -34,5 +34,5 @@ | ||
| <div class='fl pad1y space-right2'> | ||
| <span class="strong">82.7% </span> | ||
| <span class="strong">81.48% </span> | ||
| <span class="quiet">Branches</span> | ||
| <span class='fraction'>153/185</span> | ||
| <span class='fraction'>154/189</span> | ||
| </div> | ||
@@ -49,5 +49,5 @@ | ||
| <div class='fl pad1y space-right2'> | ||
| <span class="strong">91.91% </span> | ||
| <span class="strong">91.5% </span> | ||
| <span class="quiet">Lines</span> | ||
| <span class='fraction'>182/198</span> | ||
| <span class='fraction'>183/200</span> | ||
| </div> | ||
@@ -486,3 +486,5 @@ | ||
| <a name='L417'></a><a href='#L417'>417</a> | ||
| <a name='L418'></a><a href='#L418'>418</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span> | ||
| <a name='L418'></a><a href='#L418'>418</a> | ||
| <a name='L419'></a><a href='#L419'>419</a> | ||
| <a name='L420'></a><a href='#L420'>420</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
@@ -575,2 +577,3 @@ <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-no"> </span> | ||
| <span class="cline-any cline-no"> </span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
@@ -901,2 +904,3 @@ <span class="cline-any cline-neutral"> </span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-yes">1x</span> | ||
| <span class="cline-any cline-neutral"> </span> | ||
@@ -992,4 +996,5 @@ <span class="cline-any cline-no"> </span> | ||
| const sec = <span class="cstat-no" title="statement not covered" >root.security as SecurityFields;</span> | ||
| <span class="cstat-no" title="statement not covered" > if (sec.requireMfa !== undefined) {</span> | ||
| <span class="cstat-no" title="statement not covered" > lines.push(`${INDENT}${INDENT}requireMfa: ${sec.requireMfa}`);</span> | ||
| <span class="cstat-no" title="statement not covered" > if (sec.policy !== undefined) {</span> | ||
| const value = <span class="cstat-no" title="statement not covered" >sec.policy === false ? 'none' : sec.policy;</span> | ||
| <span class="cstat-no" title="statement not covered" > lines.push(`${INDENT}${INDENT}policy: ${value}`);</span> | ||
| } | ||
@@ -1315,6 +1320,7 @@ } | ||
| const sec = security as SecurityFields; | ||
| if (sec.requireMfa !== undefined) { | ||
| const comment = ctx.includeComments && sec.requireMfaDescription ? <span class="branch-0 cbranch-no" title="branch not covered" >` # ${sec.requireMfaDescription}` : '</span>'; | ||
| if (sec.policy !== undefined) { | ||
| const comment = ctx.includeComments && sec.policyDescription ? <span class="branch-0 cbranch-no" title="branch not covered" >` # ${sec.policyDescription}` : '</span>'; | ||
| const value = sec.policy === false ? <span class="branch-0 cbranch-no" title="branch not covered" >'none' : s</span>ec.policy; | ||
| lines.push(`${indent}security: {`); | ||
| lines.push(`${INDENT.repeat(depth + 1)}requireMfa: ${sec.requireMfa}${comment}`); | ||
| lines.push(`${INDENT.repeat(depth + 1)}policy: ${value}${comment}`); | ||
| lines.push(`${indent}}`); | ||
@@ -1332,3 +1338,3 @@ } else <span class="missing-if-branch" title="else path not taken" >E</span>{ | ||
| <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> | ||
| at 2026-05-08T13:02:58.721Z | ||
| at 2026-05-13T17:08:50.965Z | ||
| </div> | ||
@@ -1335,0 +1341,0 @@ <script src="../prettify.js"></script> |
@@ -286,3 +286,3 @@ | ||
| <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> | ||
| at 2026-05-08T13:02:58.721Z | ||
| at 2026-05-13T17:08:50.965Z | ||
| </div> | ||
@@ -289,0 +289,0 @@ <script src="../prettify.js"></script> |
@@ -523,3 +523,3 @@ | ||
| <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> | ||
| at 2026-05-08T13:02:58.721Z | ||
| at 2026-05-13T17:08:50.965Z | ||
| </div> | ||
@@ -526,0 +526,0 @@ <script src="../prettify.js"></script> |
+14
-14
@@ -26,5 +26,5 @@ | ||
| <div class='fl pad1y space-right2'> | ||
| <span class="strong">80.79% </span> | ||
| <span class="strong">80.72% </span> | ||
| <span class="quiet">Statements</span> | ||
| <span class='fraction'>715/885</span> | ||
| <span class='fraction'>716/887</span> | ||
| </div> | ||
@@ -34,5 +34,5 @@ | ||
| <div class='fl pad1y space-right2'> | ||
| <span class="strong">70.7% </span> | ||
| <span class="strong">70.45% </span> | ||
| <span class="quiet">Branches</span> | ||
| <span class='fraction'>514/727</span> | ||
| <span class='fraction'>515/731</span> | ||
| </div> | ||
@@ -49,5 +49,5 @@ | ||
| <div class='fl pad1y space-right2'> | ||
| <span class="strong">83.18% </span> | ||
| <span class="strong">83.1% </span> | ||
| <span class="quiet">Lines</span> | ||
| <span class='fraction'>668/803</span> | ||
| <span class='fraction'>669/805</span> | ||
| </div> | ||
@@ -86,13 +86,13 @@ | ||
| <td class="file high" data-value="ast-to-ck.ts"><a href="ast-to-ck.ts.html">ast-to-ck.ts</a></td> | ||
| <td data-value="90.65" class="pic high"> | ||
| <td data-value="90.27" class="pic high"> | ||
| <div class="chart"><div class="cover-fill" style="width: 90%"></div><div class="cover-empty" style="width: 10%"></div></div> | ||
| </td> | ||
| <td data-value="90.65" class="pct high">90.65%</td> | ||
| <td data-value="214" class="abs high">194/214</td> | ||
| <td data-value="82.7" class="pct high">82.7%</td> | ||
| <td data-value="185" class="abs high">153/185</td> | ||
| <td data-value="90.27" class="pct high">90.27%</td> | ||
| <td data-value="216" class="abs high">195/216</td> | ||
| <td data-value="81.48" class="pct high">81.48%</td> | ||
| <td data-value="189" class="abs high">154/189</td> | ||
| <td data-value="100" class="pct high">100%</td> | ||
| <td data-value="18" class="abs high">18/18</td> | ||
| <td data-value="91.91" class="pct high">91.91%</td> | ||
| <td data-value="198" class="abs high">182/198</td> | ||
| <td data-value="91.5" class="pct high">91.5%</td> | ||
| <td data-value="200" class="abs high">183/200</td> | ||
| </tr> | ||
@@ -213,3 +213,3 @@ | ||
| <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> | ||
| at 2026-05-08T13:02:58.721Z | ||
| at 2026-05-13T17:08:50.965Z | ||
| </div> | ||
@@ -216,0 +216,0 @@ <script src="../prettify.js"></script> |
@@ -1150,3 +1150,3 @@ | ||
| <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> | ||
| at 2026-05-08T13:02:58.721Z | ||
| at 2026-05-13T17:08:50.965Z | ||
| </div> | ||
@@ -1153,0 +1153,0 @@ <script src="../prettify.js"></script> |
@@ -1079,4 +1079,4 @@ | ||
| | ||
| // The DSL's security model is simpler — OpenAPI scopes/roles don't map onto requireMfa, | ||
| // so any non-empty security requirement is collapsed to "authenticated, no MFA". | ||
| // The DSL's security model is simpler — OpenAPI scopes/roles don't map onto named policies, | ||
| // so any non-empty security requirement is collapsed to "authenticated, default policy". | ||
| <span class="cstat-no" title="statement not covered" > return { loc: LOC };</span> | ||
@@ -1118,3 +1118,3 @@ } | ||
| <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> | ||
| at 2026-05-08T13:02:58.721Z | ||
| at 2026-05-13T17:08:50.965Z | ||
| </div> | ||
@@ -1121,0 +1121,0 @@ <script src="../prettify.js"></script> |
@@ -1366,3 +1366,3 @@ | ||
| <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> | ||
| at 2026-05-08T13:02:58.721Z | ||
| at 2026-05-13T17:08:50.965Z | ||
| </div> | ||
@@ -1369,0 +1369,0 @@ <script src="../prettify.js"></script> |
@@ -673,3 +673,3 @@ | ||
| <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> | ||
| at 2026-05-08T13:02:58.721Z | ||
| at 2026-05-13T17:08:50.965Z | ||
| </div> | ||
@@ -676,0 +676,0 @@ <script src="../prettify.js"></script> |
@@ -142,3 +142,3 @@ | ||
| <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> | ||
| at 2026-05-08T13:02:58.721Z | ||
| at 2026-05-13T17:08:50.965Z | ||
| </div> | ||
@@ -145,0 +145,0 @@ <script src="../prettify.js"></script> |
@@ -547,3 +547,3 @@ | ||
| <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> | ||
| at 2026-05-08T13:02:58.721Z | ||
| at 2026-05-13T17:08:50.965Z | ||
| </div> | ||
@@ -550,0 +550,0 @@ <script src="../prettify.js"></script> |
@@ -104,3 +104,3 @@ | ||
| <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> | ||
| at 2026-05-08T13:02:58.721Z | ||
| at 2026-05-13T17:08:50.965Z | ||
| </div> | ||
@@ -107,0 +107,0 @@ <script src="../prettify.js"></script> |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"ast-to-ck.d.ts","sourceRoot":"","sources":["../src/ast-to-ck.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACR,UAAU,EAGV,gBAAgB,EAUnB,MAAM,mBAAmB,CAAC;AAM3B,MAAM,WAAW,gBAAgB;IAC7B,6DAA6D;IAC7D,eAAe,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,GAAE,gBAAqB,GAAG,MAAM,CAoBhF;AAiID,wBAAgB,aAAa,CAAC,IAAI,EAAE,gBAAgB,GAAG,MAAM,CA2B5D"} | ||
| {"version":3,"file":"ast-to-ck.d.ts","sourceRoot":"","sources":["../src/ast-to-ck.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACR,UAAU,EAGV,gBAAgB,EAUnB,MAAM,mBAAmB,CAAC;AAM3B,MAAM,WAAW,gBAAgB;IAC7B,6DAA6D;IAC7D,eAAe,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,GAAE,gBAAqB,GAAG,MAAM,CAoBhF;AAkID,wBAAgB,aAAa,CAAC,IAAI,EAAE,gBAAgB,GAAG,MAAM,CA2B5D"} |
+1
-1
@@ -14,3 +14,3 @@ import { | ||
| splitByTag | ||
| } from "./chunk-T2G2WRZ2.js"; | ||
| } from "./chunk-YOTHJ2V4.js"; | ||
| export { | ||
@@ -17,0 +17,0 @@ astToCk, |
+1
-1
| import { | ||
| __name, | ||
| convertOpenApiToCk | ||
| } from "./chunk-T2G2WRZ2.js"; | ||
| } from "./chunk-YOTHJ2V4.js"; | ||
@@ -6,0 +6,0 @@ // src/plugin.ts |
+2
-2
| { | ||
| "name": "@contractkit/openapi-to-ck", | ||
| "version": "0.7.8", | ||
| "version": "0.8.0", | ||
| "description": "Convert OpenAPI specs (2.0/3.0/3.1) to Contract Kit .ck files", | ||
@@ -32,3 +32,3 @@ "author": { | ||
| "yaml": "^2.8.3", | ||
| "@contractkit/core": "0.17.0" | ||
| "@contractkit/core": "0.18.0" | ||
| }, | ||
@@ -35,0 +35,0 @@ "devDependencies": { |
+7
-5
@@ -87,4 +87,5 @@ import type { | ||
| const sec = root.security as SecurityFields; | ||
| if (sec.requireMfa !== undefined) { | ||
| lines.push(`${INDENT}${INDENT}requireMfa: ${sec.requireMfa}`); | ||
| if (sec.policy !== undefined) { | ||
| const value = sec.policy === false ? 'none' : sec.policy; | ||
| lines.push(`${INDENT}${INDENT}policy: ${value}`); | ||
| } | ||
@@ -410,6 +411,7 @@ } | ||
| const sec = security as SecurityFields; | ||
| if (sec.requireMfa !== undefined) { | ||
| const comment = ctx.includeComments && sec.requireMfaDescription ? ` # ${sec.requireMfaDescription}` : ''; | ||
| if (sec.policy !== undefined) { | ||
| const comment = ctx.includeComments && sec.policyDescription ? ` # ${sec.policyDescription}` : ''; | ||
| const value = sec.policy === false ? 'none' : sec.policy; | ||
| lines.push(`${indent}security: {`); | ||
| lines.push(`${INDENT.repeat(depth + 1)}requireMfa: ${sec.requireMfa}${comment}`); | ||
| lines.push(`${INDENT.repeat(depth + 1)}policy: ${value}${comment}`); | ||
| lines.push(`${indent}}`); | ||
@@ -416,0 +418,0 @@ } else { |
@@ -318,4 +318,4 @@ import type { | ||
| // The DSL's security model is simpler — OpenAPI scopes/roles don't map onto requireMfa, | ||
| // so any non-empty security requirement is collapsed to "authenticated, no MFA". | ||
| // The DSL's security model is simpler — OpenAPI scopes/roles don't map onto named policies, | ||
| // so any non-empty security requirement is collapsed to "authenticated, default policy". | ||
| return { loc: LOC }; | ||
@@ -322,0 +322,0 @@ } |
@@ -330,3 +330,3 @@ import { describe, it, expect } from 'vitest'; | ||
| it('serializes security with requireMfa', () => { | ||
| it('serializes security with policy', () => { | ||
| const root = ckRoot({ | ||
@@ -336,3 +336,3 @@ routes: [ | ||
| opOperation('get', { | ||
| security: { requireMfa: true, loc: { file: 'test.ck', line: 1 } }, | ||
| security: { policy: 'adminWrite', loc: { file: 'test.ck', line: 1 } }, | ||
| responses: [opResponse(200, refType('Data'), 'application/json')], | ||
@@ -345,3 +345,3 @@ }), | ||
| expect(result).toContain(' security: {'); | ||
| expect(result).toContain(' requireMfa: true'); | ||
| expect(result).toContain(' policy: adminWrite'); | ||
| }); | ||
@@ -348,0 +348,0 @@ |
| var __defProp = Object.defineProperty; | ||
| var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); | ||
| // src/normalize.ts | ||
| function normalize(doc, warnings) { | ||
| const version = detectVersion(doc); | ||
| if (version === "2.0") { | ||
| return normalizeSwagger2(doc, warnings); | ||
| } | ||
| if (version === "3.0") { | ||
| return normalizeOas30(doc, warnings); | ||
| } | ||
| return doc; | ||
| } | ||
| __name(normalize, "normalize"); | ||
| function detectVersion(doc) { | ||
| if (typeof doc.swagger === "string" && doc.swagger.startsWith("2")) return "2.0"; | ||
| if (typeof doc.openapi === "string") { | ||
| if (doc.openapi.startsWith("3.0")) return "3.0"; | ||
| } | ||
| return "3.1"; | ||
| } | ||
| __name(detectVersion, "detectVersion"); | ||
| function normalizeSwagger2(doc, warnings) { | ||
| const info = doc.info ?? { | ||
| title: "Untitled", | ||
| version: "0.0.0" | ||
| }; | ||
| const basePath = doc.basePath ?? ""; | ||
| const schemes = doc.schemes ?? [ | ||
| "https" | ||
| ]; | ||
| const host = doc.host ?? "localhost"; | ||
| const globalConsumes = doc.consumes ?? [ | ||
| "application/json" | ||
| ]; | ||
| const globalProduces = doc.produces ?? [ | ||
| "application/json" | ||
| ]; | ||
| const result = { | ||
| openapi: "3.1.0", | ||
| info: { | ||
| title: info.title ?? "Untitled", | ||
| version: info.version ?? "0.0.0", | ||
| description: info.description | ||
| }, | ||
| servers: [ | ||
| { | ||
| url: `${schemes[0]}://${host}${basePath}` | ||
| } | ||
| ], | ||
| paths: {}, | ||
| components: { | ||
| schemas: {}, | ||
| securitySchemes: {} | ||
| }, | ||
| tags: doc.tags ?? [] | ||
| }; | ||
| const definitions = doc.definitions ?? {}; | ||
| for (const [name, schema] of Object.entries(definitions)) { | ||
| result.components.schemas[name] = normalizeNullable30(schema); | ||
| } | ||
| const secDefs = doc.securityDefinitions ?? {}; | ||
| for (const [name, scheme] of Object.entries(secDefs)) { | ||
| result.components.securitySchemes[name] = convertSecurityScheme2(scheme); | ||
| } | ||
| const paths = doc.paths ?? {}; | ||
| for (const [path, pathItem] of Object.entries(paths)) { | ||
| result.paths[path] = normalizePathItem2(pathItem, globalConsumes, globalProduces, warnings); | ||
| } | ||
| if (doc.security) { | ||
| result.security = doc.security; | ||
| } | ||
| return result; | ||
| } | ||
| __name(normalizeSwagger2, "normalizeSwagger2"); | ||
| function normalizePathItem2(pathItem, globalConsumes, globalProduces, warnings) { | ||
| const methods = [ | ||
| "get", | ||
| "post", | ||
| "put", | ||
| "patch", | ||
| "delete", | ||
| "head", | ||
| "options" | ||
| ]; | ||
| const normalized = {}; | ||
| const pathParams = pathItem.parameters ?? []; | ||
| for (const method of methods) { | ||
| const op = pathItem[method]; | ||
| if (!op) continue; | ||
| const opConsumes = op.consumes ?? globalConsumes; | ||
| const opProduces = op.produces ?? globalProduces; | ||
| const params = [ | ||
| ...pathParams, | ||
| ...op.parameters ?? [] | ||
| ]; | ||
| const nonBodyParams = []; | ||
| let requestBody; | ||
| for (const param of params) { | ||
| if (param.in === "body") { | ||
| const contentType = opConsumes[0] ?? "application/json"; | ||
| requestBody = { | ||
| description: param.description, | ||
| required: param.required ?? true, | ||
| content: { | ||
| [contentType]: { | ||
| schema: normalizeNullable30(param.schema ?? {}) | ||
| } | ||
| } | ||
| }; | ||
| } else if (param.in === "formData") { | ||
| warnings.info(`#/paths/${encodePathSegment(method)}`, "formData parameters converted to multipart/form-data requestBody"); | ||
| if (!requestBody) { | ||
| requestBody = { | ||
| content: { | ||
| "multipart/form-data": { | ||
| schema: { | ||
| type: "object", | ||
| properties: {}, | ||
| required: [] | ||
| } | ||
| } | ||
| } | ||
| }; | ||
| } | ||
| const formSchema = requestBody.content["multipart/form-data"].schema; | ||
| const props = formSchema.properties; | ||
| props[param.name] = normalizeNullable30(param); | ||
| if (param.required) { | ||
| formSchema.required.push(param.name); | ||
| } | ||
| } else { | ||
| const normalizedParam = { | ||
| ...param | ||
| }; | ||
| if (param.type) { | ||
| normalizedParam.schema = normalizeNullable30({ | ||
| type: param.type, | ||
| format: param.format, | ||
| enum: param.enum, | ||
| items: param.items, | ||
| default: param.default, | ||
| minimum: param.minimum, | ||
| maximum: param.maximum, | ||
| minLength: param.minLength, | ||
| maxLength: param.maxLength, | ||
| pattern: param.pattern | ||
| }); | ||
| delete normalizedParam.type; | ||
| delete normalizedParam.format; | ||
| delete normalizedParam.enum; | ||
| delete normalizedParam.items; | ||
| } | ||
| nonBodyParams.push(normalizedParam); | ||
| } | ||
| } | ||
| const responses = {}; | ||
| const opResponses = op.responses ?? {}; | ||
| for (const [code, resp] of Object.entries(opResponses)) { | ||
| const contentType = opProduces[0] ?? "application/json"; | ||
| const headers = convertResponseHeaders2(resp.headers); | ||
| const responseEntry = { | ||
| description: resp.description ?? "" | ||
| }; | ||
| if (resp.schema) { | ||
| responseEntry.content = { | ||
| [contentType]: { | ||
| schema: normalizeNullable30(resp.schema) | ||
| } | ||
| }; | ||
| } | ||
| if (headers) { | ||
| responseEntry.headers = headers; | ||
| } | ||
| responses[code] = responseEntry; | ||
| } | ||
| normalized[method] = { | ||
| operationId: op.operationId, | ||
| summary: op.summary, | ||
| description: op.description, | ||
| tags: op.tags, | ||
| parameters: nonBodyParams.length > 0 ? nonBodyParams : void 0, | ||
| requestBody, | ||
| responses, | ||
| security: op.security, | ||
| deprecated: op.deprecated | ||
| }; | ||
| } | ||
| return normalized; | ||
| } | ||
| __name(normalizePathItem2, "normalizePathItem2"); | ||
| function convertResponseHeaders2(headers) { | ||
| if (!headers) return void 0; | ||
| const out = {}; | ||
| for (const [name, header] of Object.entries(headers)) { | ||
| if (!header || typeof header !== "object") continue; | ||
| const { description, type, format, items, ...rest } = header; | ||
| const schema = { | ||
| ...rest | ||
| }; | ||
| if (type !== void 0) schema.type = type; | ||
| if (format !== void 0) schema.format = format; | ||
| if (items !== void 0) schema.items = items; | ||
| const normalized = {}; | ||
| if (description !== void 0) normalized.description = description; | ||
| if (Object.keys(schema).length > 0) normalized.schema = normalizeNullable30(schema); | ||
| out[name] = normalized; | ||
| } | ||
| return Object.keys(out).length > 0 ? out : void 0; | ||
| } | ||
| __name(convertResponseHeaders2, "convertResponseHeaders2"); | ||
| function convertSecurityScheme2(scheme) { | ||
| const type = scheme.type; | ||
| if (type === "basic") { | ||
| return { | ||
| type: "http", | ||
| scheme: "basic" | ||
| }; | ||
| } | ||
| if (type === "apiKey") { | ||
| return { | ||
| type: "apiKey", | ||
| name: scheme.name, | ||
| in: scheme.in | ||
| }; | ||
| } | ||
| if (type === "oauth2") { | ||
| const flow = scheme.flow; | ||
| const flows = {}; | ||
| if (flow === "implicit") { | ||
| flows.implicit = { | ||
| authorizationUrl: scheme.authorizationUrl, | ||
| scopes: scheme.scopes ?? {} | ||
| }; | ||
| } else if (flow === "password") { | ||
| flows.password = { | ||
| tokenUrl: scheme.tokenUrl, | ||
| scopes: scheme.scopes ?? {} | ||
| }; | ||
| } else if (flow === "application") { | ||
| flows.clientCredentials = { | ||
| tokenUrl: scheme.tokenUrl, | ||
| scopes: scheme.scopes ?? {} | ||
| }; | ||
| } else if (flow === "accessCode") { | ||
| flows.authorizationCode = { | ||
| authorizationUrl: scheme.authorizationUrl, | ||
| tokenUrl: scheme.tokenUrl, | ||
| scopes: scheme.scopes ?? {} | ||
| }; | ||
| } | ||
| return { | ||
| type: "oauth2", | ||
| flows | ||
| }; | ||
| } | ||
| return scheme; | ||
| } | ||
| __name(convertSecurityScheme2, "convertSecurityScheme2"); | ||
| function normalizeOas30(doc, _warnings) { | ||
| if (doc.components?.schemas) { | ||
| for (const [name, schema] of Object.entries(doc.components.schemas)) { | ||
| doc.components.schemas[name] = normalizeNullable30(schema); | ||
| } | ||
| } | ||
| if (doc.paths) { | ||
| for (const pathItem of Object.values(doc.paths)) { | ||
| normalizePathItemSchemas(pathItem); | ||
| } | ||
| } | ||
| doc.openapi = "3.1.0"; | ||
| return doc; | ||
| } | ||
| __name(normalizeOas30, "normalizeOas30"); | ||
| function normalizePathItemSchemas(pathItem) { | ||
| const methods = [ | ||
| "get", | ||
| "post", | ||
| "put", | ||
| "patch", | ||
| "delete", | ||
| "head", | ||
| "options" | ||
| ]; | ||
| for (const method of methods) { | ||
| const op = pathItem[method]; | ||
| if (!op) continue; | ||
| const params = op.parameters ?? []; | ||
| for (const param of params) { | ||
| if (param.schema) { | ||
| param.schema = normalizeNullable30(param.schema); | ||
| } | ||
| } | ||
| const reqBody = op.requestBody; | ||
| if (reqBody?.content) { | ||
| for (const mediaType of Object.values(reqBody.content)) { | ||
| if (mediaType.schema) { | ||
| mediaType.schema = normalizeNullable30(mediaType.schema); | ||
| } | ||
| } | ||
| } | ||
| const responses = op.responses ?? {}; | ||
| for (const resp of Object.values(responses)) { | ||
| if (resp.content) { | ||
| for (const mediaType of Object.values(resp.content)) { | ||
| if (mediaType.schema) { | ||
| mediaType.schema = normalizeNullable30(mediaType.schema); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| __name(normalizePathItemSchemas, "normalizePathItemSchemas"); | ||
| function normalizeNullable30(schema) { | ||
| if (!schema || typeof schema !== "object") return schema; | ||
| const result = { | ||
| ...schema | ||
| }; | ||
| if (result.nullable === true && typeof result.type === "string") { | ||
| result.type = [ | ||
| result.type, | ||
| "null" | ||
| ]; | ||
| delete result.nullable; | ||
| } | ||
| if (result.properties && typeof result.properties === "object") { | ||
| const props = result.properties; | ||
| for (const [key, val] of Object.entries(props)) { | ||
| props[key] = normalizeNullable30(val); | ||
| } | ||
| } | ||
| if (result.items && typeof result.items === "object" && !Array.isArray(result.items)) { | ||
| result.items = normalizeNullable30(result.items); | ||
| } | ||
| if (result.additionalProperties && typeof result.additionalProperties === "object") { | ||
| result.additionalProperties = normalizeNullable30(result.additionalProperties); | ||
| } | ||
| for (const combiner of [ | ||
| "allOf", | ||
| "oneOf", | ||
| "anyOf" | ||
| ]) { | ||
| if (Array.isArray(result[combiner])) { | ||
| result[combiner] = result[combiner].map(normalizeNullable30); | ||
| } | ||
| } | ||
| return result; | ||
| } | ||
| __name(normalizeNullable30, "normalizeNullable30"); | ||
| function encodePathSegment(s) { | ||
| return s.replace(/~/g, "~0").replace(/\//g, "~1"); | ||
| } | ||
| __name(encodePathSegment, "encodePathSegment"); | ||
| // src/circular-refs.ts | ||
| function detectCircularRefs(schemas) { | ||
| const circular = /* @__PURE__ */ new Set(); | ||
| const visiting = /* @__PURE__ */ new Set(); | ||
| const visited = /* @__PURE__ */ new Set(); | ||
| function visit(name) { | ||
| if (visited.has(name)) return; | ||
| if (visiting.has(name)) { | ||
| circular.add(name); | ||
| return; | ||
| } | ||
| visiting.add(name); | ||
| const schema = schemas[name]; | ||
| if (schema && typeof schema === "object") { | ||
| for (const ref of collectRefs(schema)) { | ||
| const refName = extractRefName(ref); | ||
| if (refName && schemas[refName]) { | ||
| visit(refName); | ||
| } | ||
| } | ||
| } | ||
| visiting.delete(name); | ||
| visited.add(name); | ||
| } | ||
| __name(visit, "visit"); | ||
| for (const name of Object.keys(schemas)) { | ||
| visit(name); | ||
| } | ||
| return circular; | ||
| } | ||
| __name(detectCircularRefs, "detectCircularRefs"); | ||
| function collectRefs(obj) { | ||
| const refs = []; | ||
| function walk(val) { | ||
| if (!val || typeof val !== "object") return; | ||
| if (Array.isArray(val)) { | ||
| for (const item of val) walk(item); | ||
| return; | ||
| } | ||
| const record = val; | ||
| if (typeof record.$ref === "string") { | ||
| refs.push(record.$ref); | ||
| } | ||
| for (const v of Object.values(record)) { | ||
| walk(v); | ||
| } | ||
| } | ||
| __name(walk, "walk"); | ||
| walk(obj); | ||
| return refs; | ||
| } | ||
| __name(collectRefs, "collectRefs"); | ||
| function extractRefName(ref) { | ||
| const match = ref.match(/^#\/(?:components\/schemas|definitions)\/(.+)$/); | ||
| return match?.[1]; | ||
| } | ||
| __name(extractRefName, "extractRefName"); | ||
| // src/schema-to-ast.ts | ||
| var LOC = { | ||
| file: "", | ||
| line: 0 | ||
| }; | ||
| var FORMAT_TO_SCALAR = { | ||
| email: "email", | ||
| uri: "url", | ||
| url: "url", | ||
| uuid: "uuid", | ||
| date: "date", | ||
| "date-time": "datetime", | ||
| time: "time", | ||
| binary: "binary", | ||
| int64: "bigint" | ||
| }; | ||
| function schemasToModels(schemas, ctx) { | ||
| const models = []; | ||
| for (const [name, schema] of Object.entries(schemas)) { | ||
| const modelCtx = { | ||
| ...ctx, | ||
| path: `#/components/schemas/${name}` | ||
| }; | ||
| const model = schemaToModel(name, schema, modelCtx); | ||
| if (model) models.push(model); | ||
| } | ||
| models.push(...ctx.extractedModels); | ||
| return models; | ||
| } | ||
| __name(schemasToModels, "schemasToModels"); | ||
| function schemaToModel(name, schema, ctx) { | ||
| warnUnsupported(schema, ctx); | ||
| const description = ctx.includeComments ? schema.description : void 0; | ||
| if (schema.allOf && schema.allOf.length === 2) { | ||
| const [first, second] = schema.allOf; | ||
| const refMember = first?.$ref ? first : second?.$ref ? second : null; | ||
| const objectMember = first?.$ref ? second : first; | ||
| if (refMember?.$ref && objectMember?.properties) { | ||
| const baseName = extractRefName(refMember.$ref); | ||
| if (baseName) { | ||
| const fields = schemaPropertiesToFields(objectMember, ctx); | ||
| return { | ||
| kind: "model", | ||
| name, | ||
| bases: [ | ||
| baseName | ||
| ], | ||
| fields, | ||
| description, | ||
| loc: LOC | ||
| }; | ||
| } | ||
| } | ||
| } | ||
| if (schema.properties || schema.type === "object" && !schema.additionalProperties) { | ||
| const fields = schemaPropertiesToFields(schema, ctx); | ||
| return { | ||
| kind: "model", | ||
| name, | ||
| fields, | ||
| description, | ||
| loc: LOC | ||
| }; | ||
| } | ||
| const typeNode = schemaToTypeNode(schema, ctx); | ||
| return { | ||
| kind: "model", | ||
| name, | ||
| fields: [], | ||
| type: typeNode, | ||
| description, | ||
| loc: LOC | ||
| }; | ||
| } | ||
| __name(schemaToModel, "schemaToModel"); | ||
| function schemaToTypeNode(schema, ctx) { | ||
| if (schema.$ref) { | ||
| const refName = extractRefName(schema.$ref); | ||
| if (refName) { | ||
| if (ctx.circularRefs.has(refName)) { | ||
| return { | ||
| kind: "lazy", | ||
| inner: { | ||
| kind: "ref", | ||
| name: refName | ||
| } | ||
| }; | ||
| } | ||
| return { | ||
| kind: "ref", | ||
| name: refName | ||
| }; | ||
| } | ||
| ctx.warnings.warn(ctx.path, `Unresolvable $ref: ${schema.$ref}`); | ||
| return { | ||
| kind: "scalar", | ||
| name: "unknown" | ||
| }; | ||
| } | ||
| if (schema.const !== void 0) { | ||
| return { | ||
| kind: "literal", | ||
| value: schema.const | ||
| }; | ||
| } | ||
| if (schema.enum) { | ||
| return { | ||
| kind: "enum", | ||
| values: schema.enum.map(String) | ||
| }; | ||
| } | ||
| if (schema.oneOf && schema.oneOf.length > 0) { | ||
| if (schema.discriminator?.propertyName) { | ||
| return toDiscriminatedUnion(schema.oneOf, schema.discriminator.propertyName, ctx); | ||
| } | ||
| return toUnion(schema.oneOf, ctx); | ||
| } | ||
| if (schema.anyOf && schema.anyOf.length > 0) { | ||
| if (schema.discriminator?.propertyName) { | ||
| return toDiscriminatedUnion(schema.anyOf, schema.discriminator.propertyName, ctx); | ||
| } | ||
| return toUnion(schema.anyOf, ctx); | ||
| } | ||
| if (schema.allOf && schema.allOf.length > 0) { | ||
| if (schema.allOf.length === 1) { | ||
| return schemaToTypeNode(schema.allOf[0], ctx); | ||
| } | ||
| return { | ||
| kind: "intersection", | ||
| members: schema.allOf.map((s) => schemaToTypeNode(s, ctx)) | ||
| }; | ||
| } | ||
| const types = normalizeTypeField(schema); | ||
| if (types === null) { | ||
| if (schema.properties) { | ||
| return schemaToInlineObject(schema, ctx); | ||
| } | ||
| return { | ||
| kind: "scalar", | ||
| name: "unknown" | ||
| }; | ||
| } | ||
| const { baseType, nullable } = types; | ||
| let typeNode; | ||
| switch (baseType) { | ||
| case "string": | ||
| typeNode = stringSchemaToType(schema); | ||
| break; | ||
| case "integer": | ||
| typeNode = integerSchemaToType(schema); | ||
| break; | ||
| case "number": | ||
| typeNode = numberSchemaToType(schema); | ||
| break; | ||
| case "boolean": | ||
| typeNode = { | ||
| kind: "scalar", | ||
| name: "boolean" | ||
| }; | ||
| break; | ||
| case "null": | ||
| typeNode = { | ||
| kind: "scalar", | ||
| name: "null" | ||
| }; | ||
| break; | ||
| case "array": | ||
| typeNode = arraySchemaToType(schema, ctx); | ||
| break; | ||
| case "object": | ||
| typeNode = objectSchemaToType(schema, ctx); | ||
| break; | ||
| default: | ||
| ctx.warnings.warn(ctx.path, `Unknown type: ${baseType}`); | ||
| typeNode = { | ||
| kind: "scalar", | ||
| name: "unknown" | ||
| }; | ||
| } | ||
| if (nullable) { | ||
| return { | ||
| kind: "union", | ||
| members: [ | ||
| typeNode, | ||
| { | ||
| kind: "scalar", | ||
| name: "null" | ||
| } | ||
| ] | ||
| }; | ||
| } | ||
| return typeNode; | ||
| } | ||
| __name(schemaToTypeNode, "schemaToTypeNode"); | ||
| function stringSchemaToType(schema) { | ||
| if (schema.format) { | ||
| const scalarName = FORMAT_TO_SCALAR[schema.format]; | ||
| if (scalarName) { | ||
| return { | ||
| kind: "scalar", | ||
| name: scalarName | ||
| }; | ||
| } | ||
| } | ||
| const mods = {}; | ||
| if (schema.minLength !== void 0 && schema.maxLength !== void 0 && schema.minLength === schema.maxLength) { | ||
| mods.len = schema.minLength; | ||
| } else { | ||
| if (schema.minLength !== void 0) mods.min = schema.minLength; | ||
| if (schema.maxLength !== void 0) mods.max = schema.maxLength; | ||
| } | ||
| if (schema.pattern) mods.regex = `/${schema.pattern}/`; | ||
| if (schema.format && !FORMAT_TO_SCALAR[schema.format]) mods.format = schema.format; | ||
| return { | ||
| kind: "scalar", | ||
| name: "string", | ||
| ...mods | ||
| }; | ||
| } | ||
| __name(stringSchemaToType, "stringSchemaToType"); | ||
| function integerSchemaToType(schema) { | ||
| const name = schema.format === "int64" ? "bigint" : "int"; | ||
| const mods = {}; | ||
| if (schema.minimum !== void 0) mods.min = schema.minimum; | ||
| if (schema.maximum !== void 0) mods.max = schema.maximum; | ||
| return { | ||
| kind: "scalar", | ||
| name, | ||
| ...mods | ||
| }; | ||
| } | ||
| __name(integerSchemaToType, "integerSchemaToType"); | ||
| function numberSchemaToType(schema) { | ||
| const mods = {}; | ||
| if (schema.minimum !== void 0) mods.min = schema.minimum; | ||
| if (schema.maximum !== void 0) mods.max = schema.maximum; | ||
| return { | ||
| kind: "scalar", | ||
| name: "number", | ||
| ...mods | ||
| }; | ||
| } | ||
| __name(numberSchemaToType, "numberSchemaToType"); | ||
| function arraySchemaToType(schema, ctx) { | ||
| if (schema.prefixItems && schema.prefixItems.length > 0) { | ||
| return { | ||
| kind: "tuple", | ||
| items: schema.prefixItems.map((s) => schemaToTypeNode(s, ctx)) | ||
| }; | ||
| } | ||
| const item = schema.items ? schemaToTypeNode(schema.items, ctx) : { | ||
| kind: "scalar", | ||
| name: "unknown" | ||
| }; | ||
| const mods = {}; | ||
| if (schema.minItems !== void 0) mods.min = schema.minItems; | ||
| if (schema.maxItems !== void 0) mods.max = schema.maxItems; | ||
| return { | ||
| kind: "array", | ||
| item, | ||
| ...mods | ||
| }; | ||
| } | ||
| __name(arraySchemaToType, "arraySchemaToType"); | ||
| function objectSchemaToType(schema, ctx) { | ||
| if (schema.additionalProperties && typeof schema.additionalProperties === "object" && !schema.properties) { | ||
| return { | ||
| kind: "record", | ||
| key: { | ||
| kind: "scalar", | ||
| name: "string" | ||
| }, | ||
| value: schemaToTypeNode(schema.additionalProperties, ctx) | ||
| }; | ||
| } | ||
| if (schema.properties) { | ||
| return schemaToInlineObject(schema, ctx); | ||
| } | ||
| return { | ||
| kind: "scalar", | ||
| name: "object" | ||
| }; | ||
| } | ||
| __name(objectSchemaToType, "objectSchemaToType"); | ||
| function schemaToInlineObject(schema, ctx) { | ||
| const fields = schemaPropertiesToFields(schema, ctx); | ||
| return { | ||
| kind: "inlineObject", | ||
| fields | ||
| }; | ||
| } | ||
| __name(schemaToInlineObject, "schemaToInlineObject"); | ||
| function schemaPropertiesToFields(schema, ctx) { | ||
| const properties = schema.properties ?? {}; | ||
| const required = new Set(schema.required ?? []); | ||
| const fields = []; | ||
| for (const [name, propSchema] of Object.entries(properties)) { | ||
| const propCtx = { | ||
| ...ctx, | ||
| path: `${ctx.path}/properties/${name}` | ||
| }; | ||
| const fieldType = schemaToTypeNode(propSchema, propCtx); | ||
| let nullable = false; | ||
| let effectiveType = fieldType; | ||
| if (fieldType.kind === "union") { | ||
| const nonNull = fieldType.members.filter((m) => !(m.kind === "scalar" && m.name === "null")); | ||
| if (nonNull.length < fieldType.members.length) { | ||
| nullable = true; | ||
| effectiveType = nonNull.length === 1 ? nonNull[0] : { | ||
| kind: "union", | ||
| members: nonNull | ||
| }; | ||
| } | ||
| } | ||
| const visibility = propSchema.readOnly ? "readonly" : propSchema.writeOnly ? "writeonly" : "normal"; | ||
| fields.push({ | ||
| name, | ||
| optional: !required.has(name), | ||
| nullable, | ||
| visibility, | ||
| type: effectiveType, | ||
| default: propSchema.default, | ||
| deprecated: propSchema.deprecated, | ||
| description: ctx.includeComments ? propSchema.description : void 0, | ||
| loc: LOC | ||
| }); | ||
| } | ||
| return fields; | ||
| } | ||
| __name(schemaPropertiesToFields, "schemaPropertiesToFields"); | ||
| function normalizeTypeField(schema) { | ||
| if (!schema.type) return null; | ||
| if (typeof schema.type === "string") { | ||
| return { | ||
| baseType: schema.type, | ||
| nullable: false | ||
| }; | ||
| } | ||
| if (Array.isArray(schema.type)) { | ||
| const types = schema.type; | ||
| const nonNull = types.filter((t) => t !== "null"); | ||
| const nullable = types.includes("null"); | ||
| if (nonNull.length === 1) { | ||
| return { | ||
| baseType: nonNull[0], | ||
| nullable | ||
| }; | ||
| } | ||
| if (nonNull.length === 0) { | ||
| return { | ||
| baseType: "null", | ||
| nullable: false | ||
| }; | ||
| } | ||
| return { | ||
| baseType: nonNull[0], | ||
| nullable | ||
| }; | ||
| } | ||
| return null; | ||
| } | ||
| __name(normalizeTypeField, "normalizeTypeField"); | ||
| function toUnion(schemas, ctx) { | ||
| const members = schemas.map((s) => schemaToTypeNode(s, ctx)); | ||
| if (members.length === 1) return members[0]; | ||
| return { | ||
| kind: "union", | ||
| members | ||
| }; | ||
| } | ||
| __name(toUnion, "toUnion"); | ||
| function toDiscriminatedUnion(schemas, discriminator, ctx) { | ||
| const members = schemas.map((s) => schemaToTypeNode(s, ctx)); | ||
| if (members.length === 1) return members[0]; | ||
| return { | ||
| kind: "discriminatedUnion", | ||
| discriminator, | ||
| members | ||
| }; | ||
| } | ||
| __name(toDiscriminatedUnion, "toDiscriminatedUnion"); | ||
| function warnUnsupported(schema, ctx) { | ||
| if (schema.xml) ctx.warnings.warn(ctx.path, "xml metadata is not supported, skipping"); | ||
| if (schema.externalDocs) ctx.warnings.info(ctx.path, "externalDocs is not supported, skipping"); | ||
| if (schema.not) ctx.warnings.warn(ctx.path, "not keyword is not supported, skipping"); | ||
| } | ||
| __name(warnUnsupported, "warnUnsupported"); | ||
| function extractInlineModel(schema, suggestedName, ctx) { | ||
| if (schema.$ref) { | ||
| return { | ||
| typeNode: schemaToTypeNode(schema, ctx) | ||
| }; | ||
| } | ||
| if (schema.properties || schema.type === "object" && schema.additionalProperties === void 0) { | ||
| const fields = schemaPropertiesToFields(schema, ctx); | ||
| const model = { | ||
| kind: "model", | ||
| name: suggestedName, | ||
| fields, | ||
| description: ctx.includeComments ? schema.description : void 0, | ||
| loc: LOC | ||
| }; | ||
| return { | ||
| typeNode: { | ||
| kind: "ref", | ||
| name: suggestedName | ||
| }, | ||
| model | ||
| }; | ||
| } | ||
| return { | ||
| typeNode: schemaToTypeNode(schema, ctx) | ||
| }; | ||
| } | ||
| __name(extractInlineModel, "extractInlineModel"); | ||
| function sanitizeName(name, warnings) { | ||
| const cleaned = name.replace(/[^a-zA-Z0-9_$]/g, " ").split(/\s+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(""); | ||
| if (cleaned !== name) { | ||
| warnings.info(`#/components/schemas/${name}`, `Schema name sanitized: "${name}" \u2192 "${cleaned}"`); | ||
| } | ||
| return cleaned || "UnnamedSchema"; | ||
| } | ||
| __name(sanitizeName, "sanitizeName"); | ||
| // src/paths-to-ast.ts | ||
| var LOC2 = { | ||
| file: "", | ||
| line: 0 | ||
| }; | ||
| var HTTP_METHODS = [ | ||
| "get", | ||
| "post", | ||
| "put", | ||
| "patch", | ||
| "delete" | ||
| ]; | ||
| function pathsToRoutes(doc, ctx) { | ||
| const routes = []; | ||
| const routeTags = /* @__PURE__ */ new Map(); | ||
| const paths = doc.paths ?? {}; | ||
| for (const [path, pathItem] of Object.entries(paths)) { | ||
| if (!pathItem) continue; | ||
| const result = pathItemToRoute(path, pathItem, ctx); | ||
| if (result) { | ||
| routes.push(result.route); | ||
| routeTags.set(result.route, result.tag); | ||
| } | ||
| } | ||
| return { | ||
| routes, | ||
| routeTags | ||
| }; | ||
| } | ||
| __name(pathsToRoutes, "pathsToRoutes"); | ||
| function pathItemToRoute(path, pathItem, ctx) { | ||
| const operations = []; | ||
| let primaryTag = "default"; | ||
| const pathParams = (pathItem.parameters ?? []).filter((p) => p.in === "path"); | ||
| for (const method of HTTP_METHODS) { | ||
| const op = pathItem[method]; | ||
| if (!op) continue; | ||
| const opNode = operationToNode(method, op, path, ctx); | ||
| operations.push(opNode); | ||
| if (op.tags && op.tags.length > 0 && primaryTag === "default") { | ||
| primaryTag = op.tags[0]; | ||
| } | ||
| } | ||
| if (operations.length === 0) return null; | ||
| const params = buildPathParams(path, pathParams, pathItem, ctx); | ||
| const route = { | ||
| path, | ||
| operations, | ||
| loc: LOC2 | ||
| }; | ||
| if (params.length > 0) { | ||
| route.params = { | ||
| kind: "params", | ||
| nodes: params | ||
| }; | ||
| } | ||
| if (pathItem.description && ctx.includeComments) { | ||
| route.description = pathItem.description; | ||
| } | ||
| return { | ||
| route, | ||
| tag: primaryTag | ||
| }; | ||
| } | ||
| __name(pathItemToRoute, "pathItemToRoute"); | ||
| function operationToNode(method, op, path, ctx) { | ||
| const pathPrefix = `#/paths/${encodePathSegment2(path)}/${method}`; | ||
| const schemaCtx = makeSchemaCtx(ctx, pathPrefix); | ||
| const node = { | ||
| method, | ||
| responses: [], | ||
| loc: LOC2 | ||
| }; | ||
| if (op.operationId) { | ||
| node.sdk = op.operationId; | ||
| } | ||
| if (op.description && ctx.includeComments) { | ||
| node.description = op.description; | ||
| } | ||
| if (op.deprecated) { | ||
| node.modifiers = [ | ||
| "deprecated" | ||
| ]; | ||
| } | ||
| const queryParams = []; | ||
| const headerParams = []; | ||
| for (const param of op.parameters ?? []) { | ||
| if (param.in === "query") { | ||
| queryParams.push(parameterToNode(param, schemaCtx)); | ||
| } else if (param.in === "header") { | ||
| headerParams.push(parameterToNode(param, schemaCtx)); | ||
| } | ||
| } | ||
| if (queryParams.length > 0) { | ||
| node.query = { | ||
| kind: "params", | ||
| nodes: queryParams | ||
| }; | ||
| } | ||
| if (headerParams.length > 0) { | ||
| node.headers = { | ||
| kind: "params", | ||
| nodes: headerParams | ||
| }; | ||
| } | ||
| if (op.requestBody) { | ||
| node.request = requestBodyToNode(op.requestBody, op.operationId ?? `${method}${toPascalCase(path)}`, schemaCtx, ctx); | ||
| } | ||
| const responses = op.responses ?? {}; | ||
| for (const [code, resp] of Object.entries(responses)) { | ||
| const statusCode = parseInt(code, 10); | ||
| if (isNaN(statusCode)) continue; | ||
| const respNode = responseToNode(statusCode, resp, op.operationId ?? `${method}${toPascalCase(path)}`, schemaCtx, ctx); | ||
| node.responses.push(respNode); | ||
| } | ||
| if (op.security !== void 0) { | ||
| node.security = convertSecurity(op.security); | ||
| } | ||
| return node; | ||
| } | ||
| __name(operationToNode, "operationToNode"); | ||
| function buildPathParams(path, pathLevelParams, pathItem, ctx) { | ||
| const schemaCtx = makeSchemaCtx(ctx, `#/paths/${encodePathSegment2(path)}`); | ||
| const paramMap = /* @__PURE__ */ new Map(); | ||
| for (const p of pathLevelParams) { | ||
| paramMap.set(p.name, p); | ||
| } | ||
| for (const method of HTTP_METHODS) { | ||
| const op = pathItem[method]; | ||
| if (!op?.parameters) continue; | ||
| for (const p of op.parameters) { | ||
| if (p.in === "path" && !paramMap.has(p.name)) { | ||
| paramMap.set(p.name, p); | ||
| } | ||
| } | ||
| } | ||
| const templateNames = [ | ||
| ...path.matchAll(/\{([^}]+)\}/g) | ||
| ].map((m) => m[1]); | ||
| return templateNames.map((name) => { | ||
| const param = paramMap.get(name); | ||
| if (param) { | ||
| return parameterToNode(param, schemaCtx); | ||
| } | ||
| return { | ||
| name, | ||
| optional: false, | ||
| nullable: false, | ||
| type: { | ||
| kind: "scalar", | ||
| name: "string" | ||
| }, | ||
| loc: LOC2 | ||
| }; | ||
| }); | ||
| } | ||
| __name(buildPathParams, "buildPathParams"); | ||
| function parameterToNode(param, ctx) { | ||
| const type = param.schema ? schemaToTypeNode(param.schema, ctx) : { | ||
| kind: "scalar", | ||
| name: "string" | ||
| }; | ||
| return { | ||
| name: param.name, | ||
| optional: param.in !== "path" && !param.required, | ||
| nullable: false, | ||
| type, | ||
| description: ctx.includeComments ? param.description : void 0, | ||
| loc: LOC2 | ||
| }; | ||
| } | ||
| __name(parameterToNode, "parameterToNode"); | ||
| function requestBodyToNode(reqBody, operationName, schemaCtx, ctx) { | ||
| const content = reqBody.content; | ||
| if (!content) return void 0; | ||
| const supported = /* @__PURE__ */ new Set([ | ||
| "application/json", | ||
| "application/x-www-form-urlencoded", | ||
| "multipart/form-data" | ||
| ]); | ||
| const bodies = []; | ||
| for (const [contentType, mediaType] of Object.entries(content)) { | ||
| if (!supported.has(contentType) || !mediaType?.schema) continue; | ||
| const { typeNode, model } = extractInlineModel(mediaType.schema, `${toPascalCase(operationName)}Request`, schemaCtx); | ||
| if (model) { | ||
| ctx.extractedModels.push(model); | ||
| } | ||
| bodies.push({ | ||
| contentType, | ||
| bodyType: typeNode | ||
| }); | ||
| } | ||
| if (bodies.length === 0) return void 0; | ||
| return { | ||
| bodies | ||
| }; | ||
| } | ||
| __name(requestBodyToNode, "requestBodyToNode"); | ||
| function responseToNode(statusCode, resp, operationName, schemaCtx, ctx) { | ||
| const headers = convertResponseHeaders(resp.headers, schemaCtx); | ||
| if (!resp.content) { | ||
| return headers ? { | ||
| statusCode, | ||
| headers | ||
| } : { | ||
| statusCode | ||
| }; | ||
| } | ||
| const [contentType, mediaType] = Object.entries(resp.content)[0] ?? []; | ||
| if (!contentType || !mediaType?.schema) { | ||
| return headers ? { | ||
| statusCode, | ||
| headers | ||
| } : { | ||
| statusCode | ||
| }; | ||
| } | ||
| const { typeNode, model } = extractInlineModel(mediaType.schema, `${toPascalCase(operationName)}Response${statusCode}`, schemaCtx); | ||
| if (model) { | ||
| ctx.extractedModels.push(model); | ||
| } | ||
| return { | ||
| statusCode, | ||
| contentType, | ||
| bodyType: typeNode, | ||
| ...headers ? { | ||
| headers | ||
| } : {} | ||
| }; | ||
| } | ||
| __name(responseToNode, "responseToNode"); | ||
| function convertResponseHeaders(headers, schemaCtx) { | ||
| if (!headers) return void 0; | ||
| const out = []; | ||
| for (const [name, header] of Object.entries(headers)) { | ||
| if (!header) continue; | ||
| const type = header.schema ? schemaToTypeNode(header.schema, schemaCtx) : { | ||
| kind: "scalar", | ||
| name: "string" | ||
| }; | ||
| out.push({ | ||
| name, | ||
| optional: !header.required, | ||
| type, | ||
| description: schemaCtx.includeComments ? header.description : void 0 | ||
| }); | ||
| } | ||
| return out.length > 0 ? out : void 0; | ||
| } | ||
| __name(convertResponseHeaders, "convertResponseHeaders"); | ||
| function convertSecurity(security) { | ||
| if (security.length === 0) { | ||
| return "none"; | ||
| } | ||
| return { | ||
| loc: LOC2 | ||
| }; | ||
| } | ||
| __name(convertSecurity, "convertSecurity"); | ||
| function makeSchemaCtx(ctx, path) { | ||
| return { | ||
| circularRefs: ctx.circularRefs, | ||
| warnings: ctx.warnings, | ||
| path, | ||
| includeComments: ctx.includeComments, | ||
| namedSchemas: ctx.namedSchemas, | ||
| extractedModels: ctx.extractedModels, | ||
| inlineCounter: 0 | ||
| }; | ||
| } | ||
| __name(makeSchemaCtx, "makeSchemaCtx"); | ||
| function toPascalCase(input) { | ||
| return input.replace(/[^a-zA-Z0-9]/g, " ").split(/\s+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(""); | ||
| } | ||
| __name(toPascalCase, "toPascalCase"); | ||
| function encodePathSegment2(s) { | ||
| return s.replace(/~/g, "~0").replace(/\//g, "~1"); | ||
| } | ||
| __name(encodePathSegment2, "encodePathSegment"); | ||
| // src/tag-splitter.ts | ||
| function splitByTag(models, routes, routeTags) { | ||
| const routesByTag = /* @__PURE__ */ new Map(); | ||
| for (const route of routes) { | ||
| const tag = routeTags.get(route) ?? "default"; | ||
| const group = routesByTag.get(tag) ?? []; | ||
| group.push(route); | ||
| routesByTag.set(tag, group); | ||
| } | ||
| const modelsByTag = /* @__PURE__ */ new Map(); | ||
| for (const [tag, tagRoutes] of routesByTag) { | ||
| const refs = /* @__PURE__ */ new Set(); | ||
| for (const route of tagRoutes) { | ||
| collectRouteRefs(route, refs); | ||
| } | ||
| modelsByTag.set(tag, refs); | ||
| } | ||
| const modelNameToModel = new Map(models.map((m) => [ | ||
| m.name, | ||
| m | ||
| ])); | ||
| const modelAssignment = /* @__PURE__ */ new Map(); | ||
| for (const model of models) { | ||
| const tags = []; | ||
| for (const [tag, refs] of modelsByTag) { | ||
| if (refs.has(model.name)) { | ||
| tags.push(tag); | ||
| } | ||
| } | ||
| if (tags.length === 0) { | ||
| modelAssignment.set(model.name, "shared"); | ||
| } else if (tags.length === 1) { | ||
| modelAssignment.set(model.name, tags[0]); | ||
| } else { | ||
| modelAssignment.set(model.name, "shared"); | ||
| } | ||
| } | ||
| for (const model of models) { | ||
| if (modelAssignment.get(model.name) === "shared") { | ||
| const refs = /* @__PURE__ */ new Set(); | ||
| collectModelRefs(model, refs); | ||
| for (const ref of refs) { | ||
| if (modelNameToModel.has(ref)) { | ||
| const currentTag = modelAssignment.get(ref); | ||
| if (currentTag && currentTag !== "shared") { | ||
| const otherTags = [ | ||
| ...modelsByTag.entries() | ||
| ].filter(([t, r]) => t !== currentTag && r.has(ref)).map(([t]) => t); | ||
| if (otherTags.length > 0) { | ||
| modelAssignment.set(ref, "shared"); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| const result = /* @__PURE__ */ new Map(); | ||
| const allTags = /* @__PURE__ */ new Set([ | ||
| ...routesByTag.keys(), | ||
| ...new Set(modelAssignment.values()) | ||
| ]); | ||
| for (const tag of allTags) { | ||
| const tagModels = models.filter((m) => modelAssignment.get(m.name) === tag); | ||
| const tagRoutes = routesByTag.get(tag) ?? []; | ||
| if (tagModels.length === 0 && tagRoutes.length === 0) continue; | ||
| const filename = sanitizeFilename(tag); | ||
| result.set(`${filename}.ck`, { | ||
| kind: "ckRoot", | ||
| meta: tag !== "shared" ? { | ||
| area: tag | ||
| } : {}, | ||
| services: {}, | ||
| models: tagModels, | ||
| routes: tagRoutes, | ||
| file: `${filename}.ck` | ||
| }); | ||
| } | ||
| return result; | ||
| } | ||
| __name(splitByTag, "splitByTag"); | ||
| function mergeIntoSingle(models, routes, filename = "api") { | ||
| return { | ||
| kind: "ckRoot", | ||
| meta: {}, | ||
| services: {}, | ||
| models, | ||
| routes, | ||
| file: `${filename}.ck` | ||
| }; | ||
| } | ||
| __name(mergeIntoSingle, "mergeIntoSingle"); | ||
| function collectRouteRefs(route, refs) { | ||
| if (route.params) { | ||
| collectParamSourceRefs(route.params, refs); | ||
| } | ||
| for (const op of route.operations) { | ||
| if (op.query) collectParamSourceRefs(op.query, refs); | ||
| if (op.headers) collectParamSourceRefs(op.headers, refs); | ||
| if (op.request) { | ||
| for (const body of op.request.bodies) collectTypeRefs(body.bodyType, refs); | ||
| } | ||
| for (const resp of op.responses) { | ||
| if (resp.bodyType) collectTypeRefs(resp.bodyType, refs); | ||
| } | ||
| } | ||
| } | ||
| __name(collectRouteRefs, "collectRouteRefs"); | ||
| function collectParamSourceRefs(source, refs) { | ||
| if (typeof source === "string") { | ||
| refs.add(source); | ||
| return; | ||
| } | ||
| if (Array.isArray(source)) { | ||
| for (const param of source) { | ||
| if (param && typeof param === "object" && "type" in param) { | ||
| collectTypeRefs(param.type, refs); | ||
| } | ||
| } | ||
| return; | ||
| } | ||
| if (source && typeof source === "object" && "kind" in source) { | ||
| collectTypeRefs(source, refs); | ||
| } | ||
| } | ||
| __name(collectParamSourceRefs, "collectParamSourceRefs"); | ||
| function collectTypeRefs(type, refs) { | ||
| switch (type.kind) { | ||
| case "ref": | ||
| refs.add(type.name); | ||
| break; | ||
| case "array": | ||
| collectTypeRefs(type.item, refs); | ||
| break; | ||
| case "tuple": | ||
| for (const item of type.items) collectTypeRefs(item, refs); | ||
| break; | ||
| case "record": | ||
| collectTypeRefs(type.key, refs); | ||
| collectTypeRefs(type.value, refs); | ||
| break; | ||
| case "union": | ||
| case "discriminatedUnion": | ||
| case "intersection": | ||
| for (const member of type.members) collectTypeRefs(member, refs); | ||
| break; | ||
| case "inlineObject": | ||
| for (const field of type.fields) collectTypeRefs(field.type, refs); | ||
| break; | ||
| case "lazy": | ||
| collectTypeRefs(type.inner, refs); | ||
| break; | ||
| } | ||
| } | ||
| __name(collectTypeRefs, "collectTypeRefs"); | ||
| function collectModelRefs(model, refs) { | ||
| if (model.bases) for (const b of model.bases) refs.add(b); | ||
| if (model.type) collectTypeRefs(model.type, refs); | ||
| for (const field of model.fields) { | ||
| collectTypeRefs(field.type, refs); | ||
| } | ||
| } | ||
| __name(collectModelRefs, "collectModelRefs"); | ||
| function sanitizeFilename(tag) { | ||
| return tag.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "") || "default"; | ||
| } | ||
| __name(sanitizeFilename, "sanitizeFilename"); | ||
| // src/ast-to-ck.ts | ||
| var INDENT = " "; | ||
| function astToCk(root, options = {}) { | ||
| const { includeComments = true } = options; | ||
| const ctx = { | ||
| includeComments | ||
| }; | ||
| const parts = []; | ||
| const optionsBlock = serializeOptions(root); | ||
| if (optionsBlock) parts.push(optionsBlock); | ||
| for (const model of root.models) { | ||
| parts.push(serializeModel(model, ctx)); | ||
| } | ||
| for (const route of root.routes) { | ||
| parts.push(serializeRoute(route, ctx)); | ||
| } | ||
| return parts.join("\n\n") + "\n"; | ||
| } | ||
| __name(astToCk, "astToCk"); | ||
| function serializeOptions(root) { | ||
| const hasKeys = Object.keys(root.meta).length > 0; | ||
| const hasServices = root.services && Object.keys(root.services).length > 0; | ||
| const hasSecurity = root.security !== void 0; | ||
| if (!hasKeys && !hasServices && !hasSecurity) return null; | ||
| const lines = [ | ||
| "options {" | ||
| ]; | ||
| if (hasKeys) { | ||
| lines.push(`${INDENT}keys: {`); | ||
| for (const [key, value] of Object.entries(root.meta)) { | ||
| lines.push(`${INDENT}${INDENT}${key}: ${value}`); | ||
| } | ||
| lines.push(`${INDENT}}`); | ||
| } | ||
| if (hasServices) { | ||
| lines.push(`${INDENT}services: {`); | ||
| for (const [name, path] of Object.entries(root.services)) { | ||
| lines.push(`${INDENT}${INDENT}${name}: "${path}"`); | ||
| } | ||
| lines.push(`${INDENT}}`); | ||
| } | ||
| if (hasSecurity) { | ||
| lines.push(`${INDENT}security: {`); | ||
| if (root.security === "none") { | ||
| lines.push(`${INDENT}${INDENT}none`); | ||
| } else { | ||
| const sec = root.security; | ||
| if (sec.requireMfa !== void 0) { | ||
| lines.push(`${INDENT}${INDENT}requireMfa: ${sec.requireMfa}`); | ||
| } | ||
| } | ||
| lines.push(`${INDENT}}`); | ||
| } | ||
| lines.push("}"); | ||
| return lines.join("\n"); | ||
| } | ||
| __name(serializeOptions, "serializeOptions"); | ||
| function serializeModel(model, ctx) { | ||
| const parts = []; | ||
| const prefixes = []; | ||
| if (model.inputCase && model.inputCase !== "camel") { | ||
| prefixes.push(`format(input=${model.inputCase})`); | ||
| } | ||
| if (model.mode && model.mode !== "strict") { | ||
| prefixes.push(`mode(${model.mode})`); | ||
| } | ||
| if (model.deprecated) { | ||
| prefixes.push("deprecated"); | ||
| } | ||
| const prefix = prefixes.length > 0 ? prefixes.join(" ") + " " : ""; | ||
| const comment = ctx.includeComments && model.description ? ` # ${model.description}` : ""; | ||
| if (model.type) { | ||
| parts.push(`contract ${prefix}${model.name}: ${serializeType(model.type)}${comment}`); | ||
| return parts.join(""); | ||
| } | ||
| if (model.bases && model.bases.length > 0) { | ||
| parts.push(`contract ${prefix}${model.name}: ${model.bases.join(" & ")} & {${comment}`); | ||
| } else { | ||
| parts.push(`contract ${prefix}${model.name}: {${comment}`); | ||
| } | ||
| for (const field of model.fields) { | ||
| parts.push(serializeField(field, 1, ctx)); | ||
| } | ||
| parts.push("}"); | ||
| return parts.join("\n"); | ||
| } | ||
| __name(serializeModel, "serializeModel"); | ||
| function serializeField(field, depth, ctx) { | ||
| const indent = INDENT.repeat(depth); | ||
| const optional = field.optional ? "?" : ""; | ||
| const visibility = field.visibility !== "normal" ? `${field.visibility} ` : ""; | ||
| const deprecated = field.deprecated ? "deprecated " : ""; | ||
| let typeStr = serializeType(field.type); | ||
| if (field.nullable && !typeContainsNull(field.type)) { | ||
| typeStr = `${typeStr} | null`; | ||
| } | ||
| const defaultVal = field.default !== void 0 ? ` = ${serializeDefault(field.default)}` : ""; | ||
| const comment = ctx.includeComments && field.description ? ` # ${field.description}` : ""; | ||
| return `${indent}${field.name}${optional}: ${deprecated}${visibility}${typeStr}${defaultVal}${comment}`; | ||
| } | ||
| __name(serializeField, "serializeField"); | ||
| function typeContainsNull(type) { | ||
| if (type.kind === "scalar" && type.name === "null") return true; | ||
| if (type.kind === "union") return type.members.some(typeContainsNull); | ||
| return false; | ||
| } | ||
| __name(typeContainsNull, "typeContainsNull"); | ||
| function serializeDefault(value) { | ||
| if (typeof value === "string") { | ||
| if (/^[a-zA-Z_$][a-zA-Z0-9_$\-.]*$/.test(value)) return value; | ||
| return `"${value}"`; | ||
| } | ||
| return String(value); | ||
| } | ||
| __name(serializeDefault, "serializeDefault"); | ||
| function serializeType(type) { | ||
| switch (type.kind) { | ||
| case "scalar": | ||
| return serializeScalar(type); | ||
| case "array": | ||
| return serializeArray(type); | ||
| case "tuple": | ||
| return `tuple(${type.items.map(serializeType).join(", ")})`; | ||
| case "record": | ||
| return `record(${serializeType(type.key)}, ${serializeType(type.value)})`; | ||
| case "enum": | ||
| return `enum(${type.values.join(", ")})`; | ||
| case "literal": | ||
| return serializeLiteral(type); | ||
| case "union": | ||
| return type.members.map(serializeType).join(" | "); | ||
| case "discriminatedUnion": | ||
| return `discriminated(by=${type.discriminator}, ${type.members.map(serializeType).join(" | ")})`; | ||
| case "intersection": | ||
| return type.members.map(serializeType).join(" & "); | ||
| case "ref": | ||
| return type.name; | ||
| case "inlineObject": | ||
| return serializeInlineObject(type); | ||
| case "lazy": | ||
| return `lazy(${serializeType(type.inner)})`; | ||
| } | ||
| } | ||
| __name(serializeType, "serializeType"); | ||
| function serializeScalar(type) { | ||
| const args = []; | ||
| if (type.len !== void 0) args.push(`length=${type.len}`); | ||
| if (type.min !== void 0) args.push(typeof type.min === "string" ? `min="${type.min}"` : `min=${type.min}`); | ||
| if (type.max !== void 0) args.push(typeof type.max === "string" ? `max="${type.max}"` : `max=${type.max}`); | ||
| if (type.regex !== void 0) args.push(`regex=${type.regex}`); | ||
| if (type.format !== void 0) args.push(`format=${type.format}`); | ||
| if (args.length === 0) return type.name; | ||
| return `${type.name}(${args.join(", ")})`; | ||
| } | ||
| __name(serializeScalar, "serializeScalar"); | ||
| function serializeArray(type) { | ||
| const args = [ | ||
| serializeType(type.item) | ||
| ]; | ||
| if (type.min !== void 0) args.push(`min=${type.min}`); | ||
| if (type.max !== void 0) args.push(`max=${type.max}`); | ||
| return `array(${args.join(", ")})`; | ||
| } | ||
| __name(serializeArray, "serializeArray"); | ||
| function serializeLiteral(type) { | ||
| if (typeof type.value === "string") return `literal("${type.value}")`; | ||
| return `literal(${type.value})`; | ||
| } | ||
| __name(serializeLiteral, "serializeLiteral"); | ||
| function serializeInlineObject(type) { | ||
| const modePrefix = type.mode ? `mode(${type.mode}) ` : ""; | ||
| if (type.fields.length === 0) return `${modePrefix}{}`; | ||
| const lines = [ | ||
| `${modePrefix}{` | ||
| ]; | ||
| for (const field of type.fields) { | ||
| lines.push(serializeField(field, 2, { | ||
| includeComments: true | ||
| })); | ||
| } | ||
| lines.push(`${INDENT}}`); | ||
| return lines.join("\n"); | ||
| } | ||
| __name(serializeInlineObject, "serializeInlineObject"); | ||
| function serializeRoute(route, ctx) { | ||
| const lines = []; | ||
| const modStr = serializeModifiers(route.modifiers); | ||
| const comment = ctx.includeComments && route.description ? ` # ${route.description}` : ""; | ||
| lines.push(`operation${modStr} ${route.path}: {${comment}`); | ||
| if (route.params) { | ||
| serializeParamSource(lines, "params", route.params, route.paramsMode, 1, ctx); | ||
| } | ||
| if (route.security !== void 0) { | ||
| serializeSecurityBlock(lines, route.security, 1, ctx); | ||
| } | ||
| for (const op of route.operations) { | ||
| serializeOperation(lines, op, 1, ctx); | ||
| } | ||
| lines.push("}"); | ||
| return lines.join("\n"); | ||
| } | ||
| __name(serializeRoute, "serializeRoute"); | ||
| function serializeOperation(lines, op, depth, ctx) { | ||
| const indent = INDENT.repeat(depth); | ||
| const modStr = serializeModifiers(op.modifiers); | ||
| const comment = ctx.includeComments && op.description ? ` # ${op.description}` : ""; | ||
| lines.push(`${indent}${op.method}${modStr}: {${comment}`); | ||
| const inner = INDENT.repeat(depth + 1); | ||
| if (op.service) { | ||
| lines.push(`${inner}service: ${op.service}`); | ||
| } | ||
| if (op.sdk) { | ||
| lines.push(`${inner}sdk: ${op.sdk}`); | ||
| } | ||
| if (op.signature) { | ||
| const sigComment = ctx.includeComments && op.signatureDescription ? ` # ${op.signatureDescription}` : ""; | ||
| lines.push(`${inner}signature: ${op.signature}${sigComment}`); | ||
| } | ||
| if (op.security !== void 0) { | ||
| serializeSecurityBlock(lines, op.security, depth + 1, ctx); | ||
| } | ||
| if (op.query) { | ||
| serializeParamSource(lines, "query", op.query, op.queryMode, depth + 1, ctx); | ||
| } | ||
| if (op.headers) { | ||
| serializeParamSource(lines, "headers", op.headers, op.headersMode, depth + 1, ctx); | ||
| } | ||
| if (op.request) { | ||
| serializeRequest(lines, op.request, depth + 1); | ||
| } | ||
| if (op.responses.length > 0) { | ||
| serializeResponses(lines, op.responses, depth + 1); | ||
| } | ||
| lines.push(`${indent}}`); | ||
| return lines; | ||
| } | ||
| __name(serializeOperation, "serializeOperation"); | ||
| function serializeModifiers(modifiers) { | ||
| if (!modifiers || modifiers.length === 0) return ""; | ||
| return `(${modifiers.join(", ")})`; | ||
| } | ||
| __name(serializeModifiers, "serializeModifiers"); | ||
| function serializeParamSource(lines, keyword, source, mode, depth, ctx) { | ||
| const indent = INDENT.repeat(depth); | ||
| if (source.kind === "ref") { | ||
| lines.push(`${indent}${keyword}: ${source.name}`); | ||
| return; | ||
| } | ||
| if (source.kind === "type") { | ||
| lines.push(`${indent}${keyword}: ${serializeType(source.node)}`); | ||
| return; | ||
| } | ||
| const modeStr = mode ? `mode(${mode}) ` : ""; | ||
| lines.push(`${indent}${keyword}: ${modeStr}{`); | ||
| for (const param of source.nodes) { | ||
| const optional = param.optional ? "?" : ""; | ||
| let typeStr = serializeType(param.type); | ||
| if (param.nullable && !typeContainsNull(param.type)) { | ||
| typeStr = `${typeStr} | null`; | ||
| } | ||
| const defaultVal = param.default !== void 0 ? ` = ${serializeDefault(param.default)}` : ""; | ||
| const comment = ctx.includeComments && param.description ? ` # ${param.description}` : ""; | ||
| lines.push(`${INDENT.repeat(depth + 1)}${param.name}${optional}: ${typeStr}${defaultVal}${comment}`); | ||
| } | ||
| lines.push(`${indent}}`); | ||
| } | ||
| __name(serializeParamSource, "serializeParamSource"); | ||
| function serializeRequest(lines, request, depth) { | ||
| const indent = INDENT.repeat(depth); | ||
| lines.push(`${indent}request: {`); | ||
| for (const body of request.bodies) { | ||
| lines.push(`${INDENT.repeat(depth + 1)}${body.contentType}: ${serializeType(body.bodyType)}`); | ||
| } | ||
| lines.push(`${indent}}`); | ||
| } | ||
| __name(serializeRequest, "serializeRequest"); | ||
| function serializeResponses(lines, responses, depth) { | ||
| const indent = INDENT.repeat(depth); | ||
| lines.push(`${indent}response: {`); | ||
| for (const resp of responses) { | ||
| const hasBody = resp.bodyType && resp.contentType; | ||
| const hasHeaders = resp.headers && resp.headers.length > 0; | ||
| if (hasBody || hasHeaders) { | ||
| lines.push(`${INDENT.repeat(depth + 1)}${resp.statusCode}: {`); | ||
| if (hasBody) { | ||
| lines.push(`${INDENT.repeat(depth + 2)}${resp.contentType}: ${serializeType(resp.bodyType)}`); | ||
| } | ||
| if (hasHeaders) { | ||
| lines.push(`${INDENT.repeat(depth + 2)}headers: {`); | ||
| for (const h of resp.headers) { | ||
| const opt = h.optional ? "?" : ""; | ||
| const trail = h.description ? ` # ${h.description}` : ""; | ||
| lines.push(`${INDENT.repeat(depth + 3)}${h.name}${opt}: ${serializeType(h.type)}${trail}`); | ||
| } | ||
| lines.push(`${INDENT.repeat(depth + 2)}}`); | ||
| } | ||
| lines.push(`${INDENT.repeat(depth + 1)}}`); | ||
| } else { | ||
| lines.push(`${INDENT.repeat(depth + 1)}${resp.statusCode}:`); | ||
| } | ||
| } | ||
| lines.push(`${indent}}`); | ||
| } | ||
| __name(serializeResponses, "serializeResponses"); | ||
| function serializeSecurityBlock(lines, security, depth, ctx) { | ||
| const indent = INDENT.repeat(depth); | ||
| if (security === "none") { | ||
| lines.push(`${indent}security: none`); | ||
| return; | ||
| } | ||
| const sec = security; | ||
| if (sec.requireMfa !== void 0) { | ||
| const comment = ctx.includeComments && sec.requireMfaDescription ? ` # ${sec.requireMfaDescription}` : ""; | ||
| lines.push(`${indent}security: {`); | ||
| lines.push(`${INDENT.repeat(depth + 1)}requireMfa: ${sec.requireMfa}${comment}`); | ||
| lines.push(`${indent}}`); | ||
| } else { | ||
| lines.push(`${indent}security: {}`); | ||
| } | ||
| } | ||
| __name(serializeSecurityBlock, "serializeSecurityBlock"); | ||
| // src/convert.ts | ||
| import { readFileSync } from "fs"; | ||
| import { parse as parseYaml } from "yaml"; | ||
| // src/warnings.ts | ||
| var WarningCollector = class { | ||
| static { | ||
| __name(this, "WarningCollector"); | ||
| } | ||
| warnings = []; | ||
| onWarning; | ||
| constructor(onWarning) { | ||
| this.onWarning = onWarning; | ||
| } | ||
| warn(path, message) { | ||
| this.add({ | ||
| path, | ||
| message, | ||
| severity: "warn" | ||
| }); | ||
| } | ||
| info(path, message) { | ||
| this.add({ | ||
| path, | ||
| message, | ||
| severity: "info" | ||
| }); | ||
| } | ||
| add(warning) { | ||
| this.warnings.push(warning); | ||
| this.onWarning?.(warning); | ||
| } | ||
| }; | ||
| // src/convert.ts | ||
| async function convertOpenApiToCk(options) { | ||
| const { split = "by-tag", includeComments = true } = options; | ||
| const warnings = new WarningCollector(options.onWarning); | ||
| const rawDoc = await parseInput(options.input); | ||
| const doc = normalize(rawDoc, warnings); | ||
| const schemas = sanitizeSchemaNames(doc, warnings); | ||
| const circularRefs = detectCircularRefs(schemas); | ||
| const extractedModels = []; | ||
| const schemaCtx = { | ||
| circularRefs, | ||
| warnings, | ||
| path: "#/components/schemas", | ||
| includeComments, | ||
| namedSchemas: schemas, | ||
| extractedModels, | ||
| inlineCounter: 0 | ||
| }; | ||
| const models = schemasToModels(schemas, schemaCtx); | ||
| const { routes, routeTags } = pathsToRoutes(doc, { | ||
| circularRefs, | ||
| warnings, | ||
| includeComments, | ||
| namedSchemas: schemas, | ||
| extractedModels, | ||
| globalSecurity: doc.security | ||
| }); | ||
| const files = /* @__PURE__ */ new Map(); | ||
| if (split === "by-tag") { | ||
| const ckRoots = splitByTag(models, routes, routeTags); | ||
| for (const [filename, root] of ckRoots) { | ||
| files.set(filename, astToCk(root, { | ||
| includeComments | ||
| })); | ||
| } | ||
| } else { | ||
| const root = mergeIntoSingle(models, routes); | ||
| files.set("api.ck", astToCk(root, { | ||
| includeComments | ||
| })); | ||
| } | ||
| return { | ||
| files, | ||
| warnings: warnings.warnings | ||
| }; | ||
| } | ||
| __name(convertOpenApiToCk, "convertOpenApiToCk"); | ||
| async function parseInput(input) { | ||
| if (typeof input === "object") { | ||
| return input; | ||
| } | ||
| try { | ||
| const content = readFileSync(input, "utf-8"); | ||
| return parseJsonOrYaml(content); | ||
| } catch { | ||
| return parseJsonOrYaml(input); | ||
| } | ||
| } | ||
| __name(parseInput, "parseInput"); | ||
| function parseJsonOrYaml(content) { | ||
| try { | ||
| return JSON.parse(content); | ||
| } catch { | ||
| return parseYaml(content); | ||
| } | ||
| } | ||
| __name(parseJsonOrYaml, "parseJsonOrYaml"); | ||
| function sanitizeSchemaNames(doc, warnings) { | ||
| const original = doc.components?.schemas ?? {}; | ||
| const sanitized = {}; | ||
| const nameMap = /* @__PURE__ */ new Map(); | ||
| for (const name of Object.keys(original)) { | ||
| const clean = sanitizeName(name, warnings); | ||
| if (sanitized[clean]) { | ||
| warnings.warn(`#/components/schemas/${name}`, `Name collision after sanitization: "${name}" and another schema both map to "${clean}"`); | ||
| let i = 2; | ||
| while (sanitized[`${clean}${i}`]) i++; | ||
| nameMap.set(name, `${clean}${i}`); | ||
| sanitized[`${clean}${i}`] = original[name]; | ||
| } else { | ||
| nameMap.set(name, clean); | ||
| sanitized[clean] = original[name]; | ||
| } | ||
| } | ||
| if (nameMap.size > 0) { | ||
| updateRefs(doc, nameMap); | ||
| } | ||
| return sanitized; | ||
| } | ||
| __name(sanitizeSchemaNames, "sanitizeSchemaNames"); | ||
| function updateRefs(obj, nameMap) { | ||
| if (!obj || typeof obj !== "object") return; | ||
| if (Array.isArray(obj)) { | ||
| for (const item of obj) updateRefs(item, nameMap); | ||
| return; | ||
| } | ||
| const record = obj; | ||
| if (typeof record.$ref === "string") { | ||
| const match = record.$ref.match(/^#\/components\/schemas\/(.+)$/); | ||
| if (match?.[1] && nameMap.has(match[1])) { | ||
| record.$ref = `#/components/schemas/${nameMap.get(match[1])}`; | ||
| } | ||
| } | ||
| for (const value of Object.values(record)) { | ||
| updateRefs(value, nameMap); | ||
| } | ||
| } | ||
| __name(updateRefs, "updateRefs"); | ||
| export { | ||
| __name, | ||
| normalize, | ||
| detectCircularRefs, | ||
| extractRefName, | ||
| schemasToModels, | ||
| schemaToTypeNode, | ||
| sanitizeName, | ||
| pathsToRoutes, | ||
| splitByTag, | ||
| mergeIntoSingle, | ||
| astToCk, | ||
| serializeType, | ||
| convertOpenApiToCk | ||
| }; | ||
| //# sourceMappingURL=chunk-T2G2WRZ2.js.map |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
1032726
0.4%7056
0.11%+ Added
- Removed
Updated