feat: dashboard agent - package upgrades (#3793)

1. in webapp folder update ai-sdk to 6.x.x
2. update vitest to 4.xx
This commit is contained in:
Dan
2026-06-02 10:46:34 +01:00
committed by GitHub
parent 4c4ed22e82
commit cd252801eb
30 changed files with 515 additions and 576 deletions
+2 -2
View File
@@ -96,7 +96,7 @@ jobs:
run: pnpm run generate
- name: 🧪 Run Internal Unit Tests
run: pnpm run test:internal --reporter=default --reporter=blob --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
run: pnpm run test:internal --reporter=default --reporter=blob --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --passWithNoTests
- name: Gather all reports
if: ${{ !cancelled() }}
@@ -145,4 +145,4 @@ jobs:
merge-multiple: true
- name: Merge reports
run: pnpm dlx vitest@3.1.4 run --merge-reports --pass-with-no-tests
run: pnpm dlx vitest@4.1.7 run --merge-reports --pass-with-no-tests
+2 -2
View File
@@ -96,7 +96,7 @@ jobs:
run: pnpm run generate
- name: 🧪 Run Package Unit Tests
run: pnpm run test:packages --reporter=default --reporter=blob --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
run: pnpm run test:packages --reporter=default --reporter=blob --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --passWithNoTests
- name: Gather all reports
if: ${{ !cancelled() }}
@@ -145,4 +145,4 @@ jobs:
merge-multiple: true
- name: Merge reports
run: pnpm dlx vitest@3.1.4 run --merge-reports --pass-with-no-tests
run: pnpm dlx vitest@4.1.7 run --merge-reports --pass-with-no-tests
+2 -2
View File
@@ -96,7 +96,7 @@ jobs:
run: pnpm run generate
- name: 🧪 Run Webapp Unit Tests
run: pnpm run test:webapp --reporter=default --reporter=blob --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
run: pnpm run test:webapp --reporter=default --reporter=blob --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --passWithNoTests
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/postgres
DIRECT_URL: postgresql://postgres:postgres@localhost:5432/postgres
@@ -153,4 +153,4 @@ jobs:
merge-multiple: true
- name: Merge reports
run: pnpm dlx vitest@3.1.4 run --merge-reports --pass-with-no-tests
run: pnpm dlx vitest@4.1.7 run --merge-reports --pass-with-no-tests
@@ -116,19 +116,19 @@ export async function action({ request, params }: ActionFunctionArgs) {
for await (const part of result.fullStream) {
switch (part.type) {
case "text-delta": {
sendEvent({ type: "thinking", content: part.textDelta });
sendEvent({ type: "thinking", content: part.text });
break;
}
case "tool-call": {
sendEvent({
type: "tool_call",
tool: part.toolName,
args: part.args,
args: part.input,
});
// If it's a setTimeFilter call, emit the time_filter event immediately
if (part.toolName === "setTimeFilter") {
const args = part.args as { period?: string; from?: string; to?: string };
const args = part.input as { period?: string; from?: string; to?: string };
sendEvent({
type: "time_filter",
filter: {
@@ -1,5 +1,5 @@
import { openai } from "@ai-sdk/openai";
import { streamText, tool } from "ai";
import { streamText, stepCountIs, tool } from "ai";
import { type ActionFunctionArgs } from "@remix-run/server-runtime";
import { z } from "zod";
import { env } from "~/env.server";
@@ -105,19 +105,19 @@ export async function action({ request, params }: ActionFunctionArgs) {
getTaskSourceCode: tool({
description:
"Look up the source code of the task to understand what payload shape it expects. Use this when there is no JSON Schema available and you need to infer the payload structure from the task implementation.",
parameters: z.object({}),
inputSchema: z.object({}),
execute: async () => {
return getTaskSourceCode(environment.id, environment.type, taskIdentifier);
},
}),
},
maxSteps: 3,
stopWhen: stepCountIs(3),
});
for await (const part of result.fullStream) {
switch (part.type) {
case "text-delta": {
sendEvent({ type: "thinking", content: part.textDelta });
sendEvent({ type: "thinking", content: part.text });
break;
}
case "tool-call": {
@@ -5,7 +5,7 @@ import {
type TableSchema,
type ValidationIssue,
} from "@internal/tsql";
import { streamText, type LanguageModelV1, tool } from "ai";
import { streamText, stepCountIs, type LanguageModel, tool } from "ai";
import { z } from "zod";
import type { AITimeFilter } from "~/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query/types";
@@ -55,7 +55,7 @@ export class AIQueryService {
constructor(
private readonly tableSchema: TableSchema[],
private readonly model: LanguageModelV1 = openai("gpt-4.1-mini")
private readonly model: LanguageModel = openai("gpt-4.1-mini")
) {}
/**
@@ -66,7 +66,7 @@ export class AIQueryService {
return tool({
description:
"Set the time filter for the query page UI instead of adding time conditions to the query. ALWAYS use this tool when the user wants to filter by time (e.g., 'last 7 days', 'past hour', 'yesterday'). The UI will apply this filter automatically using the table's time column (triggered_at for runs, bucket_start for metrics). Do NOT add triggered_at or bucket_start to the WHERE clause for time filtering - use this tool instead.",
parameters: z.object({
inputSchema: z.object({
period: z
.string()
.optional()
@@ -125,7 +125,7 @@ export class AIQueryService {
validateTSQLQuery: tool({
description:
"Validate a TSQL query for syntax errors and schema compliance. Always use this tool to verify your query before returning it to the user.",
parameters: z.object({
inputSchema: z.object({
query: z.string().describe("The TSQL query to validate"),
}),
execute: async ({ query }) => {
@@ -135,7 +135,7 @@ export class AIQueryService {
getTableSchema: tool({
description:
"Get detailed schema information about available tables and columns. Use this to understand what data is available and how to query it.",
parameters: z.object({
inputSchema: z.object({
tableName: z
.string()
.optional()
@@ -147,7 +147,7 @@ export class AIQueryService {
}),
setTimeFilter: this.buildSetTimeFilterTool(),
},
maxSteps: 5,
stopWhen: stepCountIs(5),
experimental_telemetry: {
isEnabled: true,
metadata: {
@@ -191,7 +191,7 @@ export class AIQueryService {
validateTSQLQuery: tool({
description:
"Validate a TSQL query for syntax errors and schema compliance. Always use this tool to verify your query before returning it to the user.",
parameters: z.object({
inputSchema: z.object({
query: z.string().describe("The TSQL query to validate"),
}),
execute: async ({ query }) => {
@@ -201,7 +201,7 @@ export class AIQueryService {
getTableSchema: tool({
description:
"Get detailed schema information about available tables and columns. Use this to understand what data is available and how to query it.",
parameters: z.object({
inputSchema: z.object({
tableName: z
.string()
.optional()
@@ -213,7 +213,7 @@ export class AIQueryService {
}),
setTimeFilter: this.buildSetTimeFilterTool(),
},
maxSteps: 5,
stopWhen: stepCountIs(5),
experimental_telemetry: {
isEnabled: true,
metadata: {
@@ -1,5 +1,5 @@
import { openai } from "@ai-sdk/openai";
import { generateText, type LanguageModelV1 } from "ai";
import { generateText, type LanguageModel } from "ai";
import { env } from "~/env.server";
/**
@@ -13,7 +13,7 @@ export type AIQueryTitleResult =
* Service for generating concise titles for SQL queries using AI
*/
export class AIQueryTitleService {
constructor(private readonly model: LanguageModelV1 = openai("gpt-4o-mini")) {}
constructor(private readonly model: LanguageModel = openai("gpt-4o-mini")) {}
/**
* Generate a concise title for a SQL query
@@ -45,7 +45,7 @@ Examples:
- "Average execution time by task"
- "Recent runs with errors"`,
prompt: `Generate a concise title for this SQL query:\n\n${query}`,
maxTokens: 50,
maxOutputTokens: 50,
experimental_telemetry: {
isEnabled: true,
metadata: {
@@ -1,6 +1,6 @@
import { openai } from "@ai-sdk/openai";
import { type TaskTriggerSource } from "@trigger.dev/database";
import { generateText, LanguageModelV1, Output, tool } from "ai";
import { generateText, stepCountIs, type LanguageModel, Output, tool } from "ai";
import { z } from "zod";
import { TaskRunListSearchFilters } from "~/components/runs/v3/RunFilters";
import { logger } from "~/services/logger.server";
@@ -14,17 +14,25 @@ const AIFilters = TaskRunListSearchFilters.omit({
to: z.string().optional().describe("The ISO datetime to filter to"),
});
// The response is wrapped in an object with a single `response` field because
// OpenAI structured outputs (both the Chat and Responses APIs) require the root
// JSON Schema to be `type: "object"`. A bare `z.discriminatedUnion` compiles to
// a root-level `anyOf`, which OpenAI rejects ("schema must be of type object").
// Nesting the union under a property keeps the discriminated-union ergonomics
// while satisfying the root-object constraint.
const AIFilterResponseSchema = z
.discriminatedUnion("success", [
z.object({
success: z.literal(true),
filters: AIFilters,
}),
z.object({
success: z.literal(false),
error: z.string().describe("A short human-readable error message"),
}),
])
.object({
response: z.discriminatedUnion("success", [
z.object({
success: z.literal(true),
filters: AIFilters,
}),
z.object({
success: z.literal(false),
error: z.string().describe("A short human-readable error message"),
}),
]),
})
.describe("The response from the AI filter service");
export interface QueryQueues {
@@ -80,7 +88,7 @@ export class AIRunFilterService {
queryQueues: QueryQueues;
queryTasks: QueryTasks;
},
private readonly model: LanguageModelV1 = openai("gpt-4o-mini")
private readonly model: LanguageModel = openai("gpt-4o-mini")
) {}
async call(text: string, environmentId: string): Promise<AIFilterResult> {
@@ -88,10 +96,20 @@ export class AIRunFilterService {
const result = await generateText({
model: this.model,
experimental_output: Output.object({ schema: AIFilterResponseSchema }),
// Disable OpenAI strict JSON-schema mode. The filters schema has many
// optional fields, and strict mode requires every property to appear in
// `required` (it rejects bare optionals). Non-strict mode treats the
// schema as guidance; the `AIFilters.safeParse` below still validates
// the result, so correctness is preserved.
providerOptions: {
openai: {
strictJsonSchema: false,
},
},
tools: {
lookupTags: tool({
description: "Look up available tags in the environment",
parameters: z.object({
inputSchema: z.object({
query: z.string().optional().describe("Optional search query to filter tags"),
}),
execute: async ({ query }) => {
@@ -101,7 +119,7 @@ export class AIRunFilterService {
lookupVersions: tool({
description:
"Look up available versions in the environment. If you specify `isCurrent` it will return a single version string if it finds one. Otherwise it will return an array of version strings.",
parameters: z.object({
inputSchema: z.object({
isCurrent: z
.boolean()
.optional()
@@ -119,7 +137,7 @@ export class AIRunFilterService {
}),
lookupQueues: tool({
description: "Look up available queues in the environment",
parameters: z.object({
inputSchema: z.object({
query: z.string().optional().describe("Optional search query to filter queues"),
type: z
.enum(["task", "custom"])
@@ -135,13 +153,13 @@ export class AIRunFilterService {
lookupTasks: tool({
description:
"Look up available tasks in the environment. It will return each one. The `slug` is used for the filtering. You also get the triggerSource which is either `STANDARD` or `SCHEDULED`",
parameters: z.object({}),
inputSchema: z.object({}),
execute: async () => {
return await this.queryFns.queryTasks.query();
},
}),
},
maxSteps: 5,
stopWhen: stepCountIs(5),
system: `You are an AI assistant that converts natural language descriptions into structured filter parameters for a task run filtering system.
Available filter options:
@@ -198,21 +216,24 @@ export class AIRunFilterService {
The filters object should only contain the fields that are actually being filtered. Do not include fields with empty arrays or undefined values.
CRITICAL: The response must be a valid JSON object with exactly this structure:
CRITICAL: The response must be a valid JSON object with a single top-level "response" key wrapping this structure:
{
"success": true,
"filters": {
// only include fields that have actual values
},
"explanation": "string explaining what filters were applied"
"response": {
"success": true,
"filters": {
// only include fields that have actual values
}
}
}
or if you can't figure out the filters then return:
{
"success": false,
"error": "<short human understandable suggestion>"
"response": {
"success": false,
"error": "<short human understandable suggestion>"
}
}
Make the error no more than 8 words.
`,
prompt: text,
@@ -224,19 +245,21 @@ export class AIRunFilterService {
},
});
if (!result.experimental_output.success) {
const output = result.experimental_output.response;
if (!output.success) {
return {
success: false,
error: result.experimental_output.error,
error: output.error,
};
}
// Validate the filters against the schema to catch any issues
const validationResult = AIFilters.safeParse(result.experimental_output.filters);
const validationResult = AIFilters.safeParse(output.filters);
if (!validationResult.success) {
logger.error("AI filter validation failed", {
errors: validationResult.error.errors,
filters: result.experimental_output.filters,
filters: output.filters,
});
return {
@@ -245,14 +268,35 @@ export class AIRunFilterService {
};
}
// `from`/`to` are validated as strings, so a malformed value (e.g. the
// model returning a non-ISO date) would survive safeParse and then
// produce NaN here. NaN serializes to `null` over JSON, silently dropping
// the date constraint while still reporting success — so reject it.
const from = validationResult.data.from
? new Date(validationResult.data.from).getTime()
: undefined;
const to = validationResult.data.to
? new Date(validationResult.data.to).getTime()
: undefined;
if ((from !== undefined && Number.isNaN(from)) || (to !== undefined && Number.isNaN(to))) {
logger.error("AI filter returned an invalid datetime", {
from: validationResult.data.from,
to: validationResult.data.to,
});
return {
success: false,
error: "AI response contained an invalid date",
};
}
return {
success: true,
filters: {
...validationResult.data,
from: validationResult.data.from
? new Date(validationResult.data.from).getTime()
: undefined,
to: validationResult.data.to ? new Date(validationResult.data.to).getTime() : undefined,
from,
to,
},
};
} catch (error) {
+12
View File
@@ -0,0 +1,12 @@
import { defineConfig } from "evalite/config";
import tsconfigPaths from "vite-tsconfig-paths";
// evalite 1.0 runs its own Vite instance and does not pick up `vitest.config.ts`,
// so the `~/*` -> `./app/*` path alias must be wired in explicitly here (mirrors
// the plugin setup in vitest.config.ts).
export default defineConfig({
viteConfig: {
// @ts-ignore - vite-tsconfig-paths plugin type vs evalite's bundled vite version
plugins: [tsconfigPaths({ projects: ["./tsconfig.json"] })],
},
});
+2 -2
View File
@@ -3,7 +3,7 @@ import { Levenshtein } from "autoevals";
import { AIQueryService } from "~/v3/services/aiQueryService.server";
import { runsSchema } from "~/v3/querySchemas";
import dotenv from "dotenv";
import { traceAISDKModel } from "evalite/ai-sdk";
import { wrapAISDKModel } from "evalite/ai-sdk";
import { openai } from "@ai-sdk/openai";
dotenv.config({ path: "../../.env" });
@@ -365,7 +365,7 @@ LIMIT 100`,
];
},
task: async (input) => {
const service = new AIQueryService([runsSchema], traceAISDKModel(openai("gpt-4o-mini")));
const service = new AIQueryService([runsSchema], wrapAISDKModel(openai("gpt-4o-mini")));
const result = await service.call(input);
return JSON.stringify(result);
+2 -2
View File
@@ -8,7 +8,7 @@ import {
type QueryVersions,
} from "~/v3/services/aiRunFilterService.server";
import dotenv from "dotenv";
import { traceAISDKModel } from "evalite/ai-sdk";
import { wrapAISDKModel } from "evalite/ai-sdk";
import { openai } from "@ai-sdk/openai";
dotenv.config({ path: "../../.env" });
@@ -272,7 +272,7 @@ evalite("AI Run Filter", {
queryQueues,
queryTasks,
},
traceAISDKModel(openai("gpt-4o-mini"))
wrapAISDKModel(openai("gpt-4o-mini"))
);
const result = await service.call(input, "123456");
+3 -3
View File
@@ -27,7 +27,7 @@
"/public/build"
],
"dependencies": {
"@ai-sdk/openai": "^1.3.23",
"@ai-sdk/openai": "^3.0.0",
"@ai-sdk/react": "^3.0.0",
"@ariakit/react": "^0.4.6",
"@ariakit/react-core": "^0.4.6",
@@ -138,7 +138,7 @@
"@vercel/sdk": "^1.19.1",
"@whatwg-node/fetch": "^0.9.14",
"@window-splitter/react": "1.1.3",
"ai": "^4.3.19",
"ai": "^6.0.116",
"assert-never": "^1.2.1",
"aws4fetch": "^1.0.18",
"class-variance-authority": "^0.5.2",
@@ -286,7 +286,7 @@
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-turbo": "^2.0.4",
"evalite": "^0.11.4",
"evalite": "1.0.0-beta.16",
"npm-run-all": "^4.1.5",
"postcss-import": "^16.0.1",
"postcss-loader": "^8.1.1",
@@ -513,6 +513,7 @@ describe("RunsReplicationService (part 2/2)", () => {
containerTest(
"should be able to handle processing transactions for a long period of time",
{ timeout: 60_000 * 5 },
async ({ clickhouseContainer, redisOptions, postgresContainer, prisma }) => {
await prisma.$executeRawUnsafe(`ALTER TABLE public."TaskRun" REPLICA IDENTITY FULL;`);
@@ -614,8 +615,7 @@ describe("RunsReplicationService (part 2/2)", () => {
expect(result?.length).toBeGreaterThanOrEqual(50);
await runsReplicationService.stop();
},
{ timeout: 60_000 * 5 }
}
);
containerTest(
@@ -6,11 +6,6 @@ export default defineConfig({
globals: true,
isolate: true,
fileParallelism: false,
poolOptions: {
threads: {
singleThread: true,
},
},
testTimeout: 60_000,
coverage: {
provider: "v8",
@@ -11,7 +11,7 @@
},
"devDependencies": {
"@internal/testcontainers": "workspace:*",
"vitest": "3.1.4"
"vitest": "4.1.7"
},
"scripts": {
"test": "vitest --sequence.concurrent=false --no-file-parallelism",
@@ -6,11 +6,6 @@ export default defineConfig({
globals: true,
isolate: true,
fileParallelism: false,
poolOptions: {
threads: {
singleThread: true,
},
},
testTimeout: 120_000,
},
});
@@ -6,11 +6,6 @@ export default defineConfig({
globals: true,
isolate: true,
fileParallelism: false,
poolOptions: {
threads: {
singleThread: true,
},
},
testTimeout: 60_000,
coverage: {
provider: "v8",
@@ -1,4 +1,4 @@
import { TaskContext, test, TestAPI } from "vitest";
import { TestContext, test, TestAPI } from "vitest";
import {
logCleanup,
network,
@@ -36,7 +36,7 @@ type EngineOptions = {
};
};
const engineOptions = async ({}: TaskContext, use: Use<EngineOptions>) => {
const engineOptions = async ({}: TestContext, use: Use<EngineOptions>) => {
const options: EngineOptions = {
worker: {
workers: 1,
@@ -74,7 +74,7 @@ const engine = async (
engineOptions: EngineOptions;
redisOptions: RedisOptions;
prisma: PrismaClient;
} & TaskContext,
} & TestContext,
use: Use<RunEngine>
) => {
const engine = new RunEngine({
@@ -6,11 +6,6 @@ export default defineConfig({
globals: true,
isolate: true,
fileParallelism: false,
poolOptions: {
threads: {
singleThread: true,
},
},
testTimeout: 120_000,
coverage: {
provider: "v8",
@@ -15,6 +15,6 @@
"esbuild": "^0.24.0",
"execa": "^9.3.0",
"typescript": "^5.5.0",
"vitest": "3.1.4"
"vitest": "4.1.7"
}
}
@@ -9,7 +9,7 @@
"declaration": false,
"outDir": "dist",
"rootDir": "src",
"types": ["vitest/globals"]
"types": ["vitest/globals", "node"]
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules", "dist", "src/fixtures"]
@@ -3,7 +3,7 @@ import { StartedRedisContainer } from "@testcontainers/redis";
import { PrismaClient } from "@trigger.dev/database";
import { RedisOptions } from "ioredis";
import { Network, type StartedNetwork } from "testcontainers";
import { TaskContext, test } from "vitest";
import { TestContext, test } from "vitest";
import {
createClickHouseContainer,
createElectricContainer,
@@ -58,7 +58,7 @@ export type {
type Use<T> = (value: T) => Promise<void>;
export const network = async ({ task }: TaskContext, use: Use<StartedNetwork>) => {
export const network = async ({ task }: TestContext, use: Use<StartedNetwork>) => {
const testName = task.name;
logSetup("network: starting", { testName });
@@ -85,7 +85,7 @@ export const network = async ({ task }: TaskContext, use: Use<StartedNetwork>) =
};
export const postgresContainer = async (
{ network, task }: { network: StartedNetwork } & TaskContext,
{ network, task }: { network: StartedNetwork } & TestContext,
use: Use<StartedPostgreSqlContainer>
) => {
const { container, metadata } = await withContainerSetup({
@@ -98,7 +98,7 @@ export const postgresContainer = async (
};
export const prisma = async (
{ postgresContainer, task }: { postgresContainer: StartedPostgreSqlContainer } & TaskContext,
{ postgresContainer, task }: { postgresContainer: StartedPostgreSqlContainer } & TestContext,
use: Use<PrismaClient>
) => {
const testName = task.name;
@@ -123,7 +123,7 @@ export const prisma = async (
export const postgresTest = test.extend<PostgresContext>({ network, postgresContainer, prisma });
export const redisContainer = async (
{ network, task }: { network: StartedNetwork } & TaskContext,
{ network, task }: { network: StartedNetwork } & TestContext,
use: Use<StartedRedisContainer>
) => {
const { container, metadata } = await withContainerSetup({
@@ -180,7 +180,7 @@ const electricOrigin = async (
postgresContainer,
network,
task,
}: { postgresContainer: StartedPostgreSqlContainer; network: StartedNetwork } & TaskContext,
}: { postgresContainer: StartedPostgreSqlContainer; network: StartedNetwork } & TestContext,
use: Use<string>
) => {
const { origin, container, metadata } = await withContainerSetup({
@@ -193,7 +193,7 @@ const electricOrigin = async (
};
const clickhouseContainer = async (
{ network, task }: { network: StartedNetwork } & TaskContext,
{ network, task }: { network: StartedNetwork } & TestContext,
use: Use<StartedClickHouseContainer>
) => {
const { container, metadata } = await withContainerSetup({
@@ -206,7 +206,7 @@ const clickhouseContainer = async (
};
const clickhouseClient = async (
{ clickhouseContainer, task }: { clickhouseContainer: StartedClickHouseContainer } & TaskContext,
{ clickhouseContainer, task }: { clickhouseContainer: StartedClickHouseContainer } & TestContext,
use: Use<ClickHouseClient>
) => {
const testName = task.name;
@@ -268,7 +268,7 @@ export const containerWithElectricAndRedisTest = test.extend<ContainerWithElectr
});
const minioContainer = async (
{ network, task }: { network: StartedNetwork } & TaskContext,
{ network, task }: { network: StartedNetwork } & TestContext,
use: Use<StartedMinIOContainer>
) => {
const { container, metadata } = await withContainerSetup({
+2 -2
View File
@@ -1,5 +1,5 @@
import { env, isCI } from "std-env";
import { TaskContext } from "vitest";
import { TestContext } from "vitest";
import { DockerDiagnostics, getDockerDiagnostics } from "./docker";
import { StartedTestContainer } from "testcontainers";
@@ -31,7 +31,7 @@ export function getContainerMetadata(container: StartedTestContainer) {
};
}
export function getTaskMetadata(task: TaskContext["task"]) {
export function getTaskMetadata(task: TestContext["task"]) {
return {
testName: task.name,
};
@@ -7,7 +7,7 @@ import path from "path";
import { isDebug } from "std-env";
import { GenericContainer, StartedNetwork, StartedTestContainer, Wait } from "testcontainers";
import { x } from "tinyexec";
import type { TaskContext } from "vitest";
import type { TestContext } from "vitest";
import { ClickHouseContainer, runClickhouseMigrations } from "./clickhouse";
import { MinIOContainer } from "./minio";
import { getContainerMetadata, getTaskMetadata, logCleanup, logSetup } from "./logs";
@@ -209,7 +209,7 @@ export async function withContainerSetup<T>({
setup,
}: {
name: string;
task: TaskContext["task"];
task: TestContext["task"];
setup: Promise<T extends { container: StartedTestContainer } ? T : never>;
}): Promise<T & { metadata: Record<string, unknown> }> {
const testName = task.name;
@@ -236,7 +236,7 @@ export async function useContainer<TContainer extends StartedTestContainer>(
container,
task,
use,
}: { container: TContainer; task: TaskContext["task"]; use: () => Promise<void> }
}: { container: TContainer; task: TestContext["task"]; use: () => Promise<void> }
) {
const metadata = {
...getTaskMetadata(task),
-5
View File
@@ -6,11 +6,6 @@ export default defineConfig({
globals: true,
isolate: true,
fileParallelism: false,
poolOptions: {
threads: {
singleThread: true,
},
},
testTimeout: 60_000,
coverage: {
provider: "v8",
+2 -2
View File
@@ -55,7 +55,7 @@
"@playwright/test": "^1.36.2",
"@trigger.dev/database": "workspace:*",
"@types/node": "20.14.14",
"@vitest/coverage-v8": "3.1.4",
"@vitest/coverage-v8": "4.1.7",
"autoprefixer": "^10.4.12",
"eslint-plugin-turbo": "^2.0.4",
"lefthook": "^1.11.3",
@@ -65,7 +65,7 @@
"turbo": "^1.10.3",
"typescript": "5.5.4",
"vite-tsconfig-paths": "^4.0.5",
"vitest": "3.1.4"
"vitest": "4.1.7"
},
"packageManager": "pnpm@10.33.2",
"dependencies": {
+8 -1
View File
@@ -104,10 +104,17 @@ describe.concurrent("buildWorker", async () => {
for (let testCase of testCases) {
test.extend<E2EFixtureTest>({
// Seed `workspaceRelativeDir` before the spread so the key always exists.
// vitest 4 resolves fixture-to-fixture dependencies strictly at
// `test.extend()` time: the `workspaceDir` fixture below destructures
// `workspaceRelativeDir`, so it must be a defined fixture even for test
// cases that don't set it (it's an optional `TestCase` field). The spread
// overrides this default when the case provides its own value.
workspaceRelativeDir: "",
...testCase,
fixtureDir: async ({ id }, use) =>
await use(path.resolve(path.join(process.cwd(), "e2e/fixtures", id))),
workspaceDir: async ({ fixtureDir, workspaceRelativeDir = "" }, use) =>
workspaceDir: async ({ fixtureDir, workspaceRelativeDir }, use) =>
await use(path.resolve(path.join(fixtureDir, workspaceRelativeDir))),
packageManager: async ({ workspaceDir }, use) =>
await use(await parsePackageManager(options.packageManager, workspaceDir)),
-5
View File
@@ -5,10 +5,5 @@ export default defineConfig({
include: ["**/*.test.ts"],
globals: true,
fileParallelism: false,
poolOptions: {
threads: {
singleThread: true,
},
},
},
});
+359 -453
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -43,6 +43,6 @@
"tailwindcss": "^4",
"trigger.dev": "workspace:*",
"typescript": "^5",
"vitest": "^3.1.4"
"vitest": "^4.1.7"
}
}