@toolproof-core/schema 1.0.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.
Files changed (74) hide show
  1. package/dist/generated/types/Resource_Genesis.d.ts +3 -0
  2. package/dist/generated/types/Resource_Genesis.js +1 -0
  3. package/dist/generated/types/Resource_Job.d.ts +3 -0
  4. package/dist/generated/types/Resource_Job.js +1 -0
  5. package/dist/generated/types/Resource_RawStrategy.d.ts +3 -0
  6. package/dist/generated/types/Resource_RawStrategy.js +1 -0
  7. package/dist/generated/types/Resource_ResourceType.d.ts +3 -0
  8. package/dist/generated/types/Resource_ResourceType.js +1 -0
  9. package/dist/generated/types/Resource_RunnableStrategy.d.ts +3 -0
  10. package/dist/generated/types/Resource_RunnableStrategy.js +1 -0
  11. package/dist/generated/types/types.d.ts +1784 -0
  12. package/dist/generated/types/types.js +1 -0
  13. package/dist/scripts/_lib/config.d.ts +53 -0
  14. package/dist/scripts/_lib/config.js +138 -0
  15. package/dist/scripts/extractSchemas.d.ts +1 -0
  16. package/dist/scripts/extractSchemas.js +210 -0
  17. package/dist/scripts/extractSubSchemaWithDefs.d.ts +1 -0
  18. package/dist/scripts/extractSubSchemaWithDefs.js +187 -0
  19. package/dist/scripts/generateDependencies.d.ts +1 -0
  20. package/dist/scripts/generateDependencies.js +106 -0
  21. package/dist/scripts/generateResourceShells.d.ts +1 -0
  22. package/dist/scripts/generateResourceShells.js +91 -0
  23. package/dist/scripts/generateResourceTypeType.d.ts +1 -0
  24. package/dist/scripts/generateResourceTypeType.js +93 -0
  25. package/dist/scripts/generateSchemaShims.d.ts +1 -0
  26. package/dist/scripts/generateSchemaShims.js +105 -0
  27. package/dist/scripts/generateTypes.d.ts +1 -0
  28. package/dist/scripts/generateTypes.js +550 -0
  29. package/dist/scripts/rewriteAnchors.d.ts +1 -0
  30. package/dist/scripts/rewriteAnchors.js +96 -0
  31. package/package.json +45 -0
  32. package/src/Genesis.json +2043 -0
  33. package/src/Roadmap.json +102 -0
  34. package/src/generated/dependencies.json +299 -0
  35. package/src/generated/resourceTypes/Genesis.json +2043 -0
  36. package/src/generated/resourceTypes/Genesis.ts +2 -0
  37. package/src/generated/resources/Genesis.json +2962 -0
  38. package/src/generated/resources/Genesis.ts +2 -0
  39. package/src/generated/schemas/Genesis.json +1489 -0
  40. package/src/generated/schemas/Genesis.ts +2 -0
  41. package/src/generated/schemas/Goal.json +86 -0
  42. package/src/generated/schemas/Goal.ts +2 -0
  43. package/src/generated/schemas/Job.json +236 -0
  44. package/src/generated/schemas/Job.ts +2 -0
  45. package/src/generated/schemas/RawStrategy.json +667 -0
  46. package/src/generated/schemas/RawStrategy.ts +2 -0
  47. package/src/generated/schemas/ResourceType.json +140 -0
  48. package/src/generated/schemas/ResourceType.ts +2 -0
  49. package/src/generated/schemas/RunnableStrategy.json +737 -0
  50. package/src/generated/schemas/RunnableStrategy.ts +2 -0
  51. package/src/generated/schemas/StrategyRun.json +1025 -0
  52. package/src/generated/schemas/StrategyRun.ts +2 -0
  53. package/src/generated/types/Resource_Genesis.d.ts +3 -0
  54. package/src/generated/types/Resource_Genesis.js +1 -0
  55. package/src/generated/types/Resource_Job.d.ts +3 -0
  56. package/src/generated/types/Resource_Job.js +1 -0
  57. package/src/generated/types/Resource_RawStrategy.d.ts +3 -0
  58. package/src/generated/types/Resource_RawStrategy.js +1 -0
  59. package/src/generated/types/Resource_ResourceType.d.ts +3 -0
  60. package/src/generated/types/Resource_ResourceType.js +1 -0
  61. package/src/generated/types/Resource_RunnableStrategy.d.ts +3 -0
  62. package/src/generated/types/Resource_RunnableStrategy.js +1 -0
  63. package/src/generated/types/types.d.ts +1784 -0
  64. package/src/generated/types/types.js +1 -0
  65. package/src/index.ts +1 -0
  66. package/src/scripts/_lib/config.ts +181 -0
  67. package/src/scripts/extractSchemas.ts +229 -0
  68. package/src/scripts/extractSubSchemaWithDefs.ts +196 -0
  69. package/src/scripts/generateDependencies.ts +120 -0
  70. package/src/scripts/generateResourceShells.ts +105 -0
  71. package/src/scripts/generateResourceTypeType.ts +110 -0
  72. package/src/scripts/generateSchemaShims.ts +115 -0
  73. package/src/scripts/generateTypes.ts +615 -0
  74. package/src/scripts/rewriteAnchors.ts +123 -0
