@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.
- package/LICENSE +702 -0
- package/dist/card-metadata-updater.d.ts +33 -0
- package/dist/card-metadata-updater.js +121 -0
- package/dist/card-metadata-updater.js.map +1 -0
- package/dist/command-handler.d.ts +96 -0
- package/dist/command-handler.js +557 -0
- package/dist/command-handler.js.map +1 -0
- package/dist/command-manager.d.ts +43 -0
- package/dist/command-manager.js +73 -0
- package/dist/command-manager.js.map +1 -0
- package/dist/commands/calculate.d.ts +86 -0
- package/dist/commands/calculate.js +444 -0
- package/dist/commands/calculate.js.map +1 -0
- package/dist/commands/create.d.ts +114 -0
- package/dist/commands/create.js +389 -0
- package/dist/commands/create.js.map +1 -0
- package/dist/commands/edit.d.ts +37 -0
- package/dist/commands/edit.js +99 -0
- package/dist/commands/edit.js.map +1 -0
- package/dist/commands/export-site.d.ts +45 -0
- package/dist/commands/export-site.js +301 -0
- package/dist/commands/export-site.js.map +1 -0
- package/dist/commands/export.d.ts +53 -0
- package/dist/commands/export.js +251 -0
- package/dist/commands/export.js.map +1 -0
- package/dist/commands/import.d.ts +53 -0
- package/dist/commands/import.js +133 -0
- package/dist/commands/import.js.map +1 -0
- package/dist/commands/index.d.ts +26 -0
- package/dist/commands/index.js +27 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/move.d.ts +55 -0
- package/dist/commands/move.js +341 -0
- package/dist/commands/move.js.map +1 -0
- package/dist/commands/remove.d.ts +38 -0
- package/dist/commands/remove.js +192 -0
- package/dist/commands/remove.js.map +1 -0
- package/dist/commands/rename.d.ts +46 -0
- package/dist/commands/rename.js +289 -0
- package/dist/commands/rename.js.map +1 -0
- package/dist/commands/show.d.ts +124 -0
- package/dist/commands/show.js +345 -0
- package/dist/commands/show.js.map +1 -0
- package/dist/commands/transition.d.ts +27 -0
- package/dist/commands/transition.js +92 -0
- package/dist/commands/transition.js.map +1 -0
- package/dist/commands/update.d.ts +29 -0
- package/dist/commands/update.js +64 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/commands/validate.d.ts +143 -0
- package/dist/commands/validate.js +689 -0
- package/dist/commands/validate.js.map +1 -0
- package/dist/containers/card-container.d.ts +44 -0
- package/dist/containers/card-container.js +282 -0
- package/dist/containers/card-container.js.map +1 -0
- package/dist/containers/project/project-paths.d.ts +46 -0
- package/dist/containers/project/project-paths.js +105 -0
- package/dist/containers/project/project-paths.js.map +1 -0
- package/dist/containers/project/resource-collector.d.ts +86 -0
- package/dist/containers/project/resource-collector.js +331 -0
- package/dist/containers/project/resource-collector.js.map +1 -0
- package/dist/containers/project.d.ts +351 -0
- package/dist/containers/project.js +896 -0
- package/dist/containers/project.js.map +1 -0
- package/dist/containers/template.d.ts +108 -0
- package/dist/containers/template.js +433 -0
- package/dist/containers/template.js.map +1 -0
- package/dist/exceptions/index.d.ts +19 -0
- package/dist/exceptions/index.js +26 -0
- package/dist/exceptions/index.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/interfaces/adoc.d.ts +12 -0
- package/dist/interfaces/adoc.js +13 -0
- package/dist/interfaces/adoc.js.map +1 -0
- package/dist/interfaces/macros.d.ts +45 -0
- package/dist/interfaces/macros.js +13 -0
- package/dist/interfaces/macros.js.map +1 -0
- package/dist/interfaces/project-interfaces.d.ts +121 -0
- package/dist/interfaces/project-interfaces.js +21 -0
- package/dist/interfaces/project-interfaces.js.map +1 -0
- package/dist/interfaces/request-status-interfaces.d.ts +28 -0
- package/dist/interfaces/request-status-interfaces.js +20 -0
- package/dist/interfaces/request-status-interfaces.js.map +1 -0
- package/dist/interfaces/resource-interfaces.d.ts +117 -0
- package/dist/interfaces/resource-interfaces.js +20 -0
- package/dist/interfaces/resource-interfaces.js.map +1 -0
- package/dist/macros/base-macro.d.ts +31 -0
- package/dist/macros/base-macro.js +126 -0
- package/dist/macros/base-macro.js.map +1 -0
- package/dist/macros/common.d.ts +17 -0
- package/dist/macros/common.js +23 -0
- package/dist/macros/common.js.map +1 -0
- package/dist/macros/createCards/index.d.ts +36 -0
- package/dist/macros/createCards/index.js +35 -0
- package/dist/macros/createCards/index.js.map +1 -0
- package/dist/macros/createCards/metadata.d.ts +14 -0
- package/dist/macros/createCards/metadata.js +18 -0
- package/dist/macros/createCards/metadata.js.map +1 -0
- package/dist/macros/graph/index.d.ts +29 -0
- package/dist/macros/graph/index.js +91 -0
- package/dist/macros/graph/index.js.map +1 -0
- package/dist/macros/graph/metadata.d.ts +14 -0
- package/dist/macros/graph/metadata.js +18 -0
- package/dist/macros/graph/metadata.js.map +1 -0
- package/dist/macros/index.d.ts +93 -0
- package/dist/macros/index.js +237 -0
- package/dist/macros/index.js.map +1 -0
- package/dist/macros/report/index.d.ts +26 -0
- package/dist/macros/report/index.js +70 -0
- package/dist/macros/report/index.js.map +1 -0
- package/dist/macros/report/metadata.d.ts +14 -0
- package/dist/macros/report/metadata.js +18 -0
- package/dist/macros/report/metadata.js.map +1 -0
- package/dist/macros/scoreCard/index.d.ts +30 -0
- package/dist/macros/scoreCard/index.js +38 -0
- package/dist/macros/scoreCard/index.js.map +1 -0
- package/dist/macros/scoreCard/metadata.d.ts +14 -0
- package/dist/macros/scoreCard/metadata.js +18 -0
- package/dist/macros/scoreCard/metadata.js.map +1 -0
- package/dist/macros/task-queue.d.ts +46 -0
- package/dist/macros/task-queue.js +69 -0
- package/dist/macros/task-queue.js.map +1 -0
- package/dist/module-manager.d.ts +62 -0
- package/dist/module-manager.js +350 -0
- package/dist/module-manager.js.map +1 -0
- package/dist/permissions/action-guard.d.ts +28 -0
- package/dist/permissions/action-guard.js +61 -0
- package/dist/permissions/action-guard.js.map +1 -0
- package/dist/project-settings.d.ts +42 -0
- package/dist/project-settings.js +120 -0
- package/dist/project-settings.js.map +1 -0
- package/dist/resources/array-handler.d.ts +28 -0
- package/dist/resources/array-handler.js +116 -0
- package/dist/resources/array-handler.js.map +1 -0
- package/dist/resources/card-type-resource.d.ts +72 -0
- package/dist/resources/card-type-resource.js +334 -0
- package/dist/resources/card-type-resource.js.map +1 -0
- package/dist/resources/create-defaults.d.ts +81 -0
- package/dist/resources/create-defaults.js +184 -0
- package/dist/resources/create-defaults.js.map +1 -0
- package/dist/resources/field-type-resource.d.ts +88 -0
- package/dist/resources/field-type-resource.js +411 -0
- package/dist/resources/field-type-resource.js.map +1 -0
- package/dist/resources/file-resource.d.ts +50 -0
- package/dist/resources/file-resource.js +301 -0
- package/dist/resources/file-resource.js.map +1 -0
- package/dist/resources/folder-resource.d.ts +66 -0
- package/dist/resources/folder-resource.js +100 -0
- package/dist/resources/folder-resource.js.map +1 -0
- package/dist/resources/graph-model-resource.d.ts +78 -0
- package/dist/resources/graph-model-resource.js +164 -0
- package/dist/resources/graph-model-resource.js.map +1 -0
- package/dist/resources/graph-view-resource.d.ts +78 -0
- package/dist/resources/graph-view-resource.js +163 -0
- package/dist/resources/graph-view-resource.js.map +1 -0
- package/dist/resources/link-type-resource.d.ts +62 -0
- package/dist/resources/link-type-resource.js +150 -0
- package/dist/resources/link-type-resource.js.map +1 -0
- package/dist/resources/report-resource.d.ts +77 -0
- package/dist/resources/report-resource.js +171 -0
- package/dist/resources/report-resource.js.map +1 -0
- package/dist/resources/resource-object.d.ts +108 -0
- package/dist/resources/resource-object.js +147 -0
- package/dist/resources/resource-object.js.map +1 -0
- package/dist/resources/template-resource.d.ts +82 -0
- package/dist/resources/template-resource.js +173 -0
- package/dist/resources/template-resource.js.map +1 -0
- package/dist/resources/workflow-resource.d.ts +67 -0
- package/dist/resources/workflow-resource.js +156 -0
- package/dist/resources/workflow-resource.js.map +1 -0
- package/dist/types/queries.d.ts +142 -0
- package/dist/types/queries.js +16 -0
- package/dist/types/queries.js.map +1 -0
- package/dist/utils/card-utils.d.ts +34 -0
- package/dist/utils/card-utils.js +78 -0
- package/dist/utils/card-utils.js.map +1 -0
- package/dist/utils/clingo-fact-builder.d.ts +58 -0
- package/dist/utils/clingo-fact-builder.js +126 -0
- package/dist/utils/clingo-fact-builder.js.map +1 -0
- package/dist/utils/clingo-facts.d.ts +97 -0
- package/dist/utils/clingo-facts.js +352 -0
- package/dist/utils/clingo-facts.js.map +1 -0
- package/dist/utils/clingo-parser.d.ts +59 -0
- package/dist/utils/clingo-parser.js +403 -0
- package/dist/utils/clingo-parser.js.map +1 -0
- package/dist/utils/clingo-program-builder.d.ts +39 -0
- package/dist/utils/clingo-program-builder.js +57 -0
- package/dist/utils/clingo-program-builder.js.map +1 -0
- package/dist/utils/common-utils.d.ts +24 -0
- package/dist/utils/common-utils.js +47 -0
- package/dist/utils/common-utils.js.map +1 -0
- package/dist/utils/constants.d.ts +18 -0
- package/dist/utils/constants.js +27 -0
- package/dist/utils/constants.js.map +1 -0
- package/dist/utils/csv.d.ts +18 -0
- package/dist/utils/csv.js +45 -0
- package/dist/utils/csv.js.map +1 -0
- package/dist/utils/file-utils.d.ts +69 -0
- package/dist/utils/file-utils.js +158 -0
- package/dist/utils/file-utils.js.map +1 -0
- package/dist/utils/json.d.ts +61 -0
- package/dist/utils/json.js +108 -0
- package/dist/utils/json.js.map +1 -0
- package/dist/utils/lexorank.d.ts +59 -0
- package/dist/utils/lexorank.js +159 -0
- package/dist/utils/lexorank.js.map +1 -0
- package/dist/utils/log-utils.d.ts +40 -0
- package/dist/utils/log-utils.js +109 -0
- package/dist/utils/log-utils.js.map +1 -0
- package/dist/utils/random.d.ts +19 -0
- package/dist/utils/random.js +34 -0
- package/dist/utils/random.js.map +1 -0
- package/dist/utils/resource-utils.d.ts +45 -0
- package/dist/utils/resource-utils.js +137 -0
- package/dist/utils/resource-utils.js.map +1 -0
- package/dist/utils/sanitize-svg.d.ts +18 -0
- package/dist/utils/sanitize-svg.js +38 -0
- package/dist/utils/sanitize-svg.js.map +1 -0
- package/dist/utils/user-preferences.d.ts +64 -0
- package/dist/utils/user-preferences.js +106 -0
- package/dist/utils/user-preferences.js.map +1 -0
- package/dist/utils/validate.d.ts +26 -0
- package/dist/utils/validate.js +53 -0
- package/dist/utils/validate.js.map +1 -0
- package/dist/utils/value-utils.d.ts +58 -0
- package/dist/utils/value-utils.js +181 -0
- package/dist/utils/value-utils.js.map +1 -0
- package/package.json +67 -0
- package/src/card-metadata-updater.ts +182 -0
- package/src/command-handler.ts +686 -0
- package/src/command-manager.ts +99 -0
- package/src/commands/calculate.ts +591 -0
- package/src/commands/create.ts +559 -0
- package/src/commands/edit.ts +123 -0
- package/src/commands/export-site.ts +356 -0
- package/src/commands/export.ts +315 -0
- package/src/commands/import.ts +169 -0
- package/src/commands/index.ts +42 -0
- package/src/commands/move.ts +451 -0
- package/src/commands/remove.ts +244 -0
- package/src/commands/rename.ts +378 -0
- package/src/commands/show.ts +442 -0
- package/src/commands/transition.ts +127 -0
- package/src/commands/update.ts +76 -0
- package/src/commands/validate.ts +962 -0
- package/src/containers/card-container.ts +378 -0
- package/src/containers/project/project-paths.ts +127 -0
- package/src/containers/project/resource-collector.ts +379 -0
- package/src/containers/project.ts +1135 -0
- package/src/containers/template.ts +573 -0
- package/src/exceptions/index.ts +29 -0
- package/src/index.ts +33 -0
- package/src/interfaces/adoc.ts +18 -0
- package/src/interfaces/macros.ts +54 -0
- package/src/interfaces/project-interfaces.ts +208 -0
- package/src/interfaces/request-status-interfaces.ts +30 -0
- package/src/interfaces/resource-interfaces.ts +179 -0
- package/src/macros/base-macro.ts +176 -0
- package/src/macros/common.ts +24 -0
- package/src/macros/createCards/index.ts +57 -0
- package/src/macros/createCards/metadata.ts +21 -0
- package/src/macros/graph/index.ts +130 -0
- package/src/macros/graph/metadata.ts +21 -0
- package/src/macros/index.ts +321 -0
- package/src/macros/report/index.ts +88 -0
- package/src/macros/report/metadata.ts +21 -0
- package/src/macros/scoreCard/index.ts +55 -0
- package/src/macros/scoreCard/metadata.ts +21 -0
- package/src/macros/task-queue.ts +79 -0
- package/src/module-manager.ts +443 -0
- package/src/permissions/action-guard.ts +77 -0
- package/src/project-settings.ts +140 -0
- package/src/resources/array-handler.ts +141 -0
- package/src/resources/card-type-resource.ts +455 -0
- package/src/resources/create-defaults.ts +216 -0
- package/src/resources/field-type-resource.ts +533 -0
- package/src/resources/file-resource.ts +433 -0
- package/src/resources/folder-resource.ts +140 -0
- package/src/resources/graph-model-resource.ts +205 -0
- package/src/resources/graph-view-resource.ts +199 -0
- package/src/resources/link-type-resource.ts +191 -0
- package/src/resources/report-resource.ts +224 -0
- package/src/resources/resource-object.ts +246 -0
- package/src/resources/template-resource.ts +210 -0
- package/src/resources/workflow-resource.ts +205 -0
- package/src/types/queries.ts +149 -0
- package/src/utils/card-utils.ts +83 -0
- package/src/utils/clingo-fact-builder.ts +167 -0
- package/src/utils/clingo-facts.ts +550 -0
- package/src/utils/clingo-parser.ts +519 -0
- package/src/utils/clingo-program-builder.ts +71 -0
- package/src/utils/common-utils.ts +54 -0
- package/src/utils/constants.ts +32 -0
- package/src/utils/csv.ts +53 -0
- package/src/utils/file-utils.ts +182 -0
- package/src/utils/json.ts +118 -0
- package/src/utils/lexorank.ts +180 -0
- package/src/utils/log-utils.ts +127 -0
- package/src/utils/random.ts +37 -0
- package/src/utils/resource-utils.ts +180 -0
- package/src/utils/sanitize-svg.ts +46 -0
- package/src/utils/user-preferences.ts +126 -0
- package/src/utils/validate.ts +66 -0
- package/src/utils/value-utils.ts +189 -0
|
@@ -0,0 +1,169 @@
|
|
|
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
|
+
|
|
14
|
+
import type { CardType } from '../interfaces/resource-interfaces.js';
|
|
15
|
+
import { type Create, Validate } from './index.js';
|
|
16
|
+
import { ModuleManager } from '../module-manager.js';
|
|
17
|
+
import type { ModuleSettingOptions } from '../interfaces/project-interfaces.js';
|
|
18
|
+
import type { Project } from '../containers/project.js';
|
|
19
|
+
import { readCsvFile } from '../utils/csv.js';
|
|
20
|
+
import { resourceName } from '../utils/resource-utils.js';
|
|
21
|
+
import { TemplateResource } from '../resources/template-resource.js';
|
|
22
|
+
|
|
23
|
+
export class Import {
|
|
24
|
+
private moduleManager: ModuleManager;
|
|
25
|
+
constructor(
|
|
26
|
+
private project: Project,
|
|
27
|
+
private createCmd: Create,
|
|
28
|
+
) {
|
|
29
|
+
this.moduleManager = new ModuleManager(this.project, this);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Imports cards based on a csv file
|
|
34
|
+
* @param csvFilePath path to the csv file
|
|
35
|
+
* @param parentCardKey the cards in the csv file will be created under this card
|
|
36
|
+
* @returns card keys of the imported cards
|
|
37
|
+
*/
|
|
38
|
+
public async importCsv(
|
|
39
|
+
csvFilePath: string,
|
|
40
|
+
parentCardKey?: string,
|
|
41
|
+
): Promise<string[]> {
|
|
42
|
+
const csv = await readCsvFile(csvFilePath);
|
|
43
|
+
|
|
44
|
+
const isValid = Validate.getInstance().validateJson(csv, 'csvSchema');
|
|
45
|
+
if (isValid.length !== 0) {
|
|
46
|
+
throw new Error(isValid);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const importedCards = [];
|
|
50
|
+
|
|
51
|
+
for (const row of csv) {
|
|
52
|
+
const { title, template, description, labels, ...customFields } = row;
|
|
53
|
+
const templateResource = new TemplateResource(
|
|
54
|
+
this.project,
|
|
55
|
+
resourceName(template),
|
|
56
|
+
);
|
|
57
|
+
const templateObject = templateResource.templateObject();
|
|
58
|
+
if (!templateObject) {
|
|
59
|
+
throw new Error(`Template '${template}' not found`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const templateCards = await templateObject.cards();
|
|
63
|
+
if (templateCards.length !== 1) {
|
|
64
|
+
console.warn(
|
|
65
|
+
`Template '${template}' for card '${title}' does not have exactly one card. Skipping row.`,
|
|
66
|
+
);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Create card
|
|
71
|
+
const cards = await this.createCmd.createCard(template, parentCardKey);
|
|
72
|
+
|
|
73
|
+
if (cards.length !== 1) {
|
|
74
|
+
throw new Error('Card not created');
|
|
75
|
+
}
|
|
76
|
+
const cardKey = cards[0].key;
|
|
77
|
+
const card = await this.project.findSpecificCard(cardKey, {
|
|
78
|
+
metadata: true,
|
|
79
|
+
});
|
|
80
|
+
const cardType = await this.project.resource<CardType>(
|
|
81
|
+
card?.metadata?.cardType || '',
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
if (!cardType) {
|
|
85
|
+
throw new Error(`Card type not found for card ${cardKey}`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (description) {
|
|
89
|
+
await this.project.updateCardContent(cardKey, description);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (labels) {
|
|
93
|
+
for (const label of labels.split(' ')) {
|
|
94
|
+
try {
|
|
95
|
+
await this.createCmd.createLabel(cardKey, label);
|
|
96
|
+
} catch (e) {
|
|
97
|
+
console.error(
|
|
98
|
+
`Failed to create label ${label}: ${e instanceof Error ? e.message : 'Unknown error'}`,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
await this.project.updateCardMetadataKey(cardKey, 'title', title);
|
|
105
|
+
for (const [key, value] of Object.entries(customFields)) {
|
|
106
|
+
if (cardType.customFields.find((field) => field.name === key)) {
|
|
107
|
+
await this.project.updateCardMetadataKey(cardKey, key, value);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
importedCards.push(cardKey);
|
|
111
|
+
}
|
|
112
|
+
return importedCards;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Imports a module to a project. Copies resources to the project.
|
|
117
|
+
* Resources will be added to a new directory under '.cards/modules'.
|
|
118
|
+
* The name of the new folder will be module's prefix.
|
|
119
|
+
*
|
|
120
|
+
* Note that file references are relative, and thus URI must be
|
|
121
|
+
* 'file:<relative path>', instead of 'file://<relative path>'.
|
|
122
|
+
*
|
|
123
|
+
* @param source Path to module that will be imported
|
|
124
|
+
* @param destination Path to project that will receive the imported module
|
|
125
|
+
* @param options Additional options for module import. Optional.
|
|
126
|
+
* branch: Git branch for module from Git.
|
|
127
|
+
* private: If true, uses credentials to clone the repository
|
|
128
|
+
*/
|
|
129
|
+
public async importModule(
|
|
130
|
+
source: string,
|
|
131
|
+
destination?: string,
|
|
132
|
+
options?: ModuleSettingOptions,
|
|
133
|
+
) {
|
|
134
|
+
const gitModule = source.startsWith('https');
|
|
135
|
+
const modulePrefix = gitModule
|
|
136
|
+
? await this.moduleManager.importGitModule(source, options)
|
|
137
|
+
: await this.moduleManager.importFileModule(source, destination);
|
|
138
|
+
|
|
139
|
+
if (!modulePrefix) {
|
|
140
|
+
throw new Error(
|
|
141
|
+
`Cannot find prefix for imported module '${source}'. Import cancelled.`,
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Add module as a dependency.
|
|
146
|
+
return this.project.importModule({
|
|
147
|
+
name: modulePrefix,
|
|
148
|
+
branch: options ? options.branch : undefined,
|
|
149
|
+
private: options ? options.private : undefined,
|
|
150
|
+
location: gitModule ? source : `file:${source}`,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Updates all imported modules.
|
|
156
|
+
*/
|
|
157
|
+
public async updateAllModules() {
|
|
158
|
+
return this.moduleManager.update();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Updates 'moduleName' module from its source.
|
|
163
|
+
* Modules using gitUrl, are first copied to .temp
|
|
164
|
+
* @param moduleName module name (prefix) to update
|
|
165
|
+
*/
|
|
166
|
+
public async updateExistingModule(moduleName: string) {
|
|
167
|
+
await this.moduleManager.importFileModule(moduleName);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Cyberismo
|
|
3
|
+
Copyright © Cyberismo Ltd and contributors 2025
|
|
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. This program is distributed in the hope that it
|
|
7
|
+
will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
|
8
|
+
of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
|
|
9
|
+
General Public License for more details.
|
|
10
|
+
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
|
+
|
|
14
|
+
import { Calculate } from './calculate.js';
|
|
15
|
+
import { Create } from './create.js';
|
|
16
|
+
import { Edit } from './edit.js';
|
|
17
|
+
import { Export } from './export.js';
|
|
18
|
+
import { ExportSite } from './export-site.js';
|
|
19
|
+
import { Import } from './import.js';
|
|
20
|
+
import { Move } from './move.js';
|
|
21
|
+
import { Remove } from './remove.js';
|
|
22
|
+
import { Rename } from './rename.js';
|
|
23
|
+
import { Show } from './show.js';
|
|
24
|
+
import { Transition } from './transition.js';
|
|
25
|
+
import { Update } from './update.js';
|
|
26
|
+
import { Validate } from './validate.js';
|
|
27
|
+
|
|
28
|
+
export {
|
|
29
|
+
Calculate,
|
|
30
|
+
Create,
|
|
31
|
+
Edit,
|
|
32
|
+
Export,
|
|
33
|
+
ExportSite,
|
|
34
|
+
Import,
|
|
35
|
+
Move,
|
|
36
|
+
Remove,
|
|
37
|
+
Rename,
|
|
38
|
+
Show,
|
|
39
|
+
Transition,
|
|
40
|
+
Update,
|
|
41
|
+
Validate,
|
|
42
|
+
};
|
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Cyberismo
|
|
3
|
+
Copyright © Cyberismo Ltd and contributors 2024
|
|
4
|
+
|
|
5
|
+
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License version 3 as published by the Free Software Foundation.
|
|
6
|
+
|
|
7
|
+
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
|
|
8
|
+
|
|
9
|
+
You should have received a copy of the GNU Affero General Public
|
|
10
|
+
License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// node
|
|
14
|
+
import { join, sep } from 'node:path';
|
|
15
|
+
|
|
16
|
+
import { ActionGuard } from '../permissions/action-guard.js';
|
|
17
|
+
import { copyDir, deleteDir } from '../utils/file-utils.js';
|
|
18
|
+
import type { Calculate } from './index.js';
|
|
19
|
+
import type {
|
|
20
|
+
Card,
|
|
21
|
+
FetchCardDetails,
|
|
22
|
+
} from '../interfaces/project-interfaces.js';
|
|
23
|
+
import { type Project, ResourcesFrom } from '../containers/project.js';
|
|
24
|
+
import {
|
|
25
|
+
EMPTY_RANK,
|
|
26
|
+
FIRST_RANK,
|
|
27
|
+
getRankAfter,
|
|
28
|
+
getRankBetween,
|
|
29
|
+
rebalanceRanks,
|
|
30
|
+
sortItems,
|
|
31
|
+
} from '../utils/lexorank.js';
|
|
32
|
+
import { isTemplateCard } from '../utils/card-utils.js';
|
|
33
|
+
import { resourceName } from '../utils/resource-utils.js';
|
|
34
|
+
import { TemplateResource } from '../resources/template-resource.js';
|
|
35
|
+
|
|
36
|
+
// @todo - we should have project wide constants, so that if we need them, only the const value needs to be changed.
|
|
37
|
+
const ROOT: string = 'root';
|
|
38
|
+
|
|
39
|
+
export class Move {
|
|
40
|
+
constructor(
|
|
41
|
+
private project: Project,
|
|
42
|
+
private calculateCmd: Calculate,
|
|
43
|
+
) {}
|
|
44
|
+
|
|
45
|
+
// Fetches a card (either template or project card).
|
|
46
|
+
private async getCard(cardKey: string, options: FetchCardDetails) {
|
|
47
|
+
let card: Card | undefined;
|
|
48
|
+
const templateCard = await this.project.isTemplateCard(cardKey);
|
|
49
|
+
if (templateCard) {
|
|
50
|
+
card = (await this.project.allTemplateCards(options)).find(
|
|
51
|
+
(card) => card.key === cardKey,
|
|
52
|
+
);
|
|
53
|
+
} else {
|
|
54
|
+
card = await this.project.findSpecificCard(cardKey, options);
|
|
55
|
+
}
|
|
56
|
+
if (!card) {
|
|
57
|
+
throw new Error('Card was not found from the project');
|
|
58
|
+
}
|
|
59
|
+
return card;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Returns children of a parent card or root cards
|
|
63
|
+
private async getSiblings(card: Card) {
|
|
64
|
+
const parentCardKey = card.parent || ROOT;
|
|
65
|
+
|
|
66
|
+
// since we don't know if 'root' is templateRoot or cardRoot, we need to check the card
|
|
67
|
+
if (parentCardKey === ROOT) {
|
|
68
|
+
if (isTemplateCard(card)) {
|
|
69
|
+
const template = this.project.createTemplateObjectFromCard(card);
|
|
70
|
+
if (!template) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
`Cannot find template for the template card '${card.key}'`,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
if (card?.path.includes(`${sep}modules${sep}`)) {
|
|
76
|
+
throw new Error(`Cannot rank module cards`);
|
|
77
|
+
}
|
|
78
|
+
return template.cards();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
let parentCard;
|
|
83
|
+
if (parentCardKey !== ROOT) {
|
|
84
|
+
parentCard = await this.project.findSpecificCard(parentCardKey, {
|
|
85
|
+
children: true,
|
|
86
|
+
metadata: true,
|
|
87
|
+
});
|
|
88
|
+
if (!parentCard) {
|
|
89
|
+
throw new Error(`Card ${parentCardKey} not found from project`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (parentCard) {
|
|
94
|
+
return parentCard.children || [];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return this.project.showProjectCards();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
//
|
|
101
|
+
private async rebalanceCards(cards: Card[]) {
|
|
102
|
+
const ranks = rebalanceRanks(cards.length);
|
|
103
|
+
|
|
104
|
+
cards = sortItems(cards, (item) => item.metadata?.rank || 'z');
|
|
105
|
+
|
|
106
|
+
for (let i = 0; i < cards.length; i++) {
|
|
107
|
+
const card = cards[i];
|
|
108
|
+
await this.project.updateCardMetadataKey(card.key, 'rank', ranks[i]);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Rebalances the project recursively.
|
|
113
|
+
private async rebalanceProjectRecursively(cards: Card[]) {
|
|
114
|
+
const ranks = rebalanceRanks(cards.length);
|
|
115
|
+
|
|
116
|
+
cards = sortItems(cards, (item) => item.metadata?.rank || 'z');
|
|
117
|
+
|
|
118
|
+
for (let i = 0; i < cards.length; i++) {
|
|
119
|
+
const card = cards[i];
|
|
120
|
+
await this.project.updateCardMetadataKey(card.key, 'rank', ranks[i]);
|
|
121
|
+
if (card.children && card.children.length > 0) {
|
|
122
|
+
await this.rebalanceProjectRecursively(card.children);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Moves card from 'destination' to 'source'.
|
|
129
|
+
* @param source source card to move
|
|
130
|
+
* @param destination destination card where source card will be moved to; or to root
|
|
131
|
+
*/
|
|
132
|
+
public async moveCard(source: string, destination: string) {
|
|
133
|
+
if (source === ROOT) {
|
|
134
|
+
throw new Error('Cannot move "root"');
|
|
135
|
+
}
|
|
136
|
+
if (source === destination) {
|
|
137
|
+
throw new Error(`Card cannot be moved to itself`);
|
|
138
|
+
}
|
|
139
|
+
const promiseContainer = [];
|
|
140
|
+
promiseContainer.push(this.project.findSpecificCard(source));
|
|
141
|
+
if (destination !== ROOT) {
|
|
142
|
+
promiseContainer.push(this.project.findSpecificCard(destination));
|
|
143
|
+
} else {
|
|
144
|
+
const returnObject: Card = {
|
|
145
|
+
key: '',
|
|
146
|
+
path: this.project.paths.cardRootFolder,
|
|
147
|
+
children: [],
|
|
148
|
+
attachments: [],
|
|
149
|
+
};
|
|
150
|
+
promiseContainer.push(Promise.resolve(returnObject));
|
|
151
|
+
}
|
|
152
|
+
const [sourceCard, destinationCard] = await Promise.all(promiseContainer);
|
|
153
|
+
|
|
154
|
+
if (!sourceCard) {
|
|
155
|
+
throw new Error(`Card ${source} not found from project`);
|
|
156
|
+
}
|
|
157
|
+
if (!destinationCard) {
|
|
158
|
+
throw new Error(`Card ${destination} not found from project`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (destinationCard.path.includes(source)) {
|
|
162
|
+
throw new Error(`Card cannot be moved to inside itself`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Imported templates cannot be modified.
|
|
166
|
+
if (
|
|
167
|
+
destinationCard.path.includes(`${sep}modules`) ||
|
|
168
|
+
sourceCard.path.includes(`${sep}modules${sep}`)
|
|
169
|
+
) {
|
|
170
|
+
throw new Error(`Cannot modify imported module templates`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const bothTemplateCards =
|
|
174
|
+
isTemplateCard(sourceCard) && isTemplateCard(destinationCard);
|
|
175
|
+
const bothProjectCards =
|
|
176
|
+
this.project.hasCard(sourceCard.key) &&
|
|
177
|
+
this.project.hasCard(destinationCard.key);
|
|
178
|
+
if (!(bothTemplateCards || bothProjectCards)) {
|
|
179
|
+
throw new Error(
|
|
180
|
+
`Cards cannot be moved from project to template or vice versa`,
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const destinationPath =
|
|
185
|
+
destination === ROOT
|
|
186
|
+
? join(this.project.paths.cardRootFolder, sourceCard.key)
|
|
187
|
+
: join(destinationCard.path, 'c', sourceCard.key);
|
|
188
|
+
|
|
189
|
+
// if the card is already in the destination, do nothing
|
|
190
|
+
if (sourceCard.path === destinationPath) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// if both are project cards, make sure source card can be moved
|
|
195
|
+
const actionGuard = new ActionGuard(this.calculateCmd);
|
|
196
|
+
await actionGuard.checkPermission('move', source);
|
|
197
|
+
|
|
198
|
+
// rerank the card in the new location
|
|
199
|
+
// it will be the last one in the new location
|
|
200
|
+
let children;
|
|
201
|
+
if (destination !== ROOT) {
|
|
202
|
+
const parent = await this.project.findSpecificCard(destination, {
|
|
203
|
+
children: true,
|
|
204
|
+
metadata: true,
|
|
205
|
+
});
|
|
206
|
+
if (!parent) {
|
|
207
|
+
throw new Error(`Parent card ${destination} not found from project`);
|
|
208
|
+
}
|
|
209
|
+
children = parent.children;
|
|
210
|
+
} else {
|
|
211
|
+
children = await this.project.showProjectCards();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (!children) {
|
|
215
|
+
throw new Error(`Children not found from card ${destination}`);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
children = sortItems(children, (item) => item?.metadata?.rank || '1|z');
|
|
219
|
+
const lastChild = children[children.length - 1];
|
|
220
|
+
|
|
221
|
+
const rank =
|
|
222
|
+
lastChild && lastChild.metadata
|
|
223
|
+
? getRankAfter(lastChild.metadata.rank)
|
|
224
|
+
: FIRST_RANK;
|
|
225
|
+
await this.project.updateCardMetadataKey(sourceCard.key, 'rank', rank);
|
|
226
|
+
await copyDir(sourceCard.path, destinationPath);
|
|
227
|
+
await deleteDir(sourceCard.path);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Ranks card using position given as 'index'.
|
|
232
|
+
* @param cardKey card key
|
|
233
|
+
* @param index to which position should card be ranked to
|
|
234
|
+
*/
|
|
235
|
+
public async rankByIndex(cardKey: string, index: number) {
|
|
236
|
+
if (index < 0) {
|
|
237
|
+
throw new Error(`Index must be greater than 0`);
|
|
238
|
+
}
|
|
239
|
+
if (index === 0) {
|
|
240
|
+
await this.rankFirst(cardKey);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const card = await this.project.findSpecificCard(cardKey, {
|
|
245
|
+
metadata: true,
|
|
246
|
+
parent: true,
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
if (!card || !card.parent) {
|
|
250
|
+
throw new Error(`Card ${cardKey} not found from project`);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const children = sortItems(
|
|
254
|
+
await this.getSiblings(card),
|
|
255
|
+
(item) => item.metadata?.rank || EMPTY_RANK,
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
if (!children || children.length === 0) {
|
|
259
|
+
throw new Error(`Children not found from card ${card.parent}`);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (children.length < index) {
|
|
263
|
+
throw new Error(`Index ${index} is out of bounds`);
|
|
264
|
+
}
|
|
265
|
+
await this.rankCard(cardKey, children[index - 1].key);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Sets the rank of a card to be after another card.
|
|
270
|
+
* @param cardKey Card to rank
|
|
271
|
+
* @param beforeCardKey Card key after which the card will be ranked
|
|
272
|
+
*/
|
|
273
|
+
public async rankCard(cardKey: string, beforeCardKey: string) {
|
|
274
|
+
const card = await this.project.findSpecificCard(cardKey, {
|
|
275
|
+
metadata: true,
|
|
276
|
+
parent: true,
|
|
277
|
+
});
|
|
278
|
+
if (!card) {
|
|
279
|
+
throw new Error(`Card ${cardKey} not found from project`);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const beforeCard = await this.project.findSpecificCard(beforeCardKey, {
|
|
283
|
+
metadata: true,
|
|
284
|
+
parent: true,
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
if (!beforeCard) {
|
|
288
|
+
throw new Error(`Card ${beforeCardKey} not found from project`);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (beforeCard.parent !== card.parent) {
|
|
292
|
+
throw new Error(`Cards must be from the same parent`);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const children = sortItems(
|
|
296
|
+
await this.getSiblings(beforeCard),
|
|
297
|
+
(item) => item.metadata?.rank || EMPTY_RANK,
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
if (!children) {
|
|
301
|
+
throw new Error(`Children not found from card ${beforeCard.parent}`);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const beforeCardIndex = children.findIndex(
|
|
305
|
+
(child) => child.key === beforeCard.key,
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
if (beforeCardIndex === -1) {
|
|
309
|
+
throw new Error(
|
|
310
|
+
`Card ${beforeCardKey} is not a child of ${beforeCard.parent}`,
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (
|
|
315
|
+
children[beforeCardIndex].key === cardKey ||
|
|
316
|
+
children[beforeCardIndex + 1]?.key === cardKey
|
|
317
|
+
) {
|
|
318
|
+
throw new Error(`Card ${cardKey} is already in the correct position`);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (beforeCardIndex === children.length - 1) {
|
|
322
|
+
await this.project.updateCardMetadataKey(
|
|
323
|
+
cardKey,
|
|
324
|
+
'rank',
|
|
325
|
+
getRankAfter(beforeCard.metadata?.rank as string),
|
|
326
|
+
);
|
|
327
|
+
} else {
|
|
328
|
+
await this.project.updateCardMetadataKey(
|
|
329
|
+
cardKey,
|
|
330
|
+
'rank',
|
|
331
|
+
getRankBetween(
|
|
332
|
+
beforeCard.metadata?.rank as string,
|
|
333
|
+
children[beforeCardIndex + 1].metadata?.rank as string,
|
|
334
|
+
),
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Ranks card first.
|
|
341
|
+
* @param cardKey card key
|
|
342
|
+
*/
|
|
343
|
+
public async rankFirst(cardKey: string) {
|
|
344
|
+
const card = await this.getCard(cardKey, {
|
|
345
|
+
metadata: true,
|
|
346
|
+
parent: true,
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
const children = sortItems(
|
|
350
|
+
await this.getSiblings(card),
|
|
351
|
+
(item) => item.metadata?.rank || EMPTY_RANK,
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
if (!children || children.length === 0) {
|
|
355
|
+
throw new Error(`Children not found from card ${card.parent}`);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (children[0].key === cardKey && children[0].metadata?.rank) {
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const firstRank = children[0].metadata?.rank;
|
|
363
|
+
if (!firstRank) {
|
|
364
|
+
await this.project.updateCardMetadataKey(cardKey, 'rank', FIRST_RANK);
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Set the rank to be the first one
|
|
369
|
+
if (firstRank === FIRST_RANK) {
|
|
370
|
+
// if the first card is already at the first rank, we need to move the card to the next one
|
|
371
|
+
const secondRank = children[1].metadata?.rank;
|
|
372
|
+
if (!secondRank) {
|
|
373
|
+
throw new Error(`Second rank not found`);
|
|
374
|
+
}
|
|
375
|
+
const rankBetween = getRankBetween(firstRank, secondRank);
|
|
376
|
+
await this.project.updateCardMetadataKey(
|
|
377
|
+
children[0].key,
|
|
378
|
+
'rank',
|
|
379
|
+
rankBetween,
|
|
380
|
+
);
|
|
381
|
+
await this.project.updateCardMetadataKey(cardKey, 'rank', firstRank);
|
|
382
|
+
} else {
|
|
383
|
+
// if the card is not at the first rank, we just use the first rank
|
|
384
|
+
await this.project.updateCardMetadataKey(cardKey, 'rank', FIRST_RANK);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Rebalances the ranks of the children of a card.
|
|
390
|
+
* @param parentCardKey parent card key
|
|
391
|
+
*/
|
|
392
|
+
public async rebalanceChildren(parentCardKey: string) {
|
|
393
|
+
const parentCard = await this.project.findSpecificCard(parentCardKey, {
|
|
394
|
+
children: true,
|
|
395
|
+
metadata: true,
|
|
396
|
+
});
|
|
397
|
+
if (!parentCard || !parentCard.children) {
|
|
398
|
+
throw new Error(`Card ${parentCardKey} not found from project`);
|
|
399
|
+
}
|
|
400
|
+
await this.rebalanceCards(parentCard.children);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Rebalances the ranks of the cards in the whole project, including templates
|
|
405
|
+
* Can be used even if the ranks do not exist
|
|
406
|
+
*/
|
|
407
|
+
public async rebalanceProject() {
|
|
408
|
+
const cards = await this.project.showProjectCards();
|
|
409
|
+
|
|
410
|
+
await this.rebalanceProjectRecursively(cards);
|
|
411
|
+
|
|
412
|
+
// rebalance templates
|
|
413
|
+
const templates = await this.project.templates(ResourcesFrom.localOnly);
|
|
414
|
+
for (const template of templates) {
|
|
415
|
+
const templateResource = new TemplateResource(
|
|
416
|
+
this.project,
|
|
417
|
+
resourceName(template.name),
|
|
418
|
+
);
|
|
419
|
+
const templateObject = templateResource.templateObject();
|
|
420
|
+
|
|
421
|
+
if (!templateObject) {
|
|
422
|
+
throw new Error(`Template '${template.name}' not found`);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const templateCards = await templateObject.cards('', {
|
|
426
|
+
parent: true,
|
|
427
|
+
metadata: true,
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
const cardGroups = templateCards.reduce(
|
|
431
|
+
(result, card) => {
|
|
432
|
+
// template card root cards have a parent(the template itself) so this shouldn't happen
|
|
433
|
+
if (!card.parent) {
|
|
434
|
+
return result;
|
|
435
|
+
}
|
|
436
|
+
// if the parent does not exist yet in the result, we create it
|
|
437
|
+
if (!result[card.parent]) {
|
|
438
|
+
result[card.parent] = [];
|
|
439
|
+
}
|
|
440
|
+
result[card.parent].push(card);
|
|
441
|
+
return result;
|
|
442
|
+
},
|
|
443
|
+
{} as Record<string, Card[]>,
|
|
444
|
+
);
|
|
445
|
+
|
|
446
|
+
for (const [, cards] of Object.entries(cardGroups)) {
|
|
447
|
+
await this.rebalanceCards(cards);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|