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


Quelle  release-check.ts

  Sprache: JAVA
 

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

#!/usr/bin/env -S node --import tsx

import { execFileSync, execSync } from "node:child_process";
import {
  existsSync,
  mkdtempSync,
  mkdirSync,
  realpathSync,
  readdirSync,
  readFileSync,
  rmSync,
  writeFileSync,
} from "node:fs";
import { tmpdir } from "node:os";
import { dirname, join, resolve } from "node:path";
import { pathToFileURL } from "node:url";
import {
  PACKAGE_DIST_INVENTORY_RELATIVE_PATH,
  writePackageDistInventory,
} from "../src/infra/package-dist-inventory.ts";
import {
  resolveBundledRuntimeDependencyInstallRoot,
  resolveBundledRuntimeDependencyPackageInstallRoot,
} from "../src/plugins/bundled-runtime-deps.ts";
import {
  collectBundledExtensionManifestErrors,
  type BundledExtension,
  type ExtensionPackageJson as PackageJson,
} from "./lib/bundled-extension-manifest.ts";
import { listBundledPluginPackArtifacts } from "./lib/bundled-plugin-build-entries.mjs";
import {
  collectBuiltBundledPluginStagedRuntimeDependencyErrors,
  collectBundledPluginRootRuntimeMirrorErrors,
  collectBundledPluginRuntimeDependencySpecs,
  collectRootDistBundledRuntimeMirrors,
} from "./lib/bundled-plugin-root-runtime-mirrors.mjs";
import { collectPackUnpackedSizeErrors as collectNpmPackUnpackedSizeErrors } from "./lib/npm-pack-budget.mjs";
import { listPluginSdkDistArtifacts } from "./lib/plugin-sdk-entries.mjs";
import {
  runInstalledWorkspaceBootstrapSmoke,
  WORKSPACE_TEMPLATE_PACK_PATHS,
} from "./lib/workspace-bootstrap-smoke.mjs";
import { discoverBundledPluginRuntimeDeps } from "./postinstall-bundled-plugins.mjs";
import { listStaticExtensionAssetOutputs } from "./runtime-postbuild.mjs";
import { sparkleBuildFloorsFromShortVersion, type SparkleBuildFloors } from "./sparkle-build.ts";
import { buildCmdExeCommandLine } from "./windows-cmd-helpers.mjs";

export { collectBundledExtensionManifestErrors } from "./lib/bundled-extension-manifest.ts";
export {
  collectBuiltBundledPluginStagedRuntimeDependencyErrors,
  collectBundledPluginRootRuntimeMirrorErrors,
  collectRootDistBundledRuntimeMirrors,
  packageNameFromSpecifier,
} from "./lib/bundled-plugin-root-runtime-mirrors.mjs";

type PackFile = { path: string };
type PackResult = { files?: PackFile[]; filename?: string; unpackedSize?: number };

