c7861be520
Once this is merged, oxlint is at a pretty sensible baseline. **Enable `no-unused-vars`, `typescript/consistent-type-imports`, and `import/no-duplicates` lint rules** Turns on three previously-disabled oxlint rules across the monorepo and fixes all violations: - **`no-unused-vars`** – enabled as an error with standard ignore patterns: unused function arguments are ignored by default (`args: "none"`), variables/caught errors/destructured array elements prefixed with `_` are allowed, and rest siblings are permitted. - **`typescript/consistent-type-imports`** – enforced as an error; all type-only imports now use the `import type` syntax. - **`import/no-duplicates`** – enforced as an error; duplicate import statements from the same module have been merged. The remaining commits clean up the violations found across the codebase: removing unused variables/imports/type aliases, adding `_` prefixes to intentionally unused bindings, fixing duplicate imports, and converting value imports to `import type` where appropriate.
330 lines
10 KiB
TypeScript
330 lines
10 KiB
TypeScript
import type { AsyncIterableStream } from "@trigger.dev/core/v3";
|
|
import {
|
|
createAsyncIterableStreamFromAsyncIterable,
|
|
SemanticInternalAttributes,
|
|
taskContext,
|
|
} from "@trigger.dev/core/v3";
|
|
import { logger } from "@trigger.dev/sdk/v3";
|
|
import { carrierFromContext } from "@trigger.dev/core/v3/otel";
|
|
import assert from "node:assert";
|
|
import fs from "node:fs";
|
|
import type { Result, Options as XOptions } from "tinyexec";
|
|
import { x } from "tinyexec";
|
|
import { createTempFileSync, withTempFile } from "./utils/tempFiles.js";
|
|
|
|
export type PythonExecOptions = Partial<XOptions> & {
|
|
env?: { [key: string]: string | undefined };
|
|
};
|
|
|
|
export const python = {
|
|
async run(scriptArgs: string[] = [], options: PythonExecOptions = {}): Promise<Result> {
|
|
const pythonBin = process.env.PYTHON_BIN_PATH || "python";
|
|
|
|
const carrier = carrierFromContext();
|
|
|
|
return await logger.trace(
|
|
"python.run()",
|
|
async (span) => {
|
|
const result = await x(pythonBin, scriptArgs, {
|
|
...options,
|
|
nodeOptions: {
|
|
...(options.nodeOptions || {}),
|
|
env: {
|
|
...process.env,
|
|
...options.env,
|
|
TRACEPARENT: carrier["traceparent"],
|
|
OTEL_RESOURCE_ATTRIBUTES: `${
|
|
SemanticInternalAttributes.EXECUTION_ENVIRONMENT
|
|
}=trigger,${Object.entries(taskContext.attributes)
|
|
.map(([key, value]) => `${key}=${value}`)
|
|
.join(",")}`,
|
|
},
|
|
},
|
|
throwOnError: false, // Ensure errors are handled manually
|
|
});
|
|
|
|
if (result.exitCode) {
|
|
span.setAttribute("exitCode", result.exitCode);
|
|
}
|
|
|
|
if (result.exitCode !== 0) {
|
|
throw new Error(
|
|
`${scriptArgs.join(" ")} exited with a non-zero code ${result.exitCode}:\n${
|
|
result.stderr
|
|
}`
|
|
);
|
|
}
|
|
|
|
return result;
|
|
},
|
|
{
|
|
attributes: {
|
|
pythonBin,
|
|
args: scriptArgs.join(" "),
|
|
[SemanticInternalAttributes.STYLE_ICON]: "python",
|
|
},
|
|
}
|
|
);
|
|
},
|
|
|
|
async runScript(
|
|
scriptPath: string,
|
|
scriptArgs: string[] = [],
|
|
options: PythonExecOptions = {}
|
|
): Promise<Result> {
|
|
assert(scriptPath, "Script path is required");
|
|
assert(fs.existsSync(scriptPath), `Script does not exist: ${scriptPath}`);
|
|
|
|
return await logger.trace(
|
|
"python.runScript()",
|
|
async (span) => {
|
|
span.setAttribute("scriptPath", scriptPath);
|
|
|
|
const carrier = carrierFromContext();
|
|
|
|
const result = await x(
|
|
process.env.PYTHON_BIN_PATH || "python",
|
|
[scriptPath, ...scriptArgs],
|
|
{
|
|
...options,
|
|
nodeOptions: {
|
|
...(options.nodeOptions || {}),
|
|
env: {
|
|
...process.env,
|
|
...options.env,
|
|
TRACEPARENT: carrier["traceparent"],
|
|
OTEL_RESOURCE_ATTRIBUTES: `${
|
|
SemanticInternalAttributes.EXECUTION_ENVIRONMENT
|
|
}=trigger,${Object.entries(taskContext.attributes)
|
|
.map(([key, value]) => `${key}=${value}`)
|
|
.join(",")}`,
|
|
OTEL_LOG_LEVEL: "DEBUG",
|
|
},
|
|
},
|
|
throwOnError: false,
|
|
}
|
|
);
|
|
|
|
if (result.exitCode) {
|
|
span.setAttribute("exitCode", result.exitCode);
|
|
}
|
|
|
|
if (result.exitCode !== 0) {
|
|
throw new Error(
|
|
`${scriptPath} ${scriptArgs.join(" ")} exited with a non-zero code ${
|
|
result.exitCode
|
|
}:\n${result.stdout}\n${result.stderr}`
|
|
);
|
|
}
|
|
|
|
return result;
|
|
},
|
|
{
|
|
attributes: {
|
|
pythonBin: process.env.PYTHON_BIN_PATH || "python",
|
|
scriptPath,
|
|
args: scriptArgs.join(" "),
|
|
[SemanticInternalAttributes.STYLE_ICON]: "python",
|
|
},
|
|
}
|
|
);
|
|
},
|
|
|
|
async runInline(scriptContent: string, options: PythonExecOptions = {}): Promise<Result> {
|
|
assert(scriptContent, "Script content is required");
|
|
|
|
return await logger.trace(
|
|
"python.runInline()",
|
|
async (span) => {
|
|
span.setAttribute("contentLength", scriptContent.length);
|
|
|
|
// Using the withTempFile utility to handle the temporary file
|
|
return await withTempFile(
|
|
`script_${Date.now()}.py`,
|
|
async (tempFilePath) => {
|
|
span.setAttribute("tempFilePath", tempFilePath);
|
|
|
|
const carrier = carrierFromContext();
|
|
|
|
const pythonBin = process.env.PYTHON_BIN_PATH || "python";
|
|
const result = await x(pythonBin, [tempFilePath], {
|
|
...options,
|
|
nodeOptions: {
|
|
...(options.nodeOptions || {}),
|
|
env: {
|
|
...process.env,
|
|
...options.env,
|
|
TRACEPARENT: carrier["traceparent"],
|
|
OTEL_RESOURCE_ATTRIBUTES: `${
|
|
SemanticInternalAttributes.EXECUTION_ENVIRONMENT
|
|
}=trigger,${Object.entries(taskContext.attributes)
|
|
.map(([key, value]) => `${key}=${value}`)
|
|
.join(",")}`,
|
|
},
|
|
},
|
|
throwOnError: false,
|
|
});
|
|
|
|
if (result.exitCode) {
|
|
span.setAttribute("exitCode", result.exitCode);
|
|
}
|
|
|
|
if (result.exitCode !== 0) {
|
|
throw new Error(
|
|
`Inline script exited with a non-zero code ${result.exitCode}:\n${result.stderr}`
|
|
);
|
|
}
|
|
|
|
return result;
|
|
},
|
|
scriptContent
|
|
);
|
|
},
|
|
{
|
|
attributes: {
|
|
pythonBin: process.env.PYTHON_BIN_PATH || "python",
|
|
contentPreview:
|
|
scriptContent.substring(0, 100) + (scriptContent.length > 100 ? "..." : ""),
|
|
[SemanticInternalAttributes.STYLE_ICON]: "python",
|
|
},
|
|
}
|
|
);
|
|
},
|
|
// Stream namespace for streaming functions
|
|
stream: {
|
|
run(scriptArgs: string[] = [], options: PythonExecOptions = {}): AsyncIterableStream<string> {
|
|
const pythonBin = process.env.PYTHON_BIN_PATH || "python";
|
|
|
|
const carrier = carrierFromContext();
|
|
|
|
const pythonProcess = x(pythonBin, scriptArgs, {
|
|
...options,
|
|
nodeOptions: {
|
|
...(options.nodeOptions || {}),
|
|
env: {
|
|
...process.env,
|
|
...options.env,
|
|
TRACEPARENT: carrier["traceparent"],
|
|
OTEL_RESOURCE_ATTRIBUTES: `${
|
|
SemanticInternalAttributes.EXECUTION_ENVIRONMENT
|
|
}=trigger,${Object.entries(taskContext.attributes)
|
|
.map(([key, value]) => `${key}=${value}`)
|
|
.join(",")}`,
|
|
},
|
|
},
|
|
throwOnError: false,
|
|
});
|
|
|
|
const span = logger.startSpan("python.stream.run()", {
|
|
attributes: {
|
|
pythonBin,
|
|
args: scriptArgs.join(" "),
|
|
[SemanticInternalAttributes.STYLE_ICON]: "python",
|
|
},
|
|
});
|
|
|
|
return createAsyncIterableStreamFromAsyncIterable(pythonProcess, {
|
|
transform: (chunk, controller) => {
|
|
controller.enqueue(chunk);
|
|
},
|
|
flush: () => {
|
|
span.end();
|
|
},
|
|
});
|
|
},
|
|
runScript(
|
|
scriptPath: string,
|
|
scriptArgs: string[] = [],
|
|
options: PythonExecOptions = {}
|
|
): AsyncIterableStream<string> {
|
|
assert(scriptPath, "Script path is required");
|
|
assert(fs.existsSync(scriptPath), `Script does not exist: ${scriptPath}`);
|
|
|
|
const pythonBin = process.env.PYTHON_BIN_PATH || "python";
|
|
|
|
const carrier = carrierFromContext();
|
|
|
|
const pythonProcess = x(pythonBin, [scriptPath, ...scriptArgs], {
|
|
...options,
|
|
nodeOptions: {
|
|
...(options.nodeOptions || {}),
|
|
env: {
|
|
...process.env,
|
|
...options.env,
|
|
TRACEPARENT: carrier["traceparent"],
|
|
OTEL_RESOURCE_ATTRIBUTES: `${
|
|
SemanticInternalAttributes.EXECUTION_ENVIRONMENT
|
|
}=trigger,${Object.entries(taskContext.attributes)
|
|
.map(([key, value]) => `${key}=${value}`)
|
|
.join(",")}`,
|
|
},
|
|
},
|
|
throwOnError: false,
|
|
});
|
|
|
|
const span = logger.startSpan("python.stream.runScript()", {
|
|
attributes: {
|
|
pythonBin,
|
|
scriptPath,
|
|
args: scriptArgs.join(" "),
|
|
[SemanticInternalAttributes.STYLE_ICON]: "python",
|
|
},
|
|
});
|
|
|
|
return createAsyncIterableStreamFromAsyncIterable(pythonProcess, {
|
|
transform: (chunk, controller) => {
|
|
controller.enqueue(chunk);
|
|
},
|
|
flush: () => {
|
|
span.end();
|
|
},
|
|
});
|
|
},
|
|
runInline(scriptContent: string, options: PythonExecOptions = {}): AsyncIterableStream<string> {
|
|
assert(scriptContent, "Script content is required");
|
|
|
|
const pythonBin = process.env.PYTHON_BIN_PATH || "python";
|
|
|
|
const pythonScriptPath = createTempFileSync(`script_${Date.now()}.py`, scriptContent);
|
|
|
|
const carrier = carrierFromContext();
|
|
|
|
const pythonProcess = x(pythonBin, [pythonScriptPath], {
|
|
...options,
|
|
nodeOptions: {
|
|
...(options.nodeOptions || {}),
|
|
env: {
|
|
...process.env,
|
|
...options.env,
|
|
TRACEPARENT: carrier["traceparent"],
|
|
OTEL_RESOURCE_ATTRIBUTES: `${
|
|
SemanticInternalAttributes.EXECUTION_ENVIRONMENT
|
|
}=trigger,${Object.entries(taskContext.attributes)
|
|
.map(([key, value]) => `${key}=${value}`)
|
|
.join(",")}`,
|
|
},
|
|
},
|
|
throwOnError: false,
|
|
});
|
|
|
|
const span = logger.startSpan("python.stream.runInline()", {
|
|
attributes: {
|
|
pythonBin,
|
|
contentPreview:
|
|
scriptContent.substring(0, 100) + (scriptContent.length > 100 ? "..." : ""),
|
|
[SemanticInternalAttributes.STYLE_ICON]: "python",
|
|
},
|
|
});
|
|
|
|
return createAsyncIterableStreamFromAsyncIterable(pythonProcess, {
|
|
transform: (chunk, controller) => {
|
|
controller.enqueue(chunk);
|
|
},
|
|
flush: () => {
|
|
span.end();
|
|
},
|
|
});
|
|
},
|
|
},
|
|
};
|