@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.
- 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 +420 -320
- 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/include/index.js +16 -1
- package/dist/macros/include/index.js.map +1 -1
- package/dist/macros/include/types.d.ts +15 -12
- package/dist/macros/index.js +4 -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.d.ts +6 -0
- package/dist/resources/workflow-resource.js +29 -13
- 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/csv.d.ts +8 -0
- package/dist/utils/csv.js +11 -0
- package/dist/utils/csv.js.map +1 -1
- 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.d.ts +17 -10
- package/dist/utils/json.js +27 -14
- 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 -63
- 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 +10 -10
- 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 +39 -6
- 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/include/index.ts +19 -1
- package/src/macros/include/types.ts +15 -12
- package/src/macros/index.ts +4 -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 +45 -16
- package/src/utils/card-utils.ts +1 -1
- package/src/utils/commit-context.ts +45 -0
- package/src/utils/csv.ts +12 -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 +29 -15
- package/src/utils/log-utils.ts +33 -4
- package/src/utils/report.ts +8 -74
- package/src/utils/rw-lock.ts +279 -0
- package/src/utils/user-preferences.ts +3 -0
package/src/module-manager.ts
CHANGED
|
@@ -157,6 +157,7 @@ export class ModuleManager {
|
|
|
157
157
|
if (error instanceof Error)
|
|
158
158
|
throw new Error(
|
|
159
159
|
`Failed to clone module '${module.name}': ${error.message}`,
|
|
160
|
+
{ cause: error },
|
|
160
161
|
);
|
|
161
162
|
}
|
|
162
163
|
|
|
@@ -245,7 +246,7 @@ export class ModuleManager {
|
|
|
245
246
|
module: ModuleSetting,
|
|
246
247
|
credentials?: Credentials,
|
|
247
248
|
) {
|
|
248
|
-
let moduleRoot
|
|
249
|
+
let moduleRoot: string;
|
|
249
250
|
if (this.isFileModule(module)) {
|
|
250
251
|
const urlStart = FILE_PROTOCOL.length;
|
|
251
252
|
// Remove 'file:' from location
|
|
@@ -350,7 +351,9 @@ export class ModuleManager {
|
|
|
350
351
|
});
|
|
351
352
|
}
|
|
352
353
|
} catch (error) {
|
|
353
|
-
throw new Error(`Failed to prepare temporary directory: ${error}
|
|
354
|
+
throw new Error(`Failed to prepare temporary directory: ${error}`, {
|
|
355
|
+
cause: error,
|
|
356
|
+
});
|
|
354
357
|
}
|
|
355
358
|
}
|
|
356
359
|
|
package/src/project-settings.ts
CHANGED
|
@@ -150,6 +150,7 @@ export class ProjectConfiguration implements ProjectSettings {
|
|
|
150
150
|
if (error instanceof TypeError) {
|
|
151
151
|
throw new Error(
|
|
152
152
|
`Invalid hub URL '${trimmedHub}'. Please provide a valid HTTP or HTTPS URL.`,
|
|
153
|
+
{ cause: error },
|
|
153
154
|
);
|
|
154
155
|
}
|
|
155
156
|
throw error;
|
|
@@ -195,7 +196,7 @@ export class ProjectConfiguration implements ProjectSettings {
|
|
|
195
196
|
if (this.schemaVersion < SCHEMA_VERSION) {
|
|
196
197
|
return {
|
|
197
198
|
isCompatible: false,
|
|
198
|
-
message: `Schema version mismatch: Project schema version (${this.schemaVersion}) is older than the application schema version (${SCHEMA_VERSION}). A migration is needed.`,
|
|
199
|
+
message: `Schema version mismatch: Project schema version (${this.schemaVersion}) is older than the application schema version (${SCHEMA_VERSION}). A migration is needed. Run 'cyberismo migrate' to update the project schema.`,
|
|
199
200
|
};
|
|
200
201
|
}
|
|
201
202
|
|
|
@@ -448,7 +448,7 @@ export class CardTypeResource extends FileResource<CardType> {
|
|
|
448
448
|
) {
|
|
449
449
|
const { key } = updateKey;
|
|
450
450
|
|
|
451
|
-
if (key
|
|
451
|
+
if (this.isBaseProperty(key)) {
|
|
452
452
|
await super.update(updateKey, op);
|
|
453
453
|
} else {
|
|
454
454
|
const content = structuredClone(this.content);
|
|
@@ -408,16 +408,14 @@ export class FieldTypeResource extends FileResource<FieldType> {
|
|
|
408
408
|
) {
|
|
409
409
|
const { key } = updateKey;
|
|
410
410
|
|
|
411
|
-
if (key
|
|
411
|
+
if (this.isBaseProperty(key)) {
|
|
412
412
|
await super.update(updateKey, op);
|
|
413
413
|
} else {
|
|
414
414
|
const content = structuredClone(this.content);
|
|
415
415
|
const typeChange = key === 'dataType';
|
|
416
416
|
const enumChange = key === 'enumValues';
|
|
417
417
|
const existingType = this.content.dataType;
|
|
418
|
-
if (key === '
|
|
419
|
-
content.name = super.handleScalar(op) as string;
|
|
420
|
-
} else if (key === 'dataType') {
|
|
418
|
+
if (key === 'dataType') {
|
|
421
419
|
const toType = op as ChangeOperation<DataType>;
|
|
422
420
|
if (!FieldTypeResource.fieldDataTypes().includes(toType.to)) {
|
|
423
421
|
throw new Error(
|
|
@@ -118,8 +118,10 @@ export abstract class FileResource<
|
|
|
118
118
|
content.displayName = super.handleScalar(op) as string;
|
|
119
119
|
} else if (key === 'description') {
|
|
120
120
|
content.description = super.handleScalar(op) as string;
|
|
121
|
+
} else if (key === 'category') {
|
|
122
|
+
content.category = super.handleScalar(op) as string;
|
|
121
123
|
} else {
|
|
122
|
-
throw new Error(`Unknown property '${key}' for
|
|
124
|
+
throw new Error(`Unknown property '${key}' for file resource`);
|
|
123
125
|
}
|
|
124
126
|
|
|
125
127
|
await super.postUpdate(content, updateKey, op);
|
|
@@ -62,7 +62,7 @@ export abstract class FolderResource<
|
|
|
62
62
|
* Creates a new folder type object. Base class writes the object to disk automatically.
|
|
63
63
|
* @param newContent Content for the type.
|
|
64
64
|
*/
|
|
65
|
-
|
|
65
|
+
public async create(newContent?: T) {
|
|
66
66
|
// Validate resource identifier before creating on disk
|
|
67
67
|
this.validateResourceIdentifier();
|
|
68
68
|
await super.create(newContent);
|
|
@@ -154,7 +154,10 @@ export abstract class FolderResource<
|
|
|
154
154
|
} catch (error) {
|
|
155
155
|
const message =
|
|
156
156
|
error instanceof Error ? error.message : 'Unknown error';
|
|
157
|
-
throw new Error(
|
|
157
|
+
throw new Error(
|
|
158
|
+
`Invalid JSON content for '${key}' update: ${message}`,
|
|
159
|
+
{ cause: error },
|
|
160
|
+
);
|
|
158
161
|
}
|
|
159
162
|
}
|
|
160
163
|
const contentToWrite = isJson
|
|
@@ -252,6 +255,8 @@ export abstract class FolderResource<
|
|
|
252
255
|
content.displayName = super.handleScalar(op) as string;
|
|
253
256
|
} else if (key === 'description') {
|
|
254
257
|
content.description = super.handleScalar(op) as string;
|
|
258
|
+
} else if (key === 'category') {
|
|
259
|
+
content.category = super.handleScalar(op) as string;
|
|
255
260
|
} else {
|
|
256
261
|
throw new Error(`Unknown property '${key}' for folder resource`);
|
|
257
262
|
}
|
|
@@ -22,12 +22,8 @@ import { writeFileSafe } from '../utils/file-utils.js';
|
|
|
22
22
|
import { CONTENT_FILES } from '../interfaces/folder-content-interfaces.js';
|
|
23
23
|
|
|
24
24
|
import type { Card } from '../interfaces/project-interfaces.js';
|
|
25
|
-
import type {
|
|
26
|
-
GraphModelMetadata,
|
|
27
|
-
UpdateKey,
|
|
28
|
-
} from '../interfaces/resource-interfaces.js';
|
|
25
|
+
import type { GraphModelMetadata } from '../interfaces/resource-interfaces.js';
|
|
29
26
|
import type { GraphModelContent } from '../interfaces/folder-content-interfaces.js';
|
|
30
|
-
import type { Operation } from './resource-object.js';
|
|
31
27
|
import type { Project } from '../containers/project.js';
|
|
32
28
|
import type { ResourceName } from '../utils/resource-utils.js';
|
|
33
29
|
|
|
@@ -103,26 +99,6 @@ export class GraphModelResource extends FolderResource<
|
|
|
103
99
|
return this.onNameChange(existingName);
|
|
104
100
|
}
|
|
105
101
|
|
|
106
|
-
/**
|
|
107
|
-
* Updates graph model resource.
|
|
108
|
-
* @param updateKey Key to modify
|
|
109
|
-
* @param op Operation to perform on 'key'
|
|
110
|
-
*/
|
|
111
|
-
public async update<Type, K extends string>(
|
|
112
|
-
updateKey: UpdateKey<K>,
|
|
113
|
-
op: Operation<Type>,
|
|
114
|
-
) {
|
|
115
|
-
if (updateKey.key === 'category') {
|
|
116
|
-
const content = structuredClone(this.content);
|
|
117
|
-
content.category = super.handleScalar(op) as string;
|
|
118
|
-
|
|
119
|
-
await super.postUpdate(content, updateKey, op);
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
await super.update(updateKey, op);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
102
|
/**
|
|
127
103
|
* List where this resource is used.
|
|
128
104
|
* Always returns card key references first, then calculation references.
|
|
@@ -24,11 +24,7 @@ import { sortCards } from '../utils/card-utils.js';
|
|
|
24
24
|
|
|
25
25
|
import type { Card } from '../interfaces/project-interfaces.js';
|
|
26
26
|
import type { GraphViewContent } from '../interfaces/folder-content-interfaces.js';
|
|
27
|
-
import type {
|
|
28
|
-
GraphViewMetadata,
|
|
29
|
-
UpdateKey,
|
|
30
|
-
} from '../interfaces/resource-interfaces.js';
|
|
31
|
-
import type { Operation } from './resource-object.js';
|
|
27
|
+
import type { GraphViewMetadata } from '../interfaces/resource-interfaces.js';
|
|
32
28
|
import type { Project } from '../containers/project.js';
|
|
33
29
|
import type { ResourceName } from '../utils/resource-utils.js';
|
|
34
30
|
|
|
@@ -115,26 +111,6 @@ export class GraphViewResource extends FolderResource<
|
|
|
115
111
|
return this.onNameChange(existingName);
|
|
116
112
|
}
|
|
117
113
|
|
|
118
|
-
/**
|
|
119
|
-
* Updates graph view resource.
|
|
120
|
-
* @param updateKey Key to modify
|
|
121
|
-
* @param op Operation to perform on 'key'
|
|
122
|
-
*/
|
|
123
|
-
public async update<Type, K extends string>(
|
|
124
|
-
updateKey: UpdateKey<K>,
|
|
125
|
-
op: Operation<Type>,
|
|
126
|
-
) {
|
|
127
|
-
if (updateKey.key === 'category') {
|
|
128
|
-
const content = structuredClone(this.content) as GraphViewMetadata;
|
|
129
|
-
content.category = super.handleScalar(op) as string;
|
|
130
|
-
|
|
131
|
-
await super.postUpdate(content, updateKey, op);
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
await super.update(updateKey, op);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
114
|
/**
|
|
139
115
|
* List where this resource is used.
|
|
140
116
|
* Always returns card key references first, then calculation references.
|
|
@@ -127,7 +127,7 @@ export class LinkTypeResource extends FileResource<LinkType> {
|
|
|
127
127
|
) {
|
|
128
128
|
const { key } = updateKey;
|
|
129
129
|
|
|
130
|
-
if (key
|
|
130
|
+
if (this.isBaseProperty(key)) {
|
|
131
131
|
await super.update(updateKey, op);
|
|
132
132
|
} else {
|
|
133
133
|
const content = structuredClone(this.content);
|
|
@@ -24,13 +24,9 @@ import { sortCards } from '../utils/card-utils.js';
|
|
|
24
24
|
import { Validate } from '../commands/validate.js';
|
|
25
25
|
|
|
26
26
|
import type { Card } from '../interfaces/project-interfaces.js';
|
|
27
|
-
import type { Operation } from './resource-object.js';
|
|
28
27
|
import type { Project } from '../containers/project.js';
|
|
29
28
|
import type { ReportContent } from '../interfaces/folder-content-interfaces.js';
|
|
30
|
-
import type {
|
|
31
|
-
ReportMetadata,
|
|
32
|
-
UpdateKey,
|
|
33
|
-
} from '../interfaces/resource-interfaces.js';
|
|
29
|
+
import type { ReportMetadata } from '../interfaces/resource-interfaces.js';
|
|
34
30
|
import type { ResourceName } from '../utils/resource-utils.js';
|
|
35
31
|
|
|
36
32
|
const PARAMETER_SCHEMA_ID = 'jsonSchema';
|
|
@@ -120,26 +116,6 @@ export class ReportResource extends FolderResource<
|
|
|
120
116
|
return this.onNameChange(existingName);
|
|
121
117
|
}
|
|
122
118
|
|
|
123
|
-
/**
|
|
124
|
-
* Updates report resource.
|
|
125
|
-
* @param updateKey Key to modify
|
|
126
|
-
* @param op Operation to perform on 'key'
|
|
127
|
-
*/
|
|
128
|
-
public async update<Type, K extends string>(
|
|
129
|
-
updateKey: UpdateKey<K>,
|
|
130
|
-
op: Operation<Type>,
|
|
131
|
-
) {
|
|
132
|
-
if (updateKey.key === 'category') {
|
|
133
|
-
const content = structuredClone(this.content);
|
|
134
|
-
content.category = super.handleScalar(op) as string;
|
|
135
|
-
|
|
136
|
-
await super.postUpdate(content, updateKey, op);
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
await super.update(updateKey, op);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
119
|
/**
|
|
144
120
|
* List where this resource is used.
|
|
145
121
|
* Always returns card key references first, then calculation references.
|
|
@@ -270,7 +270,7 @@ export abstract class ResourceObject<
|
|
|
270
270
|
* @param newContent Content for resource.
|
|
271
271
|
* @throws when resource already exists in the project.
|
|
272
272
|
*/
|
|
273
|
-
|
|
273
|
+
public async create(newContent?: T) {
|
|
274
274
|
this.validateResourceIdentifier();
|
|
275
275
|
|
|
276
276
|
if (this.exists()) {
|
|
@@ -357,6 +357,7 @@ export abstract class ResourceObject<
|
|
|
357
357
|
if (error instanceof Error) {
|
|
358
358
|
throw new Error(
|
|
359
359
|
`Cannot perform operation on '${arrayName}'. ${error.message}`,
|
|
360
|
+
{ cause: error },
|
|
360
361
|
);
|
|
361
362
|
}
|
|
362
363
|
}
|
|
@@ -507,6 +508,7 @@ export abstract class ResourceObject<
|
|
|
507
508
|
const errorValue = typeof op === 'object' ? toValue(op) : op;
|
|
508
509
|
throw new Error(
|
|
509
510
|
`Cannot ${op.name} '${updateKey.key}' --> '${errorValue}: ${error.message}'`,
|
|
511
|
+
{ cause: error },
|
|
510
512
|
);
|
|
511
513
|
}
|
|
512
514
|
}
|
|
@@ -576,6 +578,25 @@ export abstract class ResourceObject<
|
|
|
576
578
|
} as ChangeOperation<string>);
|
|
577
579
|
}
|
|
578
580
|
|
|
581
|
+
/**
|
|
582
|
+
* Base properties shared by all resources.
|
|
583
|
+
*/
|
|
584
|
+
private static readonly BASE_PROPERTIES = [
|
|
585
|
+
'name',
|
|
586
|
+
'displayName',
|
|
587
|
+
'description',
|
|
588
|
+
'category',
|
|
589
|
+
] as const;
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* Checks if the given key is a base property shared by all resources.
|
|
593
|
+
* @param key The property key to check
|
|
594
|
+
* @returns true if the key is a base property
|
|
595
|
+
*/
|
|
596
|
+
protected isBaseProperty(key: string): boolean {
|
|
597
|
+
return (ResourceObject.BASE_PROPERTIES as readonly string[]).includes(key);
|
|
598
|
+
}
|
|
599
|
+
|
|
579
600
|
/**
|
|
580
601
|
* Update resource; the base class makes some checks only.
|
|
581
602
|
* @template type Resource type
|
|
@@ -22,13 +22,11 @@ import { Template } from '../containers/template.js';
|
|
|
22
22
|
import { writeJsonFile } from '../utils/json.js';
|
|
23
23
|
|
|
24
24
|
import type { Card } from '../interfaces/project-interfaces.js';
|
|
25
|
-
import type { Operation } from './resource-object.js';
|
|
26
25
|
import type { Project } from '../containers/project.js';
|
|
27
26
|
import type { ResourceName } from '../utils/resource-utils.js';
|
|
28
27
|
import type {
|
|
29
28
|
TemplateConfiguration,
|
|
30
29
|
TemplateMetadata,
|
|
31
|
-
UpdateKey,
|
|
32
30
|
} from '../interfaces/resource-interfaces.js';
|
|
33
31
|
|
|
34
32
|
/**
|
|
@@ -135,27 +133,6 @@ export class TemplateResource extends FolderResource<TemplateMetadata, never> {
|
|
|
135
133
|
return this.cardContainer;
|
|
136
134
|
}
|
|
137
135
|
|
|
138
|
-
/**
|
|
139
|
-
* Updates template resource.
|
|
140
|
-
* @param updateKey Key to modify
|
|
141
|
-
* @param op Operation to perform on 'key'
|
|
142
|
-
* @throws if key is unknown.
|
|
143
|
-
*/
|
|
144
|
-
public async update<Type, K extends string>(
|
|
145
|
-
updateKey: UpdateKey<K>,
|
|
146
|
-
op: Operation<Type>,
|
|
147
|
-
) {
|
|
148
|
-
if (updateKey.key === 'category') {
|
|
149
|
-
const content = structuredClone(this.content);
|
|
150
|
-
content.category = super.handleScalar(op) as string;
|
|
151
|
-
|
|
152
|
-
await super.postUpdate(content, updateKey, op);
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
await super.update(updateKey, op);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
136
|
/**
|
|
160
137
|
* List where template is used.
|
|
161
138
|
* Always returns card key references first, then calculation references.
|
|
@@ -78,16 +78,11 @@ export class WorkflowResource extends FileResource<Workflow> {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
// Handle change of workflow state.
|
|
81
|
-
private async handleStateChange(
|
|
82
|
-
|
|
81
|
+
private async handleStateChange(
|
|
82
|
+
content: Workflow,
|
|
83
|
+
op: ChangeOperation<WorkflowState>,
|
|
84
|
+
) {
|
|
83
85
|
const stateName = this.targetName(op) as string;
|
|
84
|
-
// Check that state can be changed to
|
|
85
|
-
content.transitions = content.transitions.filter(
|
|
86
|
-
(t) => t.toState !== stateName,
|
|
87
|
-
);
|
|
88
|
-
content.transitions.forEach((t) => {
|
|
89
|
-
t.fromState = t.fromState.filter((state) => state !== stateName);
|
|
90
|
-
});
|
|
91
86
|
// validate that new state contains 'name' and 'category'
|
|
92
87
|
if (op.to.name === undefined || op.to.category === undefined) {
|
|
93
88
|
throw new Error(
|
|
@@ -95,16 +90,26 @@ export class WorkflowResource extends FileResource<Workflow> {
|
|
|
95
90
|
Updated state must have 'name' and 'category' properties.`,
|
|
96
91
|
);
|
|
97
92
|
}
|
|
98
|
-
//
|
|
93
|
+
// Rename transitions to use the new state name
|
|
99
94
|
const toStateName = op.to.name;
|
|
100
|
-
|
|
95
|
+
content.transitions.forEach((t) => {
|
|
96
|
+
if (t.toState === stateName) {
|
|
97
|
+
t.toState = toStateName;
|
|
98
|
+
}
|
|
99
|
+
t.fromState = t.fromState.map((state) =>
|
|
100
|
+
state === stateName ? toStateName : state,
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
// Update all cards that use this state.
|
|
101
104
|
await this.updateCardStates(stateName, toStateName);
|
|
102
105
|
}
|
|
103
106
|
|
|
104
107
|
// Handle removal of workflow state.
|
|
105
108
|
// State can be removed with or without replacement.
|
|
106
|
-
private async handleStateRemoval(
|
|
107
|
-
|
|
109
|
+
private async handleStateRemoval(
|
|
110
|
+
content: Workflow,
|
|
111
|
+
op: RemoveOperation<WorkflowState>,
|
|
112
|
+
) {
|
|
108
113
|
const stateName = this.targetName(op) as string;
|
|
109
114
|
|
|
110
115
|
// If there is no replacement value, remove all transitions "to" and "from" this state.
|
|
@@ -232,6 +237,28 @@ export class WorkflowResource extends FileResource<Workflow> {
|
|
|
232
237
|
return super.create(newContent);
|
|
233
238
|
}
|
|
234
239
|
|
|
240
|
+
/**
|
|
241
|
+
* Validates the content of the workflow resource.
|
|
242
|
+
* @param content Content to be validated.
|
|
243
|
+
* @throws if content is invalid.
|
|
244
|
+
*/
|
|
245
|
+
public async validate(content?: Workflow) {
|
|
246
|
+
// Base class run basic schema checks
|
|
247
|
+
await super.validate(content);
|
|
248
|
+
|
|
249
|
+
const workflowContent = content ?? this.content;
|
|
250
|
+
|
|
251
|
+
const newCardTransitions = workflowContent.transitions.filter(
|
|
252
|
+
(t) => t.fromState.includes('') || t.fromState.length === 0,
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
if (newCardTransitions.length !== 1) {
|
|
256
|
+
throw new Error(
|
|
257
|
+
`Workflow '${workflowContent.name}' must have exactly one transition from "New Card" (empty fromState), found ${newCardTransitions.length}.`,
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
235
262
|
/**
|
|
236
263
|
* Renames the object and the file.
|
|
237
264
|
* @param newName New name for the resource.
|
|
@@ -254,7 +281,7 @@ export class WorkflowResource extends FileResource<Workflow> {
|
|
|
254
281
|
) {
|
|
255
282
|
const { key } = updateKey;
|
|
256
283
|
|
|
257
|
-
if (key
|
|
284
|
+
if (this.isBaseProperty(key)) {
|
|
258
285
|
await super.update(updateKey, op);
|
|
259
286
|
} else {
|
|
260
287
|
const content = structuredClone(this.content) as Workflow;
|
|
@@ -329,11 +356,13 @@ export class WorkflowResource extends FileResource<Workflow> {
|
|
|
329
356
|
removeOp = {
|
|
330
357
|
name: 'remove',
|
|
331
358
|
target: toBeRemovedState as WorkflowState,
|
|
359
|
+
replacementValue: (op as RemoveOperation<unknown>)
|
|
360
|
+
.replacementValue as WorkflowState,
|
|
332
361
|
};
|
|
333
362
|
} else {
|
|
334
363
|
removeOp = op as RemoveOperation<WorkflowState>;
|
|
335
364
|
}
|
|
336
|
-
await this.handleStateRemoval(removeOp);
|
|
365
|
+
await this.handleStateRemoval(content, removeOp);
|
|
337
366
|
} else if (key === 'states' && op.name === 'change') {
|
|
338
367
|
// If workflow state is renamed, replace all transitions "to" and "from" the old state with new state.
|
|
339
368
|
let changeOp: ChangeOperation<WorkflowState>;
|
|
@@ -349,7 +378,7 @@ export class WorkflowResource extends FileResource<Workflow> {
|
|
|
349
378
|
} else {
|
|
350
379
|
changeOp = op as ChangeOperation<WorkflowState>;
|
|
351
380
|
}
|
|
352
|
-
await this.handleStateChange(changeOp);
|
|
381
|
+
await this.handleStateChange(content, changeOp);
|
|
353
382
|
}
|
|
354
383
|
|
|
355
384
|
await super.postUpdate(content, updateKey, op);
|
package/src/utils/card-utils.ts
CHANGED
|
@@ -95,7 +95,7 @@ export const cardPathParts = (
|
|
|
95
95
|
const cardKey = pathParts.at(pathParts.length - 1);
|
|
96
96
|
const parents = [];
|
|
97
97
|
let template = '';
|
|
98
|
-
let startIndex
|
|
98
|
+
let startIndex: number;
|
|
99
99
|
let templatesNameIndex = -1;
|
|
100
100
|
|
|
101
101
|
const cardRootIndex = pathParts.indexOf('cardRoot');
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Cyberismo
|
|
3
|
+
Copyright © Cyberismo Ltd and contributors 2026
|
|
4
|
+
|
|
5
|
+
This program is free software: you can redistribute it and/or modify it under
|
|
6
|
+
the terms of the GNU Affero General Public License version 3 as published by
|
|
7
|
+
the Free Software Foundation. This program is distributed in the hope that it
|
|
8
|
+
will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
|
9
|
+
of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
10
|
+
See the GNU Affero General Public License for more details.
|
|
11
|
+
You should have received a copy of the GNU Affero General Public
|
|
12
|
+
License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
16
|
+
|
|
17
|
+
export interface CommitContext {
|
|
18
|
+
message?: string;
|
|
19
|
+
author?: { name: string; email: string };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const context = new AsyncLocalStorage<CommitContext>();
|
|
23
|
+
|
|
24
|
+
export function runWithCommitContext<T>(
|
|
25
|
+
ctx: CommitContext,
|
|
26
|
+
fn: () => Promise<T>,
|
|
27
|
+
): Promise<T> {
|
|
28
|
+
const current = context.getStore();
|
|
29
|
+
// Merge with any existing context (e.g. author set at middleware level, message set at decorator level)
|
|
30
|
+
const merged = { ...current, ...ctx };
|
|
31
|
+
return context.run(merged, fn);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function getCommitContext(): CommitContext {
|
|
35
|
+
return context.getStore() ?? {};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function runWithDefaultCommitMessage<T>(
|
|
39
|
+
message: string,
|
|
40
|
+
fn: () => Promise<T>,
|
|
41
|
+
): Promise<T> {
|
|
42
|
+
return getCommitContext().message !== undefined
|
|
43
|
+
? fn()
|
|
44
|
+
: runWithCommitContext({ message }, fn);
|
|
45
|
+
}
|
package/src/utils/csv.ts
CHANGED
|
@@ -14,6 +14,18 @@ import { readFile } from 'node:fs/promises';
|
|
|
14
14
|
import { parse } from 'csv-parse/sync';
|
|
15
15
|
import type { CSVRowRaw } from '../interfaces/project-interfaces.js';
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Escapes a string for use as a CSV field.
|
|
19
|
+
* Escapes double quotes by doubling them. The caller is responsible for
|
|
20
|
+
* wrapping the result in quotes.
|
|
21
|
+
* @param str The string to escape
|
|
22
|
+
* @returns The escaped string (without surrounding quotes)
|
|
23
|
+
*/
|
|
24
|
+
export function escapeCsvField(str: string): string {
|
|
25
|
+
// Escape double quotes by doubling them
|
|
26
|
+
return str.replace(/"/g, '""');
|
|
27
|
+
}
|
|
28
|
+
|
|
17
29
|
/**
|
|
18
30
|
* Reads a CSV file and returns its content as an array of objects.
|
|
19
31
|
* @param file Path to the CSV file.
|
package/src/utils/file-utils.ts
CHANGED
|
@@ -36,7 +36,9 @@ export async function availableSpace(path: string): Promise<number> {
|
|
|
36
36
|
const stats = await statfs(path);
|
|
37
37
|
return stats.bavail * stats.bsize;
|
|
38
38
|
} catch (error) {
|
|
39
|
-
throw new Error(`Failed to check available disk space: ${error}
|
|
39
|
+
throw new Error(`Failed to check available disk space: ${error}`, {
|
|
40
|
+
cause: error,
|
|
41
|
+
});
|
|
40
42
|
}
|
|
41
43
|
}
|
|
42
44
|
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Cyberismo
|
|
3
|
+
Copyright © Cyberismo Ltd and contributors 2026
|
|
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.
|
|
9
|
+
See the GNU Affero 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 { simpleGit, type SimpleGit } from 'simple-git';
|
|
15
|
+
import { getChildLogger } from './log-utils.js';
|
|
16
|
+
|
|
17
|
+
export class GitManager {
|
|
18
|
+
private git: SimpleGit;
|
|
19
|
+
private logger = getChildLogger({ module: 'GitManager' });
|
|
20
|
+
|
|
21
|
+
constructor(projectPath: string) {
|
|
22
|
+
this.git = simpleGit(projectPath, {
|
|
23
|
+
config: ['user.name=Cyberismo Bot', 'user.email=bot@cyberismo.com'],
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Ensure the project is a git repo. Idempotent. */
|
|
28
|
+
async initialize(author?: { name: string; email: string }): Promise<void> {
|
|
29
|
+
const isRepo = await this.git.checkIsRepo();
|
|
30
|
+
if (!isRepo) {
|
|
31
|
+
await this.git.init();
|
|
32
|
+
// Initial commit so rollback has a baseline
|
|
33
|
+
await this.git.add('.');
|
|
34
|
+
const commitOpts: Record<string, string | null> = {
|
|
35
|
+
'--allow-empty': null,
|
|
36
|
+
};
|
|
37
|
+
if (author) {
|
|
38
|
+
commitOpts['--author'] = `${author.name} <${author.email}>`;
|
|
39
|
+
}
|
|
40
|
+
await this.git.commit('Initial commit', undefined, commitOpts);
|
|
41
|
+
this.logger.info('New repo created with baseline commit');
|
|
42
|
+
} else {
|
|
43
|
+
this.logger.debug('Repo already exists');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Commit current changes (cardRoot + .cards). */
|
|
48
|
+
async commit(
|
|
49
|
+
message: string = 'Autocommit',
|
|
50
|
+
author?: { name: string; email: string },
|
|
51
|
+
): Promise<void> {
|
|
52
|
+
// Stage only the directories we care about
|
|
53
|
+
this.logger.debug('Staging changes');
|
|
54
|
+
await this.git.add(['cardRoot', '.cards']);
|
|
55
|
+
|
|
56
|
+
// Check if there's anything to commit
|
|
57
|
+
const status = await this.git.status();
|
|
58
|
+
if (status.staged.length === 0) {
|
|
59
|
+
this.logger.debug('Nothing to commit, skipping');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
this.logger.info(
|
|
64
|
+
{ message, stagedFiles: status.staged.length },
|
|
65
|
+
'Committing changes',
|
|
66
|
+
);
|
|
67
|
+
const commitOpts: Record<string, string> = {};
|
|
68
|
+
if (author) {
|
|
69
|
+
commitOpts['--author'] = `${author.name} <${author.email}>`;
|
|
70
|
+
}
|
|
71
|
+
await this.git.commit(message, undefined, commitOpts);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Rollback: restore cardRoot and .cards to last committed state. */
|
|
75
|
+
async rollback(): Promise<void> {
|
|
76
|
+
this.logger.info('Rollback starting');
|
|
77
|
+
// Restore modified tracked files (ignore errors if paths have no tracked content)
|
|
78
|
+
try {
|
|
79
|
+
await this.git.checkout(['--', 'cardRoot', '.cards']);
|
|
80
|
+
} catch {
|
|
81
|
+
this.logger.debug('No tracked files to restore');
|
|
82
|
+
}
|
|
83
|
+
// Remove new untracked files created during the failed write
|
|
84
|
+
await this.git.clean('f', ['-d', 'cardRoot', '.cards']);
|
|
85
|
+
this.logger.info('Rollback completed');
|
|
86
|
+
}
|
|
87
|
+
}
|