Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
122 commits
Select commit Hold shift + click to select a range
2743dc8
Abort async operations on SIGTERM/SIGINT
fredrikekelund Dec 18, 2025
d34ee1e
Also send abort message to child server
fredrikekelund Dec 18, 2025
9a31e58
Only create AbortController when topic is not `abort`
fredrikekelund Dec 18, 2025
61f3256
Fix
fredrikekelund Dec 18, 2025
f81ba0c
More fixes
fredrikekelund Dec 18, 2025
bd6b4d9
Tweaks
fredrikekelund Dec 18, 2025
eb32b3b
Merge branch 'dev/studio-cli-i2' into f26d/cli-abort-async-operations
fredrikekelund Dec 18, 2025
a6a7e7a
Fix unit tests
fredrikekelund Dec 18, 2025
27bd5c3
Merge branch 'dev/studio-cli-i2' into f26d/cli-abort-async-operations
fredrikekelund Dec 19, 2025
569058f
Fix types
fredrikekelund Dec 19, 2025
8786163
Remove `this.sessionPath` files individually
fredrikekelund Dec 19, 2025
abbcfc8
Stop running servers in a detached process
fredrikekelund Dec 19, 2025
fa0537e
Revert "Remove `this.sessionPath` files individually"
fredrikekelund Dec 19, 2025
c984234
Retry
fredrikekelund Dec 19, 2025
87e62ec
Increase timeouts
fredrikekelund Dec 19, 2025
d42d25e
Fix deprecated blueprint syntax
fredrikekelund Dec 19, 2025
3f9ef30
Merge branch 'dev/studio-cli-i2' into f26d/fix-e2e-tests-windows
fredrikekelund Dec 19, 2025
10955cf
Increase timeout
fredrikekelund Dec 19, 2025
fc4fa56
Try adding a small delay
fredrikekelund Dec 19, 2025
814d4ae
Kill `site list --watch` on SIGINT
fredrikekelund Dec 19, 2025
96e03aa
Create main window after creating site watcher
fredrikekelund Dec 19, 2025
ef932ca
Try using async fs method for cleanup
fredrikekelund Dec 22, 2025
0354512
Try rimraf (which has advanced retry strategies)
fredrikekelund Dec 22, 2025
1511151
Merge branch 'dev/studio-cli-i2' into f26d/fix-e2e-tests-windows
fredrikekelund Jan 2, 2026
71c5d1f
Merge branch 'dev/studio-cli-i2' into f26d/fix-e2e-tests-windows
fredrikekelund Jan 7, 2026
07fc679
New approach: don't remove `sessionPath` dir
fredrikekelund Jan 7, 2026
055d14c
Unused import
fredrikekelund Jan 7, 2026
0d0dc26
Force close app
fredrikekelund Jan 7, 2026
bc83d93
disconnect from pm2 in response to SIGTERM
fredrikekelund Jan 8, 2026
8264bbe
Revert user data watcher changes from #2313
fredrikekelund Jan 8, 2026
1c0cfda
Wait for running button
fredrikekelund Jan 8, 2026
e12e4ad
Use Electron's will-quit event in `execute-command.ts`
fredrikekelund Jan 8, 2026
21b2e77
SIGINT and SIGTERM listeners in `wp` command
fredrikekelund Jan 8, 2026
6355215
More SIGINT and SIGTERM listeners in `wp` command
fredrikekelund Jan 8, 2026
22bdd58
Merge branch 'dev/studio-cli-i2' into f26d/fix-e2e-tests-windows
fredrikekelund Jan 8, 2026
0d16ae5
Temporarily skip blueprints test
fredrikekelund Jan 8, 2026
d64e35a
Revert "Temporarily skip blueprints test"
fredrikekelund Jan 8, 2026
07eaa56
Try with force kill again
fredrikekelund Jan 8, 2026
1534b2c
Logging
fredrikekelund Jan 8, 2026
f75e007
New approach to waiting for app close
fredrikekelund Jan 8, 2026
bc41e38
Logging again
fredrikekelund Jan 8, 2026
f99f4b0
Try to make all child processes detached
fredrikekelund Jan 8, 2026
3596bac
Experiment
fredrikekelund Jan 8, 2026
4b7364a
Revert "Experiment"
fredrikekelund Jan 8, 2026
c0ab642
Revert "Try to make all child processes detached"
fredrikekelund Jan 8, 2026
1682341
Try a 5s timeout for closing the app
fredrikekelund Jan 8, 2026
e2faab8
Experiment with removing stopAllServersOnQuit
fredrikekelund Jan 8, 2026
9d56d0c
shutdown message
bcotrim Jan 8, 2026
ed7991a
Revert "shutdown message"
fredrikekelund Jan 9, 2026
8c2e2ba
Revert E2ESession::closeApp implementation
fredrikekelund Jan 9, 2026
8307750
Temporarily skip app.test.ts
fredrikekelund Jan 9, 2026
9d78492
Temporarily skip blueprints.test.ts
fredrikekelund Jan 9, 2026
57878b9
Revert "Temporarily skip app.test.ts"
fredrikekelund Jan 9, 2026
0727a04
Revert "Temporarily skip blueprints.test.ts"
fredrikekelund Jan 9, 2026
64e90d8
Add logging to Playwright source code
fredrikekelund Jan 9, 2026
438fcc8
pidtree
fredrikekelund Jan 9, 2026
6b8f3d4
More playwright logging
fredrikekelund Jan 9, 2026
453fa90
More logging and await pidtree
fredrikekelund Jan 9, 2026
2a77010
pidtree after close
fredrikekelund Jan 9, 2026
368eec0
Remove stdio listeners
fredrikekelund Jan 9, 2026
1ad9224
Fix pidtree logging after close
fredrikekelund Jan 9, 2026
6a21d36
destroy stdio streams on exit
fredrikekelund Jan 9, 2026
37fe3ee
Disconnect IPC
fredrikekelund Jan 9, 2026
42099ce
Try teardown workaround
fredrikekelund Jan 9, 2026
96f4816
Log pid and result in will-quit handler
fredrikekelund Jan 9, 2026
5fd1f89
Unregister will-quit handlers
fredrikekelund Jan 9, 2026
8c5b80e
Bring back logging
fredrikekelund Jan 9, 2026
22ca274
Stop all servers on quit
fredrikekelund Jan 9, 2026
cd06620
Undo all Windows hacks in E2ESession
fredrikekelund Jan 9, 2026
a6084b5
Bring back tree-kill
fredrikekelund Jan 9, 2026
601f39e
Log stop-all pid and pidtree
fredrikekelund Jan 9, 2026
c10e414
Catch errors from tree-kill
fredrikekelund Jan 9, 2026
d437c1d
Remove pidtree
fredrikekelund Jan 9, 2026
60906ff
No IPC channel in stopAllServersOnQuit
fredrikekelund Jan 9, 2026
31b5091
Update playwright-core patch to test theory
fredrikekelund Jan 11, 2026
a64720d
Fix patch
fredrikekelund Jan 11, 2026
b49fddd
`spawn` over `fork`
fredrikekelund Jan 11, 2026
e1e08d9
Temporarily remove `stopAllServersOnQuit`
fredrikekelund Jan 12, 2026
2c2c4e6
Never call `electronApp.close` on Windows
fredrikekelund Jan 12, 2026
b45ccc5
Restore `e2e/e2e-helpers.ts`
fredrikekelund Jan 12, 2026
f6edf0a
Reinstate `stopAllServersOnQuit`
fredrikekelund Jan 12, 2026
25af961
Revert `spawn` and IPC channel theory
fredrikekelund Jan 12, 2026
4599ffc
Fix unit tests
fredrikekelund Jan 12, 2026
76e16a3
Remove playwright-core patch
fredrikekelund Jan 12, 2026
15bcdf0
Remove tree-kill dependency
fredrikekelund Jan 12, 2026
b42fa0b
Remove `started` event
fredrikekelund Jan 12, 2026
5d811c5
More lenient child process cleanup
fredrikekelund Jan 12, 2026
5c05abb
Revert "Remove `started` event"
fredrikekelund Jan 12, 2026
a2e06ea
Destroy stdio streams again
fredrikekelund Jan 12, 2026
24a7256
Explicitly disconnect IPC
fredrikekelund Jan 12, 2026
138e87d
Clean up `E2ESession`
fredrikekelund Jan 12, 2026
9e1d826
Always call `killRemainingProcesses`
fredrikekelund Jan 12, 2026
ca04e5c
Reset test timeouts
fredrikekelund Jan 12, 2026
0bae09f
No `detached` option
fredrikekelund Jan 12, 2026
16e9294
Merge branch 'dev/studio-cli-i2' into f26d/fix-e2e-tests-windows
fredrikekelund Jan 12, 2026
fd7ec40
Reset more test timings
fredrikekelund Jan 12, 2026
4eca451
Only prevent `will-quit` when applicable
fredrikekelund Jan 12, 2026
2506be6
fix e2e test
bcotrim Jan 12, 2026
95ec7a6
Children connect to pm2 daemon in sequence
fredrikekelund Jan 13, 2026
c3107f4
Fix unit tests
fredrikekelund Jan 13, 2026
fa6b51e
Kill pm2 daemon in `site stop --all` command
fredrikekelund Jan 13, 2026
ebd3e1d
Fix stop command unit test, async disconnect, `process.exit`
fredrikekelund Jan 13, 2026
506c661
Remove stray `console.info` calls
fredrikekelund Jan 13, 2026
2194bf6
Undo all hacks in `E2ESession`
fredrikekelund Jan 13, 2026
0b1b166
Restore `E2ESession`, but don't kill children
fredrikekelund Jan 13, 2026
49cdd94
Increase timeout
fredrikekelund Jan 13, 2026
e0d6057
Don't log child pids
fredrikekelund Jan 13, 2026
01ae1c4
Experimental: detach `site stop --all` again
fredrikekelund Jan 13, 2026
d48ab37
Clean up session files again
fredrikekelund Jan 13, 2026
770a042
Let's give rimraf one more try
fredrikekelund Jan 13, 2026
75f4975
Experiment with removing `child.disconnect()` call
fredrikekelund Jan 13, 2026
1db9c6f
Don't destroy stdio streams on exit
fredrikekelund Jan 13, 2026
e43bfb2
Always run `site stop --all` command if pending update
fredrikekelund Jan 13, 2026
1f89523
Notify Studio if user runs `site stop --all`
fredrikekelund Jan 13, 2026
d623f12
Merge branch 'dev/studio-cli-i2' into f26d/fix-e2e-tests-windows
fredrikekelund Jan 13, 2026
f395a5f
Fix `package-lock.json` diff
fredrikekelund Jan 13, 2026
35e22ad
Fix and document "stop sites on exit" logic
fredrikekelund Jan 14, 2026
050417e
Speed up `site stop --all`
fredrikekelund Jan 14, 2026
f992eff
Fix logic
fredrikekelund Jan 14, 2026
710794c
Revert "Experimental: detach `site stop --all` again"
fredrikekelund Jan 14, 2026
1840cb9
Merge branch 'dev/studio-cli-i2' into f26d/fix-e2e-tests-windows
fredrikekelund Jan 14, 2026
18d1794
String tweaks
fredrikekelund Jan 14, 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
2 changes: 1 addition & 1 deletion cli/commands/site/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ export async function runCommand(
logger.reportKeyValuePair( 'id', siteDetails.id );
logger.reportKeyValuePair( 'running', String( siteDetails.running ) );
} finally {
disconnect();
await disconnect();
}
}

