@pipelab/core-node 1.0.0-beta.13 → 1.0.0-beta.15

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,20 +1,21 @@
1
1
  import { createRequire } from "node:module";
2
2
  import path, { delimiter, dirname, isAbsolute, join, resolve } from "node:path";
3
- import { homedir, platform, tmpdir } from "node:os";
3
+ import { homedir, platform } from "node:os";
4
4
  import { fileURLToPath, pathToFileURL } from "node:url";
5
5
  import fs, { createWriteStream, existsSync, readFileSync, readdirSync, statSync } from "node:fs";
6
+ import fs$1, { access, chmod, cp, mkdir, mkdtemp, readFile, readdir, realpath, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
7
+ import { DEFAULT_NODE_VERSION, DEFAULT_PNPM_VERSION, SandboxFolder, getUiDevServerMissingWarning, uiDevPort, websocketPort } from "@pipelab/constants";
6
8
  import { WebSocket, WebSocketServer as WebSocketServer$1 } from "ws";
7
9
  import http from "node:http";
8
10
  import { nanoid } from "nanoid";
9
- import { SubscriptionRequiredError, WebSocketError, appSettingsMigrator, configRegistry, fileRepoMigrations, isRequired, isSupabaseAvailable, isWebSocketRequestMessage, normalizePipelineConfig, processGraph, savedFileMigrator, supabase, transformUrl, useLogger, usePlugins } from "@pipelab/shared";
10
- import { getUiDevServerMissingWarning, uiDevPort, websocketPort } from "@pipelab/constants";
11
- import fs$1, { access, chmod, cp, mkdir, mkdtemp, readFile, readdir, realpath, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
11
+ import { SubscriptionRequiredError, WebSocketError, configRegistry, isRequired, isSupabaseAvailable, isWebSocketRequestMessage, normalizePipelineConfig, processGraph, savedFileMigrator, supabase, transformUrl, useLogger, usePlugins } from "@pipelab/shared";
12
12
  import { execa } from "execa";
13
13
  import tar from "tar";
14
14
  import yauzl from "yauzl";
15
15
  import archiver from "archiver";
16
16
  import { pipeline } from "node:stream/promises";
17
17
  import checkDiskSpace from "check-disk-space";
18
+ import dns from "node:dns/promises";
18
19
  import pacote from "pacote";
19
20
  import semver from "semver";
20
21
  import http$1 from "http";
@@ -53,7 +54,8 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
53
54
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
54
55
  //#endregion
55
56
  //#region src/context.ts
56
- const _dirname = typeof __dirname !== "undefined" ? __dirname : typeof import.meta !== "undefined" && import.meta.url ? dirname(fileURLToPath(import.meta.url)) : process.cwd();
57
+ const metaUrl = typeof import.meta !== "undefined" ? import.meta.url : void 0;
58
+ const _dirname = typeof __dirname !== "undefined" ? __dirname : metaUrl ? dirname(fileURLToPath(metaUrl)) : process.cwd();
57
59
  const isDev = process.env.NODE_ENV === "development";
58
60
  const getDefaultUserDataPath = (env) => {
59
61
  const base = (() => {
@@ -94,6 +96,59 @@ var PipelabContext = class {
94
96
  getConfigPath(...subpaths) {
95
97
  return join(this.userDataPath, "config", ...subpaths);
96
98
  }
99
+ getTempPath(...subpaths) {
100
+ return join(this.userDataPath, "temp", ...subpaths);
101
+ }
102
+ async createTempFolder(prefix = "pipelab-") {
103
+ const baseDir = this.getTempPath();
104
+ await mkdir(baseDir, { recursive: true });
105
+ return await mkdtemp(join(await realpath(baseDir), prefix));
106
+ }
107
+ getCachePath(...subpaths) {
108
+ return join(this.userDataPath, "cache", ...subpaths);
109
+ }
110
+ getPnpmPath(...subpaths) {
111
+ return join(this.userDataPath, "pnpm", ...subpaths);
112
+ }
113
+ getNodePath(version = DEFAULT_NODE_VERSION) {
114
+ const isWindows = process.platform === "win32";
115
+ return this.getThirdPartyPath("node", version, isWindows ? "node.exe" : "bin/node");
116
+ }
117
+ getPnpmBinPath(version = DEFAULT_PNPM_VERSION) {
118
+ return this.getPackagesPath("pnpm", version, "bin", "pnpm.cjs");
119
+ }
120
+ getSandboxFolders() {
121
+ const foldersRecord = {
122
+ [SandboxFolder.Packages]: {
123
+ label: "Packages",
124
+ path: this.getPackagesPath()
125
+ },
126
+ [SandboxFolder.ThirdParty]: {
127
+ label: "Third-Party Tools",
128
+ path: this.getThirdPartyPath()
129
+ },
130
+ [SandboxFolder.Config]: {
131
+ label: "Configuration",
132
+ path: this.getConfigPath()
133
+ },
134
+ [SandboxFolder.Temp]: {
135
+ label: "Temporary Files",
136
+ path: this.getTempPath()
137
+ },
138
+ [SandboxFolder.Cache]: {
139
+ label: "Cache",
140
+ path: this.getCachePath()
141
+ },
142
+ [SandboxFolder.Pnpm]: {
143
+ label: "PNPM Home",
144
+ path: this.getPnpmPath()
145
+ }
146
+ };
147
+ return Object.keys(foldersRecord).map((key) => ({
148
+ name: key,
149
+ ...foldersRecord[key]
150
+ }));
151
+ }
97
152
  };
98
153
  //#endregion
99
154
  //#region src/ipc-core.ts
@@ -460,14 +515,6 @@ const ensure = async (filesPath, defaultContent = "{}") => {
460
515
  }
461
516
  };
462
517
  /**
463
- * Generates a unique temporary folder.
464
- */
465
- const generateTempFolder = async (base) => {
466
- const targetBase = base || tmpdir();
467
- await mkdir(targetBase, { recursive: true });
468
- return await mkdtemp(join(await realpath(targetBase), "pipelab-"));
469
- };
470
- /**
471
518
  * Extracts a .tar.gz archive.
472
519
  */
473
520
  async function extractTarGz(archivePath, destinationDir) {
@@ -600,8 +647,7 @@ async function getFolderSize(dirPath) {
600
647
  //#endregion
601
648
  //#region src/config.ts
602
649
  const getMigrator = (name) => {
603
- if (configRegistry[name]) return configRegistry[name];
604
- if (name.startsWith("pipeline-") || path.isAbsolute(name) || name.endsWith(".json")) return configRegistry["pipeline"];
650
+ return configRegistry[name] || configRegistry["pipeline"];
605
651
  };
606
652
  const setupConfigFile = async (name, options) => {
607
653
  const ctx = options.context;
@@ -633,8 +679,9 @@ const setupConfigFile = async (name, options) => {
633
679
  parseFailed = true;
634
680
  }
635
681
  let json = void 0;
682
+ let migrationFailed = false;
636
683
  try {
637
- json = await migrator.migrate(originalJson, {
684
+ if (!parseFailed) json = await migrator.migrate(originalJson, {
638
685
  debug: false,
639
686
  onStep: async (state, version) => {
640
687
  const parsedPath = path.parse(filesPath);
@@ -647,16 +694,30 @@ const setupConfigFile = async (name, options) => {
647
694
  }
648
695
  }
649
696
  });
697
+ else json = migrator.defaultValue;
650
698
  } catch (e) {
651
699
  logger().error(`Error migrating config ${name}:`, e);
700
+ migrationFailed = true;
652
701
  json = migrator.defaultValue;
653
702
  }
654
703
  let normalized = false;
655
704
  if (name.startsWith("pipeline-") || path.isAbsolute(name) || name.endsWith(".json") || name === "pipeline") normalized = normalizePipelineConfig(json);
656
- if (originalJson?.version !== json?.version || normalized || content === void 0 || parseFailed) try {
657
- await fs$1.writeFile(filesPath, JSON.stringify(json));
658
- } catch (e) {
659
- logger().error(`Error saving migrated config ${name}:`, e);
705
+ if (originalJson?.version !== json?.version || normalized || content === void 0 || parseFailed || migrationFailed) {
706
+ if (parseFailed || migrationFailed) try {
707
+ const parsedPath = path.parse(filesPath);
708
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
709
+ const corruptedPath = path.join(parsedPath.dir, `${parsedPath.name}.corrupted.${timestamp}.json`);
710
+ const backupContent = parseFailed ? content || "" : JSON.stringify(originalJson, null, 2);
711
+ await fs$1.writeFile(corruptedPath, backupContent);
712
+ logger().info(`Corrupted config file preserved at ${corruptedPath}`);
713
+ } catch (e) {
714
+ logger().error(`Failed to backup corrupted config ${name}:`, e);
715
+ }
716
+ try {
717
+ await fs$1.writeFile(filesPath, JSON.stringify(json));
718
+ } catch (e) {
719
+ logger().error(`Error saving migrated config ${name}:`, e);
720
+ }
660
721
  }
661
722
  return json;
662
723
  }
@@ -999,6 +1060,15 @@ var BuildHistoryStorage = class {
999
1060
  const files = await this.getAllPipelineFiles();
1000
1061
  const diskSpace = await checkDiskSpace(this.context.userDataPath);
1001
1062
  const pipelabSize = await getFolderSize(this.context.userDataPath);
1063
+ const folders = [];
1064
+ for (const folder of this.context.getSandboxFolders()) {
1065
+ const size = await getFolderSize(folder.path);
1066
+ folders.push({
1067
+ name: folder.name,
1068
+ label: folder.label,
1069
+ size
1070
+ });
1071
+ }
1002
1072
  const policy = (await (await setupConfigFile("settings", { context: this.context })).getConfig())?.buildHistory?.retentionPolicy || {
1003
1073
  enabled: false,
1004
1074
  maxEntries: 50,
@@ -1017,7 +1087,8 @@ var BuildHistoryStorage = class {
1017
1087
  disk: {
1018
1088
  total: diskSpace.size,
1019
1089
  free: diskSpace.free,
1020
- pipelab: pipelabSize
1090
+ pipelab: pipelabSize,
1091
+ folders
1021
1092
  }
1022
1093
  };
1023
1094
  let totalSize = 0;
@@ -1045,7 +1116,8 @@ var BuildHistoryStorage = class {
1045
1116
  disk: {
1046
1117
  total: diskSpace.size,
1047
1118
  free: diskSpace.free,
1048
- pipelab: pipelabSize
1119
+ pipelab: pipelabSize,
1120
+ folders
1049
1121
  }
1050
1122
  };
1051
1123
  } catch (error) {
@@ -1357,7 +1429,7 @@ const registerHistoryHandlers = (context) => {
1357
1429
  }
1358
1430
  }
1359
1431
  };
1360
- await settings.saveConfig(newConfig);
1432
+ await settings.setConfig(newConfig);
1361
1433
  send({
1362
1434
  type: "end",
1363
1435
  data: {
@@ -8831,10 +8903,7 @@ function registerMigrationHandlers(context) {
8831
8903
  const { config: name } = value;
8832
8904
  logger().info("[CLI Migration] config:load", name);
8833
8905
  try {
8834
- let migrator = null;
8835
- if (name === "projects") migrator = fileRepoMigrations;
8836
- else if (name === "settings") migrator = appSettingsMigrator;
8837
- else if (name.startsWith("pipeline-") || name.endsWith(".plb")) migrator = savedFileMigrator;
8906
+ const migrator = getMigrator(name);
8838
8907
  if (!migrator) throw new Error(`No migrator found for configuration: ${name}. All files loaded via config:load MUST have a migration schema.`);
8839
8908
  send({
8840
8909
  type: "end",
@@ -8946,8 +9015,6 @@ async function serveCommand(options, version, _dirname) {
8946
9015
  }
8947
9016
  //#endregion
8948
9017
  //#region src/utils/remote.ts
8949
- const DEFAULT_NODE_VERSION = "24.14.1";
8950
- const DEFAULT_PNPM_VERSION = "10.12.0";
8951
9018
  function isPackageComplete(packageDir) {
8952
9019
  return existsSync(join(packageDir, "package.json"));
8953
9020
  }
@@ -8988,6 +9055,20 @@ async function withLock(key, fn) {
8988
9055
  activeOperations.set(key, promise);
8989
9056
  return promise;
8990
9057
  }
9058
+ let isOnlineCached = null;
9059
+ let lastCheckTime = 0;
9060
+ async function isOnline() {
9061
+ const now = Date.now();
9062
+ if (isOnlineCached !== null && now - lastCheckTime < 1e4) return isOnlineCached;
9063
+ try {
9064
+ await Promise.race([dns.lookup("registry.npmjs.org"), new Promise((_, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error("timeout")), 1500))]);
9065
+ isOnlineCached = true;
9066
+ } catch {
9067
+ isOnlineCached = false;
9068
+ }
9069
+ lastCheckTime = now;
9070
+ return isOnlineCached;
9071
+ }
8991
9072
  /**
8992
9073
  * Robust utility to fetch, cache, and resolve an NPM package.
8993
9074
  * Centralized in core-node to avoid circular dependencies.
@@ -9014,7 +9095,15 @@ async function fetchPackage(packageName, versionOrRange, options) {
9014
9095
  if (resolvedVersionOrRange === "local") resolvedVersionOrRange = "latest";
9015
9096
  console.log(`[Fetcher] Resolving ${packageName}@${resolvedVersionOrRange || "latest"}...`);
9016
9097
  const resolveStart = Date.now();
9017
- try {
9098
+ if (!await isOnline()) {
9099
+ console.warn(`[Fetcher] ${packageName}: offline mode detected (${Date.now() - resolveStart}ms), trying local fallback...`);
9100
+ const fallbackStart = Date.now();
9101
+ const fallbackVersion = await tryLocalFallback(resolvedVersionOrRange, /* @__PURE__ */ new Error("Offline"), baseDir, packageName);
9102
+ if (fallbackVersion) {
9103
+ resolvedVersion = fallbackVersion;
9104
+ console.log(`[Fetcher] ${packageName}: Resolved to local fallback ${resolvedVersion} (${Date.now() - fallbackStart}ms)`);
9105
+ } else throw new Error(`Offline and no local fallback version available for ${packageName}`);
9106
+ } else try {
9018
9107
  const cachePath = join(ctx.userDataPath, "cache", "pacote");
9019
9108
  let packumentPromise = packumentRequests.get(packageName);
9020
9109
  if (!packumentPromise) {
@@ -9024,7 +9113,16 @@ async function fetchPackage(packageName, versionOrRange, options) {
9024
9113
  const packument = await packumentPromise;
9025
9114
  const versions = Object.keys(packument.versions);
9026
9115
  const range = resolvedVersionOrRange || "latest";
9027
- const foundVersion = packument["dist-tags"]?.[range] || semver.maxSatisfying(versions, range);
9116
+ let foundVersion = packument["dist-tags"]?.[range] || semver.maxSatisfying(versions, range);
9117
+ if (range === "latest" && ctx.releaseTag && ctx.releaseTag !== "latest") {
9118
+ const releaseTagVersion = packument["dist-tags"]?.[ctx.releaseTag];
9119
+ if (releaseTagVersion && semver.valid(releaseTagVersion)) {
9120
+ if (!foundVersion || semver.valid(foundVersion) && semver.gte(releaseTagVersion, foundVersion)) {
9121
+ console.log(`[Fetcher] Using release tag "${ctx.releaseTag}" (${releaseTagVersion}) instead of "latest" (${foundVersion || "none"}) for ${packageName}`);
9122
+ foundVersion = releaseTagVersion;
9123
+ } else if (foundVersion) console.warn(`[Fetcher] Tag "${ctx.releaseTag}" (${releaseTagVersion}) is older than "latest" (${foundVersion}) for ${packageName}, keeping "latest"`);
9124
+ }
9125
+ }
9028
9126
  if (!foundVersion) throw new Error(`Package ${packageName}@${range} not found on npm (available tags: ${Object.keys(packument["dist-tags"] || {}).join(", ")})`);
9029
9127
  resolvedVersion = foundVersion;
9030
9128
  console.log(`[Fetcher] ${packageName}: Resolved to v${resolvedVersion} via npm (${Date.now() - resolveStart}ms)`);
@@ -9139,7 +9237,7 @@ async function ensureNodeJS(context, version = DEFAULT_NODE_VERSION) {
9139
9237
  const extension = isWindows ? "zip" : "tar.gz";
9140
9238
  const fileName = `node-v${version}-${platform === "osx" ? "darwin" : platform}-${arch}.${extension}`;
9141
9239
  const downloadUrl = `https://nodejs.org/dist/v${version}/${fileName}`;
9142
- const tempDir = await generateTempFolder(tmpdir());
9240
+ const tempDir = await context.createTempFolder("node-download-");
9143
9241
  const archivePath = join(tempDir, fileName);
9144
9242
  sendStartupProgress(`Downloading Node.js v${version}...`);
9145
9243
  console.log(`Downloading Node.js from ${downloadUrl}...`);
@@ -9671,7 +9769,6 @@ const builtInPlugins = async (options) => {
9671
9769
  webSocketServer.broadcast("startup:progress", { type: "ready" });
9672
9770
  }, 2e3);
9673
9771
  })();
9674
- return [];
9675
9772
  };
9676
9773
  //#endregion
9677
9774
  //#region src/utils.ts
@@ -9727,7 +9824,7 @@ const executeGraphWithHistory = async ({ graph, variables, projectName, projectP
9727
9824
  const shouldDisableHistory = process.env.PIPELAB_DISABLE_HISTORY === "true";
9728
9825
  if (!shouldDisableHistory) await buildHistoryStorage.save(initialEntry);
9729
9826
  const logs = [];
9730
- const sandboxPath = await generateTempFolder(cachePath);
9827
+ const sandboxPath = await ctx.createTempFolder("pipeline-sandbox-");
9731
9828
  const { logger } = useLogger();
9732
9829
  let completedSteps = 0;
9733
9830
  const { registerPlugins, plugins: registeredPlugins } = usePlugins();
@@ -10193,8 +10290,9 @@ const registerEngineHandlers = (context) => {
10193
10290
  };
10194
10291
  handle("action:execute", async (event, { send, value }) => {
10195
10292
  const { nodeId, params, pluginId } = value;
10196
- const cachePath = (await (await setupConfigFile("settings", { context })).getConfig())?.cacheFolder || tmpdir();
10197
- const cwd = await generateTempFolder(cachePath);
10293
+ await (await setupConfigFile("settings", { context })).getConfig();
10294
+ const cachePath = join(context.userDataPath, "cache", "actions", pluginId, nodeId);
10295
+ const cwd = await context.createTempFolder("action-execute-");
10198
10296
  const mainWindow = void 0;
10199
10297
  abortControllerGraph = new AbortController();
10200
10298
  const signalPromise = new Promise((resolve, reject) => {
@@ -10233,11 +10331,11 @@ const registerEngineHandlers = (context) => {
10233
10331
  });
10234
10332
  handle("graph:execute", async (event, { send, value }) => {
10235
10333
  const { graph, variables, projectName, projectPath, pipelineId } = value;
10236
- const config = await (await setupConfigFile("settings", { context })).getConfig();
10334
+ await (await setupConfigFile("settings", { context })).getConfig();
10237
10335
  const effectiveProjectName = projectName || "Unnamed Project";
10238
10336
  const effectiveProjectPath = projectPath || "";
10239
10337
  const effectivePipelineId = pipelineId || "unknown";
10240
- const effectiveCachePath = config?.cacheFolder || tmpdir();
10338
+ const effectiveCachePath = join(context.userDataPath, "cache", effectivePipelineId);
10241
10339
  const mainWindow = void 0;
10242
10340
  abortControllerGraph = new AbortController();
10243
10341
  try {
@@ -10741,14 +10839,11 @@ const registerPluginsHandlers = (context) => {
10741
10839
  }
10742
10840
  });
10743
10841
  handle("plugin:ensure-loaded", async (_, { send, value }) => {
10744
- const { pluginIds, plugins } = value;
10842
+ const { plugins } = value;
10745
10843
  const { plugins: registeredPlugins, registerPlugins } = usePlugins();
10746
10844
  const loaded = [];
10747
10845
  const failed = [];
10748
10846
  const pluginsToEnsure = /* @__PURE__ */ new Set();
10749
- if (Array.isArray(pluginIds)) {
10750
- for (const id of pluginIds) if (id) pluginsToEnsure.add(id);
10751
- }
10752
10847
  if (plugins && typeof plugins === "object") {
10753
10848
  for (const id of Object.keys(plugins)) if (id) pluginsToEnsure.add(id);
10754
10849
  }
@@ -53238,6 +53333,6 @@ async function fetchLatestDesktopRelease(options = {}) {
53238
53333
  return latest;
53239
53334
  }
53240
53335
  //#endregion
53241
- export { BuildHistoryStorage, DEFAULT_NODE_VERSION, DEFAULT_PNPM_VERSION, PipelabContext, WebSocketServer, builtInPlugins, downloadFile, ensure, ensureNodeJS, ensurePNPM, executeGraphWithHistory, extractTarGz, extractZip, fetchLatestDesktopRelease, fetchLatestPackageRelease, fetchPackage, fetchPackageReleases, fetchPipelabAsset, fetchPipelabCli, fetchPipelabPlugin, findInstalledPlugins, generateTempFolder, getDefaultUserDataPath, getFinalPlugins, getFolderSize, getMigrator, handleActionExecute, isDev, loadCustomPlugin, loadPipelabPlugin, projectRoot, registerAgentsHandlers, registerAllHandlers, registerConfigHandlers, registerEngineHandlers, registerFsHandlers, registerHistoryHandlers, registerMigrationHandlers, registerShellHandlers, runPipelineCommand, runPnpm, runWithLiveLogs, sendStartupProgress, sendStartupReady, serveCommand, setupConfigFile, useAPI, usePluginAPI, webSocketServer, zipFolder };
53336
+ export { BuildHistoryStorage, PipelabContext, WebSocketServer, builtInPlugins, 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 };
53242
53337
 
53243
53338
  //# sourceMappingURL=index.mjs.map