fix(core): vendor superjson to fix ESM/CJS compatibility (#2949)
Bundle superjson and its dependency (copy-anything) during build to avoid ERR_REQUIRE_ESM errors on Node.js versions that don't support require(ESM) by default (< 22.12.0) and AWS Lambda which intentionally disables it. - Add scripts/bundle-superjson.mjs to bundle superjson with esbuild - Update build script to bundle vendor files before tshy compilation - Move superjson from dependencies to devDependencies - Update imports to use vendored bundles Fixes #2937 <!-- devin-review-badge-begin --> --- <a href="https://app.devin.ai/review/triggerdotdev/trigger.dev/pull/2949"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1"> <img src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1" alt="Open with Devin"> </picture> </a> <!-- devin-review-badge-end --> --------- Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Eric Allam <ericallam@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@trigger.dev/core": patch
|
||||
---
|
||||
|
||||
fix: vendor superjson to fix ESM/CJS compatibility
|
||||
|
||||
Bundle superjson during build to avoid `ERR_REQUIRE_ESM` errors on Node.js versions that don't support `require(ESM)` by default (< 22.12.0) and AWS Lambda which intentionally disables it.
|
||||
@@ -29,3 +29,7 @@ jobs:
|
||||
with:
|
||||
package: cli-v3
|
||||
secrets: inherit
|
||||
|
||||
sdk-compat:
|
||||
uses: ./.github/workflows/sdk-compat.yml
|
||||
secrets: inherit
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
name: "🔌 SDK Compatibility Tests"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
node-compat:
|
||||
name: "Node.js ${{ matrix.node }} (${{ matrix.os }})"
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
node: ["20.20", "22.12"]
|
||||
|
||||
steps:
|
||||
- name: ⬇️ Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: ⎔ Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10.23.0
|
||||
|
||||
- name: ⎔ Setup node
|
||||
uses: buildjet/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
cache: "pnpm"
|
||||
|
||||
- name: 📥 Download deps
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: 📀 Generate Prisma Client
|
||||
run: pnpm run generate
|
||||
|
||||
- name: 🔨 Build SDK dependencies
|
||||
shell: bash
|
||||
run: pnpm run build --filter '@trigger.dev/sdk^...'
|
||||
|
||||
- name: 🔨 Build SDK
|
||||
shell: bash
|
||||
run: pnpm run build --filter '@trigger.dev/sdk'
|
||||
|
||||
- name: 🧪 Run SDK Compatibility Tests
|
||||
shell: bash
|
||||
run: pnpm --filter @internal/sdk-compat-tests test
|
||||
|
||||
bun-compat:
|
||||
name: "Bun Runtime"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: ⬇️ Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: ⎔ Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10.23.0
|
||||
|
||||
- name: ⎔ Setup node
|
||||
uses: buildjet/setup-node@v4
|
||||
with:
|
||||
node-version: 20.20.0
|
||||
cache: "pnpm"
|
||||
|
||||
- name: 🥟 Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
- name: 📥 Download deps
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: 📀 Generate Prisma Client
|
||||
run: pnpm run generate
|
||||
|
||||
- name: 🔨 Build SDK dependencies
|
||||
run: pnpm run build --filter @trigger.dev/sdk^...
|
||||
|
||||
- name: 🔨 Build SDK
|
||||
run: pnpm run build --filter @trigger.dev/sdk
|
||||
|
||||
- name: 🧪 Run Bun Compatibility Test
|
||||
working-directory: internal-packages/sdk-compat-tests/src/fixtures/bun
|
||||
run: bun run test.ts
|
||||
|
||||
deno-compat:
|
||||
name: "Deno Runtime"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: ⬇️ Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: ⎔ Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10.23.0
|
||||
|
||||
- name: ⎔ Setup node
|
||||
uses: buildjet/setup-node@v4
|
||||
with:
|
||||
node-version: 20.20.0
|
||||
cache: "pnpm"
|
||||
|
||||
- name: 🦕 Setup Deno
|
||||
uses: denoland/setup-deno@v2
|
||||
with:
|
||||
deno-version: v2.x
|
||||
|
||||
- name: 📥 Download deps
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: 📀 Generate Prisma Client
|
||||
run: pnpm run generate
|
||||
|
||||
- name: 🔨 Build SDK dependencies
|
||||
run: pnpm run build --filter @trigger.dev/sdk^...
|
||||
|
||||
- name: 🔨 Build SDK
|
||||
run: pnpm run build --filter @trigger.dev/sdk
|
||||
|
||||
- name: 🔗 Link node_modules for Deno fixture
|
||||
working-directory: internal-packages/sdk-compat-tests/src/fixtures/deno
|
||||
run: ln -s ../../../../../node_modules node_modules
|
||||
|
||||
- name: 🧪 Run Deno Compatibility Test
|
||||
working-directory: internal-packages/sdk-compat-tests/src/fixtures/deno
|
||||
run: deno run --allow-read --allow-env --allow-sys test.ts
|
||||
|
||||
cloudflare-compat:
|
||||
name: "Cloudflare Workers"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: ⬇️ Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: ⎔ Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10.23.0
|
||||
|
||||
- name: ⎔ Setup node
|
||||
uses: buildjet/setup-node@v4
|
||||
with:
|
||||
node-version: 20.20.0
|
||||
cache: "pnpm"
|
||||
|
||||
- name: 📥 Download deps
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: 📀 Generate Prisma Client
|
||||
run: pnpm run generate
|
||||
|
||||
- name: 🔨 Build SDK dependencies
|
||||
run: pnpm run build --filter @trigger.dev/sdk^...
|
||||
|
||||
- name: 🔨 Build SDK
|
||||
run: pnpm run build --filter @trigger.dev/sdk
|
||||
|
||||
- name: 📥 Install Cloudflare fixture deps
|
||||
working-directory: internal-packages/sdk-compat-tests/src/fixtures/cloudflare-worker
|
||||
run: pnpm install
|
||||
|
||||
- name: 🧪 Run Cloudflare Workers Compatibility Test (dry-run)
|
||||
working-directory: internal-packages/sdk-compat-tests/src/fixtures/cloudflare-worker
|
||||
run: npx wrangler deploy --dry-run --outdir dist
|
||||
@@ -15,6 +15,9 @@ out/
|
||||
dist
|
||||
packages/**/dist
|
||||
|
||||
# vendored bundles (generated during build)
|
||||
packages/**/src/**/vendor
|
||||
|
||||
# Tailwind
|
||||
apps/**/styles/tailwind.css
|
||||
packages/**/styles/tailwind.css
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "@internal/sdk-compat-tests",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "vitest",
|
||||
"test:watch": "vitest",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@trigger.dev/sdk": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"esbuild": "^0.24.0",
|
||||
"execa": "^9.3.0",
|
||||
"typescript": "^5.5.0",
|
||||
"vitest": "3.1.4"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "bun-fixture",
|
||||
"private": true,
|
||||
"type": "module"
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Bun Import Test Fixture
|
||||
*
|
||||
* Tests that the SDK works correctly with Bun runtime.
|
||||
* Bun has high Node.js compatibility but uses its own module resolver.
|
||||
*/
|
||||
|
||||
import { task, logger, schedules, runs, configure, queue, retry, wait } from "@trigger.dev/sdk";
|
||||
|
||||
// Validate exports exist
|
||||
const checks: [string, boolean][] = [
|
||||
["task", typeof task === "function"],
|
||||
["logger", typeof logger === "object" && typeof logger.info === "function"],
|
||||
["schedules", typeof schedules === "object"],
|
||||
["runs", typeof runs === "object"],
|
||||
["configure", typeof configure === "function"],
|
||||
["queue", typeof queue === "function"],
|
||||
["retry", typeof retry === "object"],
|
||||
["wait", typeof wait === "object"],
|
||||
];
|
||||
|
||||
let failed = false;
|
||||
for (const [name, passed] of checks) {
|
||||
if (!passed) {
|
||||
console.error(`FAIL: ${name} export check failed`);
|
||||
failed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Test task definition with types
|
||||
interface Payload {
|
||||
message: string;
|
||||
}
|
||||
|
||||
const myTask = task({
|
||||
id: "bun-test-task",
|
||||
run: async (payload: Payload) => {
|
||||
return { received: payload.message };
|
||||
},
|
||||
});
|
||||
|
||||
if (myTask.id !== "bun-test-task") {
|
||||
console.error(`FAIL: task.id mismatch`);
|
||||
failed = true;
|
||||
}
|
||||
|
||||
// Test queue definition
|
||||
const myQueue = queue({
|
||||
name: "bun-test-queue",
|
||||
concurrencyLimit: 5,
|
||||
});
|
||||
|
||||
if (!myQueue) {
|
||||
console.error(`FAIL: queue creation failed`);
|
||||
failed = true;
|
||||
}
|
||||
|
||||
if (failed) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log("SUCCESS: Bun imports validated");
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "cjs-require-fixture",
|
||||
"private": true
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* CJS Require Test Fixture
|
||||
*
|
||||
* This file validates that the SDK can be required using CommonJS syntax.
|
||||
* This is critical for:
|
||||
* - Node.js < 22.12.0 (where require(ESM) is not enabled by default)
|
||||
* - AWS Lambda (intentionally disables require(ESM))
|
||||
* - Legacy Node.js applications
|
||||
*/
|
||||
|
||||
// Test main export
|
||||
const sdk = require("@trigger.dev/sdk");
|
||||
|
||||
// Test /v3 subpath
|
||||
const sdkV3 = require("@trigger.dev/sdk/v3");
|
||||
|
||||
// Validate exports exist
|
||||
const checks = [
|
||||
["task", typeof sdk.task === "function"],
|
||||
["taskV3", typeof sdkV3.task === "function"],
|
||||
["logger", typeof sdk.logger === "object" && typeof sdk.logger.info === "function"],
|
||||
["schedules", typeof sdk.schedules === "object"],
|
||||
["runs", typeof sdk.runs === "object"],
|
||||
["configure", typeof sdk.configure === "function"],
|
||||
["queue", typeof sdk.queue === "function"],
|
||||
["retry", typeof sdk.retry === "object"],
|
||||
["wait", typeof sdk.wait === "object"],
|
||||
["metadata", typeof sdk.metadata === "object"],
|
||||
["tags", typeof sdk.tags === "object"],
|
||||
];
|
||||
|
||||
let failed = false;
|
||||
for (const [name, passed] of checks) {
|
||||
if (!passed) {
|
||||
console.error(`FAIL: ${name} export check failed`);
|
||||
failed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Test task definition works
|
||||
const myTask = sdk.task({
|
||||
id: "cjs-test-task",
|
||||
run: async (payload) => {
|
||||
return { received: payload };
|
||||
},
|
||||
});
|
||||
|
||||
if (myTask.id !== "cjs-test-task") {
|
||||
console.error(`FAIL: task.id mismatch: expected "cjs-test-task", got "${myTask.id}"`);
|
||||
failed = true;
|
||||
}
|
||||
|
||||
if (failed) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log("SUCCESS: All CJS requires validated");
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "cloudflare-worker-fixture",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "wrangler deploy --dry-run --outdir dist"
|
||||
},
|
||||
"devDependencies": {
|
||||
"wrangler": "^3.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Cloudflare Worker Test Fixture
|
||||
*
|
||||
* Tests that the SDK can be bundled for Cloudflare Workers (workerd runtime).
|
||||
* This validates the bundling process works - actual execution would require
|
||||
* a Trigger.dev API connection.
|
||||
*/
|
||||
|
||||
import { task, runs, configure } from "@trigger.dev/sdk";
|
||||
|
||||
// Define a task (won't execute in worker, but validates import)
|
||||
const myTask = task({
|
||||
id: "cloudflare-test-task",
|
||||
run: async (payload: { message: string }) => {
|
||||
return { received: payload.message };
|
||||
},
|
||||
});
|
||||
|
||||
export default {
|
||||
async fetch(request: Request, env: unknown, ctx: ExecutionContext): Promise<Response> {
|
||||
// Validate SDK imports work
|
||||
const checks = {
|
||||
taskDefined: typeof task === "function",
|
||||
runsDefined: typeof runs === "object",
|
||||
configureDefined: typeof configure === "function",
|
||||
taskIdCorrect: myTask.id === "cloudflare-test-task",
|
||||
};
|
||||
|
||||
const allPassed = Object.values(checks).every((v) => v === true);
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: allPassed,
|
||||
checks,
|
||||
}),
|
||||
{
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
name = "sdk-compat-test"
|
||||
main = "src/index.ts"
|
||||
compatibility_date = "2024-01-01"
|
||||
compatibility_flags = ["nodejs_compat"]
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"tasks": {
|
||||
"test": "deno run --allow-read --allow-env --allow-sys test.ts"
|
||||
},
|
||||
"nodeModulesDir": "manual"
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Deno Import Test Fixture
|
||||
*
|
||||
* Tests that the SDK can be imported in Deno using Node.js compatibility.
|
||||
* The CI workflow installs the SDK into node_modules via npm for local resolution.
|
||||
*/
|
||||
|
||||
// Use bare specifier - resolved via node_modules when nodeModulesDir is enabled
|
||||
import { task, logger, schedules, runs, configure, queue, retry, wait, metadata, tags } from "@trigger.dev/sdk";
|
||||
|
||||
// Validate exports exist
|
||||
const checks: [string, boolean][] = [
|
||||
["task", typeof task === "function"],
|
||||
["logger", typeof logger === "object" && typeof logger.info === "function"],
|
||||
["schedules", typeof schedules === "object"],
|
||||
["runs", typeof runs === "object"],
|
||||
["configure", typeof configure === "function"],
|
||||
["queue", typeof queue === "function"],
|
||||
["retry", typeof retry === "object"],
|
||||
["wait", typeof wait === "object"],
|
||||
["metadata", typeof metadata === "object"],
|
||||
["tags", typeof tags === "object"],
|
||||
];
|
||||
|
||||
let failed = false;
|
||||
for (const [name, passed] of checks) {
|
||||
if (!passed) {
|
||||
console.error(`FAIL: ${name} export check failed`);
|
||||
failed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Test task definition with types
|
||||
interface Payload {
|
||||
message: string;
|
||||
}
|
||||
|
||||
const myTask = task({
|
||||
id: "deno-test-task",
|
||||
run: async (payload: Payload) => {
|
||||
return { received: payload.message };
|
||||
},
|
||||
});
|
||||
|
||||
if (myTask.id !== "deno-test-task") {
|
||||
console.error(`FAIL: task.id mismatch`);
|
||||
failed = true;
|
||||
}
|
||||
|
||||
// Test queue definition
|
||||
const myQueue = queue({
|
||||
name: "deno-test-queue",
|
||||
concurrencyLimit: 5,
|
||||
});
|
||||
|
||||
if (!myQueue) {
|
||||
console.error(`FAIL: queue creation failed`);
|
||||
failed = true;
|
||||
}
|
||||
|
||||
if (failed) {
|
||||
Deno.exit(1);
|
||||
}
|
||||
|
||||
console.log("SUCCESS: Deno imports validated");
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "esm-import-fixture",
|
||||
"private": true,
|
||||
"type": "module"
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* SuperJSON Serialization Test
|
||||
*
|
||||
* This validates the fix for #2937 - ESM/CJS compatibility with superjson.
|
||||
* Tests that complex types (Date, Set, Map, BigInt) serialize correctly.
|
||||
*/
|
||||
|
||||
import { task, logger } from "@trigger.dev/sdk";
|
||||
|
||||
// The SDK uses superjson internally for serialization
|
||||
// This test ensures the vendored superjson works correctly
|
||||
|
||||
const complexData = {
|
||||
date: new Date("2024-01-15T12:00:00Z"),
|
||||
set: new Set([1, 2, 3]),
|
||||
map: new Map([
|
||||
["key1", "value1"],
|
||||
["key2", "value2"],
|
||||
]),
|
||||
bigint: BigInt("9007199254740991"),
|
||||
nested: {
|
||||
innerDate: new Date("2024-06-01"),
|
||||
innerSet: new Set(["a", "b"]),
|
||||
},
|
||||
};
|
||||
|
||||
// Create a task that uses complex types
|
||||
const complexTask = task({
|
||||
id: "superjson-test-task",
|
||||
run: async (payload) => {
|
||||
// Just verify the payload structure matches expectations
|
||||
return {
|
||||
hasDate: payload.date instanceof Date,
|
||||
hasSet: payload.set instanceof Set,
|
||||
hasMap: payload.map instanceof Map,
|
||||
hasBigInt: typeof payload.bigint === "bigint",
|
||||
hasNestedDate: payload.nested?.innerDate instanceof Date,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
// Verify task was created successfully
|
||||
if (!complexTask.id) {
|
||||
console.error("FAIL: Task creation failed");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Test that logger works (it uses superjson for structured logging)
|
||||
try {
|
||||
logger.info("Testing superjson serialization", {
|
||||
complexData,
|
||||
timestamp: new Date(),
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("FAIL: Logger with complex data failed:", error);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log("SUCCESS: SuperJSON serialization validated");
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* ESM Import Test Fixture
|
||||
*
|
||||
* This file validates that the SDK can be imported using ESM syntax.
|
||||
* It tests all major export paths and verifies runtime functionality.
|
||||
*/
|
||||
|
||||
// Test main export
|
||||
import { task, logger, schedules, runs, configure, queue, retry, wait, metadata, tags } from "@trigger.dev/sdk";
|
||||
|
||||
// Test /v3 subpath (legacy, but should still work)
|
||||
import { task as taskV3 } from "@trigger.dev/sdk/v3";
|
||||
|
||||
// Validate exports are functions/objects
|
||||
const checks = [
|
||||
["task", typeof task === "function"],
|
||||
["taskV3", typeof taskV3 === "function"],
|
||||
["logger", typeof logger === "object" && typeof logger.info === "function"],
|
||||
["schedules", typeof schedules === "object"],
|
||||
["runs", typeof runs === "object"],
|
||||
["configure", typeof configure === "function"],
|
||||
["queue", typeof queue === "function"],
|
||||
["retry", typeof retry === "object"],
|
||||
["wait", typeof wait === "object"],
|
||||
["metadata", typeof metadata === "object"],
|
||||
["tags", typeof tags === "object"],
|
||||
];
|
||||
|
||||
let failed = false;
|
||||
for (const [name, passed] of checks) {
|
||||
if (!passed) {
|
||||
console.error(`FAIL: ${name} export check failed`);
|
||||
failed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Test task definition works
|
||||
const myTask = task({
|
||||
id: "esm-test-task",
|
||||
run: async (payload) => {
|
||||
return { received: payload };
|
||||
},
|
||||
});
|
||||
|
||||
if (myTask.id !== "esm-test-task") {
|
||||
console.error(`FAIL: task.id mismatch: expected "esm-test-task", got "${myTask.id}"`);
|
||||
failed = true;
|
||||
}
|
||||
|
||||
if (failed) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log("SUCCESS: All ESM imports validated");
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "typescript-fixture",
|
||||
"private": true,
|
||||
"type": "module"
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* TypeScript Import Test Fixture
|
||||
*
|
||||
* This file validates that the SDK types work correctly with TypeScript.
|
||||
* It tests type inference, generics, and type-only imports.
|
||||
*/
|
||||
|
||||
import {
|
||||
task,
|
||||
logger,
|
||||
schedules,
|
||||
runs,
|
||||
configure,
|
||||
queue,
|
||||
retry,
|
||||
wait,
|
||||
metadata,
|
||||
tags,
|
||||
type Context,
|
||||
type RetryOptions,
|
||||
} from "@trigger.dev/sdk";
|
||||
|
||||
// Type-only import test
|
||||
import type { ApiClientConfiguration } from "@trigger.dev/sdk";
|
||||
|
||||
// Test typed task with payload
|
||||
interface MyPayload {
|
||||
message: string;
|
||||
count: number;
|
||||
}
|
||||
|
||||
interface MyOutput {
|
||||
processed: boolean;
|
||||
result: string;
|
||||
}
|
||||
|
||||
const typedTask = task({
|
||||
id: "typescript-test-task",
|
||||
run: async (payload: MyPayload, { ctx }): Promise<MyOutput> => {
|
||||
// Verify context type
|
||||
const runId: string = ctx.run.id;
|
||||
|
||||
return {
|
||||
processed: true,
|
||||
result: `Processed ${payload.message} with count ${payload.count}`,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
// Verify task type inference
|
||||
type TaskPayload = Parameters<typeof typedTask.trigger>[0];
|
||||
type _PayloadCheck = TaskPayload extends MyPayload ? true : never;
|
||||
|
||||
// Test queue definition
|
||||
const myQueue = queue({
|
||||
name: "test-queue",
|
||||
concurrencyLimit: 10,
|
||||
});
|
||||
|
||||
// Test retry options type
|
||||
const retryOpts: RetryOptions = {
|
||||
maxAttempts: 3,
|
||||
factor: 2,
|
||||
minTimeoutInMs: 1000,
|
||||
maxTimeoutInMs: 30000,
|
||||
};
|
||||
|
||||
// Validate runtime
|
||||
if (typedTask.id !== "typescript-test-task") {
|
||||
console.error(`FAIL: task.id mismatch`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log("SUCCESS: TypeScript types validated");
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"noEmit": true
|
||||
},
|
||||
"include": ["test.ts"]
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* Bundler Compatibility Tests
|
||||
*
|
||||
* These tests validate that the SDK can be bundled correctly using
|
||||
* common bundlers like esbuild.
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from "vitest";
|
||||
import * as esbuild from "esbuild";
|
||||
import { resolve, dirname } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const fixturesDir = resolve(__dirname, "../fixtures");
|
||||
|
||||
describe("esbuild Bundling Tests", () => {
|
||||
it("should bundle ESM entrypoint without errors", async () => {
|
||||
const result = await esbuild.build({
|
||||
entryPoints: [resolve(fixturesDir, "esm-import/test.mjs")],
|
||||
bundle: true,
|
||||
format: "esm",
|
||||
platform: "node",
|
||||
target: "node18",
|
||||
write: false,
|
||||
external: ["@trigger.dev/sdk", "@trigger.dev/sdk/*"],
|
||||
logLevel: "silent",
|
||||
});
|
||||
|
||||
expect(result.errors).toHaveLength(0);
|
||||
expect(result.outputFiles).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("should bundle CJS entrypoint without errors", async () => {
|
||||
const result = await esbuild.build({
|
||||
entryPoints: [resolve(fixturesDir, "cjs-require/test.cjs")],
|
||||
bundle: true,
|
||||
format: "cjs",
|
||||
platform: "node",
|
||||
target: "node18",
|
||||
write: false,
|
||||
external: ["@trigger.dev/sdk", "@trigger.dev/sdk/*"],
|
||||
logLevel: "silent",
|
||||
});
|
||||
|
||||
expect(result.errors).toHaveLength(0);
|
||||
expect(result.outputFiles).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("should bundle SDK inline (simulating production build)", async () => {
|
||||
// This simulates what happens when a user bundles their app with the SDK included
|
||||
const entryContent = `
|
||||
import { task, logger } from "@trigger.dev/sdk";
|
||||
|
||||
export const myTask = task({
|
||||
id: "bundled-task",
|
||||
run: async (payload) => {
|
||||
logger.info("Processing", { payload });
|
||||
return { success: true };
|
||||
},
|
||||
});
|
||||
`;
|
||||
|
||||
const result = await esbuild.build({
|
||||
stdin: {
|
||||
contents: entryContent,
|
||||
loader: "ts",
|
||||
resolveDir: resolve(__dirname, "../../"),
|
||||
},
|
||||
bundle: true,
|
||||
format: "esm",
|
||||
platform: "node",
|
||||
target: "node18",
|
||||
write: false,
|
||||
// Don't externalize SDK - bundle it inline
|
||||
logLevel: "silent",
|
||||
metafile: true,
|
||||
});
|
||||
|
||||
expect(result.errors).toHaveLength(0);
|
||||
expect(result.outputFiles).toHaveLength(1);
|
||||
|
||||
// Verify the bundle contains the SDK code
|
||||
const bundleContent = result.outputFiles[0].text;
|
||||
expect(bundleContent).toBeTruthy();
|
||||
expect(bundleContent.length).toBeGreaterThan(1000); // Should be substantial
|
||||
});
|
||||
|
||||
it("should handle tree-shaking correctly", async () => {
|
||||
// Import only specific functions to test tree-shaking
|
||||
const entryContent = `
|
||||
import { task } from "@trigger.dev/sdk";
|
||||
|
||||
export const myTask = task({
|
||||
id: "tree-shake-task",
|
||||
run: async () => ({ done: true }),
|
||||
});
|
||||
`;
|
||||
|
||||
const result = await esbuild.build({
|
||||
stdin: {
|
||||
contents: entryContent,
|
||||
loader: "ts",
|
||||
resolveDir: resolve(__dirname, "../../"),
|
||||
},
|
||||
bundle: true,
|
||||
format: "esm",
|
||||
platform: "node",
|
||||
target: "node18",
|
||||
write: false,
|
||||
treeShaking: true,
|
||||
logLevel: "silent",
|
||||
});
|
||||
|
||||
expect(result.errors).toHaveLength(0);
|
||||
expect(result.outputFiles).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* Import Validation Tests
|
||||
*
|
||||
* These tests validate that the SDK can be imported correctly across
|
||||
* different module systems (ESM and CJS).
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeAll } from "vitest";
|
||||
import { execa, type Options as ExecaOptions } from "execa";
|
||||
import { resolve, dirname } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const fixturesDir = resolve(__dirname, "../fixtures");
|
||||
|
||||
// Find the SDK package in the monorepo
|
||||
const sdkDir = resolve(__dirname, "../../../../packages/trigger-sdk");
|
||||
|
||||
// Common execa options
|
||||
const execaOpts: ExecaOptions = {
|
||||
env: {
|
||||
...process.env,
|
||||
// Ensure Node.js can resolve workspace packages
|
||||
NODE_PATH: resolve(__dirname, "../../../../node_modules"),
|
||||
},
|
||||
timeout: 30_000,
|
||||
};
|
||||
|
||||
describe("ESM Import Tests", () => {
|
||||
it("should import SDK using ESM syntax", async () => {
|
||||
const result = await execa("node", ["test.mjs"], {
|
||||
...execaOpts,
|
||||
cwd: resolve(fixturesDir, "esm-import"),
|
||||
});
|
||||
|
||||
expect(result.stdout).toContain("SUCCESS");
|
||||
expect(result.exitCode).toBe(0);
|
||||
});
|
||||
|
||||
it("should validate superjson serialization in ESM", async () => {
|
||||
const result = await execa("node", ["superjson-test.mjs"], {
|
||||
...execaOpts,
|
||||
cwd: resolve(fixturesDir, "esm-import"),
|
||||
});
|
||||
|
||||
expect(result.stdout).toContain("SUCCESS");
|
||||
expect(result.exitCode).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("CJS Require Tests", () => {
|
||||
it("should require SDK using CommonJS syntax", async () => {
|
||||
const result = await execa("node", ["test.cjs"], {
|
||||
...execaOpts,
|
||||
cwd: resolve(fixturesDir, "cjs-require"),
|
||||
});
|
||||
|
||||
expect(result.stdout).toContain("SUCCESS");
|
||||
expect(result.exitCode).toBe(0);
|
||||
});
|
||||
|
||||
it("should work with --experimental-require-module flag on older Node", async () => {
|
||||
// This flag is needed for Node < 22.12.0 to require ESM modules
|
||||
// On newer Node.js, it's a no-op
|
||||
const result = await execa("node", ["--experimental-require-module", "test.cjs"], {
|
||||
...execaOpts,
|
||||
cwd: resolve(fixturesDir, "cjs-require"),
|
||||
});
|
||||
|
||||
expect(result.stdout).toContain("SUCCESS");
|
||||
expect(result.exitCode).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("TypeScript Compilation Tests", () => {
|
||||
it("should typecheck SDK imports successfully", async () => {
|
||||
const result = await execa("npx", ["tsc", "--noEmit"], {
|
||||
...execaOpts,
|
||||
cwd: resolve(fixturesDir, "typescript"),
|
||||
});
|
||||
|
||||
expect(result.exitCode).toBe(0);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"declaration": false,
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"types": ["vitest/globals"]
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["node_modules", "dist", "src/fixtures"]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { defineConfig } from "vitest/config";
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
include: ["src/tests/**/*.test.ts"],
|
||||
globals: true,
|
||||
isolate: true,
|
||||
testTimeout: 120_000, // Some framework builds can take time
|
||||
hookTimeout: 60_000,
|
||||
},
|
||||
});
|
||||
@@ -157,11 +157,13 @@
|
||||
},
|
||||
"sideEffects": false,
|
||||
"scripts": {
|
||||
"clean": "rimraf dist .tshy .tshy-build .turbo",
|
||||
"clean": "rimraf dist .tshy .tshy-build .turbo src/v3/vendor",
|
||||
"update-version": "tsx ../../scripts/updateVersion.ts",
|
||||
"build": "tshy && pnpm run update-version",
|
||||
"dev": "tshy --watch",
|
||||
"typecheck": "tsc --noEmit -p tsconfig.src.json",
|
||||
"bundle-vendor": "node scripts/bundle-superjson.mjs",
|
||||
"build": "pnpm run bundle-vendor && tshy && node scripts/bundle-superjson.mjs --copy && pnpm run update-version",
|
||||
"dev": "pnpm run bundle-vendor && tshy --watch",
|
||||
"typecheck": "pnpm run bundle-vendor && tsc --noEmit -p tsconfig.src.json",
|
||||
"pretest": "pnpm run bundle-vendor",
|
||||
"test": "vitest",
|
||||
"check-exports": "attw --pack ."
|
||||
},
|
||||
@@ -193,7 +195,6 @@
|
||||
"socket.io": "4.7.4",
|
||||
"socket.io-client": "4.7.5",
|
||||
"std-env": "^3.8.1",
|
||||
"superjson": "^2.2.1",
|
||||
"tinyexec": "^0.3.2",
|
||||
"uncrypto": "^0.1.3",
|
||||
"zod": "3.25.76",
|
||||
@@ -212,6 +213,7 @@
|
||||
"defu": "^6.1.4",
|
||||
"esbuild": "^0.23.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"superjson": "^2.2.1",
|
||||
"ts-essentials": "10.0.1",
|
||||
"tshy": "^3.0.2",
|
||||
"tsx": "4.17.0"
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* This script bundles superjson and its dependency (copy-anything) into
|
||||
* vendored CJS and ESM bundles to avoid the ERR_REQUIRE_ESM error.
|
||||
*
|
||||
* superjson v2.x is ESM-only, which causes issues on:
|
||||
* - Node.js versions before 22.12.0 (require(ESM) not enabled by default)
|
||||
* - AWS Lambda (intentionally disables require(ESM))
|
||||
*
|
||||
* The output files are gitignored and regenerated during each build.
|
||||
* This script runs automatically as part of `pnpm run build`.
|
||||
*
|
||||
* Usage:
|
||||
* node scripts/bundle-superjson.mjs # Bundle to src/v3/vendor
|
||||
* node scripts/bundle-superjson.mjs --copy # Also copy to dist directories
|
||||
*/
|
||||
|
||||
import * as esbuild from "esbuild";
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { readFileSync, mkdirSync, copyFileSync } from "node:fs";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const packageRoot = join(__dirname, "..");
|
||||
const vendorDir = join(packageRoot, "src", "v3", "vendor");
|
||||
|
||||
// Get the installed superjson version for the banner
|
||||
const superjsonPkg = JSON.parse(
|
||||
readFileSync(join(packageRoot, "node_modules", "superjson", "package.json"), "utf-8")
|
||||
);
|
||||
const banner = `/**
|
||||
* Bundled superjson v${superjsonPkg.version}
|
||||
*
|
||||
* This file is auto-generated by scripts/bundle-superjson.mjs
|
||||
* Do not edit directly - run the script to regenerate.
|
||||
*
|
||||
* Original package: https://github.com/flightcontrolhq/superjson
|
||||
* License: MIT
|
||||
*/`;
|
||||
|
||||
async function bundle() {
|
||||
// Ensure vendor directory exists
|
||||
mkdirSync(vendorDir, { recursive: true });
|
||||
|
||||
// Bundle for CommonJS
|
||||
await esbuild.build({
|
||||
entryPoints: [join(packageRoot, "node_modules", "superjson", "dist", "index.js")],
|
||||
bundle: true,
|
||||
format: "cjs",
|
||||
platform: "node",
|
||||
target: "node18",
|
||||
outfile: join(vendorDir, "superjson.cjs"),
|
||||
banner: { js: banner },
|
||||
// Don't minify to keep it debuggable
|
||||
minify: false,
|
||||
});
|
||||
|
||||
// Bundle for ESM
|
||||
await esbuild.build({
|
||||
entryPoints: [join(packageRoot, "node_modules", "superjson", "dist", "index.js")],
|
||||
bundle: true,
|
||||
format: "esm",
|
||||
platform: "node",
|
||||
target: "node18",
|
||||
outfile: join(vendorDir, "superjson.mjs"),
|
||||
banner: { js: banner },
|
||||
minify: false,
|
||||
});
|
||||
|
||||
console.log("Bundled superjson v" + superjsonPkg.version);
|
||||
console.log(" -> src/v3/vendor/superjson.cjs (CommonJS)");
|
||||
console.log(" -> src/v3/vendor/superjson.mjs (ESM)");
|
||||
|
||||
// Copy to dist directories if --copy flag is passed
|
||||
if (process.argv.includes("--copy")) {
|
||||
const distCommonjsVendor = join(packageRoot, "dist", "commonjs", "v3", "vendor");
|
||||
const distEsmVendor = join(packageRoot, "dist", "esm", "v3", "vendor");
|
||||
|
||||
mkdirSync(distCommonjsVendor, { recursive: true });
|
||||
mkdirSync(distEsmVendor, { recursive: true });
|
||||
|
||||
copyFileSync(join(vendorDir, "superjson.cjs"), join(distCommonjsVendor, "superjson.cjs"));
|
||||
copyFileSync(join(vendorDir, "superjson.mjs"), join(distEsmVendor, "superjson.mjs"));
|
||||
|
||||
console.log("Copied to dist directories");
|
||||
}
|
||||
}
|
||||
|
||||
bundle().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1,8 +1,10 @@
|
||||
// Use vendored superjson bundle to avoid ESM/CJS compatibility issues
|
||||
// See: https://github.com/triggerdotdev/trigger.dev/issues/2937
|
||||
// @ts-ignore
|
||||
const { default: superjson } = require("superjson");
|
||||
const superjson = require("../vendor/superjson.cjs");
|
||||
|
||||
// @ts-ignore
|
||||
superjson.registerCustom<Buffer, number[]>(
|
||||
superjson.default.registerCustom<Buffer, number[]>(
|
||||
{
|
||||
isApplicable: (v: unknown): v is Buffer => typeof Buffer === "function" && Buffer.isBuffer(v),
|
||||
serialize: (v: Buffer) => [...v],
|
||||
@@ -12,4 +14,4 @@ superjson.registerCustom<Buffer, number[]>(
|
||||
);
|
||||
|
||||
// @ts-ignore
|
||||
module.exports.default = superjson;
|
||||
module.exports.default = superjson.default;
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
// Use vendored superjson bundle to avoid ESM/CJS compatibility issues
|
||||
// See: https://github.com/triggerdotdev/trigger.dev/issues/2937
|
||||
// @ts-ignore
|
||||
import superjson from "superjson";
|
||||
import superjson from "../vendor/superjson.mjs";
|
||||
|
||||
superjson.registerCustom<Buffer, number[]>(
|
||||
{
|
||||
isApplicable: (v): v is Buffer => typeof Buffer === "function" && Buffer.isBuffer(v),
|
||||
serialize: (v) => [...v],
|
||||
deserialize: (v) => Buffer.from(v),
|
||||
isApplicable: (v: unknown): v is Buffer => typeof Buffer === "function" && Buffer.isBuffer(v),
|
||||
serialize: (v: Buffer) => [...v],
|
||||
deserialize: (v: number[]) => Buffer.from(v),
|
||||
},
|
||||
"buffer"
|
||||
);
|
||||
|
||||
Generated
+448
-16
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user