@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pipelab/core-node",
3
- "version": "1.0.0-beta.2",
3
+ "version": "1.0.0-beta.22",
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.1",
44
- "@pipelab/shared": "1.0.0-beta.1"
43
+ "@pipelab/constants": "1.0.0-beta.20",
44
+ "@pipelab/shared": "1.0.0-beta.18"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@types/adm-zip": "0.5.7",
@@ -52,13 +52,17 @@
52
52
  "@types/tar": "7.0.87",
53
53
  "@types/ws": "8.18.1",
54
54
  "@types/yauzl": "2.10.3",
55
+ "memfs": "4.57.6",
55
56
  "tsdown": "0.21.2",
56
57
  "typescript": "^5.0.0",
57
- "@pipelab/tsconfig": "1.0.0-beta.1"
58
+ "vitest": "3.1.4",
59
+ "@pipelab/tsconfig": "1.0.0-beta.18"
58
60
  },
59
61
  "scripts": {
60
62
  "build": "tsdown",
61
63
  "format": "oxfmt .",
62
- "lint": "oxlint ."
64
+ "lint": "oxlint .",
65
+ "typecheck": "tsc -b",
66
+ "test": "vitest run"
63
67
  }
64
68
  }
@@ -0,0 +1,120 @@
1
+ import pacote from "pacote";
2
+ import semver from "semver";
3
+ import { fetchLatestDesktopRelease } from "../src/utils/github.js";
4
+
5
+ const PACKAGES = [
6
+ // Apps & Core
7
+ "@pipelab/cli",
8
+ "@pipelab/ui",
9
+ "@pipelab/shared",
10
+ "@pipelab/core-node",
11
+ "@pipelab/constants",
12
+ "@pipelab/migration",
13
+
14
+ // Plugins
15
+ "@pipelab/plugin-construct",
16
+ "@pipelab/plugin-core",
17
+ "@pipelab/plugin-discord",
18
+ "@pipelab/plugin-electron",
19
+ "@pipelab/plugin-filesystem",
20
+ "@pipelab/plugin-itch",
21
+ "@pipelab/plugin-minify",
22
+ "@pipelab/plugin-netlify",
23
+ "@pipelab/plugin-nvpatch",
24
+ "@pipelab/plugin-poki",
25
+ "@pipelab/plugin-steam",
26
+ "@pipelab/plugin-system",
27
+ "@pipelab/plugin-tauri",
28
+
29
+ // Assets
30
+ "@pipelab/asset-discord",
31
+ "@pipelab/asset-electron",
32
+ "@pipelab/asset-netlify",
33
+ "@pipelab/asset-tauri",
34
+ ];
35
+
36
+ async function simulate() {
37
+ console.log("==========================================");
38
+ console.log(" PIPELAB UPDATE FLOW SIMULATOR ");
39
+ console.log("==========================================\n");
40
+
41
+ // 1. Simulate Desktop GitHub Release Update
42
+ console.log("--- 1. Simulating Desktop Application (GitHub Releases) ---");
43
+ try {
44
+ const stableApp = await fetchLatestDesktopRelease({ allowPrerelease: false });
45
+ const betaApp = await fetchLatestDesktopRelease({ allowPrerelease: true });
46
+
47
+ console.log(
48
+ `[STABLE] Resolved version: ${stableApp ? stableApp.tag_name : "None (Stable users protected)"}`,
49
+ );
50
+ console.log(`[BETA] Resolved version: ${betaApp ? betaApp.tag_name : "None"}`);
51
+ } catch (error) {
52
+ console.error("Failed to fetch desktop releases from GitHub:", error);
53
+ }
54
+
55
+ console.log("\n--- 2. Simulating NPM Packages (CLI, UI, Plugins, Assets) ---");
56
+ console.log("Fetching data from NPM registry. Please wait...\n");
57
+
58
+ const results: Array<{
59
+ package: string;
60
+ stable: string;
61
+ beta: string;
62
+ notes: string;
63
+ }> = [];
64
+
65
+ for (const pkg of PACKAGES) {
66
+ try {
67
+ const packument = await pacote.packument(pkg);
68
+ const stableVersion = packument["dist-tags"]?.latest || "N/A";
69
+ const betaVersion = packument["dist-tags"]?.beta || "N/A";
70
+
71
+ let notes = "";
72
+ if (stableVersion === "N/A" && betaVersion === "N/A") {
73
+ notes = "⚠️ Package not published on NPM";
74
+ } else if (betaVersion !== "N/A" && stableVersion !== "N/A") {
75
+ if (betaVersion === stableVersion) {
76
+ notes = "Stable and Beta are in sync";
77
+ } else {
78
+ // Attempt semver comparison
79
+ const cleanStable = semver.coerce(stableVersion);
80
+ const cleanBeta = semver.coerce(betaVersion);
81
+ if (cleanStable && cleanBeta) {
82
+ const comp = semver.compare(stableVersion, betaVersion);
83
+ if (comp < 0) {
84
+ notes = "🚀 Beta is ahead";
85
+ } else if (comp > 0) {
86
+ notes = "Stable is ahead";
87
+ } else {
88
+ notes = "Stable and Beta are in sync (coerced)";
89
+ }
90
+ } else {
91
+ notes = "Different versions (non-semver)";
92
+ }
93
+ }
94
+ } else if (betaVersion !== "N/A" && stableVersion === "N/A") {
95
+ notes = "Only Beta version exists";
96
+ } else if (stableVersion !== "N/A" && betaVersion === "N/A") {
97
+ notes = "No Beta tag published";
98
+ }
99
+
100
+ results.push({
101
+ package: pkg,
102
+ stable: stableVersion,
103
+ beta: betaVersion,
104
+ notes,
105
+ });
106
+ } catch (error: any) {
107
+ results.push({
108
+ package: pkg,
109
+ stable: "ERROR",
110
+ beta: "ERROR",
111
+ notes: `Failed to fetch: ${error.message}`,
112
+ });
113
+ }
114
+ }
115
+
116
+ console.table(results);
117
+ console.log("\n==========================================\n");
118
+ }
119
+
120
+ simulate().catch(console.error);
@@ -0,0 +1,232 @@
1
+ import { describe, test, expect, beforeEach, vi } from "vitest";
2
+ import {
3
+ setupConfigFile,
4
+ setupProjectsConfigFile,
5
+ deletePipelineConfigFileByName,
6
+ deletePipelineConfigFileByPath,
7
+ } from "./config";
8
+ import { FileRepo } from "@pipelab/shared";
9
+ import { PipelabContext } from "./context";
10
+ import fs from "node:fs/promises";
11
+ import { existsSync } from "node:fs";
12
+ import path from "node:path";
13
+ import { vol } from "memfs";
14
+
15
+ // Mock the node:fs and node:fs/promises modules to use memfs
16
+ vi.mock("node:fs", async () => {
17
+ const memfs = await import("memfs");
18
+ return {
19
+ ...memfs.fs,
20
+ default: memfs.fs,
21
+ };
22
+ });
23
+
24
+ vi.mock("node:fs/promises", async () => {
25
+ const memfs = await import("memfs");
26
+ return {
27
+ ...memfs.fs.promises,
28
+ default: memfs.fs.promises,
29
+ };
30
+ });
31
+
32
+ describe("setupConfigFile & Backup Creation", () => {
33
+ let tempDir: string;
34
+ let context: PipelabContext;
35
+
36
+ beforeEach(() => {
37
+ vol.reset();
38
+ tempDir = "/tmp/pipelab-test-config";
39
+ context = new PipelabContext({ userDataPath: tempDir });
40
+ });
41
+
42
+ test("should migrate config and create backup files on disk", async () => {
43
+ // 1. Setup V1 configuration file in the context's config path
44
+ const configDir = context.getConfigPath();
45
+ await fs.mkdir(configDir, { recursive: true });
46
+
47
+ const projectsFilePath = context.getProjectsPath();
48
+
49
+ const v1Config = {
50
+ version: "1.0.0",
51
+ data: {
52
+ "pipeline-1": {
53
+ project: "main",
54
+ type: "internal",
55
+ configName: "pipeline-1",
56
+ lastModified: "2026-06-04",
57
+ },
58
+ },
59
+ };
60
+
61
+ await fs.writeFile(projectsFilePath, JSON.stringify(v1Config));
62
+
63
+ // 2. Initialize setupConfigFile
64
+ const configInstance = await setupProjectsConfigFile(context);
65
+
66
+ // Verify file exists
67
+ expect(existsSync(projectsFilePath)).toBe(true);
68
+
69
+ // 3. Retrieve config (this triggers the migration process and onStep callback)
70
+ const migratedConfig = await configInstance.getConfig();
71
+
72
+ // 4. Assert the main config is updated on disk to version 3.0.0
73
+ expect(migratedConfig.version).toBe("3.0.0");
74
+
75
+ const updatedContent = JSON.parse(await fs.readFile(projectsFilePath, "utf8"));
76
+ expect(updatedContent.version).toBe("3.0.0");
77
+
78
+ // 5. Assert that the intermediate backup file was created on disk
79
+ const backupFilePath = path.join(configDir, "projects.v2.0.0.json");
80
+ expect(existsSync(backupFilePath)).toBe(true);
81
+
82
+ const backupContent = JSON.parse(await fs.readFile(backupFilePath, "utf8"));
83
+ expect(backupContent.version).toBe("2.0.0");
84
+ expect(backupContent.projects).toHaveLength(1);
85
+ expect(backupContent.pipelines).toHaveLength(1);
86
+ });
87
+
88
+ test("should create default config file if missing on setup", async () => {
89
+ const configDir = context.getConfigPath();
90
+ const projectsFilePath = context.getProjectsPath();
91
+
92
+ expect(existsSync(projectsFilePath)).toBe(false);
93
+
94
+ // Initialize setupConfigFile
95
+ await setupProjectsConfigFile(context);
96
+
97
+ // Should create file with default value
98
+ expect(existsSync(projectsFilePath)).toBe(true);
99
+ const content = JSON.parse(await fs.readFile(projectsFilePath, "utf8"));
100
+ expect(content.version).toBe("3.0.0");
101
+ expect(content.projects).toHaveLength(1);
102
+ expect(content.pipelines).toEqual([]);
103
+ });
104
+
105
+ test("should fallback to default config and preserve corrupted file if parsing fails", async () => {
106
+ const configDir = context.getConfigPath();
107
+ await fs.mkdir(configDir, { recursive: true });
108
+ const projectsFilePath = context.getProjectsPath();
109
+
110
+ await fs.writeFile(projectsFilePath, "{ corrupted json... }");
111
+
112
+ const configInstance = await setupProjectsConfigFile(context);
113
+ const config = await configInstance.getConfig();
114
+
115
+ expect(config.version).toBe("3.0.0");
116
+ expect(config.projects).toHaveLength(1);
117
+ expect(config.pipelines).toEqual([]);
118
+
119
+ // Verify a timestamped corrupted backup file exists
120
+ const files = await fs.readdir(configDir);
121
+ const corruptedFile = files.find(
122
+ (f) => f.startsWith("projects.corrupted.") && f.endsWith(".json"),
123
+ );
124
+ expect(corruptedFile).toBeDefined();
125
+ const corruptedContent = await fs.readFile(path.join(configDir, corruptedFile!), "utf8");
126
+ expect(corruptedContent).toBe("{ corrupted json... }");
127
+ });
128
+
129
+ test("should fallback to default config and preserve file if migration throws", async () => {
130
+ const configDir = context.getConfigPath();
131
+ await fs.mkdir(configDir, { recursive: true });
132
+ const projectsFilePath = context.getProjectsPath();
133
+
134
+ const customMigrator = {
135
+ defaultValue: { version: "2.0.0", projects: [], pipelines: [] } as any,
136
+ migrate: async () => {
137
+ throw new Error("Migration failed!");
138
+ },
139
+ };
140
+
141
+ await fs.writeFile(projectsFilePath, JSON.stringify({ version: "1.0.0", invalidData: true }));
142
+
143
+ const configInstance = await setupConfigFile<any>(context.getProjectsPath(), {
144
+ context,
145
+ migrator: customMigrator,
146
+ });
147
+ const config = await configInstance.getConfig();
148
+
149
+ expect(config.version).toBe("2.0.0");
150
+
151
+ // Verify a timestamped corrupted backup file exists
152
+ const files = await fs.readdir(configDir);
153
+ const corruptedFile = files.find(
154
+ (f) => f.startsWith("projects.corrupted.") && f.endsWith(".json"),
155
+ );
156
+ expect(corruptedFile).toBeDefined();
157
+ const corruptedContent = JSON.parse(
158
+ await fs.readFile(path.join(configDir, corruptedFile!), "utf8"),
159
+ );
160
+ expect(corruptedContent.invalidData).toBe(true);
161
+ });
162
+
163
+ test("should save config to disk via setConfig", async () => {
164
+ const configInstance = await setupProjectsConfigFile(context);
165
+ const initialConfig = await configInstance.getConfig();
166
+
167
+ const newConfig: FileRepo = {
168
+ ...initialConfig,
169
+ pipelines: [
170
+ {
171
+ id: "pipeline-new",
172
+ project: "main",
173
+ type: "internal",
174
+ configName: "pipeline-new",
175
+ lastModified: "2026-06-05",
176
+ },
177
+ ],
178
+ };
179
+
180
+ const success = await configInstance.setConfig(newConfig);
181
+ expect(success).toBe(true);
182
+
183
+ const savedContent = JSON.parse(
184
+ await fs.readFile(context.getProjectsPath(), "utf8"),
185
+ );
186
+ expect(savedContent.pipelines).toHaveLength(1);
187
+ expect(savedContent.pipelines[0].id).toBe("pipeline-new");
188
+ });
189
+ });
190
+
191
+ describe("deletePipelineConfigFile", () => {
192
+ let tempDir: string;
193
+ let context: PipelabContext;
194
+
195
+ beforeEach(() => {
196
+ vol.reset();
197
+ tempDir = "/tmp/pipelab-test-config";
198
+ context = new PipelabContext({ userDataPath: tempDir });
199
+ });
200
+
201
+ test("should delete pipeline config file using relative name", async () => {
202
+ const configDir = context.getConfigPath();
203
+ await fs.mkdir(configDir, { recursive: true });
204
+
205
+ const pipelineFilePath = path.join(configDir, "my-pipeline.json");
206
+ await fs.writeFile(pipelineFilePath, JSON.stringify({ name: "My Pipeline" }));
207
+ expect(existsSync(pipelineFilePath)).toBe(true);
208
+
209
+ await deletePipelineConfigFileByName("my-pipeline", context);
210
+ expect(existsSync(pipelineFilePath)).toBe(false);
211
+ });
212
+
213
+ test("should delete pipeline config file using absolute path", async () => {
214
+ const configDir = context.getConfigPath();
215
+ await fs.mkdir(configDir, { recursive: true });
216
+
217
+ const pipelineFilePath = path.join(configDir, "another-pipeline.json");
218
+ await fs.writeFile(pipelineFilePath, JSON.stringify({ name: "Another Pipeline" }));
219
+ expect(existsSync(pipelineFilePath)).toBe(true);
220
+
221
+ await deletePipelineConfigFileByPath(pipelineFilePath, context);
222
+ expect(existsSync(pipelineFilePath)).toBe(false);
223
+ });
224
+
225
+ test("should not throw error when attempting to delete non-existent pipeline file", async () => {
226
+ const nonExistentPath = context.getConfigPath("does-not-exist.json");
227
+ expect(existsSync(nonExistentPath)).toBe(false);
228
+
229
+ await expect(deletePipelineConfigFileByName("does-not-exist", context)).resolves.not.toThrow();
230
+ });
231
+ });
232
+
package/src/config.ts CHANGED
@@ -2,36 +2,26 @@ import { PipelabContext } from "./context";
2
2
  import path from "node:path";
3
3
  import { ensure } from "./utils/fs-extras";
4
4
  import fs from "node:fs/promises";
5
- import { useLogger } from "@pipelab/shared";
6
- import { configRegistry, Migrator } from "@pipelab/shared";
7
-
8
- export const getMigrator = <T>(name: string) => {
9
- if (configRegistry[name]) {
10
- return configRegistry[name] as Migrator<T>;
11
- }
12
-
13
- if (name.startsWith("pipeline-")) {
14
- return configRegistry["pipeline"] as Migrator<T>;
15
- }
16
-
17
- return undefined;
18
- };
5
+ import {
6
+ useLogger,
7
+ Migrator,
8
+ AppConfig,
9
+ ConnectionsConfig,
10
+ FileRepo,
11
+ SavedFile,
12
+ appSettingsMigrator,
13
+ connectionsMigrator,
14
+ fileRepoMigrations,
15
+ savedFileMigrator,
16
+ } from "@pipelab/shared";
19
17
 
20
18
  export const setupConfigFile = async <T>(
21
- name: string,
22
- options: { context: PipelabContext; migrator?: Migrator<T> },
19
+ filesPath: string,
20
+ options: { context: PipelabContext; migrator: Migrator<T> },
23
21
  ) => {
24
22
  const ctx = options.context;
25
- const migrator = options.migrator || getMigrator<T>(name);
26
-
27
- if (!migrator) {
28
- throw new Error(
29
- `No migrator found for configuration: ${name}. All managed files must have a migration schema.`,
30
- );
31
- }
32
-
33
- const isAbsolutePath = path.isAbsolute(name);
34
- const filesPath = isAbsolutePath ? name : ctx.getConfigPath(`${name}.json`);
23
+ const parsedPath = path.parse(filesPath);
24
+ const migrator = options.migrator;
35
25
 
36
26
  await ensure(filesPath, JSON.stringify(migrator.defaultValue));
37
27
 
@@ -42,7 +32,7 @@ export const setupConfigFile = async <T>(
42
32
  await fs.writeFile(filesPath, JSON.stringify(config));
43
33
  return true;
44
34
  } catch (e) {
45
- logger().error(`Error saving config ${name}:`, e);
35
+ logger().error(`Error saving config ${parsedPath.name}:`, e);
46
36
  return false;
47
37
  }
48
38
  },
