Skip to content

Commit

Permalink
fix: draft 2nd blog post project automation (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
Eomm committed Sep 6, 2020
1 parent 9383c94 commit d2e4a6d
Show file tree
Hide file tree
Showing 11 changed files with 272 additions and 32 deletions.
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
NODE_ENV=development
BASE_URL=http://localhost:3000
DISCORD_CLIENT_ID=12345678
DISCORD_SECRET=XXXXXXXXXXXXXXXXX
45 changes: 45 additions & 0 deletions .github/workflows/cd.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: cd

on:
push:
branches:
- release

jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2

- name: Version Bump
uses: phips28/gh-action-bump-version@master
with:
skip-tag: true
minor-wording: add:,minor,feat

- name: Build Changelog message
id: changelog
uses: scottbrenner/generate-changelog-action@master
env:
REPO: ${{ github.repository }}

- name: Create Github Release
id: create_release
uses: actions/create-release@latest
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: v${{ github.ref }}
release_name: Release ${{ github.ref }}
body: |
${{ steps.changelog.outputs.changelog }}
draft: false
prerelease: false

- name: Deploy to Heroku
uses: akhileshns/heroku-deploy@v3.4.6
with:
heroku_api_key: ${{ secrets.HEROKU_API_KEY }}
heroku_app_name: ${{ secrets.HEROKU_APP_NAME }}
heroku_email: ${{ secrets.HEROKU_EMAIL }}
healthcheck: "https://${{ secrets.HEROKU_APP_NAME }}.herokuapp.com/health"
29 changes: 29 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: ci

on:
pull_request:

env:
CI: true

jobs:
test:
runs-on: ${{ matrix.os }}

strategy:
matrix:
node-version: [10.x, 12.x, 14.x]
os: [ubuntu-latest]

steps:
- uses: actions/checkout@v2

- name: Use Node.js
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}

- name: Install
run: npm install --ignore-scripts
- name: Run tests
run: npm test
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ and here you will find the source code and the commit history!
## Posts

