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).
Why first: Zero runtime impact, independently shippable, and unblocks Phase 2 (removing eslint-config-prettier from ESLint configs).
-
Add oxfmt as a devDependency. Create config (standalone
.oxfmtrc.jsoninitially; moves tovite.config.tsin Phase 3):{ "arrowParens": "avoid", "trailingComma": "all", "singleQuote": true } -
Convert
.prettierignoreto 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(excepttypedoc.json),*.md
- Directories:
-
Update root scripts in
package.json:lint:prettier->lint:fmtinvoking oxfmt checkformat-> oxfmt writelint->yarn lint:fmt && yarn lint:eslint
-
Update per-package scripts:
packages/marshal/package.json:pretty-fix,pretty-check-> oxfmt equivalentspackages/compartment-mapper/package.json:prettier-fixtures-> oxfmt equivalent
-
Remove Prettier:
- Delete
.prettierrc.json - Remove
prettierkey from rootpackage.json - Remove
prettierfrom devDependencies - Remove
eslint-config-prettierfrom devDependencies (and from each sub-package that lists it) - Remove
'prettier'fromextendsinpackages/eslint-plugin/lib/configs/internal.js:45andpackages/eslint-plugin/lib/configs/style.js - Keep
packages/marshal/.prettierignore-> convert to oxfmt ignore or delete if covered by root
- Delete
-
Bulk reformat the entire repo and commit. Add the commit SHA to
.git-blame-ignore-revs. -
Update CI:
ci.ymllint job runsyarn lintwhich now calls oxfmt. No structural change needed.
/.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
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-pluginentirely — its processor (@jessie.js/use-jessie) is not needed, and Oxlint may not support ESLint processors. - Drop type-aware rules —
restrict-comparison-operandsand@typescript-eslint/restrict-plus-operandsare removed rather than keeping a residual ESLint. This means ESLint can be fully removed.
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.
eslint-plugin-jsdoc: Extended viastyle.jsconfig. Oxlint has JSDoc rules built-in; map each enabled rule to its Oxlint equivalent.eslint-config-airbnb-base: Extended viastyle.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 ininternal.js:51for PascalCase interfaces): Check if Oxlint has equivalent.
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. Addno-polymorphic-callfrom 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 aliasesinternal).
Update eslintConfig in each package's package.json to reference Oxlint configs. Update lint:eslint scripts to invoke oxlint.
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'fromextends(already done in Phase 1) - Remove
@jessie.jsextends + processor fromrecommended.js - Remove the
restrict-comparison-operandsrule andrecommended-requiring-type-checkingconfig - 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.
- Replace
eslintCLI calls in root and per-packagelint:eslintscripts withoxlint - Configure Oxlint (
.oxlintrc.jsonorvite.config.tslint block) to load@endo/eslint-pluginviajsPlugins - Remove ESLint as a direct devDependency of the root package (Oxlint runs the plugin natively)
- Keep
eslintas a devDependency of@endo/eslint-pluginitself (for its own tests)
/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)
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.
-
Install
vite-plus(thevpCLI). Add to devDependencies. -
Create
vite.config.tsat repo root (if not already created in Phase 1). Move oxfmt config into thefmtblock. Add task runner config. -
Replace root scripts one at a time:
build:yarn workspaces foreach --all run build->vp run buildtest:yarn workspaces foreach --all --exclude @endo/skel run test->vp run test(verify exclusion mechanism)test:c8,test:xs,test262->vp run test:c8etc.lint:workspaces->vp run lintlint:workspaces:eslint->vp run lint:eslint
-
Replace Lerna task execution in CI:
viable-releasejob usesyarn lerna run --reject-cycles --concurrency 1 prepack- Replace with
vp run prepackwith 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.
-
Validate caching: Modify a file in
@endo/pass-style, runvp run test, confirm only downstream packages re-test. -
Update CI workflows:
ci.yml: Replace allyarn workspaces foreachinvocationsdepcheck.yml: Replace workspace iteration- Keep
yarncommands for install (until Phase 5)
/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)
Why: Highest-value change — skip unaffected tests in CI. Currently ALL tests run on every PR.
Dependency: Phase 3 (needs vp run for orchestration).
-
Create a Vite plugin for compartment-mapper module resolution.
The plugin hooks into Vite's
resolveIdandloadto understand Endo's module graph:- Standard ESM imports (the majority of packages) — Vite handles natively
@endo/compartment-mapperresolution — custom logic inpackages/compartment-mapper/src/node-modules.jsandsrc/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(walksnode_modules)
-
Build affected-test mapping. For each test file, trace transitive imports via the Vite module graph. Map changed files -> affected test files.
-
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
- Compute changed files:
-
CI integration:
- PR builds: Run only affected tests
masterpush: Run full test suite as safety net- Add
--affectedflag or equivalent tovp run test
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.
- 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
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.
-
Configure
vpto use Yarn as its underlying package manager. Verify it respects:- Yarn catalogs (
catalog:devprotocol) nodeLinker: pnpmsettingenableScripts: falseworkspace:^protocolresolutionsfield
- Yarn catalogs (
-
Update CI workflows to use
vp install(which callsyarn installunder the hood):- Keep
cache: yarninsetup-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)
- Keep
-
Update contributor docs (CONTRIBUTING.md, README) with
vpcommands alongsideyarnequivalents during transition.
/package.json(scripts)/.github/workflows/*.yml(all workflows)/CONTRIBUTING.md,/README.md(docs)
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)
Each phase should be verified independently:
- Phase 1:
oxfmt --check .passes; CI lint job green; diff from reformat is style-only - Phase 2:
oxlintpasses 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 usingpackages/eslint-plugin/test/ - Phase 3:
vp run testproduces same results asyarn 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 inpackages/ses/src/, verify all tests run - Phase 5:
vp installdelegates toyarn installcorrectly; Yarn catalogs, nodeLinker, and workspace protocol all work throughvp; all CI jobs green