@sheplu/editorconfig 0.8.7 → 0.10.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 CHANGED
@@ -4,8 +4,8 @@ A small CLI to manage a **consistent `.editorconfig`** across your projects.
4
4
 
5
5
  - ✅ Generate a sane default `.editorconfig` in seconds
6
6
  - ✅ Check if your existing file matches the target setup
7
- - 🔜 Propose updates (with confirmation) instead of blindly overwriting
8
- - 🔜 Compare your file against the recommended template
7
+ - Confirm before overwriting an existing `.editorconfig` (or pass `--overwrite` to skip the prompt)
8
+ - Override the built-in template with a team-shared file via `--template` (local path or `https://` URL)
9
9
 
10
10
  ## Why?
11
11
 
@@ -49,8 +49,8 @@ npx @sheplu/editorconfig --mode=write
49
49
  This will:
50
50
 
51
51
  - Create a `.editorconfig` file if it doesn’t exist
52
- - (Current behavior) Write the default template
53
- - (Future behavior) Ask before overwriting an existing file
52
+ - Write the default template
53
+ - Ask before overwriting an existing file (or fail in non-interactive contexts unless `--overwrite` is passed)
54
54
 
55
55
  ## Current Features
56
56
 
@@ -62,6 +62,23 @@ npx @sheplu/editorconfig --mode=write
62
62
 
63
63
  Creates a base `.editorconfig` file in the current directory.
64
64
 
65
+ If a `.editorconfig` already exists at the target path, the CLI will:
66
+
67
+ - Prompt for confirmation in an interactive terminal (`y` / `yes` to overwrite, anything else keeps the file).
68
+ - Exit non-zero in non-interactive contexts (CI, redirected stdin) so a script never silently destroys an existing config.
69
+
70
+ Pass `--overwrite` (or `-o`) to bypass the prompt and force a rewrite:
71
+
72
+ ```bash
73
+ npx @sheplu/editorconfig --mode=write --overwrite
74
+ ```
75
+
76
+ You can also point at a custom path:
77
+
78
+ ```bash
79
+ npx @sheplu/editorconfig --mode=write --path=path/to/.editorconfig
80
+ ```
81
+
65
82
  Typical content (example):
66
83
 
67
84
  ```ini
@@ -96,6 +113,36 @@ This command will:
96
113
  - `0` if everything matches
97
114
  - `1` if differences are found
98
115
 
116
+ ### `--template` (custom team template)
117
+
118
+ Both `write` and `check` accept a `--template` (or `-t`) flag pointing at a custom `.editorconfig`-syntax file. Sections in that file override the built-in defaults; languages it doesn't redefine still come from the built-ins. This lets a team host a single source of truth and reference it from every repo.
119
+
120
+ The flag accepts either a local path or an `https://` URL:
121
+
122
+ ```bash
123
+ # local file
124
+ npx @sheplu/editorconfig --mode=write --template=./team.editorconfig
125
+
126
+ # remote URL
127
+ npx @sheplu/editorconfig --mode=check --template=https://raw.githubusercontent.com/example/team-config/main/.editorconfig
128
+ ```
129
+
130
+ The custom template must follow the same semantics as the built-ins:
131
+
132
+ - Use only known section headers (`[*]`, `[*.md]`, `[*.{js,jsx,...}]`, etc.). Unknown headers (like `[*.proto]`) are rejected.
133
+ - Include `root = true` in the preamble whenever the template redefines `[*]`.
134
+ - Each header may appear at most once.
135
+
136
+ 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.
137
+
138
+ ### `--help`
139
+
140
+ ```bash
141
+ npx @sheplu/editorconfig --help
142
+ ```
143
+
144
+ Prints the full usage, the available commands, and every supported option.
145
+
99
146
  ## Planned / Upcoming Features
100
147
 
101
148
  ### 1. Interactive update / replace
@@ -115,10 +162,27 @@ npx @sheplu/editorconfig --mode=diff
115
162
  - [ ] Add diff logic and `diff` command
116
163
  - [ ] Add interactive `fix` / `update` command
117
164
  - [ ] Expose presets or configuration options
118
- - [ ] Add tests and CI examples
119
165
 