Expand Down
10 changes: 5 additions & 5 deletions cli/commands/site/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,14 @@ export async function runCommand(
deleteFiles: boolean = false
): Promise< void > {
try {
logger.reportStart( LoggerAction.LOAD_SITES, __( 'Loading site…' ) );
const site = await getSiteByFolder( siteFolder );
logger.reportSuccess( __( 'Site loaded' ) );

logger.reportStart( LoggerAction.START_DAEMON, __( 'Starting process daemon…' ) );
await connect();
logger.reportSuccess( __( 'Process daemon started' ) );

logger.reportStart( LoggerAction.LOAD_SITES, __( 'Loading site…' ) );
const site = await getSiteByFolder( siteFolder );
logger.reportSuccess( __( 'Site loaded' ) );

const runningProcess = await isServerRunning( site.id );
if ( runningProcess ) {
logger.reportStart( LoggerAction.STOP_SITE, __( 'Stopping WordPress server…' ) );
Expand Down Expand Up @@ -126,7 +126,7 @@ export async function runCommand(
logger.reportSuccess( __( 'Site files deleted' ) );
}
} finally {
disconnect();
await disconnect();
}
}

Expand Down
18 changes: 16 additions & 2 deletions cli/commands/site/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { __, _n, sprintf } from '@wordpress/i18n';
import Table from 'cli-table3';
import { SiteCommandLoggerAction as LoggerAction } from 'common/logger-actions';
import { getSiteUrl, readAppdata, type SiteData } from 'cli/lib/appdata';
import { connect, disconnect } from 'cli/lib/pm2-manager';
import { connect, disconnect, subscribePm2KillEvent } from 'cli/lib/pm2-manager';
import { getColumnWidths, getPrettyPath } from 'cli/lib/utils';
import { isServerRunning, subscribeSiteEvents } from 'cli/lib/wordpress-server-manager';
import { Logger, LoggerError } from 'cli/logger';
Expand Down Expand Up @@ -126,10 +126,24 @@ export async function runCommand( format: 'table' | 'json', watch: boolean ): Pr
},
{ debounceMs: 500 }
);

