diff --git a/.changeset/config.json b/.changeset/config.json new file mode 100644 index 0000000..a9e6638 --- /dev/null +++ b/.changeset/config.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@3.0.2/schema.json", + "changelog": "@changesets/cli/changelog", + "commit": false, + "fixed": [["@eop/*"]], + "linked": [], + "access": "restricted", + "baseBranch": "master", + "updateInternalDependencies": "patch", + "ignore": [] +} diff --git a/.changeset/real-teachers-argue.md b/.changeset/real-teachers-argue.md new file mode 100644 index 0000000..954a92a --- /dev/null +++ b/.changeset/real-teachers-argue.md @@ -0,0 +1,13 @@ +--- +"@eop/typescript-config": major +"@eop/cornucopia-cards": major +"@eop/prettier-config": major +"@eop/eslint-config": major +"@eop/shared": major +"@eop/client": major +"@eop/server": major +--- + +Separate package into subpackages and add support for Node.js 18, 20 and 22. + +Support for Node.js 16 has been dropped. diff --git a/.dockerignore b/.dockerignore index 8b1e8e3..5476acc 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,6 +1,4 @@ node_modules/ deploy/ build/ -db/ -**/*.test.js -.heroku/ +dist/ diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index b6a69bb..0000000 --- a/.eslintignore +++ /dev/null @@ -1,3 +0,0 @@ -build/ -build-server/ -coverage/ \ No newline at end of file diff --git a/.eslintrc.cjs b/.eslintrc.cjs deleted file mode 100644 index f83c18e..0000000 --- a/.eslintrc.cjs +++ /dev/null @@ -1,82 +0,0 @@ -module.exports = { - parserOptions: { - ecmaVersion: 2021, - sourceType: 'module', - }, - - env: { - es2021: true, - }, - - extends: ['eslint:recommended', 'plugin:prettier/recommended'], - - rules: { - 'prettier/prettier': 0, - }, - - overrides: [ - { - // all typescript files - files: ['./src/**/*.ts', './src/**/*.tsx'], - extends: ['plugin:@typescript-eslint/recommended'], - plugins: ['@typescript-eslint'], - }, - { - // all client files - files: ['./src/index.tsx', './src/client/**'], - parserOptions: { - ecmaFeatures: { - jsx: true, - }, - }, - env: { - browser: true, - node: true, // needed because `process` etc. is used in client - }, - extends: ['plugin:react/recommended'], - plugins: ['react'], - settings: { - react: { - version: 'detect', - }, - }, - }, - { - // client typescript files - files: ['./src/index.tsx', './src/client/**/*.tsx'], - parserOptions: { - project: './tsconfig.json', - }, - }, - { - // non-client typescript files - files: [ - './src/server/**/*.ts', - './src/game/**/*.ts', - './src/utils/**/*.ts', - ], - parserOptions: { - module: 'tsconfig.server.json', - }, - }, - { - // all server files - files: ['./src/server/**'], - env: { - node: true, - }, - }, - { - // test files - files: ['./src/**/*.test.*'], - env: { jest: true }, - }, - { - // configuration files - files: ['./.eslintrc.cjs', './.prettierrc.cjs'], - env: { - node: true, - }, - }, - ], -}; diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 92eb92f..b6afb11 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -7,21 +7,26 @@ on: branches: [master] env: - node-version: '16.x' + DO_NOT_TRACK: '1' jobs: build: + strategy: + matrix: + node-version: ['18.x', '20.x', '22.x'] + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Use Node.js ${{ env.node-version }} - uses: actions/setup-node@v2 + - uses: actions/checkout@v4 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 with: - node-version: ${{ env.node-version }} + node-version: ${{ matrix.node-version }} cache: 'npm' - run: npm ci --prefer-offline - - run: npm run checkformat - - run: npm run lint - - run: npm run build - - run: npm test + - run: npx sherif + - run: npx turbo run checkformat + - run: npx turbo run lint + - run: npx turbo run build + - run: npx turbo run test diff --git a/.gitignore b/.gitignore index 3bae9fe..fe68771 100644 --- a/.gitignore +++ b/.gitignore @@ -1,16 +1,14 @@ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies -/node_modules +node_modules /.pnp .pnp.js -# testing -/coverage - # production -/build -/build-server +build +dist +*.tsbuildinfo # misc .DS_Store @@ -23,10 +21,11 @@ npm-debug.log* yarn-debug.log* yarn-error.log* -db/ -db-images/ deploy/ # IDE .idea .vscode + +# Turbo +.turbo \ No newline at end of file diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index 43650e6..0000000 --- a/.prettierignore +++ /dev/null @@ -1,3 +0,0 @@ -build/ -build-server/ -coverage/ diff --git a/README.md b/README.md index 8f810dc..ad7f388 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,9 @@ For the case of Elevation of Privilege, we partly support the latest version (as This repository allows you to build docker containers ready to be deployed e.g. to Kubernetes. -Frontend and backend are written in Javascript, although there are plans to migrate to Typescript. This game uses [boardgame.io](https://boardgame.io/) as a framework for turn based games. The backend furthermore exposes an API using [koa](https://koajs.com/). Frontend is written in [react](https://reactjs.org/). +The frontend and backend are written in TypeScript and use [boardgame.io](https://boardgame.io/) as a framework for turn based games. The backend furthermore exposes an API using [koa](https://koajs.com/). The frontend is written in [react](https://reactjs.org/). + +This repository uses [npm workspaces](https://docs.npmjs.com/cli/v10/using-npm/workspaces) to structure the different sub-packages and [Turborepo](https://turbo.build/repo) as a build system and task runner. ### Running the app There are two components that need to be started in order to run the game. @@ -99,20 +101,28 @@ This will start the app on port `8080` and make it accessible at [http://localho The docker-compose setup starts two containers: * `threats-client`: running `nginx` as a reverse proxy and serving the react application -* `threats-server`: running the `nodejs` backends: public API and game server +* `threats-server`: running the Node.js backend: public API and game server ![docker-compose setup](docs/docker-setup.svg) #### Local deployment -The server can be started using: +To start both the server and UI in dev mode, just run ```bash -npm run server +npx turbo run dev ``` -This will also automatically build the server first, compiling any TypeScript code, and then start the backend -application listening on the following ports: +For starting and building the individual applications separately, read on. + +The server can be started in dev mode using: + +```bash +npx turbo run dev --filter=@eop/server +``` + +This will build any dependencies of the server if necessary and then start the backend application in dev mode, +listening on the following ports: | Application | Description | Environment Variable | Default | |-------------|-------------------------------------------------------------------|----------------------|---------| @@ -120,33 +130,38 @@ application listening on the following ports: | Lobby API | Internal API for lobby operations, should not be exposed publicly | `INTERNAL_API_PORT` | 8002 | | Public API | Public API to create games and retrieve game info | `API_PORT` | 8001 | - If you want to build the server code manually, you can do so by running +If you want to build the server code manually, you can do so by running + +```bash +npx turbo run build --filter=@eop/server +``` + +You can also start the server in production mode: ```bash -npm run build:server +npx turbo run start --filter=@eop/server ``` -The UI can be started using +The UI can be started in dev mode using ```bash -npm start +npx turbo run dev --filter=@eop/client ``` -which starts a watch mode that automatically compiles and reloads the UI every time source files are changed. The UI is -accessible at [http://localhost:3000/](http://localhost:3000/). +The UI is accessible at [http://localhost:3000/](http://localhost:3000/). You can also build the client manually by running ```bash -npm run build:client +npx turbo run build --filter=@eop/client ``` -The UI can also be built and served statically (see the [dockerfile](docker/client.dockerfile)), keep in mind that the values of the port numbers will be hard coded in the generated files. +The UI can also be built and served statically (see the [Dockerfile](apps/client/Dockerfile)). Keep in mind that the values of the port numbers will be hard coded in the generated files. To build both the client and the server, just run ```bash -npm run build +npx turbo run build ``` #### Imprint and privacy notices @@ -162,22 +177,28 @@ are set when building the app. When building the client via docker these env vars can be set by defining `build-args` ```bash -docker build --build-arg "REACT_APP_EOP_IMPRINT=https://example.tld/imprint/" --build-arg "REACT_APP_EOP_PRIVACY=https://example.tld/privacy/" -f docker/client.dockerfile . -t "some-tag" +docker build --build-arg "REACT_APP_EOP_IMPRINT=https://example.tld/imprint/" --build-arg "REACT_APP_EOP_PRIVACY=https://example.tld/privacy/" -f apps/client/Dockerfile . -t "some-tag" ``` -### Using MongoDB +### Versioning -As of boardgame.io v0.39.0, MongoDB is no longer supported as a database connector. There is currently no external library providing this functionality, however there is an [implementation](https://github.com/boardgameio/boardgame.io/issues/6#issuecomment-656144940) posted on github. This class implements the abstract functions in [this base class](https://github.com/boardgameio/boardgame.io/blob/ce8ef4a16bcc420b05c5e0751b41f168352bce7d/src/server/db/base.ts#L49-L111). +This repository uses [Changesets](https://github.com/changesets/changesets) for versioning. -MongoDB has also been removed as a dependency so must be installed by running +When you introduce a change that warrants a version bump (e.g., a new feature or bug fix), please run ```bash -npm install mongodb +npx changeset add ``` -An equivalent to `ModelFlatFile` should also be implemented. This extends the FlatFile database connector to allow the model to be saved to the database. The functions this implements are `setModel`, which allows the model to be set, and `fetch`, which is also overwritten to allow the model to be read in addition to the other properties. The implementations of these for the FlatFile object are available in `ModelFlatFile.ts` +and follow the instructions to add a new changeset for the relevant packages. + +A release can then be performed by running + +```bash +npx changeset version +``` -Once the database connector is fully implemented, it can be used instead of a FlatFile by changing the object used in `config.ts`. Just replace `ModelFlatFile` with the name of the mongoDB database connector. +and committing and pushing the changes. ## Credits The card game Elevation of Privilege was originally invented by [Adam Shostack](https://adam.shostack.org/) at Microsoft and is licensed under [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/). The [EoP Whitepaper](http://download.microsoft.com/download/F/A/E/FAE1434F-6D22-4581-9804-8B60C04354E4/EoP_Whitepaper.pdf) written by Adam can be downloaded which describes the motivation, experience and lessons learned in creating the game. diff --git a/apps/client/.prettierignore b/apps/client/.prettierignore new file mode 100644 index 0000000..378eac2 --- /dev/null +++ b/apps/client/.prettierignore @@ -0,0 +1 @@ +build diff --git a/apps/client/Dockerfile b/apps/client/Dockerfile new file mode 100644 index 0000000..74af4cf --- /dev/null +++ b/apps/client/Dockerfile @@ -0,0 +1,29 @@ +FROM node:20-alpine AS base + +FROM base AS pruner +WORKDIR /app +ENV DO_NOT_TRACK=1 +COPY . . +RUN npm ci --workspaces=false +RUN npx turbo prune @eop/client --docker + +FROM base AS builder +WORKDIR /app +ENV DO_NOT_TRACK=1 +COPY --from=pruner /app/out/json/ . +COPY --from=pruner /app/out/package-lock.json ./package-lock.json +RUN npm ci +COPY --from=pruner /app/out/full/ . +ARG REACT_APP_EOP_IMPRINT +ARG REACT_APP_EOP_PRIVACY +ARG REACT_APP_EOP_BANNER_TEXT +ENV REACT_APP_EOP_IMPRINT=$REACT_APP_EOP_IMPRINT +ENV REACT_APP_EOP_PRIVACY=$REACT_APP_EOP_PRIVACY +ENV REACT_APP_EOP_BANNER_TEXT=$REACT_APP_EOP_BANNER_TEXT +RUN npx turbo run build + +FROM nginxinc/nginx-unprivileged:1.27.0-alpine +COPY --from=builder /app/apps/client/nginx/etc/nginx/nginx.conf /etc/nginx/nginx.conf +COPY --from=builder /app/apps/client/nginx/etc/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf +COPY --from=builder /app/apps/client/build /usr/share/nginx/html/ +EXPOSE 8080 diff --git a/apps/client/eslint.config.mjs b/apps/client/eslint.config.mjs new file mode 100644 index 0000000..e291661 --- /dev/null +++ b/apps/client/eslint.config.mjs @@ -0,0 +1,11 @@ +import config from '@eop/eslint-config/type-checked'; +import react from 'eslint-plugin-react/configs/recommended.js'; +import tseslint from 'typescript-eslint'; + +export default tseslint.config( + { ignores: ['build/'] }, + { languageOptions: { parserOptions: { project: './tsconfig.json' } } }, + { settings: { react: { version: 'detect' } } }, + react, + ...config, +); diff --git a/docker/files/etc/nginx/conf.d/default.conf b/apps/client/nginx/etc/nginx/conf.d/default.conf similarity index 100% rename from docker/files/etc/nginx/conf.d/default.conf rename to apps/client/nginx/etc/nginx/conf.d/default.conf diff --git a/docker/files/etc/nginx/nginx.conf b/apps/client/nginx/etc/nginx/nginx.conf similarity index 100% rename from docker/files/etc/nginx/nginx.conf rename to apps/client/nginx/etc/nginx/nginx.conf diff --git a/apps/client/package.json b/apps/client/package.json new file mode 100644 index 0000000..0b0cfcf --- /dev/null +++ b/apps/client/package.json @@ -0,0 +1,72 @@ +{ + "name": "@eop/client", + "type": "module", + "version": "0.24.0", + "main": "src/client/index.tsx", + "scripts": { + "build": "react-scripts build", + "dev": "react-scripts start", + "test": "react-scripts test --watchAll=false", + "test:watch": "react-scripts test", + "lint": "eslint .", + "fixlint": "eslint . --fix", + "checkformat": "prettier . --check", + "format": "prettier . --write" + }, + "dependencies": { + "@eop/cornucopia-cards": "*", + "@eop/shared": "*", + "@fortawesome/free-solid-svg-icons": "^5.15.3", + "@fortawesome/react-fontawesome": "^0.2.0", + "bootstrap": "^4.6.0", + "jointjs": "^3.7.7", + "lodash": "^4.17.21", + "react": "^17.0.2", + "react-countdown-circle-timer": "^3.2.1", + "react-dom": "^17.0.2", + "react-helmet": "^6.1.0", + "react-map-interaction": "^2.1.0", + "react-nl2br": "^1.0.4", + "react-router-dom": "^5.3.1", + "reactstrap": "^8.10.1", + "reactstrap-confirm": "^1.3.2", + "superagent": "^9.0.2" + }, + "devDependencies": { + "@babel/plugin-proposal-private-property-in-object": "^7.21.11", + "@eop/eslint-config": "*", + "@eop/prettier-config": "*", + "@eop/typescript-config": "*", + "@testing-library/jest-dom": "^5.16.5", + "@testing-library/react": "^12.1.5", + "@testing-library/user-event": "^14.5.2", + "@types/backbone": "^1.4.19", + "@types/react": "^17.0.43", + "@types/react-dom": "^17.0.14", + "@types/react-helmet": "^6.1.11", + "@types/react-router-dom": "^5.3.3", + "@types/superagent": "^8.1.7", + "eslint-plugin-react": "^7.34.3", + "nock": "^13.3.8", + "prettier": "^3.3.2", + "react-scripts": "^5.0.1", + "typescript": "^4.8.4", + "typescript-eslint": "^8.0.0-alpha.37" + }, + "eslintConfig": { + "extends": "react-app" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "private": true +} diff --git a/apps/client/prettier.config.js b/apps/client/prettier.config.js new file mode 100644 index 0000000..f213e04 --- /dev/null +++ b/apps/client/prettier.config.js @@ -0,0 +1,3 @@ +import config from '@eop/prettier-config'; + +export default config; diff --git a/public/favicon.ico b/apps/client/public/favicon.ico similarity index 100% rename from public/favicon.ico rename to apps/client/public/favicon.ico diff --git a/public/index.html b/apps/client/public/index.html similarity index 96% rename from public/index.html rename to apps/client/public/index.html index 06145ec..84484b1 100644 --- a/public/index.html +++ b/apps/client/public/index.html @@ -1,4 +1,4 @@ - +
diff --git a/public/logo.png b/apps/client/public/logo.png similarity index 100% rename from public/logo.png rename to apps/client/public/logo.png diff --git a/public/manifest.json b/apps/client/public/manifest.json similarity index 100% rename from public/manifest.json rename to apps/client/public/manifest.json diff --git a/src/client/components/banner/banner.css b/apps/client/src/components/banner/banner.css similarity index 100% rename from src/client/components/banner/banner.css rename to apps/client/src/components/banner/banner.css diff --git a/src/client/components/banner/banner.test.tsx b/apps/client/src/components/banner/banner.test.tsx similarity index 83% rename from src/client/components/banner/banner.test.tsx rename to apps/client/src/components/banner/banner.test.tsx index b3b892e..3b2933f 100644 --- a/src/client/components/banner/banner.test.tsx +++ b/apps/client/src/components/banner/banner.test.tsx @@ -19,13 +19,13 @@ describe('Banner', () => { expect(banner).toBeInTheDocument(); }); - it('should not render link if env var is not defined', async () => { + it('should not render link if env var is not defined', () => { // given process.env.REACT_APP_EOP_BANNER_TEXT = ''; render(