bestjsonformatter 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Suraj Krishnan Rajan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,39 +1,163 @@
1
- # Best JSON Formatter for agents
1
+ # Best JSON Formatter CLI
2
2
 
3
- Private, deterministic JSON tools for MCP clients, coding agents, CI, and the
4
- terminal. Documents are processed inside the local Node.js process. The package
5
- does not make network requests or collect analytics.
3
+ [![npm](https://img.shields.io/npm/v/bestjsonformatter)](https://www.npmjs.com/package/bestjsonformatter)
4
+ [![CI](https://github.com/skrcode/bestjsonformatter-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/skrcode/bestjsonformatter-cli/actions/workflows/ci.yml)
5
+ [![skills.sh](https://skills.sh/b/skrcode/bestjsonformatter-cli)](https://skills.sh/skrcode/bestjsonformatter-cli/best-json-formatter)
6
6
 
7
- ## MCP
7
+ Fast, local JSON formatting and correctness checks without lossy
8
+ `JSON.parse()`/`JSON.stringify()` round trips.
8
9
 
9
- Add a stdio server with this command:
10
+ ```bash
11
+ npx -y bestjsonformatter@latest check response.json
12
+ npx -y bestjsonformatter@latest format response.json --write
13
+ ```
14
+
15
+ Best JSON Formatter preserves duplicate object properties, integers beyond JavaScript's safe
16
+ range, decimal and exponent spelling, string escape spelling, and property order. It performs no
17
+ network requests, collects no analytics, and requires no MCP server or account.
18
+
19
+ ## Install
20
+
21
+ Use it without installing:
22
+
23
+ ```bash
24
+ npx -y bestjsonformatter@latest check package.json
25
+ ```
26
+
27
+ Or install the short `bjf` command:
28
+
29
+ ```bash
30
+ npm install --global bestjsonformatter
31
+ bjf check package.json
32
+ ```
33
+
34
+ Node.js 20 or newer is required.
35
+
36
+ ## Check JSON
37
+
38
+ `check` validates syntax and finds duplicate keys and numbers that ordinary JavaScript parsing may
39
+ round, overflow, underflow, or rewrite:
40
+
41
+ ```bash
42
+ bjf check response.json
43
+ bjf check response.json --json
44
+ bjf check response.json --schema response.schema.json
45
+ ```
46
+
47
+ Exit codes are stable for CI and coding agents:
48
+
49
+ | Code | Meaning |
50
+ | ---: | --- |
51
+ | `0` | Valid JSON with no selected correctness findings |
52
+ | `1` | Invalid JSON, correctness findings, schema errors, or formatting differences |
53
+ | `2` | Invalid command, inaccessible file, or internal failure |
54
+
55
+ ## Format JSON
56
+
57
+ ```bash
58
+ bjf format response.json
59
+ bjf format response.json --check
60
+ bjf format response.json --write
61
+ bjf format response.json --indent minified
62
+ bjf format response.json --indent tab
63
+ bjf format response.json --sort
64
+ ```
10
65
 
11
- ```text
12
- npx -y bestjsonformatter mcp
66
+ Ordinary formatting changes whitespace only. `--sort` explicitly reorders object properties while
67
+ preserving array order and exact scalar tokens. `--write` uses a same-directory temporary file and
68
+ atomic rename.
69
+
70
+ Multiple files are supported when checking or writing:
71
+
72
+ ```bash
73
+ bjf format package.json fixtures/*.json --check
74
+ bjf format package.json fixtures/*.json --write
75
+ bjf check fixtures/*.json --json
76
+ ```
77
+
78
+ ## Repair, query, compare, convert, and use schemas
79
+
80
+ ```bash
81
+ bjf repair broken.json
82
+ bjf repair broken.json --write
83
+ bjf duplicates response.json
84
+ bjf precision response.json
85
+ bjf compare before.json after.json
86
+ bjf query '$.items[*].id' response.json
87
+ bjf to-yaml response.json
88
+ bjf to-csv records.json
89
+ bjf to-xml response.json
90
+ bjf schema-infer response.json
91
+ bjf schema-validate response.json schema.json
92
+ bjf schema-sample schema.json
13
93
  ```
14
94
 
15
- The server exposes formatting, validation, repair, duplicate-key and
16
- number-precision analysis, structural comparison, JSONPath, YAML/CSV/XML
17
- conversion, and Draft 2020-12 JSON Schema tools.
95
+ Repair output is a proposal unless `--write` is explicitly supplied. Use `-o output.ext` to write
96
+ conversion or repair output to a separate file.
97
+
98
+ ## Coding agents
99
+
100
+ Agents can call the CLI directly through their normal shell, so JSON never has to be copied into an
101
+ MCP tool argument or model context.
18
102
 
19
- ## CLI
103
+ Install the agent skill:
20
104
 
21
105
  ```bash
22
- printf '%s' '{"id":9007199254740993}' | npx bestjsonformatter format --indent 2
23
- npx bestjsonformatter validate response.json
24
- npx bestjsonformatter duplicates response.json
25
- npx bestjsonformatter precision response.json
26
- npx bestjsonformatter compare before.json after.json
27
- npx bestjsonformatter query '$.items[*].id' response.json
106
+ npx skills add skrcode/bestjsonformatter-cli
107
+ ```
108
+
109
+ The skill teaches Codex, Claude Code, Cursor, GitHub Copilot, and other compatible agents to run
110
+ compact checks first and request explicit authorization before `--write`.
111
+
112
+ ## Library
113
+
114
+ The same engine is available as an ESM library:
115
+
116
+ ```ts
117
+ import { checkJson, formatLosslessJson } from "bestjsonformatter";
118
+
119
+ const checked = checkJson(source);
120
+ const formatted = formatLosslessJson(source, 2).formatted;
28
121
  ```
29
122
 
30
- Run `npx bestjsonformatter --help` for every command.
123
+ Heavy repair, diff, query, conversion, and schema dependencies are loaded only when their
124
+ operations are requested. Formatting, validation, and correctness checks stay on the small local
125
+ hot path.
126
+
127
+ ## Performance and correctness
128
+
129
+ In the repository's reproducible five-run benchmark, Best JSON Formatter is the fastest formatter
130
+ that passes every exact-token check.
131
+
132
+ | Fresh-process median | 155 B | 105 KB | 1.07 MB | 5.35 MB | Fully lossless |
133
+ | --- | ---: | ---: | ---: | ---: | :---: |
134
+ | Best JSON Formatter 0.2.0 | **54 ms** | **76 ms** | **143 ms** | **473 ms** | Yes |
135
+ | Biome 2.5.6 | 58 ms | 88 ms | 280 ms | 1,150 ms | No |
136
+ | Prettier 3.9.6 | 120 ms | 234 ms | 955 ms | 3,926 ms | No |
137
+ | jsonc-parser 3.3.1 | 63 ms | 825 ms | >15,000 ms | >15,000 ms | Yes |
138
+ | jq 1.8.2 | 5 ms | 14 ms | 105 ms | 500 ms | No |
31
139
 
32
- ## Correctness
140
+ The comparison uses identical stdin payloads and complete fresh CLI processes. jq is faster on
141
+ small and one-megabyte inputs but discards duplicate keys and rewrites number and escape spelling.
142
+ Results are bounded to the recorded machine, versions, inputs, and command boundaries—not a
143
+ universal speed claim. See [the complete method, raw samples, Python and native Node
144
+ results](docs/BENCHMARKS.md).
145
+
146
+ ## Privacy
147
+
148
+ Documents are read and processed inside the local Node.js process. The package contains no
149
+ telemetry client, post-install script, network request, account system, or hosted document API.
150
+
151
+ ## Development
152
+
153
+ ```bash
154
+ npm install
155
+ npm run check
156
+ npm run benchmark
157
+ ```
33
158
 
34
- Unlike a `JSON.parse()` and `JSON.stringify()` round trip, formatting preserves
35
- duplicate object properties, integers beyond JavaScript's safe range, exponent
36
- notation, and original escape spelling. Repair output is always a proposal for
37
- review rather than an automatic source-file mutation.
159
+ Every formatting change must preserve the exact-token fixtures. Performance changes must record
160
+ the complete journey, competitor versions, raw samples, median, p95, correctness, and timeouts.
38
161
 
39
- Website and documentation: <https://bestjsonformatter.org/agents>
162
+ MIT licensed. The browser tool is available at
163
+ [bestjsonformatter.org](https://bestjsonformatter.org).
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,429 @@
1
+ #!/usr/bin/env node
2
+ import { randomUUID } from "node:crypto";
3
+ import { once } from "node:events";
4
+ import { readFile, rename, stat, unlink, writeFile } from "node:fs/promises";
5
+ import { basename, dirname, join } from "node:path";
6
+ const help = `Best JSON Formatter — fast, local, lossless JSON tools
7
+
8
+ Usage:
9
+ bjf format [file ...] [--indent minified|2|3|4|tab] [--sort] [--write|--check]
10
+ bjf check [file ...] [--schema schema.json] [--json]
11
+ bjf validate [file ...] [--json]
12
+ bjf repair [file|-] [--write|-o output.json]
13
+ bjf duplicates [file|-]
14
+ bjf precision [file|-]
15
+ bjf compare <original.json> <changed.json>
16
+ bjf query <jsonpath> [file|-]
17
+ bjf to-yaml|to-csv|to-xml [file|-] [-o output]
18
+ bjf schema-infer [file|-]
19
+ bjf schema-validate <document.json> <schema.json>
20
+ bjf schema-sample [schema.json|-]
21
+
22
+ Use "-" or pipe stdin for document input. Formatting changes whitespace only:
23
+ duplicate keys, large integers, number notation, string escapes, and key order
24
+ remain exact unless --sort or repair is explicitly requested.
25
+
26
+ Options:
27
+ -w, --write Write the result back atomically
28
+ -c, --check Exit 1 when formatting differs
29
+ -o, --output Write to a separate file
30
+ --json Emit machine-readable check or validation results
31
+ -q, --quiet Suppress successful human-readable status
32
+ -h, --help Show help
33
+ -v, --version Show the installed version`;
34
+ function parseIndent(value) {
35
+ if (value === "minified" || value === "0")
36
+ return 0;
37
+ if (value === "tab")
38
+ return "tab";
39
+ if (value === "2" || value === "3" || value === "4")
40
+ return Number(value);
41
+ throw new TypeError("--indent must be minified, 2, 3, 4, or tab.");
42
+ }
43
+ function parseArguments(args) {
44
+ const options = {
45
+ indent: 2,
46
+ sort: false,
47
+ write: false,
48
+ check: false,
49
+ json: false,
50
+ quiet: false,
51
+ };
52
+ const operands = [];
53
+ for (let index = 0; index < args.length; index += 1) {
54
+ const argument = args[index];
55
+ if (argument === "--") {
56
+ operands.push(...args.slice(index + 1));
57
+ break;
58
+ }
59
+ if (!argument.startsWith("-") || argument === "-") {
60
+ operands.push(argument);
61
+ continue;
62
+ }
63
+ const [name, inlineValue] = argument.split("=", 2);
64
+ const value = () => {
65
+ if (inlineValue !== undefined)
66
+ return inlineValue;
67
+ const next = args[index + 1];
68
+ if (next === undefined)
69
+ throw new TypeError(`${name} requires a value.`);
70
+ index += 1;
71
+ return next;
72
+ };
73
+ switch (name) {
74
+ case "--indent":
75
+ options.indent = parseIndent(value());
76
+ break;
77
+ case "--output":
78
+ case "-o":
79
+ options.output = value();
80
+ break;
81
+ case "--schema":
82
+ options.schema = value();
83
+ break;
84
+ case "--sort":
85
+ options.sort = true;
86
+ break;
87
+ case "--write":
88
+ case "-w":
89
+ options.write = true;
90
+ break;
91
+ case "--check":
92
+ case "-c":
93
+ options.check = true;
94
+ break;
95
+ case "--json":
96
+ options.json = true;
97
+ break;
98
+ case "--quiet":
99
+ case "-q":
100
+ options.quiet = true;
101
+ break;
102
+ default:
103
+ throw new TypeError(`Unknown option: ${name}`);
104
+ }
105
+ }
106
+ return { options, operands };
107
+ }
108
+ async function installedVersion() {
109
+ const packageJson = JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf8"));
110
+ return packageJson.version;
111
+ }
112
+ async function readStdin() {
113
+ const chunks = [];
114
+ for await (const chunk of process.stdin) {
115
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
116
+ }
117
+ return Buffer.concat(chunks).toString("utf8");
118
+ }
119
+ async function readDocument(path) {
120
+ if (path !== "-")
121
+ return readFile(path, "utf8");
122
+ if (process.stdin.isTTY)
123
+ throw new TypeError("Provide a file path or pipe JSON through stdin.");
124
+ return readStdin();
125
+ }
126
+ async function writeStdout(value) {
127
+ if (!process.stdout.write(value))
128
+ await once(process.stdout, "drain");
129
+ }
130
+ function withFinalNewline(value) {
131
+ return `${value.replace(/\n*$/, "")}\n`;
132
+ }
133
+ async function writeAtomically(path, value) {
134
+ const temporaryPath = join(dirname(path), `.${basename(path)}.${process.pid}.${randomUUID()}.tmp`);
135
+ let mode;
136
+ try {
137
+ mode = (await stat(path)).mode;
138
+ }
139
+ catch (error) {
140
+ if (!(error instanceof Error && "code" in error && error.code === "ENOENT"))
141
+ throw error;
142
+ }
143
+ try {
144
+ await writeFile(temporaryPath, value, mode === undefined ? undefined : { mode });
145
+ await rename(temporaryPath, path);
146
+ }
147
+ catch (error) {
148
+ await unlink(temporaryPath).catch(() => undefined);
149
+ throw error;
150
+ }
151
+ }
152
+ function displayPath(path) {
153
+ return path === "-" ? "<stdin>" : path;
154
+ }
155
+ function setFailure() {
156
+ process.exitCode = 1;
157
+ }
158
+ function syntaxFailure(path, error) {
159
+ if (error &&
160
+ typeof error === "object" &&
161
+ "line" in error &&
162
+ "column" in error &&
163
+ "message" in error) {
164
+ const line = String(error.line);
165
+ const column = String(error.column);
166
+ process.stderr.write(`${displayPath(path)}:${line}:${column} ${String(error.message)}\n`);
167
+ }
168
+ else {
169
+ const message = error instanceof Error ? error.message : "Invalid JSON.";
170
+ process.stderr.write(`${displayPath(path)}: ${message}\n`);
171
+ }
172
+ setFailure();
173
+ }
174
+ async function formatCommand(files, options) {
175
+ const paths = files.length ? files : ["-"];
176
+ if (options.write && options.check)
177
+ throw new TypeError("--write and --check cannot be combined.");
178
+ if (options.output && (options.write || options.check || paths.length !== 1)) {
179
+ throw new TypeError("--output requires one input and cannot be combined with --write or --check.");
180
+ }
181
+ if (paths.length > 1 && !options.write && !options.check) {
182
+ throw new TypeError("Multiple format inputs require --write or --check.");
183
+ }
184
+ if ((options.write || options.check) && paths.includes("-")) {
185
+ throw new TypeError("--write and --check require file paths, not stdin.");
186
+ }
187
+ const { JsonSyntaxError, formatLosslessJson } = await import("./lib/lossless-json.js");
188
+ for (const path of paths) {
189
+ const input = await readDocument(path);
190
+ try {
191
+ let output;
192
+ if (options.sort) {
193
+ const { runTool } = await import("./lib/json-tools.js");
194
+ const result = await runTool({
195
+ operation: "sort",
196
+ input,
197
+ indent: options.indent,
198
+ includeEdits: false,
199
+ });
200
+ if (!result.ok) {
201
+ syntaxFailure(path, result);
202
+ continue;
203
+ }
204
+ output = result.value;
205
+ }
206
+ else {
207
+ output = formatLosslessJson(input, options.indent).formatted;
208
+ }
209
+ const rendered = withFinalNewline(output);
210
+ if (options.check) {
211
+ if (input !== rendered) {
212
+ process.stderr.write(`${path}: formatting differs\n`);
213
+ setFailure();
214
+ }
215
+ else if (!options.quiet) {
216
+ process.stdout.write(`${path}: formatted\n`);
217
+ }
218
+ }
219
+ else if (options.write) {
220
+ if (input !== rendered)
221
+ await writeAtomically(path, rendered);
222
+ if (!options.quiet)
223
+ process.stdout.write(`${path}: ${input === rendered ? "unchanged" : "formatted"}\n`);
224
+ }
225
+ else if (options.output) {
226
+ await writeAtomically(options.output, rendered);
227
+ }
228
+ else {
229
+ await writeStdout(rendered);
230
+ }
231
+ }
232
+ catch (error) {
233
+ if (error instanceof JsonSyntaxError)
234
+ syntaxFailure(path, error);
235
+ else
236
+ throw error;
237
+ }
238
+ }
239
+ }
240
+ async function validateCommand(files, options) {
241
+ const paths = files.length ? files : ["-"];
242
+ const { JsonSyntaxError, parseLosslessJson } = await import("./lib/lossless-json.js");
243
+ const results = [];
244
+ for (const path of paths) {
245
+ const input = await readDocument(path);
246
+ try {
247
+ const parsed = parseLosslessJson(input);
248
+ results.push({ path: displayPath(path), valid: true, metadata: parsed.metadata });
249
+ if (!options.json && !options.quiet)
250
+ process.stdout.write(`${displayPath(path)}: valid JSON\n`);
251
+ }
252
+ catch (error) {
253
+ setFailure();
254
+ if (error instanceof JsonSyntaxError) {
255
+ results.push({
256
+ path: displayPath(path),
257
+ valid: false,
258
+ message: error.message,
259
+ line: error.line,
260
+ column: error.column,
261
+ offset: error.offset,
262
+ });
263
+ if (!options.json)
264
+ syntaxFailure(path, error);
265
+ }
266
+ else {
267
+ throw error;
268
+ }
269
+ }
270
+ }
271
+ if (options.json) {
272
+ await writeStdout(`${JSON.stringify(paths.length === 1 ? results[0] : results, null, 2)}\n`);
273
+ }
274
+ }
275
+ async function checkCommand(files, options) {
276
+ const paths = files.length ? files : ["-"];
277
+ const { checkJson } = await import("./lib/json-check.js");
278
+ const schemaInput = options.schema ? await readFile(options.schema, "utf8") : undefined;
279
+ const results = [];
280
+ for (const path of paths) {
281
+ const input = await readDocument(path);
282
+ const result = checkJson(input);
283
+ const schemaErrors = [];
284
+ if (result.valid && schemaInput !== undefined) {
285
+ const { validateJsonSchema } = await import("./lib/json-schema.js");
286
+ const schemaResult = await validateJsonSchema(input, schemaInput);
287
+ schemaErrors.push(...schemaResult.errors);
288
+ }
289
+ const clean = result.valid && result.clean && schemaErrors.length === 0;
290
+ if (!clean)
291
+ setFailure();
292
+ results.push({
293
+ path: displayPath(path),
294
+ ...result,
295
+ ...(schemaInput === undefined
296
+ ? {}
297
+ : { schemaValid: schemaErrors.length === 0, schemaErrors }),
298
+ });
299
+ if (options.json)
300
+ continue;
301
+ if (!result.valid) {
302
+ process.stderr.write(`${displayPath(path)}:${result.line}:${result.column} ${result.message}\n`);
303
+ continue;
304
+ }
305
+ for (const issue of result.issues) {
306
+ const location = issue.locations[0];
307
+ process.stderr.write(`${displayPath(path)}:${location.line}:${location.column} ${issue.severity} ${issue.code} ${issue.path}: ${issue.message}\n`);
308
+ }
309
+ for (const error of schemaErrors) {
310
+ process.stderr.write(`${displayPath(path)}: error schema: ${error}\n`);
311
+ }
312
+ if (clean && !options.quiet) {
313
+ process.stdout.write(`${displayPath(path)}: valid, lossless-safe JSON\n`);
314
+ }
315
+ }
316
+ if (options.json) {
317
+ await writeStdout(`${JSON.stringify(paths.length === 1 ? results[0] : results, null, 2)}\n`);
318
+ }
319
+ }
320
+ async function executeToolCommand(command, operands, options) {
321
+ const { runTool } = await import("./lib/json-tools.js");
322
+ let operation;
323
+ let inputPath = operands[0] ?? "-";
324
+ let secondaryInput;
325
+ let query;
326
+ switch (command) {
327
+ case "repair":
328
+ operation = "repair";
329
+ break;
330
+ case "duplicates":
331
+ operation = "duplicates";
332
+ break;
333
+ case "precision":
334
+ operation = "precision";
335
+ break;
336
+ case "compare":
337
+ if (operands.length !== 2)
338
+ throw new TypeError("compare requires two JSON files.");
339
+ operation = "compare";
340
+ inputPath = operands[0];
341
+ secondaryInput = await readDocument(operands[1]);
342
+ break;
343
+ case "query":
344
+ if (!operands[0])
345
+ throw new TypeError("query requires a JSONPath expression.");
346
+ operation = "jsonpath";
347
+ query = operands[0];
348
+ inputPath = operands[1] ?? "-";
349
+ break;
350
+ case "to-yaml":
351
+ case "to-csv":
352
+ case "to-xml":
353
+ operation = command.slice(3);
354
+ break;
355
+ case "schema-infer":
356
+ operation = "schema";
357
+ break;
358
+ case "schema-validate":
359
+ if (operands.length !== 2) {
360
+ throw new TypeError("schema-validate requires a JSON file and a schema file.");
361
+ }
362
+ operation = "schema_validate";
363
+ inputPath = operands[0];
364
+ secondaryInput = await readDocument(operands[1]);
365
+ break;
366
+ case "schema-sample":
367
+ operation = "schema_sample";
368
+ break;
369
+ default:
370
+ throw new TypeError(`Unknown command: ${command}`);
371
+ }
372
+ if (options.write && command !== "repair") {
373
+ throw new TypeError("--write is supported by format and repair.");
374
+ }
375
+ if (options.output && options.write)
376
+ throw new TypeError("--output and --write cannot be combined.");
377
+ if (options.write && inputPath === "-")
378
+ throw new TypeError("--write requires a file path.");
379
+ const input = await readDocument(inputPath);
380
+ const result = await runTool({
381
+ operation,
382
+ input,
383
+ secondaryInput,
384
+ query,
385
+ indent: options.indent,
386
+ includeEdits: false,
387
+ });
388
+ if (!result.ok) {
389
+ syntaxFailure(inputPath, result);
390
+ return;
391
+ }
392
+ const rendered = withFinalNewline(result.value);
393
+ if (options.write) {
394
+ await writeAtomically(inputPath, rendered);
395
+ if (!options.quiet)
396
+ process.stdout.write(`${inputPath}: repaired\n`);
397
+ }
398
+ else if (options.output) {
399
+ await writeAtomically(options.output, rendered);
400
+ }
401
+ else {
402
+ await writeStdout(rendered);
403
+ }
404
+ }
405
+ async function main(args) {
406
+ const first = args[0];
407
+ if (!first || first === "help" || first === "--help" || first === "-h") {
408
+ process.stdout.write(`${help}\n`);
409
+ return;
410
+ }
411
+ if (first === "--version" || first === "-v" || first === "version") {
412
+ process.stdout.write(`${await installedVersion()}\n`);
413
+ return;
414
+ }
415
+ const { options, operands } = parseArguments(args.slice(1));
416
+ if (first === "format")
417
+ await formatCommand(operands, options);
418
+ else if (first === "validate")
419
+ await validateCommand(operands, options);
420
+ else if (first === "check")
421
+ await checkCommand(operands, options);
422
+ else
423
+ await executeToolCommand(first, operands, options);
424
+ }
425
+ main(process.argv.slice(2)).catch((error) => {
426
+ const message = error instanceof Error ? error.message : "The command failed.";
427
+ process.stderr.write(`bestjsonformatter: ${message}\n`);
428
+ process.exitCode = 2;
429
+ });
@@ -0,0 +1,3 @@
1
+ export { checkJson, type JsonCheckIssue, type JsonCheckResult, type JsonIssueLocation, } from "./lib/json-check.js";
2
+ export { parseJson, runTool, type TextEdit, type ToolFailure, type ToolOperation, type ToolRequest, type ToolResponse, type ToolResult, } from "./lib/json-tools.js";
3
+ export { canonicalJsonNumber, formatLosslessJson, type JsonNode, type JsonParseMetadata, JsonSyntaxError, jsonNodeToNative, type LosslessFormatResult, LosslessNumber, type LosslessParseResult, type LosslessTreeResult, parseLosslessJson, serializeJsonNode, stringifyNativeLosslessly, } from "./lib/lossless-json.js";
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { checkJson, } from "./lib/json-check.js";
2
+ export { parseJson, runTool, } from "./lib/json-tools.js";
3
+ export { canonicalJsonNumber, formatLosslessJson, JsonSyntaxError, jsonNodeToNative, LosslessNumber, parseLosslessJson, serializeJsonNode, stringifyNativeLosslessly, } from "./lib/lossless-json.js";
@@ -0,0 +1,32 @@
1
+ import { type JsonParseMetadata } from "./lossless-json.js";
2
+ export type JsonIssueLocation = {
3
+ line: number;
4
+ column: number;
5
+ offset: number;
6
+ };
7
+ export type JsonCheckIssue = {
8
+ code: "duplicate-key" | "unsafe-number" | "number-overflow" | "number-underflow" | "number-spelling";
9
+ severity: "error" | "warning";
10
+ path: string;
11
+ message: string;
12
+ token?: string;
13
+ locations: JsonIssueLocation[];
14
+ };
15
+ type JsonCheckSuccess = {
16
+ valid: true;
17
+ clean: boolean;
18
+ issues: JsonCheckIssue[];
19
+ metadata: JsonParseMetadata;
20
+ };
21
+ type JsonCheckFailure = {
22
+ valid: false;
23
+ clean: false;
24
+ issues: [];
25
+ message: string;
26
+ line: number;
27
+ column: number;
28
+ offset: number;
29
+ };
30
+ export type JsonCheckResult = JsonCheckSuccess | JsonCheckFailure;
31
+ export declare function checkJson(input: string): JsonCheckResult;
32
+ export {};