const requiredPathGroups = [
  PACKAGE_DIST_INVENTORY_RELATIVE_PATH,
  ["dist/index.js", "dist/index.mjs"],
  ["dist/entry.js", "dist/entry.mjs"],
  ...listPluginSdkDistArtifacts(),
  ...listBundledPluginPackArtifacts(),
  ...listStaticExtensionAssetOutputs(),
  ...WORKSPACE_TEMPLATE_PACK_PATHS,
  "scripts/npm-runner.mjs",
  "scripts/preinstall-package-manager-warning.mjs",
  "scripts/postinstall-bundled-plugins.mjs",
  "dist/plugin-sdk/compat.js",
  "dist/plugin-sdk/root-alias.cjs",
  "dist/build-info.json",
  "dist/channel-catalog.json",
  "dist/control-ui/index.html",
];
const forbiddenPrefixes = [
  "dist-runtime/",
  "dist/OpenClaw.app/",
  "dist/extensions/qa-channel/",
  "dist/extensions/qa-lab/",
  "dist/plugin-sdk/extensions/qa-lab/",
  "dist/plugin-sdk/qa-lab.",
  "dist/plugin-sdk/qa-runtime.",
  "dist/plugin-sdk/src/plugin-sdk/qa-lab.d.ts",
  "dist/plugin-sdk/src/plugin-sdk/qa-runtime.d.ts",
  "dist/qa-runtime-",
  "dist/plugin-sdk/.tsbuildinfo",
  "docs/.generated/",
  "qa/",
];
const forbiddenPrivateQaContentMarkers = [
  "//#region extensions/qa-lab/",
  "qa-channel/runtime-api.js",
  "qa-lab/cli.js",
  "qa-lab/runtime-api.js",
] as const;
const forbiddenPrivateQaContentScanPrefixes = ["dist/"] as const;
const appcastPath = resolve("appcast.xml");
const laneBuildMin = 1_000_000_000;
const laneFloorAdoptionDateKey = 20260227;
const SAFE_UNIX_SMOKE_PATH = "/usr/bin:/bin";
export const PACKED_CLI_SMOKE_COMMANDS = [
  ["--help"],
  ["status", "--json", "--timeout", "1"],
  ["config", "schema"],
  ["models", "list", "--provider", "amazon-bedrock"],
] as const;

function collectBundledExtensions(): BundledExtension[] {
  const extensionsDir = resolve("extensions");
  const entries = readdirSync(extensionsDir, { withFileTypes: true }).filter((entry) =>
    entry.isDirectory(),
  );

  return entries.flatMap((entry) => {
    const packagePath = join(extensionsDir, entry.name, "package.json");
    try {
      return [
        {
          id: entry.name,
          packageJson: JSON.parse(readFileSync(packagePath, "utf8")) as PackageJson,
        },
      ];
    } catch {
      return [];
    }
  });
}

function checkBundledExtensionMetadata() {
  const extensions = collectBundledExtensions();
  const manifestErrors = collectBundledExtensionManifestErrors(extensions);
  const rootPackage = JSON.parse(readFileSync(resolve("package.json"), "utf8")) as {
    dependencies?: Record<string, string>;
    optionalDependencies?: Record<string, string>;
  };
  const bundledRuntimeDependencySpecs = collectBundledPluginRuntimeDependencySpecs(
    resolve("extensions"),
  );
  const requiredRootMirrors = collectRootDistBundledRuntimeMirrors({
    bundledRuntimeDependencySpecs,
    distDir: resolve("dist"),
  });
  const rootMirrorErrors = collectBundledPluginRootRuntimeMirrorErrors({
    bundledRuntimeDependencySpecs,
    requiredRootMirrors,
    rootPackageJson: rootPackage,
  });
  const builtArtifactErrors = collectBuiltBundledPluginStagedRuntimeDependencyErrors({
    bundledPluginsDir: resolve("dist/extensions"),
  });
  const errors = [...manifestErrors, ...rootMirrorErrors, ...builtArtifactErrors];
  if (errors.length > 0) {
    console.error("release-check: bundled extension manifest validation failed:");
    for (const error of errors) {
      console.error(`  - ${error}`);
    }
    process.exit(1);
  }
}

function runPackDry(): PackResult[] {
  const raw = execSync("npm pack --dry-run --json --ignore-scripts", {
    encoding: "utf8",
    stdio: ["ignore", "pipe", "pipe"],
    maxBuffer: 1024 * 1024 * 100,
  });
  return JSON.parse(raw) as PackResult[];
}

function runPack(packDestination: string): PackResult[] {
  const raw = execFileSync(
    "npm",
    ["pack", "--json", "--ignore-scripts", "--pack-destination", packDestination],
    {
      encoding: "utf8",
      stdio: ["ignore", "pipe", "pipe"],
      maxBuffer: 1024 * 1024 * 100,
    },
  );
  return JSON.parse(raw) as PackResult[];
}

