@pipelab/core-node 1.0.0-beta.2 → 1.0.0-beta.22

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 (45) hide show
  1. package/.oxfmtrc.json +1 -1
  2. package/CHANGELOG.md +173 -0
  3. package/dist/config-Bi0ORcTK.mjs +2 -0
  4. package/dist/config-CFgGRD9U.mjs +265 -0
  5. package/dist/config-CFgGRD9U.mjs.map +1 -0
  6. package/dist/index.d.mts +74 -53
  7. package/dist/index.d.mts.map +1 -1
  8. package/dist/index.mjs +1705 -576
  9. package/dist/index.mjs.map +1 -1
  10. package/package.json +9 -5
  11. package/scratch/simulate-updates.ts +120 -0
  12. package/src/config.test.ts +232 -0
  13. package/src/config.ts +111 -50
  14. package/src/context.ts +117 -5
  15. package/src/handler-func.ts +2 -77
  16. package/src/handlers/build-history.ts +60 -90
  17. package/src/handlers/config.ts +265 -55
  18. package/src/handlers/engine.ts +32 -35
  19. package/src/handlers/history.ts +0 -40
  20. package/src/handlers/index.ts +4 -0
  21. package/src/handlers/migration.ts +621 -0
  22. package/src/handlers/plugins.ts +305 -0
  23. package/src/handlers/system.ts +7 -2
  24. package/src/index.ts +9 -2
  25. package/src/plugins-registry.ts +280 -38
  26. package/src/presets/c3toSteam.ts +13 -7
  27. package/src/presets/demo.ts +9 -9
  28. package/src/presets/list.ts +0 -6
  29. package/src/presets/moreToCome.ts +3 -2
  30. package/src/presets/newProject.ts +3 -2
  31. package/src/presets/test-c3-offline.ts +15 -6
  32. package/src/presets/test-c3-unzip.ts +19 -8
  33. package/src/runner.ts +2 -8
  34. package/src/server.ts +45 -4
  35. package/src/types/runner.ts +2 -30
  36. package/src/utils/fs-extras.ts +6 -24
  37. package/src/utils/github.test.ts +211 -0
  38. package/src/utils/github.ts +90 -10
  39. package/src/utils/remote.test.ts +209 -0
  40. package/src/utils/remote.ts +325 -87
  41. package/src/utils/storage.ts +2 -1
  42. package/src/utils.ts +20 -24
  43. package/src/migrations.ts +0 -72
  44. package/src/presets/if.ts +0 -69
  45. package/src/presets/loop.ts +0 -65
package/src/context.ts CHANGED
@@ -1,18 +1,25 @@
1
1
  import { join, dirname, resolve } from "node:path";
2
2
  import { homedir, platform } from "node:os";
3
3
  import { fileURLToPath } from "node:url";
4
- import { existsSync } from "node:fs";
4
+ import { existsSync, readFileSync } from "node:fs";
5
+ import { mkdir, realpath, mkdtemp } from "node:fs/promises";
6
+ import { SandboxFolder, DEFAULT_NODE_VERSION, DEFAULT_PNPM_VERSION } from "@pipelab/constants";
7
+
8
+ // @ts-ignore import.meta is only allowed in ES Modules
9
+ const metaUrl = typeof import.meta !== "undefined" ? import.meta.url : undefined;
5
10
 
6
11
  const _dirname =
7
12
  typeof __dirname !== "undefined"
8
13
  ? __dirname
9
- : typeof import.meta !== "undefined" && import.meta.url
10
- ? dirname(fileURLToPath(import.meta.url))
14
+ : metaUrl
15
+ ? dirname(fileURLToPath(metaUrl))
11
16
  : process.cwd();
12
17
 
13
18
  export const isDev = process.env.NODE_ENV === "development";
14
19
 
