pythonExtension and python runtime improvements (#1734)

* pythonExtension and python runtime improvements

* Adding streaming support

* Use writeFileSync

* Restructure extension docs and add python extension docs

* Fix broken link

* Update docs/config/extensions/overview.mdx

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update docs/config/extensions/aptGet.mdx

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update docs/config/extensions/custom.mdx

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Add environment variable support

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
This commit is contained in:
Eric Allam
2025-03-04 12:30:26 +00:00
committed by GitHub
parent bbd82adbda
commit 8f3a28effb
52 changed files with 2064 additions and 1081 deletions
+9 -452
View File
@@ -315,479 +315,36 @@ Build extension allow you to hook into the build system and customize the build
#### additionalFiles
Import the `additionalFiles` build extension and use it in your `trigger.config.ts` file:
```ts
import { defineConfig } from "@trigger.dev/sdk/v3";
import { additionalFiles } from "@trigger.dev/build/extensions/core";
export default defineConfig({
project: "<project ref>",
// Your other config settings...
build: {
extensions: [
additionalFiles({ files: ["wrangler/wrangler.toml", "./assets/**", "./fonts/**"] }),
],
},
});
```
This will copy the files specified in the `files` array to the build directory. The `files` array can contain globs. The output paths will match the path of the file, relative to the root of the project.
<Note>The root of the project is the directory that contains the trigger.config.ts file</Note>
See the [additionalFiles documentation](/config/extensions/additionalFiles) for more information.
#### `additionalPackages`
Import the `additionalPackages` build extension and use it in your `trigger.config.ts` file:
```ts
import { defineConfig } from "@trigger.dev/sdk/v3";
import { additionalPackages } from "@trigger.dev/build/extensions/core";
export default defineConfig({
project: "<project ref>",
// Your other config settings...
build: {
extensions: [additionalPackages({ packages: ["wrangler"] })],
},
});
```
This allows you to include additional packages in the build that are not automatically included via imports. This is useful if you want to install a package that includes a CLI tool that you want to invoke in your tasks via `exec`. We will try to automatically resolve the version of the package but you can specify the version by using the `@` symbol:
```ts
import { defineConfig } from "@trigger.dev/sdk/v3";
export default defineConfig({
project: "<project ref>",
// Your other config settings...
build: {
extensions: [additionalPackages({ packages: ["wrangler@1.19.0"] })],
},
});
```
See the [additionalPackages documentation](/config/extensions/additionalPackages) for more information.
#### `emitDecoratorMetadata`
If you need support for the `emitDecoratorMetadata` typescript compiler option, import the `emitDecoratorMetadata` build extension and use it in your `trigger.config.ts` file:
```ts
import { defineConfig } from "@trigger.dev/sdk/v3";
import { emitDecoratorMetadata } from "@trigger.dev/build/extensions/typescript";
export default defineConfig({
project: "<project ref>",
// Your other config settings...
build: {
extensions: [emitDecoratorMetadata()],
},
});
```
This is usually required if you are using certain ORMs, like TypeORM, that require this option to be enabled. It's not enabled by default because there is a performance cost to enabling it.
<Note>
emitDecoratorMetadata works by hooking into the esbuild bundle process and using the TypeScript
compiler API to compile files where we detect the use of decorators. This means you must have
`emitDecoratorMetadata` enabled in your `tsconfig.json` file, as well as `typescript` installed in
your `devDependencies`.
</Note>
See the [emitDecoratorMetadata documentation](/config/extensions/emitDecoratorMetadata) for more information.
#### Prisma
If you are using Prisma, you should use the prisma build extension.
- Automatically handles copying Prisma files to the build directory
- Generates the Prisma client during the deploy process
- Optionally will migrate the database during the deploy process
- Support for TypedSQL and multiple schema files
- You can use `prismaSchemaFolder` to specify just the directory containing your schema file, instead of the full path
- You can add the extension twice if you have multiple separate schemas in the same project (example below)
You can use it for a simple Prisma setup like this:
```ts
import { defineConfig } from "@trigger.dev/sdk/v3";
import { prismaExtension } from "@trigger.dev/build/extensions/prisma";
export default defineConfig({
project: "<project ref>",
// Your other config settings...
build: {
extensions: [
prismaExtension({
version: "5.19.0", // optional, we'll automatically detect the version if not provided
schema: "prisma/schema.prisma",
}),
],
},
});
```
<Note>
This does not have any effect when running the `dev` command, only when running the `deploy`
command.
</Note>
If you want to also run migrations during the build process, you can pass in the `migrate` option:
```ts
import { defineConfig } from "@trigger.dev/sdk/v3";
import { prismaExtension } from "@trigger.dev/build/extensions/prisma";
export default defineConfig({
project: "<project ref>",
// Your other config settings...
build: {
extensions: [
prismaExtension({
schema: "prisma/schema.prisma",
migrate: true,
directUrlEnvVarName: "DATABASE_URL_UNPOOLED", // optional - the name of the environment variable that contains the direct database URL if you are using a direct database URL
}),
],
},
});
```
If you have multiple `generator` statements defined in your schema file, you can pass in the `clientGenerator` option to specify the `prisma-client-js` generator, which will prevent other generators from being generated. Some examples where you may need to do this include when using the `prisma-kysely` or `prisma-json-types-generator` generators.
<CodeGroup>
```prisma schema.prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
directUrl = env("DATABASE_URL_UNPOOLED")
}
// We only want to generate the prisma-client-js generator
generator client {
provider = "prisma-client-js"
}
generator kysely {
provider = "prisma-kysely"
output = "../../src/kysely"
enumFileName = "enums.ts"
fileName = "types.ts"
}
generator json {
provider = "prisma-json-types-generator"
}
```
```ts trigger.config.ts
import { defineConfig } from "@trigger.dev/sdk/v3";
import { prismaExtension } from "@trigger.dev/build/extensions/prisma";
export default defineConfig({
project: "<project ref>",
// Your other config settings...
build: {
extensions: [
prismaExtension({
schema: "prisma/schema.prisma",
clientGenerator: "client",
}),
],
},
});
```
</CodeGroup>
If you are using [TypedSQL](https://www.prisma.io/typedsql), you'll need to enable it via the `typedSql` option:
```ts
import { defineConfig } from "@trigger.dev/sdk/v3";
export default defineConfig({
project: "<project ref>",
// Your other config settings...
build: {
extensions: [
prismaExtension({
schema: "prisma/schema.prisma",
typedSql: true,
}),
],
},
});
```
<Note>
The `prismaExtension` will inject the `DATABASE_URL` environment variable into the build process. Learn more about setting environment variables for deploying in our [Environment Variables](/deploy-environment-variables) guide.
These environment variables are only used during the build process and are not embedded in the final container image.
</Note>
If you have multiple separate schemas in the same project you can add the extension multiple times:
```ts
prismaExtension({
schema: 'prisma/schema/main.prisma',
version: '6.2.0',
migrate: false,
}),
prismaExtension({
schema: 'prisma/schema/secondary.prisma',
version: '6.2.0',
migrate: false,
}),
```
See the [prismaExtension documentation](/config/extensions/prismaExtension) for more information.
#### syncEnvVars
The `syncEnvVars` build extension replaces the deprecated `resolveEnvVars` export. Check out our [syncEnvVars documentation](/deploy-environment-variables#sync-env-vars-from-another-service) for more information.
```ts
import { syncEnvVars } from "@trigger.dev/build/extensions/core";
export default defineConfig({
project: "<project ref>",
// Your other config settings...
build: {
extensions: [syncEnvVars()],
},
});
```
#### syncVercelEnvVars
The `syncVercelEnvVars` build extension syncs environment variables from your Vercel project to Trigger.dev.
<Note>
You need to set the `VERCEL_ACCESS_TOKEN` and `VERCEL_PROJECT_ID` environment variables, or pass
in the token and project ID as arguments to the `syncVercelEnvVars` build extension. If you're
working with a team project, you'll also need to set `VERCEL_TEAM_ID`, which can be found in your
team settings. You can find / generate the `VERCEL_ACCESS_TOKEN` in your Vercel
[dashboard](https://vercel.com/account/settings/tokens). Make sure the scope of the token covers
the project with the environment variables you want to sync.
</Note>
```ts
import { defineConfig } from "@trigger.dev/sdk/v3";
import { syncVercelEnvVars } from "@trigger.dev/build/extensions/core";
export default defineConfig({
project: "<project ref>",
// Your other config settings...
build: {
extensions: [syncVercelEnvVars()],
},
});
```
#### audioWaveform
Previously, we installed [Audio Waveform](https://github.com/bbc/audiowaveform) in the build image. That's been moved to a build extension:
```ts
import { defineConfig } from "@trigger.dev/sdk/v3";
import { audioWaveform } from "@trigger.dev/build/extensions/audioWaveform";
export default defineConfig({
project: "<project ref>",
// Your other config settings...
build: {
extensions: [audioWaveform()], // uses verson 1.1.0 of audiowaveform by default
},
});
```
See the [syncEnvVars documentation](/config/extensions/syncEnvVars) for more information.
#### puppeteer
<ScrapingWarning />
To use Puppeteer in your project, add these build settings to your `trigger.config.ts` file:
```ts trigger.config.ts
import { defineConfig } from "@trigger.dev/sdk/v3";
import { puppeteer } from "@trigger.dev/build/extensions/puppeteer";
export default defineConfig({
project: "<project ref>",
// Your other config settings...
build: {
extensions: [puppeteer()],
},
});
```
And add the following environment variable in your Trigger.dev dashboard on the Environment Variables page:
```bash
PUPPETEER_EXECUTABLE_PATH: "/usr/bin/google-chrome-stable",
```
Follow [this example](/guides/examples/puppeteer) to get setup with Trigger.dev and Puppeteer in your project.
See the [puppeteer documentation](/config/extensions/puppeteer) for more information.
#### ffmpeg
You can add the `ffmpeg` build extension to your build process:
```ts
import { defineConfig } from "@trigger.dev/sdk/v3";
import { ffmpeg } from "@trigger.dev/build/extensions/core";
export default defineConfig({
project: "<project ref>",
// Your other config settings...
build: {
extensions: [ffmpeg()],
},
});
```
By default, this will install the version of `ffmpeg` that is available in the Debian package manager. If you need a specific version, you can pass in the version as an argument:
```ts
import { defineConfig } from "@trigger.dev/sdk/v3";
import { ffmpeg } from "@trigger.dev/build/extensions/core";
export default defineConfig({
project: "<project ref>",
// Your other config settings...
build: {
extensions: [ffmpeg({ version: "6.0-4" })],
},
});
```
This extension will also add the `FFMPEG_PATH` and `FFPROBE_PATH` to your environment variables, making it easy to use popular ffmpeg libraries like `fluent-ffmpeg`.
Note that `fluent-ffmpeg` needs to be added to [`external`](/config/config-file#external) in your `trigger.config.ts` file.
Follow [this example](/guides/examples/ffmpeg-video-processing) to get setup with Trigger.dev and FFmpeg in your project.
See the [ffmpeg documentation](/config/extensions/ffmpeg) for more information.
#### esbuild plugins
You can easily add existing or custom esbuild plugins to your build process using the `esbuildPlugin` extension:
```ts
import { defineConfig } from "@trigger.dev/sdk/v3";
import { esbuildPlugin } from "@trigger.dev/build/extensions";
import { sentryEsbuildPlugin } from "@sentry/esbuild-plugin";
export default defineConfig({
project: "<project ref>",
// Your other config settings...
build: {
extensions: [
esbuildPlugin(
sentryEsbuildPlugin({
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_PROJECT,
authToken: process.env.SENTRY_AUTH_TOKEN,
}),
// optional - only runs during the deploy command, and adds the plugin to the end of the list of plugins
{ placement: "last", target: "deploy" }
),
],
},
});
```
See the [esbuild plugins documentation](/config/extensions/esbuildPlugin) for more information.
#### aptGet
You can install system packages into the deployed image using using the `aptGet` extension:
```ts
import { defineConfig } from "@trigger.dev/sdk/v3";
import { aptGet } from "@trigger.dev/build/extensions/core";
export default defineConfig({
project: "<project ref>",
// Your other config settings...
build: {
extensions: [aptGet({ packages: ["ffmpeg"] })],
},
});
```
If you want to install a specific version of a package, you can specify the version like this:
```ts
import { defineConfig } from "@trigger.dev/sdk/v3";
export default defineConfig({
project: "<project ref>",
// Your other config settings...
build: {
extensions: [aptGet({ packages: ["ffmpeg=6.0-4"] })],
},
});
```
#### Custom extensions
You can create your own extensions to further customize the build process. Extensions are an object with a `name` and zero or more lifecycle hooks (`onBuildStart` and `onBuildComplete`) that allow you to modify the `BuildContext` object that is passed to the build process through adding layers. For example, this is how the `aptGet` extension is implemented:
```ts
import { BuildExtension } from "@trigger.dev/core/v3/build";
export type AptGetOptions = {
packages: string[];
};
export function aptGet(options: AptGetOptions): BuildExtension {
return {
name: "aptGet",
onBuildComplete(context) {
if (context.target === "dev") {
return;
}
context.logger.debug("Adding apt-get layer", {
pkgs: options.packages,
});
context.addLayer({
id: "apt-get",
image: {
pkgs: options.packages,
},
});
},
};
}
```
Instead of creating this function and worrying about types, you can define an extension inline in your `trigger.config.ts` file:
```ts trigger.config.ts
import { defineConfig } from "@trigger.dev/sdk/v3";
export default defineConfig({
project: "<project ref>",
// Your other config settings...
build: {
extensions: [
{
name: "aptGet",
onBuildComplete(context) {
if (context.target === "dev") {
return;
}
context.logger.debug("Adding apt-get layer", {
pkgs: ["ffmpeg"],
});
context.addLayer({
id: "apt-get",
image: {
pkgs: ["ffmpeg"],
},
});
},
},
],
},
});
```
We'll be expanding the documentation on how to create custom extensions in the future, but for now you are encouraged to look at the existing extensions in the `@trigger.dev/build` package for inspiration, which you can see in our repo [here](https://github.com/triggerdotdev/trigger.dev/tree/main/packages/build/src/extensions)
See the [aptGet documentation](/config/extensions/aptGet) for more information.
@@ -0,0 +1,28 @@
---
title: "Additional Files"
sidebarTitle: "additionalFiles"
description: "Use the additionalFiles build extension to copy additional files to the build directory"
---
Import the `additionalFiles` build extension and use it in your `trigger.config.ts` file:
```ts
import { defineConfig } from "@trigger.dev/sdk/v3";
import { additionalFiles } from "@trigger.dev/build/extensions/core";
export default defineConfig({
project: "<project ref>",
// Your other config settings...
build: {
extensions: [
additionalFiles({ files: ["wrangler/wrangler.toml", "./assets/**", "./fonts/**"] }),
],
},
});
```
This will copy the files specified in the `files` array to the build directory. The `files` array can contain globs. The output paths will match the path of the file, relative to the root of the project.
This extension effects both the `dev` and the `deploy` commands, and the resulting paths will be the same for both.
<Note>The root of the project is the directory that contains the trigger.config.ts file</Note>
@@ -0,0 +1,36 @@
---
title: "Additional Packages"
sidebarTitle: "additionalPackages"
description: "Use the additionalPackages build extension to include additional packages in the build"
---
Import the `additionalPackages` build extension and use it in your `trigger.config.ts` file:
```ts
import { defineConfig } from "@trigger.dev/sdk/v3";
import { additionalPackages } from "@trigger.dev/build/extensions/core";
export default defineConfig({
project: "<project ref>",
// Your other config settings...
build: {
extensions: [additionalPackages({ packages: ["wrangler"] })],
},
});
```
This allows you to include additional packages in the build that are not automatically included via imports. This is useful if you want to install a package that includes a CLI tool that you want to invoke in your tasks via `exec`. We will try to automatically resolve the version of the package but you can specify the version by using the `@` symbol:
```ts
import { defineConfig } from "@trigger.dev/sdk/v3";
export default defineConfig({
project: "<project ref>",
// Your other config settings...
build: {
extensions: [additionalPackages({ packages: ["wrangler@1.19.0"] })],
},
});
```
This extension does not do anything in `dev` mode, but it will install the packages in the build directory when you run `deploy`. The packages will be installed in the `node_modules` directory in the build directory.
+34
View File
@@ -0,0 +1,34 @@
---
title: "apt-get"
sidebarTitle: "aptGet"
description: "Use the aptGet build extension to install system packages into the deployed image"
---
You can install system packages into the deployed image using the `aptGet` extension:
```ts
import { defineConfig } from "@trigger.dev/sdk/v3";
import { aptGet } from "@trigger.dev/build/extensions/core";
export default defineConfig({
project: "<project ref>",
// Your other config settings...
build: {
extensions: [aptGet({ packages: ["ffmpeg"] })],
},
});
```
If you want to install a specific version of a package, you can specify the version like this:
```ts
import { defineConfig } from "@trigger.dev/sdk/v3";
export default defineConfig({
project: "<project ref>",
// Your other config settings...
build: {
extensions: [aptGet({ packages: ["ffmpeg=6.0-4"] })],
},
});
```
+20
View File
@@ -0,0 +1,20 @@
---
title: "Audio Waveform"
sidebarTitle: "audioWaveform"
description: "Use the audioWaveform build extension to add support for Audio Waveform in your project"
---
Previously, we installed [Audio Waveform](https://github.com/bbc/audiowaveform) in the build image. That's been moved to a build extension:
```ts
import { defineConfig } from "@trigger.dev/sdk/v3";
import { audioWaveform } from "@trigger.dev/build/extensions/audioWaveform";
export default defineConfig({
project: "<project ref>",
// Your other config settings...
build: {
extensions: [audioWaveform()], // uses verson 1.1.0 of audiowaveform by default
},
});
```
+380
View File
@@ -0,0 +1,380 @@
---
title: "Custom build extensions"
sidebarTitle: "Custom"
description: "Customize how your project is built and deployed to Trigger.dev with your own custom build extensions"
---
Build extensions allow you to hook into the build system and customize the build process or the resulting bundle and container image (in the case of deploying). See our [build extension overview](/config/extensions/overview) for more information on how to install and use our built-in extensions. Build extensions can do the following:
- Add additional files to the build
- Add dependencies to the list of externals
- Add esbuild plugins
- Add additional npm dependencies
- Add additional system packages to the image build container
- Add commands to run in the image build container
- Add environment variables to the image build container
- Sync environment variables to your Trigger.dev project
## Creating a build extension
Build extensions are added to your `trigger.config.ts` file, with a required `name` and optional build hook functions. Here's a simple example of a build extension that just logs a message when the build starts:
```ts
import { defineConfig } from "@trigger.dev/sdk/v3";
export default defineConfig({
project: "my-project",
build: {
extensions: [
{
name: "my-extension",
onBuildStart: async (context) => {
console.log("Build starting!");
},
},
],
},
});
```
You can also extract that out into a function instead of defining it inline, in which case you will need to import the `BuildExtension` type from the `@trigger.dev/build` package:
<Note>
You'll need to add the `@trigger.dev/build` package to your `devDependencies` before the below
code will work. Make sure it's version matches that of the installed `@trigger.dev/sdk` package.
</Note>
```ts
import { defineConfig } from "@trigger.dev/sdk/v3";
import { BuildExtension } from "@trigger.dev/build";
export default defineConfig({
project: "my-project",
build: {
extensions: [myExtension()],
},
});
function myExtension(): BuildExtension {
return {
name: "my-extension",
onBuildStart: async (context) => {
console.log("Build starting!");
},
};
}
```
## Build hooks
### externalsForTarget
This allows the extension to add additional dependencies to the list of externals for the build. This is useful for dependencies that are not included in the bundle, but are expected to be available at runtime.
```ts
import { defineConfig } from "@trigger.dev/sdk/v3";
export default defineConfig({
project: "my-project",
build: {
extensions: [
{
name: "my-extension",
externalsForTarget: async (target) => {
return ["my-dependency"];
},
},
],
},
});
```
### onBuildStart
This hook runs before the build starts. It receives the `BuildContext` object as an argument.
```ts
import { defineConfig } from "@trigger.dev/sdk/v3";
export default defineConfig({
project: "my-project",
build: {
extensions: [
{
name: "my-extension",
onBuildStart: async (context) => {
console.log("Build starting!");
},
},
],
},
});
```
If you want to add an esbuild plugin, you must do so in the `onBuildStart` hook. Here's an example of adding a custom esbuild plugin:
```ts
import { defineConfig } from "@trigger.dev/sdk/v3";
export default defineConfig({
project: "my-project",
build: {
extensions: [
{
name: "my-extension",
onBuildStart: async (context) => {
context.registerPlugin({
name: "my-plugin",
setup(build) {
build.onLoad({ filter: /.*/, namespace: "file" }, async (args) => {
return {
contents: "console.log('Hello, world!')",
loader: "js",
};
});
},
});
},
},
],
},
});
```
You can use the `BuildContext.target` property to determine if the build is for `dev` or `deploy`:
```ts
import { defineConfig } from "@trigger.dev/sdk/v3";
export default defineConfig({
project: "my-project",
build: {
extensions: [
{
name: "my-extension",
onBuildStart: async (context) => {
if (context.target === "dev") {
console.log("Building for dev");
} else {
console.log("Building for deploy");
}
},
},
],
},
});
```
### onBuildComplete
This hook runs after the build completes. It receives the `BuildContext` object and a `BuildManifest` object as arguments. This is where you can add in one or more `BuildLayer`'s to the context.
```ts
import { defineConfig } from "@trigger.dev/sdk/v3";
export default defineConfig({
project: "my-project",
build: {
extensions: [
{
name: "my-extension",
onBuildComplete: async (context, manifest) => {
context.addLayer({
id: "more-dependencies",
dependencies,
});
},
},
],
},
});
```
See the [addLayer](#addlayer) documentation for more information on how to use `addLayer`.
## BuildTarget
Can either be `dev` or `deploy`, matching the CLI command name that is being run.
```sh
npx trigger.dev@latest dev # BuildTarget is "dev"
npx trigger.dev@latest deploy # BuildTarget is "deploy"
```
## BuildContext
### addLayer()
<ParamField path="layer" type="BuildLayer">
The layer to add to the build context. See the [BuildLayer](#buildlayer) documentation for more
information.
</ParamField>
### registerPlugin()
<ParamField path="plugin" type="esbuild.Plugin" required>
The esbuild plugin to register.
</ParamField>
<ParamField path="options" type="object">
<Expandable title="properties">
<ResponseField name="target" type="BuildTarget">
An optional target to register the plugin for. If not provided, the plugin will be registered
for all targets.
</ResponseField>
<ResponseField name="placement" type="first | last">
An optional placement for the plugin. If not provided, the plugin will be registered in place.
This allows you to control the order of plugins.
</ResponseField>
</Expandable>
</ParamField>
### resolvePath()
Resolves a path relative to the project's working directory.
<ParamField path="path" type="string">
The path to resolve.
</ParamField>
```ts
const resolvedPath = context.resolvePath("my-other-dependency");
```
### properties
<ParamField path="target" type="BuildTarget">
The target of the build, either `dev` or `deploy`.
</ParamField>
<ParamField path="config" type="ResolvedConfig">
<Expandable title="properties">
<ResponseField name="runtime" type="string">
The runtime of the project (either node or bun)
</ResponseField>
<ResponseField name="project" type="string">
The project ref
</ResponseField>
<ResponseField name="dirs" type="string[]">
The trigger directories to search for tasks
</ResponseField>
<ResponseField name="build" type="object">
The build configuration object
</ResponseField>
<ResponseField name="workingDir" type="string">
The working directory of the project
</ResponseField>
<ResponseField name="workspaceDir" type="string">
The root workspace directory of the project
</ResponseField>
<ResponseField name="packageJsonPath" type="string">
The path to the package.json file
</ResponseField>
<ResponseField name="lockfilePath" type="string">
The path to the lockfile (package-lock.json, yarn.lock, or pnpm-lock.yaml)
</ResponseField>
<ResponseField name="configFile" type="string">
The path to the trigger.config.ts file
</ResponseField>
<ResponseField name="tsconfigPath" type="string">
The path to the tsconfig.json file
</ResponseField>
</Expandable>
</ParamField>
<ParamField path="logger" type="BuildLogger">
A logger object that can be used to log messages to the console.
</ParamField>
## BuildLayer
<ParamField path="id" type="string">
A unique identifier for the layer.
</ParamField>
<ParamField path="commands" type="string[]">
An array of commands to run in the image build container.
```ts
commands: ["echo 'Hello, world!'"];
```
These commands are run after packages have been installed and the code copied into the container in the "build" stage of the Dockerfile. This means you cannot install system packages in these commands because they won't be available in the final stage. To do that, please use the `pkgs` property of the `image` object.
</ParamField>
<ParamField path="image" type="object">
<Expandable title="properties">
<ResponseField name="pkgs" type="string[]">
An array of system packages to install in the image build container.
</ResponseField>
<ResponseField name="instructions" type="string[]">
An array of instructions to add to the Dockerfile.
</ResponseField>
</Expandable>
</ParamField>
<ParamField path="build" type="object">
<Expandable title="properties">
<ResponseField name="env" type="Record<string, string>">
Environment variables to add to the image build container, but only during the "build" stage
of the Dockerfile. This is where you'd put environment variables that are needed when running
any of the commands in the `commands` array.
</ResponseField>
</Expandable>
</ParamField>
<ParamField path="deploy" type="object">
<Expandable title="properties">
<ResponseField name="env" type="Record<string, string>">
Environment variables that should sync to the Trigger.dev project, which will then be avalable
in your tasks at runtime. Importantly, these are NOT added to the image build container, but
are instead added to the Trigger.dev project and stored securely.
</ResponseField>
</Expandable>
</ParamField>
<ParamField path="dependencies" type="Record<string, string>">
An object of dependencies to add to the build. The key is the package name and the value is the
version.
```ts
dependencies: {
"my-dependency": "^1.0.0",
};
```
</ParamField>
### examples
Add a command that will echo the value of an environment variable:
```ts
context.addLayer({
id: "my-layer",
commands: [`echo $MY_ENV_VAR`],
build: {
env: {
MY_ENV_VAR: "Hello, world!",
},
},
});
```
## Troubleshooting
When creating a build extension, you may run into issues with the build process. One thing that can help is turning on `debug` logging when running either `dev` or `deploy`:
```sh
npx trigger.dev@latest dev --log-level debug
npx trigger.dev@latest deploy --log-level debug
```
Another helpful tool is the `--dry-run` flag on the `deploy` command, which will bundle your project and generate the Containerfile (e.g. the Dockerfile) without actually deploying it. This can help you see what the final image will look like and debug any issues with the build process.
```sh
npx trigger.dev@latest deploy --dry-run
```
You should also take a look at our built in extensions for inspiration on how to create your own. You can find them in in [the source code here](https://github.com/triggerdotdev/trigger.dev/tree/main/packages/build/src/extensions).
@@ -0,0 +1,29 @@
---
title: "Emit Decorator Metadata"
sidebarTitle: "emitDecoratorMetadata"
description: "Use the emitDecoratorMetadata build extension to enable support for the emitDecoratorMetadata TypeScript compiler option"
---
If you need support for the `emitDecoratorMetadata` typescript compiler option, import the `emitDecoratorMetadata` build extension and use it in your `trigger.config.ts` file:
```ts
import { defineConfig } from "@trigger.dev/sdk/v3";
import { emitDecoratorMetadata } from "@trigger.dev/build/extensions/typescript";
export default defineConfig({
project: "<project ref>",
// Your other config settings...
build: {
extensions: [emitDecoratorMetadata()],
},
});
```
This is usually required if you are using certain ORMs, like TypeORM, that require this option to be enabled. It's not enabled by default because there is a performance cost to enabling it.
<Note>
emitDecoratorMetadata works by hooking into the esbuild bundle process and using the TypeScript
compiler API to compile files where we detect the use of decorators. This means you must have
`emitDecoratorMetadata` enabled in your `tsconfig.json` file, as well as `typescript` installed in
your `devDependencies`.
</Note>
+31
View File
@@ -0,0 +1,31 @@
---
title: "esbuild Plugin"
sidebarTitle: "esbuildPlugin"
description: "Use the esbuildPlugin build extension to add existing or custom esbuild plugins to your build process"
---
You can easily add existing or custom esbuild plugins to your build process using the `esbuildPlugin` extension:
```ts
import { defineConfig } from "@trigger.dev/sdk/v3";
import { esbuildPlugin } from "@trigger.dev/build/extensions";
import { sentryEsbuildPlugin } from "@sentry/esbuild-plugin";
export default defineConfig({
project: "<project ref>",
// Your other config settings...
build: {
extensions: [
esbuildPlugin(
sentryEsbuildPlugin({
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_PROJECT,
authToken: process.env.SENTRY_AUTH_TOKEN,
}),
// optional - only runs during the deploy command, and adds the plugin to the end of the list of plugins
{ placement: "last", target: "deploy" }
),
],
},
});
```
+41
View File
@@ -0,0 +1,41 @@
---
title: "FFmpeg"
sidebarTitle: "ffmpeg"
description: "Use the ffmpeg build extension to include FFmpeg in your project"
---
You can add the `ffmpeg` build extension to your build process:
```ts
import { defineConfig } from "@trigger.dev/sdk/v3";
import { ffmpeg } from "@trigger.dev/build/extensions/core";
export default defineConfig({
project: "<project ref>",
// Your other config settings...
build: {
extensions: [ffmpeg()],
},
});
```
By default, this will install the version of `ffmpeg` that is available in the Debian package manager. If you need a specific version, you can pass in the version as an argument:
```ts
import { defineConfig } from "@trigger.dev/sdk/v3";
import { ffmpeg } from "@trigger.dev/build/extensions/core";
export default defineConfig({
project: "<project ref>",
// Your other config settings...
build: {
extensions: [ffmpeg({ version: "6.0-4" })],
},
});
```
This extension will also add the `FFMPEG_PATH` and `FFPROBE_PATH` to your environment variables, making it easy to use popular ffmpeg libraries like `fluent-ffmpeg`.
Note that `fluent-ffmpeg` needs to be added to [`external`](/config/config-file#external) in your `trigger.config.ts` file.
Follow [this example](/guides/examples/ffmpeg-video-processing) to get setup with Trigger.dev and FFmpeg in your project.
+25 -338
View File
@@ -1,22 +1,14 @@
---
title: "Build extensions"
sidebarTitle: "Overview"
description: "Customize how your project is built and deployed to Trigger.dev with build extensions"
---
Build extension allow you to hook into the build system and customize the build process or the resulting bundle and container image (in the case of deploying). See our [trigger.config.ts reference](/config/config-file#extensions) for more information on how to install and use our built-in extensions. Build extensions can do the following:
Build extensions allow you to hook into the build system and customize the build process or the resulting bundle and container image (in the case of deploying).
- Add additional files to the build
- Add dependencies to the list of externals
- Add esbuild plugins
- Add additional npm dependencies
- Add additional system packages to the image build container
- Add commands to run in the image build container
- Add environment variables to the image build container
- Sync environment variables to your Trigger.dev project
You can use pre-built extensions by installing the `@trigger.dev/build` package into your `devDependencies`, or you can create your own.
## Creating a build extension
Build extensions are added to your `trigger.config.ts` file, with a required `name` and optional build hook functions. Here's a simple example of a build extension that just logs a message when the build starts:
Build extensions are added to your `trigger.config.ts` file under the `build.extensions` property:
```ts
import { defineConfig } from "@trigger.dev/sdk/v3";
@@ -36,344 +28,39 @@ export default defineConfig({
});
```
You can also extract that out into a function instead of defining it inline, in which case you will need to import the `BuildExtension` type from the `@trigger.dev/build` package:
<Note>
You'll need to add the `@trigger.dev/build` package to your `devDependencies` before the below
code will work. Make sure it's version matches that of the installed `@trigger.dev/sdk` package.
</Note>
If you are using a pre-built extension, you can import it from the `@trigger.dev/build` package:
```ts
import { defineConfig } from "@trigger.dev/sdk/v3";
import { BuildExtension } from "@trigger.dev/build";
import { ffmpeg } from "@trigger.dev/build/extensions/core";
export default defineConfig({
project: "my-project",
build: {
extensions: [myExtension()],
},
});
function myExtension(): BuildExtension {
return {
name: "my-extension",
onBuildStart: async (context) => {
console.log("Build starting!");
},
};
}
```
## Build hooks
### externalsForTarget
This allows the extension to add additional dependencies to the list of externals for the build. This is useful for dependencies that are not included in the bundle, but are expected to be available at runtime.
```ts
import { defineConfig } from "@trigger.dev/sdk/v3";
export default defineConfig({
project: "my-project",
build: {
extensions: [
{
name: "my-extension",
externalsForTarget: async (target) => {
return ["my-dependency"];
},
},
],
extensions: [ffmpeg()],
},
});
```
### onBuildStart
## Built-in extensions
This hook runs before the build starts. It receives the `BuildContext` object as an argument.
Trigger.dev provides a set of built-in extensions that you can use to customize how your project is built and deployed. These extensions are available out of the box and can be configured in your `trigger.config.ts` file.
```ts
import { defineConfig } from "@trigger.dev/sdk/v3";
| Extension | Description |
| :-------------------------------------------------------------------- | :----------------------------------------------------------------------------- |
| [prismaExtension](/config/extensions/prismaExtension) | Using prisma in your Trigger.dev tasks |
| [pythonExtension](/config/extensions/pythonExtension) | Execute Python scripts in your project |
| [puppeteer](/config/extensions/puppeteer) | Use Puppeteer in your Trigger.dev tasks |
| [ffmpeg](/config/extensions/ffmpeg) | Use FFmpeg in your Trigger.dev tasks |
| [aptGet](/config/extensions/aptGet) | Install system packages in your build image |
| [additionalFiles](/config/extensions/additionalFiles) | Copy additional files to your build image |
| [additionalPackages](/config/extensions/additionalPackages) | Install additional npm packages in your build image |
| [syncEnvVars](/config/extensions/syncEnvVars) | Automatically sync environment variables from external services to Trigger.dev |
| [syncVercelEnvVars](/config/extensions/syncEnvVars#syncVercelEnvVars) | Automatically sync environment variables from Vercel to Trigger.dev |
| [esbuildPlugin](/config/extensions/esbuildPlugin) | Add existing or custom esbuild extensions to customize your build process |
| [emitDecoratorMetadata](/config/extensions/emitDecoratorMetadata) | Enable `emitDecoratorMetadata` in your TypeScript build |
| [audioWaveform](/config/extensions/audioWaveform) | Add Audio Waveform to your build image |
export default defineConfig({
project: "my-project",
build: {
extensions: [
{
name: "my-extension",
onBuildStart: async (context) => {
console.log("Build starting!");
},
},
],
},
});
```
## Custom extensions
If you want to add an esbuild plugin, you must do so in the `onBuildStart` hook. Here's an example of adding a custom esbuild plugin:
```ts
import { defineConfig } from "@trigger.dev/sdk/v3";
export default defineConfig({
project: "my-project",
build: {
extensions: [
{
name: "my-extension",
onBuildStart: async (context) => {
context.registerPlugin({
name: "my-plugin",
setup(build) {
build.onLoad({ filter: /.*/, namespace: "file" }, async (args) => {
return {
contents: "console.log('Hello, world!')",
loader: "js",
};
});
},
});
},
},
],
},
});
```
You can use the `BuildContext.target` property to determine if the build is for `dev` or `deploy`:
```ts
import { defineConfig } from "@trigger.dev/sdk/v3";
export default defineConfig({
project: "my-project",
build: {
extensions: [
{
name: "my-extension",
onBuildStart: async (context) => {
if (context.target === "dev") {
console.log("Building for dev");
} else {
console.log("Building for deploy");
}
},
},
],
},
});
```
### onBuildComplete
This hook runs after the build completes. It receives the `BuildContext` object and a `BuildManifest` object as arguments. This is where you can add in one or more `BuildLayer`'s to the context.
```ts
import { defineConfig } from "@trigger.dev/sdk/v3";
export default defineConfig({
project: "my-project",
build: {
extensions: [
{
name: "my-extension",
onBuildComplete: async (context, manifest) => {
context.addLayer({
id: "more-dependencies",
dependencies,
});
},
},
],
},
});
```
See the [addLayer](#addlayer) documentation for more information on how to use `addLayer`.
## BuildTarget
Can either be `dev` or `deploy`, matching the CLI command name that is being run.
```sh
npx trigger.dev@latest dev # BuildTarget is "dev"
npx trigger.dev@latest deploy # BuildTarget is "deploy"
```
## BuildContext
### addLayer()
<ParamField path="layer" type="BuildLayer">
The layer to add to the build context. See the [BuildLayer](#buildlayer) documentation for more
information.
</ParamField>
### registerPlugin()
<ParamField path="plugin" type="esbuild.Plugin" required>
The esbuild plugin to register.
</ParamField>
<ParamField path="options" type="object">
<Expandable title="properties">
<ResponseField name="target" type="BuildTarget">
An optional target to register the plugin for. If not provided, the plugin will be registered
for all targets.
</ResponseField>
<ResponseField name="placement" type="first | last">
An optional placement for the plugin. If not provided, the plugin will be registered in place.
This allows you to control the order of plugins.
</ResponseField>
</Expandable>
</ParamField>
### resolvePath()
Resolves a path relative to the project's working directory.
<ParamField path="path" type="string">
The path to resolve.
</ParamField>
```ts
const resolvedPath = context.resolvePath("my-other-dependency");
```
### properties
<ParamField path="target" type="BuildTarget">
The target of the build, either `dev` or `deploy`.
</ParamField>
<ParamField path="config" type="ResolvedConfig">
<Expandable title="properties">
<ResponseField name="runtime" type="string">
The runtime of the project (either node or bun)
</ResponseField>
<ResponseField name="project" type="string">
The project ref
</ResponseField>
<ResponseField name="dirs" type="string[]">
The trigger directories to search for tasks
</ResponseField>
<ResponseField name="build" type="object">
The build configuration object
</ResponseField>
<ResponseField name="workingDir" type="string">
The working directory of the project
</ResponseField>
<ResponseField name="workspaceDir" type="string">
The root workspace directory of the project
</ResponseField>
<ResponseField name="packageJsonPath" type="string">
The path to the package.json file
</ResponseField>
<ResponseField name="lockfilePath" type="string">
The path to the lockfile (package-lock.json, yarn.lock, or pnpm-lock.yaml)
</ResponseField>
<ResponseField name="configFile" type="string">
The path to the trigger.config.ts file
</ResponseField>
<ResponseField name="tsconfigPath" type="string">
The path to the tsconfig.json file
</ResponseField>
</Expandable>
</ParamField>
<ParamField path="logger" type="BuildLogger">
A logger object that can be used to log messages to the console.
</ParamField>
## BuildLayer
<ParamField path="id" type="string">
A unique identifier for the layer.
</ParamField>
<ParamField path="commands" type="string[]">
An array of commands to run in the image build container.
```ts
commands: ["echo 'Hello, world!'"];
```
These commands are run after packages have been installed and the code copied into the container in the "build" stage of the Dockerfile. This means you cannot install system packages in these commands because they won't be available in the final stage. To do that, please use the `pkgs` property of the `image` object.
</ParamField>
<ParamField path="image" type="object">
<Expandable title="properties">
<ResponseField name="pkgs" type="string[]">
An array of system packages to install in the image build container.
</ResponseField>
<ResponseField name="instructions" type="string[]">
An array of instructions to add to the Dockerfile.
</ResponseField>
</Expandable>
</ParamField>
<ParamField path="build" type="object">
<Expandable title="properties">
<ResponseField name="env" type="Record<string, string>">
Environment variables to add to the image build container, but only during the "build" stage
of the Dockerfile. This is where you'd put environment variables that are needed when running
any of the commands in the `commands` array.
</ResponseField>
</Expandable>
</ParamField>
<ParamField path="deploy" type="object">
<Expandable title="properties">
<ResponseField name="env" type="Record<string, string>">
Environment variables that should sync to the Trigger.dev project, which will then be avalable
in your tasks at runtime. Importantly, these are NOT added to the image build container, but
are instead added to the Trigger.dev project and stored securely.
</ResponseField>
</Expandable>
</ParamField>
<ParamField path="dependencies" type="Record<string, string>">
An object of dependencies to add to the build. The key is the package name and the value is the
version.
```ts
dependencies: {
"my-dependency": "^1.0.0",
};
```
</ParamField>
### examples
Add a command that will echo the value of an environment variable:
```ts
context.addLayer({
id: "my-layer",
commands: [`echo $MY_ENV_VAR`],
build: {
env: {
MY_ENV_VAR: "Hello, world!",
},
},
});
```
## Troubleshooting
When creating a build extension, you may run into issues with the build process. One thing that can help is turning on `debug` logging when running either `dev` or `deploy`:
```sh
npx trigger.dev@latest dev --log-level debug
npx trigger.dev@latest deploy --log-level debug
```
Another helpful tool is the `--dry-run` flag on the `deploy` command, which will bundle your project and generate the Containerfile (e.g. the Dockerfile) without actually deploying it. This can help you see what the final image will look like and debug any issues with the build process.
```sh
npx trigger.dev@latest deploy --dry-run
```
You should also take a look at our built in extensions for inspiration on how to create your own. You can find them in in [the source code here](https://github.com/triggerdotdev/trigger.dev/tree/main/packages/build/src/extensions).
If one of the built-in extensions doesn't meet your needs, you can create your own custom extension. See our [guide on creating custom build extensions](/config/extensions/custom) for more information.
+157
View File
@@ -0,0 +1,157 @@
---
title: "Prisma"
sidebarTitle: "prismaExtension"
description: "Use the prismaExtension build extension to use Prisma with Trigger.dev"
---
If you are using Prisma, you should use the prisma build extension.
- Automatically handles copying Prisma files to the build directory
- Generates the Prisma client during the deploy process
- Optionally will migrate the database during the deploy process
- Support for TypedSQL and multiple schema files
- You can use `prismaSchemaFolder` to specify just the directory containing your schema file, instead of the full path
- You can add the extension twice if you have multiple separate schemas in the same project (example below)
You can use it for a simple Prisma setup like this:
```ts
import { defineConfig } from "@trigger.dev/sdk/v3";
import { prismaExtension } from "@trigger.dev/build/extensions/prisma";
export default defineConfig({
project: "<project ref>",
// Your other config settings...
build: {
extensions: [
prismaExtension({
version: "5.19.0", // optional, we'll automatically detect the version if not provided
schema: "prisma/schema.prisma",
}),
],
},
});
```
<Note>
This does not have any effect when running the `dev` command, only when running the `deploy`
command.
</Note>
### Migrations
If you want to also run migrations during the build process, you can pass in the `migrate` option:
```ts
import { defineConfig } from "@trigger.dev/sdk/v3";
import { prismaExtension } from "@trigger.dev/build/extensions/prisma";
export default defineConfig({
project: "<project ref>",
// Your other config settings...
build: {
extensions: [
prismaExtension({
schema: "prisma/schema.prisma",
migrate: true,
directUrlEnvVarName: "DATABASE_URL_UNPOOLED", // optional - the name of the environment variable that contains the direct database URL if you are using a direct database URL
}),
],
},
});
```
### clientGenerator
If you have multiple `generator` statements defined in your schema file, you can pass in the `clientGenerator` option to specify the `prisma-client-js` generator, which will prevent other generators from being generated. Some examples where you may need to do this include when using the `prisma-kysely` or `prisma-json-types-generator` generators.
<CodeGroup>
```prisma schema.prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
directUrl = env("DATABASE_URL_UNPOOLED")
}
// We only want to generate the prisma-client-js generator
generator client {
provider = "prisma-client-js"
}
generator kysely {
provider = "prisma-kysely"
output = "../../src/kysely"
enumFileName = "enums.ts"
fileName = "types.ts"
}
generator json {
provider = "prisma-json-types-generator"
}
```
```ts trigger.config.ts
import { defineConfig } from "@trigger.dev/sdk/v3";
import { prismaExtension } from "@trigger.dev/build/extensions/prisma";
export default defineConfig({
project: "<project ref>",
// Your other config settings...
build: {
extensions: [
prismaExtension({
schema: "prisma/schema.prisma",
clientGenerator: "client",
}),
],
},
});
```
</CodeGroup>
### TypedSQL
If you are using [TypedSQL](https://www.prisma.io/typedsql), you'll need to enable it via the `typedSql` option:
```ts
import { defineConfig } from "@trigger.dev/sdk/v3";
export default defineConfig({
project: "<project ref>",
// Your other config settings...
build: {
extensions: [
prismaExtension({
schema: "prisma/schema.prisma",
typedSql: true,
}),
],
},
});
```
<Note>
The `prismaExtension` will inject the `DATABASE_URL` environment variable into the build process. Learn more about setting environment variables for deploying in our [Environment Variables](/deploy-environment-variables) guide.
These environment variables are only used during the build process and are not embedded in the final container image.
</Note>
### Multiple schemas
If you have multiple separate schemas in the same project you can add the extension multiple times:
```ts
prismaExtension({
schema: 'prisma/schema/main.prisma',
version: '6.2.0',
migrate: false,
}),
prismaExtension({
schema: 'prisma/schema/secondary.prisma',
version: '6.2.0',
migrate: false,
}),
```
+30
View File
@@ -0,0 +1,30 @@
---
title: "Puppeteer"
sidebarTitle: "puppeteer"
description: "Use the puppeteer build extension to enable support for Puppeteer in your project"
---
<ScrapingWarning />
To use Puppeteer in your project, add these build settings to your `trigger.config.ts` file:
```ts trigger.config.ts
import { defineConfig } from "@trigger.dev/sdk/v3";
import { puppeteer } from "@trigger.dev/build/extensions/puppeteer";
export default defineConfig({
project: "<project ref>",
// Your other config settings...
build: {
extensions: [puppeteer()],
},
});
```
And add the following environment variable in your Trigger.dev dashboard on the Environment Variables page:
```bash
PUPPETEER_EXECUTABLE_PATH: "/usr/bin/google-chrome-stable",
```
Follow [this example](/guides/examples/puppeteer) to get setup with Trigger.dev and Puppeteer in your project.
+182
View File
@@ -0,0 +1,182 @@
---
title: "Python"
sidebarTitle: "pythonExtension"
description: "Use the python build extension to add support for executing Python scripts in your project"
---
If you need to execute Python scripts in your Trigger.dev project, you can use the `pythonExtension` build extension via the `@trigger.dev/python` package.
First, you'll need to install the `@trigger.dev/python` package:
```bash
npm add @trigger.dev/python
```
Then, you can use the `pythonExtension` build extension in your `trigger.config.ts` file:
```ts
import { defineConfig } from "@trigger.dev/sdk/v3";
import { pythonExtension } from "@trigger.dev/python/extension";
export default defineConfig({
project: "<project ref>",
build: {
extensions: [pythonExtension()],
},
});
```
This will take care of adding python to the build image and setting up the necessary environment variables to execute Python scripts. You can then use our `python` utilities in the `@trigger.dev/python` package to execute Python scripts in your tasks. For example, running a Python script inline in a task:
```ts
import { task } from "@trigger.dev/sdk/v3";
import { python } from "@trigger.dev/python";
export const myScript = task({
id: "my-python-script",
run: async () => {
const result = await python.runInline(`print("Hello, world!")`);
return result.stdout;
},
});
```
## Adding python scripts
You can automatically add python scripts to your project using the `scripts` option in the `pythonExtension` function. This will copy the specified scripts to the build directory during the deploy process. For example:
```ts
import { defineConfig } from "@trigger.dev/sdk/v3";
import { pythonExtension } from "@trigger.dev/python/extension";
export default defineConfig({
project: "<project ref>",
build: {
extensions: [
pythonExtension({
scripts: ["./python/**/*.py"],
}),
],
},
});
```
This will copy all Python files in the `python` directory to the build directory during the deploy process. You can then execute these scripts using the `python.runScript` function:
```ts
import { task } from "@trigger.dev/sdk/v3";
import { python } from "@trigger.dev/python";
export const myScript = task({
id: "my-python-script",
run: async () => {
const result = await python.runScript("./python/my_script.py", ["hello", "world"]);
return result.stdout;
},
});
```
<Note>
The pythonExtension will also take care of moving the scripts to the correct location during `dev`
mode, so you can use the same exact path in development as you do in production.
</Note>
## Using requirements files
If you have a `requirements.txt` file in your project, you can use the `requirementsFile` option in the `pythonExtension` function to install the required packages during the build process. For example:
```ts
import { defineConfig } from "@trigger.dev/sdk/v3";
import { pythonExtension } from "@trigger.dev/python/extension";
export default defineConfig({
project: "<project ref>",
build: {
extensions: [
pythonExtension({
requirementsFile: "./requirements.txt",
}),
],
},
});
```
This will install the packages specified in the `requirements.txt` file during the build process. You can then use these packages in your Python scripts.
<Note>
The `requirementsFile` option is only available in production mode. In development mode, you can
install the required packages manually using the `pip` command.
</Note>
## Virtual environments
If you are using a virtual environment in your project, you can use the `devPythonBinaryPath` option in the `pythonExtension` function to specify the path to the Python binary in the virtual environment. For example:
```ts
import { defineConfig } from "@trigger.dev/sdk/v3";
import { pythonExtension } from "@trigger.dev/python/extension";
export default defineConfig({
project: "<project ref>",
build: {
extensions: [
pythonExtension({
devPythonBinaryPath: ".venv/bin/python",
}),
],
},
});
```
This has no effect in production mode, but in development mode, it will use the specified Python binary to execute Python scripts.
## Streaming output
All of the `python` functions have a streaming version that allows you to stream the output of the Python script as it runs. For example:
```ts
import { task } from "@trigger.dev/sdk/v3";
import { python } from "@trigger.dev/python";
export const myStreamingScript = task({
id: "my-streaming-python-script",
run: async () => {
// You don't need to await the result
const result = python.stream.runScript("./python/my_script.py", ["hello", "world"]);
// result is an async iterable/readable stream
for await (const chunk of streamingResult) {
console.log(chunk);
}
},
});
```
## Environment variables
We automatically inject the environment variables in the `process.env` object when running Python scripts. You can access these environment variables in your Python scripts using the `os.environ` dictionary. For example:
```python
import os
print(os.environ["MY_ENV_VAR"])
```
You can also pass additional environment variables to the Python script using the `env` option in the `python.runScript` function. For example:
```ts
import { task } from "@trigger.dev/sdk/v3";
import { python } from "@trigger.dev/python";
export const myScript = task({
id: "my-python-script",
run: async () => {
const result = await python.runScript("./python/my_script.py", ["hello", "world"], {
env: {
MY_ENV_VAR: "my value",
},
});
return result.stdout;
},
});
```
+116
View File
@@ -0,0 +1,116 @@
---
title: "Sync env vars"
sidebarTitle: "syncEnvVars"
description: "Use the syncEnvVars build extension to automatically sync environment variables to Trigger.dev"
---
The `syncEnvVars` build extension will sync env vars from another service into Trigger.dev before the deployment starts. This is useful if you are using a secret store service like Infisical or AWS Secrets Manager to store your secrets.
`syncEnvVars` takes an async callback function, and any env vars returned from the callback will be synced to Trigger.dev.
```ts
import { defineConfig } from "@trigger.dev/sdk/v3";
import { syncEnvVars } from "@trigger.dev/build/extensions/core";
export default defineConfig({
build: {
extensions: [
syncEnvVars(async (ctx) => {
return [
{ name: "SECRET_KEY", value: "secret-value" },
{ name: "ANOTHER_SECRET", value: "another-secret-value" },
];
}),
],
},
});
```
The callback is passed a context object with the following properties:
- `environment`: The environment name that the task is being deployed to (e.g. `production`, `staging`, etc.)
- `projectRef`: The project ref of the Trigger.dev project
- `env`: The environment variables that are currently set in the Trigger.dev project
### Example: Sync env vars from Infisical
In this example we're using env vars from [Infisical](https://infisical.com).
```ts trigger.config.ts
import { defineConfig } from "@trigger.dev/sdk/v3";
import { syncEnvVars } from "@trigger.dev/build/extensions/core";
import { InfisicalSDK } from "@infisical/sdk";
export default defineConfig({
build: {
extensions: [
syncEnvVars(async (ctx) => {
const client = new InfisicalSDK();
await client.auth().universalAuth.login({
clientId: process.env.INFISICAL_CLIENT_ID!,
clientSecret: process.env.INFISICAL_CLIENT_SECRET!,
});
const { secrets } = await client.secrets().listSecrets({
environment: ctx.environment,
projectId: process.env.INFISICAL_PROJECT_ID!,
});
return secrets.map((secret) => ({
name: secret.secretKey,
value: secret.secretValue,
}));
}),
],
},
});
```
### syncVercelEnvVars
The `syncVercelEnvVars` build extension syncs environment variables from your Vercel project to Trigger.dev.
<Note>
You need to set the `VERCEL_ACCESS_TOKEN` and `VERCEL_PROJECT_ID` environment variables, or pass
in the token and project ID as arguments to the `syncVercelEnvVars` build extension. If you're
working with a team project, you'll also need to set `VERCEL_TEAM_ID`, which can be found in your
team settings. You can find / generate the `VERCEL_ACCESS_TOKEN` in your Vercel
[dashboard](https://vercel.com/account/settings/tokens). Make sure the scope of the token covers
the project with the environment variables you want to sync.
</Note>
```ts
import { defineConfig } from "@trigger.dev/sdk/v3";
import { syncVercelEnvVars } from "@trigger.dev/build/extensions/core";
export default defineConfig({
project: "<project ref>",
// Your other config settings...
build: {
// This will automatically use the VERCEL_ACCESS_TOKEN and VERCEL_PROJECT_ID environment variables
extensions: [syncVercelEnvVars()],
},
});
```
Or you can pass in the token and project ID as arguments:
```ts
import { defineConfig } from "@trigger.dev/sdk/v3";
import { syncVercelEnvVars } from "@trigger.dev/build/extensions/core";
export default defineConfig({
project: "<project ref>",
// Your other config settings...
build: {
extensions: [
syncVercelEnvVars({
projectId: "your-vercel-project-id",
vercelAccessToken: "your-vercel-access-token",
vercelTeamId: "your-vercel-team-id", // optional
}),
],
},
});
```
+61 -35
View File
@@ -39,36 +39,7 @@
},
"triggering",
"runs",
"apikeys",
{
"group": "Configuration",
"pages": [
"config/config-file",
"config/extensions/overview"
]
}
]
},
{
"group": "Development",
"pages": [
"cli-dev",
"run-tests"
]
},
{
"group": "Deployment",
"pages": [
"deployment/overview",
"deploy-environment-variables",
"github-actions",
"deployment/atomic-deployment",
{
"group": "Deployment integrations",
"pages": [
"vercel-integration"
]
}
"apikeys"
]
},
{
@@ -91,14 +62,61 @@
"versioning",
"machines",
"idempotency",
"replaying",
"runs/max-duration",
"tags",
"runs/metadata",
"run-usage",
"context",
"bulk-actions",
"examples"
"context"
]
},
{
"group": "Configuration",
"pages": [
"config/config-file",
{
"group": "Build extensions",
"pages": [
"config/extensions/overview",
{
"group": "Built-in extensions",
"pages": [
"config/extensions/prismaExtension",
"config/extensions/pythonExtension",
"config/extensions/puppeteer",
"config/extensions/ffmpeg",
"config/extensions/aptGet",
"config/extensions/additionalFiles",
"config/extensions/additionalPackages",
"config/extensions/syncEnvVars",
"config/extensions/esbuildPlugin",
"config/extensions/emitDecoratorMetadata",
"config/extensions/audioWaveform"
]
},
"config/extensions/custom"
]
}
]
},
{
"group": "Development",
"pages": [
"cli-dev"
]
},
{
"group": "Deployment",
"pages": [
"deployment/overview",
"deploy-environment-variables",
"github-actions",
"deployment/atomic-deployment",
{
"group": "Deployment integrations",
"pages": [
"vercel-integration"
]
}
]
},
{
@@ -146,6 +164,15 @@
}
]
},
{
"group": "Using the Dashboard",
"pages": [
"run-tests",
"troubleshooting-alerts",
"replaying",
"bulk-actions"
]
},
{
"group": "Troubleshooting",
"pages": [
@@ -153,7 +180,6 @@
"troubleshooting-debugging-in-vscode",
"upgrading-packages",
"upgrading-beta",
"troubleshooting-alerts",
"troubleshooting-uptime-status",
"troubleshooting-github-issues",
"request-feature"
@@ -29,7 +29,7 @@ export default defineConfig({
```
<Note>
[Build extensions](/config/config-file#extensions) allow you to hook into the build system and
[Build extensions](/config/extensions/overview) allow you to hook into the build system and
customize the build process or the resulting bundle and container image (in the case of
deploying). You can use pre-built extensions or create your own.
</Note>
@@ -34,7 +34,7 @@ export default defineConfig({
```
<Note>
[Build extensions](/config/config-file#extensions) allow you to hook into the build system and
[Build extensions](/config/extensions/overview) allow you to hook into the build system and
customize the build process or the resulting bundle and container image (in the case of
deploying). You can use pre-built extensions or create your own.
</Note>
+1 -3
View File
@@ -12,7 +12,7 @@ This example demonstrates how to use Trigger.dev to turn a PDF into a series of
## Update your build configuration
To use this example, add these build settings below to your `trigger.config.ts` file. They ensure that the `mutool` and `curl` packages are installed when you deploy your task. You can learn more about this and see more build settings [here](/config/config-file#aptget).
To use this example, add these build settings below to your `trigger.config.ts` file. They ensure that the `mutool` and `curl` packages are installed when you deploy your task. You can learn more about this and see more build settings [here](/config/extensions/aptGet).
```ts trigger.config.ts
export default defineConfig({
@@ -100,5 +100,3 @@ To test this task in the dashboard, you can use the following payload:
```
<LocalDevelopment packages={"mupdf-tools from MuPDF"} />
+2 -2
View File
@@ -22,7 +22,7 @@ There are 3 example tasks to follow on this page:
<ScrapingWarning />
## Build configurations
## Build configuration
To use all examples on this page, you'll first need to add these build settings to your `trigger.config.ts` file:
@@ -40,7 +40,7 @@ export default defineConfig({
});
```
Learn more about [build configurations](/config/config-file#build-configuration) including setting default retry settings, customizing the build environment, and more.
Learn more about the [trigger.config.ts](/config/config-file) file including setting default retry settings, customizing the build environment, and more.
## Set an environment variable
+4 -4
View File
@@ -12,9 +12,9 @@ import ScrapingWarning from "/snippets/web-scraping-warning.mdx";
height="315"
src="https://www.youtube.com/embed/6azvzrZITKY?si=muKtsBiS9TJGGKWg"
title="YouTube video player"
allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerPolicy="strict-origin-when-cross-origin"
allowFullScreen
allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerPolicy="strict-origin-when-cross-origin"
allowFullScreen
/>
## Overview
@@ -63,7 +63,7 @@ export default defineConfig({
});
```
Learn more about [build configurations](/config/config-file#build-configuration) including setting default retry settings, customizing the build environment, and more.
Learn more about the [trigger.config.ts](/config/config-file) file including setting default retry settings, customizing the build environment, and more.
### Environment variables
@@ -67,7 +67,7 @@ export default defineConfig({
```
<Note>
[Build extensions](/config/config-file#extensions) allow you to hook into the build system and
[Build extensions](/config/extensions/overview) allow you to hook into the build system and
customize the build process or the resulting bundle and container image (in the case of
deploying). You can use pre-built extensions or create your own.
</Note>
@@ -34,7 +34,7 @@ export default defineConfig({
```
<Note>
[Build extensions](/config/config-file#extensions) allow you to hook into the build system and
[Build extensions](/config/extensions/overview) allow you to hook into the build system and
customize the build process or the resulting bundle and container image (in the case of
deploying). You can use pre-built extensions or create your own.
</Note>
+2 -2
View File
@@ -84,7 +84,7 @@ Next, configure the Prisma [build extension](https://trigger.dev/docs/config/ext
This will ensure that the Prisma client is available when the task runs.
For a full list of options available in the Prisma build extension, see the [Prisma build extension documentation](https://trigger.dev/docs/config/config-file#prisma).
For a full list of options available in the Prisma build extension, see the [Prisma build extension documentation](https://trigger.dev/docs/config/extensions/prismaExtension).
```js /trigger.config.js
export default defineConfig({
@@ -103,7 +103,7 @@ export default defineConfig({
```
<Note>
[Build extensions](/config/config-file#extensions) allow you to hook into the build system and
[Build extensions](/config/extensions/overview) allow you to hook into the build system and
customize the build process or the resulting bundle and container image (in the case of
deploying). You can use pre-built extensions or create your own.
</Note>
@@ -260,7 +260,7 @@ export default defineConfig({
```
<Note>
[Build extensions](/config/config-file#extensions) allow you to hook into the build system and
[Build extensions](/config/extensions/overview) allow you to hook into the build system and
customize the build process or the resulting bundle and container image (in the case of
deploying). You can use pre-built extensions or create your own.
</Note>
+2 -1
View File
@@ -94,13 +94,14 @@ Your code is deployed separately from the rest of your app(s) so you need to mak
### `Error: @prisma/client did not initialize yet.`
Prisma uses code generation to create the client from your schema file. This means you need to add a bit of config so we can generate this file before your tasks run: [Read the guide](/config/config-file#prisma).
Prisma uses code generation to create the client from your schema file. This means you need to add a bit of config so we can generate this file before your tasks run: [Read the guide](/config/extensions/prismaExtension).
### `Parallel waits are not supported`
In the current version, you can't perform more that one "wait" in parallel.
Waits include:
- `wait.for()`
- `wait.until()`
- `task.triggerAndWait()`
+15
View File
@@ -23,6 +23,7 @@
"exports": {
"./package.json": "./package.json",
".": "./src/index.ts",
"./internal": "./src/internal.ts",
"./extensions": "./src/extensions/index.ts",
"./extensions/core": "./src/extensions/core.ts",
"./extensions/prisma": "./src/extensions/prisma.ts",
@@ -36,6 +37,9 @@
},
"typesVersions": {
"*": {
"internal": [
"dist/commonjs/internal.d.ts"
],
"extensions": [
"dist/commonjs/extensions/index.d.ts"
],
@@ -95,6 +99,17 @@
"default": "./dist/commonjs/index.js"
}
},
"./internal": {
"import": {
"@triggerdotdev/source": "./src/internal.ts",
"types": "./dist/esm/internal.d.ts",
"default": "./dist/esm/internal.js"
},
"require": {
"types": "./dist/commonjs/internal.d.ts",
"default": "./dist/commonjs/internal.js"
}
},
"./extensions": {
"import": {
"@triggerdotdev/source": "./src/extensions/index.ts",
@@ -1,7 +1,5 @@
import { relative, join, posix, dirname } from "node:path";
import { glob } from "tinyglobby";
import { copyFile, mkdir } from "node:fs/promises";
import { BuildExtension } from "@trigger.dev/core/v3/build";
import { addAdditionalFilesToBuild } from "../../internal/additionalFiles.js";
export type AdditionalFilesOptions = {
files: string[];
@@ -11,86 +9,7 @@ export function additionalFiles(options: AdditionalFilesOptions): BuildExtension
return {
name: "additionalFiles",
async onBuildComplete(context, manifest) {
// Copy any static assets to the destination
const staticAssets = await findStaticAssetFiles(options.files ?? [], manifest.outputPath, {
cwd: context.workingDir,
});
for (const { assets, matcher } of staticAssets) {
if (assets.length === 0) {
console.warn("No files found for matcher", matcher);
}
}
await copyStaticAssets(staticAssets);
await addAdditionalFilesToBuild("additionalFiles", options, context, manifest);
},
};
}
type MatchedStaticAssets = { source: string; destination: string }[];
type FoundStaticAssetFiles = Array<{
matcher: string;
assets: MatchedStaticAssets;
}>;
async function findStaticAssetFiles(
matchers: string[],
destinationPath: string,
options?: { cwd?: string; ignore?: string[] }
): Promise<FoundStaticAssetFiles> {
const result: FoundStaticAssetFiles = [];
for (const matcher of matchers) {
const assets = await findStaticAssetsForMatcher(matcher, destinationPath, options);
result.push({ matcher, assets });
}
return result;
}
async function findStaticAssetsForMatcher(
matcher: string,
destinationPath: string,
options?: { cwd?: string; ignore?: string[] }
): Promise<MatchedStaticAssets> {
const result: MatchedStaticAssets = [];
const files = await glob({
patterns: [matcher],
cwd: options?.cwd,
ignore: options?.ignore ?? [],
onlyFiles: true,
absolute: true,
});
let matches = 0;
for (const file of files) {
matches++;
const pathInsideDestinationDir = relative(options?.cwd ?? process.cwd(), file)
.split(posix.sep)
.filter((p) => p !== "..")
.join(posix.sep);
const relativeDestinationPath = join(destinationPath, pathInsideDestinationDir);
result.push({
source: file,
destination: relativeDestinationPath,
});
}
return result;
}
async function copyStaticAssets(staticAssetFiles: FoundStaticAssetFiles): Promise<void> {
for (const { assets } of staticAssetFiles) {
for (const { source, destination } of assets) {
await mkdir(dirname(destination), { recursive: true });
await copyFile(source, destination);
}
}
}
+1
View File
@@ -0,0 +1 @@
export * from "./internal/additionalFiles.js";
@@ -0,0 +1,106 @@
import { BuildManifest } from "@trigger.dev/core/v3";
import { BuildContext } from "@trigger.dev/core/v3/build";
import { copyFile, mkdir } from "node:fs/promises";
import { dirname, join, posix, relative } from "node:path";
import { glob } from "tinyglobby";
export type AdditionalFilesOptions = {
files: string[];
};
export async function addAdditionalFilesToBuild(
source: string,
options: AdditionalFilesOptions,
context: BuildContext,
manifest: BuildManifest
) {
// Copy any static assets to the destination
const staticAssets = await findStaticAssetFiles(options.files ?? [], manifest.outputPath, {
cwd: context.workingDir,
});
for (const { assets, matcher } of staticAssets) {
if (assets.length === 0) {
context.logger.warn(`[${source}] No files found for matcher`, matcher);
} else {
context.logger.debug(`[${source}] Found ${assets.length} files for matcher`, matcher);
}
}
await copyStaticAssets(staticAssets, source, context);
}
type MatchedStaticAssets = { source: string; destination: string }[];
type FoundStaticAssetFiles = Array<{
matcher: string;
assets: MatchedStaticAssets;
}>;
async function findStaticAssetFiles(
matchers: string[],
destinationPath: string,
options?: { cwd?: string; ignore?: string[] }
): Promise<FoundStaticAssetFiles> {
const result: FoundStaticAssetFiles = [];
for (const matcher of matchers) {
const assets = await findStaticAssetsForMatcher(matcher, destinationPath, options);
result.push({ matcher, assets });
}
return result;
}
async function findStaticAssetsForMatcher(
matcher: string,
destinationPath: string,
options?: { cwd?: string; ignore?: string[] }
): Promise<MatchedStaticAssets> {
const result: MatchedStaticAssets = [];
const files = await glob({
patterns: [matcher],
cwd: options?.cwd,
ignore: options?.ignore ?? [],
onlyFiles: true,
absolute: true,
});
let matches = 0;
for (const file of files) {
matches++;
const pathInsideDestinationDir = relative(options?.cwd ?? process.cwd(), file)
.split(posix.sep)
.filter((p) => p !== "..")
.join(posix.sep);
const relativeDestinationPath = join(destinationPath, pathInsideDestinationDir);
result.push({
source: file,
destination: relativeDestinationPath,
});
}
return result;
}
async function copyStaticAssets(
staticAssetFiles: FoundStaticAssetFiles,
sourceName: string,
context: BuildContext
): Promise<void> {
for (const { assets } of staticAssetFiles) {
for (const { source, destination } of assets) {
await mkdir(dirname(destination), { recursive: true });
context.logger.debug(`[${sourceName}] Copying ${source} to ${destination}`);
await copyFile(source, destination);
}
}
}
+1 -2
View File
@@ -61,14 +61,13 @@ import {
SubscribeToRunsQueryParams,
UpdateEnvironmentVariableParams,
} from "./types.js";
import type { AsyncIterableStream } from "./stream.js";
import { AsyncIterableStream } from "../streams/asyncIterableStream.js";
export type {
CreateEnvironmentVariableParams,
ImportEnvironmentVariablesParams,
SubscribeToRunsQueryParams,
UpdateEnvironmentVariableParams,
AsyncIterableStream,
};
export type ClientTriggerOptions = {
+2 -3
View File
@@ -16,12 +16,11 @@ import {
} from "../utils/ioSerialization.js";
import { ApiError } from "./errors.js";
import { ApiClient } from "./index.js";
import { LineTransformStream, zodShapeStream } from "./stream.js";
import {
AsyncIterableStream,
createAsyncIterableReadable,
LineTransformStream,
zodShapeStream,
} from "./stream.js";
} from "../streams/asyncIterableStream.js";
export type RunShape<TRunTypes extends AnyRunTypes> = TRunTypes extends AnyRunTypes
? {
+1 -51
View File
@@ -9,6 +9,7 @@ import {
type Row,
type ShapeStreamInterface,
} from "@electric-sql/client";
import { AsyncIterableStream, createAsyncIterableStream } from "../streams/asyncIterableStream.js";
export type ZodShapeStreamOptions = {
headers?: Record<string, string>;
@@ -82,57 +83,6 @@ export function zodShapeStream<TShapeSchema extends z.ZodTypeAny>(
};
}
export type AsyncIterableStream<T> = AsyncIterable<T> & ReadableStream<T>;
export function createAsyncIterableStream<S, T>(
source: ReadableStream<S>,
transformer: Transformer<S, T>
): AsyncIterableStream<T> {
const transformedStream: any = source.pipeThrough(new TransformStream(transformer));
transformedStream[Symbol.asyncIterator] = () => {
const reader = transformedStream.getReader();
return {
async next(): Promise<IteratorResult<string>> {
const { done, value } = await reader.read();
return done ? { done: true, value: undefined } : { done: false, value };
},
};
};
return transformedStream;
}
export function createAsyncIterableReadable<S, T>(
source: ReadableStream<S>,
transformer: Transformer<S, T>,
signal: AbortSignal
): AsyncIterableStream<T> {
return new ReadableStream<T>({
async start(controller) {
const transformedStream = source.pipeThrough(new TransformStream(transformer));
const reader = transformedStream.getReader();
signal.addEventListener("abort", () => {
queueMicrotask(() => {
reader.cancel();
controller.close();
});
});
while (true) {
const { done, value } = await reader.read();
if (done) {
controller.close();
break;
}
controller.enqueue(value);
}
},
}) as AsyncIterableStream<T>;
}
class ReadableShapeStream<T extends Row<unknown> = Row> {
readonly #stream: ShapeStreamInterface<T>;
readonly #currentState: Map<string, T> = new Map();
+1
View File
@@ -22,6 +22,7 @@ export * from "./types/index.js";
export { links } from "./links.js";
export * from "./jwt.js";
export * from "./idempotencyKeys.js";
export * from "./streams/asyncIterableStream.js";
export * from "./utils/getEnv.js";
export {
formatDuration,
+7 -3
View File
@@ -1,6 +1,6 @@
import { NoopTaskLogger, TaskLogger } from "./taskLogger.js";
import { getGlobal, registerGlobal, unregisterGlobal } from "../utils/globals.js";
import { Span } from "@opentelemetry/api";
import { Span, SpanOptions } from "@opentelemetry/api";
const API_NAME = "logger";
@@ -47,8 +47,12 @@ export class LoggerAPI implements TaskLogger {
this.#getTaskLogger().error(message, metadata);
}
public trace<T>(name: string, fn: (span: Span) => Promise<T>): Promise<T> {
return this.#getTaskLogger().trace(name, fn);
public trace<T>(name: string, fn: (span: Span) => Promise<T>, options?: SpanOptions): Promise<T> {
return this.#getTaskLogger().trace(name, fn, options);
}
public startSpan(name: string, options?: SpanOptions): Span {
return this.#getTaskLogger().startSpan(name, options);
}
#getTaskLogger(): TaskLogger {
@@ -24,6 +24,7 @@ export interface TaskLogger {
warn(message: string, properties?: Record<string, unknown>): void;
error(message: string, properties?: Record<string, unknown>): void;
trace<T>(name: string, fn: (span: Span) => Promise<T>, options?: SpanOptions): Promise<T>;
startSpan(name: string, options?: SpanOptions): Span;
}
export class OtelTaskLogger implements TaskLogger {
@@ -90,6 +91,10 @@ export class OtelTaskLogger implements TaskLogger {
return this._config.tracer.startActiveSpan(name, fn, options);
}
startSpan(name: string, options?: SpanOptions): Span {
return this._config.tracer.startSpan(name, options);
}
#getTimestampInHrTime(): ClockTime {
return clock.preciseNow();
}
@@ -104,6 +109,9 @@ export class NoopTaskLogger implements TaskLogger {
trace<T>(name: string, fn: (span: Span) => Promise<T>): Promise<T> {
return fn({} as Span);
}
startSpan(): Span {
return {} as Span;
}
}
function safeJsonProcess(value?: Record<string, unknown>): Record<string, unknown> | undefined {
+1 -1
View File
@@ -1,5 +1,5 @@
import { DeserializedJson } from "../../schemas/json.js";
import { AsyncIterableStream } from "../apiClient/stream.js";
import { AsyncIterableStream } from "../streams/asyncIterableStream.js";
import { getGlobal, registerGlobal } from "../utils/globals.js";
import { ApiRequestOptions } from "../zodfetch.js";
import { NoopRunMetadataManager } from "./noopManager.js";
+1 -1
View File
@@ -1,12 +1,12 @@
import { dequal } from "dequal/lite";
import { DeserializedJson } from "../../schemas/json.js";
import { ApiClient } from "../apiClient/index.js";
import { AsyncIterableStream } from "../apiClient/stream.js";
import { FlushedRunMetadata, RunMetadataChangeOperation } from "../schemas/common.js";
import { ApiRequestOptions } from "../zodfetch.js";
import { MetadataStream } from "./metadataStream.js";
import { applyMetadataOperations } from "./operations.js";
import { RunMetadataManager, RunMetadataUpdater } from "./types.js";
import { AsyncIterableStream } from "../streams/asyncIterableStream.js";
const MAXIMUM_ACTIVE_STREAMS = 5;
const MAXIMUM_TOTAL_STREAMS = 10;
@@ -1,5 +1,5 @@
import { DeserializedJson } from "../../schemas/json.js";
import { AsyncIterableStream } from "../apiClient/stream.js";
import { AsyncIterableStream } from "../streams/asyncIterableStream.js";
import { ApiRequestOptions } from "../zodfetch.js";
import type { RunMetadataManager, RunMetadataUpdater } from "./types.js";
+1 -1
View File
@@ -1,5 +1,5 @@
import { DeserializedJson } from "../../schemas/json.js";
import { AsyncIterableStream } from "../apiClient/stream.js";
import { AsyncIterableStream } from "../streams/asyncIterableStream.js";
import { ApiRequestOptions } from "../zodfetch.js";
export interface RunMetadataUpdater {
@@ -0,0 +1,97 @@
export type AsyncIterableStream<T> = AsyncIterable<T> & ReadableStream<T>;
export function createAsyncIterableStream<S, T>(
source: ReadableStream<S>,
transformer: Transformer<S, T>
): AsyncIterableStream<T> {
const transformedStream: any = source.pipeThrough(new TransformStream(transformer));
transformedStream[Symbol.asyncIterator] = () => {
const reader = transformedStream.getReader();
return {
async next(): Promise<IteratorResult<string>> {
const { done, value } = await reader.read();
return done ? { done: true, value: undefined } : { done: false, value };
},
};
};
return transformedStream;
}
export function createAsyncIterableReadable<S, T>(
source: ReadableStream<S>,
transformer: Transformer<S, T>,
signal: AbortSignal
): AsyncIterableStream<T> {
return new ReadableStream<T>({
async start(controller) {
const transformedStream = source.pipeThrough(new TransformStream(transformer));
const reader = transformedStream.getReader();
signal.addEventListener("abort", () => {
queueMicrotask(() => {
reader.cancel();
controller.close();
});
});
while (true) {
const { done, value } = await reader.read();
if (done) {
controller.close();
break;
}
controller.enqueue(value);
}
},
}) as AsyncIterableStream<T>;
}
export function createAsyncIterableStreamFromAsyncIterable<T>(
asyncIterable: AsyncIterable<T>,
transformer: Transformer<T, T>,
signal?: AbortSignal
): AsyncIterableStream<T> {
const stream = new ReadableStream<T>({
async start(controller) {
try {
if (signal) {
signal.addEventListener("abort", () => {
controller.close();
});
}
const iterator = asyncIterable[Symbol.asyncIterator]();
while (true) {
if (signal?.aborted) {
break;
}
const { done, value } = await iterator.next();
if (done) {
controller.close();
break;
}
controller.enqueue(value);
}
} catch (error) {
controller.error(error);
}
},
cancel() {
// If the stream is a tinyexec process with a kill method, kill it
if ("kill" in asyncIterable) {
(asyncIterable as any).kill();
}
},
});
const transformedStream = stream.pipeThrough(new TransformStream(transformer));
return transformedStream as AsyncIterableStream<T>;
}
+45 -9
View File
@@ -14,7 +14,7 @@ This extension introduces the <code>pythonExtension</code> build extension, whic
- <code>run</code>: Executes Python commands with proper environment setup.
- <code>runInline</code>: Executes inline Python code directly from Node.
- <code>runScript</code>: Executes standalone <code>.py</code> script files.
- **Custom Python Path:** In development, you can configure <code>pythonBinaryPath</code> to point to a custom Python installation.
- **Custom Python Path:** In development, you can configure <code>devPythonBinaryPath</code> to point to a custom Python installation.
## Usage
@@ -22,7 +22,7 @@ This extension introduces the <code>pythonExtension</code> build extension, whic
```typescript
import { defineConfig } from "@trigger.dev/sdk/v3";
import pythonExtension from "@trigger.dev/python/extension";
import { pythonExtension } from "@trigger.dev/python/extension";
export default defineConfig({
project: "<project ref>",
@@ -30,8 +30,8 @@ export default defineConfig({
extensions: [
pythonExtension({
requirementsFile: "./requirements.txt", // Optional: Path to your requirements file
pythonBinaryPath: path.join(rootDir, `.venv/bin/python`), // Optional: Custom Python binary path
scripts: ["my_script.py"], // List of Python scripts to include
devPythonBinaryPath: ".venv/bin/python", // Optional: Custom Python binary path
scripts: ["src/python/**/*.py"], // Glob pattern for Python scripts
}),
],
},
@@ -40,13 +40,34 @@ export default defineConfig({
2. (Optional) Create a <code>requirements.txt</code> file in your project root with the necessary Python dependencies.
```plaintext title="requirements.txt"
pandas==1.3.3
numpy==1.21.2
```
```typescript title="trigger.config.ts"
import { defineConfig } from "@trigger.dev/sdk/v3";
import { pythonExtension } from "@trigger.dev/python/extension";
export default defineConfig({
project: "<project ref>",
build: {
extensions: [
pythonExtension({
requirementsFile: "./requirements.txt",
}),
],
},
});
```
3. Execute Python scripts within your tasks using one of the provided functions:
### Running a Python Script
```typescript
import { task } from "@trigger.dev/sdk/v3";
import python from "@trigger.dev/python";
import { python } from "@trigger.dev/python";
export const myScript = task({
id: "my-python-script",
@@ -55,13 +76,29 @@ export const myScript = task({
return result.stdout;
},
});
export const myStreamingScript = task({
id: "my-streaming-python-script",
run: async () => {
// You can also stream the output of the script
const result = python.stream.runScript("my_script.py", ["hello", "world"]);
// result is an async iterable/readable stream
for await (const chunk of streamingResult) {
logger.debug("convert-url-to-markdown", {
url: payload.url,
chunk,
});
}
},
});
```
### Running Inline Python Code
```typescript
import { task } from "@trigger.dev/sdk/v3";
import python from "@trigger.dev/python";
import { python } from "@trigger.dev/python";
export const myTask = task({
id: "to_datetime-task",
@@ -69,7 +106,7 @@ export const myTask = task({
const result = await python.runInline(`
import pandas as pd
pandas.to_datetime("${+new Date() / 1000}")
pd.to_datetime("${+new Date() / 1000}")
`);
return result.stdout;
},
@@ -80,7 +117,7 @@ pandas.to_datetime("${+new Date() / 1000}")
```typescript
import { task } from "@trigger.dev/sdk/v3";
import python from "@trigger.dev/python";
import { python } from "@trigger.dev/python";
export const pythonVersionTask = task({
id: "python-version-task",
@@ -94,7 +131,6 @@ export const pythonVersionTask = task({
## Limitations
- This is a **partial implementation** and does not provide full Python support as an execution runtime for tasks.
- Only basic Python script execution is supported; scripts are not automatically copied to staging/production containers.
- Manual intervention may be required for installing and configuring binary dependencies in development environments.
## Additional Information
+7 -3
View File
@@ -45,9 +45,7 @@
"check-exports": "attw --pack ."
},
"dependencies": {
"@trigger.dev/build": "workspace:3.3.16",
"@trigger.dev/core": "workspace:3.3.16",
"@trigger.dev/sdk": "workspace:3.3.16",
"tinyexec": "^0.3.2"
},
"devDependencies": {
@@ -57,7 +55,13 @@
"typescript": "^5.5.4",
"tsx": "4.17.0",
"esbuild": "^0.23.0",
"@arethetypeswrong/cli": "^0.15.4"
"@arethetypeswrong/cli": "^0.15.4",
"@trigger.dev/build": "workspace:3.3.16",
"@trigger.dev/sdk": "workspace:3.3.16"
},
"peerDependencies": {
"@trigger.dev/sdk": "workspace:^3.3.16",
"@trigger.dev/build": "workspace:^3.3.16"
},
"engines": {
"node": ">=18.20.0"
+59 -22
View File
@@ -1,6 +1,6 @@
import fs from "node:fs";
import assert from "node:assert";
import { additionalFiles } from "@trigger.dev/build/extensions/core";
import { addAdditionalFilesToBuild } from "@trigger.dev/build/internal";
import { BuildManifest } from "@trigger.dev/core/v3";
import { BuildContext, BuildExtension } from "@trigger.dev/core/v3/build";
@@ -16,7 +16,7 @@ export type PythonOptions = {
*
* Example: `/usr/bin/python3` or `C:\\Python39\\python.exe`
*/
pythonBinaryPath?: string;
devPythonBinaryPath?: string;
/**
* An array of glob patterns that specify which Python scripts are allowed to be executed.
*
@@ -57,13 +57,18 @@ class PythonExtension implements BuildExtension {
}
async onBuildComplete(context: BuildContext, manifest: BuildManifest) {
await additionalFiles({
files: this.options.scripts ?? [],
}).onBuildComplete!(context, manifest);
await addAdditionalFilesToBuild(
"pythonExtension",
{
files: this.options.scripts ?? [],
},
context,
manifest
);
if (context.target === "dev") {
if (this.options.pythonBinaryPath) {
process.env.PYTHON_BIN_PATH = this.options.pythonBinaryPath;
if (this.options.devPythonBinaryPath) {
process.env.PYTHON_BIN_PATH = this.options.devPythonBinaryPath;
}
return;
@@ -93,27 +98,59 @@ class PythonExtension implements BuildExtension {
},
});
context.addLayer({
id: "python-dependencies",
build: {
env: {
REQUIREMENTS_CONTENT: this.options.requirements?.join("\n") || "",
if (this.options.requirementsFile) {
if (this.options.requirements) {
context.logger.warn(
`[pythonExtension] Both options.requirements and options.requirementsFile are specified. requirements will be ignored.`
);
}
// Copy requirements file to the container
await addAdditionalFilesToBuild(
"pythonExtension",
{
files: [this.options.requirementsFile],
},
},
image: {
instructions: splitAndCleanComments(`
context,
manifest
);
// Add a layer to the build that installs the requirements
context.addLayer({
id: "python-dependencies",
image: {
instructions: splitAndCleanComments(`
# Copy the requirements file
COPY ${this.options.requirementsFile} .
# Install dependencies
RUN pip install --no-cache-dir -r ${this.options.requirementsFile}
`),
},
deploy: {
override: true,
},
});
} else if (this.options.requirements) {
context.addLayer({
id: "python-dependencies",
build: {
env: {
REQUIREMENTS_CONTENT: this.options.requirements?.join("\n") || "",
},
},
image: {
instructions: splitAndCleanComments(`
ARG REQUIREMENTS_CONTENT
RUN echo "$REQUIREMENTS_CONTENT" > requirements.txt
# Install dependencies
RUN pip install --no-cache-dir -r requirements.txt
`),
},
deploy: {
override: true,
},
});
},
deploy: {
override: true,
},
});
}
}
}
export default pythonExtension;
+267 -53
View File
@@ -1,63 +1,277 @@
import fs from "node:fs";
import assert from "node:assert";
import {
AsyncIterableStream,
createAsyncIterableStreamFromAsyncIterable,
SemanticInternalAttributes,
} from "@trigger.dev/core/v3";
import { logger } from "@trigger.dev/sdk/v3";
import { x, Options as XOptions, Result } from "tinyexec";
import assert from "node:assert";
import fs from "node:fs";
import { Result, x, Options as XOptions } from "tinyexec";
import { createTempFileSync, withTempFile } from "./utils/tempFiles.js";
export const run = async (
scriptArgs: string[] = [],
options: Partial<XOptions> = {}
): Promise<Result> => {
const pythonBin = process.env.PYTHON_BIN_PATH || "python";
return await logger.trace("Python call", async (span) => {
span.addEvent("Properties", {
command: `${pythonBin} ${scriptArgs.join(" ")}`,
});
const result = await x(pythonBin, scriptArgs, {
...options,
throwOnError: false, // Ensure errors are handled manually
});
span.addEvent("Output", { ...result });
if (result.exitCode !== 0) {
logger.error(result.stderr, { ...result });
throw new Error(`Python command exited with non-zero code ${result.exitCode}`);
}
return result;
});
export type PythonExecOptions = Partial<XOptions> & {
env?: { [key: string]: string | undefined };
};
export const runScript = (
scriptPath: string,
scriptArgs: string[] = [],
options: Partial<XOptions> = {}
) => {
assert(scriptPath, "Script path is required");
assert(fs.existsSync(scriptPath), `Script does not exist: ${scriptPath}`);
export const python = {
async run(scriptArgs: string[] = [], options: PythonExecOptions = {}): Promise<Result> {
const pythonBin = process.env.PYTHON_BIN_PATH || "python";
return run([scriptPath, ...scriptArgs], options);
};
return await logger.trace(
"python.run()",
async (span) => {
const result = await x(pythonBin, scriptArgs, {
...options,
nodeOptions: {
...(options.nodeOptions || {}),
env: {
...process.env,
...options.env,
},
},
throwOnError: false, // Ensure errors are handled manually
});
export const runInline = async (scriptContent: string, options: Partial<XOptions> = {}) => {
assert(scriptContent, "Script content is required");
if (result.exitCode) {
span.setAttribute("exitCode", result.exitCode);
}
const tmpFile = `/tmp/script_${Date.now()}.py`;
await fs.promises.writeFile(tmpFile, scriptContent, { mode: 0o600 });
if (result.exitCode !== 0) {
throw new Error(
`${scriptArgs.join(" ")} exited with a non-zero code ${result.exitCode}:\n${
result.stderr
}`
);
}
try {
return await runScript(tmpFile, [], options);
} finally {
try {
await fs.promises.unlink(tmpFile);
} catch (error) {
logger.warn(`Failed to clean up temporary file ${tmpFile}:`, {
error: (error as Error).stack || (error as Error).message,
return result;
},
{
attributes: {
pythonBin,
args: scriptArgs.join(" "),
[SemanticInternalAttributes.STYLE_ICON]: "brand-python",
},
}
);
},
async runScript(
scriptPath: string,
scriptArgs: string[] = [],
options: PythonExecOptions = {}
): Promise<Result> {
assert(scriptPath, "Script path is required");
assert(fs.existsSync(scriptPath), `Script does not exist: ${scriptPath}`);
return await logger.trace(
"python.runScript()",
async (span) => {
span.setAttribute("scriptPath", scriptPath);
const result = await x(
process.env.PYTHON_BIN_PATH || "python",
[scriptPath, ...scriptArgs],
{
...options,
nodeOptions: {
...(options.nodeOptions || {}),
env: {
...process.env,
...options.env,
},
},
throwOnError: false,
}
);
if (result.exitCode) {
span.setAttribute("exitCode", result.exitCode);
}
if (result.exitCode !== 0) {
throw new Error(
`${scriptPath} ${scriptArgs.join(" ")} exited with a non-zero code ${
result.exitCode
}:\n${result.stderr}`
);
}
return result;
},
{
attributes: {
pythonBin: process.env.PYTHON_BIN_PATH || "python",
scriptPath,
args: scriptArgs.join(" "),
[SemanticInternalAttributes.STYLE_ICON]: "brand-python",
},
}
);
},
async runInline(scriptContent: string, options: PythonExecOptions = {}): Promise<Result> {
assert(scriptContent, "Script content is required");
return await logger.trace(
"python.runInline()",
async (span) => {
span.setAttribute("contentLength", scriptContent.length);
// Using the withTempFile utility to handle the temporary file
return await withTempFile(
`script_${Date.now()}.py`,
async (tempFilePath) => {
span.setAttribute("tempFilePath", tempFilePath);
const pythonBin = process.env.PYTHON_BIN_PATH || "python";
const result = await x(pythonBin, [tempFilePath], {
...options,
nodeOptions: {
...(options.nodeOptions || {}),
env: {
...process.env,
...options.env,
},
},
throwOnError: false,
});
if (result.exitCode) {
span.setAttribute("exitCode", result.exitCode);
}
if (result.exitCode !== 0) {
throw new Error(
`Inline script exited with a non-zero code ${result.exitCode}:\n${result.stderr}`
);
}
return result;
},
scriptContent
);
},
{
attributes: {
pythonBin: process.env.PYTHON_BIN_PATH || "python",
contentPreview:
scriptContent.substring(0, 100) + (scriptContent.length > 100 ? "..." : ""),
[SemanticInternalAttributes.STYLE_ICON]: "brand-python",
},
}
);
},
// Stream namespace for streaming functions
stream: {
run(scriptArgs: string[] = [], options: PythonExecOptions = {}): AsyncIterableStream<string> {
const pythonBin = process.env.PYTHON_BIN_PATH || "python";
const pythonProcess = x(pythonBin, scriptArgs, {
...options,
nodeOptions: {
...(options.nodeOptions || {}),
env: {
...process.env,
...options.env,
},
},
throwOnError: false,
});
}
}
};
export default { run, runScript, runInline };
const span = logger.startSpan("python.stream.run()", {
attributes: {
pythonBin,
args: scriptArgs.join(" "),
[SemanticInternalAttributes.STYLE_ICON]: "brand-python",
},
});
return createAsyncIterableStreamFromAsyncIterable(pythonProcess, {
transform: (chunk, controller) => {
controller.enqueue(chunk);
},
flush: () => {
span.end();
},
});
},
runScript(
scriptPath: string,
scriptArgs: string[] = [],
options: PythonExecOptions = {}
): AsyncIterableStream<string> {
assert(scriptPath, "Script path is required");
assert(fs.existsSync(scriptPath), `Script does not exist: ${scriptPath}`);
const pythonBin = process.env.PYTHON_BIN_PATH || "python";
const pythonProcess = x(pythonBin, [scriptPath, ...scriptArgs], {
...options,
nodeOptions: {
...(options.nodeOptions || {}),
env: {
...process.env,
...options.env,
},
},
throwOnError: false,
});
const span = logger.startSpan("python.stream.runScript()", {
attributes: {
pythonBin,
scriptPath,
args: scriptArgs.join(" "),
[SemanticInternalAttributes.STYLE_ICON]: "brand-python",
},
});
return createAsyncIterableStreamFromAsyncIterable(pythonProcess, {
transform: (chunk, controller) => {
controller.enqueue(chunk);
},
flush: () => {
span.end();
},
});
},
runInline(scriptContent: string, options: PythonExecOptions = {}): AsyncIterableStream<string> {
assert(scriptContent, "Script content is required");
const pythonBin = process.env.PYTHON_BIN_PATH || "python";
const pythonScriptPath = createTempFileSync(`script_${Date.now()}.py`, scriptContent);
const pythonProcess = x(pythonBin, [pythonScriptPath], {
...options,
nodeOptions: {
...(options.nodeOptions || {}),
env: {
...process.env,
...options.env,
},
},
throwOnError: false,
});
const span = logger.startSpan("python.stream.runInline()", {
attributes: {
pythonBin,
contentPreview:
scriptContent.substring(0, 100) + (scriptContent.length > 100 ? "..." : ""),
[SemanticInternalAttributes.STYLE_ICON]: "brand-python",
},
});
return createAsyncIterableStreamFromAsyncIterable(pythonProcess, {
transform: (chunk, controller) => {
controller.enqueue(chunk);
},
flush: () => {
span.end();
},
});
},
},
};
+39
View File
@@ -0,0 +1,39 @@
import { mkdtempSync, writeFileSync } from "node:fs";
import { mkdtemp, writeFile, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
/**
* Creates a temporary file with a custom filename, passes it to the callback function, and ensures cleanup
* @param filename The filename to use for the temporary file
* @param callback Function that receives the path to the temporary file
* @param content Optional content to write to the file
* @returns Whatever the callback returns
*/
export async function withTempFile<T>(
filename: string,
callback: (filePath: string) => Promise<T>,
content: string | Buffer = ""
): Promise<T> {
// Create temporary directory with random suffix
const tempDir = await mkdtemp(join(tmpdir(), "app-"));
const tempFile = join(tempDir, filename);
try {
// Write to the temporary file with appropriate permissions
await writeFile(tempFile, content, { mode: 0o600 });
// Use the file
return await callback(tempFile);
} finally {
// Clean up
await rm(tempDir, { recursive: true, force: true });
}
}
export function createTempFileSync(filename: string, content: string | Buffer = ""): string {
const tempDir = mkdtempSync(join(tmpdir(), "app-"));
const tempFile = join(tempDir, filename);
writeFileSync(tempFile, content, { mode: 0o600 });
return tempFile;
}
+28 -6
View File
@@ -1419,15 +1419,9 @@ importers:
packages/python:
dependencies:
'@trigger.dev/build':
specifier: workspace:3.3.16
version: link:../build
'@trigger.dev/core':
specifier: workspace:3.3.16
version: link:../core
'@trigger.dev/sdk':
specifier: workspace:3.3.16
version: link:../trigger-sdk
tinyexec:
specifier: ^0.3.2
version: 0.3.2
@@ -1435,6 +1429,12 @@ importers:
'@arethetypeswrong/cli':
specifier: ^0.15.4
version: 0.15.4
'@trigger.dev/build':
specifier: workspace:3.3.16
version: link:../build
'@trigger.dev/sdk':
specifier: workspace:3.3.16
version: link:../trigger-sdk
'@types/node':
specifier: 20.14.14
version: 20.14.14
@@ -1781,6 +1781,28 @@ importers:
specifier: ^5
version: 5.5.4
references/python-catalog:
dependencies:
'@trigger.dev/python':
specifier: workspace:*
version: link:../../packages/python
'@trigger.dev/sdk':
specifier: workspace:*
version: link:../../packages/trigger-sdk
zod:
specifier: 3.23.8
version: 3.23.8
devDependencies:
'@trigger.dev/build':
specifier: workspace:*
version: link:../../packages/build
trigger.dev:
specifier: workspace:*
version: link:../../packages/cli-v3
typescript:
specifier: ^5.5.4
version: 5.5.4
references/test-tasks:
dependencies:
'@trigger.dev/sdk':
+20
View File
@@ -0,0 +1,20 @@
{
"name": "references-python-catalog",
"private": true,
"type": "module",
"scripts": {
"dev": "trigger dev",
"deploy": "trigger deploy --self-hosted --load-image",
"install-python-deps": "uv pip sync requirements.txt"
},
"dependencies": {
"@trigger.dev/sdk": "workspace:*",
"@trigger.dev/python": "workspace:*",
"zod": "3.23.8"
},
"devDependencies": {
"@trigger.dev/build": "workspace:*",
"trigger.dev": "workspace:*",
"typescript": "^5.5.4"
}
}
@@ -0,0 +1,5 @@
html2text==2024.2.26
requests==2.32.3
urllib3==2.3.0
idna==3.10
certifi==2025.1.31
@@ -0,0 +1,37 @@
import html2text
import requests
import argparse
import sys
def fetch_html(url):
"""Fetch HTML content from a URL."""
try:
response = requests.get(url)
response.raise_for_status() # Raise an exception for HTTP errors
return response.text
except requests.exceptions.RequestException as e:
print(f"Error fetching URL: {e}", file=sys.stderr)
sys.exit(1)
def main():
# Set up command line argument parsing
parser = argparse.ArgumentParser(description='Convert HTML from a URL to plain text.')
parser.add_argument('url', help='The URL to fetch HTML from')
parser.add_argument('--ignore-links', action='store_true',
help='Ignore converting links from HTML')
args = parser.parse_args()
# Fetch HTML from the URL
html_content = fetch_html(args.url)
# Configure html2text
h = html2text.HTML2Text()
h.ignore_links = args.ignore_links
# Convert HTML to text and print
text_content = h.handle(html_content)
print(text_content)
if __name__ == "__main__":
main()
@@ -0,0 +1,68 @@
import { logger, schemaTask, task } from "@trigger.dev/sdk/v3";
import { python } from "@trigger.dev/python";
import { z } from "zod";
export const convertUrlToMarkdown = schemaTask({
id: "convert-url-to-markdown",
schema: z.object({
url: z.string().url(),
}),
run: async (payload) => {
const result = await python.runScript("./src/python/html2text_url.py", [payload.url]);
logger.debug("convert-url-to-markdown", {
url: payload.url,
output: result.stdout,
});
const streamingResult = python.stream.runScript("./src/python/html2text_url.py", [payload.url]);
for await (const chunk of streamingResult) {
logger.debug("convert-url-to-markdown", {
url: payload.url,
chunk,
});
}
},
});
export const pythonRunInlineTask = task({
id: "python-run-inline",
run: async () => {
const result = await python.runInline(
`
import os
import html2text as h2t
h = h2t.HTML2Text()
print(h.handle("<p>Hello, <a href='https://www.google.com/earth/'>world</a>!"))
print(f"API Key: {os.environ['OPENAI_API_KEY']}")
`,
{
env: {
OPENAI_API_KEY: "sk-1234567890",
},
}
);
console.log(result.stdout);
const streamingResult = python.stream.runInline(`
import html2text as h2t
h = h2t.HTML2Text()
print(h.handle("<p>Hello, <a href='https://www.google.com/earth/'>world</a>!"))
print(h.handle("<p>Hello, <a href='https://www.google.com/earth/'>world</a>!"))
`);
for await (const chunk of streamingResult) {
logger.debug("python-run-inline", {
chunk,
});
}
return result.stdout;
},
});
@@ -0,0 +1,29 @@
import { defineConfig } from "@trigger.dev/sdk/v3";
import { pythonExtension } from "@trigger.dev/python/extension";
export default defineConfig({
runtime: "node",
project: "proj_hbsqkjxevkyuklehrgrp",
machine: "small-1x",
maxDuration: 3600,
dirs: ["./src/trigger"],
build: {
extensions: [
pythonExtension({
requirementsFile: "./requirements.txt", // Optional: Path to your requirements file
devPythonBinaryPath: `.venv/bin/python`, // Optional: Custom Python binary path
scripts: ["src/python/**/*.py"], // List of Python scripts to include
}),
],
},
retries: {
enabledInDev: true,
default: {
maxAttempts: 3,
minTimeoutInMs: 1_000,
maxTimeoutInMs: 5_000,
factor: 1.6,
randomize: true,
},
},
});
+20
View File
@@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "esnext",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"esModuleInterop": true,
"strict": true,
"outDir": "dist",
"skipLibCheck": true,
"customConditions": ["@triggerdotdev/source"],
"jsx": "preserve",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"lib": ["DOM", "DOM.Iterable"],
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["./src/**/*.ts", "trigger.config.ts"]
}