Skip to content

Commit 2568391

Browse files
committed
Merge remote-tracking branch 'origin/main' into pr/2192
2 parents 5aec497 + 8a86e7b commit 2568391

58 files changed

Lines changed: 4264 additions & 63 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.changeset/chilled-weeks-switch.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"trigger.dev": patch
3+
---
4+
5+
Fix update command version mismatch detection

.changeset/red-rings-marry.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@trigger.dev/sdk": patch
3+
---
4+
5+
Provide realtime skipColumns option via untamperable public access tokens

.github/workflows/release-helm.yml

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
name: 🧭 Helm Chart Release
2+
3+
on:
4+
push:
5+
tags:
6+
- 'helm-v*'
7+
workflow_dispatch:
8+
inputs:
9+
chart_version:
10+
description: 'Chart version to release'
11+
required: true
12+
type: string
13+
14+
env:
15+
REGISTRY: ghcr.io
16+
CHART_NAME: trigger
17+
18+
jobs:
19+
lint-and-test:
20+
runs-on: ubuntu-latest
21+
permissions:
22+
contents: read
23+
steps:
24+
- name: Checkout
25+
uses: actions/checkout@v4
26+
27+
- name: Set up Helm
28+
uses: azure/setup-helm@v4
29+
with:
30+
version: "3.18.3"
31+
32+
- name: Lint Helm Chart
33+
run: |
34+
helm lint ./hosting/k8s/helm/
35+
36+
- name: Render templates
37+
run: |
38+
helm template test-release ./hosting/k8s/helm/ \
39+
--values ./hosting/k8s/helm/values.yaml \
40+
--output-dir ./helm-output
41+
42+
- name: Validate manifests
43+
uses: docker://ghcr.io/yannh/kubeconform:v0.7.0
44+
with:
45+
entrypoint: '/kubeconform'
46+
args: "-summary -output json ./helm-output"
47+
48+
release:
49+
needs: lint-and-test
50+
runs-on: ubuntu-latest
51+
permissions:
52+
contents: write # for gh-release
53+
packages: write
54+
steps:
55+
- name: Checkout
56+
uses: actions/checkout@v4
57+
58+
- name: Set up Helm
59+
uses: azure/setup-helm@v4
60+
with:
61+
version: "3.18.3"
62+
63+
- name: Log in to Container Registry
64+
uses: docker/login-action@v3
65+
with:
66+
registry: ${{ env.REGISTRY }}
67+
username: ${{ github.actor }}
68+
password: ${{ secrets.GITHUB_TOKEN }}
69+
70+
- name: Extract version from tag or input
71+
id: version
72+
run: |
73+
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
74+
VERSION="${{ github.event.inputs.chart_version }}"
75+
else
76+
VERSION="${{ github.ref_name }}"
77+
VERSION="${VERSION#helm-v}"
78+
fi
79+
echo "version=$VERSION" >> $GITHUB_OUTPUT
80+
echo "Releasing version: $VERSION"
81+
82+
- name: Check Chart.yaml version matches release version
83+
run: |
84+
VERSION="${{ steps.version.outputs.version }}"
85+
CHART_VERSION=$(grep '^version:' ./hosting/k8s/helm/Chart.yaml | awk '{print $2}')
86+
echo "Chart.yaml version: $CHART_VERSION"
87+
echo "Release version: $VERSION"
88+
if [ "$CHART_VERSION" != "$VERSION" ]; then
89+
echo "❌ Chart.yaml version does not match release version!"
90+
exit 1
91+
fi
92+
echo "✅ Chart.yaml version matches release version."
93+
94+
- name: Package Helm Chart
95+
run: |
96+
helm package ./hosting/k8s/helm/ --destination /tmp/
97+
98+
- name: Push Helm Chart to GHCR
99+
run: |
100+
VERSION="${{ steps.version.outputs.version }}"
101+
CHART_PACKAGE="/tmp/${{ env.CHART_NAME }}-${VERSION}.tgz"
102+
103+
# Push to GHCR OCI registry
104+
helm push "$CHART_PACKAGE" "oci://${{ env.REGISTRY }}/${{ github.repository_owner }}/charts"
105+
106+
- name: Create GitHub Release
107+
id: release
108+
uses: softprops/action-gh-release@v1
109+
if: github.event_name == 'push'
110+
with:
111+
tag_name: ${{ github.ref_name }}
112+
name: "Helm Chart ${{ steps.version.outputs.version }}"
113+
body: |
114+
### Installation
115+
```bash
116+
helm upgrade --install trigger \
117+
oci://${{ env.REGISTRY }}/${{ github.repository_owner }}/charts/${{ env.CHART_NAME }} \
118+
--version ${{ steps.version.outputs.version }}
119+
```
120+
121+
### Changes
122+
See commit history for detailed changes in this release.
123+
files: |
124+
/tmp/${{ env.CHART_NAME }}-${{ steps.version.outputs.version }}.tgz
125+
token: ${{ secrets.GITHUB_TOKEN }}
126+
draft: true
127+
prerelease: true

