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
4 changes: 3 additions & 1 deletion .github/workflows/e2e-standard-profile.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,9 @@ jobs:
mapfile -t archives < <(find "$RUNNER_TEMP/openshell-sdk" -maxdepth 1 -type f -name '*.tgz' -print)
test "${#archives[@]}" -eq 1
env -u NODE_AUTH_TOKEN -u GITHUB_TOKEN -u GH_TOKEN \
npm install --no-save --package-lock=false --ignore-scripts "${archives[0]}"
npm cache add "${archives[0]}" --offline --ignore-scripts
env -u NODE_AUTH_TOKEN -u GITHUB_TOKEN -u GH_TOKEN \
npm ci --ignore-scripts --prefer-offline --no-audit --no-fund
env -u NODE_AUTH_TOKEN -u GITHUB_TOKEN -u GH_TOKEN \
node --input-type=module -e 'const { OpenShellClient } = await import("@nvidia/openshell-sdk"); if (typeof OpenShellClient?.connect !== "function") throw new Error("OpenShell SDK connection API is unavailable");'

Expand Down
8 changes: 6 additions & 2 deletions .github/workflows/e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3417,8 +3417,12 @@ jobs:
set -euo pipefail
mapfile -t archives < <(find "$RUNNER_TEMP/openshell-sdk" -maxdepth 1 -type f -name '*.tgz' -print)
test "${#archives[@]}" -eq 1
env -u NODE_AUTH_TOKEN -u GITHUB_TOKEN \
npm install --no-save --package-lock=false --ignore-scripts "${archives[0]}"
env -u NODE_AUTH_TOKEN -u GITHUB_TOKEN -u GH_TOKEN \
npm cache add "${archives[0]}" --offline --ignore-scripts
env -u NODE_AUTH_TOKEN -u GITHUB_TOKEN -u GH_TOKEN \
npm ci --ignore-scripts --prefer-offline --no-audit --no-fund
env -u NODE_AUTH_TOKEN -u GITHUB_TOKEN -u GH_TOKEN \
node --input-type=module -e 'const { OpenShellClient } = await import("@nvidia/openshell-sdk"); if (typeof OpenShellClient?.connect !== "function") throw new Error("OpenShell SDK connection API is unavailable");'

- name: Install OpenShell CLI
run: env -u NODE_AUTH_TOKEN -u GITHUB_TOKEN bash scripts/install-openshell.sh
Expand Down
4 changes: 3 additions & 1 deletion test/e2e/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,9 @@ and transport. It also stops the gateway and removes its temporary state.
Every catalogue profile installs the reviewed OpenShell SDK archive before restoring the candidate CLI.
The shared package job downloads and verifies the pinned SDK with package-read permission.
Catalogue jobs receive the run-scoped archive without package credentials and reject a missing or ambiguous archive.
The install disables package scripts and verifies that the SDK connection API loads before running tests.
Catalogue and external-gateway health jobs add the archive to npm's cache, then reinstall dependencies from the lockfile with package scripts disabled.
This preserves the locked dependency versions and avoids npm resolving a new peer dependency graph during SDK installation.
Both jobs verify that the SDK connection API loads before running tests.
This keeps the private optional dependency available for SDK-backed commands such as configuration export.

The `network-policy` target also owns live configuration-export evidence for #10938 and PR #11065.
Expand Down
298 changes: 242 additions & 56 deletions test/e2e/support/openshell-sdk-install.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { spawnSync } from "node:child_process";
import { execFileSync, spawnSync } from "node:child_process";
import { createHash } from "node:crypto";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
Expand All @@ -17,91 +18,276 @@ const installScript = profile.jobs.run.steps.find(
(step) => step.name === "Install reviewed OpenShell SDK archive without package credentials",
)!.run!;

const externalGateway = YAML.parse(fs.readFileSync(".github/workflows/e2e.yaml", "utf8")) as {
jobs: Record<string, { steps: Array<{ name?: string; run?: string }> }>;
};
const externalGatewayInstallScript = externalGateway.jobs["external-gateway-health"]!.steps.find(
(step) => step.name === "Install reviewed OpenShell SDK archive without package credentials",
)!.run!;

