Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
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
34 changes: 5 additions & 29 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,28 +419,8 @@ export const registerCommand = ( yargs: StudioArgv ) => {
} )
.option( 'wp', {
type: 'string',
description: __(
'WordPress version. Use "latest" to let the site auto-update, or pin a version (e.g., "6.4", "6.4.1")'
),
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
52 changes: 16 additions & 36 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,11 +1129,9 @@ export const registerCommand = ( yargs: StudioArgv ) => {
} )
.option( 'wp', {
type: 'string',
describe: __(
'WordPress version. Use "latest" to let the site auto-update, or pin a version (e.g., "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 @@ -1379,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/ );
} );
} );
63 changes: 63 additions & 0 deletions apps/cli/lib/wp-version-option.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
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.
*
* `latest` is the legacy spelling. It stays the internal value — it names a cache
* directory, a segment of the wordpress.org download URL, and the persisted
* `wpVersion` — so `auto-update` is resolved to it here, at the boundary, and
* never reaches storage. Passing `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: 1: the CLI value "auto-update". 2: the legacy CLI value "latest". Do not translate either. */
__(
'WordPress version. Use "%1$s" to let the site auto-update, or pin a version (e.g., "6.4", "6.4.1"). Replaces the legacy "%2$s" option, which still works.'
),
CLI_AUTO_UPDATE_WP_VERSION,
DEFAULT_WORDPRESS_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;
}
25 changes: 25 additions & 0 deletions apps/studio/src/components/settings-section.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { cx } from 'src/lib/cx';
import type { ReactNode } from 'react';

/**
* One titled group of settings, separated from the previous group by a rule.
* The first section omits the rule so a form doesn't open with a stray line.
*/
export function SettingsSection( {
title,
isFirst = false,
children,
}: {
title: string;
isFirst?: boolean;
children: ReactNode;
} ) {
return (
<section
className={ cx( 'flex flex-col', ! isFirst && 'mt-6 border-t border-frame-border pt-6' ) }
>
<h2 className="a8c-subtitle-small mb-3">{ title }</h2>
{ children }
</section>
);
}
Loading