Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
360784c
Add studio ui local web server reusing apps/ui with a desktop-converg…
youknowriad Jun 25, 2026
b8aaab6
Add design doc: Studio apps & surfaces (architecture, convergence, sh…
youknowriad Jun 25, 2026
ca15e3d
Harden studio ui server: restrict CORS, fix REST-proxy SSRF, drop /me…
youknowriad Jun 25, 2026
10d2612
Use const for the auth popup poll timer (prefer-const lint fix)
youknowriad Jun 25, 2026
11624cc
Share the agent-stats dedup store in app.json across desktop + studio ui
youknowriad Jun 25, 2026
ce8fe3a
Drop refactor-meta comments from the converged shared modules
youknowriad Jun 25, 2026
db7d73f
Merge remote-tracking branch 'origin/trunk' into claude/distracted-eu…
youknowriad Jun 30, 2026
3d12aa9
Split the DB-export fix and surfaces design doc into their own PRs
youknowriad Jun 30, 2026
9411b1c
Fix collapsed-sidebar toggle alignment in the browser surface
youknowriad Jun 30, 2026
39b5e32
Add the @studio/local workspace to package-lock.json
youknowriad Jun 30, 2026
59a15d0
Implement getPublishCheckoutUrl for the local connector
youknowriad Jun 30, 2026
d3bf229
Auto-connect a newly-published site on the local surface (Option B)
youknowriad Jun 30, 2026
cd3458b
Open studio ui at http://studio.localhost instead of localhost:port
youknowriad Jun 30, 2026
566953f
Drop the STUDIO_LOCAL_URL_HOST override; hardcode studio.localhost
youknowriad Jun 30, 2026
7438625
Revert to localhost:8081 for the studio ui URL
youknowriad Jun 30, 2026
006f121
Derive existing custom domains from the site list; drop getAllCustomD…
youknowriad Jun 30, 2026
f4b7eee
Derive the Xdebug-enabled site from the site list; drop getXdebugEnab…
youknowriad Jun 30, 2026
70c9d79
Merge remote-tracking branch 'origin/trunk' into claude/distracted-eu…
youknowriad Jun 30, 2026
73db67f
Merge remote-tracking branch 'origin/trunk' into claude/distracted-eu…
youknowriad Jul 1, 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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,8 @@ cli/vendor/

# Build output
dist
dist-web
dist-hosted
dist-local

# Playwright traces
test-results
Expand Down
72 changes: 72 additions & 0 deletions apps/cli/commands/ui.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { __ } from '@wordpress/i18n';
import { openBrowser } from 'cli/lib/browser';
import { StudioArgv } from 'cli/types';

export const registerCommand = ( yargs: StudioArgv ) => {
return yargs.command( {
command: 'ui',
describe: __( 'Start the local Studio web UI and open it in your browser' ),
builder: ( uiYargs: StudioArgv ) =>
uiYargs
.option( 'port', {
type: 'number',
description: __( 'Port to listen on' ),
} )
.option( 'open', {
type: 'boolean',
default: true,
description: __( 'Open the UI in your default browser' ),
} )
.option( 'path', { hidden: true } ),
handler: async ( argv ) => {
// `@studio/local` is bundled into the CLI from source (see the vite
// alias); the dynamic import keeps Express off the startup path of
// every other command.
const [ { startLocalServer }, { getAiSessionsRootDirectory }, { STUDIO_SITES_ROOT } ] =
await Promise.all( [
import( '@studio/local' ),
import( 'cli/ai/sessions/paths' ),
import( 'cli/lib/site-paths' ),
] );

// The server forks this same CLI for site + agent operations. When run
// from the packaged CLI, `process.argv[1]` is that binary;
// `STUDIO_CLI_BIN` overrides it for development.
const cliBinary = process.env.STUDIO_CLI_BIN ?? process.argv[ 1 ];

// The built browser UI (apps/ui `dist-local`) is copied next to the CLI
// bundle at build time. In dev it may be absent — the server then
// serves the API only and the UI is run via
// `npm run dev:local --workspace=apps/ui`.
const uiDist =
process.env.STUDIO_LOCAL_UI_DIST ??
path.join( path.dirname( fileURLToPath( import.meta.url ) ), 'ui' );

const server = await startLocalServer( {
cliBinary,
sessionsRoot: getAiSessionsRootDirectory(),
sitesRoot: STUDIO_SITES_ROOT,
port: argv.port as number | undefined,
uiDist,
} );

console.log( '' );
console.log( __( 'WordPress Studio is running at:' ) );
console.log( ` ${ server.url }` );
console.log( '' );
console.log( __( 'Press Ctrl+C to stop.' ) );

if ( argv.open ) {
await openBrowser( server.url ).catch( () => undefined );
}

const shutdown = () => {
void server.close().finally( () => process.exit( 0 ) );
};
process.on( 'SIGINT', shutdown );
process.on( 'SIGTERM', shutdown );
},
} );
};
2 changes: 2 additions & 0 deletions apps/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { registerCommand as registerSiteListCommand } from 'cli/commands/site/li
import { registerCommand as registerSiteStartCommand } from 'cli/commands/site/start';
import { registerCommand as registerSiteStatusCommand } from 'cli/commands/site/status';
import { registerCommand as registerSiteStopCommand } from 'cli/commands/site/stop';
import { registerCommand as registerUiCommand } from 'cli/commands/ui';
import { registerCommand as registerUninstallCommand } from 'cli/commands/uninstall';
import {
bumpAggregatedUniqueStat,
Expand Down Expand Up @@ -216,6 +217,7 @@ async function main() {
registerImportCommand( studioArgv );
registerExportCommand( studioArgv );

registerUiCommand( studioArgv );
registerUninstallCommand( studioArgv );

studioArgv.command( 'preview', __( 'Manage preview sites' ), async ( previewYargs ) => {
Expand Down
1 change: 1 addition & 0 deletions apps/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
},
"devDependencies": {
"@studio/common": "file:../../packages/common",
"@studio/local": "file:../local",
"@types/archiver": "^8.0.0",
"@types/express": "^4.17.23",
"@types/http-proxy": "^1.17.17",
Expand Down
3 changes: 2 additions & 1 deletion apps/cli/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"moduleResolution": "bundler",
"paths": {
"cli/*": [ "./*" ],
"@studio/common/*": [ "../../packages/common/*" ]
"@studio/common/*": [ "../../packages/common/*" ],
"@studio/local": [ "../local/src/index.ts" ]
}
},
"include": [ "**/*" ],
Expand Down
11 changes: 11 additions & 0 deletions apps/cli/vite.config.base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ const phpSourceCodePath = resolve( __dirname, 'php' );
// The Skill tool loads skills from `<chunk dir>/skills` at runtime (see
// `ai/skills.ts`), so they must sit directly next to the built chunks.
const skillsSourcePath = resolve( __dirname, 'ai/skills' );
// The `studio ui` command serves the built browser UI (apps/ui `dist-local`)
// from `<chunk dir>/ui`, so it must sit next to the built chunks too. Built
// separately (`npm run build:local --workspace=apps/ui`); absent in API-only
// or dev-server setups, which is fine.
const localUiDistPath = resolve( __dirname, '../ui/dist-local' );

export const baseConfig = defineConfig( {
oxc: {
Expand All @@ -56,6 +61,9 @@ export const baseConfig = defineConfig( {
if ( existsSync( skillsSourcePath ) ) {
cpSync( skillsSourcePath, resolve( outDir, 'skills' ), { recursive: true } );
}
if ( existsSync( localUiDistPath ) ) {
cpSync( localUiDistPath, resolve( outDir, 'ui' ), { recursive: true } );
}
},
},
],
Expand Down Expand Up @@ -120,6 +128,9 @@ export const baseConfig = defineConfig( {
alias: {
cli: resolve( __dirname, '.' ),
'@studio/common': resolve( __dirname, '../../packages/common' ),
// The `studio ui` local server (apps/local) is bundled into the CLI
// from source, the same way `@studio/common` is.
'@studio/local': resolve( __dirname, '../local/src' ),
'@wp-playground/blueprints/blueprint-schema-validator': resolve(
__dirname,
'../../node_modules/@wp-playground/blueprints/blueprint-schema-validator.js'
Expand Down
4 changes: 4 additions & 0 deletions apps/local/globals.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// `wpcom-xhr-request` ships no types; `@studio/common`'s sync code imports it
// (pulled in here via the syncable-sites fetch). Each app declares it for its
// own compilation, mirroring apps/cli/globals.d.ts and apps/studio.
declare module 'wpcom-xhr-request';
30 changes: 30 additions & 0 deletions apps/local/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "@studio/local",
"author": "Automattic Inc.",
"version": "0.0.0",
"productName": "Studio Local Server",
"description": "Local Studio web server (HTTP/SSE) bundled into the CLI and launched by `studio ui`",
"license": "GPL-2.0-or-later",
"private": true,
"type": "module",
"engines": {
"node": ">=22.0.0"
},
"repository": {
"type": "git",
"url": "git+https://github.com/Automattic/studio.git",
"directory": "apps/local"
},
"scripts": {
"lint": "eslint .",
"typecheck": "tsc -p tsconfig.json --noEmit"
},
"dependencies": {
"express": "^4.22.0",
"express-rate-limit": "^8.5.2"
},
"devDependencies": {
"@studio/common": "file:../../packages/common",
"@types/express": "^4.17.23"
}
}
Loading
Loading