Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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: 2 additions & 2 deletions .github/actions/ci-cli-coverage-merge/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ runs:
shell: bash
run: |
test -s dist/nemoclaw.js
npx tsx scripts/check-dist-sourcemaps.ts dist
npx tsx scripts/check-dist-sourcemaps.mts dist

- name: Download CLI shard blob reports
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
Expand Down Expand Up @@ -95,7 +95,7 @@ runs:
--coverage.include="src/**/*.ts" \
--coverage.exclude="test/**/*.js" \
--coverage.exclude="test/**/*.ts"
npx tsx scripts/check-coverage-ratchet.ts coverage/cli/coverage-summary.json ci/coverage-threshold-cli.json "CLI coverage"
npx tsx scripts/check-coverage-ratchet.mts coverage/cli/coverage-summary.json ci/coverage-threshold-cli.json "CLI coverage"

- name: Upload CLI coverage report
if: ${{ always() && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }}
Expand Down
2 changes: 1 addition & 1 deletion .github/actions/ci-cli-coverage-shard/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ runs:
run: |
node -e "require('node:fs').rmSync('dist', { recursive: true, force: true })"
npm run build:cli
npx tsx scripts/check-dist-sourcemaps.ts dist
npx tsx scripts/check-dist-sourcemaps.mts dist

- name: Upload compiled CLI artifact
if: ${{ steps.validate-shard-inputs.outputs.upload_build_artifact == 'true' && success() }}
Expand Down
2 changes: 1 addition & 1 deletion .github/actions/ci-plugin-coverage/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ runs:
--coverage.include="nemoclaw/src/**/*.ts" \
--coverage.include="nemoclaw/src/**/*.cts" \
--coverage.exclude="**/*.test.ts"
npx tsx scripts/check-coverage-ratchet.ts coverage/plugin/coverage-summary.json ci/coverage-threshold-plugin.json "Plugin coverage"
npx tsx scripts/check-coverage-ratchet.mts coverage/plugin/coverage-summary.json ci/coverage-threshold-plugin.json "Plugin coverage"

- name: Upload plugin coverage report
if: ${{ always() && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }}
Expand Down
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -306,15 +306,15 @@ repos:
entry: npm run source-shape:check
language: system
pass_filenames: false
files: ^(.+\.(test|spec)\.(js|ts|mjs|mts|cjs|cts)|scripts/find-source-shape-tests\.ts|ci/source-shape-test-budget\.json)$
files: ^(.+\.(test|spec)\.(js|ts|mjs|mts|cjs|cts)|scripts/find-source-shape-tests\.mts|ci/source-shape-test-budget\.json)$
priority: 20

- id: test-file-size-budget
name: Test file size budget
entry: npm run test-size:check
language: system
pass_filenames: false
files: ^(test/|src/.*\.(test|spec)\.(ts|js|mts|mjs|cts|cjs)$|nemoclaw/src/.*\.(test|spec)\.(ts|js|mts|mjs|cts|cjs)$|scripts/check-test-file-size-budget\.ts$|ci/test-file-size-budget\.json$)
files: ^(test/|src/.*\.(test|spec)\.(ts|js|mts|mjs|cts|cjs)$|nemoclaw/src/.*\.(test|spec)\.(ts|js|mts|mjs|cts|cjs)$|scripts/check-test-file-size-budget\.mts$|ci/test-file-size-budget\.json$)
priority: 20

