diff --git a/jenkins/Jenkinsfile b/jenkins/Jenkinsfile
new file mode 100644
index 0000000..cf6ea71
--- /dev/null
+++ b/jenkins/Jenkinsfile
@@ -0,0 +1,30 @@
+pipeline {
+ agent {
+ docker {
+ image 'node:6-alpine'
+ args '-p 3000:3000'
+ }
+ }
+ environment {
+ CI = 'true'
+ }
+ stages {
+ stage('Build') {
+ steps {
+ sh 'npm install'
+ }
+ }
+ stage('Test') {
+ steps {
+ sh './jenkins/scripts/test.sh'
+ }
+ }
+ stage('Deliver') {
+ steps {
+ sh './jenkins/scripts/deliver.sh'
+ input message: 'Finished using the web site? (Click "Proceed" to continue)'
+ sh './jenkins/scripts/kill.sh'
+ }
+ }
+ }
+}
diff --git a/jenkins/scripts/deliver.sh b/jenkins/scripts/deliver.sh
new file mode 100755
index 0000000..21ddafd
--- /dev/null
+++ b/jenkins/scripts/deliver.sh
@@ -0,0 +1,29 @@
+#!/usr/bin/env sh
+
+echo 'The following "npm" command builds your Node.js/React application for'
+echo 'production in the local "build" directory (i.e. within the'
+echo '"/var/jenkins_home/workspace/simple-node-js-react-app" directory),'
+echo 'correctly bundles React in production mode and optimizes the build for'
+echo 'the best performance.'
+set -x
+npm run build
+set +x
+
+echo 'The following "npm" command runs your Node.js/React application in'
+echo 'development mode and makes the application available for web browsing.'
+echo 'The "npm start" command has a trailing ampersand so that the command runs'
+echo 'as a background process (i.e. asynchronously). Otherwise, this command'
+echo 'can pause running builds of CI/CD applications indefinitely. "npm start"'
+echo 'is followed by another command that retrieves the process ID (PID) value'
+echo 'of the previously run process (i.e. "npm start") and writes this value to'
+echo 'the file ".pidfile".'
+set -x
+npm start &
+sleep 1
+echo $! > .pidfile
+set +x
+
+echo 'Now...'
+echo 'Visit http://localhost:3000 to see your Node.js/React application in action.'
+echo '(This is why you specified the "args ''-p 3000:3000''" parameter when you'
+echo 'created your initial Pipeline as a Jenkinsfile.)'
diff --git a/jenkins/scripts/kill.sh b/jenkins/scripts/kill.sh
new file mode 100755
index 0000000..94ae46b
--- /dev/null
+++ b/jenkins/scripts/kill.sh
@@ -0,0 +1,7 @@
+#!/usr/bin/env sh
+
+echo 'The following command terminates the "npm start" process using its PID'
+echo '(written to ".pidfile"), all of which were conducted when "deliver.sh"'
+echo 'was executed.'
+set -x
+kill $(cat .pidfile)
diff --git a/jenkins/scripts/test.sh b/jenkins/scripts/test.sh
new file mode 100755
index 0000000..360d00b
--- /dev/null
+++ b/jenkins/scripts/test.sh
@@ -0,0 +1,20 @@
+#!/usr/bin/env sh
+
+echo 'The following "npm" command (if executed) installs the "cross-env"'
+echo 'dependency into the local "node_modules" directory, which will ultimately'
+echo 'be stored in the Jenkins home directory. As described in'
+echo 'https://docs.npmjs.com/cli/install, the "--save-dev" flag causes the'
+echo '"cross-env" dependency to be installed as "devDependencies". For the'
+echo 'purposes of this tutorial, this flag is not important. However, when'
+echo 'installing this dependency, it would typically be done so using this'
+echo 'flag. For a comprehensive explanation about "devDependencies", see'
+echo 'https://stackoverflow.com/questions/18875674/whats-the-difference-between-dependencies-devdependencies-and-peerdependencies.'
+set -x
+# npm install --save-dev cross-env
+set +x
+
+echo 'The following "npm" command tests that your simple Node.js/React'
+echo 'application renders satisfactorily. This command actually invokes the test'
+echo 'runner Jest (https://facebook.github.io/jest/).'
+set -x
+npm test
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..53720bd
--- /dev/null
+++ b/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "my-app",
+ "version": "0.1.0",
+ "private": true,
+ "dependencies": {
+ "react": "^16.0.0",
+ "react-dom": "^16.0.0",
+ "react-scripts": "1.0.14"
+ },
+ "scripts": {
+ "start": "react-scripts start",
+ "build": "react-scripts build",
+ "test": "react-scripts test --env=jsdom",
+ "eject": "react-scripts eject"
+ }
+}
\ No newline at end of file
diff --git a/public/favicon.ico b/public/favicon.ico
new file mode 100644
index 0000000..a11777c
Binary files /dev/null and b/public/favicon.ico differ
diff --git a/public/index.html b/public/index.html
new file mode 100644
index 0000000..7bee027
--- /dev/null
+++ b/public/index.html
@@ -0,0 +1,40 @@
+
+
+
+ To get started, edit src/App.js and save to reload.
+
+
+ );
+ }
+}
+
+export default App;
diff --git a/src/App.test.js b/src/App.test.js
new file mode 100644
index 0000000..b84af98
--- /dev/null
+++ b/src/App.test.js
@@ -0,0 +1,8 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import App from './App';
+
+it('renders without crashing', () => {
+ const div = document.createElement('div');
+ ReactDOM.render(, div);
+});
diff --git a/src/index.css b/src/index.css
new file mode 100644
index 0000000..b4cc725
--- /dev/null
+++ b/src/index.css
@@ -0,0 +1,5 @@
+body {
+ margin: 0;
+ padding: 0;
+ font-family: sans-serif;
+}
diff --git a/src/index.js b/src/index.js
new file mode 100644
index 0000000..fae3e35
--- /dev/null
+++ b/src/index.js
@@ -0,0 +1,8 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import './index.css';
+import App from './App';
+import registerServiceWorker from './registerServiceWorker';
+
+ReactDOM.render(, document.getElementById('root'));
+registerServiceWorker();
diff --git a/src/logo.svg b/src/logo.svg
new file mode 100644
index 0000000..6b60c10
--- /dev/null
+++ b/src/logo.svg
@@ -0,0 +1,7 @@
+
diff --git a/src/registerServiceWorker.js b/src/registerServiceWorker.js
new file mode 100644
index 0000000..4a3ccf0
--- /dev/null
+++ b/src/registerServiceWorker.js
@@ -0,0 +1,108 @@
+// In production, we register a service worker to serve assets from local cache.
+
+// This lets the app load faster on subsequent visits in production, and gives
+// it offline capabilities. However, it also means that developers (and users)
+// will only see deployed updates on the "N+1" visit to a page, since previously
+// cached resources are updated in the background.
+
+// To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
+// This link also includes instructions on opting out of this behavior.
+
+const isLocalhost = Boolean(
+ window.location.hostname === 'localhost' ||
+ // [::1] is the IPv6 localhost address.
+ window.location.hostname === '[::1]' ||
+ // 127.0.0.1/8 is considered localhost for IPv4.
+ window.location.hostname.match(
+ /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
+ )
+);
+
+export default function register() {
+ if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
+ // The URL constructor is available in all browsers that support SW.
+ const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
+ if (publicUrl.origin !== window.location.origin) {
+ // Our service worker won't work if PUBLIC_URL is on a different origin
+ // from what our page is served on. This might happen if a CDN is used to
+ // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
+ return;
+ }
+
+ window.addEventListener('load', () => {
+ const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
+
+ if (!isLocalhost) {
+ // Is not local host. Just register service worker
+ registerValidSW(swUrl);
+ } else {
+ // This is running on localhost. Lets check if a service worker still exists or not.
+ checkValidServiceWorker(swUrl);
+ }
+ });
+ }
+}
+
+function registerValidSW(swUrl) {
+ navigator.serviceWorker
+ .register(swUrl)
+ .then(registration => {
+ registration.onupdatefound = () => {
+ const installingWorker = registration.installing;
+ installingWorker.onstatechange = () => {
+ if (installingWorker.state === 'installed') {
+ if (navigator.serviceWorker.controller) {
+ // At this point, the old content will have been purged and
+ // the fresh content will have been added to the cache.
+ // It's the perfect time to display a "New content is
+ // available; please refresh." message in your web app.
+ console.log('New content is available; please refresh.');
+ } else {
+ // At this point, everything has been precached.
+ // It's the perfect time to display a
+ // "Content is cached for offline use." message.
+ console.log('Content is cached for offline use.');
+ }
+ }
+ };
+ };
+ })
+ .catch(error => {
+ console.error('Error during service worker registration:', error);
+ });
+}
+
+function checkValidServiceWorker(swUrl) {
+ // Check if the service worker can be found. If it can't reload the page.
+ fetch(swUrl)
+ .then(response => {
+ // Ensure service worker exists, and that we really are getting a JS file.
+ if (
+ response.status === 404 ||
+ response.headers.get('content-type').indexOf('javascript') === -1
+ ) {
+ // No service worker found. Probably a different app. Reload the page.
+ navigator.serviceWorker.ready.then(registration => {
+ registration.unregister().then(() => {
+ window.location.reload();
+ });
+ });
+ } else {
+ // Service worker found. Proceed as normal.
+ registerValidSW(swUrl);
+ }
+ })
+ .catch(() => {
+ console.log(
+ 'No internet connection found. App is running in offline mode.'
+ );
+ });
+}
+
+export function unregister() {
+ if ('serviceWorker' in navigator) {
+ navigator.serviceWorker.ready.then(registration => {
+ registration.unregister();
+ });
+ }
+}