Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ jobs:
config: |
paths-ignore:
- dist/**
- scripts/**
- uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
with:
category: "/language:javascript-typescript"
58 changes: 15 additions & 43 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -38763,10 +38763,10 @@ function stringifyCell(v) {
}
/** Escape + truncate a value to fit inside a markdown table cell. */
function cell(v, maxLen = CELL_MAX) {
let s = stringifyCell(v);
s = s.replace(/\|/g, "\\|").replace(/\r?\n/g, " ");
let s = stringifyCell(v).replace(/[\r\n]+/g, " ");
if (s.length > maxLen)
s = s.slice(0, maxLen - 1) + "…";
s = s.replace(/\\/g, "\\\\").replace(/\|/g, "\\|");
return s || "—";
}
function formatScore(v) {
Expand Down Expand Up @@ -42885,32 +42885,23 @@ class NodeScript extends ExperimentScript {
});
}
}
async function readJsSdkVersion(nodeModulesDir) {
try {
const raw = await promises_namespaceObject.readFile(external_node_path_namespaceObject.join(nodeModulesDir, JS_SDK_PACKAGE, "package.json"), "utf8");
const pkg = JSON.parse(raw);
return pkg.version ?? null;
}
catch {
return null;
}
}
/**
* Stable directory where the Node runtime packages live for the duration of
* a job. Using a fixed path (under `$RUNNER_TEMP` when available, OS tmpdir
* otherwise) means repeated action invocations within the same job see the
* existing install and can skip `npm install`.
* Private directory where the Node runtime packages live for this action
* invocation. Use a freshly-created temp directory instead of a predictable
* shared path so self-hosted/shared runners cannot pre-seed files there.
*/
function nodeSdkRoot() {
async function createNodeSdkRoot() {
const base = process.env.RUNNER_TEMP ?? external_node_os_namespaceObject.tmpdir();
return external_node_path_namespaceObject.join(base, "langfuse-experiment-action", "node");
await promises_namespaceObject.mkdir(base, { recursive: true });
const tmpRoot = await promises_namespaceObject.mkdtemp(external_node_path_namespaceObject.join(base, "langfuse-experiment-action-node-"));
await promises_namespaceObject.chmod(tmpRoot, 0o700);
return tmpRoot;
}
/**
* Install the Langfuse JS SDK (and OTel support packages + tsx) into a
* stable directory. Skips `npm install` only when a specific version was
* requested and the same version is already present — common on repeat
* invocations in a single job. For `latest` we always run npm so an older
* cached install gets upgraded.
* private temporary directory. Each action invocation installs into a fresh
* directory to avoid trusting predictable shared temp paths on self-hosted
* runners.
*
* When `skipInstallation` is true we don't install anything and instead
* return the caller's CWD `node_modules` — the user's workflow is expected
Expand All @@ -42922,28 +42913,9 @@ async function ensureNodeSdk(sdkVersion, skipInstallation) {
info(`JS SDK install skipped (should_skip_sdk_installation=true) — using ${cwdNodeModules}.`);
return cwdNodeModules;
}
const tmpRoot = nodeSdkRoot();
const tmpRoot = await createNodeSdkRoot();
const nodeModulesDir = external_node_path_namespaceObject.join(tmpRoot, "node_modules");
await promises_namespaceObject.mkdir(tmpRoot, { recursive: true });
const pkgPath = external_node_path_namespaceObject.join(tmpRoot, "package.json");
try {
await promises_namespaceObject.access(pkgPath);
}
catch {
await promises_namespaceObject.writeFile(pkgPath, JSON.stringify({ name: "langfuse-action-runtime", private: true, type: "module" }, null, 2));
}
// Skip only on exact-version match. For `latest` we always run npm so an
// older cached copy actually gets upgraded to the current latest — skipping
// purely because *something* is there would silently violate the contract.
const existing = await readJsSdkVersion(nodeModulesDir);
if (existing && sdkVersion !== "latest" && existing === sdkVersion) {
info(`JS SDK already present (${JS_SDK_PACKAGE}@${existing}); skipping install.`);
return nodeModulesDir;
}
if (existing) {
core_debug(`${JS_SDK_PACKAGE} ${existing} already at ${nodeModulesDir}; running npm to ` +
`${sdkVersion === "latest" ? "refresh to latest" : `switch to ${sdkVersion}`}.`);
}
await promises_namespaceObject.writeFile(external_node_path_namespaceObject.join(tmpRoot, "package.json"), JSON.stringify({ name: "langfuse-action-runtime", private: true, type: "module" }, null, 2));
const sdkSpec = sdkVersion === "latest" ? `${JS_SDK_PACKAGE}@latest` : `${JS_SDK_PACKAGE}@${sdkVersion}`;
const specs = [sdkSpec, ...JS_SUPPORT_PACKAGES, "tsx"];
info(`Installing JS SDK into ${tmpRoot}: ${specs.join(", ")}`);
Expand Down
2 changes: 1 addition & 1 deletion dist/index.js.map

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions src/comment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,9 @@ function stringifyCell(v: unknown): string {

/** Escape + truncate a value to fit inside a markdown table cell. */
function cell(v: unknown, maxLen = CELL_MAX): string {
let s = stringifyCell(v);
s = s.replace(/\|/g, "\\|").replace(/\r?\n/g, " ");
let s = stringifyCell(v).replace(/[\r\n]+/g, " ");
if (s.length > maxLen) s = s.slice(0, maxLen - 1) + "…";
s = s.replace(/\\/g, "\\\\").replace(/\|/g, "\\|");
return s || "—";
}

Expand Down
66 changes: 16 additions & 50 deletions src/executors/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,36 +62,24 @@ export class NodeScript extends ExperimentScript {
}
}

