name: release

on:
  push:
    paths:
      - ".changeset/**"
      - ".github/**"
      - "packages/**"
      - "package.json"
      - "package-lock.json"
    branches:
      - main

jobs:
  release:
    name: Release
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: 20
          cache: npm

      - name: Install dependencies
        run: npm ci

      - name: Download Turborepo cache
        uses: actions/cache@v3
        with:
          path: node_modules/.cache/turbo
          key: ${{ runner.os }}-node-20-turbo-${{ hashFiles('node_modules/.cache/turbo') }}
          restore-keys: |
            ${{ runner.os }}-node-20-turbo-

      - name: Check types
        run: npm run tsc

      - name: Build packages
        run: npm run build

      - name: Run tests
        run: npm run test -- --ci --maxWorkers=2

      - name: Create Release Pull Request or Publish Packages
        id: changesets
        uses: changesets/action@v1
        with:
          version: npm run changeset:version
          publish: npm run changeset:publish
          commit: "chore(release): version packages"
        env:
          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Pack Dist Archive
        run: npm run build:archive

      - name: Upload Release Assets
        uses: actions/github-script@v7
        with:
          script: |
            const fs = require("fs");

            const { owner, repo } = context.repo;
            const distFile = "dist.zip";
            const assetName = "jspsych.zip";

            /**
            * Returns all recent releases without a dist archive asset, up to (excluding) the first release
            * that has a dist archive asset.
            **/
            async function fetchReleasesWithoutDistArchive() {
              const releasesWithoutDistArchive = [];

              for await (const response of github.paginate.iterator(github.rest.repos.listReleases, {
                owner,
                repo,
              })) {
                for (const release of response.data) {
                  if (!release.assets.some((asset) => asset.name === assetName)) {
                    releasesWithoutDistArchive.push(release);
                  } else {
                    return releasesWithoutDistArchive;
                  }
                }
              }

              return releasesWithoutDistArchive;
            }

            console.log("Collecting recent releases that do not have a dist archive");
            const releasesWithoutDistArchive = await fetchReleasesWithoutDistArchive();

            if (releasesWithoutDistArchive.length === 0) {
              console.log("The most recent release already has a dist archive. Skipping asset upload.");
              return;
            }

            console.log(`Found ${releasesWithoutDistArchive.length} recent releases without a dist archive.`);

            const distFileSize = fs.statSync(distFile).size;
            const distFileContents = fs.readFileSync(distFile);

            // Upload dist archive for each release without a dist archive, in reverse order so re-running an
            // aborted or failed job will pick up where it left off last time
            for (const release of releasesWithoutDistArchive.reverse()) {
              console.log(`Uploading dist archive release asset for ${release.tag_name}`);

              // https://octokit.github.io/rest.js/v20#repos-upload-release-asset
              await github.rest.repos.uploadReleaseAsset({
                owner,
                repo,
                release_id: release.id,
                name: assetName,
                label: "Dist archive (zip)",
                headers: {
                  "content-type": "application/zip",
                  "content-length": distFileSize,
                },
                data: distFileContents,
              });
            }
