Update code-freeze tool to work with accelerated
This commit is contained in:
parent
703936e307
commit
4d6d57e63c
|
@ -1,7 +1,7 @@
|
|||
name: 'Release: Code freeze'
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 23 * * 1' # Run at 2300 UTC on Mondays.
|
||||
- cron: '0 0 * * 4' # Run at start of day UTC on Thursdays.
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
timeOverride:
|
||||
|
@ -31,10 +31,15 @@ jobs:
|
|||
issues: write
|
||||
pull-requests: write
|
||||
outputs:
|
||||
freeze: ${{ steps.check-freeze.outputs.freeze }}
|
||||
nextReleaseBranch: ${{ steps.branch.outputs.nextReleaseBranch }}
|
||||
nextReleaseVersion: ${{ steps.milestone.outputs.nextReleaseVersion }}
|
||||
nextDevelopmentVersion: ${{ steps.milestone.outputs.nextDevelopmentVersion }}
|
||||
isTodayAcceleratedFreeze: ${{ steps.get-versions.outputs.isTodayAcceleratedFreeze }}
|
||||
isTodayMonthlyFreeze: ${{ steps.get-versions.outputs.isTodayMonthlyFreeze }}
|
||||
acceleratedVersion: ${{ steps.get-versions.outputs.acceleratedVersion }}
|
||||
monthlyVersion: ${{ steps.get-versions.outputs.monthlyVersion }}
|
||||
monthlyVersionXY: ${{ steps.get-versions.outputs.monthlyVersionXY }}
|
||||
releasesFrozenToday: ${{ steps.get-versions.outputs.releasesFrozenToday }}
|
||||
acceleratedBranch: ${{ steps.get-versions.outputs.acceleratedBranch }}
|
||||
monthlyBranch: ${{ steps.get-versions.outputs.monthlyBranch }}
|
||||
monthlyMilestone: ${{ steps.get-versions.outputs.monthlyMilestone }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
@ -60,43 +65,74 @@ jobs:
|
|||
pnpm build
|
||||
working-directory: tools/monorepo-utils
|
||||
|
||||
- name: 'Check whether today is the code freeze day'
|
||||
id: check-freeze
|
||||
run: pnpm utils code-freeze verify-day -o $TIME_OVERRIDE
|
||||
- name: 'Get the versions for the accelerated and monthly releases'
|
||||
id: get-versions
|
||||
run: pnpm utils code-freeze get-version -o $TIME_OVERRIDE
|
||||
|
||||
- name: Create next milestone
|
||||
- name: Create next monthly milestone
|
||||
id: milestone
|
||||
if: steps.check-freeze.outputs.freeze == 'true'
|
||||
if: steps.get-versions.outputs.isTodayMonthlyFreeze == 'yes'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: pnpm run utils code-freeze milestone -o ${{ github.repository_owner }}
|
||||
run: pnpm run utils code-freeze milestone -o ${{ github.repository_owner }} -m ${{ steps.get-versions.outputs.monthlyMilestone }}
|
||||
|
||||
- name: Create next release branch
|
||||
- name: Create next monthly release branch
|
||||
id: branch
|
||||
if: steps.check-freeze.outputs.freeze == 'true'
|
||||
if: steps.get-versions.outputs.isTodayMonthlyFreeze == 'yes'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: pnpm run utils code-freeze branch -o ${{ github.repository_owner }}
|
||||
run: pnpm run utils code-freeze branch -o ${{ github.repository_owner }} -b ${{ steps.get-versions.outputs.monthlyBranch }}
|
||||
|
||||
- name: Create next accelerated release branch
|
||||
id: branch-accel
|
||||
if: steps.get-versions.outputs.isTodayAcceleratedFreeze == 'yes'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: pnpm run utils code-freeze branch -o ${{ github.repository_owner }} -b ${{ steps.get-versions.outputs.acceleratedBranch }}
|
||||
|
||||
- name: Bump versions for Beta.1 monthly release
|
||||
id: version-bump
|
||||
if: steps.get-versions.outputs.isTodayMonthlyFreeze == 'yes'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: pnpm run utils code-freeze version-bump -o ${{ github.repository_owner }} -b ${{ steps.get-versions.outputs.monthlyBranch }} -c ${{ steps.get-versions.outputs.monthlyVersion }}-beta.1
|
||||
|
||||
- name: Bump versions for accelerated release
|
||||
id: version-bump-accel
|
||||
if: steps.get-versions.outputs.isTodayAcceleratedFreeze == 'yes'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: pnpm run utils code-freeze version-bump -o ${{ github.repository_owner }} -b ${{ steps.get-versions.outputs.acceleratedBranch }} -c ${{ steps.get-versions.outputs.acceleratedVersion }} -af
|
||||
|
||||
- name: Prep accelerated release
|
||||
id: accel-release-prep
|
||||
if: steps.get-versions.outputs.isTodayAcceleratedFreeze == 'yes'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: pnpm utils code-freeze accelerated-prep -o ${{ github.repository_owner }} -b ${{ steps.get-versions.outputs.acceleratedBranch }} -c ${{ steps.get-versions.outputs.acceleratedVersion }} ${{ steps.get-versions.outputs.acceleratedReleaseDate }}
|
||||
|
||||
- name: Prepare trunk for next development cycle
|
||||
id: prep-trunk
|
||||
if: steps.check-freeze.outputs.freeze == 'true'
|
||||
if: steps.get-versions.outputs.isTodayMonthlyFreeze == 'yes'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: pnpm run utils code-freeze version-bump ${{ steps.milestone.outputs.nextDevelopmentVersion }}.0-dev -o ${{ github.repository_owner }}
|
||||
run: pnpm run utils code-freeze version-bump ${{ steps.get-versions.outputs.monthlyMilestone }}-dev -o ${{ github.repository_owner }}
|
||||
|
||||
- name: Generate changelog changes
|
||||
id: changelog
|
||||
if: steps.check-freeze.outputs.freeze == 'true'
|
||||
if: steps.get-versions.outputs.isTodayMonthlyFreeze == 'yes'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: pnpm run utils code-freeze changelog -o ${{ github.repository_owner }} -v ${{ steps.milestone.outputs.nextReleaseVersion }}
|
||||
run: pnpm run utils code-freeze changelog -c -o ${{ github.repository_owner }} -v ${{ steps.get-versions.outputs.monthlyVersionXY }}
|
||||
|
||||
|
||||
notify-slack:
|
||||
name: 'Sends code freeze notification to Slack'
|
||||
runs-on: ubuntu-20.04
|
||||
needs: code-freeze-prep
|
||||
if: ${{ needs.code-freeze-prep.outputs.freeze == 'true' && inputs.skipSlackPing != true }}
|
||||
if: ${{ inputs.skipSlackPing != true && ( needs.code-freeze-prep.outputs.isTodayAcceleratedFreeze == 'yes' || needs.code-freeze-prep.outputs.isTodayMonthlyFreeze == 'yes' ) }}
|
||||
outputs:
|
||||
ts: ${{ steps.notify.outputs.ts }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
@ -126,6 +162,210 @@ jobs:
|
|||
id: notify
|
||||
run: |
|
||||
pnpm utils slack "${{ secrets.CODE_FREEZE_BOT_TOKEN }}" "
|
||||
:warning-8c: ${{ needs.code-freeze-prep.outputs.nextReleaseVersion }} Code Freeze :ice_cube:
|
||||
The automation to cut the release branch for ${{ needs.code-freeze-prep.outputs.nextReleaseVersion }} has run. Any PRs that were not already merged will be a part of ${{ needs.code-freeze-prep.outputs.nextDevelopmentVersion }} by default. If you have something that needs to make ${{ needs.code-freeze-prep.outputs.nextReleaseVersion }} that hasn't yet been merged, please see the <${{ secrets.FG_LINK }}/code-freeze-for-woocommerce-core-release/|fieldguide page for the code freeze>.
|
||||
:warning-8c: ${{ join( fromJSON( needs.code-freeze-prep.outputs.releasesFrozenToday ), ' and ' ) }} Code Freeze :ice_cube:
|
||||
The freeze automation for ${{ join( fromJSON( needs.code-freeze-prep.outputs.releasesFrozenToday ), ' and ' ) }} has finished. ${{ ( needs.code-freeze-prep.outputs.isTodayMonthlyFreeze == 'yes' && 'If you need to request a code freeze exception, see the <' ) || '' }}${{ ( needs.code-freeze-prep.outputs.isTodayMonthlyFreeze == 'yes' && secrets.FG_LINK ) || '' }}${{ ( needs.code-freeze-prep.outputs.isTodayMonthlyFreeze == 'yes' && '/code-freeze-for-woocommerce-core-release/|fieldguide page for the code freeze>.' ) || '' }}
|
||||
|
||||
The build for ${{ join( fromJSON( needs.code-freeze-prep.outputs.releasesFrozenToday ), ' and ' ) }} will appear in this thread shortly... :thread:
|
||||
" "${{ inputs.slackChannelOverride || secrets.WOO_RELEASE_SLACK_CHANNEL }}"
|
||||
|
||||
build-monthly:
|
||||
name: Build beta zip file
|
||||
runs-on: ubuntu-20.04
|
||||
needs: code-freeze-prep
|
||||
if: ${{ needs.code-freeze-prep.outputs.isTodayMonthlyFreeze == 'yes' }}
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ needs.code-freeze-prep.outputs.monthlyBranch }}
|
||||
|
||||
- name: Setup WooCommerce Monorepo
|
||||
uses: ./.github/actions/setup-woocommerce-monorepo
|
||||
with:
|
||||
build: false
|
||||
|
||||
- name: Build zip
|
||||
working-directory: plugins/woocommerce
|
||||
run: bash bin/build-zip.sh
|
||||
|
||||
- name: Upload the zip file as an artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
name: double-zipped-woocommerce.${{ needs.code-freeze-prep.outputs.monthlyVersion }}-beta.1
|
||||
path: plugins/woocommerce/woocommerce.zip
|
||||
retention-days: 2
|
||||
|
||||
build-a:
|
||||
name: Build accelerated zip file
|
||||
runs-on: ubuntu-20.04
|
||||
needs: code-freeze-prep
|
||||
if: ${{ needs.code-freeze-prep.outputs.isTodayAcceleratedFreeze == 'yes' }}
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ needs.code-freeze-prep.outputs.acceleratedBranch }}
|
||||
|
||||
- name: Setup WooCommerce Monorepo
|
||||
uses: ./.github/actions/setup-woocommerce-monorepo
|
||||
with:
|
||||
build: false
|
||||
|
||||
- name: Build zip
|
||||
working-directory: plugins/woocommerce
|
||||
run: bash bin/build-zip.sh
|
||||
|
||||
- name: Upload the zip file as an artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
name: double-zipped-woocommerce.${{ needs.code-freeze-prep.outputs.acceleratedVersion }}
|
||||
path: plugins/woocommerce/woocommerce.zip
|
||||
retention-days: 2
|
||||
|
||||
slack-upload-monthly:
|
||||
name: Upload Beta to Slack
|
||||
runs-on: ubuntu-20.04
|
||||
needs: [ code-freeze-prep, notify-slack, build-monthly ]
|
||||
if: ${{ needs.code-freeze-prep.outputs.isTodayMonthlyFreeze == 'yes' && inputs.skipSlackPing != true }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup PNPM
|
||||
uses: pnpm/action-setup@c3b53f6a16e57305370b4ae5a540c2077a1d50dd
|
||||
with:
|
||||
version: '8.6.7'
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
cache: pnpm
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
- name: Install prerequisites
|
||||
run: |
|
||||
pnpm install --filter monorepo-utils --ignore-scripts
|
||||
# ignore scripts speeds up setup signficantly, but we still need to build monorepo utils
|
||||
pnpm build
|
||||
working-directory: tools/monorepo-utils
|
||||
|
||||
- id: download
|
||||
uses: actions/download-artifact@v3
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
name: double-zipped-woocommerce.${{ needs.code-freeze-prep.outputs.monthlyVersion }}-beta.1
|
||||
path: download
|
||||
|
||||
- run: ls -lah ${{steps.download.outputs.download-path}}
|
||||
|
||||
- name: Send release zip to Slack
|
||||
id: send-file-slack
|
||||
run : |
|
||||
pnpm utils slack file "${{ secrets.CODE_FREEZE_BOT_TOKEN }}" "Here's the generated release build for ${{ needs.code-freeze-prep.outputs.monthlyVersion }}-beta.1" "${{ steps.download.outputs.download-path }}/woocommerce.zip" "${{ inputs.slackChannelOverride || secrets.WOO_RELEASE_SLACK_CHANNEL }}" --reply-ts ${{ needs.notify-slack.outputs.ts }} --filename "woocommerce.${{ needs.code-freeze-prep.outputs.monthlyVersion }}-beta.1.zip"
|
||||
|
||||
slack-upload-accelerated:
|
||||
name: Upload Accelerated to Slack
|
||||
runs-on: ubuntu-20.04
|
||||
needs: [ code-freeze-prep, notify-slack, build-a ]
|
||||
if: ${{ needs.code-freeze-prep.outputs.isTodayAcceleratedFreeze == 'yes' && inputs.skipSlackPing != true }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup PNPM
|
||||
uses: pnpm/action-setup@c3b53f6a16e57305370b4ae5a540c2077a1d50dd
|
||||
with:
|
||||
version: '8.6.7'
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
cache: pnpm
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
- name: Install prerequisites
|
||||
run: |
|
||||
pnpm install --filter monorepo-utils --ignore-scripts
|
||||
# ignore scripts speeds up setup signficantly, but we still need to build monorepo utils
|
||||
pnpm build
|
||||
working-directory: tools/monorepo-utils
|
||||
|
||||
- id: download
|
||||
uses: actions/download-artifact@v3
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
name: double-zipped-woocommerce.${{ needs.code-freeze-prep.outputs.acceleratedVersion }}
|
||||
path: download
|
||||
|
||||
- run: ls -lah ${{steps.download.outputs.download-path}}
|
||||
|
||||
- name: Send release zip to Slack
|
||||
id: send-file-slack
|
||||
run : |
|
||||
pnpm utils slack file "${{ secrets.CODE_FREEZE_BOT_TOKEN }}" "Here's the generated release build for ${{ needs.code-freeze-prep.outputs.acceleratedVersion }}" "${{ steps.download.outputs.download-path }}/woocommerce.zip" "${{ inputs.slackChannelOverride || secrets.WOO_RELEASE_SLACK_CHANNEL }}" --reply-ts ${{ needs.notify-slack.outputs.ts }} --filename "woocommerce.${{ needs.code-freeze-prep.outputs.acceleratedVersion }}.zip"
|
||||
|
||||
github-upload-monthly:
|
||||
name: Create single-zipped GitHub asset (Monthly)
|
||||
runs-on: ubuntu-20.04
|
||||
needs: [ code-freeze-prep, build-monthly ]
|
||||
if: ${{ needs.code-freeze-prep.outputs.isTodayMonthlyFreeze == 'yes' }}
|
||||
steps:
|
||||
- id: download
|
||||
uses: actions/download-artifact@v3
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
name: double-zipped-woocommerce.${{ needs.code-freeze-prep.outputs.monthlyVersion }}-beta.1
|
||||
path: download
|
||||
|
||||
- name: Unzip the file (prevents double zip problem)
|
||||
run: unzip ${{ steps.download.outputs.download-path }}/woocommerce.zip -d zipfile
|
||||
|
||||
- name: Upload the zip file as an artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
name: woocommerce.${{ needs.code-freeze-prep.outputs.monthlyVersion }}-beta.1
|
||||
path: zipfile
|
||||
retention-days: 10
|
||||
|
||||
github-upload-accelerated:
|
||||
name: Create single-zipped GitHub asset (Accelerated)
|
||||
runs-on: ubuntu-20.04
|
||||
needs: [ code-freeze-prep, build-a ]
|
||||
if: ${{ needs.code-freeze-prep.outputs.isTodayAcceleratedFreeze == 'yes' }}
|
||||
steps:
|
||||
- id: download
|
||||
uses: actions/download-artifact@v3
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
name: double-zipped-woocommerce.${{ needs.code-freeze-prep.outputs.acceleratedVersion }}
|
||||
path: download
|
||||
|
||||
- name: Unzip the file (prevents double zip problem)
|
||||
run: unzip ${{ steps.download.outputs.download-path }}/woocommerce.zip -d zipfile
|
||||
|
||||
- name: Upload the zip file as an artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
name: woocommerce.${{ needs.code-freeze-prep.outputs.acceleratedVersion }}
|
||||
path: zipfile
|
||||
retention-days: 10
|
1331
pnpm-lock.yaml
1331
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -13,6 +13,7 @@
|
|||
"@octokit/graphql": "4.8.0",
|
||||
"@octokit/graphql-schema": "^14.1.0",
|
||||
"@octokit/types": "^9.2.0",
|
||||
"@slack/web-api": "^6.9.0",
|
||||
"@types/cli-table": "^0.3.1",
|
||||
"@types/uuid": "^9.0.1",
|
||||
"chalk": "^4.1.2",
|
||||
|
@ -24,13 +25,13 @@
|
|||
"graphql": "^16.6.0",
|
||||
"gray-matter": "^4.0.3",
|
||||
"js-yaml": "^4.1.0",
|
||||
"luxon": "^3.4.4",
|
||||
"octokit": "^2.0.14",
|
||||
"ora": "^5.4.1",
|
||||
"promptly": "^3.2.0",
|
||||
"semver": "^7.3.2",
|
||||
"simple-git": "^3.10.0",
|
||||
"uuid": "^9.0.0",
|
||||
"@slack/web-api": "^6.9.0"
|
||||
"uuid": "^9.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^27.4.1",
|
||||
|
|
|
@ -0,0 +1,159 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Command } from '@commander-js/extra-typings';
|
||||
import simpleGit from 'simple-git';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Logger } from '../../../core/logger';
|
||||
import {
|
||||
sparseCheckoutRepoShallow,
|
||||
checkoutRemoteBranch,
|
||||
} from '../../../core/git';
|
||||
import { createPullRequest } from '../../../core/github/repo';
|
||||
import { getEnvVar } from '../../../core/environment';
|
||||
import { Options } from './types';
|
||||
import { addHeader, createChangelog } from './lib/prep';
|
||||
|
||||
export const acceleratedPrepCommand = new Command( 'accelerated-prep' )
|
||||
.description( 'Prep for an accelerated release' )
|
||||
.argument( '<version>', 'Version to bump to use for changelog' )
|
||||
.argument( '<date>', 'Release date to use in changelog' )
|
||||
.option(
|
||||
'-o --owner <owner>',
|
||||
'Repository owner. Default: woocommerce',
|
||||
'woocommerce'
|
||||
)
|
||||
.option(
|
||||
'-n --name <name>',
|
||||
'Repository name. Default: woocommerce',
|
||||
'woocommerce'
|
||||
)
|
||||
.option(
|
||||
'-b --base <base>',
|
||||
'Base branch to create the PR against. Default: trunk',
|
||||
'trunk'
|
||||
)
|
||||
.option(
|
||||
'-d --dry-run',
|
||||
'Prepare the version bump and log a diff. Do not create a PR or push to branch',
|
||||
false
|
||||
)
|
||||
.option(
|
||||
'-c --commit-direct-to-base',
|
||||
'Commit directly to the base branch. Do not create a PR just push directly to base branch',
|
||||
false
|
||||
)
|
||||
.action( async ( version, date, options: Options ) => {
|
||||
const { owner, name, base, dryRun, commitDirectToBase } = options;
|
||||
|
||||
Logger.startTask(
|
||||
`Making a temporary clone of '${ owner }/${ name }'`
|
||||
);
|
||||
|
||||
const source = `github.com/${ owner }/${ name }`;
|
||||
const token = getEnvVar( 'GITHUB_TOKEN', true );
|
||||
const remote = `https://${ owner }:${ token }@${ source }`;
|
||||
const tmpRepoPath = await sparseCheckoutRepoShallow(
|
||||
remote,
|
||||
'woocommerce',
|
||||
[
|
||||
'plugins/woocommerce/includes/class-woocommerce.php',
|
||||
// All that's needed is the line above, but including these here for completeness.
|
||||
'plugins/woocommerce/composer.json',
|
||||
'plugins/woocommerce/package.json',
|
||||
'plugins/woocommerce/readme.txt',
|
||||
'plugins/woocommerce/woocommerce.php',
|
||||
]
|
||||
);
|
||||
Logger.endTask();
|
||||
|
||||
Logger.notice(
|
||||
`Temporary clone of '${ owner }/${ name }' created at ${ tmpRepoPath }`
|
||||
);
|
||||
|
||||
const git = simpleGit( {
|
||||
baseDir: tmpRepoPath,
|
||||
config: [ 'core.hooksPath=/dev/null' ],
|
||||
} );
|
||||
|
||||
const branch = `prep/${ base }-accelerated`;
|
||||
|
||||
try {
|
||||
if ( commitDirectToBase ) {
|
||||
if ( base === 'trunk' ) {
|
||||
Logger.error(
|
||||
`The --commit-direct-to-base option cannot be used with the trunk branch as a base. A pull request must be created instead.`
|
||||
);
|
||||
}
|
||||
Logger.notice( `Checking out ${ base }` );
|
||||
await checkoutRemoteBranch( tmpRepoPath, base );
|
||||
} else {
|
||||
const exists = await git.raw( 'ls-remote', 'origin', branch );
|
||||
|
||||
if ( ! dryRun && exists.trim().length > 0 ) {
|
||||
Logger.error(
|
||||
`Branch ${ branch } already exists. Run \`git push <remote> --delete ${ branch }\` and rerun this command.`
|
||||
);
|
||||
}
|
||||
|
||||
if ( base !== 'trunk' ) {
|
||||
// if the base is not trunk, we need to checkout the base branch first before creating a new branch.
|
||||
Logger.notice( `Checking out ${ base }` );
|
||||
await checkoutRemoteBranch( tmpRepoPath, base );
|
||||
}
|
||||
Logger.notice( `Creating new branch ${ branch }` );
|
||||
await git.checkoutBranch( branch, base );
|
||||
}
|
||||
|
||||
const workingBranch = commitDirectToBase ? base : branch;
|
||||
|
||||
Logger.notice(
|
||||
`Adding Woo header to main plugin file and creating changelog.txt on ${ workingBranch } branch`
|
||||
);
|
||||
addHeader( tmpRepoPath );
|
||||
createChangelog( tmpRepoPath, version, date );
|
||||
|
||||
if ( dryRun ) {
|
||||
const diff = await git.diffSummary();
|
||||
Logger.notice(
|
||||
`The prep has been completed in the following files:`
|
||||
);
|
||||
Logger.warn( diff.files.map( ( f ) => f.file ).join( '\n' ) );
|
||||
Logger.notice(
|
||||
'Dry run complete. No pull was request created nor was a commit made.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.notice( 'Adding and committing changes' );
|
||||
await git.add( '.' );
|
||||
await git.commit(
|
||||
`Add Woo header to main plugin file and create changelog in ${ base }`
|
||||
);
|
||||
|
||||
Logger.notice( `Pushing ${ workingBranch } branch to Github` );
|
||||
await git.push( 'origin', workingBranch );
|
||||
|
||||
if ( ! commitDirectToBase ) {
|
||||
Logger.startTask( 'Creating a pull request' );
|
||||
|
||||
const pullRequest = await createPullRequest( {
|
||||
owner,
|
||||
name,
|
||||
title: `Add Woo header to main plugin file and create changelog in ${ base }`,
|
||||
body: `This PR adds the Woo header to the main plugin file and creates a changelog.txt file in ${ base }.`,
|
||||
head: branch,
|
||||
base,
|
||||
} );
|
||||
Logger.notice(
|
||||
`Pull request created: ${ pullRequest.html_url }`
|
||||
);
|
||||
Logger.endTask();
|
||||
}
|
||||
} catch ( error ) {
|
||||
Logger.error( error );
|
||||
}
|
||||
} );
|
|
@ -0,0 +1,57 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { readFile, writeFile } from 'fs/promises';
|
||||
import { join } from 'path';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Logger } from '../../../../core/logger';
|
||||
|
||||
/**
|
||||
* Add Woo header to main plugin file.
|
||||
*
|
||||
* @param tmpRepoPath cloned repo path
|
||||
*/
|
||||
export const addHeader = async ( tmpRepoPath: string ): Promise< void > => {
|
||||
const filePath = join( tmpRepoPath, 'plugins/woocommerce/woocommerce.php' );
|
||||
try {
|
||||
const pluginFileContents = await readFile( filePath, 'utf8' );
|
||||
|
||||
const updatedPluginFileContents = pluginFileContents.replace(
|
||||
' * @package WooCommerce\n */',
|
||||
' *\n * Woo: 18734002369816:624a1b9ba2fe66bb06d84bcdd401c6a6\n *\n * @package WooCommerce\n */'
|
||||
);
|
||||
|
||||
await writeFile( filePath, updatedPluginFileContents );
|
||||
} catch ( e ) {
|
||||
Logger.error( e );
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create changelog file.
|
||||
*
|
||||
* @param tmpRepoPath cloned repo path
|
||||
* @param version version for the changelog file
|
||||
* @param date date of the release (Y-m-d)
|
||||
*/
|
||||
export const createChangelog = async (
|
||||
tmpRepoPath: string,
|
||||
version: string,
|
||||
date: string
|
||||
): Promise< void > => {
|
||||
const filePath = join( tmpRepoPath, 'plugins/woocommerce/changelog.txt' );
|
||||
try {
|
||||
const changelogContents = `*** WooCommerce ***
|
||||
|
||||
${ date } - Version ${ version }
|
||||
* Update - Deploy of WOoCommerce ${ version }
|
||||
`;
|
||||
|
||||
await writeFile( filePath, changelogContents );
|
||||
} catch ( e ) {
|
||||
Logger.error( e );
|
||||
}
|
||||
};
|
|
@ -0,0 +1,7 @@
|
|||
export type Options = {
|
||||
owner?: string;
|
||||
name?: string;
|
||||
base?: string;
|
||||
dryRun?: boolean;
|
||||
commitDirectToBase?: boolean;
|
||||
};
|
|
@ -28,6 +28,11 @@ export const changelogCommand = new Command( 'changelog' )
|
|||
'-d --dev-repo-path <devRepoPath>',
|
||||
'Path to existing repo. Use this option to avoid cloning a fresh repo for development purposes. Note that using this option assumes dependencies are already installed.'
|
||||
)
|
||||
.option(
|
||||
'-c --commit-direct-to-base',
|
||||
'Commit directly to the base branch. Do not create a PR just push directly to base branch',
|
||||
false
|
||||
)
|
||||
.option(
|
||||
'-o, --override <override>',
|
||||
"Time Override: The time to use in checking whether the action should run (default: 'now').",
|
||||
|
@ -39,10 +44,15 @@ export const changelogCommand = new Command( 'changelog' )
|
|||
Logger.startTask(
|
||||
`Making a temporary clone of '${ owner }/${ name }'`
|
||||
);
|
||||
|
||||
const cloneOptions = {
|
||||
owner: owner ? owner : 'woocommerce',
|
||||
name: name ? name : 'woocommerce',
|
||||
};
|
||||
// Use a supplied path, otherwise do a full clone of the repo, including history so that changelogs can be created with links to PRs.
|
||||
const tmpRepoPath = devRepoPath
|
||||
? devRepoPath
|
||||
: await cloneAuthenticatedRepo( options, false );
|
||||
: await cloneAuthenticatedRepo( cloneOptions, false );
|
||||
|
||||
Logger.endTask();
|
||||
|
||||
|
|
|
@ -13,7 +13,10 @@ import { Logger } from '../../../../core/logger';
|
|||
import { checkoutRemoteBranch } from '../../../../core/git';
|
||||
import { createPullRequest } from '../../../../core/github/repo';
|
||||
import { Options } from '../types';
|
||||
import { getToday } from '../../verify-day/utils';
|
||||
import {
|
||||
getToday,
|
||||
DAYS_BETWEEN_CODE_FREEZE_AND_RELEASE,
|
||||
} from '../../get-version/lib';
|
||||
|
||||
/**
|
||||
* Perform changelog adjustments after Jetpack Changelogger has run.
|
||||
|
@ -27,9 +30,10 @@ const updateReleaseChangelogs = async (
|
|||
) => {
|
||||
const today = getToday( override );
|
||||
|
||||
// The release date is 22 days after the code freeze.
|
||||
const releaseTime = new Date( today.getTime() + 22 * 24 * 60 * 60 * 1000 );
|
||||
const releaseDate = releaseTime.toISOString().split( 'T' )[ 0 ];
|
||||
const releaseTime = today.plus( {
|
||||
days: DAYS_BETWEEN_CODE_FREEZE_AND_RELEASE,
|
||||
} );
|
||||
const releaseDate = releaseTime.toISODate();
|
||||
|
||||
const readmeFile = path.join(
|
||||
tmpRepoPath,
|
||||
|
@ -79,7 +83,7 @@ export const updateReleaseBranchChangelogs = async (
|
|||
tmpRepoPath: string,
|
||||
releaseBranch: string
|
||||
): Promise< { deletionCommitHash: string; prNumber: number } > => {
|
||||
const { owner, name, version } = options;
|
||||
const { owner, name, version, commitDirectToBase } = options;
|
||||
try {
|
||||
// Do a full checkout so that we can find the correct PR numbers for changelog entries.
|
||||
await checkoutRemoteBranch( tmpRepoPath, releaseBranch, false );
|
||||
|
@ -100,10 +104,12 @@ export const updateReleaseBranchChangelogs = async (
|
|||
const branch = `update/${ version }-changelog`;
|
||||
|
||||
try {
|
||||
await git.checkout( {
|
||||
'-b': null,
|
||||
[ branch ]: null,
|
||||
} );
|
||||
if ( ! commitDirectToBase ) {
|
||||
await git.checkout( {
|
||||
'-b': null,
|
||||
[ branch ]: null,
|
||||
} );
|
||||
}
|
||||
|
||||
Logger.notice( `Running the changelog script in ${ tmpRepoPath }` );
|
||||
execSync(
|
||||
|
@ -131,9 +137,18 @@ export const updateReleaseBranchChangelogs = async (
|
|||
await git.commit(
|
||||
`Update the readme files for the ${ version } release`
|
||||
);
|
||||
await git.push( 'origin', branch );
|
||||
await git.push( 'origin', commitDirectToBase ? releaseBranch : branch );
|
||||
await git.checkout( '.' );
|
||||
|
||||
if ( commitDirectToBase ) {
|
||||
Logger.notice(
|
||||
`Changelog update was committed directly to ${ releaseBranch }`
|
||||
);
|
||||
return {
|
||||
deletionCommitHash: deletionCommitHash.trim(),
|
||||
prNumber: -1,
|
||||
};
|
||||
}
|
||||
Logger.notice( `Creating PR for ${ branch }` );
|
||||
const pullRequest = await createPullRequest( {
|
||||
owner,
|
||||
|
@ -194,7 +209,9 @@ export const updateTrunkChangelog = async (
|
|||
owner,
|
||||
name,
|
||||
title: `Release: Remove ${ version } change files`,
|
||||
body: `This pull request was automatically generated during the code freeze to remove the changefiles from ${ version } that are compiled into the \`${ releaseBranch }\` branch via #${ prNumber }`,
|
||||
body: `This pull request was automatically generated during the code freeze to remove the changefiles from ${ version } that are compiled into the \`${ releaseBranch }\` ${
|
||||
prNumber > 0 ? `branch via #${ prNumber }` : ''
|
||||
}`,
|
||||
head: branch,
|
||||
base: 'trunk',
|
||||
} );
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
export type Options = {
|
||||
owner: string;
|
||||
name: string;
|
||||
owner?: string;
|
||||
name?: string;
|
||||
version: string;
|
||||
devRepoPath?: string;
|
||||
override: string;
|
||||
commitDirectToBase?: boolean;
|
||||
override?: string;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,168 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Command } from '@commander-js/extra-typings';
|
||||
import { DateTime } from 'luxon';
|
||||
import { setOutput } from '@actions/core';
|
||||
import chalk from 'chalk';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Logger } from '../../../core/logger';
|
||||
import { isGithubCI } from '../../../core/environment';
|
||||
import {
|
||||
getToday,
|
||||
getMonthlyCycle,
|
||||
getAcceleratedCycle,
|
||||
getVersionsBetween,
|
||||
} from './lib/index';
|
||||
|
||||
const getRange = ( override, between ) => {
|
||||
if ( isGithubCI() ) {
|
||||
Logger.error(
|
||||
'-b, --between option is not compatible with GitHub CI Output.'
|
||||
);
|
||||
process.exit( 1 );
|
||||
}
|
||||
|
||||
const today = getToday( override );
|
||||
const end = getToday( between );
|
||||
const versions = getVersionsBetween( today, end );
|
||||
|
||||
Logger.notice(
|
||||
chalk.greenBright.bold(
|
||||
`Releases Between ${ today.toFormat( 'DDDD' ) } and ${ end.toFormat(
|
||||
'DDDD'
|
||||
) }\n`
|
||||
)
|
||||
);
|
||||
|
||||
Logger.table(
|
||||
[ 'Version', 'Development Begins', 'Freeze', 'Release' ],
|
||||
versions.map( ( v ) =>
|
||||
Object.values( v ).map( ( d: DateTime | string ) =>
|
||||
typeof d.toFormat === 'function'
|
||||
? d.toFormat( 'EEE, MMM dd, yyyy' )
|
||||
: d
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
process.exit( 0 );
|
||||
};
|
||||
|
||||
export const getVersionCommand = new Command( 'get-version' )
|
||||
.description( 'Get the release calendar for a given date' )
|
||||
.option(
|
||||
'-o, --override <override>',
|
||||
"Time Override: The time to use in checking whether the action should run (default: 'now').",
|
||||
'now'
|
||||
)
|
||||
.option(
|
||||
'-b, --between <between>',
|
||||
'When provided, instead of showing a single day, will show a releases in the range of <override> to <end>.'
|
||||
)
|
||||
.action( ( { override, between } ) => {
|
||||
if ( between ) {
|
||||
return getRange( override, between );
|
||||
}
|
||||
|
||||
const today = getToday( override );
|
||||
const acceleratedRelease = getAcceleratedCycle( today, false );
|
||||
const acceleratedDevelopment = getAcceleratedCycle( today );
|
||||
const monthlyRelease = getMonthlyCycle( today, false );
|
||||
const monthlyDevelopment = getMonthlyCycle( today );
|
||||
|
||||
// Generate human-friendly output.
|
||||
Logger.notice(
|
||||
chalk.greenBright.bold(
|
||||
`Release Calendar for ${ today.toFormat( 'DDDD' ) }\n`
|
||||
)
|
||||
);
|
||||
const table = [];
|
||||
// We're not in a release cycle on Wednesday.
|
||||
if ( today.get( 'weekday' ) !== 3 ) {
|
||||
table.push( [
|
||||
`${ chalk.red( 'Accelerated Release Cycle' ) }`,
|
||||
acceleratedRelease.version,
|
||||
acceleratedRelease.begin.toFormat( 'EEE, MMM dd, yyyy' ),
|
||||
acceleratedRelease.freeze.toFormat( 'EEE, MMM dd, yyyy' ),
|
||||
acceleratedRelease.release.toFormat( 'EEE, MMM dd, yyyy' ),
|
||||
] );
|
||||
}
|
||||
table.push( [
|
||||
`${ chalk.red( 'Accelerated Development Cycle' ) }`,
|
||||
acceleratedDevelopment.version,
|
||||
acceleratedDevelopment.begin.toFormat( 'EEE, MMM dd, yyyy' ),
|
||||
acceleratedDevelopment.freeze.toFormat( 'EEE, MMM dd, yyyy' ),
|
||||
acceleratedDevelopment.release.toFormat( 'EEE, MMM dd, yyyy' ),
|
||||
] );
|
||||
// We're only in a release cycle if it is after the freeze day.
|
||||
if ( today > monthlyRelease.freeze ) {
|
||||
table.push( [
|
||||
`${ chalk.red( 'Monthly Release Cycle' ) }`,
|
||||
monthlyRelease.version,
|
||||
monthlyRelease.begin.toFormat( 'EEE, MMM dd, yyyy' ),
|
||||
monthlyRelease.freeze.toFormat( 'EEE, MMM dd, yyyy' ),
|
||||
monthlyRelease.release.toFormat( 'EEE, MMM dd, yyyy' ),
|
||||
] );
|
||||
}
|
||||
table.push( [
|
||||
`${ chalk.red( 'Monthly Development Cycle' ) }`,
|
||||
monthlyDevelopment.version,
|
||||
monthlyDevelopment.begin.toFormat( 'EEE, MMM dd, yyyy' ),
|
||||
monthlyDevelopment.freeze.toFormat( 'EEE, MMM dd, yyyy' ),
|
||||
monthlyDevelopment.release.toFormat( 'EEE, MMM dd, yyyy' ),
|
||||
] );
|
||||
Logger.table(
|
||||
[ '', 'Version', 'Development Begins', 'Freeze', 'Release' ],
|
||||
table
|
||||
);
|
||||
|
||||
if ( isGithubCI() ) {
|
||||
// For the machines.
|
||||
const isTodayAcceleratedFreeze = today.get( 'weekday' ) === 4;
|
||||
const isTodayMonthlyFreeze = +today === +monthlyDevelopment.begin;
|
||||
const monthlyVersionXY = monthlyRelease.version.substr(
|
||||
0,
|
||||
monthlyRelease.version.lastIndexOf( '.' )
|
||||
);
|
||||
setOutput(
|
||||
'isTodayAcceleratedFreeze',
|
||||
isTodayAcceleratedFreeze ? 'yes' : 'no'
|
||||
);
|
||||
setOutput(
|
||||
'isTodayMonthlyFreeze',
|
||||
isTodayMonthlyFreeze ? 'yes' : 'no'
|
||||
);
|
||||
setOutput( 'acceleratedVersion', acceleratedRelease.version );
|
||||
setOutput( 'monthlyVersion', monthlyRelease.version );
|
||||
setOutput( 'monthlyVersionXY', monthlyVersionXY );
|
||||
setOutput(
|
||||
'releasesFrozenToday',
|
||||
JSON.stringify(
|
||||
Object.values( {
|
||||
...( isTodayMonthlyFreeze && {
|
||||
monthlyVersion: `${ monthlyRelease.version } (Monthly)`,
|
||||
} ),
|
||||
...( isTodayAcceleratedFreeze && {
|
||||
aVersion: `${ acceleratedRelease.version } (AF)`,
|
||||
} ),
|
||||
} )
|
||||
)
|
||||
);
|
||||
setOutput(
|
||||
'acceleratedBranch',
|
||||
`release/${ acceleratedRelease.version }`
|
||||
);
|
||||
setOutput( 'monthlyBranch', `release/${ monthlyVersionXY }` );
|
||||
setOutput( 'monthlyMilestone', monthlyDevelopment.version );
|
||||
setOutput(
|
||||
'acceleratedReleaseDate',
|
||||
acceleratedDevelopment.release.toISODate()
|
||||
);
|
||||
}
|
||||
|
||||
process.exit( 0 );
|
||||
} );
|
|
@ -0,0 +1,150 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
export const DAYS_BETWEEN_CODE_FREEZE_AND_RELEASE = 19;
|
||||
|
||||
/**
|
||||
* Get a DateTime object of now or the override time when specified. DateTime is normalized to start of day.
|
||||
*
|
||||
* @param {string} now The time to use in checking if today is the day of the code freeze. Default to now. Supports ISO formatted dates or 'now'.
|
||||
*
|
||||
* @return {DateTime} The DateTime object of now or the override time when specified.
|
||||
*/
|
||||
export const getToday = ( now = 'now' ): DateTime => {
|
||||
const today =
|
||||
now === 'now'
|
||||
? DateTime.now().setZone( 'utc' )
|
||||
: DateTime.fromISO( now, { zone: 'utc' } );
|
||||
if ( isNaN( today.toMillis() ) ) {
|
||||
throw new Error(
|
||||
'Invalid date: Check the override parameter (-o, --override) is a correct ISO formatted string or "now"'
|
||||
);
|
||||
}
|
||||
return today.set( { hour: 0, minute: 0, second: 0, millisecond: 0 } );
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the second Tuesday of the month, given a DateTime.
|
||||
*
|
||||
* @param {DateTime} when A DateTime object.
|
||||
*
|
||||
* @return {DateTime} The second Tuesday of the month contained in the input.
|
||||
*/
|
||||
export const getSecondTuesday = ( when: DateTime ): DateTime => {
|
||||
const year = when.get( 'year' );
|
||||
const month = when.get( 'month' );
|
||||
const firstDayOfMonth = DateTime.utc( year, month, 1 );
|
||||
const dayOfWeek = firstDayOfMonth.get( 'weekday' );
|
||||
const secondTuesday = dayOfWeek <= 2 ? 10 - dayOfWeek : 17 - dayOfWeek;
|
||||
return DateTime.utc( year, month, secondTuesday );
|
||||
};
|
||||
|
||||
export const getMonthlyCycle = ( when: DateTime, development = true ) => {
|
||||
// July 12, 2023 is the start-point for 8.0.0, all versions follow that starting point.
|
||||
const startTime = DateTime.fromObject(
|
||||
{
|
||||
year: 2023,
|
||||
month: 7,
|
||||
day: 12,
|
||||
hour: 0,
|
||||
minute: 0,
|
||||
},
|
||||
{ zone: 'UTC' }
|
||||
);
|
||||
const currentMonthRelease = getSecondTuesday( when );
|
||||
const nextMonthRelease = getSecondTuesday(
|
||||
currentMonthRelease.plus( { months: 1 } )
|
||||
);
|
||||
const release =
|
||||
when <= currentMonthRelease ? currentMonthRelease : nextMonthRelease;
|
||||
const previousRelease = getSecondTuesday(
|
||||
release.minus( { days: DAYS_BETWEEN_CODE_FREEZE_AND_RELEASE + 2 } )
|
||||
);
|
||||
const nextRelease = getSecondTuesday( release.plus( { months: 1 } ) );
|
||||
const freeze = release.minus( {
|
||||
days: DAYS_BETWEEN_CODE_FREEZE_AND_RELEASE + 1,
|
||||
} );
|
||||
const monthNumber =
|
||||
( previousRelease.get( 'year' ) - startTime.get( 'year' ) ) * 12 +
|
||||
previousRelease.get( 'month' ) -
|
||||
startTime.get( 'month' );
|
||||
const version = ( ( 80 + monthNumber ) / 10 ).toFixed( 1 ) + '.0';
|
||||
|
||||
if ( development ) {
|
||||
if ( when > freeze ) {
|
||||
return getMonthlyCycle( nextRelease, false );
|
||||
}
|
||||
}
|
||||
|
||||
const begin = previousRelease.minus( {
|
||||
days: DAYS_BETWEEN_CODE_FREEZE_AND_RELEASE,
|
||||
} );
|
||||
|
||||
return {
|
||||
version,
|
||||
begin,
|
||||
freeze,
|
||||
release,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Get version and all dates / related to an accelerated cycle.
|
||||
*
|
||||
* @param {DateTime} when A DateTime object.
|
||||
* @param {boolean} development When true, the active development cycle will be returned, otherwise the active release cycle.
|
||||
* @return {Object} An object containing version and dates for a release.
|
||||
*/
|
||||
export const getAcceleratedCycle = ( when: DateTime, development = true ) => {
|
||||
if ( ! development ) {
|
||||
when = when.minus( { week: 1 } );
|
||||
}
|
||||
const dayOfWeek = when.get( 'weekday' );
|
||||
const daysTilWednesday = dayOfWeek < 4 ? 3 - dayOfWeek : 10 - dayOfWeek;
|
||||
const freeze = when.plus( { days: daysTilWednesday } );
|
||||
const lastAccelerated = freeze.minus( { days: 1 } );
|
||||
const release = freeze.plus( { days: 6 } );
|
||||
const begin = freeze.minus( { days: 6 } );
|
||||
const currentMonthRelease = getSecondTuesday( lastAccelerated );
|
||||
const nextMonthRelease = getSecondTuesday(
|
||||
currentMonthRelease.plus( { months: 1 } )
|
||||
);
|
||||
const monthlyRelease =
|
||||
freeze <= currentMonthRelease ? currentMonthRelease : nextMonthRelease;
|
||||
const monthlyCycle = getMonthlyCycle( monthlyRelease, false );
|
||||
const previousMonthlyRelease = getSecondTuesday(
|
||||
monthlyRelease.minus( { days: 28 } )
|
||||
);
|
||||
|
||||
const aVersion =
|
||||
10 *
|
||||
( lastAccelerated.diff( previousMonthlyRelease, 'weeks' ).toObject()
|
||||
.weeks +
|
||||
1 );
|
||||
const version = `${ monthlyCycle.version }.${ aVersion }`;
|
||||
|
||||
return {
|
||||
version,
|
||||
begin,
|
||||
freeze,
|
||||
release,
|
||||
};
|
||||
};
|
||||
|
||||
export const getVersionsBetween = ( start: DateTime, end: DateTime ) => {
|
||||
if ( start > end ) {
|
||||
return getVersionsBetween( end, start );
|
||||
}
|
||||
const versions = {};
|
||||
for ( let i = start; i < end; i = i.plus( { days: 28 } ) ) {
|
||||
const monthly = getMonthlyCycle( i, false );
|
||||
versions[ monthly.version ] = monthly;
|
||||
}
|
||||
for ( let i = start; i < end; i = i.plus( { days: 7 } ) ) {
|
||||
const accelerated = getAcceleratedCycle( i, false );
|
||||
versions[ accelerated.version ] = accelerated;
|
||||
}
|
||||
return Object.values( versions );
|
||||
};
|
|
@ -6,18 +6,20 @@ import { Command } from '@commander-js/extra-typings';
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { verifyDayCommand } from './verify-day';
|
||||
import { getVersionCommand } from './get-version';
|
||||
import { milestoneCommand } from './milestone';
|
||||
import { branchCommand } from './branch';
|
||||
import { versionBumpCommand } from './version-bump';
|
||||
import { changelogCommand } from './changelog';
|
||||
import { acceleratedPrepCommand } from './accelerated-prep';
|
||||
|
||||
const program = new Command( 'code-freeze' )
|
||||
.description( 'Code freeze utilities' )
|
||||
.addCommand( verifyDayCommand )
|
||||
.addCommand( getVersionCommand )
|
||||
.addCommand( milestoneCommand )
|
||||
.addCommand( branchCommand )
|
||||
.addCommand( versionBumpCommand )
|
||||
.addCommand( changelogCommand );
|
||||
.addCommand( changelogCommand )
|
||||
.addCommand( acceleratedPrepCommand );
|
||||
|
||||
export default program;
|
||||
|
|
|
@ -9,10 +9,8 @@ import ora from 'ora';
|
|||
*/
|
||||
import { getLatestGithubReleaseVersion } from '../../../core/github/repo';
|
||||
import { octokitWithAuth } from '../../../core/github/api';
|
||||
import { setGithubMilestoneOutputs } from './utils';
|
||||
import { WPIncrement } from '../../../core/version';
|
||||
import { Logger } from '../../../core/logger';
|
||||
import { isGithubCI } from '../../../core/environment';
|
||||
|
||||
export const milestoneCommand = new Command( 'milestone' )
|
||||
.description( 'Create a milestone' )
|
||||
|
@ -33,14 +31,6 @@ export const milestoneCommand = new Command( 'milestone' )
|
|||
)
|
||||
.action( async ( options ) => {
|
||||
const { owner, name, dryRun, milestone } = options;
|
||||
const isGithub = isGithubCI();
|
||||
|
||||
if ( milestone && isGithub ) {
|
||||
Logger.error(
|
||||
"You can't manually supply a milestone using Github mode. Please use the CLI locally to add a milestone."
|
||||
);
|
||||
process.exit( 1 );
|
||||
}
|
||||
|
||||
let nextMilestone;
|
||||
let nextReleaseVersion;
|
||||
|
@ -105,12 +95,6 @@ export const milestoneCommand = new Command( 'milestone' )
|
|||
Logger.notice(
|
||||
`Milestone ${ nextMilestone } already exists in ${ owner }/${ name }`
|
||||
);
|
||||
if ( isGithub ) {
|
||||
setGithubMilestoneOutputs(
|
||||
nextReleaseVersion,
|
||||
nextMilestone
|
||||
);
|
||||
}
|
||||
process.exit( 0 );
|
||||
} else {
|
||||
milestoneSpinner.fail();
|
||||
|
@ -123,9 +107,7 @@ export const milestoneCommand = new Command( 'milestone' )
|
|||
}
|
||||
|
||||
milestoneSpinner.succeed();
|
||||
if ( isGithub ) {
|
||||
setGithubMilestoneOutputs( nextReleaseVersion, nextMilestone );
|
||||
}
|
||||
|
||||
Logger.notice(
|
||||
`Successfully created milestone ${ nextMilestone } in ${ owner }/${ name }`
|
||||
);
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { setOutput } from '@actions/core';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { getMajorMinor } from '../../../core/version';
|
||||
|
||||
/**
|
||||
* Set Github outputs.
|
||||
*
|
||||
* @param {string} nextReleaseVersion Next release version
|
||||
* @param {string} nextMilestone Next milestone
|
||||
*/
|
||||
export const setGithubMilestoneOutputs = (
|
||||
nextReleaseVersion: string,
|
||||
nextMilestone: string
|
||||
): void => {
|
||||
setOutput( 'nextReleaseVersion', getMajorMinor( nextReleaseVersion ) );
|
||||
setOutput( 'nextDevelopmentVersion', getMajorMinor( nextMilestone ) );
|
||||
};
|
|
@ -1,48 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Command } from '@commander-js/extra-typings';
|
||||
import { setOutput } from '@actions/core';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import {
|
||||
isTodayCodeFreezeDay,
|
||||
DAYS_BETWEEN_CODE_FREEZE_AND_RELEASE,
|
||||
getToday,
|
||||
getFutureDate,
|
||||
} from './utils';
|
||||
import { Logger } from '../../../core/logger';
|
||||
import { isGithubCI } from '../../../core/environment';
|
||||
|
||||
export const verifyDayCommand = new Command( 'verify-day' )
|
||||
.description( 'Verify if today is the code freeze day' )
|
||||
.option(
|
||||
'-o, --override <override>',
|
||||
"Time Override: The time to use in checking whether the action should run (default: 'now').",
|
||||
'now'
|
||||
)
|
||||
.action( ( { override } ) => {
|
||||
const today = getToday( override );
|
||||
const futureDate = getFutureDate( today );
|
||||
Logger.warn( "Today's timestamp UTC is: " + today.toUTCString() );
|
||||
Logger.warn(
|
||||
`Checking to see if ${ DAYS_BETWEEN_CODE_FREEZE_AND_RELEASE } days from today is the second Tuesday of the month.`
|
||||
);
|
||||
const isCodeFreezeDay = isTodayCodeFreezeDay( override );
|
||||
Logger.notice(
|
||||
`${ futureDate.toUTCString() } ${
|
||||
isCodeFreezeDay ? 'is' : 'is not'
|
||||
} release day.`
|
||||
);
|
||||
Logger.notice(
|
||||
`Today is ${ isCodeFreezeDay ? 'indeed' : 'not' } code freeze day.`
|
||||
);
|
||||
|
||||
if ( isGithubCI() ) {
|
||||
setOutput( 'freeze', isCodeFreezeDay.toString() );
|
||||
}
|
||||
|
||||
process.exit( 0 );
|
||||
} );
|
|
@ -1,36 +0,0 @@
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { isTodayCodeFreezeDay } from '../index';
|
||||
|
||||
describe( 'isTodayCodeFreezeDay', () => {
|
||||
it( 'should return false when given a day not 22 days before release', () => {
|
||||
const JUNE_5_2023 = '2023-06-05T00:00:00.000Z';
|
||||
const JUNE_12_2023 = '2023-06-12T00:00:00.000Z';
|
||||
const JUNE_26_2023 = '2023-06-26T00:00:00.000Z';
|
||||
const AUG_10_2023 = '2023-08-10T00:00:00.000Z';
|
||||
const AUG_17_2023 = '2023-08-17T00:00:00.000Z';
|
||||
const AUG_24_2023 = '2023-08-24T00:00:00.000Z';
|
||||
|
||||
expect( isTodayCodeFreezeDay( JUNE_5_2023 ) ).toBeFalsy();
|
||||
expect( isTodayCodeFreezeDay( JUNE_12_2023 ) ).toBeFalsy();
|
||||
expect( isTodayCodeFreezeDay( JUNE_26_2023 ) ).toBeFalsy();
|
||||
expect( isTodayCodeFreezeDay( AUG_10_2023 ) ).toBeFalsy();
|
||||
expect( isTodayCodeFreezeDay( AUG_17_2023 ) ).toBeFalsy();
|
||||
expect( isTodayCodeFreezeDay( AUG_24_2023 ) ).toBeFalsy();
|
||||
} );
|
||||
|
||||
it( 'should return true when given a day 22 days before release', () => {
|
||||
const JUNE_19_2023 = '2023-06-19T00:00:00.000Z';
|
||||
const JULY_17_2023 = '2023-07-17T00:00:00.000Z';
|
||||
const AUGUST_21_2023 = '2023-08-21T00:00:00.000Z';
|
||||
|
||||
expect( isTodayCodeFreezeDay( JUNE_19_2023 ) ).toBeTruthy();
|
||||
expect( isTodayCodeFreezeDay( JULY_17_2023 ) ).toBeTruthy();
|
||||
expect( isTodayCodeFreezeDay( AUGUST_21_2023 ) ).toBeTruthy();
|
||||
} );
|
||||
|
||||
it( 'should error out when passed an invalid date', () => {
|
||||
expect( () => isTodayCodeFreezeDay( 'invalid date' ) ).toThrow();
|
||||
} );
|
||||
} );
|
|
@ -1,46 +0,0 @@
|
|||
const MILLIS_IN_A_DAY = 24 * 60 * 60 * 1000;
|
||||
export const DAYS_BETWEEN_CODE_FREEZE_AND_RELEASE = 22;
|
||||
|
||||
/**
|
||||
* Get a Date object of now or the override time when specified.
|
||||
*
|
||||
* @param {string} now The time to use in checking if today is the day of the code freeze. Default to now.
|
||||
* @return {Date} The Date object of now or the override time when specified.
|
||||
*/
|
||||
export const getToday = ( now = 'now' ): Date => {
|
||||
const today = now === 'now' ? new Date() : new Date( now );
|
||||
if ( isNaN( today.getTime() ) ) {
|
||||
throw new Error(
|
||||
'Invalid date: Check the override parameter (-o, --override) is a correct Date string'
|
||||
);
|
||||
}
|
||||
return today;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a future date from today to see if its the release day.
|
||||
*
|
||||
* @param {string} today The time to use in checking if today is the day of the code freeze. Default to now.
|
||||
* @return {Date} The Date object of the future date.
|
||||
*/
|
||||
export const getFutureDate = ( today: Date ) => {
|
||||
return new Date(
|
||||
today.getTime() + DAYS_BETWEEN_CODE_FREEZE_AND_RELEASE * MILLIS_IN_A_DAY
|
||||
);
|
||||
};
|
||||
/**
|
||||
* Determines if today is the day of the code freeze.
|
||||
*
|
||||
* @param {string} now The time to use in checking if today is the day of the code freeze. Default to now.
|
||||
* @return {boolean} true if today is the day of the code freeze.
|
||||
*/
|
||||
export const isTodayCodeFreezeDay = ( now: string ) => {
|
||||
const today = getToday( now );
|
||||
const futureDate = getFutureDate( today );
|
||||
const month = futureDate.getUTCMonth();
|
||||
const year = futureDate.getUTCFullYear();
|
||||
const firstDayOfMonth = new Date( Date.UTC( year, month, 1 ) );
|
||||
const dayOfWeek = firstDayOfMonth.getUTCDay();
|
||||
const secondTuesday = dayOfWeek <= 2 ? 10 - dayOfWeek : 17 - dayOfWeek;
|
||||
return futureDate.getUTCDate() === secondTuesday;
|
||||
};
|
|
@ -4,10 +4,6 @@
|
|||
import { Command } from '@commander-js/extra-typings';
|
||||
import { ErrorCode, WebClient } from '@slack/web-api';
|
||||
import { basename } from 'path';
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { existsSync } from 'fs';
|
||||
|
||||
/**
|
||||
|
@ -31,53 +27,72 @@ export const slackFileCommand = new Command( 'file' )
|
|||
'--dont-fail',
|
||||
'Do not fail the command if a message fails to send to any channel.'
|
||||
)
|
||||
.action( async ( token, text, filePath, channels, { dontFail } ) => {
|
||||
Logger.startTask(
|
||||
`Attempting to send message to Slack for channels: ${ channels.join(
|
||||
','
|
||||
) }`
|
||||
);
|
||||
|
||||
const shouldFail = ! dontFail;
|
||||
|
||||
if ( filePath && ! existsSync( filePath ) ) {
|
||||
Logger.error(
|
||||
`Unable to open file with path: ${ filePath }`,
|
||||
shouldFail
|
||||
.option(
|
||||
'--reply-ts <replyTs>',
|
||||
'Reply to the message with the corresponding ts'
|
||||
)
|
||||
.option(
|
||||
'--filename <filename>',
|
||||
'If provided, the filename that will be used for the file on Slack.'
|
||||
)
|
||||
.action(
|
||||
async (
|
||||
token,
|
||||
text,
|
||||
filePath,
|
||||
channels,
|
||||
{ dontFail, replyTs, filename }
|
||||
) => {
|
||||
Logger.startTask(
|
||||
`Attempting to send message to Slack for channels: ${ channels.join(
|
||||
','
|
||||
) }`
|
||||
);
|
||||
}
|
||||
|
||||
const client = new WebClient( token );
|
||||
const shouldFail = ! dontFail;
|
||||
|
||||
for ( const channel of channels ) {
|
||||
try {
|
||||
await client.files.uploadV2( {
|
||||
file: filePath,
|
||||
filename: basename( filePath ),
|
||||
channel_id: channel,
|
||||
initial_comment: text.replace( /\\n/g, '\n' ),
|
||||
request_file_info: false,
|
||||
} );
|
||||
|
||||
Logger.notice(
|
||||
`Successfully uploaded ${ filePath } to channel: ${ channel }`
|
||||
if ( filePath && ! existsSync( filePath ) ) {
|
||||
Logger.error(
|
||||
`Unable to open file with path: ${ filePath }`,
|
||||
shouldFail
|
||||
);
|
||||
} catch ( e ) {
|
||||
if (
|
||||
'code' in e &&
|
||||
e.code === ErrorCode.PlatformError &&
|
||||
'message' in e &&
|
||||
e.message.includes( 'missing_scope' )
|
||||
) {
|
||||
Logger.error(
|
||||
`The provided token does not have the required scopes, please add files:write and chat:write to the token.`,
|
||||
shouldFail
|
||||
}
|
||||
|
||||
const client = new WebClient( token );
|
||||
|
||||
for ( const channel of channels ) {
|
||||
try {
|
||||
const requestOptions = {
|
||||
file: filePath,
|
||||
filename: filename ? filename : basename( filePath ),
|
||||
channel_id: channel,
|
||||
initial_comment: text.replace( /\\n/g, '\n' ),
|
||||
request_file_info: false,
|
||||
thread_ts: replyTs ? replyTs : null,
|
||||
};
|
||||
|
||||
await client.files.uploadV2( requestOptions );
|
||||
|
||||
Logger.notice(
|
||||
`Successfully uploaded ${ filePath } to channel: ${ channel }`
|
||||
);
|
||||
} else {
|
||||
Logger.error( e, shouldFail );
|
||||
} catch ( e ) {
|
||||
if (
|
||||
'code' in e &&
|
||||
e.code === ErrorCode.PlatformError &&
|
||||
'message' in e &&
|
||||
e.message.includes( 'missing_scope' )
|
||||
) {
|
||||
Logger.error(
|
||||
`The provided token does not have the required scopes, please add files:write and chat:write to the token.`,
|
||||
shouldFail
|
||||
);
|
||||
} else {
|
||||
Logger.error( e, shouldFail );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Logger.endTask();
|
||||
} );
|
||||
Logger.endTask();
|
||||
}
|
||||
);
|
||||
|
|
|
@ -2,16 +2,20 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { Command } from '@commander-js/extra-typings';
|
||||
import { setOutput } from '@actions/core';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Logger } from '../../../core/logger';
|
||||
import { requestAsync } from '../../../core/util';
|
||||
import { isGithubCI } from '../../../core/environment';
|
||||
|
||||
type SlackResponse = {
|
||||
ok: boolean;
|
||||
error?: string;
|
||||
channel?: string;
|
||||
ts?: string;
|
||||
};
|
||||
|
||||
export const slackMessageCommand = new Command( 'message' )
|
||||
|
@ -72,6 +76,9 @@ export const slackMessageCommand = new Command( 'message' )
|
|||
Logger.notice(
|
||||
`Slack message sent successfully to channel: ${ channel }`
|
||||
);
|
||||
if ( isGithubCI() ) {
|
||||
setOutput( 'ts', response.ts );
|
||||
}
|
||||
}
|
||||
} catch ( e: unknown ) {
|
||||
Logger.error( e, shouldFail );
|
||||
|
|
Loading…
Reference in New Issue