Skip to content

Commit 13095d4

Browse files
committed
stop proxy if needed
1 parent 93f54ca commit 13095d4

File tree

6 files changed

+209
-2
lines changed

6 files changed

+209
-2
lines changed

cli/commands/site/stop.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { arePathsEqual } from 'common/lib/fs-utils';
33
import { SiteCommandLoggerAction as LoggerAction } from 'common/logger-actions';
44
import { clearSiteLatestCliPid, readAppdata } from 'cli/lib/appdata';
55
import { connect, disconnect } from 'cli/lib/pm2-manager';
6+
import { stopProxyIfNoSitesNeedIt } from 'cli/lib/site-utils';
67
import { isServerRunning, stopWordPressServer } from 'cli/lib/wordpress-server-manager';
78
import { Logger, LoggerError } from 'cli/logger';
89
import { StudioArgv } from 'cli/types';
@@ -31,6 +32,7 @@ export async function runCommand( siteFolder: string ): Promise< void > {
3132
await stopWordPressServer( site.id );
3233
await clearSiteLatestCliPid( site.id );
3334
logger.reportSuccess( __( 'WordPress site stopped' ) );
35+
await stopProxyIfNoSitesNeedIt( site.id, logger );
3436
} catch ( error ) {
3537
throw new LoggerError( __( 'Failed to stop WordPress server' ), error );
3638
}

cli/commands/site/tests/stop.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { SiteData, clearSiteLatestCliPid, readAppdata } from 'cli/lib/appdata';
22
import { connect, disconnect } from 'cli/lib/pm2-manager';
3+
import { stopProxyIfNoSitesNeedIt } from 'cli/lib/site-utils';
34
import { isServerRunning, stopWordPressServer } from 'cli/lib/wordpress-server-manager';
45
import { Logger, LoggerError } from 'cli/logger';
56

@@ -15,6 +16,7 @@ jest.mock( 'common/lib/fs-utils', () => ( {
1516
arePathsEqual: jest.fn( ( a: string, b: string ) => a === b ),
1617
} ) );
1718
jest.mock( 'cli/lib/pm2-manager' );
19+
jest.mock( 'cli/lib/site-utils' );
1820
jest.mock( 'cli/lib/wordpress-server-manager' );
1921
jest.mock( 'cli/logger' );
2022

@@ -60,6 +62,7 @@ describe( 'Site Stop Command', () => {
6062
( isServerRunning as jest.Mock ).mockResolvedValue( undefined );
6163
( stopWordPressServer as jest.Mock ).mockResolvedValue( undefined );
6264
( clearSiteLatestCliPid as jest.Mock ).mockResolvedValue( undefined );
65+
( stopProxyIfNoSitesNeedIt as jest.Mock ).mockResolvedValue( undefined );
6366
} );
6467

6568
afterEach( () => {
@@ -152,6 +155,32 @@ describe( 'Site Stop Command', () => {
152155
expect( mockLogger.reportSuccess ).toHaveBeenCalledWith( 'WordPress site stopped' );
153156
expect( disconnect ).toHaveBeenCalled();
154157
} );
158+
159+
it( 'should call stopProxyIfNoSitesNeedIt after stopping a site', async () => {
160+
( readAppdata as jest.Mock ).mockResolvedValue( {
161+
sites: [ mockSiteData ],
162+
snapshots: [],
163+
} );
164+
( isServerRunning as jest.Mock ).mockResolvedValue( mockProcessDescription );
165+
166+
const { runCommand } = await import( '../stop' );
167+
await runCommand( mockSiteFolder );
168+
169+
expect( stopProxyIfNoSitesNeedIt ).toHaveBeenCalledWith( mockSiteData.id, mockLogger );
170+
} );
171+
172+
it( 'should not call stopProxyIfNoSitesNeedIt if site is not running', async () => {
173+
( readAppdata as jest.Mock ).mockResolvedValue( {
174+
sites: [ mockSiteData ],
175+
snapshots: [],
176+
} );
177+
( isServerRunning as jest.Mock ).mockResolvedValue( undefined );
178+
179+
const { runCommand } = await import( '../stop' );
180+
await runCommand( mockSiteFolder );
181+
182+
expect( stopProxyIfNoSitesNeedIt ).not.toHaveBeenCalled();
183+
} );
155184
} );
156185

157186
describe( 'Cleanup', () => {

cli/lib/pm2-manager.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,10 @@ export async function isProxyProcessRunning(): Promise< ProcessDescription | und
165165
return isProcessRunning( PROXY_PROCESS_NAME );
166166
}
167167

168+
export async function stopProxyProcess(): Promise< void > {
169+
return stopProcess( PROXY_PROCESS_NAME );
170+
}
171+
168172
export async function isProcessRunning(
169173
processName: string
170174
): Promise< ProcessDescription | undefined > {

cli/lib/site-utils.ts

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { __ } from '@wordpress/i18n';
22
import { SiteCommandLoggerAction as LoggerAction } from 'common/logger-actions';
3-
import { getSiteUrl, SiteData } from 'cli/lib/appdata';
3+
import { getSiteUrl, readAppdata, SiteData } from 'cli/lib/appdata';
44
import { openBrowser } from 'cli/lib/browser';
55
import { generateSiteCertificate } from 'cli/lib/certificate-manager';
66
import { addDomainToHosts } from 'cli/lib/hosts-file';
7-
import { isProxyProcessRunning, startProxyProcess } from 'cli/lib/pm2-manager';
7+
import { isProxyProcessRunning, startProxyProcess, stopProxyProcess } from 'cli/lib/pm2-manager';
8+
import { isServerRunning } from 'cli/lib/wordpress-server-manager';
89
import { Logger, LoggerError } from 'cli/logger';
910

1011
/**
@@ -74,3 +75,31 @@ export async function setupCustomDomain(
7475
throw new LoggerError( __( 'Failed to add domain to hosts file' ), error );
7576
}
7677
}
78+
79+
/**
80+
* Stops the HTTP proxy server if no remaining running sites need it.
81+
* A site needs the proxy if it has a custom domain configured.
82+
*
83+
* @param stoppedSiteId - The ID of the site that was just stopped (to exclude from the check)
84+
*/
85+
export async function stopProxyIfNoSitesNeedIt(
86+
stoppedSiteId: string,
87+
logger: Logger< LoggerAction >
88+
): Promise< void > {
89+
const proxyProcess = await isProxyProcessRunning();
90+
if ( ! proxyProcess ) {
91+
return;
92+
}
93+
94+
const appdata = await readAppdata();
95+
96+
for ( const site of appdata.sites ) {
97+
if ( site.id !== stoppedSiteId && site.customDomain && ( await isServerRunning( site.id ) ) ) {
98+
return;
99+
}
100+
}
101+
102+
logger.reportStart( LoggerAction.STOP_PROXY, __( 'Stopping HTTP proxy server...' ) );
103+
await stopProxyProcess();
104+
logger.reportSuccess( __( 'HTTP proxy server stopped' ) );
105+
}

cli/lib/tests/site-utils.test.ts

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import { SiteCommandLoggerAction as LoggerAction } from 'common/logger-actions';
2+
import { SiteData, readAppdata } from 'cli/lib/appdata';
3+
import { isProxyProcessRunning, stopProxyProcess } from 'cli/lib/pm2-manager';
4+
import { stopProxyIfNoSitesNeedIt } from 'cli/lib/site-utils';
5+
import { isServerRunning } from 'cli/lib/wordpress-server-manager';
6+
import { Logger } from 'cli/logger';
7+
8+
jest.mock( 'cli/lib/appdata', () => ( {
9+
...jest.requireActual( 'cli/lib/appdata' ),
10+
getAppdataDirectory: jest.fn().mockReturnValue( '/test/appdata' ),
11+
readAppdata: jest.fn(),
12+
} ) );
13+
jest.mock( 'cli/lib/pm2-manager' );
14+
jest.mock( 'cli/lib/wordpress-server-manager' );
15+
16+
describe( 'stopProxyIfNoSitesNeedIt', () => {
17+
const mockProcessDescription = {
18+
name: 'studio-proxy',
19+
pmId: 0,
20+
status: 'online',
21+
pid: 12345,
22+
};
23+
24+
let mockLogger: Logger< LoggerAction >;
25+
26+
const createSiteData = ( overrides: Partial< SiteData > = {} ): SiteData => ( {
27+
id: 'site-1',
28+
name: 'Test Site',
29+
path: '/test/site',
30+
port: 8881,
31+
phpVersion: '8.0',
32+
...overrides,
33+
} );
34+
35+
beforeEach( () => {
36+
jest.clearAllMocks();
37+
38+
mockLogger = {
39+
reportStart: jest.fn(),
40+
reportSuccess: jest.fn(),
41+
reportError: jest.fn(),
42+
} as unknown as Logger< LoggerAction >;
43+
44+
( isProxyProcessRunning as jest.Mock ).mockResolvedValue( undefined );
45+
( stopProxyProcess as jest.Mock ).mockResolvedValue( undefined );
46+
( isServerRunning as jest.Mock ).mockResolvedValue( undefined );
47+
( readAppdata as jest.Mock ).mockResolvedValue( { sites: [], snapshots: [] } );
48+
} );
49+
50+
afterEach( () => {
51+
jest.restoreAllMocks();
52+
} );
53+
54+
it( 'should do nothing if proxy is not running', async () => {
55+
( isProxyProcessRunning as jest.Mock ).mockResolvedValue( undefined );
56+
57+
await stopProxyIfNoSitesNeedIt( 'site-1', mockLogger );
58+
59+
expect( readAppdata ).not.toHaveBeenCalled();
60+
expect( stopProxyProcess ).not.toHaveBeenCalled();
61+
} );
62+
63+
it( 'should stop proxy if no other sites exist', async () => {
64+
( isProxyProcessRunning as jest.Mock ).mockResolvedValue( mockProcessDescription );
65+
( readAppdata as jest.Mock ).mockResolvedValue( {
66+
sites: [ createSiteData( { id: 'stopped-site' } ) ],
67+
snapshots: [],
68+
} );
69+
70+
await stopProxyIfNoSitesNeedIt( 'stopped-site', mockLogger );
71+
72+
expect( mockLogger.reportStart ).toHaveBeenCalledWith(
73+
'stopProxy',
74+
'Stopping HTTP proxy server...'
75+
);
76+
expect( stopProxyProcess ).toHaveBeenCalled();
77+
expect( mockLogger.reportSuccess ).toHaveBeenCalledWith( 'HTTP proxy server stopped' );
78+
} );
79+
80+
it( 'should stop proxy if other sites exist but none have custom domains', async () => {
81+
( isProxyProcessRunning as jest.Mock ).mockResolvedValue( mockProcessDescription );
82+
( readAppdata as jest.Mock ).mockResolvedValue( {
83+
sites: [
84+
createSiteData( { id: 'stopped-site', customDomain: 'stopped.local' } ),
85+
createSiteData( { id: 'other-site-1' } ),
86+
createSiteData( { id: 'other-site-2' } ),
87+
],
88+
snapshots: [],
89+
} );
90+
91+
await stopProxyIfNoSitesNeedIt( 'stopped-site', mockLogger );
92+
93+
expect( stopProxyProcess ).toHaveBeenCalled();
94+
} );
95+
96+
it( 'should stop proxy if other sites have custom domains but are not running', async () => {
97+
( isProxyProcessRunning as jest.Mock ).mockResolvedValue( mockProcessDescription );
98+
( readAppdata as jest.Mock ).mockResolvedValue( {
99+
sites: [
100+
createSiteData( { id: 'stopped-site', customDomain: 'stopped.local' } ),
101+
createSiteData( { id: 'other-site', customDomain: 'other.local' } ),
102+
],
103+
snapshots: [],
104+
} );
105+
( isServerRunning as jest.Mock ).mockResolvedValue( undefined );
106+
107+
await stopProxyIfNoSitesNeedIt( 'stopped-site', mockLogger );
108+
109+
expect( isServerRunning ).toHaveBeenCalledWith( 'other-site' );
110+
expect( stopProxyProcess ).toHaveBeenCalled();
111+
} );
112+
113+
it( 'should not stop proxy if another site with custom domain is running', async () => {
114+
( isProxyProcessRunning as jest.Mock ).mockResolvedValue( mockProcessDescription );
115+
( readAppdata as jest.Mock ).mockResolvedValue( {
116+
sites: [
117+
createSiteData( { id: 'stopped-site', customDomain: 'stopped.local' } ),
118+
createSiteData( { id: 'running-site', customDomain: 'running.local' } ),
119+
],
120+
snapshots: [],
121+
} );
122+
( isServerRunning as jest.Mock ).mockResolvedValue( mockProcessDescription );
123+
124+
await stopProxyIfNoSitesNeedIt( 'stopped-site', mockLogger );
125+
126+
expect( isServerRunning ).toHaveBeenCalledWith( 'running-site' );
127+
expect( stopProxyProcess ).not.toHaveBeenCalled();
128+
} );
129+
130+
it( 'should not check if the stopped site is running', async () => {
131+
( isProxyProcessRunning as jest.Mock ).mockResolvedValue( mockProcessDescription );
132+
( readAppdata as jest.Mock ).mockResolvedValue( {
133+
sites: [ createSiteData( { id: 'stopped-site', customDomain: 'stopped.local' } ) ],
134+
snapshots: [],
135+
} );
136+
137+
await stopProxyIfNoSitesNeedIt( 'stopped-site', mockLogger );
138+
139+
expect( isServerRunning ).not.toHaveBeenCalledWith( 'stopped-site' );
140+
expect( stopProxyProcess ).toHaveBeenCalled();
141+
} );
142+
} );

common/logger-actions.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export enum SiteCommandLoggerAction {
2121
START_DAEMON = 'startDaemon',
2222
LOAD_SITES = 'loadSites',
2323
START_PROXY = 'startProxy',
24+
STOP_PROXY = 'stopProxy',
2425
GENERATE_CERT = 'generateCert',
2526
ADD_DOMAIN_TO_HOSTS = 'addDomainToHosts',
2627
START_SITE = 'startSite',

0 commit comments

Comments
 (0)