Introduce a beta post command to the release post tool (#37142)

This commit is contained in:
Sam Seay 2023-03-17 08:37:53 +13:00 committed by GitHub
parent 0f733d1db2
commit 9f9fef7ed3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 487 additions and 11 deletions

View File

@ -2207,6 +2207,7 @@ importers:
commander: 9.4.0
dotenv: ^10.0.0
ejs: ^3.1.8
enquirer: ^2.3.6
express: ^4.18.1
form-data: ^4.0.0
lodash.shuffle: ^4.2.0
@ -2223,6 +2224,7 @@ importers:
commander: 9.4.0
dotenv: 10.0.0
ejs: 3.1.8
enquirer: 2.3.6
express: 4.18.1
form-data: 4.0.0
lodash.shuffle: 4.2.0
@ -18164,7 +18166,6 @@ packages:
/ansi-colors/4.1.1:
resolution: {integrity: sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==}
engines: {node: '>=6'}
dev: true
/ansi-escapes/3.2.0:
resolution: {integrity: sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==}
@ -22760,7 +22761,6 @@ packages:
engines: {node: '>=8.6'}
dependencies:
ansi-colors: 4.1.1
dev: true
/entities/1.1.2:
resolution: {integrity: sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==}
@ -30417,7 +30417,7 @@ packages:
log-update: 4.0.0
p-map: 4.0.0
rfdc: 1.3.0
rxjs: 7.5.5
rxjs: 7.8.0
through: 2.3.8
wrap-ansi: 7.0.0
dev: true
@ -36792,6 +36792,12 @@ packages:
tslib: 2.5.0
dev: true
/rxjs/7.8.0:
resolution: {integrity: sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==}
dependencies:
tslib: 2.5.0
dev: true
/safe-buffer/5.1.1:
resolution: {integrity: sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==}
dev: true

View File

@ -10,6 +10,7 @@ program
.name( 'release-post' )
.version( '0.0.1' )
.command( 'release', 'Generate release post', { isDefault: true } )
.command( 'beta', 'Generate draft beta release post' )
.command(
'contributors',
'Generate a list of contributors for a release post'

View File

@ -0,0 +1,225 @@
/**
* External dependencies
*/
import semver from 'semver';
import { writeFile } from 'fs/promises';
import { tmpdir } from 'os';
import { join } from 'path';
import { Logger } from 'cli-core/src/logger';
import { Command } from '@commander-js/extra-typings';
import dotenv from 'dotenv';
// @ts-expect-error - The enquirer types are incorrect.
// eslint-disable-next-line @woocommerce/dependency-group
import { Select } from 'enquirer';
/**
* Internal dependencies
*/
import { renderTemplate } from '../../lib/render-template';
import { getWordpressComAuthToken } from '../../lib/oauth-helper';
import { getEnvVar } from '../../lib/environment';
import { getMostRecentFinal } from '../../lib/github-api';
import {
getFirstTuesdayOfTheMonth,
getSecondTuesdayOfTheMonth,
} from '../../lib/dates';
import {
createWpComDraftPost,
searchForPostsByCategory,
} from '../../lib/draft-post';
const DEVELOPER_WOOCOMMERCE_SITE_ID = '96396764';
dotenv.config();
// Define the release post command
const program = new Command()
.command( 'beta' )
.description( 'CLI to automate generation of a draft beta release post.' )
.argument(
'<releaseVersion>',
'The version for this post in x.y.z-beta.n format. Ex: 7.1.0-beta.1'
)
.option(
'--releaseDate <date>',
'The date for the final release as mm-dd-yyyy, year inferred as current year, defaults to second tuesday of next month.',
getSecondTuesdayOfTheMonth(
new Date().getMonth() + 1
).toLocaleDateString( 'en-US', {
month: '2-digit',
day: '2-digit',
year: 'numeric',
} )
)
.option( '--outputOnly', 'Only output the post, do not publish it' )
.option(
'--tags <tags>',
'Comma separated list of tags to add to the post.',
'Releases,WooCommerce Core'
)
.option(
'--siteId <siteId>',
'For posting to a non-default site (for testing)'
)
.action( async ( releaseVersion, options ) => {
const {
outputOnly,
siteId = DEVELOPER_WOOCOMMERCE_SITE_ID,
tags,
releaseDate,
} = options;
const postTags = ( tags &&
tags.split( ',' ).map( ( tag ) => tag.trim() ) ) || [
'WooCommerce Core',
'Releases',
];
const finalReleaseDate = new Date( releaseDate );
const isOutputOnly = !! outputOnly;
const semverVersion = semver.parse( releaseVersion );
// This is supposed to be a beta post so throw if the version provided is not a beta version.
// Things we don't accept:
// * missing beta.x
// * any other kind of prerelease, e.g. rc
// * .x must be a number, so not: beta.1b or beta.1.1 but beta.1 is ok.
if (
! semverVersion ||
! semverVersion.prerelease.length ||
typeof semverVersion.prerelease[ 1 ] === 'string'
) {
throw new Error(
`Invalid current version: ${ releaseVersion }. Provide current version in x.y.z-beta.n format.`
);
} else {
const [ , prereleaseVersion ] = semverVersion.prerelease;
// Now infer the previous version, if the one you provide is beta.1 we'll need to find the last major release from
// Github releases. If what you provided is beta.2 we'll assume previous was beta.1
const previousVersion =
prereleaseVersion === 1
? ( await getMostRecentFinal() ).tag_name
: `${ semverVersion.major }.${ semverVersion.minor }.${
semverVersion.patch
}-beta.${ prereleaseVersion - 1 }`;
const semverPreviousVersion = semver.parse( previousVersion );
if ( ! semverPreviousVersion ) {
throw new Error(
`Could not parse previous version from: ${ previousVersion }`
);
}
const clientId = getEnvVar( 'WPCOM_OAUTH_CLIENT_ID', true );
const clientSecret = getEnvVar( 'WPCOM_OAUTH_CLIENT_SECRET', true );
const redirectUri =
getEnvVar( 'WPCOM_OAUTH_REDIRECT_URI' ) ||
'http://localhost:3000/oauth';
Logger.startTask(
'Getting auth token for WordPress.com (needed to find last beta post).'
);
const authToken = await getWordpressComAuthToken(
clientId,
clientSecret,
siteId,
redirectUri,
'posts'
);
Logger.endTask();
const versionSearch =
prereleaseVersion === 1
? `WooCommerce ${ semverPreviousVersion.major }.${ semverPreviousVersion.minor }.${ semverPreviousVersion.patch }`
: `WooCommerce ${ semverPreviousVersion.major }.${ semverPreviousVersion.minor } Beta ${ semverPreviousVersion.prerelease[ 1 ] }`;
Logger.startTask(
`Finding recent release posts with title: ${ versionSearch }`
);
const posts =
( await searchForPostsByCategory(
siteId,
versionSearch,
'WooCommerce Core',
authToken
) ) || [];
Logger.endTask();
const prompt = new Select( {
name: 'Previous post',
message: 'Choose the previous post to link to:',
choices: posts.length
? posts.map( ( p ) => p.title )
: [ 'No posts found - generate default link' ],
} );
const lastReleasePostTitle: string = await prompt.run();
const lastReleasePost = posts.find(
( p ) => p.title === lastReleasePostTitle
);
if ( ! lastReleasePost ) {
Logger.warn(
'Could not find previous release post, make sure to update the link in the post before publishing.'
);
}
if ( ! authToken && ! isOutputOnly ) {
throw new Error(
'Error getting auth token, check your env settings are correct.'
);
} else {
const html = await renderTemplate( 'beta-release.ejs', {
releaseDate,
betaNumber: prereleaseVersion,
version: semverVersion,
previousVersion: semverPreviousVersion,
prettyVersion: `${ semverVersion.major }.${ semverVersion.minor }.${ semverVersion.patch } Beta ${ prereleaseVersion }`,
prettyPreviousVersion: `${ semverPreviousVersion.major }.${
semverPreviousVersion.minor
}.${ semverPreviousVersion.patch }${
semverPreviousVersion.prerelease.length
? ' ' +
semverPreviousVersion.prerelease[ 0 ] +
' ' +
semverPreviousVersion.prerelease[ 1 ]
: ''
}`,
rcReleaseDate: getFirstTuesdayOfTheMonth(
finalReleaseDate.getMonth()
),
finalReleaseDate,
lastReleasePostUrl:
lastReleasePost?.URL ||
'https://developer.woocommerce.com/category/woocommerce-core-release-notes/',
} );
if ( isOutputOnly ) {
const tmpFile = join(
tmpdir(),
`beta-release-${ releaseVersion }.html`
);
await writeFile( tmpFile, html );
Logger.notice( `Output written to ${ tmpFile }` );
} else {
Logger.startTask( 'Publishing draft release post' );
await createWpComDraftPost(
siteId,
`WooCommerce ${ semverVersion.major }.${ semverVersion.minor } Beta ${ prereleaseVersion } Released`,
html,
postTags,
authToken
);
Logger.endTask();
}
}
}
} );
program.parse( process.argv );

View File

@ -150,7 +150,7 @@ const program = new Command()
let postContent;
if ( 'undefined' !== typeof options.editPostId ) {
if ( typeof options.editPostId !== 'undefined' ) {
try {
const prevPost = await fetchWpComPost(
siteId,
@ -197,7 +197,7 @@ const program = new Command()
};
const html =
'undefined' !== typeof options.editPostId
typeof options.editPostId !== 'undefined'
? editPostHTML( postContent, {
hooks: await renderTemplate(
'hooks.ejs',
@ -234,7 +234,7 @@ const program = new Command()
try {
const { URL } =
'undefined' !== typeof options.editPostId
typeof options.editPostId !== 'undefined'
? await editWpComPostContent(
siteId,
options.editPostId,

View File

@ -0,0 +1,22 @@
export const getFirstTuesdayOfTheMonth = ( month: number ): Date => {
// create a new Date object for the first day of the month
const firstDayOfMonth = new Date( new Date().getFullYear(), month, 1 );
// create a new Date object for the first Tuesday of the month
const firstTuesday = new Date( firstDayOfMonth );
firstTuesday.setDate( 1 + ( ( 2 - firstDayOfMonth.getDay() + 7 ) % 7 ) );
return firstTuesday;
};
export const getSecondTuesdayOfTheMonth = ( month: number ): Date => {
// create a new Date object for the first Tuesday of the month
const firstTuesday = getFirstTuesdayOfTheMonth( month );
// create a new Date object for the second Tuesday of the current month
const secondTuesday = new Date( firstTuesday );
secondTuesday.setDate( secondTuesday.getDate() + 7 );
return secondTuesday;
};

View File

@ -4,6 +4,14 @@
import fetch from 'node-fetch';
import { Logger } from 'cli-core/src/logger';
// Typing just the things we need from the WP.com Post object.
// (which is not the same as WP Post object or API Post object).
// See example response here: https://developer.wordpress.com/docs/api/1.1/get/sites/%24site/posts/ to add more props.
type WordpressComPost = {
title: string;
URL: string;
};
/**
* Fetch a post from WordPress.com
*
@ -41,6 +49,39 @@ export const fetchWpComPost = async (
}
};
export const searchForPostsByCategory = async (
siteId: string,
search: string,
category: string,
authToken: string
) => {
try {
const post = await fetch(
`https://public-api.wordpress.com/rest/v1.1/sites/${ siteId }/posts?${ new URLSearchParams(
{ search, category }
) }`,
{
headers: {
Authorization: `Bearer ${ authToken }`,
'Content-Type': 'application/json',
},
method: 'GET',
}
);
if ( post.status !== 200 ) {
const text = await post.text();
throw new Error( `Error creating draft post: ${ text }` );
}
return ( await post.json() ).posts as WordpressComPost[];
} catch ( e: unknown ) {
if ( e instanceof Error ) {
Logger.error( e.message );
}
}
};
/**
* Edit a post on wordpress.com
*

View File

@ -102,3 +102,16 @@ export const getContributorData = async (
headRef,
} as ContributorData;
};
export const getMostRecentFinal = async () => {
const octokit = new Octokit( {
auth: getEnvVar( 'GITHUB_ACCESS_TOKEN', true ),
} );
const release = await octokit.repos.getLatestRelease( {
owner: 'woocommerce',
repo: 'woocommerce',
} );
return release.data;
};

View File

@ -31,6 +31,7 @@
"commander": "9.4.0",
"dotenv": "^10.0.0",
"ejs": "^3.1.8",
"enquirer": "^2.3.6",
"express": "^4.18.1",
"form-data": "^4.0.0",
"lodash.shuffle": "^4.2.0",

View File

@ -0,0 +1,164 @@
<!-- wp:paragraph -->
<p>
Beta <%= betaNumber %> for the <%=
finalReleaseDate.toLocaleDateString('en-US', {month: 'long', day:
'numeric'}) %> release of WooCommerce is now available for testing! You can
either
<a
href="https://downloads.wordpress.org/plugin/woocommerce.<%= version.version %>.zip"
target="_blank"
rel="noreferrer noopener"
>download it directly from WordPress.org</a
>
or install our
<a
rel="noreferrer noopener"
href="https://woocommerce.wordpress.com/2018/07/30/woocommerce-beta-tester-2-0-0/"
target="_blank"
>WooCommerce Beta Tester Plugin</a
>.
</p>
<!-- /wp:paragraph -->
<!-- wp:heading -->
<h2 class="wp-block-heading">Highlights</h2>
<!-- /wp:heading -->
<!-- wp:paragraph -->
<p>
Since the release of
<a href="<%= lastReleasePostUrl %>"><%= prettyPreviousVersion %></a>, the
following changes have been made:
</p>
<!-- /wp:paragraph -->
<!-- wp:list -->
<ul>
<!-- wp:list-item -->
<li>List your changes here.</li>
<!-- /wp:list-item -->
</ul>
<!-- /wp:list -->
<!-- wp:paragraph -->
<p>
For the complete list, view the&nbsp;<a
href="https://github.com/woocommerce/woocommerce/blob/release/<%= version.major %>.<%= version.minor %>/plugins/woocommerce/readme.txt"
>changelog</a
>&nbsp;in the readme for this release.
</p>
<!-- /wp:paragraph -->
<!-- wp:heading {"anchor":"actions-and-filters"} -->
<h2 class="wp-block-heading" id="actions-and-filters">Actions and Filters</h2>
<!-- /wp:heading -->
<!-- wp:paragraph -->
<p>No changes introduced.</p>
<!-- /wp:paragraph -->
<!-- wp:heading {"anchor":"database-changes"} -->
<h2 class="wp-block-heading" id="database-changes">Database Changes</h2>
<!-- /wp:heading -->
<!-- wp:paragraph -->
<p>No changes introduced.</p>
<!-- /wp:paragraph -->
<!-- wp:heading {"anchor":"template-changes"} -->
<h2 class="wp-block-heading" id="template-changes">Template Changes</h2>
<!-- /wp:heading -->
<!-- wp:paragraph -->
<p>No changes introduced.</p>
<!-- /wp:paragraph -->
<!-- wp:heading {"anchor":"release-schedule"} -->
<h2 class="wp-block-heading" id="release-schedule">Release Schedule</h2>
<!-- /wp:heading -->
<!-- wp:paragraph -->
<p>
We're still on track for our planned <%=
finalReleaseDate.toLocaleDateString('en-US', {month: 'long', day:
'numeric'}) %> release.
</p>
<!-- /wp:paragraph -->
<!-- wp:table -->
<figure class="wp-block-table">
<table>
<tbody>
<tr>
<td><strong>Version</strong></td>
<td><strong>Release</strong></td>
</tr>
<tr>
<td>Release Candidate</td>
<td>
<%= rcReleaseDate.toLocaleDateString('en-US', { month:
'long', day: 'numeric', year: 'numeric' }) %>
</td>
</tr>
<tr>
<td>Final Release</td>
<td>
<%= finalReleaseDate.toLocaleDateString('en-US', { month:
'long', day: 'numeric', year: 'numeric' }) %>
</td>
</tr>
</tbody>
</table>
</figure>
<!-- /wp:table -->
<!-- wp:heading {"anchor":"testing"} -->
<h2 class="wp-block-heading" id="testing">Testing</h2>
<!-- /wp:heading -->
<!-- wp:paragraph -->
<p>
If you'd like to dive in and help test this new release, our handy&nbsp;<a
href="https://wordpress.org/plugins/woocommerce-beta-tester/"
target="_blank"
>WooCommerce Beta Tester plugin</a
>&nbsp;allows you to switch between beta versions and release candidates.
You can also&nbsp;<a
href="https://downloads.wordpress.org/plugin/woocommerce.<%= version.version %>.zip"
target="_blank"
rel="noreferrer noopener"
>download the release from WordPress.org</a
>.
</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>
A set of testing instructions has been published on&nbsp;our&nbsp;<a
href="https://github.com/woocommerce/woocommerce/wiki/Release-Testing-Instructions-WooCommerce-<%= version.major %>.<%= version.minor %>"
target="_blank"
rel="noreferrer noopener"
>Wiki page in GitHub</a
>. We've also posted&nbsp;<a
href="https://woocommerce.wordpress.com/2015/07/25/how-to-beta-test-woocommerce/"
target="_blank"
>a helpful writeup on beta testing</a
>&nbsp;to help get you started.
</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>
If you discover any bugs during the testing process, please let us know
by&nbsp;<a
href="https://github.com/woocommerce/woocommerce/issues/new?assignees=&amp;labels=&amp;template=1-bug-report.yml&amp;title=[<%= version.major %>.<%= version.minor %> beta]: Title of the issue"
target="_blank"
rel="noreferrer noopener"
>logging a report in GitHub</a
>.
</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p></p>
<!-- /wp:paragraph -->

View File

@ -1,7 +1,10 @@
{
"extends": "@tsconfig/node16/tsconfig.json",
"ts-node": {
"transpileOnly": true,
"files": true,
}
"extends": "@tsconfig/node16/tsconfig.json",
"compilerOptions": {
"module": "Node16"
},
"ts-node": {
"transpileOnly": true,
"files": true
}
}