yini-cli 1.3.4 → 1.5.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
@@ -1,13 +1,12 @@
1
1
  # YINI CLI
2
- > **Readable configuration without indentation pitfalls or JSON verbosity.**
3
2
 
4
- **The official CLI for validating, inspecting, and converting YINI configuration files to JSON or JavaScript, built by the YINI-lang project.**
3
+ The official CLI for validating, inspecting, and converting YINI configuration files to JSON or JavaScript, maintained by the YINI-lang project.
5
4
 
6
- *YINI is an INI-inspired, human-friendly configuration format with real structure, nested sections, comments, and predictable parsing.*
5
+ YINI is an INI-inspired, human-readable configuration format with explicit structure, nested sections, comments, and predictable parsing.
7
6
 
8
- [![npm version](https://img.shields.io/npm/v/yini-cli.svg)](https://www.npmjs.com/package/yini-cli) [![All Test Suites](https://github.com/YINI-lang/yini-cli/actions/workflows/run-all-tests.yml/badge.svg)](https://github.com/YINI-lang/yini-cli/actions/workflows/run-all-tests.yml) [![Regression Tests](https://github.com/YINI-lang/yini-cli/actions/workflows/run-regression-tests.yml/badge.svg)](https://github.com/YINI-lang/yini-cli/actions/workflows/run-regression-tests.yml) [![CLI Test CI](https://github.com/YINI-lang/yini-cli/actions/workflows/run-cli-test.yml/badge.svg)](https://github.com/YINI-lang/yini-cli/actions/workflows/run-cli-test.yml) [![npm downloads](https://img.shields.io/npm/dm/yini-cli)](https://www.npmjs.com/package/yini-cli)
7
+ YINI is intended to emphasize clarity, readability, explicit structure, predictability, and deterministic parsing, while remaining simple, but not simplistic.
9
8
 
10
- Designed for developers and teams who want human-edited configuration with explicit structure and no indentation-based semantics.
9
+ [![npm version](https://img.shields.io/npm/v/yini-cli.svg)](https://www.npmjs.com/package/yini-cli) [![TypeScript](https://img.shields.io/badge/TypeScript-5.x-3178C6?logo=typescript&logoColor=white)](https://www.typescriptlang.org/) [![All Test Suites](https://github.com/YINI-lang/yini-cli/actions/workflows/run-all-tests.yml/badge.svg)](https://github.com/YINI-lang/yini-cli/actions/workflows/run-all-tests.yml) [![Regression Tests](https://github.com/YINI-lang/yini-cli/actions/workflows/run-regression-tests.yml/badge.svg)](https://github.com/YINI-lang/yini-cli/actions/workflows/run-regression-tests.yml) [![CLI Test CI](https://github.com/YINI-lang/yini-cli/actions/workflows/run-cli-test.yml/badge.svg)](https://github.com/YINI-lang/yini-cli/actions/workflows/run-cli-test.yml)
11
10
 
12
11
  ## Quick Start
13
12
 
@@ -37,7 +36,7 @@ YINI CLI requires Node.js **v20 or later**.
37
36
 
38
37
  3. **Test functionality**
39
38
  Create a simple test file, for example: `config.yini`:
40
- ```yini
39
+ ```ini
41
40
  ^ App
42
41
  name = "My App Title"
43
42
  version = "1.2.3"
@@ -82,11 +81,11 @@ Source: [config.yini](./samples/config.yini)
82
81
 
83
82
  ---
84
83
 
85
- ## 🙋‍♀️ Why YINI?
84
+ ## YINI characteristics
86
85
  - **Indentation-independent structure:** YINI is indentation-independent — whitespace never alters structural meaning.
87
- - **Explicit nesting:** It uses clear header markers (`^`, `^^`, `^^^`) to define hierarchy, making large configurations easier to scan and refactor.
86
+ - **Explicit nesting:** Section markers such as `^`, `^^`, and `^^^` define hierarchy explicitly.
88
87
  - **Multiple data types:** Supports booleans (`true` / `false`, `yes` / `no`, etc.), numbers, lists, and inline objects, with explicit string syntax.
89
- - **Comment support:** YINI supports multiple comment styles (`#`, `//`, `/* ... */`, and `;`), making it easier to document configuration directly in the file.
88
+ - **Comment support:** YINI supports multiple comment styles (`#`, `//`, `/* ... */`, and `;`) for documenting configuration directly in the file.
90
89
  - **Predictable parsing:** Well-defined rules with optional strict and lenient modes for different use cases.
91
90
 
92
91
  ---
@@ -128,10 +127,112 @@ yini parse --help
128
127
 
129
128
  ---
130
129
 
130
+ ## Why YINI?
131
+
132
+ YINI is intended for configuration files where human readability, explicit structure, and predictable parsing are more important than minimal syntax or maximum flexibility.
133
+
134
+ Compared with common configuration formats:
135
+ - **INI:** YINI supports clearer nested sections and typed values.
136
+ - **JSON:** YINI supports comments and is easier to edit by hand.
137
+ - **YAML:** YINI does not use indentation to define structure.
138
+ - **TOML:** YINI uses explicit section markers for hierarchy instead of dotted table names.
139
+
140
+ The same small configuration can be written in several formats:
141
+
142
+ ### YINI
143
+ ```ini
144
+ ^ Application
145
+ name = 'demo'
146
+ environment = 'dev'
147
+
148
+ ^^ Server
149
+ host = 'localhost'
150
+ ports = [8080, 8081]
151
+
152
+ ^^^ TLS
153
+ enabled = true
154
+ mode = 'optional'
155
+ ```
156
+
157
+ - `Application` contains the top-level application settings.
158
+ - `Server` is nested under `Application`.
159
+ - `TLS` is nested under `Server`.
160
+ - The section markers `^` make the nesting explicit. Indentation is optional and not required for structure.
161
+ - Strings can use either `'` or `"`.
162
+
163
+ ### JSON
164
+ ```json
165
+ {
166
+ "Application": {
167
+ "name": "demo",
168
+ "environment": "dev",
169
+ "Server": {
170
+ "host": "localhost",
171
+ "ports": [8080, 8081],
172
+ "TLS": {
173
+ "enabled": true,
174
+ "mode": "optional"
175
+ }
176
+ }
177
+ }
178
+ }
179
+ ```
180
+
181
+ ### YAML
182
+ ```yaml
183
+ Application:
184
+ name: demo
185
+ environment: dev
186
+ Server:
187
+ host: localhost
188
+ ports:
189
+ - 8080
190
+ - 8081
191
+ TLS:
192
+ enabled: true
193
+ mode: optional
194
+ ```
195
+
196
+ ### TOML
197
+ ```toml
198
+ [Application]
199
+ name = "demo"
200
+ environment = "dev"
201
+
202
+ [Application.Server]
203
+ host = "localhost"
204
+ ports = [8080, 8081]
205
+
206
+ [Application.Server.TLS]
207
+ enabled = true
208
+ mode = "optional"
209
+ ```
210
+
211
+ YINI may not be the right choice when you need mature ecosystem support, existing schema tooling, or maximum compatibility with infrastructure that already expects JSON, YAML, or TOML. The format and parser are still alpha-stage and best suited for testing, experiments, and early integration feedback.
212
+
213
+ ---
214
+
215
+ ## Feedback and bug reports
216
+
217
+ If you find a problem, please open an issue on GitHub:
218
+
219
+ - [Report a bug or issue](https://github.com/YINI-lang/yini-cli/issues)
220
+
221
+ When reporting parser behavior, it is helpful to include:
222
+ - The YINI input that caused the issue.
223
+ - The command and options used.
224
+ - The expected result.
225
+ - The actual result or error message.
226
+ - The installed `yini-cli` version.
227
+ - The Node.js version used.
228
+ - The operating system and version used.
229
+
230
+ ---
231
+
131
232
  ## A closer look at YINI
132
233
 
133
234
  Here's a small example showing YINI structure and comments:
134
- ```yini
235
+ ```ini
135
236
  // This is a comment in YINI
136
237
 
137
238
  ^ App // Defines section (group) "App"
@@ -177,10 +278,8 @@ Here's a small example showing YINI structure and comments:
177
278
  }
178
279
  ```
179
280
 
180
- That's it!
181
-
182
- - ▶️ See more on [YINI Homepage](https://yini-lang.org/?utm_source=github&utm_medium=referral&utm_campaign=yini_cli&utm_content=readme_middle).
183
- - ▶️ Link to [Demo Apps](https://github.com/YINI-lang/yini-demo-apps/tree/main) with complete basic usage.
281
+ - [YINI Homepage](https://yini-lang.org/?utm_source=github&utm_medium=referral&utm_campaign=yini_cli&utm_content=readme_middle).
282
+ - [YINI Demo Apps](https://github.com/YINI-lang/yini-demo-apps/tree/main) with usage examples.
184
283
 
185
284
  ---
186
285
 
@@ -196,8 +295,8 @@ The `parse` command supports multiple output formats:
196
295
  | `yini parse config.yini --js` | JavaScript object | JavaScript-style object (unquoted keys, single quotes). |
197
296
  | `yini parse config.yini -o out.json` | File output | Writes formatted JSON to file (default format). |
198
297
 
199
- >💡 `--js` and `--compact` are mutually exclusive.
200
- >💡 Tip: You can combine --output with any style flag to control both formatting and destination.
298
+ > `--js` and `--compact` are mutually exclusive.
299
+ > `--output` can be combined with a style flag to control both formatting and destination.
201
300
 
202
301
  ### Output File Handling
203
302
 
@@ -237,20 +336,20 @@ Use `--overwrite` to force replacement.
237
336
  ---
238
337
 
239
338
  ## Contributing
240
- Contributions, bug reports, and feedback are welcome. Even small improvements or suggestions are appreciated.
339
+ Bug reports, feedback, and contributions are welcome.
241
340
 
242
- If this tool is useful to you, a GitHub star helps more people discover the project and supports future development.
341
+ GitHub Issues and Discussions are available for feedback and project discussion.
243
342
 
244
343
  ---
245
344
 
246
345
  ## License
247
- This project is licensed under the Apache-2.0 license — see the [LICENSE](./LICENSE) file for details.
248
-
249
- In this project on GitHub, the `libs` directory contains third party software and each is licensed under its own license which is described in its own license file under the respective directory under `libs`.
346
+ This project is licensed under the Apache License 2.0 — see the [LICENSE](./LICENSE) file for details.
250
347
 
251
348
  ---
252
349
 
253
350
  **^YINI ≡**
254
- > Readable like INI. Structured like JSON. No indentation surprises.
351
+ > YINI is a human-readable configuration format designed for clarity, readability, explicit structure, predictability, and deterministic parsing.
352
+ >
353
+ > See the specification for syntax and format details.
255
354
 
256
355
  [yini-lang.org](https://yini-lang.org/?utm_source=github&utm_medium=referral&utm_campaign=yini_cli&utm_content=readme_footer) · [YINI-lang on GitHub](https://github.com/YINI-lang)
@@ -0,0 +1,15 @@
1
+ import { IGlobalOptions } from '../types.js';
2
+ export type TValidateRunMode = 'file' | 'directory';
3
+ export declare const printStdout: (options: IGlobalOptions, text: string) => void;
4
+ export declare const printStderr: (options: IGlobalOptions, text: string) => void;
5
+ export declare const printWarning: (options: IGlobalOptions, text: string) => void;
6
+ export declare const resolveStrictMode: (options: IGlobalOptions) => boolean;
7
+ export declare const resolveRunModeFromTargets: (targets: string[]) => TValidateRunMode;
8
+ export declare const getDisplayBaseDir: (targets: string[]) => string;
9
+ export declare const toDisplayPath: (filePath: string, baseDir: string) => string;
10
+ /**
11
+ *
12
+ * @note Windows safe, since Windows paths are case-insensitive,
13
+ * so on Windows cases are normalized too.
14
+ */
15
+ export declare const assertInputAndOutputAreDifferent: (srcPath: string, destPath: string) => void;
@@ -0,0 +1,76 @@
1
+ // src/commands/commonFunctions.ts
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ export const printStdout = (options, text) => {
5
+ if (options.silent)
6
+ return;
7
+ console.log(text);
8
+ };
9
+ export const printStderr = (options, text) => {
10
+ if (options.silent)
11
+ return;
12
+ console.error(text);
13
+ };
14
+ export const printWarning = (options, text) => {
15
+ if (options.silent)
16
+ return;
17
+ if (options.quiet)
18
+ return;
19
+ console.warn(text);
20
+ };
21
+ export const resolveStrictMode = (options) => {
22
+ if (options.strict && options.lenient) {
23
+ throw new Error('--strict and --lenient cannot be used together.');
24
+ }
25
+ if (options.strict)
26
+ return true;
27
+ if (options.lenient)
28
+ return false;
29
+ return false; // Default = lenient
30
+ };
31
+ export const resolveRunModeFromTargets = (targets) => {
32
+ for (const target of targets) {
33
+ const resolved = path.resolve(target);
34
+ if (!fs.existsSync(resolved)) {
35
+ throw new Error(`Path does not exist: "${target}"`);
36
+ }
37
+ if (fs.statSync(resolved).isDirectory()) {
38
+ return 'directory';
39
+ }
40
+ }
41
+ return 'file';
42
+ };
43
+ export const getDisplayBaseDir = (targets) => {
44
+ if (!targets.length) {
45
+ return process.cwd();
46
+ }
47
+ if (targets.length === 1) {
48
+ const resolved = path.resolve(targets[0]);
49
+ if (fs.existsSync(resolved) && fs.statSync(resolved).isDirectory()) {
50
+ return resolved;
51
+ }
52
+ return path.dirname(resolved);
53
+ }
54
+ return process.cwd();
55
+ };
56
+ export const toDisplayPath = (filePath, baseDir) => {
57
+ const relative = path.relative(baseDir, filePath);
58
+ if (!relative || relative.startsWith('..')) {
59
+ return filePath;
60
+ }
61
+ return relative;
62
+ };
63
+ /**
64
+ *
65
+ * @note Windows safe, since Windows paths are case-insensitive,
66
+ * so on Windows cases are normalized too.
67
+ */
68
+ export const assertInputAndOutputAreDifferent = (srcPath, destPath) => {
69
+ const resolvedSrc = path.resolve(srcPath);
70
+ const resolvedDest = path.resolve(destPath);
71
+ const normalizedSrc = process.platform === 'win32' ? resolvedSrc.toLowerCase() : resolvedSrc;
72
+ const normalizedDest = process.platform === 'win32' ? resolvedDest.toLowerCase() : resolvedDest;
73
+ if (normalizedSrc === normalizedDest) {
74
+ throw new Error('Output file must be different from the input file.');
75
+ }
76
+ };
@@ -21,5 +21,5 @@ export interface IParseCommandOptions extends IGlobalOptions {
21
21
  * @returns
22
22
  */
23
23
  export declare const shouldSkipBecauseDestNewer: (file: string, optionOverwrite?: boolean | undefined, outputFile?: string) => boolean;
24
- export declare const isAllowWriteOutput: (srcPath: string, destPath: string, newContent: string, overwrite?: boolean) => boolean;
24
+ export declare const isAllowWriteOutput: (commandOptions: IParseCommandOptions, srcPath: string, destPath: string, newContent: string, overwrite?: boolean) => boolean;
25
25
  export declare const parseFile: (file: string, commandOptions: IParseCommandOptions) => void;
@@ -1,9 +1,10 @@
1
1
  // src/commands/parseCommand.ts
2
- import fs from 'node:fs';
3
- import path from 'node:path';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
4
  import YINI from 'yini-parser';
5
5
  import { getSerializer } from '../serializers/index.js';
6
6
  import { debugPrint, printObject } from '../utils/print.js';
7
+ import { assertInputAndOutputAreDifferent, printStderr, printStdout, printWarning, resolveStrictMode, } from './commonFunctions.js';
7
8
  // -------------------------------------------------------------------------
8
9
  /**
9
10
  * Will return true if:
@@ -12,7 +13,9 @@ import { debugPrint, printObject } from '../utils/print.js';
12
13
  * * destination is newer than source
13
14
  * @returns
14
15
  */
15
- export const shouldSkipBecauseDestNewer = (file, optionOverwrite,
16
+ export const shouldSkipBecauseDestNewer = (
17
+ // commandOptions: IParseCommandOptions,
18
+ file, optionOverwrite,
16
19
  // optionVerbose?: boolean | undefined,
17
20
  outputFile = '') => {
18
21
  if (outputFile && optionOverwrite === undefined) {
@@ -27,7 +30,7 @@ outputFile = '') => {
27
30
  }
28
31
  return false;
29
32
  };
30
- export const isAllowWriteOutput = (srcPath, destPath, newContent, overwrite) => {
33
+ export const isAllowWriteOutput = (commandOptions, srcPath, destPath, newContent, overwrite) => {
31
34
  if (!fs.existsSync(destPath)) {
32
35
  return true;
33
36
  }
@@ -40,29 +43,31 @@ export const isAllowWriteOutput = (srcPath, destPath, newContent, overwrite) =>
40
43
  const srcStat = fs.statSync(srcPath);
41
44
  const destStat = fs.statSync(destPath);
42
45
  if (destStat.mtimeMs > srcStat.mtimeMs) {
43
- reportAction('skip', destPath, `newer than source "${srcPath}"`);
46
+ reportAction(commandOptions, 'skip', destPath, `newer than source "${srcPath}"`);
44
47
  return false;
45
48
  }
46
49
  const existing = fs.readFileSync(destPath, 'utf-8');
47
50
  if (existing === newContent) {
48
- reportAction('skip', destPath, 'output unchanged');
51
+ reportAction(commandOptions, 'skip', destPath, 'output unchanged');
49
52
  return false;
50
53
  }
51
54
  return true;
52
55
  };
53
- const reportAction = (action, file, reason) => {
54
- let txt = '';
55
- if (reason) {
56
- txt = `${action.padEnd(6)} "${file}" (${reason})`;
57
- }
58
- else {
59
- txt = `${action.padEnd(6)} "${file}"`;
60
- }
56
+ const reportAction = (options, action, file, reason) => {
57
+ if (options.silent)
58
+ return;
59
+ let txt = reason
60
+ ? `${action.padEnd(6)} "${file}" (${reason})`
61
+ : `${action.padEnd(6)} "${file}"`;
61
62
  if (action === 'skip') {
62
- console.warn(txt);
63
+ if (!options.quiet) {
64
+ console.warn(txt);
65
+ }
63
66
  }
64
67
  else {
65
- console.log(txt);
68
+ if (options.verbose && !options.quiet) {
69
+ console.log(txt);
70
+ }
66
71
  }
67
72
  };
68
73
  /*
@@ -89,7 +94,7 @@ const reportAction = (action, file, reason) => {
89
94
 
90
95
  Execution control:
91
96
  --fail-fast
92
- --best-effort = ignore-errors within a file, attempt recovery and still emit outp
97
+ --best-effort = ignore-errors within a file, attempt recovery, and still emit output
93
98
  --No for parse, --keep-going = continue to the next file when one fails
94
99
  --max-errors <n>
95
100
  --verbose
@@ -111,21 +116,20 @@ export const parseFile = (file, commandOptions) => {
111
116
  doParseFile(file, commandOptions, outputFormat, outputFile);
112
117
  };
113
118
  const resolveOutputFormat = (options) => {
114
- if (options.js && options.compact) {
115
- throw new Error('--js and --compact cannot be combined.');
119
+ const selected = [
120
+ options.json ? 'json' : null,
121
+ options.compact ? 'json-compact' : null,
122
+ options.js ? 'js' : null,
123
+ options.yaml ? 'yaml' : null,
124
+ options.xml ? 'xml' : null,
125
+ ].filter(Boolean);
126
+ if (selected.length > 1) {
127
+ throw new Error('Choose only one output format: --json, --compact, --js, --yaml, or --xml.');
116
128
  }
117
- if (options.compact)
118
- return 'json-compact';
119
- if (options.js)
120
- return 'js';
121
- if (options.yaml)
122
- return 'yaml';
123
- if (options.xml)
124
- return 'xml';
125
129
  if (options.pretty) {
126
- console.warn('Warning: --pretty is deprecated. Use --json instead.');
130
+ printWarning(options, 'Warning: --pretty is deprecated. Use --json instead.');
127
131
  }
128
- return 'json';
132
+ return selected[0] ?? 'json';
129
133
  };
130
134
  /**
131
135
  * Returns true if the parsed result appears to contain usable output data.
@@ -149,55 +153,94 @@ const hasMeaningfulParsedData = (value) => {
149
153
  return false;
150
154
  return Object.keys(value).length > 0;
151
155
  };
152
- const doParseFile = (file, commandOptions, outputFormat, outputFile = '') => {
153
- debugPrint('File = ' + file);
154
- debugPrint('outputFormat = ' + outputFormat);
155
- const parseOptions = {
156
- strictMode: commandOptions.strict ?? false,
157
- failLevel: commandOptions.bestEffort ? 'ignore-errors' : 'auto',
158
- includeMetadata: true,
159
- includeDiagnostics: true,
160
- };
161
- // Check early if should skip before parsing,
162
- // saves a lot of time in some cases.
163
- if (shouldSkipBecauseDestNewer(file, commandOptions.overwrite,
164
- // commandOptions.verbose,
165
- outputFile)) {
166
- if (commandOptions.verbose) {
167
- const resolved = path.resolve(outputFile);
168
- reportAction('skip', resolved, `newer than source "${file}"`);
156
+ const formatParserDiagnostics = (parsed) => {
157
+ const errors = parsed.meta?.diagnostics?.errors?.payload ?? [];
158
+ return errors.map((diagnostic) => {
159
+ if (typeof diagnostic === 'string') {
160
+ return diagnostic;
161
+ }
162
+ if (!diagnostic || typeof diagnostic !== 'object') {
163
+ return String(diagnostic);
169
164
  }
165
+ const issue = diagnostic;
166
+ const message = issue.message ?? issue.code ?? 'Unknown parser error';
167
+ const location = issue.line != null
168
+ ? `line ${issue.line}${issue.column != null ? `:${issue.column}` : ''}: `
169
+ : '';
170
+ return `${location}${String(message)}`;
171
+ });
172
+ };
173
+ const printParseFailure = (commandOptions, parsed) => {
174
+ const messages = formatParserDiagnostics(parsed);
175
+ if (messages.length === 0) {
176
+ printStderr(commandOptions, 'Syntax error: failed to parse YINI input.');
170
177
  return;
171
178
  }
179
+ for (const message of messages) {
180
+ const normalizedMessage = message.toLowerCase().includes('syntax error')
181
+ ? message
182
+ : `Syntax error: ${message}`;
183
+ printStderr(commandOptions, normalizedMessage);
184
+ }
185
+ };
186
+ const doParseFile = (file, commandOptions, outputFormat, outputFile = '') => {
187
+ debugPrint('File = ' + file);
188
+ debugPrint('outputFormat = ' + outputFormat);
172
189
  try {
190
+ const strictMode = resolveStrictMode(commandOptions);
191
+ const parseOptions = {
192
+ strictMode: strictMode,
193
+ failLevel: commandOptions.bestEffort ? 'ignore-errors' : 'auto',
194
+ includeMetadata: true,
195
+ includeDiagnostics: true,
196
+ };
197
+ // Check that output and input file are not the same.
198
+ if (outputFile) {
199
+ assertInputAndOutputAreDifferent(file, outputFile);
200
+ }
201
+ // Check early if should skip before parsing,
202
+ // saves a lot of time in some cases.
203
+ if (shouldSkipBecauseDestNewer(
204
+ // commandOptions,
205
+ file, commandOptions.overwrite,
206
+ // commandOptions.verbose,
207
+ outputFile)) {
208
+ if (commandOptions.verbose) {
209
+ const resolved = path.resolve(outputFile);
210
+ reportAction(commandOptions, 'skip', resolved, `newer than source "${file}"`);
211
+ }
212
+ return;
213
+ }
173
214
  const parsedWithMeta = YINI.parseFile(file, parseOptions);
174
215
  const errorCount = parsedWithMeta?.meta?.diagnostics?.errors?.errorCount ?? 0;
175
216
  const parsedData = parsedWithMeta.result;
176
217
  const hasUsableOutput = hasMeaningfulParsedData(parsedData);
177
- if (errorCount > 0 &&
178
- !commandOptions.bestEffort &&
179
- (commandOptions.strict || !hasUsableOutput)) {
218
+ const hasErrors = errorCount > 0;
219
+ const bestEffort = !!commandOptions.bestEffort;
220
+ const shouldFailHard = hasErrors && !bestEffort && (strictMode || !hasUsableOutput);
221
+ if (shouldFailHard) {
222
+ printParseFailure(commandOptions, parsedWithMeta);
180
223
  process.exit(1);
181
224
  }
182
225
  const serializer = getSerializer(outputFormat);
183
226
  const output = serializer.serialize(parsedData);
184
227
  if (outputFile) {
185
228
  const resolved = path.resolve(outputFile);
186
- if (!isAllowWriteOutput(file, resolved, output, commandOptions.overwrite)) {
229
+ if (!isAllowWriteOutput(commandOptions, file, resolved, output, commandOptions.overwrite)) {
187
230
  return;
188
231
  }
189
232
  fs.writeFileSync(resolved, output, 'utf-8');
190
233
  if (commandOptions.verbose) {
191
- reportAction('write', resolved);
234
+ reportAction(commandOptions, 'write', resolved);
192
235
  }
193
236
  }
194
237
  else {
195
- console.log(output);
238
+ printStdout(commandOptions, output);
196
239
  }
197
240
  }
198
241
  catch (err) {
199
242
  const message = err instanceof Error ? err.message : String(err);
200
- console.error(`Error: ${message}`);
243
+ printStderr(commandOptions, `Error: ${message}`);
201
244
  process.exit(1);
202
245
  }
203
246
  };
@@ -1,5 +1,10 @@
1
1
  import { IGlobalOptions } from '../types.js';
2
2
  export interface IValidateCommandOptions extends IGlobalOptions {
3
3
  stats?: boolean;
4
+ warningsAsErrors?: boolean;
5
+ format?: 'json' | 'text';
6
+ failFast?: boolean;
7
+ recursive?: boolean;
8
+ maxErrors?: number;
4
9
  }
5
- export declare const validateFile: (file: string, commandOptions?: IValidateCommandOptions) => never;
10
+ export declare const validateTargets: (targets: string[], options: IValidateCommandOptions) => never;