Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  postinstall-bundled-plugins.mjs   Sprache: unbekannt

 
Spracherkennung für: .mjs vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

#!/usr/bin/env node
// Runs after install to keep packaged dist safe and compatible.
// Bundled extension runtime dependencies are extension-owned. Do not install
// every bundled extension dependency during core package install unless the
// legacy eager-install escape hatch is explicitly enabled; `openclaw doctor
// --fix` owns the repair path for extensions that are actually used.
import { spawnSync } from "node:child_process";
import { randomUUID } from "node:crypto";
import {
  chmodSync,
  closeSync,
  existsSync,
  lstatSync,
  mkdirSync,
  openSync,
  readdirSync,
  readFileSync,
  realpathSync,
  renameSync,
  rmdirSync,
  rmSync,
  unlinkSync,
  writeFileSync,
} from "node:fs";
import { basename, dirname, isAbsolute, join, relative } from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { resolveNpmRunner } from "./npm-runner.mjs";

export const BUNDLED_PLUGIN_INSTALL_TARGETS = [];

const __dirname = dirname(fileURLToPath(import.meta.url));
const DEFAULT_EXTENSIONS_DIR = join(__dirname, "..", "dist", "extensions");
const DEFAULT_PACKAGE_ROOT = join(__dirname, "..");
const DISABLE_POSTINSTALL_ENV = "OPENCLAW_DISABLE_BUNDLED_PLUGIN_POSTINSTALL";
const EAGER_BUNDLED_PLUGIN_DEPS_ENV = "OPENCLAW_EAGER_BUNDLED_PLUGIN_DEPS";
const DIST_INVENTORY_PATH = "dist/postinstall-inventory.json";
const LEGACY_QA_CHANNEL_DIR = ["qa", "channel"].join("-");
const LEGACY_QA_LAB_DIR = ["qa", "lab"].join("-");
const LEGACY_UPDATE_COMPAT_SIDECARS = [
  {
    path: `dist/extensions/${LEGACY_QA_CHANNEL_DIR}/runtime-api.js`,
    content: "export {};\n",
  },
  {
    path: `dist/extensions/${LEGACY_QA_LAB_DIR}/runtime-api.js`,
    content: "export {};\n",
  },
];
const BAILEYS_MEDIA_FILE = join(
  "node_modules",
  "@whiskeysockets",
  "baileys",
  "lib",
  "Utils",
  "messages-media.js",
);
const BAILEYS_MEDIA_HOTFIX_NEEDLE = [
  "        encFileWriteStream.write(mac);",
  "        encFileWriteStream.end();",
  "        originalFileStream?.end?.();",
  "        stream.destroy();",
  "        logger?.debug('encrypted data successfully');",
].join("\n");
const BAILEYS_MEDIA_HOTFIX_REPLACEMENT = [
  "        encFileWriteStream.write(mac);",
  "        const encFinishPromise = once(encFileWriteStream, 'finish');",
  "        const originalFinishPromise = originalFileStream ? once(originalFileStream, 'finish') : Promise.resolve();",
  "        encFileWriteStream.end();",
  "        originalFileStream?.end?.();",
  "        stream.destroy();",
  "        await Promise.all([encFinishPromise, originalFinishPromise]);",
  "        logger?.debug('encrypted data successfully');",
].join("\n");
const BAILEYS_MEDIA_HOTFIX_SEQUENTIAL_REPLACEMENT = [
  "        encFileWriteStream.write(mac);",
  "        const encFinishPromise = once(encFileWriteStream, 'finish');",
  "        const originalFinishPromise = originalFileStream ? once(originalFileStream, 'finish') : Promise.resolve();",
  "        encFileWriteStream.end();",
  "        originalFileStream?.end?.();",
  "        stream.destroy();",
  "        await encFinishPromise;",
  "        await originalFinishPromise;",
  "        logger?.debug('encrypted data successfully');",
].join("\n");
const BAILEYS_MEDIA_HOTFIX_FINISH_PROMISES_RE =
  /const\s+encFinishPromise\s*=\s*once\(encFileWriteStream,\s*'finish'\);\s*\n[\s\S]*const\s+originalFinishPromise\s*=\s*originalFileStream\s*\?\s*once\(originalFileStream,\s*'finish'\)\s*:\s*Promise\.resolve\(\);/u;
