Skip to content

Commit

Permalink
Updated deploy process and some config
Browse files Browse the repository at this point in the history
  • Loading branch information
Mark Hanna committed Dec 16, 2023
1 parent 79fe0b7 commit 72e1d09
Show file tree
Hide file tree
Showing 11 changed files with 195 additions and 69 deletions.
33 changes: 32 additions & 1 deletion .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ module.exports = {
project: [
'./tsconfig.json',
'./scripts/tsconfig.json',
'./test/tsconfig.json',
],
},
extends: [
Expand All @@ -26,6 +27,7 @@ module.exports = {
],
ignorePatterns: [
'*.md',
'tsconfig.json',
'.eslintrc.cjs',
'stylelint.config.cjs',
'jest.config.js',
Expand All @@ -35,6 +37,18 @@ module.exports = {
// Overriding defaults //
/////////////////////////

// The `{}` type has many legitimate uses, primarily in "tagging"
// types to change some behaviours of the TypeScript compiler.
'@typescript-eslint/ban-types': [
'error',
{
'types': {
'{}': false,
},
'extendDefaults': true
}
],

// Sometimes it's useful to leave a name for an unused argument,
// in case it might be used in the future. Also, using a warning
// level makes it clearer when there's not a "real" error while
Expand Down Expand Up @@ -315,6 +329,9 @@ module.exports = {
exceptions: [
'/',
],
markers: [
'/',
],
block: {
balanced: true,
},
Expand Down Expand Up @@ -356,5 +373,19 @@ module.exports = {
},
},
],
}
},

overrides: [
{
files: ['*.{spec,test}.{j,t}{s,sx}'],
plugins: ['jest'],
extends: ['plugin:jest/recommended'],
},
{
files: ['scripts/**/*'],
rules: {
'no-console': 'off',
},
},
],
};
35 changes: 18 additions & 17 deletions .github/workflows/build-and-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ on:
push:
branches:
- main
permissions:
contents: write
jobs:
build-and-test:

permissions:
contents: write
runs-on: ubuntu-latest

strategy:
Expand All @@ -33,24 +33,25 @@ jobs:
- name: Test
run: npm test

- name: Upload GitHub Pages artifact
uses: actions/upload-pages-artifact@v2
with:
path: './app'

deploy:
needs: build-and-test

permissions:
contents: read
pages: write
id-token: write
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v3

- name: Install and Build
run: |
npm install
npm run build
env:
MODE: production
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}

