Skip to content
Open
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
4 changes: 4 additions & 0 deletions biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
"scripts/**/*.mjs",
"scripts/**/*.ts",
"test/**/*.js",
"test/credentials-shim.test.ts",
"test/runner-basic.test.ts",
"docs/_ext/**/*.js",
"nemoclaw/src/**/*.ts",
"!dist",
Expand Down Expand Up @@ -95,6 +97,8 @@
"scripts/**/*.js",
"scripts/**/*.mjs",
"test/**/*.js",
"test/credentials-shim.test.ts",
"test/runner-basic.test.ts",
"docs/_ext/**/*.js"
],
"linter": {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
"docs:deps": "node -p \"require('./fern/fern.config.json').version\" | xargs -I {} npx --yes fern-api@{} --version",
"docs:strict": "FERN_VERSION=$(node -p \"require('./fern/fern.config.json').version\") && cd fern && npx --yes \"fern-api@${FERN_VERSION}\" check",
"docs:live": "FERN_VERSION=$(node -p \"require('./fern/fern.config.json').version\") && cd fern && npx --yes \"fern-api@${FERN_VERSION}\" docs dev",
"docs:preview:watch": "node scripts/watch-fern-preview.mjs",
"docs:preview:watch": "tsx scripts/watch-fern-preview.ts",
"docs:clean": "rm -rf .fern-cache fern/.fern-cache docs/_build",
"prepare": "if command -v tsc >/dev/null 2>&1 || [ -x node_modules/.bin/tsc ]; then npm run build:cli; fi && (npm install --omit=dev --ignore-scripts 2>/dev/null || true) && if [ -d .git ]; then bash scripts/npm-link-or-shim.sh; if command -v prek >/dev/null 2>&1; then prek install; else echo \"Skipping git hook setup (prek not installed)\"; fi; fi",
"prepublishOnly": "git describe --tags --match 'v*' | sed 's/^v//' > .version && test -s .version && cd nemoclaw && env -u npm_config_global -u npm_config_prefix -u npm_config_omit npm install --ignore-scripts && ./node_modules/.bin/tsc"
Expand Down
100 changes: 76 additions & 24 deletions scripts/benchmark-sandbox-image-build.js → scripts/benchmark-sandbox-image-build.ts
100755 → 100644
Original file line number Diff line number Diff line change
@@ -1,19 +1,53 @@
#!/usr/bin/env node
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

const fs = require("fs");
const os = require("os");
const path = require("path");
const { execFileSync, spawnSync } = require("child_process");
const {
import { execFileSync, spawnSync } from "node:child_process";
import type { StdioOptions } from "node:child_process";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";

import {
collectBuildContextStats,
stageLegacySandboxBuildContext,
stageOptimizedSandboxBuildContext,
} = require("../dist/lib/sandbox/build-context");
type StagedBuildContext,
} from "../dist/lib/sandbox/build-context";

type Args = {
currentRepo: string;
mainRef: string;
noCache: boolean;
keepWorktree: boolean;
};

type RunOptions = {
cwd?: string;
stdio?: StdioOptions;
};

type StageBuildContext = (repoRoot: string, tmpRoot: string) => StagedBuildContext;

type ImageBuildResult = {
label: string;
buildCtx: string;
fileCount: number;
totalBytes: number;
elapsedSeconds: number;
imageBytes: number;
imageTag: string;
};

function requireValue(argv: string[], index: number, flag: string): string {
const value = argv[index];
if (!value || value.startsWith("-")) {
throw new Error(`Missing value for ${flag}`);
}
return value;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

function parseArgs(argv) {
const args = {
function parseArgs(argv: string[]): Args {
const args: Args = {
currentRepo: process.cwd(),
mainRef: "origin/main",
noCache: true,
Expand All @@ -22,20 +56,28 @@ function parseArgs(argv) {

for (let i = 0; i < argv.length; i += 1) {
const arg = argv[i];
if (arg === "--current-repo") args.currentRepo = argv[++i];
else if (arg === "--main-ref") args.mainRef = argv[++i];
else if (arg === "--cache") args.noCache = false;
else if (arg === "--keep-worktree") args.keepWorktree = true;
else throw new Error(`Unknown argument: ${arg}`);
if (arg === "--current-repo") {
i += 1;
args.currentRepo = requireValue(argv, i, arg);
} else if (arg === "--main-ref") {
i += 1;
args.mainRef = requireValue(argv, i, arg);
} else if (arg === "--cache") {
args.noCache = false;
} else if (arg === "--keep-worktree") {
args.keepWorktree = true;
} else {
throw new Error(`Unknown argument: ${arg}`);
}
}

return args;
}

function run(command, args, options = {}) {
function run(command: string, args: string[], options: RunOptions = {}): string {
const result = spawnSync(command, args, {
encoding: "utf8",
stdio: options.stdio || "pipe",
stdio: options.stdio ?? "pipe",
cwd: options.cwd,
});
if (result.status !== 0) {
Expand All @@ -44,13 +86,18 @@ function run(command, args, options = {}) {
return result.stdout.trim();
}

function makeTempWorktree(mainRef, currentRepo) {
function makeTempWorktree(mainRef: string, currentRepo: string): string {
const worktreeRoot = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-main-worktree-"));
run("git", ["worktree", "add", "--detach", worktreeRoot, mainRef], { cwd: currentRepo });
try {
run("git", ["worktree", "add", "--detach", worktreeRoot, mainRef], { cwd: currentRepo });
} catch (error) {
fs.rmSync(worktreeRoot, { recursive: true, force: true });
throw error;
}
return worktreeRoot;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

function removeWorktree(worktreeRoot, currentRepo) {
function removeWorktree(worktreeRoot: string, currentRepo: string): void {
try {
run("git", ["worktree", "remove", "--force", worktreeRoot], { cwd: currentRepo });
} catch {
Expand All @@ -59,7 +106,12 @@ function removeWorktree(worktreeRoot, currentRepo) {
fs.rmSync(worktreeRoot, { recursive: true, force: true });
}

function dockerBuild(repoRoot, stageFn, label, noCache) {
function dockerBuild(
repoRoot: string,
stageFn: StageBuildContext,
label: string,
noCache: boolean,
): ImageBuildResult {
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), `nemoclaw-bench-${label}-`));
const { buildCtx } = stageFn(repoRoot, tmpRoot);
const stats = collectBuildContextStats(buildCtx);
Expand Down Expand Up @@ -90,15 +142,15 @@ function dockerBuild(repoRoot, stageFn, label, noCache) {
}
}

function fmtMiB(bytes) {
function fmtMiB(bytes: number): string {
return `${(bytes / (1024 * 1024)).toFixed(1)} MiB`;
}

function fmtSeconds(seconds) {
function fmtSeconds(seconds: number): string {
return `${seconds.toFixed(1)}s`;
}

function printSummary(results) {
function printSummary(results: ImageBuildResult[]): void {
console.log("");
console.log("Sandbox image build benchmark");
console.log("");
Expand All @@ -121,7 +173,7 @@ function printSummary(results) {
}
}

function main() {
function main(): void {
const args = parseArgs(process.argv.slice(2));
const currentRepo = path.resolve(args.currentRepo);
const currentHead = execFileSync("git", ["rev-parse", "--short", "HEAD"], {
Expand Down
Loading
Loading