await subscribePm2KillEvent( () => {
for ( const site of sitesData ) {
const payload = {
siteId: site.id,
status: 'stopped',
url: site.url,
};
logger.reportKeyValuePair( 'site-status', JSON.stringify( payload ) );
}
} );

process.on( 'SIGINT', disconnect );
Comment on lines +129 to +141
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The studio site list --watch command would previously not interrupt in response to Ctrl+C

process.on( 'SIGTERM', disconnect );
}
} finally {
if ( ! watch ) {
disconnect();
await disconnect();
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion cli/commands/site/set.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ export async function runCommand(

return { usedWpCli: wpChanged };
} finally {
disconnect();
await disconnect();
}
}

Expand Down
10 changes: 5 additions & 5 deletions cli/commands/site/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ const logger = new Logger< LoggerAction >();

export async function runCommand( sitePath: string, skipBrowser = false ): Promise< void > {
try {
logger.reportStart( LoggerAction.LOAD_SITES, __( 'Loading site…' ) );
const site = await getSiteByFolder( sitePath );
logger.reportSuccess( __( 'Site loaded' ) );

logger.reportStart( LoggerAction.START_DAEMON, __( 'Starting process daemon…' ) );
await connect();
logger.reportSuccess( __( 'Process daemon started' ) );

logger.reportStart( LoggerAction.LOAD_SITES, __( 'Loading site…' ) );
const site = await getSiteByFolder( sitePath );
logger.reportSuccess( __( 'Site loaded' ) );

Comment on lines -15 to +22
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved the connect call to the top of this function to fix a bug where triggering many site start commands in quick succession would cause only one site to start (the others would hang indefinitely).

const runningProcess = await isServerRunning( site.id );
if ( runningProcess ) {
logger.reportSuccess( __( 'WordPress server is already running' ) );
Expand Down Expand Up @@ -60,7 +60,7 @@ export async function runCommand( sitePath: string, skipBrowser = false ): Promi
throw new LoggerError( __( 'Failed to start WordPress server' ), error );
}
} finally {
disconnect();
await disconnect();
}
}

Expand Down
8 changes: 5 additions & 3 deletions cli/commands/site/status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ const logger = new Logger< LoggerAction >();

export async function runCommand( siteFolder: string, format: 'table' | 'json' ): Promise< void > {
try {
logger.reportStart( LoggerAction.START_DAEMON, __( 'Starting process daemon…' ) );
await connect();
logger.reportSuccess( __( 'Process daemon started' ) );

logger.reportStart( LoggerAction.LOAD_SITES, __( 'Loading site…' ) );
const site = await getSiteByFolder( siteFolder );
logger.reportSuccess( __( 'Site loaded' ) );

await connect();

const isOnline = Boolean( await isServerRunning( site.id ) );
const status = isOnline ? `🟢 ${ __( 'Online' ) }` : `🔴 ${ __( 'Offline' ) }`;
const siteUrl = getSiteUrl( site );
Expand Down Expand Up @@ -74,7 +76,7 @@ export async function runCommand( siteFolder: string, format: 'table' | 'json' )
console.log( JSON.stringify( logData, null, 2 ) );
}
} finally {
disconnect();
await disconnect();
}
}

