@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,689 @@
1
+ /**
2
+ Cyberismo
3
+ Copyright © Cyberismo Ltd and contributors 2024
4
+ This program is free software: you can redistribute it and/or modify it under
5
+ the terms of the GNU Affero General Public License version 3 as published by
6
+ the Free Software Foundation.
7
+ This program is distributed in the hope that it will be useful, but WITHOUT
8
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
9
+ FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
10
+ details. You should have received a copy of the GNU Affero General Public
11
+ License along with this program. If not, see <https://www.gnu.org/licenses/>.
12
+ */
13
+ import { basename, dirname, extname, join, parse, resolve } from 'node:path';
14
+ import { fileURLToPath } from 'node:url';
15
+ import { readdir } from 'node:fs/promises';
16
+ // dependencies
17
+ import { Validator as JSONValidator } from 'jsonschema';
18
+ import { Validator as DirectoryValidator } from 'directory-schema-validator';
19
+ import { parentSchema, schemas } from '@cyberismocom/resources';
20
+ import { errorFunction } from '../utils/log-utils.js';
21
+ import { isTemplateCard } from '../utils/card-utils.js';
22
+ import { pathExists } from '../utils/file-utils.js';
23
+ import { Project } from '../containers/project.js';
24
+ import { readJsonFile } from '../utils/json.js';
25
+ import { resourceName } from '../utils/resource-utils.js';
26
+ const invalidNames = new RegExp('[<>:"/\\|?*\x00-\x1F]|^(?:aux|con|clock$|nul|prn|com[1-9]|lpt[1-9])$');
27
+ const SHORT_TEXT_MAX_LENGTH = 80;
28
+ import * as EmailValidator from 'email-validator';
29
+ import { evaluateMacros } from '../macros/index.js';
30
+ const baseDir = dirname(fileURLToPath(import.meta.url));
31
+ const subFoldersToValidate = ['.cards', 'cardRoot'];
32
+ /**
33
+ * Validates content.
34
+ */
35
+ export class Validate {
36
+ static instance;
37
+ validator;
38
+ directoryValidator;
39
+ parentSchema;
40
+ validatedCardTypes;
41
+ validatedWorkflows;
42
+ validatedFieldTypes;
43
+ static baseFolder;
44
+ static jsonFileExtension = '.json';
45
+ static parentSchemaFile;
46
+ static schemaConfigurationFile = '.schema';
47
+ static projectConfigurationFile = 'cardsConfig.json';
48
+ static cardMetadataFile = 'index.json';
49
+ static dotSchemaSchemaId = '/dotSchema';
50
+ static parameterSchemaFile = 'parameterSchema.json';
51
+ constructor() {
52
+ Validate.baseFolder = pathExists(join(process.cwd(), '../../schema', 'cardTreeDirectorySchema.json'))
53
+ ? join(process.cwd(), '../../schema')
54
+ : join(baseDir, '../../../schema');
55
+ Validate.parentSchemaFile = join(Validate.baseFolder, 'cardTreeDirectorySchema.json');
56
+ this.validator = new JSONValidator();
57
+ this.directoryValidator = new DirectoryValidator();
58
+ this.parentSchema = parentSchema;
59
+ this.addChildSchemas();
60
+ this.validatedFieldTypes = new Map();
61
+ this.validatedWorkflows = new Map();
62
+ this.validatedCardTypes = new Map();
63
+ }
64
+ // Helper to get length from types when needed.
65
+ length(item) {
66
+ return item.length;
67
+ }
68
+ // Loads child schemas to validator.
69
+ addChildSchemas() {
70
+ schemas.forEach((schema) => {
71
+ this.validator.addSchema(schema, schema.$id);
72
+ });
73
+ }
74
+ // Validates that 'name' in resources matches filename, location and project prefix.
75
+ checkResourceName(file, content, projectPrefixes) {
76
+ const errors = [];
77
+ const fullFileNameWithPath = this.fullPath(file);
78
+ // Exclude cardsConfig.json, .schemas and resource specific JSON files.
79
+ if (file.name !== Validate.projectConfigurationFile &&
80
+ file.name !== Validate.cardMetadataFile &&
81
+ file.name !== Validate.dotSchemaSchemaId &&
82
+ file.name !== Validate.parameterSchemaFile) {
83
+ const namedContent = content;
84
+ if (!namedContent.name) {
85
+ errors.push(`File '${file.name}' does not contain 'name' property. Cannot validate resource's 'name'.`);
86
+ return errors;
87
+ }
88
+ const { identifier, prefix, type } = resourceName(namedContent.name);
89
+ const filenameWithoutExtension = parse(file.name).name;
90
+ if (!projectPrefixes.includes(prefix)) {
91
+ errors.push(`Wrong prefix in resource '${namedContent.name}'. Project prefixes are '[${projectPrefixes.join(', ')}]'`);
92
+ }
93
+ if (identifier !== filenameWithoutExtension) {
94
+ errors.push(`Resource 'name' ${namedContent.name} mismatch with file path '${fullFileNameWithPath}'`);
95
+ }
96
+ if (!fullFileNameWithPath.includes(type)) {
97
+ errors.push(`Wrong type name in resource '${namedContent.name}'. Should match filename path: '${fullFileNameWithPath}'`);
98
+ }
99
+ }
100
+ return errors;
101
+ }
102
+ // Return full path and filename.
103
+ fullPath(file) {
104
+ return join(file.parentPath, file.name);
105
+ }
106
+ // Puts resource to a local cache if found and returns the resource.
107
+ // If value is already cached, returns from cache.
108
+ async getAndCacheResource(project, cachedValues, valueName) {
109
+ return (cachedValues.get(valueName) ||
110
+ project.resource(valueName).then((resource) => {
111
+ if (!resource) {
112
+ return undefined;
113
+ }
114
+ cachedValues.set(valueName, resource);
115
+ return resource;
116
+ }));
117
+ }
118
+ parseValidatorMessage(errorObject) {
119
+ let parsedErrorMessage = '';
120
+ // todo: get schema name here?
121
+ for (const error of errorObject) {
122
+ let instancePath = '';
123
+ let params = '';
124
+ let message = '';
125
+ let fileError = false;
126
+ if (Object.prototype.hasOwnProperty.call(error, 'instancePath')) {
127
+ const temp = Object(error)['instancePath'];
128
+ if (temp.endsWith('files'))
129
+ fileError = true;
130
+ instancePath = temp;
131
+ instancePath = instancePath.replace(/\/directories/g, '');
132
+ instancePath = instancePath.replace(/\/files/g, '');
133
+ if (instancePath === '') {
134
+ instancePath = 'project root';
135
+ }
136
+ if (instancePath[0] === '/') {
137
+ instancePath = instancePath.slice(1);
138
+ }
139
+ }
140
+ if (Object.prototype.hasOwnProperty.call(error, 'params')) {
141
+ params = Object(error)['params']['additionalProperty'];
142
+ }
143
+ if (Object.prototype.hasOwnProperty.call(error, 'message')) {
144
+ message = Object(error)['message'];
145
+ message = message.replace('must have required property', fileError ? 'must have file' : 'must have subdirectory');
146
+ if (message === 'must NOT have additional properties') {
147
+ message = message.replace('must NOT have additional properties', 'non-allowed additional property');
148
+ message = message + `: ${params}`;
149
+ }
150
+ }
151
+ parsedErrorMessage += `\nAt '${instancePath}' ${message}`;
152
+ }
153
+ return parsedErrorMessage;
154
+ }
155
+ // Handles reading and validating 'contentSchema' in a directory.
156
+ async readAndValidateContentFiles(project, path) {
157
+ const message = [];
158
+ try {
159
+ const prefixes = await project.projectPrefixes();
160
+ const files = await readdir(path, {
161
+ withFileTypes: true,
162
+ });
163
+ const foldersToValidate = files.filter((dirent) => dirent.isDirectory() && subFoldersToValidate.includes(dirent.name));
164
+ // Validate subfolders parallel.
165
+ const promises = [];
166
+ foldersToValidate.forEach((folder) => {
167
+ promises.push(this.validateFolder(prefixes, folder));
168
+ });
169
+ const result = await Promise.all(promises);
170
+ message.push(...result.flat(1));
171
+ }
172
+ catch (error) {
173
+ throw new Error(errorFunction(error));
174
+ }
175
+ return message;
176
+ }
177
+ // Removes same items from an array.
178
+ removeDuplicateEntries(value, index, array) {
179
+ return array.indexOf(value) === index;
180
+ }
181
+ // Validate one subfolder.
182
+ async validateFolder(prefixes, path) {
183
+ const messages = [];
184
+ const files = await readdir(this.fullPath(path), {
185
+ withFileTypes: true,
186
+ recursive: true,
187
+ });
188
+ const schemaFiles = files.filter((dirent) => dirent.isFile() && dirent.name === Validate.schemaConfigurationFile);
189
+ messages.push(...(await this.validateSchemaFiles(schemaFiles)));
190
+ // no point in validating contents if .schema files are not valid
191
+ if (messages.length !== 0) {
192
+ return messages;
193
+ }
194
+ const schemaConfigs = (await Promise.all(schemaFiles.map(async (dirent) => ({
195
+ dirent,
196
+ content: await readJsonFile(this.fullPath(dirent)),
197
+ })))).reduce((acc, { dirent, content }) => {
198
+ acc[dirent.parentPath] = content;
199
+ return acc;
200
+ }, {});
201
+ // Fetches nearest parent's .schema file.
202
+ function schemaConfigFile(path, schemaConfigs) {
203
+ let schemas = schemaConfigs[path];
204
+ let parentPath = path;
205
+ while (!schemas) {
206
+ parentPath = resolve(parentPath, '..');
207
+ if (dirname(parentPath) === parentPath) {
208
+ break;
209
+ }
210
+ schemas = schemaConfigs[parentPath];
211
+ }
212
+ return schemas;
213
+ }
214
+ // Go through every file
215
+ for (const file of files.filter((dirent) => dirent.isFile() &&
216
+ dirent.name !== Validate.schemaConfigurationFile &&
217
+ extname(dirent.name) === Validate.jsonFileExtension)) {
218
+ const fullPath = this.fullPath(file);
219
+ const content = await readJsonFile(fullPath);
220
+ const nameErrors = this.checkResourceName(file, content, prefixes);
221
+ if (nameErrors) {
222
+ messages.push(...nameErrors);
223
+ }
224
+ const schemas = schemaConfigFile(file.parentPath, schemaConfigs);
225
+ // if schema is not defined for the directory, skip it
226
+ if (!schemas) {
227
+ continue;
228
+ }
229
+ const fileSchema = schemas.find((schema) => schema.file === file.name || (schemas.length === 1 && !schema.file));
230
+ if (!fileSchema) {
231
+ continue;
232
+ }
233
+ if (!fileSchema.id.startsWith('/')) {
234
+ fileSchema.id = '/' + fileSchema.id;
235
+ }
236
+ const schema = this.validator.schemas[fileSchema.id];
237
+ if (!schema) {
238
+ throw new Error(`Unknown schema name '${fileSchema.id}', aborting.`);
239
+ }
240
+ const result = this.validator.validate(content, schema);
241
+ for (const error of result.errors) {
242
+ const msg = `Validation error from '${fullPath}': ${error.message}.`;
243
+ messages.push(msg);
244
+ }
245
+ }
246
+ return messages;
247
+ }
248
+ // Handles validating .schema files
249
+ async validateSchemaFiles(files) {
250
+ const schema = this.validator.schemas[Validate.dotSchemaSchemaId];
251
+ if (!schema) {
252
+ throw new Error(`'${Validate.dotSchemaSchemaId}' schema not found`);
253
+ }
254
+ const message = [];
255
+ for (const file of files) {
256
+ const fullPath = this.fullPath(file);
257
+ const result = this.validator.validate(await readJsonFile(fullPath), schema);
258
+ for (const error of result.errors) {
259
+ const msg = `Validation error from '${fullPath}': ${error.message}.`;
260
+ message.push(msg);
261
+ }
262
+ }
263
+ return message;
264
+ }
265
+ // Validate array of custom field names
266
+ async validateArrayOfFields(project, cardType, fieldArray, nameOfArray) {
267
+ const errors = [];
268
+ if (cardType && fieldArray) {
269
+ const validationPromises = fieldArray.map(async (field) => {
270
+ const fieldType = await this.getAndCacheResource(project, this.validatedFieldTypes, field);
271
+ if (!fieldType) {
272
+ return `Card type '${cardType.name}' has invalid reference to unknown ${nameOfArray} '${field}'`;
273
+ }
274
+ return null;
275
+ });
276
+ const results = await Promise.all(validationPromises);
277
+ errors.push(...results.filter((result) => result !== null));
278
+ }
279
+ return errors;
280
+ }
281
+ // Validates that arrays have only string elements.
282
+ validateListValues(list) {
283
+ let valid = true;
284
+ list.forEach((value) => {
285
+ if (typeof value !== 'string') {
286
+ valid = false;
287
+ }
288
+ });
289
+ return valid;
290
+ }
291
+ // Validates that card's dataType can be used with JS types.
292
+ validType(value, fieldType) {
293
+ const field = fieldType.dataType;
294
+ const typeOfValue = typeof value;
295
+ // Nulls are always accepted.
296
+ if (typeOfValue === 'object' && value === null) {
297
+ return true;
298
+ }
299
+ if (field === 'date' || field === 'dateTime') {
300
+ return !isNaN(Date.parse(value));
301
+ }
302
+ if (field === 'list') {
303
+ return Array.isArray(value) && this.validateListValues(value);
304
+ }
305
+ if (field === 'boolean' || field === 'number') {
306
+ return typeOfValue === field;
307
+ }
308
+ if (field === 'shortText') {
309
+ return (typeOfValue === 'string' &&
310
+ this.length(value) <= SHORT_TEXT_MAX_LENGTH);
311
+ }
312
+ if (field === 'longText') {
313
+ return typeOfValue === 'string';
314
+ }
315
+ if (field === 'integer') {
316
+ return typeOfValue === 'number' && Number.isInteger(value);
317
+ }
318
+ if (field === 'person') {
319
+ // Accept empty names
320
+ return (value === undefined ||
321
+ EmailValidator.validate(value) ||
322
+ this.length(value) === 0);
323
+ }
324
+ if (field === 'enum') {
325
+ const found = fieldType.enumValues?.find((item) => item.enumValue === value);
326
+ return found ? true : false;
327
+ }
328
+ console.error(`Type ${field} is not supported`);
329
+ return false;
330
+ }
331
+ /**
332
+ * Validates that new identifier of a resource is according to naming convention.
333
+ * @param identifier: resource identifier
334
+ * returns true if identifier is valid, and false otherwise.
335
+ */
336
+ static isValidIdentifierName(identifier) {
337
+ const validIdentifier = new RegExp('^[A-Za-z0-9 ._-]+$');
338
+ const contentValidated = validIdentifier.test(identifier);
339
+ const lengthValidated = identifier.length > 0 && identifier.length < 256;
340
+ const notInvalidIdentifier = !invalidNames.test(identifier);
341
+ return contentValidated && lengthValidated && notInvalidIdentifier;
342
+ }
343
+ /**
344
+ * Validates that 'name' can be used as a project name.
345
+ * @param name project name
346
+ * @returns true if name is valid, and false otherwise.
347
+ * @note that on Windows, if path + filename is longer than 256 characters, some file operations
348
+ * are not possible. Thus, setting the maximum length of project name to 64 characters.
349
+ * The 192 characters usually should be enough for the path.
350
+ */
351
+ static isValidProjectName(name) {
352
+ const validName = new RegExp('^[A-Za-z ._-]+$');
353
+ const contentValidated = validName.test(name);
354
+ const lengthValidated = name.length > 0 && name.length < 64;
355
+ const notInvalidName = !invalidNames.test(name);
356
+ return contentValidated && lengthValidated && notInvalidName;
357
+ }
358
+ /**
359
+ * Validates that 'name' can be used as label name.
360
+ * Labels are less restricted than other names, as they are never file names.
361
+ * @param name label name
362
+ * @returns true if name is valid, and false otherwise.
363
+ */
364
+ static isValidLabelName(name) {
365
+ const validName = new RegExp('^[-a-zA-Z0-9._-]+(?: [a-zA-Z0-9._-]+)*$');
366
+ const contentValidated = validName.test(name);
367
+ const lengthValidated = name.length > 0 && name.length < 256;
368
+ return contentValidated && lengthValidated;
369
+ }
370
+ /**
371
+ * Validates that a given directory path (and its children) conform to a JSON schema.
372
+ * @note Validates also content in the directory tree, if .schema file is found.
373
+ * @param projectPath path to validate.
374
+ * @returns string containing all validation errors
375
+ */
376
+ async validate(projectPath) {
377
+ let validationErrors = '';
378
+ this.validatedFieldTypes.clear();
379
+ this.validatedWorkflows.clear();
380
+ this.validatedCardTypes.clear();
381
+ try {
382
+ // First, validate that the directory content conforms to the schema.
383
+ const valid = this.directoryValidator.validate(this.parentSchema, projectPath);
384
+ if (!valid && this.directoryValidator.errors) {
385
+ const errorMsg = this.parseValidatorMessage(this.directoryValidator.errors);
386
+ if (errorMsg) {
387
+ validationErrors = errorMsg;
388
+ }
389
+ return validationErrors;
390
+ }
391
+ else {
392
+ const errorMsg = [];
393
+ const project = new Project(projectPath);
394
+ // Then, validate that each 'contentSchema' children as well.
395
+ const result = await this.readAndValidateContentFiles(project, projectPath);
396
+ if (result.length > 0) {
397
+ errorMsg.push(...result);
398
+ }
399
+ // Finally, validate that each card is correct
400
+ const cards = await project.cards();
401
+ cards.push(...(await project.allTemplateCards()));
402
+ for (const card of cards) {
403
+ if (card.metadata) {
404
+ // validate card's workflow
405
+ if (!isTemplateCard(card)) {
406
+ const validWorkflow = await this.validateWorkflowState(project, card);
407
+ if (validWorkflow.length !== 0) {
408
+ errorMsg.push(validWorkflow);
409
+ }
410
+ }
411
+ }
412
+ const validCustomFields = await this.validateCustomFields(project, card);
413
+ if (validCustomFields.length !== 0) {
414
+ errorMsg.push(validCustomFields);
415
+ }
416
+ const validLabels = await this.validateCardLabels(card);
417
+ if (validLabels.length > 0) {
418
+ errorMsg.push(validLabels);
419
+ }
420
+ // Validate macros in content
421
+ if (card.content) {
422
+ await evaluateMacros(card.content, {
423
+ mode: 'validate',
424
+ projectPath,
425
+ cardKey: card.key,
426
+ });
427
+ }
428
+ }
429
+ if (errorMsg.length) {
430
+ validationErrors += errorMsg
431
+ .filter(this.removeDuplicateEntries)
432
+ .join('\n');
433
+ }
434
+ }
435
+ }
436
+ catch (error) {
437
+ validationErrors += errorFunction(error);
438
+ }
439
+ return validationErrors;
440
+ }
441
+ /**
442
+ * Validates folder name.
443
+ * @todo: This should check that the path is resolvable and can be used as a folder name in various operating systems.
444
+ * @param path path to a folder
445
+ * @returns true, if the path is valid and can be used; false otherwise.
446
+ */
447
+ static validateFolder(path) {
448
+ if (path === '' || path === '.' || path === '..') {
449
+ return false;
450
+ }
451
+ return !invalidNames.test(basename(path));
452
+ }
453
+ /**
454
+ * Validates that 'object' conforms to JSON schema 'schemaId'.
455
+ * @param content Object to validate.
456
+ * @param schemaId Schema ID to identify a JSON schema.
457
+ * @returns string containing all validation errors
458
+ */
459
+ validateJson(content, schemaId) {
460
+ const validationErrors = [];
461
+ if (!schemaId.startsWith('/')) {
462
+ schemaId = '/' + schemaId;
463
+ }
464
+ if (this.validator.schemas[schemaId] === undefined) {
465
+ validationErrors.push(`Unknown schema ${schemaId}`);
466
+ }
467
+ else {
468
+ const result = this.validator.validate(content, this.validator.schemas[schemaId]);
469
+ for (const error of result.errors) {
470
+ const msg = `Schema '${schemaId}' validation Error: ${error.message}\n`;
471
+ validationErrors.push(msg);
472
+ }
473
+ }
474
+ return validationErrors.join('\n');
475
+ }
476
+ /**
477
+ * Validate that resource names and identifiers are valid.
478
+ * @param resourceType Type of resource
479
+ * @param name Name of resource
480
+ * @param prefixes currently used project prefixes
481
+ * @returns resource name as valid resource name; throws in error cases.
482
+ */
483
+ async validResourceName(resourceType, name, prefixes) {
484
+ const resource = resourceName(name);
485
+ resource.type = resource.type ? resource.type : resourceType;
486
+ // a bit shaky way to ensure that prefix is set; first of the project prefixes should be the actual project prefix.
487
+ if (resource.prefix === '') {
488
+ resource.prefix = prefixes.length > 0 ? prefixes.at(0) || '' : '';
489
+ if (resource.prefix === '') {
490
+ throw new Error(`Project prefix cannot be empty string`);
491
+ }
492
+ }
493
+ if (!prefixes.includes(resource.prefix)) {
494
+ throw new Error(`Resource name can only refer to project that it is part of. Prefix '${resource.prefix}' is not included in '[${prefixes.join(',')}]'`);
495
+ }
496
+ if (resourceType !== resource.type) {
497
+ throw new Error(`Resource name must match the resource type. Type '${resource.type}' does not match '${resourceType}'`);
498
+ }
499
+ if (!Validate.isValidIdentifierName(resource.identifier)) {
500
+ throw new Error(`Resource identifier must follow naming rules. Identifier '${resource.identifier}' is invalid`);
501
+ }
502
+ return `${resource.prefix}/${resourceType}/${resource.identifier}`;
503
+ }
504
+ /**
505
+ * Validates that 'prefix' is valid project prefix.
506
+ * @param prefix project prefix
507
+ * @returns true, if prefix can be used as project prefix, false otherwise.
508
+ */
509
+ static validatePrefix(prefix) {
510
+ const validPrefix = new RegExp('^[a-z]+$');
511
+ const contentValidated = validPrefix.test(prefix);
512
+ const lengthValidated = prefix.length > 2 && prefix.length < 11;
513
+ return contentValidated && lengthValidated;
514
+ }
515
+ /**
516
+ * Validate schema that matches schemaId from path.
517
+ * @param projectPath path to schema
518
+ * @param schemaId schema's id
519
+ * @returns string containing all validation errors
520
+ * @todo - unused; remove?
521
+ */
522
+ async validateSchema(projectPath, schemaId) {
523
+ const validationErrors = [];
524
+ if (!schemaId.startsWith('/')) {
525
+ schemaId = '/' + schemaId;
526
+ }
527
+ const activeJsonSchema = this.validator.schemas[schemaId];
528
+ if (activeJsonSchema === undefined) {
529
+ throw new Error(`Unknown schema '${schemaId}'`);
530
+ }
531
+ else {
532
+ let contentFile = '';
533
+ try {
534
+ contentFile = await readJsonFile(projectPath);
535
+ }
536
+ catch {
537
+ throw new Error(`Path is not valid ${projectPath}`);
538
+ }
539
+ const result = this.validator.validate(contentFile, activeJsonSchema);
540
+ for (const error of result.errors) {
541
+ const msg = `Schema '${schemaId}' validation Error: ${error.message}\n`;
542
+ validationErrors.push(msg);
543
+ }
544
+ }
545
+ return validationErrors.join('\n');
546
+ }
547
+ /**
548
+ * Validates that card's custom fields are according to schema and have correct data in them.
549
+ * @param project currently used Project
550
+ * @param card specific card
551
+ * @returns string containing all validation errors
552
+ */
553
+ async validateCustomFields(project, card) {
554
+ const validationErrors = [];
555
+ if (!card.metadata) {
556
+ throw new Error(`Card '${card.key}' has no metadata. Card object needs to be instantiated with '{metadata: true}'`);
557
+ }
558
+ const cardType = await this.getAndCacheResource(project, this.validatedCardTypes, card.metadata?.cardType);
559
+ if (!cardType) {
560
+ validationErrors.push(`Card '${card.key}' has invalid card type '${card.metadata?.cardType}'`);
561
+ return validationErrors.join('\n');
562
+ }
563
+ // Check that arrays of field types refer to existing fields.
564
+ let fieldErrors = await this.validateArrayOfFields(project, cardType, cardType.optionallyVisibleFields, 'optionally visible fields');
565
+ validationErrors.push(...fieldErrors);
566
+ fieldErrors = await this.validateArrayOfFields(project, cardType, cardType.alwaysVisibleFields, 'always visible fields');
567
+ validationErrors.push(...fieldErrors);
568
+ for (const field of cardType.customFields) {
569
+ const found = await project.resourceExists('fieldTypes', field.name);
570
+ if (!found) {
571
+ validationErrors.push(`Custom field '${field.name}' from card type '${cardType.name}' not found from project`);
572
+ }
573
+ if (field.isCalculated) {
574
+ if (card.metadata[field.name] !== undefined) {
575
+ validationErrors.push(`Card '${card.key}' not allowed to have a value in a calculated field '${field.name}'`);
576
+ }
577
+ continue;
578
+ }
579
+ else {
580
+ if (card.metadata[field.name] === undefined) {
581
+ validationErrors.push(`Card '${card.key}' is missing custom field '${field.name}'`);
582
+ continue;
583
+ }
584
+ }
585
+ const fieldType = await this.getAndCacheResource(project, this.validatedFieldTypes, field.name);
586
+ if (!fieldType) {
587
+ validationErrors.push(`In card '${card.key}' field '${field.name}' is missing from project\n`);
588
+ continue;
589
+ }
590
+ if (!this.validType(card.metadata[field.name], fieldType)) {
591
+ const typeOfValue = typeof card.metadata[field.name];
592
+ let fieldValue = card.metadata[field.name];
593
+ if (typeOfValue === 'string') {
594
+ fieldValue = card.metadata[field.name]
595
+ ? `"${card.metadata[field.name]}"`
596
+ : '""';
597
+ }
598
+ if (fieldType.dataType === 'enum') {
599
+ const listOfEnumValues = fieldType.enumValues?.map((item) => item.enumValue);
600
+ validationErrors.push(`In card '${card.key}' field '${field.name}' is defined as '${fieldType.dataType}', possible enumerations are: ${listOfEnumValues?.join(', ')}\n`);
601
+ continue;
602
+ }
603
+ if (fieldType.dataType === 'person') {
604
+ validationErrors.push(`In card '${card.key}' field '${field.name}' value '${card.metadata[field.name]}' cannot be used as '${fieldType.dataType}'. Not a valid email address.'`);
605
+ continue;
606
+ }
607
+ validationErrors.push(`In card '${card.key}' field '${field.name}' is defined as '${fieldType.dataType}', but it is '${typeOfValue}' with value of ${fieldValue}\n`);
608
+ }
609
+ }
610
+ return validationErrors.join('\n');
611
+ }
612
+ /**
613
+ * Validates the labels of a card
614
+ * @param card card to validate. Card must have metadata.
615
+ */
616
+ async validateCardLabels(card) {
617
+ const validationErrors = [];
618
+ if (!card.metadata) {
619
+ validationErrors.push(`Card '${card.key}' has no metadata. Card object needs to be instantiated with '{metadata: true}'`);
620
+ }
621
+ // labels are not mandatory
622
+ if (card.metadata?.labels) {
623
+ if (!Array.isArray(card.metadata?.labels)) {
624
+ validationErrors.push(`In card '${card.key}' expected labels to be an array of strings, but instead got ${card.metadata.labels}`);
625
+ }
626
+ else {
627
+ for (const label of card.metadata.labels) {
628
+ // labels follow same name guidance as resource names
629
+ if (!Validate.isValidLabelName(label)) {
630
+ validationErrors.push(`In card '${card.key}' label '${label}' does not follow naming rules`);
631
+ }
632
+ }
633
+ }
634
+ }
635
+ return validationErrors.join('\n');
636
+ }
637
+ /**
638
+ * Checks if card's current workflow state matches workflow that card's card type is using.
639
+ * Template cards are expected to have empty workflow state.
640
+ * @param project Project object.
641
+ * @param card Card object to validate
642
+ * @returns string containing all validation errors
643
+ */
644
+ async validateWorkflowState(project, card) {
645
+ const validationErrors = [];
646
+ if (!card.metadata) {
647
+ validationErrors.push(`Card '${card.key}' has no metadata. Card object needs to be instantiated with '{metadata: true}'`);
648
+ }
649
+ // Use caches for cardTypes and workflows, to avoid re-reading the same JSON files multiple times.
650
+ const cardType = await this.getAndCacheResource(project, this.validatedCardTypes, card.metadata?.cardType || '');
651
+ if (!cardType) {
652
+ validationErrors.push(`Card '${card.key}' has invalid card type '${card.metadata?.cardType}'`);
653
+ return validationErrors.join('\n');
654
+ }
655
+ if (!cardType.workflow) {
656
+ validationErrors.push(`Card type '${card.metadata?.cardType}' does not have 'workflow'`);
657
+ return validationErrors.join('\n');
658
+ }
659
+ const workflow = await this.getAndCacheResource(project, this.validatedWorkflows, cardType.workflow);
660
+ if (!workflow) {
661
+ validationErrors.push(`Workflow of '${cardType.workflow}' card type '${card.metadata?.cardType}' does not exist in the project`);
662
+ return validationErrors.join('\n');
663
+ }
664
+ const cardState = card.metadata?.workflowState;
665
+ if (!isTemplateCard(card)) {
666
+ const found = workflow.states.find((item) => item.name === cardState);
667
+ if (!found) {
668
+ validationErrors.push(`Card '${card.key}' has invalid state '${cardState}'`);
669
+ }
670
+ }
671
+ else {
672
+ if (cardState) {
673
+ validationErrors.push(`Template card ${card.key} must have empty "workflowState"`);
674
+ }
675
+ }
676
+ return validationErrors.join('\n');
677
+ }
678
+ /**
679
+ * Possibly creates (if no instance exists) and returns an instance of Validate command.
680
+ * @returns instance of Validate command.
681
+ */
682
+ static getInstance() {
683
+ if (!Validate.instance) {
684
+ Validate.instance = new Validate();
685
+ }
686
+ return Validate.instance;
687
+ }
688
+ }
689
+ //# sourceMappingURL=validate.js.map