function resolvePackedTarballPath(packDestination: string, results: PackResult[]): string {
  const filenames = results
    .map((entry) => entry.filename)
    .filter((filename): filename is string => typeof filename === "string" && filename.length > 0);
  if (filenames.length !== 1) {
    throw new Error(
      `release-check: npm pack produced ${filenames.length} tarballs; expected exactly one.`,
    );
  }
  return resolve(packDestination, filenames[0]);
}

function installPackedTarball(prefixDir: string, tarballPath: string, cwd: string): void {
  execFileSync(
    "npm",
    [
      "install",
      "-g",
      "--prefix",
      prefixDir,
      "--ignore-scripts",
      "--no-audit",
      "--no-fund",
      tarballPath,
    ],
    {
      cwd,
      encoding: "utf8",
      stdio: "inherit",
    },
  );
}

function resolveGlobalRoot(prefixDir: string, cwd: string): string {
  return execFileSync("npm", ["root", "-g", "--prefix", prefixDir], {
    cwd,
    encoding: "utf8",
    stdio: ["ignore", "pipe", "pipe"],
  }).trim();
}

function resolveInstalledBinaryPath(prefixDir: string): string {
  return process.platform === "win32"
    ? join(prefixDir, "openclaw.cmd")
    : join(prefixDir, "bin", "openclaw");
}

export function createPackedBundledPluginPostinstallEnv(
  env: NodeJS.ProcessEnv = process.env,
): NodeJS.ProcessEnv {
  return {
    ...env,
    OPENCLAW_DISABLE_BUNDLED_ENTRY_SOURCE_FALLBACK: "1",
  };
}

export function createPackedCliSmokeEnv(
  env: NodeJS.ProcessEnv,
  overrides: NodeJS.ProcessEnv = {},
): NodeJS.ProcessEnv {
  const allowlistedEnvEntries = [
    "HOME",
    "TMPDIR",
    "TMP",
    "TEMP",
    "SystemRoot",
    "ComSpec",
    "PATHEXT",
    "WINDIR",
  ] as const;
  const windowsRoot = env.SystemRoot ?? env.WINDIR ?? "C:\\Windows";
  const nodeBinDir = dirname(process.execPath);
  const trustedCmdPath = join(windowsRoot, "System32", "cmd.exe");
  const safePath =
    process.platform === "win32"
      ? `${nodeBinDir};${windowsRoot}\\System32;${windowsRoot}`
      : `${nodeBinDir}:${SAFE_UNIX_SMOKE_PATH}`;
  const homeDir = overrides.HOME ?? env.HOME ?? overrides.USERPROFILE ?? env.USERPROFILE ?? "";

  return {
    ...Object.fromEntries(
      allowlistedEnvEntries.flatMap((key) => {
        const value = env[key];
        return typeof value === "string" && value.length > 0 ? [[key, value]] : [];
      }),
    ),
    PATH: safePath,
    HOME: homeDir,
    USERPROFILE: homeDir,
    ComSpec: trustedCmdPath,
    APPDATA: homeDir ? join(homeDir, "AppData", "Roaming") : undefined,
    LOCALAPPDATA: homeDir ? join(homeDir, "AppData", "Local") : undefined,
    AWS_EC2_METADATA_DISABLED: "true",
    AWS_SHARED_CREDENTIALS_FILE: homeDir ? join(homeDir, ".aws", "credentials") : undefined,
    AWS_CONFIG_FILE: homeDir ? join(homeDir, ".aws", "config") : undefined,
    OPENCLAW_DISABLE_BUNDLED_ENTRY_SOURCE_FALLBACK: "1",
    OPENCLAW_NO_ONBOARD: "1",
    OPENCLAW_SUPPRESS_NOTES: "1",
    ...overrides,
  };
}

