@cyberismo/data-handler 0.0.20 → 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.
Files changed (177) hide show
  1. package/dist/command-handler.js +13 -24
  2. package/dist/command-handler.js.map +1 -1
  3. package/dist/command-manager.d.ts +21 -6
  4. package/dist/command-manager.js +34 -32
  5. package/dist/command-manager.js.map +1 -1
  6. package/dist/commands/calculate.js +101 -46
  7. package/dist/commands/calculate.js.map +1 -1
  8. package/dist/commands/create.js +420 -320
  9. package/dist/commands/create.js.map +1 -1
  10. package/dist/commands/edit.js +117 -68
  11. package/dist/commands/edit.js.map +1 -1
  12. package/dist/commands/export.js +301 -252
  13. package/dist/commands/export.js.map +1 -1
  14. package/dist/commands/fetch.js +205 -156
  15. package/dist/commands/fetch.js.map +1 -1
  16. package/dist/commands/import.js +189 -134
  17. package/dist/commands/import.js.map +1 -1
  18. package/dist/commands/migrate.js +91 -45
  19. package/dist/commands/migrate.js.map +1 -1
  20. package/dist/commands/move.js +347 -267
  21. package/dist/commands/move.js.map +1 -1
  22. package/dist/commands/remove.d.ts +1 -0
  23. package/dist/commands/remove.js +202 -135
  24. package/dist/commands/remove.js.map +1 -1
  25. package/dist/commands/rename.js +233 -187
  26. package/dist/commands/rename.js.map +1 -1
  27. package/dist/commands/show.d.ts +8 -8
  28. package/dist/commands/show.js +477 -372
  29. package/dist/commands/show.js.map +1 -1
  30. package/dist/commands/transition.js +119 -73
  31. package/dist/commands/transition.js.map +1 -1
  32. package/dist/commands/update.js +8 -3
  33. package/dist/commands/update.js.map +1 -1
  34. package/dist/commands/validate.js +1 -1
  35. package/dist/commands/validate.js.map +1 -1
  36. package/dist/containers/project/calculation-engine.js +0 -1
  37. package/dist/containers/project/calculation-engine.js.map +1 -1
  38. package/dist/containers/project/card-cache.js +1 -1
  39. package/dist/containers/project/card-cache.js.map +1 -1
  40. package/dist/containers/project.d.ts +16 -0
  41. package/dist/containers/project.js +59 -1
  42. package/dist/containers/project.js.map +1 -1
  43. package/dist/containers/template.js +1 -1
  44. package/dist/containers/template.js.map +1 -1
  45. package/dist/interfaces/command-options.d.ts +1 -0
  46. package/dist/interfaces/resource-interfaces.d.ts +5 -12
  47. package/dist/interfaces/resource-interfaces.js.map +1 -1
  48. package/dist/macros/base-macro.js +1 -1
  49. package/dist/macros/base-macro.js.map +1 -1
  50. package/dist/macros/graph/index.js +3 -1
  51. package/dist/macros/graph/index.js.map +1 -1
  52. package/dist/macros/include/index.js +16 -1
  53. package/dist/macros/include/index.js.map +1 -1
  54. package/dist/macros/include/types.d.ts +15 -12
  55. package/dist/macros/index.js +4 -1
  56. package/dist/macros/index.js.map +1 -1
  57. package/dist/macros/report/index.js +1 -1
  58. package/dist/macros/report/index.js.map +1 -1
  59. package/dist/module-manager.js +5 -3
  60. package/dist/module-manager.js.map +1 -1
  61. package/dist/project-settings.js +2 -2
  62. package/dist/project-settings.js.map +1 -1
  63. package/dist/resources/card-type-resource.js +1 -1
  64. package/dist/resources/card-type-resource.js.map +1 -1
  65. package/dist/resources/create-defaults.js +0 -1
  66. package/dist/resources/create-defaults.js.map +1 -1
  67. package/dist/resources/field-type-resource.js +2 -5
  68. package/dist/resources/field-type-resource.js.map +1 -1
  69. package/dist/resources/file-resource.js +4 -1
  70. package/dist/resources/file-resource.js.map +1 -1
  71. package/dist/resources/folder-resource.d.ts +1 -1
  72. package/dist/resources/folder-resource.js +4 -1
  73. package/dist/resources/folder-resource.js.map +1 -1
  74. package/dist/resources/graph-model-resource.d.ts +1 -8
  75. package/dist/resources/graph-model-resource.js +0 -14
  76. package/dist/resources/graph-model-resource.js.map +1 -1
  77. package/dist/resources/graph-view-resource.d.ts +1 -8
  78. package/dist/resources/graph-view-resource.js +0 -14
  79. package/dist/resources/graph-view-resource.js.map +1 -1
  80. package/dist/resources/link-type-resource.js +1 -1
  81. package/dist/resources/link-type-resource.js.map +1 -1
  82. package/dist/resources/report-resource.d.ts +1 -8
  83. package/dist/resources/report-resource.js +0 -14
  84. package/dist/resources/report-resource.js.map +1 -1
  85. package/dist/resources/resource-object.d.ts +11 -1
  86. package/dist/resources/resource-object.js +19 -2
  87. package/dist/resources/resource-object.js.map +1 -1
  88. package/dist/resources/template-resource.d.ts +1 -9
  89. package/dist/resources/template-resource.js +0 -15
  90. package/dist/resources/template-resource.js.map +1 -1
  91. package/dist/resources/workflow-resource.d.ts +6 -0
  92. package/dist/resources/workflow-resource.js +29 -13
  93. package/dist/resources/workflow-resource.js.map +1 -1
  94. package/dist/utils/card-utils.js +1 -1
  95. package/dist/utils/card-utils.js.map +1 -1
  96. package/dist/utils/commit-context.d.ts +23 -0
  97. package/dist/utils/commit-context.js +30 -0
  98. package/dist/utils/commit-context.js.map +1 -0
  99. package/dist/utils/csv.d.ts +8 -0
  100. package/dist/utils/csv.js +11 -0
  101. package/dist/utils/csv.js.map +1 -1
  102. package/dist/utils/file-utils.js +3 -1
  103. package/dist/utils/file-utils.js.map +1 -1
  104. package/dist/utils/git-manager.d.ts +29 -0
  105. package/dist/utils/git-manager.js +76 -0
  106. package/dist/utils/git-manager.js.map +1 -0
  107. package/dist/utils/handlebars-helpers.d.ts +22 -0
  108. package/dist/utils/handlebars-helpers.js +78 -0
  109. package/dist/utils/handlebars-helpers.js.map +1 -0
  110. package/dist/utils/json.d.ts +17 -10
  111. package/dist/utils/json.js +27 -14
  112. package/dist/utils/json.js.map +1 -1
  113. package/dist/utils/log-utils.d.ts +7 -2
  114. package/dist/utils/log-utils.js +28 -3
  115. package/dist/utils/log-utils.js.map +1 -1
  116. package/dist/utils/report.d.ts +0 -19
  117. package/dist/utils/report.js +4 -63
  118. package/dist/utils/report.js.map +1 -1
  119. package/dist/utils/rw-lock.d.ts +71 -0
  120. package/dist/utils/rw-lock.js +220 -0
  121. package/dist/utils/rw-lock.js.map +1 -0
  122. package/dist/utils/user-preferences.js +3 -3
  123. package/dist/utils/user-preferences.js.map +1 -1
  124. package/package.json +10 -10
  125. package/src/command-handler.ts +14 -22
  126. package/src/command-manager.ts +43 -37
  127. package/src/commands/calculate.ts +8 -1
  128. package/src/commands/create.ts +39 -6
  129. package/src/commands/edit.ts +3 -0
  130. package/src/commands/export.ts +8 -2
  131. package/src/commands/fetch.ts +3 -0
  132. package/src/commands/import.ts +5 -0
  133. package/src/commands/migrate.ts +2 -0
  134. package/src/commands/move.ts +34 -0
  135. package/src/commands/remove.ts +24 -2
  136. package/src/commands/rename.ts +2 -0
  137. package/src/commands/show.ts +63 -34
  138. package/src/commands/transition.ts +2 -0
  139. package/src/commands/update.ts +9 -3
  140. package/src/commands/validate.ts +1 -1
  141. package/src/containers/project/calculation-engine.ts +0 -1
  142. package/src/containers/project/card-cache.ts +1 -0
  143. package/src/containers/project.ts +75 -1
  144. package/src/containers/template.ts +1 -1
  145. package/src/interfaces/command-options.ts +1 -0
  146. package/src/interfaces/resource-interfaces.ts +5 -12
  147. package/src/macros/base-macro.ts +1 -1
  148. package/src/macros/graph/index.ts +3 -0
  149. package/src/macros/include/index.ts +19 -1
  150. package/src/macros/include/types.ts +15 -12
  151. package/src/macros/index.ts +4 -1
  152. package/src/macros/report/index.ts +1 -0
  153. package/src/module-manager.ts +5 -2
  154. package/src/project-settings.ts +2 -1
  155. package/src/resources/card-type-resource.ts +1 -1
  156. package/src/resources/create-defaults.ts +0 -1
  157. package/src/resources/field-type-resource.ts +2 -4
  158. package/src/resources/file-resource.ts +3 -1
  159. package/src/resources/folder-resource.ts +7 -2
  160. package/src/resources/graph-model-resource.ts +1 -25
  161. package/src/resources/graph-view-resource.ts +1 -25
  162. package/src/resources/link-type-resource.ts +1 -1
  163. package/src/resources/report-resource.ts +1 -25
  164. package/src/resources/resource-object.ts +22 -1
  165. package/src/resources/template-resource.ts +0 -23
  166. package/src/resources/workflow-resource.ts +45 -16
  167. package/src/utils/card-utils.ts +1 -1
  168. package/src/utils/commit-context.ts +45 -0
  169. package/src/utils/csv.ts +12 -0
  170. package/src/utils/file-utils.ts +3 -1
  171. package/src/utils/git-manager.ts +87 -0
  172. package/src/utils/handlebars-helpers.ts +95 -0
  173. package/src/utils/json.ts +29 -15
  174. package/src/utils/log-utils.ts +33 -4
  175. package/src/utils/report.ts +8 -74
  176. package/src/utils/rw-lock.ts +279 -0
  177. package/src/utils/user-preferences.ts +3 -0
