@pipelab/core-node 1.0.0-beta.26 → 1.0.0-beta.30

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.26",
3
+ "version": "1.0.0-beta.30",
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.23",
44
- "@pipelab/shared": "1.0.0-beta.22"
43
+ "@pipelab/constants": "1.0.0-beta.26",
44
+ "@pipelab/shared": "1.0.0-beta.25"
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.21"
59
+ "@pipelab/tsconfig": "1.0.0-beta.24"
60
60
  },
61
61
  "scripts": {
62
62
  "build": "tsdown",
package/src/context.ts CHANGED
@@ -66,11 +66,13 @@ export interface PipelabContextOptions {
66
66
  releaseTag?: string;
67
67
  }
68
68
 
69
- type Join<T extends string[], D extends string> =
70
- T extends [] ? "" :
71
- T extends [infer F extends string] ? F :
72
- T extends [infer F extends string, ...infer R extends string[]] ? `${F}${D}${Join<R, D>}` :
73
- string;
69
+ type Join<T extends string[], D extends string> = T extends []
70
+ ? ""
71
+ : T extends [infer F extends string]
72
+ ? F
73
+ : T extends [infer F extends string, ...infer R extends string[]]
74
+ ? `${F}${D}${Join<R, D>}`
75
+ : string;
74
76
 
75
77
  export class PipelabContext {
76
78
  public readonly userDataPath: string;
@@ -146,7 +148,10 @@ export class PipelabContext {
146
148
  }
147
149
 
148
150
  getCachePath(): `CACHE/`;
149
- getCachePath<F extends CacheFolderType, S extends string[]>(folder: F, ...subpaths: S): `CACHE/${F}/${Join<S, "/">}`;
151
+ getCachePath<F extends CacheFolderType, S extends string[]>(
152
+ folder: F,
153
+ ...subpaths: S
154
+ ): `CACHE/${F}/${Join<S, "/">}`;
150
155
  getCachePath(folder?: CacheFolderType, ...subpaths: string[]): string {
151
156
  const settings = this.getSettings();
152
157
  const base = settings?.cacheFolder || join(this.userDataPath, "cache");
@@ -166,13 +171,17 @@ export class PipelabContext {
166
171
  return join(this.userDataPath, "build-history", ...subpaths);
167
172
  }
168
173
 
169
- getNodePath<V extends string = typeof DEFAULT_NODE_VERSION>(version?: V): `THIRDPARTY/node/${V}/${string}`;
174
+ getNodePath<V extends string = typeof DEFAULT_NODE_VERSION>(
175
+ version?: V,
176
+ ): `THIRDPARTY/node/${V}/${string}`;
170
177
  getNodePath(version = DEFAULT_NODE_VERSION): string {
171
178
  const isWindows = process.platform === "win32";
172
179
  return this.getThirdPartyPath("node", version, isWindows ? "node.exe" : "bin/node");
173
180
  }
174
181
 
175
- getPnpmBinPath<V extends string = typeof DEFAULT_PNPM_VERSION>(version?: V): `PACKAGES/pnpm/${V}/bin/pnpm.cjs`;
182
+ getPnpmBinPath<V extends string = typeof DEFAULT_PNPM_VERSION>(
183
+ version?: V,
184
+ ): `PACKAGES/pnpm/${V}/bin/pnpm.cjs`;
176
185
  getPnpmBinPath(version = DEFAULT_PNPM_VERSION): string {
177
186
  return this.getPackagesPath("pnpm", version, "bin", "pnpm.cjs");
178
187
  }
@@ -20,4 +20,30 @@ export const registerSystemHandlers = (options: { version: string; context: Pipe
20
20
  },
21
21
  });
22
22
  });
23
+
24
+ handle("system:packages:cleanup", async (_, { send }) => {
25
+ try {
26
+ const packagesDir = options.context.getPackagesPath();
27
+ const { existsSync } = await import("fs");
28
+ const { rm } = await import("fs/promises");
29
+ if (existsSync(packagesDir)) {
30
+ await rm(packagesDir, { recursive: true, force: true });
31
+ }
32
+ send({
33
+ type: "end",
34
+ data: {
35
+ type: "success",
36
+ result: true,
37
+ },
38
+ });
39
+ } catch (e: any) {
40
+ send({
41
+ type: "end",
42
+ data: {
43
+ type: "error",
44
+ ipcError: e.message,
45
+ },
46
+ });
47
+ }
48
+ });
23
49
  };
@@ -77,18 +77,57 @@ export const zipFolder = async (
77
77
  from: string,
78
78
  to: string,
79
79
  log: typeof console.log = console.log,
80
+ abortSignal?: AbortSignal,
80
81
  ) => {
82
+ if (abortSignal?.aborted) {
83
+ const abortError = new Error("Aborted");
84
+ abortError.name = "AbortError";
85
+ throw abortError;
86
+ }
87
+
81
88
  const output = createWriteStream(to);
82
89
  const archive = archiver("zip", { zlib: { level: 9 } });
90
+
83
91
  return new Promise<string>((resolve, reject) => {
92
+ const onAbort = () => {
93
+ try {
94
+ archive.abort();
95
+ } catch {}
96
+ try {
97
+ output.destroy();
98
+ } catch {}
99
+ const abortError = new Error("Aborted");
100
+ abortError.name = "AbortError";
101
+ reject(abortError);
102
+ };
103
+
104
+ if (abortSignal) {
105
+ abortSignal.addEventListener("abort", onAbort);
106
+ }
107
+
84
108
  output.on("close", () => {
109
+ if (abortSignal) {
110
+ abortSignal.removeEventListener("abort", onAbort);
111
+ }
85
112
  log(archive.pointer() + " total bytes");
86
113
  resolve(to);
87
114
  });
88
- archive.on("error", reject);
115
+
116
+ archive.on("error", (err) => {
117
+ if (abortSignal) {
118
+ abortSignal.removeEventListener("abort", onAbort);
119
+ }
120
+ reject(err);
121
+ });
122
+
89
123
  archive.pipe(output);
90
124
  archive.directory(from, false);
91
- archive.finalize();
125
+ archive.finalize().catch((err) => {
126
+ if (abortSignal) {
127
+ abortSignal.removeEventListener("abort", onAbort);
128
+ }
129
+ reject(err);
130
+ });
92
131
  });
93
132
  };
94
133
 
@@ -149,7 +188,7 @@ export const runWithLiveLogs = async (
149
188
  TERM: "xterm-256color",
150
189
  FORCE_STDERR_LOGGING: "1",
151
190
  },
152
- cancelSignal: abortSignal,
191
+ cancelSignal: abortSignal ?? execaOptions.cancelSignal,
153
192
  });
154
193
 
155
194
  hooks?.onCreated?.(subprocess);
