@sheplu/editorconfig 0.13.0 → 0.14.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 +33 -0
- package/index.js +5 -1
- package/package.json +1 -1
- package/src/check-recursive.js +78 -10
- package/src/check.js +31 -4
- package/src/cli/dispatch.js +6 -5
- package/src/cli/options.js +12 -1
package/README.md
CHANGED
|
@@ -183,6 +183,39 @@ The custom template must follow the same semantics as the built-ins:
|
|
|
183
183
|
|
|
184
184
|
URL fetching is constrained for safety: `https://` only (`http://` is rejected before any network call), redirects must stay on https (max 5 hops), 10-second timeout, 1 MB response cap. Templates are fetched on every invocation — there is no local cache.
|
|
185
185
|
|
|
186
|
+
### `--json` (machine-readable check output)
|
|
187
|
+
|
|
188
|
+
`check` accepts a `--json` flag that swaps the human-readable report for a structured JSON document on stdout. The exit code is unchanged (`0` pass, `1` fail), so it stays drop-in for CI while letting dashboards and scripts parse the result.
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
npx @sheplu/editorconfig --mode=check --json
|
|
192
|
+
npx @sheplu/editorconfig --mode=check --recursive --json
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Single-file shape:
|
|
196
|
+
|
|
197
|
+
```json
|
|
198
|
+
{
|
|
199
|
+
"mode": "check",
|
|
200
|
+
"path": ".editorconfig",
|
|
201
|
+
"ok": true,
|
|
202
|
+
"summary": { "total": 1, "matched": 1, "failed": 0, "unknown": 0 },
|
|
203
|
+
"sections": [
|
|
204
|
+
{ "header": "[*]", "status": "match", "detail": "" }
|
|
205
|
+
]
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
In `--recursive` mode the payload instead carries `recursive: true`, a `files` array (each with its `role`, `summary`, and `sections`) and a `crossFileIssues` array describing `child-root` / `redundant` / `contradiction` findings. When no JSON output is requested, the human-readable report is printed exactly as before.
|
|
210
|
+
|
|
211
|
+
### `--version`
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
npx @sheplu/editorconfig --version
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Prints the installed package version (also available as `-v`).
|
|
218
|
+
|
|
186
219
|
### `--help`
|
|
187
220
|
|
|
188
221
|
```bash
|
package/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { compareEditorConfig } from './src/check.js';
|
|
4
|
-
import { parseCliArgs, printHelp } from './src/cli/options.js';
|
|
4
|
+
import { parseCliArgs, printHelp, printVersion } from './src/cli/options.js';
|
|
5
5
|
import { dispatchValues } from './src/cli/dispatch.js';
|
|
6
6
|
import { createEditorConfig } from './src/cli/write-flow.js';
|
|
7
7
|
|
|
@@ -16,6 +16,10 @@ async function main() {
|
|
|
16
16
|
printHelp();
|
|
17
17
|
return;
|
|
18
18
|
}
|
|
19
|
+
if (parsed.values.version) {
|
|
20
|
+
printVersion();
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
19
23
|
await dispatchValues(parsed.values);
|
|
20
24
|
};
|
|
21
25
|
|
package/package.json
CHANGED
package/src/check-recursive.js
CHANGED
|
@@ -4,9 +4,11 @@ import { discoverEditorConfigs } from './discover.js';
|
|
|
4
4
|
import { classify, crossFileIssues } from './cascade.js';
|
|
5
5
|
import {
|
|
6
6
|
compareEditorConfigForRole,
|
|
7
|
+
identityReplacer,
|
|
7
8
|
NO_LANGUAGE_FILTER,
|
|
8
9
|
printReport,
|
|
9
10
|
reportIsFailing,
|
|
11
|
+
reportToSections,
|
|
10
12
|
summarizeReport,
|
|
11
13
|
} from './check.js';
|
|
12
14
|
import { logger } from './utils/logger.js';
|
|
@@ -162,21 +164,23 @@ function formatGlobalSummary({ entries, crossIssues, strict }) {
|
|
|
162
164
|
return { line: `${summaryHead(passed)} — ${parts.join('; ')}`, failed: !passed };
|
|
163
165
|
}
|
|
164
166
|
|
|
165
|
-
function processTree({ tree, parsedLanguages, overrides, startDir }) {
|
|
167
|
+
function processTree({ tree, parsedLanguages, overrides, startDir, json }) {
|
|
166
168
|
const entries = buildFileEntries(tree, parsedLanguages, overrides);
|
|
167
169
|
const [rootEntry, ...childEntries] = entries;
|
|
168
170
|
const crossIssues = collectCrossIssues(rootEntry, childEntries);
|
|
169
|
-
|
|
170
|
-
|
|
171
|
+
if (!json) {
|
|
172
|
+
for (const entry of entries) {
|
|
173
|
+
printFileBlock(entry, startDir);
|
|
174
|
+
}
|
|
171
175
|
}
|
|
172
176
|
return { entries, crossIssues };
|
|
173
177
|
}
|
|
174
178
|
|
|
175
|
-
function gatherAll({ trees, parsedLanguages, overrides, startDir }) {
|
|
179
|
+
function gatherAll({ trees, parsedLanguages, overrides, startDir, json }) {
|
|
176
180
|
const allEntries = [];
|
|
177
181
|
const allCrossIssues = [];
|
|
178
182
|
for (const tree of trees) {
|
|
179
|
-
const { entries, crossIssues } = processTree({ tree, parsedLanguages, overrides, startDir });
|
|
183
|
+
const { entries, crossIssues } = processTree({ tree, parsedLanguages, overrides, startDir, json });
|
|
180
184
|
allEntries.push(...entries);
|
|
181
185
|
allCrossIssues.push(...crossIssues);
|
|
182
186
|
}
|
|
@@ -192,18 +196,82 @@ function reportAndExit({ allEntries, allCrossIssues, strict, startDir }) {
|
|
|
192
196
|
}
|
|
193
197
|
}
|
|
194
198
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
199
|
+
function jsonFileEntry(entry, startDir, strict) {
|
|
200
|
+
return {
|
|
201
|
+
path: displayPath(entry.path, startDir),
|
|
202
|
+
role: entry.role,
|
|
203
|
+
failed: reportIsFailing(entry.report, strict),
|
|
204
|
+
summary: summarizeReport(entry.report),
|
|
205
|
+
sections: reportToSections(entry.report),
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function jsonCrossIssue(issue, startDir) {
|
|
210
|
+
const copy = {};
|
|
211
|
+
for (const [key, value] of Object.entries(issue)) {
|
|
212
|
+
copy[key] = value;
|
|
213
|
+
}
|
|
214
|
+
copy.file = displayPath(issue.file, startDir);
|
|
215
|
+
return copy;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function buildRecursiveJson({ allEntries, allCrossIssues, strict, startDir }) {
|
|
219
|
+
const summary = formatGlobalSummary({ entries: allEntries, crossIssues: allCrossIssues, strict });
|
|
220
|
+
return {
|
|
221
|
+
mode: 'check',
|
|
222
|
+
recursive: true,
|
|
223
|
+
startDir,
|
|
224
|
+
ok: !summary.failed,
|
|
225
|
+
files: allEntries.map((entry) => jsonFileEntry(entry, startDir, strict)),
|
|
226
|
+
crossFileIssues: allCrossIssues.map((issue) => jsonCrossIssue(issue, startDir)),
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function emitJson(payload) {
|
|
231
|
+
logger.log(JSON.stringify(payload, identityReplacer, 2));
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function reportRecursiveJson(payload) {
|
|
235
|
+
const json = buildRecursiveJson(payload);
|
|
236
|
+
emitJson(json);
|
|
237
|
+
if (!json.ok) {
|
|
238
|
+
process.exitCode = 1;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function emitEmptyRecursiveJson(startDir) {
|
|
243
|
+
emitJson({ mode: 'check', recursive: true, startDir, ok: true, files: [], crossFileIssues: [] });
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function reportEmpty(startDir, json) {
|
|
247
|
+
if (json) {
|
|
248
|
+
emitEmptyRecursiveJson(startDir);
|
|
200
249
|
return;
|
|
201
250
|
}
|
|
251
|
+
logger.log(`No .editorconfig files found under ${startDir}`);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function runWalk({ startDir, paths, parsedLanguages, strict, overrides, json }) {
|
|
202
255
|
const { allEntries, allCrossIssues } = gatherAll({
|
|
203
256
|
trees: classify(paths),
|
|
204
257
|
parsedLanguages,
|
|
205
258
|
overrides,
|
|
206
259
|
startDir,
|
|
260
|
+
json,
|
|
207
261
|
});
|
|
262
|
+
if (json) {
|
|
263
|
+
reportRecursiveJson({ allEntries, allCrossIssues, strict, startDir });
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
208
266
|
reportAndExit({ allEntries, allCrossIssues, strict, startDir });
|
|
209
267
|
}
|
|
268
|
+
|
|
269
|
+
export function runCheckRecursive({ startDir: rawStart, parsedLanguages, strict, overrides, json }) {
|
|
270
|
+
const startDir = resolveStartDir(rawStart);
|
|
271
|
+
const paths = discoverEditorConfigs(startDir);
|
|
272
|
+
if (paths.length === 0) {
|
|
273
|
+
reportEmpty(startDir, json);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
runWalk({ startDir, paths, parsedLanguages, strict, overrides, json });
|
|
277
|
+
}
|
package/src/check.js
CHANGED
|
@@ -143,7 +143,7 @@ function formatLine({ header, status }) {
|
|
|
143
143
|
export function reportIsFailing({ baseIssues, results }, strict) {
|
|
144
144
|
const failing = [...baseIssues, ...results].some((entry) => FAILING_STATUSES.has(entry.status));
|
|
145
145
|
const hasUnknown = results.some((entry) => entry.status === 'unknown');
|
|
146
|
-
return failing || (strict && hasUnknown);
|
|
146
|
+
return failing || Boolean(strict && hasUnknown);
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
function buildHeading(displayPath, label) {
|
|
@@ -208,11 +208,38 @@ function formatSummary(report, isFailure) {
|
|
|
208
208
|
return formatPassSummary(counts);
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
-
export function
|
|
211
|
+
export function identityReplacer(_key, value) {
|
|
212
|
+
return value;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function reportToSections(report) {
|
|
216
|
+
return [...report.baseIssues, ...report.results].map(({ header, status }) => ({
|
|
217
|
+
header,
|
|
218
|
+
status,
|
|
219
|
+
detail: STATUS_DETAIL[status],
|
|
220
|
+
}));
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export function buildCheckJson({ path, report, failed }) {
|
|
224
|
+
return {
|
|
225
|
+
mode: 'check',
|
|
226
|
+
path,
|
|
227
|
+
ok: !failed,
|
|
228
|
+
summary: summarizeReport(report),
|
|
229
|
+
sections: reportToSections(report),
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export function runCheck({ path, parsedLanguages, strict, overrides, json }) {
|
|
212
234
|
const report = compareEditorConfig(path, parsedLanguages, overrides);
|
|
213
|
-
printReport(path, report);
|
|
214
235
|
const failed = reportIsFailing(report, strict);
|
|
215
|
-
|
|
236
|
+
if (json) {
|
|
237
|
+
logger.log(JSON.stringify(buildCheckJson({ path, report, failed }), identityReplacer, 2));
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
printReport(path, report);
|
|
241
|
+
logger.log(formatSummary(report, failed));
|
|
242
|
+
}
|
|
216
243
|
if (failed) {
|
|
217
244
|
process.exitCode = 1;
|
|
218
245
|
}
|
package/src/cli/dispatch.js
CHANGED
|
@@ -32,17 +32,17 @@ function resolveCheckPath(values) {
|
|
|
32
32
|
return '.editorconfig';
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
function dispatchCheck({ path, languages, strict, overrides, recursive }) {
|
|
35
|
+
function dispatchCheck({ path, languages, strict, overrides, recursive, json }) {
|
|
36
36
|
let filter = languages;
|
|
37
37
|
if (filter === NOT_PROVIDED) {
|
|
38
38
|
filter = NO_LANGUAGE_FILTER;
|
|
39
39
|
}
|
|
40
40
|
try {
|
|
41
41
|
if (recursive) {
|
|
42
|
-
runCheckRecursive({ startDir: path, parsedLanguages: filter, strict, overrides });
|
|
42
|
+
runCheckRecursive({ startDir: path, parsedLanguages: filter, strict, overrides, json });
|
|
43
43
|
return;
|
|
44
44
|
}
|
|
45
|
-
runCheck({ path, parsedLanguages: filter, strict, overrides });
|
|
45
|
+
runCheck({ path, parsedLanguages: filter, strict, overrides, json });
|
|
46
46
|
}
|
|
47
47
|
catch (error) {
|
|
48
48
|
logger.error(error.message);
|
|
@@ -69,13 +69,13 @@ async function handleWrite({ path, overwrite, languages, overrides, recursive })
|
|
|
69
69
|
await dispatchWrite({ path, overwrite, languages, overrides });
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
async function runCommand({ mode, path, overwrite, languages, strict, overrides, recursive }) {
|
|
72
|
+
async function runCommand({ mode, path, overwrite, languages, strict, overrides, recursive, json }) {
|
|
73
73
|
if (mode === 'write') {
|
|
74
74
|
await handleWrite({ path, overwrite, languages, overrides, recursive });
|
|
75
75
|
return;
|
|
76
76
|
}
|
|
77
77
|
if (mode === 'check') {
|
|
78
|
-
dispatchCheck({ path, languages, strict, overrides, recursive });
|
|
78
|
+
dispatchCheck({ path, languages, strict, overrides, recursive, json });
|
|
79
79
|
return;
|
|
80
80
|
}
|
|
81
81
|
logger.error('invalid command');
|
|
@@ -95,5 +95,6 @@ export async function dispatchValues(values) {
|
|
|
95
95
|
strict: values.strict,
|
|
96
96
|
overrides,
|
|
97
97
|
recursive: Boolean(values.recursive),
|
|
98
|
+
json: Boolean(values.json),
|
|
98
99
|
});
|
|
99
100
|
}
|
package/src/cli/options.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
1
2
|
import { parseArgs } from 'node:util';
|
|
2
3
|
import {
|
|
3
4
|
ALIASES,
|
|
@@ -10,10 +11,18 @@ export const options = {
|
|
|
10
11
|
path: { type: 'string', short: 'p' },
|
|
11
12
|
languages: { type: 'string', short: 'l' },
|
|
12
13
|
help: { type: 'boolean', short: 'h' },
|
|
14
|
+
version: { type: 'boolean', short: 'v' },
|
|
13
15
|
overwrite: { type: 'boolean', short: 'o' },
|
|
14
16
|
strict: { type: 'boolean', short: 's' },
|
|
15
17
|
template: { type: 'string', short: 't' },
|
|
16
18
|
recursive: { type: 'boolean', short: 'r' },
|
|
19
|
+
json: { type: 'boolean' },
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export function printVersion() {
|
|
23
|
+
const pkgUrl = new URL('../../package.json', import.meta.url);
|
|
24
|
+
const { version } = JSON.parse(readFileSync(pkgUrl, 'utf8'));
|
|
25
|
+
logger.log(version);
|
|
17
26
|
};
|
|
18
27
|
|
|
19
28
|
export const NOT_PROVIDED = Symbol('languages-not-provided');
|
|
@@ -41,7 +50,7 @@ function formatAliasList() {
|
|
|
41
50
|
}
|
|
42
51
|
|
|
43
52
|
export function printHelp() {
|
|
44
|
-
logger.log(`Usage: editorconfig --mode=<command> [--path=<path>] [--languages=<list>] [--template=<path|url>] [--recursive]
|
|
53
|
+
logger.log(`Usage: editorconfig --mode=<command> [--path=<path>] [--languages=<list>] [--template=<path|url>] [--recursive] [--json]
|
|
45
54
|
|
|
46
55
|
Commands:
|
|
47
56
|
write Create a .editorconfig file with the selected language sections
|
|
@@ -55,6 +64,8 @@ Options:
|
|
|
55
64
|
-s, --strict Treat unknown section headers as failures (check only)
|
|
56
65
|
-t, --template Path or https URL to a custom .editorconfig-syntax file whose sections override the built-in ones
|
|
57
66
|
-r, --recursive Check only. Walk the start directory and validate every .editorconfig (root + children) with cascade checks
|
|
67
|
+
--json Emit check results as JSON instead of human-readable text (check only)
|
|
68
|
+
-v, --version Show the installed version
|
|
58
69
|
-h, --help Show this help message
|
|
59
70
|
|
|
60
71
|
Languages: ${AVAILABLE_LANGUAGES.join(', ')}
|