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:
@@ -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
|
||||
Vendored
+8
@@ -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) => {
|
||||
|
||||
+12
-5
@@ -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>
|
||||
}
|
||||
|
||||
+10
-2
@@ -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 })));
|
||||
}
|
||||
@@ -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
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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": []
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Create Env Var"
|
||||
openapi: "v3-openapi POST /api/v1/projects/{projectRef}/envvars/{env}"
|
||||
---
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Delete Env Var"
|
||||
openapi: "v3-openapi DELETE /api/v1/projects/{projectRef}/envvars/{env}/{name}"
|
||||
---
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Import Env Vars"
|
||||
openapi: "v3-openapi POST /api/v1/projects/{projectRef}/envvars/{env}/import"
|
||||
---
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "List Env Vars"
|
||||
openapi: "v3-openapi GET /api/v1/projects/{projectRef}/envvars/{env}"
|
||||
---
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Retrieve Env Var"
|
||||
openapi: "v3-openapi GET /api/v1/projects/{projectRef}/envvars/{env}/{name}"
|
||||
---
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Update Env Var"
|
||||
openapi: "v3-openapi PUT /api/v1/projects/{projectRef}/envvars/{env}/{name}"
|
||||
---
|
||||
@@ -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");
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -2,4 +2,7 @@ export type {
|
||||
ProjectConfig as TriggerConfig,
|
||||
HandleErrorArgs,
|
||||
HandleErrorFunction,
|
||||
ResolveEnvironmentVariablesFunction,
|
||||
ResolveEnvironmentVariablesParams,
|
||||
ResolveEnvironmentVariablesResult,
|
||||
} from "@trigger.dev/core/v3";
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
Generated
+137
@@ -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'}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
THIS_IS_MY_KEY=1234567890
|
||||
ANOTHER_KEY=0987654321
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 }) => {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
Reference in New Issue
Block a user