function runPackedBundledPluginPostinstall(packageRoot: string): void {
  execFileSync(process.execPath, [join(packageRoot, "scripts/postinstall-bundled-plugins.mjs")], {
    cwd: packageRoot,
    stdio: "inherit",
    env: createPackedBundledPluginPostinstallEnv(),
  });
}

export function collectInstalledBundledPluginRuntimeDepErrors(packageRoot: string): string[] {
  const extensionsDir = join(packageRoot, "dist", "extensions");
  if (!existsSync(extensionsDir)) {
    return [];
  }
  const runtimeDeps = discoverBundledPluginRuntimeDeps({ extensionsDir });
  return runtimeDeps
    .filter((dep) => !existsSync(join(packageRoot, dep.sentinelPath)))
    .map((dep) => {
      const owners = dep.pluginIds.length > 0 ? dep.pluginIds.join(", ") : "unknown";
      return `bundled plugin runtime dependency '${dep.name}@${dep.version}' (owners: ${owners}) is missing at ${dep.sentinelPath}.`;
    })
    .toSorted((left, right) => left.localeCompare(right));
}

function bundledRuntimeDependencySentinelPath(
  packageRoot: string,
  pluginId: string,
  dependencyName: string,
): string {
  return join(
    packageRoot,
    "dist",
    "extensions",
    pluginId,
    "node_modules",
    ...dependencyName.split("/"),
    "package.json",
  );
}

export function bundledRuntimeDependencySentinelCandidates(
  packageRoot: string,
  pluginId: string,
  dependencyName: string,
  env: NodeJS.ProcessEnv = process.env,
): string[] {
  const dependencyParts = dependencyName.split("/");
  const packageRoots = [
    packageRoot,
    (() => {
      try {
        return realpathSync(packageRoot);
      } catch {
        return packageRoot;
      }
    })(),
  ];
  const runtimeRoots = packageRoots.flatMap((root) => [
    resolveBundledRuntimeDependencyPackageInstallRoot(root, { env }),
    resolveBundledRuntimeDependencyInstallRoot(join(root, "dist", "extensions", pluginId), {
      env,
    }),
  ]);
  return [
    bundledRuntimeDependencySentinelPath(packageRoot, pluginId, dependencyName),
    join(packageRoot, "dist", "extensions", "node_modules", ...dependencyParts, "package.json"),
    join(packageRoot, "node_modules", ...dependencyParts, "package.json"),
    ...runtimeRoots.map((root) => join(root, "node_modules", ...dependencyParts, "package.json")),
  ].filter((candidate, index, candidates) => candidates.indexOf(candidate) === index);
}

function assertBundledRuntimeDependencyAbsent(params: {
  packageRoot: string;
  pluginId: string;
  dependencyName: string;
  env?: NodeJS.ProcessEnv;
}): void {
  const sentinelPath = bundledRuntimeDependencySentinelCandidates(
    params.packageRoot,
    params.pluginId,
    params.dependencyName,
    params.env,
  ).find((candidate) => existsSync(candidate));
  if (sentinelPath) {
    throw new Error(
      `release-check: ${params.pluginId} runtime dependency ${params.dependencyName} was installed before plugin activation (${sentinelPath}).`,
    );
  }
}

function assertBundledRuntimeDependencyPresent(params: {
  packageRoot: string;
  pluginId: string;
  dependencyName: string;
  env?: NodeJS.ProcessEnv;
}): void {
  const sentinelPath = bundledRuntimeDependencySentinelCandidates(
    params.packageRoot,
    params.pluginId,
    params.dependencyName,
    params.env,
  ).find((candidate) => existsSync(candidate));
  if (sentinelPath) {
    return;
  }
  throw new Error(
    `release-check: ${params.pluginId} runtime dependency ${params.dependencyName} was not installed during plugin activation.`,
  );
}

