yini-cli 1.3.4-beta → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -53
- package/dist/commands/commonFunctions.d.ts +15 -0
- package/dist/commands/commonFunctions.js +76 -0
- package/dist/commands/parseCommand.d.ts +1 -1
- package/dist/commands/parseCommand.js +64 -51
- package/dist/commands/validateCommand.d.ts +6 -1
- package/dist/commands/validateCommand.js +440 -402
- package/dist/descriptions.js +6 -1
- package/dist/globalOptions/helpOption.js +9 -6
- package/dist/index.js +52 -32
- package/dist/utils/string.js +1 -0
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -1,20 +1,13 @@
|
|
|
1
|
-
# YINI
|
|
1
|
+
# YINI CLI
|
|
2
2
|
> **Readable configuration without indentation pitfalls or JSON verbosity.**
|
|
3
3
|
|
|
4
|
-
**The official
|
|
4
|
+
**The official CLI for validating, inspecting, and converting YINI configuration files to JSON or JavaScript, built by the YINI-lang project.**
|
|
5
5
|
|
|
6
|
-
*YINI is an INI-inspired
|
|
6
|
+
*YINI is an INI-inspired, human-friendly configuration format with real structure, nested sections, comments, and predictable parsing.*
|
|
7
7
|
|
|
8
|
-
[](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) [](https://www.npmjs.com/package/yini-cli)
|
|
8
|
+
[](https://www.npmjs.com/package/yini-cli) [](https://www.typescriptlang.org/) [](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) [](https://www.npmjs.com/package/yini-cli)
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
---
|
|
13
|
-
|
|
14
|
-
## Example of YINI code
|
|
15
|
-
> A basic YINI configuration example, showing a section, nested section, comments:
|
|
16
|
-

|
|
17
|
-
Source: [basic.yini](./samples/basic.yini)
|
|
10
|
+
Designed for developers and teams who want human-edited configuration with explicit structure and no indentation-based semantics.
|
|
18
11
|
|
|
19
12
|
## Quick Start
|
|
20
13
|
|
|
@@ -23,7 +16,7 @@ YINI CLI requires Node.js **v20 or later**.
|
|
|
23
16
|
|
|
24
17
|
### Installation
|
|
25
18
|
|
|
26
|
-
1. **Install
|
|
19
|
+
1. **Install globally from npm — (requires Node.js)**
|
|
27
20
|
Open your terminal and run:
|
|
28
21
|
```
|
|
29
22
|
npm install -g yini-cli
|
|
@@ -34,22 +27,22 @@ YINI CLI requires Node.js **v20 or later**.
|
|
|
34
27
|
```bash
|
|
35
28
|
yini --version
|
|
36
29
|
```
|
|
37
|
-
This should print the installed version
|
|
30
|
+
This should print the installed version.
|
|
38
31
|
|
|
39
|
-
Then
|
|
32
|
+
Then try:
|
|
40
33
|
```bash
|
|
41
34
|
yini --help
|
|
42
35
|
```
|
|
43
|
-
|
|
36
|
+
This should show the CLI help.
|
|
44
37
|
|
|
45
38
|
3. **Test functionality**
|
|
46
39
|
Create a simple test file, for example: `config.yini`:
|
|
47
40
|
```yini
|
|
48
41
|
^ App
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
42
|
+
name = "My App Title"
|
|
43
|
+
version = "1.2.3"
|
|
44
|
+
pageSize = 25
|
|
45
|
+
darkTheme = off
|
|
53
46
|
```
|
|
54
47
|
|
|
55
48
|
Then run:
|
|
@@ -57,7 +50,7 @@ YINI CLI requires Node.js **v20 or later**.
|
|
|
57
50
|
yini parse config.yini
|
|
58
51
|
```
|
|
59
52
|
|
|
60
|
-
Expected
|
|
53
|
+
Expected output:
|
|
61
54
|
```json
|
|
62
55
|
{
|
|
63
56
|
"App": {
|
|
@@ -77,7 +70,12 @@ YINI CLI requires Node.js **v20 or later**.
|
|
|
77
70
|
|
|
78
71
|
---
|
|
79
72
|
|
|
80
|
-
##
|
|
73
|
+
## What YINI looks like
|
|
74
|
+
> A basic YINI configuration example, showing a section, nested section, comments:
|
|
75
|
+

|
|
76
|
+
Source: [basic.yini](./samples/basic.yini)
|
|
77
|
+
|
|
78
|
+
### A larger example
|
|
81
79
|
> A real-world YINI configuration example, showing sections, nesting, comments, and multiple data types:
|
|
82
80
|

|
|
83
81
|
Source: [config.yini](./samples/config.yini)
|
|
@@ -86,10 +84,10 @@ Source: [config.yini](./samples/config.yini)
|
|
|
86
84
|
|
|
87
85
|
## 🙋♀️ Why YINI?
|
|
88
86
|
- **Indentation-independent structure:** YINI is indentation-independent — whitespace never alters structural meaning.
|
|
89
|
-
- **Explicit nesting
|
|
90
|
-
- **Multiple data types:** Supports
|
|
91
|
-
- **
|
|
92
|
-
- **Predictable parsing
|
|
87
|
+
- **Explicit nesting:** It uses clear header markers (`^`, `^^`, `^^^`) to define hierarchy, making large configurations easier to scan and refactor.
|
|
88
|
+
- **Multiple data types:** Supports booleans (`true` / `false`, `yes` / `no`, etc.), numbers, lists, and inline objects, with explicit string syntax.
|
|
89
|
+
- **Comment support:** YINI supports multiple comment styles (`#`, `//`, `/* ... */`, and `;`), making it easier to document configuration directly in the file.
|
|
90
|
+
- **Predictable parsing:** Well-defined rules with optional strict and lenient modes for different use cases.
|
|
93
91
|
|
|
94
92
|
---
|
|
95
93
|
|
|
@@ -130,7 +128,7 @@ yini parse --help
|
|
|
130
128
|
|
|
131
129
|
---
|
|
132
130
|
|
|
133
|
-
##
|
|
131
|
+
## A closer look at YINI
|
|
134
132
|
|
|
135
133
|
Here's a small example showing YINI structure and comments:
|
|
136
134
|
```yini
|
|
@@ -149,7 +147,7 @@ Here's a small example showing YINI structure and comments:
|
|
|
149
147
|
# This is a comment too.
|
|
150
148
|
```
|
|
151
149
|
|
|
152
|
-
**The above YINI
|
|
150
|
+
**The above YINI as a JavaScript object:**
|
|
153
151
|
```js
|
|
154
152
|
{
|
|
155
153
|
App: {
|
|
@@ -201,9 +199,7 @@ The `parse` command supports multiple output formats:
|
|
|
201
199
|
>💡 `--js` and `--compact` are mutually exclusive.
|
|
202
200
|
>💡 Tip: You can combine --output with any style flag to control both formatting and destination.
|
|
203
201
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
## 📁 Output File Handling
|
|
202
|
+
### Output File Handling
|
|
207
203
|
|
|
208
204
|
When using `-o, --output <file>`, YINI CLI applies safe write rules:
|
|
209
205
|
|
|
@@ -211,45 +207,44 @@ When using `-o, --output <file>`, YINI CLI applies safe write rules:
|
|
|
211
207
|
|----------|--------|
|
|
212
208
|
| File does not exist | File is written |
|
|
213
209
|
| File exists and is **older** than the input YINI file | File is overwritten |
|
|
214
|
-
| File exists and is **newer** than the input YINI file |
|
|
210
|
+
| File exists and is **newer** than the input YINI file | Skipped by default |
|
|
211
|
+
| File exists and output content is unchanged | Skipped |
|
|
215
212
|
| `--overwrite` is used | File is always overwritten |
|
|
216
213
|
| `--no-overwrite` is used | Command fails if file exists |
|
|
217
214
|
|
|
218
|
-
This
|
|
215
|
+
This helps avoid overwriting newer generated files and avoids rewriting unchanged output unnecessarily.
|
|
219
216
|
|
|
220
|
-
Use
|
|
221
|
-
`--overwrite` to force replacement.
|
|
217
|
+
Use `--overwrite` to force replacement.
|
|
222
218
|
|
|
223
219
|
---
|
|
224
220
|
|
|
225
|
-
##
|
|
226
|
-
|
|
221
|
+
## Links
|
|
222
|
+
- ➡️ [YINI Homepage](https://yini-lang.org)
|
|
223
|
+
*Tutorials, guides, and examples.*
|
|
227
224
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
3. Command `format`: Pretty-print or normalize a `.yini` file.
|
|
231
|
-
4. Command `lint`: Stricter stylistic checks (like `validate`, but opinionated).
|
|
232
|
-
5. Command `diff`: Compare two YINI files and show structural/config differences.
|
|
233
|
-
6. Import JSON or XML into YINI format.
|
|
225
|
+
- ➡️ [Read the YINI Specification](https://yini-lang.org/refs/specification)
|
|
226
|
+
*Full syntax and format reference.*
|
|
234
227
|
|
|
235
|
-
---
|
|
236
|
-
|
|
237
|
-
## Links
|
|
238
228
|
- ➡️ [YINI Parser on npm](https://www.npmjs.com/package/yini-parser)
|
|
239
|
-
*
|
|
229
|
+
*The TypeScript/Node.js parser used by this CLI.*
|
|
240
230
|
|
|
241
|
-
- ➡️ [
|
|
242
|
-
*
|
|
231
|
+
- ➡️ [Demo Apps](https://github.com/YINI-lang/yini-demo-apps/tree/main)
|
|
232
|
+
*Complete basic usage examples.*
|
|
233
|
+
|
|
234
|
+
- ➡️ [YINI-lang Project](https://github.com/YINI-lang)
|
|
235
|
+
*Repositories and related ecosystem projects.*
|
|
243
236
|
|
|
244
237
|
---
|
|
245
238
|
|
|
246
|
-
##
|
|
247
|
-
Contributions,
|
|
239
|
+
## Contributing
|
|
240
|
+
Contributions, bug reports, and feedback are welcome. Even small improvements or suggestions are appreciated.
|
|
241
|
+
|
|
242
|
+
If this tool is useful to you, a GitHub star helps more people discover the project and supports future development.
|
|
248
243
|
|
|
249
244
|
---
|
|
250
245
|
|
|
251
246
|
## License
|
|
252
|
-
This project is licensed under the Apache-2.0 license
|
|
247
|
+
This project is licensed under the Apache-2.0 license — see the [LICENSE](./LICENSE) file for details.
|
|
253
248
|
|
|
254
249
|
In this project on GitHub, the `libs` directory contains third party software and each is licensed under its own license which is described in its own license file under the respective directory under `libs`.
|
|
255
250
|
|
|
@@ -258,4 +253,4 @@ In this project on GitHub, the `libs` directory contains third party software an
|
|
|
258
253
|
**^YINI ≡**
|
|
259
254
|
> Readable like INI. Structured like JSON. No indentation surprises.
|
|
260
255
|
|
|
261
|
-
[yini-lang.org](https://yini-lang.org/?utm_source=github&utm_medium=referral&utm_campaign=yini_cli&utm_content=readme_footer) · [YINI on GitHub](https://github.com/YINI-lang)
|
|
256
|
+
[yini-lang.org](https://yini-lang.org/?utm_source=github&utm_medium=referral&utm_campaign=yini_cli&utm_content=readme_footer) · [YINI-lang on GitHub](https://github.com/YINI-lang)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { IGlobalOptions } from '../types.js';
|
|
2
|
+
export type TValidateRunMode = 'file' | 'directory';
|
|
3
|
+
export declare const printStdout: (options: IGlobalOptions, text: string) => void;
|
|
4
|
+
export declare const printStderr: (options: IGlobalOptions, text: string) => void;
|
|
5
|
+
export declare const printWarning: (options: IGlobalOptions, text: string) => void;
|
|
6
|
+
export declare const resolveStrictMode: (options: IGlobalOptions) => boolean;
|
|
7
|
+
export declare const resolveRunModeFromTargets: (targets: string[]) => TValidateRunMode;
|
|
8
|
+
export declare const getDisplayBaseDir: (targets: string[]) => string;
|
|
9
|
+
export declare const toDisplayPath: (filePath: string, baseDir: string) => string;
|
|
10
|
+
/**
|
|
11
|
+
*
|
|
12
|
+
* @note Windows safe, since Windows paths are case-insensitive,
|
|
13
|
+
* so on Windows cases are normalized too.
|
|
14
|
+
*/
|
|
15
|
+
export declare const assertInputAndOutputAreDifferent: (srcPath: string, destPath: string) => void;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// src/commands/commonFunctions.ts
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
export const printStdout = (options, text) => {
|
|
5
|
+
if (options.silent)
|
|
6
|
+
return;
|
|
7
|
+
console.log(text);
|
|
8
|
+
};
|
|
9
|
+
export const printStderr = (options, text) => {
|
|
10
|
+
if (options.silent)
|
|
11
|
+
return;
|
|
12
|
+
console.error(text);
|
|
13
|
+
};
|
|
14
|
+
export const printWarning = (options, text) => {
|
|
15
|
+
if (options.silent)
|
|
16
|
+
return;
|
|
17
|
+
if (options.quiet)
|
|
18
|
+
return;
|
|
19
|
+
console.warn(text);
|
|
20
|
+
};
|
|
21
|
+
export const resolveStrictMode = (options) => {
|
|
22
|
+
if (options.strict && options.lenient) {
|
|
23
|
+
throw new Error('--strict and --lenient cannot be used together.');
|
|
24
|
+
}
|
|
25
|
+
if (options.strict)
|
|
26
|
+
return true;
|
|
27
|
+
if (options.lenient)
|
|
28
|
+
return false;
|
|
29
|
+
return false; // Default = lenient
|
|
30
|
+
};
|
|
31
|
+
export const resolveRunModeFromTargets = (targets) => {
|
|
32
|
+
for (const target of targets) {
|
|
33
|
+
const resolved = path.resolve(target);
|
|
34
|
+
if (!fs.existsSync(resolved)) {
|
|
35
|
+
throw new Error(`Path does not exist: "${target}"`);
|
|
36
|
+
}
|
|
37
|
+
if (fs.statSync(resolved).isDirectory()) {
|
|
38
|
+
return 'directory';
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return 'file';
|
|
42
|
+
};
|
|
43
|
+
export const getDisplayBaseDir = (targets) => {
|
|
44
|
+
if (!targets.length) {
|
|
45
|
+
return process.cwd();
|
|
46
|
+
}
|
|
47
|
+
if (targets.length === 1) {
|
|
48
|
+
const resolved = path.resolve(targets[0]);
|
|
49
|
+
if (fs.existsSync(resolved) && fs.statSync(resolved).isDirectory()) {
|
|
50
|
+
return resolved;
|
|
51
|
+
}
|
|
52
|
+
return path.dirname(resolved);
|
|
53
|
+
}
|
|
54
|
+
return process.cwd();
|
|
55
|
+
};
|
|
56
|
+
export const toDisplayPath = (filePath, baseDir) => {
|
|
57
|
+
const relative = path.relative(baseDir, filePath);
|
|
58
|
+
if (!relative || relative.startsWith('..')) {
|
|
59
|
+
return filePath;
|
|
60
|
+
}
|
|
61
|
+
return relative;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
*
|
|
65
|
+
* @note Windows safe, since Windows paths are case-insensitive,
|
|
66
|
+
* so on Windows cases are normalized too.
|
|
67
|
+
*/
|
|
68
|
+
export const assertInputAndOutputAreDifferent = (srcPath, destPath) => {
|
|
69
|
+
const resolvedSrc = path.resolve(srcPath);
|
|
70
|
+
const resolvedDest = path.resolve(destPath);
|
|
71
|
+
const normalizedSrc = process.platform === 'win32' ? resolvedSrc.toLowerCase() : resolvedSrc;
|
|
72
|
+
const normalizedDest = process.platform === 'win32' ? resolvedDest.toLowerCase() : resolvedDest;
|
|
73
|
+
if (normalizedSrc === normalizedDest) {
|
|
74
|
+
throw new Error('Output file must be different from the input file.');
|
|
75
|
+
}
|
|
76
|
+
};
|
|
@@ -21,5 +21,5 @@ export interface IParseCommandOptions extends IGlobalOptions {
|
|
|
21
21
|
* @returns
|
|
22
22
|
*/
|
|
23
23
|
export declare const shouldSkipBecauseDestNewer: (file: string, optionOverwrite?: boolean | undefined, outputFile?: string) => boolean;
|
|
24
|
-
export declare const isAllowWriteOutput: (srcPath: string, destPath: string, newContent: string, overwrite?: boolean) => boolean;
|
|
24
|
+
export declare const isAllowWriteOutput: (commandOptions: IParseCommandOptions, srcPath: string, destPath: string, newContent: string, overwrite?: boolean) => boolean;
|
|
25
25
|
export declare const parseFile: (file: string, commandOptions: IParseCommandOptions) => void;
|
|
@@ -4,6 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
import YINI from 'yini-parser';
|
|
5
5
|
import { getSerializer } from '../serializers/index.js';
|
|
6
6
|
import { debugPrint, printObject } from '../utils/print.js';
|
|
7
|
+
import { assertInputAndOutputAreDifferent, printStderr, printStdout, printWarning, resolveStrictMode, } from './commonFunctions.js';
|
|
7
8
|
// -------------------------------------------------------------------------
|
|
8
9
|
/**
|
|
9
10
|
* Will return true if:
|
|
@@ -12,7 +13,9 @@ import { debugPrint, printObject } from '../utils/print.js';
|
|
|
12
13
|
* * destination is newer than source
|
|
13
14
|
* @returns
|
|
14
15
|
*/
|
|
15
|
-
export const shouldSkipBecauseDestNewer = (
|
|
16
|
+
export const shouldSkipBecauseDestNewer = (
|
|
17
|
+
// commandOptions: IParseCommandOptions,
|
|
18
|
+
file, optionOverwrite,
|
|
16
19
|
// optionVerbose?: boolean | undefined,
|
|
17
20
|
outputFile = '') => {
|
|
18
21
|
if (outputFile && optionOverwrite === undefined) {
|
|
@@ -27,7 +30,7 @@ outputFile = '') => {
|
|
|
27
30
|
}
|
|
28
31
|
return false;
|
|
29
32
|
};
|
|
30
|
-
export const isAllowWriteOutput = (srcPath, destPath, newContent, overwrite) => {
|
|
33
|
+
export const isAllowWriteOutput = (commandOptions, srcPath, destPath, newContent, overwrite) => {
|
|
31
34
|
if (!fs.existsSync(destPath)) {
|
|
32
35
|
return true;
|
|
33
36
|
}
|
|
@@ -40,29 +43,31 @@ export const isAllowWriteOutput = (srcPath, destPath, newContent, overwrite) =>
|
|
|
40
43
|
const srcStat = fs.statSync(srcPath);
|
|
41
44
|
const destStat = fs.statSync(destPath);
|
|
42
45
|
if (destStat.mtimeMs > srcStat.mtimeMs) {
|
|
43
|
-
reportAction('skip', destPath, `newer than source "${srcPath}"`);
|
|
46
|
+
reportAction(commandOptions, 'skip', destPath, `newer than source "${srcPath}"`);
|
|
44
47
|
return false;
|
|
45
48
|
}
|
|
46
49
|
const existing = fs.readFileSync(destPath, 'utf-8');
|
|
47
50
|
if (existing === newContent) {
|
|
48
|
-
reportAction('skip', destPath, 'output unchanged');
|
|
51
|
+
reportAction(commandOptions, 'skip', destPath, 'output unchanged');
|
|
49
52
|
return false;
|
|
50
53
|
}
|
|
51
54
|
return true;
|
|
52
55
|
};
|
|
53
|
-
const reportAction = (action, file, reason) => {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
txt = `${action.padEnd(6)} "${file}"`;
|
|
60
|
-
}
|
|
56
|
+
const reportAction = (options, action, file, reason) => {
|
|
57
|
+
if (options.silent)
|
|
58
|
+
return;
|
|
59
|
+
let txt = reason
|
|
60
|
+
? `${action.padEnd(6)} "${file}" (${reason})`
|
|
61
|
+
: `${action.padEnd(6)} "${file}"`;
|
|
61
62
|
if (action === 'skip') {
|
|
62
|
-
|
|
63
|
+
if (!options.quiet) {
|
|
64
|
+
console.warn(txt);
|
|
65
|
+
}
|
|
63
66
|
}
|
|
64
67
|
else {
|
|
65
|
-
|
|
68
|
+
if (options.verbose && !options.quiet) {
|
|
69
|
+
console.log(txt);
|
|
70
|
+
}
|
|
66
71
|
}
|
|
67
72
|
};
|
|
68
73
|
/*
|
|
@@ -89,7 +94,7 @@ const reportAction = (action, file, reason) => {
|
|
|
89
94
|
|
|
90
95
|
Execution control:
|
|
91
96
|
--fail-fast
|
|
92
|
-
--best-effort = ignore-errors within a file, attempt recovery and still emit
|
|
97
|
+
--best-effort = ignore-errors within a file, attempt recovery, and still emit output
|
|
93
98
|
--No for parse, --keep-going = continue to the next file when one fails
|
|
94
99
|
--max-errors <n>
|
|
95
100
|
--verbose
|
|
@@ -111,21 +116,20 @@ export const parseFile = (file, commandOptions) => {
|
|
|
111
116
|
doParseFile(file, commandOptions, outputFormat, outputFile);
|
|
112
117
|
};
|
|
113
118
|
const resolveOutputFormat = (options) => {
|
|
114
|
-
|
|
115
|
-
|
|
119
|
+
const selected = [
|
|
120
|
+
options.json ? 'json' : null,
|
|
121
|
+
options.compact ? 'json-compact' : null,
|
|
122
|
+
options.js ? 'js' : null,
|
|
123
|
+
options.yaml ? 'yaml' : null,
|
|
124
|
+
options.xml ? 'xml' : null,
|
|
125
|
+
].filter(Boolean);
|
|
126
|
+
if (selected.length > 1) {
|
|
127
|
+
throw new Error('Choose only one output format: --json, --compact, --js, --yaml, or --xml.');
|
|
116
128
|
}
|
|
117
|
-
if (options.compact)
|
|
118
|
-
return 'json-compact';
|
|
119
|
-
if (options.js)
|
|
120
|
-
return 'js';
|
|
121
|
-
if (options.yaml)
|
|
122
|
-
return 'yaml';
|
|
123
|
-
if (options.xml)
|
|
124
|
-
return 'xml';
|
|
125
129
|
if (options.pretty) {
|
|
126
|
-
|
|
130
|
+
printWarning(options, 'Warning: --pretty is deprecated. Use --json instead.');
|
|
127
131
|
}
|
|
128
|
-
return 'json';
|
|
132
|
+
return selected[0] ?? 'json';
|
|
129
133
|
};
|
|
130
134
|
/**
|
|
131
135
|
* Returns true if the parsed result appears to contain usable output data.
|
|
@@ -152,52 +156,61 @@ const hasMeaningfulParsedData = (value) => {
|
|
|
152
156
|
const doParseFile = (file, commandOptions, outputFormat, outputFile = '') => {
|
|
153
157
|
debugPrint('File = ' + file);
|
|
154
158
|
debugPrint('outputFormat = ' + outputFormat);
|
|
155
|
-
const parseOptions = {
|
|
156
|
-
strictMode: commandOptions.strict ?? false,
|
|
157
|
-
failLevel: commandOptions.bestEffort ? 'ignore-errors' : 'auto',
|
|
158
|
-
includeMetadata: true,
|
|
159
|
-
includeDiagnostics: true,
|
|
160
|
-
};
|
|
161
|
-
// Check early if should skip before parsing,
|
|
162
|
-
// saves a lot of time in some cases.
|
|
163
|
-
if (shouldSkipBecauseDestNewer(file, commandOptions.overwrite,
|
|
164
|
-
// commandOptions.verbose,
|
|
165
|
-
outputFile)) {
|
|
166
|
-
if (commandOptions.verbose) {
|
|
167
|
-
const resolved = path.resolve(outputFile);
|
|
168
|
-
reportAction('skip', resolved, `newer than source "${file}"`);
|
|
169
|
-
}
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
159
|
try {
|
|
160
|
+
const strictMode = resolveStrictMode(commandOptions);
|
|
161
|
+
const parseOptions = {
|
|
162
|
+
strictMode: strictMode,
|
|
163
|
+
failLevel: commandOptions.bestEffort ? 'ignore-errors' : 'auto',
|
|
164
|
+
includeMetadata: true,
|
|
165
|
+
includeDiagnostics: true,
|
|
166
|
+
};
|
|
167
|
+
// Check that output and input file are not the same.
|
|
168
|
+
if (outputFile) {
|
|
169
|
+
assertInputAndOutputAreDifferent(file, outputFile);
|
|
170
|
+
}
|
|
171
|
+
// Check early if should skip before parsing,
|
|
172
|
+
// saves a lot of time in some cases.
|
|
173
|
+
if (shouldSkipBecauseDestNewer(
|
|
174
|
+
// commandOptions,
|
|
175
|
+
file, commandOptions.overwrite,
|
|
176
|
+
// commandOptions.verbose,
|
|
177
|
+
outputFile)) {
|
|
178
|
+
if (commandOptions.verbose) {
|
|
179
|
+
const resolved = path.resolve(outputFile);
|
|
180
|
+
reportAction(commandOptions, 'skip', resolved, `newer than source "${file}"`);
|
|
181
|
+
}
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
173
184
|
const parsedWithMeta = YINI.parseFile(file, parseOptions);
|
|
174
185
|
const errorCount = parsedWithMeta?.meta?.diagnostics?.errors?.errorCount ?? 0;
|
|
175
186
|
const parsedData = parsedWithMeta.result;
|
|
176
187
|
const hasUsableOutput = hasMeaningfulParsedData(parsedData);
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
188
|
+
const hasErrors = errorCount > 0;
|
|
189
|
+
const bestEffort = !!commandOptions.bestEffort;
|
|
190
|
+
// const strictMode = resolveStrictMode(commandOptions)
|
|
191
|
+
const shouldFailHard = hasErrors && !bestEffort && (strictMode || !hasUsableOutput);
|
|
192
|
+
if (shouldFailHard) {
|
|
180
193
|
process.exit(1);
|
|
181
194
|
}
|
|
182
195
|
const serializer = getSerializer(outputFormat);
|
|
183
196
|
const output = serializer.serialize(parsedData);
|
|
184
197
|
if (outputFile) {
|
|
185
198
|
const resolved = path.resolve(outputFile);
|
|
186
|
-
if (!isAllowWriteOutput(file, resolved, output, commandOptions.overwrite)) {
|
|
199
|
+
if (!isAllowWriteOutput(commandOptions, file, resolved, output, commandOptions.overwrite)) {
|
|
187
200
|
return;
|
|
188
201
|
}
|
|
189
202
|
fs.writeFileSync(resolved, output, 'utf-8');
|
|
190
203
|
if (commandOptions.verbose) {
|
|
191
|
-
reportAction('write', resolved);
|
|
204
|
+
reportAction(commandOptions, 'write', resolved);
|
|
192
205
|
}
|
|
193
206
|
}
|
|
194
207
|
else {
|
|
195
|
-
|
|
208
|
+
printStdout(commandOptions, output);
|
|
196
209
|
}
|
|
197
210
|
}
|
|
198
211
|
catch (err) {
|
|
199
212
|
const message = err instanceof Error ? err.message : String(err);
|
|
200
|
-
|
|
213
|
+
printStderr(commandOptions, `Error: ${message}`);
|
|
201
214
|
process.exit(1);
|
|
202
215
|
}
|
|
203
216
|
};
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { IGlobalOptions } from '../types.js';
|
|
2
2
|
export interface IValidateCommandOptions extends IGlobalOptions {
|
|
3
3
|
stats?: boolean;
|
|
4
|
+
warningsAsErrors?: boolean;
|
|
5
|
+
format?: 'json' | 'text';
|
|
6
|
+
failFast?: boolean;
|
|
7
|
+
recursive?: boolean;
|
|
8
|
+
maxErrors?: number;
|
|
4
9
|
}
|
|
5
|
-
export declare const
|
|
10
|
+
export declare const validateTargets: (targets: string[], options: IValidateCommandOptions) => never;
|