diff --git a/cli/commands/site/set-domain.ts b/cli/commands/site/set-domain.ts new file mode 100644 index 0000000000..87ff1f8689 --- /dev/null +++ b/cli/commands/site/set-domain.ts @@ -0,0 +1,106 @@ +import { __, _n } from '@wordpress/i18n'; +import { getDomainNameValidationError } from 'common/lib/domains'; +import { arePathsEqual } from 'common/lib/fs-utils'; +import { SiteCommandLoggerAction as LoggerAction } from 'common/logger-actions'; +import { lockAppdata, readAppdata, saveAppdata, SiteData, unlockAppdata } from 'cli/lib/appdata'; +import { removeDomainFromHosts } from 'cli/lib/hosts-file'; +import { connect, disconnect } from 'cli/lib/pm2-manager'; +import { setupCustomDomain } from 'cli/lib/site-utils'; +import { + isServerRunning, + startWordPressServer, + stopWordPressServer, +} from 'cli/lib/wordpress-server-manager'; +import { Logger, LoggerError } from 'cli/logger'; +import { StudioArgv } from 'cli/types'; + +const logger = new Logger< LoggerAction >(); + +export async function runCommand( sitePath: string, domainName: string ): Promise< void > { + try { + let site: SiteData; + let oldDomainName: string | undefined; + + try { + await lockAppdata(); + const appdata = await readAppdata(); + + const foundSite = appdata.sites.find( ( site ) => arePathsEqual( site.path, sitePath ) ); + if ( ! foundSite ) { + throw new LoggerError( __( 'The specified folder is not added to Studio.' ) ); + } + + site = foundSite; + const existingDomainNames = appdata.sites + .map( ( site ) => site.customDomain ) + .filter( ( domain ): domain is string => Boolean( domain ) ); + const domainError = getDomainNameValidationError( true, domainName, existingDomainNames ); + + if ( domainError ) { + throw new LoggerError( domainError ); + } + + oldDomainName = site.customDomain; + + if ( oldDomainName === domainName ) { + throw new LoggerError( __( 'The specified domain is already set for this site.' ) ); + } + + site.customDomain = domainName; + await saveAppdata( appdata ); + } finally { + await unlockAppdata(); + } + + if ( oldDomainName ) { + logger.reportStart( + LoggerAction.REMOVE_DOMAIN_FROM_HOSTS, + __( 'Removing domain from hosts file...' ) + ); + await removeDomainFromHosts( oldDomainName ); + logger.reportSuccess( __( 'Domain removed from hosts file' ) ); + } + + logger.reportStart( LoggerAction.START_DAEMON, __( 'Starting process daemon...' ) ); + await connect(); + logger.reportSuccess( __( 'Process daemon started' ) ); + + const runningProcess = await isServerRunning( site.id ); + + if ( runningProcess ) { + await stopWordPressServer( site.id ); + await setupCustomDomain( site, logger ); + logger.reportStart( LoggerAction.START_SITE, __( 'Restarting site...' ) ); + await startWordPressServer( site ); + logger.reportSuccess( __( 'Site restarted' ) ); + } + } finally { + disconnect(); + } +} + +export const registerCommand = ( yargs: StudioArgv ) => { + return yargs.command( { + command: 'set-domain ', + describe: __( 'Set domain for a local site' ), + builder: ( yargs ) => { + return yargs.positional( 'domain', { + type: 'string', + description: __( 'Domain name' ), + demandOption: true, + } ); + }, + handler: async ( argv ) => { + try { + await runCommand( argv.path, argv.domain ); + } catch ( error ) { + if ( error instanceof LoggerError ) { + logger.reportError( error ); + } else { + const loggerError = new LoggerError( __( 'Failed to start site infrastructure' ), error ); + logger.reportError( loggerError ); + } + } + }, + } ); +}; diff --git a/cli/commands/site/set-https.ts b/cli/commands/site/set-https.ts new file mode 100644 index 0000000000..345d9d630a --- /dev/null +++ b/cli/commands/site/set-https.ts @@ -0,0 +1,90 @@ +import { __, _n } from '@wordpress/i18n'; +import { arePathsEqual } from 'common/lib/fs-utils'; +import { SiteCommandLoggerAction as LoggerAction } from 'common/logger-actions'; +import { lockAppdata, readAppdata, saveAppdata, SiteData, unlockAppdata } from 'cli/lib/appdata'; +import { connect, disconnect } from 'cli/lib/pm2-manager'; +import { + isServerRunning, + startWordPressServer, + stopWordPressServer, +} from 'cli/lib/wordpress-server-manager'; +import { Logger, LoggerError } from 'cli/logger'; +import { StudioArgv } from 'cli/types'; + +const logger = new Logger< LoggerAction >(); + +export async function runCommand( sitePath: string, enableHttps: boolean ): Promise< void > { + try { + let site: SiteData; + + try { + await lockAppdata(); + const appdata = await readAppdata(); + + const foundSite = appdata.sites.find( ( site ) => arePathsEqual( site.path, sitePath ) ); + if ( ! foundSite ) { + throw new LoggerError( __( 'The specified folder is not added to Studio.' ) ); + } + + site = foundSite; + + if ( ! site.customDomain ) { + throw new LoggerError( __( 'Site does not have a custom domain.' ) ); + } + + if ( site.enableHttps === enableHttps ) { + if ( enableHttps ) { + throw new LoggerError( __( 'HTTPS is already enabled for this site.' ) ); + } else { + throw new LoggerError( __( 'HTTPS is already disabled for this site.' ) ); + } + } + + site.enableHttps = enableHttps; + await saveAppdata( appdata ); + } finally { + await unlockAppdata(); + } + + logger.reportStart( LoggerAction.START_DAEMON, __( 'Starting process daemon...' ) ); + await connect(); + logger.reportSuccess( __( 'Process daemon started' ) ); + + const runningProcess = await isServerRunning( site.id ); + + if ( runningProcess ) { + logger.reportStart( LoggerAction.START_SITE, __( 'Restarting site...' ) ); + await stopWordPressServer( site.id ); + await startWordPressServer( site ); + logger.reportSuccess( __( 'Site restarted' ) ); + } + } finally { + disconnect(); + } +} + +export const registerCommand = ( yargs: StudioArgv ) => { + return yargs.command( { + command: 'set-https ', + describe: __( 'Set HTTPS for a local site' ), + builder: ( yargs ) => { + return yargs.positional( 'enable', { + type: 'boolean', + description: __( 'Enable HTTPS' ), + demandOption: true, + } ); + }, + handler: async ( argv ) => { + try { + await runCommand( argv.path, argv.enable ); + } catch ( error ) { + if ( error instanceof LoggerError ) { + logger.reportError( error ); + } else { + const loggerError = new LoggerError( __( 'Failed to start site infrastructure' ), error ); + logger.reportError( loggerError ); + } + } + }, + } ); +}; diff --git a/cli/commands/site/tests/set-domain.test.ts b/cli/commands/site/tests/set-domain.test.ts new file mode 100644 index 0000000000..8d079c18be --- /dev/null +++ b/cli/commands/site/tests/set-domain.test.ts @@ -0,0 +1,195 @@ +import { getDomainNameValidationError } from 'common/lib/domains'; +import { arePathsEqual } from 'common/lib/fs-utils'; +import { SiteData, lockAppdata, readAppdata, saveAppdata, unlockAppdata } from 'cli/lib/appdata'; +import { removeDomainFromHosts } from 'cli/lib/hosts-file'; +import { connect, disconnect } from 'cli/lib/pm2-manager'; +import { setupCustomDomain } from 'cli/lib/site-utils'; +import { + isServerRunning, + startWordPressServer, + stopWordPressServer, +} from 'cli/lib/wordpress-server-manager'; + +jest.mock( 'cli/lib/appdata', () => ( { + ...jest.requireActual( 'cli/lib/appdata' ), + lockAppdata: jest.fn(), + readAppdata: jest.fn(), + saveAppdata: jest.fn(), + unlockAppdata: jest.fn(), +} ) ); +jest.mock( 'cli/lib/pm2-manager' ); +jest.mock( 'cli/lib/wordpress-server-manager' ); +jest.mock( 'common/lib/fs-utils' ); +jest.mock( 'cli/lib/hosts-file' ); +jest.mock( 'cli/lib/site-utils' ); +jest.mock( 'common/lib/domains' ); + +describe( 'Site Set-Domain Command', () => { + // Simple test data + const testSitePath = '/test/site/path'; + const testDomainName = 'example.local'; + + const createTestSite = (): SiteData => ( { + id: 'site-1', + name: 'Test Site', + path: testSitePath, + port: 8881, + adminUsername: 'admin', + adminPassword: 'password123', + running: false, + phpVersion: '8.0', + } ); + + const testProcessDescription = { + pid: 12345, + status: 'online', + }; + + let testSite: SiteData; + + beforeEach( () => { + jest.clearAllMocks(); + + testSite = createTestSite(); + + ( connect as jest.Mock ).mockResolvedValue( undefined ); + ( disconnect as jest.Mock ).mockResolvedValue( undefined ); + ( lockAppdata as jest.Mock ).mockResolvedValue( undefined ); + ( readAppdata as jest.Mock ).mockResolvedValue( { + sites: [ testSite ], + snapshots: [], + } ); + ( saveAppdata as jest.Mock ).mockResolvedValue( undefined ); + ( unlockAppdata as jest.Mock ).mockResolvedValue( undefined ); + ( isServerRunning as jest.Mock ).mockResolvedValue( undefined ); + ( startWordPressServer as jest.Mock ).mockResolvedValue( testProcessDescription ); + ( stopWordPressServer as jest.Mock ).mockResolvedValue( undefined ); + ( arePathsEqual as jest.Mock ).mockImplementation( ( a: string, b: string ) => a === b ); + ( removeDomainFromHosts as jest.Mock ).mockResolvedValue( undefined ); + ( setupCustomDomain as jest.Mock ).mockResolvedValue( undefined ); + ( getDomainNameValidationError as jest.Mock ).mockReturnValue( '' ); + } ); + + afterEach( () => { + jest.restoreAllMocks(); + } ); + + describe( 'Error Cases', () => { + it( 'should throw when site not found', async () => { + ( readAppdata as jest.Mock ).mockResolvedValue( { + sites: [], + snapshots: [], + } ); + + const { runCommand } = await import( '../set-domain' ); + + await expect( runCommand( testSitePath, testDomainName ) ).rejects.toThrow( + 'The specified folder is not added to Studio.' + ); + expect( disconnect ).toHaveBeenCalled(); + } ); + + it( 'should throw when domain is invalid', async () => { + ( getDomainNameValidationError as jest.Mock ).mockReturnValue( + 'Please enter a valid domain name' + ); + + const { runCommand } = await import( '../set-domain' ); + + await expect( runCommand( testSitePath, 'invalid domain' ) ).rejects.toThrow(); + expect( saveAppdata ).not.toHaveBeenCalled(); + expect( disconnect ).toHaveBeenCalled(); + } ); + + it( 'should throw when domain already in use by another site', async () => { + ( getDomainNameValidationError as jest.Mock ).mockReturnValue( + 'The domain name is already in use' + ); + + const { runCommand } = await import( '../set-domain' ); + + await expect( runCommand( testSitePath, testDomainName ) ).rejects.toThrow(); + expect( saveAppdata ).not.toHaveBeenCalled(); + expect( disconnect ).toHaveBeenCalled(); + } ); + + it( 'should throw when domain is identical to current domain', async () => { + ( readAppdata as jest.Mock ).mockResolvedValue( { + sites: [ { ...testSite, customDomain: testDomainName } ], + snapshots: [], + } ); + + const { runCommand } = await import( '../set-domain' ); + + await expect( runCommand( testSitePath, testDomainName ) ).rejects.toThrow( + 'The specified domain is already set for this site.' + ); + expect( saveAppdata ).not.toHaveBeenCalled(); + expect( disconnect ).toHaveBeenCalled(); + } ); + + it( 'should throw when appdata save fails', async () => { + ( saveAppdata as jest.Mock ).mockRejectedValue( new Error( 'Save failed' ) ); + + const { runCommand } = await import( '../set-domain' ); + + await expect( runCommand( testSitePath, testDomainName ) ).rejects.toThrow(); + expect( disconnect ).toHaveBeenCalled(); + } ); + } ); + + describe( 'Success Cases', () => { + it( 'should set domain on a stopped site', async () => { + const { runCommand } = await import( '../set-domain' ); + + await runCommand( testSitePath, testDomainName ); + + expect( lockAppdata ).toHaveBeenCalled(); + expect( readAppdata ).toHaveBeenCalled(); + expect( saveAppdata ).toHaveBeenCalled(); + expect( unlockAppdata ).toHaveBeenCalled(); + + const savedAppdata = ( saveAppdata as jest.Mock ).mock.calls[ 0 ][ 0 ]; + expect( savedAppdata.sites[ 0 ].customDomain ).toBe( testDomainName ); + + expect( isServerRunning ).toHaveBeenCalledWith( testSite.id ); + expect( stopWordPressServer ).not.toHaveBeenCalled(); + expect( startWordPressServer ).not.toHaveBeenCalled(); + expect( removeDomainFromHosts ).not.toHaveBeenCalled(); + expect( disconnect ).toHaveBeenCalled(); + } ); + + it( 'should set domain and restart a running site', async () => { + ( isServerRunning as jest.Mock ).mockResolvedValue( testProcessDescription ); + + const { runCommand } = await import( '../set-domain' ); + + await runCommand( testSitePath, testDomainName ); + + expect( saveAppdata ).toHaveBeenCalled(); + const savedAppdata = ( saveAppdata as jest.Mock ).mock.calls[ 0 ][ 0 ]; + expect( savedAppdata.sites[ 0 ].customDomain ).toBe( testDomainName ); + + expect( isServerRunning ).toHaveBeenCalledWith( testSite.id ); + expect( stopWordPressServer ).toHaveBeenCalledWith( testSite.id ); + expect( setupCustomDomain ).toHaveBeenCalledWith( testSite, expect.any( Object ) ); + expect( startWordPressServer ).toHaveBeenCalledWith( testSite ); + expect( disconnect ).toHaveBeenCalled(); + } ); + + it( 'should remove old domain from hosts file when replacing domain', async () => { + const oldDomain = 'old.local'; + ( readAppdata as jest.Mock ).mockResolvedValue( { + sites: [ { ...testSite, customDomain: oldDomain } ], + snapshots: [], + } ); + + const { runCommand } = await import( '../set-domain' ); + + await runCommand( testSitePath, testDomainName ); + + expect( removeDomainFromHosts ).toHaveBeenCalledWith( oldDomain ); + expect( disconnect ).toHaveBeenCalled(); + } ); + } ); +} ); diff --git a/cli/commands/site/tests/set-https.test.ts b/cli/commands/site/tests/set-https.test.ts new file mode 100644 index 0000000000..58e06f4567 --- /dev/null +++ b/cli/commands/site/tests/set-https.test.ts @@ -0,0 +1,223 @@ +import { arePathsEqual } from 'common/lib/fs-utils'; +import { SiteData, lockAppdata, readAppdata, saveAppdata, unlockAppdata } from 'cli/lib/appdata'; +import { connect, disconnect } from 'cli/lib/pm2-manager'; +import { + isServerRunning, + startWordPressServer, + stopWordPressServer, +} from 'cli/lib/wordpress-server-manager'; + +jest.mock( 'cli/lib/appdata', () => ( { + ...jest.requireActual( 'cli/lib/appdata' ), + lockAppdata: jest.fn(), + readAppdata: jest.fn(), + saveAppdata: jest.fn(), + unlockAppdata: jest.fn(), +} ) ); +jest.mock( 'cli/lib/pm2-manager' ); +jest.mock( 'cli/lib/wordpress-server-manager' ); +jest.mock( 'common/lib/fs-utils' ); + +describe( 'Site Set-HTTPS Command', () => { + const mockSiteFolder = '/test/site/path'; + + const createMockSiteData = (): SiteData => ( { + id: 'test-site-id', + name: 'Test Site', + path: mockSiteFolder, + port: 8881, + adminUsername: 'admin', + adminPassword: 'password123', + running: false, + phpVersion: '8.0', + url: `http://localhost:8881`, + enableHttps: false, + customDomain: 'test.local', + } ); + + const mockProcessDescription = { + name: 'test-site-id', + pmId: 0, + status: 'online', + pid: 12345, + }; + + let mockSiteData: SiteData; + + beforeEach( () => { + jest.clearAllMocks(); + + mockSiteData = createMockSiteData(); + + ( connect as jest.Mock ).mockResolvedValue( undefined ); + ( disconnect as jest.Mock ).mockReturnValue( undefined ); + ( lockAppdata as jest.Mock ).mockResolvedValue( undefined ); + ( readAppdata as jest.Mock ).mockResolvedValue( { + sites: [ mockSiteData ], + snapshots: [], + } ); + ( saveAppdata as jest.Mock ).mockResolvedValue( undefined ); + ( unlockAppdata as jest.Mock ).mockResolvedValue( undefined ); + ( isServerRunning as jest.Mock ).mockResolvedValue( undefined ); + ( startWordPressServer as jest.Mock ).mockResolvedValue( mockProcessDescription ); + ( stopWordPressServer as jest.Mock ).mockResolvedValue( undefined ); + ( arePathsEqual as jest.Mock ).mockImplementation( ( a: string, b: string ) => a === b ); + } ); + + afterEach( () => { + jest.restoreAllMocks(); + } ); + + describe( 'Error Cases', () => { + it( 'should throw when site not found', async () => { + ( readAppdata as jest.Mock ).mockResolvedValue( { + sites: [], + snapshots: [], + } ); + + const { runCommand } = await import( '../set-https' ); + + await expect( runCommand( mockSiteFolder, true ) ).rejects.toThrow( + 'The specified folder is not added to Studio.' + ); + expect( disconnect ).toHaveBeenCalled(); + } ); + + it( 'should throw when site does not have a custom domain', async () => { + const siteWithoutDomain = { ...mockSiteData, customDomain: undefined }; + ( readAppdata as jest.Mock ).mockResolvedValue( { + sites: [ siteWithoutDomain ], + snapshots: [], + } ); + + const { runCommand } = await import( '../set-https' ); + + await expect( runCommand( mockSiteFolder, true ) ).rejects.toThrow( + 'Site does not have a custom domain.' + ); + expect( disconnect ).toHaveBeenCalled(); + } ); + + it( 'should throw if passing the same config value that the site already has', async () => { + ( readAppdata as jest.Mock ).mockResolvedValue( { + sites: [ { ...mockSiteData, enableHttps: true } ], + snapshots: [], + } ); + + const { runCommand } = await import( '../set-https' ); + + await expect( runCommand( mockSiteFolder, true ) ).rejects.toThrow( + 'HTTPS is already enabled for this site.' + ); + expect( disconnect ).toHaveBeenCalled(); + } ); + + it( 'should throw when appdata save fails', async () => { + ( saveAppdata as jest.Mock ).mockRejectedValue( new Error( 'Save failed' ) ); + + const { runCommand } = await import( '../set-https' ); + + await expect( runCommand( mockSiteFolder, true ) ).rejects.toThrow(); + expect( disconnect ).toHaveBeenCalled(); + } ); + + it( 'should throw when PM2 connection fails', async () => { + ( connect as jest.Mock ).mockRejectedValue( new Error( 'PM2 connection failed' ) ); + + const { runCommand } = await import( '../set-https' ); + + await expect( runCommand( mockSiteFolder, true ) ).rejects.toThrow(); + expect( disconnect ).toHaveBeenCalled(); + } ); + + it( 'should throw when stopping running site fails', async () => { + ( isServerRunning as jest.Mock ).mockResolvedValue( mockProcessDescription ); + ( stopWordPressServer as jest.Mock ).mockRejectedValue( new Error( 'Server stop failed' ) ); + + const { runCommand } = await import( '../set-https' ); + + await expect( runCommand( mockSiteFolder, true ) ).rejects.toThrow(); + expect( disconnect ).toHaveBeenCalled(); + } ); + } ); + + describe( 'Success Cases', () => { + it( 'should enable HTTPS on a stopped site', async () => { + const { runCommand } = await import( '../set-https' ); + + await runCommand( mockSiteFolder, true ); + + expect( lockAppdata ).toHaveBeenCalled(); + expect( readAppdata ).toHaveBeenCalled(); + expect( saveAppdata ).toHaveBeenCalled(); + + const savedAppdata = ( saveAppdata as jest.Mock ).mock.calls[ 0 ][ 0 ]; + expect( savedAppdata.sites[ 0 ].enableHttps ).toBe( true ); + + expect( unlockAppdata ).toHaveBeenCalled(); + expect( isServerRunning ).toHaveBeenCalledWith( mockSiteData.id ); + expect( stopWordPressServer ).not.toHaveBeenCalled(); + expect( startWordPressServer ).not.toHaveBeenCalled(); + expect( disconnect ).toHaveBeenCalled(); + } ); + + it( 'should disable HTTPS on a stopped site', async () => { + const siteWithHttps = { ...mockSiteData, enableHttps: true }; + ( readAppdata as jest.Mock ).mockResolvedValue( { + sites: [ siteWithHttps ], + snapshots: [], + } ); + + const { runCommand } = await import( '../set-https' ); + + await runCommand( mockSiteFolder, false ); + + expect( saveAppdata ).toHaveBeenCalled(); + const savedAppdata = ( saveAppdata as jest.Mock ).mock.calls[ 0 ][ 0 ]; + expect( savedAppdata.sites[ 0 ].enableHttps ).toBe( false ); + + expect( stopWordPressServer ).not.toHaveBeenCalled(); + expect( startWordPressServer ).not.toHaveBeenCalled(); + expect( disconnect ).toHaveBeenCalled(); + } ); + + it( 'should enable HTTPS and restart a running site', async () => { + ( isServerRunning as jest.Mock ).mockResolvedValue( mockProcessDescription ); + + const { runCommand } = await import( '../set-https' ); + + await runCommand( mockSiteFolder, true ); + + expect( saveAppdata ).toHaveBeenCalled(); + const savedAppdata = ( saveAppdata as jest.Mock ).mock.calls[ 0 ][ 0 ]; + expect( savedAppdata.sites[ 0 ].enableHttps ).toBe( true ); + + expect( isServerRunning ).toHaveBeenCalledWith( mockSiteData.id ); + expect( stopWordPressServer ).toHaveBeenCalledWith( mockSiteData.id ); + expect( startWordPressServer ).toHaveBeenCalledWith( expect.any( Object ) ); + expect( disconnect ).toHaveBeenCalled(); + } ); + + it( 'should disable HTTPS and restart a running site', async () => { + const siteWithHttps = { ...mockSiteData, enableHttps: true }; + ( readAppdata as jest.Mock ).mockResolvedValue( { + sites: [ siteWithHttps ], + snapshots: [], + } ); + ( isServerRunning as jest.Mock ).mockResolvedValue( mockProcessDescription ); + + const { runCommand } = await import( '../set-https' ); + + await runCommand( mockSiteFolder, false ); + + expect( saveAppdata ).toHaveBeenCalled(); + const savedAppdata = ( saveAppdata as jest.Mock ).mock.calls[ 0 ][ 0 ]; + expect( savedAppdata.sites[ 0 ].enableHttps ).toBe( false ); + + expect( isServerRunning ).toHaveBeenCalledWith( mockSiteData.id ); + expect( stopWordPressServer ).toHaveBeenCalledWith( mockSiteData.id ); + expect( startWordPressServer ).toHaveBeenCalled(); + expect( disconnect ).toHaveBeenCalled(); + } ); + } ); +} ); diff --git a/cli/index.ts b/cli/index.ts index 4f520eb4d0..b314715109 100644 --- a/cli/index.ts +++ b/cli/index.ts @@ -13,6 +13,8 @@ import { registerCommand as registerListCommand } from 'cli/commands/preview/lis import { registerCommand as registerUpdateCommand } from 'cli/commands/preview/update'; import { registerCommand as registerSiteCreateCommand } from 'cli/commands/site/create'; import { registerCommand as registerSiteListCommand } from 'cli/commands/site/list'; +import { registerCommand as registerSiteSetDomainCommand } from 'cli/commands/site/set-domain'; +import { registerCommand as registerSiteSetHttpsCommand } from 'cli/commands/site/set-https'; 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'; @@ -88,6 +90,8 @@ async function main() { registerSiteListCommand( sitesYargs ); registerSiteStartCommand( sitesYargs ); registerSiteStopCommand( sitesYargs ); + registerSiteSetHttpsCommand( sitesYargs ); + registerSiteSetDomainCommand( sitesYargs ); sitesYargs.demandCommand( 1, __( 'You must provide a valid command' ) ); } ); } diff --git a/cli/lib/hosts-file.ts b/cli/lib/hosts-file.ts index 7e5ac77718..5d8c402236 100644 --- a/cli/lib/hosts-file.ts +++ b/cli/lib/hosts-file.ts @@ -127,47 +127,6 @@ export const removeDomainFromHosts = async ( domain: string ): Promise< void > = } }; -export const updateDomainInHosts = async ( - oldDomain: string | undefined, - newDomain: string | undefined, - port: number -): Promise< void > => { - if ( oldDomain === newDomain ) { - return; - } - - if ( ! oldDomain && newDomain ) { - await addDomainToHosts( newDomain, port ); - return; - } - - if ( oldDomain && ! newDomain ) { - await removeDomainFromHosts( oldDomain ); - return; - } - - try { - const hostsContent = await readHostsFile(); - const encodedOldDomain = domainToASCII( oldDomain as string ); - const encodedNewDomain = domainToASCII( newDomain as string ); - const oldPattern = createHostsEntryPattern( encodedOldDomain ); - const newContent = updateStudioBlock( hostsContent, ( entries ) => { - const filtered = entries.filter( ( entry ) => ! entry.match( oldPattern ) ); - return [ ...filtered, `127.0.0.1 ${ encodedNewDomain } # Port ${ port }` ]; - } ); - - if ( newContent !== hostsContent ) { - await writeHostsFile( newContent ); - } - } catch ( error ) { - console.error( - `Error replacing domain ${ oldDomain } with ${ newDomain } in hosts file:`, - error - ); - throw error; - } -}; - /** * Helper function for manipulating the "block" of entries in the hosts file * pertaining to WordPress Studio. diff --git a/cli/lib/proxy-server.ts b/cli/lib/proxy-server.ts index 23a4692f1e..142495ac93 100644 --- a/cli/lib/proxy-server.ts +++ b/cli/lib/proxy-server.ts @@ -157,7 +157,7 @@ async function startHttpsProxy(): Promise< void > { SNICallback: async ( servername, cb ) => { try { const site = await getSiteByHost( servername ); - if ( ! site || ! site.customDomain ) { + if ( ! site || ! site.customDomain || ! site.enableHttps ) { console.error( `[Proxy] SNI: Invalid hostname: ${ servername }` ); cb( new Error( `Invalid hostname: ${ servername }` ) ); return; diff --git a/cli/lib/wordpress-server-manager.ts b/cli/lib/wordpress-server-manager.ts index c04a1e45b9..7195ab2946 100644 --- a/cli/lib/wordpress-server-manager.ts +++ b/cli/lib/wordpress-server-manager.ts @@ -141,21 +141,13 @@ async function sendMessage( maxTotalElapsedTime = PLAYGROUND_CLI_MAX_TIMEOUT ): Promise< unknown > { const bus = await getPm2Bus(); + const messageId = nextMessageId++; + let responseHandler: ( packet: unknown ) => void; return new Promise( ( resolve, reject ) => { - const messageId = nextMessageId++; const startTime = Date.now(); let lastActivityTimestamp = Date.now(); - const cleanup = () => { - bus.off( 'process:msg', responseHandler ); - const tracker = messageActivityTrackers.get( messageId ); - if ( tracker ) { - clearInterval( tracker.activityCheckIntervalId ); - messageActivityTrackers.delete( messageId ); - } - }; - const activityCheckIntervalId = setInterval( () => { const now = Date.now(); const timeSinceLastActivity = now - lastActivityTimestamp; @@ -165,7 +157,6 @@ async function sendMessage( timeSinceLastActivity > PLAYGROUND_CLI_INACTIVITY_TIMEOUT || totalElapsedTime > maxTotalElapsedTime ) { - cleanup(); const timeoutReason = totalElapsedTime > maxTotalElapsedTime ? `Maximum timeout of ${ maxTotalElapsedTime / 1000 }s exceeded` @@ -180,10 +171,9 @@ async function sendMessage( activityCheckIntervalId, } ); - const responseHandler = ( packet: unknown ) => { + responseHandler = ( packet: unknown ) => { const validationResult = childMessagePm2Schema.safeParse( packet ); if ( ! validationResult.success ) { - cleanup(); reject( validationResult.error ); return; } @@ -201,13 +191,11 @@ async function sendMessage( if ( validPacket.raw.errorStack ) { error.stack = validPacket.raw.errorStack; } - cleanup(); reject( error ); } else if ( validPacket.raw.topic === 'result' && validPacket.raw.originalMessageId === messageId ) { - cleanup(); resolve( validPacket.raw.result ); } }; @@ -215,6 +203,14 @@ async function sendMessage( bus.on( 'process:msg', responseHandler ); sendMessageToProcess( pmId, { ...message, messageId } ).catch( reject ); + } ).finally( () => { + bus.off( 'process:msg', responseHandler ); + + const tracker = messageActivityTrackers.get( messageId ); + if ( tracker ) { + clearInterval( tracker.activityCheckIntervalId ); + messageActivityTrackers.delete( messageId ); + } } ); } diff --git a/cli/polyfills/browser-globals.js b/cli/polyfills/browser-globals.js index 74e76eed13..a9149edb39 100644 --- a/cli/polyfills/browser-globals.js +++ b/cli/polyfills/browser-globals.js @@ -14,6 +14,7 @@ if ( typeof document === 'undefined' ) { global.document = { createElement: () => ( {} ), addEventListener: () => {}, + baseURI: 'file:///', }; } diff --git a/common/logger-actions.ts b/common/logger-actions.ts index 9dcc3ff413..7d853b48e8 100644 --- a/common/logger-actions.ts +++ b/common/logger-actions.ts @@ -24,6 +24,7 @@ export enum SiteCommandLoggerAction { STOP_PROXY = 'stopProxy', GENERATE_CERT = 'generateCert', ADD_DOMAIN_TO_HOSTS = 'addDomainToHosts', + REMOVE_DOMAIN_FROM_HOSTS = 'removeDomainFromHosts', START_SITE = 'startSite', STOP_SITE = 'stopSite', VALIDATE = 'validate', diff --git a/package-lock.json b/package-lock.json index 6768e055b5..2cf1a4ea97 100644 --- a/package-lock.json +++ b/package-lock.json @@ -62,7 +62,7 @@ "winreg": "1.2.4", "wpcom": "^7.1.0", "wpcom-xhr-request": "^1.3.0", - "yargs": "17.7.2", + "yargs": "^18.0.0", "yauzl": "^3.2.0", "zod": "^3.24.3" }, @@ -3784,6 +3784,21 @@ "node": ">=8" } }, + "node_modules/@electron/rebuild/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@electron/rebuild/node_modules/fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", @@ -3908,6 +3923,25 @@ "dev": true, "license": "ISC" }, + "node_modules/@electron/rebuild/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@electron/universal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@electron/universal/-/universal-2.0.3.tgz", @@ -7746,6 +7780,20 @@ "fs-ext": "2.1.1" } }, + "node_modules/@php-wasm/fs-journal/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@php-wasm/fs-journal/node_modules/ini": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.2.tgz", @@ -7755,6 +7803,36 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/@php-wasm/fs-journal/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@php-wasm/fs-journal/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@php-wasm/logger": { "version": "3.0.22", "resolved": "https://registry.npmjs.org/@php-wasm/logger/-/logger-3.0.22.tgz", @@ -7805,6 +7883,20 @@ "fs-ext": "2.1.1" } }, + "node_modules/@php-wasm/node/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@php-wasm/node/node_modules/ini": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.2.tgz", @@ -7813,6 +7905,36 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/@php-wasm/node/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@php-wasm/node/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@php-wasm/progress": { "version": "3.0.22", "resolved": "https://registry.npmjs.org/@php-wasm/progress/-/progress-3.0.22.tgz", @@ -7939,6 +8061,20 @@ "fs-ext": "2.1.1" } }, + "node_modules/@php-wasm/web/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@php-wasm/web/node_modules/ini": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.2.tgz", @@ -7948,6 +8084,36 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/@php-wasm/web/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@php-wasm/web/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@php-wasm/xdebug-bridge": { "version": "3.0.22", "resolved": "https://registry.npmjs.org/@php-wasm/xdebug-bridge/-/xdebug-bridge-3.0.22.tgz", @@ -7976,6 +8142,20 @@ "fs-ext": "2.1.1" } }, + "node_modules/@php-wasm/xdebug-bridge/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@php-wasm/xdebug-bridge/node_modules/ini": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.2.tgz", @@ -7985,6 +8165,36 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/@php-wasm/xdebug-bridge/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@php-wasm/xdebug-bridge/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -11510,6 +11720,20 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/@wp-playground/blueprints/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@wp-playground/blueprints/node_modules/ini": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.2.tgz", @@ -11530,6 +11754,36 @@ "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.10.tgz", "integrity": "sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==" }, + "node_modules/@wp-playground/blueprints/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@wp-playground/blueprints/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@wp-playground/cli": { "version": "3.0.22", "resolved": "https://registry.npmjs.org/@wp-playground/cli/-/cli-3.0.22.tgz", @@ -11595,6 +11849,20 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/@wp-playground/cli/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@wp-playground/cli/node_modules/ini": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.2.tgz", @@ -11616,6 +11884,36 @@ "integrity": "sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==", "license": "(MIT AND Zlib)" }, + "node_modules/@wp-playground/cli/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@wp-playground/cli/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@wp-playground/common": { "version": "3.0.22", "resolved": "https://registry.npmjs.org/@wp-playground/common/-/common-3.0.22.tgz", @@ -11676,6 +11974,20 @@ "fs-ext": "2.1.1" } }, + "node_modules/@wp-playground/storage/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@wp-playground/storage/node_modules/diff3": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/diff3/-/diff3-0.0.3.tgz", @@ -11700,6 +12012,36 @@ "node": ">=6" } }, + "node_modules/@wp-playground/storage/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@wp-playground/storage/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@wp-playground/wordpress": { "version": "3.0.22", "resolved": "https://registry.npmjs.org/@wp-playground/wordpress/-/wordpress-3.0.22.tgz", @@ -11725,6 +12067,20 @@ "fs-ext": "2.1.1" } }, + "node_modules/@wp-playground/wordpress/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@wp-playground/wordpress/node_modules/ini": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.2.tgz", @@ -11734,6 +12090,36 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/@wp-playground/wordpress/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@wp-playground/wordpress/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@xmldom/xmldom": { "version": "0.8.11", "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.11.tgz", @@ -13452,27 +13838,69 @@ } }, "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", + "license": "ISC", "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" }, + "engines": { + "node": ">=20" + } + }, + "node_modules/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", "engines": { "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/cliui/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, "node_modules/clone": { @@ -18988,6 +19416,53 @@ } } }, + "node_modules/jest-cli/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/jest-cli/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-cli/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/jest-config": { "version": "30.0.5", "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.0.5.tgz", @@ -28808,20 +29283,20 @@ } }, "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", + "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", + "license": "MIT", "dependencies": { - "cliui": "^8.0.1", + "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", + "string-width": "^7.2.0", "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" + "yargs-parser": "^22.0.0" }, "engines": { - "node": ">=12" + "node": "^20.19.0 || ^22.12.0 || >=23" } }, "node_modules/yargs-parser": { @@ -28832,6 +29307,38 @@ "node": ">=12" } }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yargs/node_modules/yargs-parser": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", + "license": "ISC", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, "node_modules/yauzl": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.2.0.tgz", diff --git a/package.json b/package.json index 6ba1bc24a8..a409c9207d 100644 --- a/package.json +++ b/package.json @@ -156,7 +156,7 @@ "winreg": "1.2.4", "wpcom": "^7.1.0", "wpcom-xhr-request": "^1.3.0", - "yargs": "17.7.2", + "yargs": "^18.0.0", "yauzl": "^3.2.0", "zod": "^3.24.3" },