@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
@@ -12,17 +12,29 @@
12
12
  */
13
13
 
14
14
  import { ActionGuard } from '../permissions/action-guard.js';
15
- import type {
16
- CardType,
17
- Workflow,
18
- WorkflowState,
19
- } from '../interfaces/resource-interfaces.js';
20
15
  import { CardMetadataUpdater } from '../card-metadata-updater.js';
21
16
  import type { Project } from '../containers/project.js';
17
+ import type { WorkflowState } from '../interfaces/resource-interfaces.js';
22
18
 
19
+ /**
20
+ * Handles transitions.
21
+ */
23
22
  export class Transition {
23
+ /**
24
+ * Creates an instance of Transition command.
25
+ * @param project Project to use.
26
+ */
24
27
  constructor(private project: Project) {}
25
28
 
29
+ // Wrapper to run onTransition query.
30
+ private async transitionChangesQuery(cardKey: string, transition: string) {
31
+ if (!cardKey || !transition) return undefined;
32
+ return this.project.calculationEngine.runQuery('onTransition', 'localApp', {
33
+ cardKey,
34
+ transition,
35
+ });
36
+ }
37
+
26
38
  /**
27
39
  * Transitions a card from its current state to a new state.
28
40
  * @param cardKey card key
@@ -31,23 +43,18 @@ export class Transition {
31
43
  public async cardTransition(cardKey: string, transition: WorkflowState) {
32
44
  const card = this.project.findCard(cardKey);
33
45
 
34
- // Card type
35
- const cardType = this.project.resource<CardType>(
36
- card.metadata?.cardType || '',
37
- );
38
- if (cardType === undefined) {
39
- throw new Error(
40
- `Card's card type '${card.metadata?.cardType}' does not exist in the project`,
41
- );
46
+ if (!card.metadata?.cardType) {
47
+ throw new Error(`Card does not have card type`);
42
48
  }
49
+ // Card type
50
+ const cardType = this.project.resources
51
+ .byType(card.metadata?.cardType, 'cardTypes')
52
+ .show();
43
53
 
44
54
  // Workflow
45
- const workflow = await this.project.resource<Workflow>(cardType.workflow);
46
- if (workflow === undefined) {
47
- throw new Error(
48
- `Card's workflow '${cardType.workflow}' does not exist in the project`,
49
- );
50
- }
55
+ const workflow = this.project.resources
56
+ .byType(cardType.workflow, 'workflows')
57
+ .show();
51
58
 
52
59
  // Check that the state transition can be made "from".
53
60
  const foundFrom = workflow.transitions.find(
@@ -108,13 +115,4 @@ export class Transition {
108
115
  .catch((error) => console.error(error));
109
116
  }
110
117
  }
111
-
112
- // Wrapper to run onTransition query.
113
- private async transitionChangesQuery(cardKey: string, transition: string) {
114
- if (!cardKey || !transition) return undefined;
115
- return this.project.calculationEngine.runQuery('onTransition', 'localApp', {
116
- cardKey,
117
- transition,
118
- });
119
- }
120
118
  }
@@ -19,16 +19,39 @@ import type {
19
19
  RemoveOperation,
20
20
  UpdateOperations,
21
21
  } from '../resources/resource-object.js';
22
- import { Project } from '../containers/project.js';
23
- import { resourceName } from '../utils/resource-utils.js';
22
+ import type { Project } from '../containers/project.js';
24
23
  import type { UpdateKey } from '../interfaces/resource-interfaces.js';
25
24
 
26
25
  /**
27
26
  * Class that handles 'update' commands.
28
27
  */
29
28
  export class Update {
29
+ /**
30
+ * Creates an instance of Update command.
31
+ * @param project Project to use.
32
+ */
30
33
  constructor(private project: Project) {}
31
34
 
35
+ /**
36
+ * Update single resource property
37
+ * This is similar to updateValue, but allows the operation to be fully specified
38
+ * @param name Name of the resource to operate on.
39
+ * @param updateKey Property to change in resource or in resource content.
40
+ * @param operation The full operation object
41
+ * @template Type Type of the target of the operation
42
+ * @template T Type of operation ('add', 'remove', 'change', 'rank')
43
+ * @template K Type of the key to change
44
+ */
45
+ public async applyResourceOperation<
46
+ Type,
47
+ T extends UpdateOperations,
48
+ K extends string,
49
+ >(name: string, updateKey: UpdateKey<K>, operation: OperationFor<Type, T>) {
50
+ const type = this.project.resources.extractType(name);
51
+ const resource = this.project.resources.byType(name, type);
52
+ await resource?.update(updateKey, operation);
53
+ }
54
+
32
55
  /**
33
56
  * Updates single resource property.
34
57
  * @param name Name of the resource to operate on.
@@ -95,24 +118,4 @@ export class Update {
95
118
  await this.applyResourceOperation(name, { key: parsedKey }, op);
96
119
  }
97
120
  }
98
-
99
- /**
100
- * Update single resource property
101
- * This is similar to updateValue, but allows the operation to be fully specified
102
- * @param name Name of the resource to operate on.
103
- * @param updateKey Property to change in resource or in resource content.
104
- * @param operation The full operation object
105
- * @template Type Type of the target of the operation
106
- * @template T Type of operation ('add', 'remove', 'change', 'rank')
107
- * @template K Type of the key to change
108
- */
109
- public async applyResourceOperation<
110
- Type,
111
- T extends UpdateOperations,
112
- K extends string,
113
- >(name: string, updateKey: UpdateKey<K>, operation: OperationFor<Type, T>) {
114
- const resource = Project.resourceObject(this.project, resourceName(name));
115
- await resource?.update(updateKey, operation);
116
- this.project.collectLocalResources();
117
- }
118
121
  }
@@ -12,7 +12,7 @@
12
12
  */
13
13
 
14
14
  // node
15
- import { type Dirent } from 'node:fs';
15
+ import type { Dirent } from 'node:fs';
16
16
  import { basename, dirname, extname, join, parse, resolve } from 'node:path';
17
17
  import { readdir } from 'node:fs/promises';
18
18
 
@@ -33,13 +33,14 @@ import type {
33
33
  CustomField,
34
34
  FieldType,
35
35
  ReportMetadata,
36
- ResourceContent,
36
+ AnyResourceContent,
37
37
  Workflow,
38
38
  } from '../interfaces/resource-interfaces.js';
39
39
  import { errorFunction } from '../utils/error-utils.js';
40
40
  import { isTemplateCard } from '../utils/card-utils.js';
41
+ import { isPredefinedField } from '../utils/constants.js';
41
42
  import { pathExists } from '../utils/file-utils.js';
42
- import { Project } from '../containers/project.js';
43
+ import type { Project } from '../containers/project.js';
43
44
  import { readJsonFile } from '../utils/json.js';
44
45
  import { type ResourceName, resourceName } from '../utils/resource-utils.js';
45
46
 
@@ -51,7 +52,7 @@ const SHORT_TEXT_MAX_LENGTH = 80;
51
52
 
52
53
  import * as EmailValidator from 'email-validator';
53
54
  import { evaluateMacros } from '../macros/index.js';
54
- const baseDir = import.meta.dirname;
55
+ const baseDir = import.meta.dirname ?? new URL('.', import.meta.url).pathname;
55
56
  const subFoldersToValidate = ['.cards', 'cardRoot'];
56
57
 
57
58
  export interface LengthProvider {
@@ -82,6 +83,9 @@ export class Validate {
82
83
  static dotSchemaSchemaId = '/dotSchema';
83
84
  static parameterSchemaFile = 'parameterSchema.json';
84
85
 
86
+ /**
87
+ * Creates an instance of Validate command.
88
+ */
85
89
  constructor() {
86
90
  Validate.baseFolder = pathExists(
87
91
  join(process.cwd(), '../../schema', 'cardTreeDirectorySchema.json'),
@@ -117,7 +121,7 @@ export class Validate {
117
121
  private checkResourceName(
118
122
  file: Dirent,
119
123
  content:
120
- | ResourceContent
124
+ | AnyResourceContent
121
125
  | CustomField
122
126
  | DotSchemaContent
123
127
  | ProjectSettings
@@ -133,7 +137,7 @@ export class Validate {
133
137
  file.name !== Validate.dotSchemaSchemaId &&
134
138
  file.name !== Validate.parameterSchemaFile
135
139
  ) {
136
- const namedContent = content as ResourceContent | ReportMetadata;
140
+ const namedContent = content as AnyResourceContent | ReportMetadata;
137
141
  if (!namedContent.name) {
138
142
  errors.push(
139
143
  `File '${file.name}' does not contain 'name' property. Cannot validate resource's 'name'.`,
@@ -167,20 +171,7 @@ export class Validate {
167
171
  return join(file.parentPath, file.name);
168
172
  }
169
173
 
170
- // Puts resource to a local cache if found and returns the resource.
171
- // If value is already cached, returns from cache.
172
- private getAndCacheResource<Type>(
173
- project: Project,
174
- cachedValues: Map<string, Type>,
175
- valueName: string,
176
- ): Type | undefined {
177
- const resource = project.resource<Type>(valueName);
178
- if (resource) {
179
- cachedValues.set(valueName, resource);
180
- }
181
- return resource;
182
- }
183
-
174
+ // Parses validator messages
184
175
  private parseValidatorMessage(errorObject: object[]): string {
185
176
  let parsedErrorMessage = '';
186
177
  // todo: get schema name here?
@@ -231,7 +222,7 @@ export class Validate {
231
222
  ): Promise<string[]> {
232
223
  const message: string[] = [];
233
224
  try {
234
- const prefixes = await project.projectPrefixes();
225
+ const prefixes = project.projectPrefixes();
235
226
  const files = await readdir(path, {
236
227
  withFileTypes: true,
237
228
  });
@@ -390,11 +381,13 @@ export class Validate {
390
381
  const errors: string[] = [];
391
382
  if (cardType && fieldArray) {
392
383
  const validationPromises = fieldArray.map(async (field) => {
393
- const fieldType = this.getAndCacheResource(
394
- project,
395
- this.validatedFieldTypes,
396
- field,
397
- );
384
+ let fieldType;
385
+ try {
386
+ fieldType = project.resources.byType(field, 'fieldTypes').show();
387
+ } catch {
388
+ fieldType = undefined;
389
+ }
390
+
398
391
  if (!fieldType) {
399
392
  return `Card type '${cardType.name}' has invalid reference to unknown ${nameOfArray} '${field}'`;
400
393
  }
@@ -471,7 +464,7 @@ export class Validate {
471
464
 
472
465
  /**
473
466
  * Validates that new identifier of a resource is according to naming convention.
474
- * @param identifier: resource identifier
467
+ * @param identifier Resource identifier
475
468
  * returns true if identifier is valid, and false otherwise.
476
469
  */
477
470
  public static isValidIdentifierName(identifier: string): boolean {
@@ -561,6 +554,7 @@ export class Validate {
561
554
  cards.push(...project.allTemplateCards());
562
555
 
563
556
  const cardIds = new Map<string, number>();
557
+ const allPrefixes = await project.projectPrefixes();
564
558
 
565
559
  for (const card of cards) {
566
560
  if (cardIds.has(card.key)) {
@@ -571,16 +565,19 @@ export class Validate {
571
565
 
572
566
  if (card.metadata) {
573
567
  if (!isTemplateCard(card)) {
574
- const validWorkflow = this.validateWorkflowState(project, card);
568
+ const validWorkflow = await this.validateWorkflowState(
569
+ project,
570
+ card,
571
+ );
575
572
  if (validWorkflow.length !== 0) {
576
573
  errorMsg.push(validWorkflow);
577
574
  }
578
575
  }
579
576
  }
580
-
581
577
  const validCustomFields = await this.validateCustomFields(
582
578
  project,
583
579
  card,
580
+ allPrefixes,
584
581
  );
585
582
  if (validCustomFields.length !== 0) {
586
583
  errorMsg.push(validCustomFields);
@@ -663,6 +660,9 @@ export class Validate {
663
660
  * @param resourceType Type of resource
664
661
  * @param name Name of resource
665
662
  * @param prefixes currently used project prefixes
663
+ * @throws when resource is not from given prefixes,
664
+ * or when actual resource does not match the type,
665
+ * or when identifier name is not valid
666
666
  * @returns resource name as valid resource name; throws in error cases.
667
667
  */
668
668
  public validResourceName(
@@ -709,11 +709,13 @@ export class Validate {
709
709
  * Validates that card's custom fields are according to schema and have correct data in them.
710
710
  * @param project currently used Project
711
711
  * @param card specific card
712
- * @returns string containing all validation errors
712
+ * @throws when card does not have metadata
713
+ * @returns validation errors, if any.
713
714
  */
714
715
  public async validateCustomFields(
715
716
  project: Project,
716
717
  card: Card,
718
+ prefixes: string[],
717
719
  ): Promise<string> {
718
720
  const validationErrors: string[] = [];
719
721
 
@@ -723,11 +725,14 @@ export class Validate {
723
725
  );
724
726
  }
725
727
 
726
- const cardType = this.getAndCacheResource(
727
- project,
728
- this.validatedCardTypes,
729
- card.metadata?.cardType,
730
- );
728
+ let cardType;
729
+ try {
730
+ cardType = project.resources
731
+ .byType(card.metadata?.cardType, 'cardTypes')
732
+ .show();
733
+ } catch {
734
+ cardType = undefined;
735
+ }
731
736
 
732
737
  if (!cardType) {
733
738
  validationErrors.push(
@@ -753,11 +758,19 @@ export class Validate {
753
758
  validationErrors.push(...fieldErrors);
754
759
 
755
760
  for (const field of cardType.customFields) {
756
- const found = await project.resourceExists('fieldTypes', field.name);
757
- if (!found) {
761
+ let fieldType;
762
+ try {
763
+ fieldType = await project.resources
764
+ .byType(field.name, 'fieldTypes')
765
+ .show();
766
+ } catch {
767
+ fieldType = undefined;
768
+ }
769
+ if (!fieldType) {
758
770
  validationErrors.push(
759
- `Custom field '${field.name}' from card type '${cardType.name}' not found from project`,
771
+ `In card '${card.key}' field '${field.name}' is missing from project\n`,
760
772
  );
773
+ continue;
761
774
  }
762
775
  if (field.isCalculated) {
763
776
  if (card.metadata[field.name] !== undefined) {
@@ -775,19 +788,6 @@ export class Validate {
775
788
  }
776
789
  }
777
790
 
778
- const fieldType = await this.getAndCacheResource(
779
- project,
780
- this.validatedFieldTypes,
781
- field.name,
782
- );
783
-
784
- if (!fieldType) {
785
- validationErrors.push(
786
- `In card '${card.key}' field '${field.name}' is missing from project\n`,
787
- );
788
- continue;
789
- }
790
-
791
791
  if (!this.validType(card.metadata[field.name], fieldType)) {
792
792
  const typeOfValue = typeof card.metadata[field.name];
793
793
  let fieldValue = card.metadata[field.name];
@@ -817,12 +817,44 @@ export class Validate {
817
817
  }
818
818
  }
819
819
 
820
+ // Validate that all metadata keys are either predefined fields or valid field type names
821
+ for (const key of Object.keys(card.metadata)) {
822
+ if (
823
+ (isPredefinedField(key) as boolean) ||
824
+ key === 'labels' ||
825
+ key === 'links'
826
+ ) {
827
+ continue;
828
+ }
829
+ try {
830
+ this.validResourceName('fieldTypes', key, prefixes);
831
+ } catch {
832
+ validationErrors.push(
833
+ `Card '${card.key}' has invalid metadata key '${key}'`,
834
+ );
835
+ continue;
836
+ }
837
+ // Check that the card's fieldType exists in the project
838
+ let fieldType;
839
+ try {
840
+ fieldType = await project.resources.byType(key, 'fieldTypes').show();
841
+ } catch {
842
+ fieldType = undefined;
843
+ }
844
+ if (!fieldType) {
845
+ validationErrors.push(
846
+ `Card '${card.key}' has field '${key}' that does not exist in the project`,
847
+ );
848
+ }
849
+ }
850
+
820
851
  return validationErrors.join('\n');
821
852
  }
822
853
 
823
854
  /**
824
855
  * Validates the labels of a card
825
856
  * @param card card to validate. Card must have metadata.
857
+ * @returns validation errors, if any.
826
858
  */
827
859
  public validateCardLabels(card: Card): string {
828
860
  const validationErrors: string[] = [];
@@ -856,9 +888,12 @@ export class Validate {
856
888
  * Template cards are expected to have empty workflow state.
857
889
  * @param project Project object.
858
890
  * @param card Card object to validate
859
- * @returns string containing all validation errors
891
+ * @returns validation errors, if any.
860
892
  */
861
- public validateWorkflowState(project: Project, card: Card): string {
893
+ public async validateWorkflowState(
894
+ project: Project,
895
+ card: Card,
896
+ ): Promise<string> {
862
897
  const validationErrors: string[] = [];
863
898
 
864
899
  if (!card.metadata) {
@@ -867,12 +902,14 @@ export class Validate {
867
902
  );
868
903
  }
869
904
 
870
- // Use caches for cardTypes and workflows, to avoid re-reading the same JSON files multiple times.
871
- const cardType = this.getAndCacheResource(
872
- project,
873
- this.validatedCardTypes,
874
- card.metadata?.cardType || '',
875
- );
905
+ let cardType;
906
+ try {
907
+ cardType = card.metadata?.cardType
908
+ ? project.resources.byType(card.metadata?.cardType, 'cardTypes').show()
909
+ : undefined;
910
+ } catch {
911
+ cardType = undefined;
912
+ }
876
913
  if (!cardType) {
877
914
  validationErrors.push(
878
915
  `Card '${card.key}' has invalid card type '${card.metadata?.cardType}'`,
@@ -886,11 +923,14 @@ export class Validate {
886
923
  return validationErrors.join('\n');
887
924
  }
888
925
 
889
- const workflow = this.getAndCacheResource(
890
- project,
891
- this.validatedWorkflows,
892
- cardType.workflow,
893
- );
926
+ let workflow;
927
+ try {
928
+ workflow = project.resources
929
+ .byType(cardType.workflow, 'workflows')
930
+ .show();
931
+ } catch {
932
+ workflow = undefined;
933
+ }
894
934
 
895
935
  if (!workflow) {
896
936
  validationErrors.push(
@@ -919,15 +959,16 @@ export class Validate {
919
959
 
920
960
  /**
921
961
  * Validates a single resource.
922
- * @param resource Resource to validate
923
- * @returns string containing all validation errors
962
+ * @param resourceName Resource to validate
963
+ * @param project Project instance to use.
964
+ * @returns validation errors, if any.
924
965
  */
925
966
  public async validateResource(
926
967
  resourceName: ResourceName,
927
968
  project: Project,
928
969
  ): Promise<string> {
929
970
  try {
930
- const resource = Project.resourceObject(project, resourceName);
971
+ const resource = project.resources.byType(resourceName);
931
972
  await resource.validate();
932
973
  return '';
933
974
  } catch (error) {