Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
59 changes: 25 additions & 34 deletions .github/workflows/node-azure-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ env:
NODE_VERSION: '18.x'
AZURE_WEBAPP_NAME: 'webapp-az400-demo'
AZURE_WEBAPP_PACKAGE_PATH: './nodeapp-1'
WORKING_DIRECTORY: 'nodeapp-1'

jobs:
# ========================================
Expand All @@ -44,42 +45,38 @@ jobs:
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
cache-dependency-path: '${{ env.AZURE_WEBAPP_PACKAGE_PATH }}/package-lock.json'
cache-dependency-path: '${{ env.WORKING_DIRECTORY }}/package-lock.json'

- name: 📦 Install dependencies
run: npm ci
working-directory: ${{ env.AZURE_WEBAPP_PACKAGE_PATH }}
working-directory: ${{ env.WORKING_DIRECTORY }}

- name: 🔒 Security audit
run: npm audit --audit-level=high
working-directory: ${{ env.AZURE_WEBAPP_PACKAGE_PATH }}
working-directory: ${{ env.WORKING_DIRECTORY }}
continue-on-error: true

- name: 🧹 Lint code
run: npm run lint || echo "No lint script configured"
working-directory: ${{ env.AZURE_WEBAPP_PACKAGE_PATH }}
run: npm run lint
working-directory: ${{ env.WORKING_DIRECTORY }}

- name: 🧪 Run tests
run: npm test -- --coverage || npm test
working-directory: ${{ env.AZURE_WEBAPP_PACKAGE_PATH }}
- name: 🧪 Run tests with Mocha
run: npm test
working-directory: ${{ env.WORKING_DIRECTORY }}

- name: 📊 Upload coverage reports
uses: codecov/codecov-action@v3
- name: 📊 Upload test results
uses: actions/upload-artifact@v4
if: always()
with:
directory: ${{ env.AZURE_WEBAPP_PACKAGE_PATH }}/coverage
flags: unittests
name: codecov-umbrella
fail_ci_if_error: false

- name: 🔨 Build application
run: npm run build || echo "No build required"
working-directory: ${{ env.AZURE_WEBAPP_PACKAGE_PATH }}
name: mochawesome-report
path: ${{ env.WORKING_DIRECTORY }}/mochawesome-report
retention-days: 7

- name: 📤 Upload artifact for deployment
uses: actions/upload-artifact@v4
with:
name: node-app
path: ${{ env.AZURE_WEBAPP_PACKAGE_PATH }}
path: ${{ env.WORKING_DIRECTORY }}
retention-days: 5

# ========================================
Expand Down Expand Up @@ -137,6 +134,9 @@ jobs:
url: ${{ steps.deploy-to-webapp.outputs.webapp-url }}

steps:
- name: 📥 Checkout for tests
uses: actions/checkout@v4

- name: 📥 Download artifact
uses: actions/download-artifact@v4
with:
Expand All @@ -158,9 +158,11 @@ jobs:

- name: 🧪 Run integration tests
run: |
echo "Running integration tests against staging..."
# Add your integration test commands here
echo "✅ Integration tests passed!"
cd ${{ env.WORKING_DIRECTORY }}
npm ci
export TEST_URL=https://${{ env.AZURE_WEBAPP_NAME }}-staging.azurewebsites.net
npm test
continue-on-error: true

# ========================================
# JOB 4: DEPLOY TO PRODUCTION
Expand Down Expand Up @@ -204,12 +206,6 @@ jobs:
});
console.log(`✅ Created tag: ${tag}`);

- name: 📊 Monitor deployment
run: |
echo "Monitoring production deployment..."
# Add Application Insights queries here
echo "✅ Production deployment healthy!"

# ========================================
# REUSABLE WORKFLOW: SECURITY SCAN
# ========================================
Expand All @@ -222,12 +218,7 @@ jobs:
- name: 📥 Checkout code
uses: actions/checkout@v4

- name: 🔍 Run CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
languages: javascript

- name: 🛡️ Run Dependabot scan
- name: 🔍 Run Dependabot scan
uses: github/super-linter@v5
env:
DEFAULT_BRANCH: main
Expand Down
15 changes: 15 additions & 0 deletions nodeapp-1/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
node_modules
npm-debug.log
mochawesome-report
.git
.gitignore
.npmrc
.eslintrc.json
.mocharc.json
test
debug-solution.txt
*.code-workspace
.vscode
.devcontainer
.github
.azure
20 changes: 20 additions & 0 deletions nodeapp-1/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"env": {
"node": true,
"es2021": true,
"mocha": true
},
"extends": "eslint:recommended",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"rules": {
"indent": ["error", 2],
"linebreak-style": ["error", "unix"],
"quotes": ["error", "single"],
"semi": ["error", "always"],
"no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
"no-console": "off"
}
}
13 changes: 13 additions & 0 deletions nodeapp-1/.mocharc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"timeout": 5000,
"exit": true,
"reporter": "mochawesome",
"reporter-options": [
"reportDir=mochawesome-report",
"reportFilename=test-results",
"html=true",
"json=true",
"overwrite=true",
"inline=true"
]
}
19 changes: 19 additions & 0 deletions nodeapp-1/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
FROM node:18-alpine

# Create app directory
WORKDIR /usr/src/app

# Install app dependencies
# A wildcard is used to ensure both package.json AND package-lock.json are copied
COPY package*.json ./

RUN npm ci --only=production

# Bundle app source
COPY . .

# Expose port
EXPOSE 3000

