Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
7567c00
Rename the "latest" WordPress version option to "Auto-update" and nam…
gcsecsey Aug 27, 2026
852ddf9
Merge remote-tracking branch 'origin/trunk' into gcsecsey/wordpress-v…
gcsecsey Aug 27, 2026
b1e2083
Fall back to the bare Auto-update label when the installed version is…
gcsecsey Aug 27, 2026
5089e5e
Show the installed version in the Auto-update label right after switc…
gcsecsey Aug 27, 2026
3cd1e55
Merge branch 'trunk' into gcsecsey/wordpress-version-expectation
gcsecsey Aug 31, 2026
4fe0936
Remove Playwright MCP page snapshots and ignore the directory
gcsecsey Aug 31, 2026
eccd3a6
Show the installed version as soon as a pinned site selects Auto-update
gcsecsey Sep 1, 2026
a4671b0
Trim narrating comments and name the Auto-update string for translators
gcsecsey Sep 1, 2026
2f1e1b5
Accept --wp auto-update as the CLI name for the auto-update mode
gcsecsey Sep 1, 2026
5b4398d
Split the WordPress version setting into an update mode and a pinned …
gcsecsey Sep 1, 2026
a7d0b6e
Show the WordPress update mode as an Automatic updates toggle in both…
gcsecsey Sep 2, 2026
470f605
Associate both toggle description lines with the Automatic updates co…
gcsecsey Sep 2, 2026
da6c1dc
Accept --wp auto-update as the CLI name for the auto-update mode
gcsecsey Sep 1, 2026
537ba04
Describe "latest" as the legacy name for the auto-update mode
gcsecsey Sep 3, 2026
132f4f6
Use radio buttons for the WordPress update mode and give the field it…
gcsecsey Sep 3, 2026
faab475
Merge remote-tracking branch 'origin/gcsecsey/accept-auto-update-wp-v…
gcsecsey Sep 3, 2026
60f4bb3
Separate the WordPress update mode radios in Studio Classic
gcsecsey Sep 3, 2026
fc37fea
Group site creation and settings fields into labeled sections
gcsecsey Sep 3, 2026
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: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ dist-local
# Playwright traces
test-results

# Page snapshots written by the Playwright MCP browser tools. Throwaway debug
# output, and it captures whatever is on screen — including site credentials.
.playwright-mcp/

# Data-heavy e2e fixtures downloaded by `npm run e2e:fixtures` (see test-fixtures/readme.md)
test-fixtures/downloads/

Expand Down
10 changes: 8 additions & 2 deletions apps/cli/commands/blueprint/use.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ import { __, _n, sprintf } from '@wordpress/i18n';
import { runCommand as runCreateSiteCommand } from 'cli/commands/site/create';
import { getDefaultSitePath } from 'cli/lib/site-paths';
import { untildify } from 'cli/lib/utils';
import {
CLI_AUTO_UPDATE_WP_VERSION,
coerceWpVersionOption,
getWpVersionOptionDescription,
} from 'cli/lib/wp-version-option';
import { Logger, LoggerError } from 'cli/logger';
import { StudioArgv } from 'cli/types';