120
166
  Additional properties can be found on the [editorconfig wiki](https://github.com/editorconfig/editorconfig/wiki/editorconfig-properties).
121
167
 
168
+ ## Tests
169
+
170
+ Tests live under `test/` and are split by scope:
171
+
172
+ - `test/unit/` — fast, in-process tests of exported functions (no spawning, no I/O beyond a tmp dir).
173
+ - `test/integration/` — full CLI runs. The interactive prompt path is exercised in a real pseudoterminal via [`node-pty`](https://github.com/microsoft/node-pty); the rest go through `child_process.spawnSync` with a closed stdin.
174
+
175
+ Run them with:
176
+
177
+ ```bash
178
+ npm test # everything
179
+ npm run test:unit # unit only — runs in ~50ms
180
+ npm run test:integration # integration only — spawns the CLI
181
+ npm run lint # oxlint
182
+ ```
183
+
184
+ CI runs lint, audit, and the full suite on every PR across Node 24 / 26.
185
+
122
186
  ## Documentation
123
187
 
124
188
  [Editorconfig documentation](https://github.com/editorconfig/editorconfig/wiki/editorconfig-properties)
package/index.js CHANGED
@@ -1,60 +1,286 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { readFileSync, writeFileSync } from "node:fs";
3
+ import { existsSync, writeFileSync } from "node:fs";
4
+ import { createInterface } from 'node:readline';
4
5
  import { parseArgs } from 'node:util';
6
+ import {
7
+ ALIASES,
8
+ AVAILABLE_LANGUAGES,
9
+ composeEditorConfig,
10
+ EMPTY_OVERRIDES,
11
+ } from './src/templates/index.js';
12
+ import { loadCustomTemplate } from './src/templates/custom-template.js';
13
+ import { compareEditorConfig, NO_LANGUAGE_FILTER, runCheck } from './src/check.js';
14
+ import { logger } from './src/utils/logger.js';
5
15
 
6
- const editorconfigContent = `root = true
7
-
8
- [*]
9
- indent_style = tab
10
- indent_size = 4
11
- tab_width = 4
12
- end_of_line = lf
13
- charset = utf-8
14
- spelling_language = en
15
- trim_trailing_whitespace = true
16
- insert_final_newline = true
17
- quote_type = single
18
- spaces_around_operators = true
19
- `;
20
-
21
- export function createEditorConfig(path = '.editorconfig') {
22
- writeFileSync(path, editorconfigContent, 'utf8');
16
+ export { compareEditorConfig };
17
+
18
+ export function createEditorConfig(path = '.editorconfig', languages = [], overrides = EMPTY_OVERRIDES) {
19
+ writeFileSync(path, composeEditorConfig(languages, overrides), 'utf8');
20
+ };
21
+
22
+ export const options = {
23
+ mode: {
24
+ type: 'string',
25
+ short: 'm',
26
+ },
27
+ path: {
28
+ type: 'string',
29
+ short: 'p',
30
+ },
31
+ languages: {
32
+ type: 'string',
33
+ short: 'l',
34
+ },
35
+ help: {
36
+ type: 'boolean',
37
+ short: 'h',
38
+ },
39
+ overwrite: {
40
+ type: 'boolean',
41
+ short: 'o',
42
+ },
43
+ strict: {
44
+ type: 'boolean',
45
+ short: 's',
46
+ },
47
+ template: {
48
+ type: 'string',
49
+ short: 't',
50
+ },
23
51
  };
24
52
 
25
- export function compareEditorConfig(path = '.editorconfig') {
26
- const file = readFileSync(path).toString();
27
- if (editorconfigContent === file) {
28
- console.log('✅ Editorconfig is matching the expected configuration')
53
+ export const NOT_PROVIDED = Symbol('languages-not-provided');
54
+
55
+ export function parseLanguages(raw) {
56
+ if (typeof raw !== 'string') {
57
+ return NOT_PROVIDED;
29
58
  }
30
- else {
31
- console.log('❌ Editorconfig is not matching the expected configuration');
59
+ return raw
60
+ .split(',')
61
+ .map((token) => token.trim().toLowerCase())
62
+ .filter((token) => token.length > 0);
63
+ }
64
+
65
+ function formatAliasList() {
66
+ const grouped = new Map();
67
+ for (const [alias, target] of Object.entries(ALIASES)) {
68
+ const list = grouped.get(target) ?? [];
69
+ list.push(alias);
70
+ grouped.set(target, list);
32
71
  }
72
+ return [...grouped.entries()]
73
+ .map(([target, aliases]) => `${aliases.join(', ')} -> ${target}`)
74
+ .join('; ');
75
+ }
76
+
77
+ export function printHelp() {
78
+ logger.log(`Usage: editorconfig --mode=<command> [--path=<path>] [--languages=<list>] [--template=<path|url>]
79
+
80
+ Commands:
81
+ write Create a .editorconfig file with the selected language sections
82
+ check Validate per-section against the canonical templates
83
+
84
+ Options:
85
+ -m, --mode Command to run (write | check)
86
+ -p, --path Path to the .editorconfig file (default: .editorconfig)
87
+ -l, --languages Comma-separated language sections (write: which to emit; check: required set)
88
+ -o, --overwrite Overwrite an existing .editorconfig without confirmation
89
+ -s, --strict Treat unknown section headers as failures (check only)
90
+ -t, --template Path or https URL to a custom .editorconfig-syntax file whose sections override the built-in ones
91
+ -h, --help Show this help message
92
+
93
+ Languages: ${AVAILABLE_LANGUAGES.join(', ')}
94
+ Aliases: ${formatAliasList()}
95
+
96
+ Examples:
97
+ editorconfig --mode=write --languages=js,md
98
+ editorconfig --mode=write --languages= # base only
99
+ editorconfig --mode=write # interactive in TTY, base only otherwise
100
+ editorconfig --mode=check # validate sections present in the file
101
+ editorconfig --mode=check --languages=js,md # require exactly base + js + md
102
+ editorconfig --mode=check --strict # fail on any unknown section header
103
+ editorconfig --mode=write --template=./team.editorconfig
104
+ editorconfig --mode=check --template=./team.editorconfig
105
+ editorconfig --mode=check --template=https://team.example.com/.editorconfig`);
33
106
  };
34
107
 
35
- function main() {
36
- const args = process.argv.slice(2);
37
- const options = {
38
- mode: {
39
- type: 'string',
40
- short: 'm',
41
- },
42
- path: {
43
- type: 'string',
44
- short: 'p',
45
- },
46
- };
47
- const { values } = parseArgs({ args, options });
48
- const path = values.path || '.editorconfig'
49
- if (values.mode === 'write') {
50
- createEditorConfig(path);
51
- }
52
- else if (values.mode === 'check') {
53
- compareEditorConfig(path);
108
+ function resolvePromptAnswer(answer) {
109
+ const tokens = answer
110
+ .split(',')
111
+ .map((token) => token.trim().toLowerCase())
112
+ .filter((token) => token.length > 0);
113
+ return tokens.map((token) => {
114
+ const asNumber = Number.parseInt(token, 10);
115
+ if (Number.isInteger(asNumber) && asNumber >= 1 && asNumber <= AVAILABLE_LANGUAGES.length) {
116
+ return AVAILABLE_LANGUAGES[asNumber - 1];
117
+ }
118
+ return token;
119
+ });
120
+ }
121
+
122
+ function promptLanguages() {
123
+ return new Promise((resolve) => {
124
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
125
+ const numbered = AVAILABLE_LANGUAGES
126
+ .map((name, index) => ` ${index + 1}. ${name}`)
127
+ .join('\n');
128
+ logger.log(`Available language sections:\n${numbered}`);
129
+ rl.question('Languages? [comma-separated names or indices, blank=base only] ', (answer) => {
130
+ rl.close();
131
+ resolve(resolvePromptAnswer(answer));
132
+ });
133
+ });
134
+ }
135
+
136
+ function resolveLanguages(parsedLanguages) {
137
+ if (parsedLanguages !== NOT_PROVIDED) {
138
+ return Promise.resolve(parsedLanguages);
139
+ }
140
+ if (process.stdin.isTTY) {
141
+ return promptLanguages();
142
+ }
143
+ return Promise.resolve([]);
144
+ }
145
+
146
+ function confirmOverwrite(path) {
147
+ return new Promise((resolve) => {
148
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
149
+ rl.question(`\`${path}\` already exists. Overwrite? [y/N] `, (answer) => {
150
+ rl.close();
151
+ resolve(/^y(es)?$/iu.test(answer.trim()));
152
+ });
153
+ });
154
+ }
155
+
156
+ async function handleExistingTarget(path, languages, overrides) {
157
+ if (!process.stdin.isTTY) {
158
+ logger.error(`\`${path}\` already exists. Use --overwrite to replace it.`);
159
+ process.exitCode = 1;
160
+ return;
161
+ }
162
+ const confirmed = await confirmOverwrite(path);
163
+ if (confirmed) {
164
+ createEditorConfig(path, languages, overrides);
54
165
  }
55
166
  else {
56
- console.error('invalid commande');
167
+ logger.log(`Skipped: \`${path}\` was not modified.`);
168
+ }
169
+ }
170
+
171
+ async function runWrite({ path, overwrite, parsedLanguages, overrides }) {
172
+ const languages = await resolveLanguages(parsedLanguages);
173
+ if (!existsSync(path) || overwrite) {
174
+ createEditorConfig(path, languages, overrides);
175
+ return;
176
+ }
177
+ await handleExistingTarget(path, languages, overrides);
178
+ }
179
+
180
+ function dispatchCheck({ path, languages, strict, overrides }) {
181
+ let filter = languages;
182
+ if (filter === NOT_PROVIDED) {
183
+ filter = NO_LANGUAGE_FILTER;
184
+ }
185
+ try {
186
+ runCheck({ path, parsedLanguages: filter, strict, overrides });
187
+ }
188
+ catch (error) {
189
+ logger.error(error.message);
190
+ process.exitCode = 1;
57
191
  }
192
+ }
193
+
194
+ async function dispatchWrite({ path, overwrite, languages, overrides }) {
195
+ try {
196
+ await runWrite({ path, overwrite, parsedLanguages: languages, overrides });
197
+ }
198
+ catch (error) {
199
+ logger.error(error.message);
200
+ process.exitCode = 1;
201
+ }
202
+ }
203
+
204
+ async function runCommand({ mode, path, overwrite, languages, strict, overrides }) {
205
+ if (mode === 'write') {
206
+ await dispatchWrite({ path, overwrite, languages, overrides });
207
+ return;
208
+ }
209
+ if (mode === 'check') {
210
+ dispatchCheck({ path, languages, strict, overrides });
211
+ return;
212
+ }
213
+ logger.error('invalid command');
214
+ process.exitCode = 1;
215
+ }
216
+
217
+ function formatCliError(error) {
218
+ if (error.code === 'ERR_PARSE_ARGS_UNKNOWN_OPTION') {
219
+ return `${error.message.split('.')[0]}. See --help.`;
220
+ }
221
+ return error.message;
222
+ }
223
+
224
+ function parseCliArgs(args) {
225
+ try {
226
+ return parseArgs({ args, options });
227
+ }
228
+ catch (error) {
229
+ logger.error(formatCliError(error));
230
+ process.exitCode = 1;
231
+ return false;
232
+ }
233
+ }
234
+
235
+ const OVERRIDES_FAILED = Symbol('overrides-failed');
236
+
237
+ async function resolveOverrides(templateValue) {
238
+ if (typeof templateValue !== 'string') {
239
+ return EMPTY_OVERRIDES;
240
+ }
241
+ try {
242
+ return await loadCustomTemplate(templateValue);
243
+ }
244
+ catch (error) {
245
+ logger.error(error.message);
246
+ process.exitCode = 1;
247
+ return OVERRIDES_FAILED;
248
+ }
249
+ }
250
+
251
+ async function dispatchValues(values) {
252
+ const overrides = await resolveOverrides(values.template);
253
+ if (overrides === OVERRIDES_FAILED) {
254
+ return;
255
+ }
256
+ await runCommand({
257
+ mode: values.mode,
258
+ path: values.path || '.editorconfig',
259
+ overwrite: values.overwrite,
260
+ languages: parseLanguages(values.languages),
261
+ strict: values.strict,
262
+ overrides,
263
+ });
264
+ }
265
+
266
+ async function main() {
267
+ const parsed = parseCliArgs(process.argv.slice(2));
268
+ if (!parsed) {
269
+ return;
270
+ }
271
+ if (parsed.values.help) {
272
+ printHelp();
273
+ return;
274
+ }
275
+ await dispatchValues(parsed.values);
58
276
  };
59
277
 
60
- main();
278
+ if (process.argv[1] === import.meta.filename) {
279
+ try {
280
+ await main();
281
+ }
282
+ catch (error) {
283
+ logger.error(error.message);
284
+ process.exitCode = 1;
285
+ }
286
+ }
package/package.json CHANGED
@@ -1,10 +1,17 @@
1
1
  {
2
2
  "name": "@sheplu/editorconfig",
3
- "version": "0.8.7",
3
+ "version": "0.10.0",
4
4
  "description": "",
5
5
  "main": "index.js",
6
+ "files": [
7
+ "index.js",
8
+ "src/"
9
+ ],
6
10
  "scripts": {
7
- "test": "echo \"Error: no test specified\" && exit 1"
11
+ "lint": "oxlint",
12
+ "test": "node --test",
13
+ "test:unit": "node --test 'test/unit/*.test.js'",
14
+ "test:integration": "node --test 'test/integration/*.test.js'"
8
15
  },
9
16
  "bin": {
10
17
  "@sheplu/editorconfig": "index.js"
@@ -24,8 +31,15 @@
24
31
  "author": "Jean Burellier",
25
32
  "license": "MIT",
26
33
  "type": "module",
34
+ "engines": {
35
+ "node": ">=24"
36
+ },
27
37
  "bugs": {
28
38
  "url": "https://github.com/sheplu/editorconfig/issues"
29
39
  },
30
- "homepage": "https://github.com/sheplu/editorconfig#readme"
40
+ "homepage": "https://github.com/sheplu/editorconfig#readme",
41
+ "devDependencies": {
42
+ "node-pty": "1.0.0",
43
+ "oxlint": "^1.65.0"
44
+ }
31
45
  }
package/src/check.js ADDED
@@ -0,0 +1,176 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import {
3
+ BASE_SECTION_HEADER,
4
+ compareSection,
5
+ EMPTY_OVERRIDES,
6
+ expectedBodyForLanguage,
7
+ headerToLanguage,
8
+ languageToHeader,
9
+ parseSections,
10
+ resolveLanguageNames,
11
+ } from './templates/index.js';
12
+ import { logger } from './utils/logger.js';
13
+
14
+ export const NO_LANGUAGE_FILTER = Symbol('check-no-language-filter');
15
+
16
+ const STATUS_GLYPH = {
17
+ match: '✅',
18
+ mismatch: '❌',
19
+ missing: '❌',
20
+ 'no-root': '❌',
21
+ unknown: '⚠️ ',
22
+ };
23
+
24
+ const STATUS_DETAIL = {
25
+ match: '',
26
+ mismatch: 'section body does not match',
27
+ missing: 'missing',
28
+ 'no-root': "missing 'root = true' before sections",
29
+ unknown: 'unknown header (not validated)',
30
+ };
31
+
32
+ function languageForHeader(header) {
33
+ if (header === BASE_SECTION_HEADER) {
34
+ return 'base';
35
+ }
36
+ return headerToLanguage(header);
37
+ }
38
+
39
+ function checkSection(section, overrides) {
40
+ const language = languageForHeader(section.header);
41
+ if (!language) {
42
+ return { header: section.header, status: 'unknown' };
43
+ }
44
+ const expected = expectedBodyForLanguage(language, overrides);
45
+ const { ok } = compareSection(section.body, expected);
46
+ if (ok) {
47
+ return { header: section.header, status: 'match' };
48
+ }
49
+ return { header: section.header, status: 'mismatch' };
50
+ }
51
+
52
+ function buildExpectedHeaders(parsedLanguages) {
53
+ const resolved = resolveLanguageNames(parsedLanguages);
54
+ return [BASE_SECTION_HEADER, ...resolved.map((name) => languageToHeader(name))];
55
+ }
56
+
57
+ function buildResults({ parsedLanguages, parsed, overrides }) {
58
+ const results = parsed.sections.map((section) => checkSection(section, overrides));
59
+ if (parsedLanguages !== NO_LANGUAGE_FILTER) {
60
+ const present = new Set(parsed.sections.map((section) => section.header));
61
+ const expectedHeaders = buildExpectedHeaders(parsedLanguages);
62
+ for (const header of expectedHeaders) {
63
+ if (!present.has(header)) {
64
+ results.push({ header, status: 'missing' });
65
+ }
66
+ }
67
+ }
68
+ return results;
69
+ }
70
+
71
+ function buildBaseIssues(parsed) {
72
+ const issues = [];
73
+ const hasBase = parsed.sections.some((section) => section.header === BASE_SECTION_HEADER);
74
+ if (!hasBase) {
75
+ issues.push({ header: BASE_SECTION_HEADER, status: 'missing' });
76
+ }
77
+ else if (!parsed.hasRoot) {
78
+ issues.push({ header: BASE_SECTION_HEADER, status: 'no-root' });
79
+ }
80
+ return issues;
81
+ }
82
+
83
+ export function compareEditorConfig(path = '.editorconfig', parsedLanguages = NO_LANGUAGE_FILTER, overrides = EMPTY_OVERRIDES) {
84
+ if (!existsSync(path)) {
85
+ throw new Error(`'${path}' does not exist`);
86
+ }
87
+ const text = readFileSync(path, 'utf8');
88
+ const parsed = parseSections(text);
89
+ const baseIssues = buildBaseIssues(parsed);
90
+ const results = buildResults({ parsedLanguages, parsed, overrides });
91
+ return { baseIssues, results };
92
+ }
93
+
94
+ function formatLine({ header, status }) {
95
+ const glyph = STATUS_GLYPH[status];
96
+ const detail = STATUS_DETAIL[status];
97
+ if (detail) {
98
+ return ` ${glyph} ${header} ${detail}`;
99
+ }
100
+ return ` ${glyph} ${header}`;
101
+ }
102
+
103
+ export function reportIsFailing({ baseIssues, results }, strict) {
104
+ const failing = [...baseIssues, ...results].some((entry) =>
105
+ entry.status === 'mismatch' || entry.status === 'missing' || entry.status === 'no-root',
106
+ );
107
+ const hasUnknown = results.some((entry) => entry.status === 'unknown');
108
+ return failing || (strict && hasUnknown);
109
+ }
110
+
111
+ function printReport(path, report) {
112
+ logger.log(`Checking ${path}`);
113
+ logger.log('');
114
+ for (const issue of report.baseIssues) {
115
+ logger.log(formatLine(issue));
116
+ }
117
+ for (const entry of report.results) {
118
+ logger.log(formatLine(entry));
119
+ }
120
+ logger.log('');
121
+ }
122
+
123
+ function summarize(report) {
124
+ const lines = [...report.baseIssues, ...report.results];
125
+ const total = lines.length;
126
+ const matched = lines.filter((entry) => entry.status === 'match').length;
127
+ const failed = lines.filter((entry) =>
128
+ entry.status === 'mismatch' || entry.status === 'missing' || entry.status === 'no-root',
129
+ ).length;
130
+ const unknown = lines.filter((entry) => entry.status === 'unknown').length;
131
+ return { total, matched, failed, unknown };
132
+ }
133
+
134
+ function pluralize(count, singular) {
135
+ if (count === 1) {
136
+ return singular;
137
+ }
138
+ return `${singular}s`;
139
+ }
140
+
141
+ function formatFailureSummary({ total, failed, unknown }) {
142
+ const reasons = [];
143
+ if (failed > 0) {
144
+ reasons.push(`${failed} of ${total} ${pluralize(total, 'section')} did not match`);
145
+ }
146
+ if (unknown > 0) {
147
+ reasons.push(`${unknown} unknown ${pluralize(unknown, 'header')}`);
148
+ }
149
+ return `❌ FAIL — ${reasons.join('; ')}`;
150
+ }
151
+
152
+ function formatPassSummary({ matched, unknown }) {
153
+ const matchedLabel = `${matched} ${pluralize(matched, 'section')} matched`;
154
+ if (unknown > 0) {
155
+ return `✅ PASS — ${matchedLabel} (${unknown} unknown ${pluralize(unknown, 'header')} ignored)`;
156
+ }
157
+ return `✅ PASS — ${matchedLabel}`;
158
+ }
159
+
160
+ function formatSummary(report, isFailure) {
161
+ const counts = summarize(report);
162
+ if (isFailure) {
163
+ return formatFailureSummary(counts);
164
+ }
165
+ return formatPassSummary(counts);
166
+ }
167
+
168
+ export function runCheck({ path, parsedLanguages, strict, overrides }) {
169
+ const report = compareEditorConfig(path, parsedLanguages, overrides);
170
+ printReport(path, report);
171
+ const failed = reportIsFailing(report, strict);
172
+ logger.log(formatSummary(report, failed));
173
+ if (failed) {
174
+ process.exitCode = 1;
175
+ }
176
+ }
@@ -1,4 +1,4 @@
1
- root = true
1
+ export const base = `root = true
2
2
 
3
3
  [*]
4
4
  indent_style = tab
@@ -11,3 +11,4 @@ trim_trailing_whitespace = true
11
11
  insert_final_newline = true
12
12
  quote_type = single
13
13
  spaces_around_operators = true
14
+ `;
@@ -0,0 +1,4 @@
1
+ export const css = `[*.{css,scss,sass,less}]
2
+ indent_style = space
3
+ indent_size = 2
4
+ `;