Skip to content

Add live backup copy support for partial dev-env db sync and db export#2476

Merged
rinatkhaziev merged 28 commits intotrunkfrom
custom-dev-env-sync
Aug 27, 2025
Merged

Add live backup copy support for partial dev-env db sync and db export#2476
rinatkhaziev merged 28 commits intotrunkfrom
custom-dev-env-sync

Conversation

@luismulinari
Copy link
Copy Markdown
Contributor

@luismulinari luismulinari commented Jul 23, 2025

Description

This PR adds live backup copy functionality to the export sql and dev-env sync sql commands, enabling developers to download or sync partial database dumps from production environments. Instead of downloading full database exports, users can now specify custom configurations to sync/download only specific tables, subsites, or configs generated by custom WP-CLI commands.

Changes Made

  • New --config-file option: Added support for JSON configuration files that define partial backup parameters
  • Live backup copy integration: Implemented generateLiveBackupCopy() method that creates on-demand backups based on configuration
  • Flexible backup types: Support for:
    • Specific database tables with custom options (where clauses, skip-add-drop-table, etc.)
    • Subsite-specific data for multisite installations
    • Custom WP-CLI commands that generate dynamic backup configurations
      files

Configuration Examples

Table-specific backup with conditions

{
    "type": "tables",
    "tool": "mysqldump",
    "tables": {
        "wp_posts": {
            "where": "post_date >= NOW() - INTERVAL 30 DAY OR post_modified >= NOW() - INTERVAL 30 DAY"
        },
        "wp_users": {
            "where": "ID > 100"
            "skip-add-drop-table": true,
            "replace": true
        },
        "wp_options": {
            "skip-add-drop-table": true,
            "replace": true
        }
    }
}

Subsite-specific backup (multisite)

{
    "type": "subsite_ids",
    "tool": "mysqldump",
    "subsiteIds": [1, 2, 5]
}

Custom WP-CLI command

{
    "type": "wpcli_command",
    "tool": "mysqldump",
    "wpcliCommand": "custom-db-copy-config --since='2025-01-01'"
}

Changelog Description

Added

Removed

Fixed

Changed

Pull request checklist

New release checklist

Steps to Test

Outline the steps to test and verify the PR here.

Example:

  1. Check out PR.
  2. Run npm run build
  3. Run ./dist/bin/vip-cookies.js nom
  4. Verify cookies are delicious.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Jul 23, 2025

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@rinatkhaziev
Copy link
Copy Markdown
Contributor

/changelog

@github-actions
Copy link
Copy Markdown
Contributor

AI-Generated Changelog Entry

  • Added an option to use a custom configuration file when syncing database backups in local development environments, giving users more control over their backup process.

@luismulinari luismulinari force-pushed the custom-dev-env-sync branch from 4edc7ad to eb14388 Compare July 30, 2025 13:37
@luismulinari luismulinari changed the title Add dev-env sync custom config option Add live backup copy support for partial dev-env database sync Jul 30, 2025
@luismulinari luismulinari marked this pull request as ready for review July 30, 2025 13:49
@luismulinari luismulinari requested review from a team and rinatkhaziev July 30, 2025 13:49
@sjinks sjinks requested a review from Copilot July 30, 2025 14:54

This comment was marked as outdated.

luismulinari and others added 4 commits July 30, 2025 11:58
Co-authored-by: Volodymyr Kolesnykov <volodymyr.kolesnykov@automattic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@Automattic Automattic deleted a comment from Copilot AI Jul 30, 2025
@luismulinari luismulinari requested review from Copilot and sjinks July 30, 2025 20:53
luismulinari and others added 4 commits August 1, 2025 17:30
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@luismulinari luismulinari changed the title Add live backup copy support for partial dev-env database sync Add live backup copy support for partial dev-env db sync and db export Aug 1, 2025
- Replace backupConfigFile parameter with liveBackupCopyCLIOptions structure
- Add support for --table, --subsite-id, and --wpcli-command CLI options
- Implement parseLiveBackupCopyCLIOptions utility with validation
- Add comprehensive test coverage for new CLI options functionality
- Update dev-env-sync-sql and export-sql commands to use new options structure