function writePackageArchive(
root: string,
name: string,
dependencies: Record<string, string> = {},
) {
const source = path.join(root, name.replaceAll("/", "-"));
fs.mkdirSync(source);
fs.writeFileSync(
path.join(source, "package.json"),
JSON.stringify({
name,
version: "1.0.0",
type: "module",
exports: "./index.js",
dependencies,
scripts: {
preinstall: "node -e \"require('node:fs').writeFileSync('lifecycle-ran', 'yes')\"",
},
}),
);
fs.writeFileSync(
path.join(source, "index.js"),
name === "@nvidia/openshell-sdk"
? 'import { version } from "fixture-transport"; if (process.env.NODE_AUTH_TOKEN || process.env.GITHUB_TOKEN || process.env.GH_TOKEN) throw new Error("Unexpected credential"); export class OpenShellClient { static connect() { return version; } }'
: 'export const version = "1.0.0";',
);
const packed = JSON.parse(
execFileSync(
"npm",
["pack", source, "--pack-destination", root, "--json", "--offline", "--ignore-scripts"],
{
cwd: root,
encoding: "utf8",
env: {
PATH: process.env.PATH,
HOME: root,
NPM_CONFIG_CACHE: path.join(root, "pack-cache"),
},
timeout: 10_000,
},
),
) as Array<{ filename: string }>;
const archive = path.join(root, packed[0]!.filename);
return {
archive,
lock: {
version: "1.0.0",
hasInstallScript: true,
resolved: `https://registry.example.invalid/${path.basename(archive)}`,
integrity: `sha512-${createHash("sha512").update(fs.readFileSync(archive)).digest("base64")}`,
dependencies,
},
};
}

const npmFixture = `#!/usr/bin/env node
const fs = require("node:fs");
fs.writeFileSync(process.env.INSTALL_LOG, JSON.stringify({
const calls = JSON.parse(fs.readFileSync(process.env.INSTALL_LOG, "utf8"));
calls.push({
args: process.argv.slice(2),
auth: [process.env.NODE_AUTH_TOKEN, process.env.GITHUB_TOKEN, process.env.GH_TOKEN],
}));
});
fs.writeFileSync(process.env.INSTALL_LOG, JSON.stringify(calls));
if (process.argv[2] === process.env.NPM_FAILURE) process.exit(17);
if (process.argv[2] === "cache") process.exit(0);
const directory = "node_modules/@nvidia/openshell-sdk";
fs.mkdirSync(directory, { recursive: true });
fs.writeFileSync(directory + "/package.json", JSON.stringify({ type: "module", exports: "./index.js" }));
fs.writeFileSync(directory + "/index.js", process.env.SDK_SOURCE);
`;