15
- export const getDefaultUserDataPath = () => {
20
+ export type PipelabEnv = "dev" | "beta" | "prod";
21
+
22
+ export const getDefaultUserDataPath = (env?: PipelabEnv) => {
16
23
  const base = (() => {
17
24
  switch (platform()) {
18
25
  case "win32":
@@ -24,7 +31,10 @@ export const getDefaultUserDataPath = () => {
24
31
  }
25
32
  })();
26
33
 
27
- return join(base, "@pipelab", isDev ? "app-dev" : "app");
34
+ const mode = env ?? (isDev ? "dev" : "prod");
35
+ const folder = mode === "dev" ? "app-dev" : mode === "beta" ? "app-beta" : "app";
36
+
37
+ return join(base, "@pipelab", folder);
28
38
  };
29
39
 
30
40
  /**
@@ -43,15 +53,26 @@ function findProjectRoot(startDir: string): string | null {
43
53
 
44
54
  export const projectRoot = findProjectRoot(_dirname);
45
55
 
56
+ export const CacheFolder = {
57
+ Actions: "actions",
58
+ Pipelines: "pipelines",
59
+ Pacote: "pacote",
60
+ } as const;
61
+
62
+ export type CacheFolderType = (typeof CacheFolder)[keyof typeof CacheFolder];
63
+
46
64
  export interface PipelabContextOptions {
47
65
  userDataPath: string;
66
+ releaseTag?: string;
48
67
  }
49
68
 
50
69
  export class PipelabContext {
51
70
  public readonly userDataPath: string;
71
+ public readonly releaseTag: string;
52
72
 
53
73
  constructor(options: PipelabContextOptions) {
54
74
  this.userDataPath = options.userDataPath;
75
+ this.releaseTag = options.releaseTag || "latest";
55
76
  }
56
77
 
57
78
  getPackagesPath(...subpaths: string[]) {
@@ -65,4 +86,95 @@ export class PipelabContext {
65
86
  getConfigPath(...subpaths: string[]) {
66
87
  return join(this.userDataPath, "config", ...subpaths);
67
88
  }
89
+
90
+ getSettingsPath() {
91
+ return this.getConfigPath("settings.json");
92
+ }
93
+
94
+ getConnectionsPath() {
95
+ return this.getConfigPath("connections.json");
96
+ }
97
+
98
+ getProjectsPath() {
99
+ return this.getConfigPath("projects.json");
100
+ }
101
+
102
+ private _cachedSettings: any = null;
103
+ private _cachedSettingsTime: number = 0;
104
+
105
+ private getSettings() {
106
+ const settingsPath = this.getSettingsPath();
107
+ if (!existsSync(settingsPath)) {
108
+ return null;
109
+ }
110
+ const now = Date.now();
111
+ if (this._cachedSettings && now - this._cachedSettingsTime < 2000) {
112
+ return this._cachedSettings;
113
+ }
114
+ try {
115
+ const content = readFileSync(settingsPath, "utf8");
116
+ this._cachedSettings = JSON.parse(content);
117
+ this._cachedSettingsTime = now;
118
+ return this._cachedSettings;
119
+ } catch (e) {
120
+ return this._cachedSettings;
121
+ }
122
+ }
123
+
124
+ getTempPath(...subpaths: string[]) {
125
+ const settings = this.getSettings();
126
+ const base = settings?.tempFolder || join(this.userDataPath, "temp");
127
+ return join(base, ...subpaths);
128
+ }
129
+
130
+ async createTempFolder(prefix = "pipelab-") {
131
+ const baseDir = this.getTempPath();
132
+ await mkdir(baseDir, { recursive: true });
133
+ const realBaseDir = await realpath(baseDir);
134
+ return await mkdtemp(join(realBaseDir, prefix));
135
+ }
136
+
137
+ getCachePath(): string;
138
+ getCachePath(folder: CacheFolderType, ...subpaths: string[]): string;
139
+ getCachePath(folder?: CacheFolderType, ...subpaths: string[]) {
140
+ const settings = this.getSettings();
141
+ const base = settings?.cacheFolder || join(this.userDataPath, "cache");
142
+ if (!folder) {
143
+ return base;
144
+ }
145
+ return join(base, folder, ...subpaths);
146
+ }
147
+
148
+ getPnpmPath(...subpaths: string[]) {
149
+ return join(this.userDataPath, "pnpm", ...subpaths);
150
+ }
151
+
152
+ getBuildHistoryPath(...subpaths: string[]) {
153
+ return join(this.userDataPath, "build-history", ...subpaths);
154
+ }
155
+
156
+ getNodePath(version = DEFAULT_NODE_VERSION) {
157
+ const isWindows = process.platform === "win32";
158
+ return this.getThirdPartyPath("node", version, isWindows ? "node.exe" : "bin/node");
159
+ }
160
+
161
+ getPnpmBinPath(version = DEFAULT_PNPM_VERSION) {
162
+ return this.getPackagesPath("pnpm", version, "bin", "pnpm.cjs");
163
+ }
164
+
165
+ getSandboxFolders(): Array<{ name: SandboxFolder; label: string; path: string }> {
166
+ const foldersRecord: Record<SandboxFolder, { label: string; path: string }> = {
167
+ [SandboxFolder.Packages]: { label: "Packages", path: this.getPackagesPath() },
168
+ [SandboxFolder.ThirdParty]: { label: "Third-Party Tools", path: this.getThirdPartyPath() },
169
+ [SandboxFolder.Config]: { label: "Configuration", path: this.getConfigPath() },
170
+ [SandboxFolder.Temp]: { label: "Temporary Files", path: this.getTempPath() },
171
+ [SandboxFolder.Cache]: { label: "Cache", path: this.getCachePath() },
172
+ [SandboxFolder.Pnpm]: { label: "PNPM Home", path: this.getPnpmPath() },
173
+ };
174
+
175
+ return (Object.keys(foldersRecord) as SandboxFolder[]).map((key) => ({
176
+ name: key,
177
+ ...foldersRecord[key],
178
+ }));
179
+ }
68
180
  }
@@ -1,13 +1,13 @@
1
1
  import { End } from "@pipelab/shared";
2
2
  import { ensureNodeJS, ensurePNPM } from "./utils/remote";
3
- import { Action, ActionRunner, Condition, ConditionRunner } from "./types/runner";
3
+ import { Action, ActionRunner } from "./types/runner";
4
4
  import { InputsDefinition } from "@pipelab/shared";
5
5
  import { usePlugins } from "@pipelab/shared";
6
6
  import { isRequired } from "@pipelab/shared";
7
7
  import { mkdir, stat } from "node:fs/promises";
8
8
  import { PipelabContext } from "./context";
9
9
  import { useLogger } from "@pipelab/shared";
10
- import { BlockCondition } from "@pipelab/shared";
10
+
11
11
  import { HandleListenerSendFn } from "./handlers";
12
12
  import path from "node:path";
13
13
  const { join } = path;
@@ -38,81 +38,6 @@ const checkParams = (definitionParams: InputsDefinition, elementParams: Record<s
38
38
  }
39
39
  };
40
40
 
41
- export const handleConditionExecute = async (
42
- nodeId: string,
43
- pluginId: string,
44
- params: BlockCondition["params"],
45
- cwd: string,
46
- context: PipelabContext,
47
- ): Promise<End<"condition:execute">> => {
48
- const ctx = context;
49
- const { plugins } = usePlugins();
50
- const { logger } = useLogger();
51
-
52
- const node = plugins.value
53
- .find((plugin) => plugin.id === pluginId)
54
- ?.nodes.find((node: any) => node.node.id === nodeId) as
55
- | {
56
- node: Condition;
57
- runner: ConditionRunner<any>;
58
- }
59
- | undefined;
60
-
61
- if (!node) {
62
- return {
63
- type: "error",
64
- ipcError: "Node not found",
65
- };
66
- }
67
-
68
- try {
69
- const s = await stat(cwd);
70
- if (!s.isDirectory()) {
71
- throw new Error(`Execution directory "${cwd}" exists but is not a directory.`);
72
- }
73
- } catch (e: any) {
74
- if (e.code === "ENOENT") {
75
- await mkdir(cwd, { recursive: true });
76
- } else {
77
- throw e;
78
- }
79
- }
80
-
81
- try {
82
- checkParams(node.node.params, params);
83
- const resolvedInputs = params; // await resolveConditionInputs(params, node.node, steps)
84
-
85
- const result = await node.runner({
86
- inputs: resolvedInputs,
87
- log: (...args) => {
88
- logger().info(`[${node.node.name}]`, ...args);
89
- },
90
- meta: {
91
- definition: "",
92
- },
93
- setMeta: () => {
94
- logger().info("set meta defined here");
95
- },
96
- cwd,
97
- context: ctx,
98
- });
99
-
100
- return {
101
- type: "success",
102
- result: {
103
- outputs: {},
104
- value: result,
105
- },
106
- };
107
- } catch (e) {
108
- logger().error("Error in condition execution:", e);
109
- return {
110
- type: "error",
111
- ipcError: String(e),
112
- };
113
- }
114
- };
115
-
116
41
  export const handleActionExecute = async (
117
42
  nodeId: string,
118
43
  pluginId: string,
@@ -1,11 +1,10 @@
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";
4
- import { tmpdir } from "node:os";
3
+ import { writeFile, readFile, unlink, mkdir, stat, readdir, rm } from "node:fs/promises";
5
4
  import { useLogger, BuildHistoryEntry, IBuildHistoryStorage, AppConfig } from "@pipelab/shared";
6
- import { setupConfigFile } from "../config";
7
5
  import checkDiskSpace from "check-disk-space";
8
6
  import { getFolderSize } from "../utils/fs-extras";
7
+ import { SandboxFolder } from "@pipelab/constants";
9
8
 
10
9
  // Simplified storage - one file per pipeline containing array of build entries
11
10
 
@@ -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,15 +260,11 @@ 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;
296
266
  pipelab: number;
267
+ folders: Array<{ name: SandboxFolder; label: string; size: number }>;
297
268
  };
298
269
  }> {
299
270
  try {
@@ -303,13 +274,15 @@ export class BuildHistoryStorage implements IBuildHistoryStorage {
303
274
  const diskSpace = await checkDiskSpace(this.context.userDataPath);
304
275
  const pipelabSize = await getFolderSize(this.context.userDataPath);
305
276
 
306
- const settings = await setupConfigFile<AppConfig>("settings", { context: this.context });
307
- const config = await settings.getConfig();
308
- const policy = config?.buildHistory?.retentionPolicy || {
309
- enabled: false,
310
- maxEntries: 50,
311
- maxAge: 30,
312
- };
277
+ const folders = [];
278
+ for (const folder of this.context.getSandboxFolders()) {
279
+ const size = await getFolderSize(folder.path);
280
+ folders.push({
281
+ name: folder.name,
282
+ label: folder.label,
283
+ size,
284
+ });
285
+ }
313
286
 
314
287
  if (allEntries.length === 0) {
315
288
  return {
@@ -317,15 +290,11 @@ export class BuildHistoryStorage implements IBuildHistoryStorage {
317
290
  totalSize: 0,
318
291
  numberOfPipelines: files.length,
319
292
  userDataPath: this.context.userDataPath,
320
- retentionPolicy: {
321
- enabled: policy.enabled,
322
- maxEntries: policy.maxEntries,
323
- maxAge: policy.maxAge,
324
- },
325
293
  disk: {
326
294
  total: diskSpace.size,
327
295
  free: diskSpace.free,
328
296
  pipelab: pipelabSize,
297
+ folders,
329
298
  },
330
299
  };
331
300
  }
@@ -350,15 +319,11 @@ export class BuildHistoryStorage implements IBuildHistoryStorage {
350
319
  newestEntry: sortedEntries[sortedEntries.length - 1]?.createdAt,
351
320
  numberOfPipelines: files.length,
352
321
  userDataPath: this.context.userDataPath,
353
- retentionPolicy: {
354
- enabled: policy.enabled,
355
- maxEntries: policy.maxEntries,
356
- maxAge: policy.maxAge,
357
- },
358
322
  disk: {
359
323
  total: diskSpace.size,
360
324
  free: diskSpace.free,
361
325
  pipelab: pipelabSize,
326
+ folders,
362
327
  },
363
328
  };
364
329
  } catch (error) {
@@ -371,9 +336,14 @@ export class BuildHistoryStorage implements IBuildHistoryStorage {
371
336
  try {
372
337
  await this.ensureStoragePath();
373
338
  const files = await readdir(this.getStoragePath());
374
- return files.filter((file) => file.startsWith("pipeline-") && file.endsWith(".json"));
339
+ return files.filter((file) => file.endsWith(".history.json"));
375
340
  } catch (error) {
376
341
  return [];
377
342
  }
378
343
  }
344
+
345
+ private parsePipelineIdFromFilename(filename: string): string | null {
346
+ const match = filename.match(/^(.+)\.history\.json$/);
347
+ return match ? match[1] : null;
348
+ }
379
349
  }