@cyberismo/data-handler 0.0.15 → 0.0.16

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 (173) hide show
  1. package/dist/card-metadata-updater.js +7 -1
  2. package/dist/card-metadata-updater.js.map +1 -1
  3. package/dist/command-handler.d.ts +4 -0
  4. package/dist/command-handler.js +19 -3
  5. package/dist/command-handler.js.map +1 -1
  6. package/dist/command-manager.d.ts +24 -1
  7. package/dist/command-manager.js +27 -3
  8. package/dist/command-manager.js.map +1 -1
  9. package/dist/commands/create.d.ts +1 -1
  10. package/dist/commands/create.js +34 -36
  11. package/dist/commands/create.js.map +1 -1
  12. package/dist/commands/export.d.ts +11 -2
  13. package/dist/commands/export.js +54 -41
  14. package/dist/commands/export.js.map +1 -1
  15. package/dist/commands/import.d.ts +9 -1
  16. package/dist/commands/import.js +15 -7
  17. package/dist/commands/import.js.map +1 -1
  18. package/dist/commands/move.js +0 -1
  19. package/dist/commands/move.js.map +1 -1
  20. package/dist/commands/remove.d.ts +8 -1
  21. package/dist/commands/remove.js +8 -4
  22. package/dist/commands/remove.js.map +1 -1
  23. package/dist/commands/rename.d.ts +4 -9
  24. package/dist/commands/rename.js +32 -101
  25. package/dist/commands/rename.js.map +1 -1
  26. package/dist/commands/show.d.ts +16 -10
  27. package/dist/commands/show.js +71 -55
  28. package/dist/commands/show.js.map +1 -1
  29. package/dist/commands/transition.d.ts +9 -2
  30. package/dist/commands/transition.js +25 -17
  31. package/dist/commands/transition.js.map +1 -1
  32. package/dist/commands/update.d.ts +16 -12
  33. package/dist/commands/update.js +19 -17
  34. package/dist/commands/update.js.map +1 -1
  35. package/dist/commands/validate.d.ts +17 -9
  36. package/dist/commands/validate.js +96 -35
  37. package/dist/commands/validate.js.map +1 -1
  38. package/dist/containers/project/calculation-engine.d.ts +7 -4
  39. package/dist/containers/project/calculation-engine.js +61 -66
  40. package/dist/containers/project/calculation-engine.js.map +1 -1
  41. package/dist/containers/project/project-paths.d.ts +5 -4
  42. package/dist/containers/project/project-paths.js +16 -12
  43. package/dist/containers/project/project-paths.js.map +1 -1
  44. package/dist/containers/project/resource-cache.d.ts +169 -0
  45. package/dist/containers/project/resource-cache.js +507 -0
  46. package/dist/containers/project/resource-cache.js.map +1 -0
  47. package/dist/containers/project/resource-handler.d.ts +129 -0
  48. package/dist/containers/project/resource-handler.js +206 -0
  49. package/dist/containers/project/resource-handler.js.map +1 -0
  50. package/dist/containers/project.d.ts +38 -153
  51. package/dist/containers/project.js +129 -405
  52. package/dist/containers/project.js.map +1 -1
  53. package/dist/containers/template.d.ts +8 -2
  54. package/dist/containers/template.js +20 -15
  55. package/dist/containers/template.js.map +1 -1
  56. package/dist/interfaces/folder-content-interfaces.d.ts +5 -3
  57. package/dist/interfaces/folder-content-interfaces.js +3 -3
  58. package/dist/interfaces/folder-content-interfaces.js.map +1 -1
  59. package/dist/interfaces/project-interfaces.d.ts +2 -4
  60. package/dist/interfaces/project-interfaces.js.map +1 -1
  61. package/dist/interfaces/resource-interfaces.d.ts +14 -1
  62. package/dist/interfaces/resource-interfaces.js.map +1 -1
  63. package/dist/macros/graph/index.js +12 -26
  64. package/dist/macros/graph/index.js.map +1 -1
  65. package/dist/macros/index.d.ts +1 -1
  66. package/dist/macros/index.js +2 -2
  67. package/dist/macros/index.js.map +1 -1
  68. package/dist/macros/report/index.js +3 -6
  69. package/dist/macros/report/index.js.map +1 -1
  70. package/dist/module-manager.d.ts +16 -3
  71. package/dist/module-manager.js +51 -19
  72. package/dist/module-manager.js.map +1 -1
  73. package/dist/project-settings.d.ts +16 -3
  74. package/dist/project-settings.js +79 -14
  75. package/dist/project-settings.js.map +1 -1
  76. package/dist/resources/calculation-resource.d.ts +4 -3
  77. package/dist/resources/calculation-resource.js +11 -5
  78. package/dist/resources/calculation-resource.js.map +1 -1
  79. package/dist/resources/card-type-resource.d.ts +6 -1
  80. package/dist/resources/card-type-resource.js +34 -23
  81. package/dist/resources/card-type-resource.js.map +1 -1
  82. package/dist/resources/create-defaults.d.ts +3 -2
  83. package/dist/resources/create-defaults.js +3 -2
  84. package/dist/resources/create-defaults.js.map +1 -1
  85. package/dist/resources/field-type-resource.d.ts +4 -1
  86. package/dist/resources/field-type-resource.js +22 -23
  87. package/dist/resources/field-type-resource.js.map +1 -1
  88. package/dist/resources/file-resource.d.ts +5 -9
  89. package/dist/resources/file-resource.js +6 -11
  90. package/dist/resources/file-resource.js.map +1 -1
  91. package/dist/resources/folder-resource.d.ts +29 -32
  92. package/dist/resources/folder-resource.js +59 -78
  93. package/dist/resources/folder-resource.js.map +1 -1
  94. package/dist/resources/graph-model-resource.d.ts +4 -1
  95. package/dist/resources/graph-model-resource.js +11 -4
  96. package/dist/resources/graph-model-resource.js.map +1 -1
  97. package/dist/resources/graph-view-resource.d.ts +5 -2
  98. package/dist/resources/graph-view-resource.js +7 -3
  99. package/dist/resources/graph-view-resource.js.map +1 -1
  100. package/dist/resources/link-type-resource.d.ts +5 -2
  101. package/dist/resources/link-type-resource.js +5 -2
  102. package/dist/resources/link-type-resource.js.map +1 -1
  103. package/dist/resources/report-resource.d.ts +6 -7
  104. package/dist/resources/report-resource.js +14 -23
  105. package/dist/resources/report-resource.js.map +1 -1
  106. package/dist/resources/resource-object.d.ts +93 -8
  107. package/dist/resources/resource-object.js +162 -110
  108. package/dist/resources/resource-object.js.map +1 -1
  109. package/dist/resources/template-resource.d.ts +7 -3
  110. package/dist/resources/template-resource.js +10 -3
  111. package/dist/resources/template-resource.js.map +1 -1
  112. package/dist/resources/workflow-resource.d.ts +5 -2
  113. package/dist/resources/workflow-resource.js +18 -22
  114. package/dist/resources/workflow-resource.js.map +1 -1
  115. package/dist/utils/card-utils.d.ts +2 -2
  116. package/dist/utils/card-utils.js +1 -1
  117. package/dist/utils/clingo-fact-builder.d.ts +25 -14
  118. package/dist/utils/clingo-fact-builder.js +27 -5
  119. package/dist/utils/clingo-fact-builder.js.map +1 -1
  120. package/dist/utils/clingo-facts.js +3 -4
  121. package/dist/utils/clingo-facts.js.map +1 -1
  122. package/dist/utils/resource-utils.d.ts +1 -0
  123. package/dist/utils/resource-utils.js +2 -1
  124. package/dist/utils/resource-utils.js.map +1 -1
  125. package/package.json +8 -8
  126. package/src/card-metadata-updater.ts +6 -2
  127. package/src/command-handler.ts +24 -5
  128. package/src/command-manager.ts +29 -17
  129. package/src/commands/create.ts +43 -78
  130. package/src/commands/export.ts +63 -52
  131. package/src/commands/import.ts +24 -14
  132. package/src/commands/move.ts +0 -1
  133. package/src/commands/remove.ts +11 -7
  134. package/src/commands/rename.ts +43 -149
  135. package/src/commands/show.ts +113 -78
  136. package/src/commands/transition.ts +26 -28
  137. package/src/commands/update.ts +25 -22
  138. package/src/commands/validate.ts +108 -67
  139. package/src/containers/project/calculation-engine.ts +61 -93
  140. package/src/containers/project/project-paths.ts +21 -13
  141. package/src/containers/project/resource-cache.ts +648 -0
  142. package/src/containers/project/resource-handler.ts +265 -0
  143. package/src/containers/project.ts +178 -522
  144. package/src/containers/template.ts +24 -19
  145. package/src/interfaces/folder-content-interfaces.ts +7 -6
  146. package/src/interfaces/project-interfaces.ts +7 -6
  147. package/src/interfaces/resource-interfaces.ts +18 -3
  148. package/src/macros/graph/index.ts +26 -47
  149. package/src/macros/index.ts +2 -2
  150. package/src/macros/report/index.ts +3 -9
  151. package/src/module-manager.ts +74 -17
  152. package/src/project-settings.ts +83 -14
  153. package/src/resources/calculation-resource.ts +18 -18
  154. package/src/resources/card-type-resource.ts +50 -50
  155. package/src/resources/create-defaults.ts +3 -2
  156. package/src/resources/field-type-resource.ts +41 -55
  157. package/src/resources/file-resource.ts +10 -36
  158. package/src/resources/folder-resource.ts +69 -120
  159. package/src/resources/graph-model-resource.ts +20 -22
  160. package/src/resources/graph-view-resource.ts +15 -17
  161. package/src/resources/link-type-resource.ts +10 -13
  162. package/src/resources/report-resource.ts +21 -43
  163. package/src/resources/resource-object.ts +194 -152
  164. package/src/resources/template-resource.ts +17 -16
  165. package/src/resources/workflow-resource.ts +25 -44
  166. package/src/utils/card-utils.ts +2 -2
  167. package/src/utils/clingo-fact-builder.ts +28 -16
  168. package/src/utils/clingo-facts.ts +3 -4
  169. package/src/utils/resource-utils.ts +2 -1
  170. package/dist/containers/project/resource-collector.d.ts +0 -110
  171. package/dist/containers/project/resource-collector.js +0 -344
  172. package/dist/containers/project/resource-collector.js.map +0 -1
  173. package/src/containers/project/resource-collector.ts +0 -404
@@ -23,9 +23,7 @@ import {
23
23
  type Card,
24
24
  type CardAttachment,
25
25
  CardNameRegEx,
26
- type Resource,
27
26
  } from '../interfaces/project-interfaces.js';
28
- import type { CardType, Workflow } from '../interfaces/resource-interfaces.js';
29
27
  import { pathExists, stripExtension } from '../utils/file-utils.js';
30
28
  import { DefaultContent } from '../resources/create-defaults.js';
31
29
 
@@ -42,6 +40,9 @@ import { resourceName } from '../utils/resource-utils.js';
42
40
 
43
41
  import { ROOT } from '../utils/constants.js';
44
42
 
43
+ // @todo: Fix the constructor to not use Resource.
44
+ import type { Resource } from './project/resource-cache.js';
45
+
45
46
  // creates template instance based on a project path and name
46
47
  export class Template extends CardContainer {
47
48
  private templateName: string;
@@ -55,6 +56,12 @@ export class Template extends CardContainer {
55
56
  });
56
57
  }
57
58
 
59
+ /**
60
+ * Creates an instance of Template container that holds template related cards.
61
+ * @param project Project in which template is.
62
+ * @param template Template resource that this container is connected to.
63
+ */
64
+ // @todo: Fix the constructor to not use Resource, but resource full path
58
65
  constructor(project: Project, template: Resource) {
59
66
  // Templates might come from modules. Remove module name from template name.
60
67
  const templateName = stripExtension(basename(template.name));
@@ -199,18 +206,14 @@ export class Template extends CardContainer {
199
206
  // Process metadata
200
207
  const processMetadata = async (card: Card, parentCards: Card[]) => {
201
208
  if (!card.metadata) return card;
209
+ const cardType = this.project.resources
210
+ .byType(card.metadata?.cardType, 'cardTypes')
211
+ .show();
202
212
 
203
- const cardType = this.project.resource<CardType>(card.metadata?.cardType);
204
- if (!cardType) {
205
- throw new Error(
206
- `Card type '${card.metadata?.cardType}' of card ${card.key} cannot be found`,
207
- );
208
- }
213
+ const workflow = this.project.resources
214
+ .byType(cardType.workflow, 'workflows')
215
+ .show();
209
216
 
210
- const workflow = this.project.resource<Workflow>(cardType.workflow);
211
- if (!workflow) {
212
- throw new Error(`Workflow '${cardType.workflow}' cannot be found`);
213
- }
214
217
  const initialWorkflowState = workflow.transitions.find(
215
218
  (item) => item.fromState.includes('') || item.fromState.length === 0,
216
219
  );
@@ -268,7 +271,7 @@ export class Template extends CardContainer {
268
271
  await mkdir(processedCard.path, { recursive: true });
269
272
 
270
273
  await Promise.all([
271
- processedCard.metadata && this.saveCardMetadata(processedCard),
274
+ this.saveCardMetadata(processedCard),
272
275
  writeFile(
273
276
  join(processedCard.path, Project.cardContentFile),
274
277
  processedAttachments.content || '',
@@ -389,18 +392,19 @@ export class Template extends CardContainer {
389
392
  parentCard?: Card,
390
393
  ): Promise<string> {
391
394
  const destinationCardPath = parentCard
392
- ? join(await this.cardFolder(parentCard.key), 'c')
395
+ ? join(this.cardFolder(parentCard.key), 'c')
393
396
  : this.templateCardsPath;
394
397
  let newCardKey = '';
395
398
 
396
399
  try {
400
+ // todo: to use cache instead of file access
397
401
  if (!pathExists(this.templateFolder())) {
398
402
  throw new Error(`Template '${this.containerName}' does not exist`);
399
403
  }
400
- const cardType = this.project.resource<CardType>(cardTypeName);
401
- if (cardType === undefined) {
402
- throw new Error(`Card type '${cardTypeName}' does not exist`);
403
- }
404
+ const cardType = this.project.resources
405
+ .byType(cardTypeName, 'cardTypes')
406
+ .show();
407
+
404
408
  if (parentCard && !this.hasTemplateCard(parentCard.key)) {
405
409
  throw new Error(
406
410
  `Card '${parentCard.key}' does not exist in template '${this.containerName}'`,
@@ -522,7 +526,7 @@ export class Template extends CardContainer {
522
526
  /**
523
527
  * Checks if a specific card key exists in a template.
524
528
  * @param cardKey Card key to find from template.
525
- * @return true if card with a given card key exists in the template, false otherwise.
529
+ * @returns true if card with a given card key exists in the template, false otherwise.
526
530
  */
527
531
  public hasTemplateCard(cardKey: string): boolean {
528
532
  return this.project.hasTemplateCard(cardKey);
@@ -533,6 +537,7 @@ export class Template extends CardContainer {
533
537
  * @returns true, if template is exists in project; false otherwise
534
538
  */
535
539
  public isCreated(): boolean {
540
+ // todo: to use cache instead of file access
536
541
  return pathExists(this.templateCardsPath);
537
542
  }
538
543
 
@@ -13,6 +13,8 @@
13
13
 
14
14
  import type { Schema } from 'jsonschema';
15
15
 
16
+ export type { Schema };
17
+
16
18
  // All file mappings for lookup (filename -> property name)
17
19
  export const ALL_FILE_MAPPINGS = {
18
20
  'calculation.lp': 'calculation',
@@ -24,7 +26,7 @@ export const ALL_FILE_MAPPINGS = {
24
26
  } as const;
25
27
 
26
28
  // Reverse mappings from property names to filenames
27
- export const REVERSE_FILE_MAPPINGS = {
29
+ export const CONTENT_FILES = {
28
30
  calculation: 'calculation.lp',
29
31
  contentTemplate: 'index.adoc.hbs',
30
32
  model: 'model.lp',
@@ -34,7 +36,7 @@ export const REVERSE_FILE_MAPPINGS = {
34
36
  } as const;
35
37
 
36
38
  // Union type of all valid content property names
37
- export type ContentPropertyName = keyof typeof REVERSE_FILE_MAPPINGS;
39
+ export type ContentPropertyName = keyof typeof CONTENT_FILES;
38
40
 
39
41
  // Content interface for Calculation resources
40
42
  export interface CalculationContent {
@@ -49,6 +51,7 @@ export interface GraphModelContent {
49
51
  // Content interface for Graph View resources
50
52
  export interface GraphViewContent {
51
53
  viewTemplate?: string;
54
+ schema?: Schema;
52
55
  }
53
56
 
54
57
  // Content interface for Report resources
@@ -70,9 +73,7 @@ export type FolderResourceContent =
70
73
  * @returns filename that matches property name
71
74
  */
72
75
  export function filename(propertyName: string): string | undefined {
73
- return REVERSE_FILE_MAPPINGS[
74
- propertyName as keyof typeof REVERSE_FILE_MAPPINGS
75
- ];
76
+ return CONTENT_FILES[propertyName as keyof typeof CONTENT_FILES];
76
77
  }
77
78
 
78
79
  /**
@@ -80,7 +81,7 @@ export function filename(propertyName: string): string | undefined {
80
81
  * @param filename Filename.
81
82
  * @returns property name that matches filename
82
83
  */
83
- export function propertyName(
84
+ export function contentPropertyName(
84
85
  filename: string,
85
86
  ): ContentPropertyName | undefined {
86
87
  return ALL_FILE_MAPPINGS[filename as keyof typeof ALL_FILE_MAPPINGS];
@@ -178,6 +178,7 @@ export interface ProjectMetadata {
178
178
 
179
179
  // Project's settings (=cardsConfig.json).
180
180
  export interface ProjectSettings {
181
+ schemaVersion?: number;
181
182
  cardKeyPrefix: string;
182
183
  name: string;
183
184
  modules: ModuleSetting[];
@@ -228,12 +229,9 @@ export type RemovableResourceTypes =
228
229
  | 'workflow'
229
230
  | 'label';
230
231
 
231
- // Project resource, such as workflow, template or card type as file system object.
232
- export interface Resource {
233
- name: string;
234
- path: string;
235
- }
236
-
232
+ // TODO: fix terminology. In DH, modules are not resources.
233
+ // Also, this contains non-folder resources
234
+ // This was done likely like this, because on the CLI, they act similarly to resources
237
235
  // Resources that have own folders.
238
236
  export type ResourceFolderType =
239
237
  | 'calculations'
@@ -247,6 +245,9 @@ export type ResourceFolderType =
247
245
  | 'templates'
248
246
  | 'workflows';
249
247
 
248
+ // This covers all 'true' resources
249
+ export type ResourceType = Exclude<ResourceFolderType, 'modules'>;
250
+
250
251
  // All resource types; both singular and plural.
251
252
  export type ResourceTypes =
252
253
  | RemovableResourceTypes
@@ -17,6 +17,7 @@ import type {
17
17
  GraphViewContent,
18
18
  ReportContent,
19
19
  } from './folder-content-interfaces.js';
20
+ import type { ResourceType } from './project-interfaces.js';
20
21
 
21
22
  /**
22
23
  * Each resource represents a file (or a folder in some cases) with metadata stored
@@ -140,17 +141,31 @@ export interface ResourceBaseMetadata {
140
141
  }
141
142
 
142
143
  // All resources metadata content.
143
- export type ResourceContent =
144
- | CalculationMetadata
144
+ export type AnyResourceContent =
145
+ | Calculation
145
146
  | CardType
146
147
  | FieldType
147
148
  | GraphModel
148
149
  | GraphView
149
150
  | LinkType
150
- | ReportMetadata
151
+ | Report
151
152
  | TemplateMetadata
152
153
  | Workflow;
153
154
 
155
+ export type ResourceContent<T extends ResourceType> = ResourceContentMap[T];
156
+
157
+ export type ResourceContentMap = {
158
+ calculations: Calculation;
159
+ cardTypes: CardType;
160
+ fieldTypes: FieldType;
161
+ graphModels: GraphModel;
162
+ graphViews: GraphView;
163
+ linkTypes: LinkType;
164
+ reports: Report;
165
+ templates: TemplateMetadata;
166
+ workflows: Workflow;
167
+ };
168
+
154
169
  // Template configuration details.
155
170
  export interface TemplateConfiguration extends TemplateMetadata {
156
171
  path: string;
@@ -12,19 +12,14 @@
12
12
  */
13
13
 
14
14
  import BaseMacro from '../base-macro.js';
15
- import type { GraphOptions } from './types.js';
16
15
  import { createImage, validateMacroContent } from '../index.js';
17
16
  import Handlebars from 'handlebars';
18
- import type { MacroGenerationContext } from '../../interfaces/macros.js';
19
17
  import macroMetadata from './metadata.js';
20
- import { pathExists } from '../../utils/file-utils.js';
21
- import { readFile } from 'node:fs/promises';
22
- import { readFileSync } from 'node:fs';
23
- import type { Schema } from 'jsonschema';
24
- import type TaskQueue from '../task-queue.js';
25
18
  import { ClingoError } from '@cyberismo/node-clingo';
26
- import { resourceFilePath } from '../../utils/resource-utils.js';
27
- import { resourceName } from '../../utils/resource-utils.js';
19
+
20
+ import type { GraphOptions } from './types.js';
21
+ import type { MacroGenerationContext } from '../../interfaces/macros.js';
22
+ import type TaskQueue from '../task-queue.js';
28
23
 
29
24
  class GraphMacro extends BaseMacro {
30
25
  constructor(tasksQueue: TaskQueue) {
@@ -38,40 +33,35 @@ class GraphMacro extends BaseMacro {
38
33
  handleStatic = async (context: MacroGenerationContext, input: unknown) => {
39
34
  const options = this.parseOptions(input, context);
40
35
 
41
- const modelLocation = resourceFilePath(
42
- context.project,
43
- resourceName(options.model),
44
- 'model.lp',
45
- );
46
- const viewLocation = resourceFilePath(
47
- context.project,
48
- resourceName(options.view),
49
- 'view.lp.hbs',
36
+ const modelResource = context.project.resources.byType(
37
+ options.model,
38
+ 'graphModels',
50
39
  );
40
+ const modelContent = modelResource.contentData();
51
41
 
52
- if (!pathExists(modelLocation)) {
53
- throw new Error(`Graph: Model ${options.model} does not exist`);
42
+ const viewResource = context.project.resources.byType(
43
+ options.view,
44
+ 'graphViews',
45
+ );
46
+ const viewContent = viewResource.contentData();
47
+ if (!viewContent.viewTemplate) {
48
+ throw new Error(`Graph: View ${options.view} has no view template`);
54
49
  }
55
50
 
56
- let viewContent = '';
57
- try {
58
- viewContent = await readFile(viewLocation, { encoding: 'utf-8' });
59
- } catch {
60
- throw new Error(`Graph: View ${options.view} does not exist`);
61
- }
62
51
  const handlebarsContext = {
63
52
  cardKey: context.cardKey,
64
53
  ...options,
65
54
  };
66
55
 
67
56
  const handlebars = Handlebars.create();
68
- const view = handlebars.compile(viewContent)(handlebarsContext);
57
+ const view = handlebars.compile(viewContent.viewTemplate)(
58
+ handlebarsContext,
59
+ );
69
60
 
70
- const modelContent = await readFile(modelLocation, { encoding: 'utf-8' });
71
61
  let result: string;
72
62
  try {
73
63
  result = await context.project.calculationEngine.runGraph(
74
- modelContent,
64
+ modelContent.model,
75
65
  view,
76
66
  context.context,
77
67
  );
@@ -98,24 +88,13 @@ class GraphMacro extends BaseMacro {
98
88
  ): GraphOptions {
99
89
  const options = validateMacroContent<GraphOptions>(this.metadata, input);
100
90
 
101
- let schema: Schema | null = null;
102
- try {
103
- schema = JSON.parse(
104
- readFileSync(
105
- resourceFilePath(
106
- context.project,
107
- resourceName(options.view),
108
- 'parameterSchema.json',
109
- ),
110
- { encoding: 'utf-8' },
111
- ),
112
- );
113
- } catch (err) {
114
- this.logger.trace(
115
- err,
116
- 'Graph schema not found or failed to read, skipping validation',
117
- );
118
- }
91
+ // Get schema from view resource content if available
92
+ const resource = context.project.resources.byType(
93
+ options.view,
94
+ 'graphViews',
95
+ );
96
+ const content = resource.contentData();
97
+ const schema = content.schema;
119
98
 
120
99
  if (schema) {
121
100
  validateMacroContent(this.metadata, input, schema);
@@ -186,7 +186,7 @@ export function validateMacroContent<T>(
186
186
  * @param tasks - Tasks to register
187
187
  * @returns macro instances
188
188
  */
189
- export function registerMacros(
189
+ export async function registerMacros(
190
190
  instance: typeof Handlebars,
191
191
  context: MacroGenerationContext,
192
192
  tasks: TaskQueue,
@@ -242,7 +242,7 @@ export async function evaluateMacros(
242
242
  ) {
243
243
  const handlebars = Handlebars.create();
244
244
  const tasks = new TaskQueue();
245
- registerMacros(handlebars, context, tasks);
245
+ await registerMacros(handlebars, context, tasks);
246
246
  let result = content;
247
247
  while ((context.maxTries ?? 10) > 0) {
248
248
  await tasks.reset();
@@ -18,8 +18,6 @@ import macroMetadata from './metadata.js';
18
18
  import BaseMacro from '../base-macro.js';
19
19
  import { validateJson } from '../../utils/validate.js';
20
20
  import type TaskQueue from '../task-queue.js';
21
- import { ReportResource } from '../../resources/report-resource.js';
22
- import { resourceName } from '../../utils/resource-utils.js';
23
21
  import { generateReportContent } from '../../utils/report.js';
24
22
  import { ClingoError } from '@cyberismo/node-clingo';
25
23
  import type { ReportOptions } from './types.js';
@@ -34,13 +32,9 @@ class ReportMacro extends BaseMacro {
34
32
 
35
33
  handleStatic = async (context: MacroGenerationContext, data: unknown) => {
36
34
  const options = this.validate(data);
37
- const resource = new ReportResource(
38
- context.project,
39
- resourceName(options.name),
40
- );
41
- const report = await resource.show();
42
-
43
- if (!report) throw new Error(`Report ${options.name} does not exist`);
35
+ const report = context.project.resources
36
+ .byType(options.name, 'reports')
37
+ .show();
44
38
 
45
39
  if (report.content.schema) {
46
40
  validateJson(options, {
@@ -26,7 +26,7 @@ import { Project } from './containers/project.js';
26
26
  import type { ProjectConfiguration } from './project-settings.js';
27
27
  import { ProjectPaths } from './containers/project/project-paths.js';
28
28
  import { readJsonFile } from './utils/json.js';
29
- import { Validate } from './commands/index.js';
29
+ import { Validate } from './commands/validate.js';
30
30
 
31
31
  const FILE_PROTOCOL = 'file:';
32
32
  const HTTPS_PROTOCOL = 'https:';
@@ -54,7 +54,7 @@ export class ModuleManager {
54
54
  await copyDir(sourcePath, destinationPath);
55
55
 
56
56
  // Update the resources.
57
- await this.project.collectModuleResources();
57
+ this.project.resources.changedModules();
58
58
  }
59
59
 
60
60
  // Creates a map of what dependencies each module depend from.
@@ -213,13 +213,11 @@ export class ModuleManager {
213
213
 
214
214
  // Fetches direct dependencies of a module.
215
215
  private async dependencies(moduleName: string): Promise<Set<string>> {
216
- const allModules = await this.project.modules();
217
- if (!allModules) return new Set();
218
- const module = allModules.find((m) => m.name === moduleName);
216
+ const module = await this.project.module(moduleName);
219
217
  if (!module) {
220
218
  throw new Error(`Module '${moduleName}' not found`);
221
219
  }
222
- const modulePath = join(module.path, module.name, 'cardsConfig.json');
220
+ const modulePath = join(module.path, 'cardsConfig.json');
223
221
  const moduleConfiguration = (await readJsonFile(
224
222
  modulePath,
225
223
  )) as ProjectConfiguration;
@@ -287,7 +285,7 @@ export class ModuleManager {
287
285
 
288
286
  // Imports from a given folder. Is used both for .temp/<module name> and file locations.
289
287
  private async importFromFolder(path: string, name: string) {
290
- await this.importFileModule(path);
288
+ await this.importFileModule(path, undefined, true); // Skip validation during updates
291
289
  console.log(
292
290
  `... Imported module '${name}' to '${this.project.configuration.name}'`,
293
291
  );
@@ -361,6 +359,9 @@ export class ModuleManager {
361
359
  try {
362
360
  await this.removeModuleFiles(module.name);
363
361
  console.log(`... Removed imported module '${module.name}'`);
362
+
363
+ // Refresh module resources in cache after filesystem removal to avoid stale prefixes
364
+ this.project.resources.changedModules();
364
365
  } catch (error) {
365
366
  if (error instanceof Error)
366
367
  console.error(
@@ -479,7 +480,11 @@ export class ModuleManager {
479
480
  }
480
481
 
481
482
  // Updates modules in the project.
482
- private async update(module?: ModuleSetting, credentials?: Credentials) {
483
+ private async update(
484
+ module?: ModuleSetting,
485
+ credentials?: Credentials,
486
+ skipModules?: Set<string>,
487
+ ) {
483
488
  // Prints dots every half second so that user knows that something is ongoing
484
489
  function start() {
485
490
  console.log('... Collecting unique modules. This takes a moment.');
@@ -514,22 +519,46 @@ export class ModuleManager {
514
519
  uniqueModules.map((item) => item.name),
515
520
  );
516
521
 
522
+ // Filter out modules that are already imported
523
+ const modulesToImport = skipModules
524
+ ? uniqueModules.filter((module) => !skipModules.has(module.name))
525
+ : uniqueModules;
526
+
527
+ if (
528
+ skipModules &&
529
+ skipModules.size > 0 &&
530
+ modulesToImport.length < uniqueModules.length
531
+ ) {
532
+ const skippedModules = uniqueModules
533
+ .filter((module) => skipModules.has(module.name))
534
+ .map((m) => m.name)
535
+ .join(', ');
536
+ console.log(
537
+ `... Skipping already imported module(s): ${skippedModules}`,
538
+ );
539
+ }
540
+
517
541
  // Update modules parallel.
518
542
  const promises: Promise<void>[] = [];
519
- uniqueModules.forEach((module) =>
543
+ modulesToImport.forEach((module) =>
520
544
  promises.push(this.handleModule(module)),
521
545
  );
522
546
  await Promise.all(promises);
523
547
  await deleteDir(this.tempModulesDir);
524
- await this.project.collectModuleResources();
548
+ this.project.resources.changedModules();
525
549
  }
526
550
  }
527
551
 
528
552
  // Checks that module prefix is not in use in the project
529
- private async validatePrefix(modulePrefix: string) {
553
+ // Optionally skip check for modules that are already imported (during updates)
554
+ private validatePrefix(modulePrefix: string, skipIfExists = false) {
530
555
  // Do not allow modules with same prefixes.
531
- const currentlyUsedPrefixes = await this.project.projectPrefixes();
556
+ const currentlyUsedPrefixes = this.project.projectPrefixes();
532
557
  if (currentlyUsedPrefixes.includes(modulePrefix)) {
558
+ // If skipIfExists is true, allow re-importing modules that are already present
559
+ if (skipIfExists) {
560
+ return;
561
+ }
533
562
  throw new Error(
534
563
  `Imported project has a prefix '${modulePrefix}' that is already used in the project. Cannot import from module.`,
535
564
  );
@@ -540,8 +569,14 @@ export class ModuleManager {
540
569
  * Imports module from local file path.
541
570
  * @param source Path to import from.
542
571
  * @param destination is this really needed???
572
+ * @param skipValidation Skip prefix validation (used during updates)
573
+ * @returns Module prefix of the imported module.
543
574
  */
544
- public async importFileModule(source: string, destination?: string) {
575
+ public async importFileModule(
576
+ source: string,
577
+ destination?: string,
578
+ skipValidation = false,
579
+ ) {
545
580
  if (!Validate.validateFolder(source)) {
546
581
  throw new Error(
547
582
  `Input validation error: folder name is invalid '${source}'`,
@@ -566,7 +601,7 @@ export class ModuleManager {
566
601
  );
567
602
  const sourcePath = sourceProject.paths.resourcesFolder;
568
603
 
569
- await this.validatePrefix(modulePrefix);
604
+ this.validatePrefix(modulePrefix, skipValidation);
570
605
 
571
606
  // Copy files.
572
607
  await this.addFileContents(sourcePath, destinationPath);
@@ -578,12 +613,14 @@ export class ModuleManager {
578
613
  * @param source Git URL to import from.
579
614
  * @param options Modules setting options.
580
615
  * @param credentials Credentials for private repositories.
616
+ * @param skipValidation Skip prefix validation (used during updates)
581
617
  * @returns module prefix as defined in its CardsConfig.json
582
618
  */
583
619
  public async importGitModule(
584
620
  source: string,
585
621
  options?: ModuleSettingOptions,
586
622
  credentials?: Credentials,
623
+ skipValidation = false,
587
624
  ) {
588
625
  const clonedName = await this.clone(
589
626
  {
@@ -596,7 +633,7 @@ export class ModuleManager {
596
633
  );
597
634
  const clonePath = join(this.tempModulesDir, clonedName);
598
635
  const modulePrefix = (await this.configuration(clonePath)).cardKeyPrefix;
599
- await this.validatePrefix(modulePrefix);
636
+ this.validatePrefix(modulePrefix, skipValidation);
600
637
 
601
638
  const sourcePath = new ProjectPaths(clonePath).resourcesFolder;
602
639
  const destinationPath = join(
@@ -612,6 +649,7 @@ export class ModuleManager {
612
649
  * If module is not used by any other modules, then will remove the module from disk as well.
613
650
  * Otherwise, only updates project configuration.
614
651
  * @param moduleName Name of the module to remove
652
+ * @throws If module was not found.
615
653
  */
616
654
  public async removeModule(moduleName: string) {
617
655
  const projectModules = this.project.configuration.modules;
@@ -639,14 +677,33 @@ export class ModuleManager {
639
677
  await this.project.removeModule(moduleName);
640
678
  }
641
679
 
680
+ /**
681
+ * Updates dependencies for a module without re-importing the module itself.
682
+ * Used during module import to fetch dependencies after the main module is already imported.
683
+ * @param module Module whose dependencies should be updated.
684
+ * @param credentials Optional credentials for private repositories.
685
+ * @returns Module prefix as defined in its CardsConfig.json
686
+ */
687
+ public async updateDependencies(
688
+ module: ModuleSetting,
689
+ credentials?: Credentials,
690
+ ) {
691
+ return this.update(module, credentials, new Set([module.name]));
692
+ }
693
+
642
694
  /**
643
695
  * Imports module from a local file path or a git URL.
644
696
  * @param module Module to update. If not provided, updates all modules.
645
697
  * @param credentials Optional credentials for private repositories.
698
+ * @param skipModules Optional set of module names to skip during import.
646
699
  * @returns Module prefix as defined in its CardsConfig.json
647
700
  */
648
- public async updateModule(module: ModuleSetting, credentials?: Credentials) {
649
- return this.update(module, credentials);
701
+ public async updateModule(
702
+ module: ModuleSetting,
703
+ credentials?: Credentials,
704
+ skipModules?: Set<string>,
705
+ ) {
706
+ return this.update(module, credentials, skipModules);
650
707
  }
651
708
 
652
709
  /**