diff --git a/.github/actions/frontend/marketing/ui-tests/action.yml b/.github/actions/frontend/marketing/ui-tests/action.yml new file mode 100644 index 00000000..7288309e --- /dev/null +++ b/.github/actions/frontend/marketing/ui-tests/action.yml @@ -0,0 +1,91 @@ +name: "Run Marketing UI Tests" +description: "Runs the marketing UI tests" + +inputs: + CONTENTFUL_SPACE_ID: + description: "Contentful Space ID" + required: true + DRAFT_MODE_TOKEN: + description: "Marketing App Draft Mode Token" + required: true + APPLITOOLS_API_KEY: + description: "Applitools API Key" + required: true + APPLICATION_BASE_ADDRESS: + description: "The base URL for the application. Defaults to http://code.marketing-sites.localhost:3001" + default: "code.marketing-sites.localhost:3001" + ENVIRONMENT_TYPE: + description: "local, pr, test, or production" + required: true + SHARD_INDEX: + description: "The shard index to use for the tests." + required: true + SHARD_TOTAL: + description: "The total number of shards to use for the tests." + required: true + GITHUB_ENVIRONMENT_NAME: + description: "The name of the GitHub environment to use for this action" + required: false + BRANCHED_TESTING_ENABLED: + description: "Whether branched testing is enabled. Defaults to false" + default: "false" + required: false + PR_HEAD_REF: + description: "The head ref of the pull request." + required: false + SITE_TYPE: + description: "The type of site being tested. Defaults to 'corporate'." + default: "corporate" + required: false + +runs: + using: composite + + steps: + - name: Prepare UI Test Environment + shell: bash + working-directory: ./apps/marketing + run: | + echo "APPLITOOLS_API_KEY=${{ inputs.APPLITOOLS_API_KEY }} + DRAFT_MODE_TOKEN=${{ inputs.DRAFT_MODE_TOKEN }} + CI=true + STAGE=${{ inputs.ENVIRONMENT_TYPE }} + NEXT_PUBLIC_STAGE=${{ inputs.ENVIRONMENT_TYPE }} + APPLICATION_BASE_ADDRESS=${{ inputs.APPLICATION_BASE_ADDRESS }} + BRANCHED_TESTING_ENABLED=${{ inputs.BRANCHED_TESTING_ENABLED }} + PR_HEAD_REF=${{ inputs.PR_HEAD_REF }} + SITE_TYPE=${{ inputs.SITE_TYPE }} + " >> .env + + - name: UI Tests + shell: bash + # If updating playwright, also update the version in package.json + run: | + docker run --network host \ + --env-file ./apps/marketing/.env \ + -v $PWD:/workspace \ + -w /workspace\ + mcr.microsoft.com/playwright:v1.49.1-noble \ + bash -c "corepack enable && HOME=/root yarn workspace @code-dot-org/marketing test:ui:ci --shard ${{ inputs.SHARD_INDEX }}/${{ inputs.SHARD_TOTAL }}" + working-directory: ./ + + - name: Upload shard blob report to GitHub Actions Artifacts + if: ${{ !cancelled() }} + uses: actions/upload-artifact@v4 + with: + name: blob-report-${{ inputs.GITHUB_ENVIRONMENT_NAME || 'pr' }}-${{ inputs.SHARD_INDEX }} + path: ./apps/marketing/blob-report + retention-days: 1 + + - name: Run Google Lighthouse Audits + uses: treosh/lighthouse-ci-action@9917efea514615fb2ff2890f5b8be2d51e703b6e + # Only run lighthouse audits once + if: inputs.SHARD_INDEX == '1' + env: + HTTP_PROTOCOL: ${{ inputs.APPLICATION_BASE_ADDRESS == 'code.marketing-sites.localhost:3001' && 'http' || 'https' }} + with: + urls: ${{ env.HTTP_PROTOCOL }}://${{ inputs.APPLICATION_BASE_ADDRESS }}/en-US/engineering/all-the-things + uploadArtifacts: true + temporaryPublicStorage: true + configPath: "./apps/marketing/.lighthouserc.js" + artifactName: "lighthouse-report-${{ inputs.GITHUB_ENVIRONMENT_NAME || 'local' }}" diff --git a/.github/actions/frontend/merge-playwright-reports/action.yml b/.github/actions/frontend/merge-playwright-reports/action.yml new file mode 100644 index 00000000..ebc034f1 --- /dev/null +++ b/.github/actions/frontend/merge-playwright-reports/action.yml @@ -0,0 +1,32 @@ +name: "Merge Playwright Test Reports" +description: "Merges playwright test reports from multiple shards into one single report" + +inputs: + GITHUB_ENVIRONMENT_NAME: + description: "The name of the GitHub environment to use for this action" + default: "pr" + required: false + +runs: + using: composite + steps: + - name: Setup Frontend + uses: ./.github/actions/frontend/setup + + - name: Download blob reports from GitHub Actions Artifacts + uses: actions/download-artifact@v4 + with: + path: all-blob-reports + pattern: blob-report-${{ inputs.GITHUB_ENVIRONMENT_NAME }}-* + merge-multiple: true + + - name: Merge into HTML Report + shell: bash + run: yarn dlx playwright merge-reports --reporter html ./all-blob-reports + + - name: Upload HTML report + uses: actions/upload-artifact@v4 + with: + name: html-report--attempt-${{ inputs.GITHUB_ENVIRONMENT_NAME }}-${{ github.run_attempt }} + path: playwright-report + retention-days: 1 diff --git a/.github/actions/frontend/setup/action.yml b/.github/actions/frontend/setup/action.yml new file mode 100644 index 00000000..c051b039 --- /dev/null +++ b/.github/actions/frontend/setup/action.yml @@ -0,0 +1,31 @@ +name: "Install & Setup Frontend Directory" +description: "Setup for the frontend directory" + +inputs: + INSTALL_DEPENDENCIES: + description: "Whether to install dependencies. Defaults to true" + default: "true" + required: false + +runs: + using: composite + steps: + - name: Enable Corepack before setting up Node + shell: bash + run: corepack enable + + - name: Cache turborepo + uses: rharkor/caching-for-turbo@a1c4079258ae08389be75b57d4d7a70f23c1c66d # v1.8 + + - uses: actions/setup-node@v4 + if: inputs.INSTALL_DEPENDENCIES == 'true' + with: + node-version-file: ".nvmrc" + cache: "yarn" + cache-dependency-path: 'yarn.lock' + + - name: Install dependencies + if: inputs.INSTALL_DEPENDENCIES == 'true' + shell: bash + run: yarn install + working-directory: ./ diff --git a/.github/workflows/component-library-ci.yml b/.github/workflows/component-library-ci.yml new file mode 100644 index 00000000..f3dd64ac --- /dev/null +++ b/.github/workflows/component-library-ci.yml @@ -0,0 +1,105 @@ +name: Component-Library-CI + +on: + workflow_call: + +jobs: + build: + runs-on: ubuntu-24.04 + + steps: + - uses: actions/checkout@v4 + + - name: Setup Frontend + uses: ./.github/actions/frontend/setup + + - name: Install Playwright for Design System Storybook + run: npx playwright install --with-deps + working-directory: ./apps/design-system-storybook + + - name: Build + run: yarn build --filter @code-dot-org/component-library --filter @code-dot-org/design-system-storybook + working-directory: ./ + + - name: Lint + run: yarn lint --filter @code-dot-org/component-library --filter @code-dot-org/design-system-storybook + working-directory: ./ + + - name: Unit Tests + run: yarn test --filter @code-dot-org/component-library --filter @code-dot-org/design-system-storybook + working-directory: ./ + + - name: UI Tests + run: yarn test:ui:ci --filter @code-dot-org/component-library --filter @code-dot-org/design-system-storybook + working-directory: ./ + + - name: Upload static files as pages artifact + if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call' + id: deployment + uses: actions/upload-pages-artifact@v3 + with: + path: apps/design-system-storybook/dist + name: component-library-storybook + + eyes: + runs-on: ubuntu-24.04 + + permissions: + pull-requests: write + issues: write + + steps: + - uses: actions/checkout@v4 + + - name: Setup Frontend + uses: ./.github/actions/frontend/setup + + - name: Build + run: yarn build --filter @code-dot-org/design-system-storybook + working-directory: ./ + + - name: Eyes Tests + id: eyes + run: | + EYES_REPORT=$(COREPACK_ENABLE_DOWNLOAD_PROMPT=0 yarn eyes-storybook) + echo "$EYES_REPORT" + # Send multiline report to environment var + echo "EYES_REPORT<> $GITHUB_OUTPUT + echo "$EYES_REPORT" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + # Check if the report was successful, failing if not. + if echo "$EYES_REPORT" | grep -q "✅"; then + echo "✅ Eyes report was successful, exiting with 0" + exit 0 + else + echo "❌ Eyes report unsuccessful, exiting with 1" + exit 1 + fi + working-directory: ./apps/design-system-storybook + continue-on-error: ${{ github.event_name == 'pull_request' }} + env: + APPLITOOLS_API_KEY: ${{ secrets.APPLITOOLS_API_KEY }} + + - name: Find Report Comment + if: github.event.pull_request.number + uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e # v3.1.0 + id: eyes-report-comment + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: 'github-actions[bot]' + body-includes: 'Visual Comparison Report' + + - name: Create comment (if not exists) + if: github.event.pull_request.number && steps.eyes-report-comment.outputs.comment-id == '' + uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 + with: + issue-number: ${{ github.event.pull_request.number }} + body: ${{ steps.eyes.outputs.EYES_REPORT }} + + - name: Update comment (if exists) + if: github.event.pull_request.number && steps.eyes-report-comment.outputs.comment-id != '' + uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 + with: + comment-id: ${{ steps.eyes-report-comment.outputs.comment-id }} + edit-mode: replace + body: ${{ steps.eyes.outputs.EYES_REPORT }} \ No newline at end of file diff --git a/.github/workflows/component-library-deploy.yml b/.github/workflows/component-library-deploy.yml new file mode 100644 index 00000000..6163cab5 --- /dev/null +++ b/.github/workflows/component-library-deploy.yml @@ -0,0 +1,37 @@ +name: Component-Library-Deploy + +on: + workflow_dispatch: + + push: + branches: + - main + paths: + - 'packages/component-library/**' + - 'packages/component-library-styles/**' + - 'shared/**' + +jobs: + ci: + uses: ./.github/workflows/component-library-ci.yml + secrets: inherit + + deploy: + needs: + - ci + + permissions: + contents: read + pages: write + id-token: write + + environment: + name: component-library-storybook + + runs-on: ubuntu-24.04 + + steps: + - name: Deploy to GitHub Pages + uses: actions/deploy-pages@v4 + with: + artifact_name: "component-library-storybook" diff --git a/.github/workflows/frontend-ci.yml b/.github/workflows/frontend-ci.yml new file mode 100644 index 00000000..6676f84e --- /dev/null +++ b/.github/workflows/frontend-ci.yml @@ -0,0 +1,62 @@ +# Common workflow for execution of continuous integration for the `frontend` code base. +# This workflow handles setup and teardown for parallel workflow execution for the various apps and packages in `frontend`. +name: Frontend-CI + +on: + pull_request: + branches: + - main + +env: + APPLITOOLS_BATCH_ID: ${{ github.event.pull_request.head.sha || github.sha }} + +jobs: + # Initializes common variables and environment prior to executing other jobs + setup: + runs-on: ubuntu-24.04 + outputs: + component-library-changed: ${{ steps.changes.outputs.component-library }} + steps: + # Determine what changed to later skip workflows + # Github Actions does not offer conditional runtime path filtering, thus use this action to calculate the + # changes. Root level applications (such as marketing) are not skipped as its dependency tree always changes + - name: Get Change Sets + uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 + id: changes + with: + filters: | + component-library: + - 'apps/design-system-storybook/**' + - 'packages/component-library/**' + - 'packages/component-library-styles/**' + - 'packages/fonts/**' + - '*' + + # Start the marketing app CI workflow + marketing: + needs: setup + uses: ./.github/workflows/marketing-app-ci.yml + with: + BRANCHED_TESTING_ENABLED: ${{ contains( github.event.pull_request.labels.*.name, 'All The Things') }} + PR_HEAD_REF: ${{ github.event.pull_request.head.ref }} + secrets: inherit + + # Start the component library CI workflow + component-library: + if: ${{ needs.setup.outputs.component-library-changed == 'true' }} + needs: setup + uses: ./.github/workflows/component-library-ci.yml + secrets: inherit + + # Common teardown actions that must occur after _all_ workflows have completed. + # For example, closing the Applitools batch after its completion. + teardown: + needs: [setup, marketing, component-library] + if: always() + runs-on: ubuntu-24.04 + steps: + # Close the eyes batch after all distributed eyes tests have completed + - name: Update Applitools batch status + shell: bash + run: | + curl -X POST "https://eyesapi.applitools.com/api/externals/github/servers/github.com/commit/${{ env.APPLITOOLS_BATCH_ID }}/${{ github.run_id }}/complete?apiKey=${{secrets.APPLITOOLS_API_KEY}}" diff --git a/.github/workflows/marketing-app-ci.yml b/.github/workflows/marketing-app-ci.yml new file mode 100644 index 00000000..854aff8a --- /dev/null +++ b/.github/workflows/marketing-app-ci.yml @@ -0,0 +1,188 @@ +name: Marketing-CI + +on: + workflow_call: + inputs: + BRANCHED_TESTING_ENABLED: + type: boolean + description: "Whether branched testing is enabled. Defaults to false" + default: false + required: false + PR_HEAD_REF: + type: string + description: "The head ref of the pull request." + required: false + +defaults: + run: + shell: bash + working-directory: ./ + +jobs: + dryrun-release: + runs-on: ubuntu-24.04 + + steps: + - uses: actions/checkout@v4 + + - name: Setup Frontend + uses: ./.github/actions/frontend/setup + + - name: Dryrun Release + run: yarn release:dryrun --filter @code-dot-org/marketing + + storybook-test: + runs-on: ubuntu-24.04 + container: + # Make sure to grab the latest version of the Playwright image + # https://playwright.dev/docs/docker#pull-the-image + image: mcr.microsoft.com/playwright:v1.49.1-noble + steps: + - uses: actions/checkout@v4 + + - name: Setup Frontend + uses: ./.github/actions/frontend/setup + + - name: Build + run: yarn build --filter @code-dot-org/marketing-storybook + + - name: Run tests + env: + PLAYWRIGHT_BROWSERS_PATH: /ms-playwright + run: yarn workspace @code-dot-org/marketing-storybook test:ui:ci + + storybook-eyes: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + + - name: Setup Frontend + uses: ./.github/actions/frontend/setup + + - name: Build + run: yarn build --filter @code-dot-org/marketing-storybook + + - name: Run visual tests + env: + APPLITOOLS_API_KEY: ${{ secrets.APPLITOOLS_API_KEY }} + run: yarn workspace @code-dot-org/marketing-storybook eyes-storybook + + build-docker: + # Skip CI docker build if this is not a pull request. + if: ${{ github.event_name == 'pull_request' }} + runs-on: ubuntu-24.04 + + steps: + - uses: actions/checkout@v4 + + - name: Setup Frontend + uses: ./.github/actions/frontend/setup + with: + INSTALL_DEPENDENCIES: false # We don't need to install dependencies for the Docker build. + + - name: Build Docker image + id: build-docker-image + uses: docker/build-push-action@v6.10.0 + with: + context: . + file: apps/marketing/Dockerfile + tags: marketing:test + push: false + network: host # This is required for the `turbo` command to work properly in the Dockerfile. + # Note TURBO_TOKEN is not a secret because we use a local turbo instance, it is stubbed out for future use only. + build-args: | + TURBO_TEAM=${{ vars.TURBO_REPO_TEAM }} + TURBO_TOKEN=${{ secrets.TURBO_REPO_TOKEN }} + TURBO_API=${{ vars.TURBO_REPO_API }} + + - name: Save Docker Image to Artifacts + run: docker save marketing:test | zstd -T0 > marketing-test-${{ github.run_id }}.tar.zst + + - name: Upload Docker image artifact + uses: actions/upload-artifact@v4 + with: + compression: 0 + name: marketing-test-${{ github.run_id }}.tar.zst + path: marketing-test-${{ github.run_id }}.tar.zst + + ui-tests: + if: ${{ github.event_name == 'pull_request' }} + runs-on: ubuntu-24.04 + needs: build-docker + + strategy: + fail-fast: false + matrix: + shardIndex: [1, 2, 3, 4] + # shardTotal is used to determine how many shards there are in total. + shardTotal: [4] + + steps: + - uses: actions/checkout@v4 + + - name: Setup Frontend + uses: ./.github/actions/frontend/setup + + - name: Build + run: yarn build --filter @code-dot-org/marketing + + - name: Download Docker image artifact + uses: actions/download-artifact@v4 + with: + name: marketing-test-${{ github.run_id }}.tar.zst + path: . + + - name: Extract Docker image artifact + run: zstd -d marketing-test-${{ github.run_id }}.tar.zst + + - name: Load Docker image + run: docker load -i marketing-test-${{ github.run_id }}.tar + + - name: Prepare Test Environment + shell: bash + run: | + echo "CONTENTFUL_SPACE_ID=${{ vars.CONTENTFUL_SPACE_ID }} + CONTENTFUL_ENV_ID=development + CONTENTFUL_API_HOST=cdn.contentful.com + CONTENTFUL_DELIVERY_TOKEN=${{ secrets.CONTENTFUL_DELIVERY_TOKEN }} + CONTENTFUL_EXPERIENCE_CONTENT_TYPE_ID=experience + CONTENTFUL_REVALIDATE_TOKEN=ci-revalidate-test + DRAFT_MODE_TOKEN=ci-draft-mode + HOSTNAME=0.0.0.0 + NEXT_PUBLIC_STAGE=development + " > .env + + - name: Run Docker Container + run: | + docker run -d --env-file .env --rm --name marketing \ + --network=host \ + -e PORT=3001 \ + marketing:test + + # Tail the docker logs + docker logs -f marketing & + + - name: UI Tests + uses: ./.github/actions/frontend/marketing/ui-tests + with: + ENVIRONMENT_TYPE: pr + CONTENTFUL_SPACE_ID: ${{ vars.CONTENTFUL_SPACE_ID }} + CONTENTFUL_DELIVERY_TOKEN: ${{ secrets.CONTENTFUL_DELIVERY_TOKEN}} + APPLITOOLS_API_KEY: ${{ secrets.APPLITOOLS_API_KEY}} + DRAFT_MODE_TOKEN: ci-draft-mode + BRANCHED_TESTING_ENABLED: ${{ inputs.BRANCHED_TESTING_ENABLED }} + PR_HEAD_REF: ${{ inputs.PR_HEAD_REF }} + SHARD_INDEX: ${{ matrix.shardIndex }} + SHARD_TOTAL: ${{ matrix.shardTotal }} + + merge-reports: + # Merge reports after playwright-tests, even if some shards have failed + if: ${{ github.event_name == 'pull_request' && !cancelled() }} + needs: [ ui-tests ] + + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + + - name: Merge Playwright Reports + uses: ./.github/actions/frontend/merge-playwright-reports \ No newline at end of file diff --git a/.github/workflows/marketing-app-deploy-to-environment.yml b/.github/workflows/marketing-app-deploy-to-environment.yml new file mode 100644 index 00000000..1d106b9f --- /dev/null +++ b/.github/workflows/marketing-app-deploy-to-environment.yml @@ -0,0 +1,175 @@ +# Deploys the Marketing App to a specific Github Environment. +# See Marketing-App-Deploy for the parent workflow that provides the matrix inputs to this reusable workflow. + +# The name does not use spaces due to https://github.com/integrations/slack/issues/1790 +name: Marketing-App-Deploy-To-Environment + +# Configures this workflow to run every time a change is pushed to `frontend` +on: + workflow_dispatch: + inputs: + ENVIRONMENT_TYPE: + description: "The type of environment to deploy to, e.g. 'test' or 'production'." + required: true + type: string + SITE_TYPE: + description: "The type of site to deploy, e.g. 'csforall' or 'corporate'." + required: true + type: string + CONTAINER_IMAGE_DIGEST: + description: "The sha256 digest of the container image to deploy." + required: true + type: string + workflow_call: + inputs: + ENVIRONMENT_TYPE: + description: "The type of environment to deploy to, e.g. 'test' or 'production'." + required: true + type: string + SITE_TYPE: + description: "The type of site to deploy, e.g. 'csforall' or 'corporate'." + required: true + type: string + CONTAINER_IMAGE_DIGEST: + description: "The sha256 digest of the container image to deploy." + required: true + type: string + +# Defines two custom environment variables for the workflow. These are used for the Container registry domain, and a name for the Docker image that this workflow builds. +env: + REGISTRY: ghcr.io + IMAGE_NAME: code-dot-org/marketing + GITHUB_ENVIRONMENT_NAME: marketing-sites-${{ inputs.ENVIRONMENT_TYPE }}-${{ inputs.SITE_TYPE }} + +# There is a single job in this workflow. It's configured to run on the latest available version of Ubuntu. +jobs: + # Job: deploy + # This job pulls the task definition for the current task running in the cluster and updates the docker image tag with the one built in the previous job + # Then, the task definition is deployed to the ECS service + # See: https://docs.github.com/en/actions/use-cases-and-examples/deploying/deploying-to-amazon-elastic-container-service + deploy: + runs-on: ubuntu-24.04 + environment: marketing-sites-${{ inputs.ENVIRONMENT_TYPE }}-${{ inputs.SITE_TYPE }} + + permissions: + # Allows Github to retrieve AWS credentials via OIDC + # See: https://docs.github.com/en/actions/security-for-github-actions/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services#adding-permissions-settings + id-token: write + + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - uses: ruby/setup-ruby@eaecf785f6a34567a6d97f686bbb7bccc1ac1e5c # v1.237.0 + with: + working-directory: ./apps/marketing/cicd + bundler-cache: true + + # Warning: Use minimal permissions for the deployer + # See: https://github.com/aws-actions/amazon-ecs-deploy-task-definition?tab=readme-ov-file#permissions + - name: Configure ECS/Github Deployer AWS credentials + uses: aws-actions/configure-aws-credentials@f24d7193d98baebaeacc7e2227925dd47cc267f5 # v4.2.0 + with: + role-to-assume: ${{ secrets.GH_ACTIONS_DEPLOYER_IAM_ROLE_ARN }} + role-session-name: GithubActions-${{ env.GITHUB_ENVIRONMENT_NAME }} + aws-region: ${{ vars.AWS_REGION }} + + - name: "Download Built Docker Image" + run: | + docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ inputs.CONTAINER_IMAGE_DIGEST }} + docker create --name temp ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ inputs.CONTAINER_IMAGE_DIGEST }} + + - name: "Extract Static Assets from Docker" + run: | + docker cp temp:/app/apps/marketing/.next/static/ ./static + + - name: "[${{ env.GITHUB_ENVIRONMENT_NAME }}] Upload Static Assets to S3" + env: + BASE_DOMAIN: ${{ vars.APPLICATION_BASE_DOMAIN }} + SUBDOMAIN_NAME: ${{ vars.APPLICATION_SUBDOMAIN_NAME }} + run: | + BUCKET_NAME="$SUBDOMAIN_NAME.$BASE_DOMAIN-static-assets" + + echo "Uploading static assets to $BUCKET_NAME" + + # Upload to S3 and set the cache control for these static assets to be immutable. + # More details: https://nextjs.org/docs/app/guides/self-hosting#automatic-caching + aws s3 sync static/ "s3://$BUCKET_NAME/_next/static" --quiet --cache-control "public, max-age=31536000, immutable" + + - name: "[${{ env.GITHUB_ENVIRONMENT_NAME }}] Deploy Marketing App to AWS" + working-directory: ./apps/marketing/cicd/3-app + run: | + bundle exec ruby deploy.rb --environment_type ${{ vars.ENVIRONMENT_TYPE }} \ + --site_type ${{ vars.SITE_TYPE }} \ + --region ${{ vars.AWS_REGION }} \ + --hosted_zone_id ${{ secrets.ROOT_HOSTED_ZONE_ID }} \ + --base_domain_name ${{ vars.APPLICATION_BASE_DOMAIN }} \ + --subdomain_name ${{ vars.APPLICATION_SUBDOMAIN_NAME }} \ + --container_image_hash ${{ inputs.CONTAINER_IMAGE_DIGEST }} \ + --role_arn ${{ secrets.CLOUDFORMATION_DEPLOYER_ROLE_ARN }} \ + --web_application_server_secrets_arn ${{ secrets.APPLICATION_SERVER_SECRETS_ARN }} \ + --cloudformation_role_boundary ${{ secrets.CLOUDFORMATION_ROLE_BOUNDARY_ARN }} \ + --production_domain_name "${{ vars.PRODUCTION_DOMAIN_NAME }}" \ + --production_hosted_zone_id "${{ secrets.PRODUCTION_HOSTED_ZONE_ID }}" \ + --web-application-firewall "${{ secrets.WEB_APPLICATION_FIREWALL_ARN }}" + + - name: "[${{ env.GITHUB_ENVIRONMENT_NAME }}] Invalidate CloudFront Cache" + env: + AWS_REGION: ${{ vars.AWS_REGION }} + STACK_NAME: "${{ vars.APPLICATION_SUBDOMAIN_NAME }}-${{ vars.APPLICATION_BASE_DOMAIN }}" + run: | + CFN_STACK_NAME=$(echo "$STACK_NAME" | tr '.' '-') + CLOUDFRONT_DISTRIBUTION_ID=$(aws cloudformation describe-stacks --region $AWS_REGION --stack-name $CFN_STACK_NAME --query "Stacks[0].Outputs[?OutputKey=='CloudFrontDistributionId'].OutputValue" --output text) + + echo "::add-mask::$CLOUDFRONT_DISTRIBUTION_ID" + + aws cloudfront create-invalidation \ + --distribution-id $CLOUDFRONT_DISTRIBUTION_ID \ + --paths "/*" + + ui-tests: + runs-on: ubuntu-24.04 + environment: marketing-sites-${{ inputs.ENVIRONMENT_TYPE }}-${{ inputs.SITE_TYPE }} + needs: + - deploy + + strategy: + fail-fast: false + matrix: + shardIndex: [1, 2, 3, 4] + # shardTotal is used to determine how many shards there are in total. + shardTotal: [4] + + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Setup Frontend + uses: ./.github/actions/frontend/setup + + - name: Build + run: yarn build --filter @code-dot-org/marketing + working-directory: ./ + + - name: UI Tests + uses: ./.github/actions/frontend/marketing/ui-tests + with: + ENVIRONMENT_TYPE: ${{ vars.ENVIRONMENT_TYPE }} + CONTENTFUL_SPACE_ID: ${{ vars.CONTENTFUL_SPACE_ID }} + APPLITOOLS_API_KEY: ${{ secrets.APPLITOOLS_API_KEY}} + APPLICATION_BASE_ADDRESS: "${{ vars.APPLICATION_SUBDOMAIN_NAME }}.${{ vars.APPLICATION_BASE_DOMAIN }}" + DRAFT_MODE_TOKEN: ${{ secrets.DRAFT_MODE_TOKEN }} + GITHUB_ENVIRONMENT_NAME: ${{ env.GITHUB_ENVIRONMENT_NAME }} + SITE_TYPE: ${{ inputs.SITE_TYPE }} + SHARD_INDEX: ${{ matrix.shardIndex }} + SHARD_TOTAL: ${{ matrix.shardTotal }} + + merge-reports: + needs: [ ui-tests ] + + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + + - name: Merge Playwright Reports + uses: ./.github/actions/frontend/merge-playwright-reports + with: + GITHUB_ENVIRONMENT_NAME: ${{ env.GITHUB_ENVIRONMENT_NAME }} diff --git a/.github/workflows/marketing-app-deploy.yml b/.github/workflows/marketing-app-deploy.yml new file mode 100644 index 00000000..e164d9ce --- /dev/null +++ b/.github/workflows/marketing-app-deploy.yml @@ -0,0 +1,140 @@ +# Currently, only the `marketing` app has a Dockerfile. This workflow is intended to be expanded upon in the future to include other apps that have Dockerfiles. +# Future iterations of this workflow will include a matrix strategy to build and publish Docker images for all apps that have Dockerfiles. + +# The name does not use spaces due to https://github.com/integrations/slack/issues/1790 +name: Marketing-App-Deploy + +# Configures this workflow to run every time a change is pushed to `frontend` +on: + workflow_dispatch: + push: + branches: + - main + paths: + - '**' + +# Defines two custom environment variables for the workflow. These are used for the Container registry domain, and a name for the Docker image that this workflow builds. +env: + REGISTRY: ghcr.io + IMAGE_NAME: code-dot-org/marketing + +concurrency: + # For pushes to main, requests are grouped into the "deploy-marketing" concurrency group to queue them up + # For pull requests, there are no limits to concurrency + group: ${{ github.run_id }} + # For pushes to main, multiple pushes are queued + # For pull requests, multiple pushes to the same PR cancels previous executions + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +# There is a single job in this workflow. It's configured to run on the latest available version of Ubuntu. +jobs: + ci: + uses: ./.github/workflows/marketing-app-ci.yml + secrets: inherit + + # Job: build-and-push-image + # This job builds a docker image for the frontend repository and pushes the docker image to Github Packages. + # More information: https://docs.github.com/en/actions/use-cases-and-examples/publishing-packages/publishing-docker-images + build-and-push-image: + runs-on: ubuntu-24.04 + + # Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job. + permissions: + contents: read + packages: write + attestations: write + id-token: write + + # Expose the docker image and tag that this job produces + outputs: + fullImageUri: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME}}@${{ steps.push.outputs.digest }} + digest: ${{ steps.push.outputs.digest }} + + steps: + - uses: actions/checkout@v4 + + - name: Setup Frontend + uses: ./.github/actions/frontend/setup + + # Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here. + - name: Log in to the Container registry + uses: docker/login-action@v3.3.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels. + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5.6.1 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + + # This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages. + # It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository. + # It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step. + - name: Build and push Docker image + id: push + uses: docker/build-push-action@v6.10.0 + with: + context: . + file: apps/marketing/Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + network: host # This is required for the `turbo` command to work properly in the Dockerfile. + # Note TURBO_TOKEN is not a secret because we use a local turbo instance, it is stubbed out for future use only. + build-args: | + TURBO_TEAM=${{ vars.TURBO_REPO_TEAM }} + TURBO_TOKEN=${{ secrets.TURBO_REPO_TOKEN }} + TURBO_API=${{ vars.TURBO_REPO_API }} + + # This step generates an artifact attestation for the image, which is an unforgeable statement about where and how it was built. It increases supply chain security for people who consume the image. For more information, see "[AUTOTITLE](/actions/security-guides/using-artifact-attestations-to-establish-provenance-for-builds)." + - name: Generate artifact attestation + uses: actions/attest-build-provenance@v2 + with: + subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME}} + subject-digest: ${{ steps.push.outputs.digest }} + push-to-registry: true + + # Deploy test environments in parallel + deploy-test: + needs: + - ci + - build-and-push-image + uses: ./.github/workflows/marketing-app-deploy-to-environment.yml + secrets: inherit + # Do not allow other deployments to deploy while this deployment is in progress. + concurrency: + group: deploy-marketing-sites-test-${{ github.ref_name }}-${{ matrix.SITE_TYPE }} + cancel-in-progress: false + with: + CONTAINER_IMAGE_DIGEST: ${{ needs.build-and-push-image.outputs.digest }} + ENVIRONMENT_TYPE: test + SITE_TYPE: ${{ matrix.SITE_TYPE }} + strategy: + fail-fast: true + matrix: + SITE_TYPE: [ corporate, csforall, aiday ] + + # Deploy production environments in parallel, after test environments completes successfully + deploy-production: + needs: + - ci + - build-and-push-image + - deploy-test + uses: ./.github/workflows/marketing-app-deploy-to-environment.yml + secrets: inherit + # Do not allow other deployments to deploy while this deployment is in progress. + concurrency: + group: deploy-marketing-sites-production-${{ github.ref_name }}-${{ matrix.SITE_TYPE }} + cancel-in-progress: false + with: + CONTAINER_IMAGE_DIGEST: ${{ needs.build-and-push-image.outputs.digest }} + ENVIRONMENT_TYPE: production + SITE_TYPE: ${{ matrix.SITE_TYPE }} + strategy: + fail-fast: true + matrix: + SITE_TYPE: [ corporate, csforall, aiday ] \ No newline at end of file diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000..2bd5a0a9 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +22 diff --git a/README.md b/README.md index 8db46981..5276876d 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,6 @@ -# Code.org Frontend +# Code.org Marketing Websites -This directory contains packages and applications that power Code.org sites. - -**Note**: Most of Code.org's Studio product (student experience, curriculum, teacher tools, etc.) is built in the -top-level `apps` package and is not currently located in this directory. +This directory contains packages and applications that power Code.org marketing websites. For Code.org's learning platform, see [this repository](https://github.com/code-dot-org/code-dot-org). ## What's inside? @@ -14,22 +11,22 @@ This directory uses [Turborepo](https://turbo.build/) to manage the monorepo and ### Apps -Open source Code.org applications: +Open source Code.org marketing applications: - [@code-dot-org/marketing](apps/marketing): Code.org's marketing site (uses Contentful). -- [@code-dot-org/design-system-storybook](apps/design-system-storybook): A [Storybook](https://storybook.js.org/) - instance for the Code.org design system (`@code-dot-org/component-library`). Publicly available at - [https://code-dot-org.github.io/code-dot-org/component-library-storybook](https://code-dot-org.github.io/code-dot-org/component-library-storybook). +- [@code-dot-org/design-system-storybook](apps/marketing-storybook): A [Storybook](https://storybook.js.org/) + instance for the Code.org design system (`@code-dot-org/marketing-component-library`). Publicly available at + [https://code-dot-org.github.io/code-dot-org/marketing-component-library-storybook](https://code-dot-org.github.io/code-dot-org/marketing-component-library-storybook). ### Packages Publicly available packages: -- [@code-dot-org/component-library](packages/component-library): The Code.org Design System React component library. -- [@code-dot-org/component-library-styles](packages/component-library-styles): Common Styles - (`variables`, `colors`, `mixins`, `typography styles`, etc) of Code.org Design System - ([@code-dot-org/component-library](packages/component-library)). Based on [Figma](https://www.figma.com/design/NIVcvUgU3WmXpAmp9U2vVy/DSCO-Variables?node-id=2925-33951&m=dev). - Used by [@code-dot-org/component-library](packages/component-library), should also be used as +- [@code-dot-org/marketing-component-library](packages/marketing-component-library): The Code.org Marketing Design System React component library. +- [@code-dot-org/marketing-component-library-styles](packages/marketing-component-library-styles): Common Styles + (`variables`, `colors`, `mixins`, `typography styles`, etc) of Code.org Marketing Design System + ([@code-dot-org/marketing-component-library](packages/marketing-component-library)). Based on [Figma](https://www.figma.com/design/NIVcvUgU3WmXpAmp9U2vVy/DSCO-Variables?node-id=2925-33951&m=dev). + Used by [@code-dot-org/marketing-component-library](packages/marketing-component-library), should also be used as a standalone package for styling components with Code.org's Design System styles. - [@code-dot-org/lint-config](packages/lint-config): Shared linters configuration for Code.org projects (includes `eslint`, `lint-staged,` `prettier`, `stylelint`, `typescript` configs). @@ -40,7 +37,7 @@ Publicly available packages: ## Getting Started _(!!!)_ If you're unable to find some information in this README.md, please refer to the documentation of package/app -that you're working on. (e.g. go to [packages/component-library/README.md](packages/component-library/README.md), +that you're working on. (e.g. go to [packages/marketing-component-library/README.md](packages/marketing-component-library/README.md), [apps/marketing/README.md](apps/marketing/README.md), etc) ### Prerequisites @@ -81,7 +78,7 @@ More information on this command [here](https://yarnpkg.com/cli/workspace). For example, to only run the design system storybook: ```bash -yarn workspace @code-dot-org/design-system-storybook dev +yarn workspace @code-dot-org/marketing-storybook dev ``` ### Formatting, Linting. (Prettier, ESLint, Stylelint) @@ -95,11 +92,11 @@ yarn lint:fix You can also run this command for some specific package or app using yarn workspace: ```bash -yarn lint:fix --filter @code-dot-org/component-library +yarn lint:fix --filter @code-dot-org/marketing-component-library OR -yarn workspace @code-dot-org/component-library lint:fix +yarn workspace @code-dot-org/marketing-component-library lint:fix ``` ### Pre-release Testing diff --git a/apps/design-system-storybook/public/images/action-block-01.png b/apps/design-system-storybook/public/images/action-block-01.png index 7c36c887..d1e78b01 100644 Binary files a/apps/design-system-storybook/public/images/action-block-01.png and b/apps/design-system-storybook/public/images/action-block-01.png differ diff --git a/apps/design-system-storybook/public/images/action-block-02.png b/apps/design-system-storybook/public/images/action-block-02.png index cbec09e3..9432f8e1 100644 Binary files a/apps/design-system-storybook/public/images/action-block-02.png and b/apps/design-system-storybook/public/images/action-block-02.png differ diff --git a/apps/design-system-storybook/public/images/action-block-03.png b/apps/design-system-storybook/public/images/action-block-03.png index 8aec9713..79f74cf3 100644 Binary files a/apps/design-system-storybook/public/images/action-block-03.png and b/apps/design-system-storybook/public/images/action-block-03.png differ diff --git a/apps/design-system-storybook/public/images/action-block-04.png b/apps/design-system-storybook/public/images/action-block-04.png index dc321b71..ac647343 100644 Binary files a/apps/design-system-storybook/public/images/action-block-04.png and b/apps/design-system-storybook/public/images/action-block-04.png differ diff --git a/apps/design-system-storybook/public/images/action-block-05.png b/apps/design-system-storybook/public/images/action-block-05.png index e66a0cd3..330acd5c 100644 Binary files a/apps/design-system-storybook/public/images/action-block-05.png and b/apps/design-system-storybook/public/images/action-block-05.png differ diff --git a/apps/design-system-storybook/public/images/action-block-06.png b/apps/design-system-storybook/public/images/action-block-06.png index c2837fd0..22307059 100644 Binary files a/apps/design-system-storybook/public/images/action-block-06.png and b/apps/design-system-storybook/public/images/action-block-06.png differ diff --git a/apps/design-system-storybook/public/images/admins-page-top.png b/apps/design-system-storybook/public/images/admins-page-top.png index 5676d0da..0da8a0a0 100644 Binary files a/apps/design-system-storybook/public/images/admins-page-top.png and b/apps/design-system-storybook/public/images/admins-page-top.png differ diff --git a/apps/design-system-storybook/public/images/bg-pattern.png b/apps/design-system-storybook/public/images/bg-pattern.png index 7540e9e1..9c80365c 100644 Binary files a/apps/design-system-storybook/public/images/bg-pattern.png and b/apps/design-system-storybook/public/images/bg-pattern.png differ diff --git a/apps/design-system-storybook/public/images/code-org-logo.png b/apps/design-system-storybook/public/images/code-org-logo.png index 262cd149..8d67b9a7 100644 Binary files a/apps/design-system-storybook/public/images/code-org-logo.png and b/apps/design-system-storybook/public/images/code-org-logo.png differ diff --git a/apps/design-system-storybook/public/images/header-all-projects-icon.png b/apps/design-system-storybook/public/images/header-all-projects-icon.png index 07946025..677f3d3f 100644 Binary files a/apps/design-system-storybook/public/images/header-all-projects-icon.png and b/apps/design-system-storybook/public/images/header-all-projects-icon.png differ diff --git a/apps/design-system-storybook/public/images/header-app-lab-icon.png b/apps/design-system-storybook/public/images/header-app-lab-icon.png index e5ca05b2..136b7727 100644 Binary files a/apps/design-system-storybook/public/images/header-app-lab-icon.png and b/apps/design-system-storybook/public/images/header-app-lab-icon.png differ diff --git a/apps/design-system-storybook/public/images/header-artist-icon.png b/apps/design-system-storybook/public/images/header-artist-icon.png index b4e27a4e..b6faa7ae 100644 Binary files a/apps/design-system-storybook/public/images/header-artist-icon.png and b/apps/design-system-storybook/public/images/header-artist-icon.png differ diff --git a/apps/design-system-storybook/public/images/header-dance-party-icon.png b/apps/design-system-storybook/public/images/header-dance-party-icon.png index e0bd0125..96c50d15 100644 Binary files a/apps/design-system-storybook/public/images/header-dance-party-icon.png and b/apps/design-system-storybook/public/images/header-dance-party-icon.png differ diff --git a/apps/design-system-storybook/public/images/header-game-lab-icon.png b/apps/design-system-storybook/public/images/header-game-lab-icon.png index b42b749f..71b3e4c1 100644 Binary files a/apps/design-system-storybook/public/images/header-game-lab-icon.png and b/apps/design-system-storybook/public/images/header-game-lab-icon.png differ diff --git a/apps/design-system-storybook/public/images/header-music-lab-icon.png b/apps/design-system-storybook/public/images/header-music-lab-icon.png index 6ab3ae45..c806ede9 100644 Binary files a/apps/design-system-storybook/public/images/header-music-lab-icon.png and b/apps/design-system-storybook/public/images/header-music-lab-icon.png differ diff --git a/apps/design-system-storybook/public/images/header-python-lab-icon.png b/apps/design-system-storybook/public/images/header-python-lab-icon.png index 476815c7..ad1f61af 100644 Binary files a/apps/design-system-storybook/public/images/header-python-lab-icon.png and b/apps/design-system-storybook/public/images/header-python-lab-icon.png differ diff --git a/apps/design-system-storybook/public/images/header-sprite-lab-icon.png b/apps/design-system-storybook/public/images/header-sprite-lab-icon.png index 317488c2..62b27232 100644 Binary files a/apps/design-system-storybook/public/images/header-sprite-lab-icon.png and b/apps/design-system-storybook/public/images/header-sprite-lab-icon.png differ diff --git a/apps/design-system-storybook/public/images/help-page-top.png b/apps/design-system-storybook/public/images/help-page-top.png index 8e576cfb..5e9bc81a 100644 Binary files a/apps/design-system-storybook/public/images/help-page-top.png and b/apps/design-system-storybook/public/images/help-page-top.png differ diff --git a/apps/design-system-storybook/public/images/hero-banner-custom-bg-example.png b/apps/design-system-storybook/public/images/hero-banner-custom-bg-example.png index 8c196574..898bd587 100644 Binary files a/apps/design-system-storybook/public/images/hero-banner-custom-bg-example.png and b/apps/design-system-storybook/public/images/hero-banner-custom-bg-example.png differ diff --git a/apps/design-system-storybook/public/images/image-component.png b/apps/design-system-storybook/public/images/image-component.png index 86ca75cc..095cb9d2 100644 Binary files a/apps/design-system-storybook/public/images/image-component.png and b/apps/design-system-storybook/public/images/image-component.png differ diff --git a/apps/design-system-storybook/public/images/teach-page-top.png b/apps/design-system-storybook/public/images/teach-page-top.png index 19ff9f5c..48785226 100644 Binary files a/apps/design-system-storybook/public/images/teach-page-top.png and b/apps/design-system-storybook/public/images/teach-page-top.png differ diff --git a/apps/marketing/public/images/error/404.png b/apps/marketing/public/images/error/404.png index 8e739a43..ae415a53 100644 Binary files a/apps/marketing/public/images/error/404.png and b/apps/marketing/public/images/error/404.png differ diff --git a/apps/marketing/public/images/error/500.png b/apps/marketing/public/images/error/500.png index 5f83dbaf..77cda577 100644 Binary files a/apps/marketing/public/images/error/500.png and b/apps/marketing/public/images/error/500.png differ