diff --git a/src/constants.ts b/src/constants.ts index 2381292fd..81ed06176 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -49,3 +49,9 @@ export const DAY_MS = HOUR_MS * 24; // IMPORTANT: When updating this value, we need to update the string located in `AIClearHistoryReminder` component. // Reference: https://github.com/Automattic/studio/blob/3dd5c58cdb7998e458d191e508e8e859177225a9/src/components/ai-clear-history-reminder.tsx#L78 export const CLEAR_HISTORY_REMINDER_TIME = 2 * HOUR_MS; // In milliseconds + +// WP-CLI +export const WP_CLI_DEFAULT_RESPONSE_TIMEOUT = 5 * 60 * 1000; // 5min +export const WP_CLI_IMPORT_EXPORT_RESPONSE_TIMEOUT_IN_HRS = 6; +export const WP_CLI_IMPORT_EXPORT_RESPONSE_TIMEOUT = + WP_CLI_IMPORT_EXPORT_RESPONSE_TIMEOUT_IN_HRS * 60 * 60 * 1000; // 6hr diff --git a/src/hooks/use-import-export.tsx b/src/hooks/use-import-export.tsx index fa3ca15c8..8ac998499 100644 --- a/src/hooks/use-import-export.tsx +++ b/src/hooks/use-import-export.tsx @@ -1,6 +1,7 @@ import * as Sentry from '@sentry/electron/renderer'; -import { __ } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; import { createContext, useMemo, useState, useCallback, useContext } from 'react'; +import { WP_CLI_IMPORT_EXPORT_RESPONSE_TIMEOUT_IN_HRS } from 'src/constants'; import { useIpcListener } from 'src/hooks/use-ipc-listener'; import { useSiteDetails } from 'src/hooks/use-site-details'; import { getIpcApi } from 'src/lib/get-ipc-api'; @@ -92,6 +93,18 @@ export const ImportExportProvider = ( { children }: { children: React.ReactNode 'The ZIP archive is invalid. Try to unpack and pack it again. If this problem persists, please contact support.' ), } ); + } else if ( + ( error as Error ).message.includes( 'WP-CLI command was canceled (timed out)' ) + ) { + await getIpcApi().showErrorMessageBox( { + title: __( 'Failed importing site' ), + message: sprintf( + __( + 'The import process timed out after %d hours, which can occur when processing very large imports. If the issue persists, please contact support.' + ), + WP_CLI_IMPORT_EXPORT_RESPONSE_TIMEOUT_IN_HRS + ), + } ); } else { await getIpcApi().showErrorMessageBox( { title: __( 'Failed importing site' ), diff --git a/src/lib/import-export/import/importers/importer.ts b/src/lib/import-export/import/importers/importer.ts index b82ec5d2a..cb0d8c288 100644 --- a/src/lib/import-export/import/importers/importer.ts +++ b/src/lib/import-export/import/importers/importer.ts @@ -61,11 +61,11 @@ abstract class BaseImporter extends EventEmitter implements Importer { ); if ( stderr ) { - console.error( `Warning during import of ${ sqlFile }:`, stderr ); + console.error( `Error during import of ${ sqlFile }:`, stderr ); } if ( exitCode ) { - throw new Error( 'Database import failed' ); + throw new Error( 'Database import failed: ' + stderr ); } } finally { await this.safelyDeletePath( tmpPath ); diff --git a/src/lib/wp-cli-process.ts b/src/lib/wp-cli-process.ts index 264273284..51eb56578 100644 --- a/src/lib/wp-cli-process.ts +++ b/src/lib/wp-cli-process.ts @@ -1,5 +1,9 @@ import { app, utilityProcess, UtilityProcess } from 'electron'; import * as Sentry from '@sentry/electron/renderer'; +import { + WP_CLI_DEFAULT_RESPONSE_TIMEOUT as DEFAULT_RESPONSE_TIMEOUT, + WP_CLI_IMPORT_EXPORT_RESPONSE_TIMEOUT as IMPORT_EXPORT_RESPONSE_TIMEOUT, +} from 'src/constants'; import { executeWPCli } from 'vendor/wp-now/src/execute-wp-cli'; // This allows TypeScript to pick up the magic constants that's auto-generated by Forge's Webpack @@ -11,8 +15,6 @@ export type MessageName = 'execute'; export type WpCliResult = ReturnType< typeof executeWPCli >; export type MessageCanceled = { error: Error; canceled: boolean }; -const DEFAULT_RESPONSE_TIMEOUT = 300 * 1000; - export default class WpCliProcess { lastMessageId = 0; process?: UtilityProcess; @@ -62,7 +64,11 @@ export default class WpCliProcess { args, phpVersion, } ); - return await this.waitForResponse( message, messageId ); + const timeout = + args[ 0 ] === 'sqlite' && [ 'import', 'export' ].includes( args[ 1 ] ) + ? IMPORT_EXPORT_RESPONSE_TIMEOUT + : DEFAULT_RESPONSE_TIMEOUT; + return await this.waitForResponse( message, messageId, timeout ); } async stop() { diff --git a/src/site-server.ts b/src/site-server.ts index 973d541a3..c40ce3bb5 100644 --- a/src/site-server.ts +++ b/src/site-server.ts @@ -202,11 +202,19 @@ export class SiteServer { return await this.wpCliExecutor.execute( wpCliArgs as string[], { phpVersion } ); } catch ( error ) { if ( ( error as MessageCanceled )?.canceled ) { - return { stdout: '', stderr: 'wp-cli command canceled', exitCode: 1 }; + return { + stdout: '', + stderr: 'WP-CLI command was canceled (timed out)', + exitCode: 1, + }; } Sentry.captureException( error ); - return { stdout: '', stderr: 'error when executing wp-cli command', exitCode: 1 }; + return { + stdout: '', + stderr: `Error executing WP-CLI command: ${ ( error as MessageCanceled ).error.message }`, + exitCode: 1, + }; } }