@cyberismo/data-handler 0.0.18 → 0.0.20
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.d.ts +2 -0
- package/dist/command-handler.js +31 -3
- package/dist/command-handler.js.map +1 -1
- package/dist/command-manager.d.ts +2 -0
- package/dist/command-manager.js +3 -0
- package/dist/command-manager.js.map +1 -1
- package/dist/commands/create.d.ts +3 -1
- package/dist/commands/create.js +10 -1
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/migrate.d.ts +33 -0
- package/dist/commands/migrate.js +66 -0
- package/dist/commands/migrate.js.map +1 -0
- package/dist/containers/project/card-cache.js +13 -1
- package/dist/containers/project/card-cache.js.map +1 -1
- package/dist/containers/project/project-paths.d.ts +1 -0
- package/dist/containers/project/project-paths.js +5 -2
- package/dist/containers/project/project-paths.js.map +1 -1
- package/dist/containers/project.d.ts +10 -0
- package/dist/containers/project.js +39 -2
- package/dist/containers/project.js.map +1 -1
- package/dist/containers/template.js +2 -0
- package/dist/containers/template.js.map +1 -1
- package/dist/interfaces/command-options.d.ts +6 -1
- package/dist/interfaces/project-interfaces.d.ts +4 -0
- package/dist/interfaces/project-interfaces.js.map +1 -1
- package/dist/macros/include/index.js +3 -1
- package/dist/macros/include/index.js.map +1 -1
- package/dist/macros/include/types.d.ts +6 -0
- package/dist/migrations/index.d.ts +14 -0
- package/dist/migrations/index.js +14 -0
- package/dist/migrations/index.js.map +1 -0
- package/dist/migrations/migration-executor.d.ts +79 -0
- package/dist/migrations/migration-executor.js +312 -0
- package/dist/migrations/migration-executor.js.map +1 -0
- package/dist/migrations/migration-worker.d.ts +13 -0
- package/dist/migrations/migration-worker.js +156 -0
- package/dist/migrations/migration-worker.js.map +1 -0
- package/dist/migrations/worker-executor.d.ts +24 -0
- package/dist/migrations/worker-executor.js +157 -0
- package/dist/migrations/worker-executor.js.map +1 -0
- package/dist/project-settings.d.ts +2 -0
- package/dist/project-settings.js +7 -0
- package/dist/project-settings.js.map +1 -1
- package/dist/resources/calculation-resource.d.ts +9 -0
- package/dist/resources/calculation-resource.js +13 -2
- package/dist/resources/calculation-resource.js.map +1 -1
- package/dist/resources/card-type-resource.d.ts +7 -2
- package/dist/resources/card-type-resource.js +13 -13
- package/dist/resources/card-type-resource.js.map +1 -1
- package/dist/resources/create-defaults.js +1 -1
- package/dist/resources/create-defaults.js.map +1 -1
- package/dist/resources/field-type-resource.d.ts +5 -0
- package/dist/resources/field-type-resource.js +23 -10
- package/dist/resources/field-type-resource.js.map +1 -1
- package/dist/resources/file-resource.d.ts +13 -1
- package/dist/resources/file-resource.js +17 -8
- package/dist/resources/file-resource.js.map +1 -1
- package/dist/resources/folder-resource.js +20 -8
- package/dist/resources/folder-resource.js.map +1 -1
- package/dist/resources/graph-model-resource.d.ts +5 -0
- package/dist/resources/graph-model-resource.js +6 -0
- package/dist/resources/graph-model-resource.js.map +1 -1
- package/dist/resources/graph-view-resource.d.ts +5 -0
- package/dist/resources/graph-view-resource.js +6 -0
- package/dist/resources/graph-view-resource.js.map +1 -1
- package/dist/resources/link-type-resource.d.ts +6 -0
- package/dist/resources/link-type-resource.js +26 -0
- package/dist/resources/link-type-resource.js.map +1 -1
- package/dist/resources/report-resource.d.ts +6 -1
- package/dist/resources/report-resource.js +7 -1
- package/dist/resources/report-resource.js.map +1 -1
- package/dist/resources/resource-object.d.ts +22 -7
- package/dist/resources/resource-object.js +44 -15
- package/dist/resources/resource-object.js.map +1 -1
- package/dist/resources/template-resource.d.ts +5 -1
- package/dist/resources/template-resource.js +6 -1
- package/dist/resources/template-resource.js.map +1 -1
- package/dist/resources/workflow-resource.d.ts +6 -2
- package/dist/resources/workflow-resource.js +11 -6
- package/dist/resources/workflow-resource.js.map +1 -1
- package/dist/svg/percentage.js +7 -3
- package/dist/svg/percentage.js.map +1 -1
- package/dist/svg/scoreCard.js +10 -4
- package/dist/svg/scoreCard.js.map +1 -1
- package/dist/types/queries.d.ts +1 -1
- package/dist/utils/card-utils.d.ts +1 -1
- package/dist/utils/common-utils.d.ts +8 -0
- package/dist/utils/common-utils.js +14 -0
- package/dist/utils/common-utils.js.map +1 -1
- package/dist/utils/file-utils.d.ts +15 -3
- package/dist/utils/file-utils.js +48 -9
- package/dist/utils/file-utils.js.map +1 -1
- package/dist/utils/json.js +2 -2
- package/dist/utils/json.js.map +1 -1
- package/dist/utils/resource-utils.js +1 -0
- package/dist/utils/resource-utils.js.map +1 -1
- package/package.json +7 -5
- package/src/command-handler.ts +42 -2
- package/src/command-manager.ts +3 -0
- package/src/commands/create.ts +11 -0
- package/src/commands/migrate.ts +88 -0
- package/src/containers/project/card-cache.ts +18 -1
- package/src/containers/project/project-paths.ts +6 -2
- package/src/containers/project.ts +66 -1
- package/src/containers/template.ts +5 -0
- package/src/interfaces/command-options.ts +8 -0
- package/src/interfaces/project-interfaces.ts +4 -0
- package/src/macros/include/index.ts +3 -1
- package/src/macros/include/types.ts +6 -0
- package/src/migrations/index.ts +20 -0
- package/src/migrations/migration-executor.ts +478 -0
- package/src/migrations/migration-worker.ts +190 -0
- package/src/migrations/worker-executor.ts +185 -0
- package/src/project-settings.ts +7 -0
- package/src/resources/calculation-resource.ts +13 -2
- package/src/resources/card-type-resource.ts +19 -14
- package/src/resources/create-defaults.ts +1 -1
- package/src/resources/field-type-resource.ts +31 -13
- package/src/resources/file-resource.ts +25 -8
- package/src/resources/folder-resource.ts +21 -7
- package/src/resources/graph-model-resource.ts +6 -0
- package/src/resources/graph-view-resource.ts +6 -0
- package/src/resources/link-type-resource.ts +34 -0
- package/src/resources/report-resource.ts +7 -1
- package/src/resources/resource-object.ts +57 -18
- package/src/resources/template-resource.ts +6 -1
- package/src/resources/workflow-resource.ts +17 -7
- package/src/svg/percentage.ts +7 -3
- package/src/svg/scoreCard.ts +10 -4
- package/src/types/queries.ts +1 -1
- package/src/utils/common-utils.ts +15 -0
- package/src/utils/file-utils.ts +56 -12
- package/src/utils/json.ts +2 -6
- package/src/utils/resource-utils.ts +1 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Cyberismo
|
|
3
|
+
Copyright © Cyberismo Ltd and contributors 2025
|
|
4
|
+
This program is free software: you can redistribute it and/or modify it under
|
|
5
|
+
the terms of the GNU Affero General Public License version 3 as published by
|
|
6
|
+
the Free Software Foundation.
|
|
7
|
+
This program is distributed in the hope that it will be useful, but WITHOUT
|
|
8
|
+
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
9
|
+
FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
|
10
|
+
details. You should have received a copy of the GNU Affero General Public
|
|
11
|
+
License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { dirname, join } from 'node:path';
|
|
15
|
+
import { fileURLToPath } from 'node:url';
|
|
16
|
+
import { Worker } from 'node:worker_threads';
|
|
17
|
+
|
|
18
|
+
import { getChildLogger } from '../utils/log-utils.js';
|
|
19
|
+
import type {
|
|
20
|
+
MigrationContext,
|
|
21
|
+
MigrationStepResult,
|
|
22
|
+
} from '@cyberismo/migrations';
|
|
23
|
+
import type { WorkerMessage, WorkerResponse } from './migration-executor.js';
|
|
24
|
+
|
|
25
|
+
const CANCEL_PERIOD_MS = 100;
|
|
26
|
+
const logger = getChildLogger({ module: 'WorkerExecutor' });
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Execute a migration step in a separate worker thread.
|
|
30
|
+
* The worker loads the migration program dynamically and executes the specified step.
|
|
31
|
+
*
|
|
32
|
+
* @param migrationPath Absolute path to the migration's 'index.js' file
|
|
33
|
+
* @param stepName The migration step to execute
|
|
34
|
+
* @param context Migration context
|
|
35
|
+
* @param timeoutMilliSeconds Timeout in milliseconds to wait for the step to complete
|
|
36
|
+
* @returns Migration step result
|
|
37
|
+
*/
|
|
38
|
+
export async function executeStep(
|
|
39
|
+
migrationPath: string,
|
|
40
|
+
stepName: string,
|
|
41
|
+
context: MigrationContext,
|
|
42
|
+
timeoutMilliSeconds: number,
|
|
43
|
+
): Promise<MigrationStepResult> {
|
|
44
|
+
// Always uses the compiled .js version from the dist directory.
|
|
45
|
+
function _workerPath() {
|
|
46
|
+
const currentFilePath = fileURLToPath(import.meta.url);
|
|
47
|
+
const currentDir = dirname(currentFilePath);
|
|
48
|
+
const srcMigrationsSegment = join('src', 'migrations');
|
|
49
|
+
const distMigrationsSegment = join('dist', 'migrations');
|
|
50
|
+
const distDir = currentDir.replace(
|
|
51
|
+
srcMigrationsSegment,
|
|
52
|
+
distMigrationsSegment,
|
|
53
|
+
);
|
|
54
|
+
return join(distDir, 'migration-worker.js');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return new Promise((resolve, reject) => {
|
|
58
|
+
const worker = new Worker(_workerPath(), {
|
|
59
|
+
execArgv: process.execArgv,
|
|
60
|
+
});
|
|
61
|
+
let timeoutId: NodeJS.Timeout | undefined;
|
|
62
|
+
let isResolved = false;
|
|
63
|
+
|
|
64
|
+
const cleanup = () => {
|
|
65
|
+
if (timeoutId) {
|
|
66
|
+
clearTimeout(timeoutId);
|
|
67
|
+
timeoutId = undefined;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const terminate = async (sendCancel: boolean = false): Promise<void> => {
|
|
72
|
+
if (sendCancel) {
|
|
73
|
+
try {
|
|
74
|
+
const cancelMessage: WorkerMessage = { type: 'cancel' };
|
|
75
|
+
worker.postMessage(cancelMessage);
|
|
76
|
+
await new Promise((_resolve) =>
|
|
77
|
+
setTimeout(_resolve, CANCEL_PERIOD_MS),
|
|
78
|
+
);
|
|
79
|
+
} catch {
|
|
80
|
+
// Ignore errors when sending cancel message
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
await worker.terminate();
|
|
86
|
+
} catch (error) {
|
|
87
|
+
logger.debug({ error }, 'Error terminating worker');
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
timeoutId = setTimeout(() => {
|
|
92
|
+
if (!isResolved) {
|
|
93
|
+
isResolved = true;
|
|
94
|
+
cleanup();
|
|
95
|
+
|
|
96
|
+
void (async () => {
|
|
97
|
+
await terminate(true);
|
|
98
|
+
resolve({
|
|
99
|
+
success: false,
|
|
100
|
+
error: new Error(`Migration step '${stepName}' timeout`),
|
|
101
|
+
});
|
|
102
|
+
})();
|
|
103
|
+
}
|
|
104
|
+
}, timeoutMilliSeconds);
|
|
105
|
+
|
|
106
|
+
worker.on('message', (response: WorkerResponse) => {
|
|
107
|
+
if (isResolved) return;
|
|
108
|
+
|
|
109
|
+
isResolved = true;
|
|
110
|
+
cleanup();
|
|
111
|
+
|
|
112
|
+
void (async () => {
|
|
113
|
+
if (response.type === 'error') {
|
|
114
|
+
await terminate(false);
|
|
115
|
+
resolve({
|
|
116
|
+
success: false,
|
|
117
|
+
error: new Error(response.error || 'Unknown worker error'),
|
|
118
|
+
});
|
|
119
|
+
} else if (response.type === 'result' && response.result) {
|
|
120
|
+
await terminate(false);
|
|
121
|
+
resolve(response.result);
|
|
122
|
+
} else {
|
|
123
|
+
await terminate(false);
|
|
124
|
+
resolve({
|
|
125
|
+
success: false,
|
|
126
|
+
error: new Error('Invalid worker response'),
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
})();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
worker.on('error', (error) => {
|
|
133
|
+
if (isResolved) return;
|
|
134
|
+
|
|
135
|
+
isResolved = true;
|
|
136
|
+
cleanup();
|
|
137
|
+
|
|
138
|
+
void (async () => {
|
|
139
|
+
await terminate(false);
|
|
140
|
+
resolve({
|
|
141
|
+
success: false,
|
|
142
|
+
error,
|
|
143
|
+
});
|
|
144
|
+
})();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
worker.on('exit', (code) => {
|
|
148
|
+
if (isResolved) return;
|
|
149
|
+
|
|
150
|
+
isResolved = true;
|
|
151
|
+
cleanup();
|
|
152
|
+
|
|
153
|
+
if (code !== 0) {
|
|
154
|
+
resolve({
|
|
155
|
+
success: false,
|
|
156
|
+
error: new Error(`Worker exited with code ${code}`),
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const migrationContext: MigrationContext = {
|
|
162
|
+
cardRootPath: context.cardRootPath,
|
|
163
|
+
cardsConfigPath: context.cardsConfigPath,
|
|
164
|
+
fromVersion: context.fromVersion,
|
|
165
|
+
toVersion: context.toVersion,
|
|
166
|
+
backupDir: context.backupDir,
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const executeMessage: WorkerMessage = {
|
|
170
|
+
type: 'execute',
|
|
171
|
+
migrationPath,
|
|
172
|
+
stepName,
|
|
173
|
+
context: migrationContext,
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
worker.postMessage(executeMessage);
|
|
178
|
+
} catch (error) {
|
|
179
|
+
isResolved = true;
|
|
180
|
+
cleanup();
|
|
181
|
+
void terminate(false);
|
|
182
|
+
reject(error);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
}
|
package/src/project-settings.ts
CHANGED
|
@@ -35,6 +35,8 @@ export class ProjectConfiguration implements ProjectSettings {
|
|
|
35
35
|
schemaVersion?: number;
|
|
36
36
|
name: string;
|
|
37
37
|
cardKeyPrefix: string;
|
|
38
|
+
category?: string;
|
|
39
|
+
description: string;
|
|
38
40
|
modules: ModuleSetting[];
|
|
39
41
|
hubs: HubSetting[];
|
|
40
42
|
private logger = getChildLogger({ module: 'Project' });
|
|
@@ -45,6 +47,7 @@ export class ProjectConfiguration implements ProjectSettings {
|
|
|
45
47
|
this.name = '';
|
|
46
48
|
this.settingPath = path;
|
|
47
49
|
this.cardKeyPrefix = '';
|
|
50
|
+
this.description = '';
|
|
48
51
|
this.modules = [];
|
|
49
52
|
this.hubs = [];
|
|
50
53
|
this.autoSave = autoSave;
|
|
@@ -79,6 +82,8 @@ export class ProjectConfiguration implements ProjectSettings {
|
|
|
79
82
|
this.schemaVersion = settings.schemaVersion;
|
|
80
83
|
this.cardKeyPrefix = settings.cardKeyPrefix;
|
|
81
84
|
this.name = settings.name;
|
|
85
|
+
this.category = settings.category;
|
|
86
|
+
this.description = settings.description || '';
|
|
82
87
|
this.modules = settings.modules || [];
|
|
83
88
|
this.hubs = settings.hubs || [];
|
|
84
89
|
} else {
|
|
@@ -106,6 +111,8 @@ export class ProjectConfiguration implements ProjectSettings {
|
|
|
106
111
|
schemaVersion: this.schemaVersion,
|
|
107
112
|
cardKeyPrefix: this.cardKeyPrefix,
|
|
108
113
|
name: this.name,
|
|
114
|
+
category: this.category,
|
|
115
|
+
description: this.description,
|
|
109
116
|
modules: this.modules,
|
|
110
117
|
hubs: this.hubs,
|
|
111
118
|
};
|
|
@@ -34,6 +34,11 @@ export class CalculationResource extends FolderResource<
|
|
|
34
34
|
CalculationMetadata,
|
|
35
35
|
CalculationContent
|
|
36
36
|
> {
|
|
37
|
+
/**
|
|
38
|
+
* Creates instance of CalculationResource
|
|
39
|
+
* @param project Project to use
|
|
40
|
+
* @param name Resource name
|
|
41
|
+
*/
|
|
37
42
|
constructor(project: Project, name: ResourceName) {
|
|
38
43
|
super(project, name, 'calculations');
|
|
39
44
|
|
|
@@ -41,9 +46,15 @@ export class CalculationResource extends FolderResource<
|
|
|
41
46
|
this.contentSchema = super.contentSchemaContent(this.contentSchemaId);
|
|
42
47
|
}
|
|
43
48
|
|
|
44
|
-
|
|
49
|
+
/**
|
|
50
|
+
* When resource name changes
|
|
51
|
+
* @param existingName Current resource name
|
|
52
|
+
*/
|
|
45
53
|
protected async onNameChange(existingName: string) {
|
|
46
|
-
await
|
|
54
|
+
await Promise.all([
|
|
55
|
+
super.updateCalculations(existingName, this.content.name),
|
|
56
|
+
super.updateCardContentReferences(existingName, this.content.name),
|
|
57
|
+
]);
|
|
47
58
|
await this.write();
|
|
48
59
|
}
|
|
49
60
|
|
|
@@ -17,6 +17,7 @@ import { FileResource } from './file-resource.js';
|
|
|
17
17
|
import { resourceName, resourceNameToString } from '../utils/resource-utils.js';
|
|
18
18
|
import { ResourcesFrom } from '../containers/project.js';
|
|
19
19
|
import { sortCards } from '../utils/card-utils.js';
|
|
20
|
+
import { removeValue } from '../utils/common-utils.js';
|
|
20
21
|
import { Validate } from '../commands/validate.js';
|
|
21
22
|
|
|
22
23
|
import type {
|
|
@@ -39,6 +40,11 @@ import type { ResourceName } from '../utils/resource-utils.js';
|
|
|
39
40
|
* Card type resource class.
|
|
40
41
|
*/
|
|
41
42
|
export class CardTypeResource extends FileResource<CardType> {
|
|
43
|
+
/**
|
|
44
|
+
* Creates instance of CardTypeResource
|
|
45
|
+
* @param project Project to use
|
|
46
|
+
* @param name Resource name
|
|
47
|
+
*/
|
|
42
48
|
constructor(project: Project, name: ResourceName) {
|
|
43
49
|
super(project, name, 'cardTypes');
|
|
44
50
|
|
|
@@ -69,7 +75,10 @@ export class CardTypeResource extends FileResource<CardType> {
|
|
|
69
75
|
if (op && op.name === 'rank') return;
|
|
70
76
|
|
|
71
77
|
// Collect both project cards and template cards.
|
|
72
|
-
const cards = await this.collectCards(
|
|
78
|
+
const cards = await this.collectCards(
|
|
79
|
+
this.content.name,
|
|
80
|
+
(card, cardTypeName) => card.metadata?.cardType === cardTypeName,
|
|
81
|
+
);
|
|
73
82
|
|
|
74
83
|
if (op && op.name === 'change') {
|
|
75
84
|
const from = (op as ChangeOperation<string>).target;
|
|
@@ -119,7 +128,10 @@ export class CardTypeResource extends FileResource<CardType> {
|
|
|
119
128
|
op: ChangeOperation<Type>,
|
|
120
129
|
) {
|
|
121
130
|
await this.verifyStateMapping(stateMapping, op);
|
|
122
|
-
const cards = await this.collectCards(
|
|
131
|
+
const cards = await this.collectCards(
|
|
132
|
+
this.content.name,
|
|
133
|
+
(card, cardTypeName) => card.metadata?.cardType === cardTypeName,
|
|
134
|
+
);
|
|
123
135
|
|
|
124
136
|
const unmappedStates: string[] = [];
|
|
125
137
|
|
|
@@ -179,15 +191,6 @@ export class CardTypeResource extends FileResource<CardType> {
|
|
|
179
191
|
return references;
|
|
180
192
|
}
|
|
181
193
|
|
|
182
|
-
// Remove value from array.
|
|
183
|
-
// todo: make it as generic and move to utils
|
|
184
|
-
private removeValue(array: string[], value: string) {
|
|
185
|
-
const index = array.findIndex((element) => element === value);
|
|
186
|
-
if (index !== -1) {
|
|
187
|
-
array.splice(index, 1);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
194
|
// If value from 'customFields' is removed, remove it also from 'optionallyVisible' and 'alwaysVisible' arrays.
|
|
192
195
|
private removeValueFromOtherArrays<Type>(
|
|
193
196
|
op: Operation<Type>,
|
|
@@ -201,8 +204,8 @@ export class CardTypeResource extends FileResource<CardType> {
|
|
|
201
204
|
field = { name: target['name' as keyof Type] };
|
|
202
205
|
}
|
|
203
206
|
const fieldName = (field ? field.name : target) as string;
|
|
204
|
-
|
|
205
|
-
|
|
207
|
+
removeValue(content.alwaysVisibleFields, fieldName);
|
|
208
|
+
removeValue(content.optionallyVisibleFields, fieldName);
|
|
206
209
|
}
|
|
207
210
|
|
|
208
211
|
// Sets content container values to be either '[]' or with proper values.
|
|
@@ -385,6 +388,7 @@ export class CardTypeResource extends FileResource<CardType> {
|
|
|
385
388
|
await Promise.all([
|
|
386
389
|
super.updateHandleBars(existingName, this.content.name),
|
|
387
390
|
super.updateCalculations(existingName, this.content.name),
|
|
391
|
+
super.updateCardContentReferences(existingName, this.content.name),
|
|
388
392
|
this.updateLinkTypes(existingName),
|
|
389
393
|
]);
|
|
390
394
|
|
|
@@ -394,7 +398,8 @@ export class CardTypeResource extends FileResource<CardType> {
|
|
|
394
398
|
/**
|
|
395
399
|
* Creates a new card type object. Base class writes the object to disk automatically.
|
|
396
400
|
* @param workflowName Workflow name that this card type uses.
|
|
397
|
-
* @throws when workflow is empty, or
|
|
401
|
+
* @throws when workflow is empty, or
|
|
402
|
+
* when workflow does not exist in the project.
|
|
398
403
|
*/
|
|
399
404
|
public async createCardType(workflowName: string) {
|
|
400
405
|
if (!workflowName) {
|
|
@@ -115,7 +115,7 @@ export abstract class DefaultContent {
|
|
|
115
115
|
displayName: '',
|
|
116
116
|
dataType: dataType,
|
|
117
117
|
} as FieldType;
|
|
118
|
-
if (dataType === 'enum') {
|
|
118
|
+
if (dataType === 'enum' || dataType === 'list') {
|
|
119
119
|
value.enumValues = [{ enumValue: 'value1' }, { enumValue: 'value2' }];
|
|
120
120
|
}
|
|
121
121
|
return value;
|
|
@@ -50,6 +50,11 @@ export class FieldTypeResource extends FileResource<FieldType> {
|
|
|
50
50
|
private fromType: DataType = 'integer';
|
|
51
51
|
private toType: DataType = 'integer';
|
|
52
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Creates an instance of FieldTypeResource
|
|
55
|
+
* @param project Project to use
|
|
56
|
+
* @param name Resource name
|
|
57
|
+
*/
|
|
53
58
|
constructor(project: Project, name: ResourceName) {
|
|
54
59
|
super(project, name, 'fieldTypes');
|
|
55
60
|
|
|
@@ -183,13 +188,16 @@ export class FieldTypeResource extends FileResource<FieldType> {
|
|
|
183
188
|
);
|
|
184
189
|
}
|
|
185
190
|
const newValue = (op as ChangeOperation<EnumDefinition>).to;
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
throw new Error(
|
|
191
|
-
`Cannot perform operation on 'enumValues'. Enum with value '${(op.to as EnumDefinition).enumValue}' already exists`,
|
|
191
|
+
// Only check for duplicates if the enumValue itself is being changed
|
|
192
|
+
if (newValue.enumValue !== targetValue.enumValue) {
|
|
193
|
+
const foundTo = values.find(
|
|
194
|
+
(item) => (item as EnumDefinition).enumValue === newValue.enumValue,
|
|
192
195
|
);
|
|
196
|
+
if (foundTo) {
|
|
197
|
+
throw new Error(
|
|
198
|
+
`Cannot perform operation on 'enumValues'. Enum with value '${(op.to as EnumDefinition).enumValue}' already exists`,
|
|
199
|
+
);
|
|
200
|
+
}
|
|
193
201
|
}
|
|
194
202
|
}
|
|
195
203
|
// Return the whole object; caller can just provide 'enumValue'.
|
|
@@ -206,7 +214,12 @@ export class FieldTypeResource extends FileResource<FieldType> {
|
|
|
206
214
|
const removedValue = (op.target as EnumDefinition).enumValue;
|
|
207
215
|
const cardTypes = this.relevantCardTypes();
|
|
208
216
|
const allCards = await Promise.all(
|
|
209
|
-
cardTypes.map((cardType) =>
|
|
217
|
+
cardTypes.map((cardType) =>
|
|
218
|
+
this.collectCards(
|
|
219
|
+
cardType,
|
|
220
|
+
(card, cardTypeName) => card.metadata?.cardType === cardTypeName,
|
|
221
|
+
),
|
|
222
|
+
),
|
|
210
223
|
);
|
|
211
224
|
const cardsToUpdate = allCards
|
|
212
225
|
.flat()
|
|
@@ -283,6 +296,7 @@ export class FieldTypeResource extends FileResource<FieldType> {
|
|
|
283
296
|
await Promise.all([
|
|
284
297
|
super.updateHandleBars(existingName, this.content.name),
|
|
285
298
|
super.updateCalculations(existingName, this.content.name),
|
|
299
|
+
super.updateCardContentReferences(existingName, this.content.name),
|
|
286
300
|
this.updateCardTypes(existingName),
|
|
287
301
|
]);
|
|
288
302
|
await this.write();
|
|
@@ -314,16 +328,16 @@ export class FieldTypeResource extends FileResource<FieldType> {
|
|
|
314
328
|
*/
|
|
315
329
|
public static fieldDataTypes(): DataType[] {
|
|
316
330
|
return [
|
|
317
|
-
'shortText',
|
|
318
|
-
'longText',
|
|
319
|
-
'number',
|
|
320
|
-
'integer',
|
|
321
331
|
'boolean',
|
|
322
|
-
'enum',
|
|
323
|
-
'list',
|
|
324
332
|
'date',
|
|
325
333
|
'dateTime',
|
|
334
|
+
'enum',
|
|
335
|
+
'integer',
|
|
336
|
+
'list',
|
|
337
|
+
'longText',
|
|
338
|
+
'number',
|
|
326
339
|
'person',
|
|
340
|
+
'shortText',
|
|
327
341
|
];
|
|
328
342
|
}
|
|
329
343
|
|
|
@@ -420,6 +434,10 @@ export class FieldTypeResource extends FileResource<FieldType> {
|
|
|
420
434
|
}
|
|
421
435
|
content.dataType = super.handleScalar(op) as DataType;
|
|
422
436
|
} else if (key === 'enumValues') {
|
|
437
|
+
// Initialize enumValues array if it doesn't exist
|
|
438
|
+
if (!content.enumValues) {
|
|
439
|
+
content.enumValues = [];
|
|
440
|
+
}
|
|
423
441
|
if (op.name === 'add' || op.name === 'change' || op.name === 'remove') {
|
|
424
442
|
const existingValue = this.enumValueExists<EnumDefinition>(
|
|
425
443
|
op as Operation<EnumDefinition>,
|
|
@@ -35,23 +35,35 @@ export abstract class FileResource<
|
|
|
35
35
|
super(project, name, type);
|
|
36
36
|
this.initialize();
|
|
37
37
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Collects cards that match the given filter function.
|
|
40
|
+
* @param resourceName The resource name to filter by
|
|
41
|
+
* @param filterFn Function that returns true for cards to include
|
|
42
|
+
* @returns Array of cards that match the filter
|
|
43
|
+
*/
|
|
44
|
+
protected async collectCards(
|
|
45
|
+
resourceName: string,
|
|
46
|
+
filterFn: (card: Card, resourceName: string) => boolean,
|
|
47
|
+
): Promise<Card[]> {
|
|
48
|
+
function filteredCards(
|
|
49
|
+
cardSource: Card[],
|
|
50
|
+
resourceName: string,
|
|
51
|
+
filterFn: (card: Card, resourceName: string) => boolean,
|
|
52
|
+
): Card[] {
|
|
53
|
+
return cardSource.filter((card) => filterFn(card, resourceName));
|
|
43
54
|
}
|
|
44
55
|
|
|
45
56
|
// Collect both project cards ...
|
|
46
57
|
const projectCards = filteredCards(
|
|
47
58
|
this.project.cards(this.project.paths.cardRootFolder),
|
|
48
|
-
|
|
59
|
+
resourceName,
|
|
60
|
+
filterFn,
|
|
49
61
|
);
|
|
50
62
|
// ... and cards from each template that would be affected.
|
|
51
63
|
const templates = this.project.resources.templates(ResourcesFrom.localOnly);
|
|
52
64
|
const templateCards = templates.map((template) => {
|
|
53
65
|
const templateObject = template.templateObject();
|
|
54
|
-
return filteredCards(templateObject.cards(),
|
|
66
|
+
return filteredCards(templateObject.cards(), resourceName, filterFn);
|
|
55
67
|
});
|
|
56
68
|
// Return all affected cards
|
|
57
69
|
const cards = [projectCards, ...templateCards].reduce(
|
|
@@ -67,7 +79,12 @@ export abstract class FileResource<
|
|
|
67
79
|
*/
|
|
68
80
|
protected abstract onNameChange?(previousName: string): Promise<void>;
|
|
69
81
|
|
|
70
|
-
|
|
82
|
+
/**
|
|
83
|
+
* Updates resource key to a new prefix
|
|
84
|
+
* @param name Resource name
|
|
85
|
+
* @param prefixes list of prefixes in the project
|
|
86
|
+
* @returns updated resource name
|
|
87
|
+
*/
|
|
71
88
|
protected updatePrefixInResourceName(name: string, prefixes: string[]) {
|
|
72
89
|
const { identifier, prefix, type } = resourceName(name);
|
|
73
90
|
if (this.moduleResource) {
|
|
@@ -19,6 +19,7 @@ import { isContentKey } from '../interfaces/resource-interfaces.js';
|
|
|
19
19
|
import {
|
|
20
20
|
filename,
|
|
21
21
|
contentPropertyName,
|
|
22
|
+
ALL_FILE_MAPPINGS,
|
|
22
23
|
} from '../interfaces/folder-content-interfaces.js';
|
|
23
24
|
import { formatJson } from '../utils/json.js';
|
|
24
25
|
import { VALID_FOLDER_RESOURCE_FILES } from '../utils/constants.js';
|
|
@@ -95,7 +96,7 @@ export abstract class FolderResource<
|
|
|
95
96
|
for (const [fileName, fileContent] of contentFiles.entries()) {
|
|
96
97
|
const key = contentPropertyName(fileName);
|
|
97
98
|
if (key) {
|
|
98
|
-
const isJson = key === '
|
|
99
|
+
const isJson = key === ALL_FILE_MAPPINGS['parameterSchema.json'];
|
|
99
100
|
content[key] = isJson ? JSON.parse(fileContent) : fileContent;
|
|
100
101
|
}
|
|
101
102
|
}
|
|
@@ -143,15 +144,28 @@ export abstract class FolderResource<
|
|
|
143
144
|
throw new Error(`File '${fileName}' is not allowed to be updated`);
|
|
144
145
|
}
|
|
145
146
|
|
|
146
|
-
|
|
147
|
+
// TODO: Updates should either use valid strings or allow for objects
|
|
148
|
+
const key = contentPropertyName(fileName);
|
|
149
|
+
const isJson = key === ALL_FILE_MAPPINGS['parameterSchema.json'];
|
|
150
|
+
let parsedContent: unknown = changedContent;
|
|
151
|
+
if (isJson) {
|
|
152
|
+
try {
|
|
153
|
+
parsedContent = JSON.parse(changedContent);
|
|
154
|
+
} catch (error) {
|
|
155
|
+
const message =
|
|
156
|
+
error instanceof Error ? error.message : 'Unknown error';
|
|
157
|
+
throw new Error(`Invalid JSON content for '${key}' update: ${message}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
const contentToWrite = isJson
|
|
161
|
+
? formatJson(parsedContent as object)
|
|
162
|
+
: changedContent;
|
|
163
|
+
|
|
164
|
+
await writeFileSafe(filePath, contentToWrite, { flag: 'w' });
|
|
147
165
|
|
|
148
166
|
// Update this resource's content
|
|
149
|
-
const key = contentPropertyName(fileName);
|
|
150
167
|
if (key) {
|
|
151
|
-
|
|
152
|
-
(this.resourceContent as Record<string, unknown>)[key] = isJson
|
|
153
|
-
? JSON.parse(changedContent)
|
|
154
|
-
: changedContent;
|
|
168
|
+
(this.resourceContent as Record<string, unknown>)[key] = parsedContent;
|
|
155
169
|
}
|
|
156
170
|
}
|
|
157
171
|
|
|
@@ -38,6 +38,11 @@ export class GraphModelResource extends FolderResource<
|
|
|
38
38
|
GraphModelMetadata,
|
|
39
39
|
GraphModelContent
|
|
40
40
|
> {
|
|
41
|
+
/**
|
|
42
|
+
* Creates an instance of GraphModelResource
|
|
43
|
+
* @param project Project to use
|
|
44
|
+
* @param name Resource name
|
|
45
|
+
*/
|
|
41
46
|
constructor(project: Project, name: ResourceName) {
|
|
42
47
|
super(project, name, 'graphModels');
|
|
43
48
|
|
|
@@ -55,6 +60,7 @@ export class GraphModelResource extends FolderResource<
|
|
|
55
60
|
join(this.internalFolder, CONTENT_FILES.model),
|
|
56
61
|
]),
|
|
57
62
|
super.updateCalculations(existingName, this.content.name),
|
|
63
|
+
super.updateCardContentReferences(existingName, this.content.name),
|
|
58
64
|
]);
|
|
59
65
|
// Finally, write updated content.
|
|
60
66
|
await this.write();
|
|
@@ -39,6 +39,11 @@ export class GraphViewResource extends FolderResource<
|
|
|
39
39
|
GraphViewMetadata,
|
|
40
40
|
GraphViewContent
|
|
41
41
|
> {
|
|
42
|
+
/**
|
|
43
|
+
* Creates instance of GraphViewResource
|
|
44
|
+
* @param project Project to use
|
|
45
|
+
* @param name Resource name
|
|
46
|
+
*/
|
|
42
47
|
constructor(project: Project, name: ResourceName) {
|
|
43
48
|
super(project, name, 'graphViews');
|
|
44
49
|
|
|
@@ -56,6 +61,7 @@ export class GraphViewResource extends FolderResource<
|
|
|
56
61
|
await this.handleBarFile(),
|
|
57
62
|
]),
|
|
58
63
|
super.updateCalculations(existingName, this.content.name),
|
|
64
|
+
super.updateCardContentReferences(existingName, this.content.name),
|
|
59
65
|
]);
|
|
60
66
|
await this.write();
|
|
61
67
|
}
|
|
@@ -25,6 +25,11 @@ import type { ResourceName } from '../utils/resource-utils.js';
|
|
|
25
25
|
* Link Type resource class.
|
|
26
26
|
*/
|
|
27
27
|
export class LinkTypeResource extends FileResource<LinkType> {
|
|
28
|
+
/**
|
|
29
|
+
* Creates instance of LinkTypeResource
|
|
30
|
+
* @param project Project to use
|
|
31
|
+
* @param name Resource name
|
|
32
|
+
*/
|
|
28
33
|
constructor(project: Project, name: ResourceName) {
|
|
29
34
|
super(project, name, 'linkTypes');
|
|
30
35
|
|
|
@@ -32,6 +37,33 @@ export class LinkTypeResource extends FileResource<LinkType> {
|
|
|
32
37
|
this.contentSchema = super.contentSchemaContent(this.contentSchemaId);
|
|
33
38
|
}
|
|
34
39
|
|
|
40
|
+
// Update card metadata links when link type is renamed
|
|
41
|
+
private async updateCardLinks(from: string, to: string) {
|
|
42
|
+
const cards = await this.collectCards(
|
|
43
|
+
from,
|
|
44
|
+
(card, linkTypeName) =>
|
|
45
|
+
card.metadata?.links?.some((link) => link.linkType === linkTypeName) ??
|
|
46
|
+
false,
|
|
47
|
+
);
|
|
48
|
+
if (cards.length === 0) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
await Promise.all(
|
|
53
|
+
cards.map(async (card) => {
|
|
54
|
+
if (card.metadata?.links) {
|
|
55
|
+
card.metadata.links = card.metadata.links.map((link) => {
|
|
56
|
+
if (link.linkType === from) {
|
|
57
|
+
return { ...link, linkType: to };
|
|
58
|
+
}
|
|
59
|
+
return link;
|
|
60
|
+
});
|
|
61
|
+
await this.project.updateCardMetadata(card, card.metadata);
|
|
62
|
+
}
|
|
63
|
+
}),
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
35
67
|
/**
|
|
36
68
|
* When resource name changes.
|
|
37
69
|
* @param existingName Current resource name.
|
|
@@ -52,6 +84,8 @@ export class LinkTypeResource extends FileResource<LinkType> {
|
|
|
52
84
|
await Promise.all([
|
|
53
85
|
super.updateHandleBars(existingName, this.content.name),
|
|
54
86
|
super.updateCalculations(existingName, this.content.name),
|
|
87
|
+
super.updateCardContentReferences(existingName, this.content.name),
|
|
88
|
+
this.updateCardLinks(existingName, this.content.name),
|
|
55
89
|
]);
|
|
56
90
|
// Finally, write updated content.
|
|
57
91
|
await this.write();
|
|
@@ -42,6 +42,11 @@ export class ReportResource extends FolderResource<
|
|
|
42
42
|
ReportMetadata,
|
|
43
43
|
ReportContent
|
|
44
44
|
> {
|
|
45
|
+
/**
|
|
46
|
+
* Creates instance of ReportResource
|
|
47
|
+
* @param project Project to use
|
|
48
|
+
* @param name Resource name
|
|
49
|
+
*/
|
|
45
50
|
constructor(project: Project, name: ResourceName) {
|
|
46
51
|
super(project, name, 'reports');
|
|
47
52
|
|
|
@@ -68,6 +73,7 @@ export class ReportResource extends FolderResource<
|
|
|
68
73
|
await this.handleBarFiles(),
|
|
69
74
|
),
|
|
70
75
|
super.updateCalculations(existingName, this.content.name),
|
|
76
|
+
super.updateCardContentReferences(existingName, this.content.name),
|
|
71
77
|
]);
|
|
72
78
|
// Finally, write updated content.
|
|
73
79
|
await this.write();
|
|
@@ -152,9 +158,9 @@ export class ReportResource extends FolderResource<
|
|
|
152
158
|
|
|
153
159
|
/**
|
|
154
160
|
* Validates report.
|
|
155
|
-
* @throws when there are validation errors.
|
|
156
161
|
* @param content Content to be validated.
|
|
157
162
|
* @note If content is not provided, base class validation will use resource's current content.
|
|
163
|
+
* @throws when there are validation errors.
|
|
158
164
|
*/
|
|
159
165
|
public async validate(content?: object) {
|
|
160
166
|
const resourceContent = this.contentData();
|