Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 26 additions & 11 deletions apps/studio/e2e/sites.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,30 @@ async function completeOnboardingWithParams( customSiteName?: string, customFold
};
}

type PersistedSite = { id: string; path: string };

async function getPersistedSite( siteName: string ): Promise< PersistedSite | undefined > {
const cliConfig = await fs
.readJson( path.join( session.cliConfigPath, 'cli.json' ) )
.catch( () => ( { sites: [] } ) );
return cliConfig.sites.find( ( s: { name: string } ) => s.name === siteName );
}

/**
* The sidebar can show a copied site (optimistic placeholder, then the IPC
* result) slightly before the CLI's cli.json write is observable, so poll the
* on-disk config instead of sampling it once.
*/
async function waitForPersistedSite( siteName: string ): Promise< PersistedSite > {
await expect
.poll( async () => Boolean( await getPersistedSite( siteName ) ), {
message: `site "${ siteName }" was never persisted to cli.json`,
timeout: 30_000,
} )
.toBe( true );
return ( await getPersistedSite( siteName ) ) as PersistedSite;
}

/**
* Drive the "Add site" modal through the create-site flow while selecting a
* pre-existing local folder (returned by the mocked folder dialog via the
Expand Down Expand Up @@ -345,11 +369,7 @@ test.describe( 'Sites', () => {
const copiedSiteContent = new SiteContent( session.mainWindow, expectedCopyName );
await expect( copiedSiteContent.runningButton ).toBeAttached( { timeout: 120_000 } );

const cliConfig = await fs.readJson( path.join( session.cliConfigPath, 'cli.json' ) );
const copiedSite = cliConfig.sites.find(
( s: { name: string } ) => s.name === expectedCopyName
);
expect( copiedSite ).toBeDefined();
const copiedSite = await waitForPersistedSite( expectedCopyName );
expect( await pathExists( path.join( copiedSite.path, 'wp-config.php' ) ) ).toBe( true );
} );
} );
Expand Down Expand Up @@ -405,12 +425,7 @@ test.describe( 'Sites without cleanup in-between', () => {
const copiedSiteContent = new SiteContent( session.mainWindow, expectedCopyName );
await expect( copiedSiteContent.runningButton ).toBeAttached( { timeout: 120_000 } );

const updatedCliConfig = await fs.readJson( cliConfigFile );
const copiedSite = updatedCliConfig.sites.find(
( s: { name: string } ) => s.name === expectedCopyName
);
expect( copiedSite ).toBeDefined();

const copiedSite = await waitForPersistedSite( expectedCopyName );
expect( await pathExists( path.join( copiedSite.path, 'wp-config.php' ) ) ).toBe( true );

const copiedThumbnailPath = path.join( thumbnailsDir, `${ copiedSite.id }.png` );
Expand Down
32 changes: 21 additions & 11 deletions packages/common/lib/fs-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,27 +98,37 @@ export async function pathExists( path: string ): Promise< boolean > {
}
}

// The source may be mutated while copying (e.g. duplicating a running site whose
// SQLite journal and cache files come and go), so entries that disappear between
// enumeration and copy are skipped rather than failing the whole copy. A missing
// top-level source still throws.
export async function recursiveCopyDirectory(
source: string,
destination: string
): Promise< void > {
await fsPromises.mkdir( destination, { recursive: true } );

const entries = await fsPromises.readdir( source, { withFileTypes: true } );

await fsPromises.mkdir( destination, { recursive: true } );

await Promise.all(
entries.map( ( entry ) => {
entries.map( async ( entry ) => {
const sourcePath = path.join( source, entry.name );
const destinationPath = path.join( destination, entry.name );

if ( entry.isDirectory() ) {
return recursiveCopyDirectory( sourcePath, destinationPath );
}
if ( entry.isFile() ) {
return fsPromises.cp( sourcePath, destinationPath, {
mode: fs.constants.COPYFILE_FICLONE,
preserveTimestamps: true,
} );
try {
if ( entry.isDirectory() ) {
await recursiveCopyDirectory( sourcePath, destinationPath );
} else if ( entry.isFile() ) {
await fsPromises.cp( sourcePath, destinationPath, {
mode: fs.constants.COPYFILE_FICLONE,
preserveTimestamps: true,
} );
}
} catch ( error ) {
if ( isErrnoException( error ) && error.code === 'ENOENT' ) {
return;
}
throw error;
}
} )
);
Expand Down
Loading