diff --git a/.github/workflows/automated-release-tasks.yml b/.github/workflows/automated-release-tasks.yml new file mode 100644 index 000000000..726f0f49b --- /dev/null +++ b/.github/workflows/automated-release-tasks.yml @@ -0,0 +1,63 @@ +name: automated-release-tasks +on: + schedule: + # Cron syntax is "minute[0-59] hour[0-23] date[1-31] month[1-12] day[0-6]". '*' is 'any value,' and multiple values + # can be specified with comma-separated lists. All times are UTC. + # So this expression means "run at 12 PM UTC, every Friday". + - cron: "0 12 * * 5" + + +jobs: + # Depending on circumstances, we may want to exit early instead of running the workflow to completion. + verify-should-run: + runs-on: macos-latest + outputs: + should-run: ${{ steps.main.outputs.should_run }} + steps: + - id: main + run: | + # `date -u` returns UTC datetime, and `%u` formats the output to be the day of the week, with 1 being Monday, + # 2 being Tuesday, etc. + TODAY_DOW=$(date -u +%u) + # This `date` expression returns the last Tuesday of the month, which is our Release Day. %d formats the output + # as the day of the month (1-31). + NEXT_RELEASE_DATE=$(date -u -v1d -v+1m -v-1d -v-tue +%d) + # This `date` expression returns next Tuesday, and `%d` formats the output as the day of the month (1-31). + NEXT_TUESDAY_DATE=$(date -u -v+tue +%d) + # This workflow should only be allowed to run to completion on the Friday before Release Day. + [[ $TODAY_DOW != 5 || $NEXT_RELEASE_DATE != $NEXT_TUESDAY_DATE ]] && echo "should_run=false" >> "$GITHUB_OUTPUT" || echo "should_run=true" >> "$GITHUB_OUTPUT" + create-v5-release-branch: + runs-on: macos-latest + needs: verify-should-run + if: ${{ needs.verify-should-run.outputs.should-run == 'true' }} + steps: + - name: Invoke v5 beta workflow + uses: actions/github-script@v6 + with: + github-token: ${{ secrets.SVC_CLI_BOT_GITHUB_TOKEN }} + script: | + await github.rest.actions.createWorkflowDispatch({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: 'create-release-branch.yml', + ref: 'dev' + }); + create-v4-release-branch: + runs-on: macos-latest + needs: verify-should-run + if: ${{ needs.verify-should-run.outputs.should-run == 'true' }} + steps: + - name: Invoke v4 GA workflow + uses: actions/github-script@v6 + with: + github-token: ${{ secrets.SVC_CLI_BOT_GITHUB_TOKEN }} + script: | + await github.rest.actions.createWorkflowDispatch({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: 'create-release-branch.yml', + ref: 'dev-4', + inputs: { + "release-type": "minor" + } + }); diff --git a/.github/workflows/create-github-release.yml b/.github/workflows/create-github-release.yml index b8368b57d..1be0f5aae 100644 --- a/.github/workflows/create-github-release.yml +++ b/.github/workflows/create-github-release.yml @@ -2,7 +2,7 @@ name: create-github-release on: pull_request: branches: - - main-5 + - main types: # There's no event type for "merged", so we just run any time a PR is closed, and exit early # if the PR wasn't actually merged. @@ -10,7 +10,7 @@ on: jobs: create-github-release: - # Since the workflow runs any time a PR against main-5 is closed, we need this + # Since the workflow runs any time a PR against main is closed, we need this # `if` to make sure that the workflow only does anything meaningful if the PR # was actually merged. if: github.event.pull_request.merged == true @@ -18,10 +18,10 @@ jobs: permissions: contents: write steps: - - name: Checkout main-5 + - name: Checkout main uses: actions/checkout@v4 with: - ref: main-5 + ref: main - name: Get version property id: get-version-property run: | @@ -32,7 +32,7 @@ jobs: with: tag_name: v${{ steps.get-version-property.outputs.package_version }} name: v${{ steps.get-version-property.outputs.package_version }} - target_commitish: main-5 + target_commitish: main body: See [release notes](https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/guide/release-notes.html) token: ${{ secrets.SVC_CLI_BOT_GITHUB_TOKEN }} make_latest: true diff --git a/.github/workflows/create-release-branch.yml b/.github/workflows/create-release-branch.yml index 4007ef6b5..8f41a9b8a 100644 --- a/.github/workflows/create-release-branch.yml +++ b/.github/workflows/create-release-branch.yml @@ -12,10 +12,10 @@ jobs: outputs: branch-name: ${{ steps.create-branch.outputs.branch_name }} steps: - # Checkout `dev-5` + # Checkout `dev` - uses: actions/checkout@v4 with: - ref: 'dev-5' + ref: 'dev' # We need to set up Node and install our Node dependencies. - uses: actions/setup-node@v4 with: @@ -24,7 +24,7 @@ jobs: # Increment the version as desired locally, without actually committing anything. - name: Locally increment version run: | - npm --no-git-tag-version version prerelease --preid alpha + npm --no-git-tag-version version prerelease --preid beta # The branch protection rule for `release-x.y.z` branches prevents pushing commits directly. To work around this, # we create an interim branch that we _can_ push commits to, and we'll do our version bookkeeping in that branch # instead. diff --git a/.github/workflows/daily-smoke-tests.yml b/.github/workflows/daily-smoke-tests.yml index 9a0f0099d..16f0ada10 100644 --- a/.github/workflows/daily-smoke-tests.yml +++ b/.github/workflows/daily-smoke-tests.yml @@ -12,3 +12,17 @@ jobs: uses: ./.github/workflows/run-tests.yml with: node-matrix: "[{version: 'lts/*', artifact: 'lts'}, {version: 'latest', artifact: 'latest'}]" + v4-smoke-test: + runs-on: macos-latest + steps: + - name: Invoke v4 smoke tests + uses: actions/github-script@v6 + with: + github-token: ${{ secrets.SVC_CLI_BOT_GITHUB_TOKEN }} + script: | + await github.rest.actions.createWorkflowDispatch({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: 'daily-smoke-tests.yml', + ref: 'dev-4' + }); diff --git a/.github/workflows/production-heartbeat.yml b/.github/workflows/production-heartbeat.yml index b72143bf4..5b4842514 100644 --- a/.github/workflows/production-heartbeat.yml +++ b/.github/workflows/production-heartbeat.yml @@ -10,139 +10,16 @@ on: - cron: '45 13,17,21 * * 1,2,3,4,5' jobs: production-heartbeat: - strategy: - # By default, if any job in a matrix fails, all other jobs are immediately cancelled. This makes the jobs run to completion instead. - fail-fast: false - matrix: - os: [{vm: ubuntu-latest, exe: .sh}, {vm: windows-2019, exe: .cmd}] - node: ['lts/*'] - runs-on: ${{ matrix.os.vm }} - timeout-minutes: 60 + runs-on: macos-latest steps: - # === Setup. We need to get the code, set up nodejs, and create the results directory. === - - uses: actions/checkout@v4 + - name: Invoke v4 workflow + uses: actions/github-script@v6 with: - ref: 'release' - - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node }} - - run: mkdir smoke-test-results - - # === Set our environment variables, either using default values or the repo's secrets === - - name: Set environment variables - id: env_var_setup - # We'll want to use bash for this, to avoid any cross-platform shenanigans - shell: bash - run: | - # In the following script, the use of the `echo "name=value" >> $GITHUB_ENV` structure is used to set/update - # environment variables. Such updates are visible to all subsequent steps. - # - # If the CLI_VERSION repo secret is set, we want to install that version ofsf-cli, so we set an environment - # variable. Otherwise, we leave the environment variable unset, so it implicitly defaults to `latest`. - # Note: This can be used to intentionally fail the GHA by providing an invalid version number. - if [[ -n "${{ secrets.CLI_VERSION }}" ]]; then - echo "CLI_VERSION=@${{ secrets.CLI_VERSION}}" >> $GITHUB_ENV - fi - # If the SCANNER_VERSION repo secret is set, we want to install that version of sfdx-scanner, so we set an - # environment variable. Otherwise, we leave the environment variable unset, so it implicitly defaults to `latest`. - # Note: This can be used to intentionally fail the GHA by providing an invalid version number. - if [[ -n "${{ secrets.SCANNER_VERSION }}" ]]; then - echo "SCANNER_VERSION=@${{ secrets.SCANNER_VERSION }}" >> $GITHUB_ENV - fi - # If the FAIL_SMOKE_TESTS repo secret is set to ANY value, we should respond by deleting the `test/test-jars` - # folder. The smoke tests expect this folder's contents to exist, so an invocation of `scanner:rule:add` should - # fail, thereby failing the smoke tests as a whole. - # Note: This serves no purpose aside from providing a way to simulate a smoke test failure. - if [[ -n "${{ secrets.FAIL_SMOKE_TESTS }}" ]]; then - rm -rf ./test/test-jars - fi - - - # === Make three attempts to install SF through npm === - - name: Install SF - id: sf_install - # If the first attempt fails, wait a minute and try again. After a second failure, wait 5 minutes then try again. Then give up. - # Set an output parameter, `retry_count`, indicating the number of retry attempts that were made. - run: | - (echo "::set-output name=retry_count::0" && npm install -g @salesforce/cli${{ env.CLI_VERSION }}) || - (echo "::set-output name=retry_count::1" && sleep 60 && npm install -g @salesforce/cli${{ env.CLI_VERSION }}) || - (echo "::set-output name=retry_count::2" && sleep 300 && npm install -g @salesforce/cli${{ env.CLI_VERSION }}) - - # === Make three attempts to install the scanner plugin through sf === - - name: Install Scanner Plugin - id: scanner_install - # If the first attempt fails, wait a minute and try again. After a second failure, wait 5 minutes then try again. Then give up. - # Set an output parameter, `retry_count`, indicating the number of retry attempts that were made. - run: | - (echo "::set-output name=retry_count::0" && sf plugins install @salesforce/sfdx-scanner${{ env.SCANNER_VERSION }}) || - (echo "::set-output name=retry_count::1" && sleep 60 && sf plugins install @salesforce/sfdx-scanner${{ env.SCANNER_VERSION }}) || - (echo "::set-output name=retry_count::2" && sleep 300 && sf plugins install @salesforce/sfdx-scanner${{ env.SCANNER_VERSION }}) - - # === Log the installed plugins for easier debugging === - - name: Log plugins - run: sf plugins - - # === Attempt to execute the smoke tests === - - name: Run smoke tests - id: smoke_tests - run: smoke-tests/smoke-test${{ matrix.os.exe }} sf - - # === Upload the smoke-test-results folder as an artifact === - - name: Upload smoke-test-results folder as artifact - if: ${{ always() }} - uses: actions/upload-artifact@v4 - with: - name: smoke-test-results-${{ runner.os }} - path: smoke-test-results - - # === Report any problems === - - name: Report problems - # There are problems if any step failed or was skipped. - # Note that the `join()` call omits null values, so if any steps were skipped, they won't have a corresponding - # value in the string. - if: ${{ failure() || cancelled() }} - shell: bash - env: - # If we're here because steps failed or were skipped, then that's a critical problem. Otherwise it's a normal one. - # We can't use the `failure()` or `cancelled()` convenience methods outside of the `if` condition, hence the - # `contains()` calls. - IS_CRITICAL: ${{ contains(join(steps.*.outcome), 'failure') || contains(join(steps.*.outcome), 'skipped') }} - # Build the status strings for each step as environment variables to save space later. Null retry_count values - # will be replaced with `n/a` to maintain readability in the alert. - CLI_INSTALL_STATUS: ${{ steps.sf_install.outcome }} after ${{ steps.sf_install.outputs.retry_count || 'n/a' }} retries - SCANNER_INSTALL_STATUS: ${{ steps.scanner_install.outcome }} after ${{ steps.scanner_install.outputs.retry_count || 'n/a' }} retries - SMOKE_TESTS_STATUS: ${{ steps.smoke_tests.outcome }} - # A link to this run, so the PagerDuty assignee can quickly get here. - RUN_LINK: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} - run: | - # GHA env-vars don't have robust conditional logic, so we'll use this if-else branch to define some bash env-vars. - if [[ ${{ env.IS_CRITICAL }} == true ]]; then - ALERT_SEV="critical" - ALERT_SUMMARY="Production heartbeat script failed on ${{ runner.os }}" - else - ALERT_SEV="info" - ALERT_SUMMARY="Production heartbeat script succeeded with retries on ${{ runner.os }}" - fi - # Define a helper function to create our POST request's data, to sidestep issues with nested quotations. - generate_post_data() { - # This is known as a HereDoc, and it lets us declare multi-line input ending when the specified limit string, - # in this case EOF, is encountered. - cat <> $GITHUB_OUTPUT id: get-branch-commit # Checkout the tag we want to release, and get its head commit as output for later. @@ -31,9 +31,9 @@ jobs: - name: Fail non-matching commits if: ${{ steps.get-branch-commit.outputs.COMMIT_ID != steps.get-tag-commit.outputs.COMMIT_ID }} run: | - echo "Tag commit must match latest commit in main-5. Branch is ${{ steps.get-branch-commit.outputs.COMMIT_ID }}. Tag is ${{ steps.get-tag-commit.outputs.COMMIT_ID }}" + echo "Tag commit must match latest commit in main. Branch is ${{ steps.get-branch-commit.outputs.COMMIT_ID }}. Tag is ${{ steps.get-tag-commit.outputs.COMMIT_ID }}" exit 1 - # Verify that the `package.json`'s version property is 5.Y.Z, as we want to restrict the `dev-5` and `main-5` + # Verify that the `package.json`'s version property is 5.Y.Z, as we want to restrict the `dev` and `main` # branches to publishing v5.x. - name: Verify major version run: | @@ -53,7 +53,7 @@ jobs: with: ctc: false # We've been told we don't have to care about this until someone makes us care. sign: true - tag: latest-alpha-rc # Publish as a release candidate, so we can do our validations against it. + tag: latest-beta-rc # Publish as a release candidate, so we can do our validations against it. githubTag: ${{ github.event.release.tag_name || inputs.tag }} secrets: inherit # Step 3: Run smoke tests against the release candidate. @@ -81,7 +81,7 @@ jobs: java-version: '11' # For now, Java version is hardcoded. # Install SF, and the release candidate version. - run: npm install -g @salesforce/cli - - run: sf plugins install @salesforce/plugin-code-analyzer@latest-alpha-rc + - run: sf plugins install @salesforce/plugin-code-analyzer@latest-beta-rc # Log the installed plugins for easier debugging. - run: sf plugins # Attempt to run the smoke tests. @@ -102,9 +102,9 @@ jobs: node-version: 'lts/*' - run: | echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc - npm dist-tag add @salesforce/plugin-code-analyzer@${{ github.event.release.tag_name || inputs.tag }} latest-alpha + npm dist-tag add @salesforce/plugin-code-analyzer@${{ github.event.release.tag_name || inputs.tag }} latest-beta npm dist-tag add @salesforce/plugin-code-analyzer@${{ github.event.release.tag_name || inputs.tag }} latest - # Step 5: Create a Pull Request for merging `main-5` into `dev-5` + # Step 5: Create a Pull Request for merging `main` into `dev` create-main2dev-pull-request: needs: promote-to-latest runs-on: macos-latest @@ -114,31 +114,31 @@ jobs: contents: write pull-requests: write steps: - # Check out `main-5` + # Check out `main` - uses: actions/checkout@v4 with: - ref: 'main-5' - # Create a new branch based on `main-5`, so that merge conflicts can be manually resolved if need be. + ref: 'main' + # Create a new branch based on `main`, so that merge conflicts can be manually resolved if need be. - run: | NEW_VERSION=$(jq -r ".version" package.json) git checkout -b m2d/v$NEW_VERSION git push --set-upstream origin m2d/v$NEW_VERSION - # Create a Pull Request from the new branch into `dev-5`. + # Create a Pull Request from the new branch into `dev`. - run: | NEW_VERSION=$(jq -r ".version" package.json) # For whatever reason, the version of 'echo' on GHAs doesn't process backspace by default. # The non-POSIX-standard -e flag causes it to do that. echo -e "This branch and PR were automatically created following the successful release of v$NEW_VERSION.\n\ - It must be MERGED into dev-5, NOT SQUASHED OR REBASED. Squashing or rebasing this branch onto dev-5 can cause potentially irreconcilable merge conflicts later.\n\ - As an additional safeguard and reminder, the title of this PR MUST include the word 'merging' in the description portion of the PR title, e.g., 'Main2Dev @W-XXXXXXX@ Merging main-5 to dev-5 after vX.Y.Z'.\n\ - If there are conflicts between dev-5 and this branch, you should do the following locally:\n\ - - $ git checkout dev-5\n\ + It must be MERGED into dev, NOT SQUASHED OR REBASED. Squashing or rebasing this branch onto dev can cause potentially irreconcilable merge conflicts later.\n\ + As an additional safeguard and reminder, the title of this PR MUST include the word 'merging' in the description portion of the PR title, e.g., 'Main2Dev @W-XXXXXXX@ Merging main to dev after vX.Y.Z'.\n\ + If there are conflicts between dev and this branch, you should do the following locally:\n\ + - $ git checkout dev\n\ - $ git pull\n\ - $ git fetch --all\n\ - $ git checkout m2d/v$NEW_VERSION\n\ - - $ git pull origin dev-5 --no-rebase # You MUST include this flag, or someone's day will be ruined.\n\ + - $ git pull origin dev --no-rebase # You MUST include this flag, or someone's day will be ruined.\n\ - Resolve the merge conflicts manually. When in doubt, ask the code's author for help.\n\ - $ git commit\n\ - $ git push" > body.txt # Create the pull request. - gh pr create -B dev-5 -H m2d/v$NEW_VERSION --title "Filler title. Read description and rename." -F body.txt + gh pr create -B dev -H m2d/v$NEW_VERSION --title "Filler title. Read description and rename." -F body.txt diff --git a/.github/workflows/validate-pr.yml b/.github/workflows/validate-pr.yml index d2ed0a913..2e1747677 100644 --- a/.github/workflows/validate-pr.yml +++ b/.github/workflows/validate-pr.yml @@ -4,14 +4,14 @@ on: types: [edited, opened, reopened, synchronize] jobs: - # We want to prevent cross-contamination between the 3.x and 4.x pipelines. So we should prevent PRs - # based on this flow to merge into `dev-3` or `release-3`. + # We want to prevent cross-contamination between the 4.x and 5.x pipelines. So we should prevent PRs + # based on this flow to merge into `dev-4` or `main-4`. verify_target_branch: runs-on: ubuntu-latest steps: - - if: ${{ github.base_ref == 'dev-3' || github.base_ref == 'release-3' }} + - if: ${{ github.base_ref == 'dev-4' || github.base_ref == 'main-4' }} run: | - echo "Forbidden to merge this branch into dev-3 or release-3" + echo "Forbidden to merge this branch into dev-4 or release-4" exit 1 # We need to verify that the Pull Request's title matches the desired format. verify_pr_title: diff --git a/github-actions/verify-pr-title/dist/index.js b/github-actions/verify-pr-title/dist/index.js index f1f8e7cae..e7ef48617 100644 --- a/github-actions/verify-pr-title/dist/index.js +++ b/github-actions/verify-pr-title/dist/index.js @@ -31258,7 +31258,7 @@ function run() { return; } } - else if (baseBranch == "dev" || /^release-\d+\.\d+\.\d+$/.test(baseBranch)) { + else if (baseBranch == "dev" || /^release-\d+\.\d+\.\d+\.*$/.test(baseBranch)) { // There's a title convention for merging feature branch PRs into `dev` or `release-X.Y.Z` // branches. if ((0, verifyFeaturePrTitle_1.verifyFeaturePrTitle)(title)) { diff --git a/github-actions/verify-pr-title/src/index.ts b/github-actions/verify-pr-title/src/index.ts index 8de7ff413..7ab4e6eb9 100644 --- a/github-actions/verify-pr-title/src/index.ts +++ b/github-actions/verify-pr-title/src/index.ts @@ -46,7 +46,7 @@ function run(): void { ); return; } - } else if (baseBranch == "dev" || /^release-\d+\.\d+\.\d+$/.test(baseBranch)) { + } else if (baseBranch == "dev" || /^release-\d+\.\d+\.\d+\.*$/.test(baseBranch)) { // There's a title convention for merging feature branch PRs into `dev` or `release-X.Y.Z` // branches. if (verifyFeaturePrTitle(title)) {