v3: env var management API (#1116)

* WIP env var management API

* Add import env var API endpoint

* Adding docs and support for using both API keys and PATs when interacting with the env var endpoints

* WIP envvar SDK

* Uploading env vars in a variety of formats now works

* Finish env var endpoints and add resolveEnvVars hook

* Add changeset
This commit is contained in:
Eric Allam
2024-05-23 16:11:25 +01:00
committed by GitHub
parent 1f462eaa2f
commit 3a1b0c486a
44 changed files with 3271 additions and 1090 deletions
+7
View File
@@ -0,0 +1,7 @@
---
"@trigger.dev/sdk": patch
"trigger.dev": patch
"@trigger.dev/core": patch
---
v3: Environment variable management API and SDK, along with resolveEnvVars CLI hook
+8
View File
@@ -45,6 +45,14 @@
"cwd": "${workspaceFolder}/references/v3-catalog",
"sourceMaps": true
},
{
"type": "node-terminal",
"request": "launch",
"name": "Debug V3 Management",
"command": "pnpm run management",
"cwd": "${workspaceFolder}/references/v3-catalog",
"sourceMaps": true
},
{
"type": "node",
"request": "attach",
@@ -87,7 +87,7 @@ export class EnvironmentVariablesPresenter {
);
const repository = new EnvironmentVariablesRepository(this.#prismaClient);
const variables = await repository.getProject(project.id, userId);
const variables = await repository.getProject(project.id);
return {
environmentVariables: environmentVariables.map((environmentVariable) => {
@@ -70,7 +70,7 @@ const Variable = z.object({
type Variable = z.infer<typeof Variable>;
const schema = z.object({
overwrite: z.preprocess((i) => {
override: z.preprocess((i) => {
if (i === "true") return true;
if (i === "false") return false;
return;
@@ -115,6 +115,13 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
const project = await prisma.project.findUnique({
where: {
slug: params.projectParam,
organization: {
members: {
some: {
userId,
},
},
},
},
select: {
id: true,
@@ -126,7 +133,7 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
}
const repository = new EnvironmentVariablesRepository(prisma);
const result = await repository.create(project.id, userId, submission.value);
const result = await repository.create(project.id, submission.value);
if (!result.success) {
if (result.variableErrors) {
@@ -249,7 +256,7 @@ export default function Page() {
type="submit"
variant="primary/small"
disabled={isLoading}
name="overwrite"
name="override"
value="false"
>
{isLoading ? "Saving" : "Save"}
@@ -257,10 +264,10 @@ export default function Page() {
<Button
variant="secondary/small"
disabled={isLoading}
name="overwrite"
name="override"
value="true"
>
{isLoading ? "Overwriting" : "Overwrite"}
{isLoading ? "Overriding" : "Override"}
</Button>
</div>
}
@@ -106,6 +106,13 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
const project = await prisma.project.findUnique({
where: {
slug: params.projectParam,
organization: {
members: {
some: {
userId,
},
},
},
},
select: {
id: true,
@@ -119,7 +126,7 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
switch (submission.value.action) {
case "edit": {
const repository = new EnvironmentVariablesRepository(prisma);
const result = await repository.edit(project.id, userId, submission.value);
const result = await repository.edit(project.id, submission.value);
if (!result.success) {
submission.error.key = result.error;
@@ -138,7 +145,7 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
}
case "delete": {
const repository = new EnvironmentVariablesRepository(prisma);
const result = await repository.delete(project.id, userId, submission.value);
const result = await repository.delete(project.id, submission.value);
if (!result.success) {
submission.error.key = result.error;
@@ -334,6 +341,7 @@ function EditEnvironmentVariablePanel({
name={`values[${index}].value`}
placeholder="Not set"
defaultValue={value}
type="password"
/>
</Fragment>
);
@@ -0,0 +1,137 @@
import { ActionFunctionArgs, LoaderFunctionArgs, json } from "@remix-run/server-runtime";
import { UpdateEnvironmentVariableRequestBody } from "@trigger.dev/core/v3";
import { z } from "zod";
import { prisma } from "~/db.server";
import {
authenticateProjectApiKeyOrPersonalAccessToken,
authenticatedEnvironmentForAuthentication,
} from "~/services/apiAuth.server";
import { EnvironmentVariablesRepository } from "~/v3/environmentVariables/environmentVariablesRepository.server";
const ParamsSchema = z.object({
projectRef: z.string(),
slug: z.string(),
name: z.string(),
});
export async function action({ params, request }: ActionFunctionArgs) {
const parsedParams = ParamsSchema.safeParse(params);
if (!parsedParams.success) {
return json({ error: "Invalid params" }, { status: 400 });
}
const authenticationResult = await authenticateProjectApiKeyOrPersonalAccessToken(request);
if (!authenticationResult) {
return json({ error: "Invalid or Missing API key" }, { status: 401 });
}
const environment = await authenticatedEnvironmentForAuthentication(
authenticationResult,
parsedParams.data.projectRef,
parsedParams.data.slug
);
// Find the environment variable
const variable = await prisma.environmentVariable.findFirst({
where: {
key: parsedParams.data.name,
projectId: environment.project.id,
},
});
if (!variable) {
return json({ error: "Environment variable not found" }, { status: 404 });
}
const repository = new EnvironmentVariablesRepository();
switch (request.method.toUpperCase()) {
case "DELETE": {
const result = await repository.deleteValue(environment.project.id, {
id: variable.id,
environmentId: environment.id,
});
if (result.success) {
return json({ success: true });
} else {
return json({ error: result.error }, { status: 400 });
}
}
case "PUT":
case "POST": {
const jsonBody = await request.json();
const body = UpdateEnvironmentVariableRequestBody.safeParse(jsonBody);
if (!body.success) {
return json({ error: "Invalid request body", issues: body.error.issues }, { status: 400 });
}
const result = await repository.edit(environment.project.id, {
values: [
{
value: body.data.value,
environmentId: environment.id,
},
],
id: variable.id,
keepEmptyValues: true,
});
if (result.success) {
return json({ success: true });
} else {
return json({ error: result.error }, { status: 400 });
}
}
}
}
export async function loader({ params, request }: LoaderFunctionArgs) {
const parsedParams = ParamsSchema.safeParse(params);
if (!parsedParams.success) {
return json({ error: "Invalid params" }, { status: 400 });
}
const authenticationResult = await authenticateProjectApiKeyOrPersonalAccessToken(request);
if (!authenticationResult) {
return json({ error: "Invalid or Missing API key" }, { status: 401 });
}
const environment = await authenticatedEnvironmentForAuthentication(
authenticationResult,
parsedParams.data.projectRef,
parsedParams.data.slug
);
// Find the environment variable
const variable = await prisma.environmentVariable.findFirst({
where: {
key: parsedParams.data.name,
projectId: environment.project.id,
},
});
if (!variable) {
return json({ error: "Environment variable not found" }, { status: 404 });
}
const repository = new EnvironmentVariablesRepository();
const variables = await repository.getEnvironment(environment.project.id, environment.id, true);
const environmentVariable = variables.find((v) => v.key === parsedParams.data.name);
if (!environmentVariable) {
return json({ error: "Environment variable not found" }, { status: 404 });
}
return json({
value: environmentVariable.value,
});
}
@@ -0,0 +1,84 @@
import { ActionFunctionArgs, json } from "@remix-run/server-runtime";
import { ImportEnvironmentVariablesRequestBody } from "@trigger.dev/core/v3";
import { parse } from "dotenv";
import { z } from "zod";
import {
authenticateProjectApiKeyOrPersonalAccessToken,
authenticatedEnvironmentForAuthentication,
} from "~/services/apiAuth.server";
import { EnvironmentVariablesRepository } from "~/v3/environmentVariables/environmentVariablesRepository.server";
const ParamsSchema = z.object({
projectRef: z.string(),
slug: z.string(),
});
export async function action({ params, request }: ActionFunctionArgs) {
const parsedParams = ParamsSchema.safeParse(params);
if (!parsedParams.success) {
return json({ error: "Invalid params" }, { status: 400 });
}
const authenticationResult = await authenticateProjectApiKeyOrPersonalAccessToken(request);
if (!authenticationResult) {
return json({ error: "Invalid or Missing API key" }, { status: 401 });
}
const environment = await authenticatedEnvironmentForAuthentication(
authenticationResult,
parsedParams.data.projectRef,
parsedParams.data.slug
);
const repository = new EnvironmentVariablesRepository();
const body = await parseImportBody(request);
const result = await repository.create(environment.project.id, {
override: typeof body.override === "boolean" ? body.override : false,
environmentIds: [environment.id],
variables: Object.entries(body.variables).map(([key, value]) => ({
key,
value,
})),
});
if (result.success) {
return json({ success: true });
} else {
return json({ error: result.error, variableErrors: result.variableErrors }, { status: 400 });
}
}
async function parseImportBody(request: Request): Promise<ImportEnvironmentVariablesRequestBody> {
const contentType = request.headers.get("content-type") ?? "application/json";
if (contentType.includes("multipart/form-data")) {
const formData = await request.formData();
const file = formData.get("variables");
const override = formData.get("override") === "true";
if (file instanceof File) {
const buffer = await file.arrayBuffer();
const variables = parse(Buffer.from(buffer));
return { variables, override };
} else {
throw json({ error: "Invalid file" }, { status: 400 });
}
} else {
const rawBody = await request.json();
const body = ImportEnvironmentVariablesRequestBody.safeParse(rawBody);
if (!body.success) {
throw json({ error: "Invalid body" }, { status: 400 });
}
return body.data;
}
}
@@ -0,0 +1,86 @@
import { ActionFunctionArgs, LoaderFunctionArgs, json } from "@remix-run/server-runtime";
import { CreateEnvironmentVariableRequestBody } from "@trigger.dev/core/v3";
import { z } from "zod";
import {
authenticateProjectApiKeyOrPersonalAccessToken,
authenticatedEnvironmentForAuthentication,
} from "~/services/apiAuth.server";
import { EnvironmentVariablesRepository } from "~/v3/environmentVariables/environmentVariablesRepository.server";
const ParamsSchema = z.object({
projectRef: z.string(),
slug: z.string(),
});
export async function action({ params, request }: ActionFunctionArgs) {
const parsedParams = ParamsSchema.safeParse(params);
if (!parsedParams.success) {
return json({ error: "Invalid params" }, { status: 400 });
}
const authenticationResult = await authenticateProjectApiKeyOrPersonalAccessToken(request);
if (!authenticationResult) {
return json({ error: "Invalid or Missing API key" }, { status: 401 });
}
const environment = await authenticatedEnvironmentForAuthentication(
authenticationResult,
parsedParams.data.projectRef,
parsedParams.data.slug
);
const jsonBody = await request.json();
const body = CreateEnvironmentVariableRequestBody.safeParse(jsonBody);
if (!body.success) {
return json({ error: "Invalid request body", issues: body.error.issues }, { status: 400 });
}
const repository = new EnvironmentVariablesRepository();
const result = await repository.create(environment.project.id, {
override: true,
environmentIds: [environment.id],
variables: [
{
key: body.data.name,
value: body.data.value,
},
],
});
if (result.success) {
return json({ success: true });
} else {
return json({ error: result.error, variableErrors: result.variableErrors }, { status: 400 });
}
}
export async function loader({ params, request }: LoaderFunctionArgs) {
const parsedParams = ParamsSchema.safeParse(params);
if (!parsedParams.success) {
return json({ error: "Invalid params" }, { status: 400 });
}
const authenticationResult = await authenticateProjectApiKeyOrPersonalAccessToken(request);
if (!authenticationResult) {
return json({ error: "Invalid or Missing API key" }, { status: 401 });
}
const environment = await authenticatedEnvironmentForAuthentication(
authenticationResult,
parsedParams.data.projectRef,
parsedParams.data.slug
);
const repository = new EnvironmentVariablesRepository();
const variables = await repository.getEnvironment(environment.project.id, environment.id, true);
return json(variables.map((variable) => ({ name: variable.key, value: variable.value })));
}
+117
View File
@@ -4,6 +4,14 @@ import {
findEnvironmentByApiKey,
findEnvironmentByPublicApiKey,
} from "~/models/runtimeEnvironment.server";
import {
PersonalAccessTokenAuthenticationResult,
authenticateApiRequestWithPersonalAccessToken,
isPersonalAccessToken,
} from "./personalAccessToken.server";
import { prisma } from "~/db.server";
import { json } from "@remix-run/server-runtime";
import { findProjectByRef } from "~/models/project.server";
type Optional<T, K extends keyof T> = Prettify<Omit<T, K> & Partial<Pick<T, K>>>;
@@ -92,3 +100,112 @@ export function getApiKeyResult(apiKey: string) {
const type = isPublicApiKey(apiKey) ? ("PUBLIC" as const) : ("PRIVATE" as const);
return { apiKey, type };
}
export type DualAuthenticationResult =
| {
type: "personalAccessToken";
result: PersonalAccessTokenAuthenticationResult;
}
| {
type: "apiKey";
result: ApiAuthenticationResult;
};
export async function authenticateProjectApiKeyOrPersonalAccessToken(
request: Request
): Promise<DualAuthenticationResult | undefined> {
const apiKey = getApiKeyFromRequest(request);
if (!apiKey) {
return;
}
if (isPersonalAccessToken(apiKey)) {
const result = await authenticateApiRequestWithPersonalAccessToken(request);
if (!result) {
return;
}
return {
type: "personalAccessToken",
result,
};
}
const result = await authenticateApiKey(apiKey, { allowPublicKey: false });
if (!result) {
return;
}
return {
type: "apiKey",
result,
};
}
export async function authenticatedEnvironmentForAuthentication(
auth: DualAuthenticationResult,
projectRef: string,
slug: string
): Promise<AuthenticatedEnvironment> {
switch (auth.type) {
case "apiKey": {
if (auth.result.environment.project.externalRef !== projectRef) {
throw json(
{
error:
"Invalid project ref for this API key. Make sure you are using an API key associated with that project.",
},
{ status: 400 }
);
}
if (auth.result.environment.slug !== slug) {
throw json(
{
error:
"Invalid environment slug for this API key. Make sure you are using an API key associated with that environment.",
},
{ status: 400 }
);
}
return auth.result.environment;
}
case "personalAccessToken": {
const user = await prisma.user.findUnique({
where: {
id: auth.result.userId,
},
});
if (!user) {
throw json({ error: "Invalid or Missing API key" }, { status: 401 });
}
const project = await findProjectByRef(projectRef, user.id);
if (!project) {
throw json({ error: "Project not found" }, { status: 404 });
}
const environment = await prisma.runtimeEnvironment.findFirst({
where: {
projectId: project.id,
slug: slug,
},
include: {
project: true,
organization: true,
},
});
if (!environment) {
throw json({ error: "Environment not found" }, { status: 404 });
}
return environment;
}
}
}
@@ -90,7 +90,7 @@ export async function revokePersonalAccessToken(tokenId: string) {
});
}
type PersonalAccessTokenAuthenticationResult = {
export type PersonalAccessTokenAuthenticationResult = {
userId: string;
};
@@ -169,6 +169,10 @@ export async function authenticatePersonalAccessToken(
};
}
export function isPersonalAccessToken(token: string) {
return token.startsWith(tokenPrefix);
}
export function createAuthorizationCode() {
return prisma.authorizationCode.create({
data: {
@@ -7,6 +7,8 @@ import { getSecretStore } from "~/services/secrets/secretStore.server";
import { generateFriendlyId } from "../friendlyIdentifiers";
import {
CreateResult,
DeleteEnvironmentVariable,
DeleteEnvironmentVariableValue,
EnvironmentVariable,
ProjectEnvironmentVariable,
Repository,
@@ -41,9 +43,8 @@ export class EnvironmentVariablesRepository implements Repository {
async create(
projectId: string,
userId: string,
options: {
overwrite: boolean;
override: boolean;
environmentIds: string[];
variables: {
key: string;
@@ -54,13 +55,6 @@ export class EnvironmentVariablesRepository implements Repository {
const project = await this.prismaClient.project.findUnique({
where: {
id: projectId,
organization: {
members: {
some: {
userId,
},
},
},
deletedAt: null,
},
select: {
@@ -92,6 +86,15 @@ export class EnvironmentVariablesRepository implements Repository {
return { success: false as const, error: `Environment not found` };
}
// Check to see if any of the variables are `TRIGGER_SECRET_KEY` or `TRIGGER_API_URL`
const triggerKeys = options.variables.map((v) => v.key);
if (triggerKeys.includes("TRIGGER_SECRET_KEY") || triggerKeys.includes("TRIGGER_API_URL")) {
return {
success: false as const,
error: `You cannot set the variables TRIGGER_SECRET_KEY or TRIGGER_API_URL as they will be set automatically`,
};
}
//get rid of empty variables
const values = options.variables.filter((v) => v.key.trim() !== "" && v.value.trim() !== "");
if (values.length === 0) {
@@ -99,7 +102,7 @@ export class EnvironmentVariablesRepository implements Repository {
}
//check if any of them exist in an environment we're setting
if (!options.overwrite) {
if (!options.override) {
const existingVariableKeys: { key: string; environments: RuntimeEnvironmentType[] }[] = [];
for (const variable of values) {
const existingVariable = project.environmentVariables.find((v) => v.key === variable.key);
@@ -119,7 +122,7 @@ export class EnvironmentVariablesRepository implements Repository {
if (existingVariableKeys.length > 0) {
return {
success: false as const,
error: `Some of the variables are already set for these environments`,
error: `Some of the variables are already set for these environments. Set override to true to override them.`,
variableErrors: existingVariableKeys.map((val) => ({
key: val.key,
error: `Variable already set in ${val.environments
@@ -217,19 +220,15 @@ export class EnvironmentVariablesRepository implements Repository {
async edit(
projectId: string,
userId: string,
options: { values: { value: string; environmentId: string }[]; id: string }
options: {
values: { value: string; environmentId: string }[];
id: string;
keepEmptyValues?: boolean;
}
): Promise<Result> {
const project = await this.prismaClient.project.findUnique({
where: {
id: projectId,
organization: {
members: {
some: {
userId,
},
},
},
deletedAt: null,
},
select: {
@@ -237,18 +236,6 @@ export class EnvironmentVariablesRepository implements Repository {
select: {
id: true,
},
where: {
OR: [
{
orgMember: null,
},
{
orgMember: {
userId,
},
},
],
},
},
},
});
@@ -266,12 +253,15 @@ export class EnvironmentVariablesRepository implements Repository {
//add in empty values for environments that don't have a value
const environmentIds = project.environments.map((e) => e.id);
for (const environmentId of environmentIds) {
if (!values.some((v) => v.environmentId === environmentId)) {
values.push({
environmentId,
value: "",
});
if (!options.keepEmptyValues) {
for (const environmentId of environmentIds) {
if (!values.some((v) => v.environmentId === environmentId)) {
values.push({
environmentId,
value: "",
});
}
}
}
@@ -364,17 +354,10 @@ export class EnvironmentVariablesRepository implements Repository {
}
}
async getProject(projectId: string, userId: string): Promise<ProjectEnvironmentVariable[]> {
async getProject(projectId: string): Promise<ProjectEnvironmentVariable[]> {
const project = await this.prismaClient.project.findUnique({
where: {
id: projectId,
organization: {
members: {
some: {
userId,
},
},
},
deletedAt: null,
},
select: {
@@ -446,19 +429,12 @@ export class EnvironmentVariablesRepository implements Repository {
async getEnvironment(
projectId: string,
userId: string,
environmentId: string
environmentId: string,
excludeInternalVariables?: boolean
): Promise<EnvironmentVariable[]> {
const project = await this.prismaClient.project.findUnique({
where: {
id: projectId,
organization: {
members: {
some: {
userId,
},
},
},
deletedAt: null,
},
select: {
@@ -477,7 +453,7 @@ export class EnvironmentVariablesRepository implements Repository {
return [];
}
return this.getEnvironmentVariables(projectId, environmentId);
return this.getEnvironmentVariables(projectId, environmentId, excludeInternalVariables);
}
async #getTriggerEnvironmentVariables(environmentId: string): Promise<EnvironmentVariable[]> {
@@ -621,25 +597,24 @@ export class EnvironmentVariablesRepository implements Repository {
async getEnvironmentVariables(
projectId: string,
environmentId: string
environmentId: string,
excludeInternalVariables?: boolean
): Promise<EnvironmentVariable[]> {
const secretEnvVars = await this.#getSecretEnvironmentVariables(projectId, environmentId);
if (excludeInternalVariables) {
return secretEnvVars;
}
const triggerEnvVars = await this.#getTriggerEnvironmentVariables(environmentId);
return [...secretEnvVars, ...triggerEnvVars];
}
async delete(projectId: string, userId: string, options: { id: string }): Promise<Result> {
async delete(projectId: string, options: DeleteEnvironmentVariable): Promise<Result> {
const project = await this.prismaClient.project.findUnique({
where: {
id: projectId,
organization: {
members: {
some: {
userId,
},
},
},
deletedAt: null,
},
select: {
@@ -647,18 +622,6 @@ export class EnvironmentVariablesRepository implements Repository {
select: {
id: true,
},
where: {
OR: [
{
orgMember: null,
},
{
orgMember: {
userId,
},
},
],
},
},
},
});
@@ -703,7 +666,7 @@ export class EnvironmentVariablesRepository implements Repository {
prismaClient: tx,
});
//create the secret values and references
//delete the secret values and references
for (const value of environmentVariable.values) {
const key = secretKey(projectId, value.environmentId, environmentVariable.key);
await secretStore.deleteSecret(key);
@@ -728,4 +691,94 @@ export class EnvironmentVariablesRepository implements Repository {
};
}
}
async deleteValue(projectId: string, options: DeleteEnvironmentVariableValue): Promise<Result> {
const project = await this.prismaClient.project.findUnique({
where: {
id: projectId,
deletedAt: null,
},
select: {
environments: {
select: {
id: true,
},
},
},
});
if (!project) {
return { success: false as const, error: "Project not found" };
}
const environmentVariable = await this.prismaClient.environmentVariable.findUnique({
select: {
id: true,
key: true,
values: {
select: {
id: true,
environmentId: true,
valueReference: {
select: {
key: true,
},
},
},
},
},
where: {
id: options.id,
},
});
if (!environmentVariable) {
return { success: false as const, error: "Environment variable not found" };
}
const value = environmentVariable.values.find((v) => v.environmentId === options.environmentId);
if (!value) {
return { success: false as const, error: "Environment variable value not found" };
}
// If this is the last value, delete the whole variable
if (environmentVariable.values.length === 1) {
return this.delete(projectId, { id: options.id });
}
try {
await $transaction(this.prismaClient, async (tx) => {
const secretStore = getSecretStore("DATABASE", {
prismaClient: tx,
});
const key = secretKey(projectId, options.environmentId, environmentVariable.key);
await secretStore.deleteSecret(key);
if (value.valueReference) {
await tx.secretReference.delete({
where: {
key: value.valueReference.key,
},
});
}
await tx.environmentVariableValue.delete({
where: {
id: value.id,
},
});
});
return {
success: true as const,
};
} catch (error) {
return {
success: false as const,
error: error instanceof Error ? error.message : "Something went wrong",
};
}
}
}
@@ -31,14 +31,22 @@ export const EditEnvironmentVariable = z.object({
value: z.string(),
})
),
keepEmptyValues: z.boolean().optional(),
});
export type EditEnvironmentVariable = z.infer<typeof EditEnvironmentVariable>;
export const DeleteEnvironmentVariable = z.object({
id: z.string(),
environmentId: z.string().optional(),
});
export type DeleteEnvironmentVariable = z.infer<typeof DeleteEnvironmentVariable>;
export const DeleteEnvironmentVariableValue = z.object({
id: z.string(),
environmentId: z.string(),
});
export type DeleteEnvironmentVariableValue = z.infer<typeof DeleteEnvironmentVariableValue>;
export type Result =
| {
success: true;
@@ -65,18 +73,19 @@ export type EnvironmentVariable = {
};
export interface Repository {
create(
projectId: string,
userId: string,
options: CreateEnvironmentVariables
): Promise<CreateResult>;
edit(projectId: string, userId: string, options: EditEnvironmentVariable): Promise<Result>;
getProject(projectId: string, userId: string): Promise<ProjectEnvironmentVariable[]>;
create(projectId: string, options: CreateEnvironmentVariables): Promise<CreateResult>;
edit(projectId: string, options: EditEnvironmentVariable): Promise<Result>;
getProject(projectId: string): Promise<ProjectEnvironmentVariable[]>;
getEnvironment(
projectId: string,
userId: string,
environmentId: string
environmentId: string,
excludeInternalVariables?: boolean
): Promise<EnvironmentVariable[]>;
getEnvironmentVariables(projectId: string, environmentId: string): Promise<EnvironmentVariable[]>;
delete(projectId: string, userId: string, options: DeleteEnvironmentVariable): Promise<Result>;
getEnvironmentVariables(
projectId: string,
environmentId: string,
excludeInternalVariables?: boolean
): Promise<EnvironmentVariable[]>;
delete(projectId: string, options: DeleteEnvironmentVariable): Promise<Result>;
deleteValue(projectId: string, options: DeleteEnvironmentVariableValue): Promise<Result>;
}
+93 -20
View File
@@ -1,8 +1,20 @@
{
"$schema": "https://mintlify.com/schema.json",
"name": "Trigger.dev",
"openapi": ["/openapi.yml", "/v3-openapi.json"],
"versions": ["v3 (Developer Preview)", "v2"],
"openapi": [
"/openapi.yml",
"/v3-openapi.yaml"
],
"versions": [
"v3 (Developer Preview)",
"v2"
],
"api": {
"playground": {
"mode": "hide"
},
"maintainOrder": true
},
"logo": {
"dark": "/logo/dark.png",
"light": "/logo/light.png",
@@ -90,12 +102,19 @@
{
"group": "",
"version": "v3 (Developer Preview)",
"pages": ["v3/introduction"]
"pages": [
"v3/introduction"
]
},
{
"group": "Getting Started",
"version": "v3 (Developer Preview)",
"pages": ["v3/quick-start", "v3/upgrading-from-v2", "v3/changelog", "v3/feature-matrix"]
"pages": [
"v3/quick-start",
"v3/upgrading-from-v2",
"v3/changelog",
"v3/feature-matrix"
]
},
{
"group": "Fundamentals",
@@ -107,7 +126,10 @@
"v3/apikeys",
{
"group": "Task types",
"pages": ["v3/tasks-regular", "v3/tasks-scheduled"]
"pages": [
"v3/tasks-regular",
"v3/tasks-scheduled"
]
},
"v3/trigger-config"
]
@@ -115,7 +137,10 @@
{
"group": "Development",
"version": "v3 (Developer Preview)",
"pages": ["v3/cli-dev", "v3/run-tests"]
"pages": [
"v3/cli-dev",
"v3/run-tests"
]
},
{
"group": "Deployment",
@@ -126,7 +151,9 @@
"v3/github-actions",
{
"group": "Deployment integrations",
"pages": ["v3/vercel-integration"]
"pages": [
"v3/vercel-integration"
]
}
]
},
@@ -178,13 +205,28 @@
"v3/management-deactivate-schedule",
"v3/management-activate-schedule"
]
},
{
"group": "Env Vars API",
"pages": [
"v3/management-envvars-list",
"v3/management-envvars-import",
"v3/management-envvars-create",
"v3/management-envvars-retrieve",
"v3/management-envvars-update",
"v3/management-envvars-delete"
]
}
]
},
{
"group": "Open source",
"version": "v3 (Developer Preview)",
"pages": ["v3/github-repo", "v3/open-source-self-hosting", "v3/open-source-contributing"]
"pages": [
"v3/github-repo",
"v3/open-source-self-hosting",
"v3/open-source-contributing"
]
},
{
"group": "Troubleshooting",
@@ -199,7 +241,11 @@
{
"group": "Help",
"version": "v3 (Developer Preview)",
"pages": ["v3/community", "v3/help-slack", "v3/help-email"]
"pages": [
"v3/community",
"v3/help-slack",
"v3/help-email"
]
},
{
"group": "Getting Started",
@@ -391,7 +437,10 @@
"pages": [
{
"group": "Airtable",
"pages": ["integrations/apis/airtable", "integrations/apis/airtable-tasks"]
"pages": [
"integrations/apis/airtable",
"integrations/apis/airtable-tasks"
]
},
{
"group": "GitHub",
@@ -417,16 +466,25 @@
},
{
"group": "Plain",
"pages": ["integrations/apis/plain", "integrations/apis/plain-tasks"]
"pages": [
"integrations/apis/plain",
"integrations/apis/plain-tasks"
]
},
"integrations/apis/replicate",
{
"group": "SendGrid",
"pages": ["integrations/apis/sendgrid", "integrations/apis/sendgrid-tasks"]
"pages": [
"integrations/apis/sendgrid",
"integrations/apis/sendgrid-tasks"
]
},
{
"group": "Resend",
"pages": ["integrations/apis/resend", "integrations/apis/resend-tasks"]
"pages": [
"integrations/apis/resend",
"integrations/apis/resend-tasks"
]
},
{
"group": "Shopify",
@@ -438,7 +496,10 @@
},
{
"group": "Slack",
"pages": ["integrations/apis/slack", "integrations/apis/slack-tasks"]
"pages": [
"integrations/apis/slack",
"integrations/apis/slack-tasks"
]
},
"integrations/apis/stripe",
{
@@ -464,7 +525,9 @@
"sdk/triggerclient/constructor",
{
"group": "Instance properties",
"pages": ["sdk/triggerclient/store"]
"pages": [
"sdk/triggerclient/store"
]
},
{
"group": "Instance methods",
@@ -527,7 +590,10 @@
"sdk/dynamictrigger/constructor",
{
"group": "Instance methods",
"pages": ["sdk/dynamictrigger/register", "sdk/dynamictrigger/unregister"]
"pages": [
"sdk/dynamictrigger/register",
"sdk/dynamictrigger/unregister"
]
}
]
},
@@ -538,7 +604,10 @@
"sdk/dynamicschedule/constructor",
{
"group": "Instance methods",
"pages": ["sdk/dynamicschedule/register", "sdk/dynamicschedule/unregister"]
"pages": [
"sdk/dynamicschedule/register",
"sdk/dynamicschedule/unregister"
]
}
]
},
@@ -551,7 +620,9 @@
{
"group": "HTTP Reference",
"version": "v2",
"pages": ["sdk/api-reference/events/create-an-event"]
"pages": [
"sdk/api-reference/events/create-an-event"
]
},
{
"group": "React SDK",
@@ -567,7 +638,9 @@
{
"group": "Overview",
"version": "v2",
"pages": ["examples/introduction"]
"pages": [
"examples/introduction"
]
}
],
"footerSocials": {
@@ -575,4 +648,4 @@
"github": "https://github.com/triggerdotdev",
"linkedin": "https://www.linkedin.com/company/triggerdotdev"
}
}
}
-942
View File
@@ -1,942 +0,0 @@
{
"openapi": "3.1.0",
"info": {
"title": "Trigger.dev v3 REST API",
"description": "The REST API lets you trigger and manage runs on Trigger.dev. You can trigger a run, get the status of a run, and get the results of a run. ",
"version": "2024-04"
},
"servers": [
{
"url": "https://api.trigger.dev",
"description": "Trigger.dev API"
}
],
"paths": {
"/api/v1/schedules": {
"post": {
"operationId": "create_schedule_v1",
"description": "Create a new schedule based on the specified options.",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateScheduleOptions"
}
}
}
},
"responses": {
"200": {
"description": "Schedule created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScheduleObject"
}
}
}
},
"400": {
"description": "Invalid request parameters"
},
"422": {
"description": "Unprocessable Entity"
},
"401": {
"description": "Unauthorized"
}
},
"tags": [
"schedules"
],
"security": [
{
"bearerAuth": []
}
],
"x-codeSamples": [
{
"lang": "typescript",
"source": "import { schedules } from \"@trigger.dev/sdk/v3\";\n\nconst schedule = await schedules.create({\n task: 'my-task',\n cron: '0 0 * * *'\n});"
},
{
"lang": "sh",
"source": "curl --request POST \\\n\t--url https://api.trigger.dev/api/v1/schedules \\\n\t--header 'Authorization: Bearer <token>' \\\n\t--header 'Content-Type: application/json' \\\n\t--data '{\"task\":\"my-task\",\"cron\":\"0 0 * * *\"}'"
}
]
},
"get": {
"operationId": "list_schedules_v1",
"description": "List all schedules.",
"parameters": [
{
"in": "query",
"name": "page",
"schema": {
"type": "integer"
},
"required": false,
"description": "Page number of the schedule listing"
},
{
"in": "query",
"name": "perPage",
"schema": {
"type": "integer"
},
"required": false,
"description": "Number of schedules per page"
}
],
"responses": {
"200": {
"description": "Successful request",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSchedulesResult"
}
}
}
},
"401": {
"description": "Unauthorized request"
}
},
"tags": [
"schedules"
],
"security": [
{
"bearerAuth": []
}
],
"x-codeSamples": [
{
"lang": "typescript",
"source": "import { schedules } from \"@trigger.dev/sdk/v3\";\n\nconst allSchedules = await schedules.list();"
},
{
"lang": "sh",
"source": "curl --request GET \\\n\t--url https://api.trigger.dev/api/v1/schedules \\\n\t--header 'Authorization: Bearer <token>'"
}
]
}
},
"/api/v1/schedules/{schedule_id}": {
"get": {
"operationId": "get_schedule_v1",
"description": "Get a schedule by its ID.",
"parameters": [
{
"in": "path",
"name": "schedule_id",
"required": true,
"schema": {
"type": "string"
},
"description": "The ID of the schedule."
}
],
"responses": {
"200": {
"description": "Successful request",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScheduleObject"
}
}
}
},
"401": {
"description": "Unauthorized request"
},
"404": {
"description": "Resource not found"
}
},
"tags": [
"schedules"
],
"security": [
{
"bearerAuth": []
}
],
"x-codeSamples": [
{
"lang": "typescript",
"source": "import { schedules } from \"@trigger.dev/sdk/v3\";\n\nconst schedule = await schedules.retrieve(scheduleId);"
},
{
"lang": "sh",
"source": "curl --request GET \\\n\t--url https://api.trigger.dev/api/v1/schedules/{schedule_id} \\\n\t--header 'Authorization: Bearer <token>'"
}
]
},
"put": {
"operationId": "update_schedule_v1",
"description": "Update a schedule by its ID.",
"parameters": [
{
"in": "path",
"name": "schedule_id",
"required": true,
"schema": {
"type": "string"
},
"description": "The ID of the schedule."
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateScheduleOptions"
}
}
}
},
"responses": {
"200": {
"description": "Schedule updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScheduleObject"
}
}
}
},
"400": {
"description": "Invalid request parameters"
},
"401": {
"description": "Unauthorized"
},
"404": {
"description": "Resource not found"
},
"422": {
"description": "Unprocessable Entity"
}
},
"tags": [
"schedules"
],
"security": [
{
"bearerAuth": []
}
],
"x-codeSamples": [
{
"lang": "typescript",
"source": "import { schedules } from \"@trigger.dev/sdk/v3\";\n\nconst updatedSchedule = await schedules.update(scheduleId, {\n task: 'my-updated-task',\n cron: '0 0 * * *'\n});"
},
{
"lang": "sh",
"source": "curl --request PUT \\\n\t--url https://api.trigger.dev/api/v1/schedules/{schedule_id} \\\n\t--header 'Authorization: Bearer <token>' \\\n\t--header 'Content-Type: application/json' \\\n\t--data '{\"task\":\"my-updated-task\",\"cron\":\"0 0 * * *\"}'"
}
]
},
"delete": {
"operationId": "delete_schedule_v1",
"description": "Delete a schedule by its ID.",
"parameters": [
{
"in": "path",
"name": "schedule_id",
"required": true,
"schema": {
"type": "string"
},
"description": "The ID of the schedule."
}
],
"responses": {
"200": {
"description": "Schedule deleted successfully"
},
"401": {
"description": "Unauthorized request"
},
"404": {
"description": "Resource not found"
}
},
"tags": [
"schedules"
],
"security": [
{
"bearerAuth": []
}
],
"x-codeSamples": [
{
"lang": "typescript",
"source": "import { schedules } from \"@trigger.dev/sdk/v3\";\n\nawait schedules.del(scheduleId);"
},
{
"lang": "sh",
"source": "curl --request DELETE \\\n\t--url https://api.trigger.dev/api/v1/schedules/{schedule_id} \\\n\t--header 'Authorization: Bearer <token>'"
}
]
}
},
"/api/v1/schedules/{schedule_id}/deactivate": {
"post": {
"operationId": "deactivate_schedule_v1",
"description": "Deactivate a schedule by its ID.",
"parameters": [
{
"in": "path",
"name": "schedule_id",
"required": true,
"schema": {
"type": "string"
},
"description": "The ID of the schedule."
}
],
"responses": {
"200": {
"description": "Schedule updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScheduleObject"
}
}
}
},
"401": {
"description": "Unauthorized request"
},
"404": {
"description": "Resource not found"
}
},
"tags": [
"schedules"
],
"security": [
{
"bearerAuth": []
}
],
"x-codeSamples": [
{
"lang": "typescript",
"source": "import { schedules } from \"@trigger.dev/sdk/v3\";\n\nconst schedule = await schedules.deactivate(scheduleId);"
},
{
"lang": "sh",
"source": "curl --request POST \\\n\t--url https://api.trigger.dev/api/v1/schedules/{schedule_id}/deactivate \\\n\t--header 'Authorization: Bearer <token>'"
}
]
}
},
"/api/v1/schedules/{schedule_id}/activate": {
"post": {
"operationId": "activate_schedule_v1",
"description": "Activate a schedule by its ID.",
"parameters": [
{
"in": "path",
"name": "schedule_id",
"required": true,
"schema": {
"type": "string"
},
"description": "The ID of the schedule."
}
],
"responses": {
"200": {
"description": "Schedule updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScheduleObject"
}
}
}
},
"401": {
"description": "Unauthorized request"
},
"404": {
"description": "Resource not found"
}
},
"tags": [
"schedules"
],
"security": [
{
"bearerAuth": []
}
],
"x-codeSamples": [
{
"lang": "typescript",
"source": "import { schedules } from \"@trigger.dev/sdk/v3\";\n\nconst schedule = await schedules.activate(scheduleId);"
},
{
"lang": "sh",
"source": "curl --request POST \\\n\t--url https://api.trigger.dev/api/v1/schedules/{schedule_id}/activate \\\n\t--header 'Authorization: Bearer <token>'"
}
]
}
},
"/api/v1/runs/{run_id}/replay": {
"post": {
"description": "Creates a new run with the same payload and options as the original run.",
"parameters": [
{
"in": "path",
"name": "run_id",
"required": true,
"schema": {
"type": "string"
},
"description": "The ID of an existing run. When you trigger a run you will get an id in the response."
}
],
"responses": {
"200": {
"description": "Successful request",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "The ID of the new run."
}
}
}
}
}
},
"400": {
"description": "Invalid request",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string",
"enum": [
"Invalid or missing run ID",
"Failed to create new run"
]
}
}
}
}
}
},
"401": {
"description": "Unauthorized request",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string",
"enum": [
"Invalid or Missing API key"
]
}
}
}
}
}
},
"404": {
"description": "Resource not found",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string",
"enum": [
"Run not found"
]
}
}
}
}
}
}
},
"tags": [
"run"
],
"security": [
{
"bearerAuth": []
}
],
"operationId": "replay_run_v1",
"x-codeSamples": [
{
"lang": "typescript",
"source": "import { runs } from \"@trigger.dev/sdk/v3\";\n\nconst handle = await runs.replay(\"run_1234\");"
},
{
"lang": "sh",
"source": "curl --request POST \\\n\t--url https://api.trigger.dev/api/v1/runs/{run_id}/replay \\\n\t--header 'Authorization: Bearer <token>'"
}
]
}
},
"/api/v1/runs/{run_id}/cancel": {
"post": {
"description": "Cancels a run.",
"parameters": [
{
"in": "path",
"name": "run_id",
"required": true,
"schema": {
"type": "string"
},
"description": "The ID of an existing run. When you trigger a run you will get an id in the response."
}
],
"responses": {
"200": {
"description": "Successful request",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"message": {
"type": "string",
"description": "Confirmation message that the run was canceled."
}
}
}
}
}
},
"400": {
"description": "Invalid request",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string",
"enum": [
"Invalid or missing run ID",
"Failed to create new run"
]
}
}
}
}
}
},
"401": {
"description": "Unauthorized request",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string",
"enum": [
"Invalid or Missing API key"
]
}
}
}
}
}
},
"404": {
"description": "Resource not found",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string",
"enum": [
"Run not found"
]
}
}
}
}
}
}
},
"tags": [
"run"
],
"security": [
{
"bearerAuth": []
}
],
"operationId": "replay_run_v1",
"x-codeSamples": [
{
"lang": "typescript",
"source": "import { runs } from \"@trigger.dev/sdk/v3\";\n\nawait runs.cancel(\"run_1234\");"
},
{
"lang": "sh",
"source": "curl --request POST \\\n\t--url https://api.trigger.dev/api/v1/runs/{run_id}/cancel \\\n\t--header 'Authorization: Bearer <token>'"
}
]
}
},
"/api/v3/runs/{run_id}": {
"get": {
"description": "Retrieve a run",
"parameters": [
{
"in": "path",
"name": "run_id",
"required": true,
"schema": {
"type": "string"
},
"description": "The ID of an existing run. When you trigger a run you will get an id in the response."
}
],
"responses": {
"200": {
"description": "Successful request",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RetrieveRunResponse"
}
}
}
},
"400": {
"description": "Invalid request",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string",
"enum": [
"Invalid or missing run ID"
]
}
}
}
}
}
},
"401": {
"description": "Unauthorized request",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string",
"enum": [
"Invalid or Missing API key"
]
}
}
}
}
}
},
"404": {
"description": "Resource not found",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string",
"enum": [
"Run not found"
]
}
}
}
}
}
}
},
"tags": [
"run"
],
"security": [
{
"bearerAuth": []
}
],
"operationId": "retrieve_run_v1",
"x-codeSamples": [
{
"lang": "typescript",
"source": "import { runs } from \"@trigger.dev/sdk/v3\";\n\nawait runs.retrieve(\"run_1234\");"
},
{
"lang": "sh",
"source": "curl --request GET \\\n\t--url https://api.trigger.dev/api/v3/runs/{run_id} \\\n\t--header 'Authorization: Bearer <token>'"
}
]
}
}
},
"components": {
"securitySchemes": {
"bearerAuth": {
"type": "http",
"scheme": "bearer",
"description": "Use your Secret API key in the form 'Bearer <SECRET KEY>' (without the quotation marks)"
}
},
"schemas": {
"RetrieveRunResponse": {
"type": "object",
"required": [
"id",
"status",
"taskIdentifier",
"createdAt",
"updatedAt",
"attempts"
],
"properties": {
"id": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"PENDING",
"EXECUTING",
"PAUSED",
"COMPLETED",
"FAILED",
"CANCELED"
]
},
"taskIdentifier": {
"type": "string"
},
"idempotencyKey": {
"type": "string"
},
"version": {
"type": "string"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": "string",
"format": "date-time"
},
"attempts": {
"type": "array",
"items": {
"type": "object",
"required": [
"id",
"status",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"PENDING",
"EXECUTING",
"PAUSED",
"COMPLETED",
"FAILED",
"CANCELED"
]
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": "string",
"format": "date-time"
},
"startedAt": {
"type": "string",
"format": "date-time"
},
"completedAt": {
"type": "string",
"format": "date-time"
}
}
}
}
}
},
"CreateScheduleOptions": {
"type": "object",
"properties": {
"task": {
"type": "string"
},
"cron": {
"type": "string"
},
"deduplicationKey": {
"type": "string"
},
"externalId": {
"type": "string"
}
},
"required": [
"task",
"cron"
]
},
"ScheduleObject": {
"type": "object",
"properties": {
"id": {
"type": "string",
"example": "sched_1234",
"description": "The unique ID of the schedule, prefixed with 'sched_'"
},
"task": {
"type": "string",
"example": "my-scheduled-task",
"description": "The id of the scheduled task that will be triggered by this schedule"
},
"active": {
"type": "boolean",
"example": true,
"description": "Whether the schedule is active or not"
},
"deduplicationKey": {
"type": "string",
"example": "dedup_key_1234",
"description": "The deduplication key used to prevent creating duplicate schedules"
},
"externalId": {
"type": "string",
"example": "user_1234",
"description": "The external ID of the schedule. Can be anything that is useful to you (e.g., user ID, org ID, etc.)"
},
"generator": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"CRON"
]
},
"expression": {
"type": "string",
"description": "The cron expression used to generate the schedule",
"example": "0 0 * * *"
},
"description": {
"type": "string",
"description": "The description of the generator in plain english",
"example": "Every day at midnight"
}
}
},
"nextRun": {
"type": "string",
"format": "date-time",
"description": "The next time the schedule will run",
"example": "2024-04-01T00:00:00Z"
},
"environments": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScheduleEnvironment"
}
}
}
},
"ListSchedulesResult": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScheduleObject"
}
},
"pagination": {
"type": "object",
"properties": {
"currentPage": {
"type": "integer"
},
"totalPages": {
"type": "integer"
},
"count": {
"type": "integer"
}
}
}
}
},
"ScheduleEnvironment": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"type": {
"type": "string"
},
"userName": {
"type": "string"
}
}
}
}
},
"security": [
{
"bearerAuth": []
}
]
}
+1176
View File
File diff suppressed because it is too large Load Diff
+4
View File
@@ -0,0 +1,4 @@
---
title: "Create Env Var"
openapi: "v3-openapi POST /api/v1/projects/{projectRef}/envvars/{env}"
---
+4
View File
@@ -0,0 +1,4 @@
---
title: "Delete Env Var"
openapi: "v3-openapi DELETE /api/v1/projects/{projectRef}/envvars/{env}/{name}"
---
+4
View File
@@ -0,0 +1,4 @@
---
title: "Import Env Vars"
openapi: "v3-openapi POST /api/v1/projects/{projectRef}/envvars/{env}/import"
---
+4
View File
@@ -0,0 +1,4 @@
---
title: "List Env Vars"
openapi: "v3-openapi GET /api/v1/projects/{projectRef}/envvars/{env}"
---
+4
View File
@@ -0,0 +1,4 @@
---
title: "Retrieve Env Var"
openapi: "v3-openapi GET /api/v1/projects/{projectRef}/envvars/{env}/{name}"
---
+4
View File
@@ -0,0 +1,4 @@
---
title: "Update Env Var"
openapi: "v3-openapi PUT /api/v1/projects/{projectRef}/envvars/{env}/{name}"
---
+25
View File
@@ -14,6 +14,8 @@ import {
GetDeploymentResponseBody,
GetProjectsResponseBody,
GetProjectResponseBody,
ImportEnvironmentVariablesRequestBody,
EnvironmentVariableResponseBody,
} from "@trigger.dev/core/v3";
export class CliApiClient {
@@ -139,6 +141,29 @@ export class CliApiClient {
);
}
async importEnvVars(
projectRef: string,
slug: "dev" | "prod" | "staging",
params: ImportEnvironmentVariablesRequestBody
) {
if (!this.accessToken) {
throw new Error("importEnvVars: No access token");
}
return zodfetch(
EnvironmentVariableResponseBody,
`${this.apiURL}/api/v1/projects/${projectRef}/envvars/${slug}/import`,
{
method: "POST",
headers: {
Authorization: `Bearer ${this.accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify(params),
}
);
}
async initializeDeployment(body: InitializeDeploymentRequestBody) {
if (!this.accessToken) {
throw new Error("initializeDeployment: No access token");
+92 -2
View File
@@ -14,7 +14,7 @@ import { execa } from "execa";
import { createHash } from "node:crypto";
import { readFileSync } from "node:fs";
import { copyFile, mkdir, readFile, writeFile } from "node:fs/promises";
import { dirname, join, posix, relative } from "node:path";
import { dirname, join, posix, relative, resolve } from "node:path";
import { setTimeout } from "node:timers/promises";
import terminalLink from "terminal-link";
import invariant from "tiny-invariant";
@@ -30,7 +30,7 @@ import {
tracer,
wrapCommandAction,
} from "../cli/common.js";
import { readConfig } from "../utilities/configFiles.js";
import { ReadConfigResult, readConfig } from "../utilities/configFiles.js";
import { createTempDir, writeJSONFile } from "../utilities/fileSystem";
import { printStandloneInitialBanner } from "../utilities/initialBanner.js";
import {
@@ -63,6 +63,7 @@ import { cliRootPath } from "../utilities/resolveInternalFilePath";
import { safeJsonParse } from "../utilities/safeJsonParse";
import { escapeImportPath, spinner } from "../utilities/windows";
import { updateTriggerPackages } from "./update";
import { callResolveEnvVars } from "../utilities/resolveEnvVars";
const DeployCommandOptions = CommonCommandOptions.extend({
skipTypecheck: z.boolean().default(false),
@@ -258,6 +259,9 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) {
logger.debug("Compilation result", { compilation });
// Optional Step 1.1: resolve environment variables
await resolveEnvironmentVariables(resolvedConfig, environmentClient, options);
// Step 2: Initialize a deployment on the server (response will have everything we need to build an image)
const deploymentResponse = await environmentClient.initializeDeployment({
contentHash: compilation.contentHash,
@@ -1391,6 +1395,92 @@ async function compileProject(
});
}
async function resolveEnvironmentVariables(
config: ReadConfigResult,
apiClient: CliApiClient,
options: DeployCommandOptions
) {
if (config.status !== "file") {
return;
}
if (!config.module || typeof config.module.resolveEnvVars !== "function") {
return;
}
const projectConfig = config.config;
return await tracer.startActiveSpan("resolveEnvironmentVariables", async (span) => {
try {
const $spinner = spinner();
$spinner.start("Resolving environment variables");
let processEnv: Record<string, string | undefined> = {
...process.env,
};
// Step 1: Get existing env vars from the apiClient
const environmentVariables = await apiClient.getEnvironmentVariables(projectConfig.project);
if (environmentVariables.success) {
processEnv = {
...processEnv,
...environmentVariables.data.variables,
};
}
logger.debug("Existing environment variables", {
keys: Object.keys(processEnv),
});
// Step 2: Call the resolveEnvVars function with the existing env vars (and process.env)
const resolvedEnvVars = await callResolveEnvVars(
config.module,
processEnv,
options.env,
projectConfig.project
);
// Step 3: Upload the new env vars via the apiClient
if (resolvedEnvVars) {
const total = Object.keys(resolvedEnvVars.variables).length;
logger.debug("Resolved env vars", {
keys: Object.keys(resolvedEnvVars.variables),
});
if (total > 0) {
$spinner.message(
`Syncing ${total} environment variable${total > 1 ? "s" : ""} with the server`
);
const uploadResult = await apiClient.importEnvVars(projectConfig.project, options.env, {
variables: resolvedEnvVars.variables,
override:
typeof resolvedEnvVars.override === "boolean" ? resolvedEnvVars.override : true,
});
if (uploadResult.success) {
$spinner.stop(`${total} environment variable${total > 1 ? "s" : ""} synced`);
} else {
$spinner.stop("Failed to sync environment variables");
throw new Error(uploadResult.error);
}
} else {
$spinner.stop("No environment variables to sync");
}
}
} catch (e) {
recordSpanException(span, e);
throw e;
} finally {
span.end();
}
});
}
// Let's first create a digest from the package.json, and then use that digest to lookup a cached package-lock.json
// in the `.trigger/cache` directory. If the package-lock.json is found, we'll write it to the project directory
// If the package-lock.json is not found, we will run `npm install --package-lock-only` and then write the package-lock.json
+39 -4
View File
@@ -55,6 +55,7 @@ import { cliRootPath } from "../utilities/resolveInternalFilePath";
import { escapeImportPath } from "../utilities/windows";
import { updateTriggerPackages } from "./update";
import { esbuildDecorators } from "@anatine/esbuild-decorators";
import { callResolveEnvVars } from "../utilities/resolveEnvVars";
let apiClient: CliApiClient | undefined;
@@ -162,7 +163,8 @@ async function startDev(
async function getDevReactElement(
configParam: ResolvedConfig,
authorization: { apiUrl: string; accessToken: string },
configPath?: string
configPath?: string,
configModule?: any
) {
const accessToken = authorization.accessToken;
const apiUrl = authorization.apiUrl;
@@ -201,6 +203,7 @@ async function startDev(
debuggerOn={options.debugger}
debugOtel={options.debugOtel}
configPath={configPath}
configModule={configModule}
/>
);
}
@@ -209,7 +212,8 @@ async function startDev(
await getDevReactElement(
config.config,
authorization,
config.status === "file" ? config.path : undefined
config.status === "file" ? config.path : undefined,
config.status === "file" ? config.module : undefined
)
);
@@ -236,6 +240,7 @@ type DevProps = {
debuggerOn: boolean;
debugOtel: boolean;
configPath?: string;
configModule?: any;
};
function useDev({
@@ -248,6 +253,7 @@ function useDev({
debuggerOn,
debugOtel,
configPath,
configModule,
}: DevProps) {
useEffect(() => {
const websocketUrl = new URL(apiUrl);
@@ -345,6 +351,8 @@ function useDev({
let ctx: BuildContext | undefined;
let firstBuild = true;
async function runBuild() {
if (ctx) {
// This will stop the watching
@@ -386,8 +394,6 @@ function useDev({
);
}
let firstBuild = true;
logger.log(chalkGrey("○ Building background worker…"));
ctx = await context({
@@ -512,6 +518,7 @@ function useDev({
},
debuggerOn,
debugOtel,
resolveEnvVariables: createResolveEnvironmentVariablesFunction(configModule),
});
try {
@@ -870,3 +877,31 @@ async function findPnpmNodeModulesPath(): Promise<string | undefined> {
{ type: "directory" }
);
}
let hasResolvedEnvVars = false;
let resolvedEnvVars: Record<string, string> = {};
function createResolveEnvironmentVariablesFunction(configModule?: any) {
return async (
env: Record<string, string>,
worker: BackgroundWorker
): Promise<Record<string, string> | undefined> => {
if (hasResolvedEnvVars) {
return resolvedEnvVars;
}
const $resolvedEnvVars = await callResolveEnvVars(
configModule,
env,
"dev",
worker.params.projectConfig.project
);
if ($resolvedEnvVars) {
resolvedEnvVars = $resolvedEnvVars.variables;
hasResolvedEnvVars = true;
}
return resolvedEnvVars;
};
}
@@ -122,6 +122,7 @@ export type ReadConfigResult =
status: "file";
config: ResolvedConfig;
path: string;
module?: any;
}
| {
status: "in-memory";
@@ -183,6 +184,14 @@ export async function readConfig(
tsx: false,
force: false,
}),
{
name: "native-node-modules",
setup(build) {
const opts = build.initialOptions;
opts.loader = opts.loader || {};
opts.loader[".node"] = "copy";
},
},
],
});
@@ -202,6 +211,7 @@ export async function readConfig(
status: "file",
config: await resolveConfig(absoluteDir, config),
path: configPath,
module: userConfigModule,
};
} catch (error) {
return {
@@ -0,0 +1,62 @@
import { logger } from "./logger";
export async function callResolveEnvVars(
configModule: any,
env: Record<string, string | undefined>,
environment: string,
projectRef: string
): Promise<{ variables: Record<string, string>; override: boolean } | undefined> {
if (
configModule &&
configModule.resolveEnvVars &&
typeof configModule.resolveEnvVars === "function"
) {
let resolvedEnvVars: Record<string, string> = {};
try {
let result = await configModule.resolveEnvVars({
projectRef,
environment,
env,
});
if (!result) {
return;
}
result = await result;
if (typeof result === "object" && result !== null && "variables" in result) {
const variables = result.variables;
if (Array.isArray(variables)) {
for (const item of variables) {
if (
typeof item === "object" &&
item !== null &&
"name" in item &&
"value" in item &&
typeof item.name === "string" &&
typeof item.value === "string"
) {
resolvedEnvVars[item.name] = item.value;
}
}
} else if (typeof variables === "object") {
for (const [key, value] of Object.entries(variables)) {
if (typeof key === "string" && typeof value === "string") {
resolvedEnvVars[key] = value;
}
}
}
}
return {
variables: resolvedEnvVars,
override: result.override,
};
} catch (error) {
logger.error(error);
}
}
}
@@ -37,6 +37,7 @@ import { safeDeleteFileSync } from "../../utilities/fileSystem.js";
import { installPackages } from "../../utilities/installPackages.js";
import { logger } from "../../utilities/logger.js";
import { TaskMetadataParseError, UncaughtExceptionError } from "../common/errors.js";
import { env } from "node:process";
export type CurrentWorkers = BackgroundWorkerCoordinator["currentWorkers"];
export class BackgroundWorkerCoordinator {
@@ -263,7 +264,12 @@ export type BackgroundWorkerParams = {
projectConfig: ResolvedConfig;
debuggerOn: boolean;
debugOtel?: boolean;
resolveEnvVariables?: (
env: Record<string, string>,
worker: BackgroundWorker
) => Promise<Record<string, string> | undefined>;
};
export class BackgroundWorker {
private _initialized: boolean = false;
private _handler = new ZodMessageHandler({
@@ -280,9 +286,11 @@ export class BackgroundWorker {
private _closed: boolean = false;
private _fullEnv: Record<string, string> = {};
constructor(
public path: string,
private params: BackgroundWorkerParams
public params: BackgroundWorkerParams
) {}
close() {
@@ -320,19 +328,34 @@ export class BackgroundWorker {
const cwd = dirname(this.path);
const fullEnv = {
this._fullEnv = {
...this.params.env,
...this.#readEnvVars(),
...(this.params.debugOtel ? { OTEL_LOG_LEVEL: "debug" } : {}),
};
logger.debug("Initializing worker", { path: this.path, cwd, fullEnv });
let resolvedEnvVars: Record<string, string> = {};
if (this.params.resolveEnvVariables) {
const resolvedEnv = await this.params.resolveEnvVariables(this._fullEnv, this);
if (resolvedEnv) {
resolvedEnvVars = resolvedEnv;
}
}
this._fullEnv = {
...this._fullEnv,
...resolvedEnvVars,
};
logger.debug("Initializing worker", { path: this.path, cwd, fullEnv: this._fullEnv });
this.tasks = await new Promise<Array<TaskMetadataWithFilePath>>((resolve, reject) => {
const child = fork(this.path, {
stdio: [/*stdin*/ "ignore", /*stdout*/ "pipe", /*stderr*/ "pipe", "ipc"],
cwd,
env: fullEnv,
env: this._fullEnv,
});
// Set a timeout to kill the child process if it doesn't respond
@@ -404,9 +427,8 @@ export class BackgroundWorker {
payload.execution,
this.path,
{
...this.params.env,
...this._fullEnv,
...(payload.environment ?? {}),
...this.#readEnvVars(),
},
this.metadata,
this.params
+5 -4
View File
@@ -134,13 +134,14 @@
"@opentelemetry/sdk-trace-base": "^1.22.0",
"@opentelemetry/sdk-trace-node": "^1.22.0",
"@opentelemetry/semantic-conventions": "^1.22.0",
"form-data-encoder": "^4.0.2",
"humanize-duration": "^3.27.3",
"socket.io-client": "4.7.4",
"superjson": "^2.2.1",
"ulidx": "^2.2.1",
"zod": "3.22.3",
"zod-error": "1.5.0",
"zod-validation-error": "^1.5.0",
"socket.io-client": "4.7.4"
"zod-validation-error": "^1.5.0"
},
"devDependencies": {
"@trigger.dev/tsconfig": "workspace:*",
@@ -150,10 +151,10 @@
"@types/node": "20.12.7",
"jest": "^29.6.2",
"rimraf": "^3.0.2",
"socket.io": "4.7.4",
"ts-jest": "^29.1.1",
"tsup": "^8.0.1",
"typescript": "^5.3.0",
"socket.io": "4.7.4"
"typescript": "^5.3.0"
},
"engines": {
"node": ">=18.0.0"
+103 -1
View File
@@ -6,9 +6,13 @@ import {
BatchTriggerTaskRequestBody,
BatchTriggerTaskResponse,
CanceledRunResponse,
CreateEnvironmentVariableRequestBody,
CreateScheduleOptions,
CreateUploadPayloadUrlResponseBody,
DeletedScheduleObject,
EnvironmentVariableResponseBody,
EnvironmentVariableValue,
EnvironmentVariables,
ListScheduleOptions,
ListSchedulesResult,
ReplayRunResponse,
@@ -17,10 +21,22 @@ import {
TaskRunExecutionResult,
TriggerTaskRequestBody,
TriggerTaskResponse,
UpdateEnvironmentVariableRequestBody,
UpdateScheduleOptions,
} from "../schemas";
import { taskContext } from "../task-context-api";
import { ZodFetchOptions, zodfetch } from "../zodfetch";
import { ZodFetchOptions, isRecordLike, zodfetch, zodupload } from "../zodfetch";
import {
ImportEnvironmentVariablesParams,
CreateEnvironmentVariableParams,
UpdateEnvironmentVariableParams,
} from "./types";
export type {
ImportEnvironmentVariablesParams,
CreateEnvironmentVariableParams,
UpdateEnvironmentVariableParams,
};
export type TriggerOptions = {
spanParentAsLink?: boolean;
@@ -234,6 +250,92 @@ export class ApiClient {
});
}
listEnvVars(projectRef: string, slug: string) {
return zodfetch(
EnvironmentVariables,
`${this.baseUrl}/api/v1/projects/${projectRef}/envvars/${slug}`,
{
method: "GET",
headers: this.#getHeaders(false),
}
);
}
importEnvVars(projectRef: string, slug: string, body: ImportEnvironmentVariablesParams) {
if (isRecordLike(body.variables)) {
return zodfetch(
EnvironmentVariableResponseBody,
`${this.baseUrl}/api/v1/projects/${projectRef}/envvars/${slug}/import`,
{
method: "POST",
headers: this.#getHeaders(false),
body: JSON.stringify(body),
}
);
} else {
return zodupload(
EnvironmentVariableResponseBody,
`${this.baseUrl}/api/v1/projects/${projectRef}/envvars/${slug}/import`,
body,
{
method: "POST",
headers: this.#getHeaders(false),
}
);
}
}
retrieveEnvVar(projectRef: string, slug: string, key: string) {
return zodfetch(
EnvironmentVariableValue,
`${this.baseUrl}/api/v1/projects/${projectRef}/envvars/${slug}/${key}`,
{
method: "GET",
headers: this.#getHeaders(false),
}
);
}
createEnvVar(projectRef: string, slug: string, body: CreateEnvironmentVariableRequestBody) {
return zodfetch(
EnvironmentVariableResponseBody,
`${this.baseUrl}/api/v1/projects/${projectRef}/envvars/${slug}`,
{
method: "POST",
headers: this.#getHeaders(false),
body: JSON.stringify(body),
}
);
}
updateEnvVar(
projectRef: string,
slug: string,
key: string,
body: UpdateEnvironmentVariableRequestBody
) {
return zodfetch(
EnvironmentVariableResponseBody,
`${this.baseUrl}/api/v1/projects/${projectRef}/envvars/${slug}/${key}`,
{
method: "PUT",
headers: this.#getHeaders(false),
body: JSON.stringify(body),
}
);
}
deleteEnvVar(projectRef: string, slug: string, key: string) {
return zodfetch(
EnvironmentVariableResponseBody,
`${this.baseUrl}/api/v1/projects/${projectRef}/envvars/${slug}/${key}`,
{
method: "DELETE",
headers: this.#getHeaders(false),
}
);
}
#getHeaders(spanParentAsLink: boolean) {
const headers: Record<string, string> = {
"Content-Type": "application/json",
+24
View File
@@ -0,0 +1,24 @@
import { BlobLikePart, Uploadable } from "../zodfetch";
export interface ImportEnvironmentVariablesParams {
/**
* The variables to be imported. If a variable with the same key already exists, it will be overwritten when `override` is `true`.
*
* There are two ways to specify the variables:
*
* 1. As a record of key-value pairs. e.g. `{ "key1": "value1", "key2": "value2" }`
* 2. As an "uploadable" object in dotenv format. An uploadable can be a Node readable stream, a string, or a Buffer. You can also pass the return value of a `fetch` call.
*/
variables: Uploadable | BlobLikePart | Record<string, string>;
override?: boolean;
}
export interface CreateEnvironmentVariableParams {
name: string;
value: string;
}
export interface UpdateEnvironmentVariableParams {
value: string;
}
@@ -33,7 +33,7 @@ export class APIClientManagerAPI {
get accessToken(): string | undefined {
const store = this.#getConfig();
return store?.secretKey ?? getEnvVar("TRIGGER_SECRET_KEY");
return store?.secretKey ?? getEnvVar("TRIGGER_SECRET_KEY") ?? getEnvVar("TRIGGER_ACCESS_TOKEN");
}
get client(): ApiClient | undefined {
+47
View File
@@ -363,3 +363,50 @@ export const RetrieveRunResponse = z.object({
});
export type RetrieveRunResponse = z.infer<typeof RetrieveRunResponse>;
export const CreateEnvironmentVariableRequestBody = z.object({
name: z.string(),
value: z.string(),
});
export type CreateEnvironmentVariableRequestBody = z.infer<
typeof CreateEnvironmentVariableRequestBody
>;
export const UpdateEnvironmentVariableRequestBody = z.object({
value: z.string(),
});
export type UpdateEnvironmentVariableRequestBody = z.infer<
typeof UpdateEnvironmentVariableRequestBody
>;
export const ImportEnvironmentVariablesRequestBody = z.object({
variables: z.record(z.string()),
override: z.boolean().optional(),
});
export type ImportEnvironmentVariablesRequestBody = z.infer<
typeof ImportEnvironmentVariablesRequestBody
>;
export const EnvironmentVariableResponseBody = z.object({
success: z.boolean(),
});
export type EnvironmentVariableResponseBody = z.infer<typeof EnvironmentVariableResponseBody>;
export const EnvironmentVariableValue = z.object({
value: z.string(),
});
export type EnvironmentVariableValue = z.infer<typeof EnvironmentVariableValue>;
export const EnvironmentVariable = z.object({
name: z.string(),
value: z.string(),
});
export const EnvironmentVariables = z.array(EnvironmentVariable);
export type EnvironmentVariables = z.infer<typeof EnvironmentVariables>;
+21
View File
@@ -66,6 +66,27 @@ export type HandleErrorFunction = (
params: HandleErrorArgs
) => HandleErrorResult;
type ResolveEnvironmentVariablesOptions = {
variables: Record<string, string> | Array<{ name: string; value: string }>;
override?: boolean;
};
export type ResolveEnvironmentVariablesResult =
| ResolveEnvironmentVariablesOptions
| Promise<void | undefined | ResolveEnvironmentVariablesOptions>
| void
| undefined;
export type ResolveEnvironmentVariablesParams = {
projectRef: string;
environment: "dev" | "staging" | "prod";
env: Record<string, string>;
};
export type ResolveEnvironmentVariablesFunction = (
params: ResolveEnvironmentVariablesParams
) => ResolveEnvironmentVariablesResult;
export type TaskMetadataWithFunctions = TaskMetadata & {
fns: {
run: (payload: any, params: RunFnParams<any>) => Promise<any>;
+269 -6
View File
@@ -3,6 +3,8 @@ import { fromZodError } from "zod-validation-error";
import { APIConnectionError, APIError } from "./apiErrors";
import { RetryOptions } from "./schemas";
import { calculateNextRetryDelay } from "./utils/retries";
import { FormDataEncoder } from "form-data-encoder";
import { Readable } from "stream";
export const defaultRetryOptions = {
maxAttempts: 3,
@@ -16,22 +18,75 @@ export type ZodFetchOptions = {
retry?: RetryOptions;
};
export async function zodfetch<TResponseBody extends any>(
schema: z.Schema<TResponseBody>,
export async function zodfetch<TResponseBodySchema extends z.ZodTypeAny>(
schema: TResponseBodySchema,
url: string,
requestInit?: RequestInit,
options?: ZodFetchOptions
): Promise<TResponseBody> {
): Promise<z.output<TResponseBodySchema>> {
return await _doZodFetch(schema, url, requestInit, options);
}
async function _doZodFetch<TResponseBody extends any>(
schema: z.Schema<TResponseBody>,
export class MultipartBody {
constructor(public body: any) {}
get [Symbol.toStringTag](): string {
return "MultipartBody";
}
}
export async function zodupload<
TResponseBodySchema extends z.ZodTypeAny,
TBody = Record<string, unknown>,
>(
schema: TResponseBodySchema,
url: string,
body: TBody,
requestInit?: RequestInit,
options?: ZodFetchOptions
): Promise<z.output<TResponseBodySchema>> {
const form = await createForm(body);
const encoder = new FormDataEncoder(form);
const finalHeaders: Record<string, string> = {};
for (const [key, value] of Object.entries(requestInit?.headers || {})) {
finalHeaders[key] = value as string;
}
for (const [key, value] of Object.entries(encoder.headers)) {
finalHeaders[key] = value;
}
finalHeaders["Content-Length"] = String(encoder.contentLength);
const finalRequestInit: RequestInit = {
...requestInit,
headers: finalHeaders,
body: Readable.from(encoder) as any,
// @ts-expect-error
duplex: "half",
};
return await _doZodFetch(schema, url, finalRequestInit, options);
}
export const createForm = async <T = Record<string, unknown>>(
body: T | undefined
): Promise<FormData> => {
const form = new FormData();
await Promise.all(
Object.entries(body || {}).map(([key, value]) => addFormValue(form, key, value))
);
return form;
};
async function _doZodFetch<TResponseBodySchema extends z.ZodTypeAny>(
schema: TResponseBodySchema,
url: string,
requestInit?: RequestInit,
options?: ZodFetchOptions,
attempt = 1
): Promise<TResponseBody> {
): Promise<z.output<TResponseBodySchema>> {
try {
const response = await fetch(url, requestInitWithCache(requestInit));
@@ -172,3 +227,211 @@ function requestInitWithCache(requestInit?: RequestInit): RequestInit {
return requestInit ?? {};
}
}
const addFormValue = async (form: FormData, key: string, value: unknown): Promise<void> => {
if (value === undefined) return;
if (value == null) {
throw new TypeError(
`Received null for "${key}"; to pass null in FormData, you must use the string 'null'`
);
}
// TODO: make nested formats configurable
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
form.append(key, String(value));
} else if (
isUploadable(value) ||
isBlobLike(value) ||
value instanceof Buffer ||
value instanceof ArrayBuffer
) {
const file = await toFile(value);
form.append(key, file as File);
} else if (Array.isArray(value)) {
await Promise.all(value.map((entry) => addFormValue(form, key + "[]", entry)));
} else if (typeof value === "object") {
await Promise.all(
Object.entries(value).map(([name, prop]) => addFormValue(form, `${key}[${name}]`, prop))
);
} else {
throw new TypeError(
`Invalid value given to form, expected a string, number, boolean, object, Array, File or Blob but got ${value} instead`
);
}
};
export type ToFileInput = Uploadable | Exclude<BlobLikePart, string> | AsyncIterable<BlobLikePart>;
/**
* Helper for creating a {@link File} to pass to an SDK upload method from a variety of different data formats
* @param value the raw content of the file. Can be an {@link Uploadable}, {@link BlobLikePart}, or {@link AsyncIterable} of {@link BlobLikePart}s
* @param {string=} name the name of the file. If omitted, toFile will try to determine a file name from bits if possible
* @param {Object=} options additional properties
* @param {string=} options.type the MIME type of the content
* @param {number=} options.lastModified the last modified timestamp
* @returns a {@link File} with the given properties
*/
export async function toFile(
value: ToFileInput | PromiseLike<ToFileInput>,
name?: string | null | undefined,
options?: FilePropertyBag | undefined
): Promise<FileLike> {
// If it's a promise, resolve it.
value = await value;
// Use the file's options if there isn't one provided
options ??= isFileLike(value) ? { lastModified: value.lastModified, type: value.type } : {};
if (isResponseLike(value)) {
const blob = await value.blob();
name ||= new URL(value.url).pathname.split(/[\\/]/).pop() ?? "unknown_file";
return new File([blob as any], name, options);
}
const bits = await getBytes(value);
name ||= getName(value) ?? "unknown_file";
if (!options.type) {
const type = (bits[0] as any)?.type;
if (typeof type === "string") {
options = { ...options, type };
}
}
return new File(bits, name, options);
}
function getName(value: any): string | undefined {
return (
getStringFromMaybeBuffer(value.name) ||
getStringFromMaybeBuffer(value.filename) ||
// For fs.ReadStream
getStringFromMaybeBuffer(value.path)?.split(/[\\/]/).pop()
);
}
const getStringFromMaybeBuffer = (x: string | Buffer | unknown): string | undefined => {
if (typeof x === "string") return x;
if (typeof Buffer !== "undefined" && x instanceof Buffer) return String(x);
return undefined;
};
async function getBytes(value: ToFileInput): Promise<Array<BlobPart>> {
let parts: Array<BlobPart> = [];
if (
typeof value === "string" ||
ArrayBuffer.isView(value) || // includes Uint8Array, Buffer, etc.
value instanceof ArrayBuffer
) {
parts.push(value);
} else if (isBlobLike(value)) {
parts.push(await value.arrayBuffer());
} else if (
isAsyncIterableIterator(value) // includes Readable, ReadableStream, etc.
) {
for await (const chunk of value) {
parts.push(chunk as BlobPart); // TODO, consider validating?
}
} else {
throw new Error(
`Unexpected data type: ${typeof value}; constructor: ${value?.constructor
?.name}; props: ${propsForError(value)}`
);
}
return parts;
}
function propsForError(value: any): string {
const props = Object.getOwnPropertyNames(value);
return `[${props.map((p) => `"${p}"`).join(", ")}]`;
}
const isAsyncIterableIterator = (value: any): value is AsyncIterableIterator<unknown> =>
value != null && typeof value === "object" && typeof value[Symbol.asyncIterator] === "function";
/**
* Intended to match web.Blob, node.Blob, node-fetch.Blob, etc.
*/
export interface BlobLike {
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/size) */
readonly size: number;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/type) */
readonly type: string;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/text) */
text(): Promise<string>;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/slice) */
slice(start?: number, end?: number): BlobLike;
// unfortunately @types/node-fetch@^2.6.4 doesn't type the arrayBuffer method
}
/**
* Intended to match web.File, node.File, node-fetch.File, etc.
*/
export interface FileLike extends BlobLike {
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/lastModified) */
readonly lastModified: number;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/name) */
readonly name: string;
}
/**
* Intended to match web.Response, node.Response, node-fetch.Response, etc.
*/
export interface ResponseLike {
url: string;
blob(): Promise<BlobLike>;
}
export type Uploadable = FileLike | ResponseLike | Readable;
export const isResponseLike = (value: any): value is ResponseLike =>
value != null &&
typeof value === "object" &&
typeof value.url === "string" &&
typeof value.blob === "function";
export const isFileLike = (value: any): value is FileLike =>
value != null &&
typeof value === "object" &&
typeof value.name === "string" &&
typeof value.lastModified === "number" &&
isBlobLike(value);
/**
* The BlobLike type omits arrayBuffer() because @types/node-fetch@^2.6.4 lacks it; but this check
* adds the arrayBuffer() method type because it is available and used at runtime
*/
export const isBlobLike = (
value: any
): value is BlobLike & { arrayBuffer(): Promise<ArrayBuffer> } =>
value != null &&
typeof value === "object" &&
typeof value.size === "number" &&
typeof value.type === "string" &&
typeof value.text === "function" &&
typeof value.slice === "function" &&
typeof value.arrayBuffer === "function";
export const isFsReadStream = (value: any): value is Readable => value instanceof Readable;
export const isUploadable = (value: any): value is Uploadable => {
return isFileLike(value) || isResponseLike(value) || isFsReadStream(value);
};
export type BlobLikePart =
| string
| ArrayBuffer
| ArrayBufferView
| BlobLike
| Uint8Array
| DataView;
export const isRecordLike = (value: any): value is Record<string, string> =>
value != null &&
typeof value === "object" &&
!Array.isArray(value) &&
Object.keys(value).length > 0 &&
Object.keys(value).every((key) => typeof key === "string" && typeof value[key] === "string");
+3
View File
@@ -2,4 +2,7 @@ export type {
ProjectConfig as TriggerConfig,
HandleErrorArgs,
HandleErrorFunction,
ResolveEnvironmentVariablesFunction,
ResolveEnvironmentVariablesParams,
ResolveEnvironmentVariablesResult,
} from "@trigger.dev/core/v3";
+364
View File
@@ -0,0 +1,364 @@
import type {
ImportEnvironmentVariablesParams,
EnvironmentVariableResponseBody,
EnvironmentVariables,
CreateEnvironmentVariableParams,
EnvironmentVariableValue,
UpdateEnvironmentVariableParams,
} from "@trigger.dev/core/v3";
import { SemanticInternalAttributes, apiClientManager, taskContext } from "@trigger.dev/core/v3";
import { apiClientMissingError } from "./shared";
import { tracer } from "./tracer";
export type { ImportEnvironmentVariablesParams, CreateEnvironmentVariableParams };
export async function upload(
projectRef: string,
slug: string,
params: ImportEnvironmentVariablesParams
): Promise<EnvironmentVariableResponseBody>;
export async function upload(
params: ImportEnvironmentVariablesParams
): Promise<EnvironmentVariableResponseBody>;
export async function upload(
projectRefOrParams: string | ImportEnvironmentVariablesParams,
slug?: string,
params?: ImportEnvironmentVariablesParams
): Promise<EnvironmentVariableResponseBody> {
let $projectRef: string;
let $params: ImportEnvironmentVariablesParams;
let $slug: string;
if (taskContext.ctx) {
if (typeof projectRefOrParams === "string") {
$projectRef = projectRefOrParams;
$slug = slug ?? taskContext.ctx.environment.slug;
if (!params) {
throw new Error("params is required");
}
$params = params;
} else {
$params = projectRefOrParams;
$projectRef = taskContext.ctx.project.ref;
$slug = taskContext.ctx.environment.slug;
}
} else {
if (typeof projectRefOrParams !== "string") {
throw new Error("projectRef is required");
}
if (!slug) {
throw new Error("slug is required");
}
if (!params) {
throw new Error("params is required");
}
$projectRef = projectRefOrParams;
$slug = slug;
$params = params;
}
const apiClient = apiClientManager.client;
if (!apiClient) {
throw apiClientMissingError();
}
return await tracer.startActiveSpan(
"envvars.upload",
async (span) => {
return await apiClient.importEnvVars($projectRef, $slug, $params);
},
{
attributes: {
[SemanticInternalAttributes.STYLE_ICON]: "file-upload",
},
}
);
}
export async function list(projectRef: string, slug: string): Promise<EnvironmentVariables>;
export async function list(): Promise<EnvironmentVariables>;
export async function list(projectRef?: string, slug?: string): Promise<EnvironmentVariables> {
const $projectRef = projectRef ?? taskContext.ctx?.project.ref;
const $slug = slug ?? taskContext.ctx?.environment.slug;
if (!$projectRef) {
throw new Error("projectRef is required");
}
if (!$slug) {
throw new Error("slug is required");
}
const apiClient = apiClientManager.client;
if (!apiClient) {
throw apiClientMissingError();
}
return await tracer.startActiveSpan(
"envvars.list",
async (span) => {
return await apiClient.listEnvVars($projectRef, $slug);
},
{
attributes: {
[SemanticInternalAttributes.STYLE_ICON]: "id",
},
}
);
}
export async function create(
projectRef: string,
slug: string,
params: CreateEnvironmentVariableParams
): Promise<EnvironmentVariableResponseBody>;
export async function create(
params: CreateEnvironmentVariableParams
): Promise<EnvironmentVariableResponseBody>;
export async function create(
projectRefOrParams: string | CreateEnvironmentVariableParams,
slug?: string,
params?: CreateEnvironmentVariableParams
): Promise<EnvironmentVariableResponseBody> {
let $projectRef: string;
let $slug: string;
let $params: CreateEnvironmentVariableParams;
if (taskContext.ctx) {
if (typeof projectRefOrParams === "string") {
$projectRef = projectRefOrParams;
$slug = slug ?? taskContext.ctx.environment.slug;
if (!params) {
throw new Error("params is required");
}
$params = params;
} else {
$params = projectRefOrParams;
$projectRef = taskContext.ctx.project.ref;
$slug = taskContext.ctx.environment.slug;
}
} else {
if (typeof projectRefOrParams !== "string") {
throw new Error("projectRef is required");
}
if (!slug) {
throw new Error("slug is required");
}
if (!params) {
throw new Error("params is required");
}
$projectRef = projectRefOrParams;
$slug = slug;
$params = params;
}
const apiClient = apiClientManager.client;
if (!apiClient) {
throw apiClientMissingError();
}
return await tracer.startActiveSpan(
"envvars.create",
async (span) => {
return await apiClient.createEnvVar($projectRef, $slug, $params);
},
{
attributes: {
[SemanticInternalAttributes.STYLE_ICON]: "id",
},
}
);
}
export async function retrieve(
projectRef: string,
slug: string,
name: string
): Promise<EnvironmentVariableValue>;
export async function retrieve(name: string): Promise<EnvironmentVariableValue>;
export async function retrieve(
projectRefOrName: string,
slug?: string,
name?: string
): Promise<EnvironmentVariableValue> {
let $projectRef: string;
let $slug: string;
let $name: string;
if (typeof name === "string") {
$projectRef = projectRefOrName;
$slug = slug!;
$name = name;
} else {
$projectRef = taskContext.ctx?.project.ref!;
$slug = taskContext.ctx?.environment.slug!;
$name = projectRefOrName;
}
if (!$projectRef) {
throw new Error("projectRef is required");
}
if (!$slug) {
throw new Error("slug is required");
}
const apiClient = apiClientManager.client;
if (!apiClient) {
throw apiClientMissingError();
}
return await tracer.startActiveSpan(
"envvars.retrieve",
async (span) => {
return await apiClient.retrieveEnvVar($projectRef, $slug, $name);
},
{
attributes: {
[SemanticInternalAttributes.STYLE_ICON]: "id",
},
}
);
}
export async function del(
projectRef: string,
slug: string,
name: string
): Promise<EnvironmentVariableResponseBody>;
export async function del(name: string): Promise<EnvironmentVariableResponseBody>;
export async function del(
projectRefOrName: string,
slug?: string,
name?: string
): Promise<EnvironmentVariableResponseBody> {
let $projectRef: string;
let $slug: string;
let $name: string;
if (typeof name === "string") {
$projectRef = projectRefOrName;
$slug = slug!;
$name = name;
} else {
$projectRef = taskContext.ctx?.project.ref!;
$slug = taskContext.ctx?.environment.slug!;
$name = projectRefOrName;
}
if (!$projectRef) {
throw new Error("projectRef is required");
}
if (!$slug) {
throw new Error("slug is required");
}
const apiClient = apiClientManager.client;
if (!apiClient) {
throw apiClientMissingError();
}
return await tracer.startActiveSpan(
"envvars.delete",
async (span) => {
return await apiClient.deleteEnvVar($projectRef, $slug, $name);
},
{
attributes: {
[SemanticInternalAttributes.STYLE_ICON]: "id",
},
}
);
}
export async function update(
projectRef: string,
slug: string,
name: string,
params: UpdateEnvironmentVariableParams
): Promise<EnvironmentVariableResponseBody>;
export async function update(
name: string,
params: UpdateEnvironmentVariableParams
): Promise<EnvironmentVariableResponseBody>;
export async function update(
projectRefOrName: string,
slugOrParams: string | UpdateEnvironmentVariableParams,
name?: string,
params?: UpdateEnvironmentVariableParams
): Promise<EnvironmentVariableResponseBody> {
let $projectRef: string;
let $slug: string;
let $name: string;
let $params: UpdateEnvironmentVariableParams;
if (taskContext.ctx) {
if (typeof slugOrParams === "string") {
$projectRef = slugOrParams;
$slug = slugOrParams ?? taskContext.ctx.environment.slug;
$name = name!;
if (!params) {
throw new Error("params is required");
}
$params = params;
} else {
$params = slugOrParams;
$projectRef = taskContext.ctx.project.ref;
$slug = taskContext.ctx.environment.slug;
$name = projectRefOrName;
}
} else {
if (typeof slugOrParams !== "string") {
throw new Error("slug is required");
}
if (!projectRefOrName) {
throw new Error("projectRef is required");
}
if (!params) {
throw new Error("params is required");
}
$projectRef = projectRefOrName;
$slug = slugOrParams;
$name = name!;
$params = params;
}
const apiClient = apiClientManager.client;
if (!apiClient) {
throw apiClientMissingError();
}
return await tracer.startActiveSpan(
"envvars.update",
async (span) => {
return await apiClient.updateEnvVar($projectRef, $slug, $name, $params);
},
{
attributes: {
[SemanticInternalAttributes.STYLE_ICON]: "id",
},
}
);
}
+3 -1
View File
@@ -27,8 +27,10 @@ export {
type LogLevel,
} from "@trigger.dev/core/v3";
export { runs } from "./management";
export { runs } from "./runs";
export * as schedules from "./schedules";
export * as envvars from "./envvars";
export type { ImportEnvironmentVariablesParams } from "./envvars";
/**
* Register the global API client configuration. Alternatively, you can set the `TRIGGER_SECRET_KEY` and `TRIGGER_API_URL` environment variables.
+137
View File
@@ -1748,6 +1748,9 @@ importers:
'@opentelemetry/semantic-conventions':
specifier: ^1.22.0
version: 1.22.0
form-data-encoder:
specifier: ^4.0.2
version: 4.0.2
humanize-duration:
specifier: ^3.27.3
version: 3.27.3
@@ -3065,6 +3068,9 @@ importers:
'@ffprobe-installer/ffprobe':
specifier: ^2.1.2
version: 2.1.2
'@infisical/sdk':
specifier: ^2.1.9
version: 2.1.9
'@opentelemetry/api':
specifier: 1.4.1
version: 1.4.1
@@ -7828,6 +7834,132 @@ packages:
/@humanwhocodes/object-schema@1.2.1:
resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==}
/@infisical/sdk-android-arm-eabi@2.1.9:
resolution: {integrity: sha512-tx0y9efMOmqxdw152BjAxpDcKhlURKftg20aAGUwHxgHaHtqJ1jHaZZ1B3Eo019NB+tDc/ynU1TpjIRKh28kxw==}
engines: {node: '>= 10'}
cpu: [arm]
os: [android]
requiresBuild: true
dev: false
optional: true
/@infisical/sdk-android-arm64@2.1.9:
resolution: {integrity: sha512-kheXrht4rvEb6F7t9Xu821Pdm6ddzvB51NZjSiyIRwRDqv63HJEYr+MEd7ynIgux0JuMPQZIxmsyRZ5p/NDuFw==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [android]
requiresBuild: true
dev: false
optional: true
/@infisical/sdk-darwin-arm64@2.1.9:
resolution: {integrity: sha512-qjsg67AKZYpjqG2DzJ/OAr+9thDT0YJg49ULknoBO+yaa6fT7PFRy682yRqnbJjg7vxRObXAyWcXSYjls+D4Lw==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [darwin]
requiresBuild: true
dev: false
optional: true
/@infisical/sdk-darwin-x64@2.1.9:
resolution: {integrity: sha512-ErvwcR095VFIzCrXFyOCuED1jWltpX7S1et6WySdTsA811hLp6Txy/S57YNeGLJu/birVwrJdYbksVWdd+bqkQ==}
engines: {node: '>= 10'}
cpu: [x64]
os: [darwin]
requiresBuild: true
dev: false
optional: true
/@infisical/sdk-linux-arm-gnueabihf@2.1.9:
resolution: {integrity: sha512-2w0EVQc1Xq7ZE6nDsMwBVkaUwTuL0DIdwWdqxVZjo8jn26jwlhSOStxtbrk/cnhtWMQysQ/xKUI+0zZ/T32T3g==}
engines: {node: '>= 10'}
cpu: [arm]
os: [linux]
requiresBuild: true
dev: false
optional: true
/@infisical/sdk-linux-arm64-gnu@2.1.9:
resolution: {integrity: sha512-5DMG5CJxX/wlWDyG3+cy1UkZAYuqt1OOEWwx3GNLU656RQ47AzwDFkvyiT9lalIihPHJFDN55hhSb002ru/zPw==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: false
optional: true
/@infisical/sdk-linux-arm64-musl@2.1.9:
resolution: {integrity: sha512-61oxqGh9Ih/d7igtduKS2ZjkZ4NU7PjcNDgK8IUwP6kruTQdOEAiNyAWChDx75zvJExvztjdpeczj62SHxmsRw==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: false
optional: true
/@infisical/sdk-linux-x64-gnu@2.1.9:
resolution: {integrity: sha512-X38Z4Q+GT0QU8nbRuWl3zyBv06Rfz1aWpewopxL3jahbKGoV8DaITDIaagVhwFl6YV5mMOLzXis5XrIMBJ6tLw==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
requiresBuild: true
dev: false
optional: true
/@infisical/sdk-linux-x64-musl@2.1.9:
resolution: {integrity: sha512-8RuL3MNSr2G/Xo79kdKHddxlprVOn4VmCbNG7K2VoY/ytUCGy8bCp5BrkdZOIr1jGm7GlD8eq1dXQkHa3Rn0og==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
requiresBuild: true
dev: false
optional: true
/@infisical/sdk-win32-arm64-msvc@2.1.9:
resolution: {integrity: sha512-IUnFDIUFXFRUJ0B/wM32xGWu6Xz4w7GAw+75g9AYv1Ol+LtpbzoDcToh0IUFPsNpvIaOUZn1zxUUWsteWlAy3Q==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [win32]
requiresBuild: true
dev: false
optional: true
/@infisical/sdk-win32-ia32-msvc@2.1.9:
resolution: {integrity: sha512-pDMpuP6X3GTSjzeahvCAscA0GBEkpMic0rwY+FdAmDcAX5SKoZ1pbRvcrMpAd4HMxfyLGeISuvzZ6pdMAowG5g==}
engines: {node: '>= 10'}
cpu: [ia32]
os: [win32]
requiresBuild: true
dev: false
optional: true
/@infisical/sdk-win32-x64-msvc@2.1.9:
resolution: {integrity: sha512-nrIfVPZPTJX85+8PZmZoA/Sk8rKpORzsYUTZ3zyqdfKAzQCpl587JXWCa5hud0ZWRw8SZuBYY06f9b75N+enyA==}
engines: {node: '>= 10'}
cpu: [x64]
os: [win32]
requiresBuild: true
dev: false
optional: true
/@infisical/sdk@2.1.9:
resolution: {integrity: sha512-Ds6W4huyjiEnDisf2UJyVugTpCapjHB4RvL69hSCi/raH7NKITff1R+ifWktQJ1cx7hFAE9wi+U0vRrW+WhUMA==}
engines: {node: '>= 10'}
optionalDependencies:
'@infisical/sdk-android-arm-eabi': 2.1.9
'@infisical/sdk-android-arm64': 2.1.9
'@infisical/sdk-darwin-arm64': 2.1.9
'@infisical/sdk-darwin-x64': 2.1.9
'@infisical/sdk-linux-arm-gnueabihf': 2.1.9
'@infisical/sdk-linux-arm64-gnu': 2.1.9
'@infisical/sdk-linux-arm64-musl': 2.1.9
'@infisical/sdk-linux-x64-gnu': 2.1.9
'@infisical/sdk-linux-x64-musl': 2.1.9
'@infisical/sdk-win32-arm64-msvc': 2.1.9
'@infisical/sdk-win32-ia32-msvc': 2.1.9
'@infisical/sdk-win32-x64-msvc': 2.1.9
dev: false
/@inquirer/confirm@3.0.0:
resolution: {integrity: sha512-LHeuYP1D8NmQra1eR4UqvZMXwxEdDXyElJmmZfU44xdNLL6+GcQBS0uE16vyfZVjH8c22p9e+DStROfE/hyHrg==}
engines: {node: '>=18'}
@@ -22728,6 +22860,11 @@ packages:
engines: {node: '>= 14.17'}
dev: false
/form-data-encoder@4.0.2:
resolution: {integrity: sha512-KQVhvhK8ZkWzxKxOr56CPulAhH3dobtuQ4+hNQ+HekH/Wp5gSOafqRAeTphQUJAIk0GBvHZgJ2ZGRWd5kphMuw==}
engines: {node: '>= 18'}
dev: false
/form-data@2.3.3:
resolution: {integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==}
engines: {node: '>= 0.12'}
+2
View File
@@ -0,0 +1,2 @@
THIS_IS_MY_KEY=1234567890
ANOTHER_KEY=0987654321
+1
View File
@@ -9,6 +9,7 @@
"dependencies": {
"@ffmpeg-installer/ffmpeg": "^1.1.0",
"@ffprobe-installer/ffprobe": "^2.1.2",
"@infisical/sdk": "^2.1.9",
"@opentelemetry/api": "1.4.1",
"@react-email/components": "^0.0.17",
"@react-email/render": "^0.0.7",
+85 -2
View File
@@ -1,11 +1,93 @@
import { tracer } from "./tracer";
import { APIError, configure, runs, schedules } from "@trigger.dev/sdk/v3";
import { APIError, configure, runs, schedules, envvars } from "@trigger.dev/sdk/v3";
import { simpleChildTask } from "./trigger/subtasks";
import dotenv from "dotenv";
import { firstScheduledTask } from "./trigger/scheduled";
import { createReadStream } from "node:fs";
dotenv.config();
async function uploadEnvVars() {
configure({
secretKey: process.env.TRIGGER_ACCESS_TOKEN,
});
const response1 = await envvars.upload("yubjwjsfkxnylobaqvqz", "dev", {
variables: {
MY_ENV_VAR: "MY_ENV_VAR_VALUE",
},
override: true,
});
console.log("response1", response1);
const envVars = await envvars.list("yubjwjsfkxnylobaqvqz", "dev");
console.log("envVars", envVars);
const createResponse = await envvars.create("yubjwjsfkxnylobaqvqz", "dev", {
name: "MY_ENV_VAR_CREATE",
value: "MY_ENV_VAR_VALUE_CREATE",
});
console.log("createResponse", createResponse);
const retrieveResponse = await envvars.retrieve(
"yubjwjsfkxnylobaqvqz",
"dev",
"MY_ENV_VAR_CREATE"
);
console.log("retrieveResponse", retrieveResponse);
const updateResponse = await envvars.update("yubjwjsfkxnylobaqvqz", "dev", "MY_ENV_VAR_CREATE", {
value: "MY_ENV_VAR_VALUE_CREATE_UPDATED",
});
console.log("updateResponse", updateResponse);
const deleteResponse = await envvars.del("yubjwjsfkxnylobaqvqz", "dev", "MY_ENV_VAR_CREATE");
console.log("deleteResponse", deleteResponse);
const response2 = await envvars.upload("yubjwjsfkxnylobaqvqz", "dev", {
variables: createReadStream(".uploadable-env"),
override: true,
});
console.log("response2", response2);
const response3 = await envvars.upload("yubjwjsfkxnylobaqvqz", "prod", {
variables: createReadStream(".uploadable-env"),
override: true,
});
console.log("response3", response3);
const response4 = await envvars.upload("yubjwjsfkxnylobaqvqz", "prod", {
variables: await fetch(
"https://gist.githubusercontent.com/ericallam/7a1001c6b03986a74d0f8aad4fd890aa/raw/fe2bc4da82f3b17178d47f58ec1458af47af5035/.env"
),
override: true,
});
console.log("response4", response4);
const response5 = await envvars.upload("yubjwjsfkxnylobaqvqz", "prod", {
variables: new File(["IM_A_FILE=GREAT_FOR_YOU"], ".env"),
override: true,
});
console.log("response5", response5);
const response6 = await envvars.upload("yubjwjsfkxnylobaqvqz", "prod", {
variables: Buffer.from("IN_BUFFER=TRUE"),
override: true,
});
console.log("response6", response6);
}
export async function run() {
await tracer.startActiveSpan("run", async (span) => {
try {
@@ -88,4 +170,5 @@ export async function run() {
});
}
run();
// run();
uploadEnvVars().catch(console.error);
+14 -2
View File
@@ -1,5 +1,5 @@
import "server-only";
import { logger, task, wait } from "@trigger.dev/sdk/v3";
import { envvars, logger, task, wait } from "@trigger.dev/sdk/v3";
import { traceAsync } from "@/telemetry";
export const simplestTask = task({
@@ -10,7 +10,7 @@ export const simplestTask = task({
body: JSON.stringify({
hello: "world",
taskId: "fetch-post-task",
foo: "barrrrrrrrrrrrrrrrrrrr",
foo: "barrrrrrrrrrrrrrrrrrrrrr",
}),
});
@@ -31,6 +31,18 @@ export const taskWithSpecialCharacters = task({
},
});
export const updateEnvVars = task({
id: "update-env-vars",
run: async () => {
return await envvars.upload({
variables: await fetch(
"https://gist.githubusercontent.com/ericallam/7a1001c6b03986a74d0f8aad4fd890aa/raw/fe2bc4da82f3b17178d47f58ec1458af47af5035/.env"
),
override: true,
});
},
});
export const createJsonHeroDoc = task({
id: "create-jsonhero-doc",
run: async (payload: { title: string; content: any }, { ctx }) => {
+25 -1
View File
@@ -1,9 +1,33 @@
import type { TriggerConfig } from "@trigger.dev/sdk/v3";
import type { TriggerConfig, ResolveEnvironmentVariablesFunction } from "@trigger.dev/sdk/v3";
import { OpenAIInstrumentation } from "@traceloop/instrumentation-openai";
import { AppDataSource } from "@/trigger/orm";
import { InfisicalClient } from "@infisical/sdk";
export { handleError } from "./src/handleError";
export const resolveEnvVars: ResolveEnvironmentVariablesFunction = async ({
projectRef,
env,
environment,
}) => {
const client = new InfisicalClient({
clientId: env.INFISICAL_CLIENT_ID,
clientSecret: env.INFISICAL_CLIENT_SECRET,
});
const secrets = await client.listSecrets({
environment,
projectId: env.INFISICAL_PROJECT_ID!,
});
return {
variables: secrets.map((secret) => ({
name: secret.secretKey,
value: secret.secretValue,
})),
};
};
export const config: TriggerConfig = {
project: "yubjwjsfkxnylobaqvqz",
retries: {