From 6f53b593d7108ec33380424982b7fb22f1028b1c Mon Sep 17 00:00:00 2001 From: GitHub Copilot Date: Wed, 7 May 2025 13:54:02 +0000 Subject: [PATCH] Refactor shell scripts for improved safety and consistency; add guidelines to documentation --- .devcontainer/build-and-push.sh | 112 +++++++++++++--------- .devcontainer/library-scripts/load-env.sh | 44 +++++++++ .github/copilot-instructions.md | 25 +++++ 3 files changed, 136 insertions(+), 45 deletions(-) mode change 100644 => 100755 .devcontainer/build-and-push.sh create mode 100755 .devcontainer/library-scripts/load-env.sh diff --git a/.devcontainer/build-and-push.sh b/.devcontainer/build-and-push.sh old mode 100644 new mode 100755 index 09a85bf..36c2968 --- a/.devcontainer/build-and-push.sh +++ b/.devcontainer/build-and-push.sh @@ -1,90 +1,112 @@ -#!/bin/bash +#!/usr/bin/env bash -# Exit on error, undefined variables, and pipe failures -set -euo pipefail +# Exit on error. Append "|| true" if you expect an error. +set -o errexit +# Exit on error inside any functions or subshells. +set -o errtrace +# Do not allow use of undefined vars. Use ${VAR:-} to use an undefined VAR +set -o nounset +# Catch pipeline errors +set -o pipefail +# Turn on traces, useful while debugging but commented out by default +# set -o xtrace # Configuration -GITHUB_USERNAME=$1 -IMAGE_NAME="finance-devcontainer" -IMAGE_TAG="latest" - -# Load environment variables from .env file if it exists -ENV_FILE="$(cd "$(dirname "${BASH_SOURCE[0]}")" &> /dev/null && pwd)/.env" -if [ -f "$ENV_FILE" ]; then - echo "Loading environment from $ENV_FILE" - # Use grep to find the PAT line and extract the value, handling both Unix and Windows line endings - GITHUB_PERSONAL_ACCESS_TOKEN=$(grep -a "^GITHUB_PERSONAL_ACCESS_TOKEN=" "$ENV_FILE" | sed 's/^GITHUB_PERSONAL_ACCESS_TOKEN=//' | tr -d '\r') - export GITHUB_PERSONAL_ACCESS_TOKEN -fi +readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" &> /dev/null && pwd)" +readonly ENV_FILE="${SCRIPT_DIR}/.env" +readonly IMAGE_NAME="finance-devcontainer" +readonly IMAGE_TAG="latest" # Check for required username argument -if [ -z "${GITHUB_USERNAME:-}" ]; then - echo "Error: GitHub username is required" - echo "Usage: $0 " - echo "Example: $0 acedanger" +if [[ $# -lt 1 ]]; then + echo >&2 "Error: GitHub username is required" + echo >&2 "Usage: $0 " + echo >&2 "Example: $0 acedanger" exit 1 fi +readonly GITHUB_USERNAME="$1" +readonly DOCKERFILE_PATH="${SCRIPT_DIR}/Dockerfile" +readonly FULL_IMAGE_NAME="ghcr.io/${GITHUB_USERNAME}/${IMAGE_NAME}:${IMAGE_TAG}" + # Check for required tools for cmd in docker gh; do - if ! command -v "$cmd" >/dev/null 2>&1; then - echo "Error: $cmd is required but not installed" + if ! command -v "${cmd}" >/dev/null 2>&1; then + echo >&2 "Error: ${cmd} is required but not installed" exit 1 fi done +# Load environment variables from .env file if it exists +if [[ ! -f "${ENV_FILE}" ]]; then + echo >&2 "Error: Environment file not found: ${ENV_FILE}" + exit 1 +fi + +echo "Loading environment from ${ENV_FILE}" +# Read the env file line by line to handle special characters correctly +while IFS= read -r line || [[ -n "${line}" ]]; do + # Skip comments and empty lines + [[ "${line}" =~ ^[[:space:]]*# ]] && continue + [[ -z "${line}" ]] && continue + + # Export the variable if it's the PAT + if [[ "${line}" =~ ^GITHUB_PERSONAL_ACCESS_TOKEN= ]]; then + export "${line}" + break + fi +done < "${ENV_FILE}" + # Verify PAT is loaded -if [ -z "${GITHUB_PERSONAL_ACCESS_TOKEN:-}" ]; then - echo "Error: GITHUB_PERSONAL_ACCESS_TOKEN is not set" - echo "Please ensure it is defined in $ENV_FILE" +if [[ -z "${GITHUB_PERSONAL_ACCESS_TOKEN:-}" ]]; then + echo >&2 "Error: GITHUB_PERSONAL_ACCESS_TOKEN is not set in ${ENV_FILE}" exit 1 fi # Check GitHub authentication if ! gh auth status >/dev/null 2>&1; then - echo "Error: Not authenticated with GitHub. Please run 'gh auth login' first" + echo >&2 "Error: Not authenticated with GitHub. Please run 'gh auth login' first" exit 1 fi -# Get absolute path to Dockerfile -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" &> /dev/null && pwd)" -DOCKERFILE_PATH="$SCRIPT_DIR/Dockerfile" - -FULL_IMAGE_NAME="ghcr.io/$GITHUB_USERNAME/$IMAGE_NAME:$IMAGE_TAG" - echo "=== Building Development Container ===" -echo "Username: $GITHUB_USERNAME" -echo "Image: $FULL_IMAGE_NAME" -echo "Dockerfile: $DOCKERFILE_PATH" +echo "Username: ${GITHUB_USERNAME}" +echo "Image: ${FULL_IMAGE_NAME}" +echo "Dockerfile: ${DOCKERFILE_PATH}" echo "Using PAT: ${GITHUB_PERSONAL_ACCESS_TOKEN:0:4}... (first 4 chars)" # Build the image echo -e "\n=> Building image..." -if ! docker build -t "$FULL_IMAGE_NAME" -f "$DOCKERFILE_PATH" "$SCRIPT_DIR"; then - echo "Error: Docker build failed" +if ! docker build -t "${FULL_IMAGE_NAME}" -f "${DOCKERFILE_PATH}" "${SCRIPT_DIR}"; then + echo >&2 "Error: Docker build failed" exit 1 fi # Log in to GitHub Container Registry echo -e "\n=> Logging into GitHub Container Registry..." -echo "$GITHUB_PERSONAL_ACCESS_TOKEN" | docker login ghcr.io -u "$GITHUB_USERNAME" --password-stdin +if ! echo "${GITHUB_PERSONAL_ACCESS_TOKEN}" | docker login ghcr.io -u "${GITHUB_USERNAME}" --password-stdin; then + echo >&2 "Error: Failed to log in to GitHub Container Registry" + exit 1 +fi # Push to GitHub Container Registry echo -e "\n=> Pushing image to GitHub Container Registry..." -if ! docker push "$FULL_IMAGE_NAME"; then - echo "Error: Failed to push image" - echo "Please check your GitHub PAT has the required permissions:" - echo " - read:packages" - echo " - write:packages" +if ! docker push "${FULL_IMAGE_NAME}"; then + echo >&2 "Error: Failed to push image" + echo >&2 "Please check your GitHub PAT has the required permissions:" + echo >&2 " - read:packages" + echo >&2 " - write:packages" exit 1 fi echo -e "\n=== Success! ===" echo "The development container image has been built and pushed" echo -e "\nTo use this image, update your devcontainer.json with:" -echo '{ - "image": "'$FULL_IMAGE_NAME'" -}' +cat << EOF +{ + "image": "${FULL_IMAGE_NAME}" +} +EOF echo -e "\nNext steps:" echo "1. Update .devcontainer/devcontainer.json with the image reference above" diff --git a/.devcontainer/library-scripts/load-env.sh b/.devcontainer/library-scripts/load-env.sh new file mode 100755 index 0000000..cae2b47 --- /dev/null +++ b/.devcontainer/library-scripts/load-env.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash + +# Exit on error. Append "|| true" if you expect an error. +set -o errexit +# Exit on error inside any functions or subshells. +set -o errtrace +# Do not allow use of undefined vars. Use ${VAR:-} to use an undefined VAR +set -o nounset +# Catch the error in case mysqldump fails (but gzip succeeds) in `mysqldump |gzip` +set -o pipefail +# Turn on traces, useful while debugging but commented out by default +# set -o xtrace + +# Load environment variables from .env file +readonly ENV_FILE="/workspaces/finance/.devcontainer/.env" + +if [[ ! -f "${ENV_FILE}" ]]; then + echo >&2 "Error: Environment file not found: ${ENV_FILE}" + exit 1 +fi + +echo "Loading environment variables from ${ENV_FILE}" + +# Read the env file line by line to handle special characters correctly +while IFS= read -r line || [[ -n "${line}" ]]; do + # Skip comments and empty lines + [[ "${line}" =~ ^[[:space:]]*# ]] && continue + [[ -z "${line}" ]] && continue + + # Export the variable + if [[ "${line}" =~ ^[[:alpha:]][[:alnum:]_]*= ]]; then + export "${line}" + else + echo >&2 "Warning: Skipping invalid line: ${line}" + fi +done < "${ENV_FILE}" + +# Verify PAT loaded correctly (without printing the actual token) +if [[ -n "${GITHUB_PERSONAL_ACCESS_TOKEN:-}" ]]; then + echo "GitHub Personal Access Token loaded successfully" +else + echo >&2 "ERROR: GitHub Personal Access Token not loaded" + exit 1 +fi diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 00a5593..eb7ffff 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -133,6 +133,31 @@ This project is a financial transaction management application built with Astro ## Code Style & Conventions +### Shell Scripts + +All shell scripts should follow these conventions: + +- Use `#!/usr/bin/env bash` instead of direct path to bash +- Include standard safety flags at the start of each script: + ```bash + set -o errexit # Exit on error + set -o errtrace # Exit on error inside functions + set -o nounset # Error on undefined variables + set -o pipefail # Error if any command in a pipe fails + ``` +- Use `readonly` for constants and configuration values +- Write error messages to stderr using `>&2` +- Use proper error handling and exit codes +- Validate all required parameters and dependencies +- Use `[[` instead of `[` for better feature support +- Quote all variable expansions +- Use `${variable:-}` pattern for safe variable expansion +- Handle special characters in input properly +- Include clear error messages and validation +- Group configuration/constants at the top of the script +- Add descriptive comments for complex logic +- Follow consistent indentation and formatting + ### Import Path Aliases Always use the path aliases defined in `tsconfig.json` instead of relative imports. This makes the code more maintainable and easier to refactor.