Expand Down
89 changes: 38 additions & 51 deletions cli/commands/site/stop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ import { SiteCommandLoggerAction as LoggerAction } from 'common/logger-actions';
import {
clearSiteLatestCliPid,
getSiteByFolder,
lockAppdata,
readAppdata,
saveAppdata,
unlockAppdata,
updateSiteAutoStart,
type SiteData,
} from 'cli/lib/appdata';
import { connect, disconnect } from 'cli/lib/pm2-manager';
import { connect, disconnect, killDaemonAndAllChildren } from 'cli/lib/pm2-manager';
import { stopProxyIfNoSitesNeedIt } from 'cli/lib/site-utils';
import { isServerRunning, stopWordPressServer } from 'cli/lib/wordpress-server-manager';
import { Logger, LoggerError } from 'cli/logger';
Expand Down Expand Up @@ -38,92 +41,76 @@ export async function runCommand(
try {
await connect();

const sitesToTarget: SiteData[] = [];

if ( target === Mode.STOP_SINGLE_SITE && siteFolder ) {
const site = await getSiteByFolder( siteFolder );
sitesToTarget.push( site );

const runningProcess = await isServerRunning( site.id );
if ( ! runningProcess ) {
logger.reportSuccess( __( 'WordPress server is not running' ) );
return;
}

logger.reportStart( LoggerAction.STOP_SITE, __( 'Stopping WordPress server…' ) );
logger.reportStart( LoggerAction.STOP_SITE, __( 'Stopping WordPress servers…' ) );

try {
await stopWordPressServer( site.id );
await clearSiteLatestCliPid( site.id );
await updateSiteAutoStart( site.id, autoStart );
logger.reportSuccess( __( 'WordPress server stopped' ) );
await stopProxyIfNoSitesNeedIt( site.id, logger );
} catch ( error ) {
throw new LoggerError( __( 'Failed to stop WordPress server' ), error );
}
} else {
const appdata = await readAppdata();
const runningSites: SiteData[] = [];

for ( const site of appdata.sites ) {
const runningProcess = await isServerRunning( site.id );

if ( runningProcess ) {
sitesToTarget.push( site );
runningSites.push( site );
}
}

if ( ! sitesToTarget.length ) {
if ( ! runningSites.length ) {
logger.reportSuccess( __( 'No sites are currently running' ) );
return;
}

logger.reportStart( LoggerAction.STOP_ALL_SITES, __( 'Stopping all WordPress sites...' ) );
}

const stoppedSiteIds: string[] = [];
logger.reportStart( LoggerAction.STOP_ALL_SITES, __( 'Stopping all WordPress servers…' ) );
await killDaemonAndAllChildren();

for ( const site of sitesToTarget ) {
try {
logger.reportProgress(
sprintf(
__( 'Stopping site "%s" (%d/%d)…' ),
site.name,
stoppedSiteIds.length + 1,
sitesToTarget.length
)
);
await stopWordPressServer( site.id );
await clearSiteLatestCliPid( site.id );
await updateSiteAutoStart( site.id, autoStart );

stoppedSiteIds.push( site.id );
} catch ( error ) {
logger.reportError(
new LoggerError( sprintf( __( 'Failed to stop site %s' ), site.name ) )
);
await lockAppdata();
const appdata = await readAppdata();
for ( const site of appdata.sites ) {
if ( runningSites.find( ( r ) => r.id === site.id ) ) {
delete site.latestCliPid;
site.autoStart = autoStart;
}
}
await saveAppdata( appdata );
} finally {
await unlockAppdata();
}
}

try {
await stopProxyIfNoSitesNeedIt( stoppedSiteIds, logger );
} catch ( error ) {
throw new LoggerError( __( 'Failed to stop proxy server' ), error );
}

if ( stoppedSiteIds.length === sitesToTarget.length ) {
logger.reportSuccess(
sprintf(
_n(
'Successfully stopped %d site',
'Successfully stopped %d sites',
sitesToTarget.length
runningSites.length
),
sitesToTarget.length
)
);
} else if ( stoppedSiteIds.length === 0 && sitesToTarget.length === 0 ) {
throw new LoggerError( __( 'Failed to stop site' ) );
} else {
throw new LoggerError(
sprintf(
_n( 'Stopped %d site out of %d', 'Stopped %d sites out of %d', stoppedSiteIds.length ),
stoppedSiteIds.length,
sitesToTarget.length
runningSites.length
)
);

// Calling `pm2.killDaemon` requires us to forcefully exit the process. pm2 does the same
// thing internally in its CLI.
process.exit( 0 );
}
} finally {
disconnect();
await disconnect();
}
}

Expand Down
Loading