diff --git a/apps/cli/commands/site/create.ts b/apps/cli/commands/site/create.ts index 386951805f..aa5f07d5dd 100644 --- a/apps/cli/commands/site/create.ts +++ b/apps/cli/commands/site/create.ts @@ -9,7 +9,10 @@ import { DEFAULT_WORDPRESS_VERSION, MINIMUM_WORDPRESS_VERSION, } from '@studio/common/constants'; -import { extractFormValuesFromBlueprint } from '@studio/common/lib/blueprint-settings'; +import { + blueprintHasMultisite, + extractFormValuesFromBlueprint, +} from '@studio/common/lib/blueprint-settings'; import { filterUnsupportedBlueprintFeatures, validateBlueprintData, @@ -142,6 +145,14 @@ export async function runCommand( blueprint = filterUnsupportedBlueprintFeatures( options.blueprint.contents as Record< string, unknown > ); + + if ( blueprint && blueprintHasMultisite( blueprint ) && ! options.customDomain ) { + throw new LoggerError( + __( + 'The enableMultisite Blueprint step requires a custom domain. WordPress multisite does not support custom ports. Use --domain .local to set a custom domain.' + ) + ); + } } const appdata = await readAppdata(); diff --git a/apps/cli/commands/site/tests/create.test.ts b/apps/cli/commands/site/tests/create.test.ts index cd9e8cd0fd..4f5fe13990 100644 --- a/apps/cli/commands/site/tests/create.test.ts +++ b/apps/cli/commands/site/tests/create.test.ts @@ -563,6 +563,41 @@ describe( 'CLI: studio site create', () => { } ); } ); + describe( 'Multisite Validation', () => { + it( 'should error when enableMultisite step is present without custom domain', async () => { + const multisiteBlueprint: Blueprint = { + steps: [ { step: 'enableMultisite' } ], + }; + + await expect( + runCommand( mockSitePath, { + ...defaultTestOptions, + blueprint: { + uri: '/home/test/blueprint.json', + contents: multisiteBlueprint, + }, + } ) + ).rejects.toThrow( /enableMultisite.*custom domain/i ); + } ); + + it( 'should proceed when enableMultisite step is present with custom domain', async () => { + const multisiteBlueprint: Blueprint = { + steps: [ { step: 'enableMultisite' } ], + }; + + await runCommand( mockSitePath, { + ...defaultTestOptions, + customDomain: 'test.local', + blueprint: { + uri: '/home/test/blueprint.json', + contents: multisiteBlueprint, + }, + } ); + + expect( startWordPressServer ).toHaveBeenCalled(); + } ); + } ); + describe( 'noStart Option', () => { it( 'should not start server when noStart is true', async () => { await runCommand( mockSitePath, { diff --git a/apps/studio/src/hooks/use-add-site.ts b/apps/studio/src/hooks/use-add-site.ts index b7f2c4f762..1577596ddf 100644 --- a/apps/studio/src/hooks/use-add-site.ts +++ b/apps/studio/src/hooks/use-add-site.ts @@ -74,6 +74,7 @@ export function useAddSite() { const [ blueprintSuggestedSiteName, setBlueprintSuggestedSiteName ] = useState< string | undefined >(); + const [ blueprintRequiresCustomDomain, setBlueprintRequiresCustomDomain ] = useState( false ); const [ isDeeplinkFlow, setIsDeeplinkFlow ] = useState( false ); const [ existingDomainNames, setExistingDomainNames ] = useState< string[] >( [] ); @@ -89,6 +90,7 @@ export function useAddSite() { setBlueprintSuggestedDomain( undefined ); setBlueprintSuggestedHttps( undefined ); setBlueprintSuggestedSiteName( undefined ); + setBlueprintRequiresCustomDomain( false ); }, [] ); // For blueprint deeplinks - we need temporary state for PHP/WP versions @@ -105,6 +107,7 @@ export function useAddSite() { setBlueprintSuggestedDomain( undefined ); setBlueprintSuggestedHttps( undefined ); setBlueprintSuggestedSiteName( undefined ); + setBlueprintRequiresCustomDomain( false ); setSelectedRemoteSite( undefined ); setDeeplinkPhpVersion( defaultPhpVersion as AllowedPHPVersion ); setDeeplinkWpVersion( defaultWordPressVersion ); @@ -341,6 +344,8 @@ export function useAddSite() { setBlueprintSuggestedHttps, blueprintSuggestedSiteName, setBlueprintSuggestedSiteName, + blueprintRequiresCustomDomain, + setBlueprintRequiresCustomDomain, selectedRemoteSite, setSelectedRemoteSite, existingDomainNames, @@ -368,6 +373,7 @@ export function useAddSite() { blueprintSuggestedDomain, blueprintSuggestedHttps, blueprintSuggestedSiteName, + blueprintRequiresCustomDomain, selectedRemoteSite, existingDomainNames, loadAllCustomDomains, diff --git a/apps/studio/src/modules/add-site/components/create-site-form.tsx b/apps/studio/src/modules/add-site/components/create-site-form.tsx index af11ab9c04..d944631382 100644 --- a/apps/studio/src/modules/add-site/components/create-site-form.tsx +++ b/apps/studio/src/modules/add-site/components/create-site-form.tsx @@ -52,6 +52,8 @@ interface CreateSiteFormProps { blueprintSuggestedDomain?: string; /** Blueprint suggested HTTPS setting from defineSiteUrl step */ blueprintSuggestedHttps?: boolean; + /** Whether the blueprint requires a custom domain (e.g., multisite) */ + blueprintRequiresCustomDomain?: boolean; /** Blueprint login credentials for pre-filling admin fields */ blueprintCredentials?: { adminUsername?: string; adminPassword?: string }; /** Called when form is submitted */ @@ -168,6 +170,7 @@ export const CreateSiteForm = ( { blueprintPreferredVersions, blueprintSuggestedDomain, blueprintSuggestedHttps, + blueprintRequiresCustomDomain, blueprintCredentials, onSubmit, onValidityChange, @@ -259,6 +262,14 @@ export const CreateSiteForm = ( { setAdvancedSettingsVisible( true ); }, [ blueprintSuggestedDomain, blueprintSuggestedHttps ] ); + useEffect( () => { + if ( ! blueprintRequiresCustomDomain ) { + return; + } + setUseCustomDomain( true ); + setAdvancedSettingsVisible( true ); + }, [ blueprintRequiresCustomDomain ] ); + useEffect( () => { if ( useCustomDomain && isCertificateTrusted ) { setEnableHttps( true ); @@ -649,11 +660,18 @@ export const CreateSiteForm = ( { type="checkbox" id="use-custom-domain" checked={ useCustomDomain } + disabled={ blueprintRequiresCustomDomain } onChange={ ( e ) => setUseCustomDomain( e.target.checked ) } /> + { blueprintRequiresCustomDomain && ( + + { __( 'WordPress multisite requires a custom domain.' ) } + + ) } +
{ __( 'Your system password will be required to set up the domain.' ) }
diff --git a/apps/studio/src/modules/add-site/components/create-site.tsx b/apps/studio/src/modules/add-site/components/create-site.tsx index e6b281c5eb..8014a786d6 100644 --- a/apps/studio/src/modules/add-site/components/create-site.tsx +++ b/apps/studio/src/modules/add-site/components/create-site.tsx @@ -22,6 +22,7 @@ interface CreateSiteProps { blueprintPreferredVersions?: BlueprintPreferredVersions; blueprintSuggestedDomain?: string; blueprintSuggestedHttps?: boolean; + blueprintRequiresCustomDomain?: boolean; blueprintCredentials?: { adminUsername?: string; adminPassword?: string }; originalDefaultVersions?: { phpVersion?: AllowedPHPVersion; @@ -40,6 +41,7 @@ export default function CreateSite( { blueprintPreferredVersions, blueprintSuggestedDomain, blueprintSuggestedHttps, + blueprintRequiresCustomDomain, blueprintCredentials, onSubmit, onValidityChange, @@ -61,6 +63,7 @@ export default function CreateSite( { blueprintPreferredVersions={ blueprintPreferredVersions } blueprintSuggestedDomain={ blueprintSuggestedDomain } blueprintSuggestedHttps={ blueprintSuggestedHttps } + blueprintRequiresCustomDomain={ blueprintRequiresCustomDomain } blueprintCredentials={ blueprintCredentials } onSubmit={ onSubmit } onValidityChange={ onValidityChange } diff --git a/apps/studio/src/modules/add-site/hooks/tests/use-blueprint-deeplink.test.tsx b/apps/studio/src/modules/add-site/hooks/tests/use-blueprint-deeplink.test.tsx index 9b11a93564..8dceb40665 100644 --- a/apps/studio/src/modules/add-site/hooks/tests/use-blueprint-deeplink.test.tsx +++ b/apps/studio/src/modules/add-site/hooks/tests/use-blueprint-deeplink.test.tsx @@ -24,6 +24,7 @@ describe( 'useBlueprintDeeplink', () => { const mockSetBlueprintSuggestedDomain = vi.fn(); const mockSetBlueprintSuggestedHttps = vi.fn(); const mockSetBlueprintSuggestedSiteName = vi.fn(); + const mockSetBlueprintRequiresCustomDomain = vi.fn(); const mockSetIsDeeplinkFlow = vi.fn(); let ipcCallback: Parameters< typeof useIpcListener >[ 1 ]; @@ -40,6 +41,7 @@ describe( 'useBlueprintDeeplink', () => { setBlueprintSuggestedDomain: mockSetBlueprintSuggestedDomain, setBlueprintSuggestedHttps: mockSetBlueprintSuggestedHttps, setBlueprintSuggestedSiteName: mockSetBlueprintSuggestedSiteName, + setBlueprintRequiresCustomDomain: mockSetBlueprintRequiresCustomDomain, setIsDeeplinkFlow: mockSetIsDeeplinkFlow, } ), { wrapper } @@ -227,8 +229,8 @@ describe( 'useBlueprintDeeplink', () => { } ); } ); - expect( mockSetBlueprintSuggestedDomain ).not.toHaveBeenCalled(); - expect( mockSetBlueprintSuggestedHttps ).not.toHaveBeenCalled(); + expect( mockSetBlueprintSuggestedDomain ).toHaveBeenCalledWith( undefined ); + expect( mockSetBlueprintSuggestedHttps ).toHaveBeenCalledWith( undefined ); } ); it( 'should set site name from setSiteOptions blogname', async () => { diff --git a/apps/studio/src/modules/add-site/hooks/use-blueprint-deeplink.ts b/apps/studio/src/modules/add-site/hooks/use-blueprint-deeplink.ts index 949ff94048..5a46a9d6c8 100644 --- a/apps/studio/src/modules/add-site/hooks/use-blueprint-deeplink.ts +++ b/apps/studio/src/modules/add-site/hooks/use-blueprint-deeplink.ts @@ -1,16 +1,13 @@ +import { generateDefaultBlueprintDescription } from '@studio/common/lib/blueprint-settings'; import { - extractFormValuesFromBlueprint, - generateDefaultBlueprintDescription, -} from '@studio/common/lib/blueprint-settings'; -import { - BlueprintValidationWarning, BlueprintPreferredVersions, + BlueprintValidationWarning, } from '@studio/common/lib/blueprint-validation'; -import { useI18n } from '@wordpress/react-i18n'; import { useCallback } from 'react'; import { useIpcListener } from 'src/hooks/use-ipc-listener'; import { getIpcApi } from 'src/lib/get-ipc-api'; import { Blueprint } from 'src/stores/wpcom-api'; +import { applyBlueprintFormValues } from '../lib/apply-blueprint-form-values'; type BlueprintMetadata = { title?: string; @@ -27,12 +24,12 @@ interface UseBlueprintDeeplinkOptions { setBlueprintSuggestedDomain: ( domain: string | undefined ) => void; setBlueprintSuggestedHttps: ( https: boolean | undefined ) => void; setBlueprintSuggestedSiteName: ( name: string | undefined ) => void; + setBlueprintRequiresCustomDomain: ( requires: boolean ) => void; setIsDeeplinkFlow: ( isDeeplink: boolean ) => void; onModalOpen?: () => void; } export function useBlueprintDeeplink( options: UseBlueprintDeeplinkOptions ): void { - const { __ } = useI18n(); const { isAnySiteProcessing, setSelectedBlueprint, @@ -43,6 +40,7 @@ export function useBlueprintDeeplink( options: UseBlueprintDeeplinkOptions ): vo setBlueprintSuggestedDomain, setBlueprintSuggestedHttps, setBlueprintSuggestedSiteName, + setBlueprintRequiresCustomDomain, setIsDeeplinkFlow, onModalOpen, } = options; @@ -80,26 +78,15 @@ export function useBlueprintDeeplink( options: UseBlueprintDeeplinkOptions ): vo setSelectedBlueprint( fileBlueprint ); - const formValues = extractFormValuesFromBlueprint( blueprintJson ); - - if ( blueprintJson.preferredVersions ) { - setBlueprintPreferredVersions( - blueprintJson.preferredVersions as BlueprintPreferredVersions - ); - } - if ( formValues.phpVersion ) { - setPhpVersion( formValues.phpVersion ); - } - if ( formValues.wpVersion ) { - setWpVersion( formValues.wpVersion ); - } - if ( formValues.customDomain ) { - setBlueprintSuggestedDomain( formValues.customDomain ); - setBlueprintSuggestedHttps( formValues.enableHttps ); - } - if ( formValues.siteName ) { - setBlueprintSuggestedSiteName( formValues.siteName ); - } + applyBlueprintFormValues( blueprintJson, { + setBlueprintPreferredVersions, + setPhpVersion, + setWpVersion, + setBlueprintSuggestedDomain, + setBlueprintSuggestedHttps, + setBlueprintSuggestedSiteName, + setBlueprintRequiresCustomDomain, + } ); setBlueprintWarnings( warnings ); setIsDeeplinkFlow( true ); @@ -118,6 +105,7 @@ export function useBlueprintDeeplink( options: UseBlueprintDeeplinkOptions ): vo setBlueprintSuggestedDomain, setBlueprintSuggestedHttps, setBlueprintSuggestedSiteName, + setBlueprintRequiresCustomDomain, setIsDeeplinkFlow, onModalOpen, ] diff --git a/apps/studio/src/modules/add-site/index.tsx b/apps/studio/src/modules/add-site/index.tsx index db8416c33b..4f1ab13dc7 100644 --- a/apps/studio/src/modules/add-site/index.tsx +++ b/apps/studio/src/modules/add-site/index.tsx @@ -33,6 +33,7 @@ import AddSiteOptions, { type AddSiteFlowType } from './components/options'; import { PullRemoteSite } from './components/pull-remote-site'; import Stepper from './components/stepper'; import { useFindAvailableSiteName } from './hooks/use-find-available-site-name'; +import { applyBlueprintFormValues } from './lib/apply-blueprint-form-values'; type BlueprintsData = ReturnType< typeof useGetBlueprints >[ 'data' ]; @@ -79,6 +80,8 @@ interface NavigationContentProps { blueprintCredentials?: { adminUsername?: string; adminPassword?: string }; blueprintSuggestedSiteName?: string; setBlueprintSuggestedSiteName?: ( name: string | undefined ) => void; + blueprintRequiresCustomDomain: boolean; + setBlueprintRequiresCustomDomain: ( requires: boolean ) => void; selectedRemoteSite?: SyncSite; setSelectedRemoteSite: ( site?: SyncSite ) => void; isDeeplinkFlow: boolean; @@ -114,6 +117,8 @@ function NavigationContent( props: NavigationContentProps ) { blueprintCredentials, blueprintSuggestedSiteName, setBlueprintSuggestedSiteName, + blueprintRequiresCustomDomain, + setBlueprintRequiresCustomDomain, selectedRemoteSite, setSelectedRemoteSite, isDeeplinkFlow, @@ -239,7 +244,7 @@ function NavigationContent( props: NavigationContentProps ) { setBlueprintSuggestedSiteName, ] ); - const applyBlueprintFormValues = useCallback( + const handleBlueprintFormValues = useCallback( ( blueprint?: Blueprint ) => { setSelectedBlueprint( blueprint ); @@ -248,20 +253,17 @@ function NavigationContent( props: NavigationContentProps ) { setBlueprintSuggestedDomain?.( undefined ); setBlueprintSuggestedHttps?.( undefined ); setBlueprintSuggestedSiteName?.( undefined ); + setBlueprintRequiresCustomDomain( false ); return; } - const formValues = extractFormValuesFromBlueprint( blueprint.blueprint ); - - if ( blueprint.blueprint.preferredVersions ) { - setBlueprintPreferredVersions?.( blueprint.blueprint.preferredVersions ); - } else { - setBlueprintPreferredVersions?.( undefined ); - } - - setBlueprintSuggestedDomain?.( formValues.customDomain ); - setBlueprintSuggestedHttps?.( formValues.enableHttps ); - setBlueprintSuggestedSiteName?.( formValues.siteName ); + applyBlueprintFormValues( blueprint.blueprint, { + setBlueprintPreferredVersions, + setBlueprintSuggestedDomain, + setBlueprintSuggestedHttps, + setBlueprintSuggestedSiteName, + setBlueprintRequiresCustomDomain, + } ); }, [ setSelectedBlueprint, @@ -269,6 +271,7 @@ function NavigationContent( props: NavigationContentProps ) { setBlueprintSuggestedDomain, setBlueprintSuggestedHttps, setBlueprintSuggestedSiteName, + setBlueprintRequiresCustomDomain, ] ); @@ -278,18 +281,18 @@ function NavigationContent( props: NavigationContentProps ) { ( b: Blueprint ) => b.slug === blueprintId ); setBlueprintWarnings?.( undefined ); - applyBlueprintFormValues( blueprint ); + handleBlueprintFormValues( blueprint ); }, - [ blueprintsData?.blueprints, setBlueprintWarnings, applyBlueprintFormValues ] + [ blueprintsData?.blueprints, setBlueprintWarnings, handleBlueprintFormValues ] ); const handleFileBlueprintSelect = useCallback( ( blueprint: Blueprint, warnings?: BlueprintValidationWarning[] ) => { - applyBlueprintFormValues( blueprint ); + handleBlueprintFormValues( blueprint ); setBlueprintWarnings?.( warnings ); goTo( '/blueprint/select/details' ); }, - [ applyBlueprintFormValues, setBlueprintWarnings, goTo ] + [ handleBlueprintFormValues, setBlueprintWarnings, goTo ] ); // Build default values with blueprint preferred versions applied @@ -357,6 +360,7 @@ function NavigationContent( props: NavigationContentProps ) { blueprintPreferredVersions={ blueprintPreferredVersions } blueprintSuggestedDomain={ blueprintSuggestedDomain } blueprintSuggestedHttps={ blueprintSuggestedHttps } + blueprintRequiresCustomDomain={ blueprintRequiresCustomDomain } /> @@ -376,6 +380,7 @@ function NavigationContent( props: NavigationContentProps ) { blueprintPreferredVersions={ blueprintPreferredVersions } blueprintSuggestedDomain={ blueprintSuggestedDomain } blueprintSuggestedHttps={ blueprintSuggestedHttps } + blueprintRequiresCustomDomain={ blueprintRequiresCustomDomain } /> @@ -469,6 +474,8 @@ export function AddSiteModalContent( { setBlueprintSuggestedHttps, blueprintSuggestedSiteName, setBlueprintSuggestedSiteName, + blueprintRequiresCustomDomain, + setBlueprintRequiresCustomDomain, selectedRemoteSite, setSelectedRemoteSite, existingDomainNames, @@ -612,6 +619,8 @@ export function AddSiteModalContent( { blueprintCredentials={ blueprintCredentials } blueprintSuggestedSiteName={ blueprintSuggestedSiteName } setBlueprintSuggestedSiteName={ setBlueprintSuggestedSiteName } + blueprintRequiresCustomDomain={ blueprintRequiresCustomDomain } + setBlueprintRequiresCustomDomain={ setBlueprintRequiresCustomDomain } selectedRemoteSite={ selectedRemoteSite } setSelectedRemoteSite={ setSelectedRemoteSite } isDeeplinkFlow={ isDeeplinkFlow } @@ -651,6 +660,7 @@ export default function AddSiteModal( { className }: AddSiteModalProps ) { setBlueprintSuggestedDomain, setBlueprintSuggestedHttps, setBlueprintSuggestedSiteName, + setBlueprintRequiresCustomDomain, setIsDeeplinkFlow, } = addSiteProps; @@ -676,6 +686,7 @@ export default function AddSiteModal( { className }: AddSiteModalProps ) { setBlueprintSuggestedDomain, setBlueprintSuggestedHttps, setBlueprintSuggestedSiteName, + setBlueprintRequiresCustomDomain, setIsDeeplinkFlow, onModalOpen: openModal, } ); diff --git a/apps/studio/src/modules/add-site/lib/apply-blueprint-form-values.ts b/apps/studio/src/modules/add-site/lib/apply-blueprint-form-values.ts new file mode 100644 index 0000000000..3922af61e6 --- /dev/null +++ b/apps/studio/src/modules/add-site/lib/apply-blueprint-form-values.ts @@ -0,0 +1,40 @@ +import { extractFormValuesFromBlueprint } from '@studio/common/lib/blueprint-settings'; +import { BlueprintPreferredVersions } from '@studio/common/lib/blueprint-validation'; +import type { Blueprint } from '@wp-playground/blueprints'; + +interface BlueprintFormValueSetters { + setBlueprintPreferredVersions: ( versions: BlueprintPreferredVersions | undefined ) => void; + setPhpVersion?: ( version: string ) => void; + setWpVersion?: ( version: string ) => void; + setBlueprintSuggestedDomain?: ( domain: string | undefined ) => void; + setBlueprintSuggestedHttps?: ( https: boolean | undefined ) => void; + setBlueprintSuggestedSiteName?: ( name: string | undefined ) => void; + setBlueprintRequiresCustomDomain: ( requires: boolean ) => void; +} + +export function applyBlueprintFormValues( + blueprintJson: Blueprint, + setters: BlueprintFormValueSetters +): void { + const formValues = extractFormValuesFromBlueprint( blueprintJson ); + + if ( blueprintJson.preferredVersions ) { + setters.setBlueprintPreferredVersions( + blueprintJson.preferredVersions as BlueprintPreferredVersions + ); + } else { + setters.setBlueprintPreferredVersions( undefined ); + } + + if ( formValues.phpVersion ) { + setters.setPhpVersion?.( formValues.phpVersion ); + } + if ( formValues.wpVersion ) { + setters.setWpVersion?.( formValues.wpVersion ); + } + + setters.setBlueprintSuggestedDomain?.( formValues.customDomain ); + setters.setBlueprintSuggestedHttps?.( formValues.enableHttps ); + setters.setBlueprintSuggestedSiteName?.( formValues.siteName ); + setters.setBlueprintRequiresCustomDomain( !! formValues.requiresCustomDomain ); +} diff --git a/tools/common/lib/blueprint-settings.ts b/tools/common/lib/blueprint-settings.ts index f966aa4ff8..b53f05fbef 100644 --- a/tools/common/lib/blueprint-settings.ts +++ b/tools/common/lib/blueprint-settings.ts @@ -8,8 +8,19 @@ type BlueprintSiteSettings = Partial< adminUsername?: string; adminPassword?: string; siteName?: string; + requiresCustomDomain?: boolean; }; +/** + * Checks if a blueprint contains the enableMultisite step. + */ +export function blueprintHasMultisite( blueprintJson: Blueprint ): boolean { + return ( + Array.isArray( blueprintJson.steps ) && + blueprintJson.steps.some( ( step: { step?: string } ) => step.step === 'enableMultisite' ) + ); +} + /** * Extracts form-relevant values from a blueprint. */ @@ -25,6 +36,10 @@ export function extractFormValuesFromBlueprint( blueprintJson: Blueprint ): Blue } } + if ( blueprintHasMultisite( blueprintJson ) ) { + values.requiresCustomDomain = true; + } + if ( blueprintJson.steps && Array.isArray( blueprintJson.steps ) ) { const defineSiteUrlStep = blueprintJson.steps.find( ( step: { step?: string } ) => step.step === 'defineSiteUrl' diff --git a/tools/common/lib/blueprint-validation.ts b/tools/common/lib/blueprint-validation.ts index 21d7ba08c0..0a73be5b55 100644 --- a/tools/common/lib/blueprint-validation.ts +++ b/tools/common/lib/blueprint-validation.ts @@ -11,13 +11,7 @@ interface UnsupportedFeature { /** * List of blueprint features that are not supported in Studio */ -const UNSUPPORTED_BLUEPRINT_FEATURES: UnsupportedFeature[] = [ - { - type: 'step', - name: 'enableMultisite', - reason: __( 'Multisite functionality is not currently supported in Studio.' ), - }, -]; +const UNSUPPORTED_BLUEPRINT_FEATURES: UnsupportedFeature[] = []; /** * Blueprint properties that are not supported in Studio diff --git a/tools/common/lib/sqlite-integration.ts b/tools/common/lib/sqlite-integration.ts index 28da188335..c5b7d40c7a 100644 --- a/tools/common/lib/sqlite-integration.ts +++ b/tools/common/lib/sqlite-integration.ts @@ -49,10 +49,15 @@ export abstract class SqliteIntegrationProvider { const sqliteSourcePath = this.getSqlitePluginSourcePath(); const dbCopyContent = await fs.readFile( path.join( sqliteSourcePath, 'db.copy' ), 'utf8' ); const sqliteDirname = this.getSqliteDirname(); - const updatedContent = dbCopyContent.replace( - "'{SQLITE_IMPLEMENTATION_FOLDER_PATH}'", - `realpath( __DIR__ . '/mu-plugins/${ sqliteDirname }' )` - ); + const updatedContent = dbCopyContent + .replace( + "'{SQLITE_IMPLEMENTATION_FOLDER_PATH}'", + `realpath( __DIR__ . '/mu-plugins/${ sqliteDirname }' )` + ) + .replace( + "define( 'SQLITE_DB_DROPIN_VERSION'", + `define( 'SQLITE_MAIN_FILE', __FILE__ );\ndefine( 'SQLITE_DB_DROPIN_VERSION'` + ); await fs.writeFile( path.join( wpContentPath, 'db.php' ), updatedContent ); const sqliteDestPath = path.join( wpContentPath, 'mu-plugins', sqliteDirname ); diff --git a/tools/common/lib/tests/blueprint-settings.test.ts b/tools/common/lib/tests/blueprint-settings.test.ts index 5e42b96e98..1146cf4b79 100644 --- a/tools/common/lib/tests/blueprint-settings.test.ts +++ b/tools/common/lib/tests/blueprint-settings.test.ts @@ -1,4 +1,5 @@ import { + blueprintHasMultisite, extractFormValuesFromBlueprint, generateDefaultBlueprintDescription, updateBlueprintWithFormValues, @@ -284,6 +285,70 @@ describe( 'blueprint-settings', () => { expect( result.siteName ).toBe( 'First Name' ); } ); + + it( 'should set requiresCustomDomain when enableMultisite step is present', () => { + const blueprint = { + steps: [ { step: 'enableMultisite' } ], + }; + + const result = extractFormValuesFromBlueprint( blueprint ); + + expect( result.requiresCustomDomain ).toBe( true ); + } ); + + it( 'should not set requiresCustomDomain when enableMultisite step is absent', () => { + const blueprint = { + steps: [ { step: 'installPlugin', pluginData: { slug: 'akismet' } } ], + }; + + const result = extractFormValuesFromBlueprint( blueprint ); + + expect( result.requiresCustomDomain ).toBeUndefined(); + } ); + } ); + + describe( 'blueprintHasMultisite', () => { + it( 'should return true when enableMultisite step is present', () => { + const blueprint = { + steps: [ { step: 'enableMultisite' } ], + }; + + expect( blueprintHasMultisite( blueprint ) ).toBe( true ); + } ); + + it( 'should return true when enableMultisite is among other steps', () => { + const blueprint = { + steps: [ + { step: 'login' }, + { step: 'enableMultisite' }, + { step: 'installPlugin', pluginData: { slug: 'akismet' } }, + ], + }; + + expect( blueprintHasMultisite( blueprint ) ).toBe( true ); + } ); + + it( 'should return false when enableMultisite is not present', () => { + const blueprint = { + steps: [ { step: 'login' } ], + }; + + expect( blueprintHasMultisite( blueprint ) ).toBe( false ); + } ); + + it( 'should return false for empty blueprint', () => { + expect( blueprintHasMultisite( {} ) ).toBe( false ); + } ); + + it( 'should return false when steps is not an array', () => { + const blueprint = { steps: 'not-an-array' }; + + expect( + blueprintHasMultisite( + blueprint as unknown as Parameters< typeof blueprintHasMultisite >[ 0 ] + ) + ).toBe( false ); + } ); } ); describe( 'updateBlueprintWithFormValues', () => { diff --git a/tools/common/lib/tests/blueprint-validation.test.ts b/tools/common/lib/tests/blueprint-validation.test.ts index c4d5cbe059..9af9fc306f 100644 --- a/tools/common/lib/tests/blueprint-validation.test.ts +++ b/tools/common/lib/tests/blueprint-validation.test.ts @@ -65,11 +65,6 @@ describe( 'validateBlueprintData', () => { it( 'should return warnings for unsupported features', async () => { const blueprint = { landingPage: '/wp-admin/', - steps: [ - { - step: 'enableMultisite', - }, - ], }; const result = await validateBlueprintData( blueprint ); @@ -78,7 +73,6 @@ describe( 'validateBlueprintData', () => { if ( result.valid ) { expect( result.warnings.length ).toBeGreaterThan( 0 ); expect( result.warnings.map( ( w ) => w.feature ) ).toContain( 'landingPage' ); - expect( result.warnings.map( ( w ) => w.feature ) ).toContain( 'enableMultisite' ); } } ); } );