- name: Deploy
uses: JamesIves/github-pages-deploy-action@v4
with:
branch: gh-pages
folder: app
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v3
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"typescript.tsdk": "node_modules\\typescript\\lib"
}
32 changes: 20 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,24 +60,30 @@ For more information on the differences, see [Differences between ES modules and

### Tests

The [Jest](https://jestjs.io/)-based test suite is configured in [jest.config.js](./jest.config.js). No custom test name matcher is specified, which means [Jest's default matcher](https://jestjs.io/docs/configuration#testmatch-arraystring) will be used:
The [Jest](https://jestjs.io/)-based test suite is configured in [jest.config.js](./test/jest.config.js). No custom test name matcher is specified, which means [Jest's default matcher](https://jestjs.io/docs/configuration#testmatch-arraystring) will be used:

> By default it looks for `.js`, `.jsx`, `.ts` and `.tsx` files inside of `__tests__` folders, as well as any files with a suffix of `.test` or `.spec` (e.g. `Component.test.js` or `Component.spec.js`). It will also find files called `test.js` or `spec.js`.
If any extra setup needs to be done before tests are run, such as polyfilling functionality not supported by `jsdom`, code for this can be placed in [jest.setup.ts](./test/jest.setup.ts').

### .env

See [.env](#env-1) for information on setting up a `.env` file.

## GitHub Pages

This project is set up to use a GitHub Action every time new code is pushed to the `main` branch. This `build-and-deploy` workflow runs the `build` npm script, then runs the test script, then if the tests passed it deploys the contents of the `app` directory by committing them to a `gh-pages` branch. This `gh-pages` branch should be configured in GitHub to be published to GitHub Pages.
This project is set up to use a GitHub Action every time new code is pushed to the `main` branch. This `build-and-deploy` workflow runs the `build` npm script, then runs the test script, then if the tests passed it deploys the contents of the `app` directory directly to GitHub Pages.

In order to allow the `main` branch to be used to publish to GitHub Pages, you need to set up an **environment** called `github-pages` in the settings for your project. This environment should be configured to allow branches with the name pattern `main` to deploy to GitHub Pages.

When publishing a project using [GitHub Pages](https://pages.github.com/), the project usually appears at a URL with a path, such as `https://cipscis.github.io/base-project`. This means using root relative URLs such as `/assets/css/main.css` would work locally, but would break when the project is published on GitHub Pages.
When publishing a project using [GitHub Pages](https://pages.github.com/), if you are not using a custom domain the project usually appears at a URL with a path, such as `https://cipscis.github.io/base-project`. This means using root relative URLs such as `/assets/css/main.css` would work locally, but would break when the project is published on GitHub Pages.

To fix this, the local Node.js server looks for a `PROJECT_NAME` variable in your [`.env`](#env-1) file. If it finds one, it sets up redirects so URLs starting with `/${PROJECT_NAME}` can be used as though they were root relative, so they will find your assets.
To fix this, the local Node.js server looks for a `PROJECT_NAME` variable in your [`.env`](#env-1) file. If it finds one, it sets up rewrites so you will need to use the same `/${PROJECT_NAME}/` paths during local development as would be required by GitHub Pages.

By default, the `index.html` file is configured to be published to GitHub Pages under the project name `base-project`. When you use it as a base for your own project, you will need to update these URLs.

If you are publishing to GitHub Pages using a custom domain, you can remove the `PROJECT_NAME` variable from your [`.env](#env-1) file and any `/${PROJECT_NAME}/` paths specified in other files.

---

**Delete everything above here when creating a new project**
Expand Down Expand Up @@ -112,22 +118,22 @@ Usually, you will just want to run `npm start`, but this project also provides t
* `npm start` runs both the `server` and `watch` tasks simultaneously.

* `npm test` runs any configured test suites using [Jest](https://jestjs.io/).
* `npm run testCoverage` runs any configured test suites using [Jest](https://jestjs.io/), and reports coverage information.
* `npm run testWatch` runs any configured test suites using [Jest](https://jestjs.io/) in watch mode.
* `npm run test:coverage` runs any configured test suites using [Jest](https://jestjs.io/), and reports coverage information.
* `npm run watch:test` runs any configured test suites using [Jest](https://jestjs.io/) in watch mode.

### .env

The `.env` file contains the following environment variables:

* `PROJECT_NAME` `(string)`
* `PROJECT_NAME: string`

If present, used by [Express](https://expressjs.com/) to set up redirects for emulating [GitHub Pages](#github-pages).

* `MODE` `(string 'development' | 'production')`
* `MODE: 'development' | 'production'`

Used by Webpack to determine what optimisations to use and how to generate sourcemaps.
Used to determine what optimisations to use when running the build process.

* `PORT` `(int)`
* `PORT: number`

Used by [Express](https://expressjs.com/) to determine which port to use when running a local Node.js server.

Expand Down Expand Up @@ -199,8 +205,10 @@ These dependencies are used when working on the project locally.

These dependencies are used for deploying the project to GitHub Pages.

* [checkout](https://github.com/marketplace/actions/checkout): Used to check out the repository to a workspace so it can be built
* [checkout](https://github.com/marketplace/actions/checkout): Used to check out the repository to a workspace so it can be built.

* [setup-node](https://github.com/marketplace/actions/setup-node-js-environment): Use to set up a Node.JS environment for the build and test scripts to run on during the deployment process.

* [Deploy to GitHub Pages](https://github.com/marketplace/actions/deploy-to-github-pages): Used to deploy the project to GitHub pages once it has been built
* [upload-pages-artifact](https://github.com/marketplace/actions/upload-github-pages-artifact): Used to upload an artifact to use for deploying to GitHub Pages.

* [deploy-pages](https://github.com/marketplace/actions/deploy-github-pages-site): Used to deploy the artifact to GitHub Pages.
14 changes: 14 additions & 0 deletions app/404.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Page not found</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex">


<link rel="stylesheet" href="/assets/css/main.css">
</head>
<body>
<h1>Page not found</h1>
</body>
</html>
6 changes: 3 additions & 3 deletions app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
<html lang="en">
<head>
<title>Index</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="viewport" content="width=device-width, initial-scale=1">

<meta name="description" content="Index page." />
<meta name="description" content="Index page.">

<link rel="stylesheet" href="/base-project/assets/css/main.css" />
<link rel="stylesheet" href="/base-project/assets/css/main.css">
<script type="module" src="/base-project/assets/js/dist/main.js"></script>
</head>
<body>
Expand Down
50 changes: 24 additions & 26 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,23 @@
"scripts": {
"server": "node --loader ts-node/esm scripts/server.ts",

"buildJs": "concurrently \"tsc --noEmit\" \"node --loader ts-node/esm scripts/build.ts\"",
"buildCss": "sass app/assets/scss:app/assets/css",
"build": "concurrently \"npm run buildJs\" \"npm run buildCss\"",
"build:js": "concurrently \"tsc\" \"node --loader ts-node/esm scripts/build.ts\"",
"build:css": "sass app/assets/scss:app/assets/css",
"build": "concurrently \"npm:build:*\"",

"pretest": "tsc --noEmit",
"test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest",
"testCoverage": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --collectCoverage",
"test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --config=./test/jest.config.js",
"test:coverage": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --config=./test/jest.config.js --collectCoverage",

"watchJs": "node --loader ts-node/esm scripts/build-watch.ts",
"watchCss": "sass app/assets/scss:app/assets/css --watch",
"testWatch": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --watch",
"watch": "concurrently --kill-others \"tsc --watch --preserveWatchOutput --noEmit\" \"npm run watchJs\" \"npm run watchCss\" \"npm run testWatch\"",
"watch:js": "node --loader ts-node/esm scripts/build-watch.ts",
"watch:css": "sass app/assets/scss:app/assets/css --watch",
"watch:test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --config=./test/jest.config.js --watch",
"watch": "concurrently --kill-others \"tsc --watch --preserveWatchOutput\" \"npm:watch:*\"",

"lintJs": "eslint app/assets/js/src/**",
"lintCss": "stylelint app/assets/scss/**/*.scss",
"lint": "npm run lintJs && npm run lintCss",
"lint:js": "eslint app/assets/js/src/**",
"lint:css": "stylelint app/assets/scss/**/*.scss",
"lint": "npm run lint:js && npm run lint:css",

"start": "concurrently --kill-others \"npm run server\" \"npm run watch\"",
"prepare": "npm test"
"start": "concurrently --kill-others \"npm run server\" \"npm run watch\""
},
"author": "Mark Hanna",
"repository": {
Expand All @@ -35,26 +33,26 @@
"license": "Hippocratic-2.1",
"devDependencies": {
"@jest/globals": "^29.7.0",
"@stylistic/eslint-plugin": "^1.0.0",
"@testing-library/jest-dom": "^6.1.4",
"@stylistic/eslint-plugin": "^1.5.1",
"@testing-library/jest-dom": "^6.1.5",
"@testing-library/user-event": "^14.5.1",
"@types/express": "^4.17.20",
"@typescript-eslint/eslint-plugin": "^6.9.0",
"@typescript-eslint/parser": "^6.9.0",
"@types/express": "^4.17.21",
"@typescript-eslint/eslint-plugin": "^6.14.0",
"@typescript-eslint/parser": "^6.14.0",
"concurrently": "^8.2.2",
"cross-env": "^7.0.3",
"dotenv": "^16.3.1",
"esbuild": "^0.19.5",
"eslint": "^8.52.0",
"esbuild": "^0.19.9",
"eslint": "^8.55.0",
"express": "^4.18.2",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"sass": "^1.69.5",
"stylelint": "^15.11.0",
"stylelint-config-recommended-scss": "^13.0.0",
"stylelint": "^16.0.2",
"stylelint-config-recommended-scss": "^14.0.0",
"ts-jest": "^29.1.1",
"ts-node": "^10.9.1",
"typescript": "^5.2.2"
"ts-node": "^10.9.2",
"typescript": "^5.3.3"
},
"engines": {
"node": ">=20"
Expand Down
42 changes: 33 additions & 9 deletions scripts/server.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,51 @@
import dotenv from 'dotenv';
dotenv.config();
import { dirname, join } from 'path';
import { fileURLToPath } from 'url';

import dotenv from 'dotenv';
import express from 'express';

const __dirname = dirname(fileURLToPath(import.meta.url));

dotenv.config();
const app = express();

const port = process.env.PORT;
const projectName = process.env.PROJECT_NAME;

app.use(express.static('app'));

if (projectName) {
// GitHub Pages publishes projects to <username>.github.io/<projectname>
// This breaks root-relative URLs, so instead use "/projectname/path/" locally
// and resolve it by redirecting it here to a root relative path.
const ghPagesPathPattern = new RegExp(`^/${projectName}(/|$)`, 'i');

const ghPagesPathPattern = new RegExp(`^/${projectName}/`, 'i');
app.get(ghPagesPathPattern, (req, res) => {
const path = req.url.replace(ghPagesPathPattern, '/');
const url = `http://${req.headers.host}${path}`;
app.use((request, response, next) => {
if (!ghPagesPathPattern.test(request.url)) {
response.status(404);
response.send(`<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>Cannot GET ${request.url}
Did you mean <a href="/${projectName}${request.url}">/${projectName}${request.url}</a>?</pre>
</body>
</html>`);
return;
}

res.redirect(url);
request.url = request.url.replace(ghPagesPathPattern, '/');
next();
});
}

app.use(express.static('app'));

// Anything not already handled is a 404
app.get('*', (request, response, next) => {
response.status(404).sendFile(join(__dirname, '../app/404.html'));
});

app.listen(port, () => {});
console.log(`Listening on port ${port}`);
4 changes: 3 additions & 1 deletion jest.config.js → test/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const config = {
// Don't inject globals. Require them to be imported from `@jest/globals`
injectGlobals: false,
// Specify where the tests are
rootDir: './app',
rootDir: '../app',
// Tell Jest how to follow module resolution rules based on tsconfig's baseUrl
moduleDirectories: ['node_modules', './app/assets/js/src'],
// Provide a mocked DOM environment for tests
Expand All @@ -19,6 +19,8 @@ const config = {
testEnvironmentOptions: {
customExportConditions: ['node'],
},
// Run a setup script before all test suites
setupFilesAfterEnv: ['../test/jest.setup.ts'],

// Allow tests to be written in TypeScript using ESM syntax
preset: 'ts-jest/presets/default-esm',
Expand Down
5 changes: 5 additions & 0 deletions test/jest.setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { beforeAll } from '@jest/globals';

beforeAll(() => {
// Any test setup, such as polyfilling features not supported by jsdom, can go here
});
Loading

0 comments on commit 72e1d09

Please sign in to comment.