mirror of
https://github.com/openclaw/clawhub.git
synced 2026-08-14 08:52:21 +00:00
chore: remove kitchen sink repair tooling (#3307)
This commit is contained in:
@@ -1,142 +0,0 @@
|
||||
name: Repair Kitchen Sink Latest Pointer
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
expected_sha:
|
||||
description: "Exact main SHA containing the repair mutation"
|
||||
required: true
|
||||
type: string
|
||||
apply:
|
||||
description: "Apply the repair after the dry-run"
|
||||
required: true
|
||||
default: false
|
||||
type: boolean
|
||||
confirm:
|
||||
description: "Required confirmation token when apply is true"
|
||||
required: false
|
||||
type: string
|
||||
|
||||
concurrency:
|
||||
group: repair-kitchen-sink-latest
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
repair:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
environment:
|
||||
name: Production
|
||||
url: https://clawhub.ai/packages/%40openclaw%2Fkitchen-sink
|
||||
steps:
|
||||
- name: Require exact main revision
|
||||
env:
|
||||
EXPECTED_SHA: ${{ inputs.expected_sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ "$GITHUB_REF" != "refs/heads/main" ]]; then
|
||||
echo "::error::The repair must run from main."
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$GITHUB_SHA" != "$EXPECTED_SHA" ]]; then
|
||||
echo "::error::Main moved: expected $EXPECTED_SHA, got $GITHUB_SHA."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6
|
||||
with:
|
||||
bun-version: 1.3.10
|
||||
|
||||
- name: Install
|
||||
run: bun install --frozen-lockfile
|
||||
|
||||
- name: Dry-run repair
|
||||
id: preflight
|
||||
env:
|
||||
CONVEX_DEPLOY_KEY: ${{ secrets.CONVEX_DEPLOY_KEY }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ -z "$CONVEX_DEPLOY_KEY" ]]; then
|
||||
echo "::error::Missing Production environment secret CONVEX_DEPLOY_KEY"
|
||||
exit 1
|
||||
fi
|
||||
result="$(bunx convex run maintenance:repairPackageLatestPointer \
|
||||
'{"name":"@openclaw/kitchen-sink"}' --prod)"
|
||||
if jq -e '
|
||||
.dryRun == true and
|
||||
.previousLatestVersion == "0.2.11" and
|
||||
.selectedVersion == "0.2.12" and
|
||||
.eligibleReleaseCount >= 2
|
||||
' <<< "$result" >/dev/null; then
|
||||
{
|
||||
echo "already_repaired=false"
|
||||
echo "plan_token=$(jq -r '.planToken' <<< "$result")"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
if jq -e '
|
||||
.dryRun == true and
|
||||
.previousLatestVersion == "0.2.12" and
|
||||
.selectedVersion == "0.2.12" and
|
||||
(.releaseTagChanges | length) == 0
|
||||
' <<< "$result" >/dev/null; then
|
||||
echo "already_repaired=true" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
echo "::error::Production package state did not match the expected pre- or post-repair state."
|
||||
exit 1
|
||||
|
||||
- name: Require apply confirmation
|
||||
if: inputs.apply
|
||||
env:
|
||||
CONFIRM: ${{ inputs.confirm }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ "$CONFIRM" != "repair-package-latest-pointer-2026-07-30" ]]; then
|
||||
echo "::error::The exact repair confirmation token is required."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Apply repair
|
||||
if: inputs.apply && steps.preflight.outputs.already_repaired != 'true'
|
||||
env:
|
||||
CONVEX_DEPLOY_KEY: ${{ secrets.CONVEX_DEPLOY_KEY }}
|
||||
EXPECTED_PLAN_TOKEN: ${{ steps.preflight.outputs.plan_token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
required_confirm="repair-package-latest-pointer-2026-07-30"
|
||||
args="$(jq -nc \
|
||||
--arg name "@openclaw/kitchen-sink" \
|
||||
--arg confirm "$required_confirm" \
|
||||
--arg planToken "$EXPECTED_PLAN_TOKEN" \
|
||||
'{
|
||||
name: $name,
|
||||
dryRun: false,
|
||||
confirm: $confirm,
|
||||
expectedPlanToken: $planToken
|
||||
}')"
|
||||
result="$(bunx convex run maintenance:repairPackageLatestPointer "$args" --prod)"
|
||||
jq -e '
|
||||
.dryRun == false and
|
||||
.selectedVersion == "0.2.12"
|
||||
' <<< "$result"
|
||||
|
||||
- name: Verify repaired state
|
||||
if: inputs.apply
|
||||
env:
|
||||
CONVEX_DEPLOY_KEY: ${{ secrets.CONVEX_DEPLOY_KEY }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
readback="$(bunx convex run maintenance:repairPackageLatestPointer \
|
||||
'{"name":"@openclaw/kitchen-sink"}' --prod)"
|
||||
jq -e '
|
||||
.dryRun == true and
|
||||
.previousLatestVersion == "0.2.12" and
|
||||
.selectedVersion == "0.2.12" and
|
||||
(.releaseTagChanges | length) == 0
|
||||
' <<< "$readback"
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { PackageArtifactSummary } from "clawhub-schema";
|
||||
import type { Doc } from "../_generated/dataModel";
|
||||
|
||||
type PackageReleaseArtifactHashFields = Pick<
|
||||
@@ -6,20 +5,6 @@ type PackageReleaseArtifactHashFields = Pick<
|
||||
"artifactKind" | "clawpackSha256" | "sha256hash"
|
||||
>;
|
||||
|
||||
type PackageReleaseArtifactSummaryFields = Pick<
|
||||
Doc<"packageReleases">,
|
||||
| "artifactKind"
|
||||
| "clawpackSha256"
|
||||
| "sha256hash"
|
||||
| "clawpackSize"
|
||||
| "clawpackFormat"
|
||||
| "npmIntegrity"
|
||||
| "npmShasum"
|
||||
| "npmTarballName"
|
||||
| "npmUnpackedSize"
|
||||
| "npmFileCount"
|
||||
>;
|
||||
|
||||
export function getPackageReleaseArtifactSha256(
|
||||
release: PackageReleaseArtifactHashFields,
|
||||
): string | null {
|
||||
@@ -28,26 +13,3 @@ export function getPackageReleaseArtifactSha256(
|
||||
}
|
||||
return release.sha256hash ?? null;
|
||||
}
|
||||
|
||||
export function summarizePackageReleaseArtifact(
|
||||
release: PackageReleaseArtifactSummaryFields,
|
||||
): PackageArtifactSummary {
|
||||
if (release.artifactKind === "npm-pack") {
|
||||
return {
|
||||
kind: "npm-pack",
|
||||
sha256: getPackageReleaseArtifactSha256(release) ?? undefined,
|
||||
size: release.clawpackSize,
|
||||
format: release.clawpackFormat ?? "tgz",
|
||||
npmIntegrity: release.npmIntegrity,
|
||||
npmShasum: release.npmShasum,
|
||||
npmTarballName: release.npmTarballName,
|
||||
npmUnpackedSize: release.npmUnpackedSize,
|
||||
npmFileCount: release.npmFileCount,
|
||||
};
|
||||
}
|
||||
return {
|
||||
kind: "legacy-zip",
|
||||
sha256: getPackageReleaseArtifactSha256(release) ?? undefined,
|
||||
format: "zip",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -83,7 +83,6 @@ const {
|
||||
applySkillLineageCycleRepairInternalHandler,
|
||||
inspectSkillLineageCycleInternalHandler,
|
||||
nominateEmptySkillSpammersInternalHandler,
|
||||
repairPackageLatestPointerHandler,
|
||||
repairLegacyPluginSkillSpectorBatchInternalHandler,
|
||||
repairLegacyPublisherOwnershipForUserHandler,
|
||||
repairSkillLineageCyclesInternalHandler,
|
||||
@@ -2007,198 +2006,3 @@ describe("maintenance empty skill nominations", () => {
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
function makePackageLatestPointerRepairDb() {
|
||||
const pkg = {
|
||||
_id: "packages:kitchen-sink",
|
||||
name: "@openclaw/kitchen-sink",
|
||||
normalizedName: "@openclaw/kitchen-sink",
|
||||
family: "code-plugin",
|
||||
tags: { latest: "packageReleases:old" },
|
||||
latestReleaseId: "packageReleases:old",
|
||||
};
|
||||
const releases = [
|
||||
{
|
||||
_id: "packageReleases:old",
|
||||
packageId: pkg._id,
|
||||
version: "0.2.11",
|
||||
publicationStatus: "published",
|
||||
changelog: "old",
|
||||
summary: "old summary",
|
||||
icon: "old-icon",
|
||||
distTags: ["latest"],
|
||||
compatibility: {},
|
||||
verification: {},
|
||||
runtimeId: "old-runtime",
|
||||
sourceRepo: "https://github.com/openclaw/kitchen-sink",
|
||||
artifactKind: "npm-pack",
|
||||
clawpackSha256: "old-sha",
|
||||
clawpackSize: 10,
|
||||
clawpackFormat: "tgz",
|
||||
createdAt: 100,
|
||||
},
|
||||
{
|
||||
_id: "packageReleases:new",
|
||||
packageId: pkg._id,
|
||||
version: "0.2.12",
|
||||
publicationStatus: "published",
|
||||
changelog: "new",
|
||||
summary: "new summary",
|
||||
icon: "new-icon",
|
||||
distTags: [],
|
||||
compatibility: {},
|
||||
verification: {},
|
||||
runtimeId: "new-runtime",
|
||||
sourceRepo: "https://github.com/openclaw/kitchen-sink",
|
||||
artifactKind: "npm-pack",
|
||||
clawpackSha256: "new-sha",
|
||||
clawpackSize: 20,
|
||||
clawpackFormat: "tgz",
|
||||
createdAt: 200,
|
||||
},
|
||||
{
|
||||
_id: "packageReleases:pending",
|
||||
packageId: pkg._id,
|
||||
version: "0.2.13",
|
||||
publicationStatus: "pending",
|
||||
changelog: "pending",
|
||||
distTags: [],
|
||||
compatibility: {},
|
||||
verification: {},
|
||||
artifactKind: "npm-pack",
|
||||
clawpackSha256: "pending-sha",
|
||||
createdAt: 300,
|
||||
},
|
||||
];
|
||||
const patch = vi.fn();
|
||||
const insert = vi.fn();
|
||||
const query = vi.fn((table: string) => ({
|
||||
withIndex: (_index: string, applyIndex: (q: QueryEq) => unknown) => {
|
||||
const q = { eq: vi.fn() } as unknown as QueryEq;
|
||||
vi.mocked(q.eq).mockReturnValue(q);
|
||||
applyIndex(q);
|
||||
if (table === "packages") {
|
||||
return { unique: async () => pkg };
|
||||
}
|
||||
if (table === "packageReleases") {
|
||||
return { collect: async () => releases };
|
||||
}
|
||||
throw new Error(`Unexpected table: ${table}`);
|
||||
},
|
||||
}));
|
||||
|
||||
return { db: { query, patch, insert }, pkg, releases, patch, insert };
|
||||
}
|
||||
|
||||
describe("maintenance package latest pointer repair", () => {
|
||||
it("dry-runs the canonical published release without writing", async () => {
|
||||
const fixture = makePackageLatestPointerRepairDb();
|
||||
|
||||
const result = await repairPackageLatestPointerHandler(fixture as never, {
|
||||
name: "@openclaw/kitchen-sink",
|
||||
});
|
||||
|
||||
expect(result).toMatchObject({
|
||||
dryRun: true,
|
||||
confirmRequired: "repair-package-latest-pointer-2026-07-30",
|
||||
previousLatestVersion: "0.2.11",
|
||||
selectedVersion: "0.2.12",
|
||||
eligibleReleaseCount: 2,
|
||||
planToken: expect.stringMatching(/^[a-f\d]{64}$/),
|
||||
releaseTagChanges: [
|
||||
{
|
||||
releaseId: "packageReleases:old",
|
||||
version: "0.2.11",
|
||||
action: "remove",
|
||||
},
|
||||
{
|
||||
releaseId: "packageReleases:new",
|
||||
version: "0.2.12",
|
||||
action: "add",
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(fixture.patch).not.toHaveBeenCalled();
|
||||
expect(fixture.insert).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("requires the exact confirmation token before applying", async () => {
|
||||
const fixture = makePackageLatestPointerRepairDb();
|
||||
|
||||
await expect(
|
||||
repairPackageLatestPointerHandler(fixture as never, {
|
||||
name: "@openclaw/kitchen-sink",
|
||||
dryRun: false,
|
||||
confirm: "wrong",
|
||||
}),
|
||||
).rejects.toThrow('Pass confirm="repair-package-latest-pointer-2026-07-30" to apply.');
|
||||
expect(fixture.patch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("requires the applied repair to match the dry-run plan", async () => {
|
||||
const fixture = makePackageLatestPointerRepairDb();
|
||||
|
||||
await expect(
|
||||
repairPackageLatestPointerHandler(fixture as never, {
|
||||
name: "@openclaw/kitchen-sink",
|
||||
dryRun: false,
|
||||
confirm: "repair-package-latest-pointer-2026-07-30",
|
||||
expectedPlanToken: "wrong",
|
||||
}),
|
||||
).rejects.toThrow(
|
||||
"Package latest repair plan changed after dry-run; rerun the dry-run before applying.",
|
||||
);
|
||||
expect(fixture.patch).not.toHaveBeenCalled();
|
||||
expect(fixture.insert).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("repoints the package, normalizes release tags, and audits the repair", async () => {
|
||||
const fixture = makePackageLatestPointerRepairDb();
|
||||
const preflight = await repairPackageLatestPointerHandler(fixture as never, {
|
||||
name: "@openclaw/kitchen-sink",
|
||||
});
|
||||
|
||||
const result = await repairPackageLatestPointerHandler(fixture as never, {
|
||||
name: "@openclaw/kitchen-sink",
|
||||
dryRun: false,
|
||||
confirm: "repair-package-latest-pointer-2026-07-30",
|
||||
expectedPlanToken: preflight.planToken,
|
||||
});
|
||||
|
||||
expect(result.selectedVersion).toBe("0.2.12");
|
||||
expect(fixture.patch).toHaveBeenCalledWith("packageReleases:old", { distTags: [] });
|
||||
expect(fixture.patch).toHaveBeenCalledWith("packageReleases:new", {
|
||||
distTags: ["latest"],
|
||||
});
|
||||
expect(fixture.patch).toHaveBeenCalledWith(
|
||||
fixture.pkg._id,
|
||||
expect.objectContaining({
|
||||
tags: { latest: "packageReleases:new" },
|
||||
latestReleaseId: "packageReleases:new",
|
||||
summary: "new summary",
|
||||
icon: "new-icon",
|
||||
runtimeId: "new-runtime",
|
||||
scanStatus: "not-run",
|
||||
latestVersionSummary: expect.objectContaining({
|
||||
version: "0.2.12",
|
||||
artifact: expect.objectContaining({
|
||||
kind: "npm-pack",
|
||||
sha256: "new-sha",
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
expect(fixture.insert).toHaveBeenCalledWith(
|
||||
"auditLogs",
|
||||
expect.objectContaining({
|
||||
action: "package.latest_pointer.repair",
|
||||
targetType: "package",
|
||||
targetId: fixture.pkg._id,
|
||||
metadata: expect.objectContaining({
|
||||
previousLatestVersion: "0.2.11",
|
||||
selectedVersion: "0.2.12",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { ConvexError, v } from "convex/values";
|
||||
import semver from "semver";
|
||||
import { internal } from "./_generated/api";
|
||||
import type { Doc, Id } from "./_generated/dataModel";
|
||||
import type { ActionCtx, MutationCtx, QueryCtx } from "./_generated/server";
|
||||
@@ -11,11 +10,7 @@ import {
|
||||
syncPackageSearchDigestForPackageId,
|
||||
} from "./functions";
|
||||
import { assertRole, requireUserFromAction } from "./lib/access";
|
||||
import { sha256Hex } from "./lib/clawpack";
|
||||
import { summarizePackageReleaseArtifact } from "./lib/packageArtifacts";
|
||||
import { normalizePackageName } from "./lib/packageRegistry";
|
||||
import { extractPackageDigestFields } from "./lib/packageSearchDigest";
|
||||
import { resolvePackageReleaseScanStatus } from "./lib/packageSecurity";
|
||||
import {
|
||||
derivePersonalPublisherHandle,
|
||||
ensurePersonalPublisherForUser,
|
||||
@@ -51,7 +46,6 @@ const PUBLISHER_ABUSE_SIGNAL_SMOKE_OWNER_KEY =
|
||||
const PUBLISHER_ABUSE_SIGNAL_SMOKE_CONFIRM =
|
||||
"create-publisher-abuse-hermit-digest-smoke-2026-07-03" as const;
|
||||
const SKILL_LINEAGE_CYCLE_REPAIR_CONFIRM = "repair-skill-lineage-cycles-2026-07-23" as const;
|
||||
const PACKAGE_LATEST_POINTER_REPAIR_CONFIRM = "repair-package-latest-pointer-2026-07-30" as const;
|
||||
const legacyPluginSkillSpectorRepairFamilyValidator = v.union(
|
||||
v.literal("code-plugin"),
|
||||
v.literal("bundle-plugin"),
|
||||
@@ -3279,203 +3273,6 @@ export const repairLegacyPublisherOwnershipForUser = internalMutation({
|
||||
handler: repairLegacyPublisherOwnershipForUserHandler,
|
||||
});
|
||||
|
||||
function isPublishedPackageRelease(release: Doc<"packageReleases">) {
|
||||
return (
|
||||
!release.softDeletedAt &&
|
||||
release.ownerDeletedAt === undefined &&
|
||||
(release.publicationStatus === undefined || release.publicationStatus === "published")
|
||||
);
|
||||
}
|
||||
|
||||
function comparePackageLatestCandidates(
|
||||
family: Doc<"packages">["family"],
|
||||
a: Doc<"packageReleases">,
|
||||
b: Doc<"packageReleases">,
|
||||
) {
|
||||
if (family === "bundle-plugin") {
|
||||
if (a.createdAt !== b.createdAt) return a.createdAt - b.createdAt;
|
||||
return a._id.localeCompare(b._id);
|
||||
}
|
||||
const aSemver = semver.valid(a.version);
|
||||
const bSemver = semver.valid(b.version);
|
||||
if (aSemver && bSemver) return semver.compare(aSemver, bSemver);
|
||||
if (aSemver) return 1;
|
||||
if (bSemver) return -1;
|
||||
if (a.createdAt !== b.createdAt) return a.createdAt - b.createdAt;
|
||||
return a._id.localeCompare(b._id);
|
||||
}
|
||||
|
||||
async function buildPackageLatestPointerPlanToken(params: {
|
||||
pkg: Doc<"packages">;
|
||||
releases: Doc<"packageReleases">[];
|
||||
selectedRelease: Doc<"packageReleases">;
|
||||
releaseTagChanges: Array<{
|
||||
releaseId: Id<"packageReleases">;
|
||||
version: string;
|
||||
action: "add" | "remove";
|
||||
}>;
|
||||
}) {
|
||||
const payload = JSON.stringify({
|
||||
packageLatestReleaseId: params.pkg.latestReleaseId ?? null,
|
||||
packageTags: params.pkg.tags,
|
||||
selectedRelease: params.selectedRelease,
|
||||
releaseTags: params.releases
|
||||
.map((release) => ({ releaseId: release._id, distTags: release.distTags }))
|
||||
.sort((a, b) => a.releaseId.localeCompare(b.releaseId)),
|
||||
releaseTagChanges: params.releaseTagChanges,
|
||||
});
|
||||
return await sha256Hex(new TextEncoder().encode(payload));
|
||||
}
|
||||
|
||||
export async function repairPackageLatestPointerHandler(
|
||||
ctx: Pick<MutationCtx, "db">,
|
||||
args: {
|
||||
name: string;
|
||||
dryRun?: boolean;
|
||||
confirm?: string;
|
||||
expectedPlanToken?: string;
|
||||
},
|
||||
) {
|
||||
const dryRun = args.dryRun !== false;
|
||||
if (!dryRun && args.confirm !== PACKAGE_LATEST_POINTER_REPAIR_CONFIRM) {
|
||||
throw new ConvexError(`Pass confirm="${PACKAGE_LATEST_POINTER_REPAIR_CONFIRM}" to apply.`);
|
||||
}
|
||||
|
||||
const normalizedName = normalizePackageName(args.name);
|
||||
const pkg = await ctx.db
|
||||
.query("packages")
|
||||
.withIndex("by_name", (q) => q.eq("normalizedName", normalizedName))
|
||||
.unique();
|
||||
if (!pkg || pkg.softDeletedAt) {
|
||||
throw new ConvexError(`Active package not found: ${normalizedName}`);
|
||||
}
|
||||
|
||||
const releases = await ctx.db
|
||||
.query("packageReleases")
|
||||
.withIndex("by_package", (q) => q.eq("packageId", pkg._id))
|
||||
.collect();
|
||||
const eligibleReleases = releases.filter(isPublishedPackageRelease);
|
||||
const selectedRelease = eligibleReleases.reduce<Doc<"packageReleases"> | null>(
|
||||
(best, release) =>
|
||||
!best || comparePackageLatestCandidates(pkg.family, best, release) < 0 ? release : best,
|
||||
null,
|
||||
);
|
||||
if (!selectedRelease) {
|
||||
throw new ConvexError(`No published releases found for package: ${normalizedName}`);
|
||||
}
|
||||
|
||||
const releaseTagChanges = releases.flatMap((release) => {
|
||||
const hadLatest = release.distTags.includes("latest");
|
||||
const shouldHaveLatest = release._id === selectedRelease._id;
|
||||
return hadLatest === shouldHaveLatest
|
||||
? []
|
||||
: [
|
||||
{
|
||||
releaseId: release._id,
|
||||
version: release.version,
|
||||
action: shouldHaveLatest ? ("add" as const) : ("remove" as const),
|
||||
},
|
||||
];
|
||||
});
|
||||
const previousLatestRelease = pkg.latestReleaseId
|
||||
? (releases.find((release) => release._id === pkg.latestReleaseId) ?? null)
|
||||
: null;
|
||||
const planToken = await buildPackageLatestPointerPlanToken({
|
||||
pkg,
|
||||
releases,
|
||||
selectedRelease,
|
||||
releaseTagChanges,
|
||||
});
|
||||
|
||||
const result = {
|
||||
dryRun,
|
||||
confirmRequired: dryRun ? PACKAGE_LATEST_POINTER_REPAIR_CONFIRM : undefined,
|
||||
packageId: pkg._id,
|
||||
packageName: pkg.name,
|
||||
normalizedName,
|
||||
family: pkg.family,
|
||||
releasesScanned: releases.length,
|
||||
eligibleReleaseCount: eligibleReleases.length,
|
||||
previousLatestReleaseId: pkg.latestReleaseId,
|
||||
previousLatestVersion: previousLatestRelease?.version,
|
||||
selectedReleaseId: selectedRelease._id,
|
||||
selectedVersion: selectedRelease.version,
|
||||
releaseTagChanges,
|
||||
planToken,
|
||||
};
|
||||
if (dryRun) return result;
|
||||
if (!args.expectedPlanToken || args.expectedPlanToken !== planToken) {
|
||||
throw new ConvexError(
|
||||
"Package latest repair plan changed after dry-run; rerun the dry-run before applying.",
|
||||
);
|
||||
}
|
||||
|
||||
for (const change of releaseTagChanges) {
|
||||
const release = releases.find((candidate) => candidate._id === change.releaseId);
|
||||
if (!release) continue;
|
||||
await ctx.db.patch(release._id, {
|
||||
distTags:
|
||||
change.action === "add"
|
||||
? [...release.distTags, "latest"]
|
||||
: release.distTags.filter((tag) => tag !== "latest"),
|
||||
});
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
const packagePatch: Partial<Doc<"packages">> = {
|
||||
tags: { ...pkg.tags, latest: selectedRelease._id },
|
||||
latestReleaseId: selectedRelease._id,
|
||||
latestVersionSummary: {
|
||||
version: selectedRelease.version,
|
||||
createdAt: selectedRelease.createdAt,
|
||||
changelog: selectedRelease.changelog,
|
||||
icon: selectedRelease.icon,
|
||||
compatibility: selectedRelease.compatibility,
|
||||
verification: selectedRelease.verification,
|
||||
artifact: summarizePackageReleaseArtifact(selectedRelease),
|
||||
},
|
||||
summary: selectedRelease.summary,
|
||||
icon: selectedRelease.icon,
|
||||
sourceRepo: selectedRelease.sourceRepo ?? selectedRelease.verification?.sourceRepo,
|
||||
runtimeId: selectedRelease.runtimeId,
|
||||
compatibility: selectedRelease.compatibility,
|
||||
verification: selectedRelease.verification,
|
||||
scanStatus: resolvePackageReleaseScanStatus(selectedRelease),
|
||||
updatedAt: now,
|
||||
};
|
||||
// The wrapped internal mutation DB runs the package trigger in functions.ts,
|
||||
// which refreshes package search digests from this patched document.
|
||||
await ctx.db.patch(pkg._id, packagePatch);
|
||||
await ctx.db.insert("auditLogs", {
|
||||
action: "package.latest_pointer.repair",
|
||||
targetType: "package",
|
||||
targetId: pkg._id,
|
||||
metadata: {
|
||||
normalizedName,
|
||||
previousLatestReleaseId: pkg.latestReleaseId,
|
||||
previousLatestVersion: previousLatestRelease?.version,
|
||||
selectedReleaseId: selectedRelease._id,
|
||||
selectedVersion: selectedRelease.version,
|
||||
releaseTagChanges,
|
||||
},
|
||||
createdAt: now,
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// One-off production repair. Run dry first, then pass the exact confirmation token.
|
||||
// npx convex run maintenance:repairPackageLatestPointer '{"name":"@scope/package"}' --prod
|
||||
export const repairPackageLatestPointer = internalMutation({
|
||||
args: {
|
||||
name: v.string(),
|
||||
dryRun: v.optional(v.boolean()),
|
||||
confirm: v.optional(v.string()),
|
||||
expectedPlanToken: v.optional(v.string()),
|
||||
},
|
||||
handler: repairPackageLatestPointerHandler,
|
||||
});
|
||||
|
||||
function clampInt(value: number, min: number, max: number) {
|
||||
const rounded = Math.trunc(value);
|
||||
if (!Number.isFinite(rounded)) return min;
|
||||
|
||||
+40
-8
@@ -68,10 +68,7 @@ import { normalizeGitHubRepository } from "./lib/githubActionsOidc";
|
||||
import { readGlobalPublicPluginsCount } from "./lib/globalStats";
|
||||
import { toDayKey } from "./lib/leaderboards";
|
||||
import { isOfficialPublisher } from "./lib/officialPublishers";
|
||||
import {
|
||||
getPackageReleaseArtifactSha256,
|
||||
summarizePackageReleaseArtifact,
|
||||
} from "./lib/packageArtifacts";
|
||||
import { getPackageReleaseArtifactSha256 } from "./lib/packageArtifacts";
|
||||
import {
|
||||
assertPackageVersion,
|
||||
derivePluginManifestSummary,
|
||||
@@ -1219,7 +1216,7 @@ function toPublicPackage(
|
||||
latestRelease === undefined
|
||||
? pkg.latestVersionSummary?.artifact
|
||||
: isPublishedPackageRelease(latestRelease)
|
||||
? summarizePackageReleaseArtifact(latestRelease)
|
||||
? packageArtifactSummary(latestRelease)
|
||||
: undefined,
|
||||
clawManifestSummary:
|
||||
pkg.family === "claw" && isPublishedPackageRelease(latestRelease)
|
||||
@@ -1348,6 +1345,41 @@ async function paginatePublishedPackageReleases(
|
||||
return { page, isDone, continueCursor: isDone ? "" : continueCursor };
|
||||
}
|
||||
|
||||
function packageArtifactSummary(
|
||||
release: Pick<
|
||||
Doc<"packageReleases">,
|
||||
| "artifactKind"
|
||||
| "clawpackSha256"
|
||||
| "sha256hash"
|
||||
| "clawpackSize"
|
||||
| "clawpackFormat"
|
||||
| "npmIntegrity"
|
||||
| "npmShasum"
|
||||
| "npmTarballName"
|
||||
| "npmUnpackedSize"
|
||||
| "npmFileCount"
|
||||
>,
|
||||
): PackageArtifactSummary {
|
||||
if (release.artifactKind === "npm-pack") {
|
||||
return {
|
||||
kind: "npm-pack",
|
||||
sha256: getPackageReleaseArtifactSha256(release) ?? undefined,
|
||||
size: release.clawpackSize,
|
||||
format: release.clawpackFormat ?? "tgz",
|
||||
npmIntegrity: release.npmIntegrity,
|
||||
npmShasum: release.npmShasum,
|
||||
npmTarballName: release.npmTarballName,
|
||||
npmUnpackedSize: release.npmUnpackedSize,
|
||||
npmFileCount: release.npmFileCount,
|
||||
};
|
||||
}
|
||||
return {
|
||||
kind: "legacy-zip",
|
||||
sha256: getPackageReleaseArtifactSha256(release) ?? undefined,
|
||||
format: "zip",
|
||||
};
|
||||
}
|
||||
|
||||
function digestMatchesFilters(
|
||||
digest: PackageDigestLike,
|
||||
args: {
|
||||
@@ -5740,7 +5772,7 @@ function packageLatestSummaryFromRelease(release: Doc<"packageReleases"> | null)
|
||||
icon: release.icon,
|
||||
compatibility: release.compatibility,
|
||||
verification: release.verification,
|
||||
artifact: summarizePackageReleaseArtifact(release),
|
||||
artifact: packageArtifactSummary(release),
|
||||
}
|
||||
: undefined;
|
||||
}
|
||||
@@ -5841,7 +5873,7 @@ async function restorePackageDoc(
|
||||
icon: nextLatest.icon,
|
||||
compatibility: nextLatest.compatibility,
|
||||
verification: nextLatest.verification,
|
||||
artifact: summarizePackageReleaseArtifact(nextLatest),
|
||||
artifact: packageArtifactSummary(nextLatest),
|
||||
}
|
||||
: undefined,
|
||||
summary: nextLatest?.summary,
|
||||
@@ -10889,7 +10921,7 @@ export const insertReleaseInternal = internalMutation({
|
||||
icon: args.icon,
|
||||
compatibility: args.compatibility,
|
||||
verification: releaseVerification,
|
||||
artifact: summarizePackageReleaseArtifact(args),
|
||||
artifact: packageArtifactSummary(args),
|
||||
}
|
||||
: pkg.latestVersionSummary,
|
||||
tags: nextTags,
|
||||
|
||||
Reference in New Issue
Block a user