@toolproof-core/schema 1.0.3 → 1.0.4
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/dist/generated/normalized/Genesis.json +17 -9
- package/dist/generated/resources/Genesis.json +19 -11
- package/dist/generated/schemas/Genesis.json +15 -7
- package/dist/generated/schemas/standalone/RawStrategy.json +15 -7
- package/dist/generated/schemas/standalone/RunnableStrategy.json +15 -7
- package/dist/generated/schemas/standalone/StrategyRun.json +15 -7
- package/dist/generated/{types → typesTS}/standalone/Resource_Genesis.d.ts +1 -1
- package/{src/generated/types → dist/generated/typesTS}/standalone/Resource_Job.d.ts +1 -1
- package/dist/generated/{types → typesTS}/standalone/Resource_RawStrategy.d.ts +1 -1
- package/{src/generated/types → dist/generated/typesTS}/standalone/Resource_ResourceType.d.ts +1 -1
- package/dist/generated/{types → typesTS}/standalone/Resource_RunnableStrategy.d.ts +1 -1
- package/dist/generated/typesTS/typesTS.d.ts +745 -0
- package/dist/index.d.ts +6 -9
- package/dist/index.js +0 -2
- package/dist/scripts/_lib/config.d.ts +10 -10
- package/dist/scripts/_lib/config.js +23 -23
- package/dist/scripts/extractSchemasFromResourceTypeShells.js +109 -103
- package/dist/scripts/generateDependencies.js +15 -14
- package/dist/scripts/generateSchemaShims.js +76 -84
- package/dist/scripts/generateStandaloneSchema.js +47 -37
- package/dist/scripts/generateStandaloneTypeTS.js +108 -0
- package/dist/scripts/generateTypesTS.js +430 -0
- package/dist/scripts/normalizeAnchorsToPointers.js +37 -30
- package/dist/scripts/wrapResourceTypesWithResourceShells.js +14 -16
- package/package.json +11 -12
- package/src/Genesis.json +2007 -1999
- package/{dist/generated → src/generated/dependencies}/dependencyMap.json +8 -7
- package/src/generated/normalized/Genesis.json +17 -9
- package/src/generated/resources/Genesis.json +19 -11
- package/src/generated/schemas/Genesis.json +15 -7
- package/src/generated/schemas/standalone/RawStrategy.json +15 -7
- package/src/generated/schemas/standalone/RunnableStrategy.json +15 -7
- package/src/generated/schemas/standalone/StrategyRun.json +15 -7
- package/src/generated/{types → typesTS}/standalone/Resource_Genesis.d.ts +1 -1
- package/{dist/generated/types → src/generated/typesTS}/standalone/Resource_Job.d.ts +1 -1
- package/src/generated/{types → typesTS}/standalone/Resource_RawStrategy.d.ts +1 -1
- package/{dist/generated/types → src/generated/typesTS}/standalone/Resource_ResourceType.d.ts +1 -1
- package/src/generated/{types → typesTS}/standalone/Resource_RunnableStrategy.d.ts +1 -1
- package/src/generated/typesTS/typesTS.d.ts +745 -0
- package/src/index.ts +67 -93
- package/src/scripts/_lib/config.ts +205 -205
- package/src/scripts/extractSchemasFromResourceTypeShells.ts +261 -218
- package/src/scripts/generateDependencies.ts +121 -120
- package/src/scripts/generateSchemaShims.ts +127 -135
- package/src/scripts/generateStandaloneSchema.ts +185 -175
- package/src/scripts/generateStandaloneTypeTS.ts +127 -0
- package/src/scripts/generateTypesTS.ts +532 -0
- package/src/scripts/normalizeAnchorsToPointers.ts +115 -123
- package/src/scripts/wrapResourceTypesWithResourceShells.ts +82 -84
- package/dist/generated/types/types.d.ts +0 -1723
- package/dist/scripts/generateStandaloneType.js +0 -102
- package/dist/scripts/generateTypes.js +0 -550
- package/src/generated/dependencyMap.json +0 -292
- package/src/generated/types/types.d.ts +0 -1723
- package/src/scripts/generateStandaloneType.ts +0 -119
- package/src/scripts/generateTypes.ts +0 -615
- /package/dist/generated/{types → typesTS}/standalone/Resource_Genesis.js +0 -0
- /package/dist/generated/{types → typesTS}/standalone/Resource_Job.js +0 -0
- /package/dist/generated/{types → typesTS}/standalone/Resource_RawStrategy.js +0 -0
- /package/dist/generated/{types → typesTS}/standalone/Resource_ResourceType.js +0 -0
- /package/dist/generated/{types → typesTS}/standalone/Resource_RunnableStrategy.js +0 -0
- /package/dist/generated/{types/types.js → typesTS/typesTS.js} +0 -0
- /package/dist/scripts/{generateStandaloneType.d.ts → generateStandaloneTypeTS.d.ts} +0 -0
- /package/dist/scripts/{generateTypes.d.ts → generateTypesTS.d.ts} +0 -0
- /package/src/generated/{types → typesTS}/standalone/Resource_Genesis.js +0 -0
- /package/src/generated/{types → typesTS}/standalone/Resource_Job.js +0 -0
- /package/src/generated/{types → typesTS}/standalone/Resource_RawStrategy.js +0 -0
- /package/src/generated/{types → typesTS}/standalone/Resource_ResourceType.js +0 -0
- /package/src/generated/{types → typesTS}/standalone/Resource_RunnableStrategy.js +0 -0
- /package/src/generated/{types/types.js → typesTS/typesTS.js} +0 -0
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { compile } from 'json-schema-to-typescript';
|
|
4
|
+
import { getConfig } from './_lib/config.js';
|
|
5
|
+
|
|
6
|
+
// PURE: Format a JSON path for error messages.
|
|
7
|
+
function formatPath(pathSegments: Array<string | number>): string {
|
|
8
|
+
let out = '';
|
|
9
|
+
for (const seg of pathSegments) {
|
|
10
|
+
if (typeof seg === 'number') {
|
|
11
|
+
out += `[${seg}]`;
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
out = out ? `${out}.${seg}` : seg;
|
|
15
|
+
}
|
|
16
|
+
return out || '<root>';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// PURE: Validate schema array-keyword shapes; returns a list of issues (no mutation, no I/O).
|
|
20
|
+
function validateSchemaArrayKeywords(node: any, parentKey?: string, pathSegments: Array<string | number> = []): string[] {
|
|
21
|
+
if (Array.isArray(node)) {
|
|
22
|
+
const issues: string[] = [];
|
|
23
|
+
for (let i = 0; i < node.length; i++) {
|
|
24
|
+
issues.push(...validateSchemaArrayKeywords(node[i], parentKey, pathSegments.concat([i])));
|
|
25
|
+
}
|
|
26
|
+
return issues;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!node || typeof node !== 'object') return [];
|
|
30
|
+
|
|
31
|
+
// IMPORTANT:
|
|
32
|
+
// In meta-schemas we often have `properties: { required: { ...schema... } }`.
|
|
33
|
+
// Here, the key "required" is a *property name*, not the JSON-Schema keyword.
|
|
34
|
+
// So when we are iterating a property-name map, do not treat keys as schema keywords.
|
|
35
|
+
const isPropertyNameMap =
|
|
36
|
+
parentKey === 'properties' ||
|
|
37
|
+
parentKey === 'patternProperties' ||
|
|
38
|
+
parentKey === '$defs' ||
|
|
39
|
+
parentKey === 'dependentSchemas' ||
|
|
40
|
+
parentKey === 'dependentRequired';
|
|
41
|
+
|
|
42
|
+
const arrayKeys = ['anyOf', 'allOf', 'oneOf', 'required', 'enum'] as const;
|
|
43
|
+
const issues: string[] = [];
|
|
44
|
+
|
|
45
|
+
for (const [k, v] of Object.entries(node)) {
|
|
46
|
+
// Keyword sanity checks for schema objects (regardless of where they appear).
|
|
47
|
+
// These reduce the chance of feeding obviously malformed shapes into the generator.
|
|
48
|
+
if (k === 'properties' || k === 'patternProperties' || k === '$defs' || k === 'dependentSchemas') {
|
|
49
|
+
if (v === null || typeof v !== 'object' || Array.isArray(v)) {
|
|
50
|
+
issues.push(
|
|
51
|
+
`${formatPath(pathSegments.concat([k]))}: expected \`${k}\` to be an object map, got ${v === null ? 'null' : Array.isArray(v) ? 'array' : typeof v}`
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (k === 'dependentRequired') {
|
|
56
|
+
if (v === null || typeof v !== 'object' || Array.isArray(v)) {
|
|
57
|
+
issues.push(
|
|
58
|
+
`${formatPath(pathSegments.concat([k]))}: expected \`dependentRequired\` to be an object map, got ${v === null ? 'null' : Array.isArray(v) ? 'array' : typeof v}`
|
|
59
|
+
);
|
|
60
|
+
} else {
|
|
61
|
+
for (const [depKey, depVal] of Object.entries(v as any)) {
|
|
62
|
+
if (!Array.isArray(depVal)) {
|
|
63
|
+
issues.push(
|
|
64
|
+
`${formatPath(pathSegments.concat([k, depKey]))}: expected dependentRequired entries to be string arrays, got ${depVal === null ? 'null' : typeof depVal}`
|
|
65
|
+
);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
for (let i = 0; i < depVal.length; i++) {
|
|
69
|
+
if (typeof depVal[i] !== 'string') {
|
|
70
|
+
issues.push(
|
|
71
|
+
`${formatPath(pathSegments.concat([k, depKey, i]))}: expected dependentRequired entries to be strings, got ${depVal[i] === null ? 'null' : typeof depVal[i]}`
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (!isPropertyNameMap && (arrayKeys as readonly string[]).includes(k) && Object.prototype.hasOwnProperty.call(node, k)) {
|
|
80
|
+
if (!Array.isArray(v)) {
|
|
81
|
+
issues.push(
|
|
82
|
+
`${formatPath(pathSegments.concat([k]))}: expected \`${k}\` to be an array, got ${v === null ? 'null' : typeof v}`
|
|
83
|
+
);
|
|
84
|
+
} else {
|
|
85
|
+
if ((k === 'anyOf' || k === 'allOf' || k === 'oneOf') && v.length === 0) {
|
|
86
|
+
issues.push(`${formatPath(pathSegments.concat([k]))}: expected \`${k}\` to be a non-empty array`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (k === 'anyOf' || k === 'allOf' || k === 'oneOf') {
|
|
90
|
+
for (let i = 0; i < v.length; i++) {
|
|
91
|
+
const item = v[i];
|
|
92
|
+
const ok =
|
|
93
|
+
typeof item === 'boolean' ||
|
|
94
|
+
(item !== null && typeof item === 'object' && !Array.isArray(item));
|
|
95
|
+
if (!ok) {
|
|
96
|
+
issues.push(
|
|
97
|
+
`${formatPath(pathSegments.concat([k, i]))}: expected a schema (object or boolean), got ${item === null ? 'null' : Array.isArray(item) ? 'array' : typeof item}`
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (k === 'required') {
|
|
104
|
+
// `required` must be string[] when used as a JSON-Schema keyword.
|
|
105
|
+
const seen = new Set<string>();
|
|
106
|
+
for (let i = 0; i < v.length; i++) {
|
|
107
|
+
if (typeof v[i] !== 'string') {
|
|
108
|
+
issues.push(
|
|
109
|
+
`${formatPath(pathSegments.concat([k, i]))}: expected \`required\` entries to be strings, got ${v[i] === null ? 'null' : typeof v[i]}`
|
|
110
|
+
);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (seen.has(v[i])) {
|
|
114
|
+
issues.push(`${formatPath(pathSegments.concat([k]))}: duplicate required entry \`${v[i]}\``);
|
|
115
|
+
}
|
|
116
|
+
seen.add(v[i]);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
issues.push(...validateSchemaArrayKeywords(v, k, pathSegments.concat([k])));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return issues;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// PURE: Move sibling object keywords into `allOf` overlays (returns a new tree; does not mutate inputs).
|
|
129
|
+
function normalizeAllOfSiblingObjectKeywords(node: any): any {
|
|
130
|
+
if (Array.isArray(node)) {
|
|
131
|
+
let changed = false;
|
|
132
|
+
const out = node.map((item) => {
|
|
133
|
+
const next = normalizeAllOfSiblingObjectKeywords(item);
|
|
134
|
+
if (next !== item) changed = true;
|
|
135
|
+
return next;
|
|
136
|
+
});
|
|
137
|
+
return changed ? out : node;
|
|
138
|
+
}
|
|
139
|
+
if (!node || typeof node !== 'object') return node;
|
|
140
|
+
|
|
141
|
+
const hasAllOf = Array.isArray((node as any).allOf) && (node as any).allOf.length > 0;
|
|
142
|
+
const looksLikeObjectSchema =
|
|
143
|
+
(node as any).type === 'object' ||
|
|
144
|
+
(node as any).properties !== undefined ||
|
|
145
|
+
(node as any).required !== undefined ||
|
|
146
|
+
(node as any).unevaluatedProperties !== undefined ||
|
|
147
|
+
(node as any).additionalProperties !== undefined;
|
|
148
|
+
|
|
149
|
+
const siblingKeys = [
|
|
150
|
+
'properties',
|
|
151
|
+
'required',
|
|
152
|
+
'additionalProperties',
|
|
153
|
+
'unevaluatedProperties',
|
|
154
|
+
'propertyNames',
|
|
155
|
+
'patternProperties',
|
|
156
|
+
'dependentRequired',
|
|
157
|
+
'dependentSchemas',
|
|
158
|
+
'minProperties',
|
|
159
|
+
'maxProperties'
|
|
160
|
+
] as const;
|
|
161
|
+
|
|
162
|
+
let localNode: any = node;
|
|
163
|
+
if (hasAllOf && looksLikeObjectSchema) {
|
|
164
|
+
const hasSiblingObjectKeywords = siblingKeys.some((k) => k in localNode);
|
|
165
|
+
if (hasSiblingObjectKeywords) {
|
|
166
|
+
const overlay: any = {};
|
|
167
|
+
if ((localNode as any).type === 'object') overlay.type = 'object';
|
|
168
|
+
|
|
169
|
+
const nextNode: any = { ...localNode };
|
|
170
|
+
for (const k of siblingKeys) {
|
|
171
|
+
if (k in nextNode) {
|
|
172
|
+
overlay[k] = nextNode[k];
|
|
173
|
+
delete nextNode[k];
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
nextNode.allOf = [overlay, ...(nextNode.allOf as any[])];
|
|
178
|
+
localNode = nextNode;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
let changed = localNode !== node;
|
|
183
|
+
const out: Record<string, any> = localNode === node ? ({} as any) : { ...localNode };
|
|
184
|
+
for (const [k, v] of Object.entries(localNode)) {
|
|
185
|
+
const next = normalizeAllOfSiblingObjectKeywords(v);
|
|
186
|
+
if (next !== v) {
|
|
187
|
+
if (!changed) {
|
|
188
|
+
changed = true;
|
|
189
|
+
Object.assign(out, localNode);
|
|
190
|
+
}
|
|
191
|
+
out[k] = next;
|
|
192
|
+
} else if (changed) {
|
|
193
|
+
out[k] = v;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return changed ? out : node;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// PURE: Clone + apply all generator-normalizations.
|
|
201
|
+
function normalizeSchemaForGenerator(schema: any): any {
|
|
202
|
+
return normalizeAllOfSiblingObjectKeywords(schema);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// PURE: Convert a regex-ish pattern into a TS template literal when recognizable.
|
|
206
|
+
function deriveTemplateFromPattern(pattern: string): string | undefined {
|
|
207
|
+
// Common (and currently canonical for identities): ^PREFIX-.+$ => `PREFIX-${string}`
|
|
208
|
+
const m1 = /^\^([^$]+)\.\+\$/.exec(pattern);
|
|
209
|
+
if (m1) {
|
|
210
|
+
const prefix = m1[1];
|
|
211
|
+
if (!/[`]/.test(prefix)) {
|
|
212
|
+
return '`' + prefix + '${string}`';
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return undefined;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// PURE: Extract Identity template-literal aliases from a parsed schema.
|
|
219
|
+
function loadPatternTemplatesFromSchema(schema: any): Record<string, string> {
|
|
220
|
+
const map: Record<string, string> = {};
|
|
221
|
+
const defs = schema?.$defs && typeof schema.$defs === 'object' ? schema.$defs : {};
|
|
222
|
+
for (const [defName, defVal] of Object.entries(defs)) {
|
|
223
|
+
const isPatternType = /Identity$/.test(defName);
|
|
224
|
+
if (!isPatternType) continue;
|
|
225
|
+
|
|
226
|
+
const v: any = defVal;
|
|
227
|
+
if (v && v.type === 'string' && typeof v.pattern === 'string') {
|
|
228
|
+
const tmpl = deriveTemplateFromPattern(v.pattern);
|
|
229
|
+
if (tmpl) map[defName] = tmpl;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return map;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// PURE: Remove duplicate union members from emitted `export type X = | ...` blocks.
|
|
236
|
+
function removeDuplicateUnionEntries(ts: string): string {
|
|
237
|
+
return ts.replace(/export type ([A-Za-z0-9_]+) =([\s\S]*?);/g, (_m, typeName, body) => {
|
|
238
|
+
const lines = body.split(/\r?\n/);
|
|
239
|
+
const seen = new Set<string>();
|
|
240
|
+
const kept: string[] = [];
|
|
241
|
+
for (const line of lines) {
|
|
242
|
+
const trimmed = line.trim();
|
|
243
|
+
const match = /^\|\s*([A-Za-z0-9_]+)\b/.exec(trimmed);
|
|
244
|
+
if (match) {
|
|
245
|
+
const name = match[1];
|
|
246
|
+
if (!seen.has(name)) {
|
|
247
|
+
seen.add(name);
|
|
248
|
+
kept.push(' | ' + name);
|
|
249
|
+
}
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
if (trimmed.length) kept.push(line);
|
|
253
|
+
}
|
|
254
|
+
return `export type ${typeName} =\n` + kept.join('\n') + ';';
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// PURE: Escape a string for use inside a RegExp pattern.
|
|
259
|
+
function escapeRegExpLiteral(value: string): string {
|
|
260
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// PURE: Fix a known json-schema-to-typescript edge case where a JSON-like recursive value union
|
|
264
|
+
// accidentally includes a direct self-reference (`| JsonData`) instead of the intended array case (`| JsonData[]`).
|
|
265
|
+
function fixJsonDataSelfReference(ts: string): string {
|
|
266
|
+
const lines = ts.split(/\r?\n/);
|
|
267
|
+
const startIndex = lines.findIndex((l) => /^export\s+type\s+JsonData\s*=\s*$/.test(l.trimEnd()));
|
|
268
|
+
if (startIndex < 0) return ts;
|
|
269
|
+
|
|
270
|
+
// This type currently ends with a union member object whose closing line is `};`.
|
|
271
|
+
// Find that terminator after the type header.
|
|
272
|
+
let endIndex = -1;
|
|
273
|
+
for (let i = startIndex + 1; i < lines.length; i++) {
|
|
274
|
+
if (/^\s*};\s*$/.test(lines[i])) {
|
|
275
|
+
endIndex = i;
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
if (endIndex < 0) return ts;
|
|
280
|
+
|
|
281
|
+
for (let i = startIndex + 1; i < endIndex; i++) {
|
|
282
|
+
if (/^\s*\|\s*JsonData\s*$/.test(lines[i])) {
|
|
283
|
+
lines[i] = lines[i].replace(/\bJsonData\b/, 'JsonData[]');
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return lines.join('\n');
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// PURE: Apply deterministic post-processing to the generator output (no I/O).
|
|
291
|
+
function postProcessEmittedTypes(ts: string, parsedSchema: any): string {
|
|
292
|
+
// Remove permissive index signatures that make interfaces open-ended.
|
|
293
|
+
// Keep meaningful map-like signatures (e.g., `[k: string]: ResourceRoleValue`) intact.
|
|
294
|
+
// Robust single-pass: delete the entire line (with optional trailing newline) where the signature appears.
|
|
295
|
+
// This avoids introducing extra blank lines while handling CRLF/LF and varying indentation.
|
|
296
|
+
ts = ts.replace(/^\s*\[k:\s*string\]:\s*unknown;\s*(?:\r?\n)?/gm, '');
|
|
297
|
+
|
|
298
|
+
// Fix meta-schema types where json-schema-to-typescript can still incorrectly interpret
|
|
299
|
+
// schema definitions as literal values (or emit overly-restrictive `{}` objects).
|
|
300
|
+
// We do this as a broad post-pass on the emitted TS because the generator output varies
|
|
301
|
+
// (e.g. `allOf?: { }[];` vs `allOf?: [{type:"array"; ...}]`).
|
|
302
|
+
|
|
303
|
+
// `$defs?: { }` or `$defs: { }` -> map type (preserve required/optional marker)
|
|
304
|
+
// NOTE: We emit `Record<...>` instead of an index signature because later cleanup
|
|
305
|
+
// strips standalone `[k: string]: unknown;` lines.
|
|
306
|
+
ts = ts.replace(
|
|
307
|
+
/^(\s*)(\$defs\??:\s*)\{\s*\r?\n\s*\};/gm,
|
|
308
|
+
(_m, indent, head) => `${indent}${head}Record<string, unknown>;`
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
// `properties?: { }` or `properties: { }` -> map type
|
|
312
|
+
ts = ts.replace(
|
|
313
|
+
/^(\s*)(properties\??:\s*)\{\s*\r?\n\s*\};/gm,
|
|
314
|
+
(_m, indent, head) => `${indent}${head}Record<string, unknown>;`
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
// `allOf?: { }[];` (and similar for anyOf/oneOf) -> array of schema-ish objects
|
|
318
|
+
ts = ts.replace(
|
|
319
|
+
/^(\s*)((?:allOf|anyOf|oneOf)\??:\s*)\{\s*\r?\n\s*\}\[\];/gm,
|
|
320
|
+
(_m, indent, head) => `${indent}${head}Array<{[k: string]: unknown}>;`
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
ts = removeDuplicateUnionEntries(ts);
|
|
324
|
+
|
|
325
|
+
// Strip URL-derived root schema interface names produced by json-schema-to-typescript
|
|
326
|
+
// (e.g. `HttpsSchemasToolproofComV2GenesisJson`). These are naming artifacts and not
|
|
327
|
+
// meaningful domain types.
|
|
328
|
+
const schemaId = parsedSchema?.$id;
|
|
329
|
+
if (typeof schemaId === 'string') {
|
|
330
|
+
const m = /\/([^/]+)\.json$/i.exec(schemaId);
|
|
331
|
+
const rootBaseName = m?.[1];
|
|
332
|
+
if (rootBaseName) {
|
|
333
|
+
const rootJsonSuffix = `${rootBaseName}Json`;
|
|
334
|
+
const rootJsonSuffixEsc = escapeRegExpLiteral(rootJsonSuffix);
|
|
335
|
+
|
|
336
|
+
// Remove the (typically empty) URL-derived root interface, if present.
|
|
337
|
+
ts = ts.replace(
|
|
338
|
+
new RegExp(`^export interface Https[A-Za-z0-9_]*${rootJsonSuffixEsc}\\s*\\{\\s*\\}\\s*(?:\\r?\\n)?`, 'gm'),
|
|
339
|
+
''
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
// Replace remaining references to that URL-derived name in doc/comments.
|
|
343
|
+
ts = ts.replace(
|
|
344
|
+
new RegExp(`\\bHttps[A-Za-z0-9_]*${rootJsonSuffixEsc}\\b`, 'g'),
|
|
345
|
+
rootBaseName
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (!ts || !ts.trim()) {
|
|
351
|
+
throw new Error('Type generator emitted no output for Genesis schema.');
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Overlay Identity aliases with template literal types inferred from Genesis.json patterns.
|
|
355
|
+
// For each $defs entry ending with "Identity" that provides a `pattern`,
|
|
356
|
+
// derive a TS template literal; otherwise fall back to plain `string`.
|
|
357
|
+
const patternTemplates = loadPatternTemplatesFromSchema(parsedSchema);
|
|
358
|
+
|
|
359
|
+
// Replace any exported Identity aliases to use the inferred template literals where available.
|
|
360
|
+
// Handle the common `= string;` output from json-schema-to-typescript.
|
|
361
|
+
ts = ts.replace(
|
|
362
|
+
/^(export\s+type\s+)([A-Za-z_][A-Za-z0-9_]*Identity)(\s*=\s*)string\s*;$/gm,
|
|
363
|
+
(_m, p1, typeName, p3) => {
|
|
364
|
+
const tmpl = patternTemplates[typeName];
|
|
365
|
+
return `${p1}${typeName}${p3}${tmpl ?? 'string'};`;
|
|
366
|
+
}
|
|
367
|
+
);
|
|
368
|
+
|
|
369
|
+
// Post-process map-like interfaces to enforce key constraints via template literal Identity types.
|
|
370
|
+
// Replace index-signature interfaces with Record<KeyType, ValueType> so object literal keys are checked.
|
|
371
|
+
const resourceRoleKeyType = 'ResourceRoleIdentity';
|
|
372
|
+
const jobStepKeyType = 'JobStepIdentity';
|
|
373
|
+
const strategyThreadKeyType = 'StrategyThreadIdentity';
|
|
374
|
+
|
|
375
|
+
ts = ts.replace(
|
|
376
|
+
/export interface RoleMap\s*{[^}]*}/g,
|
|
377
|
+
`export type RoleMap = Record<${resourceRoleKeyType}, ResourceRoleValue>;`
|
|
378
|
+
);
|
|
379
|
+
// Normalize StrategyState & related socket maps to identity-keyed Records.
|
|
380
|
+
// These are emitted as `[k: string]` by json-schema-to-typescript but are identity-keyed in practice.
|
|
381
|
+
// Per schema: JobStepSocket: Record<ResourceRoleIdentity, Resource>
|
|
382
|
+
// StrategyState: Record<JobStepIdentity, JobStepSocket>
|
|
383
|
+
const jobStepSocketValueType = 'Resource';
|
|
384
|
+
ts = ts.replace(
|
|
385
|
+
/export interface JobStepSocket\s*\{\s*\[k:\s*string\]:\s*[^;]+;\s*\}/g,
|
|
386
|
+
`export type JobStepSocket = Record<${resourceRoleKeyType}, ${jobStepSocketValueType}>;`
|
|
387
|
+
);
|
|
388
|
+
ts = ts.replace(
|
|
389
|
+
/export interface StrategyState\s*\{\s*\[k:\s*string\]:\s*JobStepSocket;\s*\}/g,
|
|
390
|
+
`export type StrategyState = Record<${jobStepKeyType}, JobStepSocket>;`
|
|
391
|
+
);
|
|
392
|
+
ts = ts.replace(
|
|
393
|
+
/(strategyStateUpdate\??:\s*)\{\s*\[k:\s*string\]:\s*JobStepSocket;\s*\};/g,
|
|
394
|
+
`$1Record<${jobStepKeyType}, JobStepSocket>;`
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
// Ensure key constraints for strategyThreadMap are preserved as template-literal identity keys.
|
|
398
|
+
// json-schema-to-typescript emits `[k: string]: StepArray;`, but we want keys to be `StrategyThreadIdentity`.
|
|
399
|
+
ts = ts.replace(
|
|
400
|
+
/export interface StrategyThreadMap\s*\{\s*\[k:\s*string\]:\s*StepArray;\s*\}/g,
|
|
401
|
+
`export type StrategyThreadMap = Record<${strategyThreadKeyType}, StepArray>;`
|
|
402
|
+
);
|
|
403
|
+
|
|
404
|
+
ts = fixJsonDataSelfReference(ts);
|
|
405
|
+
|
|
406
|
+
return ts;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// PURE: Add banner + formatting/guards to build the final .d.ts content.
|
|
410
|
+
function finalizeOutputDts(emittedTypesTs: string): string {
|
|
411
|
+
const banner = '// Auto-generated from JSON schemas. Do not edit.\n';
|
|
412
|
+
|
|
413
|
+
let output = banner + '\n' + emittedTypesTs + '\n';
|
|
414
|
+
|
|
415
|
+
// Final guard: strip any lingering `[k: string]: unknown;` that might have been
|
|
416
|
+
// reintroduced by later transforms.
|
|
417
|
+
output = output.replace(/^\s*\[k:\s*string\]:\s*unknown;\s*$/gm, '');
|
|
418
|
+
|
|
419
|
+
// Cosmetic post-format: remove lone blank lines before closing braces and collapse excessive blank lines
|
|
420
|
+
// - Remove a single blank line before `};` and `}`
|
|
421
|
+
// - Collapse 3+ consecutive newlines into a maximum of 2
|
|
422
|
+
output = output
|
|
423
|
+
.replace(/\r?\n\s*\r?\n(\s*};)/g, '\n$1')
|
|
424
|
+
.replace(/\r?\n\s*\r?\n(\s*})/g, '\n$1')
|
|
425
|
+
.replace(/(\r?\n){3,}/g, '\n\n');
|
|
426
|
+
|
|
427
|
+
// As an additional safeguard, make sure the final .d.ts is treated as a module.
|
|
428
|
+
// If no export/interface/module is present, append an empty export.
|
|
429
|
+
if (!/\bexport\b|\bdeclare\s+module\b|\bdeclare\s+namespace\b/.test(output)) {
|
|
430
|
+
output += '\nexport {}\n';
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
return output;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// IMPURE: Main script entrypoint (config + filesystem I/O + console + process exit).
|
|
437
|
+
async function main() {
|
|
438
|
+
try {
|
|
439
|
+
const config = getConfig();
|
|
440
|
+
const inputDir = config.getSchemasDir();
|
|
441
|
+
const srcLibTypesDir = config.getTypesTsSrcDir();
|
|
442
|
+
const srcLibOutputPath = config.getTypesTsSrcPath('typesTS.d.ts');
|
|
443
|
+
|
|
444
|
+
fs.mkdirSync(srcLibTypesDir, { recursive: true });
|
|
445
|
+
|
|
446
|
+
const schemaFileName = config.getSourceFile();
|
|
447
|
+
const schemaPath = path.join(inputDir, schemaFileName);
|
|
448
|
+
if (!fs.existsSync(schemaPath)) {
|
|
449
|
+
throw new Error(
|
|
450
|
+
`Root schema not found: ${schemaPath}\n` +
|
|
451
|
+
`Run the schema extraction step first (e.g. pnpm run extractSchemasFromResourceTypeShells).`
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const rawSchema = fs.readFileSync(schemaPath, 'utf8');
|
|
456
|
+
const parsedSchema: any = JSON.parse(rawSchema);
|
|
457
|
+
|
|
458
|
+
const validationIssues = validateSchemaArrayKeywords(parsedSchema);
|
|
459
|
+
if (validationIssues.length) {
|
|
460
|
+
throw new Error(
|
|
461
|
+
'Genesis schema is not in canonical form for type generation:\n' +
|
|
462
|
+
validationIssues.map((s) => `- ${s}`).join('\n')
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const normalizedSchema = normalizeSchemaForGenerator(parsedSchema);
|
|
467
|
+
|
|
468
|
+
let ts: string;
|
|
469
|
+
|
|
470
|
+
const rootName = path.basename(schemaFileName, path.extname(schemaFileName));
|
|
471
|
+
ts = await compile(normalizedSchema, rootName, {
|
|
472
|
+
bannerComment: '',
|
|
473
|
+
declareExternallyReferenced: true,
|
|
474
|
+
unreachableDefinitions: true,
|
|
475
|
+
$refOptions: {
|
|
476
|
+
resolve: {
|
|
477
|
+
file: { order: 2 },
|
|
478
|
+
http: false,
|
|
479
|
+
https: false
|
|
480
|
+
}
|
|
481
|
+
} as any
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
ts = postProcessEmittedTypes(ts, parsedSchema);
|
|
485
|
+
const output = finalizeOutputDts(ts);
|
|
486
|
+
|
|
487
|
+
// Write only under configured types output folder
|
|
488
|
+
try {
|
|
489
|
+
fs.writeFileSync(srcLibOutputPath, output, 'utf8');
|
|
490
|
+
console.log('Wrote', srcLibOutputPath);
|
|
491
|
+
} catch (e) {
|
|
492
|
+
console.warn('Failed to write types to src/_lib:', e);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Also write a copy into dist so consumers get the generated declarations
|
|
496
|
+
const distLibTypesDir = config.getTypesTsDistDir();
|
|
497
|
+
const distLibOutputPath = config.getTypesTsDistPath('typesTS.d.ts');
|
|
498
|
+
try {
|
|
499
|
+
fs.mkdirSync(distLibTypesDir, { recursive: true });
|
|
500
|
+
fs.writeFileSync(distLibOutputPath, output, 'utf8');
|
|
501
|
+
console.log('Wrote', distLibOutputPath);
|
|
502
|
+
} catch (e) {
|
|
503
|
+
// If copying to dist fails, log but don't crash the generator.
|
|
504
|
+
console.warn('Failed to write types to dist:', e);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Ensure there is a runtime-resolvable module next to `typesTS.d.ts`
|
|
508
|
+
// Some consumers and TS NodeNext resolution expect a concrete .js next to .d.ts
|
|
509
|
+
// The file is intentionally empty as all exports are types-only.
|
|
510
|
+
try {
|
|
511
|
+
const srcLibTypesJsPath = config.getTypesTsSrcPath('typesTS.js');
|
|
512
|
+
if (!fs.existsSync(srcLibTypesJsPath)) {
|
|
513
|
+
fs.writeFileSync(srcLibTypesJsPath, 'export {}\n', 'utf8');
|
|
514
|
+
console.log('Wrote', srcLibTypesJsPath);
|
|
515
|
+
}
|
|
516
|
+
} catch (e) {
|
|
517
|
+
console.warn('Failed to write typesTS.js to src/_lib:', e);
|
|
518
|
+
}
|
|
519
|
+
try {
|
|
520
|
+
const distLibTypesJsPath = config.getTypesTsDistPath('typesTS.js');
|
|
521
|
+
fs.writeFileSync(distLibTypesJsPath, 'export {}\n', 'utf8');
|
|
522
|
+
console.log('Wrote', distLibTypesJsPath);
|
|
523
|
+
} catch (e) {
|
|
524
|
+
console.warn('Failed to write typesTS.js to dist/_lib:', e);
|
|
525
|
+
}
|
|
526
|
+
} catch (err) {
|
|
527
|
+
console.error(err);
|
|
528
|
+
process.exitCode = 1;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
void main();
|