function writePackedBundledPluginActivationConfig(homeDir: string): void {
  const configPath = join(homeDir, ".openclaw", "openclaw.json");
  mkdirSync(join(homeDir, ".openclaw"), { recursive: true });
  writeFileSync(
    configPath,
    `${JSON.stringify(
      {
        agents: {
          defaults: {
            model: { primary: "openai/gpt-4.1-mini" },
          },
        },
        channels: {
          feishu: {
            enabled: true,
          },
        },
        models: {
          providers: {
            openai: {
              apiKey: "sk-openclaw-release-check",
              baseUrl: "https://api.openai.com/v1",
              models: [],
            },
          },
        },
        plugins: {
          enabled: true,
          entries: {
            feishu: {
              enabled: true,
            },
          },
        },
      },
      null,
      2,
    )}\n`,
    "utf8",
  );
}

function runPackedBundledPluginActivationSmoke(packageRoot: string, tmpRoot: string): void {
  const lazyDeps = [
    { pluginId: "browser", dependencyName: "playwright-core" },
    { pluginId: "feishu", dependencyName: "@larksuiteoapi/node-sdk" },
  ] as const;

  const homeDir = join(tmpRoot, "activation-home");
  mkdirSync(homeDir, { recursive: true });
  const env = createPackedCliSmokeEnv(process.env, {
    HOME: homeDir,
    OPENAI_API_KEY: "sk-openclaw-release-check",
  });
  for (const dep of lazyDeps) {
    assertBundledRuntimeDependencyAbsent({ packageRoot, env, ...dep });
  }

  writePackedBundledPluginActivationConfig(homeDir);
  execFileSync(process.execPath, [join(packageRoot, "openclaw.mjs"), "plugins", "doctor"], {
    cwd: packageRoot,
    stdio: "inherit",
    env,
  });

  for (const dep of lazyDeps) {
    assertBundledRuntimeDependencyPresent({ packageRoot, env, ...dep });
  }
}

function runPackedCliSmoke(params: {
  prefixDir: string;
  cwd: string;
  homeDir: string;
  stateDir: string;
}): void {
  const binaryPath = resolveInstalledBinaryPath(params.prefixDir);
  const env = createPackedCliSmokeEnv(process.env, {
    HOME: params.homeDir,
    OPENCLAW_STATE_DIR: params.stateDir,
    OPENAI_API_KEY: "sk-openclaw-release-check",
  });
  const windowsRoot = env.SystemRoot ?? env.WINDIR ?? "C:\\Windows";
  const trustedCmdPath = join(windowsRoot, "System32", "cmd.exe");

  for (const args of PACKED_CLI_SMOKE_COMMANDS) {
    if (process.platform === "win32") {
      execFileSync(
        trustedCmdPath,
        ["/d", "/s", "/c", buildCmdExeCommandLine(binaryPath, [...args])],
        {
          cwd: params.cwd,
          stdio: "inherit",
          env,
          shell: false,
          windowsVerbatimArguments: true,
        },
      );
      continue;
    }
    execFileSync(binaryPath, [...args], {
      cwd: params.cwd,
      stdio: "inherit",
      env,
      shell: false,
    });
  }
}

function runPackedBundledChannelEntrySmoke(): void {
  const tmpRoot = mkdtempSync(join(tmpdir(), "openclaw-release-pack-smoke-"));
  try {
    const packDir = join(tmpRoot, "pack");
    mkdirSync(packDir);

    const packResults = runPack(packDir);
    const tarballPath = resolvePackedTarballPath(packDir, packResults);
    const prefixDir = join(tmpRoot, "prefix");
    installPackedTarball(prefixDir, tarballPath, tmpRoot);

    const packageRoot = join(resolveGlobalRoot(prefixDir, tmpRoot), "openclaw");
    const homeDir = join(tmpRoot, "home");
    const stateDir = join(tmpRoot, "state");
    mkdirSync(homeDir, { recursive: true });
    runPackedCliSmoke({
      prefixDir,
      cwd: packageRoot,
      homeDir,
      stateDir,
    });
    runPackedBundledPluginPostinstall(packageRoot);
    runPackedBundledPluginActivationSmoke(packageRoot, tmpRoot);
    execFileSync(
      process.execPath,
      [
        resolve("scripts/test-built-bundled-channel-entry-smoke.mjs"),
        "--package-root",
        packageRoot,
      ],
      {
        stdio: "inherit",
        env: {
          ...process.env,
          OPENCLAW_DISABLE_BUNDLED_ENTRY_SOURCE_FALLBACK: "1",
        },
      },
    );

    execFileSync(
      process.execPath,
      [join(packageRoot, "openclaw.mjs"), "completion", "--write-state"],
      {
        cwd: packageRoot,
        stdio: "inherit",
        env: {
          ...process.env,
          HOME: homeDir,
          OPENCLAW_STATE_DIR: stateDir,
          OPENCLAW_SUPPRESS_NOTES: "1",
          OPENCLAW_DISABLE_BUNDLED_ENTRY_SOURCE_FALLBACK: "1",
        },
      },
    );

    const completionFiles = readdirSync(join(stateDir, "completions")).filter(
      (entry) => !entry.startsWith("."),
    );
    if (completionFiles.length === 0) {
      throw new Error("release-check: packed completion smoke produced no completion files.");
    }

    runInstalledWorkspaceBootstrapSmoke({ packageRoot });
  } finally {
    rmSync(tmpRoot, { recursive: true, force: true });
  }
}

export function collectMissingPackPaths(paths: Iterable<string>): string[] {
  const available = new Set(paths);
  return requiredPathGroups
    .flatMap((group) => {
      if (Array.isArray(group)) {
        return group.some((path) => available.has(path)) ? [] : [group.join(" or ")];
      }
      return available.has(group) ? [] : [group];
    })
    .toSorted((left, right) => left.localeCompare(right));
}

export function collectForbiddenPackPaths(paths: Iterable<string>): string[] {
  return [...paths]
    .filter(
      (path) =>
        forbiddenPrefixes.some((prefix) => path.startsWith(prefix)) ||
        /(^|\/)\.openclaw-runtime-deps-[^/]+(\/|$)/u.test(path) ||
        path.endsWith("/.openclaw-runtime-deps-stamp.json") ||
        path.includes("node_modules/"),
    )
    .toSorted((left, right) => left.localeCompare(right));
}

export function collectForbiddenPackContentPaths(
  paths: Iterable<string>,
  rootDir = process.cwd(),
): string[] {
  const textPathPattern = /\.(?:[cm]?js|d\.ts|json|md|mjs|cjs)$/u;
  return [...paths]
    .filter((packedPath) => {
      if (packedPath === PACKAGE_DIST_INVENTORY_RELATIVE_PATH) {
        return false;
      }
      if (!forbiddenPrivateQaContentScanPrefixes.some((prefix) => packedPath.startsWith(prefix))) {
        return false;
      }
      if (!textPathPattern.test(packedPath)) {
        return false;
      }
      let content: string;
      try {
        content = readFileSync(resolve(rootDir, packedPath), "utf8");
      } catch {
        return false;
      }
      return forbiddenPrivateQaContentMarkers.some((marker) => content.includes(marker));
    })
    .toSorted((left, right) => left.localeCompare(right));
}

export { collectPackUnpackedSizeErrors } from "./lib/npm-pack-budget.mjs";

function extractTag(item: string, tag: string): string | null {
  const escapedTag = tag.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
  const regex = new RegExp(`<${escapedTag}>([^<]+)</${escapedTag}>`);
  return regex.exec(item)?.[1]?.trim() ?? null;
}

