Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions apps/cli/commands/push.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ import { __, sprintf } from '@wordpress/i18n';
import { getSiteByFolder } from 'cli/lib/cli-config/sites';
import { getExporter } from 'cli/lib/import-export/export/export-manager';
import { ExportOptions } from 'cli/lib/import-export/export/types';
import { keepSqliteIntegrationUpdated } from 'cli/lib/sqlite-integration';
import {
installSqliteIntegration,
isSqliteIntegrationInstalled,
keepSqliteIntegrationUpdated,
} from 'cli/lib/sqlite-integration';
import {
fetchSyncableSites,
initiateImport,
Expand Down Expand Up @@ -56,7 +60,16 @@ export async function runCommand(
LoggerAction.INSTALL_SQLITE,
__( 'Setting up SQLite integration, if needed…' )
);
await keepSqliteIntegrationUpdated( siteFolder );
// Reprint-pulled (imported) sites wire SQLite through runtime.php and ship no db.php
// drop-in, so keepSqliteIntegrationUpdated skips them. The database export below uses
// `wp sqlite export`, which requires the SQLite integration to be discoverable in
// wp-content. Install it for imported sites; the exporter excludes db.php and the
// integration from the upload, so it never reaches the remote.
if ( site.runtimeBlueprintPath && ! ( await isSqliteIntegrationInstalled( siteFolder ) ) ) {
await installSqliteIntegration( siteFolder );
} else {
await keepSqliteIntegrationUpdated( siteFolder );
}
Comment thread
epeicher marked this conversation as resolved.
Outdated
logger.reportSuccess( __( 'SQLite integration configured as needed' ) );

logger.reportStart( LoggerAction.FETCH_REMOTE_SITES, __( 'Fetching WordPress.com sites…' ) );
Expand Down
38 changes: 38 additions & 0 deletions apps/cli/lib/pull/runtime-start-options.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { vi } from 'vitest';
import {
ensureImportedSiteSqliteReady,
getExtraDirectoryMountsFromImporterState,
getImportedSiteAutoPrependFile,
loadImportedRuntimeStartOptions,
loadRuntimeBlueprint,
} from './runtime-start-options';
Expand Down Expand Up @@ -228,4 +229,41 @@ if (!defined('STREAMING_SITE_MIGRATION_REMOTE_UPLOAD_PROXY_STATE_FILE')) {
fs.rmSync( importRoot, { recursive: true, force: true } );
}
} );

describe( 'getImportedSiteAutoPrependFile', () => {
it( 'returns the runtime.php sibling for an imported site that has it', () => {
const importRoot = fs.mkdtempSync( path.join( os.tmpdir(), 'studio-prepend-' ) );
const runtimeDir = path.join( importRoot, 'runtime' );
const runtimeBlueprintPath = path.join( runtimeDir, 'blueprint.json' );
const runtimePhpPath = path.join( runtimeDir, 'runtime.php' );

try {
fs.mkdirSync( runtimeDir, { recursive: true } );
fs.writeFileSync( runtimeBlueprintPath, '{}' );
fs.writeFileSync( runtimePhpPath, '<?php' );

expect( getImportedSiteAutoPrependFile( { runtimeBlueprintPath } ) ).toBe( runtimePhpPath );
} finally {
fs.rmSync( importRoot, { recursive: true, force: true } );
}
} );

it( 'returns undefined for a normal site without runtimeBlueprintPath', () => {
expect( getImportedSiteAutoPrependFile( {} ) ).toBeUndefined();
} );

it( 'returns undefined (does not throw) when runtime.php is missing', () => {
const importRoot = fs.mkdtempSync( path.join( os.tmpdir(), 'studio-prepend-missing-' ) );
const runtimeBlueprintPath = path.join( importRoot, 'runtime', 'blueprint.json' );

try {
fs.mkdirSync( path.dirname( runtimeBlueprintPath ), { recursive: true } );
fs.writeFileSync( runtimeBlueprintPath, '{}' );
// Intentionally no runtime.php written.
expect( getImportedSiteAutoPrependFile( { runtimeBlueprintPath } ) ).toBeUndefined();
} finally {
fs.rmSync( importRoot, { recursive: true, force: true } );
}
} );
} );
} );
23 changes: 23 additions & 0 deletions apps/cli/lib/pull/runtime-start-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,29 @@ export function loadImportedRuntimeStartOptionsNative(
};
}

/**
* Locates the reprint `runtime.php` to load as a native WP-CLI `auto_prepend_file`.
*
* Reprint-pulled sites wire SQLite only through `runtime.php`, which the web server
* applies as a PHP `auto_prepend_file`. A standalone native WP-CLI process never loads
* it, so WordPress falls back to MySQL and fails with "Error establishing a database
* connection". Loading the same file the server uses gives WP-CLI matching SQLite wiring.
*
* Only reprint/imported sites have `runtimeBlueprintPath`; `runtime.php` is its sibling
* (the same directory the native server-start path resolves it from above). Returns
* undefined — never throws — for normal `studio create` sites or when the file is absent,
* so those are unaffected.
*/
Comment thread
epeicher marked this conversation as resolved.
export function getImportedSiteAutoPrependFile( site: {
runtimeBlueprintPath?: string;
} ): string | undefined {
Comment thread
epeicher marked this conversation as resolved.
Outdated
if ( ! site.runtimeBlueprintPath ) {
return undefined;
}
const runtimePhpPath = path.join( path.dirname( site.runtimeBlueprintPath ), 'runtime.php' );
return fs.existsSync( runtimePhpPath ) ? runtimePhpPath : undefined;
}
Comment thread
epeicher marked this conversation as resolved.

export function loadRuntimeBlueprint( runtimeBlueprintPath: string ): Blueprint {
if ( ! fs.existsSync( runtimeBlueprintPath ) ) {
throw new LoggerError( `Runtime Blueprint not found: ${ runtimeBlueprintPath }` );
Expand Down
17 changes: 16 additions & 1 deletion apps/cli/lib/run-wp-cli-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
killPhpProcessTree,
reapPhpTreeOnInterrupt,
} from './native-php/php-process';
import { getImportedSiteAutoPrependFile } from './pull/runtime-start-options';
import { isServerRunning, sendWpCliCommand } from './wordpress-server-manager';
import { stripLeadingShebang } from './wp-cli-shebang';
import type { SiteData } from 'cli/lib/cli-config/core';
Expand Down Expand Up @@ -188,10 +189,24 @@ async function runNativeWpCliCommand(

// Don't apply open_basedir or disable_functions to the WP-CLI process
const defaultArgs = getDefaultPhpArgs( phpVersion );
// Reprint-pulled sites wire SQLite only through runtime.php, which the web server
// loads as auto_prepend_file. Load it here too so native WP-CLI connects to the same
// SQLite database instead of falling back to MySQL ("Error establishing a database
// connection"). No-op for normal sites (helper returns undefined).
//
// Exception: the sqlite-command (`wp sqlite export`/`tables`, requireSqliteCliCommand)
// loads its own copy of the SQLite integration and reads the database file directly.
// Prepending runtime.php there loads a second integration copy and fatals, so it runs
// without the prepend and relies on the integration installed in wp-content (see the
// imported-site setup in push.ts).
const autoPrependFile = options.requireSqliteCliCommand
? undefined
: getImportedSiteAutoPrependFile( site );
const prependArgs = autoPrependFile ? [ '-d', `auto_prepend_file=${ autoPrependFile }` ] : [];
Comment thread
epeicher marked this conversation as resolved.
Outdated
const nativeArgs = applyWpCliCommandOptions( 'native', args, options );
const child = spawn(
getPhpBinaryPath( phpVersion ),
[ ...defaultArgs, getWpCliPharPath(), `--path=${ site.path }`, ...nativeArgs ],
[ ...defaultArgs, ...prependArgs, getWpCliPharPath(), `--path=${ site.path }`, ...nativeArgs ],
{
cwd: site.path,
stdio: options.stdio === 'inherit' ? 'inherit' : [ 'ignore', 'pipe', 'pipe' ],
Expand Down
Loading