zodrift 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +43 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +50 -0
- package/dist/commands/check.d.ts +1 -0
- package/dist/commands/check.js +52 -0
- package/dist/commands/codegen.d.ts +1 -0
- package/dist/commands/codegen.js +63 -0
- package/dist/commands/fix.d.ts +1 -0
- package/dist/commands/fix.js +113 -0
- package/dist/commands/forms.d.ts +1 -0
- package/dist/commands/forms.js +41 -0
- package/dist/commands/openapi.d.ts +1 -0
- package/dist/commands/openapi.js +49 -0
- package/dist/core/checker.d.ts +2 -0
- package/dist/core/checker.js +39 -0
- package/dist/core/compare.d.ts +2 -0
- package/dist/core/compare.js +190 -0
- package/dist/core/emit.d.ts +10 -0
- package/dist/core/emit.js +229 -0
- package/dist/core/file-discovery.d.ts +5 -0
- package/dist/core/file-discovery.js +37 -0
- package/dist/core/pairing.d.ts +6 -0
- package/dist/core/pairing.js +27 -0
- package/dist/core/ts-parser.d.ts +5 -0
- package/dist/core/ts-parser.js +239 -0
- package/dist/core/type-utils.d.ts +16 -0
- package/dist/core/type-utils.js +139 -0
- package/dist/core/types.d.ts +133 -0
- package/dist/core/types.js +1 -0
- package/dist/core/zod-parser.d.ts +5 -0
- package/dist/core/zod-parser.js +321 -0
- package/dist/reporters/index.d.ts +2 -0
- package/dist/reporters/index.js +14 -0
- package/dist/reporters/json.d.ts +2 -0
- package/dist/reporters/json.js +18 -0
- package/dist/reporters/pretty.d.ts +2 -0
- package/dist/reporters/pretty.js +35 -0
- package/dist/reporters/sarif.d.ts +2 -0
- package/dist/reporters/sarif.js +85 -0
- package/dist/utils/args.d.ts +9 -0
- package/dist/utils/args.js +55 -0
- package/package.json +44 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { canonicalSignature, normalizePath, stringifyType, unwrapOptional } from "./type-utils.js";
|
|
2
|
+
function isUnknownNode(node) {
|
|
3
|
+
return node.kind === "primitive" && node.name === "unknown";
|
|
4
|
+
}
|
|
5
|
+
function normalizeComparable(node) {
|
|
6
|
+
const unwrapped = unwrapOptional(node);
|
|
7
|
+
return unwrapped.node;
|
|
8
|
+
}
|
|
9
|
+
function addIssue(issues, issue) {
|
|
10
|
+
issues.push({
|
|
11
|
+
...issue,
|
|
12
|
+
message: issue.message ??
|
|
13
|
+
`${issue.kind} at ${issue.path || "(root)"}: type=${issue.typeValue ?? "n/a"}, schema=${issue.schemaValue ?? "n/a"}`,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
function compareUnion(pairName, path, typeNode, schemaNode, issues, typeLocation, schemaLocation) {
|
|
17
|
+
if (typeNode.kind !== "union" || schemaNode.kind !== "union") {
|
|
18
|
+
addIssue(issues, {
|
|
19
|
+
kind: "type_mismatch",
|
|
20
|
+
pairName,
|
|
21
|
+
path,
|
|
22
|
+
typeValue: stringifyType(typeNode),
|
|
23
|
+
schemaValue: stringifyType(schemaNode),
|
|
24
|
+
typeLocation,
|
|
25
|
+
schemaLocation,
|
|
26
|
+
message: `type mismatch for ${path || "(root)"}: type=${stringifyType(typeNode)}, schema=${stringifyType(schemaNode)}`,
|
|
27
|
+
});
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const typeSet = new Set(typeNode.members.map((member) => canonicalSignature(member)));
|
|
31
|
+
const schemaSet = new Set(schemaNode.members.map((member) => canonicalSignature(member)));
|
|
32
|
+
const equal = typeSet.size === schemaSet.size &&
|
|
33
|
+
Array.from(typeSet).every((value) => schemaSet.has(value));
|
|
34
|
+
if (!equal) {
|
|
35
|
+
addIssue(issues, {
|
|
36
|
+
kind: "type_mismatch",
|
|
37
|
+
pairName,
|
|
38
|
+
path,
|
|
39
|
+
typeValue: stringifyType(typeNode),
|
|
40
|
+
schemaValue: stringifyType(schemaNode),
|
|
41
|
+
typeLocation,
|
|
42
|
+
schemaLocation,
|
|
43
|
+
message: `type mismatch for ${path || "(root)"}: type=${stringifyType(typeNode)}, schema=${stringifyType(schemaNode)}`,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function compareNode(pairName, path, typeNodeRaw, schemaNodeRaw, issues, typeLocation, schemaLocation) {
|
|
48
|
+
const typeNode = normalizeComparable(typeNodeRaw);
|
|
49
|
+
const schemaNode = normalizeComparable(schemaNodeRaw);
|
|
50
|
+
if (isUnknownNode(typeNode) || isUnknownNode(schemaNode)) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (typeNode.kind === "object" && schemaNode.kind === "object") {
|
|
54
|
+
const typeProps = typeNode.properties;
|
|
55
|
+
const schemaProps = schemaNode.properties;
|
|
56
|
+
for (const [name, typeProp] of Object.entries(typeProps)) {
|
|
57
|
+
const nestedPath = normalizePath(path, name);
|
|
58
|
+
const schemaProp = schemaProps[name];
|
|
59
|
+
if (!schemaProp) {
|
|
60
|
+
addIssue(issues, {
|
|
61
|
+
kind: "missing_in_schema",
|
|
62
|
+
pairName,
|
|
63
|
+
path: nestedPath,
|
|
64
|
+
typeLocation: typeProp.location ?? typeLocation,
|
|
65
|
+
schemaLocation,
|
|
66
|
+
message: `missing in schema: ${nestedPath}`,
|
|
67
|
+
});
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (typeProp.optional !== schemaProp.optional) {
|
|
71
|
+
addIssue(issues, {
|
|
72
|
+
kind: "optional_mismatch",
|
|
73
|
+
pairName,
|
|
74
|
+
path: nestedPath,
|
|
75
|
+
typeValue: typeProp.optional ? "optional" : "required",
|
|
76
|
+
schemaValue: schemaProp.optional ? "optional" : "required",
|
|
77
|
+
typeLocation: typeProp.location ?? typeLocation,
|
|
78
|
+
schemaLocation: schemaProp.location ?? schemaLocation,
|
|
79
|
+
message: `optional mismatch for ${nestedPath}: type=${typeProp.optional ? "optional" : "required"}, schema=${schemaProp.optional ? "optional" : "required"}`,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
compareNode(pairName, nestedPath, typeProp.type, schemaProp.type, issues, typeProp.location ?? typeLocation, schemaProp.location ?? schemaLocation);
|
|
83
|
+
}
|
|
84
|
+
for (const [name, schemaProp] of Object.entries(schemaProps)) {
|
|
85
|
+
if (typeProps[name]) {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const nestedPath = normalizePath(path, name);
|
|
89
|
+
addIssue(issues, {
|
|
90
|
+
kind: "extra_in_schema",
|
|
91
|
+
pairName,
|
|
92
|
+
path: nestedPath,
|
|
93
|
+
typeLocation,
|
|
94
|
+
schemaLocation: schemaProp.location ?? schemaLocation,
|
|
95
|
+
message: `extra in schema: ${nestedPath}`,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (typeNode.kind === "array" && schemaNode.kind === "array") {
|
|
101
|
+
compareNode(pairName, `${path}[]`, typeNode.element, schemaNode.element, issues, typeLocation, schemaLocation);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (typeNode.kind === "tuple" && schemaNode.kind === "tuple") {
|
|
105
|
+
if (typeNode.items.length !== schemaNode.items.length) {
|
|
106
|
+
addIssue(issues, {
|
|
107
|
+
kind: "type_mismatch",
|
|
108
|
+
pairName,
|
|
109
|
+
path,
|
|
110
|
+
typeValue: stringifyType(typeNode),
|
|
111
|
+
schemaValue: stringifyType(schemaNode),
|
|
112
|
+
typeLocation,
|
|
113
|
+
schemaLocation,
|
|
114
|
+
message: `type mismatch for ${path || "(root)"}: type=${stringifyType(typeNode)}, schema=${stringifyType(schemaNode)}`,
|
|
115
|
+
});
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
for (let i = 0; i < typeNode.items.length; i += 1) {
|
|
119
|
+
compareNode(pairName, `${path}[${i}]`, typeNode.items[i], schemaNode.items[i], issues, typeLocation, schemaLocation);
|
|
120
|
+
}
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (typeNode.kind === "union" || schemaNode.kind === "union") {
|
|
124
|
+
compareUnion(pairName, path, typeNode, schemaNode, issues, typeLocation, schemaLocation);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (typeNode.kind === "literal" && schemaNode.kind === "literal") {
|
|
128
|
+
if (typeNode.value !== schemaNode.value) {
|
|
129
|
+
addIssue(issues, {
|
|
130
|
+
kind: "type_mismatch",
|
|
131
|
+
pairName,
|
|
132
|
+
path,
|
|
133
|
+
typeValue: stringifyType(typeNode),
|
|
134
|
+
schemaValue: stringifyType(schemaNode),
|
|
135
|
+
typeLocation,
|
|
136
|
+
schemaLocation,
|
|
137
|
+
message: `type mismatch for ${path || "(root)"}: type=${stringifyType(typeNode)}, schema=${stringifyType(schemaNode)}`,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (typeNode.kind === "reference" && schemaNode.kind === "reference") {
|
|
143
|
+
if (typeNode.name !== schemaNode.name) {
|
|
144
|
+
addIssue(issues, {
|
|
145
|
+
kind: "type_mismatch",
|
|
146
|
+
pairName,
|
|
147
|
+
path,
|
|
148
|
+
typeValue: typeNode.name,
|
|
149
|
+
schemaValue: schemaNode.name,
|
|
150
|
+
typeLocation,
|
|
151
|
+
schemaLocation,
|
|
152
|
+
message: `type mismatch for ${path || "(root)"}: type=${typeNode.name}, schema=${schemaNode.name}`,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (typeNode.kind === "primitive" && schemaNode.kind === "primitive") {
|
|
158
|
+
if (typeNode.name !== schemaNode.name) {
|
|
159
|
+
addIssue(issues, {
|
|
160
|
+
kind: "type_mismatch",
|
|
161
|
+
pairName,
|
|
162
|
+
path,
|
|
163
|
+
typeValue: typeNode.name,
|
|
164
|
+
schemaValue: schemaNode.name,
|
|
165
|
+
typeLocation,
|
|
166
|
+
schemaLocation,
|
|
167
|
+
message: `type mismatch for ${path || "(root)"}: type=${typeNode.name}, schema=${schemaNode.name}`,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
if (canonicalSignature(typeNode) !== canonicalSignature(schemaNode)) {
|
|
173
|
+
addIssue(issues, {
|
|
174
|
+
kind: "type_mismatch",
|
|
175
|
+
pairName,
|
|
176
|
+
path,
|
|
177
|
+
typeValue: stringifyType(typeNode),
|
|
178
|
+
schemaValue: stringifyType(schemaNode),
|
|
179
|
+
typeLocation,
|
|
180
|
+
schemaLocation,
|
|
181
|
+
message: `type mismatch for ${path || "(root)"}: type=${stringifyType(typeNode)}, schema=${stringifyType(schemaNode)}`,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
export function comparePair(pair) {
|
|
186
|
+
const pairName = `${pair.typeDecl.name} ↔ ${pair.schemaDecl.name}`;
|
|
187
|
+
const issues = [];
|
|
188
|
+
compareNode(pairName, "", pair.typeDecl.node, pair.schemaDecl.node, issues, pair.typeDecl.location, pair.schemaDecl.location);
|
|
189
|
+
return issues;
|
|
190
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { TypeNode } from "./types.js";
|
|
2
|
+
export declare function typeNodeToZod(node: TypeNode): string;
|
|
3
|
+
export declare function typeNodeToTs(node: TypeNode): string;
|
|
4
|
+
export declare function typeNodeToOpenApi(node: TypeNode): Record<string, unknown>;
|
|
5
|
+
export interface FormField {
|
|
6
|
+
path: string;
|
|
7
|
+
required: boolean;
|
|
8
|
+
type: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function typeNodeToFormFields(node: TypeNode): FormField[];
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { unwrapOptional } from "./type-utils.js";
|
|
2
|
+
function isSimpleIdentifier(name) {
|
|
3
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name);
|
|
4
|
+
}
|
|
5
|
+
function quoteKey(name) {
|
|
6
|
+
return isSimpleIdentifier(name) ? name : JSON.stringify(name);
|
|
7
|
+
}
|
|
8
|
+
export function typeNodeToZod(node) {
|
|
9
|
+
const normalized = unwrapOptional(node);
|
|
10
|
+
let body = "z.unknown()";
|
|
11
|
+
switch (normalized.node.kind) {
|
|
12
|
+
case "primitive":
|
|
13
|
+
switch (normalized.node.name) {
|
|
14
|
+
case "string":
|
|
15
|
+
body = "z.string()";
|
|
16
|
+
break;
|
|
17
|
+
case "number":
|
|
18
|
+
body = "z.number()";
|
|
19
|
+
break;
|
|
20
|
+
case "boolean":
|
|
21
|
+
body = "z.boolean()";
|
|
22
|
+
break;
|
|
23
|
+
case "any":
|
|
24
|
+
body = "z.any()";
|
|
25
|
+
break;
|
|
26
|
+
case "unknown":
|
|
27
|
+
body = "z.unknown()";
|
|
28
|
+
break;
|
|
29
|
+
case "null":
|
|
30
|
+
body = "z.null()";
|
|
31
|
+
break;
|
|
32
|
+
case "undefined":
|
|
33
|
+
body = "z.undefined()";
|
|
34
|
+
break;
|
|
35
|
+
default:
|
|
36
|
+
body = "z.unknown()";
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
break;
|
|
40
|
+
case "literal":
|
|
41
|
+
body = `z.literal(${JSON.stringify(normalized.node.value)})`;
|
|
42
|
+
break;
|
|
43
|
+
case "array":
|
|
44
|
+
body = `z.array(${typeNodeToZod(normalized.node.element)})`;
|
|
45
|
+
break;
|
|
46
|
+
case "tuple":
|
|
47
|
+
body = `z.tuple([${normalized.node.items.map((item) => typeNodeToZod(item)).join(", ")}])`;
|
|
48
|
+
break;
|
|
49
|
+
case "union":
|
|
50
|
+
body = `z.union([${normalized.node.members.map((member) => typeNodeToZod(member)).join(", ")}])`;
|
|
51
|
+
break;
|
|
52
|
+
case "reference":
|
|
53
|
+
body = `${normalized.node.name}Schema`;
|
|
54
|
+
break;
|
|
55
|
+
case "object": {
|
|
56
|
+
const fields = Object.values(normalized.node.properties)
|
|
57
|
+
.map((property) => {
|
|
58
|
+
const zodType = property.optional
|
|
59
|
+
? `${typeNodeToZod(property.type)}.optional()`
|
|
60
|
+
: typeNodeToZod(property.type);
|
|
61
|
+
return `${quoteKey(property.name)}: ${zodType}`;
|
|
62
|
+
})
|
|
63
|
+
.join(", ");
|
|
64
|
+
body = `z.object({ ${fields} })`;
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
case "optional":
|
|
68
|
+
body = `${typeNodeToZod(normalized.node.inner)}.optional()`;
|
|
69
|
+
break;
|
|
70
|
+
default:
|
|
71
|
+
body = "z.unknown()";
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
if (normalized.optional) {
|
|
75
|
+
return `${body}.optional()`;
|
|
76
|
+
}
|
|
77
|
+
return body;
|
|
78
|
+
}
|
|
79
|
+
export function typeNodeToTs(node) {
|
|
80
|
+
const normalized = unwrapOptional(node);
|
|
81
|
+
let body = "unknown";
|
|
82
|
+
switch (normalized.node.kind) {
|
|
83
|
+
case "primitive":
|
|
84
|
+
body = normalized.node.name;
|
|
85
|
+
break;
|
|
86
|
+
case "literal":
|
|
87
|
+
body = JSON.stringify(normalized.node.value);
|
|
88
|
+
break;
|
|
89
|
+
case "array":
|
|
90
|
+
body = `${typeNodeToTs(normalized.node.element)}[]`;
|
|
91
|
+
break;
|
|
92
|
+
case "tuple":
|
|
93
|
+
body = `[${normalized.node.items.map((item) => typeNodeToTs(item)).join(", ")}]`;
|
|
94
|
+
break;
|
|
95
|
+
case "union":
|
|
96
|
+
body = normalized.node.members.map((member) => typeNodeToTs(member)).join(" | ");
|
|
97
|
+
break;
|
|
98
|
+
case "reference":
|
|
99
|
+
body = normalized.node.name;
|
|
100
|
+
break;
|
|
101
|
+
case "object": {
|
|
102
|
+
const fields = Object.values(normalized.node.properties)
|
|
103
|
+
.map((property) => `${quoteKey(property.name)}${property.optional ? "?" : ""}: ${typeNodeToTs(property.type)}`)
|
|
104
|
+
.join("; ");
|
|
105
|
+
body = `{ ${fields} }`;
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
case "optional":
|
|
109
|
+
body = `${typeNodeToTs(normalized.node.inner)} | undefined`;
|
|
110
|
+
break;
|
|
111
|
+
default:
|
|
112
|
+
body = "unknown";
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
return normalized.optional ? `${body} | undefined` : body;
|
|
116
|
+
}
|
|
117
|
+
export function typeNodeToOpenApi(node) {
|
|
118
|
+
const normalized = unwrapOptional(node);
|
|
119
|
+
switch (normalized.node.kind) {
|
|
120
|
+
case "primitive":
|
|
121
|
+
switch (normalized.node.name) {
|
|
122
|
+
case "string":
|
|
123
|
+
return { type: "string" };
|
|
124
|
+
case "number":
|
|
125
|
+
return { type: "number" };
|
|
126
|
+
case "boolean":
|
|
127
|
+
return { type: "boolean" };
|
|
128
|
+
case "null":
|
|
129
|
+
return { type: "null" };
|
|
130
|
+
default:
|
|
131
|
+
return {};
|
|
132
|
+
}
|
|
133
|
+
case "literal":
|
|
134
|
+
return { enum: [normalized.node.value] };
|
|
135
|
+
case "array":
|
|
136
|
+
return {
|
|
137
|
+
type: "array",
|
|
138
|
+
items: typeNodeToOpenApi(normalized.node.element),
|
|
139
|
+
};
|
|
140
|
+
case "tuple":
|
|
141
|
+
return {
|
|
142
|
+
type: "array",
|
|
143
|
+
prefixItems: normalized.node.items.map((item) => typeNodeToOpenApi(item)),
|
|
144
|
+
};
|
|
145
|
+
case "union":
|
|
146
|
+
return {
|
|
147
|
+
oneOf: normalized.node.members.map((member) => typeNodeToOpenApi(member)),
|
|
148
|
+
};
|
|
149
|
+
case "reference":
|
|
150
|
+
return {
|
|
151
|
+
$ref: `#/components/schemas/${normalized.node.name}`,
|
|
152
|
+
};
|
|
153
|
+
case "object": {
|
|
154
|
+
const properties = {};
|
|
155
|
+
const required = [];
|
|
156
|
+
for (const property of Object.values(normalized.node.properties)) {
|
|
157
|
+
properties[property.name] = typeNodeToOpenApi(property.type);
|
|
158
|
+
if (!property.optional) {
|
|
159
|
+
required.push(property.name);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
type: "object",
|
|
164
|
+
properties,
|
|
165
|
+
...(required.length > 0 ? { required } : {}),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
case "optional": {
|
|
169
|
+
return {
|
|
170
|
+
anyOf: [typeNodeToOpenApi(normalized.node.inner), { type: "null" }],
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
default:
|
|
174
|
+
return {};
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
function primitiveUiType(node) {
|
|
178
|
+
const normalized = unwrapOptional(node);
|
|
179
|
+
switch (normalized.node.kind) {
|
|
180
|
+
case "primitive":
|
|
181
|
+
return normalized.node.name;
|
|
182
|
+
case "literal":
|
|
183
|
+
return typeof normalized.node.value;
|
|
184
|
+
case "array":
|
|
185
|
+
return "array";
|
|
186
|
+
case "object":
|
|
187
|
+
return "object";
|
|
188
|
+
case "union":
|
|
189
|
+
return "union";
|
|
190
|
+
case "tuple":
|
|
191
|
+
return "tuple";
|
|
192
|
+
case "reference":
|
|
193
|
+
return "reference";
|
|
194
|
+
default:
|
|
195
|
+
return "unknown";
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
function collectFromProperty(property, basePath, requiredFromParent, fields) {
|
|
199
|
+
const path = basePath ? `${basePath}.${property.name}` : property.name;
|
|
200
|
+
const required = requiredFromParent && !property.optional;
|
|
201
|
+
const normalized = unwrapOptional(property.type);
|
|
202
|
+
if (normalized.node.kind === "object") {
|
|
203
|
+
for (const childProperty of Object.values(normalized.node.properties)) {
|
|
204
|
+
collectFromProperty(childProperty, path, required, fields);
|
|
205
|
+
}
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
fields.push({
|
|
209
|
+
path,
|
|
210
|
+
required,
|
|
211
|
+
type: primitiveUiType(property.type),
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
export function typeNodeToFormFields(node) {
|
|
215
|
+
const fields = [];
|
|
216
|
+
const normalized = unwrapOptional(node);
|
|
217
|
+
if (normalized.node.kind === "object") {
|
|
218
|
+
for (const property of Object.values(normalized.node.properties)) {
|
|
219
|
+
collectFromProperty(property, "", true, fields);
|
|
220
|
+
}
|
|
221
|
+
return fields;
|
|
222
|
+
}
|
|
223
|
+
fields.push({
|
|
224
|
+
path: "$",
|
|
225
|
+
required: true,
|
|
226
|
+
type: primitiveUiType(node),
|
|
227
|
+
});
|
|
228
|
+
return fields;
|
|
229
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import ts from "typescript";
|
|
4
|
+
function readFilesByPattern(cwd, pattern) {
|
|
5
|
+
const files = ts.sys.readDirectory(cwd, [".ts", ".tsx"], undefined, [pattern]);
|
|
6
|
+
return files.filter((filePath) => !filePath.endsWith(".d.ts"));
|
|
7
|
+
}
|
|
8
|
+
function gitChangedFiles(cwd) {
|
|
9
|
+
try {
|
|
10
|
+
const output = execSync("git diff --name-only --diff-filter=ACMR && git diff --name-only --cached --diff-filter=ACMR", { cwd, stdio: ["ignore", "pipe", "ignore"] })
|
|
11
|
+
.toString("utf8")
|
|
12
|
+
.trim();
|
|
13
|
+
if (!output) {
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
const all = output
|
|
17
|
+
.split(/\r?\n/)
|
|
18
|
+
.map((item) => item.trim())
|
|
19
|
+
.filter(Boolean)
|
|
20
|
+
.map((item) => path.resolve(cwd, item));
|
|
21
|
+
return Array.from(new Set(all));
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export function discoverSourceFiles(options) {
|
|
28
|
+
const allFiles = readFilesByPattern(options.cwd, options.pattern);
|
|
29
|
+
if (!options.changedOnly) {
|
|
30
|
+
return allFiles;
|
|
31
|
+
}
|
|
32
|
+
const changed = new Set(gitChangedFiles(options.cwd));
|
|
33
|
+
if (changed.size === 0) {
|
|
34
|
+
return allFiles;
|
|
35
|
+
}
|
|
36
|
+
return allFiles.filter((filePath) => changed.has(path.resolve(filePath)));
|
|
37
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Pairing, SchemaDeclaration, TypeDeclaration } from "./types.js";
|
|
2
|
+
export declare function pairTypesWithSchemas(typeDeclarations: TypeDeclaration[], schemaDeclarations: SchemaDeclaration[]): {
|
|
3
|
+
pairs: Pairing[];
|
|
4
|
+
unmatchedTypes: string[];
|
|
5
|
+
unmatchedSchemas: string[];
|
|
6
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export function pairTypesWithSchemas(typeDeclarations, schemaDeclarations) {
|
|
2
|
+
const typeMap = new Map(typeDeclarations.map((declaration) => [declaration.name, declaration]));
|
|
3
|
+
const schemaMap = new Map(schemaDeclarations.map((declaration) => [declaration.name, declaration]));
|
|
4
|
+
const pairs = [];
|
|
5
|
+
for (const schemaDecl of schemaDeclarations) {
|
|
6
|
+
if (!schemaDecl.name.endsWith("Schema")) {
|
|
7
|
+
continue;
|
|
8
|
+
}
|
|
9
|
+
const baseName = schemaDecl.name.slice(0, -"Schema".length);
|
|
10
|
+
const typeDecl = typeMap.get(baseName);
|
|
11
|
+
if (typeDecl) {
|
|
12
|
+
pairs.push({
|
|
13
|
+
typeDecl,
|
|
14
|
+
schemaDecl,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const pairedTypeNames = new Set(pairs.map((pair) => pair.typeDecl.name));
|
|
19
|
+
const pairedSchemaNames = new Set(pairs.map((pair) => pair.schemaDecl.name));
|
|
20
|
+
const unmatchedTypes = Array.from(typeMap.keys()).filter((name) => !pairedTypeNames.has(name));
|
|
21
|
+
const unmatchedSchemas = Array.from(schemaMap.keys()).filter((name) => !pairedSchemaNames.has(name));
|
|
22
|
+
return {
|
|
23
|
+
pairs,
|
|
24
|
+
unmatchedTypes,
|
|
25
|
+
unmatchedSchemas,
|
|
26
|
+
};
|
|
27
|
+
}
|