diff --git a/patches/nextcloud/appstore-build-publish.yml.patch b/patches/nextcloud/appstore-build-publish.yml.patch index 72fabc2..1badc74 100644 --- a/patches/nextcloud/appstore-build-publish.yml.patch +++ b/patches/nextcloud/appstore-build-publish.yml.patch @@ -1,60 +1,241 @@ --- upstream/vendor/nextcloud/appstore-build-publish.yml +++ workflow-templates/appstore-build-publish.yml -@@ -1,6 +1,6 @@ - # This workflow is provided via the organization template repository - # -+# https://github.com/LibreCodeCoop/.github +@@ -1,202 +1,36 @@ +-# This workflow is provided via the organization template repository +-# -# https://github.com/nextcloud/.github - # https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization - # - # SPDX-FileCopyrightText: 2021-2024 Nextcloud GmbH and Nextcloud contributors -@@ -19,9 +19,6 @@ - build_and_publish: - runs-on: ubuntu-latest - +-# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization +-# +-# SPDX-FileCopyrightText: 2021-2024 Nextcloud GmbH and Nextcloud contributors +-# SPDX-License-Identifier: MIT +- +-name: Build and publish app release +- +-on: +- release: +- types: [published] +- +-permissions: +- contents: write +- +-jobs: +- build_and_publish: +- runs-on: ubuntu-latest +- - # Only allowed to be run on nextcloud-releases repositories - if: ${{ github.repository_owner == 'nextcloud-releases' }} - - steps: - - name: Check actor permission - uses: skjnldsv/check-actor-permission@69e92a3c4711150929bca9fcf34448c5bf5526e7 # v3.0 -@@ -143,6 +140,14 @@ - cd ${{ env.APP_NAME }} - make appstore - -+ - name: Verify app store package -+ if: steps.krankerl.outputs.files_exists != 'true' -+ working-directory: ${{ env.APP_NAME }} -+ run: | -+ if make -qp 2>/dev/null | grep -q '^verify-appstore-package:'; then -+ make verify-appstore-package -+ fi +- steps: +- - name: Check actor permission +- uses: skjnldsv/check-actor-permission@69e92a3c4711150929bca9fcf34448c5bf5526e7 # v3.0 +- with: +- require: write +- +- - name: Set app env +- run: | +- # Split and keep last +- echo "APP_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV +- echo "APP_VERSION=${GITHUB_REF##*/}" >> $GITHUB_ENV +- +- - name: Checkout +- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 +- with: +- persist-credentials: false +- path: ${{ env.APP_NAME }} +- +- - name: Get app version number +- id: app-version +- uses: skjnldsv/xpath-action@f5b036e9d973f42c86324833fd00be90665fbf77 # v1.0.0 +- with: +- filename: ${{ env.APP_NAME }}/appinfo/info.xml +- expression: "//info//version/text()" +- +- - name: Validate app version against tag +- run: | +- [ "${{ env.APP_VERSION }}" = "v${{ fromJSON(steps.app-version.outputs.result).version }}" ] +- +- - name: Get appinfo data +- id: appinfo +- uses: skjnldsv/xpath-action@f5b036e9d973f42c86324833fd00be90665fbf77 # v1.0.0 +- with: +- filename: ${{ env.APP_NAME }}/appinfo/info.xml +- expression: "//info//dependencies//nextcloud/@min-version" +- +- - name: Read package.json node and npm engines version +- uses: skjnldsv/read-package-engines-version-actions@06d6baf7d8f41934ab630e97d9e6c0bc9c9ac5e4 # v3 +- id: versions +- # Continue if no package.json +- continue-on-error: true +- with: +- path: ${{ env.APP_NAME }} +- fallbackNode: '^24' +- fallbackNpm: '^11.3' +- +- - name: Set up node ${{ steps.versions.outputs.nodeVersion }} +- # Skip if no package.json +- if: ${{ steps.versions.outputs.nodeVersion }} +- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 +- with: +- node-version: ${{ steps.versions.outputs.nodeVersion }} +- package-manager-cache: false +- +- - name: Set up npm ${{ steps.versions.outputs.npmVersion }} +- # Skip if no package.json +- if: ${{ steps.versions.outputs.npmVersion }} +- run: npm i -g 'npm@${{ steps.versions.outputs.npmVersion }}' +- +- - name: Get php version +- id: php-versions +- uses: nextcloud-libraries/nextcloud-version-matrix@cd0211ffcef1065e2020cd579e4843b8746e7a58 # v1.3.3 +- with: +- filename: ${{ env.APP_NAME }}/appinfo/info.xml +- +- - name: Set up php ${{ steps.php-versions.outputs.php-min }} +- uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2 +- with: +- php-version: ${{ steps.php-versions.outputs.php-min }} +- coverage: none +- env: +- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} +- +- - name: Check composer.json +- id: check_composer +- uses: andstor/file-existence-action@558493d6c74bf472d87c84eab196434afc2fa029 # v3.1.0 +- with: +- files: "${{ env.APP_NAME }}/composer.json" +- +- - name: Install composer dependencies +- if: steps.check_composer.outputs.files_exists == 'true' +- uses: ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda # 4.0.0 +- with: +- composer-options: '--no-dev' +- working-directory: ${{ env.APP_NAME }} +- ignore-cache: 'yes' +- +- - name: Build ${{ env.APP_NAME }} +- # Skip if no package.json +- if: ${{ steps.versions.outputs.nodeVersion }} +- env: +- CYPRESS_INSTALL_BINARY: 0 +- run: | +- cd ${{ env.APP_NAME }} +- npm ci +- npm run build --if-present +- +- - name: Check Krankerl config +- id: krankerl +- uses: andstor/file-existence-action@558493d6c74bf472d87c84eab196434afc2fa029 # v3.1.0 +- with: +- files: ${{ env.APP_NAME }}/krankerl.toml +- +- - name: Install Krankerl +- if: steps.krankerl.outputs.files_exists == 'true' +- run: | +- wget https://github.com/ChristophWurst/krankerl/releases/download/v0.14.0/krankerl_0.14.0_amd64.deb +- sudo dpkg -i krankerl_0.14.0_amd64.deb +- +- - name: Package ${{ env.APP_NAME }} ${{ env.APP_VERSION }} with krankerl +- if: steps.krankerl.outputs.files_exists == 'true' +- run: | +- cd ${{ env.APP_NAME }} +- krankerl package +- +- - name: Package ${{ env.APP_NAME }} ${{ env.APP_VERSION }} with makefile +- if: steps.krankerl.outputs.files_exists != 'true' +- run: | +- cd ${{ env.APP_NAME }} +- make appstore +- +- - name: Check server download link for ${{ fromJSON(steps.appinfo.outputs.result).nextcloud.min-version }} +- run: | +- NCVERSION='${{ fromJSON(steps.appinfo.outputs.result).nextcloud.min-version }}' +- DOWNLOAD_URL=$(curl -s "https://updates.nextcloud.com/updater_server/latest?channel=beta&version=$NCVERSION" | jq -r '.downloads.zip[0]') +- echo "DOWNLOAD_URL=$DOWNLOAD_URL" >> $GITHUB_ENV +- +- - name: Download server ${{ fromJSON(steps.appinfo.outputs.result).nextcloud.min-version }} +- continue-on-error: true +- id: server-download +- if: ${{ env.DOWNLOAD_URL != 'null' }} +- run: | +- echo "Downloading release tarball from $DOWNLOAD_URL" +- wget $DOWNLOAD_URL -O nextcloud.zip +- unzip nextcloud.zip +- +- - name: Checkout server master fallback +- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 +- if: ${{ steps.server-download.outcome != 'success' }} +- with: +- persist-credentials: false +- submodules: true +- repository: nextcloud/server +- path: nextcloud +- +- +- - name: Sign app +- run: | +- # Extracting release +- cd ${{ env.APP_NAME }}/build/artifacts +- tar -xvf ${{ env.APP_NAME }}.tar.gz +- cd ../../../ +- # Setting up keys +- echo '${{ secrets.APP_PRIVATE_KEY }}' > ${{ env.APP_NAME }}.key +- wget --quiet "https://github.com/nextcloud/app-certificate-requests/raw/master/${{ env.APP_NAME }}/${{ env.APP_NAME }}.crt" +- # Signing +- php nextcloud/occ integrity:sign-app --privateKey=../${{ env.APP_NAME }}.key --certificate=../${{ env.APP_NAME }}.crt --path=../${{ env.APP_NAME }}/build/artifacts/${{ env.APP_NAME }} +- # Rebuilding archive +- cd ${{ env.APP_NAME }}/build/artifacts +- tar -zcvf ${{ env.APP_NAME }}.tar.gz ${{ env.APP_NAME }} +- +- - name: Attach tarball to github release +- uses: svenstaro/upload-release-action@29e53e917877a24fad85510ded594ab3c9ca12de # 2.11.5 +- id: attach_to_release +- with: +- repo_token: ${{ secrets.GITHUB_TOKEN }} +- file: ${{ env.APP_NAME }}/build/artifacts/${{ env.APP_NAME }}.tar.gz +- asset_name: ${{ env.APP_NAME }}-${{ env.APP_VERSION }}.tar.gz +- tag: ${{ github.ref }} +- overwrite: true +- +- - name: Upload app to Nextcloud appstore +- uses: nextcloud-libraries/nextcloud-appstore-push-action@a011fe619bcf6e77ddebc96f9908e1af4071b9c1 # v1.0.3 +- with: +- app_name: ${{ env.APP_NAME }} +- appstore_token: ${{ secrets.APPSTORE_TOKEN }} +- download_url: ${{ steps.attach_to_release.outputs.browser_download_url }} +- app_private_key: ${{ secrets.APP_PRIVATE_KEY }} ++# This workflow is provided via the organization template repository ++# ++# https://github.com/LibreCodeCoop/.github ++# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization ++# ++# SPDX-FileCopyrightText: 2021-2026 Nextcloud GmbH, LibreCode coop and contributors ++# SPDX-License-Identifier: MIT + - - name: Check server download link for ${{ fromJSON(steps.appinfo.outputs.result).nextcloud.min-version }} - run: | - NCVERSION='${{ fromJSON(steps.appinfo.outputs.result).nextcloud.min-version }}' -@@ -183,6 +188,13 @@ - cd ${{ env.APP_NAME }}/build/artifacts - tar -zcvf ${{ env.APP_NAME }}.tar.gz ${{ env.APP_NAME }} - -+ - name: Validate release artifact -+ uses: LibreCodeCoop/release-tool/actions/artifact-validate@710c4c83fba47bf01713f46e9c4cb4cf63debfba -+ with: -+ artifact: ${{ env.APP_NAME }}/build/artifacts/${{ env.APP_NAME }}.tar.gz -+ app-name: ${{ env.APP_NAME }} -+ version: ${{ env.APP_VERSION }} ++name: Build and publish app release ++ ++on: ++ release: ++ types: [published] ++ workflow_dispatch: ++ inputs: ++ release_tag: ++ description: Existing release tag to build and publish. ++ required: true ++ type: string + - - name: Attach tarball to github release - uses: svenstaro/upload-release-action@29e53e917877a24fad85510ded594ab3c9ca12de # 2.11.5 - id: attach_to_release -@@ -200,3 +212,10 @@ - appstore_token: ${{ secrets.APPSTORE_TOKEN }} - download_url: ${{ steps.attach_to_release.outputs.browser_download_url }} - app_private_key: ${{ secrets.APP_PRIVATE_KEY }} ++permissions: ++ contents: write + -+ - name: Verify App Store publication -+ uses: LibreCodeCoop/release-tool/actions/appstore-publication-wait@710c4c83fba47bf01713f46e9c4cb4cf63debfba ++jobs: ++ build_and_publish: ++ runs-on: ubuntu-latest ++ steps: ++ - name: Build and publish release ++ uses: LibreCodeCoop/.github/actions/nextcloud-appstore-publish@748b0416ea5b734292671dd0feec33aaf8ec6b82 + with: -+ app-name: ${{ env.APP_NAME }} -+ version: ${{ env.APP_VERSION }} -+ platform: ${{ fromJSON(steps.appinfo.outputs.result).nextcloud.min-version }} ++ app-name: ${{ github.event.repository.name }} ++ release-tag: ${{ github.event_name == 'workflow_dispatch' && inputs.release_tag || github.event.release.tag_name }} ++ github-token: ${{ secrets.GITHUB_TOKEN }} ++ app-private-key: ${{ secrets.APP_PRIVATE_KEY }} ++ appstore-token: ${{ secrets.APPSTORE_TOKEN }} ++ manual-recovery: ${{ github.event_name == 'workflow_dispatch' }} diff --git a/tests/test_appstore_publication_contract.py b/tests/test_appstore_publication_contract.py index 8524918..57cfcaa 100644 --- a/tests/test_appstore_publication_contract.py +++ b/tests/test_appstore_publication_contract.py @@ -8,9 +8,16 @@ ROOT = Path(__file__).resolve().parents[1] ACTION = ROOT / "actions/nextcloud-appstore-publish/action.yml" +TEMPLATE = ROOT / "workflow-templates/appstore-build-publish.yml" class AppStorePublicationContractTest(unittest.TestCase): + def test_consumer_template_is_thin_wrapper(self) -> None: + content = TEMPLATE.read_text(encoding="utf-8") + self.assertIn("LibreCodeCoop/.github/actions/nextcloud-appstore-publish@748b0416ea5b734292671dd0feec33aaf8ec6b82", content) + self.assertNotIn("\n run: |", content) + self.assertLessEqual(len(content.splitlines()), 40) + def test_external_actions_are_immutable(self) -> None: content = ACTION.read_text(encoding="utf-8") revisions = re.findall(r"^\s*uses:\s*([^@\s]+)@([^\s#]+)", content, re.MULTILINE) diff --git a/workflow-templates/appstore-build-publish.yml b/workflow-templates/appstore-build-publish.yml index 142e70b..2d64f38 100644 --- a/workflow-templates/appstore-build-publish.yml +++ b/workflow-templates/appstore-build-publish.yml @@ -3,7 +3,7 @@ # https://github.com/LibreCodeCoop/.github # https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization # -# SPDX-FileCopyrightText: 2021-2024 Nextcloud GmbH and Nextcloud contributors +# SPDX-FileCopyrightText: 2021-2026 Nextcloud GmbH, LibreCode coop and contributors # SPDX-License-Identifier: MIT name: Build and publish app release @@ -11,6 +11,12 @@ name: Build and publish app release on: release: types: [published] + workflow_dispatch: + inputs: + release_tag: + description: Existing release tag to build and publish. + required: true + type: string permissions: contents: write @@ -18,204 +24,13 @@ permissions: jobs: build_and_publish: runs-on: ubuntu-latest - steps: - - name: Check actor permission - uses: skjnldsv/check-actor-permission@69e92a3c4711150929bca9fcf34448c5bf5526e7 # v3.0 - with: - require: write - - - name: Set app env - run: | - # Split and keep last - echo "APP_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV - echo "APP_VERSION=${GITHUB_REF##*/}" >> $GITHUB_ENV - - - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - path: ${{ env.APP_NAME }} - - - name: Get app version number - id: app-version - uses: skjnldsv/xpath-action@f5b036e9d973f42c86324833fd00be90665fbf77 # v1.0.0 - with: - filename: ${{ env.APP_NAME }}/appinfo/info.xml - expression: "//info//version/text()" - - - name: Validate app version against tag - run: | - [ "${{ env.APP_VERSION }}" = "v${{ fromJSON(steps.app-version.outputs.result).version }}" ] - - - name: Get appinfo data - id: appinfo - uses: skjnldsv/xpath-action@f5b036e9d973f42c86324833fd00be90665fbf77 # v1.0.0 - with: - filename: ${{ env.APP_NAME }}/appinfo/info.xml - expression: "//info//dependencies//nextcloud/@min-version" - - - name: Read package.json node and npm engines version - uses: skjnldsv/read-package-engines-version-actions@06d6baf7d8f41934ab630e97d9e6c0bc9c9ac5e4 # v3 - id: versions - # Continue if no package.json - continue-on-error: true - with: - path: ${{ env.APP_NAME }} - fallbackNode: '^24' - fallbackNpm: '^11.3' - - - name: Set up node ${{ steps.versions.outputs.nodeVersion }} - # Skip if no package.json - if: ${{ steps.versions.outputs.nodeVersion }} - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 - with: - node-version: ${{ steps.versions.outputs.nodeVersion }} - package-manager-cache: false - - - name: Set up npm ${{ steps.versions.outputs.npmVersion }} - # Skip if no package.json - if: ${{ steps.versions.outputs.npmVersion }} - run: npm i -g 'npm@${{ steps.versions.outputs.npmVersion }}' - - - name: Get php version - id: php-versions - uses: nextcloud-libraries/nextcloud-version-matrix@cd0211ffcef1065e2020cd579e4843b8746e7a58 # v1.3.3 - with: - filename: ${{ env.APP_NAME }}/appinfo/info.xml - - - name: Set up php ${{ steps.php-versions.outputs.php-min }} - uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2 - with: - php-version: ${{ steps.php-versions.outputs.php-min }} - coverage: none - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Check composer.json - id: check_composer - uses: andstor/file-existence-action@558493d6c74bf472d87c84eab196434afc2fa029 # v3.1.0 - with: - files: "${{ env.APP_NAME }}/composer.json" - - - name: Install composer dependencies - if: steps.check_composer.outputs.files_exists == 'true' - uses: ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda # 4.0.0 - with: - composer-options: '--no-dev' - working-directory: ${{ env.APP_NAME }} - ignore-cache: 'yes' - - - name: Build ${{ env.APP_NAME }} - # Skip if no package.json - if: ${{ steps.versions.outputs.nodeVersion }} - env: - CYPRESS_INSTALL_BINARY: 0 - run: | - cd ${{ env.APP_NAME }} - npm ci - npm run build --if-present - - - name: Check Krankerl config - id: krankerl - uses: andstor/file-existence-action@558493d6c74bf472d87c84eab196434afc2fa029 # v3.1.0 - with: - files: ${{ env.APP_NAME }}/krankerl.toml - - - name: Install Krankerl - if: steps.krankerl.outputs.files_exists == 'true' - run: | - wget https://github.com/ChristophWurst/krankerl/releases/download/v0.14.0/krankerl_0.14.0_amd64.deb - sudo dpkg -i krankerl_0.14.0_amd64.deb - - - name: Package ${{ env.APP_NAME }} ${{ env.APP_VERSION }} with krankerl - if: steps.krankerl.outputs.files_exists == 'true' - run: | - cd ${{ env.APP_NAME }} - krankerl package - - - name: Package ${{ env.APP_NAME }} ${{ env.APP_VERSION }} with makefile - if: steps.krankerl.outputs.files_exists != 'true' - run: | - cd ${{ env.APP_NAME }} - make appstore - - - name: Verify app store package - if: steps.krankerl.outputs.files_exists != 'true' - working-directory: ${{ env.APP_NAME }} - run: | - if make -qp 2>/dev/null | grep -q '^verify-appstore-package:'; then - make verify-appstore-package - fi - - - name: Check server download link for ${{ fromJSON(steps.appinfo.outputs.result).nextcloud.min-version }} - run: | - NCVERSION='${{ fromJSON(steps.appinfo.outputs.result).nextcloud.min-version }}' - DOWNLOAD_URL=$(curl -s "https://updates.nextcloud.com/updater_server/latest?channel=beta&version=$NCVERSION" | jq -r '.downloads.zip[0]') - echo "DOWNLOAD_URL=$DOWNLOAD_URL" >> $GITHUB_ENV - - - name: Download server ${{ fromJSON(steps.appinfo.outputs.result).nextcloud.min-version }} - continue-on-error: true - id: server-download - if: ${{ env.DOWNLOAD_URL != 'null' }} - run: | - echo "Downloading release tarball from $DOWNLOAD_URL" - wget $DOWNLOAD_URL -O nextcloud.zip - unzip nextcloud.zip - - - name: Checkout server master fallback - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - if: ${{ steps.server-download.outcome != 'success' }} - with: - persist-credentials: false - submodules: true - repository: nextcloud/server - path: nextcloud - - - - name: Sign app - run: | - # Extracting release - cd ${{ env.APP_NAME }}/build/artifacts - tar -xvf ${{ env.APP_NAME }}.tar.gz - cd ../../../ - # Setting up keys - echo '${{ secrets.APP_PRIVATE_KEY }}' > ${{ env.APP_NAME }}.key - wget --quiet "https://github.com/nextcloud/app-certificate-requests/raw/master/${{ env.APP_NAME }}/${{ env.APP_NAME }}.crt" - # Signing - php nextcloud/occ integrity:sign-app --privateKey=../${{ env.APP_NAME }}.key --certificate=../${{ env.APP_NAME }}.crt --path=../${{ env.APP_NAME }}/build/artifacts/${{ env.APP_NAME }} - # Rebuilding archive - cd ${{ env.APP_NAME }}/build/artifacts - tar -zcvf ${{ env.APP_NAME }}.tar.gz ${{ env.APP_NAME }} - - - name: Validate release artifact - uses: LibreCodeCoop/release-tool/actions/artifact-validate@710c4c83fba47bf01713f46e9c4cb4cf63debfba - with: - artifact: ${{ env.APP_NAME }}/build/artifacts/${{ env.APP_NAME }}.tar.gz - app-name: ${{ env.APP_NAME }} - version: ${{ env.APP_VERSION }} - - - name: Attach tarball to github release - uses: svenstaro/upload-release-action@29e53e917877a24fad85510ded594ab3c9ca12de # 2.11.5 - id: attach_to_release - with: - repo_token: ${{ secrets.GITHUB_TOKEN }} - file: ${{ env.APP_NAME }}/build/artifacts/${{ env.APP_NAME }}.tar.gz - asset_name: ${{ env.APP_NAME }}-${{ env.APP_VERSION }}.tar.gz - tag: ${{ github.ref }} - overwrite: true - - - name: Upload app to Nextcloud appstore - uses: nextcloud-libraries/nextcloud-appstore-push-action@a011fe619bcf6e77ddebc96f9908e1af4071b9c1 # v1.0.3 - with: - app_name: ${{ env.APP_NAME }} - appstore_token: ${{ secrets.APPSTORE_TOKEN }} - download_url: ${{ steps.attach_to_release.outputs.browser_download_url }} - app_private_key: ${{ secrets.APP_PRIVATE_KEY }} - - - name: Verify App Store publication - uses: LibreCodeCoop/release-tool/actions/appstore-publication-wait@710c4c83fba47bf01713f46e9c4cb4cf63debfba - with: - app-name: ${{ env.APP_NAME }} - version: ${{ env.APP_VERSION }} - platform: ${{ fromJSON(steps.appinfo.outputs.result).nextcloud.min-version }} + - name: Build and publish release + uses: LibreCodeCoop/.github/actions/nextcloud-appstore-publish@748b0416ea5b734292671dd0feec33aaf8ec6b82 + with: + app-name: ${{ github.event.repository.name }} + release-tag: ${{ github.event_name == 'workflow_dispatch' && inputs.release_tag || github.event.release.tag_name }} + github-token: ${{ secrets.GITHUB_TOKEN }} + app-private-key: ${{ secrets.APP_PRIVATE_KEY }} + appstore-token: ${{ secrets.APPSTORE_TOKEN }} + manual-recovery: ${{ github.event_name == 'workflow_dispatch' }}