yini-cli 1.0.2-beta → 1.1.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 +80 -3
- package/dist/commands/parseCommand.d.ts +7 -0
- package/dist/commands/{parse.js → parseCommand.js} +25 -24
- package/dist/commands/validateCommand.d.ts +6 -0
- package/dist/commands/validateCommand.js +217 -0
- package/dist/descriptions.js +1 -1
- package/dist/globalOptions/helpOption.d.ts +8 -0
- package/dist/globalOptions/helpOption.js +52 -0
- package/dist/{commands/info.js → globalOptions/infoOption.js} +2 -1
- package/dist/index.js +100 -129
- package/dist/types.d.ts +4 -6
- package/dist/types.js +4 -0
- package/dist/utils/print.d.ts +3 -4
- package/dist/utils/print.js +11 -8
- package/dist/utils/yiniCliHelpers.d.ts +2 -0
- package/dist/utils/yiniCliHelpers.js +13 -0
- package/package.json +6 -4
- package/sample.yini +2 -0
- package/dist/commands/parse.d.ts +0 -2
- package/dist/commands/validate.d.ts +0 -7
- package/dist/commands/validate.js +0 -32
- /package/dist/{commands/info.d.ts → globalOptions/infoOption.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
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.*
|
|
5
5
|
|
|
6
|
-
[](https://www.npmjs.com/package/yini-cli) [](https://github.com/YINI-lang/yini-cli/actions/workflows/run-all-tests.yml) [](https://github.com/YINI-lang/yini-cli/actions/workflows/run-regression-tests.yml) [](https://github.com/YINI-lang/yini-cli/actions/workflows/run-cli-test.yml)
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
@@ -15,6 +15,15 @@
|
|
|
15
15
|
* Supports commonly used configuration structures.
|
|
16
16
|
- *Developed to meet practical needs, driven by curiosity and a desire **for configuration clarity, simplicity, minimalism, and flexibility**.
|
|
17
17
|
|
|
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!
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Requirements
|
|
23
|
+
YINI CLI requires Node.js **v20 or later**.
|
|
24
|
+
|
|
25
|
+
(It has also been tested with Node.js v13+, but v20+ is recommended for best compatibility.)
|
|
26
|
+
|
|
18
27
|
---
|
|
19
28
|
|
|
20
29
|
## 💡 What is YINI?
|
|
@@ -28,7 +37,75 @@
|
|
|
28
37
|
|
|
29
38
|
---
|
|
30
39
|
|
|
31
|
-
##
|
|
40
|
+
## Quick Into to YINI Format
|
|
41
|
+
|
|
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.
|
|
46
|
+
|
|
47
|
+
// Note: In YINI, spaces and tabs don't change meaning - indentation is just
|
|
48
|
+
// for readability.
|
|
49
|
+
|
|
50
|
+
/* This is a block comment
|
|
51
|
+
|
|
52
|
+
In YINI, section headers use repeated characters "^" at the start to
|
|
53
|
+
show their level: (Section header names are case-sensitive.)
|
|
54
|
+
|
|
55
|
+
^ SectionLevel1
|
|
56
|
+
^^ SectionLevel2
|
|
57
|
+
^^^ SectionLevel3
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
^ App // Definition of section (group) "App"
|
|
61
|
+
name = 'My Title' // Keys and values are written as key = value
|
|
62
|
+
items = 25
|
|
63
|
+
darkMode = true // "ON" and "YES" works too
|
|
64
|
+
|
|
65
|
+
// Sub-section of the "App" section
|
|
66
|
+
^^ Special
|
|
67
|
+
primaryColor = #336699 // Hex number format
|
|
68
|
+
isCaching = false // "OFF" and "NO" works too
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**The above YINI converted to a JS object:**
|
|
72
|
+
```js
|
|
73
|
+
{
|
|
74
|
+
App: {
|
|
75
|
+
name: 'My Title',
|
|
76
|
+
items: 25,
|
|
77
|
+
darkMode: true,
|
|
78
|
+
Special: {
|
|
79
|
+
primaryColor: 3368601,
|
|
80
|
+
isCaching: false
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**In JSON:**
|
|
87
|
+
```json
|
|
88
|
+
{
|
|
89
|
+
"App":{
|
|
90
|
+
"name":"My Title",
|
|
91
|
+
"items":25,
|
|
92
|
+
"darkMode":true,
|
|
93
|
+
"Special":{
|
|
94
|
+
"primaryColor":3368601,
|
|
95
|
+
"isCaching":false
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
That's it!
|
|
102
|
+
|
|
103
|
+
- ▶️ Link to [examples/](https://github.com/YINI-lang/yini-parser-typescript/tree/main/examples) files.
|
|
104
|
+
- ▶️ Link to [Demo Apps](https://github.com/YINI-lang/yini-demo-apps/tree/main) with complete basic usage.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Bigger Intro into YINI Config Format
|
|
32
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.
|
|
33
110
|
|
|
34
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.
|
|
@@ -39,7 +116,7 @@ To learn more, see the [Getting Started: Intro to YINI Config Format](https://gi
|
|
|
39
116
|
|
|
40
117
|
### Installation
|
|
41
118
|
|
|
42
|
-
1. **Install it globally from npm**
|
|
119
|
+
1. **Install it globally from npm — (requires Node.js)**
|
|
43
120
|
Open your terminal and run:
|
|
44
121
|
```
|
|
45
122
|
npm install -g yini-cli
|
|
@@ -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
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
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 = ' +
|
|
12
|
-
debugPrint('
|
|
13
|
-
printObject(
|
|
14
|
-
if (
|
|
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 (
|
|
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,
|
|
24
|
+
doParseFile(file, commandOptions, outputStyle, outputFile);
|
|
27
25
|
};
|
|
28
|
-
const doParseFile = (file,
|
|
29
|
-
// let strictMode = !!
|
|
30
|
-
let
|
|
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
|
-
|
|
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,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
|
+
};
|
package/dist/descriptions.js
CHANGED
|
@@ -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': '
|
|
5
|
+
'For-command-info': 'Deprecated: Use `yini --info` or `yini -i` instead.',
|
|
6
6
|
};
|
|
@@ -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(
|
|
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
|
-
//
|
|
3
|
-
//
|
|
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 {
|
|
7
|
-
import {
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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(
|
|
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',
|
|
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
|
-
|
|
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
|
-
// .
|
|
105
|
-
// .
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
//
|
|
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
|
-
|
|
118
|
-
|
|
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('
|
|
133
|
-
console.log(toPrettyJSON(
|
|
105
|
+
console.log('mergedOptions:');
|
|
106
|
+
console.log(toPrettyJSON(mergedOptions));
|
|
134
107
|
}
|
|
135
108
|
if (file) {
|
|
136
|
-
parseFile(file,
|
|
109
|
+
parseFile(file, mergedOptions);
|
|
137
110
|
}
|
|
138
111
|
else {
|
|
139
112
|
program.help();
|
|
140
113
|
}
|
|
141
114
|
});
|
|
115
|
+
appendGlobalOptionsTo(parseCmd);
|
|
142
116
|
/**
|
|
143
|
-
*
|
|
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('--
|
|
162
|
-
.option('--details', 'Print detailed
|
|
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
|
-
|
|
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,
|
|
137
|
+
validateFile(file, mergedOptions);
|
|
168
138
|
}
|
|
169
139
|
else {
|
|
170
140
|
program.help();
|
|
171
141
|
}
|
|
172
142
|
});
|
|
173
|
-
|
|
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
|
|
1
|
+
export interface IGlobalOptions {
|
|
2
2
|
strict?: boolean;
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
+
// -------------------------------------------------------------------------
|
package/dist/utils/print.d.ts
CHANGED
|
@@ -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;
|
package/dist/utils/print.js
CHANGED
|
@@ -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 (
|
|
27
|
-
|
|
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 (
|
|
38
|
-
|
|
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,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
|
+
"version": "1.1.0-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",
|
|
@@ -46,9 +46,11 @@
|
|
|
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",
|
|
@@ -67,8 +69,8 @@
|
|
|
67
69
|
},
|
|
68
70
|
"author": "Marko K. Seppänen",
|
|
69
71
|
"dependencies": {
|
|
70
|
-
"commander": "^14.0.
|
|
71
|
-
"yini-parser": "^1.0
|
|
72
|
+
"commander": "^14.0.1",
|
|
73
|
+
"yini-parser": "^1.3.0-beta"
|
|
72
74
|
},
|
|
73
75
|
"devDependencies": {
|
|
74
76
|
"@eslint/js": "^9.31.0",
|
package/sample.yini
CHANGED
package/dist/commands/parse.d.ts
DELETED
|
@@ -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
|
-
};
|
|
File without changes
|