describe("catalogue OpenShell SDK installation", () => {
it.each([
{ name: "catalogue", script: installScript },
{ name: "external gateway", script: externalGatewayInstallScript },
])("installs the SDK and locked dependencies offline for $name", ({ script }) => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-sdk-real-npm-"));
try {
const sdk = writePackageArchive(root, "@nvidia/openshell-sdk", {
"fixture-transport": "^1.0.0",
});
const transport = writePackageArchive(root, "fixture-transport");
const sibling = writePackageArchive(root, "fixture-sibling");
const workspace = path.join(root, "workspace");
fs.mkdirSync(workspace);
const manifest = JSON.stringify({
name: "sdk-install-fixture",
version: "1.0.0",
dependencies: { "fixture-sibling": "^1.0.0" },
optionalDependencies: { "@nvidia/openshell-sdk": "1.0.0" },
scripts: {
preinstall: "node -e \"require('node:fs').writeFileSync('lifecycle-ran', 'yes')\"",
},
});
const lock = JSON.stringify({
name: "sdk-install-fixture",
version: "1.0.0",
lockfileVersion: 3,
requires: true,
packages: {
"": JSON.parse(manifest),
"node_modules/@nvidia/openshell-sdk": { ...sdk.lock, optional: true },
"node_modules/fixture-transport": { ...transport.lock, optional: true },
"node_modules/fixture-sibling": sibling.lock,
},
});
fs.writeFileSync(path.join(workspace, "package.json"), manifest);
fs.writeFileSync(path.join(workspace, "package-lock.json"), lock);
const env = {
PATH: process.env.PATH,
HOME: root,
NPM_CONFIG_CACHE: path.join(root, "cache"),
NPM_CONFIG_OFFLINE: "true",
NPM_CONFIG_AUDIT: "false",
NPM_CONFIG_FUND: "false",
NPM_CONFIG_UPDATE_NOTIFIER: "false",
RUNNER_TEMP: root,
};
const runNpm = (args: string[]) =>
execFileSync("npm", args, {
cwd: workspace,
encoding: "utf8",
env,
timeout: 10_000,
});
runNpm(["cache", "add", transport.archive, sibling.archive, "--offline", "--ignore-scripts"]);
runNpm(["ci", "--ignore-scripts"]);
expect(fs.existsSync(path.join(workspace, "node_modules/@nvidia/openshell-sdk"))).toBe(false);
fs.mkdirSync(path.join(root, "openshell-sdk"));
fs.copyFileSync(sdk.archive, path.join(root, "openshell-sdk", "sdk.tgz"));

const result = spawnSync("bash", ["-c", script], {
cwd: workspace,
encoding: "utf8",
env,
timeout: 20_000,
});

expect(result.error).toBeUndefined();
expect(result.status, `${result.stdout}\n${result.stderr}`).toBe(0);
const observed = execFileSync(
process.execPath,
[
"--input-type=module",
"-e",
'import { OpenShellClient } from "@nvidia/openshell-sdk"; import { version } from "fixture-sibling"; console.log(JSON.stringify([OpenShellClient.connect(), version]));',
],
{ cwd: workspace, encoding: "utf8", env },
);
expect(JSON.parse(observed)).toEqual(["1.0.0", "1.0.0"]);
expect(fs.existsSync(path.join(workspace, "lifecycle-ran"))).toBe(false);
expect(
fs.existsSync(path.join(workspace, "node_modules/@nvidia/openshell-sdk/lifecycle-ran")),
).toBe(false);
expect(
fs.existsSync(path.join(workspace, "node_modules/fixture-transport/lifecycle-ran")),
).toBe(false);
expect(
fs.existsSync(path.join(workspace, "node_modules/fixture-sibling/lifecycle-ran")),
).toBe(false);
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
});

it.each([
{
name: "one reviewed archive",
archives: ["sdk.tgz"],
sdk: 'if (process.env.NODE_AUTH_TOKEN || process.env.GITHUB_TOKEN || process.env.GH_TOKEN) throw new Error("Unexpected credential"); export class OpenShellClient { static connect() {} }',
status: 0,
install: true,
calls: 2,
failure: "",
},
{ name: "no archive", archives: [], sdk: "", status: 1, install: false },
{ name: "no archive", archives: [], sdk: "", status: 1, calls: 0, failure: "" },
{
name: "ambiguous archives",
archives: ["first.tgz", "second.tgz"],
sdk: "",
status: 1,
install: false,
calls: 0,
failure: "",
},
{
name: "an SDK without the connection API",
archives: ["sdk.tgz"],
sdk: "export const OpenShellClient = {};",
status: 1,
install: true,
calls: 2,
failure: "",
},
])("checks $name before running the catalogue target", ({ archives, sdk, status, install }) => {
const directory = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-sdk-install-"));
const archiveDirectory = path.join(directory, "openshell-sdk");
const bin = path.join(directory, "bin");
const log = path.join(directory, "install.json");
try {
fs.mkdirSync(archiveDirectory);
fs.mkdirSync(bin);
fs.writeFileSync(log, "null");
archives.forEach((archive) =>
fs.writeFileSync(path.join(archiveDirectory, archive), "fixture"),
);
fs.writeFileSync(path.join(bin, "npm"), npmFixture, { mode: 0o755 });
fs.symlinkSync(process.execPath, path.join(bin, "node"));
{
name: "a cache staging failure",
archives: ["sdk.tgz"],
sdk: "",
status: 17,
calls: 1,
failure: "cache",
},
{
name: "a dependency install failure",
archives: ["sdk.tgz"],
sdk: "",
status: 17,
calls: 2,
failure: "ci",
},
])(
"checks $name before running the catalogue target",
({ archives, sdk, status, calls, failure }) => {
const directory = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-sdk-install-"));
const archiveDirectory = path.join(directory, "openshell-sdk");
const bin = path.join(directory, "bin");
const log = path.join(directory, "install.json");
try {
fs.mkdirSync(archiveDirectory);
fs.mkdirSync(bin);
fs.writeFileSync(log, "[]");
archives.forEach((archive) =>
fs.writeFileSync(path.join(archiveDirectory, archive), "fixture"),
);
fs.writeFileSync(path.join(bin, "npm"), npmFixture, { mode: 0o755 });
fs.symlinkSync(process.execPath, path.join(bin, "node"));

const result = spawnSync("bash", ["-c", installScript], {
cwd: directory,
encoding: "utf8",
timeout: 10_000,
env: {
PATH: `${bin}${path.delimiter}${process.env.PATH ?? ""}`,
RUNNER_TEMP: directory,
INSTALL_LOG: log,
SDK_SOURCE: sdk,
NODE_AUTH_TOKEN: "package-credential-canary",
GITHUB_TOKEN: "github-credential-canary",
GH_TOKEN: "gh-credential-canary",
},
});
const result = spawnSync("bash", ["-c", installScript], {
cwd: directory,
encoding: "utf8",
timeout: 10_000,
env: {
PATH: `${bin}${path.delimiter}${process.env.PATH ?? ""}`,
RUNNER_TEMP: directory,
INSTALL_LOG: log,
SDK_SOURCE: sdk,
NPM_FAILURE: failure,
NODE_AUTH_TOKEN: "package-credential-canary",
GITHUB_TOKEN: "github-credential-canary",
GH_TOKEN: "gh-credential-canary",
},
});

expect(result.error).toBeUndefined();
expect(result.signal).toBeNull();
expect(result.status, result.stderr).toBe(status);
expect(JSON.parse(fs.readFileSync(log, "utf8"))).toEqual(
install
? {
args: [
"install",
"--no-save",
"--package-lock=false",
"--ignore-scripts",
path.join(archiveDirectory, archives[0]!),
],
auth: [null, null, null],
}
: null,
);
} finally {
fs.rmSync(directory, { recursive: true, force: true });
}
});
expect(result.error).toBeUndefined();
expect(result.signal).toBeNull();
expect(result.status, result.stderr).toBe(status);
const expectedCalls = [
{
args: [
"cache",
"add",
path.join(archiveDirectory, archives[0] ?? ""),
"--offline",
"--ignore-scripts",
],
auth: [null, null, null],
},
{
args: ["ci", "--ignore-scripts", "--prefer-offline", "--no-audit", "--no-fund"],
auth: [null, null, null],
},
].slice(0, calls);
expect(JSON.parse(fs.readFileSync(log, "utf8"))).toEqual(expectedCalls);
} finally {
fs.rmSync(directory, { recursive: true, force: true });
}
},
);
});
10 changes: 8 additions & 2 deletions test/helpers/vitest-watch-triggers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -321,11 +321,17 @@ export const vitestWatchTriggerPatterns: VitestWatchTriggerPattern[] = [
},
{
pattern: /(?:^|\/)\.github\/workflows\/e2e\.yaml$/,
testsToRun: runTests(...E2E_WORKFLOW_CONTRACTS),
testsToRun: runTests(
...E2E_WORKFLOW_CONTRACTS,
"test/e2e/support/openshell-sdk-install.test.ts",
),
},
{
pattern: /(?:^|\/)\.github\/workflows\/e2e-standard-profile\.yaml$/,
testsToRun: runTests("test/e2e/support/standard-profile-workflow-boundary.test.ts"),
testsToRun: runTests(
"test/e2e/support/standard-profile-workflow-boundary.test.ts",
"test/e2e/support/openshell-sdk-install.test.ts",
),
},
{
pattern: /(?:^|\/)\.github\/workflows\/portable-profile-e2e\.yaml$/,
Expand Down
Loading
Loading