Skip to content

Update studio site list CLI command - #2127

Merged
fredrikekelund merged 10 commits into
trunkfrom
f26d/cli-site-list-command
Nov 27, 2025
Merged

Update studio site list CLI command#2127
fredrikekelund merged 10 commits into
trunkfrom
f26d/cli-site-list-command

Conversation

@fredrikekelund

@fredrikekelund fredrikekelund commented Nov 25, 2025

Copy link
Copy Markdown
Contributor

Related issues

Proposed Changes

@youknowriad originally implemented the studio site list command in #1742. This PR updates that command with relevant site details now that #2096 has been merged. We now include:

  • Site status (online/offline)
  • Site name
  • Site path (prettified to make many paths shorter)
  • Site URL
  • PHP version

Testing Instructions

  1. Run npm run cli:build
  2. Run node dist/cli/main.js site list
  3. Ensure that the output is good
  4. Run node dist/cli/main.js site start --path PATH_TO_SITE (where PATH_TO_SITE is the path to a Studio site)
  5. Run node dist/cli/main.js site list again
  6. Ensure that the site is now marked as "online" in the output

Pre-merge Checklist

  • Have you checked for TypeScript, React or other console errors?
@fredrikekelund
fredrikekelund requested review from a team, bcotrim and youknowriad November 25, 2025 13:45
@fredrikekelund fredrikekelund self-assigned this Nov 25, 2025
const loggerError = new LoggerError( __( 'Failed to start site infrastructure' ), error );
logger.reportError( loggerError );
}
process.exit( 1 );

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We already set the exit code in cli/logger.ts. No need to explicitly exit the process in this way.

@nightnei

nightnei commented Nov 25, 2025

Copy link
Copy Markdown
Contributor

@fredrikekelund after Run node dist/cli/main.js site list I got:
Screenshot 2025-11-25 at 14 17 39

Maybe something is wrong with my environment. Could you please double-check, and if it indeed works for you, then I will take a look deeper into why it fails for me.

@github-actions

github-actions Bot commented Nov 25, 2025

Copy link
Copy Markdown
Contributor

📊 Performance Test Results

Comparing 27207ff vs trunk

site-editor

Metric trunk 27207ff Diff Change
load 14874.00 ms 15821.00 ms +947.00 ms 🔴 6.4%

site-startup

Metric trunk 27207ff Diff Change
siteCreation 22282.00 ms 27277.00 ms +4995.00 ms 🔴 22.4%
siteStartup 9021.00 ms 11010.00 ms +1989.00 ms 🔴 22.0%

Results are median values from multiple test runs.

Legend: 🟢 Improvement (faster) | 🔴 Regression (slower) | ⚪ No change

Comment thread cli/commands/site/list.ts
site.status,
site.name,
site.path,
{ href: new URL( site.url ).toString(), content: site.url },

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@nightnei, I believe the error you mentioned #2127 (comment) happens here. Could you please add a simple console.log(site.url) call right above the table.push call here to see what might be causing the error here?

@nightnei nightnei left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM and everything works as expected:
Screenshot 2025-11-25 at 15 29 07
Screenshot 2025-11-25 at 15 32 16
Screenshot 2025-11-25 at 15 32 31

However, I have a few questions:

  1. If Studio is running and we have started sites - they are not reflected in CLI, is it expected?
  2. After running a site via CLI, then running Studio (with previously started sites, so it's expected that they will be started after running Studio again) - I get the next error:
Screenshot 2025-11-25 at 15 35 10

Marking as "requested changes", just not to miss your answer :)

@fredrikekelund

Copy link
Copy Markdown
Contributor Author

If Studio is running and we have started sites - they are not reflected in CLI, is it expected?

It's expected for now. We intend to make the Studio CLI the "back-end" of Studio, and use it to start/stop/create sites. At that point, the CLI "running status" will be the single source of truth. For now, though, a site's running status isn't shared between the CLI and the app.

After running a site via CLI, then running Studio (with previously started sites, so it's expected that they will be started after running Studio again) - I get the next error:

Same thing: this is expected for now, but it will be resolved when the Studio CLI is responsible for starting/stopping/creating sites.

@bcotrim bcotrim left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code LGTM and works as described 👍

Image

Should we also display the WordPress version, or just PHP?
Edit: I just realized we don't store wp version in appdata, so we could address this in a follow-up.
We show this in Studio settings using common/lib/get-wordpress-version.ts
image

Comment thread cli/commands/site/list.ts
return table;
}
for await ( const site of sites ) {
const isOnline = await isServerRunning( site.id );

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

isServerRunning will eventually call listProcesses I think we could do some optimization here to only fetch the list of running processes once.

@fredrikekelund

Copy link
Copy Markdown
Contributor Author

Should we also display the WordPress version, or just PHP?

I pondered this and ultimately decided to remove the PHP version. There are three truly relevant properties that this command should return, IMO: running status, path, and URL.

The name is largely irrelevant in the CLI context, but still makes sense to keep for consistency. Rather than printing more properties with this command (PHP version and WP version), users can turn to the studio site status command for comprehensive details about a single site.

isServerRunning will eventually call listProcesses I think we could do some optimization here to only fetch the list of running processes once.

I implemented a cacheFunctionTTL higher-order function that caches the return value of listProcesses for a short duration. This didn't make a big difference with the 10 sites I have (20 milliseconds), but I still decided to leave it in for now.

Comment thread common/lib/cache-function-ttl.ts Outdated
Comment on lines +1 to +16
const cache = new Map< () => Promise< unknown >, { result: unknown; timestamp: number } >();

export function cacheFunctionTTL< Args extends unknown[], Return >(
fn: ( ...args: Args ) => Promise< Return >,
ttl = 1 * 1000
) {
return async ( ...args: Args ) => {
const cachedValue = cache.get( fn );
if ( cachedValue && Date.now() - cachedValue.timestamp < ttl ) {
return cachedValue.result as Return;
}
const value = await fn( ...args );
cache.set( fn, { result: value, timestamp: Date.now() } );
return value;
};
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
const cache = new Map< () => Promise< unknown >, { result: unknown; timestamp: number } >();
export function cacheFunctionTTL< Args extends unknown[], Return >(
fn: ( ...args: Args ) => Promise< Return >,
ttl = 1 * 1000
) {
return async ( ...args: Args ) => {
const cachedValue = cache.get( fn );
if ( cachedValue && Date.now() - cachedValue.timestamp < ttl ) {
return cachedValue.result as Return;
}
const value = await fn( ...args );
cache.set( fn, { result: value, timestamp: Date.now() } );
return value;
};
}
export function cacheFunctionTTL< Args extends unknown[], Return >(
fn: ( ...args: Args ) => Promise< Return >,
ttl = 1000
) {
const cache = new Map< string, { result: Return; timestamp: number } >();
return async ( ...args: Args ) => {
const key = JSON.stringify( args );
const cachedValue = cache.get( key );
if ( cachedValue && Date.now() - cachedValue.timestamp < ttl ) {
return cachedValue.result;
}
const value = await fn( ...args );
cache.set( key, { result: value, timestamp: Date.now() } );
return value;
};
}

Currently this function has a bug for calls with different arguments.
A test like this would fail:

it( 'should return different results for different arguments', async () => {
		const fetchUser = jest.fn( async ( userId: number ) => {
			return { id: userId, name: `User ${ userId }` };
		} );

		const cachedFetchUser = cacheFunctionTTL( fetchUser, 1000 );

		const user1 = await cachedFetchUser( 1 );
		const user2 = await cachedFetchUser( 2 );

		expect( user1 ).toEqual( { id: 1, name: 'User 1' } );
		expect( user2 ).toEqual( { id: 2, name: 'User 2' } );
	} );

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch 👍 I've updated cacheFunctionTTL to account for function arguments, too (using fast-deep-equal to compare function arguments)

@bcotrim bcotrim left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for making the change to the cache function. I would still go for the simple suggestion but your implementation is very solid and well tested.
LGTM 👍

Image
@fredrikekelund
fredrikekelund merged commit 1b8d86c into trunk Nov 27, 2025
10 checks passed
@fredrikekelund
fredrikekelund deleted the f26d/cli-site-list-command branch November 27, 2025 15:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

3 participants