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:
Eric Allam
2026-01-30 09:15:02 +00:00
committed by GitHub
parent 01208fde27
commit 3925f8cc49
29 changed files with 1463 additions and 28 deletions
+7
View File
@@ -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.
+4
View File
@@ -29,3 +29,7 @@ jobs:
with:
package: cli-v3
secrets: inherit
sdk-compat:
uses: ./.github/workflows/sdk-compat.yml
secrets: inherit
+178
View File
@@ -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
+3
View File
@@ -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,
},
});
+7 -5
View File
@@ -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;
+6 -4
View File
@@ -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"
);
+448 -16
View File
File diff suppressed because it is too large Load Diff