apps/webapp/app/env.server.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -831,6 +831,9 @@ const EnvironmentSchema = z.object({
831831
RUN_REPLICATION_LEADER_LOCK_EXTEND_INTERVAL_MS: z.coerce.number().int().default(10_000),
832832
RUN_REPLICATION_ACK_INTERVAL_SECONDS: z.coerce.number().int().default(10),
833833
RUN_REPLICATION_LOG_LEVEL: z.enum(["log", "error", "warn", "info", "debug"]).default("info"),
834+
RUN_REPLICATION_CLICKHOUSE_LOG_LEVEL: z
835+
.enum(["log", "error", "warn", "info", "debug"])
836+
.default("info"),
834837
RUN_REPLICATION_LEADER_LOCK_ADDITIONAL_TIME_MS: z.coerce.number().int().default(10_000),
835838
RUN_REPLICATION_LEADER_LOCK_RETRY_INTERVAL_MS: z.coerce.number().int().default(500),
836839
RUN_REPLICATION_WAIT_FOR_ASYNC_INSERT: z.string().default("0"),

apps/webapp/app/routes/api.v1.tasks.$taskId.trigger.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ import { TaskRun } from "@trigger.dev/database";
88
import { z } from "zod";
99
import { env } from "~/env.server";
1010
import { EngineServiceValidationError } from "~/runEngine/concerns/errors";
11-
import { AuthenticatedEnvironment, getOneTimeUseToken } from "~/services/apiAuth.server";
11+
import {
12+
ApiAuthenticationResultSuccess,
13+
AuthenticatedEnvironment,
14+
getOneTimeUseToken,
15+
} from "~/services/apiAuth.server";
1216
import { logger } from "~/services/logger.server";
1317
import { createActionApiRoute } from "~/services/routeBuilders/apiBuilder.server";
1418
import { resolveIdempotencyKeyTTL } from "~/utils/idempotencyKeys.server";
@@ -100,11 +104,7 @@ const { action, loader } = createActionApiRoute(
100104
return json({ error: "Task not found" }, { status: 404 });
101105
}
102106

103-
const $responseHeaders = await responseHeaders(
104-
result.run,
105-
authentication.environment,
106-
triggerClient
107-
);
107+
const $responseHeaders = await responseHeaders(result.run, authentication, triggerClient);
108108

109109
return json(
110110
{
@@ -133,19 +133,23 @@ const { action, loader } = createActionApiRoute(
133133

134134
async function responseHeaders(
135135
run: TaskRun,
136-
environment: AuthenticatedEnvironment,
136+
authentication: ApiAuthenticationResultSuccess,
137137
triggerClient?: string | null
138138
): Promise<Record<string, string>> {
139+
const { environment, realtime } = authentication;
140+
139141
const claimsHeader = JSON.stringify({
140142
sub: environment.id,
141143
pub: true,
144+
realtime,
142145
});
143146

144147
if (triggerClient === "browser") {
145148
const claims = {
146149
sub: environment.id,
147150
pub: true,
148151
scopes: [`read:runs:${run.friendlyId}`],
152+
realtime,
149153
};
150154

151155
const jwt = await internal_generateJWT({

apps/webapp/app/routes/realtime.v1.batches.$batchId.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export const loader = createLoaderApiRoute(
3131
request.url,
3232
authentication.environment,
3333
batchRun.id,
34+
authentication.realtime,
3435
request.headers.get("x-trigger-electric-version") ?? undefined
3536
);
3637
}

apps/webapp/app/routes/realtime.v1.runs.$runId.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export const loader = createLoaderApiRoute(
4444
request.url,
4545
authentication.environment,
4646
run.id,
47+
authentication.realtime,
4748
request.headers.get("x-trigger-electric-version") ?? undefined
4849
);
4950
}

apps/webapp/app/routes/realtime.v1.runs.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export const loader = createLoaderApiRoute(
2929
request.url,
3030
authentication.environment,
3131
searchParams,
32+
authentication.realtime,
3233
request.headers.get("x-trigger-electric-version") ?? undefined
3334
);
3435
}

apps/webapp/app/services/apiAuth.server.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ const ClaimsSchema = z.object({
2323
scopes: z.array(z.string()).optional(),
2424
// One-time use token
2525
otu: z.boolean().optional(),
26+
realtime: z
27+
.object({
28+
skipColumns: z.array(z.string()).optional(),
29+
})
30+
.optional(),
2631
});
2732

2833
type Optional<T, K extends keyof T> = Prettify<Omit<T, K> & Partial<Pick<T, K>>>;
@@ -43,6 +48,9 @@ export type ApiAuthenticationResultSuccess = {
4348
environment: AuthenticatedEnvironment;
4449
scopes?: string[];
4550
oneTimeUse?: boolean;
51+
realtime?: {
52+
skipColumns?: string[];
53+
};
4654
};
4755

4856
export type ApiAuthenticationResultFailure = {
@@ -151,6 +159,7 @@ export async function authenticateApiKey(
151159
environment: validationResults.environment,
152160
scopes: parsedClaims.success ? parsedClaims.data.scopes : [],
153161
oneTimeUse: parsedClaims.success ? parsedClaims.data.otu : false,
162+
realtime: parsedClaims.success ? parsedClaims.data.realtime : undefined,
154163
};
155164
}
156165
}
@@ -233,6 +242,7 @@ async function authenticateApiKeyWithFailure(
233242
environment: validationResults.environment,
234243
scopes: parsedClaims.success ? parsedClaims.data.scopes : [],
235244
oneTimeUse: parsedClaims.success ? parsedClaims.data.otu : false,
245+
realtime: parsedClaims.success ? parsedClaims.data.realtime : undefined,
236246
};
237247
}
238248
}

apps/webapp/app/services/realtimeClient.server.ts

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ export type RealtimeRunsParams = {
6565
createdAt?: string;
6666
};
6767

68+
export type RealtimeRequestOptions = {
69+
skipColumns?: string[];
70+
};
71+
6872
export class RealtimeClient {
6973
private redis: RedisClient;
7074
private expiryTimeInSeconds: number;
@@ -124,15 +128,17 @@ export class RealtimeClient {
124128
url: URL | string,
125129
environment: RealtimeEnvironment,
126130
runId: string,
131+
requestOptions?: RealtimeRequestOptions,
127132
clientVersion?: string
128133
) {
129-
return this.#streamRunsWhere(url, environment, `id='${runId}'`, clientVersion);
134+
return this.#streamRunsWhere(url, environment, `id='${runId}'`, requestOptions, clientVersion);
130135
}
131136

132137
async streamBatch(
133138
url: URL | string,
134139
environment: RealtimeEnvironment,
135140
batchId: string,
141+
requestOptions?: RealtimeRequestOptions,
136142
clientVersion?: string
137143
) {
138144
const whereClauses: string[] = [
@@ -142,13 +148,14 @@ export class RealtimeClient {
142148

143149
const whereClause = whereClauses.join(" AND ");
144150

145-
return this.#streamRunsWhere(url, environment, whereClause, clientVersion);
151+
return this.#streamRunsWhere(url, environment, whereClause, requestOptions, clientVersion);
146152
}
147153

148154
async streamRuns(
149155
url: URL | string,
150156
environment: RealtimeEnvironment,
151157
params: RealtimeRunsParams,
158+
requestOptions?: RealtimeRequestOptions,
152159
clientVersion?: string
153160
) {
154161
const whereClauses: string[] = [`"runtimeEnvironmentId"='${environment.id}'`];
@@ -165,7 +172,13 @@ export class RealtimeClient {
165172

166173
const whereClause = whereClauses.join(" AND ");
167174

168-
const response = await this.#streamRunsWhere(url, environment, whereClause, clientVersion);
175+
const response = await this.#streamRunsWhere(
176+
url,
177+
environment,
178+
whereClause,
179+
requestOptions,
180+
clientVersion
181+
);
169182

170183
if (createdAtFilter) {
171184
const [setCreatedAtFilterError] = await tryCatch(
@@ -256,12 +269,14 @@ export class RealtimeClient {
256269
url: URL | string,
257270
environment: RealtimeEnvironment,
258271
whereClause: string,
272+
requestOptions?: RealtimeRequestOptions,
259273
clientVersion?: string
260274
) {
261275
const electricUrl = this.#constructRunsElectricUrl(
262276
url,
263277
environment,
264278
whereClause,
279+
requestOptions,
265280
clientVersion
266281
);
267282

@@ -272,6 +287,7 @@ export class RealtimeClient {
272287
url: URL | string,
273288
environment: RealtimeEnvironment,
274289
whereClause: string,
290+
requestOptions?: RealtimeRequestOptions,
275291
clientVersion?: string
276292
): URL {
277293
const $url = new URL(url.toString());
@@ -297,13 +313,10 @@ export class RealtimeClient {
297313
electricUrl.searchParams.set("handle", electricUrl.searchParams.get("shape_id") ?? "");
298314
}
299315

300-
const skipColumnsRaw = $url.searchParams.get("skipColumns");
316+
let skipColumns = getSkipColumns($url.searchParams, requestOptions);
301317

302-
if (skipColumnsRaw) {
303-
const skipColumns = skipColumnsRaw
304-
.split(",")
305-
.map((c) => c.trim())
306-
.filter((c) => c !== "" && !RESERVED_COLUMNS.includes(c));
318+
if (skipColumns.length > 0) {
319+
skipColumns = skipColumns.filter((c) => c !== "" && !RESERVED_COLUMNS.includes(c));
307320

308321
electricUrl.searchParams.set(
309322
"columns",
@@ -543,3 +556,17 @@ declare module "ioredis" {
543556
): Result<number, Context>;
544557
}
545558
}
559+
560+
function getSkipColumns(searchParams: URLSearchParams, requestOptions?: RealtimeRequestOptions) {
561+
if (requestOptions?.skipColumns) {
562+
return requestOptions.skipColumns;
563+
}
564+
565+
const skipColumnsRaw = searchParams.get("skipColumns");
566+
567+
if (skipColumnsRaw) {
568+
return skipColumnsRaw.split(",").map((c) => c.trim());
569+
}
570+
571+
return [];
572+
}

0 commit comments

Comments
 (0)