Fix studio push for native reprint-pulled sites#3994
Conversation
Sites created by `studio pull-reprint` on the native-php runtime wire SQLite only through a generated runtime.php (applied by the server as auto_prepend_file) and ship no wp-content/db.php drop-in. Standalone WP-CLI never loaded runtime.php, so `studio push` failed two ways: `wp plugin list` and `wp theme list` (meta.json) hit "Error establishing a database connection", and `wp sqlite export` (the DB dump) reported "Could not locate the SQLite integration plugin". The native WP-CLI runner now loads the site's runtime.php as auto_prepend_file for imported sites, restoring the database connection for general WP-CLI. This also fixes the desktop get-theme-details / get-site-icon errors. The sqlite-command (wp sqlite export/tables) is the exception: it loads its own SQLite integration copy and reads the database file directly, so prepending runtime.php loads a second integration copy and fatals. It runs without the prepend, and push installs the SQLite integration into the imported site's wp-content so the command can find it. This is safe: runtime.php takes precedence over the drop-in on the running server (require_wp_db returns early when $wpdb is set), and the exporter already excludes db.php and the integration from the upload, so neither the local server nor the MySQL remote is affected. Claude-Session: https://claude.ai/code/session_015EADJrWvjTQrmdgpFZre6j
| await keepSqliteIntegrationUpdated( siteFolder ); | ||
| // Reprint-pulled (imported) sites wire SQLite through runtime.php and ship no db.php | ||
| // drop-in, so the step above 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 ); | ||
| } |
There was a problem hiding this comment.
Could we if/else here?
Addresses review feedback on #3994: move keepSqliteIntegrationUpdated into the else branch so the imported-site install and the normal-site update are mutually exclusive, instead of always calling keepSqliteIntegrationUpdated and then conditionally installing. Behavior-preserving. Claude-Session: https://claude.ai/code/session_015EADJrWvjTQrmdgpFZre6j
…rify-that-studio-push-functionality-works-the-same-with
📊 Performance Test ResultsComparing 3b6e83b vs trunk app-size
site-editor
site-startup
Results are median values from multiple test runs. Legend: 🟢 Improvement (faster) | 🔴 Regression (slower) | ⚪ No change (<50ms diff) |
…ity-works-the-same-with
fredrikekelund
left a comment
There was a problem hiding this comment.
Nice and targeted fix 👍 Looks good overall, although it'd be nice to address the SQLite integration issue in the export command here, too. Let me know your thoughts there, @epeicher
| const autoPrependFile = options.requireSqliteCliCommand | ||
| ? undefined | ||
| : getImportedSiteAutoPrependFile( site ); | ||
| const prependArgs = autoPrependFile ? [ '-d', `auto_prepend_file=${ autoPrependFile }` ] : []; |
There was a problem hiding this comment.
Can we move this into getDefaultPhpArgs() and edit spawnPhpProcess() in apps/cli/lib/native-php/php-process.ts accordingly?
| export function getImportedSiteAutoPrependFile( site: { | ||
| runtimeBlueprintPath?: string; | ||
| } ): string | undefined { |
There was a problem hiding this comment.
| export function getImportedSiteAutoPrependFile( site: { | |
| runtimeBlueprintPath?: string; | |
| } ): string | undefined { | |
| export function getImportedSiteAutoPrependFile( site: SiteData ): string | undefined { |
We might as well use the canonical type here, unless we have good reason to take a partial type
| /** | ||
| * 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. | ||
| */ |
There was a problem hiding this comment.
I think this can be shortened quite a bit. It's just unnecessarily verbose
| // 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 ); | ||
| } |
There was a problem hiding this comment.
I noticed the export CLI command failing for a Reprint-pulled site on the database step, and I assume that's because of a missing "regular" SQLite integration. That's arguably a separate issue, but it seems small enough that we might fix it here. Either by moving this SQLite integration updating/installation into the exporter, or by copying this logic to the export command.
We should also consider preview site creation.
Related issues
How AI was used in this PR
Claude Code was used throughout: to reproduce and diagnose the failure, to discover that there were two distinct blockers (a DB-connection error and a separate "could not locate the SQLite integration plugin" error), to validate the fix design with controlled experiments against a real reprint-pulled site, to implement the changes, and to verify the full push end-to-end. All generated code was reviewed.
Proposed Changes
Makes
studio pushwork for sites created bystudio pull-reprinton the native-php runtime. The export step previously failed — first with "Error establishing a database connection", then "Database export failed". The same WP-CLI path backs the desktop "get theme details" / "get site icon" features, which failed identically for these sites and are fixed too.Why it failed: a reprint pull wires SQLite only through a generated
runtime.php, which the web server applies as a PHPauto_prepend_file; these sites have nowp-content/db.phpdrop-in. The server reaches the database, but standalone WP-CLI — which push uses forwp plugin list,wp theme list, andwp sqlite export— never loadsruntime.php. That produced two separate failures:wp plugin list/theme listfell back to MySQL → "Error establishing a database connection".wp sqlite exportadditionally needs the SQLite integration plugin discoverable inwp-content, which reprint sites don't have → "Could not locate the SQLite integration plugin".WASM/Playground imported sites were never affected because their pull installs a real
db.phpdrop-in; native imported sites never did.The fix is two coordinated changes:
runtime.phpasauto_prepend_filefor imported sites, restoring the database connection for general WP-CLI.wp sqlite export/tables) is the exception: it loads its own integration copy and reads the database file directly, so it runs without the prepend, and push installs the SQLite integration into the imported site'swp-contentso the command can find it.Safe on both ends:
runtime.phptakes precedence over the drop-in on the running server (require_wp_db()returns early when$wpdbis set, so the drop-in is inert), and the exporter already excludesdb.phpandsqlite-database-integrationfrom the upload, so neither reaches the MySQL remote.Testing Instructions
npm run cli:buildSTUDIO_ENABLE_PULL_REPRINT=true node apps/cli/dist/cli/main.mjs pull-reprint --path <path-to-Studio-site>topulla remote site using Reprint, e.g.STUDIO_ENABLE_PULL_REPRINT=true node apps/cli/dist/cli/main.mjs pull-reprint --path ~/Studio/my-shiny-website. Select the remote site to pull and wait for completioncd <path-to-Studio-site> && node ~/github/studio/apps/cli/dist/cli/main.mjs push, e.g.cd ~/Studio/my-shiny-website && node ~/github/studio/apps/cli/dist/cli/main.mjs pushPushcomplete successfully