@pipelab/core-node 1.0.0-beta.10 → 1.0.0-beta.12

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
@@ -2,11 +2,11 @@ import { createRequire } from "node:module";
2
2
  import path, { delimiter, dirname, isAbsolute, join, resolve } from "node:path";
3
3
  import { homedir, platform, tmpdir } from "node:os";
4
4
  import { fileURLToPath, pathToFileURL } from "node:url";
5
- import fs, { createWriteStream, existsSync, readdirSync, statSync } from "node:fs";
5
+ import fs, { createWriteStream, existsSync, readFileSync, readdirSync, statSync } from "node:fs";
6
6
  import { WebSocket, WebSocketServer as WebSocketServer$1 } from "ws";
7
7
  import http from "node:http";
8
8
  import { nanoid } from "nanoid";
9
- import { SubscriptionRequiredError, WebSocketError, appSettingsMigrator, configRegistry, fileRepoMigrations, isRequired, isSupabaseAvailable, isWebSocketRequestMessage, processGraph, savedFileMigrator, supabase, useLogger, usePlugins } from "@pipelab/shared";
9
+ import { SubscriptionRequiredError, WebSocketError, appSettingsMigrator, configRegistry, fileRepoMigrations, isRequired, isSupabaseAvailable, isWebSocketRequestMessage, normalizePipelineConfig, processGraph, savedFileMigrator, supabase, transformUrl, useLogger, usePlugins } from "@pipelab/shared";
10
10
  import { getUiDevServerMissingWarning, uiDevPort, websocketPort } from "@pipelab/constants";
11
11
  import fs$1, { access, chmod, cp, mkdir, mkdtemp, readFile, readdir, realpath, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
12
12
  import { execa } from "execa";
@@ -454,7 +454,7 @@ const registerFsHandlers = (_context) => {
454
454
  const ensure = async (filesPath, defaultContent = "{}") => {
455
455
  await mkdir(dirname(filesPath), { recursive: true });
456
456
  try {
457
- await access(filesPath);
457
+ if ((await stat(filesPath)).size === 0) await writeFile(filesPath, defaultContent);
458
458
  } catch {
459
459
  await writeFile(filesPath, defaultContent);
460
460
  }
@@ -601,7 +601,7 @@ async function getFolderSize(dirPath) {
601
601
  //#region src/config.ts
602
602
  const getMigrator = (name) => {
603
603
  if (configRegistry[name]) return configRegistry[name];
604
- if (name.startsWith("pipeline-")) return configRegistry["pipeline"];
604
+ if (name.startsWith("pipeline-") || path.isAbsolute(name) || name.endsWith(".json")) return configRegistry["pipeline"];
605
605
  };
606
606
  const setupConfigFile = async (name, options) => {
607
607
  const ctx = options.context;
@@ -624,11 +624,13 @@ const setupConfigFile = async (name, options) => {
624
624
  const { logger } = useLogger();
625
625
  let content = void 0;
626
626
  let originalJson = void 0;
627
+ let parseFailed = false;
627
628
  try {
628
629
  content = await fs$1.readFile(filesPath, "utf8");
629
630
  if (content !== void 0) originalJson = JSON.parse(content);
630
631
  } catch (e) {
631
632
  logger().error(`Error reading or parsing config ${name}:`, e);
633
+ parseFailed = true;
632
634
  }
633
635
  let json = void 0;
634
636
  try {
@@ -649,7 +651,9 @@ const setupConfigFile = async (name, options) => {
649
651
  logger().error(`Error migrating config ${name}:`, e);
650
652
  json = migrator.defaultValue;
651
653
  }
652
- if (originalJson?.version !== json?.version || content === void 0) try {
654
+ let normalized = false;
655
+ 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 {
653
657
  await fs$1.writeFile(filesPath, JSON.stringify(json));
654
658
  } catch (e) {
655
659
  logger().error(`Error saving migrated config ${name}:`, e);
@@ -740,6 +744,40 @@ const registerConfigHandlers = (context) => {
740
744
  });
741
745
  }
742
746
  });
747
+ handle("config:delete", async (_, { send, value }) => {
748
+ const { config: name } = value;
749
+ logger().info("config:delete", name);
750
+ try {
751
+ const filesPath = path.isAbsolute(name) ? name : context.getConfigPath(`${name}.json`);
752
+ await fs$1.rm(filesPath, { force: true });
753
+ const parsedPath = path.parse(filesPath);
754
+ const dirEntries = await fs$1.readdir(parsedPath.dir).catch(() => []);
755
+ const prefix = `${parsedPath.name}.v`;
756
+ const suffix = `.json`;
757
+ for (const entry of dirEntries) if (entry.startsWith(prefix) && entry.endsWith(suffix)) {
758
+ const backupPath = path.join(parsedPath.dir, entry);
759
+ await fs$1.rm(backupPath, { force: true }).catch((err) => {
760
+ logger().error(`Failed to delete backup ${backupPath}:`, err);
761
+ });
762
+ }
763
+ send({
764
+ type: "end",
765
+ data: {
766
+ type: "success",
767
+ result: { result: "ok" }
768
+ }
769
+ });
770
+ } catch (e) {
771
+ logger().error(`config:delete error for ${name}:`, e);
772
+ send({
773
+ type: "end",
774
+ data: {
775
+ type: "error",
776
+ ipcError: e instanceof Error ? e.message : `Unable to delete config ${name}`
777
+ }
778
+ });
779
+ }
780
+ });
743
781
  };
744
782
  //#endregion
745
783
  //#region src/handlers/build-history.ts
@@ -8843,6 +8881,35 @@ async function serveCommand(options, version, _dirname) {
8843
8881
  let rawAssetFolder;
8844
8882
  if (!isDev) rawAssetFolder = await fetchPipelabAsset("@pipelab/ui", releaseTag, { context });
8845
8883
  const server = http$1.createServer(async (request, response) => {
8884
+ if (request.url?.startsWith("/media-file/")) {
8885
+ const encodedPath = request.url.substring(12);
8886
+ const filePath = decodeURIComponent(encodedPath);
8887
+ const normalizedPath = filePath.startsWith("/") && filePath.match(/^\/[a-zA-Z]:/) ? filePath.substring(1) : filePath;
8888
+ if (existsSync(normalizedPath)) try {
8889
+ const content = await readFile(normalizedPath);
8890
+ let contentType = "application/octet-stream";
8891
+ if (normalizedPath.endsWith(".png")) contentType = "image/png";
8892
+ else if (normalizedPath.endsWith(".jpg") || normalizedPath.endsWith(".jpeg")) contentType = "image/jpeg";
8893
+ else if (normalizedPath.endsWith(".svg")) contentType = "image/svg+xml";
8894
+ else if (normalizedPath.endsWith(".gif")) contentType = "image/gif";
8895
+ else if (normalizedPath.endsWith(".webp")) contentType = "image/webp";
8896
+ response.writeHead(200, {
8897
+ "Content-Type": contentType,
8898
+ "Access-Control-Allow-Origin": "*"
8899
+ });
8900
+ response.end(content);
8901
+ return;
8902
+ } catch (e) {
8903
+ response.writeHead(500, { "Content-Type": "text/plain" });
8904
+ response.end(`Error reading file: ${e}`);
8905
+ return;
8906
+ }
8907
+ else {
8908
+ response.writeHead(404, { "Content-Type": "text/plain" });
8909
+ response.end(`File not found: ${normalizedPath}`);
8910
+ return;
8911
+ }
8912
+ }
8846
8913
  if (isDev) {
8847
8914
  response.writeHead(200, { "Content-Type": "text/html" });
8848
8915
  response.end(`
@@ -8926,11 +8993,13 @@ async function withLock(key, fn) {
8926
8993
  * Centralized in core-node to avoid circular dependencies.
8927
8994
  */
8928
8995
  async function fetchPackage(packageName, versionOrRange, options) {
8996
+ const start = Date.now();
8929
8997
  if (isDev && projectRoot && process.env.PIPELAB_FORCE_NPM !== "true") {
8930
8998
  if (packageName.startsWith("@pipelab/")) {
8999
+ const localStart = Date.now();
8931
9000
  const local = await tryResolveMonorepoPackage(packageName);
8932
9001
  if (local) {
8933
- console.log(`[Fetcher] ${packageName}: Resolved to local source at ${local.packageDir}`);
9002
+ console.log(`[Fetcher] ${packageName}: Resolved to local source at ${local.packageDir} (${Date.now() - localStart}ms)`);
8934
9003
  return {
8935
9004
  ...local,
8936
9005
  resolvedVersion: "workspace"
@@ -8941,7 +9010,10 @@ async function fetchPackage(packageName, versionOrRange, options) {
8941
9010
  const ctx = options.context;
8942
9011
  const baseDir = ctx.getPackagesPath(packageName);
8943
9012
  let resolvedVersion;
8944
- console.log(`[Fetcher] Resolving ${packageName}@${versionOrRange || "latest"}...`);
9013
+ let resolvedVersionOrRange = versionOrRange;
9014
+ if (resolvedVersionOrRange === "local") resolvedVersionOrRange = "latest";
9015
+ console.log(`[Fetcher] Resolving ${packageName}@${resolvedVersionOrRange || "latest"}...`);
9016
+ const resolveStart = Date.now();
8945
9017
  try {
8946
9018
  const cachePath = join(ctx.userDataPath, "cache", "pacote");
8947
9019
  let packumentPromise = packumentRequests.get(packageName);
@@ -8951,26 +9023,36 @@ async function fetchPackage(packageName, versionOrRange, options) {
8951
9023
  }
8952
9024
  const packument = await packumentPromise;
8953
9025
  const versions = Object.keys(packument.versions);
8954
- const range = versionOrRange || "latest";
9026
+ const range = resolvedVersionOrRange || "latest";
8955
9027
  const foundVersion = packument["dist-tags"]?.[range] || semver.maxSatisfying(versions, range);
8956
9028
  if (!foundVersion) throw new Error(`Package ${packageName}@${range} not found on npm (available tags: ${Object.keys(packument["dist-tags"] || {}).join(", ")})`);
8957
9029
  resolvedVersion = foundVersion;
8958
- console.log(`[Fetcher] ${packageName}: Resolved to v${resolvedVersion} via npm`);
9030
+ console.log(`[Fetcher] ${packageName}: Resolved to v${resolvedVersion} via npm (${Date.now() - resolveStart}ms)`);
8959
9031
  } catch (error) {
8960
- console.warn(`[Fetcher] ${packageName}: remote resolution failed, trying local fallback...`);
8961
- const fallbackVersion = await tryLocalFallback(versionOrRange, error, baseDir, packageName);
8962
- if (fallbackVersion) resolvedVersion = fallbackVersion;
8963
- else throw error;
9032
+ console.warn(`[Fetcher] ${packageName}: remote resolution failed (${Date.now() - resolveStart}ms), trying local fallback...`);
9033
+ const fallbackStart = Date.now();
9034
+ const fallbackVersion = await tryLocalFallback(resolvedVersionOrRange, error, baseDir, packageName);
9035
+ if (fallbackVersion) {
9036
+ resolvedVersion = fallbackVersion;
9037
+ console.log(`[Fetcher] ${packageName}: Resolved to local fallback ${resolvedVersion} (${Date.now() - fallbackStart}ms)`);
9038
+ } else throw error;
8964
9039
  }
8965
9040
  const cachePath = join(ctx.userDataPath, "cache", "pacote");
8966
9041
  const packageDir = join(baseDir, resolvedVersion);
8967
- if (options?.installDeps ? isPackageComplete(packageDir) && isDependenciesInstalledSync(packageDir) : isPackageComplete(packageDir)) return {
8968
- packageDir,
8969
- resolvedVersion
8970
- };
9042
+ const checkStart = Date.now();
9043
+ const isInstalled = options?.installDeps ? isPackageComplete(packageDir) && isDependenciesInstalledSync(packageDir) : isPackageComplete(packageDir);
9044
+ const checkDuration = Date.now() - checkStart;
9045
+ if (isInstalled) {
9046
+ console.log(`[Fetcher] ${packageName}@${resolvedVersion}: Already installed (check took ${checkDuration}ms, fetchPackage took ${Date.now() - start}ms)`);
9047
+ return {
9048
+ packageDir,
9049
+ resolvedVersion
9050
+ };
9051
+ }
8971
9052
  return withLock(`package:${packageName}:${resolvedVersion}`, async () => {
8972
9053
  if (!isPackageComplete(packageDir)) {
8973
9054
  console.log(`[Fetcher] ${packageName}@${resolvedVersion}: Downloading to ${packageDir}...`);
9055
+ const downloadStart = Date.now();
8974
9056
  const tempDir = join(baseDir, `.tmp-${resolvedVersion}-${Math.random().toString(36).slice(2)}`);
8975
9057
  await mkdir(tempDir, { recursive: true });
8976
9058
  try {
@@ -8986,6 +9068,7 @@ async function fetchPackage(packageName, versionOrRange, options) {
8986
9068
  if (isPackageComplete(packageDir)) console.log(`[Fetcher] Destination ${packageDir} already exists and is valid.`);
8987
9069
  else throw err;
8988
9070
  }
9071
+ console.log(`[Fetcher] ${packageName}@${resolvedVersion}: Downloaded and extracted in ${Date.now() - downloadStart}ms`);
8989
9072
  } catch (err) {
8990
9073
  await rm(tempDir, {
8991
9074
  recursive: true,
@@ -8994,8 +9077,15 @@ async function fetchPackage(packageName, versionOrRange, options) {
8994
9077
  throw err;
8995
9078
  }
8996
9079
  }
9080
+ const entryStart = Date.now();
8997
9081
  const entryPoint = await resolveEntryPoint(packageDir, packageName);
8998
- if (options?.installDeps) await installDependencies(packageDir, packageName, options);
9082
+ console.log(`[Fetcher] ${packageName}@${resolvedVersion}: Resolved entry point in ${Date.now() - entryStart}ms`);
9083
+ if (options?.installDeps) {
9084
+ const depsStart = Date.now();
9085
+ await installDependencies(packageDir, packageName, options);
9086
+ console.log(`[Fetcher] ${packageName}@${resolvedVersion}: Installed dependencies in ${Date.now() - depsStart}ms`);
9087
+ }
9088
+ console.log(`[Fetcher] ${packageName}@${resolvedVersion}: FetchPackage complete in ${Date.now() - start}ms`);
8999
9089
  return {
9000
9090
  packageDir,
9001
9091
  resolvedVersion,
@@ -9034,10 +9124,14 @@ async function runPnpm(cwd, options) {
9034
9124
  * Installs a specific version of Node.js if not already present.
9035
9125
  */
9036
9126
  async function ensureNodeJS(context, version = DEFAULT_NODE_VERSION) {
9127
+ const checkStart = Date.now();
9037
9128
  const isWindows = process.platform === "win32";
9038
9129
  const nodeDir = context.getThirdPartyPath("node", version);
9039
9130
  const finalNodePath = join(nodeDir, isWindows ? "node.exe" : "bin/node");
9040
- if (isNodeJSComplete(finalNodePath)) return finalNodePath;
9131
+ if (isNodeJSComplete(finalNodePath)) {
9132
+ console.log(`[Environment] Node.js check took ${Date.now() - checkStart}ms (found at ${finalNodePath})`);
9133
+ return finalNodePath;
9134
+ }
9041
9135
  return withLock(`node:${version}`, async () => {
9042
9136
  if (isNodeJSComplete(finalNodePath)) return finalNodePath;
9043
9137
  const arch = process.arch === "x64" ? "x64" : process.arch === "arm64" ? "arm64" : "x86";
@@ -9049,9 +9143,12 @@ async function ensureNodeJS(context, version = DEFAULT_NODE_VERSION) {
9049
9143
  const archivePath = join(tempDir, fileName);
9050
9144
  sendStartupProgress(`Downloading Node.js v${version}...`);
9051
9145
  console.log(`Downloading Node.js from ${downloadUrl}...`);
9146
+ const dlStart = Date.now();
9052
9147
  await downloadFile(downloadUrl, archivePath);
9148
+ console.log(`[Environment] Node.js download took ${Date.now() - dlStart}ms`);
9053
9149
  sendStartupProgress(`Extracting Node.js v${version}...`);
9054
9150
  console.log(`Extracting Node.js to ${tempDir}...`);
9151
+ const extStart = Date.now();
9055
9152
  const extractTempDir = join(tempDir, "extracted");
9056
9153
  await mkdir(extractTempDir, { recursive: true });
9057
9154
  if (extension === "zip") await extractZip(archivePath, extractTempDir);
@@ -9076,6 +9173,7 @@ async function ensureNodeJS(context, version = DEFAULT_NODE_VERSION) {
9076
9173
  if (isNodeJSComplete(finalNodePath)) console.log(`[Fetcher] Node.js directory already exists and is valid.`);
9077
9174
  else throw err;
9078
9175
  }
9176
+ console.log(`[Environment] Node.js extraction took ${Date.now() - extStart}ms`);
9079
9177
  } finally {
9080
9178
  await rm(tempNodeDir, {
9081
9179
  recursive: true,
@@ -9086,6 +9184,7 @@ async function ensureNodeJS(context, version = DEFAULT_NODE_VERSION) {
9086
9184
  force: true
9087
9185
  }).catch(() => {});
9088
9186
  }
9187
+ console.log(`[Environment] Node.js set up complete in ${Date.now() - checkStart}ms`);
9089
9188
  return finalNodePath;
9090
9189
  });
9091
9190
  }
@@ -9093,16 +9192,22 @@ async function ensureNodeJS(context, version = DEFAULT_NODE_VERSION) {
9093
9192
  * Installs the PNPM package from npm if not already present.
9094
9193
  */
9095
9194
  async function ensurePNPM(context, version = DEFAULT_PNPM_VERSION) {
9195
+ const checkStart = Date.now();
9096
9196
  const pnpmPath = join(context.getPackagesPath("pnpm", version), "bin", "pnpm.cjs");
9097
- if (existsSync(pnpmPath)) return pnpmPath;
9197
+ if (existsSync(pnpmPath)) {
9198
+ console.log(`[Environment] PNPM check took ${Date.now() - checkStart}ms (found at ${pnpmPath})`);
9199
+ return pnpmPath;
9200
+ }
9098
9201
  return withLock(`pnpm:${version}`, async () => {
9099
9202
  if (existsSync(pnpmPath)) return pnpmPath;
9100
9203
  sendStartupProgress(`Checking PNPM v${version}...`);
9101
9204
  const { packageDir } = await fetchPackage("pnpm", version, { context });
9205
+ console.log(`[Environment] PNPM set up complete in ${Date.now() - checkStart}ms`);
9102
9206
  return join(packageDir, "bin", "pnpm.cjs");
9103
9207
  });
9104
9208
  }
9105
9209
  async function installDependencies(packageDir, packageName, options) {
9210
+ const start = Date.now();
9106
9211
  const nodeModulesPath = join(packageDir, "node_modules");
9107
9212
  if (isDependenciesInstalledSync(packageDir)) {
9108
9213
  console.log(`[Fetcher] ${packageName}: Dependencies already installed, skipping.`);
@@ -9113,18 +9218,21 @@ async function installDependencies(packageDir, packageName, options) {
9113
9218
  try {
9114
9219
  await cp(join(packageDir, "package.json"), join(tempDir, "package.json"));
9115
9220
  console.log(`[Fetcher] ${packageName}: Ensuring dependencies are installed...`);
9221
+ const pnpmStart = Date.now();
9116
9222
  const { all } = await runPnpm(tempDir, {
9117
9223
  signal: options.signal,
9118
9224
  context: options.context
9119
9225
  });
9226
+ console.log(`[Fetcher] ${packageName}: pnpm install command took ${Date.now() - pnpmStart}ms`);
9120
9227
  if (all) console.log(`[Fetcher] ${packageName}: Installation trace:\n${all}`);
9121
9228
  const tempNodeModules = join(tempDir, "node_modules");
9122
9229
  if (existsSync(nodeModulesPath)) await rm(nodeModulesPath, {
9123
9230
  recursive: true,
9124
9231
  force: true
9125
9232
  }).catch(() => {});
9233
+ const renameStart = Date.now();
9126
9234
  await rename(tempNodeModules, nodeModulesPath);
9127
- console.log(`[Fetcher] ${packageName}: Dependencies installed successfully.`);
9235
+ console.log(`[Fetcher] ${packageName}: Dependencies installed successfully (rename took ${Date.now() - renameStart}ms, total installDependencies took ${Date.now() - start}ms).`);
9128
9236
  } catch (err) {
9129
9237
  console.error(`[Fetcher] ${packageName}: CRITICAL ERROR during dependency installation: ${err.message}`);
9130
9238
  if (err.all) console.error(`[Fetcher] ${packageName}: Error details:\n${err.all}`);
@@ -9226,22 +9334,27 @@ async function crawlMonorepoPackages() {
9226
9334
  }
9227
9335
  return cache;
9228
9336
  }
9229
- async function findLatestLocalVersion(baseDir) {
9337
+ async function tryLocalFallback(versionOrRange, _error, baseDir, logPrefix) {
9230
9338
  if (!existsSync(baseDir)) return null;
9231
9339
  try {
9232
- return (await readdir(baseDir, { withFileTypes: true })).filter((e) => e.isDirectory() || e.isSymbolicLink()).map((e) => e.name).sort((a, b) => b.localeCompare(a, void 0, {
9233
- numeric: true,
9234
- sensitivity: "base"
9235
- }))[0] || null;
9236
- } catch {
9237
- return null;
9238
- }
9239
- }
9240
- async function tryLocalFallback(_version, _error, baseDir, logPrefix) {
9241
- const latestLocal = await findLatestLocalVersion(baseDir);
9242
- if (latestLocal) {
9243
- console.info(`[Fetcher] ${logPrefix}: Using locally cached version: ${latestLocal}`);
9244
- return latestLocal;
9340
+ const localVersions = (await readdir(baseDir, { withFileTypes: true })).filter((e) => e.isDirectory() || e.isSymbolicLink()).map((e) => e.name).filter((name) => !!semver.valid(name));
9341
+ if (localVersions.length === 0) return null;
9342
+ const range = versionOrRange || "latest";
9343
+ if (range === "latest") {
9344
+ const latestLocal = localVersions.sort((a, b) => semver.rcompare(a, b))[0] || null;
9345
+ if (latestLocal) {
9346
+ console.info(`[Fetcher] ${logPrefix}: Using locally cached latest version: ${latestLocal}`);
9347
+ return latestLocal;
9348
+ }
9349
+ } else {
9350
+ const matched = semver.maxSatisfying(localVersions, range);
9351
+ if (matched) {
9352
+ console.info(`[Fetcher] ${logPrefix}: Using locally cached matching version: ${matched} for range ${range}`);
9353
+ return matched;
9354
+ }
9355
+ }
9356
+ } catch (e) {
9357
+ console.warn(`[Fetcher] ${logPrefix}: Error during local fallback resolution:`, e);
9245
9358
  }
9246
9359
  return null;
9247
9360
  }
@@ -9275,50 +9388,6 @@ const checkParams = (definitionParams, elementParams) => {
9275
9388
  } else console.warn("Unexpected param \"" + param + "\"");
9276
9389
  for (const param of expected) if (isRequired(definitionParams[param])) throw new Error("Missing param \"" + param + "\"");
9277
9390
  };
9278
- const handleConditionExecute = async (nodeId, pluginId, params, cwd, context) => {
9279
- const ctx = context;
9280
- const { plugins } = usePlugins();
9281
- const { logger } = useLogger();
9282
- const node = plugins.value.find((plugin) => plugin.id === pluginId)?.nodes.find((node) => node.node.id === nodeId);
9283
- if (!node) return {
9284
- type: "error",
9285
- ipcError: "Node not found"
9286
- };
9287
- try {
9288
- if (!(await stat(cwd)).isDirectory()) throw new Error(`Execution directory "${cwd}" exists but is not a directory.`);
9289
- } catch (e) {
9290
- if (e.code === "ENOENT") await mkdir(cwd, { recursive: true });
9291
- else throw e;
9292
- }
9293
- try {
9294
- checkParams(node.node.params, params);
9295
- const resolvedInputs = params;
9296
- return {
9297
- type: "success",
9298
- result: {
9299
- outputs: {},
9300
- value: await node.runner({
9301
- inputs: resolvedInputs,
9302
- log: (...args) => {
9303
- logger().info(`[${node.node.name}]`, ...args);
9304
- },
9305
- meta: { definition: "" },
9306
- setMeta: () => {
9307
- logger().info("set meta defined here");
9308
- },
9309
- cwd,
9310
- context: ctx
9311
- })
9312
- }
9313
- };
9314
- } catch (e) {
9315
- logger().error("Error in condition execution:", e);
9316
- return {
9317
- type: "error",
9318
- ipcError: String(e)
9319
- };
9320
- }
9321
- };
9322
9391
  const handleActionExecute = async (nodeId, pluginId, params, mainWindow, send, abortSignal, cwd, cachePath, context) => {
9323
9392
  const ctx = context;
9324
9393
  const { plugins } = usePlugins();
@@ -9404,26 +9473,55 @@ const handleActionExecute = async (nodeId, pluginId, params, mainWindow, send, a
9404
9473
  };
9405
9474
  //#endregion
9406
9475
  //#region src/plugins-registry.ts
9407
- const DEFAULT_PLUGIN_IDS = [
9408
- "construct",
9409
- "filesystem",
9410
- "system",
9411
- "steam",
9412
- "itch",
9413
- "electron",
9414
- "discord",
9415
- "poki",
9416
- "nvpatch",
9417
- "tauri",
9418
- "minify",
9419
- "netlify"
9420
- ];
9476
+ const enhancePluginDefinition = async (plugin, packageDir, fallbackName, fallbackVersion) => {
9477
+ if (!plugin) return plugin;
9478
+ let packageName = fallbackName;
9479
+ let version = fallbackVersion;
9480
+ let pipelabMeta = null;
9481
+ let pkgDescription = "";
9482
+ try {
9483
+ const pkgJsonPath = join(packageDir, "package.json");
9484
+ if (existsSync(pkgJsonPath)) {
9485
+ const pkgContent = await readFile(pkgJsonPath, "utf8");
9486
+ const pkg = JSON.parse(pkgContent);
9487
+ if (pkg.name) packageName = pkg.name;
9488
+ if (pkg.version) version = pkg.version;
9489
+ if (pkg.description) pkgDescription = pkg.description;
9490
+ if (pkg.pipelab) pipelabMeta = pkg.pipelab;
9491
+ }
9492
+ } catch (e) {
9493
+ console.error(`[Plugins] Failed to read package.json in ${packageDir}:`, e);
9494
+ }
9495
+ plugin.packageName = packageName;
9496
+ plugin.id = packageName;
9497
+ plugin.version = fallbackVersion === "local" ? "local" : version;
9498
+ plugin.isOfficial = packageName.startsWith("@pipelab/");
9499
+ plugin.name = pipelabMeta?.name || packageName;
9500
+ plugin.description = pipelabMeta?.description || pkgDescription || "";
9501
+ if (pipelabMeta?.icon) if (typeof pipelabMeta.icon === "string") {
9502
+ let iconPath = pipelabMeta.icon;
9503
+ if (iconPath.startsWith(".")) iconPath = pathToFileURL(join(packageDir, iconPath)).href;
9504
+ plugin.icon = {
9505
+ type: "image",
9506
+ image: iconPath
9507
+ };
9508
+ } else plugin.icon = pipelabMeta.icon;
9509
+ else plugin.icon = {
9510
+ type: "icon",
9511
+ icon: "pi pi-box"
9512
+ };
9513
+ return plugin;
9514
+ };
9421
9515
  const loadPipelabPlugin = async (id, options) => {
9516
+ const start = Date.now();
9422
9517
  try {
9423
- const { packageDir, entryPoint } = await fetchPipelabPlugin(`@pipelab/plugin-${id}`, options.context.releaseTag, {
9518
+ const packageName = id;
9519
+ const fetchStart = Date.now();
9520
+ const { packageDir, entryPoint } = await fetchPipelabPlugin(packageName, options.context.releaseTag, {
9424
9521
  context: options.context,
9425
9522
  installDeps: false
9426
9523
  });
9524
+ const fetchDuration = Date.now() - fetchStart;
9427
9525
  console.log(`[Plugins] [${id}] Attempting to import from: ${entryPoint}`);
9428
9526
  if (!existsSync(entryPoint)) {
9429
9527
  console.error(`[Plugins] [${id}] CRITICAL: Plugin entry point not found at ${entryPoint}`);
@@ -9432,32 +9530,142 @@ const loadPipelabPlugin = async (id, options) => {
9432
9530
  console.log(`[Plugins] [${id}] Directory contents:`, files);
9433
9531
  } catch (e) {}
9434
9532
  }
9533
+ const importStart = Date.now();
9435
9534
  const pluginModule = await import(pathToFileURL(entryPoint).href);
9436
- console.log(`[Plugins] [${id}] Successfully loaded from: ${packageDir}`);
9437
- return pluginModule.default;
9535
+ const importDuration = Date.now() - importStart;
9536
+ const totalDuration = Date.now() - start;
9537
+ console.log(`[Plugins] [${id}] Successfully loaded from: ${packageDir} (fetch: ${fetchDuration}ms, import: ${importDuration}ms, total: ${totalDuration}ms)`);
9538
+ const plugin = pluginModule.default;
9539
+ await enhancePluginDefinition(plugin, packageDir, packageName, options.context.releaseTag);
9540
+ return plugin;
9438
9541
  } catch (e) {
9439
- console.error(`[Plugins] [${id}] CRITICAL: Failed to load:`, e);
9542
+ console.error(`[Plugins] [${id}] CRITICAL: Failed to load after ${Date.now() - start}ms:`, e);
9440
9543
  if (e.code === "ERR_MODULE_NOT_FOUND") console.error(`[Plugins] [${id}] This usually means a dependency is missing in the plugin's node_modules.`);
9441
9544
  return null;
9442
9545
  }
9443
9546
  };
9547
+ const loadCustomPlugin = async (packageName, version, options) => {
9548
+ const start = Date.now();
9549
+ try {
9550
+ const fetchStart = Date.now();
9551
+ const { packageDir, entryPoint } = await fetchPipelabPlugin(packageName, version, {
9552
+ context: options.context,
9553
+ installDeps: false
9554
+ });
9555
+ const fetchDuration = Date.now() - fetchStart;
9556
+ console.log(`[Plugins] [${packageName}] Attempting to import custom plugin from: ${entryPoint}`);
9557
+ if (!existsSync(entryPoint)) {
9558
+ console.error(`[Plugins] [${packageName}] CRITICAL: Plugin entry point not found at ${entryPoint}`);
9559
+ return null;
9560
+ }
9561
+ const importStart = Date.now();
9562
+ const pluginModule = await import(pathToFileURL(entryPoint).href);
9563
+ const importDuration = Date.now() - importStart;
9564
+ const totalDuration = Date.now() - start;
9565
+ console.log(`[Plugins] [${packageName}] Successfully loaded custom plugin from: ${packageDir} (fetch: ${fetchDuration}ms, import: ${importDuration}ms, total: ${totalDuration}ms)`);
9566
+ const plugin = pluginModule.default;
9567
+ await enhancePluginDefinition(plugin, packageDir, packageName, version);
9568
+ return plugin;
9569
+ } catch (e) {
9570
+ console.error(`[Plugins] [${packageName}] CRITICAL: Failed to load after ${Date.now() - start}ms:`, e);
9571
+ return null;
9572
+ }
9573
+ };
9574
+ async function findInstalledPlugins(packagesDir) {
9575
+ const installed = [];
9576
+ if (!existsSync(packagesDir)) return installed;
9577
+ async function scan(dir, depth = 0) {
9578
+ if (depth > 4) return;
9579
+ try {
9580
+ const entries = await readdir(dir, { withFileTypes: true });
9581
+ if (entries.some((e) => e.isFile() && e.name === "package.json")) {
9582
+ try {
9583
+ const content = await readFile(join(dir, "package.json"), "utf8");
9584
+ const pkg = JSON.parse(content);
9585
+ if (pkg.name && pkg.name !== "pnpm") installed.push({
9586
+ name: pkg.name,
9587
+ version: pkg.version || "0.0.0",
9588
+ packageDir: dir,
9589
+ description: pkg.description || ""
9590
+ });
9591
+ } catch (e) {}
9592
+ return;
9593
+ }
9594
+ for (const entry of entries) if (entry.isDirectory() && entry.name !== "node_modules" && !entry.name.startsWith(".")) await scan(join(dir, entry.name), depth + 1);
9595
+ } catch (e) {}
9596
+ }
9597
+ await scan(packagesDir);
9598
+ return installed;
9599
+ }
9444
9600
  const builtInPlugins = async (options) => {
9445
9601
  console.log("[Plugins] Starting background plugin loading...");
9446
9602
  sendStartupProgress("Preparing environment...");
9603
+ const envStart = Date.now();
9447
9604
  await Promise.all([ensureNodeJS(options.context), ensurePNPM(options.context)]);
9605
+ console.log(`[Plugins] Environment preparation took ${Date.now() - envStart}ms`);
9448
9606
  const { usePlugins } = await import("@pipelab/shared");
9449
9607
  const { registerPlugins } = usePlugins();
9450
9608
  const { webSocketServer } = await import("./index.mjs");
9451
9609
  (async () => {
9452
- for (const id of DEFAULT_PLUGIN_IDS) {
9453
- sendStartupProgress(`Loading plugin: ${id}`);
9454
- const plugin = await loadPipelabPlugin(id, options);
9455
- if (plugin) {
9456
- registerPlugins([plugin]);
9457
- webSocketServer.broadcast("plugin:loaded", { plugin });
9610
+ const totalStart = Date.now();
9611
+ const pluginsToLoad = /* @__PURE__ */ new Map();
9612
+ for (const name of [
9613
+ "@pipelab/plugin-construct",
9614
+ "@pipelab/plugin-filesystem",
9615
+ "@pipelab/plugin-system",
9616
+ "@pipelab/plugin-steam",
9617
+ "@pipelab/plugin-itch",
9618
+ "@pipelab/plugin-electron",
9619
+ "@pipelab/plugin-discord",
9620
+ "@pipelab/plugin-poki",
9621
+ "@pipelab/plugin-nvpatch",
9622
+ "@pipelab/plugin-tauri",
9623
+ "@pipelab/plugin-minify",
9624
+ "@pipelab/plugin-netlify"
9625
+ ]) pluginsToLoad.set(name, "latest");
9626
+ if (isDev && projectRoot) {
9627
+ const pluginsDir = join(projectRoot, "plugins");
9628
+ if (existsSync(pluginsDir)) try {
9629
+ const entries = await readdir(pluginsDir, { withFileTypes: true });
9630
+ for (const entry of entries) if (entry.isDirectory()) {
9631
+ const pkgPath = join(pluginsDir, entry.name, "package.json");
9632
+ if (existsSync(pkgPath)) try {
9633
+ const pkgContent = await readFile(pkgPath, "utf-8");
9634
+ const pkg = JSON.parse(pkgContent);
9635
+ if (pkg.name && pkg.name.startsWith("@pipelab/plugin-") && pkg.name !== "@pipelab/plugin-core") pluginsToLoad.set(pkg.name, "local");
9636
+ } catch (e) {
9637
+ console.error(`[Plugins] Failed to parse package.json for ${entry.name}:`, e);
9638
+ }
9639
+ }
9640
+ } catch (e) {
9641
+ console.error(`[Plugins] Failed to scan local workspace plugins directory:`, e);
9642
+ }
9643
+ }
9644
+ try {
9645
+ const { setupConfigFile } = await import("./config-CEkOf95p.mjs");
9646
+ const settingsPlugins = (await (await setupConfigFile("settings", { context: options.context })).getConfig())?.plugins || [];
9647
+ for (const plugin of settingsPlugins) if (plugin.name) if (plugin.enabled) {
9648
+ if (!pluginsToLoad.has(plugin.name)) pluginsToLoad.set(plugin.name, "latest");
9649
+ } else pluginsToLoad.delete(plugin.name);
9650
+ } catch (e) {
9651
+ console.error(`[Plugins] Failed to load settings config on startup:`, e);
9652
+ }
9653
+ console.log(`[Plugins] Total plugins to load on startup:`, Array.from(pluginsToLoad.entries()));
9654
+ for (const [packageName, version] of pluginsToLoad.entries()) {
9655
+ sendStartupProgress(`Loading plugin: ${packageName}`);
9656
+ const pluginStart = Date.now();
9657
+ try {
9658
+ const plugin = await loadCustomPlugin(packageName, version, options);
9659
+ if (plugin) {
9660
+ registerPlugins([plugin]);
9661
+ webSocketServer.broadcast("plugin:loaded", { plugin });
9662
+ console.log(`[Plugins] Loaded ${packageName}@${version} in ${Date.now() - pluginStart}ms`);
9663
+ }
9664
+ } catch (err) {
9665
+ console.error(`[Plugins] Failed to load plugin ${packageName} at startup:`, err);
9458
9666
  }
9459
9667
  }
9460
- console.log("[Plugins] All default plugins loaded.");
9668
+ console.log(`[Plugins] All startup plugins loaded in ${Date.now() - totalStart}ms.`);
9461
9669
  sendStartupProgress("All plugins loaded.");
9462
9670
  setTimeout(() => {
9463
9671
  webSocketServer.broadcast("startup:progress", { type: "ready" });
@@ -9472,10 +9680,6 @@ const getFinalPlugins = () => {
9472
9680
  const finalPlugins = [];
9473
9681
  for (const plugin of plugins.value) {
9474
9682
  const finalNodes = [];
9475
- const transformUrl = (url) => {
9476
- if (url.startsWith("file://")) return url.replace("file://", "media://");
9477
- return url;
9478
- };
9479
9683
  const finalIcon = plugin.icon?.type === "image" ? {
9480
9684
  ...plugin.icon,
9481
9685
  image: transformUrl(plugin.icon.image)
@@ -9621,7 +9825,7 @@ const executeGraphWithHistory = async ({ graph, variables, projectName, projectP
9621
9825
  const c3toSteamPreset = async () => {
9622
9826
  return {
9623
9827
  data: {
9624
- version: "3.0.0",
9828
+ version: "5.0.0",
9625
9829
  name: "Construct 3 to Steam",
9626
9830
  description: "A basic project to get you started with Construct 3 and Steam",
9627
9831
  variables: [],
@@ -9629,8 +9833,9 @@ const c3toSteamPreset = async () => {
9629
9833
  triggers: [{
9630
9834
  type: "event",
9631
9835
  origin: {
9632
- pluginId: "system",
9633
- nodeId: "manual"
9836
+ pluginId: "@pipelab/plugin-system",
9837
+ nodeId: "manual",
9838
+ version: "latest"
9634
9839
  },
9635
9840
  uid: "manual-start",
9636
9841
  params: {}
@@ -9641,7 +9846,8 @@ const c3toSteamPreset = async () => {
9641
9846
  type: "action",
9642
9847
  origin: {
9643
9848
  nodeId: "export-construct-project",
9644
- pluginId: "construct"
9849
+ pluginId: "@pipelab/plugin-construct",
9850
+ version: "latest"
9645
9851
  },
9646
9852
  params: {
9647
9853
  file: {
@@ -9679,7 +9885,8 @@ const c3toSteamPreset = async () => {
9679
9885
  type: "action",
9680
9886
  origin: {
9681
9887
  nodeId: "unzip-file-node",
9682
- pluginId: "filesystem"
9888
+ pluginId: "@pipelab/plugin-filesystem",
9889
+ version: "latest"
9683
9890
  },
9684
9891
  params: { file: {
9685
9892
  editor: "editor",
@@ -9691,7 +9898,8 @@ const c3toSteamPreset = async () => {
9691
9898
  type: "action",
9692
9899
  origin: {
9693
9900
  nodeId: "electron:package:v2",
9694
- pluginId: "electron"
9901
+ pluginId: "@pipelab/plugin-electron",
9902
+ version: "latest"
9695
9903
  },
9696
9904
  params: {
9697
9905
  arch: {
@@ -9821,7 +10029,8 @@ const c3toSteamPreset = async () => {
9821
10029
  type: "action",
9822
10030
  origin: {
9823
10031
  nodeId: "steam-upload",
9824
- pluginId: "steam"
10032
+ pluginId: "@pipelab/plugin-steam",
10033
+ version: "latest"
9825
10034
  },
9826
10035
  params: {
9827
10036
  sdk: {
@@ -9864,7 +10073,8 @@ const c3toSteamPreset = async () => {
9864
10073
  type: "action",
9865
10074
  origin: {
9866
10075
  nodeId: "fs:open-in-explorer",
9867
- pluginId: "filesystem"
10076
+ pluginId: "@pipelab/plugin-filesystem",
10077
+ version: "latest"
9868
10078
  },
9869
10079
  params: { path: {
9870
10080
  editor: "editor",
@@ -9883,7 +10093,7 @@ const c3toSteamPreset = async () => {
9883
10093
  const newProjectPreset = async () => {
9884
10094
  return {
9885
10095
  data: {
9886
- version: "3.0.0",
10096
+ version: "5.0.0",
9887
10097
  name: "Empty project",
9888
10098
  description: "A default project with no tasks added",
9889
10099
  variables: [],
@@ -9891,8 +10101,9 @@ const newProjectPreset = async () => {
9891
10101
  triggers: [{
9892
10102
  type: "event",
9893
10103
  origin: {
9894
- pluginId: "system",
9895
- nodeId: "manual"
10104
+ pluginId: "@pipelab/plugin-system",
10105
+ nodeId: "manual",
10106
+ version: "latest"
9896
10107
  },
9897
10108
  uid: "manual-start",
9898
10109
  params: {}
@@ -9908,7 +10119,7 @@ const newProjectPreset = async () => {
9908
10119
  const moreToCome = async () => {
9909
10120
  return {
9910
10121
  data: {
9911
- version: "3.0.0",
10122
+ version: "5.0.0",
9912
10123
  name: "More to come!",
9913
10124
  description: "Do not hesitate to suggest templates you would see here",
9914
10125
  variables: [],
@@ -9916,8 +10127,9 @@ const moreToCome = async () => {
9916
10127
  triggers: [{
9917
10128
  type: "event",
9918
10129
  origin: {
9919
- pluginId: "system",
9920
- nodeId: "manual"
10130
+ pluginId: "@pipelab/plugin-system",
10131
+ nodeId: "manual",
10132
+ version: "latest"
9921
10133
  },
9922
10134
  uid: "manual-start",
9923
10135
  params: {}
@@ -9961,10 +10173,6 @@ const registerEngineHandlers = (context) => {
9961
10173
  }
9962
10174
  });
9963
10175
  });
9964
- handle("condition:execute", async (_, { value }) => {
9965
- const { nodeId, params, pluginId } = value;
9966
- await handleConditionExecute(nodeId, pluginId, params, await generateTempFolder(tmpdir()), context);
9967
- });
9968
10176
  let abortControllerGraph = void 0;
9969
10177
  const effectiveActionExecute = async (nodeId, pluginId, params, mainWindow, send, cwd, cachePath) => {
9970
10178
  try {
@@ -10346,6 +10554,241 @@ const registerSystemHandlers = (options) => {
10346
10554
  });
10347
10555
  };
10348
10556
  //#endregion
10557
+ //#region src/handlers/plugins.ts
10558
+ const localPluginsMap = /* @__PURE__ */ new Map();
10559
+ if (isDev && projectRoot) {
10560
+ const pluginsDir = join(projectRoot, "plugins");
10561
+ if (existsSync(pluginsDir)) try {
10562
+ const entries = readdirSync(pluginsDir, { withFileTypes: true });
10563
+ for (const entry of entries) if (entry.isDirectory()) {
10564
+ const pkgPath = join(pluginsDir, entry.name, "package.json");
10565
+ if (existsSync(pkgPath)) try {
10566
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
10567
+ if (pkg.name) localPluginsMap.set(pkg.name, join(pluginsDir, entry.name));
10568
+ } catch (e) {
10569
+ console.error(`[Plugins] Failed to parse package.json for ${entry.name}:`, e);
10570
+ }
10571
+ }
10572
+ } catch (e) {
10573
+ console.error(`[Plugins] Failed to scan local workspace plugins directory:`, e);
10574
+ }
10575
+ }
10576
+ /**
10577
+ * Resolves a requested plugin version.
10578
+ * If the plugin is present in the local workspace map during development,
10579
+ * its version is overridden to "local" so Pipelab loads it from the source files.
10580
+ */
10581
+ function resolvePluginVersion(packageName, requestedVersion) {
10582
+ if (isDev && projectRoot && process.env.PIPELAB_FORCE_NPM !== "true" && localPluginsMap.has(packageName)) return "local";
10583
+ return requestedVersion || "latest";
10584
+ }
10585
+ const registerPluginsHandlers = (context) => {
10586
+ const { handle } = useAPI();
10587
+ handle("plugin:search", async (_, { send, value }) => {
10588
+ try {
10589
+ const query = value.query || "";
10590
+ const text = query ? `${query} keywords:pipelab-plugin` : "keywords:pipelab-plugin";
10591
+ const url = `https://registry.npmjs.org/-/v1/search?text=${encodeURIComponent(text)}&size=50`;
10592
+ const response = await fetch(url);
10593
+ if (!response.ok) throw new Error(`NPM registry search failed with status ${response.status}`);
10594
+ send({
10595
+ type: "end",
10596
+ data: {
10597
+ type: "success",
10598
+ result: { results: ((await response.json()).objects || []).map((obj) => ({
10599
+ name: obj.package.name,
10600
+ version: obj.package.version,
10601
+ description: obj.package.description,
10602
+ keywords: obj.package.keywords,
10603
+ date: obj.package.date
10604
+ })) }
10605
+ }
10606
+ });
10607
+ } catch (e) {
10608
+ send({
10609
+ type: "end",
10610
+ data: {
10611
+ type: "error",
10612
+ ipcError: e.message || "Failed to search npm registry"
10613
+ }
10614
+ });
10615
+ }
10616
+ });
10617
+ handle("plugin:get-details", async (_, { send, value }) => {
10618
+ try {
10619
+ const { packageName } = value;
10620
+ const packument = await pacote.packument(packageName, { fullMetadata: true });
10621
+ const latestVersion = packument["dist-tags"]?.latest || Object.keys(packument.versions).pop() || "0.0.0";
10622
+ const latestPkg = packument.versions[latestVersion];
10623
+ send({
10624
+ type: "end",
10625
+ data: {
10626
+ type: "success",
10627
+ result: {
10628
+ name: packument.name,
10629
+ latestVersion,
10630
+ versions: Object.keys(packument.versions).reverse(),
10631
+ description: latestPkg?.description
10632
+ }
10633
+ }
10634
+ });
10635
+ } catch (e) {
10636
+ send({
10637
+ type: "end",
10638
+ data: {
10639
+ type: "error",
10640
+ ipcError: e.message || "Failed to get plugin details"
10641
+ }
10642
+ });
10643
+ }
10644
+ });
10645
+ handle("plugin:install", async (_, { send, value }) => {
10646
+ try {
10647
+ const { packageName, version } = value;
10648
+ const mappedVersion = resolvePluginVersion(packageName, version);
10649
+ console.log(`[Plugins] Installing ${packageName}@${mappedVersion}...`);
10650
+ const { packageDir } = await fetchPipelabPlugin(packageName, mappedVersion, {
10651
+ context,
10652
+ installDeps: false
10653
+ });
10654
+ const plugin = await loadCustomPlugin(packageName, mappedVersion, { context });
10655
+ if (!plugin) throw new Error("Failed to load installed plugin module.");
10656
+ const { registerPlugins } = usePlugins();
10657
+ registerPlugins([plugin]);
10658
+ webSocketServer.broadcast("plugin:loaded", { plugin });
10659
+ send({
10660
+ type: "end",
10661
+ data: {
10662
+ type: "success",
10663
+ result: { result: "ok" }
10664
+ }
10665
+ });
10666
+ } catch (e) {
10667
+ console.error(`[Plugins] Installation failed for ${value.packageName}:`, e);
10668
+ send({
10669
+ type: "end",
10670
+ data: {
10671
+ type: "error",
10672
+ ipcError: e.message || "Failed to install plugin"
10673
+ }
10674
+ });
10675
+ }
10676
+ });
10677
+ handle("plugin:uninstall", async (_, { send, value }) => {
10678
+ try {
10679
+ const { packageName } = value;
10680
+ console.log(`[Plugins] Uninstalling plugin ${packageName}...`);
10681
+ const targetDir = context.getPackagesPath(packageName);
10682
+ if (existsSync(targetDir)) await rm(targetDir, {
10683
+ recursive: true,
10684
+ force: true
10685
+ });
10686
+ send({
10687
+ type: "end",
10688
+ data: {
10689
+ type: "success",
10690
+ result: { result: "ok" }
10691
+ }
10692
+ });
10693
+ } catch (e) {
10694
+ send({
10695
+ type: "end",
10696
+ data: {
10697
+ type: "error",
10698
+ ipcError: e.message || "Failed to uninstall plugin"
10699
+ }
10700
+ });
10701
+ }
10702
+ });
10703
+ handle("plugin:list-installed", async (_, { send }) => {
10704
+ try {
10705
+ const rawInstalled = await findInstalledPlugins(context.getPackagesPath());
10706
+ const DEFAULT_PLUGIN_IDS = [
10707
+ "construct",
10708
+ "filesystem",
10709
+ "system",
10710
+ "steam",
10711
+ "itch",
10712
+ "electron",
10713
+ "discord",
10714
+ "poki",
10715
+ "nvpatch",
10716
+ "tauri",
10717
+ "minify",
10718
+ "netlify"
10719
+ ];
10720
+ send({
10721
+ type: "end",
10722
+ data: {
10723
+ type: "success",
10724
+ result: { installed: rawInstalled.filter((item) => {
10725
+ return !DEFAULT_PLUGIN_IDS.some((id) => item.name === `@pipelab/plugin-${id}`);
10726
+ }).map((item) => ({
10727
+ name: item.name,
10728
+ version: item.version,
10729
+ description: item.description
10730
+ })) }
10731
+ }
10732
+ });
10733
+ } catch (e) {
10734
+ send({
10735
+ type: "end",
10736
+ data: {
10737
+ type: "error",
10738
+ ipcError: e.message || "Failed to list installed plugins"
10739
+ }
10740
+ });
10741
+ }
10742
+ });
10743
+ handle("plugin:ensure-loaded", async (_, { send, value }) => {
10744
+ const { pluginIds, plugins } = value;
10745
+ const { plugins: registeredPlugins, registerPlugins } = usePlugins();
10746
+ const loaded = [];
10747
+ const failed = [];
10748
+ const pluginsToEnsure = /* @__PURE__ */ new Set();
10749
+ if (Array.isArray(pluginIds)) {
10750
+ for (const id of pluginIds) if (id) pluginsToEnsure.add(id);
10751
+ }
10752
+ if (plugins && typeof plugins === "object") {
10753
+ for (const id of Object.keys(plugins)) if (id) pluginsToEnsure.add(id);
10754
+ }
10755
+ for (const packageName of pluginsToEnsure) {
10756
+ const mappedVersion = resolvePluginVersion(packageName);
10757
+ if (registeredPlugins.value.some((p) => {
10758
+ if (p.packageName !== packageName) return false;
10759
+ if (mappedVersion === "latest") return true;
10760
+ return p.version === mappedVersion;
10761
+ })) continue;
10762
+ try {
10763
+ console.log(`[Plugins] JIT loading plugin "${packageName}@${mappedVersion}" for pipeline...`);
10764
+ const plugin = await loadCustomPlugin(packageName, mappedVersion, { context });
10765
+ if (plugin) {
10766
+ registerPlugins([plugin]);
10767
+ webSocketServer.broadcast("plugin:loaded", { plugin });
10768
+ loaded.push(packageName);
10769
+ console.log(`[Plugins] JIT loaded "${packageName}" successfully.`);
10770
+ } else {
10771
+ console.warn(`[Plugins] JIT load for "${packageName}" returned no plugin.`);
10772
+ failed.push(packageName);
10773
+ }
10774
+ } catch (e) {
10775
+ console.error(`[Plugins] JIT load failed for "${packageName}":`, e);
10776
+ failed.push(packageName);
10777
+ }
10778
+ }
10779
+ send({
10780
+ type: "end",
10781
+ data: {
10782
+ type: "success",
10783
+ result: {
10784
+ loaded,
10785
+ failed
10786
+ }
10787
+ }
10788
+ });
10789
+ });
10790
+ };
10791
+ //#endregion
10349
10792
  //#region src/handlers/index.ts
10350
10793
  const registerAllHandlers = async (options) => {
10351
10794
  const context = options.context;
@@ -10357,6 +10800,7 @@ const registerAllHandlers = async (options) => {
10357
10800
  registerAgentsHandlers(context);
10358
10801
  registerAuthHandlers(context);
10359
10802
  registerSystemHandlers(options);
10803
+ registerPluginsHandlers(context);
10360
10804
  const { registerPlugins } = usePlugins();
10361
10805
  builtInPlugins({ context });
10362
10806
  };
@@ -52704,15 +53148,54 @@ async function runPipelineCommand(file, options, version) {
52704
53148
  */
52705
53149
  async function fetchPackageReleases(packageName, options = {}) {
52706
53150
  const { repo = "CynToolkit/pipelab", allowPrerelease = false } = options;
52707
- const url = `https://api.github.com/repos/${repo}/releases`;
52708
- console.log(`[GitHub] Fetching releases for ${packageName} from ${url}...`);
52709
53151
  try {
52710
- const response = await fetch(url, { headers: {
53152
+ const override = process.env.PIPELAB_OVERRIDE_RELEASE;
53153
+ if (override) {
53154
+ const targetTag = override.includes("@") ? override : `${packageName}@${override}`;
53155
+ console.log(`[GitHub] Fetching specific override release: ${targetTag}`);
53156
+ const response = await fetch(`https://api.github.com/repos/${repo}/releases/tags/${encodeURIComponent(targetTag)}`, { headers: {
53157
+ "User-Agent": "Pipelab-Desktop-Updater",
53158
+ Accept: "application/vnd.github.v3+json"
53159
+ } });
53160
+ if (response.ok) return [await response.json()];
53161
+ console.warn(`[GitHub] Override release tag ${targetTag} not found or error occurred`);
53162
+ }
53163
+ const matchingRefsUrl = `https://api.github.com/repos/${repo}/git/matching-refs/tags/${encodeURIComponent(packageName)}`;
53164
+ console.log(`[GitHub] Querying matching tags from ${matchingRefsUrl}...`);
53165
+ const response = await fetch(matchingRefsUrl, { headers: {
52711
53166
  "User-Agent": "Pipelab-Desktop-Updater",
52712
53167
  Accept: "application/vnd.github.v3+json"
52713
53168
  } });
52714
53169
  if (!response.ok) throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
52715
- return (await response.json()).filter((r) => r.tag_name.startsWith(`${packageName}@`)).filter((r) => allowPrerelease || !r.prerelease);
53170
+ const refs = await response.json();
53171
+ if (!Array.isArray(refs)) return [];
53172
+ const filteredTags = refs.map((r) => r.ref.replace("refs/tags/", "")).filter((tag) => tag.startsWith(`${packageName}@`)).filter((tag) => {
53173
+ const version = tag.split("@").pop();
53174
+ if (!version || !semver.valid(version)) return false;
53175
+ const isPrerelease = semver.prerelease(version) !== null;
53176
+ return allowPrerelease || !isPrerelease;
53177
+ });
53178
+ if (filteredTags.length === 0) return [];
53179
+ filteredTags.sort((a, b) => {
53180
+ const vA = a.split("@").pop() || "0.0.0";
53181
+ const vB = b.split("@").pop() || "0.0.0";
53182
+ return semver.rcompare(vA, vB);
53183
+ });
53184
+ for (const tag of filteredTags) {
53185
+ console.log(`[GitHub] Fetching release details for tag: ${tag}`);
53186
+ const releaseResponse = await fetch(`https://api.github.com/repos/${repo}/releases/tags/${encodeURIComponent(tag)}`, { headers: {
53187
+ "User-Agent": "Pipelab-Desktop-Updater",
53188
+ Accept: "application/vnd.github.v3+json"
53189
+ } });
53190
+ if (releaseResponse.status === 404) {
53191
+ console.warn(`[GitHub] Tag "${tag}" has no associated Release, skipping.`);
53192
+ continue;
53193
+ }
53194
+ if (!releaseResponse.ok) throw new Error(`GitHub API error: ${releaseResponse.status} ${releaseResponse.statusText}`);
53195
+ return [await releaseResponse.json()];
53196
+ }
53197
+ console.warn(`[GitHub] No published Release found for any matching tag of "${packageName}".`);
53198
+ return [];
52716
53199
  } catch (error) {
52717
53200
  console.error(`[GitHub] Failed to fetch releases for ${packageName}:`, error);
52718
53201
  return [];
@@ -52755,6 +53238,6 @@ async function fetchLatestDesktopRelease(options = {}) {
52755
53238
  return latest;
52756
53239
  }
52757
53240
  //#endregion
52758
- 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, generateTempFolder, getDefaultUserDataPath, getFinalPlugins, getFolderSize, getMigrator, handleActionExecute, handleConditionExecute, isDev, loadPipelabPlugin, projectRoot, registerAgentsHandlers, registerAllHandlers, registerConfigHandlers, registerEngineHandlers, registerFsHandlers, registerHistoryHandlers, registerMigrationHandlers, registerShellHandlers, runPipelineCommand, runPnpm, runWithLiveLogs, sendStartupProgress, sendStartupReady, serveCommand, setupConfigFile, useAPI, usePluginAPI, webSocketServer, zipFolder };
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 };
52759
53242
 
52760
53243
  //# sourceMappingURL=index.mjs.map