@cyberismo/data-handler 0.0.14 → 0.0.15
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.
- package/dist/card-metadata-updater.js +1 -3
- package/dist/card-metadata-updater.js.map +1 -1
- package/dist/command-handler.js +10 -16
- package/dist/command-handler.js.map +1 -1
- package/dist/command-manager.d.ts +1 -1
- package/dist/command-manager.js +4 -3
- package/dist/command-manager.js.map +1 -1
- package/dist/commands/create.js +13 -59
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/edit.d.ts +1 -15
- package/dist/commands/edit.js +15 -89
- package/dist/commands/edit.js.map +1 -1
- package/dist/commands/export.js +4 -17
- package/dist/commands/export.js.map +1 -1
- package/dist/commands/import.js +3 -5
- package/dist/commands/import.js.map +1 -1
- package/dist/commands/move.d.ts +1 -2
- package/dist/commands/move.js +108 -146
- package/dist/commands/move.js.map +1 -1
- package/dist/commands/remove.js +9 -44
- package/dist/commands/remove.js.map +1 -1
- package/dist/commands/rename.js +2 -7
- package/dist/commands/rename.js.map +1 -1
- package/dist/commands/show.d.ts +7 -25
- package/dist/commands/show.js +38 -102
- package/dist/commands/show.js.map +1 -1
- package/dist/commands/transition.js +27 -30
- package/dist/commands/transition.js.map +1 -1
- package/dist/commands/update.d.ts +5 -3
- package/dist/commands/update.js +19 -5
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/validate.d.ts +3 -3
- package/dist/commands/validate.js +19 -26
- package/dist/commands/validate.js.map +1 -1
- package/dist/containers/card-container.d.ts +87 -24
- package/dist/containers/card-container.js +183 -279
- package/dist/containers/card-container.js.map +1 -1
- package/dist/containers/project/calculation-engine.d.ts +6 -0
- package/dist/containers/project/calculation-engine.js +19 -12
- package/dist/containers/project/calculation-engine.js.map +1 -1
- package/dist/containers/project/card-cache.d.ts +146 -0
- package/dist/containers/project/card-cache.js +411 -0
- package/dist/containers/project/card-cache.js.map +1 -0
- package/dist/containers/project/resource-collector.d.ts +24 -1
- package/dist/containers/project/resource-collector.js +8 -1
- package/dist/containers/project/resource-collector.js.map +1 -1
- package/dist/containers/project.d.ts +117 -83
- package/dist/containers/project.js +418 -252
- package/dist/containers/project.js.map +1 -1
- package/dist/containers/template.d.ts +15 -31
- package/dist/containers/template.js +97 -104
- package/dist/containers/template.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/interfaces/folder-content-interfaces.d.ts +2 -1
- package/dist/interfaces/folder-content-interfaces.js.map +1 -1
- package/dist/interfaces/macros.d.ts +1 -0
- package/dist/interfaces/macros.js +1 -1
- package/dist/interfaces/macros.js.map +1 -1
- package/dist/interfaces/project-interfaces.d.ts +5 -1
- package/dist/interfaces/project-interfaces.js.map +1 -1
- package/dist/interfaces/resource-interfaces.d.ts +11 -21
- package/dist/interfaces/resource-interfaces.js +3 -0
- package/dist/interfaces/resource-interfaces.js.map +1 -1
- package/dist/macros/common.d.ts +10 -10
- package/dist/macros/createCards/index.d.ts +0 -13
- package/dist/macros/createCards/index.js.map +1 -1
- package/dist/macros/createCards/types.d.ts +44 -0
- package/dist/macros/createCards/types.js +15 -0
- package/dist/macros/createCards/types.js.map +1 -0
- package/dist/macros/graph/index.d.ts +2 -6
- package/dist/macros/graph/index.js +2 -2
- package/dist/macros/graph/index.js.map +1 -1
- package/dist/macros/graph/types.d.ts +23 -0
- package/dist/macros/graph/types.js +15 -0
- package/dist/macros/graph/types.js.map +1 -0
- package/dist/macros/image/index.d.ts +8 -16
- package/dist/macros/image/index.js +36 -33
- package/dist/macros/image/index.js.map +1 -1
- package/dist/macros/image/types.d.ts +38 -0
- package/dist/macros/image/types.js +15 -0
- package/dist/macros/image/types.js.map +1 -0
- package/dist/macros/include/index.d.ts +1 -6
- package/dist/macros/include/index.js +4 -7
- package/dist/macros/include/index.js.map +1 -1
- package/dist/macros/include/types.d.ts +31 -0
- package/dist/macros/include/types.js +15 -0
- package/dist/macros/include/types.js.map +1 -0
- package/dist/macros/percentage/index.d.ts +0 -6
- package/dist/macros/percentage/index.js.map +1 -1
- package/dist/macros/percentage/types.d.ts +31 -0
- package/dist/macros/percentage/types.js +15 -0
- package/dist/macros/percentage/types.js.map +1 -0
- package/dist/macros/report/index.d.ts +0 -3
- package/dist/macros/report/index.js.map +1 -1
- package/dist/macros/report/types.d.ts +19 -0
- package/dist/macros/report/types.js +15 -0
- package/dist/macros/report/types.js.map +1 -0
- package/dist/macros/scoreCard/index.d.ts +0 -6
- package/dist/macros/scoreCard/index.js.map +1 -1
- package/dist/macros/scoreCard/types.d.ts +31 -0
- package/dist/macros/scoreCard/types.js +15 -0
- package/dist/macros/scoreCard/types.js.map +1 -0
- package/dist/macros/types.d.ts +25 -0
- package/dist/macros/types.js +2 -0
- package/dist/macros/types.js.map +1 -0
- package/dist/macros/vega/index.d.ts +0 -4
- package/dist/macros/vega/index.js.map +1 -1
- package/dist/macros/vega/types.d.ts +20 -0
- package/dist/macros/vega/types.js +2 -0
- package/dist/macros/vega/types.js.map +1 -0
- package/dist/macros/vegalite/index.d.ts +0 -4
- package/dist/macros/vegalite/index.js.map +1 -1
- package/dist/macros/vegalite/types.d.ts +20 -0
- package/dist/macros/vegalite/types.js +15 -0
- package/dist/macros/vegalite/types.js.map +1 -0
- package/dist/macros/xref/index.d.ts +0 -3
- package/dist/macros/xref/index.js +5 -14
- package/dist/macros/xref/index.js.map +1 -1
- package/dist/macros/xref/types.d.ts +19 -0
- package/dist/macros/xref/types.js +15 -0
- package/dist/macros/xref/types.js.map +1 -0
- package/dist/module-manager.js +4 -4
- package/dist/module-manager.js.map +1 -1
- package/dist/project-settings.js.map +1 -1
- package/dist/resources/calculation-resource.d.ts +4 -32
- package/dist/resources/calculation-resource.js +0 -55
- package/dist/resources/calculation-resource.js.map +1 -1
- package/dist/resources/card-type-resource.d.ts +4 -21
- package/dist/resources/card-type-resource.js +13 -44
- package/dist/resources/card-type-resource.js.map +1 -1
- package/dist/resources/field-type-resource.d.ts +4 -21
- package/dist/resources/field-type-resource.js +14 -38
- package/dist/resources/field-type-resource.js.map +1 -1
- package/dist/resources/file-resource.d.ts +12 -29
- package/dist/resources/file-resource.js +19 -293
- package/dist/resources/file-resource.js.map +1 -1
- package/dist/resources/folder-resource.d.ts +31 -50
- package/dist/resources/folder-resource.js +68 -96
- package/dist/resources/folder-resource.js.map +1 -1
- package/dist/resources/graph-model-resource.d.ts +5 -33
- package/dist/resources/graph-model-resource.js +8 -61
- package/dist/resources/graph-model-resource.js.map +1 -1
- package/dist/resources/graph-view-resource.d.ts +5 -28
- package/dist/resources/graph-view-resource.js +6 -45
- package/dist/resources/graph-view-resource.js.map +1 -1
- package/dist/resources/link-type-resource.d.ts +4 -21
- package/dist/resources/link-type-resource.js +6 -31
- package/dist/resources/link-type-resource.js.map +1 -1
- package/dist/resources/report-resource.d.ts +5 -17
- package/dist/resources/report-resource.js +6 -44
- package/dist/resources/report-resource.js.map +1 -1
- package/dist/resources/resource-object.d.ts +58 -23
- package/dist/resources/resource-object.js +293 -24
- package/dist/resources/resource-object.js.map +1 -1
- package/dist/resources/template-resource.d.ts +4 -15
- package/dist/resources/template-resource.js +10 -25
- package/dist/resources/template-resource.js.map +1 -1
- package/dist/resources/workflow-resource.d.ts +4 -23
- package/dist/resources/workflow-resource.js +12 -38
- package/dist/resources/workflow-resource.js.map +1 -1
- package/dist/utils/card-utils.d.ts +69 -19
- package/dist/utils/card-utils.js +179 -30
- package/dist/utils/card-utils.js.map +1 -1
- package/dist/utils/clingo-facts.js +11 -3
- package/dist/utils/clingo-facts.js.map +1 -1
- package/dist/utils/clingo-parser.js +1 -1
- package/dist/utils/clingo-parser.js.map +1 -1
- package/dist/utils/constants.d.ts +2 -0
- package/dist/utils/constants.js +4 -0
- package/dist/utils/constants.js.map +1 -1
- package/dist/utils/csv.js +1 -1
- package/dist/utils/csv.js.map +1 -1
- package/package.json +5 -5
- package/src/card-metadata-updater.ts +3 -5
- package/src/command-handler.ts +11 -18
- package/src/command-manager.ts +4 -3
- package/src/commands/create.ts +17 -83
- package/src/commands/edit.ts +16 -132
- package/src/commands/export.ts +8 -29
- package/src/commands/import.ts +4 -6
- package/src/commands/move.ts +144 -179
- package/src/commands/remove.ts +9 -52
- package/src/commands/rename.ts +2 -7
- package/src/commands/show.ts +50 -143
- package/src/commands/transition.ts +30 -33
- package/src/commands/update.ts +27 -9
- package/src/commands/validate.ts +21 -36
- package/src/containers/card-container.ts +200 -360
- package/src/containers/project/calculation-engine.ts +21 -13
- package/src/containers/project/card-cache.ts +497 -0
- package/src/containers/project/resource-collector.ts +9 -1
- package/src/containers/project.ts +529 -327
- package/src/containers/template.ts +109 -127
- package/src/index.ts +1 -0
- package/src/interfaces/folder-content-interfaces.ts +7 -1
- package/src/interfaces/macros.ts +2 -0
- package/src/interfaces/project-interfaces.ts +7 -1
- package/src/interfaces/resource-interfaces.ts +12 -24
- package/src/macros/createCards/index.ts +1 -12
- package/src/macros/createCards/types.ts +46 -0
- package/src/macros/graph/index.ts +3 -7
- package/src/macros/graph/types.ts +24 -0
- package/src/macros/image/index.ts +50 -61
- package/src/macros/image/types.ts +39 -0
- package/src/macros/include/index.ts +6 -15
- package/src/macros/include/types.ts +32 -0
- package/src/macros/percentage/index.ts +1 -7
- package/src/macros/percentage/types.ts +32 -0
- package/src/macros/report/index.ts +1 -4
- package/src/macros/report/types.ts +20 -0
- package/src/macros/scoreCard/index.ts +1 -7
- package/src/macros/scoreCard/types.ts +32 -0
- package/src/macros/types.ts +48 -0
- package/src/macros/vega/index.ts +1 -4
- package/src/macros/vega/types.ts +21 -0
- package/src/macros/vegalite/index.ts +1 -4
- package/src/macros/vegalite/types.ts +22 -0
- package/src/macros/xref/index.ts +6 -20
- package/src/macros/xref/types.ts +20 -0
- package/src/module-manager.ts +5 -5
- package/src/project-settings.ts +1 -1
- package/src/resources/calculation-resource.ts +6 -76
- package/src/resources/card-type-resource.ts +24 -59
- package/src/resources/field-type-resource.ts +22 -51
- package/src/resources/file-resource.ts +27 -409
- package/src/resources/folder-resource.ts +98 -124
- package/src/resources/graph-model-resource.ts +17 -74
- package/src/resources/graph-view-resource.ts +14 -54
- package/src/resources/link-type-resource.ts +13 -40
- package/src/resources/report-resource.ts +17 -57
- package/src/resources/resource-object.ts +435 -32
- package/src/resources/template-resource.ts +16 -29
- package/src/resources/workflow-resource.ts +26 -50
- package/src/utils/card-utils.ts +217 -31
- package/src/utils/clingo-facts.ts +13 -3
- package/src/utils/clingo-parser.ts +1 -1
- package/src/utils/constants.ts +6 -0
- package/src/utils/csv.ts +1 -1
|
@@ -13,8 +13,15 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
// node
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
16
|
+
import { basename, join, resolve } from 'node:path';
|
|
17
|
+
import {
|
|
18
|
+
constants as fsConstants,
|
|
19
|
+
copyFile,
|
|
20
|
+
mkdir,
|
|
21
|
+
readdir,
|
|
22
|
+
unlink,
|
|
23
|
+
writeFile,
|
|
24
|
+
} from 'node:fs/promises';
|
|
18
25
|
|
|
19
26
|
import { CardContainer } from './card-container.js'; // base class
|
|
20
27
|
|
|
@@ -35,13 +42,18 @@ import {
|
|
|
35
42
|
type Resource,
|
|
36
43
|
type ResourceFolderType,
|
|
37
44
|
} from '../interfaces/project-interfaces.js';
|
|
38
|
-
import {
|
|
45
|
+
import { pathExists } from '../utils/file-utils.js';
|
|
39
46
|
import { generateRandomString } from '../utils/random.js';
|
|
40
|
-
import {
|
|
47
|
+
import {
|
|
48
|
+
cardPathParts,
|
|
49
|
+
isModulePath,
|
|
50
|
+
isTemplateCard,
|
|
51
|
+
} from '../utils/card-utils.js';
|
|
41
52
|
import { ProjectConfiguration } from '../project-settings.js';
|
|
42
53
|
import { ProjectPaths } from './project/project-paths.js';
|
|
43
54
|
import { readJsonFile } from '../utils/json.js';
|
|
44
55
|
import {
|
|
56
|
+
pathToResourceName,
|
|
45
57
|
resourceName,
|
|
46
58
|
type ResourceName,
|
|
47
59
|
resourceNameToString,
|
|
@@ -64,7 +76,9 @@ import { TemplateResource } from '../resources/template-resource.js';
|
|
|
64
76
|
import { WorkflowResource } from '../resources/workflow-resource.js';
|
|
65
77
|
|
|
66
78
|
import { ContentWatcher } from './project/project-content-watcher.js';
|
|
67
|
-
import {
|
|
79
|
+
import { getChildLogger } from '../utils/log-utils.js';
|
|
80
|
+
|
|
81
|
+
import { ROOT } from '../utils/constants.js';
|
|
68
82
|
|
|
69
83
|
// Re-export this, so that classes that use Project do not need to have separate import.
|
|
70
84
|
export { ResourcesFrom };
|
|
@@ -74,34 +88,44 @@ export { ResourcesFrom };
|
|
|
74
88
|
*/
|
|
75
89
|
export class Project extends CardContainer {
|
|
76
90
|
public calculationEngine: CalculationEngine;
|
|
77
|
-
private resources: ResourceCollector;
|
|
78
|
-
private projectPaths: ProjectPaths;
|
|
79
|
-
private settings: ProjectConfiguration;
|
|
80
|
-
private validator: Validate;
|
|
81
|
-
private resourceWatcher: ContentWatcher | undefined;
|
|
82
|
-
|
|
83
91
|
// Created resources are held in a cache.
|
|
84
92
|
// In the cache, key is resource name, and data is resource metadata (as JSON).
|
|
85
93
|
private createdResources = new Map<string, JSON>();
|
|
94
|
+
private logger = getChildLogger({ module: 'Project' });
|
|
95
|
+
private projectPaths: ProjectPaths;
|
|
96
|
+
private resources: ResourceCollector;
|
|
97
|
+
private resourceWatcher: ContentWatcher | undefined;
|
|
98
|
+
private settings: ProjectConfiguration;
|
|
99
|
+
private validator: Validate;
|
|
86
100
|
|
|
87
101
|
constructor(
|
|
88
102
|
path: string,
|
|
89
103
|
private watchResourceChanges?: boolean,
|
|
90
104
|
) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
this.calculationEngine = new CalculationEngine(this);
|
|
94
|
-
|
|
95
|
-
this.settings = new ProjectConfiguration(
|
|
105
|
+
const settings = new ProjectConfiguration(
|
|
96
106
|
join(path, '.cards', 'local', Project.projectConfigFileName),
|
|
97
107
|
);
|
|
108
|
+
super(path, settings.cardKeyPrefix, '');
|
|
109
|
+
this.settings = settings;
|
|
110
|
+
|
|
111
|
+
this.logger.info({ path }, 'Initializing project');
|
|
112
|
+
|
|
113
|
+
this.calculationEngine = new CalculationEngine(this);
|
|
98
114
|
this.projectPaths = new ProjectPaths(path);
|
|
99
115
|
this.resources = new ResourceCollector(this);
|
|
100
116
|
|
|
101
117
|
this.containerName = this.settings.name;
|
|
102
118
|
// todo: implement project validation
|
|
103
119
|
this.validator = Validate.getInstance();
|
|
120
|
+
this.logger.info(
|
|
121
|
+
{ resourcesFolder: this.paths.resourcesFolder },
|
|
122
|
+
'Collecting local resources',
|
|
123
|
+
);
|
|
104
124
|
this.resources.collectLocalResources();
|
|
125
|
+
this.logger.info(
|
|
126
|
+
{ name: this.containerName },
|
|
127
|
+
'Project initialization complete',
|
|
128
|
+
);
|
|
105
129
|
|
|
106
130
|
const ignoreRenameFileChanges = true;
|
|
107
131
|
|
|
@@ -135,17 +159,15 @@ export class Project extends CardContainer {
|
|
|
135
159
|
}
|
|
136
160
|
}
|
|
137
161
|
|
|
138
|
-
//
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
if (this.createdResources.has(resourceName)) {
|
|
143
|
-
// First, remove the old version from cache
|
|
144
|
-
this.createdResources.delete(resourceName);
|
|
162
|
+
// Changes a card's parent in the cache and updates all relationships.
|
|
163
|
+
private changeParent(updatedCard: Card, previousParent?: string) {
|
|
164
|
+
if (previousParent && previousParent !== ROOT) {
|
|
165
|
+
this.removeCachedChildren(previousParent, updatedCard.key);
|
|
145
166
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
167
|
+
if (updatedCard.parent && updatedCard.parent !== ROOT) {
|
|
168
|
+
this.updateCachedChildren(updatedCard.parent, updatedCard);
|
|
169
|
+
}
|
|
170
|
+
this.cardCache.updateCard(updatedCard.key, updatedCard);
|
|
149
171
|
}
|
|
150
172
|
|
|
151
173
|
// Finds specific module.
|
|
@@ -155,8 +177,55 @@ export class Project extends CardContainer {
|
|
|
155
177
|
);
|
|
156
178
|
}
|
|
157
179
|
|
|
180
|
+
// Handles attachment changes after filesystem operations.
|
|
181
|
+
private async handleAttachmentChange(
|
|
182
|
+
cardKey: string,
|
|
183
|
+
operation: 'added' | 'removed' | 'refresh',
|
|
184
|
+
fileName: string,
|
|
185
|
+
): Promise<void> {
|
|
186
|
+
if (operation === 'added') {
|
|
187
|
+
this.cardCache.addAttachment(cardKey, fileName);
|
|
188
|
+
} else if (operation === 'removed') {
|
|
189
|
+
this.cardCache.deleteAttachment(cardKey, fileName);
|
|
190
|
+
} else if (operation === 'refresh') {
|
|
191
|
+
const newAttachments = this.cardCache.getCardAttachments(cardKey);
|
|
192
|
+
if (newAttachments) {
|
|
193
|
+
this.cardCache.updateCardAttachments(cardKey, newAttachments);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Determines the parent card key from a card's filesystem path.
|
|
199
|
+
private parentFromPath(cardPath: string): string {
|
|
200
|
+
return cardPathParts(this.projectPrefix, cardPath).parents.at(-1) || 'root';
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Remove children from a card in the card cache
|
|
204
|
+
private removeCachedChildren(parentKey: string, childKey: string) {
|
|
205
|
+
const parentCard = this.cardCache.getCard(parentKey);
|
|
206
|
+
if (parentCard && parentCard.children) {
|
|
207
|
+
parentCard.children = parentCard.children.filter(
|
|
208
|
+
(child) => child !== childKey,
|
|
209
|
+
);
|
|
210
|
+
this.cardCache.updateCard(parentCard.key, parentCard);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Removes current version of a resource from the resource cache.
|
|
215
|
+
// Then re-creates the resource with current data and caches the value again.
|
|
216
|
+
// If the value wasn't in the cache before, it will be added.
|
|
217
|
+
private async replaceCacheValue(resourceName: string) {
|
|
218
|
+
if (this.createdResources.has(resourceName)) {
|
|
219
|
+
// First, remove the old version from cache
|
|
220
|
+
this.createdResources.delete(resourceName);
|
|
221
|
+
}
|
|
222
|
+
const resourceData = await this.resource(resourceName);
|
|
223
|
+
if (resourceData) {
|
|
224
|
+
this.createdResources.set(resourceName, resourceData as JSON);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
158
228
|
// Returns (local or all) resources of a given type.
|
|
159
|
-
// @todo: if this would be public, we could remove cardTypes(), fieldTypes(), ... and similar APIs
|
|
160
229
|
private async resourcesOfType(
|
|
161
230
|
type: ResourceFolderType,
|
|
162
231
|
from: ResourcesFrom = ResourcesFrom.localOnly,
|
|
@@ -164,6 +233,81 @@ export class Project extends CardContainer {
|
|
|
164
233
|
return this.resources.resources(type, from);
|
|
165
234
|
}
|
|
166
235
|
|
|
236
|
+
// Updates children in the card cache
|
|
237
|
+
private updateCachedChildren(parentKey: string, newChild: Card) {
|
|
238
|
+
const parentCard = this.cardCache.getCard(parentKey);
|
|
239
|
+
if (parentCard) {
|
|
240
|
+
// Add or update the child in the parent's children array
|
|
241
|
+
const existingChildIndex = parentCard.children?.findIndex(
|
|
242
|
+
(child) => child === newChild.key,
|
|
243
|
+
);
|
|
244
|
+
if (existingChildIndex === -1) {
|
|
245
|
+
parentCard.children.push(newChild.key);
|
|
246
|
+
}
|
|
247
|
+
this.cardCache.updateCard(parentCard.key, parentCard);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Populate template cards into the card cache.
|
|
253
|
+
*/
|
|
254
|
+
protected async populateTemplateCards(): Promise<void> {
|
|
255
|
+
try {
|
|
256
|
+
// Gets local & module templates
|
|
257
|
+
const templateResources = await this.templates();
|
|
258
|
+
const prefixes = await this.projectPrefixes();
|
|
259
|
+
const loadPromises = templateResources.map(async (template) => {
|
|
260
|
+
try {
|
|
261
|
+
this.validator.validResourceName(
|
|
262
|
+
'templates',
|
|
263
|
+
template.name,
|
|
264
|
+
prefixes,
|
|
265
|
+
);
|
|
266
|
+
} catch (error) {
|
|
267
|
+
this.logger.warn(
|
|
268
|
+
{ templateName: template.name, error },
|
|
269
|
+
`Template name '${template.name}' does not follow required format, skipping`,
|
|
270
|
+
);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const templateResource = new TemplateResource(
|
|
275
|
+
this,
|
|
276
|
+
resourceName(template.name),
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
const templateObject = templateResource.templateObject();
|
|
280
|
+
const isCreated = templateObject && templateObject.isCreated();
|
|
281
|
+
if (!templateObject || !isCreated) {
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
await this.cardCache.populateFromPath(
|
|
286
|
+
templateObject.templateCardsFolder(),
|
|
287
|
+
false,
|
|
288
|
+
);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
await Promise.all(loadPromises);
|
|
292
|
+
|
|
293
|
+
// Once all templates have been fetched, build child-parent relationships.
|
|
294
|
+
this.cardCache.populateChildrenRelationships();
|
|
295
|
+
} catch (error) {
|
|
296
|
+
this.logger.error(
|
|
297
|
+
{ error },
|
|
298
|
+
'Failed to populate template cards into the card cache',
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Populate both the project cards, and all template cards into card cache.
|
|
305
|
+
*/
|
|
306
|
+
protected async populateCardsCache(): Promise<void> {
|
|
307
|
+
await this.cardCache.populateFromPath(this.paths.cardRootFolder);
|
|
308
|
+
await this.populateTemplateCards();
|
|
309
|
+
}
|
|
310
|
+
|
|
167
311
|
/**
|
|
168
312
|
* Add a given 'resource' to the local resource arrays.
|
|
169
313
|
* @param resource Resource to add.
|
|
@@ -174,14 +318,52 @@ export class Project extends CardContainer {
|
|
|
174
318
|
this.createdResources.set(resource.name, data);
|
|
175
319
|
}
|
|
176
320
|
|
|
321
|
+
/**
|
|
322
|
+
* Returns all template cards from the project. This includes all module templates' cards.
|
|
323
|
+
* @returns all the template cards from the project
|
|
324
|
+
*/
|
|
325
|
+
public allTemplateCards(): Card[] {
|
|
326
|
+
return this.cardCache.getAllTemplateCards();
|
|
327
|
+
}
|
|
328
|
+
|
|
177
329
|
/**
|
|
178
330
|
* Returns an array of all the attachments in the project card's (excluding ones in templates).
|
|
179
331
|
* @returns all attachments in the project.
|
|
180
332
|
*/
|
|
181
|
-
public
|
|
333
|
+
public attachments(): CardAttachment[] {
|
|
182
334
|
return super.attachments(this.paths.cardRootFolder);
|
|
183
335
|
}
|
|
184
336
|
|
|
337
|
+
/**
|
|
338
|
+
* Returns attachments from cards at a specific path using the card cache.
|
|
339
|
+
* This method allows templates to access attachments from the shared cache.
|
|
340
|
+
* @param path The path to get attachments from
|
|
341
|
+
* @returns Array of attachments from cards at the specified path
|
|
342
|
+
*/
|
|
343
|
+
public attachmentsByPath(path: string): CardAttachment[] {
|
|
344
|
+
return super.attachments(path);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Returns all the attachments in the template cards.
|
|
349
|
+
* @returns all the attachments in the template cards.
|
|
350
|
+
*/
|
|
351
|
+
public async attachmentsFromTemplates() {
|
|
352
|
+
const templateAttachments: CardAttachment[] = [];
|
|
353
|
+
const templates = await this.templates();
|
|
354
|
+
for (const template of templates) {
|
|
355
|
+
const templateResource = new TemplateResource(
|
|
356
|
+
this,
|
|
357
|
+
resourceName(template.name),
|
|
358
|
+
);
|
|
359
|
+
const templateObject = templateResource.templateObject();
|
|
360
|
+
if (templateObject) {
|
|
361
|
+
templateAttachments.push(...templateObject.attachments());
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
return templateAttachments;
|
|
365
|
+
}
|
|
366
|
+
|
|
185
367
|
/**
|
|
186
368
|
* Returns an array of all the calculation files (*.lp) in the project.
|
|
187
369
|
* @param from Defines where resources are collected from.
|
|
@@ -194,59 +376,111 @@ export class Project extends CardContainer {
|
|
|
194
376
|
}
|
|
195
377
|
|
|
196
378
|
/**
|
|
197
|
-
* Returns path to card's attachment folder.
|
|
379
|
+
* Returns path to a card's attachment folder.
|
|
198
380
|
* @param cardKey card key
|
|
199
|
-
* @returns path to card's attachment folder.
|
|
200
|
-
|
|
381
|
+
* @returns path to a card's attachment folder.
|
|
382
|
+
*/
|
|
383
|
+
public cardAttachmentFolder(cardKey: string): string {
|
|
384
|
+
const pathToCard = this.findCard(cardKey).path;
|
|
385
|
+
return join(pathToCard, 'a');
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Creates an attachment for a card.
|
|
390
|
+
* @param cardKey The card to add attachment to
|
|
391
|
+
* @param attachmentName The name for the attachment file
|
|
392
|
+
* @param attachmentData The attachment data (file path or buffer)
|
|
393
|
+
* @throws If trying to add attachment to module card, or if attachment is not found
|
|
201
394
|
*/
|
|
202
|
-
public async
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
395
|
+
public async createCardAttachment(
|
|
396
|
+
cardKey: string,
|
|
397
|
+
attachmentName: string,
|
|
398
|
+
attachmentData: string | Buffer,
|
|
399
|
+
): Promise<void> {
|
|
400
|
+
const attachmentFolder = this.cardAttachmentFolder(cardKey);
|
|
401
|
+
|
|
402
|
+
// Check if this is a module template
|
|
403
|
+
if (isModulePath(attachmentFolder)) {
|
|
404
|
+
throw new Error(`Cannot modify imported module`);
|
|
207
405
|
}
|
|
208
406
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
407
|
+
// Create the attachment folder if it doesn't exist
|
|
408
|
+
await mkdir(attachmentFolder, { recursive: true });
|
|
409
|
+
|
|
410
|
+
const attachmentPath = join(attachmentFolder, basename(attachmentName));
|
|
411
|
+
|
|
412
|
+
if (Buffer.isBuffer(attachmentData)) {
|
|
413
|
+
await writeFile(attachmentPath, attachmentData, { flag: 'wx' });
|
|
414
|
+
} else {
|
|
415
|
+
try {
|
|
416
|
+
await copyFile(
|
|
417
|
+
attachmentData,
|
|
418
|
+
attachmentPath,
|
|
419
|
+
fsConstants.COPYFILE_EXCL,
|
|
420
|
+
);
|
|
421
|
+
} catch {
|
|
422
|
+
throw new Error(`Attachment file not found: ${attachmentData}`);
|
|
423
|
+
}
|
|
212
424
|
}
|
|
213
|
-
|
|
425
|
+
|
|
426
|
+
// Update cache
|
|
427
|
+
await this.handleAttachmentChange(
|
|
428
|
+
cardKey,
|
|
429
|
+
'added',
|
|
430
|
+
basename(attachmentName),
|
|
431
|
+
);
|
|
214
432
|
}
|
|
215
433
|
|
|
216
434
|
/**
|
|
217
|
-
*
|
|
218
|
-
* @param cardKey card
|
|
219
|
-
* @param
|
|
220
|
-
* @
|
|
435
|
+
* Removes an attachment from a card.
|
|
436
|
+
* @param cardKey The card to remove attachment from
|
|
437
|
+
* @param fileName The name of the attachment file to remove
|
|
438
|
+
* @throws if trying to remove module card attachment, or the attachment was not found.
|
|
221
439
|
*/
|
|
222
|
-
public async
|
|
440
|
+
public async removeCardAttachment(
|
|
223
441
|
cardKey: string,
|
|
224
|
-
|
|
225
|
-
): Promise<
|
|
226
|
-
|
|
442
|
+
fileName: string,
|
|
443
|
+
): Promise<void> {
|
|
444
|
+
const attachmentFolder = this.cardAttachmentFolder(cardKey);
|
|
445
|
+
|
|
446
|
+
// Modules cannot be modified.
|
|
447
|
+
if (isModulePath(attachmentFolder)) {
|
|
448
|
+
throw new Error(`Cannot modify imported module`);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const attachmentPath = join(attachmentFolder, fileName);
|
|
452
|
+
|
|
453
|
+
try {
|
|
454
|
+
await unlink(attachmentPath);
|
|
455
|
+
} catch (error) {
|
|
456
|
+
this.logger.error({ error }, 'Removing card attachment');
|
|
457
|
+
throw new Error(`Attachment not found: ${fileName}`);
|
|
458
|
+
}
|
|
459
|
+
await this.handleAttachmentChange(cardKey, 'removed', fileName);
|
|
227
460
|
}
|
|
228
461
|
|
|
229
462
|
/**
|
|
230
|
-
* Returns path to card's folder.
|
|
463
|
+
* Returns path to a card's folder.
|
|
231
464
|
* @param cardKey card key
|
|
232
|
-
* @returns path to card's folder.
|
|
465
|
+
* @returns path to a card's folder.
|
|
233
466
|
*/
|
|
234
467
|
public async cardFolder(cardKey: string): Promise<string> {
|
|
235
|
-
const found =
|
|
468
|
+
const found = super.findCard(cardKey);
|
|
236
469
|
if (found) {
|
|
237
470
|
return found.path;
|
|
238
471
|
}
|
|
239
472
|
|
|
240
473
|
const templates = await this.templates();
|
|
241
|
-
const templatePromises = templates.map(
|
|
474
|
+
const templatePromises = templates.map((template) => {
|
|
242
475
|
const templateObject = new TemplateResource(
|
|
243
476
|
this,
|
|
244
477
|
resourceName(template.name),
|
|
245
478
|
).templateObject();
|
|
246
479
|
const templateCard = templateObject
|
|
247
|
-
?
|
|
480
|
+
? templateObject.findCard(cardKey)
|
|
248
481
|
: undefined;
|
|
249
|
-
|
|
482
|
+
const path = templateCard ? templateCard.path : '';
|
|
483
|
+
return path;
|
|
250
484
|
});
|
|
251
485
|
|
|
252
486
|
const templatePaths = await Promise.all(templatePromises);
|
|
@@ -254,78 +488,41 @@ export class Project extends CardContainer {
|
|
|
254
488
|
}
|
|
255
489
|
|
|
256
490
|
/**
|
|
257
|
-
*
|
|
258
|
-
*
|
|
259
|
-
* @
|
|
260
|
-
* @returns card path logical parts
|
|
261
|
-
* @throws when called with wrong path, or wrong card owner
|
|
262
|
-
* todo: if prefix would be parameter; this could be static, or util method
|
|
491
|
+
* Fetches full Card data for given card keys
|
|
492
|
+
* @param cardIds array of card keys to fetch
|
|
493
|
+
* @returns Card data to the given card keys
|
|
263
494
|
*/
|
|
264
|
-
public
|
|
265
|
-
const
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
let startIndex = -1;
|
|
271
|
-
let templatesNameIndex = -1;
|
|
272
|
-
|
|
273
|
-
const cardRootIndex = pathParts.indexOf('cardRoot');
|
|
274
|
-
const projectInternalsIndex = pathParts.indexOf('.cards');
|
|
275
|
-
|
|
276
|
-
if (projectInternalsIndex === -1 && cardRootIndex >= 0) {
|
|
277
|
-
startIndex = projectInternalsIndex;
|
|
278
|
-
} else if (projectInternalsIndex >= 0 && cardRootIndex === -1) {
|
|
279
|
-
const templatesIndex = pathParts.indexOf('templates');
|
|
280
|
-
startIndex = templatesIndex;
|
|
281
|
-
if (templatesIndex === -1) {
|
|
282
|
-
throw new Error(
|
|
283
|
-
`Invalid card path. Template card must have 'templates' in path`,
|
|
284
|
-
);
|
|
285
|
-
}
|
|
286
|
-
const modulesIndex = pathParts.indexOf('modules');
|
|
287
|
-
if (modulesIndex !== -1) {
|
|
288
|
-
prefix = pathParts.at(modulesIndex + 1) || '';
|
|
289
|
-
}
|
|
290
|
-
templatesNameIndex = templatesIndex + 1;
|
|
291
|
-
template = `${prefix}/templates/${pathParts.at(templatesNameIndex)}`;
|
|
292
|
-
} else {
|
|
293
|
-
throw new Error(`Card must be either project card, or template card`);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
// Look for parents in the path.
|
|
297
|
-
let previousWasParent = false;
|
|
298
|
-
for (let index = startIndex; index <= pathParts.length; index++) {
|
|
299
|
-
if (previousWasParent) {
|
|
300
|
-
previousWasParent = false;
|
|
301
|
-
parents.push(pathParts.at(index - 2));
|
|
302
|
-
}
|
|
303
|
-
const cardsSubFolder = pathParts.at(index) === 'c';
|
|
304
|
-
const ignoreOrNotTemplatesParent =
|
|
305
|
-
index - 1 !== templatesNameIndex || templatesNameIndex === -1;
|
|
306
|
-
if (cardsSubFolder && ignoreOrNotTemplatesParent) {
|
|
307
|
-
previousWasParent = true;
|
|
495
|
+
public cardKeysToCards(cardIds: string[]): Card[] {
|
|
496
|
+
const cards: Card[] = [];
|
|
497
|
+
for (const cardId of cardIds) {
|
|
498
|
+
const card = this.cardCache.getCard(cardId);
|
|
499
|
+
if (card) {
|
|
500
|
+
cards.push(card);
|
|
308
501
|
}
|
|
309
502
|
}
|
|
503
|
+
return cards;
|
|
504
|
+
}
|
|
310
505
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
506
|
+
/**
|
|
507
|
+
* Accessor for cards cache.
|
|
508
|
+
* Used by template container (it needs to access project's cache, not their own instance).
|
|
509
|
+
* @note Should not be used directly (other than Template).
|
|
510
|
+
*/
|
|
511
|
+
public get cardsCache() {
|
|
512
|
+
return this.cardCache;
|
|
317
513
|
}
|
|
318
514
|
|
|
319
515
|
/**
|
|
320
|
-
* Returns an array of all the cards in the project.
|
|
321
|
-
* @
|
|
322
|
-
* @param
|
|
516
|
+
* Returns an array of all the cards in the project.
|
|
517
|
+
* @note These are project cards only, by default (unless path dictates otherwise).
|
|
518
|
+
* @param path Path from which to fetch the cards. Generally it is best to fetch from Project root, e.g. Project.cardRootFolder
|
|
519
|
+
* @param details Which details to include in the cards; by default all details are included.
|
|
323
520
|
* @returns all cards from the given path in the project.
|
|
324
521
|
*/
|
|
325
|
-
public
|
|
522
|
+
public cards(
|
|
326
523
|
path: string = this.paths.cardRootFolder,
|
|
327
|
-
details
|
|
328
|
-
):
|
|
524
|
+
details?: FetchCardDetails,
|
|
525
|
+
): Card[] {
|
|
329
526
|
return super.cards(path, details);
|
|
330
527
|
}
|
|
331
528
|
|
|
@@ -340,6 +537,22 @@ export class Project extends CardContainer {
|
|
|
340
537
|
return this.resources.resources('cardTypes', from);
|
|
341
538
|
}
|
|
342
539
|
|
|
540
|
+
/**
|
|
541
|
+
* Returns children of a given card; as Card array
|
|
542
|
+
* @param card Parent card to fetch children from
|
|
543
|
+
* @returns children of a given card; as Card array
|
|
544
|
+
*/
|
|
545
|
+
public childrenCards(card: Card): Card[] {
|
|
546
|
+
const cards: Card[] = [];
|
|
547
|
+
for (const child of card.children) {
|
|
548
|
+
const card = this.cardCache.getCard(child);
|
|
549
|
+
if (card) {
|
|
550
|
+
cards.push(card);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
return cards;
|
|
554
|
+
}
|
|
555
|
+
|
|
343
556
|
/**
|
|
344
557
|
* Updates all local resources.
|
|
345
558
|
*/
|
|
@@ -350,8 +563,8 @@ export class Project extends CardContainer {
|
|
|
350
563
|
/**
|
|
351
564
|
* Updates all imported module resources.
|
|
352
565
|
*/
|
|
353
|
-
public
|
|
354
|
-
|
|
566
|
+
public collectModuleResources() {
|
|
567
|
+
this.resources.moduleImported();
|
|
355
568
|
}
|
|
356
569
|
|
|
357
570
|
/**
|
|
@@ -371,7 +584,7 @@ export class Project extends CardContainer {
|
|
|
371
584
|
if (!card || !card.path || !isTemplateCard(card)) {
|
|
372
585
|
return undefined;
|
|
373
586
|
}
|
|
374
|
-
const { template } = this.
|
|
587
|
+
const { template } = cardPathParts(this.projectPrefix, card.path);
|
|
375
588
|
return new TemplateResource(this, resourceName(template)).templateObject();
|
|
376
589
|
}
|
|
377
590
|
|
|
@@ -421,46 +634,8 @@ export class Project extends CardContainer {
|
|
|
421
634
|
* @param details Defines which card details are included in the return values.
|
|
422
635
|
* @returns specific card details, or undefined if card is not part of the project.
|
|
423
636
|
*/
|
|
424
|
-
public
|
|
425
|
-
cardToFind
|
|
426
|
-
details: ProjectFetchCardDetails = {},
|
|
427
|
-
): Promise<Card | undefined> {
|
|
428
|
-
let card;
|
|
429
|
-
|
|
430
|
-
if (
|
|
431
|
-
details.location === CardLocation.projectOnly ||
|
|
432
|
-
details.location === CardLocation.all ||
|
|
433
|
-
!details.location
|
|
434
|
-
) {
|
|
435
|
-
card = await super.findCard(
|
|
436
|
-
this.paths.cardRootFolder,
|
|
437
|
-
cardToFind,
|
|
438
|
-
details,
|
|
439
|
-
);
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
if (
|
|
443
|
-
!card &&
|
|
444
|
-
(details.location === CardLocation.templatesOnly ||
|
|
445
|
-
details.location === CardLocation.all ||
|
|
446
|
-
!details.location)
|
|
447
|
-
) {
|
|
448
|
-
const templates = await this.templates();
|
|
449
|
-
for (const template of templates) {
|
|
450
|
-
const templateObject = new TemplateResource(
|
|
451
|
-
this,
|
|
452
|
-
resourceName(template.name),
|
|
453
|
-
).templateObject();
|
|
454
|
-
if (!templateObject) continue;
|
|
455
|
-
|
|
456
|
-
// optimize: execute each find in template parallel
|
|
457
|
-
card = await templateObject.findSpecificCard(cardToFind, details);
|
|
458
|
-
if (card) {
|
|
459
|
-
break;
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
return card;
|
|
637
|
+
public findCard(cardToFind: string, details?: ProjectFetchCardDetails): Card {
|
|
638
|
+
return super.findCard(cardToFind, details);
|
|
464
639
|
}
|
|
465
640
|
|
|
466
641
|
/**
|
|
@@ -490,6 +665,7 @@ export class Project extends CardContainer {
|
|
|
490
665
|
* @param changedCard Card that was changed.
|
|
491
666
|
*/
|
|
492
667
|
public async handleCardChanged(changedCard: Card) {
|
|
668
|
+
// Notify the calculation engine about the change
|
|
493
669
|
return this.calculationEngine.handleCardChanged(changedCard);
|
|
494
670
|
}
|
|
495
671
|
|
|
@@ -497,39 +673,86 @@ export class Project extends CardContainer {
|
|
|
497
673
|
* When cards are removed.
|
|
498
674
|
* @param deletedCard Card that is to be removed.
|
|
499
675
|
*/
|
|
500
|
-
public async
|
|
676
|
+
public async handleCardDeleted(deletedCard: Card) {
|
|
677
|
+
// Delete children from the cache first
|
|
678
|
+
if (deletedCard.children && deletedCard.children.length > 0) {
|
|
679
|
+
for (const child of deletedCard.children) {
|
|
680
|
+
try {
|
|
681
|
+
const childCard = this.findCard(child);
|
|
682
|
+
await this.handleCardDeleted(childCard);
|
|
683
|
+
} catch {
|
|
684
|
+
this.logger.warn(
|
|
685
|
+
`Accessing child '${child}' of '${deletedCard.key}' when deleting cards caused an exception`,
|
|
686
|
+
);
|
|
687
|
+
continue;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
await super.removeCard(deletedCard.key);
|
|
501
692
|
return this.calculationEngine.handleDeleteCard(deletedCard);
|
|
502
693
|
}
|
|
503
694
|
|
|
504
695
|
/**
|
|
505
|
-
* When
|
|
506
|
-
* @param
|
|
696
|
+
* When card is moved.
|
|
697
|
+
* @param movedCard Card that moved
|
|
698
|
+
* @param newParentCard New parent for the 'movedCard'
|
|
699
|
+
* @param oldParentCard Previous parent of the 'movedCard'
|
|
507
700
|
*/
|
|
508
|
-
public async
|
|
509
|
-
|
|
701
|
+
public async handleCardMoved(
|
|
702
|
+
movedCard: Card,
|
|
703
|
+
newParentCard?: Card,
|
|
704
|
+
oldParentCard?: Card,
|
|
705
|
+
) {
|
|
706
|
+
if (newParentCard) {
|
|
707
|
+
this.cardCache.updateCard(newParentCard.key, newParentCard);
|
|
708
|
+
}
|
|
709
|
+
if (oldParentCard) {
|
|
710
|
+
this.cardCache.updateCard(oldParentCard.key, oldParentCard);
|
|
711
|
+
}
|
|
712
|
+
this.cardCache.updateCard(movedCard.key, movedCard);
|
|
713
|
+
|
|
714
|
+
// todo: it would be enough to just update parent, previous parent and changed card
|
|
715
|
+
this.cardCache.populateChildrenRelationships();
|
|
716
|
+
await this.handleCardChanged(movedCard);
|
|
717
|
+
await this.calculationEngine.handleCardMoved();
|
|
510
718
|
}
|
|
511
719
|
|
|
512
720
|
/**
|
|
513
|
-
*
|
|
514
|
-
* @param
|
|
515
|
-
* @returns true if a given card is found from project, false otherwise.
|
|
721
|
+
* When new cards are added.
|
|
722
|
+
* @param cards Added cards.
|
|
516
723
|
*/
|
|
517
|
-
public
|
|
518
|
-
|
|
724
|
+
public async handleNewCards(cards: Card[]) {
|
|
725
|
+
// Add new cards to the card cache
|
|
726
|
+
cards.forEach((card) => {
|
|
727
|
+
const cardWithParent = {
|
|
728
|
+
...card,
|
|
729
|
+
parent: card.parent || this.parentFromPath(card.path),
|
|
730
|
+
};
|
|
731
|
+
|
|
732
|
+
this.cardCache.updateCard(cardWithParent.key, cardWithParent);
|
|
733
|
+
|
|
734
|
+
// Update the parent's children list in the cache
|
|
735
|
+
if (cardWithParent.parent && cardWithParent.parent !== ROOT) {
|
|
736
|
+
this.updateCachedChildren(cardWithParent.parent, cardWithParent);
|
|
737
|
+
}
|
|
738
|
+
});
|
|
739
|
+
return this.calculationEngine.handleNewCards(cards);
|
|
519
740
|
}
|
|
520
741
|
|
|
521
742
|
/**
|
|
522
743
|
* Adds a module from project.
|
|
523
|
-
* @param module
|
|
744
|
+
* @param module Module to add
|
|
524
745
|
*/
|
|
525
746
|
public async importModule(module: ModuleSetting) {
|
|
526
747
|
// Add module as a dependency.
|
|
527
748
|
await this.configuration.addModule(module);
|
|
528
|
-
|
|
749
|
+
this.collectModuleResources();
|
|
750
|
+
await this.populateTemplateCards();
|
|
751
|
+
this.logger.info(`Imported module '${module.name}'`);
|
|
529
752
|
}
|
|
530
753
|
|
|
531
754
|
/**
|
|
532
|
-
* Checks if given path is a project.
|
|
755
|
+
* Checks if a given path is a project.
|
|
533
756
|
* @param path Path to a project
|
|
534
757
|
* @returns true, if in the given path there is a project; false otherwise
|
|
535
758
|
*/
|
|
@@ -537,17 +760,6 @@ export class Project extends CardContainer {
|
|
|
537
760
|
return pathExists(join(path, 'cardRoot'));
|
|
538
761
|
}
|
|
539
762
|
|
|
540
|
-
/**
|
|
541
|
-
* Returns whether card is a template card or not
|
|
542
|
-
* @param cardKey card to check.
|
|
543
|
-
* @todo: This is only used from 'remove'. Could it use the static checker?
|
|
544
|
-
* @returns true, if card is template card; false otherwise
|
|
545
|
-
*/
|
|
546
|
-
public async isTemplateCard(cardKey: string): Promise<boolean> {
|
|
547
|
-
const templateCards = await this.allTemplateCards();
|
|
548
|
-
return templateCards.find((card) => card.key === cardKey) != null;
|
|
549
|
-
}
|
|
550
|
-
|
|
551
763
|
/**
|
|
552
764
|
* Returns an array of all the link types in the project.
|
|
553
765
|
* @param from Defines where resources are collected from.
|
|
@@ -563,7 +775,7 @@ export class Project extends CardContainer {
|
|
|
563
775
|
* Returns an array of cards in the project, in the templates or both.
|
|
564
776
|
* Cards don't have content and nor metadata.
|
|
565
777
|
* @param cardsFrom Where to return cards from (project, templates, or both)
|
|
566
|
-
* @returns all cards in the project.
|
|
778
|
+
* @returns all cards in the project per container.
|
|
567
779
|
*/
|
|
568
780
|
public async listCards(
|
|
569
781
|
cardsFrom: CardLocation = CardLocation.all,
|
|
@@ -573,9 +785,9 @@ export class Project extends CardContainer {
|
|
|
573
785
|
cardsFrom === CardLocation.all ||
|
|
574
786
|
cardsFrom === CardLocation.projectOnly
|
|
575
787
|
) {
|
|
576
|
-
const projectCards =
|
|
577
|
-
(
|
|
578
|
-
|
|
788
|
+
const projectCards = super
|
|
789
|
+
.cards(this.paths.cardRootFolder)
|
|
790
|
+
.map((item) => item.key);
|
|
579
791
|
cardListContainer.push({
|
|
580
792
|
name: this.projectName,
|
|
581
793
|
type: 'project',
|
|
@@ -595,8 +807,8 @@ export class Project extends CardContainer {
|
|
|
595
807
|
).templateObject();
|
|
596
808
|
if (templateObject) {
|
|
597
809
|
// todo: optimization - do all this in parallel
|
|
598
|
-
const templateCards =
|
|
599
|
-
if (templateCards) {
|
|
810
|
+
const templateCards = templateObject.listCards();
|
|
811
|
+
if (templateCards.length) {
|
|
600
812
|
cardListContainer.push({
|
|
601
813
|
name: template.name,
|
|
602
814
|
type: 'template',
|
|
@@ -618,69 +830,27 @@ export class Project extends CardContainer {
|
|
|
618
830
|
public async listCardIds(
|
|
619
831
|
cardsFrom: CardLocation = CardLocation.all,
|
|
620
832
|
): Promise<Set<string>> {
|
|
621
|
-
const
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
promises.push(
|
|
627
|
-
super
|
|
628
|
-
.cards(this.paths.cardRootFolder)
|
|
629
|
-
.then((cards) => new Set(cards.map((card) => card.key))),
|
|
630
|
-
);
|
|
833
|
+
const cardContainers = await this.listCards(cardsFrom);
|
|
834
|
+
const allCardIDs = new Set<string>();
|
|
835
|
+
for (const container of cardContainers) {
|
|
836
|
+
const cards = container.cards;
|
|
837
|
+
cards.forEach((card) => allCardIDs.add(card));
|
|
631
838
|
}
|
|
632
|
-
|
|
633
|
-
cardsFrom === CardLocation.all ||
|
|
634
|
-
cardsFrom === CardLocation.templatesOnly
|
|
635
|
-
) {
|
|
636
|
-
promises.push(
|
|
637
|
-
(async () => {
|
|
638
|
-
const templates = await this.templates();
|
|
639
|
-
const templateResources = templates.map(
|
|
640
|
-
(template) =>
|
|
641
|
-
new TemplateResource(this, resourceName(template.name)),
|
|
642
|
-
);
|
|
643
|
-
const templateObjectsResults =
|
|
644
|
-
await Promise.allSettled(templateResources);
|
|
645
|
-
const templateObjects = templateObjectsResults
|
|
646
|
-
.filter(
|
|
647
|
-
(result): result is PromiseFulfilledResult<TemplateResource> =>
|
|
648
|
-
result.status === 'fulfilled' && result.value !== null,
|
|
649
|
-
)
|
|
650
|
-
.map((result) => result.value);
|
|
651
|
-
|
|
652
|
-
const listCardsResults = await Promise.allSettled(
|
|
653
|
-
templateObjects.map((obj) => obj.templateObject().listCards()),
|
|
654
|
-
);
|
|
655
|
-
const templateCardIds = new Set<string>();
|
|
656
|
-
listCardsResults
|
|
657
|
-
.filter(
|
|
658
|
-
(result): result is PromiseFulfilledResult<Card[]> =>
|
|
659
|
-
result.status === 'fulfilled',
|
|
660
|
-
)
|
|
661
|
-
.forEach((result) => {
|
|
662
|
-
result.value.forEach((card) => templateCardIds.add(card.key));
|
|
663
|
-
});
|
|
664
|
-
return templateCardIds;
|
|
665
|
-
})(),
|
|
666
|
-
);
|
|
667
|
-
}
|
|
668
|
-
const allCardIdSets = await Promise.all(promises);
|
|
669
|
-
return new Set(allCardIdSets.flatMap((set) => [...set]));
|
|
839
|
+
return allCardIDs;
|
|
670
840
|
}
|
|
671
841
|
|
|
672
842
|
/**
|
|
673
843
|
* Returns details of a certain module.
|
|
674
844
|
* @param moduleName Name of the module.
|
|
675
|
-
* @returns module details, or undefined if
|
|
845
|
+
* @returns module details, or undefined if module cannot be found.
|
|
676
846
|
*/
|
|
677
847
|
public async module(moduleName: string): Promise<ModuleContent | undefined> {
|
|
678
848
|
const module = await this.findModule(moduleName);
|
|
679
849
|
if (module && module.path) {
|
|
680
850
|
const modulePath = join(module.path, module.name);
|
|
681
|
-
const moduleConfig =
|
|
851
|
+
const moduleConfig = await readJsonFile(
|
|
682
852
|
join(modulePath, Project.projectConfigFileName),
|
|
683
|
-
)
|
|
853
|
+
);
|
|
684
854
|
return {
|
|
685
855
|
name: moduleConfig.name,
|
|
686
856
|
modules: moduleConfig.modules,
|
|
@@ -822,15 +992,12 @@ export class Project extends CardContainer {
|
|
|
822
992
|
}
|
|
823
993
|
|
|
824
994
|
/**
|
|
825
|
-
*
|
|
826
|
-
* @param cardKey card to check path for.
|
|
827
|
-
* @returns path to a given card.
|
|
995
|
+
* Populates the card cache, if it has not been populated.
|
|
828
996
|
*/
|
|
829
|
-
public
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
return foundFile ? dirname(foundFile) : '';
|
|
997
|
+
public async populateCaches() {
|
|
998
|
+
if (!this.cardCache.isPopulated) {
|
|
999
|
+
await this.populateCardsCache();
|
|
1000
|
+
}
|
|
834
1001
|
}
|
|
835
1002
|
|
|
836
1003
|
/**
|
|
@@ -857,6 +1024,7 @@ export class Project extends CardContainer {
|
|
|
857
1024
|
const prefixes: string[] = [this.projectPrefix];
|
|
858
1025
|
let files;
|
|
859
1026
|
try {
|
|
1027
|
+
// TODO: Could be optimized so that prefixes are stored once fetched.
|
|
860
1028
|
files = await readdir(this.paths.modulesFolder, {
|
|
861
1029
|
withFileTypes: true,
|
|
862
1030
|
recursive: true,
|
|
@@ -874,13 +1042,48 @@ export class Project extends CardContainer {
|
|
|
874
1042
|
|
|
875
1043
|
const configurationPrefixes = await Promise.all(configurationPromises);
|
|
876
1044
|
prefixes.push(...configurationPrefixes);
|
|
877
|
-
} catch {
|
|
878
|
-
|
|
1045
|
+
} catch (error) {
|
|
1046
|
+
this.logger.error({ error }, 'Failed to collect prefixes in use');
|
|
879
1047
|
}
|
|
880
1048
|
|
|
881
1049
|
return prefixes;
|
|
882
1050
|
}
|
|
883
1051
|
|
|
1052
|
+
/**
|
|
1053
|
+
* Removes a module from the project
|
|
1054
|
+
* @param module Module (name) to remove.
|
|
1055
|
+
*/
|
|
1056
|
+
public async removeModule(moduleName: string) {
|
|
1057
|
+
const toBeRemovedTemplates = this.resources.moduleResources.resourceArray(
|
|
1058
|
+
'templates',
|
|
1059
|
+
moduleName,
|
|
1060
|
+
);
|
|
1061
|
+
// First, remove cards from the cache
|
|
1062
|
+
for (const template of toBeRemovedTemplates) {
|
|
1063
|
+
this.cardCache.deleteCardsFromTemplate(template.name);
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
// Then, remove module from project configuration
|
|
1067
|
+
await this.configuration.removeModule(moduleName);
|
|
1068
|
+
this.collectModuleResources();
|
|
1069
|
+
|
|
1070
|
+
this.logger.info(`Removed module '${moduleName}'`);
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
/**
|
|
1074
|
+
* Removes a resource from Project.
|
|
1075
|
+
* @param resource Resource to remove.
|
|
1076
|
+
*/
|
|
1077
|
+
public removeResource(resource: Resource) {
|
|
1078
|
+
// Template cards must be removed from the cache when resource is removed.
|
|
1079
|
+
if (resource.path.includes('templates')) {
|
|
1080
|
+
const templateName = resourceNameToString(resourceName(resource.name));
|
|
1081
|
+
this.cardCache.deleteCardsFromTemplate(templateName);
|
|
1082
|
+
}
|
|
1083
|
+
this.resources.remove(resource);
|
|
1084
|
+
this.createdResources.delete(resource.name);
|
|
1085
|
+
}
|
|
1086
|
+
|
|
884
1087
|
/**
|
|
885
1088
|
* Array of reports in the project.
|
|
886
1089
|
* @param from Defines where resources are collected from.
|
|
@@ -908,21 +1111,12 @@ export class Project extends CardContainer {
|
|
|
908
1111
|
return handleBarFiles;
|
|
909
1112
|
}
|
|
910
1113
|
|
|
911
|
-
/**
|
|
912
|
-
* Removes a resource from Project.
|
|
913
|
-
* @param resource Resource to remove.
|
|
914
|
-
*/
|
|
915
|
-
public removeResource(resource: Resource) {
|
|
916
|
-
this.resources.remove(resource);
|
|
917
|
-
this.createdResources.delete(resource.name);
|
|
918
|
-
}
|
|
919
|
-
|
|
920
1114
|
/**
|
|
921
1115
|
* Returns metadata from a given resource
|
|
922
1116
|
* @param name Name of a resource
|
|
923
1117
|
* @returns Metadata from the resource.
|
|
924
1118
|
*/
|
|
925
|
-
public
|
|
1119
|
+
public resource<Type>(name: string): Type | undefined {
|
|
926
1120
|
const resName = resourceName(name);
|
|
927
1121
|
if (this.createdResources.has(resourceNameToString(resName))) {
|
|
928
1122
|
const value = this.createdResources.get(
|
|
@@ -1022,45 +1216,23 @@ export class Project extends CardContainer {
|
|
|
1022
1216
|
* Show cards of a project.
|
|
1023
1217
|
* @returns an array of all project cards in the project.
|
|
1024
1218
|
*/
|
|
1025
|
-
public
|
|
1219
|
+
public showProjectCards(): Card[] {
|
|
1026
1220
|
return this.showCards(this.paths.cardRootFolder);
|
|
1027
1221
|
}
|
|
1028
1222
|
|
|
1029
|
-
/**
|
|
1030
|
-
* Returns all template cards from the project. This includes all module templates' cards.
|
|
1031
|
-
* @param cardDetails which details to fetch. Optional.
|
|
1032
|
-
* @returns all the template cards from the project
|
|
1033
|
-
*/
|
|
1034
|
-
public async allTemplateCards(
|
|
1035
|
-
cardDetails?: FetchCardDetails,
|
|
1036
|
-
): Promise<Card[]> {
|
|
1037
|
-
const templates = await this.templates();
|
|
1038
|
-
const cards: Card[] = [];
|
|
1039
|
-
for (const template of templates) {
|
|
1040
|
-
const templateCards = await this.templateCards(
|
|
1041
|
-
template.name,
|
|
1042
|
-
cardDetails,
|
|
1043
|
-
);
|
|
1044
|
-
if (templateCards) cards.push(...templateCards);
|
|
1045
|
-
}
|
|
1046
|
-
return cards;
|
|
1047
|
-
}
|
|
1048
|
-
|
|
1049
1223
|
/**
|
|
1050
1224
|
* Returns cards from single template.
|
|
1051
|
-
* @param templateName Name of the template
|
|
1052
|
-
* @param cardDetails Card information
|
|
1225
|
+
* @param templateName Name of the template (supports both full names like 'decision/templates/decision' and short names like 'decision')
|
|
1053
1226
|
* @returns List of cards from template.
|
|
1054
1227
|
*/
|
|
1055
|
-
public
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
)
|
|
1063
|
-
return await templateObject?.cards('', cardDetails);
|
|
1228
|
+
public templateCards(templateName: string): Card[] {
|
|
1229
|
+
const templateCards = this.cardCache.getAllTemplateCards();
|
|
1230
|
+
return templateCards.filter((cachedCard) => {
|
|
1231
|
+
if (cachedCard.location === 'project') {
|
|
1232
|
+
return false;
|
|
1233
|
+
}
|
|
1234
|
+
return cachedCard.location === templateName;
|
|
1235
|
+
});
|
|
1064
1236
|
}
|
|
1065
1237
|
|
|
1066
1238
|
/**
|
|
@@ -1075,20 +1247,21 @@ export class Project extends CardContainer {
|
|
|
1075
1247
|
}
|
|
1076
1248
|
|
|
1077
1249
|
/**
|
|
1078
|
-
* Update card content.
|
|
1079
|
-
* @param cardKey card
|
|
1250
|
+
* Update a card's content.
|
|
1251
|
+
* @param cardKey card key to update.
|
|
1080
1252
|
* @param content changed content
|
|
1081
1253
|
*/
|
|
1082
1254
|
public async updateCardContent(cardKey: string, content: string) {
|
|
1083
|
-
const card =
|
|
1084
|
-
metadata: true,
|
|
1085
|
-
content: true,
|
|
1086
|
-
});
|
|
1087
|
-
if (!card) {
|
|
1088
|
-
throw new Error(`Card '${cardKey}' does not exist in the project`);
|
|
1089
|
-
}
|
|
1255
|
+
const card = this.findCard(cardKey);
|
|
1090
1256
|
card.content = content;
|
|
1257
|
+
|
|
1258
|
+
// Update lastUpdated timestamp in metadata
|
|
1259
|
+
if (card.metadata) {
|
|
1260
|
+
card.metadata.lastUpdated = new Date().toISOString();
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1091
1263
|
await this.saveCard(card);
|
|
1264
|
+
await this.handleCardChanged(card);
|
|
1092
1265
|
}
|
|
1093
1266
|
|
|
1094
1267
|
/**
|
|
@@ -1102,21 +1275,15 @@ export class Project extends CardContainer {
|
|
|
1102
1275
|
changedKey: string,
|
|
1103
1276
|
newValue: MetadataContent,
|
|
1104
1277
|
) {
|
|
1105
|
-
const
|
|
1106
|
-
const card = await this.findCard(
|
|
1107
|
-
templateCard ? this.paths.templatesFolder : this.paths.cardRootFolder,
|
|
1108
|
-
cardKey,
|
|
1109
|
-
{
|
|
1110
|
-
metadata: true,
|
|
1111
|
-
},
|
|
1112
|
-
);
|
|
1113
|
-
if (!card) {
|
|
1114
|
-
throw new Error(`Card '${cardKey}' does not exist in the project`);
|
|
1115
|
-
}
|
|
1116
|
-
|
|
1278
|
+
const card = this.findCard(cardKey);
|
|
1117
1279
|
if (!card.metadata || card.metadata[changedKey] === newValue) {
|
|
1118
1280
|
return;
|
|
1119
1281
|
}
|
|
1282
|
+
|
|
1283
|
+
const isRankChange = changedKey === 'rank';
|
|
1284
|
+
const previousPath = isRankChange ? card.path : undefined;
|
|
1285
|
+
const previousParent = isRankChange ? card.parent : undefined;
|
|
1286
|
+
|
|
1120
1287
|
const cardAsRecord: Record<string, MetadataContent> = card.metadata;
|
|
1121
1288
|
cardAsRecord[changedKey] = newValue;
|
|
1122
1289
|
|
|
@@ -1127,35 +1294,70 @@ export class Project extends CardContainer {
|
|
|
1127
1294
|
throw new Error(invalidCard);
|
|
1128
1295
|
}
|
|
1129
1296
|
|
|
1130
|
-
await this.saveCardMetadata(card);
|
|
1297
|
+
const updated = await this.saveCardMetadata(card);
|
|
1298
|
+
if (!updated) return;
|
|
1299
|
+
|
|
1300
|
+
// For rank changes, check if path changed (indicating a move)
|
|
1301
|
+
if (isRankChange) {
|
|
1302
|
+
const updatedCard = this.findCard(cardKey);
|
|
1303
|
+
if (updatedCard.path !== previousPath) {
|
|
1304
|
+
this.changeParent(updatedCard, previousParent);
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1131
1307
|
}
|
|
1132
1308
|
|
|
1133
1309
|
/**
|
|
1134
|
-
* Updates card
|
|
1310
|
+
* Updates the entire card in the card cache and handles any path/parent changes.
|
|
1311
|
+
* Also persists changes to content and metadata files.
|
|
1312
|
+
* @param card The card with updated information (path, parent, metadata, etc.)
|
|
1313
|
+
*/
|
|
1314
|
+
public async updateCard(card: Card) {
|
|
1315
|
+
const cachedCard = this.cardCache.getCard(card.key);
|
|
1316
|
+
const pathChange = cachedCard && cachedCard.path !== card.path;
|
|
1317
|
+
|
|
1318
|
+
if (pathChange) {
|
|
1319
|
+
this.changeParent(card, cachedCard.parent);
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
const metadataChanged =
|
|
1323
|
+
cachedCard &&
|
|
1324
|
+
JSON.stringify(cachedCard.metadata) !== JSON.stringify(card.metadata);
|
|
1325
|
+
if (metadataChanged) {
|
|
1326
|
+
await this.saveCardMetadata(card);
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
const contentChanged = cachedCard && cachedCard.content !== card.content;
|
|
1330
|
+
if (contentChanged) {
|
|
1331
|
+
await this.saveCardContent(card);
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
this.cardCache.updateCard(card.key, card);
|
|
1335
|
+
if (metadataChanged || contentChanged || pathChange) {
|
|
1336
|
+
await this.handleCardChanged(card);
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
/**
|
|
1341
|
+
* Updates a card's metadata.
|
|
1135
1342
|
* @param card affected card
|
|
1136
1343
|
* @param changedMetadata changed content for the card
|
|
1137
1344
|
*/
|
|
1138
1345
|
public async updateCardMetadata(card: Card, changedMetadata: CardMetadata) {
|
|
1139
1346
|
card.metadata = changedMetadata;
|
|
1140
|
-
|
|
1347
|
+
if (await this.saveCardMetadata(card)) {
|
|
1348
|
+
await this.handleCardChanged(card);
|
|
1349
|
+
}
|
|
1141
1350
|
}
|
|
1142
1351
|
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
* @param card Card to validate.
|
|
1146
|
-
* @returns validation errors, if any
|
|
1147
|
-
*/
|
|
1148
|
-
public async validateCard(card: Card): Promise<string> {
|
|
1352
|
+
// Validates that card's data is valid.
|
|
1353
|
+
private async validateCard(card: Card): Promise<string> {
|
|
1149
1354
|
const invalidCustomData = await this.validator.validateCustomFields(
|
|
1150
1355
|
this,
|
|
1151
1356
|
card,
|
|
1152
1357
|
);
|
|
1153
|
-
const invalidWorkFlow =
|
|
1154
|
-
this,
|
|
1155
|
-
card,
|
|
1156
|
-
);
|
|
1358
|
+
const invalidWorkFlow = this.validator.validateWorkflowState(this, card);
|
|
1157
1359
|
|
|
1158
|
-
const invalidLabels =
|
|
1360
|
+
const invalidLabels = this.validator.validateCardLabels(card);
|
|
1159
1361
|
if (
|
|
1160
1362
|
invalidCustomData.length === 0 &&
|
|
1161
1363
|
invalidWorkFlow.length === 0 &&
|