diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 000000000..3a1fa1795 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,23 @@ +{ + "name": "React UI (${localWorkspaceFolderBasename})", + "initializeCommand": "bash ./setup.sh", + "dockerComposeFile": [ + "../docker-compose.yml" + ], + "service": "devcontainer", + "shutdownAction": "stopCompose", + "workspaceFolder": "/workspace", + "forwardPorts": [ + "docs:8000" + ], + "portsAttributes": { + "docs:8000": { + "label": "Docs server", + "protocol": "http", + "onAutoForward": "openBrowser" + } + }, + "otherPortsAttributes": { + "onAutoForward": "ignore" + } +} diff --git a/.env.dist b/.env.dist index d60e62cef..f410ad558 100644 --- a/.env.dist +++ b/.env.dist @@ -1,13 +1,52 @@ -############################### -# Docker compose configuration # -############################### +################################ +# Docker Compose configuration # +################################ -# Host system port where the live documentation is to be made accessible -COMPOSE_START_PORT=8000 +# Docker compose project name must match directory name of the project to allow devcontainer to communicate with other containers +COMPOSE_PROJECT_NAME=react-ui -# Host system port where Playwright Component Testing report is to be made accessible +# Docker compose ports for Docs server instances +COMPOSE_START_DOCS_SERVER_PORT=8000 + +# Docker compose ports for Playwright Component Testing report server COMPOSE_PLAYWRIGHT_REPORT_PORT=9323 +# Flag whether to start JavaScript files watcher at container start +COMPOSE_START_JS_FILES_WATCHER_AT_START=true + +# Flag whether to start docs server at container start +COMPOSE_START_DOCS_SERVER_AT_START=true + # Ownership of the files created in the container +# ⚠️ [Linux] This needs to be set to the output of `id --user` +# ⚠️ [MacOS] This needs to be set to 1000 COMPOSE_UID=1000 +# ⚠️ [Linux] This needs to be set to the output of `id --group` +# ⚠️ [MacOS] This needs to be set to 1000 COMPOSE_GID=1000 + +############################# +# Devcontainer configuration # +############################# + +# IDEs automatically mount the host's SSH agent socket into the container +# Visual Studio Code does this by default, it can be disabled by setting the following variable to true. +# JetBrains IDEs do not mount this by default, but they can be configured to do so. +BLOCK_SSH_AUTH_SOCK=false + +# Select your preferred editor and visual (vim, nano) +EDITOR=vim +VISUAL=vim + +# Select your preferred shell (/bin/bash, /bin/fish, /bin/zsh) +SHELL=/bin/bash + +########################### +# Playwright configuration # +########################### + +# Number of workers to use to run Playwright tests +PW_WORKERS=1 + +# Port used by Playwright Component Testing to serve the test files +PW_CT_PORT=3100 diff --git a/.env.playwright.dist b/.env.playwright.dist deleted file mode 100644 index 41bd68e69..000000000 --- a/.env.playwright.dist +++ /dev/null @@ -1,9 +0,0 @@ -########################### -# Playwright configuration # -########################### - -# Number of workers to use to run Playwright tests -PW_WORKERS=1 - -# Port used by Playwright Component Testing to serve the test files -PW_CT_PORT=3100 diff --git a/.gitignore b/.gitignore index 2b0232bac..e2ab471d0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ /coverage +/docker-compose.yml +/docker/react_ui_devcontainer_local +!/docker/react_ui_devcontainer_local/Dockerfile.dist /dist /node_modules /playwright-report/ @@ -6,6 +9,5 @@ /src/docs/_assets/generated/* /tests/playwright/.temp/ .env -.env.playwright statistics.html !.gitkeep diff --git a/CLAUDE.md b/CLAUDE.md index ebcbc7f3f..65b606a1c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,17 +4,10 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Commands -All `npm` commands must be run inside Docker containers. Use `node_shell` for most tasks, `playwright` for visual tests. +All commands below are meant to be run directly inside the Docker container `devcontainer`. +If you open the project in a Dev Container, you can run these commands without manually +starting Docker Compose on the host. -```bash -# Enter node_shell container -docker compose run --rm node_shell - -# Enter playwright container (for visual tests) -docker compose run --rm --service-ports playwright -``` - -**Within `node_shell`:** ```bash npm run lint # All linters (ESLint + Stylelint + Markdownlint) @@ -23,11 +16,6 @@ npm run stylelint # SCSS linting npm run test:jest # All Jest unit tests npm run test:jest:ts -- # Single TypeScript test file npm run test:jest:js -- # Single JavaScript test file -``` - -**Within `playwright`:** - -```bash npm run test:playwright-ct:all # All component tests npm run test:playwright-ct:all-with-update # Update snapshots npm run test:playwright-ct:all -- -- src/components/Button # Tests for one component diff --git a/docker-compose.base.yml b/docker-compose.base.yml new file mode 100644 index 000000000..c48b9d7ba --- /dev/null +++ b/docker-compose.base.yml @@ -0,0 +1,91 @@ +services: + # This service is responsible for providing the main development environment for developers + devcontainer: + hostname: ${COMPOSE_PROJECT_NAME:-react-ui}_devcontainer + build: + context: docker/react_ui_devcontainer/ + dockerfile: Dockerfile + # Start dependent services before starting the `devcontainer` service to ensure that the necessary environments + # and tools are available when the `devcontainer` starts. + depends_on: + node: + condition: service_started + playwright: + condition: service_started + docs: + condition: service_started + user: ${COMPOSE_UID}:${COMPOSE_GID} + # Keep the container running indefinitely to allow developers to attach to it and use it as their development environment + command: sleep infinity + # Injects environment variables from the `.env` file into the `devcontainer` service, + # making them accessible within the container's environment. + env_file: + - .env + environment: + # This must be set correctly for the `devcontainer` to be able to access the host's Docker daemon, + # enabling Docker-from-Docker capabilities (e.g., running Docker commands from within the `devcontainer`). + COMPOSE_PROJECT_NAME: ${COMPOSE_PROJECT_NAME:-react-ui} + init: true + volumes: + - .:/workspace:z + # The following volume is used to allow the `devcontainer` to access the host's Docker daemon, + # enabling Docker-from-Docker capabilities (e.g., running Docker commands from within the `devcontainer`). + - /var/run/docker.sock:/var/run/docker.sock + # The following named volumes persist data (e.g. terminal history, AI tools data, etc.) across container restarts. + # Using separate named volumes (instead of a single volume with subpaths) allows Docker to automatically + # seed the volume with data from the image on first use. + - terminal-history:/home/developer/.terminal_history + - claude-config:/home/developer/.config/claude + - claude-share:/home/developer/.local/share/claude + - claude-state:/home/developer/.local/state/claude + - copilot:/home/developer/.copilot + - copilot-config:/home/developer/.config/copilot + - opencode-config:/home/developer/.config/opencode + - opencode-share:/home/developer/.local/share/opencode + - opencode-state:/home/developer/.local/state/opencode + + # This service provides Node environment and NPM + node: + build: docker/node + user: ${COMPOSE_UID}:${COMPOSE_GID} + command: sh -c 'if [ "$$COMPOSE_START_JS_FILES_WATCHER_AT_START" = "true" ]; then npm ci && npm start; else sleep infinity; fi' + env_file: + - .env + volumes: + - .:/workspace:z + + # This service provides Playwright environment and tools for browser automation and testing + playwright: + build: docker/playwright + user: ${COMPOSE_UID}:${COMPOSE_GID} + command: sleep infinity + env_file: + - .env + ports: + - ${COMPOSE_PLAYWRIGHT_REPORT_PORT}:9323 + volumes: + - .:/workspace:z + + # This provides server for documentation + docs: + build: docker/mkdocs + user: ${COMPOSE_UID}:${COMPOSE_GID} + entrypoint: sh -c 'if [ "$$COMPOSE_START_DOCS_SERVER_AT_START" = "true" ]; then mkdocs serve; else sleep infinity; fi' + env_file: + - .env + ports: + - ${COMPOSE_START_DOCS_SERVER_PORT}:8000 + volumes: + - .:/workspace:z + +volumes: + # The following volumes are used to persist data (e.g. terminal history, AI tools data, etc.) across container restarts + terminal-history: + claude-config: + claude-share: + claude-state: + copilot: + copilot-config: + opencode-config: + opencode-share: + opencode-state: diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 6896b094f..000000000 --- a/docker-compose.yml +++ /dev/null @@ -1,43 +0,0 @@ -services: - # Base services - do not run directly - mkdocs: - build: docker/mkdocs - user: ${COMPOSE_UID-1000}:${COMPOSE_GID-1000} - volumes: - - .:/workspace:z - node: - build: docker/node - user: ${COMPOSE_UID-1000}:${COMPOSE_GID-1000} - volumes: - - .:/workspace:z - - # Dev services - mkdocs_dev_server: - extends: mkdocs - entrypoint: mkdocs serve - ports: - - ${COMPOSE_START_PORT-8000}:8000 - node_dev_server: - extends: node - entrypoint: npm start - node_shell: - extends: node - entrypoint: bash - - # For running Playwright tests - playwright: - build: docker/playwright - entrypoint: bash - user: ${COMPOSE_UID-1000}:${COMPOSE_GID-1000} - ports: - - ${COMPOSE_PLAYWRIGHT_REPORT_PORT-9323}:9323 - volumes: - - .:/workspace:z - - # Build services - mkdocs_build_site: - extends: mkdocs - entrypoint: mkdocs build - node_build_site: - extends: node - entrypoint: npm run build diff --git a/docker-compose.yml.dist b/docker-compose.yml.dist new file mode 100644 index 000000000..3cd78e1f7 --- /dev/null +++ b/docker-compose.yml.dist @@ -0,0 +1,42 @@ +services: + # This service is responsible for providing the main development environment for developers + devcontainer: + extends: + file: docker-compose.base.yml + service: devcontainer + # Use `build` when you want to customize the devcontainer using `docker/react_ui_devcontainer_local/Dockerfile` + # build: + # context: ./docker/react_ui_devcontainer_local/ + # dockerfile: Dockerfile + # Use `image` when you want to use the default devcontainer + image: react-ui_devcontainer + + # This service provides Node environment and NPM + node: + extends: + file: docker-compose.base.yml + service: node + + # This service provides Playwright environment and tools for browser automation and testing + playwright: + extends: + file: docker-compose.base.yml + service: playwright + + # This provides server for documentation + docs: + extends: + file: docker-compose.base.yml + service: docs + +volumes: + # The following volumes are used to persist data (e.g. terminal history, AI tools data, etc.) across container restarts + terminal-history: + claude-config: + claude-share: + claude-state: + copilot: + copilot-config: + opencode-config: + opencode-share: + opencode-state: diff --git a/docker/build-docker-images.sh b/docker/build-docker-images.sh new file mode 100644 index 000000000..03ad3368e --- /dev/null +++ b/docker/build-docker-images.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +set -e +trap 'echo "Failed to build Docker images"; exit 1' ERR + +cd "$(dirname "$0")" + +echo "Building Docker images..." + +if [ ! -f ../.env ]; then + echo "Error: .env file not found in the project root" + exit 1 +fi + +PROJECT_NAME=$(grep -E '^COMPOSE_PROJECT_NAME=' ../.env | cut -d '=' -f 2-) +PROJECT_DEVCONTAINER_IMAGE="${PROJECT_NAME}_devcontainer" + +echo "Building Docker image $PROJECT_DEVCONTAINER_IMAGE..." +docker build -t "$PROJECT_DEVCONTAINER_IMAGE" -f ./react_ui_devcontainer/Dockerfile ./react_ui_devcontainer/ + +cd .. + +echo "Building project Docker images using docker-compose..." +docker compose build + +echo "All Docker images built successfully!" diff --git a/docker/react_ui_devcontainer/Dockerfile b/docker/react_ui_devcontainer/Dockerfile new file mode 100644 index 000000000..a0765218d --- /dev/null +++ b/docker/react_ui_devcontainer/Dockerfile @@ -0,0 +1,128 @@ +FROM debian:bookworm AS react-ui-devcontainer + +# Build arguments for user configuration +# Those can be changed using fixuid's remapping feature when running the container +ARG USERNAME=developer +ARG USER_UID=1000 +ARG USER_GID=1000 +ARG HOME="/home/${USERNAME}" + +# Default shell and editor settings, overridable via .env +ENV EDITOR="vim" +ENV VISUAL="vim" +ENV SHELL="/bin/bash" +ENV CLAUDE_CONFIG_DIR="${HOME}/.config/claude" +ENV OPENCODE_CONFIG="${HOME}/.config/opencode/opencode.json" +ENV OPENCODE_CONFIG_DIR="${HOME}/.config/opencode" + +# Install sudo, editors (vim, nano), SSH client, Git, shells (zsh, fish) and common utilities +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + sudo \ + ca-certificates \ + curl \ + gnupg \ + vim \ + nano \ + openssh-client \ + git \ + fish \ + zsh && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +# Create the user +RUN addgroup --gid $USER_GID $USERNAME && \ + adduser --uid $USER_UID --ingroup $USERNAME --home $HOME --shell $SHELL --disabled-password --gecos "" $USERNAME && \ + echo "$USERNAME ALL=(ALL:ALL) NOPASSWD:ALL" > /etc/sudoers.d/$USERNAME && \ + chmod 0440 /etc/sudoers.d/$USERNAME + +# Install fixuid to allow remapping the `developer` user to the host UID/GID when running in a container +RUN USER=$USERNAME && \ + GROUP=$USERNAME && \ + ARCH=$(dpkg --print-architecture) && \ + curl -SsL https://github.com/boxboat/fixuid/releases/download/v0.6.0/fixuid-0.6.0-linux-${ARCH}.tar.gz | tar -C /usr/local/bin -xzf - && \ + chown root:root /usr/local/bin/fixuid && \ + chmod 4755 /usr/local/bin/fixuid && \ + mkdir -p /etc/fixuid && \ + printf "user: $USER\ngroup: $GROUP\npaths: [$HOME]" > /etc/fixuid/config.yml + +# Install Docker CLI and Compose plugin to enable Docker-from-Docker +RUN install -m 0755 -d /etc/apt/keyrings && \ + curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --batch --yes --dearmor -o /etc/apt/keyrings/docker.gpg && \ + chmod a+r /etc/apt/keyrings/docker.gpg && \ + echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \ + https://download.docker.com/linux/debian $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \ + > /etc/apt/sources.list.d/docker.list && \ + apt-get update && \ + apt-get install -y --no-install-recommends docker-ce-cli docker-compose-plugin && \ + apt-get clean && rm -rf /var/lib/apt/lists/* + +# Switch to the `developer` user for all user-level installations +USER $USERNAME:$USERNAME + +# Install Oh My Zsh +RUN sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended + +# Persist terminal history across container restarts via a Docker volume +# mounted at $HOME/.terminal_history in docker-compose.base.yml +RUN mkdir -p "$HOME/.terminal_history" && \ + touch "$HOME/.terminal_history/bash_history" && \ + touch "$HOME/.terminal_history/zsh_history" && \ + touch "$HOME/.terminal_history/fish_history" && \ + SNIPPET_BASH="export PROMPT_COMMAND='history -a' && export HISTFILE=$HOME/.terminal_history/bash_history" && \ + echo "$SNIPPET_BASH" >> "$HOME/.bashrc" && \ + SNIPPET_ZSH="export PROMPT_COMMAND='history -a' && export HISTFILE=$HOME/.terminal_history/zsh_history" && \ + echo "$SNIPPET_ZSH" >> "$HOME/.zshrc" && \ + mkdir -p "$HOME/.local/share/fish" && \ + touch "$HOME/.terminal_history/fish_history" && \ + ln -sf "$HOME/.terminal_history/fish_history" "$HOME/.local/share/fish/fish_history" + +# Install Claude Code CLI +RUN mkdir -p "$HOME/.config/claude" && \ + curl -fsSL https://claude.ai/install.sh | bash + +# Install Open Code CLI +RUN curl -fsSL https://opencode.ai/install | bash -s -- --no-modify-path + +# Install GitHub Copilot CLI +# Note: Copilot CLI does not support XDG configuration, see: +# * +# * +RUN curl -fsSL https://gh.io/copilot-install | PREFIX="$HOME/.local" bash + +# Wrapper scripts for AI coding assistants ensure they are available in PATH +# and handle necessary environment variable setup +COPY --chmod=+x files/usr/local/bin/copilot /usr/local/bin/copilot +COPY --chmod=+x files/usr/local/bin/claude /usr/local/bin/claude +COPY --chmod=+x files/usr/local/bin/opencode /usr/local/bin/opencode +COPY --chmod=+x files/usr/local/bin/docker-entrypoint /usr/local/bin/docker-entrypoint +COPY --chmod=+x files/home/$USERNAME/shell-init.sh ${HOME}/shell-init.sh + +# Wrapper scripts that execute commands inside the appropriate Docker containers. +# These scripts use Docker-from-Docker to run the commands in the context of the +# respective Docker containers, allowing container agnostic development workflows. +COPY --chmod=+x files/usr/local/bin/node /usr/local/bin/node +COPY --chmod=+x files/usr/local/bin/npm /usr/local/bin/npm +COPY --chmod=+x files/usr/local/bin/npx /usr/local/bin/npx +COPY --chmod=+x files/usr/local/bin/mkdocs /usr/local/bin/mkdocs + +# Ensure /usr/local/bin takes precedence over VS Code injected paths +# and run profile script on every shell startup to apply .env configuration +RUN mkdir -p $HOME/.config/fish && \ + echo 'export PATH="/usr/local/bin:$PATH"' >> $HOME/.bashrc && \ + echo 'export PATH="/usr/local/bin:$PATH"' >> $HOME/.zshrc && \ + echo 'set -gx PATH /usr/local/bin $PATH' >> $HOME/.config/fish/config.fish && \ + echo '$HOME/shell-init.sh' >> $HOME/.bashrc && \ + echo '$HOME/shell-init.sh' >> $HOME/.zshrc && \ + echo '$HOME/shell-init.sh' >> $HOME/.config/fish/config.fish + +# Set the default working directory when starting a container from this image +WORKDIR /workspace + +# Set the default command to run when starting a container from this image +# The fixuid command will adjust the `developer` user's UID and GID to match +# the host user's UID and GID when running in a container, ensuring that +# any files created by the `developer` user inside the container have +# the correct ownership on the host system. +ENTRYPOINT ["docker-entrypoint"] diff --git a/docker/react_ui_devcontainer/files/home/developer/shell-init.sh b/docker/react_ui_devcontainer/files/home/developer/shell-init.sh new file mode 100644 index 000000000..3e704e44a --- /dev/null +++ b/docker/react_ui_devcontainer/files/home/developer/shell-init.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +# Block SSH agent forwarding if the environment variable `BLOCK_SSH_AUTH_SOCK` is set to "true" +if [ "$BLOCK_SSH_AUTH_SOCK" = "true" ]; then + # Disable SSH agent forwarding for the current shell session + export SSH_AUTH_SOCK=/dev/null + # Remove any existing SSH agent socket files created by VS Code + find /tmp -maxdepth 1 -name 'vscode-ssh-auth-*.sock' -delete 2>/dev/null +fi diff --git a/docker/react_ui_devcontainer/files/usr/local/bin/claude b/docker/react_ui_devcontainer/files/usr/local/bin/claude new file mode 100644 index 000000000..de603a311 --- /dev/null +++ b/docker/react_ui_devcontainer/files/usr/local/bin/claude @@ -0,0 +1,3 @@ +#!/bin/sh + +/home/developer/.local/bin/claude "$@" diff --git a/docker/react_ui_devcontainer/files/usr/local/bin/copilot b/docker/react_ui_devcontainer/files/usr/local/bin/copilot new file mode 100644 index 000000000..4df0dff87 --- /dev/null +++ b/docker/react_ui_devcontainer/files/usr/local/bin/copilot @@ -0,0 +1,3 @@ +#!/bin/sh + +/home/developer/.local/bin/copilot --config-dir "/home/developer/.config/copilot" "$@" diff --git a/docker/react_ui_devcontainer/files/usr/local/bin/docker-entrypoint b/docker/react_ui_devcontainer/files/usr/local/bin/docker-entrypoint new file mode 100644 index 000000000..661ebd04f --- /dev/null +++ b/docker/react_ui_devcontainer/files/usr/local/bin/docker-entrypoint @@ -0,0 +1,10 @@ +#!/bin/sh + +# Ensure the developer user owns their home directory and all files within it +sudo chown -R developer:developer /home/developer + +# Remap the developer user's UID/GID to match the host user +eval "$(fixuid -q)" + +# Execute the command passed to the container (e.g. via `command` in docker-compose) +exec "$@" diff --git a/docker/react_ui_devcontainer/files/usr/local/bin/mkdocs b/docker/react_ui_devcontainer/files/usr/local/bin/mkdocs new file mode 100644 index 000000000..20789ea83 --- /dev/null +++ b/docker/react_ui_devcontainer/files/usr/local/bin/mkdocs @@ -0,0 +1,3 @@ +#!/bin/sh + +sudo docker compose -p "$COMPOSE_PROJECT_NAME" exec docs mkdocs "$@" diff --git a/docker/react_ui_devcontainer/files/usr/local/bin/node b/docker/react_ui_devcontainer/files/usr/local/bin/node new file mode 100644 index 000000000..60e2db562 --- /dev/null +++ b/docker/react_ui_devcontainer/files/usr/local/bin/node @@ -0,0 +1,3 @@ +#!/bin/sh + +sudo docker compose -p "$COMPOSE_PROJECT_NAME" exec node node "$@" diff --git a/docker/react_ui_devcontainer/files/usr/local/bin/npm b/docker/react_ui_devcontainer/files/usr/local/bin/npm new file mode 100644 index 000000000..931941c1d --- /dev/null +++ b/docker/react_ui_devcontainer/files/usr/local/bin/npm @@ -0,0 +1,7 @@ +#!/bin/sh + +if [ "$1" = "run" ] && echo "$2" | grep -q '^test:playwright'; then + sudo docker compose -p "$COMPOSE_PROJECT_NAME" exec playwright npm "$@" +else + sudo docker compose -p "$COMPOSE_PROJECT_NAME" exec node npm "$@" +fi diff --git a/docker/react_ui_devcontainer/files/usr/local/bin/npx b/docker/react_ui_devcontainer/files/usr/local/bin/npx new file mode 100644 index 000000000..b4c97acc5 --- /dev/null +++ b/docker/react_ui_devcontainer/files/usr/local/bin/npx @@ -0,0 +1,3 @@ +#!/bin/sh + +sudo docker compose -p "$COMPOSE_PROJECT_NAME" exec node npx "$@" diff --git a/docker/react_ui_devcontainer/files/usr/local/bin/opencode b/docker/react_ui_devcontainer/files/usr/local/bin/opencode new file mode 100644 index 000000000..17c2198ee --- /dev/null +++ b/docker/react_ui_devcontainer/files/usr/local/bin/opencode @@ -0,0 +1,3 @@ +#!/bin/sh + +/home/developer/.opencode/bin/opencode "$@" diff --git a/docker/react_ui_devcontainer_local/Dockerfile.dist b/docker/react_ui_devcontainer_local/Dockerfile.dist new file mode 100644 index 000000000..b61d6a879 --- /dev/null +++ b/docker/react_ui_devcontainer_local/Dockerfile.dist @@ -0,0 +1,4 @@ +# Image name `react-ui_devcontainer` might differ based on Docker compose project name +FROM react-ui_devcontainer as react-ui_devcontainer_local + +# Your additional setup for the local development can go here diff --git a/package.json b/package.json index 4a3cb3758..781a0d165 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,6 @@ "lint": "npm run eslint && npm run markdownlint && npm run stylelint", "markdownlint": "markdownlint-cli2 \"README.md\" \"src/**/*.md\"", "postbuild": "npm run copy", - "postinstall": "cp -n .env.dist .env && cp -n .env.playwright.dist .env.playwright || true", "precopy": "rm -rf dist && mkdir dist", "prepublishOnly": "npm run build", "start": "webpack --watch --mode=development", diff --git a/setup.sh b/setup.sh new file mode 100755 index 000000000..e62a87db6 --- /dev/null +++ b/setup.sh @@ -0,0 +1,104 @@ +#!/bin/bash + +DEFAULT_PROJECT_NAME="react-ui" +DEFAULT_USER_ID=1000 +DEFAULT_GROUP_ID=1000 + +set -e +trap 'echo "Failed to setup project"; rf -f .env.temp; exit 1' ERR + +# Function to handle sed command with cross-platform compatibility +sed_cmd() { + if [[ "$OSTYPE" == "darwin"* ]]; then + sed -i '' "$@" + else + sed -i "$@" + fi +} + +cd "$(dirname "$0")" + +echo "Setting up project..." + +# Create and configure .env file if it doesn't exist +if [ ! -f .env ]; then + echo "Creating .env file..." + cp .env.dist .env.temp + + echo "Configuring .env file..." + + PROJECT_PATH=$(pwd) + PROJECT_NAME=$(basename "$PROJECT_PATH") + + if [[ "$OSTYPE" == "darwin"* ]]; then + # MacOS + USER_ID=$DEFAULT_USER_ID + GROUP_ID=$DEFAULT_GROUP_ID + else + # Linux + USER_ID=$(id -u) + GROUP_ID=$(id -g) + fi + + sed_cmd "s|^COMPOSE_PROJECT_NAME=.*|COMPOSE_PROJECT_NAME=$PROJECT_NAME|" .env.temp + sed_cmd "s|^COMPOSE_UID=.*|COMPOSE_UID=$USER_ID|" .env.temp + sed_cmd "s|^COMPOSE_GID=.*|COMPOSE_GID=$GROUP_ID|" .env.temp + + LOCAL_SHELL= + if [ -n "$SHELL" ]; then + sed_cmd "s|^SHELL=.*|SHELL=$SHELL|" .env.temp + LOCAL_SHELL=$SHELL + fi + + LOCAL_EDITOR= + if [ -n "$EDITOR" ]; then + sed_cmd "s|^EDITOR=.*|EDITOR=$EDITOR|" .env.temp + LOCAL_EDITOR=$EDITOR + fi + + LOCAL_VISUAL= + if [ -n "$VISUAL" ]; then + sed_cmd "s|^VISUAL=.*|VISUAL=$VISUAL|" .env.temp + LOCAL_VISUAL=$VISUAL + fi + + cp .env.temp .env + rm .env.temp + + echo "Configured .env file with the following values:" + echo "Project name: $PROJECT_NAME" + echo "Project path: $PROJECT_PATH" + echo "User ID: $USER_ID" + echo "Group ID: $GROUP_ID" + + if [ -n "$LOCAL_SHELL" ]; then + echo "Shell: $LOCAL_SHELL" + fi + if [ -n "$LOCAL_EDITOR" ]; then + echo "Editor: $LOCAL_EDITOR" + fi + if [ -n "$LOCAL_VISUAL" ]; then + echo "Visual: $LOCAL_VISUAL" + fi +else + echo ".env file already exists, skipping creation." +fi + +# Create docker-compose.yml if it doesn't exist +if [ ! -f docker-compose.yml ]; then + echo "Creating and configuring docker-compose.yml file..." + cp docker-compose.yml.dist docker-compose.yml + + DEFAULT_PROJECT_DEVCONTAINER_IMAGE="${DEFAULT_PROJECT_NAME}_devcontainer" + PROJECT_NAME=$(grep -E '^COMPOSE_PROJECT_NAME=' .env | cut -d '=' -f 2-) + PROJECT_DEVCONTAINER_IMAGE="${PROJECT_NAME}_devcontainer" + + sed_cmd "s|image: $DEFAULT_PROJECT_DEVCONTAINER_IMAGE|image: $PROJECT_DEVCONTAINER_IMAGE|" docker-compose.yml +else + echo "docker-compose.yml file already exists, skipping creation." +fi + +# Build Docker images +sh ./docker/build-docker-images.sh + +echo "Project setup completed successfully!" diff --git a/src/docs/contribute/general-guidelines.md b/src/docs/contribute/general-guidelines.md index 74aa840ac..9dc76af92 100644 --- a/src/docs/contribute/general-guidelines.md +++ b/src/docs/contribute/general-guidelines.md @@ -2,99 +2,205 @@ In the first place, thank you for your interest in contributing! 🙏 -## Development +## Development Environment -Working on the site requires: +### Overview -* [Docker] -* [Docker Compose] +All development is done inside the container named `devcontainer` which +contains all necessary tools and dependencies. All commands in the documentation +are container-agnostic and are meant to be run directly inside the `devcontainer`. -This allows running the documentation site which serves as a development platform. +The `devcontainer` orchestrates other service containers behind the scenes via +Docker-from-Docker. Other containers are implementation details and should not +be accessed directly. -### Configure Docker Compose +There are two supported ways to access the development environment. Recommended +way is to use [Development Containers] with an IDE, which provides a more +seamless experience. The alternative is to use Docker Compose directly. -Review the default env variable values in the `docker-compose.yml` file. -The defaults should work for most systems, but can be changed if needed. -To change them, edit the `.env` file as needed. +### Requirements -### Use Docker Compose +* [Docker] +* [Docker Compose] +* [Development Containers] (strongly recommended) -#### Node shell +### Setup -All npm commands such as `npm ci`, `npm test`, `npm run eslint` and others you -need to run them within the `node_shell` Docker container. +#### Automatic setup -To log into the container, run: +Run the setup script to automatically create and configure all necessary files +and build Docker images: ```bash -docker compose run --rm node_shell +bash setup.sh ``` -If you want to run single command, run: +[Development Containers] run this script automatically if the project has not +been set up prior to opening it. -```bash -docker compose run --rm node_shell -c 'npm ' -``` +#### Manual setup -#### Run the Dev Server +If you prefer to set up the project manually: -1. **Within `node_shell`:** Install dependencies: +1. Create `.env` file and configure it: ```bash - npm ci + cp .env.dist .env ``` -2. **On host:** Run development server: +2. Create `docker-compose.yml` and configure it: ```bash - docker compose up node_dev_server mkdocs_dev_server + cp docker-compose.yml.dist docker-compose.yml ``` -#### Build the Project - -1. **On host:** Make sure the dev server is not running: +3. Build Docker images: ```bash - docker compose down + bash docker/build-docker-images.sh ``` -2. **Within `node_shell`:** Install dependencies: +#### Environment + +The `.env` file configures services (ports, UID/GID, source mapping), +the `devcontainer` shell, editor and SSH agent forwarding, as well as application +settings. See `.env.dist` for available options. + +### Accessing the Development Environment + +#### Using Development Containers + +Open the project in an IDE that supports [Development Containers] (e.g. +[Visual Studio Code][vscode-devcontainers], [JetBrains IDEs][jetbrains-devcontainers]). +The IDE will automatically setup the environment using the configuration in +`.devcontainer/devcontainer.json`. + +#### Using Docker Compose + +1. Start the `devcontainer` in the background: ```bash - npm ci + docker compose up -d ``` -3. **On host:** Build JS: +2. Open a shell inside the `devcontainer`: ```bash - docker compose run --rm node_build_site + docker compose exec devcontainer bash ``` -4. **On host:** Build mkDocs: +3. To stop the environment: ```bash - docker compose run --rm mkdocs_build_site + docker compose down ``` -#### Playwright +### Customization + +To customize the `devcontainer`, create a +`docker/react_ui_devcontainer_local/Dockerfile` that extends the base image: + +```Dockerfile +# Image name `react-ui_devcontainer` might differ based on Docker compose project name +FROM react-ui_devcontainer as react-ui_devcontainer_local +# Add your customizations here +``` + +Then ensure `docker-compose.yml` has the `build` directive for the `devcontainer` +service: + +```yml +devcontainer: + extends: + file: docker-compose.base.yml + service: devcontainer + build: + context: ./docker/react_ui_devcontainer_local/ + dockerfile: Dockerfile +``` + +Rebuild the images after making changes: + +```bash +bash docker/build-docker-images.sh +``` + +If you need to persist additional data across container restarts, see how it is +done in `docker-compose.base.yml`. You will need to add a volume mapping to the +`devcontainer` service and add a corresponding named volume definition. -npm commands such as `test:playwright-ct:all` and `test:playwright-ct:all-with-update` -need to be run them within the `playwright` Docker container. +### What the `devcontainer` Contains -To log into the container, run: +The `devcontainer` is built in the following layers: + +#### Base Layer (`react-ui_devcontainer`) + +General-purpose development layer. Makes the environment container-agnostic +by wrapping commands to run in the appropriate service containers. + +* **OS:** Debian Bookworm +* **Shells:** Bash, Zsh (with Oh My Zsh), Fish +* **Editors:** Vim, Nano +* **Tools:** Git, SSH client, Docker CLI (Docker-from-Docker) +* **AI coding assistants:** Claude Code, GitHub Copilot CLI, Open Code + +#### Local Layer (`react-ui_devcontainer_local`) + +Optional layer that allows individual developers to customize the environment. +See [Customization](#customization) for details. + +### Service Containers + +The `devcontainer` depends on the following service containers defined in +`docker-compose.base.yml`: + +| Container | Purpose | +|--------------|--------------------------------------------------| +| `node` | Runs Node.js commands (`npm`, `node`) | +| `playwright` | Runs Playwright and Lighthouse tests | +| `docs` | Serves documentation via MkDocs | + +All service containers mount the workspace at `/workspace` so that file changes +are shared. + +## Installing Dependencies + +Run it on initial setup or when dependencies have changed: ```bash -docker compose run --rm --service-ports playwright +npm ci ``` -If you want to run single command, run: +## Building + +To build the JavaScript code: ```bash -docker compose run --rm --service-ports playwright -c 'npm run test:playwright-ct:*' +npm run build ``` -Argument `--service-ports` is used to expose the ports of the container to the host -to serve the test report. +To build the documentation: + +```bash +mkdocs build +``` + +## Running + +> See `.env` whether both the application and documentation server are not configured +> to auto-start on container startup. If they are, you can skip the following steps. + +To start building JavaScript files in watch mode: + +```bash +npm run start +``` + +To start the documentation server: + +```bash +mkdocs serve +``` ## Testing @@ -161,7 +267,7 @@ pull request from the changelog. The best way for development of React UI is to link `react-ui` into your application with `npm link` so you can see it in action. -1. In React UI repository, run `npm link` +1. In React UI repository **on your host machine**, run `npm link` 2. In your application, run `npm link @react-ui-org/react-ui` To prevent [Invalid Hook Call Warning][react-invalid-hook] when React UI is @@ -202,8 +308,11 @@ the documentation platform. Do see their respective documentation for details. -[Docker]: https://www.docker.com +[Development Containers]: https://containers.dev/ +[Docker]: https://docs.docker.com/get-started/ [Docker Compose]: https://docs.docker.com/compose/ +[jetbrains-devcontainers]: https://www.jetbrains.com/help/idea/start-dev-container-inside-ide.html#dev_container_context_menu +[vscode-devcontainers]: https://code.visualstudio.com/docs/devcontainers/tutorial [react-invalid-hook]: https://reactjs.org/warnings/invalid-hook-call-warning.html#duplicate-react [mkdocs-material]: https://squidfunk.github.io/mkdocs-material/ [Docoff]: https://github.com/react-ui-org/docoff diff --git a/src/docs/contribute/testing-guidelines.md b/src/docs/contribute/testing-guidelines.md index 2ff90ccbd..33b235bb7 100644 --- a/src/docs/contribute/testing-guidelines.md +++ b/src/docs/contribute/testing-guidelines.md @@ -3,9 +3,9 @@ Tools used to test the application: * **ESLint** (static code analysis of JS files) -* **Markdownlint** (static analysis of MD files) * **Stylelint** (static code analysis of CSS files) -* **Jest** (unit testing) +* **Markdownlint** (static code analysis of Markdown files) +* **Jest** (unit tests) * **Playwright** (visual and functional component testing) Generally, `npm test` and `npm run test:playwright-ct:all` should be run within @@ -13,17 +13,13 @@ their designated Docker containers before pushing changes to the repository. ## Tools -### Linters (ESlint, Markdownlint, Stylelint) - -**On host:** - -[Open][gh-gg-node-shell] `node_shell` Docker container: +You can run all tests with a single command: ```bash -docker compose run --rm node_shell +npm run lint && npm test && npm run test:playwright-ct:all ``` -**Within `node_shell`:** +### Linters (ESLint, Markdownlint, Stylelint) Run linters either all together: @@ -39,52 +35,18 @@ npm run ### Jest -**On host:** - -[Open][gh-gg-node-shell] `node_shell` Docker container: - -```bash -docker compose run --rm node_shell -``` - -**Within `node_shell`:** - -Run Jest tests: - ```bash npm run test:jest ``` ### Playwright -Playwright tests must be run in a Docker container to ensure a uniform -environment. Otherwise, snapshots would differ between operating systems. - -This is the reason why you need to run Playwright tests separately -from other tools. - #### Configuration -Test parameters can be tweaked by creating and tweaking `.env.playwright` file: - -```bash -cp .env.playwright.dist .env.playwright -``` +Test parameters can be tweaked by creating and tweaking `.env` file. #### Running Tests -Playwright tests can be run using the following commands: - -**On host:** - -[Open][gh-gg-playwright] `playwright` Docker container: - -```bash -docker compose run --rm --service-ports playwright -``` - -**Within `playwright`:** - Run tests: ```bash @@ -116,6 +78,4 @@ Then open the displayed URL (typically `http://localhost:9323`) in your browser. Please note that the test report is only available if the tests were run prior to serving the report. -[gh-gg-node-shell]: ./general-guidelines.md#node-shell -[gh-gg-playwright]: ./general-guidelines.md#playwright [playwright-cli]: https://playwright.dev/docs/test-cli#reference diff --git a/tests/playwright/env/parseDotEnvFile.ts b/tests/playwright/env/parseDotEnvFile.ts index 02aa3a346..9f1ac73aa 100644 --- a/tests/playwright/env/parseDotEnvFile.ts +++ b/tests/playwright/env/parseDotEnvFile.ts @@ -1,6 +1,6 @@ import dotenv from 'dotenv'; -const PLAYWRIGHT_ENV_FILE = '.env.playwright'; +const PLAYWRIGHT_ENV_FILE = '.env'; /** * Load and parse Playwright environment file into an object.