Skip to content

Add --all flag to preview list command#3098

Merged
bcotrim merged 12 commits intotrunkfrom
stu-1434-cli-list-all-preview-sites
Apr 22, 2026
Merged

Add --all flag to preview list command#3098
bcotrim merged 12 commits intotrunkfrom
stu-1434-cli-list-all-preview-sites

Conversation

@bcotrim
Copy link
Copy Markdown
Contributor

@bcotrim bcotrim commented Apr 15, 2026

Related issues

How AI was used in this PR

Claude Code (Sonnet 4.5) helped scaffold the implementation and tests based on the existing studio preview list command structure. I reviewed each change, iterated on several design decisions (flag vs. subcommand, grouping strategy, sort order, orphan coalescing), and ran the full lint/typecheck/test loop after each revision.

Proposed Changes

  • Add a new --all flag to studio preview list that lists preview sites across all local Studio sites, grouped by site
  • Groups are sorted by preview-site count descending, with alphabetical tie-breaking — this directly answers the "which local site is closest to the 10-preview-site limit?" question from the issue
  • Orphaned snapshots (whose local site no longer exists in the config) coalesce into a single Unknown site group
  • Extract a shared buildSnapshotTable() helper so the single-site and --all table rendering stay consistent
  • Add 6 new tests covering the --all path (happy path, grouped headers, sort order, orphan fallback, orphan coalescing, expired badge, empty state) + 1 regression test for the single-site path

Note: The JSON output path (--format=json) is unchanged by this PR — it already dumps the full config.snapshots array regardless of the current site, so --all has no effect there. Keeping that behavior consistent with the existing command; happy to tighten it up in a follow-up if desired.

Testing Instructions

Setup: Have at least 2 local Studio sites with one or more preview sites each (use studio preview create in each site folder to generate some).

