From 24df31a2f3fc7253efa3eda6d842a8f3dc5f12fc Mon Sep 17 00:00:00 2001 From: Steve Boyd Date: Fri, 21 Jul 2023 11:02:14 +1200 Subject: [PATCH 1/5] ENH Manually trigger CI workflow --- action.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/action.yml b/action.yml index 6508317..d705624 100644 --- a/action.yml +++ b/action.yml @@ -197,4 +197,23 @@ runs: git commit --no-edit git push origin $INTO_BRANCH echo "Succesfully merged-up $FROM_BRANCH into $INTO_BRANCH" + + # Trigger the CI workflow manually via GitHub API + # Do this because the ci.yml `push` event does not seem to be triggered by another workflow doing a push, + # instead it only seems to be triggered by a normal git user doing a push + # https://docs.github.com/en/rest/actions/workflows?apiVersion=2022-11-28#create-a-workflow-dispatch-event + RESP_CODE=$(curl -w %{http_code} -s -L -o /dev/null \ + -X POST \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer ${{ github.token }}"\ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/repos/$GITHUB_REPOSITORY/actions/workflows/ci.yml/dispatches \ + -d "{\"ref\":\"$INTO_BRANCH\"}" + ) + if [[ $RESP_CODE != "204" ]]; then + echo "Failed to dispatch workflow - HTTP response code was $RESP_CODE" + exit 1 + else + echo "Succesfully triggered CI workflow for $INTO_BRANCH" + fi done From a518b2239e866904bcb7339f6d6fe649a9da3d18 Mon Sep 17 00:00:00 2001 From: Steve Boyd Date: Thu, 3 Aug 2023 10:52:35 +1200 Subject: [PATCH 2/5] ENH Rebuild client/dist on merge-up --- action.yml | 153 +++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 136 insertions(+), 17 deletions(-) diff --git a/action.yml b/action.yml index d705624..050e7ae 100644 --- a/action.yml +++ b/action.yml @@ -127,12 +127,6 @@ runs: exit 1 fi done - - # Don't allow merge-ups when there are JS changes that would require a yarn build - if [[ $(echo "$FILES" | grep client/dist) != "" ]]; then - echo "Unable to mergeup between $FROM_BRANCH and $INTO_BRANCH - there are changes to JS dist files" - exit 1 - fi done # actions/checkout with fetch-depth: 0 will fetch ALL git history for the repository @@ -146,6 +140,7 @@ runs: shell: bash env: BRANCHES: ${{ steps.determine.outputs.branches }} + GITHUB_REPOSITORY: ${{ github.repository }} run: | # Set git user to github-actions bot # The 41898282+ email prefixed is the required, matches the ID here @@ -168,33 +163,157 @@ runs: git checkout $FROM_BRANCH git checkout $INTO_BRANCH + # Determine if we will rebuild dist file during merge-up + # This is based simply on if there are changes in the client/ directory + REBUILD=0 + CLIENT_DIFF_FILES=$(git diff --name-only $INTO_BRANCH...$FROM_BRANCH | grep -P ^client/) + if [[ $CLIENT_DIFF_FILES != "" ]]; then + REBUILD=1 + fi + echo "CLIENT_DIFF_FILES is:" + # The following line is quoted so that newlines show + echo "$CLIENT_DIFF_FILES" + echo "REBUILD is $REBUILD" + # Perform the merge-up - git merge --no-ff --no-commit $FROM_BRANCH + # `|| true is suffixed to the command to prevent the job from stopping on merge conflict + # This is because git will sent a non-zero exit code when there is a merge conflict + # We often expect a merge-conflict when there are client/dist file differences + git merge --no-ff --no-commit $FROM_BRANCH || true - # Check for merge conflicts - this is just an additional check that is probably - # not required as git seems like it does the equivalent of exit 1 when it - # detects a merge conflict. Still it doesn't hurt to be extra cautious. - GIT_STATUS=$(git status) - if [[ "$GIT_STATUS" =~ 'Changes not staged for commit' ]]; then + # Only merge conflicts in client/dist are allowed, stop for all others + # See https://git-scm.com/docs/git-status#_output for information on the porcelain format + UNMERGED_FILES=$(git status --porcelain=v1 | grep -P '^(DD|AU|UD|UA|DU|AA|UU)' | grep -v client/dist) || true + if [[ $UNMERGED_FILES != "" ]]; then echo "Merge conflict found when merging-up $FROM_BRANCH into $INTO_BRANCH. Aborting." + # The following line needs to be quoted so that line breaks show + echo "$UNMERGED_FILES" + exit 1 + fi + + # Rebuild client/dist if needed + if [[ $REBUILD == 1 ]]; then + + # Ensure .nvmrc is present + if ! [[ -f .nvmrc ]]; then + echo "Unable to find .nvmrc file" + exit 1 + fi + # Piping into xargs trims out whitespace + NVM_VERSION=$(cat .nvmrc | xargs) + echo "NVM_VERSION is $NVM_VERSION" + + # Ensure nvm is installed + if [[ $(which nvm) == "" ]]; then + wget -q -O __nvm_install.sh https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.4/install.sh + if [[ $(sha1sum __nvm_install.sh) != "c10365646e699a74e279e6ae71569430f82ba014 __nvm_install.sh" ]]; then + "Unable to verify integrity of nvm install script" + exit 1 + fi + . __nvm_install.sh + rm __nvm_install.sh + + # Load nvm without needing terminal restart + export NVM_DIR="$HOME/.nvm" + [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" + fi + + # Install correct version of node and yarn + nvm install $NVM_VERSION + nvm use $NVM_VERSION + if [[ $(which yarn) == "" ]]; then + npm install -g yarn + fi + + # Get composer name of the module + COMPOSER_NAME=$(jq -r ".name" composer.json) + + # Install silverstripe/admin in sibling directory if needed so that shared components are available + if [[ $COMPOSER_NAME != silverstripe/admin ]]; then + DIR=$(pwd) + + # Work out version of silverstripe/admin to checkout + cd .. + mkdir __tmp + cd __tmp + ADMIN_VERSION=$(COMPOSER_NAME=$COMPOSER_NAME INTO_BRANCH=$INTO_BRANCH php -r ' + $COMPOSER_NAME = getenv("COMPOSER_NAME"); + $INTO_BRANCH = getenv("INTO_BRANCH"); + $json = [ + "require" => [ + "$COMPOSER_NAME" => "$INTO_BRANCH.x-dev", + "silverstripe/recipe-kitchen-sink" => "*" + ], + "prefer-stable" => false, + "minimum-stability" => "dev" + ]; + file_put_contents("composer.json", json_encode($json, JSON_UNESCAPED_SLASHES)); + shell_exec("composer update --no-install"); + $lock = json_decode(file_get_contents("composer.lock"), true); + $version = array_values(array_filter( + $lock["packages"], + fn($p) => $p["name"] === "silverstripe/admin" + ))[0]["version"]; + echo str_replace(".x-dev", "", $version); + ') + echo "ADMIN_VERSION is $ADMIN_VERSION" + if ! [[ $ADMIN_VERSION =~ ^[0-9]+(\.[0-9]+)?$ ]]; then + echo "Unable to determine major or minor branch of silverstripe/admin version to checkout" + exit 1 + fi + cd .. + rm -rf __tmp + + # Install admin if required + if [[ ! -d admin ]]; then + git clone https://github.com/silverstripe/silverstripe-admin.git admin + fi + + # Checkout admin version + cd admin + git checkout $ADMIN_VERSION + yarn install + cd $DIR + fi + + # Rebuild dist files + yarn build + + # Only add client files + git add client/dist + fi + + # See https://git-scm.com/docs/git-status#_output for information on the porcelain format + GIT_STATUS=$(git status --porcelain=v1) + + # Check for any unmerged files + UNMERGED_FILES=$(echo "$GIT_STATUS" | grep -P '^(DD|AU|UD|UA|DU|AA|UU)') || true + if [[ $UNMERGED_FILES != "" ]]; then + echo "Unmerged files found when merging-up $FROM_BRANCH into $INTO_BRANCH. Aborting." + echo "$UNMERGED_FILES" exit 1 fi - # Check for any random files that shouldn't be committed - if [[ "$GIT_STATUS" =~ 'Untracked files' ]]; then + # Check for any random untracked files that shouldn't be committed + UNTRACKED_FILES=$(echo "$GIT_STATUS" | grep -P '^\?') || true + if [[ $UNTRACKED_FILES != "" ]]; then echo "Untracked files found when merging-up $FROM_BRANCH into $INTO_BRANCH. Aborting." + echo "$UNTRACKED_FILES" exit 1 fi # Continue if there's nothing to commit - if [[ "$GIT_STATUS" =~ 'nothing to commit, working tree clean' ]]; then + if [[ $GIT_STATUS == "" ]]; then echo "No changes found when merging-up $FROM_BRANCH into $INTO_BRANCH. Skipping." continue fi # Commit and push the merge-up - # --no-edit in the context of a merge commit uses the default auto-generated commit message. - git commit --no-edit + # The quotes in the commit message are intential and match the auto-generated + # title of e.g. Merge branch '2.0' into 2 + # Using this instead of simply `git status --no-edit` which auto-generates a commit message because + # that includes commented out details of the merge conflict if there was one + git commit -m "Merge branch '$FROM_BRANCH' into $INTO_BRANCH" git push origin $INTO_BRANCH echo "Succesfully merged-up $FROM_BRANCH into $INTO_BRANCH" From 839dc8c078dfdde1efe7da7347b41a6547e46e68 Mon Sep 17 00:00:00 2001 From: Steve Boyd Date: Mon, 7 Aug 2023 12:05:35 +1200 Subject: [PATCH 3/5] FIX Add or true after grep --- action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/action.yml b/action.yml index 050e7ae..51aff47 100644 --- a/action.yml +++ b/action.yml @@ -166,7 +166,7 @@ runs: # Determine if we will rebuild dist file during merge-up # This is based simply on if there are changes in the client/ directory REBUILD=0 - CLIENT_DIFF_FILES=$(git diff --name-only $INTO_BRANCH...$FROM_BRANCH | grep -P ^client/) + CLIENT_DIFF_FILES=$(git diff --name-only $INTO_BRANCH...$FROM_BRANCH | grep -P ^client/) || true if [[ $CLIENT_DIFF_FILES != "" ]]; then REBUILD=1 fi From 1e23091136d1abab2e50c5f1c74d9c9e4c68c40b Mon Sep 17 00:00:00 2001 From: Steve Boyd Date: Wed, 16 Aug 2023 12:49:40 +1200 Subject: [PATCH 4/5] FIX Find cms major for developer-docs --- action.yml | 2 +- branches.php | 3 ++- funcs.php | 12 ++++++++++-- tests/BranchesTest.php | 39 ++++++++++++++++++++++++++++++++++++++- 4 files changed, 51 insertions(+), 5 deletions(-) diff --git a/action.yml b/action.yml index 51aff47..61a0205 100644 --- a/action.yml +++ b/action.yml @@ -74,7 +74,7 @@ runs: # Download composer.json for use in branches.php curl -s -o __composer.json https://raw.githubusercontent.com/$GITHUB_REPOSITORY/$DEFAULT_BRANCH/composer.json - BRANCHES=$(MINIMUM_CMS_MAJOR=$MINIMUM_CMS_MAJOR DEFAULT_BRANCH=$DEFAULT_BRANCH php ${{ github.action_path }}/branches.php) + BRANCHES=$(MINIMUM_CMS_MAJOR=$MINIMUM_CMS_MAJOR DEFAULT_BRANCH=$DEFAULT_BRANCH GITHUB_REPOSITORY=$GITHUB_REPOSITORY php ${{ github.action_path }}/branches.php) echo "BRANCHES is $BRANCHES" if [[ $BRANCHES =~ "^FAILURE \- (.+)$" ]]; then MESSAGE=${BASH_REMATCH[1]} diff --git a/branches.php b/branches.php index 25b5844..7306d62 100644 --- a/branches.php +++ b/branches.php @@ -4,6 +4,7 @@ $defaultBranch = getenv('DEFAULT_BRANCH'); $minimumCmsMajor = getenv('MINIMUM_CMS_MAJOR'); +$githubRepository = getenv('GITHUB_REPOSITORY'); -$branches = branches($defaultBranch, $minimumCmsMajor); +$branches = branches($defaultBranch, $minimumCmsMajor, $githubRepository); echo implode(' ', $branches); diff --git a/funcs.php b/funcs.php index aa54717..52fa5dc 100644 --- a/funcs.php +++ b/funcs.php @@ -3,6 +3,7 @@ function branches( string $defaultBranch, string $minimumCmsMajor, + string $githubRepository, // The following params are purely for unit testing, for the actual github action it will read json files instead string $composerJson = '', string $branchesJson = '', @@ -28,7 +29,13 @@ function branches( } $defaultCmsMajor = ''; $matchedOnBranchThreeLess = false; - $version = preg_replace('#[^0-9\.]#', '', $json->require->{'silverstripe/framework'} ?? ''); + $version = ''; + if ($githubRepository === 'silverstripe/developer-docs') { + $version = $defaultBranch; + } + if (!$version) { + $version = preg_replace('#[^0-9\.]#', '', $json->require->{'silverstripe/framework'} ?? ''); + } if (!$version) { $version = preg_replace('#[^0-9\.]#', '', $json->require->{'silverstripe/cms'} ?? ''); } @@ -115,7 +122,8 @@ function branches( unset($branches[$i]); continue; } - if (isset($minorsWithStableTags[$major][$branch])) { + // for developer-docs which has no tags, pretend that every branch has a tag + if (isset($minorsWithStableTags[$major][$branch]) || $githubRepository === 'silverstripe/developer-docs') { $foundMinorBranchWithStableTag[$major] = true; } $foundMinorInMajor[$major] = true; diff --git a/tests/BranchesTest.php b/tests/BranchesTest.php index 8e79a52..6aacd1e 100644 --- a/tests/BranchesTest.php +++ b/tests/BranchesTest.php @@ -11,11 +11,19 @@ public function testBranches( array $expected, string $defaultBranch, string $minimumCmsMajor, + string $githubRepository, string $composerJson = '', string $branchesJson = '', string $tagsJson = '' ) { - $actual = branches($defaultBranch, $minimumCmsMajor, $composerJson, $branchesJson, $tagsJson); + $actual = branches( + $defaultBranch, + $minimumCmsMajor, + $githubRepository, + $composerJson, + $branchesJson, + $tagsJson + ); $this->assertSame($expected, $actual); } @@ -26,6 +34,7 @@ public function provideBranches() 'expected' => ['4.13', '4', '5.0', '5.1', '5', '6'], 'defaultBranch' => '5', 'minimumCmsMajor' => '4', + 'githubRepository' => 'lorem/ipsum', 'composerJson' => << ['4.13', '4', '5.1', '5'], 'defaultBranch' => '5', 'minimumCmsMajor' => '4', + 'githubRepository' => 'lorem/ipsum', 'composerJson' => << ['4.13', '4', '5.1', '5'], 'defaultBranch' => '5', 'minimumCmsMajor' => '4', + 'githubRepository' => 'lorem/ipsum', 'composerJson' => << ['4.13', '4', '5.1', '5'], 'defaultBranch' => '5', 'minimumCmsMajor' => '4', + 'githubRepository' => 'lorem/ipsum', 'composerJson' => << ['1.13', '2.0', '2.1', '2'], 'defaultBranch' => '2', 'minimumCmsMajor' => '4', + 'githubRepository' => 'lorem/ipsum', 'composerJson' => << ['1.13', '1', '2.1', '2.2', '2.3', '2'], 'defaultBranch' => '2', 'minimumCmsMajor' => '4', + 'githubRepository' => 'lorem/ipsum', 'composerJson' => << ['5.9', '5', '6.0', '6', '7'], 'defaultBranch' => '5', // this repo has a `5` branch for CMS 4 and a '6' branch for CMS 5 'minimumCmsMajor' => '4', + 'githubRepository' => 'lorem/ipsum', 'composerJson' => << [ + 'expected' => ['4.13', '4', '5.0', '5'], + 'defaultBranch' => '5', + 'minimumCmsMajor' => '4', + 'githubRepository' => 'silverstripe/developer-docs', + 'composerJson' => << << '[]', + ], ]; } } From f580d61efefc1470507fb7492f741b9633365ea9 Mon Sep 17 00:00:00 2001 From: Steve Boyd Date: Wed, 16 Aug 2023 15:04:28 +1200 Subject: [PATCH 5/5] MNT Run module-standardiser --- .github/workflows/action-ci.yml | 11 +++++++++++ .github/workflows/keepalive.yml | 17 +++++++++++++++++ .github/workflows/merge-up.yml | 17 +++++++++++++++++ LICENSE | 2 +- 4 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/action-ci.yml create mode 100644 .github/workflows/keepalive.yml create mode 100644 .github/workflows/merge-up.yml diff --git a/.github/workflows/action-ci.yml b/.github/workflows/action-ci.yml new file mode 100644 index 0000000..3fd70a4 --- /dev/null +++ b/.github/workflows/action-ci.yml @@ -0,0 +1,11 @@ +name: Action CI + +on: + push: + pull_request: + workflow_dispatch: + +jobs: + actionci: + name: Action CI + uses: silverstripe/gha-action-ci/.github/workflows/action-ci.yml@v1 diff --git a/.github/workflows/keepalive.yml b/.github/workflows/keepalive.yml new file mode 100644 index 0000000..8a2197e --- /dev/null +++ b/.github/workflows/keepalive.yml @@ -0,0 +1,17 @@ +name: Keepalive + +on: + # At 12:00 AM UTC, on day 21 of the month + schedule: + - cron: '0 0 21 * *' + workflow_dispatch: + +jobs: + keepalive: + name: Keepalive + # Only run cron on the silverstripe account + if: (github.event_name == 'schedule' && github.repository_owner == 'silverstripe') || (github.event_name != 'schedule') + runs-on: ubuntu-latest + steps: + - name: Keepalive + uses: silverstripe/gha-keepalive@v1 diff --git a/.github/workflows/merge-up.yml b/.github/workflows/merge-up.yml new file mode 100644 index 0000000..a284f0e --- /dev/null +++ b/.github/workflows/merge-up.yml @@ -0,0 +1,17 @@ +name: Merge-up + +on: + # At 12:00 AM UTC, only on Saturday + schedule: + - cron: '0 0 * * 6' + workflow_dispatch: + +jobs: + merge-up: + name: Merge-up + # Only run cron on the silverstripe account + if: (github.event_name == 'schedule' && github.repository_owner == 'silverstripe') || (github.event_name != 'schedule') + runs-on: ubuntu-latest + steps: + - name: Merge-up + uses: silverstripe/gha-merge-up@v1 diff --git a/LICENSE b/LICENSE index 82361bc..f86cb0e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ BSD 3-Clause License -Copyright (c) 2023, SilverStripe Limited - www.silverstripe.com +Copyright (c) 2023, Silverstripe Limited - www.silverstripe.com All rights reserved. Redistribution and use in source and binary forms, with or without