export function collectAppcastSparkleVersionErrors(xml: string): string[] {
  const itemMatches = [...xml.matchAll(/<item>([\s\S]*?)<\/item>/g)];
  const errors: string[] = [];
  const calverItems: Array<{ title: string; sparkleBuild: number; floors: SparkleBuildFloors }> =
    [];

  if (itemMatches.length === 0) {
    errors.push("appcast.xml contains no <item> entries.");
  }

  for (const [, item] of itemMatches) {
    const title = extractTag(item, "title") ?? "unknown";
    const shortVersion = extractTag(item, "sparkle:shortVersionString");
    const sparkleVersion = extractTag(item, "sparkle:version");

    if (!sparkleVersion) {
      errors.push(`appcast item '${title}' is missing sparkle:version.`);
      continue;
    }
    if (!/^[0-9]+$/.test(sparkleVersion)) {
      errors.push(`appcast item '${title}' has non-numeric sparkle:version '${sparkleVersion}'.`);
      continue;
    }

    if (!shortVersion) {
      continue;
    }
    const floors = sparkleBuildFloorsFromShortVersion(shortVersion);
    if (floors === null) {
      continue;
    }

    calverItems.push({ title, sparkleBuild: Number(sparkleVersion), floors });
  }

  const observedLaneAdoptionDateKey = calverItems
    .filter((item) => item.sparkleBuild >= laneBuildMin)
    .map((item) => item.floors.dateKey)
    .toSorted((a, b) => a - b)[0];
  const effectiveLaneAdoptionDateKey =
    typeof observedLaneAdoptionDateKey === "number"
      ? Math.min(observedLaneAdoptionDateKey, laneFloorAdoptionDateKey)
      : laneFloorAdoptionDateKey;

  for (const item of calverItems) {
    const expectLaneFloor =
      item.sparkleBuild >= laneBuildMin || item.floors.dateKey >= effectiveLaneAdoptionDateKey;
    const floor = expectLaneFloor ? item.floors.laneFloor : item.floors.legacyFloor;
    if (item.sparkleBuild < floor) {
      const floorLabel = expectLaneFloor ? "lane floor" : "legacy floor";
      errors.push(
        `appcast item '${item.title}' has sparkle:version ${item.sparkleBuild} below ${floorLabel} ${floor}.`,
      );
    }
  }

  return errors;
}

function checkAppcastSparkleVersions() {
  const xml = readFileSync(appcastPath, "utf8");
  const errors = collectAppcastSparkleVersionErrors(xml);
  if (errors.length > 0) {
    console.error("release-check: appcast sparkle version validation failed:");
    for (const error of errors) {
      console.error(`  - ${error}`);
    }
    process.exit(1);
  }
}

// Critical functions that channel extension plugins import from openclaw/plugin-sdk.
// If any are missing from the compiled output, plugins crash at runtime (#27569).
const requiredPluginSdkExports = [
  "isDangerousNameMatchingEnabled",
  "createAccountListHelpers",
  "buildAgentMediaPayload",
  "createReplyPrefixOptions",
  "createTypingCallbacks",
  "logInboundDrop",
  "logTypingFailure",
  "buildPendingHistoryContextFromMap",
  "clearHistoryEntriesIfEnabled",
  "recordPendingHistoryEntryIfEnabled",
  "resolveControlCommandGate",
  "resolveDmGroupAccessWithLists",
  "resolveAllowlistProviderRuntimeGroupPolicy",
  "resolveDefaultGroupPolicy",
  "resolveChannelMediaMaxBytes",
  "warnMissingProviderGroupPolicyFallbackOnce",
  "emptyPluginConfigSchema",
  "onDiagnosticEvent",
  "normalizePluginHttpPath",
  "registerPluginHttpRoute",
  "DEFAULT_ACCOUNT_ID",
  "DEFAULT_GROUP_HISTORY_LIMIT",
];

