mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 07:20:45 +00:00
Refactor release hardening follow-ups (#39959)
* build: fail fast on stale host-env swift policy * build: sync generated host env swift policy * build: guard bundled extension root dependency gaps * refactor: centralize provider capability quirks * test: table-drive provider regression coverage * fix: block merge when prep branch has unpushed commits * refactor: simplify models config merge preservation
This commit is contained in:
committed by
GitHub
parent
27558806b5
commit
eba9dcc67a
25
scripts/pr
25
scripts/pr
@@ -229,6 +229,30 @@ checkout_prep_branch() {
|
||||
git checkout "$prep_branch"
|
||||
}
|
||||
|
||||
verify_prep_branch_matches_prepared_head() {
|
||||
local pr="$1"
|
||||
local prepared_head_sha="$2"
|
||||
|
||||
require_artifact .local/prep-context.env
|
||||
checkout_prep_branch "$pr"
|
||||
|
||||
local prep_branch_head_sha
|
||||
prep_branch_head_sha=$(git rev-parse HEAD)
|
||||
if [ "$prep_branch_head_sha" = "$prepared_head_sha" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "Local prep branch moved after prepare-push (expected $prepared_head_sha, got $prep_branch_head_sha)."
|
||||
if git merge-base --is-ancestor "$prepared_head_sha" "$prep_branch_head_sha" 2>/dev/null; then
|
||||
echo "Unpushed local commits on prep branch:"
|
||||
git log --oneline "${prepared_head_sha}..${prep_branch_head_sha}" | sed 's/^/ /' || true
|
||||
echo "Run scripts/pr prepare-sync-head $pr to push them before merge."
|
||||
else
|
||||
echo "Prep branch no longer contains the prepared head. Re-run prepare-init."
|
||||
fi
|
||||
exit 1
|
||||
}
|
||||
|
||||
resolve_head_push_url() {
|
||||
# shellcheck disable=SC1091
|
||||
source .local/pr-meta.env
|
||||
@@ -1667,6 +1691,7 @@ merge_verify() {
|
||||
require_artifact .local/prep.env
|
||||
# shellcheck disable=SC1091
|
||||
source .local/prep.env
|
||||
verify_prep_branch_matches_prepared_head "$pr" "$PREP_HEAD_SHA"
|
||||
|
||||
local json
|
||||
json=$(pr_meta_json "$pr")
|
||||
|
||||
@@ -8,6 +8,17 @@ import { sparkleBuildFloorsFromShortVersion, type SparkleBuildFloors } from "./s
|
||||
|
||||
type PackFile = { path: string };
|
||||
type PackResult = { files?: PackFile[] };
|
||||
type PackageJson = {
|
||||
name?: string;
|
||||
version?: string;
|
||||
dependencies?: Record<string, string>;
|
||||
optionalDependencies?: Record<string, string>;
|
||||
openclaw?: {
|
||||
install?: {
|
||||
npmSpec?: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
const requiredPathGroups = [
|
||||
["dist/index.js", "dist/index.mjs"],
|
||||
@@ -108,11 +119,6 @@ const appcastPath = resolve("appcast.xml");
|
||||
const laneBuildMin = 1_000_000_000;
|
||||
const laneFloorAdoptionDateKey = 20260227;
|
||||
|
||||
type PackageJson = {
|
||||
name?: string;
|
||||
version?: string;
|
||||
};
|
||||
|
||||
function normalizePluginSyncVersion(version: string): string {
|
||||
const normalized = version.trim().replace(/^v/, "");
|
||||
const base = /^([0-9]+\.[0-9]+\.[0-9]+)/.exec(normalized)?.[1];
|
||||
@@ -122,6 +128,92 @@ function normalizePluginSyncVersion(version: string): string {
|
||||
return normalized.replace(/[-+].*$/, "");
|
||||
}
|
||||
|
||||
const ALLOWLISTED_BUNDLED_EXTENSION_ROOT_DEP_GAPS: Record<string, string[]> = {
|
||||
googlechat: ["google-auth-library"],
|
||||
matrix: ["@matrix-org/matrix-sdk-crypto-nodejs", "@vector-im/matrix-bot-sdk", "music-metadata"],
|
||||
msteams: ["@microsoft/agents-hosting"],
|
||||
nostr: ["nostr-tools"],
|
||||
tlon: ["@tloncorp/api", "@tloncorp/tlon-skill", "@urbit/aura"],
|
||||
zalouser: ["zca-js"],
|
||||
};
|
||||
|
||||
export function collectBundledExtensionRootDependencyGapErrors(params: {
|
||||
rootPackage: PackageJson;
|
||||
extensions: Array<{ id: string; packageJson: PackageJson }>;
|
||||
}): string[] {
|
||||
const rootDeps = {
|
||||
...params.rootPackage.dependencies,
|
||||
...params.rootPackage.optionalDependencies,
|
||||
};
|
||||
const errors: string[] = [];
|
||||
|
||||
for (const extension of params.extensions) {
|
||||
if (!extension.packageJson.openclaw?.install?.npmSpec) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const missing = Object.keys(extension.packageJson.dependencies ?? {})
|
||||
.filter((dep) => dep !== "openclaw" && !rootDeps[dep])
|
||||
.toSorted();
|
||||
const allowlisted = [
|
||||
...(ALLOWLISTED_BUNDLED_EXTENSION_ROOT_DEP_GAPS[extension.id] ?? []),
|
||||
].toSorted();
|
||||
if (missing.join("\n") !== allowlisted.join("\n")) {
|
||||
const unexpected = missing.filter((dep) => !allowlisted.includes(dep));
|
||||
const resolved = allowlisted.filter((dep) => !missing.includes(dep));
|
||||
const parts = [
|
||||
`bundled extension '${extension.id}' root dependency mirror drift`,
|
||||
`missing in root package: ${missing.length > 0 ? missing.join(", ") : "(none)"}`,
|
||||
];
|
||||
if (unexpected.length > 0) {
|
||||
parts.push(`new gaps: ${unexpected.join(", ")}`);
|
||||
}
|
||||
if (resolved.length > 0) {
|
||||
parts.push(`remove stale allowlist entries: ${resolved.join(", ")}`);
|
||||
}
|
||||
errors.push(parts.join(" | "));
|
||||
}
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
function collectBundledExtensions(): Array<{ id: string; packageJson: PackageJson }> {
|
||||
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 checkBundledExtensionRootDependencyMirrors() {
|
||||
const rootPackage = JSON.parse(readFileSync(resolve("package.json"), "utf8")) as PackageJson;
|
||||
const errors = collectBundledExtensionRootDependencyGapErrors({
|
||||
rootPackage,
|
||||
extensions: collectBundledExtensions(),
|
||||
});
|
||||
if (errors.length > 0) {
|
||||
console.error("release-check: bundled extension root dependency mirror 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",
|
||||
@@ -321,6 +413,7 @@ function main() {
|
||||
checkPluginVersions();
|
||||
checkAppcastSparkleVersions();
|
||||
checkPluginSdkExports();
|
||||
checkBundledExtensionRootDependencyMirrors();
|
||||
|
||||
const results = runPackDry();
|
||||
const files = results.flatMap((entry) => entry.files ?? []);
|
||||
|
||||
Reference in New Issue
Block a user