Skip to content
Closed
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
12 changes: 10 additions & 2 deletions .github/actions/basic-checks/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,19 @@ runs:
[ "$EXPECTED" = "$ACTUAL" ] || { echo "::error::hadolint checksum mismatch"; exit 1; }
chmod +x /usr/local/bin/hadolint

- name: Validate npm lockfiles
shell: bash
run: |
npm ci --ignore-scripts --dry-run
cd nemoclaw
npm ci --ignore-scripts --dry-run

- name: Install dependencies
shell: bash
run: |
npm install --ignore-scripts
cd nemoclaw && npm install
npm ci --ignore-scripts
cd nemoclaw
npm ci --ignore-scripts

- name: Build TypeScript plugin
shell: bash
Expand Down
37 changes: 23 additions & 14 deletions scripts/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1274,8 +1274,8 @@ fix_npm_permissions() {
# Work around openclaw tarball missing directory entries (GH-503).
# npm's tar extractor hard-fails because the tarball is missing directory
# entries for extensions/, skills/, and dist/plugin-sdk/config/. System tar
# handles this fine. We pre-extract openclaw into node_modules BEFORE npm
# install so npm sees the dependency is already satisfied and skips it.
# handles this fine. npm ci removes node_modules before installing, so restore
# the package after ci and before build steps that need the unpacked contents.
pre_extract_openclaw() {
local install_dir="$1"
local openclaw_version
Expand Down Expand Up @@ -1314,6 +1314,17 @@ pre_extract_openclaw() {
rm -rf "$tmpdir"
}

restore_pre_extracted_openclaw() {
local install_dir="$1"

if [[ -n "${NEMOCLAW_AGENT:-}" && "${NEMOCLAW_AGENT}" != "openclaw" ]]; then
return 0
fi

spin "Restoring OpenClaw package" bash -c "$(declare -f info warn resolve_openclaw_version pre_extract_openclaw); pre_extract_openclaw \"\$1\"" _ "$install_dir" \
|| warn "OpenClaw package restore failed - subsequent build steps may fail"
}

resolve_openclaw_version() {
local install_dir="$1"
local package_json dockerfile_base resolved_version
Expand Down Expand Up @@ -1377,13 +1388,12 @@ install_nemoclaw() {
if is_source_checkout "$repo_root"; then
info "${_CLI_DISPLAY} package.json found in the selected source checkout — installing from source…"
NEMOCLAW_SOURCE_ROOT="$repo_root"
if [[ -z "${NEMOCLAW_AGENT:-}" || "${NEMOCLAW_AGENT}" == "openclaw" ]]; then
spin "Preparing OpenClaw package" bash -c "$(declare -f info warn resolve_openclaw_version pre_extract_openclaw); pre_extract_openclaw \"\$1\"" _ "$NEMOCLAW_SOURCE_ROOT" \
|| warn "Pre-extraction failed — npm install may fail if openclaw tarball is broken"
fi
spin "Installing ${_CLI_DISPLAY} dependencies" bash -c "cd \"$NEMOCLAW_SOURCE_ROOT\" && npm install --ignore-scripts"
spin "Installing ${_CLI_DISPLAY} dependencies" bash -c "cd \"$NEMOCLAW_SOURCE_ROOT\" && npm ci --ignore-scripts"
restore_pre_extracted_openclaw "$NEMOCLAW_SOURCE_ROOT"
spin "Building ${_CLI_DISPLAY} CLI modules" bash -c "cd \"$NEMOCLAW_SOURCE_ROOT\" && npm run --if-present build:cli"
spin "Building ${_CLI_DISPLAY} plugin" bash -c "cd \"$NEMOCLAW_SOURCE_ROOT\"/nemoclaw && npm install --ignore-scripts && npm run build"
spin "Installing ${_CLI_DISPLAY} plugin dependencies" bash -c "cd \"$NEMOCLAW_SOURCE_ROOT\"/nemoclaw && npm ci --ignore-scripts"
restore_pre_extracted_openclaw "$NEMOCLAW_SOURCE_ROOT"
spin "Building ${_CLI_DISPLAY} plugin" bash -c "cd \"$NEMOCLAW_SOURCE_ROOT\"/nemoclaw && npm run build"
spin "Linking ${_CLI_DISPLAY} CLI" bash -c "cd \"$NEMOCLAW_SOURCE_ROOT\" && npm link"

# Bootstrap OpenShell when the source checkout is being used as a fresh
Expand Down Expand Up @@ -1420,13 +1430,12 @@ install_nemoclaw() {
# unavailable or tags are pruned later.
git -C "$nemoclaw_src" describe --tags --match 'v*' 2>/dev/null \
| sed 's/^v//' >"$nemoclaw_src/.version" || true
if [[ -z "${NEMOCLAW_AGENT:-}" || "${NEMOCLAW_AGENT}" == "openclaw" ]]; then
spin "Preparing OpenClaw package" bash -c "$(declare -f info warn resolve_openclaw_version pre_extract_openclaw); pre_extract_openclaw \"\$1\"" _ "$nemoclaw_src" \
|| warn "Pre-extraction failed — npm install may fail if openclaw tarball is broken"
fi
spin "Installing ${_CLI_DISPLAY} dependencies" bash -c "cd \"$nemoclaw_src\" && npm install --ignore-scripts"
spin "Installing ${_CLI_DISPLAY} dependencies" bash -c "cd \"$nemoclaw_src\" && npm ci --ignore-scripts"
restore_pre_extracted_openclaw "$nemoclaw_src"
spin "Building ${_CLI_DISPLAY} CLI modules" bash -c "cd \"$nemoclaw_src\" && npm run --if-present build:cli"
spin "Building ${_CLI_DISPLAY} plugin" bash -c "cd \"$nemoclaw_src\"/nemoclaw && npm install --ignore-scripts && npm run build"
spin "Installing ${_CLI_DISPLAY} plugin dependencies" bash -c "cd \"$nemoclaw_src\"/nemoclaw && npm ci --ignore-scripts"
restore_pre_extracted_openclaw "$nemoclaw_src"
spin "Building ${_CLI_DISPLAY} plugin" bash -c "cd \"$nemoclaw_src\"/nemoclaw && npm run build"
spin "Linking ${_CLI_DISPLAY} CLI" bash -c "cd \"$nemoclaw_src\" && npm link"

# Install/upgrade the OpenShell CLI on the GitHub-clone path (curl|bash).
Expand Down
33 changes: 28 additions & 5 deletions test/install-preflight.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@
set -euo pipefail
if [ "$1" = "--version" ]; then echo "10.9.2"; exit 0; fi
if [ "$1" = "config" ] && [ "$2" = "get" ] && [ "$3" = "prefix" ]; then echo "$NPM_PREFIX"; exit 0; fi
if [ "$1" = "install" ] || [ "$1" = "link" ] || [ "$1" = "uninstall" ] || [ "$1" = "pack" ] || [ "$1" = "run" ]; then
if [ "$1" = "ci" ] || [ "$1" = "install" ] || [ "$1" = "link" ] || [ "$1" = "uninstall" ] || [ "$1" = "pack" ] || [ "$1" = "run" ]; then
${installSnippet}
fi
Comment on lines +98 to 100

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

writeNpmStub() still leaves many test-specific handlers rejecting npm ci.

Line 98 now dispatches ci, but a lot of callers in this file still only branch on "$1" = "install" inside installSnippet. Those tests will now fall through to unexpected npm invocation: ci --ignore-scripts, so the suite is still out of sync with the installer change.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/install-preflight.test.ts` around lines 98 - 100, The test harness
function writeNpmStub and the installSnippet handlers are not accounting for npm
"ci" invocations, causing many tests to hit the "unexpected npm invocation: ci
--ignore-scripts" path; update writeNpmStub (and any installSnippet blocks used
in test/install-preflight.test.ts) to accept and handle the "ci" command the
same way as "install" (e.g., add "$1" = "ci" branches or normalize "$1" to
"install" inside the stub) so existing test-specific handlers match the new
installer behavior and no longer reject npm ci.

echo "unexpected npm invocation: $*" >&2; exit 98`,
Expand Down Expand Up @@ -340,7 +340,7 @@
},
});

expect(result.status).toBe(0);

Check failure on line 343 in test/install-preflight.test.ts

View workflow job for this annotation

GitHub Actions / unit-vitest-linux

[installer-integration] test/install-preflight.test.ts > installer runtime preflight > treats the installer script's checkout as the source root even when cwd is elsewhere

AssertionError: expected 98 to be +0 // Object.is equality - Expected + Received - 0 + 98 ❯ test/install-preflight.test.ts:343:27
const gitCalls = fs.readFileSync(gitLog, "utf-8");
expect(gitCalls).not.toMatch(/clone/);
expect(gitCalls).not.toMatch(/fetch/);
Expand Down Expand Up @@ -433,7 +433,7 @@

const output = `${result.stdout}${result.stderr}`;
expect(result.status).not.toBe(0);
expect(output).toMatch(/curl -fsSL https:\/\/www\.nvidia\.com\/nemoclaw\.sh \| bash/);

Check failure on line 436 in test/install-preflight.test.ts

View workflow job for this annotation

GitHub Actions / unit-vitest-linux

[installer-integration] test/install-preflight.test.ts > installer runtime preflight > prints the HTTPS GitHub remediation when the binary is missing

AssertionError: expected '\n ███╗ ██╗███████╗███╗ ███╗ ██…' to match /curl -fsSL https:\/\/www\.nvidia\.co…/\ - Expected: /curl -fsSL https:\/\/www\.nvidia\.com\/nemoclaw\.sh \| bash/ + Received: " ███╗ ██╗███████╗███╗ ███╗ ██████╗ ██████╗██╗ █████╗ ██╗ ██╗ ████╗ ██║██╔════╝████╗ ████║██╔═══██╗██╔════╝██║ ██╔══██╗██║ ██║ ██╔██╗ ██║█████╗ ██╔████╔██║██║ ██║██║ ██║ ███████║██║ █╗ ██║ ██║╚██╗██║██╔══╝ ██║╚██╔╝██║██║ ██║██║ ██║ ██╔══██║██║███╗██║ ██║ ╚████║███████╗██║ ╚═╝ ██║╚██████╔╝╚██████╗███████╗██║ ██║╚███╔███╔╝ ╚═╝ ╚═══╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝╚══════╝╚═╝ ╚═╝ ╚══╝╚══╝ Launch OpenClaw in an OpenShell sandbox. [1/3] Node.js ────────────────────────────────────────────────── [INFO] Node.js found: v22.16.0 [INFO] Runtime OK: Node.js v22.16.0, npm 10.9.2 [2/3] NemoClaw CLI ────────────────────────────────────────────────── [INFO] NemoClaw package.json found in the selected source checkout — installing from source… [INFO] Installing NemoClaw dependencies unexpected npm invocation: ci --ignore-scripts " ❯ test/install-preflight.test.ts:436:20
expect(output).not.toMatch(/npm install -g nemoclaw/);
});