async function collectDistPluginSdkExports(): Promise<Set<string>> {
  const pluginSdkDir = resolve("dist", "plugin-sdk");
  let entries: string[];
  try {
    entries = readdirSync(pluginSdkDir)
      .filter((entry) => entry.endsWith(".js"))
      .toSorted();
  } catch {
    console.error("release-check: dist/plugin-sdk directory not found (build missing?).");
    process.exit(1);
    return new Set();
  }

  const exportedNames = new Set<string>();
  for (const entry of entries) {
    const content = readFileSync(join(pluginSdkDir, entry), "utf8");
    for (const match of content.matchAll(/export\s*\{([^}]+)\}(?:\s*from\s*["'][^"']+["'])?/g)) {
      const names = match[1]?.split(",") ?? [];
      for (const name of names) {
        const parts = name.trim().split(/\s+as\s+/);
        const exportName = (parts[parts.length - 1] || "").trim();
        if (exportName) {
          exportedNames.add(exportName);
        }
      }
    }
    for (const match of content.matchAll(
      /export\s+(?:const|function|class|let|var)\s+([A-Za-z0-9_$]+)/g,
    )) {
      const exportName = match[1]?.trim();
      if (exportName) {
        exportedNames.add(exportName);
      }
    }
  }

  return exportedNames;
}

async function checkPluginSdkExports() {
  const exportedNames = await collectDistPluginSdkExports();
  const missingExports = requiredPluginSdkExports.filter((name) => !exportedNames.has(name));
  if (missingExports.length > 0) {
    console.error("release-check: missing critical plugin-sdk exports (#27569):");
    for (const name of missingExports) {
      console.error(`  - ${name}`);
    }
    process.exit(1);
  }
}

async function main() {
  checkAppcastSparkleVersions();
  await checkPluginSdkExports();
  checkBundledExtensionMetadata();
  await writePackageDistInventory(process.cwd());

  const results = runPackDry();
  const files = results.flatMap((entry) => entry.files ?? []);
  const paths = new Set(files.map((file) => file.path));

  const missing = requiredPathGroups
    .flatMap((group) => {
      if (Array.isArray(group)) {
        return group.some((path) => paths.has(path)) ? [] : [group.join(" or ")];
      }
      return paths.has(group) ? [] : [group];
    })
    .toSorted((left, right) => left.localeCompare(right));
  const forbidden = collectForbiddenPackPaths(paths);
  const forbiddenContent = collectForbiddenPackContentPaths(paths);
  const sizeErrors = collectNpmPackUnpackedSizeErrors(results);

  if (
    missing.length > 0 ||
    forbidden.length > 0 ||
    forbiddenContent.length > 0 ||
    sizeErrors.length > 0
  ) {
    if (missing.length > 0) {
      console.error("release-check: missing files in npm pack:");
      for (const path of missing) {
        console.error(`  - ${path}`);
      }
      if (
        missing.some(
          (path) =>
            path === "dist/build-info.json" ||
            path === "dist/control-ui/index.html" ||
            path.startsWith("dist/"),
        )
      ) {
        console.error(
          "release-check: build artifacts are missing. Run `pnpm build` before `pnpm release:check`.",
        );
      }
    }
    if (forbidden.length > 0) {
      console.error("release-check: forbidden files in npm pack:");
      for (const path of forbidden) {
        console.error(`  - ${path}`);
      }
    }
    if (forbiddenContent.length > 0) {
      console.error("release-check: forbidden private QA markers in npm pack:");
      for (const path of forbiddenContent) {
        console.error(`  - ${path}`);
      }
    }
    if (sizeErrors.length > 0) {
      console.error("release-check: npm pack unpacked size budget exceeded:");
      for (const error of sizeErrors) {
        console.error(`  - ${error}`);
      }
    }
    process.exit(1);
  }

  runPackedBundledChannelEntrySmoke();

  console.log("release-check: npm pack contents and bundled channel entrypoints look OK.");
}

if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
  void main().catch((error: unknown) => {
    console.error(error);
    process.exit(1);
  });
}

¤ Dauer der Verarbeitung: 0.28 Sekunden  (vorverarbeitet am  2026-04-27) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


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