Merge remote-tracking branch 'origin/main' into claude/merge-clickhouse-main-ZNrD1
# Conflicts: # apps/webapp/app/env.server.ts # apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx # apps/webapp/app/v3/eventRepository/clickhouseEventRepository.server.ts # apps/webapp/test/utils/tracing.ts
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
---
|
||||
"@trigger.dev/sdk": patch
|
||||
"@trigger.dev/core": patch
|
||||
"@trigger.dev/build": patch
|
||||
"trigger.dev": patch
|
||||
---
|
||||
|
||||
Add Agent Skills for `chat.agent`. Drop a folder with a `SKILL.md` and any helper scripts/references next to your task code, register it with `skills.define({ id, path })`, and the CLI bundles it into the deploy image automatically — no `trigger.config.ts` changes. The agent gets a one-line summary in its system prompt and discovers full instructions on demand via `loadSkill`, with `bash` and `readFile` tools scoped per-skill (path-traversal guards, output caps, abort-signal propagation).
|
||||
|
||||
```ts
|
||||
const pdfSkill = skills.define({ id: "pdf-extract", path: "./skills/pdf-extract" });
|
||||
|
||||
chat.skills.set([await pdfSkill.local()]);
|
||||
```
|
||||
|
||||
Built on the [AI SDK cookbook pattern](https://ai-sdk.dev/cookbook/guides/agent-skills) — portable across providers. SDK + CLI only for now; dashboard-editable `SKILL.md` text is on the roadmap.
|
||||
@@ -0,0 +1,52 @@
|
||||
---
|
||||
"@trigger.dev/sdk": minor
|
||||
---
|
||||
|
||||
**AI Prompts** — define prompt templates as code alongside your tasks, version them on deploy, and override the text or model from the dashboard without redeploying. Prompts integrate with the Vercel AI SDK via `toAISDKTelemetry()` (links every generation span back to the prompt) and with `chat.agent` via `chat.prompt.set()` + `chat.toStreamTextOptions()`.
|
||||
|
||||
```ts
|
||||
import { prompts } from "@trigger.dev/sdk";
|
||||
import { generateText } from "ai";
|
||||
import { openai } from "@ai-sdk/openai";
|
||||
import { z } from "zod";
|
||||
|
||||
export const supportPrompt = prompts.define({
|
||||
id: "customer-support",
|
||||
model: "gpt-4o",
|
||||
config: { temperature: 0.7 },
|
||||
variables: z.object({
|
||||
customerName: z.string(),
|
||||
plan: z.string(),
|
||||
issue: z.string(),
|
||||
}),
|
||||
content: `You are a support agent for Acme.
|
||||
|
||||
Customer: {{customerName}} ({{plan}} plan)
|
||||
Issue: {{issue}}`,
|
||||
});
|
||||
|
||||
const resolved = await supportPrompt.resolve({
|
||||
customerName: "Alice",
|
||||
plan: "Pro",
|
||||
issue: "Can't access billing",
|
||||
});
|
||||
|
||||
const result = await generateText({
|
||||
model: openai(resolved.model ?? "gpt-4o"),
|
||||
system: resolved.text,
|
||||
prompt: "Can't access billing",
|
||||
...resolved.toAISDKTelemetry(),
|
||||
});
|
||||
```
|
||||
|
||||
**What you get:**
|
||||
|
||||
- **Code-defined, deploy-versioned templates** — define with `prompts.define({ id, model, config, variables, content })`. Every deploy creates a new version visible in the dashboard. Mustache-style placeholders (`{{var}}`, `{{#cond}}...{{/cond}}`) with Zod / ArkType / Valibot-typed variables.
|
||||
- **Dashboard overrides** — change a prompt's text or model from the dashboard without redeploying. Overrides take priority over the deployed "current" version and are environment-scoped (dev / staging / production independent).
|
||||
- **Resolve API** — `prompt.resolve(vars, { version?, label? })` returns the compiled `text`, resolved `model`, `version`, and labels. Standalone `prompts.resolve<typeof handle>(slug, vars)` for cross-file resolution with full type inference on slug and variable shape.
|
||||
- **AI SDK integration** — spread `resolved.toAISDKTelemetry({ ...extra })` into any `generateText` / `streamText` call and every generation span links to the prompt in the dashboard alongside its input variables, model, tokens, and cost.
|
||||
- **`chat.agent` integration** — `chat.prompt.set(resolved)` stores the resolved prompt run-scoped; `chat.toStreamTextOptions({ registry })` pulls `system`, `model` (resolved via the AI SDK provider registry), `temperature` / `maxTokens` / etc., and telemetry into a single spread for `streamText`.
|
||||
- **Management SDK** — `prompts.list()`, `prompts.versions(slug)`, `prompts.promote(slug, version)`, `prompts.createOverride(slug, body)`, `prompts.updateOverride(slug, body)`, `prompts.removeOverride(slug)`, `prompts.reactivateOverride(slug, version)`.
|
||||
- **Dashboard** — prompts list with per-prompt usage sparklines; per-prompt detail with Template / Details / Versions / Generations / Metrics tabs. AI generation spans get a custom inspector showing the linked prompt's metadata, input variables, and template content alongside model, tokens, cost, and the message thread.
|
||||
|
||||
See [/docs/ai/prompts](https://trigger.dev/docs/ai/prompts) for the full reference — template syntax, version resolution order, override workflow, and type utilities (`PromptHandle`, `PromptIdentifier`, `PromptVariables`).
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
"@trigger.dev/sdk": patch
|
||||
---
|
||||
|
||||
Add `ai.toolExecute(task)` so you can wire a Trigger subtask in as the `execute` handler of an AI SDK `tool()` while defining `description` and `inputSchema` yourself — useful when you want full control over the tool surface and just need Trigger's subtask machinery for the body.
|
||||
|
||||
```ts
|
||||
const myTool = tool({
|
||||
description: "...",
|
||||
inputSchema: z.object({ ... }),
|
||||
execute: ai.toolExecute(mySubtask),
|
||||
});
|
||||
```
|
||||
|
||||
`ai.tool(task)` (`toolFromTask`) keeps doing the all-in-one wrap and now aligns its return type with AI SDK's `ToolSet`. Minimum `ai` peer raised to `^6.0.116` to avoid cross-version `ToolSet` mismatches in monorepos.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@trigger.dev/core": patch
|
||||
---
|
||||
|
||||
Reject overlong `idempotencyKey` values at the API boundary so they no longer trip an internal size limit on the underlying unique index and surface as a generic 500. Inputs are capped at 2048 characters — well above what `idempotencyKeys.create()` produces (a 64-character hash) and above any realistic raw key. Applies to `tasks.trigger`, `tasks.batchTrigger`, `batch.create` (Phase 1 streaming batches), `wait.createToken`, `wait.forDuration`, and the input/session stream waitpoint endpoints. Over-limit requests now return a structured 400 instead.
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
"@trigger.dev/sdk": minor
|
||||
---
|
||||
|
||||
Adds `onBoot` to `chat.agent` — a lifecycle hook that fires once per worker process picking up the chat. Runs for the initial run, preloaded runs, AND reactive continuation runs (post-cancel, crash, `endRun`, `requestUpgrade`, OOM retry), before any other hook. Use it to initialize `chat.local`, open per-process resources, or re-hydrate state from your DB on continuation — anywhere the SAME run picking up after suspend/resume isn't enough.
|
||||
|
||||
```ts
|
||||
const userContext = chat.local<{ name: string; plan: string }>({ id: "userContext" });
|
||||
|
||||
export const myChat = chat.agent({
|
||||
id: "my-chat",
|
||||
onBoot: async ({ clientData, continuation }) => {
|
||||
const user = await db.user.findUnique({ where: { id: clientData.userId } });
|
||||
userContext.init({ name: user.name, plan: user.plan });
|
||||
},
|
||||
run: async ({ messages, signal }) =>
|
||||
streamText({ model: openai("gpt-4o"), messages, abortSignal: signal }),
|
||||
});
|
||||
```
|
||||
|
||||
Use `onBoot` (not `onChatStart`) for state setup that must run every time a worker picks up the chat — `onChatStart` fires once per chat and won't run on continuation, leaving `chat.local` uninitialized when `run()` tries to use it.
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
"@trigger.dev/sdk": minor
|
||||
"@trigger.dev/core": patch
|
||||
---
|
||||
|
||||
**AI Agents** — run AI SDK chat completions as durable Trigger.dev agents instead of fragile API routes. Define an agent in one function, point `useChat` at it from React, and the conversation survives page refreshes, network blips, and process restarts.
|
||||
|
||||
```ts
|
||||
import { chat } from "@trigger.dev/sdk/ai";
|
||||
import { streamText } from "ai";
|
||||
import { openai } from "@ai-sdk/openai";
|
||||
|
||||
export const myChat = chat.agent({
|
||||
id: "my-chat",
|
||||
run: async ({ messages, signal }) =>
|
||||
streamText({ model: openai("gpt-4o"), messages, abortSignal: signal }),
|
||||
});
|
||||
```
|
||||
|
||||
```tsx
|
||||
import { useChat } from "@ai-sdk/react";
|
||||
import { useTriggerChatTransport } from "@trigger.dev/sdk/chat/react";
|
||||
|
||||
const transport = useTriggerChatTransport({ task: "my-chat", accessToken, startSession });
|
||||
const { messages, sendMessage } = useChat({ transport });
|
||||
```
|
||||
|
||||
**What you get:**
|
||||
|
||||
- **AI SDK `useChat` integration** — a custom [`ChatTransport`](https://sdk.vercel.ai/docs/ai-sdk-ui/transport) (`useTriggerChatTransport`) plugs straight into Vercel AI SDK's `useChat` hook. Text streaming, tool calls, reasoning, and `data-*` parts all work natively over Trigger.dev's realtime streams. No custom API routes needed.
|
||||
- **First-turn fast path (`chat.headStart`)** — opt-in handler that runs the first turn's `streamText` step in your warm server process while the agent run boots in parallel, cutting cold-start TTFC by roughly half (measured 2801ms → 1218ms on `claude-sonnet-4-6`). The agent owns step 2+ (tool execution, persistence, hooks) so heavy deps stay where they belong. Web Fetch handler works natively in Next.js, Hono, SvelteKit, Remix, Workers, etc.; bridge to Express/Fastify/Koa via `chat.toNodeListener`. New `@trigger.dev/sdk/chat-server` subpath.
|
||||
- **Multi-turn durability via Sessions** — every chat is backed by a durable Session that outlives any individual run. Conversations resume across page refreshes, idle timeout, crashes, and deploys; `resume: true` reconnects via `lastEventId` so clients only see new chunks. `sessions.list` enumerates chats for inbox-style UIs.
|
||||
- **Auto-accumulated history, delta-only wire** — the backend accumulates the full conversation across turns; clients only ship the new message each turn. Long chats never hit the 512 KiB body cap. Register `hydrateMessages` to be the source of truth yourself.
|
||||
- **Lifecycle hooks** — `onPreload`, `onChatStart`, `onValidateMessages`, `hydrateMessages`, `onTurnStart`, `onBeforeTurnComplete`, `onTurnComplete`, `onChatSuspend`, `onChatResume` — for persistence, validation, and post-turn work.
|
||||
- **Stop generation** — client-driven `transport.stopGeneration(chatId)` aborts mid-stream; the run stays alive for the next message, partial response is captured, and aborted parts (stuck `partial-call` tools, in-progress reasoning) are auto-cleaned.
|
||||
- **Tool approvals (HITL)** — tools with `needsApproval: true` pause until the user approves or denies via `addToolApprovalResponse`. The runtime reconciles the updated assistant message by ID and continues `streamText`.
|
||||
- **Steering and background injection** — `pendingMessages` injects user messages between tool-call steps so users can steer the agent mid-execution; `chat.inject()` + `chat.defer()` adds context from background work (self-review, RAG, safety checks) between turns.
|
||||
- **Actions** — non-turn frontend commands (undo, rollback, regenerate, edit) sent via `transport.sendAction`. Fire `hydrateMessages` + `onAction` only — no turn hooks, no `run()`. `onAction` can return a `StreamTextResult` for a model response, or `void` for side-effect-only.
|
||||
- **Typed state primitives** — `chat.local<T>` for per-run state accessible from hooks, `run()`, tools, and subtasks (auto-serialized through `ai.toolExecute`); `chat.store` for typed shared data between agent and client; `chat.history` for reading and mutating the message chain; `clientDataSchema` for typed `clientData` in every hook.
|
||||
- **`chat.toStreamTextOptions()`** — one spread into `streamText` wires up versioned system [Prompts](https://trigger.dev/docs/ai/prompts), model resolution, telemetry metadata, compaction, steering, and background injection.
|
||||
- **Multi-tab coordination** — `multiTab: true` + `useMultiTabChat` prevents duplicate sends and syncs state across browser tabs via `BroadcastChannel`. Non-active tabs go read-only with live updates.
|
||||
- **Network resilience** — built-in indefinite retry with bounded backoff, reconnect on `online` / tab refocus / bfcache restore, `Last-Event-ID` mid-stream resume. No app code needed.
|
||||
|
||||
See [/docs/ai-chat](https://trigger.dev/docs/ai-chat/overview) for the full surface — quick start, three backend approaches (`chat.agent`, `chat.createSession`, raw task), persistence and code-sandbox patterns, type-level guides, and API reference.
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
"@trigger.dev/sdk": minor
|
||||
---
|
||||
|
||||
Add read primitives to `chat.history` for HITL flows: `getPendingToolCalls()`, `getResolvedToolCalls()`, `extractNewToolResults(message)`, `getChain()`, and `findMessage(messageId)`. These lift the accumulator-walking logic that customers building human-in-the-loop tools were re-implementing into the SDK.
|
||||
|
||||
Use `getPendingToolCalls()` to gate fresh user turns while a tool call is awaiting an answer. Use `extractNewToolResults(message)` to dedup tool results when persisting to your own store — the helper returns only the parts whose `toolCallId` is not already resolved on the chain.
|
||||
|
||||
```ts
|
||||
const pending = chat.history.getPendingToolCalls();
|
||||
if (pending.length > 0) {
|
||||
// an addToolOutput is expected before a new user message
|
||||
}
|
||||
|
||||
onTurnComplete: async ({ responseMessage }) => {
|
||||
const newResults = chat.history.extractNewToolResults(responseMessage);
|
||||
for (const r of newResults) {
|
||||
await db.toolResults.upsert({ id: r.toolCallId, output: r.output, errorText: r.errorText });
|
||||
}
|
||||
};
|
||||
```
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@trigger.dev/sdk": patch
|
||||
"@trigger.dev/core": patch
|
||||
---
|
||||
|
||||
Stamp `gen_ai.conversation.id` (the chat id) on every span and metric emitted from inside a `chat.task` or `chat.agent` run. Lets you filter dashboard spans, runs, and metrics by the chat conversation that produced them — independent of the run boundary, so multi-run chats correlate cleanly. No code changes required on the user side.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"trigger.dev": patch
|
||||
---
|
||||
|
||||
Add `TRIGGER_BUILD_SKIP_REWRITE_TIMESTAMP=1` escape hatch for local self-hosted builds whose buildx driver doesn't support `rewrite-timestamp` alongside push (e.g. orbstack's default `docker` driver).
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@trigger.dev/core": patch
|
||||
---
|
||||
|
||||
Fix `LocalsKey<T>` type incompatibility across dual-package builds. The phantom value-type brand no longer uses a module-level `unique symbol`, so a single TypeScript compilation that resolves the type from both the ESM and CJS outputs (which can happen under certain pnpm hoisting layouts) no longer sees two structurally-incompatible variants of the same type.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"trigger.dev": patch
|
||||
---
|
||||
|
||||
The CLI MCP server's agent-chat tools (`start_agent_chat`, `send_agent_message`, `close_agent_chat`) now run on the new Sessions primitive, so AI assistants driving a `chat.agent` get the same idempotent-by-`chatId`, durable-across-runs behavior the browser transport gets. Required PAT scopes go from `write:inputStreams` to `read:sessions` + `write:sessions`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"trigger.dev": patch
|
||||
---
|
||||
|
||||
MCP `list_runs` tool: add a `region` filter input and surface each run's executing region in the formatted summary.
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
"@trigger.dev/sdk": patch
|
||||
"@trigger.dev/core": patch
|
||||
---
|
||||
|
||||
Unit-test `chat.agent` definitions offline with `mockChatAgent` from `@trigger.dev/sdk/ai/test`. Drives a real agent's turn loop in-process — no network, no task runtime — so you can send messages, actions, and stop signals via driver methods, inspect captured output chunks, and verify hooks fire. Pairs with `MockLanguageModelV3` from `ai/test` for model mocking. `setupLocals` lets you pre-seed `locals` (DB clients, service stubs) before `run()` starts.
|
||||
|
||||
The broader `runInMockTaskContext` harness it's built on lives at `@trigger.dev/core/v3/test` — useful for unit-testing any task code, not just chat.
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
"@trigger.dev/redis-worker": patch
|
||||
---
|
||||
|
||||
Add MollifierBuffer and MollifierDrainer primitives for trigger burst smoothing.
|
||||
|
||||
MollifierBuffer (`accept`, `pop`, `ack`, `requeue`, `fail`, `evaluateTrip`) is a per-env FIFO over Redis with atomic Lua transitions for status tracking. `evaluateTrip` is a sliding-window trip evaluator the webapp gate uses to detect per-env trigger bursts.
|
||||
|
||||
MollifierDrainer pops entries through a polling loop with a user-supplied handler. The loop survives transient Redis errors via capped exponential backoff (up to 5s), and per-env pop failures don't poison the rest of the batch — one env's blip is logged and counted as failed for that tick. Rotation is two-level: orgs at the top, envs within each org. The buffer maintains `mollifier:orgs` and `mollifier:org-envs:${orgId}` atomically with per-env queues, so the drainer walks orgs → envs directly without an in-memory cache. The `maxOrgsPerTick` option (default 500) caps how many orgs are scheduled per tick; for each picked org, one env is popped (rotating round-robin within the org). An org with N envs gets the same per-tick scheduling slot as an org with 1 env, so tenant-level drainage throughput is determined by org count rather than env count.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@trigger.dev/plugins": patch
|
||||
---
|
||||
|
||||
The public interfaces for a plugin system. Initially consolidated authentication and authorization interfaces.
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"mode": "pre",
|
||||
"tag": "rc",
|
||||
"initialVersions": {
|
||||
"coordinator": "0.0.1",
|
||||
"docker-provider": "0.0.1",
|
||||
"kubernetes-provider": "0.0.1",
|
||||
"supervisor": "0.0.1",
|
||||
"webapp": "1.0.0",
|
||||
"@trigger.dev/build": "4.4.6",
|
||||
"trigger.dev": "4.4.6",
|
||||
"@trigger.dev/core": "4.4.6",
|
||||
"@trigger.dev/plugins": "4.4.6",
|
||||
"@trigger.dev/python": "4.4.6",
|
||||
"@trigger.dev/react-hooks": "4.4.6",
|
||||
"@trigger.dev/redis-worker": "4.4.6",
|
||||
"@trigger.dev/rsc": "4.4.6",
|
||||
"@trigger.dev/schema-to-json": "4.4.6",
|
||||
"@trigger.dev/sdk": "4.4.6"
|
||||
},
|
||||
"changesets": []
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@trigger.dev/core": patch
|
||||
---
|
||||
|
||||
Retry `TASK_PROCESS_SIGSEGV` task crashes under the user's retry policy instead of failing the run on the first segfault. SIGSEGV in Node tasks is frequently non-deterministic (native addon races, JIT/GC interaction, near-OOM in native code, host issues), so retrying on a fresh process often succeeds. The retry is gated by the task's existing `retry` config + `maxAttempts` — same path `TASK_PROCESS_SIGTERM` and uncaught exceptions already use — so tasks without a retry policy still fail fast.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@trigger.dev/core": patch
|
||||
"@trigger.dev/sdk": patch
|
||||
---
|
||||
|
||||
Add `region` to the runs list / retrieve API: filter runs by region (`runs.list({ region: "..." })` / `filter[region]=<masterQueue>`) and read each run's executing region from the new `region` field on the response.
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
"@trigger.dev/sdk": minor
|
||||
"@trigger.dev/core": patch
|
||||
---
|
||||
|
||||
**Sessions** — a durable, run-aware stream channel keyed on a stable `externalId`. A Session is the unit of state that owns a multi-run conversation: messages flow through `.in`, responses through `.out`, both survive run boundaries. Sessions back the new `chat.agent` runtime, and you can build on them directly for any pattern that needs durable bi-directional streaming across runs.
|
||||
|
||||
```ts
|
||||
import { sessions, tasks } from "@trigger.dev/sdk";
|
||||
|
||||
// Trigger a task and subscribe to its session output in one call
|
||||
const { runId, stream } = await tasks.triggerAndSubscribe("my-task", payload, {
|
||||
externalId: "user-456",
|
||||
});
|
||||
|
||||
for await (const chunk of stream) {
|
||||
// ...
|
||||
}
|
||||
|
||||
// Enumerate existing sessions (powers inbox-style UIs without a separate index)
|
||||
for await (const s of sessions.list({ type: "chat.agent", tag: "user:user-456" })) {
|
||||
console.log(s.id, s.externalId, s.createdAt, s.closedAt);
|
||||
}
|
||||
```
|
||||
|
||||
See [/docs/ai-chat/overview](https://trigger.dev/docs/ai-chat/overview) for the full surface — Sessions powers the durable, resumable chat runtime described there.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@trigger.dev/core": patch
|
||||
---
|
||||
|
||||
Truncate large error stacks and messages to prevent OOM crashes. Stack traces are capped at 50 frames (keeping top 5 + bottom 45 with an omission notice), individual stack lines at 1024 chars, and error messages at 1000 chars. Applied in parseError, sanitizeError, and OTel span recording.
|
||||
@@ -0,0 +1,50 @@
|
||||
# REVIEW.md — Trigger.dev OSS
|
||||
|
||||
Repo-specific signal for anyone (human or agent) reviewing a PR in this codebase. Calibrates what counts as critical, what to always check, and what to skip.
|
||||
|
||||
## What makes a 🔴 Important finding here
|
||||
|
||||
Reserve 🔴 for things that would page someone or block a rollback. In this codebase, that means:
|
||||
|
||||
- **Rolling-deploy breakage.** Old and new versions of the webapp/supervisor run side-by-side during deploys. A change is broken if:
|
||||
- A Lua script's behavior changes for a given key set without versioning (rename the script with a behavior-descriptive suffix like `Tracked` rather than `V2` — both versions must coexist safely).
|
||||
- A Redis data shape used by both versions changes in place. New shapes need a new key namespace.
|
||||
- A migration is not backward-compatible with the prior image.
|
||||
- **Schema / migration safety.** Prisma migrations must be backward-compatible with the prior deploy. Adding NOT NULL without a default, dropping a column an old image still reads, renaming a column — all 🔴.
|
||||
- **ClickHouse migration ordering + idempotency.** Goose runs in strict mode in the deploy pipeline and refuses to apply a missing version below the current version — slotting a new file in below the latest already-applied version blocks the deploy. New ClickHouse migration files MUST use the next available number (`max(files in internal-packages/clickhouse/schema/) + 1`); if main has added migrations while you've been on a branch, renumber yours. DDL must also be idempotent (`ADD COLUMN IF NOT EXISTS`, `DROP COLUMN IF EXISTS`, `CREATE TABLE IF NOT EXISTS`, `ADD INDEX IF NOT EXISTS`) so a partial / `--allow-missing` apply elsewhere doesn't fail on retry. Either fault is 🔴 — both break test/prod deploys. Rules live in `internal-packages/clickhouse/CLAUDE.md`.
|
||||
- **Queue / concurrency correctness.** RunQueue, MarQS (V1, legacy), redis-worker — any change to enqueue / dequeue / locking semantics. Re-derive the invariant on paper before flagging or accepting.
|
||||
- **Missing index on a hot table.** New Prisma queries against `TaskRun`, `TaskRunExecutionSnapshot`, `JobRun`, `Project`, etc. must use an existing index. Check `internal-packages/database/prisma/schema.prisma` for the relevant `@@index` lines — don't guess and don't propose `EXPLAIN`.
|
||||
- **Recovery-path queries.** Any `TaskRun.findFirst` / `findMany` added to a schedule, run-recovery, or restart loop. Recovery fan-outs (Redis crash, restart storms) turn "rare indexed query" into a DB incident. 🔴 even if indexed.
|
||||
- **Aggregations on hot tables.** No `COUNT` / `GROUP BY` on `TaskRun` or other multi-million-row tables. Use Redis or ClickHouse for counts.
|
||||
- **Prod Redis blast-radius.** New code paths that `SCAN` with broad patterns (`*foo*`) on prod-shaped Redis, or `EVAL` Lua with `SCAN` loops inside. Both are 🔴.
|
||||
- **`@trigger.dev/core` direct import** from anywhere outside the SDK package. Always import from `@trigger.dev/sdk`. Core direct imports are 🔴 — they break the public API contract.
|
||||
- **Heavy execute-deps imported into request-handler bundles.** Specifically `chat.handover` and similar split-bundle entry points must not transitively import the agent task's execute path. Watch for new imports added at module top-level of route files.
|
||||
- **V1 engine code modified in a "V2 only" PR.** The `apps/webapp/app/v3/` directory contains both. If the PR description says V2-only but it touches `triggerTaskV1`, `cancelTaskRunV1`, `MarQS`, etc. — 🔴.
|
||||
|
||||
## Always check
|
||||
|
||||
- **Tests use testcontainers, not mocks.** Vitest with `redisTest` / `postgresTest` / `containerTest` from `@internal/testcontainers`. Any new `vi.mock(...)` on Redis, Postgres, BullMQ, or other infra is wrong here — 🔴 if added in production-path tests, 🟡 if isolated unit test.
|
||||
- **Public-package changes have a changeset.** `pnpm run changeset:add` produces `.changeset/*.md`. Required for any edit under `packages/*`. Missing → 🟡; missing on a breaking change → 🔴.
|
||||
- **Server-only changes have `.server-changes/*.md`.** Required for `apps/webapp/`, `apps/supervisor/` edits with no public-package change. Body should be 1-2 sentences (it has to fit as one bullet in a future changelog). Missing → 🟡.
|
||||
- **Lua script naming.** Coexisting scripts use behavior-descriptive suffixes (`Tracked`), never `V2`. Old name must keep working until the next deploy clears it.
|
||||
- **RunQueue payload shape.** V2 run-queue payload's `projectId` is consumed by `workerQueueResolver` for override matching. If a PR drops it from the payload, 🔴.
|
||||
- **`safeSend` scope.** Defensive IPC wrappers belong on loop / interval / handler contexts, not one-shot terminal sends. If the PR adds `safeSend` to a single terminal call for consistency, 🟡 with a "remove this" suggestion.
|
||||
- **Zod version.** Pinned to `3.25.76` monorepo-wide. New package adding zod with a different version or range — 🔴.
|
||||
|
||||
## Skip (do NOT flag)
|
||||
|
||||
- Anything Prettier / ESLint catches. CI runs both.
|
||||
- TypeScript style preferences (`type` vs `interface`) — already covered by repo standards.
|
||||
- Test coverage exhortations as a generic suggestion. Only flag missing tests when a specific code path is genuinely untested and the path has prior incidents.
|
||||
- `agentcrumbs` markers (`// @crumbs`, `// #region @crumbs`) and `agentcrumbs` imports — these are temporary debug instrumentation stripped before merge.
|
||||
- `// removed comments for removed code`, renamed `_unused` vars, re-exported types as "backwards compatibility shims" — also covered by repo standards.
|
||||
- Suggestions to "add error handling" without naming a specific scenario that breaks.
|
||||
- Documentation prose nitpicks in `docs/*` MDX files unless factually wrong.
|
||||
|
||||
## Things V1/legacy that should NOT block a PR
|
||||
|
||||
The `apps/webapp/app/v3/` directory name is misleading — most code there is V2. Only specific files are V1-only legacy: `MarQS` queue, `triggerTaskV1`, `cancelTaskRunV1`, and a handful of others (see `apps/webapp/CLAUDE.md` for the exact list). Don't flag "you should refactor this to use V2" on those — they're frozen.
|
||||
|
||||
## Confidence calibration for this repo
|
||||
|
||||
The most common false-positive pattern: speculating about race conditions in code paths the agent doesn't have runtime visibility into. If the only evidence is "this *could* race", drop it. If you can point to a specific interleaving with file:line for each step, surface it.
|
||||
@@ -0,0 +1,287 @@
|
||||
# Review guide — chat.agent on Sessions, row-agnostic addressing
|
||||
|
||||
Scope: the 12 uncommitted files. **No new behaviour beyond the public surface
|
||||
already on this branch** — this is plumbing cleanup that:
|
||||
|
||||
1. Eliminates the transport's session-creation step
|
||||
2. Makes `chatId` the universal addressing string everywhere
|
||||
3. Makes the server-side stream/append/wait routes row-agnostic
|
||||
|
||||
## The two design moves
|
||||
|
||||
**Move 1 — agent owns session lifecycle.** `chat.agent` and
|
||||
`chat.customAgent` upsert the backing `Session` row at bind, fire-and-forget,
|
||||
keyed on `externalId = payload.chatId`. The transport, server-side
|
||||
`AgentChat`, and `chat.createTriggerAction` no longer create sessions at all.
|
||||
Browsers cannot mint sessions either (`POST /api/v1/sessions` is now
|
||||
secret-key-only). One owner, one path.
|
||||
|
||||
**Move 2 — `chatId` is the only address.** The transport, server-side
|
||||
`AgentChat`, JWT scopes, and S2 stream paths all use `chatId` directly. The
|
||||
Session's friendlyId is informational. To make this safe, the three stream
|
||||
routes (`.in/.out` PUT, GET, POST append, plus the run-engine `wait`
|
||||
endpoint) became "row-optional" and derive a *canonical addressing key*
|
||||
(`row.externalId ?? row.friendlyId`, fallback to the URL param when the row
|
||||
hasn't been upserted yet). Same canonical key is used to build the S2 stream
|
||||
path, the waitpoint cache key, and the JWT resource set — so any caller
|
||||
addressing by either form converges on the same physical stream.
|
||||
|
||||
Together these remove an entire class of "did the row land yet?" races. The
|
||||
transport can subscribe to `/sessions/{chatId}/out` before the agent boots,
|
||||
the agent's `void sessions.create({externalId: chatId})` lands a moment
|
||||
later, and any earlier reads/writes are already on the right S2 key.
|
||||
|
||||
---
|
||||
|
||||
## Read in this order
|
||||
|
||||
### 1. `apps/webapp/app/services/realtime/sessions.server.ts` (+34 lines)
|
||||
|
||||
The new primitive. Two helpers:
|
||||
|
||||
- `isSessionFriendlyIdForm(value)` — `value.startsWith("session_")`. Used to
|
||||
decide whether a missing row is a hard 404 (opaque friendlyId) or a soft
|
||||
"row will land later" (externalId form).
|
||||
- `canonicalSessionAddressingKey(row, paramSession)` — `row.externalId ??
|
||||
row.friendlyId` if the row exists, else `paramSession`. **This is the load-
|
||||
bearing function.** Read its docstring.
|
||||
|
||||
**Question to ask:** can two callers addressing the "same" session ever get
|
||||
different canonical keys? Only if the row exists for one and not the other,
|
||||
*and* the URL forms differ — but in that case the row-less caller used the
|
||||
externalId form (friendlyId-form would have 404'd earlier), and the row-ful
|
||||
caller computes `row.externalId ?? row.friendlyId`. If the row's externalId
|
||||
matches the URL, they converge. If it doesn't, there's no row to find by
|
||||
that string anyway. The interesting edge is "row exists with no externalId",
|
||||
addressed via friendlyId — both sides read `row.friendlyId`. ✓
|
||||
|
||||
### 2. `apps/webapp/app/routes/realtime.v1.sessions.$session.$io.ts` (+47/-12)
|
||||
|
||||
PUT initialize + GET subscribe (SSE). Both use the helper. The interesting
|
||||
part is the loader's `findResource` + `authorization.resource`:
|
||||
|
||||
```ts
|
||||
findResource: async (params, auth) => {
|
||||
const row = await resolveSessionByIdOrExternalId(...);
|
||||
if (!row && isSessionFriendlyIdForm(params.session)) return undefined; // 404
|
||||
return { row, addressingKey: canonicalSessionAddressingKey(row, params.session) };
|
||||
},
|
||||
authorization: {
|
||||
resource: ({ row, addressingKey }) => {
|
||||
const ids = new Set<string>([addressingKey]);
|
||||
if (row) {
|
||||
ids.add(row.friendlyId);
|
||||
if (row.externalId) ids.add(row.externalId);
|
||||
}
|
||||
return { sessions: [...ids] };
|
||||
},
|
||||
superScopes: ["read:sessions", "read:all", "admin"],
|
||||
},
|
||||
```
|
||||
|
||||
**Why three IDs in the resource set?** `checkAuthorization` is "any-match"
|
||||
across the resource values. We want a JWT scoped to *either* form to
|
||||
authorize *either* URL form. Smoke test verified the 4-cell matrix passes.
|
||||
|
||||
**The PUT path** (action handler) is simpler — it just resolves the row,
|
||||
builds an addressing key, and hands it to `initializeSessionStream`. Worth
|
||||
noting the `closedAt` check is now `maybeSession?.closedAt` — no row means
|
||||
no closedAt to enforce.
|
||||
|
||||
### 3. `apps/webapp/app/routes/realtime.v1.sessions.$session.$io.append.ts` (+22/-13)
|
||||
|
||||
POST append (browser writes a record to `.in` or server writes to `.out`).
|
||||
Same row-optional pattern. Both the S2 append and the waitpoint drain use
|
||||
`addressingKey`.
|
||||
|
||||
**Question to ask:** what fires the waitpoint? An agent's
|
||||
`session.in.wait()` registers a waitpoint keyed on `(addressingKey, io)` via
|
||||
the wait endpoint (file 4). The append handler drains by the *same* key —
|
||||
even if the agent registered with externalId form and the transport
|
||||
appended via friendlyId form, both compute the same canonical key, so they
|
||||
converge. ✓
|
||||
|
||||
### 4. `apps/webapp/app/routes/api.v1.runs.$runFriendlyId.session-streams.wait.ts` (+18/-13)
|
||||
|
||||
The agent's `.in.wait()` endpoint. Run-engine creates the waitpoint, then
|
||||
registers it in Redis under `(addressingKey, io)`. The race-check that runs
|
||||
right after creation reads from S2 by the same key. Three call sites —
|
||||
`addSessionStreamWaitpoint`, `readSessionStreamRecords`,
|
||||
`removeSessionStreamWaitpoint` — all consistent.
|
||||
|
||||
### 5. `apps/webapp/app/routes/api.v1.sessions.ts` (+4/-2)
|
||||
|
||||
**Security tightening.** Removed `allowJWT: true` and `corsStrategy: "all"`
|
||||
from the `POST /api/v1/sessions` action — secret-key only now.
|
||||
|
||||
**Question to ask:** was the JWT path actually used? Until this branch, the
|
||||
transport called it via `ensureSession` (now deleted). After this branch,
|
||||
nobody reaches it from the browser. `chat.createTriggerAction` (server
|
||||
secret key) is the only browser-adjacent path.
|
||||
|
||||
### 6. `packages/trigger-sdk/src/v3/ai.ts` (+62/-39)
|
||||
|
||||
Two near-identical edits — one in `chatAgent`, one in `chatCustomAgent`.
|
||||
Both bind on `payload.chatId` and fire-and-forget the upsert:
|
||||
|
||||
```ts
|
||||
locals.set(chatSessionHandleKey, sessions.open(payload.chatId));
|
||||
void sessions
|
||||
.create({ type: "chat.agent", externalId: payload.chatId })
|
||||
.catch(() => { /* best effort */ });
|
||||
```
|
||||
|
||||
**Question to ask:** why `void`-and-`catch`? Awaiting the upsert would gate
|
||||
the agent's bind on a network round-trip that doesn't unblock anything
|
||||
user-visible — `.in/.out` routes are row-agnostic and the waitpoint cache
|
||||
is keyed on the addressing string, not the row id. If the upsert genuinely
|
||||
fails, the next bind retries the same idempotent call (`sessions.create`
|
||||
upserts on `externalId`, so concurrent triggers on one chatId converge to
|
||||
one row). The row matters for downstream metadata + listing, not for live
|
||||
addressing.
|
||||
|
||||
The PAT scope minting in `chatAgent` (two call sites — preload and
|
||||
sendMessage) now uses `payload.chatId` for the `sessions:` resource. That
|
||||
matches what the transport/AgentChat use as the JWT resource and what the
|
||||
JWT's resource set in the loader includes. Cross-form addressing works
|
||||
either way (smoke-tested), but using `chatId` keeps the chain tight.
|
||||
|
||||
`createChatTriggerAction` is the most visibly trimmed: no pre-create, no
|
||||
threading `sessionId` into payload, scope mint uses `chatId`. Return type
|
||||
no longer carries `sessionId` — note `TriggerChatTaskResult.sessionId` was
|
||||
already declared optional, so this isn't a public-API break.
|
||||
|
||||
**Stale docstring to flag:** `chat.ts:59` and `chat.ts:112` still describe
|
||||
PAT scopes as `read:sessions:{sessionId}` and
|
||||
`write:sessions:{sessionId}`. Functionally either ID works (row lookup
|
||||
canonicalises), but the doc text is now out of date — it should say
|
||||
`{chatId}`. Worth a tidy-up before merge but not blocking.
|
||||
|
||||
### 7. `packages/trigger-sdk/src/v3/chat.ts` (+63/-117)
|
||||
|
||||
**The biggest mechanical edit.** Net -54 lines from deleting `ensureSession`
|
||||
and untangling its callers.
|
||||
|
||||
What disappeared:
|
||||
- `private async ensureSession(chatId)` — gone
|
||||
- The "lazy upsert from the browser if no triggerTask callback" branch in
|
||||
`sendMessages` and `preload` — gone
|
||||
- The "throw if neither path surfaced a sessionId" guard — gone
|
||||
- All `state.sessionId` URL params replaced with `chatId`
|
||||
- `subscribeToSessionStream`'s `chatId?` (optional) is now `chatId` (required)
|
||||
|
||||
What stayed:
|
||||
- `state.sessionId` in `ChatSessionState` — optional, informational
|
||||
- The `restore from external storage` branch in the constructor still
|
||||
hydrates `sessionId` if persisted, just doesn't *require* it
|
||||
- `notifySessionChange` still surfaces `sessionId` if known
|
||||
|
||||
**Question to ask:** does the transport ever still need the friendlyId? The
|
||||
only place is the `onSessionChange` callback's payload (so consumers
|
||||
persisting state can save it for later display). The transport itself never
|
||||
puts it in a URL or a waitpoint key.
|
||||
|
||||
The `sendMessages` path is worth re-reading: when state.runId is set, it
|
||||
appends to `.in/append` and subscribes to `.out`. If the append fails with
|
||||
a non-auth error, it falls through to triggering a new run (legacy "run is
|
||||
dead" detection — unchanged from pre-Sessions, doesn't depend on
|
||||
addressing).
|
||||
|
||||
### 8. `packages/trigger-sdk/src/v3/chat-client.ts` (+34/-33)
|
||||
|
||||
Server-side `AgentChat`. Mirrors the transport changes — every URL uses
|
||||
`this.chatId`. `triggerNewRun` no longer pre-creates a session. `ChatSession`
|
||||
and internal `SessionState` types now have optional `sessionId`.
|
||||
|
||||
The shape of the diff is identical to the transport: delete the upsert,
|
||||
swap addressing identifiers, optionalise the friendlyId. If you've read
|
||||
`chat.ts` carefully, this one is mostly mechanical confirmation that both
|
||||
client surfaces (browser transport + server-side AgentChat) speak the same
|
||||
addressing protocol.
|
||||
|
||||
### 9. Test infrastructure — `sessions.ts` (+18) + `mock-chat-agent.ts` (+25)
|
||||
|
||||
`__setSessionCreateImplForTests` mirrors the existing
|
||||
`__setSessionOpenImplForTests`. `mockChatAgent` installs a no-op create stub
|
||||
returning a synthetic `CreatedSessionResponseBody` so the agent's bind-time
|
||||
`void sessions.create(...)` doesn't try to hit a real API. Cleanup runs in
|
||||
the same `.finally` as the open override.
|
||||
|
||||
**Question to ask:** is the synthetic response shape correct? It mirrors
|
||||
`CreatedSessionResponseBody` — `id`, `externalId`, `type`, `tags`,
|
||||
`metadata`, `closedAt`, `closedReason`, `expiresAt`, `createdAt`,
|
||||
`updatedAt`, `isCached`. Tests don't currently assert on this object, so
|
||||
the bar is "doesn't crash + matches the type". Met.
|
||||
|
||||
### 10. `packages/trigger-sdk/src/v3/chat.test.ts` (+13/-12)
|
||||
|
||||
Three classes of test edits, all consequences:
|
||||
|
||||
- Stream URL assertion: `chat-1` (the chatId) instead of
|
||||
`session_streamurl` (the friendlyId)
|
||||
- `renewRunAccessToken` callback: `sessionId: undefined` (was
|
||||
`DEFAULT_SESSION_ID` because the mocked trigger doesn't surface it)
|
||||
- Token resolve count: `1` (was `2` — second resolve was for `ensureSession`)
|
||||
- One `onSessionChange` matchObject loses `sessionId`
|
||||
|
||||
### 11. `apps/webapp/app/routes/_app.../playground/.../route.tsx` (1 line)
|
||||
|
||||
`sessionId: string` → `sessionId?: string` in the playground sidebar prop
|
||||
to track the transport type change.
|
||||
|
||||
---
|
||||
|
||||
## Edge cases I checked, so you don't have to
|
||||
|
||||
- **Cross-form JWT auth (curl matrix).** JWT scoped to externalId can call
|
||||
externalId URL ✓ and friendlyId URL ✓. JWT scoped to friendlyId can call
|
||||
externalId URL ✓ and friendlyId URL ✓. Smoke-tested.
|
||||
- **Row materialises after subscribe.** Transport opens
|
||||
`GET /sessions/{chatId}/out` before agent's bind upsert lands → 200 OK,
|
||||
`addressingKey = chatId` (paramSession fallback). Once the row lands
|
||||
with `externalId = chatId`, addressingKey resolves to the same value via
|
||||
`row.externalId`. Same S2 key throughout.
|
||||
- **Concurrent triggers on one chatId.** Two browser tabs trigger two runs
|
||||
→ two binds → two `sessions.create({externalId: chatId})` calls. Upsert
|
||||
semantics: both return the same row.
|
||||
- **Closed session enforcement.** Still enforced when a row exists.
|
||||
`maybeSession?.closedAt` is null-safe; no row = no close-state to honour.
|
||||
- **Agent run cancellation.** Frontend doesn't auto-detect — unchanged from
|
||||
pre-Sessions; messages sit in S2 until the next trigger (the existing
|
||||
run-PAT auth-error path is the only reaper). Out of scope for this branch.
|
||||
- **Idle timeout in dev.** Runs stay `EXECUTING_WITH_WAITPOINTS` past the
|
||||
configured idle because dev runs don't snapshot/restore; the in-process
|
||||
idle clock advances locally without touching the row. Expected, not a
|
||||
regression.
|
||||
|
||||
## Things explicitly **not** in this branch
|
||||
|
||||
- Run-state subscription on the transport side (the "run died, re-trigger
|
||||
silently" UX gap)
|
||||
- Session auto-close on agent exit (still client-driven by design)
|
||||
- Any change to `Session` schema, `sessions.create` semantics, or
|
||||
`chatAccessTokenTTL`
|
||||
- Docstring updates for `read:sessions:{sessionId}` / `write:sessions:{sessionId}`
|
||||
in `chat.ts:59` and `chat.ts:112` (functional but textually stale —
|
||||
follow-up nit)
|
||||
|
||||
---
|
||||
|
||||
## What I'd be ready to answer cold
|
||||
|
||||
- Why fire-and-forget upsert (vs. `await`) in the agent's bind step
|
||||
- Why the route's authorization resource set has three IDs (cross-form JWT
|
||||
auth)
|
||||
- Why `POST /api/v1/sessions` lost `allowJWT` (security tightening — no
|
||||
caller needs it after the transport's `ensureSession` is gone)
|
||||
- What converges two callers using different URL forms onto the same S2
|
||||
stream (`canonicalSessionAddressingKey`, identical computation on both
|
||||
sides for any given row)
|
||||
- What makes `sessions.create` race-safe under concurrent triggers
|
||||
(`externalId` upsert)
|
||||
- Why `state.sessionId` stayed on `ChatSessionState` at all (pure
|
||||
informational, surfaced via `onSessionChange` for consumer persistence;
|
||||
zero addressing role)
|
||||
- Why the chat-client (server-side AgentChat) and chat (transport) edits
|
||||
look near-identical (they implement the same client protocol against the
|
||||
same row-agnostic routes)
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
paths:
|
||||
- "**/package.json"
|
||||
---
|
||||
|
||||
# Installing Packages
|
||||
|
||||
When adding a new dependency to any package.json in the monorepo:
|
||||
|
||||
1. **Look up the latest version** on npm before adding:
|
||||
```bash
|
||||
pnpm view <package-name> version
|
||||
```
|
||||
If unsure which version to use (e.g. major version compatibility), confirm with the user.
|
||||
|
||||
2. **Edit the package.json directly** — do NOT use `pnpm add` as it can cause issues in the monorepo. Add the dependency with the correct version range (typically `^x.y.z`).
|
||||
|
||||
3. **Run `pnpm i` from the repo root** after editing to install and update the lockfile:
|
||||
```bash
|
||||
pnpm i
|
||||
```
|
||||
Always run from the repo root, not from the package directory.
|
||||
+47
-1
@@ -29,6 +29,52 @@ REDIS_TLS_DISABLED="true"
|
||||
DEV_OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:3030/otel"
|
||||
DEV_OTEL_BATCH_PROCESSING_ENABLED="0"
|
||||
|
||||
# Realtime streams v2 (Sessions, chat.agent, large stream backfills) backed
|
||||
# by S2 (https://s2.dev). The `s2` service in docker/docker-compose.yml runs
|
||||
# the open-source s2-lite binary and pre-creates a basin named `trigger-local`
|
||||
# (see docker/config/s2-spec.json). Comment these out to fall back to v1
|
||||
# (Redis-only) streams; Sessions and chat.agent then become unavailable.
|
||||
REALTIME_STREAMS_S2_BASIN=trigger-local
|
||||
REALTIME_STREAMS_S2_ACCESS_TOKEN=ignored
|
||||
REALTIME_STREAMS_S2_ENDPOINT=http://localhost:4566/v1
|
||||
REALTIME_STREAMS_S2_SKIP_ACCESS_TOKENS=true
|
||||
REALTIME_STREAMS_DEFAULT_VERSION=v2
|
||||
|
||||
# Running multiple instances side by side (worktrees, branch experiments)
|
||||
#
|
||||
# Every host port in docker/docker-compose.yml is `${VAR:-default}` and the
|
||||
# project name comes from `COMPOSE_PROJECT_NAME`. To stand up a second stack
|
||||
# alongside the default one, uncomment the block below in this clone's `.env`
|
||||
# (pick any offset that doesn't clash with anything else running), then update
|
||||
# the URL/PORT vars further up to match. Default values are commented for
|
||||
# reference.
|
||||
#
|
||||
# --- core (pnpm run docker) ---
|
||||
# COMPOSE_PROJECT_NAME=triggerdotdev-docker-alt
|
||||
# CONTAINER_PREFIX=alt-
|
||||
# POSTGRES_HOST_PORT=15432 # default 5432
|
||||
# REDIS_HOST_PORT=16379 # default 6379
|
||||
# ELECTRIC_HOST_PORT=13060 # default 3060
|
||||
# MINIO_API_HOST_PORT=19005 # default 9005
|
||||
# MINIO_CONSOLE_HOST_PORT=19006 # default 9006
|
||||
# CLICKHOUSE_HTTP_HOST_PORT=18123 # default 8123
|
||||
# CLICKHOUSE_TCP_HOST_PORT=19000 # default 9000
|
||||
# S2_HOST_PORT=14566 # default 4566
|
||||
# REMIX_APP_PORT=13030 # default 3030
|
||||
# --- extras (only needed if you also run `pnpm run docker:full`) ---
|
||||
# ELECTRIC_SHARD_1_HOST_PORT=13061 # default 3061
|
||||
# CH_UI_HOST_PORT=15521 # default 5521
|
||||
# TOXIPROXY_PROXY_HOST_PORT=40303 # default 30303
|
||||
# TOXIPROXY_API_HOST_PORT=18474 # default 8474
|
||||
# NGINX_H2_HOST_PORT=18443 # default 8443
|
||||
# OTEL_GRPC_HOST_PORT=14317 # default 4317
|
||||
# OTEL_HTTP_HOST_PORT=14318 # default 4318
|
||||
# OTEL_PROMETHEUS_HOST_PORT=18889 # default 8889
|
||||
# PROMETHEUS_HOST_PORT=19090 # default 9090
|
||||
# GRAFANA_HOST_PORT=13001 # default 3001
|
||||
# (and update DATABASE_URL / CLICKHOUSE_URL / REDIS_PORT / APP_ORIGIN /
|
||||
# LOGIN_ORIGIN / ELECTRIC_ORIGIN / REALTIME_STREAMS_S2_ENDPOINT to match)
|
||||
|
||||
# When the domain is set to `localhost` the CLI deploy command will only --load the image by default and not --push it
|
||||
DEPLOY_REGISTRY_HOST=localhost:5000
|
||||
|
||||
@@ -106,7 +152,7 @@ POSTHOG_PROJECT_KEY=
|
||||
# INTERNAL_OTEL_TRACE_LOGGING_ENABLED=1
|
||||
# INTERNAL_OTEL_TRACE_INSTRUMENT_PRISMA_ENABLED=0
|
||||
|
||||
# Enable local observability stack (requires `pnpm run docker` to start otel-collector)
|
||||
# Enable local observability stack (requires `pnpm run docker:full` to bring up otel-collector + prometheus + grafana)
|
||||
# Uncomment these to send metrics to the local Prometheus via OTEL Collector:
|
||||
# INTERNAL_OTEL_METRIC_EXPORTER_ENABLED=1
|
||||
# INTERNAL_OTEL_METRIC_EXPORTER_URL=http://localhost:4318/v1/metrics
|
||||
|
||||
+3
-1
@@ -13,10 +13,12 @@ samejr
|
||||
isshaddad
|
||||
# Bots
|
||||
devin-ai-integration[bot]
|
||||
dependabot[bot]
|
||||
# Outside contributors
|
||||
gautamsi
|
||||
capaj
|
||||
chengzp
|
||||
bharathkumar39293
|
||||
bhekanik
|
||||
jrossi
|
||||
jrossi
|
||||
ThullyoCunha
|
||||
@@ -23,35 +23,37 @@ runs:
|
||||
id: get_tag
|
||||
shell: bash
|
||||
run: |
|
||||
if [[ -n "${{ inputs.tag }}" ]]; then
|
||||
tag="${{ inputs.tag }}"
|
||||
elif [[ "${{ github.ref_type }}" == "tag" ]]; then
|
||||
if [[ "${{ github.ref_name }}" == infra-*-* ]]; then
|
||||
env=$(echo ${{ github.ref_name }} | cut -d- -f2)
|
||||
sha=$(echo ${{ github.sha }} | head -c7)
|
||||
if [[ -n "${INPUTS_TAG}" ]]; then
|
||||
tag="${INPUTS_TAG}"
|
||||
elif [[ "${GITHUB_REF_TYPE}" == "tag" ]]; then
|
||||
if [[ "${GITHUB_REF_NAME}" == infra-*-* ]]; then
|
||||
env=$(echo ${GITHUB_REF_NAME} | cut -d- -f2)
|
||||
sha=$(echo "${GITHUB_SHA}" | head -c7)
|
||||
ts=$(date +%s)
|
||||
tag=${env}-${sha}-${ts}
|
||||
elif [[ "${{ github.ref_name }}" == re2-*-* ]]; then
|
||||
env=$(echo ${{ github.ref_name }} | cut -d- -f2)
|
||||
sha=$(echo ${{ github.sha }} | head -c7)
|
||||
elif [[ "${GITHUB_REF_NAME}" == re2-*-* ]]; then
|
||||
env=$(echo ${GITHUB_REF_NAME} | cut -d- -f2)
|
||||
sha=$(echo "${GITHUB_SHA}" | head -c7)
|
||||
ts=$(date +%s)
|
||||
tag=${env}-${sha}-${ts}
|
||||
elif [[ "${{ github.ref_name }}" == v.docker.* ]]; then
|
||||
elif [[ "${GITHUB_REF_NAME}" == v.docker.* ]]; then
|
||||
version="${GITHUB_REF_NAME#v.docker.}"
|
||||
tag="v${version}"
|
||||
elif [[ "${{ github.ref_name }}" == build-* ]]; then
|
||||
elif [[ "${GITHUB_REF_NAME}" == build-* ]]; then
|
||||
tag="${GITHUB_REF_NAME#build-}"
|
||||
else
|
||||
echo "Invalid git tag: ${{ github.ref_name }}"
|
||||
echo "Invalid git tag: ${GITHUB_REF_NAME}"
|
||||
exit 1
|
||||
fi
|
||||
elif [[ "${{ github.ref_name }}" == "main" ]]; then
|
||||
elif [[ "${GITHUB_REF_NAME}" == "main" ]]; then
|
||||
tag="main"
|
||||
else
|
||||
echo "Invalid git ref: ${{ github.ref }}"
|
||||
echo "Invalid git ref: ${GITHUB_REF}"
|
||||
exit 1
|
||||
fi
|
||||
echo "tag=${tag}" >> "$GITHUB_OUTPUT"
|
||||
env:
|
||||
INPUTS_TAG: ${{ inputs.tag }}
|
||||
|
||||
- name: 🔍 Check for validity
|
||||
id: check_validity
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
cooldown:
|
||||
default-days: 7
|
||||
groups:
|
||||
github-actions:
|
||||
patterns:
|
||||
- "*"
|
||||
@@ -25,15 +25,15 @@ jobs:
|
||||
if: github.repository == 'triggerdotdev/trigger.dev'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 # zizmor: ignore[artipacked] changesets/action pushes the release branch; no artifact upload here so no leak path
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||
|
||||
- name: Setup node
|
||||
uses: buildjet/setup-node@v4
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: 20.20.0
|
||||
cache: "pnpm"
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
|
||||
- name: Create release PR
|
||||
id: changesets
|
||||
uses: changesets/action@v1
|
||||
uses: changesets/action@6a0a831ff30acef54f2c6aa1cbbc1096b066edaf # v1.7.0
|
||||
with:
|
||||
version: pnpm run changeset:version
|
||||
commit: "chore: release"
|
||||
@@ -72,84 +72,3 @@ jobs:
|
||||
-f body="$ENHANCED_BODY"
|
||||
fi
|
||||
fi
|
||||
|
||||
update-lockfile:
|
||||
name: Update lockfile on release PR
|
||||
runs-on: ubuntu-latest
|
||||
needs: release-pr
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout release branch
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: changeset-release/main
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10.23.0
|
||||
|
||||
- name: Setup node
|
||||
uses: buildjet/setup-node@v4
|
||||
with:
|
||||
node-version: 20.20.0
|
||||
|
||||
- name: Install and update lockfile
|
||||
run: pnpm install --no-frozen-lockfile
|
||||
|
||||
- name: Clean up consumed .server-changes/ files
|
||||
run: |
|
||||
set -e
|
||||
shopt -s nullglob
|
||||
files=(.server-changes/*.md)
|
||||
for f in "${files[@]}"; do
|
||||
if [ "$(basename "$f")" != "README.md" ]; then
|
||||
git rm --ignore-unmatch "$f"
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Commit and push lockfile + server-changes cleanup
|
||||
run: |
|
||||
set -e
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git add pnpm-lock.yaml
|
||||
if ! git diff --cached --quiet; then
|
||||
git commit -m "chore: update lockfile and clean up .server-changes/ for release"
|
||||
git push origin changeset-release/main
|
||||
else
|
||||
echo "No changes to commit"
|
||||
fi
|
||||
|
||||
bump-chart-version:
|
||||
name: Bump Helm chart version on release PR
|
||||
runs-on: ubuntu-latest
|
||||
needs: update-lockfile
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout release branch
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: changeset-release/main
|
||||
|
||||
- name: Bump Chart.yaml
|
||||
run: |
|
||||
set -e
|
||||
VERSION=$(jq -r '.version' packages/cli-v3/package.json)
|
||||
sed -i "s/^version:.*/version: ${VERSION}/" ./hosting/k8s/helm/Chart.yaml
|
||||
sed -i "s/^appVersion:.*/appVersion: v${VERSION}/" ./hosting/k8s/helm/Chart.yaml
|
||||
|
||||
- name: Commit and push Chart.yaml bump
|
||||
run: |
|
||||
set -e
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git add hosting/k8s/helm/Chart.yaml
|
||||
if ! git diff --cached --quiet; then
|
||||
git commit -m "chore: bump helm chart version for release"
|
||||
git push origin changeset-release/main
|
||||
else
|
||||
echo "Chart.yaml already at target version, no-op"
|
||||
fi
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
name: 🔎 REVIEW.md Drift Audit
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, ready_for_review, synchronize]
|
||||
paths-ignore:
|
||||
- "docs/**"
|
||||
- ".changeset/**"
|
||||
- ".server-changes/**"
|
||||
- "references/**"
|
||||
|
||||
concurrency:
|
||||
group: review-md-drift-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
audit:
|
||||
if: >-
|
||||
github.event.pull_request.draft == false &&
|
||||
github.event.pull_request.head.repo.full_name == github.repository
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Run Claude Code
|
||||
id: claude
|
||||
uses: anthropics/claude-code-action@fefa07e9c665b7320f08c3b525980457f22f58aa # v1.0.111
|
||||
with:
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
use_sticky_comment: true
|
||||
allowed_bots: "devin-ai-integration[bot]"
|
||||
|
||||
claude_args: |
|
||||
--max-turns 30
|
||||
--allowedTools "Read,Glob,Grep,Bash(git diff:*)"
|
||||
|
||||
prompt: |
|
||||
You are auditing this PR for drift against `.claude/REVIEW.md`.
|
||||
|
||||
## Context
|
||||
|
||||
`.claude/REVIEW.md` is the repo's source of truth for what AI / agent code reviewers should treat as critical findings (rolling-deploy safety, hot-table indexes, recovery-path queries, testcontainers usage, Lua versioning, etc.). It is consumed by review agents to calibrate severity. If REVIEW.md goes stale, every future agent review degrades.
|
||||
|
||||
## Strategy — read this first
|
||||
|
||||
You have a hard turn budget. Spend it on signal, not coverage. The audit is allowed to miss things; it is NOT allowed to time out.
|
||||
|
||||
1. Read `.claude/REVIEW.md` once, in full.
|
||||
2. Run `git diff origin/main...HEAD --name-only` to get the list of changed files. Do NOT read the diff content yet.
|
||||
3. Scan the file-list for relevance to REVIEW.md scope. Relevance signals: changes to Prisma schema, Redis / queue / Lua code, hot tables, recovery / restart loops, new packages, deletions of paths REVIEW.md cites. Skim everything else.
|
||||
4. Open at most **5 files** total — only the ones most likely to surface a real signal. If nothing in the file-list looks relevant to any REVIEW.md rule, do NOT read any files; go straight to the verdict.
|
||||
5. Form a verdict and stop. Do not exhaust the turn budget exploring.
|
||||
|
||||
Large PRs (>50 files changed) are a strong signal to be MORE selective, not more thorough. Pick 3-5 files at most.
|
||||
|
||||
## What to look for
|
||||
|
||||
- **Stale references** — does any REVIEW.md rule cite a file, directory, function, table, Prisma model, or package name that has been removed or renamed in this PR (or is already gone from `main`)?
|
||||
- **Contradictions** — does code in this PR clearly violate a current REVIEW.md rule? (Don't re-review the PR. Only flag if REVIEW.md and the PR plainly disagree.)
|
||||
- **Missing rules** — does this PR introduce a new pattern future reviewers should know about? Examples: a new hot table, a new Lua-script versioning convention, a new safety wrapper, a new "must always check" invariant.
|
||||
- **Obsolete rules** — has the repo moved past a constraint REVIEW.md still asserts? (e.g. a deprecated path is gone, a pattern is now linted, V1 code is deleted.)
|
||||
|
||||
## Response format
|
||||
|
||||
If nothing needs changing:
|
||||
|
||||
✅ REVIEW.md looks current for this PR.
|
||||
|
||||
Otherwise:
|
||||
|
||||
📝 **REVIEW.md updates suggested:**
|
||||
|
||||
- **[stale]** `<rule excerpt>` — <what's stale and why>
|
||||
- **[contradiction]** `<rule excerpt>` — <what in this PR disagrees>
|
||||
- **[missing]** under `## <section>` — <one-sentence draft rule>
|
||||
- **[obsolete]** `<rule excerpt>` — <why this rule no longer applies>
|
||||
|
||||
## Rules
|
||||
|
||||
- Maximum 3 suggestions per audit. Pick the highest-signal ones.
|
||||
- Only flag things that would actually mislead a future reviewer. Style and wording do not count.
|
||||
- Do NOT review the PR itself. Do NOT propose rules outside REVIEW.md's existing sections.
|
||||
- Do NOT propose rules for one-off PR specifics that don't generalize to future PRs.
|
||||
- If REVIEW.md does not exist in the repo, respond with `(skip)` and stop.
|
||||
- When in doubt between "one more file read" and "finish now" — finish now.
|
||||
@@ -16,7 +16,9 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
audit:
|
||||
if: github.event.pull_request.draft == false
|
||||
if: >-
|
||||
github.event.pull_request.draft == false &&
|
||||
github.event.pull_request.head.repo.full_name == github.repository
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -25,15 +27,16 @@ jobs:
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Run Claude Code
|
||||
id: claude
|
||||
uses: anthropics/claude-code-action@v1
|
||||
uses: anthropics/claude-code-action@fefa07e9c665b7320f08c3b525980457f22f58aa # v1.0.111
|
||||
with:
|
||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
use_sticky_comment: true
|
||||
allowed_bots: "devin-ai-integration[bot]"
|
||||
|
||||
|
||||
@@ -19,24 +19,25 @@ jobs:
|
||||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
issues: read
|
||||
contents: write
|
||||
pull-requests: write
|
||||
issues: write
|
||||
id-token: write
|
||||
actions: read # Required for Claude to read CI results on PRs
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 1
|
||||
persist-credentials: false
|
||||
|
||||
- name: ⎔ Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||
with:
|
||||
version: 10.23.0
|
||||
version: 10.33.2
|
||||
|
||||
- name: ⎔ Setup node
|
||||
uses: buildjet/setup-node@v4
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: 20.20.0
|
||||
cache: "pnpm"
|
||||
@@ -49,9 +50,9 @@ jobs:
|
||||
|
||||
- name: Run Claude Code
|
||||
id: claude
|
||||
uses: anthropics/claude-code-action@v1
|
||||
uses: anthropics/claude-code-action@fefa07e9c665b7320f08c3b525980457f22f58aa # v1.0.111
|
||||
with:
|
||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
|
||||
# This is an optional setting that allows Claude to read CI results on PRs
|
||||
additional_permissions: |
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
name: Dependabot Weekly Summary
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 8 * * 1" # Mon 08:00 UTC
|
||||
workflow_dispatch:
|
||||
|
||||
# Single-purpose monitoring workflow; serialise on workflow name only - we never
|
||||
# want two concurrent summary runs racing to post the same digest.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read # gh CLI baseline
|
||||
pull-requests: read # gh pr list (open dependabot PRs)
|
||||
actions: read # gh run list / view (parse latest dependabot run logs)
|
||||
|
||||
jobs:
|
||||
summary:
|
||||
name: Post weekly Dependabot summary
|
||||
runs-on: ubuntu-latest
|
||||
environment: dependabot-summary
|
||||
env:
|
||||
# Severities surface in the actions list when their remaining TTR drops
|
||||
# below this many days. Override via repo/env var ACTION_THRESHOLD_DAYS.
|
||||
THRESHOLD_DAYS: ${{ vars.ACTION_THRESHOLD_DAYS || '7' }}
|
||||
steps:
|
||||
- name: Fetch alerts and compute summaries
|
||||
id: alerts
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.DEPENDABOT_ALERTS_TOKEN }}
|
||||
REPO: ${{ github.repository }}
|
||||
run: |
|
||||
if ! gh api -X GET "/repos/$REPO/dependabot/alerts" --paginate > pages.json 2> err.txt; then
|
||||
echo "total=?" >> "$GITHUB_OUTPUT"
|
||||
ERR=$(head -c 200 err.txt | tr '\n' ' ')
|
||||
echo "by_severity=:x: _failed to fetch alerts: ${ERR}_" >> "$GITHUB_OUTPUT"
|
||||
echo "actions=:x: _alerts unavailable_" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
jq -s '[.[][] | select(.state == "open")]' pages.json > open.json
|
||||
|
||||
TOTAL=$(jq 'length' open.json)
|
||||
echo "total=$TOTAL" >> "$GITHUB_OUTPUT"
|
||||
|
||||
if [ "$TOTAL" = "0" ]; then
|
||||
echo "by_severity=:white_check_mark: No open alerts." >> "$GITHUB_OUTPUT"
|
||||
echo "actions=_None_" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Severity breakdown - real newlines so jq --arg in the payload
|
||||
# builder encodes them as proper \n in JSON (Slack renders as breaks).
|
||||
BY_SEV=$(jq -r '
|
||||
group_by(.security_advisory.severity)
|
||||
| map({sev: .[0].security_advisory.severity,
|
||||
count: length,
|
||||
weight: ({"critical":0,"high":1,"medium":2,"low":3}[.[0].security_advisory.severity])})
|
||||
| sort_by(.weight)
|
||||
| map("• *\(.count)* \(.sev)")
|
||||
| join("\n")
|
||||
' open.json)
|
||||
{
|
||||
echo "by_severity<<EOF"
|
||||
echo "$BY_SEV"
|
||||
echo "EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Actions: alerts within THRESHOLD_DAYS of their TTR (P0=7d, P1=30d, P2=90d, P3=no deadline)
|
||||
# Grouped by (package, severity); shows earliest deadline per group.
|
||||
ACTIONS=$(jq -r --argjson threshold "$THRESHOLD_DAYS" '
|
||||
[.[]
|
||||
| (.security_advisory.severity) as $sev
|
||||
| ({"critical":7,"high":30,"medium":90,"low":null}[$sev]) as $ttr
|
||||
| select($ttr != null)
|
||||
| ((now - (.created_at | fromdateiso8601)) / 86400 | floor) as $age
|
||||
| {pkg: .dependency.package.name, sev: $sev, remaining: ($ttr - $age)}
|
||||
]
|
||||
| group_by([.pkg, .sev])
|
||||
| map({pkg: .[0].pkg, sev: .[0].sev, count: length, min_remaining: ([.[].remaining] | min)})
|
||||
| map(select(.min_remaining < $threshold))
|
||||
| sort_by(.min_remaining)
|
||||
| if length == 0 then "_None_"
|
||||
else (map(
|
||||
"• *\(.pkg)* (\(.sev))" +
|
||||
(if .count > 1 then " ×\(.count)" else "" end) + " - " +
|
||||
(if .min_remaining < 0 then "*OVERDUE* by \(-.min_remaining)d"
|
||||
else "\(.min_remaining)d remaining" end)
|
||||
) | join("\n"))
|
||||
end
|
||||
' open.json)
|
||||
{
|
||||
echo "actions<<EOF"
|
||||
echo "$ACTIONS"
|
||||
echo "EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Fetch open dependabot PRs
|
||||
id: prs
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
REPO: ${{ github.repository }}
|
||||
REPO_URL: https://github.com/${{ github.repository }}
|
||||
run: |
|
||||
if ! PR_JSON=$(gh pr list --repo "$REPO" --state open --author "app/dependabot" --json number,title 2> err.txt); then
|
||||
ERR=$(head -c 200 err.txt | tr '\n' ' ')
|
||||
echo "list=:x: _failed to fetch PRs: ${ERR}_" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
LIST=$(echo "$PR_JSON" | jq -r --arg url "$REPO_URL" '
|
||||
if length == 0 then "_None_"
|
||||
else (map("• <\($url)/pull/\(.number)|#\(.number)> \(.title)") | join("\n"))
|
||||
end
|
||||
')
|
||||
{
|
||||
echo "list<<EOF"
|
||||
echo "$LIST"
|
||||
echo "EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Find latest npm dependabot run
|
||||
id: latest
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
REPO: ${{ github.repository }}
|
||||
run: |
|
||||
# Repos without a dependabot.yml have no "Dependabot Updates" workflow;
|
||||
# treat the lookup failure as "no recent run found" rather than failing.
|
||||
if ! RUN_ID=$(gh run list --repo "$REPO" --workflow "Dependabot Updates" --status success --limit 30 --json databaseId,name --jq 'first(.[] | select(.name | startswith("npm_and_yarn")) | .databaseId) // empty' 2>/dev/null); then
|
||||
RUN_ID=""
|
||||
fi
|
||||
echo "run_id=$RUN_ID" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Extract stuck deps (only if actions pending)
|
||||
id: stuck
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
REPO: ${{ github.repository }}
|
||||
RUN_ID: ${{ steps.latest.outputs.run_id }}
|
||||
ACTIONS: ${{ steps.alerts.outputs.actions }}
|
||||
run: |
|
||||
# Skip the stuck section entirely when nothing in the actions list
|
||||
# - keeps the digest tidy when there's nothing to actually act on.
|
||||
if [ "$ACTIONS" = "_None_" ]; then
|
||||
echo "section=" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
HEADER=$'\n\n*Couldn\'t auto-fix (need manual `pnpm.overrides`):*\n'
|
||||
if [ -z "$RUN_ID" ]; then
|
||||
{
|
||||
echo "section<<EOF"
|
||||
echo "${HEADER}_(no recent npm run found)_"
|
||||
echo "EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
gh run view "$RUN_ID" --repo "$REPO" --log > log.txt 2>&1 || true
|
||||
STUCK=$(grep -oE "No update possible for [^[:space:]]+ [0-9][^[:space:]]*" log.txt | sed 's/No update possible for //' | sort -u || true)
|
||||
if [ -z "$STUCK" ]; then
|
||||
{
|
||||
echo "section<<EOF"
|
||||
echo "${HEADER}_None_"
|
||||
echo "EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
LIST=$(echo "$STUCK" | awk 'NR>1{printf "\n"} {printf "• *%s* %s", $1, $2}')
|
||||
{
|
||||
echo "section<<EOF"
|
||||
echo "${HEADER}${LIST}"
|
||||
echo "EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Build Slack payload
|
||||
env:
|
||||
REPO: ${{ github.repository }}
|
||||
CHANNEL: ${{ vars.SLACK_CHANNEL_ID }}
|
||||
TOTAL: ${{ steps.alerts.outputs.total }}
|
||||
BY_SEVERITY: ${{ steps.alerts.outputs.by_severity }}
|
||||
PRS_LIST: ${{ steps.prs.outputs.list }}
|
||||
ACTIONS: ${{ steps.alerts.outputs.actions }}
|
||||
STUCK: ${{ steps.stuck.outputs.section }}
|
||||
run: |
|
||||
# Build payload via jq so PR titles or error strings containing
|
||||
# quotes/backslashes/newlines can't break the JSON.
|
||||
jq -n \
|
||||
--arg channel "$CHANNEL" \
|
||||
--arg repo "$REPO" \
|
||||
--arg total "$TOTAL" \
|
||||
--arg by_severity "$BY_SEVERITY" \
|
||||
--arg prs_list "$PRS_LIST" \
|
||||
--arg actions "$ACTIONS" \
|
||||
--arg stuck "$STUCK" \
|
||||
--arg threshold "$THRESHOLD_DAYS" \
|
||||
'{
|
||||
channel: $channel,
|
||||
text: ":calendar: *Weekly Dependabot summary* - `\($repo)`\n\n*Open alerts (\($total)):*\n\($by_severity)\n\n*Open Dependabot PRs:*\n\($prs_list)\n\n*Actions needed (<\($threshold)d remaining):*\n\($actions)\($stuck)\n\n<https://github.com/\($repo)/security/dependabot|Dependabot alerts>"
|
||||
}' > payload.json
|
||||
|
||||
- name: Post Slack summary
|
||||
uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c # v3.0.3
|
||||
with:
|
||||
method: chat.postMessage
|
||||
token: ${{ secrets.SLACK_BOT_TOKEN }}
|
||||
payload-file-path: payload.json
|
||||
@@ -26,10 +26,12 @@ jobs:
|
||||
working-directory: ./docs
|
||||
steps:
|
||||
- name: 📥 Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: 📦 Cache npm
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: |
|
||||
~/.npm
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
name: "🛡️ E2E Tests: Webapp Auth (full)"
|
||||
|
||||
# Comprehensive RBAC auth test suite — see TRI-8731. Runs separately from
|
||||
# the smoke e2e-webapp.yml because it covers every route family with a
|
||||
# pass/fail matrix and would otherwise dominate per-PR CI time.
|
||||
#
|
||||
# Triggered:
|
||||
# - Manually via workflow_dispatch.
|
||||
# - Nightly via schedule.
|
||||
# - On pull requests touching auth-relevant files only (paths filter).
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 4 * * *" # 04:00 UTC daily
|
||||
pull_request:
|
||||
paths:
|
||||
- "apps/webapp/app/services/routeBuilders/**"
|
||||
- "apps/webapp/app/services/rbac.server.ts"
|
||||
- "apps/webapp/app/services/apiAuth.server.ts"
|
||||
- "apps/webapp/app/services/personalAccessToken.server.ts"
|
||||
- "apps/webapp/app/services/sessionStorage.server.ts"
|
||||
- "apps/webapp/app/routes/api.v*.**"
|
||||
- "apps/webapp/app/routes/realtime.v*.**"
|
||||
- "apps/webapp/test/**/*.e2e.full.test.ts"
|
||||
- "apps/webapp/test/setup/global-e2e-full-setup.ts"
|
||||
- "apps/webapp/test/helpers/sharedTestServer.ts"
|
||||
- "apps/webapp/test/helpers/seedTestSession.ts"
|
||||
- "apps/webapp/vitest.e2e.full.config.ts"
|
||||
- "internal-packages/rbac/**"
|
||||
- "packages/plugins/**"
|
||||
- ".github/workflows/e2e-webapp-auth-full.yml"
|
||||
|
||||
jobs:
|
||||
e2eAuthFull:
|
||||
name: "🛡️ E2E Auth Tests (full)"
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
env:
|
||||
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
steps:
|
||||
- name: 🔧 Disable IPv6
|
||||
run: |
|
||||
sudo sysctl -w net.ipv6.conf.all.disable_ipv6=1
|
||||
sudo sysctl -w net.ipv6.conf.default.disable_ipv6=1
|
||||
sudo sysctl -w net.ipv6.conf.lo.disable_ipv6=1
|
||||
|
||||
- name: 🔧 Configure docker address pool
|
||||
run: |
|
||||
CONFIG='{
|
||||
"default-address-pools" : [
|
||||
{
|
||||
"base" : "172.17.0.0/12",
|
||||
"size" : 20
|
||||
},
|
||||
{
|
||||
"base" : "192.168.0.0/16",
|
||||
"size" : 24
|
||||
}
|
||||
]
|
||||
}'
|
||||
mkdir -p /etc/docker
|
||||
echo "$CONFIG" | sudo tee /etc/docker/daemon.json
|
||||
|
||||
- name: 🔧 Restart docker daemon
|
||||
run: sudo systemctl restart docker
|
||||
|
||||
- name: ⬇️ Checkout repo
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
# Don't leave the GITHUB_TOKEN in .git/config — this job
|
||||
# doesn't need to push and the persisted creds would be
|
||||
# readable from any subsequent step (zizmor/artipacked).
|
||||
persist-credentials: false
|
||||
|
||||
- name: ⎔ Setup pnpm
|
||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||
with:
|
||||
version: 10.33.2
|
||||
|
||||
- name: ⎔ Setup node
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: 20.20.0
|
||||
cache: "pnpm"
|
||||
|
||||
- name: 🐳 Login to DockerHub
|
||||
if: ${{ env.DOCKERHUB_USERNAME }}
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: 🐳 Skipping DockerHub login (no secrets available)
|
||||
if: ${{ !env.DOCKERHUB_USERNAME }}
|
||||
run: echo "DockerHub login skipped because secrets are not available."
|
||||
|
||||
- name: 🐳 Pre-pull testcontainer images
|
||||
if: ${{ env.DOCKERHUB_USERNAME }}
|
||||
run: |
|
||||
docker pull postgres:14
|
||||
docker pull redis:7.2
|
||||
docker pull testcontainers/ryuk:0.11.0
|
||||
|
||||
- name: 📥 Download deps
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: 📀 Generate Prisma Client
|
||||
run: pnpm run generate
|
||||
|
||||
- name: 🏗️ Build Webapp
|
||||
run: pnpm run build --filter webapp
|
||||
|
||||
- name: 🛡️ Run Webapp Full Auth E2E Tests
|
||||
run: cd apps/webapp && pnpm exec vitest run --config vitest.e2e.full.config.ts --reporter=default
|
||||
env:
|
||||
WEBAPP_TEST_VERBOSE: "1"
|
||||
@@ -5,6 +5,11 @@ permissions:
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
secrets:
|
||||
DOCKERHUB_USERNAME:
|
||||
required: false
|
||||
DOCKERHUB_TOKEN:
|
||||
required: false
|
||||
|
||||
jobs:
|
||||
e2eTests:
|
||||
@@ -41,17 +46,18 @@ jobs:
|
||||
run: sudo systemctl restart docker
|
||||
|
||||
- name: ⬇️ Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: ⎔ Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||
with:
|
||||
version: 10.23.0
|
||||
version: 10.33.2
|
||||
|
||||
- name: ⎔ Setup node
|
||||
uses: buildjet/setup-node@v4
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: 20.20.0
|
||||
cache: "pnpm"
|
||||
@@ -59,7 +65,7 @@ jobs:
|
||||
# ..to avoid rate limits when pulling images
|
||||
- name: 🐳 Login to DockerHub
|
||||
if: ${{ env.DOCKERHUB_USERNAME }}
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
@@ -24,17 +24,18 @@ jobs:
|
||||
package-manager: ["npm", "pnpm"]
|
||||
steps:
|
||||
- name: ⬇️ Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: ⎔ Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||
with:
|
||||
version: 10.23.0
|
||||
version: 10.33.2
|
||||
|
||||
- name: ⎔ Setup node
|
||||
uses: buildjet/setup-node@v4
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: 20.20.0
|
||||
|
||||
@@ -48,7 +49,7 @@ jobs:
|
||||
run: pnpm run build --filter trigger.dev^...
|
||||
|
||||
- name: 🔧 Build worker template files
|
||||
run: pnpm --filter trigger.dev run build:workers
|
||||
run: pnpm --filter trigger.dev run --if-present build:workers
|
||||
|
||||
- name: Enable corepack
|
||||
run: corepack enable
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
name: 🧭 Helm Chart PR Prerelease
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
paths:
|
||||
- "hosting/k8s/helm/**"
|
||||
|
||||
concurrency:
|
||||
group: helm-prerelease-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
CHART_NAME: trigger
|
||||
|
||||
jobs:
|
||||
lint-and-test:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Helm
|
||||
uses: azure/setup-helm@v4
|
||||
with:
|
||||
version: "3.18.3"
|
||||
|
||||
- name: Build dependencies
|
||||
run: helm dependency build ./hosting/k8s/helm/
|
||||
|
||||
- name: Extract dependency charts
|
||||
run: |
|
||||
cd ./hosting/k8s/helm/
|
||||
for file in ./charts/*.tgz; do echo "Extracting $file"; tar -xzf "$file" -C ./charts; done
|
||||
|
||||
- name: Lint Helm Chart
|
||||
run: |
|
||||
helm lint ./hosting/k8s/helm/
|
||||
|
||||
- name: Render templates
|
||||
run: |
|
||||
helm template test-release ./hosting/k8s/helm/ \
|
||||
--values ./hosting/k8s/helm/values.yaml \
|
||||
--output-dir ./helm-output
|
||||
|
||||
- name: Validate manifests
|
||||
uses: docker://ghcr.io/yannh/kubeconform:v0.7.0
|
||||
with:
|
||||
entrypoint: "/kubeconform"
|
||||
args: "-summary -output json ./helm-output"
|
||||
|
||||
prerelease:
|
||||
needs: lint-and-test
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Helm
|
||||
uses: azure/setup-helm@v4
|
||||
with:
|
||||
version: "3.18.3"
|
||||
|
||||
- name: Build dependencies
|
||||
run: helm dependency build ./hosting/k8s/helm/
|
||||
|
||||
- name: Extract dependency charts
|
||||
run: |
|
||||
cd ./hosting/k8s/helm/
|
||||
for file in ./charts/*.tgz; do echo "Extracting $file"; tar -xzf "$file" -C ./charts; done
|
||||
|
||||
- name: Log in to Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Generate prerelease version
|
||||
id: version
|
||||
run: |
|
||||
BASE_VERSION=$(grep '^version:' ./hosting/k8s/helm/Chart.yaml | awk '{print $2}')
|
||||
PR_NUMBER=${{ github.event.pull_request.number }}
|
||||
SHORT_SHA=$(echo "${{ github.event.pull_request.head.sha }}" | cut -c1-7)
|
||||
PRERELEASE_VERSION="${BASE_VERSION}-pr${PR_NUMBER}.${SHORT_SHA}"
|
||||
echo "version=$PRERELEASE_VERSION" >> $GITHUB_OUTPUT
|
||||
echo "Prerelease version: $PRERELEASE_VERSION"
|
||||
|
||||
- name: Update Chart.yaml with prerelease version
|
||||
run: |
|
||||
sed -i "s/^version:.*/version: ${{ steps.version.outputs.version }}/" ./hosting/k8s/helm/Chart.yaml
|
||||
|
||||
- name: Package Helm Chart
|
||||
run: |
|
||||
helm package ./hosting/k8s/helm/ --destination /tmp/
|
||||
|
||||
- name: Push Helm Chart to GHCR
|
||||
run: |
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
CHART_PACKAGE="/tmp/${{ env.CHART_NAME }}-${VERSION}.tgz"
|
||||
|
||||
# Push to GHCR OCI registry
|
||||
helm push "$CHART_PACKAGE" "oci://${{ env.REGISTRY }}/${{ github.repository_owner }}/charts"
|
||||
|
||||
- name: Find existing comment
|
||||
uses: peter-evans/find-comment@v3
|
||||
id: find-comment
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
comment-author: "github-actions[bot]"
|
||||
body-includes: "Helm Chart Prerelease Published"
|
||||
|
||||
- name: Create or update PR comment
|
||||
uses: peter-evans/create-or-update-comment@v4
|
||||
with:
|
||||
comment-id: ${{ steps.find-comment.outputs.comment-id }}
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body: |
|
||||
### 🧭 Helm Chart Prerelease Published
|
||||
|
||||
**Version:** `${{ steps.version.outputs.version }}`
|
||||
|
||||
**Install:**
|
||||
```bash
|
||||
helm upgrade --install trigger \
|
||||
oci://ghcr.io/${{ github.repository_owner }}/charts/trigger \
|
||||
--version "${{ steps.version.outputs.version }}"
|
||||
```
|
||||
|
||||
> ⚠️ This is a prerelease for testing. Do not use in production.
|
||||
edit-mode: replace
|
||||
@@ -0,0 +1,200 @@
|
||||
name: 🧭 Helm Chart Prerelease
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
paths:
|
||||
- "hosting/k8s/helm/**"
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "hosting/k8s/helm/**"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
app_version:
|
||||
description: "Override appVersion (e.g. 'main', 'v4.4.4'). Leave empty to keep Chart.yaml value."
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
|
||||
concurrency:
|
||||
group: helm-prerelease-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
CHART_NAME: trigger
|
||||
|
||||
jobs:
|
||||
lint-and-test:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Helm
|
||||
uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5.0.0
|
||||
with:
|
||||
version: "3.18.3"
|
||||
|
||||
- name: Build dependencies
|
||||
run: helm dependency build ./hosting/k8s/helm/
|
||||
|
||||
- name: Extract dependency charts
|
||||
run: |
|
||||
cd ./hosting/k8s/helm/
|
||||
for file in ./charts/*.tgz; do echo "Extracting $file"; tar -xzf "$file" -C ./charts; done
|
||||
|
||||
- name: Lint Helm Chart
|
||||
run: |
|
||||
helm lint ./hosting/k8s/helm/
|
||||
|
||||
- name: Render templates
|
||||
run: |
|
||||
helm template test-release ./hosting/k8s/helm/ \
|
||||
--values ./hosting/k8s/helm/values.yaml \
|
||||
--output-dir ./helm-output
|
||||
|
||||
- name: Validate manifests
|
||||
uses: docker://ghcr.io/yannh/kubeconform:v0.7.0@sha256:85dbef6b4b312b99133decc9c6fc9495e9fc5f92293d4ff3b7e1b30f5611823c
|
||||
with:
|
||||
entrypoint: "/kubeconform"
|
||||
args: "-summary -output json ./helm-output"
|
||||
|
||||
prerelease:
|
||||
needs: lint-and-test
|
||||
if: |
|
||||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) ||
|
||||
github.event_name == 'push' ||
|
||||
github.event_name == 'workflow_dispatch'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Helm
|
||||
uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5.0.0
|
||||
with:
|
||||
version: "3.18.3"
|
||||
|
||||
- name: Build dependencies
|
||||
run: helm dependency build ./hosting/k8s/helm/
|
||||
|
||||
- name: Extract dependency charts
|
||||
run: |
|
||||
cd ./hosting/k8s/helm/
|
||||
for file in ./charts/*.tgz; do echo "Extracting $file"; tar -xzf "$file" -C ./charts; done
|
||||
|
||||
- name: Log in to Container Registry
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Generate prerelease version
|
||||
id: version
|
||||
run: |
|
||||
BASE_VERSION=$(grep '^version:' ./hosting/k8s/helm/Chart.yaml | awk '{print $2}')
|
||||
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
|
||||
PR_NUMBER=${{ github.event.pull_request.number }}
|
||||
SHORT_SHA=$(echo "${{ github.event.pull_request.head.sha }}" | cut -c1-7)
|
||||
PRERELEASE_VERSION="${BASE_VERSION}-pr${PR_NUMBER}.${SHORT_SHA}"
|
||||
elif [[ "${{ github.event_name }}" == "push" ]]; then
|
||||
SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7)
|
||||
PRERELEASE_VERSION="${BASE_VERSION}-main.${SHORT_SHA}"
|
||||
else
|
||||
SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7)
|
||||
REF_SLUG=$(echo "${GITHUB_REF_NAME}" | tr '/' '-' | tr -cd 'a-zA-Z0-9-')
|
||||
if [[ -z "$REF_SLUG" ]]; then
|
||||
REF_SLUG="manual"
|
||||
fi
|
||||
PRERELEASE_VERSION="${BASE_VERSION}-${REF_SLUG}.${SHORT_SHA}"
|
||||
fi
|
||||
echo "version=$PRERELEASE_VERSION" >> "$GITHUB_OUTPUT"
|
||||
echo "Prerelease version: $PRERELEASE_VERSION"
|
||||
|
||||
- name: Update Chart.yaml with prerelease version
|
||||
run: |
|
||||
sed -i "s/^version:.*/version: ${STEPS_VERSION_OUTPUTS_VERSION}/" ./hosting/k8s/helm/Chart.yaml
|
||||
env:
|
||||
STEPS_VERSION_OUTPUTS_VERSION: ${{ steps.version.outputs.version }}
|
||||
|
||||
- name: Override appVersion
|
||||
if: github.event_name == 'workflow_dispatch' && inputs.app_version != ''
|
||||
env:
|
||||
APP_VERSION: ${{ inputs.app_version }}
|
||||
run: |
|
||||
yq -i '.appVersion = strenv(APP_VERSION)' ./hosting/k8s/helm/Chart.yaml
|
||||
|
||||
- name: Package Helm Chart
|
||||
run: |
|
||||
helm package ./hosting/k8s/helm/ --destination /tmp/
|
||||
|
||||
- name: Push Helm Chart to GHCR
|
||||
run: |
|
||||
VERSION="${STEPS_VERSION_OUTPUTS_VERSION}"
|
||||
CHART_PACKAGE="/tmp/${{ env.CHART_NAME }}-${VERSION}.tgz"
|
||||
|
||||
# Push to GHCR OCI registry
|
||||
helm push "$CHART_PACKAGE" "oci://${{ env.REGISTRY }}/${{ github.repository_owner }}/charts"
|
||||
env:
|
||||
STEPS_VERSION_OUTPUTS_VERSION: ${{ steps.version.outputs.version }}
|
||||
|
||||
- name: Write run summary
|
||||
run: |
|
||||
{
|
||||
echo "### 🧭 Helm Chart Prerelease Published"
|
||||
echo ""
|
||||
echo "**Version:** \`${STEPS_VERSION_OUTPUTS_VERSION}\`"
|
||||
echo ""
|
||||
echo "**Install:**"
|
||||
echo '```bash'
|
||||
echo "helm upgrade --install trigger \\"
|
||||
echo " oci://${{ env.REGISTRY }}/${{ github.repository_owner }}/charts/${{ env.CHART_NAME }} \\"
|
||||
echo " --version \"${STEPS_VERSION_OUTPUTS_VERSION}\""
|
||||
echo '```'
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
env:
|
||||
STEPS_VERSION_OUTPUTS_VERSION: ${{ steps.version.outputs.version }}
|
||||
|
||||
- name: Find existing comment
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad # v4.0.0
|
||||
id: find-comment
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
comment-author: "github-actions[bot]"
|
||||
body-includes: "Helm Chart Prerelease Published"
|
||||
|
||||
- name: Create or update PR comment
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0
|
||||
with:
|
||||
comment-id: ${{ steps.find-comment.outputs.comment-id }}
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body: |
|
||||
### 🧭 Helm Chart Prerelease Published
|
||||
|
||||
**Version:** `${{ steps.version.outputs.version }}`
|
||||
|
||||
**Install:**
|
||||
```bash
|
||||
helm upgrade --install trigger \
|
||||
oci://ghcr.io/${{ github.repository_owner }}/charts/trigger \
|
||||
--version "${{ steps.version.outputs.version }}"
|
||||
```
|
||||
|
||||
> ⚠️ This is a prerelease for testing. Do not use in production.
|
||||
edit-mode: replace
|
||||
+156
-13
@@ -3,10 +3,6 @@ name: 🤖 PR Checks
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
paths-ignore:
|
||||
- "docs/**"
|
||||
- ".changeset/**"
|
||||
- "hosting/**"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
@@ -14,23 +10,170 @@ concurrency:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
pull-requests: read
|
||||
|
||||
jobs:
|
||||
typecheck:
|
||||
uses: ./.github/workflows/typecheck.yml
|
||||
secrets: inherit
|
||||
changes:
|
||||
name: Detect changes
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
code: ${{ steps.code_filter.outputs.code }}
|
||||
typecheck_self: ${{ steps.filter.outputs.typecheck_self }}
|
||||
webapp: ${{ steps.filter.outputs.webapp }}
|
||||
packages: ${{ steps.filter.outputs.packages }}
|
||||
internal: ${{ steps.filter.outputs.internal }}
|
||||
cli: ${{ steps.filter.outputs.cli }}
|
||||
sdk: ${{ steps.filter.outputs.sdk }}
|
||||
steps:
|
||||
# `code` uses `every` semantics so the negation patterns actually subtract.
|
||||
# With the default `some` quantifier, `**` matches every file and the
|
||||
# subsequent `!...` patterns are no-ops (each pattern is OR'd, not AND'd).
|
||||
- uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
|
||||
id: code_filter
|
||||
with:
|
||||
predicate-quantifier: every
|
||||
filters: |
|
||||
code:
|
||||
- '**'
|
||||
- '!docs/**'
|
||||
- '!.changeset/**'
|
||||
- '!hosting/**'
|
||||
- '!.github/**'
|
||||
- '!references/**'
|
||||
- '!**/*.md'
|
||||
- '!**/.env.example'
|
||||
- uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
|
||||
id: filter
|
||||
with:
|
||||
filters: |
|
||||
typecheck_self:
|
||||
- '.github/workflows/pr_checks.yml'
|
||||
- '.github/workflows/typecheck.yml'
|
||||
webapp:
|
||||
- 'apps/webapp/**'
|
||||
- 'packages/**'
|
||||
- 'internal-packages/**'
|
||||
- '.github/workflows/pr_checks.yml'
|
||||
- '.github/workflows/unit-tests-webapp.yml'
|
||||
- '.github/workflows/e2e-webapp.yml'
|
||||
- '.configs/**'
|
||||
- 'package.json'
|
||||
- 'pnpm-lock.yaml'
|
||||
- 'pnpm-workspace.yaml'
|
||||
- 'turbo.json'
|
||||
packages:
|
||||
- 'packages/**'
|
||||
- '.github/workflows/pr_checks.yml'
|
||||
- '.github/workflows/unit-tests-packages.yml'
|
||||
- '.configs/**'
|
||||
- 'package.json'
|
||||
- 'pnpm-lock.yaml'
|
||||
- 'pnpm-workspace.yaml'
|
||||
- 'turbo.json'
|
||||
internal:
|
||||
- 'internal-packages/**'
|
||||
- 'packages/**'
|
||||
- '.github/workflows/pr_checks.yml'
|
||||
- '.github/workflows/unit-tests-internal.yml'
|
||||
- '.configs/**'
|
||||
- 'package.json'
|
||||
- 'pnpm-lock.yaml'
|
||||
- 'pnpm-workspace.yaml'
|
||||
- 'turbo.json'
|
||||
cli:
|
||||
- 'packages/cli-v3/**'
|
||||
- 'packages/build/**'
|
||||
- 'packages/core/**'
|
||||
- 'packages/schema-to-json/**'
|
||||
- '.github/workflows/pr_checks.yml'
|
||||
- '.github/workflows/e2e.yml'
|
||||
- '.configs/**'
|
||||
- 'package.json'
|
||||
- 'pnpm-lock.yaml'
|
||||
- 'pnpm-workspace.yaml'
|
||||
- 'turbo.json'
|
||||
sdk:
|
||||
- 'packages/trigger-sdk/**'
|
||||
- 'packages/core/**'
|
||||
- '.github/workflows/pr_checks.yml'
|
||||
- '.github/workflows/sdk-compat.yml'
|
||||
- '.configs/**'
|
||||
- 'package.json'
|
||||
- 'pnpm-lock.yaml'
|
||||
- 'pnpm-workspace.yaml'
|
||||
- 'turbo.json'
|
||||
|
||||
units:
|
||||
uses: ./.github/workflows/unit-tests.yml
|
||||
secrets: inherit
|
||||
typecheck:
|
||||
needs: changes
|
||||
if: needs.changes.outputs.code == 'true' || needs.changes.outputs.typecheck_self == 'true'
|
||||
uses: ./.github/workflows/typecheck.yml
|
||||
|
||||
webapp:
|
||||
needs: changes
|
||||
if: needs.changes.outputs.webapp == 'true'
|
||||
uses: ./.github/workflows/unit-tests-webapp.yml
|
||||
secrets:
|
||||
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
e2e-webapp:
|
||||
needs: changes
|
||||
if: needs.changes.outputs.webapp == 'true'
|
||||
uses: ./.github/workflows/e2e-webapp.yml
|
||||
secrets:
|
||||
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
packages:
|
||||
needs: changes
|
||||
if: needs.changes.outputs.packages == 'true'
|
||||
uses: ./.github/workflows/unit-tests-packages.yml
|
||||
secrets:
|
||||
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
internal:
|
||||
needs: changes
|
||||
if: needs.changes.outputs.internal == 'true'
|
||||
uses: ./.github/workflows/unit-tests-internal.yml
|
||||
secrets:
|
||||
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
e2e:
|
||||
needs: changes
|
||||
if: needs.changes.outputs.cli == 'true'
|
||||
uses: ./.github/workflows/e2e.yml
|
||||
with:
|
||||
package: cli-v3
|
||||
secrets: inherit
|
||||
|
||||
sdk-compat:
|
||||
needs: changes
|
||||
if: needs.changes.outputs.sdk == 'true'
|
||||
uses: ./.github/workflows/sdk-compat.yml
|
||||
secrets: inherit
|
||||
|
||||
all-checks:
|
||||
name: All PR Checks
|
||||
needs:
|
||||
- changes
|
||||
- typecheck
|
||||
- webapp
|
||||
- e2e-webapp
|
||||
- packages
|
||||
- internal
|
||||
- e2e
|
||||
- sdk-compat
|
||||
if: always()
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Verify all checks
|
||||
run: |
|
||||
if [[ "${{ contains(needs.*.result, 'failure') }}" == "true" ]]; then
|
||||
echo "One or more checks failed"
|
||||
exit 1
|
||||
fi
|
||||
if [[ "${{ contains(needs.*.result, 'cancelled') }}" == "true" ]]; then
|
||||
echo "One or more checks were cancelled"
|
||||
exit 1
|
||||
fi
|
||||
echo "All checks passed or were skipped due to path filters"
|
||||
|
||||
@@ -4,6 +4,7 @@ permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
id-token: write
|
||||
attestations: write
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
@@ -13,6 +14,9 @@ on:
|
||||
type: string
|
||||
required: false
|
||||
default: ""
|
||||
secrets:
|
||||
SENTRY_AUTH_TOKEN:
|
||||
required: false
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
@@ -24,12 +28,13 @@ jobs:
|
||||
short_sha: ${{ steps.get_commit.outputs.sha_short }}
|
||||
steps:
|
||||
- name: 🏭 Setup Depot CLI
|
||||
uses: depot/setup-action@v1
|
||||
uses: depot/setup-action@15c09a5f77a0840ad4bce955686522a257853461 # v1.7.1
|
||||
|
||||
- name: ⬇️ Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
submodules: recursive
|
||||
persist-credentials: false
|
||||
|
||||
- name: "#️⃣ Get the image tag"
|
||||
id: get_tag
|
||||
@@ -40,42 +45,52 @@ jobs:
|
||||
- name: 🔢 Get the commit hash
|
||||
id: get_commit
|
||||
run: |
|
||||
echo "sha_short=$(echo ${{ github.sha }} | cut -c1-7)" >> "$GITHUB_OUTPUT"
|
||||
echo "sha_short=$(echo "${GITHUB_SHA}" | cut -c1-7)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: 📛 Set the tags
|
||||
id: set_tags
|
||||
run: |
|
||||
ref_without_tag=ghcr.io/triggerdotdev/trigger.dev
|
||||
image_tags=$ref_without_tag:${{ steps.get_tag.outputs.tag }}
|
||||
image_tags=$ref_without_tag:${STEPS_GET_TAG_OUTPUTS_TAG}
|
||||
|
||||
# if tag is a semver, also tag it as v4
|
||||
if [[ "${{ steps.get_tag.outputs.is_semver }}" == true ]]; then
|
||||
# TODO: switch to v4 tag on GA
|
||||
image_tags=$image_tags,$ref_without_tag:v4-beta
|
||||
# when pushing the mutable main tag, also push an immutable-by-convention
|
||||
# full-commit-sha tag so a commit can be resolved to a specific digest
|
||||
if [[ "${STEPS_GET_TAG_OUTPUTS_TAG}" == "main" ]]; then
|
||||
image_tags=$image_tags,$ref_without_tag:${GITHUB_SHA}
|
||||
fi
|
||||
|
||||
echo "image_tags=${image_tags}" >> "$GITHUB_OUTPUT"
|
||||
env:
|
||||
STEPS_GET_TAG_OUTPUTS_TAG: ${{ steps.get_tag.outputs.tag }}
|
||||
STEPS_GET_TAG_OUTPUTS_IS_SEMVER: ${{ steps.get_tag.outputs.is_semver }}
|
||||
|
||||
- name: 📝 Set the build info
|
||||
id: set_build_info
|
||||
run: |
|
||||
tag=${{ steps.get_tag.outputs.tag }}
|
||||
if [[ "${{ steps.get_tag.outputs.is_semver }}" == true ]]; then
|
||||
echo "BUILD_APP_VERSION=${tag}" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
echo "BUILD_GIT_SHA=${{ github.sha }}" >> "$GITHUB_OUTPUT"
|
||||
echo "BUILD_GIT_REF_NAME=${{ github.ref_name }}" >> "$GITHUB_OUTPUT"
|
||||
echo "BUILD_TIMESTAMP_SECONDS=$(date +%s)" >> "$GITHUB_OUTPUT"
|
||||
{
|
||||
tag="${STEPS_GET_TAG_OUTPUTS_TAG}"
|
||||
if [[ "${STEPS_GET_TAG_OUTPUTS_IS_SEMVER}" == true ]]; then
|
||||
echo "BUILD_APP_VERSION=${tag}"
|
||||
fi
|
||||
echo "BUILD_GIT_SHA=${GITHUB_SHA}"
|
||||
echo "BUILD_GIT_REF_NAME=${GITHUB_REF_NAME}"
|
||||
echo "BUILD_TIMESTAMP_SECONDS=$(date +%s)"
|
||||
echo "BUILD_TIMESTAMP_RFC3339=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
env:
|
||||
STEPS_GET_TAG_OUTPUTS_TAG: ${{ steps.get_tag.outputs.tag }}
|
||||
STEPS_GET_TAG_OUTPUTS_IS_SEMVER: ${{ steps.get_tag.outputs.is_semver }}
|
||||
|
||||
- name: 🐙 Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: 🐳 Build image and push to GitHub Container Registry
|
||||
uses: depot/build-push-action@v1
|
||||
id: build_push
|
||||
uses: depot/build-push-action@5f3b3c2e5a00f0093de47f657aeaefcedff27d18 # v1.17.0
|
||||
with:
|
||||
file: ./docker/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
@@ -86,8 +101,20 @@ jobs:
|
||||
BUILD_GIT_SHA=${{ steps.set_build_info.outputs.BUILD_GIT_SHA }}
|
||||
BUILD_GIT_REF_NAME=${{ steps.set_build_info.outputs.BUILD_GIT_REF_NAME }}
|
||||
BUILD_TIMESTAMP_SECONDS=${{ steps.set_build_info.outputs.BUILD_TIMESTAMP_SECONDS }}
|
||||
BUILD_TIMESTAMP_RFC3339=${{ steps.set_build_info.outputs.BUILD_TIMESTAMP_RFC3339 }}
|
||||
SENTRY_RELEASE=${{ steps.set_build_info.outputs.BUILD_GIT_SHA }}
|
||||
SENTRY_ORG=triggerdev
|
||||
SENTRY_PROJECT=trigger-cloud
|
||||
secrets: |
|
||||
sentry_auth_token=${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
|
||||
- name: 🪪 Attest build provenance
|
||||
# Image is already pushed by this point — don't fail releases (and the
|
||||
# downstream publish-helm job) on a Sigstore/GHCR-referrer hiccup. Real
|
||||
# config errors still surface as a step warning in the workflow run.
|
||||
continue-on-error: true
|
||||
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
|
||||
with:
|
||||
subject-name: ghcr.io/triggerdotdev/trigger.dev
|
||||
subject-digest: ${{ steps.build_push.outputs.digest }}
|
||||
push-to-registry: true
|
||||
|
||||
@@ -37,19 +37,22 @@ jobs:
|
||||
DOCKER_BUILDKIT: "1"
|
||||
steps:
|
||||
- name: 🏭 Setup Depot CLI
|
||||
uses: depot/setup-action@v1
|
||||
uses: depot/setup-action@15c09a5f77a0840ad4bce955686522a257853461 # v1.7.1
|
||||
|
||||
- name: ⬇️ Checkout git repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: 📦 Get image repo
|
||||
id: get_repository
|
||||
env:
|
||||
PACKAGE: ${{ matrix.package }}
|
||||
run: |
|
||||
if [[ "${{ matrix.package }}" == *-provider ]]; then
|
||||
provider_type=$(echo "${{ matrix.package }}" | cut -d- -f1)
|
||||
repo=provider/${provider_type}
|
||||
if [[ "$PACKAGE" == *-provider ]]; then
|
||||
repo="provider/${PACKAGE%-provider}"
|
||||
else
|
||||
repo="${{ matrix.package }}"
|
||||
repo="$PACKAGE"
|
||||
fi
|
||||
echo "repo=${repo}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
@@ -62,26 +65,24 @@ jobs:
|
||||
- name: 📛 Set tags to push
|
||||
id: set_tags
|
||||
run: |
|
||||
ref_without_tag=ghcr.io/triggerdotdev/${{ steps.get_repository.outputs.repo }}
|
||||
image_tags=$ref_without_tag:${{ steps.get_tag.outputs.tag }}
|
||||
|
||||
# if tag is a semver, also tag it as v4
|
||||
if [[ "${{ steps.get_tag.outputs.is_semver }}" == true ]]; then
|
||||
# TODO: switch to v4 tag on GA
|
||||
image_tags=$image_tags,$ref_without_tag:v4-beta
|
||||
fi
|
||||
ref_without_tag=ghcr.io/triggerdotdev/${STEPS_GET_REPOSITORY_OUTPUTS_REPO}
|
||||
image_tags=$ref_without_tag:${STEPS_GET_TAG_OUTPUTS_TAG}
|
||||
|
||||
echo "image_tags=${image_tags}" >> "$GITHUB_OUTPUT"
|
||||
env:
|
||||
STEPS_GET_REPOSITORY_OUTPUTS_REPO: ${{ steps.get_repository.outputs.repo }}
|
||||
STEPS_GET_TAG_OUTPUTS_TAG: ${{ steps.get_tag.outputs.tag }}
|
||||
STEPS_GET_TAG_OUTPUTS_IS_SEMVER: ${{ steps.get_tag.outputs.is_semver }}
|
||||
|
||||
- name: 🐙 Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: 🐳 Build image and push to GitHub Container Registry
|
||||
uses: depot/build-push-action@v1
|
||||
uses: depot/build-push-action@5f3b3c2e5a00f0093de47f657aeaefcedff27d18 # v1.17.0
|
||||
with:
|
||||
file: ./apps/${{ matrix.package }}/Containerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
|
||||
@@ -8,6 +8,11 @@ on:
|
||||
type: string
|
||||
required: false
|
||||
default: ""
|
||||
secrets:
|
||||
DOCKERHUB_USERNAME:
|
||||
required: false
|
||||
DOCKERHUB_TOKEN:
|
||||
required: false
|
||||
push:
|
||||
tags:
|
||||
- "infra-dev-*"
|
||||
@@ -26,18 +31,22 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
DOCKER_BUILDKIT: "1"
|
||||
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
steps:
|
||||
- name: ⬇️ Checkout git repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: 📦 Get image repo
|
||||
id: get_repository
|
||||
env:
|
||||
PACKAGE: ${{ matrix.package }}
|
||||
run: |
|
||||
if [[ "${{ matrix.package }}" == *-provider ]]; then
|
||||
provider_type=$(echo "${{ matrix.package }}" | cut -d- -f1)
|
||||
repo=provider/${provider_type}
|
||||
if [[ "$PACKAGE" == *-provider ]]; then
|
||||
repo="provider/${PACKAGE%-provider}"
|
||||
else
|
||||
repo="${{ matrix.package }}"
|
||||
repo="$PACKAGE"
|
||||
fi
|
||||
echo "repo=${repo}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
@@ -47,11 +56,12 @@ jobs:
|
||||
tag: ${{ inputs.image_tag }}
|
||||
|
||||
- name: 🐋 Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||
|
||||
# ..to avoid rate limits when pulling images
|
||||
- name: 🐳 Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
if: ${{ env.DOCKERHUB_USERNAME }}
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
@@ -62,7 +72,7 @@ jobs:
|
||||
|
||||
# ..to push image
|
||||
- name: 🐙 Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
|
||||
@@ -8,6 +8,13 @@ on:
|
||||
description: The image tag to publish
|
||||
required: true
|
||||
type: string
|
||||
secrets:
|
||||
DOCKERHUB_USERNAME:
|
||||
required: false
|
||||
DOCKERHUB_TOKEN:
|
||||
required: false
|
||||
SENTRY_AUTH_TOKEN:
|
||||
required: false
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
@@ -37,8 +44,6 @@ on:
|
||||
- "tests/**"
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
packages: write
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
@@ -50,29 +55,44 @@ env:
|
||||
jobs:
|
||||
typecheck:
|
||||
uses: ./.github/workflows/typecheck.yml
|
||||
secrets: inherit
|
||||
|
||||
units:
|
||||
uses: ./.github/workflows/unit-tests.yml
|
||||
secrets: inherit
|
||||
secrets:
|
||||
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
publish-webapp:
|
||||
needs: [typecheck]
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
id-token: write
|
||||
attestations: write
|
||||
uses: ./.github/workflows/publish-webapp.yml
|
||||
secrets: inherit
|
||||
secrets:
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
with:
|
||||
image_tag: ${{ inputs.image_tag }}
|
||||
|
||||
publish-worker:
|
||||
needs: [typecheck]
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
uses: ./.github/workflows/publish-worker.yml
|
||||
secrets: inherit
|
||||
secrets:
|
||||
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
with:
|
||||
image_tag: ${{ inputs.image_tag }}
|
||||
|
||||
publish-worker-v4:
|
||||
needs: [typecheck]
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
id-token: write
|
||||
uses: ./.github/workflows/publish-worker-v4.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
image_tag: ${{ inputs.image_tag }}
|
||||
|
||||
@@ -28,10 +28,12 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Helm
|
||||
uses: azure/setup-helm@v4
|
||||
uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5.0.0
|
||||
with:
|
||||
version: "3.18.3"
|
||||
|
||||
@@ -54,7 +56,7 @@ jobs:
|
||||
--output-dir ./helm-output
|
||||
|
||||
- name: Validate manifests
|
||||
uses: docker://ghcr.io/yannh/kubeconform:v0.7.0
|
||||
uses: docker://ghcr.io/yannh/kubeconform:v0.7.0@sha256:85dbef6b4b312b99133decc9c6fc9495e9fc5f92293d4ff3b7e1b30f5611823c
|
||||
with:
|
||||
entrypoint: '/kubeconform'
|
||||
args: "-summary -output json ./helm-output"
|
||||
@@ -67,10 +69,12 @@ jobs:
|
||||
packages: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Helm
|
||||
uses: azure/setup-helm@v4
|
||||
uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5.0.0
|
||||
with:
|
||||
version: "3.18.3"
|
||||
|
||||
@@ -83,7 +87,7 @@ jobs:
|
||||
for file in ./charts/*.tgz; do echo "Extracting $file"; tar -xzf "$file" -C ./charts; done
|
||||
|
||||
- name: Log in to Container Registry
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
@@ -92,18 +96,20 @@ jobs:
|
||||
- name: Extract version from tag or input
|
||||
id: version
|
||||
run: |
|
||||
if [ -n "${{ inputs.chart_version }}" ]; then
|
||||
VERSION="${{ inputs.chart_version }}"
|
||||
if [ -n "${INPUTS_CHART_VERSION}" ]; then
|
||||
VERSION="${INPUTS_CHART_VERSION}"
|
||||
else
|
||||
VERSION="${{ github.ref_name }}"
|
||||
VERSION="${GITHUB_REF_NAME}"
|
||||
VERSION="${VERSION#helm-v}"
|
||||
fi
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||
echo "Releasing version: $VERSION"
|
||||
env:
|
||||
INPUTS_CHART_VERSION: ${{ inputs.chart_version }}
|
||||
|
||||
- name: Check Chart.yaml version matches release version
|
||||
run: |
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
VERSION="${STEPS_VERSION_OUTPUTS_VERSION}"
|
||||
CHART_VERSION=$(grep '^version:' ./hosting/k8s/helm/Chart.yaml | awk '{print $2}')
|
||||
echo "Chart.yaml version: $CHART_VERSION"
|
||||
echo "Release version: $VERSION"
|
||||
@@ -112,6 +118,8 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ Chart.yaml version matches release version."
|
||||
env:
|
||||
STEPS_VERSION_OUTPUTS_VERSION: ${{ steps.version.outputs.version }}
|
||||
|
||||
- name: Package Helm Chart
|
||||
run: |
|
||||
@@ -119,15 +127,17 @@ jobs:
|
||||
|
||||
- name: Push Helm Chart to GHCR
|
||||
run: |
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
VERSION="${STEPS_VERSION_OUTPUTS_VERSION}"
|
||||
CHART_PACKAGE="/tmp/${{ env.CHART_NAME }}-${VERSION}.tgz"
|
||||
|
||||
# Push to GHCR OCI registry
|
||||
helm push "$CHART_PACKAGE" "oci://${{ env.REGISTRY }}/${{ github.repository_owner }}/charts"
|
||||
env:
|
||||
STEPS_VERSION_OUTPUTS_VERSION: ${{ steps.version.outputs.version }}
|
||||
|
||||
- name: Create GitHub Release
|
||||
id: release
|
||||
uses: softprops/action-gh-release@v1
|
||||
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
|
||||
with:
|
||||
tag_name: helm-v${{ steps.version.outputs.version }}
|
||||
name: "Helm Chart ${{ steps.version.outputs.version }}"
|
||||
|
||||
@@ -33,6 +33,7 @@ jobs:
|
||||
show-release-summary:
|
||||
name: 📋 Release Summary
|
||||
runs-on: ubuntu-latest
|
||||
permissions: {}
|
||||
if: |
|
||||
github.repository == 'triggerdotdev/trigger.dev' &&
|
||||
github.event_name == 'pull_request' &&
|
||||
@@ -43,7 +44,7 @@ jobs:
|
||||
env:
|
||||
PR_BODY: ${{ github.event.pull_request.body }}
|
||||
run: |
|
||||
echo "$PR_BODY" | sed -n '/^# Releases/,$p' >> $GITHUB_STEP_SUMMARY
|
||||
echo "$PR_BODY" | sed -n '/^# Releases/,$p' >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
release:
|
||||
name: 🚀 Release npm packages
|
||||
@@ -63,9 +64,10 @@ jobs:
|
||||
published: ${{ steps.changesets.outputs.published }}
|
||||
published_packages: ${{ steps.changesets.outputs.publishedPackages }}
|
||||
published_package_version: ${{ steps.get_version.outputs.package_version }}
|
||||
is_prerelease: ${{ steps.get_version.outputs.is_prerelease }}
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 # zizmor: ignore[artipacked] needs persisted git creds for tag push; no artifact upload here so no leak path
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.ref || github.sha }}
|
||||
@@ -73,18 +75,20 @@ jobs:
|
||||
- name: Verify ref is on main
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
run: |
|
||||
if ! git merge-base --is-ancestor ${{ github.event.inputs.ref }} origin/main; then
|
||||
if ! git merge-base --is-ancestor "${GITHUB_EVENT_INPUTS_REF}" origin/main; then
|
||||
echo "Error: ref must be an ancestor of main (i.e., already merged)"
|
||||
exit 1
|
||||
fi
|
||||
env:
|
||||
GITHUB_EVENT_INPUTS_REF: ${{ github.event.inputs.ref }}
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||
with:
|
||||
version: 10.23.0
|
||||
version: 10.33.2
|
||||
|
||||
- name: Setup node
|
||||
uses: buildjet/setup-node@v4
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: 20.20.0
|
||||
cache: "pnpm"
|
||||
@@ -108,7 +112,7 @@ jobs:
|
||||
|
||||
- name: Publish
|
||||
id: changesets
|
||||
uses: changesets/action@v1
|
||||
uses: changesets/action@6a0a831ff30acef54f2c6aa1cbbc1096b066edaf # v1.7.0
|
||||
with:
|
||||
publish: pnpm run changeset:release
|
||||
createGithubReleases: false
|
||||
@@ -119,35 +123,54 @@ jobs:
|
||||
if: steps.changesets.outputs.published == 'true'
|
||||
id: get_version
|
||||
run: |
|
||||
package_version=$(echo '${{ steps.changesets.outputs.publishedPackages }}' | jq -r '.[0].version')
|
||||
package_version=$(echo "${STEPS_CHANGESETS_OUTPUTS_PUBLISHEDPACKAGES}" | jq -r '.[0].version')
|
||||
echo "package_version=${package_version}" >> "$GITHUB_OUTPUT"
|
||||
# Any semver with a hyphen is a prerelease (e.g. 4.5.0-rc.0, 0.0.0-snapshot-...)
|
||||
if [[ "${package_version}" == *-* ]]; then
|
||||
echo "is_prerelease=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "is_prerelease=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
env:
|
||||
STEPS_CHANGESETS_OUTPUTS_PUBLISHEDPACKAGES: ${{ steps.changesets.outputs.publishedPackages }}
|
||||
|
||||
- name: Create unified GitHub release
|
||||
if: steps.changesets.outputs.published == 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
RELEASE_PR_BODY: ${{ github.event.pull_request.body }}
|
||||
STEPS_GET_VERSION_OUTPUTS_PACKAGE_VERSION: ${{ steps.get_version.outputs.package_version }}
|
||||
STEPS_GET_VERSION_OUTPUTS_IS_PRERELEASE: ${{ steps.get_version.outputs.is_prerelease }}
|
||||
run: |
|
||||
VERSION="${{ steps.get_version.outputs.package_version }}"
|
||||
VERSION="${STEPS_GET_VERSION_OUTPUTS_PACKAGE_VERSION}"
|
||||
node scripts/generate-github-release.mjs "$VERSION" > /tmp/release-body.md
|
||||
PRERELEASE_FLAG=""
|
||||
if [ "${STEPS_GET_VERSION_OUTPUTS_IS_PRERELEASE}" = "true" ]; then
|
||||
PRERELEASE_FLAG="--prerelease"
|
||||
fi
|
||||
gh release create "v${VERSION}" \
|
||||
--title "trigger.dev v${VERSION}" \
|
||||
--notes-file /tmp/release-body.md \
|
||||
--target main
|
||||
--target main \
|
||||
$PRERELEASE_FLAG
|
||||
|
||||
- name: Create and push Docker tag
|
||||
if: steps.changesets.outputs.published == 'true'
|
||||
run: |
|
||||
set -e
|
||||
git tag "v.docker.${{ steps.get_version.outputs.package_version }}"
|
||||
git push origin "v.docker.${{ steps.get_version.outputs.package_version }}"
|
||||
git tag "v.docker.${STEPS_GET_VERSION_OUTPUTS_PACKAGE_VERSION}"
|
||||
git push origin "v.docker.${STEPS_GET_VERSION_OUTPUTS_PACKAGE_VERSION}"
|
||||
env:
|
||||
STEPS_GET_VERSION_OUTPUTS_PACKAGE_VERSION: ${{ steps.get_version.outputs.package_version }}
|
||||
|
||||
- name: Create and push Helm chart tag
|
||||
if: steps.changesets.outputs.published == 'true'
|
||||
run: |
|
||||
set -e
|
||||
git tag "helm-v${{ steps.get_version.outputs.package_version }}"
|
||||
git push origin "helm-v${{ steps.get_version.outputs.package_version }}"
|
||||
git tag "helm-v${STEPS_GET_VERSION_OUTPUTS_PACKAGE_VERSION}"
|
||||
git push origin "helm-v${STEPS_GET_VERSION_OUTPUTS_PACKAGE_VERSION}"
|
||||
env:
|
||||
STEPS_GET_VERSION_OUTPUTS_PACKAGE_VERSION: ${{ steps.get_version.outputs.package_version }}
|
||||
|
||||
# Trigger Docker builds directly via workflow_call since tags pushed with
|
||||
# GITHUB_TOKEN don't trigger other workflows (GitHub Actions limitation).
|
||||
@@ -155,8 +178,16 @@ jobs:
|
||||
name: 🐳 Publish Docker images
|
||||
needs: release
|
||||
if: needs.release.outputs.published == 'true'
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
id-token: write
|
||||
attestations: write
|
||||
uses: ./.github/workflows/publish.yml
|
||||
secrets: inherit
|
||||
secrets:
|
||||
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
with:
|
||||
image_tag: v${{ needs.release.outputs.published_package_version }}
|
||||
|
||||
@@ -171,7 +202,6 @@ jobs:
|
||||
contents: write
|
||||
packages: write
|
||||
uses: ./.github/workflows/release-helm.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
chart_version: ${{ needs.release.outputs.published_package_version }}
|
||||
|
||||
@@ -189,9 +219,10 @@ jobs:
|
||||
- name: Update GitHub release with Docker image link
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
NEEDS_RELEASE_OUTPUTS_PUBLISHED_PACKAGE_VERSION: ${{ needs.release.outputs.published_package_version }}
|
||||
run: |
|
||||
set -e
|
||||
VERSION="${{ needs.release.outputs.published_package_version }}"
|
||||
VERSION="${NEEDS_RELEASE_OUTPUTS_PUBLISHED_PACKAGE_VERSION}"
|
||||
TAG="v${VERSION}"
|
||||
|
||||
# Query GHCR for the version ID matching this tag
|
||||
@@ -221,10 +252,11 @@ jobs:
|
||||
dispatch-changelog:
|
||||
name: 📝 Dispatch changelog PR
|
||||
needs: [release, update-release]
|
||||
if: needs.release.outputs.published == 'true'
|
||||
if: needs.release.outputs.published == 'true' && needs.release.outputs.is_prerelease != 'true'
|
||||
runs-on: ubuntu-latest
|
||||
permissions: {}
|
||||
steps:
|
||||
- uses: peter-evans/repository-dispatch@v3
|
||||
- uses: peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697 # v4.0.1
|
||||
with:
|
||||
token: ${{ secrets.CROSS_REPO_PAT }}
|
||||
repository: triggerdotdev/trigger.dev-site-v3
|
||||
@@ -242,18 +274,19 @@ jobs:
|
||||
if: github.repository == 'triggerdotdev/trigger.dev' && github.event_name == 'workflow_dispatch' && github.event.inputs.type == 'prerelease'
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.event.inputs.ref }}
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||
with:
|
||||
version: 10.23.0
|
||||
version: 10.33.2
|
||||
|
||||
- name: Setup node
|
||||
uses: buildjet/setup-node@v4
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: 20.20.0
|
||||
cache: "pnpm"
|
||||
@@ -269,10 +302,18 @@ jobs:
|
||||
- name: Generate Prisma Client
|
||||
run: pnpm run generate
|
||||
|
||||
- name: Exit changeset pre mode (if active)
|
||||
run: |
|
||||
if [ -f .changeset/pre.json ]; then
|
||||
echo "Repo is in changeset pre mode; exiting so snapshot release can run"
|
||||
pnpm exec changeset pre exit
|
||||
fi
|
||||
|
||||
- name: Snapshot version
|
||||
run: pnpm exec changeset version --snapshot ${{ github.event.inputs.prerelease_tag }}
|
||||
run: pnpm exec changeset version --snapshot "${GITHUB_EVENT_INPUTS_PRERELEASE_TAG}"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_EVENT_INPUTS_PRERELEASE_TAG: ${{ github.event.inputs.prerelease_tag }}
|
||||
|
||||
- name: Clean
|
||||
run: pnpm run clean --filter "@trigger.dev/*" --filter "trigger.dev"
|
||||
@@ -281,6 +322,7 @@ jobs:
|
||||
run: pnpm run build --filter "@trigger.dev/*" --filter "trigger.dev"
|
||||
|
||||
- name: Publish prerelease
|
||||
run: pnpm exec changeset publish --no-git-tag --snapshot --tag ${{ github.event.inputs.prerelease_tag }}
|
||||
run: pnpm exec changeset publish --no-git-tag --snapshot --tag "${GITHUB_EVENT_INPUTS_PRERELEASE_TAG}"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_EVENT_INPUTS_PRERELEASE_TAG: ${{ github.event.inputs.prerelease_tag }}
|
||||
|
||||
@@ -18,17 +18,18 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: ⬇️ Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: ⎔ Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||
with:
|
||||
version: 10.23.0
|
||||
version: 10.33.2
|
||||
|
||||
- name: ⎔ Setup node
|
||||
uses: buildjet/setup-node@v4
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
cache: "pnpm"
|
||||
@@ -56,23 +57,24 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: ⬇️ Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: ⎔ Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||
with:
|
||||
version: 10.23.0
|
||||
version: 10.33.2
|
||||
|
||||
- name: ⎔ Setup node
|
||||
uses: buildjet/setup-node@v4
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: 20.20.0
|
||||
cache: "pnpm"
|
||||
|
||||
- name: 🥟 Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
@@ -97,23 +99,24 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: ⬇️ Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: ⎔ Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||
with:
|
||||
version: 10.23.0
|
||||
version: 10.33.2
|
||||
|
||||
- name: ⎔ Setup node
|
||||
uses: buildjet/setup-node@v4
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: 20.20.0
|
||||
cache: "pnpm"
|
||||
|
||||
- name: 🦕 Setup Deno
|
||||
uses: denoland/setup-deno@v2
|
||||
uses: denoland/setup-deno@667a34cdef165d8d2b2e98dde39547c9daac7282 # v2.0.4
|
||||
with:
|
||||
deno-version: v2.x
|
||||
|
||||
@@ -142,17 +145,18 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: ⬇️ Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: ⎔ Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||
with:
|
||||
version: 10.23.0
|
||||
version: 10.33.2
|
||||
|
||||
- name: ⎔ Setup node
|
||||
uses: buildjet/setup-node@v4
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: 20.20.0
|
||||
cache: "pnpm"
|
||||
|
||||
@@ -12,17 +12,18 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: ⬇️ Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: ⎔ Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||
with:
|
||||
version: 10.23.0
|
||||
version: 10.33.2
|
||||
|
||||
- name: ⎔ Setup node
|
||||
uses: buildjet/setup-node@v4
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: 20.20.0
|
||||
cache: "pnpm"
|
||||
|
||||
@@ -5,6 +5,11 @@ permissions:
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
secrets:
|
||||
DOCKERHUB_USERNAME:
|
||||
required: false
|
||||
DOCKERHUB_TOKEN:
|
||||
required: false
|
||||
|
||||
jobs:
|
||||
unitTests:
|
||||
@@ -46,17 +51,18 @@ jobs:
|
||||
run: sudo systemctl restart docker
|
||||
|
||||
- name: ⬇️ Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: ⎔ Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||
with:
|
||||
version: 10.23.0
|
||||
version: 10.33.2
|
||||
|
||||
- name: ⎔ Setup node
|
||||
uses: buildjet/setup-node@v4
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: 20.20.0
|
||||
cache: "pnpm"
|
||||
@@ -64,7 +70,7 @@ jobs:
|
||||
# ..to avoid rate limits when pulling images
|
||||
- name: 🐳 Login to DockerHub
|
||||
if: ${{ env.DOCKERHUB_USERNAME }}
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
@@ -101,7 +107,7 @@ jobs:
|
||||
|
||||
- name: Upload blob reports to GitHub Actions Artifacts
|
||||
if: ${{ !cancelled() }}
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: internal-blob-report-${{ matrix.shardIndex }}
|
||||
path: .vitest-reports/*
|
||||
@@ -115,23 +121,24 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: ⬇️ Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: ⎔ Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||
with:
|
||||
version: 10.23.0
|
||||
version: 10.33.2
|
||||
|
||||
- name: ⎔ Setup node
|
||||
uses: buildjet/setup-node@v4
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: 20.20.0
|
||||
# no cache enabled, we're not installing deps
|
||||
|
||||
- name: Download blob reports from GitHub Actions Artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
path: .vitest-reports
|
||||
pattern: internal-blob-report-*
|
||||
|
||||
@@ -5,6 +5,11 @@ permissions:
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
secrets:
|
||||
DOCKERHUB_USERNAME:
|
||||
required: false
|
||||
DOCKERHUB_TOKEN:
|
||||
required: false
|
||||
|
||||
jobs:
|
||||
unitTests:
|
||||
@@ -46,17 +51,18 @@ jobs:
|
||||
run: sudo systemctl restart docker
|
||||
|
||||
- name: ⬇️ Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: ⎔ Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||
with:
|
||||
version: 10.23.0
|
||||
version: 10.33.2
|
||||
|
||||
- name: ⎔ Setup node
|
||||
uses: buildjet/setup-node@v4
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: 20.20.0
|
||||
cache: "pnpm"
|
||||
@@ -64,7 +70,7 @@ jobs:
|
||||
# ..to avoid rate limits when pulling images
|
||||
- name: 🐳 Login to DockerHub
|
||||
if: ${{ env.DOCKERHUB_USERNAME }}
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
@@ -101,7 +107,7 @@ jobs:
|
||||
|
||||
- name: Upload blob reports to GitHub Actions Artifacts
|
||||
if: ${{ !cancelled() }}
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: packages-blob-report-${{ matrix.shardIndex }}
|
||||
path: .vitest-reports/*
|
||||
@@ -115,23 +121,24 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: ⬇️ Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: ⎔ Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||
with:
|
||||
version: 10.23.0
|
||||
version: 10.33.2
|
||||
|
||||
- name: ⎔ Setup node
|
||||
uses: buildjet/setup-node@v4
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: 20.20.0
|
||||
# no cache enabled, we're not installing deps
|
||||
|
||||
- name: Download blob reports from GitHub Actions Artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
path: .vitest-reports
|
||||
pattern: packages-blob-report-*
|
||||
|
||||
@@ -5,6 +5,11 @@ permissions:
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
secrets:
|
||||
DOCKERHUB_USERNAME:
|
||||
required: false
|
||||
DOCKERHUB_TOKEN:
|
||||
required: false
|
||||
|
||||
jobs:
|
||||
unitTests:
|
||||
@@ -46,17 +51,18 @@ jobs:
|
||||
run: sudo systemctl restart docker
|
||||
|
||||
- name: ⬇️ Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: ⎔ Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||
with:
|
||||
version: 10.23.0
|
||||
version: 10.33.2
|
||||
|
||||
- name: ⎔ Setup node
|
||||
uses: buildjet/setup-node@v4
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: 20.20.0
|
||||
cache: "pnpm"
|
||||
@@ -64,7 +70,7 @@ jobs:
|
||||
# ..to avoid rate limits when pulling images
|
||||
- name: 🐳 Login to DockerHub
|
||||
if: ${{ env.DOCKERHUB_USERNAME }}
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
@@ -109,7 +115,7 @@ jobs:
|
||||
|
||||
- name: Upload blob reports to GitHub Actions Artifacts
|
||||
if: ${{ !cancelled() }}
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: webapp-blob-report-${{ matrix.shardIndex }}
|
||||
path: .vitest-reports/*
|
||||
@@ -123,23 +129,24 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: ⬇️ Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: ⎔ Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||
with:
|
||||
version: 10.23.0
|
||||
version: 10.33.2
|
||||
|
||||
- name: ⎔ Setup node
|
||||
uses: buildjet/setup-node@v4
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: 20.20.0
|
||||
# no cache enabled, we're not installing deps
|
||||
|
||||
- name: Download blob reports from GitHub Actions Artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
path: .vitest-reports
|
||||
pattern: webapp-blob-report-*
|
||||
|
||||
@@ -5,17 +5,30 @@ permissions:
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
secrets:
|
||||
DOCKERHUB_USERNAME:
|
||||
required: false
|
||||
DOCKERHUB_TOKEN:
|
||||
required: false
|
||||
|
||||
jobs:
|
||||
webapp:
|
||||
uses: ./.github/workflows/unit-tests-webapp.yml
|
||||
secrets: inherit
|
||||
secrets:
|
||||
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
e2e-webapp:
|
||||
uses: ./.github/workflows/e2e-webapp.yml
|
||||
secrets: inherit
|
||||
secrets:
|
||||
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
packages:
|
||||
uses: ./.github/workflows/unit-tests-packages.yml
|
||||
secrets: inherit
|
||||
secrets:
|
||||
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
internal:
|
||||
uses: ./.github/workflows/unit-tests-internal.yml
|
||||
secrets: inherit
|
||||
secrets:
|
||||
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
name: Vouch - Check PR
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
pull_request_target: # zizmor: ignore[dangerous-triggers] needed to comment/close fork PRs; safe because we never check out PR HEAD ref so no fork-controlled code runs
|
||||
types: [opened, reopened]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
issues: read
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
check-vouch:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write # auto-close unvouched PRs
|
||||
issues: read
|
||||
steps:
|
||||
- uses: mitchellh/vouch/action/check-pr@c6d80ead49839655b61b422700b7a3bc9d0804a9 # v1.4.2
|
||||
with:
|
||||
@@ -23,12 +24,15 @@ jobs:
|
||||
|
||||
require-draft:
|
||||
needs: check-vouch
|
||||
permissions:
|
||||
pull-requests: write # close non-draft PRs with a comment
|
||||
if: >
|
||||
github.event.pull_request.draft == false &&
|
||||
github.event.pull_request.author_association != 'MEMBER' &&
|
||||
github.event.pull_request.author_association != 'OWNER' &&
|
||||
github.event.pull_request.author_association != 'COLLABORATOR' &&
|
||||
github.event.pull_request.user.login != 'devin-ai-integration[bot]'
|
||||
github.event.pull_request.user.login != 'devin-ai-integration[bot]' &&
|
||||
github.event.pull_request.user.login != 'dependabot[bot]'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Close non-draft PR
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
name: Workflow Checks
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- '.github/workflows/**'
|
||||
- '.github/actions/**'
|
||||
- '.github/zizmor.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/workflows/**'
|
||||
- '.github/actions/**'
|
||||
- '.github/zizmor.yml'
|
||||
|
||||
permissions: {}
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
actionlint:
|
||||
name: Actionlint
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Run actionlint
|
||||
uses: docker://rhysd/actionlint:1.7.12@sha256:b1934ee5f1c509618f2508e6eb47ee0d3520686341fec936f3b79331f9315667
|
||||
|
||||
zizmor:
|
||||
name: Zizmor
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
security-events: write # Upload SARIF to GitHub Security tab
|
||||
contents: read # Read workflow files for analysis
|
||||
actions: read # Read workflow run metadata
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Run zizmor
|
||||
uses: zizmorcore/zizmor-action@b1d7e1fb5de872772f31590499237e7cce841e8e # v0.5.3
|
||||
@@ -0,0 +1,5 @@
|
||||
rules:
|
||||
unpinned-uses:
|
||||
config:
|
||||
policies:
|
||||
'*': hash-pin
|
||||
@@ -65,6 +65,10 @@ apps/**/public/build
|
||||
/packages/trigger-sdk/src/package.json
|
||||
/packages/python/src/package.json
|
||||
**/.claude/settings.local.json
|
||||
.claude/architecture/
|
||||
.claude/docs-plans/
|
||||
.claude/review-guides/
|
||||
.claude/scheduled_tasks.lock
|
||||
.mcp.log
|
||||
.mcp.json
|
||||
.cursor/debug.log
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
area: webapp
|
||||
type: feature
|
||||
---
|
||||
|
||||
Add a "Back office" tab to `/admin` and a per-organization detail page at `/admin/back-office/orgs/:orgId`. The first action available on that page is editing the org's API rate limit: admins can save a `tokenBucket` override (refill rate, interval, max tokens) and see a plain-English preview of the resulting sustained rate and burst allowance. Writes are audit-logged via the server logger.
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
area: webapp
|
||||
type: improvement
|
||||
---
|
||||
|
||||
Make it clear in the admin that feature flags are global and should rarely be changed.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
area: webapp
|
||||
type: improvement
|
||||
---
|
||||
|
||||
Preserve search string when switching between the Users and Organizations tabs in the admin dashboard.
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
area: webapp
|
||||
type: improvement
|
||||
---
|
||||
|
||||
Admin worker groups API: add GET loader and expose more fields on POST.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
area: webapp
|
||||
type: feature
|
||||
---
|
||||
|
||||
New Agent Playground for testing `chat.agent` tasks interactively — multi-turn chat with tool-call visualization, a side panel for payload / schema / clientData configuration, and trigger-config controls for `maxDuration`, version pin, and region.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
area: webapp
|
||||
type: feature
|
||||
---
|
||||
|
||||
New Agents page in the dashboard listing every `chat.agent` task in the environment with active/inactive status and run counts, plus fuzzy search for navigating large agent catalogs.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
area: webapp
|
||||
type: feature
|
||||
---
|
||||
|
||||
AI generation spans in the run trace get a dedicated inspector showing model, provider, token counts, cost, token speed, finish reason, service tier, tool count, and a link to the prompt version that produced the generation.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
area: webapp
|
||||
type: fix
|
||||
---
|
||||
|
||||
Batch items that hit the environment queue size limit now fast-fail without
|
||||
retries and without creating pre-failed TaskRuns.
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
area: webapp
|
||||
type: fix
|
||||
---
|
||||
|
||||
Show the cancel button in the runs list for runs in `DEQUEUED` status. `DEQUEUED` was missing from `NON_FINAL_RUN_STATUSES` so the list hid the button even though the single run page allowed it.
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
area: webapp
|
||||
type: breaking
|
||||
---
|
||||
|
||||
Add server-side deprecation gate for deploys from v3 CLI versions (gated by `DEPRECATE_V3_CLI_DEPLOYS_ENABLED`). v4 CLI deploys are unaffected.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
area: webapp
|
||||
type: fix
|
||||
---
|
||||
|
||||
Dashboard runs, sessions, batches, and schedule-detail loaders now return 404 (or redirect to the user's home with a toast for missing projects) instead of 500 when a slug doesn't resolve.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
area: webapp
|
||||
type: feature
|
||||
---
|
||||
|
||||
Expose `is_warm_start` in the TRQL `runs` schema so warm vs cold start data can be queried and visualized in Dashboards.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
area: webapp
|
||||
type: fix
|
||||
---
|
||||
|
||||
Per-queue length limits and the dashboard's "Queued | Running" columns now reflect the true total across all concurrency-key variants. Previously both read 0 for any queue that used concurrency keys, allowing the per-queue cap to be bypassed.
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
area: webapp
|
||||
type: fix
|
||||
---
|
||||
|
||||
Fix memory leak where every aborted SSE connection pinned the full request/response graph on Node 20, caused by `AbortSignal.any()` in `sse.ts` retaining its source signals indefinitely (see nodejs/node#54614, nodejs/node#55351). Also clear the `setTimeout(abort)` timer in `entry.server.tsx` so successful HTML renders don't pin the React tree for 30s per request.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
area: webapp
|
||||
type: fix
|
||||
---
|
||||
|
||||
Retry on unique-constraint collisions when assigning the next worker deployment version so concurrent deploys to the same environment no longer fail with P2002.
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
area: webapp
|
||||
type: improvement
|
||||
---
|
||||
|
||||
Add 60s fresh / 60s stale SWR cache to `getEntitlement` in `platform.v3.server.ts`. Eliminates a synchronous billing-service HTTP round trip on every trigger. Reuses the existing `platformCache` (LRU memory + Redis) pattern already used for `limits` and `usage`. Cache key is `${orgId}`. Errors return a permissive `{ hasAccess: true }` fallback (existing behavior) and are also cached to prevent thundering-herd on billing outages.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
area: webapp
|
||||
type: improvement
|
||||
---
|
||||
|
||||
Downgrade the "Google auth conflict" log from `error` to `warn`. This branch handles an expected user-state mismatch (Google ID belongs to one user, email is on another) by returning the existing auth user — there's no exception to chase, so it shouldn't page on the Sentry error channel.
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
area: webapp
|
||||
type: improvement
|
||||
---
|
||||
|
||||
Show a `MicroVM` badge next to the region name on the regions page.
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
area: webapp
|
||||
type: improvement
|
||||
---
|
||||
|
||||
Increase default maximum project count per organization from 10 to 25
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
area: webapp
|
||||
type: fix
|
||||
---
|
||||
|
||||
Validate email format on the magic link login form.
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
area: webapp
|
||||
type: improvement
|
||||
---
|
||||
|
||||
Merge execution snapshot creation into the dequeue taskRun.update transaction, reducing 2 DB commits to 1 per dequeue operation
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
area: webapp
|
||||
type: feature
|
||||
---
|
||||
|
||||
New Models page in the dashboard: a provider-grouped catalog of LLMs (OpenAI, Anthropic, Google, etc.) with pricing, capabilities, and cross-tenant usage metrics, plus per-model detail pages with token / cost / latency charts and a side-by-side compare panel.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
area: webapp
|
||||
type: feature
|
||||
---
|
||||
|
||||
Lay the groundwork for an opt-in burst-protection layer on the trigger hot path. This release ships **monitoring only** — operators can observe per-env trigger storms via two opt-in modes, but no trigger calls are diverted or rate-limited yet (active burst smoothing follows in a later release). All new env vars are prefixed `TRIGGER_MOLLIFIER_*` and default off, so existing deployments see no behaviour change. With `TRIGGER_MOLLIFIER_SHADOW_MODE=1`, each trigger evaluates a per-env rate counter and logs `mollifier.would_mollify` when the threshold is crossed. With `TRIGGER_MOLLIFIER_ENABLED=1` plus a per-org `mollifierEnabled` flag, over-threshold triggers are also recorded in a Redis audit buffer alongside the normal `engine.trigger` call, drained by a background no-op consumer. The drainer has its own switch (`TRIGGER_MOLLIFIER_DRAINER_ENABLED`) so multi-replica deployments can pin the polling loop to a single worker service while every replica still produces into the buffer; unset, it inherits `TRIGGER_MOLLIFIER_ENABLED` so single-container self-hosters need only one flag. Drainer misconfiguration (shutdown-timeout reconciliation against `GRACEFUL_SHUTDOWN_TIMEOUT`, or `TRIGGER_MOLLIFIER_ENABLED=1` with no buffer Redis) now throws `MollifierConfigurationError` at boot and crashes the process, so the misconfig surfaces to the orchestrator instead of disappearing into a log line; transient init failures (Redis blip) are still logged-and-swallowed. Emits the `mollifier.decisions` OTel counter for per-env rate visibility.
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
area: webapp
|
||||
type: improvement
|
||||
---
|
||||
|
||||
Add per-worker Node.js heap metrics to the OTel meter — `nodejs.memory.heap.used`, `nodejs.memory.heap.total`, `nodejs.memory.heap.limit`, `nodejs.memory.external`, `nodejs.memory.array_buffers`, `nodejs.memory.rss`. Host-metrics only publishes RSS, which overstates V8 heap by the external + native footprint; these give direct heap visibility per cluster worker so `NODE_MAX_OLD_SPACE_SIZE` can be sized against observed heap peaks rather than RSS.
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
area: webapp
|
||||
type: fix
|
||||
---
|
||||
|
||||
Recover from ClickHouse `JSONEachRow` parse failures caused by lone
|
||||
UTF-16 surrogates in OTel attribute strings (`Cannot parse JSON object
|
||||
here ... ParallelParsingBlockInputFormat`).
|
||||
|
||||
`ClickhouseEventRepository.#flushBatch` and `#flushLlmMetricsBatch` now
|
||||
retry once after sanitizing every row in the batch: any string value
|
||||
containing a lone surrogate is replaced with `"[invalid-utf16]"`. If
|
||||
the sanitizer touched no fields (the parse error isn't a surrogate
|
||||
issue) or the retry still fails, the batch is dropped without further
|
||||
ClickHouse round-trips, `permanentlyDroppedBatches` increments, and an
|
||||
error log with a 1KB sample row is emitted. Non-parse errors propagate
|
||||
unchanged.
|
||||
|
||||
Detection reuses `detectBadJsonStrings` via `JSON.stringify(value)`,
|
||||
with a latent regex bug fixed: the low-surrogate hex nibble matched
|
||||
`[cd]` instead of `[c-f]`, missing the U+DE00–U+DFFF half of the range
|
||||
and false-flagging common emoji pairs. Healthy batches pay zero scan
|
||||
cost — the check only runs when ClickHouse has already rejected.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
area: webapp
|
||||
type: improvement
|
||||
---
|
||||
|
||||
Webapp now supports a plugin system. Initially consolidates authentication and authorization paths.
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
area: webapp
|
||||
type: improvement
|
||||
---
|
||||
|
||||
Tag Prisma spans with `db.datasource: "writer" | "replica"` so monitors and trace queries can distinguish the writer pool from the replica pool. Applies to all `prisma:engine:*` spans (including `prisma:engine:connection` used by the connection-pool monitors) and the outer `prisma:client:operation` span.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
area: webapp
|
||||
type: feature
|
||||
---
|
||||
|
||||
New Prompts page in the dashboard: list view with per-prompt usage sparklines, detail view with the template alongside Generations / Metrics / Versions tabs, and a dashboard override UI for changing the template text or model without redeploying.
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
area: webapp
|
||||
type: improvement
|
||||
---
|
||||
|
||||
Add `RUN_ENGINE_READ_REPLICA_SNAPSHOTS_SINCE_ENABLED` flag (default off) to route the Prisma reads inside `RunEngine.getSnapshotsSince` through the read-only replica client. Offloads the snapshot polling queries (fired by every running task runner) from the primary. When disabled, behavior is unchanged.
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
area: webapp
|
||||
type: fix
|
||||
---
|
||||
|
||||
Fix Redis connection leak in realtime streams and broken abort signal propagation.
|
||||
|
||||
**Redis connections**: Non-blocking methods (ingestData, appendPart, getLastChunkIndex) now share a single Redis connection instead of creating one per request. streamResponse still uses dedicated connections (required for XREAD BLOCK) but now tears them down immediately via disconnect() instead of graceful quit(), with a 15s inactivity fallback.
|
||||
|
||||
**Abort signal**: request.signal is broken in Remix/Express due to a Node.js undici GC bug (nodejs/node#55428) that severs the signal chain when Remix clones the Request internally. Added getRequestAbortSignal() wired to Express res.on("close") via httpAsyncStorage, which fires reliably on client disconnect. All SSE/streaming routes updated to use it.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
area: webapp
|
||||
type: fix
|
||||
---
|
||||
|
||||
Dedupe the `realtimeStreams` array push on `PUT /realtime/v1/streams/:runId/:target/:streamId` so repeat stream-init calls for the same `(run, streamId)` skip the row UPDATE, mirroring the existing append handler.
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
area: webapp
|
||||
type: feature
|
||||
---
|
||||
|
||||
Regenerating a RuntimeEnvironment API key no longer invalidates the previous key immediately. The old key is recorded in a new `RevokedApiKey` table with a 24 hour grace window, and `findEnvironmentByApiKey` falls back to it when the submitted key doesn't match any live environment. The grace window can be ended early (or extended) by updating `expiresAt` on the row.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
area: webapp
|
||||
type: feature
|
||||
---
|
||||
|
||||
Run detail page gains an Agent view alongside the Trace view, rendering the agent's `UIMessage` conversation in real time from the backing Session for any run whose `taskKind` is `AGENT`.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
area: webapp
|
||||
type: feature
|
||||
---
|
||||
|
||||
Task source filter on the Runs list — slice runs by Standard, Scheduled, or Agent so agent runs can be separated from mixed workloads at a glance.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
area: webapp
|
||||
type: fix
|
||||
---
|
||||
|
||||
Include the S2 access-token scope fingerprint in its cache key so a scope change in code (e.g. adding a new op) auto-invalidates pre-deploy cached tokens instead of returning stale ones for up to 24h.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
area: webapp
|
||||
type: fix
|
||||
---
|
||||
|
||||
Expand API error response sanitization to additional loaders and actions so internal exception messages (Prisma errors, etc.) no longer leak to callers via 5xx response bodies.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
area: webapp
|
||||
type: feature
|
||||
---
|
||||
|
||||
New Sessions page in the dashboard for inspecting `chat.agent` Session rows alongside their underlying runs, with filters by status, type, task identifier, and period, and a detail view that streams the live conversation from the backing Session's `.out` and `.in` channels.
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
area: webapp
|
||||
type: fix
|
||||
---
|
||||
|
||||
Prevent dashboard crash (React error #31) when span accessory item text is not a string. Filters out malformed accessory items in SpanCodePathAccessory instead of passing objects to React as children.
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
area: webapp
|
||||
type: improvement
|
||||
---
|
||||
|
||||
Stop creating TaskRunTag records and _TaskRunToTaskRunTag join table entries during task triggering. The denormalized runTags string array on TaskRun already stores tag names, making the M2M relation redundant write overhead.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
area: webapp
|
||||
type: improvement
|
||||
---
|
||||
|
||||
Upgrade streamdown from v1.4.0 to v2.5.0. Custom Shiki syntax highlighting theme matching our CodeMirror dark theme colors. Consolidate duplicated lazy StreamdownRenderer into a shared component.
|
||||
@@ -1,8 +0,0 @@
|
||||
---
|
||||
area: supervisor
|
||||
type: feature
|
||||
---
|
||||
|
||||
Add `KUBERNETES_POD_DNS_NDOTS_OVERRIDE_ENABLED` flag (off by default) that overrides the cluster default and sets `dnsConfig.options.ndots` on runner pods (defaulting to 2, configurable via `KUBERNETES_POD_DNS_NDOTS`). Kubernetes defaults pods to `ndots: 5`, so any name with fewer than 5 dots — including typical external domains like `api.example.com` — is first walked through every entry in the cluster search list (`<ns>.svc.cluster.local`, `svc.cluster.local`, `cluster.local`) before being tried as-is, turning one resolution into 4+ CoreDNS queries (×2 with A+AAAA). Using a lower `ndots` value reduces DNS query amplification in the `cluster.local` zone.
|
||||
|
||||
Note: before enabling, make sure no code path relies on search-list expansion for names with dots ≥ the configured value — those names will hit their as-is form first and could resolve externally before falling back to the cluster search path.
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
area: webapp
|
||||
type: improvement
|
||||
---
|
||||
|
||||
Replace the expensive DISTINCT query for task filter dropdowns with a dedicated TaskIdentifier registry table backed by Redis. Environments migrate automatically on their next deploy, with a transparent fallback to the legacy query for unmigrated environments. Also fixes duplicate dropdown entries when a task changes trigger source, and adds active/archived grouping for removed tasks. Moves BackgroundWorkerTask reads in the trigger hot path to the read replica.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
area: webapp
|
||||
type: improvement
|
||||
---
|
||||
|
||||
Cache task defaults in Redis so the trigger API skips per-request database lookups, restoring the fast trigger path when callers pass queue and TTL options.
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
area: webapp
|
||||
type: fix
|
||||
---
|
||||
|
||||
Upgrade Remix packages from 2.1.0 to 2.17.4 to address security vulnerabilities in React Router
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
area: webapp
|
||||
type: feature
|
||||
---
|
||||
|
||||
Show the currently pinned `TRIGGER_VERSION` under the Atomic deployments toggle on the Vercel
|
||||
integration settings, and prompt the user to clear it from Vercel production when they disable
|
||||
atomic deployments. Also mark `TRIGGER_SECRET_KEY` writes to Vercel as `sensitive` so the value
|
||||
cannot be read back from the Vercel dashboard or API once written.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user