Expand Down Expand Up @@ -552,7 +552,7 @@
expect(output).not.toMatch(/0\.1\.0/);
});

it("uses npm install + npm link for a source checkout (no -g)", { timeout: 20000 }, () => {
it("uses npm ci + npm link for a source checkout (no -g)", { timeout: 20000 }, () => {
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-install-source-"));
const fakeBin = path.join(tmp, "bin");
const prefix = path.join(tmp, "prefix");
Expand Down Expand Up @@ -594,8 +594,16 @@
tar -czf "$tmpdir/openclaw-2026.3.11.tgz" -C "$tmpdir" package
exit 0
fi
if [ "$1" = "ci" ]; then rm -rf node_modules; exit 0; fi
if [ "$1" = "install" ]; then exit 0; fi
if [ "$1" = "run" ] && { [ "$2" = "build" ] || [ "$2" = "build:cli" ] || [ "$2" = "--if-present" ]; }; then exit 0; fi
if [ "$1" = "run" ] && [ "$2" = "--if-present" ] && [ "$3" = "build:cli" ]; then
test -d node_modules/openclaw || { echo "missing root openclaw before build:cli" >&2; exit 70; }
exit 0
fi
if [ "$1" = "run" ] && [ "$2" = "build" ]; then
test -d ../node_modules/openclaw || { echo "missing root openclaw before plugin build" >&2; exit 71; }
exit 0
fi
if [ "$1" = "link" ]; then
cat > "$NPM_PREFIX/bin/nemoclaw" <<'EOS'
#!/usr/bin/env bash
Expand All @@ -619,6 +627,9 @@
path.join(tmp, "nemoclaw", "package.json"),
JSON.stringify({ name: "nemoclaw-plugin", version: "0.1.0" }, null, 2),
);
fs.mkdirSync(path.join(tmp, "bin", "lib"), { recursive: true });
fs.writeFileSync(path.join(tmp, "bin", "lib", "usage-notice.js"), "process.exit(0);\n");
fs.writeFileSync(path.join(tmp, "Dockerfile.base"), "ARG OPENCLAW_VERSION=2026.3.11\n");
fs.mkdirSync(path.join(tmp, "nemoclaw-blueprint", "router", "llm-router"), {
recursive: true,
});
Expand All @@ -638,6 +649,7 @@
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1",
NEMOCLAW_DEFER_OPENSHELL_INSTALL: "1",
NPM_PREFIX: prefix,
NEMOCLAW_REPO_ROOT: tmp,
NPM_LOG_PATH: npmLog,
PYTHON_LOG_PATH: pythonLog,
GIT_LOG_PATH: gitLog,
Expand All @@ -646,8 +658,19 @@

expect(result.status).toBe(0);
const log = fs.readFileSync(npmLog, "utf-8");
// install (no -g) and link must both have been called
expect(log).toMatch(/^install(?!\s+-g)/m);
// Root and sandbox payload installs must use npm ci so host installs do not rewrite lockfiles.
const npmCalls = log.trim().split("\n");
const ciIndexes = npmCalls.flatMap((line, index) => (line === "ci --ignore-scripts" ? [index] : []));
const packIndexes = npmCalls.flatMap((line, index) =>
line.startsWith("pack openclaw@2026.3.11 --pack-destination ") ? [index] : [],
);
expect(ciIndexes).toHaveLength(2);
expect(packIndexes).toHaveLength(2);
expect(ciIndexes[0] ?? -1).toBeLessThan(packIndexes[0] ?? -1);
expect(ciIndexes[1] ?? -1).toBeLessThan(packIndexes[1] ?? -1);
expect(packIndexes[0] ?? -1).toBeLessThan(npmCalls.indexOf("run --if-present build:cli"));
expect(packIndexes[1] ?? -1).toBeLessThan(npmCalls.indexOf("run build"));
expect(log).not.toMatch(/^install(?!\s+-g)/m);
expect(log).toMatch(/^link/m);
// the GitHub URL must NOT appear — this is a local install
expect(log).not.toMatch(new RegExp(GITHUB_INSTALL_URL.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")));
Expand Down Expand Up @@ -732,7 +755,7 @@
},
});

expect(result.status).toBe(0);

Check failure on line 758 in test/install-preflight.test.ts

View workflow job for this annotation

GitHub Actions / unit-vitest-linux

[installer-integration] test/install-preflight.test.ts > installer runtime preflight > source-checkout: installs OpenShell when missing from PATH (#3989)

AssertionError: expected 98 to be +0 // Object.is equality - Expected + Received - 0 + 98 ❯ test/install-preflight.test.ts:758:29
expect(fs.existsSync(openshellLog)).toBe(true);
expect(fs.readFileSync(openshellLog, "utf-8")).toMatch(/install-openshell\.sh invoked/);
},
Expand Down Expand Up @@ -820,7 +843,7 @@
},
});

