@toolproof-npm/schema 0.1.12

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.
@@ -0,0 +1,132 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { compileFromFile } from 'json-schema-to-typescript';
4
+ /**
5
+ * Generate a typed ResourceData variant where `extractedData` is typed to a specific schema
6
+ * extracted under `src/schemas/<name>.json`.
7
+ *
8
+ * Usage: node ./dist/scripts/generateResourceData_js --name Job
9
+ */
10
+ async function main() {
11
+ const { name } = parseArgs(process.argv.slice(2));
12
+ if (!name) {
13
+ console.error('Missing --name <SchemaBasename> argument');
14
+ process.exit(1);
15
+ }
16
+ const projectRoot = process.cwd();
17
+ const schemasDir = path.join(projectRoot, 'src', 'schemas');
18
+ const inPath = path.join(schemasDir, `${name}.json`);
19
+ if (!fs.existsSync(inPath)) {
20
+ console.error(`Schema file not found: ${inPath}`);
21
+ process.exit(1);
22
+ }
23
+ // Basic validation against the expected shape of ExtractionSchema.
24
+ const raw = fs.readFileSync(inPath, 'utf8');
25
+ let parsed = null;
26
+ try {
27
+ parsed = JSON.parse(raw);
28
+ }
29
+ catch (e) {
30
+ console.error(`Failed to parse JSON schema ${inPath}:`, e);
31
+ process.exit(1);
32
+ }
33
+ // Minimal checks that roughly match the ExtractionSchema constraints used elsewhere.
34
+ if (parsed.$schema && parsed.$schema !== 'https://json-schema.org/draft/2020-12/schema') {
35
+ console.warn(`Warning: schema $schema is '${parsed.$schema}', expected draft 2020-12. Proceeding anyway.`);
36
+ }
37
+ if (parsed.type && parsed.type !== 'object') {
38
+ console.warn(`Warning: ExtractionSchema usually has type: 'object' but this schema has type: '${parsed.type}'. Proceeding.`);
39
+ }
40
+ // Ensure output folder exists
41
+ const outDir = path.join(projectRoot, 'src', '_lib', 'types');
42
+ fs.mkdirSync(outDir, { recursive: true });
43
+ const outName = `ResourceData_${name}.d.ts`;
44
+ const outPath = path.join(outDir, outName);
45
+ const outJsName = `ResourceData_${name}.js`;
46
+ const outJsPath = path.join(outDir, outJsName);
47
+ // Build a composite schema in src/schemas so that local $ref paths are simple
48
+ const schemasOutDir = path.join(projectRoot, 'src', 'schemas');
49
+ const compositePath = path.join(schemasOutDir, `.composite.ResourceData_${name}.json`);
50
+ const compositeSchema = {
51
+ $schema: 'https://json-schema.org/draft/2020-12/schema',
52
+ title: `ResourceData_${name}`,
53
+ type: 'object',
54
+ allOf: [
55
+ { $ref: './Genesis.json#/$defs/ResourceDataMetaBase' },
56
+ {
57
+ type: 'object',
58
+ required: ['extractedData'],
59
+ properties: {
60
+ extractedData: { $ref: `./${name}.json` }
61
+ },
62
+ unevaluatedProperties: false
63
+ }
64
+ ],
65
+ unevaluatedProperties: false
66
+ };
67
+ // Write composite schema
68
+ fs.writeFileSync(compositePath, JSON.stringify(compositeSchema, null, 2), 'utf8');
69
+ try {
70
+ // Compile to TypeScript declarations
71
+ let ts = await compileFromFile(compositePath, {
72
+ bannerComment: '',
73
+ declareExternallyReferenced: true,
74
+ unreachableDefinitions: true,
75
+ $refOptions: {
76
+ resolve: {
77
+ file: { order: 1 },
78
+ http: false,
79
+ https: false
80
+ }
81
+ }
82
+ });
83
+ // Remove noisy index signatures that make types too permissive
84
+ ts = ts.replace(/\n\s*\[k:\s*string\]:\s*unknown;\n/g, '\n');
85
+ // Ensure it is a module
86
+ if (!/\bexport\b/.test(ts)) {
87
+ ts += '\nexport {}\n';
88
+ }
89
+ const header = '// Auto-generated strict composite type. Do not edit.\n';
90
+ fs.writeFileSync(outPath, header + ts, 'utf8');
91
+ console.log(`Wrote ${outPath}`);
92
+ // Ensure a runtime-resolvable JS shim exists alongside the .d.ts for NodeNext resolution
93
+ if (!fs.existsSync(outJsPath)) {
94
+ fs.writeFileSync(outJsPath, 'export {}\n', 'utf8');
95
+ console.log(`Wrote ${outJsPath}`);
96
+ }
97
+ // Also copy both files into dist so consumers can resolve the module and its types
98
+ const distLibDir = path.join(projectRoot, 'dist', '_lib', 'types');
99
+ fs.mkdirSync(distLibDir, { recursive: true });
100
+ const distDtsPath = path.join(distLibDir, outName);
101
+ const distJsPath = path.join(distLibDir, outJsName);
102
+ fs.writeFileSync(distDtsPath, header + ts, 'utf8');
103
+ fs.writeFileSync(distJsPath, 'export {}\n', 'utf8');
104
+ console.log(`Wrote ${distDtsPath}`);
105
+ console.log(`Wrote ${distJsPath}`);
106
+ }
107
+ finally {
108
+ // Cleanup composite schema file
109
+ try {
110
+ fs.unlinkSync(compositePath);
111
+ }
112
+ catch { }
113
+ }
114
+ }
115
+ function parseArgs(args) {
116
+ let name;
117
+ for (let i = 0; i < args.length; i++) {
118
+ const a = args[i];
119
+ if (a === '--name') {
120
+ name = args[i + 1];
121
+ i++;
122
+ }
123
+ else if (a.startsWith('--name=')) {
124
+ name = a.split('=')[1];
125
+ }
126
+ }
127
+ return { name };
128
+ }
129
+ main().catch((e) => {
130
+ console.error(e);
131
+ process.exit(1);
132
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,411 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { compileFromFile } from 'json-schema-to-typescript';
4
+ const projectRoot = process.cwd();
5
+ const inputDir = path.join(projectRoot, 'src', 'schemas');
6
+ // We emit under src/_lib/types and dist/_lib/types
7
+ const srcLibTypesDir = path.join(projectRoot, 'src', '_lib', 'types');
8
+ const srcLibOutputPath = path.join(srcLibTypesDir, 'types.d.ts');
9
+ // Build an index of all schema files by their basename
10
+ // This supports location-independent $id values where folder segments were removed
11
+ function buildSchemaIndex(root) {
12
+ const index = new Map();
13
+ const stack = [root];
14
+ while (stack.length) {
15
+ const current = stack.pop();
16
+ const entries = fs.readdirSync(current, { withFileTypes: true });
17
+ for (const entry of entries) {
18
+ const full = path.join(current, entry.name);
19
+ if (entry.isDirectory()) {
20
+ // Skip dist or types output folders if present inside schemas (defensive)
21
+ if (entry.name === 'node_modules' || entry.name.startsWith('.'))
22
+ continue;
23
+ stack.push(full);
24
+ }
25
+ else if (entry.isFile() && entry.name.endsWith('.json')) {
26
+ if (entry.name === '.combined-schema.json')
27
+ continue; // ignore temp file
28
+ const baseName = entry.name; // keep extension for direct mapping
29
+ if (index.has(baseName)) {
30
+ // Hard fail on collisions so they can be fixed explicitly
31
+ const existing = index.get(baseName);
32
+ throw new Error(`Schema basename collision detected for "${baseName}"\n` +
33
+ `First: ${existing}\n` +
34
+ `Second: ${full}\n` +
35
+ `Please rename one of the schemas to ensure unique basenames.`);
36
+ }
37
+ else {
38
+ index.set(baseName, full);
39
+ }
40
+ }
41
+ }
42
+ }
43
+ return index;
44
+ }
45
+ // List all schema files (relative to inputDir), excluding documentation and temp files
46
+ function listAllSchemaFiles(root) {
47
+ const files = [];
48
+ const stack = [root];
49
+ while (stack.length) {
50
+ const current = stack.pop();
51
+ const entries = fs.readdirSync(current, { withFileTypes: true });
52
+ for (const entry of entries) {
53
+ const full = path.join(current, entry.name);
54
+ if (entry.isDirectory()) {
55
+ if (entry.name === 'documentation' || entry.name === 'node_modules' || entry.name.startsWith('.'))
56
+ continue;
57
+ stack.push(full);
58
+ }
59
+ else if (entry.isFile() && entry.name.endsWith('.json')) {
60
+ if (entry.name === '.combined-schema.json')
61
+ continue; // ignore temp
62
+ // buildersuce path relative to root with posix separators
63
+ const rel = path.relative(root, full).split(path.sep).join('/');
64
+ files.push(rel);
65
+ }
66
+ }
67
+ }
68
+ files.sort(); // deterministic order
69
+ return files;
70
+ }
71
+ async function main() {
72
+ // Ensure src/_lib/types exists (we no longer write to src/types to avoid duplication)
73
+ fs.mkdirSync(srcLibTypesDir, { recursive: true });
74
+ const parts = [];
75
+ parts.push('// Auto-generated from JSON schemas. Do not edit.\n');
76
+ // Precompute index for location-independent IDs (filename only after version segment)
77
+ const schemaIndex = buildSchemaIndex(inputDir);
78
+ const idToCanonical = {};
79
+ // Custom resolver to map our absolute schema IDs to local files, preventing HTTP fetches.
80
+ // Supports two patterns:
81
+ // 1. Legacy full-path IDs: https://schemas.toolproof.com/v0/genesis/Foo.json
82
+ // 2. Location-independent IDs: https://schemas.toolproof.com/v0/Foo.json
83
+ const toolproofResolver = {
84
+ order: 1,
85
+ canRead: (file) => {
86
+ const url = (typeof file.url === 'string' ? file.url : file.url?.href) || '';
87
+ return (/^https?:\/\/schemas\.toolproof\.(documentation|com)\//i.test(url) ||
88
+ /^https?:\/\/toolproof\.com\/schemas\//i.test(url));
89
+ },
90
+ read: (file) => {
91
+ const url = (typeof file.url === 'string' ? file.url : file.url?.href) || '';
92
+ // Strip hash part (anchors) for path resolution
93
+ const noHash = url.split('#')[0];
94
+ // Remove base domains
95
+ let rel = noHash
96
+ .replace(/^https?:\/\/schemas\.toolproof\.(documentation|com)\//i, '')
97
+ .replace(/^https?:\/\/toolproof\.com\/schemas\//i, '');
98
+ // Drop leading version segment (v0/, v1/, etc.) if present
99
+ rel = rel.replace(/^v\d+\//i, '');
100
+ // Resolve by basename only (location-independent IDs)
101
+ const fileName = path.basename(rel);
102
+ const indexed = schemaIndex.get(fileName);
103
+ if (indexed) {
104
+ return fs.readFileSync(indexed, 'utf8');
105
+ }
106
+ throw new Error(`Toolproof resolver: could not locate schema for URL "${url}". ` +
107
+ `Tried basename "${fileName}" in schema index. Ensure unique basenames and correct $id.`);
108
+ }
109
+ };
110
+ // Files to include in the combined schema (auto-discovered, excludes documentation)
111
+ const toCompile = listAllSchemaFiles(inputDir);
112
+ // Build definitions for a combined schema that references each file.
113
+ const definitions = {};
114
+ const includedNames = [];
115
+ for (const fileName of toCompile) {
116
+ const p = path.join(inputDir, fileName);
117
+ if (!fs.existsSync(p)) {
118
+ console.warn(`Schema file missing, skipping: ${p}`);
119
+ continue;
120
+ }
121
+ // Definition key: basename without extension, with path segments removed
122
+ const base = path.basename(fileName, '.json');
123
+ // Prefer the schema's declared $id so all references point to the same absolute URI
124
+ // This helps json-schema-to-typescript dedupe declarations instead of emitting FooJson and FooJson1
125
+ let refValue = `./${fileName}`;
126
+ try {
127
+ const raw = fs.readFileSync(p, 'utf8');
128
+ const parsed = JSON.parse(raw);
129
+ if (parsed && typeof parsed.$id === 'string' && parsed.$id.trim()) {
130
+ refValue = parsed.$id.trim();
131
+ idToCanonical[refValue] = base + 'Json';
132
+ }
133
+ // Promote this file's $defs to top-level combined $defs so each gets its own exported type
134
+ if (parsed && parsed.$defs && typeof parsed.$defs === 'object') {
135
+ const defNames = Object.keys(parsed.$defs).filter((k) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(k));
136
+ for (const defName of defNames) {
137
+ // Prefer bare def name; if collision, namespace with file base
138
+ let entryName = defName;
139
+ if (definitions[entryName]) {
140
+ entryName = `${base}_${defName}`;
141
+ }
142
+ if (!definitions[entryName]) {
143
+ definitions[entryName] = { $ref: `${refValue}#/$defs/${defName}` };
144
+ includedNames.push(entryName);
145
+ }
146
+ }
147
+ }
148
+ }
149
+ catch (e) {
150
+ // If parsing fails, fall back to relative ref; proceed gracefully
151
+ }
152
+ definitions[base] = { $ref: refValue };
153
+ includedNames.push(base);
154
+ }
155
+ if (includedNames.length === 0) {
156
+ console.warn('No schema files found to compile. Nothing to do.');
157
+ return;
158
+ }
159
+ const combinedSchema = {
160
+ $id: 'combined-entry',
161
+ $schema: 'https://json-schema.org/draft/2020-12/schema',
162
+ $defs: definitions,
163
+ anyOf: includedNames.map((n) => ({ $ref: `#/$defs/${n}` }))
164
+ };
165
+ console.log('combinedSchema:', JSON.stringify(combinedSchema, null, 2));
166
+ // Write combined schema to a temp file inside inputDir so relative $ref resolves.
167
+ const combinedPath = path.join(inputDir, '.combined-schema.json');
168
+ try {
169
+ fs.writeFileSync(combinedPath, JSON.stringify(combinedSchema, null, 2), 'utf8');
170
+ // Compile the single combined schema; referenced schemas will be emitted once.
171
+ let ts = await compileFromFile(combinedPath, {
172
+ bannerComment: '',
173
+ //
174
+ declareExternallyReferenced: true,
175
+ unreachableDefinitions: true,
176
+ // Forward ref parser options so absolute $id/$ref URLs resolve from local files
177
+ $refOptions: {
178
+ // Don’t go to the network; we provide a local resolver for our domain
179
+ resolve: {
180
+ file: { order: 2 },
181
+ http: false,
182
+ https: false,
183
+ toolproof: toolproofResolver
184
+ }
185
+ }
186
+ });
187
+ // Remove permissive index signatures that make interfaces open-ended.
188
+ // Keep meaningful map-like signatures (e.g., `[k: string]: RoleLiteral`) intact.
189
+ // Robust single-pass: delete the entire line (with optional trailing newline) where the signature appears.
190
+ // This avoids introducing extra blank lines while handling CRLF/LF and varying indentation.
191
+ ts = ts.replace(/^\s*\[k:\s*string\]:\s*unknown;\s*(?:\r?\n)?/gm, '');
192
+ // Prune verbose type/interface names buildersuced from absolute $id URLs.
193
+ // Deterministic pruning based on original $id -> baseName map
194
+ // This avoids heuristic truncation that dropped prefixes like Resource / Workflow.
195
+ function idToGeneratedIdentifier(id) {
196
+ // json-schema-to-typescript seems to create a PascalCase of the URL with protocol prefix
197
+ // Simplified reconstruction: 'https://' => 'Https', then capitalize path & host segments
198
+ const noProto = id.replace(/^https?:\/\//i, '');
199
+ const tokens = noProto
200
+ .split(/[\/#?.=&_-]+/)
201
+ .filter(Boolean)
202
+ .map((t) => t.replace(/[^A-Za-z0-9]/g, ''))
203
+ .filter(Boolean)
204
+ .map((t) => t.charAt(0).toUpperCase() + t.slice(1));
205
+ return 'Https' + tokens.join('') + 'Json';
206
+ }
207
+ // Perform replacements for known IDs
208
+ for (const [id, canonical] of Object.entries(idToCanonical)) {
209
+ const longName = idToGeneratedIdentifier(id);
210
+ if (longName === canonical)
211
+ continue; // already minimal
212
+ const re = new RegExp(`\\b${longName}\\b`, 'g');
213
+ ts = ts.replace(re, canonical);
214
+ }
215
+ // Remove version prefixes inside any remaining long identifiers: Https...V0... -> remove V0 if followed by capital
216
+ ts = ts.replace(/(Https[A-Za-z0-9_]*?)V\d+([A-Z])/g, '$1$2');
217
+ // Final cleanup: aggressively strip the domain prefix `HttpsSchemasToolproofCom` from ALL identifiers.
218
+ // This is safe because those long names are only artifacts of json-schema-to-typescript; base names don't start with that sequence.
219
+ ts = ts.replace(/\bHttpsSchemasToolproofCom(?=[A-Z])/g, '');
220
+ // Remove accidental duplicate union entries in CombinedEntry after shortening.
221
+ ts = ts.replace(/export type CombinedEntry =([\s\S]*?);/, (m, body) => {
222
+ const lines = body.split(/\n/);
223
+ const seen2 = new Set();
224
+ const kept = [];
225
+ for (const line of lines) {
226
+ const trimmed = line.trim();
227
+ const match = /^\|\s*([A-Za-z0-9_]+)\b/.exec(trimmed);
228
+ if (match) {
229
+ const name = match[1];
230
+ if (!seen2.has(name)) {
231
+ seen2.add(name);
232
+ kept.push(' | ' + name);
233
+ }
234
+ }
235
+ else if (trimmed.length) {
236
+ kept.push(line);
237
+ }
238
+ }
239
+ return 'export type CombinedEntry =\n' + kept.join('\n') + ';';
240
+ });
241
+ // If the compiler returned nothing (can happen if everything is externalized),
242
+ // try a direct compile of the primary catalog (Genesis.json) as a fallback.
243
+ if (!ts || !ts.trim()) {
244
+ const primary = path.join(inputDir, 'Genesis.json');
245
+ if (fs.existsSync(primary)) {
246
+ try {
247
+ ts = await compileFromFile(primary, {
248
+ bannerComment: '',
249
+ declareExternallyReferenced: true,
250
+ unreachableDefinitions: true,
251
+ $refOptions: {
252
+ resolve: {
253
+ file: { order: 2 },
254
+ http: false,
255
+ https: false,
256
+ toolproof: toolproofResolver
257
+ }
258
+ }
259
+ });
260
+ }
261
+ catch (e) {
262
+ // ignore and fall through to stub
263
+ }
264
+ }
265
+ }
266
+ // Still nothing? ensure we emit a module so downstream imports don't fail.
267
+ if (!ts || !ts.trim()) {
268
+ ts = '// (No concrete types emitted by generator)\nexport {}\n';
269
+ }
270
+ // Overlay Id aliases with template literal types inferred from Genesis.json patterns.
271
+ // For each $defs entry ending with "Id" or specific key types that provide a `pattern`,
272
+ // we derive a TS template literal. If no recognizable pattern exists,
273
+ // we fall back to plain `string` (moving away from branded types).
274
+ function deriveTemplateFromPattern(pattern) {
275
+ // Common form: ^PREFIX-.+$ => PREFIX-${string}
276
+ const m1 = /^\^([^$]+)\.\+\$/.exec(pattern);
277
+ if (m1) {
278
+ const prefix = m1[1]; // e.g., 'WORKFLOW-'
279
+ // Basic safety: ensure backticks/interpolations aren't present
280
+ if (!/[`]/.test(prefix)) {
281
+ return '`' + prefix + '${string}`';
282
+ }
283
+ }
284
+ // Slightly stricter forms: ^PREFIX-[A-Za-z0-9]+$ => PREFIX-${string}
285
+ const m2 = /^\^([A-Za-z0-9._:-]+-)\[?\^?[A-Za-z0-9]+\]?\+?\$/.exec(pattern);
286
+ if (m2) {
287
+ const prefix = m2[1];
288
+ if (!/[`]/.test(prefix)) {
289
+ return '`' + prefix + '${string}`';
290
+ }
291
+ }
292
+ return undefined;
293
+ }
294
+ function loadIdTemplates() {
295
+ const map = {};
296
+ try {
297
+ const genesisPath = path.join(inputDir, 'Genesis.json');
298
+ if (fs.existsSync(genesisPath)) {
299
+ const raw = fs.readFileSync(genesisPath, 'utf8');
300
+ const parsed = JSON.parse(raw);
301
+ const defs = parsed?.$defs && typeof parsed.$defs === 'object' ? parsed.$defs : {};
302
+ for (const [defName, defVal] of Object.entries(defs)) {
303
+ // Process Id types
304
+ const isIdType = /Id$/.test(defName);
305
+ if (!isIdType)
306
+ continue;
307
+ const v = defVal;
308
+ if (v && v.type === 'string' && typeof v.pattern === 'string') {
309
+ const tmpl = deriveTemplateFromPattern(v.pattern);
310
+ if (tmpl)
311
+ map[defName] = tmpl;
312
+ }
313
+ }
314
+ }
315
+ }
316
+ catch {
317
+ // ignore failures; we'll just fall back to string
318
+ }
319
+ return map;
320
+ }
321
+ const idTemplates = loadIdTemplates();
322
+ // Replace any exported Id aliases to use the inferred template literals where available.
323
+ // Handle both `= string;` and any pre-existing branded alias `= Branded<string, 'X'>;`.
324
+ ts = ts.replace(/^(export\s+type\s+)([A-Za-z_][A-Za-z0-9_]*Id)(\s*=\s*)Branded<string,\s*'[^']+'>\s*;$/gm, (_m, p1, typeName, p3) => {
325
+ const tmpl = idTemplates[typeName];
326
+ return `${p1}${typeName}${p3}${tmpl ?? 'string'};`;
327
+ });
328
+ ts = ts.replace(/^(export\s+type\s+)([A-Za-z_][A-Za-z0-9_]*Id)(\s*=\s*)string\s*;$/gm, (_m, p1, typeName, p3) => {
329
+ const tmpl = idTemplates[typeName];
330
+ return `${p1}${typeName}${p3}${tmpl ?? 'string'};`;
331
+ });
332
+ // Post-process map-like interfaces to enforce key constraints via template literal Id types.
333
+ // Replace index-signature interfaces with Record<IdType, ValueType> so object literal keys are checked.
334
+ ts = ts.replace(/export interface RoleMap\s*{[^}]*}/g, 'export type RoleMap = Record<RoleId, RoleLiteral>;');
335
+ ts = ts.replace(/export interface RoleBindingMap\s*{[^}]*}/g, 'export type RoleBindingMap = Record<RoleId, ResourceId>;');
336
+ ts = ts.replace(/export interface ResourceMap\s*\{[^}]*\{[^}]*\}[^}]*\}/gs, 'export type ResourceMap = Record<ExecutionId, Record<RoleId, ResourcePotential | ResourceData>>;');
337
+ parts.push(ts);
338
+ let output = parts.join('\n');
339
+ // Final guard: strip any lingering `[k: string]: unknown;` that might have been
340
+ // reintroduced by later transforms.
341
+ output = output.replace(/^\s*\[k:\s*string\]:\s*unknown;\s*$/gm, '');
342
+ // Cosmetic post-format: remove lone blank lines before closing braces and collapse excessive blank lines
343
+ // - Remove a single blank line before `};` and `}`
344
+ // - Collapse 3+ consecutive newlines into a maximum of 2
345
+ output = output
346
+ .replace(/\r?\n\s*\r?\n(\s*};)/g, '\n$1')
347
+ .replace(/\r?\n\s*\r?\n(\s*})/g, '\n$1')
348
+ .replace(/(\r?\n){3,}/g, '\n\n');
349
+ // As an additional safeguard, make sure the final .d.ts is treated as a module.
350
+ // If no export/interface/module is present, append an empty export.
351
+ if (!/\bexport\b|\bdeclare\s+module\b|\bdeclare\s+namespace\b/.test(output)) {
352
+ output += '\nexport {}\n';
353
+ }
354
+ // Write only under src/_lib/types to avoid duplicate src/types folder
355
+ try {
356
+ fs.writeFileSync(srcLibOutputPath, output, 'utf8');
357
+ console.log('Wrote', srcLibOutputPath);
358
+ }
359
+ catch (e) {
360
+ console.warn('Failed to write types to src/_lib:', e);
361
+ }
362
+ // Also write a copy into dist so consumers get the generated declarations
363
+ // Write only to dist/_lib/types to keep the same path structure under dist
364
+ const distLibTypesDir = path.join(projectRoot, 'dist', '_lib', 'types');
365
+ const distLibOutputPath = path.join(distLibTypesDir, 'types.d.ts');
366
+ try {
367
+ fs.mkdirSync(distLibTypesDir, { recursive: true });
368
+ fs.writeFileSync(distLibOutputPath, output, 'utf8');
369
+ console.log('Wrote', distLibOutputPath);
370
+ }
371
+ catch (e) {
372
+ // If copying to dist fails, log but don't crash the generator.
373
+ console.warn('Failed to write types to dist:', e);
374
+ }
375
+ // Ensure there is a runtime-resolvable module for './_lib/types/types.js'
376
+ // Some consumers and TS NodeNext resolution expect a concrete .js next to .d.ts
377
+ // The file is intentionally empty as all exports are types-only.
378
+ try {
379
+ const srcLibTypesJsPath = path.join(srcLibTypesDir, 'types.js');
380
+ if (!fs.existsSync(srcLibTypesJsPath)) {
381
+ fs.writeFileSync(srcLibTypesJsPath, 'export {}\n', 'utf8');
382
+ console.log('Wrote', srcLibTypesJsPath);
383
+ }
384
+ }
385
+ catch (e) {
386
+ console.warn('Failed to write types.js to src/_lib:', e);
387
+ }
388
+ try {
389
+ const distLibTypesJsPath = path.join(distLibTypesDir, 'types.js');
390
+ fs.writeFileSync(distLibTypesJsPath, 'export {}\n', 'utf8');
391
+ console.log('Wrote', distLibTypesJsPath);
392
+ }
393
+ catch (e) {
394
+ console.warn('Failed to write types.js to dist/_lib:', e);
395
+ }
396
+ }
397
+ finally {
398
+ // Best-effort cleanup of the temporary combined schema
399
+ try {
400
+ if (fs.existsSync(combinedPath))
401
+ fs.unlinkSync(combinedPath);
402
+ }
403
+ catch (e) {
404
+ // ignore cleanup errors
405
+ }
406
+ }
407
+ }
408
+ main().catch((err) => {
409
+ console.error(err);
410
+ process.exitCode = 1;
411
+ });
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@toolproof-npm/schema",
3
+ "version": "0.1.12",
4
+ "description": "JSON schemas and TypeScript types for ToolProof",
5
+ "keywords": ["toolproof", "schemas", "json-schema", "typescript"],
6
+ "author": "ToolProof Team",
7
+ "license": "MIT",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/ToolProof/core.git",
11
+ "directory": "packages/_schemas"
12
+ },
13
+ "homepage": "https://github.com/ToolProof/core#readme",
14
+ "bugs": {
15
+ "url": "https://github.com/ToolProof/core/issues"
16
+ },
17
+ "type": "module",
18
+ "main": "dist/index.js",
19
+ "types": "dist/index.d.ts",
20
+ "exports": {
21
+ ".": {
22
+ "import": "./dist/index.js",
23
+ "types": "./dist/index.d.ts"
24
+ }
25
+ },
26
+ "scripts": {
27
+ "build": "tsc -b",
28
+ "build:scripts": "tsc -p tsconfig.scripts.json",
29
+ "extractSchemas": "node ./dist/scripts/extractSchemas.js",
30
+ "extractSubschema": "node ./dist/scripts/extractSubschemaWithDefs.js",
31
+ "generateTypes": "node ./dist/scripts/generateTypes.js",
32
+ "generateResourceData": "node ./dist/scripts/generateResourceData.js",
33
+ "update": "rimraf /s /q dist && pnpm run build:scripts && pnpm run extractSchemas -- --in src/genesis/Genesis.json --out src/schemas/Genesis.json --id 'https://schemas.toolproof.com/v0/Genesis.json' && pnpm run extractSubschema -- --name Job && pnpm run generateTypes && pnpm run generateResourceData -- --name Job && pnpm run build"
34
+ },
35
+ "files": [
36
+ "dist",
37
+ "README.md"
38
+ ],
39
+ "devDependencies": {
40
+ "@apidevtools/json-schema-ref-parser": "^14.2.1",
41
+ "@types/node": "^20.8.1",
42
+ "ajv-cli": "^5.0.0",
43
+ "ajv-formats": "^3.0.1",
44
+ "json-schema-to-typescript": "^15.0.4",
45
+ "typescript": "^5.0.0"
46
+ }
47
+ }