Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
4 changes: 3 additions & 1 deletion apps/cli/lib/import-export/import/importers/importer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,9 @@ abstract class BaseBackupImporter extends BaseImporter {
}

if ( getSiteRuntime( site ) === 'native-php' && ! site.runtimeBlueprintPath ) {
await ensureWpConfig( site.path, this.resolvePhpVersion( site ) );
await ensureWpConfig( site.path, this.resolvePhpVersion( site ), undefined, {
forceDefaultDatabaseName: true,
} );
}
}

Expand Down
14 changes: 12 additions & 2 deletions apps/cli/lib/native-php/site-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { escapePhpSingleQuotedString } from '@studio/common/lib/mu-plugins';
import { decodePassword } from '@studio/common/lib/passwords';
import { type NativePhpSupportedVersion } from '@studio/common/lib/php-binary-metadata';
import { getWpCliPharPath } from 'cli/lib/dependency-management/paths';
import { isSqliteIntegrationInstalled } from 'cli/lib/sqlite-integration';
import { ensurePhpBinaryAvailable } from '../dependency-management/php-binary';
import { runPhpCommand } from './php-process';
import { getFullyResolvedTmpDirPath } from './tmp-dir';
Expand All @@ -20,11 +21,15 @@ const DEFAULT_WP_CONFIG_CONSTANTS = { DB_NAME: 'wordpress' } as const;

type Logger = ( ...args: Parameters< typeof console.log > ) => void;

type EnsureWpConfigOptions = Pick< ServerConfig, 'enableDebugLog' | 'enableDebugDisplay' > & {
forceDefaultDatabaseName?: boolean;
};

export async function ensureWpConfig(
siteFolder: string,
phpVersion: NativePhpSupportedVersion,
signal?: AbortSignal,
config?: Pick< ServerConfig, 'enableDebugLog' | 'enableDebugDisplay' >
config?: EnsureWpConfigOptions
): Promise< void > {
const wpConfigPath = path.join( siteFolder, 'wp-config.php' );
const wpConfigSamplePath = path.join( siteFolder, 'wp-config-sample.php' );
Expand All @@ -46,12 +51,17 @@ $transformer->to_file( $wp_config_path );

const enableDebugLog = config?.enableDebugLog ?? false;
const enableDebugDisplay = config?.enableDebugDisplay ?? false;
const constants = {
const constants: Record< string, boolean | string > = {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we build the constants object conditionally instead of widening its type to allow the delete, because that widening loses the compile-time check that WP_DEBUG_LOG/WP_DEBUG_DISPLAY stay boolean?

const constants = {
	...( shouldSetDefaultDatabaseName ? DEFAULT_WP_CONFIG_CONSTANTS : {} ),
	WP_DEBUG: enableDebugLog || enableDebugDisplay,
	WP_DEBUG_LOG: enableDebugLog,
	WP_DEBUG_DISPLAY: enableDebugDisplay,
};

...DEFAULT_WP_CONFIG_CONSTANTS,
WP_DEBUG: enableDebugLog || enableDebugDisplay,
WP_DEBUG_LOG: enableDebugLog,
WP_DEBUG_DISPLAY: enableDebugDisplay,
};
const shouldSetDefaultDatabaseName =
config?.forceDefaultDatabaseName ?? ( await isSqliteIntegrationInstalled( siteFolder ) );
if ( ! shouldSetDefaultDatabaseName ) {
delete constants.DB_NAME;
}
await ensurePhpBinaryAvailable( phpVersion );

try {
Expand Down
168 changes: 168 additions & 0 deletions apps/cli/lib/native-php/tests/site-setup.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { ensureWpConfig } from '../site-setup';

const { ensurePhpBinaryAvailable, isSqliteIntegrationInstalled, runPhpCommand } = vi.hoisted(
() => ( {
ensurePhpBinaryAvailable: vi.fn( async () => undefined ),
isSqliteIntegrationInstalled: vi.fn( async () => false ),
runPhpCommand: vi.fn(),
} )
);

vi.mock( '../../dependency-management/php-binary', () => ( {
ensurePhpBinaryAvailable,
} ) );

vi.mock( '../php-process', () => ( {
runPhpCommand,
} ) );

vi.mock( 'cli/lib/sqlite-integration', () => ( {
isSqliteIntegrationInstalled,
} ) );

const PHP_VERSION = '8.4' as Parameters< typeof ensureWpConfig >[ 1 ];
const TRANSFORMER_PATH = path.resolve(
import.meta.dirname,
'../../../php/wp-config-transformer.php'
);

function phpAvailable(): boolean {
try {
execFileSync( 'php', [ '--version' ], { stdio: 'ignore' } );
return true;
} catch {
return false;
}
}

describe.skipIf( ! phpAvailable() )( 'ensureWpConfig', () => {
let tmpDir: string;

beforeEach( () => {
tmpDir = fs.mkdtempSync( path.join( os.tmpdir(), 'studio-site-setup-' ) );
ensurePhpBinaryAvailable.mockClear();
isSqliteIntegrationInstalled.mockReset();
isSqliteIntegrationInstalled.mockResolvedValue( false );
runPhpCommand.mockImplementation( async ( args: string[] ) => {
const [ , script, , wpConfigPath, constants ] = args;
const runnerPath = path.join( tmpDir, 'run-transformer.php' );
fs.writeFileSync( runnerPath, `<?php\n${ script }` );
execFileSync( 'php', [ runnerPath, TRANSFORMER_PATH, wpConfigPath, constants ], {
stdio: 'pipe',
} );
} );
} );

afterEach( () => {
fs.rmSync( tmpDir, { recursive: true, force: true } );
} );

it( 'preserves an external database name while updating debug constants', async () => {
const wpConfigPath = path.join( tmpDir, 'wp-config.php' );
fs.writeFileSync(
wpConfigPath,
"<?php\ndefine( 'DB_NAME', 'database_demo' );\ndefine( 'WP_DEBUG', true );\n"
);

await ensureWpConfig( tmpDir, PHP_VERSION );

const contents = fs.readFileSync( wpConfigPath, 'utf8' );
expect( contents ).toContain( "define( 'DB_NAME', 'database_demo' );" );
expect( contents ).toContain( "define( 'WP_DEBUG', false );" );
} );

it.each( [
{
name: 'replaces the sample placeholder for SQLite',
sqliteInstalled: true,
input: [ "define( 'DB_NAME', 'database_name_here' );" ],
expected: [ "define( 'DB_NAME', 'wordpress' );" ],
},
{
name: 'replaces an empty value for SQLite',
sqliteInstalled: true,
input: [ "define( 'DB_NAME', '' );" ],
expected: [ "define( 'DB_NAME', 'wordpress' );" ],
},
{
name: 'replaces a dynamic value for SQLite',
sqliteInstalled: true,
input: [ "define( 'DB_NAME', getenv( 'DB_NAME' ) );" ],
expected: [ "define( 'DB_NAME', 'wordpress' );" ],
},
{
name: 'replaces every duplicate value for SQLite',
sqliteInstalled: true,
input: [
"define( 'DB_NAME', 'database_name_here' );",
"define( 'DB_NAME', 'database_demo' );",
],
expected: [ "define( 'DB_NAME', 'wordpress' );", "define( 'DB_NAME', 'wordpress' );" ],
},
{
name: 'preserves an empty value for external MySQL',
sqliteInstalled: false,
input: [ "define( 'DB_NAME', '' );" ],
expected: [ "define( 'DB_NAME', '' );" ],
},
{
name: 'preserves a dynamic value for external MySQL',
sqliteInstalled: false,
input: [ "define( 'DB_NAME', getenv( 'DB_NAME' ) );" ],
expected: [ "define( 'DB_NAME', getenv( 'DB_NAME' ) );" ],
},
{
name: 'preserves duplicate external values when the sample comes first',
sqliteInstalled: false,
input: [
"define( 'DB_NAME', 'database_name_here' );",
"define( 'DB_NAME', 'database_demo' );",
],
expected: [
"define( 'DB_NAME', 'database_name_here' );",
"define( 'DB_NAME', 'database_demo' );",
],
},
{
name: 'preserves duplicate external values when the sample comes second',
sqliteInstalled: false,
input: [
"define( 'DB_NAME', 'database_demo' );",
"define( 'DB_NAME', 'database_name_here' );",
],
expected: [
"define( 'DB_NAME', 'database_demo' );",
"define( 'DB_NAME', 'database_name_here' );",
],
},
] )( '$name', async ( { sqliteInstalled, input, expected } ) => {
isSqliteIntegrationInstalled.mockResolvedValue( sqliteInstalled );
const wpConfigPath = path.join( tmpDir, 'wp-config.php' );
fs.writeFileSync( wpConfigPath, `<?php\n${ input.join( '\n' ) }\n` );

await ensureWpConfig( tmpDir, PHP_VERSION );

const databaseDefinitions = fs
.readFileSync( wpConfigPath, 'utf8' )
.split( '\n' )
.filter( ( line ) => line.startsWith( "define( 'DB_NAME'" ) );
expect( databaseDefinitions ).toEqual( expected );
} );

it( 'ignores a commented-out external database name', async () => {
isSqliteIntegrationInstalled.mockResolvedValue( true );
const wpConfigPath = path.join( tmpDir, 'wp-config.php' );
fs.writeFileSync( wpConfigPath, "<?php\n// define( 'DB_NAME', 'database_demo' );\n" );

await ensureWpConfig( tmpDir, PHP_VERSION );

expect( fs.readFileSync( wpConfigPath, 'utf8' ) ).toContain(
"define( 'DB_NAME', 'wordpress' );"
);
} );
} );