This refactoring provides more flexible and type-safe handling of live backup copy configurations while maintaining backward compatibility.
@luismulinari luismulinari requested a review from Copilot August 5, 2025 14:49

This comment was marked as outdated.

@rinatkhaziev rinatkhaziev added [Status] Needs Docs The feature or update requires an update to our public VIP Documentation DO NOT MERGE labels Aug 13, 2025
@rinatkhaziev
Copy link
Copy Markdown
Contributor

I'm only putting DO NOT MERGE because the docs. I'm going to publish the pre-release based on this branch.

@sjinks sjinks requested a review from Copilot August 14, 2025 09:12
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR adds live backup copy functionality to enable downloading or syncing partial database dumps from production environments. Instead of downloading full database exports, users can now specify custom configurations to sync/download only specific tables, subsites, or configs generated by custom WP-CLI commands.

Key changes:

  • Added --config-file option for JSON configuration files that define partial backup parameters
  • Implemented live backup copy integration with generateLiveBackupCopy() method
  • Enhanced polling utilities with timeout support and error handling

Reviewed Changes

Copilot reviewed 8 out of 14 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/lib/utils.ts Enhanced pollUntil function with timeout support and return value handling
src/lib/live-backup-copy.ts New module implementing live backup copy functionality with GraphQL mutations
src/lib/http/download-file.ts New utility for downloading files with progress tracking
src/lib/backup-storage-availability/backup-storage-availability.ts Refactored to accept archive size as parameter instead of constructor injection
src/commands/export-sql.ts Enhanced to support live backup copy options and refactored download logic
src/commands/dev-env-sync-sql.ts Updated to support live backup copy options
tests/lib/backup-storage-availability/backup-storage-availability.ts Updated tests to match new constructor signature
tests/commands/export-sql.ts Added comprehensive tests for live backup copy functionality

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

let done = false;
while ( ! done ) {
isDone: ( v: T ) => boolean,
timeoutMs: number = 6 * 60 * 60 * 1000 // Default to 6 hours
Copy link

Copilot AI Aug 14, 2025

Choose a reason for hiding this comment

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

[nitpick] The 6-hour default timeout seems excessive and could lead to operations hanging for too long. Consider using a more reasonable default like 30 minutes (30 * 60 * 1000) or making this configurable based on the operation type.

Suggested change
timeoutMs: number = 6 * 60 * 60 * 1000 // Default to 6 hours
timeoutMs: number = 30 * 60 * 1000 // Default to 30 minutes

Copilot uses AI. Check for mistakes.
export interface DBLiveCopyConfig {
tool?: SQLDumpTool;
type: BackupLiveCopyType;
tables?: Record< string, Record< string, string | boolean > >;
Copy link

Copilot AI Aug 14, 2025

Choose a reason for hiding this comment

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

The type definition for tables is overly permissive. Consider defining a more specific interface for table options to improve type safety and API clarity, such as Record<string, TableOptions> where TableOptions has specific properties like where?: string, skipAddDropTable?: boolean, etc.

Suggested change
tables?: Record< string, Record< string, string | boolean > >;
export interface TableOptions {
where?: string;
skipAddDropTable?: boolean;
// Add other specific options as needed
}
export interface DBLiveCopyConfig {
tool?: SQLDumpTool;
type: BackupLiveCopyType;
tables?: Record<string, TableOptions>;

Copilot uses AI. Check for mistakes.
appId,
environmentId,
copyId,
timeoutInSeconds = 2 * 60 * 60, // 2 hours default timeout
Copy link

Copilot AI Aug 14, 2025

Choose a reason for hiding this comment

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

[nitpick] The 2-hour default timeout for getting download URL is very long and inconsistent with the 6-hour polling timeout. Consider using consistent timeout values or making them configurable based on operation complexity.

Suggested change
timeoutInSeconds = 2 * 60 * 60, // 2 hours default timeout
timeoutInSeconds = DEFAULT_POLLING_TIMEOUT_SECONDS, // 6 hours default timeout (consistent with polling)

Copilot uses AI. Check for mistakes.
result.data?.generateLiveBackupCopyDownloadURL?.url
? result.data?.generateLiveBackupCopyDownloadURL?.url
: 'Unknown error'
}`
Copy link

Copilot AI Aug 14, 2025

Choose a reason for hiding this comment

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

The error message construction is confusing - it checks for url existence but then uses url in the error message when it should be describing the actual failure. Consider using a more descriptive error message like 'Failed to generate download URL: operation was not successful' or similar.

Suggested change
}`
`Failed to generate download URL: operation was not successful. ` +
`Missing or invalid fields: ` +
`success=${result.data?.generateLiveBackupCopyDownloadURL?.success}, ` +
`url=${result.data?.generateLiveBackupCopyDownloadURL?.url}, ` +
`size=${result.data?.generateLiveBackupCopyDownloadURL?.size}`

Copilot uses AI. Check for mistakes.
}

throw new Error(
`Failed to write file to disk: ${ error instanceof Error ? error.message : 'Unknown error' }`
Copy link

Copilot AI Aug 14, 2025

Choose a reason for hiding this comment

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

Using console.error directly in a library function is not ideal as it bypasses any logging configuration the application might have. Consider using a proper logging mechanism or allowing the caller to handle this error appropriately.

Suggested change
`Failed to write file to disk: ${ error instanceof Error ? error.message : 'Unknown error' }`
let unlinkErrorMessage = '';
try {
await fs.promises.unlink( destinationPath );
} catch ( unlinkErr ) {
unlinkErrorMessage = `; Additionally, failed to delete partial file ${ destinationPath }: ${ unlinkErr instanceof Error ? unlinkErr.message : String(unlinkErr) }`;
}
throw new Error(
`Failed to write file to disk: ${ error instanceof Error ? error.message : 'Unknown error' }${ unlinkErrorMessage }`

Copilot uses AI. Check for mistakes.

if ( this.liveBackupCopyCLIOptions?.subsiteIds ) {
type = BackupLiveCopyType.SUBSITE_IDS;
subsiteIds = this.liveBackupCopyCLIOptions?.subsiteIds.map( id => Number( id ) );
Copy link

Copilot AI Aug 14, 2025

Choose a reason for hiding this comment

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

Calling Number() on values that are already numbers (as defined in the interface) is unnecessary and could potentially cause issues. The subsiteIds property is already typed as number[], so this conversion is redundant.

Suggested change
subsiteIds = this.liveBackupCopyCLIOptions?.subsiteIds.map( id => Number( id ) );
subsiteIds = this.liveBackupCopyCLIOptions?.subsiteIds;

Copilot uses AI. Check for mistakes.
if ( ! fs.existsSync( configFile ) ) {
throw new Error( `Configuration file not found: ${ configFile }` );
}

Copy link

Copilot AI Aug 14, 2025

Choose a reason for hiding this comment

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

Using fs.existsSync() followed by fs.readFileSync() creates a race condition where the file could be deleted between the existence check and the read operation. Consider using a try-catch block around fs.readFileSync() instead to handle the file not existing.

Suggested change

Copilot uses AI. Check for mistakes.
@sonarqubecloud
Copy link
Copy Markdown

@rinatkhaziev rinatkhaziev merged commit 9696680 into trunk Aug 27, 2025
19 checks passed
@rinatkhaziev rinatkhaziev deleted the custom-dev-env-sync branch August 27, 2025 19:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Status] Needs Docs The feature or update requires an update to our public VIP Documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants