@pipelab/core-node 1.0.0-beta.17 → 1.0.0-beta.18

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pipelab/core-node",
3
- "version": "1.0.0-beta.17",
3
+ "version": "1.0.0-beta.18",
4
4
  "private": false,
5
5
  "description": "The Pipelab automation engine for Node.js",
6
6
  "license": "FSL-1.1-MIT",
@@ -40,8 +40,8 @@
40
40
  "type-fest": "4.39.0",
41
41
  "ws": "8.18.3",
42
42
  "yauzl": "2.10.0",
43
- "@pipelab/constants": "1.0.0-beta.15",
44
- "@pipelab/shared": "1.0.0-beta.13"
43
+ "@pipelab/constants": "1.0.0-beta.16",
44
+ "@pipelab/shared": "1.0.0-beta.14"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@types/adm-zip": "0.5.7",
@@ -56,7 +56,7 @@
56
56
  "tsdown": "0.21.2",
57
57
  "typescript": "^5.0.0",
58
58
  "vitest": "3.1.4",
59
- "@pipelab/tsconfig": "1.0.0-beta.13"
59
+ "@pipelab/tsconfig": "1.0.0-beta.14"
60
60
  },
61
61
  "scripts": {
62
62
  "build": "tsdown",
@@ -38,9 +38,9 @@ describe("setupConfigFile & Backup Creation", () => {
38
38
  // 1. Setup V1 configuration file in the context's config path
39
39
  const configDir = context.getConfigPath();
40
40
  await fs.mkdir(configDir, { recursive: true });
41
-
41
+
42
42
  const projectsFilePath = path.join(configDir, "projects.json");
43
-
43
+
44
44
  const v1Config = {
45
45
  version: "1.0.0",
46
46
  data: {
@@ -49,10 +49,10 @@ describe("setupConfigFile & Backup Creation", () => {
49
49
  type: "internal",
50
50
  configName: "pipeline-1",
51
51
  lastModified: "2026-06-04",
52
- }
53
- }
52
+ },
53
+ },
54
54
  };
55
-
55
+
56
56
  await fs.writeFile(projectsFilePath, JSON.stringify(v1Config));
57
57
 
58
58
  // 2. Initialize setupConfigFile
@@ -64,11 +64,11 @@ describe("setupConfigFile & Backup Creation", () => {
64
64
  // 3. Retrieve config (this triggers the migration process and onStep callback)
65
65
  const migratedConfig = await configInstance.getConfig();
66
66
 
67
- // 4. Assert the main config is updated on disk to version 2.0.0
68
- expect(migratedConfig.version).toBe("2.0.0");
69
-
67
+ // 4. Assert the main config is updated on disk to version 3.0.0
68
+ expect(migratedConfig.version).toBe("3.0.0");
69
+
70
70
  const updatedContent = JSON.parse(await fs.readFile(projectsFilePath, "utf8"));
71
- expect(updatedContent.version).toBe("2.0.0");
71
+ expect(updatedContent.version).toBe("3.0.0");
72
72
 
73
73
  // 5. Assert that the intermediate backup file was created on disk
74
74
  const backupFilePath = path.join(configDir, "projects.v2.0.0.json");
@@ -92,7 +92,7 @@ describe("setupConfigFile & Backup Creation", () => {
92
92
  // Should create file with default value
93
93
  expect(existsSync(projectsFilePath)).toBe(true);
94
94
  const content = JSON.parse(await fs.readFile(projectsFilePath, "utf8"));
95
- expect(content.version).toBe("2.0.0");
95
+ expect(content.version).toBe("3.0.0");
96
96
  expect(content.projects).toHaveLength(1);
97
97
  expect(content.pipelines).toEqual([]);
98
98
  });
@@ -107,13 +107,15 @@ describe("setupConfigFile & Backup Creation", () => {
107
107
  const configInstance = await setupConfigFile<FileRepo>("projects", { context });
108
108
  const config = await configInstance.getConfig();
109
109
 
110
- expect(config.version).toBe("2.0.0");
110
+ expect(config.version).toBe("3.0.0");
111
111
  expect(config.projects).toHaveLength(1);
112
112
  expect(config.pipelines).toEqual([]);
113
113
 
114
114
  // Verify a timestamped corrupted backup file exists
115
115
  const files = await fs.readdir(configDir);
116
- const corruptedFile = files.find(f => f.startsWith("projects.corrupted.") && f.endsWith(".json"));
116
+ const corruptedFile = files.find(
117
+ (f) => f.startsWith("projects.corrupted.") && f.endsWith(".json"),
118
+ );
117
119
  expect(corruptedFile).toBeDefined();
118
120
  const corruptedContent = await fs.readFile(path.join(configDir, corruptedFile!), "utf8");
119
121
  expect(corruptedContent).toBe("{ corrupted json... }");
@@ -143,9 +145,13 @@ describe("setupConfigFile & Backup Creation", () => {
143
145
 
144
146
  // Verify a timestamped corrupted backup file exists
145
147
  const files = await fs.readdir(configDir);
146
- const corruptedFile = files.find(f => f.startsWith("projects.corrupted.") && f.endsWith(".json"));
148
+ const corruptedFile = files.find(
149
+ (f) => f.startsWith("projects.corrupted.") && f.endsWith(".json"),
150
+ );
147
151
  expect(corruptedFile).toBeDefined();
148
- const corruptedContent = JSON.parse(await fs.readFile(path.join(configDir, corruptedFile!), "utf8"));
152
+ const corruptedContent = JSON.parse(
153
+ await fs.readFile(path.join(configDir, corruptedFile!), "utf8"),
154
+ );
149
155
  expect(corruptedContent.invalidData).toBe(true);
150
156
  });
151
157
 
@@ -169,7 +175,9 @@ describe("setupConfigFile & Backup Creation", () => {
169
175
  const success = await configInstance.setConfig(newConfig);
170
176
  expect(success).toBe(true);
171
177
 
172
- const savedContent = JSON.parse(await fs.readFile(path.join(context.getConfigPath(), "projects.json"), "utf8"));
178
+ const savedContent = JSON.parse(
179
+ await fs.readFile(path.join(context.getConfigPath(), "projects.json"), "utf8"),
180
+ );
173
181
  expect(savedContent.pipelines).toHaveLength(1);
174
182
  expect(savedContent.pipelines[0].id).toBe("pipeline-new");
175
183
  });
package/src/config.ts CHANGED
@@ -3,7 +3,7 @@ import path from "node:path";
3
3
  import { ensure } from "./utils/fs-extras";
4
4
  import fs from "node:fs/promises";
5
5
  import { useLogger } from "@pipelab/shared";
6
- import { configRegistry, Migrator, normalizePipelineConfig } from "@pipelab/shared";
6
+ import { configRegistry, Migrator } from "@pipelab/shared";
7
7
 
8
8
  export const getMigrator = <T>(name: string) => {
9
9
  return (configRegistry[name] || configRegistry["pipeline"]) as Migrator<T>;
@@ -62,12 +62,15 @@ export const setupConfigFile = async <T>(
62
62
  debug: false,
63
63
  onStep: async (state: any, version: string) => {
64
64
  const parsedPath = path.parse(filesPath);
65
- const versionedPath = path.join(parsedPath.dir, `${parsedPath.name}.v${version}.json`);
65
+ const versionedPath = ctx.getConfigPath(`${parsedPath.name}.v${version}.json`);
66
66
  try {
67
67
  await fs.writeFile(versionedPath, JSON.stringify(state));
68
68
  logger().info(`Intermediate backup created for ${name} at ${versionedPath}`);
69
69
  } catch (e) {
70
- logger().error(`Failed to create intermediate backup for ${name} at v${version}:`, e);
70
+ logger().error(
71
+ `Failed to create intermediate backup for ${name} at v${version}:`,
72
+ e,
73
+ );
71
74
  }
72
75
  },
73
76
  });
@@ -80,36 +83,23 @@ export const setupConfigFile = async <T>(
80
83
  json = migrator.defaultValue;
81
84
  }
82
85
 
83
- let normalized = false;
84
- const isPipeline =
85
- name.startsWith("pipeline-") ||
86
- path.isAbsolute(name) ||
87
- name.endsWith(".json") ||
88
- name === "pipeline";
89
- if (isPipeline) {
90
- normalized = normalizePipelineConfig(json);
91
- }
92
-
93
86
  const originalVersion = originalJson?.version;
94
87
  const newVersion = json?.version;
95
88
 
96
89
  const shouldSaveBack =
97
- originalVersion !== newVersion ||
98
- normalized ||
99
- content === undefined ||
100
- parseFailed ||
101
- migrationFailed;
90
+ originalVersion !== newVersion || content === undefined || parseFailed || migrationFailed;
102
91
 
103
92
  if (shouldSaveBack) {
104
93
  if (parseFailed || migrationFailed) {
105
94
  try {
106
95
  const parsedPath = path.parse(filesPath);
107
96
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
108
- const corruptedPath = path.join(
109
- parsedPath.dir,
97
+ const corruptedPath = ctx.getConfigPath(
110
98
  `${parsedPath.name}.corrupted.${timestamp}.json`,
111
99
  );
112
- const backupContent = parseFailed ? (content || "") : JSON.stringify(originalJson, null, 2);
100
+ const backupContent = parseFailed
101
+ ? content || ""
102
+ : JSON.stringify(originalJson, null, 2);
113
103
  await fs.writeFile(corruptedPath, backupContent);
114
104
  logger().info(`Corrupted config file preserved at ${corruptedPath}`);
115
105
  } catch (e) {
@@ -128,3 +118,10 @@ export const setupConfigFile = async <T>(
128
118
  },
129
119
  };
130
120
  };
121
+
122
+ export const deleteConfigFile = async (nameOrPath: string, context: PipelabContext) => {
123
+ const isAbsolutePath = path.isAbsolute(nameOrPath);
124
+ const filesPath = isAbsolutePath ? nameOrPath : context.getConfigPath(`${nameOrPath}.json`);
125
+
126
+ await fs.rm(filesPath, { force: true });
127
+ };
package/src/context.ts CHANGED
@@ -51,6 +51,14 @@ function findProjectRoot(startDir: string): string | null {
51
51
 
52
52
  export const projectRoot = findProjectRoot(_dirname);
53
53
 
54
+ export const CacheFolder = {
55
+ Actions: "actions",
56
+ Pipelines: "pipelines",
57
+ Pacote: "pacote",
58
+ } as const;
59
+
60
+ export type CacheFolderType = (typeof CacheFolder)[keyof typeof CacheFolder];
61
+
54
62
  export interface PipelabContextOptions {
55
63
  userDataPath: string;
56
64
  releaseTag?: string;
@@ -88,14 +96,23 @@ export class PipelabContext {
88
96
  return await mkdtemp(join(realBaseDir, prefix));
89
97
  }
90
98
 
91
- getCachePath(...subpaths: string[]) {
92
- return join(this.userDataPath, "cache", ...subpaths);
99
+ getCachePath(): string;
100
+ getCachePath(folder: CacheFolderType, ...subpaths: string[]): string;
101
+ getCachePath(folder?: CacheFolderType, ...subpaths: string[]) {
102
+ if (!folder) {
103
+ return join(this.userDataPath, "cache");
104
+ }
105
+ return join(this.userDataPath, "cache", folder, ...subpaths);
93
106
  }
94
107
 
95
108
  getPnpmPath(...subpaths: string[]) {
96
109
  return join(this.userDataPath, "pnpm", ...subpaths);
97
110
  }
98
111
 
112
+ getBuildHistoryPath(...subpaths: string[]) {
113
+ return join(this.userDataPath, "build-history", ...subpaths);
114
+ }
115
+
99
116
  getNodePath(version = DEFAULT_NODE_VERSION) {
100
117
  const isWindows = process.platform === "win32";
101
118
  return this.getThirdPartyPath("node", version, isWindows ? "node.exe" : "bin/node");
@@ -1,8 +1,7 @@
1
1
  import { PipelabContext } from "../context";
2
2
  import { join } from "node:path";
3
- import { writeFile, readFile, unlink, mkdir, stat, readdir } from "node:fs/promises";
3
+ import { writeFile, readFile, unlink, mkdir, stat, readdir, rm } from "node:fs/promises";
4
4
  import { useLogger, BuildHistoryEntry, IBuildHistoryStorage, AppConfig } from "@pipelab/shared";
5
- import { setupConfigFile } from "../config";
6
5
  import checkDiskSpace from "check-disk-space";
7
6
  import { getFolderSize } from "../utils/fs-extras";
8
7
  import { SandboxFolder } from "@pipelab/constants";
@@ -17,18 +16,11 @@ export class BuildHistoryStorage implements IBuildHistoryStorage {
17
16
  }
18
17
 
19
18
  private getStoragePath() {
20
- return join(this.context.userDataPath, "build-history");
19
+ return this.context.getConfigPath("pipelines");
21
20
  }
22
21
 
23
22
  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`);
23
+ return join(this.getStoragePath(), `${pipelineId}.history.json`);
32
24
  }
33
25
 
34
26
  private async ensureStoragePath(): Promise<void> {
@@ -65,51 +57,6 @@ export class BuildHistoryStorage implements IBuildHistoryStorage {
65
57
  }
66
58
  }
67
59
 
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.sort((a, b) => b.createdAt - a.createdAt).slice(0, maxEntries);
97
- }
98
-
99
- if (entries.length < originalCount) {
100
- this.logger
101
- .logger()
102
- .info(`[${pipelineId}] Pruned ${originalCount - entries.length} history entries.`);
103
- await this.savePipelineHistory(pipelineId, entries);
104
- }
105
- }
106
- this.logger.logger().info("Retention policy applied successfully.");
107
- } catch (error) {
108
- this.logger.logger().error("Failed to apply retention policy:", error);
109
- // We don't re-throw here as this is a background task and shouldn't crash the app
110
- }
111
- }
112
-
113
60
  async save(entry: BuildHistoryEntry): Promise<void> {
114
61
  try {
115
62
  const entries = await this.loadPipelineHistory(entry.pipelineId);
@@ -125,11 +72,6 @@ export class BuildHistoryStorage implements IBuildHistoryStorage {
125
72
  this.logger
126
73
  .logger()
127
74
  .info(`Saved build history entry: ${entry.id} for pipeline: ${entry.pipelineId}`);
128
-
129
- // Apply retention policy after each save
130
- this.applyRetentionPolicy().catch((err) => {
131
- this.logger.logger().error("Failed to apply retention policy after save:", err);
132
- });
133
75
  } catch (error) {
134
76
  this.logger.logger().error("Failed to save build history entry:", error);
135
77
  throw new Error(`Failed to save build history entry: ${error}`);
@@ -145,7 +87,8 @@ export class BuildHistoryStorage implements IBuildHistoryStorage {
145
87
 
146
88
  const files = await this.getAllPipelineFiles();
147
89
  for (const file of files) {
148
- const pId = file.replace("pipeline-", "").replace(".json", "");
90
+ const pId = this.parsePipelineIdFromFilename(file);
91
+ if (!pId) continue;
149
92
  const entries = await this.loadPipelineHistory(pId);
150
93
  const entry = entries.find((e) => e.id === id);
151
94
  if (entry) return entry;
@@ -162,7 +105,8 @@ export class BuildHistoryStorage implements IBuildHistoryStorage {
162
105
  const files = await this.getAllPipelineFiles();
163
106
  const allEntries: BuildHistoryEntry[] = [];
164
107
  for (const file of files) {
165
- const pipelineId = file.replace("pipeline-", "").replace(".json", "");
108
+ const pipelineId = this.parsePipelineIdFromFilename(file);
109
+ if (!pipelineId) continue;
166
110
  const entries = await this.loadPipelineHistory(pipelineId);
167
111
  allEntries.push(...entries);
168
112
  }
@@ -200,7 +144,8 @@ export class BuildHistoryStorage implements IBuildHistoryStorage {
200
144
  } else {
201
145
  const files = await this.getAllPipelineFiles();
202
146
  for (const file of files) {
203
- const pId = file.replace("pipeline-", "").replace(".json", "");
147
+ const pId = this.parsePipelineIdFromFilename(file);
148
+ if (!pId) continue;
204
149
  const entries = await this.loadPipelineHistory(pId);
205
150
  const entryIndex = entries.findIndex((e) => e.id === id);
206
151
  if (entryIndex >= 0) {
@@ -231,7 +176,8 @@ export class BuildHistoryStorage implements IBuildHistoryStorage {
231
176
  } else {
232
177
  const files = await this.getAllPipelineFiles();
233
178
  for (const file of files) {
234
- const pId = file.replace("pipeline-", "").replace(".json", "");
179
+ const pId = this.parsePipelineIdFromFilename(file);
180
+ if (!pId) continue;
235
181
  const entries = await this.loadPipelineHistory(pId);
236
182
  const entryIndex = entries.findIndex((e) => e.id === id);
237
183
  if (entryIndex >= 0) {
@@ -253,8 +199,26 @@ export class BuildHistoryStorage implements IBuildHistoryStorage {
253
199
  try {
254
200
  await this.ensureStoragePath();
255
201
  const files = await this.getAllPipelineFiles();
256
- await Promise.all(files.map((file) => unlink(join(this.getStoragePath(), file))));
202
+ const cachePathsToDelete = new Set<string>();
203
+
204
+ for (const file of files) {
205
+ const pipelineId = this.parsePipelineIdFromFilename(file);
206
+ if (pipelineId) {
207
+ const entries = await this.loadPipelineHistory(pipelineId);
208
+ for (const entry of entries) {
209
+ if (entry.cachePath) {
210
+ cachePathsToDelete.add(entry.cachePath);
211
+ }
212
+ }
213
+ }
214
+ await unlink(join(this.getStoragePath(), file));
215
+ }
216
+
257
217
  this.logger.logger().info("Cleared all build history");
218
+
219
+ for (const cachePath of cachePathsToDelete) {
220
+ await rm(cachePath, { recursive: true, force: true }).catch(() => {});
221
+ }
258
222
  } catch (error) {
259
223
  this.logger.logger().error("Failed to clear build history:", error);
260
224
  throw new Error(`Failed to clear build history: ${error}`);
@@ -264,8 +228,19 @@ export class BuildHistoryStorage implements IBuildHistoryStorage {
264
228
  async clearByPipeline(pipelineId: string): Promise<void> {
265
229
  try {
266
230
  const pipelinePath = this.getPipelinePath(pipelineId);
231
+ const entries = await this.loadPipelineHistory(pipelineId);
267
232
  await unlink(pipelinePath);
268
233
  this.logger.logger().info(`Cleared history for pipeline "${pipelineId}"`);
234
+
235
+ const cachePathsToDelete = new Set<string>();
236
+ for (const entry of entries) {
237
+ if (entry.cachePath) {
238
+ cachePathsToDelete.add(entry.cachePath);
239
+ }
240
+ }
241
+ for (const cachePath of cachePathsToDelete) {
242
+ await rm(cachePath, { recursive: true, force: true }).catch(() => {});
243
+ }
269
244
  } catch (error: any) {
270
245
  if (error.code === "ENOENT") {
271
246
  this.logger
@@ -285,11 +260,6 @@ export class BuildHistoryStorage implements IBuildHistoryStorage {
285
260
  newestEntry?: number;
286
261
  numberOfPipelines: number;
287
262
  userDataPath: string;
288
- retentionPolicy: {
289
- enabled: boolean;
290
- maxEntries: number;
291
- maxAge: number;
292
- };
293
263
  disk: {
294
264
  total: number;
295
265
  free: number;
@@ -314,25 +284,12 @@ export class BuildHistoryStorage implements IBuildHistoryStorage {
314
284
  });
315
285
  }
316
286
 
317
- const settings = await setupConfigFile<AppConfig>("settings", { context: this.context });
318
- const config = await settings.getConfig();
319
- const policy = config?.buildHistory?.retentionPolicy || {
320
- enabled: false,
321
- maxEntries: 50,
322
- maxAge: 30,
323
- };
324
-
325
287
  if (allEntries.length === 0) {
326
288
  return {
327
289
  totalEntries: 0,
328
290
  totalSize: 0,
329
291
  numberOfPipelines: files.length,
330
292
  userDataPath: this.context.userDataPath,
331
- retentionPolicy: {
332
- enabled: policy.enabled,
333
- maxEntries: policy.maxEntries,
334
- maxAge: policy.maxAge,
335
- },
336
293
  disk: {
337
294
  total: diskSpace.size,
338
295
  free: diskSpace.free,
@@ -362,11 +319,6 @@ export class BuildHistoryStorage implements IBuildHistoryStorage {
362
319
  newestEntry: sortedEntries[sortedEntries.length - 1]?.createdAt,
363
320
  numberOfPipelines: files.length,
364
321
  userDataPath: this.context.userDataPath,
365
- retentionPolicy: {
366
- enabled: policy.enabled,
367
- maxEntries: policy.maxEntries,
368
- maxAge: policy.maxAge,
369
- },
370
322
  disk: {
371
323
  total: diskSpace.size,
372
324
  free: diskSpace.free,
@@ -384,9 +336,14 @@ export class BuildHistoryStorage implements IBuildHistoryStorage {
384
336
  try {
385
337
  await this.ensureStoragePath();
386
338
  const files = await readdir(this.getStoragePath());
387
- return files.filter((file) => file.startsWith("pipeline-") && file.endsWith(".json"));
339
+ return files.filter((file) => file.endsWith(".history.json"));
388
340
  } catch (error) {
389
341
  return [];
390
342
  }
391
343
  }
344
+
345
+ private parsePipelineIdFromFilename(filename: string): string | null {
346
+ const match = filename.match(/^(.+)\.history\.json$/);
347
+ return match ? match[1] : null;
348
+ }
392
349
  }
@@ -1,6 +1,6 @@
1
1
  import { useAPI } from "../ipc-core";
2
2
  import { useLogger, configRegistry } from "@pipelab/shared";
3
- import { setupConfigFile, getMigrator } from "../config";
3
+ import { setupConfigFile, getMigrator, deleteConfigFile } from "../config";
4
4
  import { PipelabContext } from "../context";
5
5
  import fs from "node:fs/promises";
6
6
  import path from "node:path";
@@ -114,26 +114,7 @@ export const registerConfigHandlers = (context: PipelabContext) => {
114
114
  logger().info("config:delete", name);
115
115
 
116
116
  try {
117
- // Config could be an absolute path (for external files/pipelines)
118
- // or a name/identifier (for internal config files stored in Pipelab's app data directory).
119
- const isAbsolutePath = path.isAbsolute(name);
120
- const filesPath = isAbsolutePath ? name : context.getConfigPath(`${name}.json`);
121
-
122
- await fs.rm(filesPath, { force: true });
123
-
124
- // Clean up versioned backups too
125
- const parsedPath = path.parse(filesPath);
126
- const dirEntries = await fs.readdir(parsedPath.dir).catch(() => [] as string[]);
127
- const prefix = `${parsedPath.name}.v`;
128
- const suffix = `.json`;
129
- for (const entry of dirEntries) {
130
- if (entry.startsWith(prefix) && entry.endsWith(suffix)) {
131
- const backupPath = path.join(parsedPath.dir, entry);
132
- await fs.rm(backupPath, { force: true }).catch((err) => {
133
- logger().error(`Failed to delete backup ${backupPath}:`, err);
134
- });
135
- }
136
- }
117
+ await deleteConfigFile(name, context);
137
118
 
138
119
  send({
139
120
  type: "end",
@@ -1,10 +1,11 @@
1
1
  import { useAPI, HandleListenerSendFn } from "../ipc-core";
2
- import { PipelabContext } from "../context";
2
+ import { PipelabContext, CacheFolder } from "../context";
3
3
  import { useLogger } from "@pipelab/shared";
4
4
  import { getFinalPlugins, executeGraphWithHistory } from "../utils";
5
5
  import { presets } from "../presets/list";
6
6
  import { handleActionExecute } from "../handler-func";
7
7
  import { join } from "node:path";
8
+ import { rm } from "node:fs/promises";
8
9
  import { setupConfigFile } from "../config";
9
10
  import { AppConfig } from "@pipelab/shared";
10
11
 
@@ -78,32 +79,35 @@ export const registerEngineHandlers = (context: PipelabContext) => {
78
79
 
79
80
  handle("action:execute", async (event, { send, value }) => {
80
81
  const { nodeId, params, pluginId } = value;
81
- const settings = await setupConfigFile<AppConfig>("settings", { context });
82
- const config = await settings.getConfig();
83
-
84
- const cachePath = join(context.userDataPath, "cache", "actions", pluginId, nodeId);
82
+ const cachePath = context.getCachePath(CacheFolder.Actions, pluginId, nodeId);
85
83
  const cwd = await context.createTempFolder("action-execute-");
86
84
 
87
- const mainWindow: undefined = undefined;
88
- abortControllerGraph = new AbortController();
85
+ try {
86
+ const mainWindow: undefined = undefined;
87
+ abortControllerGraph = new AbortController();
89
88
 
90
- const signalPromise = new Promise((resolve, reject) => {
91
- abortControllerGraph!.signal.addEventListener("abort", async () => {
92
- await send({
93
- type: "end",
94
- data: {
95
- ipcError: "Action aborted",
96
- type: "error",
97
- },
89
+ const signalPromise = new Promise((resolve, reject) => {
90
+ abortControllerGraph!.signal.addEventListener("abort", async () => {
91
+ await send({
92
+ type: "end",
93
+ data: {
94
+ ipcError: "Action aborted",
95
+ type: "error",
96
+ },
97
+ });
98
+ return reject(new Error("Action interrupted"));
98
99
  });
99
- return reject(new Error("Action interrupted"));
100
100
  });
101
- });
102
101
 
103
- await Promise.race([
104
- signalPromise,
105
- effectiveActionExecute(nodeId, pluginId, params, mainWindow, send, cwd, cachePath),
106
- ]);
102
+ await Promise.race([
103
+ signalPromise,
104
+ effectiveActionExecute(nodeId, pluginId, params, mainWindow, send, cwd, cachePath),
105
+ ]);
106
+ } finally {
107
+ await rm(cwd, { recursive: true, force: true }).catch((err) => {
108
+ console.warn(`Failed to cleanup temp folder at ${cwd}:`, err);
109
+ });
110
+ }
107
111
  });
108
112
 
109
113
  handle("constants:get", async (_, { send }) => {
@@ -144,7 +148,7 @@ export const registerEngineHandlers = (context: PipelabContext) => {
144
148
  const effectiveProjectName = projectName || "Unnamed Project";
145
149
  const effectiveProjectPath = projectPath || "";
146
150
  const effectivePipelineId = pipelineId || "unknown";
147
- const effectiveCachePath = join(context.userDataPath, "cache", effectivePipelineId);
151
+ const effectiveCachePath = context.getCachePath(CacheFolder.Pipelines, effectivePipelineId);
148
152
 
149
153
  const mainWindow: undefined = undefined;
150
154
  abortControllerGraph = new AbortController();
@@ -284,44 +284,4 @@ export const registerHistoryHandlers = (context: PipelabContext) => {
284
284
  });
285
285
  }
286
286
  });
287
-
288
- handle("build-history:configure", async (_, { send, value }) => {
289
- try {
290
- logger().info("Updating build history configuration:", value.config);
291
-
292
- const settings = await setupConfigFile<AppConfig>("settings", { context });
293
- const currentConfig = await settings.getConfig();
294
-
295
- // Deep merge the new config with the existing one
296
- const newConfig = {
297
- ...currentConfig,
298
- buildHistory: {
299
- ...currentConfig?.buildHistory,
300
- retentionPolicy: {
301
- ...currentConfig?.buildHistory?.retentionPolicy,
302
- ...value.config.retentionPolicy,
303
- },
304
- },
305
- };
306
-
307
- await settings.setConfig(newConfig);
308
-
309
- send({
310
- type: "end",
311
- data: {
312
- type: "success",
313
- result: { result: "ok" },
314
- },
315
- });
316
- } catch (error) {
317
- logger().error("Failed to configure build history:", error);
318
- send({
319
- type: "end",
320
- data: {
321
- type: "error",
322
- ipcError: error instanceof Error ? error.message : "Failed to configure build history",
323
- },
324
- });
325
- }
326
- });
327
287
  };