async function readJsSdkVersion(nodeModulesDir: string): Promise<string | null> {
try {
const raw = await fs.readFile(
path.join(nodeModulesDir, JS_SDK_PACKAGE, "package.json"),
"utf8",
);
const pkg = JSON.parse(raw) as { version?: string };
return pkg.version ?? null;
} catch {
return null;
}
}

/**
* Stable directory where the Node runtime packages live for the duration of
* a job. Using a fixed path (under `$RUNNER_TEMP` when available, OS tmpdir
* otherwise) means repeated action invocations within the same job see the
* existing install and can skip `npm install`.
* Private directory where the Node runtime packages live for this action
* invocation. Use a freshly-created temp directory instead of a predictable
* shared path so self-hosted/shared runners cannot pre-seed files there.
*/
function nodeSdkRoot(): string {
async function createNodeSdkRoot(): Promise<string> {
const base = process.env.RUNNER_TEMP ?? os.tmpdir();
return path.join(base, "langfuse-experiment-action", "node");
await fs.mkdir(base, { recursive: true });
const tmpRoot = await fs.mkdtemp(path.join(base, "langfuse-experiment-action-node-"));
await fs.chmod(tmpRoot, 0o700);
return tmpRoot;
}

/**
* Install the Langfuse JS SDK (and OTel support packages + tsx) into a
* stable directory. Skips `npm install` only when a specific version was
* requested and the same version is already present — common on repeat
* invocations in a single job. For `latest` we always run npm so an older
* cached install gets upgraded.
* private temporary directory. Each action invocation installs into a fresh
* directory to avoid trusting predictable shared temp paths on self-hosted
* runners.
*
* When `skipInstallation` is true we don't install anything and instead
* return the caller's CWD `node_modules` — the user's workflow is expected
Expand All @@ -109,34 +97,12 @@ export async function ensureNodeSdk(
return cwdNodeModules;
}

const tmpRoot = nodeSdkRoot();
const tmpRoot = await createNodeSdkRoot();
const nodeModulesDir = path.join(tmpRoot, "node_modules");
await fs.mkdir(tmpRoot, { recursive: true });

const pkgPath = path.join(tmpRoot, "package.json");
try {
await fs.access(pkgPath);
} catch {
await fs.writeFile(
pkgPath,
JSON.stringify({ name: "langfuse-action-runtime", private: true, type: "module" }, null, 2),
);
}

// Skip only on exact-version match. For `latest` we always run npm so an
// older cached copy actually gets upgraded to the current latest — skipping
// purely because *something* is there would silently violate the contract.
const existing = await readJsSdkVersion(nodeModulesDir);
if (existing && sdkVersion !== "latest" && existing === sdkVersion) {
core.info(`JS SDK already present (${JS_SDK_PACKAGE}@${existing}); skipping install.`);
return nodeModulesDir;
}
if (existing) {
core.debug(
`${JS_SDK_PACKAGE} ${existing} already at ${nodeModulesDir}; running npm to ` +
`${sdkVersion === "latest" ? "refresh to latest" : `switch to ${sdkVersion}`}.`,
);
}
await fs.writeFile(
path.join(tmpRoot, "package.json"),
JSON.stringify({ name: "langfuse-action-runtime", private: true, type: "module" }, null, 2),
);

const sdkSpec =
sdkVersion === "latest" ? `${JS_SDK_PACKAGE}@latest` : `${JS_SDK_PACKAGE}@${sdkVersion}`;
Expand Down
24 changes: 24 additions & 0 deletions tests/comment.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,30 @@ describe("renderScriptSection snapshots", () => {
expect(section).not.toContain("2026-04-20T");
});

it("truncates table cells before escaping markdown characters", () => {
const input = `${"a".repeat(78)}|tail`;
const section = renderScriptSection({
result: {
...pyPassingResult,
normalizedResult: normalizeExperimentResult({
name: "Uppercase task",
experiment_id: "0f212f9182320769",
run_evaluations: [{ name: "avg_accuracy", value: 1 }],
item_results: [
{
item: { input, expected_output: "expected" },
output: "actual",
evaluations: [{ name: "exact_match", value: 0 }],
},
],
}),
},
});

expect(section).toContain(`${"a".repeat(78)}\\|…`);
expect(section).not.toContain(`${"a".repeat(78)}\\…`);
});

it("caps the per-item table and adds a truncation note when there are many items", () => {
// 60 synthetic items → over the 50-row cap.
const manyItems = Array.from({ length: 60 }, (_, i) => ({
Expand Down
Loading