-
Notifications
You must be signed in to change notification settings - Fork 34
Studio: Follow symlinks when exporting site #1427
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: trunk
Are you sure you want to change the base?
Studio: Follow symlinks when exporting site #1427
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code LGTM.
Added a minor comment, but it shouldn't be a blocker.
Testing instructions worked as described. Something I found during tests that might be an issue: I tried to use the export site functionality but noticed the symlinked plugin isn't included. Creating a preview site works as expected (the symlinked plugin is visible and functional).
I'm not sure if this is the expected behavior, if it is consider the PR approved.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for tackling this! This PR works as expected 👍
I think there's an opportunity to simplify the logic for iterating over the wp-content
directory (per my inline comment), but that's only a suggestion. Feel free to merge the current changes if you prefer, @epeicher.
@bcotrim, thanks for your review!
Well spotted! The export functionality is slightly different than creating the preview archive, it uses the default-exporter.ts. So I think it will be better to tackle that as part of a follow-up task. Also, please note that I've changed the approach for this PR as suggested here, so the code is now slightly different. Happy to have another round of review. |
@epeicher while re-reviewing the changes, I tested a new scenario with a broken symlink in the plugins folder. Is this the expected behavior? |
@bcotrim that is a very good point, to cover the errors and edge cases that the users will face for sure. The current implementation assumes the links point to correct targets, but it can be updated, so when the target does not exist, symlinks can be ignored. I think this is better than the production behavior, which adds the symlinks (which could be broken), what do you think? |
That sounds good to me! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for considering my suggestion, @epeicher. I realized that I got confused about the behavior of readdir
when passing just recursive
compared to when withFileTypes
is also passed. I left a suggestion for how we could simplify this even further by using only the recursive
option.
cli/lib/archive.ts
Outdated
const wpContentFolder = path.join( siteFolder, 'wp-content' ); | ||
const directoryContents = await fsPromises.readdir( wpContentFolder, { | ||
recursive: true, | ||
withFileTypes: true, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
withFileTypes: true, |
Sorry for getting this wrong in my previous comment, but I see now that readdir
only resolves symlinks if recursive: true
and withFileTypes: false
.
I suggest simplifying the logic by removing withFileTypes
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, that's a very key point. I tested that to check if readdir
followed symlinks, but didn't think about changing withFileTypes
to false
. Let me do a quick iteration as I think it will simplify the code a lot.
cli/lib/archive.ts
Outdated
for ( const dirEnt of directoryContents ) { | ||
const filePath = path.join( dirEnt.parentPath, dirEnt.name ); | ||
if ( shouldExcludeEntry( filePath ) ) { | ||
continue; | ||
} | ||
); | ||
|
||
const archivePath = path.relative( siteFolder, filePath ); | ||
if ( dirEnt.isSymbolicLink() ) { | ||
const realPath = fs.realpathSync( filePath ); | ||
const stat = fs.statSync( realPath ); | ||
const isDirectory = stat.isDirectory(); | ||
if ( isDirectory ) { | ||
archive.directory( realPath, archivePath, ( entry: EntryData ) => { | ||
if ( shouldExcludeEntry( entry.name ) ) { | ||
return false; | ||
} | ||
return entry; | ||
} ); | ||
} else { | ||
archive.file( realPath, { name: archivePath } ); | ||
} | ||
} else if ( dirEnt.isFile() ) { | ||
archive.file( filePath, { name: archivePath } ); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for ( const entryPath of directoryContents ) {
if ( entryPath.includes( '.git' ) || entryPath.includes( 'node_modules' ) ) {
continue;
}
const absolutePath = path.join( wpContentFolder, entryPath );
const stat = fs.statSync( absolutePath );
if ( stat.isFile() ) {
const archivePath = path.relative( siteFolder, absolutePath );
archive.file( absolutePath, { name: archivePath } );
}
}
Without the withFileTypes
option to readdir
, the recursive
option is much more useful, because it also resolves symlinks.
Here's a suggestion for how we could simplify this loop more along the lines of what I had in mind to begin with.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, let me apply that or a similar one and test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the suggestion @fredrikekelund! I have applied a similar approach but I've needed to check for symlinks that point to files as they are not followed by readdir
, you can see my changes in this commit a1ea258. I have added a try..catch
to handle errors if the path does not exist. That can be always extracted to an external function that returns the Extracted to an external function in 683e81dfilePath
if it exists.
I believe my latest suggestion should also fix the issue @bcotrim raised about broken symlinks. |
The suggestion was great, but I have needed to follow symlinks using @bcotrim , @fredrikekelund , this is ready for another review 👀 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have needed to follow symlinks using realpathSync because the target files are not returned by readdir for symlinked files
Good catch 👍
This is looking great and works as expected!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM 👍
Related issues
Proposed Changes
getSymlinks
function that returns recursively all the symlinks of a folderarchive.directory
archiver
zip file as an additional stepTesting Instructions
This can be tested using Studio or the CLI
npm start
ornpm run cli:watch
wp-content
folder, add some symlinks for eitherplugins
orthemes
(although they can be on any other folder). For example, if you download the following repo to your~/github
folder, you could add a symbolic link to it in yourplugins
folder usingln -s ~/github/wp-data-layer-app-pages ~/Studio/my-symlinked-website/wp-content/plugins/wp-data-layer-app-pages
(replacingmy-symlinked-website
by your Studio site).node dist/cli/main.js preview create --path ~/Studio/my-symlinked-website
/wp-admin/plugins.php
or navigate to Plugins -> Intalled PluginsWP Data Layer App Pages
Optional Suggestion: Apart from running one E2E test as explained above, for faster iterations to run multiple different tests, you can see the commit 797a39b that includes a
test.ts
command that can be used to only run the zip step. You can then check that the zip contains the expected symlinks resolved to their target paths. That test can be executed bynode dist/cli/main.js preview test --path ~/Studio/my-symlinked-website
Pre-merge Checklist