Expand Down Expand Up @@ -162,8 +167,9 @@ export const registerCommand = ( yargs: StudioArgv ) => {
} )
.option( 'wp', {
type: 'string',
describe: __( 'WordPress version' ),
defaultDescription: DEFAULT_WORDPRESS_VERSION,
describe: getWpVersionOptionDescription(),
defaultDescription: CLI_AUTO_UPDATE_WP_VERSION,
coerce: coerceWpVersionOption,
} )
.option( 'php', {
type: 'string',
Expand Down
32 changes: 5 additions & 27 deletions apps/cli/commands/config/set.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DEFAULT_WORDPRESS_VERSION, MINIMUM_WORDPRESS_VERSION } from '@studio/common/constants';
import { DEFAULT_WORDPRESS_VERSION } from '@studio/common/constants';
import { SITE_EVENTS } from '@studio/common/lib/cli-events';
import { getDomainNameValidationError } from '@studio/common/lib/domains';
import { arePathsEqual } from '@studio/common/lib/fs-utils';
Expand All @@ -24,11 +24,7 @@ import {
siteRuntimeFromMode,
type SiteMode,
} from '@studio/common/lib/site-runtime';
import {
getWordPressVersionUrl,
isValidWordPressVersion,
isWordPressVersionAtLeast,
} from '@studio/common/lib/wordpress-version-utils';
import { getWordPressVersionUrl } from '@studio/common/lib/wordpress-version-utils';
import { SiteCommandLoggerAction as LoggerAction } from '@studio/common/logger-actions';
import { SupportedPHPVersions } from '@studio/common/types/php-versions';
import { __, sprintf } from '@wordpress/i18n';
Expand All @@ -46,12 +42,12 @@ import { validateSupportedPhpVersion } from 'cli/lib/php-versions';
import { runWpCliCommand } from 'cli/lib/run-wp-cli-command';
import { withSiteOperation } from 'cli/lib/site-operations';
import { setupCustomDomain } from 'cli/lib/site-utils';
import { ValidationError } from 'cli/lib/validation-error';
import {
isServerRunning,
startWordPressServer,
stopWordPressServer,
} from 'cli/lib/wordpress-server-manager';
import { coerceWpVersionOption, getWpVersionOptionDescription } from 'cli/lib/wp-version-option';
import { Logger, LoggerError } from 'cli/logger';
import { StudioArgv } from 'cli/types';

Expand Down Expand Up @@ -423,26 +419,8 @@ export const registerCommand = ( yargs: StudioArgv ) => {
} )
.option( 'wp', {
type: 'string',
description: __( 'WordPress version' ),
coerce: ( value: string ) => {
if ( ! isValidWordPressVersion( value ) ) {
throw new ValidationError(
'wp',
value,
__(
'Must be: "latest", "nightly", or a valid version number (e.g., "6.4", "6.4.1", "6.4-beta1")'
)
);
}
if ( ! isWordPressVersionAtLeast( value, MINIMUM_WORDPRESS_VERSION ) ) {
throw new ValidationError(
'wp',
value,
sprintf( __( 'Must be: at least %s' ), MINIMUM_WORDPRESS_VERSION )
);
}
return value;
},
description: getWpVersionOptionDescription(),
coerce: coerceWpVersionOption,
} )
.option( 'runtime', {
type: 'string',
Expand Down
50 changes: 16 additions & 34 deletions apps/cli/commands/site/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import crypto from 'crypto';
import fs from 'fs';
import path from 'path';
import { confirm, input, password, select } from '@inquirer/prompts';
import { DEFAULT_WORDPRESS_VERSION, MINIMUM_WORDPRESS_VERSION } from '@studio/common/constants';
import { DEFAULT_WORDPRESS_VERSION } from '@studio/common/constants';
import { installAiInstructionsToSite } from '@studio/common/lib/agent-skills';
import {
createBlueprintTempDirSync,
Expand Down Expand Up @@ -53,10 +53,6 @@ import {
} from '@studio/common/lib/site-runtime';
import { sortSites } from '@studio/common/lib/sort-sites';
import { getServerFilesPath } from '@studio/common/lib/well-known-paths';
import {
isValidWordPressVersion,
isWordPressVersionAtLeast,
} from '@studio/common/lib/wordpress-version-utils';
import { fetchWordPressVersions } from '@studio/common/lib/wordpress-versions';
import { SiteCommandLoggerAction as LoggerAction } from '@studio/common/logger-actions';
import {
Expand Down Expand Up @@ -98,6 +94,11 @@ import { StatsGroup } from 'cli/lib/types/bump-stats';
import { untildify } from 'cli/lib/utils';
import { ValidationError } from 'cli/lib/validation-error';
import { runBlueprint, startWordPressServer } from 'cli/lib/wordpress-server-manager';
import {
CLI_AUTO_UPDATE_WP_VERSION,
coerceWpVersionOption,
getWpVersionOptionDescription,
} from 'cli/lib/wp-version-option';
import { Logger, LoggerError } from 'cli/logger';
import { StudioArgv } from 'cli/types';

Expand Down Expand Up @@ -1110,28 +1111,6 @@ function coerceSiteId( value: string ) {
return value;
}

function coerceWpVersion( value: string ) {
if ( ! isValidWordPressVersion( value ) ) {
throw new ValidationError(
'wp',
value,
__(
'Must be: "latest", "nightly", or a valid version number (e.g., "6.4", "6.4.1", "6.4-beta1")'
)
);
}

if ( ! isWordPressVersionAtLeast( value, MINIMUM_WORDPRESS_VERSION ) ) {
throw new ValidationError(
'wp',
value,
sprintf( __( 'Must be: at least %s' ), MINIMUM_WORDPRESS_VERSION )
);
}

return value;
}

export const registerCommand = ( yargs: StudioArgv ) => {
return yargs.command( {
command: 'create',
Expand All @@ -1150,9 +1129,9 @@ export const registerCommand = ( yargs: StudioArgv ) => {
} )
.option( 'wp', {
type: 'string',
describe: __( 'WordPress version (e.g., "latest", "6.4", "6.4.1")' ),
defaultDescription: DEFAULT_WORDPRESS_VERSION,
coerce: coerceWpVersion,
describe: getWpVersionOptionDescription(),
defaultDescription: CLI_AUTO_UPDATE_WP_VERSION,
coerce: coerceWpVersionOption,
} )
.option( 'php', {
type: 'string',
Expand Down Expand Up @@ -1377,15 +1356,18 @@ export const registerCommand = ( yargs: StudioArgv ) => {
try {
const versions = await fetchWordPressVersions();
wpChoices = versions.map( ( v ) => ( {
name: v.value === 'latest' ? `latest (${ v.label })` : v.label,
name:
v.value === DEFAULT_WORDPRESS_VERSION
? sprintf( __( 'Auto-update (%s)' ), v.label )
: v.label,
value: v.value,
} ) );
} catch {
// Offline or API failure — offer only "latest"
// Offline or API failure — offer only the auto-update mode
wpChoices = [
{
name: __( 'Latest (recommended)' ),
value: 'latest',
name: __( 'Auto-update (recommended)' ),
value: DEFAULT_WORDPRESS_VERSION,
},
];
}
Expand Down
46 changes: 46 additions & 0 deletions apps/cli/lib/tests/wp-version-option.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { DEFAULT_WORDPRESS_VERSION } from '@studio/common/constants';
import { ValidationError } from 'cli/lib/validation-error';
import {
CLI_AUTO_UPDATE_WP_VERSION,
coerceWpVersionOption,
normalizeCliWpVersion,
} from 'cli/lib/wp-version-option';

describe( 'normalizeCliWpVersion', () => {
it( 'resolves the auto-update alias to the internal mode value', () => {
expect( normalizeCliWpVersion( CLI_AUTO_UPDATE_WP_VERSION ) ).toBe( DEFAULT_WORDPRESS_VERSION );
} );

it( 'passes every other value through untouched', () => {
for ( const value of [ 'latest', 'nightly', '6.4', '6.4.1', '6.4-beta1' ] ) {
expect( normalizeCliWpVersion( value ) ).toBe( value );
}
} );
} );

describe( 'coerceWpVersionOption', () => {
it( 'accepts the auto-update value and returns the internal mode', () => {
expect( coerceWpVersionOption( CLI_AUTO_UPDATE_WP_VERSION ) ).toBe( DEFAULT_WORDPRESS_VERSION );
} );

it( 'still accepts `latest`, so existing scripts keep working', () => {
expect( coerceWpVersionOption( 'latest' ) ).toBe( DEFAULT_WORDPRESS_VERSION );
} );

it( 'accepts pinned versions and nightly', () => {
expect( coerceWpVersionOption( '6.4.1' ) ).toBe( '6.4.1' );
expect( coerceWpVersionOption( 'nightly' ) ).toBe( 'nightly' );
} );

it( 'rejects an unknown value', () => {
expect( () => coerceWpVersionOption( 'auto' ) ).toThrow( ValidationError );
} );

it( 'rejects a version below the supported minimum', () => {
expect( () => coerceWpVersionOption( '4.9' ) ).toThrow( ValidationError );
} );

it( 'reports the value the user typed, not the resolved one', () => {
expect( () => coerceWpVersionOption( 'auto-updates' ) ).toThrow( /auto-updates/ );
} );
} );
62 changes: 62 additions & 0 deletions apps/cli/lib/wp-version-option.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { DEFAULT_WORDPRESS_VERSION, MINIMUM_WORDPRESS_VERSION } from '@studio/common/constants';
import {
isValidWordPressVersion,
isWordPressVersionAtLeast,
} from '@studio/common/lib/wordpress-version-utils';
import { __, sprintf } from '@wordpress/i18n';
import { ValidationError } from 'cli/lib/validation-error';

/**
* CLI-facing name for the auto-update mode, matching the "Auto-update" option in
* the apps.
*
* Internally the mode stays `latest`: it names a cache directory, a segment of
* the wordpress.org download URL, and the persisted `wpVersion`. The alias is
* resolved here so it never reaches storage, and `latest` keeps working for
* existing scripts.
*/
export const CLI_AUTO_UPDATE_WP_VERSION = 'auto-update';

export function normalizeCliWpVersion( value: string ): string {
return value === CLI_AUTO_UPDATE_WP_VERSION ? DEFAULT_WORDPRESS_VERSION : value;
}

/** Description for the `--wp` option, shared so both commands read alike. */
export function getWpVersionOptionDescription(): string {
return sprintf(
/* translators: %s: the literal CLI value "auto-update", do not translate. */
__(
'WordPress version. Use "%s" to let the site auto-update, or pin a version (e.g., "6.4", "6.4.1"). "latest" is accepted as an alias.'
),
CLI_AUTO_UPDATE_WP_VERSION
);
}

/** Resolves the auto-update alias, then validates. Returns the internal value. */
export function coerceWpVersionOption( value: string ): string {
const version = normalizeCliWpVersion( value );

if ( ! isValidWordPressVersion( version ) ) {
throw new ValidationError(
'wp',
value,
sprintf(
/* translators: %s: the literal CLI value "auto-update", do not translate. */
__(
'Must be: "%s", "nightly", or a valid version number (e.g., "6.4", "6.4.1", "6.4-beta1")'
),
CLI_AUTO_UPDATE_WP_VERSION
)
);
}

if ( ! isWordPressVersionAtLeast( version, MINIMUM_WORDPRESS_VERSION ) ) {
throw new ValidationError(
'wp',
value,
sprintf( __( 'Must be: at least %s' ), MINIMUM_WORDPRESS_VERSION )
);
}

return version;
}
14 changes: 8 additions & 6 deletions apps/studio/src/components/wp-version-selector/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { DEFAULT_WORDPRESS_VERSION, MINIMUM_WORDPRESS_VERSION } from '@studio/common/constants';
import { getAutoUpdateVersionLabel } from '@studio/common/lib/wordpress-version-labels';
import { isWordPressBetaVersion } from '@studio/common/lib/wordpress-version-utils';
import { SelectControl, Icon } from '@wordpress/components';
import { info } from '@wordpress/icons';
Expand All @@ -23,6 +24,8 @@ type WPVersionSelectorProps = {
fallbackOptions: { label: string; value: string }[];
/** Custom message to show when offline. If not provided, will use the default message */
offlineMessage?: string;
/** Shown in the auto-update label. Only pass it for auto-updating sites. */
installedVersion?: string;
};

export const WPVersionSelector = ( {
Expand All @@ -33,6 +36,7 @@ export const WPVersionSelector = ( {
extraOptions,
fallbackOptions,
offlineMessage,
installedVersion,
}: WPVersionSelectorProps ) => {
const { __ } = useI18n();
const isOffline = useOffline();
Expand Down Expand Up @@ -76,7 +80,7 @@ export const WPVersionSelector = ( {
<label className="flex flex-1 flex-col gap-1.5 leading-4">
<span className="font-semibold flex items-center gap-0.5">
{ __( 'WordPress version' ) }
{ selectedValue !== 'latest' && (
{ selectedValue !== DEFAULT_WORDPRESS_VERSION && (
<Tooltip
text={ __( 'WordPress Core automatic updates will be disabled for this site.' ) }
placement="top-start"
Expand All @@ -102,11 +106,9 @@ export const WPVersionSelector = ( {
>
{ wpVersions.length > 0 ? (
<>
<optgroup label={ __( 'Auto-updating' ) }>
<option key={ DEFAULT_WORDPRESS_VERSION } value={ DEFAULT_WORDPRESS_VERSION }>
{ __( 'latest' ) }
</option>
</optgroup>
<option key={ DEFAULT_WORDPRESS_VERSION } value={ DEFAULT_WORDPRESS_VERSION }>
{ getAutoUpdateVersionLabel( installedVersion ) }
</option>
<optgroup label={ __( 'Beta & Nightly' ) }>
{ betaVersions.map( ( { label, value } ) => (
<option key={ value } value={ value }>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
SITE_RUNTIME_PLAYGROUND,
type SiteRuntime,
} from '@studio/common/lib/site-runtime';
import { getAutoUpdateVersionLabel } from '@studio/common/lib/wordpress-version-labels';
import {
RecommendedPHPVersion,
SupportedPHPVersion,
Expand Down Expand Up @@ -518,7 +519,7 @@ export const CreateSiteForm = ( {
selectedValue={ wpVersion }
onChange={ setWpVersion }
fallbackOptions={ [
{ label: __( 'Latest' ), value: DEFAULT_WORDPRESS_VERSION },
{ label: getAutoUpdateVersionLabel(), value: DEFAULT_WORDPRESS_VERSION },
] }
offlineMessage={ __(
'You are currently offline so your site will be created with the latest version. Selecting a different WordPress version requires an internet connection.'
Expand Down
5 changes: 5 additions & 0 deletions apps/studio/src/modules/site-settings/edit-site-details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,11 @@ const EditSiteDetails = ( { currentWpVersion, onSave }: EditSiteDetailsProps ) =
onChange={ setSelectedWpVersion }
disabled={ isEditingSite }
errorMessage={ errorUpdatingWpVersion }
installedVersion={
selectedWpVersion === DEFAULT_WORDPRESS_VERSION
? currentWpVersion
: undefined
}
extraOptions={ [
{
label: currentWpVersion,
Expand Down
Loading