@cyberismo/data-handler 0.0.11 → 0.0.13
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/card-metadata-updater.js +1 -1
- package/dist/card-metadata-updater.js.map +1 -1
- package/dist/command-handler.d.ts +26 -39
- package/dist/command-handler.js +76 -31
- package/dist/command-handler.js.map +1 -1
- package/dist/command-manager.d.ts +2 -1
- package/dist/command-manager.js +4 -2
- package/dist/command-manager.js.map +1 -1
- package/dist/commands/calculate.d.ts +7 -0
- package/dist/commands/calculate.js +9 -0
- package/dist/commands/calculate.js.map +1 -1
- package/dist/commands/create.d.ts +5 -0
- package/dist/commands/create.js +15 -8
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/fetch.d.ts +24 -0
- package/dist/commands/fetch.js +118 -0
- package/dist/commands/fetch.js.map +1 -0
- package/dist/commands/import.js +2 -2
- package/dist/commands/import.js.map +1 -1
- package/dist/commands/index.d.ts +2 -1
- package/dist/commands/index.js +2 -1
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/remove.d.ts +1 -0
- package/dist/commands/remove.js +16 -12
- package/dist/commands/remove.js.map +1 -1
- package/dist/commands/rename.js +4 -6
- package/dist/commands/rename.js.map +1 -1
- package/dist/commands/show.d.ts +22 -1
- package/dist/commands/show.js +56 -0
- package/dist/commands/show.js.map +1 -1
- package/dist/commands/update.d.ts +11 -1
- package/dist/commands/update.js +14 -2
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/validate.d.ts +2 -1
- package/dist/commands/validate.js +10 -10
- package/dist/commands/validate.js.map +1 -1
- package/dist/containers/card-container.js +1 -1
- package/dist/containers/card-container.js.map +1 -1
- package/dist/containers/project/calculation-engine.d.ts +8 -0
- package/dist/containers/project/calculation-engine.js +21 -10
- package/dist/containers/project/calculation-engine.js.map +1 -1
- package/dist/containers/project.d.ts +19 -8
- package/dist/containers/project.js +52 -34
- 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/index.d.ts +4 -2
- package/dist/index.js.map +1 -1
- package/dist/interfaces/command-options.d.ts +81 -0
- package/dist/interfaces/command-options.js +14 -0
- package/dist/interfaces/command-options.js.map +1 -0
- package/dist/interfaces/folder-content-interfaces.d.ts +50 -0
- package/dist/interfaces/folder-content-interfaces.js +45 -0
- package/dist/interfaces/folder-content-interfaces.js.map +1 -0
- package/dist/interfaces/project-interfaces.d.ts +13 -2
- package/dist/interfaces/project-interfaces.js.map +1 -1
- package/dist/interfaces/resource-interfaces.d.ts +28 -10
- package/dist/interfaces/resource-interfaces.js.map +1 -1
- package/dist/macros/base-macro.d.ts +1 -1
- package/dist/macros/base-macro.js +1 -1
- package/dist/macros/base-macro.js.map +1 -1
- package/dist/macros/createCards/index.d.ts +1 -1
- package/dist/macros/createCards/index.js +1 -1
- package/dist/macros/createCards/index.js.map +1 -1
- package/dist/macros/graph/index.d.ts +1 -1
- package/dist/macros/graph/index.js +21 -29
- package/dist/macros/graph/index.js.map +1 -1
- package/dist/macros/image/index.d.ts +1 -1
- package/dist/macros/image/index.js +1 -7
- package/dist/macros/image/index.js.map +1 -1
- package/dist/macros/include/index.d.ts +1 -1
- package/dist/macros/include/index.js +1 -1
- package/dist/macros/include/index.js.map +1 -1
- package/dist/macros/index.d.ts +12 -5
- package/dist/macros/index.js +19 -7
- package/dist/macros/index.js.map +1 -1
- package/dist/macros/percentage/index.d.ts +1 -1
- package/dist/macros/percentage/index.js +1 -1
- package/dist/macros/percentage/index.js.map +1 -1
- package/dist/macros/report/index.d.ts +1 -1
- package/dist/macros/report/index.js +5 -5
- package/dist/macros/report/index.js.map +1 -1
- package/dist/macros/scoreCard/index.d.ts +1 -1
- package/dist/macros/scoreCard/index.js +1 -1
- package/dist/macros/scoreCard/index.js.map +1 -1
- package/dist/macros/vega/index.d.ts +1 -1
- package/dist/macros/vega/index.js +1 -1
- package/dist/macros/vega/index.js.map +1 -1
- package/dist/macros/vegalite/index.d.ts +1 -1
- package/dist/macros/vegalite/index.js +1 -1
- package/dist/macros/vegalite/index.js.map +1 -1
- package/dist/macros/xref/index.d.ts +1 -1
- package/dist/macros/xref/index.js +1 -1
- package/dist/macros/xref/index.js.map +1 -1
- package/dist/project-settings.d.ts +14 -1
- package/dist/project-settings.js +51 -1
- package/dist/project-settings.js.map +1 -1
- package/dist/resources/card-type-resource.js +11 -5
- 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 +9 -4
- package/dist/resources/field-type-resource.js.map +1 -1
- package/dist/resources/folder-resource.d.ts +37 -9
- package/dist/resources/folder-resource.js +108 -12
- package/dist/resources/folder-resource.js.map +1 -1
- package/dist/resources/graph-model-resource.d.ts +7 -4
- package/dist/resources/graph-model-resource.js +12 -25
- package/dist/resources/graph-model-resource.js.map +1 -1
- package/dist/resources/graph-view-resource.d.ts +7 -4
- package/dist/resources/graph-view-resource.js +15 -31
- 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 +14 -10
- package/dist/resources/report-resource.js +41 -45
- package/dist/resources/report-resource.js.map +1 -1
- package/dist/resources/resource-object.d.ts +7 -0
- package/dist/resources/resource-object.js.map +1 -1
- package/dist/resources/template-resource.d.ts +5 -1
- package/dist/resources/template-resource.js +12 -7
- package/dist/resources/template-resource.js.map +1 -1
- package/dist/resources/workflow-resource.js +12 -5
- package/dist/resources/workflow-resource.js.map +1 -1
- package/dist/utils/log-utils.js +1 -1
- package/dist/utils/log-utils.js.map +1 -1
- package/dist/utils/report.js +6 -0
- package/dist/utils/report.js.map +1 -1
- package/dist/utils/resource-utils.d.ts +8 -0
- package/dist/utils/resource-utils.js +11 -0
- package/dist/utils/resource-utils.js.map +1 -1
- package/package.json +11 -11
- package/src/card-metadata-updater.ts +1 -1
- package/src/command-handler.ts +129 -61
- package/src/command-manager.ts +4 -1
- package/src/commands/calculate.ts +18 -0
- package/src/commands/create.ts +31 -19
- package/src/commands/fetch.ts +152 -0
- package/src/commands/import.ts +2 -0
- package/src/commands/index.ts +2 -0
- package/src/commands/remove.ts +18 -12
- package/src/commands/rename.ts +11 -11
- package/src/commands/show.ts +72 -0
- package/src/commands/update.ts +20 -2
- package/src/commands/validate.ts +13 -10
- package/src/containers/card-container.ts +1 -1
- package/src/containers/project/calculation-engine.ts +27 -11
- package/src/containers/project.ts +71 -61
- package/src/containers/template.ts +1 -1
- package/src/index.ts +36 -2
- package/src/interfaces/command-options.ts +144 -0
- package/src/interfaces/folder-content-interfaces.ts +69 -0
- package/src/interfaces/project-interfaces.ts +18 -0
- package/src/interfaces/resource-interfaces.ts +41 -12
- package/src/macros/base-macro.ts +5 -2
- package/src/macros/createCards/index.ts +1 -1
- package/src/macros/graph/index.ts +47 -51
- package/src/macros/image/index.ts +1 -7
- package/src/macros/include/index.ts +1 -1
- package/src/macros/index.ts +19 -7
- package/src/macros/percentage/index.ts +1 -1
- package/src/macros/report/index.ts +5 -5
- package/src/macros/scoreCard/index.ts +1 -1
- package/src/macros/vega/index.ts +1 -1
- package/src/macros/vegalite/index.ts +1 -1
- package/src/macros/xref/index.ts +1 -1
- package/src/project-settings.ts +62 -1
- package/src/resources/card-type-resource.ts +12 -6
- package/src/resources/field-type-resource.ts +9 -4
- package/src/resources/folder-resource.ts +149 -19
- package/src/resources/graph-model-resource.ts +16 -27
- package/src/resources/graph-view-resource.ts +23 -33
- package/src/resources/link-type-resource.ts +1 -1
- package/src/resources/report-resource.ts +60 -62
- package/src/resources/resource-object.ts +11 -0
- package/src/resources/template-resource.ts +12 -7
- package/src/resources/workflow-resource.ts +11 -6
- package/src/utils/log-utils.ts +1 -1
- package/src/utils/report.ts +6 -0
- package/src/utils/resource-utils.ts +16 -0
package/src/commands/create.ts
CHANGED
|
@@ -44,6 +44,8 @@ import { ReportResource } from '../resources/report-resource.js';
|
|
|
44
44
|
import { TemplateResource } from '../resources/template-resource.js';
|
|
45
45
|
import { WorkflowResource } from '../resources/workflow-resource.js';
|
|
46
46
|
|
|
47
|
+
const MODULES_PATH = `${sep}modules${sep}`;
|
|
48
|
+
|
|
47
49
|
// todo: Is there a easy to way to make JSON schema into a TypeScript interface/type?
|
|
48
50
|
// Check this out: https://www.npmjs.com/package/json-schema-to-ts
|
|
49
51
|
|
|
@@ -65,6 +67,7 @@ export class Create {
|
|
|
65
67
|
cardKeyPrefix: '$PROJECT-PREFIX',
|
|
66
68
|
name: '$PROJECT-NAME',
|
|
67
69
|
modules: [],
|
|
70
|
+
hubs: [],
|
|
68
71
|
},
|
|
69
72
|
name: Project.projectConfigFileName,
|
|
70
73
|
},
|
|
@@ -122,13 +125,13 @@ export class Create {
|
|
|
122
125
|
? await templateObject.findSpecificCard(card)
|
|
123
126
|
: undefined;
|
|
124
127
|
if (card && !specificCard) {
|
|
125
|
-
throw Error(
|
|
128
|
+
throw new Error(
|
|
126
129
|
`Card '${card}' was not found from template '${origTemplateName}'`,
|
|
127
130
|
);
|
|
128
131
|
}
|
|
129
132
|
|
|
130
133
|
if (templateObject.templateFolder().includes(`${sep}modules${sep}`)) {
|
|
131
|
-
throw Error(`Cannot add cards to imported module templates`);
|
|
134
|
+
throw new Error(`Cannot add cards to imported module templates`);
|
|
132
135
|
}
|
|
133
136
|
|
|
134
137
|
// Collect all add-card promises and settle them in parallel.
|
|
@@ -159,6 +162,14 @@ export class Create {
|
|
|
159
162
|
}
|
|
160
163
|
}
|
|
161
164
|
|
|
165
|
+
/**
|
|
166
|
+
* Adds a new hub location.
|
|
167
|
+
* @param hubUrl URL of the hub
|
|
168
|
+
*/
|
|
169
|
+
public async addHubLocation(hubUrl: string) {
|
|
170
|
+
return this.project.configuration.addHub(hubUrl);
|
|
171
|
+
}
|
|
172
|
+
|
|
162
173
|
/**
|
|
163
174
|
* Adds an attachment to a card.
|
|
164
175
|
* @param cardKey card ID
|
|
@@ -176,12 +187,10 @@ export class Create {
|
|
|
176
187
|
);
|
|
177
188
|
}
|
|
178
189
|
const attachmentFolder = await this.project.cardAttachmentFolder(cardKey);
|
|
179
|
-
if (!attachmentFolder) {
|
|
180
|
-
throw new Error(`Attachment folder for '${cardKey}' not found`);
|
|
181
|
-
}
|
|
182
190
|
|
|
183
191
|
// Imported templates cannot be modified.
|
|
184
|
-
|
|
192
|
+
// @todo: make MODULES_PATH project level constant
|
|
193
|
+
if (attachmentFolder.includes(MODULES_PATH)) {
|
|
185
194
|
throw new Error(`Cannot modify imported module`);
|
|
186
195
|
}
|
|
187
196
|
|
|
@@ -528,20 +537,23 @@ export class Create {
|
|
|
528
537
|
),
|
|
529
538
|
);
|
|
530
539
|
});
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
540
|
+
|
|
541
|
+
await Promise.all(
|
|
542
|
+
Create.JSONFileContent.map(async (entry) => {
|
|
543
|
+
if ('cardKeyPrefix' in entry.content) {
|
|
544
|
+
if (entry.content.cardKeyPrefix.includes('$PROJECT-PREFIX')) {
|
|
545
|
+
entry.content.cardKeyPrefix = projectPrefix.toLowerCase();
|
|
546
|
+
}
|
|
547
|
+
if (entry.content.name.includes('$PROJECT-NAME')) {
|
|
548
|
+
entry.content.name = projectName;
|
|
549
|
+
}
|
|
538
550
|
}
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
)
|
|
544
|
-
|
|
551
|
+
await writeJsonFile(
|
|
552
|
+
join(projectPath, entry.path, entry.name),
|
|
553
|
+
entry.content,
|
|
554
|
+
);
|
|
555
|
+
}),
|
|
556
|
+
);
|
|
545
557
|
|
|
546
558
|
try {
|
|
547
559
|
await writeFile(
|
|
@@ -0,0 +1,152 @@
|
|
|
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 { mkdir } from 'node:fs/promises';
|
|
15
|
+
import { resolve, sep } from 'node:path';
|
|
16
|
+
import type { Project } from '../containers/project.js';
|
|
17
|
+
|
|
18
|
+
import { writeJsonFile } from '../utils/json.js';
|
|
19
|
+
import { validateJson } from '../utils/validate.js';
|
|
20
|
+
import { type ModuleSetting } from '../interfaces/project-interfaces.js';
|
|
21
|
+
import { errorFunction, getChildLogger } from '../utils/log-utils.js';
|
|
22
|
+
|
|
23
|
+
const FETCH_TIMEOUT = 30000; // 30s timeout for fetching a hub file.
|
|
24
|
+
const MAX_RESPONSE_SIZE = 1024 * 1024; // 1MB limit for safety
|
|
25
|
+
const HUB_SCHEMA = 'hubSchema';
|
|
26
|
+
const MODULE_LIST_FILE = 'moduleList.json';
|
|
27
|
+
const TEMP_FOLDER = `.temp`;
|
|
28
|
+
|
|
29
|
+
export const MODULE_LIST_FULL_PATH = `${TEMP_FOLDER}/${MODULE_LIST_FILE}`;
|
|
30
|
+
|
|
31
|
+
export class Fetch {
|
|
32
|
+
constructor(private project: Project) {}
|
|
33
|
+
|
|
34
|
+
private get logger() {
|
|
35
|
+
return getChildLogger({
|
|
36
|
+
module: 'fetch',
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private async fetchJSON(location: string, schemaId: string) {
|
|
41
|
+
try {
|
|
42
|
+
const url = new URL(`${location}/${MODULE_LIST_FILE}`);
|
|
43
|
+
if (!['http:', 'https:'].includes(url.protocol)) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
`Invalid protocol: ${url.protocol}. Only HTTP and HTTPS are supported.`,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
this.logger.info(`Fetching module list from: ${url.toString()}`);
|
|
50
|
+
const response = await fetch(url.toString(), {
|
|
51
|
+
method: 'GET',
|
|
52
|
+
headers: {
|
|
53
|
+
Accept: 'application/json',
|
|
54
|
+
'User-Agent': 'Cyberismo/1.0',
|
|
55
|
+
},
|
|
56
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT),
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
if (!response.ok) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
`HTTP ${response.status}: ${response.statusText} when fetching from ${url.toString()}`,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Check content length before downloading
|
|
66
|
+
const contentLength = response.headers.get('content-length');
|
|
67
|
+
if (contentLength && parseInt(contentLength) > MAX_RESPONSE_SIZE) {
|
|
68
|
+
throw new Error(
|
|
69
|
+
`Response too large: ${contentLength} bytes (max: ${MAX_RESPONSE_SIZE})`,
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const contentType = response.headers.get('content-type');
|
|
74
|
+
if (!contentType?.includes('application/json')) {
|
|
75
|
+
this.logger.warn(`Expected JSON response, got: ${contentType}`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const json = await response.json();
|
|
79
|
+
// Validate the incoming JSON before saving it into a file.
|
|
80
|
+
await validateJson(json, { schemaId: schemaId });
|
|
81
|
+
|
|
82
|
+
// Validate JSON structure and prevent prototype pollution
|
|
83
|
+
if (typeof json !== 'object' || json === null || Array.isArray(json)) {
|
|
84
|
+
throw new Error('Response must be a JSON object');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Additional size check after JSON parsing
|
|
88
|
+
if (JSON.stringify(json).length > MAX_RESPONSE_SIZE) {
|
|
89
|
+
throw new Error('JSON content too large after parsing');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return json;
|
|
93
|
+
} catch (error) {
|
|
94
|
+
this.logger.error(
|
|
95
|
+
error,
|
|
96
|
+
`Failed to fetch module list from ${location}: ${errorFunction(error)}`,
|
|
97
|
+
);
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Fetches modules from modules hub(s) and writes them to a file.
|
|
104
|
+
*/
|
|
105
|
+
public async fetchHubs() {
|
|
106
|
+
const hubs = this.project.configuration.hubs;
|
|
107
|
+
|
|
108
|
+
const moduleMap: Map<string, ModuleSetting> = new Map([]);
|
|
109
|
+
|
|
110
|
+
for (const hub of hubs) {
|
|
111
|
+
const json = await this.fetchJSON(hub.location, HUB_SCHEMA);
|
|
112
|
+
json.modules.forEach((module: ModuleSetting) => {
|
|
113
|
+
if (!moduleMap.has(module.name)) {
|
|
114
|
+
moduleMap.set(module.name, module);
|
|
115
|
+
} else {
|
|
116
|
+
this.logger.info(
|
|
117
|
+
`Skipping module '${module.name}' since it was already listed.`,
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
const fullPath = resolve(this.project.basePath, MODULE_LIST_FULL_PATH);
|
|
125
|
+
const normalizedBasePath = resolve(this.project.basePath);
|
|
126
|
+
|
|
127
|
+
// Ensure the file is written within the project directory (prevent path traversal)
|
|
128
|
+
if (
|
|
129
|
+
!fullPath.startsWith(normalizedBasePath + sep) &&
|
|
130
|
+
fullPath !== normalizedBasePath
|
|
131
|
+
) {
|
|
132
|
+
throw new Error(
|
|
133
|
+
'Invalid file path: attempting to write outside project directory',
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
await mkdir(resolve(this.project.basePath, TEMP_FOLDER), {
|
|
138
|
+
recursive: true,
|
|
139
|
+
});
|
|
140
|
+
await writeJsonFile(fullPath, {
|
|
141
|
+
modules: Array.from(moduleMap.values()),
|
|
142
|
+
});
|
|
143
|
+
this.logger.info(`Module list written to: ${fullPath}`);
|
|
144
|
+
} catch (error) {
|
|
145
|
+
this.logger.error(
|
|
146
|
+
error,
|
|
147
|
+
`Failed to write module list to local file: ${errorFunction(error)}`,
|
|
148
|
+
);
|
|
149
|
+
throw error;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
package/src/commands/import.ts
CHANGED
|
@@ -136,6 +136,7 @@ export class Import {
|
|
|
136
136
|
) {
|
|
137
137
|
const beforeImportValidateErrors = await Validate.getInstance().validate(
|
|
138
138
|
this.project.basePath,
|
|
139
|
+
() => this.project,
|
|
139
140
|
);
|
|
140
141
|
const gitModule = source.startsWith('https') || source.startsWith('git@');
|
|
141
142
|
const modulePrefix = gitModule
|
|
@@ -164,6 +165,7 @@ export class Import {
|
|
|
164
165
|
// Validate the project after module has been imported
|
|
165
166
|
const afterImportValidateErrors = await Validate.getInstance().validate(
|
|
166
167
|
this.project.basePath,
|
|
168
|
+
() => this.project,
|
|
167
169
|
);
|
|
168
170
|
if (afterImportValidateErrors.length > beforeImportValidateErrors.length) {
|
|
169
171
|
console.error(
|
package/src/commands/index.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { Calculate } from './calculate.js';
|
|
|
15
15
|
import { Create } from './create.js';
|
|
16
16
|
import { Edit } from './edit.js';
|
|
17
17
|
import { Export } from './export.js';
|
|
18
|
+
import { Fetch } from './fetch.js';
|
|
18
19
|
import { Import } from './import.js';
|
|
19
20
|
import { Move } from './move.js';
|
|
20
21
|
import { Remove } from './remove.js';
|
|
@@ -29,6 +30,7 @@ export {
|
|
|
29
30
|
Create,
|
|
30
31
|
Edit,
|
|
31
32
|
Export,
|
|
33
|
+
Fetch,
|
|
32
34
|
Import,
|
|
33
35
|
Move,
|
|
34
36
|
Remove,
|
package/src/commands/remove.ts
CHANGED
|
@@ -53,9 +53,6 @@ export class Remove {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
const attachmentFolder = await this.project.cardAttachmentFolder(cardKey);
|
|
56
|
-
if (!attachmentFolder) {
|
|
57
|
-
throw new Error(`Card '${cardKey}' not found`);
|
|
58
|
-
}
|
|
59
56
|
|
|
60
57
|
// Imported templates cannot be modified.
|
|
61
58
|
if (attachmentFolder.includes(MODULES_PATH)) {
|
|
@@ -95,13 +92,16 @@ export class Remove {
|
|
|
95
92
|
},
|
|
96
93
|
);
|
|
97
94
|
const promiseContainer: Promise<void>[] = [];
|
|
98
|
-
|
|
99
|
-
|
|
95
|
+
|
|
96
|
+
for (const item of allCards) {
|
|
97
|
+
const links = item.metadata?.links ?? [];
|
|
98
|
+
for (const link of links) {
|
|
100
99
|
if (link.cardKey === cardKey) {
|
|
101
100
|
promiseContainer.push(this.removeLink(item.key, link.cardKey));
|
|
102
101
|
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
105
|
await Promise.all(promiseContainer);
|
|
106
106
|
|
|
107
107
|
// Calculations need to be updated before card is removed.
|
|
@@ -182,6 +182,11 @@ export class Remove {
|
|
|
182
182
|
await this.project.updateCardMetadataKey(sourceCardKey, 'links', newLinks);
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
+
// Remove a hub from project.
|
|
186
|
+
private async removeHubLocation(name: string) {
|
|
187
|
+
await this.project.configuration.removeHub(name);
|
|
188
|
+
}
|
|
189
|
+
|
|
185
190
|
/**
|
|
186
191
|
* Removes either attachment, card, imported module, link or resource from project.
|
|
187
192
|
* @param type Type of resource
|
|
@@ -221,14 +226,15 @@ export class Remove {
|
|
|
221
226
|
return resource?.delete();
|
|
222
227
|
} else {
|
|
223
228
|
// Something else than resources...
|
|
224
|
-
if (type
|
|
229
|
+
if (type === 'attachment')
|
|
225
230
|
return this.removeAttachment(targetName, rest[0]);
|
|
226
|
-
else if (type
|
|
227
|
-
else if (type
|
|
231
|
+
else if (type === 'card') return this.removeCard(targetName);
|
|
232
|
+
else if (type === 'link')
|
|
228
233
|
return this.removeLink(targetName, rest[0], rest[1], rest.at(2));
|
|
229
|
-
else if (type
|
|
234
|
+
else if (type === 'module')
|
|
230
235
|
return this.moduleManager.removeModule(targetName);
|
|
231
|
-
else if (type
|
|
236
|
+
else if (type === 'label') return this.removeLabel(targetName, rest[0]);
|
|
237
|
+
else if (type === 'hub') return this.removeHubLocation(targetName);
|
|
232
238
|
}
|
|
233
239
|
throw new Error(`Unknown resource type '${type}'`);
|
|
234
240
|
}
|
package/src/commands/rename.ts
CHANGED
|
@@ -160,17 +160,17 @@ export class Rename {
|
|
|
160
160
|
);
|
|
161
161
|
|
|
162
162
|
// Then replace all values that match in the conversion map.
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
encoding: 'utf-8'
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
163
|
+
await Promise.all(
|
|
164
|
+
files.map(async (item) => {
|
|
165
|
+
const target = join(item.parentPath, item.name);
|
|
166
|
+
let fileContent = await readFile(target, { encoding: 'utf-8' });
|
|
167
|
+
for (const [key, value] of conversionMap) {
|
|
168
|
+
const re = new RegExp(key, 'g');
|
|
169
|
+
fileContent = fileContent.replace(re, value);
|
|
170
|
+
}
|
|
171
|
+
await writeFile(target, fileContent);
|
|
172
|
+
}),
|
|
173
|
+
);
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
// Changes the name of a resource to match the new prefix.
|
package/src/commands/show.ts
CHANGED
|
@@ -18,6 +18,8 @@ import { join, resolve } from 'node:path';
|
|
|
18
18
|
import { spawn } from 'node:child_process';
|
|
19
19
|
import { readFile, writeFile } from 'node:fs/promises';
|
|
20
20
|
|
|
21
|
+
import { MODULE_LIST_FULL_PATH } from './fetch.js';
|
|
22
|
+
|
|
21
23
|
import mime from 'mime-types';
|
|
22
24
|
|
|
23
25
|
import type { attachmentPayload } from '../interfaces/request-status-interfaces.js';
|
|
@@ -26,6 +28,8 @@ import type {
|
|
|
26
28
|
Card,
|
|
27
29
|
CardListContainer,
|
|
28
30
|
ModuleContent,
|
|
31
|
+
HubSetting,
|
|
32
|
+
ModuleSettingFromHub,
|
|
29
33
|
ProjectFetchCardDetails,
|
|
30
34
|
ProjectMetadata,
|
|
31
35
|
Resource,
|
|
@@ -53,6 +57,8 @@ import ReportMacro from '../macros/report/index.js';
|
|
|
53
57
|
import TaskQueue from '../macros/task-queue.js';
|
|
54
58
|
import { evaluateMacros } from '../macros/index.js';
|
|
55
59
|
import { FolderResource } from '../resources/folder-resource.js';
|
|
60
|
+
import { readJsonFile } from '../utils/json.js';
|
|
61
|
+
import { getChildLogger } from '../utils/log-utils.js';
|
|
56
62
|
|
|
57
63
|
/**
|
|
58
64
|
* Show command.
|
|
@@ -76,6 +82,12 @@ export class Show {
|
|
|
76
82
|
]);
|
|
77
83
|
}
|
|
78
84
|
|
|
85
|
+
private get logger() {
|
|
86
|
+
return getChildLogger({
|
|
87
|
+
module: 'show',
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
79
91
|
// Collect all labels from cards.
|
|
80
92
|
private collectLabels = (cards: Card[]): string[] => {
|
|
81
93
|
return cards.reduce<string[]>((labels, card) => {
|
|
@@ -320,6 +332,8 @@ export class Show {
|
|
|
320
332
|
|
|
321
333
|
/**
|
|
322
334
|
* Shows the content of a file in a resource.
|
|
335
|
+
* TODO: To be removed
|
|
336
|
+
* @deprecated
|
|
323
337
|
* @param resourceName Name of the resource.
|
|
324
338
|
* @param fileName Name of the file to show.
|
|
325
339
|
* @returns the content of the file.
|
|
@@ -348,6 +362,8 @@ export class Show {
|
|
|
348
362
|
|
|
349
363
|
/**
|
|
350
364
|
* Shows all file names in a folder resource.
|
|
365
|
+
* TODO: To be removed
|
|
366
|
+
* @deprecated
|
|
351
367
|
* @param resourceName Name of the resource.
|
|
352
368
|
* @returns all file names in the resource.
|
|
353
369
|
*/
|
|
@@ -369,6 +385,54 @@ export class Show {
|
|
|
369
385
|
}
|
|
370
386
|
return resource.showFileNames();
|
|
371
387
|
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Shows importable modules.
|
|
391
|
+
* @param showAll - When true, shows all importable modules, even if they have already been imported
|
|
392
|
+
* @param showDetails - When true, shows all properties of modules, not just name.
|
|
393
|
+
* @returns list of modules; the list content depends on the parameters provided
|
|
394
|
+
* by default it is a list of module names that could be imported into the project,
|
|
395
|
+
* with 'showDetails' true, instead of name, the list consists of full details of the modules
|
|
396
|
+
* with 'showAll' true, the list consists of all modules in the hubs, even if they have already been imported
|
|
397
|
+
* Note that the two boolean options can be combined.
|
|
398
|
+
*/
|
|
399
|
+
public async showImportableModules(
|
|
400
|
+
showAll?: boolean,
|
|
401
|
+
showDetails?: boolean,
|
|
402
|
+
): Promise<ModuleSettingFromHub[]> {
|
|
403
|
+
try {
|
|
404
|
+
const moduleList = (
|
|
405
|
+
await readJsonFile(
|
|
406
|
+
resolve(this.project.basePath, MODULE_LIST_FULL_PATH),
|
|
407
|
+
)
|
|
408
|
+
).modules;
|
|
409
|
+
const currentModules = await this.project.modules();
|
|
410
|
+
const nonImportedModules = moduleList.filter(
|
|
411
|
+
(item: ModuleSettingFromHub) => {
|
|
412
|
+
return !currentModules.some((module) => item.name === module.name);
|
|
413
|
+
},
|
|
414
|
+
);
|
|
415
|
+
|
|
416
|
+
if (showAll && showDetails) {
|
|
417
|
+
return moduleList;
|
|
418
|
+
}
|
|
419
|
+
if (showAll) {
|
|
420
|
+
return moduleList?.map((item: ModuleSettingFromHub) => item?.name);
|
|
421
|
+
}
|
|
422
|
+
if (showDetails) {
|
|
423
|
+
return nonImportedModules;
|
|
424
|
+
}
|
|
425
|
+
// By default return the non-imported modules
|
|
426
|
+
return nonImportedModules.map((item: ModuleSettingFromHub) => item?.name);
|
|
427
|
+
} catch (error) {
|
|
428
|
+
if (error instanceof Error) {
|
|
429
|
+
this.logger.error(error.message);
|
|
430
|
+
}
|
|
431
|
+
// Module list doesn't exist, return empty list
|
|
432
|
+
return [];
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
372
436
|
/**
|
|
373
437
|
* Returns all unique labels in a project
|
|
374
438
|
* @returns labels in a list
|
|
@@ -406,6 +470,14 @@ export class Show {
|
|
|
406
470
|
return moduleDetails;
|
|
407
471
|
}
|
|
408
472
|
|
|
473
|
+
/**
|
|
474
|
+
* Shows hubs of the project.
|
|
475
|
+
* @returns list of hubs.
|
|
476
|
+
*/
|
|
477
|
+
public showHubs(): HubSetting[] {
|
|
478
|
+
return this.project.configuration.hubs;
|
|
479
|
+
}
|
|
480
|
+
|
|
409
481
|
/**
|
|
410
482
|
* Returns all project cards in the project. Cards don't have content and nor metadata.
|
|
411
483
|
* @note AppUi uses this method.
|
package/src/commands/update.ts
CHANGED
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
AddOperation,
|
|
15
15
|
ChangeOperation,
|
|
16
16
|
Operation,
|
|
17
|
+
OperationFor,
|
|
17
18
|
RankOperation,
|
|
18
19
|
RemoveOperation,
|
|
19
20
|
UpdateOperations,
|
|
@@ -44,7 +45,6 @@ export class Update {
|
|
|
44
45
|
optionalDetail?: Type, // todo: for 'rank' it might be reasonable to accept also 'number'
|
|
45
46
|
mappingTable?: { stateMapping: Record<string, string> },
|
|
46
47
|
) {
|
|
47
|
-
const resource = Project.resourceObject(this.project, resourceName(name));
|
|
48
48
|
const op: Operation<Type> = {
|
|
49
49
|
name: operation,
|
|
50
50
|
target: '' as Type,
|
|
@@ -76,7 +76,25 @@ export class Update {
|
|
|
76
76
|
: undefined;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
await
|
|
79
|
+
await this.applyResourceOperation(name, key, op);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Update single resource property
|
|
84
|
+
* This is similar to updateValue, but allows the operation to be fully specified
|
|
85
|
+
* @param name Name of the resource to operate on.
|
|
86
|
+
* @param key Property to change in resource JSON
|
|
87
|
+
* @param operation The full operation object
|
|
88
|
+
* @template Type Type of the target of the operation
|
|
89
|
+
* @template T Type of operation ('add', 'remove', 'change', 'rank')
|
|
90
|
+
*/
|
|
91
|
+
public async applyResourceOperation<Type, T extends UpdateOperations>(
|
|
92
|
+
name: string,
|
|
93
|
+
key: string,
|
|
94
|
+
operation: OperationFor<Type, T>,
|
|
95
|
+
) {
|
|
96
|
+
const resource = Project.resourceObject(this.project, resourceName(name));
|
|
97
|
+
await resource?.update(key, operation);
|
|
80
98
|
this.project.collectLocalResources();
|
|
81
99
|
}
|
|
82
100
|
}
|
package/src/commands/validate.ts
CHANGED
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
// node
|
|
15
15
|
import { type Dirent } from 'node:fs';
|
|
16
16
|
import { basename, dirname, extname, join, parse, resolve } from 'node:path';
|
|
17
|
-
import { fileURLToPath } from 'node:url';
|
|
18
17
|
import { readdir } from 'node:fs/promises';
|
|
19
18
|
|
|
20
19
|
// dependencies
|
|
@@ -52,7 +51,7 @@ const SHORT_TEXT_MAX_LENGTH = 80;
|
|
|
52
51
|
|
|
53
52
|
import * as EmailValidator from 'email-validator';
|
|
54
53
|
import { evaluateMacros } from '../macros/index.js';
|
|
55
|
-
const baseDir =
|
|
54
|
+
const baseDir = import.meta.dirname;
|
|
56
55
|
const subFoldersToValidate = ['.cards', 'cardRoot'];
|
|
57
56
|
|
|
58
57
|
export interface LengthProvider {
|
|
@@ -522,9 +521,13 @@ export class Validate {
|
|
|
522
521
|
* Validates that a given directory path (and its children) conform to a JSON schema.
|
|
523
522
|
* @note Validates also content in the directory tree, if .schema file is found.
|
|
524
523
|
* @param projectPath path to validate.
|
|
524
|
+
* @param projectFn function that returns a Project instance.
|
|
525
525
|
* @returns string containing all validation errors
|
|
526
526
|
*/
|
|
527
|
-
public async validate(
|
|
527
|
+
public async validate(
|
|
528
|
+
projectPath: string,
|
|
529
|
+
projectFn: () => Project,
|
|
530
|
+
): Promise<string> {
|
|
528
531
|
let validationErrors = '';
|
|
529
532
|
this.validatedFieldTypes.clear();
|
|
530
533
|
this.validatedWorkflows.clear();
|
|
@@ -546,7 +549,7 @@ export class Validate {
|
|
|
546
549
|
return validationErrors;
|
|
547
550
|
} else {
|
|
548
551
|
const errorMsg: string[] = [];
|
|
549
|
-
const project =
|
|
552
|
+
const project = projectFn();
|
|
550
553
|
|
|
551
554
|
// Then, validate that each 'contentSchema' children as well.
|
|
552
555
|
const result = await this.readAndValidateContentFiles(
|
|
@@ -606,17 +609,17 @@ export class Validate {
|
|
|
606
609
|
});
|
|
607
610
|
}
|
|
608
611
|
}
|
|
609
|
-
if (errorMsg.length) {
|
|
610
|
-
validationErrors += errorMsg
|
|
611
|
-
.filter(this.removeDuplicateEntries)
|
|
612
|
-
.join('\n');
|
|
613
|
-
}
|
|
614
612
|
// Validate that there are no duplicate card keys
|
|
615
613
|
for (const [key, count] of cardIds) {
|
|
616
614
|
if (count > 1) {
|
|
617
|
-
|
|
615
|
+
errorMsg.push(`Duplicate card key '${key}' found ${count} times`);
|
|
618
616
|
}
|
|
619
617
|
}
|
|
618
|
+
if (errorMsg.length) {
|
|
619
|
+
validationErrors += errorMsg
|
|
620
|
+
.filter(this.removeDuplicateEntries)
|
|
621
|
+
.join('\n');
|
|
622
|
+
}
|
|
620
623
|
}
|
|
621
624
|
} catch (error) {
|
|
622
625
|
validationErrors += errorFunction(error);
|
|
@@ -18,9 +18,9 @@ import type { Dirent } from 'node:fs';
|
|
|
18
18
|
import { readdir, readFile, writeFile } from 'node:fs/promises';
|
|
19
19
|
|
|
20
20
|
import { findParentPath } from '../utils/card-utils.js';
|
|
21
|
+
import { getFilesSync } from '../utils/file-utils.js';
|
|
21
22
|
import { readJsonFile } from '../utils/json.js';
|
|
22
23
|
import { writeJsonFile } from '../utils/json.js';
|
|
23
|
-
import { getFilesSync } from '../utils/file-utils.js';
|
|
24
24
|
|
|
25
25
|
// interfaces
|
|
26
26
|
import {
|