Skip to content

Commit

Permalink
build(all): switch from yarn to pnpm
Browse files Browse the repository at this point in the history
I tried to get all the apps and libraries under a single yarn workspace and found I could not do it. This message describes the difficulties I had.

`react-scripts` does some preflight checks to ensure that there isn't another resolvable copy of some of the packages it needs that might conflict with the versions it wants. When it finds one, [it bails with an error message](facebook/create-react-app#4296).

Trying to put multiple `react-scripts`-using projects into the same yarn workspace means that yarn will hoist shared dependencies, such as `babel-eslint`, to the root. Thus, these packages become visible to the preflight check and it aborts. To work around this, you can use the [`nohoist`](https://classic.yarnpkg.com/blog/2018/02/15/nohoist/) option to tell yarn which packages not to hoist. This sort of worked, but there were subtle issues remaining.

I got BMD and ballot-encoder into the workspace together, but when I added election-manager [it broke BMD](#139). I still don't know exactly why, but I believe something changed the versions of some packages that BMD was using in a way that caused the tests to fail.

Sometimes multiple packages reference the same types, i.e. for a mutual dependency. However, sometimes they'd end up using different (and subtly incompatible) versions of the same `@types/` package. When this happened, the best recourse was to use another yarn workspaces feature: `resolutions`. This allows you to configure what version packages should resolve to. This fixed most of the type incompatibility issues, but I suspect it caused another issue.

I noticed some crashes due to an API incompatibility. Specifically, one of the `@babel/` packages required another one in the `^7.12.0` range, but yarn hooked it up with `7.8.6`. Why? I don't know. Perhaps it was the `resolutions` stuff. I was only able to work around this by finding the entry in the `yarn.lock` file, deleting it, and re-running `yarn`.

I tried both npm v7 and pnpm. npm v7 has support for workspaces similar to yarn, but does not yet have support for configuring hoisting behavior. Lacking hoisting control meant it hoisted everything it could, which made `react-scripts` angry. Thus I couldn't use npm v7.

Previous versions of pnpm did not hoist anything, and still was able to share dependencies by symlinking. Though it does hoist by default now, that behavior can be turned off. So I added `hoist = false` to `.npmrc` and then started trying to run everything to determine what would break without hoisting. It turns out that a good number of packages have implicit dependencies that hoisting allowed them to access, but that could not be accessed in a non-hoisted `node_modules` layout.

pnpm has a hook called `readPackage` that allows altering the package data for any npm package using any custom logic you want. This allows us to act as if these packages with implicit dependencies actually had those dependencies listed explicitly. In my testing, this has yielded a good balance of correctness and functionality.

So pnpm seems to work and I think is flexible enough to meet our needs going forward.
  • Loading branch information
eventualbuddha committed Jan 14, 2021
1 parent e3f6765 commit 8fe2a7e
Show file tree
Hide file tree
Showing 56 changed files with 21,777 additions and 63,627 deletions.
135 changes: 48 additions & 87 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,154 +13,85 @@ jobs:
executor: nodejs
resource_class: xlarge
steps:
- checkout
- restore_cache:
key:
dotcache-cache-{{checksum ".circleci/config.yml" }}-{{ checksum "yarn.lock" }}
- run:
name: Setup Dependencies
command: yarn --cwd libs/ballot-encoder install --frozen-lockfile
- save_cache:
key:
dotcache-cache-{{checksum ".circleci/config.yml" }}-{{ checksum "yarn.lock" }}
paths:
- node_modules
- libs/ballot-encoder/node_modules
- checkout-and-install
- run:
name: Run Linters
command: |
yarn --cwd libs/ballot-encoder lint
pnpm --dir libs/ballot-encoder lint
- run:
name: Run Front-End Test and Coverage
command: |
yarn --cwd libs/ballot-encoder test:coverage
pnpm --dir libs/ballot-encoder test:coverage
test-bas:
executor: nodejs-browsers
resource_class: xlarge
steps:
- checkout
- restore_cache:
key:
dotcache-cache-{{checksum ".circleci/config.yml" }}-{{ checksum "apps/bas/yarn.lock" }}
- run:
name: Setup Dependencies
command: yarn --cwd apps/bas install --frozen-lockfile
- save_cache:
key:
dotcache-cache-{{checksum ".circleci/config.yml" }}-{{ checksum "apps/bas/yarn.lock" }}
paths:
- apps/bas/node_modules
- checkout-and-install
- run:
name: Run Linters
command: |
yarn --cwd apps/bas lint
pnpm --dir apps/bas lint
- run:
name: Run Front-End Test and Coverage
command: |
yarn --cwd apps/bas test:coverage
pnpm --dir apps/bas test:coverage
test-bmd:
executor: nodejs-browsers
resource_class: xlarge
steps:
- checkout
- restore_cache:
key:
dotcache-cache-{{checksum ".circleci/config.yml" }}-{{ checksum "yarn.lock" }}
- run:
name: Setup Dependencies
command: yarn --cwd apps/bmd install --frozen-lockfile
- save_cache:
key:
dotcache-cache-{{checksum ".circleci/config.yml" }}-{{ checksum "yarn.lock" }}
paths:
- node_modules
- apps/bmd/node_modules
- checkout-and-install
- run:
name: Run Linters
command: |
yarn --cwd apps/bmd lint
pnpm --dir apps/bmd lint
- run:
name: Run Front-End Test and Coverage
command: |
yarn --cwd apps/bmd test:coverage
pnpm --dir apps/bmd test:coverage
test-bsd:
executor: nodejs-browsers
resource_class: xlarge
steps:
- checkout
- restore_cache:
key:
dotcache-cache-{{checksum ".circleci/config.yml" }}-{{ checksum "apps/bsd/yarn.lock" }}
- run:
name: Setup Dependencies
command: yarn --cwd apps/bsd install --frozen-lockfile
- save_cache:
key:
dotcache-cache-{{checksum ".circleci/config.yml" }}-{{ checksum "apps/bsd/yarn.lock" }}
paths:
- apps/bsd/node_modules
- checkout-and-install
- run:
name: Run Linters
command: |
yarn --cwd apps/bsd lint
pnpm --dir apps/bsd lint
- run:
name: Run Front-End Test and Coverage
command: |
yarn --cwd apps/bsd test:coverage
pnpm --dir apps/bsd test:coverage
test-election-manager:
executor: nodejs-browsers
resource_class: xlarge
steps:
- checkout
- restore_cache:
key:
dotcache-cache-{{checksum ".circleci/config.yml" }}-{{ checksum "apps/election-manager/yarn.lock" }}
- run:
name: Setup Dependencies
command: yarn --cwd apps/election-manager install --frozen-lockfile
- save_cache:
key:
dotcache-cache-{{checksum ".circleci/config.yml" }}-{{ checksum "apps/election-manager/yarn.lock" }}
paths:
- apps/election-manager/node_modules
- checkout-and-install
- run:
name: Run Linters
command: |
yarn --cwd apps/election-manager lint
pnpm --dir apps/election-manager lint
- run:
name: Run Front-End Test and Coverage
command: |
yarn --cwd apps/election-manager test:coverage
pnpm --dir apps/election-manager test:coverage
test-hmpb-interpreter:
executor: nodejs
resource_class: xlarge
steps:
- checkout
- restore_cache:
key:
dotcache-cache-{{checksum ".circleci/config.yml" }}-{{ checksum "yarn.lock" }}
- run:
name: Setup Dependencies
command: yarn --cwd libs/hmpb-interpreter install --frozen-lockfile
- save_cache:
key:
dotcache-cache-{{checksum ".circleci/config.yml" }}-{{ checksum "yarn.lock" }}
paths:
- node_modules
- libs/hmpb-interpreter/node_modules
- checkout-and-install
- run:
name: Run Linters
command: |
yarn --cwd libs/hmpb-interpreter lint
pnpm --dir libs/hmpb-interpreter lint
- run:
name: Run Tests with Coverage
command: |
yarn --cwd libs/hmpb-interpreter test:coverage
pnpm --dir libs/hmpb-interpreter test:coverage
test-module-scan:
executor: nodejs
Expand Down Expand Up @@ -218,3 +149,33 @@ workflows:
- test-hmpb-interpreter
- test-module-scan
- test-module-smartcards

commands:
checkout-and-install:
description: Get the code and install dependencies.
steps:
- run:
name: Install pnpm
command: |
curl -L https://unpkg.com/@pnpm/self-installer | sudo node
- checkout
- restore_cache:
key:
dotcache-cache-{{checksum ".circleci/config.yml" }}-{{ checksum "pnpm-lock.yaml" }}
- run:
name: Setup Dependencies
command: pnpm install --frozen-lockfile
- save_cache:
key:
dotcache-cache-{{checksum ".circleci/config.yml" }}-{{ checksum "pnpm-lock.yaml" }}
paths:
- node_modules
- apps/bas/node_modules
- apps/bmd/node_modules
- apps/bsd/node_modules
- apps/election-manager/node_modules
- libs/ballot-encoder/node_modules
- libs/eslint-plugin-no-array-sort-mutation/node_modules
- libs/hmpb-interpreter/node_modules
- libs/@types/jsfeat/node_modules
- libs/@types/node-quirc/node_modules
9 changes: 9 additions & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Prevent packages from accessing anything they don't explicitly depend on. This
# is mostly here so that the `react-scripts` preflight check succeeds and does
# not find babel-eslint or babel-jest or any of the other packages it is afraid
# it might get the wrong version of.
hoist = false

# We _do_ try to hoist `@types` packages so that TS will not end up with type
# errors from comparing types from different versions of the same package.
public-hoist-pattern=['@types/*']
61 changes: 61 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# vxsuite

The VotingWorks in-person voting system.

## About

Includes software for a [ballot-marking device (BMD)](./apps/bmd), a
[ballot activation system (BAS)](./apps/bas), a
[ballot scanning device (BSD)](./apps/bsd), and an
[election manager](./apps/election-manager). See https://voting.works for more
information about VotingWorks.

## Development

Building VxSuite for development requires git, [NodeJS](https://nodejs.org/)
v12.19.0 and [pnpm](https://pnpm.js.org).

### Ubuntu Quickstart

This expects Ubuntu 18.0.4, though it may work on other versions. This installs
the right version of NodeJS manually. You can use a tool like
[nvm](https://github.com/nvm-sh/nvm) or [volta](https://volta.sh) to do this in
a nicer way.

```sh
# change this to wherever you want:
NODE_ROOT="${HOME}/usr/local/nodejs"

# download and install:
mkdir -p "$NODE_ROOT"
curl -sLo- https://nodejs.org/dist/v12.19.0/node-v12.19.0-linux-x64.tar.gz | \
tar xz --strip-components 1 -C "${NODE_ROOT}"

# configure your shell; this assumes bash:
echo "export PATH=\$PATH:${NODE_ROOT}/bin"
export PATH="${PATH}:${NODE_ROOT}"
node -v # should print "v12.19.0"

# install pnpm:
npm i -g pnpm

# clone the repository:
sudo apt install git -y # in case you don't have git
mkdir -p ~/src && cd ~/src
git clone https://github.com/votingworks/vxsuite.git

# install dependencies:
cd vxsuite
pnpm install

# try out BMD:
cd apps/bmd
pnpm start
# if it worked, go to http://localhost:3000/
```

See the individual README documents for more information on how to run the individual services.

## License

GPLv3
2 changes: 1 addition & 1 deletion apps/bas/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"editor.tabSize": 2,
"eslint.packageManager": "yarn",
"eslint.packageManager": "pnpm",
"eslint.validate": [
"javascript",
"javascriptreact",
Expand Down
2 changes: 1 addition & 1 deletion apps/bas/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
FORCE:

build: FORCE
yarn install && yarn build && cd prodserver && yarn install
pnpm install && pnpm build

run:
cd prodserver && node index.js
16 changes: 8 additions & 8 deletions apps/bas/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ demo url which can be found in the comments of the pull request.

## Install and Run App Locally

This assumes you have `git` and `yarn` installed.
This assumes you have `git` and `pnpm` installed.

1. Clone the repo:

Expand All @@ -20,13 +20,13 @@ This assumes you have `git` and `yarn` installed.
2. Install dependencies:

```
yarn install
pnpm install
```

3. Run the app in your local browser:

```
yarn start
pnpm start
```

## Contributing
Expand All @@ -35,11 +35,11 @@ TBD

## Local Development Scripts

- `yarn install` - Install the dependencies.
- `yarn start` - Run the app locally.
- `yarn test`- Run tests in interactive mode.
- `yarn lint` - lint and format JavaScript & TypeScript
- `yarn format` - format other files (non JavaScript & TypeScript files)
- `pnpm install` - Install the dependencies.
- `pnpm start` - Run the app locally.
- `pnpm test`- Run tests in interactive mode.
- `pnpm lint` - lint and format JavaScript & TypeScript
- `pnpm format` - format other files (non JavaScript & TypeScript files)

See `package.json` for all available scripts.

Expand Down
22 changes: 13 additions & 9 deletions apps/bas/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
"version": "0.1.0",
"private": true,
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"test:coverage": "yarn test --coverage --watchAll=false",
"eject": "react-scripts eject",
"start": "script/react-scripts start",
"build": "script/react-scripts build",
"test": "script/react-scripts test",
"test:coverage": "pnpm test -- --coverage --watchAll=false",
"eject": "script/react-scripts eject",
"stylelint:run": "stylelint 'src/**/*.{js,jsx,ts,tsx}' && stylelint 'src/**/*.css' --config .stylelintrc-css.js",
"lint": "tsc --noEmit && eslint '*/**/*.{js,jsx,ts,tsx}' --quiet && yarn stylelint:run",
"stylelint:run:fix": "stylelint 'src/**/*.{js,jsx,ts,tsx}' --fix && stylelint 'src/**/*.css' --config .stylelintrc-css.js --fix",
"lint": "tsc --build && eslint '*/**/*.{js,jsx,ts,tsx}' --quiet --fix && pnpm stylelint:run",
"lint:fix": "tsc --build && eslint '*/**/*.{js,jsx,ts,tsx}' --quiet --fix && pnpm stylelint:run:fix",
"format": "prettier '**/*.+(css|graphql|json|less|md|mdx|sass|scss|yaml|yml)' --write"
},
"husky": {
Expand All @@ -20,7 +22,7 @@
"lint-staged": {
"linters": {
"*.+(js|jsx|ts|tsx)": [
"stylelint",
"stylelint --fix",
"eslint --quiet --fix",
"git add"
],
Expand Down Expand Up @@ -56,7 +58,7 @@
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"@types/styled-components": "^4.1.14",
"http-proxy-middleware": "^0.19.1",
"http-proxy-middleware": "^1.0.0",
"normalize.css": "^8.0.1",
"pluralize": "^8.0.0",
"react": "^17.0.1",
Expand All @@ -66,14 +68,16 @@
"typescript": "^4.1.3"
},
"devDependencies": {
"@types/history": "^4.7.8",
"@typescript-eslint/eslint-plugin": "^4.11.0",
"@typescript-eslint/parser": "^4.11.0",
"eslint": "^7.17.0",
"eslint-config-prettier": "^6.15.0",
"eslint-import-resolver-node": "^0.3.4",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-jest": "^24.1.0",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-no-array-sort-mutation": "../../libs/eslint-plugin-no-array-sort-mutation",
"eslint-plugin-no-array-sort-mutation": "workspace:*",
"eslint-plugin-no-null": "^1.0.2",
"eslint-plugin-prettier": "^3.1.4",
"eslint-plugin-react": "^7.21.5",
Expand Down
Loading

0 comments on commit 8fe2a7e

Please sign in to comment.