Skip to content

Instantly share code, notes, and snippets.

@turadg
Created March 19, 2026 20:37
Show Gist options
  • Select an option

  • Save turadg/6d100f8a851a22c6f951bd5d32d3dd93 to your computer and use it in GitHub Desktop.

Select an option

Save turadg/6d100f8a851a22c6f951bd5d32d3dd93 to your computer and use it in GitHub Desktop.
Vite+ Adoption Plan for Endo Monorepo

Vite+ Adoption Plan for Endo Monorepo

Context

The Endo monorepo currently uses Yarn 4 (Berry) + yarn workspaces foreach for task orchestration, Prettier for formatting, ESLint with a custom @endo/eslint-plugin for linting, AVA for testing, and tsc for builds. The goal is to adopt Vite+ incrementally to modernize the toolchain while preserving Endo's unique requirements (SES lockdown, compartment-mapper bundling, Hardened JS constraints).

What we're adopting: oxfmt, Oxlint (running @endo/eslint-plugin via JS plugin API), vp run (task runner), vp as package manager layer (wrapping Yarn), Vite file graph for affected test selection. What we're keeping: AVA (test framework), tsc (type definitions), SES custom bundler, Yarn 4 (under vp), @endo/eslint-plugin as a published ESLint plugin (for downstream consumers). What we're NOT adopting: Vitest, Rolldown. What we're dropping: Prettier (replaced by oxfmt), @jessie.js/eslint-plugin enforcement, type-aware lint rules (restrict-comparison-operands, restrict-plus-operands).


Phase 1: Replace Prettier with oxfmt (Low Risk)

Why first: Zero runtime impact, independently shippable, and unblocks Phase 2 (removing eslint-config-prettier from ESLint configs).

Steps

  1. Add oxfmt as a devDependency. Create config (standalone .oxfmtrc.json initially; moves to vite.config.ts in Phase 3):

    { "arrowParens": "avoid", "trailingComma": "all", "singleQuote": true }
  2. Convert .prettierignore to oxfmt's ignore mechanism. Current ignores:

    • Directories: .cache, bundles, dist, fixtures, tmp, test262
    • Paths: packages/ses-integration-test/transform-tests/output, packages/test262-runner/prelude
    • Extensions: *.html, *.json (except typedoc.json), *.md
  3. Update root scripts in package.json:

    • lint:prettier -> lint:fmt invoking oxfmt check
    • format -> oxfmt write
    • lint -> yarn lint:fmt && yarn lint:eslint
  4. Update per-package scripts:

    • packages/marshal/package.json: pretty-fix, pretty-check -> oxfmt equivalents
    • packages/compartment-mapper/package.json: prettier-fixtures -> oxfmt equivalent
  5. Remove Prettier:

    • Delete .prettierrc.json
    • Remove prettier key from root package.json
    • Remove prettier from devDependencies
    • Remove eslint-config-prettier from devDependencies (and from each sub-package that lists it)
    • Remove 'prettier' from extends in packages/eslint-plugin/lib/configs/internal.js:45 and packages/eslint-plugin/lib/configs/style.js
    • Keep packages/marshal/.prettierignore -> convert to oxfmt ignore or delete if covered by root
  6. Bulk reformat the entire repo and commit. Add the commit SHA to .git-blame-ignore-revs.

  7. Update CI: ci.yml lint job runs yarn lint which now calls oxfmt. No structural change needed.

Key files

  • /.prettierrc.json (delete)
  • /.prettierignore (convert or replace)
  • /package.json (scripts + deps)
  • /packages/eslint-plugin/lib/configs/internal.js:45 (remove 'prettier' extend)
  • /packages/eslint-plugin/lib/configs/style.js (remove 'prettier' extend)
  • /.github/workflows/ci.yml

Phase 2: Replace ESLint with Oxlint (Medium Risk)

Why: Oxlint's March 2026 JS Plugins Alpha supports ESLint-compatible plugin APIs including AST traversal, scope analysis, control flow, and sourceCode APIs. Most existing plugins should work.

Dependency: Phase 1 must complete first (removes eslint-config-prettier).

Decisions:

  • Drop @jessie.js/eslint-plugin entirely — its processor (@jessie.js/use-jessie) is not needed, and Oxlint may not support ESLint processors.
  • Drop type-aware rulesrestrict-comparison-operands and @typescript-eslint/restrict-plus-operands are removed rather than keeping a residual ESLint. This means ESLint can be fully removed.

2a: Port custom rules to Oxlint's JS plugin API

Port 5 of the 6 custom @endo/eslint-plugin rules (the 6th, restrict-comparison-operands, is dropped):

Rule API needs Oxlint support Risk
no-polymorphic-call Simple AST pattern match Yes Low
no-multi-name-local-export Export specifier tracking Yes Low
harden-exports AST walk + auto-fix Yes Medium
no-assign-to-exported-let-var-or-function Scope analysis, getDeclaredVariables Yes Medium
assert-fail-as-throw CodePathAnalyzer hooks Yes (control flow API) High

Action: Run the existing test suite (packages/eslint-plugin/test/) against Oxlint's JS plugin runner for each rule. Fix any API incompatibilities. Port in order of increasing complexity.

2b: Map third-party plugin rules to Oxlint built-ins

  • eslint-plugin-jsdoc: Extended via style.js config. Oxlint has JSDoc rules built-in; map each enabled rule to its Oxlint equivalent.
  • eslint-config-airbnb-base: Extended via style.js. Most rules map to Oxlint's 650+ built-ins. Identify any gaps and decide whether to drop or reimplement.
  • @typescript-eslint/naming-convention (used in internal.js:51 for PascalCase interfaces): Check if Oxlint has equivalent.

2c: Port config presets to Oxlint

Convert the ESLint config presets to Oxlint config equivalents. Drop presets that are no longer needed:

  • internal (most packages): Strip jessie.js + prettier extends. Port strict rules + typescript-eslint naming convention to Oxlint config.
  • ses (security-critical packages): no-restricted-globals (75+ globals) is a standard rule already in Oxlint. Add no-polymorphic-call from custom plugin.
  • recommended: Remove jessie.js extends + processor. Keep custom rule enables and SES-safe globals list.
  • Drop recommended-requiring-type-checking (only contained the dropped type-aware rule).
  • Drop daemon (already deprecated, just aliases internal).

2d: Migrate all packages

Update eslintConfig in each package's package.json to reference Oxlint configs. Update lint:eslint scripts to invoke oxlint.

2e: Keep @endo/eslint-plugin intact for downstream consumers

Important: @endo/eslint-plugin must continue to work as an ESLint plugin. Downstream consumers (e.g. Agoric SDK) may still use ESLint. Changes to the plugin should be minimal:

  • Remove 'prettier' from extends (already done in Phase 1)
  • Remove @jessie.js extends + processor from recommended.js
  • Remove the restrict-comparison-operands rule and recommended-requiring-type-checking config
  • All other rules and configs remain functional ESLint configs

The Endo monorepo itself switches its lint:eslint scripts to invoke oxlint (which runs ESLint plugins via its JS plugin API), but @endo/eslint-plugin remains a published ESLint plugin package.

2f: Switch Endo's lint invocation from ESLint to Oxlint

  • Replace eslint CLI calls in root and per-package lint:eslint scripts with oxlint
  • Configure Oxlint (.oxlintrc.json or vite.config.ts lint block) to load @endo/eslint-plugin via jsPlugins
  • Remove ESLint as a direct devDependency of the root package (Oxlint runs the plugin natively)
  • Keep eslint as a devDependency of @endo/eslint-plugin itself (for its own tests)

Key files

  • /packages/eslint-plugin/ (minimal changes — keep as ESLint plugin)
  • /packages/eslint-plugin/lib/configs/internal.js (remove prettier extend)
  • /packages/eslint-plugin/lib/configs/ses.js (no change)
  • /packages/eslint-plugin/lib/configs/recommended.js (remove jessie.js)
  • /package.json (swap eslint -> oxlint in devDependencies, update scripts)
  • Every packages/*/package.json (update lint scripts to use oxlint; keep eslintConfig for plugin compatibility)

Phase 3: Adopt Vite+ Task Runner — vp run (Medium Risk)

Why: Replaces yarn workspaces foreach with dependency-aware cached execution. Automatic cache invalidation (no manual config like Turborepo).

Can run in parallel with Phase 2.

Steps

  1. Install vite-plus (the vp CLI). Add to devDependencies.

  2. Create vite.config.ts at repo root (if not already created in Phase 1). Move oxfmt config into the fmt block. Add task runner config.

  3. Replace root scripts one at a time:

    • build: yarn workspaces foreach --all run build -> vp run build
    • test: yarn workspaces foreach --all --exclude @endo/skel run test -> vp run test (verify exclusion mechanism)
    • test:c8, test:xs, test262 -> vp run test:c8 etc.
    • lint:workspaces -> vp run lint
    • lint:workspaces:eslint -> vp run lint:eslint
  4. Replace Lerna task execution in CI:

    • viable-release job uses yarn lerna run --reject-cycles --concurrency 1 prepack
    • Replace with vp run prepack with topological ordering + concurrency limit
    • Note: Lerna is also used for version management and publishing (lerna publish from-package). That functionality is orthogonal to the task runner and can stay on Lerna.
  5. Validate caching: Modify a file in @endo/pass-style, run vp run test, confirm only downstream packages re-test.

  6. Update CI workflows:

    • ci.yml: Replace all yarn workspaces foreach invocations
    • depcheck.yml: Replace workspace iteration
    • Keep yarn commands for install (until Phase 5)

Key files

  • /package.json (scripts)
  • /vite.config.ts (new — task runner + fmt config)
  • /.github/workflows/ci.yml
  • /.github/workflows/release.yml
  • /.github/workflows/depcheck.yml
  • /lerna.json (keep for versioning; remove task-running usage)

Phase 4: Vite File Graph for Affected Test Selection (High Risk)

Why: Highest-value change — skip unaffected tests in CI. Currently ALL tests run on every PR.

Dependency: Phase 3 (needs vp run for orchestration).

Steps

  1. Create a Vite plugin for compartment-mapper module resolution.

    The plugin hooks into Vite's resolveId and load to understand Endo's module graph:

    • Standard ESM imports (the majority of packages) — Vite handles natively
    • @endo/compartment-mapper resolution — custom logic in packages/compartment-mapper/src/node-modules.js and src/search.js
    • The plugin doesn't run compartment-mapper at build time; it traces which source files contribute to compartment-mapper bundles (used in test fixtures)

    Key source files to understand:

    • packages/compartment-mapper/src/compartment-map.js (560 lines — builds compartment map descriptors)
    • packages/compartment-mapper/src/import-hook.js (30KB — module resolution hook)
    • packages/compartment-mapper/src/node-modules.js (walks node_modules)
  2. Build affected-test mapping. For each test file, trace transitive imports via the Vite module graph. Map changed files -> affected test files.

  3. Integrate with vp run test.

    • Compute changed files: git diff origin/master...HEAD --name-only
    • Query Vite module graph for affected test files
    • Pass only affected test files to AVA (AVA supports file list arguments)
    • Handle the SES package specially: SES is a near-universal dependency, so changes to packages/ses/src/ should invalidate all downstream tests
  4. CI integration:

    • PR builds: Run only affected tests
    • master push: Run full test suite as safety net
    • Add --affected flag or equivalent to vp run test

Fallback

Start with standard ESM import tracing only (covers ~90% of packages). Add compartment-mapper-aware tracing as a follow-up. Even partial affected-test selection is a significant CI speedup.

Key files

  • New: /packages/vite-plugin-compartment-mapper/ or /plugins/compartment-mapper.ts
  • /packages/compartment-mapper/src/node-modules.js
  • /packages/compartment-mapper/src/search.js
  • /packages/compartment-mapper/src/compartment-map.js
  • /.github/workflows/ci.yml

Phase 5: Adopt Vite+ Package Manager Layer (Low-Medium Risk)

Why last: Even though vp wraps Yarn (so this is less disruptive than a full PM migration), it's best done after the task runner is stable.

Dependency: Phase 3 should be stable first.

Key insight: vp wraps Yarn rather than replacing it. Yarn 4, yarn.lock, .yarnrc.yml, catalogs, and workspace:^ all remain. The vp CLI becomes the developer-facing entry point but delegates to Yarn under the hood.

Steps

  1. Configure vp to use Yarn as its underlying package manager. Verify it respects:

    • Yarn catalogs (catalog:dev protocol)
    • nodeLinker: pnpm setting
    • enableScripts: false
    • workspace:^ protocol
    • resolutions field
  2. Update CI workflows to use vp install (which calls yarn install under the hood):

    • Keep cache: yarn in setup-node (same lockfile/cache)
    • Replace yarn install --immutable -> vp install (verify immutable/frozen equivalent)
    • Replace remaining yarn <script> calls -> vp <script> (already partially done in Phase 3)
  3. Update contributor docs (CONTRIBUTING.md, README) with vp commands alongside yarn equivalents during transition.

Key files

  • /package.json (scripts)
  • /.github/workflows/*.yml (all workflows)
  • /CONTRIBUTING.md, /README.md (docs)

Phase Ordering and Parallelism

Phase 1 (oxfmt) ──────> Phase 2 (Oxlint)
      │                       │
      │  [can run in parallel] │
      v                       v
Phase 3 (vp run) ─────────────┘
      │
      v
Phase 4 (affected tests)
      │
      v
Phase 5 (vp package manager)
  • Phases 1 & 3 can start in parallel
  • Phase 2 depends on Phase 1 (prettier removal from ESLint config)
  • Phase 2 & 3 can proceed in parallel with each other
  • Phase 4 depends on Phase 3 (needs vp run)
  • Phase 5 should be last (wraps Yarn with vp, low disruption but benefits from stable task runner)

Verification

Each phase should be verified independently:

  • Phase 1: oxfmt --check . passes; CI lint job green; diff from reformat is style-only
  • Phase 2: oxlint passes on all packages with no ESLint residual; compare lint results against ESLint baseline (run both side-by-side before cutting over) to catch regressions; verify the 5 ported custom rules catch the same violations as their ESLint originals using packages/eslint-plugin/test/
  • Phase 3: vp run test produces same results as yarn workspaces foreach --all run test; caching works (second run is fast with no changes)
  • Phase 4: Change a file in @endo/pass-style, verify only downstream tests run; change a file in packages/ses/src/, verify all tests run
  • Phase 5: vp install delegates to yarn install correctly; Yarn catalogs, nodeLinker, and workspace protocol all work through vp; all CI jobs green
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment