-
Notifications
You must be signed in to change notification settings - Fork 55
Use CLI site set command to edit site details from Studio #2360
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,6 +25,7 @@ import { ProcessDescription } from 'cli/lib/types/pm2'; | |
| import { | ||
| ServerConfig, | ||
| childMessagePm2Schema, | ||
| pm2ProcessEventSchema, | ||
| ManagerMessagePayload, | ||
| } from 'cli/lib/types/wordpress-server-ipc'; | ||
| import { Logger } from 'cli/logger'; | ||
|
|
@@ -125,6 +126,7 @@ export async function startWordPressServer( | |
| await waitForReadyMessage( processDesc.pmId ); | ||
| await sendMessage( | ||
| processDesc.pmId, | ||
| processName, | ||
| { | ||
| topic: 'start-server', | ||
| data: { config: serverConfig }, | ||
|
|
@@ -192,13 +194,15 @@ interface SendMessageOptions { | |
|
|
||
| async function sendMessage( | ||
| pmId: number, | ||
| processName: string, | ||
| message: ManagerMessagePayload, | ||
| options: SendMessageOptions = {} | ||
| ): Promise< unknown > { | ||
| const { maxTotalElapsedTime = PLAYGROUND_CLI_MAX_TIMEOUT, logger } = options; | ||
| const bus = await getPm2Bus(); | ||
| const messageId = crypto.randomUUID(); | ||
| let responseHandler: ( packet: unknown ) => void; | ||
| let processEventHandler: ( event: unknown ) => void; | ||
| let abortListener: () => void; | ||
|
|
||
| return new Promise( ( resolve, reject ) => { | ||
|
|
@@ -228,6 +232,17 @@ async function sendMessage( | |
| activityCheckIntervalId, | ||
| } ); | ||
|
|
||
| processEventHandler = ( event: unknown ) => { | ||
| const result = pm2ProcessEventSchema.safeParse( event ); | ||
| if ( ! result.success ) { | ||
| return; | ||
| } | ||
|
|
||
| if ( result.data.process.name === processName && result.data.event === 'exit' ) { | ||
| reject( new Error( 'WordPress server process exited unexpectedly' ) ); | ||
| } | ||
| }; | ||
|
Comment on lines
+235
to
+244
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems sensible. Out of curiosity, did you add this because of something related to the core purpose of this PR, or was it just something that happened to come up here?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This came up during testing this PR. When changing WP version to an incompatible one (e.g., 6.4.7 with Twenty Twenty-Five theme which requires WP 6.5+), the CLI would hang forever instead of reporting the error. |
||
|
|
||
| responseHandler = ( packet: unknown ) => { | ||
| const validationResult = childMessagePm2Schema.safeParse( packet ); | ||
| if ( ! validationResult.success ) { | ||
|
|
@@ -274,11 +289,13 @@ async function sendMessage( | |
| }; | ||
| abortController.signal.addEventListener( 'abort', abortListener ); | ||
|
|
||
| bus.on( 'process:event', processEventHandler ); | ||
| bus.on( 'process:msg', responseHandler ); | ||
|
|
||
| sendMessageToProcess( pmId, { ...message, messageId } ).catch( reject ); | ||
| } ).finally( () => { | ||
| abortController.signal.removeEventListener( 'abort', abortListener ); | ||
| bus.off( 'process:event', processEventHandler ); | ||
| bus.off( 'process:msg', responseHandler ); | ||
|
|
||
| const tracker = messageActivityTrackers.get( messageId ); | ||
|
|
@@ -299,6 +316,7 @@ export async function stopWordPressServer( siteId: string ): Promise< void > { | |
| try { | ||
| await sendMessage( | ||
| runningProcess.pmId, | ||
| processName, | ||
| { topic: 'stop-server', data: {} }, | ||
| { maxTotalElapsedTime: GRACEFUL_STOP_TIMEOUT } | ||
| ); | ||
|
|
@@ -376,6 +394,7 @@ export async function runBlueprint( | |
| await waitForReadyMessage( processDesc.pmId ); | ||
| await sendMessage( | ||
| processDesc.pmId, | ||
| processName, | ||
| { | ||
| topic: 'run-blueprint', | ||
| data: { config: serverConfig }, | ||
|
|
@@ -405,7 +424,7 @@ export async function sendWpCliCommand( | |
| throw new Error( `WordPress server is not running` ); | ||
| } | ||
|
|
||
| const result = await sendMessage( runningProcess.pmId, { | ||
| const result = await sendMessage( runningProcess.pmId, processName, { | ||
| topic: 'wp-cli-command', | ||
| data: { args }, | ||
| } ); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -344,11 +344,48 @@ const runWpCliCommand = sequential( | |
| { concurrent: 3, max: 10 } | ||
| ); | ||
|
|
||
| function parsePhpError( error: unknown ): string { | ||
| if ( ! ( error instanceof Error ) ) { | ||
| return String( error ); | ||
| } | ||
|
|
||
| const message = error.message; | ||
|
|
||
| // Check for WordPress critical error in HTML output | ||
| const wpDieMatch = message.match( /<div class="wp-die-message"[^>]*>([\s\S]*?)<\/div>/ ); | ||
| if ( wpDieMatch ) { | ||
| // Extract text from HTML, removing tags | ||
| const htmlContent = wpDieMatch[ 1 ]; | ||
| const textContent = htmlContent | ||
| .replace( /<[^>]+>/g, ' ' ) | ||
| .replace( /\s+/g, ' ' ) | ||
| .trim(); | ||
| if ( textContent ) { | ||
| return `WordPress error: ${ textContent }`; | ||
| } | ||
| } | ||
|
|
||
| // Check for PHP fatal error pattern | ||
| const fatalMatch = message.match( /PHP Fatal error:\s*(.+?)(?:\sin\s|$)/i ); | ||
| if ( fatalMatch ) { | ||
| return `PHP Fatal error: ${ fatalMatch[ 1 ].trim() }`; | ||
| } | ||
|
Comment on lines
+354
to
+372
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm curious what these two conditions are based on. XDebug errors?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
|
||
| // Check for generic PHP.run() failure - provide a cleaner message | ||
| if ( message.includes( 'PHP.run() failed with exit code' ) ) { | ||
| const exitCodeMatch = message.match( /exit code (\d+)/ ); | ||
| const exitCode = exitCodeMatch ? exitCodeMatch[ 1 ] : 'unknown'; | ||
| return `WordPress failed to start (PHP exit code ${ exitCode }). Check the site's debug.log for details.`; | ||
| } | ||
|
|
||
| return message; | ||
| } | ||
|
|
||
| function sendErrorMessage( messageId: string, error: unknown ) { | ||
| const errorResponse: ChildMessageRaw = { | ||
| originalMessageId: messageId, | ||
| topic: 'error', | ||
| errorMessage: error instanceof Error ? error.message : String( error ), | ||
| errorMessage: parsePhpError( error ), | ||
| errorStack: error instanceof Error ? error.stack : undefined, | ||
| cliArgs: lastCliArgs ?? undefined, | ||
| }; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| export interface SiteSettingChanges { | ||
| domainChanged?: boolean; | ||
| httpsChanged?: boolean; | ||
| phpChanged?: boolean; | ||
| wpChanged?: boolean; | ||
| xdebugChanged?: boolean; | ||
| } | ||
|
|
||
| export function siteNeedsRestart( changes: SiteSettingChanges ): boolean { | ||
| const { domainChanged, httpsChanged, phpChanged, wpChanged, xdebugChanged } = changes; | ||
|
|
||
| return !! ( domainChanged || httpsChanged || phpChanged || wpChanged || xdebugChanged ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| import { siteNeedsRestart } from '../site-needs-restart'; | ||
|
|
||
| describe( 'siteNeedsRestart', () => { | ||
| it( 'returns false when no changes are provided', () => { | ||
| expect( siteNeedsRestart( {} ) ).toBe( false ); | ||
| } ); | ||
|
|
||
| it( 'returns false when all changes are false', () => { | ||
| expect( | ||
| siteNeedsRestart( { | ||
| domainChanged: false, | ||
| httpsChanged: false, | ||
| phpChanged: false, | ||
| wpChanged: false, | ||
| xdebugChanged: false, | ||
| } ) | ||
| ).toBe( false ); | ||
| } ); | ||
|
|
||
| it( 'returns true when domain changed', () => { | ||
| expect( siteNeedsRestart( { domainChanged: true } ) ).toBe( true ); | ||
| } ); | ||
|
|
||
| it( 'returns true when https changed', () => { | ||
| expect( siteNeedsRestart( { httpsChanged: true } ) ).toBe( true ); | ||
| } ); | ||
|
|
||
| it( 'returns true when php changed', () => { | ||
| expect( siteNeedsRestart( { phpChanged: true } ) ).toBe( true ); | ||
| } ); | ||
|
|
||
| it( 'returns true when wp changed', () => { | ||
| expect( siteNeedsRestart( { wpChanged: true } ) ).toBe( true ); | ||
| } ); | ||
|
|
||
| it( 'returns true when xdebug changed', () => { | ||
| expect( siteNeedsRestart( { xdebugChanged: true } ) ).toBe( true ); | ||
| } ); | ||
|
|
||
| it( 'returns true when multiple settings changed', () => { | ||
| expect( | ||
| siteNeedsRestart( { | ||
| phpChanged: true, | ||
| wpChanged: true, | ||
| } ) | ||
| ).toBe( true ); | ||
| } ); | ||
|
|
||
| it( 'returns true when one change is true among false values', () => { | ||
| expect( | ||
| siteNeedsRestart( { | ||
| domainChanged: false, | ||
| httpsChanged: false, | ||
| phpChanged: true, | ||
| wpChanged: false, | ||
| xdebugChanged: false, | ||
| } ) | ||
| ).toBe( true ); | ||
| } ); | ||
| } ); |

There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice catch 👍