chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:32:57 +08:00
commit cd420f9332
4811 changed files with 884702 additions and 0 deletions
+237
View File
@@ -0,0 +1,237 @@
# Trigger.dev CLI E2E suite
E2E test suite for the Trigger.dev v3 CLI.
Note: this only works with Trigger.dev v3 projects and later. There is no E2E test suite for the [@trigger.dev/cli](https://www.npmjs.com/package/@trigger.dev/cli) package yet.
Trigger.dev is an open source platform that makes it easy to create event-driven background tasks directly in your existing project.
## Description
This suite aims to test the outputs fo the `triggerdev deploy` command.
To do so, it runs the deploy code against fixture projects that are located under `packages/cli-v3/e2e/fixtures/`.
Those fixtures reproduce minimal project structure and contents, in order to reproduce known bugs and run fast.
**Notes**
- The suite uses vitest
- Everything happens locally
- There is no login required
- There is not real project reference needed
- No docker image is created or built, instead, the bundled worker file is started with node directly inside the vitest process
## Usage
If you have not done it yet, build the CLI:
```sh
pnpm run build --filter trigger.dev
```
Then, run the v3 CLI E2E test suite:
```sh
pnpm --filter trigger.dev run test:e2e
```
| Option | Description |
| ---------------------- | ---------------------------------------------------------------------------- |
| `MOD=<fixture-name>` | The name of any folder directly nested under `packages/cli-v3/e2e/fixtures/` |
| `PM=<package-manager>` | The package manager to use. One of `npm`, `pnpm`, `yarn`. Defaults to `npm` |
Example:
```sh
MOD=server-only PM=yarn pnpm --filter trigger.dev run test:e2e
```
This will run the test suite for the `server-only` fixture using `yarn` to install and resolve dependencies.
## Debugging
When debugging an issue with the `triggerdev deploy` or `triggerdev dev` command, it is recommended to reproduce it with a minimal project fixture in the e2e suite.
Check [Adding a fixture](#adding-a-fixture) for more information.
Then run:
```sh
MOD=<fixture-name> pnpm run test:e2e
```
This will test your fixture project, and generate outputs in the `packages/cli-v3/e2e/fixtures/<fixture-name>/.trigger` folder, so you can easily debug.
## Adding a fixture
1. Create a new `packages/cli-v3/e2e/fixtures/<fixture-name>` folder.
It will hold the project to test.
2. Add a `package.json` file in your `packages/cli-v3/e2e/fixtures/<fixture-name>` folder.
Use the following template:
```json package.json
{
"name": "<fixture-name>",
"private": true,
"engines": {
"pnpm": "8.15.5",
"yarn": "4.2.2"
},
"packageManager": "pnpm@8.15.5"
}
```
> The `engines` field is used to store the versions of pnpm and yarn to use when running the suite.
3. Add an empty `pnpm-workspace.yaml` in your `packages/cli-v3/e2e/fixtures/<fixture-name>` folder.
This is necessary to prevent the Trigger.dev monorepo from handling this project.
Please check https://github.com/pnpm/pnpm/issues/2412 for more inforation.
4. Add an empty `yarn.lock` in your fixture folder.
This is necessary to allow to use `yarn` without having a warning on the current project being a `pnpm` project.
5. Add the following `.yarnrc.yaml` in your fixture folder.
This will avoid having `.pnp.cjs` and `.pnp.loader.mjs` and keep versioned files to a minimum.
```yaml .yarnrc.yml
nodeLinker: node-modules
```
6. Install the fixture dependencies and generate lockfiles.
Like you would in any project.
E.g. if your fixture contains a trigger task that uses the `jsdom` library:
```sh
cd packages/cli-v3/e2e/fixtures/<fixture-name>
corepack use pnpm@8.15.5
pnpm install jsdom
```
> This will update the `package.json` and generate the `pnpm-lock.yaml` file.
7. Make sure typescript is installed in the fixture project.
```sh
cd packages/cli-v3/e2e/fixtures/<fixture-name>
corepack use pnpm@8.15.5
pnpm install typescript
```
> This is necessary to typecheck the project during the test suite.
8. Add a tsconfig.json file similar to the one below:
```json tsconfig.json
{
"include": ["src/**/*.ts", "trigger.config.ts"],
"compilerOptions": {
"target": "es2022",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "NodeNext",
"moduleResolution": "NodeNext",
"moduleDetection": "force",
"verbatimModuleSyntax": false,
"jsx": "react",
"strict": true,
"alwaysStrict": true,
"strictPropertyInitialization": false,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noFallthroughCasesInSwitch": true,
"resolveJsonModule": true,
"removeComments": false,
"esModuleInterop": true,
"emitDecoratorMetadata": false,
"experimentalDecorators": false,
"downlevelIteration": true,
"isolatedModules": true,
"noUncheckedIndexedAccess": true,
"pretty": true
}
}
```
9. To run the test suite against multiple package manager, we need to generate the other lockfiles.
```sh
cd packages/cli-v3/e2e/fixtures/<fixture-name>
rm -rf **/node_modules
npm install
rm -rf **/node_modules
corepack use yarn@4.2.2 # will update the yarn lockfile
```
> Do it in this order, otherwise `npm install` will update the existing `yarn.lock` file with legacy version 1.
10. Create a new `packages/cli-v3/e2e/fixtures/trigger` folder, and create a trigger task in it.
Here is an example:
```javascript
import { task } from "@trigger.dev/sdk/v3";
export const helloWorldTask = task({
id: "hello-world",
run: async (payload) => {
console.log("Hello, World!", payload);
},
});
```
11. Add a trigger configuration file.
The configuration file is mandatory here, the E2E suite does not execute `trigger.dev` commands.
```javascript
export const config = {
project: "<fixture-name>",
triggerDirectories: ["./trigger"],
};
```
> The project reference can be anything here, as the suite runs locally without connecting to the platform.
12. Commit your changes.
13. Add your fixture test configuration in `fixtures.config.js`.
```javascript fixtures.config.js
export const fixturesConfig = [
// ...
{
id: "<fixture-name>",
},
// ...
];
```
> You might expect a specific error for a specific test, so use those configuration option at your discretion.
## Updating the SDK in the fixtures
The `@trigger.dev/sdk` package is installed in the fixtures as a real dependency (not from the monorepo).
To update it, you'll need to update the version in the `package.json` file, and then run the following commands:
> NOTE: Some fixtures don't support all the package managers, like the monorepo-react-email only supports yarn and pnpm.
```sh
cd packages/cli-v3/e2e/fixtures/<fixture-name>
rm -rf **/node_modules
corepack use pnpm@8.15.5
rm -rf **/node_modules
npm install
rm -rf **/node_modules
corepack use yarn@4.2.2
rm -rf **/node_modules
```
+337
View File
@@ -0,0 +1,337 @@
import { alwaysExternal } from "@trigger.dev/core/v3/build";
import type { BuildManifest, WorkerManifest } from "@trigger.dev/core/v3/schemas";
import * as fs from "node:fs";
import { mkdir, rename, rm } from "node:fs/promises";
import * as path from "node:path";
import { rimraf } from "rimraf";
import { buildWorker, rewriteBuildManifestPaths } from "../src/build/buildWorker.js";
import { loadConfig } from "../src/config.js";
import { indexWorkerManifest } from "../src/indexing/indexWorkerManifest.js";
import { writeJSONFile } from "../src/utilities/fileSystem.js";
import { logger } from "../src/utilities/logger.js";
import { normalizeImportPath } from "../src/utilities/normalizeImportPath.js";
import { getTmpDir } from "../src/utilities/tempDirectories.js";
import type { TestCase } from "./fixtures.js";
import { fixturesConfig } from "./fixtures.js";
import type { E2EOptions } from "./schemas.js";
import { E2EOptionsSchema } from "./schemas.js";
import type { PackageManager } from "./utils.js";
import {
executeTestCaseRun,
installFixtureDeps,
LOCKFILES,
parsePackageManager,
runTsc,
} from "./utils.js";
const TIMEOUT = 120_000;
interface E2EFixtureTest extends TestCase {
fixtureDir: string;
packageManager: PackageManager;
tempDir: string;
workspaceDir: string;
}
const testCases: TestCase[] = process.env.MOD
? fixturesConfig.filter(({ id }) => process.env.MOD === id)
: fixturesConfig;
let options: E2EOptions;
try {
options = E2EOptionsSchema.parse({
logLevel: process.env.LOG,
packageManager: process.env.PM,
});
} catch (_e) {
options = {
logLevel: "log",
};
}
logger.loggerLevel = options.logLevel;
if (testCases.length === 0) {
if (process.env.MOD) {
throw new Error(`No test case found for ${process.env.MOD}`);
} else {
throw new Error("Nothing to test");
}
}
describe("buildWorker", async () => {
beforeEach<E2EFixtureTest>(async ({ fixtureDir, skip, packageManager, workspaceDir }) => {
await rimraf(path.join(workspaceDir, "**/node_modules"), {
glob: true,
});
await rimraf(path.join(workspaceDir, ".yarn"), { glob: true });
if (
packageManager === "npm" &&
(fs.existsSync(path.resolve(path.join(workspaceDir, "yarn.lock"))) ||
fs.existsSync(path.resolve(path.join(workspaceDir, "yarn.lock.copy"))))
) {
// `npm ci` & `npm install` will update an existing yarn.lock
try {
await rename(
path.resolve(path.join(workspaceDir, "yarn.lock")),
path.resolve(path.join(workspaceDir, "yarn.lock.copy"))
);
} catch (_e) {
await rename(
path.resolve(path.join(workspaceDir, "yarn.lock.copy")),
path.resolve(path.join(workspaceDir, "yarn.lock"))
);
}
}
if (
options.packageManager &&
!fs.existsSync(path.resolve(fixtureDir, LOCKFILES[options.packageManager]))
) {
skip();
}
await installFixtureDeps({ fixtureDir, packageManager, workspaceDir });
}, TIMEOUT);
afterEach<E2EFixtureTest>(async ({ packageManager, workspaceDir }) => {
if (packageManager === "npm") {
try {
await rename(
path.resolve(path.join(workspaceDir, "yarn.lock.copy")),
path.resolve(path.join(workspaceDir, "yarn.lock"))
);
} catch {}
}
vi.unstubAllEnvs();
});
for (let testCase of testCases) {
test.extend<E2EFixtureTest>({
// Seed `workspaceRelativeDir` before the spread so the key always exists.
// vitest 4 resolves fixture-to-fixture dependencies strictly at
// `test.extend()` time: the `workspaceDir` fixture below destructures
// `workspaceRelativeDir`, so it must be a defined fixture even for test
// cases that don't set it (it's an optional `TestCase` field). The spread
// overrides this default when the case provides its own value.
workspaceRelativeDir: "",
...testCase,
fixtureDir: async ({ id }, use) =>
await use(path.resolve(path.join(process.cwd(), "e2e/fixtures", id))),
workspaceDir: async ({ fixtureDir, workspaceRelativeDir }, use) =>
await use(path.resolve(path.join(fixtureDir, workspaceRelativeDir))),
packageManager: async ({ workspaceDir }, use) =>
await use(await parsePackageManager(options.packageManager, workspaceDir)),
tempDir: async ({ workspaceDir }, use) => {
const existingTempDir = path.resolve(path.join(workspaceDir, ".trigger"));
if (fs.existsSync(existingTempDir)) {
await rm(existingTempDir, { force: true, recursive: true });
}
await use(
(await mkdir(path.join(workspaceDir, ".trigger"), { recursive: true })) as string
);
},
})(
`fixture ${testCase.id}`,
{ timeout: TIMEOUT },
async ({
id,
tempDir,
tsconfig,
packageManager,
fixtureDir,
workspaceDir,
wantConfigInvalidError,
wantConfigNotFoundError,
wantBuildWorkerError,
wantIndexingError,
buildManifestMatcher,
workerManifestMatcher,
runs,
}) => {
let resolvedConfig: Awaited<ReturnType<typeof loadConfig>>;
const configExpect = expect(
(async () => {
resolvedConfig = await loadConfig({
cwd: workspaceDir,
});
})(),
wantConfigNotFoundError || wantConfigInvalidError
? "does not resolve config"
: "resolves config"
);
if (wantConfigNotFoundError) {
await configExpect.rejects.toThrowError();
return;
}
await configExpect.resolves.not.toThrowError();
if (wantConfigInvalidError) {
expect(resolvedConfig!).toBeUndefined();
return;
}
expect(resolvedConfig!).toBeTruthy();
if (tsconfig) {
const tscResult = await runTsc(
workspaceDir,
tsconfig,
packageManager === "yarn" ? fixtureDir : undefined
);
expect(tscResult.success).toBe(true);
}
const destination = getTmpDir(workspaceDir, "build");
let buildManifest: BuildManifest;
const buildExpect = expect(
(async () => {
buildManifest = await buildWorker({
target: "deploy",
environment: "test",
destination: destination.path,
resolvedConfig: resolvedConfig!,
rewritePaths: false,
forcedExternals: alwaysExternal,
});
})(),
wantBuildWorkerError ? "does not build" : "builds"
);
if (wantBuildWorkerError) {
await buildExpect.rejects.toThrowError();
return;
}
await buildExpect.resolves.not.toThrowError();
if (buildManifestMatcher) {
for (const external of buildManifestMatcher.externals ?? []) {
expect(buildManifest!.externals).toContainEqual(external);
}
for (const file of buildManifestMatcher.files ?? []) {
const found = (buildManifestMatcher.files ?? []).find((f) => f?.entry === file?.entry);
expect(found).toBeTruthy();
}
} else {
expect(buildManifest!).toBeTruthy();
}
logger.debug("Build manifest", buildManifest!);
const rewrittenManifest = rewriteBuildManifestPaths(buildManifest!, destination.path);
if (resolvedConfig!.instrumentedPackageNames?.length ?? 0 > 0) {
expect(rewrittenManifest.loaderEntryPoint).toBe("/app/src/entryPoints/loader.mjs");
} else {
expect(rewrittenManifest.loaderEntryPoint).toBeUndefined();
}
expect(rewrittenManifest.indexWorkerEntryPoint).toBe(
"/app/src/entryPoints/managed-index-worker.mjs"
);
const stdout: string[] = [];
const stderr: string[] = [];
let workerManifest: WorkerManifest;
const indexExpect = expect(
(async () => {
workerManifest = await indexWorkerManifest({
runtime: buildManifest!.runtime,
indexWorkerPath: buildManifest!.indexWorkerEntryPoint,
buildManifestPath: path.join(destination.path, "build.json"),
nodeOptions: buildManifest!.loaderEntryPoint
? `--import=${normalizeImportPath(buildManifest!.loaderEntryPoint)}`
: undefined,
env: testCase.envVars ?? {},
otelHookExclude: buildManifest!.otelImportHook?.exclude,
otelHookInclude: buildManifest!.otelImportHook?.include,
handleStdout(data) {
stdout.push(data);
logger.debug("indexWorkerManifest handleStdout");
logger.debug(data);
},
handleStderr(data) {
if (!data.includes("DeprecationWarning")) {
stderr.push(data);
logger.debug("indexWorkerManifest handleStderr");
logger.debug(data);
}
},
});
})(),
wantIndexingError ? "does not index" : "indexes"
);
if (wantIndexingError) {
await indexExpect.rejects.toThrowError();
return;
}
await indexExpect.resolves.not.toThrowError();
if (workerManifestMatcher) {
expect(workerManifest!).toMatchObject(workerManifestMatcher);
} else {
expect(workerManifest!).toBeTruthy();
}
logger.debug("Worker manifest", workerManifest!);
if (runs && runs.length > 0) {
await writeJSONFile(path.join(destination.path, "index.json"), workerManifest!);
}
for (const taskRun of runs || []) {
const { result, totalDurationMs, spans } = await executeTestCaseRun({
run: taskRun,
testCase,
destination: destination.path,
workerManifest: workerManifest!,
contentHash: buildManifest!.contentHash,
});
logger.debug("Task run result", result);
expect(result.ok).toBe(taskRun.result.ok);
if (result.ok) {
if (taskRun.result.durationMs) {
expect(totalDurationMs).toBeGreaterThanOrEqual(taskRun.result.durationMs);
}
if (taskRun.result.output) {
expect(result.output).toEqual(taskRun.result.output);
}
if (taskRun.result.outputType) {
expect(result.outputType).toEqual(taskRun.result.outputType);
}
if (taskRun.result.spans) {
for (const spanName of taskRun.result.spans) {
const foundSpan = spans.find((span) => span.name === spanName);
expect(foundSpan).toBeTruthy();
}
}
}
}
}
);
}
});
+207
View File
@@ -0,0 +1,207 @@
import type { BuildManifest, WorkerManifest } from "@trigger.dev/core/v3/schemas";
type DeepPartial<T> = T extends object
? {
[P in keyof T]?: DeepPartial<T[P]>;
}
: T;
export interface TestCaseRun {
task: {
id: string;
filePath: string;
exportName?: string;
};
payload: string;
payloadType?: string;
result: {
ok: boolean;
durationMs?: number;
output?: string;
outputType?: string;
spans?: string[];
};
}
export interface TestCase {
resolveEnv?: { [key: string]: string };
id: string;
workspaceRelativeDir?: string;
wantConfigNotFoundError?: boolean;
wantConfigInvalidError?: boolean;
wantBuildWorkerError?: boolean;
wantIndexingError?: boolean;
wantWorkerError?: boolean;
wantDependenciesError?: boolean;
wantInstallationError?: boolean;
buildManifestMatcher?: DeepPartial<BuildManifest>;
workerManifestMatcher?: DeepPartial<WorkerManifest>;
runs?: TestCaseRun[];
tsconfig?: string;
envVars?: { [key: string]: string };
}
export const fixturesConfig: TestCase[] = [
{
id: "hello-world",
buildManifestMatcher: {
runtime: "node",
externals: [
{
name: "import-in-the-middle",
version: "3.0.1",
},
],
files: [{ entry: "src/trigger/helloWorld.ts" }],
},
workerManifestMatcher: {
tasks: [
{
id: "helloWorld",
filePath: "src/trigger/helloWorld.ts",
},
],
},
runs: [
{
task: { id: "helloWorld", filePath: "src/trigger/helloWorld.ts", exportName: "helloWorld" },
payload: "{}",
result: { ok: true, durationMs: 500 },
},
],
tsconfig: "tsconfig.json",
},
{
id: "otel-telemetry-loader",
buildManifestMatcher: {
runtime: "node",
externals: [
{
name: "openai",
version: "4.47.0",
},
{
name: "import-in-the-middle",
version: "3.0.1",
},
],
files: [{ entry: "src/trigger/ai.ts" }],
},
workerManifestMatcher: {
tasks: [
{
id: "ai",
filePath: "src/trigger/ai.ts",
},
],
},
runs: [
{
task: { id: "ai", filePath: "src/trigger/ai.ts" },
payload: '{"prompt":"be funny"}',
result: { ok: true, durationMs: 1 },
},
],
tsconfig: "tsconfig.json",
envVars: {
OPENAI_API_KEY: "my-api-key",
},
},
{
id: "emit-decorator-metadata",
buildManifestMatcher: {
runtime: "node",
externals: [
{
name: "import-in-the-middle",
version: "3.0.1",
},
],
files: [{ entry: "src/trigger/decorators.ts" }],
},
workerManifestMatcher: {
tasks: [
{
id: "decoratorsTask",
filePath: "src/trigger/decorators.ts",
},
],
},
runs: [
{
task: { id: "decoratorsTask", filePath: "src/trigger/decorators.ts" },
payload: "{}",
result: { ok: true, durationMs: 1 },
},
],
tsconfig: "tsconfig.json",
},
{
id: "monorepo-react-email",
workspaceRelativeDir: "packages/trigger",
tsconfig: "tsconfig.json",
buildManifestMatcher: {
runtime: "node",
externals: [
{
name: "import-in-the-middle",
version: "3.0.1",
},
],
files: [{ entry: "src/reactEmail.tsx" }],
},
workerManifestMatcher: {
tasks: [
{
id: "react-email",
filePath: "src/reactEmail.tsx",
},
],
},
runs: [
{
task: { id: "react-email", filePath: "src/reactEmail.tsx" },
payload: "{}",
result: {
ok: true,
output:
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><!--$--><html dir="ltr" lang="en"><a href="https://example.com" style="line-height:100%;text-decoration:none;display:inline-block;max-width:100%;mso-padding-alt:0px;background:#000;color:#fff;padding:12px 20px 12px 20px" target="_blank"><span><!--[if mso]><i style="mso-font-width:500%;mso-text-raise:18" hidden>&#8202;&#8202;</i><![endif]--></span><span style="max-width:100%;display:inline-block;line-height:120%;mso-padding-alt:0px;mso-text-raise:9px">Click me</span><span><!--[if mso]><i style="mso-font-width:500%" hidden>&#8202;&#8202;&#8203;</i><![endif]--></span></a></html><!--/$-->',
outputType: "text/plain",
},
},
],
},
{
id: "esm-only-external",
buildManifestMatcher: {
runtime: "node",
externals: [
{
name: "import-in-the-middle",
version: "3.0.1",
},
{
name: "mupdf",
version: "0.3.0",
},
],
files: [{ entry: "src/trigger/helloWorld.ts" }],
},
workerManifestMatcher: {
tasks: [
{
id: "helloWorld",
filePath: "src/trigger/helloWorld.ts",
},
],
},
runs: [
{
task: { id: "helloWorld", filePath: "src/trigger/helloWorld.ts" },
payload: "{}",
result: { ok: true, durationMs: 1 },
},
],
tsconfig: "tsconfig.json",
},
];
@@ -0,0 +1 @@
nodeLinker: node-modules
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,21 @@
{
"name": "emit-decorator-metadata",
"private": true,
"packageManager": "yarn@4.2.2+sha512.c44e283c54e02de9d1da8687025b030078c1b9648d2895a65aab8e64225bfb7becba87e1809fc0b4b6778bbd47a1e2ab6ac647de4c5e383a53a7c17db6c3ff4b",
"engines": {
"pnpm": "10.33.2",
"yarn": "4.2.2"
},
"workspaces": [
"packages/*"
],
"dependencies": {
"@trigger.dev/sdk": "0.0.0-prerelease-20250321122618",
"reflect-metadata": "0.2.2"
},
"devDependencies": {
"@trigger.dev/build": "0.0.0-prerelease-20250321122618",
"@types/node": "^22.20.0",
"typescript": "5.5.4"
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,3 @@
# https://github.com/pnpm/pnpm/issues/2412
packages:
- "packages/*"
@@ -0,0 +1,56 @@
import { task } from "@trigger.dev/sdk/v3";
import "reflect-metadata";
class Point {
constructor(
public x: number,
public y: number
) {}
}
class Line {
private _start: Point;
private _end: Point;
@validate
set start(value: Point) {
this._start = value;
}
get start() {
return this._start;
}
@validate
set end(value: Point) {
this._end = value;
}
get end() {
return this._end;
}
}
function validate<T>(target: any, propertyKey: string, descriptor: TypedPropertyDescriptor<T>) {
let set = descriptor.set!;
descriptor.set = function (value: T) {
let type = Reflect.getMetadata("design:type", target, propertyKey);
if (!(value instanceof type)) {
throw new TypeError(`Invalid type, got ${typeof value} not ${type.name}.`);
}
set.call(this, value);
};
}
export const decoratorsTask = task({
id: "decoratorsTask",
run: async () => {
const line = new Line();
line.start = new Point(0, 0);
console.log("Hello, World!", { line });
},
});
@@ -0,0 +1,11 @@
import { defineConfig } from "@trigger.dev/sdk/v3";
import { emitDecoratorMetadata } from "@trigger.dev/build/extensions/typescript";
export default defineConfig({
project: "<fixture project>",
dirs: ["./src/trigger"],
build: {
extensions: [emitDecoratorMetadata()],
},
maxDuration: 3600,
});
@@ -0,0 +1,31 @@
{
"compilerOptions": {
"target": "es2022",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "NodeNext",
"moduleResolution": "NodeNext",
"moduleDetection": "force",
"verbatimModuleSyntax": false,
"jsx": "react",
"strict": true,
"alwaysStrict": true,
"strictPropertyInitialization": false,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noFallthroughCasesInSwitch": true,
"resolveJsonModule": true,
"removeComments": false,
"esModuleInterop": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"downlevelIteration": true,
"isolatedModules": true,
"noUncheckedIndexedAccess": true,
"pretty": true
}
}
@@ -0,0 +1,7 @@
{
"include": ["src/**/*.ts", "trigger.config.ts"],
"extends": "./tsconfig.base.json",
"compilerOptions": {
"emitDecoratorMetadata": true
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1 @@
nodeLinker: node-modules
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,20 @@
{
"name": "esm-only-external",
"private": true,
"type": "module",
"packageManager": "yarn@4.2.2+sha256.1aa43a5304405be7a7cb9cb5de7b97de9c4e8ddd3273e4dad00d6ae3eb39f0ef",
"engines": {
"pnpm": "8.15.5",
"yarn": "4.2.2"
},
"workspaces": [
"packages/*"
],
"dependencies": {
"@trigger.dev/sdk": "0.0.0-prerelease-20250321122618",
"mupdf": "^0.3.0"
},
"devDependencies": {
"typescript": "5.5.4"
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,3 @@
# https://github.com/pnpm/pnpm/issues/2412
packages:
- "packages/*"
@@ -0,0 +1,11 @@
import { task } from "@trigger.dev/sdk/v3";
import * as mupdf from "mupdf";
export const helloWorld = task({
id: "helloWorld",
run: async () => {
console.log("Hello, World!", {
metaformat: mupdf.PDFDocument.META_FORMAT,
});
},
});
@@ -0,0 +1,10 @@
import { defineConfig } from "@trigger.dev/sdk/v3";
export default defineConfig({
project: "<fixture project>",
dirs: ["./src/trigger"],
build: {
external: ["mupdf"],
},
maxDuration: 3600,
});
@@ -0,0 +1,32 @@
{
"include": ["src/**/*.ts", "trigger.config.ts"],
"compilerOptions": {
"target": "es2022",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "NodeNext",
"moduleResolution": "NodeNext",
"moduleDetection": "force",
"verbatimModuleSyntax": false,
"jsx": "react",
"strict": true,
"alwaysStrict": true,
"strictPropertyInitialization": false,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noFallthroughCasesInSwitch": true,
"resolveJsonModule": true,
"removeComments": false,
"esModuleInterop": true,
"emitDecoratorMetadata": false,
"experimentalDecorators": false,
"downlevelIteration": true,
"isolatedModules": true,
"noUncheckedIndexedAccess": true,
"pretty": true
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1 @@
nodeLinker: node-modules
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,18 @@
{
"name": "hello-world",
"private": true,
"packageManager": "yarn@4.2.2+sha256.1aa43a5304405be7a7cb9cb5de7b97de9c4e8ddd3273e4dad00d6ae3eb39f0ef",
"engines": {
"pnpm": "8.15.5",
"yarn": "4.2.2"
},
"workspaces": [
"packages/*"
],
"dependencies": {
"@trigger.dev/sdk": "0.0.0-prerelease-20250321122618"
},
"devDependencies": {
"typescript": "5.5.4"
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,3 @@
# https://github.com/pnpm/pnpm/issues/2412
packages:
- "packages/*"
@@ -0,0 +1,10 @@
import { task } from "@trigger.dev/sdk/v3";
import { setTimeout } from "node:timers/promises";
export const helloWorld = task({
id: "helloWorld",
run: async () => {
await setTimeout(1000);
console.log("Hello, World!");
},
});
@@ -0,0 +1,7 @@
import { defineConfig } from "@trigger.dev/sdk/v3";
export default defineConfig({
project: "<fixture project>",
dirs: ["./src/trigger"],
maxDuration: 3600,
});
@@ -0,0 +1,32 @@
{
"include": ["src/**/*.ts", "trigger.config.ts"],
"compilerOptions": {
"target": "es2022",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "NodeNext",
"moduleResolution": "NodeNext",
"moduleDetection": "force",
"verbatimModuleSyntax": false,
"jsx": "react",
"strict": true,
"alwaysStrict": true,
"strictPropertyInitialization": false,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noFallthroughCasesInSwitch": true,
"resolveJsonModule": true,
"removeComments": false,
"esModuleInterop": true,
"emitDecoratorMetadata": false,
"experimentalDecorators": false,
"downlevelIteration": true,
"isolatedModules": true,
"noUncheckedIndexedAccess": true,
"pretty": true
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1 @@
nodeLinker: node-modules
@@ -0,0 +1,12 @@
{
"name": "monorepo-react-email",
"private": true,
"packageManager": "pnpm@8.15.5+sha256.4b4efa12490e5055d59b9b9fc9438b7d581a6b7af3b5675eb5c5f447cee1a589",
"engines": {
"pnpm": "8.15.5",
"yarn": "4.2.2"
},
"workspaces": [
"packages/*"
]
}
@@ -0,0 +1,13 @@
{
"name": "@repo/email",
"private": true,
"dependencies": {
"@react-email/components": "0.0.24",
"@react-email/render": "1.0.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-email": "^3.0.1"
},
"main": "./src/index.ts",
"types": "./src/index.ts"
}
@@ -0,0 +1,19 @@
import { Button, Html } from "@react-email/components";
import { render } from "@react-email/render";
function ExampleEmail(props: {}) {
return (
<Html>
<Button
href="https://example.com"
style={{ background: "#000", color: "#fff", padding: "12px 20px" }}
>
Click me
</Button>
</Html>
);
}
export function renderExampleEmail() {
return render(<ExampleEmail />);
}
@@ -0,0 +1 @@
export * from "./emails";
@@ -0,0 +1,32 @@
{
"include": ["src/**/*.ts", "src/**/*.tsx"],
"compilerOptions": {
"target": "es2022",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "NodeNext",
"moduleResolution": "NodeNext",
"moduleDetection": "force",
"verbatimModuleSyntax": false,
"jsx": "react-jsx",
"strict": true,
"alwaysStrict": true,
"strictPropertyInitialization": false,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noFallthroughCasesInSwitch": true,
"resolveJsonModule": true,
"removeComments": false,
"esModuleInterop": true,
"emitDecoratorMetadata": false,
"experimentalDecorators": false,
"downlevelIteration": true,
"isolatedModules": true,
"noUncheckedIndexedAccess": true,
"pretty": true
}
}
@@ -0,0 +1,11 @@
{
"name": "@repo/trigger",
"private": true,
"dependencies": {
"@repo/email": "workspace:*",
"@trigger.dev/sdk": "0.0.0-prerelease-20250321122618"
},
"devDependencies": {
"typescript": "5.5.4"
}
}
@@ -0,0 +1,9 @@
import { task } from "@trigger.dev/sdk/v3";
import { renderExampleEmail } from "@repo/email";
export const reactEmail = task({
id: "react-email",
run: async () => {
return await renderExampleEmail();
},
});
@@ -0,0 +1,7 @@
import { defineConfig } from "@trigger.dev/sdk/v3";
export default defineConfig({
project: "<fixture project>",
dirs: ["./src"],
maxDuration: 3600,
});
@@ -0,0 +1,32 @@
{
"include": ["src/**/*.ts", "trigger.config.ts"],
"compilerOptions": {
"target": "es2022",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "NodeNext",
"moduleResolution": "NodeNext",
"moduleDetection": "force",
"verbatimModuleSyntax": false,
"jsx": "react-jsx",
"strict": true,
"alwaysStrict": true,
"strictPropertyInitialization": false,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noFallthroughCasesInSwitch": true,
"resolveJsonModule": true,
"removeComments": false,
"esModuleInterop": true,
"emitDecoratorMetadata": false,
"experimentalDecorators": false,
"downlevelIteration": true,
"isolatedModules": true,
"noUncheckedIndexedAccess": true,
"pretty": true
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,3 @@
# https://github.com/pnpm/pnpm/issues/2412
packages:
- "packages/*"
File diff suppressed because it is too large Load Diff
@@ -0,0 +1 @@
nodeLinker: node-modules
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,20 @@
{
"name": "otel-telemetry-loader",
"private": true,
"packageManager": "yarn@4.2.2+sha256.1aa43a5304405be7a7cb9cb5de7b97de9c4e8ddd3273e4dad00d6ae3eb39f0ef",
"engines": {
"pnpm": "8.15.5",
"yarn": "4.2.2"
},
"workspaces": [
"packages/*"
],
"dependencies": {
"@trigger.dev/sdk": "0.0.0-prerelease-20250321122618",
"openai": "4.47.0"
},
"devDependencies": {
"@traceloop/instrumentation-openai": "^0.10.0",
"typescript": "5.5.4"
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,3 @@
# https://github.com/pnpm/pnpm/issues/2412
packages:
- "packages/*"
@@ -0,0 +1,20 @@
import { task } from "@trigger.dev/sdk/v3";
import OpenAI from "openai";
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
baseURL: process.env.OPENAI_BASE_URL,
});
export const aiTask = task({
id: "ai",
run: async (payload: { prompt: string }) => {
const chatCompletion = await openai.chat.completions.create({
messages: [{ role: "user", content: payload.prompt }],
model: "gpt-3.5-turbo",
});
return chatCompletion.choices[0];
},
});
@@ -0,0 +1,9 @@
import { defineConfig } from "@trigger.dev/sdk/v3";
import { OpenAIInstrumentation } from "@traceloop/instrumentation-openai";
export default defineConfig({
project: "<fixture project>",
dirs: ["./src/trigger"],
instrumentations: [new OpenAIInstrumentation()],
maxDuration: 3600,
});
@@ -0,0 +1,32 @@
{
"include": ["src/**/*.ts", "trigger.config.ts"],
"compilerOptions": {
"target": "es2022",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "NodeNext",
"moduleResolution": "NodeNext",
"moduleDetection": "force",
"verbatimModuleSyntax": false,
"jsx": "react",
"strict": true,
"alwaysStrict": true,
"strictPropertyInitialization": false,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noFallthroughCasesInSwitch": true,
"resolveJsonModule": true,
"removeComments": false,
"esModuleInterop": true,
"emitDecoratorMetadata": false,
"experimentalDecorators": false,
"downlevelIteration": true,
"isolatedModules": true,
"noUncheckedIndexedAccess": true,
"pretty": true
}
}
File diff suppressed because it is too large Load Diff
+9
View File
@@ -0,0 +1,9 @@
import { z } from "zod";
const LogLevelSchema = z.enum(["debug", "info", "log", "warn", "error", "none"]).default("log");
const PackageManagerSchema = z.enum(["npm", "pnpm", "yarn"]);
export const E2EOptionsSchema = z.object({
logLevel: LogLevelSchema,
packageManager: PackageManagerSchema.optional(),
});
export type E2EOptions = z.infer<typeof E2EOptionsSchema>;
+485
View File
@@ -0,0 +1,485 @@
import { execa } from "execa";
import * as nodePath from "node:path";
import * as fs from "node:fs";
import { logger } from "../src/utilities/logger.js";
import { findUpMultiple, findUp } from "find-up";
import type { TaskRunExecutionResult, WorkerManifest } from "@trigger.dev/core/v3/schemas";
import { TaskRunProcess } from "../src/executions/taskRunProcess.js";
import { createTestHttpServer } from "@epic-web/test-server/http";
import type { TestCase, TestCaseRun } from "./fixtures.js";
import { access } from "node:fs/promises";
import type { MachinePreset } from "@trigger.dev/core/v3";
export type PackageManager = "npm" | "pnpm" | "yarn";
export const LOCKFILES = {
npm: "package-lock.json",
npmShrinkwrap: "npm-shrinkwrap.json",
pnpm: "pnpm-lock.yaml",
yarn: "yarn.lock",
bun: "bun.lockb",
};
export async function installFixtureDeps(options: {
fixtureDir: string;
packageManager: PackageManager;
workspaceDir: string;
}) {
const { packageManager, workspaceDir } = options;
if (["pnpm", "yarn"].includes(packageManager)) {
const version = await detectPackageManagerVersion(options);
debug(`Detected ${packageManager}@${version} from package.json 'engines' field`);
const { stdout, stderr } = await execa("corepack", ["use", `${packageManager}@${version}`], {
cwd: workspaceDir,
});
debug(stdout);
if (stderr) console.error(stderr);
} else {
const { stdout, stderr } = await execa(packageManager, installArgs(packageManager), {
cwd: workspaceDir,
env: {
...process.env,
NODE_PATH: nodePath.resolve(nodePath.join(workspaceDir, "node_modules")),
},
});
debug(stdout);
if (stderr) console.error(stderr);
}
}
export async function parsePackageManager(
packageManager: PackageManager | undefined,
fixtureDir: string
): Promise<PackageManager> {
let $packageManager: PackageManager;
if (packageManager) {
$packageManager = packageManager;
} else {
$packageManager = await detectPackageManagerFromArtifacts(fixtureDir);
}
return $packageManager;
}
export async function detectPackageManagerFromArtifacts(path: string): Promise<PackageManager> {
const foundPath = await findUp(Object.values(LOCKFILES), { cwd: path });
if (!foundPath) {
throw new Error("Could not detect package manager from artifacts");
}
logger.debug("Found path from package manager artifacts", { foundPath });
switch (nodePath.basename(foundPath)) {
case LOCKFILES.yarn:
logger.debug("Found yarn artifact", { foundPath });
return "yarn";
case LOCKFILES.pnpm:
logger.debug("Found pnpm artifact", { foundPath });
return "pnpm";
case LOCKFILES.npm:
case LOCKFILES.npmShrinkwrap:
logger.debug("Found npm artifact", { foundPath });
return "npm";
case LOCKFILES.bun:
logger.debug("Found bun artifact", { foundPath });
return "npm";
default:
throw new Error(`Unhandled package manager detection path: ${foundPath}`);
}
}
function debug(message: string) {
if (logger.loggerLevel === "debug") {
console.log(message);
}
}
function installArgs(packageManager: string) {
switch (packageManager) {
case "bun":
return ["install", "--frozen-lockfile"];
case "pnpm":
case "yarn":
throw new Error("pnpm and yarn must install using `corepack use`");
case "npm":
return ["ci", "--no-audit"];
default:
throw new Error(`Unknown package manager '${packageManager}'`);
}
}
async function detectPackageManagerVersion(options: {
fixtureDir: string;
packageManager: PackageManager;
workspaceDir: string;
}): Promise<string> {
const { fixtureDir, packageManager, workspaceDir } = options;
const pkgPaths = await findUpMultiple("package.json", { cwd: workspaceDir, stopAt: fixtureDir });
for (let pkgPath of pkgPaths) {
const buffer = fs.readFileSync(pkgPath, "utf8");
const pkgJSON = JSON.parse(buffer.toString());
if (!pkgJSON.engines) continue;
const version = pkgJSON.engines[packageManager];
if (version) return version;
}
throw new Error(`No version found for package manager ${packageManager}`);
}
export interface TypeScriptError {
file: string;
line: number;
column: number;
message: string;
}
export interface TscResult {
success: boolean;
errors: TypeScriptError[];
stdout: string;
stderr: string;
}
export async function runTsc(
cwd: string,
tsconfigName: string = "tsconfig.json",
binBasePath: string = cwd
): Promise<TscResult> {
const tsconfigPath = nodePath.join(cwd, tsconfigName);
const tscPath = nodePath.join(binBasePath, "node_modules", ".bin", "tsc");
// Ensure the tsconfig file exists
try {
await access(tsconfigPath);
} catch (_error) {
throw new Error(`TSConfig file not found: ${tsconfigPath}`);
}
try {
logger.debug(`Running TypeScript compiler: ${tscPath} --project ${tsconfigPath} --noEmit`, {
cwd,
});
const result = await execa(tscPath, ["--project", tsconfigPath, "--noEmit"], {
cwd,
reject: false,
});
const success = result.exitCode === 0;
const errors = success ? [] : parseTypeScriptErrors(result.stderr);
logger.debug(result.stdout);
logger.debug(result.stderr);
return {
success,
errors,
stdout: result.stdout,
stderr: result.stderr,
};
} catch (error) {
throw new Error(`Failed to run TypeScript compiler: ${error}`);
}
}
function parseTypeScriptErrors(stderr: string): TypeScriptError[] {
const errorRegex = /(.+)\((\d+),(\d+)\):\s+error\s+TS\d+:\s+(.+)/g;
const errors: TypeScriptError[] = [];
let match;
while ((match = errorRegex.exec(stderr)) !== null) {
errors.push({
file: match[1]!,
line: parseInt(match[2]!, 10),
column: parseInt(match[3]!, 10),
message: match[4]!,
});
}
return errors;
}
export type ExecuteTaskCaseRunOptions = {
testCase: TestCase;
run: TestCaseRun;
destination: string;
workerManifest: WorkerManifest;
contentHash: string;
};
export type ExecuteTaskRunUsageReport = {
durationMs: number;
};
export type ExecuteTaskRunResult = {
result: TaskRunExecutionResult;
usageReports: Array<ExecuteTaskRunUsageReport>;
totalDurationMs: number;
spans: Array<ExecuteTaskTraceEvent>;
};
export type ExecuteTaskTraceEvent = {
name: string;
traceId: string;
spanId: string;
parentSpanId?: string;
durationMs: number;
attributes?: { [key: string]: string | number | boolean | undefined };
};
export async function executeTestCaseRun({
run,
testCase,
destination,
workerManifest,
contentHash,
}: ExecuteTaskCaseRunOptions): Promise<ExecuteTaskRunResult> {
const usageReports: Array<ExecuteTaskRunUsageReport> = [];
const spans: Array<ExecuteTaskTraceEvent> = [];
// Create a disposable "server" instance.
const server = await createTestHttpServer({
defineRoutes(router) {
router.post("/usage", async ({ req }) => {
const jsonBody = await req.json();
usageReports.push({
durationMs: jsonBody.durationMs,
});
return Response.json({});
});
router.post("/v1/traces", async ({ req }) => {
const jsonBody = await req.json();
spans.push(...parseTraceBodyIntoEvents(jsonBody));
// TODO: Implement trace endpoint
return Response.json({});
});
router.post("/v1/logs", () => {
// TODO: Implement logs endpoint
return Response.json({});
});
router.post("/v1/chat/completions", async ({ req }) => {
return Response.json({
id: "chatcmpl-7XYZ123ABC456DEF789GHI",
object: "chat.completion",
created: 1631619199,
model: "gpt-3.5-turbo-0613",
choices: [
{
index: 0,
message: {
role: "assistant",
content:
"The capital of France is Paris. Paris is not only the political capital but also the cultural and economic center of France. It's known for its iconic landmarks such as the Eiffel Tower, the Louvre Museum, and Notre-Dame Cathedral.",
},
finish_reason: "stop",
},
],
usage: {
prompt_tokens: 29,
completion_tokens: 48,
total_tokens: 77,
},
});
});
},
});
const machine = {
name: "small-1x",
cpu: 1,
memory: 256,
centsPerMs: 0.0000001,
} satisfies MachinePreset;
try {
const taskRunProcess = new TaskRunProcess({
workerManifest: workerManifest!,
cwd: destination,
env: {
USAGE_EVENT_URL: server.http.url("/usage").href,
OTEL_EXPORTER_OTLP_ENDPOINT: server.http.url().origin,
TRIGGER_JWT: "test-jwt",
TRIGGER_SECRET_KEY: "test-secret",
TRIGGER_API_URL: server.http.url().origin,
USAGE_HEARTBEAT_INTERVAL_MS: "500",
OPENAI_API_KEY: "api-key",
OPENAI_BASE_URL: server.http.url().origin + "/v1",
},
serverWorker: {
id: "test",
version: "1.0.0",
contentHash,
},
machineResources: machine,
}).initialize();
const result = await taskRunProcess.execute({
payload: {
traceContext: {
traceparent: "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
},
environment: {},
execution: {
task: run.task,
attempt: {
id: "attempt_1234",
status: "RUNNING",
number: 1,
startedAt: new Date(),
backgroundWorkerId: "worker_1234",
backgroundWorkerTaskId: "task_1234",
},
run: {
id: "run_1234",
startedAt: new Date(),
payload: run.payload,
payloadType: run.payloadType ?? "application/json",
tags: [],
context: {},
isTest: false,
createdAt: new Date(),
durationMs: 0,
costInCents: 0,
baseCostInCents: 0,
version: "1.0.0",
},
queue: {
id: "queue_1234",
name: "test",
},
environment: {
type: "DEVELOPMENT",
id: "env_1234",
slug: "dev",
},
organization: {
id: "org_1234",
slug: "test",
name: "test",
},
project: {
id: "project_1234",
slug: "test",
ref: "main",
name: "test",
},
machine,
},
},
messageId: "run_1234",
});
await taskRunProcess.cleanup(true);
return {
result,
usageReports,
totalDurationMs: usageReports.reduce((acc, report) => acc + report.durationMs, 0),
spans,
};
} finally {
await server.close();
}
}
function parseTraceBodyIntoEvents(body: any): ExecuteTaskTraceEvent[] {
return body.resourceSpans.flatMap(parseResourceSpanIntoEvents);
}
function parseResourceSpanIntoEvents(resourceSpan: any): ExecuteTaskTraceEvent[] {
return resourceSpan.scopeSpans.flatMap((scopeSpan: any) =>
parseScopeSpanIntoEvents(scopeSpan, resourceSpan.resource)
);
}
function parseScopeSpanIntoEvents(scopeSpan: any, resource: any): ExecuteTaskTraceEvent[] {
return scopeSpan.spans.flatMap((span: any) => parseSpanInEvent(span, resource));
}
function parseSpanInEvent(span: any, resource: any): ExecuteTaskTraceEvent {
return {
name: span.name,
traceId: span.traceId,
spanId: span.spanId,
parentSpanId: span.parentSpanId,
durationMs: calculateSpanDurationMs(span),
attributes: {
...parseAttributes(resource.attributes),
...parseAttributes(span.attributes),
},
};
}
function calculateSpanDurationMs(span: any): number {
return Number(BigInt(span.endTimeUnixNano) - BigInt(span.startTimeUnixNano) / BigInt(1e6));
}
function parseAttributes(attributes: any): ExecuteTaskTraceEvent["attributes"] {
if (!attributes) return {};
return attributes.reduce((acc: any, attribute: any) => {
acc[attribute.key] = isStringValue(attribute.value)
? attribute.value.stringValue
: isIntValue(attribute.value)
? Number(attribute.value.intValue)
: isDoubleValue(attribute.value)
? attribute.value.doubleValue
: isBoolValue(attribute.value)
? attribute.value.boolValue
: isBytesValue(attribute.value)
? binaryToHex(attribute.value.bytesValue)
: undefined;
return acc;
}, {});
}
function isBoolValue(value: any | undefined): value is { boolValue: boolean } {
if (!value) return false;
return typeof value.boolValue === "boolean";
}
function isStringValue(value: any | undefined): value is { stringValue: string } {
if (!value) return false;
return typeof value.stringValue === "string";
}
function isIntValue(value: any | undefined): value is { intValue: bigint } {
if (!value) return false;
return typeof value.intValue === "number";
}
function isDoubleValue(value: any | undefined): value is { doubleValue: number } {
if (!value) return false;
return typeof value.doubleValue === "number";
}
function isBytesValue(value: any | undefined): value is { bytesValue: Buffer } {
if (!value) return false;
return Buffer.isBuffer(value.bytesValue);
}
function binaryToHex(buffer: Buffer | string): string;
function binaryToHex(buffer: Buffer | string | undefined): string | undefined;
function binaryToHex(buffer: Buffer | string | undefined): string | undefined {
if (!buffer) return undefined;
if (typeof buffer === "string") return buffer;
return Buffer.from(Array.from(buffer)).toString("hex");
}
+8
View File
@@ -0,0 +1,8 @@
import { configDefaults, defineConfig } from "vitest/config";
export default defineConfig({
test: {
globals: true,
exclude: [...configDefaults.exclude, "src/**/*"],
},
});