diff --git a/.github/actions/check_clean/action.yml b/.github/actions/check_clean/action.yml new file mode 100644 index 0000000000..484958480f --- /dev/null +++ b/.github/actions/check_clean/action.yml @@ -0,0 +1,12 @@ +name: check_clean +runs: + using: composite + steps: + - name: Check Repository Clean + run: |- + # Print status for debugging on CircleCI. + git status + # Fail the build if any step leaves uncommitted changes in the repo + # that would prevent Lerna from publishing (Lerna gets this right). + git diff --exit-code + shell: bash diff --git a/.github/actions/count_deps/action.yml b/.github/actions/count_deps/action.yml new file mode 100644 index 0000000000..f5c84584c2 --- /dev/null +++ b/.github/actions/count_deps/action.yml @@ -0,0 +1,18 @@ +name: count_deps +runs: + using: composite + steps: + - name: Count Generated Project Dependencies + # TODO: Can TOTAL_PACKAGES be exported in a cleaner way? + run: |- + MAX_PACKAGES="2260" + total=$(./scripts/count-dependencies.js generated-${{ matrix.template }}) + echo "TOTAL_PACKAGES=${total}" >> $GITHUB_ENV + + if [ "$total" -gt "$MAX_PACKAGES" ]; then + echo "Error: Found $TOTAL_PACKAGES installed packages (max $MAX_PACKAGES)."; + exit 1; + else + echo "Found $TOTAL_PACKAGES installed packages (max $MAX_PACKAGES)."; + fi + shell: bash diff --git a/.github/actions/create_mrt/action.yml b/.github/actions/create_mrt/action.yml new file mode 100644 index 0000000000..9c9f153884 --- /dev/null +++ b/.github/actions/create_mrt/action.yml @@ -0,0 +1,14 @@ +name: create_mrt +inputs: + mobify_user: + description: "Mobify user email" + mobify_api_key: + description: "Mobify user API key" +runs: + using: composite + steps: + - name: Create MRT credentials file + run: |- + # Add credentials file at ~/.mobify so we can upload to Mobify Cloud + npm run save-credentials --prefix packages/template-retail-react-app -- --user "${{inputs.mobify_user}}" --key "${{inputs.mobify_api_key}}" + shell: bash diff --git a/.github/actions/datadog/action.yml b/.github/actions/datadog/action.yml new file mode 100644 index 0000000000..a35bcc51fc --- /dev/null +++ b/.github/actions/datadog/action.yml @@ -0,0 +1,16 @@ +name: datadog +inputs: + datadog_api_key: + description: "Datadog API key" + TOTAL_PACKAGES: + description: "Total # of packages" +runs: + using: composite + steps: + - name: Send metrics to Datadog + run : | + # Add a dogrc so we can submit metrics to datadog + printf "[Connection]\napikey = ${{inputs.datadog_api_key}}\nappkey =\n" > ~/.dogrc + + dog metric post mobify_platform_sdks.generated_project_total_packages ${{ inputs.TOTAL_PACKAGES }} + shell: bash diff --git a/.github/actions/lighthouse_ci/action.yml b/.github/actions/lighthouse_ci/action.yml new file mode 100644 index 0000000000..3b6474959a --- /dev/null +++ b/.github/actions/lighthouse_ci/action.yml @@ -0,0 +1,7 @@ +name: lighthouse_ci +runs: + using: composite + steps: + - name: Run Lighthouse CI on the PWA + run: npm run test:lighthouse --prefix packages/template-retail-react-app + shell: bash diff --git a/.github/actions/publish_to_npm/action.yml b/.github/actions/publish_to_npm/action.yml new file mode 100644 index 0000000000..0f9267ea5c --- /dev/null +++ b/.github/actions/publish_to_npm/action.yml @@ -0,0 +1,21 @@ +name: publish_to_npm +inputs: + NODE_AUTH_TOKEN: + description: "Node auth token" +runs: + using: composite + steps: + # TODO: Figure out a way to specify whether to publish to "latest" or "next" tag + - name: Publish to NPM + run: |- + # Add NPM token to allow publishing + echo "//registry.npmjs.org/:_authToken=${{ inputs.NODE_AUTH_TOKEN }}" > ~/.npmrc + + # Publish all changed packages. The "from-package" arg means "look + # at the version numbers in each package.json file and if that doesn't + # exist on NPM, publish" + npm run lerna -- publish from-package --yes --no-verify-access + + # Cleanup + rm ~/.npmrc + shell: bash diff --git a/.github/actions/push_to_mrt/action.yml b/.github/actions/push_to_mrt/action.yml new file mode 100644 index 0000000000..c7affda607 --- /dev/null +++ b/.github/actions/push_to_mrt/action.yml @@ -0,0 +1,18 @@ +name: push_to_mrt +inputs: + CWD: + description: Project directory + TARGET: + description: MRT target +runs: + using: composite + steps: + - name: Push Bundle to MRT + run: |- + cd ${{ inputs.CWD }} + project="scaffold-pwa" + build="build ${{ github.run_id }} on ${{ github.ref }} (${{ github.sha }})" + if [[ ${{ inputs.TARGET }} ]]; then + npm run push -- -s $project --message "$build" --target ${{ inputs.TARGET }} + fi + shell: bash diff --git a/.github/actions/setup_ubuntu/action.yml b/.github/actions/setup_ubuntu/action.yml new file mode 100644 index 0000000000..87b24e699e --- /dev/null +++ b/.github/actions/setup_ubuntu/action.yml @@ -0,0 +1,35 @@ +name: setup_ubuntu +inputs: + cwd: + required: false + default: "${PWD}" +description: "Setup Ubuntu Machine" +runs: + using: composite + steps: + - name: Install Dependencies + run: |- + # Install system dependencies + sudo apt-get update -yq + sudo apt-get install python2 python3-pip time -yq + sudo pip install -U pip setuptools + sudo pip install awscli==1.18.85 datadog==0.40.1 + + # Install node dependencies + node ./scripts/gtime.js monorepo_install npm ci + + # Build the PWA + npm run lerna -- run analyze-build --scope "retail-react-app" + + # Report bundle sizes + node ./scripts/report-bundle-size.js + + # Check that packages are all using the same versions of compilers, etc. + node ./scripts/check-dependencies.js + + # Install Snyk CLI + sudo npm install -g snyk + + # Install Lighthouse CI CLI + sudo npm install -g @lhci/cli + shell: bash diff --git a/.github/actions/setup_windows/action.yml b/.github/actions/setup_windows/action.yml new file mode 100644 index 0000000000..bea04deb09 --- /dev/null +++ b/.github/actions/setup_windows/action.yml @@ -0,0 +1,14 @@ +name: setup_windows +inputs: + cwd: + required: false + default: "${PWD}" +description: "Setup Windows Machine" +runs: + using: composite + steps: + - name: Install Dependencies + run: |- + # Install node dependencies + npm ci + shell: bash diff --git a/.github/actions/smoke_tests/action.yml b/.github/actions/smoke_tests/action.yml new file mode 100644 index 0000000000..135e6aca25 --- /dev/null +++ b/.github/actions/smoke_tests/action.yml @@ -0,0 +1,14 @@ +name: smoke_tests +inputs: + dir: + required: false + # The path to a project to test + default: "./packages/template-retail-react-app" +runs: + using: composite + steps: + - name: Smoke test scripts + run: |- + # Basic smoke-tests for uncommonly run scripts in a project + node ./scripts/smoke-test-npm-scripts.js --dir ${{ inputs.dir }} + shell: bash diff --git a/.github/actions/snyk/action.yml b/.github/actions/snyk/action.yml new file mode 100644 index 0000000000..fb3cbe0541 --- /dev/null +++ b/.github/actions/snyk/action.yml @@ -0,0 +1,15 @@ +name: snyk +inputs: + snyk_token: + description: "Snyk token" + DEVELOP: + description: "Is this the 'develop' branch?" +runs: + using: composite + steps: + - name: Audit Generated Project + run: |- + # Run snyk auth - authenticate snyk using environment variables to add the token + snyk auth ${{ inputs.snyk_token }} + snyk monitor --ignore-policy --remote-repo-url='https://github.com/SalesforceCommerceCloud/pwa-kit.git' --project-name='generated-scaffold-pwa' + shell: bash diff --git a/.github/actions/unit_tests/action.yml b/.github/actions/unit_tests/action.yml new file mode 100644 index 0000000000..2b7bbe010c --- /dev/null +++ b/.github/actions/unit_tests/action.yml @@ -0,0 +1,27 @@ +name: unit_tests +inputs: + cwd: + required: false + default: "${PWD}" +description: "Run tests action description" +runs: + using: composite + steps: + - name: Run tests step + # TODO: The pilefile policy is a legacy of CircleCI. Is it still needed? + run: |- + # Explicitly set pipefile policy. This is the default for non-windows, but seems + # that is needs to be set on windows to fail immediately. + set -eo pipefail + + cd ${{ inputs.cwd }} + + # Note: Each of these test commands need to be exposed on the monorepo + # root and *also* on the PWA package. This section is run on both. + + # Ensure bundlesize is in check + npm run test:max-file-size + + # Always run fast unit tests + npm run test + shell: bash diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000000..d11228af86 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,277 @@ +# WARNING! Conditionals are set as variables to minimize repetitive checks. +# However, this results in the variables being the *string* values "true" or "false". +# As a result, you must always explicitly check for those strings. For example, +# ${{ env.DEVELOP }} will ALWAYS evaluate as true; to achieve the expected result +# you must check ${{ env.DEVELOP == 'true' }}. There's probably a better way to DRY, +# but this is what we have for now. + +name: SalesforceCommerceCloud/pwa-kit/test +on: + # PRs from forks trigger `pull_request`, but do NOT have access to secrets. + # More info: + # https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#workflows-in-forked-repositories-1 + # https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ + # https://docs.github.com/en/actions/managing-workflow-runs/approving-workflow-runs-from-public-forks + pull_request: # Default: opened, reopened, synchronize (head branch updated) + push: + branches: + - develop + # TODO: Should we run on all pushes to release branches, or should we run on GitHub releases? + - 'release-*' + schedule: + - cron: 0 8 * * * +env: + IS_NOT_FORK: ${{ github.repository == 'SalesforceCommerceCloud/pwa-kit' }} + DEVELOP: ${{ github.repository == 'SalesforceCommerceCloud/pwa-kit' && github.ref == 'refs/heads/develop' }} + RELEASE: ${{ github.repository == 'SalesforceCommerceCloud/pwa-kit' && startsWith(github.ref, 'refs/heads/release-') }} +jobs: + pwa-kit: + strategy: + matrix: + node: [14] + npm: [6, 7, 8] + runs-on: ubuntu-latest + env: + # The "default" npm is the one that ships with a given version of node + # node v14 uses npm@6, latest node v16 uses npm@8 + # For more: https://nodejs.org/en/download/releases/ + IS_DEFAULT_NPM: ${{ matrix.npm == 6 }} + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node }} + cache: npm + + - name: Update NPM version + if: env.IS_DEFAULT_NPM == 'false' + run: |- + npm install -g npm@${{ matrix.npm }} + + - name: Setup Ubuntu Machine + uses: "./.github/actions/setup_ubuntu" + + - name: Run unit tests + uses: "./.github/actions/unit_tests" + + - name: Run Lighthouse CI on the PWA + if: env.IS_DEFAULT_NPM == 'true' + uses: "./.github/actions/lighthouse_ci" + + - name: Smoke test scripts + if: env.IS_DEFAULT_NPM == 'true' + uses: "./.github/actions/smoke_tests" + + - name: Create MRT credentials file + if: env.IS_NOT_FORK == 'true' && env.IS_DEFAULT_NPM == 'true' + uses: "./.github/actions/create_mrt" + with: + mobify_user: ${{ secrets.MOBIFY_CLIENT_USER }} + mobify_api_key: ${{ secrets.MOBIFY_CLIENT_API_KEY }} + + - name: Push Bundle to MRT (Development) + if: env.IS_NOT_FORK == 'true' && env.IS_DEFAULT_NPM == 'true' && env.DEVELOP == 'true' + uses: "./.github/actions/push_to_mrt" + with: + CWD: "./packages/template-retail-react-app" + TARGET: staging + + - name: Push Bundle to MRT (Production) + if: env.IS_NOT_FORK == 'true' && env.IS_DEFAULT_NPM == 'true' && env.RELEASE == 'true' + uses: "./.github/actions/push_to_mrt" + with: + CWD: "./packages/template-retail-react-app" + TARGET: production + + - name: Push Bundle to MRT (Commerce SDK React) + if: env.IS_NOT_FORK == 'true' && env.IS_DEFAULT_NPM == 'true' && env.DEVELOPMENT == 'true' + uses: "./.github/actions/push_to_mrt" + with: + CWD: "./packages/test-commerce-sdk-react" + TARGET: commerce-sdk-react + + - name: Check Repository Clean + if: env.IS_NOT_FORK == 'true' && env.IS_DEFAULT_NPM == 'true' + uses: "./.github/actions/check_clean" + + - name: Publish to NPM + if: env.IS_NOT_FORK == 'true' && env.IS_DEFAULT_NPM == 'true' && env.RELEASE == 'true' + uses: "./.github/actions/publish_to_npm" + with: + NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }} + + - name: Send GitHub Action data to Slack workflow (PWA Kit) + id: slack + if: env.IS_NOT_FORK == 'true' && env.IS_DEFAULT_NPM == 'true' && env.DEVELOP == 'true' && failure() + uses: slackapi/slack-github-action@v1.23.0 + with: + payload: | + { + "test": "testNode${{ matrix.node }}" + } + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + pwa-kit-windows: + strategy: + # TODO: We don't *need* a matrix with single values, + # but is it worth keeping for supporting multiple versions in the future? + matrix: + node: [14] + npm: [6] + runs-on: windows-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node }} + cache: npm + + - name: Setup Windows Machine + uses: "./.github/actions/setup_windows" + + - name: Run tests + uses: "./.github/actions/unit_tests" + + # TODO: The generated workflow is identical to the generated-windows workflow, + # with a few extra steps. Can the workflows be merged? (Add `os` to the matrix?) + generated: + strategy: + matrix: + template: [test-project, retail-react-app-demo, express-minimal-test-project, typescript-minimal-test-project] + runs-on: ubuntu-latest + env: + IS_TEMPLATE_FROM_RETAIL_REACT_APP: ${{ matrix.template == 'test-project' || matrix.template == 'retail-react-app-demo' }} + PROJECT_DIR: generated-${{ matrix.template }} + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version: 14 + + - name: Setup Ubuntu Machine + uses: "./.github/actions/setup_ubuntu" + + - name: Generate ${{ matrix.template }} project + run: |- + node packages/pwa-kit-create-app/scripts/create-mobify-app-dev.js --outputDir ${{ env.PROJECT_DIR }} + env: + GENERATOR_PRESET: ${{ matrix.template }} + timeout-minutes: 5 + + - name: Run unit tests + if: env.IS_TEMPLATE_FROM_RETAIL_REACT_APP == 'true' + uses: "./.github/actions/unit_tests" + with: + cwd: ${{ env.PROJECT_DIR }} + + - name: Run smoke tests + if: env.IS_TEMPLATE_FROM_RETAIL_REACT_APP == 'true' + uses: "./.github/actions/smoke_tests" + with: + dir: ${{ env.PROJECT_DIR }} + + - name: Count Generated Project Dependencies + id: count_deps + if: env.IS_TEMPLATE_FROM_RETAIL_REACT_APP == 'true' + uses: "./.github/actions/count_deps" + + - name: Store Verdaccio logfile artifact + uses: actions/upload-artifact@v3 + with: + path: packages/pwa-kit-create-app/local-npm-repo/verdaccio.log + + - name: Audit Generated Project + if: env.IS_NOT_FORK == 'true' && env.IS_TEMPLATE_FROM_RETAIL_REACT_APP == 'true' && env.DEVELOP == 'true' + uses: "./.github/actions/snyk" + with: + snyk_token: ${{ secrets.SNYK_TOKEN }} + + - name: Send metrics to Datadog + if: env.IS_NOT_FORK == 'true' && env.IS_TEMPLATE_FROM_RETAIL_REACT_APP == 'true' + uses: "./.github/actions/datadog" + with: + datadog_api_key: ${{ secrets.DATADOG_API_KEY }} + # TODO: The way this is set is a little bit magic - can it be cleaned up? + TOTAL_PACKAGES: $TOTAL_PACKAGES + + - name: Create MRT credentials file + if: env.IS_NOT_FORK == 'true' && env.IS_TEMPLATE_FROM_RETAIL_REACT_APP == 'true' + uses: "./.github/actions/create_mrt" + with: + mobify_user: ${{ secrets.MOBIFY_CLIENT_USER }} + mobify_api_key: ${{ secrets.MOBIFY_CLIENT_API_KEY }} + + - name: Push Bundle to MRT + if: env.IS_NOT_FORK == 'true' && env.DEVELOP == 'true' && matrix.template == 'test-project' + uses: "./.github/actions/push_to_mrt" + with: + CWD: ${{ env.PROJECT_DIR }} + TARGET: generated-pwa + + - name: Send GitHub Action data to Slack workflow (Generated) + id: slack + if: env.IS_NOT_FORK == 'true' && env.IS_TEMPLATE_FROM_RETAIL_REACT_APP == 'true' && env.DEVELOP == 'true' && failure() + uses: slackapi/slack-github-action@v1.23.0 + with: + payload: | + { + "test": "generated ${{ matrix.template }}" + } + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + generated-windows: + strategy: + matrix: + template: [test-project, retail-react-app-demo, express-minimal-test-project, typescript-minimal-test-project] + runs-on: windows-latest + env: + IS_TEMPLATE_FROM_RETAIL_REACT_APP: ${{ matrix.template == 'test-project' || matrix.template == 'retail-react-app-demo' }} + PROJECT_DIR: generated-${{ matrix.template }} + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version: 14 + + - name: Setup Windows Machine + uses: "./.github/actions/setup_windows" + + - name: Generate ${{ matrix.template }} project + run: |- + node packages/pwa-kit-create-app/scripts/create-mobify-app-dev.js --outputDir generated-${{ matrix.template }} + env: + GENERATOR_PRESET: ${{ matrix.template }} + timeout-minutes: 5 + + - name: Run unit tests + if: env.IS_TEMPLATE_FROM_RETAIL_REACT_APP == 'true' + uses: "./.github/actions/unit_tests" + with: + cwd: ${{ env.PROJECT_DIR }} + + - name: Run smoke tests + if: env.IS_TEMPLATE_FROM_RETAIL_REACT_APP == 'true' + uses: "./.github/actions/smoke_tests" + with: + dir: ${{ env.PROJECT_DIR }} + + - name: Count Generated Project Dependencies + if: env.IS_TEMPLATE_FROM_RETAIL_REACT_APP == 'true' + uses: "./.github/actions/count_deps" + + - name: Store Verdaccio logfile artifact + uses: actions/upload-artifact@v3 + with: + path: packages/pwa-kit-create-app/local-npm-repo/verdaccio.log