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
16 changes: 10 additions & 6 deletions src/lib/symlink-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,10 @@ export class SymlinkManager {
* @param filename
*/
private async addSymlink( filename: string ) {
const fullPath = path.join( this.projectPath, filename );
let target: string;
const fullPath = path.resolve( this.projectPath, filename );
let hostTarget: string;
try {
target = await fs.realpath( fullPath );
hostTarget = await fs.realpath( fullPath );
} catch ( err ) {
if ( isErrnoException( err ) && err.code === 'ENOENT' ) {
console.error( `Symlink target does not exist: ${ fullPath }` );
Expand All @@ -152,18 +152,19 @@ export class SymlinkManager {
return;
}

const vfsPath = path.posix.join( this.documentRoot, filename );
const vfsPath = path.posix.resolve( this.documentRoot, filename );

// Double check to ensure the symlink exists
if ( ! this.php.fileExists( vfsPath ) ) {
return;
}

// Get the realpath of the symlink within the PHP runtime.
const vfsTarget = this.php.readlink( vfsPath );
const vfsTarget = path.posix.resolve( path.dirname( vfsPath ), this.php.readlink( vfsPath ) );

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.

php.realpath does not accomplish the same thing (confirmed in my testing). I believe it just returns false because the target path does not yet exist at this point.

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.

To expand on this: realpath works as a more compact way of expressing this logic if the target path is already within the document root. If this is not the case, realpath returns false because the target does not yet exist (see PHP docs).

The SymlinkManager::isTargetInsideDocumentRoot added in this PR stems from the realization that if the target path is already within the document root, there's no need for SymlinkManager to intervene. This is a performance optimization that should help speed up server starts for sites with many symlinks.

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.

Following up about SymlinkManager::isTargetInsideDocumentRoot: I realized that SymlinkManager::mountTarget already has this performance optimization in that it checks if the file or directory already exists in the Playground FS before attempting to mount it. Hence, I removed this method.


this.symlinks.set( filename, vfsTarget );

await this.mountTarget( vfsTarget, target );
await this.mountTarget( vfsTarget, hostTarget );
}

/**
Expand Down Expand Up @@ -195,6 +196,9 @@ export class SymlinkManager {
vfsTarget,
createNodeFsMountHandler( hostTarget )
);
// For some reason, we must access the target directory after mounting it, otherwise the
// mount will not work.
this.php.listFiles( vfsTarget );

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.

@bgrgicak @adamziel @ashfame, I'm not sure why this is needed, but the Yoast SEO test case (see PR description) didn't work without it in my testing. Any idea why that's the case..?

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.

It continues to work for me when I comment out this line. Maybe it depends on the underlying filesystem and we use different ones?

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.

To quickly double-check: did you restart the entire app after commenting out this line?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I was able to mount and start a site with wp-data-layer-app-pages plugin without listing the files.

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.

Yeah, the wp-data-layer-app-pages test case works without this line for me, too. It's only the Yoast test case that was troublesome

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.

We may find that this line is unnecessary after further testing, but I have a hard time seeing how it would break anything, so I'll land the PR with this included and we can keep the discussion going after merging.

}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ describe( 'SymlinkManager', () => {
isDir: jest.fn(),
unlink: jest.fn(),
rmdir: jest.fn(),
listFiles: jest.fn(),
requestHandler: {
documentRoot: mockDocumentRoot,
},
Expand Down