- id: test-skills-yaml
Expand Down
14 changes: 7 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
"test:diagnose:leaks": "vitest run --project cli --project plugin --project e2e-support --detectAsyncLeaks --coverage=false --reporter=default --reporter=hanging-process",
"test:integration": "npm run clean:cli && npm run build:cli && vitest run --project integration --project installer-integration",
"test:package": "npm run clean:cli && npm --prefix nemoclaw run clean && npm run build:cli && npm --prefix nemoclaw run build && vitest run --project package-contract",
"test:coverage:cli": "npm run clean:cli && npm run build:cli && tsx scripts/check-dist-sourcemaps.ts dist && vitest run --project cli --project integration --coverage --coverage.reporter=text-summary --coverage.reporter=json-summary --coverage.reportsDirectory=coverage/cli --coverage.include=\"bin/**/*.js\" --coverage.include=\"src/**/*.ts\" --coverage.exclude=\"test/**/*.js\" --coverage.exclude=\"test/**/*.ts\" && tsx scripts/check-coverage-ratchet.ts coverage/cli/coverage-summary.json ci/coverage-threshold-cli.json \"CLI coverage\"",
"test:coverage:plugin": "vitest run --project plugin --coverage --coverage.reporter=text-summary --coverage.reporter=json-summary --coverage.reportsDirectory=coverage/plugin --coverage.include=\"nemoclaw/src/**/*.ts\" --coverage.include=\"nemoclaw/src/**/*.cts\" --coverage.exclude=\"**/*.test.ts\" && tsx scripts/check-coverage-ratchet.ts coverage/plugin/coverage-summary.json ci/coverage-threshold-plugin.json \"Plugin coverage\"",
"test:coverage:cli": "npm run clean:cli && npm run build:cli && tsx scripts/check-dist-sourcemaps.mts dist && vitest run --project cli --project integration --coverage --coverage.reporter=text-summary --coverage.reporter=json-summary --coverage.reportsDirectory=coverage/cli --coverage.include=\"bin/**/*.js\" --coverage.include=\"src/**/*.ts\" --coverage.exclude=\"test/**/*.js\" --coverage.exclude=\"test/**/*.ts\" && tsx scripts/check-coverage-ratchet.mts coverage/cli/coverage-summary.json ci/coverage-threshold-cli.json \"CLI coverage\"",
"test:coverage:plugin": "vitest run --project plugin --coverage --coverage.reporter=text-summary --coverage.reporter=json-summary --coverage.reportsDirectory=coverage/plugin --coverage.include=\"nemoclaw/src/**/*.ts\" --coverage.include=\"nemoclaw/src/**/*.cts\" --coverage.exclude=\"**/*.test.ts\" && tsx scripts/check-coverage-ratchet.mts coverage/plugin/coverage-summary.json ci/coverage-threshold-plugin.json \"Plugin coverage\"",
"test:live-e2e": "npm run clean:cli && npm run build:cli && NEMOCLAW_RUN_LIVE_E2E=1 vitest run --project e2e-live",
"test:imports:check": "tsx scripts/checks/no-test-dist-imports.ts",
"test:projects:check": "tsx scripts/checks/vitest-project-overlap.ts",
Expand All @@ -54,11 +54,11 @@
"clean:cli": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
"typecheck:cli": "tsc -p tsconfig.cli.json",
"validate:configs": "tsx scripts/validate-configs.ts",
"type-safety:hotspots": "tsx scripts/type-safety-hotspots.ts",
"source-shape:scan": "tsx scripts/find-source-shape-tests.ts --metrics",
"source-shape:check": "tsx scripts/find-source-shape-tests.ts --check",
"test-size:check": "tsx scripts/check-test-file-size-budget.ts",
"test-conditionals:scan": "tsx scripts/find-test-conditionals.ts",
"type-safety:hotspots": "tsx scripts/type-safety-hotspots.mts",
"source-shape:scan": "tsx scripts/find-source-shape-tests.mts --metrics",
"source-shape:check": "tsx scripts/find-source-shape-tests.mts --check",
"test-size:check": "tsx scripts/check-test-file-size-budget.mts",
"test-conditionals:scan": "tsx scripts/find-test-conditionals.mts",
"bump:version": "tsx scripts/bump-version.ts",
"release:plan": "tsx scripts/release-plan.ts",
"release:cut": "bash scripts/release-cut-tag.sh",
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";

