Skip to content

[#3060] Synchronised npm lock files when the installer removes front-end tools. - #3062

Merged
AlexSkrypnyk merged 4 commits into
mainfrom
feature/3060-regen-lock-file
Aug 21, 2026
Merged

[#3060] Synchronised npm lock files when the installer removes front-end tools.#3062
AlexSkrypnyk merged 4 commits into
mainfrom
feature/3060-regen-lock-file

Conversation

@AlexSkrypnyk

Copy link
Copy Markdown
Member

Closes #3060

Problem

When the installer removed a front-end tool it rewrote package.json via JsonManipulator::updateFile() but left package-lock.json untouched. ahoy fei runs npm ci, which treats the lock as authoritative and aborts when the pair disagrees, so a generated project failed on its first build. Full deselection was safe because the frontend_all group removes both files together; partial deselection (ESLint off, Jest kept) was the broken path.

Fix

A new DrevOps\VortexInstaller\Utils\NpmLock utility reconciles the lock file with the manifest next to it. NpmLock::sync() copies the manifest's dependency blocks (dependencies, devDependencies, optionalDependencies, peerDependencies) onto the lock's root packages[""] entry, then garbage-collects every packages entry no longer reachable from a root, following node's resolution rules: the innermost node_modules copy wins, falling back outwards to the hoisted tree. It is offline and deterministic, with no npm invocation, because Node is not an installer prerequisite. It writes the lock the way npm does: two-space indent, unescaped slashes, trailing newline.

It is wired in at the manifest edit itself, in the new Tools::updateNpmManifest(), so the sync is derived from the tool map's single list of package names rather than a second list that could drift out of sync. Every tool in the map gets it, not just the one that exposed the bug.

Custom theme treatment

The custom theme carries its own package.json and package-lock.json, with the full ESLint, Prettier and Stylelint dependency sets and its own lint scripts, and the tool map never touched them, so a project that deselected ESLint still installed and ran ESLint inside the theme, and ahoy lint-fe calls the theme's npm run lint directly. New theme.package.json entries on the ESLint and Stylelint tool definitions clean the theme manifest, and its lock is synced the same way as the root pair. The theme manifest and lock are never deleted, even when every front-end tool is deselected, because they also drive the Sass/PostCSS/Terser build.

Dead aggregate scripts

With both linters deselected, each linter's callback rewrote lint to call the other one, leaving lint and lint-fix pointing at scripts that no longer exist. The frontend_linting group now removes that pair from both manifests, drops the theme lint lines from .ahoy.yml, and drops the two matching rows from the theme README.md command table.

CI configs

The theme lint step in .github/workflows/build-test-deploy.yml and .circleci/config.yml was fenced only on DRUPAL_THEME, so with both linters deselected CI still ran npm run lint in the theme against a script that had been removed. Both steps now sit inside the TOOL_ESLINT_STYLELINT fence as well.

Refactor

The content-removal pass (strings and lines) was extracted from processTool() into processContent() and is now shared with processGroup(), so groups can declare content removals the same way tools do. It is skipped when a definition declares neither key.

Tests

New NpmLockTest unit suite (18 cases) covers root reconciliation, orphan pruning, retention of transitives shared with a surviving dependency, nested node_modules shadowing and fall-back to the hoisted tree, workspace/link entries as roots, unresolvable optional dependencies, a missing lock, an unsupported lock, non-object dependency blocks, npm-shaped output formatting and empty-object round-tripping. ToolsHandlerProcessTest gains manifest/lock sync assertions on the partial-deselection scenarios plus a new tools_no_eslint_no_stylelint fixture scenario (both linters off, Jest kept). Snapshots regenerated.

Fixture snapshots continue to ignore lock file content via .ignorecontent, so the lock is asserted programmatically in the tests rather than as an 8,600-line fixture diff.

Verification

The PHP prune was checked against real npm 10.9.2: on the template's own root pair it produced exactly the same 421-entry package set as npm install --package-lock-only, with no differences beyond libc metadata that npm drops and this preserves. Running the installer end-to-end with both linters deselected then produced a project where npm ci --dry-run succeeds at the root (331 packages) and in the theme (175 packages).

Screenshots

N/A

Before / After

BEFORE - partial deselection (ESLint off, Jest kept)

    package.json                     package-lock.json
    -------------------------        -------------------------
    "devDependencies": {             "packages": {
      "jest": "^29.0.0"                "": {
    }                                    "devDependencies": {
                                            "eslint": "^9.0.0",
                                            "jest": "^29.0.0"
                                          }
                                        },
                                        "node_modules/eslint": { ... },
                                        "node_modules/jest": { ... }
                                      }

    $ npm ci
    npm error `npm ci` can only install packages when your package.json and
    npm error package-lock.json are in sync. Please update your lock file.
    npm error Missing: eslint@9.0.0 from lock file
    npm error
    npm error Build fails on the first run.

AFTER - NpmLock::sync() reconciles the pair

    package.json                     package-lock.json
    -------------------------        -------------------------
    "devDependencies": {             "packages": {
      "jest": "^29.0.0"                "": {
    }                                    "devDependencies": {
                                            "jest": "^29.0.0"
                                          }
                                        },
                                        "node_modules/jest": { ... }
                                        (eslint and its orphans pruned)
                                      }

    $ npm ci
    added 331 packages in 4s
    Build succeeds.

@coderabbitai

coderabbitai Bot commented Aug 21, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your current included review allowance is based on your included PR review attempts over the past 7 days.

Next review available in: 18 minutes

Limit details: You’ve used all 2 included reviews currently available. Your 82 included PR review attempts over the past 7 days set your current allowance at 2 reviews per hour.

You’re in a promotional period — use the checkbox below to run this review for free:

  • Run review for free

On-demand reviews are free for the next 31 days. After that, they cost $0.25 per reviewed file.

How can I continue?

Run this review now using the option above, or comment @coderabbitai review --use-credits.

You can also wait for the limit to reset, then comment @coderabbitai review or push new commits to the PR.

An organization admin can change what happens after included review limits in Billing.

How do review limits work?

CodeRabbit enforces per-developer PR review limits within each organization.

For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 8dc7b0c3-5379-4fe2-827c-6186383969b6

📥 Commits

Reviewing files that changed from the base of the PR and between 3d557c5 and a456a13.

⛔ Files ignored due to path filters (27)
  • .vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint/.ahoy.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint/.github/workflows/build-test-deploy.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint/web/themes/custom/star_wars/README.md is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint/web/themes/custom/star_wars/package.json is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_circleci/.ahoy.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_circleci/.circleci/config.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_circleci/web/themes/custom/star_wars/README.md is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_circleci/web/themes/custom/star_wars/package.json is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_eslint/.github/workflows/build-test-deploy.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_eslint/web/themes/custom/star_wars/package.json is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_eslint_circleci/.circleci/config.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_eslint_circleci/web/themes/custom/star_wars/package.json is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_eslint_no_stylelint/-.prettierignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_eslint_no_stylelint/-.prettierrc.json is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_eslint_no_stylelint/-.stylelintrc.js is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_eslint_no_stylelint/-eslint.config.mjs is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_eslint_no_stylelint/.ahoy.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_eslint_no_stylelint/.github/workflows/build-test-deploy.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_eslint_no_stylelint/package.json is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_eslint_no_stylelint/web/themes/custom/star_wars/README.md is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_eslint_no_stylelint/web/themes/custom/star_wars/package.json is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_stylelint/web/themes/custom/star_wars/package.json is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_stylelint_circleci/web/themes/custom/star_wars/package.json is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_none/.ahoy.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_none/.github/workflows/build-test-deploy.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_none/web/themes/custom/star_wars/README.md is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_none/web/themes/custom/star_wars/package.json is excluded by !.vortex/installer/tests/Fixtures/**
📒 Files selected for processing (6)
  • .circleci/config.yml
  • .github/workflows/build-test-deploy.yml
  • .vortex/installer/src/Prompts/Handlers/Tools.php
  • .vortex/installer/src/Utils/NpmLock.php
  • .vortex/installer/tests/Functional/Prompts/Handlers/ToolsHandlerProcessTest.php
  • .vortex/installer/tests/Unit/Utils/NpmLockTest.php

Comment @coderabbitai help to get the list of available commands.

@AlexSkrypnyk AlexSkrypnyk added this to the 1.41.0 milestone Aug 21, 2026
@AlexSkrypnyk AlexSkrypnyk added the A1 Board worker 1 label Aug 21, 2026
@github-actions

Copy link
Copy Markdown

Code coverage (threshold: 90%)

  Classes: 100.00% (1/1)
  Methods: 100.00% (2/2)
  Lines:   98.56% (206/209)
Per-class coverage
Drupal\ys_demo\Plugin\Block\CounterBlock
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% ( 10/ 10)

@AlexSkrypnyk

This comment has been minimized.

2 similar comments
@AlexSkrypnyk

Copy link
Copy Markdown
Member Author

Code coverage (threshold: 90%)

  Classes: 100.00% (1/1)
  Methods: 100.00% (2/2)
  Lines:   98.56% (206/209)
Per-class coverage
Drupal\ys_demo\Plugin\Block\CounterBlock
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% ( 10/ 10)

@AlexSkrypnyk

Copy link
Copy Markdown
Member Author

Code coverage (threshold: 90%)

  Classes: 100.00% (1/1)
  Methods: 100.00% (2/2)
  Lines:   98.56% (206/209)
Per-class coverage
Drupal\ys_demo\Plugin\Block\CounterBlock
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% ( 10/ 10)

@codecov

codecov Bot commented Aug 21, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.27536% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 86.95%. Comparing base (3d557c5) to head (a456a13).

Files with missing lines Patch % Lines
.vortex/installer/src/Utils/NpmLock.php 98.33% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3062      +/-   ##
==========================================
- Coverage   87.08%   86.95%   -0.14%     
==========================================
  Files         106      100       -6     
  Lines        4994     4928      -66     
  Branches       58        3      -55     
==========================================
- Hits         4349     4285      -64     
+ Misses        645      643       -2     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions

Copy link
Copy Markdown

📖 Documentation preview for this pull request has been deployed to Netlify:

https://6a87acc7163f70cb205e504a--vortex-docs.netlify.app

This preview is rebuilt on every commit and is not the production documentation site.

@AlexSkrypnyk AlexSkrypnyk added the Needs review Pull request needs a review from assigned developers label Aug 21, 2026
@AlexSkrypnyk
AlexSkrypnyk merged commit ca0ca82 into main Aug 21, 2026
36 checks passed
@AlexSkrypnyk
AlexSkrypnyk deleted the feature/3060-regen-lock-file branch August 21, 2026 05:50
@github-project-automation github-project-automation Bot moved this from BACKLOG to Release queue in Vortex 1.x Aug 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A1 Board worker 1 Needs review Pull request needs a review from assigned developers

Projects

Status: Released in 1.41.0

Development

Successfully merging this pull request may close these issues.

Regenerate the lock file when the installer removes front-end tool dependencies

1 participant