@cyberismo/data-handler 0.0.14 → 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 (280) hide show
  1. package/dist/card-metadata-updater.js +8 -4
  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 +29 -19
  5. package/dist/command-handler.js.map +1 -1
  6. package/dist/command-manager.d.ts +25 -2
  7. package/dist/command-manager.js +30 -5
  8. package/dist/command-manager.js.map +1 -1
  9. package/dist/commands/create.d.ts +1 -1
  10. package/dist/commands/create.js +45 -93
  11. package/dist/commands/create.js.map +1 -1
  12. package/dist/commands/edit.d.ts +1 -15
  13. package/dist/commands/edit.js +15 -89
  14. package/dist/commands/edit.js.map +1 -1
  15. package/dist/commands/export.d.ts +11 -2
  16. package/dist/commands/export.js +58 -58
  17. package/dist/commands/export.js.map +1 -1
  18. package/dist/commands/import.d.ts +9 -1
  19. package/dist/commands/import.js +17 -11
  20. package/dist/commands/import.js.map +1 -1
  21. package/dist/commands/move.d.ts +1 -2
  22. package/dist/commands/move.js +107 -146
  23. package/dist/commands/move.js.map +1 -1
  24. package/dist/commands/remove.d.ts +8 -1
  25. package/dist/commands/remove.js +17 -48
  26. package/dist/commands/remove.js.map +1 -1
  27. package/dist/commands/rename.d.ts +4 -9
  28. package/dist/commands/rename.js +34 -108
  29. package/dist/commands/rename.js.map +1 -1
  30. package/dist/commands/show.d.ts +22 -34
  31. package/dist/commands/show.js +103 -151
  32. package/dist/commands/show.js.map +1 -1
  33. package/dist/commands/transition.d.ts +9 -2
  34. package/dist/commands/transition.js +49 -44
  35. package/dist/commands/transition.js.map +1 -1
  36. package/dist/commands/update.d.ts +18 -12
  37. package/dist/commands/update.js +34 -18
  38. package/dist/commands/update.js.map +1 -1
  39. package/dist/commands/validate.d.ts +18 -10
  40. package/dist/commands/validate.js +101 -47
  41. package/dist/commands/validate.js.map +1 -1
  42. package/dist/containers/card-container.d.ts +87 -24
  43. package/dist/containers/card-container.js +183 -279
  44. package/dist/containers/card-container.js.map +1 -1
  45. package/dist/containers/project/calculation-engine.d.ts +13 -4
  46. package/dist/containers/project/calculation-engine.js +79 -77
  47. package/dist/containers/project/calculation-engine.js.map +1 -1
  48. package/dist/containers/project/card-cache.d.ts +146 -0
  49. package/dist/containers/project/card-cache.js +411 -0
  50. package/dist/containers/project/card-cache.js.map +1 -0
  51. package/dist/containers/project/project-paths.d.ts +5 -4
  52. package/dist/containers/project/project-paths.js +16 -12
  53. package/dist/containers/project/project-paths.js.map +1 -1
  54. package/dist/containers/project/resource-cache.d.ts +169 -0
  55. package/dist/containers/project/resource-cache.js +507 -0
  56. package/dist/containers/project/resource-cache.js.map +1 -0
  57. package/dist/containers/project/resource-handler.d.ts +129 -0
  58. package/dist/containers/project/resource-handler.js +206 -0
  59. package/dist/containers/project/resource-handler.js.map +1 -0
  60. package/dist/containers/project.d.ts +114 -195
  61. package/dist/containers/project.js +425 -535
  62. package/dist/containers/project.js.map +1 -1
  63. package/dist/containers/template.d.ts +22 -32
  64. package/dist/containers/template.js +113 -115
  65. package/dist/containers/template.js.map +1 -1
  66. package/dist/index.d.ts +1 -0
  67. package/dist/index.js +1 -0
  68. package/dist/index.js.map +1 -1
  69. package/dist/interfaces/folder-content-interfaces.d.ts +7 -4
  70. package/dist/interfaces/folder-content-interfaces.js +3 -3
  71. package/dist/interfaces/folder-content-interfaces.js.map +1 -1
  72. package/dist/interfaces/macros.d.ts +1 -0
  73. package/dist/interfaces/macros.js +1 -1
  74. package/dist/interfaces/macros.js.map +1 -1
  75. package/dist/interfaces/project-interfaces.d.ts +7 -5
  76. package/dist/interfaces/project-interfaces.js.map +1 -1
  77. package/dist/interfaces/resource-interfaces.d.ts +25 -22
  78. package/dist/interfaces/resource-interfaces.js +3 -0
  79. package/dist/interfaces/resource-interfaces.js.map +1 -1
  80. package/dist/macros/common.d.ts +10 -10
  81. package/dist/macros/createCards/index.d.ts +0 -13
  82. package/dist/macros/createCards/index.js.map +1 -1
  83. package/dist/macros/createCards/types.d.ts +44 -0
  84. package/dist/macros/createCards/types.js +15 -0
  85. package/dist/macros/createCards/types.js.map +1 -0
  86. package/dist/macros/graph/index.d.ts +2 -6
  87. package/dist/macros/graph/index.js +14 -28
  88. package/dist/macros/graph/index.js.map +1 -1
  89. package/dist/macros/graph/types.d.ts +23 -0
  90. package/dist/macros/graph/types.js +15 -0
  91. package/dist/macros/graph/types.js.map +1 -0
  92. package/dist/macros/image/index.d.ts +8 -16
  93. package/dist/macros/image/index.js +36 -33
  94. package/dist/macros/image/index.js.map +1 -1
  95. package/dist/macros/image/types.d.ts +38 -0
  96. package/dist/macros/image/types.js +15 -0
  97. package/dist/macros/image/types.js.map +1 -0
  98. package/dist/macros/include/index.d.ts +1 -6
  99. package/dist/macros/include/index.js +4 -7
  100. package/dist/macros/include/index.js.map +1 -1
  101. package/dist/macros/include/types.d.ts +31 -0
  102. package/dist/macros/include/types.js +15 -0
  103. package/dist/macros/include/types.js.map +1 -0
  104. package/dist/macros/index.d.ts +1 -1
  105. package/dist/macros/index.js +2 -2
  106. package/dist/macros/index.js.map +1 -1
  107. package/dist/macros/percentage/index.d.ts +0 -6
  108. package/dist/macros/percentage/index.js.map +1 -1
  109. package/dist/macros/percentage/types.d.ts +31 -0
  110. package/dist/macros/percentage/types.js +15 -0
  111. package/dist/macros/percentage/types.js.map +1 -0
  112. package/dist/macros/report/index.d.ts +0 -3
  113. package/dist/macros/report/index.js +3 -6
  114. package/dist/macros/report/index.js.map +1 -1
  115. package/dist/macros/report/types.d.ts +19 -0
  116. package/dist/macros/report/types.js +15 -0
  117. package/dist/macros/report/types.js.map +1 -0
  118. package/dist/macros/scoreCard/index.d.ts +0 -6
  119. package/dist/macros/scoreCard/index.js.map +1 -1
  120. package/dist/macros/scoreCard/types.d.ts +31 -0
  121. package/dist/macros/scoreCard/types.js +15 -0
  122. package/dist/macros/scoreCard/types.js.map +1 -0
  123. package/dist/macros/types.d.ts +25 -0
  124. package/dist/macros/types.js +2 -0
  125. package/dist/macros/types.js.map +1 -0
  126. package/dist/macros/vega/index.d.ts +0 -4
  127. package/dist/macros/vega/index.js.map +1 -1
  128. package/dist/macros/vega/types.d.ts +20 -0
  129. package/dist/macros/vega/types.js +2 -0
  130. package/dist/macros/vega/types.js.map +1 -0
  131. package/dist/macros/vegalite/index.d.ts +0 -4
  132. package/dist/macros/vegalite/index.js.map +1 -1
  133. package/dist/macros/vegalite/types.d.ts +20 -0
  134. package/dist/macros/vegalite/types.js +15 -0
  135. package/dist/macros/vegalite/types.js.map +1 -0
  136. package/dist/macros/xref/index.d.ts +0 -3
  137. package/dist/macros/xref/index.js +5 -14
  138. package/dist/macros/xref/index.js.map +1 -1
  139. package/dist/macros/xref/types.d.ts +19 -0
  140. package/dist/macros/xref/types.js +15 -0
  141. package/dist/macros/xref/types.js.map +1 -0
  142. package/dist/module-manager.d.ts +16 -3
  143. package/dist/module-manager.js +55 -23
  144. package/dist/module-manager.js.map +1 -1
  145. package/dist/project-settings.d.ts +16 -3
  146. package/dist/project-settings.js +79 -14
  147. package/dist/project-settings.js.map +1 -1
  148. package/dist/resources/calculation-resource.d.ts +6 -33
  149. package/dist/resources/calculation-resource.js +11 -60
  150. package/dist/resources/calculation-resource.js.map +1 -1
  151. package/dist/resources/card-type-resource.d.ts +10 -22
  152. package/dist/resources/card-type-resource.js +46 -66
  153. package/dist/resources/card-type-resource.js.map +1 -1
  154. package/dist/resources/create-defaults.d.ts +3 -2
  155. package/dist/resources/create-defaults.js +3 -2
  156. package/dist/resources/create-defaults.js.map +1 -1
  157. package/dist/resources/field-type-resource.d.ts +8 -22
  158. package/dist/resources/field-type-resource.js +35 -60
  159. package/dist/resources/field-type-resource.js.map +1 -1
  160. package/dist/resources/file-resource.d.ts +14 -35
  161. package/dist/resources/file-resource.js +22 -301
  162. package/dist/resources/file-resource.js.map +1 -1
  163. package/dist/resources/folder-resource.d.ts +44 -66
  164. package/dist/resources/folder-resource.js +102 -149
  165. package/dist/resources/folder-resource.js.map +1 -1
  166. package/dist/resources/graph-model-resource.d.ts +9 -34
  167. package/dist/resources/graph-model-resource.js +18 -64
  168. package/dist/resources/graph-model-resource.js.map +1 -1
  169. package/dist/resources/graph-view-resource.d.ts +9 -29
  170. package/dist/resources/graph-view-resource.js +13 -48
  171. package/dist/resources/graph-view-resource.js.map +1 -1
  172. package/dist/resources/link-type-resource.d.ts +9 -23
  173. package/dist/resources/link-type-resource.js +11 -33
  174. package/dist/resources/link-type-resource.js.map +1 -1
  175. package/dist/resources/report-resource.d.ts +10 -23
  176. package/dist/resources/report-resource.js +20 -67
  177. package/dist/resources/report-resource.js.map +1 -1
  178. package/dist/resources/resource-object.d.ts +143 -23
  179. package/dist/resources/resource-object.js +369 -48
  180. package/dist/resources/resource-object.js.map +1 -1
  181. package/dist/resources/template-resource.d.ts +10 -17
  182. package/dist/resources/template-resource.js +19 -27
  183. package/dist/resources/template-resource.js.map +1 -1
  184. package/dist/resources/workflow-resource.d.ts +9 -25
  185. package/dist/resources/workflow-resource.js +25 -55
  186. package/dist/resources/workflow-resource.js.map +1 -1
  187. package/dist/utils/card-utils.d.ts +69 -19
  188. package/dist/utils/card-utils.js +179 -30
  189. package/dist/utils/card-utils.js.map +1 -1
  190. package/dist/utils/clingo-fact-builder.d.ts +25 -14
  191. package/dist/utils/clingo-fact-builder.js +27 -5
  192. package/dist/utils/clingo-fact-builder.js.map +1 -1
  193. package/dist/utils/clingo-facts.js +14 -7
  194. package/dist/utils/clingo-facts.js.map +1 -1
  195. package/dist/utils/clingo-parser.js +1 -1
  196. package/dist/utils/clingo-parser.js.map +1 -1
  197. package/dist/utils/constants.d.ts +2 -0
  198. package/dist/utils/constants.js +4 -0
  199. package/dist/utils/constants.js.map +1 -1
  200. package/dist/utils/csv.js +1 -1
  201. package/dist/utils/csv.js.map +1 -1
  202. package/dist/utils/resource-utils.d.ts +1 -0
  203. package/dist/utils/resource-utils.js +2 -1
  204. package/dist/utils/resource-utils.js.map +1 -1
  205. package/package.json +11 -11
  206. package/src/card-metadata-updater.ts +9 -7
  207. package/src/command-handler.ts +35 -23
  208. package/src/command-manager.ts +32 -19
  209. package/src/commands/create.ts +59 -160
  210. package/src/commands/edit.ts +16 -132
  211. package/src/commands/export.ts +71 -81
  212. package/src/commands/import.ts +26 -18
  213. package/src/commands/move.ts +143 -179
  214. package/src/commands/remove.ts +20 -59
  215. package/src/commands/rename.ts +45 -156
  216. package/src/commands/show.ts +153 -211
  217. package/src/commands/transition.ts +53 -58
  218. package/src/commands/update.ts +44 -23
  219. package/src/commands/validate.ts +108 -82
  220. package/src/containers/card-container.ts +200 -360
  221. package/src/containers/project/calculation-engine.ts +81 -105
  222. package/src/containers/project/card-cache.ts +497 -0
  223. package/src/containers/project/project-paths.ts +21 -13
  224. package/src/containers/project/resource-cache.ts +648 -0
  225. package/src/containers/project/resource-handler.ts +265 -0
  226. package/src/containers/project.ts +551 -693
  227. package/src/containers/template.ts +129 -142
  228. package/src/index.ts +1 -0
  229. package/src/interfaces/folder-content-interfaces.ts +14 -7
  230. package/src/interfaces/macros.ts +2 -0
  231. package/src/interfaces/project-interfaces.ts +14 -7
  232. package/src/interfaces/resource-interfaces.ts +30 -27
  233. package/src/macros/createCards/index.ts +1 -12
  234. package/src/macros/createCards/types.ts +46 -0
  235. package/src/macros/graph/index.ts +27 -52
  236. package/src/macros/graph/types.ts +24 -0
  237. package/src/macros/image/index.ts +50 -61
  238. package/src/macros/image/types.ts +39 -0
  239. package/src/macros/include/index.ts +6 -15
  240. package/src/macros/include/types.ts +32 -0
  241. package/src/macros/index.ts +2 -2
  242. package/src/macros/percentage/index.ts +1 -7
  243. package/src/macros/percentage/types.ts +32 -0
  244. package/src/macros/report/index.ts +4 -13
  245. package/src/macros/report/types.ts +20 -0
  246. package/src/macros/scoreCard/index.ts +1 -7
  247. package/src/macros/scoreCard/types.ts +32 -0
  248. package/src/macros/types.ts +48 -0
  249. package/src/macros/vega/index.ts +1 -4
  250. package/src/macros/vega/types.ts +21 -0
  251. package/src/macros/vegalite/index.ts +1 -4
  252. package/src/macros/vegalite/types.ts +22 -0
  253. package/src/macros/xref/index.ts +6 -20
  254. package/src/macros/xref/types.ts +20 -0
  255. package/src/module-manager.ts +79 -22
  256. package/src/project-settings.ts +84 -15
  257. package/src/resources/calculation-resource.ts +21 -91
  258. package/src/resources/card-type-resource.ts +74 -109
  259. package/src/resources/create-defaults.ts +3 -2
  260. package/src/resources/field-type-resource.ts +61 -104
  261. package/src/resources/file-resource.ts +33 -441
  262. package/src/resources/folder-resource.ts +130 -207
  263. package/src/resources/graph-model-resource.ts +36 -95
  264. package/src/resources/graph-view-resource.ts +28 -70
  265. package/src/resources/link-type-resource.ts +23 -53
  266. package/src/resources/report-resource.ts +34 -96
  267. package/src/resources/resource-object.ts +511 -66
  268. package/src/resources/template-resource.ts +32 -44
  269. package/src/resources/workflow-resource.ts +42 -85
  270. package/src/utils/card-utils.ts +217 -31
  271. package/src/utils/clingo-fact-builder.ts +28 -16
  272. package/src/utils/clingo-facts.ts +16 -7
  273. package/src/utils/clingo-parser.ts +1 -1
  274. package/src/utils/constants.ts +6 -0
  275. package/src/utils/csv.ts +1 -1
  276. package/src/utils/resource-utils.ts +2 -1
  277. package/dist/containers/project/resource-collector.d.ts +0 -87
  278. package/dist/containers/project/resource-collector.js +0 -337
  279. package/dist/containers/project/resource-collector.js.map +0 -1
  280. package/src/containers/project/resource-collector.ts +0 -396
