Skip to content
12 changes: 12 additions & 0 deletions apps/cli/ai/tests/tools.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { mkdir, mkdtemp, readFile, rm, stat, writeFile } from 'fs/promises';
import os from 'os';
import path from 'path';
import { SITE_RUNTIME_PLAYGROUND } from '@studio/common/lib/site-runtime';
import { vi } from 'vitest';
import { getSharedBrowser } from 'cli/ai/browser-utils';
import { emitEvent } from 'cli/ai/json-events';
Expand Down Expand Up @@ -636,6 +637,7 @@ describe( 'Studio AI MCP tools', () => {
pmId: 1,
status: 'online',
pid: 1234,
runtime: SITE_RUNTIME_PLAYGROUND,
} );

await expect(
Expand All @@ -655,6 +657,7 @@ describe( 'Studio AI MCP tools', () => {
pmId: 1,
status: 'online',
pid: 1234,
runtime: SITE_RUNTIME_PLAYGROUND,
} );
vi.mocked( sendWpCliCommand ).mockResolvedValue( {
stdout: '123',
Expand Down Expand Up @@ -685,6 +688,7 @@ describe( 'Studio AI MCP tools', () => {
pmId: 1,
status: 'online',
pid: 1234,
runtime: SITE_RUNTIME_PLAYGROUND,
} );
vi.mocked( sendWpCliCommand ).mockResolvedValue( {
stdout: '123',
Expand Down Expand Up @@ -712,6 +716,7 @@ describe( 'Studio AI MCP tools', () => {
pmId: 1,
status: 'online',
pid: 1234,
runtime: SITE_RUNTIME_PLAYGROUND,
} );
vi.mocked( sendWpCliCommand ).mockResolvedValue( {
stdout: '123',
Expand Down Expand Up @@ -741,6 +746,7 @@ describe( 'Studio AI MCP tools', () => {
pmId: 1,
status: 'online',
pid: 1234,
runtime: SITE_RUNTIME_PLAYGROUND,
} );
vi.mocked( sendWpCliCommand ).mockResolvedValue( {
stdout: '123',
Expand Down Expand Up @@ -769,6 +775,7 @@ describe( 'Studio AI MCP tools', () => {
pmId: 1,
status: 'online',
pid: 1234,
runtime: SITE_RUNTIME_PLAYGROUND,
} );
vi.mocked( sendWpCliCommand ).mockResolvedValue( {
stdout: '123',
Expand Down Expand Up @@ -801,6 +808,7 @@ describe( 'Studio AI MCP tools', () => {
pmId: 1,
status: 'online',
pid: 1234,
runtime: SITE_RUNTIME_PLAYGROUND,
} );
vi.mocked( sendWpCliCommand ).mockResolvedValue( {
stdout: '123',
Expand Down Expand Up @@ -833,6 +841,7 @@ describe( 'Studio AI MCP tools', () => {
pmId: 1,
status: 'online',
pid: 1234,
runtime: SITE_RUNTIME_PLAYGROUND,
} );

await expect(
Expand Down Expand Up @@ -991,6 +1000,7 @@ describe( 'Studio AI MCP tools', () => {
pmId: 1,
status: 'online',
pid: 1234,
runtime: SITE_RUNTIME_PLAYGROUND,
} );
vi.mocked( sendWpCliCommand ).mockResolvedValue( {
stdout: "Success: Switched to 'Acme Studio' theme.",
Expand Down Expand Up @@ -1020,6 +1030,7 @@ describe( 'Studio AI MCP tools', () => {
pmId: 1,
status: 'online',
pid: 1234,
runtime: SITE_RUNTIME_PLAYGROUND,
} );

const result = await getTool( 'scaffold_theme' ).rawHandler( {
Expand Down Expand Up @@ -1056,6 +1067,7 @@ describe( 'Studio AI MCP tools', () => {
pmId: 1,
status: 'online',
pid: 1234,
runtime: SITE_RUNTIME_PLAYGROUND,
} );
vi.mocked( sendWpCliCommand ).mockResolvedValue( {
stdout: '',
Expand Down
47 changes: 23 additions & 24 deletions apps/cli/commands/site/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,7 @@ import crypto from 'crypto';
import fs from 'fs';
import path from 'path';
import { confirm, input, password, select } from '@inquirer/prompts';
import { SupportedPHPVersions } from '@php-wasm/universal';
import {
DEFAULT_PHP_VERSION,
DEFAULT_WORDPRESS_VERSION,
MINIMUM_WORDPRESS_VERSION,
} from '@studio/common/constants';
import { DEFAULT_WORDPRESS_VERSION, MINIMUM_WORDPRESS_VERSION } from '@studio/common/constants';
import { installAiInstructionsToSite } from '@studio/common/lib/agent-skills';
import { extractFormValuesFromBlueprint } from '@studio/common/lib/blueprint-settings';
import { validateBlueprintData } from '@studio/common/lib/blueprint-validation';
Expand Down Expand Up @@ -43,6 +38,7 @@ import {
} 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 { type SupportedPHPVersion } from '@studio/common/types/php-versions';
import { __, sprintf } from '@wordpress/i18n';
import { isStepDefinition, type BlueprintV1Declaration } from '@wp-playground/blueprints';
import { bumpStat, getPlatformMetric } from 'cli/lib/bump-stat';
Expand All @@ -51,8 +47,6 @@ import {
readCliConfig,
saveCliConfig,
SiteData,
SiteRuntime,
siteRuntimeSchema,
unlockCliConfig,
} from 'cli/lib/cli-config/core';
import {
Expand All @@ -64,6 +58,11 @@ import { connectToDaemon, disconnectFromDaemon, emitCliEvent } from 'cli/lib/dae
import { getAiInstructionsPath } from 'cli/lib/dependency-management/paths';
import { updateServerFiles } from 'cli/lib/dependency-management/setup';
import { copyLanguagePackToSite } from 'cli/lib/language-packs';
import {
getRecommendedPhpVersionForSiteRuntime,
getSupportedPhpVersionsForSiteRuntime,
validatePhpVersionForSiteRuntime,
} from 'cli/lib/php-versions';
import { getPreferredSiteLanguage } from 'cli/lib/site-language';
import { generateSiteName } from 'cli/lib/site-name';
import { getDefaultSitePath } from 'cli/lib/site-paths';
Expand All @@ -76,15 +75,13 @@ import { runBlueprint, startWordPressServer } from 'cli/lib/wordpress-server-man
import { Logger, LoggerError } from 'cli/logger';
import { StudioArgv } from 'cli/types';

const ALLOWED_PHP_VERSIONS = [ ...SupportedPHPVersions ];

const logger = new Logger< LoggerAction >();

export type CreateCommandOptions = {
name?: string;
siteId?: string;
wpVersion: string;
phpVersion: ( typeof ALLOWED_PHP_VERSIONS )[ number ];
phpVersion: SupportedPHPVersion;
customDomain?: string;
enableHttps: boolean;
blueprint?: {
Expand All @@ -99,14 +96,11 @@ export type CreateCommandOptions = {
skipLogDetails: boolean;
};

function resolveRuntimeFromEnv(): SiteRuntime {
return siteRuntimeSchema.catch( 'playground' ).parse( process.env.STUDIO_RUNTIME );
}

export async function runCommand(
sitePath: string,
options: CreateCommandOptions
): Promise< void > {
const phpVersion = validatePhpVersionForSiteRuntime( options.phpVersion );
const isOnlineStatus = await isOnline();

try {
Expand Down Expand Up @@ -292,13 +286,12 @@ export async function runCommand(
adminPassword,
adminEmail,
port,
phpVersion: options.phpVersion,
phpVersion,
running: false,
isWpAutoUpdating: options.wpVersion === DEFAULT_WORDPRESS_VERSION,
customDomain: options.customDomain,
enableHttps: options.enableHttps,
landingPage: normalizeLandingPage( blueprint?.landingPage ),
runtime: resolveRuntimeFromEnv(),
};

logger.reportStart( LoggerAction.SAVE_SITE, __( 'Saving site…' ) );
Expand Down Expand Up @@ -484,6 +477,8 @@ export const registerCommand = ( yargs: StudioArgv ) => {
command: 'create',
describe: __( 'Create a new site' ),
builder: ( yargs ) => {
const supportedPhpVersions = getSupportedPhpVersionsForSiteRuntime();
const recommendedPhpVersion = getRecommendedPhpVersionForSiteRuntime();
return yargs
.option( 'id', {
type: 'string',
Expand All @@ -504,8 +499,8 @@ export const registerCommand = ( yargs: StudioArgv ) => {
.option( 'php', {
type: 'string',
describe: __( 'PHP version' ),
choices: ALLOWED_PHP_VERSIONS,
defaultDescription: DEFAULT_PHP_VERSION,
choices: supportedPhpVersions,
defaultDescription: recommendedPhpVersion,
} )
.option( 'domain', {
type: 'string',
Expand Down Expand Up @@ -564,6 +559,8 @@ export const registerCommand = ( yargs: StudioArgv ) => {
let adminUsername = argv.adminUsername;
let adminPassword = argv.adminPassword;
let adminEmail = argv.adminEmail;
const supportedPhpVersions = getSupportedPhpVersionsForSiteRuntime();
const recommendedPhpVersion = getRecommendedPhpVersionForSiteRuntime();

// Validate and resolve the WordPress version against available versions before prompting
if ( wpVersion && wpVersion !== 'latest' && wpVersion !== 'nightly' ) {
Expand Down Expand Up @@ -662,11 +659,11 @@ export const registerCommand = ( yargs: StudioArgv ) => {
if ( ! phpVersion ) {
phpVersion = await select( {
message: __( 'PHP version:' ),
choices: ALLOWED_PHP_VERSIONS.map( ( v ) => ( {
name: v === DEFAULT_PHP_VERSION ? sprintf( __( '%s (recommended)' ), v ) : v,
choices: supportedPhpVersions.map( ( v ) => ( {
name: v === recommendedPhpVersion ? sprintf( __( '%s (recommended)' ), v ) : v,
value: v,
} ) ),
default: DEFAULT_PHP_VERSION,
default: recommendedPhpVersion,
} );
}

Expand Down Expand Up @@ -723,13 +720,15 @@ export const registerCommand = ( yargs: StudioArgv ) => {

// Apply defaults for non-interactive mode when flags weren't provided
wpVersion = wpVersion ?? DEFAULT_WORDPRESS_VERSION;
phpVersion = phpVersion ?? DEFAULT_PHP_VERSION;
const resolvedPhpVersion = validatePhpVersionForSiteRuntime(
phpVersion ?? recommendedPhpVersion
);

const config: CreateCommandOptions = {
name: siteName,
siteId: argv.id,
wpVersion,
phpVersion,
phpVersion: resolvedPhpVersion,
customDomain,
enableHttps,
adminUsername,
Expand Down
15 changes: 9 additions & 6 deletions apps/cli/commands/site/set.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { SupportedPHPVersions } from '@php-wasm/universal';
import { DEFAULT_WORDPRESS_VERSION, MINIMUM_WORDPRESS_VERSION } from '@studio/common/constants';
import { SITE_EVENTS } from '@studio/common/lib/cli-events';
import { getDomainNameValidationError } from '@studio/common/lib/domains';
Expand Down Expand Up @@ -26,6 +25,10 @@ import {
import { getSiteByFolder, updateSiteLatestCliPid } from 'cli/lib/cli-config/sites';
import { connectToDaemon, disconnectFromDaemon, emitCliEvent } from 'cli/lib/daemon-client';
import { updateDomainInHosts } from 'cli/lib/hosts-file';
import {
getSupportedPhpVersionsForSiteRuntime,
validatePhpVersionForSiteRuntime,
} from 'cli/lib/php-versions';
import { runWpCliCommand } from 'cli/lib/run-wp-cli-command';
import { setupCustomDomain } from 'cli/lib/site-utils';
import { ValidationError } from 'cli/lib/validation-error';
Expand All @@ -37,8 +40,6 @@ import {
import { Logger, LoggerError } from 'cli/logger';
import { StudioArgv } from 'cli/types';

const ALLOWED_PHP_VERSIONS = [ ...SupportedPHPVersions ];

const logger = new Logger< LoggerAction >();

export interface SetCommandOptions {
Expand Down Expand Up @@ -69,6 +70,7 @@ export async function runCommand( sitePath: string, options: SetCommandOptions )
debugDisplay,
} = options;
let { adminEmail } = options;
const validatedPhp = php === undefined ? undefined : validatePhpVersionForSiteRuntime( php );

if (
name === undefined &&
Expand Down Expand Up @@ -159,7 +161,7 @@ export async function runCommand( sitePath: string, options: SetCommandOptions )
const nameChanged = name !== undefined && name !== site.name;
const domainChanged = domain !== undefined && domain !== site.customDomain;
const httpsChanged = https !== undefined && https !== site.enableHttps;
const phpChanged = php !== undefined && php !== site.phpVersion;
const phpChanged = validatedPhp !== undefined && validatedPhp !== site.phpVersion;
const wpChanged = wp !== undefined;
const xdebugChanged = xdebug !== undefined && xdebug !== site.enableXdebug;
const adminUsernameChanged =
Expand Down Expand Up @@ -217,7 +219,7 @@ export async function runCommand( sitePath: string, options: SetCommandOptions )
foundSite.enableHttps = https;
}
if ( phpChanged ) {
foundSite.phpVersion = php!;
foundSite.phpVersion = validatedPhp!;
}
if ( xdebugChanged ) {
foundSite.enableXdebug = xdebug;
Expand Down Expand Up @@ -327,6 +329,7 @@ export const registerCommand = ( yargs: StudioArgv ) => {
command: 'set',
describe: __( 'Configure site settings' ),
builder: ( yargs ) => {
const supportedPhpVersions = getSupportedPhpVersionsForSiteRuntime();
return yargs
.option( 'name', {
type: 'string',
Expand All @@ -343,7 +346,7 @@ export const registerCommand = ( yargs: StudioArgv ) => {
.option( 'php', {
type: 'string',
description: __( 'PHP version' ),
choices: ALLOWED_PHP_VERSIONS,
choices: supportedPhpVersions,
} )
.option( 'wp', {
type: 'string',
Expand Down
Loading