expect(result.status).toBe(0);

Check failure on line 846 in test/install-preflight.test.ts

View workflow job for this annotation

GitHub Actions / unit-vitest-linux

[installer-integration] test/install-preflight.test.ts > installer runtime preflight > source-checkout: skips OpenShell install when openshell is already on PATH (#3989)

AssertionError: expected 98 to be +0 // Object.is equality - Expected + Received - 0 + 98 ❯ test/install-preflight.test.ts:846:29
expect(fs.existsSync(openshellLog)).toBe(false);
},
);
Expand Down Expand Up @@ -906,7 +929,7 @@
},
});

expect(result.status).toBe(0);

Check failure on line 932 in test/install-preflight.test.ts

View workflow job for this annotation

GitHub Actions / unit-vitest-linux

[installer-integration] test/install-preflight.test.ts > installer runtime preflight > auto-resumes an interrupted onboarding session during install

AssertionError: expected 98 to be +0 // Object.is equality - Expected + Received - 0 + 98 ❯ test/install-preflight.test.ts:932:27
expect(`${result.stdout}${result.stderr}`).toMatch(
/Found an interrupted onboarding session — resuming it\./,
);
Expand Down Expand Up @@ -1009,7 +1032,7 @@
});

expect(result.status).not.toBe(0);
expect(`${result.stdout}${result.stderr}`).toMatch(/Previous onboarding session failed/);

