Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
7b3e5b9
upgrade to node 24
ohadschn Jan 15, 2026
ad1aba5
upgrade react-router
ohadschn Jan 15, 2026
cc8beb6
remove eslint from prod commands
ohadschn Jan 15, 2026
fc55aeb
upgrade server packages
ohadschn Jan 15, 2026
12c8dac
upgrade client dependencies
ohadschn Jan 15, 2026
6e34b00
normalize npm scripts
ohadschn Jan 15, 2026
def5e3b
official build
ohadschn Jan 15, 2026
659dc1e
print build files in package script
ohadschn Jan 16, 2026
fa8c8d8
reusable npm template - meaningful names
ohadschn Jan 16, 2026
ff6cd9f
package client dist only
ohadschn Jan 16, 2026
efa8150
normalize package folder structure
ohadschn Jan 16, 2026
f366b05
minor job name change
ohadschn Jan 16, 2026
8b05250
add npm test placeholders
ohadschn Jan 16, 2026
fade299
improved node setup
ohadschn Jan 16, 2026
5a19c3e
minor - shorten app package artifact name
ohadschn Jan 16, 2026
6119b34
minor comment
ohadschn Jan 16, 2026
74f731b
Normalize workflow names
ohadschn Jan 16, 2026
69d5f77
fail if no files were found for artifact
ohadschn Jan 17, 2026
bb570b8
combined deployment
ohadschn Jan 17, 2026
4d6e9bc
deploy webapp
ohadschn Jan 17, 2026
8dbdbc5
remove x86 node (doesn't exist)
ohadschn Jan 17, 2026
b0249f5
download-artifact - specify repo param
ohadschn Jan 17, 2026
2a013cf
downgrade download-artifact to 6
ohadschn Jan 17, 2026
c079cb2
allow manual execution of stage deploy
ohadschn Jan 17, 2026
4d828b6
pass GH PAT secret to reusable deploy workflow
ohadschn Jan 17, 2026
a7affbc
reusable deploy workflow - inherit secrets
ohadschn Jan 17, 2026
ae2fbc2
update package locks with engines
ohadschn Jan 17, 2026
41526d3
Readme (#1)
ohadschn Jan 17, 2026
292c7e4
add local build script + explanation (#2)
ohadschn Jan 17, 2026
e6c58f3
remove CORS in prod (#3)
ohadschn Jan 17, 2026
b574b48
remove unnecessary web app instructions
ohadschn Jan 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .github/workflows/deploy-prod.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: Deploy-Prod

on:
workflow_dispatch:
inputs:
build-run-id:
required: true
type: string

jobs:
deploy-webapp:
uses: ./.github/workflows/reusable-deploy-webapp.yml
with:
build-run-id: ${{ inputs.build-run-id }}
environment: prod
secrets: inherit
21 changes: 21 additions & 0 deletions .github/workflows/deploy-stage.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: Deploy-Stage

on:
workflow_run:
workflows: ["Official-Build"]
types:
- completed
workflow_dispatch:
inputs:
build-run-id:
required: true
type: string

jobs:
deploy-webapp:
if: ${{ github.event.workflow_run.conclusion == 'success' }}
uses: ./.github/workflows/reusable-deploy-webapp.yml
with:
build-run-id: ${{ github.event.workflow_run.id || inputs.build-run-id }}
environment: stage
secrets: inherit
16 changes: 16 additions & 0 deletions .github/workflows/deploy-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: Deploy-Test

on:
workflow_dispatch:
inputs:
build-run-id:
required: true
type: string

jobs:
deploy-webapp:
uses: ./.github/workflows/reusable-deploy-webapp.yml
with:
build-run-id: ${{ inputs.build-run-id }}
environment: test
secrets: inherit
11 changes: 11 additions & 0 deletions .github/workflows/official-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
name: Official-Build

on:
workflow_dispatch:
push:
branches:
- main

jobs:
build:
uses: ./.github/workflows/reusable-build.yml
21 changes: 8 additions & 13 deletions .github/workflows/pr-validation.yml
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
name: PR Validation
name: PR-Validation

on: [pull_request, workflow_dispatch]
on:
workflow_dispatch:
pull_request:
branches:
- main

jobs:
client:
uses: ./.github/workflows/reusable-test-npm.yml
with:
folder: ./client
npm-command: build

server:
uses: ./.github/workflows/reusable-test-npm.yml
with:
folder: ./server
npm-command: lint
build:
uses: ./.github/workflows/reusable-build.yml
74 changes: 74 additions & 0 deletions .github/workflows/reusable-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
on:
workflow_call

jobs:
client-lint:
uses: ./.github/workflows/reusable-npm.yml
with:
folder: ./client
npm-command: lint

server-lint:
uses: ./.github/workflows/reusable-npm.yml
with:
folder: ./server
npm-command: lint

client-test:
uses: ./.github/workflows/reusable-npm.yml
with:
folder: ./client
npm-command: test

server-test:
uses: ./.github/workflows/reusable-npm.yml
with:
folder: ./server
npm-command: test

client-build:
uses: ./.github/workflows/reusable-npm.yml
with:
folder: ./client
npm-command: build
artifact-name: client-build
artifact-path: ./client/dist

server-build:
uses: ./.github/workflows/reusable-npm.yml
with:
folder: ./server
npm-ci-args: "--omit=dev"
npm-command: build
artifact-name: server-build

package:
needs:
- client-build
- server-build
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6

- name: Download server build artifact
uses: actions/download-artifact@v7
with:
name: server-build
path: ./build/server

- name: Download client build artifact
uses: actions/download-artifact@v7
with:
name: client-build
path: ./build/client

- name: Package application
shell: pwsh
run: ./package.ps1 -ServerBuild ./build/server -ClientBuild ./build/client

- name: Upload package artifact
uses: actions/upload-artifact@v6
with:
name: app-package
path: ./package
45 changes: 45 additions & 0 deletions .github/workflows/reusable-deploy-webapp.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: Deploy Web App

on:
workflow_call:
inputs:
build-run-id:
required: true
type: string
environment:
required: true
type: string

permissions:
id-token: write
contents: read

jobs:
deploy:
environment: ${{ inputs.environment }}
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v6

- name: Download app package artifact
uses: actions/download-artifact@v7
with:
name: app-package
run-id: ${{ inputs.build-run-id }}
github-token: ${{ secrets.GH_PAT_ACTIONS_READONLY }}
path: package

- name: 'Azure Login (OIDC)'
uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

- name: 'Deploy web app'
uses: azure/webapps-deploy@v3
with:
app-name: ${{ vars.AZURE_WEBAPP_NAME }}
package: package
53 changes: 53 additions & 0 deletions .github/workflows/reusable-npm.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: Run NPM command (reusable)

on:
workflow_call:
inputs:
folder:
required: true
type: string
npm-ci-args:
required: false
type: string
default: ""
npm-command:
required: true
type: string
artifact-name:
required: false
type: string
default: ""
artifact-path:
required: false
type: string
default: ""

jobs:
run-npm:
name: "npm '${{ inputs.npm-command }}' (${{ inputs.folder }})"
defaults:
run:
working-directory: ${{ inputs.folder }}

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v6

- name: Use Node.js
uses: actions/setup-node@v6
with:
node-version: 24
check-latest: true
cache-dependency-path: ${{ inputs.folder }}/package-lock.json

- run: npm ci ${{ inputs.npm-ci-args }}
- run: npm run ${{ inputs.npm-command }}

- name: Publish build artifacts
if: ${{ inputs.artifact-name != '' }}
uses: actions/upload-artifact@v6
with:
name: ${{ inputs.artifact-name }}
path: ${{ inputs.artifact-path != '' && inputs.artifact-path || inputs.folder }}
if-no-files-found: error
30 changes: 0 additions & 30 deletions .github/workflows/reusable-test-npm.yml

This file was deleted.

2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
package/

# Logs
logs
*.log
Expand Down
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,29 @@ Contains the Node.js / Express backend application.
- `images/`: Contains the duck images referenced by the duck data above
- `routes/`: Defines the API endpoints and maps them to controller functions.

## Setting up Continuous Integration
- Protect your `main` branch against direct pushes so only PRs are allowed.
- A sample PR validation workflow is provided at `.github/workflows/pr-validation.yml` - note that it contains 7 jobs which can be added as checks.
- Recommended - block merge commits and only allow squash (rebase) PR merges.
- Docs: https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches
- Upon each merge to `main`, a package will be built by `.github/workflows/official-build.yml`
- The same package is also created by the PR validation workflow, to validate that packaging hasn't been broken and to allow test env deployment (see below)

## Deploying the app
The repo includes a sample deployment workflow to Azure App Services Web App (which includes a free tier): https://azure.microsoft.com/en-us/products/app-service/web
- When deployed to a web app, the express server statically exposes the vite-compiled client.
- To emulate this setup locally for debugging, run `localBuild.ps1` followed by `package.ps1` to create a `package` folder similar to the one generated in the workflows.
- Then simply run `npm run start` in the `package` folder to have both API and client served on http://localhost:5000/ (you should be able to navigate to that address directly).
- The model in this repo assumes 3 GitHub environments: https://docs.github.com/en/actions/how-tos/deploy/configure-and-manage-deployments/manage-environments
- `Test` - used to deploy PR build artifacts (manually dispatched via `.github/workflows/deploy-test.yml`)
- `Stage` - used for continuous deployment (automatically dispatched upon official build completion via `.github/workflows/deploy-stage.yml`)
- `Prod` - used for production deployment of official build artifacts (manually dispatched via `.github/workflows/deploy-prod.yml`)
- Note that each of the above environments requires its own web app (so you'd have 3 in total)
- Under each GitHub environment, specify the corresponding web app's name in the `AZURE_WEBAPP_NAME` environment variable
- In order for deployment to work, you will need to follow the docs (specifically, create the repo secrets mentioned there for OIDC login): https://learn.microsoft.com/en-us/azure/app-service/deploy-github-actions?tabs=openid%2Caspnetcore
- In order for the deployment job to be able to fetch the built package from the build workflow, you'll need to create a `GH_PAT_ACTIONS_READONLY` repo secret containing a GitHub Personal Access Token (PAT) with `actions:read` permissions on your repo: https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens


## Best practices & Teamwork
[Full guide](BestPractices.md)

Expand Down
Loading