Happy path

  1. npm run cli:build
  2. From anywhere on disk (doesn't need to be inside a site folder): node apps/cli/dist/cli/main.mjs preview list --all
  3. Verify the output shows one grouped table per local site, with a header like My Site (3 preview sites)
  4. Verify groups appear in descending order of preview-site count

Regression — existing single-site behavior

  1. cd into a Studio site folder
  2. node apps/cli/dist/cli/main.mjs preview list
  3. Verify the output is unchanged from before (single table, no grouped headers)

Edge cases

  • No preview sites at all: run preview list --all with a user that has zero preview sites → friendly No preview sites found message
  • Outside a site folder, no --all: run preview list from ~/Desktop or similar → clear error that the directory isn't added to Studio (unchanged behavior)
  • Expired snapshots: if any preview sites are expired, verify the summary shows Found N preview sites (M expired) in the --all output
  • Not logged in: run studio auth logout then preview list --allAuthentication required error (unchanged behavior)

Automated

  • npm test -- apps/cli/commands/preview/tests/list.test.ts — 11 tests should pass
  • npx eslint apps/cli/commands/preview/list.ts apps/cli/commands/preview/tests/list.test.ts — clean

Pre-merge Checklist

  • Have you checked for TypeScript, React or other console errors?

@bcotrim bcotrim self-assigned this Apr 15, 2026
@bcotrim bcotrim requested a review from a team April 15, 2026 11:53
@wpmobilebot
Copy link
Copy Markdown
Collaborator

wpmobilebot commented Apr 15, 2026

📊 Performance Test Results

Comparing 7a846f1 vs trunk

app-size

Metric trunk 7a846f1 Diff Change
App Size (Mac) 1491.74 MB 1491.74 MB +0.00 MB ⚪ 0.0%

site-editor

Metric trunk 7a846f1 Diff Change
load 1552 ms 1897 ms +345 ms 🔴 22.2%

site-startup

Metric trunk 7a846f1 Diff Change
siteCreation 10126 ms 10149 ms +23 ms ⚪ 0.0%
siteStartup 5936 ms 5945 ms +9 ms ⚪ 0.0%

Results are median values from multiple test runs.

Legend: 🟢 Improvement (faster) | 🔴 Regression (slower) | ⚪ No change (<50ms diff)

Copy link
Copy Markdown
Contributor

@fredrikekelund fredrikekelund left a comment

Choose a reason for hiding this comment

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

I think we should consider excluding expired preview sites without an associated local sites. They are basically guaranteed to be deleted on the server, and they risk cluttering the output of this command.

@bcotrim bcotrim requested a review from fredrikekelund April 16, 2026 15:47
Copy link
Copy Markdown
Contributor

@fredrikekelund fredrikekelund left a comment

Choose a reason for hiding this comment

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

Nice and clean! 👍 I left a couple of comments that'd be nice to address, but they're all minor.

Comment on lines 114 to 119
for ( const snapshot of snapshots ) {
const durationUntilExpiry = formatDurationUntilExpiry( snapshot.date );
const url = `https://${ snapshot.url }`;

table.push( [
{ href: url, content: url },
snapshot.name,
format( snapshot.date, 'yyyy-MM-dd HH:mm' ),
durationUntilExpiry,
] );
const siteName = siteNameById.get( snapshot.localSiteId ) ?? unknownSiteLabel;
const bucket = snapshotsBySiteName.get( siteName ) ?? [];
bucket.push( snapshot );
snapshotsBySiteName.set( siteName, bucket );
}
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.

Studio doesn't enforce local site name uniqueness. I think it would be smarter to group the preview sites by local site ID instead of by name.

Comment thread apps/cli/commands/preview/list.ts Outdated
Comment on lines +110 to +111
if ( all && siteNameById ) {
const unknownSiteLabel = __( 'Unknown site' );
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
if ( all && siteNameById ) {
const unknownSiteLabel = __( 'Unknown site' );
if ( all ) {
const config = await readCliConfig();
const siteNameById = new Map< string, string >(
config.sites.map( ( site ) => [ site.id, site.name ] )
);
const unknownSiteLabel = __( 'Unknown site' );

I don't really see a good reason to construct siteNameById outside this if statement. If there are no local sites, then it'd be better to group all existing snapshots under Unknown site

Comment thread apps/cli/commands/preview/list.ts Outdated
Comment on lines +76 to +79
// Expired orphans are almost certainly already deleted server-side — clean them up.
if ( all ) {
await pruneExpiredOrphanedSnapshots( token.id, isSnapshotExpired );
}
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
// Expired orphans are almost certainly already deleted server-side — clean them up.
if ( all ) {
await pruneExpiredOrphanedSnapshots( token.id, isSnapshotExpired );
}
// Expired orphans are almost certainly already deleted server-side — clean them up.
await pruneExpiredOrphanedSnapshots( token.id, isSnapshotExpired );

Not a big deal either way, but we could do this regardless of whether the --all option was passed or not.

Comment thread apps/cli/commands/preview/list.ts Outdated
Comment on lines +163 to +174
choices: [ 'table', 'json' ],
default: 'table',
description: __( 'Output format' ),
} )
.option( 'all', {
type: 'boolean',
default: false,
description: __( 'List preview sites for all local sites, grouped by site' ),
} );
},
handler: async ( argv ) => {
await runCommand( argv.path, argv.format as 'table' | 'json' );
await runCommand( argv.path, argv.format as 'table' | 'json', Boolean( argv.all ) );
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.

					choices: [ 'table', 'json' ] as const,
					default: 'table' as const,
					description: __( 'Output format' ),
				} )
				.option( 'all', {
					type: 'boolean',
					default: false,
					description: __( 'List preview sites for all local sites, grouped by site' ),
				} );
		},
		handler: async ( argv ) => {
			await runCommand( argv.path, argv.format, argv.all );

Two nits:

  1. By adding const assertions to the choices and default config params, TS can correctly infer the argv.format type, and we can remove the type assertion.
  2. argv.all is already a boolean, so there's no need to cast it.

@bcotrim bcotrim merged commit 6220a76 into trunk Apr 22, 2026
10 checks passed
@bcotrim bcotrim deleted the stu-1434-cli-list-all-preview-sites branch April 22, 2026 12:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants