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


Quelle  changed-lanes.mjs   Sprache: unbekannt

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

import { execFileSync } from "node:child_process";
import { appendFileSync } from "node:fs";
import { booleanFlag, parseFlagArgs, stringFlag } from "./lib/arg-utils.mjs";

const DOCS_PATH_RE = /^(?:docs\/|README\.md$|AGENTS\.md$|.*\.mdx?$)/u;
const APP_PATH_RE = /^(?:apps\/|Swabble\/|appcast\.xml$)/u;
const EXTENSION_PATH_RE = /^extensions\/[^/]+(?:\/|$)/u;
const CORE_PATH_RE = /^(?:src\/|ui\/|packages\/)/u;
const TOOLING_PATH_RE =
  /^(?:scripts\/|test\/vitest\/|\.github\/|git-hooks\/|vitest(?:\..+)?\.config\.ts$|tsconfig.*\.json$|\.gitignore$|\.oxlint.*|\.oxfmt.*)/u;
const ROOT_GLOBAL_PATH_RE =
  /^(?:package\.json$|pnpm-lock\.yaml$|pnpm-workspace\.yaml$|tsdown\.config\.ts$|vitest\.config\.ts$)/u;
const TEST_PATH_RE =
  /(?:^|\/)(?:test|__tests__)\/|(?:\.|\/)(?:test|spec|e2e|browser\.test)\.[cm]?[jt]sx?$/u;
const PUBLIC_EXTENSION_CONTRACT_RE =
  /^(?:src\/plugin-sdk\/|src\/plugins\/contracts\/|src\/channels\/plugins\/|scripts\/lib\/plugin-sdk-entrypoints\.json$|scripts\/sync-plugin-sdk-exports\.mjs$|scripts\/generate-plugin-sdk-api-baseline\.ts$)/u;
export const RELEASE_METADATA_PATHS = new Set([
  "CHANGELOG.md",
  "apps/android/app/build.gradle.kts",
  "apps/ios/CHANGELOG.md",
  "apps/ios/Config/Version.xcconfig",
  "apps/ios/fastlane/metadata/en-US/release_notes.txt",
  "apps/ios/version.json",
  "apps/macos/Sources/OpenClaw/Resources/Info.plist",
  "docs/.generated/config-baseline.sha256",
  "docs/install/updating.md",
  "package.json",
  "src/config/schema.base.generated.ts",
]);

/** @typedef {"core" | "coreTests" | "extensions" | "extensionTests" | "apps" | "docs" | "tooling" | "releaseMetadata" | "all"} ChangedLane */

/**
 * @typedef {{
 *   paths: string[];
 *   lanes: Record<ChangedLane, boolean>;
 *   extensionImpactFromCore: boolean;
 *   docsOnly: boolean;
 *   reasons: string[];
 * }} ChangedLaneResult
 */

export function normalizeChangedPath(inputPath) {
  return String(inputPath ?? "")
    .trim()
    .replaceAll("\\", "/")
    .replace(/^\.\/+/u, "");
}

export function createEmptyChangedLanes() {
  return {
    core: false,
    coreTests: false,
    extensions: false,
    extensionTests: false,
    apps: false,
    docs: false,
    tooling: false,
    releaseMetadata: false,
    all: false,
  };
}

/**
 * @param {string[]} changedPaths
 * @returns {ChangedLaneResult}
 */
export function detectChangedLanes(changedPaths) {
  const paths = [...new Set(changedPaths.map(normalizeChangedPath).filter(Boolean))].toSorted(
    (left, right) => left.localeCompare(right),
  );
  const lanes = createEmptyChangedLanes();
  const reasons = [];
  let extensionImpactFromCore = false;
  let hasNonDocs = false;

  if (paths.length === 0) {
    reasons.push("no changed paths");
    return { paths, lanes, extensionImpactFromCore: false, docsOnly: false, reasons };
  }

  if (
    paths.some((changedPath) => RELEASE_METADATA_PATHS.has(changedPath)) &&
    paths.every(
      (changedPath) => RELEASE_METADATA_PATHS.has(changedPath) || DOCS_PATH_RE.test(changedPath),
    )
  ) {
    lanes.releaseMetadata = true;
    lanes.docs = paths.some((changedPath) => DOCS_PATH_RE.test(changedPath));
    for (const changedPath of paths) {
      reasons.push(`${changedPath}: release metadata`);
    }
    return { paths, lanes, extensionImpactFromCore: false, docsOnly: false, reasons };
  }

  for (const changedPath of paths) {
    if (DOCS_PATH_RE.test(changedPath)) {
      lanes.docs = true;
      continue;
    }

    hasNonDocs = true;

    if (ROOT_GLOBAL_PATH_RE.test(changedPath)) {
      lanes.all = true;
      extensionImpactFromCore = true;
      reasons.push(`${changedPath}: root config/package surface`);
      continue;
    }

    if (PUBLIC_EXTENSION_CONTRACT_RE.test(changedPath)) {
      lanes.core = true;
      lanes.coreTests = true;
      lanes.extensions = true;
      lanes.extensionTests = true;
      extensionImpactFromCore = true;
      reasons.push(`${changedPath}: public core/plugin contract affects extensions`);
      continue;
    }

    if (EXTENSION_PATH_RE.test(changedPath)) {
      if (TEST_PATH_RE.test(changedPath)) {
        lanes.extensionTests = true;
        reasons.push(`${changedPath}: extension test`);
      } else {
        lanes.extensions = true;
        lanes.extensionTests = true;
        reasons.push(`${changedPath}: extension production`);
      }
      continue;
    }

    if (CORE_PATH_RE.test(changedPath)) {
      if (TEST_PATH_RE.test(changedPath)) {
        lanes.coreTests = true;
        reasons.push(`${changedPath}: core test`);
      } else {
        lanes.core = true;
        lanes.coreTests = true;
        reasons.push(`${changedPath}: core production`);
      }
      continue;
    }

    if (APP_PATH_RE.test(changedPath)) {
      lanes.apps = true;
      reasons.push(`${changedPath}: app surface`);
      continue;
    }

    if (changedPath.startsWith("test/")) {
      lanes.tooling = true;
      reasons.push(`${changedPath}: root test/support surface`);
      continue;
    }

    if (TOOLING_PATH_RE.test(changedPath)) {
      lanes.tooling = true;
      reasons.push(`${changedPath}: tooling surface`);
      continue;
    }

    lanes.all = true;
    extensionImpactFromCore = true;
    reasons.push(`${changedPath}: unknown surface; fail-safe all lanes`);
  }

  return {
    paths,
    lanes,
    extensionImpactFromCore,
    docsOnly: lanes.docs && !hasNonDocs,
    reasons,
  };
}

/**
 * @param {{ base: string; head?: string; includeWorktree?: boolean }} params
 * @returns {string[]}
 */
export function listChangedPathsFromGit(params) {
  const base = params.base;
  const head = params.head ?? "HEAD";
  if (!base) {
    return [];
  }
  const rangePaths = runGitNameOnlyDiff([`${base}...${head}`]);
  if (params.includeWorktree === false) {
    return rangePaths;
  }
  return [
    ...new Set([
      ...rangePaths,
      ...runGitNameOnlyDiff(["--cached", "--diff-filter=ACMR"]),
      ...runGitNameOnlyDiff(["--diff-filter=ACMR"]),
      ...runGitLsFiles(["--others", "--exclude-standard"]),
    ]),
  ].toSorted((left, right) => left.localeCompare(right));
}

function runGitNameOnlyDiff(extraArgs) {
  const output = execFileSync("git", ["diff", "--name-only", ...extraArgs], {
    stdio: ["ignore", "pipe", "pipe"],
    encoding: "utf8",
  });
  return output.split("\n").map(normalizeChangedPath).filter(Boolean);
}

function runGitLsFiles(extraArgs) {
  const output = execFileSync("git", ["ls-files", ...extraArgs], {
    stdio: ["ignore", "pipe", "pipe"],
    encoding: "utf8",
  });
  return output.split("\n").map(normalizeChangedPath).filter(Boolean);
}

export function listStagedChangedPaths() {
  const output = execFileSync("git", ["diff", "--cached", "--name-only", "--diff-filter=ACMR"], {
    stdio: ["ignore", "pipe", "pipe"],
    encoding: "utf8",
  });
  return output.split("\n").map(normalizeChangedPath).filter(Boolean);
}

export function writeChangedLaneGitHubOutput(result, outputPath = process.env.GITHUB_OUTPUT) {
  if (!outputPath) {
    throw new Error("GITHUB_OUTPUT is required");
  }
  for (const [lane, enabled] of Object.entries(result.lanes)) {
    appendFileSync(outputPath, `run_${toSnakeCase(lane)}=${String(enabled)}\n`, "utf8");
  }
  appendFileSync(outputPath, `docs_only=${result.docsOnly}\n`, "utf8");
  appendFileSync(
    outputPath,
    `extension_impact_from_core=${result.extensionImpactFromCore}\n`,
    "utf8",
  );
}

function toSnakeCase(value) {
  return value.replace(/[A-Z]/gu, (match) => `_${match.toLowerCase()}`);
}

function parseArgs(argv) {
  const args = {
    base: "origin/main",
    head: "HEAD",
    staged: false,
    json: false,
    githubOutput: false,
    paths: [],
  };
  return parseFlagArgs(
    argv,
    args,
    [
      stringFlag("--base", "base"),
      stringFlag("--head", "head"),
      booleanFlag("--staged", "staged"),
      booleanFlag("--json", "json"),
      booleanFlag("--github-output", "githubOutput"),
    ],
    {
      onUnhandledArg(arg, target) {
        target.paths.push(arg);
        return "handled";
      },
    },
  );
}

function isDirectRun() {
  const direct = process.argv[1];
  return Boolean(direct && import.meta.url.endsWith(direct));
}

function printHuman(result) {
  const enabled = Object.entries(result.lanes)
    .filter(([, value]) => value)
    .map(([lane]) => lane);
  console.log(`lanes: ${enabled.length > 0 ? enabled.join(", ") : "none"}`);
  if (result.docsOnly) {
    console.log("docs-only: true");
  }
  if (result.extensionImpactFromCore) {
    console.log("extension-impact-from-core: true");
  }
  if (result.paths.length > 0) {
    console.log("paths:");
    for (const changedPath of result.paths) {
      console.log(`- ${changedPath}`);
    }
  }
  if (result.reasons.length > 0) {
    console.log("reasons:");
    for (const reason of result.reasons) {
      console.log(`- ${reason}`);
    }
  }
}

if (isDirectRun()) {
  const args = parseArgs(process.argv.slice(2));
  const paths =
    args.paths.length > 0
      ? args.paths
      : args.staged
        ? listStagedChangedPaths()
        : listChangedPathsFromGit({ base: args.base, head: args.head });
  const result = detectChangedLanes(paths);
  if (args.githubOutput) {
    writeChangedLaneGitHubOutput(result);
  }
  if (args.json) {
    console.log(JSON.stringify(result, null, 2));
  } else if (!args.githubOutput) {
    printHuman(result);
  }
}

[Dauer der Verarbeitung: 0.25 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