2024-06-05 14:53:32 +00:00
import { execSync , spawn } from 'child_process' ;
2022-04-26 16:21:47 +00:00
import fs , { existsSync } from 'fs' ;
2021-09-29 15:01:05 +00:00
import open from 'open' ;
2021-10-01 14:42:39 +00:00
import inquirer from 'inquirer' ;
2022-06-16 06:25:35 +00:00
import { RewritingStream } from 'parse5-html-rewriting-stream' ;
import { table } from 'table' ;
2022-08-09 17:41:14 +00:00
import progressbar from 'string-progressbar' ;
2023-04-27 08:03:00 +00:00
import semver from 'semver' ;
2021-09-29 15:01:05 +00:00
const remoteSSH = 'wpcom-sandbox' ;
const sandboxPublicThemesFolder = '/home/wpdev/public_html/wp-content/themes/pub' ;
const sandboxRootFolder = '/home/wpdev/public_html/' ;
2023-04-27 08:03:00 +00:00
const glotPressScript = '~/public_html/bin/i18n/create-glotpress-project-for-theme.php' ;
2021-09-29 15:01:05 +00:00
const isWin = process . platform === 'win32' ;
2023-11-13 12:19:31 +00:00
const coreThemes = [ 'twentyten' , 'twentyeleven' , 'twentytwelve' , 'twentythirteen' , 'twentyfourteen' , 'twentyfifteen' , 'twentysixteen' , 'twentyseventeen' , 'twentynineteen' , 'twentytwenty' , 'twentytwentyone' , 'twentytwentytwo' , 'twentytwentythree' , 'twentytwentyfour' ] ;
2021-09-29 15:01:05 +00:00
2022-05-27 14:12:42 +00:00
const commands = {
"push-button-deploy" : {
helpText : `
* Gets the last deployed hash from the sandbox
* Version bump all themes that have changes since the last deployment
* Commit the version bump change to github
* Clean the sandbox and ensure it is up - to - date
* Push all changed files ( including removal of deleted files ) since the last deployment
* Update the 'last deployed' hash on the sandbox
* Create a phabricator diff based on the changes since the last deployment . The description including the commit messages since the last deployment .
* Open the Phabricator Diff in your browser
* Create a tag in the github repository at this point of change which includes the phabricator link in the description
* After pausing to allow testing , land and deploy the changes
` ,
run : pushButtonDeploy
} ,
"clean-sandbox" : {
helpText : 'Perform a hard reset, checkout trunk, and pull on the public themes working copy on your sandbox.' ,
run : cleanSandbox
} ,
"push-to-sandbox" : {
helpText : 'Uses rsync to copy all modified files for all themes from the local machine to your sandbox.' ,
run : pushToSandbox
} ,
"push-changes-to-sandbox" : {
helpText : 'Uses rsync to copy all modified files for any modified themes from the local machine to your sandbox.' ,
run : pushChangesToSandbox
} ,
"push-theme-to-sandbox" : {
helpText : 'Uses rsync to copy all modified files for the specified theme from the local machine to your sandbox.' ,
additionalArgs : '<theme-slug>' ,
run : ( args ) => pushThemeToSandbox ( args ? . [ 1 ] )
} ,
"version-bump-themes" : {
helpText : 'Bump the version of any theme that has had changes since the last deployment. This includes bumping the version of any parent themes and updating the changelog for the theme.' ,
run : versionBumpThemes
} ,
"land-diff" : {
helpText : 'Run arc land to merge in the specified diff id.' ,
additionalArgs : '<arc diff id>' ,
run : ( args ) => landChanges ( args ? . [ 1 ] )
} ,
"deploy-preview" : {
helpText : 'Display a list of the changes to be deployed.' ,
run : deployPreview
} ,
"deploy-theme" : {
helpText : 'This runs "deploy pub <theme>" on the provided list of themes.' ,
additionalArgs : '<array of theme slugs>' ,
2022-07-28 16:23:47 +00:00
run : ( args ) => deployThemes ( args ? . [ 1 ] . split ( /[ ,]+/ ) )
2022-05-27 14:12:42 +00:00
} ,
2024-04-15 20:57:02 +00:00
"add-strict-typing" : {
helpText : 'Adds strict typing to any changed themes.' ,
run : ( ) => addStrictTypesToChangedThemes ( )
} ,
2022-05-27 14:12:42 +00:00
"build-com-zip" : {
helpText : 'Build the production zip file for the specified theme.' ,
additionalArgs : '<theme-slug>' ,
2022-07-28 16:23:47 +00:00
run : ( args ) => buildComZips ( args ? . [ 1 ] . split ( /[ ,]+/ ) )
2022-05-27 14:12:42 +00:00
} ,
2022-07-28 13:14:29 +00:00
"checkout-core-theme" : {
helpText : 'Use SVN to checkout the given core themes from the wpcom SVN repository.' ,
additionalArgs : '<theme-slug>' ,
run : ( args ) => checkoutCoreTheme ( args ? . [ 1 ] )
} ,
2024-01-09 21:27:59 +00:00
"pull-all-themes" : {
helpText : 'Use rsync to copy all public theme files from your sandbox to your local machine.' ,
run : pullAllThemes
} ,
2022-05-27 14:12:42 +00:00
"pull-core-themes" : {
2022-07-28 13:14:29 +00:00
helpText : 'Use rsync to copy all public CORE theme files from your sandbox to your local machine. CORE themes are any of the Twenty<whatever> themes.' ,
2022-05-27 14:12:42 +00:00
run : pullCoreThemes
} ,
"push-core-themes" : {
2022-07-28 13:14:29 +00:00
helpText : 'Use rsync to copy all public CORE theme files from your local machine to your sandbox. CORE themes are any of the Twenty<whatever> themes.' ,
2022-05-27 14:12:42 +00:00
run : pushCoreThemes
} ,
"sync-core-theme" : {
2022-07-28 13:14:29 +00:00
helpText : 'Given a theme slug and SVN revision, sync the theme from the specified revision to the latest. This requires the core theme to be currently checked out from the wpcom svn repository.' ,
2022-05-27 14:12:42 +00:00
additionalArgs : '<theme-slug> <since-revision>' ,
run : ( args ) => syncCoreTheme ( args ? . [ 1 ] , args ? . [ 2 ] )
} ,
"deploy-sync-core-theme" : {
helpText : 'Given a theme slug and SVN revision, sync the theme from the specified revision to the latest. This command contains additional prompts and error checking not provided by sync-core-theme.' ,
additionalArgs : '<theme-slug> <since-revision>' ,
run : ( args ) => deploySyncCoreTheme ( args ? . [ 1 ] , args ? . [ 2 ] )
} ,
2022-07-28 13:14:29 +00:00
"create-core-phabricator-diff" : {
helpText : 'Given a theme slug and specific revision create a Phabricator diff from the resources currently on the sandbox.' ,
additionalArgs : '<theme-slug> <since-revision>' ,
run : ( args ) => createCorePhabriactorDiff ( args ? . [ 1 ] , args ? . [ 2 ] )
} ,
2022-05-27 14:12:42 +00:00
"update-theme-changelog" : {
helpText : 'Use the commit log to build a list of recent changes and add them as a new changelog entry. If add-changes is true, the updated readme.txt will be staged.' ,
additionalArgs : '<theme-slug> <add-changes, true/false>' ,
run : ( args ) => updateThemeChangelog ( args ? . [ 1 ] , false , args ? . [ 2 ] )
} ,
"rebuild-theme-changelog" : {
helpText : 'Rebuild the entire change long from the given starting hash.' ,
additionalArgs : '<theme-slug> <since>' ,
run : ( args ) => rebuildThemeChangelog ( args ? . [ 1 ] , args ? . [ 2 ] )
} ,
2022-06-16 06:25:35 +00:00
"escape-patterns" : {
helpText : 'Escapes block patterns for pattern files that have changes (staged or unstaged).' ,
run : ( ) => escapePatterns ( )
} ,
2022-05-27 14:12:42 +00:00
"help" : {
helpText : 'Displays the main help message.' ,
run : ( args ) => showHelp ( args ? . [ 1 ] )
} ,
} ;
2021-09-29 15:01:05 +00:00
( async function start ( ) {
let args = process . argv . slice ( 2 ) ;
let command = args ? . [ 0 ] ;
2022-05-27 14:12:42 +00:00
if ( ! commands [ command ] ) {
return showHelp ( ) ;
2021-09-29 15:01:05 +00:00
}
2022-05-27 14:12:42 +00:00
commands [ command ] . run ( args ) ;
2021-09-29 15:01:05 +00:00
} ) ( ) ;
2022-05-27 14:12:42 +00:00
function showHelp ( command = '' ) {
if ( ! command || ! commands . hasOwnProperty ( command ) ) {
console . log ( `
node theme - utils . mjs [ command ]
Available commands :
_ ( theme - utils . mjs help [ command ] for more details ) _
\ t$ { Object . keys ( commands ) . join ( '\n\t' ) }
` );
return ;
}
const { helpText , additionalArgs } = commands [ command ] ;
console . log ( `
$ { command } $ { additionalArgs ? ? '' }
$ { helpText }
` );
2021-09-29 15:01:05 +00:00
}
2022-04-20 18:28:17 +00:00
/ *
Create list of changes from git logs
Optionally pass in a deployed hash or default to calling getLastDeployedHash ( )
Optionally pass in boolean bulletPoints to add bullet points to each commit log
* /
2022-04-21 14:23:35 +00:00
async function getCommitLogs ( hash , bulletPoints , theme ) {
2022-04-20 18:28:17 +00:00
if ( ! hash ) {
hash = await getLastDeployedHash ( ) ;
}
2022-04-21 14:23:35 +00:00
let format = 'format:%s' ;
let themeDir = '' ;
2022-04-20 18:28:17 +00:00
if ( bulletPoints ) {
2022-04-21 14:23:35 +00:00
format = 'format:"* %s"' ;
2022-04-20 18:28:17 +00:00
}
2022-04-21 14:23:35 +00:00
if ( theme ) {
themeDir = ` -- ./ ${ theme } ` ;
}
let logs = await executeCommand ( ` git log --reverse --pretty= ${ format } ${ hash } ..HEAD ${ themeDir } ` ) ;
2022-04-20 18:28:17 +00:00
// Remove any double quotes from commit messages
logs = logs . replace ( /"/g , '' ) ;
return logs ;
}
2021-10-14 12:18:24 +00:00
/ *
2021-10-01 14:42:39 +00:00
Determine what changes would be deployed
* /
async function deployPreview ( ) {
console . clear ( ) ;
console . log ( 'To ensure accuracy clean your sandbox before previewing. (It is not automatically done).' ) ;
let message = await checkForDeployability ( ) ;
if ( message ) {
console . log ( ` \n ${ message } \n \n ` ) ;
}
let hash = await getLastDeployedHash ( ) ;
console . log ( ` Last deployed hash: ${ hash } ` ) ;
let changedThemes = await getChangedThemes ( hash ) ;
console . log ( ` The following themes have changes: \n ${ changedThemes } ` ) ;
2021-10-14 12:18:24 +00:00
2022-04-20 18:28:17 +00:00
let logs = await getCommitLogs ( hash ) ;
2021-10-01 14:42:39 +00:00
console . log ( ` \n \n Commit log of changes to be deployed: \n \n ${ logs } \n \n ` ) ;
}
2024-04-15 20:57:02 +00:00
async function addStrictTypesToChangedThemes ( ) {
let hash = await getLastDeployedHash ( ) ;
const changedThemes = await getChangedThemes ( hash ) ;
for ( let theme of changedThemes ) {
await executeCommand ( `
bash - c "./add-strict-types.sh ${theme}"
` , true)
. catch ( ( err ) => {
console . log ( ` Error adding strict types to ${ theme } : ${ err } ` ) ;
} ) ;
}
}
2021-09-29 15:01:05 +00:00
/ *
Execute the first phase of a deployment .
* Gets the last deployed hash from the sandbox
2022-05-27 14:12:42 +00:00
* Version bump all themes that have changes since the last deployment
2021-09-29 15:01:05 +00:00
* Commit the version bump change to github
* Clean the sandbox and ensure it is up - to - date
* Push all changed files ( including removal of deleted files ) since the last deployment
* Update the 'last deployed' hash on the sandbox
* Create a phabricator diff based on the changes since the last deployment . The description including the commit messages since the last deployment .
* Open the Phabricator Diff in your browser
* Create a tag in the github repository at this point of change which includes the phabricator link in the description
* /
2022-02-02 09:53:54 +00:00
async function pushButtonDeploy ( ) {
2021-09-29 15:01:05 +00:00
2021-10-11 20:00:20 +00:00
console . clear ( ) ;
let prompt = await inquirer . prompt ( [ {
type : 'confirm' ,
message : 'You are about to deploy /trunk. Are you ready to continue?' ,
name : "continue" ,
default : false
} ] ) ;
2022-05-27 14:12:42 +00:00
if ( ! prompt . continue ) {
2021-10-11 20:00:20 +00:00
return ;
}
2021-09-29 15:01:05 +00:00
2021-10-01 14:42:39 +00:00
let message = await checkForDeployability ( ) ;
if ( message ) {
return console . log ( ` \n \n ${ message } \n \n ` ) ;
}
2021-09-29 15:01:05 +00:00
2021-10-01 14:42:39 +00:00
try {
2022-02-02 09:53:54 +00:00
await cleanSandbox ( ) ;
2021-09-29 15:01:05 +00:00
2024-04-15 20:57:02 +00:00
const hash = await getLastDeployedHash ( ) ;
await addStrictTypesToChangedThemes ( ) ;
const thingsWentBump = await versionBumpThemes ( ) ;
2021-10-14 14:41:04 +00:00
2022-05-27 14:12:42 +00:00
if ( thingsWentBump ) {
2022-01-17 17:45:15 +00:00
prompt = await inquirer . prompt ( [ {
type : 'confirm' ,
2022-04-20 18:28:17 +00:00
message : 'Are you good with the version bump and changelog updates? Make any manual adjustments now if necessary.' ,
2022-01-17 17:45:15 +00:00
name : "continue" ,
default : false
} ] ) ;
2022-05-27 14:12:42 +00:00
if ( ! prompt . continue ) {
console . log ( ` Aborted Automated Deploy Process at version bump changes. ` ) ;
2022-01-17 17:45:15 +00:00
return ;
}
}
2024-04-15 20:57:02 +00:00
const changedThemes = await getChangedThemes ( hash ) ;
2021-10-14 14:41:04 +00:00
2023-04-27 08:03:00 +00:00
if ( ! changedThemes . length ) {
console . log ( ` \n \n Everything is upto date. Nothing new to deploy. \n \n ` ) ;
return ;
}
2021-10-14 14:41:04 +00:00
2023-04-27 08:03:00 +00:00
await pushChangesToSandbox ( ) ;
await createGlotPressProject ( changedThemes ) ;
2021-10-01 14:42:39 +00:00
2021-12-15 18:04:22 +00:00
//push changes (from version bump)
2022-05-27 14:12:42 +00:00
if ( thingsWentBump ) {
2021-12-15 18:04:22 +00:00
prompt = await inquirer . prompt ( [ {
type : 'confirm' ,
message : 'Are you ready to push this version bump change to the source repository (Github)?' ,
name : "continue" ,
default : false
} ] ) ;
2022-05-27 14:12:42 +00:00
if ( ! prompt . continue ) {
console . log ( ` Aborted Automated Deploy Process at version bump push change. ` ) ;
2021-12-15 18:04:22 +00:00
return ;
}
await executeCommand ( `
2021-12-15 18:18:32 +00:00
git commit - m "Version Bump" ;
2023-07-07 12:40:05 +00:00
git push -- set - upstream origin trunk
2021-12-15 18:04:22 +00:00
` , true);
}
2022-01-17 17:45:15 +00:00
await updateLastDeployedHash ( ) ;
2022-02-21 20:52:07 +00:00
let commitMessage = await buildPhabricatorCommitMessageSince ( hash ) ;
let diffUrl = await createPhabricatorDiff ( commitMessage ) ;
2021-10-01 14:42:39 +00:00
let diffId = diffUrl . split ( 'a8c.com/' ) [ 1 ] ;
2021-10-14 14:41:04 +00:00
2021-10-01 14:42:39 +00:00
await tagDeployment ( {
hash : hash ,
diffId : diffId
} ) ;
2021-11-12 13:50:34 +00:00
console . log ( ` \n \n Phase One Complete \n \n Your sandbox has been updated and the diff is available for review. \n Please give your sandbox a smoke test to determine that the changes work as expected. \n The following themes have had changes: \n \n ${ changedThemes . join ( ' ' ) } \n \n \n ` ) ;
2021-10-01 14:42:39 +00:00
prompt = await inquirer . prompt ( [ {
type : 'confirm' ,
message : 'Are you ready to land these changes?' ,
name : "continue" ,
default : false
} ] ) ;
2022-05-27 14:12:42 +00:00
if ( ! prompt . continue ) {
console . log ( ` Aborted Automated Deploy Process Landing Phase \n \n You will have to land these changes manually. The ID of the diff to land: ${ diffId } ` ) ;
2021-10-01 14:42:39 +00:00
return ;
}
2022-02-02 09:53:54 +00:00
await landChanges ( diffId ) ;
2021-10-01 14:42:39 +00:00
2022-02-01 14:26:32 +00:00
try {
2023-04-28 22:32:19 +00:00
await deployThemes ( changedThemes ) ;
2022-05-27 14:12:42 +00:00
}
2022-02-01 14:26:32 +00:00
catch ( err ) {
prompt = await inquirer . prompt ( [ {
type : 'confirm' ,
message : ` There was an error deploying themes. ${ err } Do you wish to continue to the next step? ` ,
name : "continue" ,
default : false
} ] ) ;
2022-05-27 14:12:42 +00:00
if ( ! prompt . continue ) {
console . log ( ` Aborted Automated Deploy during deploy phase. ` ) ;
2022-02-01 14:26:32 +00:00
return ;
}
}
2023-05-04 18:47:12 +00:00
await buildComZips ( changedThemes ) ;
2022-02-01 14:26:32 +00:00
2021-10-11 20:45:21 +00:00
console . log ( ` The following themes have changed: \n ${ changedThemes . join ( '\n' ) } ` )
2021-10-01 14:42:39 +00:00
console . log ( '\n\nAll Done!!\n\n' ) ;
}
catch ( err ) {
2022-02-01 14:26:32 +00:00
console . log ( "ERROR with deploy script: " , err ) ;
2021-10-01 14:42:39 +00:00
}
2021-09-29 15:01:05 +00:00
}
2021-10-29 07:38:36 +00:00
2022-02-21 20:52:07 +00:00
async function deploySyncCoreTheme ( theme , sinceRevision ) {
2022-07-28 13:14:29 +00:00
if ( ! theme ) {
console . log ( 'Must supply theme to sync and revision to start from' ) ;
return ;
}
2022-02-21 20:52:07 +00:00
await cleanSandbox ( ) ;
2022-07-28 13:14:29 +00:00
await checkoutCoreTheme ( theme ) ;
await syncCoreTheme ( theme , sinceRevision ) ;
2022-02-21 20:52:07 +00:00
let prompt = await inquirer . prompt ( [ {
type : 'confirm' ,
2022-07-28 13:14:29 +00:00
message : ` Changes have been synced locally. Please resolve any conflicts now. Are you ready to continue? ` ,
2022-02-21 20:52:07 +00:00
name : "continue" ,
default : false
} ] ) ;
2022-05-27 14:12:42 +00:00
if ( ! prompt . continue ) {
console . log ( ` Aborted Core Sync Deploy. ` ) ;
2022-02-21 20:52:07 +00:00
return ;
}
2022-07-28 13:14:29 +00:00
await pushThemeToSandbox ( theme ) ;
let diffId = await createCorePhabriactorDiff ( theme , sinceRevision ) ;
2022-02-21 20:52:07 +00:00
prompt = await inquirer . prompt ( [ {
type : 'confirm' ,
message : 'Are you ready to land these changes?' ,
name : "continue" ,
default : false
} ] ) ;
2022-05-27 14:12:42 +00:00
if ( ! prompt . continue ) {
console . log ( ` Aborted Automated Deploy Sync Process Landing Phase \n \n You will have to land these changes manually. The ID of the diff to land: ${ diffId } ` ) ;
2022-02-21 20:52:07 +00:00
return ;
}
2023-05-15 15:36:10 +00:00
await landChanges ( diffId ) ;
await deployThemes ( [ theme ] ) ;
await buildComZips ( [ theme ] ) ;
2022-02-21 20:52:07 +00:00
return ;
2022-07-28 13:14:29 +00:00
}
async function buildCorePhabricatorCommitMessageSince ( theme , sinceRevision ) {
let latestRevision = await executeCommand ( ` svn info -r HEAD https://develop.svn.wordpress.org/trunk | grep Revision | egrep -o "[0-9]+" ` ) ;
let logs = await executeCommand ( ` svn log https://core.svn.wordpress.org/trunk/wp-content/themes/ ${ theme } -r ${ sinceRevision } :HEAD ` )
// Remove any double or back quotes from commit messages
logs = logs . replace ( /"/g , '' ) ;
logs = logs . replace ( /`/g , "'" ) ;
2022-08-04 07:59:33 +00:00
logs = logs . replace ( /\$/g , "%24" ) ;
2022-02-21 20:52:07 +00:00
2022-07-28 13:14:29 +00:00
return ` ${ theme } : Merge latest core changes up to [wp ${ latestRevision } ]
Summary :
$ { logs }
Test Plan : Activate $ { theme } and ensure nothing is broken
Reviewers :
# themes _team
Subscribers :
` ;
}
/ * *
* Deploys the localy copy of a core theme to wpcom .
* /
async function createCorePhabriactorDiff ( theme , sinceRevision ) {
let commitMessage = await buildCorePhabricatorCommitMessageSince ( theme , sinceRevision ) ;
let diffUrl = await createPhabricatorDiff ( commitMessage ) ;
let diffId = diffUrl . split ( 'a8c.com/' ) [ 1 ] ;
return diffId ;
2022-02-21 20:52:07 +00:00
}
2021-10-29 07:38:36 +00:00
/ *
Build . zip file for . com
* /
async function buildComZip ( themeSlug ) {
2022-05-27 14:12:42 +00:00
console . log ( ` Building ${ themeSlug } .zip ` ) ;
2021-10-29 07:38:36 +00:00
let styleCss = fs . readFileSync ( ` ${ themeSlug } /style.css ` , 'utf8' ) ;
// Gets the theme version (Version:) and minimum WP version (Tested up to:) from the theme's style.css
2021-11-12 13:50:34 +00:00
let themeVersion = getThemeMetadata ( styleCss , 'Version' ) ;
2021-12-20 15:18:46 +00:00
let wpVersionCompat = getThemeMetadata ( styleCss , 'Requires at least' ) ;
2021-10-29 07:38:36 +00:00
if ( themeVersion && wpVersionCompat ) {
2022-02-01 14:26:32 +00:00
await executeOnSandbox ( ` php ${ sandboxRootFolder } bin/themes/theme-downloads/build-theme-zip.php --stylesheet=pub/ ${ themeSlug } --themeversion= ${ themeVersion } --wpversioncompat= ${ wpVersionCompat } ; ` , true ) ;
2021-12-14 16:45:07 +00:00
}
2021-11-12 13:50:34 +00:00
else {
2021-10-29 07:38:36 +00:00
console . log ( 'Unable to build theme .zip.' ) ;
if ( ! themeVersion ) {
console . log ( 'Could not find theme version (Version:) in the theme style.css.' ) ;
}
if ( ! wpVersionCompat ) {
console . log ( 'Could not find WP compat version (Tested up to:) in the theme style.css.' ) ;
}
console . log ( 'Please build the .zip file for the theme manually.' , themeSlug ) ;
open ( 'https://mc.a8c.com/themes/downloads/' ) ;
}
}
2021-10-26 15:22:14 +00:00
async function buildComZips ( themes ) {
2022-08-09 17:41:14 +00:00
console . log ( ` Building dotcom .zip files ` ) ;
const progress = startProgress ( themes . length ) ;
const failedThemes = [ ]
2022-05-27 14:12:42 +00:00
for ( let theme of themes ) {
2022-02-01 14:26:32 +00:00
try {
await buildComZip ( theme ) ;
} catch ( err ) {
console . log ( ` There was an error building dotcom zip for ${ theme } . ${ err } ` ) ;
2022-08-09 17:41:14 +00:00
failedThemes . push ( theme ) ;
2022-02-01 14:26:32 +00:00
}
2022-08-09 17:41:14 +00:00
progress . increment ( ) ;
}
if ( failedThemes . length ) {
const tableConfig = {
columnDefault : {
width : 40 ,
} ,
header : {
alignment : 'center' ,
content : ` There was an error building dotcom zip for following themes. ` ,
}
} ;
console . log ( table ( failedThemes . map ( t => [ t ] ) , tableConfig ) ) ;
2021-10-29 07:38:36 +00:00
}
2021-10-26 15:22:14 +00:00
}
2021-09-29 15:01:05 +00:00
/ *
2021-10-01 14:42:39 +00:00
Check to ensure that :
* The current branch is / trunk
* That trunk is up - to - date with origin / trunk
2021-09-29 15:01:05 +00:00
* /
2022-05-27 14:12:42 +00:00
async function checkForDeployability ( ) {
2021-10-01 14:42:39 +00:00
let branchName = await executeCommand ( 'git symbolic-ref --short HEAD' ) ;
2022-05-27 14:12:42 +00:00
if ( branchName !== 'trunk' ) {
2021-10-01 14:42:39 +00:00
return 'Only the /trunk branch can be deployed.' ;
}
2021-09-29 15:01:05 +00:00
2021-10-01 14:42:39 +00:00
await executeCommand ( 'git remote update' , true ) ;
let localMasterHash = await executeCommand ( 'git rev-parse trunk' )
let remoteMasterHash = await executeCommand ( 'git rev-parse origin/trunk' )
2022-05-27 14:12:42 +00:00
if ( localMasterHash !== remoteMasterHash ) {
2021-10-01 14:42:39 +00:00
return 'Local /trunk is out-of-date. Pull changes to continue.'
}
return null ;
}
2021-09-29 15:01:05 +00:00
2021-10-01 14:42:39 +00:00
/ *
Land the changes from the given diff ID . This is the "production merge" .
* /
2022-05-27 14:12:42 +00:00
async function landChanges ( diffId ) {
2022-02-01 14:47:28 +00:00
return executeCommand ( ` ssh -tt -A ${ remoteSSH } "cd ${ sandboxPublicThemesFolder } ; /usr/local/bin/arc patch ${ diffId } ; /usr/local/bin/arc land; exit;" ` , true ) ;
2021-10-01 14:42:39 +00:00
}
2021-09-29 15:01:05 +00:00
2021-10-01 14:42:39 +00:00
async function getChangedThemes ( hash ) {
2021-10-11 20:00:20 +00:00
console . log ( 'Determining all changed themes' ) ;
2021-10-01 14:42:39 +00:00
let themes = await getActionableThemes ( ) ;
let changedThemes = [ ] ;
for ( let theme of themes ) {
let hasChanges = await checkThemeForChanges ( theme , hash ) ;
2022-05-27 14:12:42 +00:00
if ( hasChanges ) {
2021-11-12 13:50:34 +00:00
changedThemes . push ( theme ) ;
2021-10-01 14:42:39 +00:00
}
}
return changedThemes ;
}
2021-09-29 15:01:05 +00:00
2021-10-11 20:00:20 +00:00
/ *
2021-10-26 15:22:14 +00:00
Deploy a collection of themes .
Part of the push - button - deploy process .
Can also be triggered to deploy a single theme with the command :
node . / theme - utils . mjs deploy - theme THEMENAME
2021-10-11 20:00:20 +00:00
* /
2022-05-27 14:12:42 +00:00
async function deployThemes ( themes ) {
2021-10-26 15:22:14 +00:00
2021-10-01 14:42:39 +00:00
let response ;
2022-08-09 17:41:14 +00:00
const failedThemes = [ ] ;
const progress = startProgress ( themes . length ) ;
2021-10-11 20:00:20 +00:00
2022-05-27 14:12:42 +00:00
for ( let theme of themes ) {
2021-10-26 15:22:14 +00:00
2022-05-27 14:12:42 +00:00
console . log ( ` Deploying ${ theme } ` ) ;
2021-10-26 15:22:14 +00:00
let deploySuccess = false ;
let attempt = 0 ;
2022-05-27 14:12:42 +00:00
while ( ! deploySuccess && attempt <= 2 ) {
2021-10-26 15:22:14 +00:00
attempt ++ ;
console . log ( ` \n attempt # ${ attempt } \n \n ` ) ;
2022-08-09 17:41:14 +00:00
try {
response = await executeOnSandbox ( ` deploy pub ${ theme } ;exit; ` , true , true ) ;
deploySuccess = response . includes ( 'successfully deployed to' ) ;
} catch ( error ) {
deploySuccess = false
}
2021-10-26 15:22:14 +00:00
2022-05-27 14:12:42 +00:00
if ( ! deploySuccess ) {
console . log ( 'Deploy was not successful. Trying again in 10 seconds...' ) ;
2021-10-26 15:22:14 +00:00
await new Promise ( resolve => setTimeout ( resolve , 10000 ) ) ;
2021-10-29 07:38:36 +00:00
}
2021-10-26 15:22:14 +00:00
else {
2022-05-27 14:12:42 +00:00
console . log ( "Deploy successful." ) ;
2021-10-26 15:22:14 +00:00
}
}
2022-05-27 14:12:42 +00:00
if ( ! deploySuccess ) {
2022-08-09 17:41:14 +00:00
console . log ( ` ${ theme } was not sucessfully deployed and should be deployed manually. ` ) ;
failedThemes . push ( theme ) ;
2022-02-01 14:26:32 +00:00
}
2022-08-09 17:41:14 +00:00
progress . increment ( ) ;
}
if ( failedThemes . length ) {
const tableConfig = {
columnDefault : {
width : 40 ,
} ,
header : {
alignment : 'center' ,
content : ` Following themes are not deployed. ` ,
}
} ;
console . log ( table ( failedThemes . map ( t => [ t ] ) , tableConfig ) ) ;
2021-10-01 14:42:39 +00:00
}
2021-09-29 15:01:05 +00:00
}
/ *
Provide the hash of the last managed deployment .
This hash is used to determine all the changes that have happened between that point and the current point .
* /
async function getLastDeployedHash ( ) {
let result = await executeOnSandbox ( `
cat $ { sandboxPublicThemesFolder } / . pub - git - hash
` );
return result ;
}
/ *
Update the 'last deployed hash' on the server with the current hash .
* /
async function updateLastDeployedHash ( ) {
let hash = await executeCommand ( ` git rev-parse HEAD ` ) ;
await executeOnSandbox ( `
echo '${hash}' > $ { sandboxPublicThemesFolder } / . pub - git - hash
` );
}
/ *
Version bump ( increment version patch ) any theme project that has had changes since the last deployment .
2021-10-01 14:42:39 +00:00
If a theme ' s version has already been changed since that last deployment then do not version bump it .
2021-09-29 15:01:05 +00:00
If any theme projects have had a version bump also version bump the parent project .
2022-04-20 18:28:17 +00:00
If a theme has changes also update its changelog .
2021-10-01 14:42:39 +00:00
Commit the change .
2021-09-29 15:01:05 +00:00
* /
2021-10-01 14:42:39 +00:00
async function versionBumpThemes ( ) {
2021-09-29 15:01:05 +00:00
console . log ( "Version Bumping" ) ;
2024-06-05 14:53:32 +00:00
const themes = await getActionableThemes ( ) ;
const latestTag = execSync ( 'git describe --tags --abbrev=0' ) . toString ( ) . trim ( ) ;
// Get the hash for the latest tag.
const hash = execSync ( ` git rev-parse ${ latestTag } ` ) . toString ( ) . trim ( ) ;
2021-09-29 15:01:05 +00:00
let versionBumpCount = 0 ;
2024-06-05 14:53:32 +00:00
let changesWereMade = false ;
2021-09-29 15:01:05 +00:00
for ( let theme of themes ) {
2024-06-05 14:53:32 +00:00
const hasChanges = await checkThemeForChanges ( theme , hash ) ;
2022-05-27 14:12:42 +00:00
if ( ! hasChanges ) {
2021-09-29 15:01:05 +00:00
continue ;
}
versionBumpCount ++ ;
2024-06-05 14:53:32 +00:00
const hasVersionBump = await checkThemeForVersionBump ( theme , hash ) ;
2022-05-27 14:12:42 +00:00
if ( hasVersionBump ) {
2021-09-29 15:01:05 +00:00
continue ;
}
2021-10-14 14:41:04 +00:00
2021-12-15 18:04:22 +00:00
await versionBumpTheme ( theme , true ) ;
2022-04-20 18:28:17 +00:00
await updateThemeChangelog ( theme , true ) ;
2021-12-15 18:04:22 +00:00
changesWereMade = true ;
2021-09-29 15:01:05 +00:00
}
//version bump the root project if there were changes to any of the themes
2024-06-05 14:53:32 +00:00
const rootHasVersionBump = await checkProjectForVersionBump ( hash ) ;
2022-05-27 14:12:42 +00:00
if ( versionBumpCount > 0 && ! rootHasVersionBump ) {
2021-12-15 18:04:22 +00:00
await executeCommand ( ` npm version patch --no-git-tag-version && git add package.json package-lock.json ` ) ;
changesWereMade = true ;
2021-09-29 15:01:05 +00:00
}
2021-12-15 18:04:22 +00:00
return changesWereMade ;
2021-09-29 15:01:05 +00:00
}
2022-08-11 16:30:00 +00:00
export function getThemeMetadata ( styleCss , attribute , trimWPCom = true ) {
2022-05-27 14:12:42 +00:00
if ( ! styleCss || ! attribute ) {
2021-11-12 13:50:34 +00:00
return null ;
}
2022-05-27 14:12:42 +00:00
switch ( attribute ) {
2021-11-12 13:50:34 +00:00
case 'Version' :
2022-08-11 16:30:00 +00:00
const version = styleCss
2021-11-12 13:50:34 +00:00
. match ( / ( ? < = V e r s i o n : \ s * ) . * ? ( ? = \ s * \ r ? \ n | \ r g ) / g s ) [ 0 ]
2022-08-11 16:30:00 +00:00
. trim ( ) ;
return trimWPCom ? version . replace ( '-wpcom' , '' ) : version ;
2021-12-20 15:18:46 +00:00
case 'Requires at least' :
2021-11-12 13:50:34 +00:00
return styleCss
2021-12-20 15:18:46 +00:00
. match ( / ( ? < = R e q u i r e s a t l e a s t : \ s * ) . * ? ( ? = \ s * \ r ? \ n | \ r g ) / g s ) ;
2021-11-12 13:50:34 +00:00
}
}
2022-04-21 14:23:35 +00:00
/* Rebuild theme changelog from a given starting hash */
async function rebuildThemeChangelog ( theme , since ) {
console . log ( ` Rebuilding ${ theme } changelog since ${ since || 'forever' } ` ) ;
if ( since ) {
since = ` ${ since } ..HEAD ` ;
} else {
since = 'HEAD' ;
}
let hashes = await executeCommand ( ` git rev-list ${ since } -- ./ ${ theme } ` ) ;
hashes = hashes . split ( '\n' ) ;
let logs = '== Changelog ==\n' ;
2022-05-27 14:12:42 +00:00
for ( let hash of hashes ) {
2022-04-21 14:23:35 +00:00
let log = await executeCommand ( ` git log -n 1 --pretty=format:"* %s" ${ hash } ` ) ;
2022-05-27 14:12:42 +00:00
if ( log . includes ( 'Version Bump' ) ) {
2022-04-21 14:23:35 +00:00
let previousStyleString = await executeCommand ( ` git show ${ hash } : ${ theme } /style.css 2>/dev/null ` ) ;
let version = getThemeMetadata ( previousStyleString , 'Version' ) ;
logs += ` \n = ${ version } = \n ` ;
} else {
// Remove any double quotes from commit messages
log = log . replace ( /"/g , '' ) ;
logs += log + '\n' ;
}
}
// Get theme readme.txt
2022-04-26 16:21:47 +00:00
let readmeFilePath = ` ${ theme } /readme.txt ` ;
2022-04-21 14:23:35 +00:00
// Update readme.txt
2022-05-27 14:12:42 +00:00
fs . readFile ( readmeFilePath , 'utf8' , function ( err , data ) {
2022-04-21 14:23:35 +00:00
let changelogSection = '== Changelog ==' ;
let regex = new RegExp ( '^.*' + changelogSection + '.*$' , 'gm' ) ;
let formattedChangelog = data . replace ( regex , logs ) ;
2022-05-27 14:12:42 +00:00
fs . writeFile ( readmeFilePath , formattedChangelog , 'utf8' , function ( err ) {
2022-04-21 14:23:35 +00:00
if ( err ) return console . log ( err ) ;
} ) ;
} ) ;
}
2022-04-20 18:28:17 +00:00
/ *
Update theme changelog using current commit logs .
Used by versionBumpThemes to update each theme changelog .
* /
async function updateThemeChangelog ( theme , addChanges ) {
console . log ( ` Updating ${ theme } changelog ` ) ;
// Get theme version
2022-05-27 14:12:42 +00:00
let styleCss = fs . readFileSync ( ` ${ theme } /style.css ` , 'utf8' ) ;
let version = getThemeMetadata ( styleCss , 'Version' ) ;
2022-04-20 18:28:17 +00:00
// Get list of updates with bullet points
2024-06-05 14:53:32 +00:00
const lastestTagHash = execSync ( 'git describe --tags --abbrev=0' ) . toString ( ) . trim ( ) ;
let logs = await getCommitLogs ( lastestTagHash , true , theme ) ;
2022-04-20 18:28:17 +00:00
// Get theme readme.txt
2022-04-26 16:21:47 +00:00
let readmeFilePath = ` ${ theme } /readme.txt ` ;
if ( ! existsSync ( readmeFilePath ) ) {
console . log ( ` Unable to find a readme.txt for ${ theme } . ` ) ;
return ;
}
2022-04-20 18:28:17 +00:00
// Build changelog entry
let newChangelogEntry = ` == Changelog ==
= $ { version } =
$ { logs } ` ;
// Update readme.txt
2022-05-27 14:12:42 +00:00
fs . readFile ( readmeFilePath , 'utf8' , function ( err , data ) {
2022-04-20 18:28:17 +00:00
let changelogSection = '== Changelog ==' ;
let regex = new RegExp ( '^.*' + changelogSection + '.*$' , 'gm' ) ;
let formattedChangelog = data . replace ( regex , newChangelogEntry ) ;
2022-05-27 14:12:42 +00:00
fs . writeFile ( readmeFilePath , formattedChangelog , 'utf8' , function ( err ) {
2022-04-20 18:28:17 +00:00
if ( err ) return console . log ( err ) ;
} ) ;
} ) ;
// Stage readme.txt
if ( addChanges ) {
2022-04-26 16:21:47 +00:00
await executeCommand ( ` git add ${ readmeFilePath } ` ) ;
2022-04-20 18:28:17 +00:00
}
}
2021-11-12 13:50:34 +00:00
2021-09-29 15:01:05 +00:00
/ *
Version Bump a Theme .
Used by versionBumpThemes to do the work of version bumping .
2021-11-12 13:50:34 +00:00
First increment the patch version in style . css
Then update any of these files with the new version : [ package . json , style . scss , style - child - theme . scss ]
2021-09-29 15:01:05 +00:00
* /
2022-05-27 14:12:42 +00:00
async function versionBumpTheme ( theme , addChanges ) {
2021-11-12 13:50:34 +00:00
2021-09-29 15:01:05 +00:00
console . log ( ` ${ theme } needs a version bump ` ) ;
2021-11-12 13:50:34 +00:00
await executeCommand ( ` perl -pi -e 's/Version: (( \\ d+ \\ .)*)( \\ d+)(.*) $ /"Version: ". $ 1.( $ 3+1). $ 4/ge' ${ theme } /style.css ` , true ) ;
2021-12-16 19:06:38 +00:00
await executeCommand ( ` git add ${ theme } /style.css ` ) ;
2021-11-12 13:50:34 +00:00
let styleCss = fs . readFileSync ( ` ${ theme } /style.css ` , 'utf8' ) ;
2022-08-11 16:30:00 +00:00
let currentVersion = getThemeMetadata ( styleCss , 'Version' , false ) ;
2021-11-12 13:50:34 +00:00
2022-08-11 16:30:00 +00:00
let filesToUpdate = await executeCommand ( ` find ${ theme } -not \\ ( -path "*/node_modules/*" -prune \\ ) -and \\ ( -name package.json -or -name style.scss -or -name style-rtl.css -or -name style-child-theme.scss \\ ) -maxdepth 3 ` ) ;
2021-11-12 13:50:34 +00:00
filesToUpdate = filesToUpdate . split ( '\n' ) . filter ( item => item != '' ) ;
2022-05-27 14:12:42 +00:00
for ( let file of filesToUpdate ) {
2022-08-09 17:41:14 +00:00
const isPackageJson = file === ` ${ theme } /package.json ` ;
if ( isPackageJson ) {
// update theme/package.json and package-lock.json
2024-03-22 10:17:48 +00:00
await executeCommand ( ` npm version ${ currentVersion . replace ( '-wpcom' , '' ) } --workspace= ${ theme } ` ) ;
2022-08-09 17:41:14 +00:00
} else {
await executeCommand ( ` perl -pi -e 's/Version: (.*) $ /"Version: ' ${ currentVersion } '"/ge' ${ file } ` ) ;
}
2022-05-27 14:12:42 +00:00
if ( addChanges ) {
2021-12-15 18:04:22 +00:00
await executeCommand ( ` git add ${ file } ` ) ;
}
2021-09-29 15:01:05 +00:00
}
}
/ *
Determine if a theme has had a version bump since a given hash .
Used by versionBumpThemes
2021-11-12 13:50:34 +00:00
Compares the value of 'version' in style . css between the hash and current value
2021-09-29 15:01:05 +00:00
* /
2022-05-27 14:12:42 +00:00
async function checkThemeForVersionBump ( theme , hash ) {
2021-11-12 13:50:34 +00:00
return executeCommand ( `
git show $ { hash } : $ { theme } / style . css 2 > / d e v / n u l l
` )
2022-05-27 14:12:42 +00:00
. catch ( ( error ) => {
//This is a new theme, no need to bump versions so we'll just say we've already done it
return true ;
} )
. then ( ( previousStyleString ) => {
if ( previousStyleString === true ) {
return previousStyleString ;
}
let previousVersion = getThemeMetadata ( previousStyleString , 'Version' ) ;
let styleCss = fs . readFileSync ( ` ${ theme } /style.css ` , 'utf8' ) ;
let currentVersion = getThemeMetadata ( styleCss , 'Version' ) ;
return previousVersion != currentVersion ;
} ) ;
2021-09-29 15:01:05 +00:00
}
2021-12-15 18:04:22 +00:00
/ *
Determine if the project has had a version bump since a given hash .
Used by versionBumpThemes
Compares the value of 'version' in package . json between the hash and current value
* /
2022-05-27 14:12:42 +00:00
async function checkProjectForVersionBump ( hash ) {
2021-12-15 18:04:22 +00:00
let previousPackageString = await executeCommand ( `
git show $ { hash } : . / package . json 2 > / d e v / n u l l
` );
let previousPackage = JSON . parse ( previousPackageString ) ;
let currentPackage = JSON . parse ( fs . readFileSync ( ` ./package.json ` ) )
return previousPackage . version != currentPackage . version ;
}
2021-09-29 15:01:05 +00:00
/ *
Determine if a theme has had changes since a given hash .
Used by versionBumpThemes
2021-10-01 14:42:39 +00:00
* /
2022-05-27 14:12:42 +00:00
async function checkThemeForChanges ( theme , hash ) {
2021-09-29 15:01:05 +00:00
let comittedChanges = await executeCommand ( ` git diff --name-only ${ hash } HEAD -- ${ theme } ` ) ;
2022-02-01 16:36:16 +00:00
return comittedChanges != '' ;
2021-09-29 15:01:05 +00:00
}
/ *
2021-11-12 13:50:34 +00:00
Provide a list of 'actionable' themes ( those themes that have style . css files )
2021-09-29 15:01:05 +00:00
* /
async function getActionableThemes ( ) {
2021-12-14 16:45:07 +00:00
let result = await executeCommand ( ` for d in */; do
2021-11-12 13:50:34 +00:00
if test - f "./$d/style.css" ; then
2021-12-14 16:45:07 +00:00
echo $d ;
2021-11-12 13:50:34 +00:00
fi
done ` );
return result
. split ( '\n' )
2022-05-27 14:12:42 +00:00
. map ( item => item . replace ( '/' , '' ) ) ;
2021-09-29 15:01:05 +00:00
}
/ *
Clean the theme sandbox .
2022-02-01 14:26:32 +00:00
checkout origin / trunk and ensure it ' s up - to - date .
2021-09-29 15:01:05 +00:00
Remove any other changes .
* /
2022-02-02 09:53:54 +00:00
async function cleanSandbox ( ) {
2021-09-29 15:01:05 +00:00
console . log ( 'Cleaning the Themes Sandbox' ) ;
2021-10-01 14:42:39 +00:00
await executeOnSandbox ( `
2021-09-29 15:01:05 +00:00
cd $ { sandboxPublicThemesFolder } ;
git reset -- hard HEAD ;
git clean - fd ;
2022-02-01 14:26:32 +00:00
git checkout trunk ;
2021-09-29 15:01:05 +00:00
git pull ;
echo ;
git status
2021-10-01 14:42:39 +00:00
` , true);
2021-09-29 15:01:05 +00:00
console . log ( 'All done cleaning.' ) ;
}
/ *
2022-02-02 09:53:54 +00:00
Push exactly what is here ( all files ) up to the sandbox ( with the exclusion of files noted in . sandbox - ignore )
2021-09-29 15:01:05 +00:00
* /
2022-02-02 09:53:54 +00:00
async function pushToSandbox ( ) {
console . log ( "Pushing All Themes to Sandbox." ) ;
let allThemes = await getActionableThemes ( ) ;
console . log ( ` Syncing ${ allThemes . length } themes ` ) ;
2022-05-27 14:12:42 +00:00
for ( let theme of allThemes ) {
2022-02-02 09:53:54 +00:00
await pushThemeToSandbox ( theme ) ;
}
2021-09-29 15:01:05 +00:00
}
2022-02-02 09:53:54 +00:00
async function pushThemeToSandbox ( theme ) {
2022-05-27 14:12:42 +00:00
console . log ( ` Syncing ${ theme } ` ) ;
2022-02-02 09:53:54 +00:00
return executeCommand ( `
rsync - avR -- no - p -- no - times -- delete - m -- exclude - from = '.sandbox-ignore' . / $ { theme } / wpcom - sandbox : $ { sandboxPublicThemesFolder } /
2021-10-01 14:42:39 +00:00
` , true);
2021-09-29 15:01:05 +00:00
}
/ *
Push only ( and every ) change since the point - of - diversion from / trunk
Remove files from the sandbox that have been removed since the last deployed hash
* /
async function pushChangesToSandbox ( ) {
2022-02-02 09:53:54 +00:00
console . log ( "Pushing Changed Themes to Sandbox." ) ;
2021-09-29 15:01:05 +00:00
let hash = await getLastDeployedHash ( ) ;
2022-01-14 17:26:13 +00:00
let changedThemes = await getChangedThemes ( hash ) ;
console . log ( ` Syncing ${ changedThemes . length } themes ` ) ;
2021-09-29 15:01:05 +00:00
2022-05-27 14:12:42 +00:00
for ( let theme of changedThemes ) {
2022-02-02 21:37:17 +00:00
await pushThemeToSandbox ( theme ) ;
2022-01-13 20:38:55 +00:00
}
2021-09-29 15:01:05 +00:00
}
2022-07-28 13:14:29 +00:00
async function checkoutCoreTheme ( theme ) {
if ( ! theme ) {
console . log ( 'Must supply theme to sync and revision to start from' ) ;
return ;
}
return executeCommand ( `
rm - rf . / $ { theme }
svn checkout https : //wpcom-themes.svn.automattic.com/${theme} ./${theme}
` );
}
2024-01-09 21:27:59 +00:00
async function pullAllThemes ( ) {
console . log ( "Pulling ALL themes from sandbox." ) ;
let allThemes = await getActionableThemes ( ) ;
for ( let theme of allThemes ) {
try {
await executeCommand ( `
rsync - avr -- no - p -- no - times -- delete - m -- exclude - from = '.sandbox-ignore' wpcom - sandbox : $ { sandboxPublicThemesFolder } / $ { theme } / . / $ { theme } /
` , true);
}
catch ( err ) {
console . log ( 'Error pulling:' , err ) ;
}
}
}
2022-02-21 20:52:07 +00:00
async function pullCoreThemes ( ) {
console . log ( "Pulling CORE themes from sandbox." ) ;
2022-05-27 14:12:42 +00:00
for ( let theme of coreThemes ) {
2022-02-21 20:52:07 +00:00
await executeCommand ( `
rsync - avr -- no - p -- no - times -- delete - m -- exclude - from = '.sandbox-ignore' wpcom - sandbox : $ { sandboxPublicThemesFolder } / $ { theme } / . / $ { theme } /
` , true);
}
}
async function pushCoreThemes ( ) {
console . log ( "Pushing CORE themes to sandbox." ) ;
2022-05-27 14:12:42 +00:00
for ( let theme of coreThemes ) {
2022-02-21 20:52:07 +00:00
await executeCommand ( `
rsync - avr -- no - p -- no - times -- delete - m -- exclude - from = '.sandbox-ignore' . / $ { theme } / wpcom - sandbox : $ { sandboxPublicThemesFolder } / $ { theme } /
` , true);
}
}
async function syncCoreTheme ( theme , sinceRevision ) {
2022-05-27 14:12:42 +00:00
if ( ! theme ) {
2022-02-21 20:52:07 +00:00
console . log ( 'Must supply theme to sync and revision to start from' ) ;
return ;
}
2022-05-27 14:12:42 +00:00
if ( ! sinceRevision ) {
2022-07-28 13:14:29 +00:00
sinceRevision = await executeCommand ( ` cat ./ ${ theme } /.pub-svn-revision ` ) ;
2022-02-21 20:52:07 +00:00
}
2022-07-28 13:14:29 +00:00
let latestRevision = await executeCommand ( ` svn info -r HEAD https://develop.svn.wordpress.org/trunk | grep Revision | egrep -o "[0-9]+" ` ) ;
console . log ( ` syncing core theme ${ theme } from ${ sinceRevision } to ${ latestRevision } ` ) ;
2022-02-21 20:52:07 +00:00
try {
2022-07-28 13:14:29 +00:00
await executeCommand ( `
svn merge -- accept postpone http : //develop.svn.wordpress.org/trunk/src/wp-content/themes/${theme} ./${theme} -r${sinceRevision}:HEAD
echo '${latestRevision}' > . / $ { theme } / . pub - svn - revision
2022-02-21 20:52:07 +00:00
` , true);
}
catch ( err ) {
console . log ( 'Error merging:' , err ) ;
}
return latestRevision ;
}
2021-09-29 15:01:05 +00:00
/ *
Build the Phabricator commit message .
This message contains the logs from all of the commits since the given hash .
Used by create * PhabricatorDiff
* /
2022-05-27 14:12:42 +00:00
async function buildPhabricatorCommitMessageSince ( hash ) {
2021-09-29 15:01:05 +00:00
let projectVersion = await executeCommand ( ` node -p "require('./package.json').version" ` ) ;
2022-04-20 18:28:17 +00:00
let logs = await getCommitLogs ( hash ) ;
2021-09-29 15:01:05 +00:00
return ` Deploy Themes ${ projectVersion } to wpcom
2021-10-01 14:42:39 +00:00
2021-09-29 15:01:05 +00:00
Summary :
$ { logs }
Test Plan : Execute Smoke Test
2021-10-01 14:42:39 +00:00
2021-09-29 15:01:05 +00:00
Reviewers :
2021-10-01 14:42:39 +00:00
2021-09-29 15:01:05 +00:00
Subscribers :
` ;
}
/ *
2022-02-21 20:52:07 +00:00
Create a Phabricator diff with the given message based on the contents currently in the sandbox .
2021-09-29 15:01:05 +00:00
Open the phabricator diff in your browser .
Provide the URL of the phabricator diff .
* /
2022-02-21 20:52:07 +00:00
async function createPhabricatorDiff ( commitMessage ) {
2021-09-29 15:01:05 +00:00
console . log ( 'creating Phabricator Diff' ) ;
let result = await executeOnSandbox ( `
cd $ { sandboxPublicThemesFolder } ;
git branch - D deploy
git checkout - b deploy
git add -- all
2021-11-12 16:23:31 +00:00
git commit - m "${commitMessage}"
2021-09-29 15:01:05 +00:00
arc diff -- create -- verbatim
2021-10-01 14:42:39 +00:00
` , true);
2021-09-29 15:01:05 +00:00
let phabricatorUrl = getPhabricatorUrlFromResponse ( result ) ;
console . log ( 'Diff Created at: ' , phabricatorUrl ) ;
2022-05-27 14:12:42 +00:00
if ( phabricatorUrl ) {
2021-09-29 15:01:05 +00:00
open ( phabricatorUrl ) ;
}
return phabricatorUrl ;
}
/ *
Utility to pull the Phabricator URL from the diff creation command .
2022-02-02 09:53:54 +00:00
Used by createPhabricatorDiff
2021-09-29 15:01:05 +00:00
* /
2022-05-27 14:12:42 +00:00
function getPhabricatorUrlFromResponse ( response ) {
2021-10-01 14:42:39 +00:00
return response
2021-09-29 15:01:05 +00:00
? . split ( '\n' )
2022-05-27 14:12:42 +00:00
? . find ( item => {
2021-09-29 15:01:05 +00:00
return item . includes ( 'Revision URI: ' ) ;
} )
? . split ( "Revision URI: " ) [ 1 ] ;
}
/ *
Create a git tag at the current hash .
In the description include the commit logs since the given hash .
Include the ( cleansed ) Phabricator link .
* /
2022-05-27 14:12:42 +00:00
async function tagDeployment ( options = { } ) {
2021-09-29 15:01:05 +00:00
2021-12-15 18:04:22 +00:00
console . log ( 'tagging deployment' ) ;
2021-09-29 15:01:05 +00:00
let hash = options . hash || await getLastDeployedHash ( ) ;
let workInTheOpenPhabricatorUrl = '' ;
2021-10-01 14:42:39 +00:00
if ( options . diffId ) {
2021-10-06 12:48:12 +00:00
workInTheOpenPhabricatorUrl = ` Phabricator: ${ options . diffId } -code ` ;
2021-09-29 15:01:05 +00:00
}
let projectVersion = await executeCommand ( ` node -p "require('./package.json').version" ` ) ;
2022-04-20 18:28:17 +00:00
let logs = await getCommitLogs ( hash ) ;
2021-09-29 15:01:05 +00:00
let tag = ` v ${ projectVersion } ` ;
let message = ` Deploy Themes ${ tag } to wpcom. \n \n ${ logs } \n \n ${ workInTheOpenPhabricatorUrl } ` ;
await executeCommand ( `
git tag - a $ { tag } - m "${message}"
git push origin $ { tag }
2021-12-15 18:04:22 +00:00
` , true);
2021-09-29 15:01:05 +00:00
}
2023-04-27 08:03:00 +00:00
async function createGlotPressProject ( changedThemes ) {
for ( const themeSlug of changedThemes ) {
let styleCss = fs . readFileSync ( ` ${ themeSlug } /style.css ` , 'utf8' ) ;
let themeVersion = getThemeMetadata ( styleCss , 'Version' ) ;
2024-02-20 20:21:19 +00:00
// Check if theme version is correctly formatted. Temporarily coerce the value to a parseable semver version if it's not.
if ( ! semver . valid ( themeVersion ) ) {
console . log ( ` \n [WARN] Invalid version in style.css for ${ themeSlug } : ${ themeVersion } \n ` ) ;
themeVersion = semver . coerce ( themeVersion ) ;
}
2023-04-27 08:03:00 +00:00
if ( semver . gte ( themeVersion , '1.0.0' ) ) {
console . log ( ` \n Creating GlotPress project for ${ themeSlug } \n ` ) ;
await executeOnSandbox ( `
cd $ { sandboxPublicThemesFolder } ;
php $ { glotPressScript } $ { sandboxPublicThemesFolder } / $ { themeSlug } ;
` , true);
} else {
console . log ( ` \n Skipped GlotPress project creation for ${ themeSlug } . \n Version bump to a major release to create GlotPress project. \n ` ) ;
}
}
}
2021-09-29 15:01:05 +00:00
/ *
Execute a command on the sandbox .
Expects the following to be configured in your ~ / . s s h / c o n f i g f i l e :
Host wpcom - sandbox
User wpdev
HostName SANDBOXURL . wordpress . com
ForwardAgent yes
* /
2022-05-27 14:12:42 +00:00
function executeOnSandbox ( command , logResponse , enablePsudoterminal ) {
2021-10-26 15:22:14 +00:00
2022-05-27 14:12:42 +00:00
if ( enablePsudoterminal ) {
2021-10-26 15:22:14 +00:00
return executeCommand ( ` ssh -tt -A ${ remoteSSH } << EOF
$ { command }
EOF ` , logResponse);
}
2021-10-01 14:42:39 +00:00
return executeCommand ( ` ssh -TA ${ remoteSSH } << EOF
$ { command }
EOF ` , logResponse);
2021-09-29 15:01:05 +00:00
}
/ *
Execute a command locally .
* /
2022-01-31 16:37:12 +00:00
export async function executeCommand ( command , logResponse ) {
2022-08-09 17:41:14 +00:00
const timeout = 2 * 60 * 1000 ; // 2 min
2021-09-29 15:01:05 +00:00
2022-08-09 17:41:14 +00:00
return new Promise ( ( resolove , reject ) => {
2021-09-29 15:01:05 +00:00
let child ;
let response = '' ;
let errResponse = '' ;
if ( isWin ) {
child = spawn ( 'cmd.exe' , [ '/s' , '/c' , '"' + command + '"' ] , {
windowsVerbatimArguments : true ,
stdio : [ process . stdin , 'pipe' , 'pipe' ] ,
2022-08-09 17:41:14 +00:00
detached : true ,
2021-09-29 15:01:05 +00:00
} )
} else {
2023-10-31 20:03:39 +00:00
/ *
* Determines the shell to execute the command .
* - Prioritizes using the user 's default shell unless it' s fish , a known problematic shell .
* - In this case , falls back to ` /bin/bash ` for better syntax compatibility .
* /
let shellPath = process . env . SHELL || '/bin/bash' ;
if ( shellPath . includes ( 'fish' ) && fs . existsSync ( '/bin/bash' ) ) {
shellPath = '/bin/bash' ;
}
child = spawn ( shellPath , [ '-c' , command ] , {
2022-02-01 14:26:32 +00:00
stdio : [ process . stdin , 'pipe' , 'pipe' ] ,
2022-08-09 17:41:14 +00:00
detached : true ,
2022-02-01 14:26:32 +00:00
} ) ;
2021-09-29 15:01:05 +00:00
}
2022-08-09 17:41:14 +00:00
var timer = setTimeout ( ( ) => {
try {
process . kill ( - child . pid , 'SIGKILL' ) ;
} catch ( e ) {
console . log ( 'Cannot kill process' ) ;
}
} , timeout ) ;
2021-09-29 15:01:05 +00:00
child . stdout . on ( 'data' , ( data ) => {
response += data ;
2022-05-27 14:12:42 +00:00
if ( logResponse ) {
2021-10-01 14:42:39 +00:00
console . log ( data . toString ( ) ) ;
}
2021-09-29 15:01:05 +00:00
} ) ;
child . stderr . on ( 'data' , ( data ) => {
errResponse += data ;
2022-05-27 14:12:42 +00:00
if ( logResponse ) {
2021-10-01 14:42:39 +00:00
console . log ( data . toString ( ) ) ;
}
2021-09-29 15:01:05 +00:00
} ) ;
child . on ( 'exit' , ( code ) => {
2022-08-09 17:41:14 +00:00
clearTimeout ( timer )
2021-09-29 15:01:05 +00:00
if ( code !== 0 ) {
reject ( errResponse . trim ( ) ) ;
}
resolove ( response . trim ( ) ) ;
} ) ;
} ) ;
}
2022-06-16 06:25:35 +00:00
async function escapePatterns ( ) {
// get staged files
const staged = ( await executeCommand ( ` git diff --cached --name-only ` ) ) . split ( '\n' ) ;
// get unstaged, untracked files
const unstaged = ( await executeCommand ( ` git ls-files -m -o --exclude-standard ` ) ) . split ( '\n' ) ;
// avoid duplicates and filter pattern files
const patterns = [ ... new Set ( [ ... staged , ... unstaged ] ) ] . filter ( file => file . match ( /.*\/patterns\/.*.php/g ) ) ;
// arrange patterns by theme
const themePatterns = patterns . reduce ( ( acc , file ) => {
const themeSlug = file . split ( '/' ) . shift ( ) ;
return {
... acc ,
[ themeSlug ] : ( acc [ themeSlug ] || [ ] ) . concat ( file )
} ;
} , { } ) ;
Object . entries ( themePatterns ) . forEach ( async ( [ themeSlug , patterns ] ) => {
console . log ( getPatternTable ( themeSlug , patterns ) ) ;
const prompt = await inquirer . prompt ( [ {
type : 'input' ,
message : 'Verify the theme slug' ,
name : "themeSlug" ,
default : themeSlug
} ] ) ;
if ( ! prompt . themeSlug ) {
return ;
}
patterns . forEach ( file => {
2022-12-22 14:02:27 +00:00
const rewriter = getReWriter ( prompt . themeSlug ) ;
2022-06-16 06:25:35 +00:00
const tmpFile = ` ${ file } -tmp ` ;
2022-12-22 14:02:27 +00:00
const readStream = fs . createReadStream ( file , { encoding : 'UTF-8' } ) ;
const writeStream = fs . createWriteStream ( tmpFile , { encoding : 'UTF-8' } ) ;
writeStream . on ( 'finish' , ( ) => {
2022-06-16 06:25:35 +00:00
fs . renameSync ( tmpFile , file ) ;
} ) ;
2022-12-22 14:02:27 +00:00
readStream . pipe ( rewriter ) . pipe ( writeStream ) ;
2022-06-16 06:25:35 +00:00
} ) ;
} ) ;
// Helper functions
function getReWriter ( themeSlug ) {
const rewriter = new RewritingStream ( ) ;
rewriter . on ( 'text' , ( _ , raw ) => {
rewriter . emitRaw ( escapeText ( raw , themeSlug ) ) ;
} ) ;
rewriter . on ( 'startTag' , ( startTag , rawHtml ) => {
if ( startTag . tagName === 'img' ) {
const attrs = startTag . attrs . filter ( attr => [ 'src' , 'alt' ] . includes ( attr . name ) ) ;
attrs . forEach ( attr => {
if ( attr . name === 'src' ) {
attr . value = escapeImagePath ( attr . value ) ;
} else if ( attr . name === 'alt' ) {
attr . value = escapeText ( attr . value , themeSlug , true ) ;
}
} ) ;
}
rewriter . emitStartTag ( startTag ) ;
} ) ;
2022-07-27 04:30:38 +00:00
rewriter . on ( 'comment' , ( comment , rawHtml ) => {
if ( comment . text . startsWith ( '?php' ) ) {
rewriter . emitRaw ( rawHtml ) ;
return ;
}
// escape the strings in block config (blocks that are represented as comments)
// ex: <!-- wp:search {label: "Search"} /-->
const block = escapeBlockAttrs ( comment . text , themeSlug )
rewriter . emitComment ( { ... comment , text : block } )
} ) ;
2022-06-16 06:25:35 +00:00
return rewriter ;
}
2022-07-27 04:30:38 +00:00
function escapeBlockAttrs ( block , themeSlug ) {
// Set isAttr to true if it is an attribute in the result HTML
// If set to true, it generates esc_attr_, otherwise it generates esc_html_
const allowedAttrs = [
{ name : 'label' } ,
{ name : 'placeholder' , isAttr : true } ,
{ name : 'buttonText' } ,
{ name : 'content' }
] ;
const start = block . indexOf ( '{' ) ;
const end = block . lastIndexOf ( '}' ) ;
const configPrefix = block . slice ( 0 , start ) ;
const config = block . slice ( start , end + 1 ) ;
const configSuffix = block . slice ( end + 1 ) ;
try {
const configJson = JSON . parse ( config ) ;
allowedAttrs . forEach ( ( attr ) => {
if ( ! configJson [ attr . name ] ) return ;
configJson [ attr . name ] = escapeText ( configJson [ attr . name ] , themeSlug , attr . isAttr )
} )
return configPrefix + JSON . stringify ( configJson ) + configSuffix ;
} catch ( error ) {
// do nothing
return block
}
}
2022-06-16 06:25:35 +00:00
function escapeText ( text , themeSlug , isAttr = false ) {
const trimmedText = text && text . trim ( ) ;
if ( ! themeSlug || ! trimmedText || trimmedText . startsWith ( ` <?php ` ) ) return text ;
const escFunction = isAttr ? 'esc_attr__' : 'esc_html__' ;
const spaceChar = text . startsWith ( ' ' ) ? ' ' : ''
const resultText = text . replace ( '\'' , '\\\'' ) . trim ( ) ;
return ` ${ spaceChar } <?php echo ${ escFunction } ( ' ${ resultText } ', ' ${ themeSlug } ' ); ?> ` ;
}
function escapeImagePath ( src ) {
if ( ! src || src . trim ( ) . startsWith ( '<?php' ) ) return src ;
const assetsDir = 'assets' ;
const parts = src . split ( '/' ) ;
const resultSrc = parts . slice ( parts . indexOf ( assetsDir ) ) . join ( '/' ) ;
return ` <?php echo esc_url( get_template_directory_uri() ); ?>/ ${ resultSrc } ` ;
}
function getPatternTable ( themeSlug , patterns ) {
const tableConfig = {
columnDefault : {
2022-12-22 14:02:27 +00:00
width : 40 ,
2022-06-16 06:25:35 +00:00
} ,
header : {
alignment : 'center' ,
content : ` THEME: ${ themeSlug } \n \n Following patterns may get updated with escaped strings and/or image paths ` ,
}
} ;
2022-12-22 14:02:27 +00:00
return table ( patterns . map ( p => [ p ] ) , tableConfig ) ;
2022-06-16 06:25:35 +00:00
}
2022-08-09 17:41:14 +00:00
}
function startProgress ( length ) {
let current = 0 ;
function render ( ) {
const [ progress , percentage ] = progressbar . filledBar ( length , current ) ;
console . log ( '\nProgress:' , [ progress , Math . round ( percentage * 100 ) / 100 ] , ` ${ current } / ${ length } \n ` ) ;
}
render ( ) ;
return {
increment ( ) {
current ++ ;
render ( ) ;
}
} ;
}