Skip to content

fix: repair pnpm run lint and gate it in CI - #107

Merged
JohnMcLear merged 1 commit into
mainfrom
fix/lint-typescript-pin
Sep 20, 2026
Merged

JohnMcLear merged 1 commit into
mainfrom
fix/lint-typescript-pin

Conversation

@JohnMcLear

Copy link
Copy Markdown
Member

The bug

pnpm run lint fails on the default branch of this repo (and of ~80 other ether/ep_* plugins). Before this PR:

$ pnpm run lint
Oops! Something went wrong! :(
ESLint: 8.57.1
TypeError: Failed to load plugin '@typescript-eslint' declared in
'.eslintrc.cjs » eslint-config-etherpad/plugin#overrides[0] » ./node.js » ./index.js#overrides[0]':
Cannot read properties of undefined (reading 'Intrinsic')
    at .../ts-api-utils@1.4.3_typescript@7.0.2/node_modules/ts-api-utils/lib/index.cjs:779:57

Root cause

Dependabot bumped typescript to ^7.0.2. TypeScript 7 is the native (Go) port and no longer exposes the legacy compiler API surface, so ts.TypeFlags is undefined. ts-api-utils evaluates this at module load:

var IntrinsicTypeFlags = ts.TypeFlags.Intrinsic ?? ...

…which throws, taking @typescript-eslint — and therefore all of eslint-config-etherpad — down with it. ESLint exits 2 without linting a single file.

No released @typescript-eslint supports TypeScript 7. 8.70.0 and every 8.70.1-alpha.* declare typescript: ">=4.8.4 <6.1.0". So this cannot be fixed by upgrading the lint toolchain today; typescript has to be held inside the supported range.

The fix

Pin typescript to ~6.0.3 — the newest line @typescript-eslint supports, and an exact match for its <6.1.0 ceiling.

Two alternatives were tried and do not work:

Attempt Result
Remove typescript from devDependencies (this is a JS plugin) ❌ Still broken. @typescript-eslint/typescript-estree declares typescript as an unversioned optional peer, so pnpm auto-installs the latest (7.0.2) anyway. pnpm overrides/packageExtensions don't redirect peers either.
pnpm.overridests-api-utils: ^2.5.0 ❌ Still broken. 2.5.0 reads the same missing ts.TypeFlags.Intrinsic (just at line 787).

The lockfile shrinks because TypeScript 7 ships ~20 per-platform native binary packages (@typescript/typescript-linux-x64, -darwin-arm64, …) that TypeScript 6 doesn't have. No unrelated packages change.

Why it rotted: lint ran in no CI job

That's the real defect — the breakage sat on main unnoticed. This PR adds a reusable .github/workflows/lint.yml and wires it into test-and-release.yml as a third job that release depends on:

jobs:
  lint:
    uses: ./.github/workflows/lint.yml
    secrets: inherit
  backend: ...
  frontend: ...
  release:
    needs:
      - lint
      - backend
      - frontend

Because automerge.yml only auto-merges Dependabot PRs when the Node.js Package run succeeds, a future dependency bump that breaks lint now turns that run red and is held back instead of landing silently. No Dependabot ignore rule is added — CI is the gate.

Second failure shape in this repo: eslint 10

This repo had also been bumped to eslint: ^10.10.0, which fails differently:

ESLint: 10.10.0
ESLint couldn't find an eslint.config.* file.
From ESLint v9.0.0, the default configuration file is now eslint.config.*.

eslint-config-etherpad@4.0.5 ships only eslintrc-format configs (index.js, node.js, browser.js, plugin.js) and has no flat-config entry point, so eslint 9/10 cannot consume it at all. This PR moves eslint back to ^8.57.1, matching the ~78 sibling plugins. Migrating the org to flat config needs a new eslint-config-etherpad major first — tracked separately, not in this PR.

Verification

$ pnpm install --ignore-scripts && pnpm run lint
> ep_git_commit_saved_revision@11.0.30 lint
> eslint .

✖ 7 problems (0 errors, 7 warnings)

$ echo $?
0

Lint now runs and exits 0. The 7 remaining items are warnings only (camelcase, no-use-before-define), which eslint-config-etherpad deliberately sets to warn rather than error. They are pre-existing and are left alone here so this PR stays a build fix rather than a code rewrite.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Toh2QEdVZd7WsTdua9qYL2

`pnpm run lint` has been broken on the default branch. Dependabot bumped
`typescript` to ^7.0.2 (the native port), which no longer exposes the legacy
compiler API. `ts-api-utils` reads `ts.TypeFlags.Intrinsic` at module load,
`ts.TypeFlags` is now undefined, and eslint dies before linting anything:

    TypeError: Failed to load plugin '@typescript-eslint' declared in
    '.eslintrc.cjs » eslint-config-etherpad/plugin ...':
    Cannot read properties of undefined (reading 'Intrinsic')

No released `@typescript-eslint` supports TypeScript 7 — 8.70.0 and its
alphas all declare `typescript: ">=4.8.4 <6.1.0"` — so the fix is to hold
`typescript` inside that supported range. `~6.0.3` is the newest line that
works and matches the upstream ceiling exactly.

Removing `typescript` from devDependencies does NOT work: `@typescript-eslint/
typescript-estree` declares `typescript` as an unversioned *optional peer*, so
pnpm just auto-installs the latest (7.0.2) and the same crash returns. The
version has to be pinned explicitly.
Also drops eslint from ^10.10.0 back to ^8.57.1. eslint-config-etherpad
4.0.5 ships only eslintrc-format configs (index.js / node.js / browser.js /
plugin.js); it has no flat-config entry point, so eslint 10 aborts with
"ESLint couldn't find an eslint.config.* file" before it lints anything.

Lint ran in no CI job in this repo, which is why this rotted unnoticed. Adds a
reusable `lint.yml` and wires it into `test-and-release.yml` as a third job
that `release` depends on, so a future dependency bump that breaks lint turns
the "Node.js Package" run red and is not auto-merged.

`pnpm run lint` now exits 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Toh2QEdVZd7WsTdua9qYL2
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Restore ESLint compatibility and enforce linting in CI

🐞 Bug fix ⚙️ Configuration changes 🕐 20-40 Minutes

Grey Divider

AI Description

• Restores linting with ESLint 8 and TypeScript 6 versions supported by Etherpad tooling.
• Adds reusable CI lint validation with pnpm caching and deterministic dependency installation.
• Blocks releases and Dependabot automerge when lint validation fails.
Diagram

graph TD
  A["GitHub Event"] --> B["Test and Release"] --> C["Lint Workflow"] --> D["package.json"] --> E["ESLint"]
  B --> F["Backend Tests"] --> H["Release Gate"]
  B --> G["Frontend Tests"] --> H
  E --> H
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Remove explicit TypeScript dependency
  • ➕ Avoids pinning TypeScript in a JavaScript-only plugin.
  • ➕ Reduces direct development dependencies.
  • ➖ pnpm auto-installs TypeScript 7 through the unversioned optional peer.
  • ➖ The @typescript-eslint module-load failure remains unresolved.
2. Override ts-api-utils
  • ➕ Would preserve the newer TypeScript dependency if compatibility existed.
  • ➕ Limits the workaround to the failing transitive package.
  • ➖ Current ts-api-utils releases still access the removed TypeFlags API.
  • ➖ Creates a fragile transitive override without restoring compatibility.
3. Migrate to ESLint flat config
  • ➕ Enables supported ESLint 9 or 10 releases.
  • ➕ Provides a durable path beyond legacy eslintrc configuration.
  • ➖ Requires a new eslint-config-etherpad major with flat-config exports.
  • ➖ Expands this focused repair into an organization-wide migration.

Recommendation: Keep the explicit TypeScript 6 and ESLint 8 constraints and enforce linting through CI. This is the smallest working repair until eslint-config-etherpad supports flat config and @typescript-eslint supports TypeScript 7.

Files changed (4) +403 / -507

Bug fix (1) +2 / -2
package.jsonPin lint tooling to compatible versions +2/-2

Pin lint tooling to compatible versions

• Downgrades ESLint to the eslintrc-compatible 8.57 line and pins TypeScript to the supported 6.0 line. This prevents ESLint 10 configuration failures and @typescript-eslint crashes against TypeScript 7.

package.json

Other (3) +401 / -505
lint.ymlAdd reusable ESLint workflow +35/-0

Add reusable ESLint workflow

• Adds a workflow-callable lint job using Node.js 25 and pnpm 10. It caches the pnpm store, installs dependencies without lifecycle scripts, and runs the existing lint command.

.github/workflows/lint.yml

test-and-release.ymlRequire lint success before release +4/-0

Require lint success before release

• Invokes the new lint workflow alongside backend and frontend tests. Adds lint to the release job dependencies so failed lint validation blocks publishing and associated automerge flows.

.github/workflows/test-and-release.yml

pnpm-lock.yamlResolve the compatible lint dependency graph +362/-505

Resolve the compatible lint dependency graph

• Regenerates the lockfile for ESLint 8.57.1 and TypeScript 6.0.3, including updated peer snapshots and transitive packages. Removes TypeScript 7's platform-specific native packages and the ESLint 10 dependency graph.

pnpm-lock.yaml

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Sep 20, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Compromised lint tools can alter code 🐞 Bug ⛨ Security
Description
The lint reusable job runs checkout, dependency installation, and ESLint while inheriting the
caller's workflow-wide contents: write and id-token: write permissions. A compromised action,
ESLint configuration, or loaded lint dependency therefore executes with publishing-oriented
privileges even though this job only needs repository read access.
Code

.github/workflows/test-and-release.yml[R17-19]

+  lint:
+    uses: ./.github/workflows/lint.yml
+    secrets: inherit
Evidence
The parent workflow grants contents: write and id-token: write globally, while the newly added
caller neither narrows those permissions nor avoids secret inheritance. The called workflow checks
out the repository, installs packages, and loads the lint toolchain under those inherited
privileges.

.github/workflows/test-and-release.yml[11-19]
.github/workflows/lint.yml[11-17]
.github/workflows/lint.yml[32-35]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The reusable lint job inherits the publishing workflow's write and identity-token permissions despite only requiring repository read access, unnecessarily increasing the impact of a compromised lint action or dependency.
## Fix Focus Areas
- .github/workflows/test-and-release.yml[17-19]
## Recommended Fix
Add a job-level `permissions` block with only `contents: read` to the `lint` caller job, which implicitly disables `id-token: write`, and remove `secrets: inherit` because the lint workflow does not consume repository secrets.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can turn on the rule miner and Qodo learns your standards from review history

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment on lines +17 to +19
lint:
uses: ./.github/workflows/lint.yml
secrets: inherit

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

1. Compromised lint tools can alter code 🐞 Bug ⛨ Security

The lint reusable job runs checkout, dependency installation, and ESLint while inheriting the
caller's workflow-wide contents: write and id-token: write permissions. A compromised action,
ESLint configuration, or loaded lint dependency therefore executes with publishing-oriented
privileges even though this job only needs repository read access.
Agent Prompt
## Issue description
The reusable lint job inherits the publishing workflow's write and identity-token permissions despite only requiring repository read access, unnecessarily increasing the impact of a compromised lint action or dependency.

## Fix Focus Areas
- .github/workflows/test-and-release.yml[17-19]

## Recommended Fix
Add a job-level `permissions` block with only `contents: read` to the `lint` caller job, which implicitly disables `id-token: write`, and remove `secrets: inherit` because the lint workflow does not consume repository secrets.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@qodo-code-review

Copy link
Copy Markdown

ⓘ Qodo reviews are paused because the subscription is no longer active. Ask your workspace admin to reactivate the subscription to resume reviews. Manage billing

@JohnMcLear
JohnMcLear merged commit 01d4b79 into main Sep 20, 2026
4 checks passed
@JohnMcLear
JohnMcLear deleted the fix/lint-typescript-pin branch September 20, 2026 17:32
This was referenced Sep 20, 2026
This was referenced Sep 20, 2026
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