@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.
package/dist/index.mjs CHANGED
@@ -1,19 +1,16 @@
1
+ import { a as setupPipelineConfigFileByName, c as setupSettingsConfigFile, d as extractTarGz, f as extractZip, h as zipFolder, i as setupConnectionsConfigFile, l as downloadFile, m as runWithLiveLogs, n as deletePipelineConfigFileByPath, o as setupPipelineConfigFileByPath, p as getFolderSize, s as setupProjectsConfigFile, t as deletePipelineConfigFileByName, u as ensure } from "./config-CFgGRD9U.mjs";
1
2
  import { createRequire } from "node:module";
2
3
  import path, { delimiter, dirname, isAbsolute, join, resolve } from "node:path";
3
4
  import { homedir, platform } from "node:os";
4
5
  import { fileURLToPath, pathToFileURL } from "node:url";
5
- import fs, { createWriteStream, existsSync, readFileSync, readdirSync, statSync } from "node:fs";
6
+ import fs, { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
6
7
  import fs$1, { access, chmod, cp, mkdir, mkdtemp, readFile, readdir, realpath, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
7
8
  import { DEFAULT_NODE_VERSION, DEFAULT_PNPM_VERSION, SandboxFolder, getUiDevServerMissingWarning, uiDevPort, websocketPort } from "@pipelab/constants";
8
9
  import { WebSocket, WebSocketServer as WebSocketServer$1 } from "ws";
9
10
  import http from "node:http";
10
11
  import { nanoid } from "nanoid";
11
- import { SubscriptionRequiredError, WebSocketError, configRegistry, isRequired, isSupabaseAvailable, isWebSocketRequestMessage, processGraph, savedFileMigrator, supabase, transformUrl, useLogger, usePlugins } from "@pipelab/shared";
12
+ import { SubscriptionRequiredError, WebSocketError, appSettingsMigrator, connectionsMigrator, fileRepoMigrations, isRequired, isSupabaseAvailable, isWebSocketRequestMessage, processGraph, savedFileMigrator, supabase, transformUrl, useLogger, usePlugins } from "@pipelab/shared";
12
13
  import { execa } from "execa";
13
- import tar from "tar";
14
- import yauzl from "yauzl";
15
- import archiver from "archiver";
16
- import { pipeline } from "node:stream/promises";
17
14
  import checkDiskSpace from "check-disk-space";
18
15
  import dns from "node:dns/promises";
19
16
  import pacote from "pacote";
@@ -101,8 +98,33 @@ var PipelabContext = class {
101
98
  getConfigPath(...subpaths) {
102
99
  return join(this.userDataPath, "config", ...subpaths);
103
100
  }
101
+ getSettingsPath() {
102
+ return this.getConfigPath("settings.json");
103
+ }
104
+ getConnectionsPath() {
105
+ return this.getConfigPath("connections.json");
106
+ }
107
+ getProjectsPath() {
108
+ return this.getConfigPath("projects.json");
109
+ }
110
+ _cachedSettings = null;
111
+ _cachedSettingsTime = 0;
112
+ getSettings() {
113
+ const settingsPath = this.getSettingsPath();
114
+ if (!existsSync(settingsPath)) return null;
115
+ const now = Date.now();
116
+ if (this._cachedSettings && now - this._cachedSettingsTime < 2e3) return this._cachedSettings;
117
+ try {
118
+ const content = readFileSync(settingsPath, "utf8");
119
+ this._cachedSettings = JSON.parse(content);
120
+ this._cachedSettingsTime = now;
121
+ return this._cachedSettings;
122
+ } catch (e) {
123
+ return this._cachedSettings;
124
+ }
125
+ }
104
126
  getTempPath(...subpaths) {
105
- return join(this.userDataPath, "temp", ...subpaths);
127
+ return join(this.getSettings()?.tempFolder || join(this.userDataPath, "temp"), ...subpaths);
106
128
  }
107
129
  async createTempFolder(prefix = "pipelab-") {
108
130
  const baseDir = this.getTempPath();
@@ -110,8 +132,9 @@ var PipelabContext = class {
110
132
  return await mkdtemp(join(await realpath(baseDir), prefix));
111
133
  }
112
134
  getCachePath(folder, ...subpaths) {
113
- if (!folder) return join(this.userDataPath, "cache");
114
- return join(this.userDataPath, "cache", folder, ...subpaths);
135
+ const base = this.getSettings()?.cacheFolder || join(this.userDataPath, "cache");
136
+ if (!folder) return base;
137
+ return join(base, folder, ...subpaths);
115
138
  }
116
139
  getPnpmPath(...subpaths) {
117
140
  return join(this.userDataPath, "pnpm", ...subpaths);
@@ -511,289 +534,207 @@ const registerFsHandlers = (_context) => {
511
534
  });
512
535
  };
513
536
  //#endregion
514
- //#region src/utils/fs-extras.ts
515
- /**
516
- * Ensures a directory exists and a file is created with default content if missing.
517
- */
518
- const ensure = async (filesPath, defaultContent = "{}") => {
519
- await mkdir(dirname(filesPath), { recursive: true });
520
- try {
521
- if ((await stat(filesPath)).size === 0) await writeFile(filesPath, defaultContent);
522
- } catch {
523
- await writeFile(filesPath, defaultContent);
524
- }
525
- };
526
- /**
527
- * Extracts a .tar.gz archive.
528
- */
529
- async function extractTarGz(archivePath, destinationDir) {
530
- await mkdir(destinationDir, { recursive: true });
531
- await tar.x({
532
- file: archivePath,
533
- cwd: destinationDir
534
- });
535
- }
536
- /**
537
- * Extracts a .zip archive.
538
- */
539
- async function extractZip(archivePath, destinationDir) {
540
- await mkdir(destinationDir, { recursive: true });
541
- return new Promise((resolve, reject) => {
542
- yauzl.open(archivePath, { lazyEntries: true }, (err, zipfile) => {
543
- if (err || !zipfile) return reject(err || /* @__PURE__ */ new Error("Could not open zip file"));
544
- zipfile.on("error", reject);
545
- zipfile.readEntry();
546
- zipfile.on("entry", (entry) => {
547
- const entryPath = join(destinationDir, entry.fileName);
548
- if (/\/$/.test(entry.fileName)) mkdir(entryPath, { recursive: true }).then(() => zipfile.readEntry()).catch(reject);
549
- else mkdir(dirname(entryPath), { recursive: true }).then(() => {
550
- zipfile.openReadStream(entry, (err, readStream) => {
551
- if (err || !readStream) return reject(err || /* @__PURE__ */ new Error("Could not open read stream"));
552
- readStream.on("error", reject);
553
- const writeStream = createWriteStream(entryPath);
554
- writeStream.on("error", reject);
555
- writeStream.on("close", () => zipfile.readEntry());
556
- readStream.pipe(writeStream);
557
- });
558
- }).catch(reject);
537
+ //#region src/handlers/config.ts
538
+ const registerConfigHandlers = (context) => {
539
+ const { handle } = useAPI();
540
+ const { logger } = useLogger();
541
+ handle("settings:load", async (_, { send }) => {
542
+ logger().info("settings:load");
543
+ try {
544
+ send({
545
+ type: "end",
546
+ data: {
547
+ type: "success",
548
+ result: await (await setupSettingsConfigFile(context)).getConfig()
549
+ }
559
550
  });
560
- zipfile.on("end", () => resolve());
561
- });
551
+ } catch (e) {
552
+ logger().error("settings:load error:", e);
553
+ send({
554
+ type: "end",
555
+ data: {
556
+ type: "error",
557
+ ipcError: e instanceof Error ? e.message : "Unable to load settings"
558
+ }
559
+ });
560
+ }
562
561
  });
563
- }
564
- /**
565
- * Zips a folder.
566
- */
567
- const zipFolder = async (from, to, log = console.log) => {
568
- const output = createWriteStream(to);
569
- const archive = archiver("zip", { zlib: { level: 9 } });
570
- return new Promise((resolve, reject) => {
571
- output.on("close", () => {
572
- log(archive.pointer() + " total bytes");
573
- resolve(to);
574
- });
575
- archive.on("error", reject);
576
- archive.pipe(output);
577
- archive.directory(from, false);
578
- archive.finalize();
562
+ handle("settings:save", async (_, { send, value }) => {
563
+ const { data } = value;
564
+ try {
565
+ const manager = await setupSettingsConfigFile(context);
566
+ const json = typeof data === "string" ? JSON.parse(data) : data;
567
+ await manager.setConfig(json);
568
+ send({
569
+ type: "end",
570
+ data: {
571
+ type: "success",
572
+ result: "ok"
573
+ }
574
+ });
575
+ } catch (e) {
576
+ logger().error("settings:save error:", e);
577
+ send({
578
+ type: "end",
579
+ data: {
580
+ type: "error",
581
+ ipcError: e instanceof Error ? e.message : "Unable to save settings"
582
+ }
583
+ });
584
+ }
579
585
  });
580
- };
581
- const downloadFile = async (url, localPath, hooks, abortSignal) => {
582
- const response = await fetch(url, { signal: abortSignal });
583
- if (!response.ok) throw new Error(`Failed to fetch file: ${response.statusText}`);
584
- const contentLength = response.headers.get("content-length");
585
- if (!contentLength) throw new Error("Content-Length header is missing");
586
- const totalSize = parseInt(contentLength, 10);
587
- let downloadedSize = 0;
588
- const fileStream = createWriteStream(localPath);
589
- const progressStream = new TransformStream({ transform(chunk, controller) {
590
- downloadedSize += chunk.length;
591
- const progress = downloadedSize / totalSize * 100;
592
- hooks?.onProgress?.({
593
- progress,
594
- downloadedSize
595
- });
596
- controller.enqueue(chunk);
597
- } });
598
- const readable = response.body?.pipeThrough(progressStream);
599
- if (!readable) throw new Error("Failed to create a readable stream");
600
- await pipeline(readable, fileStream);
601
- };
602
- const runWithLiveLogs = async (command, args, execaOptions, log, hooks, abortSignal) => {
603
- const subprocess = execa(command, args, {
604
- ...execaOptions,
605
- stdout: "pipe",
606
- stderr: "pipe",
607
- stdin: "pipe",
608
- env: {
609
- ...process.env,
610
- ...execaOptions.env,
611
- TERM: "xterm-256color",
612
- FORCE_STDERR_LOGGING: "1"
613
- },
614
- cancelSignal: abortSignal
586
+ handle("settings:reset", async (_, { send, value }) => {
587
+ const { key } = value;
588
+ try {
589
+ const manager = await setupSettingsConfigFile(context);
590
+ const currentConfig = await manager.getConfig();
591
+ const defaultValue = appSettingsMigrator.defaultValue[key];
592
+ await manager.setConfig({
593
+ ...currentConfig ? currentConfig : {},
594
+ [key]: defaultValue
595
+ });
596
+ send({
597
+ type: "end",
598
+ data: {
599
+ type: "success",
600
+ result: "ok"
601
+ }
602
+ });
603
+ } catch (e) {
604
+ logger().error("settings:reset error:", e);
605
+ send({
606
+ type: "end",
607
+ data: {
608
+ type: "error",
609
+ ipcError: e instanceof Error ? e.message : "Unable to reset settings"
610
+ }
611
+ });
612
+ }
615
613
  });
616
- hooks?.onCreated?.(subprocess);
617
- subprocess.stdout?.on("data", (data) => {
618
- hooks?.onStdout?.(data.toString(), subprocess);
614
+ handle("connections:load", async (_, { send }) => {
615
+ logger().info("connections:load");
616
+ try {
617
+ send({
618
+ type: "end",
619
+ data: {
620
+ type: "success",
621
+ result: await (await setupConnectionsConfigFile(context)).getConfig()
622
+ }
623
+ });
624
+ } catch (e) {
625
+ logger().error("connections:load error:", e);
626
+ send({
627
+ type: "end",
628
+ data: {
629
+ type: "error",
630
+ ipcError: e instanceof Error ? e.message : "Unable to load connections"
631
+ }
632
+ });
633
+ }
619
634
  });
620
- subprocess.stderr?.on("data", (data) => {
621
- hooks?.onStderr?.(data.toString(), subprocess);
635
+ handle("connections:save", async (_, { send, value }) => {
636
+ const { data } = value;
637
+ try {
638
+ const manager = await setupConnectionsConfigFile(context);
639
+ const json = typeof data === "string" ? JSON.parse(data) : data;
640
+ await manager.setConfig(json);
641
+ send({
642
+ type: "end",
643
+ data: {
644
+ type: "success",
645
+ result: "ok"
646
+ }
647
+ });
648
+ } catch (e) {
649
+ logger().error("connections:save error:", e);
650
+ send({
651
+ type: "end",
652
+ data: {
653
+ type: "error",
654
+ ipcError: e instanceof Error ? e.message : "Unable to save connections"
655
+ }
656
+ });
657
+ }
622
658
  });
623
- try {
624
- const { exitCode } = await subprocess;
625
- hooks?.onExit?.(exitCode ?? 0);
626
- } catch (error) {
627
- const code = error.exitCode ?? 1;
628
- hooks?.onExit?.(code);
629
- throw new Error(`Command failed with exit code ${code}: ${error.message}`);
630
- }
631
- };
632
- /**
633
- * Calculates the total size of a directory recursively.
634
- */
635
- async function getFolderSize(dirPath) {
636
- try {
637
- const ArrayOfPromises = (await readdir(dirPath, { withFileTypes: true })).map(async (file) => {
638
- const path = join(dirPath, file.name);
639
- if (file.isDirectory()) try {
640
- return await getFolderSize(path);
641
- } catch {
642
- return 0;
643
- }
644
- try {
645
- const { size } = await stat(path);
646
- return size;
647
- } catch {
648
- return 0;
649
- }
650
- });
651
- return (await Promise.all(ArrayOfPromises)).reduce((acc, size) => acc + size, 0);
652
- } catch {
653
- return 0;
654
- }
655
- }
656
- //#endregion
657
- //#region src/config.ts
658
- const getMigrator = (name) => {
659
- return configRegistry[name] || configRegistry["pipeline"];
660
- };
661
- const setupConfigFile = async (name, options) => {
662
- const ctx = options.context;
663
- const migrator = options.migrator || getMigrator(name);
664
- if (!migrator) throw new Error(`No migrator found for configuration: ${name}. All managed files must have a migration schema.`);
665
- const filesPath = path.isAbsolute(name) ? name : ctx.getConfigPath(`${name}.json`);
666
- await ensure(filesPath, JSON.stringify(migrator.defaultValue));
667
- return {
668
- setConfig: async (config) => {
669
- const { logger } = useLogger();
670
- try {
671
- await fs$1.writeFile(filesPath, JSON.stringify(config));
672
- return true;
673
- } catch (e) {
674
- logger().error(`Error saving config ${name}:`, e);
675
- return false;
676
- }
677
- },
678
- getConfig: async () => {
679
- const { logger } = useLogger();
680
- let content = void 0;
681
- let originalJson = void 0;
682
- let parseFailed = false;
683
- try {
684
- content = await fs$1.readFile(filesPath, "utf8");
685
- if (content !== void 0) originalJson = JSON.parse(content);
686
- } catch (e) {
687
- logger().error(`Error reading or parsing config ${name}:`, e);
688
- parseFailed = true;
689
- }
690
- let json = void 0;
691
- let migrationFailed = false;
692
- try {
693
- if (!parseFailed) json = await migrator.migrate(originalJson, {
694
- debug: false,
695
- onStep: async (state, version) => {
696
- const parsedPath = path.parse(filesPath);
697
- const versionedPath = ctx.getConfigPath(`${parsedPath.name}.v${version}.json`);
698
- try {
699
- await fs$1.writeFile(versionedPath, JSON.stringify(state));
700
- logger().info(`Intermediate backup created for ${name} at ${versionedPath}`);
701
- } catch (e) {
702
- logger().error(`Failed to create intermediate backup for ${name} at v${version}:`, e);
703
- }
704
- }
705
- });
706
- else json = migrator.defaultValue;
707
- } catch (e) {
708
- logger().error(`Error migrating config ${name}:`, e);
709
- migrationFailed = true;
710
- json = migrator.defaultValue;
711
- }
712
- if (originalJson?.version !== json?.version || content === void 0 || parseFailed || migrationFailed) {
713
- if (parseFailed || migrationFailed) try {
714
- const parsedPath = path.parse(filesPath);
715
- const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
716
- const corruptedPath = ctx.getConfigPath(`${parsedPath.name}.corrupted.${timestamp}.json`);
717
- const backupContent = parseFailed ? content || "" : JSON.stringify(originalJson, null, 2);
718
- await fs$1.writeFile(corruptedPath, backupContent);
719
- logger().info(`Corrupted config file preserved at ${corruptedPath}`);
720
- } catch (e) {
721
- logger().error(`Failed to backup corrupted config ${name}:`, e);
659
+ handle("connections:reset", async (_, { send, value }) => {
660
+ const { key } = value;
661
+ try {
662
+ const manager = await setupConnectionsConfigFile(context);
663
+ const currentConfig = await manager.getConfig();
664
+ const defaultValue = connectionsMigrator.defaultValue[key];
665
+ await manager.setConfig({
666
+ ...currentConfig ? currentConfig : {},
667
+ [key]: defaultValue
668
+ });
669
+ send({
670
+ type: "end",
671
+ data: {
672
+ type: "success",
673
+ result: "ok"
722
674
  }
723
- try {
724
- await fs$1.writeFile(filesPath, JSON.stringify(json));
725
- } catch (e) {
726
- logger().error(`Error saving migrated config ${name}:`, e);
675
+ });
676
+ } catch (e) {
677
+ logger().error("connections:reset error:", e);
678
+ send({
679
+ type: "end",
680
+ data: {
681
+ type: "error",
682
+ ipcError: e instanceof Error ? e.message : "Unable to reset connections"
727
683
  }
728
- }
729
- return json;
684
+ });
730
685
  }
731
- };
732
- };
733
- const deleteConfigFile = async (nameOrPath, context) => {
734
- const filesPath = path.isAbsolute(nameOrPath) ? nameOrPath : context.getConfigPath(`${nameOrPath}.json`);
735
- await fs$1.rm(filesPath, { force: true });
736
- };
737
- //#endregion
738
- //#region src/handlers/config.ts
739
- const registerConfigHandlers = (context) => {
740
- const { handle } = useAPI();
741
- const { logger } = useLogger();
742
- handle("config:load", async (_, { send, value }) => {
743
- const { config: name } = value;
744
- logger().info("config:load", name);
686
+ });
687
+ handle("projects:load", async (_, { send }) => {
688
+ logger().info("projects:load");
745
689
  try {
746
690
  send({
747
691
  type: "end",
748
692
  data: {
749
693
  type: "success",
750
- result: { result: await (await setupConfigFile(name, { context })).getConfig() }
694
+ result: await (await setupProjectsConfigFile(context)).getConfig()
751
695
  }
752
696
  });
753
697
  } catch (e) {
754
- logger().error(`config:load error for ${name}:`, e);
698
+ logger().error("projects:load error:", e);
755
699
  send({
756
700
  type: "end",
757
701
  data: {
758
702
  type: "error",
759
- ipcError: e instanceof Error ? e.message : `Unable to load config ${name}`
703
+ ipcError: e instanceof Error ? e.message : "Unable to load projects"
760
704
  }
761
705
  });
762
706
  }
763
707
  });
764
- handle("config:save", async (_, { send, value }) => {
765
- const { data, config: name } = value;
708
+ handle("projects:save", async (_, { send, value }) => {
709
+ const { data } = value;
766
710
  try {
767
- const manager = await setupConfigFile(name, { context });
711
+ const manager = await setupProjectsConfigFile(context);
768
712
  const json = typeof data === "string" ? JSON.parse(data) : data;
769
713
  await manager.setConfig(json);
770
714
  send({
771
715
  type: "end",
772
716
  data: {
773
717
  type: "success",
774
- result: { result: "ok" }
718
+ result: "ok"
775
719
  }
776
720
  });
777
721
  } catch (e) {
778
- logger().error(`config:save error for ${name}:`, e);
722
+ logger().error("projects:save error:", e);
779
723
  send({
780
724
  type: "end",
781
725
  data: {
782
726
  type: "error",
783
- ipcError: e instanceof Error ? e.message : `Unable to save config ${name}`
727
+ ipcError: e instanceof Error ? e.message : "Unable to save projects"
784
728
  }
785
729
  });
786
730
  }
787
731
  });
788
- handle("config:reset", async (event, { value, send }) => {
789
- const { config: name, key } = value;
790
- logger().info("config:reset", name, key);
732
+ handle("projects:reset", async (_, { send, value }) => {
733
+ const { key } = value;
791
734
  try {
792
- const manager = await setupConfigFile(name, { context });
735
+ const manager = await setupProjectsConfigFile(context);
793
736
  const currentConfig = await manager.getConfig();
794
- const migrator = getMigrator(name);
795
- if (!migrator) throw new Error(`No migrator found for configuration: ${name}`);
796
- const defaultValue = migrator.defaultValue[key];
737
+ const defaultValue = fileRepoMigrations.defaultValue[key];
797
738
  await manager.setConfig({
798
739
  ...currentConfig ? currentConfig : {},
799
740
  [key]: defaultValue
@@ -802,39 +743,154 @@ const registerConfigHandlers = (context) => {
802
743
  type: "end",
803
744
  data: {
804
745
  type: "success",
805
- result: { result: "ok" }
746
+ result: "ok"
806
747
  }
807
748
  });
808
749
  } catch (e) {
809
- logger().error(`config:reset error for ${name}:`, e);
750
+ logger().error("projects:reset error:", e);
810
751
  send({
811
752
  type: "end",
812
753
  data: {
813
754
  type: "error",
814
- ipcError: e instanceof Error ? e.message : `Unable to reset config ${name}`
755
+ ipcError: e instanceof Error ? e.message : "Unable to reset projects"
815
756
  }
816
757
  });
817
758
  }
818
759
  });
819
- handle("config:delete", async (_, { send, value }) => {
820
- const { config: name } = value;
821
- logger().info("config:delete", name);
760
+ handle("pipeline:load-by-name", async (_, { send, value }) => {
761
+ const { name } = value;
762
+ logger().info("pipeline:load-by-name", name);
822
763
  try {
823
- await deleteConfigFile(name, context);
824
764
  send({
825
765
  type: "end",
826
766
  data: {
827
767
  type: "success",
828
- result: { result: "ok" }
768
+ result: await (await setupPipelineConfigFileByName(name, context)).getConfig()
769
+ }
770
+ });
771
+ } catch (e) {
772
+ logger().error(`pipeline:load-by-name error for ${name}:`, e);
773
+ send({
774
+ type: "end",
775
+ data: {
776
+ type: "error",
777
+ ipcError: e instanceof Error ? e.message : `Unable to load pipeline ${name}`
778
+ }
779
+ });
780
+ }
781
+ });
782
+ handle("pipeline:load-by-path", async (_, { send, value }) => {
783
+ const { path: absolutePath } = value;
784
+ logger().info("pipeline:load-by-path", absolutePath);
785
+ try {
786
+ send({
787
+ type: "end",
788
+ data: {
789
+ type: "success",
790
+ result: await (await setupPipelineConfigFileByPath(absolutePath, context)).getConfig()
791
+ }
792
+ });
793
+ } catch (e) {
794
+ logger().error(`pipeline:load-by-path error for ${absolutePath}:`, e);
795
+ send({
796
+ type: "end",
797
+ data: {
798
+ type: "error",
799
+ ipcError: e instanceof Error ? e.message : `Unable to load pipeline ${absolutePath}`
800
+ }
801
+ });
802
+ }
803
+ });
804
+ handle("pipeline:save-by-name", async (_, { send, value }) => {
805
+ const { data, name } = value;
806
+ try {
807
+ const manager = await setupPipelineConfigFileByName(name, context);
808
+ const json = typeof data === "string" ? JSON.parse(data) : data;
809
+ await manager.setConfig(json);
810
+ send({
811
+ type: "end",
812
+ data: {
813
+ type: "success",
814
+ result: "ok"
815
+ }
816
+ });
817
+ } catch (e) {
818
+ logger().error(`pipeline:save-by-name error for ${name}:`, e);
819
+ send({
820
+ type: "end",
821
+ data: {
822
+ type: "error",
823
+ ipcError: e instanceof Error ? e.message : `Unable to save pipeline ${name}`
824
+ }
825
+ });
826
+ }
827
+ });
828
+ handle("pipeline:save-by-path", async (_, { send, value }) => {
829
+ const { data, path: absolutePath } = value;
830
+ try {
831
+ const manager = await setupPipelineConfigFileByPath(absolutePath, context);
832
+ const json = typeof data === "string" ? JSON.parse(data) : data;
833
+ await manager.setConfig(json);
834
+ send({
835
+ type: "end",
836
+ data: {
837
+ type: "success",
838
+ result: "ok"
839
+ }
840
+ });
841
+ } catch (e) {
842
+ logger().error(`pipeline:save-by-path error for ${absolutePath}:`, e);
843
+ send({
844
+ type: "end",
845
+ data: {
846
+ type: "error",
847
+ ipcError: e instanceof Error ? e.message : `Unable to save pipeline ${absolutePath}`
848
+ }
849
+ });
850
+ }
851
+ });
852
+ handle("pipeline:delete-by-name", async (_, { send, value }) => {
853
+ const { name } = value;
854
+ logger().info("pipeline:delete-by-name", name);
855
+ try {
856
+ await deletePipelineConfigFileByName(name, context);
857
+ send({
858
+ type: "end",
859
+ data: {
860
+ type: "success",
861
+ result: "ok"
862
+ }
863
+ });
864
+ } catch (e) {
865
+ logger().error(`pipeline:delete-by-name error for ${name}:`, e);
866
+ send({
867
+ type: "end",
868
+ data: {
869
+ type: "error",
870
+ ipcError: e instanceof Error ? e.message : `Unable to delete pipeline ${name}`
871
+ }
872
+ });
873
+ }
874
+ });
875
+ handle("pipeline:delete-by-path", async (_, { send, value }) => {
876
+ const { path: absolutePath } = value;
877
+ logger().info("pipeline:delete-by-path", absolutePath);
878
+ try {
879
+ await deletePipelineConfigFileByPath(absolutePath, context);
880
+ send({
881
+ type: "end",
882
+ data: {
883
+ type: "success",
884
+ result: "ok"
829
885
  }
830
886
  });
831
887
  } catch (e) {
832
- logger().error(`config:delete error for ${name}:`, e);
888
+ logger().error(`pipeline:delete-by-path error for ${absolutePath}:`, e);
833
889
  send({
834
890
  type: "end",
835
891
  data: {
836
892
  type: "error",
837
- ipcError: e instanceof Error ? e.message : `Unable to delete config ${name}`
893
+ ipcError: e instanceof Error ? e.message : `Unable to delete pipeline ${absolutePath}`
838
894
  }
839
895
  });
840
896
  }
@@ -8383,7 +8439,7 @@ var require_error = /* @__PURE__ */ __commonJSMin(((exports, module) => {
8383
8439
  })();
8384
8440
  }));
8385
8441
  //#endregion
8386
- //#region src/migrations.ts
8442
+ //#region src/server.ts
8387
8443
  var import_src = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
8388
8444
  const { promisify } = __require("util");
8389
8445
  const path$1 = __require("path");
@@ -8837,44 +8893,6 @@ var import_src = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((export
8837
8893
  stream.pipe(response);
8838
8894
  };
8839
8895
  })))(), 1);
8840
- /**
8841
- * Registers migration handlers for the CLI/Standalone server.
8842
- * This overrides the default handlers from @pipelab/core-node to include
8843
- * pipeline and file repo migrations directly in the backend.
8844
- */
8845
- function registerMigrationHandlers(context) {
8846
- const { handle } = useAPI();
8847
- const { logger } = useLogger();
8848
- handle("config:load", async (_, { send, value }) => {
8849
- const { config: name } = value;
8850
- logger().info("[CLI Migration] config:load", name);
8851
- try {
8852
- const migrator = getMigrator(name);
8853
- if (!migrator) throw new Error(`No migrator found for configuration: ${name}. All files loaded via config:load MUST have a migration schema.`);
8854
- send({
8855
- type: "end",
8856
- data: {
8857
- type: "success",
8858
- result: { result: await (await setupConfigFile(name, {
8859
- context,
8860
- migrator
8861
- })).getConfig() }
8862
- }
8863
- });
8864
- } catch (e) {
8865
- logger().error(`[CLI Migration] config:load error for ${name}:`, e);
8866
- send({
8867
- type: "end",
8868
- data: {
8869
- type: "error",
8870
- ipcError: e instanceof Error ? e.message : `Unable to load config ${name}`
8871
- }
8872
- });
8873
- }
8874
- });
8875
- }
8876
- //#endregion
8877
- //#region src/server.ts
8878
8896
  const sendStartupProgress = (message) => {
8879
8897
  console.log(`[Startup Progress] ${message}`);
8880
8898
  webSocketServer.broadcast("startup:progress", {
@@ -8961,7 +8979,6 @@ async function serveCommand(options, version, _dirname) {
8961
8979
  version,
8962
8980
  context
8963
8981
  });
8964
- registerMigrationHandlers(context);
8965
8982
  sendStartupReady();
8966
8983
  return server;
8967
8984
  }
@@ -9693,8 +9710,8 @@ const builtInPlugins = async (options) => {
9693
9710
  }
9694
9711
  }
9695
9712
  try {
9696
- const { setupConfigFile } = await import("./config-CEkOf95p.mjs");
9697
- const settingsPlugins = (await (await setupConfigFile("settings", { context: options.context })).getConfig())?.plugins || [];
9713
+ const { setupSettingsConfigFile } = await import("./config-Bi0ORcTK.mjs");
9714
+ const settingsPlugins = (await (await setupSettingsConfigFile(options.context)).getConfig())?.plugins || [];
9698
9715
  for (const plugin of settingsPlugins) if (plugin.name) if (plugin.enabled) {
9699
9716
  if (!pluginsToLoad.has(plugin.name)) pluginsToLoad.set(plugin.name, "latest");
9700
9717
  } else pluginsToLoad.delete(plugin.name);
@@ -9793,7 +9810,7 @@ const executeGraphWithHistory = async ({ graph, variables, projectName, projectP
9793
9810
  } else logger().error(`[Runner] Failed to load or register plugin "${pluginId}"`);
9794
9811
  }
9795
9812
  logger().info(`[Sandbox] Execution sandbox created at: ${sandboxPath}`);
9796
- await (await setupConfigFile("settings", { context: ctx })).getConfig();
9813
+ await (await setupSettingsConfigFile(ctx)).getConfig();
9797
9814
  try {
9798
9815
  const result = await processGraph({
9799
9816
  graph,
@@ -10301,7 +10318,7 @@ const registerEngineHandlers = (context) => {
10301
10318
  });
10302
10319
  handle("graph:execute", async (event, { send, value }) => {
10303
10320
  const { graph, variables, projectName, projectPath, pipelineId } = value;
10304
- await (await setupConfigFile("settings", { context })).getConfig();
10321
+ await (await setupSettingsConfigFile(context)).getConfig();
10305
10322
  const effectiveProjectName = projectName || "Unnamed Project";
10306
10323
  const effectiveProjectPath = projectPath || "";
10307
10324
  const effectivePipelineId = pipelineId || "unknown";
@@ -10854,6 +10871,470 @@ const registerPluginsHandlers = (context) => {
10854
10871
  });
10855
10872
  };
10856
10873
  //#endregion
10874
+ //#region src/handlers/migration.ts
10875
+ const registerMigrationHandlers = (context) => {
10876
+ const { handle } = useAPI();
10877
+ const { logger } = useLogger();
10878
+ const getFileMetadata = async (filePath) => {
10879
+ const exists = existsSync(filePath);
10880
+ let mtime = Date.now();
10881
+ let version = null;
10882
+ if (exists) {
10883
+ try {
10884
+ const stat = await fs$1.stat(filePath);
10885
+ mtime = stat.mtime ? stat.mtime.getTime() : Date.now();
10886
+ } catch (e) {
10887
+ mtime = Date.now();
10888
+ }
10889
+ try {
10890
+ const content = await fs$1.readFile(filePath, "utf8");
10891
+ const json = JSON.parse(content);
10892
+ version = json && typeof json.version === "string" ? json.version : null;
10893
+ } catch (e) {}
10894
+ } else mtime = Date.now();
10895
+ return {
10896
+ exists,
10897
+ mtime,
10898
+ version
10899
+ };
10900
+ };
10901
+ const isVersionImportable = (sourceVer, targetVer) => {
10902
+ if (!sourceVer) return false;
10903
+ if (!targetVer) return true;
10904
+ try {
10905
+ const coercedSource = semver.coerce(sourceVer);
10906
+ const coercedTarget = semver.coerce(targetVer);
10907
+ if (!coercedSource) return false;
10908
+ if (!coercedTarget) return true;
10909
+ return semver.lte(coercedSource, coercedTarget);
10910
+ } catch {
10911
+ return sourceVer <= targetVer;
10912
+ }
10913
+ };
10914
+ handle("migration:scan-stable", async (_, { send }) => {
10915
+ logger().info("[Migration] Scanning other channel database...");
10916
+ try {
10917
+ const isStable = context.userDataPath.endsWith("app") || !context.userDataPath.includes("app-beta");
10918
+ const sourceEnv = isStable ? "beta" : "prod";
10919
+ const sourcePath = getDefaultUserDataPath(sourceEnv);
10920
+ if (sourcePath === context.userDataPath) {
10921
+ logger().info("[Migration] Running in same channel. Disabling migration scan.");
10922
+ return send({
10923
+ type: "end",
10924
+ data: {
10925
+ type: "success",
10926
+ result: {
10927
+ sourceChannel: isStable ? "Beta" : "Stable",
10928
+ targetChannel: isStable ? "Stable" : "Beta",
10929
+ settingsExists: false,
10930
+ settingsVersion: null,
10931
+ settingsVersionTarget: null,
10932
+ settingsMtimeSource: Date.now(),
10933
+ settingsMtimeTarget: Date.now(),
10934
+ settingsImportable: false,
10935
+ connectionsExists: false,
10936
+ connectionsCount: 0,
10937
+ connectionsVersion: null,
10938
+ connectionsVersionTarget: null,
10939
+ connectionsMtimeSource: Date.now(),
10940
+ connectionsMtimeTarget: Date.now(),
10941
+ connectionsImportable: false,
10942
+ projectsExists: false,
10943
+ projectsVersion: null,
10944
+ projectsVersionTarget: null,
10945
+ projectsMtimeSource: Date.now(),
10946
+ projectsMtimeTarget: Date.now(),
10947
+ projectsImportable: false,
10948
+ projects: []
10949
+ }
10950
+ }
10951
+ });
10952
+ }
10953
+ const sourceContext = new PipelabContext({
10954
+ userDataPath: sourcePath,
10955
+ releaseTag: sourceEnv
10956
+ });
10957
+ const sourceSettingsPath = sourceContext.getSettingsPath();
10958
+ const targetSettingsPath = context.getSettingsPath();
10959
+ const sourceConnectionsPath = sourceContext.getConnectionsPath();
10960
+ const targetConnectionsPath = context.getConnectionsPath();
10961
+ const sourceProjectsPath = sourceContext.getProjectsPath();
10962
+ const targetProjectsPath = context.getProjectsPath();
10963
+ const sourceSettingsMeta = await getFileMetadata(sourceSettingsPath);
10964
+ const targetSettingsMeta = await getFileMetadata(targetSettingsPath);
10965
+ const sourceConnectionsMeta = await getFileMetadata(sourceConnectionsPath);
10966
+ const targetConnectionsMeta = await getFileMetadata(targetConnectionsPath);
10967
+ const sourceProjectsMeta = await getFileMetadata(sourceProjectsPath);
10968
+ const targetProjectsMeta = await getFileMetadata(targetProjectsPath);
10969
+ const settingsExists = sourceSettingsMeta.exists;
10970
+ const settingsVersion = sourceSettingsMeta.version;
10971
+ const settingsMtimeSource = sourceSettingsMeta.mtime;
10972
+ const settingsMtimeTarget = targetSettingsMeta.mtime;
10973
+ const settingsImportable = isVersionImportable(settingsVersion, targetSettingsMeta.version);
10974
+ const connectionsExists = sourceConnectionsMeta.exists;
10975
+ const connectionsVersion = sourceConnectionsMeta.version;
10976
+ const connectionsMtimeSource = sourceConnectionsMeta.mtime;
10977
+ const connectionsMtimeTarget = targetConnectionsMeta.mtime;
10978
+ const connectionsImportable = isVersionImportable(connectionsVersion, targetConnectionsMeta.version);
10979
+ const projectsExists = sourceProjectsMeta.exists;
10980
+ const projectsVersion = sourceProjectsMeta.version;
10981
+ const projectsMtimeSource = sourceProjectsMeta.mtime;
10982
+ const projectsMtimeTarget = targetProjectsMeta.mtime;
10983
+ const projectsImportable = isVersionImportable(projectsVersion, targetProjectsMeta.version);
10984
+ let connectionsCount = 0;
10985
+ try {
10986
+ if (sourceConnectionsMeta.exists) {
10987
+ const connContent = await fs$1.readFile(sourceConnectionsPath, "utf8");
10988
+ const connJson = JSON.parse(connContent);
10989
+ if (connJson && Array.isArray(connJson.connections)) connectionsCount = connJson.connections.length;
10990
+ }
10991
+ } catch (err) {
10992
+ logger().error("[Migration] Error parsing source connections.json:", err);
10993
+ }
10994
+ let sourceProjectsList = [];
10995
+ let sourcePipelinesList = [];
10996
+ try {
10997
+ if (sourceProjectsMeta.exists) {
10998
+ const projContent = await fs$1.readFile(sourceProjectsPath, "utf8");
10999
+ const projJson = JSON.parse(projContent);
11000
+ sourceProjectsList = projJson.projects || [];
11001
+ sourcePipelinesList = projJson.pipelines || [];
11002
+ }
11003
+ } catch (err) {
11004
+ logger().error("[Migration] Error parsing source projects.json:", err);
11005
+ }
11006
+ let targetPipelinesList = [];
11007
+ try {
11008
+ if (targetProjectsMeta.exists) {
11009
+ const targetProjContent = await fs$1.readFile(targetProjectsPath, "utf8");
11010
+ targetPipelinesList = JSON.parse(targetProjContent).pipelines || [];
11011
+ }
11012
+ } catch (err) {}
11013
+ const projectsReport = [];
11014
+ for (const proj of sourceProjectsList) {
11015
+ const projPipelines = sourcePipelinesList.filter((p) => p.project === proj.id);
11016
+ const pipelinesReport = [];
11017
+ for (const pipe of projPipelines) {
11018
+ const targetPipe = targetPipelinesList.find((bp) => bp.id === pipe.id);
11019
+ const existsInBeta = !!targetPipe;
11020
+ let pipeName = pipe.id;
11021
+ let pipeDesc = "";
11022
+ if (pipe.type === "internal") {
11023
+ const sourcePipeFile = sourceContext.getConfigPath(`${pipe.configName}.json`);
11024
+ try {
11025
+ if (existsSync(sourcePipeFile)) {
11026
+ const pipeContent = await fs$1.readFile(sourcePipeFile, "utf8");
11027
+ const pipeJson = JSON.parse(pipeContent);
11028
+ pipeName = pipeJson.name || pipeName;
11029
+ pipeDesc = pipeJson.description || pipeDesc;
11030
+ }
11031
+ } catch (err) {
11032
+ logger().error(`[Migration] Error reading source pipeline file ${sourcePipeFile}:`, err);
11033
+ }
11034
+ } else if (pipe.type === "external") {
11035
+ pipeName = pipe.summary?.name || pipeName;
11036
+ pipeDesc = pipe.summary?.description || pipeDesc;
11037
+ }
11038
+ pipelinesReport.push({
11039
+ id: pipe.id,
11040
+ name: pipeName,
11041
+ description: pipeDesc,
11042
+ type: pipe.type,
11043
+ lastModifiedStable: pipe.type !== "pipelab-cloud" ? pipe.lastModified : void 0,
11044
+ lastModifiedBeta: targetPipe && targetPipe.type !== "pipelab-cloud" ? targetPipe.lastModified : void 0,
11045
+ existsInBeta
11046
+ });
11047
+ }
11048
+ projectsReport.push({
11049
+ id: proj.id,
11050
+ name: proj.name,
11051
+ description: proj.description || "",
11052
+ pipelines: pipelinesReport
11053
+ });
11054
+ }
11055
+ send({
11056
+ type: "end",
11057
+ data: {
11058
+ type: "success",
11059
+ result: {
11060
+ sourceChannel: isStable ? "Beta" : "Stable",
11061
+ targetChannel: isStable ? "Stable" : "Beta",
11062
+ settingsExists,
11063
+ settingsVersion,
11064
+ settingsVersionTarget: targetSettingsMeta.version,
11065
+ settingsMtimeSource,
11066
+ settingsMtimeTarget,
11067
+ settingsImportable,
11068
+ connectionsExists,
11069
+ connectionsCount,
11070
+ connectionsVersion,
11071
+ connectionsVersionTarget: targetConnectionsMeta.version,
11072
+ connectionsMtimeSource,
11073
+ connectionsMtimeTarget,
11074
+ connectionsImportable,
11075
+ projectsExists,
11076
+ projectsVersion,
11077
+ projectsVersionTarget: targetProjectsMeta.version,
11078
+ projectsMtimeSource,
11079
+ projectsMtimeTarget,
11080
+ projectsImportable,
11081
+ projects: projectsReport
11082
+ }
11083
+ }
11084
+ });
11085
+ } catch (e) {
11086
+ logger().error("[Migration] Error scanning other channel folder:", e);
11087
+ send({
11088
+ type: "end",
11089
+ data: {
11090
+ type: "error",
11091
+ ipcError: e instanceof Error ? e.message : "Failed to scan other channel database"
11092
+ }
11093
+ });
11094
+ }
11095
+ });
11096
+ handle("migration:perform", async (_, { send, value }) => {
11097
+ logger().info("[Migration] Performing migration...");
11098
+ try {
11099
+ const { migrateSettings, migrateConnections, selectedProjects, selectedPipelines } = value;
11100
+ const sourceEnv = context.userDataPath.endsWith("app") || !context.userDataPath.includes("app-beta") ? "beta" : "prod";
11101
+ const sourcePath = getDefaultUserDataPath(sourceEnv);
11102
+ const sourceContext = new PipelabContext({
11103
+ userDataPath: sourcePath,
11104
+ releaseTag: sourceEnv
11105
+ });
11106
+ sourceContext.getConfigPath();
11107
+ const targetConfigDir = context.getConfigPath();
11108
+ if (sourcePath === context.userDataPath) throw new Error("Cannot migrate data: Source and target directories are identical.");
11109
+ if (existsSync(targetConfigDir)) {
11110
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
11111
+ const backupDir = context.getConfigPath("backups", `backup_${timestamp}`);
11112
+ await fs$1.mkdir(backupDir, { recursive: true });
11113
+ const entries = await fs$1.readdir(targetConfigDir, { withFileTypes: true });
11114
+ for (const entry of entries) {
11115
+ if (entry.name === "backups") continue;
11116
+ const srcPath = join(targetConfigDir, entry.name);
11117
+ const destPath = join(backupDir, entry.name);
11118
+ try {
11119
+ await fs$1.cp(srcPath, destPath, { recursive: true });
11120
+ } catch (copyErr) {
11121
+ logger().error(`[Migration] Failed to backup ${entry.name}:`, copyErr);
11122
+ }
11123
+ }
11124
+ logger().info(`[Migration] Config backup successfully created at ${backupDir}`);
11125
+ }
11126
+ if (migrateSettings) {
11127
+ const sourceSettingsFile = sourceContext.getSettingsPath();
11128
+ const targetSettingsFile = context.getSettingsPath();
11129
+ const sourceMeta = await getFileMetadata(sourceSettingsFile);
11130
+ const targetMeta = await getFileMetadata(targetSettingsFile);
11131
+ if (!isVersionImportable(sourceMeta.version, targetMeta.version)) throw new Error("Cannot migrate settings: Source version is newer than current version.");
11132
+ if (existsSync(sourceSettingsFile)) {
11133
+ let stableSettings = {
11134
+ version: "7.0.0",
11135
+ locale: "en-US",
11136
+ theme: "light",
11137
+ autosave: true,
11138
+ agents: [],
11139
+ plugins: [],
11140
+ tours: {
11141
+ dashboard: {
11142
+ step: 0,
11143
+ completed: false
11144
+ },
11145
+ editor: {
11146
+ step: 0,
11147
+ completed: false
11148
+ }
11149
+ }
11150
+ };
11151
+ try {
11152
+ const stableContent = await fs$1.readFile(sourceSettingsFile, "utf8");
11153
+ stableSettings = JSON.parse(stableContent);
11154
+ } catch (e) {
11155
+ logger().error("[Migration] Failed to read source settings.json:", e);
11156
+ }
11157
+ let betaSettings = {
11158
+ version: "7.0.0",
11159
+ locale: "en-US",
11160
+ theme: "light",
11161
+ autosave: true,
11162
+ agents: [],
11163
+ plugins: [],
11164
+ tours: {
11165
+ dashboard: {
11166
+ step: 0,
11167
+ completed: false
11168
+ },
11169
+ editor: {
11170
+ step: 0,
11171
+ completed: false
11172
+ }
11173
+ }
11174
+ };
11175
+ if (existsSync(targetSettingsFile)) try {
11176
+ const betaContent = await fs$1.readFile(targetSettingsFile, "utf8");
11177
+ betaSettings = JSON.parse(betaContent);
11178
+ } catch (e) {}
11179
+ betaSettings.theme = stableSettings.theme ?? betaSettings.theme;
11180
+ betaSettings.locale = stableSettings.locale ?? betaSettings.locale;
11181
+ betaSettings.autosave = stableSettings.autosave ?? betaSettings.autosave;
11182
+ if (stableSettings.tours) {
11183
+ betaSettings.tours = betaSettings.tours || {
11184
+ dashboard: {
11185
+ step: 0,
11186
+ completed: false
11187
+ },
11188
+ editor: {
11189
+ step: 0,
11190
+ completed: false
11191
+ }
11192
+ };
11193
+ if (stableSettings.tours.dashboard) betaSettings.tours.dashboard = stableSettings.tours.dashboard;
11194
+ if (stableSettings.tours.editor) betaSettings.tours.editor = stableSettings.tours.editor;
11195
+ }
11196
+ const betaAgents = betaSettings.agents || [];
11197
+ const stableAgents = stableSettings.agents || [];
11198
+ for (const sAgent of stableAgents) {
11199
+ const existingIdx = betaAgents.findIndex((a) => a.id === sAgent.id);
11200
+ if (existingIdx >= 0) betaAgents[existingIdx] = { ...sAgent };
11201
+ else betaAgents.push({ ...sAgent });
11202
+ }
11203
+ betaSettings.agents = betaAgents;
11204
+ const betaPlugins = betaSettings.plugins || [];
11205
+ const stablePlugins = stableSettings.plugins || [];
11206
+ for (const sPlugin of stablePlugins) {
11207
+ const existingIdx = betaPlugins.findIndex((p) => p.name === sPlugin.name);
11208
+ if (existingIdx >= 0) betaPlugins[existingIdx] = { ...sPlugin };
11209
+ else betaPlugins.push({ ...sPlugin });
11210
+ }
11211
+ betaSettings.plugins = betaPlugins;
11212
+ await fs$1.mkdir(dirname(targetSettingsFile), { recursive: true });
11213
+ await fs$1.writeFile(targetSettingsFile, JSON.stringify(betaSettings, null, 2));
11214
+ logger().info("[Migration] Settings merged successfully");
11215
+ }
11216
+ }
11217
+ if (migrateConnections) {
11218
+ const sourceConnectionsFile = sourceContext.getConnectionsPath();
11219
+ const targetConnectionsFile = context.getConnectionsPath();
11220
+ const sourceMeta = await getFileMetadata(sourceConnectionsFile);
11221
+ const targetMeta = await getFileMetadata(targetConnectionsFile);
11222
+ if (!isVersionImportable(sourceMeta.version, targetMeta.version)) throw new Error("Cannot migrate connections: Source version is newer than current version.");
11223
+ if (existsSync(sourceConnectionsFile)) {
11224
+ let stableConnections = {
11225
+ version: "1.0.0",
11226
+ connections: []
11227
+ };
11228
+ try {
11229
+ const stableContent = await fs$1.readFile(sourceConnectionsFile, "utf8");
11230
+ stableConnections = JSON.parse(stableContent);
11231
+ } catch (e) {
11232
+ logger().error("[Migration] Failed to read source connections.json:", e);
11233
+ }
11234
+ let betaConnections = {
11235
+ version: "1.0.0",
11236
+ connections: []
11237
+ };
11238
+ if (existsSync(targetConnectionsFile)) try {
11239
+ const betaContent = await fs$1.readFile(targetConnectionsFile, "utf8");
11240
+ betaConnections = JSON.parse(betaContent);
11241
+ } catch (e) {}
11242
+ betaConnections.connections = betaConnections.connections || [];
11243
+ stableConnections.connections = stableConnections.connections || [];
11244
+ for (const sConn of stableConnections.connections) {
11245
+ const existingIdx = betaConnections.connections.findIndex((c) => c.id === sConn.id);
11246
+ if (existingIdx >= 0) betaConnections.connections[existingIdx] = { ...sConn };
11247
+ else betaConnections.connections.push({ ...sConn });
11248
+ }
11249
+ await fs$1.mkdir(dirname(targetConnectionsFile), { recursive: true });
11250
+ await fs$1.writeFile(targetConnectionsFile, JSON.stringify(betaConnections, null, 2));
11251
+ logger().info("[Migration] Connections merged successfully");
11252
+ }
11253
+ }
11254
+ if (selectedPipelines.length > 0 || selectedProjects.length > 0) {
11255
+ const sourceProjectsFile = sourceContext.getProjectsPath();
11256
+ const targetProjectsFile = context.getProjectsPath();
11257
+ const sourceMeta = await getFileMetadata(sourceProjectsFile);
11258
+ const targetMeta = await getFileMetadata(targetProjectsFile);
11259
+ if (!isVersionImportable(sourceMeta.version, targetMeta.version)) throw new Error("Cannot migrate projects: Source version is newer than current version.");
11260
+ let stableFileRepo = {
11261
+ version: "3.0.0",
11262
+ projects: [],
11263
+ pipelines: []
11264
+ };
11265
+ if (existsSync(sourceProjectsFile)) {
11266
+ const stableContent = await fs$1.readFile(sourceProjectsFile, "utf8");
11267
+ stableFileRepo = JSON.parse(stableContent);
11268
+ }
11269
+ let betaFileRepo = {
11270
+ version: "3.0.0",
11271
+ projects: [],
11272
+ pipelines: []
11273
+ };
11274
+ if (existsSync(targetProjectsFile)) try {
11275
+ const betaContent = await fs$1.readFile(targetProjectsFile, "utf8");
11276
+ betaFileRepo = JSON.parse(betaContent);
11277
+ } catch (readErr) {
11278
+ logger().warn("[Migration] Failed to parse existing target projects.json, starting fresh:", readErr);
11279
+ }
11280
+ betaFileRepo.projects = betaFileRepo.projects || [];
11281
+ betaFileRepo.pipelines = betaFileRepo.pipelines || [];
11282
+ for (const projId of selectedProjects) {
11283
+ const stableProj = stableFileRepo.projects.find((p) => p.id === projId);
11284
+ if (stableProj) {
11285
+ const existingProjIdx = betaFileRepo.projects.findIndex((p) => p.id === projId);
11286
+ if (existingProjIdx >= 0) betaFileRepo.projects[existingProjIdx] = { ...stableProj };
11287
+ else betaFileRepo.projects.push({ ...stableProj });
11288
+ }
11289
+ }
11290
+ for (const pipeId of selectedPipelines) {
11291
+ const stablePipe = (stableFileRepo.pipelines || []).find((p) => p.id === pipeId);
11292
+ if (stablePipe) {
11293
+ const parentProjId = stablePipe.project;
11294
+ if (!betaFileRepo.projects.some((p) => p.id === parentProjId)) {
11295
+ const stableProj = stableFileRepo.projects.find((p) => p.id === parentProjId);
11296
+ if (stableProj) betaFileRepo.projects.push({ ...stableProj });
11297
+ }
11298
+ if (stablePipe.type === "internal") {
11299
+ const stablePipeFile = sourceContext.getConfigPath(`${stablePipe.configName}.json`);
11300
+ const betaPipeFile = context.getConfigPath(`${stablePipe.configName}.json`);
11301
+ if (existsSync(stablePipeFile)) {
11302
+ await fs$1.mkdir(dirname(betaPipeFile), { recursive: true });
11303
+ await fs$1.copyFile(stablePipeFile, betaPipeFile);
11304
+ logger().info(`[Migration] Copied pipeline file: ${stablePipe.configName}`);
11305
+ }
11306
+ }
11307
+ const updatedPipe = { ...stablePipe };
11308
+ if (updatedPipe.type !== "pipelab-cloud") updatedPipe.lastModified = (/* @__PURE__ */ new Date()).toISOString();
11309
+ const existingPipeIdx = betaFileRepo.pipelines.findIndex((p) => p.id === pipeId);
11310
+ if (existingPipeIdx >= 0) betaFileRepo.pipelines[existingPipeIdx] = updatedPipe;
11311
+ else betaFileRepo.pipelines.push(updatedPipe);
11312
+ }
11313
+ }
11314
+ await fs$1.mkdir(dirname(targetProjectsFile), { recursive: true });
11315
+ await fs$1.writeFile(targetProjectsFile, JSON.stringify(betaFileRepo, null, 2));
11316
+ logger().info("[Migration] Merged and saved projects.json successfully");
11317
+ }
11318
+ send({
11319
+ type: "end",
11320
+ data: {
11321
+ type: "success",
11322
+ result: { result: "ok" }
11323
+ }
11324
+ });
11325
+ } catch (e) {
11326
+ logger().error("[Migration] Error performing migration:", e);
11327
+ send({
11328
+ type: "end",
11329
+ data: {
11330
+ type: "error",
11331
+ ipcError: e instanceof Error ? e.message : "Failed to migrate selected data"
11332
+ }
11333
+ });
11334
+ }
11335
+ });
11336
+ };
11337
+ //#endregion
10857
11338
  //#region src/handlers/index.ts
10858
11339
  const registerAllHandlers = async (options) => {
10859
11340
  const context = options.context;
@@ -10866,6 +11347,7 @@ const registerAllHandlers = async (options) => {
10866
11347
  registerAuthHandlers(context);
10867
11348
  registerSystemHandlers(options);
10868
11349
  registerPluginsHandlers(context);
11350
+ registerMigrationHandlers(context);
10869
11351
  const { registerPlugins } = usePlugins();
10870
11352
  builtInPlugins({ context });
10871
11353
  };
@@ -53138,7 +53620,6 @@ async function runPipelineCommand(file, options, version) {
53138
53620
  version,
53139
53621
  context
53140
53622
  });
53141
- registerMigrationHandlers(context);
53142
53623
  const cachePath = context.getCachePath(CacheFolder.Pipelines, effectivePipelineId);
53143
53624
  await mkdir(cachePath, { recursive: true });
53144
53625
  const abortController = new AbortController();
@@ -53311,6 +53792,6 @@ async function fetchLatestDesktopRelease(options = {}) {
53311
53792
  return latest;
53312
53793
  }
53313
53794
  //#endregion
53314
- export { BuildHistoryStorage, CacheFolder, PipelabContext, WebSocketServer, builtInPlugins, deleteConfigFile, downloadFile, ensure, ensureNodeJS, ensurePNPM, executeGraphWithHistory, extractTarGz, extractZip, fetchLatestDesktopRelease, fetchLatestPackageRelease, fetchPackage, fetchPackageReleases, fetchPipelabAsset, fetchPipelabCli, fetchPipelabPlugin, findInstalledPlugins, getDefaultUserDataPath, getFinalPlugins, getFolderSize, getMigrator, handleActionExecute, isDev, isOnline, loadCustomPlugin, loadPipelabPlugin, projectRoot, registerAgentsHandlers, registerAllHandlers, registerConfigHandlers, registerEngineHandlers, registerFsHandlers, registerHistoryHandlers, registerMigrationHandlers, registerShellHandlers, runPipelineCommand, runPnpm, runWithLiveLogs, sendStartupProgress, sendStartupReady, serveCommand, setupConfigFile, useAPI, usePluginAPI, webSocketServer, zipFolder };
53795
+ export { BuildHistoryStorage, CacheFolder, PipelabContext, WebSocketServer, builtInPlugins, deletePipelineConfigFileByName, deletePipelineConfigFileByPath, downloadFile, ensure, ensureNodeJS, ensurePNPM, executeGraphWithHistory, extractTarGz, extractZip, fetchLatestDesktopRelease, fetchLatestPackageRelease, fetchPackage, fetchPackageReleases, fetchPipelabAsset, fetchPipelabCli, fetchPipelabPlugin, findInstalledPlugins, getDefaultUserDataPath, getFinalPlugins, getFolderSize, handleActionExecute, isDev, isOnline, loadCustomPlugin, loadPipelabPlugin, projectRoot, registerAgentsHandlers, registerAllHandlers, registerConfigHandlers, registerEngineHandlers, registerFsHandlers, registerHistoryHandlers, registerShellHandlers, runPipelineCommand, runPnpm, runWithLiveLogs, sendStartupProgress, sendStartupReady, serveCommand, setupConnectionsConfigFile, setupPipelineConfigFileByName, setupPipelineConfigFileByPath, setupProjectsConfigFile, setupSettingsConfigFile, useAPI, usePluginAPI, webSocketServer, zipFolder };
53315
53796
 
53316
53797
  //# sourceMappingURL=index.mjs.map