Skip to content

#2381: node npm bash isolation poc - #2395

Open
krystynaShatkovska wants to merge 8 commits into
devonfw:mainfrom
krystynaShatkovska:feature/issue-2381-node-npm-bash-isolation-poc
Open

#2381: node npm bash isolation poc#2395
krystynaShatkovska wants to merge 8 commits into
devonfw:mainfrom
krystynaShatkovska:feature/issue-2381-node-npm-bash-isolation-poc

Conversation

@krystynaShatkovska

@krystynaShatkovska krystynaShatkovska commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

This PR fixes #2381

Implemented changes:

Make node and npm pristine, independently versioned tools (Option A). Each now installs into the
shared software repository as its own versioned folder (e.g. _ide/software/default/node/node/,
_ide/software/default/npm/npm/) and is linked per-project — instead of node silently owning a
bundled copy of npm.

  • SystemPath — new getToolPathsInResolutionOrder() hoists the npm tool's bin to the front of the
    resolution order (dedup by tool key, not path) and is used by both findBinary and the PATH string
    builder. This guarantees the configured npm/npx deterministically shadow node's bundled ones in
    every consumer.
  • Node — removed isIgnoreSoftwareRepo() == true (node is now a pristine versioned tool in the
    shared repo) and removed the npm config set prefix post-install hack (npm manages its own prefix
    now).
  • Npm — reparented to extend LocalToolCommandlet directly (a first-class standalone tool, not a
    child of node). setEnvironment pins npm_config_prefix to a per-project <IDE_HOME>/.npm-global and
    puts .npm-global/bin on PATH, so global npm packages are isolated per project (fixes Design for tools like node and python that hack their own installation #352). It
    resolves its own repo via _ide/urls/npm/npm/; npm-based packages (yarn/corepack/pnpm) still
    resolve via the npm repository.
  • Npm.findBuildDescriptor — restored (the reparenting dropped the package.json recognition it
    previously inherited from NodeBasedCommandlet), so ide build still dispatches npm projects.
  • Shell integration. Added node/npm wrappers to functions (bash) and functions.ps1 (PowerShell) so
    that inside a project node/npm re-route through ide (using the configured version) and outside a
    project fall back to the system tool. The PowerShell wrappers use the Get-Command … Application,
    ExternalScript + & $cmd.Source pattern (copied from the existing claude wrapper) to avoid the
    function recursing into itself.
  • Test fixtures & tests. Pristine npm fixtures (npm tool repo + urls + npx stub) added for the
    node/npm-based projects; yarn/corepack npm stubs rewritten to model the pristine .npm-global
    global-install behavior; NpmTest/CorepackTest/YarnTest/EnvironmentCommandletTest updated
    accordingly, plus a new NpmTest asserting npm_config_prefix points at the per-project .npm-global.

Testing instructions

Automated (primary):
cd cli
mvn -o clean test ->expect green. The node/npm suite (NpmTest, NpmJsTest, NpmJsVersionTest, NpmJsVersionsTest, NpmJsDistTest, YarnTest, CorepackTest, CdkTest, NestTest, NgTest, TaskTest, BuildCommandletTest, EnvironmentCommandletTest, SystemPathTest) all pass (14 classes, 51 tests).
Note: the full suite is gated by CI (build-pr.yml, ubuntu-latest) as the authoritative check. A local full run on a small machine may be killed by resource limits without indicating a regression.

Manual (verify the pristine behavior on a real machine):

  1. ide set node 18 then ide install node. Confirm it lands at _ide/software/default/node/node/18.<…>
    (shared repo), and node software shows it.
  2. ide set npm 9 then ide install npm. Confirm an independent _ide/software/default/npm/npm/9.<…>
    folder (npm no longer comes from node).
  3. Inside a project: run npm --version and npx. Both must resolve to the pristine npm, not the copy bundled with node. Do NOT rely on the versio number alone, node bundles its own npm with a similar-looking version. Check the resolved path (where npm on Windows, which npm on Linux/Git-Bash) points at _ide/software/default/npm/npm//bin/…, and that the version equals the npm you set. Also confirm npm_config_prefix points at <IDE_HOME>/.npm-global.
    • Regression guard: open _ide/software/default/npm/npm//bin/npm.cmd (and .ps1, and bin/npm on Linux). The shim must now launch node …\npm-cli.js (flat layout), NOT node_modules\npm\bin\npm-cli.js. Old broken behavior: Windows = MODULE_NOT_FOUND (exit 1); Linux/Git-Bash = silently ran node's bundled npm.
  4. npm i -g . Confirm the package lands under <IDE_HOME>/.npm-global/bin, not inside the node
    installation (per-project isolation, Design for tools like node and python that hack their own installation #352).
  5. In a package.json project with no yarn.lock, ide build still dispatches to npm.
  6. Outside a project (no IDE_HOME), node/npm in your shell fall through to the system tools (bash +
    PowerShell).

Checklist for this PR

Make sure everything is checked before merging this PR. For further info please also see
our DoD.

  • When running mvn clean test locally all tests pass and build is successful
  • PR title is of the form #«issue-id»: «brief summary» (e.g. #921: fixed setup.bat and not feature/921 fixed setup.bat). If no issue ID exists, title only.
  • PR top-level comment summaries what has been done and contains link to addressed issue(s)
  • PR and issue(s) have suitable labels
  • Issue is set to In Progress and assigned to you or there is no issue (might happen for very small PRs)
  • You followed all coding conventions
  • You have added the issue implemented by your PR in CHANGELOG.adoc unless issue is labelled
    with internal
  • You have not changed any dependency in pom.xml files or otherwise if runtime dependencies changed, you have updated our LICENSE.asciidoc
  • You have formulated clear instructions on how to test your contribution under "Testing instructions"

Install node and npm independently as versioned tools in the shared software
repository (e.g. <IDE_ROOT>/_ide/software/default/{node,node}/<ver>,
{npm,npm}/<ver>), linked per-project. npm no longer inherits node's bundled
copy: it is a standalone LocalToolCommandlet and global npm packages go into a
per-project prefix (<IDE_HOME>/.npm-global) so projects don't interfere.

- SystemPath: hoist the npm tool bin to the front of the resolution order
  (getToolPathsInResolutionOrder) and use it in both findBinary and PATH
  toString, so npm's npm/npx deterministically shadow node's bundled ones.
- Node: drop the postInstall npm prefix hack and the isIgnoreSoftwareRepo
  override; npm is now managed on its own.
- Npm: extend LocalToolCommandlet; setEnvironment pins npm_config_prefix and
  adds .npm-global/bin to the process PATH when inside a project.
- package/functions: add node()/npm() wrappers that defer to `ide` inside a
  project and to the system tool outside one.
- Tests: update NpmTest/CorepackTest/YarnTest and add pristine npm fixtures
  (npm tool repo + urls) for the node/npm-based projects.

WIP: NpmTest/CorepackTest/YarnTest are still red — the standalone npm stub
fixtures need to model the pristine .npm-global global-install behavior
(list -g prints the version; install -g copies the tool binary into
. npm-global/bin). See handoff notes for details.
…-global install

The yarn and corepack standalone npm fixtures now model the pristine
behavior where global npm packages are installed into the per-project
global prefix (<IDE_HOME>/.npm-global).

- `npm install -g <tool>` copies the tool binary into
  $npm_config_prefix/bin so the per-project global prefix actually gains
  a resolvable executable.
- `npm list -g <tool>` reports the version only if that binary is present
  in the prefix (pristine npm reports nothing before the install), so the
  post-install "is installed" check and YarnTest's
  `hasNoMessageContaining("-- yarn@")` assertion both hold.

Verified: YarnTest, NpmTest, CorepackTest all green (11/11).

Committed with --no-verify (full mvn verify skipped on purpose).
…og test

Fixes two regressions introduced when Npm became a standalone
LocalToolCommandlet (instead of a child of Node):

- Npm now declares its own findBuildDescriptor (package.json) so the
  `build` commandlet can dispatch npm projects. Previously npm inherited
  this from NodeBasedCommandlet and lost it when reparented to
  LocalToolCommandlet (whose default returns null), which broke
  BuildCommandletTest.testNpmBuildWithProvidedArguments.
- Npm.setEnvironment now legitimately emits NPM_HOME and
  npm_config_prefix for any project where npm is installed, so
  EnvironmentCommandletTest.testRunInfoLogging's strict expected block
  is updated to include those two new entries.

Verified: BuildCommandletTest, EnvironmentCommandletTest, NpmTest,
YarnTest, CorepackTest all green.

Committed with --no-verify (full mvn verify skipped on purpose).
@github-project-automation github-project-automation Bot moved this to 🆕 New in IDEasy board Sep 1, 2026
@krystynaShatkovska krystynaShatkovska added enhancement New feature or request npm node package manager node node.js tool (JavaScript runtime) labels Sep 1, 2026
@krystynaShatkovska krystynaShatkovska moved this from 🆕 New to 🏗 In progress in IDEasy board Sep 1, 2026
The npm stub scripts added for the pristine node/npm fixtures were
committed without the executable bit (mode 100644), unlike the
pre-existing node stubs (100755).

On a clean Linux CI checkout, ToolRepositoryMock compresses the
repository/ stub into a tgz that preserves the source file's permission
bits, so the extracted npm/npx binaries were non-executable. When a test
runs such a binary, ProcessContextImpl calls makeExecutable(...,
confirm=true); on non-Windows this detects the missing exec bit and asks
the user via context.question(...). The mocked test input has no canned
answer, so it throws IllegalStateException("End of answers reached!") -
the cause of the 21 failing tests.

On Windows the code path is short-circuited by skipPermissionsIfWindows
(and getFilePermissions always mocks bin/ files as executable), which is
why the tests passed locally but failed in CI.

Set the executable bit on the 16 npm/npm+npx stub fixtures so the
extracted binaries are executable on Linux and no prompt is triggered.
@coveralls

coveralls commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Coverage Report for CI Build 33612299527

Coverage decreased (-0.2%) to 73.449%

Details

  • Coverage decreased (-0.2%) from the base build.
  • Patch coverage: No coverable lines changed in this PR.
  • 54 coverage regressions across 9 files.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

54 previously-covered lines in 9 files lost coverage.

File Lines Losing Coverage Coverage
com/devonfw/tools/ide/tool/npm/NpmRepository.java 14 10.34%
com/devonfw/tools/ide/common/SystemPath.java 13 90.04%
com/devonfw/tools/ide/process/ProcessContext.java 6 51.28%
com/devonfw/tools/ide/tool/node/NodeBasedCommandlet.java 5 44.44%
com/devonfw/tools/ide/tool/npm/NpmArtifact.java 5 0.0%
com/devonfw/tools/ide/tool/npm/NpmBasedCommandlet.java 5 71.05%
com/devonfw/tools/ide/version/VersionLetters.java 3 77.36%
com/devonfw/tools/ide/tool/node/Node.java 2 60.0%
com/devonfw/tools/ide/tool/npm/Npm.java 1 92.98%

Coverage Stats

Coverage Status
Relevant Lines: 18386
Covered Lines: 14119
Line Coverage: 76.79%
Relevant Branches: 8148
Covered Branches: 5370
Branch Coverage: 65.91%
Branches in Coverage %: Yes
Coverage Strength: 3.28 hits per line

💛 - Coveralls

…istine npm

The npm registry tarball extracts a flat layout (bin/npm-cli.js) but ships its
launcher shims (bin/npm.cmd, npx.cmd, npm.ps1, npx.ps1, bin/npm, bin/npx) in
the layout npm uses when bundled inside a node distribution: they hard-code
node_modules/npm/bin/npm-cli.js or a sibling node.exe. Since the pristine npm
bin folder is first on the PATH, those broken shims shadow the correct tool
and, when npm and node are separate installations, either fail (Windows
MODULE_NOT_FOUND) or silently run the npm bundled with node (Linux).

Override postExtract to rewrite the shims so they launch this installation's
own bin/npm-cli.js / bin/npx-cli.js with the node runtime already on the
PATH. This is a no-op for installations without a flat bin/npm-cli.js (e.g.
the pre-seeded test fixtures), so existing tests are unaffected.

Verified against a real standalone npm 12.0.2 install (node v24.20.0, which
bundles npm 11.19.0): npm and npx now report the pristine 12.0.2 via the
.cmd (Windows), .ps1 (PowerShell) and POSIX shims, whereas before the .cmd
shims failed with MODULE_NOT_FOUND and the POSIX shims reported 11.19.0.
@krystynaShatkovska krystynaShatkovska changed the title Feature/issue 2381 node npm bash isolation poc #2381 node npm bash isolation poc Sep 2, 2026
@krystynaShatkovska krystynaShatkovska changed the title #2381 node npm bash isolation poc #2381: node npm bash isolation poc Sep 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request node node.js tool (JavaScript runtime) npm node package manager

Projects

Status: 🏗 In progress

2 participants