@cyberismo/data-handler 0.0.15 → 0.0.17

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 (191) 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 +22 -8
  5. package/dist/command-handler.js.map +1 -1
  6. package/dist/command-manager.d.ts +24 -1
  7. package/dist/command-manager.js +31 -7
  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/fetch.d.ts +8 -0
  16. package/dist/commands/fetch.js +101 -23
  17. package/dist/commands/fetch.js.map +1 -1
  18. package/dist/commands/import.d.ts +14 -3
  19. package/dist/commands/import.js +27 -10
  20. package/dist/commands/import.js.map +1 -1
  21. package/dist/commands/move.js +0 -1
  22. package/dist/commands/move.js.map +1 -1
  23. package/dist/commands/remove.d.ts +11 -2
  24. package/dist/commands/remove.js +15 -5
  25. package/dist/commands/remove.js.map +1 -1
  26. package/dist/commands/rename.d.ts +4 -9
  27. package/dist/commands/rename.js +37 -101
  28. package/dist/commands/rename.js.map +1 -1
  29. package/dist/commands/show.d.ts +20 -12
  30. package/dist/commands/show.js +79 -57
  31. package/dist/commands/show.js.map +1 -1
  32. package/dist/commands/transition.d.ts +9 -2
  33. package/dist/commands/transition.js +25 -17
  34. package/dist/commands/transition.js.map +1 -1
  35. package/dist/commands/update.d.ts +16 -12
  36. package/dist/commands/update.js +19 -17
  37. package/dist/commands/update.js.map +1 -1
  38. package/dist/commands/validate.d.ts +17 -9
  39. package/dist/commands/validate.js +94 -35
  40. package/dist/commands/validate.js.map +1 -1
  41. package/dist/containers/card-container.d.ts +7 -5
  42. package/dist/containers/card-container.js +30 -5
  43. package/dist/containers/card-container.js.map +1 -1
  44. package/dist/containers/project/calculation-engine.d.ts +7 -4
  45. package/dist/containers/project/calculation-engine.js +61 -66
  46. package/dist/containers/project/calculation-engine.js.map +1 -1
  47. package/dist/containers/project/project-paths.d.ts +7 -4
  48. package/dist/containers/project/project-paths.js +22 -12
  49. package/dist/containers/project/project-paths.js.map +1 -1
  50. package/dist/containers/project/resource-cache.d.ts +169 -0
  51. package/dist/containers/project/resource-cache.js +509 -0
  52. package/dist/containers/project/resource-cache.js.map +1 -0
  53. package/dist/containers/project/resource-handler.d.ts +129 -0
  54. package/dist/containers/project/resource-handler.js +206 -0
  55. package/dist/containers/project/resource-handler.js.map +1 -0
  56. package/dist/containers/project.d.ts +46 -152
  57. package/dist/containers/project.js +179 -409
  58. package/dist/containers/project.js.map +1 -1
  59. package/dist/containers/template.d.ts +8 -2
  60. package/dist/containers/template.js +24 -19
  61. package/dist/containers/template.js.map +1 -1
  62. package/dist/interfaces/command-options.d.ts +3 -1
  63. package/dist/interfaces/folder-content-interfaces.d.ts +5 -3
  64. package/dist/interfaces/folder-content-interfaces.js +3 -3
  65. package/dist/interfaces/folder-content-interfaces.js.map +1 -1
  66. package/dist/interfaces/project-interfaces.d.ts +7 -9
  67. package/dist/interfaces/project-interfaces.js.map +1 -1
  68. package/dist/interfaces/resource-interfaces.d.ts +14 -1
  69. package/dist/interfaces/resource-interfaces.js.map +1 -1
  70. package/dist/macros/graph/index.js +12 -26
  71. package/dist/macros/graph/index.js.map +1 -1
  72. package/dist/macros/index.d.ts +1 -1
  73. package/dist/macros/index.js +2 -2
  74. package/dist/macros/index.js.map +1 -1
  75. package/dist/macros/report/index.js +3 -6
  76. package/dist/macros/report/index.js.map +1 -1
  77. package/dist/module-manager.d.ts +16 -3
  78. package/dist/module-manager.js +51 -19
  79. package/dist/module-manager.js.map +1 -1
  80. package/dist/project-settings.d.ts +21 -3
  81. package/dist/project-settings.js +91 -14
  82. package/dist/project-settings.js.map +1 -1
  83. package/dist/resources/calculation-resource.d.ts +4 -3
  84. package/dist/resources/calculation-resource.js +11 -5
  85. package/dist/resources/calculation-resource.js.map +1 -1
  86. package/dist/resources/card-type-resource.d.ts +6 -1
  87. package/dist/resources/card-type-resource.js +34 -23
  88. package/dist/resources/card-type-resource.js.map +1 -1
  89. package/dist/resources/create-defaults.d.ts +3 -2
  90. package/dist/resources/create-defaults.js +3 -2
  91. package/dist/resources/create-defaults.js.map +1 -1
  92. package/dist/resources/field-type-resource.d.ts +4 -1
  93. package/dist/resources/field-type-resource.js +22 -23
  94. package/dist/resources/field-type-resource.js.map +1 -1
  95. package/dist/resources/file-resource.d.ts +5 -9
  96. package/dist/resources/file-resource.js +6 -11
  97. package/dist/resources/file-resource.js.map +1 -1
  98. package/dist/resources/folder-resource.d.ts +29 -32
  99. package/dist/resources/folder-resource.js +59 -78
  100. package/dist/resources/folder-resource.js.map +1 -1
  101. package/dist/resources/graph-model-resource.d.ts +4 -1
  102. package/dist/resources/graph-model-resource.js +11 -4
  103. package/dist/resources/graph-model-resource.js.map +1 -1
  104. package/dist/resources/graph-view-resource.d.ts +5 -2
  105. package/dist/resources/graph-view-resource.js +7 -3
  106. package/dist/resources/graph-view-resource.js.map +1 -1
  107. package/dist/resources/link-type-resource.d.ts +5 -2
  108. package/dist/resources/link-type-resource.js +5 -2
  109. package/dist/resources/link-type-resource.js.map +1 -1
  110. package/dist/resources/report-resource.d.ts +6 -7
  111. package/dist/resources/report-resource.js +14 -23
  112. package/dist/resources/report-resource.js.map +1 -1
  113. package/dist/resources/resource-object.d.ts +94 -8
  114. package/dist/resources/resource-object.js +212 -109
  115. package/dist/resources/resource-object.js.map +1 -1
  116. package/dist/resources/template-resource.d.ts +7 -3
  117. package/dist/resources/template-resource.js +10 -3
  118. package/dist/resources/template-resource.js.map +1 -1
  119. package/dist/resources/workflow-resource.d.ts +5 -2
  120. package/dist/resources/workflow-resource.js +18 -22
  121. package/dist/resources/workflow-resource.js.map +1 -1
  122. package/dist/utils/card-utils.d.ts +2 -2
  123. package/dist/utils/card-utils.js +1 -1
  124. package/dist/utils/clingo-fact-builder.d.ts +25 -14
  125. package/dist/utils/clingo-fact-builder.js +27 -5
  126. package/dist/utils/clingo-fact-builder.js.map +1 -1
  127. package/dist/utils/clingo-facts.js +3 -4
  128. package/dist/utils/clingo-facts.js.map +1 -1
  129. package/dist/utils/configuration-logger.d.ts +91 -0
  130. package/dist/utils/configuration-logger.js +151 -0
  131. package/dist/utils/configuration-logger.js.map +1 -0
  132. package/dist/utils/constants.d.ts +1 -1
  133. package/dist/utils/constants.js +5 -3
  134. package/dist/utils/constants.js.map +1 -1
  135. package/dist/utils/resource-utils.d.ts +1 -0
  136. package/dist/utils/resource-utils.js +2 -1
  137. package/dist/utils/resource-utils.js.map +1 -1
  138. package/package.json +9 -9
  139. package/src/card-metadata-updater.ts +6 -2
  140. package/src/command-handler.ts +39 -12
  141. package/src/command-manager.ts +33 -21
  142. package/src/commands/create.ts +43 -78
  143. package/src/commands/export.ts +63 -52
  144. package/src/commands/fetch.ts +143 -34
  145. package/src/commands/import.ts +37 -15
  146. package/src/commands/move.ts +0 -1
  147. package/src/commands/remove.ts +20 -7
  148. package/src/commands/rename.ts +58 -149
  149. package/src/commands/show.ts +123 -80
  150. package/src/commands/transition.ts +26 -28
  151. package/src/commands/update.ts +25 -22
  152. package/src/commands/validate.ts +104 -67
  153. package/src/containers/card-container.ts +37 -5
  154. package/src/containers/project/calculation-engine.ts +61 -93
  155. package/src/containers/project/project-paths.ts +29 -13
  156. package/src/containers/project/resource-cache.ts +651 -0
  157. package/src/containers/project/resource-handler.ts +265 -0
  158. package/src/containers/project.ts +250 -527
  159. package/src/containers/template.ts +28 -23
  160. package/src/interfaces/command-options.ts +3 -1
  161. package/src/interfaces/folder-content-interfaces.ts +7 -6
  162. package/src/interfaces/project-interfaces.ts +12 -11
  163. package/src/interfaces/resource-interfaces.ts +18 -3
  164. package/src/macros/graph/index.ts +26 -47
  165. package/src/macros/index.ts +2 -2
  166. package/src/macros/report/index.ts +3 -9
  167. package/src/module-manager.ts +74 -17
  168. package/src/project-settings.ts +96 -14
  169. package/src/resources/calculation-resource.ts +18 -18
  170. package/src/resources/card-type-resource.ts +50 -50
  171. package/src/resources/create-defaults.ts +3 -2
  172. package/src/resources/field-type-resource.ts +41 -55
  173. package/src/resources/file-resource.ts +10 -36
  174. package/src/resources/folder-resource.ts +69 -120
  175. package/src/resources/graph-model-resource.ts +20 -22
  176. package/src/resources/graph-view-resource.ts +15 -17
  177. package/src/resources/link-type-resource.ts +10 -13
  178. package/src/resources/report-resource.ts +21 -43
  179. package/src/resources/resource-object.ts +263 -149
  180. package/src/resources/template-resource.ts +17 -16
  181. package/src/resources/workflow-resource.ts +25 -44
  182. package/src/utils/card-utils.ts +2 -2
  183. package/src/utils/clingo-fact-builder.ts +28 -16
  184. package/src/utils/clingo-facts.ts +3 -4
  185. package/src/utils/configuration-logger.ts +206 -0
  186. package/src/utils/constants.ts +5 -3
  187. package/src/utils/resource-utils.ts +2 -1
  188. package/dist/containers/project/resource-collector.d.ts +0 -110
  189. package/dist/containers/project/resource-collector.js +0 -344
  190. package/dist/containers/project/resource-collector.js.map +0 -1
  191. package/src/containers/project/resource-collector.ts +0 -404
