@cyberismo/data-handler 0.0.18 → 0.0.19
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 +26 -2
- 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/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/field-type-resource.d.ts +5 -0
- package/dist/resources/field-type-resource.js +13 -7
- 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/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/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/package.json +5 -3
- package/src/command-handler.ts +38 -1
- 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/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/field-type-resource.ts +18 -7
- package/src/resources/file-resource.ts +25 -8
- 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/utils/common-utils.ts +15 -0
- package/src/utils/file-utils.ts +56 -12
- package/src/utils/json.ts +2 -6
|
@@ -0,0 +1,190 @@
|
|
|
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 { parentPort } from 'node:worker_threads';
|
|
15
|
+
import { pathToFileURL } from 'node:url';
|
|
16
|
+
|
|
17
|
+
import type {
|
|
18
|
+
Migration,
|
|
19
|
+
MigrationContext,
|
|
20
|
+
MigrationStepResult,
|
|
21
|
+
} from '@cyberismo/migrations';
|
|
22
|
+
import type { WorkerMessage, WorkerResponse } from './migration-executor.js';
|
|
23
|
+
|
|
24
|
+
let currentMigration: Migration | undefined;
|
|
25
|
+
let abortController: AbortController | undefined;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Execute a specific step of a migration.
|
|
29
|
+
*
|
|
30
|
+
* Runs one of the migration steps (before, backup, migrate, after).
|
|
31
|
+
* Checks for cancellation before and after execution.
|
|
32
|
+
*
|
|
33
|
+
* @param migration The loaded migration object
|
|
34
|
+
* @param stepName Which step to execute ('before', 'backup', 'migrate', or 'after')
|
|
35
|
+
* @param context Migration context with paths and version information
|
|
36
|
+
* @returns Migration step result
|
|
37
|
+
*/
|
|
38
|
+
async function executeStep(
|
|
39
|
+
migration: Migration,
|
|
40
|
+
stepName: string,
|
|
41
|
+
context: MigrationContext,
|
|
42
|
+
): Promise<MigrationStepResult> {
|
|
43
|
+
if (abortController?.signal.aborted) {
|
|
44
|
+
return {
|
|
45
|
+
success: false,
|
|
46
|
+
error: new Error('Migration cancelled'),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
let result: MigrationStepResult;
|
|
52
|
+
|
|
53
|
+
switch (stepName) {
|
|
54
|
+
case 'before':
|
|
55
|
+
if (!migration.before) {
|
|
56
|
+
return { success: true, message: `No 'before' step` };
|
|
57
|
+
}
|
|
58
|
+
result = await migration.before(context);
|
|
59
|
+
break;
|
|
60
|
+
|
|
61
|
+
case 'backup':
|
|
62
|
+
if (!migration.backup) {
|
|
63
|
+
return { success: true, message: `No 'backup' step` };
|
|
64
|
+
}
|
|
65
|
+
result = await migration.backup(context);
|
|
66
|
+
break;
|
|
67
|
+
|
|
68
|
+
case 'migrate':
|
|
69
|
+
result = await migration.migrate(context);
|
|
70
|
+
break;
|
|
71
|
+
|
|
72
|
+
case 'after':
|
|
73
|
+
if (!migration.after) {
|
|
74
|
+
return { success: true, message: `No 'after' step` };
|
|
75
|
+
}
|
|
76
|
+
result = await migration.after(context);
|
|
77
|
+
break;
|
|
78
|
+
|
|
79
|
+
default:
|
|
80
|
+
return {
|
|
81
|
+
success: false,
|
|
82
|
+
error: new Error(`Unknown migration step: ${stepName}`),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (abortController?.signal.aborted) {
|
|
87
|
+
return {
|
|
88
|
+
success: false,
|
|
89
|
+
error: new Error('Migration cancelled'),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return result;
|
|
94
|
+
} catch (error) {
|
|
95
|
+
return {
|
|
96
|
+
success: false,
|
|
97
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Handle a cancellation request from the main thread.
|
|
104
|
+
*/
|
|
105
|
+
async function handleCancel(): Promise<void> {
|
|
106
|
+
abortController?.abort();
|
|
107
|
+
|
|
108
|
+
if (currentMigration?.cancel) {
|
|
109
|
+
try {
|
|
110
|
+
await currentMigration.cancel();
|
|
111
|
+
} catch {
|
|
112
|
+
// Ignore errors during cancellation - we're cancelling anyway
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Worker thread message handler.
|
|
119
|
+
*
|
|
120
|
+
* Listens for messages from the main thread:
|
|
121
|
+
* - 'execute': Run a migration step
|
|
122
|
+
* - 'cancel': Cancel the currently running migration
|
|
123
|
+
*
|
|
124
|
+
* Responds with:
|
|
125
|
+
* - 'result': The migration step result
|
|
126
|
+
* - 'error': An error message when execution failed
|
|
127
|
+
*/
|
|
128
|
+
if (parentPort) {
|
|
129
|
+
parentPort.on('message', (message: WorkerMessage) => {
|
|
130
|
+
if (!parentPort) return;
|
|
131
|
+
|
|
132
|
+
void (async () => {
|
|
133
|
+
try {
|
|
134
|
+
if (message.type === 'cancel') {
|
|
135
|
+
await handleCancel();
|
|
136
|
+
const response: WorkerResponse = {
|
|
137
|
+
type: 'result',
|
|
138
|
+
result: {
|
|
139
|
+
success: false,
|
|
140
|
+
error: new Error('Migration cancelled'),
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
parentPort.postMessage(response);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (message.type === 'execute') {
|
|
148
|
+
if (!message.migrationPath || !message.stepName || !message.context) {
|
|
149
|
+
throw new Error('Missing required parameters for "execute"');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
abortController = new AbortController();
|
|
153
|
+
|
|
154
|
+
const migrationUrl = pathToFileURL(message.migrationPath).href;
|
|
155
|
+
const migrationModule = await import(migrationUrl);
|
|
156
|
+
currentMigration = migrationModule.default || migrationModule;
|
|
157
|
+
|
|
158
|
+
if (typeof currentMigration?.migrate !== 'function') {
|
|
159
|
+
throw new Error(
|
|
160
|
+
`Migration does not implement migrate() function: ${message.migrationPath}`,
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const contextWithSignal: MigrationContext = {
|
|
165
|
+
...message.context,
|
|
166
|
+
signal: abortController.signal,
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const result = await executeStep(
|
|
170
|
+
currentMigration,
|
|
171
|
+
message.stepName,
|
|
172
|
+
contextWithSignal,
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
const response: WorkerResponse = {
|
|
176
|
+
type: 'result',
|
|
177
|
+
result,
|
|
178
|
+
};
|
|
179
|
+
parentPort.postMessage(response);
|
|
180
|
+
}
|
|
181
|
+
} catch (error) {
|
|
182
|
+
const response: WorkerResponse = {
|
|
183
|
+
type: 'error',
|
|
184
|
+
error: error instanceof Error ? error.message : String(error),
|
|
185
|
+
};
|
|
186
|
+
parentPort.postMessage(response);
|
|
187
|
+
}
|
|
188
|
+
})();
|
|
189
|
+
});
|
|
190
|
+
}
|
|
@@ -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) {
|
|
@@ -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
|
|
|
@@ -206,7 +211,12 @@ export class FieldTypeResource extends FileResource<FieldType> {
|
|
|
206
211
|
const removedValue = (op.target as EnumDefinition).enumValue;
|
|
207
212
|
const cardTypes = this.relevantCardTypes();
|
|
208
213
|
const allCards = await Promise.all(
|
|
209
|
-
cardTypes.map((cardType) =>
|
|
214
|
+
cardTypes.map((cardType) =>
|
|
215
|
+
this.collectCards(
|
|
216
|
+
cardType,
|
|
217
|
+
(card, cardTypeName) => card.metadata?.cardType === cardTypeName,
|
|
218
|
+
),
|
|
219
|
+
),
|
|
210
220
|
);
|
|
211
221
|
const cardsToUpdate = allCards
|
|
212
222
|
.flat()
|
|
@@ -283,6 +293,7 @@ export class FieldTypeResource extends FileResource<FieldType> {
|
|
|
283
293
|
await Promise.all([
|
|
284
294
|
super.updateHandleBars(existingName, this.content.name),
|
|
285
295
|
super.updateCalculations(existingName, this.content.name),
|
|
296
|
+
super.updateCardContentReferences(existingName, this.content.name),
|
|
286
297
|
this.updateCardTypes(existingName),
|
|
287
298
|
]);
|
|
288
299
|
await this.write();
|
|
@@ -314,16 +325,16 @@ export class FieldTypeResource extends FileResource<FieldType> {
|
|
|
314
325
|
*/
|
|
315
326
|
public static fieldDataTypes(): DataType[] {
|
|
316
327
|
return [
|
|
317
|
-
'shortText',
|
|
318
|
-
'longText',
|
|
319
|
-
'number',
|
|
320
|
-
'integer',
|
|
321
328
|
'boolean',
|
|
322
|
-
'enum',
|
|
323
|
-
'list',
|
|
324
329
|
'date',
|
|
325
330
|
'dateTime',
|
|
331
|
+
'enum',
|
|
332
|
+
'integer',
|
|
333
|
+
'list',
|
|
334
|
+
'longText',
|
|
335
|
+
'number',
|
|
326
336
|
'person',
|
|
337
|
+
'shortText',
|
|
327
338
|
];
|
|
328
339
|
}
|
|
329
340
|
|
|
@@ -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) {
|
|
@@ -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
|
}
|