Skip to content

Idea: Clean up root package.json and convert tools and tests to workspace packages #75041

Description

@manzoorwanijk

Benefits

Converting tools and test directories to workspace packages provides several key advantages:

  1. Separation of Concerns: Each tool/test suite declares its own dependencies, making it clear what each component needs to function.

  2. Cleaner Root package.json: Reduces the root package.json from 81 devDependencies to only essential shared tools (ESLint, Stylelint, Husky, TypeScript, etc.).

  3. Improved Maintainability: Changes to one tool's dependencies don't require updating the root configuration, reducing merge conflicts and simplifying updates.

  4. Better Dependency Visibility: Makes it easier to audit and understand what dependencies are used by each tool, improving security review processes.

  5. Prepares for Stricter Dependency Isolation: With npm, dependencies get hoisted to the root node_modules, so workspaces can accidentally access dependencies from anywhere in the monorepo ("phantom dependencies"). By moving dependencies to workspaces that need them and cleaning up root, we make dependency relationships explicit. This is good practice now and becomes even more impactful when we later migrate to pnpm, where workspaces can only access their own declared dependencies plus the monorepo root - making a clean root the only way to fully prevent phantom dependencies.


Already Converted (Reference)

Merged:


Conversion Pattern

Each workspace conversion follows this pattern (established in PR #74640):

  1. Create package.json in the target directory with:

    • Name: @wordpress/<workspace-name>
    • "private": true for non-published packages
    • Only required devDependencies
    • Workspace-specific scripts
  2. Create/update tsconfig.json extending ../../tsconfig.base.json

  3. Update root package.json:

    • Add to "workspaces" array
    • Update scripts to use npm run --workspace @wordpress/<name> syntax
    • Remove migrated dependencies (once all consumers are converted)
  4. Update root tsconfig.json with project reference

  5. Update CI workflows to use workspace-scoped commands


I asked Claude Code to come up with a phased plan for the conversion. Here is what it came up with:

Phase 1: Test Infrastructure

1.1 Unit Tests Workspace

1.2 Native Tests Workspace

1.3 Integration Tests Workspace


Phase 2: Documentation Tools

2.1 API Documentation Generator


Phase 3: Release & Plugin Tools

3.1 Plugin CLI Tools

3.2 Cherry-Pick Tool

3.3 PHP Sync Issue Generator


Phase 4: Build & Validation Tools

4.1 Build Tools

4.2 Validation Tools


Phase 5: ESLint Configuration

5.1 ESLint Tools


Phase 6: Miscellaneous Tools

6.1 Create Block Test

Current State:

  • bin/test-create-block.sh - Shell script for testing create-block
  • CI: create-block.yml workflow

Tasks:

  • Move bin/test-create-block.sh → tools/validation/test-create-block.mjs. Use child_process to invoke npm/bins via npm exec --no -- <bin> (no hardcoded paths). Wire it up:
  • tools/validation/package.json: add @wordpress/create-block (file:../../packages/create-block) devDep and "test-create-block": "node ./test-create-block.mjs" script.
  • Root package.json: "test:create-block": "npm run --workspace @wordpress/validation-tools test-create-block --".
  • .github/workflows/create-block.yml: run: npm run test:create-block.

6.2 Performance Results Logger


Phase 7: Cleanup & Finalization

7.1 Root Package.json Cleanup

Tasks

7.2 Dependencies to Keep in Root

Let us aim to remove as many dependencies from the root as possible.

Only truly essential dependencies that must be at root level:
  • husky - Git hooks require root installation
  • lint-staged - Pre-commit linting runs from root
  • lerna - Monorepo management
  • patch-package - Post-install patches run from root
  • eslint - Core linting tool used repo-wide
  • stylelint - Core style linting tool used repo-wide
  • typescript - Type checking used across all packages

Move to Workspaces:

  • @types/* - Move to specific workspaces that need them
  • prettier - Move to ESLint/formatting workspace
  • Build tools (sass, babel-*, loaders) - Move to build workspace
  • concurrently, wait-on, npm-run-all - Move to workspaces that use them

Verification

After all workspaces are created and verified:

7.3 CI Workflow Updates Summary

Workflows that need updates:

  • unit-test.yml - Use @wordpress/unit-tests workspace
  • static-checks.yml - Use validation tools workspace
  • performance.yml - Use release tools workspace
  • publish-npm-packages.yml - Use release tools workspace
  • create-block.yml - Potentially update paths

7.4 Documentation

Done via #78615

  • Update contributing documentation, especially highlighting to create workspaces in tools if something needs for the repo, instead of adding dependencies to the root.
  • Add workspace development guide
  • Update CI documentation

Implementation Order

Follow the phases sequentially:

  1. Phase 1: Test Infrastructure (Unit, Native, Integration tests)
  2. Phase 2: Documentation Tools (API docs generator)
  3. Phase 3: Release & Plugin Tools (CLI, cherry-pick, PHP sync)
  4. Phase 4: Build & Validation Tools
  5. Phase 5: ESLint Configuration
  6. Phase 6: Miscellaneous Tools
  7. Phase 7: Cleanup & Finalization

Notes

  • Each phase can be implemented as a separate PR for easier review
  • Test thoroughly in CI before merging
  • Keep backward compatibility during transition (old script paths can delegate to new workspaces)
  • Monitor CI performance after each change

Related PRs

Activity

  1. manzoorwanijk commented on Jan 29, 2026

    @manzoorwanijk
    MemberAuthor
  2. USERSATOSHI commented on Apr 2, 2026

    @USERSATOSHI
    Contributor

    Hi, would it be okay if I work on @wordpress/unit-tests?

  3. youknowriad commented on Apr 2, 2026

    @youknowriad
    Contributor

    Just noting that there's already an e2e-tests package, it used to contain the puppeteer tests before they get removed and replaced by the top level playwright tests. So this reverts the direction here, we can just reuse that package again.

    Overall, I think this is a good direction but I'd start by focusing on the things that are really tools (like docs generation tool...). It's a bit more unclear to me when it comes to the actual tests.

  4. USERSATOSHI commented on Apr 2, 2026

    @USERSATOSHI
    Contributor

    I see, in that case, starting with tools/api-docs sounds like a good approach.

  5. USERSATOSHI commented on Apr 2, 2026

    @USERSATOSHI
    Contributor

    Hi, I started converting bin/api-docs into a workspace package.

    It looks like we need to explicitly add all the dependencies used there into its package.json—even ones like chalk, execa, etc., which are already present at the root.

    Should I go ahead and add them to make the checks pass, or is there a preferred way to handle shared dependencies in this setup?

  6. manzoorwanijk commented on Apr 2, 2026

    @manzoorwanijk
    MemberAuthor

    Hi, I started converting bin/api-docs into a workspace package.

    It looks like we need to explicitly add all the dependencies used there into its package.json—even ones like chalk, execa, etc., which are already present at the root.

    Should I go ahead and add them to make the checks pass, or is there a preferred way to handle shared dependencies in this setup?

    If a workspace uses any dependency, it must be declared in its package.json

  7. manzoorwanijk commented on Apr 2, 2026

    @manzoorwanijk
    MemberAuthor

    Overall, I think this is a good direction but I'd start by focusing on the things that are really tools (like docs generation tool...). It's a bit more unclear to me when it comes to the actual tests.

    The main goal here is to avoid workspace package having access to root deps when we implement the isolated dependencies via #75814 or via pnpm adoption.

  8. kushagra-goyal-14 commented on Jun 5, 2026

    @kushagra-goyal-14
    Contributor

    Hi @manzoorwanijk , I was checking - Move react related types out of root and had some questions. The diff in the issue description targets React 19 (^19.2.4), but since #78940 reverted the React 19 upgrade, trunk is back at 18. Should we adapt to 18 version?

  9. manzoorwanijk commented on Jun 5, 2026

    @manzoorwanijk
    MemberAuthor

    since #78940 reverted the React 19 upgrade, trunk is back at 18. Should we adapt to 18 version?

    Yes, we need to adapt according to the current React version used by the repo.

    There is also that "Move babel deps out of root" task, which is independent of that React version.

  10. kushagra-goyal-14 commented on Jun 5, 2026

    @kushagra-goyal-14
    Contributor

    @manzoorwanijk While adapting the React diff to v18, I found two entries from the issue's diff that don't seem to apply to current trunk:

    1. packages/private-apis — the diff adds react as a devDependency, but I couldn't find any react usage in the package (src, tests, or types).
    2. tools/build-scripts — the diff adds @wordpress/element as a dependency, but the only reference in build-vendors.mjs is a commented-out line (// ' ...require("@wordpress/element/react-polyfill"),').

    Should I leave both of these out, or is there a reason to include them?

  11. manzoorwanijk commented on Jun 5, 2026

    @manzoorwanijk
    MemberAuthor

    Should I leave both of these out, or is there a reason to include them?

    Yes, we can leave those out.

  12. kushagra-goyal-14 commented on Jun 7, 2026

    @kushagra-goyal-14
    Contributor

    Hi @manzoorwanijk, I was looking into the routes/lock-unlock/route.ts task and had a small question about the file name. The task mentions routes/lock-unlock/route.ts, but since lock-unlock isn't actually a route (it just re-exports lock/unlock for the routes group), I think index.ts might be a better fit than route.ts.

    From what I can see in the build, getRouteFiles() treats any file named route.{ext} as a route entry and esbuild-bundles it into a route.min.js (+ asset file), even though the router's manifest step skips the folder since its package.json has no route field. So it will create a build that won't get used.

    Using index.ts should avoid that.

    Does that sound reasonable, or am I missing something?

    Also noticed react-scanner (the component-usage-stats script + react-scanner.config.js) still lives in root and isn't in the task list. Could it be folded into tools/validation? I also noticed: the config's crawlFrom: './', the exclude list, and outputTo: './results/gutenberg.json' are all relative to repo root, so they'd need adjusting (or running with the repo root as cwd) after the move. Happy to take it on if it makes sense.

  13. manzoorwanijk commented on Jun 8, 2026

    @manzoorwanijk
    MemberAuthor

    Does that sound reasonable, or am I missing something?

    @kushagra-goyal-14 that makes sense. Let us do that.

  14. USERSATOSHI commented on Jun 11, 2026

    @USERSATOSHI
    Contributor

    @manzoorwanijk, I accidentally got assigned since I forgot to change closes to related in PR desc. Can you unassign me?

  15. kushagra-goyal-14 commented on Jun 12, 2026

    @kushagra-goyal-14
    Contributor

    Hi @manzoorwanijk , I checked the "Move glob out of root" task and have a question.

    The task says to add "glob": "^13.0.6" to dependency-extraction-webpack-plugin and readable-js-assets-webpack-plugin. That part works fine as both only use require( 'glob' ).sync, which v13 still supports, and their tests pass.

    However, four other workspaces also declare glob at ^7.1.2 (tools/docs, tools/eslint, tools/validation, test/unit), so npm run lint:deps (syncpack) fails with a version mismatch once the two packages move to v13. I checked more and it seems those are not just version changes-

    • tools/docs/gen-components-docs/index.mjs and tools/validation/validate-tsconfig.mjs use import glob from 'glob' — v9+ removed the default export, so these need import { globSync } from 'glob'.
    • glob v7 sorted results alphabetically; v13 returns them unsorted. tools/docs/manifest.cjs builds the docs manifest from glob results, so it would need an explicit .sort() to stay deterministic.

    Should I bump all to V13? Any suggestions?

  16. manzoorwanijk commented on Jun 12, 2026

    @manzoorwanijk
    MemberAuthor

    Let us stick to the version in root and do the version upgrade later.

  17. manzoorwanijk commented on Jun 16, 2026

    @manzoorwanijk
    MemberAuthor

    We are pretty close to wrapping this up. Only three remaining items left. Thank you, everyone, for your contribution.

  18. manzoorwanijk commented on Jun 16, 2026

    @manzoorwanijk
    MemberAuthor

    Once we complete those items, we can then enable ship #78616 as a safety guard.

  19. manzoorwanijk commented on Jun 16, 2026

    @manzoorwanijk
    MemberAuthor

    I had another look at the root deps, I think we can get rid of @wordpress/vips from the root package.json. It doens't need to be moved anywhere. It's already where it needs to be.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions