########################### Node.js Package Management ########################### Overview ======== IVPM provides first-class support for Node.js packages, managing both npm registry packages and source packages (linked via ``npm link``) within a project-local Node.js environment. .. note:: The Node handler performs the work described on this page. For handler-level details (activation conditions, phase ordering, how it fits into the update pipeline), see :ref:`handler-node` in :doc:`handlers`. Node Environment Structure =========================== IVPM creates a project-local Node.js environment in ``packages/node/`` **on demand**: it is only created when at least one dependency (direct or transitive) is a Node.js package. Projects that contain no Node packages never create this directory, keeping the workspace lean. When a Node environment is created it is placed at:: packages/ └── node/ ├── package.json # Generated by IVPM — do not edit manually ├── package-lock.json # Committed for reproducible builds ├── node_modules/ # Installed packages (git-ignored) ├── export.envrc # direnv activation snippet └── .nvmrc # Node version pin (when version: is set) Package Manager Selection -------------------------- IVPM supports three Node.js package managers: 1. **npm** — default, requires Node.js to be installed 2. **pnpm** — faster, disk-efficient alternative 3. **yarn** — alternative package manager Specify the manager in ``ivpm.yaml``: .. code-block:: yaml package: name: my-project with: node: manager: pnpm # npm (default) | pnpm | yarn Configuring the Node Handler ============================= A project can permanently configure the Node handler by adding a ``with.node`` section inside ``package:`` in ``ivpm.yaml``. .. code-block:: yaml package: name: my-project with: node: manager: npm # npm (default) | pnpm | yarn version: "20" # Node version to pin (.nvmrc) env: true # Write packages.envrc entry (default: true) dep-sets: - name: default-dev deps: - name: lodash src: npm ``manager`` key --------------- Selects the package manager for all ``node install`` / ``node link`` operations. .. list-table:: :header-rows: 1 :widths: 15 85 * - Value - Behaviour * - ``npm`` (default) - Use ``npm install --prefix packages/node`` * - ``pnpm`` - Use ``pnpm install --dir packages/node`` * - ``yarn`` - Use ``yarn install --cwd packages/node`` ``version`` key --------------- When set, writes a ``.nvmrc`` file to ``packages/node/`` containing the specified version string. Tools such as ``nvm use`` and ``volta`` read this file to select the correct Node runtime automatically. .. code-block:: yaml with: node: version: "20" # writes packages/node/.nvmrc ``env`` key ----------- Controls whether the handler writes an activation entry into ``packages/packages.envrc``. Defaults to ``true``. Set to ``false`` to suppress environment modification (useful for CI or projects that manage their own PATH). .. code-block:: yaml with: node: env: false # Don't patch packages.envrc npm Registry Packages (``src: npm``) ====================================== Install a package from the npm registry by specifying ``src: npm``. .. code-block:: yaml deps: # Latest version - name: lodash src: npm # Specific version - name: react src: npm version: "^18.0.0" # Dev-only dependency - name: jest src: npm dev: true # Optional dependency - name: fsevents src: npm optional: true **Attributes:** ``version`` npm semver range (e.g., ``^18.0.0``, ``>=1.0.0 <2.0.0``). Defaults to ``*`` (latest). ``dev`` When ``true``, the package is placed in ``devDependencies`` in the generated ``packages/node/package.json``. Defaults to ``false``. ``optional`` When ``true``, marks the package as optional. Defaults to ``false``. **Install behavior:** All npm packages are installed into ``packages/node/node_modules/`` by running the configured package manager. Importing deps from an Existing package.json (``src: package.json``) ====================================================================== If your project already has a ``package.json`` (e.g., in the repo root or in a sub-package), you can import all its dependencies into the IVPM-managed environment without duplicating them: .. code-block:: yaml deps: - name: root-pkg-json src: package.json url: file://${PROJECT_ROOT}/package.json IVPM reads the ``dependencies`` and ``devDependencies`` sections of the referenced file and synthesises ``PackageNpm`` entries for each one. An explicit ``src: npm`` entry in the same dep-set always overrides a same-named entry from ``src: package.json`` (explicit entries win). **Attributes:** ``url`` Path to the ``package.json`` file. Supports: - ``file://`` prefix for absolute paths - ``${VAR}`` environment variable substitution Source Package Linking (``type: node``) ======================================== Source packages (git, dir) that provide a Node.js library can be *linked* into the managed environment using ``npm link`` so that other packages in the project can ``require()`` them directly: .. code-block:: yaml deps: # Git-hosted TypeScript library - name: my-ts-lib url: https://github.com/org/my-ts-lib.git type: node # Link but treat as dev-only - name: shared-utils url: https://github.com/org/shared-utils.git type: { node: { dev: true, link: true } } # Source present but do NOT link (just track) - name: build-tool url: https://github.com/org/build-tool.git type: { node: { link: false } } **Per-package options (``type: { node: { ... } }``):** ``dev`` Treat the linked package as a dev dependency. Defaults to ``false``. ``link`` When ``true`` (default), runs ``npm link `` for the package after install. Set to ``false`` to track the package without linking. **Auto-detection:** IVPM automatically applies ``type: node`` (with ``link: true``) to any source package (git/dir/http/gh-rls) that contains a ``package.json`` but has no explicit ``type:`` set. This mirrors the Python handler's ``pyproject.toml`` auto-detection. Generated ``packages/node/package.json`` ========================================== IVPM synthesises a ``packages/node/package.json`` from all collected npm packages. The file is written during ``ivpm update`` and should be committed to your repository (together with ``package-lock.json``) for reproducible builds. Example generated file: .. code-block:: json { "name": "ivpm-node-env", "version": "1.0.0", "private": true, "dependencies": { "lodash": "^4.17.21", "axios": "^1.0.0" }, "devDependencies": { "jest": "^29.0.0", "typescript": "^5.0.0" } } .. note:: Do **not** edit ``packages/node/package.json`` manually — IVPM regenerates it on every ``ivpm update`` run. Put all dependency declarations in ``ivpm.yaml``. **Hash-based skip:** IVPM stores a SHA-256 hash of the generated ``package.json`` in ``packages/ivpm.json``. On subsequent runs, if the hash matches and ``node_modules/`` already exists, the expensive ``npm install`` step is skipped automatically, making repeated ``ivpm update`` calls fast. Environment Activation ======================= direnv (recommended) ---------------------- The Node handler patches ``packages/packages.envrc`` to source the Node activation snippet automatically when you ``cd`` into the project:: packages/ ├── packages.envrc # Patched to source node/export.envrc └── node/ └── export.envrc # Sets PATH and NODE_PATH Contents of ``packages/node/export.envrc``: .. code-block:: bash # Generated by IVPM node handler — do not edit manually PATH_add node_modules/.bin export NODE_PATH="$PWD/node_modules" With direnv installed and ``.envrc`` sourcing ``packages/packages.envrc``, all executables in ``packages/node/node_modules/.bin`` are on your ``PATH`` automatically (e.g., ``jest``, ``tsc``, ``eslint``). Manual activation ----------------- Without direnv, source the export file manually: .. code-block:: bash $ source packages/node/export.envrc Windows ------- On Windows, IVPM additionally writes: - ``packages/node/activate_node.bat`` — adds ``node_modules/.bin`` to ``%PATH%`` - ``packages/node/activate_node.ps1`` — PowerShell equivalent - ``packages/packages_activate.bat`` / ``.ps1`` — top-level wrappers .. code-block:: bat :: In a .bat script or CMD session call packages\packages_activate.bat :: In PowerShell . packages\packages_activate.ps1 Node Version Management ======================== When ``version:`` is configured, IVPM writes a ``.nvmrc`` file: .. code-block:: yaml with: node: version: "20" .. code-block:: bash # .nvmrc written to packages/node/.nvmrc $ cat packages/node/.nvmrc 20 # nvm picks up the version automatically $ cd packages/node && nvm use Now using node v20.x.x # volta also reads .nvmrc $ volta pin node@$(cat packages/node/.nvmrc) To ensure the correct Node.js version is active before running IVPM: .. code-block:: bash $ nvm use 20 && ivpm update Lockfile Recommendations ========================= Commit ``packages/node/package-lock.json`` (or ``pnpm-lock.yaml`` / ``yarn.lock`` for other managers) to version control. This ensures every developer and CI system installs the exact same package versions. IVPM will warn if ``package-lock.json`` is present but git-ignored:: WARNING: packages/node/package-lock.json is gitignored. Consider committing it for reproducible builds. Add to ``.gitignore`` (node_modules only, **not** the lockfile): .. code-block:: text packages/node/node_modules/ Complete Examples ================= Example 1: Front-end Tooling ----------------------------- .. code-block:: yaml package: name: my-webapp default-dep-set: default-dev with: node: manager: npm version: "20" dep-sets: - name: default-dev deps: # Runtime - name: react src: npm version: "^18.0.0" - name: react-dom src: npm version: "^18.0.0" # Dev tools - name: typescript src: npm version: "^5.0.0" dev: true - name: jest src: npm version: "^29.0.0" dev: true - name: "@types/react" src: npm version: "^18.0.0" dev: true **Usage:** .. code-block:: bash $ ivpm update $ tsc --noEmit # type-check (tsc on PATH via direnv) $ jest # run tests $ node -e "require('react')" # verify install Example 2: Mixed Python + Node Project --------------------------------------- .. code-block:: yaml package: name: fullstack-app default-dep-set: default-dev with: python: venv: uv node: manager: npm dep-sets: - name: default-dev deps: # Python backend - name: fastapi src: pypi - name: uvicorn src: pypi # Node frontend build - name: webpack src: npm dev: true - name: babel-loader src: npm dev: true # Shared TypeScript library (source) - name: shared-types url: https://github.com/org/shared-types.git type: node **Usage:** .. code-block:: bash $ ivpm update $ uvicorn app:main & # Python backend $ webpack --config webpack.config.js # Node build Example 3: Importing from Existing package.json ------------------------------------------------- .. code-block:: yaml package: name: existing-project default-dep-set: default-dev dep-sets: - name: default-dev deps: # Import all deps from the repo's root package.json - name: root-deps src: package.json url: file://${PROJECT_ROOT}/package.json # Override one specific version - name: lodash src: npm version: "^4.17.21" # Overrides whatever is in package.json Best Practices ============== 1. **Commit lockfiles** (``package-lock.json``, ``pnpm-lock.yaml``, ``yarn.lock``) for reproducible builds; never commit ``node_modules/``. 2. **Pin Node.js version** using ``with.node.version`` and ``.nvmrc`` for consistent environments. 3. **Separate dev deps** with ``dev: true`` to keep production installs lean. 4. **Use auto-detection** — source packages with ``package.json`` are detected automatically; only add ``type: node`` when you need to change options. 5. **Let IVPM own** ``packages/node/package.json`` — declare all deps in ``ivpm.yaml`` and never edit the generated file directly. 6. **Use pnpm** for monorepos with many shared sub-packages; it handles symlink-based deduplication better than npm. See Also ======== - :doc:`handlers` - Node handler details and other built-in handlers - :doc:`getting_started` - Basic project setup - :doc:`dependency_sets` - Organizing Node.js and other deps together - :doc:`package_types` - npm and package.json source types - :doc:`python_packages` - Python package management (similar patterns)