Skip to content

Implement import/export in CLI - #2963

Merged
fredrikekelund merged 52 commits into
add_cli_sync_and_import_operationsfrom
stu-1351-import-export-in-cli
Apr 8, 2026
Merged

Implement import/export in CLI#2963
fredrikekelund merged 52 commits into
add_cli_sync_and_import_operationsfrom
stu-1351-import-export-in-cli

Conversation

@fredrikekelund

@fredrikekelund fredrikekelund commented Apr 2, 2026

Copy link
Copy Markdown
Contributor

Related issues

How AI was used in this PR

Primarily to review my implementation and help me preemptively fix several issues.

Proposed Changes

Tip

I'm happy to pair review this PR.

This one's a biggie. It adds site import and site export CLI commands. I've copied the core import/export logic from apps/studio and adapted it to CLI conditions (the types are slightly different, we call WP-CLI in different ways, etc).

Moreover, this PR finalizes the sync pull and sync push commands by wiring up the import/export logic there, too.

Testing Instructions

  1. npm run cli:build
  2. Try the node apps/cli/dist/cli/main.mjs site export command. Explore the flags and try different combinations.
  3. Try the node apps/cli/dist/cli/main.mjs site import command. Explore the flags and try different combinations.
  4. Try the node apps/cli/dist/cli/main.mjs sync pull command.
  5. Try the node apps/cli/dist/cli/main.mjs sync push command.

Pre-merge Checklist

  • Have you checked for TypeScript, React or other console errors?
bcotrim and others added 29 commits March 23, 2026 21:07
- Move process.emit restore into finally block in push command
- Emit SYNC_EVENTS.FAILED in catch blocks for both pull and push
- Extract poll constants and parseSyncOptions to shared locations
…gs, double-cancel, reorder validation, remove re-exports
@fredrikekelund
fredrikekelund requested a review from bcotrim April 2, 2026 13:37
@fredrikekelund
fredrikekelund force-pushed the stu-1351-import-export-in-cli branch from f26f025 to 9ab29a1 Compare April 2, 2026 13:58
Comment on lines -121 to -124
// We configure `trash` as an external module, since it includes a native macOS binary that Vite
// inlines as a base64 string, which produces a runtime error. Since `trash` is also an ESM-only
// module, we need to import it dynamically (since Rollup doesn't get a chance to process it)
const trash = ( await import( 'trash' ) ).default;

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.

Not related to the core changes in this PR, but we output ESM now, and trash is always configured as an external module when Vite bundles, so this is no longer an issue.

break;

case ExportEvents.BACKUP_CREATE_PROGRESS: {
const progressData = data as BackupCreateProgressEventData;

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.

I'd love to avoid type assertions for the event payloads, but since this copies the logic we already have in the app, I decided to stick with that for now.

Comment on lines +90 to +95
logger.reportStart(
LoggerAction.INSTALL_SQLITE,
__( 'Setting up SQLite integration, if needed…' )
);
await keepSqliteIntegrationUpdated( siteFolder );
logger.reportSuccess( __( 'SQLite integration configured as needed' ) );

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.

It's important to keep the SQLite integration up to date whenever we work with the site database. Exporting the site is one such example.

const zipUrl = getWordPressVersionUrl( wp );

const [ response, exitPhp ] = await runWpCliCommand( sitePath, phpVersion, [
await using command = await runWpCliCommand( sitePath, phpVersion, [

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.

If you're not familiar with the using keyword, I recommend a quick read-up on JavaScript resource management. Playground already uses this internally.

In short, it saves us from having to call a cleanup function (exitPhp) because the JS runtime automatically calls the method identified by Symbol.dispose when exiting the scope. Very nifty.

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.

TIL

I wonder if we could refactor our config files lock/unlock pattern with this approach.
Obviously not a blocker for this PR, and it could be a follow-up

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 an interesting candidate 👍 There's some nuance to how we handle that, though – it sometimes takes a while for the code to exit the current scope after writing the config file. Worth exploring, though.

connectToDaemon and disconnectFromDaemon also seem like good candidates to me.

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.

See my comment in apps/cli/commands/site/set.ts about JavaScript resource management to understand what's going on here.

Comment thread apps/cli/package.json Outdated
Comment on lines +30 to +39
"@php-wasm/node": "3.1.15",
"@php-wasm/universal": "3.1.15",
"@php-wasm/util": "3.1.15",
"@php-wasm/node": "3.1.13",
"@php-wasm/universal": "3.1.13",
"@php-wasm/util": "3.1.13",
"@vscode/sudo-prompt": "^9.3.2",
"@wordpress/i18n": "^6.14.0",
"@wp-playground/blueprints": "3.1.15",
"@wp-playground/cli": "3.1.15",
"@wp-playground/common": "3.1.15",
"@wp-playground/storage": "3.1.15",
"@wp-playground/wordpress": "3.1.15",
"@wp-playground/blueprints": "3.1.13",
"@wp-playground/cli": "3.1.13",
"@wp-playground/common": "3.1.13",
"@wp-playground/storage": "3.1.13",
"@wp-playground/wordpress": "3.1.13",

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.

I had to downgrade these packages because I couldn't get WP-CLI to play ball. See p1775051878031829-slack-C04GESRBWKW

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.

I was able to revert this change after copying the fix from #2980

@fredrikekelund
fredrikekelund marked this pull request as ready for review April 2, 2026 14:52
Base automatically changed from stu-1354-site-picker-sync to add_cli_sync_and_import_operations April 7, 2026 14:22

@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.

LGTM 👍
Added a few comments but no blockers.
I was considering if we should share the importer and exporters instead of copying, but since we are eventually removing the operations from Studio app and using the CLI, I think it makes sense to avoid any side effects now and just live with copied files.

describe: __( 'Export site to a backup file' ),
builder: ( yargs ) => {
return yargs
.positional( 'export-file', {

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.

Should we allow this to be empty and use a generated file name in the current folder?
I think it would be a good UX improvement

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 fair. I've made this change 👍


wasServerRunning = !! ( await isServerRunning( site.id ) );

if ( wasServerRunning ) {

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.

Nit: Could we move this after the import file and site path directory validations? It could potentially prevent an unnecessary site restart.


// Something in Playground makes it so the front-end of the site sometimes returns an error page
// on the first request. Send that first request from here to hide the error from the user.
const siteUrl = getSiteUrl( 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.

Was this reported to the Playground team?
The workaround isn't particularly problematic but I wonder what is the root cause for it, and if it has other side effects.

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.

Not yet, no. I'll put it on my to-do list. I encountered it several times, but I haven't isolated the error yet, so I'd like to do that before reporting.

const zipUrl = getWordPressVersionUrl( wp );

const [ response, exitPhp ] = await runWpCliCommand( sitePath, phpVersion, [
await using command = await runWpCliCommand( sitePath, phpVersion, [

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.

TIL

I wonder if we could refactor our config files lock/unlock pattern with this approach.
Obviously not a blocker for this PR, and it could be a follow-up

sprintf( __( 'Pulled from %s (%s)' ), remoteSite.name, remoteSite.url )
);
} finally {
fs.rmSync( tempDir, { recursive: true, force: true } );

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.

This was a pre-existing issue from my initial PR.
Concurrent sync operations, even for different sites could break since the tempDir is always the same.
Studio app uses mkdtemp

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.

Great catch 👍

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.

I made the corresponding update in the push command, too.

- Default values for export files.
- Replace `--only` with `--mode` for export command. Users can choose between "full" and "db". The `--mode` option is required and defaults to `full`.
@fredrikekelund

Copy link
Copy Markdown
Contributor Author

I was considering if we should share the importer and exporters instead of copying, but since we are eventually removing the operations from Studio app and using the CLI, I think it makes sense to avoid any side effects now and just live with copied files.

Yeah, I think so, too. We call WP-CLI in several places in the importer and exporter, and it would be an unnecessary headache to rewrite a shared version of the code to inject a WP-CLI caller that wraps the CLI/app logic. However, this assumes we proceed soon to rewrite the app's import/export logic to depend on the CLI.

I've addressed your other review comments, @bcotrim. I also changed the export command to use a --mode=db|full option instead of --only. --mode is required, but it defaults to full. When combined with the fact that export filenames are now auto-generated, I found this more convenient and logical compared to --only.

@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 changes
LGTM 👍

@fredrikekelund
fredrikekelund merged commit d5047bc into add_cli_sync_and_import_operations Apr 8, 2026
6 of 7 checks passed
@fredrikekelund
fredrikekelund deleted the stu-1351-import-export-in-cli branch April 8, 2026 10:28
fredrikekelund added a commit that referenced this pull request Apr 9, 2026
* CLI: Add sync push/pull commands (#2878)

* Add sync push/pull CLI commands with shared Zod schemas and event pipeline

* Fix CLI build by replacing stream/promises with stream import

* Fix lint errors in sync store files

* Add progress tracking, --archive flag, and better error handling to sync commands

* Add interactive selective sync to CLI push and pull commands

* Extract shared sync logic to common and fix selective pull bugs

* Fix pre-existing TS error in process.emit override

* Fix lint error in process.emit override

* Remove unused sync constants re-export

* Remove unnecessary comment in sync-operations-slice

* Add esc cancel support and unify help tip layout across prompts

* Fix return types for sync selector cancel support

* Use Zod schema for SyncOption validation instead of manual VALID_OPTIONS

* Fix sync event lifecycle bugs and extract shared constants

- Move process.emit restore into finally block in push command
- Emit SYNC_EVENTS.FAILED in catch blocks for both pull and push
- Extract poll constants and parseSyncOptions to shared locations

* Add sync size validation to CLI push and pull commands

* Simplify type annotations in sync push and pull commands

* Remove unused sync CLI events

* Add --site arg, stale-progress polling, and fix push archive flow

* Rename --site arg to --remote-site in sync commands

* Address PR feedback: move try/catch to handlers, parse options in yargs, double-cancel, reorder validation, remove re-exports

* Move logger to module scope in sync pull/push commands

* Remove type re-exports, import directly from common

* Fix import order in sync test

* Remove dead tusUpload and use raw progress for stall detection

* Address PR feedback: use normalizeHostname, chalk, camelCase schema, remove type assertion

* Address PR feedback: camelCase schemas, static tus import, sort by name, remove re-exports

---------

Co-authored-by: Fredrik Rombach Ekelund <fredrik@f26d.dev>

* Implement import/export in CLI (#2963)

* Add sync push/pull CLI commands with shared Zod schemas and event pipeline

* Fix CLI build by replacing stream/promises with stream import

* Fix lint errors in sync store files

* Add progress tracking, --archive flag, and better error handling to sync commands

* Add interactive selective sync to CLI push and pull commands

* Extract shared sync logic to common and fix selective pull bugs

* Fix pre-existing TS error in process.emit override

* Fix lint error in process.emit override

* Remove unused sync constants re-export

* Remove unnecessary comment in sync-operations-slice

* Add esc cancel support and unify help tip layout across prompts

* Fix return types for sync selector cancel support

* Use Zod schema for SyncOption validation instead of manual VALID_OPTIONS

* Fix sync event lifecycle bugs and extract shared constants

- Move process.emit restore into finally block in push command
- Emit SYNC_EVENTS.FAILED in catch blocks for both pull and push
- Extract poll constants and parseSyncOptions to shared locations

* Add sync size validation to CLI push and pull commands

* Simplify type annotations in sync push and pull commands

* Remove unused sync CLI events

* Add --site arg, stale-progress polling, and fix push archive flow

* Rename --site arg to --remote-site in sync commands

* Address PR feedback: move try/catch to handlers, parse options in yargs, double-cancel, reorder validation, remove re-exports

* Move logger to module scope in sync pull/push commands

* Remove type re-exports, import directly from common

* Fix import order in sync test

* First iteration of the `import` CLI command

* Initial version of export command

* Polishing

* Fix and refine

* Import command: restart site on error, too

* Allow .sql exports without `--only` flag

* Improved error handling

* More error handling

* Move files

* Wire up import/export logic to sync commands

* Install missing dependency

* Cleanup and test fixes

* Windows-friendly paths in test

* Fix issues identified in review

* Address review comments

* Stray console.log

* Revert Playground downgrades and implement WP-CLI fix

* Disallow database-only exports for .zip and .tar.gz files

* Numerous changes

- Default values for export files.
- Replace `--only` with `--mode` for export command. Users can choose between "full" and "db". The `--mode` option is required and defaults to `full`.

* Address review comments

---------

Co-authored-by: bcotrim <bernardo.cotrim@a8c.com>
Co-authored-by: Bernardo Cotrim <bmmcotrim@gmail.com>

* glob dev dependency for the CLI

* Fix Vite config

* Move new commands to topmost scope

Also, sort all CLI commands alphabetically

* Use search component in sync site picker and add i18n to CLI strings (#3014)

---------

Co-authored-by: Bernardo Cotrim <bmmcotrim@gmail.com>
Co-authored-by: bcotrim <bernardo.cotrim@a8c.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants