Skip to content

Why oneRepo

oneRepo is a suite of tools for managing JavaScript and TypeScript monorepos, with the goal of enabling speed and confidence for all changes.

  • Documented: All commands and options must be documented and respond to --help requests.
  • Discoverable: Code for commands must be easy to find and trace in a familiar language.
  • Clear output: All log output must be grouped, labelled with purpose, and clearly defined by its state.
  • Correct: Avoid human errors by automating correctness checks whenever possible.
  • Fast: Reduce wait time and overhead.

Other monorepo tooling is often either overly complicated or lacking in functionality, making it challenging for distributed organizations to maintain a healthy monorepo. To ensure conformance, stability, and discoverability, organizations need a tool that strikes the right balance, which existing solutions often fail to provide.

oneRepo simplifies automation by handling common tasks for you and your team. Say goodbye to spending excessive time tinkering with tooling and hello to focusing on your applications.

Gone are the days of manually configuring file glob patterns for Workspace integrity and decision making for whether or not checks and tasks must run. oneRepo automatically and accurately determines which Workspaces, tasks, and checks are necessary for any given change

With oneRepo, you can utilize shared Workspaces as source-level dependencies. This means that all import/require chains within the monorepo originate from the source files, eliminating the need to rebuild shared packages to capture changes.

oneRepo and its APIs, plugins, and configurations are all written in JavaScript (and TypeScript). There’s no need to learn a new language or decipher YAML/JSON schema DSLs to configure your tooling.

Logging output from every command and tool in oneRepo is carefully grouped, documented, and prevented from overlapping parallel executions. Every line includes context, timing, and log-type information.

Once you’ve installed oneRepo, you’re all set. There’s no need to pay extra for additional features; simply bring your own infrastructure and enjoy the full feature set.


When considering tools for managing JavaScript repositories, it’s essential to be aware of common pitfalls.

One such pitfall is script overload, where many tools require you to write individual commands into each Workspace’s package.json "scripts" field. This approach can lead to a brittle setup, copy-paste boilerplate, reduced reusability, and limited documentation and discoverability.

Take, for example, the following a real-world portion an application’s package.json file that contained 43 different scripts just for running Cypress tests. None of these scripts are documented, and many of them may be completely or nearly identical, leading to confusion and inefficiency.

package.json
{
"scripts": {
"cy:install": "cypress install",
"cy:cli": "cypress run",
"cy:ui": "cypress open",
37 collapsed lines
"cy:local:cli": "cypress run",
"cy:local:cli:desktop": "cypress run --config viewportWidth=1440,viewportHeight=900",
"cy:local:cli:mobile": "cypress run",
"cy:local:cli:tablet": "cypress run --config viewportWidth=1024,viewportHeight=768",
"cy:local:ui": "cypress open",
"cy:local:ui:desktop": "cypress open --config viewportWidth=1440,viewportHeight=900",
"cy:local:ui:mobile": "cypress open",
"cy:local:ui:tablet": "cypress open --config viewportWidth=1024,viewportHeight=768",
"cy:test:cli": "cypress run --config baseUrl=https://test.example.com/",
"cy:test:cli:desktop": "cypress run --config baseUrl=https://test.example.com,viewportWidth=1440,viewportHeight=900",
"cy:test:cli:mobile": "cypress run --config baseUrl=https://test.example.com/",
"cy:test:cli:tablet": "cypress run --config baseUrl=https://test.example.com,viewportWidth=1024,viewportHeight=768",
"cy:test:ui": "cypress open --config baseUrl=https://test.example.com/",
"cy:tacos:ui": "cypress open --config baseUrl=https://tacos.test.example.com/",
"cy:test:ui:desktop": "cypress open --config baseUrl=https://test.example.com,viewportWidth=1440,viewportHeight=900",
"cy:test:ui:mobile": "cypress open --config baseUrl=https://test.example.com/",
"cy:test:ui:tablet": "cypress open --config baseUrl=https://test.example.com,viewportWidth=1024,viewportHeight=768",
"cy:cache:list": "npx cypress cache list",
"cy:ci": "CI=true npx start-server-and-test http-get://localhost:3000 cy:percy:record",
"cy:info": "npx cypress info",
"cy:open": "npx cypress open",
"cy:open:local": "npx cypress open --config baseUrl=http://localhost:3001/",
"cy:open:local:mobile": "npx cypress open --config baseUrl=http://localhost:3001/,viewportWidth=375,viewportHeight=667",
"cy:open:test": "npx cypress open --config baseUrl=https://test.example.com/",
"cy:open:test:mobile": "npx cypress open --config baseUrl=https://test.example.com/,viewportWidth=375,viewportHeight=667",
"cy:test:open:desktop": "cypress open --config baseUrl=https://test.example.com,viewportWidth=1440,viewportHeight=900",
"cy:test:open:tablet": "cypress open --config baseUrl=https://test.example.com,viewportWidth=1024,viewportHeight=768",
"cy:percy:local": "npx percy exec -- cypress open --config baseUrl=http://localhost:3000/",
"cy:percy:test": "npx percy exec -- cypress open --config baseUrl=https://test.example.com/",
"cy:percy:record": "npx percy exec -- cypress run --record --spec 'cypress/e2e/**.spec.js'",
"cy:record:local": "npx cypress run --record --config baseUrl=http://localhost:3000/ --spec 'cypress/e2e/**.spec.js'",
"cy:run": "npx cypress run --record false --spec 'cypress/e2e/**.spec.js'",
"cy:run:local": "npx cypress run --config baseUrl=http://localhost:3001/ --record false --spec 'cypress/e2e/**.spec.js'",
"cy:test:run:desktop": "cypress run --config baseUrl=https://test.example.com,viewportWidth=1440,viewportHeight=900",
"cy:test:run:tablet": "cypress run --config baseUrl=https://test.example.com,viewportWidth=1024,viewportHeight=768",
"cy:verify": "npx cypress verify",
"cy:version": "npx cypress version",
"cy:register": "npx cypress run --record --key 3a8de5fe-003c-431b-9685-385178dd5069 --group registration --spec cypress/e2e/registration.spec.js",
"cy:login": "npx cypress run --record --key 3a8de5fe-003c-431b-9685-385178dd5069 --group submit-app --spec cypress/e2e/login.spec.js",
"cy:workflow": "npx cypress run --record --key 3a8de5fe-003c-431b-9685-385178dd5069"
}
}

The phenomenon of an excessive number of scripts cluttering the package.json file is what we refer to as “script overload”. This not only makes it challenging to remember how to run scripts in individual Workspaces but also highlights a broader issue with documentation and tooling standards.

Many tools lack proper documentation for package.json scripts, making it difficult for developers to share, understand, and use their own tools effectively.

This lack of documentation also extends to other tooling, which often relies on memory, rather than providing an immediately accessible reference.

Other tools encourage script overload through task chaining – an approach that requires defining individual and separate scripts for building modules with various definitions (ESM, CJS, TS), resulting in complex and cumbersome configurations.

moon.yml
tasks:
build-esm:
command: esbuild --format=esm
6 collapsed lines
inputs:
- ./src/**/*
outputs:
- ./dist/esm
options:
runInCI: false
build-cjs:
command: esbuild --format=cjs
6 collapsed lines
inputs:
- ./src/**/*
outputs:
- ./dist/cjs
options:
runInCI: false
build-tsdefs:
command: tsc -b tsconfig.json --emitDeclarationOnly
6 collapsed lines
inputs:
- ./src/**/*
outputs:
- ./dist/src
options:
runInCI: false
build:
deps:
- build-esm
- build-cjs
- build-tsdefs

While these approaches may seem manageable at first, as repositories grow and become more complex, Workspaces may require unique configurations and conditional checks. Using a DSL (Domain Specific Language) or similar implementation adds another layer of complexity, requiring developers to either learn new concepts or rely on dedicated teams to build infrastructure.

Utilizing cached responses based on a set of input files can significantly reduce unnecessary work. However, the challenge lies in understanding precisely which files and workspaces affect the cache for each task, a responsibility that falls on you and your team. But even the best developers can easily make a configuration mistake that leads to difficult to debug problems.

The main issue with cache-based solutions is the need to maintain knowledge of the inputs and outputs for each task or target, especially when making substantial changes. It becomes crucial to ensure that any modifications do not unintentionally alter your tool’s determinism without updating the configuration. Given the complexity of modern projects, expecting every team member to be aware of all these details at all times is unrealistic, leading to potential oversights and confusion.

Here’s an example problematic configuration:

modules/my-project/nx.json
{
10 collapsed lines
"$schema": "./node_modules/nx/schemas/nx-schema.json",
"tasksRunnerOptions": {
"default": {
"runner": "nx/tasks-runners/default",
"options": {
"cacheableOperations": ["build"]
}
}
},
"targetDefaults": {
"build": {
"inputs": ["default", "^production"]
}
},
"namedInputs": {
"default": ["{projectRoot}/**/*", "sharedGlobals"],
"sharedGlobals": ["{workspaceRoot}/prisma/**", "{workspaceRoot}/babel.config.json"],
"production": ["default", "{projectRoot}/tsconfig.json"]
}
}

Can you spot where the previous build task may have issues with cache consistency? It’s okay if you aren’t sure, because there isn’t enough information yet.

Using the previous configuration, what if we also take a look at the ./tsconfig.json?

modules/my-project/tsconfig.json
{
"extends": "@internal/tsconfig/base.json",
"compilerOptions": {
"outDir": "./dist"
},
"include": ["./src/**/*"],
"exclude": ["./dist"]
}

Take a moment to look again and see if you can spot the issue. It’s not immediately obvious.

Hint: let’s highlight the important part at line 2 from the tsconfig.json and compare to the namedInputs in the nx.json:

modules/my-project/tsconfig.json
{
"extends": "../../shared/tsconfig/base.json",
"compilerOptions": {
"outDir": "./dist"
},
"include": ["./src/**/*"],
"exclude": ["./dist"]
}

Identifying potential issues with cache consistency in a build task can be challenging without sufficient information, as seen in the example provided. For instance, consider the impact of changes made to the ./tsconfig.json file. The configuration in tsconfig.json may include references to files outside of the local workspace, such as ../../shared/tsconfig/base.json, which are not declared as inputs for cache consistency in nx.json.

In the event of modifications to the compilerOptions within ../../shared/tsconfig/base.json, it would be necessary to perform a full build of my-project without using the cache. However, due to the configuration approach in other monorepo tools like Nx and Turbo, overlooking or misconfiguring these dependencies is a common pitfall.

One big frustration with other tooling is that the log output is difficult for humans to read – particularly when running tasks in parallel. Most monorepo tooling will buffer everything immediately to the output, interleaving parallel task output together, making it difficult to follow what is happening and where there may be issues or all is fine.

Example TurboRepo output
@internal-tests/todo-list:test: +++
@internal-tests/todo-list:test:
@internal-tests/todo-list:test: dependencies:
@internal-tests/todo-list:test: + @internal-tests/todo-list 0.0.0-development
@internal-tests/todo-list:test: + @types/node 16.11.41
@internal-tests/todo-list:test: + typescript 4.7.3
@internal-tests/todo-list:test:
@internal-kit/ts:setup:test: Progress: resolved 117, reused 110, downloaded 1, added 0
@internal-tests/todo-list:test: Progress: resolved 3, reused 2, downloaded 1, added 3, done
@internal-tests/todo-list:test:
@internal-kit/ts:setup:test: Progress: resolved 219, reused 208, downloaded 1, added 0
@internal-tests/todo-list:test: PASS src/__test__/usingCli.test.ts
@internal-kit/ts:setup:test: Progress: resolved 310, reused 292, downloaded 1, added 0
@internal-kit/ts:setup:test: Progress: 421, reused 402, downloaded 1, added 0
@internal-tests/todo-list:test: PASS src/__test__/usingAsLibrary.test.ts
@internal-tests/todo-list:test:
@internal-tests/todo-list:test: Test Suites: 2 passed, 2 total
@internal-tests/todo-list:test: Tests: 2 passed, 2 total
@internal-tests/todo-list:test: Snapshots: 8 passed, 8 total
@internal-tests/todo-list:test: Time: 2.9122s, estimated 3 s
@internal-tests/todo-list:test: Ran all test suites.

oneRepo solves for this by waiting grouping output by individual task. While running, by default, only the most recent or most important information will be shown. Output will never be interwoven between individual tasks. And without manually requesting more verbose output, superfluous information will be hidden:

Log types within a step
┌ Run tests for @internal-tests/todo-list-cli
3s
┌ Run tests for @internal-kit/ts
Progress: 421, reused 402, downloaded 1, added 0
└ ⠙

oneRepo is inspired by closed source implementations at major companies like Twitter, Zillow Rentals, and Microsoft for Startups.