@cyberismo/data-handler 0.0.2

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 (306) hide show
  1. package/LICENSE +702 -0
  2. package/dist/card-metadata-updater.d.ts +33 -0
  3. package/dist/card-metadata-updater.js +121 -0
  4. package/dist/card-metadata-updater.js.map +1 -0
  5. package/dist/command-handler.d.ts +96 -0
  6. package/dist/command-handler.js +557 -0
  7. package/dist/command-handler.js.map +1 -0
  8. package/dist/command-manager.d.ts +43 -0
  9. package/dist/command-manager.js +73 -0
  10. package/dist/command-manager.js.map +1 -0
  11. package/dist/commands/calculate.d.ts +86 -0
  12. package/dist/commands/calculate.js +444 -0
  13. package/dist/commands/calculate.js.map +1 -0
  14. package/dist/commands/create.d.ts +114 -0
  15. package/dist/commands/create.js +389 -0
  16. package/dist/commands/create.js.map +1 -0
  17. package/dist/commands/edit.d.ts +37 -0
  18. package/dist/commands/edit.js +99 -0
  19. package/dist/commands/edit.js.map +1 -0
  20. package/dist/commands/export-site.d.ts +45 -0
  21. package/dist/commands/export-site.js +301 -0
  22. package/dist/commands/export-site.js.map +1 -0
  23. package/dist/commands/export.d.ts +53 -0
  24. package/dist/commands/export.js +251 -0
  25. package/dist/commands/export.js.map +1 -0
  26. package/dist/commands/import.d.ts +53 -0
  27. package/dist/commands/import.js +133 -0
  28. package/dist/commands/import.js.map +1 -0
  29. package/dist/commands/index.d.ts +26 -0
  30. package/dist/commands/index.js +27 -0
  31. package/dist/commands/index.js.map +1 -0
  32. package/dist/commands/move.d.ts +55 -0
  33. package/dist/commands/move.js +341 -0
  34. package/dist/commands/move.js.map +1 -0
  35. package/dist/commands/remove.d.ts +38 -0
  36. package/dist/commands/remove.js +192 -0
  37. package/dist/commands/remove.js.map +1 -0
  38. package/dist/commands/rename.d.ts +46 -0
  39. package/dist/commands/rename.js +289 -0
  40. package/dist/commands/rename.js.map +1 -0
  41. package/dist/commands/show.d.ts +124 -0
  42. package/dist/commands/show.js +345 -0
  43. package/dist/commands/show.js.map +1 -0
  44. package/dist/commands/transition.d.ts +27 -0
  45. package/dist/commands/transition.js +92 -0
  46. package/dist/commands/transition.js.map +1 -0
  47. package/dist/commands/update.d.ts +29 -0
  48. package/dist/commands/update.js +64 -0
  49. package/dist/commands/update.js.map +1 -0
  50. package/dist/commands/validate.d.ts +143 -0
  51. package/dist/commands/validate.js +689 -0
  52. package/dist/commands/validate.js.map +1 -0
  53. package/dist/containers/card-container.d.ts +44 -0
  54. package/dist/containers/card-container.js +282 -0
  55. package/dist/containers/card-container.js.map +1 -0
  56. package/dist/containers/project/project-paths.d.ts +46 -0
  57. package/dist/containers/project/project-paths.js +105 -0
  58. package/dist/containers/project/project-paths.js.map +1 -0
  59. package/dist/containers/project/resource-collector.d.ts +86 -0
  60. package/dist/containers/project/resource-collector.js +331 -0
  61. package/dist/containers/project/resource-collector.js.map +1 -0
  62. package/dist/containers/project.d.ts +351 -0
  63. package/dist/containers/project.js +896 -0
  64. package/dist/containers/project.js.map +1 -0
  65. package/dist/containers/template.d.ts +108 -0
  66. package/dist/containers/template.js +433 -0
  67. package/dist/containers/template.js.map +1 -0
  68. package/dist/exceptions/index.d.ts +19 -0
  69. package/dist/exceptions/index.js +26 -0
  70. package/dist/exceptions/index.js.map +1 -0
  71. package/dist/index.d.ts +16 -0
  72. package/dist/index.js +15 -0
  73. package/dist/index.js.map +1 -0
  74. package/dist/interfaces/adoc.d.ts +12 -0
  75. package/dist/interfaces/adoc.js +13 -0
  76. package/dist/interfaces/adoc.js.map +1 -0
  77. package/dist/interfaces/macros.d.ts +45 -0
  78. package/dist/interfaces/macros.js +13 -0
  79. package/dist/interfaces/macros.js.map +1 -0
  80. package/dist/interfaces/project-interfaces.d.ts +121 -0
  81. package/dist/interfaces/project-interfaces.js +21 -0
  82. package/dist/interfaces/project-interfaces.js.map +1 -0
  83. package/dist/interfaces/request-status-interfaces.d.ts +28 -0
  84. package/dist/interfaces/request-status-interfaces.js +20 -0
  85. package/dist/interfaces/request-status-interfaces.js.map +1 -0
  86. package/dist/interfaces/resource-interfaces.d.ts +117 -0
  87. package/dist/interfaces/resource-interfaces.js +20 -0
  88. package/dist/interfaces/resource-interfaces.js.map +1 -0
  89. package/dist/macros/base-macro.d.ts +31 -0
  90. package/dist/macros/base-macro.js +126 -0
  91. package/dist/macros/base-macro.js.map +1 -0
  92. package/dist/macros/common.d.ts +17 -0
  93. package/dist/macros/common.js +23 -0
  94. package/dist/macros/common.js.map +1 -0
  95. package/dist/macros/createCards/index.d.ts +36 -0
  96. package/dist/macros/createCards/index.js +35 -0
  97. package/dist/macros/createCards/index.js.map +1 -0
  98. package/dist/macros/createCards/metadata.d.ts +14 -0
  99. package/dist/macros/createCards/metadata.js +18 -0
  100. package/dist/macros/createCards/metadata.js.map +1 -0
  101. package/dist/macros/graph/index.d.ts +29 -0
  102. package/dist/macros/graph/index.js +91 -0
  103. package/dist/macros/graph/index.js.map +1 -0
  104. package/dist/macros/graph/metadata.d.ts +14 -0
  105. package/dist/macros/graph/metadata.js +18 -0
  106. package/dist/macros/graph/metadata.js.map +1 -0
  107. package/dist/macros/index.d.ts +93 -0
  108. package/dist/macros/index.js +237 -0
  109. package/dist/macros/index.js.map +1 -0
  110. package/dist/macros/report/index.d.ts +26 -0
  111. package/dist/macros/report/index.js +70 -0
  112. package/dist/macros/report/index.js.map +1 -0
  113. package/dist/macros/report/metadata.d.ts +14 -0
  114. package/dist/macros/report/metadata.js +18 -0
  115. package/dist/macros/report/metadata.js.map +1 -0
  116. package/dist/macros/scoreCard/index.d.ts +30 -0
  117. package/dist/macros/scoreCard/index.js +38 -0
  118. package/dist/macros/scoreCard/index.js.map +1 -0
  119. package/dist/macros/scoreCard/metadata.d.ts +14 -0
  120. package/dist/macros/scoreCard/metadata.js +18 -0
  121. package/dist/macros/scoreCard/metadata.js.map +1 -0
  122. package/dist/macros/task-queue.d.ts +46 -0
  123. package/dist/macros/task-queue.js +69 -0
  124. package/dist/macros/task-queue.js.map +1 -0
  125. package/dist/module-manager.d.ts +62 -0
  126. package/dist/module-manager.js +350 -0
  127. package/dist/module-manager.js.map +1 -0
  128. package/dist/permissions/action-guard.d.ts +28 -0
  129. package/dist/permissions/action-guard.js +61 -0
  130. package/dist/permissions/action-guard.js.map +1 -0
  131. package/dist/project-settings.d.ts +42 -0
  132. package/dist/project-settings.js +120 -0
  133. package/dist/project-settings.js.map +1 -0
  134. package/dist/resources/array-handler.d.ts +28 -0
  135. package/dist/resources/array-handler.js +116 -0
  136. package/dist/resources/array-handler.js.map +1 -0
  137. package/dist/resources/card-type-resource.d.ts +72 -0
  138. package/dist/resources/card-type-resource.js +334 -0
  139. package/dist/resources/card-type-resource.js.map +1 -0
  140. package/dist/resources/create-defaults.d.ts +81 -0
  141. package/dist/resources/create-defaults.js +184 -0
  142. package/dist/resources/create-defaults.js.map +1 -0
  143. package/dist/resources/field-type-resource.d.ts +88 -0
  144. package/dist/resources/field-type-resource.js +411 -0
  145. package/dist/resources/field-type-resource.js.map +1 -0
  146. package/dist/resources/file-resource.d.ts +50 -0
  147. package/dist/resources/file-resource.js +301 -0
  148. package/dist/resources/file-resource.js.map +1 -0
  149. package/dist/resources/folder-resource.d.ts +66 -0
  150. package/dist/resources/folder-resource.js +100 -0
  151. package/dist/resources/folder-resource.js.map +1 -0
  152. package/dist/resources/graph-model-resource.d.ts +78 -0
  153. package/dist/resources/graph-model-resource.js +164 -0
  154. package/dist/resources/graph-model-resource.js.map +1 -0
  155. package/dist/resources/graph-view-resource.d.ts +78 -0
  156. package/dist/resources/graph-view-resource.js +163 -0
  157. package/dist/resources/graph-view-resource.js.map +1 -0
  158. package/dist/resources/link-type-resource.d.ts +62 -0
  159. package/dist/resources/link-type-resource.js +150 -0
  160. package/dist/resources/link-type-resource.js.map +1 -0
  161. package/dist/resources/report-resource.d.ts +77 -0
  162. package/dist/resources/report-resource.js +171 -0
  163. package/dist/resources/report-resource.js.map +1 -0
  164. package/dist/resources/resource-object.d.ts +108 -0
  165. package/dist/resources/resource-object.js +147 -0
  166. package/dist/resources/resource-object.js.map +1 -0
  167. package/dist/resources/template-resource.d.ts +82 -0
  168. package/dist/resources/template-resource.js +173 -0
  169. package/dist/resources/template-resource.js.map +1 -0
  170. package/dist/resources/workflow-resource.d.ts +67 -0
  171. package/dist/resources/workflow-resource.js +156 -0
  172. package/dist/resources/workflow-resource.js.map +1 -0
  173. package/dist/types/queries.d.ts +142 -0
  174. package/dist/types/queries.js +16 -0
  175. package/dist/types/queries.js.map +1 -0
  176. package/dist/utils/card-utils.d.ts +34 -0
  177. package/dist/utils/card-utils.js +78 -0
  178. package/dist/utils/card-utils.js.map +1 -0
  179. package/dist/utils/clingo-fact-builder.d.ts +58 -0
  180. package/dist/utils/clingo-fact-builder.js +126 -0
  181. package/dist/utils/clingo-fact-builder.js.map +1 -0
  182. package/dist/utils/clingo-facts.d.ts +97 -0
  183. package/dist/utils/clingo-facts.js +352 -0
  184. package/dist/utils/clingo-facts.js.map +1 -0
  185. package/dist/utils/clingo-parser.d.ts +59 -0
  186. package/dist/utils/clingo-parser.js +403 -0
  187. package/dist/utils/clingo-parser.js.map +1 -0
  188. package/dist/utils/clingo-program-builder.d.ts +39 -0
  189. package/dist/utils/clingo-program-builder.js +57 -0
  190. package/dist/utils/clingo-program-builder.js.map +1 -0
  191. package/dist/utils/common-utils.d.ts +24 -0
  192. package/dist/utils/common-utils.js +47 -0
  193. package/dist/utils/common-utils.js.map +1 -0
  194. package/dist/utils/constants.d.ts +18 -0
  195. package/dist/utils/constants.js +27 -0
  196. package/dist/utils/constants.js.map +1 -0
  197. package/dist/utils/csv.d.ts +18 -0
  198. package/dist/utils/csv.js +45 -0
  199. package/dist/utils/csv.js.map +1 -0
  200. package/dist/utils/file-utils.d.ts +69 -0
  201. package/dist/utils/file-utils.js +158 -0
  202. package/dist/utils/file-utils.js.map +1 -0
  203. package/dist/utils/json.d.ts +61 -0
  204. package/dist/utils/json.js +108 -0
  205. package/dist/utils/json.js.map +1 -0
  206. package/dist/utils/lexorank.d.ts +59 -0
  207. package/dist/utils/lexorank.js +159 -0
  208. package/dist/utils/lexorank.js.map +1 -0
  209. package/dist/utils/log-utils.d.ts +40 -0
  210. package/dist/utils/log-utils.js +109 -0
  211. package/dist/utils/log-utils.js.map +1 -0
  212. package/dist/utils/random.d.ts +19 -0
  213. package/dist/utils/random.js +34 -0
  214. package/dist/utils/random.js.map +1 -0
  215. package/dist/utils/resource-utils.d.ts +45 -0
  216. package/dist/utils/resource-utils.js +137 -0
  217. package/dist/utils/resource-utils.js.map +1 -0
  218. package/dist/utils/sanitize-svg.d.ts +18 -0
  219. package/dist/utils/sanitize-svg.js +38 -0
  220. package/dist/utils/sanitize-svg.js.map +1 -0
  221. package/dist/utils/user-preferences.d.ts +64 -0
  222. package/dist/utils/user-preferences.js +106 -0
  223. package/dist/utils/user-preferences.js.map +1 -0
  224. package/dist/utils/validate.d.ts +26 -0
  225. package/dist/utils/validate.js +53 -0
  226. package/dist/utils/validate.js.map +1 -0
  227. package/dist/utils/value-utils.d.ts +58 -0
  228. package/dist/utils/value-utils.js +181 -0
  229. package/dist/utils/value-utils.js.map +1 -0
  230. package/package.json +67 -0
  231. package/src/card-metadata-updater.ts +182 -0
  232. package/src/command-handler.ts +686 -0
  233. package/src/command-manager.ts +99 -0
  234. package/src/commands/calculate.ts +591 -0
  235. package/src/commands/create.ts +559 -0
  236. package/src/commands/edit.ts +123 -0
  237. package/src/commands/export-site.ts +356 -0
  238. package/src/commands/export.ts +315 -0
  239. package/src/commands/import.ts +169 -0
  240. package/src/commands/index.ts +42 -0
  241. package/src/commands/move.ts +451 -0
  242. package/src/commands/remove.ts +244 -0
  243. package/src/commands/rename.ts +378 -0
  244. package/src/commands/show.ts +442 -0
  245. package/src/commands/transition.ts +127 -0
  246. package/src/commands/update.ts +76 -0
  247. package/src/commands/validate.ts +962 -0
  248. package/src/containers/card-container.ts +378 -0
  249. package/src/containers/project/project-paths.ts +127 -0
  250. package/src/containers/project/resource-collector.ts +379 -0
  251. package/src/containers/project.ts +1135 -0
  252. package/src/containers/template.ts +573 -0
  253. package/src/exceptions/index.ts +29 -0
  254. package/src/index.ts +33 -0
  255. package/src/interfaces/adoc.ts +18 -0
  256. package/src/interfaces/macros.ts +54 -0
  257. package/src/interfaces/project-interfaces.ts +208 -0
  258. package/src/interfaces/request-status-interfaces.ts +30 -0
  259. package/src/interfaces/resource-interfaces.ts +179 -0
  260. package/src/macros/base-macro.ts +176 -0
  261. package/src/macros/common.ts +24 -0
  262. package/src/macros/createCards/index.ts +57 -0
  263. package/src/macros/createCards/metadata.ts +21 -0
  264. package/src/macros/graph/index.ts +130 -0
  265. package/src/macros/graph/metadata.ts +21 -0
  266. package/src/macros/index.ts +321 -0
  267. package/src/macros/report/index.ts +88 -0
  268. package/src/macros/report/metadata.ts +21 -0
  269. package/src/macros/scoreCard/index.ts +55 -0
  270. package/src/macros/scoreCard/metadata.ts +21 -0
  271. package/src/macros/task-queue.ts +79 -0
  272. package/src/module-manager.ts +443 -0
  273. package/src/permissions/action-guard.ts +77 -0
  274. package/src/project-settings.ts +140 -0
  275. package/src/resources/array-handler.ts +141 -0
  276. package/src/resources/card-type-resource.ts +455 -0
  277. package/src/resources/create-defaults.ts +216 -0
  278. package/src/resources/field-type-resource.ts +533 -0
  279. package/src/resources/file-resource.ts +433 -0
  280. package/src/resources/folder-resource.ts +140 -0
  281. package/src/resources/graph-model-resource.ts +205 -0
  282. package/src/resources/graph-view-resource.ts +199 -0
  283. package/src/resources/link-type-resource.ts +191 -0
  284. package/src/resources/report-resource.ts +224 -0
  285. package/src/resources/resource-object.ts +246 -0
  286. package/src/resources/template-resource.ts +210 -0
  287. package/src/resources/workflow-resource.ts +205 -0
  288. package/src/types/queries.ts +149 -0
  289. package/src/utils/card-utils.ts +83 -0
  290. package/src/utils/clingo-fact-builder.ts +167 -0
  291. package/src/utils/clingo-facts.ts +550 -0
  292. package/src/utils/clingo-parser.ts +519 -0
  293. package/src/utils/clingo-program-builder.ts +71 -0
  294. package/src/utils/common-utils.ts +54 -0
  295. package/src/utils/constants.ts +32 -0
  296. package/src/utils/csv.ts +53 -0
  297. package/src/utils/file-utils.ts +182 -0
  298. package/src/utils/json.ts +118 -0
  299. package/src/utils/lexorank.ts +180 -0
  300. package/src/utils/log-utils.ts +127 -0
  301. package/src/utils/random.ts +37 -0
  302. package/src/utils/resource-utils.ts +180 -0
  303. package/src/utils/sanitize-svg.ts +46 -0
  304. package/src/utils/user-preferences.ts +126 -0
  305. package/src/utils/validate.ts +66 -0
  306. package/src/utils/value-utils.ts +189 -0
@@ -0,0 +1,1135 @@
1
+ /**
2
+ Cyberismo
3
+ Copyright © Cyberismo Ltd and contributors 2024
4
+
5
+ This program is free software: you can redistribute it and/or modify it under
6
+ the terms of the GNU Affero General Public License version 3 as published by
7
+ the Free Software Foundation. This program is distributed in the hope that it
8
+ will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
9
+ of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10
+ See the GNU Affero General Public License for more details.
11
+ You should have received a copy of the GNU Affero General Public
12
+ License along with this program. If not, see <https://www.gnu.org/licenses/>.
13
+ */
14
+
15
+ // node
16
+ import { type Dirent } from 'node:fs';
17
+ import { dirname, join, resolve, sep } from 'node:path';
18
+ import { readdir } from 'node:fs/promises';
19
+
20
+ import { CardContainer } from './card-container.js'; // base class
21
+
22
+ import {
23
+ type Card,
24
+ type CardAttachment,
25
+ CardLocation,
26
+ type CardListContainer,
27
+ type CardMetadata,
28
+ CardNameRegEx,
29
+ type FetchCardDetails,
30
+ type MetadataContent,
31
+ type ModuleContent,
32
+ type ModuleSetting,
33
+ type ProjectFetchCardDetails,
34
+ type ProjectMetadata,
35
+ type ProjectSettings,
36
+ type Resource,
37
+ type ResourceFolderType,
38
+ } from '../interfaces/project-interfaces.js';
39
+ import { findParentPath } from '../utils/card-utils.js';
40
+ import { getFilesSync, pathExists } from '../utils/file-utils.js';
41
+ import { generateRandomString } from '../utils/random.js';
42
+ import { isTemplateCard } from '../utils/card-utils.js';
43
+ import { ProjectConfiguration } from '../project-settings.js';
44
+ import { ProjectPaths } from './project/project-paths.js';
45
+ import { readJsonFile } from '../utils/json.js';
46
+ import {
47
+ resourceName,
48
+ type ResourceName,
49
+ resourceNameToString,
50
+ } from '../utils/resource-utils.js';
51
+ import {
52
+ ResourcesFrom,
53
+ ResourceCollector,
54
+ } from './project/resource-collector.js';
55
+ import type { Template } from './template.js';
56
+ import { Validate } from '../commands/validate.js';
57
+
58
+ import { CardTypeResource } from '../resources/card-type-resource.js';
59
+ import { FieldTypeResource } from '../resources/field-type-resource.js';
60
+ import { GraphModelResource } from '../resources/graph-model-resource.js';
61
+ import { GraphViewResource } from '../resources/graph-view-resource.js';
62
+ import { LinkTypeResource } from '../resources/link-type-resource.js';
63
+ import { ReportResource } from '../resources/report-resource.js';
64
+ import { TemplateResource } from '../resources/template-resource.js';
65
+ import { WorkflowResource } from '../resources/workflow-resource.js';
66
+
67
+ // Re-export this, so that classes that use Project do not need to have separate import.
68
+ export { ResourcesFrom };
69
+
70
+ /**
71
+ * Represents project folder.
72
+ */
73
+ export class Project extends CardContainer {
74
+ private resources: ResourceCollector;
75
+ private projectPaths: ProjectPaths;
76
+ private settings: ProjectConfiguration;
77
+ private validator: Validate;
78
+
79
+ // Created resources are held in a cache.
80
+ // In the cache, key is resource name, and data is resource metadata (as JSON).
81
+ private createdResources = new Map<string, JSON>();
82
+
83
+ constructor(path: string) {
84
+ super(path, '');
85
+
86
+ this.settings = new ProjectConfiguration(
87
+ join(path, '.cards', 'local', Project.projectConfigFileName),
88
+ );
89
+ this.projectPaths = new ProjectPaths(path);
90
+ this.resources = new ResourceCollector(this);
91
+
92
+ this.containerName = this.settings.name;
93
+ // todo: implement project validation
94
+ this.validator = Validate.getInstance();
95
+ this.resources.collectLocalResources();
96
+ }
97
+
98
+ // Finds specific module.
99
+ private async findModule(moduleName: string): Promise<Resource | undefined> {
100
+ return (await this.resources.resources('modules')).find(
101
+ (item) => item.name === moduleName && item.path,
102
+ );
103
+ }
104
+
105
+ // Returns (local or all) resources of a given type.
106
+ // @todo: if this would be public, we could remove cardTypes(), fieldTypes(), ... and similar APIs
107
+ private async resourcesOfType(
108
+ type: ResourceFolderType,
109
+ from: ResourcesFrom = ResourcesFrom.localOnly,
110
+ ): Promise<Resource[]> {
111
+ return this.resources.resources(type, from);
112
+ }
113
+
114
+ /**
115
+ * Add a given 'resource' to the local resource arrays.
116
+ * @param resource Resource to add.
117
+ */
118
+ public addResource(resource: Resource, data: JSON) {
119
+ this.resources.add(resource);
120
+ this.createdResources.set(resource.name, data);
121
+ }
122
+
123
+ /**
124
+ * Returns an array of all the attachments in the project card's (excluding ones in templates).
125
+ * @returns all attachments in the project.
126
+ */
127
+ public async attachments(): Promise<CardAttachment[]> {
128
+ return super.attachments(this.paths.cardRootFolder);
129
+ }
130
+
131
+ /**
132
+ * Returns an array of all the calculation files (*.lp) in the project.
133
+ * @param from Defines where resources are collected from.
134
+ * @returns array of all calculation files in the project.
135
+ */
136
+ public async calculations(
137
+ from: ResourcesFrom = ResourcesFrom.localOnly,
138
+ ): Promise<Resource[]> {
139
+ return this.resources.resources('calculations', from);
140
+ }
141
+
142
+ /**
143
+ * Returns path to card's attachment folder.
144
+ * @param cardKey card key
145
+ * @returns path to card's attachment folder.
146
+ */
147
+ public async cardAttachmentFolder(cardKey: string): Promise<string> {
148
+ // Check if it is a template card.
149
+ if (await this.isTemplateCard(cardKey)) {
150
+ const cardPath = await this.cardFolder(cardKey);
151
+ return join(cardPath, 'a');
152
+ }
153
+
154
+ const pathToProjectCard = this.pathToCard(cardKey);
155
+ return pathToProjectCard
156
+ ? join(this.paths.cardRootFolder, pathToProjectCard, 'a')
157
+ : '';
158
+ }
159
+
160
+ /**
161
+ * Returns details (as defined by cardDetails) of a card.
162
+ * @param cardKey card key (project prefix and a number, e.g. test_1)
163
+ * @param cardDetails which card details are returned.
164
+ * @returns Card details, or undefined if the card cannot be found.
165
+ */
166
+ public async cardDetailsById(
167
+ cardKey: string,
168
+ cardDetails: ProjectFetchCardDetails,
169
+ ): Promise<Card | undefined> {
170
+ return this.findSpecificCard(cardKey, cardDetails);
171
+ }
172
+
173
+ /**
174
+ * Returns path to card's folder.
175
+ * @param cardKey card key
176
+ * @returns path to card's folder.
177
+ */
178
+ public async cardFolder(cardKey: string): Promise<string> {
179
+ const found = await super.findCard(this.paths.cardRootFolder, cardKey);
180
+ if (found) {
181
+ return found.path;
182
+ }
183
+
184
+ const templates = await this.templates();
185
+ const templatePromises = templates.map(async (template) => {
186
+ const templateObject = new TemplateResource(
187
+ this,
188
+ resourceName(template.name),
189
+ ).templateObject();
190
+ const templateCard = templateObject
191
+ ? await templateObject.findSpecificCard(cardKey)
192
+ : undefined;
193
+ return templateCard ? templateCard.path : '';
194
+ });
195
+
196
+ const templatePaths = await Promise.all(templatePromises);
197
+ return templatePaths.find((path) => path !== '') || '';
198
+ }
199
+
200
+ /**
201
+ * Splits card path to parts. Returns the parts.
202
+ * Returned parts are: prefix, card key, array of parents and template name. Template name is returned only for template cards.
203
+ * @param cardPath path to a card
204
+ * @returns card path logical parts
205
+ * todo: if prefix would be parameter; this could be static, or util method
206
+ */
207
+ public cardPathParts(cardPath: string) {
208
+ const pathParts = cardPath.split(sep);
209
+ const cardKey = pathParts.at(pathParts.length - 1);
210
+ const parents = [];
211
+ let prefix = this.projectPrefix;
212
+ let template = '';
213
+ let startIndex = -1;
214
+ let templatesNameIndex = -1;
215
+
216
+ const cardRootIndex = pathParts.indexOf('cardRoot');
217
+ const projectInternalsIndex = pathParts.indexOf('.cards');
218
+
219
+ if (projectInternalsIndex === -1 && cardRootIndex >= 0) {
220
+ startIndex = projectInternalsIndex;
221
+ } else if (projectInternalsIndex >= 0 && cardRootIndex === -1) {
222
+ const templatesIndex = pathParts.indexOf('templates');
223
+ startIndex = templatesIndex;
224
+ if (templatesIndex === -1) {
225
+ throw new Error(
226
+ `Invalid card path. Template card must have 'templates' in path`,
227
+ );
228
+ }
229
+ const modulesIndex = pathParts.indexOf('modules');
230
+ if (modulesIndex !== -1) {
231
+ prefix = pathParts.at(modulesIndex + 1) || '';
232
+ }
233
+ templatesNameIndex = templatesIndex + 1;
234
+ template = `${prefix}/templates/${pathParts.at(templatesNameIndex)}`;
235
+ } else {
236
+ throw new Error(`Card must be either project card, or template card`);
237
+ }
238
+
239
+ // Look for parents in the path.
240
+ let previousWasParent = false;
241
+ for (let index = startIndex; index <= pathParts.length; index++) {
242
+ if (previousWasParent) {
243
+ previousWasParent = false;
244
+ parents.push(pathParts.at(index - 2));
245
+ }
246
+ const cardsSubFolder = pathParts.at(index) === 'c';
247
+ const ignoreOrNotTemplatesParent =
248
+ index - 1 !== templatesNameIndex || templatesNameIndex === -1;
249
+ if (cardsSubFolder && ignoreOrNotTemplatesParent) {
250
+ previousWasParent = true;
251
+ }
252
+ }
253
+
254
+ return {
255
+ cardKey: cardKey,
256
+ parents: parents,
257
+ prefix: prefix,
258
+ template: template,
259
+ };
260
+ }
261
+
262
+ /**
263
+ * Returns an array of all the cards in the project. Cards have content and metadata
264
+ * @param path Optional path from which to fetch the cards. Generally it is best to fetch from Project root, e.g. Project.cardRootFolder
265
+ * @param details Which details to include in the cards; by default only "content" and "metadata" are included.
266
+ * @returns all cards from the given path in the project.
267
+ */
268
+ public async cards(
269
+ path: string = this.paths.cardRootFolder,
270
+ details: FetchCardDetails = { content: true, metadata: true },
271
+ ): Promise<Card[]> {
272
+ return super.cards(path, details);
273
+ }
274
+
275
+ /**
276
+ * Returns an array of all the card types in the project.
277
+ * @param from Defines where resources are collected from.
278
+ * @returns array of all card types in the project.
279
+ */
280
+ public async cardTypes(
281
+ from: ResourcesFrom = ResourcesFrom.all,
282
+ ): Promise<Resource[]> {
283
+ return this.resources.resources('cardTypes', from);
284
+ }
285
+
286
+ /**
287
+ * Updates all local resources.
288
+ */
289
+ public collectLocalResources() {
290
+ this.resources.changed();
291
+ }
292
+
293
+ /**
294
+ * Updates all imported module resources.
295
+ */
296
+ public async collectModuleResources() {
297
+ await this.resources.moduleImported();
298
+ }
299
+
300
+ /**
301
+ * Returns project configuration.
302
+ * @returns project configuration.
303
+ */
304
+ public get configuration(): ProjectConfiguration {
305
+ return this.settings;
306
+ }
307
+
308
+ /**
309
+ * Creates a Template object from template Card. It is ensured that the template is part of project.
310
+ * @param card Card that is part of some template.
311
+ * @returns Template object, or undefined if card is not part of template.
312
+ */
313
+ public createTemplateObjectFromCard(card: Card): Template | undefined {
314
+ if (!card || !card.path || !isTemplateCard(card)) {
315
+ return undefined;
316
+ }
317
+ const { template } = this.cardPathParts(card.path);
318
+ return new TemplateResource(this, resourceName(template)).templateObject();
319
+ }
320
+
321
+ /**
322
+ * Returns an array of all the field types in the project.
323
+ * @param from Defines where resources are collected from.
324
+ * @returns array of all field types in the project.
325
+ */
326
+ public async fieldTypes(
327
+ from: ResourcesFrom = ResourcesFrom.all,
328
+ ): Promise<Resource[]> {
329
+ return this.resources.resources('fieldTypes', from);
330
+ }
331
+
332
+ /**
333
+ * Finds root of a project
334
+ * @param path Path where to start looking for the project root.
335
+ * @returns path to a project root, or empty string.
336
+ */
337
+ public static async findProjectRoot(path: string): Promise<string> {
338
+ const currentPath = resolve(join(path, '.cards'));
339
+ if (pathExists(currentPath)) {
340
+ return path;
341
+ }
342
+
343
+ const parentPath = resolve(path, '..');
344
+ if (parentPath === path) {
345
+ return '';
346
+ }
347
+
348
+ return Project.findProjectRoot(parentPath);
349
+ }
350
+
351
+ /**
352
+ * Returns specific card.
353
+ * @param cardToFind Card key to find
354
+ * @param details Defines which card details are included in the return values.
355
+ * @returns specific card details, or undefined if card is not part of the project.
356
+ */
357
+ public async findSpecificCard(
358
+ cardToFind: string,
359
+ details: ProjectFetchCardDetails = {},
360
+ ): Promise<Card | undefined> {
361
+ let card;
362
+
363
+ if (
364
+ details.location === CardLocation.projectOnly ||
365
+ details.location === CardLocation.all ||
366
+ !details.location
367
+ ) {
368
+ card = await super.findCard(
369
+ this.paths.cardRootFolder,
370
+ cardToFind,
371
+ details,
372
+ );
373
+ }
374
+
375
+ if (
376
+ !card &&
377
+ (details.location === CardLocation.templatesOnly ||
378
+ details.location === CardLocation.all ||
379
+ !details.location)
380
+ ) {
381
+ const templates = await this.templates();
382
+ for (const template of templates) {
383
+ const templateObject = new TemplateResource(
384
+ this,
385
+ resourceName(template.name),
386
+ ).templateObject();
387
+ if (!templateObject) continue;
388
+
389
+ // optimize: execute each find in template parallel
390
+ card = await templateObject.findSpecificCard(cardToFind, details);
391
+ if (card) {
392
+ break;
393
+ }
394
+ }
395
+ }
396
+ return card;
397
+ }
398
+
399
+ /**
400
+ * Returns an array of all the graph models in the project.
401
+ * @param from Defines where resources are collected from.
402
+ * @returns array of all the graph models in the project.
403
+ */
404
+ public async graphModels(
405
+ from: ResourcesFrom = ResourcesFrom.all,
406
+ ): Promise<Resource[]> {
407
+ return this.resources.resources('graphModels', from);
408
+ }
409
+
410
+ /**
411
+ * Returns an array of all the graph views in the project.
412
+ * @param from Defines where resources are collected from.
413
+ * @returns array of all the graph views in the project.
414
+ */
415
+ public async graphViews(
416
+ from: ResourcesFrom = ResourcesFrom.all,
417
+ ): Promise<Resource[]> {
418
+ return this.resources.resources('graphViews', from);
419
+ }
420
+
421
+ /**
422
+ * Checks if a given card is part of this project.
423
+ * @param cardKey card to check.
424
+ * @returns true if a given card is found from project, false otherwise.
425
+ */
426
+ public hasCard(cardKey: string): boolean {
427
+ return super.hasCard(cardKey, this.paths.cardRootFolder);
428
+ }
429
+
430
+ /**
431
+ * Adds a module from project.
432
+ * @param moduleName Name of the module
433
+ */
434
+ public async importModule(module: ModuleSetting) {
435
+ // Add module as a dependency.
436
+ await this.configuration.addModule(module);
437
+ await this.collectModuleResources();
438
+ }
439
+
440
+ /**
441
+ * Checks if given path is a project.
442
+ * @param path Path to a project
443
+ * @returns true, if in the given path there is a project; false otherwise
444
+ */
445
+ static isCreated(path: string): boolean {
446
+ return pathExists(join(path, 'cardRoot'));
447
+ }
448
+
449
+ /**
450
+ * Returns whether card is a template card or not
451
+ * @param cardKey card to check.
452
+ * @todo: This is only used from 'remove'. Could it use the static checker?
453
+ */
454
+ public async isTemplateCard(cardKey: string): Promise<boolean> {
455
+ const templateCards = await this.allTemplateCards();
456
+ return templateCards.find((card) => card.key === cardKey) != null;
457
+ }
458
+
459
+ /**
460
+ * Returns an array of all the link types in the project.
461
+ * @param from Defines where resources are collected from.
462
+ * @returns array of all link types in the project.
463
+ */
464
+ public async linkTypes(
465
+ from: ResourcesFrom = ResourcesFrom.all,
466
+ ): Promise<Resource[]> {
467
+ return this.resources.resources('linkTypes', from);
468
+ }
469
+
470
+ /**
471
+ * Returns an array of cards in the project, in the templates or both.
472
+ * Cards don't have content and nor metadata.
473
+ * @param includeCardsFrom Where to return cards from (project, templates, or both)
474
+ * @returns all cards in the project.
475
+ */
476
+ public async listCards(
477
+ cardsFrom: CardLocation = CardLocation.all,
478
+ ): Promise<CardListContainer[]> {
479
+ const cardListContainer: CardListContainer[] = [];
480
+ if (
481
+ cardsFrom === CardLocation.all ||
482
+ cardsFrom === CardLocation.projectOnly
483
+ ) {
484
+ const projectCards = (await super.cards(this.paths.cardRootFolder)).map(
485
+ (item) => item.key,
486
+ );
487
+ cardListContainer.push({
488
+ name: this.projectName,
489
+ type: 'project',
490
+ cards: projectCards,
491
+ });
492
+ }
493
+
494
+ if (
495
+ cardsFrom === CardLocation.all ||
496
+ cardsFrom === CardLocation.templatesOnly
497
+ ) {
498
+ const templates = await this.templates();
499
+ for (const template of templates) {
500
+ const templateObject = new TemplateResource(
501
+ this,
502
+ resourceName(template.name),
503
+ ).templateObject();
504
+ if (templateObject) {
505
+ // todo: optimization - do all this in parallel
506
+ const templateCards = await templateObject.listCards();
507
+ if (templateCards) {
508
+ cardListContainer.push({
509
+ name: template.name,
510
+ type: 'template',
511
+ cards: templateCards.map((item) => item.key),
512
+ });
513
+ }
514
+ }
515
+ }
516
+ }
517
+ return cardListContainer;
518
+ }
519
+
520
+ /**
521
+ * Return cardIDs of the cards in the project or from templates, or both.
522
+ * @param includeCardsFrom Where to return cards from (project, templates, or both)
523
+ * @returns Array of cardIDs.
524
+ * @note that cardIDs are not sorted.
525
+ */
526
+ public async listCardIds(
527
+ cardsFrom: CardLocation = CardLocation.all,
528
+ ): Promise<Set<string>> {
529
+ const promises: Promise<Set<string>>[] = [];
530
+ if (
531
+ cardsFrom === CardLocation.all ||
532
+ cardsFrom === CardLocation.projectOnly
533
+ ) {
534
+ promises.push(
535
+ super
536
+ .cards(this.paths.cardRootFolder)
537
+ .then((cards) => new Set(cards.map((card) => card.key))),
538
+ );
539
+ }
540
+ if (
541
+ cardsFrom === CardLocation.all ||
542
+ cardsFrom === CardLocation.templatesOnly
543
+ ) {
544
+ promises.push(
545
+ this.templates().then((templates) =>
546
+ Promise.allSettled(
547
+ templates.map(
548
+ (template) =>
549
+ new TemplateResource(this, resourceName(template.name)),
550
+ ),
551
+ )
552
+ .then((results) =>
553
+ results
554
+ .filter(
555
+ (
556
+ result,
557
+ ): result is PromiseFulfilledResult<TemplateResource> =>
558
+ result.status === 'fulfilled' && result.value !== null,
559
+ )
560
+ .map((result) => result.value),
561
+ )
562
+ .then((templateObjects) =>
563
+ Promise.allSettled(
564
+ templateObjects.map((obj) => obj.templateObject().listCards()),
565
+ ),
566
+ )
567
+ .then((results) => {
568
+ const templateCardIds = new Set<string>();
569
+ results
570
+ .filter(
571
+ (result): result is PromiseFulfilledResult<Card[]> =>
572
+ result.status === 'fulfilled',
573
+ )
574
+ .forEach((result) => {
575
+ result.value.forEach((card) => templateCardIds.add(card.key));
576
+ });
577
+ return templateCardIds;
578
+ }),
579
+ ),
580
+ );
581
+ }
582
+ const allCardIdSets = await Promise.all(promises);
583
+ return new Set(allCardIdSets.flatMap((set) => [...set]));
584
+ }
585
+
586
+ /**
587
+ * Returns details of a certain module.
588
+ * @param moduleName Name of the module.
589
+ * @returns module details, or undefined if workflow cannot be found.
590
+ */
591
+ public async module(moduleName: string): Promise<ModuleContent | undefined> {
592
+ const module = await this.findModule(moduleName);
593
+ if (module && module.path) {
594
+ const modulePath = join(module.path, module.name);
595
+ const moduleConfig = (await readJsonFile(
596
+ join(modulePath, Project.projectConfigFileName),
597
+ )) as ModuleContent;
598
+ return {
599
+ name: moduleConfig.name,
600
+ modules: moduleConfig.modules,
601
+ path: modulePath,
602
+ cardKeyPrefix: moduleConfig.cardKeyPrefix,
603
+ calculations: [
604
+ ...(await this.resources.collectResourcesFromModules('calculations')),
605
+ ],
606
+ cardTypes: [
607
+ ...(await this.resources.collectResourcesFromModules('cardTypes')),
608
+ ],
609
+ fieldTypes: [
610
+ ...(await this.resources.collectResourcesFromModules('fieldTypes')),
611
+ ],
612
+ graphModels: [
613
+ ...(await this.resources.collectResourcesFromModules('graphModels')),
614
+ ],
615
+ graphViews: [
616
+ ...(await this.resources.collectResourcesFromModules('graphViews')),
617
+ ],
618
+ linkTypes: [
619
+ ...(await this.resources.collectResourcesFromModules('linkTypes')),
620
+ ],
621
+ reports: [
622
+ ...(await this.resources.collectResourcesFromModules('reports')),
623
+ ],
624
+ templates: [
625
+ ...(await this.resources.collectResourcesFromModules('templates')),
626
+ ],
627
+ workflows: [
628
+ ...(await this.resources.collectResourcesFromModules('workflows')),
629
+ ],
630
+ };
631
+ }
632
+ return undefined;
633
+ }
634
+
635
+ /**
636
+ * Returns list of modules in the project.
637
+ * @returns list of modules in the project.
638
+ */
639
+ public async modules(): Promise<Resource[]> {
640
+ return this.resources.resources('modules');
641
+ }
642
+
643
+ /**
644
+ * Returns a new unique card key with project prefix (e.g. test_x649it4x).
645
+ * Random part of string will be always 8 characters in base-36 (0-9a-z)
646
+ * @returns a new card key string
647
+ * @throws if a unique key could not be created within set number of attempts
648
+ */
649
+ public newCardKey(cardIds: Set<string>): string {
650
+ const maxAttempts = 10;
651
+ const base = 36;
652
+ const length = 8;
653
+ for (let i = 0; i < maxAttempts; i++) {
654
+ // Create a key and check that there are no collisions with other keys in project
655
+ const newKey = `${this.settings.cardKeyPrefix}_${generateRandomString(base, length)}`;
656
+ if (cardIds.has(newKey)) {
657
+ continue;
658
+ } else {
659
+ cardIds.add(newKey);
660
+ }
661
+ return newKey;
662
+ }
663
+
664
+ throw 'Could not generate unique card key';
665
+ }
666
+
667
+ /**
668
+ * Returns an array of new unique card keys with project prefix (e.g. test_x649it4x).
669
+ * Random part of string will be always 8 characters in base-36 (0-9a-z)
670
+ * @returns an array of new card key strings
671
+ * @throws if a unique key could not be created within set number of attempts
672
+ */
673
+ public newCardKeys(keysToCreate: number, cardIds: Set<string>): string[] {
674
+ if (keysToCreate < 1) {
675
+ return [];
676
+ }
677
+ const createdKeys: string[] = [];
678
+ const base = 36;
679
+ const length = 8;
680
+ let maxAttempts = 10 * keysToCreate;
681
+ while (true) {
682
+ if (maxAttempts <= 0) {
683
+ throw new Error('Could not generate unique card key');
684
+ }
685
+ const newKey = `${this.settings.cardKeyPrefix}_${generateRandomString(base, length)}`;
686
+ if (cardIds.has(newKey)) {
687
+ --maxAttempts;
688
+ continue;
689
+ } else {
690
+ cardIds.add(newKey);
691
+ createdKeys.push(newKey);
692
+ }
693
+ if (createdKeys.length >= keysToCreate) {
694
+ break;
695
+ }
696
+ }
697
+ return createdKeys;
698
+ }
699
+
700
+ /**
701
+ * Getter. Returns a class that handles the project's paths.
702
+ */
703
+ public get paths(): ProjectPaths {
704
+ return this.projectPaths;
705
+ }
706
+
707
+ /**
708
+ * Returns full path to a given card.
709
+ * @param cardKey card to check path for.
710
+ * @returns path to a given card.
711
+ */
712
+ public pathToCard(cardKey: string): string {
713
+ const allFiles = getFilesSync(this.paths.cardRootFolder);
714
+ const cardIndexJsonFile = join(cardKey, Project.cardMetadataFile);
715
+ const foundFile = allFiles.find((file) => file.includes(cardIndexJsonFile));
716
+ return foundFile ? dirname(foundFile) : '';
717
+ }
718
+
719
+ /**
720
+ * Getter. Returns project name.
721
+ */
722
+ public get projectName(): string {
723
+ return this.settings.name;
724
+ }
725
+
726
+ /**
727
+ * Getter. Returns project prefix.
728
+ */
729
+ public get projectPrefix(): string {
730
+ return this.settings.cardKeyPrefix;
731
+ }
732
+
733
+ /**
734
+ * Collects all prefixes used in the project (project's own plus all from modules).
735
+ * @returns all prefixes used in the project.
736
+ * @todo - move the module prefix fetch to resource-collector.
737
+ * Make it use cached value that is only changed when module is removed/imported.
738
+ */
739
+ public async projectPrefixes(): Promise<string[]> {
740
+ const prefixes: string[] = [this.projectPrefix];
741
+ let files;
742
+ try {
743
+ files = await readdir(this.paths.modulesFolder, {
744
+ withFileTypes: true,
745
+ recursive: true,
746
+ });
747
+ const configurationFiles = files
748
+ .filter((dirent) => dirent.isFile())
749
+ .filter((dirent) => dirent.name === Project.projectConfigFileName);
750
+
751
+ const configurationPromises = configurationFiles.map(async (file) => {
752
+ const configuration = (await readJsonFile(
753
+ join(file.parentPath, file.name),
754
+ )) as ProjectSettings;
755
+ return configuration.cardKeyPrefix;
756
+ });
757
+
758
+ const configurationPrefixes = await Promise.all(configurationPromises);
759
+ prefixes.push(...configurationPrefixes);
760
+ } catch {
761
+ // do nothing if readdir throws
762
+ }
763
+
764
+ return prefixes;
765
+ }
766
+
767
+ /**
768
+ * Removes a module from project.
769
+ * @param moduleName Name of the module
770
+ */
771
+ public async removeModule(moduleName: string) {
772
+ await this.configuration.removeModule(moduleName);
773
+ await this.collectModuleResources();
774
+ }
775
+
776
+ /**
777
+ * Array of reports in the project.
778
+ * @param from Defines where resources are collected from.
779
+ * @returns array of all reports in the project.
780
+ */
781
+ public async reports(
782
+ from: ResourcesFrom = ResourcesFrom.all,
783
+ ): Promise<Resource[]> {
784
+ return this.resources.resources('reports', from);
785
+ }
786
+
787
+ /**
788
+ * Returns handlebar files from reports.
789
+ * @param from Defines where report handlebar files are collected from.
790
+ * @returns handlebar files from reports.
791
+ */
792
+ public async reportHandlerBarFiles(from: ResourcesFrom = ResourcesFrom.all) {
793
+ const reports = await this.reports(from);
794
+ const handleBarFiles: string[] = [];
795
+ for (const reportResourceName of reports) {
796
+ const name = resourceName(reportResourceName.name);
797
+ const report = new ReportResource(this, name);
798
+ handleBarFiles.push(...(await report.handleBarFiles()));
799
+ }
800
+ return handleBarFiles;
801
+ }
802
+
803
+ /**
804
+ * Removes a resource from Project.
805
+ * @param resource Resource to remove.
806
+ */
807
+ public removeResource(resource: Resource) {
808
+ this.resources.remove(resource);
809
+ this.createdResources.delete(resource.name);
810
+ }
811
+
812
+ /**
813
+ * Returns metadata from a given resource
814
+ * @param name Name of a resource
815
+ * @returns Metadata from the resource.
816
+ */
817
+ public async resource<Type>(name: string): Promise<Type | undefined> {
818
+ const resName = resourceName(name);
819
+ if (this.createdResources.has(resourceNameToString(resName))) {
820
+ const value = this.createdResources.get(
821
+ resourceNameToString(resName),
822
+ ) as unknown as Type;
823
+ return value;
824
+ }
825
+ let resource = undefined;
826
+ try {
827
+ resource = Project.resourceObject(this, resName);
828
+ } catch {
829
+ return undefined;
830
+ }
831
+ const data = resource?.data as Type;
832
+ if (!data) {
833
+ return undefined;
834
+ }
835
+ return data;
836
+ }
837
+
838
+ public get resourceCache(): Map<string, JSON> {
839
+ return this.createdResources;
840
+ }
841
+
842
+ /**
843
+ * Checks if a given resource exists in the project already.
844
+ * @param resourceType Type of resource as a string.
845
+ * @param name Valid name of resource.
846
+ * @returns boolean, true if resource exists; false otherwise.
847
+ */
848
+ public async resourceExists(
849
+ resourceType: ResourceFolderType,
850
+ name: string,
851
+ ): Promise<boolean> {
852
+ const resources = await this.resourcesOfType(
853
+ resourceType,
854
+ ResourcesFrom.all,
855
+ );
856
+ const resource = resources.find((item) => item.name === name);
857
+ return resource !== undefined;
858
+ }
859
+
860
+ /**
861
+ * Instantiates resource object from project with a resource name.
862
+ * @note that this is memory based object only.
863
+ * To manipulate the resource (create files, delete files etc), use the resource object's API.
864
+ * @param project Project from which resources are created from.
865
+ * @param name Resource name
866
+ * @throws if called with unsupported resource type.
867
+ * @returns Created resource.
868
+ */
869
+ public static resourceObject(project: Project, name: ResourceName) {
870
+ if (name.type === 'cardTypes') {
871
+ return new CardTypeResource(project, name);
872
+ } else if (name.type === 'fieldTypes') {
873
+ return new FieldTypeResource(project, name);
874
+ } else if (name.type === 'graphModels') {
875
+ return new GraphModelResource(project, name);
876
+ } else if (name.type === 'graphViews') {
877
+ return new GraphViewResource(project, name);
878
+ } else if (name.type === 'linkTypes') {
879
+ return new LinkTypeResource(project, name);
880
+ } else if (name.type === 'reports') {
881
+ return new ReportResource(project, name);
882
+ } else if (name.type === 'templates') {
883
+ return new TemplateResource(project, name);
884
+ } else if (name.type === 'workflows') {
885
+ return new WorkflowResource(project, name);
886
+ }
887
+ throw new Error(
888
+ `Unsupported resource type '${resourceNameToString(name)}'`,
889
+ );
890
+ }
891
+
892
+ /**
893
+ * Shows details of a project.
894
+ * @returns details of a project.
895
+ */
896
+ public async show(): Promise<ProjectMetadata> {
897
+ return {
898
+ name: this.containerName,
899
+ path: this.basePath,
900
+ prefix: this.projectPrefix,
901
+ modules: (await this.modules()).map((item) => item.name),
902
+ numberOfCards: (await this.listCards(CardLocation.projectOnly))[0].cards
903
+ .length,
904
+ };
905
+ }
906
+
907
+ /**
908
+ * Show cards of a project.
909
+ * @returns an array of all project cards in the project.
910
+ */
911
+ public async showProjectCards(): Promise<Card[]> {
912
+ const cards: Card[] = [];
913
+ const cardPathMap = new Map<string, Card>();
914
+ const entries = await readdir(this.paths.cardRootFolder, {
915
+ withFileTypes: true,
916
+ recursive: true,
917
+ });
918
+
919
+ // Checks if Dirent folder is a card folder
920
+ function cardFolder(
921
+ entry: Dirent,
922
+ cardPathMap: Map<string, Card>,
923
+ ): Card | undefined {
924
+ const fullPath = join(entry.parentPath, entry.name);
925
+ if (!cardPathMap.has(fullPath)) {
926
+ const newCard: Card = {
927
+ key: entry.name,
928
+ path: fullPath,
929
+ children: [],
930
+ attachments: [],
931
+ };
932
+ cardPathMap.set(fullPath, newCard);
933
+ return newCard;
934
+ }
935
+ }
936
+
937
+ // Process card directories first
938
+ entries
939
+ .filter((entry) => entry.isDirectory() && CardNameRegEx.test(entry.name))
940
+ .forEach((entry) => {
941
+ const card = cardFolder(entry, cardPathMap);
942
+ if (card) cards.push(card);
943
+ });
944
+
945
+ // Process metadata files in parallel
946
+ await Promise.all(
947
+ entries
948
+ .filter(
949
+ (entry) => entry.isFile() && entry.name === Project.cardMetadataFile,
950
+ )
951
+ .map(async (entry) => {
952
+ const parentCard = cardPathMap.get(entry.parentPath);
953
+ if (!parentCard) return;
954
+ parentCard.metadata = (await readJsonFile(
955
+ join(entry.parentPath, entry.name),
956
+ )) as CardMetadata;
957
+ }),
958
+ );
959
+
960
+ // Finally, build the card hierarchy
961
+ Array.from(cardPathMap.entries()).map(([cardPath, card]) => {
962
+ const parentPath = findParentPath(cardPath);
963
+ if (!parentPath) return;
964
+ const parentCard = cardPathMap.get(parentPath);
965
+ if (!parentCard) return;
966
+
967
+ parentCard.children.push(card);
968
+ const index = cards.indexOf(card);
969
+ if (index > -1) {
970
+ cards.splice(index, 1);
971
+ }
972
+ });
973
+ return cards;
974
+ }
975
+
976
+ /**
977
+ * Returns all template cards from the project. This includes all module templates' cards.
978
+ * @param cardDetails which details to fetch. Optional.
979
+ * @returns all the template cards from the project
980
+ */
981
+ public async allTemplateCards(
982
+ cardDetails?: FetchCardDetails,
983
+ ): Promise<Card[]> {
984
+ const templates = await this.templates();
985
+ const cards: Card[] = [];
986
+ for (const template of templates) {
987
+ const templateCards = await this.templateCards(
988
+ template.name,
989
+ cardDetails,
990
+ );
991
+ if (templateCards) cards.push(...templateCards);
992
+ }
993
+ return cards;
994
+ }
995
+
996
+ /**
997
+ * Returns cards from single template.
998
+ * @param templateName Name of the template
999
+ * @param cardDetails Card information
1000
+ * @returns List of cards from template.
1001
+ */
1002
+ public async templateCards(
1003
+ templateName: string,
1004
+ cardDetails?: FetchCardDetails,
1005
+ ): Promise<Card[]> {
1006
+ const templateObject = new TemplateResource(
1007
+ this,
1008
+ resourceName(templateName),
1009
+ ).templateObject();
1010
+ return await templateObject?.cards('', cardDetails);
1011
+ }
1012
+
1013
+ /**
1014
+ * Array of templates in the project.
1015
+ * @param from Defines where resources are collected from.
1016
+ * @returns array of all templates in the project.
1017
+ */
1018
+ public async templates(
1019
+ from: ResourcesFrom = ResourcesFrom.all,
1020
+ ): Promise<Resource[]> {
1021
+ return this.resources.resources('templates', from);
1022
+ }
1023
+
1024
+ /**
1025
+ * Update card content.
1026
+ * @param cardKey card's ID that is updated.
1027
+ * @param content changed content
1028
+ */
1029
+ public async updateCardContent(cardKey: string, content: string) {
1030
+ const card = await this.findCard(this.basePath, cardKey, {
1031
+ metadata: true,
1032
+ content: true,
1033
+ });
1034
+ if (!card) {
1035
+ throw new Error(`Card '${cardKey}' does not exist in the project`);
1036
+ }
1037
+ card.content = content;
1038
+ await this.saveCard(card);
1039
+ }
1040
+
1041
+ /**
1042
+ * Updates card metadata's single key.
1043
+ * @param cardKey card that is updated.
1044
+ * @param changedKey changed metadata key
1045
+ * @param newValue changed value for the key
1046
+ */
1047
+ public async updateCardMetadataKey(
1048
+ cardKey: string,
1049
+ changedKey: string,
1050
+ newValue: MetadataContent,
1051
+ ) {
1052
+ const templateCard = await this.isTemplateCard(cardKey);
1053
+ const card = await this.findCard(
1054
+ templateCard ? this.paths.templatesFolder : this.paths.cardRootFolder,
1055
+ cardKey,
1056
+ {
1057
+ metadata: true,
1058
+ },
1059
+ );
1060
+ if (!card) {
1061
+ throw new Error(`Card '${cardKey}' does not exist in the project`);
1062
+ }
1063
+
1064
+ if (!card.metadata || card.metadata[changedKey] === newValue) {
1065
+ return;
1066
+ }
1067
+ const cardAsRecord: Record<string, MetadataContent> = card.metadata;
1068
+ cardAsRecord[changedKey] = newValue;
1069
+
1070
+ const invalidCard = isTemplateCard(card)
1071
+ ? ''
1072
+ : await this.validateCard(card);
1073
+ if (invalidCard.length !== 0) {
1074
+ throw new Error(invalidCard);
1075
+ }
1076
+
1077
+ await this.saveCardMetadata(card);
1078
+ }
1079
+
1080
+ /**
1081
+ * Updates card metadata.
1082
+ * @param card affected card
1083
+ * @param changedMetadata changed content for the card
1084
+ */
1085
+ public async updateCardMetadata(card: Card, changedMetadata: CardMetadata) {
1086
+ card.metadata = changedMetadata;
1087
+ return this.saveCardMetadata(card);
1088
+ }
1089
+
1090
+ /**
1091
+ * Validates that card's data is valid.
1092
+ * @param card Card to validate.
1093
+ */
1094
+ public async validateCard(card: Card): Promise<string> {
1095
+ const invalidCustomData = await this.validator.validateCustomFields(
1096
+ this,
1097
+ card,
1098
+ );
1099
+ const invalidWorkFlow = await this.validator.validateWorkflowState(
1100
+ this,
1101
+ card,
1102
+ );
1103
+
1104
+ const invalidLabels = await this.validator.validateCardLabels(card);
1105
+ if (
1106
+ invalidCustomData.length === 0 &&
1107
+ invalidWorkFlow.length === 0 &&
1108
+ invalidLabels.length === 0
1109
+ ) {
1110
+ return '';
1111
+ }
1112
+ const errors: string[] = [];
1113
+ if (invalidCustomData.length > 0) {
1114
+ errors.push(invalidCustomData);
1115
+ }
1116
+ if (invalidWorkFlow.length > 0) {
1117
+ errors.push(invalidWorkFlow);
1118
+ }
1119
+ if (invalidLabels.length > 0) {
1120
+ errors.push(invalidLabels);
1121
+ }
1122
+ return errors.join('\n');
1123
+ }
1124
+
1125
+ /**
1126
+ * Array of workflows in the project.
1127
+ * @param from Defines where resources are collected from.
1128
+ * @returns array of all workflows in the project.
1129
+ */
1130
+ public async workflows(
1131
+ from: ResourcesFrom = ResourcesFrom.all,
1132
+ ): Promise<Resource[]> {
1133
+ return this.resources.resources('workflows', from);
1134
+ }
1135
+ }