diff --git a/.env.github.example b/.env.github.example deleted file mode 100644 index 85ddbe8e..00000000 --- a/.env.github.example +++ /dev/null @@ -1,57 +0,0 @@ -# General secrets - -# REVALIDATE_SECRET=xxxxxxxxxxx -WORDPRESS_THEME_NAME=superstack -# SSH_PRIVATE_KEY=ssh-private-key-for-deployement # Setting the private key in here will probably not work, as it's a rich string. You should set it manually in the Actions secrets on Github. - -# If deploy to Vercel -VERCEL_TOKEN=xxxxxxxxxxx -VERCEL_ORG_ID=xxxxxxxxxxx -VERCEL_PROJECT_ID=xxxxxxxxxxx -## - -# If not deployed to Vercel -PM2_APP_NAME=superstack -## - -# If forms => hcaptcha -WORDPRESS_HCAPTCHA_SECRET=0x48Ea5d2b9d1418586Da184Bb50352F7CAc6b79E3 - -####################################### -# STAGING / PREVIEWS -####################################### - -## BACK-END WordPress -STAGING_BE_SSH_USER=username -STAGING_BE_SSH_HOST=ip.address -STAGING_BE_SSH_PORT=22 -STAGING_WORDPRESS_PATH=/absolute/path/to/wp/dir/on/remote/server -STAGING_WORDPRESS_URL=https://admin-staging.yourdomain.com - - -## FRONT-END Next.js -# If not deployed to Vercel -STAGING_NEXT_PATH=/absolute/path/to/parent/dir/of/next/on/remote/server -STAGING_NEXT_URL=https://staging.yourdomain.com - - -####################################### -# PRODUCTION -####################################### - -## BACK-END WordPress -PRODUCTION_BE_SSH_USER=username -PRODUCTION_BE_SSH_HOST=ip.address -PRODUCTION_BE_SSH_PORT=22 -PRODUCTION_WORDPRESS_PATH=/absolute/path/to/wp/dir/on/remote/server -PRODUCTION_WORDPRESS_URL=https://admin.yourdomain.com - -## FRONT-END Next.js -# If not deployed to Vercel -PRODUCTION_NEXT_PATH=/absolute/path/to/parent/dir/of/next/on/remote/server -PRODUCTION_NEXT_URL=https://yourdomain.com - - - - - diff --git a/.env.github.secrets.example b/.env.github.secrets.example new file mode 100644 index 00000000..6d818d14 --- /dev/null +++ b/.env.github.secrets.example @@ -0,0 +1,16 @@ +# Github Token used by Composer to install and download packages +COMPOSER_GITHUB_TOKEN= +# NEXT (frontend) SSH private key used for deployment +NEXT_SSH_PRIVATE_KEY= +# Release belt password used to download custom repositories +RELEASE_BELT_PASSWORD= +# WORDPRESS database password +WORDPRESS_DB_PASSWORD= +# WORDPRESS (backend) SSH private key used for deployment +WORDPRESS_SSH_PRIVATE_KEY= +# Wordpress' admin user password +WORDPRESS_ADMIN_PASSWORD= + +## OPTIONAL +# Token for vercel deployment +VERCEL_TOKEN= diff --git a/.env.github.variables.example b/.env.github.variables.example new file mode 100644 index 00000000..ba8ad500 --- /dev/null +++ b/.env.github.variables.example @@ -0,0 +1,38 @@ +# NEXT (frontend) SSH params used for deployment +NEXT_SSH_HOST=superhuit.ch +NEXT_SSH_PATH=/var/www/public +NEXT_SSH_PORT=22 +NEXT_SSH_USER=superstack +# Frontend URL (no trailing slash) +NEXT_URL=https://superstack.superhuit.dev +# Release belt user used to download custom repositories +RELEASE_BELT_USER=superhuit +# WORDPRESS (backend) admin credentials +WORDPRESS_ADMIN_EMAIL=tech+superstack@superhuit.ch +WORDPRESS_ADMIN_USER=superstack +# WORDPRESS Database credentials +WORDPRESS_DB_HOST=localhost +WORDPRESS_DB_NAME=wordpress_superstack +WORDPRESS_DB_USER=wordpress +# WORDPRESS default locale (follow xx_XX format) +WORDPRESS_LOCALE=fr_FR +# WORDPRESS absolute folder used in the backend (also used by SSH) +WORDPRESS_PATH=/var/www/wordpress +# WORDPRESS SSH params used for deployment +WORDPRESS_SSH_HOST=superhuit.ch +WORDPRESS_SSH_PORT=22 +WORDPRESS_SSH_USER=superadmin +# Theme name (should only use alphanumeric characters) +WORDPRESS_THEME_NAME=superstack +# Theme title +WORDPRESS_THEME_TITLE="Superstack by Superhuit" +# Backend URL (without wp-admin, no trailing slash) +WORDPRESS_URL=https://superstack-admin.superhuit.dev +# WORDPRESS Version +WORDPRESS_VERSION=6.5 + +## OPTIONAL +# Vercel project ID (if set, workflow will prefer Vercel deployment) +VERCEL_PROJECT_ID= +# Vercel Organization ID +VERCEL_ORG_ID= diff --git a/.env.local.example b/.env.next.example similarity index 93% rename from .env.local.example rename to .env.next.example index a8ddf68c..3681f7f7 100644 --- a/.env.local.example +++ b/.env.next.example @@ -15,4 +15,4 @@ NEXT_PUBLIC_HCAPTCHA_KEY=10000000-ffff-ffff-ffff-000000000001 # this is a testin NEXT_PUBLIC_GTM_KEY=GTM-P7K7K7K7 # Google Tag Manager key ## Secret to authorize revalidation -REVALIDATE_SECRET=37fae9e2-0f35-4c37-8d96-429444ce0e6f +REVALIDATE_SECRET=da272fd1-cd34-437b-95f3-dfd9e53b6766 diff --git a/.github/actions/automated/check-changelog.sh b/.github/actions/automated/check-changelog.sh new file mode 100755 index 00000000..02aa347d --- /dev/null +++ b/.github/actions/automated/check-changelog.sh @@ -0,0 +1,109 @@ +#!/bin/bash + +# Usage: script.sh [--version|--desc] + +# Check input parameters +if [ "$#" -lt 2 ]; then + echo "Usage: $0 [--version|--desc]" + exit 1 +fi + +changelog_path="$1" +last_version="$2" +mode=$3 + +# Read the changelog file +if [ ! -f "$changelog_path" ]; then + echo "$changelog_path file was not found or could not be opened." + exit 1 +fi + +# Initialize variables +new_version_found=false +capture_description=false +description="" + +compare_versions() { + # Usage: compare_versions + # Outputs: 1 if version1 > version2 + # 0 if version1 == version2 + # -1 if version1 < version2 + awk -v ver1="$1" -v ver2="$2" 'BEGIN { + split(ver1, a, "."); + split(ver2, b, "."); + for (i = 1; i <= length(a) || i <= length(b); i++) { + if (a[i] + 0 < b[i] + 0) { + print -1; + exit; + } + else if (a[i] + 0 > b[i] + 0) { + print 1; + exit; + } + } + print 0; + }' +} + +# Read the file line by line +while IFS= read -r line || [[ -n "$line" ]]; do + if [[ $line =~ ^##\ ([0-9]+\.[0-9]+\.[0-9]+) ]]; then + # When a version header is found + if $new_version_found; then + # If we found the new version in the previous iteration, stop reading further + break + fi + + new_version="${BASH_REMATCH[1]}" + + comparison_result=$(compare_versions $new_version $last_version) + if [[ $comparison_result -eq 1 ]]; then + # Start capturing the description for the next version found + new_version_found=true + else + # Stop capturing if this version is less than or equal to the last known version + break + fi + elif $new_version_found; then + # Capture the description lines + description+="$line"$'\n' + fi +done < "$changelog_path" + +# Output based on mode +case "$mode" in + "--version") + # Check if a new version was found + if $new_version_found; then + echo "$new_version" + else + echo "patch" + fi + exit 0 + ;; + "--desc") +# Trim trailing newlines + while [[ "$description" =~ $'\n'$ ]]; do + description="${description%$'\n'}" + done + + # Trim leading newlines + while [[ "$description" = $'\n'* ]]; do + description="${description#*$'\n'}" + done + echo "$description" + ;; + *) + if [ -n "$mode" ]; then + echo "Invalid option: $mode" + exit 1 + fi + # Error of Do nothing if no mode specified + if ! $new_version_found; then + echo "Error - The changelog file doesn't contain any information over $last_version." + exit 1 + fi + echo "Success - A changelog entry was found for v$new_version. Good job!" + ;; +esac +exit 0 diff --git a/.github/actions/automated/git-release.sh b/.github/actions/automated/git-release.sh new file mode 100644 index 00000000..bd4c26cf --- /dev/null +++ b/.github/actions/automated/git-release.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +# Check input parameters +if [ "$#" -lt 4 ]; then + echo "Usage: $0 [--draft]" + exit 1 +fi + +# Variables +REPO=$1 # Repo should be the 1st argument +TOKEN=$2 # Pass GitHub token as the second script argument +VERSION=$3 # New version used for the release (excluding the v prefix) +changelogfile=$4 # Path to the changelog to read the description +draft_mode=$5 # Path to the changelog to read the description + +# Read the description of last version written in the changelog +DESCRIPTION=$(.github/actions/automated/check-changelog.sh $changelogfile 0.0.0 --desc) + +is_draft=false +if [[ "$draft_mode" == "--draft" ]]; then + is_draft=true +fi + +# Get the latest merged PR +PR_TITLE=$(curl -H "Authorization: token $TOKEN" \ + -H "Accept: application/vnd.github.v3+json" \ + "https://api.github.com/repos/$REPO/pulls?state=closed&base=main&sort=updated&direction=desc" \ + -s | jq -r '[.[] | select(.merged_at != null)][0].title') + +RELEASE_DATA=$(jq -n \ + --arg tag "v$VERSION" \ + --arg name "v$VERSION - $PR_TITLE" \ + --arg body "$DESCRIPTION" \ + --argjson draft $is_draft \ + '{tag_name: $tag, name: $name, body: $body, draft: $draft}') + +curl -H "Authorization: token $TOKEN" \ + -H "Content-Type: application/json" \ + -H "Accept: application/vnd.github.v3+json" \ + -X POST \ + -d "$RELEASE_DATA" \ + "https://api.github.com/repos/$REPO/releases" + +echo "A draft release for v$VERSION was sent to $REPO with the following content:" +echo "$PR_TITLE" +echo "$DESCRIPTION" diff --git a/.github/actions/automated/increment-semver.sh b/.github/actions/automated/increment-semver.sh new file mode 100755 index 00000000..085dacd1 --- /dev/null +++ b/.github/actions/automated/increment-semver.sh @@ -0,0 +1,75 @@ +#!/bin/bash +file_path=$1 +version_type=$2 +output_version=false + +# Check for arguments +for arg in "$@" +do + case $arg in + --out) + output_version=true + ;; + --help) + echo "Usage: $0 filepath {major|minor|patch|self|x.x.x} [--out]" + exit 0 + ;; + *) + # Ignore unknown arguments + ;; + esac +done + +# Extract the current version using awk +current_version=$(awk -F'"' '/"version":/ {print $4; exit}' $file_path) + +if [ -z "$current_version" ]; then + echo "Version not found in $file_path" + exit 1 +fi + +# Break the version number into major, minor, and patch +IFS='.' read -r major minor patch <<< "$current_version" + +# Increment version based on the specified type +# Construct the new version +new_version="$version_type" +case $version_type in + self) + new_version="$major.$minor.$patch" + ;; + major) + new_version="$((major + 1)).0.0" + ;; + minor) + new_version="$major.$((minor + 1)).0" + ;; + patch) + new_version="$major.$minor.$((patch + 1))" + ;; +esac + +if $output_version; then + echo "$new_version" + exit 0 +else + echo "Updating $file_path version from $current_version to: $new_version" +fi + +# Update composer.json with the new version using sed +# Using sed with proper handling for both macOS and Linux environments +# macOS requires an empty string with the -i option +# Be careful, only replace the first occurence at the top of the file +if [[ "$OSTYPE" == "darwin"* ]]; then + sed -i '' "1,/\"version\": \"$current_version\"/ s/\"version\": \"$current_version\"/\"version\": \"$new_version\"/1" $file_path +else + # Linux does not require the empty string + sed -i "s/\"version\": \"$current_version\"/\"version\": \"$new_version\"/1" $file_path +fi + +# Git commit and push +# git add $file_path +# git commit -m "Update version to $new_version" +# git push origin main # Adjust the branch name as necessary + +# echo "Version updated to $new_version and changes pushed to GitHub." diff --git a/.github/actions/automated/update-changelog.sh b/.github/actions/automated/update-changelog.sh new file mode 100755 index 00000000..0396e455 --- /dev/null +++ b/.github/actions/automated/update-changelog.sh @@ -0,0 +1,121 @@ +#!/bin/bash + +# Usage: script.sh [--version|--desc] + +# Check input parameters +if [ "$#" -lt 2 ]; then + echo "Usage: $0 " + exit 1 +fi + +REPO=$1 # Repo should be the 1st argument +TOKEN=$2 # Pass GitHub token as the second script argument +changelog_path="$3" +log_version="$4" +new_version=$5 + +# Read the changelog file +if [ ! -f "$changelog_path" ]; then + echo "Changelog file does not exist." + exit 1 +fi + +# Initialize variables +new_version_found=false +capture_description=false +description="" + +# Read the file line by line +while IFS= read -r line || [[ -n "$line" ]]; do + if [[ $line =~ ^##\ ([0-9]+\.[0-9]+\.[0-9]+) ]]; then + # When a version header is found + version_number="${BASH_REMATCH[1]}" + + if $new_version_found; then + # If we found the new version in the previous iteration, stop reading further + break + fi + + if [[ "$version_number" == "$new_version" ]]; then + # Start capturing the description for the next version found + new_version_found=true + else + if [[ $log_version == "patch" ]]; then + log_version=$version_number; + fi + # Stop capturing if this version is less than or equal to the last known version + break + fi + + elif $new_version_found; then + # Capture the description lines + description+="$line"$'\n' + fi +done < "$changelog_path" + +if $new_version_found; then + echo "Changelog was already containing information for v$new_version. Good job." # Trim trailing newline + exit 0 +else + + DATE=$(date "+%Y-%m-%d") + description="## $new_version - $DATE\n\n" + + # Get the commit SHA of the last release tag + LAST_TAG_SHA=$(curl -H "Authorization: token $TOKEN" \ + -H "Accept: application/vnd.github.v3+json" \ + "https://api.github.com/repos/$REPO/git/refs/tags/v$log_version" \ + -s | jq -r '.object.sha // empty') + + # If we can't find the tag with 'v' prefix, try without it + if [ -z "$LAST_TAG_SHA" ]; then + LAST_TAG_SHA=$(curl -H "Authorization: token $TOKEN" \ + -H "Accept: application/vnd.github.v3+json" \ + "https://api.github.com/repos/$REPO/git/refs/tags/$log_version" \ + -s | jq -r '.object.sha // empty') + fi + + # Initialize LAST_TAG_DATE + LAST_TAG_DATE="" + + # Get the date of the last release commit if we found the tag + if [ -n "$LAST_TAG_SHA" ]; then + LAST_TAG_DATE=$(curl -H "Authorization: token $TOKEN" \ + -H "Accept: application/vnd.github.v3+json" \ + "https://api.github.com/repos/$REPO/git/commits/$LAST_TAG_SHA" \ + -s | jq -r '.author.date') + fi + + # Get commits since last release or fallback to last 10 + if [ -n "$LAST_TAG_DATE" ] && [ "$LAST_TAG_DATE" != "null" ]; then + echo "Getting commits since last version $log_version (after $LAST_TAG_DATE)" + COMMITS=$(curl -H "Authorization: token $TOKEN" \ + -H "Accept: application/vnd.github.v3+json" \ + "https://api.github.com/repos/$REPO/commits?sha=main&since=$LAST_TAG_DATE" \ + -s | jq -r '.[] | select(.sha != "'$LAST_TAG_SHA'") | "- [\(.sha | .[:7])](https://github.com/'$REPO'/commit/\(.sha)) \(.commit.message) (\(.commit.author.name))"') + else + echo "Getting last 10 commits (could not find release tag v$log_version)" + COMMITS=$(curl -H "Authorization: token $TOKEN" \ + -H "Accept: application/vnd.github.v3+json" \ + "https://api.github.com/repos/$REPO/commits?sha=main" \ + -s | jq -r '.[] | "- \(.commit.message) (\(.commit.author.name))"' | head -n 10) + fi + + if [[ $COMMITS =~ [^[:space:]] ]]; then + echo "Updating Changelog for $new_version with list of commits:" + echo "$COMMITS" + description+="$COMMITS\n\n" + else + echo "Updating Changelog for $new_version wihtout any description." + fi + + awk -v line=2 -v text="$description" '{ + print $0; + if (NR == line) { + print text; + } + }' $changelog_path > $changelog_path.tmp + mv $changelog_path.tmp $changelog_path + + exit 0 +fi diff --git a/.github/actions/build-frontend/action.yml b/.github/actions/build-frontend/action.yml new file mode 100644 index 00000000..2dcd08c5 --- /dev/null +++ b/.github/actions/build-frontend/action.yml @@ -0,0 +1,36 @@ +name: 'Build Frontend' +inputs: + legacy-peer-deps: + description: 'Install node dependencies with legacy peer dependencies' + required: true + default: 'false' + +runs: + using: 'composite' + steps: + - name: Install node + uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Install node dependencies (legacy) + if: inputs.legacy-peer-deps == 'true' + shell: bash + run: npm install --frozen-lockfile --legacy-peer-deps + + - name: Install node dependencies + if: inputs.legacy-peer-deps != 'true' + shell: bash + run: npm install --frozen-lockfile + + - name: Frontend ESLint + shell: bash + run: npm run lint --verbose + + - name: Frontend Typescript Build + shell: bash + run: npm run build:typescript -- --verbose + + - name: Frontend Build + shell: bash + run: npm run build diff --git a/.github/actions/build-theme/action.yml b/.github/actions/build-theme/action.yml index db425aaf..3b7c54d2 100644 --- a/.github/actions/build-theme/action.yml +++ b/.github/actions/build-theme/action.yml @@ -1,13 +1,33 @@ name: 'Build WP theme' +inputs: + WORDPRESS_URL: + description: 'WordPress URL' + required: true + NEXT_URL: + description: 'Next URL' + required: true + legacy-peer-deps: + description: 'Install node dependencies with legacy peer dependencies' + required: true + default: 'false' + runs: using: 'composite' steps: - name: Install node uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 + + - name: Install node dependencies (legacy) + if: inputs.legacy-peer-deps == 'true' + shell: bash + run: | + npm install --frozen-lockfile --legacy-peer-deps + npm --prefix ./wordpress install --frozen-lockfile --legacy-peer-deps - name: Install node dependencies + if: inputs.legacy-peer-deps != 'true' shell: bash run: | npm install --frozen-lockfile @@ -15,13 +35,9 @@ runs: - name: Build assets shell: bash - run: npm run build + run: WORDPRESS_URL=${{ inputs.WORDPRESS_URL }} NEXT_URL=${{ inputs.NEXT_URL }} npm run build working-directory: ./wordpress - # - name: Execute script to update translation file - # shell: bash - # run: sh ./.github/actions/build-theme/update-wp-translation-files.sh - - name: Save built artifacts uses: actions/upload-artifact@v4 with: diff --git a/.github/actions/build-theme/update-wp-translation-files.sh b/.github/actions/build-theme/update-wp-translation-files.sh deleted file mode 100755 index c744eebd..00000000 --- a/.github/actions/build-theme/update-wp-translation-files.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/sh - -#=========================================== -# Update WP translation files script -#=========================================== -# -# Requirements: -# - [jq](https://stedolan.github.io/jq) -# -#=========================================== - -# vars -ASSETS_MANIFEST_FILE="./wordpress/theme/static/manifest.json" # file is automatically created inside the static folder when we build our theme assets -SUPT_TRANSLATION_FILE_FR=$(find ./wordpress/theme/languages -type f -name '*supt-fr_FR-*.json') # find file which contains supt-fr_FR in it (which is the FR of the JSON translation file) - -# Get data from Manifest file -ASSETS_MANIFEST_DATA=$(cat "$ASSETS_MANIFEST_FILE" | jq '."editor.js"'); - -# Remove 1st and last character of the string (which are " because $ASSETS_MANIFEST_DATA = '"editor.***.js"'') -ASSETS_MANIFEST_DATA_FORMATTED=${ASSETS_MANIFEST_DATA#'"'} -ASSETS_MANIFEST_DATA_FORMATTED=${ASSETS_MANIFEST_DATA_FORMATTED%'"'} - -# Search and replace -sed -i'.original' -E "s@editor.[a-zA-Z0-9]+\.js@$ASSETS_MANIFEST_DATA_FORMATTED@g" "$SUPT_TRANSLATION_FILE_FR" - -# Create MD5 hash from edit.****.js file like WP make-json does to match the servers language hash -MD5_NAME=$(echo -n static/$ASSETS_MANIFEST_DATA_FORMATTED | openssl md5) - -# Strip up to "=" sign -MD5_NAME_FORMATTED=${MD5_NAME#*= } - -# Rename file to match WP requested filename -mv $SUPT_TRANSLATION_FILE_FR ./wordpress/theme/languages/supt-fr_FR-$MD5_NAME_FORMATTED.json diff --git a/.github/actions/deploy-frontend/action.yml b/.github/actions/deploy-frontend/action.yml new file mode 100644 index 00000000..5c56ab0d --- /dev/null +++ b/.github/actions/deploy-frontend/action.yml @@ -0,0 +1,106 @@ +name: 'Deploy Next.js' +description: 'Deploy Next.js on any Server' +inputs: + NEXT_SSH_PRIVATE_KEY: + description: 'Private key of the SSH user to deploy' + required: true + NEXT_SSH_USER: + description: 'SSH user' + required: true + NEXT_SSH_HOST: + description: 'SSH Host (IP adress)' + required: true + NEXT_SSH_PORT: + description: 'SSH Port (default: 22)' + required: false + default: 22 + NEXT_SSH_PATH: + description: 'Next absolute path on remote server' + required: true + NEXT_ENV: + description: 'Next environment: local, development (default), staging or production' + required: false + default: 'development' + NEXT_URL: + description: 'Next URL' + required: true + WORDPRESS_URL: + description: 'WordPress URL' + required: true + legacy-peer-deps: + description: 'Install node dependencies with legacy peer dependencies' + required: true + default: 'false' + +runs: + using: 'composite' + steps: + - name: Install node + uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Install node dependencies (legacy) + if: inputs.legacy-peer-deps == 'true' + shell: bash + run: npm ci --legacy-peer-deps + + - name: Install node dependencies + if: inputs.legacy-peer-deps != 'true' + shell: bash + run: npm ci + + - name: Copy environment file + shell: bash + run: cp .env.next.example .env + + - name: Build Next + shell: bash + run: WORDPRESS_URL=${{ inputs.WORDPRESS_URL }} NEXT_URL=${{ inputs.NEXT_URL }} NODE_ENV=production npm run build + + - name: Deploy Next environment file (if does not exist) + uses: easingthemes/ssh-deploy@main + with: + SSH_PRIVATE_KEY: ${{ inputs.NEXT_SSH_PRIVATE_KEY }} + REMOTE_USER: ${{ inputs.NEXT_SSH_USER }} + REMOTE_HOST: ${{ inputs.NEXT_SSH_HOST }} + REMOTE_PORT: ${{ inputs.NEXT_SSH_PORT }} + ARGS: -aulrqtvz --ignore-existing + SOURCE: .env + TARGET: ${{ inputs.NEXT_SSH_PATH }}/.env + + - name: Deploy Next files + uses: easingthemes/ssh-deploy@main + with: + SSH_PRIVATE_KEY: ${{ inputs.NEXT_SSH_PRIVATE_KEY }} + REMOTE_USER: ${{ inputs.NEXT_SSH_USER }} + REMOTE_HOST: ${{ inputs.NEXT_SSH_HOST }} + REMOTE_PORT: ${{ inputs.NEXT_SSH_PORT }} + ARGS: -aulrqtvz + SOURCE: . + TARGET: ${{ inputs.NEXT_SSH_PATH }} + EXCLUDE: '.git*, .*ignore, .storybook, .vscode, docs, generators, wiki, .editorconfig, .env, .env.*.example, README.md, next-env.d.ts, vercel.json, wordpress' + + - name: Deploy WordPress files used by next + uses: easingthemes/ssh-deploy@main + with: + SSH_PRIVATE_KEY: ${{ inputs.NEXT_SSH_PRIVATE_KEY }} + REMOTE_USER: ${{ inputs.NEXT_SSH_USER }} + REMOTE_HOST: ${{ inputs.NEXT_SSH_HOST }} + REMOTE_PORT: ${{ inputs.NEXT_SSH_PORT }} + ARGS: -aulrqtvz --mkpath + SOURCE: './wordpress/theme/lib/editor/' + TARGET: ${{ inputs.NEXT_SSH_PATH }}/wordpress/theme/lib/editor/ + EXCLUDE: '*.php' + + - name: Deploy WordPress files used by next + uses: easingthemes/ssh-deploy@main + with: + SSH_PRIVATE_KEY: ${{ inputs.NEXT_SSH_PRIVATE_KEY }} + REMOTE_USER: ${{ inputs.NEXT_SSH_USER }} + REMOTE_HOST: ${{ inputs.NEXT_SSH_HOST }} + REMOTE_PORT: ${{ inputs.NEXT_SSH_PORT }} + ARGS: -aulrqtvz + SOURCE: './wordpress/package.json ./wordpress/package-lock.json' + TARGET: ${{ inputs.NEXT_SSH_PATH }}/wordpress/ + EXCLUDE: '' diff --git a/.github/actions/deploy-next/action.yml b/.github/actions/deploy-next/action.yml deleted file mode 100644 index 972d7045..00000000 --- a/.github/actions/deploy-next/action.yml +++ /dev/null @@ -1,80 +0,0 @@ -name: 'Deploy Next.js' -description: 'Deploy Next.js on any Server' -inputs: - SSH_PRIVATE_KEY: - description: 'Private key of the SSH user to deploy' - required: true - SSH_USER: - description: 'SSH user' - required: true - SSH_HOST: - description: 'SSH Host (IP adress)' - required: true - SSH_PORT: - description: 'SSH Port (default: 22)' - required: false - default: 22 - WORDPRESS_URL: - description: 'WordPress URL' - required: true - NEXT_PATH: - description: 'Next absolute path on remote server' - required: true - PM2_APP_NAME: - description: 'The name of the pm2 process to start' - required: true - PM2_APP_ENV: - description: "The pm2's env instance name. (Default: production)" - required: false - default: 'production' - -runs: - using: 'composite' - steps: - - name: Install node - uses: actions/setup-node@v4 - with: - node-version: 20 - - - name: Install node dependencies - shell: bash - run: npm ci - - - name: Pull .env file - uses: up9cloud/action-rsync@master - env: - MODE: PULL - HOST: ${{ inputs.SSH_HOST }} - USER: ${{ inputs.SSH_USER }} - PORT: ${{ inputs.SSH_PORT }} - KEY: ${{ inputs.SSH_PRIVATE_KEY }} - SOURCE: ${{ inputs.NEXT_PATH }}/.env - TARGET: ./ - - - name: Build Next - shell: bash - run: NODE_ENV=production npm run build - - - name: Deploy - uses: easingthemes/ssh-deploy@main - with: - SSH_PRIVATE_KEY: ${{ inputs.SSH_PRIVATE_KEY }} - REMOTE_USER: ${{ inputs.SSH_USER }} - REMOTE_HOST: ${{ inputs.SSH_HOST }} - REMOTE_PORT: ${{ inputs.SSH_PORT }} - ARGS: -aulrqtvz - SOURCE: . - TARGET: ${{ inputs.NEXT_PATH }}/public - EXCLUDE: '.git*, .*ignore, .storybook, .vscode, docs, generators, wiki, .editorconfig, .env.github.example, .env.local.example, README.md, next-env.d.ts, vercel.json, wordpress' - - - name: Release - uses: garygrossgarten/github-action-ssh@release - with: - username: ${{ inputs.SSH_USER }} - host: ${{ inputs.SSH_HOST }} - port: ${{ inputs.SSH_PORT }} - privateKey: ${{ inputs.SSH_PRIVATE_KEY }} - command: | - cd ${{ inputs.NEXT_PATH }}/public - pm2 restart ecosystem.config.js --only ${{ inputs.PM2_APP_NAME }} --env ${{ inputs.PM2_APP_ENV }} - pm2 save diff --git a/.github/actions/deploy-theme/action.yml b/.github/actions/deploy-theme/action.yml index 7ba16875..9319b1f2 100644 --- a/.github/actions/deploy-theme/action.yml +++ b/.github/actions/deploy-theme/action.yml @@ -1,26 +1,25 @@ name: 'Deploy WP Theme' inputs: - SSH_PRIVATE_KEY: + WORDPRESS_SSH_PRIVATE_KEY: description: 'Private key of the SSH user to deploy' required: true - REMOTE_USER: + WORDPRESS_SSH_USER: description: 'Remote server SSH user' required: true - REMOTE_HOST: + WORDPRESS_SSH_HOST: description: 'Remote server SSH Host (IP adress)' required: true - REMOTE_PORT: + WORDPRESS_SSH_PORT: description: 'Remote server SSH Port (default: 22)' required: false default: 22 - REMOTE_TARGET: + WORDPRESS_PATH: description: 'Path on the remote server. (default: /var/www/html)' required: false default: '/var/www/html' WORDPRESS_THEME_NAME: description: 'WordPress Theme name' - required: false - default: 'superstack' + required: true runs: using: 'composite' @@ -34,22 +33,22 @@ runs: - name: Deploy uses: easingthemes/ssh-deploy@main with: - SSH_PRIVATE_KEY: ${{ inputs.SSH_PRIVATE_KEY }} + SSH_PRIVATE_KEY: ${{ inputs.WORDPRESS_SSH_PRIVATE_KEY }} ARGS: '-raltzv --delete' - REMOTE_USER: ${{ inputs.REMOTE_USER }} - REMOTE_HOST: ${{ inputs.REMOTE_HOST }} - REMOTE_PORT: ${{ inputs.REMOTE_PORT }} + REMOTE_USER: ${{ inputs.WORDPRESS_SSH_USER }} + REMOTE_HOST: ${{ inputs.WORDPRESS_SSH_HOST }} + REMOTE_PORT: ${{ inputs.WORDPRESS_SSH_PORT }} SOURCE: './wordpress/theme/' - TARGET: '${{ inputs.REMOTE_TARGET }}/wp-content/themes/_new' + TARGET: '${{ inputs.WORDPRESS_PATH }}/wp-content/themes/_new' EXCLUDE: '.github, .gitignore, composer.json, composer.lock, node_modules, packages.json, webpack.config.js, package-lock.json' - name: Replace theme folder with newly uploaded uses: garygrossgarten/github-action-ssh@release with: - username: ${{ inputs.REMOTE_USER }} - host: ${{ inputs.REMOTE_HOST }} - port: ${{ inputs.REMOTE_PORT }} - privateKey: ${{ inputs.SSH_PRIVATE_KEY }} + username: ${{ inputs.WORDPRESS_SSH_USER }} + host: ${{ inputs.WORDPRESS_SSH_HOST }} + port: ${{ inputs.WORDPRESS_SSH_PORT }} + privateKey: ${{ inputs.WORDPRESS_SSH_PRIVATE_KEY }} command: | - [ -d "${{ inputs.REMOTE_TARGET }}/wp-content/themes/${{ inputs.WORDPRESS_THEME_NAME }}" ] && mv "${{ inputs.REMOTE_TARGET }}/wp-content/themes/${{ inputs.WORDPRESS_THEME_NAME }}" "${{ inputs.REMOTE_TARGET }}/wp-content/themes/_old" - mv "${{ inputs.REMOTE_TARGET }}/wp-content/themes/_new" "${{ inputs.REMOTE_TARGET }}/wp-content/themes/${{ inputs.WORDPRESS_THEME_NAME }}" && rm -rf "${{ inputs.REMOTE_TARGET }}/wp-content/themes/_old" + [ -d "${{ inputs.WORDPRESS_PATH }}/wp-content/themes/${{ inputs.WORDPRESS_THEME_NAME }}" ] && mv "${{ inputs.WORDPRESS_PATH }}/wp-content/themes/${{ inputs.WORDPRESS_THEME_NAME }}" "${{ inputs.WORDPRESS_PATH }}/wp-content/themes/_old" + mv "${{ inputs.WORDPRESS_PATH }}/wp-content/themes/_new" "${{ inputs.WORDPRESS_PATH }}/wp-content/themes/${{ inputs.WORDPRESS_THEME_NAME }}" && rm -rf "${{ inputs.WORDPRESS_PATH }}/wp-content/themes/_old" diff --git a/.github/actions/deploy-vercel/action.yml b/.github/actions/deploy-vercel/action.yml index d8910756..68f63937 100644 --- a/.github/actions/deploy-vercel/action.yml +++ b/.github/actions/deploy-vercel/action.yml @@ -31,6 +31,8 @@ inputs: runs: using: 'composite' steps: + - name: Install Vercel CLI + run: npm install -g vercel@latest - uses: BetaHuhn/deploy-to-vercel-action@v1.10.0 with: GITHUB_TOKEN: ${{ inputs.GITHUB_TOKEN }} diff --git a/.github/actions/provision-backend/action.yml b/.github/actions/provision-backend/action.yml new file mode 100644 index 00000000..c4feb8e7 --- /dev/null +++ b/.github/actions/provision-backend/action.yml @@ -0,0 +1,123 @@ +name: 'Deploy WP Theme' +inputs: + WORDPRESS_SSH_PRIVATE_KEY: + description: 'Private key of the SSH user to deploy' + required: true + WORDPRESS_SSH_USER: + description: 'Remote server SSH user name' + required: true + WORDPRESS_SSH_HOST: + description: 'Remote server SSH Host IP address' + required: true + WORDPRESS_SSH_PORT: + description: 'Remote server SSH Port (default: 22)' + required: false + default: 22 + WORDPRESS_PATH: + description: 'Path on the remote server (default: ~/admin)' + required: false + default: '~/admin' + WORDPRESS_THEME_NAME: + description: 'WordPress Theme name' + required: true + WORDPRESS_THEME_TITLE: + description: 'WordPress Theme title' + required: true + WORDPRESS_ADMIN_USER: + description: 'WordPress Admin user' + required: true + WORDPRESS_ADMIN_EMAIL: + description: 'WordPress Admin email' + required: true + WORDPRESS_ADMIN_PASSWORD: + description: 'WordPress Admin password' + required: true + NEXT_URL: + description: 'Next.js URL (no trailing slash)' + required: true + WORDPRESS_URL: + description: 'WordPress URL (no trailing slash)' + required: true + WORDPRESS_VERSION: + description: 'WordPress version' + required: true + WORDPRESS_LOCALE: + description: 'WordPress locale' + required: true + WORDPRESS_DB_HOST: + description: 'WordPress DB host' + required: true + WORDPRESS_DB_NAME: + description: 'WordPress DB name' + required: true + WORDPRESS_DB_USER: + description: 'WordPress DB user' + required: true + WORDPRESS_DB_PASSWORD: + description: 'WordPress DB password' + required: true + +runs: + using: 'composite' + steps: + - uses: actions/checkout@v4 + + - name: Deploy composer + uses: easingthemes/ssh-deploy@main + with: + SSH_PRIVATE_KEY: ${{ inputs.WORDPRESS_SSH_PRIVATE_KEY }} + ARGS: '-raultzv --delete' + REMOTE_USER: ${{ inputs.WORDPRESS_SSH_USER }} + REMOTE_HOST: ${{ inputs.WORDPRESS_SSH_HOST }} + REMOTE_PORT: ${{ inputs.WORDPRESS_SSH_PORT }} + SOURCE: './wordpress/composer.json ./wordpress/composer.lock' + TARGET: '${{ inputs.WORDPRESS_PATH }}/' + + - name: Deploy Scripts + uses: easingthemes/ssh-deploy@main + with: + SSH_PRIVATE_KEY: ${{ inputs.WORDPRESS_SSH_PRIVATE_KEY }} + ARGS: '-raltzv --delete' + REMOTE_USER: ${{ inputs.WORDPRESS_SSH_USER }} + REMOTE_HOST: ${{ inputs.WORDPRESS_SSH_HOST }} + REMOTE_PORT: ${{ inputs.WORDPRESS_SSH_PORT }} + SOURCE: './wordpress/scripts' + TARGET: '${{ inputs.WORDPRESS_PATH }}/wp-content/' + + - id: ssh + name: 'SSH Connection setup' + uses: kuuak/ssh-action@main + with: + SSH_USER: ${{ inputs.WORDPRESS_SSH_USER }} + SSH_HOST: ${{ inputs.WORDPRESS_SSH_HOST }} + SSH_PORT: ${{ inputs.WORDPRESS_SSH_PORT }} + SSH_KEY: ${{ inputs.WORDPRESS_SSH_PRIVATE_KEY }} + + - name: Require composer depedencies + shell: bash + run: ssh ${{ steps.ssh.outputs.SERVER }} "cd ${{ inputs.WORDPRESS_PATH }}; composer install --no-dev" + + - name: Execute provisiton script + shell: bash + run: | + ssh ${{ steps.ssh.outputs.SERVER }} \ + "cd ${{ inputs.WORDPRESS_PATH }}; \ + touch p.txt; \ + echo ${{ inputs.WORDPRESS_DB_PASSWORD }} > p.txt; \ + env \ + WORDPRESS_VERSION=\"${{ inputs.WORDPRESS_VERSION }}\" \ + WORDPRESS_LOCALE=\"${{ inputs.WORDPRESS_LOCALE }}\" \ + WORDPRESS_DB_HOST=\"${{ inputs.WORDPRESS_DB_HOST }}\" \ + WORDPRESS_DB_NAME=\"${{ inputs.WORDPRESS_DB_NAME }}\" \ + WORDPRESS_DB_USER=\"${{ inputs.WORDPRESS_DB_USER }}\" \ + WORDPRESS_DB_PASSWORD=\"${{ inputs.WORDPRESS_DB_PASSWORD }}\" \ + WORDPRESS_URL=\"${{ inputs.WORDPRESS_URL }}\" \ + WORDPRESS_THEME_NAME=\"${{ inputs.WORDPRESS_THEME_NAME }}\" \ + WORDPRESS_THEME_TITLE=\"${{ inputs.WORDPRESS_THEME_TITLE }}\" \ + WORDPRESS_ADMIN_USER=\"${{ inputs.WORDPRESS_ADMIN_USER }}\" \ + WORDPRESS_ADMIN_EMAIL=\"${{ inputs.WORDPRESS_ADMIN_EMAIL }}\" \ + WORDPRESS_ADMIN_PASSWORD=\"${{ inputs.WORDPRESS_ADMIN_PASSWORD }}\" \ + WORDPRESS_PATH=\"${{ inputs.WORDPRESS_PATH }}\" \ + WORDPRESS_VERSION=\"${{ inputs.WORDPRESS_VERSION }}\" \ + WORDPRESS_LOCALE=\"${{ inputs.WORDPRESS_LOCALE }}\" \ + sh ./wp-content/scripts/provision.sh" diff --git a/.github/actions/run-tests/action.yml b/.github/actions/run-tests/action.yml new file mode 100644 index 00000000..21a54edc --- /dev/null +++ b/.github/actions/run-tests/action.yml @@ -0,0 +1,119 @@ +name: 'Run tests' +description: 'Run all unit and end-to-end tests given environment variables' +inputs: + WORDPRESS_URL: + required: true + type: string + NEXT_URL: + required: true + type: string + WORDPRESS_ADMIN_USER: + required: true + type: string + WORDPRESS_ADMIN_PASSWORD: + required: true + type: string + VIDEO_RECORD: + required: false + type: boolean + default: false + CI: + required: false + type: boolean + default: true + +runs: + using: 'composite' + steps: + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: 'npm' + + - name: Install dependencies for jest + puppeteer + shell: bash + run: npm ci --legacy-peer-deps + + - name: Install Puppeteer dependencies + shell: bash + run: | + sudo apt-get install -y \ + libnss3-dev \ + libatk-bridge2.0-dev \ + libdrm-dev \ + libxkbcommon-dev \ + libgbm-dev \ + libasound2-dev \ + ffmpeg + + - name: Install Google Chrome + shell: bash + run: | + wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | sudo apt-key add - + sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list' + sudo apt-get install -y google-chrome-stable + + - name: Check Chrome installation + shell: bash + run: | + google-chrome --version + which google-chrome + + - name: prepare video logs folder + shell: bash + run: | + mkdir -p src/__tests__/video-logs/ + + - name: Run all tests + shell: bash + run: npm run test:all + env: + CI: ${{ inputs.CI }} + VIDEO_RECORD: ${{ inputs.VIDEO_RECORD }} + WORDPRESS_URL: ${{ inputs.WORDPRESS_URL }} + NEXT_URL: ${{ inputs.NEXT_URL }} + WORDPRESS_ADMIN_USER: ${{ inputs.WORDPRESS_ADMIN_USER }} + WORDPRESS_ADMIN_PASSWORD: ${{ inputs.WORDPRESS_ADMIN_PASSWORD }} + + - name: Upload test videos on failure + uses: actions/upload-artifact@v4 + if: failure() + with: + name: test-failure-videos + path: | + src/__tests__/video-logs/ + retention-days: 7 + + - name: Comment on failure with video info + if: failure() && github.event_name == 'pull_request' + uses: actions/github-script@v7 + with: + script: | + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: `❌ **End to end Tests Failed** + + To view the video recordings: + * Go to the "Summary" tab of this workflow to Download and extract the "test-failure-videos" artifact. + * Or find the link to the file in the action logs` + }) + + - name: List generated videos + if: always() + shell: bash + run: | + echo "Generated test videos:" + ls -la src/__tests__/video-logs/ || echo "No video-logs directory found" + + - name: Upload test results + uses: actions/upload-artifact@v4 + if: always() + with: + name: test-results + path: | + coverage/ + src/__tests__/video-logs/ + retention-days: 7 diff --git a/.github/actions/tests/simple-http-test.sh b/.github/actions/tests/simple-http-test.sh new file mode 100644 index 00000000..bd20d069 --- /dev/null +++ b/.github/actions/tests/simple-http-test.sh @@ -0,0 +1,38 @@ +#!/bin/sh + +mkdir -p ./http-test + +# Test WordPress +URL="http://localhost/wp-admin/" +echo "Testing WordPress admin: $URL" +response=$(curl -L --write-out "%{http_code}" -k --silent --output ./http-test/wp-admin "$URL") +if [ ! "$response" -eq 200 ]; then + echo "Warning: WordPress returned HTTP $response" + cat ./http-test/wp-admin +fi +echo "WordPress test passed (HTTP $response)" + +# Test Next.js again +# URL="http://localhost:3000/blog/hello-world/" +# CONTENT="Hello world!" +# echo "Testing Next.js page: $URL" + +# response=$(curl -L --write-out "%{http_code}" -k --silent --output ./http-test/hello-world "$URL") +# if [ ! "$response" -eq 200 ]; then +# echo "Warning: Next.js returned HTTP $response" +# cat ./http-test/hello-world +# echo "NEXT JS LOG" +# cat ./next.log +# fi + +# response_content=$(cat ./http-test/hello-world) +# if echo "$response_content" | grep -q "$CONTENT"; then +# echo "Success - Content check passed (HTTP $response)" +# exit 0 +# else +# echo "Error - Content check failed. Page does not contain '$CONTENT'" +# echo "Received content:" +# echo "$response_content" +# exit 1 +# fi + diff --git a/.github/workflows/deploy-development.yml b/.github/workflows/deploy-development.yml new file mode 100644 index 00000000..54c59b8e --- /dev/null +++ b/.github/workflows/deploy-development.yml @@ -0,0 +1,148 @@ +name: Deploy development branch +on: + push: + branches: + - 'development' + - 'develop' + workflow_dispatch: + +jobs: + # Run the test procedure to validate the code being pushed. + # Like development tests, Start the dockers and build. + # Finally, run the JEST Unit test script using `npm run test` + deploy_development: + name: Deploy in development environment + runs-on: ubuntu-latest + environment: development + strategy: + matrix: + environment: + - development + + steps: + - uses: actions/checkout@v4 + - name: Check github environment secrets and variables + run: | + echo "Checking secrets and variables for '${{ matrix.environment }}' environment..." + missing_vars=0 + for var in COMPOSER_GITHUB_TOKEN RELEASE_BELT_USER RELEASE_BELT_PASSWORD WORDPRESS_SSH_HOST WORDPRESS_PATH WORDPRESS_SSH_PORT WORDPRESS_SSH_USER WORDPRESS_SSH_PRIVATE_KEY WORDPRESS_URL WORDPRESS_VERSION WORDPRESS_LOCALE WORDPRESS_THEME_NAME WORDPRESS_THEME_TITLE WORDPRESS_ADMIN_USER WORDPRESS_ADMIN_PASSWORD WORDPRESS_ADMIN_EMAIL WORDPRESS_DB_HOST WORDPRESS_DB_NAME WORDPRESS_DB_USER WORDPRESS_DB_PASSWORD NEXT_SSH_HOST NEXT_SSH_PATH NEXT_SSH_PORT NEXT_SSH_USER NEXT_SSH_PRIVATE_KEY NEXT_URL; do + if [ -z "${!var}" ]; then + echo "$var is not set." + missing_vars=$((missing_vars+1)) + else + echo "$var is set to '${!var}'." + fi + done + + if [ $missing_vars -ne 0 ]; then + echo "inputs_checked=false" >> $GITHUB_OUTPUT + echo "Error: $missing_vars variables are missing." + exit 1 + else + echo "inputs_checked=true" >> $GITHUB_OUTPUT + fi + env: + COMPOSER_GITHUB_TOKEN: ${{ secrets.COMPOSER_GITHUB_TOKEN }} + RELEASE_BELT_USER: ${{ vars.RELEASE_BELT_USER }} + RELEASE_BELT_PASSWORD: ${{ secrets.RELEASE_BELT_PASSWORD }} + + WORDPRESS_SSH_HOST: ${{ vars.WORDPRESS_SSH_HOST }} + WORDPRESS_SSH_PORT: ${{ vars.WORDPRESS_SSH_PORT }} + WORDPRESS_SSH_USER: ${{ vars.WORDPRESS_SSH_USER }} + WORDPRESS_SSH_PRIVATE_KEY: ${{ secrets.WORDPRESS_SSH_PRIVATE_KEY }} + + WORDPRESS_PATH: ${{ vars.WORDPRESS_PATH }} + WORDPRESS_URL: ${{ vars.WORDPRESS_URL }} + WORDPRESS_VERSION: ${{ vars.WORDPRESS_VERSION }} + WORDPRESS_LOCALE: ${{ vars.WORDPRESS_LOCALE }} + WORDPRESS_THEME_NAME: ${{ vars.WORDPRESS_THEME_NAME }} + WORDPRESS_THEME_TITLE: ${{ vars.WORDPRESS_THEME_TITLE }} + + WORDPRESS_ADMIN_USER: ${{ vars.WORDPRESS_ADMIN_USER }} + WORDPRESS_ADMIN_PASSWORD: ${{ secrets.WORDPRESS_ADMIN_PASSWORD }} + WORDPRESS_ADMIN_EMAIL: ${{ vars.WORDPRESS_ADMIN_EMAIL }} + WORDPRESS_DB_HOST: ${{ vars.WORDPRESS_DB_HOST }} + WORDPRESS_DB_NAME: ${{ vars.WORDPRESS_DB_NAME }} + WORDPRESS_DB_USER: ${{ vars.WORDPRESS_DB_USER }} + WORDPRESS_DB_PASSWORD: ${{ secrets.WORDPRESS_DB_PASSWORD }} + + NEXT_URL: ${{ vars.NEXT_URL }} + + NEXT_SSH_HOST: ${{ vars.NEXT_SSH_HOST }} + NEXT_SSH_PATH: ${{ vars.NEXT_SSH_PATH }} + NEXT_SSH_PORT: ${{ vars.NEXT_SSH_PORT }} + NEXT_SSH_USER: ${{ vars.NEXT_SSH_USER }} + NEXT_SSH_PRIVATE_KEY: ${{ secrets.NEXT_SSH_PRIVATE_KEY }} + + - name: Build Theme + uses: ./.github/actions/build-theme + with: + WORDPRESS_URL: ${{ vars.WORDPRESS_URL }} + NEXT_URL: ${{ vars.NEXT_URL }} + # TODO: Remove this once the @wordpress packages are updated to support React 19 + legacy-peer-deps: true + + - name: Deploy Theme + uses: ./.github/actions/deploy-theme + with: + WORDPRESS_SSH_USER: ${{ vars.WORDPRESS_SSH_USER }} + WORDPRESS_SSH_HOST: ${{ vars.WORDPRESS_SSH_HOST }} + WORDPRESS_SSH_PORT: ${{ vars.WORDPRESS_SSH_PORT }} + WORDPRESS_SSH_PRIVATE_KEY: ${{ secrets.WORDPRESS_SSH_PRIVATE_KEY }} + WORDPRESS_PATH: ${{ vars.WORDPRESS_PATH }} + WORDPRESS_THEME_NAME: ${{ vars.WORDPRESS_THEME_NAME }} + + - name: Provision WordPress + uses: ./.github/actions/provision-backend + with: + WORDPRESS_SSH_USER: ${{ vars.WORDPRESS_SSH_USER }} + WORDPRESS_SSH_HOST: ${{ vars.WORDPRESS_SSH_HOST }} + WORDPRESS_SSH_PORT: ${{ vars.WORDPRESS_SSH_PORT }} + WORDPRESS_SSH_PRIVATE_KEY: ${{ secrets.WORDPRESS_SSH_PRIVATE_KEY }} + WORDPRESS_PATH: ${{ vars.WORDPRESS_PATH }} + WORDPRESS_THEME_NAME: ${{ vars.WORDPRESS_THEME_NAME }} + WORDPRESS_THEME_TITLE: ${{ vars.WORDPRESS_THEME_TITLE }} + WORDPRESS_ADMIN_USER: ${{ vars.WORDPRESS_ADMIN_USER }} + WORDPRESS_ADMIN_PASSWORD: ${{ secrets.WORDPRESS_ADMIN_PASSWORD }} + WORDPRESS_ADMIN_EMAIL: ${{ vars.WORDPRESS_ADMIN_EMAIL }} + WORDPRESS_URL: ${{ vars.WORDPRESS_URL }} + NEXT_URL: ${{ vars.NEXT_URL }} + WORDPRESS_VERSION: ${{ vars.WORDPRESS_VERSION }} + WORDPRESS_LOCALE: ${{ vars.WORDPRESS_LOCALE }} + WORDPRESS_DB_HOST: ${{ vars.WORDPRESS_DB_HOST }} + WORDPRESS_DB_NAME: ${{ vars.WORDPRESS_DB_NAME }} + WORDPRESS_DB_USER: ${{ vars.WORDPRESS_DB_USER }} + WORDPRESS_DB_PASSWORD: ${{ secrets.WORDPRESS_DB_PASSWORD }} + + - name: Deploy Frontend + uses: ./.github/actions/deploy-frontend + with: + NEXT_ENV: development + WORDPRESS_URL: ${{ vars.WORDPRESS_URL }} + NEXT_SSH_USER: ${{ vars.NEXT_SSH_USER }} + NEXT_SSH_HOST: ${{ vars.NEXT_SSH_HOST }} + NEXT_SSH_PORT: ${{ vars.NEXT_SSH_PORT }} + NEXT_SSH_PATH: ${{ vars.NEXT_SSH_PATH }} + NEXT_SSH_PRIVATE_KEY: ${{ secrets.NEXT_SSH_PRIVATE_KEY }} + NEXT_URL: ${{ vars.NEXT_URL }} + # TODO: Remove this once the @wordpress packages are updated to support React 19 + legacy-peer-deps: true + + # RUN THE run-tests-development.yml workflow + test_development: + name: Test development environment + needs: deploy_development + runs-on: ubuntu-latest + environment: development + + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Run tests in development environment + uses: ./.github/actions/run-tests + with: + WORDPRESS_URL: ${{ vars.WORDPRESS_URL }} + NEXT_URL: ${{ vars.NEXT_URL }} + WORDPRESS_ADMIN_USER: ${{ vars.WORDPRESS_ADMIN_USER }} + WORDPRESS_ADMIN_PASSWORD: ${{ secrets.WORDPRESS_ADMIN_PASSWORD }} diff --git a/.github/workflows/deploy-preview.yml b/.github/workflows/deploy-preview.yml deleted file mode 100644 index 2450fa27..00000000 --- a/.github/workflows/deploy-preview.yml +++ /dev/null @@ -1,103 +0,0 @@ -name: Deploy WP & Nextjs to Staging - builds and deploys -on: - # ######## Uncomment below block to enable automatic deployment - # # Only run workflow if push to branch - # push: - # branches: - # - '!production' - - # # Run workflow for pull requests as well - # pull_request: - # types: [opened, synchronize, reopened] - # ######## - - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - -jobs: - should_deploy_wp: - runs-on: ubuntu-latest - if: github.event_name != 'pull_request' && github.ref_name == 'main' # run only if not pull_request & on main branch - outputs: - inputs_checked: ${{ steps.check_secrets.outputs.inputs_checked }} - should_deploy: ${{ steps.changed_files_wp.outputs.any_changed }} - steps: - - id: check_secrets - run: | - missing_vars=0 - for var in STAGING_BE_SSH_USER STAGING_BE_SSH_HOST STAGING_BE_SSH_PORT SSH_PRIVATE_KEY STAGING_WORDPRESS_PATH STAGING_NEXT_URL WORDPRESS_THEME_NAME; do - if [ -z "${!var}" ]; then - echo "$var is not set." - missing_vars=$((missing_vars+1)) - else - echo "$var is set to '${!var}'." - fi - done - - if [ $missing_vars -ne 0 ]; then - echo "inputs_checked=false" >> $GITHUB_OUTPUT - echo "Error: $missing_vars variables are missing." - exit 1 - else - echo "inputs_checked=true" >> $GITHUB_OUTPUT - fi - # uses: svrooij/secret-gate-action@v1.1 - # with: - # inputsToCheck: 'STAGING_BE_SSH_USER,STAGING_BE_SSH_HOST,STAGING_BE_SSH_PORT,SSH_PRIVATE_KEY,STAGING_WORDPRESS_PATH,STAGING_NEXT_URL,WORDPRESS_THEME_NAME' - env: - STAGING_BE_SSH_USER: ${{ secrets.STAGING_BE_SSH_USER }} - STAGING_BE_SSH_HOST: ${{ secrets.STAGING_BE_SSH_HOST }} - STAGING_BE_SSH_PORT: ${{ secrets.STAGING_BE_SSH_PORT }} - SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} - STAGING_WORDPRESS_PATH: ${{ secrets.STAGING_WORDPRESS_PATH }} - STAGING_NEXT_URL: ${{ secrets.STAGING_NEXT_URL }} - WORDPRESS_THEME_NAME: ${{ secrets.WORDPRESS_THEME_NAME }} - - - uses: styfle/cancel-workflow-action@0.12.1 - with: - access_token: ${{ github.token }} - - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - id: changed_files_wp - if: steps.check_secrets.outputs.inputs_checked == 'true' - uses: tj-actions/changed-files@v44 - with: - since_last_remote_commit: 'true' - files: | - wordpress/** - - - name: Set output - if: steps.check_secrets.outputs.inputs_checked == 'true' - run: echo "should_deploy=${{ steps.changed_files_wp.outputs.any_changed }}" >> $GITHUB_OUTPUT - - deploy_wp: - needs: should_deploy_wp - if: needs.should_deploy_wp.outputs.should_deploy == 'true' - uses: ./.github/workflows/deploy-wp.yml - secrets: - SSH_USER: ${{ secrets.STAGING_BE_SSH_USER }} - SSH_HOST: ${{ secrets.STAGING_BE_SSH_HOST }} - SSH_PORT: ${{ secrets.STAGING_BE_SSH_PORT }} - SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} - WORDPRESS_PATH: ${{ secrets.STAGING_WORDPRESS_PATH }} - NEXT_URL: ${{ secrets.STAGING_NEXT_URL }} - WORDPRESS_THEME_NAME: ${{ secrets.WORDPRESS_THEME_NAME }} - - deploy_next: - runs-on: ubuntu-latest - needs: deploy_wp - if: ${{ always() }} # Always run the job regardless if build_deploy_wp was successful/skip or not - steps: - - uses: actions/checkout@v4 - - name: Deploy NextJS - uses: ./.github/actions/deploy-vercel - with: - GITHUB_TOKEN: ${{ github.token }} - VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} - VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} - VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} - GITHUB_DEPLOYMENT_ENV: Preview - PRODUCTION: false diff --git a/.github/workflows/deploy-production.yml b/.github/workflows/deploy-production.yml index 006a9a98..f40b5934 100644 --- a/.github/workflows/deploy-production.yml +++ b/.github/workflows/deploy-production.yml @@ -1,33 +1,27 @@ -name: Deploy WP & Nextjs to Production - builds and deploys +name: Deploy production branch on: - # ######## Uncomment below block to enable automatic deployment - # push: - # branches: - # - 'production' - # # # Only run workflow if push a server tag - # # tags: - # # - 'v*.*.*' - # ######## - - # Allows you to run this workflow manually from the Actions tab + push: + branches: + - 'main' + - 'master' workflow_dispatch: jobs: - should_deploy_wp: + deploy_backend: + name: Deploy backend in production environment runs-on: ubuntu-latest - if: github.event_name != 'pull_request' # If it's a pull request we don't want to deploy wordpress - outputs: - inputs_checked: ${{ steps.check_secrets.outputs.inputs_checked }} - should_deploy: ${{ steps.changed_files_wp.outputs.any_changed }} - steps: - - uses: styfle/cancel-workflow-action@0.12.1 - with: - access_token: ${{ github.token }} + strategy: + matrix: + environment: + - production - - id: check_secrets + steps: + - uses: actions/checkout@v4 + - name: Check github environment secrets and variables run: | + echo "Checking secrets and variables for '${{ matrix.environment }}' environment..." missing_vars=0 - for var in PRODUCTION_BE_SSH_USER PRODUCTION_BE_SSH_HOST PRODUCTION_BE_SSH_PORT SSH_PRIVATE_KEY PRODUCTION_WORDPRESS_PATH PRODUCTION_NEXT_URL WORDPRESS_THEME_NAME RELEASE_BELT_USER RELEASE_BELT_PWD; do + for var in COMPOSER_GITHUB_TOKEN RELEASE_BELT_USER RELEASE_BELT_PASSWORD WORDPRESS_SSH_HOST WORDPRESS_PATH WORDPRESS_SSH_PORT WORDPRESS_SSH_USER WORDPRESS_SSH_PRIVATE_KEY WORDPRESS_URL WORDPRESS_VERSION WORDPRESS_LOCALE WORDPRESS_THEME_NAME WORDPRESS_THEME_TITLE WORDPRESS_ADMIN_USER WORDPRESS_ADMIN_PASSWORD WORDPRESS_ADMIN_EMAIL WORDPRESS_DB_HOST WORDPRESS_DB_NAME WORDPRESS_DB_USER WORDPRESS_DB_PASSWORD NEXT_SSH_HOST NEXT_SSH_PATH NEXT_SSH_PORT NEXT_SSH_USER NEXT_SSH_PRIVATE_KEY NEXT_URL; do if [ -z "${!var}" ]; then echo "$var is not set." missing_vars=$((missing_vars+1)) @@ -43,57 +37,116 @@ jobs: else echo "inputs_checked=true" >> $GITHUB_OUTPUT fi - # uses: svrooij/secret-gate-action@v1.1 - # with: - # inputsToCheck: 'PRODUCTION_BE_SSH_USER,PRODUCTION_BE_SSH_HOST,PRODUCTION_BE_SSH_PORT,SSH_PRIVATE_KEY,PRODUCTION_WORDPRESS_PATH,PRODUCTION_NEXT_URL,WORDPRESS_THEME_NAME,RELEASE_BELT_USER,RELEASE_BELT_PWD' env: - PRODUCTION_BE_SSH_USER: ${{ secrets.PRODUCTION_BE_SSH_USER }} - PRODUCTION_BE_SSH_HOST: ${{ secrets.PRODUCTION_BE_SSH_HOST }} - PRODUCTION_BE_SSH_PORT: ${{ secrets.PRODUCTION_BE_SSH_PORT }} - SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} - PRODUCTION_WORDPRESS_PATH: ${{ secrets.PRODUCTION_WORDPRESS_PATH }} - PRODUCTION_NEXT_URL: ${{ secrets.PRODUCTION_NEXT_URL }} - WORDPRESS_THEME_NAME: ${{ secrets.WORDPRESS_THEME_NAME }} - RELEASE_BELT_USER: ${{ secrets.RELEASE_BELT_USER }} - RELEASE_BELT_PWD: ${{ secrets.RELEASE_BELT_PWD }} - - - uses: styfle/cancel-workflow-action@0.12.1 + COMPOSER_GITHUB_TOKEN: ${{ secrets.COMPOSER_GITHUB_TOKEN }} + RELEASE_BELT_USER: ${{ vars.RELEASE_BELT_USER }} + RELEASE_BELT_PASSWORD: ${{ secrets.RELEASE_BELT_PASSWORD }} + + WORDPRESS_SSH_HOST: ${{ vars.WORDPRESS_SSH_HOST }} + WORDPRESS_SSH_PORT: ${{ vars.WORDPRESS_SSH_PORT }} + WORDPRESS_SSH_USER: ${{ vars.WORDPRESS_SSH_USER }} + WORDPRESS_SSH_PRIVATE_KEY: ${{ secrets.WORDPRESS_SSH_PRIVATE_KEY }} + + WORDPRESS_PATH: ${{ vars.WORDPRESS_PATH }} + WORDPRESS_URL: ${{ vars.WORDPRESS_URL }} + WORDPRESS_VERSION: ${{ vars.WORDPRESS_VERSION }} + WORDPRESS_LOCALE: ${{ vars.WORDPRESS_LOCALE }} + WORDPRESS_THEME_NAME: ${{ vars.WORDPRESS_THEME_NAME }} + WORDPRESS_THEME_TITLE: ${{ vars.WORDPRESS_THEME_TITLE }} + + WORDPRESS_ADMIN_USER: ${{ vars.WORDPRESS_ADMIN_USER }} + WORDPRESS_ADMIN_PASSWORD: ${{ secrets.WORDPRESS_ADMIN_PASSWORD }} + WORDPRESS_ADMIN_EMAIL: ${{ vars.WORDPRESS_ADMIN_EMAIL }} + WORDPRESS_DB_HOST: ${{ vars.WORDPRESS_DB_HOST }} + WORDPRESS_DB_NAME: ${{ vars.WORDPRESS_DB_NAME }} + WORDPRESS_DB_USER: ${{ vars.WORDPRESS_DB_USER }} + WORDPRESS_DB_PASSWORD: ${{ secrets.WORDPRESS_DB_PASSWORD }} + + NEXT_URL: ${{ vars.NEXT_URL }} + + NEXT_SSH_HOST: ${{ vars.NEXT_SSH_HOST }} + NEXT_SSH_PATH: ${{ vars.NEXT_SSH_PATH }} + NEXT_SSH_PORT: ${{ vars.NEXT_SSH_PORT }} + NEXT_SSH_USER: ${{ vars.NEXT_SSH_USER }} + NEXT_SSH_PRIVATE_KEY: ${{ secrets.NEXT_SSH_PRIVATE_KEY }} + + - name: Build Theme + uses: ./.github/actions/build-theme with: - access_token: ${{ github.token }} + WORDPRESS_URL: ${{ vars.WORDPRESS_URL }} + NEXT_URL: ${{ vars.NEXT_URL }} - - uses: actions/checkout@v4 + - name: Deploy Theme + uses: ./.github/actions/deploy-theme with: - fetch-depth: 0 + WORDPRESS_SSH_USER: ${{ vars.WORDPRESS_SSH_USER }} + WORDPRESS_SSH_HOST: ${{ vars.WORDPRESS_SSH_HOST }} + WORDPRESS_SSH_PORT: ${{ vars.WORDPRESS_SSH_PORT }} + WORDPRESS_SSH_PRIVATE_KEY: ${{ secrets.WORDPRESS_SSH_PRIVATE_KEY }} + WORDPRESS_PATH: ${{ vars.WORDPRESS_PATH }} + WORDPRESS_THEME_NAME: ${{ vars.WORDPRESS_THEME_NAME }} - - id: changed_files_wp - if: steps.check_secrets.outputs.inputs_checked == 'true' - uses: tj-actions/changed-files@v44 + - name: Deploy Composer + uses: easingthemes/ssh-deploy@main with: - since_last_remote_commit: 'true' - files: | - wordpress/** - - - name: Set output - if: steps.check_secrets.outputs.inputs_checked == 'true' - run: echo "should_deploy=${{ steps.changed_files_wp.outputs.any_changed }}" >> $GITHUB_OUTPUT - - deploy_wp: - needs: should_deploy_wp - if: needs.should_deploy_wp.outputs.should_deploy == 'true' - uses: ./.github/workflows/deploy-wp.yml - secrets: - SSH_USER: ${{ secrets.PRODUCTION_BE_SSH_USER }} - SSH_HOST: ${{ secrets.PRODUCTION_BE_SSH_HOST }} - SSH_PORT: ${{ secrets.PRODUCTION_BE_SSH_PORT }} - SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} - WORDPRESS_PATH: ${{ secrets.PRODUCTION_WORDPRESS_PATH }} - NEXT_URL: ${{ secrets.PRODUCTION_NEXT_URL }} - WORDPRESS_THEME_NAME: ${{ secrets.WORDPRESS_THEME_NAME }} - - deploy_next: + SSH_PRIVATE_KEY: ${{ secrets.WORDPRESS_SSH_PRIVATE_KEY }} + ARGS: '-raultzv --delete' + REMOTE_USER: ${{ vars.WORDPRESS_SSH_USER }} + REMOTE_HOST: ${{ vars.WORDPRESS_SSH_HOST }} + REMOTE_PORT: ${{ vars.WORDPRESS_SSH_PORT }} + SOURCE: './wordpress/composer.json ./wordpress/composer.lock' + TARGET: ${{ vars.WORDPRESS_PATH }} + + - name: SSH Action + id: ssh + uses: kuuak/ssh-action@main + with: + SSH_USER: ${{ vars.WORDPRESS_SSH_USER }} + SSH_HOST: ${{ vars.WORDPRESS_SSH_HOST }} + SSH_PORT: ${{ vars.WORDPRESS_SSH_PORT }} + SSH_KEY: ${{ secrets.WORDPRESS_SSH_PRIVATE_KEY }} + + - name: Require composer depedencies + shell: bash + run: ssh ${{ steps.ssh.outputs.SERVER }} "cd ${{ vars.WORDPRESS_PATH }}; composer install --no-dev" + + - name: Execute provisiton script + shell: bash + run: | + ssh ${{ steps.ssh.outputs.SERVER }} \ + "cd ${{ vars.WORDPRESS_PATH }}; \ + env THEME_NAME=${{ vars.WORDPRESS_THEME_NAME }} \ + NEXT_URL=${{ vars.NEXT_URL }} \ + WORDPRESS_PATH=${{ vars.WORDPRESS_PATH }} \ + /bin/bash -s " < ./wordpress/scripts/provision.sh + + - name: Build Frontend + uses: ./.github/actions/build-frontend + with: + legacy-peer-deps: true + + - name: Deploy Frontend + uses: ./.github/actions/deploy-frontend + with: + NEXT_ENV: development + WORDPRESS_URL: ${{ vars.WORDPRESS_URL }} + NEXT_SSH_USER: ${{ vars.NEXT_SSH_USER }} + NEXT_SSH_HOST: ${{ vars.NEXT_SSH_HOST }} + NEXT_SSH_PORT: ${{ vars.NEXT_SSH_PORT }} + NEXT_SSH_PATH: ${{ vars.NEXT_SSH_PATH }} + NEXT_SSH_PRIVATE_KEY: ${{ secrets.NEXT_SSH_PRIVATE_KEY }} + NEXT_URL: ${{ vars.NEXT_URL }} + + deploy_frontend_using_vercel: + name: Deploy frontend to Vercel (production) runs-on: ubuntu-latest - needs: deploy_wp - if: ${{ always() }} # Always run the job regardless if build_deploy_wp was successful/skip or not + needs: deploy_backend + if: ${{ vars.VERCEL_PROJECT_ID != '' }} + strategy: + matrix: + environment: + - production + steps: - uses: actions/checkout@v4 - name: Deploy NextJS @@ -101,7 +154,30 @@ jobs: with: GITHUB_TOKEN: ${{ github.token }} VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} - VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} - VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} + VERCEL_ORG_ID: ${{ vars.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ vars.VERCEL_PROJECT_ID }} GITHUB_DEPLOYMENT_ENV: Production PRODUCTION: true + + deploy_frontend_using_ssh: + name: Deploy frontend using SSH (production) + runs-on: ubuntu-latest + needs: deploy_backend + if: ${{ vars.VERCEL_PROJECT_ID == '' }} + strategy: + matrix: + environment: + - production + + steps: + - name: Deploy Frontend + uses: ./.github/actions/deploy-frontend + with: + NEXT_ENV: development + WORDPRESS_URL: ${{ vars.WORDPRESS_URL }} + NEXT_SSH_USER: ${{ vars.NEXT_SSH_USER }} + NEXT_SSH_HOST: ${{ vars.NEXT_SSH_HOST }} + NEXT_SSH_PORT: ${{ vars.NEXT_SSH_PORT }} + NEXT_SSH_PATH: ${{ vars.NEXT_SSH_PATH }} + NEXT_SSH_PRIVATE_KEY: ${{ secrets.NEXT_SSH_PRIVATE_KEY }} + NEXT_URL: ${{ vars.NEXT_URL }} diff --git a/.github/workflows/deploy-staging.yml b/.github/workflows/deploy-staging.yml new file mode 100644 index 00000000..09ba88d0 --- /dev/null +++ b/.github/workflows/deploy-staging.yml @@ -0,0 +1,177 @@ +name: Deploy staging branch +on: + push: + branches: + - 'staging' + workflow_dispatch: + +jobs: + deploy_backend: + name: Deploy backend in staging environment + runs-on: ubuntu-latest + strategy: + matrix: + environment: + - staging + + steps: + - uses: actions/checkout@v4 + - name: Check github environment secrets and variables + run: | + echo "Checking secrets and variables for '${{ matrix.environment }}' environment..." + missing_vars=0 + for var in COMPOSER_GITHUB_TOKEN RELEASE_BELT_USER RELEASE_BELT_PASSWORD WORDPRESS_SSH_HOST WORDPRESS_PATH WORDPRESS_SSH_PORT WORDPRESS_SSH_USER WORDPRESS_SSH_PRIVATE_KEY WORDPRESS_URL WORDPRESS_VERSION WORDPRESS_LOCALE WORDPRESS_THEME_NAME WORDPRESS_THEME_TITLE WORDPRESS_ADMIN_USER WORDPRESS_ADMIN_PASSWORD WORDPRESS_ADMIN_EMAIL WORDPRESS_DB_HOST WORDPRESS_DB_NAME WORDPRESS_DB_USER WORDPRESS_DB_PASSWORD NEXT_SSH_HOST NEXT_SSH_PATH NEXT_SSH_PORT NEXT_SSH_USER NEXT_SSH_PRIVATE_KEY NEXT_URL; do + if [ -z "${!var}" ]; then + echo "$var is not set." + missing_vars=$((missing_vars+1)) + else + echo "$var is set to '${!var}'." + fi + done + + if [ $missing_vars -ne 0 ]; then + echo "inputs_checked=false" >> $GITHUB_OUTPUT + echo "Error: $missing_vars variables are missing." + exit 1 + else + echo "inputs_checked=true" >> $GITHUB_OUTPUT + fi + env: + COMPOSER_GITHUB_TOKEN: ${{ secrets.COMPOSER_GITHUB_TOKEN }} + RELEASE_BELT_USER: ${{ vars.RELEASE_BELT_USER }} + RELEASE_BELT_PASSWORD: ${{ secrets.RELEASE_BELT_PASSWORD }} + + WORDPRESS_SSH_HOST: ${{ vars.WORDPRESS_SSH_HOST }} + WORDPRESS_SSH_PORT: ${{ vars.WORDPRESS_SSH_PORT }} + WORDPRESS_SSH_USER: ${{ vars.WORDPRESS_SSH_USER }} + WORDPRESS_SSH_PRIVATE_KEY: ${{ secrets.WORDPRESS_SSH_PRIVATE_KEY }} + + WORDPRESS_PATH: ${{ vars.WORDPRESS_PATH }} + WORDPRESS_URL: ${{ vars.WORDPRESS_URL }} + WORDPRESS_VERSION: ${{ vars.WORDPRESS_VERSION }} + WORDPRESS_LOCALE: ${{ vars.WORDPRESS_LOCALE }} + WORDPRESS_THEME_NAME: ${{ vars.WORDPRESS_THEME_NAME }} + WORDPRESS_THEME_TITLE: ${{ vars.WORDPRESS_THEME_TITLE }} + + WORDPRESS_ADMIN_USER: ${{ vars.WORDPRESS_ADMIN_USER }} + WORDPRESS_ADMIN_PASSWORD: ${{ secrets.WORDPRESS_ADMIN_PASSWORD }} + WORDPRESS_ADMIN_EMAIL: ${{ vars.WORDPRESS_ADMIN_EMAIL }} + WORDPRESS_DB_HOST: ${{ vars.WORDPRESS_DB_HOST }} + WORDPRESS_DB_NAME: ${{ vars.WORDPRESS_DB_NAME }} + WORDPRESS_DB_USER: ${{ vars.WORDPRESS_DB_USER }} + WORDPRESS_DB_PASSWORD: ${{ secrets.WORDPRESS_DB_PASSWORD }} + + NEXT_URL: ${{ vars.NEXT_URL }} + + NEXT_SSH_HOST: ${{ vars.NEXT_SSH_HOST }} + NEXT_SSH_PATH: ${{ vars.NEXT_SSH_PATH }} + NEXT_SSH_PORT: ${{ vars.NEXT_SSH_PORT }} + NEXT_SSH_USER: ${{ vars.NEXT_SSH_USER }} + NEXT_SSH_PRIVATE_KEY: ${{ secrets.NEXT_SSH_PRIVATE_KEY }} + + - name: Build Theme + uses: ./.github/actions/build-theme + with: + WORDPRESS_URL: ${{ vars.WORDPRESS_URL }} + NEXT_URL: ${{ vars.NEXT_URL }} + + - name: Deploy Theme + uses: ./.github/actions/deploy-theme + with: + WORDPRESS_SSH_USER: ${{ vars.WORDPRESS_SSH_USER }} + WORDPRESS_SSH_HOST: ${{ vars.WORDPRESS_SSH_HOST }} + WORDPRESS_SSH_PORT: ${{ vars.WORDPRESS_SSH_PORT }} + WORDPRESS_SSH_PRIVATE_KEY: ${{ secrets.WORDPRESS_SSH_PRIVATE_KEY }} + WORDPRESS_PATH: ${{ vars.WORDPRESS_PATH }} + WORDPRESS_THEME_NAME: ${{ vars.WORDPRESS_THEME_NAME }} + + - name: Deploy Composer + uses: easingthemes/ssh-deploy@main + with: + SSH_PRIVATE_KEY: ${{ secrets.WORDPRESS_SSH_PRIVATE_KEY }} + ARGS: '-raultzv --delete' + REMOTE_USER: ${{ vars.WORDPRESS_SSH_USER }} + REMOTE_HOST: ${{ vars.WORDPRESS_SSH_HOST }} + REMOTE_PORT: ${{ vars.WORDPRESS_SSH_PORT }} + SOURCE: './wordpress/composer.json ./wordpress/composer.lock' + TARGET: ${{ vars.WORDPRESS_PATH }} + + - name: SSH Action + id: ssh + uses: kuuak/ssh-action@main + with: + SSH_USER: ${{ vars.WORDPRESS_SSH_USER }} + SSH_HOST: ${{ vars.WORDPRESS_SSH_HOST }} + SSH_PORT: ${{ vars.WORDPRESS_SSH_PORT }} + SSH_KEY: ${{ secrets.WORDPRESS_SSH_PRIVATE_KEY }} + + - name: Require composer depedencies + shell: bash + run: ssh ${{ steps.ssh.outputs.SERVER }} "cd ${{ vars.WORDPRESS_PATH }}; composer install --no-dev" + + - name: Execute provisiton script + shell: bash + run: | + ssh ${{ steps.ssh.outputs.SERVER }} \ + "cd ${{ vars.WORDPRESS_PATH }}; \ + env THEME_NAME=${{ vars.WORDPRESS_THEME_NAME }} \ + NEXT_URL=${{ vars.NEXT_URL }} \ + WORDPRESS_PATH=${{ vars.WORDPRESS_PATH }} \ + /bin/bash -s " < ./wordpress/scripts/provision.sh + + - name: Deploy Frontend + uses: ./.github/actions/deploy-frontend + with: + NEXT_ENV: development + WORDPRESS_URL: ${{ vars.WORDPRESS_URL }} + NEXT_SSH_USER: ${{ vars.NEXT_SSH_USER }} + NEXT_SSH_HOST: ${{ vars.NEXT_SSH_HOST }} + NEXT_SSH_PORT: ${{ vars.NEXT_SSH_PORT }} + NEXT_SSH_PATH: ${{ vars.NEXT_SSH_PATH }} + NEXT_SSH_PRIVATE_KEY: ${{ secrets.NEXT_SSH_PRIVATE_KEY }} + NEXT_URL: ${{ vars.NEXT_URL }} + + deploy_frontend_using_vercel: + name: Deploy frontend to Vercel (staging) + runs-on: ubuntu-latest + needs: deploy_backend + if: ${{ vars.VERCEL_PROJECT_ID != '' }} + strategy: + matrix: + environment: + - staging + + steps: + - uses: actions/checkout@v4 + - name: Deploy NextJS + uses: ./.github/actions/deploy-vercel + with: + GITHUB_TOKEN: ${{ github.token }} + VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} + VERCEL_ORG_ID: ${{ vars.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ vars.VERCEL_PROJECT_ID }} + GITHUB_DEPLOYMENT_ENV: Production + PRODUCTION: true + + deploy_frontend_using_ssh: + name: Deploy frontend using SSH (staging) + runs-on: ubuntu-latest + needs: deploy_backend + if: ${{ vars.VERCEL_PROJECT_ID == '' }} + strategy: + matrix: + environment: + - staging + + steps: + - name: Deploy Frontend + uses: ./.github/actions/deploy-frontend + with: + NEXT_ENV: development + WORDPRESS_URL: ${{ vars.WORDPRESS_URL }} + NEXT_SSH_USER: ${{ vars.NEXT_SSH_USER }} + NEXT_SSH_HOST: ${{ vars.NEXT_SSH_HOST }} + NEXT_SSH_PORT: ${{ vars.NEXT_SSH_PORT }} + NEXT_SSH_PATH: ${{ vars.NEXT_SSH_PATH }} + NEXT_SSH_PRIVATE_KEY: ${{ secrets.NEXT_SSH_PRIVATE_KEY }} + NEXT_URL: ${{ vars.NEXT_URL }} diff --git a/.github/workflows/deploy-wp.yml b/.github/workflows/deploy-wp.yml deleted file mode 100644 index ab4914a4..00000000 --- a/.github/workflows/deploy-wp.yml +++ /dev/null @@ -1,80 +0,0 @@ -name: Deploy WordPress - theme and plugins -on: - # Allows to be called by another workflow - workflow_call: - secrets: - SSH_USER: - required: true - description: 'SSH user.' - SSH_HOST: - required: true - description: 'SSH Host. (IP adress)' - SSH_PORT: - description: 'SSH Port.' - required: true - SSH_PRIVATE_KEY: - required: true - description: 'Private key of the SSH user to deploy.' - NEXT_URL: - required: true - description: 'Next URL.' - WORDPRESS_PATH: - required: true - description: 'WordPress absolute path on remote server.' - WORDPRESS_THEME_NAME: - description: 'WordPress folder theme name.' - required: true - -jobs: - theme: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: ./.github/actions/build-theme - - uses: ./.github/actions/deploy-theme - with: - SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} - WORDPRESS_THEME_NAME: ${{ secrets.WORDPRESS_THEME_NAME }} - REMOTE_USER: ${{ secrets.SSH_USER }} - REMOTE_HOST: ${{ secrets.SSH_HOST }} - REMOTE_PORT: ${{ secrets.SSH_PORT }} - REMOTE_TARGET: ${{ secrets.WORDPRESS_PATH }} - - provision: - runs-on: ubuntu-latest - needs: [theme] - steps: - - uses: actions/checkout@v4 - - - name: deploy composer - uses: easingthemes/ssh-deploy@main - with: - SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} - ARGS: '-raultzv --delete' - REMOTE_USER: ${{ secrets.SSH_USER }} - REMOTE_HOST: ${{ secrets.SSH_HOST }} - REMOTE_PORT: ${{ secrets.SSH_PORT }} - SOURCE: './wordpress/composer.json ./wordpress/composer.lock' - TARGET: ${{ secrets.WORDPRESS_PATH }} - - - id: ssh - uses: kuuak/ssh-action@main - with: - SSH_USER: ${{ secrets.SSH_USER }} - SSH_HOST: ${{ secrets.SSH_HOST }} - SSH_PORT: ${{ secrets.SSH_PORT }} - SSH_KEY: ${{ secrets.SSH_PRIVATE_KEY }} - - - name: Require composer depedencies - shell: bash - run: ssh ${{ steps.ssh.outputs.SERVER }} "cd ${{ secrets.WORDPRESS_PATH }}; composer install --no-dev" - - - name: Execute provisiton script - shell: bash - run: | - ssh ${{ steps.ssh.outputs.SERVER }} \ - "cd ${{ secrets.WORDPRESS_PATH }}; \ - env THEME_NAME=${{ secrets.WORDPRESS_THEME_NAME }} \ - NEXT_URL=${{ secrets.NEXT_URL }} \ - WORDPRESS_PATH=${{ secrets.WORDPRESS_PATH }} \ - /bin/bash -s " < ./wordpress/scripts/provision.sh diff --git a/.github/workflows/release-main.yml b/.github/workflows/release-main.yml new file mode 100644 index 00000000..e4c87961 --- /dev/null +++ b/.github/workflows/release-main.yml @@ -0,0 +1,61 @@ +name: Release a new version +on: + push: + branches: + - 'main' + paths-ignore: + - 'composer.json' + - 'package.json' + - '*.md' + +jobs: + release_new_version: + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Getting last version from Next NPM + run: echo "LAST_VERSION=$(./.github/actions/automated/increment-semver.sh ./package.json self --out)" >> $GITHUB_ENV + + - name: Getting new version from Changelog + run: echo "LOG_VERSION=$(./.github/actions/automated/check-changelog.sh ./CHANGELOG.md ${{ env.LAST_VERSION }} --version)" >> $GITHUB_ENV + + - name: Getting potential version from Next NPM + run: echo "NEW_VERSION=$(./.github/actions/automated/increment-semver.sh ./package.json ${{ env.LOG_VERSION }} --out)" >> $GITHUB_ENV + + - name: Update project version in Next NPM + run: bash ./.github/actions/automated/increment-semver.sh "./package.json" ${{ env.NEW_VERSION }} + + - name: Update project version in WP Composer + run: bash ./.github/actions/automated/increment-semver.sh "./wordpress/composer.json" ${{ env.NEW_VERSION }} + + - name: Update project version in WP NPM + run: bash ./.github/actions/automated/increment-semver.sh "./wordpress/package.json" ${{ env.NEW_VERSION }} + + - name: Append Changelog if missing + run: bash ./.github/actions/automated/update-changelog.sh "${{ github.repository }}" "${{ secrets.GITHUB_TOKEN }}" ./CHANGELOG.md ${{ env.LOG_VERSION }} ${{ env.NEW_VERSION }} + + - name: Set up Git + run: | + git config --global user.email "tech@superhuit.ch" + git config --global user.name "superhuit" + + - name: Commit changes + shell: bash + run: | + git add ./package.json + git add ./wordpress/composer.json + git add ./wordpress/package.json + git add ./CHANGELOG.md + git commit -m "ship: 🚀 upgrade to version ${{ env.NEW_VERSION }}" + git push origin main + + # - name: Write Draft release + # run: bash ./.github/actions/automated/git-release.sh "${{ github.repository }}" "${{ secrets.GITHUB_TOKEN }}" "${{ env.NEW_VERSION }}" "./CHANGELOG.md" --draft + + - name: Write production release + run: bash ./.github/actions/automated/git-release.sh "${{ github.repository }}" "${{ secrets.GITHUB_TOKEN }}" "${{ env.NEW_VERSION }}" "./CHANGELOG.md" + + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/run-tests-development.yml b/.github/workflows/run-tests-development.yml new file mode 100644 index 00000000..c8c29848 --- /dev/null +++ b/.github/workflows/run-tests-development.yml @@ -0,0 +1,24 @@ +name: Test development environment + +on: + workflow_dispatch: + +jobs: + test-development: + runs-on: ubuntu-latest + environment: development + # Only run this workflow on the develop branch + if: github.ref == 'refs/heads/develop' + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Run tests un the development environment + uses: ./.github/actions/run-tests + with: + CI: true + VIDEO_RECORD: true + WORDPRESS_URL: ${{ vars.WORDPRESS_URL }} + NEXT_URL: ${{ vars.NEXT_URL }} + WORDPRESS_ADMIN_USER: ${{ vars.WORDPRESS_ADMIN_USER }} + WORDPRESS_ADMIN_PASSWORD: ${{ secrets.WORDPRESS_ADMIN_PASSWORD }} diff --git a/.github/workflows/validate-pull-request.yml b/.github/workflows/validate-pull-request.yml new file mode 100644 index 00000000..420100b4 --- /dev/null +++ b/.github/workflows/validate-pull-request.yml @@ -0,0 +1,133 @@ +name: Test and Validate Pull Request +on: + pull_request: + types: [opened, reopened, synchronize] + branches: + - '*' # Trigger on pull requests targeting any branch + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +jobs: + # Pull requests made on the MAIN branch must contain a changelog description. + # The version and description written will be used to trigger new tags & releases. + check_changelog: + runs-on: ubuntu-latest + if: github.event.pull_request.base.ref == 'main' + steps: + - uses: actions/checkout@v4 + + - name: Getting version from Next NPM + run: echo "CURRENT_VERSION=$(./.github/actions/automated/increment-semver.sh ./package.json self --out)" >> $GITHUB_ENV + + - name: Check if changelog contains description + run: bash ./.github/actions/automated/check-changelog.sh "./CHANGELOG.md" ${{ env.CURRENT_VERSION }} + + # Run the test procedure to validate the code being pushed. + # Like development tests, Start the dockers and build. + # Finally, run the JEST Unit test script using `npm run test` + build_run_test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Check github environment secrets and variables + run: | + echo "Checking secrets and variables for the current environment... (${{ matrix.environment }})" + missing_vars=0 + for var in COMPOSER_GITHUB_TOKEN WORDPRESS_THEME_NAME RELEASE_BELT_USER RELEASE_BELT_PASSWORD; do + if [ -z "${!var}" ]; then + echo "$var is not set." + missing_vars=$((missing_vars+1)) + else + echo "$var is set to '${!var}'." + fi + done + + if [ $missing_vars -ne 0 ]; then + echo "inputs_checked=false" >> $GITHUB_OUTPUT + echo "Error: $missing_vars variables are missing." + exit 1 + else + echo "inputs_checked=true" >> $GITHUB_OUTPUT + fi + env: + WORDPRESS_THEME_NAME: ${{ vars.WORDPRESS_THEME_NAME }} + RELEASE_BELT_USER: ${{ vars.RELEASE_BELT_USER }} + RELEASE_BELT_PASSWORD: ${{ secrets.RELEASE_BELT_PASSWORD }} + COMPOSER_GITHUB_TOKEN: ${{ secrets.COMPOSER_GITHUB_TOKEN }} + + - name: Build WP Theme + uses: ./.github/actions/build-theme + with: + WORDPRESS_URL: ${{ vars.WORDPRESS_URL }} + NEXT_URL: ${{ vars.NEXT_URL }} + # TODO: Remove this once the @wordpress packages are updated to support React 19 + legacy-peer-deps: true + + - name: Start WP + shell: bash + run: | + cd wordpress + docker compose down -v + npm run start + env: + THEME_NAME: ${{ vars.WORDPRESS_THEME_NAME }} + RELEASE_BELT_USER: ${{ vars.RELEASE_BELT_USER }} + WORDPRESS_LOCALE: ${{ vars.WORDPRESS_LOCALE }} + RELEASE_BELT_PASSWORD: ${{ secrets.RELEASE_BELT_PASSWORD }} + COMPOSER_GITHUB_TOKEN: ${{ secrets.COMPOSER_GITHUB_TOKEN }} + + - name: Wait for WordPress + shell: bash + run: | + echo "Waiting for WordPress to be ready..." + timeout=300 + while ! curl -s http://localhost/wp-admin/ > /dev/null; do + if [ $timeout -le 0 ]; then + echo "Timeout waiting for WordPress" + exit 1 + fi + echo "Waiting... ($timeout seconds remaining)" + sleep 5 + timeout=$((timeout-5)) + done + + - name: Copy environment file + run: cp .env.next.example .env.local + + - name: Build Next Frontend + uses: ./.github/actions/build-frontend + with: + # TODO: Remove this once the @wordpress packages are updated to support React 19 + legacy-peer-deps: true + + - name: Start Next as a thread + shell: bash + run: | + nohup npm run start -- -p 3000 > next.log 2>&1 & + echo "Waiting for Next.js to be ready..." + timeout=20 + while ! curl -s http://localhost:3000/ > /dev/null; do + if [ $timeout -le 0 ]; then + echo "Timeout waiting for Next.js. Log content:" + cat next.log + exit 0 + fi + echo "Waiting... ($timeout seconds remaining)" + sleep 5 + timeout=$((timeout-5)) + done + + - name: Simple HTTP Test + shell: bash + run: sh ./.github/actions/tests/simple-http-test.sh + + - name: Run tests in local environment + uses: ./.github/actions/run-tests + with: + CI: true + VIDEO_RECORD: true + WORDPRESS_URL: http://localhost + NEXT_URL: http://localhost:3000 + WORDPRESS_ADMIN_USER: 'superstack' + WORDPRESS_ADMIN_PASSWORD: 'stacksuper' diff --git a/.gitignore b/.gitignore index 666285bf..b5933f90 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,9 @@ node_modules/ .yarn/install-state.gz # testing -/coverage +src/__tests__/coverage/ +src/__tests__/video-logs/ +.artifacts/ # next.js /.next/ @@ -35,4 +37,4 @@ yarn-error.log* # typescript *.tsbuildinfo -# next-env.d.ts +next-env.d.ts diff --git a/.nvmrc b/.nvmrc index 2edeafb0..2bd5a0a9 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -20 \ No newline at end of file +22 diff --git a/.vscode/launch.json b/.vscode/launch.json index 2af62abb..f471c256 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -19,7 +19,7 @@ "name": "Next.js: debug server-side", "type": "node-terminal", "request": "launch", - "command": "npm run dev" + "command": "nvm use && npm run dev" }, { "name": "Next.js: debug client-side", @@ -27,22 +27,26 @@ "request": "launch", "url": "http://localhost:3000" }, - { - "name": "Storybook: debug", - "type": "chrome", - "request": "launch", - "url": "http://localhost:4000" - }, { "name": "Next.js: debug full stack", "type": "node-terminal", "request": "launch", - "command": "npm run dev", + "command": "nvm use && npm run dev", "serverReadyAction": { "pattern": "started server on .+, url: (https?://.+)", "uriFormat": "%s", "action": "debugWithChrome" } + }, + { + "name": "Debug All Jest Tests", + "type": "node-terminal", + "request": "launch", + "command": "nvm use && node --inspect-brk ./node_modules/.bin/jest --runInBand --no-cache --no-coverage", + "cwd": "${workspaceFolder}", + "env": { + "NODE_ENV": "test" + } } ] } diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..88c88ce9 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,42 @@ +# Changelog + +## 1.2.0 - 2025-08-18: Update to WP 6.8 + Next 15 + React 19 + Node 22 + +This major update brings significant version upgrades across the entire stack, ensuring compatibility with the latest technologies while maintaining stability and performance. + +### 🚀 Major Version Updates + +- **WordPress 6.8**: Updated WordPress core and related dependencies ([c0cd14c](https://github.com/superhuit-agency/superstack_test/commit/c0cd14c)) +- **Node.js 22**: Upgraded to Node.js v22 with updated CI/CD configurations ([8af2282](https://github.com/superhuit-agency/superstack_test/commit/8af2282)) +- **Next.js 15 + React 19**: Updated to Next.js 15 and React 19 with all package dependencies ([62b7163](https://github.com/superhuit-agency/superstack_test/commit/62b7163)) + +### 🐛 Compatibility Fixes + +- Fixed patterns and child blocks in Gutenberg inserter after WordPress update ([a0f7f60](https://github.com/superhuit-agency/superstack_test/commit/a0f7f60)) +- Resolved WordPress 6.8 compatibility issues ([1f8637b](https://github.com/superhuit-agency/superstack_test/commit/1f8637b)) +- Fixed Next.js components rendering inside WordPress Editor ([0674b6a](https://github.com/superhuit-agency/superstack_test/commit/0674b6a)) +- Improved inner blocks and lists styling ([91fe946](https://github.com/superhuit-agency/superstack_test/commit/91fe946)) + +### ✨ Enhancements + +- Added cache option to fetchAPI parameters ([bba5069](https://github.com/superhuit-agency/superstack_test/commit/bba5069)) +- Enhanced Vercel deployment with CLI integration ([97dc5fa](https://github.com/superhuit-agency/superstack_test/commit/97dc5fa)) +- Improved block whitelisting functionality ([11c57f7](https://github.com/superhuit-agency/superstack_test/commit/11c57f7)) + +## 1.1.1 - 2025-08-18 + +- Added back the Vercel deployment github action. +- Reverted unused deprecated package. +- Fixed readme file + +## 1.1.0 - 2025-07-28: Automated tests with Jest + +Automated tests added using the `npm run test:all` command. Jest packages were added and configured. +Github workflows were changed to enable automatic testing upon pull requests and automatic versioning upon changes on the main branch. +A first set of unit tests was made on all the _atoms_ components. More details about testing in the [dedicated documentation](./docs/automation/tests.md). + +Environement variables updated to and splitted between variables and secrets for a better debug during deployment. + +## 1.0.0 - 2024-05-07 + +_Initial release._ diff --git a/README.md b/README.md index 9a2b04c1..2300d320 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ An opinionated boilerplate for decoupled (headless) websites that are both perfo | Name | Version | Download | | -------------- | ------- | ------------------------------------------------------- | | Nvm | >= 0.38 | [nvm](https://github.com/creationix/nvm) | -| Node | >= 20.x | [node](https://nodejs.org/) | +| Node | >= 22.x | [node](https://nodejs.org/) | | Docker | >= 23 | [docker](https://www.docker.com/products/docker-engine) | | Docker Compose | >= 1.29 | [docker-compose](https://docs.docker.com/compose/) | @@ -71,6 +71,7 @@ Open [http://localhost:4000](http://localhost:4000) with your browser to access ### Additional commands - `npm run generate:block` - create a new block (interactive prompt) +- `npm run test:all` - run all unit and end-to-end tests ## 🏗 Project setup @@ -98,6 +99,8 @@ Open [http://localhost:4000](http://localhost:4000) with your browser to access - [Blocks Whitelisting](./docs/features/blocks-whitelisting.md) - [Forms](./docs/features/forms.md) - [Multilang](./docs/features/multilang.md) +- [Testing](./docs/automation/tests.md) +- [Github Workflows](./docs/automation/github-workflows.md) ## 📚 Resources diff --git a/__mocks__/endtoend/@storybook/test.js b/__mocks__/endtoend/@storybook/test.js new file mode 100644 index 00000000..9cdd9cbe --- /dev/null +++ b/__mocks__/endtoend/@storybook/test.js @@ -0,0 +1,52 @@ +// Mock for @storybook/test to avoid browser dependencies in Node.js environment +// This provides Jest-compatible implementations for end-to-end tests + +module.exports = { + // Use Jest's expect which works in Node.js + expect: global.expect || require('@jest/globals').expect, + + // Mock other Storybook test utilities + within: (element) => ({ + getByText: jest.fn(), + getByRole: jest.fn(), + queryByText: jest.fn(), + queryByRole: jest.fn(), + }), + + userEvent: { + click: jest.fn(), + type: jest.fn(), + clear: jest.fn(), + setup: jest.fn(() => ({ + click: jest.fn(), + type: jest.fn(), + clear: jest.fn(), + })), + }, + + waitFor: jest.fn(async (callback) => { + if (typeof callback === 'function') { + return await callback(); + } + return Promise.resolve(); + }), + + screen: { + getByText: jest.fn(), + getByRole: jest.fn(), + queryByText: jest.fn(), + queryByRole: jest.fn(), + findByText: jest.fn(), + findByRole: jest.fn(), + }, + + fireEvent: { + click: jest.fn(), + change: jest.fn(), + submit: jest.fn(), + }, + + // Add other commonly used testing utilities + fn: jest.fn, + spyOn: jest.spyOn, +}; diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md index 21b50a6a..55f54806 100644 --- a/docs/TROUBLESHOOTING.md +++ b/docs/TROUBLESHOOTING.md @@ -25,4 +25,9 @@ This is a known issue and can be fixed by visiting the permalinks in the WordPre ### 2. Error: Unexpected token '<' (
Redirection, then click on the Start Setup and follow the steps). \ No newline at end of file +This issue is because of the redirection table not existing in the Database. To fix it, you need to setup the Redirection plugin in Wordpress (In Tools > Redirection, then click on the Start Setup and follow the steps). + +### 3. WP Core blocks don't display in the front + +This might be because of the WP GraphQL Gutenberg block registry. It may not be up to date with the correct blocks attributes. +On the WordPress Admin go to `GraphQL` > `GraphQL Gutenberg` and then click on the `update` button. diff --git a/docs/automation/github-workflows.md b/docs/automation/github-workflows.md new file mode 100644 index 00000000..95110b55 --- /dev/null +++ b/docs/automation/github-workflows.md @@ -0,0 +1,50 @@ +# Github Workflows and Actions + +## Existing workflows + +This project contains 6 workflows used to automate github deployments: + +1. **validate-pull-request.yml**: Runs on pull request creation/update to validate changes by: + + - Building the WordPress environment + - Starting WordPress and Next.js servers + - Running automated tests including end-to-end tests with video recording + - Ensuring code quality and functionality before merging + +2. **run-tests-development.yml**: Executes the test suite in the development environment: + + - Runs unit tests and integration tests + - Generates test coverage reports + - Can be configured to record test execution videos + +3. **run-tests-production.yml**: Similar to development tests but runs against production: + + - Validates functionality in production environment + - Ensures deployed code works as expected + - Generates test reports for production verification + +4. **deploy-development.yml**: Handles deployment to development environment: + + - Builds and deploys WordPress theme + - Updates Next.js frontend + - Runs post-deployment checks + +5. **deploy-staging.yml**: Manages staging environment deployments: + + - Similar to development deployment + - Additional verification steps for pre-production + - Staging-specific configuration + +6. **deploy-production.yml**: Controls production deployments: + - Strict validation checks + - Production build process + - Zero-downtime deployment + - Post-deployment health checks + +## Testing the workflows + +If you need to test some workflows locally, you can istall the `act`command (see documentation [here](https://nektosact.com/usage)) and run the following (for example the build_run_test action): + +```bash +act -j build_run_test --container-architecture linux/amd64 --secret-file .env.github.secrets.example --var-file .env.github.variables.example +``` diff --git a/docs/automation/tests.md b/docs/automation/tests.md new file mode 100644 index 00000000..c786474d --- /dev/null +++ b/docs/automation/tests.md @@ -0,0 +1,217 @@ +# How to run tests + +This project contains _unit tests_ and _end-to-end tests_. The Jest engine is used to execute the tests and follow code coverage. + +## Unit tests + +They use the react engine and can be run anytime using the following command: + +``` +npm run test:unit +``` + +This will execute all the tests located in the `src/__tests__/unit` folder. So far one test is available but covers all the components this boilerplate has to offer. + +Each component that should be tested should have a `test.tsx` file within its component folder, and that file should be referenced and exported by the `test.ts` files in the parent directories. + +### Component Test Implementation + +Unit tests in this project are tightly integrated with Storybook stories so each story automatically becomes a unit test and ensures consistency between visual testing, and unit testing. + +#### Test Structure + +Each component that needs testing follows this structure: + +```typescript +// Example: src/components/atoms/Button/test.tsx +import { expect } from '@storybook/test'; +import block from './block.json'; +import { Button } from './index'; +import * as stories from './Button.stories'; + +export const ButtonTests: ComponentTests = { + block: block, + component: Button, + tests: { + Primary: { + args: stories.Primary.args, + unitTest: async (component, container) => { + // Test implementation + expect(component).toBeInTheDocument(); + expect(component).toHaveTextContent('Button Primary'); + expect(component).toHaveClass('supt-button -primary'); + }, + }, + // ... more test cases + }, +}; +``` + +#### Storybook Integration + +The key innovation of this testing approach is the direct reuse of Storybook story arguments: + +1. **Shared Arguments**: Each test case uses `stories.StoryName.args` to ensure the same props are used in both Storybook and unit tests + +2. **Visual-Test Consistency**: What you see in Storybook is exactly what gets tested + +3. **Single Source of Truth**: Story arguments serve as the component's test data, but _when needed_ it is possible to make specific arguments in a unit tests without having a matching example in storybook. + +#### Test Execution Flow + +The test runner in `src/__tests__/unit/all-components.tsx` automatically: + +1. Imports all component tests via `@/components/test` +2. Iterates through each component's test cases +3. Renders each component with the corresponding story arguments +4. Executes the unit test function with the rendered component +5. Provides both the component DOM element and container for assertions + +## End-to-End Tests + +End-to-end tests verify the complete workflow from WordPress admin to frontend rendering. They can be run using: + +``` +npm run test:endtoend +``` + +These tests use Puppeteer to control a headless browser and perform integration testing across the entire application stack. + +### Test Architecture + +End-to-end tests reuse the same component test structure as unit tests, ensuring consistency across testing layers: + +1. **Component Reuse**: Tests import `* as AllComponentsTests from '@/components/test'` to access the same test cases +2. **Story Integration**: Uses `Stories as AllTestableComponents from '@/components/stories'` for Storybook integration +3. **WordPress Integration**: Creates actual WordPress posts/pages with component blocks +4. **Frontend Verification**: Navigates to the frontend to verify proper rendering + +### Test Flow + +The end-to-end testing process follows this workflow: + +1. **WordPress Admin Login**: Authenticates with WordPress admin panel +2. **Content Creation**: Creates a new post/page with all component blocks +3. **Block HTML Generation**: Converts component test arguments into WordPress block HTML +4. **Content Publishing**: Saves and publishes the content +5. **Frontend Navigation**: Visits the published page to verify rendering +6. **DOM Inspection**: Validates that components render correctly on the frontend + +```typescript +// Example: Block HTML generation from test arguments +for (const componentTests of Object.values(AllComponentsTests)) { + Object.values(componentTests.tests).forEach((testableStory) => { + allComponentsHTML += writeBlockHTML(blockClassName, testableStory.args); + }); +} +``` + +### Video Recording + +End-to-end tests support video recording for debugging and documentation purposes: + +- **Automatic Recording**: Each test suite records a video of the browser session +- **Debug Mode**: Use `VIDEO_RECORD=true npm run test:endtoend` to enable recording +- **Storage Location**: Videos are saved in `src/__tests__/video-logs/` + +In production, you can set this environment variable to true as well to enable the video capture. Refer to the github workflow located at `.github/workflows/run-tests-development.yml` to see more. + +## Coverage Guidelines + +Code coverage is automatically collected during unit test execution with the following configuration: + +### Coverage Settings + +- **Collection**: Enabled by default (`collectCoverage: true`) +- **Output Directory**: `src/__tests__/coverage/` +- **Reporters**: HTML, JSON, and text reports are generated +- **Threshold**: Currently no minimum threshold enforced + +### Coverage Best Practices + +1. **Component Coverage**: Aim for >80% coverage on component logic +2. **Critical Paths**: Ensure 100% coverage on form validation, data processing, and error handling +3. **Integration Points**: Focus on testing component interfaces and prop handling +4. **Accessibility**: Include coverage for ARIA attributes and keyboard navigation + +### Excluded Files + +The following are typically excluded from coverage: + +- Storybook configuration files +- Test utilities and mocks +- Build configuration files +- WordPress PHP files + +## Best Practices + +### Unit Testing + +1. **Story-Driven Testing**: Always create corresponding Storybook stories before writing tests +2. **Comprehensive Assertions**: Test multiple aspects of each component: + + - **General**: Presence in document (`toBeInTheDocument`) + - **Content**: Text content and structure + - **Display**: CSS classes and visibility + - **Accessibility**: ARIA roles, labels, and keyboard support + - **Behaviour**: If interactive, user events should be fired to the component to check their reaction. + +3. **Test Organization**: Follow the established pattern: + + ```typescript + unitTest: async (component, container) => { + // General tests + expect(component).toBeInTheDocument(); + + // Content tests + expect(component).toHaveTextContent('Expected Text'); + + // Display tests + expect(component).toBeVisible(); + expect(component).toHaveClass('expected-class'); + + // Accessibility tests + expect(component).toBeEnabled(); + expect(component).toHaveRole('button'); + + // Behaviour tests + act(() => { + fireEvent.click(component); + expect(component).toBeEnabled(); + }); + }; + ``` + +4. **Error Scenarios**: Include test cases for edge cases and error states +5. **Mock External Dependencies**: Use Jest mocks for external services and APIs + +### End-to-End Testing + +1. **Environment Setup**: Ensure WordPress and Next.js development servers are running +2. **Test Isolation**: Each test should be independent and not rely on previous test state +3. **Timeouts**: Use appropriate timeouts for WordPress admin operations (default: 120 seconds) +4. **Error Handling**: Include proper error handling and cleanup in test teardown +5. **Video Documentation**: Use video recording for complex user flows and debugging + +### General Guidelines + +1. **Test Naming**: Use descriptive test names that explain the expected behavior +2. **Documentation**: Keep test documentation up-to-date with code changes +3. **Performance**: Run tests with `--maxWorkers=1` for unit tests to avoid resource conflicts +4. **Continuous Integration**: End-to-end tests use `bail: 1` to fail fast in CI environments +5. **Debugging**: Use `node --inspect-brk` for debugging complex test scenarios + +### File Organization + +``` +src/__tests__/ +├── unit/ # Unit test execution +│ └── all-components.tsx # Test runner +├── endtoend/ # End-to-end tests +│ ├── admin_*.ts # WordPress admin tests +│ └── frontend_*.js # Frontend verification tests +├── utils/ # Test utilities +│ ├── admin-manipulation.ts +│ └── video-recorder.ts +└── coverage/ # Coverage reports (generated) +``` diff --git a/docs/create-blocks/create-blocks.md b/docs/create-blocks/create-blocks.md index 3272a1e7..abffb220 100644 --- a/docs/create-blocks/create-blocks.md +++ b/docs/create-blocks/create-blocks.md @@ -20,6 +20,7 @@ Every custom block you create will typically include the following components: - **`data.ts`**: [Optional] The data fetching logic for the block. (📚 [Read more about this file](fetch-data.md)) - **`edit.tsx`**: The WordPress editor component for block settings and attributes. (📚 [Read more about this file](#dive-into-edittsx)) - **`index.tsx`**: The React component rendered on the front end. +- **`test.tsx`**: The Unit test scripts. - **`typings.d.ts`**: The typescript typings for the block. - **CSS Files**: `styles.css` for front-end styling and `styles.edit.css` for editor-specific styling (if needed). - **Storybook Story File** - `**.stories.tsx`: Optional but recommended for testing the block's UI. @@ -44,6 +45,7 @@ Navigate to the newly created directory in your blocks folder and begin shaping - **`edit.tsx`**: Craft the Gutenberg editor experience for your block. - **`index.tsx`**: Implement the React component for front-end display. - **`typings.d.ts`**: Review and increment the typings for the block attributes and props. +- **`test.tsx`**: Add relevant tests to your block and its different stories. - `**.stories.tsx`: Optionally develop a story to prototype your block's UI on Storybook. ### Step 3: (Optional) Fetch datas @@ -92,6 +94,10 @@ export const ButtonBlock: WpBlockType = { > We've added a custom property `postTypes` for the blocks' settings to define on which WP Post Type it can be available.
📚 [Learn more on how we've handled blocks whitelisting in here.](../features/blocks-whitelisting.md) +### Unit tests + +Follow [this documentation](../automation/tests.md) to implement some unit tests to your components and ensure a good code coverage. + ## 🏷️ Typing `typings.d.ts` When you create a new block, a TypeScript typing file is automatically generated. This file provides a basic structure for your block's types, but it's important to note that you should modify and adapt these types according to the specific needs of your block. diff --git a/docs/setup/deployement.md b/docs/setup/deployement.md index ef1a468f..53b9361b 100644 --- a/docs/setup/deployement.md +++ b/docs/setup/deployement.md @@ -21,67 +21,45 @@ on: [...] ``` -## 🔐 Github Actions secrets +## 🔐 Github Actions variables and secrets -In order for the Github workflows to correctly execute, you need to configure [Actions secrets](https://docs.github.com/en/rest/actions/secrets). +In order for the Github workflows to correctly execute, you need to configure [Actions variables](https://docs.github.com/en/rest/actions/variables) and [Actions secrets](https://docs.github.com/en/rest/actions/secrets). You can for sure define them manually in the Github GUI, but the easiest way to do it is through the CLI. Here are the steps to achieve it: 1. Install [Github CLI](https://cli.github.com/) according to your system and preferences. (Homebrew is the easiest on Mac) 2. [Configure](https://cli.github.com/manual/#configuration) the CLI. -> `gh auth login` and follow the instructions -3. Copy `.env.github.example` file and edit the secrets. -> `cp .env.github.example .env.github`. ⚠️ Do not track this file in the repo as it is secrets -4. Push secrets to Github. -> `gh secret set --env-file .env.github` - -| Secret | Mandatory | Description | -| ------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| REVALIDATE_SECRET | True | Secret token only known by your Next.js & WP backend app. This secret will be used to prevent unauthorized access to the revalidation API Route. | -| WORDPRESS_THEME_NAME | True | Name of the WordPress theme on remote server | -| SSH_PRIVATE_KEY | True | Will be used to deploy to the remote server. We consider that for both Staging/preview & production servers the same key will be used. | -| VERCEL_TOKEN | False | Mandatory if deploying to Vercel. https://vercel.com/guides/how-do-i-use-a-vercel-api-access-token | -| VERCEL_ORG_ID | False | Mandatory if deploying to Vercel. Your "Org ID" is also called your user ID or team ID, and it's found at vercel.com/account > settings > general -- at the bottom | -| VERCEL_PROJECT_ID | False | Mandatory if deploying to Vercel. Your "Project ID" is found at vercel.com/dashboard > your project name > settings > general -- at the bottom | -| PM2_APP_NAME | False | Mandatory if deploying Next.js to your own server. | -| STAGING_BE_SSH_USER | True | Remote server SSH user. | -| STAGING_BE_SSH_HOST | True | Remote server SSH host IP address or domain name | -| STAGING_BE_SSH_PORT | True | Remote server SSH port number. Default 22. | -| STAGING_WORDPRESS_PATH | True | Absolute path to WP root directory. | -| STAGING_WORDPRESS_URL | True | Admin URL. | -| STAGING_NEXT_PATH | False | Mandatory if deploying Next.js to your own server. Absolute path to the parent folder of Next.js app. ⚠️ Parent folder because to reduce the downtime we deploy to `_new` folder, then switch with the `public` folder. | -| STAGING_NEXT_URL | False | Mandatory if deploying Next.js to your own server. | -| PRODUCTION_BE_SSH_USER | True | Remote server SSH user. | -| PRODUCTION_BE_SSH_HOST | True | Remote server SSH host IP address or domain name | -| PRODUCTION_BE_SSH_PORT | True | Remote server SSH port number. Default 22. | -| PRODUCTION_WORDPRESS_PATH | True | Absolute path to WP root directory. | -| PRODUCTION_WORDPRESS_URL | True | Admin URL. | -| PRODUCTION_NEXT_PATH | False | Mandatory if deploying Next.js to your own server. Absolute path to the parent folder of Next.js app. ⚠️ Parent folder because to reduce the downtime we deploy to `_new` folder, then swith with the `public` folder. | -| PRODUCTION_NEXT_URL | False | Mandatory if deploying Next.js to your own server. | - -> ℹ️ Deployments are done automatically through Github workflows. By default Next.js is deployed to Vercel, but there is a custom action `.github/actions/deploy-next` to deploy it to a custom server. - -## 🚀 Production deployments - -When we push to the `production` git branch, Next.js and WordPress are automatically deployed to production. - -``` -Next.js -> https://yourdomain.com -WordPress -> https://admin.yourdomain.com -``` - -See `.github/workflows/deploy-production.yml` for more details. - -## 👁 Preview deployments - -When we push to any branch, or for any Merge Request: - -- Next.js is deployed on a preview URL on Vercel -- WordPress is ignored if not `main` branch and Next will consume data from WordPress Staging - -``` -Next.js -> https://yoursite-git-yourbranch.vercel.app -WordPress -> https://admin-staging.yourdomain.com -``` - -## 🔨 Custom deployments - -Duplicate `.github/workflows/deploy-production.yml` file and edit the secrets to deploy WordPress and/or Next to a specific instance. - -Be sure to not forget to add the custom action secrets to Github. +3. Push variables to Github. -> `gh variable set --env-file .env.github.variables.example` +4. Push secrets to Github. -> `gh secret set --env-file .env.github.secrets.example` + +| Secret | Mandatory | Description | +| ------------------------- | --------- | --------------------------------------------------------- | +| COMPOSER_GITHUB_TOKEN | True | Required by Github to access your repository | +| NEXT_SSH_PRIVATE_KEY | True | Your private SSH key used to deploy the Next frontend app | +| RELEASE_BELT_PASSWORD | False | If you have a private source of plugins repository | +| WORDPRESS_DB_PASSWORD | True | The Wordpress database password | +| WORDPRESS_SSH_PRIVATE_KEY | True | Your private SSH key used to deploy the WP backend app | + +| Variable | Mandatory | Default value | Description | +| --------------------- | --------- | ------------------------- | -------------------------------------------------------------- | +| NEXT_SSH_HOST | True | | The Host name of your Next server, used to deploy via SSH | +| NEXT_SSH_PATH | True | | The Path at which the SSH deployment will be done | +| NEXT_SSH_PORT | True | 22 | The SSH port used for deployment | +| NEXT_SSH_USER | True | superstack | The SSH user used for deployment | +| NEXT_URL | True | | The URL of the Next Frontend website | +| RELEASE_BELT_USER | False | | If you have a private source of plugins repository | +| WORDPRESS_ADMIN_EMAIL | True | | Your default Wordpress admin email | +| WORDPRESS_ADMIN_USER | True | superstack | Your default Wordpress admin user name | +| WORDPRESS_DB_HOST | True | localhost | The Wordpress database host | +| WORDPRESS_DB_NAME | True | wordpress | The Wordpress database name | +| WORDPRESS_DB_USER | True | wordpress | The Wordpress database user | +| WORDPRESS_LOCALE | True | fr_FR | The Wordpress locale | +| WORDPRESS_PATH | True | | The path on the server where the Wordpress website is located | +| WORDPRESS_SSH_HOST | True | | The Host name of your Wordpress server, used to deploy via SSH | +| WORDPRESS_SSH_PORT | True | 22 | The SSH port used for deployment | +| WORDPRESS_SSH_USER | True | superstack | The SSH user used for deployment | +| WORDPRESS_THEME_NAME | True | superstack | Name of the WordPress theme on remote server | +| WORDPRESS_THEME_TITLE | True | "Superstack by Superhuit" | Title of the WordPress theme on remote server | +| WORDPRESS_URL | True | | The URL of your Wordpress backend website | +| WORDPRESS_VERSION | True | 6.5 | Your Wordpres version | + +> ℹ️ Deployments are done automatically through Github workflows using SSH. diff --git a/docs/setup/installation.md b/docs/setup/installation.md index 84061346..5af3806e 100644 --- a/docs/setup/installation.md +++ b/docs/setup/installation.md @@ -2,7 +2,7 @@ Follow these steps to install the stack for the 1st time: -- `cp .env.local.example .env.local` - make default environment variables available to Next.js, Storybook and WordPress +- `cp .env.next.example .env` - make default environment variables available to Next.js, Storybook and WordPress - `nvm use` - `npm install` - install packages @@ -11,7 +11,7 @@ Follow these steps to install the stack for the 1st time: - `npm --prefix ./wordpress run build` - `npm --prefix ./wordpress run start` -Open [http://localhost/wp-admin/](http://localhost/wp-admin/) on your browser to access to the admin. (login: **superstack**, password: **superstack**) +Open [http://localhost/wp-admin/](http://localhost/wp-admin/) on your browser to access to the admin. (login: **superstack**, password: **stacksuper**) ### Next.js diff --git a/generators/block-generator.js b/generators/block-generator.js index 43750faa..8cce39ef 100644 --- a/generators/block-generator.js +++ b/generators/block-generator.js @@ -76,6 +76,11 @@ module.exports = async function (plop) { value: 'supportsData', checked: true, }, + { + name: 'Unit Tests', + value: 'supportsTests', + checked: true, + }, ], }, { @@ -147,7 +152,7 @@ module.exports = async function (plop) { delete data.blockOptionals; delete data.blockAttributesTypes; - const { supportsStory, supportsData } = data; + const { supportsStory, supportsData, supportsTests } = data; data.unprefixedBlockName = data.blockName.replace( `${CONFIG.blockPrefix}/`, @@ -164,6 +169,7 @@ module.exports = async function (plop) { ignore: [ ...(!supportsStory ? ['**/*.stories.tsx.hbs'] : []), ...(!supportsData ? ['**/data.ts.hbs'] : []), + ...(!supportsTests ? ['**/test.tsx.hbs'] : []), ], }, }); @@ -194,6 +200,16 @@ module.exports = async function (plop) { }); } + // Export component in packages/blocks/components/{{blockType}}/test.ts + if (supportsTests) { + actions.push({ + type: 'modify', + path: `${CONFIG.paths.relativePath}/{{blockType}}/test.ts`, + pattern: /(\/\/ -- GENERATOR EXPORT SLOT --)/gi, + template: `export * from './{{ pascalCase blockTitle }}/test';\r\n$1`, + }); + } + // Import component in Blocks.tsx actions.push({ type: 'modify', diff --git a/generators/templates/block/test.tsx.hbs b/generators/templates/block/test.tsx.hbs new file mode 100644 index 00000000..471c9c52 --- /dev/null +++ b/generators/templates/block/test.tsx.hbs @@ -0,0 +1,39 @@ +import { expect } from '@storybook/test'; + +import block from './block.json'; +import { {{ pascalCase blockTitle }} } from './index'; +{{#if supportsStory}} +import * as stories from './{{ pascalCase blockTitle }}.stories'; +{{/if}} + +/** + * Unique name for the component tests. + * Returns an object from each storybook story with for each a unit test to run. + */ +export const {{ pascalCase blockTitle }}Tests: ComponentTests = { + block: block, + component: {{ pascalCase blockTitle }}, + tests: { + Default: { + {{#if supportsStory}} + args: stories.Default.args, + {{/if}} + {{#ifNot supportsStory}} + args: { + {{#each blockAttributes as |attribute|}} + {{attribute.name}}: '', + {{/each}} + }, + {{/ifNot}} + unitTest: async (component, container) => { + // General + expect(component).toBeInTheDocument(); + // Content + // Display + expect(component).toBeVisible(); + // Accessibility + expect(component).toBeEnabled(); + }, + }, + }, +}; diff --git a/generators/templates/lang-migration/multilang/layout.tsx.hbs b/generators/templates/lang-migration/multilang/layout.tsx.hbs index a1fa0c2e..c8b78744 100644 --- a/generators/templates/lang-migration/multilang/layout.tsx.hbs +++ b/generators/templates/lang-migration/multilang/layout.tsx.hbs @@ -13,13 +13,15 @@ export default async function Layout({ children, }: { children: React.ReactNode; - params: { lang: Locale }; + params: NextParams; }) { - const dictionary = await getDictionary(params.lang); + const { lang } = await params; + + const dictionary = await getDictionary(lang); const [navPromise, footerPromise] = await Promise.allSettled([ - mainNavData.getData({language: params.lang}), - footerData.getData({language: params.lang}), + mainNavData.getData({language: lang}), + footerData.getData({language: lang}), ]); const mainNavProps = @@ -28,7 +30,7 @@ export default async function Layout({ footerPromise.status === 'fulfilled' ? footerPromise.value : null; return ( - + {mainNavProps ? : null}
{children}
{footerProps ?