@@ -14,41 +14,43 @@
14
14
 
15
15
  // node
16
16
  import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';
17
- import { basename, join, sep } from 'node:path';
18
-
19
- import { hasCode } from '../utils/error-utils.js';
17
+ import { basename, join } from 'node:path';
20
18
 
21
19
  import { ArrayHandler } from './array-handler.js';
20
+ import { deleteFile, pathExists } from '../utils/file-utils.js';
21
+ import { getChildLogger } from '../utils/log-utils.js';
22
+ import {
23
+ readJsonFile,
24
+ readJsonFileSync,
25
+ writeJsonFile,
26
+ } from '../utils/json.js';
27
+ import {
28
+ resourceName,
29
+ resourceNameToPath,
30
+ resourceNameToString,
31
+ type ResourceName,
32
+ } from '../utils/resource-utils.js';
33
+ import { ResourcesFrom } from '../containers/project.js';
34
+
22
35
  import type {
23
36
  Card,
24
- Resource,
25
37
  ResourceFolderType,
26
38
  } from '../interfaces/project-interfaces.js';
27
39
  import type { Logger } from 'pino';
28
40
  import type { Project } from '../containers/project.js';
29
- import { ResourcesFrom } from '../containers/project/resource-collector.js';
30
41
  import type {
31
42
  ResourceBaseMetadata,
32
43
  UpdateKey,
33
44
  } from '../interfaces/resource-interfaces.js';
34
45
  import type { Validate } from '../commands/validate.js';
46
+
35
47
  import {
36
- resourceName,
37
- resourceNameToPath,
38
- resourceNameToString,
39
- type ResourceName,
40
- } from '../utils/resource-utils.js';
41
- import { getChildLogger } from '../utils/log-utils.js';
42
- import { deleteFile, pathExists } from '../utils/file-utils.js';
43
- import {
44
- readJsonFile,
45
- readJsonFileSync,
46
- writeJsonFile,
47
- } from '../utils/json.js';
48
+ ConfigurationLogger,
49
+ ConfigurationOperation,
50
+ } from '../utils/configuration-logger.js';
48
51
 
49
52
  // Possible operations to perform when doing "update"
50
53
  export type UpdateOperations = 'add' | 'change' | 'rank' | 'remove';
51
- //| 'elementTypeChange';
52
54
 
53
55
  // Base class for update operations.
54
56
  type BaseOperation<T> = {
@@ -117,7 +119,7 @@ export abstract class AbstractResource<
117
119
  protected abstract delete(): Promise<void>; // delete from disk
118
120
  protected abstract read(): Promise<void>; // read content from disk (replaces existing content, if any)
119
121
  protected abstract rename(newName: ResourceName): Promise<void>; // change name of the resource and filename; same as update('name', ...)
120
- protected abstract show(): Promise<ShowReturnType<T, U>>; // return the content as JSON
122
+ protected abstract show(): ShowReturnType<T, U>; // return the content as JSON
121
123
  protected abstract update<Type, K extends string>(
122
124
  updateKey: UpdateKey<K>,
123
125
  operation: Operation<Type>,
@@ -136,8 +138,6 @@ export abstract class ResourceObject<
136
138
  T extends ResourceBaseMetadata,
137
139
  U,
138
140
  > extends AbstractResource<T, U> {
139
- // TODO: Remove when INTDEV-1048 is implemented, since caching is done at object level
140
- private cache: Map<string, JSON>;
141
141
  private static validateInstancePromise?: Promise<ValidateInstance>;
142
142
 
143
143
  protected content: T;
@@ -153,6 +153,12 @@ export abstract class ResourceObject<
153
153
  */
154
154
  public fileName: string = '';
155
155
 
156
+ /**
157
+ * Constructs a ResourceObject instance
158
+ * @param project Project where this resource is
159
+ * @param resourceName Name for the resource
160
+ * @param type Type of resource
161
+ */
156
162
  constructor(
157
163
  protected project: Project,
158
164
  protected resourceName: ResourceName,
@@ -161,12 +167,19 @@ export abstract class ResourceObject<
161
167
  super();
162
168
  this.moduleResource =
163
169
  this.resourceName.prefix !== this.project.projectPrefix;
164
- this.cache = this.project.resourceCache;
165
170
  this.type = type;
166
171
  this.logger = this.getLogger(this.getType);
167
172
  this.content = { name: '' } as T; // not found if name is empty
168
173
  }
169
174
 
175
+ // Check if resource already exists. First checks the cache, then filesystem.
176
+ private exists(): boolean {
177
+ const name = resourceNameToString(this.resourceName);
178
+ const existsInCache = this.project.resources.exists(name);
179
+ return existsInCache || pathExists(this.fileName);
180
+ }
181
+
182
+ // Gets Validate command instance.
170
183
  private static async getValidate(): Promise<ValidateInstance> {
171
184
  // a bit hacky solution to avoid circular dependencies
172
185
  if (!this.validateInstancePromise) {
@@ -177,11 +190,14 @@ export abstract class ResourceObject<
177
190
  return this.validateInstancePromise;
178
191
  }
179
192
 
180
- private resourceObjectToResource(): Resource {
181
- return {
182
- name: this.data ? this.data.name : '',
183
- path: this.fileName.substring(0, this.fileName.lastIndexOf(sep)),
184
- };
193
+ // Gets handlebar files.
194
+ private async reportHandlerBarFiles(from: ResourcesFrom = ResourcesFrom.all) {
195
+ const reports = this.project.resources.reports(from);
196
+ const handleBarFiles: string[] = [];
197
+ for (const report of reports) {
198
+ handleBarFiles.push(...(await report.handleBarFiles()));
199
+ }
200
+ return handleBarFiles;
185
201
  }
186
202
 
187
203
  // Type of resource.
@@ -189,19 +205,13 @@ export abstract class ResourceObject<
189
205
  return this.type;
190
206
  }
191
207
 
192
- private toCache() {
193
- this.cache.set(
194
- resourceNameToString(this.resourceName),
195
- this.content as unknown as JSON,
196
- );
197
- }
198
-
199
208
  /**
200
- * Checks if resource exists
209
+ * Checks if resource exists.
210
+ * This should only throw, if someone creates resources directly; ie. not through the cache
201
211
  * @throws if resource does not exist
202
212
  */
203
213
  protected assertResourceExists() {
204
- if (!pathExists(this.fileName)) {
214
+ if (!this.exists()) {
205
215
  const resourceType = `${this.type[0].toUpperCase()}${this.type.slice(1, this.type.length - 1)}`;
206
216
  const name = resourceNameToString(this.resourceName);
207
217
  throw new Error(
@@ -210,39 +220,30 @@ export abstract class ResourceObject<
210
220
  }
211
221
  }
212
222
 
223
+ /**
224
+ * Calculate; empty implementation.
225
+ */
213
226
  protected async calculate() {}
214
227
 
215
- // Calculations that use this resource.
228
+ /**
229
+ * Calculations that use this resource.
230
+ * @throws if accessing calculations files failed
231
+ */
216
232
  protected async calculations(): Promise<string[]> {
217
233
  const references: string[] = [];
218
234
  const resourceName = resourceNameToString(this.resourceName);
219
- for (const calculation of await this.project.calculations(
220
- ResourcesFrom.all,
221
- )) {
222
- const fileNameWithExtension = calculation.name.endsWith('.lp')
223
- ? calculation.name
224
- : calculation.name + '.lp';
225
- const filename = join(calculation.path, basename(fileNameWithExtension));
226
- try {
227
- const content = await readFile(filename, 'utf-8');
228
- if (content.includes(resourceName)) {
229
- references.push(calculation.name);
230
- }
231
- } catch (error) {
232
- // Skip files that don't exist (they may have been renamed or deleted)
233
- if (hasCode(error) && error.code === 'ENOENT') {
234
- this.logger.warn(`Skipping non-existent file: ${filename}`);
235
- continue;
236
- }
237
- throw new Error(
238
- `Failed to process file ${filename}: ${(error as Error).message}`,
239
- );
235
+ for (const calculation of this.project.resources.calculations()) {
236
+ const content = calculation.contentData();
237
+ if (content.calculation && content.calculation.includes(resourceName)) {
238
+ references.push(calculation.data!.name);
240
239
  }
241
240
  }
242
241
  return references;
243
242
  }
244
243
 
245
- // Cards from project.
244
+ /**
245
+ * Cards from project.
246
+ */
246
247
  protected cards(): Card[] {
247
248
  return [
248
249
  ...this.project.cards(undefined),
@@ -264,9 +265,15 @@ export abstract class ResourceObject<
264
265
  ] as unknown as JSON;
265
266
  }
266
267
 
267
- // Creates resource.
268
+ /**
269
+ * Creates resource.
270
+ * @param newContent Content for resource.
271
+ * @throws when resource already exists in the project.
272
+ */
268
273
  protected async create(newContent?: T) {
269
- if (pathExists(this.fileName)) {
274
+ this.validateResourceIdentifier();
275
+
276
+ if (this.exists()) {
270
277
  throw new Error(
271
278
  `Resource '${this.resourceName.identifier}' already exists in the project`,
272
279
  );
@@ -279,13 +286,17 @@ export abstract class ResourceObject<
279
286
  this.resourceFolder = this.project.paths.resourcePath(
280
287
  this.resourceName.type as ResourceFolderType,
281
288
  );
289
+ this.fileName = join(
290
+ this.resourceFolder,
291
+ this.resourceName.identifier + '.json',
292
+ );
282
293
  }
283
294
 
284
295
  const validator = await ResourceObject.getValidate();
285
296
  const validName = validator.validResourceName(
286
297
  this.resourceType(),
287
298
  resourceNameToString(this.resourceName),
288
- await this.project.projectPrefixes(),
299
+ this.project.projectPrefixes(),
289
300
  );
290
301
 
291
302
  let validContent = {} as T;
@@ -300,19 +311,27 @@ export abstract class ResourceObject<
300
311
  this.content = validContent;
301
312
  await this.write();
302
313
 
303
- // Notify project & collector
304
- this.project.addResource(
305
- this.resourceObjectToResource(),
306
- this.content as unknown as JSON,
307
- );
314
+ const resourceString = resourceNameToString(this.resourceName);
315
+ this.project.resources.add(resourceString, this);
316
+
317
+ // Log resource creation to migration log
318
+ await this.logResourceOperation('create');
308
319
  }
309
320
 
321
+ /**
322
+ * Gets a logger instance.
323
+ * @param loggerName
324
+ * @returns logger instance
325
+ */
310
326
  protected getLogger(loggerName: string): Logger {
311
327
  return getChildLogger({
312
328
  module: loggerName,
313
329
  });
314
330
  }
315
331
 
332
+ /**
333
+ * Returns type of this resource.
334
+ */
316
335
  protected get getType(): string {
317
336
  return this.type;
318
337
  }
@@ -323,6 +342,7 @@ export abstract class ResourceObject<
323
342
  * @param arrayName Name of the array, for error messages.
324
343
  * @param array Array to be updated.
325
344
  * @returns Changed array after the operation.
345
+ * @throws when operation cannot be done.
326
346
  */
327
347
  protected handleArray<Type>(
328
348
  operation: Operation<Type>,
@@ -347,6 +367,7 @@ export abstract class ResourceObject<
347
367
  * Updates scalar value. The only accepted operation is 'change'
348
368
  * @param operation Operation to perform on scalar.
349
369
  * @returns What the scalar should be changed to.
370
+ * @throws when operation cannot be done
350
371
  */
351
372
  protected handleScalar<Type>(operation: Operation<Type>): Type {
352
373
  if (
@@ -359,7 +380,9 @@ export abstract class ResourceObject<
359
380
  return operation.to;
360
381
  }
361
382
 
362
- // Initialize the resource.
383
+ /**
384
+ * Initialize the resource.
385
+ */
363
386
  protected initialize() {
364
387
  if (this.resourceName.type === '') {
365
388
  this.resourceName.type = this.type;
@@ -379,26 +402,77 @@ export abstract class ResourceObject<
379
402
  : this.project.paths.resourcePath(this.type);
380
403
  this.fileName = resourceNameToPath(this.project, this.resourceName);
381
404
  }
382
- // Read from cache, if entry exists...
383
- if (this.cache.has(resourceNameToString(this.resourceName))) {
384
- this.content = this.cache.get(
385
- resourceNameToString(this.resourceName),
386
- ) as unknown as T;
387
- return;
405
+ // Only load content from disk if resource exists in the cache registry
406
+ if (
407
+ this.project.resources.exists(resourceNameToString(this.resourceName))
408
+ ) {
409
+ try {
410
+ this.content = readJsonFileSync(this.fileName);
411
+ } catch {
412
+ this.logger.debug(
413
+ `Initializing resource '${resourceNameToString(this.resourceName)}' failed: failed to read file '${this.fileName}'`,
414
+ );
415
+ }
388
416
  }
389
- //... otherwise read from disk and add to cache
390
- try {
391
- this.content = readJsonFileSync(this.fileName);
392
- this.toCache();
393
- } catch {
394
- // do nothing, it is possible that file has not been created yet.
395
- this.logger.info(
396
- `Initializing resource '${resourceNameToString(this.resourceName)}' failed: failed to read file '${this.fileName}'`,
397
- );
417
+ }
418
+
419
+ // Log details
420
+ protected async logResourceOperation<Type>(
421
+ operationType: 'create' | 'delete' | 'update' | 'rename',
422
+ op?: Operation<Type>,
423
+ key?: string,
424
+ ): Promise<void> {
425
+ let configOperation: ConfigurationOperation;
426
+ const target = resourceNameToString(this.resourceName);
427
+ const parameters: Record<string, unknown> = { type: this.type };
428
+
429
+ switch (operationType) {
430
+ case 'create':
431
+ configOperation = ConfigurationOperation.RESOURCE_CREATE;
432
+ break;
433
+ case 'delete':
434
+ configOperation = ConfigurationOperation.RESOURCE_DELETE;
435
+ break;
436
+ case 'update':
437
+ configOperation = ConfigurationOperation.RESOURCE_UPDATE;
438
+ if (op) {
439
+ parameters.operation = op.name;
440
+ }
441
+ if (key) {
442
+ parameters.key = key;
443
+ }
444
+ break;
445
+ case 'rename':
446
+ configOperation = ConfigurationOperation.RESOURCE_RENAME;
447
+ if (op && op.name === 'change') {
448
+ const changeOp = op as ChangeOperation<string>;
449
+ parameters.oldName = changeOp.target;
450
+ parameters.newName = changeOp.to;
451
+ }
452
+ break;
453
+ default:
454
+ throw new Error(`Unknown operation type: ${operationType}`);
398
455
  }
456
+
457
+ await ConfigurationLogger.log(
458
+ this.project.basePath,
459
+ configOperation,
460
+ target,
461
+ {
462
+ parameters,
463
+ },
464
+ );
465
+
466
+ this.logger.info(`Configuration: ${configOperation} - ${target}`);
399
467
  }
400
468
 
401
- // Called after inherited class has finished 'update' operation.
469
+ /**
470
+ * Called after inherited class has finished 'update' operation.
471
+ * @param content New content for resource
472
+ * @param updateKey Which property to change
473
+ * @param op What kind of operation is performed to updateKey
474
+ * @throws if validation fails after the update
475
+ */
402
476
  protected async postUpdate<Type, K extends string>(
403
477
  content: T,
404
478
  updateKey: UpdateKey<K>,
@@ -433,38 +507,30 @@ export abstract class ResourceObject<
433
507
 
434
508
  this.content = content;
435
509
  await this.write();
436
- }
437
510
 
438
- // Update resource; the base class makes some checks only.
439
- protected async update<Type, K extends string>(
440
- key: UpdateKey<K>,
441
- _op: Operation<Type>,
442
- ): Promise<void> {
443
- const content = this.data;
444
- if (!content) {
445
- throw new Error(
446
- `Resource '${resourceNameToString(this.resourceName)}' does not exist`,
447
- );
448
- }
449
- if (this.moduleResource) {
450
- throw new Error(`Cannot update module resources`);
451
- }
452
- if (key.key === '' || key === undefined) {
453
- throw new Error(`Cannot update empty key`);
454
- }
511
+ // Log resource update to migration log
512
+ await this.logResourceOperation('update', op, updateKey.key);
455
513
  }
456
514
 
457
- // Reads content from file to memory.
515
+ /**
516
+ * Reads content from file to memory.
517
+ */
458
518
  protected async read() {
459
519
  this.content = await readJsonFile(this.fileName);
460
520
  }
461
521
 
462
- // Renames resource.
522
+ /**
523
+ * Renames resource.
524
+ * @param newName New name for the resource.
525
+ * @throws if trying to rename module resource, or
526
+ * if resource does not exist,
527
+ * if trying to rename so that type changes
528
+ */
463
529
  protected async rename(newName: ResourceName) {
464
530
  if (this.moduleResource) {
465
531
  throw new Error(`Cannot rename module resources`);
466
532
  }
467
- if (!pathExists(this.fileName)) {
533
+ if (!this.exists()) {
468
534
  throw new Error(
469
535
  `Resource '${this.resourceName.identifier}' does not exist`,
470
536
  );
@@ -479,25 +545,78 @@ export abstract class ResourceObject<
479
545
  validator.validResourceName(
480
546
  this.resourceType(),
481
547
  resourceNameToString(newName),
482
- await this.project.projectPrefixes(),
548
+ this.project.projectPrefixes(),
483
549
  );
484
550
  const newFilename = join(
485
551
  this.project.paths.resourcePath(newName.type as ResourceFolderType),
486
552
  newName.identifier + '.json',
487
553
  );
554
+
555
+ const oldName = resourceNameToString(this.resourceName);
488
556
  await rename(this.fileName, newFilename);
489
557
 
490
- this.cache.delete(resourceNameToString(this.resourceName));
491
558
  this.fileName = newFilename;
492
559
  this.content.name = resourceNameToString(newName);
560
+ const newNameString = this.content.name;
493
561
  this.resourceName = newName;
494
- this.toCache();
562
+
563
+ this.project.resources.rename(oldName, newNameString);
564
+
565
+ // Log resource rename to migration log
566
+ await this.logResourceOperation('rename', {
567
+ name: 'change',
568
+ target: oldName,
569
+ to: newNameString,
570
+ } as ChangeOperation<string>);
571
+ }
572
+
573
+ /**
574
+ * Update resource; the base class makes some checks only.
575
+ * @template type Resource type
576
+ * @template K Resource key
577
+ * @throws if resource does not exist, or
578
+ * if trying to update module content, or
579
+ * if key is empty
580
+ */
581
+ protected async update<Type, K extends string>(
582
+ key: UpdateKey<K>,
583
+ _op: Operation<Type>,
584
+ ): Promise<void> {
585
+ const content = this.data;
586
+ if (!content) {
587
+ throw new Error(
588
+ `Resource '${resourceNameToString(this.resourceName)}' does not exist`,
589
+ );
590
+ }
591
+ if (this.moduleResource) {
592
+ throw new Error(`Cannot update module resources`);
593
+ }
594
+ if (key.key === '' || key === undefined) {
595
+ throw new Error(`Cannot update empty key`);
596
+ }
597
+ }
598
+
599
+ /**
600
+ * Validates resource identifier to prevent filesystem operations with invalid names
601
+ * todo: To Validate?
602
+ */
603
+ protected validateResourceIdentifier() {
604
+ if (!this.moduleResource && this.resourceName.identifier) {
605
+ const identifier = this.resourceName.identifier;
606
+ if (!/^[a-zA-Z0-9._-]+$/.test(identifier)) {
607
+ throw new Error(
608
+ `Resource identifier must follow naming rules. Identifier '${identifier}' is invalid`,
609
+ );
610
+ }
611
+ }
495
612
  }
496
613
 
497
614
  /**
498
615
  * Update calculation files.
499
616
  * @param from Resource name to update
500
617
  * @param to New name for resource
618
+ * @throws if 'from' or 'to' is empty string, or
619
+ * if there was error accessing calculation files.
501
620
  */
502
621
  protected async updateCalculations(from: string, to: string) {
503
622
  if (!from.trim() || !to.trim()) {
@@ -506,41 +625,16 @@ export abstract class ResourceObject<
506
625
  );
507
626
  }
508
627
 
509
- const calculations = await this.project.calculations(
628
+ const calculations = this.project.resources.calculations(
510
629
  ResourcesFrom.localOnly,
511
630
  );
512
631
 
513
632
  await Promise.all(
514
633
  calculations.map(async (calculation) => {
515
- if (!calculation.path) {
516
- throw new Error(
517
- `Calculation file's '${calculation.name}' path is not defined`,
518
- );
519
- }
520
-
521
- const base = basename(calculation.name);
522
- const fileNameWithExtension = base.endsWith('.lp')
523
- ? base
524
- : base + '.lp';
525
- const filename = join(calculation.path, fileNameWithExtension);
526
-
527
- try {
528
- const content = await readFile(filename, 'utf-8');
529
- const updatedContent = content.replaceAll(from, to);
530
- await writeFile(filename, updatedContent);
531
- } catch (error) {
532
- if (hasCode(error) && error.code === 'ENOENT') {
533
- // Skip files that don't exist (they may have been renamed or deleted)
534
- this.getLogger(this.getType).warn(
535
- `Skipping non-existent file: ${filename}`,
536
- );
537
- return;
538
- }
539
- if (error instanceof Error) {
540
- throw new Error(
541
- `Failed to process file while updating calculation ${filename}: ${error.message}`,
542
- );
543
- }
634
+ const content = calculation.contentData();
635
+ if (content.calculation) {
636
+ const updatedContent = content.calculation.replaceAll(from, to);
637
+ await calculation.updateFile('calculation.lp', updatedContent);
544
638
  }
545
639
  }),
546
640
  );
@@ -551,6 +645,7 @@ export abstract class ResourceObject<
551
645
  * @param from Resource name to update
552
646
  * @param to New name for resource
553
647
  * @param handleBarFiles Optional. List of handlebar files. If omitted, affects all handlebar files in the project.
648
+ * @throws if 'from' or 'to' is empty string
554
649
  */
555
650
  protected async updateHandleBars(
556
651
  from: string,
@@ -564,7 +659,7 @@ export abstract class ResourceObject<
564
659
  }
565
660
 
566
661
  if (!handleBarFiles) {
567
- handleBarFiles = await this.project.reportHandlerBarFiles(
662
+ handleBarFiles = await this.reportHandlerBarFiles(
568
663
  ResourcesFrom.localOnly,
569
664
  );
570
665
  }
@@ -579,10 +674,14 @@ export abstract class ResourceObject<
579
674
  );
580
675
  }
581
676
 
582
- // Check if there are references to the resource in the card content.
583
- // @note that this needs to be async, since inherited classes need to async operations
677
+ /**
678
+ * Check if there are references to the resource in the card content.
679
+ * @note that this needs to be async, since inherited classes need to async operations
680
+ * @param cards cards to check
681
+ * @throws if resource does not exist
682
+ */
584
683
  protected async usage(cards?: Card[]): Promise<string[]> {
585
- if (!pathExists(this.fileName)) {
684
+ if (!this.exists()) {
586
685
  throw new Error(
587
686
  `Resource '${this.resourceName.identifier}' does not exist in the project`,
588
687
  );
@@ -596,17 +695,25 @@ export abstract class ResourceObject<
596
695
  .map((card) => card.key);
597
696
  }
598
697
 
698
+ /**
699
+ * Checks if resource name is valid.
700
+ * @param newName New name for resource.
701
+ * @returns valid name
702
+ */
599
703
  protected async validName(newName: ResourceName) {
600
704
  const validator = await ResourceObject.getValidate();
601
705
  const validName = validator.validResourceName(
602
706
  this.resourceType(),
603
707
  resourceNameToString(newName),
604
- await this.project.projectPrefixes(),
708
+ this.project.projectPrefixes(),
605
709
  );
606
710
  return validName;
607
711
  }
608
712
 
609
- // Write the content from memory to disk.
713
+ /**
714
+ * Write the content from memory to disk.
715
+ * @throws if trying to write a module resource.
716
+ */
610
717
  protected async write() {
611
718
  if (this.moduleResource) {
612
719
  throw new Error(`Cannot change module resources`);
@@ -624,15 +731,16 @@ export abstract class ResourceObject<
624
731
  // Check if "name" has changed. Changing "name" means renaming the file.
625
732
  const nameInContent = resourceName(this.content.name).identifier + '.json';
626
733
  const currentFileName = basename(this.fileName);
734
+ const resourceString = resourceNameToString(this.resourceName);
627
735
 
628
736
  if (nameInContent !== currentFileName) {
629
737
  const newFileName = join(this.resourceFolder, nameInContent);
630
738
  await rename(this.fileName, newFileName);
631
739
  this.fileName = newFileName;
740
+ this.resourceName = resourceName(this.content.name);
741
+ this.project.resources.rename(resourceString, this.content.name);
632
742
  }
633
-
634
743
  await writeJsonFile(this.fileName, this.content);
635
- this.toCache();
636
744
  }
637
745
 
638
746
  /**
@@ -646,7 +754,9 @@ export abstract class ResourceObject<
646
754
 
647
755
  /**
648
756
  * Deletes the file and removes the resource from project.
649
- * @throws if resource is a module resource or does not exist or is used by other resources.
757
+ * @throws if resource is a module resource, or
758
+ * if resource does not exist, or
759
+ * if resource is used by other resources.
650
760
  */
651
761
  public async delete() {
652
762
  if (this.moduleResource) {
@@ -657,7 +767,7 @@ export abstract class ResourceObject<
657
767
  if (!this.fileName.endsWith('.json')) {
658
768
  this.fileName += '.json';
659
769
  }
660
- if (!pathExists(this.fileName)) {
770
+ if (!this.exists()) {
661
771
  throw new Error(
662
772
  `Resource '${this.resourceName.identifier}' does not exist in the project`,
663
773
  );
@@ -669,13 +779,17 @@ export abstract class ResourceObject<
669
779
  );
670
780
  }
671
781
  await deleteFile(this.fileName);
672
- this.project.removeResource(this.resourceObjectToResource());
782
+ this.project.resources.remove(resourceNameToString(this.resourceName));
673
783
  this.fileName = '';
784
+
785
+ // Log resource deletion to migration log
786
+ await this.logResourceOperation('delete');
674
787
  }
675
788
 
676
789
  /**
677
790
  * Validates the content of the resource.
678
791
  * @param content Content to be validated.
792
+ * @throws if content is invalid.
679
793
  */
680
794
  public async validate(content?: object) {
681
795
  const validator = await ResourceObject.getValidate();