diff --git a/.github/workflows/canary-release-pr.WIP b/.github/workflows/canary-release-pr.WIP new file mode 100644 index 000000000000..0c5948948959 --- /dev/null +++ b/.github/workflows/canary-release-pr.WIP @@ -0,0 +1,88 @@ +name: Publish canary release of PR +run-name: 'Canary release: PR #${{ github.event.pull_request.number }} at ${{ github.sha }}' + +on: + pull_request: + types: [opened, synchronize, reopened] + branches: + - next + +env: + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 + PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1 + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number }} + cancel-in-progress: true + +permissions: + pull-requests: write + +jobs: + release-canary: + name: Release canary version + runs-on: ubuntu-latest + environment: canary-release + defaults: + run: + working-directory: scripts + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '16' + + - name: Cache dependencies + uses: actions/cache@v3 + with: + path: | + ~/.yarn/berry/cache + key: yarn-v1-${{ hashFiles('scripts/yarn.lock') }}-${{ hashFiles('code/yarn.lock') }} + restore-keys: | + yarn-v1-${{ hashFiles('scripts/yarn.lock') }}-${{ hashFiles('code/yarn.lock') }} + yarn-v1-${{ hashFiles('scripts/yarn.lock') }} + yarn-v1 + + - name: Install dependencies + working-directory: . + run: yarn task --task=install --start-from=install + + - name: Set version + id: version + run: | + SHORT_SHA=$(git rev-parse --short ${{ github.sha }}) + yarn release:version --release-type prerelease --pre-id pr-${{ github.event.pull_request.number }}-$SHORT_SHA --verbose + + - name: Publish v${{ steps.version.outputs.next-version }} + env: + YARN_NPM_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: yarn release:publish --tag pr-${{ github.event.pull_request.number }} --verbose + + - name: Create comment on PR + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh pr comment ${{ github.event.pull_request.number }}\ + --repo "${{github.repository }}"\ + --body "πŸš€ This pull request has been published as version \`${{ steps.version.outputs.next-version }}\` and with the tag \`pr-${{ github.event.pull_request.number }}\`. + [You can see it on the npm registry here](https://npmjs.com/package/@storybook/cli/v/${{ steps.version.outputs.next-version }})". + + - name: Create failing comment on PR + if: failure() + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh pr comment ${{ github.event.pull_request.number }}\ + --repo "${{github.repository }}"\ + --body "Failed to publish canary version of this pul request. See the failed workflow run at See run at: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" + + # - name: Report failure to Discord + # if: failure() + # env: + # DISCORD_WEBHOOK: ${{ secrets.DISCORD_MONITORING_URL }} + # uses: Ilshidur/action-discord@master + # with: + # args: 'The GitHub Action for publishing version ${{ steps.version.outputs.current-version }} (triggered by ${{ github.triggering_actor }}) failed! See run at: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}' diff --git a/.github/workflows/check-release-tasks.yml b/.github/workflows/check-release-tasks.yml new file mode 100644 index 000000000000..4848ae35f07e --- /dev/null +++ b/.github/workflows/check-release-tasks.yml @@ -0,0 +1,19 @@ +name: Check release tasks +run-name: 'Check tasks for "${{ github.event.pull_request.title }}" PR' + +on: + pull_request: + types: + - opened + - edited + branches: + - 'latest-release' + - 'next-release' + +jobs: + task-check: + runs-on: ubuntu-latest + steps: + - uses: chromaui/task-completed-checker-action@main + with: + repo-token: '${{ secrets.GITHUB_TOKEN }}' diff --git a/.github/workflows/danger-js.yml b/.github/workflows/danger-js.yml index edeb4aaad03a..40b6466fd0c9 100644 --- a/.github/workflows/danger-js.yml +++ b/.github/workflows/danger-js.yml @@ -1,6 +1,6 @@ on: pull_request: - types: [opened, synchronize, reopened, labeled, unlabeled] + types: [opened, synchronize, reopened, labeled, unlabeled, edited] name: Danger JS jobs: @@ -13,7 +13,7 @@ jobs: node-version: '16' - uses: actions/checkout@v3 - name: Danger JS - uses: danger/danger-js@10.9.0 + uses: danger/danger-js@11.2.6 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: diff --git a/.github/workflows/prepare-patch-release.yml b/.github/workflows/prepare-patch-release.yml new file mode 100644 index 000000000000..d5a8ca655af6 --- /dev/null +++ b/.github/workflows/prepare-patch-release.yml @@ -0,0 +1,175 @@ +name: Prepare patch PR +run-name: Prepare patch PR, triggered by ${{ github.triggering_actor }} + +on: + push: + branches: + - next + workflow_dispatch: + +env: + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 + PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1 + +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: true + +jobs: + prepare-patch-pull-request: + name: Prepare patch pull request + runs-on: ubuntu-latest + defaults: + run: + working-directory: scripts + steps: + - name: Checkout main + uses: actions/checkout@v3 + with: + ref: main + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '16' + + - name: Cache dependencies + uses: actions/cache@v3 + with: + path: | + ~/.yarn/berry/cache + key: yarn-v1-${{ hashFiles('scripts/yarn.lock') }}-${{ hashFiles('code/yarn.lock') }} + restore-keys: | + yarn-v1-${{ hashFiles('scripts/yarn.lock') }}-${{ hashFiles('code/yarn.lock') }} + yarn-v1-${{ hashFiles('scripts/yarn.lock') }} + yarn-v1 + + - name: Install Dependencies + working-directory: . + run: | + yarn task --task=install + + - name: Check if pull request is frozen + if: github.event_name != 'workflow_dispatch' + id: check-frozen + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: yarn release:is-pr-frozen + + - name: Cancel when frozen + if: steps.check-frozen.outputs.frozen == 'true' && github.event_name != 'workflow_dispatch' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # From https://stackoverflow.com/a/75809743 + run: | + gh run cancel ${{ github.run_id }} + gh run watch ${{ github.run_id }} + + - name: Check for unreleased changes + id: unreleased-changes + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: yarn release:unreleased-changes-exists --unpicked-patches + + - name: Fetch next branch + run: + # depth needs to be set to a high enough number that it will contain all the merge commits to cherry-pick + # as of May 2023, the whole repo had 55K commits + git fetch --depth=2000 origin next + + - name: Pick patches + id: pick-patches + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + git config --global user.name 'github-actions[bot]' + git config --global user.email 'github-actions[bot]@users.noreply.github.com' + yarn release:pick-patches + + - name: Bump version + id: bump-version + if: steps.unreleased-changes.outputs.has-changes-to-release == 'true' + run: | + yarn release:version --release-type patch --verbose + + # We need the current version to set the branch name, even when not bumping the version + - name: Get current version + id: current-version + if: steps.unreleased-changes.outputs.has-changes-to-release == 'false' + run: | + yarn release:get-current-version --verbose + + - name: Set version output + id: versions + run: | + echo "current=${{ steps.bump-version.outputs.current-version || steps.current-version.outputs.current-version }}" >> "$GITHUB_OUTPUT" + echo "next=${{ steps.bump-version.outputs.next-version || steps.current-version.outputs.current-version }}" >> "$GITHUB_OUTPUT" + + - name: Write changelog + if: steps.unreleased-changes.outputs.has-changes-to-release == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + yarn release:write-changelog ${{ steps.versions.outputs.next }} --unpicked-patches --verbose + + - name: 'Commit changes to branch: version-patch-from-${{ steps.versions.outputs.current }}' + working-directory: . + run: | + git config --global user.name 'github-actions[bot]' + git config --global user.email 'github-actions[bot]@users.noreply.github.com' + git checkout -b version-patch-from-${{ steps.versions.outputs.current }} + git add . + git commit -m "Bump version from ${{ steps.versions.outputs.current }} to ${{ steps.versions.outputs.next }}" || true + git push --force origin version-patch-from-${{ steps.versions.outputs.current }} + + - name: Generate PR description + id: description + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: yarn release:generate-pr-description --unpicked-patches --manual-cherry-picks='${{ steps.pick-patches.outputs.failed-cherry-picks }}' ${{ steps.unreleased-changes.outputs.has-changes-to-release == 'true' && format('{0}={1} {2}={3}', '--current-version', steps.versions.outputs.current, '--next-version', steps.versions.outputs.next) || '' }} --verbose + + - name: Create or update pull request with release + if: steps.unreleased-changes.outputs.has-changes-to-release == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + if PR_STATE=$(gh pr view --json state --jq .state 2>/dev/null) && [[ -n "$PR_STATE" && "$PR_STATE" == *"OPEN"* ]]; then + gh pr edit \ + --repo "${{github.repository }}" \ + --title "Bump version on \`main\`: patch from ${{ steps.versions.outputs.current }} to ${{ steps.versions.outputs.next }}" \ + --body "${{ steps.description.outputs.description }}" + else + gh pr create \ + --repo "${{github.repository }}" \ + --title "Bump version on \`main\`: patch from ${{ steps.versions.outputs.current }} to ${{ steps.versions.outputs.next }}" \ + --base latest-release \ + --head version-patch-from-${{ steps.versions.outputs.current }} \ + --body "${{ steps.description.outputs.description }}" + fi + + - name: Create or update pull request without release + if: steps.unreleased-changes.outputs.has-changes-to-release == 'false' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + if PR_STATE=$(gh pr view --json state --jq .state 2>/dev/null) && [[ -n "$PR_STATE" && "$PR_STATE" == *"OPEN"* ]]; then + gh pr edit \ + --repo "${{github.repository }}"\ + --title "Merge patches to \`main\`" \ + --body "${{ steps.description.outputs.description }}" + else + gh pr create \ + --repo "${{github.repository }}"\ + --title "Merge patches to \`main\`" \ + --base latest-release \ + --head version-patch-from-${{ steps.versions.outputs.current }} \ + --body "${{ steps.description.outputs.description }}" + fi + + - name: Report job failure to Discord + if: failure() + env: + DISCORD_WEBHOOK: ${{ secrets.DISCORD_MONITORING_URL }} + uses: Ilshidur/action-discord@master + with: + args: 'The GitHub Action for preparing the release pull request bumping from v${{ steps.versions.outputs.current }} to v${{ steps.versions.outputs.next }} (triggered by ${{ github.triggering_actor }}) failed! See run at: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}' diff --git a/.github/workflows/prepare-prerelease.yml b/.github/workflows/prepare-prerelease.yml new file mode 100644 index 000000000000..932c4b31f64f --- /dev/null +++ b/.github/workflows/prepare-prerelease.yml @@ -0,0 +1,159 @@ +name: Prepare prerelease PR +run-name: Prepare prerelease PR, triggered by ${{ github.triggering_actor }} + +on: + push: + branches: + - next + workflow_dispatch: + inputs: + release-type: + description: 'Which release type to use for bumping the version' + required: true + default: 'prerelease' + type: choice + options: + - prerelease + - prepatch + - preminor + - premajor + - patch + - minor + - major + pre-id: + description: For prerelease versions, what prerelease identifier to use, eg. 'alpha', 'beta', 'rc' + required: false + type: string + +env: + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 + PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1 + +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: true + +jobs: + prepare-prerelease-pull-request: + name: Prepare prerelease pull request + runs-on: ubuntu-latest + defaults: + run: + working-directory: scripts + steps: + - name: Checkout next + uses: actions/checkout@v3 + with: + ref: next + # this needs to be set to a high enough number that it will contain the last version tag + # as of May 2023, the whole repo had 55K commits + fetch-depth: 10000 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '16' + + - name: Cache dependencies + uses: actions/cache@v3 + with: + path: | + ~/.yarn/berry/cache + key: yarn-v1-${{ hashFiles('scripts/yarn.lock') }}-${{ hashFiles('code/yarn.lock') }} + restore-keys: | + yarn-v1-${{ hashFiles('scripts/yarn.lock') }}-${{ hashFiles('code/yarn.lock') }} + yarn-v1-${{ hashFiles('scripts/yarn.lock') }} + yarn-v1 + + - name: Install Dependencies + working-directory: . + run: | + yarn task --task=install + + - name: Check if pull request is frozen + if: github.event_name != 'workflow_dispatch' + id: check-frozen + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: yarn release:is-pr-frozen + + - name: Cancel when frozen + if: steps.check-frozen.outputs.frozen == 'true' && github.event_name != 'workflow_dispatch' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # From https://stackoverflow.com/a/75809743 + run: | + gh run cancel ${{ github.run_id }} + gh run watch ${{ github.run_id }} + + # tags are needed to get changes and changelog generation + - name: Fetch git tags + run: git fetch --tags origin + + - name: Check for unreleased changes + id: unreleased-changes + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: yarn release:unreleased-changes-exists + + - name: Cancel when no release necessary + if: steps.unreleased-changes.outputs.has-changes-to-release == 'false' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # From https://stackoverflow.com/a/75809743 + run: | + gh run cancel ${{ github.run_id }} + gh run watch ${{ github.run_id }} + + - name: Bump version + id: bump-version + run: | + yarn release:version --release-type ${{ inputs.release-type || 'prerelease' }} ${{ inputs.pre-id && format('{0} {1}', '--pre-id', inputs.pre-id) || '' }} --verbose + + - name: Write changelog + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + yarn release:write-changelog ${{ steps.bump-version.outputs.next-version }} --verbose + + - name: 'Commit changes to branch: version-prerelease-from-${{ steps.bump-version.outputs.current-version }}' + working-directory: . + run: | + git config --global user.name 'github-actions[bot]' + git config --global user.email 'github-actions[bot]@users.noreply.github.com' + git checkout -b version-prerelease-from-${{ steps.bump-version.outputs.current-version }} + git add . + git commit -m "Bump version from ${{ steps.bump-version.outputs.current-version }} to ${{ steps.bump-version.outputs.next-version }}" || true + git push --force origin version-prerelease-from-${{ steps.bump-version.outputs.current-version }} + + - name: Generate PR description + id: description + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: yarn release:generate-pr-description --current-version ${{ steps.bump-version.outputs.current-version }} --next-version ${{ steps.bump-version.outputs.next-version }} --verbose + + - name: Create or update pull request + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + if PR_STATE=$(gh pr view --json state --jq .state 2>/dev/null) && [[ -n "$PR_STATE" && "$PR_STATE" == *"OPEN"* ]]; then + gh pr edit \ + --repo "${{github.repository }}" \ + --title "Bump version on \`next\`: ${{ inputs.release-type || 'prerelease' }} ${{ inputs.pre-id && format('({0})', inputs.pre-id) }} from ${{ steps.bump-version.outputs.current-version }} to ${{ steps.bump-version.outputs.next-version }}" \ + --body "${{ steps.description.outputs.description }}" + else + gh pr create \ + --repo "${{github.repository }}"\ + --title "Bump version on \`next\`: ${{ inputs.release-type || 'prerelease' }} ${{ inputs.pre-id && format('({0})', inputs.pre-id) }} from ${{ steps.bump-version.outputs.current-version }} to ${{ steps.bump-version.outputs.next-version }}" \ + --base next-release \ + --head version-prerelease-from-${{ steps.bump-version.outputs.current-version }} \ + --body "${{ steps.description.outputs.description }}" + fi + + - name: Report job failure to Discord + if: failure() + env: + DISCORD_WEBHOOK: ${{ secrets.DISCORD_MONITORING_URL }} + uses: Ilshidur/action-discord@master + with: + args: 'The GitHub Action for preparing the release pull request bumping from v${{ steps.bump-version.outputs.current-version }} to v${{ steps.bump-version.outputs.next-version }} (triggered by ${{ github.triggering_actor }}) failed! See run at: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}' diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 000000000000..c6c92b600c2d --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,136 @@ +name: Publish +run-name: Publish new version on ${{ github.ref_name }}, triggered by ${{ github.triggering_actor }} + +on: + push: + branches: + - latest-release + - next-release + +env: + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 + PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1 + +permissions: + contents: write + pull-requests: write + +concurrency: + group: ${{ github.workflow }}-${{ github.ref_name }} + +jobs: + publish: + name: Publish new version + runs-on: ubuntu-latest + environment: release + defaults: + run: + working-directory: scripts + steps: + - name: Checkout ${{ github.ref_name }} + uses: actions/checkout@v3 + with: + fetch-depth: 100 + token: ${{ secrets.GH_TOKEN }} + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '16' + + - name: Cache dependencies + uses: actions/cache@v3 + with: + path: | + ~/.yarn/berry/cache + key: yarn-v1-${{ hashFiles('scripts/yarn.lock') }}-${{ hashFiles('code/yarn.lock') }} + restore-keys: | + yarn-v1-${{ hashFiles('scripts/yarn.lock') }}-${{ hashFiles('code/yarn.lock') }} + yarn-v1-${{ hashFiles('scripts/yarn.lock') }} + yarn-v1 + + - name: Install script dependencies + run: | + yarn install + + - name: Get current version + id: version + run: yarn release:get-current-version + + - name: Check if publish is needed + id: publish-needed + run: yarn release:is-version-published ${{ steps.version.outputs.current-version }} + + - name: Check release vs prerelease + if: steps.publish-needed.outputs.published == 'false' + id: is-prerelease + run: yarn release:is-prerelease + + - name: Install code dependencies + if: steps.publish-needed.outputs.published == 'false' + working-directory: . + run: yarn task --task=install --start-from=install + + - name: Publish + if: steps.publish-needed.outputs.published == 'false' + env: + YARN_NPM_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: yarn release:publish --tag ${{ steps.is-prerelease.outputs.prerelease == 'true' && 'next' || 'latest' }} --verbose + + - name: Get target branch + id: target + run: echo "target=${{ github.ref_name == 'next-release' && 'next' || 'main' }}" >> $GITHUB_OUTPUT + + - name: Get changelog for ${{ steps.version.outputs.current-version }} + if: steps.publish-needed.outputs.published == 'false' + id: changelog + run: yarn release:get-changelog-from-file ${{ steps.version.outputs.current-version }} + + # tags are needed to get list of patches to label as picked + - name: Fetch git tags + if: github.ref_name == 'main-release' + run: git fetch --tags origin + + - name: Label patch PRs as picked + if: github.ref_name == 'main-release' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: yarn release:label-patches + + - name: Create GitHub Release + if: steps.publish-needed.outputs.published == 'false' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release create \ + v${{ steps.version.outputs.current-version }} \ + --repo "${{ github.repository }}" \ + --target ${{ github.ref_name }} \ + --title "v${{ steps.version.outputs.current-version }}" \ + --notes "${{ steps.changelog.outputs.changelog }}" \ + ${{ steps.is-prerelease.outputs.prerelease == 'true' && '--prerelease' || '' }} + + - name: Merge ${{ github.ref_name }} into ${{ steps.target.outputs.target }} + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git fetch origin ${{ steps.target.outputs.target }} + git checkout ${{ steps.target.outputs.target }} + git merge ${{ github.ref_name }} + git push origin ${{ steps.target.outputs.target }} + + # Force push from next to main if it is not a prerelease, and this release is from next-release + # This happens when eg. next has been tracking 7.1.0-alpha.X, and now we want to release 7.1.0 + # This will keep release-next, next and main all tracking v7.1.0 + # - name: Force push ${{ steps.target.outputs.target }} to main + # if: steps.publish-needed.outputs.published == 'false' && steps.target.outputs.target == 'next' && !steps.is-prerelease.outputs.prerelease + # run: | + # git push --force origin ${{ steps.target.outputs.target }}:main + + - name: Report job failure to Discord + if: failure() + env: + DISCORD_WEBHOOK: ${{ secrets.DISCORD_MONITORING_URL }} + uses: Ilshidur/action-discord@master + with: + args: 'The GitHub Action for publishing version ${{ steps.version.outputs.current-version }} (triggered by ${{ github.triggering_actor }}) failed! See run at: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}' diff --git a/.yarnrc.yml b/.yarnrc.yml index 2bc71525011c..05080224265e 100644 --- a/.yarnrc.yml +++ b/.yarnrc.yml @@ -2,4 +2,10 @@ installStatePath: ./.yarn/root-install-state.gz nodeLinker: node-modules +npmPublishAccess: public + +plugins: + - path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs + spec: "@yarnpkg/plugin-workspace-tools" + yarnPath: .yarn/releases/yarn-3.4.1.cjs diff --git a/CHANGELOG.prerelease.md b/CHANGELOG.prerelease.md new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/code/__mocks__/fs-extra.js b/code/__mocks__/fs-extra.js index 8ec82c844bd6..a29315078944 100644 --- a/code/__mocks__/fs-extra.js +++ b/code/__mocks__/fs-extra.js @@ -15,6 +15,7 @@ function __setMockFiles(newMockFiles) { const readFile = async (filePath) => mockFiles[filePath]; const readFileSync = (filePath = '') => mockFiles[filePath]; const existsSync = (filePath) => !!mockFiles[filePath]; +const readJson = (filePath = '') => JSON.parse(mockFiles[filePath]); const readJsonSync = (filePath = '') => JSON.parse(mockFiles[filePath]); const lstatSync = (filePath) => ({ isFile: () => !!mockFiles[filePath], @@ -24,6 +25,7 @@ const lstatSync = (filePath) => ({ fs.__setMockFiles = __setMockFiles; fs.readFile = readFile; fs.readFileSync = readFileSync; +fs.readJson = readJson; fs.readJsonSync = readJsonSync; fs.existsSync = existsSync; fs.lstatSync = lstatSync; diff --git a/code/package.json b/code/package.json index 84f95b8f84f0..b61a16b57bba 100644 --- a/code/package.json +++ b/code/package.json @@ -324,10 +324,6 @@ [ "dependencies", "Dependency Upgrades" - ], - [ - "other", - "Other" ] ] } diff --git a/scripts/__mocks__/simple-git.js b/scripts/__mocks__/simple-git.js new file mode 100644 index 000000000000..89fa9f23c7b1 --- /dev/null +++ b/scripts/__mocks__/simple-git.js @@ -0,0 +1,18 @@ +/* eslint-disable no-underscore-dangle */ +const mod = jest.createMockFromModule('simple-git'); + +mod.__getRemotes = jest + .fn() + .mockReturnValue([{ name: 'origin', refs: { fetch: 'origin', push: 'origin' } }]); +mod.__fetch = jest.fn(); +mod.__revparse = jest.fn().mockResolvedValue('mockedGitCommitHash'); + +mod.simpleGit = () => { + return { + getRemotes: mod.__getRemotes, + fetch: mod.__fetch, + revparse: mod.__revparse, + }; +}; + +module.exports = mod; diff --git a/scripts/__mocks__/uuid.ts b/scripts/__mocks__/uuid.ts new file mode 100644 index 000000000000..bd6e08f5a6eb --- /dev/null +++ b/scripts/__mocks__/uuid.ts @@ -0,0 +1,8 @@ +const { v5 } = jest.requireActual('uuid'); + +let seed = 0; + +export const v4 = () => { + seed += 1; + return v5(seed.toString(), '6c7fda6d-f92f-4bd2-9d4d-da26a59196a6'); +}; diff --git a/scripts/dangerfile.ts b/scripts/dangerfile.ts index 8d04e2c28472..1d80df10e0da 100644 --- a/scripts/dangerfile.ts +++ b/scripts/dangerfile.ts @@ -49,7 +49,24 @@ const checkRequiredLabels = (labels: string[]) => { } }; +const checkPrTitle = (title: string) => { + const match = title.match(/^[A-Z].+:\s[A-Z].+$/); + if (!match) { + fail( + `PR title must be in the format of "Area: Summary", With both Area and Summary starting with a capital letter +Good examples: +- "Docs: Describe Canvas Doc Block" +- "Svelte: Support Svelte v4" +Bad examples: +- "add new api docs" +- "fix: Svelte 4 support" +- "Vue: improve docs"` + ); + } +}; + if (prLogConfig) { const { labels } = danger.github.issue; checkRequiredLabels(labels.map((l) => l.name)); + checkPrTitle(danger.github.pr.title); } diff --git a/scripts/package.json b/scripts/package.json index 8558f0614b40..3b43bff37bfa 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -11,10 +11,24 @@ "lint:js": "yarn lint:js:cmd . --quiet", "lint:js:cmd": "cross-env NODE_ENV=production eslint --cache --cache-location=../.cache/eslint --ext .js,.jsx,.json,.html,.ts,.tsx,.mjs --report-unused-disable-directives", "lint:package": "sort-package-json", + "migrate-docs": "node --require esbuild-register ./ts-to-ts49.ts", + "release:generate-pr-description": "ts-node --swc ./release/generate-pr-description.ts", + "release:get-changelog-from-file": "ts-node --swc ./release/get-changelog-from-file.ts", + "release:get-current-version": "ts-node --swc ./release/get-current-version.ts", + "release:get-version-changelog": "ts-node --swc ./release/get-version-changelog.ts", + "release:is-pr-frozen": "ts-node --swc ./release/is-pr-frozen.ts", + "release:is-prerelease": "ts-node --swc ./release/is-prerelease.ts", + "release:is-version-published": "ts-node --swc ./release/is-version-published.ts", + "release:label-patches": "ts-node --swc ./release/label-patches.ts", + "release:pick-patches": "ts-node --swc ./release/pick-patches.ts", + "release:publish": "ts-node --swc ./release/publish.ts", + "release:unreleased-changes-exists": "ts-node --swc ./release/unreleased-changes-exists.ts", + "release:version": "ts-node --swc ./release/version.ts", + "release:write-changelog": "ts-node --swc ./release/write-changelog.ts", + "strict-ts": "node --require esbuild-register ./strict-ts.ts", "task": "ts-node --swc ./task.ts", - "upgrade": "ts-node --swc ./task.ts", "test": "jest --config ./jest.config.js", - "migrate-docs": "node --require esbuild-register ./ts-to-ts49.ts" + "upgrade": "ts-node --swc ./task.ts" }, "husky": { "hooks": { @@ -38,6 +52,7 @@ "serialize-javascript": "^3.1.0" }, "dependencies": { + "@actions/core": "^1.10.0", "@babel/core": "^7.20.2", "@babel/plugin-proposal-class-properties": "^7.18.6", "@babel/plugin-proposal-decorators": "^7.20.7", @@ -55,6 +70,7 @@ "@nrwl/cli": "^15.4.5", "@nrwl/nx-cloud": "^15.0.2", "@nrwl/workspace": "^15.4.5", + "@octokit/graphql": "^5.0.5", "@storybook/eslint-config-storybook": "^3.1.2", "@storybook/jest": "^0.1.0", "@storybook/linter-config": "^3.1.2", @@ -78,9 +94,11 @@ "@types/semver": "^7.3.4", "@types/serve-static": "^1.13.8", "@types/shelljs": "^0.8.7", + "@types/uuid": "^9.0.1", "@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/experimental-utils": "^5.45.0", "@typescript-eslint/parser": "^5.45.0", + "ansi-regex": "^5.0.0", "babel-eslint": "^10.1.0", "babel-loader": "^9.1.2", "boxen": "^5.1.2", @@ -89,7 +107,8 @@ "commander": "^6.2.1", "cross-env": "^7.0.3", "cross-spawn": "^7.0.3", - "danger": "^10.6.2", + "danger": "^11.2.6", + "dataloader": "^2.2.2", "detect-port": "^1.3.0", "ejs": "^3.1.8", "ejs-lint": "^2.0.0", @@ -113,9 +132,11 @@ "jest-environment-jsdom": "^29.3.1", "jest-image-snapshot": "^6.0.0", "jest-junit": "^14.0.1", + "jest-mock-extended": "^3.0.4", "jest-os-detection": "^1.3.1", "jest-serializer-html": "^7.1.0", "jest-watch-typeahead": "^2.2.1", + "json5": "^2.2.3", "junit-xml": "^1.2.0", "lint-staged": "^10.5.4", "lodash": "^4.17.21", @@ -124,7 +145,9 @@ "node-gyp": "^8.4.0", "npmlog": "^5.0.1", "nx": "^15.4.5", + "ora": "^5.4.1", "p-limit": "^3.1.0", + "p-retry": "^5.1.2", "prettier": "^2.8.0", "pretty-hrtime": "^1.0.0", "process": "^0.11.10", @@ -140,6 +163,7 @@ "semver": "^7.3.7", "serve-static": "^1.14.1", "shelljs": "^0.8.5", + "simple-git": "^3.18.0", "slash": "^3.0.0", "sort-package-json": "^1.48.1", "tempy": "^1.0.0", @@ -150,8 +174,10 @@ "type-fest": "^3.4.0", "typescript": "~4.9.3", "util": "^0.12.4", - "wait-on": "^5.2.1", + "uuid": "^9.0.0", + "wait-on": "^7.0.1", "window-size": "^1.1.1", + "zod": "^3.21.4", "zx": "^7.0.3" }, "optionalDependencies": { diff --git a/scripts/release/__tests__/generate-pr-description.test.ts b/scripts/release/__tests__/generate-pr-description.test.ts new file mode 100644 index 000000000000..d4858a30c70a --- /dev/null +++ b/scripts/release/__tests__/generate-pr-description.test.ts @@ -0,0 +1,391 @@ +import { + generateReleaseDescription, + generateNonReleaseDescription, + mapToChangelist, + mapCherryPicksToTodo, +} from '../generate-pr-description'; +import type { Change } from '../utils/get-changes'; + +describe('Generate PR Description', () => { + const changes: Change[] = [ + { + user: 'JReinhold', + title: 'Some PR title for a bug', + labels: ['bug', 'build', 'other label', 'patch'], + commit: 'abc123', + pull: '42', + links: { + commit: '[abc123](https://github.com/storybookjs/storybook/commit/abc123)', + pull: '[#42](https://github.com/storybookjs/storybook/pull/42)', + user: '[@JReinhold](https://github.com/JReinhold)', + }, + }, + { + // this Bump version commit should be ignored + user: 'github-actions[bot]', + pull: null, + commit: '012b58140c3606efeacbe99c0c410624b0a1ed1f', + title: 'Bump version on `next`: preminor (alpha) from 7.2.0 to 7.3.0-alpha.0', + labels: null, + links: { + commit: + '[`012b58140c3606efeacbe99c0c410624b0a1ed1f`](https://github.com/storybookjs/storybook/commit/012b58140c3606efeacbe99c0c410624b0a1ed1f)', + pull: null, + user: '[@github-actions[bot]](https://github.com/github-actions%5Bbot%5D)', + }, + }, + { + user: 'shilman', + title: 'Some title for a "direct commit"', + labels: null, + commit: '22bb11', + pull: null, + links: { + commit: '[22bb11](https://github.com/storybookjs/storybook/commit/22bb11)', + pull: null, + user: '[@shilman](https://github.com/shilman)', + }, + }, + { + user: 'shilman', + title: 'Another PR `title` for docs', + labels: ['another label', 'documentation', 'patch'], + commit: 'ddd222', + pull: '11', + links: { + commit: '[ddd222](https://github.com/storybookjs/storybook/commit/ddd222)', + pull: '[#11](https://github.com/storybookjs/storybook/pull/11)', + user: '[@shilman](https://github.com/shilman)', + }, + }, + { + user: 'JReinhold', + title: "Some PR title for a 'new' feature", + labels: ['feature request', 'other label'], + commit: 'wow1337', + pull: '48', + links: { + commit: '[wow1337](https://github.com/storybookjs/storybook/commit/wow1337)', + pull: '[#48](https://github.com/storybookjs/storybook/pull/48)', + user: '[@JReinhold](https://github.com/JReinhold)', + }, + }, + { + user: 'JReinhold', + title: 'Some PR title with a missing label', + labels: ['incorrect label', 'other label'], + commit: 'bad999', + pull: '77', + links: { + commit: '[bad999](https://github.com/storybookjs/storybook/commit/bad999)', + pull: '[#77](https://github.com/storybookjs/storybook/pull/77)', + user: '[@JReinhold](https://github.com/JReinhold)', + }, + }, + ]; + describe('mapToChangelist', () => { + it('should return a correct string for releases', () => { + expect(mapToChangelist({ changes, isRelease: true })).toMatchInlineSnapshot(` + "- **πŸ› Bug**: Some PR title for a bug [#42](https://github.com/storybookjs/storybook/pull/42) + - [ ] The change is appropriate for the version bump + - [ ] The PR is labeled correctly + - [ ] The PR title is correct + - **⚠️ Direct commit**: Some title for a "direct commit" [22bb11](https://github.com/storybookjs/storybook/commit/22bb11) + - [ ] The change is appropriate for the version bump + - **πŸ“ Documentation**: Another PR \`title\` for docs [#11](https://github.com/storybookjs/storybook/pull/11) + - [ ] The change is appropriate for the version bump + - [ ] The PR is labeled correctly + - [ ] The PR title is correct + - **✨ Feature Request**: Some PR title for a 'new' feature [#48](https://github.com/storybookjs/storybook/pull/48) + - [ ] The change is appropriate for the version bump + - [ ] The PR is labeled correctly + - [ ] The PR title is correct + - **❔ Missing Label**: Some PR title with a missing label [#77](https://github.com/storybookjs/storybook/pull/77) + - [ ] The change is appropriate for the version bump + - [ ] The PR is labeled correctly + - [ ] The PR title is correct" + `); + }); + it('should return a correct string for non-releases', () => { + expect(mapToChangelist({ changes, isRelease: false })).toMatchInlineSnapshot(` + "- **πŸ› Bug**: Some PR title for a bug [#42](https://github.com/storybookjs/storybook/pull/42) + - **⚠️ Direct commit**: Some title for a "direct commit" [22bb11](https://github.com/storybookjs/storybook/commit/22bb11) + - **πŸ“ Documentation**: Another PR \`title\` for docs [#11](https://github.com/storybookjs/storybook/pull/11) + - **✨ Feature Request**: Some PR title for a 'new' feature [#48](https://github.com/storybookjs/storybook/pull/48) + - **❔ Missing Label**: Some PR title with a missing label [#77](https://github.com/storybookjs/storybook/pull/77)" + `); + }); + }); + + describe('mapCherryPicksToTodo', () => { + it('should return a correct string for releases', () => { + expect(mapCherryPicksToTodo({ changes, commits: ['abc123'] })).toMatchInlineSnapshot(` + "## πŸ’ Manual cherry picking needed! + + The following pull requests could not be cherry-picked automatically because it resulted in merge conflicts. + For each pull request below, you need to either manually cherry pick it, or discard it by removing the "patch" label from the PR and re-generate this PR. + + - [ ] [#42](https://github.com/storybookjs/storybook/pull/42): \`git cherry-pick -m1 -x abc123\`" + `); + }); + }); + + describe('description generator', () => { + const changeList = `- **πŸ› Bug**: Some PR title for a bug [#42](https://github.com/storybookjs/storybook/pull/42) +\t- [ ] The change is appropriate for the version bump +\t- [ ] The PR is labeled correctly +\t- [ ] The PR title is correct +- **⚠️ Direct commit**: Some title for a \\"direct commit\\" [22bb11](https://github.com/storybookjs/storybook/commit/22bb11) +\t- [ ] The change is appropriate for the version bump +- **πŸ“ Documentation**: Another PR \\\`title\\\` for docs [#11](https://github.com/storybookjs/storybook/pull/11) +\t- [ ] The change is appropriate for the version bump +\t- [ ] The PR is labeled correctly +\t- [ ] The PR title is correct +- **✨ Feature Request**: Some PR title for a \\'new\\' feature [#48](https://github.com/storybookjs/storybook/pull/48) +\t- [ ] The change is appropriate for the version bump +\t- [ ] The PR is labeled correctly +\t- [ ] The PR title is correct +- **⚠️ Missing Label**: Some PR title with a missing label [#77](https://github.com/storybookjs/storybook/pull/77) +\t- [ ] The change is appropriate for the version bump +\t- [ ] The PR is labeled correctly +\t- [ ] The PR title is correct`; + + const manualCherryPicks = `## πŸ’ Manual cherry picking needed! + +The following pull requests could not be cherry-picked automatically because it resulted in merge conflicts. +For each pull request below, you need to either manually cherry pick it, or discard it by removing the "patch" label from the PR and re-generate this PR. + +- [ ] [#42](https://github.com/storybookjs/storybook/pull/42): \`git cherry-pick -m1 -x abc123\``; + + it('should return a correct string with cherry picks for releases', () => { + const changelogText = `## 7.1.0-alpha.11 + +- Some PR \`title\` for a bug [#42](https://github.com/storybookjs/storybook/pull/42), thanks [@JReinhold](https://github.com/JReinhold) +- Some PR 'title' for a feature request [#48](https://github.com/storybookjs/storybook/pull/48), thanks [@JReinhold](https://github.com/JReinhold) +- Antoher PR "title" for maintainance [#49](https://github.com/storybookjs/storybook/pull/49), thanks [@JReinhold](https://github.com/JReinhold)`; + expect( + generateReleaseDescription({ + currentVersion: '7.1.0-alpha.10', + nextVersion: '7.1.0-alpha.11', + changeList, + changelogText, + manualCherryPicks, + }) + ).toMatchInlineSnapshot(` + "This is an automated pull request that bumps the version from \\\`7.1.0-alpha.10\\\` to \\\`7.1.0-alpha.11\\\`. + Once this pull request is merged, it will trigger a new release of version \\\`7.1.0-alpha.11\\\`. + If you\\'re not a core maintainer with permissions to release you can ignore this pull request. + + ## To do + + Before merging the PR, there are a few QA steps to go through: + + - [ ] Add the \\"freeze\\" label to this PR, to ensure it doesn\\'t get automatically forced pushed by new changes. + + And for each change below: + + 1. Ensure the change is appropriate for the version bump. E.g. patch release should only contain patches, not new or de-stabilizing features. If a change is not appropriate, revert the PR. + 2. Ensure the PR is labeled correctly with \\"BREAKING CHANGE\\", \\"feature request\\", \\"maintainance\\", \\"bug\\", \\"build\\" or \\"documentation\\". + 3. Ensure the PR title is correct, and follows the format \\"[Area]: [Summary]\\", e.g. *\\"React: Fix hooks in CSF3 render functions\\"*. If it is not correct, change the title in the PR. + - Areas include: React, Vue, Core, Docs, Controls, etc. + - First word of summary indicates the type: β€œAdd”, β€œFix”, β€œUpgrade”, etc. + - The entire title should fit on a line + + This is a list of all the PRs merged and commits pushed directly to \\\`next\\\`, that will be part of this release: + + - **πŸ› Bug**: Some PR title for a bug [#42](https://github.com/storybookjs/storybook/pull/42) + - [ ] The change is appropriate for the version bump + - [ ] The PR is labeled correctly + - [ ] The PR title is correct + - **⚠️ Direct commit**: Some title for a \\\\"direct commit\\\\" [22bb11](https://github.com/storybookjs/storybook/commit/22bb11) + - [ ] The change is appropriate for the version bump + - **πŸ“ Documentation**: Another PR \\\\\`title\\\\\` for docs [#11](https://github.com/storybookjs/storybook/pull/11) + - [ ] The change is appropriate for the version bump + - [ ] The PR is labeled correctly + - [ ] The PR title is correct + - **✨ Feature Request**: Some PR title for a \\\\'new\\\\' feature [#48](https://github.com/storybookjs/storybook/pull/48) + - [ ] The change is appropriate for the version bump + - [ ] The PR is labeled correctly + - [ ] The PR title is correct + - **⚠️ Missing Label**: Some PR title with a missing label [#77](https://github.com/storybookjs/storybook/pull/77) + - [ ] The change is appropriate for the version bump + - [ ] The PR is labeled correctly + - [ ] The PR title is correct + + ## πŸ’ Manual cherry picking needed! + + The following pull requests could not be cherry-picked automatically because it resulted in merge conflicts. + For each pull request below, you need to either manually cherry pick it, or discard it by removing the \\"patch\\" label from the PR and re-generate this PR. + + - [ ] [#42](https://github.com/storybookjs/storybook/pull/42): \\\`git cherry-pick -m1 -x abc123\\\` + + If you\\'ve made any changes doing the above QA (change PR titles, revert PRs), manually trigger a re-generation of this PR with [this workflow](https://github.com/storybookjs/storybook/actions/workflows/prepare-prerelease.yml) and wait for it to finish. It will wipe your progress in this to do, which is expected. + + When everything above is done: + - [ ] Merge this PR + - [ ] [Follow the publish workflow run and see it finishes succesfully](https://github.com/storybookjs/storybook/actions/workflows/publish.yml) + + --- + + # Generated changelog + + ## 7.1.0-alpha.11 + + - Some PR \\\`title\\\` for a bug [#42](https://github.com/storybookjs/storybook/pull/42), thanks [@ JReinhold](https://github.com/JReinhold) + - Some PR \\'title\\' for a feature request [#48](https://github.com/storybookjs/storybook/pull/48), thanks [@ JReinhold](https://github.com/JReinhold) + - Antoher PR \\"title\\" for maintainance [#49](https://github.com/storybookjs/storybook/pull/49), thanks [@ JReinhold](https://github.com/JReinhold)" + `); + }); + + it('should return a correct string for non-releases with cherry picks', () => { + expect(generateNonReleaseDescription(changeList, manualCherryPicks)).toMatchInlineSnapshot(` + "This is an automated pull request. None of the changes requires a version bump, they are only internal or documentation related. Merging this PR will not trigger a new release, but documentation will be updated. + If you\\'re not a core maintainer with permissions to release you can ignore this pull request. + + This is a list of all the PRs merged and commits pushed directly to \\\`next\\\` since the last release: + + - **πŸ› Bug**: Some PR title for a bug [#42](https://github.com/storybookjs/storybook/pull/42) + - [ ] The change is appropriate for the version bump + - [ ] The PR is labeled correctly + - [ ] The PR title is correct + - **⚠️ Direct commit**: Some title for a \\\\"direct commit\\\\" [22bb11](https://github.com/storybookjs/storybook/commit/22bb11) + - [ ] The change is appropriate for the version bump + - **πŸ“ Documentation**: Another PR \\\\\`title\\\\\` for docs [#11](https://github.com/storybookjs/storybook/pull/11) + - [ ] The change is appropriate for the version bump + - [ ] The PR is labeled correctly + - [ ] The PR title is correct + - **✨ Feature Request**: Some PR title for a \\\\'new\\\\' feature [#48](https://github.com/storybookjs/storybook/pull/48) + - [ ] The change is appropriate for the version bump + - [ ] The PR is labeled correctly + - [ ] The PR title is correct + - **⚠️ Missing Label**: Some PR title with a missing label [#77](https://github.com/storybookjs/storybook/pull/77) + - [ ] The change is appropriate for the version bump + - [ ] The PR is labeled correctly + - [ ] The PR title is correct + + ## πŸ’ Manual cherry picking needed! + + The following pull requests could not be cherry-picked automatically because it resulted in merge conflicts. + For each pull request below, you need to either manually cherry pick it, or discard it by removing the \\"patch\\" label from the PR and re-generate this PR. + + - [ ] [#42](https://github.com/storybookjs/storybook/pull/42): \\\`git cherry-pick -m1 -x abc123\\\` + + If you\\'ve made any changes (change PR titles, revert PRs), manually trigger a re-generation of this PR with [this workflow](https://github.com/storybookjs/storybook/actions/workflows/prepare-prerelease.yml) and wait for it to finish. + + When everything above is done: + - [ ] Merge this PR + - [ ] [Approve the publish workflow run](https://github.com/storybookjs/storybook/actions/workflows/publish.yml)" + `); + }); + + it('should return a correct string without cherry picks for releases', () => { + const changelogText = `## 7.1.0-alpha.11 + +- Some PR \`title\` for a bug [#42](https://github.com/storybookjs/storybook/pull/42), thanks [@JReinhold](https://github.com/JReinhold) +- Some PR 'title' for a feature request [#48](https://github.com/storybookjs/storybook/pull/48), thanks [@JReinhold](https://github.com/JReinhold) +- Antoher PR "title" for maintainance [#49](https://github.com/storybookjs/storybook/pull/49), thanks [@JReinhold](https://github.com/JReinhold)`; + expect( + generateReleaseDescription({ + currentVersion: '7.1.0-alpha.10', + nextVersion: '7.1.0-alpha.11', + changeList, + changelogText, + }) + ).toMatchInlineSnapshot(` + "This is an automated pull request that bumps the version from \\\`7.1.0-alpha.10\\\` to \\\`7.1.0-alpha.11\\\`. + Once this pull request is merged, it will trigger a new release of version \\\`7.1.0-alpha.11\\\`. + If you\\'re not a core maintainer with permissions to release you can ignore this pull request. + + ## To do + + Before merging the PR, there are a few QA steps to go through: + + - [ ] Add the \\"freeze\\" label to this PR, to ensure it doesn\\'t get automatically forced pushed by new changes. + + And for each change below: + + 1. Ensure the change is appropriate for the version bump. E.g. patch release should only contain patches, not new or de-stabilizing features. If a change is not appropriate, revert the PR. + 2. Ensure the PR is labeled correctly with \\"BREAKING CHANGE\\", \\"feature request\\", \\"maintainance\\", \\"bug\\", \\"build\\" or \\"documentation\\". + 3. Ensure the PR title is correct, and follows the format \\"[Area]: [Summary]\\", e.g. *\\"React: Fix hooks in CSF3 render functions\\"*. If it is not correct, change the title in the PR. + - Areas include: React, Vue, Core, Docs, Controls, etc. + - First word of summary indicates the type: β€œAdd”, β€œFix”, β€œUpgrade”, etc. + - The entire title should fit on a line + + This is a list of all the PRs merged and commits pushed directly to \\\`next\\\`, that will be part of this release: + + - **πŸ› Bug**: Some PR title for a bug [#42](https://github.com/storybookjs/storybook/pull/42) + - [ ] The change is appropriate for the version bump + - [ ] The PR is labeled correctly + - [ ] The PR title is correct + - **⚠️ Direct commit**: Some title for a \\\\"direct commit\\\\" [22bb11](https://github.com/storybookjs/storybook/commit/22bb11) + - [ ] The change is appropriate for the version bump + - **πŸ“ Documentation**: Another PR \\\\\`title\\\\\` for docs [#11](https://github.com/storybookjs/storybook/pull/11) + - [ ] The change is appropriate for the version bump + - [ ] The PR is labeled correctly + - [ ] The PR title is correct + - **✨ Feature Request**: Some PR title for a \\\\'new\\\\' feature [#48](https://github.com/storybookjs/storybook/pull/48) + - [ ] The change is appropriate for the version bump + - [ ] The PR is labeled correctly + - [ ] The PR title is correct + - **⚠️ Missing Label**: Some PR title with a missing label [#77](https://github.com/storybookjs/storybook/pull/77) + - [ ] The change is appropriate for the version bump + - [ ] The PR is labeled correctly + - [ ] The PR title is correct + + + + If you\\'ve made any changes doing the above QA (change PR titles, revert PRs), manually trigger a re-generation of this PR with [this workflow](https://github.com/storybookjs/storybook/actions/workflows/prepare-prerelease.yml) and wait for it to finish. It will wipe your progress in this to do, which is expected. + + When everything above is done: + - [ ] Merge this PR + - [ ] [Follow the publish workflow run and see it finishes succesfully](https://github.com/storybookjs/storybook/actions/workflows/publish.yml) + + --- + + # Generated changelog + + ## 7.1.0-alpha.11 + + - Some PR \\\`title\\\` for a bug [#42](https://github.com/storybookjs/storybook/pull/42), thanks [@ JReinhold](https://github.com/JReinhold) + - Some PR \\'title\\' for a feature request [#48](https://github.com/storybookjs/storybook/pull/48), thanks [@ JReinhold](https://github.com/JReinhold) + - Antoher PR \\"title\\" for maintainance [#49](https://github.com/storybookjs/storybook/pull/49), thanks [@ JReinhold](https://github.com/JReinhold)" + `); + }); + + it('should return a correct string for non-releases without cherry picks', () => { + expect(generateNonReleaseDescription(changeList)).toMatchInlineSnapshot(` + "This is an automated pull request. None of the changes requires a version bump, they are only internal or documentation related. Merging this PR will not trigger a new release, but documentation will be updated. + If you\\'re not a core maintainer with permissions to release you can ignore this pull request. + + This is a list of all the PRs merged and commits pushed directly to \\\`next\\\` since the last release: + + - **πŸ› Bug**: Some PR title for a bug [#42](https://github.com/storybookjs/storybook/pull/42) + - [ ] The change is appropriate for the version bump + - [ ] The PR is labeled correctly + - [ ] The PR title is correct + - **⚠️ Direct commit**: Some title for a \\\\"direct commit\\\\" [22bb11](https://github.com/storybookjs/storybook/commit/22bb11) + - [ ] The change is appropriate for the version bump + - **πŸ“ Documentation**: Another PR \\\\\`title\\\\\` for docs [#11](https://github.com/storybookjs/storybook/pull/11) + - [ ] The change is appropriate for the version bump + - [ ] The PR is labeled correctly + - [ ] The PR title is correct + - **✨ Feature Request**: Some PR title for a \\\\'new\\\\' feature [#48](https://github.com/storybookjs/storybook/pull/48) + - [ ] The change is appropriate for the version bump + - [ ] The PR is labeled correctly + - [ ] The PR title is correct + - **⚠️ Missing Label**: Some PR title with a missing label [#77](https://github.com/storybookjs/storybook/pull/77) + - [ ] The change is appropriate for the version bump + - [ ] The PR is labeled correctly + - [ ] The PR title is correct + + + + If you\\'ve made any changes (change PR titles, revert PRs), manually trigger a re-generation of this PR with [this workflow](https://github.com/storybookjs/storybook/actions/workflows/prepare-prerelease.yml) and wait for it to finish. + + When everything above is done: + - [ ] Merge this PR + - [ ] [Approve the publish workflow run](https://github.com/storybookjs/storybook/actions/workflows/publish.yml)" + `); + }); + }); +}); diff --git a/scripts/release/__tests__/is-pr-frozen.test.ts b/scripts/release/__tests__/is-pr-frozen.test.ts new file mode 100644 index 000000000000..9c2ce713fb0a --- /dev/null +++ b/scripts/release/__tests__/is-pr-frozen.test.ts @@ -0,0 +1,61 @@ +/* eslint-disable no-underscore-dangle */ +/* eslint-disable global-require */ +import path from 'path'; +import { run as isPrFrozen } from '../is-pr-frozen'; + +// eslint-disable-next-line jest/no-mocks-import +jest.mock('fs-extra', () => require('../../../code/__mocks__/fs-extra')); +jest.mock('../utils/get-github-info'); + +const fsExtra = require('fs-extra'); +const simpleGit = require('simple-git'); +const { getPullInfoFromCommit } = require('../utils/get-github-info'); + +const CODE_DIR_PATH = path.join(__dirname, '..', '..', '..', 'code'); +const CODE_PACKAGE_JSON_PATH = path.join(CODE_DIR_PATH, 'package.json'); + +fsExtra.__setMockFiles({ + [CODE_PACKAGE_JSON_PATH]: JSON.stringify({ version: '1.0.0' }), +}); + +describe('isPrFrozen', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should return true when PR is frozen', async () => { + getPullInfoFromCommit.mockResolvedValue({ + labels: ['freeze'], + }); + await expect(isPrFrozen({ patch: false })).resolves.toBe(true); + }); + + it('should return false when PR is not frozen', async () => { + getPullInfoFromCommit.mockResolvedValue({ + labels: [], + }); + await expect(isPrFrozen({ patch: false })).resolves.toBe(false); + }); + + it('should look for patch PRs when patch is true', async () => { + getPullInfoFromCommit.mockResolvedValue({ + labels: [], + }); + await isPrFrozen({ patch: true }); + + expect(simpleGit.__fetch).toHaveBeenCalledWith('origin', 'version-from-patch-1.0.0', { + '--depth': 1, + }); + }); + + it('should look for prerelease PRs when patch is false', async () => { + getPullInfoFromCommit.mockResolvedValue({ + labels: [], + }); + await isPrFrozen({ patch: false }); + + expect(simpleGit.__fetch).toHaveBeenCalledWith('origin', 'version-from-prerelease-1.0.0', { + '--depth': 1, + }); + }); +}); diff --git a/scripts/release/__tests__/label-patches.test.ts b/scripts/release/__tests__/label-patches.test.ts new file mode 100644 index 000000000000..31203529a0ca --- /dev/null +++ b/scripts/release/__tests__/label-patches.test.ts @@ -0,0 +1,133 @@ +import type { LogResult } from 'simple-git'; +import ansiRegex from 'ansi-regex'; +import { run } from '../label-patches'; +import * as gitClient_ from '../utils/git-client'; +import * as githubInfo_ from '../utils/get-github-info'; +import * as github_ from '../utils/github-client'; + +jest.mock('uuid'); +jest.mock('../utils/get-github-info'); +jest.mock('../utils/github-client'); +jest.mock('../utils/git-client', () => jest.requireActual('jest-mock-extended').mockDeep()); + +const gitClient = jest.mocked(gitClient_); +const github = jest.mocked(github_); +const githubInfo = jest.mocked(githubInfo_); + +const remoteMock = [ + { + name: 'origin', + refs: { + fetch: 'https://github.com/storybookjs/storybook.git', + push: 'https://github.com/storybookjs/storybook.git', + }, + }, +]; + +const gitLogMock: LogResult = { + all: [ + { + hash: 'some-hash', + date: '2023-06-07T09:45:11+02:00', + message: 'Something else', + refs: 'HEAD -> main', + body: '', + author_name: 'Jeppe Reinhold', + author_email: 'jeppe@chromatic.com', + }, + { + hash: 'b75879c4d3d72f7830e9c5fca9f75a303ddb194d', + date: '2023-06-07T09:45:11+02:00', + message: 'Merge pull request #55 from storybookjs/fixes', + refs: 'HEAD -> main', + body: + 'Legal: Fix license\n' + + '(cherry picked from commit 930b47f011f750c44a1782267d698ccdd3c04da3)\n', + author_name: 'Jeppe Reinhold', + author_email: 'jeppe@chromatic.com', + }, + ], + latest: null!, + total: 1, +}; + +const pullInfoMock = { + user: 'JReinhold', + id: 'pr_id', + pull: 55, + commit: '930b47f011f750c44a1782267d698ccdd3c04da3', + title: 'Legal: Fix license', + labels: ['documentation', 'patch', 'picked'], + links: { + commit: + '[`930b47f011f750c44a1782267d698ccdd3c04da3`](https://github.com/storybookjs/storybook/commit/930b47f011f750c44a1782267d698ccdd3c04da3)', + pull: '[#55](https://github.com/storybookjs/storybook/pull/55)', + user: '[@JReinhold](https://github.com/JReinhold)', + }, +}; + +beforeEach(() => { + // mock IO + jest.clearAllMocks(); + gitClient.getLatestTag.mockResolvedValue('v7.2.1'); + gitClient.git.log.mockResolvedValue(gitLogMock); + gitClient.git.getRemotes.mockResolvedValue(remoteMock); + githubInfo.getPullInfoFromCommit.mockResolvedValue(pullInfoMock); + github.getLabelIds.mockResolvedValue({ picked: 'pick-id' }); +}); + +test('it should fail early when no GH_TOKEN is set', async () => { + delete process.env.GH_TOKEN; + await expect(run({})).rejects.toThrowErrorMatchingInlineSnapshot( + `"GH_TOKEN environment variable must be set, exiting."` + ); +}); + +test('it should label the PR associated with cheery picks in the current branch', async () => { + process.env.GH_TOKEN = 'MY_SECRET'; + + const writeStderr = jest.spyOn(process.stderr, 'write').mockImplementation(); + + await run({}); + expect(github.githubGraphQlClient.mock.calls).toMatchInlineSnapshot(` + [ + [ + " + mutation ($input: AddLabelsToLabelableInput!) { + addLabelsToLabelable(input: $input) { + clientMutationId + } + } + ", + { + "input": { + "clientMutationId": "7efda802-d7d1-5d76-97d6-cc16a9f3e357", + "labelIds": [ + "pick-id", + ], + "labelableId": "pr_id", + }, + }, + ], + ] + `); + + expect.addSnapshotSerializer({ + serialize: (value) => { + const stripAnsi = value.map((it: string) => it.replace(ansiRegex(), '')); + return JSON.stringify(stripAnsi, null, 2); + }, + test: () => true, + }); + + expect(writeStderr.mock.calls.map(([text]) => text)).toMatchInlineSnapshot(` + [ + "- Looking for latest tag\\n", + "βœ” Found latest tag: v7.2.1\\n", + "- Looking at cherry pick commits since v7.2.1\\n", + "βœ” Found the following picks πŸ’:\\n Commit: 930b47f011f750c44a1782267d698ccdd3c04da3\\n PR: [#55](https://github.com/storybookjs/storybook/pull/55)\\n", + "- Labeling the PRs with the picked label...\\n", + "βœ” Successfully labeled all PRs with the picked label.\\n" + ] + `); +}); diff --git a/scripts/release/__tests__/version.test.ts b/scripts/release/__tests__/version.test.ts new file mode 100644 index 000000000000..c33c5fc31b8b --- /dev/null +++ b/scripts/release/__tests__/version.test.ts @@ -0,0 +1,234 @@ +/* eslint-disable global-require */ +/* eslint-disable no-underscore-dangle */ +import path from 'path'; +import { run as version } from '../version'; + +// eslint-disable-next-line jest/no-mocks-import +jest.mock('fs-extra', () => require('../../../code/__mocks__/fs-extra')); +const fsExtra = require('fs-extra'); + +jest.mock('../../../code/lib/cli/src/versions', () => ({ + '@storybook/addon-a11y': '7.1.0-alpha.29', +})); + +jest.mock('../../utils/exec'); +const { execaCommand } = require('../../utils/exec'); + +jest.mock('../../utils/workspace', () => ({ + getWorkspaces: jest.fn().mockResolvedValue([ + { + name: '@storybook/addon-a11y', + location: 'addons/a11y', + }, + ]), +})); + +jest.spyOn(console, 'log').mockImplementation(() => {}); +jest.spyOn(console, 'warn').mockImplementation(() => {}); +jest.spyOn(console, 'error').mockImplementation(() => {}); + +beforeEach(() => { + jest.clearAllMocks(); +}); + +describe('Version', () => { + const CODE_DIR_PATH = path.join(__dirname, '..', '..', '..', 'code'); + const CODE_PACKAGE_JSON_PATH = path.join(CODE_DIR_PATH, 'package.json'); + const MANAGER_API_VERSION_PATH = path.join( + CODE_DIR_PATH, + 'lib', + 'manager-api', + 'src', + 'version.ts' + ); + const VERSIONS_PATH = path.join(CODE_DIR_PATH, 'lib', 'cli', 'src', 'versions.ts'); + const A11Y_PACKAGE_JSON_PATH = path.join(CODE_DIR_PATH, 'addons', 'a11y', 'package.json'); + + it('should throw when release type is invalid', async () => { + fsExtra.__setMockFiles({ + [CODE_PACKAGE_JSON_PATH]: JSON.stringify({ version: '1.0.0' }), + [MANAGER_API_VERSION_PATH]: `export const version = "1.0.0";`, + [VERSIONS_PATH]: `export default { "@storybook/addon-a11y": "1.0.0" };`, + }); + + await expect(version({ releaseType: 'invalid' })).rejects.toThrowErrorMatchingInlineSnapshot(` + "[ + { + "received": "invalid", + "code": "invalid_enum_value", + "options": [ + "major", + "minor", + "patch", + "prerelease", + "premajor", + "preminor", + "prepatch" + ], + "path": [ + "releaseType" + ], + "message": "Invalid enum value. Expected 'major' | 'minor' | 'patch' | 'prerelease' | 'premajor' | 'preminor' | 'prepatch', received 'invalid'" + } + ]" + `); + }); + + it('should throw when prerelease identifier is combined with non-pre release type', async () => { + fsExtra.__setMockFiles({ + [CODE_PACKAGE_JSON_PATH]: JSON.stringify({ version: '1.0.0' }), + [MANAGER_API_VERSION_PATH]: `export const version = "1.0.0";`, + [VERSIONS_PATH]: `export default { "@storybook/addon-a11y": "1.0.0" };`, + }); + + await expect(version({ releaseType: 'major', preId: 'alpha' })).rejects + .toThrowErrorMatchingInlineSnapshot(` + "[ + { + "code": "custom", + "message": "Using prerelease identifier requires one of release types: premajor, preminor, prepatch, prerelease", + "path": [] + } + ]" + `); + }); + + it('should throw when exact is combined with release type', async () => { + fsExtra.__setMockFiles({ + [CODE_PACKAGE_JSON_PATH]: JSON.stringify({ version: '1.0.0' }), + [MANAGER_API_VERSION_PATH]: `export const version = "1.0.0";`, + [VERSIONS_PATH]: `export default { "@storybook/addon-a11y": "1.0.0" };`, + }); + + await expect(version({ releaseType: 'major', exact: '1.0.0' })).rejects + .toThrowErrorMatchingInlineSnapshot(` + "[ + { + "code": "custom", + "message": "Combining --exact with --release-type is invalid, but having one of them is required", + "path": [] + } + ]" + `); + }); + + it('should throw when exact is invalid semver', async () => { + fsExtra.__setMockFiles({ + [CODE_PACKAGE_JSON_PATH]: JSON.stringify({ version: '1.0.0' }), + [MANAGER_API_VERSION_PATH]: `export const version = "1.0.0";`, + [VERSIONS_PATH]: `export default { "@storybook/addon-a11y": "1.0.0" };`, + }); + + await expect(version({ exact: 'not-semver' })).rejects.toThrowErrorMatchingInlineSnapshot(` + "[ + { + "code": "custom", + "message": "--exact version has to be a valid semver string", + "path": [ + "exact" + ] + } + ]" + `); + }); + + it.each([ + // prettier-ignore + { releaseType: 'major', currentVersion: '1.1.1', expectedVersion: '2.0.0' }, + // prettier-ignore + { releaseType: 'minor', currentVersion: '1.1.1', expectedVersion: '1.2.0' }, + // prettier-ignore + { releaseType: 'patch', currentVersion: '1.1.1', expectedVersion: '1.1.2' }, + // prettier-ignore + { releaseType: 'premajor', preId: 'alpha', currentVersion: '1.1.1', expectedVersion: '2.0.0-alpha.0' }, + // prettier-ignore + { releaseType: 'preminor', preId: 'alpha', currentVersion: '1.1.1', expectedVersion: '1.2.0-alpha.0' }, + // prettier-ignore + { releaseType: 'prepatch', preId: 'alpha', currentVersion: '1.1.1', expectedVersion: '1.1.2-alpha.0' }, + // prettier-ignore + { releaseType: 'prerelease', currentVersion: '1.1.1-alpha.5', expectedVersion: '1.1.1-alpha.6' }, + // prettier-ignore + { releaseType: 'prerelease', preId: 'alpha', currentVersion: '1.1.1-alpha.5', expectedVersion: '1.1.1-alpha.6' }, + // prettier-ignore + { releaseType: 'prerelease', preId: 'beta', currentVersion: '1.1.1-alpha.10', expectedVersion: '1.1.1-beta.0' }, + // prettier-ignore + { releaseType: 'major', currentVersion: '1.1.1-rc.10', expectedVersion: '2.0.0' }, + // prettier-ignore + { releaseType: 'minor', currentVersion: '1.1.1-rc.10', expectedVersion: '1.2.0' }, + // prettier-ignore + { releaseType: 'patch', currentVersion: '1.1.1-rc.10', expectedVersion: '1.1.1' }, + // prettier-ignore + { exact: '4.2.0-canary.69', currentVersion: '1.1.1-rc.10', expectedVersion: '4.2.0-canary.69' }, + ])( + 'bump with type: "$releaseType", pre id "$preId" or exact "$exact", from: $currentVersion, to: $expectedVersion', + async ({ releaseType, preId, exact, currentVersion, expectedVersion }) => { + fsExtra.__setMockFiles({ + [CODE_PACKAGE_JSON_PATH]: JSON.stringify({ version: currentVersion }), + [MANAGER_API_VERSION_PATH]: `export const version = "${currentVersion}";`, + [VERSIONS_PATH]: `export default { "@storybook/addon-a11y": "${currentVersion}" };`, + [A11Y_PACKAGE_JSON_PATH]: JSON.stringify({ + version: currentVersion, + dependencies: { + '@storybook/core-server': currentVersion, + 'unrelated-package-a': '1.0.0', + }, + devDependencies: { + 'unrelated-package-b': currentVersion, + '@storybook/core-common': `^${currentVersion}`, + }, + peerDependencies: { + '@storybook/preview-api': `*`, + '@storybook/svelte': '0.1.1', + '@storybook/manager-api': `~${currentVersion}`, + }, + }), + [VERSIONS_PATH]: `export default { "@storybook/addon-a11y": "${currentVersion}" };`, + }); + + await version({ releaseType, preId, exact }); + + expect(fsExtra.writeJson).toHaveBeenCalledWith( + CODE_PACKAGE_JSON_PATH, + { version: expectedVersion }, + { spaces: 2 } + ); + expect(fsExtra.writeFile).toHaveBeenCalledWith( + MANAGER_API_VERSION_PATH, + `export const version = "${expectedVersion}";` + ); + expect(fsExtra.writeFile).toHaveBeenCalledWith( + VERSIONS_PATH, + `export default { "@storybook/addon-a11y": "${expectedVersion}" };` + ); + expect(fsExtra.writeJson).toHaveBeenCalledWith( + A11Y_PACKAGE_JSON_PATH, + expect.objectContaining({ + // should update package version + version: expectedVersion, + dependencies: { + // should update storybook dependencies matching current version + '@storybook/core-server': expectedVersion, + 'unrelated-package-a': '1.0.0', + }, + devDependencies: { + // should not update non-storybook dependencies, even if they match current version + 'unrelated-package-b': currentVersion, + // should update dependencies with range modifiers correctly (e.g. ^1.0.0 -> ^2.0.0) + '@storybook/core-common': `^${expectedVersion}`, + }, + peerDependencies: { + // should not update storybook depenedencies if they don't match current version + '@storybook/preview-api': `*`, + '@storybook/svelte': '0.1.1', + '@storybook/manager-api': `~${expectedVersion}`, + }, + }), + { spaces: 2 } + ); + expect(execaCommand).toHaveBeenCalledWith('yarn install --mode=update-lockfile', { + cwd: path.join(CODE_DIR_PATH), + stdio: undefined, + }); + } + ); +}); diff --git a/scripts/release/generate-pr-description.ts b/scripts/release/generate-pr-description.ts new file mode 100644 index 000000000000..eaf739761eac --- /dev/null +++ b/scripts/release/generate-pr-description.ts @@ -0,0 +1,296 @@ +/* eslint-disable no-console */ +import chalk from 'chalk'; +import program from 'commander'; +import { z } from 'zod'; +import dedent from 'ts-dedent'; +import { setOutput } from '@actions/core'; +import type { Change } from './utils/get-changes'; +import { getChanges } from './utils/get-changes'; +import { getCurrentVersion } from './get-current-version'; + +program + .name('generate-pr-description') + .description('generate a PR description for a release') + .option( + '-C, --current-version ', + 'Which version to generate changelog from, eg. "7.0.7". Defaults to the version at code/package.json' + ) + .option('-N, --next-version ', 'Which version to generate changelog to, eg. "7.0.8"') + .option('-P, --unpicked-patches', 'Set to only consider PRs labeled with "patch" label') + .option( + '-M, --manual-cherry-picks ', + 'A stringified JSON array of commit hashes, of patch PRs that needs to be cherry-picked manually' + ) + .option('-V, --verbose', 'Enable verbose logging', false); + +const optionsSchema = z.object({ + currentVersion: z.string().optional(), + nextVersion: z.string().optional(), + unpickedPatches: z.boolean().optional(), + manualCherryPicks: z + .string() + .default('[]') + .transform((val) => JSON.parse(val)) + .refine((val) => Array.isArray(val)), + verbose: z.boolean().optional(), +}); + +type Options = { + currentVersion?: string; + nextVersion?: string; + unpickedPatches?: boolean; + manualCherryPicks?: string[]; + verbose: boolean; +}; + +const LABELS_BY_IMPORTANCE = { + 'BREAKING CHANGE': '❗ Breaking Change', + 'feature request': '✨ Feature Request', + bug: 'πŸ› Bug', + maintenance: 'πŸ”§ Maintenance', + dependencies: 'πŸ“¦ Dependencies', + documentation: 'πŸ“ Documentation', + build: 'πŸ—οΈ Build', + unknown: '❔ Missing Label', +} as const; + +const CHANGE_TITLES_TO_IGNORE = [ + /^bump version.*/i, + /^merge branch.*/i, + /\[skip ci\]/i, + /\[ci skip\]/i, +]; + +export const mapToChangelist = ({ + changes, + isRelease, +}: { + changes: Change[]; + isRelease: boolean; +}): string => { + return changes + .filter((change) => { + // eslint-disable-next-line no-restricted-syntax + for (const titleToIgnore of CHANGE_TITLES_TO_IGNORE) { + if (change.title.match(titleToIgnore)) { + return false; + } + } + return true; + }) + .map((change) => { + const lines: string[] = []; + if (!change.pull) { + lines.push(`- **⚠️ Direct commit**: ${change.title} ${change.links.commit}`); + if (isRelease) { + lines.push('\t- [ ] The change is appropriate for the version bump'); + } + return lines.join('\n'); + } + + const label = (change.labels + ?.filter((l) => Object.keys(LABELS_BY_IMPORTANCE).includes(l)) + .sort( + (a, b) => + Object.keys(LABELS_BY_IMPORTANCE).indexOf(a) - + Object.keys(LABELS_BY_IMPORTANCE).indexOf(b) + )[0] || 'unknown') as keyof typeof LABELS_BY_IMPORTANCE; + + lines.push(`- **${LABELS_BY_IMPORTANCE[label]}**: ${change.title} ${change.links.pull}`); + + if (isRelease) { + lines.push('\t- [ ] The change is appropriate for the version bump'); + lines.push('\t- [ ] The PR is labeled correctly'); + lines.push('\t- [ ] The PR title is correct'); + } + return lines.join('\n'); + }) + .join('\n'); +}; + +export const mapCherryPicksToTodo = ({ + commits, + changes, + verbose, +}: { + commits: string[]; + changes: Change[]; + verbose?: boolean; +}): string => { + const list = commits + .map((commit) => { + const foundChange = changes.find((change) => change.commit === commit.substring(0, 7)); + if (!foundChange) { + throw new Error( + `Cherry pick commit "${commit}" not found in changes, this should not happen?!` + ); + } + return `- [ ] ${foundChange.links.pull}: \`git cherry-pick -m1 -x ${commit}\``; + }) + .join('\n'); + + if (verbose) { + console.log(`πŸ’ Cherry pick list:\n${list}`); + } + return dedent`## πŸ’ Manual cherry picking needed! + + The following pull requests could not be cherry-picked automatically because it resulted in merge conflicts. + For each pull request below, you need to either manually cherry pick it, or discard it by removing the "patch" label from the PR and re-generate this PR. + + ${list}`; +}; + +export const generateReleaseDescription = ({ + currentVersion, + nextVersion, + changeList, + changelogText, + manualCherryPicks, +}: { + currentVersion: string; + nextVersion: string; + changeList: string; + changelogText: string; + manualCherryPicks?: string; +}): string => { + return ( + dedent`This is an automated pull request that bumps the version from \`${currentVersion}\` to \`${nextVersion}\`. + Once this pull request is merged, it will trigger a new release of version \`${nextVersion}\`. + If you're not a core maintainer with permissions to release you can ignore this pull request. + + ## To do + + Before merging the PR, there are a few QA steps to go through: + + - [ ] Add the "freeze" label to this PR, to ensure it doesn't get automatically forced pushed by new changes. + + And for each change below: + + 1. Ensure the change is appropriate for the version bump. E.g. patch release should only contain patches, not new or de-stabilizing features. If a change is not appropriate, revert the PR. + 2. Ensure the PR is labeled correctly with "BREAKING CHANGE", "feature request", "maintainance", "bug", "build" or "documentation". + 3. Ensure the PR title is correct, and follows the format "[Area]: [Summary]", e.g. *"React: Fix hooks in CSF3 render functions"*. If it is not correct, change the title in the PR. + - Areas include: React, Vue, Core, Docs, Controls, etc. + - First word of summary indicates the type: β€œAdd”, β€œFix”, β€œUpgrade”, etc. + - The entire title should fit on a line + + This is a list of all the PRs merged and commits pushed directly to \`next\`, that will be part of this release: + + ${changeList} + + ${manualCherryPicks || ''} + + If you've made any changes doing the above QA (change PR titles, revert PRs), manually trigger a re-generation of this PR with [this workflow](https://github.com/storybookjs/storybook/actions/workflows/prepare-prerelease.yml) and wait for it to finish. It will wipe your progress in this to do, which is expected. + + When everything above is done: + - [ ] Merge this PR + - [ ] [Follow the publish workflow run and see it finishes succesfully](https://github.com/storybookjs/storybook/actions/workflows/publish.yml) + + --- + + # Generated changelog + + ${changelogText}` + // don't mention contributors in the release PR, to avoid spamming them + .replaceAll('[@', '[@ ') + .replaceAll('"', '\\"') + .replaceAll('`', '\\`') + .replaceAll("'", "\\'") + ); +}; + +export const generateNonReleaseDescription = ( + changeList: string, + manualCherryPicks?: string +): string => { + return ( + dedent`This is an automated pull request. None of the changes requires a version bump, they are only internal or documentation related. Merging this PR will not trigger a new release, but documentation will be updated. + If you're not a core maintainer with permissions to release you can ignore this pull request. + + This is a list of all the PRs merged and commits pushed directly to \`next\` since the last release: + + ${changeList} + + ${manualCherryPicks || ''} + + If you've made any changes (change PR titles, revert PRs), manually trigger a re-generation of this PR with [this workflow](https://github.com/storybookjs/storybook/actions/workflows/prepare-prerelease.yml) and wait for it to finish. + + When everything above is done: + - [ ] Merge this PR + - [ ] [Approve the publish workflow run](https://github.com/storybookjs/storybook/actions/workflows/publish.yml)` + // don't mention contributors in the release PR, to avoid spamming them + .replaceAll('[@', '[@ ') + .replaceAll('"', '\\"') + .replaceAll('`', '\\`') + .replaceAll("'", "\\'") + ); +}; + +export const run = async (rawOptions: unknown) => { + const { nextVersion, unpickedPatches, verbose, manualCherryPicks, ...options } = + optionsSchema.parse(rawOptions) as Options; + + if (!nextVersion) { + console.log( + '🚨 --next-version option not specified, generating PR description assuming no release is needed' + ); + } + + const currentVersion = options.currentVersion || (await getCurrentVersion()); + + console.log( + `πŸ’¬ Generating PR description for ${chalk.blue(nextVersion)} between ${chalk.green( + currentVersion + )} and ${chalk.green('HEAD')}` + ); + + const { changes, changelogText } = await getChanges({ + version: nextVersion, + from: `v${currentVersion}`, + to: 'HEAD', + unpickedPatches, + verbose, + }); + + const hasCherryPicks = manualCherryPicks?.length > 0; + + const description = nextVersion + ? generateReleaseDescription({ + currentVersion, + nextVersion, + changeList: mapToChangelist({ changes, isRelease: true }), + changelogText, + ...(hasCherryPicks && { + manualCherryPicks: mapCherryPicksToTodo({ + commits: manualCherryPicks, + changes, + verbose, + }), + }), + }) + : generateNonReleaseDescription( + mapToChangelist({ changes, isRelease: false }), + hasCherryPicks + ? mapCherryPicksToTodo({ + commits: manualCherryPicks, + changes, + verbose, + }) + : undefined + ); + + if (process.env.GITHUB_ACTIONS === 'true') { + setOutput('description', description); + } + console.log(`βœ… Generated PR description for ${chalk.blue(nextVersion)}`); + if (verbose) { + console.log(description); + } +}; + +if (require.main === module) { + const parsed = program.parse(); + run(parsed.opts()).catch((err) => { + console.error(err); + process.exit(1); + }); +} diff --git a/scripts/release/get-changelog-from-file.ts b/scripts/release/get-changelog-from-file.ts new file mode 100644 index 000000000000..78514d7a68af --- /dev/null +++ b/scripts/release/get-changelog-from-file.ts @@ -0,0 +1,67 @@ +/* eslint-disable no-console */ +import { setOutput } from '@actions/core'; +import chalk from 'chalk'; +import { program } from 'commander'; +import { readFile } from 'fs-extra'; +import path from 'path'; +import semver from 'semver'; +import dedent from 'ts-dedent'; +import { getCurrentVersion } from './get-current-version'; + +program + .name('get-changelog-from-file') + .description( + 'get changelog entry for specific version. If not version argument specified it will use the current version in code/package.json' + ) + .arguments('[version]') + .option('-E, --no-escape', 'Escape quote-like characters, so the output is safe in CLIs', true) + .option('-V, --verbose', 'Enable verbose logging', false); + +export const getChangelogFromFile = async (args: { + version?: string; + escape?: boolean; + verbose?: boolean; +}) => { + const version = args.version || (await getCurrentVersion()); + const isPrerelease = semver.prerelease(version) !== null; + const changelogFilename = isPrerelease ? 'CHANGELOG.prerelease.md' : 'CHANGELOG.md'; + const changelogPath = path.join(__dirname, '..', '..', changelogFilename); + + console.log(`πŸ“ Getting changelog from ${chalk.blue(changelogPath)}`); + + const fullChangelog = await readFile(changelogPath, 'utf-8'); + const changelogForVersion = fullChangelog.split(/(^|\n)## /).find((v) => v.startsWith(version)); + if (!changelogForVersion) { + throw new Error( + `Could not find changelog entry for version ${chalk.blue(version)} in ${chalk.green( + changelogPath + )}` + ); + } + const result = args.escape + ? `## ${changelogForVersion}` + .replaceAll('"', '\\"') + .replaceAll('`', '\\`') + .replaceAll("'", "\\'") + : `## ${changelogForVersion}`; + + console.log(dedent`πŸ“ Changelog entry found: + ${result}`); + + if (process.env.GITHUB_ACTIONS === 'true') { + setOutput('changelog', result); + } + return result; +}; + +if (require.main === module) { + const parsed = program.parse(); + getChangelogFromFile({ + version: parsed.args[0], + escape: parsed.opts().escape, + verbose: parsed.opts().verbose, + }).catch((err) => { + console.error(err); + process.exit(1); + }); +} diff --git a/scripts/release/get-current-version.ts b/scripts/release/get-current-version.ts new file mode 100644 index 000000000000..413c101640b6 --- /dev/null +++ b/scripts/release/get-current-version.ts @@ -0,0 +1,25 @@ +/* eslint-disable no-console */ +import chalk from 'chalk'; +import { setOutput } from '@actions/core'; +import path from 'path'; +import { readJson } from 'fs-extra'; + +const CODE_DIR_PATH = path.join(__dirname, '..', '..', 'code'); +const CODE_PACKAGE_JSON_PATH = path.join(CODE_DIR_PATH, 'package.json'); + +export const getCurrentVersion = async () => { + console.log(`πŸ“ Reading current version of Storybook...`); + const { version } = (await readJson(CODE_PACKAGE_JSON_PATH)) as { version: string }; + if (process.env.GITHUB_ACTIONS === 'true') { + setOutput('current-version', version); + } + console.log(`πŸ“¦ Current version is ${chalk.green(version)}`); + return version; +}; + +if (require.main === module) { + getCurrentVersion().catch((err) => { + console.error(err); + process.exit(1); + }); +} diff --git a/scripts/release/get-version-changelog.ts b/scripts/release/get-version-changelog.ts new file mode 100644 index 000000000000..3ece5480236d --- /dev/null +++ b/scripts/release/get-version-changelog.ts @@ -0,0 +1,35 @@ +/* eslint-disable no-console */ +import { setOutput } from '@actions/core'; +import chalk from 'chalk'; +import { program } from 'commander'; +import { getCurrentVersion } from './get-current-version'; +import { getChanges } from './utils/get-changes'; + +program + .name('get-version-changelog') + .description( + 'get changelog for specific version. If no version argument specified it will use the current version in code/package.json' + ) + .arguments('[version]') + .option('-V, --verbose', 'Enable verbose logging', false); + +export const getVersionChangelog = async (args: { version?: string; verbose?: boolean }) => { + const version = args.version || (await getCurrentVersion()); + + console.log(`πŸ“ Getting changelog for version ${chalk.blue(version)}`); + + const { changelogText } = await getChanges({ from: version, version, verbose: args.verbose }); + + if (process.env.GITHUB_ACTIONS === 'true') { + setOutput('changelog', changelogText); + } + return changelogText; +}; + +if (require.main === module) { + const parsed = program.parse(); + getVersionChangelog({ version: parsed.args[0], verbose: parsed.opts().verbose }).catch((err) => { + console.error(err); + process.exit(1); + }); +} diff --git a/scripts/release/is-pr-frozen.ts b/scripts/release/is-pr-frozen.ts new file mode 100644 index 000000000000..c96259d3fa40 --- /dev/null +++ b/scripts/release/is-pr-frozen.ts @@ -0,0 +1,101 @@ +/* eslint-disable no-console */ +import chalk from 'chalk'; +import program from 'commander'; +import { simpleGit } from 'simple-git'; +import { setOutput } from '@actions/core'; +import path from 'path'; +import { readJson } from 'fs-extra'; +import { getPullInfoFromCommit } from './utils/get-github-info'; + +program + .name('is-pr-frozen') + .description( + 'returns true if the versioning pull request associated with the current branch has the "freeze" label' + ) + .option('-P, --patch', 'Look for patch PR instead of prerelease PR', false) + .option('-V, --verbose', 'Enable verbose logging', false); + +const git = simpleGit(); + +const CODE_DIR_PATH = path.join(__dirname, '..', '..', 'code'); +const CODE_PACKAGE_JSON_PATH = path.join(CODE_DIR_PATH, 'package.json'); + +const getCurrentVersion = async () => { + console.log(`πŸ“ Reading current version of Storybook...`); + const { version } = await readJson(CODE_PACKAGE_JSON_PATH); + return version; +}; + +const getRepo = async (verbose?: boolean): Promise => { + const remotes = await git.getRemotes(true); + const originRemote = remotes.find((remote) => remote.name === 'origin'); + if (!originRemote) { + console.error( + 'Could not determine repository URL because no remote named "origin" was found. Remotes found:' + ); + console.dir(remotes, { depth: null, colors: true }); + throw new Error('No remote named "origin" found'); + } + const pushUrl = originRemote.refs.push; + const repo = pushUrl.replace(/\.git$/, '').replace(/.*:(\/\/github\.com\/)*/, ''); + if (verbose) { + console.log(`πŸ“¦ Extracted repo: ${chalk.blue(repo)}`); + } + return repo; +}; + +export const run = async (options: unknown) => { + const { verbose, patch } = options as { verbose?: boolean; patch?: boolean }; + + const version = await getCurrentVersion(); + const branch = `version-from-${patch ? 'patch' : 'prerelease'}-${version}`; + + console.log(`πŸ’¬ Determining if pull request from branch '${chalk.blue(branch)}' is frozen`); + + console.log(`⬇️ Fetching remote 'origin/${branch}'...`); + try { + await git.fetch('origin', branch, { '--depth': 1 }); + } catch (error) { + console.warn( + `❗ Could not fetch remote 'origin/${branch}', it probably does not exist yet, which is okay` + ); + console.warn(error); + console.log(`πŸ’§ Pull request doesn't exist yet! 😎`); + if (process.env.GITHUB_ACTIONS === 'true') { + setOutput('frozen', false); + } + return false; + } + + const commit = await git.revparse(`origin/${branch}`); + console.log(`πŸ” Found commit: ${commit}`); + + const repo = await getRepo(verbose); + + const pullRequest = await getPullInfoFromCommit({ repo, commit }).catch((err) => { + console.error(`🚨 Could not get pull requests from commit: ${commit}`); + console.error(err); + throw err; + }); + console.log(`πŸ” Found pull request: + ${JSON.stringify(pullRequest, null, 2)}`); + + const isFrozen = pullRequest.labels?.includes('freeze'); + if (process.env.GITHUB_ACTIONS === 'true') { + setOutput('frozen', isFrozen); + } + if (isFrozen) { + console.log(`🧊 Pull request is frozen! πŸ₯Ά`); + } else { + console.log(`πŸ”₯ Pull request is on fire! πŸ₯΅`); + } + return isFrozen; +}; + +if (require.main === module) { + const parsed = program.parse(); + run(parsed.opts()).catch((err) => { + console.error(err); + process.exit(1); + }); +} diff --git a/scripts/release/is-prerelease.ts b/scripts/release/is-prerelease.ts new file mode 100644 index 000000000000..5a3f7ef11c9e --- /dev/null +++ b/scripts/release/is-prerelease.ts @@ -0,0 +1,34 @@ +/* eslint-disable no-console */ +import chalk from 'chalk'; +import program from 'commander'; +import { setOutput } from '@actions/core'; +import semver from 'semver'; +import { getCurrentVersion } from './get-current-version'; + +program + .name('is-prerelease') + .description('returns true if the current version is a prerelease') + .option('-V, --verbose', 'Enable verbose logging', false); + +export const isPrerelease = async (versionArg?: string) => { + const version = versionArg || (await getCurrentVersion()); + const result = semver.prerelease(version) !== null; + + if (process.env.GITHUB_ACTIONS === 'true') { + setOutput('prerelease', result); + } + console.log( + `πŸ“¦ Current version ${chalk.green(version)} ${ + result ? chalk.blue('IS') : chalk.red('IS NOT') + } a prerelease` + ); + + return result; +}; + +if (require.main === module) { + isPrerelease().catch((err) => { + console.error(err); + process.exit(1); + }); +} diff --git a/scripts/release/is-version-published.ts b/scripts/release/is-version-published.ts new file mode 100644 index 000000000000..b5acd8c90ab7 --- /dev/null +++ b/scripts/release/is-version-published.ts @@ -0,0 +1,86 @@ +/* eslint-disable no-console */ +import chalk from 'chalk'; +import program from 'commander'; +import { setOutput } from '@actions/core'; +import fetch from 'node-fetch'; +import { getCurrentVersion } from './get-current-version'; + +program + .name('is-prerelease [version]') + .description('returns true if the current version is a prerelease') + .arguments('[version]'); + +const isVersionPublished = async ({ + packageName, + version, + verbose, +}: { + packageName: string; + version: string; + verbose?: boolean; +}) => { + const prettyPackage = `${chalk.blue(packageName)}@${chalk.green(version)}`; + console.log(`β›… Checking if ${prettyPackage} is published...`); + + if (verbose) { + console.log(`Fetching from npm:`); + console.log(`https://registry.npmjs.org/${chalk.blue(packageName)}/${chalk.green(version)}`); + } + const response = await fetch(`https://registry.npmjs.org/${packageName}/${version}`); + if (response.status === 404) { + console.log(`🌀️ ${prettyPackage} is not published`); + return false; + } + if (response.status !== 200) { + console.error( + `Unexpected status code when checking the current version on npm: ${response.status}` + ); + console.error(await response.text()); + throw new Error( + `Unexpected status code when checking the current version on npm: ${response.status}` + ); + } + const data = await response.json(); + if (verbose) { + console.log(`Response from npm:`); + console.log(data); + } + if (data.version !== version) { + // this should never happen + console.error( + `Unexpected version received when checking the current version on npm: ${data.version}` + ); + console.error(JSON.stringify(data, null, 2)); + throw new Error( + `Unexpected version received when checking the current version on npm: ${data.version}` + ); + } + + console.log(`β›ˆοΈ ${prettyPackage} is published`); + return true; +}; + +export const run = async (args: unknown[], options: unknown) => { + const { verbose } = options as { verbose?: boolean }; + + const version = (args[0] as string) || (await getCurrentVersion()); + + const isAlreadyPublished = await isVersionPublished({ + version, + packageName: '@storybook/manager-api', + verbose, + }); + + if (process.env.GITHUB_ACTIONS === 'true') { + setOutput('published', isAlreadyPublished); + } + return isAlreadyPublished; +}; + +if (require.main === module) { + const parsed = program.parse(); + run(parsed.args, parsed.opts()).catch((err) => { + console.error(err); + process.exit(1); + }); +} diff --git a/scripts/release/label-patches.ts b/scripts/release/label-patches.ts new file mode 100644 index 000000000000..bf18358f877a --- /dev/null +++ b/scripts/release/label-patches.ts @@ -0,0 +1,83 @@ +import program from 'commander'; +import { v4 as uuidv4 } from 'uuid'; +import ora from 'ora'; +import { getLabelIds, githubGraphQlClient } from './utils/github-client'; +import { getPullInfoFromCommits, getRepo } from './utils/get-changes'; +import { getLatestTag, git } from './utils/git-client'; + +program + .name('label-patches') + .description('Label all patches applied in current branch up to the latest release tag.'); + +async function labelPR(id: string, labelId: string) { + await githubGraphQlClient( + ` + mutation ($input: AddLabelsToLabelableInput!) { + addLabelsToLabelable(input: $input) { + clientMutationId + } + } + `, + { input: { labelIds: [labelId], labelableId: id, clientMutationId: uuidv4() } } + ); +} + +export const run = async (_: unknown) => { + if (!process.env.GH_TOKEN) { + throw new Error('GH_TOKEN environment variable must be set, exiting.'); + } + + const spinner = ora('Looking for latest tag').start(); + const latestTag = await getLatestTag(); + spinner.succeed(`Found latest tag: ${latestTag}`); + + const spinner2 = ora(`Looking at cherry pick commits since ${latestTag}`).start(); + const commitsSinceLatest = await git.log({ from: latestTag, '--first-parent': null }); + console.log(commitsSinceLatest); + const cherryPicked = commitsSinceLatest.all.flatMap((it) => { + const result = it.body.match(/\(cherry picked from commit (\b[0-9a-f]{7,40}\b)\)/); + return result ? [result?.[1]] : []; + }); + + if (cherryPicked.length === 0) { + spinner2.fail('No cherry pick commits found to label.'); + return; + } + + const repo = await getRepo(); + const pullRequests = ( + await getPullInfoFromCommits({ + repo, + commits: cherryPicked.map((hash) => ({ hash })), + }) + ).filter((it) => it.id != null); + + if (pullRequests.length === 0) { + spinner2.fail( + `Found picks: ${cherryPicked.join(', ')}, but no associated pull request found to label.` + ); + return; + } + + const commitWithPr = pullRequests.map((pr) => `Commit: ${pr.commit}\n PR: ${pr.links.pull}`); + + spinner2.succeed(`Found the following picks πŸ’:\n ${commitWithPr.join('\n')}`); + + const spinner3 = ora(`Labeling the PRs with the picked label...`).start(); + try { + const labelToId = await getLabelIds({ repo, labelNames: ['picked'] }); + await Promise.all(pullRequests.map((pr) => labelPR(pr.id, labelToId.picked))); + spinner3.succeed(`Successfully labeled all PRs with the picked label.`); + } catch (e) { + spinner3.fail(`Something went wrong when labelling the PRs.`); + console.error(e); + } +}; + +if (require.main === module) { + const options = program.parse(process.argv); + run(options).catch((err) => { + console.error(err); + process.exit(1); + }); +} diff --git a/scripts/release/pick-patches.ts b/scripts/release/pick-patches.ts new file mode 100644 index 000000000000..20a3f2d6a870 --- /dev/null +++ b/scripts/release/pick-patches.ts @@ -0,0 +1,154 @@ +/* eslint-disable no-console */ +/* eslint-disable no-await-in-loop */ +import program from 'commander'; +import chalk from 'chalk'; +import { v4 as uuidv4 } from 'uuid'; +import type { GraphQlQueryResponseData } from '@octokit/graphql'; +import ora from 'ora'; +import { simpleGit } from 'simple-git'; +import { setOutput } from '@actions/core'; +import { getUnpickedPRs } from './utils/get-unpicked-prs'; +import { githubGraphQlClient } from './utils/github-client'; + +program.name('pick-patches').description('Cherry pick patch PRs back to main'); + +const logger = console; + +const OWNER = 'storybookjs'; +const REPO = 'storybook'; +const SOURCE_BRANCH = 'next'; + +const git = simpleGit(); + +interface PR { + number: number; + id: string; + branch: string; + title: string; + mergeCommit: string; +} + +const LABEL = { + PATCH: 'patch', + PICKED: 'picked', + DOCUMENTATION: 'documentation', +} as const; + +function formatPR(pr: PR): string { + return `https://github.com/${OWNER}/${REPO}/pull/${pr.number} "${pr.title}" ${chalk.yellow( + pr.mergeCommit + )}`; +} + +// @ts-expect-error not used atm +async function getLabelIds(labelNames: string[]) { + const query = labelNames.join('+'); + const result = await githubGraphQlClient( + ` + query ($owner: String!, $repo: String!, $q: String!) { + repository(owner: $owner, name: $repo) { + labels(query: $q, first: 10) { + nodes { + id + name + description + } + } + } + } + `, + { + owner: OWNER, + repo: REPO, + q: query, + } + ); + + const { labels } = result.repository; + const labelToId = {} as Record; + labels.nodes.forEach((label: { name: string; id: string }) => { + labelToId[label.name] = label.id; + }); + return labelToId; +} + +// @ts-expect-error not used atm +async function labelPR(id: string, labelToId: Record) { + await githubGraphQlClient( + ` + mutation ($input: AddLabelsToLabelableInput!) { + addLabelsToLabelable(input: $input) { + clientMutationId + } + } + `, + { + input: { + labelIds: [labelToId[LABEL.PICKED]], + labelableId: id, + clientMutationId: uuidv4(), + }, + } + ); +} + +export const run = async (_: unknown) => { + if (!process.env.GH_TOKEN) { + logger.error('GH_TOKEN environment variable must be set, exiting.'); + process.exit(1); + } + + const sourceBranch = SOURCE_BRANCH; + + const spinner = ora('Searching for patch PRs to cherry-pick').start(); + + // const labelToId = await getLabelIds(Object.values(LABEL)); + const patchPRs = await getUnpickedPRs(sourceBranch); + + if (patchPRs.length > 0) { + spinner.succeed(`Found ${patchPRs.length} PRs to cherry-pick to main.`); + } else { + spinner.warn('No PRs found.'); + } + + const failedCherryPicks: string[] = []; + + // eslint-disable-next-line no-restricted-syntax + for (const pr of patchPRs) { + const prSpinner = ora(`Cherry picking #${pr.number}`).start(); + + try { + await git.raw(['cherry-pick', '-m', '1', '-x', pr.mergeCommit]); + prSpinner.succeed(`Picked: ${formatPR(pr)}`); + } catch (pickError) { + prSpinner.fail(`Failed to automatically pick: ${formatPR(pr)}`); + logger.error(pickError.message); + const abort = ora(`Aborting cherry pick for merge commit: ${pr.mergeCommit}`).start(); + try { + await git.raw(['cherry-pick', '--abort']); + abort.stop(); + } catch (abortError) { + abort.warn(`Failed to abort cherry pick (${pr.mergeCommit})`); + logger.error(pickError.message); + } + failedCherryPicks.push(pr.mergeCommit); + prSpinner.info( + `This PR can be picked manually with: ${chalk.grey( + `git cherry-pick -m1 -x ${pr.mergeCommit}` + )}` + ); + } + } + + if (process.env.GITHUB_ACTIONS === 'true') { + setOutput('failed-cherry-picks', JSON.stringify(failedCherryPicks)); + } +}; + +if (require.main === module) { + const options = program.parse(process.argv); + run(options).catch((err) => { + console.error(err); + process.exit(1); + }); +} diff --git a/scripts/release/publish.ts b/scripts/release/publish.ts new file mode 100644 index 000000000000..f3d3516b6a98 --- /dev/null +++ b/scripts/release/publish.ts @@ -0,0 +1,206 @@ +/* eslint-disable no-console */ +import chalk from 'chalk'; +import path from 'path'; +import program from 'commander'; +import semver from 'semver'; +import { z } from 'zod'; +import { readJson } from 'fs-extra'; +import fetch from 'node-fetch'; +import dedent from 'ts-dedent'; +import { execaCommand } from '../utils/exec'; + +program + .name('publish') + .description('publish all packages') + .requiredOption( + '-T, --tag ', + 'Specify which distribution tag to set for the version being published. Required, since leaving it undefined would publish with the "latest" tag' + ) + .option('-D, --dry-run', 'Do not publish, only output to shell', false) + .option('-V, --verbose', 'Enable verbose logging', false); + +const optionsSchema = z + .object({ + tag: z.string(), + verbose: z.boolean().optional(), + dryRun: z.boolean().optional(), + }) + .refine((schema) => (schema.tag ? !semver.valid(schema.tag) : true), { + message: + 'The tag can not be a valid semver version, it must be a plain string like "next" or "latest"', + }); + +type Options = { + tag: string; + verbose: boolean; + dryRun?: boolean; +}; + +const CODE_DIR_PATH = path.join(__dirname, '..', '..', 'code'); +const CODE_PACKAGE_JSON_PATH = path.join(CODE_DIR_PATH, 'package.json'); + +const validateOptions = (options: { [key: string]: any }): options is Options => { + optionsSchema.parse(options); + return true; +}; + +const getCurrentVersion = async (verbose?: boolean) => { + if (verbose) { + console.log(`πŸ“ Reading current version of Storybook...`); + } + const { version } = await readJson(CODE_PACKAGE_JSON_PATH); + console.log(`πŸ“ Current version of Storybook is ${chalk.green(version)}`); + return version; +}; + +const isCurrentVersionPublished = async ({ + packageName, + currentVersion, + verbose, +}: { + packageName: string; + currentVersion: string; + verbose?: boolean; +}) => { + const prettyPackage = `${chalk.blue(packageName)}@${chalk.green(currentVersion)}`; + console.log(`β›… Checking if ${prettyPackage} is published...`); + + if (verbose) { + console.log(`Fetching from npm:`); + console.log( + `https://registry.npmjs.org/${chalk.blue(packageName)}/${chalk.green(currentVersion)}` + ); + } + const response = await fetch(`https://registry.npmjs.org/${packageName}/${currentVersion}`); + if (response.status === 404) { + console.log(`🌀️ ${prettyPackage} is not published`); + return false; + } + if (response.status !== 200) { + console.error( + `Unexpected status code when checking the current version on npm: ${response.status}` + ); + console.error(await response.text()); + throw new Error( + `Unexpected status code when checking the current version on npm: ${response.status}` + ); + } + const data = await response.json(); + if (verbose) { + console.log(`Response from npm:`); + console.log(data); + } + if (data.version !== currentVersion) { + // this should never happen + console.error( + `Unexpected version received when checking the current version on npm: ${data.version}` + ); + console.error(JSON.stringify(data, null, 2)); + throw new Error( + `Unexpected version received when checking the current version on npm: ${data.version}` + ); + } + + console.log(`β›ˆοΈ ${prettyPackage} is published`); + return true; +}; + +const buildAllPackages = async () => { + console.log(`πŸ—οΈ Building all packages...`); + await execaCommand('yarn task --task=compile --start-from=compile --no-link', { + stdio: 'inherit', + cwd: CODE_DIR_PATH, + }); + console.log(`πŸ—οΈ Packages successfully built`); +}; + +const publishAllPackages = async ({ + tag, + verbose, + dryRun, +}: { + tag: string; + verbose?: boolean; + dryRun?: boolean; +}) => { + console.log(`πŸ“¦ Publishing all packages...`); + const command = `yarn workspaces foreach --parallel --no-private --verbose npm publish --tolerate-republish --tag ${tag}`; + if (verbose) { + console.log(`πŸ“¦ Executing: ${command}`); + } + if (dryRun) { + console.log(`πŸ“¦ Dry run, skipping publish. Would have executed: + ${chalk.blue(command)}`); + return; + } + + // Note this is to fool `ts-node` into not turning the `import()` into a `require()`. + // See: https://github.com/TypeStrong/ts-node/discussions/1290 + // prettier-ignore + const pRetry = ( + // eslint-disable-next-line @typescript-eslint/no-implied-eval + (await new Function('specifier', 'return import(specifier)')( + 'p-retry' + )) as typeof import('p-retry') + ).default; + /** + * 'yarn npm publish' will fail if just one package fails to publish. + * But it will continue through with all the other packages, and --tolerate-republish makes it okay to publish the same version again. + * So we can safely retry the whole publishing process if it fails. + * It's not uncommon for the registry to fail often, which Yarn catches by checking the registry after a package has been published. + */ + await pRetry( + () => + execaCommand(command, { + stdio: 'inherit', + cwd: CODE_DIR_PATH, + }), + { + retries: 4, + onFailedAttempt: (error) => + console.log( + chalk.yellow( + dedent`❗One or more packages failed to publish, retrying... + This was attempt number ${error.attemptNumber}, there are ${error.retriesLeft} retries left. 🀞` + ) + ), + } + ); + console.log(`πŸ“¦ Packages successfully published`); +}; + +export const run = async (options: unknown) => { + if (!validateOptions(options)) { + return; + } + const { tag, dryRun, verbose } = options; + + // Get the current version from code/package.json + const currentVersion = await getCurrentVersion(verbose); + const isAlreadyPublished = await isCurrentVersionPublished({ + currentVersion, + packageName: '@storybook/manager-api', + verbose, + }); + if (isAlreadyPublished) { + throw new Error( + `β›” Current version (${chalk.green(currentVersion)}) is already published, aborting.` + ); + } + await buildAllPackages(); + await publishAllPackages({ tag, verbose, dryRun }); + + console.log( + `βœ… Published all packages with version ${chalk.green(currentVersion)}${ + tag ? ` at tag ${chalk.blue(tag)}` : '' + }` + ); +}; + +if (require.main === module) { + const parsed = program.parse(); + run(parsed.opts()).catch((err) => { + console.error(err); + process.exit(1); + }); +} diff --git a/scripts/release/unreleased-changes-exists.ts b/scripts/release/unreleased-changes-exists.ts new file mode 100644 index 000000000000..8d8be843e833 --- /dev/null +++ b/scripts/release/unreleased-changes-exists.ts @@ -0,0 +1,88 @@ +/* eslint-disable no-console */ +import chalk from 'chalk'; +import program from 'commander'; +import { z } from 'zod'; +import { setOutput } from '@actions/core'; +import { intersection } from 'lodash'; +import type { Change } from './utils/get-changes'; +import { getChanges } from './utils/get-changes'; +import { getCurrentVersion } from './get-current-version'; + +program + .name('are-changes-unreleased') + .description('check if any changes since a release should be released') + .option( + '-F, --from ', + 'Which version/tag/commit to go back and check changes from. Defaults to latest release tag' + ) + .option('-P, --unpicked-patches', 'Set to only consider PRs labeled with "patch" label') + .option('-V, --verbose', 'Enable verbose logging', false); + +const optionsSchema = z.object({ + from: z.string().optional(), + unpickedPatches: z.boolean().optional(), + verbose: z.boolean().optional(), +}); + +type Options = { + from?: string; + unpickedPatches?: boolean; + verbose: boolean; +}; + +const validateOptions = (options: { [key: string]: any }): options is Options => { + optionsSchema.parse(options); + return true; +}; + +const LABELS_TO_RELEASE = ['BREAKING CHANGE', 'feature request', 'bug', 'maintenance'] as const; + +export const run = async ( + options: unknown +): Promise<{ changesToRelease: Change[]; hasChangesToRelease: boolean }> => { + if (!validateOptions(options)) { + // this will never return because the validator throws + return { changesToRelease: [], hasChangesToRelease: false }; + } + const { from, unpickedPatches, verbose } = options; + + const currentVersion = await getCurrentVersion(); + + console.log(`πŸ“ Checking if there are any unreleased changes...`); + + const { changes } = await getChanges({ + version: currentVersion, + from: from || currentVersion, + to: 'HEAD', + unpickedPatches, + verbose, + }); + + const changesToRelease = changes.filter( + ({ labels }) => intersection(LABELS_TO_RELEASE, labels).length > 0 + ); + + const hasChangesToRelease = changesToRelease.length > 0; + + if (process.env.GITHUB_ACTIONS === 'true') { + setOutput('has-changes-to-release', hasChangesToRelease); + } + if (hasChangesToRelease) { + console.log( + `${chalk.green('πŸ¦‹ The following changes are releasable')}: +${chalk.blue(changesToRelease.map(({ title, pull }) => ` #${pull}: ${title}`).join('\n'))}` + ); + } else { + console.log(chalk.red('πŸ«™ No changes to release!')); + } + + return { changesToRelease, hasChangesToRelease }; +}; + +if (require.main === module) { + const parsed = program.parse(); + run(parsed.opts()).catch((err) => { + console.error(err); + process.exit(1); + }); +} diff --git a/scripts/release/utils/__mocks__/get-github-info.js b/scripts/release/utils/__mocks__/get-github-info.js new file mode 100644 index 000000000000..2fa98bd4cb48 --- /dev/null +++ b/scripts/release/utils/__mocks__/get-github-info.js @@ -0,0 +1,4 @@ +module.exports = { + getPullInfoFromCommit: jest.fn(), + getPullInfoFromPullRequest: jest.fn(), +}; diff --git a/scripts/release/utils/get-changes.ts b/scripts/release/utils/get-changes.ts new file mode 100644 index 000000000000..1108bb25559e --- /dev/null +++ b/scripts/release/utils/get-changes.ts @@ -0,0 +1,248 @@ +/* eslint-disable no-console */ +import chalk from 'chalk'; +import semver from 'semver'; +import type { PullRequestInfo } from './get-github-info'; +import { getPullInfoFromCommit } from './get-github-info'; +import { getUnpickedPRs } from './get-unpicked-prs'; +import { git } from './git-client'; + +const LABELS_FOR_CHANGELOG = ['BREAKING CHANGE', 'feature request', 'bug', 'maintenance']; + +const getCommitAt = async (id: string, verbose?: boolean) => { + if (!semver.valid(id)) { + console.log(`πŸ” ${chalk.red(id)} is not a valid semver string, assuming it is a commit hash`); + return id; + } + const version = id.startsWith('v') ? id : `v${id}`; + const commitSha = (await git.raw(['rev-list', '-n', '1', version])).split('\n')[0]; + if (verbose) { + console.log(`πŸ” Commit at tag ${chalk.green(version)}: ${chalk.blue(commitSha)}`); + } + return commitSha; +}; + +export const getFromCommit = async (from?: string | undefined, verbose?: boolean) => { + let actualFrom = from; + if (!from) { + console.log(`πŸ” No 'from' specified, finding latest version tag, fetching all of them...`); + // await git.fetch('origin', ['--all', '--tags']); + const { latest } = await git.tags(['v*', '--sort=-v:refname', '--merged']); + if (!latest) { + throw new Error( + 'Could not automatically detect which commit to generate from, because no version tag was found in the history. Have you fetch tags?' + ); + } + actualFrom = latest; + if (verbose) { + console.log(`πŸ” No 'from' specified, found latest tag: ${chalk.blue(latest)}`); + } + } + const commit = await getCommitAt(actualFrom, verbose); + if (verbose) { + console.log(`πŸ” Found 'from' commit: ${chalk.blue(commit)}`); + } + return commit; +}; + +export const getToCommit = async (to?: string | undefined, verbose?: boolean) => { + if (!to) { + const head = await git.revparse('HEAD'); + if (verbose) { + console.log(`πŸ” No 'to' specified, HEAD is at commit: ${chalk.blue(head)}`); + } + return head; + } + + const commit = await getCommitAt(to, verbose); + if (verbose) { + console.log(`πŸ” Found 'to' commit: ${chalk.blue(commit)}`); + } + return commit; +}; + +export const getAllCommitsBetween = async ({ + from, + to, + verbose, +}: { + from: string; + to?: string; + verbose?: boolean; +}) => { + const logResult = await git.log({ from, to, '--first-parent': null }); + if (verbose) { + console.log( + `πŸ” Found ${chalk.blue(logResult.total)} commits between ${chalk.green( + `${from}` + )} and ${chalk.green(`${to}`)}:` + ); + console.dir(logResult.all, { depth: null, colors: true }); + } + return logResult.all; +}; + +export const getRepo = async (verbose?: boolean): Promise => { + const remotes = await git.getRemotes(true); + const originRemote = remotes.find((remote) => remote.name === 'origin'); + if (!originRemote) { + console.error( + 'Could not determine repository URL because no remote named "origin" was found. Remotes found:' + ); + console.dir(remotes, { depth: null, colors: true }); + throw new Error('No remote named "origin" found'); + } + const pushUrl = originRemote.refs.push; + const repo = pushUrl.replace(/\.git$/, '').replace(/.*:(\/\/github\.com\/)*/, ''); + if (verbose) { + console.log(`πŸ“¦ Extracted repo: ${chalk.blue(repo)}`); + } + return repo; +}; + +export const getPullInfoFromCommits = async ({ + repo, + commits, + verbose, +}: { + repo: string; + commits: readonly { hash: string }[]; + verbose?: boolean; +}): Promise => { + const pullRequests = await Promise.all( + commits.map((commit) => + getPullInfoFromCommit({ + repo, + commit: commit.hash, + }) + ) + ); + if (verbose) { + console.log(`πŸ” Found pull requests:`); + console.dir(pullRequests, { depth: null, colors: true }); + } + return pullRequests; +}; + +export type Change = PullRequestInfo; + +export const mapToChanges = ({ + commits, + pullRequests, + unpickedPatches, + verbose, +}: { + commits: readonly { hash: string; message?: string }[]; + pullRequests: PullRequestInfo[]; + unpickedPatches?: boolean; + verbose?: boolean; +}): Change[] => { + if (pullRequests.length !== commits.length) { + // not all commits are associated with a pull request, but the pullRequests array should still contain those commits + console.error('Pull requests and commits are not the same length, this should not happen'); + console.error(`Pull Requests: ${pullRequests.length}`); + console.dir(pullRequests, { depth: null, colors: true }); + console.error(`Commits: ${commits.length}`); + console.dir(commits, { depth: null, colors: true }); + throw new Error('Pull requests and commits are not the same length, this should not happen'); + } + const allEntries = pullRequests.map((pr, index) => { + return { + ...pr, + title: pr.title || commits[index].message, + }; + }); + + const changes: Change[] = []; + allEntries.forEach((entry) => { + // filter out any duplicate entries, eg. when multiple commits are associated with the same pull request + if (entry.pull && changes.findIndex((existing) => entry.pull === existing.pull) !== -1) { + return; + } + // filter out any entries that are not patches if unpickedPatches is set. this will also filter out direct commits + if (unpickedPatches && !entry.labels?.includes('patch')) { + return; + } + changes.push(entry); + }); + + if (verbose) { + console.log(`πŸ“ Generated changelog entries:`); + console.dir(changes, { depth: null, colors: true }); + } + return changes; +}; + +export const getChangelogText = ({ + changes, + version, +}: { + changes: Change[]; + version: string; +}): string => { + const heading = `## ${version}`; + const formattedEntries = changes + .filter((entry) => { + // don't include direct commits that are not from pull requests + if (!entry.pull) { + return false; + } + // only include PRs that with labels listed in LABELS_FOR_CHANGELOG + return entry.labels?.some((label) => LABELS_FOR_CHANGELOG.includes(label)); + }) + .map((entry) => { + const { title, links } = entry; + const { pull, commit, user } = links; + return pull + ? `- ${title} - ${pull}, thanks ${user}!` + : `- ⚠️ _Direct commit_ ${title} - ${commit} by ${user}`; + }); + const text = [heading, '', ...formattedEntries].join('\n'); + + console.log(`βœ… Generated Changelog:`); + console.log(text); + + return text; +}; + +export const getChanges = async ({ + version, + from, + to, + unpickedPatches, + verbose, +}: { + version: string; + from?: string; + to?: string; + unpickedPatches?: boolean; + verbose?: boolean; +}) => { + console.log(`πŸ’¬ Getting changes for ${chalk.blue(version)}`); + + let commits; + if (unpickedPatches) { + commits = (await getUnpickedPRs('next', verbose)).map((it) => ({ hash: it.mergeCommit })); + } else { + commits = await getAllCommitsBetween({ + from: await getFromCommit(from, verbose), + to: await getToCommit(to, verbose), + verbose, + }); + } + + const repo = await getRepo(verbose); + const pullRequests = await getPullInfoFromCommits({ repo, commits, verbose }).catch((err) => { + console.error( + `🚨 Could not get pull requests from commits, this is usually because you have unpushed commits, or you haven't set the GH_TOKEN environment variable` + ); + console.error(err); + throw err; + }); + const changes = mapToChanges({ commits, pullRequests, unpickedPatches, verbose }); + const changelogText = getChangelogText({ + changes, + version, + }); + + return { changes, changelogText }; +}; diff --git a/scripts/release/utils/get-github-info.ts b/scripts/release/utils/get-github-info.ts new file mode 100644 index 000000000000..5a5d9e9cbe1a --- /dev/null +++ b/scripts/release/utils/get-github-info.ts @@ -0,0 +1,297 @@ +/** + * This file is soft-forked from @changesets/get-github-info + * https://github.com/changesets/changesets/tree/main/packages/get-github-info + * + * The only modification is that it also returns the PR title and labels + */ + +import DataLoader from 'dataloader'; +import fetch from 'node-fetch'; + +const validRepoNameRegex = /^[\w.-]+\/[\w.-]+$/; + +type RequestData = + | { kind: 'commit'; repo: string; commit: string } + | { kind: 'pull'; repo: string; pull: number }; + +type ReposWithCommitsAndPRsToFetch = Record< + string, + ({ kind: 'commit'; commit: string } | { kind: 'pull'; pull: number })[] +>; + +function makeQuery(repos: ReposWithCommitsAndPRsToFetch) { + const query = ` + query { + ${Object.keys(repos) + .map( + (repo, i) => + `a${i}: repository( + owner: ${JSON.stringify(repo.split('/')[0])} + name: ${JSON.stringify(repo.split('/')[1])} + ) { + ${repos[repo] + .map((data) => + data.kind === 'commit' + ? `a${data.commit}: object(expression: ${JSON.stringify(data.commit)}) { + ... on Commit { + commitUrl + associatedPullRequests(first: 50) { + nodes { + number + id + title + url + mergedAt + labels(first: 50) { + nodes { + name + } + } + author { + login + url + } + } + } + author { + user { + login + url + } + } + }}` + : `pr__${data.pull}: pullRequest(number: ${data.pull}) { + url + title + author { + login + url + } + labels(first: 50) { + nodes { + name + } + } + mergeCommit { + commitUrl + abbreviatedOid + } + }` + ) + .join('\n')} + }` + ) + .join('\n')} + } + `; + return query; +} + +// why are we using dataloader? +// it provides use with two things +// 1. caching +// since getInfo will be called inside of changeset's getReleaseLine +// and there could be a lot of release lines for a single commit +// caching is important so we don't do a bunch of requests for the same commit +// 2. batching +// getReleaseLine will be called a large number of times but it'll be called at the same time +// so instead of doing a bunch of network requests, we can do a single one. +const GHDataLoader = new DataLoader( + async (requests: RequestData[]) => { + if (!process.env.GH_TOKEN) { + throw new Error( + 'Please create a GitHub personal access token at https://github.com/settings/tokens/new with `read:user` and `repo:status` permissions and add it as the GH_TOKEN environment variable' + ); + } + const repos: ReposWithCommitsAndPRsToFetch = {}; + requests.forEach(({ repo, ...data }) => { + if (repos[repo] === undefined) { + repos[repo] = []; + } + repos[repo].push(data); + }); + + const data = await fetch('https://api.github.com/graphql', { + method: 'POST', + headers: { + Authorization: `Token ${process.env.GH_TOKEN}`, + }, + body: JSON.stringify({ query: makeQuery(repos) }), + }).then((x: any) => x.json()); + + if (data.errors) { + throw new Error( + `An error occurred when fetching data from GitHub\n${JSON.stringify(data.errors, null, 2)}` + ); + } + + // this is mainly for the case where there's an authentication problem + if (!data.data) { + throw new Error(`An error occurred when fetching data from GitHub\n${JSON.stringify(data)}`); + } + + const cleanedData: Record; pull: Record }> = + {}; + Object.keys(repos).forEach((repo, index) => { + const output: { commit: Record; pull: Record } = { + commit: {}, + pull: {}, + }; + cleanedData[repo] = output; + Object.entries(data.data[`a${index}`]).forEach(([field, value]) => { + // this is "a" because that's how it was when it was first written, "a" means it's a commit not a pr + // we could change it to commit__ but then we have to get new GraphQL results from the GH API to put in the tests + if (field[0] === 'a') { + output.commit[field.substring(1)] = value; + } else { + output.pull[field.replace('pr__', '')] = value; + } + }); + }); + + return requests.map( + ({ repo, ...rest }) => + cleanedData[repo][rest.kind][rest.kind === 'pull' ? rest.pull : rest.commit] + ); + }, + { maxBatchSize: 50 } +); + +export type PullRequestInfo = { + user: string | null; + id: string | null; + title: string | null; + commit: string | null; + pull: number | null; + labels: string[] | null; + links: { + commit: string; + pull: string | null; + user: string | null; + }; +}; + +export async function getPullInfoFromCommit(request: { + commit: string; + repo: string; +}): Promise { + if (!request.commit) { + throw new Error('Please pass a commit SHA to getInfo'); + } + + if (!request.repo) { + throw new Error('Please pass a GitHub repository in the form of userOrOrg/repoName to getInfo'); + } + + if (!validRepoNameRegex.test(request.repo)) { + throw new Error( + `Please pass a valid GitHub repository in the form of userOrOrg/repoName to getInfo (it has to match the "${validRepoNameRegex.source}" pattern)` + ); + } + + const data = await GHDataLoader.load({ kind: 'commit', ...request }); + if (!data) { + return { + id: null, + user: null, + pull: null, + commit: request.commit, + title: null, + labels: null, + links: { + commit: request.commit, + pull: null, + user: null, + }, + }; + } + let user = null; + if (data.author && data.author.user) { + user = data.author.user; + } + + const associatedPullRequest = + data.associatedPullRequests && + data.associatedPullRequests.nodes && + data.associatedPullRequests.nodes.length + ? (data.associatedPullRequests.nodes as any[]).sort((a, b) => { + if (a.mergedAt === null && b.mergedAt === null) { + return 0; + } + if (a.mergedAt === null) { + return 1; + } + if (b.mergedAt === null) { + return -1; + } + const aDate = new Date(a.mergedAt); + const bDate = new Date(b.mergedAt); + if (aDate > bDate) { + return 1; + } + if (aDate < bDate) { + return -1; + } + return 0; + })[0] + : null; + if (associatedPullRequest) { + user = associatedPullRequest.author; + } + + return { + user: user ? user.login : null, + id: associatedPullRequest ? associatedPullRequest.id : null, + pull: associatedPullRequest ? associatedPullRequest.number : null, + commit: request.commit, + title: associatedPullRequest ? associatedPullRequest.title : null, + labels: associatedPullRequest + ? (associatedPullRequest.labels.nodes || []).map((label: { name: string }) => label.name) + : null, + links: { + commit: `[\`${request.commit}\`](${data.commitUrl})`, + pull: associatedPullRequest + ? `[#${associatedPullRequest.number}](${associatedPullRequest.url})` + : null, + user: user ? `[@${user.login}](${user.url})` : null, + }, + }; +} + +export async function getPullInfoFromPullRequest(request: { + pull: number; + repo: string; +}): Promise { + if (request.pull === undefined) { + throw new Error('Please pass a pull request number'); + } + + if (!request.repo) { + throw new Error('Please pass a GitHub repository in the form of userOrOrg/repoName to getInfo'); + } + + if (!validRepoNameRegex.test(request.repo)) { + throw new Error( + `Please pass a valid GitHub repository in the form of userOrOrg/repoName to getInfo (it has to match the "${validRepoNameRegex.source}" pattern)` + ); + } + + const data = await GHDataLoader.load({ kind: 'pull', ...request }); + const user = data?.author; + const title = data?.title; + const commit = data?.mergeCommit; + + return { + user: user ? user.login : null, + id: null, + pull: request.pull, + commit: commit ? commit.abbreviatedOid : null, + title: title || null, + labels: data ? (data.labels.nodes || []).map((label: { name: string }) => label.name) : null, + links: { + commit: commit ? `[\`${commit.abbreviatedOid}\`](${commit.commitUrl})` : null, + pull: `[#${request.pull}](https://github.com/${request.repo}/pull/${request.pull})`, + user: user ? `[@${user.login}](${user.url})` : null, + }, + }; +} diff --git a/scripts/release/utils/get-unpicked-prs.ts b/scripts/release/utils/get-unpicked-prs.ts new file mode 100644 index 000000000000..6b36f5e00f59 --- /dev/null +++ b/scripts/release/utils/get-unpicked-prs.ts @@ -0,0 +1,70 @@ +/* eslint-disable no-console */ +import type { GraphQlQueryResponseData } from '@octokit/graphql'; +import { githubGraphQlClient } from './github-client'; + +export interface PR { + number: number; + id: string; + branch: string; + title: string; + mergeCommit: string; +} + +export async function getUnpickedPRs(baseBranch: string, verbose?: boolean): Promise> { + console.log(`πŸ’¬ Getting unpicked patch pull requests...`); + const result = await githubGraphQlClient( + ` + query ($owner: String!, $repo: String!, $state: PullRequestState!, $order: IssueOrder!) { + repository(owner: $owner, name: $repo) { + pullRequests(states: [$state], labels: ["patch"], orderBy: $order, first: 50) { + nodes { + id + number + title + baseRefName + mergeCommit { + abbreviatedOid + } + labels(first: 20) { + nodes { + name + } + } + } + } + } + } + `, + { + owner: 'storybookjs', + repo: 'storybook', + order: { + field: 'UPDATED_AT', + direction: 'ASC', + }, + state: 'MERGED', + } + ); + + const { + pullRequests: { nodes }, + } = result.repository; + + const prs = nodes.map((node: any) => ({ + number: node.number, + id: node.id, + branch: node.baseRefName, + title: node.title, + mergeCommit: node.mergeCommit.abbreviatedOid, + labels: node.labels.nodes.map((l: any) => l.name), + })); + + const unpickedPRs = prs + .filter((pr: any) => !pr.labels.includes('picked')) + .filter((pr: any) => pr.branch === baseBranch); + if (verbose) { + console.log(`πŸ” Found unpicked patch pull requests: + ${JSON.stringify(unpickedPRs, null, 2)}`); + } + return unpickedPRs; +} diff --git a/scripts/release/utils/git-client.ts b/scripts/release/utils/git-client.ts new file mode 100644 index 000000000000..9b4e05144f8f --- /dev/null +++ b/scripts/release/utils/git-client.ts @@ -0,0 +1,7 @@ +import { simpleGit } from 'simple-git'; + +export const git = simpleGit(); + +export async function getLatestTag() { + return (await git.tags(['v*', '--sort=-v:refname', '--merged'])).latest; +} diff --git a/scripts/release/utils/github-client.ts b/scripts/release/utils/github-client.ts new file mode 100644 index 000000000000..3c6a2355e0dc --- /dev/null +++ b/scripts/release/utils/github-client.ts @@ -0,0 +1,40 @@ +import type { GraphQlQueryResponseData } from '@octokit/graphql'; +import { graphql } from '@octokit/graphql'; + +export const githubGraphQlClient = graphql.defaults({ + headers: { authorization: `token ${process.env.GH_TOKEN}` }, +}); + +export async function getLabelIds({ + repo: fullRepo, + labelNames, +}: { + labelNames: string[]; + repo: string; +}) { + const query = labelNames.join('+'); + const [owner, repo] = fullRepo.split('/'); + const result = await githubGraphQlClient( + ` + query ($owner: String!, $repo: String!, $q: String!) { + repository(owner: $owner, name: $repo) { + labels(query: $q, first: 10) { + nodes { + id + name + description + } + } + } + } + `, + { owner, repo, q: query } + ); + + const { labels } = result.repository; + const labelToId: Record = {}; + labels.nodes.forEach((label: { name: string; id: string }) => { + labelToId[label.name] = label.id; + }); + return labelToId; +} diff --git a/scripts/release/version.ts b/scripts/release/version.ts new file mode 100644 index 000000000000..03f608567392 --- /dev/null +++ b/scripts/release/version.ts @@ -0,0 +1,260 @@ +/* eslint-disable no-console */ +import { setOutput } from '@actions/core'; +import { readFile, readJson, writeFile, writeJson } from 'fs-extra'; +import chalk from 'chalk'; +import path from 'path'; +import program from 'commander'; +import semver from 'semver'; +import { z } from 'zod'; +import type { Workspace } from '../utils/workspace'; +import { getWorkspaces } from '../utils/workspace'; +import { execaCommand } from '../utils/exec'; + +program + .name('version') + .description('version all packages') + .option( + '-R, --release-type ', + 'Which release type to use to bump the version' + ) + .option('-P, --pre-id ', 'Which prerelease identifer to change to, eg. "alpha", "beta", "rc"') + .option( + '-E, --exact ', + 'Use exact version instead of calculating from current version, eg. "7.2.0-canary.123". Can not be combined with --release-type or --pre-id' + ) + .option('-V, --verbose', 'Enable verbose logging', false); + +const optionsSchema = z + .object({ + releaseType: z + .enum(['major', 'minor', 'patch', 'prerelease', 'premajor', 'preminor', 'prepatch']) + .optional(), + preId: z.string().optional(), + exact: z + .string() + .optional() + .refine((version) => (version ? semver.valid(version) !== null : true), { + message: '--exact version has to be a valid semver string', + }), + verbose: z.boolean().optional(), + }) + .superRefine((schema, ctx) => { + // manual union validation because zod + commander is not great in this case + const hasExact = 'exact' in schema && schema.exact; + const hasReleaseType = 'releaseType' in schema && schema.releaseType; + if ((hasExact && hasReleaseType) || (!hasExact && !hasReleaseType)) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: + 'Combining --exact with --release-type is invalid, but having one of them is required', + }); + } + if (schema.preId && !schema.releaseType.startsWith('pre')) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: + 'Using prerelease identifier requires one of release types: premajor, preminor, prepatch, prerelease', + }); + } + return z.NEVER; + }); + +type BaseOptions = { verbose: boolean }; +type BumpOptions = BaseOptions & { + releaseType: semver.ReleaseType; + preId?: string; +}; +type ExactOptions = BaseOptions & { + exact: semver.ReleaseType; +}; +type Options = BumpOptions | ExactOptions; + +const CODE_DIR_PATH = path.join(__dirname, '..', '..', 'code'); +const CODE_PACKAGE_JSON_PATH = path.join(CODE_DIR_PATH, 'package.json'); + +const validateOptions = (options: { [key: string]: any }): options is Options => { + optionsSchema.parse(options); + return true; +}; + +const getCurrentVersion = async () => { + console.log(`πŸ“ Reading current version of Storybook...`); + const { version } = await readJson(CODE_PACKAGE_JSON_PATH); + return version; +}; + +const bumpCodeVersion = async (nextVersion: string) => { + console.log(`🀜 Bumping version of ${chalk.cyan('code')}'s package.json...`); + + const codePkgJson = await readJson(CODE_PACKAGE_JSON_PATH); + + codePkgJson.version = nextVersion; + await writeJson(CODE_PACKAGE_JSON_PATH, codePkgJson, { spaces: 2 }); + + console.log(`βœ… Bumped version of ${chalk.cyan('code')}'s package.json`); +}; + +const bumpAllPackageVersions = async (nextVersion: string, verbose?: boolean) => { + console.log(`🀜 Bumping version of ${chalk.cyan('all packages')}...`); + + console.log(`βœ… Bumped version of ${chalk.cyan('all packages')}`); +}; + +const bumpVersionSources = async (currentVersion: string, nextVersion: string) => { + const filesToUpdate = [ + path.join(CODE_DIR_PATH, 'lib', 'manager-api', 'src', 'version.ts'), + path.join(CODE_DIR_PATH, 'lib', 'cli', 'src', 'versions.ts'), + ]; + console.log(`🀜 Bumping versions in...:\n ${chalk.cyan(filesToUpdate.join('\n '))}`); + + await Promise.all( + filesToUpdate.map(async (filename) => { + const currentContent = await readFile(filename, { encoding: 'utf-8' }); + const nextContent = currentContent.replaceAll(currentVersion, nextVersion); + return writeFile(filename, nextContent); + }) + ); + + console.log(`βœ… Bumped versions in:\n ${chalk.cyan(filesToUpdate.join('\n '))}`); +}; + +const bumpAllPackageJsons = async ({ + packages, + currentVersion, + nextVersion, + verbose, +}: { + packages: Workspace[]; + currentVersion: string; + nextVersion: string; + verbose?: boolean; +}) => { + console.log( + `🀜 Bumping versions and dependencies in ${chalk.cyan( + `all ${packages.length} package.json` + )}'s...` + ); + // 1. go through all packages in the monorepo + await Promise.all( + packages.map(async (pkg) => { + // 2. get the package.json + const packageJsonPath = path.join(CODE_DIR_PATH, pkg.location, 'package.json'); + const packageJson: { + version: string; + dependencies: Record; + devDependencies: Record; + peerDependencies: Record; + [key: string]: any; + } = await readJson(packageJsonPath); + // 3. bump the version + packageJson.version = nextVersion; + const { dependencies, devDependencies, peerDependencies } = packageJson; + if (verbose) { + console.log( + ` Bumping ${chalk.blue(pkg.name)}'s version to ${chalk.yellow(nextVersion)}` + ); + } + // 4. go through all deps in the package.json + Object.entries({ dependencies, devDependencies, peerDependencies }).forEach( + ([depType, deps]) => { + if (!deps) { + return; + } + // 5. find all storybook deps + Object.entries(deps) + .filter( + ([depName, depVersion]) => + depName.startsWith('@storybook/') && + // ignore storybook dependneices that don't use the current version + depVersion.includes(currentVersion) + ) + .forEach(([depName, depVersion]) => { + // 6. bump the version of any found storybook dep + const nextDepVersion = depVersion.replace(currentVersion, nextVersion); + if (verbose) { + console.log( + ` Bumping ${chalk.blue(pkg.name)}'s ${chalk.red(depType)} on ${chalk.green( + depName + )} from ${chalk.yellow(depVersion)} to ${chalk.yellow(nextDepVersion)}` + ); + } + packageJson[depType][depName] = nextDepVersion; + }); + } + ); + await writeJson(packageJsonPath, packageJson, { spaces: 2 }); + }) + ); + console.log(`βœ… Bumped peer dependency versions in ${chalk.cyan('all packages')}`); +}; + +export const run = async (options: unknown) => { + if (!validateOptions(options)) { + return; + } + const { verbose } = options; + + console.log(`πŸš› Finding Storybook packages...`); + + const [packages, currentVersion] = await Promise.all([getWorkspaces(), getCurrentVersion()]); + + console.log( + `πŸ“¦ found ${packages.length} storybook packages at version ${chalk.red(currentVersion)}` + ); + if (verbose) { + const formattedPackages = packages.map( + (pkg) => `${chalk.green(pkg.name.padEnd(60))}: ${chalk.cyan(pkg.location)}` + ); + console.log(`πŸ“¦ Packages: + ${formattedPackages.join('\n ')}`); + } + + let nextVersion: string; + + if ('exact' in options && options.exact) { + console.log(`πŸ“ˆ Exact version selected: ${chalk.green(options.exact)}`); + nextVersion = options.exact; + } else { + const { releaseType, preId } = options as BumpOptions; + console.log(`πŸ“ˆ Release type selected: ${chalk.green(releaseType)}`); + if (preId) { + console.log(`πŸ†” Version prerelease identifier selected: ${chalk.yellow(preId)}`); + } + + nextVersion = semver.inc(currentVersion, releaseType, preId); + + console.log( + `⏭ Bumping version ${chalk.blue(currentVersion)} with release type ${chalk.green( + releaseType + )}${ + preId ? ` and ${chalk.yellow(preId)}` : '' + } results in version: ${chalk.bgGreenBright.bold(nextVersion)}` + ); + } + + console.log(`⏭ Bumping all packages to ${chalk.blue(nextVersion)}...`); + + await bumpCodeVersion(nextVersion); + await bumpVersionSources(currentVersion, nextVersion); + await bumpAllPackageJsons({ packages, currentVersion, nextVersion, verbose }); + + console.log(`⬆️ Updating lock file with ${chalk.blue('yarn install --mode=update-lockfile')}`); + await execaCommand(`yarn install --mode=update-lockfile`, { + cwd: path.join(CODE_DIR_PATH), + stdio: verbose ? 'inherit' : undefined, + }); + console.log(`βœ… Updated lock file with ${chalk.blue('yarn install --mode=update-lockfile')}`); + + if (process.env.GITHUB_ACTIONS === 'true') { + setOutput('current-version', currentVersion); + setOutput('next-version', nextVersion); + } +}; + +if (require.main === module) { + const options = program.parse().opts(); + run(options).catch((err) => { + console.error(err); + process.exit(1); + }); +} diff --git a/scripts/release/write-changelog.ts b/scripts/release/write-changelog.ts new file mode 100644 index 000000000000..2fe20f88d4c6 --- /dev/null +++ b/scripts/release/write-changelog.ts @@ -0,0 +1,111 @@ +/* eslint-disable no-console */ +import chalk from 'chalk'; +import path from 'path'; +import program from 'commander'; +import semver from 'semver'; +import { z } from 'zod'; +import { readFile, writeFile } from 'fs-extra'; +import { getChanges } from './utils/get-changes'; + +program + .name('write-changelog') + .description( + 'write changelog based on merged PRs and commits. the argument describes the changelog entry heading, but NOT which commits/PRs to include, must be a semver string' + ) + .arguments('') + .option('-P, --unpicked-patches', 'Set to only consider PRs labeled with "patch" label') + .option( + '-F, --from ', + 'Which tag or commit to generate changelog from, eg. "7.0.7". Leave unspecified to select latest released tag in git history' + ) + .option( + '-T, --to ', + 'Which tag or commit to generate changelog to, eg. "7.1.0-beta.8". Leave unspecified to select HEAD commit' + ) + .option('-D, --dry-run', 'Do not write file, only output to shell', false) + .option('-V, --verbose', 'Enable verbose logging', false); + +const optionsSchema = z.object({ + unpickedPatches: z.boolean().optional(), + from: z.string().optional(), + to: z.string().optional(), + verbose: z.boolean().optional(), + dryRun: z.boolean().optional(), +}); + +type Options = { + unpickedPatches?: boolean; + from?: string; + to?: string; + verbose: boolean; + dryRun?: boolean; +}; + +const validateOptions = (args: unknown[], options: { [key: string]: any }): options is Options => { + optionsSchema.parse(options); + if (args.length !== 1 || !semver.valid(args[0] as string)) { + console.error( + `🚨 Invalid arguments, expected a single argument with the version to generate changelog for, eg. ${chalk.green( + '7.1.0-beta.8' + )}` + ); + return false; + } + return true; +}; + +const writeToFile = async ({ + changelogText, + version, + verbose, +}: { + changelogText: string; + version: string; + verbose?: boolean; +}) => { + const isPrerelease = semver.prerelease(version) !== null; + const changelogFilename = isPrerelease ? 'CHANGELOG.prerelease.md' : 'CHANGELOG.md'; + const changelogPath = path.join(__dirname, '..', '..', changelogFilename); + + if (verbose) { + console.log(`πŸ“ Writing changelog to ${chalk.blue(changelogPath)}`); + } + + const currentChangelog = await readFile(changelogPath, 'utf-8'); + const nextChangelog = [changelogText, currentChangelog].join('\n\n'); + + await writeFile(changelogPath, nextChangelog); +}; + +export const run = async (args: unknown[], options: unknown) => { + if (!validateOptions(args, options)) { + return; + } + const { from, to, unpickedPatches, dryRun, verbose } = options; + const version = args[0] as string; + + console.log( + `πŸ’¬ Generating changelog for ${chalk.blue(version)} between ${chalk.green( + from || 'latest' + )} and ${chalk.green(to || 'HEAD')}` + ); + + const { changelogText } = await getChanges({ version, from, to, unpickedPatches, verbose }); + + if (dryRun) { + console.log(`πŸ“ Dry run, not writing file`); + return; + } + + await writeToFile({ changelogText, version, verbose }); + + console.log(`βœ… Wrote Changelog to file`); +}; + +if (require.main === module) { + const parsed = program.parse(); + run(parsed.args, parsed.opts()).catch((err) => { + console.error(err); + process.exit(1); + }); +} diff --git a/scripts/utils/options.ts b/scripts/utils/options.ts index b741d80afcb2..8d398bed8784 100644 --- a/scripts/utils/options.ts +++ b/scripts/utils/options.ts @@ -7,6 +7,7 @@ import type { PromptObject, Falsy, PrevCaller, PromptType } from 'prompts'; import program from 'commander'; import dedent from 'ts-dedent'; import chalk from 'chalk'; +// eslint-disable-next-line import/extensions import kebabCase from 'lodash/kebabCase.js'; // Option types diff --git a/scripts/utils/workspace.ts b/scripts/utils/workspace.ts index c490d593f1b5..3219c558599f 100644 --- a/scripts/utils/workspace.ts +++ b/scripts/utils/workspace.ts @@ -4,7 +4,7 @@ import { execaCommand } from './exec'; export type Workspace = { name: string; location: string }; -async function getWorkspaces() { +export async function getWorkspaces() { const { stdout } = await execaCommand('yarn workspaces list --json', { cwd: CODE_DIRECTORY, shell: true, diff --git a/scripts/yarn.lock b/scripts/yarn.lock index 2d0d31d24dcb..4f8f559b7a24 100644 --- a/scripts/yarn.lock +++ b/scripts/yarn.lock @@ -5,6 +5,25 @@ __metadata: version: 6 cacheKey: 8c0 +"@actions/core@npm:^1.10.0": + version: 1.10.0 + resolution: "@actions/core@npm:1.10.0" + dependencies: + "@actions/http-client": ^2.0.1 + uuid: ^8.3.2 + checksum: 9214d1e0cf5cf2a5d48b8f3b12488c6be9f6722ea60f2397409226e8410b5a3e12e558d9b66c93469d180399865ec20180119408a1770f026bd9ecac6965fcda + languageName: node + linkType: hard + +"@actions/http-client@npm:^2.0.1": + version: 2.1.0 + resolution: "@actions/http-client@npm:2.1.0" + dependencies: + tunnel: ^0.0.6 + checksum: 3936947d05c394ec3365a8757e13bd8cd0fb124cc1503254e46a95b2b8342fbf3f2a3c13d56d37e1d6705939cf2808dc64b4a38b75f83995fdc2e878e3aef89c + languageName: node + linkType: hard + "@adobe/css-tools@npm:^4.0.1": version: 4.2.0 resolution: "@adobe/css-tools@npm:4.2.0" @@ -1247,16 +1266,6 @@ __metadata: languageName: node linkType: hard -"@babel/polyfill@npm:^7.2.5": - version: 7.12.1 - resolution: "@babel/polyfill@npm:7.12.1" - dependencies: - core-js: ^2.6.5 - regenerator-runtime: ^0.13.4 - checksum: f5d233d2958582e8678838c32c42ba780965119ebb3771d9b9735f85efabc7b8b49161e7d908477486e0aaf8508410e957be764c27a6a828714fb9d1b7f80bc3 - languageName: node - linkType: hard - "@babel/preset-env@npm:^7.20.2": version: 7.21.4 resolution: "@babel/preset-env@npm:7.21.4" @@ -1711,6 +1720,42 @@ __metadata: languageName: node linkType: hard +"@gitbeaker/core@npm:^21.7.0": + version: 21.7.0 + resolution: "@gitbeaker/core@npm:21.7.0" + dependencies: + "@gitbeaker/requester-utils": ^21.7.0 + form-data: ^3.0.0 + li: ^1.3.0 + xcase: ^2.0.1 + checksum: 907f1dac7f43e288c71f184243712a65601a88ab7c9a8b7ff76629d8d94360c31f995b8142dec324615ad50f7e78e12f646a4302cb595dc990da3cdbd2514dfe + languageName: node + linkType: hard + +"@gitbeaker/node@npm:^21.3.0": + version: 21.7.0 + resolution: "@gitbeaker/node@npm:21.7.0" + dependencies: + "@gitbeaker/core": ^21.7.0 + "@gitbeaker/requester-utils": ^21.7.0 + form-data: ^3.0.0 + got: ^11.1.4 + xcase: ^2.0.1 + checksum: c5be30593dae749271f8529a0e33a1831f173d7e39796c9e30206a71e3007cc6368c802d296f1a8fcca056a8e718c77f50ae61aa17de8e444f0c91bf1a05950c + languageName: node + linkType: hard + +"@gitbeaker/requester-utils@npm:^21.7.0": + version: 21.7.0 + resolution: "@gitbeaker/requester-utils@npm:21.7.0" + dependencies: + form-data: ^3.0.0 + query-string: ^6.12.1 + xcase: ^2.0.1 + checksum: 1930783d67a8add51bd6056e0524facfc867fb73d78387af4259a166a5e725eaa64a4c22c0fe33538762b0abb496781bf39d95fc8d544825354254dd05e05271 + languageName: node + linkType: hard + "@graphql-typed-document-node/core@npm:^3.1.0": version: 3.2.0 resolution: "@graphql-typed-document-node/core@npm:3.2.0" @@ -2106,6 +2151,22 @@ __metadata: languageName: node linkType: hard +"@kwsites/file-exists@npm:^1.1.1": + version: 1.1.1 + resolution: "@kwsites/file-exists@npm:1.1.1" + dependencies: + debug: ^4.1.1 + checksum: 39e693239a72ccd8408bb618a0200e4a8d61682057ca7ae2c87668d7e69196e8d7e2c9cde73db6b23b3b0230169a15e5f1bfe086539f4be43e767b2db68e8ee4 + languageName: node + linkType: hard + +"@kwsites/promise-deferred@npm:^1.1.1": + version: 1.1.1 + resolution: "@kwsites/promise-deferred@npm:1.1.1" + checksum: ef1ad3f1f50991e3bed352b175986d8b4bc684521698514a2ed63c1d1fc9848843da4f2bc2df961c9b148c94e1c34bf33f0da8a90ba2234e452481f2cc9937b1 + languageName: node + linkType: hard + "@linear/sdk@npm:^1.22.0": version: 1.22.0 resolution: "@linear/sdk@npm:1.22.0" @@ -2377,6 +2438,17 @@ __metadata: languageName: node linkType: hard +"@octokit/endpoint@npm:^7.0.0": + version: 7.0.5 + resolution: "@octokit/endpoint@npm:7.0.5" + dependencies: + "@octokit/types": ^9.0.0 + is-plain-object: ^5.0.0 + universal-user-agent: ^6.0.0 + checksum: 68de3e40b4d2b4d3decfc3e23480d5a781275ebf86d084a38ba70c5d46a6cad051b63332da1768a64d58b0b810c5b57401a3892dff4dd0060d8b6b31d78fc2f5 + languageName: node + linkType: hard + "@octokit/graphql@npm:^4.3.1, @octokit/graphql@npm:^4.5.8": version: 4.8.0 resolution: "@octokit/graphql@npm:4.8.0" @@ -2388,6 +2460,17 @@ __metadata: languageName: node linkType: hard +"@octokit/graphql@npm:^5.0.5": + version: 5.0.6 + resolution: "@octokit/graphql@npm:5.0.6" + dependencies: + "@octokit/request": ^6.0.0 + "@octokit/types": ^9.0.0 + universal-user-agent: ^6.0.0 + checksum: de1d839d97fe6d96179925f6714bf96e7af6f77929892596bb4211adab14add3291fc5872b269a3d0e91a4dcf248d16096c82606c4a43538cf241b815c2e2a36 + languageName: node + linkType: hard + "@octokit/openapi-types@npm:^12.11.0": version: 12.11.0 resolution: "@octokit/openapi-types@npm:12.11.0" @@ -2395,12 +2478,10 @@ __metadata: languageName: node linkType: hard -"@octokit/plugin-paginate-rest@npm:^1.1.1": - version: 1.1.2 - resolution: "@octokit/plugin-paginate-rest@npm:1.1.2" - dependencies: - "@octokit/types": ^2.0.1 - checksum: c0b42a7eb92c6b3fb254e85750fe48b667682be277dc9ccafbb91da241fc18396867739b058ae89d48455b3e75eb2967ac5e196fe54a276b58ed56173f5cd188 +"@octokit/openapi-types@npm:^17.2.0": + version: 17.2.0 + resolution: "@octokit/openapi-types@npm:17.2.0" + checksum: baec94b9c300171245d8b0592867ef96d3aa9cbb3261e961c5138e91894e165fffc421288d98f51031af12ab4149efb7ba597d79dee2e5b5a38962348528b1e5 languageName: node linkType: hard @@ -2424,16 +2505,6 @@ __metadata: languageName: node linkType: hard -"@octokit/plugin-rest-endpoint-methods@npm:2.4.0": - version: 2.4.0 - resolution: "@octokit/plugin-rest-endpoint-methods@npm:2.4.0" - dependencies: - "@octokit/types": ^2.0.1 - deprecation: ^2.3.1 - checksum: eedb9e6c3589651a391aa2c850d33fbfb01c94448d5da85b6208ff1fc05d556b05e660db019306c473149727ed83c5711f138179a39651d2cd548a8da2c4bc73 - languageName: node - linkType: hard - "@octokit/plugin-rest-endpoint-methods@npm:3.17.0": version: 3.17.0 resolution: "@octokit/plugin-rest-endpoint-methods@npm:3.17.0" @@ -2456,29 +2527,29 @@ __metadata: languageName: node linkType: hard -"@octokit/request-error@npm:^1.0.2": - version: 1.2.1 - resolution: "@octokit/request-error@npm:1.2.1" +"@octokit/request-error@npm:^2.0.5, @octokit/request-error@npm:^2.1.0": + version: 2.1.0 + resolution: "@octokit/request-error@npm:2.1.0" dependencies: - "@octokit/types": ^2.0.0 + "@octokit/types": ^6.0.3 deprecation: ^2.0.0 once: ^1.4.0 - checksum: 0142170094b5c963de7012aa7d081c3aa05ce19ccd365447c9ca57d475bdf64a79549cb2d5e14348deabdb3c6577966e5f6996eeaa5ea3750b87688cc1c0a0f1 + checksum: eb50eb2734aa903f1e855ac5887bb76d6f237a3aaa022b09322a7676c79bb8020259b25f84ab895c4fc7af5cc736e601ec8cc7e9040ca4629bac8cb393e91c40 languageName: node linkType: hard -"@octokit/request-error@npm:^2.0.5, @octokit/request-error@npm:^2.1.0": - version: 2.1.0 - resolution: "@octokit/request-error@npm:2.1.0" +"@octokit/request-error@npm:^3.0.0": + version: 3.0.3 + resolution: "@octokit/request-error@npm:3.0.3" dependencies: - "@octokit/types": ^6.0.3 + "@octokit/types": ^9.0.0 deprecation: ^2.0.0 once: ^1.4.0 - checksum: eb50eb2734aa903f1e855ac5887bb76d6f237a3aaa022b09322a7676c79bb8020259b25f84ab895c4fc7af5cc736e601ec8cc7e9040ca4629bac8cb393e91c40 + checksum: 1e252ac193c8af23b709909911aa327ed5372cbafcba09e4aff41e0f640a7c152579ab0a60311a92e37b4e7936392d59ee4c2feae5cdc387ee8587a33d8afa60 languageName: node linkType: hard -"@octokit/request@npm:^5.2.0, @octokit/request@npm:^5.4.0, @octokit/request@npm:^5.6.0, @octokit/request@npm:^5.6.3": +"@octokit/request@npm:^5.4.0, @octokit/request@npm:^5.6.0, @octokit/request@npm:^5.6.3": version: 5.6.3 resolution: "@octokit/request@npm:5.6.3" dependencies: @@ -2492,7 +2563,21 @@ __metadata: languageName: node linkType: hard -"@octokit/rest@npm:^16.43.0 || ^17.11.0 || ^18.12.0": +"@octokit/request@npm:^6.0.0": + version: 6.2.5 + resolution: "@octokit/request@npm:6.2.5" + dependencies: + "@octokit/endpoint": ^7.0.0 + "@octokit/request-error": ^3.0.0 + "@octokit/types": ^9.0.0 + is-plain-object: ^5.0.0 + node-fetch: ^2.6.7 + universal-user-agent: ^6.0.0 + checksum: 1f9feaedd75156ffc5b05294974e18b1798a3350ed99acb6d7f9d7b76fc338a47cb9d88b927e7d506894edf49e3a1ef1e18d877403a0aa386d0037b734ab59e4 + languageName: node + linkType: hard + +"@octokit/rest@npm:^16.43.0 || ^17.11.0 || ^18.12.0, @octokit/rest@npm:^18.12.0": version: 18.12.0 resolution: "@octokit/rest@npm:18.12.0" dependencies: @@ -2504,30 +2589,6 @@ __metadata: languageName: node linkType: hard -"@octokit/rest@npm:^16.43.1": - version: 16.43.2 - resolution: "@octokit/rest@npm:16.43.2" - dependencies: - "@octokit/auth-token": ^2.4.0 - "@octokit/plugin-paginate-rest": ^1.1.1 - "@octokit/plugin-request-log": ^1.0.0 - "@octokit/plugin-rest-endpoint-methods": 2.4.0 - "@octokit/request": ^5.2.0 - "@octokit/request-error": ^1.0.2 - atob-lite: ^2.0.0 - before-after-hook: ^2.0.0 - btoa-lite: ^1.0.0 - deprecation: ^2.0.0 - lodash.get: ^4.4.2 - lodash.set: ^4.3.2 - lodash.uniq: ^4.5.0 - octokit-pagination-methods: ^1.1.0 - once: ^1.4.0 - universal-user-agent: ^4.0.0 - checksum: 8e51e16a54dcffb007aeefa48d6dda98f84737c044e15c9e8b123765efcf546b5f3465b37e11666f502d637fac3d4c2ef770ed9e7ba7e21330d1b6d773eccde7 - languageName: node - linkType: hard - "@octokit/rest@npm:^17.1.1": version: 17.11.2 resolution: "@octokit/rest@npm:17.11.2" @@ -2540,15 +2601,6 @@ __metadata: languageName: node linkType: hard -"@octokit/types@npm:^2.0.0, @octokit/types@npm:^2.0.1": - version: 2.16.2 - resolution: "@octokit/types@npm:2.16.2" - dependencies: - "@types/node": ">= 8" - checksum: 8f324639ea2792f38dee104970f7d74584da6747ca41a5f709e0dcd54bc55095af3c47845a284f132ced4dec5a6d5a9c61ed77c3adaccfb5ad7f347fcb1a55b3 - languageName: node - linkType: hard - "@octokit/types@npm:^4.1.6": version: 4.1.10 resolution: "@octokit/types@npm:4.1.10" @@ -2576,6 +2628,15 @@ __metadata: languageName: node linkType: hard +"@octokit/types@npm:^9.0.0": + version: 9.2.3 + resolution: "@octokit/types@npm:9.2.3" + dependencies: + "@octokit/openapi-types": ^17.2.0 + checksum: 9604939ed79be2298827e832177bb8e871d44170144a7504adb0c399966e45361fb909ccffacbd7151f08b94d2e739c6a2d1c0b9a2f9a4bde09c968d27060514 + languageName: node + linkType: hard + "@parcel/watcher@npm:2.0.4": version: 2.0.4 resolution: "@parcel/watcher@npm:2.0.4" @@ -2633,6 +2694,13 @@ __metadata: languageName: node linkType: hard +"@sindresorhus/is@npm:^4.0.0": + version: 4.6.0 + resolution: "@sindresorhus/is@npm:4.6.0" + checksum: 33b6fb1d0834ec8dd7689ddc0e2781c2bfd8b9c4e4bacbcb14111e0ae00621f2c264b8a7d36541799d74888b5dccdf422a891a5cb5a709ace26325eedc81e22e + languageName: node + linkType: hard + "@sinonjs/commons@npm:^2.0.0": version: 2.0.0 resolution: "@sinonjs/commons@npm:2.0.0" @@ -2810,6 +2878,7 @@ __metadata: version: 0.0.0-use.local resolution: "@storybook/scripts@workspace:." dependencies: + "@actions/core": ^1.10.0 "@babel/core": ^7.20.2 "@babel/plugin-proposal-class-properties": ^7.18.6 "@babel/plugin-proposal-decorators": ^7.20.7 @@ -2827,6 +2896,7 @@ __metadata: "@nrwl/cli": ^15.4.5 "@nrwl/nx-cloud": ^15.0.2 "@nrwl/workspace": ^15.4.5 + "@octokit/graphql": ^5.0.5 "@storybook/eslint-config-storybook": ^3.1.2 "@storybook/jest": ^0.1.0 "@storybook/linter-config": ^3.1.2 @@ -2850,10 +2920,12 @@ __metadata: "@types/semver": ^7.3.4 "@types/serve-static": ^1.13.8 "@types/shelljs": ^0.8.7 + "@types/uuid": ^9.0.1 "@typescript-eslint/eslint-plugin": ^5.45.0 "@typescript-eslint/experimental-utils": ^5.45.0 "@typescript-eslint/parser": ^5.45.0 "@verdaccio/types": ^10.2.0 + ansi-regex: ^5.0.0 babel-eslint: ^10.1.0 babel-loader: ^9.1.2 boxen: ^5.1.2 @@ -2862,7 +2934,8 @@ __metadata: commander: ^6.2.1 cross-env: ^7.0.3 cross-spawn: ^7.0.3 - danger: ^10.6.2 + danger: ^11.2.6 + dataloader: ^2.2.2 detect-port: ^1.3.0 ejs: ^3.1.8 ejs-lint: ^2.0.0 @@ -2886,9 +2959,11 @@ __metadata: jest-environment-jsdom: ^29.3.1 jest-image-snapshot: ^6.0.0 jest-junit: ^14.0.1 + jest-mock-extended: ^3.0.4 jest-os-detection: ^1.3.1 jest-serializer-html: ^7.1.0 jest-watch-typeahead: ^2.2.1 + json5: ^2.2.3 junit-xml: ^1.2.0 lint-staged: ^10.5.4 lodash: ^4.17.21 @@ -2897,7 +2972,9 @@ __metadata: node-gyp: ^8.4.0 npmlog: ^5.0.1 nx: ^15.4.5 + ora: ^5.4.1 p-limit: ^3.1.0 + p-retry: ^5.1.2 prettier: ^2.8.0 pretty-hrtime: ^1.0.0 process: ^0.11.10 @@ -2913,6 +2990,7 @@ __metadata: semver: ^7.3.7 serve-static: ^1.14.1 shelljs: ^0.8.5 + simple-git: ^3.18.0 slash: ^3.0.0 sort-package-json: ^1.48.1 tempy: ^1.0.0 @@ -2924,10 +3002,12 @@ __metadata: type-fest: ^3.4.0 typescript: ~4.9.3 util: ^0.12.4 + uuid: ^9.0.0 verdaccio: ^5.19.1 verdaccio-auth-memory: ^10.2.0 - wait-on: ^5.2.1 + wait-on: ^7.0.1 window-size: ^1.1.1 + zod: ^3.21.4 zx: ^7.0.3 dependenciesMeta: "@verdaccio/types": @@ -3082,6 +3162,15 @@ __metadata: languageName: node linkType: hard +"@szmarczak/http-timer@npm:^4.0.5": + version: 4.0.6 + resolution: "@szmarczak/http-timer@npm:4.0.6" + dependencies: + defer-to-connect: ^2.0.0 + checksum: 73946918c025339db68b09abd91fa3001e87fc749c619d2e9c2003a663039d4c3cb89836c98a96598b3d47dec2481284ba85355392644911f5ecd2336536697f + languageName: node + linkType: hard + "@testing-library/dom@npm:^7.28.1, @testing-library/dom@npm:^7.29.4": version: 7.31.2 resolution: "@testing-library/dom@npm:7.31.2" @@ -3262,6 +3351,18 @@ __metadata: languageName: node linkType: hard +"@types/cacheable-request@npm:^6.0.1": + version: 6.0.3 + resolution: "@types/cacheable-request@npm:6.0.3" + dependencies: + "@types/http-cache-semantics": "*" + "@types/keyv": ^3.1.4 + "@types/node": "*" + "@types/responselike": ^1.0.0 + checksum: 10816a88e4e5b144d43c1d15a81003f86d649776c7f410c9b5e6579d0ad9d4ca71c541962fb403077388b446e41af7ae38d313e46692144985f006ac5e11fa03 + languageName: node + linkType: hard + "@types/connect@npm:*": version: 3.4.35 resolution: "@types/connect@npm:3.4.35" @@ -3363,6 +3464,13 @@ __metadata: languageName: node linkType: hard +"@types/http-cache-semantics@npm:*": + version: 4.0.1 + resolution: "@types/http-cache-semantics@npm:4.0.1" + checksum: 6d6068110a04cac213bdc0fff9c7bac028b5a2da390492204328987d8ddc500adc10d9cf5747a6333dab261712655dcfe120ea1d5527c205d012a39cdccc2a7b + languageName: node + linkType: hard + "@types/istanbul-lib-coverage@npm:*, @types/istanbul-lib-coverage@npm:^2.0.0, @types/istanbul-lib-coverage@npm:^2.0.1": version: 2.0.4 resolution: "@types/istanbul-lib-coverage@npm:2.0.4" @@ -3432,6 +3540,15 @@ __metadata: languageName: node linkType: hard +"@types/keyv@npm:^3.1.4": + version: 3.1.4 + resolution: "@types/keyv@npm:3.1.4" + dependencies: + "@types/node": "*" + checksum: ff8f54fc49621210291f815fe5b15d809fd7d032941b3180743440bd507ecdf08b9e844625fa346af568c84bf34114eb378dcdc3e921a08ba1e2a08d7e3c809c + languageName: node + linkType: hard + "@types/lodash@npm:^4, @types/lodash@npm:^4.14.175": version: 4.14.192 resolution: "@types/lodash@npm:4.14.192" @@ -3571,6 +3688,22 @@ __metadata: languageName: node linkType: hard +"@types/responselike@npm:^1.0.0": + version: 1.0.0 + resolution: "@types/responselike@npm:1.0.0" + dependencies: + "@types/node": "*" + checksum: 474ac2402e6d43c007eee25f50d01eb1f67255ca83dd8e036877292bbe8dd5d2d1e50b54b408e233b50a8c38e681ff3ebeaf22f18b478056eddb65536abb003a + languageName: node + linkType: hard + +"@types/retry@npm:0.12.1": + version: 0.12.1 + resolution: "@types/retry@npm:0.12.1" + checksum: d2d08393973693826fc947fb09596c34bd65863201e2f6d7e9d7a02d504199d6a2bab13eba56f6366ee0fd45434c699a9fdcfff3311e63bf2fad7a4cf34bacfd + languageName: node + linkType: hard + "@types/scheduler@npm:*": version: 0.16.3 resolution: "@types/scheduler@npm:0.16.3" @@ -3635,6 +3768,13 @@ __metadata: languageName: node linkType: hard +"@types/uuid@npm:^9.0.1": + version: 9.0.2 + resolution: "@types/uuid@npm:9.0.2" + checksum: 4c4834f9738575a69db1179589cf397830dc205850b491216697afb254764c79c96a63b92f76e81b6d03515bed9227adf184fa4d33bb04970e6377e2f7c5bab9 + languageName: node + linkType: hard + "@types/which@npm:^2.0.2": version: 2.0.2 resolution: "@types/which@npm:2.0.2" @@ -4136,15 +4276,6 @@ __metadata: languageName: node linkType: hard -"agent-base@npm:4, agent-base@npm:^4.3.0": - version: 4.3.0 - resolution: "agent-base@npm:4.3.0" - dependencies: - es6-promisify: ^5.0.0 - checksum: a618d4e4ca7c0c2023b2664346570773455c501a930718764f65016a8a9eea6d2ab5ba54255589e46de529bab4026a088523dce17f94e34ba385af1f644febe1 - languageName: node - linkType: hard - "agent-base@npm:6, agent-base@npm:^6.0.2": version: 6.0.2 resolution: "agent-base@npm:6.0.2" @@ -4582,13 +4713,6 @@ __metadata: languageName: node linkType: hard -"atob-lite@npm:^2.0.0": - version: 2.0.0 - resolution: "atob-lite@npm:2.0.0" - checksum: 8073795465dad14aa92b2cd3322472e93dbc8b87da5740150bbae9d716ee6cc254af1c375b7310a475d876eb24c25011584ae9c1277bdb3eb53ebb4cd236f501 - languageName: node - linkType: hard - "atomic-sleep@npm:^1.0.0": version: 1.0.0 resolution: "atomic-sleep@npm:1.0.0" @@ -4624,7 +4748,7 @@ __metadata: languageName: node linkType: hard -"axios@npm:^0.21.1, axios@npm:^0.21.2": +"axios@npm:^0.21.2": version: 0.21.4 resolution: "axios@npm:0.21.4" dependencies: @@ -4633,6 +4757,16 @@ __metadata: languageName: node linkType: hard +"axios@npm:^0.27.2": + version: 0.27.2 + resolution: "axios@npm:0.27.2" + dependencies: + follow-redirects: ^1.14.9 + form-data: ^4.0.0 + checksum: 76d673d2a90629944b44d6f345f01e58e9174690f635115d5ffd4aca495d99bcd8f95c590d5ccb473513f5ebc1d1a6e8934580d0c57cdd0498c3a101313ef771 + languageName: node + linkType: hard + "axios@npm:^1.0.0": version: 1.3.4 resolution: "axios@npm:1.3.4" @@ -4838,7 +4972,7 @@ __metadata: languageName: node linkType: hard -"before-after-hook@npm:^2.0.0, before-after-hook@npm:^2.1.0, before-after-hook@npm:^2.2.0": +"before-after-hook@npm:^2.1.0, before-after-hook@npm:^2.2.0": version: 2.2.3 resolution: "before-after-hook@npm:2.2.3" checksum: 0488c4ae12df758ca9d49b3bb27b47fd559677965c52cae7b335784724fb8bf96c42b6e5ba7d7afcbc31facb0e294c3ef717cc41c5bc2f7bd9e76f8b90acd31c @@ -4867,7 +5001,7 @@ __metadata: languageName: node linkType: hard -"bl@npm:^4.0.3": +"bl@npm:^4.0.3, bl@npm:^4.1.0": version: 4.1.0 resolution: "bl@npm:4.1.0" dependencies: @@ -4985,13 +5119,6 @@ __metadata: languageName: node linkType: hard -"btoa-lite@npm:^1.0.0": - version: 1.0.0 - resolution: "btoa-lite@npm:1.0.0" - checksum: 7a4f0568ae3c915464650f98fde7901ae07b13a333a614515a0c86876b3528670fafece28dfef9745d971a613bb83341823afb0c20c6f318b384c1e364b9eb95 - languageName: node - linkType: hard - "buffer-equal-constant-time@npm:1.0.1": version: 1.0.1 resolution: "buffer-equal-constant-time@npm:1.0.1" @@ -5110,6 +5237,28 @@ __metadata: languageName: node linkType: hard +"cacheable-lookup@npm:^5.0.3": + version: 5.0.4 + resolution: "cacheable-lookup@npm:5.0.4" + checksum: a6547fb4954b318aa831cbdd2f7b376824bc784fb1fa67610e4147099e3074726072d9af89f12efb69121415a0e1f2918a8ddd4aafcbcf4e91fbeef4a59cd42c + languageName: node + linkType: hard + +"cacheable-request@npm:^7.0.2": + version: 7.0.4 + resolution: "cacheable-request@npm:7.0.4" + dependencies: + clone-response: ^1.0.2 + get-stream: ^5.1.0 + http-cache-semantics: ^4.0.0 + keyv: ^4.0.0 + lowercase-keys: ^2.0.0 + normalize-url: ^6.0.1 + responselike: ^2.0.0 + checksum: 0834a7d17ae71a177bc34eab06de112a43f9b5ad05ebe929bec983d890a7d9f2bc5f1aa8bb67ea2b65e07a3bc74bea35fa62dd36dbac52876afe36fdcf83da41 + languageName: node + linkType: hard + "call-bind@npm:^1.0.0, call-bind@npm:^1.0.2": version: 1.0.2 resolution: "call-bind@npm:1.0.2" @@ -5322,6 +5471,13 @@ __metadata: languageName: node linkType: hard +"cli-spinners@npm:^2.5.0": + version: 2.9.0 + resolution: "cli-spinners@npm:2.9.0" + checksum: c0d5437acc1ace7361b1c58a4fda3c92c2d8691ff3169ac658ce30faee71280b7aa706c072bcb6d0e380c232f3495f7d5ad4668c1391fe02c4d3a39d37798f44 + languageName: node + linkType: hard + "cli-truncate@npm:^2.1.0": version: 2.1.0 resolution: "cli-truncate@npm:2.1.0" @@ -5365,6 +5521,22 @@ __metadata: languageName: node linkType: hard +"clone-response@npm:^1.0.2": + version: 1.0.3 + resolution: "clone-response@npm:1.0.3" + dependencies: + mimic-response: ^1.0.0 + checksum: 06a2b611824efb128810708baee3bd169ec9a1bf5976a5258cd7eb3f7db25f00166c6eee5961f075c7e38e194f373d4fdf86b8166ad5b9c7e82bbd2e333a6087 + languageName: node + linkType: hard + +"clone@npm:^1.0.2": + version: 1.0.4 + resolution: "clone@npm:1.0.4" + checksum: 2176952b3649293473999a95d7bebfc9dc96410f6cbd3d2595cf12fd401f63a4bf41a7adbfd3ab2ff09ed60cb9870c58c6acdd18b87767366fabfc163700f13b + languageName: node + linkType: hard + "co@npm:3.1.0": version: 3.1.0 resolution: "co@npm:3.1.0" @@ -5641,10 +5813,10 @@ __metadata: languageName: node linkType: hard -"core-js@npm:^2.6.5": - version: 2.6.12 - resolution: "core-js@npm:2.6.12" - checksum: 00128efe427789120a06b819adc94cc72b96955acb331cb71d09287baf9bd37bebd191d91f1ee4939c893a050307ead4faea08876f09115112612b6a05684b63 +"core-js@npm:^3.8.2": + version: 3.30.2 + resolution: "core-js@npm:3.30.2" + checksum: 864d7dc908d4ece507d27e6c6d2830300dcb775d88cfefeec31e34ab95be5016bb23abb29c8b1c4a930bada01318af009276199d75dcab1a230c3cebdf8d3a70 languageName: node linkType: hard @@ -5779,32 +5951,32 @@ __metadata: languageName: node linkType: hard -"danger@npm:^10.6.2": - version: 10.9.0 - resolution: "danger@npm:10.9.0" +"danger@npm:^11.2.6": + version: 11.2.6 + resolution: "danger@npm:11.2.6" dependencies: - "@babel/polyfill": ^7.2.5 - "@octokit/rest": ^16.43.1 + "@gitbeaker/node": ^21.3.0 + "@octokit/rest": ^18.12.0 async-retry: 1.2.3 chalk: ^2.3.0 commander: ^2.18.0 + core-js: ^3.8.2 debug: ^4.1.1 fast-json-patch: ^3.0.0-1 get-stdin: ^6.0.0 - gitlab: ^10.0.1 - http-proxy-agent: ^2.1.0 - https-proxy-agent: ^2.2.1 + http-proxy-agent: ^5.0.0 + https-proxy-agent: ^5.0.1 hyperlinker: ^1.0.0 json5: ^2.1.0 jsonpointer: ^5.0.0 - jsonwebtoken: ^8.4.0 + jsonwebtoken: ^9.0.0 lodash.find: ^4.6.0 lodash.includes: ^4.3.0 lodash.isobject: ^3.0.2 lodash.keys: ^4.0.8 lodash.mapvalues: ^4.6.0 lodash.memoize: ^4.1.2 - memfs-or-file-map-to-github-branch: ^1.1.0 + memfs-or-file-map-to-github-branch: ^1.2.1 micromatch: ^4.0.4 node-cleanup: ^2.1.2 node-fetch: ^2.6.7 @@ -5817,6 +5989,7 @@ __metadata: pinpoint: ^1.1.0 prettyjson: ^1.2.1 readline-sync: ^1.4.9 + regenerator-runtime: ^0.13.9 require-from-string: ^2.0.2 supports-hyperlinks: ^1.0.1 bin: @@ -5829,7 +6002,7 @@ __metadata: danger-process: distribution/commands/danger-process.js danger-reset-status: distribution/commands/danger-reset-status.js danger-runner: distribution/commands/danger-runner.js - checksum: b8a6d9d04d0ea97ee0a7bf76c6062a6499616e2b0a1a3acbe604b0115a34bbdfba1bb9e096a0abe7f58be3f7d7eb53cfbb42ff36d21184b15fce0cccbf5ee1ed + checksum: 2968fbf2be3eb4b337d95e929381f69bd226bb45e54a44546600242275aae50e3a9e48557f1e0625e542d9fc99bdd67656591434aa2fdeea1f8e64a59de8b91f languageName: node linkType: hard @@ -5860,6 +6033,13 @@ __metadata: languageName: node linkType: hard +"dataloader@npm:^2.2.2": + version: 2.2.2 + resolution: "dataloader@npm:2.2.2" + checksum: 125ec69f821478cf7c6b4360095db6cab939fe57876a0d2060c428091a8deee7152345189923b71a6afa694aaec463779f34b585317164016fd6f54f52cd94ba + languageName: node + linkType: hard + "dayjs@npm:1.11.7": version: 1.11.7 resolution: "dayjs@npm:1.11.7" @@ -5876,15 +6056,6 @@ __metadata: languageName: node linkType: hard -"debug@npm:3.1.0": - version: 3.1.0 - resolution: "debug@npm:3.1.0" - dependencies: - ms: 2.0.0 - checksum: 5bff34a352d7b2eaa31886eeaf2ee534b5461ec0548315b2f9f80bd1d2533cab7df1fa52e130ce27bc31c3945fbffb0fc72baacdceb274b95ce853db89254ea4 - languageName: node - linkType: hard - "debug@npm:4, debug@npm:4.3.4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.2.0, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4": version: 4.3.4 resolution: "debug@npm:4.3.4" @@ -5897,7 +6068,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:^3.1.0, debug@npm:^3.2.7": +"debug@npm:^3.2.7": version: 3.2.7 resolution: "debug@npm:3.2.7" dependencies: @@ -5920,6 +6091,15 @@ __metadata: languageName: node linkType: hard +"decompress-response@npm:^6.0.0": + version: 6.0.0 + resolution: "decompress-response@npm:6.0.0" + dependencies: + mimic-response: ^3.1.0 + checksum: bd89d23141b96d80577e70c54fb226b2f40e74a6817652b80a116d7befb8758261ad073a8895648a29cc0a5947021ab66705cb542fa9c143c82022b27c5b175e + languageName: node + linkType: hard + "dedent@npm:^0.7.0": version: 0.7.0 resolution: "dedent@npm:0.7.0" @@ -5966,6 +6146,22 @@ __metadata: languageName: node linkType: hard +"defaults@npm:^1.0.3": + version: 1.0.4 + resolution: "defaults@npm:1.0.4" + dependencies: + clone: ^1.0.2 + checksum: 9cfbe498f5c8ed733775db62dfd585780387d93c17477949e1670bfcfb9346e0281ce8c4bf9f4ac1fc0f9b851113bd6dc9e41182ea1644ccd97de639fa13c35a + languageName: node + linkType: hard + +"defer-to-connect@npm:^2.0.0": + version: 2.0.1 + resolution: "defer-to-connect@npm:2.0.1" + checksum: 625ce28e1b5ad10cf77057b9a6a727bf84780c17660f6644dab61dd34c23de3001f03cedc401f7d30a4ed9965c2e8a7336e220a329146f2cf85d4eddea429782 + languageName: node + linkType: hard + "define-lazy-prop@npm:^2.0.0": version: 2.0.0 resolution: "define-lazy-prop@npm:2.0.0" @@ -6575,22 +6771,6 @@ __metadata: languageName: node linkType: hard -"es6-promise@npm:^4.0.3": - version: 4.2.8 - resolution: "es6-promise@npm:4.2.8" - checksum: 2373d9c5e9a93bdd9f9ed32ff5cb6dd3dd785368d1c21e9bbbfd07d16345b3774ae260f2bd24c8f836a6903f432b4151e7816a7fa8891ccb4e1a55a028ec42c3 - languageName: node - linkType: hard - -"es6-promisify@npm:^5.0.0": - version: 5.0.0 - resolution: "es6-promisify@npm:5.0.0" - dependencies: - es6-promise: ^4.0.3 - checksum: 23284c6a733cbf7842ec98f41eac742c9f288a78753c4fe46652bae826446ced7615b9e8a5c5f121a08812b1cd478ea58630f3e1c3d70835bd5dcd69c7cd75c9 - languageName: node - linkType: hard - "esbuild-plugin-alias@npm:^0.2.1": version: 0.2.1 resolution: "esbuild-plugin-alias@npm:0.2.1" @@ -7635,7 +7815,7 @@ __metadata: languageName: node linkType: hard -"follow-redirects@npm:^1.0.0, follow-redirects@npm:^1.14.0, follow-redirects@npm:^1.15.0": +"follow-redirects@npm:^1.0.0, follow-redirects@npm:^1.14.0, follow-redirects@npm:^1.14.9, follow-redirects@npm:^1.15.0": version: 1.15.2 resolution: "follow-redirects@npm:1.15.2" peerDependenciesMeta: @@ -7661,17 +7841,6 @@ __metadata: languageName: node linkType: hard -"form-data@npm:^2.5.0": - version: 2.5.1 - resolution: "form-data@npm:2.5.1" - dependencies: - asynckit: ^0.4.0 - combined-stream: ^1.0.6 - mime-types: ^2.1.12 - checksum: 7e8fb913b84a7ac04074781a18d0f94735bbe82815ff35348803331f6480956ff0035db5bcf15826edee09fe01e665cfac664678f1526646a6374ee13f960e56 - languageName: node - linkType: hard - "form-data@npm:^3.0.0": version: 3.0.1 resolution: "form-data@npm:3.0.1" @@ -7934,7 +8103,7 @@ __metadata: languageName: node linkType: hard -"get-stream@npm:^5.0.0": +"get-stream@npm:^5.0.0, get-stream@npm:^5.1.0": version: 5.2.0 resolution: "get-stream@npm:5.2.0" dependencies: @@ -7999,21 +8168,6 @@ __metadata: languageName: node linkType: hard -"gitlab@npm:^10.0.1": - version: 10.2.1 - resolution: "gitlab@npm:10.2.1" - dependencies: - form-data: ^2.5.0 - humps: ^2.0.1 - ky: ^0.12.0 - ky-universal: ^0.3.0 - li: ^1.3.0 - query-string: ^6.8.2 - universal-url: ^2.0.0 - checksum: 0d5ca8206b0505eef6c5a1c3d1694910bacac89519c889491beec3efcf799ae5263dc8bb5953ef4ee272d557811e0f45a781d5b6ee27be3280b73d80093e0c65 - languageName: node - linkType: hard - "glob-parent@npm:^5.1.2, glob-parent@npm:~5.1.2": version: 5.1.2 resolution: "glob-parent@npm:5.1.2" @@ -8198,6 +8352,25 @@ __metadata: languageName: node linkType: hard +"got@npm:^11.1.4": + version: 11.8.6 + resolution: "got@npm:11.8.6" + dependencies: + "@sindresorhus/is": ^4.0.0 + "@szmarczak/http-timer": ^4.0.5 + "@types/cacheable-request": ^6.0.1 + "@types/responselike": ^1.0.0 + cacheable-lookup: ^5.0.3 + cacheable-request: ^7.0.2 + decompress-response: ^6.0.0 + http2-wrapper: ^1.0.0-beta.5.2 + lowercase-keys: ^2.0.0 + p-cancelable: ^2.0.0 + responselike: ^2.0.0 + checksum: 754dd44877e5cf6183f1e989ff01c648d9a4719e357457bd4c78943911168881f1cfb7b2cb15d885e2105b3ad313adb8f017a67265dd7ade771afdb261ee8cb1 + languageName: node + linkType: hard + "graceful-fs@npm:^4.1.3, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": version: 4.2.11 resolution: "graceful-fs@npm:4.2.11" @@ -8346,13 +8519,6 @@ __metadata: languageName: node linkType: hard -"hasurl@npm:^1.0.0": - version: 1.0.0 - resolution: "hasurl@npm:1.0.0" - checksum: 7762739a9713612d7c81f8c59807c59e696ea4402861ff799fd9d507860d4e37ad7eac4e1741a713ae96a74306a98ee5a03245695352ede885cc4287a44c632b - languageName: node - linkType: hard - "he@npm:^1.1.1": version: 1.2.0 resolution: "he@npm:1.2.0" @@ -8420,7 +8586,7 @@ __metadata: languageName: node linkType: hard -"http-cache-semantics@npm:^4.1.0": +"http-cache-semantics@npm:^4.0.0, http-cache-semantics@npm:^4.1.0": version: 4.1.1 resolution: "http-cache-semantics@npm:4.1.1" checksum: ce1319b8a382eb3cbb4a37c19f6bfe14e5bb5be3d09079e885e8c513ab2d3cd9214902f8a31c9dc4e37022633ceabfc2d697405deeaf1b8f3552bb4ed996fdfc @@ -8453,16 +8619,6 @@ __metadata: languageName: node linkType: hard -"http-proxy-agent@npm:^2.1.0": - version: 2.1.0 - resolution: "http-proxy-agent@npm:2.1.0" - dependencies: - agent-base: 4 - debug: 3.1.0 - checksum: 526294de33953bacb21b883d8bbc01a82e1e9f5a721785345dd538b15b62c7a5d4080b729eb3177ad15d842f931f44002431d5cf9b036cc8cea4bfb5ec172228 - languageName: node - linkType: hard - "http-proxy-agent@npm:^4.0.0, http-proxy-agent@npm:^4.0.1": version: 4.0.1 resolution: "http-proxy-agent@npm:4.0.1" @@ -8535,6 +8691,16 @@ __metadata: languageName: node linkType: hard +"http2-wrapper@npm:^1.0.0-beta.5.2": + version: 1.0.3 + resolution: "http2-wrapper@npm:1.0.3" + dependencies: + quick-lru: ^5.1.1 + resolve-alpn: ^1.0.0 + checksum: 6a9b72a033e9812e1476b9d776ce2f387bc94bc46c88aea0d5dab6bd47d0a539b8178830e77054dd26d1142c866d515a28a4dc7c3ff4232c88ff2ebe4f5d12d1 + languageName: node + linkType: hard + "https-proxy-agent@npm:5.0.1, https-proxy-agent@npm:^5.0.0, https-proxy-agent@npm:^5.0.1": version: 5.0.1 resolution: "https-proxy-agent@npm:5.0.1" @@ -8545,16 +8711,6 @@ __metadata: languageName: node linkType: hard -"https-proxy-agent@npm:^2.2.1": - version: 2.2.4 - resolution: "https-proxy-agent@npm:2.2.4" - dependencies: - agent-base: ^4.3.0 - debug: ^3.1.0 - checksum: 4bdde8fcd9ea0adc4a77282de2b4f9e27955e0441425af0f27f0fe01006946b80eaee6749e08e838d350c06ed2ebd5d11347d3beb88c45eacb0667e27276cdad - languageName: node - linkType: hard - "human-signals@npm:^1.1.1": version: 1.1.1 resolution: "human-signals@npm:1.1.1" @@ -8585,13 +8741,6 @@ __metadata: languageName: node linkType: hard -"humps@npm:^2.0.1": - version: 2.0.1 - resolution: "humps@npm:2.0.1" - checksum: 554f3bb9de780ce833f0058f30536f87615bd75ead2008b98d900598379fe5dcd3300bdd9092d3e078d47b66fade82276974dda7151318b5de7a1d837c3abe6e - languageName: node - linkType: hard - "husky@npm:^4.3.7": version: 4.3.8 resolution: "husky@npm:4.3.8" @@ -8984,6 +9133,13 @@ __metadata: languageName: node linkType: hard +"is-interactive@npm:^1.0.0": + version: 1.0.0 + resolution: "is-interactive@npm:1.0.0" + checksum: dd47904dbf286cd20aa58c5192161be1a67138485b9836d5a70433b21a45442e9611b8498b8ab1f839fc962c7620667a50535fdfb4a6bc7989b8858645c06b4d + languageName: node + linkType: hard + "is-lambda@npm:^1.0.1": version: 1.0.1 resolution: "is-lambda@npm:1.0.1" @@ -9592,6 +9748,18 @@ __metadata: languageName: node linkType: hard +"jest-mock-extended@npm:^3.0.4": + version: 3.0.4 + resolution: "jest-mock-extended@npm:3.0.4" + dependencies: + ts-essentials: ^7.0.3 + peerDependencies: + jest: ^24.0.0 || ^25.0.0 || ^26.0.0 || ^27.0.0 || ^28.0.0 || ^29.0.0 + typescript: ^3.0.0 || ^4.0.0 || ^5.0.0 + checksum: b9514c0cf2b50460ef154818f71fe356fb8b0bf66af72050257813f88f7bb7313a30172c7ff09bc19e16bc59bae7160a854be2f86364fb8fa70d6e16299db4e0 + languageName: node + linkType: hard + "jest-mock@npm:^27.3.0": version: 27.5.1 resolution: "jest-mock@npm:27.5.1" @@ -9864,16 +10032,16 @@ __metadata: languageName: node linkType: hard -"joi@npm:^17.3.0": - version: 17.9.1 - resolution: "joi@npm:17.9.1" +"joi@npm:^17.7.0": + version: 17.9.2 + resolution: "joi@npm:17.9.2" dependencies: "@hapi/hoek": ^9.0.0 "@hapi/topo": ^5.0.0 "@sideway/address": ^4.1.3 "@sideway/formula": ^3.0.1 "@sideway/pinpoint": ^2.0.0 - checksum: 27bae524494f42db55a5a5e5e794c2616ad3524695af8f92f6c122dd5e65b12f2c0b76960cf0f1da7b01e5eb06d4b0579f96edf6b4df890c3fd6517f43dee6be + checksum: 284bc34d5070c7b064a9fa68e02703961ad08229dd95dfe0baf2aa5d278c7a99543ecb979b8a6e6f72035539bfdaf1269ac7fa7684a503b6de18b173f72dcc89 languageName: node linkType: hard @@ -9985,6 +10153,13 @@ __metadata: languageName: node linkType: hard +"json-buffer@npm:3.0.1": + version: 3.0.1 + resolution: "json-buffer@npm:3.0.1" + checksum: 0d1c91569d9588e7eef2b49b59851f297f3ab93c7b35c7c221e288099322be6b562767d11e4821da500f3219542b9afd2e54c5dc573107c1126ed1080f8e96d7 + languageName: node + linkType: hard + "json-parse-even-better-errors@npm:^2.3.0": version: 2.3.1 resolution: "json-parse-even-better-errors@npm:2.3.1" @@ -10038,7 +10213,7 @@ __metadata: languageName: node linkType: hard -"json5@npm:^2.0.0, json5@npm:^2.1.0, json5@npm:^2.2.2": +"json5@npm:^2.0.0, json5@npm:^2.1.0, json5@npm:^2.2.2, json5@npm:^2.2.3": version: 2.2.3 resolution: "json5@npm:2.2.3" bin: @@ -10081,7 +10256,7 @@ __metadata: languageName: node linkType: hard -"jsonwebtoken@npm:9.0.0": +"jsonwebtoken@npm:9.0.0, jsonwebtoken@npm:^9.0.0": version: 9.0.0 resolution: "jsonwebtoken@npm:9.0.0" dependencies: @@ -10093,24 +10268,6 @@ __metadata: languageName: node linkType: hard -"jsonwebtoken@npm:^8.4.0": - version: 8.5.1 - resolution: "jsonwebtoken@npm:8.5.1" - dependencies: - jws: ^3.2.2 - lodash.includes: ^4.3.0 - lodash.isboolean: ^3.0.3 - lodash.isinteger: ^4.0.4 - lodash.isnumber: ^3.0.3 - lodash.isplainobject: ^4.0.6 - lodash.isstring: ^4.0.1 - lodash.once: ^4.0.0 - ms: ^2.1.1 - semver: ^5.6.0 - checksum: c5ad937b6fa23a230efa8ed8ca3c0da8ebfdd377bafc3e8432a11b03ef90e733400a00b26c0dfee47db44a2e64b88b154b57e9926a92990f98dd25aaed15006e - languageName: node - linkType: hard - "jsprim@npm:^1.2.2": version: 1.4.2 resolution: "jsprim@npm:1.4.2" @@ -10172,6 +10329,15 @@ __metadata: languageName: node linkType: hard +"keyv@npm:^4.0.0": + version: 4.5.2 + resolution: "keyv@npm:4.5.2" + dependencies: + json-buffer: 3.0.1 + checksum: b633bf53a5afa5591f383d326746226e110e59f13c7e1e8d3e3c9580d2c2345c5eefc21cce168cd5be7fa34b9163e391927146fbd2b7ee7aa2f3aa02b7f0a7de + languageName: node + linkType: hard + "kind-of@npm:^3.0.2": version: 3.2.2 resolution: "kind-of@npm:3.2.2" @@ -10202,25 +10368,6 @@ __metadata: languageName: node linkType: hard -"ky-universal@npm:^0.3.0": - version: 0.3.0 - resolution: "ky-universal@npm:0.3.0" - dependencies: - abort-controller: ^3.0.0 - node-fetch: ^2.6.0 - peerDependencies: - ky: ">=0.12.0" - checksum: 8f2d5dba50f113bd4c67547cb8f6fb7ed94e3a62152a4778539452bd7064636c3467c9b3126c05e68ac53a2ca02ab740f0c260f05221f9610587eb8e03e469fa - languageName: node - linkType: hard - -"ky@npm:^0.12.0": - version: 0.12.0 - resolution: "ky@npm:0.12.0" - checksum: cdca90751ddf69521fd6bcb55acc236d61d4164850a55d7d4e1c167288af2c9ac0f7c9c8e70b409d583704e905ed9af10da4d5cd41b7d717484b5ade7801e24f - languageName: node - linkType: hard - "language-subtag-registry@npm:~0.3.2": version: 0.3.22 resolution: "language-subtag-registry@npm:0.3.22" @@ -10424,13 +10571,6 @@ __metadata: languageName: node linkType: hard -"lodash.get@npm:^4.4.2": - version: 4.4.2 - resolution: "lodash.get@npm:4.4.2" - checksum: 48f40d471a1654397ed41685495acb31498d5ed696185ac8973daef424a749ca0c7871bf7b665d5c14f5cc479394479e0307e781f61d5573831769593411be6e - languageName: node - linkType: hard - "lodash.includes@npm:^4.3.0": version: 4.3.0 resolution: "lodash.includes@npm:4.3.0" @@ -10438,27 +10578,6 @@ __metadata: languageName: node linkType: hard -"lodash.isboolean@npm:^3.0.3": - version: 3.0.3 - resolution: "lodash.isboolean@npm:3.0.3" - checksum: 0aac604c1ef7e72f9a6b798e5b676606042401dd58e49f051df3cc1e3adb497b3d7695635a5cbec4ae5f66456b951fdabe7d6b387055f13267cde521f10ec7f7 - languageName: node - linkType: hard - -"lodash.isinteger@npm:^4.0.4": - version: 4.0.4 - resolution: "lodash.isinteger@npm:4.0.4" - checksum: 4c3e023a2373bf65bf366d3b8605b97ec830bca702a926939bcaa53f8e02789b6a176e7f166b082f9365bfec4121bfeb52e86e9040cb8d450e64c858583f61b7 - languageName: node - linkType: hard - -"lodash.isnumber@npm:^3.0.3": - version: 3.0.3 - resolution: "lodash.isnumber@npm:3.0.3" - checksum: 2d01530513a1ee4f72dd79528444db4e6360588adcb0e2ff663db2b3f642d4bb3d687051ae1115751ca9082db4fdef675160071226ca6bbf5f0c123dbf0aa12d - languageName: node - linkType: hard - "lodash.isobject@npm:^3.0.2": version: 3.0.2 resolution: "lodash.isobject@npm:3.0.2" @@ -10466,20 +10585,6 @@ __metadata: languageName: node linkType: hard -"lodash.isplainobject@npm:^4.0.6": - version: 4.0.6 - resolution: "lodash.isplainobject@npm:4.0.6" - checksum: afd70b5c450d1e09f32a737bed06ff85b873ecd3d3d3400458725283e3f2e0bb6bf48e67dbe7a309eb371a822b16a26cca4a63c8c52db3fc7dc9d5f9dd324cbb - languageName: node - linkType: hard - -"lodash.isstring@npm:^4.0.1": - version: 4.0.1 - resolution: "lodash.isstring@npm:4.0.1" - checksum: 09eaf980a283f9eef58ef95b30ec7fee61df4d6bf4aba3b5f096869cc58f24c9da17900febc8ffd67819b4e29de29793190e88dc96983db92d84c95fa85d1c92 - languageName: node - linkType: hard - "lodash.keys@npm:^4.0.8": version: 4.2.0 resolution: "lodash.keys@npm:4.2.0" @@ -10508,20 +10613,6 @@ __metadata: languageName: node linkType: hard -"lodash.once@npm:^4.0.0": - version: 4.1.1 - resolution: "lodash.once@npm:4.1.1" - checksum: 46a9a0a66c45dd812fcc016e46605d85ad599fe87d71a02f6736220554b52ffbe82e79a483ad40f52a8a95755b0d1077fba259da8bfb6694a7abbf4a48f1fc04 - languageName: node - linkType: hard - -"lodash.set@npm:^4.3.2": - version: 4.3.2 - resolution: "lodash.set@npm:4.3.2" - checksum: c641d31905e51df43170dce8a1d11a1cff11356e2e2e75fe2615995408e9687d58c3e1d64c3c284c2df2bc519f79a98af737d2944d382ff82ffd244ff6075c29 - languageName: node - linkType: hard - "lodash.sortby@npm:^4.7.0": version: 4.7.0 resolution: "lodash.sortby@npm:4.7.0" @@ -10529,13 +10620,6 @@ __metadata: languageName: node linkType: hard -"lodash.uniq@npm:^4.5.0": - version: 4.5.0 - resolution: "lodash.uniq@npm:4.5.0" - checksum: 262d400bb0952f112162a320cc4a75dea4f66078b9e7e3075ffbc9c6aa30b3e9df3cf20e7da7d566105e1ccf7804e4fbd7d804eee0b53de05d83f16ffbf41c5e - languageName: node - linkType: hard - "lodash@npm:4, lodash@npm:4.17.21, lodash@npm:^4.17.14, lodash@npm:^4.17.15, lodash@npm:^4.17.21, lodash@npm:^4.17.4": version: 4.17.21 resolution: "lodash@npm:4.17.21" @@ -10543,7 +10627,7 @@ __metadata: languageName: node linkType: hard -"log-symbols@npm:^4.0.0": +"log-symbols@npm:^4.0.0, log-symbols@npm:^4.1.0": version: 4.1.0 resolution: "log-symbols@npm:4.1.0" dependencies: @@ -10596,6 +10680,13 @@ __metadata: languageName: node linkType: hard +"lowercase-keys@npm:^2.0.0": + version: 2.0.0 + resolution: "lowercase-keys@npm:2.0.0" + checksum: f82a2b3568910509da4b7906362efa40f5b54ea14c2584778ddb313226f9cbf21020a5db35f9b9a0e95847a9b781d548601f31793d736b22a2b8ae8eb9ab1082 + languageName: node + linkType: hard + "lru-cache@npm:7.16.1": version: 7.16.1 resolution: "lru-cache@npm:7.16.1" @@ -10816,7 +10907,7 @@ __metadata: languageName: node linkType: hard -"memfs-or-file-map-to-github-branch@npm:^1.1.0": +"memfs-or-file-map-to-github-branch@npm:^1.2.1": version: 1.2.1 resolution: "memfs-or-file-map-to-github-branch@npm:1.2.1" dependencies: @@ -10939,6 +11030,20 @@ __metadata: languageName: node linkType: hard +"mimic-response@npm:^1.0.0": + version: 1.0.1 + resolution: "mimic-response@npm:1.0.1" + checksum: c5381a5eae997f1c3b5e90ca7f209ed58c3615caeee850e85329c598f0c000ae7bec40196580eef1781c60c709f47258131dab237cad8786f8f56750594f27fa + languageName: node + linkType: hard + +"mimic-response@npm:^3.1.0": + version: 3.1.0 + resolution: "mimic-response@npm:3.1.0" + checksum: 0d6f07ce6e03e9e4445bee655202153bdb8a98d67ee8dc965ac140900d7a2688343e6b4c9a72cfc9ef2f7944dfd76eef4ab2482eb7b293a68b84916bac735362 + languageName: node + linkType: hard + "min-indent@npm:^1.0.0": version: 1.0.1 resolution: "min-indent@npm:1.0.1" @@ -10973,7 +11078,7 @@ __metadata: languageName: node linkType: hard -"minimist@npm:^1.1.0, minimist@npm:^1.2.0, minimist@npm:^1.2.5, minimist@npm:^1.2.6, minimist@npm:^1.2.8": +"minimist@npm:^1.1.0, minimist@npm:^1.2.0, minimist@npm:^1.2.5, minimist@npm:^1.2.6, minimist@npm:^1.2.7, minimist@npm:^1.2.8": version: 1.2.8 resolution: "minimist@npm:1.2.8" checksum: 19d3fcdca050087b84c2029841a093691a91259a47def2f18222f41e7645a0b7c44ef4b40e88a1e58a40c84d2ef0ee6047c55594d298146d0eb3f6b737c20ce6 @@ -11240,7 +11345,7 @@ __metadata: languageName: node linkType: hard -"node-fetch@npm:^2.6.0, node-fetch@npm:^2.6.1, node-fetch@npm:^2.6.7": +"node-fetch@npm:^2.6.1, node-fetch@npm:^2.6.7": version: 2.6.9 resolution: "node-fetch@npm:2.6.9" dependencies: @@ -11381,6 +11486,13 @@ __metadata: languageName: node linkType: hard +"normalize-url@npm:^6.0.1": + version: 6.1.0 + resolution: "normalize-url@npm:6.1.0" + checksum: 95d948f9bdd2cfde91aa786d1816ae40f8262946e13700bf6628105994fe0ff361662c20af3961161c38a119dc977adeb41fc0b41b1745eb77edaaf9cb22db23 + languageName: node + linkType: hard + "npm-run-path@npm:^2.0.0": version: 2.0.2 resolution: "npm-run-path@npm:2.0.2" @@ -11622,13 +11734,6 @@ __metadata: languageName: node linkType: hard -"octokit-pagination-methods@npm:^1.1.0": - version: 1.1.0 - resolution: "octokit-pagination-methods@npm:1.1.0" - checksum: e8b2b346e7ad91c1b10a3d8be76d8aa33889b4df0bd5c28106dc2e8b5498185bbb5bd884ef07a57b09a5c54003deb2814280bab6ed6991e9e650c5cdc9879924 - languageName: node - linkType: hard - "on-exit-leak-free@npm:^0.2.0": version: 0.2.0 resolution: "on-exit-leak-free@npm:0.2.0" @@ -11736,6 +11841,23 @@ __metadata: languageName: node linkType: hard +"ora@npm:^5.4.1": + version: 5.4.1 + resolution: "ora@npm:5.4.1" + dependencies: + bl: ^4.1.0 + chalk: ^4.1.0 + cli-cursor: ^3.1.0 + cli-spinners: ^2.5.0 + is-interactive: ^1.0.0 + is-unicode-supported: ^0.1.0 + log-symbols: ^4.1.0 + strip-ansi: ^6.0.0 + wcwidth: ^1.0.1 + checksum: 10ff14aace236d0e2f044193362b22edce4784add08b779eccc8f8ef97195cae1248db8ec1ec5f5ff076f91acbe573f5f42a98c19b78dba8c54eefff983cae85 + languageName: node + linkType: hard + "os-homedir@npm:^1.0.0": version: 1.0.2 resolution: "os-homedir@npm:1.0.2" @@ -11760,6 +11882,13 @@ __metadata: languageName: node linkType: hard +"p-cancelable@npm:^2.0.0": + version: 2.1.1 + resolution: "p-cancelable@npm:2.1.1" + checksum: 8c6dc1f8dd4154fd8b96a10e55a3a832684c4365fb9108056d89e79fbf21a2465027c04a59d0d797b5ffe10b54a61a32043af287d5c4860f1e996cbdbc847f01 + languageName: node + linkType: hard + "p-finally@npm:^1.0.0": version: 1.0.0 resolution: "p-finally@npm:1.0.0" @@ -11828,6 +11957,16 @@ __metadata: languageName: node linkType: hard +"p-retry@npm:^5.1.2": + version: 5.1.2 + resolution: "p-retry@npm:5.1.2" + dependencies: + "@types/retry": 0.12.1 + retry: ^0.13.1 + checksum: 9017001ebfbe2a08cf45c970106f6953f5c76d1f8d8e9ff81afcbf6c25fe9f0e13499c5ac49b35d114672cf15689a952f4f3287fd1316420eb810a5a99ecf4e7 + languageName: node + linkType: hard + "p-try@npm:^2.0.0": version: 2.2.0 resolution: "p-try@npm:2.2.0" @@ -12446,7 +12585,7 @@ __metadata: languageName: node linkType: hard -"query-string@npm:^6.8.2": +"query-string@npm:^6.12.1": version: 6.14.1 resolution: "query-string@npm:6.14.1" dependencies: @@ -12479,6 +12618,13 @@ __metadata: languageName: node linkType: hard +"quick-lru@npm:^5.1.1": + version: 5.1.1 + resolution: "quick-lru@npm:5.1.1" + checksum: a24cba5da8cec30d70d2484be37622580f64765fb6390a928b17f60cd69e8dbd32a954b3ff9176fa1b86d86ff2ba05252fae55dc4d40d0291c60412b0ad096da + languageName: node + linkType: hard + "ramda@npm:^0.28.0": version: 0.28.0 resolution: "ramda@npm:0.28.0" @@ -12697,7 +12843,7 @@ __metadata: languageName: node linkType: hard -"regenerator-runtime@npm:^0.13.11, regenerator-runtime@npm:^0.13.4": +"regenerator-runtime@npm:^0.13.11, regenerator-runtime@npm:^0.13.9": version: 0.13.11 resolution: "regenerator-runtime@npm:0.13.11" checksum: 12b069dc774001fbb0014f6a28f11c09ebfe3c0d984d88c9bced77fdb6fedbacbca434d24da9ae9371bfbf23f754869307fb51a4c98a8b8b18e5ef748677ca24 @@ -13080,6 +13226,13 @@ __metadata: languageName: node linkType: hard +"resolve-alpn@npm:^1.0.0": + version: 1.2.1 + resolution: "resolve-alpn@npm:1.2.1" + checksum: b70b29c1843bc39781ef946c8cd4482e6d425976599c0f9c138cec8209e4e0736161bf39319b01676a847000085dfdaf63583c6fb4427bf751a10635bd2aa0c4 + languageName: node + linkType: hard + "resolve-cwd@npm:^3.0.0": version: 3.0.0 resolution: "resolve-cwd@npm:3.0.0" @@ -13162,6 +13315,15 @@ __metadata: languageName: node linkType: hard +"responselike@npm:^2.0.0": + version: 2.0.1 + resolution: "responselike@npm:2.0.1" + dependencies: + lowercase-keys: ^2.0.0 + checksum: 360b6deb5f101a9f8a4174f7837c523c3ec78b7ca8a7c1d45a1062b303659308a23757e318b1e91ed8684ad1205721142dd664d94771cd63499353fd4ee732b5 + languageName: node + linkType: hard + "restore-cursor@npm:^3.1.0": version: 3.1.0 resolution: "restore-cursor@npm:3.1.0" @@ -13179,6 +13341,13 @@ __metadata: languageName: node linkType: hard +"retry@npm:^0.13.1": + version: 0.13.1 + resolution: "retry@npm:0.13.1" + checksum: 9ae822ee19db2163497e074ea919780b1efa00431d197c7afdb950e42bf109196774b92a49fc9821f0b8b328a98eea6017410bfc5e8a0fc19c85c6d11adb3772 + languageName: node + linkType: hard + "reusify@npm:^1.0.4": version: 1.0.4 resolution: "reusify@npm:1.0.4" @@ -13249,7 +13418,7 @@ __metadata: languageName: node linkType: hard -"rxjs@npm:^6.5.4, rxjs@npm:^6.6.3": +"rxjs@npm:^6.5.4": version: 6.6.7 resolution: "rxjs@npm:6.6.7" dependencies: @@ -13267,6 +13436,15 @@ __metadata: languageName: node linkType: hard +"rxjs@npm:^7.8.0": + version: 7.8.1 + resolution: "rxjs@npm:7.8.1" + dependencies: + tslib: ^2.1.0 + checksum: 3c49c1ecd66170b175c9cacf5cef67f8914dcbc7cd0162855538d365c83fea631167cacb644b3ce533b2ea0e9a4d0b12175186985f89d75abe73dbd8f7f06f68 + languageName: node + linkType: hard + "safe-buffer@npm:5.1.2": version: 5.1.2 resolution: "safe-buffer@npm:5.1.2" @@ -13358,7 +13536,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:2 || 3 || 4 || 5, semver@npm:^5.5.0, semver@npm:^5.6.0": +"semver@npm:2 || 3 || 4 || 5, semver@npm:^5.5.0": version: 5.7.1 resolution: "semver@npm:5.7.1" bin: @@ -13508,6 +13686,17 @@ __metadata: languageName: node linkType: hard +"simple-git@npm:^3.18.0": + version: 3.19.0 + resolution: "simple-git@npm:3.19.0" + dependencies: + "@kwsites/file-exists": ^1.1.1 + "@kwsites/promise-deferred": ^1.1.1 + debug: ^4.3.4 + checksum: 4dc448e663f92bed8dd2d43e07b1c3dbb2444412f39d38b710fe3dc41b1c25dae07aff1960fbf5bd17d4f8f33b6908a335c82f0b9c5db5b87c4ad1d7a2873d7e + languageName: node + linkType: hard + "sisteransi@npm:^1.0.5": version: 1.0.5 resolution: "sisteransi@npm:1.0.5" @@ -14457,6 +14646,15 @@ __metadata: languageName: node linkType: hard +"ts-essentials@npm:^7.0.3": + version: 7.0.3 + resolution: "ts-essentials@npm:7.0.3" + peerDependencies: + typescript: ">=3.7.0" + checksum: ea1919534ec6ce4ca4d9cb0ff1ab8e053509237da8d4298762ab3bfba4e78ca5649a599ce78a5c7c2624f3a7a971f62b265b7b0c3c881336e4fa6acaf6f37544 + languageName: node + linkType: hard + "ts-interface-checker@npm:^0.1.9": version: 0.1.13 resolution: "ts-interface-checker@npm:0.1.13" @@ -14617,6 +14815,13 @@ __metadata: languageName: node linkType: hard +"tunnel@npm:^0.0.6": + version: 0.0.6 + resolution: "tunnel@npm:0.0.6" + checksum: e27e7e896f2426c1c747325b5f54efebc1a004647d853fad892b46d64e37591ccd0b97439470795e5262b5c0748d22beb4489a04a0a448029636670bfd801b75 + languageName: node + linkType: hard + "tweetnacl@npm:^0.14.3, tweetnacl@npm:~0.14.0": version: 0.14.5 resolution: "tweetnacl@npm:0.14.5" @@ -15000,25 +15205,6 @@ __metadata: languageName: node linkType: hard -"universal-url@npm:^2.0.0": - version: 2.0.0 - resolution: "universal-url@npm:2.0.0" - dependencies: - hasurl: ^1.0.0 - whatwg-url: ^7.0.0 - checksum: 57a5f887676987650f9f2b62811159e2706645e6fbb2b5ed83d802d996310de1e39066478e6f4f11cc9ec4e92624dd0f943f950d9ecaac2763b2b22d2aac8ae0 - languageName: node - linkType: hard - -"universal-user-agent@npm:^4.0.0": - version: 4.0.1 - resolution: "universal-user-agent@npm:4.0.1" - dependencies: - os-name: ^3.1.0 - checksum: e590abd8decb36400d1a630da5957e61f0356492bf413e12f78c169cade915080b03dbfbe8fa62c557bd73413edc681de580ad84488565bf30a9d509fd1b311f - languageName: node - linkType: hard - "universal-user-agent@npm:^5.0.0": version: 5.0.0 resolution: "universal-user-agent@npm:5.0.0" @@ -15164,6 +15350,15 @@ __metadata: languageName: node linkType: hard +"uuid@npm:^9.0.0": + version: 9.0.0 + resolution: "uuid@npm:9.0.0" + bin: + uuid: dist/bin/uuid + checksum: 8867e438990d1d33ac61093e2e4e3477a2148b844e4fa9e3c2360fa4399292429c4b6ec64537eb1659c97b2d10db349c673ad58b50e2824a11e0d3630de3c056 + languageName: node + linkType: hard + "v8-compile-cache-lib@npm:^3.0.1": version: 3.0.1 resolution: "v8-compile-cache-lib@npm:3.0.1" @@ -15418,18 +15613,18 @@ __metadata: languageName: node linkType: hard -"wait-on@npm:^5.2.1": - version: 5.3.0 - resolution: "wait-on@npm:5.3.0" +"wait-on@npm:^7.0.1": + version: 7.0.1 + resolution: "wait-on@npm:7.0.1" dependencies: - axios: ^0.21.1 - joi: ^17.3.0 + axios: ^0.27.2 + joi: ^17.7.0 lodash: ^4.17.21 - minimist: ^1.2.5 - rxjs: ^6.6.3 + minimist: ^1.2.7 + rxjs: ^7.8.0 bin: wait-on: bin/wait-on - checksum: 5ca740e3f2cf4f73ebd2787ff15f7b8d0ee99f188dd37aae0a7d116ae9837f04b8752e4306218aee5165ca4e37cfd3c5b114627748e465e46f8cd51af0da1790 + checksum: 2a9c56d26dac573e6bfd36e85d99f072021c23dc2c0faab900a411460b58e16982b96b018d9168c366040f56196314fa46f3d79ef19e3dc38f55824d5035f2ec languageName: node linkType: hard @@ -15442,6 +15637,15 @@ __metadata: languageName: node linkType: hard +"wcwidth@npm:^1.0.1": + version: 1.0.1 + resolution: "wcwidth@npm:1.0.1" + dependencies: + defaults: ^1.0.3 + checksum: 5b61ca583a95e2dd85d7078400190efd452e05751a64accb8c06ce4db65d7e0b0cde9917d705e826a2e05cc2548f61efde115ffa374c3e436d04be45c889e5b4 + languageName: node + linkType: hard + "web-streams-polyfill@npm:^3.0.3": version: 3.2.1 resolution: "web-streams-polyfill@npm:3.2.1" @@ -15722,6 +15926,13 @@ __metadata: languageName: node linkType: hard +"xcase@npm:^2.0.1": + version: 2.0.1 + resolution: "xcase@npm:2.0.1" + checksum: 11b8ae8f6734b29d442a5acf1dff3a896cabbf49e7ffa01472ff6fa687a6e6f6a25889d06c10a41950e7a90fe89239fa78d95eab0c5eb654ca75f0ccd71ba8ed + languageName: node + linkType: hard + "xdg-basedir@npm:^4.0.0": version: 4.0.0 resolution: "xdg-basedir@npm:4.0.0" @@ -15855,6 +16066,13 @@ __metadata: languageName: node linkType: hard +"zod@npm:^3.21.4": + version: 3.21.4 + resolution: "zod@npm:3.21.4" + checksum: 161e8cf7aea38a99244d65da4a9477d9d966f6a533e503feaa20ff7968a9691065c38c6f1eab5cbbdc8374142fff4a05c9cacb8479803ab50ab6a6ca80e5d624 + languageName: node + linkType: hard + "zwitch@npm:^1.0.0": version: 1.0.5 resolution: "zwitch@npm:1.0.5"