@@ -21,10 +21,12 @@ import { Project } from '../containers/project.js';
21
21
  import { Validate } from './validate.js';
22
22
 
23
23
  import { EMPTY_RANK, sortItems } from '../utils/lexorank.js';
24
+ import { ROOT } from '../utils/constants.js';
24
25
  import { isModulePath } from '../utils/card-utils.js';
25
26
  import type { DataType } from '../interfaces/resource-interfaces.js';
26
27
  import type { Card, ProjectFile } from '../interfaces/project-interfaces.js';
27
28
  import { resourceName, resourceNameToString } from '../utils/resource-utils.js';
29
+ import { write } from '../utils/rw-lock.js';
28
30
  import { writeJsonFile } from '../utils/json.js';
29
31
 
30
32
  // todo: Is there a easy to way to make JSON schema into a TypeScript interface/type?
@@ -79,6 +81,10 @@ export class Create {
79
81
  * @param count How many cards to add. By default one.
80
82
  * @returns non-empty string array with ids of added cards
81
83
  */
84
+ @write(
85
+ (cardTypeName, templateName) =>
86
+ `Add cards of type ${cardTypeName} to template ${templateName}`,
87
+ )
82
88
  public async addCards(
83
89
  cardTypeName: string,
84
90
  templateName: string,
@@ -139,6 +145,7 @@ export class Create {
139
145
  * Adds a new hub location.
140
146
  * @param hubUrl URL of the hub
141
147
  */
148
+ @write((hubUrl) => `Add hub ${hubUrl}`)
142
149
  public async addHubLocation(hubUrl: string) {
143
150
  return this.project.configuration.addHub(hubUrl);
144
151
  }
@@ -149,6 +156,7 @@ export class Create {
149
156
  * @param attachment path to an attachment file or attachment name if buffer is defined
150
157
  * @param buffer (Optional) attachment buffer
151
158
  */
159
+ @write((cardKey, attachment) => `Add attachment ${attachment} to ${cardKey}`)
152
160
  public async createAttachment(
153
161
  cardKey: string,
154
162
  attachment: string,
@@ -161,7 +169,7 @@ export class Create {
161
169
  buffer || attachment,
162
170
  );
163
171
  } catch (error) {
164
- throw new Error(errorFunction(error));
172
+ throw new Error(errorFunction(error), { cause: error });
165
173
  }
166
174
  }
167
175
 
@@ -169,6 +177,7 @@ export class Create {
169
177
  * Creates a calculation resource.
170
178
  * @param calculationName name for the calculation resource
171
179
  */
180
+ @write((calculationName) => `Create calculation ${calculationName}`)
172
181
  public async createCalculation(calculationName: string) {
173
182
  return this.project.resources
174
183
  .byType(calculationName, 'calculations')
@@ -181,6 +190,7 @@ export class Create {
181
190
  * @param parentCardKey (Optional) card-key of a parent card. If missing, cards are added to the card root.
182
191
  * @returns array of card keys that were created. Cards are sorted by their parent key and rank. Template root cards are first but the order between other card groups is not guaranteed. However, the order of cards within a group is guaranteed to be ordered by rank.
183
192
  */
193
+ @write((templateName) => `Create card from template ${templateName}`)
184
194
  public async createCard(
185
195
  templateName: string,
186
196
  parentCardKey?: string,
@@ -208,11 +218,20 @@ export class Create {
208
218
 
209
219
  const createdCards = await templateObject.createCards(specificCard);
210
220
  if (createdCards.length > 0) {
211
- // Note: This assumes that parent keys will be ahead of 'a' in the sort order.
212
- const sorted = sortItems(createdCards, (item) => {
213
- return `${item.parent === 'root' ? 'a' : item.parent}${item.metadata?.rank || EMPTY_RANK}`;
214
- });
215
- return sorted;
221
+ const rootParent = specificCard?.key ?? ROOT;
222
+ const rootCards: Card[] = [];
223
+ const childCards: Card[] = [];
224
+ for (const card of createdCards) {
225
+ if (card.parent === rootParent) {
226
+ rootCards.push(card);
227
+ } else {
228
+ childCards.push(card);
229
+ }
230
+ }
231
+ return [
232
+ ...sortItems(rootCards, (item) => item.metadata?.rank || EMPTY_RANK),
233
+ ...childCards,
234
+ ];
216
235
  }
217
236
  return [];
218
237
  }
@@ -222,6 +241,7 @@ export class Create {
222
241
  * @param cardTypeName name for the card type.
223
242
  * @param workflowName workflow name to use in the card type.
224
243
  */
244
+ @write((cardTypeName) => `Create card type ${cardTypeName}`)
225
245
  public async createCardType(cardTypeName: string, workflowName: string) {
226
246
  return this.project.resources
227
247
  .byType(cardTypeName, 'cardTypes')
@@ -233,6 +253,7 @@ export class Create {
233
253
  * @param fieldTypeName name for the field type.
234
254
  * @param dataType data type for the field type
235
255
  */
256
+ @write((fieldTypeName) => `Create field type ${fieldTypeName}`)
236
257
  public async createFieldType(fieldTypeName: string, dataType: DataType) {
237
258
  return this.project.resources
238
259
  .byType(fieldTypeName, 'fieldTypes')
@@ -243,6 +264,7 @@ export class Create {
243
264
  * Creates a new graph model.
244
265
  * @param graphModelName name for the graph model.
245
266
  */
267
+ @write((graphModelName) => `Create graph model ${graphModelName}`)
246
268
  public async createGraphModel(graphModelName: string) {
247
269
  return this.project.resources
248
270
  .byType(graphModelName, 'graphModels')
@@ -253,6 +275,7 @@ export class Create {
253
275
  * Creates a new graph view.
254
276
  * @param graphViewName name for the graph view.
255
277
  */
278
+ @write((graphViewName) => `Create graph view ${graphViewName}`)
256
279
  public async createGraphView(graphViewName: string) {
257
280
  return this.project.resources.byType(graphViewName, 'graphViews').create();
258
281
  }
@@ -262,6 +285,7 @@ export class Create {
262
285
  * @param cardKey The card to which the label is added to
263
286
  * @param label The label being added
264
287
  */
288
+ @write((cardKey, label) => `Add label ${label} to ${cardKey}`)
265
289
  public async createLabel(cardKey: string, label: string) {
266
290
  if (!Validate.isValidLabelName(label)) {
267
291
  throw new Error(`Not a valid label name'`);
@@ -282,6 +306,7 @@ export class Create {
282
306
  * Creates a new link type.
283
307
  * @param linkTypeName name for the link type.
284
308
  */
309
+ @write((linkTypeName) => `Create link type ${linkTypeName}`)
285
310
  public async createLinkType(linkTypeName: string) {
286
311
  return this.project.resources.byType(linkTypeName, 'linkTypes').create();
287
312
  }
@@ -293,6 +318,10 @@ export class Create {
293
318
  * @param linkType The type of link to add
294
319
  * @param linkDescription Optional description of the link
295
320
  */
321
+ @write(
322
+ (cardKey, destinationCardKey, linkType) =>
323
+ `Create ${linkType} link from ${cardKey} to ${destinationCardKey}`,
324
+ )
296
325
  public async createLink(
297
326
  cardKey: string,
298
327
  destinationCardKey: string,
@@ -383,6 +412,7 @@ export class Create {
383
412
  projectCategory: string,
384
413
  projectDescription: string,
385
414
  ) {
415
+ // No lock required, since we are creating a new project
386
416
  projectPath = resolve(projectPath);
387
417
 
388
418
  if (!projectPath) {
@@ -472,6 +502,7 @@ export class Create {
472
502
  * Creates a report
473
503
  * @param name name of the report
474
504
  */
505
+ @write((name) => `Create report ${name}`)
475
506
  public async createReport(name: string) {
476
507
  return this.project.resources.byType(name, 'reports').createReport();
477
508
  }
@@ -481,6 +512,7 @@ export class Create {
481
512
  * @param templateName Name of the template.
482
513
  * @param templateContent JSON content for the template file.
483
514
  */
515
+ @write((templateName) => `Create template ${templateName}`)
484
516
  public async createTemplate(templateName: string, templateContent: string) {
485
517
  return this.project.resources
486
518
  .byType(templateName, 'templates')
@@ -492,6 +524,7 @@ export class Create {
492
524
  * @param workflowName workflow name
493
525
  * @param workflowContent workflow content JSON
494
526
  */
527
+ @write((workflowName) => `Create workflow ${workflowName}`)
495
528
  public async createWorkflow(workflowName: string, workflowContent: string) {
496
529
  return this.project.resources
497
530
  .byType(workflowName, 'workflows')
@@ -19,6 +19,7 @@ import { spawnSync } from 'node:child_process';
19
19
  import { ActionGuard } from '../permissions/action-guard.js';
20
20
  import { Project } from '../containers/project.js';
21
21
  import { UserPreferences } from '../utils/user-preferences.js';
22
+ import { write } from '../utils/rw-lock.js';
22
23
 
23
24
  import type { MetadataContent } from '../interfaces/project-interfaces.js';
24
25
 
@@ -76,6 +77,7 @@ export class Edit {
76
77
  * @param cardKey The card to update.
77
78
  * @param changedContent New content for the card.
78
79
  */
80
+ @write((cardKey) => `Edit content of ${cardKey}`)
79
81
  public async editCardContent(cardKey: string, changedContent: string) {
80
82
  if (this.project.hasTemplateCard(cardKey)) {
81
83
  return this.project.updateCardContent(cardKey, changedContent);
@@ -93,6 +95,7 @@ export class Edit {
93
95
  * @param changedKey Which metadata property was changed
94
96
  * @param newValue New value for the metadata property
95
97
  */
98
+ @write((cardKey) => `Edit metadata of ${cardKey}`)
96
99
  public async editCardMetadata(
97
100
  cardKey: string,
98
101
  changedKey: string,
@@ -31,6 +31,7 @@ import { generateReportContent } from '../utils/report.js';
31
31
  import { getStaticDirectoryPath, pdfReport } from '@cyberismo/assets';
32
32
  import { Project } from '../containers/project.js';
33
33
  import type { QueryResult } from '../types/queries.js';
34
+ import { read } from '../utils/rw-lock.js';
34
35
  import type { Show } from './show.js';
35
36
  import { sortItems } from '../utils/lexorank.js';
36
37
 
@@ -246,7 +247,10 @@ export class Export {
246
247
  children: [],
247
248
  attachments: [],
248
249
  };
249
- const cardDetailsResponse = this.showCmd.showCardDetails(card.key, 'adoc');
250
+ const cardDetailsResponse = await this.showCmd.showCardDetails(
251
+ card.key,
252
+ 'adoc',
253
+ );
250
254
  let asciiDocContent = '';
251
255
  const project = this.project;
252
256
  try {
@@ -283,6 +287,7 @@ export class Export {
283
287
  * @param options Export options.
284
288
  * @returns status message
285
289
  */
290
+ @read
286
291
  public async exportPdf(
287
292
  destination: string,
288
293
  options: ExportPdfOptions,
@@ -320,6 +325,7 @@ export class Export {
320
325
  * @param cardKey If not exporting the whole card tree, card key of parent card.
321
326
  * @returns status message
322
327
  */
328
+ @read
323
329
  public async exportToADoc(
324
330
  destination: string,
325
331
  cardKey?: string,
@@ -365,7 +371,7 @@ export class Export {
365
371
  destination,
366
372
  Project.cardContentFile,
367
373
  );
368
- let message = '';
374
+ let message: string;
369
375
  try {
370
376
  await truncate(resultDocumentPath, 0);
371
377
  message = `Using existing output file '${resultDocumentPath}'`;
@@ -17,6 +17,7 @@ import { resolve, sep } from 'node:path';
17
17
  import { getChildLogger } from '../utils/log-utils.js';
18
18
  import { readJsonFile, writeJsonFile } from '../utils/json.js';
19
19
  import { validateJson } from '../utils/validate.js';
20
+ import { write } from '../utils/rw-lock.js';
20
21
 
21
22
  import type { ModuleSetting } from '../interfaces/project-interfaces.js';
22
23
  import type { Project } from '../containers/project.js';
@@ -203,6 +204,7 @@ export class Fetch {
203
204
  /**
204
205
  * Ensures the module list is up to date by fetching if needed.
205
206
  */
207
+ @write()
206
208
  public async ensureModuleListUpToDate() {
207
209
  await this.fetchHubs();
208
210
  }
@@ -211,6 +213,7 @@ export class Fetch {
211
213
  * Fetches modules from modules hub(s) and writes them to a file.
212
214
  * Only fetches if the remote version is newer than the local version.
213
215
  */
216
+ @write(() => 'Fetch hubs')
214
217
  public async fetchHubs() {
215
218
  const needsFetch = await this.fetchModuleList();
216
219
  if (!needsFetch) {
@@ -14,6 +14,7 @@
14
14
  import { ModuleManager } from '../module-manager.js';
15
15
  import { readCsvFile } from '../utils/csv.js';
16
16
  import { Validate } from './validate.js';
17
+ import { write } from '../utils/rw-lock.js';
17
18
 
18
19
  import type { Create } from './create.js';
19
20
  import type {
@@ -48,6 +49,7 @@ export class Import {
48
49
  * @param parentCardKey the cards in the csv file will be created under this card
49
50
  * @returns card keys of the imported cards
50
51
  */
52
+ @write((csvFilePath) => `Import cards from CSV ${csvFilePath}`)
51
53
  public async importCsv(
52
54
  csvFilePath: string,
53
55
  parentCardKey?: string,
@@ -137,6 +139,7 @@ export class Import {
137
139
  * private: If true, uses credentials to clone the repository
138
140
  * @param skipMigrationLog If true, skip logging to migration log (used during project creation)
139
141
  */
142
+ @write((source) => `Import module ${source}`)
140
143
  public async importModule(
141
144
  source: string,
142
145
  destination?: string,
@@ -195,6 +198,7 @@ export class Import {
195
198
  * @param credentials Optional credentials for a private module.
196
199
  * @throws if module is not part of the project
197
200
  */
201
+ @write((moduleName) => `Update module ${moduleName}`)
198
202
  public async updateModule(moduleName: string, credentials?: Credentials) {
199
203
  // Ensure module list is up to date before updating
200
204
  await this.fetchCmd.ensureModuleListUpToDate();
@@ -212,6 +216,7 @@ export class Import {
212
216
  * Updates all imported modules.
213
217
  * @param credentials Optional credentials for private modules.
214
218
  */
219
+ @write(() => 'Update all modules')
215
220
  public async updateAllModules(credentials?: Credentials) {
216
221
  // Ensure module list is up to date before updating all modules
217
222
  await this.fetchCmd.ensureModuleListUpToDate();
@@ -14,6 +14,7 @@
14
14
  import { SCHEMA_VERSION } from '@cyberismo/assets';
15
15
  import type { MigrationResult } from '@cyberismo/migrations';
16
16
  import type { Project } from '../containers/project.js';
17
+ import { write } from '../utils/rw-lock.js';
17
18
 
18
19
  /**
19
20
  * Command that handles schema migration operations.
@@ -32,6 +33,7 @@ export class Migrate {
32
33
  * @param timeoutMilliSeconds Optional timeout in milliseconds (defaults to 2 minutes)
33
34
  * @returns Migration result
34
35
  */
36
+ @write(() => 'Migrate project')
35
37
  public async migrate(
36
38
  toVersion?: number,
37
39
  backupDir?: string,
@@ -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
 
@@ -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
- // If card is destination of a link, remove the link.
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 === 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,
@@ -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`);