yini-cli 1.3.4-beta → 1.4.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,20 +1,13 @@
1
- # YINI-CLI
1
+ # YINI CLI
2
2
  > **Readable configuration without indentation pitfalls or JSON verbosity.**
3
3
 
4
- **The official terminal / command-line (CLI) tool for validating, inspecting, and converting YINI (by the YINI-lang project) configuration files to JSON or JavaScript.**
4
+ **The official CLI for validating, inspecting, and converting YINI configuration files to JSON or JavaScript, built by the YINI-lang project.**
5
5
 
6
- *YINI is an INI-inspired and human-readable text format for representing structured information. It is designed to be clear, predictable, and easy for humans to read and write. It supports nesting, comments, and a formally defined syntax. It is suitable for configuration files, application settings, and general data-storage use cases.*
6
+ *YINI is an INI-inspired, human-friendly configuration format with real structure, nested sections, comments, and predictable parsing.*
7
7
 
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)
8
+ [![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) [![npm downloads](https://img.shields.io/npm/dm/yini-cli)](https://www.npmjs.com/package/yini-cli)
9
9
 
10
- This tool is designed for teams and developers working with human-edited configuration files who require explicit structure without indentation-based semantics.
11
-
12
- ---
13
-
14
- ## Example of YINI code
15
- > A basic YINI configuration example, showing a section, nested section, comments:
16
- ![YINI Config Example](./samples/basic.yini.png)
17
- Source: [basic.yini](./samples/basic.yini)
10
+ Designed for developers and teams who want human-edited configuration with explicit structure and no indentation-based semantics.
18
11
 
19
12
  ## Quick Start
20
13
 
@@ -23,7 +16,7 @@ YINI CLI requires Node.js **v20 or later**.
23
16
 
24
17
  ### Installation
25
18
 
26
- 1. **Install it globally from npm — (requires Node.js)**
19
+ 1. **Install globally from npm — (requires Node.js)**
27
20
  Open your terminal and run:
28
21
  ```
29
22
  npm install -g yini-cli
@@ -34,22 +27,22 @@ YINI CLI requires Node.js **v20 or later**.
34
27
  ```bash
35
28
  yini --version
36
29
  ```
37
- This should print the installed version (e.g., 1.0.0).
30
+ This should print the installed version.
38
31
 
39
- Then you may try:
32
+ Then try:
40
33
  ```bash
41
34
  yini --help
42
35
  ```
43
- Should show you the CLI help for YINI.
36
+ This should show the CLI help.
44
37
 
45
38
  3. **Test functionality**
46
39
  Create a simple test file, for example: `config.yini`:
47
40
  ```yini
48
41
  ^ App
49
- name = "My App Title"
50
- version = "1.2.3"
51
- pageSize = 25
52
- darkTheme = off
42
+ name = "My App Title"
43
+ version = "1.2.3"
44
+ pageSize = 25
45
+ darkTheme = off
53
46
  ```
54
47
 
55
48
  Then run:
@@ -57,7 +50,7 @@ YINI CLI requires Node.js **v20 or later**.
57
50
  yini parse config.yini
58
51
  ```
59
52
 
60
- Expected result, your CLI should output a parsed version of the config and output something similar to:
53
+ Expected output:
61
54
  ```json
62
55
  {
63
56
  "App": {
@@ -77,7 +70,12 @@ YINI CLI requires Node.js **v20 or later**.
77
70
 
78
71
  ---
79
72
 
80
- ## Example 2
73
+ ## What YINI looks like
74
+ > A basic YINI configuration example, showing a section, nested section, comments:
75
+ ![YINI Config Example](./samples/basic.yini.png)
76
+ Source: [basic.yini](./samples/basic.yini)
77
+
78
+ ### A larger example
81
79
  > A real-world YINI configuration example, showing sections, nesting, comments, and multiple data types:
82
80
  ![YINI Config Example](./samples/config.yini.png)
83
81
  Source: [config.yini](./samples/config.yini)
@@ -86,10 +84,10 @@ Source: [config.yini](./samples/config.yini)
86
84
 
87
85
  ## 🙋‍♀️ Why YINI?
88
86
  - **Indentation-independent structure:** YINI is indentation-independent — whitespace never alters structural meaning.
89
- - **Explicit nesting & refactoring safety:** It uses clear header markers (`^`, `^^`, `^^^`) to define hierarchy (like in Markdown), without long dotted keys.
90
- - **Multiple data types:** Supports boolean literals (`true` / `false`, `Yes` / `No`, etc), numbers, arrays (lists), and JS-style objects natively, with explicit string syntax.
91
- - **Comments support:** YINI supports multiple comment styles (`#`, `//`, `/* ... */`, and `;`) allowing one to document config directly in the file.
92
- - **Predictable parsing rules:** Well-defined rules with optional strict and lenient modes, for different use-requirements.
87
+ - **Explicit nesting:** It uses clear header markers (`^`, `^^`, `^^^`) to define hierarchy, making large configurations easier to scan and refactor.
88
+ - **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.
90
+ - **Predictable parsing:** Well-defined rules with optional strict and lenient modes for different use cases.
93
91
 
94
92
  ---
95
93
 
@@ -130,7 +128,7 @@ yini parse --help
130
128
 
131
129
  ---
132
130
 
133
- ## Quick Look at YINI
131
+ ## A closer look at YINI
134
132
 
135
133
  Here's a small example showing YINI structure and comments:
136
134
  ```yini
@@ -149,7 +147,7 @@ Here's a small example showing YINI structure and comments:
149
147
  # This is a comment too.
150
148
  ```
151
149
 
152
- **The above YINI converted to a JS object:**
150
+ **The above YINI as a JavaScript object:**
153
151
  ```js
154
152
  {
155
153
  App: {
@@ -201,9 +199,7 @@ The `parse` command supports multiple output formats:
201
199
  >💡 `--js` and `--compact` are mutually exclusive.
202
200
  >💡 Tip: You can combine --output with any style flag to control both formatting and destination.
203
201
 
204
- ---
205
-
206
- ## 📁 Output File Handling
202
+ ### Output File Handling
207
203
 
208
204
  When using `-o, --output <file>`, YINI CLI applies safe write rules:
209
205
 
@@ -211,45 +207,44 @@ When using `-o, --output <file>`, YINI CLI applies safe write rules:
211
207
  |----------|--------|
212
208
  | File does not exist | File is written |
213
209
  | File exists and is **older** than the input YINI file | File is overwritten |
214
- | File exists and is **newer** than the input YINI file | Command fails |
210
+ | File exists and is **newer** than the input YINI file | Skipped by default |
211
+ | File exists and output content is unchanged | Skipped |
215
212
  | `--overwrite` is used | File is always overwritten |
216
213
  | `--no-overwrite` is used | Command fails if file exists |
217
214
 
218
- This prevents accidental overwriting of newer generated files.
215
+ This helps avoid overwriting newer generated files and avoids rewriting unchanged output unnecessarily.
219
216
 
220
- Use:
221
- `--overwrite` to force replacement.
217
+ Use `--overwrite` to force replacement.
222
218
 
223
219
  ---
224
220
 
225
- ## 🛠 Roadmap
226
- Areas of planned and possible future expansion:
221
+ ## Links
222
+ - ➡️ [YINI Homepage](https://yini-lang.org)
223
+ *Tutorials, guides, and examples.*
227
224
 
228
- 1. **Improve existing commands** Continued functionality improvements, better diagnostics, and expanded QA for `parse` and `validate` and their options.
229
- 2. Command `convert`: Batch convert YINI files to JSON or JavaScript.
230
- 3. Command `format`: Pretty-print or normalize a `.yini` file.
231
- 4. Command `lint`: Stricter stylistic checks (like `validate`, but opinionated).
232
- 5. Command `diff`: Compare two YINI files and show structural/config differences.
233
- 6. Import JSON or XML into YINI format.
225
+ - ➡️ [Read the YINI Specification](https://yini-lang.org/refs/specification)
226
+ *Full syntax and format reference.*
234
227
 
235
- ---
236
-
237
- ## Links
238
228
  - ➡️ [YINI Parser on npm](https://www.npmjs.com/package/yini-parser)
239
- *Install and view package details.*
229
+ *The TypeScript/Node.js parser used by this CLI.*
240
230
 
241
- - ➡️ [YINI Project](https://github.com/YINI-lang)
242
- *YINI home on GitHub.*
231
+ - ➡️ [Demo Apps](https://github.com/YINI-lang/yini-demo-apps/tree/main)
232
+ *Complete basic usage examples.*
233
+
234
+ - ➡️ [YINI-lang Project](https://github.com/YINI-lang)
235
+ *Repositories and related ecosystem projects.*
243
236
 
244
237
  ---
245
238
 
246
- ## Contribution & Involvement
247
- Contributions, issues, and feedback are welcome. Even small improvements or suggestions are appreciated.
239
+ ## Contributing
240
+ Contributions, bug reports, and feedback are welcome. Even small improvements or suggestions are appreciated.
241
+
242
+ If this tool is useful to you, a GitHub star helps more people discover the project and supports future development.
248
243
 
249
244
  ---
250
245
 
251
246
  ## License
252
- This project is licensed under the Apache-2.0 license - see the [LICENSE](<./LICENSE>) file for details.
247
+ This project is licensed under the Apache-2.0 license see the [LICENSE](./LICENSE) file for details.
253
248
 
254
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`.
255
250
 
@@ -258,4 +253,4 @@ In this project on GitHub, the `libs` directory contains third party software an
258
253
  **^YINI ≡**
259
254
  > Readable like INI. Structured like JSON. No indentation surprises.
260
255
 
261
- [yini-lang.org](https://yini-lang.org/?utm_source=github&utm_medium=referral&utm_campaign=yini_cli&utm_content=readme_footer) · [YINI on GitHub](https://github.com/YINI-lang)
256
+ [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;
@@ -4,6 +4,7 @@ import path from 'node: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.
@@ -152,52 +156,61 @@ const hasMeaningfulParsedData = (value) => {
152
156
  const doParseFile = (file, commandOptions, outputFormat, outputFile = '') => {
153
157
  debugPrint('File = ' + file);
154
158
  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}"`);
169
- }
170
- return;
171
- }
172
159
  try {
160
+ const strictMode = resolveStrictMode(commandOptions);
161
+ const parseOptions = {
162
+ strictMode: strictMode,
163
+ failLevel: commandOptions.bestEffort ? 'ignore-errors' : 'auto',
164
+ includeMetadata: true,
165
+ includeDiagnostics: true,
166
+ };
167
+ // Check that output and input file are not the same.
168
+ if (outputFile) {
169
+ assertInputAndOutputAreDifferent(file, outputFile);
170
+ }
171
+ // Check early if should skip before parsing,
172
+ // saves a lot of time in some cases.
173
+ if (shouldSkipBecauseDestNewer(
174
+ // commandOptions,
175
+ file, commandOptions.overwrite,
176
+ // commandOptions.verbose,
177
+ outputFile)) {
178
+ if (commandOptions.verbose) {
179
+ const resolved = path.resolve(outputFile);
180
+ reportAction(commandOptions, 'skip', resolved, `newer than source "${file}"`);
181
+ }
182
+ return;
183
+ }
173
184
  const parsedWithMeta = YINI.parseFile(file, parseOptions);
174
185
  const errorCount = parsedWithMeta?.meta?.diagnostics?.errors?.errorCount ?? 0;
175
186
  const parsedData = parsedWithMeta.result;
176
187
  const hasUsableOutput = hasMeaningfulParsedData(parsedData);
177
- if (errorCount > 0 &&
178
- !commandOptions.bestEffort &&
179
- (commandOptions.strict || !hasUsableOutput)) {
188
+ const hasErrors = errorCount > 0;
189
+ const bestEffort = !!commandOptions.bestEffort;
190
+ // const strictMode = resolveStrictMode(commandOptions)
191
+ const shouldFailHard = hasErrors && !bestEffort && (strictMode || !hasUsableOutput);
192
+ if (shouldFailHard) {
180
193
  process.exit(1);
181
194
  }
182
195
  const serializer = getSerializer(outputFormat);
183
196
  const output = serializer.serialize(parsedData);
184
197
  if (outputFile) {
185
198
  const resolved = path.resolve(outputFile);
186
- if (!isAllowWriteOutput(file, resolved, output, commandOptions.overwrite)) {
199
+ if (!isAllowWriteOutput(commandOptions, file, resolved, output, commandOptions.overwrite)) {
187
200
  return;
188
201
  }
189
202
  fs.writeFileSync(resolved, output, 'utf-8');
190
203
  if (commandOptions.verbose) {
191
- reportAction('write', resolved);
204
+ reportAction(commandOptions, 'write', resolved);
192
205
  }
193
206
  }
194
207
  else {
195
- console.log(output);
208
+ printStdout(commandOptions, output);
196
209
  }
197
210
  }
198
211
  catch (err) {
199
212
  const message = err instanceof Error ? err.message : String(err);
200
- console.error(`Error: ${message}`);
213
+ printStderr(commandOptions, `Error: ${message}`);
201
214
  process.exit(1);
202
215
  }
203
216
  };
@@ -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;