From 5382c6a7d2be2e9987f417992f552ae1494b52e4 Mon Sep 17 00:00:00 2001 From: Chris Huber Date: Mon, 19 Oct 2026 12:23:47 -0400 Subject: [PATCH] Keep the SQLite database at a fixed path with DB_PATH SQLite Database Integration 3.1 moves a database at wp-content/database/.ht.sqlite into a randomized directory the first time it loads, unless an explicit path is configured. Studio reads the database at the fixed path in many places. Define DB_PATH at the top of the db.php drop-in Studio generates, so the plugin keeps the database where it is. The drop-in is regenerated before a site starts, so existing sites get the constant before 3.1 loads. Sites that configure their own database path in wp-config.php are left alone. Bump SQLite Database Integration to 3.1.0. --- packages/common/lib/sqlite-integration.ts | 21 +++++++-- .../lib/tests/sqlite-integration.test.ts | 45 +++++++++++++++++++ scripts/download-wp-server-files.ts | 2 +- 3 files changed, 63 insertions(+), 5 deletions(-) diff --git a/packages/common/lib/sqlite-integration.ts b/packages/common/lib/sqlite-integration.ts index 21ccdbb6d1..12f727c3fb 100644 --- a/packages/common/lib/sqlite-integration.ts +++ b/packages/common/lib/sqlite-integration.ts @@ -6,6 +6,22 @@ import path from 'path'; // SQLite-compatible drop-ins are preserved. const STOCK_DB_DROPIN_MARKER = 'This file is auto-generated and copied from the sqlite plugin.'; +function generateDbDropinContent( template: string, sqliteDirname: string ): string { + const updatedContent = template.replace( + "'{SQLITE_IMPLEMENTATION_FOLDER_PATH}'", + `realpath( __DIR__ . '/mu-plugins/${ sqliteDirname }' )` + ); + const dbPathDefinition = `// Studio keeps the SQLite database at a fixed path (wp-content/database/.ht.sqlite). +if ( ! defined( 'DB_PATH' ) && ! defined( 'FQDB' ) && ! defined( 'DB_DIR' ) && ! defined( 'DB_FILE' ) ) { +\tdefine( 'DB_PATH', __DIR__ . '/database/.ht.sqlite' ); +} +`; + return updatedContent.replace( + /^<\?php\s*/, + ( openingTag ) => `${ openingTag }${ dbPathDefinition }` + ); +} + // Abstract base class for SQLite integration across different contexts export abstract class SqliteIntegrationProvider { abstract getSqliteDirname(): string; @@ -100,10 +116,7 @@ export abstract class SqliteIntegrationProvider { path.join( sqliteSourcePath, 'db.copy' ), 'utf8' ); - const updatedContent = dbCopyContent.replace( - "'{SQLITE_IMPLEMENTATION_FOLDER_PATH}'", - `realpath( __DIR__ . '/mu-plugins/${ sqliteDirname }' )` - ); + const updatedContent = generateDbDropinContent( dbCopyContent, sqliteDirname ); await fs.promises.writeFile( path.join( wpContentPath, 'db.php' ), updatedContent ); } diff --git a/packages/common/lib/tests/sqlite-integration.test.ts b/packages/common/lib/tests/sqlite-integration.test.ts index 766b3b23e8..91d885b9df 100644 --- a/packages/common/lib/tests/sqlite-integration.test.ts +++ b/packages/common/lib/tests/sqlite-integration.test.ts @@ -106,6 +106,19 @@ platformTestSuite( 'SqliteIntegrationProvider', ( { normalize } ) => { } ); describe( 'installSqliteIntegration', () => { + // Shaped like the plugin's db.copy: header with the stock marker, then the loader. + const STOCK_DB_COPY = [ + ' { volFromJSON( { [ normalize( `wp-files/${ SQLITE_DIRNAME }/db.copy` ) ]: @@ -113,6 +126,38 @@ platformTestSuite( 'SqliteIntegrationProvider', ( { normalize } ) => { } ); } ); + it( 'should define a fixed DB_PATH before the plugin loads', async () => { + volFromJSON( { + [ normalize( `wp-files/${ SQLITE_DIRNAME }/db.copy` ) ]: STOCK_DB_COPY, + } ); + + await provider.installSqliteIntegration( MOCK_SITE_PATH ); + + const generated = vi.mocked( fs.promises.writeFile ).mock.calls.at( -1 )?.[ 1 ] as string; + const definition = generated.indexOf( + "if ( ! defined( 'DB_PATH' ) && ! defined( 'FQDB' ) && ! defined( 'DB_DIR' ) && ! defined( 'DB_FILE' ) ) {\n\tdefine( 'DB_PATH', __DIR__ . '/database/.ht.sqlite' );\n}" + ); + expect( generated.startsWith( ' { + volFromJSON( { + [ normalize( `wp-files/${ SQLITE_DIRNAME }/db.copy` ) ]: STOCK_DB_COPY, + } ); + await provider.installSqliteIntegration( MOCK_SITE_PATH ); + const generated = vi.mocked( fs.promises.writeFile ).mock.calls.at( -1 )?.[ 1 ] as string; + + volFromJSON( { + [ normalize( `${ MOCK_SITE_PATH }/wp-content/db.php` ) ]: generated, + } ); + + expect( await provider.shouldReplaceDbDropin( MOCK_SITE_PATH ) ).toBe( true ); + } ); + it( 'should create database directory', async () => { await provider.installSqliteIntegration( MOCK_SITE_PATH ); diff --git a/scripts/download-wp-server-files.ts b/scripts/download-wp-server-files.ts index 3a675c9d5f..cc1b38b13c 100644 --- a/scripts/download-wp-server-files.ts +++ b/scripts/download-wp-server-files.ts @@ -10,7 +10,7 @@ import { z } from 'zod'; import { extractZip } from '../packages/common/lib/extract-zip.ts'; import { fetch, sharedDispatcher, throwForHttpStatus, withRetry } from './lib/with-retry.ts'; -const SQLITE_DATABASE_INTEGRATION_VERSION = 'v3.0.2'; +const SQLITE_DATABASE_INTEGRATION_VERSION = 'v3.1.0'; const SQLITE_DATABASE_INTEGRATION_RELEASE_URL = `https://github.com/WordPress/sqlite-database-integration/releases/download/${ SQLITE_DATABASE_INTEGRATION_VERSION }/plugin-sqlite-database-integration.zip`; async function fetchWithRetry( name: string, url: string ): Promise< Buffer > {