@cyberismo/data-handler 0.0.13 → 0.0.14
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 +3 -1
- package/dist/command-handler.js.map +1 -1
- package/dist/commands/create.d.ts +3 -3
- package/dist/commands/create.js +7 -22
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/edit.d.ts +12 -11
- package/dist/commands/edit.js +41 -16
- package/dist/commands/edit.js.map +1 -1
- package/dist/commands/fetch.js +2 -1
- package/dist/commands/fetch.js.map +1 -1
- package/dist/commands/remove.js +6 -5
- package/dist/commands/remove.js.map +1 -1
- package/dist/commands/rename.d.ts +1 -0
- package/dist/commands/rename.js +11 -0
- package/dist/commands/rename.js.map +1 -1
- package/dist/commands/show.js +2 -12
- package/dist/commands/show.js.map +1 -1
- package/dist/commands/validate.js +1 -1
- package/dist/commands/validate.js.map +1 -1
- package/dist/containers/project/calculation-engine.js +17 -17
- package/dist/containers/project/calculation-engine.js.map +1 -1
- package/dist/containers/project.d.ts +2 -1
- package/dist/containers/project.js +5 -1
- package/dist/containers/project.js.map +1 -1
- package/dist/interfaces/folder-content-interfaces.d.ts +10 -4
- package/dist/interfaces/folder-content-interfaces.js +5 -3
- package/dist/interfaces/folder-content-interfaces.js.map +1 -1
- package/dist/interfaces/project-interfaces.d.ts +11 -9
- package/dist/interfaces/project-interfaces.js +10 -8
- package/dist/interfaces/project-interfaces.js.map +1 -1
- package/dist/interfaces/resource-interfaces.d.ts +10 -1
- package/dist/interfaces/resource-interfaces.js.map +1 -1
- package/dist/resources/calculation-resource.d.ts +71 -0
- package/dist/resources/calculation-resource.js +130 -0
- package/dist/resources/calculation-resource.js.map +1 -0
- package/dist/resources/create-defaults.d.ts +13 -6
- package/dist/resources/create-defaults.js +19 -5
- package/dist/resources/create-defaults.js.map +1 -1
- package/dist/resources/file-resource.js +9 -3
- package/dist/resources/file-resource.js.map +1 -1
- package/dist/resources/folder-resource.d.ts +2 -2
- package/dist/resources/folder-resource.js.map +1 -1
- package/dist/resources/resource-object.js +14 -2
- package/dist/resources/resource-object.js.map +1 -1
- package/dist/utils/constants.js +1 -0
- package/dist/utils/constants.js.map +1 -1
- package/dist/utils/error-utils.d.ts +34 -0
- package/dist/utils/error-utils.js +56 -0
- package/dist/utils/error-utils.js.map +1 -0
- package/dist/utils/log-utils.d.ts +0 -27
- package/dist/utils/log-utils.js +0 -58
- package/dist/utils/log-utils.js.map +1 -1
- package/dist/utils/user-preferences.js +6 -3
- package/dist/utils/user-preferences.js.map +1 -1
- package/package.json +2 -2
- package/src/command-handler.ts +3 -1
- package/src/commands/create.ts +10 -28
- package/src/commands/edit.ts +51 -26
- package/src/commands/fetch.ts +2 -1
- package/src/commands/remove.ts +3 -2
- package/src/commands/rename.ts +20 -0
- package/src/commands/show.ts +1 -13
- package/src/commands/validate.ts +1 -1
- package/src/containers/project/calculation-engine.ts +22 -20
- package/src/containers/project.ts +4 -1
- package/src/interfaces/folder-content-interfaces.ts +16 -4
- package/src/interfaces/project-interfaces.ts +12 -9
- package/src/interfaces/resource-interfaces.ts +10 -0
- package/src/resources/calculation-resource.ts +171 -0
- package/src/resources/create-defaults.ts +21 -5
- package/src/resources/file-resource.ts +9 -3
- package/src/resources/folder-resource.ts +2 -2
- package/src/resources/resource-object.ts +19 -7
- package/src/utils/constants.ts +1 -0
- package/src/utils/error-utils.ts +62 -0
- package/src/utils/log-utils.ts +0 -68
- package/src/utils/user-preferences.ts +7 -3
|
@@ -53,6 +53,7 @@ import {
|
|
|
53
53
|
import type { Template } from './template.js';
|
|
54
54
|
import { Validate } from '../commands/validate.js';
|
|
55
55
|
|
|
56
|
+
import { CalculationResource } from '../resources/calculation-resource.js';
|
|
56
57
|
import { CardTypeResource } from '../resources/card-type-resource.js';
|
|
57
58
|
import { FieldTypeResource } from '../resources/field-type-resource.js';
|
|
58
59
|
import { GraphModelResource } from '../resources/graph-model-resource.js';
|
|
@@ -977,7 +978,9 @@ export class Project extends CardContainer {
|
|
|
977
978
|
* @returns Created resource.
|
|
978
979
|
*/
|
|
979
980
|
public static resourceObject(project: Project, name: ResourceName) {
|
|
980
|
-
if (name.type === '
|
|
981
|
+
if (name.type === 'calculations') {
|
|
982
|
+
return new CalculationResource(project, name);
|
|
983
|
+
} else if (name.type === 'cardTypes') {
|
|
981
984
|
return new CardTypeResource(project, name);
|
|
982
985
|
} else if (name.type === 'fieldTypes') {
|
|
983
986
|
return new FieldTypeResource(project, name);
|
|
@@ -15,22 +15,32 @@ import type { Schema } from 'jsonschema';
|
|
|
15
15
|
|
|
16
16
|
// All file mappings for lookup (filename -> property name)
|
|
17
17
|
export const ALL_FILE_MAPPINGS = {
|
|
18
|
+
'calculation.lp': 'calculation',
|
|
18
19
|
'index.adoc.hbs': 'contentTemplate',
|
|
19
|
-
'query.lp.hbs': 'queryTemplate',
|
|
20
|
-
'parameterSchema.json': 'schema',
|
|
21
20
|
'model.lp': 'model',
|
|
21
|
+
'parameterSchema.json': 'schema',
|
|
22
|
+
'query.lp.hbs': 'queryTemplate',
|
|
22
23
|
'view.lp.hbs': 'viewTemplate',
|
|
23
24
|
} as const;
|
|
24
25
|
|
|
25
26
|
// Reverse mappings from property names to filenames
|
|
26
27
|
export const REVERSE_FILE_MAPPINGS = {
|
|
28
|
+
calculation: 'calculation.lp',
|
|
27
29
|
contentTemplate: 'index.adoc.hbs',
|
|
30
|
+
model: 'model.lp',
|
|
28
31
|
queryTemplate: 'query.lp.hbs',
|
|
29
32
|
schema: 'parameterSchema.json',
|
|
30
|
-
model: 'model.lp',
|
|
31
33
|
viewTemplate: 'view.lp.hbs',
|
|
32
34
|
} as const;
|
|
33
35
|
|
|
36
|
+
// Union type of all valid content property names
|
|
37
|
+
export type ContentPropertyName = keyof typeof REVERSE_FILE_MAPPINGS;
|
|
38
|
+
|
|
39
|
+
// Content interface for Calculation resources
|
|
40
|
+
export interface CalculationContent {
|
|
41
|
+
calculation: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
34
44
|
// Content interface for Graph Model resources
|
|
35
45
|
export interface GraphModelContent {
|
|
36
46
|
model?: string;
|
|
@@ -64,6 +74,8 @@ export function filename(propertyName: string): string | undefined {
|
|
|
64
74
|
* @param filename Filename.
|
|
65
75
|
* @returns property name that matches filename
|
|
66
76
|
*/
|
|
67
|
-
export function propertyName(
|
|
77
|
+
export function propertyName(
|
|
78
|
+
filename: string,
|
|
79
|
+
): ContentPropertyName | undefined {
|
|
68
80
|
return ALL_FILE_MAPPINGS[filename as keyof typeof ALL_FILE_MAPPINGS];
|
|
69
81
|
}
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
2
|
+
Cyberismo
|
|
3
|
+
Copyright © Cyberismo Ltd and contributors 2024
|
|
4
|
+
|
|
5
|
+
This program is free software: you can redistribute it and/or modify it under
|
|
6
|
+
the terms of the GNU Affero General Public License version 3 as published by
|
|
7
|
+
the Free Software Foundation. This program is distributed in the hope that it
|
|
8
|
+
will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
|
9
|
+
of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
10
|
+
See the GNU Affero General Public License for more details.
|
|
11
|
+
You should have received a copy of the GNU Affero General Public
|
|
12
|
+
License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
11
13
|
*/
|
|
12
14
|
|
|
13
15
|
import type { Link, TemplateConfiguration } from './resource-interfaces.js';
|
|
@@ -205,6 +207,7 @@ export interface ModuleSettingOptions {
|
|
|
205
207
|
// Resources that are possible to remove.
|
|
206
208
|
export type RemovableResourceTypes =
|
|
207
209
|
| 'attachment'
|
|
210
|
+
| 'calculation'
|
|
208
211
|
| 'card'
|
|
209
212
|
| 'cardType'
|
|
210
213
|
| 'fieldType'
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import type {
|
|
15
|
+
CalculationContent,
|
|
15
16
|
GraphModelContent,
|
|
16
17
|
GraphViewContent,
|
|
17
18
|
ReportContent,
|
|
@@ -27,6 +28,15 @@ import type {
|
|
|
27
28
|
export interface CalculationMetadata extends ResourceBaseMetadata {
|
|
28
29
|
calculation: string;
|
|
29
30
|
}
|
|
31
|
+
export interface Calculation extends CalculationMetadata {
|
|
32
|
+
content: CalculationContent;
|
|
33
|
+
}
|
|
34
|
+
export type CalculationContentPropertyName = 'calculation';
|
|
35
|
+
export interface CalculationContentUpdateKey {
|
|
36
|
+
key: 'content';
|
|
37
|
+
subKey: CalculationContentPropertyName;
|
|
38
|
+
}
|
|
39
|
+
export type CalculationUpdateKey = string | CalculationContentUpdateKey;
|
|
30
40
|
|
|
31
41
|
// Card type content.
|
|
32
42
|
export interface CardType extends ResourceBaseMetadata {
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Cyberismo
|
|
3
|
+
Copyright © Cyberismo Ltd and contributors 2025
|
|
4
|
+
|
|
5
|
+
This program is free software: you can redistribute it and/or modify it under
|
|
6
|
+
the terms of the GNU Affero General Public License version 3 as published by
|
|
7
|
+
the Free Software Foundation. This program is distributed in the hope that it
|
|
8
|
+
will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
|
9
|
+
of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
10
|
+
See the GNU Affero General Public License for more details.
|
|
11
|
+
You should have received a copy of the GNU Affero General Public
|
|
12
|
+
License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { join } from 'node:path';
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
DefaultContent,
|
|
19
|
+
FolderResource,
|
|
20
|
+
resourceNameToString,
|
|
21
|
+
sortCards,
|
|
22
|
+
} from './folder-resource.js';
|
|
23
|
+
import { writeFileSafe } from '../utils/file-utils.js';
|
|
24
|
+
|
|
25
|
+
import type {
|
|
26
|
+
Calculation,
|
|
27
|
+
CalculationMetadata,
|
|
28
|
+
CalculationUpdateKey,
|
|
29
|
+
} from '../interfaces/resource-interfaces.js';
|
|
30
|
+
import type { CalculationContent } from '../interfaces/folder-content-interfaces.js';
|
|
31
|
+
import type {
|
|
32
|
+
Card,
|
|
33
|
+
Operation,
|
|
34
|
+
Project,
|
|
35
|
+
ResourceName,
|
|
36
|
+
} from './file-resource.js';
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Calculation resource class.
|
|
40
|
+
*/
|
|
41
|
+
export class CalculationResource extends FolderResource {
|
|
42
|
+
private calculationsFile = 'calculation.lp';
|
|
43
|
+
constructor(project: Project, name: ResourceName) {
|
|
44
|
+
super(project, name, 'calculations');
|
|
45
|
+
|
|
46
|
+
this.contentSchemaId = 'calculationSchema';
|
|
47
|
+
this.contentSchema = super.contentSchemaContent(this.contentSchemaId);
|
|
48
|
+
|
|
49
|
+
this.initialize();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// When resource name changes
|
|
53
|
+
protected async onNameChange(existingName: string) {
|
|
54
|
+
await super.updateCalculations(existingName, this.content.name);
|
|
55
|
+
await this.write();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Creates a new calculation object and file.
|
|
60
|
+
* @param newContent Content for the calculation.
|
|
61
|
+
*/
|
|
62
|
+
public async create(newContent?: CalculationMetadata) {
|
|
63
|
+
if (!newContent) {
|
|
64
|
+
newContent = DefaultContent.calculation(
|
|
65
|
+
resourceNameToString(this.resourceName),
|
|
66
|
+
);
|
|
67
|
+
} else {
|
|
68
|
+
await this.validate(newContent);
|
|
69
|
+
}
|
|
70
|
+
await super.create(newContent);
|
|
71
|
+
|
|
72
|
+
const calculationsFile = join(this.internalFolder, this.calculationsFile);
|
|
73
|
+
await writeFileSafe(
|
|
74
|
+
calculationsFile,
|
|
75
|
+
`% add your calculations here for '${this.resourceName.identifier}'`,
|
|
76
|
+
{
|
|
77
|
+
flag: 'wx',
|
|
78
|
+
},
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Returns content data.
|
|
84
|
+
*/
|
|
85
|
+
public get data(): CalculationMetadata {
|
|
86
|
+
return super.data as CalculationMetadata;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Deletes files from disk and clears out the memory resident object.
|
|
91
|
+
*/
|
|
92
|
+
public async delete() {
|
|
93
|
+
await super.delete();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Renames resource metadata file and renames memory resident object 'name'.
|
|
98
|
+
* @param newName New name for the resource.
|
|
99
|
+
*/
|
|
100
|
+
public async rename(newName: ResourceName) {
|
|
101
|
+
const existingName = this.content.name;
|
|
102
|
+
await super.rename(newName);
|
|
103
|
+
return this.onNameChange(existingName);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Shows metadata of the resource.
|
|
108
|
+
* @returns calculation metadata.
|
|
109
|
+
*/
|
|
110
|
+
public async show(): Promise<Calculation> {
|
|
111
|
+
const baseData = (await super.show()) as CalculationMetadata;
|
|
112
|
+
const fileContents = await super.contentData();
|
|
113
|
+
const content: CalculationContent = {
|
|
114
|
+
calculation: fileContents.calculation as string,
|
|
115
|
+
};
|
|
116
|
+
return {
|
|
117
|
+
...baseData,
|
|
118
|
+
content: content,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Updates calculation resource.
|
|
124
|
+
* @template Type The type of the operation being operated on for the given key.
|
|
125
|
+
* @param key Key to modify
|
|
126
|
+
* @param op Operation to perform on 'key'
|
|
127
|
+
* @example
|
|
128
|
+
* // Update the description
|
|
129
|
+
* await calculation.update('description', { name: 'change', to: 'New description' });
|
|
130
|
+
* await calculation.update({ key: 'content', subKey: 'calculation' }, { name: 'change', to: 'new content' });
|
|
131
|
+
*/
|
|
132
|
+
public async update<Type>(key: CalculationUpdateKey, op: Operation<Type>) {
|
|
133
|
+
if (
|
|
134
|
+
typeof key === 'object' &&
|
|
135
|
+
key.key === 'content' &&
|
|
136
|
+
key.subKey === 'calculation'
|
|
137
|
+
) {
|
|
138
|
+
const calculationContent = super.handleScalar(op) as string;
|
|
139
|
+
await this.updateFile(this.calculationsFile, calculationContent);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
await super.update(key, op);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* List where calculation resource is used in cards, or other calculation resources.
|
|
147
|
+
* Always returns card key references first, then calculation references.
|
|
148
|
+
*
|
|
149
|
+
* @param cards Optional. Check these cards for usage of this resource. If undefined, will check all cards.
|
|
150
|
+
* @returns array of card keys and calculation filenames that refer this resource.
|
|
151
|
+
*/
|
|
152
|
+
public async usage(cards?: Card[]): Promise<string[]> {
|
|
153
|
+
const allCards = cards || (await super.cards());
|
|
154
|
+
|
|
155
|
+
const [cardContentReferences, calculations] = await Promise.all([
|
|
156
|
+
super.usage(allCards),
|
|
157
|
+
super.calculations(),
|
|
158
|
+
]);
|
|
159
|
+
|
|
160
|
+
const cardReferences = cardContentReferences.sort(sortCards);
|
|
161
|
+
return [...new Set([...cardReferences, ...calculations])];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Validates the resource. If object is invalid, throws.
|
|
166
|
+
* @param content Content to validate
|
|
167
|
+
*/
|
|
168
|
+
public async validate(content?: object) {
|
|
169
|
+
return super.validate(content);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
Cyberismo
|
|
3
3
|
Copyright © Cyberismo Ltd and contributors 2024
|
|
4
|
+
|
|
4
5
|
This program is free software: you can redistribute it and/or modify it under
|
|
5
6
|
the terms of the GNU Affero General Public License version 3 as published by
|
|
6
|
-
the Free Software Foundation.
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
the Free Software Foundation. This program is distributed in the hope that it
|
|
8
|
+
will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
|
9
|
+
of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
10
|
+
See the GNU Affero General Public License for more details.
|
|
11
|
+
You should have received a copy of the GNU Affero General Public
|
|
11
12
|
License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
12
13
|
*/
|
|
13
14
|
|
|
14
15
|
import type { Card } from '../interfaces/project-interfaces.js';
|
|
15
16
|
import type {
|
|
17
|
+
CalculationMetadata,
|
|
16
18
|
CardType,
|
|
17
19
|
DataType,
|
|
18
20
|
FieldType,
|
|
@@ -70,6 +72,20 @@ export abstract class DefaultContent {
|
|
|
70
72
|
);
|
|
71
73
|
}
|
|
72
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Default content for calculation.
|
|
77
|
+
* @param calculationName calculation name
|
|
78
|
+
* @returns Default content for calculation.
|
|
79
|
+
*/
|
|
80
|
+
static calculation(calculationName: string): CalculationMetadata {
|
|
81
|
+
return {
|
|
82
|
+
name: calculationName,
|
|
83
|
+
displayName: '',
|
|
84
|
+
description: undefined,
|
|
85
|
+
calculation: 'calculation.lp',
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
73
89
|
/**
|
|
74
90
|
* Default content for card type.
|
|
75
91
|
* @param cardTypeName card type name
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
} from './resource-object.js';
|
|
31
31
|
import { DefaultContent } from './create-defaults.js';
|
|
32
32
|
import { deleteFile, pathExists } from '../utils/file-utils.js';
|
|
33
|
+
import { hasCode } from '../utils/error-utils.js';
|
|
33
34
|
import { Project, ResourcesFrom } from '../containers/project.js';
|
|
34
35
|
import {
|
|
35
36
|
readJsonFile,
|
|
@@ -242,6 +243,11 @@ export class FileResource extends ResourceObject {
|
|
|
242
243
|
references.push(calculation.name);
|
|
243
244
|
}
|
|
244
245
|
} catch (error) {
|
|
246
|
+
// Skip files that don't exist (they may have been renamed or deleted)
|
|
247
|
+
if (hasCode(error) && error.code === 'ENOENT') {
|
|
248
|
+
this.logger.warn(`Skipping non-existent file: ${filename}`);
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
245
251
|
throw new Error(
|
|
246
252
|
`Failed to process file ${filename}: ${(error as Error).message}`,
|
|
247
253
|
);
|
|
@@ -397,9 +403,9 @@ export class FileResource extends ResourceObject {
|
|
|
397
403
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
398
404
|
_op: Operation<Type>,
|
|
399
405
|
): Promise<void> {
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
throw new Error(`Resource '${
|
|
406
|
+
if (!this.data) {
|
|
407
|
+
const name = resourceNameToString(this.resourceName);
|
|
408
|
+
throw new Error(`Resource '${name}' does not exist in the project`);
|
|
403
409
|
}
|
|
404
410
|
if (this.moduleResource) {
|
|
405
411
|
throw new Error(`Cannot update module resources`);
|
|
@@ -216,7 +216,7 @@ export class FolderResource extends FileResource {
|
|
|
216
216
|
* Updates content files from a content object.
|
|
217
217
|
* @param contentFiles Object with file names as keys and file contents as values.
|
|
218
218
|
*/
|
|
219
|
-
|
|
219
|
+
protected async updateContentFiles(contentFiles: Record<string, string>) {
|
|
220
220
|
for (const [fileName, fileContent] of Object.entries(contentFiles)) {
|
|
221
221
|
await this.updateFile(fileName, fileContent);
|
|
222
222
|
}
|
|
@@ -227,7 +227,7 @@ export class FolderResource extends FileResource {
|
|
|
227
227
|
* @param fileName The name of the file to update.
|
|
228
228
|
* @param changedContent The new content for the file.
|
|
229
229
|
*/
|
|
230
|
-
|
|
230
|
+
protected async updateFile(fileName: string, changedContent: string) {
|
|
231
231
|
const filePath = join(this.internalFolder, fileName);
|
|
232
232
|
|
|
233
233
|
// Do not allow updating file in other directories
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
import { readFile, writeFile } from 'node:fs/promises';
|
|
17
17
|
import { basename, join } from 'node:path';
|
|
18
18
|
|
|
19
|
+
import { hasCode } from '../utils/error-utils.js';
|
|
20
|
+
|
|
19
21
|
import { ArrayHandler } from './array-handler.js';
|
|
20
22
|
import type {
|
|
21
23
|
Card,
|
|
@@ -222,19 +224,29 @@ export class ResourceObject extends AbstractResource {
|
|
|
222
224
|
);
|
|
223
225
|
}
|
|
224
226
|
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
227
|
+
const base = basename(calculation.name);
|
|
228
|
+
const fileNameWithExtension = base.endsWith('.lp')
|
|
229
|
+
? base
|
|
230
|
+
: base + '.lp';
|
|
231
|
+
const filename = join(calculation.path, fileNameWithExtension);
|
|
229
232
|
|
|
230
233
|
try {
|
|
231
234
|
const content = await readFile(filename, 'utf-8');
|
|
232
235
|
const updatedContent = content.replaceAll(from, to);
|
|
233
236
|
await writeFile(filename, updatedContent);
|
|
234
237
|
} catch (error) {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
+
if (hasCode(error) && error.code === 'ENOENT') {
|
|
239
|
+
// Skip files that don't exist (they may have been renamed or deleted)
|
|
240
|
+
this.getLogger(this.getType).warn(
|
|
241
|
+
`Skipping non-existent file: ${filename}`,
|
|
242
|
+
);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
if (error instanceof Error) {
|
|
246
|
+
throw new Error(
|
|
247
|
+
`Failed to process file while updating calculation ${filename}: ${error.message}`,
|
|
248
|
+
);
|
|
249
|
+
}
|
|
238
250
|
}
|
|
239
251
|
}),
|
|
240
252
|
);
|
package/src/utils/constants.ts
CHANGED
|
@@ -0,0 +1,62 @@
|
|
|
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. This program is distributed in the hope that it
|
|
7
|
+
will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
|
8
|
+
of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
9
|
+
See the GNU Affero General Public License for more details.
|
|
10
|
+
You should have received a copy of the GNU Affero General Public
|
|
11
|
+
License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Returns error message string from an Error object.
|
|
16
|
+
* @param error Error object
|
|
17
|
+
* @returns Error message.
|
|
18
|
+
*/
|
|
19
|
+
export function errorFunction(error: unknown): string {
|
|
20
|
+
if (error instanceof Error) {
|
|
21
|
+
const err: Error = error;
|
|
22
|
+
return errorMessage(`${err.message}`);
|
|
23
|
+
} else if (typeof error === 'string') {
|
|
24
|
+
return errorMessage(`${error}`);
|
|
25
|
+
} else {
|
|
26
|
+
return `errorFunction called without an error object. Actual object is ${JSON.stringify(error)}`;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Same as 'errorFunction' but can do automatic replacement of the error message string.
|
|
32
|
+
* @param message Error message
|
|
33
|
+
* @param toReplace replacement substring
|
|
34
|
+
* @param replaceWith string that 'toReplace' is replaced with.
|
|
35
|
+
* @returns Modified error message.
|
|
36
|
+
*/
|
|
37
|
+
export function errorMessage(
|
|
38
|
+
message: string,
|
|
39
|
+
toReplace?: string,
|
|
40
|
+
replaceWith?: string,
|
|
41
|
+
): string {
|
|
42
|
+
let errorMessage = message;
|
|
43
|
+
if (toReplace && replaceWith) {
|
|
44
|
+
errorMessage = message.replace(toReplace, replaceWith);
|
|
45
|
+
}
|
|
46
|
+
return `${errorMessage}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Type guard to check if an error object has a code property.
|
|
51
|
+
* @param error The error object to check
|
|
52
|
+
* @returns true if the error has a code property
|
|
53
|
+
*/
|
|
54
|
+
export function hasCode(error: unknown): error is Error & { code: string } {
|
|
55
|
+
return (
|
|
56
|
+
error instanceof Error &&
|
|
57
|
+
typeof error === 'object' &&
|
|
58
|
+
error !== null &&
|
|
59
|
+
'code' in error &&
|
|
60
|
+
typeof error.code === 'string'
|
|
61
|
+
);
|
|
62
|
+
}
|
package/src/utils/log-utils.ts
CHANGED
|
@@ -36,71 +36,3 @@ export function getChildLogger(
|
|
|
36
36
|
): Logger {
|
|
37
37
|
return _logger.child(context, options);
|
|
38
38
|
}
|
|
39
|
-
/**
|
|
40
|
-
* Returns error message string from an Error object.
|
|
41
|
-
* @param error Error object
|
|
42
|
-
* @returns Error message.
|
|
43
|
-
*/
|
|
44
|
-
export function errorFunction(error: unknown): string {
|
|
45
|
-
if (error instanceof Error) {
|
|
46
|
-
const err: Error = error;
|
|
47
|
-
return errorMessage(`${err.message}`);
|
|
48
|
-
} else if (typeof error === 'string') {
|
|
49
|
-
return errorMessage(`${error}`);
|
|
50
|
-
} else {
|
|
51
|
-
return `${logError.name} called without an error object. Actual object is ${JSON.stringify(error)}`;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Same as 'errorFunction' but can do automatic replacement fof the error message string.
|
|
57
|
-
* @param message Error message
|
|
58
|
-
* @param toReplace replacement substring
|
|
59
|
-
* @param replaceWith string that 'toReplace' is replaced with.
|
|
60
|
-
* @returns Modified error message.
|
|
61
|
-
*/
|
|
62
|
-
export function errorMessage(
|
|
63
|
-
message: string,
|
|
64
|
-
toReplace?: string,
|
|
65
|
-
replaceWith?: string,
|
|
66
|
-
): string {
|
|
67
|
-
let errorMessage = message;
|
|
68
|
-
if (toReplace && replaceWith) {
|
|
69
|
-
errorMessage = message.replace(toReplace, replaceWith);
|
|
70
|
-
}
|
|
71
|
-
return `${errorMessage}`;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Logs error from Error object.
|
|
76
|
-
* @param error potentially an Error object. When exceptions are raised, they are typically Error objects.
|
|
77
|
-
*/
|
|
78
|
-
export function logError(error: unknown) {
|
|
79
|
-
if (error instanceof Error) {
|
|
80
|
-
const err: Error = error;
|
|
81
|
-
logErrorMessage(`${err.message}`);
|
|
82
|
-
} else {
|
|
83
|
-
console.error(
|
|
84
|
-
`${logError.name} called without an error object. Actual object is ${JSON.stringify(error)}`,
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Log error message in RED. Certain parts of messages can be replaced.
|
|
91
|
-
* This is useful, if including a message from external sources, and want to reduce the verbosity of the message.
|
|
92
|
-
* @param message Error message to log.
|
|
93
|
-
* @param toReplace String to look for.
|
|
94
|
-
* @param replaceWith Replace 'toReplace' with this. Only replaces first instance of 'toReplace'.
|
|
95
|
-
*/
|
|
96
|
-
export function logErrorMessage(
|
|
97
|
-
message: string,
|
|
98
|
-
toReplace?: string,
|
|
99
|
-
replaceWith?: string,
|
|
100
|
-
) {
|
|
101
|
-
let errorMessage = message;
|
|
102
|
-
if (toReplace && replaceWith) {
|
|
103
|
-
errorMessage = message.replace(toReplace, replaceWith);
|
|
104
|
-
}
|
|
105
|
-
console.error(`${errorMessage}`);
|
|
106
|
-
}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import { dirname } from 'node:path';
|
|
13
13
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
14
14
|
|
|
15
|
+
import { hasCode } from './error-utils.js';
|
|
15
16
|
import { formatJson } from './json.js';
|
|
16
17
|
import { getChildLogger } from '../utils/log-utils.js';
|
|
17
18
|
|
|
@@ -111,16 +112,19 @@ export class UserPreferences {
|
|
|
111
112
|
flag: 'wx',
|
|
112
113
|
});
|
|
113
114
|
} catch (error) {
|
|
114
|
-
if (error
|
|
115
|
-
const err = error as NodeJS.ErrnoException;
|
|
115
|
+
if (hasCode(error)) {
|
|
116
116
|
// If file already exists (EEXIST), that's fine - we'll use the existing file
|
|
117
|
-
if (
|
|
117
|
+
if (error.code !== 'EEXIST') {
|
|
118
118
|
throw new Error(
|
|
119
119
|
`Error creating preferences file '${this.prefsFilePath}': ${error}`,
|
|
120
120
|
);
|
|
121
121
|
} else {
|
|
122
122
|
this.logger.warn('Preferences file already exists');
|
|
123
123
|
}
|
|
124
|
+
} else {
|
|
125
|
+
throw new Error(
|
|
126
|
+
`Error creating preferences file '${this.prefsFilePath}': ${error}`,
|
|
127
|
+
);
|
|
124
128
|
}
|
|
125
129
|
}
|
|
126
130
|
}
|