@@ -7,9 +7,15 @@ import {
7
7
 
8
8
  describe("GitHub Release Updates API", () => {
9
9
  let fetchSpy: any;
10
+ let consoleLogSpy: any;
11
+ let consoleWarnSpy: any;
12
+ let consoleErrorSpy: any;
10
13
 
11
14
  beforeEach(() => {
12
15
  fetchSpy = vi.spyOn(global, "fetch");
16
+ consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {});
17
+ consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
18
+ consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
13
19
  // Clean up environment variables
14
20
  delete process.env.PIPELAB_OVERRIDE_RELEASE;
15
21
  });
@@ -96,6 +96,27 @@ export type FetchOptions = {
96
96
  context: PipelabContext;
97
97
  };
98
98
 
99
+ function allowPrereleaseInRange(rangeStr: string): string {
100
+ try {
101
+ const r = new semver.Range(rangeStr);
102
+ return r.set
103
+ .map((conj) =>
104
+ conj
105
+ .map((c) => {
106
+ const v = c.semver;
107
+ if (v.prerelease && v.prerelease.length) {
108
+ return c.operator + v.format();
109
+ }
110
+ return c.operator + v.major + "." + v.minor + "." + v.patch + "-0";
111
+ })
112
+ .join(" "),
113
+ )
114
+ .join(" || ");
115
+ } catch {
116
+ return rangeStr;
117
+ }
118
+ }
119
+
99
120
  /**
100
121
  * Robust utility to fetch, cache, and resolve an NPM package.
101
122
  * Centralized in core-node to avoid circular dependencies.
@@ -131,6 +152,7 @@ export async function fetchPackage(
131
152
  const ctx = options.context;
132
153
  const baseDir = ctx.getPackagesPath(packageName);
133
154
  let resolvedVersion: string;
155
+ const includePrerelease = !!(ctx.releaseTag && ctx.releaseTag !== "latest");
134
156
 
135
157
  let resolvedVersionOrRange = versionOrRange;
136
158
  if (resolvedVersionOrRange === "local") {
@@ -151,6 +173,7 @@ export async function fetchPackage(
151
173
  new Error("Offline"),
152
174
  baseDir,
153
175
  packageName,
176
+ includePrerelease,
154
177
  );
155
178
  if (fallbackVersion) {
156
179
  resolvedVersion = fallbackVersion;
@@ -175,7 +198,41 @@ export async function fetchPackage(
175
198
  const range = resolvedVersionOrRange || "latest";
176
199
 
177
200
  // Prioritize tags (like 'latest', 'beta', etc.) over semver ranges
178
- let foundVersion = packument["dist-tags"]?.[range] || semver.maxSatisfying(versions, range);
201
+ let foundVersion =
202
+ packument["dist-tags"]?.[range] ||
203
+ semver.maxSatisfying(versions, range, { includePrerelease });
204
+
205
+ // If we are in a non-latest releaseTag channel (like "beta" or "dev"),
206
+ // and we are resolving a range, we can prefer the releaseTag version if it exists
207
+ // and satisfies the range (ignoring strict prerelease constraints if includePrerelease is true).
208
+ if (!foundVersion && ctx.releaseTag && ctx.releaseTag !== "latest") {
209
+ const releaseTagVersion = packument["dist-tags"]?.[ctx.releaseTag];
210
+ if (releaseTagVersion && semver.valid(releaseTagVersion)) {
211
+ const rewrittenRangeForCheck = allowPrereleaseInRange(range);
212
+ if (
213
+ semver.satisfies(releaseTagVersion, rewrittenRangeForCheck, { includePrerelease: true })
214
+ ) {
215
+ console.log(
216
+ `[Fetcher] Using release tag "${ctx.releaseTag}" (${releaseTagVersion}) for ${packageName}@${range} because it satisfies the range`,
217
+ );
218
+ foundVersion = releaseTagVersion;
219
+ }
220
+ }
221
+ }
222
+
223
+ // If we still don't have a version, and includePrerelease is true, try rewriting the range to allow prerelease matching
224
+ if (!foundVersion && includePrerelease) {
225
+ const rewrittenRange = allowPrereleaseInRange(range);
226
+ if (rewrittenRange !== range) {
227
+ const matched = semver.maxSatisfying(versions, rewrittenRange, { includePrerelease });
228
+ if (matched) {
229
+ console.log(
230
+ `[Fetcher] Resolved ${packageName}@${range} to ${matched} via rewritten range ${rewrittenRange}`,
231
+ );
232
+ foundVersion = matched;
233
+ }
234
+ }
235
+ }
179
236
 
180
237
  // If we are in a non-latest releaseTag channel (like "beta" or "dev"),
181
238
  // and we are resolving "latest", we prefer the releaseTag version if it exists
@@ -218,6 +275,7 @@ export async function fetchPackage(
218
275
  error,
219
276
  baseDir,
220
277
  packageName,
278
+ includePrerelease,
221
279
  );
222
280
  if (fallbackVersion) {
223
281
  resolvedVersion = fallbackVersion;
@@ -490,17 +548,10 @@ async function installDependencies(packageDir: string, packageName: string, opti
490
548
  return;
491
549
  }
492
550
 
493
- // To prevent other processes from seeing a partially populated node_modules,
494
- // we can create a temp directory next to packageDir, run pnpm there, and rename node_modules
495
- const tempDir = `${packageDir}.tmp-deps-${Math.random().toString(36).slice(2)}`;
496
- await mkdir(tempDir, { recursive: true });
497
551
  try {
498
- // Copy package.json to tempDir so pnpm can install dependencies
499
- await cp(join(packageDir, "package.json"), join(tempDir, "package.json"));
500
-
501
552
  console.log(`[Fetcher] ${packageName}: Ensuring dependencies are installed...`);
502
553
  const pnpmStart = Date.now();
503
- const { all } = await runPnpm(tempDir, {
554
+ const { all } = await runPnpm(packageDir, {
504
555
  signal: options.signal,
505
556
  context: options.context,
506
557
  });
@@ -508,24 +559,17 @@ async function installDependencies(packageDir: string, packageName: string, opti
508
559
 
509
560
  if (all) console.log(`[Fetcher] ${packageName}: Installation trace:\n${all}`);
510
561
 
511
- // Atomically rename node_modules from tempDir to packageDir/node_modules
512
- const tempNodeModules = join(tempDir, "node_modules");
513
- if (existsSync(nodeModulesPath)) {
514
- await rm(nodeModulesPath, { recursive: true, force: true }).catch(() => {});
515
- }
516
- const renameStart = Date.now();
517
- await rename(tempNodeModules, nodeModulesPath);
518
562
  console.log(
519
- `[Fetcher] ${packageName}: Dependencies installed successfully (rename took ${Date.now() - renameStart}ms, total installDependencies took ${Date.now() - start}ms).`,
563
+ `[Fetcher] ${packageName}: Dependencies installed successfully (total installDependencies took ${Date.now() - start}ms).`,
520
564
  );
521
565
  } catch (err: any) {
522
566
  console.error(
523
567
  `[Fetcher] ${packageName}: CRITICAL ERROR during dependency installation: ${err.message}`,
524
568
  );
525
569
  if (err.all) console.error(`[Fetcher] ${packageName}: Error details:\n${err.all}`);
570
+ // Clean up node_modules on error to allow retries
571
+ await rm(nodeModulesPath, { recursive: true, force: true }).catch(() => {});
526
572
  throw new Error(`Failed to install dependencies for ${packageName}. See logs for details.`);
527
- } finally {
528
- await rm(tempDir, { recursive: true, force: true }).catch(() => {});
529
573
  }
530
574
  }
531
575
 
@@ -675,6 +719,7 @@ async function tryLocalFallback(
675
719
  _error: unknown,
676
720
  baseDir: string,
677
721
  logPrefix: string,
722
+ includePrerelease = false,
678
723
  ): Promise<string | null> {
679
724
  if (!existsSync(baseDir)) return null;
680
725
  try {
@@ -696,7 +741,7 @@ async function tryLocalFallback(
696
741
  return latestLocal;
697
742
  }
698
743
  } else {
699
- const matched = semver.maxSatisfying(localVersions, range);
744
+ const matched = semver.maxSatisfying(localVersions, range, { includePrerelease });
700
745
  if (matched) {
701
746
  console.info(
702
747
  `[Fetcher] ${logPrefix}: Using locally cached matching version: ${matched} for range ${range}`,
package/tsconfig.json CHANGED
@@ -8,8 +8,6 @@
8
8
  "sourceMap": true,
9
9
  "baseUrl": ".",
10
10
  "paths": {
11
- "@pipelab/shared": ["../shared/src"],
12
- "@pipelab/constants": ["../constants/src/index.ts"],
13
11
  "@pipelab/*": ["../shared/src/libs/*"]
14
12
  }
15
13
  },
@@ -0,0 +1,12 @@
1
+ import { defineConfig } from "tsdown";
2
+
3
+ export default defineConfig({
4
+ entry: ["src/index.ts"],
5
+ format: ["esm", "cjs"],
6
+ dts: {
7
+ tsconfigRaw: {
8
+ references: [],
9
+ },
10
+ },
11
+ clean: true,
12
+ });
@@ -1,2 +0,0 @@
1
- import { c as setupSettingsConfigFile } from "./config-CFgGRD9U.mjs";
2
- export { setupSettingsConfigFile };
@@ -1 +0,0 @@
1
- {"version":3,"file":"config-CFgGRD9U.mjs","names":["mkdirP","fs"],"sources":["../src/utils/fs-extras.ts","../src/config.ts"],"sourcesContent":["import { createWriteStream } from \"node:fs\";\nimport { execa, Options, Subprocess } from \"execa\";\nimport { mkdir as mkdirP, writeFile, stat, readdir } from \"node:fs/promises\";\nimport { join, dirname } from \"node:path\";\nimport tar from \"tar\";\nimport yauzl from \"yauzl\";\nimport archiver from \"archiver\";\nimport { pipeline } from \"node:stream/promises\";\n\n/**\n * Ensures a directory exists and a file is created with default content if missing.\n */\nexport const ensure = async (filesPath: string, defaultContent = \"{}\") => {\n await mkdirP(dirname(filesPath), { recursive: true });\n try {\n const s = await stat(filesPath);\n if (s.size === 0) {\n await writeFile(filesPath, defaultContent);\n }\n } catch {\n await writeFile(filesPath, defaultContent);\n }\n};\n\n/**\n * Extracts a .tar.gz archive.\n */\nexport async function extractTarGz(archivePath: string, destinationDir: string): Promise<void> {\n await mkdirP(destinationDir, { recursive: true });\n await tar.x({\n file: archivePath,\n cwd: destinationDir,\n });\n}\n\n/**\n * Extracts a .zip archive.\n */\nexport async function extractZip(archivePath: string, destinationDir: string): Promise<void> {\n await mkdirP(destinationDir, { recursive: true });\n return new Promise((resolve, reject) => {\n yauzl.open(archivePath, { lazyEntries: true }, (err, zipfile) => {\n if (err || !zipfile) return reject(err || new Error(\"Could not open zip file\"));\n zipfile.on(\"error\", reject);\n zipfile.readEntry();\n zipfile.on(\"entry\", (entry) => {\n const entryPath = join(destinationDir, entry.fileName);\n if (/\\/$/.test(entry.fileName)) {\n mkdirP(entryPath, { recursive: true })\n .then(() => zipfile.readEntry())\n .catch(reject);\n } else {\n mkdirP(dirname(entryPath), { recursive: true })\n .then(() => {\n zipfile.openReadStream(entry, (err, readStream) => {\n if (err || !readStream)\n return reject(err || new Error(\"Could not open read stream\"));\n readStream.on(\"error\", reject);\n const writeStream = createWriteStream(entryPath);\n writeStream.on(\"error\", reject);\n writeStream.on(\"close\", () => zipfile.readEntry());\n readStream.pipe(writeStream);\n });\n })\n .catch(reject);\n }\n });\n zipfile.on(\"end\", () => resolve());\n });\n });\n}\n\n/**\n * Zips a folder.\n */\nexport const zipFolder = async (\n from: string,\n to: string,\n log: typeof console.log = console.log,\n) => {\n const output = createWriteStream(to);\n const archive = archiver(\"zip\", { zlib: { level: 9 } });\n return new Promise<string>((resolve, reject) => {\n output.on(\"close\", () => {\n log(archive.pointer() + \" total bytes\");\n resolve(to);\n });\n archive.on(\"error\", reject);\n archive.pipe(output);\n archive.directory(from, false);\n archive.finalize();\n });\n};\n\n/**\n * Downloads a file with progress tracking.\n */\nexport interface DownloadHooks {\n onProgress?: (data: { progress: number; downloadedSize: number }) => void;\n}\n\nexport const downloadFile = async (\n url: string,\n localPath: string,\n hooks?: DownloadHooks,\n abortSignal?: AbortSignal,\n): Promise<void> => {\n const response = await fetch(url, { signal: abortSignal });\n if (!response.ok) throw new Error(`Failed to fetch file: ${response.statusText}`);\n const contentLength = response.headers.get(\"content-length\");\n if (!contentLength) throw new Error(\"Content-Length header is missing\");\n const totalSize = parseInt(contentLength, 10);\n let downloadedSize = 0;\n const fileStream = createWriteStream(localPath);\n const progressStream = new TransformStream({\n transform(chunk, controller) {\n downloadedSize += chunk.length;\n const progress = (downloadedSize / totalSize) * 100;\n hooks?.onProgress?.({ progress, downloadedSize });\n controller.enqueue(chunk);\n },\n });\n const readable = response.body?.pipeThrough(progressStream);\n if (!readable) throw new Error(\"Failed to create a readable stream\");\n await pipeline(readable, fileStream);\n};\n\nexport const runWithLiveLogs = async (\n command: string,\n args: string[],\n execaOptions: Options,\n log: typeof console.log,\n hooks?: {\n onStdout?: (data: string, subprocess: Subprocess) => void;\n onStderr?: (data: string, subprocess: Subprocess) => void;\n onExit?: (code: number) => void;\n onCreated?: (subprocess: Subprocess) => void;\n },\n abortSignal?: AbortSignal,\n): Promise<void> => {\n const subprocess = execa(command, args, {\n ...execaOptions,\n stdout: \"pipe\",\n stderr: \"pipe\",\n stdin: \"pipe\",\n env: {\n ...process.env,\n ...execaOptions.env,\n TERM: \"xterm-256color\",\n FORCE_STDERR_LOGGING: \"1\",\n },\n cancelSignal: abortSignal,\n });\n\n hooks?.onCreated?.(subprocess);\n\n subprocess.stdout?.on(\"data\", (data: Buffer) => {\n hooks?.onStdout?.(data.toString(), subprocess);\n });\n\n subprocess.stderr?.on(\"data\", (data: Buffer) => {\n hooks?.onStderr?.(data.toString(), subprocess);\n });\n\n try {\n const { exitCode } = await subprocess;\n hooks?.onExit?.(exitCode ?? 0);\n } catch (error: any) {\n const code = error.exitCode ?? 1;\n hooks?.onExit?.(code);\n throw new Error(`Command failed with exit code ${code}: ${error.message}`);\n }\n};\n\n/**\n * Calculates the total size of a directory recursively.\n */\nexport async function getFolderSize(dirPath: string): Promise<number> {\n try {\n const files = await readdir(dirPath, { withFileTypes: true });\n const ArrayOfPromises = files.map(async (file) => {\n const path = join(dirPath, file.name);\n if (file.isDirectory()) {\n try {\n return await getFolderSize(path);\n } catch {\n return 0;\n }\n }\n try {\n const { size } = await stat(path);\n return size;\n } catch {\n return 0;\n }\n });\n const results = await Promise.all(ArrayOfPromises);\n return results.reduce((acc, size) => acc + size, 0);\n } catch {\n return 0;\n }\n}\n","import { PipelabContext } from \"./context\";\nimport path from \"node:path\";\nimport { ensure } from \"./utils/fs-extras\";\nimport fs from \"node:fs/promises\";\nimport {\n useLogger,\n Migrator,\n AppConfig,\n ConnectionsConfig,\n FileRepo,\n SavedFile,\n appSettingsMigrator,\n connectionsMigrator,\n fileRepoMigrations,\n savedFileMigrator,\n} from \"@pipelab/shared\";\n\nexport const setupConfigFile = async <T>(\n filesPath: string,\n options: { context: PipelabContext; migrator: Migrator<T> },\n) => {\n const ctx = options.context;\n const parsedPath = path.parse(filesPath);\n const migrator = options.migrator;\n\n await ensure(filesPath, JSON.stringify(migrator.defaultValue));\n\n return {\n setConfig: async (config: T) => {\n const { logger } = useLogger();\n try {\n await fs.writeFile(filesPath, JSON.stringify(config));\n return true;\n } catch (e) {\n logger().error(`Error saving config ${parsedPath.name}:`, e);\n return false;\n }\n },\n getConfig: async () => {\n const { logger } = useLogger();\n let content = undefined;\n let originalJson: any = undefined;\n let parseFailed = false;\n\n try {\n content = await fs.readFile(filesPath, \"utf8\");\n if (content !== undefined) {\n originalJson = JSON.parse(content);\n }\n } catch (e) {\n logger().error(`Error reading or parsing config ${parsedPath.name}:`, e);\n parseFailed = true;\n }\n\n let json: any = undefined;\n let migrationFailed = false;\n try {\n if (!parseFailed) {\n json = await migrator.migrate(originalJson, {\n debug: false,\n onStep: async (state: any, version: string) => {\n const versionedPath = path.join(\n parsedPath.dir,\n `${parsedPath.name}.v${version}.json`,\n );\n try {\n await fs.writeFile(versionedPath, JSON.stringify(state));\n logger().info(\n `Intermediate backup created for ${parsedPath.name} at ${versionedPath}`,\n );\n } catch (e) {\n logger().error(\n `Failed to create intermediate backup for ${parsedPath.name} at v${version}:`,\n e,\n );\n }\n },\n });\n } else {\n json = migrator.defaultValue;\n }\n } catch (e) {\n logger().error(`Error migrating config ${parsedPath.name}:`, e);\n migrationFailed = true;\n json = migrator.defaultValue;\n }\n\n const originalVersion = originalJson?.version;\n const newVersion = json?.version;\n\n const shouldSaveBack =\n originalVersion !== newVersion || content === undefined || parseFailed || migrationFailed;\n\n if (shouldSaveBack) {\n if (parseFailed || migrationFailed) {\n try {\n const timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n const corruptedPath = path.join(\n parsedPath.dir,\n `${parsedPath.name}.corrupted.${timestamp}.json`,\n );\n const backupContent = parseFailed\n ? content || \"\"\n : JSON.stringify(originalJson, null, 2);\n await fs.writeFile(corruptedPath, backupContent);\n logger().info(`Corrupted config file preserved at ${corruptedPath}`);\n } catch (e) {\n logger().error(`Failed to backup corrupted config ${parsedPath.name}:`, e);\n }\n }\n\n try {\n await fs.writeFile(filesPath, JSON.stringify(json));\n } catch (e) {\n logger().error(`Error saving migrated config ${parsedPath.name}:`, e);\n }\n }\n\n return json as T;\n },\n };\n};\n\nexport const setupSettingsConfigFile = (context: PipelabContext) => {\n return setupConfigFile<AppConfig>(context.getSettingsPath(), {\n context,\n migrator: appSettingsMigrator,\n });\n};\n\nexport const setupConnectionsConfigFile = (context: PipelabContext) => {\n return setupConfigFile<ConnectionsConfig>(context.getConnectionsPath(), {\n context,\n migrator: connectionsMigrator,\n });\n};\n\nexport const setupProjectsConfigFile = (context: PipelabContext) => {\n return setupConfigFile<FileRepo>(context.getProjectsPath(), {\n context,\n migrator: fileRepoMigrations,\n });\n};\n\nexport const setupPipelineConfigFileByName = (name: string, context: PipelabContext) => {\n const filesPath = context.getConfigPath(`${name}.json`);\n return setupConfigFile<SavedFile>(filesPath, {\n context,\n migrator: savedFileMigrator,\n });\n};\n\nexport const setupPipelineConfigFileByPath = (absolutePath: string, context: PipelabContext) => {\n return setupConfigFile<SavedFile>(absolutePath, {\n context,\n migrator: savedFileMigrator,\n });\n};\n\nconst deleteConfigFile = async (filesPath: string) => {\n await fs.rm(filesPath, { force: true });\n};\n\nexport const deletePipelineConfigFileByName = async (name: string, context: PipelabContext) => {\n const filesPath = context.getConfigPath(`${name}.json`);\n await deleteConfigFile(filesPath);\n};\n\nexport const deletePipelineConfigFileByPath = async (\n absolutePath: string,\n context: PipelabContext,\n) => {\n await deleteConfigFile(absolutePath);\n};\n"],"mappings":"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"}