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

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.
@@ -0,0 +1,598 @@
1
+ import { useAPI } from "../ipc-core";
2
+ import { useLogger } from "@pipelab/shared";
3
+ import { PipelabContext, getDefaultUserDataPath } from "../context";
4
+ import fs from "node:fs/promises";
5
+ import { existsSync } from "node:fs";
6
+ import { join, dirname } from "node:path";
7
+ import { FileRepo, SaveLocation, AppConfig, ConnectionsConfig } from "@pipelab/shared";
8
+ import semver from "semver";
9
+
10
+ interface MigrationPipelineItem {
11
+ id: string;
12
+ name: string;
13
+ description: string;
14
+ type: "internal" | "external" | "pipelab-cloud";
15
+ lastModifiedStable?: string;
16
+ lastModifiedBeta?: string;
17
+ existsInBeta: boolean;
18
+ }
19
+
20
+ interface MigrationProjectItem {
21
+ id: string;
22
+ name: string;
23
+ description: string;
24
+ pipelines: MigrationPipelineItem[];
25
+ }
26
+
27
+ export const registerMigrationHandlers = (context: PipelabContext) => {
28
+ const { handle } = useAPI();
29
+ const { logger } = useLogger();
30
+
31
+ const getFileMetadata = async (filePath: string) => {
32
+ const exists = existsSync(filePath);
33
+ let mtime = Date.now();
34
+ let version: string | null = null;
35
+
36
+ if (exists) {
37
+ try {
38
+ const stat = await fs.stat(filePath);
39
+ mtime = stat.mtime ? stat.mtime.getTime() : Date.now();
40
+ } catch (e) {
41
+ mtime = Date.now();
42
+ }
43
+
44
+ try {
45
+ const content = await fs.readFile(filePath, "utf8");
46
+ const json = JSON.parse(content);
47
+ version = json && typeof json.version === "string" ? json.version : null;
48
+ } catch (e) {
49
+ // Version remains null
50
+ }
51
+ } else {
52
+ mtime = Date.now();
53
+ }
54
+
55
+ return { exists, mtime, version };
56
+ };
57
+
58
+ const isVersionImportable = (sourceVer: string | null, targetVer: string | null): boolean => {
59
+ if (!sourceVer) return false;
60
+ if (!targetVer) return true;
61
+
62
+ try {
63
+ const coercedSource = semver.coerce(sourceVer);
64
+ const coercedTarget = semver.coerce(targetVer);
65
+ if (!coercedSource) return false;
66
+ if (!coercedTarget) return true;
67
+ return semver.lte(coercedSource, coercedTarget);
68
+ } catch {
69
+ return sourceVer <= targetVer;
70
+ }
71
+ };
72
+
73
+ handle("migration:scan-stable", async (_, { send }) => {
74
+ logger().info("[Migration] Scanning other channel database...");
75
+ try {
76
+ const isStable =
77
+ context.userDataPath.endsWith("app") || !context.userDataPath.includes("app-beta");
78
+ const sourceEnv = isStable ? "beta" : "prod";
79
+ const sourcePath = getDefaultUserDataPath(sourceEnv);
80
+ const targetPath = context.userDataPath;
81
+
82
+ if (sourcePath === targetPath) {
83
+ logger().info("[Migration] Running in same channel. Disabling migration scan.");
84
+ return send({
85
+ type: "end",
86
+ data: {
87
+ type: "success",
88
+ result: {
89
+ sourceChannel: isStable ? "Beta" : "Stable",
90
+ targetChannel: isStable ? "Stable" : "Beta",
91
+ settingsExists: false,
92
+ settingsVersion: null,
93
+ settingsVersionTarget: null,
94
+ settingsMtimeSource: Date.now(),
95
+ settingsMtimeTarget: Date.now(),
96
+ settingsImportable: false,
97
+ connectionsExists: false,
98
+ connectionsCount: 0,
99
+ connectionsVersion: null,
100
+ connectionsVersionTarget: null,
101
+ connectionsMtimeSource: Date.now(),
102
+ connectionsMtimeTarget: Date.now(),
103
+ connectionsImportable: false,
104
+ projectsExists: false,
105
+ projectsVersion: null,
106
+ projectsVersionTarget: null,
107
+ projectsMtimeSource: Date.now(),
108
+ projectsMtimeTarget: Date.now(),
109
+ projectsImportable: false,
110
+ projects: [],
111
+ },
112
+ },
113
+ });
114
+ }
115
+
116
+ const sourceContext = new PipelabContext({ userDataPath: sourcePath, releaseTag: sourceEnv });
117
+
118
+ const sourceSettingsPath = sourceContext.getSettingsPath();
119
+ const targetSettingsPath = context.getSettingsPath();
120
+
121
+ const sourceConnectionsPath = sourceContext.getConnectionsPath();
122
+ const targetConnectionsPath = context.getConnectionsPath();
123
+
124
+ const sourceProjectsPath = sourceContext.getProjectsPath();
125
+ const targetProjectsPath = context.getProjectsPath();
126
+
127
+ const sourceSettingsMeta = await getFileMetadata(sourceSettingsPath);
128
+ const targetSettingsMeta = await getFileMetadata(targetSettingsPath);
129
+
130
+ const sourceConnectionsMeta = await getFileMetadata(sourceConnectionsPath);
131
+ const targetConnectionsMeta = await getFileMetadata(targetConnectionsPath);
132
+
133
+ const sourceProjectsMeta = await getFileMetadata(sourceProjectsPath);
134
+ const targetProjectsMeta = await getFileMetadata(targetProjectsPath);
135
+
136
+ const settingsExists = sourceSettingsMeta.exists;
137
+ const settingsVersion = sourceSettingsMeta.version;
138
+ const settingsMtimeSource = sourceSettingsMeta.mtime;
139
+ const settingsMtimeTarget = targetSettingsMeta.mtime;
140
+ const settingsImportable = isVersionImportable(settingsVersion, targetSettingsMeta.version);
141
+
142
+ const connectionsExists = sourceConnectionsMeta.exists;
143
+ const connectionsVersion = sourceConnectionsMeta.version;
144
+ const connectionsMtimeSource = sourceConnectionsMeta.mtime;
145
+ const connectionsMtimeTarget = targetConnectionsMeta.mtime;
146
+ const connectionsImportable = isVersionImportable(
147
+ connectionsVersion,
148
+ targetConnectionsMeta.version,
149
+ );
150
+
151
+ const projectsExists = sourceProjectsMeta.exists;
152
+ const projectsVersion = sourceProjectsMeta.version;
153
+ const projectsMtimeSource = sourceProjectsMeta.mtime;
154
+ const projectsMtimeTarget = targetProjectsMeta.mtime;
155
+ const projectsImportable = isVersionImportable(projectsVersion, targetProjectsMeta.version);
156
+
157
+ let connectionsCount = 0;
158
+ try {
159
+ if (sourceConnectionsMeta.exists) {
160
+ const connContent = await fs.readFile(sourceConnectionsPath, "utf8");
161
+ const connJson = JSON.parse(connContent) as ConnectionsConfig;
162
+ if (connJson && Array.isArray(connJson.connections)) {
163
+ connectionsCount = connJson.connections.length;
164
+ }
165
+ }
166
+ } catch (err) {
167
+ logger().error("[Migration] Error parsing source connections.json:", err);
168
+ }
169
+
170
+ let sourceProjectsList: FileRepo["projects"] = [];
171
+ let sourcePipelinesList: SaveLocation[] = [];
172
+ try {
173
+ if (sourceProjectsMeta.exists) {
174
+ const projContent = await fs.readFile(sourceProjectsPath, "utf8");
175
+ const projJson = JSON.parse(projContent) as FileRepo;
176
+ sourceProjectsList = projJson.projects || [];
177
+ sourcePipelinesList = projJson.pipelines || [];
178
+ }
179
+ } catch (err) {
180
+ logger().error("[Migration] Error parsing source projects.json:", err);
181
+ }
182
+
183
+ let targetPipelinesList: SaveLocation[] = [];
184
+ try {
185
+ if (targetProjectsMeta.exists) {
186
+ const targetProjContent = await fs.readFile(targetProjectsPath, "utf8");
187
+ const targetProjJson = JSON.parse(targetProjContent) as FileRepo;
188
+ targetPipelinesList = targetProjJson.pipelines || [];
189
+ }
190
+ } catch (err) {
191
+ // Safe to ignore if target hasn't initialized projects yet
192
+ }
193
+
194
+ const projectsReport: MigrationProjectItem[] = [];
195
+ for (const proj of sourceProjectsList) {
196
+ const projPipelines = sourcePipelinesList.filter((p) => p.project === proj.id);
197
+ const pipelinesReport: MigrationPipelineItem[] = [];
198
+
199
+ for (const pipe of projPipelines) {
200
+ const targetPipe = targetPipelinesList.find((bp) => bp.id === pipe.id);
201
+ const existsInBeta = !!targetPipe;
202
+
203
+ let pipeName = pipe.id;
204
+ let pipeDesc = "";
205
+ if (pipe.type === "internal") {
206
+ const sourcePipeFile = sourceContext.getConfigPath(`${pipe.configName}.json`);
207
+ try {
208
+ if (existsSync(sourcePipeFile)) {
209
+ const pipeContent = await fs.readFile(sourcePipeFile, "utf8");
210
+ const pipeJson = JSON.parse(pipeContent) as { name?: string; description?: string };
211
+ pipeName = pipeJson.name || pipeName;
212
+ pipeDesc = pipeJson.description || pipeDesc;
213
+ }
214
+ } catch (err) {
215
+ logger().error(
216
+ `[Migration] Error reading source pipeline file ${sourcePipeFile}:`,
217
+ err,
218
+ );
219
+ }
220
+ } else if (pipe.type === "external") {
221
+ pipeName = pipe.summary?.name || pipeName;
222
+ pipeDesc = pipe.summary?.description || pipeDesc;
223
+ }
224
+
225
+ pipelinesReport.push({
226
+ id: pipe.id,
227
+ name: pipeName,
228
+ description: pipeDesc,
229
+ type: pipe.type,
230
+ lastModifiedStable: pipe.type !== "pipelab-cloud" ? pipe.lastModified : undefined,
231
+ lastModifiedBeta:
232
+ targetPipe && targetPipe.type !== "pipelab-cloud"
233
+ ? targetPipe.lastModified
234
+ : undefined,
235
+ existsInBeta,
236
+ });
237
+ }
238
+
239
+ projectsReport.push({
240
+ id: proj.id,
241
+ name: proj.name,
242
+ description: proj.description || "",
243
+ pipelines: pipelinesReport,
244
+ });
245
+ }
246
+
247
+ send({
248
+ type: "end",
249
+ data: {
250
+ type: "success",
251
+ result: {
252
+ sourceChannel: isStable ? "Beta" : "Stable",
253
+ targetChannel: isStable ? "Stable" : "Beta",
254
+ settingsExists,
255
+ settingsVersion,
256
+ settingsVersionTarget: targetSettingsMeta.version,
257
+ settingsMtimeSource,
258
+ settingsMtimeTarget,
259
+ settingsImportable,
260
+ connectionsExists,
261
+ connectionsCount,
262
+ connectionsVersion,
263
+ connectionsVersionTarget: targetConnectionsMeta.version,
264
+ connectionsMtimeSource,
265
+ connectionsMtimeTarget,
266
+ connectionsImportable,
267
+ projectsExists,
268
+ projectsVersion,
269
+ projectsVersionTarget: targetProjectsMeta.version,
270
+ projectsMtimeSource,
271
+ projectsMtimeTarget,
272
+ projectsImportable,
273
+ projects: projectsReport,
274
+ },
275
+ },
276
+ });
277
+ } catch (e) {
278
+ logger().error("[Migration] Error scanning other channel folder:", e);
279
+ send({
280
+ type: "end",
281
+ data: {
282
+ type: "error",
283
+ ipcError: e instanceof Error ? e.message : "Failed to scan other channel database",
284
+ },
285
+ });
286
+ }
287
+ });
288
+
289
+ handle("migration:perform", async (_, { send, value }) => {
290
+ logger().info("[Migration] Performing migration...");
291
+ try {
292
+ const { migrateSettings, migrateConnections, selectedProjects, selectedPipelines } = value;
293
+
294
+ const isStable =
295
+ context.userDataPath.endsWith("app") || !context.userDataPath.includes("app-beta");
296
+ const sourceEnv = isStable ? "beta" : "prod";
297
+ const sourcePath = getDefaultUserDataPath(sourceEnv);
298
+ const sourceContext = new PipelabContext({ userDataPath: sourcePath, releaseTag: sourceEnv });
299
+
300
+ const sourceConfigDir = sourceContext.getConfigPath();
301
+ const targetConfigDir = context.getConfigPath();
302
+
303
+ // 1. Check folder validity
304
+ if (sourcePath === context.userDataPath) {
305
+ throw new Error("Cannot migrate data: Source and target directories are identical.");
306
+ }
307
+
308
+ // 2. Create backup of target's config directory (excluding backups folder itself)
309
+ if (existsSync(targetConfigDir)) {
310
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
311
+ const backupDir = context.getConfigPath("backups", `backup_${timestamp}`);
312
+ await fs.mkdir(backupDir, { recursive: true });
313
+
314
+ const entries = await fs.readdir(targetConfigDir, { withFileTypes: true });
315
+ for (const entry of entries) {
316
+ if (entry.name === "backups") continue;
317
+ const srcPath = join(targetConfigDir, entry.name);
318
+ const destPath = join(backupDir, entry.name);
319
+ try {
320
+ await fs.cp(srcPath, destPath, { recursive: true });
321
+ } catch (copyErr) {
322
+ logger().error(`[Migration] Failed to backup ${entry.name}:`, copyErr);
323
+ }
324
+ }
325
+ logger().info(`[Migration] Config backup successfully created at ${backupDir}`);
326
+ }
327
+
328
+ // 3. Settings migration (Merge)
329
+ if (migrateSettings) {
330
+ const sourceSettingsFile = sourceContext.getSettingsPath();
331
+ const targetSettingsFile = context.getSettingsPath();
332
+
333
+ const sourceMeta = await getFileMetadata(sourceSettingsFile);
334
+ const targetMeta = await getFileMetadata(targetSettingsFile);
335
+ if (!isVersionImportable(sourceMeta.version, targetMeta.version)) {
336
+ throw new Error("Cannot migrate settings: Source version is newer than current version.");
337
+ }
338
+
339
+ if (existsSync(sourceSettingsFile)) {
340
+ let stableSettings: AppConfig = {
341
+ version: "7.0.0",
342
+ locale: "en-US",
343
+ theme: "light",
344
+ autosave: true,
345
+ agents: [],
346
+ plugins: [],
347
+ tours: {
348
+ dashboard: { step: 0, completed: false },
349
+ editor: { step: 0, completed: false },
350
+ },
351
+ };
352
+ try {
353
+ const stableContent = await fs.readFile(sourceSettingsFile, "utf8");
354
+ stableSettings = JSON.parse(stableContent) as AppConfig;
355
+ } catch (e) {
356
+ logger().error("[Migration] Failed to read source settings.json:", e);
357
+ }
358
+
359
+ let betaSettings: AppConfig = {
360
+ version: "7.0.0",
361
+ locale: "en-US",
362
+ theme: "light",
363
+ autosave: true,
364
+ agents: [],
365
+ plugins: [],
366
+ tours: {
367
+ dashboard: { step: 0, completed: false },
368
+ editor: { step: 0, completed: false },
369
+ },
370
+ };
371
+ if (existsSync(targetSettingsFile)) {
372
+ try {
373
+ const betaContent = await fs.readFile(targetSettingsFile, "utf8");
374
+ betaSettings = JSON.parse(betaContent) as AppConfig;
375
+ } catch (e) {
376
+ // Keep default
377
+ }
378
+ }
379
+
380
+ // Merge primitives
381
+ betaSettings.theme = stableSettings.theme ?? betaSettings.theme;
382
+ betaSettings.locale = stableSettings.locale ?? betaSettings.locale;
383
+ betaSettings.autosave = stableSettings.autosave ?? betaSettings.autosave;
384
+
385
+ // Merge tours
386
+ if (stableSettings.tours) {
387
+ betaSettings.tours = betaSettings.tours || {
388
+ dashboard: { step: 0, completed: false },
389
+ editor: { step: 0, completed: false },
390
+ };
391
+ if (stableSettings.tours.dashboard) {
392
+ betaSettings.tours.dashboard = stableSettings.tours.dashboard;
393
+ }
394
+ if (stableSettings.tours.editor) {
395
+ betaSettings.tours.editor = stableSettings.tours.editor;
396
+ }
397
+ }
398
+
399
+ // Merge agents (by id)
400
+ const betaAgents = betaSettings.agents || [];
401
+ const stableAgents = stableSettings.agents || [];
402
+ for (const sAgent of stableAgents) {
403
+ const existingIdx = betaAgents.findIndex((a) => a.id === sAgent.id);
404
+ if (existingIdx >= 0) {
405
+ betaAgents[existingIdx] = { ...sAgent };
406
+ } else {
407
+ betaAgents.push({ ...sAgent });
408
+ }
409
+ }
410
+ betaSettings.agents = betaAgents;
411
+
412
+ // Merge plugins (by name)
413
+ const betaPlugins = betaSettings.plugins || [];
414
+ const stablePlugins = stableSettings.plugins || [];
415
+ for (const sPlugin of stablePlugins) {
416
+ const existingIdx = betaPlugins.findIndex((p) => p.name === sPlugin.name);
417
+ if (existingIdx >= 0) {
418
+ betaPlugins[existingIdx] = { ...sPlugin };
419
+ } else {
420
+ betaPlugins.push({ ...sPlugin });
421
+ }
422
+ }
423
+ betaSettings.plugins = betaPlugins;
424
+
425
+ await fs.mkdir(dirname(targetSettingsFile), { recursive: true });
426
+ await fs.writeFile(targetSettingsFile, JSON.stringify(betaSettings, null, 2));
427
+ logger().info("[Migration] Settings merged successfully");
428
+ }
429
+ }
430
+
431
+ // 4. Connections migration (Merge)
432
+ if (migrateConnections) {
433
+ const sourceConnectionsFile = sourceContext.getConnectionsPath();
434
+ const targetConnectionsFile = context.getConnectionsPath();
435
+
436
+ const sourceMeta = await getFileMetadata(sourceConnectionsFile);
437
+ const targetMeta = await getFileMetadata(targetConnectionsFile);
438
+ if (!isVersionImportable(sourceMeta.version, targetMeta.version)) {
439
+ throw new Error(
440
+ "Cannot migrate connections: Source version is newer than current version.",
441
+ );
442
+ }
443
+
444
+ if (existsSync(sourceConnectionsFile)) {
445
+ let stableConnections: ConnectionsConfig = { version: "1.0.0", connections: [] };
446
+ try {
447
+ const stableContent = await fs.readFile(sourceConnectionsFile, "utf8");
448
+ stableConnections = JSON.parse(stableContent) as ConnectionsConfig;
449
+ } catch (e) {
450
+ logger().error("[Migration] Failed to read source connections.json:", e);
451
+ }
452
+
453
+ let betaConnections: ConnectionsConfig = { version: "1.0.0", connections: [] };
454
+ if (existsSync(targetConnectionsFile)) {
455
+ try {
456
+ const betaContent = await fs.readFile(targetConnectionsFile, "utf8");
457
+ betaConnections = JSON.parse(betaContent) as ConnectionsConfig;
458
+ } catch (e) {
459
+ // Keep default
460
+ }
461
+ }
462
+
463
+ betaConnections.connections = betaConnections.connections || [];
464
+ stableConnections.connections = stableConnections.connections || [];
465
+
466
+ for (const sConn of stableConnections.connections) {
467
+ const existingIdx = betaConnections.connections.findIndex((c) => c.id === sConn.id);
468
+ if (existingIdx >= 0) {
469
+ betaConnections.connections[existingIdx] = { ...sConn };
470
+ } else {
471
+ betaConnections.connections.push({ ...sConn });
472
+ }
473
+ }
474
+
475
+ await fs.mkdir(dirname(targetConnectionsFile), { recursive: true });
476
+ await fs.writeFile(targetConnectionsFile, JSON.stringify(betaConnections, null, 2));
477
+ logger().info("[Migration] Connections merged successfully");
478
+ }
479
+ }
480
+
481
+ // 5. Projects and Pipelines migration
482
+ if (selectedPipelines.length > 0 || selectedProjects.length > 0) {
483
+ const sourceProjectsFile = sourceContext.getProjectsPath();
484
+ const targetProjectsFile = context.getProjectsPath();
485
+
486
+ const sourceMeta = await getFileMetadata(sourceProjectsFile);
487
+ const targetMeta = await getFileMetadata(targetProjectsFile);
488
+ if (!isVersionImportable(sourceMeta.version, targetMeta.version)) {
489
+ throw new Error("Cannot migrate projects: Source version is newer than current version.");
490
+ }
491
+
492
+ // Read source projects
493
+ let stableFileRepo: FileRepo = { version: "3.0.0", projects: [], pipelines: [] };
494
+ if (existsSync(sourceProjectsFile)) {
495
+ const stableContent = await fs.readFile(sourceProjectsFile, "utf8");
496
+ stableFileRepo = JSON.parse(stableContent) as FileRepo;
497
+ }
498
+
499
+ // Read or initialize current target projects
500
+ let betaFileRepo: FileRepo = { version: "3.0.0", projects: [], pipelines: [] };
501
+ if (existsSync(targetProjectsFile)) {
502
+ try {
503
+ const betaContent = await fs.readFile(targetProjectsFile, "utf8");
504
+ betaFileRepo = JSON.parse(betaContent) as FileRepo;
505
+ } catch (readErr) {
506
+ logger().warn(
507
+ "[Migration] Failed to parse existing target projects.json, starting fresh:",
508
+ readErr,
509
+ );
510
+ }
511
+ }
512
+
513
+ betaFileRepo.projects = betaFileRepo.projects || [];
514
+ betaFileRepo.pipelines = betaFileRepo.pipelines || [];
515
+
516
+ // Copy selected projects metadata
517
+ for (const projId of selectedProjects) {
518
+ const stableProj = stableFileRepo.projects.find((p) => p.id === projId);
519
+ if (stableProj) {
520
+ const existingProjIdx = betaFileRepo.projects.findIndex((p) => p.id === projId);
521
+ if (existingProjIdx >= 0) {
522
+ betaFileRepo.projects[existingProjIdx] = { ...stableProj };
523
+ } else {
524
+ betaFileRepo.projects.push({ ...stableProj });
525
+ }
526
+ }
527
+ }
528
+
529
+ // Copy selected pipelines metadata and copy the pipeline config files
530
+ for (const pipeId of selectedPipelines) {
531
+ const stablePipe = (stableFileRepo.pipelines || []).find((p) => p.id === pipeId);
532
+ if (stablePipe) {
533
+ // Ensure the parent project metadata exists in target
534
+ const parentProjId = stablePipe.project;
535
+ const projectExists = betaFileRepo.projects.some((p) => p.id === parentProjId);
536
+ if (!projectExists) {
537
+ const stableProj = stableFileRepo.projects.find((p) => p.id === parentProjId);
538
+ if (stableProj) {
539
+ betaFileRepo.projects.push({ ...stableProj });
540
+ }
541
+ }
542
+
543
+ // Copy pipeline file if internal
544
+ if (stablePipe.type === "internal") {
545
+ const stablePipeFile = sourceContext.getConfigPath(`${stablePipe.configName}.json`);
546
+ const betaPipeFile = context.getConfigPath(`${stablePipe.configName}.json`);
547
+ if (existsSync(stablePipeFile)) {
548
+ await fs.mkdir(dirname(betaPipeFile), { recursive: true });
549
+ await fs.copyFile(stablePipeFile, betaPipeFile);
550
+ logger().info(`[Migration] Copied pipeline file: ${stablePipe.configName}`);
551
+ }
552
+ }
553
+
554
+ // Create updated pipeline metadata with modification date set to now timestamp
555
+ const updatedPipe = {
556
+ ...stablePipe,
557
+ };
558
+ if (updatedPipe.type !== "pipelab-cloud") {
559
+ (updatedPipe as any).lastModified = new Date().toISOString();
560
+ }
561
+
562
+ // Update target projects.json
563
+ const existingPipeIdx = betaFileRepo.pipelines.findIndex((p) => p.id === pipeId);
564
+ if (existingPipeIdx >= 0) {
565
+ betaFileRepo.pipelines[existingPipeIdx] = updatedPipe;
566
+ } else {
567
+ betaFileRepo.pipelines.push(updatedPipe);
568
+ }
569
+ }
570
+ }
571
+
572
+ // Save projects.json
573
+ await fs.mkdir(dirname(targetProjectsFile), { recursive: true });
574
+ await fs.writeFile(targetProjectsFile, JSON.stringify(betaFileRepo, null, 2));
575
+ logger().info("[Migration] Merged and saved projects.json successfully");
576
+ }
577
+
578
+ send({
579
+ type: "end",
580
+ data: {
581
+ type: "success",
582
+ result: {
583
+ result: "ok",
584
+ },
585
+ },
586
+ });
587
+ } catch (e) {
588
+ logger().error("[Migration] Error performing migration:", e);
589
+ send({
590
+ type: "end",
591
+ data: {
592
+ type: "error",
593
+ ipcError: e instanceof Error ? e.message : "Failed to migrate selected data",
594
+ },
595
+ });
596
+ }
597
+ });
598
+ };
package/src/index.ts CHANGED
@@ -2,7 +2,15 @@ export * from "./context";
2
2
  export * from "./websocket-server";
