Skip to content

fix(dev-tooling): make path-scoped JS lint and format runs work on Shopware - #191

Merged
Martin Bens (SpiGAndromeda) merged 5 commits into
mainfrom
fix/dev-tooling-js-path-scoped-npm-exec
Oct 11, 2026
Merged

Martin Bens (SpiGAndromeda) merged 5 commits into
mainfrom
fix/dev-tooling-js-path-scoped-npm-exec

Conversation

@SpiGAndromeda

@SpiGAndromeda Martin Bens (SpiGAndromeda) commented Oct 11, 2026 •

Copy link
Copy Markdown
Collaborator

Path-scoped runs of the JS lint and format tools refused on every Shopware checkout. stylelint_check/stylelint_fix and prettier_check/prettier_fix on js-admin-tooling, and stylelint_check/stylelint_fix and eslint_check/eslint_fix on js-storefront-tooling, routed a call with paths to stylelint:base, prettier:base, stylelint:app, eslint:app and eslint:components. No Shopware package.json defines these scripts, so the tools answered with npm script "stylelint:base" is not defined in package.json and refused.

Path-scoped runs

  • A call with paths now runs the package's own binary through npm exec --no -- <binary>, with the flags of the aggregate script (lint:scss, format, lint:js). The Storefront components tree runs npm exec --no -c "cd ../.. && eslint …", as lint:js does. Admin ESLint keeps lint:debugging, which Shopware defines. Runs without paths still use the aggregate scripts.
  • Before the run, the tool checks that node_modules/.bin/<binary> exists, because npm exec --no runs a global binary of the same name when the package has none. A missing binary refuses with a message naming the binary and the directory the check ran in.
  • A directory path reaches Stylelint and Prettier as a quoted pattern limited to the extensions the aggregate script lints: <dir>/**/*.scss for Admin Stylelint, <dir>/**/*.{scss,css} for Storefront Stylelint, <dir>/**/*.{js,ts,mjs} for Prettier. A directory whose name ends in such an extension gets the same pattern. Under ddev on a linked worktree, directory and glob paths for these tools are refused, because ddev exec re-parses the command in the container shell, which would expand the pattern first.
  • Scopes: components-tree ESLint paths are refused under a scope other than shopware. Storefront Stylelint adds the core --config stylelint.config.js only without a scope. A scope-selected Stylelint config on a run without paths is refused when the aggregate script already passes --config, instead of handing Stylelint two --config flags.
  • The path handling the JS servers share (glob detection, the binary check, directory patterns, the ddev worktree refusal, the --config check) lives in shared/js_paths.sh. This file belongs to dev-tooling and is not synced from templates/.

Hooks

  • The Administration and Storefront PreToolUse hooks matched only npx <binary> without flags. They now also block npm exec and npm x, a node_modules/.bin/<binary> path, an @version suffix, the valueless flags -y, --yes, --no, --no-install, -q, --quiet, --silent and --, and a -c/--call string that runs the binary. The binaries are eslint, stylelint and jest on both hooks, prettier and tsc on the Administration hook, and vitest on the Storefront hook.
  • Both hooks take the matcher for these forms from hooks/scripts/lib/js_binary.sh.
  • docs/mcp-enforcement.md documents that the hooks match each line of a command on its own, so a commit message line such as npx eslint now runs in a heredoc is blocked, and names git commit -F <file> as the way around it. It no longer claims that both package.json files declare jest:base.

Tests

  • The JS server tests answer script probes from the scripts blocks of Shopware trunk (plugin-tests/dev-tooling/fixtures/shopware-trunk/), and the npm fake fails an undefined npm run the way npm does. The previous fixtures defined the missing scripts, which is why the suite passed while every real call refused. regenerate.sh in that directory rebuilds the fixtures from a Shopware checkout.
  • The Stylelint, Prettier and ESLint commands and the binary check run through the native, docker, docker-compose, vagrant, ddev and ddev-worktree wrappers.

`stylelint_check`/`stylelint_fix` and `prettier_check`/`prettier_fix` on `js-admin-tooling`, and `stylelint_check`/`stylelint_fix` and `eslint_check`/`eslint_fix` on `js-storefront-tooling`, routed a call with `paths` to `stylelint:base`, `prettier:base`, `stylelint:app`, `eslint:app` and `eslint:components`. No Shopware `package.json` defines these scripts, so every such call refused. They now run `npm exec --no -- <binary>` with the flags of the aggregate script, after checking that `node_modules/.bin/<binary>` exists, because `npm exec --no` falls back to a global binary.

Directory paths reach Stylelint and Prettier as a quoted pattern limited to the extensions the aggregate script lints, and are refused under ddev on a linked worktree. Components-tree ESLint paths are refused under a scope, Storefront Stylelint adds the core `--config` only without a scope, and a scoped config on a run without `paths` is refused when the script already passes one.

Tests answer script probes from the `scripts` blocks of Shopware trunk instead of fixtures that defined the missing scripts.

Co-Authored-By: Claude <noreply@anthropic.com>
The Administration and Storefront PreToolUse hooks matched only `npx <binary>` without flags, so `npm exec eslint`, `npm x eslint`, `node_modules/.bin/eslint`, `npx eslint@9` and `npm exec --no -c "cd ../.. && eslint …"` ran the tools past the MCP servers. `BINARY_INVOCATION` now covers `npx`, `npm exec` and `npm x` with the valueless flags `-y`, `--yes`, `--no`, `--no-install`, `-q`, `--quiet`, `--silent` and `--`, an `@version` suffix, and the `node_modules/.bin/` path. `runs_binary` also matches a `-c` or `--call` string that runs the binary. Both apply to `eslint`, `stylelint`, `prettier`, `jest`, `tsc` and `vitest`.

`docs/mcp-enforcement.md` drops the claim that both `package.json` files declare `jest:base`, and documents that the hooks match each line on its own, with `git commit -F <file>` as the way around it.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 11, 2026 •

Copy link
Copy Markdown

The local-binary probe, the directory detection and patterns, the ddev-worktree refusal and the Stylelint `--config` detector were copied into three or four JS server libraries, and the hook binary matcher into both JS hooks. They now live once in the new `shared/js_paths.sh` and `hooks/scripts/lib/js_binary.sh`, which only dev-tooling ships. `environment.sh` and the hook library `common.sh` stay untouched, because they are template copies that shopware-env also ships. The refusals of the shared pipeline name the tool: "Refusing to run `<tool>` with paths".

Tests generate the hook rows from one form list, run the missing-binary probe through every environment wrapper, default to the trunk script layout with `jest:base` as an opt-in, and check the guard against the call log. The trunk fixtures get a README and a `regenerate.sh` that record their source commit.

Co-Authored-By: Claude <noreply@anthropic.com>
The 4.1.1 entry named `prettier` and `tsc` as blocked by both hooks, but only the Administration hook blocks them. `mcp-enforcement.md` lists the blocked binary forms instead of one long paragraph and links to `reference.md` for routing, `reference.md` explains the ddev-worktree refusal once, and `AGENTS.md` lists `shared/js_paths.sh` and `hooks/scripts/lib/js_binary.sh`.

Co-Authored-By: Claude <noreply@anthropic.com>
@SpiGAndromeda
Martin Bens (SpiGAndromeda) merged commit 7062fec into main Oct 11, 2026
13 checks passed
@SpiGAndromeda
Martin Bens (SpiGAndromeda) deleted the fix/dev-tooling-js-path-scoped-npm-exec branch October 11, 2026 21:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant