Skip to content

πŸ“„ A pnpm monorepo template where each tool lives in its own package, runs its own version of Node.js and provides strict default config to be used by your projects and the built-in optimized CI/CD πŸ”€ Clone to create your own monorepo! πŸ”„ Pull to update the tools!

License

Notifications You must be signed in to change notification settings

YotpoLtd/breakproof-base-monorepo

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Breakproof Base Repo BETA VERSION

Linux is supported macOS is supported Windows is not supported

🎯 The problems it solves β€” πŸ’‘ How it works β€” πŸš€ Getting started β€” πŸ† List of Best Practices

βš™οΈ Tools & Configuration β€” βš–οΈ Conventions β€” πŸ“‹οΈ What's next

How can tools use different node.js?

# first, <create your empty github repo>
git clone <YOUR REPO URL>
git remote add breakproof [email protected]:YotpoLtd/breakproof-base-monorepo.git
git pull breakproof main
pnpm --workspace-root generate repo init

TLDR;

What is The Breakproof base monorepo?

πŸ“„ A pnpm monorepo template meant to be cloned and used for your projects.

The breakproof monorepo template comes with ready to be used tools already in it, where each tool lives in its own package, runs its own version of Node.js and provides strict default config to be consumed by your projects and the built-in optimized CI/CD

πŸ”€ Clone to create your own monorepo! β€” πŸ”„ Pull to update the tools & CI/CD!

The immediate value of developing inside breakproof monorepo

Your clone πŸ”€ becomes a breakproof home 🏠 where you can develop your frontend projects without πŸ’₯ breaking your code or πŸ’° breaking-the-bank.

You start by importing your existing projects or generating new ones. ⚑ Immediately those projects get a ton of working processes like optimized CI/CD, meticulous code checks, automatic npm releases, precise code-editor integration, etc. And because you cloned the repo, you keep receiving updates for those processes simply via git pull.

You get:

  1. πŸš€οΈ laser-focus on your projects β€” not on the tooling around them

  2. πŸ”„ easier upgrades of your projects β€” due to full isolation between tools & projects (even different node.js versions)

  3. 🧹 low-to-none repository maintenance β€” the breakproof repo will update tooling & processes around your projects, keep them optimized & following best practices, without you having to change your code

Note

All of that πŸ”Ό is implemented by making industry-standard tools πŸ† work together, NOT introducing "yet-another-abstraction" and NOT requiring specific project tech stack.

🎯 What problems does your Breakproof Base Repo clone solve?

project-vs-tools-problem

πŸ› οΈ You want to focus building your project, not waste time in upgrading the tooling around it or making different tools for code checks, testing, build & release behave well together or with your code editor, BUT at the same time you want to get the bug fixes and performance improvements from latest versions of all tools πŸ€” (solution in next section).

Β 

project-vs-cicd-problem

πŸ‘¨β€πŸ”§ It's hard to create a well-optimized, precise & cost-effective CI/CD that doesn't break with scale and informs developers what is going to happen without noise. You want someone else to create & maintain 90% of CD/CD processes, without requiring your projects to have specific tech stack πŸ€” (solution in next section).

Β 

project-vs-upgrade-problem

πŸ—„ You cannot regularly stop development until you upgrade everything everywhere, you want to do it piece by piece in isolation πŸ€” (solution in next section).

Β 

project-vs-strictness

πŸ•Š You want to improve your codebase but cannot fix all problems at once. You need to be tolerant of existing problems but forbid new problematic code πŸ€” (solution in next section).

Β 

project-vs-yet-another-config-and-wrapper

πŸ₯΄ You are tired of learning new configuration formats with each new repo-management tool that comes out and then hitting its customization limits. You want to directly deal with the tools used but still rely on sensible default configuration you can extend πŸ€” (solution in next section).

Β 

project-vs-industry-standard-and-swap

πŸ† You want to use industry-standard best practices without having to design the entire process from scratch, BUT at the same time be free to swap individual tools without having to redo the entire system πŸ€” (solution in next section).

Β 

⬆️ Back to top nav ⬆️

Β 

πŸ’‘ How does Breakproof Base Repo solve those problems?

Breakproof package isolation

  • The repo is a pnpm workspace β€” a.k.a. monorepo managed only using pnpm without any abstraction on top of it. This, by itself, lets you isolate one project from another by making each a separate package. The breakproof repo goes a step further.

  • When using the breakproof repo, your projects are isolated from the tools they use. This is achieved by installing the tools in a separate package instead of the same package as your project. In fact each different tool is installed in its own individual package. This allows us to leverage an ability of pnpm to specify a different node.js for each package. Essentially you get an isolated pair of <tool> + <node.js>, e.g. eslint + node22 and this lets you run eslint(using node22) from any place in the repo with a pnpm command similar to: pnpm --filter=<PACKAGE NAME THAT INSTALLS TOOL IN IT> run <TOOL NAME>. For example: pnpm --filter="@repo/eslint-isolated-base" run eslint

  • The breakproof repo comes with several tool packages already in it, each installs one industry-standard tool like eslint, or TypeScript, or rollup, or jest, …( full list below ⬇️ ). Additionally, each of those packages include detailed & strict base configuration for the specific tool that your projects can extend. This means your projects are in full control of the final tool configuration. If one package needs a tool, it calls a script from another package that installs that tool. That's it. πŸ”„ Each individual tool can be swapped with another as long as it does it's job. (role of each tool is listed below ⬇️

  • On many occasions the base configuration is tweaked so it accounts for limitation of another tool, thus making them behave well together. Not all tools accept all their settings through a config file, some require CLI arguments. For those occasions, each package that contains an isolated tool defines a command in the "script" section of its package.json to act as a shortcut. This shortcut can be executed from anywhere running: pnpm --filter=<PACKAGE NAME THAT INSTALLS TOOL IN IT> run <SHORTCUT NAME>

  • The above πŸ”Ό means that suddenly you can upgrade one tool in complete isolation from the rest & your project, even if the newer version requires a newer node.js version. This will not affect other packages. It also means your code can use whatever node.js version you need while some useful tool is "stuck" in the past without dragging you down

Β 

Breakproof CI/CD setup

  • The CI/CD process is implemented as GitHub actions/workflows. Because it knows the repo uses pnpm, we can target only a subset of the packages inside it when running tasks. For example, if a PR only changes your project, then the CI/CD will run test & linting checks only for your project. It will also run them in parallel and install only the dependencies that your project needs, which are cached, so subsequent runs with the same dependencies don't waste time re-downloading them.

  • The only requirement from your projects to participate in the CI/CD is to define properties in the "scripts" section of their package.json with specific names (a convention). You can run whatever command you like there, but probably you will run some of the isolated tools as shown above. For example, if you want your project tested in GitHub PRs, the project must define a lint:github-pr script with value of pnpm --filter='@repo/jest-base-isolated' run jest where @repo/jest-base-isolated is just the name of the package that isolates jest. The convention for the names of those scripts is as straightforward as possible, so you need build for building, release for npm releases, etc.

  • Because the CI/CD is a GitHub action, it easily posts comments on your PRs with useful info about the upcoming releases when PR is merged, or summary of code problems found in the changed packages. The CI/CD will also run specific checks for repository conventions like making sure every package.json explicitly defines desired node.js version to use. The breakproof repo even implements GitHub job summary which allows for a friendly way to overview the progress of the running workflow without the noise of going through logs.

  • You can implement your custom CI/CD bits like running deployment as just another GitHub workflow, BUT with the benefit that your workflow can receive extra inputs β€” the list of packages that have been changed or the list of packages that will be automatically released to npm. You can then use that information to only trigger logic in certain cases.

Tolerance for past problems

  • To allow you to work on projects that have some code problems BUT at the same time stop new problems from popping up, the breakproof repo has created a few node.js scripts inspired by esplint that basically "snapshots"/"remembers" what kind of problems each of your files have and how many occurrences per problem type there are. This allows you to ignore the existing ones but error out if the number of problems increase. This same ability of "remembering existing problems" is useful when tools are upgraded or their configuration changes. At this point they can become stricter or change the way they detect problems. To unblock the upgrade without investing immediately investing time you can snapshot the new results and continue with your day.

Β 

⬆️ Back to top nav ⬆️

Β 

πŸš€ Get started with your breakproof repo

  1. πŸ”€ Clone this repo. (and then push it to your own blank repo)

  2. πŸŽ›οΈ Initialize your clone with your preferences & automatically configure your code editor:

    pnpm --workspace-root generate repo init
  3. πŸš€ Generate new or import your existing projects.

If you want to automatically release projects as npm packages

Expand to see steps for setting up automatic publishing

You need to add some GitHub configuration for your repository:

  1. Create a bot/ machine GitHub account that will be used ONLY for the automatic release process. Using email like bot@<your domain>.com.

  2. Set up GitHub repository variables:

  • AUTORELEASE_BOT_NAME – the name to be used as commit author of the automatic version changes to your main branch
  • AUTORELEASE_BOT_EMAIL – the email associated bot account you've created; to be used as commit author of the automatic version changes to your main branch
  1. Set up GitHub repository secrets:
  • AUTORELEASE_BOT_TOKEN: a GitHub token with rights to push to repository to be used by the bot doing automatic releases
  • NPM_REGISTRY_USER: your npm registry user
  • [Optional] NPM_REGISTRY_AUTH_TOKEN: your npm registry auth token, if you don't use a password instead
  • [Optional] NPM_REGISTRY_PASSWORD: your npm registry password if you use this instead of auth token
  1. Set up GitHub Merge Queue:
  • Head to <your github repo> -> Settings -> Rules -> Rulesets -> New ruleset -> Import a ruleset

  • And upload <repo root>/.github/branch-rulesets/Default Branch.json. This will:

    • Require a Merge Queue for your default branch.
    • Require basics job from the main GitHub workflow to pass before PRs can be merged

    The above is used to make sure that no PR is merged while the CI for automatic releases of npm packages is running. This CI job can push version bumps & changelogs to your default branch. Without the merge queue there is a risk of a race condition.

  1. Write the steps in <repo root>/.github/actions/npm-login that will authenticate you in the npm registry.
  • This action is empty by default and gets called on all places where packages are installed
  • To authenticate you would need to configure some repository secrets, depending on your auth approach. All the secrets will be accessible as environment variables, and not through the GitHub ${{ secrets }}. This is done to overcome a limitation in GitHub reusable actions: they can't inherit secrets but do inherit environment variables.
    • SECRET_NPM_REGISTRY_AUTH_TOKEN – for this to have a value you must create a secret in your repository settings called NPM_REGISTRY_AUTH_TOKEN
    • SECRET_NPM_REGISTRY_USER – for this to have a value you must create a secret in your repository settings called NPM_REGISTRY_USER
    • SECRET_NPM_REGISTRY_PASSWORD – for this to have a value you must create a secret in your repository settings called NPM_REGISTRY_PASSWORD
  1. If you are publishing to a private registry set this at the top of the <repo root>/.npmrc.

If you want to automatically deploy applications

Expand to see steps for setting up automatic application deployment
  1. Follow the first 3 steps from the instructions for automatic npm publishing
  2. If you use Slack and you want to get message to a channel of your choice, set up a GitHub repository secret named SLACK_WEBHOOK_URL with a webhook generated from your slack admin
  3. Open <repo root>/.github/workflows/_repo-deploy-single-project-on-push.yaml and copy the example job there, after which customize the steps to match the specifics of your app deployment

Β 

⬆️ Back to top nav ⬆️

Β 

πŸ† List of Best Practices

Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β badge details
generate all required files for a new project via interactive CLI step-by-step wizard
each new project comes with unit, e2e and integration tests enabled
each new project comes with typescript support & build process enabled
the typescript settings are fine-tuned based on the type of project you chose in the CLI wizard
each new project comes with exceptionally extensive list of code checks, individually customizeable for each of your projects
the code checks can apply possible autofixes to entire project or only to its files staged for commit
each new project has the ability for automatic release to npm & GitHub when merged in the default branch, following the conventional commit
each new project has its individual set of dependencies completely isolated from the dependencies of the other projects in the repo
each new project can choose a different node.js version to use, thus unlock innovation of individual projects
all tools that do code checks, tests or release of your project, in fact run in their own isolated environment with their own nodejs version, which means you can upgrade them without having to immediately upgrade your project or vice versa
automatically run some/all of the code checks & tests on each GitHub pull request, individually customizeable for each of your projects
automatically run some/all of the code checks & tests before git commit, individually customizeable for each of your projects
detect which projects have been affected by the pull request changes or pending git commits, and run code checks only for them
announce what is affected by the pull requests changes as a GitHub comment
add meaningful summary of your GitHub workflows with the contextual info of what they are running (GitHub workflow markdown summary)
ability to modify existing projects, so you can make them release-able or add code-checks, all done via a CLI step-by-step wizard
shared npm scripts listed in a single place and can be used by any of your packages to help you analyze problems with it
conventions to be followed by each package for more unified development experience
instructions and importable configuration for your code editor to get the best developer experience possible when developing your projects
instructions on how to configure your GitHub repository to prevent problems and work best with the breakproof base

Β 

⬆️ Back to top nav ⬆️

Β 

🧰 The current list of tools configured to work together

Nothing is perfect, so this list will inevitably change (see ⬇️ "What's next?"). Here are the currently used tools:

Tool What it's used for Provided Configuration node.js version used
🌐 pnpm 🧩 multiple roles βš™οΈ forkable config in repo n/a (does not rely on available node.js)
🌐 bash functions & scripts 🧩 multiple roles πŸ•³οΈ no config applied in repo n/a (does not rely on node.js)
🌐 shell-check 🧩 multiple roles πŸ•³οΈ no config applied in repo n/a (does not rely on node.js)
🌐 eslint + plugins 🧩 multiple roles βš™οΈ base config in repo v22.6.0
🌐 prettier 🧩 multiple roles βš™οΈ base config in repo v22.6.0
🌐 typescript 🧩 multiple roles βš™οΈ base config in repo <same as your package> or v22.6.0
🌐 sucrase, 🌐 ts-node & 🌐 tsx 🧩 multiple roles βš™οΈ base config in repo <same as your package> or v22.6.0
🌐 actionlint 🧩 multiple roles πŸ•³οΈ no config applied in repo n/a (does not rely on node.js)
🌐 lint-staged 🧩 multiple roles βš™οΈ base config in repo v22.6.0
🌐 dpdm 🧩 multiple roles βš™οΈ base config in repo v22.6.0
🌐 depcheck 🧩 multiple roles βš™οΈ base config in repo v22.6.0
🌐 lit-analyzer 🧩 multiple roles βš™οΈ base config in repo v22.6.0
🌐 syncpack 🧩 multiple roles βš™οΈ forkable config in repo v22.6.0
🌐 cypress 🧩 multiple roles βš™οΈ base config in repo v22.6.0
🌐 jest 🧩 multiple roles βš™οΈ base config in repo v22.6.0
🌐 conventional-changelog 🧩 multiple roles βš™οΈ base config in repo v22.6.0
🌐 release-it 🧩 multiple roles βš™οΈ base config in repo v22.6.0
🌐 webpack 🧩 multiple roles βš™οΈ base config in repo <same as your package> or v22.6.0
🌐 rollup + plugins 🧩 multiple roles βš™οΈ base config in repo <same as your package> or v22.6.0
🌐 babel + plugins 🧩 multiple roles βš™οΈ base config in repo <same as your package> or v22.6.0
🌐 hygen 🧩 multiple roles βš™οΈ forkable config in repo v22.6.0
🌐 git hooks 🧩 multiple roles βš™οΈ forkable config in repo n/a (does not rely on node.js)
🌐 github CI 🧩 multiple roles βš™οΈ forkable config in repo n/a (does not rely on node.js)
🌐 VSCode calibration 🧩 multiple roles βš™οΈ hint + importable config v22.6.0 (editor itself uses that)
🌐 JetBrains calibration 🧩 multiple roles βš™οΈ hint + importable config v22.6.0 (editor itself uses that)

Β 

⬆️ Back to top nav ⬆️

Β 

βš–οΈ Repository conventions & core principles

  • Avoid adding abstractions to the already overloaded frontend world
  • People should be aware of what comes with their dependencies
  • Each package must declare the node.js version used for running the scripts its package.json
    • mandatory eslint code check for this runs on git precommit & GitHub PR
  • Packages are allowed to pick node.js version from a white-list to avoid extreme fragmentation of versions
    • mandatory eslint code check for this runs on git precommit & GitHub PR
  • Each package must have a script called lint:precommit which would run as git hook before commits and execute preferred code checks
    • mandatory eslint code check for this runs on git precommit
  • Each package must have a script called lint:github-pr which would run on each push to a GitHub PR and execute preferred code checks
    • mandatory eslint code check for this runs on GitHub PR
  • Each package that requires a build step must have scripts called build and dev which are used to verify build passes and run before building other packages that depend on them
    • If the package is an app, the dev script can also be named serve to match popular conventions
  • Each package that should be published to npm registry must have s script called release and whatever is executed must accept arguments compatible with release-it CLI
  • Following what each configuration extends and understanding why it's configured this way should be easy
  • Writing configurations files should provide hints for available options without having to jump to documentation every time
    • Prefer .ts config over .js, .cjs, .mjs or .json config. We get type checks + ability to comment.
    • Prefer .mjs config with jsdoc over .js, .cjs, or .json config. We get type hints + ability to comment.
    • Prefer .cjs or .js config with jsdoc over .json config. We get type hints + ability to comment.
  • Configurations can have different variations (e.g. "dev" vs "production" mode) and preferably they are defined in a single file without repeating different options for each mode
  • When using CLI commands prefer --longer argument names over shorter versions like -l
  • Code editors are first-class runtime environments we need to care about
    • postinstall script that guarantees code editors see eslint and prettier but CI doesn't do this extra step
  • Build-caching should be thought as a separate layer that can be added later, not requirement from the start

Β 

⬆️ Back to top nav ⬆️

Β 

πŸ“‹οΈ What's next?

The GitHub issues of type Feature can give you a picture of what kind of improvements we've been looking into.

Since the repository is done in a way that allows tools to be swapped there are some ideas, that we haven't yet created issues for. For example:

  • eslint might in the future be swapped out with another tool like oxc linter or perhaps whatever void0 are working on.
  • rollup might in the future be swapped out with rolldown
  • webpack is a build tool that served the FE community well. People can see it as "old" or "outdated", but it keeps on living, used by next.js or reborn as rspack, or turpopack. Currently, the sandbox applications in the breakproof repo use webpack, but that could be swapped for any other build tool
  • so far lint-staged has served us well, but we might just replace it with a simple .ts script, since we are starting to tweak more from it, and we actually only use its config format and its ability to list staged files and then show their execution + some meta sugar in the terminal.
  • cypress + Cypress Cloud can be a great combo which has been a good friend in end-to-end and integration testing. But for a while we can see developers can are drawn to the more native way of writing tests with playwright.
  • ... check out the GitHub issues or create one if you have an idea.

Β 

⬆️ Back to top nav ⬆️

Β 

Breakproof repo specifics

  • conventional changelog allows a lot of prefixes, the repo only respects fix, feat and breaking changes.

Yotpo specifics

There are 2 packages providing base config for eslint and prettier. One (@repo/eslint-base-isolated) which depends on the other (@yotpo-common/shared-linter-config).

We've done this because internally some projects in yotpo still live in their own separate repos. @yotpo-common/shared-linter-config is used to share the configuration with projects outside our breakproof repo, while @repo/eslint-base-isolated extends it with some tweaks.

Breakproof repo fixes

  1. For better integration with JetBrains IDEs, there is a "fake" <repo root>/package.json. It's intentionally a symlink to a non-existent path

  2. To work around a limitation in pnpm filtering, we introduce a new array section in package.json called devtoolsDependencies. It should include names of devDependencies which do not affect the runtime or the build. So things like lint, tests, or local development helpers.

  3. In order to be strict and avoid packages accessing <repo root>/node_modules but still let code-editors find installations of tools (e.g. prettier) we introduced codeEditorDependencies in root package.json5 that installs a few dependencies only for local development (NOT in CI) and DOES NOT SAVE them to the lock file.

  4. The preinstall script:

    [ -n \"$(pwd | grep '/node_modules/')\" ] || echo $npm_config_user_agent | grep -q 'pnpm/' || (echo 'PLEASE USE PNPM, not NPM' && exit 1)

    We want to disallow package managers other than pnpm to be accidentally used in this repo. So we add this script to every package preinstall hook.

    Ideally it would be just npx only-allow pnpm as described in pnpm docs, but:

    1. There is a bug in only-allow so we work around it.
    2. If developer is only using pnpm they might not have node installed at all, so we need to check if npx exists (via which npx)

About

πŸ“„ A pnpm monorepo template where each tool lives in its own package, runs its own version of Node.js and provides strict default config to be used by your projects and the built-in optimized CI/CD πŸ”€ Clone to create your own monorepo! πŸ”„ Pull to update the tools!

Topics

Resources

License

Stars

Watchers

Forks