1. [A Discord app with Fastify!](https://dev.to/eomm/a-discord-app-with-fastify-3h8c) - [📝](./posts/01-init-application.md)
2. COOMING SOON
2. Draft: [Project Automation](TODO) - [📝](./posts/02-project-automation.md)
3. COOMING SOON

## Contributing

Expand Down
30 changes: 20 additions & 10 deletions lib/app.js → lib/app.mjs
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
'use strict'

const path = require('path')
const env = require('fastify-env')
const helmet = require('fastify-helmet')
const fastifyStatic = require('fastify-static')
const pointOfView = require('point-of-view')
const handlebars = require('handlebars')
const authRoutes = require('./auth')
import fs from 'fs'
import path from 'path'
import env from 'fastify-env'
import helmet from 'fastify-helmet'
import fastifyStatic from 'fastify-static'
import pointOfView from 'point-of-view'
import handlebars from 'handlebars'
import { fileURLToPath } from 'url'

import authRoutes from './auth.mjs'

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

const appVersion = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'))).version

const schema = {
type: 'object',
Expand All @@ -19,7 +25,7 @@ const schema = {
}
}

module.exports = function app (fastify, opts, next) {
export default function app (fastify, opts, next) {
// will load fastify.config
fastify.register(env, { schema, dotenv: true })

Expand All @@ -39,7 +45,11 @@ module.exports = function app (fastify, opts, next) {
})

fastify.get('/', function (request, reply) {
reply.sendFile('homepage.html')
reply.view('/pages/homepage.hbs', { version: appVersion })
})

fastify.get('/health', function (request, reply) {
reply.send('I am fine thanks')
})

fastify.setNotFoundHandler(function (request, reply) {
Expand Down
7 changes: 3 additions & 4 deletions lib/auth.js → lib/auth.mjs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
'use strict'

const oauth2 = require('fastify-oauth2')
const got = require('got')
import oauth2 from 'fastify-oauth2'
import got from 'got'

module.exports = function auth (fastify, opts, next) {
export default function auth (fastify, opts, next) {
fastify.register(oauth2, {
name: 'discordOAuth2',
credentials: {
Expand Down
17 changes: 17 additions & 0 deletions lib/start.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@

import Fastify from 'fastify'
import app from './app.mjs'

const server = Fastify({
logger: true,
pluginTimeout: 10000
})

server.register(app)

server.listen(process.env.PORT || 3000, '0.0.0.0', (err) => {
if (err) {
server.log.error(err)
process.exit(1)
}
})
19 changes: 10 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
"version": "1.0.0",
"description": "A Discord APP built with Fastify!",
"main": "lib/app.js",
"type": "module",
"scripts": {
"start": "fastify start -a 0.0.0.0 lib/app.js",
"start:dev": "fastify start -w lib/app.js",
"start": "node lib/start.mjs",
"start:dev": "nodemon lib/start.mjs",
"lint:fix": "standard --fix",
"test": "standard && tap test/**/*.test.js"
"test": "standard && tap test/**/*.test.mjs"
},
"repository": {
"type": "git",
Expand All @@ -21,18 +22,18 @@
},
"homepage": "https://github.com/Eomm/fastify-discord-bot-demo#readme",
"dependencies": {
"fastify": "^3.2.1",
"fastify-cli": "^2.2.0",
"fastify": "^3.3.0",
"fastify-env": "^2.0.1",
"fastify-helmet": "^5.0.0",
"fastify-oauth2": "^4.1.0",
"fastify-helmet": "^5.0.1",
"fastify-oauth2": "^4.2.0",
"fastify-static": "^3.2.0",
"got": "^11.5.2",
"got": "^11.6.0",
"handlebars": "^4.7.6",
"point-of-view": "^4.3.0"
"point-of-view": "^4.5.0"
},
"devDependencies": {
"nock": "^13.0.4",
"nodemon": "^2.0.4",
"standard": "^14.3.4",
"tap": "^14.10.8"
}
Expand Down
2 changes: 1 addition & 1 deletion pages/homepage.html → pages/homepage.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ <h5 class="card-title">Hi Guest!</h5>
</p>
</div>
<div class="card-footer text-muted">
Demo <a href="https://github.com/Eomm/fastify-discord-bot-demo" target="_blank">repository</a>
Demo <a href="https://github.com/Eomm/fastify-discord-bot-demo" target="_blank">repository</a> - v {{version}}
</div>
</div>
</body>
Expand Down
125 changes: 125 additions & 0 deletions posts/02-project-automation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# Project Automation

This tutorial is the following of the "[A Discord app with Fastify!](https://dev.to/eomm/a-discord-app-with-fastify-3h8c)".

## Road to ES Import

Node.js used the CommonJS module system (CJS) since the beginning and recently it has added support to ECMAScript Modules (ESM)
([without the `--experimental-modules` flag](https://github.com/nodejs/node/blob/master/doc/changelogs/CHANGELOG_V12.md#ecmascript-modules-----experimental-modules-flag-removal)).

So to update this project to the ESM module there are many possibilities, described in [this article](https://medium.com/@nodejs/announcing-core-node-js-support-for-ecmascript-modules-c5d6dc29b663) by Node.js Module Team.

I will follow the one that makes sense to me to a project written in CJS as first the implementation:

- add `"type": "module"` in the `package.json`
- rename the `js` files to the `mjs` extension
- fix the `__dirname` usage since it is not supported in ESM
- remove all the `require` in favour of `import`

Note that it is mandatory adding the file extension to local files import:

```js
import authRoutes from './auth.js'
```

- remove `'use strict'` since it is the default behaviour with ESM
- update the `module.exports` to `export default function app (fastify, opts, next) {..`
- fix the start script since `fastify-cli` doesn't support the ESM loading right now


## CI/CD

Adding CI/CD to the project is quite simple thanks to [GitHub Actions](https://github.com/features/actions)
and the great community around them!

### Continuous Integration

We want to run the tests automatically whenever there is a Pull Request, so the actions to take are:

```yml
#...
steps:
# checkout the project
- uses: actions/checkout@v2

# install nodejs on the Virtual Machine
- name: Use Node.js
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}

# install the project
- name: Install
run: npm install --ignore-scripts

# run the test on the project itself
- name: Run tests
run: npm test
```

### Continuous Delivery

The delivery of our application needs just to push new commits to the Heroku remote git server.
Moreover, it would be useful for our customers to see what version of the application is running and an
updated CHANGELOG file.

To automate these steps, it is necessary to define a good workflow in the first place.

For examples, the process should answer questions like:
- When releasing the application?
- What semver is the new version?
- What changes should be written in the changelog?
- Must any scripts be executed? - like a database update
- Should notification be sent?
- a lot of other headaches!!

This application will adopt a process like this:
- at every merge in the `release` branch
- using a commit messages format like [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/)
- bump a new semver version based on the commit messages
- tag the version
- generate a changelog description, grouping the commit messages
- freeze the release on GitHub with the changelog text
- deploy to Heroku

This list can be easily transformed into a GitHub Action where every step will accomplish one of these tasks and the result will be like this (omitting parameters):

```yml
- uses: actions/checkout@v2

- name: Version Bump
uses: phips28/gh-action-bump-version@master
...

- name: Build Changelog message
uses: scottbrenner/generate-changelog-action@master
...

- name: Create Github Release
uses: actions/create-release@latest
...

- name: Deploy to Heroku
uses: akhileshns/heroku-deploy@v3.4.6
...
```

Check out [the source code](https://github.com/Eomm/fastify-discord-bot-demo/tree/master/.github/workflows/cd.yml) to see the complete file.

## End

In the next posts we will:

+ add new features to the application:
+ store the token in cookies
+ add some `/api` endpoints


## Side effects

To write this article:
- I created an [issue](https://github.com/fastify/fastify-cli/issues/267) to `fastify-cli` to support ESM
- I added to the `gh-action-bump-version` GitHub Action:
- support new pattern matching strings [#36](https://github.com/phips28/gh-action-bump-version/pull/36)
- skip the tagging phase [#37](https://github.com/phips28/gh-action-bump-version/pull/37)
- I fixed a VSCode icon pack extension [#178](https://github.com/EmmanuelBeziat/vscode-great-icons/pull/178)
26 changes: 19 additions & 7 deletions test/bot.test.js → test/bot.test.mjs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
'use strict'

const fs = require('fs')
const t = require('tap')
const nock = require('nock')
const Fastify = require('fastify')
const oauth2 = require('fastify-oauth2')
import fs from 'fs'
import t from 'tap'
import nock from 'nock'
import Fastify from 'fastify'
import oauth2 from 'fastify-oauth2'

const app = require('../lib/app')
import app from '../lib/app.mjs'

const fakeToken = {
token: {
Expand All @@ -19,6 +19,13 @@ const fakeToken = {
}

t.beforeEach(function (done, childTest) {
process.env = {
NODE_ENV: 'development',
BASE_URL: 'http://localhost:3000',
DISCORD_CLIENT_ID: '12345678',
DISCORD_SECRET: 'XXXXXXXXXXXXXXXXX'
}

const server = Fastify()
server.register(app)
childTest.context.server = server
Expand All @@ -32,7 +39,12 @@ t.test('the application starts', async t => {

t.test('the application load the homepage', async t => {
const res = await t.context.server.inject('/')
t.equal(res.payload, fs.readFileSync('./pages/homepage.html', 'utf8'))
t.equal(res.payload.substr(0, 500), fs.readFileSync('./pages/homepage.hbs', 'utf8').substr(0, 500))
})

t.test('the application has an health check', async t => {
const res = await t.context.server.inject('/health')
t.equal(res.statusCode, 200)
})

t.test('the application redirect when 404', async t => {
Expand Down

0 comments on commit d2e4a6d

Please sign in to comment.