Skip to content

Commit 85a28b1

Browse files
committed
fix(sdk): resolve ReferenceError and projectRef in envvars.update (#4264)
1 parent d8c3530 commit 85a28b1

3 files changed

Lines changed: 233 additions & 7 deletions

File tree

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+
Fix `ReferenceError: name is not defined` when calling `envvars.update()` outside of a task context, and fix incorrect project reference resolution when specifying an explicit project and slug.
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
import { createServer, type IncomingMessage, type Server, type ServerResponse } from "node:http";
2+
import type { AddressInfo } from "node:net";
3+
import { apiClientManager, taskContext } from "@trigger.dev/core/v3";
4+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
5+
import * as envvars from "./envvars.js";
6+
7+
type ReceivedRequest = {
8+
method: string;
9+
url: string;
10+
authorization?: string;
11+
body: unknown;
12+
};
13+
14+
describe("envvars.update", () => {
15+
let server: Server;
16+
let baseUrl: string;
17+
let requests: ReceivedRequest[];
18+
19+
beforeEach(async () => {
20+
requests = [];
21+
server = createServer((request, response) => {
22+
void handleRequest(request, response, requests);
23+
});
24+
await new Promise<void>((resolve) => {
25+
server.listen(0, "127.0.0.1", () => {
26+
const address = server.address() as AddressInfo;
27+
baseUrl = `http://127.0.0.1:${address.port}`;
28+
resolve();
29+
});
30+
});
31+
taskContext.disable();
32+
});
33+
34+
afterEach(async () => {
35+
taskContext.disable();
36+
await new Promise<void>((resolve) => server.close(() => resolve()));
37+
});
38+
39+
describe("outside task context", () => {
40+
it("updates an environment variable successfully with 4 arguments", async () => {
41+
const key = "tr_prod_sk_0123456789abcdefghijklmn";
42+
const result = await apiClientManager.runWithConfig(
43+
{ baseURL: baseUrl, accessToken: key },
44+
() => envvars.update("proj_123", "prod", "MY_VAR", { value: "new_value" })
45+
);
46+
47+
expect(result).toEqual({ success: true });
48+
expect(requests).toEqual([
49+
{
50+
method: "PUT",
51+
url: "/api/v1/projects/proj_123/envvars/prod/MY_VAR",
52+
authorization: `Bearer ${key}`,
53+
body: { value: "new_value" },
54+
},
55+
]);
56+
});
57+
58+
it("throws when slug is missing", () => {
59+
const key = "tr_prod_sk_0123456789abcdefghijklmn";
60+
expect(() =>
61+
apiClientManager.runWithConfig({ baseURL: baseUrl, accessToken: key }, () =>
62+
// @ts-expect-error test runtime validation
63+
envvars.update("MY_VAR", { value: "new_value" })
64+
)
65+
).toThrow("slug is required");
66+
});
67+
68+
it("throws when name is missing", () => {
69+
const key = "tr_prod_sk_0123456789abcdefghijklmn";
70+
expect(() =>
71+
apiClientManager.runWithConfig({ baseURL: baseUrl, accessToken: key }, () =>
72+
// @ts-expect-error test runtime validation
73+
envvars.update("proj_123", "prod", undefined, { value: "new_value" })
74+
)
75+
).toThrow("name is required");
76+
});
77+
78+
it("throws when params is missing", () => {
79+
const key = "tr_prod_sk_0123456789abcdefghijklmn";
80+
expect(() =>
81+
apiClientManager.runWithConfig({ baseURL: baseUrl, accessToken: key }, () =>
82+
// @ts-expect-error test runtime validation
83+
envvars.update("proj_123", "prod", "MY_VAR", undefined)
84+
)
85+
).toThrow("params is required");
86+
});
87+
});
88+
89+
describe("inside task context", () => {
90+
it("updates an environment variable with 2 arguments using context", async () => {
91+
const key = "tr_prod_sk_0123456789abcdefghijklmn";
92+
taskContext.setGlobalTaskContext({
93+
ctx: {
94+
project: { id: "proj_ctx_id", ref: "proj_ctx_ref", name: "ctx_proj" },
95+
environment: { id: "env_ctx_id", slug: "dev", name: "Development", type: "development" },
96+
organization: { id: "org_ctx_id", slug: "ctx_org", name: "Ctx Org" },
97+
run: { id: "run_ctx_id", isTest: false },
98+
task: { id: "task_ctx_id", filePath: "task.ts", exportName: "myTask" },
99+
},
100+
} as any);
101+
102+
const result = await apiClientManager.runWithConfig(
103+
{ baseURL: baseUrl, accessToken: key },
104+
() => envvars.update("CTX_VAR", { value: "ctx_val" })
105+
);
106+
107+
expect(result).toEqual({ success: true });
108+
expect(requests).toEqual([
109+
{
110+
method: "PUT",
111+
url: "/api/v1/projects/proj_ctx_ref/envvars/dev/CTX_VAR",
112+
authorization: `Bearer ${key}`,
113+
body: { value: "ctx_val" },
114+
},
115+
]);
116+
});
117+
118+
it("updates an environment variable with explicit projectRef, slug, and name", async () => {
119+
const key = "tr_prod_sk_0123456789abcdefghijklmn";
120+
taskContext.setGlobalTaskContext({
121+
ctx: {
122+
project: { id: "proj_ctx_id", ref: "proj_ctx_ref", name: "ctx_proj" },
123+
environment: { id: "env_ctx_id", slug: "dev", name: "Development", type: "development" },
124+
organization: { id: "org_ctx_id", slug: "ctx_org", name: "Ctx Org" },
125+
run: { id: "run_ctx_id", isTest: false },
126+
task: { id: "task_ctx_id", filePath: "task.ts", exportName: "myTask" },
127+
},
128+
} as any);
129+
130+
const result = await apiClientManager.runWithConfig(
131+
{ baseURL: baseUrl, accessToken: key },
132+
() => envvars.update("other_proj", "staging", "OTHER_VAR", { value: "other_val" })
133+
);
134+
135+
expect(result).toEqual({ success: true });
136+
expect(requests).toEqual([
137+
{
138+
method: "PUT",
139+
url: "/api/v1/projects/other_proj/envvars/staging/OTHER_VAR",
140+
authorization: `Bearer ${key}`,
141+
body: { value: "other_val" },
142+
},
143+
]);
144+
});
145+
146+
it("throws inside task context when 4-arg form is missing name", () => {
147+
const key = "tr_prod_sk_0123456789abcdefghijklmn";
148+
taskContext.setGlobalTaskContext({
149+
ctx: {
150+
project: { id: "proj_ctx_id", ref: "proj_ctx_ref", name: "ctx_proj" },
151+
environment: { id: "env_ctx_id", slug: "dev", name: "Development", type: "development" },
152+
organization: { id: "org_ctx_id", slug: "ctx_org", name: "Ctx Org" },
153+
run: { id: "run_ctx_id", isTest: false },
154+
task: { id: "task_ctx_id", filePath: "task.ts", exportName: "myTask" },
155+
},
156+
} as any);
157+
158+
expect(() =>
159+
apiClientManager.runWithConfig({ baseURL: baseUrl, accessToken: key }, () =>
160+
// @ts-expect-error test runtime validation
161+
envvars.update("other_proj", "staging", undefined, { value: "other_val" })
162+
)
163+
).toThrow("name is required");
164+
});
165+
166+
it("throws inside task context when 4-arg form is missing params", () => {
167+
const key = "tr_prod_sk_0123456789abcdefghijklmn";
168+
taskContext.setGlobalTaskContext({
169+
ctx: {
170+
project: { id: "proj_ctx_id", ref: "proj_ctx_ref", name: "ctx_proj" },
171+
environment: { id: "env_ctx_id", slug: "dev", name: "Development", type: "development" },
172+
organization: { id: "org_ctx_id", slug: "ctx_org", name: "Ctx Org" },
173+
run: { id: "run_ctx_id", isTest: false },
174+
task: { id: "task_ctx_id", filePath: "task.ts", exportName: "myTask" },
175+
},
176+
} as any);
177+
178+
expect(() =>
179+
apiClientManager.runWithConfig({ baseURL: baseUrl, accessToken: key }, () =>
180+
// @ts-expect-error test runtime validation
181+
envvars.update("other_proj", "staging", "OTHER_VAR", undefined)
182+
)
183+
).toThrow("params is required");
184+
});
185+
});
186+
});
187+
188+
async function handleRequest(
189+
request: IncomingMessage,
190+
response: ServerResponse,
191+
requests: ReceivedRequest[]
192+
) {
193+
const chunks: Buffer[] = [];
194+
for await (const chunk of request) {
195+
chunks.push(Buffer.from(chunk));
196+
}
197+
const rawBody = Buffer.concat(chunks).toString();
198+
requests.push({
199+
method: request.method ?? "",
200+
url: request.url ?? "",
201+
authorization: request.headers.authorization,
202+
body: rawBody ? JSON.parse(rawBody) : undefined,
203+
});
204+
205+
if (request.method === "PUT" && request.url?.startsWith("/api/v1/projects/")) {
206+
return json(response, { success: true });
207+
}
208+
209+
return json(response, { error: "Not found" }, 404);
210+
}
211+
212+
function json(response: ServerResponse, body: unknown, status = 200) {
213+
response.writeHead(status, { "content-type": "application/json" });
214+
response.end(JSON.stringify(body));
215+
}

‎packages/trigger-sdk/src/v3/envvars.ts‎

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -305,12 +305,14 @@ export function update(
305305

306306
if (taskContext.ctx) {
307307
if (typeof slugOrParams === "string") {
308-
$projectRef = slugOrParams;
309-
$slug = slugOrParams ?? taskContext.ctx.environment.slug;
310-
$name =
311-
typeof nameOrRequestOptions === "string"
312-
? nameOrRequestOptions
313-
: taskContext.ctx.environment.slug;
308+
$projectRef = projectRefOrName;
309+
$slug = slugOrParams;
310+
311+
if (typeof nameOrRequestOptions !== "string") {
312+
throw new Error("name is required");
313+
}
314+
315+
$name = nameOrRequestOptions;
314316

315317
if (!params) {
316318
throw new Error("params is required");
@@ -332,13 +334,17 @@ export function update(
332334
throw new Error("projectRef is required");
333335
}
334336

337+
if (typeof nameOrRequestOptions !== "string") {
338+
throw new Error("name is required");
339+
}
340+
335341
if (!params) {
336342
throw new Error("params is required");
337343
}
338344

339345
$projectRef = projectRefOrName;
340346
$slug = slugOrParams;
341-
$name = name!;
347+
$name = nameOrRequestOptions;
342348
$params = params;
343349
}
344350

0 commit comments

Comments
 (0)