diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..4622295 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ + +Dockerfile text eol=lf +*.Dockerfile text eol=lf +*.sh eol=lf diff --git a/.github/workflows/master_com-keyman-status.yml b/.github/workflows/master_com-keyman-status.yml new file mode 100644 index 0000000..87fc9c2 --- /dev/null +++ b/.github/workflows/master_com-keyman-status.yml @@ -0,0 +1,52 @@ +# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy +# More GitHub Actions for Azure: https://github.com/Azure/actions + +name: Build and deploy Node.js app to Azure Web App - com-keyman-status + +on: + push: + branches: + - master + workflow_dispatch: + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@master + + - name: Set up Node.js version + uses: actions/setup-node@v1 + with: + node-version: '22.x' + + - name: npm ci + build - public + run: | + echo "- Building public for release" + cd public + npm ci + ./node_modules/.bin/ng build --configuration production + cd .. + + - name: npm ci + build - server + run: | + echo "- Building server for release" + cd server + npm ci + ./node_modules/.bin/tsc + cd .. + + - name: server test + run: | + cd server + npx mocha --import=./_mocha_register.js + cd .. + + - name: 'Deploy to Azure Web App' + uses: azure/webapps-deploy@v2 + with: + app-name: 'com-keyman-status' + slot-name: 'production' + publish-profile: ${{ secrets.AzureAppService_PublishProfile_29683e7e380246d28c473e1643a69a4d }} + package: . \ No newline at end of file diff --git a/.github/workflows/staging_com-keyman-staging-status.yml b/.github/workflows/staging_com-keyman-staging-status.yml new file mode 100644 index 0000000..c334d60 --- /dev/null +++ b/.github/workflows/staging_com-keyman-staging-status.yml @@ -0,0 +1,52 @@ +# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy +# More GitHub Actions for Azure: https://github.com/Azure/actions + +name: Build and deploy Node.js app to Azure Web App - com-keyman-staging-status + +on: + push: + branches: + - staging + workflow_dispatch: + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@master + + - name: Set up Node.js version + uses: actions/setup-node@v1 + with: + node-version: '22.x' + + - name: npm ci + build - public + run: | + echo "- Building public for release" + cd public + npm ci + ./node_modules/.bin/ng build --configuration production + cd .. + + - name: npm ci + build - server + run: | + echo "- Building server for release" + cd server + npm ci + ./node_modules/.bin/tsc + cd .. + + - name: server test + run: | + cd server + npx mocha --import=./_mocha_register.js + cd .. + + - name: 'Deploy to Azure Web App' + uses: azure/webapps-deploy@v2 + with: + app-name: 'com-keyman-staging-status' + slot-name: 'production' + publish-profile: ${{ secrets.AzureAppService_PublishProfile_690026b08b4f4dd681d2a0118d60b619 }} + package: . \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7c060de..1d91ee6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,10 +25,8 @@ jobs: - name: Set up Node.js version uses: actions/setup-node@v1 with: - node-version: '20.x' + node-version: '22.x' - name: npm install, build, and test run: | - npm install - npm run build - npm run test + ./build.sh configure build test --release diff --git a/.gitignore b/.gitignore index 52c0661..68b6172 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,15 @@ localenv.sh node_modules/ .keymanapp-test-bot.* -local*.sh \ No newline at end of file +local*.sh + +# Shared files are bootstrapped: +resources/bootstrap.inc.sh* +resources/.bootstrap-version +resources/.bootstrap-registry +_common/ + +# State files +_control/debug +_control/release +_control/ready diff --git a/README.md b/README.md index ecd8ece..e730b53 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,45 @@ -# Quick Start +# status.keyman.com + +## Overview + +* Back end is a node server in `/server`. +* Front end is an Angular app in `/public`. +* The [Keyman Test-bot](https://github.com/keymanapp/keyman/wiki/User-Testing-Workflows) + (@keymanapp-test-bot) is in `/server/keymanapp-test-bot`. + +The site is setup to run in Docker container(s). + +The site can be setup in development (`--debug`) or production (`--release`) +modes. When in development mode, the front end is hosted (on +http://localhost:8061) in a separate container to the back end (on +http://localhost:8060) in order to facilitate live-reload on changes. + +For production mode, the front end is compiled to static pages which are then +served by the back end (on http://localhost:8060). + +## Prerequisites + +* Docker Desktop +* On Windows, you'll need to have Git Bash installed in `C:\Program Files\git\bin\bash.exe`. + +Before building and starting the site, you need to have API tokens set as +environment variables. These should be added in script `server/localenv.sh`. + +```bash +export KEYMANSTATUS_TEAMCITY_TOKEN=[your personal auth token here] +export KEYMANSTATUS_GITHUB_TOKEN=[your personal auth token here] +export KEYMANSTATUS_SENTRY_TOKEN=[your personal auth token here] +``` + +### @keymanapp-test-bot + +Three files are needed for development: + +* `.keymanapp-test-bot.appid`: integer appid (e.g. 134443 for the normal test app) +* `.keymanapp-test-bot.pem`: certificate for GitHub integration for app +* `.keymanapp-test-bot.secret`: secret for GitHub integration for app + +## Development setup Clone the repo: @@ -7,50 +48,57 @@ git clone https://github.com/keymanapp/status.keyman.com cd status.keyman.com/ ``` -Build status.keyman.com: +### Build the docker containers + +Build status.keyman.com, in development mode: ```bash -cd server -npm install -npm run-script build -cd ../public -npm install -npm run-script build -cd .. +./build.sh stop build --debug ``` -Before running the node server, you need to have two API tokens set as environment variables. You might want to add these to script `server/localenv.sh`. +### Start the development server + +This repo is configured for live build and reload of both the client and server, +running in Docker. ```bash -export KEYMANSTATUS_TEAMCITY_TOKEN=[your personal auth token here] -export KEYMANSTATUS_GITHUB_TOKEN=[your personal auth token here] -export KEYMANSTATUS_SENTRY_TOKEN=[your personal auth token here] +./build.sh start --debug ``` -On Windows, you'll also need to have Git Bash installed in `C:\Program Files\git\bin\bash.exe`. +* Point your browser to to view the live reload version + of the application. +* The site takes a moment to compile and load; you can watch the logs to see + when it is ready. -## Development server +### Running unit tests -This repo is configured for live build and reload of both the client and server. You'll need two terminals open. In the first, run: +The unit tests will currently stop the back end container before running. ```bash -npm run start-server +./build.sh test --debug ``` -and in the second, run: +## Production setup + +This site is deployed to a Kubernetes cluster via configuration in a private +repo to status.keyman.com. + +You can run the production mode site locally with: ```bash -npm run start-client +./build.sh stop build start --release ``` -* Point your browser to to view the live reload version of the application. -* The query parameter `?c=1` adds a contributions view which is not visible by default. -* Another query parameter `?sprint=P8S4` parameter to view sprint contributions data for P8S4 - -### @keymanapp-test-bot +* Point your browser to to view the production version + of the application. -Three files needed for development: +## Site query parameters -* `.keymanapp-test-bot.appid`: integer appid (e.g. 134443 for the normal test app) -* `.keymanapp-test-bot.pem`: certificate for GitHub integration for app -* `.keymanapp-test-bot.secret`: secret for GitHub integration for app +* The following query parameters are available: + * `?c=1` shows contributions at the top center + * `?o=1` shows owner for each platform + * `?a=1` shows build agent status at the top right + * `?r=1` adds a refresh button to force a server-side full refresh (this is + costly, so only press this when there has been a data error such as a + network failure making status data out of date; most errors are + actually self-healing) diff --git a/_control/.keep b/_control/.keep new file mode 100644 index 0000000..e69de29 diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..7b449dc --- /dev/null +++ b/build.sh @@ -0,0 +1,89 @@ +#!/usr/bin/env bash +## START STANDARD SITE BUILD SCRIPT INCLUDE +readonly THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" +readonly BOOTSTRAP="$(dirname "$THIS_SCRIPT")/resources/bootstrap.inc.sh" +readonly BOOTSTRAP_VERSION=v1.0.10 +if ! [ -f "$BOOTSTRAP" ] || ! source "$BOOTSTRAP"; then + curl -H "Cache-Control: no-cache" --fail --silent --show-error -w "curl: Finished attempt to download %{url}" "https://raw.githubusercontent.com/keymanapp/shared-sites/$BOOTSTRAP_VERSION/bootstrap.inc.sh" -o "$BOOTSTRAP.tmp" || exit 1 + source "$BOOTSTRAP.tmp" + rm -f "$BOOTSTRAP.tmp" +fi +## END STANDARD SITE BUILD SCRIPT INCLUDE + +source _common/keyman-local-ports.inc.sh +source _common/docker.inc.sh + +readonly HOST_STATUS_KEYMAN_COM=status.keyman.com.localhost + +readonly THIS_CONTAINER_NAME=status-keyman-website +readonly PUBLIC_CONTAINER_NAME=status-keyman-public +readonly THIS_CONTAINER_DESC=status-keyman-com-app +readonly PUBLIC_CONTAINER_DESC=status-keyman-com-public +readonly THIS_IMAGE_NAME=status-keyman-website +readonly PUBLIC_IMAGE_NAME=status-keyman-public +readonly THIS_HOST="$HOST_STATUS_KEYMAN_COM" +readonly PUBLIC_HOST="$HOST_STATUS_KEYMAN_COM" # same host +readonly THIS_PORT="$PORT_STATUS_KEYMAN_COM" +readonly PUBLIC_PORT="$PORT_STATUS_KEYMAN_COM_PUBLIC" + + +################################ Main script ################################ + +builder_describe \ + "Setup status.keyman.com site to run via Docker." \ + configure \ + clean \ + build \ + start \ + stop \ + test + +builder_parse "$@" + +function build_docker_containers() { + build_docker_container $THIS_IMAGE_NAME $THIS_CONTAINER_NAME $BUILDER_CONFIGURATION server.Dockerfile + if builder_is_debug_build; then + build_docker_container $PUBLIC_IMAGE_NAME $PUBLIC_CONTAINER_NAME $BUILDER_CONFIGURATION public.Dockerfile + fi +} + +function start_docker_containers() { + start_docker_container $THIS_IMAGE_NAME $THIS_CONTAINER_NAME $THIS_CONTAINER_DESC $THIS_HOST $THIS_PORT $BUILDER_CONFIGURATION + if builder_is_debug_build; then + start_docker_container $PUBLIC_IMAGE_NAME $PUBLIC_CONTAINER_NAME $PUBLIC_CONTAINER_DESC $PUBLIC_HOST $PUBLIC_PORT $BUILDER_CONFIGURATION + fi +} + +function stop_docker_containers() { + rm -f ./_control/ready + stop_docker_container $THIS_IMAGE_NAME $THIS_CONTAINER_NAME + # always stop all containers - even in release build + stop_docker_container $PUBLIC_IMAGE_NAME $PUBLIC_CONTAINER_NAME +} + +function clean_docker_containers() { + clean_docker_container $THIS_IMAGE_NAME $THIS_CONTAINER_NAME + # always clean all containers - even in release build + clean_docker_container $PUBLIC_IMAGE_NAME $PUBLIC_CONTAINER_NAME +} + +function do_clean() { + clean_docker_containers + rm -rf ./server/node_modules ./server/dist + rm -rf ./public/node_modules ./public/angular ./public/dist + rm -rf ./public/angular + rm -rf ./_common + rm -rf ./resources/.bootstrap-registry ./resources/bootstrap-version ./resources/bootstrap.inc.sh +} + +function test_docker_container() { + stop_docker_container $THIS_IMAGE_NAME $THIS_CONTAINER_NAME + MSYS_NO_PATHCONV=1 start_docker_container $THIS_IMAGE_NAME $THIS_CONTAINER_NAME $THIS_CONTAINER_DESC $THIS_HOST $THIS_PORT $BUILDER_CONFIGURATION "/bin/sh" "./resources/test.sh" +} + +builder_run_action configure bootstrap_configure +builder_run_action clean do_clean +builder_run_action stop stop_docker_containers +builder_run_action build build_docker_containers +builder_run_action start start_docker_containers +builder_run_action test test_docker_container diff --git a/package.json b/package.json index fe94296..a209ad8 100644 --- a/package.json +++ b/package.json @@ -2,17 +2,7 @@ "name": "keyman-status", "version": "1.0.0", "description": "Report on status of Keyman projects", - "main": "server/dist/server/code.js", "type": "module", - "scripts": { - "build": "npm run-script build-server && npm run-script build-client", - "build-client": "cd public && npm install && npm run-script build", - "build-server": "cd server && npm install && npm run-script build", - "start-client": "cd public && npm run-script start", - "start-server": "cd server && npm run-script start", - "test": "cd server && npx mocha --import=./_mocha_register.js", - "log": "az webapp log tail --resource-group keyman --name com-keyman-status" - }, "author": "Marc Durdin", "license": "MIT" } diff --git a/public.Dockerfile b/public.Dockerfile new file mode 100644 index 0000000..35fe571 --- /dev/null +++ b/public.Dockerfile @@ -0,0 +1,13 @@ +# syntax=docker/dockerfile:1 + +FROM node:22@sha256:cd6fb7efa6490f039f3471a189214d5f548c11df1ff9e5b181aa49e22c14383e AS node-builder + +WORKDIR /var/www/html + +ARG BUILDER_CONFIGURATION="debug" +ENV BUILDER_CONFIGURATION=$BUILDER_CONFIGURATION + +# DOCKER_RUNTIME_PUBLIC env var helps prevent the resource scripts running on the host +ENV DOCKER_RUNTIME_PUBLIC=1 + +CMD [ "/bin/sh", "./resources/start-public.sh" ] diff --git a/public/package-lock.json b/public/package-lock.json index 64284ac..fdf84a6 100644 --- a/public/package-lock.json +++ b/public/package-lock.json @@ -1218,9 +1218,9 @@ } }, "node_modules/@angular/build/node_modules/@types/node": { - "version": "25.0.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.10.tgz", - "integrity": "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg==", + "version": "25.2.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.3.tgz", + "integrity": "sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==", "dev": true, "license": "MIT", "optional": true, @@ -1932,9 +1932,9 @@ } }, "node_modules/@angular/cli/node_modules/@types/node": { - "version": "25.0.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.10.tgz", - "integrity": "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg==", + "version": "25.2.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.3.tgz", + "integrity": "sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==", "dev": true, "license": "MIT", "optional": true, @@ -3783,19 +3783,6 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.11", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", - "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" - } - }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", @@ -8091,14 +8078,6 @@ "node": ">=0.1.90" } }, - "node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/compare-versions": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.6.0.tgz", @@ -10289,18 +10268,6 @@ "colors": "1.4.0" } }, - "node_modules/jiti": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", - "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, "node_modules/jose": { "version": "6.1.3", "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", @@ -13331,27 +13298,6 @@ "node": ">=18" } }, - "node_modules/terser": { - "version": "5.44.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.1.tgz", - "integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==", - "dev": true, - "license": "BSD-2-Clause", - "optional": true, - "peer": true, - "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.15.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -14856,9 +14802,9 @@ "requires": {} }, "@types/node": { - "version": "25.0.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.10.tgz", - "integrity": "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg==", + "version": "25.2.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.3.tgz", + "integrity": "sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==", "dev": true, "optional": true, "peer": true, @@ -15227,9 +15173,9 @@ } }, "@types/node": { - "version": "25.0.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.10.tgz", - "integrity": "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg==", + "version": "25.2.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.3.tgz", + "integrity": "sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==", "dev": true, "optional": true, "peer": true, @@ -16302,18 +16248,6 @@ "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", "dev": true }, - "@jridgewell/source-map": { - "version": "0.3.11", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", - "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" - } - }, "@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", @@ -18814,14 +18748,6 @@ "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", "dev": true }, - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "optional": true, - "peer": true - }, "compare-versions": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.6.0.tgz", @@ -20339,14 +20265,6 @@ "colors": "1.4.0" } }, - "jiti": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", - "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", - "dev": true, - "optional": true, - "peer": true - }, "jose": { "version": "6.1.3", "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", @@ -22419,20 +22337,6 @@ } } }, - "terser": { - "version": "5.44.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.1.tgz", - "integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.15.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - } - }, "tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", diff --git a/public/package.json b/public/package.json index ce67448..0fc7ae7 100644 --- a/public/package.json +++ b/public/package.json @@ -3,8 +3,6 @@ "version": "0.0.0", "scripts": { "ng": "ng", - "start": "ng serve --configuration development", - "build": "ng build --configuration production", "test": "ng test", "lint": "ng lint", "e2e": "ng e2e" diff --git a/public/src/environments/environment.prod.ts b/public/src/environments/environment.prod.ts index 8f02170..013a113 100644 --- a/public/src/environments/environment.prod.ts +++ b/public/src/environments/environment.prod.ts @@ -2,5 +2,5 @@ export const environment = { production: true, statusUrl: '/status', refreshBackendUrl: '/refresh', - webSocketUrl: 'wss://'+window.location.host + webSocketUrl: (window.location.protocol == 'http:' ? 'ws' : 'wss') + '://'+window.location.host }; diff --git a/public/src/environments/environment.ts b/public/src/environments/environment.ts index cc7a59e..854cf7e 100644 --- a/public/src/environments/environment.ts +++ b/public/src/environments/environment.ts @@ -2,14 +2,16 @@ // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. // The list of file replacements can be found in `angular.json`. +import { isDevMode } from "@angular/core"; + export const environment = { production: false, - // When running in development with ng watch on 4200 and nodemon on 3000, otherwise + // When running in development with ng watch on http:8061 and nodemon on 8060, otherwise // assume we are on the same host as the status backend - statusUrl: window.location.host == 'localhost:4200' ? '//localhost:3000/status' : '/status', - refreshBackendUrl: window.location.host == 'localhost:4200' ? '//localhost:3000/refresh' : '/refresh', - webSocketUrl: window.location.host == 'localhost:4200' ? 'ws://localhost:3000' : 'ws://'+window.location.host, + statusUrl: isDevMode() ? '//localhost:8060/status' : '/status', + refreshBackendUrl: isDevMode() ? '//localhost:8060/refresh' : '/refresh', + webSocketUrl: isDevMode() ? 'ws://localhost:8060' : 'ws://'+window.location.host, }; /* diff --git a/resources/start-public.sh b/resources/start-public.sh new file mode 100755 index 0000000..766d6e7 --- /dev/null +++ b/resources/start-public.sh @@ -0,0 +1,28 @@ +#!/bin/sh + +set -eu + +if [ -z "${DOCKER_RUNTIME_PUBLIC+x}" ]; then + echo "ERROR: resources/start-public.sh should only be run in the public container" + exit 1 +fi + +if [ "$BUILDER_CONFIGURATION" = release ]; then + echo "ERROR: resources/start-public.sh should only be run in debug configurations" + exit 1 +fi + +# Configuration + +echo "Initializing debug frontend" + +cd public +npm install +cd .. + +echo "Starting debug frontend" + +export NODE_ENV=development + +cd public +./node_modules/.bin/ng serve --configuration development --verbose --watch=true --host=0.0.0.0 --poll 1000 --port 80 diff --git a/resources/start-server.sh b/resources/start-server.sh new file mode 100755 index 0000000..24ff6e9 --- /dev/null +++ b/resources/start-server.sh @@ -0,0 +1,59 @@ +#!/bin/sh + +set -eu + +# Configuration + +echo "Initializing server with $BUILDER_CONFIGURATION configuration" + +if [ -z "${DOCKER_RUNTIME_SERVER+x}" ]; then + echo "ERROR: resources/start-server.sh should only be run in the server container" + exit 1 +fi + +rm -f _control/ready + +if [ "$BUILDER_CONFIGURATION" = release ]; then + # For a release build, we need to build public/ because it is served from the + # same container + echo "- Building public for release" + cd public + npm ci + ./node_modules/.bin/ng build --configuration production + cd .. + + echo "- Building server for release" + cd server + npm ci + ./node_modules/.bin/tsc + cd .. +else + # For a debug build, we only need to build server/, because public/ is served + # from a separate container, and we don't need to build first, because that is + # done with tsc-watch + echo "- Preparing server for debug" + cd server + npm ci + cd .. +fi + +echo "Starting server with $BUILDER_CONFIGURATION configuration" + +if [ -f ./server/localenv.sh ]; then + . ./server/localenv.sh +fi + +touch _control/ready + +if [ "$BUILDER_CONFIGURATION" = release ]; then + # TODO: support staging env + export NODE_ENV=production + cd server/dist/server/ + + # TODO: make code.js pwd-safe + node code.js +else + export NODE_ENV=development + cd server + ./node_modules/.bin/tsc-watch --onSuccess "node ." --onFailure "node ." +fi diff --git a/resources/test.sh b/resources/test.sh new file mode 100755 index 0000000..537d16c --- /dev/null +++ b/resources/test.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +set -eu + +# Test + +if [ -z "${DOCKER_RUNTIME_SERVER+x}" ]; then + echo "ERROR: resources/test.sh should only be run in the server container" + exit 1 +fi + +echo "Testing server with $BUILDER_CONFIGURATION configuration" + +cd server + +npm ci +npx mocha --import=./_mocha_register.js \ No newline at end of file diff --git a/server.Dockerfile b/server.Dockerfile new file mode 100644 index 0000000..06863fc --- /dev/null +++ b/server.Dockerfile @@ -0,0 +1,13 @@ +# syntax=docker/dockerfile:1 + +FROM node:22@sha256:cd6fb7efa6490f039f3471a189214d5f548c11df1ff9e5b181aa49e22c14383e AS node-builder + +WORKDIR /var/www/html + +ARG BUILDER_CONFIGURATION="release" +ENV BUILDER_CONFIGURATION=$BUILDER_CONFIGURATION + +# DOCKER_RUNTIME_SERVER env var helps prevent the resource scripts running on the host +ENV DOCKER_RUNTIME_SERVER=1 + +CMD [ "/bin/sh", "./resources/start-server.sh" ] diff --git a/server/_start_server.bat b/server/_start_server.bat deleted file mode 100644 index 02ea911..0000000 --- a/server/_start_server.bat +++ /dev/null @@ -1,4 +0,0 @@ -@echo off -"%ProgramFiles%\git\bin\bash.exe" --init-file "c:\Program Files\Git\etc\profile" -l "./_start_server.sh" -if errorlevel 1 exit /b 1 -exit /b 0 diff --git a/server/_start_server.sh b/server/_start_server.sh deleted file mode 100755 index 9643ab4..0000000 --- a/server/_start_server.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash -set -e -[ -f ./localenv.sh ] && . ./localenv.sh -./node_modules/.bin/tsc-watch --onSuccess "node ." --onFailure "node ." -# npx nodemon -w src -e ts,js --exec "npm run build-server" diff --git a/server/code.ts b/server/code.ts index 5ea5443..9b24b7b 100644 --- a/server/code.ts +++ b/server/code.ts @@ -91,7 +91,7 @@ if(debugTestBot) { testUserTestComment(); } -const port = environment == Environment.Development ? 3000 : 80; +const port = 80; const REFRESH_INTERVAL = environment == Environment.Development ? 180000 : 60000; const timingManager = new DataChangeTimingManager(); diff --git a/server/package-lock.json b/server/package-lock.json index a111786..a8caed4 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -35,7 +35,6 @@ "@typescript-eslint/parser": "^4.18.0", "eslint": "^7.22.0", "mocha": "^10.2.0", - "run-script-os": "^1.1.6", "ts-node": "^10.9.2", "tsc-watch": "^4.2.9", "typescript": "^5.8.3" @@ -556,6 +555,7 @@ "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.1.tgz", "integrity": "sha512-dKYCMuPO1bmrpuogcjQ8z7ICCH3FP6WmxpwC03yjzGfZhj9fTJg6+bS1+UAplekbN2C+M61UNllGOOoAfGCrdQ==", "license": "MIT", + "peer": true, "dependencies": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.1.0", @@ -939,6 +939,7 @@ "resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.5.tgz", "integrity": "sha512-vvmsN0r7rguA+FySiCsbaTTobSftpIDIpPW81trAmsv9TGxg3YCujAxRYp/Uy8xmDgYCzzgulG62H7KYUFmeIg==", "license": "MIT", + "peer": true, "dependencies": { "@octokit/auth-token": "^5.0.0", "@octokit/graphql": "^8.2.2", @@ -1216,6 +1217,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=8.0.0" } @@ -1237,6 +1239,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-1.30.1.tgz", "integrity": "sha512-s5vvxXPVdjqS3kTLKMeBMvop9hbWkwzBpu+mUO2M7sZtlkyDJGwFe33wRKnbaYDo8ExRVBIIdwIGrqpxHuKttA==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=14" }, @@ -1273,6 +1276,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.57.2.tgz", "integrity": "sha512-BdBGhQBh8IjZ2oIIX6F2/Q3LKm/FDDKi6ccYKcBTeilh6SNdNKveDOLk73BkSJjQLJk6qe4Yh+hHw1UPhCDdrg==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/api-logs": "0.57.2", "@types/shimmer": "^1.2.0", @@ -1746,6 +1750,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.30.0.tgz", "integrity": "sha512-4VlGgo32k2EQ2wcCY3vEU28A0O13aOtHz3Xt2/2U5FAh9EfhD6t6DqL5Z6yAnRCntbTFDU4YfbpyzSlHNWycPw==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=14" } @@ -2167,7 +2172,8 @@ "node_modules/@types/node": { "version": "16.7.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.7.1.tgz", - "integrity": "sha512-ncRdc45SoYJ2H4eWU9ReDfp3vtFqDYhjOsKlFFUDEn8V1Bgr2RjYal8YT5byfadWIRluhPFU6JiDOl0H6Sl87A==" + "integrity": "sha512-ncRdc45SoYJ2H4eWU9ReDfp3vtFqDYhjOsKlFFUDEn8V1Bgr2RjYal8YT5byfadWIRluhPFU6JiDOl0H6Sl87A==", + "peer": true }, "node_modules/@types/parse-link-header": { "version": "1.0.0", @@ -2353,6 +2359,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.18.0.tgz", "integrity": "sha512-W3z5S0ZbecwX3PhJEAnq4mnjK5JJXvXUDBYIYGoweCyWyuvAKfGHvzmpUzgB5L4cRBb+cTu9U/ro66dx7dIimA==", "dev": true, + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "4.18.0", "@typescript-eslint/types": "4.18.0", @@ -2467,6 +2474,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", "dev": true, + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3404,6 +3412,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.22.0.tgz", "integrity": "sha512-3VawOtjSJUQiiqac8MQc+w457iGLfuNGLFn8JmF051tTKbh5/x/0vlcEj8OgDCaw7Ysa2Jn8paGshV7x2abKXg==", "dev": true, + "peer": true, "dependencies": { "@babel/code-frame": "7.12.11", "@eslint/eslintrc": "^0.4.0", @@ -4369,6 +4378,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -6137,6 +6147,7 @@ "resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.6.0.tgz", "integrity": "sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q==", "license": "MIT", + "peer": true, "dependencies": { "@octokit/auth-token": "^2.4.4", "@octokit/graphql": "^4.5.8", @@ -7082,16 +7093,6 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/run-script-os": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/run-script-os/-/run-script-os-1.1.6.tgz", - "integrity": "sha512-ql6P2LzhBTTDfzKts+Qo4H94VUKpxKDFz6QxxwaUZN0mwvi7L3lpOI7BqPCq7lgDh3XLl0dpeXwfcVIitlrYrw==", - "dev": true, - "bin": { - "run-os": "index.js", - "run-script-os": "index.js" - } - }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -7772,6 +7773,7 @@ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -8565,6 +8567,7 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.1.tgz", "integrity": "sha512-dKYCMuPO1bmrpuogcjQ8z7ICCH3FP6WmxpwC03yjzGfZhj9fTJg6+bS1+UAplekbN2C+M61UNllGOOoAfGCrdQ==", + "peer": true, "requires": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.1.0", @@ -8885,6 +8888,7 @@ "version": "6.1.5", "resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.5.tgz", "integrity": "sha512-vvmsN0r7rguA+FySiCsbaTTobSftpIDIpPW81trAmsv9TGxg3YCujAxRYp/Uy8xmDgYCzzgulG62H7KYUFmeIg==", + "peer": true, "requires": { "@octokit/auth-token": "^5.0.0", "@octokit/graphql": "^8.2.2", @@ -9114,7 +9118,8 @@ "@opentelemetry/api": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", - "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==" + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "peer": true }, "@opentelemetry/api-logs": { "version": "0.57.2", @@ -9128,6 +9133,7 @@ "version": "1.30.1", "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-1.30.1.tgz", "integrity": "sha512-s5vvxXPVdjqS3kTLKMeBMvop9hbWkwzBpu+mUO2M7sZtlkyDJGwFe33wRKnbaYDo8ExRVBIIdwIGrqpxHuKttA==", + "peer": true, "requires": {} }, "@opentelemetry/core": { @@ -9149,6 +9155,7 @@ "version": "0.57.2", "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.57.2.tgz", "integrity": "sha512-BdBGhQBh8IjZ2oIIX6F2/Q3LKm/FDDKi6ccYKcBTeilh6SNdNKveDOLk73BkSJjQLJk6qe4Yh+hHw1UPhCDdrg==", + "peer": true, "requires": { "@opentelemetry/api-logs": "0.57.2", "@types/shimmer": "^1.2.0", @@ -9429,7 +9436,8 @@ "@opentelemetry/semantic-conventions": { "version": "1.30.0", "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.30.0.tgz", - "integrity": "sha512-4VlGgo32k2EQ2wcCY3vEU28A0O13aOtHz3Xt2/2U5FAh9EfhD6t6DqL5Z6yAnRCntbTFDU4YfbpyzSlHNWycPw==" + "integrity": "sha512-4VlGgo32k2EQ2wcCY3vEU28A0O13aOtHz3Xt2/2U5FAh9EfhD6t6DqL5Z6yAnRCntbTFDU4YfbpyzSlHNWycPw==", + "peer": true }, "@opentelemetry/sql-common": { "version": "0.40.1", @@ -9763,7 +9771,8 @@ "@types/node": { "version": "16.7.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.7.1.tgz", - "integrity": "sha512-ncRdc45SoYJ2H4eWU9ReDfp3vtFqDYhjOsKlFFUDEn8V1Bgr2RjYal8YT5byfadWIRluhPFU6JiDOl0H6Sl87A==" + "integrity": "sha512-ncRdc45SoYJ2H4eWU9ReDfp3vtFqDYhjOsKlFFUDEn8V1Bgr2RjYal8YT5byfadWIRluhPFU6JiDOl0H6Sl87A==", + "peer": true }, "@types/parse-link-header": { "version": "1.0.0", @@ -9909,6 +9918,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.18.0.tgz", "integrity": "sha512-W3z5S0ZbecwX3PhJEAnq4mnjK5JJXvXUDBYIYGoweCyWyuvAKfGHvzmpUzgB5L4cRBb+cTu9U/ro66dx7dIimA==", "dev": true, + "peer": true, "requires": { "@typescript-eslint/scope-manager": "4.18.0", "@typescript-eslint/types": "4.18.0", @@ -9970,7 +9980,8 @@ "version": "7.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true + "dev": true, + "peer": true }, "acorn-jsx": { "version": "5.3.1", @@ -10641,6 +10652,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.22.0.tgz", "integrity": "sha512-3VawOtjSJUQiiqac8MQc+w457iGLfuNGLFn8JmF051tTKbh5/x/0vlcEj8OgDCaw7Ysa2Jn8paGshV7x2abKXg==", "dev": true, + "peer": true, "requires": { "@babel/code-frame": "7.12.11", "@eslint/eslintrc": "^0.4.0", @@ -11348,7 +11360,8 @@ "acorn": { "version": "8.14.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==" + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "peer": true }, "acorn-import-attributes": { "version": "1.9.5", @@ -12613,6 +12626,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.6.0.tgz", "integrity": "sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q==", + "peer": true, "requires": { "@octokit/auth-token": "^2.4.4", "@octokit/graphql": "^4.5.8", @@ -13322,12 +13336,6 @@ "queue-microtask": "^1.2.2" } }, - "run-script-os": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/run-script-os/-/run-script-os-1.1.6.tgz", - "integrity": "sha512-ql6P2LzhBTTDfzKts+Qo4H94VUKpxKDFz6QxxwaUZN0mwvi7L3lpOI7BqPCq7lgDh3XLl0dpeXwfcVIitlrYrw==", - "dev": true - }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -13804,7 +13812,8 @@ "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "dev": true + "dev": true, + "peer": true }, "uglify-js": { "version": "3.19.3", diff --git a/server/package.json b/server/package.json index 13195d4..90d7442 100644 --- a/server/package.json +++ b/server/package.json @@ -4,12 +4,6 @@ "description": "Report on status of Keyman projects", "main": "dist/server/code.js", "type": "module", - "scripts": { - "build": "tsc", - "start": "run-script-os", - "start:windows": "_start_server.bat", - "start:default": "./_start_server.sh" - }, "author": "Marc Durdin", "license": "MIT", "dependencies": { @@ -39,7 +33,6 @@ "@typescript-eslint/parser": "^4.18.0", "eslint": "^7.22.0", "mocha": "^10.2.0", - "run-script-os": "^1.1.6", "ts-node": "^10.9.2", "tsc-watch": "^4.2.9", "typescript": "^5.8.3" diff --git a/server/tsconfig.json b/server/tsconfig.json index 731ac01..8e65caa 100644 --- a/server/tsconfig.json +++ b/server/tsconfig.json @@ -9,5 +9,8 @@ "allowJs": true, "skipLibCheck": true }, - "include": ["./**/*"] + "include": ["./**/*"], + "watchOptions": { + "watchFile": "dynamicPriorityPolling" + } } \ No newline at end of file