@cyberismo/data-handler 0.0.16 → 0.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/command-handler.js +5 -7
- package/dist/command-handler.js.map +1 -1
- package/dist/command-manager.js +4 -4
- package/dist/command-manager.js.map +1 -1
- package/dist/commands/create.js +1 -1
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/fetch.d.ts +8 -0
- package/dist/commands/fetch.js +101 -23
- package/dist/commands/fetch.js.map +1 -1
- package/dist/commands/import.d.ts +5 -2
- package/dist/commands/import.js +12 -3
- package/dist/commands/import.js.map +1 -1
- package/dist/commands/remove.d.ts +3 -1
- package/dist/commands/remove.js +7 -1
- package/dist/commands/remove.js.map +1 -1
- package/dist/commands/rename.js +5 -0
- package/dist/commands/rename.js.map +1 -1
- package/dist/commands/show.d.ts +4 -2
- package/dist/commands/show.js +8 -2
- package/dist/commands/show.js.map +1 -1
- package/dist/commands/validate.js +3 -5
- package/dist/commands/validate.js.map +1 -1
- package/dist/containers/card-container.d.ts +7 -5
- package/dist/containers/card-container.js +30 -5
- package/dist/containers/card-container.js.map +1 -1
- package/dist/containers/project/project-paths.d.ts +2 -0
- package/dist/containers/project/project-paths.js +6 -0
- package/dist/containers/project/project-paths.js.map +1 -1
- package/dist/containers/project/resource-cache.js +9 -7
- package/dist/containers/project/resource-cache.js.map +1 -1
- package/dist/containers/project.d.ts +11 -2
- package/dist/containers/project.js +54 -8
- package/dist/containers/project.js.map +1 -1
- package/dist/containers/template.js +4 -4
- package/dist/containers/template.js.map +1 -1
- package/dist/interfaces/command-options.d.ts +3 -1
- package/dist/interfaces/project-interfaces.d.ts +5 -5
- package/dist/interfaces/project-interfaces.js.map +1 -1
- package/dist/project-settings.d.ts +5 -0
- package/dist/project-settings.js +12 -0
- package/dist/project-settings.js.map +1 -1
- package/dist/resources/resource-object.d.ts +1 -0
- package/dist/resources/resource-object.js +52 -1
- package/dist/resources/resource-object.js.map +1 -1
- package/dist/utils/configuration-logger.d.ts +91 -0
- package/dist/utils/configuration-logger.js +151 -0
- package/dist/utils/configuration-logger.js.map +1 -0
- package/dist/utils/constants.d.ts +1 -1
- package/dist/utils/constants.js +5 -3
- package/dist/utils/constants.js.map +1 -1
- package/package.json +4 -4
- package/src/command-handler.ts +17 -9
- package/src/command-manager.ts +4 -4
- package/src/commands/create.ts +1 -1
- package/src/commands/fetch.ts +143 -34
- package/src/commands/import.ts +13 -1
- package/src/commands/remove.ts +10 -1
- package/src/commands/rename.ts +15 -0
- package/src/commands/show.ts +11 -3
- package/src/commands/validate.ts +3 -7
- package/src/containers/card-container.ts +37 -5
- package/src/containers/project/project-paths.ts +8 -0
- package/src/containers/project/resource-cache.ts +12 -9
- package/src/containers/project.ts +76 -9
- package/src/containers/template.ts +4 -4
- package/src/interfaces/command-options.ts +3 -1
- package/src/interfaces/project-interfaces.ts +5 -5
- package/src/project-settings.ts +13 -0
- package/src/resources/resource-object.ts +73 -1
- package/src/utils/configuration-logger.ts +206 -0
- package/src/utils/constants.ts +5 -3
package/src/commands/fetch.ts
CHANGED
|
@@ -13,16 +13,28 @@
|
|
|
13
13
|
|
|
14
14
|
import { mkdir } from 'node:fs/promises';
|
|
15
15
|
import { resolve, sep } from 'node:path';
|
|
16
|
-
import type { Project } from '../containers/project.js';
|
|
17
16
|
|
|
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 } from '../utils/error-utils.js';
|
|
22
17
|
import { getChildLogger } from '../utils/log-utils.js';
|
|
18
|
+
import { readJsonFile, writeJsonFile } from '../utils/json.js';
|
|
19
|
+
import { validateJson } from '../utils/validate.js';
|
|
20
|
+
|
|
21
|
+
import type { ModuleSetting } from '../interfaces/project-interfaces.js';
|
|
22
|
+
import type { Project } from '../containers/project.js';
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
// Hub structure
|
|
25
|
+
interface HubVersionInfo {
|
|
26
|
+
location: string;
|
|
27
|
+
version: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Structure of .temp/moduleList.json file.
|
|
31
|
+
interface ModuleListFile {
|
|
32
|
+
modules: ModuleSetting[];
|
|
33
|
+
hubs: HubVersionInfo[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const FETCH_TIMEOUT_MS = 30 * 1000; // 30s timeout for fetching a hub file.
|
|
37
|
+
const MAX_RESPONSE_SIZE_MB = 1024 * 1024; // 1MB limit for safety
|
|
26
38
|
const HUB_SCHEMA = 'hubSchema';
|
|
27
39
|
const MODULE_LIST_FILE = 'moduleList.json';
|
|
28
40
|
const TEMP_FOLDER = `.temp`;
|
|
@@ -30,7 +42,10 @@ const TEMP_FOLDER = `.temp`;
|
|
|
30
42
|
export const MODULE_LIST_FULL_PATH = `${TEMP_FOLDER}/${MODULE_LIST_FILE}`;
|
|
31
43
|
|
|
32
44
|
export class Fetch {
|
|
33
|
-
|
|
45
|
+
private moduleListPath;
|
|
46
|
+
constructor(private project: Project) {
|
|
47
|
+
this.moduleListPath = resolve(this.project.basePath, MODULE_LIST_FULL_PATH);
|
|
48
|
+
}
|
|
34
49
|
|
|
35
50
|
private get logger() {
|
|
36
51
|
return getChildLogger({
|
|
@@ -38,6 +53,38 @@ export class Fetch {
|
|
|
38
53
|
});
|
|
39
54
|
}
|
|
40
55
|
|
|
56
|
+
// Checks the version of the remote moduleList.json.
|
|
57
|
+
private async checkRemoteVersion(
|
|
58
|
+
location: string,
|
|
59
|
+
): Promise<number | undefined> {
|
|
60
|
+
try {
|
|
61
|
+
const url = new URL(`${location}/${MODULE_LIST_FILE}`);
|
|
62
|
+
if (!['http:', 'https:'].includes(url.protocol)) {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const response = await fetch(url.toString(), {
|
|
67
|
+
method: 'GET',
|
|
68
|
+
headers: {
|
|
69
|
+
Accept: 'application/json',
|
|
70
|
+
'User-Agent': 'Cyberismo/1.0',
|
|
71
|
+
},
|
|
72
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
if (!response.ok) {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const json = await response.json();
|
|
80
|
+
return json.version;
|
|
81
|
+
} catch (error) {
|
|
82
|
+
this.logger.error(error, `Could not check hub version for ${location} }`);
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Fetches one hub's data as JSON.
|
|
41
88
|
private async fetchJSON(location: string, schemaId: string) {
|
|
42
89
|
try {
|
|
43
90
|
const url = new URL(`${location}/${MODULE_LIST_FILE}`);
|
|
@@ -47,14 +94,14 @@ export class Fetch {
|
|
|
47
94
|
);
|
|
48
95
|
}
|
|
49
96
|
|
|
50
|
-
this.logger.info(`Fetching module list from: ${url.toString()}`);
|
|
97
|
+
this.logger.info(`Fetching module list from hub: ${url.toString()}`);
|
|
51
98
|
const response = await fetch(url.toString(), {
|
|
52
99
|
method: 'GET',
|
|
53
100
|
headers: {
|
|
54
101
|
Accept: 'application/json',
|
|
55
102
|
'User-Agent': 'Cyberismo/1.0',
|
|
56
103
|
},
|
|
57
|
-
signal: AbortSignal.timeout(
|
|
104
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
58
105
|
});
|
|
59
106
|
|
|
60
107
|
if (!response.ok) {
|
|
@@ -65,9 +112,9 @@ export class Fetch {
|
|
|
65
112
|
|
|
66
113
|
// Check content length before downloading
|
|
67
114
|
const contentLength = response.headers.get('content-length');
|
|
68
|
-
if (contentLength && parseInt(contentLength) >
|
|
115
|
+
if (contentLength && parseInt(contentLength) > MAX_RESPONSE_SIZE_MB) {
|
|
69
116
|
throw new Error(
|
|
70
|
-
`Response too large: ${contentLength} bytes (max: ${
|
|
117
|
+
`Response too large: ${contentLength} bytes (max: ${MAX_RESPONSE_SIZE_MB})`,
|
|
71
118
|
);
|
|
72
119
|
}
|
|
73
120
|
|
|
@@ -79,14 +126,10 @@ export class Fetch {
|
|
|
79
126
|
const json = await response.json();
|
|
80
127
|
// Validate the incoming JSON before saving it into a file.
|
|
81
128
|
await validateJson(json, { schemaId: schemaId });
|
|
82
|
-
|
|
83
|
-
// Validate JSON structure and prevent prototype pollution
|
|
84
129
|
if (typeof json !== 'object' || json === null || Array.isArray(json)) {
|
|
85
130
|
throw new Error('Response must be a JSON object');
|
|
86
131
|
}
|
|
87
|
-
|
|
88
|
-
// Additional size check after JSON parsing
|
|
89
|
-
if (JSON.stringify(json).length > MAX_RESPONSE_SIZE) {
|
|
132
|
+
if (JSON.stringify(json).length > MAX_RESPONSE_SIZE_MB) {
|
|
90
133
|
throw new Error('JSON content too large after parsing');
|
|
91
134
|
}
|
|
92
135
|
|
|
@@ -94,41 +137,109 @@ export class Fetch {
|
|
|
94
137
|
} catch (error) {
|
|
95
138
|
this.logger.error(
|
|
96
139
|
error,
|
|
97
|
-
`Failed to fetch module list from ${location}
|
|
140
|
+
`Failed to fetch module list from hub ${location}`,
|
|
98
141
|
);
|
|
99
142
|
throw error;
|
|
100
143
|
}
|
|
101
144
|
}
|
|
102
145
|
|
|
146
|
+
// Checks if the local moduleList.json needs to be updated by comparing
|
|
147
|
+
// each hub's version with the stored version.
|
|
148
|
+
private async fetchModuleList(): Promise<boolean> {
|
|
149
|
+
try {
|
|
150
|
+
const configuredHubs = this.project.configuration.hubs;
|
|
151
|
+
if (configuredHubs.length === 0) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const localData = (await readJsonFile(
|
|
156
|
+
this.moduleListPath,
|
|
157
|
+
)) as ModuleListFile;
|
|
158
|
+
const localHubs = localData.hubs || [];
|
|
159
|
+
if (localHubs.length !== configuredHubs.length) {
|
|
160
|
+
this.logger.info('Hub configuration changed, fetching module list');
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Check each hub's version
|
|
165
|
+
for (const configHub of configuredHubs) {
|
|
166
|
+
const localHub = localHubs.find(
|
|
167
|
+
(hub) => hub.location === configHub.location,
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
if (!localHub) {
|
|
171
|
+
this.logger.info(
|
|
172
|
+
`New hub detected: ${configHub.location}, fetching module list`,
|
|
173
|
+
);
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const remoteVersion = await this.checkRemoteVersion(configHub.location);
|
|
178
|
+
if (remoteVersion === undefined) {
|
|
179
|
+
const hubName = configHub.displayName || configHub.location;
|
|
180
|
+
this.logger.info(`Hub ${hubName} has no version data, skipped.`);
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (remoteVersion > localHub.version) {
|
|
185
|
+
this.logger.info(
|
|
186
|
+
`Hub ${configHub.location} has newer version (remote: ${remoteVersion}, local: ${localHub.version}), fetching module list`,
|
|
187
|
+
);
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
this.logger.info('Module list is up to date');
|
|
193
|
+
return false;
|
|
194
|
+
} catch (error) {
|
|
195
|
+
this.logger.error(
|
|
196
|
+
error,
|
|
197
|
+
`Error when checking versions for hub module list`,
|
|
198
|
+
);
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Ensures the module list is up to date by fetching if needed.
|
|
205
|
+
*/
|
|
206
|
+
public async ensureModuleListUpToDate() {
|
|
207
|
+
await this.fetchHubs();
|
|
208
|
+
}
|
|
209
|
+
|
|
103
210
|
/**
|
|
104
211
|
* Fetches modules from modules hub(s) and writes them to a file.
|
|
212
|
+
* Only fetches if the remote version is newer than the local version.
|
|
105
213
|
*/
|
|
106
214
|
public async fetchHubs() {
|
|
107
|
-
const
|
|
215
|
+
const needsFetch = await this.fetchModuleList();
|
|
216
|
+
if (!needsFetch) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
108
219
|
|
|
220
|
+
const hubs = this.project.configuration.hubs;
|
|
109
221
|
const moduleMap: Map<string, ModuleSetting> = new Map([]);
|
|
222
|
+
const hubVersions: HubVersionInfo[] = [];
|
|
110
223
|
|
|
111
224
|
for (const hub of hubs) {
|
|
112
225
|
const json = await this.fetchJSON(hub.location, HUB_SCHEMA);
|
|
113
226
|
json.modules.forEach((module: ModuleSetting) => {
|
|
114
227
|
if (!moduleMap.has(module.name)) {
|
|
115
228
|
moduleMap.set(module.name, module);
|
|
116
|
-
} else {
|
|
117
|
-
this.logger.info(
|
|
118
|
-
`Skipping module '${module.name}' since it was already listed.`,
|
|
119
|
-
);
|
|
120
229
|
}
|
|
121
230
|
});
|
|
231
|
+
|
|
232
|
+
hubVersions.push({
|
|
233
|
+
location: hub.location,
|
|
234
|
+
version: json.version || 1,
|
|
235
|
+
});
|
|
122
236
|
}
|
|
123
237
|
|
|
124
238
|
try {
|
|
125
|
-
const fullPath = resolve(this.project.basePath, MODULE_LIST_FULL_PATH);
|
|
126
239
|
const normalizedBasePath = resolve(this.project.basePath);
|
|
127
|
-
|
|
128
|
-
// Ensure the file is written within the project directory (prevent path traversal)
|
|
129
240
|
if (
|
|
130
|
-
!
|
|
131
|
-
|
|
241
|
+
!this.moduleListPath.startsWith(normalizedBasePath + sep) &&
|
|
242
|
+
this.moduleListPath !== normalizedBasePath
|
|
132
243
|
) {
|
|
133
244
|
throw new Error(
|
|
134
245
|
'Invalid file path: attempting to write outside project directory',
|
|
@@ -138,15 +249,13 @@ export class Fetch {
|
|
|
138
249
|
await mkdir(resolve(this.project.basePath, TEMP_FOLDER), {
|
|
139
250
|
recursive: true,
|
|
140
251
|
});
|
|
141
|
-
await writeJsonFile(
|
|
252
|
+
await writeJsonFile(this.moduleListPath, {
|
|
142
253
|
modules: Array.from(moduleMap.values()),
|
|
254
|
+
hubs: hubVersions,
|
|
143
255
|
});
|
|
144
|
-
this.logger.info(`Module list written to: ${
|
|
256
|
+
this.logger.info(`Module list written to: ${this.moduleListPath}`);
|
|
145
257
|
} catch (error) {
|
|
146
|
-
this.logger.error(
|
|
147
|
-
error,
|
|
148
|
-
`Failed to write module list to local file: ${errorFunction(error)}`,
|
|
149
|
-
);
|
|
258
|
+
this.logger.error(error, `Failed to write module list to local file`);
|
|
150
259
|
throw error;
|
|
151
260
|
}
|
|
152
261
|
}
|
package/src/commands/import.ts
CHANGED
|
@@ -20,6 +20,7 @@ import type {
|
|
|
20
20
|
Credentials,
|
|
21
21
|
ModuleSettingOptions,
|
|
22
22
|
} from '../interfaces/project-interfaces.js';
|
|
23
|
+
import type { Fetch } from './fetch.js';
|
|
23
24
|
import type { Project } from '../containers/project.js';
|
|
24
25
|
|
|
25
26
|
/**
|
|
@@ -36,6 +37,7 @@ export class Import {
|
|
|
36
37
|
constructor(
|
|
37
38
|
private project: Project,
|
|
38
39
|
private createCmd: Create,
|
|
40
|
+
private fetchCmd: Fetch,
|
|
39
41
|
) {
|
|
40
42
|
this.moduleManager = new ModuleManager(this.project);
|
|
41
43
|
}
|
|
@@ -133,12 +135,17 @@ export class Import {
|
|
|
133
135
|
* @param options Additional options for module import. Optional.
|
|
134
136
|
* branch: Git branch for module from Git.
|
|
135
137
|
* private: If true, uses credentials to clone the repository
|
|
138
|
+
* @param skipMigrationLog If true, skip logging to migration log (used during project creation)
|
|
136
139
|
*/
|
|
137
140
|
public async importModule(
|
|
138
141
|
source: string,
|
|
139
142
|
destination?: string,
|
|
140
143
|
options?: ModuleSettingOptions,
|
|
144
|
+
skipMigrationLog = false,
|
|
141
145
|
) {
|
|
146
|
+
// Ensure module list is up to date before importing
|
|
147
|
+
await this.fetchCmd.ensureModuleListUpToDate();
|
|
148
|
+
|
|
142
149
|
const beforeImportValidateErrors = await Validate.getInstance().validate(
|
|
143
150
|
this.project.basePath,
|
|
144
151
|
() => this.project,
|
|
@@ -168,7 +175,7 @@ export class Import {
|
|
|
168
175
|
);
|
|
169
176
|
|
|
170
177
|
// Add module as a dependency.
|
|
171
|
-
await this.project.importModule(moduleSettings);
|
|
178
|
+
await this.project.importModule(moduleSettings, skipMigrationLog);
|
|
172
179
|
|
|
173
180
|
// Validate the project after module has been imported
|
|
174
181
|
const afterImportValidateErrors = await Validate.getInstance().validate(
|
|
@@ -189,6 +196,9 @@ export class Import {
|
|
|
189
196
|
* @throws if module is not part of the project
|
|
190
197
|
*/
|
|
191
198
|
public async updateModule(moduleName: string, credentials?: Credentials) {
|
|
199
|
+
// Ensure module list is up to date before updating
|
|
200
|
+
await this.fetchCmd.ensureModuleListUpToDate();
|
|
201
|
+
|
|
192
202
|
const module = this.project.configuration.modules.find(
|
|
193
203
|
(item) => item.name === moduleName,
|
|
194
204
|
);
|
|
@@ -203,6 +213,8 @@ export class Import {
|
|
|
203
213
|
* @param credentials Optional credentials for private modules.
|
|
204
214
|
*/
|
|
205
215
|
public async updateAllModules(credentials?: Credentials) {
|
|
216
|
+
// Ensure module list is up to date before updating all modules
|
|
217
|
+
await this.fetchCmd.ensureModuleListUpToDate();
|
|
206
218
|
return this.moduleManager.updateModules(credentials);
|
|
207
219
|
}
|
|
208
220
|
}
|
package/src/commands/remove.ts
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import { ActionGuard } from '../permissions/action-guard.js';
|
|
15
15
|
import { isModuleCard } from '../utils/card-utils.js';
|
|
16
16
|
import { ModuleManager } from '../module-manager.js';
|
|
17
|
+
import type { Fetch } from './fetch.js';
|
|
17
18
|
import type { Project } from '../containers/project.js';
|
|
18
19
|
import type { RemovableResourceTypes } from '../interfaces/project-interfaces.js';
|
|
19
20
|
|
|
@@ -26,7 +27,10 @@ export class Remove {
|
|
|
26
27
|
* Creates a new instance of Remove command.
|
|
27
28
|
* @param project Project instance to use
|
|
28
29
|
*/
|
|
29
|
-
constructor(
|
|
30
|
+
constructor(
|
|
31
|
+
private project: Project,
|
|
32
|
+
private fetchCmd: Fetch,
|
|
33
|
+
) {
|
|
30
34
|
this.moduleManager = new ModuleManager(this.project);
|
|
31
35
|
}
|
|
32
36
|
|
|
@@ -164,6 +168,11 @@ export class Remove {
|
|
|
164
168
|
targetName: string,
|
|
165
169
|
...rest: any[] // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
166
170
|
) {
|
|
171
|
+
// Ensure module list is up to date when removing modules
|
|
172
|
+
if (type === 'module') {
|
|
173
|
+
await this.fetchCmd.ensureModuleListUpToDate();
|
|
174
|
+
}
|
|
175
|
+
|
|
167
176
|
if (type === 'attachment' && rest.length !== 1 && !rest[0]) {
|
|
168
177
|
throw new Error(
|
|
169
178
|
`Input validation error: must pass argument 'detail' if requesting to remove attachment`,
|
package/src/commands/rename.ts
CHANGED
|
@@ -17,6 +17,10 @@ import { join } from 'node:path';
|
|
|
17
17
|
import { rename, readdir, readFile, writeFile } from 'node:fs/promises';
|
|
18
18
|
|
|
19
19
|
import type { Card } from '../interfaces/project-interfaces.js';
|
|
20
|
+
import {
|
|
21
|
+
ConfigurationLogger,
|
|
22
|
+
ConfigurationOperation,
|
|
23
|
+
} from '../utils/configuration-logger.js';
|
|
20
24
|
import { isTemplateCard } from '../utils/card-utils.js';
|
|
21
25
|
import { type Project, ResourcesFrom } from '../containers/project.js';
|
|
22
26
|
import { resourceName } from '../utils/resource-utils.js';
|
|
@@ -273,6 +277,17 @@ export class Rename {
|
|
|
273
277
|
this.project.resources.changed();
|
|
274
278
|
console.info('Collected renamed resources');
|
|
275
279
|
|
|
280
|
+
// Remove these when operations properly update card cache
|
|
281
|
+
this.project.cardsCache.clear();
|
|
282
|
+
await this.project.populateCaches();
|
|
283
|
+
|
|
284
|
+
await ConfigurationLogger.log(
|
|
285
|
+
this.project.basePath,
|
|
286
|
+
ConfigurationOperation.PROJECT_RENAME,
|
|
287
|
+
this.to,
|
|
288
|
+
{},
|
|
289
|
+
);
|
|
290
|
+
|
|
276
291
|
return this.project.calculationEngine.generate();
|
|
277
292
|
}
|
|
278
293
|
}
|
package/src/commands/show.ts
CHANGED
|
@@ -18,7 +18,7 @@ import { join, resolve } from 'node:path';
|
|
|
18
18
|
import { spawn } from 'node:child_process';
|
|
19
19
|
import { writeFile } from 'node:fs/promises';
|
|
20
20
|
|
|
21
|
-
import { MODULE_LIST_FULL_PATH } from './fetch.js';
|
|
21
|
+
import { type Fetch, MODULE_LIST_FULL_PATH } from './fetch.js';
|
|
22
22
|
|
|
23
23
|
import type { attachmentPayload } from '../interfaces/request-status-interfaces.js';
|
|
24
24
|
import type {
|
|
@@ -74,7 +74,10 @@ export class Show {
|
|
|
74
74
|
workflows: (from) => this.resourceNames('workflows', from),
|
|
75
75
|
};
|
|
76
76
|
|
|
77
|
-
constructor(
|
|
77
|
+
constructor(
|
|
78
|
+
private project: Project,
|
|
79
|
+
private fetchCmd: Fetch,
|
|
80
|
+
) {}
|
|
78
81
|
|
|
79
82
|
private get logger() {
|
|
80
83
|
return getChildLogger({
|
|
@@ -326,6 +329,9 @@ export class Show {
|
|
|
326
329
|
showDetails?: boolean,
|
|
327
330
|
): Promise<ModuleSettingFromHub[]> {
|
|
328
331
|
try {
|
|
332
|
+
// Ensure module list is up to date before showing
|
|
333
|
+
await this.fetchCmd.ensureModuleListUpToDate();
|
|
334
|
+
|
|
329
335
|
const moduleList = (
|
|
330
336
|
await readJsonFile(
|
|
331
337
|
resolve(this.project.basePath, MODULE_LIST_FULL_PATH),
|
|
@@ -399,7 +405,9 @@ export class Show {
|
|
|
399
405
|
* Shows hubs of the project.
|
|
400
406
|
* @returns list of hubs.
|
|
401
407
|
*/
|
|
402
|
-
public showHubs(): HubSetting[] {
|
|
408
|
+
public async showHubs(): Promise<HubSetting[]> {
|
|
409
|
+
// Ensure module list is up to date before showing
|
|
410
|
+
await this.fetchCmd.ensureModuleListUpToDate();
|
|
403
411
|
return this.project.configuration.hubs;
|
|
404
412
|
}
|
|
405
413
|
|
package/src/commands/validate.ts
CHANGED
|
@@ -222,7 +222,7 @@ export class Validate {
|
|
|
222
222
|
): Promise<string[]> {
|
|
223
223
|
const message: string[] = [];
|
|
224
224
|
try {
|
|
225
|
-
const prefixes = project.
|
|
225
|
+
const prefixes = project.allModulePrefixes();
|
|
226
226
|
const files = await readdir(path, {
|
|
227
227
|
withFileTypes: true,
|
|
228
228
|
});
|
|
@@ -554,7 +554,7 @@ export class Validate {
|
|
|
554
554
|
cards.push(...project.allTemplateCards());
|
|
555
555
|
|
|
556
556
|
const cardIds = new Map<string, number>();
|
|
557
|
-
const allPrefixes =
|
|
557
|
+
const allPrefixes = project.allModulePrefixes();
|
|
558
558
|
|
|
559
559
|
for (const card of cards) {
|
|
560
560
|
if (cardIds.has(card.key)) {
|
|
@@ -819,11 +819,7 @@ export class Validate {
|
|
|
819
819
|
|
|
820
820
|
// Validate that all metadata keys are either predefined fields or valid field type names
|
|
821
821
|
for (const key of Object.keys(card.metadata)) {
|
|
822
|
-
if (
|
|
823
|
-
(isPredefinedField(key) as boolean) ||
|
|
824
|
-
key === 'labels' ||
|
|
825
|
-
key === 'links'
|
|
826
|
-
) {
|
|
822
|
+
if (isPredefinedField(key) as boolean) {
|
|
827
823
|
continue;
|
|
828
824
|
}
|
|
829
825
|
try {
|
|
@@ -19,17 +19,19 @@ import { writeFile } from 'node:fs/promises';
|
|
|
19
19
|
import { CardCache } from './project/card-cache.js';
|
|
20
20
|
import { cardPathParts } from '../utils/card-utils.js';
|
|
21
21
|
import { deleteDir } from '../utils/file-utils.js';
|
|
22
|
+
import { getChildLogger } from '../utils/log-utils.js';
|
|
22
23
|
import { writeJsonFile } from '../utils/json.js';
|
|
23
24
|
|
|
24
25
|
import type {
|
|
25
26
|
CardAttachment,
|
|
26
27
|
Card,
|
|
28
|
+
CardMetadata,
|
|
27
29
|
FetchCardDetails,
|
|
28
30
|
} from '../interfaces/project-interfaces.js';
|
|
29
31
|
|
|
30
32
|
import asciidoctor from '@asciidoctor/core';
|
|
31
33
|
|
|
32
|
-
import { ROOT } from '../utils/constants.js';
|
|
34
|
+
import { isPredefinedField, ROOT } from '../utils/constants.js';
|
|
33
35
|
|
|
34
36
|
/**
|
|
35
37
|
* Card container base class. Used for both Project and Template.
|
|
@@ -38,17 +40,19 @@ import { ROOT } from '../utils/constants.js';
|
|
|
38
40
|
export class CardContainer {
|
|
39
41
|
public basePath: string;
|
|
40
42
|
protected cardCache: CardCache;
|
|
41
|
-
protected containerName: string;
|
|
42
43
|
protected prefix: string;
|
|
43
44
|
|
|
45
|
+
protected static get logger() {
|
|
46
|
+
return getChildLogger({ module: 'CardContainer' });
|
|
47
|
+
}
|
|
48
|
+
|
|
44
49
|
static cardContentFile = 'index.adoc';
|
|
45
50
|
static cardMetadataFile = 'index.json';
|
|
46
51
|
static projectConfigFileName = 'cardsConfig.json';
|
|
47
52
|
static schemaContentFile = '.schema';
|
|
48
53
|
|
|
49
|
-
constructor(path: string, prefix: string
|
|
54
|
+
constructor(path: string, prefix: string) {
|
|
50
55
|
this.basePath = path;
|
|
51
|
-
this.containerName = name;
|
|
52
56
|
this.prefix = prefix;
|
|
53
57
|
this.cardCache = new CardCache(this.prefix);
|
|
54
58
|
}
|
|
@@ -226,13 +230,41 @@ export class CardContainer {
|
|
|
226
230
|
if (card.metadata != null) {
|
|
227
231
|
const metadataFile = join(card.path, CardContainer.cardMetadataFile);
|
|
228
232
|
card.metadata!.lastUpdated = new Date().toISOString();
|
|
229
|
-
|
|
233
|
+
|
|
234
|
+
const sanitizedMetadata = CardContainer.sanitizeMetadata(card);
|
|
235
|
+
await writeJsonFile(metadataFile, sanitizedMetadata);
|
|
230
236
|
return this.cardCache.updateCardMetadata(card.key, card.metadata);
|
|
231
237
|
}
|
|
232
238
|
return false;
|
|
233
239
|
}
|
|
234
240
|
|
|
235
241
|
/**
|
|
242
|
+
* Removes non-metadata fields that should not be persisted.
|
|
243
|
+
*
|
|
244
|
+
* @param metadata The metadata object to sanitize
|
|
245
|
+
* @returns Clean metadata object with only valid metadata fields
|
|
246
|
+
*/
|
|
247
|
+
private static sanitizeMetadata(card: Card): CardMetadata {
|
|
248
|
+
const sanitized: Record<string, unknown> = {};
|
|
249
|
+
|
|
250
|
+
if (card.metadata) {
|
|
251
|
+
for (const [key, value] of Object.entries(card.metadata)) {
|
|
252
|
+
// Keys are not filtered out if they are: predefined, or field types
|
|
253
|
+
if (isPredefinedField(key) || key.includes('/')) {
|
|
254
|
+
sanitized[key] = value;
|
|
255
|
+
} else {
|
|
256
|
+
this.logger.warn(
|
|
257
|
+
`Card ${card.key} had extra metadata key ${key} with value ${value}. Key was removed`,
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
// Everything else is filtered out
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return sanitized as CardMetadata;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/*
|
|
236
268
|
* Show root cards from a given path.
|
|
237
269
|
* @param path The path to get cards from
|
|
238
270
|
* @returns an array of root-level cards (each with their children populated).
|
|
@@ -64,6 +64,10 @@ export class ProjectPaths {
|
|
|
64
64
|
return join(this.resourcesFolder, 'cardsConfig.json');
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
public get configurationChangesLog(): string {
|
|
68
|
+
return join(this.migrationLogFolder, 'current', 'migrationLog.jsonl');
|
|
69
|
+
}
|
|
70
|
+
|
|
67
71
|
public get fieldTypesFolder(): string {
|
|
68
72
|
return join(this.resourcesFolder, 'fieldTypes');
|
|
69
73
|
}
|
|
@@ -84,6 +88,10 @@ export class ProjectPaths {
|
|
|
84
88
|
return join(this.path, '.logs', 'cyberismo_data-handler.log');
|
|
85
89
|
}
|
|
86
90
|
|
|
91
|
+
public get migrationLogFolder(): string {
|
|
92
|
+
return join(this.resourcesFolder, 'migrations');
|
|
93
|
+
}
|
|
94
|
+
|
|
87
95
|
public get modulesFolder(): string {
|
|
88
96
|
return join(this.path, '.cards', 'modules');
|
|
89
97
|
}
|
|
@@ -194,15 +194,16 @@ export class ResourceCache {
|
|
|
194
194
|
}
|
|
195
195
|
|
|
196
196
|
// Collect all module resources from the filesystem
|
|
197
|
-
// Only collects modules that are registered in the project configuration
|
|
198
|
-
// todo: For future:
|
|
199
|
-
// Should it also try to collect what is in .local/modules and then log for disparities?
|
|
200
197
|
private collectModuleResources() {
|
|
201
198
|
try {
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
);
|
|
205
|
-
|
|
199
|
+
const moduleEntries = readdirSync(this.project.paths.modulesFolder, {
|
|
200
|
+
withFileTypes: true,
|
|
201
|
+
});
|
|
202
|
+
const moduleNames = moduleEntries
|
|
203
|
+
.filter((entry) => entry.isDirectory())
|
|
204
|
+
.map((entry) => entry.name);
|
|
205
|
+
|
|
206
|
+
if (moduleNames.length === 0) {
|
|
206
207
|
return;
|
|
207
208
|
}
|
|
208
209
|
|
|
@@ -218,13 +219,15 @@ export class ResourceCache {
|
|
|
218
219
|
'workflows',
|
|
219
220
|
];
|
|
220
221
|
|
|
221
|
-
for (const moduleName of
|
|
222
|
+
for (const moduleName of moduleNames) {
|
|
222
223
|
for (const type of resourceTypes) {
|
|
223
224
|
this.collectResourcesOfType(type, 'module', moduleName);
|
|
224
225
|
}
|
|
225
226
|
}
|
|
226
227
|
} catch {
|
|
227
|
-
ResourceCache.logger.
|
|
228
|
+
ResourceCache.logger.debug(
|
|
229
|
+
`.cards/modules folder is missing or inaccessible`,
|
|
230
|
+
);
|
|
228
231
|
}
|
|
229
232
|
}
|
|
230
233
|
|