Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ updates:
directory: "/"
schedule:
interval: "monthly"
open-pull-requests-limit: 3 # Raised from 1: actions are SHA-pinned, so SHA bumps come as patch/minor PRs.
open-pull-requests-limit: 3 # Grouped below into one PR; headroom in case a major is held out of the group.
labels:
- "dependencies"
- "github-actions"
Expand All @@ -36,3 +36,29 @@ updates:
prefix: "chore"
# No ignore block — keep SHA pins current for patch and minor releases too,
# otherwise SHA pinning fossilizes the actions in place.
# One batched PR instead of one per action, matching the npm block below.
groups:
actions-all:
patterns: ["*"]

# evalboard frontend (npm). Without this block nothing routinely refreshes the
# Next.js app, so it drifts until a CVE fires and alerts pile up. Monthly,
# grouped bumps keep next/React/Azure ahead of most advisories.
- package-ecosystem: "npm"
directory: "/evalboard"
schedule:
interval: "monthly"
open-pull-requests-limit: 3
labels:
- "dependencies"
- "javascript"
# `chore` keeps the commit + PR title within the Conventional Commits
# allowed-type set enforced by conventional-commits.yml (see pip block).
commit-message:
prefix: "chore"
include: "scope"
# One batched PR instead of N noisy ones, matching the low-noise style of
# the pip/actions blocks above.
groups:
npm-all:
patterns: ["*"]
34 changes: 22 additions & 12 deletions evalboard/lib/blob.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import { promises as fs } from "node:fs";
import path from "node:path";
import { randomBytes } from "node:crypto";
import { DefaultAzureCredential } from "@azure/identity";
import {
BlobServiceClient,
RestError,
type ContainerClient,
} from "@azure/storage-blob";
import type { ContainerClient } from "@azure/storage-blob";

const ACCOUNT = "coderevaltests";
const CONTAINER = "runs";
Expand Down Expand Up @@ -56,14 +51,28 @@ function assertValidTaskId(id: string, label: string): void {
}
}

// Duck-typed 404 check. Avoids a runtime import of `RestError` from
// @azure/storage-blob so local-mode readers never load the Azure SDK — the
// blob client and its error type only arrive via the dynamic import in
// getContainer(). Azure's RestError carries a numeric `statusCode`.
function isNotFound(err: unknown): boolean {
return err instanceof RestError && err.statusCode === 404;
return (
typeof err === "object" &&
err !== null &&
"statusCode" in err &&
(err as { statusCode?: number }).statusCode === 404
);
}

let containerClient: ContainerClient | null = null;

function getContainer(): ContainerClient {
// The Azure SDK is loaded lazily (and lives under optionalDependencies) so
// local-mode readers — and OSS users who `pnpm install --no-optional` — never
// pull @azure/*. This only runs in remote mode, where every caller awaits it.
async function getContainer(): Promise<ContainerClient> {
if (containerClient) return containerClient;
const { BlobServiceClient } = await import("@azure/storage-blob");
const { DefaultAzureCredential } = await import("@azure/identity");
const url = `https://${ACCOUNT}.blob.core.windows.net`;
const svc = new BlobServiceClient(url, new DefaultAzureCredential());
containerClient = svc.getContainerClient(CONTAINER);
Expand All @@ -72,7 +81,7 @@ function getContainer(): ContainerClient {

export async function listRunIdsRemote(): Promise<string[]> {
if (LOCAL_RUNS_DIR) return listRunIdsLocal(LOCAL_RUNS_DIR);
const c = getContainer();
const c = await getContainer();
const ids: string[] = [];
for await (const item of c.listBlobsByHierarchy("/")) {
if (item.kind === "prefix") {
Expand Down Expand Up @@ -126,7 +135,8 @@ async function downloadBlob(
// each other's temp file.
const tmpPath = `${localPath}.${randomBytes(6).toString("hex")}.tmp`;
try {
await getContainer().getBlobClient(blobName).downloadToFile(tmpPath);
const c = await getContainer();
await c.getBlobClient(blobName).downloadToFile(tmpPath);
await fs.rename(tmpPath, localPath);
} catch (err) {
await fs.unlink(tmpPath).catch(() => {});
Expand Down Expand Up @@ -242,7 +252,7 @@ export async function ensureRunDir(
assertValidId(runId, "runId");
if (LOCAL_RUNS_DIR) return;
return dedupe(`run:${runId}`, async () => {
const c = getContainer();
const c = await getContainer();
const ops: Promise<void>[] = [];
const prefix = `${runId}/`;
for await (const blob of c.listBlobsFlat({ prefix })) {
Expand Down Expand Up @@ -271,7 +281,7 @@ export async function ensureTaskDir(
const activation = taskId.startsWith("skill-activation/");
if (activation) await ensureActivationSummary(runId, destRoot);
else await ensureRunSummary(runId, destRoot);
const c = getContainer();
const c = await getContainer();
const ops: Promise<void>[] = [];
// `listBlobsFlat` recurses, so both the flat legacy layout
// (`default/<task>/task.json`) and the nested replicate layout
Expand Down
16 changes: 13 additions & 3 deletions evalboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,17 @@
"verify": "tsc --noEmit && vitest run && next build"
},
"dependencies": {
"@azure/identity": "^4.13.1",
"@azure/storage-blob": "^12.31.0",
"next": "^15.1.0",
"next": "^15.5.20",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-markdown": "^10.1.0",
"recharts": "^2.15.0",
"remark-gfm": "^4.0.1"
},
"optionalDependencies": {
"@azure/identity": "^4.13.1",
"@azure/storage-blob": ">=12.31.0 <12.33.0"
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.19",
"@testing-library/jest-dom": "^6.0.0",
Expand All @@ -36,5 +38,13 @@
"tailwindcss": "^3.4.17",
"typescript": "^5.6.3",
"vitest": "^2.1.0"
},
"pnpm": {
"overrides": {
"postcss": "^8.5.10",
"fast-xml-builder": "^1.1.7",
"@babel/core": "^7.29.6",
"uuid": "^11.1.1"
}
}
}
Loading
Loading