@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/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, logger, fileName = defaultFiles_1.DefaultFiles.fileNames.mainDDE) {
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
- await validateJsonSchema(libraryFile.path, libraryFile.yaml, logger);
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
- const mainDdeYaml = await readYamlFile(mainDdePath, fs);
77
- await validateJsonSchema(mainDdePath, mainDdeYaml, logger);
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
- const validated = await validate(cwd, fs, logger, fileName);
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
- * @param ddeFilePath
120
- * @param yaml
121
- * @param logger
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
- async function validateJsonSchema(ddeFilePath, yaml, logger) {
124
- const json = (0, yaml_1.parseDocument)(yaml).toJSON();
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 errors = validateDynamicDde.errors || [];
128
- const error = new Error(`${vscode_uri_1.Utils.basename(ddeFilePath)} has "${errors.length}" errors"`);
129
- errors.forEach(error => logger.error(error));
130
- throw error;
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 globalDefines = await globals_1.Globals.read(cwd, fs);
319
- const autoInc = (0, dlo_1.generateAutoIncFromMap)(ddeJSON, globalDefines, manifest);
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