3
3
  export * from "./ipc-core";
4
4
  export * from "./handlers/index";
5
- export * from "./config";
5
+ export {
6
+ setupSettingsConfigFile,
7
+ setupConnectionsConfigFile,
8
+ setupProjectsConfigFile,
9
+ setupPipelineConfigFileByName,
10
+ setupPipelineConfigFileByPath,
11
+ deletePipelineConfigFileByName,
12
+ deletePipelineConfigFileByPath,
13
+ } from "./config";
6
14
  export * from "./paths";
7
15
  export * from "./api";
8
16
  export * from "./heavy";
@@ -11,7 +19,6 @@ export * from "./utils/remote";
11
19
  export * from "./utils/fs-extras";
12
20
  export * from "./types/runner";
13
21
  export * from "./runner";
14
- export * from "./migrations";
15
22
  export * from "./server";
16
23
  export * from "./utils";
17
24
  export * from "./utils/github";
@@ -275,8 +275,8 @@ export const builtInPlugins = async (options: { context: PipelabContext }): Prom
275
275
  }
276
276
 
277
277
  try {
278
- const { setupConfigFile } = await import("./config");
279
- const settingsManager = await setupConfigFile<any>("settings", { context: options.context });
278
+ const { setupSettingsConfigFile } = await import("./config");
279
+ const settingsManager = await setupSettingsConfigFile(options.context);
280
280
  const settingsConfig = await settingsManager.getConfig();
281
281
  const settingsPlugins = settingsConfig?.plugins || [];
282
282
 
package/src/runner.ts CHANGED
@@ -4,8 +4,6 @@ import { isDev, PipelabContext, CacheFolder } from "./context";
4
4
  import { readFile, access, writeFile, mkdir } from "node:fs/promises";
5
5
  import { resolve, isAbsolute, join, dirname } from "node:path";
6
6
  import { savedFileMigrator } from "@pipelab/shared";
7
- import type { AppConfig } from "@pipelab/shared";
8
- import { registerMigrationHandlers } from "./migrations";
9
7
  import { registerAllHandlers } from "./handlers/index";
10
8
 
11
9
  export interface RunOptions {
@@ -65,7 +63,6 @@ export async function runPipelineCommand(file: string, options: RunOptions, vers
65
63
  });
66
64
 
67
65
  await registerAllHandlers({ version, context });
68
- registerMigrationHandlers(context);
69
66
 
70
67
  const cachePath = context.getCachePath(CacheFolder.Pipelines, effectivePipelineId);
71
68
  await mkdir(cachePath, { recursive: true });
package/src/server.ts CHANGED
@@ -9,11 +9,9 @@ import {
9
9
  import { getUiDevServerMissingWarning, uiDevPort } from "@pipelab/constants";
10
10
  import { existsSync } from "node:fs";
11
11
  import { readFile } from "node:fs/promises";
12
- import { join } from "node:path";
13
12
  import http from "http";
14
13
  // @ts-expect-error serve-handler has no type definitions
15
14
  import handler from "serve-handler";
16
- import { registerMigrationHandlers } from "./migrations";
17
15
 
18
16
  export interface ServeOptions {
19
17
  port: string | number;
@@ -135,7 +133,6 @@ export async function serveCommand(options: ServeOptions, version: string, _dirn
135
133
  version,
136
134
  context,
137
135
  });
138
- registerMigrationHandlers(context);
139
136
 
140
137
  sendStartupReady();
141
138
 
package/src/utils.ts CHANGED
@@ -21,7 +21,7 @@ import { BuildHistoryStorage } from "./handlers/build-history";
21
21
  import { ensure, extractTarGz, extractZip, zipFolder } from "./utils/fs-extras";
22
22
  import { fetchPipelabAsset } from "./utils/remote";
23
23
  import { loadPipelabPlugin } from "./plugins-registry";
24
- import { setupConfigFile } from "./config";
24
+ import { setupSettingsConfigFile } from "./config";
25
25
 
26
26
  export const getFinalPlugins = () => {
27
27
  const { plugins } = usePlugins();
@@ -149,7 +149,7 @@ export const executeGraphWithHistory = async ({
149
149
  }
150
150
 
151
151
  logger().info(`[Sandbox] Execution sandbox created at: ${sandboxPath}`);
152
- const settingsFile = await setupConfigFile<AppConfig>("settings", { context: ctx });
152
+ const settingsFile = await setupSettingsConfigFile(ctx);
153
153
  const config = await settingsFile.getConfig();
154
154
  const shouldCleanup = true;
155
155
 
@@ -1,2 +0,0 @@
1
- import { setupConfigFile } from "./index.mjs";
2
- export { setupConfigFile };