@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.
Files changed (77) hide show
  1. package/dist/command-handler.js +3 -1
  2. package/dist/command-handler.js.map +1 -1
  3. package/dist/commands/create.d.ts +3 -3
  4. package/dist/commands/create.js +7 -22
  5. package/dist/commands/create.js.map +1 -1
  6. package/dist/commands/edit.d.ts +12 -11
  7. package/dist/commands/edit.js +41 -16
  8. package/dist/commands/edit.js.map +1 -1
  9. package/dist/commands/fetch.js +2 -1
  10. package/dist/commands/fetch.js.map +1 -1
  11. package/dist/commands/remove.js +6 -5
  12. package/dist/commands/remove.js.map +1 -1
  13. package/dist/commands/rename.d.ts +1 -0
  14. package/dist/commands/rename.js +11 -0
  15. package/dist/commands/rename.js.map +1 -1
  16. package/dist/commands/show.js +2 -12
  17. package/dist/commands/show.js.map +1 -1
  18. package/dist/commands/validate.js +1 -1
  19. package/dist/commands/validate.js.map +1 -1
  20. package/dist/containers/project/calculation-engine.js +17 -17
  21. package/dist/containers/project/calculation-engine.js.map +1 -1
  22. package/dist/containers/project.d.ts +2 -1
  23. package/dist/containers/project.js +5 -1
  24. package/dist/containers/project.js.map +1 -1
  25. package/dist/interfaces/folder-content-interfaces.d.ts +10 -4
  26. package/dist/interfaces/folder-content-interfaces.js +5 -3
  27. package/dist/interfaces/folder-content-interfaces.js.map +1 -1
  28. package/dist/interfaces/project-interfaces.d.ts +11 -9
  29. package/dist/interfaces/project-interfaces.js +10 -8
  30. package/dist/interfaces/project-interfaces.js.map +1 -1
  31. package/dist/interfaces/resource-interfaces.d.ts +10 -1
  32. package/dist/interfaces/resource-interfaces.js.map +1 -1
  33. package/dist/resources/calculation-resource.d.ts +71 -0
  34. package/dist/resources/calculation-resource.js +130 -0
  35. package/dist/resources/calculation-resource.js.map +1 -0
  36. package/dist/resources/create-defaults.d.ts +13 -6
  37. package/dist/resources/create-defaults.js +19 -5
  38. package/dist/resources/create-defaults.js.map +1 -1
  39. package/dist/resources/file-resource.js +9 -3
  40. package/dist/resources/file-resource.js.map +1 -1
  41. package/dist/resources/folder-resource.d.ts +2 -2
  42. package/dist/resources/folder-resource.js.map +1 -1
  43. package/dist/resources/resource-object.js +14 -2
  44. package/dist/resources/resource-object.js.map +1 -1
  45. package/dist/utils/constants.js +1 -0
  46. package/dist/utils/constants.js.map +1 -1
  47. package/dist/utils/error-utils.d.ts +34 -0
  48. package/dist/utils/error-utils.js +56 -0
  49. package/dist/utils/error-utils.js.map +1 -0
  50. package/dist/utils/log-utils.d.ts +0 -27
  51. package/dist/utils/log-utils.js +0 -58
  52. package/dist/utils/log-utils.js.map +1 -1
  53. package/dist/utils/user-preferences.js +6 -3
  54. package/dist/utils/user-preferences.js.map +1 -1
  55. package/package.json +2 -2
  56. package/src/command-handler.ts +3 -1
  57. package/src/commands/create.ts +10 -28
  58. package/src/commands/edit.ts +51 -26
  59. package/src/commands/fetch.ts +2 -1
  60. package/src/commands/remove.ts +3 -2
  61. package/src/commands/rename.ts +20 -0
  62. package/src/commands/show.ts +1 -13
  63. package/src/commands/validate.ts +1 -1
  64. package/src/containers/project/calculation-engine.ts +22 -20
  65. package/src/containers/project.ts +4 -1
  66. package/src/interfaces/folder-content-interfaces.ts +16 -4
  67. package/src/interfaces/project-interfaces.ts +12 -9
  68. package/src/interfaces/resource-interfaces.ts +10 -0
  69. package/src/resources/calculation-resource.ts +171 -0
  70. package/src/resources/create-defaults.ts +21 -5
  71. package/src/resources/file-resource.ts +9 -3
  72. package/src/resources/folder-resource.ts +2 -2
  73. package/src/resources/resource-object.ts +19 -7
  74. package/src/utils/constants.ts +1 -0
  75. package/src/utils/error-utils.ts +62 -0
  76. package/src/utils/log-utils.ts +0 -68
  77. 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 === 'cardTypes') {
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(filename: string): string | undefined {
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
- Cyberismo
3
- Copyright © Cyberismo Ltd and contributors 2024
4
-
5
- This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License version 3 as published by the Free Software Foundation.
6
-
7
- This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
8
-
9
- You should have received a copy of the GNU Affero General Public
10
- License along with this program. If not, see <https://www.gnu.org/licenses/>.
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
- 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
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
- const content = this.data;
401
- if (!content) {
402
- throw new Error(`Resource '${this.fileName}' does not exist`);
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
- public async updateContentFiles(contentFiles: Record<string, string>) {
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
- public async updateFile(fileName: string, changedContent: string) {
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 filename = join(
226
- calculation.path,
227
- basename(calculation.name) + '.lp',
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
- throw new Error(
236
- `Failed to process file ${filename}: ${(error as Error).message}`,
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
  );
@@ -29,6 +29,7 @@ export const VALID_FOLDER_RESOURCE_FILES = [
29
29
  'parameterSchema.json',
30
30
  'view.lp.hbs',
31
31
  'model.lp',
32
+ 'calculation.lp',
32
33
  ];
33
34
 
34
35
  /**
@@ -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
+ }
@@ -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 instanceof 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 (err?.code !== 'EEXIST') {
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
  }