yini-cli 1.1.0-beta → 1.2.0-beta

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,63 +1,142 @@
1
1
  # YINI-CLI
2
- **Command-line tool for working with YINI configuration files. Validate, inspect, and convert to JSON with pretty output.**
2
+ > **Readable configuration without indentation pitfalls or JSON verbosity.**
3
3
 
4
- *YINI aims to be a human-friendly config format: like INI, but with type-safe values, nested sections, comments, minimal syntax noise, and optional strict mode.*
4
+ **The official terminal / command-line (CLI) tool for validating, inspecting, and converting YINI configuration files to JSON or JavaScript.**
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.*
5
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)
7
9
 
8
- ---
10
+ This tool is designed for teams and developers working with human-edited configuration files who require explicit structure without indentation-based semantics.
9
11
 
10
- ## 🙋‍♀️ Why YINI?
11
- - **YINI is an alternative** to other great config formats like INI, JSON, YAML, XML, and TOML — designed for clarity, simplicity, and straightforward section nesting.
12
- - **Started as a personal project and a research challenge:** Provides structure similar to INI, with features inspired by JSON and YAML.
13
- - **Built for clarity:**
14
- * Uses concise syntax designed for clarity, especially in nested sections.
15
- * Supports commonly used configuration structures.
16
- - *Developed to meet practical needs, driven by curiosity and a desire **for configuration clarity, simplicity, minimalism, and flexibility**.
12
+ ---
17
13
 
18
- **Enjoying yini-cli?** If you like this project, [star it on GitHub](https://github.com/YINI-lang/yini-cli) — it helps a lot, thank you!
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)
19
18
 
20
- ---
19
+ ## Quick Start
21
20
 
22
- ## Requirements
21
+ ### Requirements
23
22
  YINI CLI requires Node.js **v20 or later**.
24
23
 
25
- (It has also been tested with Node.js v13+, but v20+ is recommended for best compatibility.)
24
+ ### Installation
25
+
26
+ 1. **Install it globally from npm — (requires Node.js)**
27
+ Open your terminal and run:
28
+ ```
29
+ npm install -g yini-cli
30
+ ```
31
+
32
+ 2. **Verify installation**
33
+ Run this in your terminal:
34
+ ```bash
35
+ yini --version
36
+ ```
37
+ This should print the installed version (e.g., 1.0.0).
38
+
39
+ Then you may try:
40
+ ```bash
41
+ yini --help
42
+ ```
43
+ Should show you the CLI help for YINI.
44
+
45
+ 3. **Test functionality**
46
+ Create a simple test file, for example: `config.yini`:
47
+ ```yini
48
+ ^ App
49
+ name = "My App Title"
50
+ version = "1.2.3"
51
+ pageSize = 25
52
+ darkTheme = off
53
+ ```
54
+
55
+ Then run:
56
+ ```bash
57
+ yini parse config.yini
58
+ ```
59
+
60
+ Expected result, your CLI should output a parsed version of the config and output something similar to:
61
+ ```json
62
+ {
63
+ "App": {
64
+ "name": "My App Title",
65
+ "version": "1.2.3",
66
+ "pageSize": 25,
67
+ "darkTheme": false
68
+ }
69
+ }
70
+ ```
71
+
72
+ ### Typical use cases
73
+
74
+ - Validating configuration files during development or CI.
75
+ - Inspecting and debugging structured configuration.
76
+ - Converting YINI files to JSON for tooling and automation.
26
77
 
27
78
  ---
28
79
 
29
- ## 💡 What is YINI?
30
- - **INI-inspired** with added support for typing, comments, and nested sections.
31
- - **Uses minimal syntax** — yet aims to keep maximum clarity.
32
- - Section nesting **without requiring indentation or dot-delimited keys**.
33
- - **Supports strict and lenient modes**, and all major data types.
34
- - Designed for compatibility with both **manual editing** and **automation**.
35
- - 👉 See [how YINI differs from JSON, YAML, INI, and TOML](https://github.com/YINI-lang/yini-parser-typescript/tree/main/examples/compare-formats.md).
36
- - Want the full syntax reference? See the [YINI Specification](https://github.com/YINI-lang/YINI-spec).
80
+ ## Example 2
81
+ > A real-world YINI configuration example, showing sections, nesting, comments, and multiple data types:
82
+ ![YINI Config Example](./samples/config.yini.png)
83
+ Source: [config.yini](./samples/config.yini)
37
84
 
38
85
  ---
39
86
 
40
- ## Quick Into to YINI Format
87
+ ## 🙋‍♀️ Why YINI?
88
+ - **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.
41
93
 
42
- YINI code looks like this:
43
- ```yini
44
- // This is a comment in YINI
45
- // YINI is a simple, human-readable configuration file format.
94
+ ---
46
95
 
47
- // Note: In YINI, spaces and tabs don't change meaning - indentation is just
48
- // for readability.
96
+ ## Usage
49
97
 
50
- /* This is a block comment
98
+ ### Quick Examples
51
99
 
52
- In YINI, section headers use repeated characters "^" at the start to
53
- show their level: (Section header names are case-sensitive.)
100
+ ```bash
101
+ yini parse config.yini
102
+ ```
103
+ → Parse and print formatted JSON (default).
54
104
 
55
- ^ SectionLevel1
56
- ^^ SectionLevel2
57
- ^^^ SectionLevel3
58
- */
105
+ ```bash
106
+ yini parse config.yini --compact
107
+ ```
108
+ → Output compact JSON (no whitespace).
109
+
110
+ ```bash
111
+ yini parse config.yini --js
112
+ ```
113
+ → Output as JavaScript-style object.
114
+
115
+ ```bash
116
+ yini parse config.yini -o out.json
117
+ ```
118
+ → Write formatted JSON to a file.
119
+
120
+ ```bash
121
+ yini validate --strict config.yini
122
+ ```
123
+ → Validate using strict mode.
59
124
 
60
- ^ App // Definition of section (group) "App"
125
+ For help with a specific command:
126
+
127
+ ```bash
128
+ yini parse --help
129
+ ```
130
+
131
+ ---
132
+
133
+ ## Quick Look at YINI
134
+
135
+ Here's a small example showing YINI structure and comments:
136
+ ```yini
137
+ // This is a comment in YINI
138
+
139
+ ^ App // Defines section (group) "App"
61
140
  name = 'My Title' // Keys and values are written as key = value
62
141
  items = 25
63
142
  darkMode = true // "ON" and "YES" works too
@@ -66,6 +145,8 @@ YINI code looks like this:
66
145
  ^^ Special
67
146
  primaryColor = #336699 // Hex number format
68
147
  isCaching = false // "OFF" and "NO" works too
148
+
149
+ # This is a comment too.
69
150
  ```
70
151
 
71
152
  **The above YINI converted to a JS object:**
@@ -100,107 +181,70 @@ YINI code looks like this:
100
181
 
101
182
  That's it!
102
183
 
103
- - ▶️ Link to [examples/](https://github.com/YINI-lang/yini-parser-typescript/tree/main/examples) files.
184
+ - ▶️ See more on [YINI Homepage](https://yini-lang.org/?utm_source=github&utm_medium=referral&utm_campaign=yini_cli&utm_content=readme_middle).
104
185
  - ▶️ Link to [Demo Apps](https://github.com/YINI-lang/yini-demo-apps/tree/main) with complete basic usage.
105
186
 
106
187
  ---
107
188
 
108
- ## Bigger Intro into YINI Config Format
109
- **YINI** is a simple and readable configuration format. Sections are defined with `^ SectionName`, and values are assigned using `key = value`. The format supports common data types (same as those found in JSON), including strings, numbers, booleans, nulls, and lists.
189
+ ## 📤 Output Modes for `yini parse`
110
190
 
111
- To learn more, see the [Getting Started: Intro to YINI Config Format](https://github.com/YINI-lang/YINI-spec/blob/develop/Docs/Intro-to-YINI-Config-Format.md) tutorial.
191
+ The `parse` command supports multiple output formats:
112
192
 
113
- ---
193
+ | Command Example | Output Format | Description |
194
+ |------------------------------------------|----------------------|------------|
195
+ | `yini parse config.yini` | Pretty JSON (default) | Formatted JSON with indentation (4 spaces). |
196
+ | `yini parse config.yini --json` | Pretty JSON | Explicit pretty JSON output. |
197
+ | `yini parse config.yini --compact` | Compact JSON | Minified JSON (no whitespace). |
198
+ | `yini parse config.yini --js` | JavaScript object | JavaScript-style object (unquoted keys, single quotes). |
199
+ | `yini parse config.yini -o out.json` | File output | Writes formatted JSON to file (default format). |
114
200
 
115
- ## Usage
201
+ >💡 `--js` and `--compact` are mutually exclusive.
202
+ >💡 Tip: You can combine --output with any style flag to control both formatting and destination.
116
203
 
117
- ### Installation
204
+ ---
118
205
 
119
- 1. **Install it globally from npm — (requires Node.js)**
120
- Open your terminal and run:
121
- ```
122
- npm install -g yini-cli
123
- ```
206
+ ## 📁 Output File Handling
124
207
 
125
- 2. **Verify installation**
126
- Run this in your terminal:
127
- ```bash
128
- yini --version
129
- ```
130
- Should print the version (e.g., 1.0.0).
208
+ When using `-o, --output <file>`, YINI CLI applies safe write rules:
131
209
 
132
- Then you may try:
133
- ```bash
134
- yini --help
135
- ```
136
- Should show you the CLI help for YINI.
210
+ | Scenario | Result |
211
+ |----------|--------|
212
+ | File does not exist | File is written |
213
+ | 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 |
215
+ | `--overwrite` is used | File is always overwritten |
216
+ | `--no-overwrite` is used | Command fails if file exists |
137
217
 
138
- 3. **Test functionality**
139
- Create a simple test file, for example: `config.yini`:
140
- ```yini
141
- ^ App
142
- name = "My App Title"
143
- version = "1.2.3"
144
- pageSize = 25
145
- darkTheme = off
146
- ```
218
+ This prevents accidental overwriting of newer generated files.
147
219
 
148
- Then run:
149
- ```bash
150
- yini parse config.yini
151
- ```
152
-
153
- Expected result, your CLI should output a parsed version of the config and output something similar to:
154
- ```js
155
- {
156
- App: {
157
- name: 'My App Title',
158
- version: '1.2.3',
159
- pageSize: 25,
160
- darkTheme: false
161
- }
162
- }
163
- ```
220
+ Use:
221
+ `--overwrite` to force replacement.
164
222
 
165
223
  ---
166
224
 
167
- ## 📤 Output Modes for `yini parse`
168
-
169
- The `parse` command supports multiple output styles:
170
-
171
- | Command Example | Output Style | Description |
172
- |----------------------------------------------------|----------------------|------------------------------------------------------------------------------|
173
- | `yini parse config.yini` | JS-style object | Uses Node’s `util.inspect` — human-readable, shows types, nesting, etc. |
174
- | `yini parse config.yini --pretty` | Pretty JSON | Formatted and indented with `JSON.stringify(obj, null, 4)`. |
175
- | `yini parse config.yini --json` | Compact JSON | Compact and machine-friendly `JSON.stringify(obj)`. |
176
- | `yini parse config.yini --output out.txt` | File (JS-style) | Default style, written to specified file. |
177
- | `yini parse config.yini --pretty --output out.json`| File (Pretty JSON) | Formatted JSON written to file. |
225
+ ## 🛠 Roadmap
226
+ Areas of planned and possible future expansion:
178
227
 
179
- >💡 Tip: You can combine --output with any style flag to control both formatting and destination.
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.
180
234
 
181
235
  ---
182
236
 
183
237
  ## Links
184
- - ➡️ [Getting Started: Intro to YINI Config Format](https://github.com/YINI-lang/YINI-spec/blob/develop/Docs/Intro-to-YINI-Config-Format.md)
185
- *Beginner-friendly walkthrough and basic usage examples.*
186
-
187
238
  - ➡️ [YINI Parser on npm](https://www.npmjs.com/package/yini-parser)
188
239
  *Install and view package details.*
189
240
 
190
- - ➡️ [Read the YINI Specification](https://github.com/YINI-lang/YINI-spec/blob/release/YINI-Specification.md#table-of-contents)
191
- *Full formal spec for the YINI format, including syntax and features.*
192
-
193
- - ➡️ [YINI Parser on GitHub](https://github.com/YINI-lang/yini-parser-typescript)
194
- *TypeScript source code, issue tracker, and contributing guide.*
241
+ - ➡️ [YINI Project](https://github.com/YINI-lang)
242
+ *YINI home on GitHub.*
195
243
 
196
- - ➡️ [YINI vs Other Formats](https://github.com/YINI-lang/YINI-spec/tree/release#-summary-difference-with-other-formats)
197
- *How does YINI differ: comparison with INI, YAML, and JSON.*
198
-
199
- - ➡️ [Why YINI? (Project Rationale)](https://github.com/YINI-lang/YINI-spec/blob/release/RATIONALE.md)
200
- *Learn about the motivations and design decisions behind YINI.*
244
+ ---
201
245
 
202
- - ➡️ [YINI Project](https://github.com/YINI-lang)
203
- *YINI home.*
246
+ ## Contribution & Involvement
247
+ Contributions, issues, and feedback are welcome. Even small improvements or suggestions are appreciated.
204
248
 
205
249
  ---
206
250
 
@@ -211,4 +255,7 @@ In this project on GitHub, the `libs` directory contains third party software an
211
255
 
212
256
  ---
213
257
 
214
- ~ **YINI ≡** • [https://yini-lang.org](https://yini-lang.org)
258
+ **^YINI ≡**
259
+ > YINI — Clear, Structured Configuration Files.
260
+
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)
@@ -0,0 +1,2 @@
1
+ import type { Command } from 'commander';
2
+ export declare function enableHelpAll(program: Command): void;
@@ -0,0 +1,23 @@
1
+ export function enableHelpAll(program) {
2
+ const originalFormatHelp = program.createHelp().formatHelp;
3
+ program.configureHelp({
4
+ formatHelp: (cmd, helper) => {
5
+ // Call the original formatter (not the overridden one)
6
+ let output = originalFormatHelp.call(helper, cmd, helper);
7
+ // Only expand the top-level help
8
+ if (cmd !== program)
9
+ return output;
10
+ for (const sub of program.commands) {
11
+ output += '\n\n';
12
+ output +=
13
+ '--------------------------------------------------------\n';
14
+ output += `* Command: ${sub.name()}\n`;
15
+ output +=
16
+ '--------------------------------------------------------\n';
17
+ output += originalFormatHelp.call(helper, sub, helper);
18
+ // output += '--------------------------------------------------------\n'
19
+ }
20
+ return output;
21
+ },
22
+ });
23
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * NOTE: Keep contributors in acknowledged in README or --credits.
3
+ */
4
+ export declare const printInfo: () => void;
@@ -0,0 +1,44 @@
1
+ import { createRequire } from 'module';
2
+ import { removeSuffix, toColRow } from '../utils/string.js';
3
+ // import pkg from '../../package.json' with { type: 'json' } // NOTE: Must use { type: 'json' } when in ESM mode.
4
+ const require = createRequire(import.meta.url);
5
+ const pkg = require('../../package.json');
6
+ /*
7
+ YINI CLI — Environment Information
8
+ ──────────────────────────────────
9
+
10
+ CLI Version: 1.1.1-beta
11
+ Parser Version: 1.3.2-beta
12
+
13
+ if verbose(
14
+ Node.js: v20.18.0
15
+ Platform: win32 x64
16
+ Working Directory: D:\Sources\YINI-lang-WORK\yini-cli
17
+ )
18
+
19
+ Author: Marko K. Seppänen
20
+ License: Apache-2.0
21
+
22
+ Repository: https://github.com/YINI-lang/yini-cli
23
+ Homepage: https://yini-lang.org
24
+
25
+ ---
26
+
27
+ */
28
+ /**
29
+ * NOTE: Keep contributors in acknowledged in README or --credits.
30
+ */
31
+ export const printInfo = () => {
32
+ const size = 16;
33
+ console.log();
34
+ console.log('YINI CLI — Environment Information');
35
+ console.log('==================================');
36
+ console.log();
37
+ console.log(toColRow(size, 'CLI Version:', pkg.version));
38
+ console.log(toColRow(size, 'Parser Version:', pkg.dependencies['yini-parser'].replace('^', '')));
39
+ console.log(toColRow(size, 'Author:', pkg.author));
40
+ console.log(toColRow(size, 'License:', pkg.license));
41
+ console.log();
42
+ console.log(toColRow(size, 'Repository:', 'https://github.com/YINI-lang/yini-cli'));
43
+ console.log(toColRow(size, 'Homepage:', removeSuffix(pkg.homepage, '/')));
44
+ };
@@ -1,7 +1,14 @@
1
1
  import { IGlobalOptions } from '../types.js';
2
+ /**
3
+ * @deprecated pretty Deprecated since 2026 Feb! Use `json` instead.
4
+ */
2
5
  export interface IParseCommandOptions extends IGlobalOptions {
3
6
  pretty?: boolean;
4
7
  json?: boolean;
8
+ compact?: boolean;
9
+ js?: boolean;
5
10
  output?: string;
11
+ bestEffort?: boolean;
12
+ overwrite?: boolean;
6
13
  }
7
14
  export declare const parseFile: (file: string, commandOptions: IParseCommandOptions) => void;
@@ -1,72 +1,131 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
- import util from 'util';
4
3
  import YINI from 'yini-parser';
5
- import { debugPrint, printObject, toPrettyJSON } from '../utils/print.js';
4
+ import { debugPrint, printObject, toPrettyJS, toPrettyJSON, } from '../utils/print.js';
6
5
  // -------------------------------------------------------------------------
6
+ /*
7
+ TODO / SHOULD-DO:
8
+
9
+ yini parse <file> [options]
10
+
11
+ Options
12
+ -------
13
+
14
+ Parsing mode:
15
+ --strict
16
+ --lenient (default)
17
+
18
+ Output format:
19
+ --to <json|json-compact> (default, --to json)
20
+ --json (alias for --to json, default)
21
+ --compact (alias for --to json-compact)
22
+
23
+ Output handling:
24
+ -o, --output <file> (default) No overwrite if dest is more recent than source file (override with --overwrite)
25
+ --overwrite
26
+ --no-overwrite
27
+
28
+ Execution control:
29
+ --fail-fast
30
+ --best-effort = ignore-errors within a file, attempt recovery and still emit outp
31
+ --No for parse, --keep-going = continue to the next file when one fails
32
+ --max-errors <n>
33
+ --verbose
34
+ --checks (default)
35
+ --no-checks
36
+
37
+ Policy control (advanced):
38
+ --duplicates-policy <error|warn|allow>
39
+ --reserved-policy <error|warn|allow>
40
+ */
7
41
  export const parseFile = (file, commandOptions) => {
8
42
  const outputFile = commandOptions.output || '';
9
- const isStrictMode = !!commandOptions.strict;
10
- let outputStyle = 'JS-style';
43
+ // const isStrictMode = !!commandOptions.strict
44
+ const outputStyle = resolveOutputStyle(commandOptions);
11
45
  debugPrint('file = ' + file);
12
46
  debugPrint('output = ' + commandOptions.output);
13
47
  debugPrint('commandOptions:');
14
48
  printObject(commandOptions);
15
- if (commandOptions.pretty) {
16
- outputStyle = 'Pretty-JSON';
49
+ doParseFile(file, commandOptions, outputStyle, outputFile);
50
+ };
51
+ const resolveOutputStyle = (options) => {
52
+ if (options.js && options.compact) {
53
+ throw new Error('--js and --compact cannot be combined.');
17
54
  }
18
- else if (commandOptions.json) {
19
- outputStyle = 'JSON-compact';
55
+ if (options.compact)
56
+ return 'JSON-compact';
57
+ if (options.js)
58
+ return 'JS-style';
59
+ if (options.pretty) {
60
+ console.warn('Warning: --pretty is deprecated. Use --json instead.');
20
61
  }
21
- else {
22
- outputStyle = 'JS-style';
62
+ return 'Pretty-JSON';
63
+ };
64
+ const renderOutput = (parsed, style) => {
65
+ switch (style) {
66
+ case 'JS-style':
67
+ return toPrettyJS(parsed);
68
+ case 'JSON-compact':
69
+ return JSON.stringify(parsed);
70
+ case 'Pretty-JSON':
71
+ default:
72
+ return toPrettyJSON(parsed);
23
73
  }
24
- doParseFile(file, commandOptions, outputStyle, outputFile);
25
74
  };
26
75
  const doParseFile = (file, commandOptions, outputStyle, outputFile = '') => {
27
76
  // let strictMode = !!commandOptions.strict
28
- let preferredFailLevel = 'auto';
77
+ let preferredFailLevel = commandOptions.bestEffort
78
+ ? 'ignore-errors'
79
+ : 'auto';
29
80
  let includeMetaData = false;
30
81
  debugPrint('File = ' + file);
31
82
  debugPrint('outputStyle = ' + outputStyle);
32
83
  const parseOptions = {
33
84
  strictMode: commandOptions.strict ?? false,
34
- // failLevel: 'errors',
35
85
  failLevel: preferredFailLevel,
36
- // failLevel: 'ignore-errors',
37
86
  includeMetadata: includeMetaData,
38
87
  };
39
- // If --force then override fail-level.
40
- if (commandOptions.force) {
88
+ // If --best-effort then override fail-level.
89
+ if (commandOptions.bestEffort) {
41
90
  parseOptions.failLevel = 'ignore-errors';
42
91
  }
43
92
  try {
44
93
  const parsed = YINI.parseFile(file, parseOptions);
45
- let output = '';
46
- switch (outputStyle) {
47
- case 'Pretty-JSON':
48
- output = toPrettyJSON(parsed);
49
- break;
50
- case 'Console.log':
51
- output = '<todo>';
52
- break;
53
- case 'JSON-compact':
54
- output = JSON.stringify(parsed);
55
- break;
56
- default:
57
- output = util.inspect(parsed, { depth: null, colors: false });
58
- }
94
+ const output = renderOutput(parsed, outputStyle);
59
95
  if (outputFile) {
96
+ const resolved = path.resolve(outputFile);
97
+ enforceWritePolicy(file, resolved, commandOptions.overwrite);
60
98
  // Write JSON output to file instead of stdout.
61
- fs.writeFileSync(path.resolve(outputFile), output, 'utf-8');
62
- console.log(`Output written to file: "${outputFile}"`);
99
+ fs.writeFileSync(resolved, output, 'utf-8');
100
+ if (commandOptions.verbose) {
101
+ console.log(`Output written to file: "${outputFile}"`);
102
+ }
63
103
  }
64
104
  else {
65
105
  console.log(output);
66
106
  }
67
107
  }
68
108
  catch (err) {
69
- console.error(`Error: ${err.message}`);
109
+ const message = err instanceof Error ? err.message : String(err);
110
+ console.error(`Error: ${message}`);
70
111
  process.exit(1);
71
112
  }
72
113
  };
114
+ const enforceWritePolicy = (srcPath, destPath, overwrite) => {
115
+ if (!fs.existsSync(destPath)) {
116
+ return; // File does not exist, OK to write.
117
+ }
118
+ const srcStat = fs.statSync(srcPath);
119
+ const destStat = fs.statSync(destPath);
120
+ const destIsNewer = destStat.mtimeMs >= srcStat.mtimeMs;
121
+ if (overwrite === true) {
122
+ return; // Explicit overwrite, OK.
123
+ }
124
+ if (overwrite === false) {
125
+ throw new Error(`File "${destPath}" already exists. Overwriting disabled (--no-overwrite).`);
126
+ }
127
+ // Default policy (overwrite undefined).
128
+ if (destIsNewer) {
129
+ throw new Error(`Destination file "${destPath}" is newer than source. Use --overwrite to force.`);
130
+ }
131
+ };
@@ -1,6 +1,5 @@
1
1
  import { IGlobalOptions } from '../types.js';
2
2
  export interface IValidateCommandOptions extends IGlobalOptions {
3
- report?: boolean;
4
- details?: boolean;
3
+ stats?: boolean;
5
4
  }
6
5
  export declare const validateFile: (file: string, commandOptions?: IValidateCommandOptions) => never;