json-sort-cli 4.2.3 → 4.3.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/CHANGELOG.md +12 -0
- package/README.md +2 -2
- package/cli-main.js +548 -0
- package/cli-update-notifier.js +793 -0
- package/cli.js +1 -422
- package/package.json +10 -8
- package/process-files.js +112 -100
package/cli.js
CHANGED
|
@@ -1,426 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
import { glob } from "codsen-glob";
|
|
6
|
-
import updateNotifier from "update-notifier";
|
|
7
|
-
import { ProcessingError, processFiles } from "./process-files.js";
|
|
8
|
-
|
|
9
|
-
const require1 = createRequire(import.meta.url);
|
|
10
|
-
const pkg = require1("./package.json");
|
|
11
|
-
const prefix = "✨ json-sort-cli: ";
|
|
12
|
-
const nonJsonFormats = new Set([".yml", ".toml", ".yaml"]);
|
|
13
|
-
const ignoredBasenames = new Set([
|
|
14
|
-
".DS_Store",
|
|
15
|
-
"npm-debug.log",
|
|
16
|
-
".svn",
|
|
17
|
-
"CVS",
|
|
18
|
-
"config.gypi",
|
|
19
|
-
".lock-wscript",
|
|
20
|
-
"package-lock.json",
|
|
21
|
-
"npm-shrinkwrap.json",
|
|
22
|
-
"yarn.lock",
|
|
23
|
-
]);
|
|
24
|
-
const flagDefinitions = {
|
|
25
|
-
arrays: { short: "a", type: "boolean" },
|
|
26
|
-
ci: { short: "c", type: "boolean" },
|
|
27
|
-
dry: { short: "d", type: "boolean" },
|
|
28
|
-
help: { short: "h", type: "boolean" },
|
|
29
|
-
indentationCount: { short: "i", type: "value" },
|
|
30
|
-
lineEnding: { short: "l", type: "value" },
|
|
31
|
-
nodemodules: { short: "n", type: "boolean" },
|
|
32
|
-
pack: { short: "p", type: "boolean" },
|
|
33
|
-
silent: { short: "s", type: "boolean" },
|
|
34
|
-
tabs: { short: "t", type: "boolean" },
|
|
35
|
-
version: { short: "v", type: "boolean" },
|
|
36
|
-
};
|
|
37
|
-
const shortToLong = new Map(
|
|
38
|
-
Object.entries(flagDefinitions).map(([long, definition]) => [
|
|
39
|
-
definition.short,
|
|
40
|
-
long,
|
|
41
|
-
]),
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
const help = `
|
|
45
|
-
Usage
|
|
46
|
-
$ jsonsort YOURFILE.json
|
|
47
|
-
$ sortjson templatesfolder1 templatesfolder2 package.json
|
|
48
|
-
$ jsonsort
|
|
49
|
-
|
|
50
|
-
With no arguments, jsonsort recursively sorts JSON files below the current
|
|
51
|
-
directory. Discovered symbolic links are not followed. An explicitly selected
|
|
52
|
-
symbolic-link directory is resolved as the selected root; symbolic-link files
|
|
53
|
-
are rejected before reading.
|
|
54
|
-
|
|
55
|
-
Options
|
|
56
|
-
-n, --nodemodules Include JSON files inside node_modules; lockfiles remain excluded
|
|
57
|
-
-t, --tabs Use tabs for indentation
|
|
58
|
-
-i, --indentationCount Use 0-10 spaces or tabs (default: 2 spaces or 1 tab)
|
|
59
|
-
-s, --silent Suppress all terminal output; use the exit code for the result
|
|
60
|
-
-h, --help Show this help
|
|
61
|
-
-v, --version Show the current version
|
|
62
|
-
-a, --arrays Sort arrays that contain only strings
|
|
63
|
-
-d, --dry List candidate files without reading or writing them
|
|
64
|
-
-p, --pack Exclude package.json files
|
|
65
|
-
-c, --ci Check without writing; exit 9 when canonical output differs
|
|
66
|
-
-l, --lineEnding Use "cr", "crlf", or "lf" instead of the detected line ending
|
|
67
|
-
|
|
68
|
-
Use -- before a path that begins with a dash. Invalid options exit 1 before
|
|
69
|
-
file discovery. Processing failures exit 1 and don't stop independent files.
|
|
70
|
-
|
|
71
|
-
Example
|
|
72
|
-
$ jsonsort "templates/**/*.json" --arrays --lineEnding lf
|
|
73
|
-
`;
|
|
74
|
-
|
|
75
|
-
function argumentError(message) {
|
|
76
|
-
throw new TypeError(
|
|
77
|
-
`json-sort-cli/parseArguments(): [THROW_ID_01] ${message}`,
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function requestsSilent(rawArguments) {
|
|
82
|
-
let optionsEnded = false;
|
|
83
|
-
for (const argument of rawArguments) {
|
|
84
|
-
if (optionsEnded) {
|
|
85
|
-
continue;
|
|
86
|
-
}
|
|
87
|
-
if (argument === "--") {
|
|
88
|
-
optionsEnded = true;
|
|
89
|
-
} else if (argument === "--silent") {
|
|
90
|
-
return true;
|
|
91
|
-
} else if (argument.startsWith("-") && !argument.startsWith("--")) {
|
|
92
|
-
for (const short of argument.slice(1)) {
|
|
93
|
-
const name = shortToLong.get(short);
|
|
94
|
-
if (!name) {
|
|
95
|
-
break;
|
|
96
|
-
}
|
|
97
|
-
if (name === "silent") {
|
|
98
|
-
return true;
|
|
99
|
-
}
|
|
100
|
-
if (flagDefinitions[name].type === "value") {
|
|
101
|
-
break;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
return false;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function parseArguments(rawArguments) {
|
|
110
|
-
const expanded = rawArguments.flatMap((argument) => {
|
|
111
|
-
const match = argument.match(/^-(i|l)\s+(.+)$/u);
|
|
112
|
-
return match ? [`-${match[1]}`, match[2]] : [argument];
|
|
113
|
-
});
|
|
114
|
-
const flags = Object.fromEntries(
|
|
115
|
-
Object.keys(flagDefinitions).map((name) => [name, false]),
|
|
116
|
-
);
|
|
117
|
-
flags.indentationCount = undefined;
|
|
118
|
-
flags.lineEnding = undefined;
|
|
119
|
-
const input = [];
|
|
120
|
-
const seen = new Set();
|
|
121
|
-
let optionsEnded = false;
|
|
122
|
-
|
|
123
|
-
function setFlag(name, value = true) {
|
|
124
|
-
if (seen.has(name)) {
|
|
125
|
-
argumentError(`Option --${name} was provided more than once`);
|
|
126
|
-
}
|
|
127
|
-
seen.add(name);
|
|
128
|
-
flags[name] = value;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
for (let index = 0; index < expanded.length; index += 1) {
|
|
132
|
-
const argument = expanded[index];
|
|
133
|
-
if (optionsEnded) {
|
|
134
|
-
input.push(argument);
|
|
135
|
-
continue;
|
|
136
|
-
}
|
|
137
|
-
if (argument === "--") {
|
|
138
|
-
optionsEnded = true;
|
|
139
|
-
continue;
|
|
140
|
-
}
|
|
141
|
-
if (!argument.startsWith("-") || argument === "-") {
|
|
142
|
-
input.push(argument);
|
|
143
|
-
continue;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
if (argument.startsWith("--")) {
|
|
147
|
-
const equalsIndex = argument.indexOf("=");
|
|
148
|
-
const name = argument.slice(
|
|
149
|
-
2,
|
|
150
|
-
equalsIndex === -1 ? undefined : equalsIndex,
|
|
151
|
-
);
|
|
152
|
-
const definition = flagDefinitions[name];
|
|
153
|
-
if (!definition) {
|
|
154
|
-
argumentError(`Unknown option --${name}`);
|
|
155
|
-
}
|
|
156
|
-
if (definition.type === "boolean") {
|
|
157
|
-
if (equalsIndex !== -1) {
|
|
158
|
-
argumentError(`Option --${name} doesn't accept a value`);
|
|
159
|
-
}
|
|
160
|
-
setFlag(name);
|
|
161
|
-
continue;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
let value =
|
|
165
|
-
equalsIndex === -1 ? undefined : argument.slice(equalsIndex + 1);
|
|
166
|
-
if (value === undefined) {
|
|
167
|
-
value = expanded[index + 1];
|
|
168
|
-
const flagShapedValue =
|
|
169
|
-
value?.startsWith("-") &&
|
|
170
|
-
!(name === "indentationCount" && /^-\d/u.test(value));
|
|
171
|
-
if (value === undefined || flagShapedValue) {
|
|
172
|
-
argumentError(`Option --${name} requires a value`);
|
|
173
|
-
}
|
|
174
|
-
index += 1;
|
|
175
|
-
}
|
|
176
|
-
if (value === "") {
|
|
177
|
-
argumentError(`Option --${name} requires a value`);
|
|
178
|
-
}
|
|
179
|
-
setFlag(name, value);
|
|
180
|
-
continue;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
let shortFlags = argument.slice(1);
|
|
184
|
-
while (shortFlags.length) {
|
|
185
|
-
const short = shortFlags[0];
|
|
186
|
-
const name = shortToLong.get(short);
|
|
187
|
-
if (!name) {
|
|
188
|
-
argumentError(`Unknown option -${short}`);
|
|
189
|
-
}
|
|
190
|
-
const definition = flagDefinitions[name];
|
|
191
|
-
shortFlags = shortFlags.slice(1);
|
|
192
|
-
if (definition.type === "boolean") {
|
|
193
|
-
setFlag(name);
|
|
194
|
-
continue;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
let value = shortFlags.replace(/^=/u, "");
|
|
198
|
-
shortFlags = "";
|
|
199
|
-
if (!value) {
|
|
200
|
-
value = expanded[index + 1];
|
|
201
|
-
const flagShapedValue =
|
|
202
|
-
value?.startsWith("-") &&
|
|
203
|
-
!(name === "indentationCount" && /^-\d/u.test(value));
|
|
204
|
-
if (value === undefined || flagShapedValue) {
|
|
205
|
-
argumentError(`Option -${short} requires a value`);
|
|
206
|
-
}
|
|
207
|
-
index += 1;
|
|
208
|
-
}
|
|
209
|
-
setFlag(name, value);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
const defaultIndentation = flags.tabs ? 1 : 2;
|
|
214
|
-
const indentationCount =
|
|
215
|
-
flags.indentationCount === undefined
|
|
216
|
-
? defaultIndentation
|
|
217
|
-
: Number(flags.indentationCount);
|
|
218
|
-
if (
|
|
219
|
-
!Number.isInteger(indentationCount) ||
|
|
220
|
-
indentationCount < 0 ||
|
|
221
|
-
indentationCount > 10
|
|
222
|
-
) {
|
|
223
|
-
argumentError("indentationCount must be an integer from 0 to 10");
|
|
224
|
-
}
|
|
225
|
-
if (
|
|
226
|
-
flags.lineEnding !== undefined &&
|
|
227
|
-
!["cr", "crlf", "lf"].includes(flags.lineEnding)
|
|
228
|
-
) {
|
|
229
|
-
argumentError('lineEnding must be "cr", "crlf", or "lf"');
|
|
230
|
-
}
|
|
231
|
-
if (
|
|
232
|
-
input.length === 0 &&
|
|
233
|
-
expanded.length > 0 &&
|
|
234
|
-
!flags.help &&
|
|
235
|
-
!flags.version
|
|
236
|
-
) {
|
|
237
|
-
argumentError(
|
|
238
|
-
"Provide at least one path, or run jsonsort with no arguments",
|
|
239
|
-
);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
return {
|
|
243
|
-
flags,
|
|
244
|
-
indentationCount,
|
|
245
|
-
input: input.length ? input : ["**/*.json"],
|
|
246
|
-
};
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
function isCandidate(filePath) {
|
|
250
|
-
const basename = path.basename(filePath);
|
|
251
|
-
if (ignoredBasenames.has(basename)) {
|
|
252
|
-
return false;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
const extension = path.extname(filePath).toLowerCase();
|
|
256
|
-
return (
|
|
257
|
-
extension === ".json" ||
|
|
258
|
-
(basename.startsWith(".") && !nonJsonFormats.has(extension))
|
|
259
|
-
);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
function createPrinter() {
|
|
263
|
-
const useColour = Boolean(process.stdout.isTTY && !process.env.NO_COLOR);
|
|
264
|
-
const colours = { green: 32, grey: 90, red: 31, white: 37, yellow: 33 };
|
|
265
|
-
const colour = (value, name) =>
|
|
266
|
-
useColour ? `\u001b[${colours[name]}m${value}\u001b[39m` : value;
|
|
267
|
-
return { colour };
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
async function main() {
|
|
271
|
-
const rawArguments = process.argv.slice(2);
|
|
272
|
-
const silentRequested = requestsSilent(rawArguments);
|
|
273
|
-
let parsed;
|
|
274
|
-
try {
|
|
275
|
-
parsed = parseArguments(rawArguments);
|
|
276
|
-
} catch (error) {
|
|
277
|
-
if (!silentRequested) {
|
|
278
|
-
console.error(error.message);
|
|
279
|
-
}
|
|
280
|
-
process.exitCode = 1;
|
|
281
|
-
return;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
const { flags, indentationCount, input } = parsed;
|
|
285
|
-
if (flags.version) {
|
|
286
|
-
console.log(pkg.version);
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
289
|
-
if (flags.help) {
|
|
290
|
-
console.log(help);
|
|
291
|
-
return;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
if (!flags.silent && !flags.ci && process.stdout.isTTY) {
|
|
295
|
-
try {
|
|
296
|
-
updateNotifier({ pkg }).notify();
|
|
297
|
-
} catch {}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
const { colour } = createPrinter();
|
|
301
|
-
let paths;
|
|
302
|
-
try {
|
|
303
|
-
paths = await glob(
|
|
304
|
-
[
|
|
305
|
-
...input,
|
|
306
|
-
"!**/package-lock.json",
|
|
307
|
-
"!**/npm-shrinkwrap.json",
|
|
308
|
-
"!**/yarn.lock",
|
|
309
|
-
...(flags.nodemodules ? [] : ["!**/node_modules/**"]),
|
|
310
|
-
...(flags.pack ? ["!**/package.json"] : []),
|
|
311
|
-
],
|
|
312
|
-
{
|
|
313
|
-
dot: true,
|
|
314
|
-
expandDirectories: { files: [".*", "*.json", "*.JSON"] },
|
|
315
|
-
followSymbolicLinks: false,
|
|
316
|
-
},
|
|
317
|
-
);
|
|
318
|
-
paths = paths.filter(isCandidate);
|
|
319
|
-
} catch (error) {
|
|
320
|
-
if (!flags.silent) {
|
|
321
|
-
console.error(`${prefix}${error}`);
|
|
322
|
-
}
|
|
323
|
-
process.exitCode = 1;
|
|
324
|
-
return;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
if (!paths.length) {
|
|
328
|
-
if (!flags.silent) {
|
|
329
|
-
console.log(`${prefix}The inputs don't lead to any JSON files. Exiting.`);
|
|
330
|
-
}
|
|
331
|
-
return;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
if (flags.dry) {
|
|
335
|
-
if (!flags.silent) {
|
|
336
|
-
console.log(
|
|
337
|
-
`${prefix}We'd try to sort the following files:\n${paths.join("\n")}`,
|
|
338
|
-
);
|
|
339
|
-
}
|
|
340
|
-
return;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
try {
|
|
344
|
-
const { successful, unsorted } = await processFiles(paths, {
|
|
345
|
-
arrays: flags.arrays,
|
|
346
|
-
ci: flags.ci,
|
|
347
|
-
indentationCount,
|
|
348
|
-
lineEnding: flags.lineEnding || undefined,
|
|
349
|
-
pack: flags.pack,
|
|
350
|
-
tabs: flags.tabs,
|
|
351
|
-
onOutcome(outcome) {
|
|
352
|
-
if (flags.silent) {
|
|
353
|
-
return;
|
|
354
|
-
}
|
|
355
|
-
if (outcome.status === "failure") {
|
|
356
|
-
console.error(
|
|
357
|
-
`${prefix}${outcome.path} - BAD (${outcome.stage}) - ${outcome.error}`,
|
|
358
|
-
);
|
|
359
|
-
} else if (!flags.ci) {
|
|
360
|
-
console.log(`${prefix}${outcome.path} - OK`);
|
|
361
|
-
}
|
|
362
|
-
},
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
if (flags.silent) {
|
|
366
|
-
if (flags.ci && unsorted.length) {
|
|
367
|
-
process.exitCode = 9;
|
|
368
|
-
}
|
|
369
|
-
return;
|
|
370
|
-
}
|
|
371
|
-
if (flags.ci) {
|
|
372
|
-
if (unsorted.length) {
|
|
373
|
-
console.log(
|
|
374
|
-
`${prefix}${colour("Unsorted files:", "red")}\n${unsorted.join("\n")}`,
|
|
375
|
-
);
|
|
376
|
-
process.exitCode = 9;
|
|
377
|
-
} else {
|
|
378
|
-
console.log(
|
|
379
|
-
`${prefix}${colour("All files were already sorted:", "white")}\n${successful.join("\n")}`,
|
|
380
|
-
);
|
|
381
|
-
}
|
|
382
|
-
return;
|
|
383
|
-
}
|
|
384
|
-
console.log(
|
|
385
|
-
`\n${prefix}${colour(
|
|
386
|
-
`All ${successful.length} file${successful.length === 1 ? "" : "s"} sorted`,
|
|
387
|
-
"green",
|
|
388
|
-
)}`,
|
|
389
|
-
);
|
|
390
|
-
} catch (error) {
|
|
391
|
-
if (!(error instanceof ProcessingError)) {
|
|
392
|
-
if (!flags.silent) {
|
|
393
|
-
console.error(`${prefix}${error}`);
|
|
394
|
-
}
|
|
395
|
-
process.exitCode = 1;
|
|
396
|
-
return;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
if (!flags.silent) {
|
|
400
|
-
if (flags.ci) {
|
|
401
|
-
const unsorted = new Set(error.unsorted);
|
|
402
|
-
const alreadySorted = error.successful.filter(
|
|
403
|
-
(filePath) => !unsorted.has(filePath),
|
|
404
|
-
);
|
|
405
|
-
if (alreadySorted.length) {
|
|
406
|
-
console.log(
|
|
407
|
-
`${prefix}${alreadySorted.length} file${alreadySorted.length === 1 ? "" : "s"} already sorted:\n${alreadySorted.join("\n")}`,
|
|
408
|
-
);
|
|
409
|
-
}
|
|
410
|
-
if (error.unsorted.length) {
|
|
411
|
-
console.log(`${prefix}Unsorted files:\n${error.unsorted.join("\n")}`);
|
|
412
|
-
}
|
|
413
|
-
} else if (error.successful.length) {
|
|
414
|
-
console.log(
|
|
415
|
-
`\n${prefix}${error.successful.length} file${error.successful.length === 1 ? "" : "s"} sorted`,
|
|
416
|
-
);
|
|
417
|
-
}
|
|
418
|
-
console.error(
|
|
419
|
-
`${prefix}${error.failures.length} file${error.failures.length === 1 ? "" : "s"} could not be ${flags.ci ? "checked" : "sorted"} - ${error.failures.map(({ path: failedPath }) => failedPath).join(" - ")}`,
|
|
420
|
-
);
|
|
421
|
-
}
|
|
422
|
-
process.exitCode = 1;
|
|
423
|
-
}
|
|
424
|
-
}
|
|
3
|
+
import { main } from "./cli-main.js";
|
|
425
4
|
|
|
426
5
|
await main();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "json-sort-cli",
|
|
3
|
-
"version": "4.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "4.3.1",
|
|
4
|
+
"description": "Deep-sort JSON files or standard input; package.json retains its special key order",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"app",
|
|
7
7
|
"cli",
|
|
@@ -52,9 +52,12 @@
|
|
|
52
52
|
},
|
|
53
53
|
"c8": {
|
|
54
54
|
"all": true,
|
|
55
|
+
"branches": 100,
|
|
55
56
|
"check-coverage": true,
|
|
56
|
-
"exclude": ["**/test/**/*.*"],
|
|
57
|
-
"
|
|
57
|
+
"exclude": ["**/test/**/*.*", "cli-update-notifier.js"],
|
|
58
|
+
"functions": 100,
|
|
59
|
+
"lines": 100,
|
|
60
|
+
"statements": 100
|
|
58
61
|
},
|
|
59
62
|
"lect": {
|
|
60
63
|
"licence": {
|
|
@@ -62,12 +65,11 @@
|
|
|
62
65
|
}
|
|
63
66
|
},
|
|
64
67
|
"dependencies": {
|
|
65
|
-
"codsen-glob": "^1.1.
|
|
66
|
-
"sort-package-json": "^2.15.1"
|
|
67
|
-
"update-notifier": "^7.3.1"
|
|
68
|
+
"codsen-glob": "^1.1.2",
|
|
69
|
+
"sort-package-json": "^2.15.1"
|
|
68
70
|
},
|
|
69
71
|
"devDependencies": {
|
|
70
|
-
"p-map": "^7.0.
|
|
72
|
+
"p-map": "^7.0.7"
|
|
71
73
|
},
|
|
72
74
|
"engines": {
|
|
73
75
|
"node": ">=18.20.8"
|