@cyberismo/data-handler 0.0.7 → 0.0.8

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.
Files changed (52) hide show
  1. package/dist/commands/create.d.ts +1 -1
  2. package/dist/commands/create.js +15 -10
  3. package/dist/commands/create.js.map +1 -1
  4. package/dist/commands/import.js +8 -2
  5. package/dist/commands/import.js.map +1 -1
  6. package/dist/commands/remove.d.ts +1 -1
  7. package/dist/commands/remove.js +4 -10
  8. package/dist/commands/remove.js.map +1 -1
  9. package/dist/commands/validate.d.ts +0 -8
  10. package/dist/commands/validate.js +0 -32
  11. package/dist/commands/validate.js.map +1 -1
  12. package/dist/containers/project/resource-collector.d.ts +2 -1
  13. package/dist/containers/project/resource-collector.js +33 -23
  14. package/dist/containers/project/resource-collector.js.map +1 -1
  15. package/dist/containers/project.d.ts +0 -5
  16. package/dist/containers/project.js +9 -17
  17. package/dist/containers/project.js.map +1 -1
  18. package/dist/exceptions/index.d.ts +20 -0
  19. package/dist/exceptions/index.js +16 -0
  20. package/dist/exceptions/index.js.map +1 -1
  21. package/dist/interfaces/macros.d.ts +3 -0
  22. package/dist/macros/base-macro.d.ts +2 -0
  23. package/dist/macros/base-macro.js +66 -19
  24. package/dist/macros/base-macro.js.map +1 -1
  25. package/dist/macros/graph/index.d.ts +0 -1
  26. package/dist/macros/graph/index.js +11 -7
  27. package/dist/macros/graph/index.js.map +1 -1
  28. package/dist/macros/index.d.ts +6 -0
  29. package/dist/macros/index.js +25 -2
  30. package/dist/macros/index.js.map +1 -1
  31. package/dist/macros/report/index.js +18 -9
  32. package/dist/macros/report/index.js.map +1 -1
  33. package/dist/module-manager.d.ts +16 -4
  34. package/dist/module-manager.js +179 -55
  35. package/dist/module-manager.js.map +1 -1
  36. package/dist/project-settings.js +2 -8
  37. package/dist/project-settings.js.map +1 -1
  38. package/package.json +3 -4
  39. package/src/commands/create.ts +18 -10
  40. package/src/commands/import.ts +15 -2
  41. package/src/commands/remove.ts +7 -12
  42. package/src/commands/validate.ts +0 -35
  43. package/src/containers/project/resource-collector.ts +33 -14
  44. package/src/containers/project.ts +36 -18
  45. package/src/exceptions/index.ts +36 -0
  46. package/src/interfaces/macros.ts +3 -0
  47. package/src/macros/base-macro.ts +89 -25
  48. package/src/macros/graph/index.ts +12 -7
  49. package/src/macros/index.ts +29 -2
  50. package/src/macros/report/index.ts +19 -10
  51. package/src/module-manager.ts +228 -66
  52. package/src/project-settings.ts +2 -11
@@ -72,15 +72,20 @@ export class Create {
72
72
  },
73
73
  ];
74
74
 
75
- static gitIgnoreContent: string = `.calc\n
76
- .asciidoctor\n
77
- .vscode\n
78
- *.html\n
79
- *.pdf\n
80
- *.puml\n
81
- **/.DS_Store\n
82
- *-debug.log\n
83
- *-error.log\n`;
75
+ static gitIgnoreContent: string[] = [
76
+ '.calc',
77
+ '.asciidoctor',
78
+ '.vscode',
79
+ '*.html',
80
+ '*.pdf',
81
+ '*.puml',
82
+ '**/.DS_Store',
83
+ '*-debug.log',
84
+ '*-error.log',
85
+ '.temp',
86
+ '.logs',
87
+ '.cache',
88
+ ];
84
89
 
85
90
  /**
86
91
  * Adds new cards to a template.
@@ -510,7 +515,10 @@ export class Create {
510
515
  });
511
516
 
512
517
  try {
513
- await writeFile(join(projectPath, '.gitignore'), this.gitIgnoreContent);
518
+ await writeFile(
519
+ join(projectPath, '.gitignore'),
520
+ this.gitIgnoreContent.join('\n') + '\n',
521
+ );
514
522
  } catch {
515
523
  console.error('Failed to create project');
516
524
  }
@@ -134,7 +134,10 @@ export class Import {
134
134
  destination?: string,
135
135
  options?: ModuleSettingOptions,
136
136
  ) {
137
- const gitModule = source.startsWith('https');
137
+ const beforeImportValidateErrors = await Validate.getInstance().validate(
138
+ this.project.basePath,
139
+ );
140
+ const gitModule = source.startsWith('https') || source.startsWith('git@');
138
141
  const modulePrefix = gitModule
139
142
  ? await this.moduleManager.importGitModule(source, options)
140
143
  : await this.moduleManager.importFileModule(source, destination);
@@ -156,7 +159,17 @@ export class Import {
156
159
  await this.moduleManager.updateModule(moduleSettings, options?.credentials);
157
160
 
158
161
  // Add module as a dependency.
159
- return this.project.importModule(moduleSettings);
162
+ await this.project.importModule(moduleSettings);
163
+
164
+ // Validate the project after module has been imported
165
+ const afterImportValidateErrors = await Validate.getInstance().validate(
166
+ this.project.basePath,
167
+ );
168
+ if (afterImportValidateErrors.length > beforeImportValidateErrors.length) {
169
+ console.error(
170
+ `There are new validations errors after importing the module. Check the project`,
171
+ );
172
+ }
160
173
  }
161
174
 
162
175
  /**
@@ -17,6 +17,7 @@ import { join, sep } from 'node:path';
17
17
  import { ActionGuard } from '../permissions/action-guard.js';
18
18
  import type { Calculate } from './index.js';
19
19
  import { deleteDir, deleteFile } from '../utils/file-utils.js';
20
+ import { ModuleManager } from '../module-manager.js';
20
21
  import { Project } from '../containers/project.js';
21
22
  import type { RemovableResourceTypes } from '../interfaces/project-interfaces.js';
22
23
  import { resourceName } from '../utils/resource-utils.js';
@@ -27,10 +28,13 @@ const MODULES_PATH = `${sep}modules${sep}`;
27
28
  * Remove command.
28
29
  */
29
30
  export class Remove {
31
+ private moduleManager: ModuleManager;
30
32
  constructor(
31
33
  private project: Project,
32
34
  private calculateCmd: Calculate,
33
- ) {}
35
+ ) {
36
+ this.moduleManager = new ModuleManager(this.project);
37
+ }
34
38
 
35
39
  // True, if resource is a project resource
36
40
  private projectResource(type: RemovableResourceTypes): boolean {
@@ -182,16 +186,6 @@ export class Remove {
182
186
  await this.project.updateCardMetadataKey(sourceCardKey, 'links', newLinks);
183
187
  }
184
188
 
185
- // Removes modules from project
186
- private async removeModule(moduleName: string) {
187
- const module = await this.project.module(moduleName);
188
- if (!module) {
189
- throw new Error(`Module '${moduleName}' not found`);
190
- }
191
- await this.project.removeModule(moduleName);
192
- await deleteDir(module.path);
193
- }
194
-
195
189
  /**
196
190
  * Removes either attachment, card, imported module, link or resource from project.
197
191
  * @param type Type of resource
@@ -236,7 +230,8 @@ export class Remove {
236
230
  else if (type == 'card') return this.removeCard(targetName);
237
231
  else if (type == 'link')
238
232
  return this.removeLink(targetName, rest[0], rest[1], rest.at(2));
239
- else if (type == 'module') return this.removeModule(targetName);
233
+ else if (type == 'module')
234
+ return this.moduleManager.removeModule(targetName);
240
235
  else if (type == 'label') return this.removeLabel(targetName, rest[0]);
241
236
  }
242
237
  throw new Error(`Unknown resource type '${type}'`);
@@ -704,41 +704,6 @@ export class Validate {
704
704
  return contentValidated && lengthValidated;
705
705
  }
706
706
 
707
- /**
708
- * Validate schema that matches schemaId from path.
709
- * @param projectPath path to schema
710
- * @param schemaId schema's id
711
- * @returns string containing all validation errors
712
- * @todo - unused; remove?
713
- */
714
- public async validateSchema(
715
- projectPath: string,
716
- schemaId: string,
717
- ): Promise<string> {
718
- const validationErrors: string[] = [];
719
- if (!schemaId.startsWith('/')) {
720
- schemaId = '/' + schemaId;
721
- }
722
- const activeJsonSchema = this.validator.schemas[schemaId];
723
- if (activeJsonSchema === undefined) {
724
- throw new Error(`Unknown schema '${schemaId}'`);
725
- } else {
726
- let contentFile = '';
727
- try {
728
- contentFile = await readJsonFile(projectPath);
729
- } catch {
730
- throw new Error(`Path is not valid ${projectPath}`);
731
- }
732
-
733
- const result = this.validator.validate(contentFile, activeJsonSchema);
734
- for (const error of result.errors) {
735
- const msg = `Schema '${schemaId}' validation Error: ${error.message}\n`;
736
- validationErrors.push(msg);
737
- }
738
- }
739
- return validationErrors.join('\n');
740
- }
741
-
742
707
  /**
743
708
  * Validates that card's custom fields are according to schema and have correct data in them.
744
709
  * @param project currently used Project
@@ -55,17 +55,31 @@ class ResourceCollection {
55
55
  * @param type Resource array type to return.
56
56
  * @returns resource array of a give type.
57
57
  */
58
- public resourceArray(type: ResourceFolderType): Resource[] {
59
- if (type === 'calculations') return this.calculations;
60
- if (type === 'cardTypes') return this.cardTypes;
61
- if (type === 'fieldTypes') return this.fieldTypes;
62
- if (type === 'graphViews') return this.graphViews;
63
- if (type === 'graphModels') return this.graphModels;
64
- if (type === 'linkTypes') return this.linkTypes;
65
- if (type === 'reports') return this.reports;
66
- if (type === 'templates') return this.templates;
67
- if (type === 'workflows') return this.workflows;
68
- throw new Error(`Unknown resource type '${type}'`);
58
+ public resourceArray(
59
+ type: ResourceFolderType,
60
+ moduleName?: string,
61
+ ): Resource[] {
62
+ let resources: Resource[] = [];
63
+
64
+ if (type === 'calculations') resources = this.calculations;
65
+ else if (type === 'cardTypes') resources = this.cardTypes;
66
+ else if (type === 'fieldTypes') resources = this.fieldTypes;
67
+ else if (type === 'graphViews') resources = this.graphViews;
68
+ else if (type === 'graphModels') resources = this.graphModels;
69
+ else if (type === 'linkTypes') resources = this.linkTypes;
70
+ else if (type === 'reports') resources = this.reports;
71
+ else if (type === 'templates') resources = this.templates;
72
+ else if (type === 'workflows') resources = this.workflows;
73
+ else throw new Error(`Unknown resource type '${type}'`);
74
+
75
+ if (moduleName) {
76
+ resources = resources.filter((item) => {
77
+ const { prefix } = resourceName(item.name);
78
+ return moduleName === prefix;
79
+ });
80
+ }
81
+
82
+ return resources;
69
83
  }
70
84
  }
71
85
 
@@ -162,6 +176,7 @@ export class ResourceCollector {
162
176
  // Adds a resource type from all modules.
163
177
  private async addResourcesFromModules(
164
178
  type: ResourceFolderType,
179
+ moduleName?: string,
165
180
  ): Promise<Resource[]> {
166
181
  try {
167
182
  // 'modules' is a bit special; it is collected separately from actual resources.
@@ -174,7 +189,7 @@ export class ResourceCollector {
174
189
  }
175
190
 
176
191
  await this.addModuleResources();
177
- return this.modules.resourceArray(type);
192
+ return this.modules.resourceArray(type, moduleName);
178
193
  } catch {
179
194
  return [];
180
195
  }
@@ -261,10 +276,14 @@ export class ResourceCollector {
261
276
  /**
262
277
  * Collect specific resource from modules.
263
278
  * @param type Type of resource (e.g. 'templates').
279
+ * @param moduleName Name of the module to collect resources from
264
280
  * @returns array of collected items.
265
281
  */
266
- public async collectResourcesFromModules(type: ResourceFolderType) {
267
- return (await this.addResourcesFromModules(type)).map((item) =>
282
+ public async collectResourcesFromModules(
283
+ type: ResourceFolderType,
284
+ moduleName?: string,
285
+ ) {
286
+ return (await this.addResourcesFromModules(type, moduleName)).map((item) =>
268
287
  stripExtension(item.name),
269
288
  );
270
289
  }
@@ -660,31 +660,58 @@ export class Project extends CardContainer {
660
660
  path: modulePath,
661
661
  cardKeyPrefix: moduleConfig.cardKeyPrefix,
662
662
  calculations: [
663
- ...(await this.resources.collectResourcesFromModules('calculations')),
663
+ ...(await this.resources.collectResourcesFromModules(
664
+ 'calculations',
665
+ moduleName,
666
+ )),
664
667
  ],
665
668
  cardTypes: [
666
- ...(await this.resources.collectResourcesFromModules('cardTypes')),
669
+ ...(await this.resources.collectResourcesFromModules(
670
+ 'cardTypes',
671
+ moduleName,
672
+ )),
667
673
  ],
668
674
  fieldTypes: [
669
- ...(await this.resources.collectResourcesFromModules('fieldTypes')),
675
+ ...(await this.resources.collectResourcesFromModules(
676
+ 'fieldTypes',
677
+ moduleName,
678
+ )),
670
679
  ],
671
680
  graphModels: [
672
- ...(await this.resources.collectResourcesFromModules('graphModels')),
681
+ ...(await this.resources.collectResourcesFromModules(
682
+ 'graphModels',
683
+ moduleName,
684
+ )),
673
685
  ],
674
686
  graphViews: [
675
- ...(await this.resources.collectResourcesFromModules('graphViews')),
687
+ ...(await this.resources.collectResourcesFromModules(
688
+ 'graphViews',
689
+ moduleName,
690
+ )),
676
691
  ],
677
692
  linkTypes: [
678
- ...(await this.resources.collectResourcesFromModules('linkTypes')),
693
+ ...(await this.resources.collectResourcesFromModules(
694
+ 'linkTypes',
695
+ moduleName,
696
+ )),
679
697
  ],
680
698
  reports: [
681
- ...(await this.resources.collectResourcesFromModules('reports')),
699
+ ...(await this.resources.collectResourcesFromModules(
700
+ 'reports',
701
+ moduleName,
702
+ )),
682
703
  ],
683
704
  templates: [
684
- ...(await this.resources.collectResourcesFromModules('templates')),
705
+ ...(await this.resources.collectResourcesFromModules(
706
+ 'templates',
707
+ moduleName,
708
+ )),
685
709
  ],
686
710
  workflows: [
687
- ...(await this.resources.collectResourcesFromModules('workflows')),
711
+ ...(await this.resources.collectResourcesFromModules(
712
+ 'workflows',
713
+ moduleName,
714
+ )),
688
715
  ],
689
716
  };
690
717
  }
@@ -823,15 +850,6 @@ export class Project extends CardContainer {
823
850
  return prefixes;
824
851
  }
825
852
 
826
- /**
827
- * Removes a module from project.
828
- * @param moduleName Name of the module
829
- */
830
- public async removeModule(moduleName: string) {
831
- await this.configuration.removeModule(moduleName);
832
- await this.collectModuleResources();
833
- }
834
-
835
853
  /**
836
854
  * Array of reports in the project.
837
855
  * @param from Defines where resources are collected from.
@@ -27,3 +27,39 @@ export class SchemaNotFound extends Error {
27
27
  this.name = 'SchemaNotFound';
28
28
  }
29
29
  }
30
+ /**
31
+ * Stores the context of a macro error that originated from another macro
32
+ */
33
+ export interface MacroDependency {
34
+ macroName: string;
35
+ parameters: string;
36
+ output?: string;
37
+ }
38
+ /**
39
+ * Thrown when a macro fails to execute.
40
+ */
41
+ export class MacroError extends Error {
42
+ public context: {
43
+ cardKey: string;
44
+ macroName: string;
45
+ parameters: string;
46
+ dependency?: MacroDependency;
47
+ };
48
+
49
+ constructor(
50
+ message: string,
51
+ cardKey: string,
52
+ macroName: string,
53
+ parameters: string,
54
+ dependency?: MacroDependency,
55
+ ) {
56
+ super(message);
57
+ this.name = 'MacroError';
58
+ this.context = {
59
+ cardKey,
60
+ macroName,
61
+ parameters,
62
+ dependency,
63
+ };
64
+ }
65
+ }
@@ -12,6 +12,7 @@
12
12
 
13
13
  import type { macroMetadata } from '../macros/common.js';
14
14
  import type { Project } from '../containers/project.js';
15
+ import type { MacroError } from '../exceptions/index.js';
15
16
 
16
17
  type Mode = 'validate' | 'static' | 'inject';
17
18
 
@@ -44,6 +45,8 @@ export interface MacroTaskState {
44
45
  placeholder: string;
45
46
  promiseResult: string | null;
46
47
  macro: string;
48
+ parameters: string;
49
+ error: MacroError | null;
47
50
  }
48
51
 
49
52
  // Handlebars options is not documented
@@ -17,13 +17,23 @@ import type {
17
17
  MacroTaskState,
18
18
  } from '../interfaces/macros.js';
19
19
  import { generateRandomString } from '../utils/random.js';
20
- import { handleMacroError } from './index.js';
20
+ import { MacroError } from '../exceptions/index.js';
21
21
  import type TaskQueue from './task-queue.js';
22
+ import { ClingoError } from '@cyberismo/node-clingo';
23
+ import { getChildLogger } from '../utils/log-utils.js';
22
24
 
23
25
  abstract class BaseMacro {
24
26
  private globalId: string;
25
27
  private localCounter: number = 0;
26
28
 
29
+ // Macros share the same logger
30
+ protected get logger() {
31
+ return getChildLogger({
32
+ module: 'macro',
33
+ macro: this.macroMetadata.name,
34
+ });
35
+ }
36
+
27
37
  constructor(
28
38
  protected macroMetadata: MacroMetadata,
29
39
  private readonly tasks: TaskQueue,
@@ -64,7 +74,7 @@ abstract class BaseMacro {
64
74
  if (task) {
65
75
  dependencies.push(task);
66
76
  } else {
67
- console.warn(
77
+ this.logger.warn(
68
78
  `Dependency not found for placeholder: ${placeholder} (globalId: ${globalId}, localId: ${localId})`,
69
79
  );
70
80
  }
@@ -81,6 +91,16 @@ abstract class BaseMacro {
81
91
  };
82
92
  }
83
93
 
94
+ private findTask(globalId: string, localId: number) {
95
+ const task = this.tasks.find(globalId, localId);
96
+ if (!task) {
97
+ this.logger.warn(
98
+ `Task not found for global id ${globalId}, local id ${localId}.`,
99
+ );
100
+ }
101
+ return task;
102
+ }
103
+
84
104
  /**
85
105
  * Function responsible for starting the promise and storing it along with its localId.
86
106
  */
@@ -111,20 +131,53 @@ abstract class BaseMacro {
111
131
  const dependencies = this.findDependencies(rawInput);
112
132
 
113
133
  // Create a promise to resolve dependencies, execute the macro, and handle the results
114
- const promise = Promise.all(dependencies.map((dep) => dep.promise))
134
+ const promise = Promise.allSettled(dependencies.map((dep) => dep.promise))
115
135
  .then(() => {
116
136
  for (const dependency of dependencies) {
137
+ if (dependency.error) {
138
+ const task = this.findTask(this.globalId, localId);
139
+ if (task) {
140
+ // There could be a better way, but multi-nested macros are rare
141
+ task.error = new MacroError(
142
+ dependency.error.message,
143
+ context.cardKey,
144
+ this.metadata.name,
145
+ dependency.error.context.parameters,
146
+ {
147
+ macroName: dependency.macro,
148
+ parameters: dependency.parameters,
149
+ },
150
+ );
151
+ }
152
+ return;
153
+ }
117
154
  input = input.replace(
118
155
  dependency.placeholder,
119
156
  dependency.promiseResult || '',
120
157
  );
158
+ // parse json after each dep, so we know the exact macro which produced the error
159
+ try {
160
+ JSON.parse(input);
161
+ } catch {
162
+ const task = this.findTask(this.globalId, localId);
163
+ if (task) {
164
+ task.error = new MacroError(
165
+ 'Invalid JSON produced by macro dependency',
166
+ context.cardKey,
167
+ this.metadata.name,
168
+ input,
169
+ {
170
+ macroName: dependency.macro,
171
+ parameters: dependency.parameters,
172
+ output: input,
173
+ },
174
+ );
175
+ }
176
+ return;
177
+ }
121
178
  }
122
- let parsed;
123
- try {
124
- parsed = JSON.parse(input);
125
- } catch {
126
- return 'Invalid JSON';
127
- }
179
+ // This will never throw in practice, thus no need to catch
180
+ const parsed = JSON.parse(input);
128
181
 
129
182
  // Select the function to execute based on context mode
130
183
  const functionToCall =
@@ -134,28 +187,37 @@ abstract class BaseMacro {
134
187
  return functionToCall(context, parsed);
135
188
  })
136
189
  .then((result) => {
137
- const task = this.tasks.find(this.globalId, localId);
190
+ // undefined is used to indicate that the macro did not run for some reason
191
+ if (result === undefined) {
192
+ return;
193
+ }
194
+ const task = this.findTask(this.globalId, localId);
138
195
  if (task) {
139
196
  task.promiseResult = result;
140
- } else {
141
- console.error(
142
- `Task not found after execution: macro ${this.metadata.name}, local id ${localId}.`,
143
- );
144
197
  }
145
198
  })
146
199
  .catch((err) => {
147
- const task = this.tasks.find(this.globalId, localId);
200
+ if (!(err instanceof Error)) {
201
+ this.logger.error(err, 'Unknown error');
202
+ err = new Error('Unknown error');
203
+ }
204
+ const message =
205
+ err instanceof ClingoError
206
+ ? err.details.errors.join('\n')
207
+ : err.message;
208
+ const error =
209
+ err instanceof MacroError
210
+ ? err
211
+ : new MacroError(
212
+ message,
213
+ context.cardKey,
214
+ this.metadata.name,
215
+ input,
216
+ );
217
+
218
+ const task = this.findTask(this.globalId, localId);
148
219
  if (task) {
149
- task.promiseResult = handleMacroError(
150
- err,
151
- this.metadata.name,
152
- context,
153
- );
154
- } else {
155
- console.error(
156
- `Error handling task for macro ${this.metadata.name}, local id ${localId}:`,
157
- err,
158
- );
220
+ task.error = error;
159
221
  }
160
222
  });
161
223
 
@@ -167,6 +229,8 @@ abstract class BaseMacro {
167
229
  placeholder,
168
230
  promiseResult: null,
169
231
  macro: this.macroMetadata.name,
232
+ parameters: rawInput,
233
+ error: null,
170
234
  });
171
235
  // Return the placeholder
172
236
  return placeholder;
@@ -17,7 +17,6 @@ import type { MacroOptions } from '../index.js';
17
17
  import { createImage, validateMacroContent } from '../index.js';
18
18
  import Handlebars from 'handlebars';
19
19
  import { join } from 'node:path';
20
- import { getChildLogger } from '../../utils/log-utils.js';
21
20
  import type { MacroGenerationContext } from '../../interfaces/macros.js';
22
21
  import macroMetadata from './metadata.js';
23
22
  import { pathExists } from '../../utils/file-utils.js';
@@ -26,6 +25,7 @@ import { resourceName } from '../../utils/resource-utils.js';
26
25
  import type { Schema } from 'jsonschema';
27
26
  import { validateJson } from '../../utils/validate.js';
28
27
  import type TaskQueue from '../task-queue.js';
28
+ import { ClingoError } from '@cyberismo/node-clingo';
29
29
 
30
30
  export interface GraphOptions extends MacroOptions {
31
31
  model: string;
@@ -33,11 +33,6 @@ export interface GraphOptions extends MacroOptions {
33
33
  }
34
34
 
35
35
  class ReportMacro extends BaseMacro {
36
- private get logger() {
37
- return getChildLogger({
38
- module: 'graphMacro',
39
- });
40
- }
41
36
  constructor(tasksQueue: TaskQueue) {
42
37
  super(macroMetadata, tasksQueue);
43
38
  }
@@ -117,7 +112,17 @@ class ReportMacro extends BaseMacro {
117
112
  const view = handlebars.compile(viewContent)(handlebarsContext);
118
113
 
119
114
  const modelContent = await readFile(modelLocation, { encoding: 'utf-8' });
120
- const result = await calculate.runGraph(modelContent, view);
115
+ let result: string;
116
+ try {
117
+ result = await calculate.runGraph(modelContent, view);
118
+ } catch (error) {
119
+ if (error instanceof ClingoError) {
120
+ throw new Error(
121
+ `Error running graph in view '${options.view}' in model '${options.model}': ${error.details.errors.join('\n')}`,
122
+ );
123
+ }
124
+ throw error;
125
+ }
121
126
 
122
127
  if (typeof result !== 'string') {
123
128
  throw new Error(
@@ -18,7 +18,7 @@ import report from './report/index.js';
18
18
  import scoreCard from './scoreCard/index.js';
19
19
 
20
20
  import { validateJson } from '../utils/validate.js';
21
- import { DHValidationError } from '../exceptions/index.js';
21
+ import { DHValidationError, MacroError } from '../exceptions/index.js';
22
22
  import type { AdmonitionType } from '../interfaces/adoc.js';
23
23
  import type { Validator } from 'jsonschema';
24
24
  import type {
@@ -29,6 +29,7 @@ import type {
29
29
  import type BaseMacro from './base-macro.js';
30
30
  import TaskQueue from './task-queue.js';
31
31
  import type { Calculate } from '../commands/index.js';
32
+ import { ClingoError } from '@cyberismo/node-clingo';
32
33
  const CURLY_LEFT = '&#123;';
33
34
  const CURLY_RIGHT = '&#125;';
34
35
 
@@ -211,7 +212,14 @@ export function applyMacroResults(
211
212
  context: MacroGenerationContext,
212
213
  ) {
213
214
  for (const item of tasks) {
214
- if (item.promiseResult === null) {
215
+ if (item.error) {
216
+ input = input.replace(
217
+ item.placeholder,
218
+ handleMacroError(item.error, item.macro, context),
219
+ );
220
+ } // It should not be possible that promiseResult is null if there never was an error
221
+ // Unless the function itself returns null / undefined
222
+ else if (item.promiseResult == null) {
215
223
  input = handleMacroError(
216
224
  new Error(
217
225
  `Tried to access result before it was resolved for ${item.placeholder}`,
@@ -240,7 +248,17 @@ export function handleMacroError(
240
248
  let message = `Error: ${error instanceof Error ? error.message : 'Unknown error'}`;
241
249
  if (error instanceof DHValidationError) {
242
250
  message = `Check json syntax of macro ${macro}: ${error.errors?.map((e) => e.message).join(', ')}`;
251
+ } else if (error instanceof MacroError) {
252
+ const { cardKey, macroName, dependency } = error.context;
253
+ message = `Macro error in card '${cardKey}' in macro '${macroName}':\n\n${error.message}.`;
254
+
255
+ if (dependency) {
256
+ message += `\n\nParameters:\n\n${context.mode === 'validate' ? dependency.parameters : createCodeBlock(dependency.parameters)}.\n\n${dependency.output ? `Output:\n\n${context.mode === 'validate' ? dependency.output : createCodeBlock(dependency.output)}` : ''}`;
257
+ }
258
+ } else if (error instanceof ClingoError) {
259
+ message = `Error running logic program in macro '${macro}':${error.details.errors.join('\n')}`;
243
260
  }
261
+
244
262
  if (
245
263
  typeof error === 'object' &&
246
264
  error != null &&
@@ -328,6 +346,15 @@ export function createAdmonition(
328
346
  return `[${type}]\n.${label}\n====\n${content}\n====\n\n`;
329
347
  }
330
348
 
349
+ /**
350
+ * Creates a code block
351
+ * @param content - The content of the code block
352
+ * @returns The code block as a string
353
+ */
354
+ export function createCodeBlock(content: string) {
355
+ return `\n\n----\n${content}\n----\n\n`;
356
+ }
357
+
331
358
  /**
332
359
  * Helper function for including base64 encoded images for now
333
360
  * @param image base64 encoded image