@cyberismo/data-handler 0.0.21 → 0.0.22
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/command-handler.js +13 -24
- package/dist/command-handler.js.map +1 -1
- package/dist/command-manager.d.ts +21 -6
- package/dist/command-manager.js +34 -32
- package/dist/command-manager.js.map +1 -1
- package/dist/commands/calculate.js +101 -46
- package/dist/commands/calculate.js.map +1 -1
- package/dist/commands/create.js +417 -328
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/edit.js +117 -68
- package/dist/commands/edit.js.map +1 -1
- package/dist/commands/export.js +301 -252
- package/dist/commands/export.js.map +1 -1
- package/dist/commands/fetch.js +205 -156
- package/dist/commands/fetch.js.map +1 -1
- package/dist/commands/import.js +189 -134
- package/dist/commands/import.js.map +1 -1
- package/dist/commands/migrate.js +91 -45
- package/dist/commands/migrate.js.map +1 -1
- package/dist/commands/move.js +347 -267
- package/dist/commands/move.js.map +1 -1
- package/dist/commands/remove.d.ts +1 -0
- package/dist/commands/remove.js +202 -135
- package/dist/commands/remove.js.map +1 -1
- package/dist/commands/rename.js +233 -187
- package/dist/commands/rename.js.map +1 -1
- package/dist/commands/show.d.ts +8 -8
- package/dist/commands/show.js +477 -372
- package/dist/commands/show.js.map +1 -1
- package/dist/commands/transition.js +119 -73
- package/dist/commands/transition.js.map +1 -1
- package/dist/commands/update.js +8 -3
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/validate.js +1 -1
- package/dist/commands/validate.js.map +1 -1
- package/dist/containers/project/calculation-engine.js +0 -1
- package/dist/containers/project/calculation-engine.js.map +1 -1
- package/dist/containers/project/card-cache.js +1 -1
- package/dist/containers/project/card-cache.js.map +1 -1
- package/dist/containers/project.d.ts +16 -0
- package/dist/containers/project.js +59 -1
- package/dist/containers/project.js.map +1 -1
- package/dist/containers/template.js +1 -1
- package/dist/containers/template.js.map +1 -1
- package/dist/interfaces/command-options.d.ts +1 -0
- package/dist/interfaces/resource-interfaces.d.ts +5 -12
- package/dist/interfaces/resource-interfaces.js.map +1 -1
- package/dist/macros/base-macro.js +1 -1
- package/dist/macros/base-macro.js.map +1 -1
- package/dist/macros/graph/index.js +3 -1
- package/dist/macros/graph/index.js.map +1 -1
- package/dist/macros/index.js +3 -1
- package/dist/macros/index.js.map +1 -1
- package/dist/macros/report/index.js +1 -1
- package/dist/macros/report/index.js.map +1 -1
- package/dist/module-manager.js +5 -3
- package/dist/module-manager.js.map +1 -1
- package/dist/project-settings.js +2 -2
- package/dist/project-settings.js.map +1 -1
- package/dist/resources/card-type-resource.js +1 -1
- package/dist/resources/card-type-resource.js.map +1 -1
- package/dist/resources/create-defaults.js +0 -1
- package/dist/resources/create-defaults.js.map +1 -1
- package/dist/resources/field-type-resource.js +2 -5
- package/dist/resources/field-type-resource.js.map +1 -1
- package/dist/resources/file-resource.js +4 -1
- package/dist/resources/file-resource.js.map +1 -1
- package/dist/resources/folder-resource.d.ts +1 -1
- package/dist/resources/folder-resource.js +4 -1
- package/dist/resources/folder-resource.js.map +1 -1
- package/dist/resources/graph-model-resource.d.ts +1 -8
- package/dist/resources/graph-model-resource.js +0 -14
- package/dist/resources/graph-model-resource.js.map +1 -1
- package/dist/resources/graph-view-resource.d.ts +1 -8
- package/dist/resources/graph-view-resource.js +0 -14
- package/dist/resources/graph-view-resource.js.map +1 -1
- package/dist/resources/link-type-resource.js +1 -1
- package/dist/resources/link-type-resource.js.map +1 -1
- package/dist/resources/report-resource.d.ts +1 -8
- package/dist/resources/report-resource.js +0 -14
- package/dist/resources/report-resource.js.map +1 -1
- package/dist/resources/resource-object.d.ts +11 -1
- package/dist/resources/resource-object.js +19 -2
- package/dist/resources/resource-object.js.map +1 -1
- package/dist/resources/template-resource.d.ts +1 -9
- package/dist/resources/template-resource.js +0 -15
- package/dist/resources/template-resource.js.map +1 -1
- package/dist/resources/workflow-resource.js +1 -1
- package/dist/resources/workflow-resource.js.map +1 -1
- package/dist/utils/card-utils.js +1 -1
- package/dist/utils/card-utils.js.map +1 -1
- package/dist/utils/commit-context.d.ts +23 -0
- package/dist/utils/commit-context.js +30 -0
- package/dist/utils/commit-context.js.map +1 -0
- package/dist/utils/file-utils.js +3 -1
- package/dist/utils/file-utils.js.map +1 -1
- package/dist/utils/git-manager.d.ts +29 -0
- package/dist/utils/git-manager.js +76 -0
- package/dist/utils/git-manager.js.map +1 -0
- package/dist/utils/handlebars-helpers.d.ts +22 -0
- package/dist/utils/handlebars-helpers.js +78 -0
- package/dist/utils/handlebars-helpers.js.map +1 -0
- package/dist/utils/json.js +6 -2
- package/dist/utils/json.js.map +1 -1
- package/dist/utils/log-utils.d.ts +7 -2
- package/dist/utils/log-utils.js +28 -3
- package/dist/utils/log-utils.js.map +1 -1
- package/dist/utils/report.d.ts +0 -19
- package/dist/utils/report.js +4 -67
- package/dist/utils/report.js.map +1 -1
- package/dist/utils/rw-lock.d.ts +71 -0
- package/dist/utils/rw-lock.js +220 -0
- package/dist/utils/rw-lock.js.map +1 -0
- package/dist/utils/user-preferences.js +3 -3
- package/dist/utils/user-preferences.js.map +1 -1
- package/package.json +5 -5
- package/src/command-handler.ts +14 -22
- package/src/command-manager.ts +43 -37
- package/src/commands/calculate.ts +8 -1
- package/src/commands/create.ts +24 -1
- package/src/commands/edit.ts +3 -0
- package/src/commands/export.ts +8 -2
- package/src/commands/fetch.ts +3 -0
- package/src/commands/import.ts +5 -0
- package/src/commands/migrate.ts +2 -0
- package/src/commands/move.ts +34 -0
- package/src/commands/remove.ts +24 -2
- package/src/commands/rename.ts +2 -0
- package/src/commands/show.ts +63 -34
- package/src/commands/transition.ts +2 -0
- package/src/commands/update.ts +9 -3
- package/src/commands/validate.ts +1 -1
- package/src/containers/project/calculation-engine.ts +0 -1
- package/src/containers/project/card-cache.ts +1 -0
- package/src/containers/project.ts +75 -1
- package/src/containers/template.ts +1 -1
- package/src/interfaces/command-options.ts +1 -0
- package/src/interfaces/resource-interfaces.ts +5 -12
- package/src/macros/base-macro.ts +1 -1
- package/src/macros/graph/index.ts +3 -0
- package/src/macros/index.ts +3 -1
- package/src/macros/report/index.ts +1 -0
- package/src/module-manager.ts +5 -2
- package/src/project-settings.ts +2 -1
- package/src/resources/card-type-resource.ts +1 -1
- package/src/resources/create-defaults.ts +0 -1
- package/src/resources/field-type-resource.ts +2 -4
- package/src/resources/file-resource.ts +3 -1
- package/src/resources/folder-resource.ts +7 -2
- package/src/resources/graph-model-resource.ts +1 -25
- package/src/resources/graph-view-resource.ts +1 -25
- package/src/resources/link-type-resource.ts +1 -1
- package/src/resources/report-resource.ts +1 -25
- package/src/resources/resource-object.ts +22 -1
- package/src/resources/template-resource.ts +0 -23
- package/src/resources/workflow-resource.ts +1 -1
- package/src/utils/card-utils.ts +1 -1
- package/src/utils/commit-context.ts +45 -0
- package/src/utils/file-utils.ts +3 -1
- package/src/utils/git-manager.ts +87 -0
- package/src/utils/handlebars-helpers.ts +95 -0
- package/src/utils/json.ts +6 -2
- package/src/utils/log-utils.ts +33 -4
- package/src/utils/report.ts +8 -78
- package/src/utils/rw-lock.ts +279 -0
- package/src/utils/user-preferences.ts +3 -0
package/src/commands/move.ts
CHANGED
|
@@ -18,6 +18,7 @@ import { ActionGuard } from '../permissions/action-guard.js';
|
|
|
18
18
|
import { copyDir, deleteDir } from '../utils/file-utils.js';
|
|
19
19
|
import type { Card } from '../interfaces/project-interfaces.js';
|
|
20
20
|
import type { Project } from '../containers/project.js';
|
|
21
|
+
import { write } from '../utils/rw-lock.js';
|
|
21
22
|
import {
|
|
22
23
|
EMPTY_RANK,
|
|
23
24
|
FIRST_RANK,
|
|
@@ -100,6 +101,7 @@ export class Move {
|
|
|
100
101
|
* @param source source card to move
|
|
101
102
|
* @param destination destination card where source card will be moved to; or to root
|
|
102
103
|
*/
|
|
104
|
+
@write((source, destination) => `Move card ${source} to ${destination}`)
|
|
103
105
|
public async moveCard(source: string, destination: string) {
|
|
104
106
|
if (source === ROOT) {
|
|
105
107
|
throw new Error('Cannot move "root"');
|
|
@@ -188,6 +190,9 @@ export class Move {
|
|
|
188
190
|
? getRankAfter(lastChild.metadata.rank)
|
|
189
191
|
: FIRST_RANK;
|
|
190
192
|
|
|
193
|
+
// Save old path before moving (needed to update descendant paths)
|
|
194
|
+
const oldPath = sourceCard.path;
|
|
195
|
+
|
|
191
196
|
// First do the file operations, then update metadata
|
|
192
197
|
await copyDir(sourceCard.path, destinationPath);
|
|
193
198
|
await deleteDir(sourceCard.path);
|
|
@@ -199,8 +204,32 @@ export class Move {
|
|
|
199
204
|
sourceCard.metadata.rank = rank;
|
|
200
205
|
}
|
|
201
206
|
|
|
207
|
+
// Update attachment paths for the moved card
|
|
208
|
+
if (sourceCard.attachments && sourceCard.attachments.length > 0) {
|
|
209
|
+
for (const attachment of sourceCard.attachments) {
|
|
210
|
+
if (attachment.path.startsWith(oldPath)) {
|
|
211
|
+
attachment.path = attachment.path.replace(oldPath, destinationPath);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
202
216
|
// Handle cache update and persistence
|
|
203
217
|
await this.project.updateCard(sourceCard);
|
|
218
|
+
|
|
219
|
+
// Update all descendant card paths in the cache to reflect the new filesystem location.
|
|
220
|
+
// This is critical: files have been moved on disk, but children's cached paths
|
|
221
|
+
// still point to the old location. Without this, operations on children
|
|
222
|
+
// (like edit or delete) would target non-existent paths, leaving orphaned files.
|
|
223
|
+
if (sourceCard.children && sourceCard.children.length > 0) {
|
|
224
|
+
for (const childKey of sourceCard.children) {
|
|
225
|
+
this.project.updateDescendantPathsAfterMove(
|
|
226
|
+
childKey,
|
|
227
|
+
oldPath,
|
|
228
|
+
destinationPath,
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
204
233
|
const updatedCard: Card = {
|
|
205
234
|
...sourceCard,
|
|
206
235
|
path: destinationPath,
|
|
@@ -238,6 +267,7 @@ export class Move {
|
|
|
238
267
|
* @param cardKey card key
|
|
239
268
|
* @param index to which position should card be ranked to
|
|
240
269
|
*/
|
|
270
|
+
@write((cardKey) => `Reorder card ${cardKey}`)
|
|
241
271
|
public async rankByIndex(cardKey: string, index: number) {
|
|
242
272
|
if (index < 0) {
|
|
243
273
|
throw new Error(`Index must be greater than 0`);
|
|
@@ -272,6 +302,7 @@ export class Move {
|
|
|
272
302
|
* @param cardKey Card to rank
|
|
273
303
|
* @param beforeCardKey Card key after which the card will be ranked
|
|
274
304
|
*/
|
|
305
|
+
@write((cardKey) => `Reorder card ${cardKey}`)
|
|
275
306
|
public async rankCard(cardKey: string, beforeCardKey: string) {
|
|
276
307
|
const card = this.project.findCard(cardKey);
|
|
277
308
|
const beforeCard = this.project.findCard(beforeCardKey);
|
|
@@ -325,6 +356,7 @@ export class Move {
|
|
|
325
356
|
* Ranks card first.
|
|
326
357
|
* @param cardKey card key
|
|
327
358
|
*/
|
|
359
|
+
@write((cardKey) => `Rank card ${cardKey} first`)
|
|
328
360
|
public async rankFirst(cardKey: string) {
|
|
329
361
|
const card = this.project.findCard(cardKey);
|
|
330
362
|
const children = sortItems(
|
|
@@ -370,6 +402,7 @@ export class Move {
|
|
|
370
402
|
* Rebalances the ranks of the children of a card.
|
|
371
403
|
* @param parentCardKey parent card key
|
|
372
404
|
*/
|
|
405
|
+
@write((parentCardKey) => `Rebalance children of ${parentCardKey}`)
|
|
373
406
|
public async rebalanceChildren(parentCardKey: string) {
|
|
374
407
|
const parentCard = this.project.findCard(parentCardKey);
|
|
375
408
|
if (!parentCard || !parentCard.children) {
|
|
@@ -384,6 +417,7 @@ export class Move {
|
|
|
384
417
|
* Rebalances the ranks of the cards in the whole project, including templates
|
|
385
418
|
* Can be used even if the ranks do not exist
|
|
386
419
|
*/
|
|
420
|
+
@write(() => 'Rebalance project')
|
|
387
421
|
public async rebalanceProject() {
|
|
388
422
|
const cards = this.project.showProjectCards();
|
|
389
423
|
|
package/src/commands/remove.ts
CHANGED
|
@@ -13,16 +13,21 @@
|
|
|
13
13
|
|
|
14
14
|
import { ActionGuard } from '../permissions/action-guard.js';
|
|
15
15
|
import { isModuleCard } from '../utils/card-utils.js';
|
|
16
|
+
import { getChildLogger } from '../utils/log-utils.js';
|
|
16
17
|
import { ModuleManager } from '../module-manager.js';
|
|
17
18
|
import type { Fetch } from './fetch.js';
|
|
18
19
|
import type { Project } from '../containers/project.js';
|
|
19
20
|
import type { RemovableResourceTypes } from '../interfaces/project-interfaces.js';
|
|
21
|
+
import { write } from '../utils/rw-lock.js';
|
|
20
22
|
|
|
21
23
|
/**
|
|
22
24
|
* Remove command.
|
|
23
25
|
*/
|
|
24
26
|
export class Remove {
|
|
25
27
|
private moduleManager: ModuleManager;
|
|
28
|
+
private get logger() {
|
|
29
|
+
return getChildLogger({ module: 'remove' });
|
|
30
|
+
}
|
|
26
31
|
/**
|
|
27
32
|
* Creates a new instance of Remove command.
|
|
28
33
|
* @param project Project instance to use
|
|
@@ -73,14 +78,30 @@ export class Remove {
|
|
|
73
78
|
await actionGuard.checkPermission('delete', cardKey);
|
|
74
79
|
}
|
|
75
80
|
|
|
76
|
-
//
|
|
81
|
+
// Collect all card keys that will be deleted (the card itself and all descendants).
|
|
82
|
+
const cardsToDelete = new Set<string>();
|
|
83
|
+
const collectDescendants = (c: typeof card) => {
|
|
84
|
+
cardsToDelete.add(c.key);
|
|
85
|
+
for (const childKey of c.children) {
|
|
86
|
+
try {
|
|
87
|
+
const childCard = this.project.findCard(childKey);
|
|
88
|
+
collectDescendants(childCard);
|
|
89
|
+
} catch {
|
|
90
|
+
this.logger.debug({ childKey }, 'Child card not found, skipping');
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
collectDescendants(card);
|
|
95
|
+
|
|
96
|
+
// If any of the cards to be deleted is a destination of a link, remove the link.
|
|
77
97
|
const allCards = this.project.cards(this.project.paths.cardRootFolder);
|
|
78
98
|
const promiseContainer: Promise<void>[] = [];
|
|
79
99
|
|
|
80
100
|
for (const item of allCards) {
|
|
101
|
+
if (cardsToDelete.has(item.key)) continue;
|
|
81
102
|
const links = item.metadata?.links ?? [];
|
|
82
103
|
for (const link of links) {
|
|
83
|
-
if (link.cardKey
|
|
104
|
+
if (cardsToDelete.has(link.cardKey)) {
|
|
84
105
|
promiseContainer.push(this.removeLink(item.key, link.cardKey));
|
|
85
106
|
}
|
|
86
107
|
}
|
|
@@ -163,6 +184,7 @@ export class Remove {
|
|
|
163
184
|
* when removing link, some of the mandatory parameters are missing, or
|
|
164
185
|
* when trying to remove unknown type
|
|
165
186
|
*/
|
|
187
|
+
@write((type, targetName) => `Remove ${type} ${targetName}`)
|
|
166
188
|
public async remove(
|
|
167
189
|
type: RemovableResourceTypes,
|
|
168
190
|
targetName: string,
|
package/src/commands/rename.ts
CHANGED
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
import { isTemplateCard } from '../utils/card-utils.js';
|
|
25
25
|
import { type Project, ResourcesFrom } from '../containers/project.js';
|
|
26
26
|
import { resourceName } from '../utils/resource-utils.js';
|
|
27
|
+
import { write } from '../utils/rw-lock.js';
|
|
27
28
|
|
|
28
29
|
const FILE_TYPES_WITH_PREFIX_REFERENCES = ['adoc', 'hbs', 'json', 'lp'];
|
|
29
30
|
|
|
@@ -186,6 +187,7 @@ export class Rename {
|
|
|
186
187
|
* @throws if trying to rename with current name
|
|
187
188
|
* @param to Card id, or template name
|
|
188
189
|
*/
|
|
190
|
+
@write((to) => `Rename project prefix to ${to}`)
|
|
189
191
|
public async rename(to: string) {
|
|
190
192
|
if (!to) {
|
|
191
193
|
throw new Error(`Input validation error: empty 'to' is not allowed`);
|
package/src/commands/show.ts
CHANGED
|
@@ -48,6 +48,7 @@ import type { ResourceName } from '../utils/resource-utils.js';
|
|
|
48
48
|
import type { ResourceMap } from '../containers/project/resource-cache.js';
|
|
49
49
|
|
|
50
50
|
import { UserPreferences } from '../utils/user-preferences.js';
|
|
51
|
+
import { read, write } from '../utils/rw-lock.js';
|
|
51
52
|
import ReportMacro from '../macros/report/index.js';
|
|
52
53
|
import TaskQueue from '../macros/task-queue.js';
|
|
53
54
|
import { evaluateMacros } from '../macros/index.js';
|
|
@@ -146,10 +147,13 @@ export class Show {
|
|
|
146
147
|
* Shows all template cards in a project.
|
|
147
148
|
* @returns all template cards in a project.
|
|
148
149
|
*/
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
150
|
+
@read
|
|
151
|
+
public async showAllTemplateCards(): Promise<
|
|
152
|
+
{
|
|
153
|
+
name: string;
|
|
154
|
+
cards: CardWithChildrenCards[];
|
|
155
|
+
}[]
|
|
156
|
+
> {
|
|
153
157
|
return this.project.resources.templates().map((template) => {
|
|
154
158
|
const cards = template.templateObject().listCards();
|
|
155
159
|
const buildCards = buildCardHierarchy(cards);
|
|
@@ -165,6 +169,7 @@ export class Show {
|
|
|
165
169
|
* Shows all attachments (either template or project attachments) from a project.
|
|
166
170
|
* @returns array of card attachments
|
|
167
171
|
*/
|
|
172
|
+
@read
|
|
168
173
|
public async showAttachments(): Promise<CardAttachment[]> {
|
|
169
174
|
const attachments = this.project.attachments();
|
|
170
175
|
const templateAttachments = await this.attachmentsFromTemplates();
|
|
@@ -178,7 +183,11 @@ export class Show {
|
|
|
178
183
|
* @param filename attachment filename
|
|
179
184
|
* @returns attachment details
|
|
180
185
|
*/
|
|
181
|
-
|
|
186
|
+
@read
|
|
187
|
+
public async showAttachment(
|
|
188
|
+
cardKey: string,
|
|
189
|
+
filename: string,
|
|
190
|
+
): Promise<attachmentPayload> {
|
|
182
191
|
if (!cardKey) {
|
|
183
192
|
throw new Error(`Mandatory parameter 'cardKey' missing`);
|
|
184
193
|
}
|
|
@@ -203,6 +212,7 @@ export class Show {
|
|
|
203
212
|
* @param waitDelay amount of time to wait for the application to open the attachment
|
|
204
213
|
* @todo: Move away from Show.
|
|
205
214
|
*/
|
|
215
|
+
@read
|
|
206
216
|
public async openAttachment(
|
|
207
217
|
cardKey: string,
|
|
208
218
|
filename: string,
|
|
@@ -259,10 +269,11 @@ export class Show {
|
|
|
259
269
|
* @param contentType Content format in which content is to be shown
|
|
260
270
|
* @returns card details
|
|
261
271
|
*/
|
|
262
|
-
|
|
272
|
+
@read
|
|
273
|
+
public async showCardDetails(
|
|
263
274
|
cardKey?: string,
|
|
264
275
|
contentType?: FileContentType,
|
|
265
|
-
): Card {
|
|
276
|
+
): Promise<Card> {
|
|
266
277
|
if (!cardKey) {
|
|
267
278
|
throw new Error(`Mandatory parameter 'cardKey' missing`);
|
|
268
279
|
}
|
|
@@ -285,6 +296,7 @@ export class Show {
|
|
|
285
296
|
* @param cardsFrom - The location from which to look for cards. Either from the project, templates or both.
|
|
286
297
|
* @returns cards list array
|
|
287
298
|
*/
|
|
299
|
+
@read
|
|
288
300
|
public async showCards(
|
|
289
301
|
cardsFrom?: CardLocation,
|
|
290
302
|
): Promise<CardListContainer[]> {
|
|
@@ -296,6 +308,7 @@ export class Show {
|
|
|
296
308
|
* @param cardKey The key of the card.
|
|
297
309
|
* @returns the content of the logic program.
|
|
298
310
|
*/
|
|
311
|
+
@read
|
|
299
312
|
public async showCardLogicProgram(cardKey: string) {
|
|
300
313
|
return this.project.calculationEngine.cardLogicProgram(cardKey);
|
|
301
314
|
}
|
|
@@ -304,6 +317,7 @@ export class Show {
|
|
|
304
317
|
* Shows all card types in a project.
|
|
305
318
|
* @returns array of card type details
|
|
306
319
|
*/
|
|
320
|
+
@read
|
|
307
321
|
public async showCardTypesWithDetails(): Promise<(CardType | undefined)[]> {
|
|
308
322
|
const container = [];
|
|
309
323
|
for (const cardType of this.project.resources.cardTypes()) {
|
|
@@ -324,6 +338,7 @@ export class Show {
|
|
|
324
338
|
* with 'showAll' true, the list consists of all modules in the hubs, even if they have already been imported
|
|
325
339
|
* Note that the two boolean options can be combined.
|
|
326
340
|
*/
|
|
341
|
+
@write()
|
|
327
342
|
public async showImportableModules(
|
|
328
343
|
showAll?: boolean,
|
|
329
344
|
showDetails?: boolean,
|
|
@@ -368,7 +383,8 @@ export class Show {
|
|
|
368
383
|
* Returns all unique labels in a project
|
|
369
384
|
* @returns labels in a list
|
|
370
385
|
*/
|
|
371
|
-
|
|
386
|
+
@read
|
|
387
|
+
public async showLabels(): Promise<string[]> {
|
|
372
388
|
const cards = flattenCardArray(
|
|
373
389
|
this.project.showProjectCards(),
|
|
374
390
|
this.project,
|
|
@@ -384,6 +400,7 @@ export class Show {
|
|
|
384
400
|
* @param resource Name of the resource.
|
|
385
401
|
* @returns the content of the logic program.
|
|
386
402
|
*/
|
|
403
|
+
@read
|
|
387
404
|
public async showLogicProgram(resource: ResourceName) {
|
|
388
405
|
return this.project.calculationEngine.resourceLogicProgram(resource);
|
|
389
406
|
}
|
|
@@ -393,6 +410,7 @@ export class Show {
|
|
|
393
410
|
* @param moduleName name of a module
|
|
394
411
|
* @returns details of a module.
|
|
395
412
|
*/
|
|
413
|
+
@read
|
|
396
414
|
public async showModule(moduleName: string): Promise<ModuleContent> {
|
|
397
415
|
const moduleDetails = await this.project.module(moduleName);
|
|
398
416
|
if (!moduleDetails) {
|
|
@@ -405,6 +423,7 @@ export class Show {
|
|
|
405
423
|
* Shows hubs of the project.
|
|
406
424
|
* @returns list of hubs.
|
|
407
425
|
*/
|
|
426
|
+
@write()
|
|
408
427
|
public async showHubs(): Promise<HubSetting[]> {
|
|
409
428
|
// Ensure module list is up to date before showing
|
|
410
429
|
await this.fetchCmd.ensureModuleListUpToDate();
|
|
@@ -415,7 +434,8 @@ export class Show {
|
|
|
415
434
|
* Returns all project cards in the project. Cards don't have content and nor metadata.
|
|
416
435
|
* @returns array of cards
|
|
417
436
|
*/
|
|
418
|
-
|
|
437
|
+
@read
|
|
438
|
+
public async showProjectCards(): Promise<Card[]> {
|
|
419
439
|
return this.project.showProjectCards();
|
|
420
440
|
}
|
|
421
441
|
|
|
@@ -423,7 +443,8 @@ export class Show {
|
|
|
423
443
|
* Shows all modules (if any) in a project.
|
|
424
444
|
* @returns all modules in a project.
|
|
425
445
|
*/
|
|
426
|
-
|
|
446
|
+
@read
|
|
447
|
+
public async showModules(): Promise<string[]> {
|
|
427
448
|
return this.project.resources.moduleNames().sort();
|
|
428
449
|
}
|
|
429
450
|
|
|
@@ -431,6 +452,7 @@ export class Show {
|
|
|
431
452
|
* Shows details of a particular project.
|
|
432
453
|
* @returns project information
|
|
433
454
|
*/
|
|
455
|
+
@read
|
|
434
456
|
public async showProject(): Promise<ProjectMetadata> {
|
|
435
457
|
return this.project.show();
|
|
436
458
|
}
|
|
@@ -445,6 +467,7 @@ export class Show {
|
|
|
445
467
|
* @returns Report results as a string
|
|
446
468
|
* @throws Error if the report does not exist
|
|
447
469
|
*/
|
|
470
|
+
@read
|
|
448
471
|
public async showReportResults(
|
|
449
472
|
reportName: string,
|
|
450
473
|
cardKey: string,
|
|
@@ -487,6 +510,7 @@ export class Show {
|
|
|
487
510
|
if (error instanceof Error) {
|
|
488
511
|
throw new Error(
|
|
489
512
|
`Failed to write report to ${outputPath}: ${error.message}`,
|
|
513
|
+
{ cause: error },
|
|
490
514
|
);
|
|
491
515
|
}
|
|
492
516
|
}
|
|
@@ -515,29 +539,31 @@ export class Show {
|
|
|
515
539
|
arg2?: boolean | ResourceType,
|
|
516
540
|
arg3?: boolean,
|
|
517
541
|
): Promise<AnyResourceContent> {
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
542
|
+
return this.project.lock.read(async () => {
|
|
543
|
+
const hasResourceType = typeof arg2 === 'string';
|
|
544
|
+
const resourceType = hasResourceType ? arg2 : null;
|
|
545
|
+
const showUse = hasResourceType ? arg3 : arg2;
|
|
546
|
+
|
|
547
|
+
const type = this.project.resources.extractType(name);
|
|
548
|
+
if (resourceType !== null && resourceType !== type) {
|
|
549
|
+
throw new Error(
|
|
550
|
+
`While fetching '${name}': Expected type '${resourceType}', but got '${type}' instead`,
|
|
551
|
+
);
|
|
552
|
+
}
|
|
553
|
+
const resource = this.project.resources.byType(name, type);
|
|
554
|
+
const [details, usage] = await Promise.all([
|
|
555
|
+
resource?.show(),
|
|
556
|
+
showUse ? resource?.usage() : [],
|
|
557
|
+
]);
|
|
558
|
+
if (showUse) {
|
|
559
|
+
return {
|
|
560
|
+
...details,
|
|
561
|
+
usedIn: [...usage],
|
|
562
|
+
};
|
|
563
|
+
} else {
|
|
564
|
+
return details;
|
|
565
|
+
}
|
|
566
|
+
});
|
|
541
567
|
}
|
|
542
568
|
|
|
543
569
|
/**
|
|
@@ -545,6 +571,7 @@ export class Show {
|
|
|
545
571
|
* @param type Name of resources to return (in plural form, e.g. 'templates')
|
|
546
572
|
* @returns sorted array of resources
|
|
547
573
|
*/
|
|
574
|
+
@read
|
|
548
575
|
public async showResources(type: string): Promise<string[]> {
|
|
549
576
|
const func = this.resourceFunctions[type];
|
|
550
577
|
if (!func) return [];
|
|
@@ -555,6 +582,7 @@ export class Show {
|
|
|
555
582
|
* Shows all templates with full details in a project.
|
|
556
583
|
* @returns all templates in a project.
|
|
557
584
|
*/
|
|
585
|
+
@read
|
|
558
586
|
public async showTemplatesWithDetails(): Promise<TemplateConfiguration[]> {
|
|
559
587
|
const templates = [];
|
|
560
588
|
for (const template of this.project.resources.templates()) {
|
|
@@ -567,7 +595,8 @@ export class Show {
|
|
|
567
595
|
* Shows all workflows with full details in a project.
|
|
568
596
|
* @returns workflows with full details
|
|
569
597
|
*/
|
|
570
|
-
|
|
598
|
+
@read
|
|
599
|
+
public async showWorkflowsWithDetails(): Promise<(Workflow | undefined)[]> {
|
|
571
600
|
const workflows = [];
|
|
572
601
|
for (const workflow of this.project.resources.workflows()) {
|
|
573
602
|
workflows.push(workflow.data);
|
|
@@ -15,6 +15,7 @@ import { ActionGuard } from '../permissions/action-guard.js';
|
|
|
15
15
|
import { CardMetadataUpdater } from '../card-metadata-updater.js';
|
|
16
16
|
import type { Project } from '../containers/project.js';
|
|
17
17
|
import type { WorkflowState } from '../interfaces/resource-interfaces.js';
|
|
18
|
+
import { write } from '../utils/rw-lock.js';
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* Handles transitions.
|
|
@@ -40,6 +41,7 @@ export class Transition {
|
|
|
40
41
|
* @param cardKey card key
|
|
41
42
|
* @param transition which transition to do
|
|
42
43
|
*/
|
|
44
|
+
@write((cardKey) => `Transition card ${cardKey}`)
|
|
43
45
|
public async cardTransition(cardKey: string, transition: WorkflowState) {
|
|
44
46
|
const card = this.project.findCard(cardKey);
|
|
45
47
|
|
package/src/commands/update.ts
CHANGED
|
@@ -21,6 +21,7 @@ import type {
|
|
|
21
21
|
} from '../resources/resource-object.js';
|
|
22
22
|
import type { Project } from '../containers/project.js';
|
|
23
23
|
import type { UpdateKey } from '../interfaces/resource-interfaces.js';
|
|
24
|
+
import { runWithDefaultCommitMessage } from '../utils/commit-context.js';
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
27
|
* Class that handles 'update' commands.
|
|
@@ -47,9 +48,13 @@ export class Update {
|
|
|
47
48
|
T extends UpdateOperations,
|
|
48
49
|
K extends string,
|
|
49
50
|
>(name: string, updateKey: UpdateKey<K>, operation: OperationFor<Type, T>) {
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
const run = () =>
|
|
52
|
+
this.project.lock.write(async () => {
|
|
53
|
+
const type = this.project.resources.extractType(name);
|
|
54
|
+
const resource = this.project.resources.byType(name, type);
|
|
55
|
+
await resource?.update(updateKey, operation);
|
|
56
|
+
});
|
|
57
|
+
return runWithDefaultCommitMessage('Apply resource operation', run);
|
|
53
58
|
}
|
|
54
59
|
|
|
55
60
|
/**
|
|
@@ -69,6 +74,7 @@ export class Update {
|
|
|
69
74
|
optionalDetail?: Type, // todo: for 'rank' it might be reasonable to accept also 'number'
|
|
70
75
|
mappingTable?: { stateMapping: Record<string, string> },
|
|
71
76
|
) {
|
|
77
|
+
// Safe to not have lock here, this is just a wrapper to applyResourceOperation
|
|
72
78
|
const op: Operation<Type> = {
|
|
73
79
|
name: operation,
|
|
74
80
|
target: '' as Type,
|
package/src/commands/validate.ts
CHANGED
|
@@ -240,7 +240,7 @@ export class Validate {
|
|
|
240
240
|
const result = await Promise.all(promises);
|
|
241
241
|
message.push(...result.flat(1));
|
|
242
242
|
} catch (error) {
|
|
243
|
-
throw new Error(errorFunction(error));
|
|
243
|
+
throw new Error(errorFunction(error), { cause: error });
|
|
244
244
|
}
|
|
245
245
|
return message;
|
|
246
246
|
}
|
|
@@ -56,6 +56,9 @@ import { Validate } from '../commands/validate.js';
|
|
|
56
56
|
import { ContentWatcher } from './project/project-content-watcher.js';
|
|
57
57
|
import { getChildLogger } from '../utils/log-utils.js';
|
|
58
58
|
import { MigrationExecutor } from '../migrations/migration-executor.js';
|
|
59
|
+
import { RWLock } from '../utils/rw-lock.js';
|
|
60
|
+
import { GitManager } from '../utils/git-manager.js';
|
|
61
|
+
import { getCommitContext } from '../utils/commit-context.js';
|
|
59
62
|
|
|
60
63
|
import type { MigrationResult } from '@cyberismo/migrations';
|
|
61
64
|
import type { Template } from './template.js';
|
|
@@ -78,13 +81,16 @@ export { ResourcesFrom };
|
|
|
78
81
|
export interface ProjectOptions {
|
|
79
82
|
autoSave?: boolean;
|
|
80
83
|
watchResourceChanges?: boolean;
|
|
84
|
+
autocommit?: boolean;
|
|
81
85
|
}
|
|
82
86
|
|
|
83
87
|
/**
|
|
84
88
|
* Represents project folder.
|
|
85
89
|
*/
|
|
86
90
|
export class Project extends CardContainer {
|
|
91
|
+
public readonly lock = new RWLock();
|
|
87
92
|
public calculationEngine: CalculationEngine;
|
|
93
|
+
private gitManager?: GitManager;
|
|
88
94
|
private logger = getChildLogger({ module: 'Project' });
|
|
89
95
|
private projectPaths: ProjectPaths;
|
|
90
96
|
private resourceHandler: ResourceHandler;
|
|
@@ -140,6 +146,28 @@ export class Project extends CardContainer {
|
|
|
140
146
|
},
|
|
141
147
|
);
|
|
142
148
|
}
|
|
149
|
+
|
|
150
|
+
if (this.options.autocommit) {
|
|
151
|
+
this.gitManager = new GitManager(path);
|
|
152
|
+
|
|
153
|
+
// Commit after successful writes
|
|
154
|
+
this.lock.onAfterWrite(async () => {
|
|
155
|
+
const context = getCommitContext();
|
|
156
|
+
await this.gitManager!.commit(
|
|
157
|
+
context.message ?? 'Autocommit',
|
|
158
|
+
context.author,
|
|
159
|
+
);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// Rollback on failed writes
|
|
163
|
+
this.lock.onWriteError(async () => {
|
|
164
|
+
await this.gitManager!.rollback();
|
|
165
|
+
// Invalidate caches after rollback since filesystem state changed
|
|
166
|
+
this.cardCache.clear();
|
|
167
|
+
await this.populateCardsCache();
|
|
168
|
+
this.resources.changed();
|
|
169
|
+
});
|
|
170
|
+
}
|
|
143
171
|
}
|
|
144
172
|
|
|
145
173
|
// Changes a card's parent in the cache and updates all relationships.
|
|
@@ -868,6 +896,15 @@ export class Project extends CardContainer {
|
|
|
868
896
|
return this.projectPaths;
|
|
869
897
|
}
|
|
870
898
|
|
|
899
|
+
/**
|
|
900
|
+
* Initialize git repo for autocommit mode. No-op if autocommit is disabled.
|
|
901
|
+
*/
|
|
902
|
+
public async initializeGit(): Promise<void> {
|
|
903
|
+
if (this.gitManager) {
|
|
904
|
+
await this.gitManager.initialize(getCommitContext().author);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
|
|
871
908
|
/**
|
|
872
909
|
* Populates the card cache, if it has not been populated.
|
|
873
910
|
*/
|
|
@@ -939,7 +976,7 @@ export class Project extends CardContainer {
|
|
|
939
976
|
await unlink(attachmentPath);
|
|
940
977
|
} catch (error) {
|
|
941
978
|
this.logger.error({ error }, 'Removing card attachment');
|
|
942
|
-
throw new Error(`Attachment not found: ${fileName}
|
|
979
|
+
throw new Error(`Attachment not found: ${fileName}`, { cause: error });
|
|
943
980
|
}
|
|
944
981
|
await this.handleAttachmentChange(cardKey, 'removed', fileName);
|
|
945
982
|
}
|
|
@@ -1134,6 +1171,43 @@ export class Project extends CardContainer {
|
|
|
1134
1171
|
}
|
|
1135
1172
|
}
|
|
1136
1173
|
|
|
1174
|
+
/**
|
|
1175
|
+
* Updates descendant card paths in the cache after a parent card has been moved.
|
|
1176
|
+
* This ensures cached paths reflect the actual filesystem locations.
|
|
1177
|
+
* @param cardKey The card whose descendants need path updates
|
|
1178
|
+
* @param oldBasePath The old base path before the move
|
|
1179
|
+
* @param newBasePath The new base path after the move
|
|
1180
|
+
*/
|
|
1181
|
+
public updateDescendantPathsAfterMove(
|
|
1182
|
+
cardKey: string,
|
|
1183
|
+
oldBasePath: string,
|
|
1184
|
+
newBasePath: string,
|
|
1185
|
+
): void {
|
|
1186
|
+
const card = this.cardCache.getCard(cardKey);
|
|
1187
|
+
if (!card) return;
|
|
1188
|
+
|
|
1189
|
+
if (card.path.startsWith(oldBasePath)) {
|
|
1190
|
+
card.path = card.path.replace(oldBasePath, newBasePath);
|
|
1191
|
+
|
|
1192
|
+
if (card.attachments && card.attachments.length > 0) {
|
|
1193
|
+
for (const attachment of card.attachments) {
|
|
1194
|
+
if (attachment.path.startsWith(oldBasePath)) {
|
|
1195
|
+
attachment.path = attachment.path.replace(oldBasePath, newBasePath);
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
this.cardCache.updateCard(card.key, card);
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
// Recursively update children
|
|
1204
|
+
if (card.children && card.children.length > 0) {
|
|
1205
|
+
for (const childKey of card.children) {
|
|
1206
|
+
this.updateDescendantPathsAfterMove(childKey, oldBasePath, newBasePath);
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1137
1211
|
/**
|
|
1138
1212
|
* Updates the entire card in the card cache and handles any path/parent changes.
|
|
1139
1213
|
* Also persists changes to content and metadata files.
|
|
@@ -399,7 +399,7 @@ export class Template extends CardContainer {
|
|
|
399
399
|
const destinationCardPath = parentCard
|
|
400
400
|
? join(this.cardFolder(parentCard.key), 'c')
|
|
401
401
|
: this.templateCardsPath;
|
|
402
|
-
let newCardKey
|
|
402
|
+
let newCardKey: string;
|
|
403
403
|
|
|
404
404
|
try {
|
|
405
405
|
// todo: to use cache instead of file access
|
|
@@ -92,6 +92,7 @@ export interface ShowCommandOptions extends BaseCommandOptions {
|
|
|
92
92
|
export interface StartCommandOptions extends BaseCommandOptions {
|
|
93
93
|
forceStart?: boolean;
|
|
94
94
|
watchResourceChanges?: boolean;
|
|
95
|
+
autocommit?: boolean;
|
|
95
96
|
}
|
|
96
97
|
|
|
97
98
|
// Options for 'transition' command
|