├── .DS_Store
├── .dockerignore
├── .github
└── workflows
│ └── cd-workflow.yml
├── .gitignore
├── Dockerfile
├── LICENSE
├── README.md
├── __test__
└── game.test.js
├── babel.config.js
├── index.html
├── package.json
├── public
├── .DS_Store
└── index.css
└── src
├── game.js
├── index.js
└── webpack.config.js
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/githubtraining/github-actions-for-packages-demo/bb737d4f0903432b79b54df24814a6f6259b07b1/.DS_Store
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | __test__
2 | node_modules
3 | .dockerignore
4 | .gitignore
5 | .github
6 | babel.config.js
7 | Dockerfile
8 | LICENSE
9 | package-lock.json
10 | package.json
11 | README.md
--------------------------------------------------------------------------------
/.github/workflows/cd-workflow.yml:
--------------------------------------------------------------------------------
1 | name: Docker CD
2 |
3 | on:
4 | push:
5 | # branches-ignore:
6 | # - "ci-workflow"
7 | # - "docker-workflow"
8 | paths:
9 | - "**Dockerfile**"
10 |
11 | jobs:
12 | build:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: actions/checkout@v1
16 | - name: npm install and build webpack
17 | run: |
18 | npm install
19 | npm run build
20 | - uses: actions/upload-artifact@master
21 | with:
22 | name: webpack artifacts
23 | path: public/
24 |
25 | test:
26 | runs-on: ubuntu-latest
27 | needs: build
28 | strategy:
29 | matrix:
30 | os: [ubuntu-lastest, windows-2016]
31 | node-version: [8.x, 10.x]
32 |
33 | steps:
34 | - uses: actions/checkout@v1
35 | - name: Use Node.js ${{ matrix.node-version }}
36 | uses: actions/setup-node@v1
37 | with:
38 | node-version: ${{ matrix.node-version }}
39 | - uses: actions/download-artifact@master
40 | with:
41 | name: webpack artifacts
42 | path: public
43 | - name: npm install, and test
44 | run: |
45 | npm install
46 | npm test
47 | env:
48 | CI: true
49 |
50 | Build-and-Push-Docker-Image:
51 | runs-on: ubuntu-latest
52 | needs: test
53 | name: Docker Build, Tag, Push
54 |
55 | steps:
56 | - name: Checkout
57 | uses: actions/checkout@v1
58 | - name: Download built artifact
59 | uses: actions/download-artifact@master
60 | with:
61 | name: webpack artifacts
62 | path: public
63 | - name: Build, Tag, Push
64 | uses: mattdavis0351/actions/docker-gpr@v1
65 | with:
66 | repo-token: ${{ secrets.GITHUB_TOKEN }}
67 | image-name: tic-tac-toe-for-packages # this image-name is changed for the demo
68 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | package-lock.json
3 | public/*.js
4 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # Dockerfile contents
2 | FROM nginx:1.17
3 | COPY . /usr/share/nginx/html
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 The GitHub Training Team
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Tic Tac Toe Game
2 |
3 | Let's learn about CD while using GitHub Actions and the GitHub Package Registry!
4 |
5 |
6 | Head over to your first [Pull Request](../../pull/1) to get started
--------------------------------------------------------------------------------
/__test__/game.test.js:
--------------------------------------------------------------------------------
1 | const Game = require('../src/game').default
2 |
3 | describe('Game', () => {
4 | let game, p1, p2
5 | beforeEach(() => {
6 | p1 = 'Salem'
7 | p2 = 'Nate'
8 | game = new Game(p1, p2)
9 | })
10 |
11 | describe('Game', () => {
12 | it('Initializes with two players', async () => {
13 | expect(game.p1).toBe('Salem')
14 | expect(game.p2).toBe('Nate')
15 | })
16 |
17 | it('Initializes with an empty board', async () => {
18 | for (let r = 0; r < game.board.length; r++) {
19 | for (let c = 0; c < game.board[r].lenght; c++) {
20 | expect(game.board[r][c]).toBeUndefined()
21 | }
22 | }
23 | })
24 |
25 | it('Starts the game with a random player', async () => {
26 | Math.random = () => 0.4
27 | expect(new Game(p1, p2).player).toBe('Salem')
28 |
29 | Math.random = () => 0.6
30 | expect(new Game(p1, p2).player).toBe('Nate')
31 | })
32 | })
33 |
34 | describe('turn', () => {
35 | it("Inserts an 'X' into the top center", async () => {
36 | game.turn(0, 1)
37 | expect(game.board[0][1]).toBe('X')
38 | })
39 |
40 | it("Inserts an 'X' into the top left", async () => {
41 | game.turn(0)
42 | expect(game.board[0][0]).toBe('X')
43 | })
44 | })
45 |
46 | describe('nextPlayer', () => {
47 | it('Sets the current player to be whoever it is not', async () => {
48 | Math.random = () => 0.4
49 | const game = new Game(p1, p2)
50 | expect(game.player).toBe('Salem')
51 | game.nextPlayer()
52 | expect(game.player).toBe('Nate')
53 | })
54 | })
55 |
56 | describe('hasWinner', () => {
57 | it('Wins if any row is filled', async () => {
58 | for (let r = 0; r < game.board.length; r++) {
59 | for (let c = 0; c < game.board[r].length; c++) {
60 | game.board[r][c] = 'X'
61 | }
62 | expect(game.hasWinner()).toBe(true)
63 |
64 | for (let c = 0; c < game.board[r].length; c++) {
65 | game.board[r][c] = null
66 | }
67 | }
68 | })
69 |
70 | it('Wins if any column is filled', async () => {
71 | for (let r = 0; r < game.board.length; r++) {
72 | for (let c = 0; c < game.board[r].length; c++) {
73 | game.board[c][r] = 'X'
74 | }
75 | expect(game.hasWinner()).toBe(true)
76 |
77 | for (let c = 0; c < game.board[r].length; c++) {
78 | game.board[c][r] = null
79 | }
80 | }
81 | })
82 |
83 | it('Wins if down-left diagonal is filled', async () => {
84 | for (let r = 0; r < game.board.length; r++) {
85 | game.board[r][r] = 'X'
86 | }
87 | expect(game.hasWinner()).toBe(true)
88 | })
89 |
90 | it('Wins if up-right diagonal is filled', async () => {
91 | for (let r = 0; r < game.board.length; r++) {
92 | game.board[2 - r][r] = 'X'
93 | }
94 | expect(game.hasWinner()).toBe(true)
95 | })
96 | })
97 | })
98 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | [
4 | '@babel/preset-env',
5 | {
6 | targets: {
7 | node: 'current'
8 | }
9 | }
10 | ]
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | TicTacToe
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
It's 's turn!
16 |
17 | wins!
18 |
19 |
20 |
21 |
22 |
23 |
24 | |
25 | |
26 | |
27 |
28 |
29 | |
30 | |
31 | |
32 |
33 |
34 | |
35 | |
36 | |
37 |
38 |
39 |
40 |
Score
41 |
42 |
43 |
44 | |
45 | |
46 |
47 |
48 |
49 |
50 | 0 |
51 | 0 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "actions-template",
3 | "version": "1.0.0",
4 | "description": "Learn how to use GitHub Actions!",
5 | "main": "index.js",
6 | "directories": {
7 | "test": "__test__",
8 | "src": "src"
9 | },
10 | "scripts": {
11 | "build": "npx webpack --config ./src/webpack.config.js --mode production",
12 | "dev": "npx webpack --config ./src/webpack.config.js --mode development --watch",
13 | "test": "npx standard && npx jest"
14 | },
15 | "author": "githubtraining",
16 | "license": "MIT",
17 | "standard": {
18 | "env": [
19 | "jest",
20 | "browser"
21 | ]
22 | },
23 | "devDependencies": {
24 | "@babel/core": "^7.2.2",
25 | "@babel/preset-env": "^7.2.3",
26 | "@babel/register": "^7.0.0",
27 | "babel-jest": "^24.9.0",
28 | "babel-loader": "^8.0.5",
29 | "html-webpack-plugin": "^3.2.0",
30 | "script-ext-html-webpack-plugin": "^2.1.3",
31 | "standard": "^14.3.1",
32 | "webpack": "^4.28.4",
33 | "webpack-cli": "^3.2.1",
34 | "webpack-dev-server": "^3.1.14"
35 | },
36 | "dependencies": {}
37 | }
38 |
--------------------------------------------------------------------------------
/public/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/githubtraining/github-actions-for-packages-demo/bb737d4f0903432b79b54df24814a6f6259b07b1/public/.DS_Store
--------------------------------------------------------------------------------
/public/index.css:
--------------------------------------------------------------------------------
1 | #game { display: flex; }
2 |
3 | #tictactoe {
4 | border-collapse: collapse;
5 | }
6 |
7 | #tictactoe td {
8 | min-height: 100px;
9 | min-width: 100px;
10 | height: 100px;
11 | width: 100px;
12 | text-align: center;
13 |
14 | font-size: 4em;
15 | border: thin solid black;
16 | }
17 |
18 | #tictactoe td:hover {
19 | background: #CCCCCC;
20 | }
21 |
22 | #tictactoe tr {
23 | width: 300px;
24 | height: 100px;
25 | }
26 |
27 | #score {
28 | margin-left: 5%;
29 | }
30 |
31 | #score table {
32 | border-collapse: collapse;
33 | }
34 |
35 | #score tr > * {
36 | padding: 5px;
37 | text-align: center;
38 | }
39 |
40 | #score th {
41 | border-bottom: thin solid black;
42 | }
43 |
44 | #score h2 { margin-top: 0; }
45 |
46 |
47 | #app {
48 | margin: auto;
49 | width: 60%;
50 | }
51 |
--------------------------------------------------------------------------------
/src/game.js:
--------------------------------------------------------------------------------
1 | export default class Game {
2 | constructor (p1, p2) {
3 | this.p1 = p1
4 | this.p2 = p2
5 | this.board = [[null, null, null], [null, null, null], [null, null, null]]
6 | this.player = Math.random() < 0.5 ? this.p1 : this.p2
7 | this.sym = 'X'
8 | }
9 |
10 | turn (row, col) {
11 | col = col || row
12 | this.board[row][col] = this.sym
13 | }
14 |
15 | nextPlayer () {
16 | this.player = this.player === this.p1 ? this.p2 : this.p1
17 | this.sym = this.sym === 'X' ? 'O' : 'X'
18 | }
19 |
20 | hasWinner () {
21 | return this.rowWin() || this.colWin() || this.diagWin()
22 | }
23 |
24 | rowWin () {
25 | let win = false
26 | for (let r = 0; r < 3; r++) {
27 | const row = this.board[r]
28 | if (row[0] === null) { continue }
29 | win = win || (row[0] === row[1] && row[0] === row[2])
30 | }
31 |
32 | return win
33 | }
34 |
35 | colWin () {
36 | let win = false
37 | for (let c = 0; c < 3; c++) {
38 | const col = this.board
39 | if (col[0][c] === null) { continue }
40 | win = win || (col[0][c] === col[1][c] && col[0][c] === col[2][c])
41 | }
42 |
43 | return win
44 | }
45 |
46 | diagWin () {
47 | const b = this.board
48 | return ((b[0][0] !== null && b[0][0] === b[1][1] && b[0][0] === b[2][2]) ||
49 | (b[0][2] !== null && b[0][2] === b[1][1] && b[0][2] === b[2][0]))
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import Game from './game.js'
2 |
3 | let p1, p2
4 | while (!p1) {
5 | p1 = window.prompt('Enter player 1 name:')
6 | }
7 |
8 | while (!p2 && p1 !== p2) {
9 | p2 = window.prompt(p1 === p2
10 | ? `Please enter a different name than ${p1}.`
11 | : 'Enter player 2 name:')
12 | }
13 |
14 | window.onload = () => {
15 | document.getElementById('p1Name').innerText = p1
16 | document.getElementById('p2Name').innerText = p2
17 | let score1 = 0
18 | let score2 = 0;
19 |
20 | (function playGame (p1, p2) {
21 | document.getElementById('win').style.display = 'none'
22 | document.getElementById('turn').style.display = 'inline'
23 | document.getElementById('p1Score').innerText = score1
24 | document.getElementById('p2Score').innerText = score2
25 |
26 | const game = new Game(p1, p2)
27 | const player = document.getElementById('player')
28 | player.innerText = game.player
29 |
30 | document.querySelectorAll('#tictactoe td').forEach((el) => {
31 | el.innerText = ''
32 | el.onclick = (evt) => {
33 | el.onclick = undefined
34 | evt.target.innerText = game.sym
35 | evt.target.onclick = undefined
36 |
37 | const [row, col] = evt.target.classList
38 | game.turn(row, col)
39 |
40 | if (game.hasWinner()) {
41 | document.getElementById('winner').innerText = game.player
42 | document.getElementById('win').style.display = 'inline'
43 | document.getElementById('turn').style.display = 'none'
44 |
45 | if (game.player === p1) { document.getElementById('p1Score').innerText = ++score1 } else { document.getElementById('p2Score').innerText = ++score2 }
46 |
47 | document.getElementById('newGame').style.display = 'inline'
48 | document.getElementById('newGame').onclick = () => playGame(p1, p2)
49 |
50 | document.querySelectorAll('td').forEach(el => {
51 | el.onclick = undefined
52 | })
53 | } else {
54 | game.nextPlayer()
55 | player.innerText = game.player
56 | }
57 | }
58 | })
59 | })(p1, p2)
60 | }
61 |
--------------------------------------------------------------------------------
/src/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | module.exports = {
4 | entry: {
5 | 'main.js': [
6 | path.resolve(__dirname, 'index.js'),
7 | path.resolve(__dirname, 'game.js')
8 | ]
9 | },
10 | output: {
11 | filename: 'main.js',
12 | path: path.resolve(__dirname, '../public')
13 | }
14 | }
15 |
--------------------------------------------------------------------------------