Skip to content

Refactor Studio to depend on CLI for import/export#3129

Merged
fredrikekelund merged 40 commits intotrunkfrom
stu-1540-continued-studio-import-export-refactor
Apr 23, 2026
Merged

Refactor Studio to depend on CLI for import/export#3129
fredrikekelund merged 40 commits intotrunkfrom
stu-1540-continued-studio-import-export-refactor

Conversation

@fredrikekelund
Copy link
Copy Markdown
Contributor

@fredrikekelund fredrikekelund commented Apr 17, 2026

Related issues

How AI was used in this PR

Codex and Claude were used extensively to review the refactor, focusing on the overall implementation and individual aspects, such as error handling.

Proposed Changes

This PR makes Studio depend on the CLI for import/export, rather than maintaining duplicate import/export implementations in both apps/cli and apps/studio.

We achieve this by using the existing event architecture for importers and exporters and forwarding events from the CLI to Studio over IPC, allowing the importers/exporters and the UI state management to remain largely intact.

A few things to note about the change:

  • Some of the import/export and sync wiring (like displaying error modals or notifications, and calling file cleanup functions) used to be part of the renderer process. This has been moved to the main Electron process.
  • To support push use cases, I've added two options to the CLI export command: --split-db-dump-by-table, and --include-only. Both options are hidden from the CLI's regular --help output.
  • Moreover, the export command now supports --mode=content (which is useful for selective pushing when only files are selected, and not the database). We also support .zip and .tar.gz extensions for --mode=db exports (which is useful for selective pushing when the database is selected).
  • To support Studio's behavior of always starting a server after an import, I've added the --start-server option to the import CLI command.

Testing Instructions

  1. Test full-site exports from Studio
  2. Test database-only exports from Studio
  3. Test full-site pull operations in Studio
  4. Test selective pull operations in Studio
  5. Test full-site push operations in Studio
  6. Test selective push operations in Studio
  7. Test node apps/cli/dist/cli/main.mjs export --path PATH_TO_SITE
  8. Test node apps/cli/dist/cli/main.mjs import --path PATH_TO_SITE

Pre-merge Checklist

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

Comment on lines +27 to +64
function handleExportIpc( emitter: ImportExportEventEmitter ) {
emitter.on( ExportEvents.EXPORT_START, () => {
sendIpcEvent( [ ExportEvents.EXPORT_START, undefined ] );
} );
emitter.on( ExportEvents.BACKUP_CREATE_START, () => {
sendIpcEvent( [ ExportEvents.BACKUP_CREATE_START, undefined ] );
} );
emitter.on( ExportEvents.WP_CONTENT_EXPORT_START, () => {
sendIpcEvent( [ ExportEvents.WP_CONTENT_EXPORT_START, undefined ] );
} );
emitter.on( ExportEvents.WP_CONTENT_EXPORT_COMPLETE, () => {
sendIpcEvent( [ ExportEvents.WP_CONTENT_EXPORT_COMPLETE, undefined ] );
} );
emitter.on( ExportEvents.DATABASE_EXPORT_START, () => {
sendIpcEvent( [ ExportEvents.DATABASE_EXPORT_START, undefined ] );
} );
emitter.on( ExportEvents.DATABASE_EXPORT_COMPLETE, () => {
sendIpcEvent( [ ExportEvents.DATABASE_EXPORT_COMPLETE, undefined ] );
} );
emitter.on( ExportEvents.BACKUP_CREATE_PROGRESS, ( progressData ) => {
sendIpcEvent( [ ExportEvents.BACKUP_CREATE_PROGRESS, progressData ] );
} );
emitter.on( ExportEvents.BACKUP_CREATE_COMPLETE, () => {
sendIpcEvent( [ ExportEvents.BACKUP_CREATE_COMPLETE, undefined ] );
} );
emitter.on( ExportEvents.CONFIG_EXPORT_START, () => {
sendIpcEvent( [ ExportEvents.CONFIG_EXPORT_START, undefined ] );
} );
emitter.on( ExportEvents.CONFIG_EXPORT_COMPLETE, () => {
sendIpcEvent( [ ExportEvents.CONFIG_EXPORT_COMPLETE, undefined ] );
} );
emitter.on( ExportEvents.EXPORT_COMPLETE, () => {
sendIpcEvent( [ ExportEvents.EXPORT_COMPLETE, undefined ] );
} );
emitter.on( ExportEvents.EXPORT_ERROR, ( error ) => {
sendIpcEvent( [ ExportEvents.EXPORT_ERROR, error ] );
} );
}
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.

This obviously repeats a lot of the handleExportEvents implementation, but iterating over ExportEvents in a type-safe way is not trivial. Unless I'm missing some brilliant solution to this, I think the best approach is to just accept the duplication and potentially do a more cohesive refactor in the future.

Same thing for the import command.

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.

This is a good place to start reviewing the implementation. This file defines IPC functions that Electron exposes to the renderer process. It triggers the CLI processes by calling executeExportCliCommand and executeImportCliCommand.

Comment thread apps/studio/package.json
"@wordpress/i18n": "^6.9.0",
"@wordpress/icons": "^11.4.0",
"@wordpress/react-i18n": "^4.41.0",
"archiver": "^7.0.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.

archiver was only used by the import/export implementation. I removed the dependency and the associated patch.

Base automatically changed from stu-1540-studio-depends-on-cli-import-export to trunk April 20, 2026 11:14
@wpmobilebot
Copy link
Copy Markdown
Collaborator

wpmobilebot commented Apr 20, 2026

📊 Performance Test Results

Comparing c0ddf30 vs trunk

app-size

Metric trunk c0ddf30 Diff Change
App Size (Mac) 1492.93 MB 1482.72 MB 10.21 MB 🟢 -0.7%

site-editor

Metric trunk c0ddf30 Diff Change
load 1519 ms 1883 ms +364 ms 🔴 24.0%

site-startup

Metric trunk c0ddf30 Diff Change
siteCreation 8114 ms 8146 ms +32 ms ⚪ 0.0%
siteStartup 4962 ms 4944 ms 18 ms ⚪ 0.0%

Results are median values from multiple test runs.

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

@fredrikekelund
Copy link
Copy Markdown
Contributor Author

For the record, I'm looking into the E2E test that's failing on Windows.

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.

This PR removed the last SQLite version management operations from apps/studio, so I took the opportunity to remove this file, too 👍

Comment on lines +104 to +107
if ( wasNotRunning && running ) {
void captureSiteThumbnail( siteId );
await server.getThemeDetails();
}
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.

While going over the smoke testing suite, I noticed that thumbnails and theme details were failing to load when I created a new site by importing an archive. I don't believe that was because of my other changes in this PR. In any case, this change fixes that 👍

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.

A good chunk of this file became stale due to my other changes in this PR. I could have asked AI to refactor it, but I don't think that provides us with any real assurances, so I opted to just remove it. That's because these tests relied too heavily on mocks or tested things that are now assured by proper Typescript types.

@fredrikekelund
Copy link
Copy Markdown
Contributor Author

For clarity, I've run this through multiple rounds of local review, and I've run the import/export and sync smoke tests against this PR.

This PR is definitely ready for review now 👍

Copy link
Copy Markdown
Contributor

@bcotrim bcotrim left a comment

Choose a reason for hiding this comment

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

Possible regression worth double-checking: cancelling a push/pull doesn't stop the CLI child process.

Repro: inflate a site's wp-content/uploads/ to a decent size, push, then hit Cancel mid-backup. UI shows "Cancelled" instantly, but ps | grep 'main.mjs export' shows the child still running and $TMPDIR/com.wordpress.studio//site_.tar.gz keeps growing until the CLI finishes on its own.

Can you replicate this issue?

@fredrikekelund
Copy link
Copy Markdown
Contributor Author

Good catch, @bcotrim. I've pushed a fix that kills the CLI child process when a push operation is aborted 👍

Copy link
Copy Markdown
Contributor

@bcotrim bcotrim left a comment

Choose a reason for hiding this comment

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

LGTM 👍
Couldn't find any regressions during my tests.

Added a minor comment, but it's not something I would consider a blocker.

'No suitable backup handler found for the provided backup file',
];

function isExpectedImportError( error: unknown ): boolean {
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.

Did you consider adding error codes to the payload? The substring matches in isExpectedImportError and the 'Error: absolute path: /' check are locale-fragile and library-fragile. If any parsing is necessary, the CLI should own it and send a structured code over IPC.

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.

That's a good point. This logic was there before, too, though. I'd consider this a good follow-up

@fredrikekelund fredrikekelund merged commit 9d0add3 into trunk Apr 23, 2026
14 checks passed
@fredrikekelund fredrikekelund deleted the stu-1540-continued-studio-import-export-refactor branch April 23, 2026 10:36
gcsecsey added a commit that referenced this pull request May 6, 2026
Conflicts in apps/studio/src/hooks/use-import-export.tsx and the deletion
of its test file: trunk's #3129 refactored the hook to depend on the CLI
for import/export, removing Sentry, useSiteDetails, error formatting,
and the BackupArchiveInfo path. Took trunk's simpler structure and
re-applied the logout cleanup block (useAuth + useRootSelector +
useRef-based pre-logout pull-active snapshot driving the
setImportState/setExportState reset).

Dropped the isError flag on ImportProgressState and the matching
pullImportFailed workaround in sync-connected-sites.tsx: trunk's pull
thunk now propagates importSite errors via pullStates.failed, so the
"dismiss button stuck disabled" bug those addressed is no longer
reachable. Accepted trunk's deletion of use-import-export.test.tsx as
part of the legacy-test cleanup.
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