diff --git a/.changeset/lucky-cameras-shave.md b/.changeset/lucky-cameras-shave.md new file mode 100644 index 0000000000..867f22309e --- /dev/null +++ b/.changeset/lucky-cameras-shave.md @@ -0,0 +1,6 @@ +--- +"@trigger.dev/sdk": patch +"trigger.dev": patch +--- + +feat: add ability to set custom resource properties through trigger.config.ts or via the OTEL_RESOURCE_ATTRIBUTES env var diff --git a/apps/webapp/app/presenters/v3/SpanPresenter.server.ts b/apps/webapp/app/presenters/v3/SpanPresenter.server.ts index 04af907358..4239278cee 100644 --- a/apps/webapp/app/presenters/v3/SpanPresenter.server.ts +++ b/apps/webapp/app/presenters/v3/SpanPresenter.server.ts @@ -497,7 +497,18 @@ export class SpanPresenter extends BasePresenter { duration: span.duration, events: span.events, style: span.style, - properties: span.properties ? JSON.stringify(span.properties, null, 2) : undefined, + properties: + span.properties && + typeof span.properties === "object" && + Object.keys(span.properties).length > 0 + ? JSON.stringify(span.properties, null, 2) + : undefined, + resourceProperties: + span.resourceProperties && + typeof span.resourceProperties === "object" && + Object.keys(span.resourceProperties).length > 0 + ? JSON.stringify(span.resourceProperties, null, 2) + : undefined, entity: span.entity, metadata: span.metadata, triggeredRuns, diff --git a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route.tsx b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route.tsx index 613720ef08..15514ecd59 100644 --- a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route.tsx +++ b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route.tsx @@ -1071,6 +1071,17 @@ function SpanEntity({ span }: { span: Span }) { showOpenInModal /> ) : null} + {span.resourceProperties !== undefined ? ( + + ) : null} ); } diff --git a/apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts b/apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts index b87b8001f2..0ade9436d4 100644 --- a/apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts +++ b/apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts @@ -825,12 +825,16 @@ export async function resolveVariablesForEnvironment( runtimeEnvironment: RuntimeEnvironmentForEnvRepo, parentEnvironment?: RuntimeEnvironmentForEnvRepo ) { - const projectSecrets = await environmentVariablesRepository.getEnvironmentVariables( + let projectSecrets = await environmentVariablesRepository.getEnvironmentVariables( runtimeEnvironment.projectId, runtimeEnvironment.id, parentEnvironment?.id ); + projectSecrets = renameVariables(projectSecrets, { + OTEL_RESOURCE_ATTRIBUTES: "CUSTOM_OTEL_RESOURCE_ATTRIBUTES", + }); + const overridableTriggerVariables = await resolveOverridableTriggerVariables(runtimeEnvironment); const builtInVariables = @@ -853,6 +857,15 @@ export async function resolveVariablesForEnvironment( return result; } +function renameVariables(variables: EnvironmentVariable[], renameMap: Record) { + return variables.map((variable) => { + return { + ...variable, + key: renameMap[variable.key] ?? variable.key, + }; + }); +} + async function resolveOverridableTriggerVariables( runtimeEnvironment: RuntimeEnvironmentForEnvRepo ) { diff --git a/apps/webapp/app/v3/eventRepository/clickhouseEventRepository.server.ts b/apps/webapp/app/v3/eventRepository/clickhouseEventRepository.server.ts index 15bd85f9eb..8bf841c0bc 100644 --- a/apps/webapp/app/v3/eventRepository/clickhouseEventRepository.server.ts +++ b/apps/webapp/app/v3/eventRepository/clickhouseEventRepository.server.ts @@ -184,7 +184,10 @@ export class ClickhouseEventRepository implements IEventRepository { message: event.message, kind: this.createEventToTaskEventV1InputKind(event), status: this.createEventToTaskEventV1InputStatus(event), - attributes: this.createEventToTaskEventV1InputAttributes(event.properties), + attributes: this.createEventToTaskEventV1InputAttributes( + event.properties, + event.resourceProperties + ), metadata: this.createEventToTaskEventV1InputMetadata(event), expires_at: convertDateToClickhouseDateTime( new Date(Date.now() + 365 * 24 * 60 * 60 * 1000) // 1 year @@ -392,7 +395,24 @@ export class ClickhouseEventRepository implements IEventRepository { return "OK"; } - private createEventToTaskEventV1InputAttributes(attributes: Attributes): Record { + private createEventToTaskEventV1InputAttributes( + attributes: Attributes, + resourceAttributes?: Attributes + ): Record { + if (!attributes && !resourceAttributes) { + return {}; + } + + return { + ...this.createAttributesToInputAttributes(attributes), + ...this.createAttributesToInputAttributes(resourceAttributes, "$resource"), + }; + } + + private createAttributesToInputAttributes( + attributes: Attributes | undefined, + key?: string + ): Record { if (!attributes) { return {}; } @@ -406,6 +426,12 @@ export class ClickhouseEventRepository implements IEventRepository { const unflattenedAttributes = unflattenAttributes(publicAttributes); if (unflattenedAttributes && typeof unflattenedAttributes === "object") { + if (key) { + return { + [key]: unflattenedAttributes, + }; + } + return { ...unflattenedAttributes, }; @@ -1103,6 +1129,7 @@ export class ClickhouseEventRepository implements IEventRepository { events: [], style: {}, properties: undefined, + resourceProperties: undefined, entity: { type: undefined, id: undefined, @@ -1177,8 +1204,19 @@ export class ClickhouseEventRepository implements IEventRepository { } } - if (!span.properties && typeof record.attributes_text === "string") { - span.properties = this.#parseAttributes(record.attributes_text); + if ( + (span.properties == null || + (typeof span.properties === "object" && Object.keys(span.properties).length === 0)) && + typeof record.attributes_text === "string" + ) { + const parsedAttributes = this.#parseAttributes(record.attributes_text); + const resourceAttributes = parsedAttributes["$resource"]; + + // Remove the $resource key from the attributes + delete parsedAttributes["$resource"]; + + span.properties = parsedAttributes; + span.resourceProperties = resourceAttributes as Record | undefined; } } diff --git a/apps/webapp/app/v3/eventRepository/eventRepository.types.ts b/apps/webapp/app/v3/eventRepository/eventRepository.types.ts index 2d484480ab..dcbdb07a3d 100644 --- a/apps/webapp/app/v3/eventRepository/eventRepository.types.ts +++ b/apps/webapp/app/v3/eventRepository/eventRepository.types.ts @@ -53,6 +53,7 @@ export type CreateEventInput = Omit< | "links" > & { properties: Attributes; + resourceProperties?: Attributes; metadata: Attributes | undefined; style: Attributes | undefined; }; @@ -209,6 +210,7 @@ export type SpanDetail = { events: SpanEvents; // Timeline events, SpanEvents component style: TaskEventStyle; // Icons, variants, accessories (RunIcon, SpanTitle) properties: Record | string | number | boolean | null | undefined; // Displayed as JSON in span properties (CodeBlock) + resourceProperties?: Record | string | number | boolean | null | undefined; // Displayed as JSON in span resource properties (CodeBlock) // ============================================================================ // Entity & Relationships diff --git a/apps/webapp/app/v3/otlpExporter.server.ts b/apps/webapp/app/v3/otlpExporter.server.ts index cb05b375fc..8c60e72ac4 100644 --- a/apps/webapp/app/v3/otlpExporter.server.ts +++ b/apps/webapp/app/v3/otlpExporter.server.ts @@ -204,6 +204,24 @@ function convertLogsToCreateableEvents( const resourceProperties = extractEventProperties(resourceAttributes); + const userDefinedResourceAttributes = truncateAttributes( + convertKeyValueItemsToMap(resourceAttributes ?? [], [], undefined, [ + SemanticInternalAttributes.USAGE, + SemanticInternalAttributes.SPAN, + SemanticInternalAttributes.METADATA, + SemanticInternalAttributes.STYLE, + SemanticInternalAttributes.METRIC_EVENTS, + SemanticInternalAttributes.TRIGGER, + "process", + "sdk", + "service", + "ctx", + "cli", + "cloud", + ]), + spanAttributeValueLengthLimit + ); + const taskEventStore = extractStringAttribute(resourceAttributes, [SemanticInternalAttributes.TASK_EVENT_STORE]) ?? env.EVENT_REPOSITORY_DEFAULT_STORE; @@ -249,6 +267,7 @@ function convertLogsToCreateableEvents( status: logLevelToEventStatus(log.severityNumber), startTime: log.timeUnixNano, properties, + resourceProperties: userDefinedResourceAttributes, style: convertKeyValueItemsToMap( pickAttributes(log.attributes ?? [], SemanticInternalAttributes.STYLE), [] @@ -285,6 +304,24 @@ function convertSpansToCreateableEvents( const resourceProperties = extractEventProperties(resourceAttributes); + const userDefinedResourceAttributes = truncateAttributes( + convertKeyValueItemsToMap(resourceAttributes ?? [], [], undefined, [ + SemanticInternalAttributes.USAGE, + SemanticInternalAttributes.SPAN, + SemanticInternalAttributes.METADATA, + SemanticInternalAttributes.STYLE, + SemanticInternalAttributes.METRIC_EVENTS, + SemanticInternalAttributes.TRIGGER, + "process", + "sdk", + "service", + "ctx", + "cli", + "cloud", + ]), + spanAttributeValueLengthLimit + ); + const taskEventStore = extractStringAttribute(resourceAttributes, [SemanticInternalAttributes.TASK_EVENT_STORE]) ?? env.EVENT_REPOSITORY_DEFAULT_STORE; @@ -336,6 +373,7 @@ function convertSpansToCreateableEvents( events: spanEventsToEventEvents(span.events ?? []), duration: span.endTimeUnixNano - span.startTimeUnixNano, properties, + resourceProperties: userDefinedResourceAttributes, style: convertKeyValueItemsToMap( pickAttributes(span.attributes ?? [], SemanticInternalAttributes.STYLE), [] diff --git a/packages/cli-v3/src/dev/devSupervisor.ts b/packages/cli-v3/src/dev/devSupervisor.ts index ac2a4afbb1..98214d7a00 100644 --- a/packages/cli-v3/src/dev/devSupervisor.ts +++ b/packages/cli-v3/src/dev/devSupervisor.ts @@ -455,9 +455,6 @@ class DevSupervisor implements WorkerRuntime { TRIGGER_API_URL: this.options.client.apiURL, TRIGGER_SECRET_KEY: this.options.client.accessToken!, OTEL_EXPORTER_OTLP_COMPRESSION: "none", - OTEL_RESOURCE_ATTRIBUTES: JSON.stringify({ - [SemanticInternalAttributes.PROJECT_DIR]: this.options.config.workingDir, - }), OTEL_IMPORT_HOOK_INCLUDES, }; } diff --git a/packages/cli-v3/src/entryPoints/dev-run-worker.ts b/packages/cli-v3/src/entryPoints/dev-run-worker.ts index bed0fbaf96..7cd88ab5a9 100644 --- a/packages/cli-v3/src/entryPoints/dev-run-worker.ts +++ b/packages/cli-v3/src/entryPoints/dev-run-worker.ts @@ -209,6 +209,7 @@ async function doBootstrap() { logExporters: config.telemetry?.logExporters ?? [], diagLogLevel: (env.TRIGGER_OTEL_LOG_LEVEL as TracingDiagnosticLogLevel) ?? "none", forceFlushTimeoutMillis: 30_000, + resource: config.telemetry?.resource, }); const otelTracer: Tracer = tracingSDK.getTracer("trigger-dev-worker", VERSION); diff --git a/packages/cli-v3/src/entryPoints/managed-run-worker.ts b/packages/cli-v3/src/entryPoints/managed-run-worker.ts index 14e3d24a1c..f1512f27f0 100644 --- a/packages/cli-v3/src/entryPoints/managed-run-worker.ts +++ b/packages/cli-v3/src/entryPoints/managed-run-worker.ts @@ -188,6 +188,7 @@ async function doBootstrap() { forceFlushTimeoutMillis: 30_000, exporters: config.telemetry?.exporters ?? [], logExporters: config.telemetry?.logExporters ?? [], + resource: config.telemetry?.resource, }); const otelTracer: Tracer = tracingSDK.getTracer("trigger-dev-worker", VERSION); diff --git a/packages/cli-v3/src/utilities/dotEnv.ts b/packages/cli-v3/src/utilities/dotEnv.ts index f50b26f501..83cb92822a 100644 --- a/packages/cli-v3/src/utilities/dotEnv.ts +++ b/packages/cli-v3/src/utilities/dotEnv.ts @@ -2,7 +2,13 @@ import dotenv from "dotenv"; import { resolve } from "node:path"; import { env } from "std-env"; -const ENVVAR_FILES = [".env", ".env.development", ".env.local", ".env.development.local", "dev.vars"]; +const ENVVAR_FILES = [ + ".env", + ".env.development", + ".env.local", + ".env.development.local", + "dev.vars", +]; export function resolveDotEnvVars(cwd?: string, envFile?: string) { const result: { [key: string]: string } = {}; @@ -23,6 +29,11 @@ export function resolveDotEnvVars(cwd?: string, envFile?: string) { delete result.TRIGGER_SECRET_KEY; delete result.OTEL_EXPORTER_OTLP_ENDPOINT; + if (result.OTEL_RESOURCE_ATTRIBUTES) { + result.CUSTOM_OTEL_RESOURCE_ATTRIBUTES = result.OTEL_RESOURCE_ATTRIBUTES; + delete result.OTEL_RESOURCE_ATTRIBUTES; + } + return result; } diff --git a/packages/core/src/v3/config.ts b/packages/core/src/v3/config.ts index 71c4cd6521..9c6871a264 100644 --- a/packages/core/src/v3/config.ts +++ b/packages/core/src/v3/config.ts @@ -12,6 +12,7 @@ import type { import type { LogLevel } from "./logger/taskLogger.js"; import type { MachinePresetName } from "./schemas/common.js"; import { LogRecordExporter } from "@opentelemetry/sdk-logs"; +import type { Resource } from "@opentelemetry/resources"; export type CompatibilityFlag = "run_engine_v2"; @@ -107,6 +108,13 @@ export type TriggerConfig = { * @see https://trigger.dev/docs/config/config-file#exporters */ logExporters?: Array; + + /** + * Resource to use for OpenTelemetry. This is useful if you want to add custom resources to your tasks. + * + * @see https://trigger.dev/docs/config/config-file#resource + */ + resource?: Resource; }; /** diff --git a/packages/core/src/v3/otel/tracingSDK.ts b/packages/core/src/v3/otel/tracingSDK.ts index 7fcb82450c..9bfd098ffd 100644 --- a/packages/core/src/v3/otel/tracingSDK.ts +++ b/packages/core/src/v3/otel/tracingSDK.ts @@ -10,7 +10,12 @@ import { TraceState } from "@opentelemetry/core"; import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http"; import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http"; import { registerInstrumentations, type Instrumentation } from "@opentelemetry/instrumentation"; -import { detectResources, processDetector, resourceFromAttributes } from "@opentelemetry/resources"; +import { + detectResources, + processDetector, + Resource, + resourceFromAttributes, +} from "@opentelemetry/resources"; import { BatchLogRecordProcessor, LogRecordExporter, @@ -64,6 +69,7 @@ export type TracingSDKConfig = { exporters?: SpanExporter[]; logExporters?: LogRecordExporter[]; diagLogLevel?: TracingDiagnosticLogLevel; + resource?: Resource; }; const idGenerator = new RandomIdGenerator(); @@ -84,6 +90,10 @@ export class TracingSDK { ? JSON.parse(envResourceAttributesSerialized) : {}; + const customEnvResourceAttributes = parseOtelResourceAttributes( + getEnvVar("CUSTOM_OTEL_RESOURCE_ATTRIBUTES") + ); + const commonResources = detectResources({ detectors: [processDetector], }) @@ -99,7 +109,9 @@ export class TracingSDK { }) ) .merge(resourceFromAttributes(envResourceAttributes)) - .merge(resourceFromAttributes(taskContext.resourceAttributes)); + .merge(resourceFromAttributes(customEnvResourceAttributes)) + .merge(resourceFromAttributes(taskContext.resourceAttributes)) + .merge(config.resource ?? resourceFromAttributes({})); const spanExporter = new OTLPTraceExporter({ url: `${config.url}/v1/traces`, @@ -489,3 +501,74 @@ function isTraceFlagSampled(traceFlags?: number): boolean { return (traceFlags & TraceFlags.SAMPLED) === TraceFlags.SAMPLED; } + +function isPrintableAscii(str: string): boolean { + // printable ASCII: 0x20 (space) .. 0x7E (~) + for (let i = 0; i < str.length; i++) { + const code = str.charCodeAt(i); + if (code < 0x20 || code > 0x7e) return false; + } + return true; +} + +function isValid(name: string | undefined): boolean { + if (!name) return false; + return typeof name === "string" && name.length <= 255 && isPrintableAscii(name); +} + +function isValidAndNotEmpty(name: string | undefined): boolean { + if (!name) return false; + return isValid(name) && name.length > 0; +} + +export function parseOtelResourceAttributes( + rawEnvAttributes: string | undefined | null +): Record { + if (!rawEnvAttributes) return {}; + + const COMMA = ","; + const KV = "="; + const attributes: Record = {}; + + // use negative limit to support trailing empty attribute + const rawAttributes = rawEnvAttributes.split(COMMA, -1); + for (const rawAttribute of rawAttributes) { + const keyValuePair = rawAttribute.split(KV, -1); + if (keyValuePair.length !== 2) { + // skip invalid pair + continue; + } + let [key, value] = keyValuePair; + key = key?.trim(); + // trim and remove surrounding double quotes + value = value?.trim().replace(/^"|"$/g, ""); + + if (!value || !key) { + continue; + } + + if (!isValidAndNotEmpty(key)) { + throw new Error( + `Attribute key should be a ASCII string with a length greater than 0 and not exceed 255 characters.` + ); + } + if (!isValid(value)) { + throw new Error( + `Attribute value should be a ASCII string with a length not exceed 255 characters.` + ); + } + + // decode percent-encoding (deployment%20name -> deployment name) + try { + attributes[key] = decodeURIComponent(value); + } catch (e: unknown) { + // decodeURIComponent can throw for malformed sequences; rethrow or handle + if (e instanceof Error) { + throw new Error(`Failed to decode attribute value for key ${key}: ${e.message}`); + } + throw new Error(`Failed to decode attribute value for key ${key}`); + } + } + + return attributes; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4d512a2127..b61b567b00 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2593,6 +2593,25 @@ importers: specifier: ^5 version: 5.5.4 + references/telemetry: + dependencies: + '@opentelemetry/resources': + specifier: 2.2.0 + version: 2.2.0(@opentelemetry/api@1.9.0) + '@trigger.dev/sdk': + specifier: workspace:* + version: link:../../packages/trigger-sdk + devDependencies: + '@types/node': + specifier: ^20 + version: 20.14.14 + trigger.dev: + specifier: workspace:* + version: link:../../packages/cli-v3 + typescript: + specifier: ^5 + version: 5.5.4 + references/test-tasks: dependencies: '@trigger.dev/sdk': @@ -9909,6 +9928,16 @@ packages: '@opentelemetry/semantic-conventions': 1.36.0 dev: false + /@opentelemetry/core@2.2.0(@opentelemetry/api@1.9.0): + resolution: {integrity: sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/semantic-conventions': 1.36.0 + dev: false + /@opentelemetry/exporter-logs-otlp-grpc@0.203.0(@opentelemetry/api@1.9.0): resolution: {integrity: sha512-g/2Y2noc/l96zmM+g0LdeuyYKINyBwN6FJySoU15LHPLcMN/1a0wNk2SegwKcxrRdE7Xsm7fkIR5n6XFe3QpPw==} engines: {node: ^18.19.0 || >=20.6.0} @@ -10660,6 +10689,17 @@ packages: '@opentelemetry/semantic-conventions': 1.36.0 dev: false + /@opentelemetry/resources@2.2.0(@opentelemetry/api@1.9.0): + resolution: {integrity: sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.36.0 + dev: false + /@opentelemetry/sdk-logs@0.203.0(@opentelemetry/api@1.9.0): resolution: {integrity: sha512-vM2+rPq0Vi3nYA5akQD2f3QwossDnTDLvKbea6u/A2NZ3XDkPxMfo/PNrDoXhDUD/0pPo2CdH5ce/thn9K0kLw==} engines: {node: ^18.19.0 || >=20.6.0} diff --git a/references/telemetry/package.json b/references/telemetry/package.json new file mode 100644 index 0000000000..cb9bff620f --- /dev/null +++ b/references/telemetry/package.json @@ -0,0 +1,18 @@ +{ + "name": "references-telemetry", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "trigger dev", + "deploy": "trigger deploy" + }, + "dependencies": { + "@trigger.dev/sdk": "workspace:*", + "@opentelemetry/resources": "2.2.0" + }, + "devDependencies": { + "@types/node": "^20", + "trigger.dev": "workspace:*", + "typescript": "^5" + } +} \ No newline at end of file diff --git a/references/telemetry/src/trigger/tasks.ts b/references/telemetry/src/trigger/tasks.ts new file mode 100644 index 0000000000..d1a85ec166 --- /dev/null +++ b/references/telemetry/src/trigger/tasks.ts @@ -0,0 +1,9 @@ +import { logger, task } from "@trigger.dev/sdk"; + +export const telemetryTestTask = task({ + id: "telemetry-test", + run: async (payload: any, { ctx }) => { + logger.info("Hello, world!", { payload, ctx }); + return { message: "Hello, world!" }; + }, +}); diff --git a/references/telemetry/trigger.config.ts b/references/telemetry/trigger.config.ts new file mode 100644 index 0000000000..48e6a877ac --- /dev/null +++ b/references/telemetry/trigger.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from "@trigger.dev/sdk"; +import { resourceFromAttributes } from "@opentelemetry/resources"; + +export default defineConfig({ + project: process.env.TRIGGER_PROJECT_REF!, + dirs: ["./src/trigger"], + maxDuration: 3600, + telemetry: { + resource: resourceFromAttributes({ + "foo.bar": "telemetry-test", + "foo.baz": "1.0.0", + }), + }, +}); diff --git a/references/telemetry/tsconfig.json b/references/telemetry/tsconfig.json new file mode 100644 index 0000000000..9a5ee0b9d6 --- /dev/null +++ b/references/telemetry/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2023", + "module": "Node16", + "moduleResolution": "Node16", + "esModuleInterop": true, + "strict": true, + "skipLibCheck": true, + "customConditions": ["@triggerdotdev/source"], + "jsx": "preserve", + "lib": ["DOM", "DOM.Iterable"], + "noEmit": true + }, + "include": ["./src/**/*.ts", "trigger.config.ts"] +}