const BAILEYS_MEDIA_HOTFIX_PROMISE_ALL_RE =
  /await\s+Promise\.all\(\[\s*encFinishPromise\s*,\s*originalFinishPromise\s*\]\);/u;
const BAILEYS_MEDIA_HOTFIX_SEQUENTIAL_AWAITS_RE =
  /await\s+encFinishPromise;\s*(?:\/\/[^\n]*\n|\s)*await\s+originalFinishPromise;/u;
const BAILEYS_MEDIA_DISPATCHER_NEEDLE = [
  "                const response = await fetch(url, {",
  "                    dispatcher: fetchAgent,",
  "                    method: 'POST',",
].join("\n");
const BAILEYS_MEDIA_DISPATCHER_REPLACEMENT = [
  "                const response = await fetch(url, {",
  "                    method: 'POST',",
].join("\n");
const BAILEYS_MEDIA_DISPATCHER_HEADER_NEEDLE = [
  "                        'Content-Type': 'application/octet-stream',",
  "                        Origin: DEFAULT_ORIGIN",
  "                    },",
].join("\n");
const BAILEYS_MEDIA_DISPATCHER_HEADER_REPLACEMENT = [
  "                        'Content-Type': 'application/octet-stream',",
  "                        Origin: DEFAULT_ORIGIN",
  "                    },",
  "                    // Baileys passes a generic agent here in some runtimes. Undici's",
  "                    // `dispatcher` only works with Dispatcher-compatible implementations,",
  "                    // so only wire it through when the object actually implements",
  "                    // `dispatch`.",
  "                    ...(typeof fetchAgent?.dispatch === 'function' ? { dispatcher: fetchAgent } : {}),",
].join("\n");
const BAILEYS_MEDIA_ONCE_IMPORT_RE = /import\s+\{\s*once\s*\}\s+from\s+['"]events['"]/u;
const BAILEYS_MEDIA_ASYNC_CONTEXT_RE =
  /async\s+function\s+encryptedStream|encryptedStream\s*=\s*async/u;

function readJson(filePath) {
  return JSON.parse(readFileSync(filePath, "utf8"));
}

function normalizeRelativePath(filePath) {
  return filePath.replace(/\\/g, "/");
}

function readInstalledDistInventory(params = {}) {
  const packageRoot = params.packageRoot ?? DEFAULT_PACKAGE_ROOT;
  const pathExists = params.existsSync ?? existsSync;
  const readFile = params.readFileSync ?? readFileSync;
  const inventoryPath = join(packageRoot, DIST_INVENTORY_PATH);
  if (!pathExists(inventoryPath)) {
    throw new Error(`missing dist inventory: ${DIST_INVENTORY_PATH}`);
  }
  let parsed;
  try {
    parsed = JSON.parse(readFile(inventoryPath, "utf8"));
  } catch {
    throw new Error(`invalid dist inventory: ${DIST_INVENTORY_PATH}`);
  }
  if (!Array.isArray(parsed) || parsed.some((entry) => typeof entry !== "string")) {
    throw new Error(`invalid dist inventory: ${DIST_INVENTORY_PATH}`);
  }
  return new Set(parsed.map(normalizeRelativePath));
}

function isRecoverableInstalledDistInventoryError(error) {
  return error instanceof Error && /^(missing|invalid) dist inventory: /u.test(error.message);
}

function resolveInstalledDistRoot(params = {}) {
  const packageRoot = params.packageRoot ?? DEFAULT_PACKAGE_ROOT;
  const pathExists = params.existsSync ?? existsSync;
  const pathLstat = params.lstatSync ?? lstatSync;
  const resolveRealPath = params.realpathSync ?? realpathSync;
  const distDir = join(packageRoot, "dist");
  if (!pathExists(distDir)) {
    return null;
  }
  const distStats = pathLstat(distDir);
  if (!distStats.isDirectory() || distStats.isSymbolicLink()) {
    throw new Error("unsafe dist root: dist must be a real directory");
  }
  const packageRootReal = resolveRealPath(packageRoot);
  const distDirReal = resolveRealPath(distDir);
  const relativeDistPath = relative(packageRootReal, distDirReal);
  if (relativeDistPath !== "dist") {
    throw new Error("unsafe dist root: dist escaped package root");
  }
  return { distDir, distDirReal, packageRootReal };
}

function assertSafeInstalledDistPath(relativePath, params) {
  const resolveRealPath = params.realpathSync ?? realpathSync;
  const candidatePath = join(params.packageRoot, relativePath);
  const candidateRealPath = resolveRealPath(candidatePath);
  const relativeCandidatePath = relative(params.distDirReal, candidateRealPath);
  if (relativeCandidatePath.startsWith("..") || isAbsolute(relativeCandidatePath)) {
    throw new Error(`unsafe dist path: ${relativePath}`);
  }
  return candidatePath;
}

function isStagedRuntimeNodeModulesPath(relativePath) {
  return /^dist\/extensions\/[^/]+\/node_modules(?:\/|$)/u.test(
    normalizeRelativePath(relativePath),
  );
}

function listInstalledDistFiles(params = {}) {
  const readDir = params.readdirSync ?? readdirSync;
  const distRoot = resolveInstalledDistRoot(params);
  if (distRoot === null) {
    return [];
  }
  const packageRoot = params.packageRoot ?? DEFAULT_PACKAGE_ROOT;
  const pending = [distRoot.distDir];
  const files = [];
  while (pending.length > 0) {
    const currentDir = pending.pop();
    if (!currentDir) {
      continue;
    }
    const relativeCurrentDir = normalizeRelativePath(relative(packageRoot, currentDir));
    if (isStagedRuntimeNodeModulesPath(relativeCurrentDir)) {
      continue;
    }
    for (const entry of readDir(currentDir, { withFileTypes: true })) {
      const entryPath = join(currentDir, entry.name);
      if (entry.isSymbolicLink()) {
        throw new Error(
          `unsafe dist entry: ${normalizeRelativePath(relative(packageRoot, entryPath))}`,
        );
      }
      if (entry.isDirectory()) {
        pending.push(entryPath);
        continue;
      }
      if (!entry.isFile()) {
        continue;
      }
      const relativePath = normalizeRelativePath(relative(packageRoot, entryPath));
      if (relativePath === DIST_INVENTORY_PATH) {
        continue;
      }
      files.push(relativePath);
    }
  }
  return files.toSorted((left, right) => left.localeCompare(right));
}

function pruneEmptyDistDirectories(params = {}) {
  const readDir = params.readdirSync ?? readdirSync;
  const removeDirectory = params.rmdirSync ?? rmdirSync;
  const distRoot = resolveInstalledDistRoot(params);
  if (distRoot === null) {
    return;
  }
  const packageRoot = params.packageRoot ?? DEFAULT_PACKAGE_ROOT;
  const pathLstat = params.lstatSync ?? lstatSync;

  function prune(currentDir) {
    const relativeCurrentDir = normalizeRelativePath(relative(packageRoot, currentDir));
    if (isStagedRuntimeNodeModulesPath(relativeCurrentDir)) {
      return;
    }
    for (const entry of readDir(currentDir, { withFileTypes: true })) {
      if (entry.isSymbolicLink()) {
        throw new Error(
          `unsafe dist entry: ${normalizeRelativePath(relative(packageRoot, join(currentDir, entry.name)))}`,
        );
      }
      if (!entry.isDirectory()) {
        continue;
      }
      prune(join(currentDir, entry.name));
    }
    if (currentDir === distRoot.distDir) {
      return;
    }
    const currentStats = pathLstat(currentDir);
    if (!currentStats.isDirectory() || currentStats.isSymbolicLink()) {
      throw new Error(
        `unsafe dist directory: ${normalizeRelativePath(relative(packageRoot, currentDir))}`,
      );
    }
    if (readDir(currentDir).length === 0) {
      removeDirectory(
        assertSafeInstalledDistPath(normalizeRelativePath(relative(packageRoot, currentDir)), {
          packageRoot,
          distDirReal: distRoot.distDirReal,
          realpathSync: params.realpathSync,
        }),
      );
    }
  }

  prune(distRoot.distDir);
}

export function pruneInstalledPackageDist(params = {}) {
  const packageRoot = params.packageRoot ?? DEFAULT_PACKAGE_ROOT;
  const removeFile = params.unlinkSync ?? unlinkSync;
  const log = params.log ?? console;
  const distRoot = resolveInstalledDistRoot(params);
  if (distRoot === null) {
    return [];
  }
  let expectedFiles = params.expectedFiles ?? null;
  if (expectedFiles === null) {
    try {
      expectedFiles = readInstalledDistInventory(params);
    } catch (error) {
      if (!isRecoverableInstalledDistInventoryError(error)) {
        throw error;
      }
      log.warn?.(`[postinstall] skipping dist prune: ${error.message}`);
      return [];
    }
  }
  const installedFiles = listInstalledDistFiles(params);
  const removed = [];

  for (const relativePath of installedFiles) {
    if (expectedFiles.has(relativePath)) {
      continue;
    }
    removeFile(
      assertSafeInstalledDistPath(relativePath, {
        packageRoot,
        distDirReal: distRoot.distDirReal,
        realpathSync: params.realpathSync,
      }),
    );
    removed.push(relativePath);
  }

  pruneEmptyDistDirectories(params);

  if (removed.length > 0) {
    log.log(`[postinstall] pruned stale dist files: ${removed.join(", ")}`);
  }
  return removed;
}

export function restoreLegacyUpdaterCompatSidecars(params = {}) {
  const packageRoot = params.packageRoot ?? DEFAULT_PACKAGE_ROOT;
  const writeFile = params.writeFileSync ?? writeFileSync;
  const makeDirectory = params.mkdirSync ?? mkdirSync;
  const log = params.log ?? console;
  const restored = [];

  for (const sidecar of LEGACY_UPDATE_COMPAT_SIDECARS) {
    // Older npm updater builds verify these exact sidecars after npm has
    // already replaced the package, so generate them independently of prune
    // results.
    const sidecarPath = join(packageRoot, sidecar.path);
    makeDirectory(dirname(sidecarPath), { recursive: true });
    writeFile(sidecarPath, sidecar.content, "utf8");
    restored.push(sidecar.path);
  }

  if (restored.length > 0) {
    log.log(`[postinstall] restored legacy updater compat sidecars: ${restored.join(", ")}`);
  }
  return restored;
}

function dependencySentinelPath(depName) {
  return join("node_modules", ...depName.split("/"), "package.json");
}

const KNOWN_NATIVE_PLATFORMS = new Set([
  "aix",
  "android",
  "darwin",
  "freebsd",
  "linux",
  "openbsd",
  "sunos",
  "win32",
]);
const KNOWN_NATIVE_ARCHES = new Set(["arm", "arm64", "ia32", "ppc64", "riscv64", "s390x", "x64"]);

function packageNameTokens(name) {
  return name
    .toLowerCase()
    .split(/[/@._-]+/u)
    .filter(Boolean);
}

function optionalDependencyTargetsRuntime(name, params = {}) {
  const platform = params.platform ?? process.platform;
  const arch = params.arch ?? process.arch;
  const tokens = new Set(packageNameTokens(name));
  const hasNativePlatformToken = [...tokens].some((token) => KNOWN_NATIVE_PLATFORMS.has(token));
  const hasNativeArchToken = [...tokens].some((token) => KNOWN_NATIVE_ARCHES.has(token));
  return hasNativePlatformToken && hasNativeArchToken && tokens.has(platform) && tokens.has(arch);
}

function runtimeDepNeedsInstall(params) {
  const packageJsonPath = join(params.packageRoot, params.dep.sentinelPath);
  if (!params.existsSync(packageJsonPath)) {
    return true;
  }

  try {
    const packageJson = params.readJson(packageJsonPath);
    return Object.keys(packageJson.optionalDependencies ?? {}).some(
      (childName) =>
        optionalDependencyTargetsRuntime(childName, {
          arch: params.arch,
          platform: params.platform,
        }) && !params.existsSync(join(params.packageRoot, dependencySentinelPath(childName))),
    );
  } catch {
    return true;
  }
}

function collectRuntimeDeps(packageJson) {
  return {
    ...packageJson.dependencies,
    ...packageJson.optionalDependencies,
  };
}

export function discoverBundledPluginRuntimeDeps(params = {}) {
  const extensionsDir = params.extensionsDir ?? DEFAULT_EXTENSIONS_DIR;
  const pathExists = params.existsSync ?? existsSync;
  const readDir = params.readdirSync ?? readdirSync;
  const readJsonFile = params.readJson ?? readJson;
  const deps = new Map(
    BUNDLED_PLUGIN_INSTALL_TARGETS.map((target) => [
      target.name,
      {
        name: target.name,
        version: target.version,
        sentinelPath: dependencySentinelPath(target.name),
        pluginIds: [...(target.pluginIds ?? [])],
      },
    ]),
  );

  if (!pathExists(extensionsDir)) {
    return [...deps.values()].toSorted((a, b) => a.name.localeCompare(b.name));
  }

  for (const entry of readDir(extensionsDir, { withFileTypes: true })) {
    if (!entry.isDirectory()) {
      continue;
    }
    const pluginId = entry.name;
    const packageJsonPath = join(extensionsDir, pluginId, "package.json");
    if (!pathExists(packageJsonPath)) {
      continue;
    }
    try {
      const packageJson = readJsonFile(packageJsonPath);
      for (const [name, version] of Object.entries(collectRuntimeDeps(packageJson))) {
        const existing = deps.get(name);
        if (existing) {
          if (existing.version !== version) {
            continue;
          }
          if (!existing.pluginIds.includes(pluginId)) {
            existing.pluginIds.push(pluginId);
          }
          continue;
        }
        deps.set(name, {
          name,
          version,
          sentinelPath: dependencySentinelPath(name),
          pluginIds: [pluginId],
        });
      }
    } catch {
      // Ignore malformed plugin manifests; runtime will surface those separately.
    }
  }

  return [...deps.values()]
    .map((dep) =>
      Object.assign({}, dep, {
        pluginIds: [...dep.pluginIds].toSorted((a, b) => a.localeCompare(b)),
      }),
    )
    .toSorted((a, b) => a.name.localeCompare(b.name));
}

export function createNestedNpmInstallEnv(env = process.env) {
  const nextEnv = { ...env };
  delete nextEnv.npm_config_global;
  delete nextEnv.npm_config_location;
  delete nextEnv.npm_config_prefix;
  return nextEnv;
}

export function createBundledRuntimeDependencyInstallEnv(env = process.env) {
  return {
    ...createNestedNpmInstallEnv(env),
    npm_config_legacy_peer_deps: "true",
    npm_config_package_lock: "false",
    npm_config_save: "false",
  };
}

export function createBundledRuntimeDependencyInstallArgs(missingSpecs) {
  return ["install", "--ignore-scripts", ...missingSpecs];
}

function shouldEagerInstallBundledPluginDeps(env = process.env) {
  return env?.[EAGER_BUNDLED_PLUGIN_DEPS_ENV]?.trim() === "1";
}

export function applyBaileysEncryptedStreamFinishHotfix(params = {}) {
  const packageRoot = params.packageRoot ?? DEFAULT_PACKAGE_ROOT;
  const pathExists = params.existsSync ?? existsSync;
  const pathLstat = params.lstatSync ?? lstatSync;
  const readFile = params.readFileSync ?? readFileSync;
  const resolveRealPath = params.realpathSync ?? realpathSync;
  const chmodFile = params.chmodSync ?? chmodSync;
  const openFile = params.openSync ?? openSync;
  const closeFile = params.closeSync ?? closeSync;
  const renameFile = params.renameSync ?? renameSync;
  const removePath = params.rmSync ?? rmSync;
  const createTempPath =
    params.createTempPath ??
    ((unsafeTargetPath) =>
      join(
        dirname(unsafeTargetPath),
        `.${basename(unsafeTargetPath)}.openclaw-hotfix-${randomUUID()}`,
      ));
  const writeFile =
    params.writeFileSync ?? ((filePath, value) => writeFileSync(filePath, value, "utf8"));
  const targetPath = join(packageRoot, BAILEYS_MEDIA_FILE);
  const nodeModulesRoot = join(packageRoot, "node_modules");

  function validateTargetPath() {
    if (!pathExists(targetPath)) {
      return { ok: false, reason: "missing" };
    }

    const targetStats = pathLstat(targetPath);
    if (!targetStats.isFile() || targetStats.isSymbolicLink()) {
      return { ok: false, reason: "unsafe_target", targetPath };
    }

    const nodeModulesRootReal = resolveRealPath(nodeModulesRoot);
    const targetPathReal = resolveRealPath(targetPath);
    const relativeTargetPath = relative(nodeModulesRootReal, targetPathReal);
    if (relativeTargetPath.startsWith("..") || isAbsolute(relativeTargetPath)) {
      return { ok: false, reason: "path_escape", targetPath };
    }

    return { ok: true, targetPathReal, mode: targetStats.mode & 0o777 };
  }

  try {
    const initialTargetValidation = validateTargetPath();
    if (!initialTargetValidation.ok) {
      return { applied: false, reason: initialTargetValidation.reason, targetPath };
    }

    const currentText = readFile(targetPath, "utf8");
    let patchedText = currentText;
    let applied = false;

    const encryptedStreamAlreadyPatched =
      patchedText.includes(BAILEYS_MEDIA_HOTFIX_REPLACEMENT) ||
      patchedText.includes(BAILEYS_MEDIA_HOTFIX_SEQUENTIAL_REPLACEMENT) ||
      (BAILEYS_MEDIA_HOTFIX_FINISH_PROMISES_RE.test(patchedText) &&
        (BAILEYS_MEDIA_HOTFIX_PROMISE_ALL_RE.test(patchedText) ||
          BAILEYS_MEDIA_HOTFIX_SEQUENTIAL_AWAITS_RE.test(patchedText)));
    const encryptedStreamPatchable = patchedText.includes(BAILEYS_MEDIA_HOTFIX_NEEDLE);

    let encryptedStreamResolved = encryptedStreamAlreadyPatched;
    if (!encryptedStreamResolved && encryptedStreamPatchable) {
      if (!BAILEYS_MEDIA_ONCE_IMPORT_RE.test(patchedText)) {
        return { applied: false, reason: "missing_once_import", targetPath };
      }
      if (!BAILEYS_MEDIA_ASYNC_CONTEXT_RE.test(patchedText)) {
        return { applied: false, reason: "not_async_context", targetPath };
      }
      patchedText = patchedText.replace(
        BAILEYS_MEDIA_HOTFIX_NEEDLE,
        BAILEYS_MEDIA_HOTFIX_REPLACEMENT,
      );
      applied = true;
      encryptedStreamResolved = true;
    }

    const dispatcherAlreadyPatched = patchedText.includes(
      "...(typeof fetchAgent?.dispatch === 'function' ? { dispatcher: fetchAgent } : {}),",
    );
    const dispatcherPatchable =
      patchedText.includes(BAILEYS_MEDIA_DISPATCHER_NEEDLE) &&
      patchedText.includes(BAILEYS_MEDIA_DISPATCHER_HEADER_NEEDLE);
    let dispatcherResolved = dispatcherAlreadyPatched;

    if (!dispatcherResolved && dispatcherPatchable) {
      patchedText = patchedText
        .replace(BAILEYS_MEDIA_DISPATCHER_NEEDLE, BAILEYS_MEDIA_DISPATCHER_REPLACEMENT)
        .replace(
          BAILEYS_MEDIA_DISPATCHER_HEADER_NEEDLE,
          BAILEYS_MEDIA_DISPATCHER_HEADER_REPLACEMENT,
        );
      applied = true;
      dispatcherResolved = true;
    }

    if (!dispatcherResolved) {
      return { applied: false, reason: "unexpected_content", targetPath };
    }

    if (!applied) {
      return { applied: false, reason: "already_patched" };
    }
    const tempPath = createTempPath(targetPath);
    const tempFd = openFile(tempPath, "wx", initialTargetValidation.mode);
    let tempFdClosed = false;
    try {
      writeFile(tempFd, patchedText, "utf8");
      closeFile(tempFd);
      tempFdClosed = true;
      const finalTargetValidation = validateTargetPath();
      if (!finalTargetValidation.ok) {
        return { applied: false, reason: finalTargetValidation.reason, targetPath };
      }
      renameFile(tempPath, targetPath);
      chmodFile(targetPath, initialTargetValidation.mode);
    } finally {
      if (!tempFdClosed) {
        try {
          closeFile(tempFd);
        } catch {
          // ignore failed-open cleanup
        }
      }
      removePath(tempPath, { force: true });
    }
    return { applied: true, reason: "patched", targetPath };
  } catch (error) {
    return {
      applied: false,
      reason: "error",
      targetPath,
      error: error instanceof Error ? error.message : String(error),
    };
  }
}

function applyBundledPluginRuntimeHotfixes(params = {}) {
  const log = params.log ?? console;
  const baileysResult = applyBaileysEncryptedStreamFinishHotfix(params);
  if (baileysResult.applied) {
    log.log("[postinstall] patched @whiskeysockets/baileys runtime hotfixes");
    return;
  }
  if (baileysResult.reason !== "missing" && baileysResult.reason !== "already_patched") {
    log.warn(
      `[postinstall] could not patch @whiskeysockets/baileys runtime hotfixes: ${baileysResult.reason}`,
    );
  }
}

export function isSourceCheckoutRoot(params) {
  const pathExists = params.existsSync ?? existsSync;
  return (
    (pathExists(join(params.packageRoot, ".git")) ||
      pathExists(join(params.packageRoot, "pnpm-workspace.yaml"))) &&
    pathExists(join(params.packageRoot, "src")) &&
    pathExists(join(params.packageRoot, "extensions"))
  );
}

export function pruneBundledPluginSourceNodeModules(params = {}) {
  const extensionsDir = params.extensionsDir ?? join(DEFAULT_PACKAGE_ROOT, "extensions");
  const pathExists = params.existsSync ?? existsSync;
  const readDir = params.readdirSync ?? readdirSync;
  const removePath = params.rmSync ?? rmSync;

  if (!pathExists(extensionsDir)) {
    return;
  }

  for (const entry of readDir(extensionsDir, { withFileTypes: true })) {
    if (!entry.isDirectory() || entry.isSymbolicLink()) {
      continue;
    }

    const pluginDir = join(extensionsDir, entry.name);
    if (!pathExists(join(pluginDir, "package.json"))) {
      continue;
    }

    removePath(join(pluginDir, "node_modules"), { recursive: true, force: true });
  }
}

function shouldRunBundledPluginPostinstall(params) {
  if (params.env?.[DISABLE_POSTINSTALL_ENV]?.trim()) {
    return false;
  }
  if (!params.existsSync(params.extensionsDir)) {
    return false;
  }
  return true;
}

export function runBundledPluginPostinstall(params = {}) {
  const env = params.env ?? process.env;
  const packageRoot = params.packageRoot ?? DEFAULT_PACKAGE_ROOT;
  const extensionsDir = params.extensionsDir ?? join(packageRoot, "dist", "extensions");
  const spawn = params.spawnSync ?? spawnSync;
  const pathExists = params.existsSync ?? existsSync;
  const log = params.log ?? console;
  if (env?.[DISABLE_POSTINSTALL_ENV]?.trim()) {
    return;
  }
  if (isSourceCheckoutRoot({ packageRoot, existsSync: pathExists })) {
    try {
      pruneBundledPluginSourceNodeModules({
        extensionsDir: join(packageRoot, "extensions"),
        existsSync: pathExists,
        readdirSync: params.readdirSync,
        rmSync: params.rmSync,
      });
    } catch (e) {
      log.warn(`[postinstall] could not prune bundled plugin source node_modules: ${String(e)}`);
    }
    applyBundledPluginRuntimeHotfixes({
      packageRoot,
      existsSync: pathExists,
      readFileSync: params.readFileSync,
      writeFileSync: params.writeFileSync,
      log,
    });
    return;
  }
  const prunedDistFiles = pruneInstalledPackageDist({
    packageRoot,
    existsSync: pathExists,
    readFileSync: params.readFileSync,
    readdirSync: params.readdirSync,
    rmSync: params.rmSync,
    log,
  });
  restoreLegacyUpdaterCompatSidecars({
    packageRoot,
    removedFiles: prunedDistFiles,
    mkdirSync: params.mkdirSync,
    writeFileSync: params.writeFileSync,
    log,
  });
  if (
    !shouldRunBundledPluginPostinstall({
      env,
      extensionsDir,
      packageRoot,
      existsSync: pathExists,
    })
  ) {
    return;
  }
  if (!shouldEagerInstallBundledPluginDeps(env)) {
    applyBundledPluginRuntimeHotfixes({
      packageRoot,
      existsSync: pathExists,
      readFileSync: params.readFileSync,
      writeFileSync: params.writeFileSync,
      log,
    });
    return;
  }
  const runtimeDeps =
    params.runtimeDeps ??
    discoverBundledPluginRuntimeDeps({ extensionsDir, existsSync: pathExists });
  const missingSpecs = runtimeDeps
    .filter((dep) =>
      runtimeDepNeedsInstall({
        dep,
        existsSync: pathExists,
        packageRoot,
        arch: params.arch,
        platform: params.platform,
        readJson: params.readJson ?? readJson,
      }),
    )
    .map((dep) => `${dep.name}@${dep.version}`);

  if (missingSpecs.length === 0) {
    applyBundledPluginRuntimeHotfixes({
      packageRoot,
      existsSync: pathExists,
      readFileSync: params.readFileSync,
      writeFileSync: params.writeFileSync,
      log,
    });
    return;
  }

  try {
    const installEnv = createBundledRuntimeDependencyInstallEnv(env);
    const npmRunner =
      params.npmRunner ??
      resolveNpmRunner({
        env: installEnv,
        execPath: params.execPath,
        existsSync: pathExists,
        platform: params.platform,
        comSpec: params.comSpec,
        npmArgs: createBundledRuntimeDependencyInstallArgs(missingSpecs),
      });
    const result = spawn(npmRunner.command, npmRunner.args, {
      cwd: packageRoot,
      encoding: "utf8",
      env: npmRunner.env ?? installEnv,
      stdio: "pipe",
      shell: npmRunner.shell,
      windowsVerbatimArguments: npmRunner.windowsVerbatimArguments,
    });
    if (result.status !== 0) {
      const output = [result.stderr, result.stdout].filter(Boolean).join("\n").trim();
      throw new Error(output || "npm install failed");
    }
    log.log(`[postinstall] installed bundled plugin deps: ${missingSpecs.join(", ")}`);
  } catch (e) {
    // Non-fatal: gateway will surface the missing dep via doctor.
    log.warn(`[postinstall] could not install bundled plugin deps: ${String(e)}`);
  }

  applyBundledPluginRuntimeHotfixes({
    packageRoot,
    existsSync: pathExists,
    readFileSync: params.readFileSync,
    writeFileSync: params.writeFileSync,
    log,
  });
}

export function isDirectPostinstallInvocation(params = {}) {
  const entryPath = params.entryPath ?? process.argv[1];
  if (!entryPath) {
    return false;
  }
  const modulePath = params.modulePath ?? fileURLToPath(import.meta.url);
  const resolveRealPath = params.realpathSync ?? realpathSync;
  try {
    return resolveRealPath(entryPath) === resolveRealPath(modulePath);
  } catch {
    return pathToFileURL(entryPath).href === pathToFileURL(modulePath).href;
  }
}

if (isDirectPostinstallInvocation()) {
  runBundledPluginPostinstall();
}

[Dauer der Verarbeitung: 0.29 Sekunden, vorverarbeitet 2026-04-27]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge