@microtronics/studio-cli 0.50.0 → 0.52.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/CHANGELOG.md +31 -1
- package/README.md +15 -0
- package/dist/main.js +115 -63
- package/dist/studio-cli.d.ts +354 -62
- package/out/dde/dde.js +207 -20
- package/out/dde/dynamic-dde.schema.json +331 -105
- package/out/dde/fileGenerators/dlo.js +1 -1
- package/out/defaultFiles.js +5 -2
- package/out/globals.js +286 -24
- package/out/log.js +8 -0
- package/out/myDatanetClient.js +7 -2
- package/out/package/package.js +10 -0
- package/package.json +1 -1
package/out/dde/dde.js
CHANGED
|
@@ -43,6 +43,7 @@ const globals_1 = require("../globals");
|
|
|
43
43
|
const api_1 = require("./fileGenerators/api");
|
|
44
44
|
const helper_1 = require("../helper");
|
|
45
45
|
const registryAPI_1 = require("../registryAPI");
|
|
46
|
+
var yamlRangeToLineInformation = helper_1.Helper.yamlRangeToLineInformation;
|
|
46
47
|
exports.DDE_PATH = defaultFiles_1.DefaultFiles.filePaths.dde.path;
|
|
47
48
|
// create file cache.
|
|
48
49
|
// yaml & json
|
|
@@ -64,24 +65,59 @@ var DDE;
|
|
|
64
65
|
* @param cwd
|
|
65
66
|
* @param fs
|
|
66
67
|
* @param fileName
|
|
68
|
+
* @param globalDefines
|
|
67
69
|
*/
|
|
68
|
-
async function validate(cwd, fs,
|
|
70
|
+
async function validate(cwd, fs, fileName = defaultFiles_1.DefaultFiles.fileNames.mainDDE, globalDefines) {
|
|
71
|
+
const diagnostics = [];
|
|
69
72
|
const manifest = await manifest_1.Manifest.read(cwd, fs);
|
|
70
73
|
const allLibraryFiles = await getDependencies(cwd, fs, manifest);
|
|
74
|
+
// If global defines are provided, apply template substitution to library YAML
|
|
75
|
+
if (globalDefines) {
|
|
76
|
+
for (const libraryFile of allLibraryFiles) {
|
|
77
|
+
const { result, unresolvedKeys } = globals_1.Globals.replaceDefineTemplates(libraryFile.yaml, globalDefines);
|
|
78
|
+
libraryFile.yaml = result;
|
|
79
|
+
for (const key of unresolvedKeys) {
|
|
80
|
+
diagnostics.push({
|
|
81
|
+
file: libraryFile.path,
|
|
82
|
+
level: 'error',
|
|
83
|
+
message: `Unresolved define template: {{${key}}}`,
|
|
84
|
+
line: 0,
|
|
85
|
+
startCharacter: 0,
|
|
86
|
+
endCharacter: 0
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
71
91
|
for (const libraryFile of allLibraryFiles) {
|
|
72
|
-
|
|
92
|
+
diagnostics.push(...validateJsonSchema(libraryFile.path, libraryFile.yaml));
|
|
73
93
|
}
|
|
74
94
|
// read project dde
|
|
75
95
|
const mainDdePath = vscode_uri_1.Utils.joinPath(cwd, exports.DDE_PATH, fileName);
|
|
76
|
-
|
|
77
|
-
|
|
96
|
+
let mainDdeYaml = await readYamlFile(mainDdePath, fs);
|
|
97
|
+
// Apply template substitution to main DDE YAML
|
|
98
|
+
if (globalDefines) {
|
|
99
|
+
const { result, unresolvedKeys } = globals_1.Globals.replaceDefineTemplates(mainDdeYaml, globalDefines);
|
|
100
|
+
mainDdeYaml = result;
|
|
101
|
+
for (const key of unresolvedKeys) {
|
|
102
|
+
diagnostics.push({
|
|
103
|
+
file: mainDdePath,
|
|
104
|
+
level: 'error',
|
|
105
|
+
message: `Unresolved define template: {{${key}}}`,
|
|
106
|
+
line: 0,
|
|
107
|
+
startCharacter: 0,
|
|
108
|
+
endCharacter: 0
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
diagnostics.push(...validateJsonSchema(mainDdePath, mainDdeYaml));
|
|
78
113
|
return {
|
|
79
114
|
manifest,
|
|
80
115
|
libraryFiles: allLibraryFiles,
|
|
81
116
|
main: {
|
|
82
117
|
path: mainDdePath,
|
|
83
118
|
yaml: mainDdeYaml
|
|
84
|
-
}
|
|
119
|
+
},
|
|
120
|
+
diagnostics
|
|
85
121
|
};
|
|
86
122
|
}
|
|
87
123
|
DDE.validate = validate;
|
|
@@ -94,16 +130,23 @@ var DDE;
|
|
|
94
130
|
*/
|
|
95
131
|
async function compileDDE(cwd, fs, logger, fileName = defaultFiles_1.DefaultFiles.fileNames.mainDDE) {
|
|
96
132
|
const manifest = await manifest_1.Manifest.read(cwd, fs);
|
|
97
|
-
|
|
133
|
+
// Read global defines and pass to validate for template substitution
|
|
134
|
+
const resolvedConfig = await globals_1.Globals.read(cwd, fs, logger);
|
|
135
|
+
const validated = await validate(cwd, fs, fileName, resolvedConfig.flat);
|
|
136
|
+
// If schema validation found errors, return early with validation diagnostics
|
|
137
|
+
const validationErrors = validated.diagnostics.filter(d => d.level === 'error');
|
|
138
|
+
if (validationErrors.length > 0) {
|
|
139
|
+
return { ddeJSON: null, diagnostics: validated.diagnostics };
|
|
140
|
+
}
|
|
98
141
|
const historyJson = await getHistoryJson(cwd, fs, logger);
|
|
99
142
|
const preCompiler = new yamlDdePreCompiler_1.YamlPreCompiler.PreCompiler(manifest, validated.libraryFiles, historyJson);
|
|
100
143
|
const libraryDiagnostics = preCompiler.parseLibraries();
|
|
101
144
|
const librariesHaveError = libraryDiagnostics.find(diagnostics => diagnostics.level === 'error');
|
|
102
145
|
if (librariesHaveError) {
|
|
103
|
-
return { ddeJSON: null, diagnostics: libraryDiagnostics };
|
|
146
|
+
return { ddeJSON: null, diagnostics: [...validated.diagnostics, ...libraryDiagnostics] };
|
|
104
147
|
}
|
|
105
148
|
const { ddeJSON, diagnostics } = preCompiler.parseMainDDE(validated.main.path, validated.main.yaml);
|
|
106
|
-
return { ddeJSON, diagnostics };
|
|
149
|
+
return { ddeJSON, diagnostics: [...validated.diagnostics, ...diagnostics] };
|
|
107
150
|
}
|
|
108
151
|
DDE.compileDDE = compileDDE;
|
|
109
152
|
const ajv = new ajv_1.default({
|
|
@@ -115,20 +158,56 @@ var DDE;
|
|
|
115
158
|
(0, ajv_formats_1.default)(ajv);
|
|
116
159
|
const validateDynamicDde = ajv.compile(DDE.DDE_SCHEMA);
|
|
117
160
|
/**
|
|
118
|
-
* Validate a given dde file against the dynamic dde schema
|
|
119
|
-
*
|
|
120
|
-
* @param
|
|
121
|
-
* @param
|
|
161
|
+
* Validate a given dde file against the dynamic dde schema.
|
|
162
|
+
* Returns structured diagnostics with YAML source positions instead of throwing.
|
|
163
|
+
* @param ddeFilePath - URI of the YAML file being validated
|
|
164
|
+
* @param yaml - Raw YAML content to validate
|
|
165
|
+
* @returns Array of diagnostics for any YAML syntax or schema validation errors
|
|
122
166
|
*/
|
|
123
|
-
|
|
124
|
-
const
|
|
167
|
+
function validateJsonSchema(ddeFilePath, yaml) {
|
|
168
|
+
const lineCounter = new yaml_1.LineCounter();
|
|
169
|
+
const yamlDoc = (0, yaml_1.parseDocument)(yaml, { lineCounter, keepSourceTokens: true });
|
|
170
|
+
// Collect YAML syntax errors as diagnostics
|
|
171
|
+
const diagnostics = [];
|
|
172
|
+
for (const yamlError of yamlDoc.errors) {
|
|
173
|
+
const lineInfo = yamlError.pos
|
|
174
|
+
? yamlRangeToLineInformation(lineCounter, [yamlError.pos[0], yamlError.pos[1], yamlError.pos[1]])
|
|
175
|
+
: { line: 0, startCharacter: 0, endCharacter: 0 };
|
|
176
|
+
diagnostics.push({
|
|
177
|
+
file: ddeFilePath,
|
|
178
|
+
level: 'error',
|
|
179
|
+
message: yamlError.message,
|
|
180
|
+
...lineInfo
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
// If the YAML has syntax errors, skip schema validation
|
|
184
|
+
if (diagnostics.length > 0) {
|
|
185
|
+
return diagnostics;
|
|
186
|
+
}
|
|
187
|
+
const json = yamlDoc.toJSON();
|
|
125
188
|
const result = validateDynamicDde(json);
|
|
126
189
|
if (!result) {
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
errors.
|
|
130
|
-
|
|
190
|
+
const allErrors = validateDynamicDde.errors || [];
|
|
191
|
+
// Filter out redundant 'if' keyword errors (the 'then'/'else' errors are more informative)
|
|
192
|
+
const errors = allErrors.filter(e => e.keyword !== 'if');
|
|
193
|
+
// Map each unique error message to a diagnostic with YAML source position
|
|
194
|
+
const seenMessages = new Set();
|
|
195
|
+
for (const error of errors) {
|
|
196
|
+
const msg = formatValidationError(error);
|
|
197
|
+
if (seenMessages.has(msg)) {
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
seenMessages.add(msg);
|
|
201
|
+
const lineInfo = resolveYamlLineInfo(yamlDoc, lineCounter, error);
|
|
202
|
+
diagnostics.push({
|
|
203
|
+
file: ddeFilePath,
|
|
204
|
+
level: 'error',
|
|
205
|
+
message: msg,
|
|
206
|
+
...lineInfo
|
|
207
|
+
});
|
|
208
|
+
}
|
|
131
209
|
}
|
|
210
|
+
return diagnostics;
|
|
132
211
|
}
|
|
133
212
|
DDE.validateJsonSchema = validateJsonSchema;
|
|
134
213
|
/**
|
|
@@ -309,14 +388,15 @@ var DDE;
|
|
|
309
388
|
* Generate all dde related dlo files
|
|
310
389
|
* @param cwd
|
|
311
390
|
* @param fs
|
|
391
|
+
* @param logger
|
|
312
392
|
* @param ddeJSON
|
|
313
393
|
*/
|
|
314
394
|
async function exportAutoDloFiles(cwd, fs, logger, ddeJSON) {
|
|
315
395
|
await clearAutoDloDir(cwd, fs);
|
|
316
396
|
const manifest = await manifest_1.Manifest.read(cwd, fs);
|
|
317
397
|
const dependencies = await getDependencies(cwd, fs, manifest);
|
|
318
|
-
const
|
|
319
|
-
const autoInc = (0, dlo_1.generateAutoIncFromMap)(ddeJSON,
|
|
398
|
+
const resolvedConfig = await globals_1.Globals.read(cwd, fs, logger);
|
|
399
|
+
const autoInc = (0, dlo_1.generateAutoIncFromMap)(ddeJSON, resolvedConfig.flat, manifest);
|
|
320
400
|
const appAutoDDE = new dlo_1.AutoDDEGenerator(manifest, ddeJSON, null);
|
|
321
401
|
const appDDEInc = appAutoDDE.generate();
|
|
322
402
|
// generate AutoDDe Files for the libraries:
|
|
@@ -423,4 +503,111 @@ async function getDependencies(cwd, fs, manifest) {
|
|
|
423
503
|
}
|
|
424
504
|
return ddeFiles;
|
|
425
505
|
}
|
|
506
|
+
/**
|
|
507
|
+
* Extract a custom error message from the schema's errorMessage metadata.
|
|
508
|
+
* Supports both string and object formats (e.g. { required: { field: "msg" } }).
|
|
509
|
+
* @param schemaErrorMessage - The errorMessage property from the parent schema
|
|
510
|
+
* @param keyword - The ajv error keyword
|
|
511
|
+
* @param params - The ajv error params
|
|
512
|
+
*/
|
|
513
|
+
function extractCustomErrorMessage(schemaErrorMessage, keyword, params) {
|
|
514
|
+
if (typeof schemaErrorMessage === 'string') {
|
|
515
|
+
return schemaErrorMessage;
|
|
516
|
+
}
|
|
517
|
+
if (typeof schemaErrorMessage !== 'object' || schemaErrorMessage === null) {
|
|
518
|
+
return '';
|
|
519
|
+
}
|
|
520
|
+
const keywordMessages = schemaErrorMessage[keyword];
|
|
521
|
+
if (typeof keywordMessages === 'string') {
|
|
522
|
+
return keywordMessages;
|
|
523
|
+
}
|
|
524
|
+
if (typeof keywordMessages !== 'object' || keywordMessages === null) {
|
|
525
|
+
return '';
|
|
526
|
+
}
|
|
527
|
+
const paramKey = params?.missingProperty;
|
|
528
|
+
return paramKey ? (keywordMessages[paramKey] ?? '') : '';
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* Build a human-readable message for a specific ajv error keyword.
|
|
532
|
+
* @param keyword - The ajv error keyword
|
|
533
|
+
* @param params - The ajv error params
|
|
534
|
+
* @param message - The default ajv error message
|
|
535
|
+
* @param parentSchema - The parent schema object (available in verbose mode)
|
|
536
|
+
*/
|
|
537
|
+
function buildKeywordMessage(keyword, params, message, parentSchema) {
|
|
538
|
+
const keywordMessageMap = {
|
|
539
|
+
required: () => `Missing required property '${params.missingProperty}'.`,
|
|
540
|
+
additionalProperties: () => `Unknown property '${params.additionalProperty}' is not allowed.`,
|
|
541
|
+
type: () => `Must be of type '${params.type}'.`,
|
|
542
|
+
enum: () => `Must be one of the allowed values: ${JSON.stringify(params.allowedValues)}.`,
|
|
543
|
+
pattern: () => `Value does not match the required pattern. ${parentSchema?.patternErrorMessage ?? message}`,
|
|
544
|
+
minimum: () => `${message}.`,
|
|
545
|
+
maximum: () => `${message}.`,
|
|
546
|
+
exclusiveMinimum: () => `${message}.`,
|
|
547
|
+
exclusiveMaximum: () => `${message}.`,
|
|
548
|
+
not: () => `This value is not allowed here. ${parentSchema?.description ?? ''}`.trimEnd(),
|
|
549
|
+
anyOf: () => `Value does not match any of the allowed schemas. ${parentSchema?.description ?? ''}`.trimEnd(),
|
|
550
|
+
oneOf: () => `Value does not match any of the allowed schemas. ${parentSchema?.description ?? ''}`.trimEnd()
|
|
551
|
+
};
|
|
552
|
+
const formatter = keywordMessageMap[keyword];
|
|
553
|
+
return formatter ? formatter() : message;
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* Format a single ajv validation error into a human-readable string.
|
|
557
|
+
* Uses the schema's errorMessage/description metadata when available (verbose mode)
|
|
558
|
+
* to provide more meaningful messages than the default ajv output.
|
|
559
|
+
* @param error - The ajv error object (verbose mode includes parentSchema)
|
|
560
|
+
*/
|
|
561
|
+
function formatValidationError(error) {
|
|
562
|
+
const path = error.instancePath || '/';
|
|
563
|
+
const parentSchema = error.parentSchema;
|
|
564
|
+
const customMessage = extractCustomErrorMessage(parentSchema?.errorMessage, error.keyword, error.params);
|
|
565
|
+
if (customMessage) {
|
|
566
|
+
return `${path}: ${customMessage}`;
|
|
567
|
+
}
|
|
568
|
+
const message = error.message || 'unknown validation error';
|
|
569
|
+
return `${path}: ${buildKeywordMessage(error.keyword, error.params, message, parentSchema)}`;
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* Resolve the YAML AST node range for a given AJV error's instancePath.
|
|
573
|
+
* Walks the parsed YAML document tree using the JSON Pointer segments from the error.
|
|
574
|
+
* Falls back to parent nodes when the exact path cannot be resolved.
|
|
575
|
+
* @param yamlDoc - The parsed YAML document with source positions
|
|
576
|
+
* @param instancePath - AJV instancePath (JSON Pointer format, e.g. "/measurement/temperature/type")
|
|
577
|
+
* @returns The YAML source range [offset, offsetEnd, srcTokenEnd] or null if unresolvable
|
|
578
|
+
*/
|
|
579
|
+
function resolveYamlNodeRange(yamlDoc, instancePath) {
|
|
580
|
+
const segments = instancePath
|
|
581
|
+
.split('/')
|
|
582
|
+
.filter(Boolean)
|
|
583
|
+
.map(s => {
|
|
584
|
+
const num = Number(s);
|
|
585
|
+
return Number.isNaN(num) ? s : num;
|
|
586
|
+
});
|
|
587
|
+
// Try to find the exact node, falling back to parent nodes
|
|
588
|
+
for (let depth = segments.length; depth >= 0; depth--) {
|
|
589
|
+
const path = segments.slice(0, depth);
|
|
590
|
+
const node = yamlDoc.getIn(path, true);
|
|
591
|
+
if (node?.range) {
|
|
592
|
+
return node.range;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
return null;
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Resolve YAML source line information for an AJV validation error.
|
|
599
|
+
* Uses the error's instancePath to locate the corresponding YAML AST node
|
|
600
|
+
* and converts its range to line/character positions.
|
|
601
|
+
* @param yamlDoc - The parsed YAML document
|
|
602
|
+
* @param lineCounter - The YAML LineCounter used during parsing
|
|
603
|
+
* @param error - The AJV error object
|
|
604
|
+
* @returns Object with line, startCharacter, and endCharacter
|
|
605
|
+
*/
|
|
606
|
+
function resolveYamlLineInfo(yamlDoc, lineCounter, error) {
|
|
607
|
+
const nodeRange = resolveYamlNodeRange(yamlDoc, error.instancePath || '');
|
|
608
|
+
if (nodeRange) {
|
|
609
|
+
return yamlRangeToLineInformation(lineCounter, nodeRange);
|
|
610
|
+
}
|
|
611
|
+
return { line: 0, startCharacter: 0, endCharacter: 0 };
|
|
612
|
+
}
|
|
426
613
|
//# sourceMappingURL=dde.js.map
|