Skip to content
2 changes: 1 addition & 1 deletion apps/cli/commands/site/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -659,7 +659,7 @@ export async function runCommand(

try {
if ( isOnlineStatus ) {
const updated = await updateServerFiles();
const updated = await updateServerFiles( options.wpVersion === DEFAULT_WORDPRESS_VERSION );
if ( updated ) {
logger.reportSuccess( __( 'Dependencies updated' ) );
}
Expand Down
8 changes: 6 additions & 2 deletions apps/cli/lib/dependency-management/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,13 @@ async function markDependencyCheckTime(): Promise< void > {
/**
* Checks for and applies dependency updates (e.g. WordPress versions), throttled
* to at most once per 24 hours. Returns true if the check ran, false if skipped.
*
* @param force Bypass the throttle. Site creation passes this for "latest",
* which installs by copying the cached directory instead of
* downloading a numbered release, so the cache has to be current.
*/
export async function updateServerFiles(): Promise< boolean > {
if ( ! ( await shouldCheckDependencyUpdates() ) ) {
export async function updateServerFiles( force = false ): Promise< boolean > {
if ( ! force && ! ( await shouldCheckDependencyUpdates() ) ) {
return false;
}

Expand Down
16 changes: 16 additions & 0 deletions apps/cli/lib/dependency-management/tests/setup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,22 @@ describe( 'updateServerFiles', () => {
expect( updateLatestWordPressVersion ).not.toHaveBeenCalled();
} );

it( 'runs the update inside the interval when forced', async () => {
// Creating a site on "latest" installs by copying the cached directory,
// so it has to verify the cache is current no matter when we last looked.
vi.mocked( readCliConfig ).mockResolvedValue( {
version: 1,
sites: [],
snapshots: [],
lastDependencyCheckTime: NOW - 60 * 1000,
} );

const result = await updateServerFiles( true );

expect( result ).toBe( true );
expect( updateLatestWordPressVersion ).toHaveBeenCalledTimes( 1 );
} );

it( 'runs the update when the timestamp is in the future (clock skew)', async () => {
vi.mocked( readCliConfig ).mockResolvedValue( {
version: 1,
Expand Down
7 changes: 5 additions & 2 deletions packages/common/lib/mu-plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -362,8 +362,11 @@ function getStandardMuPlugins( options: MuPluginOptions ): MuPlugin[] {
`,
} );

// Configure auto-updates based on Studio settings
if ( ! options.isWpAutoUpdating ) {
// Only an explicit `false` disables auto-updates. Sites created before this
// option existed have no flag, and every other reader treats that as
// auto-updating — reading it as falsy here left them pinned for good while
// Settings showed "latest" (STU-2348).
if ( options.isWpAutoUpdating === false ) {
muPlugins.push( {
filename: '0-disable-auto-updates.php',
content: `<?php
Expand Down
18 changes: 18 additions & 0 deletions packages/common/lib/tests/mu-plugins.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,24 @@ describe( 'writeStudioMuPluginsForNativePhpRuntime', () => {
expect( generatedPlugins ).not.toContain( '0-disable-auto-updates.php' );
} );

it.each( [
[ 'enables them when the flag is true', true, '0-enable-auto-updates.php' ],
[ 'disables them only on an explicit false', false, '0-disable-auto-updates.php' ],
// Sites created before the flag existed have no value. The settings UI reads
// that as auto-updating, so the mu-plugins must agree (STU-2348).
[ 'enables them when the flag is unset', undefined, '0-enable-auto-updates.php' ],
] )( 'auto-updates: %s', async ( _label, flag, expected ) => {
const muPluginsDir = await writeStudioMuPluginsForNativePhpRuntime( sitePath, flag );
const generatedPlugins = await readdir( muPluginsDir );

const other =
expected === '0-enable-auto-updates.php'
? '0-disable-auto-updates.php'
: '0-enable-auto-updates.php';
expect( generatedPlugins ).toContain( expected );
expect( generatedPlugins ).not.toContain( other );
} );

it( 'should reuse the existing mu-plugins directory when contents are up to date', async () => {
const firstDir = await writeStudioMuPluginsForNativePhpRuntime( sitePath, false );
const secondDir = await writeStudioMuPluginsForNativePhpRuntime( sitePath, false );
Expand Down