diff --git a/.github/workflows/frontend-deploy-workflow.yml b/.github/workflows/frontend-deploy-workflow.yml index aba844a7..3100a9b3 100644 --- a/.github/workflows/frontend-deploy-workflow.yml +++ b/.github/workflows/frontend-deploy-workflow.yml @@ -23,11 +23,15 @@ on: description: 'Use asdf-vm for version management (reads from .tool-versions)' type: boolean default: false + package-manager: + description: 'Package manager to use: "yarn" or "pnpm". Callers using pnpm must pass all commands explicitly.' + type: string + default: 'yarn' # Cache configuration # Same options as PR workflow - see frontend-pr-workflow.yml for detailed comments cache-mode: - description: 'Cache strategy: full, node_modules-only, or yarn-cache-only' + description: 'Cache strategy: "full" (node_modules + package manager cache), "node_modules-only", "yarn-cache-only", or "pnpm-store-only"' type: string default: 'full' disable-restore-keys: @@ -322,19 +326,21 @@ jobs: uses: actions/checkout@v6 - name: Setup Node with Cache - uses: Typeform/.github/shared-actions/setup-node-with-cache@v1 + uses: Typeform/.github/shared-actions/setup-node-with-cache@feat/pnpm-support-shared-frontend-workflows with: node-version: ${{ inputs.node-version }} use-asdf: ${{ inputs.use-asdf }} cache-mode: ${{ inputs.cache-mode }} disable-restore-keys: ${{ inputs.disable-restore-keys }} GH_TOKEN: ${{ secrets.GH_TOKEN }} + package-manager: ${{ inputs.package-manager }} - name: Setup Jarvis - uses: Typeform/.github/shared-actions/setup-jarvis@v1 + uses: Typeform/.github/shared-actions/setup-jarvis@feat/pnpm-support-shared-frontend-workflows with: jarvis-branch: ${{ inputs.jarvis-branch }} GH_TOKEN: ${{ secrets.GH_TOKEN }} + package-manager: ${{ inputs.package-manager }} - name: Run pre-build command if: inputs.pre-build-command != '' @@ -421,19 +427,21 @@ jobs: fetch-depth: 0 # Required for SonarCloud - name: Setup Node with Cache - uses: Typeform/.github/shared-actions/setup-node-with-cache@v1 + uses: Typeform/.github/shared-actions/setup-node-with-cache@feat/pnpm-support-shared-frontend-workflows with: node-version: ${{ inputs.node-version }} use-asdf: ${{ inputs.use-asdf }} cache-mode: ${{ inputs.cache-mode }} disable-restore-keys: ${{ inputs.disable-restore-keys }} GH_TOKEN: ${{ secrets.GH_TOKEN }} + package-manager: ${{ inputs.package-manager }} - name: Setup Jarvis - uses: Typeform/.github/shared-actions/setup-jarvis@v1 + uses: Typeform/.github/shared-actions/setup-jarvis@feat/pnpm-support-shared-frontend-workflows with: jarvis-branch: ${{ inputs.jarvis-branch }} GH_TOKEN: ${{ secrets.GH_TOKEN }} + package-manager: ${{ inputs.package-manager }} - name: Run pre-test command if: inputs.pre-test-command != '' @@ -469,23 +477,25 @@ jobs: uses: actions/checkout@v6 - name: Setup Node with Cache - uses: Typeform/.github/shared-actions/setup-node-with-cache@v1 + uses: Typeform/.github/shared-actions/setup-node-with-cache@feat/pnpm-support-shared-frontend-workflows with: node-version: ${{ inputs.node-version }} use-asdf: ${{ inputs.use-asdf }} cache-mode: ${{ inputs.cache-mode }} disable-restore-keys: ${{ inputs.disable-restore-keys }} GH_TOKEN: ${{ secrets.GH_TOKEN }} + package-manager: ${{ inputs.package-manager }} - name: Setup Jarvis - uses: Typeform/.github/shared-actions/setup-jarvis@v1 + uses: Typeform/.github/shared-actions/setup-jarvis@feat/pnpm-support-shared-frontend-workflows with: jarvis-branch: ${{ inputs.jarvis-branch }} GH_TOKEN: ${{ secrets.GH_TOKEN }} + package-manager: ${{ inputs.package-manager }} - name: Setup Playwright if: inputs.integration-test-framework == 'playwright' - uses: Typeform/.github/shared-actions/setup-playwright@v1 + uses: Typeform/.github/shared-actions/setup-playwright@feat/pnpm-support-shared-frontend-workflows - name: Run pre-test command if: inputs.pre-test-command != '' @@ -529,7 +539,7 @@ jobs: steps: - name: Run Cypress Functional Tests - uses: Typeform/.github/shared-actions/run-cypress-functional@v1 + uses: Typeform/.github/shared-actions/run-cypress-functional@feat/pnpm-support-shared-frontend-workflows with: node-version: ${{ inputs.node-version }} use-asdf: ${{ inputs.use-asdf }} @@ -544,6 +554,7 @@ jobs: artifact-retention-days: '7' GH_TOKEN: ${{ secrets.GH_TOKEN }} CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} + package-manager: ${{ inputs.package-manager }} # Job 5: Cypress Visual Tests (runs in parallel with other tests) cypress-visual: @@ -555,7 +566,7 @@ jobs: steps: - name: Run Cypress Visual Tests - uses: Typeform/.github/shared-actions/run-cypress-visual@v1 + uses: Typeform/.github/shared-actions/run-cypress-visual@feat/pnpm-support-shared-frontend-workflows with: node-version: ${{ inputs.node-version }} use-asdf: ${{ inputs.use-asdf }} @@ -575,6 +586,7 @@ jobs: VRT_APIURL: ${{ secrets.VRT_APIURL }} VRT_APIKEY: ${{ secrets.VRT_APIKEY }} VRT_PROJECT: ${{ secrets.VRT_PROJECT }} + package-manager: ${{ inputs.package-manager }} # Job 6: Playwright Visual Tests (runs in parallel with other tests) playwright-visual: @@ -586,7 +598,7 @@ jobs: steps: - name: Run Playwright Visual Tests - uses: Typeform/.github/shared-actions/run-playwright-visual@v1 + uses: Typeform/.github/shared-actions/run-playwright-visual@feat/pnpm-support-shared-frontend-workflows with: node-version: ${{ inputs.node-version }} use-asdf: ${{ inputs.use-asdf }} @@ -606,6 +618,7 @@ jobs: VRT_APIURL: ${{ secrets.VRT_APIURL }} VRT_APIKEY: ${{ secrets.VRT_APIKEY }} VRT_PROJECT: ${{ secrets.VRT_PROJECT }} + package-manager: ${{ inputs.package-manager }} # Job 7: SonarCloud Analysis (waits for unit tests if enabled) sonarcloud: @@ -614,7 +627,7 @@ jobs: needs: [build, unit-tests, check-verification] permissions: contents: read - uses: Typeform/.github/.github/workflows/sonarcloud-scan.yml@v1 + uses: Typeform/.github/.github/workflows/sonarcloud-scan.yml@feat/pnpm-support-shared-frontend-workflows with: app-name: ${{ inputs.app-name }} node-version: ${{ inputs.node-version }} @@ -622,6 +635,7 @@ jobs: runner: ${{ inputs.runner }} coverage-artifact-name: ${{ inputs.run-unit-tests && format('coverage-{0}', github.run_id) || '' }} timeout: ${{ inputs.sonarcloud-timeout }} + package-manager: ${{ inputs.package-manager }} secrets: GH_TOKEN: ${{ secrets.GH_TOKEN }} SONAR_CLOUD_TOKEN: ${{ secrets.SONAR_CLOUD_TOKEN }} @@ -666,13 +680,14 @@ jobs: git config --global user.email "${{ github.actor }}@users.noreply.github.com" - name: Setup Node with Cache - uses: Typeform/.github/shared-actions/setup-node-with-cache@v1 + uses: Typeform/.github/shared-actions/setup-node-with-cache@feat/pnpm-support-shared-frontend-workflows with: node-version: ${{ inputs.node-version }} use-asdf: ${{ inputs.use-asdf }} cache-mode: ${{ inputs.cache-mode }} disable-restore-keys: ${{ inputs.disable-restore-keys }} GH_TOKEN: ${{ secrets.GH_TOKEN }} + package-manager: ${{ inputs.package-manager }} - name: Download Build Artifacts uses: Typeform/.github/shared-actions/download-build-artifacts@v1 @@ -681,10 +696,11 @@ jobs: output-dir: ${{ inputs.build-artifact-download-path || inputs.build-output-dir }} - name: Setup Jarvis - uses: Typeform/.github/shared-actions/setup-jarvis@v1 + uses: Typeform/.github/shared-actions/setup-jarvis@feat/pnpm-support-shared-frontend-workflows with: jarvis-branch: ${{ inputs.jarvis-branch }} GH_TOKEN: ${{ secrets.GH_TOKEN }} + package-manager: ${{ inputs.package-manager }} - name: AWS auth uses: Typeform/.github-private/actions/aws-auth@aws-auth-0.0.2 diff --git a/.github/workflows/frontend-library-pr-release-workflow.yml b/.github/workflows/frontend-library-pr-release-workflow.yml index 4d61ced4..4fb84d05 100644 --- a/.github/workflows/frontend-library-pr-release-workflow.yml +++ b/.github/workflows/frontend-library-pr-release-workflow.yml @@ -59,6 +59,16 @@ on: description: 'Job timeout in minutes' type: number default: 15 + + package-manager: + description: 'Package manager to use: "yarn" or "pnpm". Callers using pnpm must pass all commands explicitly.' + type: string + default: 'yarn' + + semantic-release-command: + description: 'Semantic release command. Callers using pnpm must pass all commands explicitly.' + type: string + default: 'yarn semantic-release' secrets: GH_TOKEN: @@ -107,7 +117,7 @@ jobs: if: | inputs.run-semantic-release && contains(fromJSON(inputs.semantic-release-branches), github.ref_name) - run: yarn semantic-release + run: ${{ inputs.semantic-release-command }} env: GH_TOKEN: ${{ secrets.GH_TOKEN }} NPM_TOKEN: ${{ secrets.GH_TOKEN }} diff --git a/.github/workflows/frontend-pr-workflow.yml b/.github/workflows/frontend-pr-workflow.yml index 9ef7af03..11f18ea7 100644 --- a/.github/workflows/frontend-pr-workflow.yml +++ b/.github/workflows/frontend-pr-workflow.yml @@ -26,6 +26,10 @@ on: description: 'Use asdf-vm for version management (reads from .tool-versions)' type: boolean default: false + package-manager: + description: 'Package manager to use: "yarn" or "pnpm". Callers using pnpm must pass all commands explicitly.' + type: string + default: 'yarn' # Cache configuration # Choose the right strategy for your project: @@ -40,7 +44,7 @@ on: # โ†’ Best for: Repos with frequent dependency changes, small projects # โ†’ Why: Smallest cache, fast restore, yarn install runs every time but is fast cache-mode: - description: 'Cache strategy: full, node_modules-only, or yarn-cache-only' + description: 'Cache strategy: "full" (node_modules + package manager cache), "node_modules-only", "yarn-cache-only", or "pnpm-store-only"' type: string default: 'full' disable-restore-keys: @@ -314,19 +318,21 @@ jobs: uses: actions/checkout@v6 - name: Setup Node with Cache - uses: Typeform/.github/shared-actions/setup-node-with-cache@v1 + uses: Typeform/.github/shared-actions/setup-node-with-cache@feat/pnpm-support-shared-frontend-workflows with: node-version: ${{ inputs.node-version }} use-asdf: ${{ inputs.use-asdf }} cache-mode: ${{ inputs.cache-mode }} disable-restore-keys: ${{ inputs.disable-restore-keys }} GH_TOKEN: ${{ secrets.GH_TOKEN }} + package-manager: ${{ inputs.package-manager }} - name: Setup Jarvis - uses: Typeform/.github/shared-actions/setup-jarvis@v1 + uses: Typeform/.github/shared-actions/setup-jarvis@feat/pnpm-support-shared-frontend-workflows with: jarvis-branch: ${{ inputs.jarvis-branch }} GH_TOKEN: ${{ secrets.GH_TOKEN }} + package-manager: ${{ inputs.package-manager }} # Runs before linting AND build so that generated files (e.g. GraphQL # codegen) are available for both TypeScript-aware lint rules and @@ -387,19 +393,21 @@ jobs: uses: actions/checkout@v6 - name: Setup Node with Cache - uses: Typeform/.github/shared-actions/setup-node-with-cache@v1 + uses: Typeform/.github/shared-actions/setup-node-with-cache@feat/pnpm-support-shared-frontend-workflows with: node-version: ${{ inputs.node-version }} use-asdf: ${{ inputs.use-asdf }} cache-mode: ${{ inputs.cache-mode }} disable-restore-keys: ${{ inputs.disable-restore-keys }} GH_TOKEN: ${{ secrets.GH_TOKEN }} + package-manager: ${{ inputs.package-manager }} - name: Setup Jarvis - uses: Typeform/.github/shared-actions/setup-jarvis@v1 + uses: Typeform/.github/shared-actions/setup-jarvis@feat/pnpm-support-shared-frontend-workflows with: jarvis-branch: ${{ inputs.jarvis-branch }} GH_TOKEN: ${{ secrets.GH_TOKEN }} + package-manager: ${{ inputs.package-manager }} - name: Run pre-test command if: inputs.pre-test-command != '' @@ -435,19 +443,21 @@ jobs: uses: actions/checkout@v6 - name: Setup Node with Cache - uses: Typeform/.github/shared-actions/setup-node-with-cache@v1 + uses: Typeform/.github/shared-actions/setup-node-with-cache@feat/pnpm-support-shared-frontend-workflows with: node-version: ${{ inputs.node-version }} use-asdf: ${{ inputs.use-asdf }} cache-mode: ${{ inputs.cache-mode }} disable-restore-keys: ${{ inputs.disable-restore-keys }} GH_TOKEN: ${{ secrets.GH_TOKEN }} + package-manager: ${{ inputs.package-manager }} - name: Setup Jarvis - uses: Typeform/.github/shared-actions/setup-jarvis@v1 + uses: Typeform/.github/shared-actions/setup-jarvis@feat/pnpm-support-shared-frontend-workflows with: jarvis-branch: ${{ inputs.jarvis-branch }} GH_TOKEN: ${{ secrets.GH_TOKEN }} + package-manager: ${{ inputs.package-manager }} - name: Run pre-test command if: inputs.pre-test-command != '' @@ -458,7 +468,7 @@ jobs: - name: Setup Playwright if: inputs.integration-test-framework == 'playwright' - uses: Typeform/.github/shared-actions/setup-playwright@v1 + uses: Typeform/.github/shared-actions/setup-playwright@feat/pnpm-support-shared-frontend-workflows - name: Download Build Artifacts uses: Typeform/.github/shared-actions/download-build-artifacts@v1 @@ -501,13 +511,14 @@ jobs: uses: actions/checkout@v6 - name: Setup Node with Cache - uses: Typeform/.github/shared-actions/setup-node-with-cache@v1 + uses: Typeform/.github/shared-actions/setup-node-with-cache@feat/pnpm-support-shared-frontend-workflows with: node-version: ${{ inputs.node-version }} use-asdf: ${{ inputs.use-asdf }} cache-mode: ${{ inputs.cache-mode }} disable-restore-keys: ${{ inputs.disable-restore-keys }} GH_TOKEN: ${{ secrets.GH_TOKEN }} + package-manager: ${{ inputs.package-manager }} - name: Download Build Artifacts uses: Typeform/.github/shared-actions/download-build-artifacts@v1 @@ -516,10 +527,11 @@ jobs: output-dir: ${{ inputs.build-artifact-download-path || inputs.build-output-dir }} - name: Setup Jarvis - uses: Typeform/.github/shared-actions/setup-jarvis@v1 + uses: Typeform/.github/shared-actions/setup-jarvis@feat/pnpm-support-shared-frontend-workflows with: jarvis-branch: ${{ inputs.jarvis-branch }} GH_TOKEN: ${{ secrets.GH_TOKEN }} + package-manager: ${{ inputs.package-manager }} - name: AWS auth continue-on-error: true @@ -574,7 +586,7 @@ jobs: needs: [build, unit-tests] permissions: contents: read - uses: Typeform/.github/.github/workflows/sonarcloud-scan.yml@v1 + uses: Typeform/.github/.github/workflows/sonarcloud-scan.yml@feat/pnpm-support-shared-frontend-workflows with: app-name: ${{ inputs.app-name }} node-version: ${{ inputs.node-version }} @@ -584,6 +596,7 @@ jobs: runner: ${{ inputs.runner }} coverage-artifact-name: ${{ inputs.run-unit-tests && format('coverage-{0}', github.run_id) || '' }} timeout: ${{ inputs.sonarcloud-timeout }} + package-manager: ${{ inputs.package-manager }} secrets: GH_TOKEN: ${{ secrets.GH_TOKEN }} SONAR_CLOUD_TOKEN: ${{ secrets.SONAR_CLOUD_TOKEN }} @@ -624,7 +637,7 @@ jobs: steps: - name: Run Cypress Functional Tests - uses: Typeform/.github/shared-actions/run-cypress-functional@v1 + uses: Typeform/.github/shared-actions/run-cypress-functional@feat/pnpm-support-shared-frontend-workflows with: node-version: ${{ inputs.node-version }} use-asdf: ${{ inputs.use-asdf }} @@ -639,6 +652,7 @@ jobs: artifact-retention-days: '7' GH_TOKEN: ${{ secrets.GH_TOKEN }} CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} + package-manager: ${{ inputs.package-manager }} # Job 9: Playwright Visual Tests playwright-visual: @@ -650,7 +664,7 @@ jobs: steps: - name: Run Playwright Visual Tests - uses: Typeform/.github/shared-actions/run-playwright-visual@v1 + uses: Typeform/.github/shared-actions/run-playwright-visual@feat/pnpm-support-shared-frontend-workflows with: node-version: ${{ inputs.node-version }} use-asdf: ${{ inputs.use-asdf }} @@ -670,6 +684,7 @@ jobs: VRT_APIURL: ${{ secrets.VRT_APIURL }} VRT_APIKEY: ${{ secrets.VRT_APIKEY }} VRT_PROJECT: ${{ secrets.VRT_PROJECT }} + package-manager: ${{ inputs.package-manager }} # Job 10: Cypress Visual Tests cypress-visual: @@ -681,7 +696,7 @@ jobs: steps: - name: Run Cypress Visual Tests - uses: Typeform/.github/shared-actions/run-cypress-visual@v1 + uses: Typeform/.github/shared-actions/run-cypress-visual@feat/pnpm-support-shared-frontend-workflows with: node-version: ${{ inputs.node-version }} use-asdf: ${{ inputs.use-asdf }} @@ -701,6 +716,7 @@ jobs: VRT_APIURL: ${{ secrets.VRT_APIURL }} VRT_APIKEY: ${{ secrets.VRT_APIKEY }} VRT_PROJECT: ${{ secrets.VRT_PROJECT }} + package-manager: ${{ inputs.package-manager }} # Job 10: Record PR verification marker # Writes refs/tests-passed/ after all enabled suites pass so the deploy workflow diff --git a/.github/workflows/graphql-generate-persisted-operations.yml b/.github/workflows/graphql-generate-persisted-operations.yml index eb5fab4b..99a536b5 100644 --- a/.github/workflows/graphql-generate-persisted-operations.yml +++ b/.github/workflows/graphql-generate-persisted-operations.yml @@ -9,6 +9,11 @@ on: type: string required: false default: "ci-universal-scale-set" + package-manager: + description: 'Package manager to use: "yarn" or "pnpm"' + type: string + required: false + default: "yarn" secrets: GH_TOKEN: required: true @@ -33,8 +38,13 @@ jobs: with: node-version: 22 - - name: Install Yarn - run: npm install -g yarn@1.22.22 + - name: Install package manager + run: | + if [ "${{ inputs.package-manager }}" = "pnpm" ]; then + npm install -g pnpm + else + npm install -g yarn@1.22.22 + fi - name: Set GitHub packages registry run: | @@ -59,12 +69,22 @@ jobs: run: | mkdir persisted-ops-generator cd persisted-ops-generator - yarn init -y - yarn add --no-lockfile --non-interactive --dev \ - @typeform/generate-persisted-operations-manifest \ - @apollo/generate-persisted-query-manifest@^1.2.2 \ - @apollo/client@^3.7.0 \ - graphql@^16.0.0 + PM="${{ inputs.package-manager }}" + if [ "$PM" = "pnpm" ]; then + pnpm init + pnpm add --save-dev \ + @typeform/generate-persisted-operations-manifest \ + @apollo/generate-persisted-query-manifest@^1.2.2 \ + @apollo/client@^3.7.0 \ + graphql@^16.0.0 + else + yarn init -y + yarn add --no-lockfile --non-interactive --dev \ + @typeform/generate-persisted-operations-manifest \ + @apollo/generate-persisted-query-manifest@^1.2.2 \ + @apollo/client@^3.7.0 \ + graphql@^16.0.0 + fi cd .. - name: Generate Persisted Operations diff --git a/.github/workflows/sonarcloud-scan.yml b/.github/workflows/sonarcloud-scan.yml index 5fcb695f..3dfa2972 100644 --- a/.github/workflows/sonarcloud-scan.yml +++ b/.github/workflows/sonarcloud-scan.yml @@ -27,6 +27,10 @@ on: description: 'Disable restore-keys to avoid restoring stale caches (forces exact key match)' type: boolean default: false + package-manager: + description: 'Package manager to use: "yarn" or "pnpm"' + type: string + default: "yarn" # Runner configuration runner: @@ -68,13 +72,14 @@ jobs: fetch-depth: 0 # Required for SonarCloud to analyze git history - name: Setup Node with Cache - uses: Typeform/.github/shared-actions/setup-node-with-cache@v1 + uses: Typeform/.github/shared-actions/setup-node-with-cache@feat/pnpm-support-shared-frontend-workflows with: node-version: ${{ inputs.node-version }} use-asdf: ${{ inputs.use-asdf }} cache-mode: ${{ inputs.cache-mode }} disable-restore-keys: ${{ inputs.disable-restore-keys }} GH_TOKEN: ${{ secrets.GH_TOKEN }} + package-manager: ${{ inputs.package-manager }} - name: Download coverage artifacts if: inputs.coverage-artifact-name != '' diff --git a/reusable-workflows/frontend-pr-workflow/workflow.yml b/reusable-workflows/frontend-pr-workflow/workflow.yml index bdee4e06..d2d723f7 100644 --- a/reusable-workflows/frontend-pr-workflow/workflow.yml +++ b/reusable-workflows/frontend-pr-workflow/workflow.yml @@ -100,6 +100,12 @@ on: type: string default: 'staging' + # Package manager configuration + package-manager: + description: 'Package manager to use: "yarn" or "pnpm". Callers using pnpm must pass all commands explicitly.' + type: string + default: 'yarn' + # Timeout configuration build-timeout: description: 'Build job timeout (minutes)' @@ -151,12 +157,14 @@ jobs: with: node-version: ${{ inputs.node-version }} GH_TOKEN: ${{ secrets.GH_TOKEN }} + package-manager: ${{ inputs.package-manager }} - name: Setup Jarvis uses: Typeform/.github/shared-actions/setup-jarvis@main with: jarvis-branch: ${{ inputs.jarvis-branch }} GH_TOKEN: ${{ secrets.GH_TOKEN }} + package-manager: ${{ inputs.package-manager }} - name: Clean dist directory run: ${{ inputs.clean-command }} @@ -201,6 +209,7 @@ jobs: with: node-version: ${{ inputs.node-version }} GH_TOKEN: ${{ secrets.GH_TOKEN }} + package-manager: ${{ inputs.package-manager }} - name: Run unit tests run: ${{ inputs.unit-test-command }} @@ -230,9 +239,12 @@ jobs: with: node-version: ${{ inputs.node-version }} GH_TOKEN: ${{ secrets.GH_TOKEN }} + package-manager: ${{ inputs.package-manager }} - name: Setup Playwright uses: Typeform/.github/shared-actions/setup-playwright@main + with: + package-manager: ${{ inputs.package-manager }} - name: Download Build Artifacts uses: Typeform/.github/shared-actions/download-build-artifacts@main @@ -271,6 +283,7 @@ jobs: with: node-version: ${{ inputs.node-version }} GH_TOKEN: ${{ secrets.GH_TOKEN }} + package-manager: ${{ inputs.package-manager }} - name: Download Build Artifacts uses: Typeform/.github/shared-actions/download-build-artifacts@main @@ -283,6 +296,7 @@ jobs: with: jarvis-branch: ${{ inputs.jarvis-branch }} GH_TOKEN: ${{ secrets.GH_TOKEN }} + package-manager: ${{ inputs.package-manager }} - name: AWS auth continue-on-error: true diff --git a/shared-actions/run-cypress-functional/action.yml b/shared-actions/run-cypress-functional/action.yml index f20d86a9..d5403b8c 100644 --- a/shared-actions/run-cypress-functional/action.yml +++ b/shared-actions/run-cypress-functional/action.yml @@ -23,6 +23,10 @@ inputs: description: 'Jarvis branch to use (empty = npm version)' required: false default: '' + package-manager: + description: 'Package manager to use: "yarn" or "pnpm"' + required: false + default: "yarn" # Pre-test setup pre-test-command: @@ -74,22 +78,26 @@ runs: uses: actions/checkout@v7 - name: Setup Node with Cache - uses: Typeform/.github/shared-actions/setup-node-with-cache@v1 + uses: Typeform/.github/shared-actions/setup-node-with-cache@feat/pnpm-support-shared-frontend-workflows with: node-version: ${{ inputs.node-version }} use-asdf: ${{ inputs.use-asdf }} cache-mode: ${{ inputs.cache-mode }} disable-restore-keys: ${{ inputs.disable-restore-keys }} GH_TOKEN: ${{ inputs.GH_TOKEN }} + package-manager: ${{ inputs.package-manager }} - name: Setup Jarvis - uses: Typeform/.github/shared-actions/setup-jarvis@v1 + uses: Typeform/.github/shared-actions/setup-jarvis@feat/pnpm-support-shared-frontend-workflows with: jarvis-branch: ${{ inputs.jarvis-branch }} GH_TOKEN: ${{ inputs.GH_TOKEN }} + package-manager: ${{ inputs.package-manager }} - name: Setup Cypress - uses: Typeform/.github/shared-actions/setup-cypress@v1 + uses: Typeform/.github/shared-actions/setup-cypress@feat/pnpm-support-shared-frontend-workflows + with: + package-manager: ${{ inputs.package-manager }} - name: Run pre-test command if: inputs.pre-test-command != '' diff --git a/shared-actions/run-cypress-visual/action.yml b/shared-actions/run-cypress-visual/action.yml index 466a2482..593b3af1 100644 --- a/shared-actions/run-cypress-visual/action.yml +++ b/shared-actions/run-cypress-visual/action.yml @@ -23,6 +23,10 @@ inputs: description: 'Jarvis branch to use (empty = npm version)' required: false default: '' + package-manager: + description: 'Package manager to use: "yarn" or "pnpm"' + required: false + default: "yarn" # Pre-test setup pre-test-command: @@ -92,22 +96,26 @@ runs: uses: actions/checkout@v7 - name: Setup Node with Cache - uses: Typeform/.github/shared-actions/setup-node-with-cache@v1 + uses: Typeform/.github/shared-actions/setup-node-with-cache@feat/pnpm-support-shared-frontend-workflows with: node-version: ${{ inputs.node-version }} use-asdf: ${{ inputs.use-asdf }} cache-mode: ${{ inputs.cache-mode }} disable-restore-keys: ${{ inputs.disable-restore-keys }} GH_TOKEN: ${{ inputs.GH_TOKEN }} + package-manager: ${{ inputs.package-manager }} - name: Setup Jarvis - uses: Typeform/.github/shared-actions/setup-jarvis@v1 + uses: Typeform/.github/shared-actions/setup-jarvis@feat/pnpm-support-shared-frontend-workflows with: jarvis-branch: ${{ inputs.jarvis-branch }} GH_TOKEN: ${{ inputs.GH_TOKEN }} + package-manager: ${{ inputs.package-manager }} - name: Setup Cypress - uses: Typeform/.github/shared-actions/setup-cypress@v1 + uses: Typeform/.github/shared-actions/setup-cypress@feat/pnpm-support-shared-frontend-workflows + with: + package-manager: ${{ inputs.package-manager }} - name: Run pre-test command if: inputs.pre-test-command != '' diff --git a/shared-actions/run-playwright-visual/action.yml b/shared-actions/run-playwright-visual/action.yml index 72477e9e..035d7a27 100644 --- a/shared-actions/run-playwright-visual/action.yml +++ b/shared-actions/run-playwright-visual/action.yml @@ -23,6 +23,10 @@ inputs: description: 'Jarvis branch to use (empty = npm version)' required: false default: '' + package-manager: + description: 'Package manager to use: "yarn" or "pnpm"' + required: false + default: "yarn" # Pre-test setup pre-test-command: @@ -95,22 +99,26 @@ runs: uses: actions/checkout@v7 - name: Setup Node with Cache - uses: Typeform/.github/shared-actions/setup-node-with-cache@v1 + uses: Typeform/.github/shared-actions/setup-node-with-cache@feat/pnpm-support-shared-frontend-workflows with: node-version: ${{ inputs.node-version }} use-asdf: ${{ inputs.use-asdf }} cache-mode: ${{ inputs.cache-mode }} disable-restore-keys: ${{ inputs.disable-restore-keys }} GH_TOKEN: ${{ inputs.GH_TOKEN }} + package-manager: ${{ inputs.package-manager }} - name: Setup Jarvis - uses: Typeform/.github/shared-actions/setup-jarvis@v1 + uses: Typeform/.github/shared-actions/setup-jarvis@feat/pnpm-support-shared-frontend-workflows with: jarvis-branch: ${{ inputs.jarvis-branch }} GH_TOKEN: ${{ inputs.GH_TOKEN }} + package-manager: ${{ inputs.package-manager }} - name: Setup Playwright - uses: Typeform/.github/shared-actions/setup-playwright@v1 + uses: Typeform/.github/shared-actions/setup-playwright@feat/pnpm-support-shared-frontend-workflows + with: + package-manager: ${{ inputs.package-manager }} - name: Download Build Artifacts if: inputs.build-artifact-name != '' diff --git a/shared-actions/setup-cypress/README.md b/shared-actions/setup-cypress/README.md index 02b4e40f..b5ab63f4 100644 --- a/shared-actions/setup-cypress/README.md +++ b/shared-actions/setup-cypress/README.md @@ -32,7 +32,7 @@ jobs: - uses: actions/checkout@v4 - name: Setup Node - uses: Typeform/.github/shared-actions/setup-node-with-cache@v1 + uses: Typeform/.github/shared-actions/setup-node-with-cache@feat/pnpm-support-shared-frontend-workflows with: use-asdf: true GH_TOKEN: ${{ secrets.GH_TOKEN }} diff --git a/shared-actions/setup-cypress/action.yml b/shared-actions/setup-cypress/action.yml index a44b6b66..dbcb0dfa 100644 --- a/shared-actions/setup-cypress/action.yml +++ b/shared-actions/setup-cypress/action.yml @@ -1,6 +1,12 @@ name: 'Setup Cypress with Caching' description: 'Install and cache Cypress binary for E2E testing' +inputs: + package-manager: + description: 'Package manager to use: "yarn" or "pnpm"' + required: false + default: 'yarn' + runs: using: 'composite' steps: @@ -40,12 +46,13 @@ runs: if: steps.cypress-cache.outputs.cache-hit != 'true' shell: bash run: | - if command -v yarn &> /dev/null && [ -f "package.json" ]; then - echo "๐Ÿ“ฆ Installing Cypress binary..." - yarn cypress install + PM="${{ inputs.package-manager }}" + if [ -f "package.json" ]; then + echo "๐Ÿ“ฆ Installing Cypress binary via $PM..." + $PM cypress install echo "โœ… Cypress binary installed" else - echo "โš ๏ธ yarn or package.json not found, skipping Cypress installation" + echo "โš ๏ธ package.json not found, skipping Cypress installation" fi - name: Verify Cypress installation diff --git a/shared-actions/setup-jarvis/action.yml b/shared-actions/setup-jarvis/action.yml index 306201c7..3fcb287a 100644 --- a/shared-actions/setup-jarvis/action.yml +++ b/shared-actions/setup-jarvis/action.yml @@ -12,6 +12,10 @@ inputs: description: 'Working directory for the project' required: false default: '.' + package-manager: + description: 'Package manager to use: "yarn" or "pnpm"' + required: false + default: 'yarn' runs: using: 'composite' @@ -22,12 +26,13 @@ runs: env: GH_TOKEN: ${{ inputs.GH_TOKEN }} JARVIS_BRANCH: ${{ inputs.jarvis-branch }} + PM: ${{ inputs.package-manager }} run: | if [ "$ACT" = "true" ]; then # Local testing with act: link local Jarvis echo "๐Ÿ”— Linking local Jarvis from /Users/kevin.barz/code/jarvis" - cd /Users/kevin.barz/code/jarvis && yarn link - cd $GITHUB_WORKSPACE && yarn link @typeform/jarvis + cd /Users/kevin.barz/code/jarvis && $PM link + cd $GITHUB_WORKSPACE && $PM link @typeform/jarvis echo "โœ… Jarvis linked from local development copy" ls -la node_modules/@typeform/jarvis elif [ -n "$JARVIS_BRANCH" ]; then @@ -41,24 +46,24 @@ runs: # Install Jarvis dependencies cd "$JARVIS_DIR" - yarn install --frozen-lockfile + $PM install --frozen-lockfile # Link Jarvis - yarn link + $PM link # Link to this project cd $GITHUB_WORKSPACE - yarn link @typeform/jarvis + $PM link @typeform/jarvis echo "โœ… Jarvis linked from GitHub branch: $JARVIS_BRANCH" echo "๐Ÿ“ Jarvis location: $JARVIS_DIR" ls -la node_modules/@typeform/jarvis else - # Default: use npm version from package.json (already installed by yarn install) + # Default: use npm version from package.json (already installed by install step) echo "๐Ÿ“ฆ Using Jarvis from npm (version from package.json)" if [ -f "node_modules/@typeform/jarvis/package.json" ]; then echo "โœ… Jarvis version: $(node -p "require('./node_modules/@typeform/jarvis/package.json').version")" else - echo "โš ๏ธ Jarvis not found in node_modules - may need to run yarn install first" + echo "โš ๏ธ Jarvis not found in node_modules - may need to run $PM install first" fi - fi \ No newline at end of file + fi diff --git a/shared-actions/setup-node-with-cache/CACHE-STRATEGY-GUIDE.md b/shared-actions/setup-node-with-cache/CACHE-STRATEGY-GUIDE.md index ac2a3dba..475677f2 100644 --- a/shared-actions/setup-node-with-cache/CACHE-STRATEGY-GUIDE.md +++ b/shared-actions/setup-node-with-cache/CACHE-STRATEGY-GUIDE.md @@ -32,6 +32,8 @@ Is your project a workspace/monorepo? ## Cache Mode Comparison +### yarn + | Mode | What's Cached | Cache Size | Restore Time | Install on Hit | Install on Miss | Best For | |------|---------------|------------|--------------|----------------|-----------------|----------| | **full** (default) | `node_modules`
`~/.cache/yarn` | Large | Slow | Sometimes* | Fast | Workspaces, general use | @@ -40,6 +42,18 @@ Is your project a workspace/monorepo? \* Workspaces always run install to recreate symlinks +### pnpm + +| Mode | What's Cached | Cache Size | Restore Time | Install on Hit | Install on Miss | Best For | +|------|---------------|------------|--------------|----------------|-----------------|----------| +| **full** (default) | `node_modules`
pnpm store | Large | Slow | Sometimes* | Fast | pnpm workspaces, general use | +| **node_modules-only** | `node_modules` only | Medium | Fast | Rarely | Slow | Non-workspace pnpm repos | +| **pnpm-store-only** | pnpm store only | Small | Very Fast | Always | Fast | Frequent dep changes | + +\* Workspaces always run install to recreate symlinks + +**pnpm store path**: resolved dynamically via `pnpm store path` โ€” typically `~/.local/share/pnpm/store` on Linux runners. + ## Detailed Recommendations ### 1. Workspace/Monorepo Projects diff --git a/shared-actions/setup-node-with-cache/README.md b/shared-actions/setup-node-with-cache/README.md index 16277d4b..d8a9179d 100644 --- a/shared-actions/setup-node-with-cache/README.md +++ b/shared-actions/setup-node-with-cache/README.md @@ -1,11 +1,12 @@ # Setup Node with Enhanced Caching -Standardized Node.js setup with enhanced yarn caching and GitHub packages registry configuration. +Standardized Node.js setup with enhanced yarn/pnpm caching and GitHub packages registry configuration. ## Features - โœ… Node.js setup with configurable version -- โœ… Multi-layer caching (node_modules + yarn global cache) +- โœ… **pnpm and yarn support** โ€” opt-in via `package-manager` input +- โœ… Multi-layer caching (node_modules + yarn global cache / pnpm store) - โœ… Architecture-specific cache keys (ARM/x86) - โœ… Restore-keys for fallback caching (85%+ hit rate) - โœ… GitHub packages registry configuration @@ -32,8 +33,9 @@ Standardized Node.js setup with enhanced yarn caching and GitHub packages regist | `node-version` | Node.js version to use (ignored if use-asdf is true) | No | `'20'` | | `use-asdf` | Use asdf-vm for version management | No | `'false'` | | `GH_TOKEN` | GitHub token for private packages | Yes | - | +| `package-manager` | Package manager: `yarn` or `pnpm` | No | `'yarn'` | | `enable-yarn-cache` | Enable yarn global cache (~/.cache/yarn) | No | `'true'` | -| `cache-mode` | Cache strategy: `full`, `node_modules-only`, or `yarn-cache-only` | No | `'full'` | +| `cache-mode` | Cache strategy: `full`, `node_modules-only`, `yarn-cache-only`, or `pnpm-store-only` | No | `'full'` | | `disable-restore-keys` | Disable restore-keys to avoid restoring stale caches | No | `'false'` | **๐Ÿ“– Need help choosing the right cache mode?** See the [Cache Strategy Guide](./CACHE-STRATEGY-GUIDE.md) for detailed recommendations. @@ -45,44 +47,49 @@ None ## What It Does 1. **Sets up Node.js** with the specified version (or uses asdf-vm) -2. **Caches asdf installations** (when using asdf-vm) to avoid re-downloading Node.js -3. **Configures GitHub packages registry** for @typeform scope -4. **Caches dependencies** with multi-layer strategy: +2. **Installs pnpm** via `pnpm/action-setup` when `package-manager: pnpm` (skipped if asdf manages it) +3. **Caches asdf installations** (when using asdf-vm) to avoid re-downloading Node.js +4. **Configures GitHub packages registry** for @typeform scope +5. **Caches dependencies** with multi-layer strategy: - `node_modules/` - Installed dependencies - - `~/.cache/yarn` - Yarn global cache (always enabled for better performance) + - `~/.cache/yarn` - Yarn global cache (yarn) / `~/.local/share/pnpm/store` - pnpm store - `~/.asdf/installs` - asdf tool installations (when using asdf) -5. **Uses restore-keys** for fallback caching when exact match not found -6. **Verifies cache integrity** on cache hit: +6. **Uses restore-keys** for fallback caching when exact match not found +7. **Verifies cache integrity** on cache hit: - Checks if `node_modules/` exists and has content - - Validates `.yarn-integrity` file presence + - Validates `.yarn-integrity` (yarn) / `.modules.yaml` (pnpm) file presence - Verifies workspace packages have `node_modules/` - Forces fresh install if cache is incomplete or corrupted -7. **Logs cache status** with detailed debug information -8. **Installs dependencies** automatically on cache miss or verification failure -9. **Smart install detection** on cache hit (after verification): - - **Yarn workspaces**: ALWAYS runs install (needs workspace symlink creation) - - **Lerna monorepos**: Runs install (needs lerna bootstrap) - - **Postinstall hooks**: Runs install (needs hook execution) - - **Note**: Even Turbo monorepos need install for workspace linking +8. **Logs cache status** with detailed debug information +9. **Installs dependencies** automatically on cache miss or verification failure +10. **Smart install detection** on cache hit (after verification): + - **Workspaces**: ALWAYS runs install (needs workspace symlink creation) + - **Lerna monorepos**: Runs install (needs lerna bootstrap) + - **Postinstall hooks**: Runs install (needs hook execution) + - **Note**: Even Turbo monorepos need install for workspace linking ## Cache Strategy -### Cache Key +### Cache Keys + +**yarn:** ``` -${{ runner.os }}-${{ runner.arch }}-yarn-${{ hashFiles('**/yarn.lock', '**/package.json') }} +${{ runner.os }}-${{ runner.arch }}-yarn-${{ hashFiles('**/yarn.lock', '**/package.json') }}- ``` -### Restore Keys (Fallback) +**pnpm:** ``` -${{ runner.os }}-${{ runner.arch }}-yarn- +${{ runner.os }}-${{ runner.arch }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml', '**/package.json') }}- ``` +Cache keys are isolated per package manager โ€” no cross-PM cache pollution. + This ensures: -- **Exact match**: Uses cached dependencies if yarn.lock or package.json unchanged +- **Exact match**: Uses cached dependencies if lockfile or package.json unchanged - **Partial match**: Uses most recent cache if dependencies changed - **Architecture-specific**: Prevents ARM/x86 cache conflicts -- **Monorepo support**: `**/yarn.lock` pattern handles nested workspaces -- **Consistent keys**: Removed `.tool-versions` dependency for reliability +- **Monorepo support**: `**` pattern handles nested workspaces +- **PM-isolated**: `yarn-` vs `pnpm-` prefix prevents cache collisions ### Stale Cache Cleanup (Restore-Key Fallback) @@ -160,12 +167,22 @@ act pull_request -j build ## Examples -### Basic Usage +### Basic Usage (yarn โ€” default) +```yaml +- uses: Typeform/.github/shared-actions/setup-node-with-cache@main + with: + GH_TOKEN: ${{ secrets.GH_TOKEN }} +``` + +### pnpm Usage ```yaml - uses: Typeform/.github/shared-actions/setup-node-with-cache@main with: GH_TOKEN: ${{ secrets.GH_TOKEN }} + package-manager: 'pnpm' + cache-mode: 'node_modules-only' # or 'full' for pnpm workspaces ``` +**Note**: pnpm callers must pass all commands explicitly in the workflow (`build-command: 'pnpm run dist'`, etc.) โ€” workflow input defaults use `yarn`. ### Custom Node Version ```yaml diff --git a/shared-actions/setup-node-with-cache/action.yml b/shared-actions/setup-node-with-cache/action.yml index b2567b91..83cc7087 100644 --- a/shared-actions/setup-node-with-cache/action.yml +++ b/shared-actions/setup-node-with-cache/action.yml @@ -24,13 +24,17 @@ inputs: description: 'Disable restore-keys to avoid restoring stale caches (forces exact key match)' required: false default: 'false' + package-manager: + description: 'Package manager to use: "yarn" or "pnpm"' + required: false + default: "yarn" runs: using: 'composite' steps: - # Cache 1: asdf installations (Node.js and Yarn binaries) + # Cache 1: asdf installations (Node.js and package manager binaries) # This cache is separate because it only changes when .tool-versions is updated, - # which happens infrequently (only when upgrading Node/Yarn versions). + # which happens infrequently (only when upgrading Node/package manager versions). # By caching it separately, we avoid re-downloading binaries on every dependency change. - name: Cache asdf installations if: ${{ inputs.use-asdf == 'true' && !env.ACT }} @@ -53,7 +57,11 @@ runs: shell: bash run: | asdf reshim nodejs - asdf reshim yarn + if [ "${{ inputs.package-manager }}" == "pnpm" ]; then + asdf reshim pnpm + else + asdf reshim yarn + fi - name: Set up Node.js with setup-node if: ${{ inputs.use-asdf != 'true' }} @@ -61,19 +69,34 @@ runs: with: node-version: ${{ inputs.node-version }} + # pnpm must be installed before Node setup so setup-node can wire pnpm's built-in cache. + # Skip when asdf manages pnpm via .tool-versions. + - name: Set up pnpm + if: ${{ inputs.package-manager == 'pnpm' && inputs.use-asdf != 'true' }} + uses: pnpm/action-setup@v4 + with: + run_install: false + - name: Get yarn cache directory path + if: ${{ inputs.package-manager != 'pnpm' }} id: yarn-cache-dir-path shell: bash run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT - + + - name: Get pnpm store path + if: ${{ inputs.package-manager == 'pnpm' }} + id: pnpm-store-path + shell: bash + run: echo "dir=$(pnpm store path --silent)" >> $GITHUB_OUTPUT + - name: Set GitHub packages registry shell: bash run: | npm config set '//npm.pkg.github.com/:_authToken' ${{ inputs.GH_TOKEN }} npm config set @typeform:registry https://npm.pkg.github.com/ - # Cache 2: yarn dependencies (node_modules and yarn global cache) - # This cache changes whenever package.json or yarn.lock is modified. + # Cache 2: package manager dependencies (node_modules and package-manager global cache) + # This cache changes whenever package.json or lockfile is modified. # It's separate from the asdf cache so that dependency updates don't invalidate # the cached Node.js binaries, making builds more efficient. # Note: restore-keys allows partial matches, which can restore stale caches. @@ -81,8 +104,9 @@ runs: # https://github.com/Typeform//actions/caches # Determine cache paths based on cache-mode input - - name: Set cache paths + - name: Set cache paths (yarn) id: cache-paths + if: ${{ inputs.package-manager != 'pnpm' }} shell: bash run: | CACHE_MODE="${{ inputs.cache-mode }}" @@ -114,9 +138,43 @@ runs: # Set cache key suffix based on mode echo "key-suffix=-$CACHE_MODE" >> $GITHUB_OUTPUT + + - name: Set cache paths (pnpm) + id: cache-paths-pnpm + if: ${{ inputs.package-manager == 'pnpm' }} + shell: bash + run: | + CACHE_MODE="${{ inputs.cache-mode }}" + PNPM_STORE="${{ steps.pnpm-store-path.outputs.dir }}" + + case "$CACHE_MODE" in + "node_modules-only") + echo "Cache mode: node_modules only (no pnpm store)" + PATHS="node_modules + apps/**/node_modules + packages/**/node_modules" + ;; + "pnpm-store-only"|"yarn-cache-only") + echo "Cache mode: pnpm store only (no node_modules)" + PATHS="$PNPM_STORE" + ;; + "full"|*) + echo "Cache mode: full (node_modules + pnpm store)" + PATHS="node_modules + apps/**/node_modules + packages/**/node_modules + $PNPM_STORE" + ;; + esac + + echo "paths<> $GITHUB_OUTPUT + echo "$PATHS" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + echo "key-suffix=-$CACHE_MODE" >> $GITHUB_OUTPUT - name: Get yarn cache - if: ${{ !env.ACT }} + if: ${{ !env.ACT && inputs.package-manager != 'pnpm' }} uses: actions/cache@v6 id: yarn-cache with: @@ -124,23 +182,42 @@ runs: key: ${{ runner.os }}-${{ runner.arch }}-yarn-${{ hashFiles('**/yarn.lock', '**/package.json') }}${{ steps.cache-paths.outputs.key-suffix }} restore-keys: ${{ inputs.disable-restore-keys != 'true' && format('{0}-{1}-yarn-', runner.os, runner.arch) || '' }} + - name: Get pnpm cache + if: ${{ !env.ACT && inputs.package-manager == 'pnpm' }} + uses: actions/cache@v6 + id: pnpm-cache + with: + path: ${{ steps.cache-paths-pnpm.outputs.paths }} + key: ${{ runner.os }}-${{ runner.arch }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml', '**/package.json') }}${{ steps.cache-paths-pnpm.outputs.key-suffix }} + restore-keys: ${{ inputs.disable-restore-keys != 'true' && format('{0}-{1}-pnpm-', runner.os, runner.arch) || '' }} + - name: Debug cache contents id: debug-cache - if: ${{ !env.ACT && steps.yarn-cache.outputs.cache-hit == 'true' }} + if: ${{ !env.ACT && (steps.yarn-cache.outputs.cache-hit == 'true' || steps.pnpm-cache.outputs.cache-hit == 'true') }} shell: bash run: | set +e # Don't exit on error - we want to continue even if find fails - + + PM="${{ inputs.package-manager }}" + echo "=== ๐Ÿ” Cache Debug Info ===" - echo "Cache key: ${{ runner.os }}-${{ runner.arch }}-yarn-${{ hashFiles('**/yarn.lock', '**/package.json') }}" + if [ "$PM" == "pnpm" ]; then + echo "Cache key: ${{ runner.os }}-${{ runner.arch }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml', '**/package.json') }}" + else + echo "Cache key: ${{ runner.os }}-${{ runner.arch }}-yarn-${{ hashFiles('**/yarn.lock', '**/package.json') }}" + fi echo "" - + echo "=== ๐Ÿ“ฆ Root node_modules Status ===" if [ -d "node_modules" ]; then MODULE_COUNT=$(ls -1 node_modules 2>/dev/null | wc -l | tr -d ' ') echo "โœ… node_modules exists" echo "๐Ÿ“Š Package count: $MODULE_COUNT" - echo "๐Ÿ”’ Yarn integrity: $([ -f 'node_modules/.yarn-integrity' ] && echo 'โœ… present' || echo 'โŒ missing')" + if [ "$PM" == "pnpm" ]; then + echo "๐Ÿ”’ pnpm integrity: $([ -f 'node_modules/.modules.yaml' ] && echo 'โœ… present' || echo 'โŒ missing')" + else + echo "๐Ÿ”’ Yarn integrity: $([ -f 'node_modules/.yarn-integrity' ] && echo 'โœ… present' || echo 'โŒ missing')" + fi else echo "โŒ node_modules directory not found" fi @@ -163,21 +240,23 @@ runs: set -e # Re-enable exit on error - name: Clean stale nested node_modules on restore-key fallback - if: ${{ !env.ACT && steps.yarn-cache.outputs.cache-hit != 'true' }} + if: ${{ !env.ACT && steps.yarn-cache.outputs.cache-hit != 'true' && steps.pnpm-cache.outputs.cache-hit != 'true' }} shell: bash run: | # When actions/cache reports cache-hit != 'true', it means either: # 1. Complete cache miss (no node_modules directory exists) # 2. Restore-key fallback (stale node_modules from a previous lockfile) # - # Case 2 is dangerous: yarn install --frozen-lockfile installs the correct + # Case 2 is dangerous: install --frozen-lockfile installs the correct # top-level packages but does NOT delete leftover nested node_modules/ dirs # from the stale cache. This causes version conflicts (e.g., @types/react@17 # persisting inside a dependency's node_modules after upgrading to React 18). # # Fix: if node_modules exists (restore-key fallback), purge all nested - # node_modules directories inside dependencies before yarn install. - + # node_modules directories inside dependencies before install. + + PM="${{ inputs.package-manager }}" + if [ -d "node_modules" ]; then echo "=== ๐Ÿงน Stale Cache Cleanup ===" echo "Cache was restored via restore-key fallback (not exact match)" @@ -197,26 +276,31 @@ runs: find packages/*/node_modules -mindepth 2 -name node_modules -type d -exec rm -rf {} + 2>/dev/null || true find apps/*/node_modules -mindepth 2 -name node_modules -type d -exec rm -rf {} + 2>/dev/null || true - echo "โœ… Cleaned nested node_modules โ€” yarn install will re-resolve them" + echo "โœ… Cleaned nested node_modules โ€” install will re-resolve them" else echo "โœ… No nested node_modules found โ€” nothing to clean" fi - # Remove .yarn-integrity so yarn install runs a full resolution - rm -f node_modules/.yarn-integrity - echo "๐Ÿ”’ Removed .yarn-integrity to force full resolution" + # Remove integrity file so install runs a full resolution + if [ "$PM" == "pnpm" ]; then + rm -f node_modules/.modules.yaml + echo "๐Ÿ”’ Removed .modules.yaml to force full resolution" + else + rm -f node_modules/.yarn-integrity + echo "๐Ÿ”’ Removed .yarn-integrity to force full resolution" + fi else echo "๐Ÿ“ญ No node_modules directory โ€” clean cache miss, nothing to clean" fi - - name: Check if yarn install needed on cache hit + - name: Check if install needed on cache hit id: check-install-needed if: ${{ !env.ACT }} shell: bash run: | set +e # Don't exit on error - we want to check all conditions - # Determine if we need to run yarn install even when cache hits. + # Determine if we need to run install even when cache hits. # # Why this matters: # - Cache restores node_modules, but doesn't run postinstall hooks @@ -226,8 +310,16 @@ runs: # # We detect scenarios that require yarn install on cache hit: + PM="${{ inputs.package-manager }}" NEEDS_INSTALL=false - CACHE_HIT="${{ steps.yarn-cache.outputs.cache-hit }}" + + if [ "$PM" == "pnpm" ]; then + CACHE_HIT="${{ steps.pnpm-cache.outputs.cache-hit }}" + INTEGRITY_FILE="node_modules/.modules.yaml" + else + CACHE_HIT="${{ steps.yarn-cache.outputs.cache-hit }}" + INTEGRITY_FILE="node_modules/.yarn-integrity" + fi # On cache hit, verify cache integrity before trusting it if [ "$CACHE_HIT" == "true" ]; then @@ -245,9 +337,9 @@ runs: fi fi - # Verification 2: Check yarn integrity file - if [ "$NEEDS_INSTALL" == "false" ] && [ ! -f "node_modules/.yarn-integrity" ]; then - echo "โš ๏ธ Missing .yarn-integrity file - cache may be incomplete, forcing install" + # Verification 2: Check integrity file + if [ "$NEEDS_INSTALL" == "false" ] && [ ! -f "$INTEGRITY_FILE" ]; then + echo "โš ๏ธ Missing $INTEGRITY_FILE - cache may be incomplete, forcing install" NEEDS_INSTALL=true fi @@ -270,19 +362,19 @@ runs: # Check for monorepo configurations that need install if [ "$NEEDS_INSTALL" == "false" ]; then - # 1. Yarn workspaces: Check if workspace node_modules are cached + # 1. Workspaces: Check if workspace node_modules are cached # If we cache workspace node_modules explicitly (apps/**/node_modules, packages/**/node_modules), - # then symlinks work correctly and we don't need to run yarn install. - # If workspace node_modules are missing, we must run yarn install to populate them. + # then symlinks work correctly and we don't need to run install. + # If workspace node_modules are missing, we must run install to populate them. if grep -q '"workspaces"' package.json 2>/dev/null; then # Check if workspace node_modules directories exist (were cached) WORKSPACE_MODULES=$(find packages/*/node_modules apps/*/node_modules -maxdepth 0 -type d 2>/dev/null | wc -l | tr -d ' ') if [ "$WORKSPACE_MODULES" -gt 0 ]; then - echo "โœ… Yarn workspaces detected with cached node_modules ($WORKSPACE_MODULES workspaces)" + echo "โœ… Workspaces detected with cached node_modules ($WORKSPACE_MODULES workspaces)" echo " โœ… Workspace symlinks preserved - install NOT needed" NEEDS_INSTALL=false else - echo "โš ๏ธ Yarn workspaces detected but workspace node_modules missing" + echo "โš ๏ธ Workspaces detected but workspace node_modules missing" echo " โš ๏ธ Install needed to populate workspace directories" NEEDS_INSTALL=true fi @@ -310,39 +402,68 @@ runs: if: ${{ !env.ACT }} shell: bash run: | + PM="${{ inputs.package-manager }}" + + if [ "$PM" == "pnpm" ]; then + CACHE_HIT="${{ steps.pnpm-cache.outputs.cache-hit }}" + CACHE_KEY="${{ runner.os }}-${{ runner.arch }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml', '**/package.json') }}" + INSTALL_CMD="pnpm install" + else + CACHE_HIT="${{ steps.yarn-cache.outputs.cache-hit }}" + CACHE_KEY="${{ runner.os }}-${{ runner.arch }}-yarn-${{ hashFiles('**/yarn.lock', '**/package.json') }}" + INSTALL_CMD="yarn install" + fi + echo "=== ๐Ÿ“Š Cache Status Summary ===" - if [ "${{ steps.yarn-cache.outputs.cache-hit }}" == "true" ]; then + if [ "$CACHE_HIT" == "true" ]; then echo "โœ… Cache HIT - Dependencies restored from cache" - echo "๐Ÿ“ฆ Cache key: ${{ runner.os }}-${{ runner.arch }}-yarn-${{ hashFiles('**/yarn.lock', '**/package.json') }}" + echo "๐Ÿ“ฆ Cache key: $CACHE_KEY" echo "" if [ "${{ steps.check-install-needed.outputs.needs-install }}" == "true" ]; then - echo "๐Ÿ”„ Decision: WILL run yarn install" + echo "๐Ÿ”„ Decision: WILL run $INSTALL_CMD" echo "Reasons: Cache verification failed, workspace linking needed, or postinstall hooks detected" else - echo "โšก Decision: SKIP yarn install" + echo "โšก Decision: SKIP $INSTALL_CMD" echo "Reason: Cache verified and no workspace linking or postinstall hooks required" fi else echo "โŒ Cache MISS - Fresh installation required" - echo "๐Ÿ” Looking for key: ${{ runner.os }}-${{ runner.arch }}-yarn-${{ hashFiles('**/yarn.lock', '**/package.json') }}" + echo "๐Ÿ” Looking for key: $CACHE_KEY" echo "" - echo "๐Ÿ”„ Decision: WILL run yarn install" + echo "๐Ÿ”„ Decision: WILL run $INSTALL_CMD" fi echo "" - - name: Install Node.js dependencies - if: ${{ !env.ACT && (steps.yarn-cache.outputs.cache-hit != 'true' || steps.check-install-needed.outputs.needs-install == 'true') }} + - name: Install Node.js dependencies (yarn) + if: ${{ !env.ACT && inputs.package-manager != 'pnpm' && (steps.yarn-cache.outputs.cache-hit != 'true' || steps.check-install-needed.outputs.needs-install == 'true') }} shell: bash run: yarn install --frozen-lockfile --ignore-scripts env: NODE_AUTH_TOKEN: ${{ inputs.GH_TOKEN }} PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 'true' - - name: Run root postinstall script - if: ${{ !env.ACT && steps.check-install-needed.outputs.has-postinstall == 'true' }} + - name: Run root postinstall script (yarn) + if: ${{ !env.ACT && inputs.package-manager != 'pnpm' && steps.check-install-needed.outputs.has-postinstall == 'true' }} shell: bash run: | echo "๐Ÿ”ง Running root postinstall script" yarn run postinstall env: NODE_AUTH_TOKEN: ${{ inputs.GH_TOKEN }} + + - name: Install Node.js dependencies (pnpm) + if: ${{ !env.ACT && inputs.package-manager == 'pnpm' && (steps.pnpm-cache.outputs.cache-hit != 'true' || steps.check-install-needed.outputs.needs-install == 'true') }} + shell: bash + run: pnpm install --frozen-lockfile --ignore-scripts + env: + NODE_AUTH_TOKEN: ${{ inputs.GH_TOKEN }} + PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: "true" + + - name: Run root postinstall script (pnpm) + if: ${{ !env.ACT && inputs.package-manager == 'pnpm' && steps.check-install-needed.outputs.has-postinstall == 'true' }} + shell: bash + run: | + echo "๐Ÿ”ง Running root postinstall script" + pnpm run postinstall + env: + NODE_AUTH_TOKEN: ${{ inputs.GH_TOKEN }} diff --git a/shared-actions/setup-playwright/action.yml b/shared-actions/setup-playwright/action.yml index 25b06b22..c9b5aab1 100644 --- a/shared-actions/setup-playwright/action.yml +++ b/shared-actions/setup-playwright/action.yml @@ -1,6 +1,12 @@ name: 'Setup Playwright with Caching' description: 'Install and cache Playwright browsers for E2E testing' +inputs: + package-manager: + description: 'Package manager to use: "yarn" or "pnpm"' + required: false + default: 'yarn' + runs: using: 'composite' steps: @@ -31,15 +37,15 @@ runs: if: steps.playwright-cache.outputs.cache-hit != 'true' shell: bash run: | - if command -v yarn &> /dev/null && [ -f "package.json" ]; then - # Check if playwright:install script exists + PM="${{ inputs.package-manager }}" + if [ -f "package.json" ]; then if grep -q '"playwright:install"' package.json; then - echo "๐Ÿ“ฆ Installing Playwright browsers via yarn script..." - yarn playwright:install + echo "๐Ÿ“ฆ Installing Playwright browsers via $PM script..." + $PM run playwright:install else - echo "๐Ÿ“ฆ Installing Playwright browsers directly..." - yarn playwright install --with-deps + echo "๐Ÿ“ฆ Installing Playwright browsers directly via $PM..." + $PM exec playwright install --with-deps fi else - echo "โš ๏ธ yarn or package.json not found, skipping Playwright installation" - fi \ No newline at end of file + echo "โš ๏ธ package.json not found, skipping Playwright installation" + fi diff --git a/workflow-templates/library-checks-and-release.yml b/workflow-templates/library-checks-and-release.yml index 09bebde2..2e3d6a04 100644 --- a/workflow-templates/library-checks-and-release.yml +++ b/workflow-templates/library-checks-and-release.yml @@ -22,27 +22,14 @@ jobs: steps: - name: Check out Git repository uses: actions/checkout@v7 - - - name: Set up Node.js - uses: actions/setup-node@v6 with: - node-version: 12 - - - name: Set GitHub packages registry - run: | - npm config set '//npm.pkg.github.com/:_authToken' ${{ secrets.GH_TOKEN }} - npm config set @typeform:registry https://npm.pkg.github.com/ + fetch-depth: 0 - - name: Get yarn cache - uses: actions/cache@v6 - id: yarn-cache + - name: Setup Node with Cache + uses: Typeform/.github/shared-actions/setup-node-with-cache@main with: - path: node_modules - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}-${{ hashFiles('.github/workflows/**.yml') }} - - - name: Install Node.js dependencies - if: steps.yarn-cache.outputs.cache-hit != 'true' - run: yarn install --frozen-lockfile + node-version: '20' + GH_TOKEN: ${{ secrets.GH_TOKEN }} - name: Run linting and tests run: npx npm-run-all -p test lint