Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,14 @@ apps/**/public/build
/packages/trigger-sdk/src/package.json
/packages/python/src/package.json
.claude
.mcp.log
.mcp.log

# Python
__pycache__/
*.py[cod]
*$py.class
*.egg-info/
.mypy_cache/
.pytest_cache/
.venv/
build/
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@
"check-exports": "turbo run check-exports",
"clean": "turbo run clean",
"clean:node_modules": "find . -name 'node_modules' -type d -prune -exec rm -rf '{}' +",
"clean:python": "find . -name '*.pyc' -delete && find . -name '__pycache__' -type d -exec rm -rf '{}' + 2>/dev/null || true",
"clean:all": "pnpm run clean && pnpm run clean:python",
"rebuild:cli": "pnpm run clean:python && pnpm run build --filter trigger.dev",
"typecheck": "turbo run typecheck",
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui",
Expand Down
5 changes: 4 additions & 1 deletion packages/cli-v3/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@
"scripts": {
"clean": "rimraf dist .tshy .tshy-build .turbo",
"typecheck": "tsc -p tsconfig.src.json --noEmit",
"build": "tshy && pnpm run update-version",
"build": "tshy && pnpm run update-version && pnpm run copy-python",
"copy-python": "mkdir -p dist/esm/entryPoints/python && cp src/entryPoints/python/*.py dist/esm/entryPoints/python/",
"dev": "tshy --watch",
"test": "vitest",
"test:e2e": "vitest --run -c ./e2e/vitest.config.ts",
Expand All @@ -83,6 +84,8 @@
"dependencies": {
"@clack/prompts": "0.11.0",
"@depot/cli": "0.0.1-cli.2.80.0",
"@grpc/grpc-js": "^1.12.4",
"@grpc/proto-loader": "^0.7.15",
"@modelcontextprotocol/sdk": "^1.17.0",
"@opentelemetry/api": "1.9.0",
"@opentelemetry/api-logs": "0.203.0",
Expand Down
95 changes: 94 additions & 1 deletion packages/cli-v3/src/build/bundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { DEFAULT_RUNTIME, ResolvedConfig } from "@trigger.dev/core/v3/build";
import { BuildManifest, BuildTarget, TaskFile } from "@trigger.dev/core/v3/schemas";
import * as esbuild from "esbuild";
import { createHash } from "node:crypto";
import { join, relative, resolve } from "node:path";
import path, { join, relative, resolve } from "node:path";
import { writeFile } from "node:fs/promises";
import { createFile } from "../utilities/fileSystem.js";
import { logger } from "../utilities/logger.js";
import { resolveFileSources } from "../utilities/sourceFiles.js";
Expand All @@ -26,6 +27,7 @@ import {
import { buildPlugins } from "./plugins.js";
import { cliLink, prettyError } from "../utilities/cliOutput.js";
import { SkipLoggingError } from "../cli/common.js";
import { bundlePython, createBuildManifestFromPythonBundle } from "./pythonBundler.js";

export interface BundleOptions {
target: BuildTarget;
Expand Down Expand Up @@ -65,6 +67,11 @@ export class BundleError extends Error {
export async function bundleWorker(options: BundleOptions): Promise<BundleResult> {
const { resolvedConfig } = options;

// Handle Python runtime
if (resolvedConfig.runtime === "python") {
return bundlePythonWorker(options);
}

let currentContext: esbuild.BuildContext | undefined;

const entryPointManager = await createEntryPointManager(
Expand Down Expand Up @@ -405,3 +412,89 @@ export async function createBuildManifestFromBundle({

return copyManifestToDir(buildManifest, destination, workerDir);
}

/**
* Bundle Python worker - entry point for Python runtime.
* This is the Python equivalent of bundleWorker for Node.js.
*/
async function bundlePythonWorker(options: BundleOptions): Promise<BundleResult> {
const { resolvedConfig, destination, cwd, target } = options;

const entryPointManager = await createEntryPointManager(
resolvedConfig.dirs,
resolvedConfig,
options.target,
typeof options.watch === "boolean" ? options.watch : false,
async (newEntryPoints) => {
// TODO: Implement proper watch mode for Python (file copying + manifest regeneration)
logger.debug("Python entry points changed, rebuilding");
}
Comment on lines +428 to +431
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Restore watch-mode rebuilds for Python bundles

In watch mode this callback fires on every .py change, but we only emit a debug log. That means dev-mode hot reload never refreshes the copied sources or manifest—developers must restart the CLI to pick up edits. Please mirror the Node path by re-running bundlePython (and rewriting the manifest/output) whenever entry points change before we ship this. Until we do, Python dev workflows break after the first build.

🤖 Prompt for AI Agents
In packages/cli-v3/src/build/bundle.ts around lines 428 to 431, the watch-mode
callback for Python entry point changes currently only logs a debug message and
does not rebuild assets; update the callback to mirror the Node path by invoking
bundlePython (or the existing Python bundling routine) when newEntryPoints are
passed, ensure the bundle process copies updated .py sources and
regenerates/writes the manifest and output files, handle errors by logging and
not crashing the watcher, and keep behavior consistent with the Node watcher
(debounce rapid events if necessary) so dev hot-reload picks up Python edits
without restarting the CLI.

);

if (entryPointManager.entryPoints.length === 0) {
const errorMessageBody = `
Dirs config:
${resolvedConfig.dirs.join("\n- ")}

Search patterns:
${entryPointManager.patterns.join("\n- ")}

Possible solutions:
1. Check if the directory paths in your config are correct
2. Verify that your files match the search patterns
3. Update your trigger.config.ts runtime to "python"
`.replace(/^ {6}/gm, "");

prettyError(
"No Python task files found",
errorMessageBody,
cliLink("View the config docs", "https://trigger.dev/docs/config/config-file")
);

throw new SkipLoggingError();
}

// Bundle Python files
logger.debug("Starting Python bundle", {
entryPoints: entryPointManager.entryPoints.length,
});

const bundleResult = await bundlePython({
entryPoints: entryPointManager.entryPoints,
outputDir: destination,
projectDir: cwd,
requirementsFile: process.env.TRIGGER_REQUIREMENTS_FILE,
config: resolvedConfig,
target,
});

// Create complete BuildManifest
const buildManifest = await createBuildManifestFromPythonBundle(bundleResult, {
outputDir: destination,
config: resolvedConfig,
target,
});

// Write manifest to output
const manifestPath = join(destination, "build-manifest.json");
await writeFile(manifestPath, JSON.stringify(buildManifest, null, 2));

// Convert to BundleResult
const pythonBundleResult: BundleResult = {
contentHash: buildManifest.contentHash,
files: buildManifest.files,
configPath: buildManifest.configPath,
metafile: {} as esbuild.Metafile, // Empty for Python
loaderEntryPoint: undefined,
runWorkerEntryPoint: buildManifest.runWorkerEntryPoint,
runControllerEntryPoint: undefined,
indexWorkerEntryPoint: buildManifest.indexWorkerEntryPoint,
indexControllerEntryPoint: undefined,
initEntryPoint: undefined,
stop: async () => {
await entryPointManager.stop();
},
};

return pythonBundleResult;
}
36 changes: 29 additions & 7 deletions packages/cli-v3/src/build/entryPoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,24 +33,46 @@ const DEFAULT_IGNORE_PATTERNS = [
"**/*.spec.cjs",
];

const PYTHON_IGNORE_PATTERNS = [
"**/__pycache__/**",
"**/venv/**",
"**/.venv/**",
"**/.pytest_cache/**",
"**/.mypy_cache/**",
"**/*.test.py",
"**/*.spec.py",
"**/test_*.py",
"**/tests/**",
];

export async function createEntryPointManager(
dirs: string[],
config: ResolvedConfig,
target: BuildTarget,
watch: boolean,
onEntryPointsChange?: (entryPoints: string[]) => Promise<void>
): Promise<EntryPointManager> {
// Determine file extension patterns based on runtime
const fileExtensions =
config.runtime === "python"
? ["*.py"]
: ["*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}"];

// Patterns to match files
const patterns = dirs.flatMap((dir) => [
`${
isDynamicPattern(dir)
? `${dir}/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}`
: `${escapePath(dir)}/**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}`
}`,
]);
const patterns = dirs.flatMap((dir) =>
fileExtensions.map((ext) =>
isDynamicPattern(dir) ? `${dir}/${ext}` : `${escapePath(dir)}/**/${ext}`
)
);

// Patterns to ignore
let ignorePatterns = config.ignorePatterns ?? DEFAULT_IGNORE_PATTERNS;

// Add Python-specific ignore patterns if runtime is python
if (config.runtime === "python") {
ignorePatterns = ignorePatterns.concat(PYTHON_IGNORE_PATTERNS);
}

ignorePatterns = ignorePatterns.concat([
"**/node_modules/**",
"**/.git/**",
Expand Down
Loading