Check failure on line 1035 in test/install-preflight.test.ts

View workflow job for this annotation

GitHub Actions / unit-vitest-linux

[installer-integration] test/install-preflight.test.ts > installer runtime preflight > refuses to auto-resume a failed onboarding session in non-interactive mode (#2430)

AssertionError: expected '\n ███╗ ██╗███████╗███╗ ███╗ ██…' to match /Previous onboarding session failed/ - Expected: /Previous onboarding session failed/ + Received: " ███╗ ██╗███████╗███╗ ███╗ ██████╗ ██████╗██╗ █████╗ ██╗ ██╗ ████╗ ██║██╔════╝████╗ ████║██╔═══██╗██╔════╝██║ ██╔══██╗██║ ██║ ██╔██╗ ██║█████╗ ██╔████╔██║██║ ██║██║ ██║ ███████║██║ █╗ ██║ ██║╚██╗██║██╔══╝ ██║╚██╔╝██║██║ ██║██║ ██║ ██╔══██║██║███╗██║ ██║ ╚████║███████╗██║ ╚═╝ ██║╚██████╔╝╚██████╗███████╗██║ ██║╚███╔███╔╝ ╚═╝ ╚═══╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝╚══════╝╚═╝ ╚═╝ ╚══╝╚══╝ Launch OpenClaw in an OpenShell sandbox. [1/3] Node.js ────────────────────────────────────────────────── [INFO] Node.js found: v22.16.0 [INFO] Runtime OK: Node.js v22.16.0, npm 10.9.2 [2/3] NemoClaw CLI ────────────────────────────────────────────────── [INFO] NemoClaw package.json found in the selected source checkout — installing from source… [INFO] Installing NemoClaw dependencies unexpected npm invocation: ci --ignore-scripts " ❯ test/install-preflight.test.ts:1035:48
expect(`${result.stdout}${result.stderr}`).toMatch(/--fresh/);
// The installer must have bailed out before invoking nemoclaw onboard.
expect(fs.existsSync(onboardLog)).toBe(false);
Expand Down Expand Up @@ -1115,7 +1138,7 @@
},
});

expect(result.status).toBe(0);

Check failure on line 1141 in test/install-preflight.test.ts

View workflow job for this annotation

GitHub Actions / unit-vitest-linux

[installer-integration] test/install-preflight.test.ts > installer runtime preflight > --fresh skips auto-resume regardless of session state (#2430)

AssertionError: expected 98 to be +0 // Object.is equality - Expected + Received - 0 + 98 ❯ test/install-preflight.test.ts:1141:27
expect(`${result.stdout}${result.stderr}`).toMatch(/Starting a fresh onboarding session/);
expect(`${result.stdout}${result.stderr}`).not.toMatch(
/Found an interrupted onboarding session/,
Expand Down Expand Up @@ -1211,7 +1234,7 @@
});

const output = `${result.stdout}${result.stderr}`;
expect(result.status).toBe(0);

Check failure on line 1237 in test/install-preflight.test.ts

View workflow job for this annotation

GitHub Actions / unit-vitest-linux

[installer-integration] test/install-preflight.test.ts > installer runtime preflight > skips onboarding when shared host preflight detects Docker is missing

AssertionError: expected 98 to be +0 // Object.is equality - Expected + Received - 0 + 98 ❯ test/install-preflight.test.ts:1237:27
expect(output).toMatch(/Host preflight found issues that will prevent onboarding right now\./);
expect(output).toMatch(/Start Docker/);
expect(output).toMatch(/Skipping onboarding until the host prerequisites above are fixed\./);
Expand Down Expand Up @@ -1460,7 +1483,7 @@
});

const output = `${result.stdout}${result.stderr}`;
expect(result.status).toBe(0);

Check failure on line 1486 in test/install-preflight.test.ts

View workflow job for this annotation

GitHub Actions / unit-vitest-linux

[installer-integration] test/install-preflight.test.ts > installer runtime preflight > warns on Podman but still runs onboarding

AssertionError: expected 98 to be +0 // Object.is equality - Expected + Received - 0 + 98 ❯ test/install-preflight.test.ts:1486:27
expect(output).toMatch(/Host preflight found warnings\./);
expect(output).toMatch(/Detected container runtime: podman/);
expect(output).toMatch(
Expand Down Expand Up @@ -1609,7 +1632,7 @@
},
);

expect(result.status).toBe(0);

Check failure on line 1635 in test/install-preflight.test.ts

View workflow job for this annotation

GitHub Actions / unit-vitest-linux

[installer-integration] test/install-preflight.test.ts > installer runtime preflight > passes the acceptance flag through to non-interactive onboard

AssertionError: expected 98 to be +0 // Object.is equality - Expected + Received - 0 + 98 ❯ test/install-preflight.test.ts:1635:27
expect(fs.readFileSync(onboardLog, "utf-8")).toMatch(
/^onboard --non-interactive --yes-i-accept-third-party-software --yes$/m,
);
Expand Down
55 changes: 55 additions & 0 deletions test/lockfile-ci-guard.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
import YAML from "yaml";

const REPO_ROOT = join(import.meta.dirname, "..");

type CompositeAction = {
runs?: {
steps?: Array<{
name?: string;
run?: string;
}>;
};
};

function loadBasicChecksAction(): CompositeAction {
return YAML.parse(
readFileSync(join(REPO_ROOT, ".github/actions/basic-checks/action.yaml"), "utf-8"),
) as CompositeAction;
}

describe("lockfile CI guards", () => {
it("validates root and sandbox payload lockfiles before install can rewrite them", () => {
const action = loadBasicChecksAction();
const steps = action.runs?.steps ?? [];
const validateIndex = steps.findIndex((step) => step.name === "Validate npm lockfiles");
const installIndex = steps.findIndex((step) => step.name === "Install dependencies");
const validateStep = steps[validateIndex];

expect(validateIndex).toBeGreaterThanOrEqual(0);
expect(installIndex).toBeGreaterThanOrEqual(0);
expect(validateIndex).toBeLessThan(installIndex);
expect(validateStep?.run?.trimEnd()).toBe(
[
"npm ci --ignore-scripts --dry-run",
"cd nemoclaw",
"npm ci --ignore-scripts --dry-run",
].join("\n"),
);
});

it("uses npm ci for root and sandbox payload installs in basic checks", () => {
const action = loadBasicChecksAction();
const installStep = action.runs?.steps?.find((step) => step.name === "Install dependencies");

expect(installStep?.run?.trimEnd()).toBe(
["npm ci --ignore-scripts", "cd nemoclaw", "npm ci --ignore-scripts"].join("\n"),
);
expect(installStep?.run).not.toContain("npm install");
});
});
Loading