diff --git a/nodeapp-1/.dockerignore b/nodeapp-1/.dockerignore new file mode 100644 index 0000000..2eb5f95 --- /dev/null +++ b/nodeapp-1/.dockerignore @@ -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 \ No newline at end of file diff --git a/nodeapp-1/.eslintrc.json b/nodeapp-1/.eslintrc.json new file mode 100644 index 0000000..07f88e0 --- /dev/null +++ b/nodeapp-1/.eslintrc.json @@ -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" + } +} \ No newline at end of file diff --git a/nodeapp-1/.mocharc.json b/nodeapp-1/.mocharc.json new file mode 100644 index 0000000..ca39441 --- /dev/null +++ b/nodeapp-1/.mocharc.json @@ -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" + ] +} \ No newline at end of file diff --git a/nodeapp-1/Dockerfile b/nodeapp-1/Dockerfile new file mode 100644 index 0000000..67af834 --- /dev/null +++ b/nodeapp-1/Dockerfile @@ -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" ] \ No newline at end of file diff --git a/nodeapp-1/README.md b/nodeapp-1/README.md index 7aeb2ec..5d04243 100644 --- a/nodeapp-1/README.md +++ b/nodeapp-1/README.md @@ -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 diff --git a/nodeapp-1/app.js b/nodeapp-1/app.js index 3e81e4f..4d7975b 100644 --- a/nodeapp-1/app.js +++ b/nodeapp-1/app.js @@ -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')}`); -}); \ No newline at end of file +// 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')}`); + }); +} \ No newline at end of file diff --git a/nodeapp-1/config.js b/nodeapp-1/config.js index 60c6ce8..7ba4252 100644 --- a/nodeapp-1/config.js +++ b/nodeapp-1/config.js @@ -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/' + } }; \ No newline at end of file diff --git a/nodeapp-1/server.js b/nodeapp-1/server.js new file mode 100644 index 0000000..1db95bd --- /dev/null +++ b/nodeapp-1/server.js @@ -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'}`); +}); \ No newline at end of file diff --git a/nodeapp-1/test/contact_test.js b/nodeapp-1/test/contact_test.js index e45a537..41c3a71 100644 --- a/nodeapp-1/test/contact_test.js +++ b/nodeapp-1/test/contact_test.js @@ -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(); }); }); diff --git a/nodeapp-1/test/index_test.js b/nodeapp-1/test/index_test.js index e48fe76..74cb98c 100644 --- a/nodeapp-1/test/index_test.js +++ b/nodeapp-1/test/index_test.js @@ -3,13 +3,13 @@ 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); @@ -17,4 +17,4 @@ describe('/GET', () => { done(); }); }); -}); +}); \ No newline at end of file diff --git a/nodeapp-1/test/who_test.js b/nodeapp-1/test/who_test.js index 18529c6..04d9bf4 100644 --- a/nodeapp-1/test/who_test.js +++ b/nodeapp-1/test/who_test.js @@ -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(); }); }); -}); +}); \ No newline at end of file