yini-cli 1.3.1-beta → 1.3.2-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 +1 -1
- package/dist/commands/parseCommand.d.ts +9 -0
- package/dist/commands/parseCommand.js +91 -74
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -256,6 +256,6 @@ In this project on GitHub, the `libs` directory contains third party software an
|
|
|
256
256
|
---
|
|
257
257
|
|
|
258
258
|
**^YINI ≡**
|
|
259
|
-
>
|
|
259
|
+
> Readable like INI. Structured like JSON. No indentation surprises.
|
|
260
260
|
|
|
261
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)
|
|
@@ -13,4 +13,13 @@ export interface IParseCommandOptions extends IGlobalOptions {
|
|
|
13
13
|
bestEffort?: boolean;
|
|
14
14
|
overwrite?: boolean;
|
|
15
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Will return true if:
|
|
18
|
+
* * --overwrite was not explicitly given
|
|
19
|
+
* * destination exists
|
|
20
|
+
* * destination is newer than source
|
|
21
|
+
* @returns
|
|
22
|
+
*/
|
|
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;
|
|
16
25
|
export declare const parseFile: (file: string, commandOptions: IParseCommandOptions) => void;
|
|
@@ -5,6 +5,51 @@ import YINI from 'yini-parser';
|
|
|
5
5
|
import { getSerializer } from '../serializers/index.js';
|
|
6
6
|
import { debugPrint, printObject } from '../utils/print.js';
|
|
7
7
|
// -------------------------------------------------------------------------
|
|
8
|
+
/**
|
|
9
|
+
* Will return true if:
|
|
10
|
+
* * --overwrite was not explicitly given
|
|
11
|
+
* * destination exists
|
|
12
|
+
* * destination is newer than source
|
|
13
|
+
* @returns
|
|
14
|
+
*/
|
|
15
|
+
export const shouldSkipBecauseDestNewer = (file, optionOverwrite,
|
|
16
|
+
// optionVerbose?: boolean | undefined,
|
|
17
|
+
outputFile = '') => {
|
|
18
|
+
if (outputFile && optionOverwrite === undefined) {
|
|
19
|
+
const resolved = path.resolve(outputFile);
|
|
20
|
+
if (fs.existsSync(resolved) && fs.statSync(resolved).isFile()) {
|
|
21
|
+
const srcStat = fs.statSync(file);
|
|
22
|
+
const destStat = fs.statSync(resolved);
|
|
23
|
+
if (destStat.mtimeMs > srcStat.mtimeMs) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return false;
|
|
29
|
+
};
|
|
30
|
+
export const isAllowWriteOutput = (srcPath, destPath, newContent, overwrite) => {
|
|
31
|
+
if (!fs.existsSync(destPath)) {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
if (overwrite === true) {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
if (overwrite === false) {
|
|
38
|
+
throw new Error(`File "${destPath}" already exists. Overwriting disabled (--no-overwrite).`);
|
|
39
|
+
}
|
|
40
|
+
const srcStat = fs.statSync(srcPath);
|
|
41
|
+
const destStat = fs.statSync(destPath);
|
|
42
|
+
if (destStat.mtimeMs > srcStat.mtimeMs) {
|
|
43
|
+
reportAction('skip', destPath, `newer than source "${srcPath}"`);
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
const existing = fs.readFileSync(destPath, 'utf-8');
|
|
47
|
+
if (existing === newContent) {
|
|
48
|
+
reportAction('skip', destPath, 'output unchanged');
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
return true;
|
|
52
|
+
};
|
|
8
53
|
const reportAction = (action, file, reason) => {
|
|
9
54
|
let txt = '';
|
|
10
55
|
if (reason) {
|
|
@@ -82,69 +127,67 @@ const resolveOutputFormat = (options) => {
|
|
|
82
127
|
}
|
|
83
128
|
return 'json';
|
|
84
129
|
};
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
*/
|
|
130
|
+
/**
|
|
131
|
+
* Returns true if the parsed result appears to contain usable output data.
|
|
132
|
+
*
|
|
133
|
+
* This is used by the CLI to distinguish between:
|
|
134
|
+
* - parse runs that reported errors but still recovered meaningful data, and
|
|
135
|
+
* - parse runs that failed so badly that no useful parsed structure was produced.
|
|
136
|
+
*
|
|
137
|
+
* A value is considered meaningful only if it is:
|
|
138
|
+
* - not null or undefined,
|
|
139
|
+
* - an object,
|
|
140
|
+
* - and contains at least one own top-level property.
|
|
141
|
+
*
|
|
142
|
+
* @param value The parsed result data to inspect.
|
|
143
|
+
* @returns True if the parsed result looks usable for output; otherwise false.
|
|
144
|
+
*/
|
|
145
|
+
const hasMeaningfulParsedData = (value) => {
|
|
146
|
+
if (value == null)
|
|
147
|
+
return false;
|
|
148
|
+
if (typeof value !== 'object')
|
|
149
|
+
return false;
|
|
150
|
+
return Object.keys(value).length > 0;
|
|
151
|
+
};
|
|
100
152
|
const doParseFile = (file, commandOptions, outputFormat, outputFile = '') => {
|
|
101
|
-
let preferredFailLevel = commandOptions.bestEffort
|
|
102
|
-
? 'ignore-errors'
|
|
103
|
-
: 'auto';
|
|
104
|
-
let includeMetaData = false;
|
|
105
153
|
debugPrint('File = ' + file);
|
|
106
154
|
debugPrint('outputFormat = ' + outputFormat);
|
|
107
155
|
const parseOptions = {
|
|
108
156
|
strictMode: commandOptions.strict ?? false,
|
|
109
157
|
failLevel: commandOptions.bestEffort ? 'ignore-errors' : 'auto',
|
|
110
|
-
includeMetadata:
|
|
158
|
+
includeMetadata: true,
|
|
159
|
+
includeDiagnostics: true,
|
|
111
160
|
};
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
|
|
115
|
-
//
|
|
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
|
+
}
|
|
116
172
|
try {
|
|
117
|
-
const
|
|
173
|
+
const parsedWithMeta = YINI.parseFile(file, parseOptions);
|
|
174
|
+
const errorCount = parsedWithMeta?.meta?.diagnostics?.errors?.errorCount ?? 0;
|
|
175
|
+
const parsedData = parsedWithMeta.result;
|
|
176
|
+
const hasUsableOutput = hasMeaningfulParsedData(parsedData);
|
|
177
|
+
if (errorCount > 0 &&
|
|
178
|
+
!commandOptions.bestEffort &&
|
|
179
|
+
(commandOptions.strict || !hasUsableOutput)) {
|
|
180
|
+
process.exit(1);
|
|
181
|
+
}
|
|
118
182
|
const serializer = getSerializer(outputFormat);
|
|
119
|
-
const output = serializer.serialize(
|
|
183
|
+
const output = serializer.serialize(parsedData);
|
|
120
184
|
if (outputFile) {
|
|
121
185
|
const resolved = path.resolve(outputFile);
|
|
122
|
-
|
|
123
|
-
if (!canWrite) {
|
|
124
|
-
if (commandOptions.verbose) {
|
|
125
|
-
console.log(`skip Skipping write to "${resolved}"`);
|
|
126
|
-
}
|
|
186
|
+
if (!isAllowWriteOutput(file, resolved, output, commandOptions.overwrite)) {
|
|
127
187
|
return;
|
|
128
188
|
}
|
|
129
|
-
// Double check, if the file was actually changed by comparing the contents.
|
|
130
|
-
if (fs.existsSync(resolved) && fs.statSync(resolved).isFile()) {
|
|
131
|
-
const existing = fs.readFileSync(resolved, 'utf-8');
|
|
132
|
-
// Only write the output file if the content actually changed.
|
|
133
|
-
// Prevents constantly showing meaningless rewrites, in some cases.
|
|
134
|
-
if (existing === output) {
|
|
135
|
-
if (commandOptions.verbose) {
|
|
136
|
-
// console.log(
|
|
137
|
-
// `skip Output unchanged. Skipping write: "${resolved}"`,
|
|
138
|
-
// )
|
|
139
|
-
reportAction('skip', resolved, 'output unchanged');
|
|
140
|
-
}
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
// Write JSON output to file instead of stdout.
|
|
145
189
|
fs.writeFileSync(resolved, output, 'utf-8');
|
|
146
190
|
if (commandOptions.verbose) {
|
|
147
|
-
// console.log(`write Output written to file: "${outputFile}"`)
|
|
148
191
|
reportAction('write', resolved);
|
|
149
192
|
}
|
|
150
193
|
}
|
|
@@ -158,29 +201,3 @@ const doParseFile = (file, commandOptions, outputFormat, outputFile = '') => {
|
|
|
158
201
|
process.exit(1);
|
|
159
202
|
}
|
|
160
203
|
};
|
|
161
|
-
const enforceWritePolicy = (srcPath, destPath, overwrite) => {
|
|
162
|
-
if (!fs.existsSync(destPath)) {
|
|
163
|
-
return true; // File does not exist, OK to write.
|
|
164
|
-
}
|
|
165
|
-
const srcStat = fs.statSync(srcPath);
|
|
166
|
-
const destStat = fs.statSync(destPath);
|
|
167
|
-
// Only strictly newer triggers skip overwrite.
|
|
168
|
-
const destIsNewer = destStat.mtimeMs > srcStat.mtimeMs;
|
|
169
|
-
if (overwrite === true) {
|
|
170
|
-
return true; // Explicit overwrite, OK.
|
|
171
|
-
}
|
|
172
|
-
if (overwrite === false) {
|
|
173
|
-
throw new Error(`File "${destPath}" already exists. Overwriting disabled (--no-overwrite).`);
|
|
174
|
-
}
|
|
175
|
-
// Default policy (overwrite undefined).
|
|
176
|
-
if (destIsNewer) {
|
|
177
|
-
// console.warn(
|
|
178
|
-
// // `Destination file "${destPath}" is newer than source. Use --overwrite to force.`,
|
|
179
|
-
// //`Warning: destination file "${destPath}" is newer than source. Skipping write. Use --overwrite to force.`,
|
|
180
|
-
// `Warning: destination "${destPath}" is newer than source "${srcPath}". Skipping write. Use --overwrite to force.`,
|
|
181
|
-
// )
|
|
182
|
-
reportAction('skip', destPath, `newer than source "${srcPath}"`);
|
|
183
|
-
return false;
|
|
184
|
-
}
|
|
185
|
-
return true;
|
|
186
|
-
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yini-cli",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.2-beta",
|
|
4
4
|
"description": "CLI tool for validating and converting YINI configuration files - an INI-like format with real structure, nested sections and strict or lenient modes.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"yini",
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"commander": "^14.0.1",
|
|
75
75
|
"xmlbuilder2": "^4.0.3",
|
|
76
76
|
"yaml": "^2.8.2",
|
|
77
|
-
"yini-parser": "^1.4.
|
|
77
|
+
"yini-parser": "^1.4.1-beta"
|
|
78
78
|
},
|
|
79
79
|
"devDependencies": {
|
|
80
80
|
"@eslint/js": "^9.31.0",
|