-
Notifications
You must be signed in to change notification settings - Fork 30.3k
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
Fix --preserve-symlinks, Enhancement to exploit #10132
Conversation
(includes fix-preserve-symlinks) dependencies can be located like: /node_modules /mod /node_modules /dep practically speaking, this prevents package managers from exploiting symlinks to a machine module store. but by adding and additional search path: /node_modules /mod /mod.node_modules /dep modules can be symlinked to a machine store but still allow local trees to have dependency versions specific to the tree
change path character for adjacent-node-modules directory from '.' to '+' to ensure directory names can never clash with module names
if (p !== nmLen) | ||
paths.push(from.slice(0, last) + '\\node_modules'); | ||
if (code === 92/*\*/ || code === 47/*/*/ || code === 58/*:*/ | ||
|| (adjacentNodeModules && code === 46/*.*/)) { |
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.
Operators go at the end of the previous line. Also, the next line should match the indentation of the start of the first argument on the previous lines.
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.
done
|
||
if (adjacentNodeModules) { | ||
paths.push(parent + '+node_modules'); | ||
if (code === 46/*.*/) |
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.
Braces are missing for this conditional (even if they are syntactically optional, they're required for the project's style).
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.
done
if(!preserveSymlinks) return request | ||
|
||
request = Module._resolveFilename(request, null) | ||
if(!fs.lstatSync(request).isSymbolicLink()) return request |
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.
There should be a space between if
and (
here and all other instances.
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.
done
request = Module._resolveFilename(request, null) | ||
if(!fs.lstatSync(request).isSymbolicLink()) return request | ||
|
||
let isExecutable = false |
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.
Missing semicolon here and all other instances.
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.
done
const execExts = (process.env.PATHEXT || '').split(path.delimiter) | ||
const requestExt = path.extname(request).toLowerCase() | ||
isExecutable = | ||
execExts.some(execExt => requestExt === execExt.trim().toLowerCase()) |
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.
Braces should be included for arrow functions.
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.
done
const requestExt = path.extname(request).toLowerCase() | ||
isExecutable = | ||
execExts.some(execExt => requestExt === execExt.trim().toLowerCase()) | ||
} else try { |
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.
This is confusing, please just include the try-catch inside a normal else {}
block.
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.
done
Quick comment... can you pull the at-mention to mscdex off the title of the commit to keep him from getting pinged every time there's an action taken on that commit ;-) |
Also @phestermcs .. you mentioned test cases over in the other comment thread... this PR would definitely need to include some new test cases to validate the new behavior. Those will also help us to review the functionality here. Can you update the PR to include those tests? |
Besides tests, it might be good to see documentation on this feature and/or a full example of how it’s supposed to be used (there’s a good chance you’ll set the latter up as part of a test case anyway – in that case, feel free to ignore me!). |
@phestermcs Just rebase, editing the message of the commit containing the '@ mention' and then force push to your PR branch once the rebase is complete. |
(As a point of process, would it be ok to ask that we hold off on reviewing syntax until after substantive review takes place? "No" is an ok answer, but it does make it a little harder to dig into the meat.) I'd like to focus on two things first: "adjacent node_modules", and the changes to the main module handling. // when preserving symlinks, if the main file is both a file symlink AND
// has execute permission (on windows, its extension is in PATHEXT), then
// its target is simply read and resolved, potentially relative to the
// symlink's dirname. in all other cases the original path is preserved. I don't understand why it only does the special behavior if the main file is executable. This will be very rare on Windows (since it doesn't support shebang files, It seems like this is perhaps a nod to npm's idiosyncratic use of symlinked bin files? If that's true, though, it'd be better to avoid the special case, and build something that works in general without requiring a special loophole for npm. It seems extremely surprising to me that the module environment of running Second:
I don't think it's true that the existing For example, consider the following folder structure:
In this case, you can even have conflicting versions of meta-dependencies in play. Note that the dep graph looks like:
We do this naively in npm 2.x and before using nested folders, and somewhat less naively in npm 3 (still using nested folders for conflicts only), but there's still exactly one copy of everything in this module store, and no duplication ever. (This is essentially a very stripped down version of what ied and pnpm are doing, with many edge cases left out for clarity.) Note that this only works precisely because we resolve packages to their realpath locations. If we wanted to link I suggested a few other ways to address the I'd personally recommend not adding |
@isaacs when using a machine store with node_modules inside the store, a package will always have the same dependency resolution per machine. What if 2 packages require the exact same package but have different dependency lockfiles? @phestermcs has opened my eyes in this thread 😄. Here's a good explanation from him:
|
extremely surprising is an understatement
Your explanation does not have any obvious relationship to the comment... what does calling realpath have do with executable mode? |
That situation could be addressed using any of the approaches listed here, with a minimal change to the module system or current behavior, and without the hazards introduced by the |
I was suggesting that mscdex and others hold off on talking about code style until we're done with the substantive discussion, because it makes it harder to read the code in the online diffs on github. @phestermcs I'm not complaining about you. Please try your best to not take these things personally. This process takes time and requires diligence and patience, especially when considering a change to the most critical part of node's system. Expect that 90% or more of proposed changes to the module system are rejected because they have some unexpected downsides, and of that 10% that get through, 90% of those turn out to be unfortunate mistakes. So, without suggesting a solution yet, the issue is that you want to have to projects A and B, which both symlink a dependency I see how the My concern about the "pitchy" language of the issue and PR description is that it demonstrates a very enthusiastic attachment to a specific solution. Until we have explored a few different solutions to the problem space, I recommend no changes be made to the node module system. It is clearly good enough for millions of people to be successful with it today, and any change that could disrupt the interaction of a quarter of all developers in the world should be very carefully considered. We must tread very carefully, and diligently comb through every change in a disciplined and critical manner. |
You can't even file mode bits on a symlink in Linux, I guess there you are relying on the fact its always reported as executable? Either way, the resolution of __dirname shouldn't depend on the perceived executability of the symlink, it sounds like there is an apple-specific special case being built in. I'm still -1 on this, and that its so subtle its almost impossible to describe what its purpose is makes me -more-than-1, node's module resolution is sufficiently complex enough, I'm not eager for more complex debugging and support. |
Yes, you can get those bits. And they are always the same for every single symlink, on almost all systems, making them a poor input to conditional behaviour in the module system. On Linux, they are always set, on Windows they are always clear, and on OS X they can be set with |
Then you aren't looking at the permissions of the symlink, you are looking at the permissions of the file it is pointing to. |
@isaacs @phestermcs and others... I have opened nodejs/node-eps#46 in the nodejs/node-eps repository as a home for discussing the larger architectural issue here. The EPS repository is the right place to discuss these larger issues. Let's leave this PR thread to discuss the code in this particular PR only. The goal here is to keep the discussion organized and productive and those of us who want to spend some time with our hands in the code to understand the concrete changes being proposed will have a more difficult time doing so if the bulk of the discussion happens here. |
All mode bits are set for symlinks on Linux, and cannot be changed. |
@sam-github I am in error. |
I'm not interested in personalities or fault. This minor detail in caught my eye, but doesn't change my overall impression, which I've stated previously, multiple times. |
Do you have a github issue ref for that issue? Or is it an issue only in this PR, which I am -1 on, so don't have any suggestions for fixing? |
ec1e088
to
21fb86e
Compare
remove check of execute bit for file-symlink as it's always set. if file-symlink, just follow its target
I dont belong here :) @isaacs has got things covered |
ftr. i removed several comments as they were irrelevant to the issue and inappropriate. |
Checklist
make -j8 test
(UNIX), orvcbuild test nosign
(Windows) passesAffected core subsystem(s)
module
Description of change
please refer to this issue for details.
fix --preserve-symlinks memory bloat, add-on crashing, "main.js" symlink preservation. enhance module search paths to interleave
module+node_modules
adjacent directories