@pipelab/core-node 1.0.0-beta.0

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 (48) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/LICENSE +110 -0
  3. package/README.md +10 -0
  4. package/dist/index.d.mts +416 -0
  5. package/dist/index.d.mts.map +1 -0
  6. package/dist/index.mjs +52612 -0
  7. package/dist/index.mjs.map +1 -0
  8. package/package.json +64 -0
  9. package/src/api.ts +115 -0
  10. package/src/config.ts +105 -0
  11. package/src/context.ts +68 -0
  12. package/src/handler-func.ts +234 -0
  13. package/src/handlers/agents.ts +32 -0
  14. package/src/handlers/auth.ts +95 -0
  15. package/src/handlers/build-history.ts +381 -0
  16. package/src/handlers/config.ts +109 -0
  17. package/src/handlers/engine.ts +229 -0
  18. package/src/handlers/fs.ts +97 -0
  19. package/src/handlers/history.ts +329 -0
  20. package/src/handlers/index.ts +41 -0
  21. package/src/handlers/shell.ts +57 -0
  22. package/src/handlers/system.ts +18 -0
  23. package/src/handlers.ts +2 -0
  24. package/src/heavy.ts +4 -0
  25. package/src/index.ts +16 -0
  26. package/src/ipc-core.ts +77 -0
  27. package/src/migrations.ts +72 -0
  28. package/src/paths.ts +1 -0
  29. package/src/plugins-registry.ts +78 -0
  30. package/src/presets/c3toSteam.ts +272 -0
  31. package/src/presets/demo.ts +123 -0
  32. package/src/presets/if.ts +69 -0
  33. package/src/presets/list.ts +30 -0
  34. package/src/presets/loop.ts +65 -0
  35. package/src/presets/moreToCome.ts +32 -0
  36. package/src/presets/newProject.ts +31 -0
  37. package/src/presets/preset.model.ts +0 -0
  38. package/src/presets/test-c3-offline.ts +78 -0
  39. package/src/presets/test-c3-unzip.ts +124 -0
  40. package/src/runner.ts +160 -0
  41. package/src/server.ts +99 -0
  42. package/src/types/runner.ts +101 -0
  43. package/src/utils/fs-extras.ts +211 -0
  44. package/src/utils/remote.ts +497 -0
  45. package/src/utils/storage.ts +99 -0
  46. package/src/utils.ts +268 -0
  47. package/src/websocket-server.ts +288 -0
  48. package/tsconfig.json +19 -0
@@ -0,0 +1,95 @@
1
+ import { supabase, useLogger, isSupabaseAvailable } from "@pipelab/shared";
2
+ import { useAPI } from "../ipc-core";
3
+ import { JsonFileStorage } from "../utils/storage";
4
+ import { PipelabContext } from "../context";
5
+ import { webSocketServer } from "../websocket-server";
6
+
7
+ /**
8
+ * Registers authentication handlers for the CLI/system backend.
9
+ */
10
+ export const registerAuthHandlers = (context: PipelabContext) => {
11
+ const { handle } = useAPI();
12
+ const { logger } = useLogger();
13
+
14
+ const supabaseAvailable = isSupabaseAvailable();
15
+ if (!supabaseAvailable) {
16
+ logger().warn("[Auth] Supabase is not available. Auth handlers will not be functional.");
17
+ return;
18
+ }
19
+
20
+ // Initialize the singleton Supabase client for the backend with the custom FileStorage
21
+ const client = supabase({
22
+ auth: {
23
+ storage: new JsonFileStorage("auth-session.json", context),
24
+ persistSession: true,
25
+ autoRefreshToken: true,
26
+ detectSessionInUrl: false,
27
+ },
28
+ });
29
+
30
+ handle("auth:getUser", async (_, { send }) => {
31
+ try {
32
+ const { data, error } = await client.auth.getUser();
33
+ if (error) {
34
+ return send({ type: "end", data: { type: "success", result: { user: null } } });
35
+ }
36
+ return send({ type: "end", data: { type: "success", result: { user: data.user } } });
37
+ } catch (e) {
38
+ return send({ type: "end", data: { type: "success", result: { user: null } } });
39
+ }
40
+ });
41
+
42
+ handle("auth:signInWithPassword", async (_, { value, send }) => {
43
+ const { email, password } = value;
44
+ logger().info("[Auth] signInWithPassword:", email);
45
+ const result = await client.auth.signInWithPassword({ email, password });
46
+ return send({ type: "end", data: { type: "success", result: result as any } });
47
+ });
48
+
49
+ handle("auth:signUp", async (_, { value, send }) => {
50
+ const { email, password } = value;
51
+ logger().info("[Auth] signUp:", email);
52
+ const result = await client.auth.signUp({ email, password });
53
+ return send({ type: "end", data: { type: "success", result: result as any } });
54
+ });
55
+
56
+ handle("auth:signOut", async (_, { send }) => {
57
+ logger().info("[Auth] signOut");
58
+ await client.auth.signOut();
59
+ return send({ type: "end", data: { type: "success", result: undefined } });
60
+ });
61
+
62
+ handle("auth:resetPasswordForEmail", async (_, { value, send }) => {
63
+ const { email } = value;
64
+ logger().info("[Auth] resetPasswordForEmail:", email);
65
+ const { error } = await client.auth.resetPasswordForEmail(email);
66
+ return send({ type: "end", data: { type: "success", result: { error } } });
67
+ });
68
+
69
+ handle("auth:invoke", async (_, { value, send }) => {
70
+ const { name, options } = value;
71
+ logger().info("[Auth] invoke function:", name);
72
+ try {
73
+ const result = await client.functions.invoke(name, options);
74
+ return send({ type: "end", data: { type: "success", result } });
75
+ } catch (e) {
76
+ return send({
77
+ type: "end",
78
+ data: {
79
+ type: "error",
80
+ ipcError: e instanceof Error ? e.message : "Edge Function invocation failed",
81
+ },
82
+ });
83
+ }
84
+ });
85
+
86
+ // Maintain logs for the backend state change and broadcast to all clients
87
+ client.auth.onAuthStateChange(async (event, session) => {
88
+ logger().info(
89
+ "[Auth] State changed, broadcasting:",
90
+ event,
91
+ session?.user?.email || "anonymous",
92
+ );
93
+ webSocketServer.broadcast("auth:getUser" as any, { user: session?.user || null } as any);
94
+ });
95
+ };
@@ -0,0 +1,381 @@
1
+ import { PipelabContext } from "../context";
2
+ import { join } from "node:path";
3
+ import { writeFile, readFile, unlink, mkdir, stat, readdir } from "node:fs/promises";
4
+ import { tmpdir } from "node:os";
5
+ import { useLogger, BuildHistoryEntry, IBuildHistoryStorage, AppConfig } from "@pipelab/shared";
6
+ import { setupConfigFile } from "../config";
7
+ import checkDiskSpace from "check-disk-space";
8
+ import { getFolderSize } from "../utils/fs-extras";
9
+
10
+ // Simplified storage - one file per pipeline containing array of build entries
11
+
12
+ export class BuildHistoryStorage implements IBuildHistoryStorage {
13
+ private logger = useLogger();
14
+
15
+ constructor(private context: PipelabContext) {
16
+ // Simple initialization - no complex setup needed
17
+ }
18
+
19
+ private getStoragePath() {
20
+ return join(this.context.userDataPath, "build-history");
21
+ }
22
+
23
+ private getPipelinePath(pipelineId: string): string {
24
+ // Sanitize the pipelineId to create a valid filename
25
+ // Replace invalid filename characters with underscores
26
+ const sanitizedId = pipelineId
27
+ .replace(/[/\:*?"<>|]/g, "_")
28
+ .replace(/__/g, "_") // Replace multiple underscores with single
29
+ .replace(/^_+|_+$/g, ""); // Remove leading/trailing underscores
30
+
31
+ return join(this.getStoragePath(), `pipeline-${sanitizedId}.json`);
32
+ }
33
+
34
+ private async ensureStoragePath(): Promise<void> {
35
+ try {
36
+ await mkdir(this.getStoragePath(), { recursive: true });
37
+ } catch (error) {
38
+ this.logger.logger().error("Failed to create storage path:", error);
39
+ throw new Error(`Failed to create storage directory: ${error}`);
40
+ }
41
+ }
42
+
43
+ private async loadPipelineHistory(pipelineId: string): Promise<BuildHistoryEntry[]> {
44
+ try {
45
+ const pipelinePath = this.getPipelinePath(pipelineId);
46
+ const data = await readFile(pipelinePath, "utf-8");
47
+ return JSON.parse(data);
48
+ } catch (error) {
49
+ // File doesn't exist or is corrupted, return empty array
50
+ return [];
51
+ }
52
+ }
53
+
54
+ private async savePipelineHistory(
55
+ pipelineId: string,
56
+ entries: BuildHistoryEntry[],
57
+ ): Promise<void> {
58
+ try {
59
+ await this.ensureStoragePath();
60
+ const pipelinePath = this.getPipelinePath(pipelineId);
61
+ await writeFile(pipelinePath, JSON.stringify(entries, null, 2), "utf-8");
62
+ } catch (error) {
63
+ this.logger.logger().error("Failed to save pipeline history:", error);
64
+ throw new Error(`Failed to save pipeline history: ${error}`);
65
+ }
66
+ }
67
+
68
+ async applyRetentionPolicy(): Promise<void> {
69
+ try {
70
+ this.logger.logger().info("Applying build history retention policy...");
71
+ const settings = await setupConfigFile<AppConfig>("settings", { context: this.context });
72
+ const config = await settings.getConfig();
73
+ const policy = config?.buildHistory?.retentionPolicy;
74
+
75
+ if (!policy || !policy.enabled) {
76
+ this.logger.logger().info("Retention policy is disabled. Skipping.");
77
+ return;
78
+ }
79
+
80
+ const { maxAge, maxEntries } = policy;
81
+ const pipelineFiles = await this.getAllPipelineFiles();
82
+
83
+ for (const file of pipelineFiles) {
84
+ const pipelineId = file.replace("pipeline-", "").replace(".json", "");
85
+ let entries = await this.loadPipelineHistory(pipelineId);
86
+ const originalCount = entries.length;
87
+
88
+ // 1. Filter by maxAge
89
+ if (maxAge > 0) {
90
+ const minDate = Date.now() - maxAge * 24 * 60 * 60 * 1000;
91
+ entries = entries.filter((entry) => entry.createdAt >= minDate);
92
+ }
93
+
94
+ // 2. Filter by maxEntries (sort by date first to keep the newest)
95
+ if (maxEntries > 0 && entries.length > maxEntries) {
96
+ entries = entries
97
+ .sort((a, b) => b.createdAt - a.createdAt)
98
+ .slice(0, maxEntries);
99
+ }
100
+
101
+ if (entries.length < originalCount) {
102
+ this.logger
103
+ .logger()
104
+ .info(
105
+ `[${pipelineId}] Pruned ${originalCount - entries.length} history entries.`,
106
+ );
107
+ await this.savePipelineHistory(pipelineId, entries);
108
+ }
109
+ }
110
+ this.logger.logger().info("Retention policy applied successfully.");
111
+ } catch (error) {
112
+ this.logger.logger().error("Failed to apply retention policy:", error);
113
+ // We don't re-throw here as this is a background task and shouldn't crash the app
114
+ }
115
+ }
116
+
117
+ async save(entry: BuildHistoryEntry): Promise<void> {
118
+ try {
119
+ const entries = await this.loadPipelineHistory(entry.pipelineId);
120
+ const existingIndex = entries.findIndex((e) => e.id === entry.id);
121
+
122
+ if (existingIndex >= 0) {
123
+ entries[existingIndex] = entry;
124
+ } else {
125
+ entries.push(entry);
126
+ }
127
+
128
+ await this.savePipelineHistory(entry.pipelineId, entries);
129
+ this.logger
130
+ .logger()
131
+ .info(`Saved build history entry: ${entry.id} for pipeline: ${entry.pipelineId}`);
132
+
133
+ // Apply retention policy after each save
134
+ this.applyRetentionPolicy().catch((err) => {
135
+ this.logger.logger().error("Failed to apply retention policy after save:", err);
136
+ });
137
+ } catch (error) {
138
+ this.logger.logger().error("Failed to save build history entry:", error);
139
+ throw new Error(`Failed to save build history entry: ${error}`);
140
+ }
141
+ }
142
+
143
+ async get(id: string, pipelineId?: string): Promise<BuildHistoryEntry | undefined> {
144
+ try {
145
+ if (pipelineId) {
146
+ const entries = await this.loadPipelineHistory(pipelineId);
147
+ return entries.find((e) => e.id === id);
148
+ }
149
+
150
+ const files = await this.getAllPipelineFiles();
151
+ for (const file of files) {
152
+ const pId = file.replace("pipeline-", "").replace(".json", "");
153
+ const entries = await this.loadPipelineHistory(pId);
154
+ const entry = entries.find((e) => e.id === id);
155
+ if (entry) return entry;
156
+ }
157
+ return undefined;
158
+ } catch (error) {
159
+ this.logger.logger().error(`Failed to get build history entry ${id}:`, error);
160
+ return undefined;
161
+ }
162
+ }
163
+
164
+ async getAll(): Promise<BuildHistoryEntry[]> {
165
+ try {
166
+ const files = await this.getAllPipelineFiles();
167
+ const allEntries: BuildHistoryEntry[] = [];
168
+ for (const file of files) {
169
+ const pipelineId = file.replace("pipeline-", "").replace(".json", "");
170
+ const entries = await this.loadPipelineHistory(pipelineId);
171
+ allEntries.push(...entries);
172
+ }
173
+ return allEntries.sort((a, b) => b.createdAt - a.createdAt);
174
+ } catch (error) {
175
+ this.logger.logger().error("Failed to get all build history entries:", error);
176
+ throw new Error(`Failed to get all build history entries: ${error}`);
177
+ }
178
+ }
179
+
180
+ async getByPipeline(pipelineId: string): Promise<BuildHistoryEntry[]> {
181
+ try {
182
+ const entries = await this.loadPipelineHistory(pipelineId);
183
+ return entries.sort((a, b) => b.createdAt - a.createdAt);
184
+ } catch (error) {
185
+ this.logger.logger().error(`Failed to get build history for pipeline ${pipelineId}:`, error);
186
+ throw new Error(`Failed to get build history for pipeline: ${error}`);
187
+ }
188
+ }
189
+
190
+ async update(
191
+ id: string,
192
+ updates: Partial<BuildHistoryEntry>,
193
+ pipelineId?: string,
194
+ ): Promise<void> {
195
+ try {
196
+ if (pipelineId) {
197
+ const entries = await this.loadPipelineHistory(pipelineId);
198
+ const entryIndex = entries.findIndex((e) => e.id === id);
199
+ if (entryIndex >= 0) {
200
+ entries[entryIndex] = { ...entries[entryIndex], ...updates, updatedAt: Date.now() };
201
+ await this.savePipelineHistory(pipelineId, entries);
202
+ return;
203
+ }
204
+ } else {
205
+ const files = await this.getAllPipelineFiles();
206
+ for (const file of files) {
207
+ const pId = file.replace("pipeline-", "").replace(".json", "");
208
+ const entries = await this.loadPipelineHistory(pId);
209
+ const entryIndex = entries.findIndex((e) => e.id === id);
210
+ if (entryIndex >= 0) {
211
+ entries[entryIndex] = { ...entries[entryIndex], ...updates, updatedAt: Date.now() };
212
+ await this.savePipelineHistory(pId, entries);
213
+ return;
214
+ }
215
+ }
216
+ }
217
+ throw new Error(`Build history entry ${id} not found`);
218
+ } catch (error) {
219
+ this.logger.logger().error(`Failed to update build history entry ${id}:`, error);
220
+ throw new Error(`Failed to update build history entry: ${error}`);
221
+ }
222
+ }
223
+
224
+ async delete(id: string, pipelineId?: string): Promise<void> {
225
+ try {
226
+ if (pipelineId) {
227
+ const entries = await this.loadPipelineHistory(pipelineId);
228
+ const entryIndex = entries.findIndex((e) => e.id === id);
229
+ if (entryIndex >= 0) {
230
+ entries.splice(entryIndex, 1);
231
+ await this.savePipelineHistory(pipelineId, entries);
232
+ this.logger.logger().info(`Deleted build history entry: ${id}`);
233
+ return;
234
+ }
235
+ } else {
236
+ const files = await this.getAllPipelineFiles();
237
+ for (const file of files) {
238
+ const pId = file.replace("pipeline-", "").replace(".json", "");
239
+ const entries = await this.loadPipelineHistory(pId);
240
+ const entryIndex = entries.findIndex((e) => e.id === id);
241
+ if (entryIndex >= 0) {
242
+ entries.splice(entryIndex, 1);
243
+ await this.savePipelineHistory(pId, entries);
244
+ this.logger.logger().info(`Deleted build history entry: ${id}`);
245
+ return;
246
+ }
247
+ }
248
+ }
249
+ this.logger.logger().info(`Build history entry ${id} not found for deletion`);
250
+ } catch (error) {
251
+ this.logger.logger().error(`Failed to delete build history entry ${id}:`, error);
252
+ throw new Error(`Failed to delete build history entry: ${error}`);
253
+ }
254
+ }
255
+
256
+ async clear(): Promise<void> {
257
+ try {
258
+ await this.ensureStoragePath();
259
+ const files = await this.getAllPipelineFiles();
260
+ await Promise.all(files.map((file) => unlink(join(this.getStoragePath(), file))));
261
+ this.logger.logger().info("Cleared all build history");
262
+ } catch (error) {
263
+ this.logger.logger().error("Failed to clear build history:", error);
264
+ throw new Error(`Failed to clear build history: ${error}`);
265
+ }
266
+ }
267
+
268
+ async clearByPipeline(pipelineId: string): Promise<void> {
269
+ try {
270
+ const pipelinePath = this.getPipelinePath(pipelineId);
271
+ await unlink(pipelinePath);
272
+ this.logger.logger().info(`Cleared history for pipeline "${pipelineId}"`);
273
+ } catch (error: any) {
274
+ if (error.code === 'ENOENT') {
275
+ this.logger.logger().warn(`No history file found for pipeline "${pipelineId}". Nothing to clear.`);
276
+ return;
277
+ }
278
+ this.logger.logger().error(`Failed to clear history for pipeline "${pipelineId}":`, error);
279
+ throw new Error(`Failed to clear history for pipeline: ${error}`);
280
+ }
281
+ }
282
+
283
+ async getStorageInfo(): Promise<{
284
+ totalEntries: number;
285
+ totalSize: number;
286
+ oldestEntry?: number;
287
+ newestEntry?: number;
288
+ numberOfPipelines: number;
289
+ userDataPath: string;
290
+ retentionPolicy: {
291
+ enabled: boolean;
292
+ maxEntries: number;
293
+ maxAge: number;
294
+ };
295
+ disk: {
296
+ total: number;
297
+ free: number;
298
+ pipelab: number;
299
+ };
300
+ }> {
301
+ try {
302
+ const allEntries = await this.getAll();
303
+ const files = await this.getAllPipelineFiles();
304
+
305
+ const diskSpace = await checkDiskSpace(this.context.userDataPath);
306
+ const pipelabSize = await getFolderSize(this.context.userDataPath);
307
+
308
+ const settings = await setupConfigFile<AppConfig>("settings", { context: this.context });
309
+ const config = await settings.getConfig();
310
+ const policy = config?.buildHistory?.retentionPolicy || {
311
+ enabled: false,
312
+ maxEntries: 50,
313
+ maxAge: 30,
314
+ };
315
+
316
+ if (allEntries.length === 0) {
317
+ return {
318
+ totalEntries: 0,
319
+ totalSize: 0,
320
+ numberOfPipelines: files.length,
321
+ userDataPath: this.context.userDataPath,
322
+ retentionPolicy: {
323
+ enabled: policy.enabled,
324
+ maxEntries: policy.maxEntries,
325
+ maxAge: policy.maxAge,
326
+ },
327
+ disk: {
328
+ total: diskSpace.size,
329
+ free: diskSpace.free,
330
+ pipelab: pipelabSize,
331
+ },
332
+ };
333
+ }
334
+
335
+ let totalSize = 0;
336
+ try {
337
+ for (const file of files) {
338
+ const filePath = join(this.getStoragePath(), file);
339
+ const stats = await stat(filePath);
340
+ totalSize += stats.size;
341
+ }
342
+ } catch (error) {
343
+ totalSize = allEntries.length * 1024; // Rough estimate
344
+ }
345
+
346
+ const sortedEntries = allEntries.sort((a, b) => a.createdAt - b.createdAt);
347
+
348
+ return {
349
+ totalEntries: allEntries.length,
350
+ totalSize,
351
+ oldestEntry: sortedEntries[0]?.createdAt,
352
+ newestEntry: sortedEntries[sortedEntries.length - 1]?.createdAt,
353
+ numberOfPipelines: files.length,
354
+ userDataPath: this.context.userDataPath,
355
+ retentionPolicy: {
356
+ enabled: policy.enabled,
357
+ maxEntries: policy.maxEntries,
358
+ maxAge: policy.maxAge,
359
+ },
360
+ disk: {
361
+ total: diskSpace.size,
362
+ free: diskSpace.free,
363
+ pipelab: pipelabSize,
364
+ },
365
+ };
366
+ } catch (error) {
367
+ this.logger.logger().error("Failed to get storage info:", error);
368
+ throw new Error(`Failed to get storage info: ${error}`);
369
+ }
370
+ }
371
+
372
+ private async getAllPipelineFiles(): Promise<string[]> {
373
+ try {
374
+ await this.ensureStoragePath();
375
+ const files = await readdir(this.getStoragePath());
376
+ return files.filter((file) => file.startsWith("pipeline-") && file.endsWith(".json"));
377
+ } catch (error) {
378
+ return [];
379
+ }
380
+ }
381
+ }
@@ -0,0 +1,109 @@
1
+ import { useAPI } from "../ipc-core";
2
+ import { useLogger, configRegistry } from "@pipelab/shared";
3
+ import { setupConfigFile, getMigrator } from "../config";
4
+ import { PipelabContext } from "../context";
5
+
6
+ export const registerConfigHandlers = (context: PipelabContext) => {
7
+ const { handle } = useAPI();
8
+ const { logger } = useLogger();
9
+
10
+ handle("config:load", async (_, { send, value }) => {
11
+ const { config: name } = value;
12
+ logger().info("config:load", name);
13
+
14
+ try {
15
+ const manager = await setupConfigFile(name, { context });
16
+ const json = await manager.getConfig();
17
+
18
+ send({
19
+ type: "end",
20
+ data: {
21
+ type: "success",
22
+ result: {
23
+ result: json,
24
+ },
25
+ },
26
+ });
27
+ } catch (e) {
28
+ logger().error(`config:load error for ${name}:`, e);
29
+ send({
30
+ type: "end",
31
+ data: {
32
+ type: "error",
33
+ ipcError: e instanceof Error ? e.message : `Unable to load config ${name}`,
34
+ },
35
+ });
36
+ }
37
+ });
38
+
39
+ handle("config:save", async (_, { send, value }) => {
40
+ const { data, config: name } = value;
41
+
42
+ try {
43
+ const manager = await setupConfigFile(name, { context });
44
+ const json = typeof data === "string" ? JSON.parse(data) : data;
45
+ await manager.setConfig(json);
46
+
47
+ send({
48
+ type: "end",
49
+ data: {
50
+ type: "success",
51
+ result: {
52
+ result: "ok",
53
+ },
54
+ },
55
+ });
56
+ } catch (e) {
57
+ logger().error(`config:save error for ${name}:`, e);
58
+ send({
59
+ type: "end",
60
+ data: {
61
+ type: "error",
62
+ ipcError: e instanceof Error ? e.message : `Unable to save config ${name}`,
63
+ },
64
+ });
65
+ }
66
+ });
67
+
68
+ handle("config:reset", async (event, { value, send }) => {
69
+ const { config: name, key } = value;
70
+ logger().info("config:reset", name, key);
71
+
72
+ try {
73
+ const manager = await setupConfigFile(name, { context });
74
+ const currentConfig = await manager.getConfig();
75
+
76
+ const migrator = getMigrator(name);
77
+
78
+ if (!migrator) {
79
+ throw new Error(`No migrator found for configuration: ${name}`);
80
+ }
81
+
82
+ const defaultValue = (migrator.defaultValue as any)[key];
83
+
84
+ await manager.setConfig({
85
+ ...(currentConfig ? (currentConfig as any) : {}),
86
+ [key]: defaultValue,
87
+ } as any);
88
+
89
+ send({
90
+ type: "end",
91
+ data: {
92
+ type: "success",
93
+ result: {
94
+ result: "ok",
95
+ },
96
+ },
97
+ });
98
+ } catch (e) {
99
+ logger().error(`config:reset error for ${name}:`, e);
100
+ send({
101
+ type: "end",
102
+ data: {
103
+ type: "error",
104
+ ipcError: e instanceof Error ? e.message : `Unable to reset config ${name}`,
105
+ },
106
+ });
107
+ }
108
+ });
109
+ };