Skip to content
Closed
56 changes: 55 additions & 1 deletion cli/lib/archive.ts
Comment thread
epeicher marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import fs from 'fs';
import fsPromises from 'fs/promises';
import path from 'path';
import { __ } from '@wordpress/i18n';
import archiver, { EntryData } from 'archiver';
Expand All @@ -10,6 +11,9 @@ export async function createArchive(
siteFolder: string,
archivePath: string
): Promise< archiver.Archiver > {
const wpContentFolder = path.join( siteFolder, 'wp-content' );
const symlinks = await getSymlinks( wpContentFolder );

return new Promise( ( resolve, reject ) => {
const output = fs.createWriteStream( archivePath );
const archive = archiver( 'zip', {
Expand All @@ -28,13 +32,32 @@ export async function createArchive(
path.join( siteFolder, 'wp-content' ),
'wp-content',
( entry: EntryData ) => {
if ( entry.name.includes( '.git' ) || entry.name.includes( 'node_modules' ) ) {
if ( shouldExcludeEntry( entry.name ) ) {
return false;
}
if ( entry.stats?.isSymbolicLink() ) {
return false;
}

return entry;
}
);

for ( const symlink of symlinks ) {
const { symbolicPath, realPath } = symlink;
const archivePath = path.relative( siteFolder, symbolicPath );
if ( symlink.isDirectory ) {
archive.directory( realPath, archivePath, ( entry: EntryData ) => {
if ( shouldExcludeEntry( entry.name ) ) {
return false;
}
return entry;
Comment thread
epeicher marked this conversation as resolved.
Outdated
} );
} else {
archive.file( realPath, { name: archivePath } );
}
}

const wpConfigPath = path.join( siteFolder, 'wp-config.php' );
if ( fs.existsSync( wpConfigPath ) ) {
archive.file( wpConfigPath, { name: 'wp-config.php' } );
Expand All @@ -55,3 +78,34 @@ export async function cleanup( archivePath: string ): Promise< void > {
}, 0 );
} );
}

async function getSymlinks(
dir: string
): Promise< { isDirectory: boolean; symbolicPath: string; realPath: string }[] > {
const files = await fs.promises.readdir( dir );
const results = await Promise.all(
files.map( async ( file ) => {
const filePath = path.join( dir, file );
const stats = await fs.promises.lstat( filePath );

if ( stats.isSymbolicLink() ) {
const realPath = await fsPromises.realpath( filePath );
const realPathStats = await fsPromises.stat( realPath );
return [ { isDirectory: realPathStats.isDirectory(), symbolicPath: filePath, realPath } ];
}

if ( stats.isDirectory() ) {
return await getSymlinks( filePath );
}
if ( stats.isFile() ) {
return [];
}
return [];
} )
);
return results.flat();
}

function shouldExcludeEntry( entryName: string ): boolean {
Comment thread
epeicher marked this conversation as resolved.
Outdated
return entryName.includes( '.git' ) || entryName.includes( 'node_modules' );
}
Loading