@@ -12,59 +12,60 @@
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
29
41
  * @param transition which transition to do
30
42
  */
31
43
  public async cardTransition(cardKey: string, transition: WorkflowState) {
32
- // Card details
33
- const details = await this.project.cardDetailsById(cardKey, {
34
- metadata: true,
35
- });
36
- if (!details || !details.metadata) {
37
- throw new Error(`Card ${cardKey} does not exist in the project`);
38
- }
44
+ const card = this.project.findCard(cardKey);
39
45
 
40
- // Card type
41
- const cardType = await this.project.resource<CardType>(
42
- details.metadata?.cardType,
43
- );
44
- if (cardType === undefined) {
45
- throw new Error(
46
- `Card's card type '${details.metadata?.cardType}' does not exist in the project`,
47
- );
46
+ if (!card.metadata?.cardType) {
47
+ throw new Error(`Card does not have card type`);
48
48
  }
49
+ // Card type
50
+ const cardType = this.project.resources
51
+ .byType(card.metadata?.cardType, 'cardTypes')
52
+ .show();
49
53
 
50
54
  // Workflow
51
- const workflow = await this.project.resource<Workflow>(cardType.workflow);
52
- if (workflow === undefined) {
53
- throw new Error(
54
- `Card's workflow '${cardType.workflow}' does not exist in the project`,
55
- );
56
- }
55
+ const workflow = this.project.resources
56
+ .byType(cardType.workflow, 'workflows')
57
+ .show();
57
58
 
58
59
  // Check that the state transition can be made "from".
59
60
  const foundFrom = workflow.transitions.find(
60
61
  (item) =>
61
- (details.metadata &&
62
- item.fromState.includes(details.metadata?.workflowState)) ||
62
+ (card.metadata &&
63
+ item.fromState.includes(card.metadata?.workflowState)) ||
63
64
  item.fromState.includes('*'),
64
65
  );
65
66
  if (!foundFrom) {
66
67
  throw new Error(
67
- `Card's workflow '${cardType.workflow}' does not contain transition from card's current state '${details.metadata?.workflowState}'`,
68
+ `Card's workflow '${cardType.workflow}' does not contain transition from card's current state '${card.metadata?.workflowState}'`,
68
69
  );
69
70
  }
70
71
 
@@ -80,44 +81,38 @@ export class Transition {
80
81
 
81
82
  if (
82
83
  !(
83
- found.fromState.includes(details.metadata?.workflowState) ||
84
+ (card.metadata?.workflowState &&
85
+ found.fromState.includes(card.metadata.workflowState)) ||
84
86
  found.fromState.includes('*')
85
87
  )
86
88
  ) {
87
89
  throw new Error(
88
- `Card's workflow '${cardType.workflow}' does not contain state transition from state '${details.metadata?.workflowState}' for '${transition.name}`,
90
+ `Card's workflow '${cardType.workflow}' does not contain state transition from state '${card.metadata?.workflowState}' for '${transition.name}`,
89
91
  );
90
92
  }
91
93
 
92
94
  const actionGuard = new ActionGuard(this.project.calculationEngine);
93
95
  await actionGuard.checkPermission('transition', cardKey, transition.name);
94
96
 
95
- details.metadata.workflowState = found.toState;
96
- details.metadata.lastUpdated = new Date().toISOString();
97
- details.metadata.lastTransitioned = new Date().toISOString();
98
- return this.project
99
- .updateCardMetadata(details, details.metadata)
100
- .then(async () => this.transitionChangesQuery(cardKey, transition.name))
101
- .then(async (queryResult) => {
102
- if (
103
- !queryResult ||
104
- queryResult.at(0) === undefined ||
105
- queryResult.at(0)?.updateFields === undefined
106
- ) {
107
- return;
108
- }
109
- const fieldsToUpdate = queryResult.at(0)!.updateFields;
110
- return CardMetadataUpdater.apply(this.project, fieldsToUpdate);
111
- })
112
- .catch((error) => console.error(error));
113
- }
114
-
115
- // Wrapper to run onTransition query.
116
- private async transitionChangesQuery(cardKey: string, transition: string) {
117
- if (!cardKey || !transition) return undefined;
118
- return this.project.calculationEngine.runQuery('onTransition', 'localApp', {
119
- cardKey,
120
- transition,
121
- });
97
+ if (card.metadata) {
98
+ card.metadata.workflowState = found.toState;
99
+ card.metadata.lastUpdated = new Date().toISOString();
100
+ card.metadata.lastTransitioned = new Date().toISOString();
101
+ return this.project
102
+ .updateCardMetadata(card, card.metadata)
103
+ .then(async () => this.transitionChangesQuery(cardKey, transition.name))
104
+ .then(async (queryResult) => {
105
+ if (
106
+ !queryResult ||
107
+ queryResult.at(0) === undefined ||
108
+ queryResult.at(0)?.updateFields === undefined
109
+ ) {
110
+ return;
111
+ }
112
+ const fieldsToUpdate = queryResult.at(0)!.updateFields;
113
+ return CardMetadataUpdater.apply(this.project, fieldsToUpdate);
114
+ })
115
+ .catch((error) => console.error(error));
116
+ }
122
117
  }
123
118
  }
@@ -19,20 +19,44 @@ 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';
23
+ import type { UpdateKey } from '../interfaces/resource-interfaces.js';
24
24
 
25
25
  /**
26
26
  * Class that handles 'update' commands.
27
27
  */
28
28
  export class Update {
29
+ /**
30
+ * Creates an instance of Update command.
31
+ * @param project Project to use.
32
+ */
29
33
  constructor(private project: Project) {}
30
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
+
31
55
  /**
32
56
  * Updates single resource property.
33
57
  * @param name Name of the resource to operate on.
34
58
  * @param operation Operation to perform ('add', 'remove', 'change', 'rank')
35
- * @param key Property to change in resource JSON
59
+ * @param key Property to change in resource JSON. If content, content/<property>
36
60
  * @param value Value for 'key'
37
61
  * @param optionalDetail Additional detail needed for some operations. For example, 'update' needs a new value.
38
62
  * @param mappingTable Optional mapping table for workflow state transitions (only used for workflow changes)
@@ -75,26 +99,23 @@ export class Update {
75
99
  ? optionalDetail
76
100
  : undefined;
77
101
  }
102
+ const splitKey = key.split('/');
103
+ if (splitKey.length !== 1 && splitKey.length !== 2) {
104
+ throw new Error(
105
+ `Invalid key format: ${key}. Use 'property' or 'content/<property>'.`,
106
+ );
107
+ }
78
108
 
79
- await this.applyResourceOperation(name, key, op);
80
- }
81
-
82
- /**
83
- * Update single resource property
84
- * This is similar to updateValue, but allows the operation to be fully specified
85
- * @param name Name of the resource to operate on.
86
- * @param key Property to change in resource JSON
87
- * @param operation The full operation object
88
- * @template Type Type of the target of the operation
89
- * @template T Type of operation ('add', 'remove', 'change', 'rank')
90
- */
91
- public async applyResourceOperation<Type, T extends UpdateOperations>(
92
- name: string,
93
- key: string,
94
- operation: OperationFor<Type, T>,
95
- ) {
96
- const resource = Project.resourceObject(this.project, resourceName(name));
97
- await resource?.update(key, operation);
98
- this.project.collectLocalResources();
109
+ if (splitKey.length === 2 && splitKey[0] !== 'content') {
110
+ throw new Error(
111
+ `Invalid key format: ${key}. When using 'content', always use as 'content/<property>'.`,
112
+ );
113
+ }
114
+ const [parsedKey, subKey] = splitKey;
115
+ if (parsedKey === 'content') {
116
+ await this.applyResourceOperation(name, { key: parsedKey, subKey }, op);
117
+ } else {
118
+ await this.applyResourceOperation(name, { key: parsedKey }, op);
119
+ }
99
120
  }
100
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,25 +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 async getAndCacheResource<Type>(
173
- project: Project,
174
- cachedValues: Map<string, Type>,
175
- valueName: string,
176
- ): Promise<Type | undefined> {
177
- return (
178
- cachedValues.get(valueName) ||
179
- project.resource<Type>(valueName).then((resource) => {
180
- if (!resource) {
181
- return undefined;
182
- }
183
- cachedValues.set(valueName, resource);
184
- return resource;
185
- })
186
- );
187
- }
188
-
174
+ // Parses validator messages
189
175
  private parseValidatorMessage(errorObject: object[]): string {
190
176
  let parsedErrorMessage = '';
191
177
  // todo: get schema name here?
@@ -236,7 +222,7 @@ export class Validate {
236
222
  ): Promise<string[]> {
237
223
  const message: string[] = [];
238
224
  try {
239
- const prefixes = await project.projectPrefixes();
225
+ const prefixes = project.projectPrefixes();
240
226
  const files = await readdir(path, {
241
227
  withFileTypes: true,
242
228
  });
@@ -395,11 +381,13 @@ export class Validate {
395
381
  const errors: string[] = [];
396
382
  if (cardType && fieldArray) {
397
383
  const validationPromises = fieldArray.map(async (field) => {
398
- const fieldType = await this.getAndCacheResource(
399
- project,
400
- this.validatedFieldTypes,
401
- field,
402
- );
384
+ let fieldType;
385
+ try {
386
+ fieldType = project.resources.byType(field, 'fieldTypes').show();
387
+ } catch {
388
+ fieldType = undefined;
389
+ }
390
+
403
391
  if (!fieldType) {
404
392
  return `Card type '${cardType.name}' has invalid reference to unknown ${nameOfArray} '${field}'`;
405
393
  }
@@ -476,7 +464,7 @@ export class Validate {
476
464
 
477
465
  /**
478
466
  * Validates that new identifier of a resource is according to naming convention.
479
- * @param identifier: resource identifier
467
+ * @param identifier Resource identifier
480
468
  * returns true if identifier is valid, and false otherwise.
481
469
  */
482
470
  public static isValidIdentifierName(identifier: string): boolean {
@@ -550,6 +538,7 @@ export class Validate {
550
538
  } else {
551
539
  const errorMsg: string[] = [];
552
540
  const project = projectFn();
541
+ await project.populateCaches();
553
542
 
554
543
  // Then, validate that each 'contentSchema' children as well.
555
544
  const result = await this.readAndValidateContentFiles(
@@ -561,10 +550,11 @@ export class Validate {
561
550
  }
562
551
 
563
552
  // Finally, validate that each card is correct
564
- const cards = await project.cards();
565
- cards.push(...(await project.allTemplateCards()));
553
+ const cards = project.cards();
554
+ cards.push(...project.allTemplateCards());
566
555
 
567
556
  const cardIds = new Map<string, number>();
557
+ const allPrefixes = await project.projectPrefixes();
568
558
 
569
559
  for (const card of cards) {
570
560
  if (cardIds.has(card.key)) {
@@ -574,7 +564,6 @@ export class Validate {
574
564
  }
575
565
 
576
566
  if (card.metadata) {
577
- // validate card's workflow
578
567
  if (!isTemplateCard(card)) {
579
568
  const validWorkflow = await this.validateWorkflowState(
580
569
  project,
@@ -585,10 +574,10 @@ export class Validate {
585
574
  }
586
575
  }
587
576
  }
588
-
589
577
  const validCustomFields = await this.validateCustomFields(
590
578
  project,
591
579
  card,
580
+ allPrefixes,
592
581
  );
593
582
  if (validCustomFields.length !== 0) {
594
583
  errorMsg.push(validCustomFields);
@@ -604,7 +593,7 @@ export class Validate {
604
593
  await evaluateMacros(card.content, {
605
594
  context: 'localApp',
606
595
  mode: 'validate',
607
- project,
596
+ project: project,
608
597
  cardKey: card.key,
609
598
  });
610
599
  }
@@ -671,21 +660,20 @@ export class Validate {
671
660
  * @param resourceType Type of resource
672
661
  * @param name Name of resource
673
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
674
666
  * @returns resource name as valid resource name; throws in error cases.
675
667
  */
676
- public async validResourceName(
668
+ public validResourceName(
677
669
  resourceType: ResourceTypes,
678
670
  name: string,
679
671
  prefixes: string[],
680
- ): Promise<string> {
672
+ ): string {
681
673
  const resource = resourceName(name);
682
674
  resource.type = resource.type ? resource.type : resourceType;
683
- // a bit shaky way to ensure that prefix is set; first of the project prefixes should be the actual project prefix.
684
675
  if (resource.prefix === '') {
685
- resource.prefix = prefixes.length > 0 ? prefixes.at(0) || '' : '';
686
- if (resource.prefix === '') {
687
- throw new Error(`Project prefix cannot be empty string`);
688
- }
676
+ throw new Error(`Project prefix cannot be empty string`);
689
677
  }
690
678
  if (!prefixes.includes(resource.prefix)) {
691
679
  throw new Error(
@@ -721,11 +709,13 @@ export class Validate {
721
709
  * Validates that card's custom fields are according to schema and have correct data in them.
722
710
  * @param project currently used Project
723
711
  * @param card specific card
724
- * @returns string containing all validation errors
712
+ * @throws when card does not have metadata
713
+ * @returns validation errors, if any.
725
714
  */
726
715
  public async validateCustomFields(
727
716
  project: Project,
728
717
  card: Card,
718
+ prefixes: string[],
729
719
  ): Promise<string> {
730
720
  const validationErrors: string[] = [];
731
721
 
@@ -735,11 +725,14 @@ export class Validate {
735
725
  );
736
726
  }
737
727
 
738
- const cardType = await this.getAndCacheResource(
739
- project,
740
- this.validatedCardTypes,
741
- card.metadata?.cardType,
742
- );
728
+ let cardType;
729
+ try {
730
+ cardType = project.resources
731
+ .byType(card.metadata?.cardType, 'cardTypes')
732
+ .show();
733
+ } catch {
734
+ cardType = undefined;
735
+ }
743
736
 
744
737
  if (!cardType) {
745
738
  validationErrors.push(
@@ -765,11 +758,19 @@ export class Validate {
765
758
  validationErrors.push(...fieldErrors);
766
759
 
767
760
  for (const field of cardType.customFields) {
768
- const found = await project.resourceExists('fieldTypes', field.name);
769
- 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) {
770
770
  validationErrors.push(
771
- `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`,
772
772
  );
773
+ continue;
773
774
  }
774
775
  if (field.isCalculated) {
775
776
  if (card.metadata[field.name] !== undefined) {
@@ -787,19 +788,6 @@ export class Validate {
787
788
  }
788
789
  }
789
790
 
790
- const fieldType = await this.getAndCacheResource(
791
- project,
792
- this.validatedFieldTypes,
793
- field.name,
794
- );
795
-
796
- if (!fieldType) {
797
- validationErrors.push(
798
- `In card '${card.key}' field '${field.name}' is missing from project\n`,
799
- );
800
- continue;
801
- }
802
-
803
791
  if (!this.validType(card.metadata[field.name], fieldType)) {
804
792
  const typeOfValue = typeof card.metadata[field.name];
805
793
  let fieldValue = card.metadata[field.name];
@@ -829,14 +817,46 @@ export class Validate {
829
817
  }
830
818
  }
831
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
+
832
851
  return validationErrors.join('\n');
833
852
  }
834
853
 
835
854
  /**
836
855
  * Validates the labels of a card
837
856
  * @param card card to validate. Card must have metadata.
857
+ * @returns validation errors, if any.
838
858
  */
839
- public async validateCardLabels(card: Card): Promise<string> {
859
+ public validateCardLabels(card: Card): string {
840
860
  const validationErrors: string[] = [];
841
861
  if (!card.metadata) {
842
862
  validationErrors.push(
@@ -868,7 +888,7 @@ export class Validate {
868
888
  * Template cards are expected to have empty workflow state.
869
889
  * @param project Project object.
870
890
  * @param card Card object to validate
871
- * @returns string containing all validation errors
891
+ * @returns validation errors, if any.
872
892
  */
873
893
  public async validateWorkflowState(
874
894
  project: Project,
@@ -882,12 +902,14 @@ export class Validate {
882
902
  );
883
903
  }
884
904
 
885
- // Use caches for cardTypes and workflows, to avoid re-reading the same JSON files multiple times.
886
- const cardType = await this.getAndCacheResource(
887
- project,
888
- this.validatedCardTypes,
889
- card.metadata?.cardType || '',
890
- );
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
+ }
891
913
  if (!cardType) {
892
914
  validationErrors.push(
893
915
  `Card '${card.key}' has invalid card type '${card.metadata?.cardType}'`,
@@ -901,11 +923,14 @@ export class Validate {
901
923
  return validationErrors.join('\n');
902
924
  }
903
925
 
904
- const workflow = await this.getAndCacheResource(
905
- project,
906
- this.validatedWorkflows,
907
- cardType.workflow,
908
- );
926
+ let workflow;
927
+ try {
928
+ workflow = project.resources
929
+ .byType(cardType.workflow, 'workflows')
930
+ .show();
931
+ } catch {
932
+ workflow = undefined;
933
+ }
909
934
 
910
935
  if (!workflow) {
911
936
  validationErrors.push(
@@ -934,15 +959,16 @@ export class Validate {
934
959
 
935
960
  /**
936
961
  * Validates a single resource.
937
- * @param resource Resource to validate
938
- * @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.
939
965
  */
940
966
  public async validateResource(
941
967
  resourceName: ResourceName,
942
968
  project: Project,
943
969
  ): Promise<string> {
944
970
  try {
945
- const resource = Project.resourceObject(project, resourceName);
971
+ const resource = project.resources.byType(resourceName);
946
972
  await resource.validate();
947
973
  return '';
948
974
  } catch (error) {