jsonata-w 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.
package/README.md ADDED
@@ -0,0 +1,61 @@
1
+ # JSONata Workflow (jsonata-w)
2
+
3
+ The **w** stands for **Workflow**. A command-line utility optimized to assist AI agents in transforming and inspecting JSON files, but equally powerful for manual workflows.
4
+
5
+ ## Commands
6
+
7
+ ### Inspect
8
+ Inspects the structure of a JSON file.
9
+
10
+ ```bash
11
+ jsonata-w inspect <file> [options]
12
+ ```
13
+
14
+ #### Options
15
+ - `-s, --summary`: Show a high-level summary of the JSON structure (paths and keys).
16
+ - `--schema`: Generate and print the JSON schema for the scanned file.
17
+ - `-d, --depth <number>`: Limit the depth of inspection (default: 1).
18
+ - `--jsonpath <query>`: Filter the input JSON using a JSONPath expression before inspecting.
19
+ - `--jsonata <expression>`: Filter the input JSON using a JSONata expression before inspecting.
20
+
21
+ #### Examples
22
+ **Summary view:**
23
+ ```bash
24
+ jsonata-w inspect data.json --summary
25
+ ```
26
+
27
+ **Filter with JSONPath:**
28
+ ```bash
29
+ jsonata-w inspect data.json --jsonpath "$.users[*].name"
30
+ ```
31
+
32
+ ### Transform
33
+ Transforms a JSON file using a JSONata expression file. The input and output paths are defined directly within the JSONata file using a standard configuration block.
34
+
35
+ ```bash
36
+ jsonata-w transform <file>
37
+ ```
38
+
39
+ #### Configuration Block
40
+ The JSONata file MUST start with a configuration comment block:
41
+
42
+ ```javascript
43
+ /* @config {
44
+ "input": "./path/to/input.json",
45
+ "output": "./path/to/output.json",
46
+ "schema": "./optional/schema.json"
47
+ } */
48
+
49
+ (
50
+ /* Your JSONata expression here */
51
+ $
52
+ )
53
+ ```
54
+
55
+ - `input`: Path to the source JSON file (relative to the .jsonata file).
56
+ - `output`: Path where the transformed JSON will be saved (relative to the .jsonata file).
57
+ - `schema`: (Optional) Path to a JSON schema for validation.
58
+
59
+ #### Features
60
+ - **Embedded Config**: No need for CLI arguments for input/output.
61
+ - **Auto-Unflattening**: Results containing dot-notation keys (e.g., `{"a.b": 1}`) are automatically expanded into nested objects (`{"a": {"b": 1}}`). This simplifies generating deep hierarchies.
package/dist/cli.js ADDED
@@ -0,0 +1,197 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const yargs_1 = __importDefault(require("yargs"));
8
+ const helpers_1 = require("yargs/helpers");
9
+ const SourceLoader_1 = require("./core/SourceLoader");
10
+ const StructureInspector_1 = require("./core/StructureInspector");
11
+ const SchemaValidator_1 = require("./core/SchemaValidator");
12
+ const SchemaGenerator_1 = require("./core/SchemaGenerator");
13
+ const JsonataTransformer_1 = require("./core/JsonataTransformer");
14
+ const ConfigParser_1 = require("./core/ConfigParser");
15
+ const fs_1 = __importDefault(require("fs"));
16
+ const path_1 = __importDefault(require("path"));
17
+ const jest_diff_1 = require("jest-diff");
18
+ const js_yaml_1 = __importDefault(require("js-yaml"));
19
+ const loader = new SourceLoader_1.SourceLoader();
20
+ const validator = new SchemaValidator_1.SchemaValidator();
21
+ const generator = new SchemaGenerator_1.SchemaGenerator();
22
+ const transformer = new JsonataTransformer_1.JsonataTransformer();
23
+ function unflatten(data) {
24
+ if (typeof data !== 'object' || data === null)
25
+ return data;
26
+ if (Array.isArray(data))
27
+ return data;
28
+ const result = {};
29
+ for (const i in data) {
30
+ const keys = i.split('.');
31
+ keys.reduce(function (r, e, j) {
32
+ return r[e] || (r[e] = (keys[j + 1] === undefined ? data[i] : {}));
33
+ }, result);
34
+ }
35
+ return result;
36
+ }
37
+ /**
38
+ * Creates a subset of 'data' containing only the keys found in 'template'.
39
+ * This is used for "subset-only" validation where extra output fields are ignored.
40
+ */
41
+ function pickSubset(data, template) {
42
+ if (template === null || typeof template !== 'object')
43
+ return data;
44
+ if (data === null || typeof data !== 'object')
45
+ return data;
46
+ if (Array.isArray(template)) {
47
+ if (!Array.isArray(data))
48
+ return data;
49
+ // For arrays, we just return the data as is (subset matching in arrays is ambiguous)
50
+ return data;
51
+ }
52
+ const result = {};
53
+ for (const key in template) {
54
+ if (key in data) {
55
+ result[key] = pickSubset(data[key], template[key]);
56
+ }
57
+ }
58
+ return result;
59
+ }
60
+ (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
61
+ .command('inspect <file>', 'Inspect JSON structure', (yargs) => {
62
+ return yargs
63
+ .positional('file', { describe: 'JSON file to inspect', type: 'string', demandOption: true })
64
+ .option('depth', { alias: 'd', type: 'number', default: 1, describe: 'Depth to inspect' })
65
+ .option('summary', { alias: 's', type: 'boolean', default: false, describe: 'Show structure summary' })
66
+ .option('schema', { type: 'boolean', default: false, describe: 'Generate JSON schema from file' })
67
+ .option('jsonata', { type: 'string', describe: 'JSONata expression to narrow down inspection' })
68
+ .option('jsonpath', { type: 'string', describe: 'JSONPath expression to narrow down inspection' })
69
+ .conflicts('jsonata', 'jsonpath');
70
+ }, async (argv) => {
71
+ try {
72
+ const json = loader.load(argv.file);
73
+ let targetJson = json;
74
+ if (argv.jsonata) {
75
+ const result = await transformer.evaluate(json, argv.jsonata);
76
+ targetJson = result;
77
+ }
78
+ else if (argv.jsonpath) {
79
+ // Dynamically import jsonpath to avoid top-level issues if possible,
80
+ // but standard import is fine given we installed it.
81
+ const jp = require('jsonpath');
82
+ const result = jp.query(json, argv.jsonpath);
83
+ // jp.query returns an array of matches. If we want inspection behavior on the result, likely we want the array.
84
+ targetJson = result;
85
+ }
86
+ if (argv.schema) {
87
+ const schema = generator.generate(targetJson);
88
+ console.log(JSON.stringify(schema, null, 2));
89
+ return;
90
+ }
91
+ const inspector = new StructureInspector_1.StructureInspector(targetJson);
92
+ if (argv.summary) {
93
+ const summary = inspector.summarize();
94
+ console.log(summary.join('\n'));
95
+ }
96
+ else {
97
+ console.log(JSON.stringify(inspector.inspect(argv.depth), null, 2));
98
+ }
99
+ }
100
+ catch (e) {
101
+ console.error(e.message);
102
+ process.exit(1);
103
+ }
104
+ })
105
+ .command('transform <file>', 'Transform JSON using a single JSONata file with embedded @config', (yargs) => {
106
+ return yargs
107
+ .positional('file', { describe: 'JSONata file with embedded @config', type: 'string', demandOption: true });
108
+ }, async (argv) => {
109
+ try {
110
+ const filePath = path_1.default.resolve(argv.file);
111
+ if (!fs_1.default.existsSync(filePath)) {
112
+ throw new Error(`File not found: ${filePath}`);
113
+ }
114
+ const fileContent = fs_1.default.readFileSync(filePath, 'utf-8');
115
+ const config = ConfigParser_1.ConfigParser.extract(fileContent);
116
+ const fileDir = path_1.default.dirname(filePath);
117
+ const resolvePath = (p) => path_1.default.resolve(fileDir, p);
118
+ const inputPath = resolvePath(config.input);
119
+ const outputPath = resolvePath(config.output);
120
+ console.log(`Loading input from ${inputPath}...`);
121
+ const json = loader.load(inputPath);
122
+ console.log(`Executing transformation...`);
123
+ // Use evaluate since we already have the content
124
+ const result = await transformer.evaluate(json, fileContent);
125
+ console.log(`Unflattening result...`);
126
+ const finalResult = unflatten(result);
127
+ if (config.schema) {
128
+ const schemaPath = resolvePath(config.schema);
129
+ console.log(`Validating against schema ${schemaPath}...`);
130
+ const validation = validator.validate(finalResult, schemaPath);
131
+ if (!validation.valid) {
132
+ console.error('Validation Failed:', JSON.stringify(validation.errors, null, 2));
133
+ process.exit(1);
134
+ }
135
+ else {
136
+ console.log('Validation passed.');
137
+ }
138
+ }
139
+ if (config.examples) {
140
+ const examplePath = resolvePath(config.examples);
141
+ console.log(`Validating against example ${examplePath}...`);
142
+ if (!fs_1.default.existsSync(examplePath)) {
143
+ throw new Error(`Example file not found: ${examplePath}`);
144
+ }
145
+ const exampleContent = fs_1.default.readFileSync(examplePath, 'utf-8');
146
+ const exampleData = (examplePath.endsWith('.yaml') || examplePath.endsWith('.yml'))
147
+ ? js_yaml_1.default.load(exampleContent)
148
+ : JSON.parse(exampleContent);
149
+ const validationSubset = pickSubset(finalResult, exampleData);
150
+ const difference = (0, jest_diff_1.diff)(exampleData, validationSubset);
151
+ if (difference && !difference.includes('Compared values have no visual difference')) {
152
+ console.error('❌ Example Validation Failed!');
153
+ const getKeysRecursive = (obj, depth = 0) => {
154
+ if (depth > 2 || !obj || typeof obj !== 'object' || Array.isArray(obj))
155
+ return [];
156
+ const keys = Object.keys(obj);
157
+ return keys.map(k => {
158
+ const nested = getKeysRecursive(obj[k], depth + 1);
159
+ return nested.length > 0 ? `${k} -> [${nested.join(', ')}]` : k;
160
+ });
161
+ };
162
+ console.error(`\nDiagnostic:`);
163
+ console.error(`- Expected keys: ${JSON.stringify(getKeysRecursive(exampleData))}`);
164
+ console.error(`- Actual keys: ${JSON.stringify(getKeysRecursive(finalResult))}`);
165
+ console.error('\nTips for resolution:');
166
+ console.error('- Check for property naming discrepancies or casing (e.g., "button" vs "Button").');
167
+ console.error('- Ensure all expected keys in your example file are being generated correctly by your JSONata logic.');
168
+ console.error('- Verify that the value types (arrays, objects, strings) strictly match the example.');
169
+ console.error('\nAI Optimization Hint:');
170
+ console.error('The diff below indicates mismatches between the target example (Expected) and your generated output (Received).');
171
+ console.error('If a property is missing in Received, check your JSONata mapping for that specific node.');
172
+ console.error('If Received structure is different, adjust your $processNode or nested object builders to match the example nesting.');
173
+ console.error('\n--- DIFF START ---\n');
174
+ console.error(difference);
175
+ console.error('\n--- DIFF END ---\n');
176
+ process.exit(1);
177
+ }
178
+ else {
179
+ console.log('✅ Example validation passed.');
180
+ }
181
+ }
182
+ const dir = path_1.default.dirname(outputPath);
183
+ if (!fs_1.default.existsSync(dir))
184
+ fs_1.default.mkdirSync(dir, { recursive: true });
185
+ fs_1.default.writeFileSync(outputPath, JSON.stringify(finalResult, null, 2));
186
+ console.log(`Transformed ${config.input} -> ${config.output}`);
187
+ }
188
+ catch (e) {
189
+ console.error(e.message);
190
+ process.exit(1);
191
+ }
192
+ })
193
+ .demandCommand(1, 'You need at least one command before moving on')
194
+ .strict()
195
+ .help()
196
+ .alias('h', 'help')
197
+ .parse();
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ConfigParser = void 0;
4
+ const comment_parser_1 = require("comment-parser");
5
+ class ConfigParser {
6
+ static extract(content) {
7
+ const blocks = (0, comment_parser_1.parse)(content);
8
+ for (const block of blocks) {
9
+ const configTag = block.tags.find(tag => tag.tag === 'config');
10
+ if (configTag) {
11
+ // Reconstruct the raw content from the source tokens to bypass JSDoc type parsing (which eats braces)
12
+ const rawContent = configTag.source.map((line, index) => {
13
+ const t = line.tokens;
14
+ if (index === 0) {
15
+ // First line: skip tag
16
+ return (t.postTag || '') + (t.name || '') + (t.postName || '') + (t.type || '') + (t.postType || '') + (t.description || '');
17
+ }
18
+ else {
19
+ // Subsequent lines: take everything after delimiter
20
+ // Note: postDelimiter is usually the space after *, but we might want it for indentation
21
+ return (t.postDelimiter || '') + (t.tag || '') + (t.postTag || '') + (t.name || '') + (t.postName || '') + (t.type || '') + (t.postType || '') + (t.description || '');
22
+ }
23
+ }).join('\n');
24
+ try {
25
+ return JSON.parse(rawContent);
26
+ }
27
+ catch (_e) {
28
+ throw new Error('Invalid JSON in @config block');
29
+ }
30
+ }
31
+ }
32
+ throw new Error('No @config block found in JSONata file. usage: /* @config { "input": "...", "output": "..." } */');
33
+ }
34
+ }
35
+ exports.ConfigParser = ConfigParser;
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.JsonataTransformer = void 0;
7
+ const jsonata_1 = __importDefault(require("jsonata"));
8
+ const fs_1 = __importDefault(require("fs"));
9
+ class JsonataTransformer {
10
+ async evaluate(json, expression) {
11
+ const transformer = (0, jsonata_1.default)(expression);
12
+ // Register custom bindings if needed (e.g. for logging)
13
+ transformer.registerFunction('log', (arg) => {
14
+ console.log(arg);
15
+ return arg;
16
+ });
17
+ return transformer.evaluate(json);
18
+ }
19
+ async transform(json, expressionFile) {
20
+ if (!fs_1.default.existsSync(expressionFile)) {
21
+ throw new Error(`Expression file not found: ${expressionFile}`);
22
+ }
23
+ const expression = fs_1.default.readFileSync(expressionFile, 'utf-8');
24
+ return this.evaluate(json, expression);
25
+ }
26
+ }
27
+ exports.JsonataTransformer = JsonataTransformer;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.SchemaGenerator = void 0;
7
+ const to_json_schema_1 = __importDefault(require("to-json-schema"));
8
+ class SchemaGenerator {
9
+ generate(json) {
10
+ return (0, to_json_schema_1.default)(json, {
11
+ objects: {
12
+ postProcessFnc: (schema, obj, defaultFnc) => {
13
+ return defaultFnc(schema, obj);
14
+ }
15
+ },
16
+ arrays: {
17
+ mode: 'first' // or 'all' or 'uniform'
18
+ }
19
+ });
20
+ }
21
+ }
22
+ exports.SchemaGenerator = SchemaGenerator;
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.SchemaValidator = void 0;
7
+ const ajv_1 = __importDefault(require("ajv"));
8
+ const fs_1 = __importDefault(require("fs"));
9
+ class SchemaValidator {
10
+ constructor() {
11
+ this.ajv = new ajv_1.default();
12
+ }
13
+ validate(data, schemaPath) {
14
+ if (!fs_1.default.existsSync(schemaPath)) {
15
+ throw new Error(`Schema file not found: ${schemaPath}`);
16
+ }
17
+ const schemaContent = fs_1.default.readFileSync(schemaPath, 'utf-8');
18
+ let schema;
19
+ try {
20
+ schema = JSON.parse(schemaContent);
21
+ }
22
+ catch (_e) {
23
+ throw new Error(`Invalid JSON schema in file: ${schemaPath}`);
24
+ }
25
+ const validate = this.ajv.compile(schema);
26
+ const valid = validate(data);
27
+ if (!valid) {
28
+ return {
29
+ valid: false,
30
+ errors: validate.errors || []
31
+ };
32
+ }
33
+ return { valid: true };
34
+ }
35
+ }
36
+ exports.SchemaValidator = SchemaValidator;
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.SourceLoader = void 0;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ class SourceLoader {
9
+ constructor() {
10
+ this.cache = new Map();
11
+ }
12
+ load(path) {
13
+ if (this.cache.has(path)) {
14
+ return this.cache.get(path);
15
+ }
16
+ if (!fs_1.default.existsSync(path)) {
17
+ throw new Error(`File not found: ${path}`);
18
+ }
19
+ const content = fs_1.default.readFileSync(path, 'utf-8');
20
+ try {
21
+ const json = JSON.parse(content);
22
+ this.cache.set(path, json);
23
+ return json;
24
+ }
25
+ catch (_e) {
26
+ throw new Error(`Invalid JSON in file: ${path}`);
27
+ }
28
+ }
29
+ clearCache() {
30
+ this.cache.clear();
31
+ }
32
+ }
33
+ exports.SourceLoader = SourceLoader;
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.StructureInspector = void 0;
4
+ class StructureInspector {
5
+ constructor(json) {
6
+ this.json = json;
7
+ }
8
+ inspect(depth = 1) {
9
+ const summarize = (obj, currentDepth) => {
10
+ if (currentDepth > depth) {
11
+ if (Array.isArray(obj))
12
+ return `Array[${obj.length}]`;
13
+ return typeof obj;
14
+ }
15
+ if (Array.isArray(obj)) {
16
+ return obj.map(item => summarize(item, currentDepth + 1));
17
+ }
18
+ if (typeof obj === 'object' && obj !== null) {
19
+ const summary = {};
20
+ for (const key in obj) {
21
+ summary[key] = summarize(obj[key], currentDepth + 1);
22
+ }
23
+ return summary;
24
+ }
25
+ return obj;
26
+ };
27
+ return summarize(this.json, 1);
28
+ }
29
+ summarize() {
30
+ const paths = new Map();
31
+ const addKeys = (path, keys) => {
32
+ if (!paths.has(path))
33
+ paths.set(path, new Set());
34
+ keys.forEach(k => paths.get(path).add(k));
35
+ };
36
+ const traverse = (obj, currentPath) => {
37
+ if (Array.isArray(obj)) {
38
+ if (obj.length > 0) {
39
+ const limit = Math.min(obj.length, 5);
40
+ for (let i = 0; i < limit; i++) {
41
+ const item = obj[i];
42
+ if (typeof item === 'object' && item !== null && !Array.isArray(item)) {
43
+ const keys = Object.keys(item);
44
+ const itemPath = currentPath + '[*]';
45
+ addKeys(itemPath, keys);
46
+ keys.forEach(k => traverse(item[k], itemPath === '' ? k : `${itemPath}.${k}`));
47
+ }
48
+ else if (Array.isArray(item)) {
49
+ traverse(item, currentPath + '[*]');
50
+ }
51
+ }
52
+ }
53
+ }
54
+ else if (typeof obj === 'object' && obj !== null) {
55
+ const keys = Object.keys(obj);
56
+ if (currentPath === '') {
57
+ addKeys('(root)', keys);
58
+ }
59
+ else {
60
+ addKeys(currentPath, keys);
61
+ }
62
+ keys.forEach(k => traverse(obj[k], currentPath === '' ? k : `${currentPath}.${k}`));
63
+ }
64
+ };
65
+ traverse(this.json, '');
66
+ const result = [];
67
+ const sortedPaths = Array.from(paths.keys()).sort();
68
+ sortedPaths.forEach(path => {
69
+ const keys = paths.get(path);
70
+ if (keys && keys.size > 0) {
71
+ const keyList = Array.from(keys).join(', ');
72
+ result.push(`${path} = {${keyList}}`);
73
+ }
74
+ });
75
+ return result;
76
+ }
77
+ }
78
+ exports.StructureInspector = StructureInspector;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/dist/index.js ADDED
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./core/SourceLoader"), exports);
18
+ __exportStar(require("./core/StructureInspector"), exports);
19
+ __exportStar(require("./core/SchemaValidator"), exports);
20
+ __exportStar(require("./core/SchemaGenerator"), exports);
21
+ __exportStar(require("./core/JsonataTransformer"), exports);
22
+ __exportStar(require("./core/ConfigParser"), exports);
23
+ __exportStar(require("./core/TransformConfig"), exports);
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "jsonata-w",
3
+ "version": "1.0.0",
4
+ "description": "Tool to assist AI in transforming JSON files",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "jsonata-w": "dist/cli.js"
8
+ },
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsc",
14
+ "cli": "node dist/cli.js $*",
15
+ "test": "jest",
16
+ "lint": "eslint src test",
17
+ "lint:fix": "eslint src test --fix",
18
+ "check": "npm run lint && tsc --noEmit",
19
+ "prepublishOnly": "npm run build"
20
+ },
21
+ "author": "",
22
+ "license": "ISC",
23
+ "dependencies": {
24
+ "@types/jsonpath": "^0.2.4",
25
+ "ajv": "^8.17.1",
26
+ "comment-parser": "^1.4.1",
27
+ "jest-diff": "^30.2.0",
28
+ "js-yaml": "^4.1.1",
29
+ "jsonata": "^2.1.0",
30
+ "jsonpath": "^1.1.1",
31
+ "to-json-schema": "^0.2.5",
32
+ "yargs": "^17.7.2"
33
+ },
34
+ "devDependencies": {
35
+ "@eslint/js": "^9.39.2",
36
+ "@types/jest": "^29.5.14",
37
+ "@types/js-yaml": "^4.0.9",
38
+ "@types/node": "^22.19.3",
39
+ "@types/to-json-schema": "^0.2.4",
40
+ "@types/yargs": "^17.0.33",
41
+ "eslint": "^9.39.2",
42
+ "jest": "^29.7.0",
43
+ "ts-jest": "^29.4.6",
44
+ "typescript": "^5.7.2",
45
+ "typescript-eslint": "^8.51.0"
46
+ }
47
+ }