@@ -50,6 +40,7 @@ export const setupConfigFile = async <T>(
50
40
  const { logger } = useLogger();
51
41
  let content = undefined;
52
42
  let originalJson: any = undefined;
43
+ let parseFailed = false;
53
44
 
54
45
  try {
55
46
  content = await fs.readFile(filesPath, "utf8");
@@ -57,45 +48,66 @@ export const setupConfigFile = async <T>(
57
48
  originalJson = JSON.parse(content);
58
49
  }
59
50
  } catch (e) {
60
- logger().error(`Error reading or parsing config ${name}:`, e);
51
+ logger().error(`Error reading or parsing config ${parsedPath.name}:`, e);
52
+ parseFailed = true;
61
53
  }
62
54
 
63
- // console.log("content", content);
64
-
65
55
  let json: any = undefined;
56
+ let migrationFailed = false;
66
57
  try {
67
- json = await migrator.migrate(originalJson, {
68
- debug: false,
69
- });
58
+ if (!parseFailed) {
59
+ json = await migrator.migrate(originalJson, {
60
+ debug: false,
61
+ onStep: async (state: any, version: string) => {
62
+ const versionedPath = path.join(parsedPath.dir, `${parsedPath.name}.v${version}.json`);
63
+ try {
64
+ await fs.writeFile(versionedPath, JSON.stringify(state));
65
+ logger().info(`Intermediate backup created for ${parsedPath.name} at ${versionedPath}`);
66
+ } catch (e) {
67
+ logger().error(
68
+ `Failed to create intermediate backup for ${parsedPath.name} at v${version}:`,
69
+ e,
70
+ );
71
+ }
72
+ },
73
+ });
74
+ } else {
75
+ json = migrator.defaultValue;
76
+ }
70
77
  } catch (e) {
71
- logger().error(`Error migrating config ${name}:`, e);
78
+ logger().error(`Error migrating config ${parsedPath.name}:`, e);
79
+ migrationFailed = true;
72
80
  json = migrator.defaultValue;
73
81
  }
74
82
 
75
83
  const originalVersion = originalJson?.version;
76
84
  const newVersion = json?.version;
77
85
 
78
- // Check if migration actually changed the version
79
- if (originalVersion !== newVersion && content !== undefined) {
80
- // Backup previous file before overwriting
81
- const parsedPath = path.parse(filesPath);
82
- const versionSuffix = originalVersion || "unknown";
83
- const backupPath = path.join(parsedPath.dir, `${parsedPath.name}.${versionSuffix}.bak`);
84
-
85
- try {
86
- await fs.copyFile(filesPath, backupPath);
87
- logger().info(`Backup created for ${name} at ${backupPath} (version ${versionSuffix})`);
88
- } catch (e) {
89
- logger().error(`Failed to create backup for ${name}:`, e);
86
+ const shouldSaveBack =
87
+ originalVersion !== newVersion || content === undefined || parseFailed || migrationFailed;
88
+
89
+ if (shouldSaveBack) {
90
+ if (parseFailed || migrationFailed) {
91
+ try {
92
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
93
+ const corruptedPath = path.join(
94
+ parsedPath.dir,
95
+ `${parsedPath.name}.corrupted.${timestamp}.json`,
96
+ );
97
+ const backupContent = parseFailed
98
+ ? content || ""
99
+ : JSON.stringify(originalJson, null, 2);
100
+ await fs.writeFile(corruptedPath, backupContent);
101
+ logger().info(`Corrupted config file preserved at ${corruptedPath}`);
102
+ } catch (e) {
103
+ logger().error(`Failed to backup corrupted config ${parsedPath.name}:`, e);
104
+ }
90
105
  }
91
- }
92
106
 
93
- // Save back migrated config if changed or if it's a new file
94
- if (originalVersion !== newVersion || content === undefined) {
95
107
  try {
96
108
  await fs.writeFile(filesPath, JSON.stringify(json));
97
109
  } catch (e) {
98
- logger().error(`Error saving migrated config ${name}:`, e);
110
+ logger().error(`Error saving migrated config ${parsedPath.name}:`, e);
99
111
  }
100
112
  }
101
113
 
@@ -103,3 +115,52 @@ export const setupConfigFile = async <T>(
103
115
  },
104
116
  };
105
117
  };
118
+
119
+ export const setupSettingsConfigFile = (context: PipelabContext) => {
120
+ return setupConfigFile<AppConfig>(context.getSettingsPath(), {
121
+ context,
122
+ migrator: appSettingsMigrator,
123
+ });
124
+ };
125
+
126
+ export const setupConnectionsConfigFile = (context: PipelabContext) => {
127
+ return setupConfigFile<ConnectionsConfig>(context.getConnectionsPath(), {
128
+ context,
129
+ migrator: connectionsMigrator,
130
+ });
131
+ };
132
+
133
+ export const setupProjectsConfigFile = (context: PipelabContext) => {
134
+ return setupConfigFile<FileRepo>(context.getProjectsPath(), {
135
+ context,
136
+ migrator: fileRepoMigrations,
137
+ });
138
+ };
139
+
140
+ export const setupPipelineConfigFileByName = (name: string, context: PipelabContext) => {
141
+ const filesPath = context.getConfigPath(`${name}.json`);
142
+ return setupConfigFile<SavedFile>(filesPath, {
143
+ context,
144
+ migrator: savedFileMigrator,
145
+ });
146
+ };
147
+
148
+ export const setupPipelineConfigFileByPath = (absolutePath: string, context: PipelabContext) => {
149
+ return setupConfigFile<SavedFile>(absolutePath, {
150
+ context,
151
+ migrator: savedFileMigrator,
152
+ });
153
+ };
154
+
155
+ const deleteConfigFile = async (filesPath: string) => {
156
+ await fs.rm(filesPath, { force: true });
157
+ };
158
+
159
+ export const deletePipelineConfigFileByName = async (name: string, context: PipelabContext) => {
160
+ const filesPath = context.getConfigPath(`${name}.json`);
161
+ await deleteConfigFile(filesPath);
162
+ };
163
+
164
+ export const deletePipelineConfigFileByPath = async (absolutePath: string, context: PipelabContext) => {
165
+ await deleteConfigFile(absolutePath);
166
+ };