Skip to content
Merged
8 changes: 0 additions & 8 deletions apps/cli/commands/pull.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import { SiteData } from 'cli/lib/cli-config/core';
import { clearSiteLatestCliPid, getSiteByFolder, getSiteUrl } from 'cli/lib/cli-config/sites';
import { connectToDaemon, disconnectFromDaemon } from 'cli/lib/daemon-client';
import { DEFAULT_IMPORTER_OPTIONS, getImporter } from 'cli/lib/import-export/import/import-manager';
import { keepSqliteIntegrationUpdated } from 'cli/lib/sqlite-integration';
import {
checkBackupSize,
fetchSyncableSites,
Expand Down Expand Up @@ -213,13 +212,6 @@ export async function runCommand(
} finally {
try {
if ( site && wasServerRunning ) {
logger.reportStart(
LoggerAction.INSTALL_SQLITE,
__( 'Setting up SQLite integration, if needed…' )
);
await keepSqliteIntegrationUpdated( siteFolder );
logger.reportSuccess( __( 'SQLite integration configured as needed' ) );

logger.reportStart( LoggerAction.START_SITE, __( 'Starting WordPress server…' ) );
await startWordPressServer( site, logger );
logger.reportSuccess( __( 'WordPress server started' ) );
Expand Down
6 changes: 2 additions & 4 deletions apps/cli/commands/site/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,10 +261,8 @@ export async function runCommand(
}

logger.reportStart( LoggerAction.INSTALL_SQLITE, __( 'Setting up SQLite integration…' ) );
const isSqliteUpdated = await keepSqliteIntegrationUpdated( sitePath );
logger.reportSuccess(
isSqliteUpdated ? __( 'SQLite integration configured' ) : __( 'SQLite integration skipped' )
);
await keepSqliteIntegrationUpdated( sitePath );
logger.reportSuccess( __( 'SQLite integration configured' ) );

try {
const sharedConfig = await readSharedConfig();
Expand Down
11 changes: 1 addition & 10 deletions apps/cli/commands/site/tests/create.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ describe( 'CLI: studio site create', () => {
vi.mocked( saveCliConfig ).mockResolvedValue( undefined );
vi.mocked( lockCliConfig ).mockResolvedValue( undefined );
vi.mocked( unlockCliConfig ).mockResolvedValue( undefined );
vi.mocked( keepSqliteIntegrationUpdated ).mockResolvedValue( true );
vi.mocked( keepSqliteIntegrationUpdated ).mockResolvedValue( undefined );
vi.mocked( connectToDaemon ).mockResolvedValue( undefined );
vi.mocked( disconnectFromDaemon ).mockResolvedValue( undefined );
vi.mocked( updateServerFiles ).mockResolvedValue( true );
Expand Down Expand Up @@ -305,15 +305,6 @@ describe( 'CLI: studio site create', () => {
expect( disconnectFromDaemon ).toHaveBeenCalled();
} );

it( 'should skip SQLite integration when it is already configured', async () => {
vi.mocked( keepSqliteIntegrationUpdated ).mockResolvedValue( false );

await runCommand( mockSitePath, { ...defaultTestOptions } );

expect( keepSqliteIntegrationUpdated ).toHaveBeenCalledWith( mockSitePath );
expect( loggerReportSuccessSpy ).toHaveBeenCalledWith( 'SQLite integration skipped' );
} );

it( 'should persist the runtime and file access on the created site', async () => {
await runCommand( mockSitePath, {
...defaultTestOptions,
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/commands/site/tests/start.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ describe( 'CLI: studio site start', () => {
vi.mocked( disconnectFromDaemon ).mockResolvedValue( undefined );
vi.mocked( isServerRunning ).mockResolvedValue( undefined );
vi.mocked( setupCustomDomain ).mockResolvedValue( undefined );
vi.mocked( keepSqliteIntegrationUpdated ).mockResolvedValue( false );
vi.mocked( keepSqliteIntegrationUpdated ).mockResolvedValue( undefined );
vi.mocked( startWordPressServer ).mockResolvedValue( testProcessDescription );
vi.mocked( updateSiteLatestCliPid ).mockResolvedValue( undefined );
vi.mocked( logSiteDetails ).mockImplementation( () => {} );
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/commands/tests/export.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ describe( 'CLI: studio export', () => {
vi.mocked( connectToDaemon ).mockResolvedValue( undefined );
vi.mocked( disconnectFromDaemon ).mockResolvedValue( undefined );
vi.mocked( getSiteByFolder ).mockResolvedValue( testSite );
vi.mocked( keepSqliteIntegrationUpdated ).mockResolvedValue( false );
vi.mocked( keepSqliteIntegrationUpdated ).mockResolvedValue( undefined );
vi.mocked( getExporter ).mockResolvedValue( createExporter() as never );
} );

Expand Down
2 changes: 1 addition & 1 deletion apps/cli/commands/tests/import.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ describe( 'CLI: studio import', () => {
vi.mocked( getSiteByFolder ).mockResolvedValue( testSite );
vi.mocked( isServerRunning ).mockResolvedValue( undefined );
vi.mocked( stopWordPressServer ).mockResolvedValue( undefined );
vi.mocked( keepSqliteIntegrationUpdated ).mockResolvedValue( false );
vi.mocked( keepSqliteIntegrationUpdated ).mockResolvedValue( undefined );
vi.mocked( isWordPressDirectory ).mockReturnValue( true );
vi.mocked( getServerFilesPath ).mockReturnValue( '/server-files' );
vi.mocked( recursiveCopyDirectory ).mockResolvedValue( undefined );
Expand Down
4 changes: 0 additions & 4 deletions apps/cli/lib/sqlite-integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,6 @@ export async function isSqliteIntegrationAvailable() {
return provider.isSqliteIntegrationAvailable();
}

export async function needsSqliteSetup( sitePath: string ) {
return provider.needsSqliteSetup( sitePath );
}

export async function installSqliteIntegration( sitePath: string ) {
return provider.installSqliteIntegration( sitePath );
}
Expand Down
2 changes: 1 addition & 1 deletion apps/studio/src/tests/ipc-handlers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ vi.mock( 'src/lib/wordpress-setup', () => ( {
} ) );
vi.mock( 'src/main-window' );
vi.mock( 'src/lib/sqlite-versions', () => ( {
keepSqliteIntegrationUpdated: vi.fn().mockResolvedValue( false ),
keepSqliteIntegrationUpdated: vi.fn().mockResolvedValue( undefined ),
installSqliteIntegration: vi.fn().mockResolvedValue( undefined ),
} ) );
vi.mock( import( 'src/lib/bump-stats' ), async ( importOriginal ) => {
Expand Down
65 changes: 50 additions & 15 deletions packages/common/lib/sqlite-integration.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import fs from 'fs';
import path from 'path';

// Identifies a db.php that Studio generated from db.copy (see the auto-generated
// header in that template). Stock drop-ins are refreshed on every run; custom,
// SQLite-compatible drop-ins are preserved.
const STOCK_DB_DROPIN_MARKER = 'This file is auto-generated and copied from the sqlite plugin.';

// Abstract base class for SQLite integration across different contexts
export abstract class SqliteIntegrationProvider {
abstract getSqliteDirname(): string;
Expand All @@ -12,25 +17,57 @@ export abstract class SqliteIntegrationProvider {
return fs.existsSync( sqliteSourcePath ) && fs.existsSync( dbCopyPath );
}

// Returns true if site has db.php or no wp-config.php
/**
* Whether Studio should (re)install the SQLite integration for this site.
*
* A user can run Studio against their own database server (e.g. MySQL) by removing
* the SQLite drop-in, database, and mu-plugin, then pointing wp-config.php at it
* (https://developer.wordpress.com/docs/developer-tools/studio/frequently-asked-questions/#use-studio-with-mysql-server).
* We must not reinstall SQLite over that. But a SQLite site whose db.php drop-in went
* missing still has its database/mu-plugin, and needs the drop-in restored.
*
* So: set up SQLite for a fresh site (no wp-config.php), or whenever any SQLite artifact
* is still present; skip only when wp-config.php exists and every SQLite artifact is gone.
*/
async needsSqliteSetup( sitePath: string ): Promise< boolean > {
const hasDbPhp = fs.existsSync( path.join( sitePath, 'wp-content', 'db.php' ) );
const hasWpConfig = fs.existsSync( path.join( sitePath, 'wp-config.php' ) );
return hasDbPhp || ! hasWpConfig;
const wpContentPath = path.join( sitePath, 'wp-content' );
if ( ! fs.existsSync( path.join( sitePath, 'wp-config.php' ) ) ) {
return true;
}
return (
fs.existsSync( path.join( wpContentPath, 'db.php' ) ) ||
fs.existsSync( path.join( wpContentPath, 'database', '.ht.sqlite' ) ) ||
fs.existsSync( path.join( wpContentPath, 'mu-plugins', this.getSqliteDirname() ) )
);
}

async shouldKeepExistingDbDropin( sitePath: string ): Promise< boolean > {
/**
* Whether to overwrite the existing wp-content/db.php with Studio's stock SQLite
* drop-in instead of preserving it.
*
* A Studio site can only boot through a drop-in the local SQLite runtime understands,
* so a db.php is preserved only when it is a custom SQLite-compatible drop-in, identified
* by defining SQLITE_DB_DROPIN_VERSION (how the SQLite plugin recognizes it). Replace it
* in every other case:
* - missing/unreadable → replace (recreate it so the site can connect)
* - Studio's own stock drop-in → replace (refresh its path and version)
* - a plugin-owned db.php restored from a WordPress.com backup, etc. → replace
* - a custom drop-in defining SQLITE_DB_DROPIN_VERSION → keep (e.g. markdown-database-integration)
*/
async shouldReplaceDbDropin( sitePath: string ): Promise< boolean > {
const dbPhpPath = path.join( sitePath, 'wp-content', 'db.php' );
if ( ! fs.existsSync( dbPhpPath ) ) {
return false;
}

let content: string;
try {
const content = await fs.promises.readFile( dbPhpPath, 'utf8' );
return content.includes( '@studio-keep' );
content = await fs.promises.readFile( dbPhpPath, 'utf8' );
} catch {
return false;
return true;
}

if ( content.includes( STOCK_DB_DROPIN_MARKER ) ) {
return true;
}
return ! content.includes( 'SQLITE_DB_DROPIN_VERSION' );
}

async getSqliteVersionFromInstallation( sqliteMuPluginPath: string ): Promise< string > {
Expand Down Expand Up @@ -58,7 +95,7 @@ export abstract class SqliteIntegrationProvider {

await fs.promises.mkdir( databasePath, { recursive: true } );

if ( ! ( await this.shouldKeepExistingDbDropin( sitePath ) ) ) {
if ( await this.shouldReplaceDbDropin( sitePath ) ) {
const dbCopyContent = await fs.promises.readFile(
path.join( sqliteSourcePath, 'db.copy' ),
'utf8'
Expand All @@ -78,12 +115,10 @@ export abstract class SqliteIntegrationProvider {
} );
}

async keepSqliteIntegrationUpdated( sitePath: string ): Promise< boolean > {
async keepSqliteIntegrationUpdated( sitePath: string ): Promise< void > {
if ( await this.needsSqliteSetup( sitePath ) ) {
await this.installSqliteIntegration( sitePath );
return true;
}
return false;
}

async isSqliteInstalled( sitePath: string ): Promise< boolean > {
Expand Down
Loading
Loading