From 0b3bae50bcfee5fd4c140e2980f0b876d63d9380 Mon Sep 17 00:00:00 2001 From: Roberto Aranda Date: Wed, 28 May 2025 17:53:05 +0200 Subject: [PATCH 01/10] Follow symlinks in Preview sites when archiving directories --- patches/archiver+6.0.2.patch | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 patches/archiver+6.0.2.patch diff --git a/patches/archiver+6.0.2.patch b/patches/archiver+6.0.2.patch new file mode 100644 index 0000000000..9e10657d31 --- /dev/null +++ b/patches/archiver+6.0.2.patch @@ -0,0 +1,14 @@ +diff --git a/node_modules/archiver/lib/core.js b/node_modules/archiver/lib/core.js +index 7c0a74d..5ac4e3a 100644 +--- a/node_modules/archiver/lib/core.js ++++ b/node_modules/archiver/lib/core.js +@@ -631,7 +631,8 @@ Archiver.prototype.directory = function(dirpath, destpath, data) { + + var globOptions = { + stat: true, +- dot: true ++ dot: true, ++ follow: true, + }; + + function onGlobEnd() { From a6cc70ce9a756b2bcfadcd6640dd5bdd26cd5352 Mon Sep 17 00:00:00 2001 From: Roberto Aranda Date: Wed, 28 May 2025 18:04:19 +0200 Subject: [PATCH 02/10] Add release note --- RELEASE-NOTES.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 35b5a83942..22a3a940a6 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,6 +1,7 @@ Unreleased ========== * Miscellaneous security improvements #1411 +* Follow symlinks when creating preview sites #1447 1.5.2 ===== From f10e4c852065b2d7e6147e5abb167ee8689f8000 Mon Sep 17 00:00:00 2001 From: Roberto Aranda Date: Thu, 29 May 2025 10:54:31 +0200 Subject: [PATCH 03/10] Use an option in the archiver instead of harcoding the value --- cli/lib/archive.ts | 1 + patches/@types+archiver+6.0.2.patch | 12 ++++++++++++ .../{archiver+6.0.2.patch => archiver+6.0.1.patch} | 12 ++++++++++-- 3 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 patches/@types+archiver+6.0.2.patch rename patches/{archiver+6.0.2.patch => archiver+6.0.1.patch} (51%) diff --git a/cli/lib/archive.ts b/cli/lib/archive.ts index 31089ec91a..1b0548eec5 100644 --- a/cli/lib/archive.ts +++ b/cli/lib/archive.ts @@ -14,6 +14,7 @@ export async function createArchive( const output = fs.createWriteStream( archivePath ); const archive = archiver( 'zip', { zlib: { level: ZIP_COMPRESSION_LEVEL }, + followSymlinks: true, } ); output.on( 'close', () => { diff --git a/patches/@types+archiver+6.0.2.patch b/patches/@types+archiver+6.0.2.patch new file mode 100644 index 0000000000..945e1d0956 --- /dev/null +++ b/patches/@types+archiver+6.0.2.patch @@ -0,0 +1,12 @@ +diff --git a/node_modules/@types/archiver/index.d.ts b/node_modules/@types/archiver/index.d.ts +index 00829ac..495887b 100644 +--- a/node_modules/@types/archiver/index.d.ts ++++ b/node_modules/@types/archiver/index.d.ts +@@ -106,6 +106,7 @@ declare namespace archiver { + + interface CoreOptions { + statConcurrency?: number | undefined; ++ followSymlinks?: true + } + + interface TransformOptions { diff --git a/patches/archiver+6.0.2.patch b/patches/archiver+6.0.1.patch similarity index 51% rename from patches/archiver+6.0.2.patch rename to patches/archiver+6.0.1.patch index 9e10657d31..16862adc83 100644 --- a/patches/archiver+6.0.2.patch +++ b/patches/archiver+6.0.1.patch @@ -1,5 +1,5 @@ diff --git a/node_modules/archiver/lib/core.js b/node_modules/archiver/lib/core.js -index 7c0a74d..5ac4e3a 100644 +index 7c0a74d..c70117b 100644 --- a/node_modules/archiver/lib/core.js +++ b/node_modules/archiver/lib/core.js @@ -631,7 +631,8 @@ Archiver.prototype.directory = function(dirpath, destpath, data) { @@ -8,7 +8,15 @@ index 7c0a74d..5ac4e3a 100644 stat: true, - dot: true + dot: true, -+ follow: true, ++ follow: this.options.followSymlinks }; function onGlobEnd() { +@@ -922,6 +923,7 @@ module.exports = Archiver; + * @global + * @property {Number} [statConcurrency=4] Sets the number of workers used to + * process the internal fs stat queue. ++ * @property {Boolean} [followSymLinks=false] Sets whether to follow symlinks. + */ + + /** From be3b8a63327eb1a2c4dfe7bfb77a646fd7393728 Mon Sep 17 00:00:00 2001 From: Roberto Aranda Date: Thu, 29 May 2025 11:08:49 +0200 Subject: [PATCH 04/10] Fix error in type declaration --- patches/@types+archiver+6.0.2.patch | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/patches/@types+archiver+6.0.2.patch b/patches/@types+archiver+6.0.2.patch index 945e1d0956..5a8f12dbd7 100644 --- a/patches/@types+archiver+6.0.2.patch +++ b/patches/@types+archiver+6.0.2.patch @@ -1,12 +1,12 @@ diff --git a/node_modules/@types/archiver/index.d.ts b/node_modules/@types/archiver/index.d.ts -index 00829ac..495887b 100644 +index 00829ac..fab29c9 100644 --- a/node_modules/@types/archiver/index.d.ts +++ b/node_modules/@types/archiver/index.d.ts @@ -106,6 +106,7 @@ declare namespace archiver { interface CoreOptions { statConcurrency?: number | undefined; -+ followSymlinks?: true ++ followSymlinks?: boolean } interface TransformOptions { From 51437f9fe9e2a21ae31405e4c8b5afcb4d080b37 Mon Sep 17 00:00:00 2001 From: Roberto Aranda Date: Thu, 29 May 2025 11:11:31 +0200 Subject: [PATCH 05/10] Add new option to tests --- cli/lib/tests/archive.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cli/lib/tests/archive.test.ts b/cli/lib/tests/archive.test.ts index bb59ac32d0..aeaac1cfd5 100644 --- a/cli/lib/tests/archive.test.ts +++ b/cli/lib/tests/archive.test.ts @@ -48,7 +48,10 @@ describe( 'Archive Module', () => { const result = await createArchive( mockSiteFolder, mockArchivePath ); expect( fs.createWriteStream ).toHaveBeenCalledWith( mockArchivePath ); - expect( archiver ).toHaveBeenCalledWith( 'zip', { zlib: { level: 9 } } ); + expect( archiver ).toHaveBeenCalledWith( 'zip', { + followSymlinks: true, + zlib: { level: 9 }, + } ); expect( mockArchiver.pipe ).toHaveBeenCalledWith( mockWriteStream ); expect( path.join ).toHaveBeenCalledWith( mockSiteFolder, 'wp-content' ); expect( mockArchiver.directory ).toHaveBeenCalledWith( From bfc3fbd2df7205c4c258a2d03d669f004b53a80d Mon Sep 17 00:00:00 2001 From: Roberto Aranda Date: Wed, 28 May 2025 18:58:17 +0200 Subject: [PATCH 06/10] Follow symlink directories for Jetpack backup --- .../export/exporters/default-exporter.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/lib/import-export/export/exporters/default-exporter.ts b/src/lib/import-export/export/exporters/default-exporter.ts index c248f1dcc0..a112d0f699 100644 --- a/src/lib/import-export/export/exporters/default-exporter.ts +++ b/src/lib/import-export/export/exporters/default-exporter.ts @@ -162,11 +162,15 @@ export class DefaultExporter extends EventEmitter implements Exporter { ).filter( ( category ) => this.options.includes[ category ] ); this.emit( ExportEvents.WP_CONTENT_EXPORT_START ); for ( const category of categories ) { - for ( const file of this.backup.wpContent[ category ] ) { - const relativePath = path.relative( this.options.site.path, file ); - this.archive.file( file, { name: relativePath } ); - this.emit( ExportEvents.WP_CONTENT_EXPORT_PROGRESS, { file: relativePath } ); - } + const absolutePath = path.join( this.options.site.path, 'wp-content', category ); + const archivePath = path.relative( this.options.site.path, absolutePath ); + this.archive.directory( absolutePath, archivePath, ( entry ) => { + if ( entry.name.includes( '.git' ) || entry.name.includes( 'node_modules' ) ) { + return false; + } + return entry; + } ); + this.emit( ExportEvents.WP_CONTENT_EXPORT_PROGRESS, { directory: absolutePath } ); } this.emit( ExportEvents.WP_CONTENT_EXPORT_COMPLETE, { uploads: this.backup.wpContent.uploads.length, From 7ea8ef6cfb26bef395357d9b899490428a509ccd Mon Sep 17 00:00:00 2001 From: Roberto Aranda Date: Thu, 29 May 2025 11:16:26 +0200 Subject: [PATCH 07/10] Use explicit option to follow symlinks --- src/constants.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/constants.ts b/src/constants.ts index bcb32686d0..2d8871a59a 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -37,10 +37,12 @@ export const ACCEPTED_IMPORT_FILE_TYPES = [ '.zip', '.gz', '.gzip', '.tar', '.ta export const ARCHIVER_OPTIONS = { zip: { zlib: { level: 9 }, + followSymlinks: true, }, tar: { gzip: true, gzipOptions: { level: 9 }, + followSymlinks: true, }, }; From db3060cb639a24e1d0a3fa88ed7d60c8831efc2e Mon Sep 17 00:00:00 2001 From: Roberto Aranda Date: Thu, 29 May 2025 12:40:52 +0200 Subject: [PATCH 08/10] Translate muPlugins folder, fix tests --- .../export/exporters/default-exporter.ts | 3 +- .../export/exporters/default-exporter.test.ts | 69 ++++++++++--------- 2 files changed, 37 insertions(+), 35 deletions(-) diff --git a/src/lib/import-export/export/exporters/default-exporter.ts b/src/lib/import-export/export/exporters/default-exporter.ts index a112d0f699..74e6c8edd4 100644 --- a/src/lib/import-export/export/exporters/default-exporter.ts +++ b/src/lib/import-export/export/exporters/default-exporter.ts @@ -162,7 +162,8 @@ export class DefaultExporter extends EventEmitter implements Exporter { ).filter( ( category ) => this.options.includes[ category ] ); this.emit( ExportEvents.WP_CONTENT_EXPORT_START ); for ( const category of categories ) { - const absolutePath = path.join( this.options.site.path, 'wp-content', category ); + const folderName = category === 'muPlugins' ? 'mu-plugins' : category; + const absolutePath = path.join( this.options.site.path, 'wp-content', folderName ); const archivePath = path.relative( this.options.site.path, absolutePath ); this.archive.directory( absolutePath, archivePath, ( entry ) => { if ( entry.name.includes( '.git' ) || entry.name.includes( 'node_modules' ) ) { diff --git a/src/lib/import-export/tests/export/exporters/default-exporter.test.ts b/src/lib/import-export/tests/export/exporters/default-exporter.test.ts index 8227061d93..b75d382d4a 100644 --- a/src/lib/import-export/tests/export/exporters/default-exporter.test.ts +++ b/src/lib/import-export/tests/export/exporters/default-exporter.test.ts @@ -217,14 +217,18 @@ platformTestSuite( 'DefaultExporter', ( { normalize } ) => { it( 'should create a tar.gz archive', async () => { await exporter.export(); - expect( archiver ).toHaveBeenCalledWith( 'tar', { gzip: true, gzipOptions: { level: 9 } } ); + expect( archiver ).toHaveBeenCalledWith( 'tar', { + followSymlinks: true, + gzip: true, + gzipOptions: { level: 9 }, + } ); } ); it( 'should create a zip archive when the backup file ends with .zip', async () => { mockOptions.backupFile = '/path/to/backup.zip'; await exporter.export(); - expect( archiver ).toHaveBeenCalledWith( 'zip', { zlib: { level: 9 } } ); + expect( archiver ).toHaveBeenCalledWith( 'zip', { followSymlinks: true, zlib: { level: 9 } } ); } ); it( 'should add wp-config.php to the archive', async () => { @@ -262,7 +266,7 @@ platformTestSuite( 'DefaultExporter', ( { normalize } ) => { ); } ); - it( 'should add wp-content files to the archive', async () => { + it( 'should add wp-content directories to the archive', async () => { const options = { ...mockOptions, includes: { @@ -283,25 +287,29 @@ platformTestSuite( 'DefaultExporter', ( { normalize } ) => { normalize( '/path/to/site/wp-config.php' ), { name: 'wp-config.php' } ); - expect( mockArchiver.file ).toHaveBeenNthCalledWith( + expect( mockArchiver.directory ).toHaveBeenNthCalledWith( + 1, + normalize( '/path/to/site/wp-content/uploads' ), + normalize( 'wp-content/uploads' ), + expect.any( Function ) + ); + expect( mockArchiver.directory ).toHaveBeenNthCalledWith( 2, - normalize( '/path/to/site/wp-content/uploads/file1.jpg' ), - { name: normalize( 'wp-content/uploads/file1.jpg' ) } + normalize( '/path/to/site/wp-content/plugins' ), + normalize( 'wp-content/plugins' ), + expect.any( Function ) ); - expect( mockArchiver.file ).toHaveBeenNthCalledWith( + expect( mockArchiver.directory ).toHaveBeenNthCalledWith( 3, - normalize( '/path/to/site/wp-content/plugins/plugin1/plugin1.php' ), - { name: normalize( 'wp-content/plugins/plugin1/plugin1.php' ) } + normalize( '/path/to/site/wp-content/themes' ), + normalize( 'wp-content/themes' ), + expect.any( Function ) ); - expect( mockArchiver.file ).toHaveBeenNthCalledWith( + expect( mockArchiver.directory ).toHaveBeenNthCalledWith( 4, - normalize( '/path/to/site/wp-content/themes/theme1/index.php' ), - { name: normalize( 'wp-content/themes/theme1/index.php' ) } - ); - expect( mockArchiver.file ).toHaveBeenNthCalledWith( - 5, - normalize( '/path/to/site/wp-content/fonts/custom-font.woff2' ), - { name: normalize( 'wp-content/fonts/custom-font.woff2' ) } + normalize( '/path/to/site/wp-content/fonts' ), + normalize( 'wp-content/fonts' ), + expect.any( Function ) ); } ); @@ -326,19 +334,11 @@ platformTestSuite( 'DefaultExporter', ( { normalize } ) => { normalize( '/path/to/site/wp-config.php' ), { name: 'wp-config.php' } ); - expect( mockArchiver.file ).toHaveBeenNthCalledWith( - 2, - normalize( '/path/to/site/wp-content/mu-plugins/custom-mu-plugin.php' ), - { name: normalize( 'wp-content/mu-plugins/custom-mu-plugin.php' ) } - ); - - expect( mockArchiver.file ).not.toHaveBeenCalledWith( - normalize( '/path/to/site/wp-content/mu-plugins/sqlite-database-integration/load.php' ), - { name: normalize( 'wp-content/mu-plugins/sqlite-database-integration/load.php' ) } - ); - expect( mockArchiver.file ).not.toHaveBeenCalledWith( - normalize( '/path/to/site/wp-content/mu-plugins/0-allowed-redirect-hosts.php' ), - { name: normalize( 'wp-content/mu-plugins/0-allowed-redirect-hosts.php' ) } + expect( mockArchiver.directory ).toHaveBeenNthCalledWith( + 1, + normalize( '/path/to/site/wp-content/mu-plugins' ), + normalize( 'wp-content/mu-plugins' ), + expect.any( Function ) ); } ); @@ -486,10 +486,11 @@ platformTestSuite( 'DefaultExporter', ( { normalize } ) => { normalize( '/path/to/site/wp-config.php' ), { name: 'wp-config.php' } ); - expect( mockArchiver.file ).toHaveBeenNthCalledWith( - 2, - normalize( '/path/to/site/wp-content/fonts/custom-font.woff2' ), - { name: normalize( 'wp-content/fonts/custom-font.woff2' ) } + expect( mockArchiver.directory ).toHaveBeenNthCalledWith( + 1, + normalize( '/path/to/site/wp-content/fonts' ), + normalize( 'wp-content/fonts' ), + expect.any( Function ) ); } ); } ); From 51c198366c39c9200b95fe659f42c4adb0d05af7 Mon Sep 17 00:00:00 2001 From: Roberto Aranda Date: Thu, 29 May 2025 17:53:55 +0200 Subject: [PATCH 09/10] Eclude pathsToExclude from the archiving directories --- .../import-export/export/exporters/default-exporter.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/lib/import-export/export/exporters/default-exporter.ts b/src/lib/import-export/export/exporters/default-exporter.ts index 74e6c8edd4..4b3cef4151 100644 --- a/src/lib/import-export/export/exporters/default-exporter.ts +++ b/src/lib/import-export/export/exporters/default-exporter.ts @@ -166,7 +166,15 @@ export class DefaultExporter extends EventEmitter implements Exporter { const absolutePath = path.join( this.options.site.path, 'wp-content', folderName ); const archivePath = path.relative( this.options.site.path, absolutePath ); this.archive.directory( absolutePath, archivePath, ( entry ) => { - if ( entry.name.includes( '.git' ) || entry.name.includes( 'node_modules' ) ) { + const fullArchivePath = path.join( archivePath, entry.name ); + const isExcluded = this.pathsToExclude.some( ( pathToExclude ) => + fullArchivePath.startsWith( path.normalize( pathToExclude ) ) + ); + if ( + isExcluded || + entry.name.includes( '.git' ) || + entry.name.includes( 'node_modules' ) + ) { return false; } return entry; From fde3b60777eb0d4be6ab99db54aa7b4403c72d61 Mon Sep 17 00:00:00 2001 From: Roberto Aranda Date: Fri, 30 May 2025 10:20:53 +0200 Subject: [PATCH 10/10] Update release notes --- RELEASE-NOTES.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 22a3a940a6..ed1e29a2f9 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,7 +1,8 @@ Unreleased ========== * Miscellaneous security improvements #1411 -* Follow symlinks when creating preview sites #1447 +* Included symlinked directories and files when creating preview sites #1447 +* Included symlinked directories and files when exporting and pushing sites #1448 1.5.2 =====