@c4a/extract-contract 0.7.5 → 0.7.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +142 -14
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -21512,6 +21512,12 @@ function parseGraphQlSources(files) {
|
|
|
21512
21512
|
name,
|
|
21513
21513
|
extension,
|
|
21514
21514
|
field_names: fields.map((field) => field.name.value).sort(),
|
|
21515
|
+
fields: fields.map((field) => ({
|
|
21516
|
+
name: field.name.value,
|
|
21517
|
+
type: import_graphql.print(field.type),
|
|
21518
|
+
optional: field.type.kind !== import_graphql.Kind.NON_NULL_TYPE,
|
|
21519
|
+
...field.description === undefined ? {} : { description: field.description.value }
|
|
21520
|
+
})),
|
|
21515
21521
|
locator: locator(document.path, definition, `type:${name}${extension ? ":extension" : ""}`)
|
|
21516
21522
|
};
|
|
21517
21523
|
catalog.types.push(type);
|
|
@@ -21539,6 +21545,13 @@ function parseGraphQlSources(files) {
|
|
|
21539
21545
|
const fieldName = field.name.value;
|
|
21540
21546
|
const operation = { operation_ref: `${document.path}#root:${name}:field:${fieldName}`, protocol: "graphql", operation_kind: operationKind, name: fieldName, parent: name, deprecated: deprecated(field), locator: locator(document.path, field, `operation:${operationKind}:${name}.${fieldName}`) };
|
|
21541
21547
|
catalog.operations.push(operation);
|
|
21548
|
+
operation.fields = [...(field.arguments ?? []).map((argument) => ({
|
|
21549
|
+
name: argument.name.value,
|
|
21550
|
+
type: import_graphql.print(argument.type),
|
|
21551
|
+
optional: argument.type.kind !== import_graphql.Kind.NON_NULL_TYPE || argument.defaultValue !== undefined,
|
|
21552
|
+
location: "argument",
|
|
21553
|
+
...argument.defaultValue === undefined ? {} : { defaultValue: import_graphql.print(argument.defaultValue) }
|
|
21554
|
+
})), { name: "return", type: import_graphql.print(field.type), optional: field.type.kind !== import_graphql.Kind.NON_NULL_TYPE, location: "response" }];
|
|
21542
21555
|
}
|
|
21543
21556
|
}
|
|
21544
21557
|
}
|
|
@@ -21546,10 +21559,123 @@ function parseGraphQlSources(files) {
|
|
|
21546
21559
|
}
|
|
21547
21560
|
|
|
21548
21561
|
// src/openApiParser.ts
|
|
21549
|
-
var import_yaml = __toESM(require_dist(), 1);
|
|
21550
21562
|
import { posix } from "node:path";
|
|
21563
|
+
|
|
21564
|
+
// src/openApiFields.ts
|
|
21565
|
+
var record = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
21566
|
+
function resolved(value, root, seen = new Set) {
|
|
21567
|
+
const schema = record(value);
|
|
21568
|
+
const ref = schema.$ref;
|
|
21569
|
+
if (typeof ref !== "string" || !ref.startsWith("#/") || seen.has(ref))
|
|
21570
|
+
return schema;
|
|
21571
|
+
seen.add(ref);
|
|
21572
|
+
let target = root;
|
|
21573
|
+
for (const part of ref.slice(2).split("/"))
|
|
21574
|
+
target = record(target)[part.replace(/~1/gu, "/").replace(/~0/gu, "~")];
|
|
21575
|
+
return target === undefined ? schema : { ...resolved(target, root, seen), ...schema };
|
|
21576
|
+
}
|
|
21577
|
+
function declaredType(schema) {
|
|
21578
|
+
if (typeof schema.$ref === "string")
|
|
21579
|
+
return schema.$ref;
|
|
21580
|
+
if (schema.type === "array")
|
|
21581
|
+
return `Array<${declaredType(record(schema.items))}>`;
|
|
21582
|
+
if (Array.isArray(schema.oneOf) || Array.isArray(schema.anyOf)) {
|
|
21583
|
+
return (schema.oneOf ?? schema.anyOf).map((item) => declaredType(record(item))).join(" | ");
|
|
21584
|
+
}
|
|
21585
|
+
return Array.isArray(schema.type) ? schema.type.join(" | ") : typeof schema.type === "string" ? schema.type : "not declared";
|
|
21586
|
+
}
|
|
21587
|
+
function constraints(schema) {
|
|
21588
|
+
return Object.fromEntries([
|
|
21589
|
+
"enum",
|
|
21590
|
+
"const",
|
|
21591
|
+
"format",
|
|
21592
|
+
"pattern",
|
|
21593
|
+
"minimum",
|
|
21594
|
+
"maximum",
|
|
21595
|
+
"minLength",
|
|
21596
|
+
"maxLength",
|
|
21597
|
+
"minItems",
|
|
21598
|
+
"maxItems",
|
|
21599
|
+
"nullable",
|
|
21600
|
+
"readOnly",
|
|
21601
|
+
"writeOnly",
|
|
21602
|
+
"additionalProperties"
|
|
21603
|
+
].filter((key) => schema[key] !== undefined).map((key) => [key, schema[key]]));
|
|
21604
|
+
}
|
|
21605
|
+
function openApiFields(value, depth = 0, root = value) {
|
|
21606
|
+
if (depth > 8)
|
|
21607
|
+
return [];
|
|
21608
|
+
const schema = resolved(value, root);
|
|
21609
|
+
const required = new Set(Array.isArray(schema.required) ? schema.required : []);
|
|
21610
|
+
const own = Object.entries(record(schema.properties)).map(([name, raw]) => {
|
|
21611
|
+
const field = resolved(raw, root);
|
|
21612
|
+
return {
|
|
21613
|
+
name,
|
|
21614
|
+
type: declaredType(field),
|
|
21615
|
+
constraints: constraints(field),
|
|
21616
|
+
optional: !required.has(name),
|
|
21617
|
+
...field.default === undefined ? {} : { defaultValue: JSON.stringify(field.default) },
|
|
21618
|
+
...typeof field.description === "string" ? { description: field.description } : {},
|
|
21619
|
+
...field.properties === undefined ? {} : { fields: openApiFields(field, depth + 1, root) }
|
|
21620
|
+
};
|
|
21621
|
+
});
|
|
21622
|
+
const inherited = Array.isArray(schema.allOf) ? schema.allOf.flatMap((part) => openApiFields(part, depth + 1, root)) : [];
|
|
21623
|
+
return [...new Map([...inherited, ...own].map((field) => [field.name, field])).values()];
|
|
21624
|
+
}
|
|
21625
|
+
function openApiOperationFields(path, operation, root = {}) {
|
|
21626
|
+
const parameters = new Map;
|
|
21627
|
+
for (const raw of [
|
|
21628
|
+
...Array.isArray(path.parameters) ? path.parameters : [],
|
|
21629
|
+
...Array.isArray(operation.parameters) ? operation.parameters : []
|
|
21630
|
+
]) {
|
|
21631
|
+
const parameter = resolved(raw, root);
|
|
21632
|
+
parameters.set(`${parameter.in}/${parameter.name}`, parameter);
|
|
21633
|
+
}
|
|
21634
|
+
const fields = [...parameters.values()].map((parameter) => {
|
|
21635
|
+
const schema = resolved(parameter.schema ?? parameter, root);
|
|
21636
|
+
return {
|
|
21637
|
+
name: String(parameter.name ?? parameter.$ref ?? "parameter"),
|
|
21638
|
+
type: declaredType(schema),
|
|
21639
|
+
constraints: constraints(schema),
|
|
21640
|
+
optional: parameter.required !== true,
|
|
21641
|
+
location: String(parameter.in ?? "reference"),
|
|
21642
|
+
...schema.default === undefined ? {} : { defaultValue: JSON.stringify(schema.default) },
|
|
21643
|
+
...typeof parameter.description === "string" ? { description: parameter.description } : {}
|
|
21644
|
+
};
|
|
21645
|
+
});
|
|
21646
|
+
const body = resolved(operation.requestBody, root);
|
|
21647
|
+
for (const [media, value] of Object.entries(record(body.content))) {
|
|
21648
|
+
const schema = resolved(record(value).schema, root);
|
|
21649
|
+
fields.push({
|
|
21650
|
+
name: `request (${media})`,
|
|
21651
|
+
type: declaredType(schema),
|
|
21652
|
+
constraints: constraints(schema),
|
|
21653
|
+
optional: body.required !== true,
|
|
21654
|
+
location: "body",
|
|
21655
|
+
fields: openApiFields(schema, 0, root)
|
|
21656
|
+
});
|
|
21657
|
+
}
|
|
21658
|
+
for (const [status, response] of Object.entries(record(operation.responses))) {
|
|
21659
|
+
const content = record(resolved(response, root).content);
|
|
21660
|
+
for (const [media, value] of Object.entries(content)) {
|
|
21661
|
+
const schema = resolved(record(value).schema, root);
|
|
21662
|
+
fields.push({
|
|
21663
|
+
name: `response ${status} (${media})`,
|
|
21664
|
+
type: declaredType(schema),
|
|
21665
|
+
constraints: constraints(schema),
|
|
21666
|
+
optional: false,
|
|
21667
|
+
location: "response",
|
|
21668
|
+
fields: openApiFields(schema, 0, root)
|
|
21669
|
+
});
|
|
21670
|
+
}
|
|
21671
|
+
}
|
|
21672
|
+
return fields;
|
|
21673
|
+
}
|
|
21674
|
+
|
|
21675
|
+
// src/openApiParser.ts
|
|
21676
|
+
var import_yaml = __toESM(require_dist(), 1);
|
|
21551
21677
|
var HTTP_METHODS = new Set(["get", "put", "post", "delete", "options", "head", "patch", "trace", "query"]);
|
|
21552
|
-
function
|
|
21678
|
+
function record2(value) {
|
|
21553
21679
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
21554
21680
|
}
|
|
21555
21681
|
function portablePath(path) {
|
|
@@ -21580,7 +21706,7 @@ function valueAt(root, path) {
|
|
|
21580
21706
|
return;
|
|
21581
21707
|
continue;
|
|
21582
21708
|
}
|
|
21583
|
-
const item =
|
|
21709
|
+
const item = record2(current);
|
|
21584
21710
|
if (item === null || !(part in item))
|
|
21585
21711
|
return;
|
|
21586
21712
|
current = item[part];
|
|
@@ -21615,7 +21741,7 @@ function collectRefs(parsed) {
|
|
|
21615
21741
|
value.forEach((item2, index) => visit(item2, [...path, String(index)], inheritedBaseId));
|
|
21616
21742
|
return;
|
|
21617
21743
|
}
|
|
21618
|
-
const item =
|
|
21744
|
+
const item = record2(value);
|
|
21619
21745
|
if (item === null)
|
|
21620
21746
|
return;
|
|
21621
21747
|
const underBaseId = inheritedBaseId || typeof item.$id === "string";
|
|
@@ -21632,23 +21758,24 @@ function collectRefs(parsed) {
|
|
|
21632
21758
|
}
|
|
21633
21759
|
function schemaTypes(parsed, root) {
|
|
21634
21760
|
const candidates = [];
|
|
21635
|
-
const components =
|
|
21636
|
-
const schemas =
|
|
21637
|
-
const definitions =
|
|
21761
|
+
const components = record2(root.components);
|
|
21762
|
+
const schemas = record2(components?.schemas);
|
|
21763
|
+
const definitions = record2(root.definitions);
|
|
21638
21764
|
if (schemas !== null)
|
|
21639
21765
|
candidates.push(...Object.entries(schemas).map(([name, value]) => ({ path: ["components", "schemas", name], value })));
|
|
21640
21766
|
if (definitions !== null)
|
|
21641
21767
|
candidates.push(...Object.entries(definitions).map(([name, value]) => ({ path: ["definitions", name], value })));
|
|
21642
21768
|
return candidates.map(({ path, value }) => {
|
|
21643
21769
|
const name = path.at(-1);
|
|
21644
|
-
const schema =
|
|
21770
|
+
const schema = record2(value);
|
|
21645
21771
|
return {
|
|
21646
21772
|
type_ref: `${parsed.path}#/${path.join("/")}`,
|
|
21647
21773
|
protocol: "openapi",
|
|
21648
21774
|
kind: typeof schema?.type === "string" ? schema.type : "schema",
|
|
21649
21775
|
name,
|
|
21650
21776
|
extension: false,
|
|
21651
|
-
field_names: Object.keys(
|
|
21777
|
+
field_names: Object.keys(record2(schema?.properties) ?? {}).sort(),
|
|
21778
|
+
fields: openApiFields(schema, 0, root),
|
|
21652
21779
|
locator: yamlLocator(parsed, path, `schema:${name}`)
|
|
21653
21780
|
};
|
|
21654
21781
|
});
|
|
@@ -21656,11 +21783,11 @@ function schemaTypes(parsed, root) {
|
|
|
21656
21783
|
function pathCatalog(parsed, root, key) {
|
|
21657
21784
|
const endpoints = [];
|
|
21658
21785
|
const operations = [];
|
|
21659
|
-
const paths =
|
|
21786
|
+
const paths = record2(root[key]);
|
|
21660
21787
|
if (paths === null)
|
|
21661
21788
|
return { endpoints, operations };
|
|
21662
21789
|
for (const [route, rawPathItem] of Object.entries(paths).sort(([left], [right]) => left.localeCompare(right))) {
|
|
21663
|
-
const pathItem =
|
|
21790
|
+
const pathItem = record2(rawPathItem);
|
|
21664
21791
|
if (pathItem === null)
|
|
21665
21792
|
continue;
|
|
21666
21793
|
const parent = key === "paths" ? route : `webhook:${route}`;
|
|
@@ -21668,7 +21795,7 @@ function pathCatalog(parsed, root, key) {
|
|
|
21668
21795
|
for (const [method, rawOperation] of Object.entries(pathItem)) {
|
|
21669
21796
|
if (!HTTP_METHODS.has(method.toLowerCase()))
|
|
21670
21797
|
continue;
|
|
21671
|
-
const operation =
|
|
21798
|
+
const operation = record2(rawOperation);
|
|
21672
21799
|
if (operation === null)
|
|
21673
21800
|
continue;
|
|
21674
21801
|
const operationId = typeof operation.operationId === "string" ? operation.operationId : `${method.toLowerCase()} ${parent}`;
|
|
@@ -21679,6 +21806,7 @@ function pathCatalog(parsed, root, key) {
|
|
|
21679
21806
|
name: operationId,
|
|
21680
21807
|
parent,
|
|
21681
21808
|
deprecated: operation.deprecated === true,
|
|
21809
|
+
fields: openApiOperationFields(pathItem, operation, root),
|
|
21682
21810
|
locator: yamlLocator(parsed, [key, route, method], `operation:${method.toLowerCase()}:${parent}`)
|
|
21683
21811
|
});
|
|
21684
21812
|
}
|
|
@@ -21732,7 +21860,7 @@ function analyzeDocument(input) {
|
|
|
21732
21860
|
catalog.diagnostics.push({ code: "contract-source-unsupported", severity: "error", locator: { path: parsed.path, line: 1, column: 1, qualified_item_path: "file" }, detail: parsed.parseError });
|
|
21733
21861
|
return catalog;
|
|
21734
21862
|
}
|
|
21735
|
-
const root =
|
|
21863
|
+
const root = record2(parsed.root);
|
|
21736
21864
|
if (input.primary && root === null) {
|
|
21737
21865
|
catalog.disposition = "unsupported";
|
|
21738
21866
|
catalog.diagnostics.push({ code: "contract-source-unsupported", severity: "error", locator: { path: parsed.path, line: 1, column: 1, qualified_item_path: "file" }, detail: "contract document root must be an object" });
|
|
@@ -21795,7 +21923,7 @@ function parseOpenApiSources(files) {
|
|
|
21795
21923
|
if (/\.(?:json|ya?ml)$/iu.test(path))
|
|
21796
21924
|
candidates.set(path, parseYaml(path, source));
|
|
21797
21925
|
}
|
|
21798
|
-
const roots = [...candidates.values()].filter((item) =>
|
|
21926
|
+
const roots = [...candidates.values()].filter((item) => record2(item.root)?.openapi !== undefined || record2(item.root)?.swagger !== undefined || item.marker && item.parseError !== null);
|
|
21799
21927
|
const rootPaths = new Set(roots.map((item) => item.path));
|
|
21800
21928
|
const reachable = new Set(rootPaths);
|
|
21801
21929
|
const graph = new Map;
|