type SourceMap = {
sources?: unknown;
Expand Down Expand Up @@ -76,6 +77,6 @@ function main(): void {
process.exit(1);
}

if (require.main === module) {
if (fileURLToPath(import.meta.url) === path.resolve(process.argv[1] ?? "")) {
main();
}
Original file line number Diff line number Diff line change
Expand Up @@ -2109,7 +2109,7 @@ function isDirectInvocation(): boolean {
const invoked = process.argv[1];
return Boolean(
invoked &&
(import.meta.url === `file://${invoked}` || invoked.endsWith("find-source-shape-tests.ts")),
(import.meta.url === `file://${invoked}` || invoked.endsWith("find-source-shape-tests.mts")),
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -557,7 +557,7 @@ function parseArgs(argv: readonly string[]): CliOptions {
roots.push(argv[++index] ?? "");
} else if (arg === "--help" || arg === "-h") {
console.log(
`Usage: tsx scripts/find-test-conditionals.ts [--top N] [--min-score N] [--root PATH] [--json]\n\nScans test/spec files under test, src, and nemoclaw/src by default.`,
`Usage: tsx scripts/find-test-conditionals.mts [--top N] [--min-score N] [--root PATH] [--json]\n\nScans test/spec files under test, src, and nemoclaw/src by default.`,
);
process.exit(0);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,13 +168,13 @@ function parseArgs(argv: string[]): Options {

function printHelp(): void {
console.log(
"Usage: tsx scripts/list-command-helper-uses.ts [--root <dir>] [--names run,runInteractive,...] [--include-tests] [--list-calls] [--markdown] [--json] [path ...]\n\n" +
"Usage: tsx scripts/list-command-helper-uses.mts [--root <dir>] [--names run,runInteractive,...] [--include-tests] [--list-calls] [--markdown] [--json] [path ...]\n\n" +
"Lists AST-level callsites and assignments for command helper names such as run(), runInteractive(), runCapture(), runShell(), execFileSync(), spawnSync(), and runCommand(). By default it excludes test files and groups results by inferred command head.\n\n" +
"Examples:\n" +
" tsx scripts/list-command-helper-uses.ts\n" +
" tsx scripts/list-command-helper-uses.ts --markdown src\n" +
" tsx scripts/list-command-helper-uses.ts --include-tests --list-calls --json src test\n" +
" tsx scripts/list-command-helper-uses.ts --names run,runInteractive src test\n",
" tsx scripts/list-command-helper-uses.mts\n" +
" tsx scripts/list-command-helper-uses.mts --markdown src\n" +
" tsx scripts/list-command-helper-uses.mts --include-tests --list-calls --json src test\n" +
" tsx scripts/list-command-helper-uses.mts --names run,runInteractive src test\n",
);
}

Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion test/codebase-growth-guardrails-conditionals.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import path from "node:path";

import { describe, expect, it } from "vitest";

import { scanTextForTestConditionals } from "../scripts/find-test-conditionals";
import { scanTextForTestConditionals } from "../scripts/find-test-conditionals.mts";

const WORKFLOW_PATH = ".github/workflows/codebase-growth-guardrails.yaml";
const STEP_NAME = "Require changed test files not to add if statements";
Expand Down
4 changes: 2 additions & 2 deletions test/coverage-ratchet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { tmpdir } from "node:os";
import { join, relative } from "node:path";
import { describe, expect, it } from "vitest";

import { findCoverageFailures } from "../scripts/check-coverage-ratchet";
import { findCoverageFailures } from "../scripts/check-coverage-ratchet.mts";

const thresholds = {
lines: 71.2,
Expand Down Expand Up @@ -51,7 +51,7 @@ describe("coverage ratchet", () => {
[
"--import",
"tsx",
"scripts/check-coverage-ratchet.ts",
"scripts/check-coverage-ratchet.mts",
relative(process.cwd(), summaryPath),
relative(process.cwd(), thresholdPath),
"Test coverage",
Expand Down
98 changes: 83 additions & 15 deletions test/dist-sourcemaps.test.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,103 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { spawnSync } from "node:child_process";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";

import { describe, expect, it } from "vitest";

import { findMissingDistSourcemapSources } from "../scripts/check-dist-sourcemaps";
import { findMissingDistSourcemapSources } from "../scripts/check-dist-sourcemaps.mts";

function writeCleanFixtureDist(root: string): string {
const distLib = path.join(root, "dist", "lib");
const srcLib = path.join(root, "src", "lib");
fs.mkdirSync(distLib, { recursive: true });
fs.mkdirSync(srcLib, { recursive: true });
fs.writeFileSync(path.join(srcLib, "present.ts"), "export {};\n");
fs.writeFileSync(
path.join(distLib, "present.js.map"),
JSON.stringify({ version: 3, sources: ["../../src/lib/present.ts"], mappings: "" }),
);
return path.join(root, "dist");
}

function writeStaleFixtureDist(root: string): string {
const distDir = writeCleanFixtureDist(root);
fs.writeFileSync(
path.join(distDir, "lib", "missing.js.map"),
JSON.stringify({ version: 3, sources: ["../../src/lib/missing.ts"], mappings: "" }),
);
return distDir;
}

describe("dist sourcemap checks", () => {
it("reports JavaScript sourcemaps pointing at missing source files", () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-sourcemap-check-"));
const distLib = path.join(root, "dist", "lib");
const srcLib = path.join(root, "src", "lib");
fs.mkdirSync(distLib, { recursive: true });
fs.mkdirSync(srcLib, { recursive: true });
fs.writeFileSync(path.join(srcLib, "present.ts"), "export {};\n");
fs.writeFileSync(
path.join(distLib, "present.js.map"),
JSON.stringify({ version: 3, sources: ["../../src/lib/present.ts"], mappings: "" }),
const distDir = writeStaleFixtureDist(root);

expect(findMissingDistSourcemapSources(distDir)).toEqual([
`${path.join(distDir, "lib", "missing.js.map")} -> ../../src/lib/missing.ts`,
]);

fs.rmSync(root, { recursive: true, force: true });
});

it("invoking the .mts entrypoint directly exits 0 for a clean dist directory", () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-sourcemap-cli-clean-"));
const distDir = writeCleanFixtureDist(root);

const result = spawnSync(
process.execPath,
["--import", "tsx", "scripts/check-dist-sourcemaps.mts", distDir],
{ cwd: process.cwd(), encoding: "utf8" },
);
fs.writeFileSync(
path.join(distLib, "missing.js.map"),
JSON.stringify({ version: 3, sources: ["../../src/lib/missing.ts"], mappings: "" }),

expect(result.status).toBe(0);
expect(result.stdout).toContain(
`All JavaScript sourcemaps in ${distDir} reference existing sources.`,
);

expect(findMissingDistSourcemapSources(path.join(root, "dist"))).toEqual([
`${path.join(distLib, "missing.js.map")} -> ../../src/lib/missing.ts`,
]);
fs.rmSync(root, { recursive: true, force: true });
});

it("invoking the .mts entrypoint directly exits 1 and reports stale sources", () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-sourcemap-cli-stale-"));
const distDir = writeStaleFixtureDist(root);

const result = spawnSync(
process.execPath,
["--import", "tsx", "scripts/check-dist-sourcemaps.mts", distDir],
{ cwd: process.cwd(), encoding: "utf8" },
);

expect(result.status).toBe(1);
expect(result.stderr).toContain(`Stale JavaScript sourcemap sources found in ${distDir}:`);
expect(result.stderr).toContain(
`${path.join(distDir, "lib", "missing.js.map")} -> ../../src/lib/missing.ts`,
);

fs.rmSync(root, { recursive: true, force: true });
});

it("importing the .mts entrypoint does not run its CLI main", () => {
const scriptUrl = pathToFileURL(path.resolve("scripts/check-dist-sourcemaps.mts")).href;
const result = spawnSync(
process.execPath,
[
"--import",
"tsx",
"-e",
`import(${JSON.stringify(scriptUrl)}).then(() => { console.log("IMPORT_ONLY_OK"); });`,
],
{ cwd: process.cwd(), encoding: "utf8" },
);

expect(result.status).toBe(0);
expect(result.stdout.trim()).toBe("IMPORT_ONLY_OK");
expect(result.stdout).not.toContain("reference existing sources");
expect(result.stdout).not.toContain("Stale JavaScript sourcemap sources found");
});
});
2 changes: 1 addition & 1 deletion test/docker-abstraction-guard.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { describe, expect, it } from "vitest";

const REPO_ROOT = path.join(import.meta.dirname, "..");
const TSX = path.join(REPO_ROOT, "node_modules", ".bin", "tsx");
const INVENTORY_SCRIPT = path.join(REPO_ROOT, "scripts", "list-command-helper-uses.ts");
const INVENTORY_SCRIPT = path.join(REPO_ROOT, "scripts", "list-command-helper-uses.mts");
const DOCKER_ABSTRACTION_PREFIX = "src/lib/adapters/docker/";

type CommandUse = {
Expand Down
2 changes: 1 addition & 1 deletion test/list-command-helper-uses.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { describe, expect, it } from "vitest";

const REPO_ROOT = path.join(import.meta.dirname, "..");
const TSX = path.join(REPO_ROOT, "node_modules", ".bin", "tsx");
const SCRIPT = path.join(REPO_ROOT, "scripts", "list-command-helper-uses.ts");
const SCRIPT = path.join(REPO_ROOT, "scripts", "list-command-helper-uses.mts");

type HelperMatch = {
filePath: string;
Expand Down
6 changes: 3 additions & 3 deletions test/pr-workflow-contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -567,7 +567,7 @@ describe("pull request and main workflow contracts", () => {
"test/example.test.ts",
"src/lib/example.spec.ts",
"nemoclaw/src/example.test.ts",
"scripts/find-source-shape-tests.ts",
"scripts/find-source-shape-tests.mts",
"ci/source-shape-test-budget.json",
]) {
expect(files.test(path), path).toBe(true);
Expand Down Expand Up @@ -655,7 +655,7 @@ describe("pull request and main workflow contracts", () => {
);
expect(cliCoverageCalls[4]).toEqual([
"tsx",
"scripts/check-coverage-ratchet.ts",
"scripts/check-coverage-ratchet.mts",
"coverage/cli/coverage-summary.json",
"ci/coverage-threshold-cli.json",
"CLI coverage",
Expand All @@ -670,7 +670,7 @@ describe("pull request and main workflow contracts", () => {
);
expect(pluginCoverageCalls[1]).toEqual([
"tsx",
"scripts/check-coverage-ratchet.ts",
"scripts/check-coverage-ratchet.mts",
"coverage/plugin/coverage-summary.json",
"ci/coverage-threshold-plugin.json",
"Plugin coverage",
Expand Down
51 changes: 50 additions & 1 deletion test/source-shape-scanner.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { spawnSync } from "node:child_process";
import path from "node:path";
import { pathToFileURL } from "node:url";

import { describe, expect, it } from "vitest";

import {
contractExceptionAllowlistErrors,
scanTextForTest,
scanTextForTestReport,
sourceShapeSummary,
} from "../scripts/find-source-shape-tests";
} from "../scripts/find-source-shape-tests.mts";

function detectedCaseNames(source: string): string[] {
return scanTextForTest("test/virtual-source-shape.test.ts", source).map((entry) => entry.name);
Expand Down Expand Up @@ -666,3 +670,48 @@ describe("source-shape scanner", () => {
).toEqual([expect.stringContaining("duplicate source-shape exception identity")]);
});
});

describe("source-shape scanner CLI entrypoint", () => {
function runCli(...args: string[]): { status: number | null; stdout: string; stderr: string } {
const result = spawnSync(
process.execPath,
["--import", "tsx", "scripts/find-source-shape-tests.mts", ...args],
{ cwd: process.cwd(), encoding: "utf8" },
);
return { status: result.status, stdout: result.stdout, stderr: result.stderr };
}

it("invoking the .mts entrypoint with --check prints the human report, metrics, and preserves the budget exit status", () => {
const { status, stdout } = runCli("--check");
expect(status).toBe(0);
expect(stdout).toMatch(
/No source-shape tests detected\.|Detected \d+ source-shape test cases:/,
);
expect(stdout).toContain("METRIC source_shape_cases=");
}, 90_000);

it("invoking the .mts entrypoint with --json prints a parsable report and exits 0", () => {
const { status, stdout } = runCli("--json");
expect(status).toBe(0);
const report = JSON.parse(stdout) as { summary: { source_shape_cases: number } };
expect(typeof report.summary.source_shape_cases).toBe("number");
}, 90_000);

it("importing the .mts entrypoint does not run its CLI main", () => {
const scriptUrl = pathToFileURL(path.resolve("scripts/find-source-shape-tests.mts")).href;
const result = spawnSync(
process.execPath,
[
"--import",
"tsx",
"-e",
`import(${JSON.stringify(scriptUrl)}).then(() => { console.log("IMPORT_ONLY_OK"); });`,
],
{ cwd: process.cwd(), encoding: "utf8" },
);

expect(result.status).toBe(0);
expect(result.stdout.trim()).toBe("IMPORT_ONLY_OK");
expect(result.stdout).not.toContain("METRIC source_shape_cases=");
});
});
Loading
Loading