源码快照 @ 2026-09-02 (source-only, 剔除非源码素材)
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
import { execFile } from "node:child_process";
|
||||
import { constants } from "node:fs";
|
||||
import { access, readFile, rm, writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { promisify } from "node:util";
|
||||
import { deleteSandboxes, provisionDenSandbox, provisionDesktopSandbox } from "../packages/hosts/src/provision.ts";
|
||||
import { shouldPrepareSuite, suiteWorkerCount } from "./stack-suite.ts";
|
||||
import type { TestProject } from "vitest/node";
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
const REPO_ROOT = fileURLToPath(new URL("../..", import.meta.url));
|
||||
|
||||
export type StackPreparation =
|
||||
| { kind: "none" }
|
||||
| { kind: "local"; runtimePrepared: true; electronPrepared: true }
|
||||
| { kind: "daytona"; slots: { denSandbox: string; desktopSandbox: string }[] };
|
||||
|
||||
declare module "vitest" {
|
||||
export interface ProvidedContext {
|
||||
openworkStackPreparation: StackPreparation;
|
||||
}
|
||||
}
|
||||
|
||||
async function run(command: string, args: string[], cwd = REPO_ROOT): Promise<void> {
|
||||
await execFileAsync(command, args, {
|
||||
cwd,
|
||||
env: process.env,
|
||||
maxBuffer: 32 * 1024 * 1024,
|
||||
timeout: 20 * 60 * 1_000,
|
||||
});
|
||||
}
|
||||
|
||||
async function readable(path: string): Promise<boolean> {
|
||||
return access(path, constants.R_OK).then(() => true).catch(() => false);
|
||||
}
|
||||
|
||||
async function prepareLocal(): Promise<StackPreparation> {
|
||||
console.error("[openwork/evals] preparing shared local Den and Electron runtime once...");
|
||||
const runtimeBuilds = [
|
||||
"@openwork/email",
|
||||
"@openwork/install-config",
|
||||
"@openwork/connect-link",
|
||||
"@openwork/enterprise-mcp-client",
|
||||
"@openwork/codemode",
|
||||
"@openwork/headless-threads",
|
||||
].map((pkg) => run("pnpm", ["--filter", pkg, "build"]));
|
||||
await Promise.all(runtimeBuilds);
|
||||
await run("pnpm", ["--filter", "@openwork-ee/den-db", "build"]);
|
||||
|
||||
await Promise.all([
|
||||
run("pnpm", ["--filter", "@openwork/ui", "build"]),
|
||||
run("pnpm", ["--filter", "@openwork-ee/utils", "build"]),
|
||||
run(process.execPath, [join(REPO_ROOT, "apps/desktop/scripts/prepare-sidecar.mjs"), "--force", "--outdir", join(REPO_ROOT, "apps/desktop/resources/sidecars")], join(REPO_ROOT, "apps/desktop")),
|
||||
run(process.execPath, [join(REPO_ROOT, "apps/desktop/scripts/prepare-computer-use-helper.mjs"), "--force", "--outdir", join(REPO_ROOT, "apps/desktop/resources/helpers")], join(REPO_ROOT, "apps/desktop")),
|
||||
]);
|
||||
|
||||
const nextEnvPath = join(REPO_ROOT, "ee/apps/den-web/next-env.d.ts");
|
||||
const hadNextEnv = await readable(nextEnvPath);
|
||||
const nextEnv = hadNextEnv ? await readFile(nextEnvPath) : null;
|
||||
try {
|
||||
await run("pnpm", ["--filter", "@openwork-ee/den-web", "build"]);
|
||||
} finally {
|
||||
if (nextEnv) await writeFile(nextEnvPath, nextEnv);
|
||||
else if (!hadNextEnv) await rm(nextEnvPath, { force: true });
|
||||
}
|
||||
await rm(join(REPO_ROOT, "ee/apps/den-web/.next/dev"), { recursive: true, force: true });
|
||||
return { kind: "local", runtimePrepared: true, electronPrepared: true };
|
||||
}
|
||||
|
||||
async function prepareDaytona(argv: readonly string[]): Promise<{ preparation: StackPreparation; cleanup: () => Promise<void> }> {
|
||||
const workerCount = suiteWorkerCount(argv, process.env);
|
||||
const ref = process.env.OPENWORK_EVAL_REF?.trim() || process.env.GITHUB_SHA?.trim() || "dev";
|
||||
const created = new Set<string>();
|
||||
const log = (line: string): void => console.error(`[openwork/evals] ${line}`);
|
||||
try {
|
||||
const slots = await Promise.all(Array.from({ length: workerCount }, async (_, index) => {
|
||||
const [den, desktop] = await Promise.all([
|
||||
provisionDenSandbox({
|
||||
ref,
|
||||
bootstrapAdminEmail: process.env.OPENWORK_EVAL_DEMO_EMAIL?.trim() || "alex@acme.test",
|
||||
log,
|
||||
}).then((result) => {
|
||||
if (result.created) created.add(result.sandbox);
|
||||
return result;
|
||||
}),
|
||||
provisionDesktopSandbox({ ref, name: `suite-${index + 1}`, log }).then((result) => {
|
||||
if (result.created) created.add(result.sandbox);
|
||||
return result;
|
||||
}),
|
||||
]);
|
||||
return { denSandbox: den.sandbox, desktopSandbox: desktop.sandbox };
|
||||
}));
|
||||
console.error(`[openwork/evals] prepared ${slots.length} isolated Daytona worker slot${slots.length === 1 ? "" : "s"}.`);
|
||||
return {
|
||||
preparation: { kind: "daytona", slots },
|
||||
cleanup: async () => {
|
||||
await deleteSandboxes([...created], { log });
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
await deleteSandboxes([...created], { log }).catch(() => undefined);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export default async function setup(project: TestProject): Promise<() => Promise<void>> {
|
||||
if (!shouldPrepareSuite(process.argv) || process.env.OPENWORK_EVAL_DEN_API_URL?.trim()) {
|
||||
project.provide("openworkStackPreparation", { kind: "none" });
|
||||
return async () => undefined;
|
||||
}
|
||||
if (process.env.OPENWORK_EVAL_DAYTONA?.trim() === "1") {
|
||||
const prepared = await prepareDaytona(process.argv);
|
||||
project.provide("openworkStackPreparation", prepared.preparation);
|
||||
return prepared.cleanup;
|
||||
}
|
||||
const preparation = await prepareLocal();
|
||||
project.provide("openworkStackPreparation", preparation);
|
||||
return async () => undefined;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { inject } from "vitest";
|
||||
import { workerSlot } from "./stack-suite.ts";
|
||||
|
||||
const preparation = inject("openworkStackPreparation");
|
||||
|
||||
if (preparation.kind === "local") {
|
||||
process.env.OPENWORK_EVAL_DEN_RUNTIME_PREPARED = "1";
|
||||
process.env.OPENWORK_EVAL_ELECTRON_RESOURCES_PREPARED = "1";
|
||||
}
|
||||
|
||||
if (preparation.kind === "daytona") {
|
||||
const slot = preparation.slots[workerSlot(process.env.VITEST_WORKER_ID, preparation.slots.length)];
|
||||
if (!slot) throw new Error("Vitest worker did not resolve to a prepared Daytona slot.");
|
||||
process.env.OPENWORK_EVAL_DAYTONA_DEN_SANDBOX = slot.denSandbox;
|
||||
process.env.OPENWORK_EVAL_DAYTONA_DESKTOP_SANDBOX = slot.desktopSandbox;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
import { shouldPrepareSuite, suiteWorkerCount, workerSlot } from "./stack-suite.ts";
|
||||
|
||||
test("single explicit E2E tests keep their one-off setup", () => {
|
||||
assert.equal(shouldPrepareSuite(["vitest", "specs/example.e2e.test.ts"]), false);
|
||||
});
|
||||
|
||||
test("multi-file, glob, and whole-project runs prepare shared stack resources", () => {
|
||||
assert.equal(shouldPrepareSuite(["vitest", "specs/a.e2e.test.ts", "specs/b.e2e.test.ts"]), true);
|
||||
assert.equal(shouldPrepareSuite(["vitest", "specs/*.e2e.test.ts"]), true);
|
||||
assert.equal(shouldPrepareSuite(["vitest"]), true);
|
||||
});
|
||||
|
||||
test("Daytona defaults to two workers and never prepares more slots than explicit files", () => {
|
||||
assert.equal(suiteWorkerCount(["vitest", "specs/a.test.ts", "specs/b.test.ts"], { OPENWORK_EVAL_DAYTONA: "1" }), 2);
|
||||
assert.equal(suiteWorkerCount(["vitest", "specs/a.test.ts"], { OPENWORK_EVAL_DAYTONA: "1", OPENWORK_EVAL_MAX_WORKERS: "8" }), 1);
|
||||
assert.equal(suiteWorkerCount(["vitest", "specs/*.test.ts"], { OPENWORK_EVAL_DAYTONA: "1", OPENWORK_EVAL_MAX_WORKERS: "4" }), 4);
|
||||
});
|
||||
|
||||
test("worker ids wrap onto the prepared slot pool", () => {
|
||||
assert.equal(workerSlot("1", 2), 0);
|
||||
assert.equal(workerSlot("2", 2), 1);
|
||||
assert.equal(workerSlot("3", 2), 0);
|
||||
assert.equal(workerSlot(undefined, 2), 0);
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
const TEST_FILE = /\.test\.[cm]?[jt]sx?$/;
|
||||
const GLOB_MARKER = /[*?{}[\]]/;
|
||||
|
||||
function explicitTestFiles(argv: readonly string[]): string[] {
|
||||
return argv.filter((argument) => TEST_FILE.test(argument) && !GLOB_MARKER.test(argument));
|
||||
}
|
||||
|
||||
export function shouldPrepareSuite(argv: readonly string[]): boolean {
|
||||
const testArguments = argv.filter((argument) => argument.includes(".test.") || GLOB_MARKER.test(argument));
|
||||
return testArguments.length !== 1 || explicitTestFiles(testArguments).length !== 1;
|
||||
}
|
||||
|
||||
function configuredWorkerCount(env: NodeJS.ProcessEnv): number {
|
||||
const configured = Number.parseInt(env.OPENWORK_EVAL_MAX_WORKERS?.trim() ?? "", 10);
|
||||
if (Number.isInteger(configured) && configured > 0) return configured;
|
||||
return env.OPENWORK_EVAL_DAYTONA?.trim() === "1" ? 2 : 3;
|
||||
}
|
||||
|
||||
export function suiteWorkerCount(argv: readonly string[], env: NodeJS.ProcessEnv): number {
|
||||
const fileCount = explicitTestFiles(argv).length;
|
||||
const workers = configuredWorkerCount(env);
|
||||
return fileCount > 0 ? Math.min(fileCount, workers) : workers;
|
||||
}
|
||||
|
||||
export function workerSlot(workerId: string | undefined, slotCount: number): number {
|
||||
if (!Number.isInteger(slotCount) || slotCount < 1) throw new Error("Stack preparation requires at least one worker slot.");
|
||||
const parsed = Number.parseInt(workerId ?? "", 10);
|
||||
const worker = Number.isInteger(parsed) && parsed > 0 ? parsed : 1;
|
||||
return (worker - 1) % slotCount;
|
||||
}
|
||||
Reference in New Issue
Block a user