diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..522dfa2 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,49 @@ +# Dependencies +node_modules +**/node_modules +.pnp +.pnp.js + +# Testing +coverage +**/coverage + +# Production builds +dist +**/dist +build +**/build + +# Cache directories +.cache +**/.cache +.npm +.eslintcache +.vite +.nx +**/.parcel-cache + +# Environment files +.env.* +!.env.example + +# IDE specific files +.idea +.vscode +*.swp +*.swo + +# OS specific files +.DS_Store +Thumbs.db + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Temporary files +*.tmp +*.temp \ No newline at end of file diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 4e4d2ee..b4577c4 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -103,6 +103,10 @@ jobs: build: needs: prep runs-on: ubuntu-latest + # Just testing purposes + env: + REACT_APP_STAC_API: https://stac.eoapi.dev + PUBLIC_URL: http://stac-manager.ds.io steps: - name: Checkout @@ -123,5 +127,8 @@ jobs: - name: Install run: npm install + - name: Create .env file + run: mv packages/client/.env.example packages/client/.env + - name: Test run: npm run all:build \ No newline at end of file diff --git a/.github/workflows/deploy-gh.yml b/.github/workflows/deploy-gh.yml index 25c35b1..e09b99b 100644 --- a/.github/workflows/deploy-gh.yml +++ b/.github/workflows/deploy-gh.yml @@ -47,6 +47,9 @@ jobs: - name: Install run: npm install + - name: Create .env file + run: mv packages/client/.env.example packages/client/.env + - name: Setup SPA on Github Pages run: node packages/client/tasks/setup-gh-pages.mjs diff --git a/.gitignore b/.gitignore index 2023cdf..7c2896a 100644 --- a/.gitignore +++ b/.gitignore @@ -58,4 +58,4 @@ tmp .tmp dist parcel-bundle-reports -.nx +.nx \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6098f22 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +# Use an official Node.js runtime as a parent image +FROM node:slim + +# Set the working directory +WORKDIR /app + +# Copy the rest of the application code +COPY . . + +# Install dependencies +RUN npm i +RUN npm i -g http-server + +RUN npm run all:build + +EXPOSE 80 + +ENTRYPOINT ["http-server", "-p", "80", "packages/client/dist"] \ No newline at end of file diff --git a/README.md b/README.md index 648c7b0..cd028f0 100644 --- a/README.md +++ b/README.md @@ -20,4 +20,20 @@ All the packages are located in the `packages` directory structured as follows: To set up the project for development, follow the instructions in the [development documentation](./DEVELOPMENT.md) and get familiar with the app architecture and the plugin system by reading the [technical documentation](./docs/README.md). ## License -This project is licensed under the MIT license - see the LICENSE.md file for details. \ No newline at end of file +This project is licensed under the MIT license - see the LICENSE.md file for details. + +## Docker +To run the STAC-Manager in a Docker container, you can use the provided Dockerfile. + +**Build the Docker image** +```bash +docker build -t stac-manager . +``` + +**Run the Docker container** +```bash +docker run --rm -p 8080:80 --name stac-manager -e 'PUBLIC_URL=http://your-url.com' stac-manager +``` + +> [!NOTE] +> The application performs a complete build during container startup to ensure environment variables are properly integrated. This process may take a couple minutes to complete. \ No newline at end of file diff --git a/packages/client/.env b/packages/client/.env deleted file mode 100644 index 88af1cf..0000000 --- a/packages/client/.env +++ /dev/null @@ -1,18 +0,0 @@ -# App metadata -APP_TITLE=STAC Manager -APP_DESCRIPTION=Plugin based STAC editor -## Don't set the public url here. Check the README.md file for more information -# PUBLIC_URL= Do not set here - -# Api variables -REACT_APP_STAC_BROWSER= -REACT_APP_STAC_API= - -## Keycloak auth variables -REACT_APP_KEYCLOAK_URL= -REACT_APP_KEYCLOAK_CLIENT_ID= -REACT_APP_KEYCLOAK_REALM= - -## Theming -# REACT_APP_THEME_PRIMARY_COLOR='#6A5ACD' -# REACT_APP_THEME_SECONDARY_COLOR='#048A81' diff --git a/packages/client/.env.example b/packages/client/.env.example new file mode 100644 index 0000000..1d64453 --- /dev/null +++ b/packages/client/.env.example @@ -0,0 +1,60 @@ +# ============================================= +# STAC Manager Environment Example File +# ============================================= +# IMPORTANT: DO NOT MODIFY THIS FILE! +# Instead, create a copy named '.env' and modify that file. +# This example file serves as a template and documentation. +# ============================================= + +# ================= +# App Configuration +# ================= + +# The title of the application shown in browser tab and headers +APP_TITLE=STAC Manager + +# A brief description of the application for metadata purposes +APP_DESCRIPTION=Plugin based STAC editor + +# The base URL where the app is being served from +# DO NOT set this in the .env file. Set it as an environment variable before building. +# See the README for instructions. +# PUBLIC_URL= Do not set here + +# =============== +# API Integration +# =============== + +# URL of the STAC Browser instance (optional) +# If not set, will default to Radiant Earth's STAC Browser +REACT_APP_STAC_BROWSER= + +# URL of the STAC API endpoint (required) +# This is the API the app will interact with for STAC operations +REACT_APP_STAC_API= + +# ==================== +# Keycloak Auth Config +# ==================== +# If not provided, authentication will be disabled. + +# Base URL of the Keycloak server +REACT_APP_KEYCLOAK_URL= + +# Client ID registered in Keycloak +REACT_APP_KEYCLOAK_CLIENT_ID= + +# Realm name in Keycloak +REACT_APP_KEYCLOAK_REALM= + +# ================= +# Theme Customization +# ================= + +# Primary color for the application theme (hex color code) +# Default: #6A5ACD (SlateBlue) +# REACT_APP_THEME_PRIMARY_COLOR='#6A5ACD' + +# Secondary color for the application theme (hex color code) +# Default: #048A81 (Teal) +# REACT_APP_THEME_SECONDARY_COLOR='#048A81' diff --git a/packages/client/.gitignore b/packages/client/.gitignore index f232579..dfbed60 100644 --- a/packages/client/.gitignore +++ b/packages/client/.gitignore @@ -2,4 +2,5 @@ # Environment ################################################ -.env.* \ No newline at end of file +.env* +!.env.example \ No newline at end of file diff --git a/packages/client/README.md b/packages/client/README.md index 137023e..0245fe9 100644 --- a/packages/client/README.md +++ b/packages/client/README.md @@ -8,6 +8,15 @@ See root README.md for instructions on how to install and run the project. ## Client specific instructions +### Environment Configuration + +The application uses environment variables for configuration. A template file `.env.example` is provided as a template. + +To configure the application: +1. Copy `.env.example` to `.env` +2. Modify the `.env` file with your specific configuration values +3. Never modify `.env.example` directly as it serves as documentation + Some client options are controlled by environment variables. These are: ``` # App config @@ -66,4 +75,4 @@ icon-512.png 512x512 favicon.png 32x32 apple-touch-icon.png 360x360 meta-image.png 1920x1080 -``` +``` \ No newline at end of file diff --git a/packages/client/posthtml.config.js b/packages/client/posthtml.config.js index c8c18dc..5213067 100644 --- a/packages/client/posthtml.config.js +++ b/packages/client/posthtml.config.js @@ -1,26 +1,5 @@ +/* global process, module */ // https://github.com/parcel-bundler/parcel/issues/1209#issuecomment-942927265 -const dotenv = require('dotenv'); - -const NODE_ENV = process.env.NODE_ENV; - -const dotenvFiles = [ - '.env', - // Don't include `.env.local` for `test` environment - // since normally you expect tests to produce the same - // results for everyone - NODE_ENV === 'test' ? null : '.env.local', - `.env.${NODE_ENV}`, - `.env.${NODE_ENV}.local` -].filter(Boolean); - -const env = {}; - -for (let dotenvFile of dotenvFiles) { - const config = dotenv.config({ path: dotenvFile }); - if (config.parsed) { - Object.assign(env, config.parsed); - } -} module.exports = { plugins: { diff --git a/packages/client/tasks/build.mjs b/packages/client/tasks/build.mjs index fcea7ad..de5e3b3 100644 --- a/packages/client/tasks/build.mjs +++ b/packages/client/tasks/build.mjs @@ -5,9 +5,17 @@ import fs from 'fs-extra'; import log from 'fancy-log'; import { Parcel } from '@parcel/core'; +import { + checkRequiredEnvVars, + loadEnvironmentVariables +} from './check-env-vars.mjs'; + const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); +loadEnvironmentVariables(); +checkRequiredEnvVars(['REACT_APP_STAC_API', 'PUBLIC_URL']); + // ///////////////////////////////////////////////////////////////////////////// // --------------------------- Variables -------------------------------------// // ---------------------------------------------------------------------------// diff --git a/packages/client/tasks/check-env-vars.mjs b/packages/client/tasks/check-env-vars.mjs new file mode 100644 index 0000000..ac4be9a --- /dev/null +++ b/packages/client/tasks/check-env-vars.mjs @@ -0,0 +1,54 @@ +/* global process */ +import log from 'fancy-log'; +import dotenv from 'dotenv'; + +/** + * Check if required environment variables are set + * @param {string[]} requiredVars - Array of required environment variable names + * @throws {Error} - If any required variables are missing + */ +export function checkRequiredEnvVars(requiredVars) { + const missingVars = requiredVars.filter((varName) => !process.env[varName]); + + if (missingVars.length > 0) { + log.error('ERROR: Missing required environment variables:'); + missingVars.forEach((v) => log.error(` - ${v}`)); + console.log(); // eslint-disable-line no-console + log.info('Make sure to:'); + log.info('1. Copy .env.example to .env'); + log.info('2. Fill in all required values in .env'); + console.log(); // eslint-disable-line no-console + process.exit(1); + } +} + +/** + * Loads environment variables from `.env` files based on the current + * `NODE_ENV`. + * + * The function determines the appropriate `.env` files to load in the following + * order: + * 1. `.env` - Always included. + * 2. `.env.local` - Included unless the `NODE_ENV` is `test`. + * 3. `.env.` - Included based on the current `NODE_ENV`. + * 4. `.env..local` - Included based on the current `NODE_ENV`. + * + * Files are loaded in the order specified above, and later files override + * variables from earlier ones. The `.env.local` file is skipped for the `test` + * environment to ensure consistent test results across different environments. + */ +export function loadEnvironmentVariables() { + const dotenvFiles = [ + '.env', + // Don't include `.env.local` for `test` environment + // since normally you expect tests to produce the same + // results for everyone + process.env.NODE_ENV === 'test' ? null : '.env.local', + `.env.${process.env.NODE_ENV}`, + `.env.${process.env.NODE_ENV}.local` + ].filter(Boolean); + + dotenvFiles.forEach((dotenvFile) => { + dotenv.config({ path: dotenvFile }); + }); +} diff --git a/packages/client/tasks/server.mjs b/packages/client/tasks/server.mjs index 1ab59da..4c61459 100644 --- a/packages/client/tasks/server.mjs +++ b/packages/client/tasks/server.mjs @@ -7,10 +7,18 @@ import portscanner from 'portscanner'; import log from 'fancy-log'; import { Parcel } from '@parcel/core'; +import { + checkRequiredEnvVars, + loadEnvironmentVariables +} from './check-env-vars.mjs'; + const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const __appRoot = path.join(__dirname, '..'); +loadEnvironmentVariables(); +checkRequiredEnvVars(['REACT_APP_STAC_API']); + // ///////////////////////////////////////////////////////////////////////////// // --------------------------- Variables -------------------------------------// // ---------------------------------------------------------------------------//