@@ -0,0 +1 @@
1
+ export {}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Configuration for schema generation scripts
3
+ * All paths are configurable via environment variables
4
+ * Provides sensible defaults for standard project structure
5
+ */
6
+ /**
7
+ * Schema configuration with required environment variables
8
+ */
9
+ export declare class SchemaConfig {
10
+ private readonly root;
11
+ private readonly sourceDir;
12
+ private readonly sourceFile;
13
+ private readonly normalizedDir;
14
+ private readonly outputDir;
15
+ private readonly typesSrcDir;
16
+ private readonly typesDistDir;
17
+ private readonly generatedResourcesDir;
18
+ private readonly dependencyMapPath;
19
+ private readonly baseUrl;
20
+ private readonly version;
21
+ constructor();
22
+ getRoot(): string;
23
+ getSourceDir(): string;
24
+ getSourceFile(): string;
25
+ getSourcePath(): string;
26
+ getNormalizedDir(): string;
27
+ getNormalizedSourceFile(): string;
28
+ getNormalizedSourcePath(): string;
29
+ getOutputDir(): string;
30
+ getOutputPath(filename: string): string;
31
+ getTypesSrcDir(): string;
32
+ getTypesDistDir(): string;
33
+ getTypesSrcPath(filename: string): string;
34
+ getTypesDistPath(filename: string): string;
35
+ getGeneratedResourcesDir(): string;
36
+ getDependencyMapPath(): string;
37
+ getBaseUrl(): string;
38
+ getVersion(): string;
39
+ getSchemaId(schemaName: string): string;
40
+ /**
41
+ * Check if a URL matches the configured schema base URL pattern
42
+ */
43
+ isSchemaUrl(url: string): boolean;
44
+ /**
45
+ * Extract schema name from URL (removes base URL and version prefix)
46
+ */
47
+ extractSchemaName(url: string): string;
48
+ }
49
+ /**
50
+ * Get the schema configuration singleton
51
+ * Throws error if required environment variables are not set
52
+ */
53
+ export declare function getConfig(): SchemaConfig;
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Configuration for schema generation scripts
3
+ * All paths are configurable via environment variables
4
+ * Provides sensible defaults for standard project structure
5
+ */
6
+ import path from 'path';
7
+ /**
8
+ * Get environment variable with optional default
9
+ */
10
+ function getEnv(name, defaultValue) {
11
+ const value = process.env[name];
12
+ return value || defaultValue;
13
+ }
14
+ /**
15
+ * Schema configuration with required environment variables
16
+ */
17
+ export class SchemaConfig {
18
+ constructor() {
19
+ // Environment variables with sensible defaults
20
+ this.root = getEnv('TP_SCHEMA_ROOT', process.cwd());
21
+ this.sourceDir = getEnv('TP_SCHEMA_SOURCE_DIR', 'src/');
22
+ this.sourceFile = getEnv('TP_SCHEMA_SOURCE_FILE', 'Genesis.json');
23
+ // Intermediate, generated artifact produced by rewriteAnchors.
24
+ // This should NOT live next to the source-of-truth schemas.
25
+ this.normalizedDir = getEnv('TP_SCHEMA_NORMALIZED_DIR', 'src/generated/resourceTypes');
26
+ this.outputDir = getEnv('TP_SCHEMA_OUTPUT_DIR', 'src/generated/schemas');
27
+ this.typesSrcDir = getEnv('TP_SCHEMA_TYPES_SRC_DIR', 'src/generated/types');
28
+ this.typesDistDir = getEnv('TP_SCHEMA_TYPES_DIST_DIR', 'dist/generated/types');
29
+ this.generatedResourcesDir = getEnv('TP_SCHEMA_RESOURCES_DIR', 'src/generated/resources');
30
+ this.dependencyMapPath = getEnv('TP_SCHEMA_DEPENDENCY_MAP_PATH', 'src/generated/dependencies.json');
31
+ this.baseUrl = getEnv('TP_SCHEMA_BASE_URL', 'https://schemas.toolproof.com');
32
+ this.version = getEnv('TP_SCHEMA_VERSION', 'v0');
33
+ }
34
+ // Path getters
35
+ getRoot() {
36
+ return this.root;
37
+ }
38
+ getSourceDir() {
39
+ return path.isAbsolute(this.sourceDir)
40
+ ? this.sourceDir
41
+ : path.join(this.root, this.sourceDir);
42
+ }
43
+ getSourceFile() {
44
+ return this.sourceFile;
45
+ }
46
+ getSourcePath() {
47
+ return path.join(this.getSourceDir(), this.sourceFile);
48
+ }
49
+ getNormalizedDir() {
50
+ return path.isAbsolute(this.normalizedDir)
51
+ ? this.normalizedDir
52
+ : path.join(this.root, this.normalizedDir);
53
+ }
54
+ getNormalizedSourceFile() {
55
+ // We keep the same basename (Genesis.json) in the generated folder.
56
+ // The source-of-truth Genesis.json lives under `TP_SCHEMA_SOURCE_DIR`.
57
+ // The generated/normalized Genesis.json lives under `TP_SCHEMA_NORMALIZED_DIR`.
58
+ return this.sourceFile;
59
+ }
60
+ getNormalizedSourcePath() {
61
+ return path.join(this.getNormalizedDir(), this.getNormalizedSourceFile());
62
+ }
63
+ getOutputDir() {
64
+ return path.isAbsolute(this.outputDir)
65
+ ? this.outputDir
66
+ : path.join(this.root, this.outputDir);
67
+ }
68
+ getOutputPath(filename) {
69
+ return path.join(this.getOutputDir(), filename);
70
+ }
71
+ getTypesSrcDir() {
72
+ return path.isAbsolute(this.typesSrcDir)
73
+ ? this.typesSrcDir
74
+ : path.join(this.root, this.typesSrcDir);
75
+ }
76
+ getTypesDistDir() {
77
+ return path.isAbsolute(this.typesDistDir)
78
+ ? this.typesDistDir
79
+ : path.join(this.root, this.typesDistDir);
80
+ }
81
+ getTypesSrcPath(filename) {
82
+ return path.join(this.getTypesSrcDir(), filename);
83
+ }
84
+ getTypesDistPath(filename) {
85
+ return path.join(this.getTypesDistDir(), filename);
86
+ }
87
+ getGeneratedResourcesDir() {
88
+ return path.isAbsolute(this.generatedResourcesDir)
89
+ ? this.generatedResourcesDir
90
+ : path.join(this.root, this.generatedResourcesDir);
91
+ }
92
+ getDependencyMapPath() {
93
+ return path.isAbsolute(this.dependencyMapPath)
94
+ ? this.dependencyMapPath
95
+ : path.join(this.root, this.dependencyMapPath);
96
+ }
97
+ // Schema URL methods
98
+ getBaseUrl() {
99
+ return this.baseUrl;
100
+ }
101
+ getVersion() {
102
+ return this.version;
103
+ }
104
+ getSchemaId(schemaName) {
105
+ return `${this.baseUrl}/${this.version}/${schemaName}.json`;
106
+ }
107
+ /**
108
+ * Check if a URL matches the configured schema base URL pattern
109
+ */
110
+ isSchemaUrl(url) {
111
+ const baseUrlPattern = this.baseUrl.replace('https://', 'https?://');
112
+ return new RegExp(`^${baseUrlPattern}/`, 'i').test(url);
113
+ }
114
+ /**
115
+ * Extract schema name from URL (removes base URL and version prefix)
116
+ */
117
+ extractSchemaName(url) {
118
+ // Remove base URL
119
+ let name = url.replace(new RegExp(`^${this.baseUrl}/`, 'i'), '');
120
+ // Remove version prefix (v0/, v1/, etc.)
121
+ name = name.replace(/^v\d+\//, '');
122
+ // Remove .json extension
123
+ name = name.replace(/\.json$/, '');
124
+ return name;
125
+ }
126
+ }
127
+ // Singleton instance
128
+ let configInstance = null;
129
+ /**
130
+ * Get the schema configuration singleton
131
+ * Throws error if required environment variables are not set
132
+ */
133
+ export function getConfig() {
134
+ if (!configInstance) {
135
+ configInstance = new SchemaConfig();
136
+ }
137
+ return configInstance;
138
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,210 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { fileURLToPath } from "url";
4
+ import { getConfig } from "./_lib/config.js";
5
+ function parseArgs() {
6
+ const config = getConfig();
7
+ const argv = process.argv.slice(2);
8
+ let inPath = "";
9
+ let outPath = "";
10
+ let topLevelId;
11
+ for (let i = 0; i < argv.length; i++) {
12
+ const a = argv[i];
13
+ if (a === "--in" && i + 1 < argv.length)
14
+ inPath = argv[++i];
15
+ else if (a === "--out" && i + 1 < argv.length)
16
+ outPath = argv[++i];
17
+ else if (a === "--id" && i + 1 < argv.length) {
18
+ let v = argv[++i];
19
+ // Strip accidental surrounding quotes from PowerShell/cmd
20
+ if ((v.startsWith("'") && v.endsWith("'")) || (v.startsWith('"') && v.endsWith('"'))) {
21
+ v = v.slice(1, -1);
22
+ }
23
+ topLevelId = v;
24
+ }
25
+ }
26
+ // Use config defaults if not provided via CLI
27
+ if (!inPath) {
28
+ // Use generated/normalized version with anchor refs rewritten to pointers
29
+ inPath = config.getNormalizedSourcePath();
30
+ }
31
+ if (!outPath) {
32
+ outPath = config.getOutputPath(config.getSourceFile());
33
+ }
34
+ if (!topLevelId) {
35
+ topLevelId = config.getSchemaId('Genesis');
36
+ }
37
+ // Resolve to absolute paths from project root
38
+ const cwd = config.getRoot();
39
+ const wasInRelative = !path.isAbsolute(inPath);
40
+ const wasOutRelative = !path.isAbsolute(outPath);
41
+ if (wasInRelative)
42
+ inPath = path.join(cwd, inPath);
43
+ if (wasOutRelative)
44
+ outPath = path.join(cwd, outPath);
45
+ // Fallback: resolve relative to script directory if not found
46
+ const scriptDir = path.dirname(fileURLToPath(import.meta.url));
47
+ if (!fs.existsSync(inPath) && wasInRelative)
48
+ inPath = path.resolve(scriptDir, inPath);
49
+ const outDir = path.dirname(outPath);
50
+ if (!fs.existsSync(outDir) && wasOutRelative) {
51
+ // Try making directory relative to script dir
52
+ const altOut = path.resolve(scriptDir, outPath);
53
+ const altOutDir = path.dirname(altOut);
54
+ if (!fs.existsSync(path.dirname(outPath))) {
55
+ // Prefer creating outDir at cwd location if possible; otherwise fallback below when writing
56
+ }
57
+ else {
58
+ outPath = altOut;
59
+ }
60
+ }
61
+ return { inPath, outPath, topLevelId };
62
+ }
63
+ // Heuristic: determine if a node is a Type envelope
64
+ function isTypeEnvelope(node) {
65
+ return (node && typeof node === "object" && !Array.isArray(node) &&
66
+ // Treat any object that has an 'nucleusSchema' AND 'identity' as a Type envelope
67
+ // This prevents false positives where 'nucleusSchema' is just a regular schema property
68
+ node.nucleusSchema && typeof node.nucleusSchema === "object" &&
69
+ node.identity && typeof node.identity === "string");
70
+ }
71
+ // Merge $defs into target, without overwriting existing keys unless identical
72
+ function mergeDefs(target, source, label) {
73
+ if (!source || typeof source !== "object")
74
+ return;
75
+ const src = source["$defs"];
76
+ if (!src || typeof src !== "object")
77
+ return;
78
+ for (const [k, v] of Object.entries(src)) {
79
+ if (!(k in target)) {
80
+ target[k] = v;
81
+ }
82
+ else {
83
+ // Best-effort: if duplicate key, require structural equality; otherwise, namespace
84
+ const existing = JSON.stringify(target[k]);
85
+ const incoming = JSON.stringify(v);
86
+ if (existing !== incoming) {
87
+ const altKey = `${k}__from_${(label || "defs").replace(/[^A-Za-z0-9_]+/g, "_")}`;
88
+ if (!(altKey in target))
89
+ target[altKey] = v;
90
+ }
91
+ }
92
+ }
93
+ }
94
+ // Deeply traverse an object replacing any Type envelope with its nucleusSchema,
95
+ // and hoist its inner $defs to topDefs. Prevent infinite recursion with a visited set.
96
+ function unwrapTypes(node, topDefs, labelPath = [], visited = new Set()) {
97
+ if (node && typeof node === "object") {
98
+ if (visited.has(node))
99
+ return node; // avoid cycles
100
+ visited.add(node);
101
+ }
102
+ if (isTypeEnvelope(node)) {
103
+ const env = node;
104
+ const inner = env.nucleusSchema;
105
+ // Hoist inner $defs before stripping
106
+ mergeDefs(topDefs, inner, labelPath.join("_"));
107
+ // Return the inner schema itself, after also unwrapping any nested envelopes it may contain
108
+ const unwrappedInner = unwrapTypes(inner, topDefs, labelPath.concat([String(env.identity || "env")]), visited);
109
+ return unwrappedInner;
110
+ }
111
+ if (Array.isArray(node)) {
112
+ return node.map((v, i) => unwrapTypes(v, topDefs, labelPath.concat([String(i)]), visited));
113
+ }
114
+ if (node && typeof node === "object") {
115
+ const out = {};
116
+ for (const [k, v] of Object.entries(node)) {
117
+ if (k === "$defs" && v && typeof v === "object" && !Array.isArray(v)) {
118
+ // Process nested $defs: unwrap each entry value if it's a Type envelope
119
+ const defsOut = {};
120
+ for (const [dk, dv] of Object.entries(v)) {
121
+ const unwrapped = unwrapTypes(dv, topDefs, labelPath.concat(["$defs", dk]), visited);
122
+ defsOut[dk] = unwrapped;
123
+ }
124
+ out[k] = defsOut;
125
+ }
126
+ else {
127
+ out[k] = unwrapTypes(v, topDefs, labelPath.concat([k]), visited);
128
+ }
129
+ }
130
+ return out;
131
+ }
132
+ return node;
133
+ }
134
+ /**
135
+ * Pure function that takes a schema document and options, and returns the flattened schema.
136
+ * Performs no I/O operations.
137
+ */
138
+ function extractSchemaLogic(doc, topLevelId) {
139
+ if (!doc || typeof doc !== "object" || !doc.nucleusSchema) {
140
+ throw new Error("Input must be a Type JSON with an nucleusSchema at the top level");
141
+ }
142
+ const topSchema = doc.nucleusSchema;
143
+ const outDefs = {};
144
+ // Seed with top-level $defs (if any) before unwrapping
145
+ mergeDefs(outDefs, topSchema, "top");
146
+ // Unwrap the entire top schema tree so that any nested Type envelopes become raw schemas
147
+ const flattened = unwrapTypes(topSchema, outDefs, ["nucleusSchema"]);
148
+ // Assemble output: force $schema, optionally set $id, hoist collected $defs
149
+ let base;
150
+ if (flattened && typeof flattened === "object" && !Array.isArray(flattened)) {
151
+ base = { ...flattened };
152
+ }
153
+ else {
154
+ // If flattened is not an object (should be rare for a top-level schema), wrap it
155
+ base = { const: flattened };
156
+ }
157
+ // Assemble, but avoid duplicating $id: if the flattened base already has $id, prefer it.
158
+ const output = {
159
+ $schema: "https://json-schema.org/draft/2020-12/schema",
160
+ ...base,
161
+ };
162
+ if (topLevelId && !output.$id) {
163
+ output.$id = topLevelId;
164
+ }
165
+ // Enforce presence of $id: schema must declare an absolute identity.
166
+ if (!output.$id) {
167
+ throw new Error("Flattened schema must define $id. Provide it via CLI --id or include $id in the source nucleusSchema.");
168
+ }
169
+ // Merge collected defs into output.$defs, taking care not to clobber any existing
170
+ if (!("$defs" in output))
171
+ output.$defs = {};
172
+ const finalDefs = output.$defs || {};
173
+ for (const [k, v] of Object.entries(outDefs)) {
174
+ if (!(k in finalDefs))
175
+ finalDefs[k] = v;
176
+ }
177
+ output.$defs = finalDefs;
178
+ // Ensure a stable order for readability
179
+ return orderKeys(output, ["$id", "$schema", "$vocabulary", "$defs", "title", "description", "type", "allOf", "anyOf", "oneOf", "not", "if", "then", "else", "properties", "required", "additionalProperties", "unevaluatedProperties"]);
180
+ }
181
+ function main() {
182
+ const { inPath, outPath, topLevelId } = parseArgs();
183
+ if (!fs.existsSync(inPath)) {
184
+ console.error(`Input file not found at ${inPath}`);
185
+ process.exit(1);
186
+ }
187
+ const raw = fs.readFileSync(inPath, "utf8");
188
+ const doc = JSON.parse(raw);
189
+ // Core logic is now in a pure function
190
+ const ordered = extractSchemaLogic(doc, topLevelId);
191
+ fs.mkdirSync(path.dirname(outPath), { recursive: true });
192
+ fs.writeFileSync(outPath, JSON.stringify(ordered, null, 4), "utf8");
193
+ console.log(`Wrote flattened schema to ${outPath}`);
194
+ }
195
+ function orderKeys(obj, preferred) {
196
+ if (Array.isArray(obj))
197
+ return obj.map((v) => orderKeys(v, preferred));
198
+ if (!obj || typeof obj !== "object")
199
+ return obj;
200
+ const keys = Object.keys(obj);
201
+ const sorted = [
202
+ ...preferred.filter((k) => keys.includes(k)),
203
+ ...keys.filter((k) => !preferred.includes(k)).sort()
204
+ ];
205
+ const out = {};
206
+ for (const k of sorted)
207
+ out[k] = orderKeys(obj[k], preferred);
208
+ return out;
209
+ }
210
+ main();
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,187 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { getConfig } from './_lib/config.js';
4
+ /**
5
+ * Generic extractor: given a subschema name that exists under Genesis.json $defs,
6
+ * produce a standalone JSON Schema file that contains that subschema plus an inner $defs
7
+ * object holding all direct & transitive local $defs dependencies (by #/$defs/... $ref).
8
+ *
9
+ * Genesis.json itself is left unchanged.
10
+ *
11
+ * Usage:
12
+ * node ./dist/scripts/extractSubschemaWithDefs.js --name <DefName>
13
+ *
14
+ * Writes: src/genesis/generated/schemas/<DefName>.json
15
+ */
16
+ /**
17
+ * Pure function to extract a subschema and its dependencies from a genesis object.
18
+ * Performs no I/O operations.
19
+ *
20
+ * @param genesis The root Genesis schema object
21
+ * @param name The name of the definition to extract
22
+ * @returns The standalone subschema
23
+ */
24
+ function extractSubSchemaLogic(genesis, name) {
25
+ const rootDefs = genesis.$defs;
26
+ if (!rootDefs || typeof rootDefs !== 'object') {
27
+ throw new Error('No $defs object found in Genesis.json');
28
+ }
29
+ const target = rootDefs[name];
30
+ if (!target) {
31
+ throw new Error(`Subschema named '${name}' not found under $defs in Genesis.json`);
32
+ }
33
+ // Collect transitive local $defs names referenced by target
34
+ const needed = collectLocalDefClosure(target, rootDefs);
35
+ // Build output schema: clone target and attach collected subset as $defs
36
+ const targetClone = deepClone(target);
37
+ const defsOut = {};
38
+ for (const defName of needed) {
39
+ // Avoid including the target itself inside its own $defs (mirrors previous pattern)
40
+ if (defName === name)
41
+ continue;
42
+ const defSchema = rootDefs[defName];
43
+ if (defSchema === undefined) {
44
+ console.warn(`Warning: referenced def '${defName}' missing in root $defs (skipped).`);
45
+ continue;
46
+ }
47
+ defsOut[defName] = deepClone(defSchema);
48
+ }
49
+ // Merge any pre-existing inner $defs of targetClone (if present) giving precedence to collected ones
50
+ const existingInner = isObject(targetClone.$defs) ? targetClone.$defs : {};
51
+ targetClone.$defs = { ...existingInner, ...defsOut };
52
+ return targetClone;
53
+ }
54
+ async function main() {
55
+ const config = getConfig();
56
+ const { name } = parseArgs(process.argv.slice(2));
57
+ if (!name) {
58
+ console.error('Missing --name <DefName> argument');
59
+ process.exit(1);
60
+ }
61
+ const schemasDir = config.getOutputDir();
62
+ const genesisPath = path.join(schemasDir, config.getSourceFile());
63
+ const outPath = config.getOutputPath(`${name}.json`);
64
+ if (!fs.existsSync(genesisPath)) {
65
+ console.error(`Genesis.json not found at ${genesisPath}`);
66
+ process.exit(1);
67
+ }
68
+ const raw = fs.readFileSync(genesisPath, 'utf-8');
69
+ const genesis = JSON.parse(raw);
70
+ try {
71
+ // Call the pure logic function
72
+ const result = extractSubSchemaLogic(genesis, name);
73
+ // I/O: Write the result
74
+ fs.mkdirSync(path.dirname(outPath), { recursive: true });
75
+ fs.writeFileSync(outPath, JSON.stringify(result, null, 2) + '\n');
76
+ console.log(`Extracted subschema '${name}' -> ${outPath}`);
77
+ }
78
+ catch (error) {
79
+ console.error(error.message);
80
+ process.exit(1);
81
+ }
82
+ }
83
+ // ---- Helpers ----
84
+ function parseArgs(args) {
85
+ let name;
86
+ for (let i = 0; i < args.length; i++) {
87
+ const a = args[i];
88
+ if (a === '--name') {
89
+ name = args[i + 1];
90
+ i++;
91
+ }
92
+ else if (a.startsWith('--name=')) {
93
+ name = a.split('=')[1];
94
+ }
95
+ }
96
+ return { name };
97
+ }
98
+ function isObject(v) {
99
+ return v !== null && typeof v === 'object' && !Array.isArray(v);
100
+ }
101
+ function deepClone(v) {
102
+ if (Array.isArray(v))
103
+ return v.map((x) => deepClone(x));
104
+ if (isObject(v)) {
105
+ const out = {};
106
+ for (const k of Object.keys(v))
107
+ out[k] = deepClone(v[k]);
108
+ return out;
109
+ }
110
+ return v;
111
+ }
112
+ function extractPointerDefName(ref) {
113
+ // Accept refs like '#/$defs/Name' only (single-level under $defs)
114
+ if (!ref || !ref.startsWith('#/'))
115
+ return null;
116
+ const parts = ref.slice(2).split('/'); // remove '#/'
117
+ if (parts.length !== 2)
118
+ return null;
119
+ if (parts[0] !== '$defs')
120
+ return null;
121
+ // Decode JSON Pointer tokens for the def name
122
+ const name = parts[1].replace(/~1/g, '/').replace(/~0/g, '~');
123
+ return name;
124
+ }
125
+ function resolveRefToDefName(ref, rootDefs) {
126
+ if (!ref)
127
+ return null;
128
+ // Case 1: JSON Pointer into $defs: '#/$defs/Name'
129
+ const byPointer = extractPointerDefName(ref);
130
+ if (byPointer)
131
+ return byPointer;
132
+ // Case 2: Anchor ref: '#Name' -> find a def whose nucleusSchema.$anchor equals 'Name'
133
+ if (ref.startsWith('#') && !ref.startsWith('#/')) {
134
+ const anchor = ref.slice(1);
135
+ if (!anchor)
136
+ return null;
137
+ for (const [defName, defSchema] of Object.entries(rootDefs)) {
138
+ if (!defSchema || typeof defSchema !== 'object')
139
+ continue;
140
+ // Flattened Genesis has defs as the extraction schema itself (with $anchor at the top level).
141
+ // Unflattened may have { nucleusSchema: { $anchor } } envelopes. Support both.
142
+ const topLevelAnchor = defSchema.$anchor;
143
+ const nested = defSchema.nucleusSchema;
144
+ const nestedAnchor = nested && typeof nested === 'object' ? nested.$anchor : undefined;
145
+ const defAnchor = typeof topLevelAnchor === 'string' ? topLevelAnchor : (typeof nestedAnchor === 'string' ? nestedAnchor : undefined);
146
+ if (defAnchor === anchor) {
147
+ return defName;
148
+ }
149
+ }
150
+ }
151
+ return null;
152
+ }
153
+ function collectLocalDefClosure(node, rootDefs) {
154
+ const needed = new Set();
155
+ const queue = [];
156
+ function visit(n) {
157
+ if (Array.isArray(n)) {
158
+ for (const item of n)
159
+ visit(item);
160
+ return;
161
+ }
162
+ if (!isObject(n))
163
+ return;
164
+ if (typeof n.$ref === 'string') {
165
+ const name = resolveRefToDefName(n.$ref, rootDefs);
166
+ if (name && !needed.has(name)) {
167
+ needed.add(name);
168
+ queue.push(name);
169
+ }
170
+ }
171
+ for (const val of Object.values(n))
172
+ visit(val);
173
+ }
174
+ visit(node);
175
+ while (queue.length > 0) {
176
+ const name = queue.shift();
177
+ const def = rootDefs[name];
178
+ if (!def)
179
+ continue; // Missing def handled earlier
180
+ visit(def);
181
+ }
182
+ return needed;
183
+ }
184
+ main().catch((e) => {
185
+ console.error(e);
186
+ process.exit(1);
187
+ });
@@ -0,0 +1 @@
1
+ export {};