import process from 'process'; import { execFile } from 'child_process'; import 'dotenv/config'; import { validateThemes } from './validate-themes.mjs'; import { escapePatterns } from './escape-patterns.mjs'; import { checkStrictTypesToChangedThemes } from './check-strict-types.mjs'; import { pullCoreThemes, pushCoreThemes, syncCoreTheme, checkoutCoreTheme, deploySyncCoreTheme, createCoreGithubPR, } from './core-theme-utils.mjs'; import { pushButtonDeploy, deployPreview, deployThemes, versionBumpThemes, versionBumpThemesFromLastBump, rebuildThemeChangelog, pushToSandbox, pullAllThemes, cleanSandbox, pushChangesToSandbox, pushThemeToSandbox, updateThemeChangelog, } from './deploy-utils.mjs'; import { versionBumpInPr } from './version-bump.mjs'; const commands = { 'push-button-deploy': { helpText: ` * Version bumps all changed themes since the last deployment * Cleans and updates the sandbox * Pushes all changed files (including removing deleted files) since the last deployment * After pausing to allow testing, commits the version bump change to github.a8c.com * Creates a tag in the github.a8c.com repository to mark the deployment Flags: --skip-strict-types Skip the strict types check for PHP files `, run: pushButtonDeploy, }, 'test-push-button-deploy': { helpText: 'Test the push button deploy command. Version bumps and changelog updates will be committed to the current branch. No changes will be deployed.', run: ( args ) => pushButtonDeploy( args?.[ 1 ] ), }, 'audit-dependencies': { helpText: 'Audits and fixes dependencies for all the themes.', run: () => runShellScript( '.theme-utils/theme-batch-utils.sh', 'audit-dependencies' ), }, 'build-all': { helpText: 'Builds all the themes (runs `npm run build` on each of them).', run: () => runShellScript( '.theme-utils/theme-batch-utils.sh', 'build-all' ), }, 'build-blockbase-children': { helpText: 'Builds all blockbase children (runs `npm run build` on each of them).', run: () => runShellScript( '.theme-utils/theme-batch-utils.sh', 'build-blockbase-children' ), }, 'install-dependencies': { helpText: 'Install node dependencies for all the themes.', run: () => runShellScript( '.theme-utils/theme-batch-utils.sh', 'install-dependencies' ), }, 'update-dependencies': { helpText: 'Update node dependencies for all themes.', run: () => runShellScript( '.theme-utils/theme-batch-utils.sh', 'update-dependencies' ), }, 'clean-sandbox': { helpText: 'Perform a hard reset, checkout trunk, and pull wpcom-themes 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: '', 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. Optionally specify a single theme to version bump.', run: ( args ) => versionBumpThemes( args?.[ 1 ]?.split( /[ ,]+/ ) ), }, 'version-bump-from-last-bump': { helpText: 'Bump the version of any theme that has had changes since the last "Version Bump" commit. This includes updating the changelog for the theme. Optionally specify a single theme to version bump.', run: ( args ) => versionBumpThemesFromLastBump( args?.[ 1 ]?.split( /[ ,]+/ ) ), }, 'version-bump-in-pr': { helpText: 'Version bump and update the changelog for all changed themes in a PR.', run: ( args ) => versionBumpInPr( args?.[ 1 ] ), }, 'deploy-preview': { helpText: 'Display a list of the changes to be deployed.', run: deployPreview, }, 'deploy-theme': { helpText: 'This runs "deploy pub " on the provided list of themes.', additionalArgs: '', run: ( args ) => deployThemes( args?.[ 1 ].split( /[ ,]+/ ) ), }, 'check-strict-typing': { helpText: 'Checks if all PHP files in changed themes have strict types declarations. Lists any files missing the declaration.', run: () => checkStrictTypesToChangedThemes(), }, 'checkout-core-theme': { helpText: 'Use SVN to checkout the given core themes from the wpcom SVN repository.', additionalArgs: '', run: ( args ) => checkoutCoreTheme( args?.[ 1 ] ), }, 'package-dotorg': { helpText: 'Package the specified theme for the dotorg repository.', additionalArgs: '', run: ( args ) => runShellScript( '.theme-utils/package-dotorg.sh', args?.[ 1 ] ), }, 'pull-all-themes': { helpText: 'Use rsync to copy all public theme files from your sandbox to your local machine.', run: pullAllThemes, }, 'pull-core-themes': { helpText: 'Use rsync to copy all public Core theme files from your sandbox to your local machine. Core themes are any of the Twenty themes.', run: pullCoreThemes, }, 'push-core-themes': { helpText: 'Use rsync to copy all public Core theme files from your local machine to your sandbox. Core themes are any of the Twenty themes.', run: pushCoreThemes, }, 'sync-core-theme': { 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.', additionalArgs: ' ', 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: ' ', run: ( args ) => deploySyncCoreTheme( args?.[ 1 ], args?.[ 2 ] ), }, 'create-core-github-pr': { helpText: 'Given a theme slug and specific revision create a GitHub pull request from the resources currently on the sandbox.', additionalArgs: ' ', run: ( args ) => createCoreGithubPR( args?.[ 1 ], args?.[ 2 ] ), }, '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: ' ', run: ( args ) => updateThemeChangelog( args?.[ 1 ], false, args?.[ 2 ] ), }, 'rebuild-theme-changelog': { helpText: 'Rebuild the entire changelog from the given starting hash.', additionalArgs: ' ', run: ( args ) => rebuildThemeChangelog( args?.[ 1 ], args?.[ 2 ] ), }, 'escape-patterns': { helpText: 'Escapes block patterns for pattern files that have changes (staged or unstaged).', additionalArgs: '[directory]', run: ( args ) => escapePatterns( args?.[ 1 ] ), }, 'validate-theme': { helpText: [ 'Validates a theme against the WordPress theme requirements.', '--format=FORMAT', wrapIndent( 'Output format. Possible values: *table*, json, dir.' ), '--color=WHEN', wrapIndent( 'Colorize the output for table or dir formats. The automatic mode only enables colors if an interactive terminal is detected. Possible values: *auto*, always, never.' ), '--table-width=COLUMNS', wrapIndent( 'Explicitly set the width of the table format instead of determining it automatically. Will default to 120 if omitted and width cannot be determined automatically.' ), ].join( '\n\n' ), additionalArgs: '[--format=FORMAT] [--color=WHEN] [--table-width=COLUMNS] ', run: async ( args ) => { args.shift(); const options = {}; while ( args[ 0 ] && args[ 0 ].startsWith( '--' ) ) { const flag = args.shift().slice( 2 ); const [ key, value ] = flag.split( '=' ); const camelCaseKey = key.replace( /-([a-z])/g, ( [ , c ] ) => c.toUpperCase() ); options[ camelCaseKey ] = value ?? true; } const themes = args[ 0 ] ? args[ 0 ].split( /[ ,]+/ ) : []; if ( themes.length ) { await validateThemes( themes, options ); } }, }, help: { helpText: 'Displays the main help message.', run: ( args ) => showHelp( args?.[ 1 ] ), }, }; ( async function start() { let args = process.argv.slice( 2 ); let command = args?.[ 0 ]; if ( ! commands[ command ] ) { showHelp(); process.exit( 1 ); } await commands[ command ].run( args ); } )(); function runShellScript( command, args = '' ) { execFile( 'zsh', args ? [command, args] : [command], (error, stdout) => { // Display any output from the script if (stdout.trim()) { console.log(stdout.trim()); } // error will be non-null if the script exited with code 1 if (error) { console.error(error); } } ); } function wrapIndent( text, indent = ' ', newline = '\n', width = process.stdout.columns || 80 ) { return text .match( new RegExp( `.{1,${ width - indent.length - 1 }}(\\s+|$)|[^\\s]+?(\\s+|$)`, 'g' ) ) .map( ( line ) => indent + line ) .join( newline ); } function showHelp( command = '' ) { if ( ! command || ! commands.hasOwnProperty( command ) ) { console.log( ` node .theme-utils/index.mjs [command] Available commands: _(.theme-utils/index.mjs help [command] for more details)_ \t${ Object.keys( commands ).join( '\n\t' ) } ` ); return; } const { helpText, additionalArgs } = commands[ command ]; console.log( ` ${ command } ${ additionalArgs ?? '' } ${ helpText } ` ); }