feat(contractor-invoices): support skipping a scheduled invoice and previewing a draft invoice PDF #1981
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: PR Checks | |
| on: | |
| pull_request: | |
| branches: | |
| - main | |
| - release/* | |
| jobs: | |
| lint-and-format: | |
| name: Lint and Format | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 | |
| with: | |
| persist-credentials: false | |
| - name: Use Node.js | |
| uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6 | |
| with: | |
| node-version: '24.15.0' | |
| - name: Install dependencies | |
| run: npm ci --include=optional --ignore-scripts | |
| - name: Run format check | |
| run: npm run check-format | |
| - name: Run lint | |
| run: npm run lint | |
| - name: Run type check | |
| run: npm run type-check | |
| build-and-exports: | |
| name: Build and Check Exports | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 | |
| with: | |
| persist-credentials: false | |
| - name: Use Node.js | |
| uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6 | |
| with: | |
| node-version: '24.15.0' | |
| - name: Install dependencies | |
| run: npm ci --include=optional --ignore-scripts | |
| - name: Build | |
| run: npm run build | |
| - name: Check exports | |
| run: npm run check-exports | |
| tests: | |
| name: Tests with Coverage | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 | |
| with: | |
| persist-credentials: false | |
| - name: Use Node.js | |
| uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6 | |
| with: | |
| node-version: '24.15.0' | |
| - name: Install dependencies | |
| run: npm ci --include=optional --ignore-scripts | |
| - name: Run tests with coverage | |
| run: npm run test:coverage | |
| - name: Create output directory | |
| run: mkdir -p out | |
| - name: Extract current coverage | |
| run: npm run coverage:extract -- --output out/current-coverage.json | |
| - name: Upload current coverage | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 | |
| with: | |
| name: current-coverage | |
| path: out/current-coverage.json | |
| retention-days: 1 | |
| base-coverage: | |
| name: Generate Base Coverage | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 | |
| with: | |
| persist-credentials: false | |
| - name: Check if coverage scripts exist | |
| id: check-scripts | |
| run: | | |
| if [ -f "scripts/compare-coverage.ts" ] && [ -f "scripts/extract-coverage.ts" ]; then | |
| echo "exists=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "exists=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Check cache for base coverage | |
| if: steps.check-scripts.outputs.exists == 'true' | |
| id: cache-base | |
| uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5 | |
| with: | |
| path: base-coverage.json | |
| key: coverage-${{ github.base_ref }}-${{ github.event.pull_request.base.sha }} | |
| - name: Checkout base branch | |
| if: steps.check-scripts.outputs.exists == 'true' && steps.cache-base.outputs.cache-hit != 'true' | |
| run: | | |
| git fetch origin ${GITHUB_BASE_REF} | |
| git checkout origin/${GITHUB_BASE_REF} | |
| - name: Check if coverage scripts exist in base branch | |
| if: steps.check-scripts.outputs.exists == 'true' && steps.cache-base.outputs.cache-hit != 'true' | |
| id: check-base-scripts | |
| run: | | |
| if [ -f "scripts/compare-coverage.ts" ] && [ -f "scripts/extract-coverage.ts" ]; then | |
| echo "exists=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "exists=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Use Node.js | |
| if: steps.check-scripts.outputs.exists == 'true' && steps.cache-base.outputs.cache-hit != 'true' && steps.check-base-scripts.outputs.exists == 'true' | |
| uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6 | |
| with: | |
| node-version: '24.15.0' | |
| - name: Install dependencies | |
| if: steps.check-scripts.outputs.exists == 'true' && steps.cache-base.outputs.cache-hit != 'true' && steps.check-base-scripts.outputs.exists == 'true' | |
| run: npm ci --include=optional --ignore-scripts | |
| - name: Run tests with coverage | |
| if: steps.check-scripts.outputs.exists == 'true' && steps.cache-base.outputs.cache-hit != 'true' && steps.check-base-scripts.outputs.exists == 'true' | |
| run: npm run test:coverage | |
| - name: Extract base coverage | |
| if: steps.check-scripts.outputs.exists == 'true' && steps.cache-base.outputs.cache-hit != 'true' && steps.check-base-scripts.outputs.exists == 'true' | |
| run: npm run coverage:extract -- --output base-coverage.json | |
| - name: Upload base coverage | |
| if: steps.check-scripts.outputs.exists == 'true' | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 | |
| with: | |
| name: base-coverage | |
| path: base-coverage.json | |
| retention-days: 1 | |
| example-checks: | |
| name: Example App Checks | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 | |
| with: | |
| persist-credentials: false | |
| - name: Use Node.js | |
| uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6 | |
| with: | |
| node-version: '24.15.0' | |
| - name: Install main package dependencies | |
| run: npm ci --include=optional --ignore-scripts | |
| - name: Build main package | |
| run: npm run build | |
| - name: Install example dependencies | |
| working-directory: ./example | |
| run: npm ci --include=optional --ignore-scripts | |
| - name: Lint example app | |
| working-directory: ./example | |
| run: npm run lint | |
| - name: Type-check example app | |
| working-directory: ./example | |
| run: npm run type-check | |
| coverage-compare: | |
| name: Compare Coverage | |
| runs-on: ubuntu-latest | |
| needs: [tests, base-coverage] | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 | |
| with: | |
| persist-credentials: false | |
| - name: Use Node.js | |
| uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6 | |
| with: | |
| node-version: '24.15.0' | |
| - name: Install dependencies | |
| run: npm ci --include=optional --ignore-scripts | |
| - name: Create output directory | |
| run: mkdir -p out | |
| - name: Download current coverage | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 | |
| with: | |
| name: current-coverage | |
| path: out | |
| - name: Download base coverage | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 | |
| with: | |
| name: base-coverage | |
| path: out | |
| continue-on-error: true | |
| - name: Compare coverage | |
| id: compare | |
| run: | | |
| # Run comparison (handles missing base file gracefully) | |
| npm exec tsx scripts/compare-coverage.ts out/current-coverage.json out/base-coverage.json > out/coverage-report.md 2>&1 || true | |
| # Set output for PR comment | |
| echo "report<<EOF" >> $GITHUB_OUTPUT | |
| cat out/coverage-report.md >> $GITHUB_OUTPUT | |
| echo "EOF" >> $GITHUB_OUTPUT | |
| - name: Post PR comment | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const report = process.env.COVERAGE_REPORT; | |
| // Find existing comment | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| }); | |
| const botComment = comments.find(comment => | |
| comment.user.type === 'Bot' && | |
| comment.body.includes('📊 Coverage Report') | |
| ); | |
| const commentBody = report; | |
| if (botComment) { | |
| // Update existing comment | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: botComment.id, | |
| body: commentBody | |
| }); | |
| } else { | |
| // Create new comment | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body: commentBody | |
| }); | |
| } | |
| env: | |
| COVERAGE_REPORT: ${{ steps.compare.outputs.report }} | |
| - name: Upload coverage analysis | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 | |
| if: always() | |
| with: | |
| name: coverage-analysis | |
| path: | | |
| out/current-coverage.json | |
| out/base-coverage.json | |
| out/coverage-report.md | |
| retention-days: 30 | |
| deploy-remote-flows: | |
| name: Deploy Remote Flows Preview | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 | |
| with: | |
| persist-credentials: false | |
| - name: Deploy remote-flows to vercel | |
| id: vercel-remote-flows | |
| uses: amondnet/vercel-action@de09aeac2ace6599ec9b11ef87558759a496bac4 # v42.3.0 | |
| with: | |
| vercel-token: ${{ secrets.VERCEL_TOKEN }} | |
| vercel-org-id: ${{ secrets.VERCEL_ORG_ID }} | |
| vercel-project-id: ${{ secrets.VERCEL_REMOTE_FLOWS_PROJECT_ID }} | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| github-comment: true | |
| scope: ${{ secrets.VERCEL_ORG_ID }} | |
| - name: Save deployment URL | |
| run: | | |
| echo "${STEPS_VERCEL_REMOTE_FLOWS_OUTPUTS_PREVIEW_URL}" > remote-flows-url.txt | |
| env: | |
| STEPS_VERCEL_REMOTE_FLOWS_OUTPUTS_PREVIEW_URL: ${{ steps.vercel-remote-flows.outputs.preview-url }} | |
| - name: Upload deployment URL | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 | |
| with: | |
| name: remote-flows-url | |
| path: remote-flows-url.txt | |
| deploy-cost-calculator: | |
| name: Deploy Cost Calculator Preview | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 | |
| with: | |
| persist-credentials: false | |
| - name: Deploy cost-calculator to vercel | |
| uses: amondnet/vercel-action@de09aeac2ace6599ec9b11ef87558759a496bac4 # v42.3.0 | |
| with: | |
| vercel-token: ${{ secrets.VERCEL_TOKEN }} | |
| vercel-org-id: ${{ secrets.VERCEL_ORG_ID }} | |
| vercel-project-id: ${{ secrets.VERCEL_COST_CALCULATOR_PROJECT_ID }} | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| github-comment: true | |
| scope: ${{ secrets.VERCEL_ORG_ID }} | |
| e2e-tests: | |
| name: E2E Tests | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| needs: | |
| [ | |
| lint-and-format, | |
| build-and-exports, | |
| tests, | |
| example-checks, | |
| deploy-remote-flows, | |
| ] | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 | |
| with: | |
| persist-credentials: false | |
| - name: Use Node.js | |
| uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6 | |
| with: | |
| node-version: '24.15.0' | |
| - name: Install main package dependencies | |
| run: npm ci --include=optional --ignore-scripts | |
| - name: Install example/ dependencies | |
| working-directory: ./example | |
| run: npm ci --include=optional --ignore-scripts | |
| - name: Get Playwright version | |
| id: playwright-version | |
| working-directory: ./example | |
| run: echo "version=$(node -p "require('./package.json').devDependencies['@playwright/test']")" >> $GITHUB_OUTPUT | |
| - name: Cache Playwright browsers | |
| uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5 | |
| id: playwright-cache | |
| with: | |
| path: ~/.cache/ms-playwright | |
| key: playwright-${{ runner.os }}-${{ steps.playwright-version.outputs.version }} | |
| - name: Install Playwright system dependencies | |
| working-directory: ./example | |
| timeout-minutes: 5 | |
| run: | | |
| # Install system dependencies using Playwright's built-in command | |
| # This ensures all OS-specific and browser-specific deps are installed | |
| # Retry with backoff in case apt-get has issues | |
| for i in 1 2 3; do | |
| echo "Installing system dependencies (attempt $i of 3)" | |
| if npx playwright install-deps chromium; then | |
| echo "Successfully installed system dependencies" | |
| exit 0 | |
| fi | |
| if [ $i -lt 3 ]; then | |
| echo "System dependency installation failed, waiting $((i * 10)) seconds..." | |
| sleep $((i * 10)) | |
| fi | |
| done | |
| echo "Failed to install system dependencies after 3 attempts" | |
| exit 1 | |
| - name: Install Playwright browsers | |
| if: steps.playwright-cache.outputs.cache-hit != 'true' | |
| working-directory: ./example | |
| timeout-minutes: 5 | |
| run: | | |
| # Install browser binaries only (deps already installed above) | |
| # Retry up to 3 times with exponential backoff | |
| for i in 1 2 3; do | |
| echo "Attempt $i of 3" | |
| if npx playwright install chromium; then | |
| echo "Successfully installed Playwright browsers" | |
| exit 0 | |
| fi | |
| if [ $i -lt 3 ]; then | |
| echo "Installation failed, waiting $((i * 10)) seconds before retry..." | |
| sleep $((i * 10)) | |
| fi | |
| done | |
| echo "Failed to install Playwright browsers after 3 attempts" | |
| exit 1 | |
| - name: Download remote-flows URL | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 | |
| with: | |
| name: remote-flows-url | |
| - name: Set deployment URLs | |
| id: set-urls | |
| run: | | |
| echo "REMOTE_FLOWS_URL=$(cat remote-flows-url.txt)" >> $GITHUB_ENV | |
| - name: Run Playwright tests | |
| env: | |
| BASE_URL: ${{ env.REMOTE_FLOWS_URL }} | |
| VERCEL_BYPASS_TOKEN: ${{ secrets.VERCEL_BYPASS_TOKEN }} | |
| DEBUG: pw:api | |
| working-directory: ./example | |
| run: npx playwright test --project=chromium | |
| - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 | |
| if: ${{ !cancelled() }} | |
| with: | |
| name: playwright-report | |
| path: playwright-report/ | |
| retention-days: 30 |