# Start the application
CMD [ "node", "app.js" ]
69 changes: 53 additions & 16 deletions nodeapp-1/README.md
Original file line number Diff line number Diff line change
@@ -1,40 +1,77 @@
# Node & Express Demo App for GitHub Actions, Azure DevOps, and Beyond
# Node Express Azure - AZ-400 Demo App

## Last edited by Tim Warner
Sample Node.js Express application for demonstrating CI/CD pipelines in AZ-400 training.

> Build Your First CI/CD Pipeline using Azure DevOps with this Demo App.
## Features

This is a Node and Express web application used to demonstrate CI/CD with Azure DevOps. You can clone this repo and use it within Azure DevOps to build, test, and release to an Azure App Service web app.
- Express.js web application
- Handlebars templating
- Mocha/Chai testing with mochawesome reports
- ESLint for code quality
- Docker support
- Azure DevOps and GitHub Actions ready

[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new/timothywarner/node-express-azure)
## Getting Started

## Running and Testing Locally:
### Prerequisites

You can use these commands to install, test, and run the app locally. (Not Required)
- Node.js 18.x or higher
- npm 8.x or higher

### Install
### Installation

```
```bash
npm install
```

### Test
### Running Locally

```bash
npm start
```

The app will be available at http://localhost:3000

### Running Tests

```bash
npm test
```

![alt text](https://user-images.githubusercontent.com/5126491/51065379-c1743280-15c1-11e9-80fd-6a3d7ab4ac1b.jpg "Unit Test")
### Linting

Navigate to the `/test` folder to review the unit tests for this project. These tests will run as part of your Azure DevOps Build pipeline. See `azure-pipelines.yml` in this repo.
```bash
npm run lint
```

### Start
### Docker

Build the image:
```bash
docker build -t nodeapp-1 .
```
npm start

Run the container:
```bash
docker run -p 3000:3000 nodeapp-1
```

## CI/CD Pipelines

This app includes example pipelines for:
- Azure Pipelines (see `/pipelines` folder in repo root)
- GitHub Actions (see `.github/workflows` in repo root)

## Azure Artifacts

To publish to Azure Artifacts:

1. Create `.npmrc` from `.npmrc.template`
2. Set up authentication
3. Run `npm run publish:dev` or `npm run publish:prod`

### License
## Environment Variables

This project is licensed under the Apache License 2.0
- `PORT` - Server port (default: 3000)
- `NODE_ENV` - Environment (development/production)
- `AZURE_ARTIFACTS_FEED` - Azure Artifacts feed URL
13 changes: 9 additions & 4 deletions nodeapp-1/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,12 @@ app.use('/', index);
app.use('/who', who);
app.use('/contact', contact);

// Start the server
app.listen(app.get('port'), () => {
console.log(`Server running on port ${app.get('port')}`);
});
// Export the app for testing
module.exports = app;

// Only start the server if this file is run directly
if (require.main === module) {
app.listen(app.get('port'), () => {
console.log(`Server running on port ${app.get('port')}`);
});
}
6 changes: 5 additions & 1 deletion nodeapp-1/config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
module.exports = {
port: process.env.PORT || 443,
port: process.env.PORT || 3000,
environment: process.env.NODE_ENV || 'development',
azure: {
artifactsFeed: process.env.AZURE_ARTIFACTS_FEED || 'https://pkgs.dev.azure.com/certstarorg/_packaging/az400-npm-feed/npm/registry/'
}
};
9 changes: 9 additions & 0 deletions nodeapp-1/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Server entry point for production
const app = require('./app');

const port = process.env.PORT || app.get('port');

app.listen(port, () => {
console.log(`Server running on port ${port}`);
console.log(`Environment: ${process.env.NODE_ENV || 'development'}`);
});
8 changes: 4 additions & 4 deletions nodeapp-1/test/contact_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@ const config = require('../config');
const chai = require('chai');
const chaiHttp = require('chai-http');
const should = chai.should();
const server = require('../app');
const app = require('../app');

chai.use(chaiHttp);

describe('/GET', () => {
describe('/GET contact', () => {
it('returns the contact page', (done) => {
chai.request(`http://localhost:${config.port}`)
chai.request(app)
.get('/contact')
.end((err, res) => {
res.should.have.status(200);
res.text.should.contain('Contact Us');
res.text.should.contain('Contact information');
done();
});
});
Expand Down
6 changes: 3 additions & 3 deletions nodeapp-1/test/index_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@ const config = require('../config');
const chai = require('chai');
const chaiHttp = require('chai-http');
const should = chai.should();
const server = require('../app');
const app = require('../app');

chai.use(chaiHttp);

describe('/GET', () => {
it('returns the homepage', (done) => {
chai.request(`http://localhost:${config.port}`)
chai.request(app)
.get('/')
.end((err, res) => {
res.should.have.status(200);
res.text.should.contain('Welcome to GitHub Copilot Training!');
done();
});
});
});
});
10 changes: 5 additions & 5 deletions nodeapp-1/test/who_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@ const config = require('../config');
const chai = require('chai');
const chaiHttp = require('chai-http');
const should = chai.should();
const server = require('../app');
const app = require('../app');

chai.use(chaiHttp);

describe('/GET', () => {
describe('/GET who', () => {
it('returns the who page', (done) => {
chai.request(`http://localhost:${config.port}`)
chai.request(app)
.get('/who')
.end((err, res) => {
res.should.have.status(200);
res.text.should.contain('Who We Are');
res.text.should.contain('Who are you?');
done();
});
});
});
});
Loading
Loading