How we configure each GitHub repo to maximize teamwork
Overview
This article describes how to configure GitHub to maximize teamwork.
NOTE: Content here are my personal opinions, and not intended to represent any employer (past or present). “PROTIP:” here highlight information I haven’t seen elsewhere on the internet because it is hard-won, little-know but significant facts based on my personal research and experience.
.gitignore in root folder
This defines all the folders and files which should NOT be pushed up to GitHub for the team to see. We look at this first because it summarizes many of the various utilities used:
See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# macOS: .DS_Store # AWS CDK infrastructure/cdk.out # NodeJs dependencies: node_modules npm-debug.log* yarn-debug.log* yarn-error.log* /.pnp .pnp.js # testing /coverage # production /build # misc .env.local .env.development.local .env.test.local .env.production.local # ReactJs: webapp/public/config.json webapp/build
package.json in root
For NodeJs developers, there are two competing utilities to automate install of dependencies specified in import statements within NodeJs code:: npm vs. yarn:
-
yarn users create a yarn.json file.
-
npm users create a package.json file
Here is a sample package.json file:
{ "name": "ps-serverless-app", "private": true, "workspaces": [ "infrastructure", "webapp", "services/*" ], "scripts": { "lint": "npx eslint . --fix --ext .js,.ts,.jsx", "test": "", "load:sampleData": "aws dynamodb batch-write-item --request-items file://sampleData.json" }, "devDependencies": { "@types/jest": "^26.0.15", "@typescript-eslint/eslint-plugin": "^4.6.1", "@typescript-eslint/parser": "^4.6.1", "@babel/eslint-parser": "^7.13.14", "eslint-config-airbnb": "^18.2.1", "esbuild": "0", "eslint": "^7.12.1", "eslint-config-airbnb-base": "^14.2.1", "eslint-config-airbnb-typescript": "^12.0.0", "eslint-config-airbnb-typescript-prettier": "^3.1.0", "eslint-config-prettier": "^6.15.0", "eslint-import-resolver-node": "^0.3.4", "eslint-import-resolver-typescript": "^2.3.0", "eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-import": "^2.22.1", "eslint-plugin-jest": "^24.1.0", "eslint-plugin-prettier": "^3.1.4", "eslint-plugin-promise": "^4.2.1", "eslint-plugin-simple-import-sort": "^5.0.3", "eslint-plugin-unicorn": "^23.0.0", "husky": "^4.3.0", "jest-html-reporter": "^3.3.0", "jest-junit": "^12.0.0", "lint-staged": "^10.5.1", "prettier": "^2.1.2", "prettier-eslint": "^11.0.0", "ts-jest": "^26.4.3", "typescript": "^4.0.5" }, "resolutions": { "@typescript-eslint/eslint-plugin": "^4.6.1", "@typescript-eslint/parser": "^4.6.1", "jest": "26.6.0" } }
-
“dependencies” are packages required by the app in production.
-
“devDependencies” are packages that are only needed for local development and testing.
-
“resolutions” specifies custom package versions or ranges instead of waiting for the author of transitive sub-dependencies to update their package dependencies (to resolve a GitHub Dependabot alert). Or your dependency defines a broad version range and your sub-dependency has a problematic update so you want to pin it to an earlier version.
"resolutions": { "@typescript-eslint/eslint-plugin": "^4.6.1", "@typescript-eslint/parser": "^4.6.1", "jest": "26.6.0"
So “jest”: “26.6.0” serves to specify a version without the security vulnerability which should be used inside transitive dependencies.
If you use yarn, the above is instead of manual edits in the yarn.lock file before running yarn install.
Scripts in npm package.json
References:
- https://levelup.gitconnected.com/understanding-dependency-management-with-node-modules-1c47bcdee98b
- https://medium.com/learnwithrahul/understanding-npm-dependency-resolution-84a24180901b
- https://www.digitalocean.com/community/tutorials/how-to-use-node-js-modules-with-npm-and-package-json
- https://juffalow.com/blog/javascript/how-yarn-resolutions-can-save-you/
- https://www.linkedin.com/learning/github-dependabot-dependency-updates/get-started-with-dependabot
However, if you use npm rather than yarn, you can change the package-lock.json file and running npm, use overrides use github.com/rogeriochaves/npm-force-resolutions which forces the installation of specific version of a transitive dependency, as a last resort after contacting maintainers of top-level dependency libraries.
-
The utility uses Clojure, which requires Java JDK 6+. Clojure has the Leiningen build tool. So install Clojure (one time on the macOS machine running npm)
https://gist.github.com/fscm/4e9b719c7556997d54068556a7101630
brew install clojure/tools/clojure
NOTE: Clojure has rlwarp for tab completions and parenthesis matching.
-
Verify install:
which clojure
Response:
/usr/local/bin/clojure
-
Cleanup: remove the downloaded installers:
rm -rf "${HOME}/.src"
-
Clojure has an uncommon way to check whether it is installed:
clj -e '(+ 1 1)'
should return 2 and take you back to the Terminal prompt.
NOTE: clj -e nil returns “WARNING: Implicit use of clojure.main with options is deprecated, use -M” and puts you in the Clojure REPL “user=>”.
-
To invoke the script using npx, add to package-json
{, "scripts": { "preinstall": "npx npm-force-resolutions"
-
Run npm install as you would normally do at your project’s folder:
npm install
-
Use a text editor to open the package-lock.json file created:
cat package-lock.json
This file should be comitted and pushed to GitHub (it should not be specified within .gitignore).
-
Verify that the package version has been updated to the new value.
ESLint install
- In package.json, npx eslint temporarily installs and runs eslint (on every run).
"resolutions": { "@typescript-eslint/eslint-plugin": "^4.6.1", "@typescript-eslint/parser": "^4.6.1",
-
In .vscode/settings.json
This invokes ESLint on each save:
{ "editor.codeActionsOnSave": { "source.fixAll.eslint": true } }
ESLint catches “code smells” according to a set of rules hopefully agreed upon by the team, which is why it’s in the team’s GitHub instead of locally on your laptop.
-
In file .eslintignore
dist cdk.out node_modules build
-
In file .eslintrc
{ "root": true, "parser": "@babel/eslint-parser", "parserOptions": { "requireConfigFile": false } }
babel.config.js in root
-
Within babel.config.js in root folder:
module.exports = { presets: ['@babel/preset-env', '@babel/preset-react'], targets: { node: 'current', }, };