yini-cli 1.0.3-beta → 1.1.1-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,54 +1,130 @@
1
1
  # YINI-CLI
2
- **Command-line tool for working with YINI configuration files. Validate, inspect, and convert to JSON with pretty output.**
2
+ **Command-line tool for validating, inspecting, and converting YINI configuration files to JSON.**
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
+ *YINI is an INI-inspired configuration format designed for clarity and predictability. It supports nesting, comments, and a formally defined syntax, so configuration files stay easy to read and reason about as they grow.*
5
5
 
6
- [![npm version](https://img.shields.io/npm/v/yini-parser.svg)](https://www.npmjs.com/package/yini-parser) [![All Tests](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)
6
+ [![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
+
8
+ This tool is useful if you work with human-edited configuration files and want predictable structure without indentation-based rules.
7
9
 
8
10
  ---
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
+ ## Quick Start
13
+
14
+ ### Requirements
15
+ YINI CLI requires Node.js **v20 or later**.
16
+
17
+ (It has also been tested with Node.js v13+, but v20+ is recommended for best compatibility.)
18
+
19
+ ### Installation
20
+
21
+ 1. **Install it globally from npm — (requires Node.js)**
22
+ Open your terminal and run:
23
+ ```
24
+ npm install -g yini-cli
25
+ ```
26
+
27
+ 2. **Verify installation**
28
+ Run this in your terminal:
29
+ ```bash
30
+ yini --version
31
+ ```
32
+ Should print the version (e.g., 1.0.0).
33
+
34
+ Then you may try:
35
+ ```bash
36
+ yini --help
37
+ ```
38
+ Should show you the CLI help for YINI.
39
+
40
+ 3. **Test functionality**
41
+ Create a simple test file, for example: `config.yini`:
42
+ ```yini
43
+ ^ App
44
+ name = "My App Title"
45
+ version = "1.2.3"
46
+ pageSize = 25
47
+ darkTheme = off
48
+ ```
49
+
50
+ Then run:
51
+ ```bash
52
+ yini parse config.yini
53
+ ```
54
+
55
+ Expected result, your CLI should output a parsed version of the config and output something similar to:
56
+ ```js
57
+ {
58
+ App: {
59
+ name: 'My App Title',
60
+ version: '1.2.3',
61
+ pageSize: 25,
62
+ darkTheme: false
63
+ }
64
+ }
65
+ ```
66
+
67
+ ⭐ If this was useful, [star it on GitHub](https://github.com/YINI-lang/yini-cli) — it helps a lot, thank you!
17
68
 
18
69
  ---
19
70
 
20
- ## 💡 What is YINI?
21
- - **INI-inspired** — with added support for typing, comments, and nested sections.
22
- - **Uses minimal syntax** yet aims to keep maximum clarity.
23
- - Section nesting **without requiring indentation or dot-delimited keys**.
24
- - **Supports strict and lenient modes**, and all major data types.
25
- - Designed for compatibility with both **manual editing** and **automation**.
26
- - 👉 See [how YINI differs from JSON, YAML, INI, and TOML](https://github.com/YINI-lang/yini-parser-typescript/tree/main/examples/compare-formats.md).
27
- - Want the full syntax reference? See the [YINI Specification](https://github.com/YINI-lang/YINI-spec).
71
+ ### Typical use cases
72
+
73
+ - Validating configuration files during development or CI.
74
+ - Inspecting and debugging structured configuration.
75
+ - Converting YINI files to JSON for tooling and automation.
28
76
 
29
77
  ---
30
78
 
31
- ## Quick Into to YINI Format
79
+ ## 🙋‍♀️ Why YINI?
80
+ - **Indentation-independent structure:** The YINI config format is indentation-independent, meaning any space or tab never changes meaning.
81
+ - **Explicit nesting:** It uses clear header markers (`^`, `^^`, `^^^`) to define hierarchy (like in Markdown), without long dotted keys.
82
+ - **Multiple data types:** Supports boolean literals (`true` / `false`, `Yes` / `No`, etc), numbers, arrays (lists), and JS-style objects natively, with explicit string syntax.
83
+ - **Comments support:** YINI supports multiple comment styles (`#`, `//`, `/* ... */`, and `;`) allowing one to document config directly in the file.
84
+ - **Predictable parsing rules:** Well-defined rules with optional strict and lenient modes, for different use-requirements.
85
+
86
+ ---
32
87
 
33
- YINI code looks like this:
34
- ```yini
35
- // This is a comment in YINI
36
- // YINI is a simple, human-readable configuration file format.
88
+ ## Usage of command `yini`
37
89
 
38
- // Note: In YINI, spaces and tabs don't change meaning - indentation is just
39
- // for readability.
90
+ ```bash
91
+ Usage: yini [options] [command]
40
92
 
41
- /* This is a block comment
93
+ CLI for parsing and validating YINI config files.
42
94
 
43
- In YINI, section headers use repeated characters "^" at the start to
44
- show their level: (Section header names are case-sensitive.)
95
+ Options:
96
+ -v, --version Output the version number.
97
+ -i, --info Show extended information (details, links, etc.).
98
+ -s, --strict Enable strict parsing mode.
99
+ -f, --force Continue parsing even if errors occur.
100
+ -q, --quiet Reduce output (show only errors).
101
+ --silent Suppress all output (even errors, exit code only).
102
+ -h, --help Display help for command.
45
103
 
46
- ^ SectionLevel1
47
- ^^ SectionLevel2
48
- ^^^ SectionLevel3
49
- */
104
+ Commands:
105
+ parse [options] <file> Parse a YINI file (*.yini) and print the result.
106
+ validate [options] <file> Checks if the file can be parsed as valid YINI.
107
+ info Deprecated: Use `yini --info` or `yini -i` instead.
108
+ help [command] Display help for command.
109
+
110
+ Examples:
111
+ $ yini parse config.yini
112
+ $ yini validate --strict config.yini
113
+ $ yini parse config.yini --pretty --output out.json
114
+
115
+ For help with a specific command, use -h or --help. For example:
116
+ $ yini validate --help
117
+ ```
118
+
119
+ ---
120
+
121
+ ## Quick Look at YINI
122
+
123
+ Here's a small example showing YINI structure and comments:
124
+ ```yini
125
+ // This is a comment in YINI
50
126
 
51
- ^ App // Definition of section (group) "App"
127
+ ^ App // Defines section (group) "App"
52
128
  name = 'My Title' // Keys and values are written as key = value
53
129
  items = 25
54
130
  darkMode = true // "ON" and "YES" works too
@@ -57,6 +133,8 @@ YINI code looks like this:
57
133
  ^^ Special
58
134
  primaryColor = #336699 // Hex number format
59
135
  isCaching = false // "OFF" and "NO" works too
136
+
137
+ # This is a comment too.
60
138
  ```
61
139
 
62
140
  **The above YINI converted to a JS object:**
@@ -96,65 +174,6 @@ That's it!
96
174
 
97
175
  ---
98
176
 
99
- ## Bigger Intro into YINI Config Format
100
- **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.
101
-
102
- 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.
103
-
104
- ---
105
-
106
- ## Usage
107
-
108
- ### Installation
109
-
110
- 1. **Install it globally from npm — (requires Node.js)**
111
- Open your terminal and run:
112
- ```
113
- npm install -g yini-cli
114
- ```
115
-
116
- 2. **Verify installation**
117
- Run this in your terminal:
118
- ```bash
119
- yini --version
120
- ```
121
- Should print the version (e.g., 1.0.0).
122
-
123
- Then you may try:
124
- ```bash
125
- yini --help
126
- ```
127
- Should show you the CLI help for YINI.
128
-
129
- 3. **Test functionality**
130
- Create a simple test file, for example: `config.yini`:
131
- ```yini
132
- ^ App
133
- name = "My App Title"
134
- version = "1.2.3"
135
- pageSize = 25
136
- darkTheme = off
137
- ```
138
-
139
- Then run:
140
- ```bash
141
- yini parse config.yini
142
- ```
143
-
144
- Expected result, your CLI should output a parsed version of the config and output something similar to:
145
- ```js
146
- {
147
- App: {
148
- name: 'My App Title',
149
- version: '1.2.3',
150
- pageSize: 25,
151
- darkTheme: false
152
- }
153
- }
154
- ```
155
-
156
- ---
157
-
158
177
  ## 📤 Output Modes for `yini parse`
159
178
 
160
179
  The `parse` command supports multiple output styles:
@@ -171,27 +190,29 @@ The `parse` command supports multiple output styles:
171
190
 
172
191
  ---
173
192
 
174
- ## Links
175
- - ➡️ [Getting Started: Intro to YINI Config Format](https://github.com/YINI-lang/YINI-spec/blob/develop/Docs/Intro-to-YINI-Config-Format.md)
176
- *Beginner-friendly walkthrough and basic usage examples.*
193
+ ## 🛠 Roadmap
194
+ Areas of planned and possible future expansion:
195
+
196
+ 1. **Improve existing commands** — Continued functionality improvements, better diagnostics, and expanded QA for `parse` and `validate` and their options.
197
+ 2. Command `format`: Pretty-print or normalize a `.yini` file.
198
+ 3. Command `lint`: Stricter stylistic checks (like `validate`, but opinionated).
199
+ 4. Command `diff`: Compare two YINI files and show structural/config differences.
200
+ 5. Command `convert`: Convert a `JSON` or `XML` file into YINI format.
201
+
202
+ ---
177
203
 
204
+ ## Links
178
205
  - ➡️ [YINI Parser on npm](https://www.npmjs.com/package/yini-parser)
179
206
  *Install and view package details.*
180
207
 
181
- - ➡️ [Read the YINI Specification](https://github.com/YINI-lang/YINI-spec/blob/release/YINI-Specification.md#table-of-contents)
182
- *Full formal spec for the YINI format, including syntax and features.*
183
-
184
- - ➡️ [YINI Parser on GitHub](https://github.com/YINI-lang/yini-parser-typescript)
185
- *TypeScript source code, issue tracker, and contributing guide.*
208
+ - ➡️ [YINI Project](https://github.com/YINI-lang)
209
+ *YINI home on gitHub.*
186
210
 
187
- - ➡️ [YINI vs Other Formats](https://github.com/YINI-lang/YINI-spec/tree/release#-summary-difference-with-other-formats)
188
- *How does YINI differ: comparison with INI, YAML, and JSON.*
189
-
190
- - ➡️ [Why YINI? (Project Rationale)](https://github.com/YINI-lang/YINI-spec/blob/release/RATIONALE.md)
191
- *Learn about the motivations and design decisions behind YINI.*
211
+ ---
192
212
 
193
- - ➡️ [YINI Project](https://github.com/YINI-lang)
194
- *YINI home.*
213
+ ## Contribution & Involvement
214
+ Interested in contributing or trying ideas?
215
+ Issues, feedback, and experiments are welcome — even small ones.
195
216
 
196
217
  ---
197
218
 
@@ -202,4 +223,9 @@ In this project on GitHub, the `libs` directory contains third party software an
202
223
 
203
224
  ---
204
225
 
205
- ~ **YINI ≡** [https://yini-lang.org](https://yini-lang.org)
226
+ If you found this useful, a GitHub star helps the project a lot ⭐
227
+
228
+ **^YINI ≡**
229
+ > YINI — Clear, Structured Configuration Files.
230
+
231
+ [yini-lang.org](https://yini-lang.org) · [YINI on GitHub](https://github.com/YINI-lang)
@@ -0,0 +1,7 @@
1
+ import { IGlobalOptions } from '../types.js';
2
+ export interface IParseCommandOptions extends IGlobalOptions {
3
+ pretty?: boolean;
4
+ json?: boolean;
5
+ output?: string;
6
+ }
7
+ export declare const parseFile: (file: string, commandOptions: IParseCommandOptions) => void;
@@ -3,44 +3,45 @@ import path from 'node:path';
3
3
  import util from 'util';
4
4
  import YINI from 'yini-parser';
5
5
  import { debugPrint, printObject, toPrettyJSON } from '../utils/print.js';
6
- export const parseFile = (file, options) => {
7
- const outputFile = options.output || '';
8
- const isStrictMode = !!options.strict;
6
+ // -------------------------------------------------------------------------
7
+ export const parseFile = (file, commandOptions) => {
8
+ const outputFile = commandOptions.output || '';
9
+ const isStrictMode = !!commandOptions.strict;
9
10
  let outputStyle = 'JS-style';
10
11
  debugPrint('file = ' + file);
11
- debugPrint('output = ' + options.output);
12
- debugPrint('options:');
13
- printObject(options);
14
- if (options.pretty) {
12
+ debugPrint('output = ' + commandOptions.output);
13
+ debugPrint('commandOptions:');
14
+ printObject(commandOptions);
15
+ if (commandOptions.pretty) {
15
16
  outputStyle = 'Pretty-JSON';
16
17
  }
17
- else if (options.log) {
18
- outputStyle = 'Console.log';
19
- }
20
- else if (options.json) {
18
+ else if (commandOptions.json) {
21
19
  outputStyle = 'JSON-compact';
22
20
  }
23
21
  else {
24
22
  outputStyle = 'JS-style';
25
23
  }
26
- doParseFile(file, outputStyle, isStrictMode, outputFile);
24
+ doParseFile(file, commandOptions, outputStyle, outputFile);
27
25
  };
28
- const doParseFile = (file, outputStyle, isStrictMode = false, outputFile = '') => {
29
- // let strictMode = !!options.strict
30
- let bailSensitivity = 'auto';
26
+ const doParseFile = (file, commandOptions, outputStyle, outputFile = '') => {
27
+ // let strictMode = !!commandOptions.strict
28
+ let preferredFailLevel = 'auto';
31
29
  let includeMetaData = false;
32
30
  debugPrint('File = ' + file);
33
31
  debugPrint('outputStyle = ' + outputStyle);
32
+ const parseOptions = {
33
+ strictMode: commandOptions.strict ?? false,
34
+ // failLevel: 'errors',
35
+ failLevel: preferredFailLevel,
36
+ // failLevel: 'ignore-errors',
37
+ includeMetadata: includeMetaData,
38
+ };
39
+ // If --force then override fail-level.
40
+ if (commandOptions.force) {
41
+ parseOptions.failLevel = 'ignore-errors';
42
+ }
34
43
  try {
35
- // const raw = fs.readFileSync(file, 'utf-8')
36
- // const parsed = YINI.parseFile(
37
- //const parsed = YINI.parseFile(file)
38
- const parsed = YINI.parseFile(file, isStrictMode, bailSensitivity, includeMetaData);
39
- // const parsed = YINI.parse(raw)
40
- // const output = options.pretty
41
- // ? // ? JSON.stringify(parsed, null, 2)
42
- // toPrettyJSON(parsed)
43
- // : JSON.stringify(parsed)
44
+ const parsed = YINI.parseFile(file, parseOptions);
44
45
  let output = '';
45
46
  switch (outputStyle) {
46
47
  case 'Pretty-JSON':
@@ -0,0 +1,6 @@
1
+ import { IGlobalOptions } from '../types.js';
2
+ export interface IValidateCommandOptions extends IGlobalOptions {
3
+ report?: boolean;
4
+ details?: boolean;
5
+ }
6
+ export declare const validateFile: (file: string, commandOptions?: IValidateCommandOptions) => never;
@@ -0,0 +1,217 @@
1
+ import assert from 'node:assert';
2
+ import { exit } from 'node:process';
3
+ import YINI from 'yini-parser';
4
+ const IS_DEBUG = false; // For local debugging purposes, etc.
5
+ // -------------------------------------------------------------------------
6
+ export const validateFile = (file, commandOptions = {}) => {
7
+ let parsedResult = undefined;
8
+ let isCatchedError = true;
9
+ const parseOptions = {
10
+ strictMode: commandOptions.strict ?? false,
11
+ // failLevel: 'errors',
12
+ failLevel: commandOptions.force ? 'ignore-errors' : 'errors',
13
+ // failLevel: 'ignore-errors',
14
+ includeMetadata: true,
15
+ includeDiagnostics: true,
16
+ silent: true,
17
+ };
18
+ try {
19
+ parsedResult = YINI.parseFile(file, parseOptions);
20
+ isCatchedError = false;
21
+ }
22
+ catch (err) {
23
+ isCatchedError = true;
24
+ }
25
+ let metadata = null;
26
+ let errors = 0;
27
+ let warnings = 0;
28
+ let notices = 0;
29
+ let infos = 0;
30
+ if (!isCatchedError && parsedResult?.meta) {
31
+ metadata = parsedResult?.meta;
32
+ assert(metadata); // Make sure there is metadata!
33
+ // printObject(metadata, true)
34
+ assert(metadata.diagnostics);
35
+ const diag = metadata.diagnostics;
36
+ errors = diag.errors.errorCount;
37
+ warnings = diag.warnings.warningCount;
38
+ notices = diag.notices.noticeCount;
39
+ infos = diag.infos.infoCount;
40
+ }
41
+ IS_DEBUG && console.log();
42
+ IS_DEBUG && console.log('isCatchedError = ' + isCatchedError);
43
+ IS_DEBUG && console.log('TEMP OUTPUT');
44
+ IS_DEBUG && console.log('isCatchedError = ' + isCatchedError);
45
+ IS_DEBUG && console.log(' errors = ' + errors);
46
+ IS_DEBUG && console.log('warnings = ' + warnings);
47
+ IS_DEBUG && console.log(' notices = ' + notices);
48
+ IS_DEBUG && console.log(' infor = ' + infos);
49
+ IS_DEBUG && console.log('metadata = ' + metadata);
50
+ IS_DEBUG &&
51
+ console.log('includeMetadata = ' +
52
+ metadata?.diagnostics?.effectiveOptions.includeMetadata);
53
+ IS_DEBUG && console.log('commandOptions.report = ' + commandOptions?.report);
54
+ IS_DEBUG && console.log();
55
+ if (!commandOptions.silent && !isCatchedError) {
56
+ if (commandOptions.report) {
57
+ if (!metadata) {
58
+ console.error('Internal Error: No meta data found');
59
+ }
60
+ assert(metadata); // Make sure there is metadata!
61
+ console.log();
62
+ console.log(formatToReport(file, metadata).trim());
63
+ }
64
+ if (commandOptions.details) {
65
+ if (!metadata) {
66
+ console.error('Internal Error: No meta data found');
67
+ }
68
+ assert(metadata); // Make sure there is metadata!
69
+ console.log();
70
+ printDetailsOnAllIssue(file, metadata);
71
+ }
72
+ }
73
+ //state returned:
74
+ // - passed (no errors/warnings),
75
+ // - finished (with warnings, no errors) / or - passed with warnings
76
+ // - failed (errors),
77
+ if (isCatchedError) {
78
+ errors = 1;
79
+ }
80
+ // console.log()
81
+ if (errors) {
82
+ // red ✖
83
+ console.error(formatToStatus('Failed', errors, warnings, notices, infos));
84
+ exit(1);
85
+ }
86
+ else if (warnings) {
87
+ // yellow ⚠️
88
+ console.warn(formatToStatus('Passed-with-Warnings', errors, warnings, notices, infos));
89
+ exit(0);
90
+ }
91
+ else {
92
+ // green ✔
93
+ console.log(formatToStatus('Passed', errors, warnings, notices, infos));
94
+ exit(0);
95
+ }
96
+ };
97
+ const formatToStatus = (statusType, errors, warnings, notices, infos) => {
98
+ const totalMsgs = errors + warnings + notices + infos;
99
+ let str = ``;
100
+ switch (statusType) {
101
+ case 'Passed':
102
+ str = '✔ Validation passed';
103
+ break;
104
+ case 'Passed-with-Warnings':
105
+ str = '⚠️ Validation finished';
106
+ break;
107
+ case 'Failed':
108
+ str = '✖ Validation failed';
109
+ break;
110
+ }
111
+ str += ` (${errors} errors, ${warnings} warnings, ${totalMsgs} total messages)`;
112
+ return str;
113
+ };
114
+ // --- Format to a Report --------------------------------------------------------
115
+ //@todo format parsed.meta to report as
116
+ /*
117
+ - Produce a summary-level validation report.
118
+ - Output is structured and concise (e.g. JSON or table-like).
119
+ - Focus on counts, pass/fail, severity summary.
120
+
121
+ Example:
122
+ Validation report for config.yini:
123
+ Errors: 3
124
+ Warnings: 1
125
+ Notices: 0
126
+ Result: INVALID
127
+ */
128
+ const formatToReport = (fileWithPath, metadata) => {
129
+ // console.log('formatToReport(..)')
130
+ // printObject(metadata)
131
+ // console.log()
132
+ assert(metadata.diagnostics);
133
+ const diag = metadata.diagnostics;
134
+ const issuesCount = diag.errors.errorCount +
135
+ diag.warnings.warningCount +
136
+ diag.notices.noticeCount +
137
+ diag.infos.infoCount;
138
+ const str = `Validation Report
139
+ =================
140
+
141
+ File "${fileWithPath}"
142
+ Issues: ${issuesCount}
143
+
144
+ Summary
145
+ -------
146
+ Mode: ${metadata.mode}
147
+ Strict: ${metadata.mode === 'strict'}
148
+
149
+ Errors: ${diag.errors.errorCount}
150
+ Warnings: ${diag.warnings.warningCount}
151
+ Notices: ${diag.notices.noticeCount}
152
+ Infos: ${diag.infos.infoCount}
153
+
154
+ Stats
155
+ -----
156
+ Line Count: ${metadata.source.lineCount}
157
+ Section Count: ${metadata.structure.sectionCount}
158
+ Member Count: ${metadata.structure.memberCount}
159
+ Nesting Depth: ${metadata.structure.maxDepth}
160
+
161
+ Has @YINI: ${metadata.source.hasYiniMarker}
162
+ Has /END: ${metadata.source.hasDocumentTerminator}
163
+ Byte Size: ${metadata.source.sourceType === 'inline' ? 'n/a' : metadata.source.byteSize + ' bytes'}
164
+ `;
165
+ return str;
166
+ };
167
+ // -------------------------------------------------------------------------
168
+ // --- Format to a Details --------------------------------------------------------
169
+ //@todo format parsed.meta to details as
170
+ /*
171
+ - Show full detailed validation messages.
172
+ - Output includes line numbers, columns, error codes, and descriptive text.
173
+ - Useful for debugging YINI files.
174
+
175
+ Example:
176
+ Error at line 5, column 9: Unexpected '/END' — expected <EOF>
177
+ Warning at line 10, column 3: Section level skipped (0 → 2)
178
+ Notice at line 1: Unused @yini directive
179
+ */
180
+ const printDetailsOnAllIssue = (fileWithPath, metadata) => {
181
+ // console.log('printDetails(..)')
182
+ // printObject(metadata)
183
+ // console.log(toPrettyJSON(metadata))
184
+ // console.log()
185
+ assert(metadata.diagnostics);
186
+ const diag = metadata.diagnostics;
187
+ console.log('Details');
188
+ console.log('-------');
189
+ console.log();
190
+ const errors = diag.errors.payload;
191
+ printIssues('Error ', 'E', errors);
192
+ const warnings = diag.warnings.payload;
193
+ printIssues('Warning', 'W', warnings);
194
+ const notices = diag.notices.payload;
195
+ printIssues('Notice ', 'N', notices);
196
+ const infos = diag.infos.payload;
197
+ printIssues('Info ', 'I', infos);
198
+ return;
199
+ };
200
+ // -------------------------------------------------------------------------
201
+ const printIssues = (typeLabel, prefix, issues) => {
202
+ const leftPadding = ' ';
203
+ issues.forEach((iss, i) => {
204
+ const id = '#' + prefix + '-0' + (i + 1);
205
+ // const id: string = '' + prefix + '-0' + (i+1) + ':'
206
+ let str = `${typeLabel} [${id}]:\n`;
207
+ str +=
208
+ leftPadding +
209
+ `At line ${iss.line}, column ${iss.column}: ${iss.message}`;
210
+ if (iss.advice)
211
+ str += '\n' + leftPadding + iss.advice;
212
+ if (iss.hint)
213
+ str += '\n' + leftPadding + iss.hint;
214
+ console.log(str);
215
+ console.log();
216
+ });
217
+ };
@@ -2,5 +2,5 @@ export const descriptions = {
2
2
  yini: 'CLI for parsing and validating YINI config files.',
3
3
  'For-command-parse': 'Parse a YINI file (*.yini) and print the result.',
4
4
  'For-command-validate': 'Checks if the file can be parsed as valid YINI.',
5
- 'For-command-info': 'Show extended information (details, links, etc.).',
5
+ 'For-command-info': 'Deprecated: Use `yini --info` or `yini -i` instead.',
6
6
  };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Text to be displayed BEFORE the built-in help text block.
3
+ */
4
+ export declare const getHelpTextBefore: () => string;
5
+ /**
6
+ * Text to be displayed AFTER the built-in help text block.
7
+ */
8
+ export declare const getHelpTextAfter: () => string;
@@ -0,0 +1,52 @@
1
+ /*
2
+ * The main (global) option help.
3
+ */
4
+ import { getPackageName, getPackageVersion } from '../utils/yiniCliHelpers.js';
5
+ /**
6
+ * Text to be displayed BEFORE the built-in help text block.
7
+ */
8
+ export const getHelpTextBefore = () => {
9
+ return `${getPackageName()} ${getPackageVersion()}
10
+ YINI CLI (Yet another INI)
11
+
12
+ Parse and validate YINI configuration files.
13
+ A config format, inspired by INI, with type-safe values, nested
14
+ sections, comments, minimal syntax noise, and optional strict mode.
15
+
16
+ Designed for clarity and consistency. :)\n`;
17
+ };
18
+ /**
19
+ * Text to be displayed AFTER the built-in help text block.
20
+ */
21
+ export const getHelpTextAfter = () => {
22
+ return `
23
+ Examples:
24
+ $ yini parse config.yini
25
+ $ yini validate --strict config.yini
26
+ $ yini parse config.yini --pretty --output out.json
27
+
28
+ For help with a specific command, use -h or --help. For example:
29
+ $ yini validate --help
30
+
31
+ Sample "config.yini":
32
+ ^ App
33
+ title = 'My App'
34
+ items = 10
35
+ debug = ON
36
+
37
+ ^ Server
38
+ host = 'localhost'
39
+ port = 8080
40
+ useTLS = OFF
41
+
42
+ // Sub-section of Server.
43
+ ^^ Login
44
+ username = 'user'
45
+ password = 'secret'
46
+
47
+ More info:
48
+ https://github.com/YINI-lang/yini-cli
49
+
50
+ Into to YINI Config:
51
+ https://github.com/YINI-lang/YINI-spec/blob/develop/Docs/Intro-to-YINI-Config-Format.md`;
52
+ };
@@ -1,8 +1,9 @@
1
1
  import { createRequire } from 'module';
2
+ // import pkg from '../../package.json' with { type: 'json' } // NOTE: Must use { type: 'json' } when in ESM mode.
2
3
  const require = createRequire(import.meta.url);
3
4
  const pkg = require('../../package.json');
4
5
  export const printInfo = () => {
5
- console.log(`** YINI CLI **`);
6
+ console.log(`*** YINI CLI ***`);
6
7
  console.log(`yini-cli: ${pkg.version}`);
7
8
  console.log(`yini-parser: ${pkg.dependencies['yini-parser'].replace('^', '')}`);
8
9
  console.log(`Author: ${pkg.author}`);
package/dist/index.js CHANGED
@@ -1,187 +1,158 @@
1
1
  #!/usr/bin/env node
2
- // (!) NOTE: Leave above shebang as first line!
3
- // import pkg from '../package.json'
2
+ //
3
+ // (!) IMPORTANT: Leave the top shebang as the very first line! (otherwise command will break)
4
+ //
4
5
  import { createRequire } from 'module';
5
6
  import { Command } from 'commander';
6
- import { printInfo } from './commands/info.js';
7
- import { parseFile } from './commands/parse.js';
8
- import { validateFile } from './commands/validate.js';
7
+ import { parseFile } from './commands/parseCommand.js';
8
+ import { validateFile, } from './commands/validateCommand.js';
9
9
  import { isDebug, isDev } from './config/env.js';
10
10
  import { descriptions as descr } from './descriptions.js';
11
+ import { getHelpTextAfter, getHelpTextBefore, } from './globalOptions/helpOption.js';
12
+ import { printInfo } from './globalOptions/infoOption.js';
11
13
  import { debugPrint, toPrettyJSON } from './utils/print.js';
14
+ import { getPackageVersion } from './utils/yiniCliHelpers.js';
12
15
  const require = createRequire(import.meta.url);
13
- const pkg = require('../package.json');
14
- const program = new Command();
15
- /*
16
-
17
- Idea/suggestion
18
- yini [parse] [--strict] [--pretty] [--output]
19
-
20
- Current suggestion:
21
- * yini parse config.yini
22
- JS-style object using printObject()
23
- to stdout
24
- * yini parse config.yini --pretty
25
- Pretty JSON using JSON.stringify(obj, null, 4)
26
- to stdout
27
- * yini parse config.yini --output out.txt
28
- JS-style object
29
- to out.txt
30
- * yini parse config.yini --pretty --output out.json
31
- Pretty JSON
32
- to out.json
33
-
34
- New suggestion:
35
- Current suggestion:
36
- * yini parse config.yini
37
- JS-style object using printObject(obj) (using using util.inspect)
38
- to stdout
39
- * yini parse config.yini --pretty
40
- Pretty JSON using JSON.stringify(obj, null, 4) (formatted, readable)
41
- to stdout
42
- * yini parse config.yini --log
43
- Intended for quick output using console.log (nested object may get compacted/abbreviate)
44
- to stdout
45
- * yini parse config.yini --json
46
- Stringigies JSON using using JSON.stringify(obj) (compact, machine-parseable)
47
- to stdout
48
- * yini parse config.yini --output out.txt
49
- JS-style object
50
- to out.txt
51
- * yini parse config.yini --pretty --output out.json
52
- Pretty JSON
53
- to out.json
54
-
55
- */
56
- // Display help for command
57
- program
16
+ // const pkg = require('../package.json')
17
+ // --- Helper functions --------------------------------------------------------
18
+ function appendGlobalOptionsTo(cmd) {
19
+ const help = program.createHelp();
20
+ const globals = help.visibleOptions(program);
21
+ if (!globals.length)
22
+ return;
23
+ const lines = globals
24
+ // .map(
25
+ // (opt) =>
26
+ // ` ${help.optionTerm(opt)} ${help.optionDescription(opt)}`,
27
+ // )
28
+ // .join('\n')
29
+ .map((option) => {
30
+ const optName = option.name();
31
+ if (optName === 'version' ||
32
+ optName === 'info' ||
33
+ optName === 'help') {
34
+ debugPrint('Skip patching option.name() = ' +
35
+ option.name() +
36
+ ' into per-command help');
37
+ }
38
+ else {
39
+ return ` ${help.optionTerm(option)} ${help.optionDescription(option)}`;
40
+ }
41
+ })
42
+ .join('\n')
43
+ .trim();
44
+ cmd.addHelpText('after', `\nGlobal options:\n ${lines}`);
45
+ // cmd.addHelpText('after', ` ${lines}`)
46
+ }
47
+ // -------------------------------------------------------------------------
48
+ const program = new Command()
58
49
  .name('yini')
59
50
  .description(descr.yini)
60
51
  // Below will replace all auto-registered items (especially the descriptions starting with a capital and ending with a period).
61
- .version(pkg.version, '-v, --version', 'Output the version number.')
52
+ .version(getPackageVersion(), '-v, --version', 'Output the version number.')
62
53
  .helpOption('-h, --help', 'Display help for command.')
63
54
  .helpCommand('help [command]', 'Display help for command.');
64
- program.addHelpText('before', `YINI CLI (Yet another INI)
65
-
66
- For parsing and validating YINI configuration files.
67
- A config format, inspired by INI, with type-safe values, nested
68
- sections, comments, minimal syntax noise, and optional strict mode.
69
-
70
- Designed for clarity and consistency. :)\n`);
71
- program.addHelpText('after', `
72
- Examples:
73
- $ yini parse config.yini
74
- $ yini validate config.yini --strict
75
- $ yini parse config.yini --pretty --output out.json
76
-
77
- Example of "config.yini":
78
- ^ App
79
- title = 'My App'
80
- items = 10
81
- debug = ON
82
-
83
- ^ Server
84
- host = 'localhost'
85
- port = 8080
86
- useTLS = OFF
87
-
88
- // Sub-section of Server.
89
- ^^ Login
90
- username = 'user'
91
- password = 'secret'
92
-
93
- More info:
94
- https://github.com/YINI-lang/yini-cli
95
-
96
- Into to YINI Config:
97
- https://github.com/YINI-lang/YINI-spec/blob/develop/Docs/Intro-to-YINI-Config-Format.md`);
98
- //program.command('help [command]').description('Display help for command')
55
+ program.addHelpText('before', getHelpTextBefore());
56
+ program.addHelpText('after', getHelpTextAfter());
99
57
  /**
100
- *
101
- * Maybe later, to as default command: parse <parse>
58
+ * The (main/global) option: "--info, --strict, --quite, --silent"
59
+ */
60
+ // Suggestions for future: --verbose, --debug, --no-color, --color, --timing, --stdin
61
+ program
62
+ .option('-i, --info', 'Show extended information (details, links, etc.).')
63
+ .option('-s, --strict', 'Enable strict parsing mode.')
64
+ .option('-f, --force', 'Continue parsing even if errors occur.')
65
+ .option('-q, --quiet', 'Reduce output (show only errors).')
66
+ .option('--silent', 'Suppress all output (even errors, exit code only).')
67
+ .action((options) => {
68
+ debugPrint('Run global options');
69
+ if (isDebug()) {
70
+ console.log('Global options:');
71
+ console.log(toPrettyJSON(options));
72
+ }
73
+ printInfo();
74
+ });
75
+ /**
76
+ * The (main/global) option: "--info"
102
77
  */
103
78
  // program
104
- // .argument('<file>', 'File to parse')
105
- // .option('--strict', 'Parse YINI in strict-mode')
106
- // .option('--pretty', 'Pretty-print output as JSON')
107
- // // .option('--log', 'Use console.log output format (compact, quick view)')
108
- // .option('--json', 'Compact JSON output using JSON.stringify')
109
- // .option('--output <file>', 'Write output to a specified file')
110
- // .action((file, options) => {
111
- // if (file) {
112
- // parseFile(file, options)
113
- // } else {
114
- // program.help()
79
+ // .option('-s, --strict', 'Enable parsing in strict-mode.')
80
+ // .action((options) => {
81
+ // debugPrint('Run (global) option "strict"')
82
+ // if (isDebug()) {
83
+ // console.log('options:')
84
+ // console.log(toPrettyJSON(options))
115
85
  // }
86
+ // printInfo()
116
87
  // })
117
- // Explicit "parse" command
118
- program
88
+ /**
89
+ * The command: "parse <file>"
90
+ */
91
+ const parseCmd = program
119
92
  .command('parse <file>')
120
93
  .description(descr['For-command-parse'])
121
- .option('--strict', 'Parse YINI in strict-mode.')
122
94
  .option('--pretty', 'Pretty-print output as JSON.')
123
- // .option('--log', 'Use console.log output format (compact, quick view)')
124
95
  .option('--json', 'Compact JSON output using JSON.stringify.')
125
96
  .option('--output <file>', 'Write output to a specified file.')
126
97
  .action((file, options) => {
98
+ const globals = program.opts(); // Global options.
99
+ const mergedOptions = { ...globals, ...options }; // Merge global options with per-command options.
127
100
  debugPrint('Run command "parse"');
128
101
  debugPrint('isDebug(): ' + isDebug());
129
102
  debugPrint('isDev() : ' + isDev());
130
103
  debugPrint(`<file> = ${file}`);
131
104
  if (isDebug()) {
132
- console.log('options:');
133
- console.log(toPrettyJSON(options));
105
+ console.log('mergedOptions:');
106
+ console.log(toPrettyJSON(mergedOptions));
134
107
  }
135
108
  if (file) {
136
- parseFile(file, options);
109
+ parseFile(file, mergedOptions);
137
110
  }
138
111
  else {
139
112
  program.help();
140
113
  }
141
114
  });
115
+ appendGlobalOptionsTo(parseCmd);
142
116
  /**
143
- * To handle command validate, e.g.:
144
- * yini validate config.yini
145
- * yini validate config.yini --strict
146
- * yini validate config.yini --details
147
- * yini validate config.yini --silent
148
- *
149
- * If details:
150
- * Details:
151
- * - YINI version: 1.0.0-beta.6
152
- * - Mode: strict
153
- * - Keys: 42
154
- * - Sections: 6
155
- * - Nesting depth: 3
156
- * - Has @yini: true
117
+ * The command: "validate <file>"
157
118
  */
158
- program
119
+ const validateCmd = program
159
120
  .command('validate <file>')
160
121
  .description(descr['For-command-validate'])
161
- .option('--strict', 'Enable parsing in strict-mode')
162
- .option('--details', 'Print detailed meta-data info (e.g., key count, nesting, etc.).')
163
- .option('--silent', 'Suppress output')
122
+ .option('--report', 'Print detailed meta-data info (e.g., key count, nesting, etc.).')
123
+ .option('--details', 'Print detailed validation info (e.g., line locations, error codes, descriptive text, etc.).')
124
+ // .option('--silent', 'Suppress output')
164
125
  .action((file, options) => {
165
- //@todo add debugPrint
126
+ const globals = program.opts(); // Global options.
127
+ const mergedOptions = { ...globals, ...options }; // Merge global options with per-command options.
128
+ debugPrint('Run command "parse"');
129
+ debugPrint('isDebug(): ' + isDebug());
130
+ debugPrint('isDev() : ' + isDev());
131
+ debugPrint(`<file> = ${file}`);
132
+ if (isDebug()) {
133
+ console.log('mergedOptions:');
134
+ console.log(toPrettyJSON(mergedOptions));
135
+ }
166
136
  if (file) {
167
- validateFile(file, options);
137
+ validateFile(file, mergedOptions);
168
138
  }
169
139
  else {
170
140
  program.help();
171
141
  }
172
142
  });
173
- // Command info
143
+ appendGlobalOptionsTo(validateCmd);
144
+ // About to get deleted, moved to main option --info
145
+ //@todo Delete
174
146
  program
175
147
  .command('info')
176
- // .command('')
177
148
  .description(descr['For-command-info'])
178
- // .option('info')
179
149
  .action((options) => {
180
150
  debugPrint('Run command "info"');
181
151
  if (isDebug()) {
182
152
  console.log('options:');
183
153
  console.log(toPrettyJSON(options));
184
154
  }
155
+ console.warn('Deprecated: Use `yini --info` or `yini -i` instead of `yini info`.');
185
156
  printInfo();
186
157
  });
187
158
  // NOTE: Converting YINI files to other formats than json and js.
package/dist/types.d.ts CHANGED
@@ -1,8 +1,6 @@
1
- export interface ICLIParseOptions {
1
+ export interface IGlobalOptions {
2
2
  strict?: boolean;
3
- pretty?: boolean;
4
- log?: boolean;
5
- json?: boolean;
6
- output?: string;
3
+ force?: boolean;
4
+ quiet?: boolean;
5
+ silent?: boolean;
7
6
  }
8
- export type TBailSensitivity = 'auto' | 0 | 1 | 2;
package/dist/types.js CHANGED
@@ -1 +1,5 @@
1
+ // //export type TBailSensitivity = 'auto' | 0 | 1 | 2
2
+ // export type TPreferredFailLevel = 'auto' | 0 | 1 | 2 // Preferred bail sensitivity level.
3
+ // export type TBailSensitivityLevel = 0 | 1 | 2 // Bail sensitivity level.
1
4
  export {};
5
+ // -------------------------------------------------------------------------
@@ -2,14 +2,13 @@ export declare const debugPrint: (str?: any) => void;
2
2
  export declare const devPrint: (str?: any) => void;
3
3
  export declare const toJSON: (obj: any) => string;
4
4
  export declare const toPrettyJSON: (obj: any) => string;
5
- /**
6
- * Pretty-prints a JavaScript object as formatted JSON to the console.
5
+ /** Pretty-prints a JavaScript object as formatted JSON to the console.
7
6
  * Strict JSON, all keys are enclosed in ", etc.
8
7
  */
9
- export declare const printJSON: (obj: any) => void;
8
+ export declare const printJSON: (obj: any, isForce?: boolean) => void;
10
9
  /**
11
10
  * Print a full JavaScript object in a human-readable way (not as JSON).
12
11
  * Not strict JSON, and shows functions, symbols, getters/setters, and class names.
13
12
  * @param isColors If true, the output is styled with ANSI color codes.
14
13
  */
15
- export declare const printObject: (obj: any, isColors?: boolean) => void;
14
+ export declare const printObject: (obj: any, isForce?: boolean, isColors?: boolean) => void;
@@ -18,13 +18,14 @@ export const toPrettyJSON = (obj) => {
18
18
  const str = JSON.stringify(obj, null, 4);
19
19
  return str;
20
20
  };
21
- /**
22
- * Pretty-prints a JavaScript object as formatted JSON to the console.
21
+ /** Pretty-prints a JavaScript object as formatted JSON to the console.
23
22
  * Strict JSON, all keys are enclosed in ", etc.
24
23
  */
25
- export const printJSON = (obj) => {
26
- if (isProdEnv() || (isTestEnv() && !isDebug()))
27
- return;
24
+ export const printJSON = (obj, isForce = false) => {
25
+ if (!isForce) {
26
+ if (isProdEnv() || (isTestEnv() && !isDebug()))
27
+ return;
28
+ }
28
29
  const str = toPrettyJSON(obj);
29
30
  console.log(str);
30
31
  };
@@ -33,8 +34,10 @@ export const printJSON = (obj) => {
33
34
  * Not strict JSON, and shows functions, symbols, getters/setters, and class names.
34
35
  * @param isColors If true, the output is styled with ANSI color codes.
35
36
  */
36
- export const printObject = (obj, isColors = true) => {
37
- if (isProdEnv() || (isTestEnv() && !isDebug()))
38
- return;
37
+ export const printObject = (obj, isForce = false, isColors = true) => {
38
+ if (!isForce) {
39
+ if (isProdEnv() || (isTestEnv() && !isDebug()))
40
+ return;
41
+ }
39
42
  console.log(util.inspect(obj, { depth: null, colors: isColors }));
40
43
  };
@@ -0,0 +1,2 @@
1
+ export declare const getPackageName: () => string;
2
+ export declare const getPackageVersion: () => string;
@@ -0,0 +1,13 @@
1
+ import { readFileSync } from 'fs';
2
+ import { dirname, resolve } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ const __filename = fileURLToPath(import.meta.url);
5
+ const __dirname = dirname(__filename);
6
+ const pkg = JSON.parse(readFileSync(resolve(__dirname, '../../package.json'), 'utf-8'));
7
+ // import pkg from '../../package.json' with { type: 'json' } // NOTE: Must use { type: 'json' } when in ESM mode.
8
+ export const getPackageName = () => {
9
+ return '' + pkg.name;
10
+ };
11
+ export const getPackageVersion = () => {
12
+ return '' + pkg.version;
13
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yini-cli",
3
- "version": "1.0.3-beta",
3
+ "version": "1.1.1-beta",
4
4
  "description": "CLI for parsing and validating YINI config files: type-safe values, nested sections, comments, minimal syntax noise, and optional strict mode.",
5
5
  "keywords": [
6
6
  "yini",
@@ -29,7 +29,7 @@
29
29
  "bin": {
30
30
  "yini": "./dist/index.js"
31
31
  },
32
- "homepage": "https://github.com/YINI-lang",
32
+ "homepage": "https://yini-lang.org/",
33
33
  "license": "Apache-2.0",
34
34
  "files": [
35
35
  "dist/",
@@ -46,15 +46,18 @@
46
46
  "start": "node ./bin/yini.js",
47
47
  "start:dev": "cross-env NODE_ENV=development isDev=1 tsx src/index.ts",
48
48
  "start:dev:debug": "cross-env isDebug=1 npm run start:dev",
49
+ "run:help": "npm run start -- --help",
49
50
  "run:version": "npm run start -- --version",
50
- "run:info": "npm run start -- info",
51
+ "run:info": "npm run start -- --info",
51
52
  "run:parse": "npm run start -- parse sample.yini",
53
+ "run:validate": "npm run start -- validate sample.yini",
52
54
  "test:smoke": "vitest tests/smoke",
53
55
  "test:general": "vitest tests/general",
54
56
  "test": "vitest run",
55
57
  "test:debug": "cross-env isDebug=1 vitest run --reporter=verbose",
56
58
  "test:general:debug": "cross-env isDebug=1 npm run test:general",
57
59
  "test:watch": "vitest --watch",
60
+ "test:cov": "nyc npm test",
58
61
  "ci:test": "vitest run --reporter=verbose",
59
62
  "ci:test:smoke": "vitest run tests/smoke --reporter=verbose",
60
63
  "lint": "eslint src --ext .ts",
@@ -67,8 +70,8 @@
67
70
  },
68
71
  "author": "Marko K. Seppänen",
69
72
  "dependencies": {
70
- "commander": "^14.0.0",
71
- "yini-parser": "^1.1.0-beta"
73
+ "commander": "^14.0.1",
74
+ "yini-parser": "^1.3.2-beta"
72
75
  },
73
76
  "devDependencies": {
74
77
  "@eslint/js": "^9.31.0",
@@ -83,6 +86,7 @@
83
86
  "execa": "^9.6.0",
84
87
  "husky": "^9.1.7",
85
88
  "lint-staged": "^16.0.0",
89
+ "nyc": "^17.1.0",
86
90
  "prettier": "^3.5.3",
87
91
  "tsx": "^4.7.0",
88
92
  "typescript": "^5.8.3",
package/sample.yini CHANGED
@@ -17,3 +17,5 @@ Enabled = true
17
17
 
18
18
  ^ Env // Defines a section named Env.
19
19
  code = "dev"
20
+
21
+ /End // The document terminator '/END' is optional.
@@ -1,2 +0,0 @@
1
- import { ICLIParseOptions } from '../types.js';
2
- export declare const parseFile: (file: string, options: ICLIParseOptions) => void;
@@ -1,7 +0,0 @@
1
- interface IValidateOptions {
2
- strict?: boolean;
3
- details?: boolean;
4
- silent?: boolean;
5
- }
6
- export declare const validateFile: (file: string, options?: IValidateOptions) => never;
7
- export {};
@@ -1,32 +0,0 @@
1
- import fs from 'node:fs';
2
- import { exit } from 'node:process';
3
- import YINI from 'yini-parser';
4
- import { printObject } from '../utils/print.js';
5
- export const validateFile = (file, options = {}) => {
6
- try {
7
- const content = fs.readFileSync(file, 'utf-8');
8
- const isMeta = true;
9
- const parsed = YINI.parse(content, options.strict ?? false, 'auto', isMeta);
10
- if (!options.silent) {
11
- console.log(`✔ File is valid${options.strict ? ' (strict mode)' : ''}.`);
12
- if (options.details) {
13
- //@todo format parsed.meta to details as
14
- /*
15
- * Details:
16
- * - YINI version: 1.0.0-beta.6
17
- * - Mode: strict
18
- * - Keys: 42
19
- * - Sections: 6
20
- * - Nesting depth: 3
21
- * - Has @yini: true
22
- */
23
- printObject(parsed.meta);
24
- }
25
- }
26
- exit(0);
27
- }
28
- catch (err) {
29
- console.error(`✖ Validation failed: ${err.message}`);
30
- exit(1);
31
- }
32
- };