-
Notifications
You must be signed in to change notification settings - Fork 2.7k
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(linking): Handle missing package when using --ignore-optional #5059
Conversation
Woot! Thanks @kjbekkelund, do you feel comfortable trying to resolve this bug? Otherwise I can take a look it. Let me know :) |
@kaylieEB I've been exploring a bit, but I haven't had much progress yet. Would be great if you could take a look. |
I think it's related to So it feels like there's a need to walk the dependencies in Line 115 in 9c2bbca
Thoughts? |
Ah, or maybe Line 221 in ef8185b
However, that depends on that |
c2c64ac
to
6ad221f
Compare
This change will increase the build size from 10.51 MB to 10.51 MB, an increase of 762 bytes (0%)
|
@kaylieEB Looks like I got it running in the end. The problem is that you need to know if it was listed in yarn/src/cli/commands/install.js Lines 319 to 321 in 2065988
Let me know if there's a better way to handle this. |
16eee4d
to
6437382
Compare
With the latest fix, this should also handle #4876 |
6437382
to
39bc4ee
Compare
39bc4ee
to
abbd567
Compare
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.
Looks pretty good, thank you!
I've added some comments and curious what you think about those suggestions.
src/package-hoister.js
Outdated
const isMarkedAsOptional = depinfo.pkg._reference && depinfo.pkg._reference.optional && this.ignoreOptional; | ||
const depRef = depinfo.pkg._reference; | ||
|
||
let isMarkedAsOptional = depRef && depRef.optional && this.ignoreOptional; |
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.
We should be able to reduce this to:
let isMarkedAsOptional = depRef && depRef.optional && this.ignoreOptional && !(info.isRequired && depRef.hint !== 'optional');
What do you think? The comment you added should stay though, it is great!
src/package-request.js
Outdated
@@ -52,6 +53,7 @@ export default class PackageRequest { | |||
config: Config; | |||
registry: ResolverRegistryNames; | |||
optional: boolean; | |||
hint: ?string; |
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.
Would you mind changing this to an enum both to avoid unnecessary string usage all over the app and to avoid potential typing errors.
My suggestion is to add something like the following into constants.js and use that:
const REQUEST_HINTS = Object.freeze({
dev: Symbol('REQUEST_HINT_DEV'),
optional: Symbol('REQUEST_HINT_OPTIONAL'),
});
src/package-hoister.js
Outdated
// If it's marked as optional, but the parent is required and the | ||
// dependency was not listed in `optionalDependencies`, then we mark the | ||
// dependency as required. | ||
if (isMarkedAsOptional && info.isRequired && depRef && depRef.hint !== 'optional') { |
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.
Can we not use depRef.isRequired
instead of this? Or may be add depRef.isOptional
?
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 tried that at first, but it fails with "deeper optionals". E.g.
foo (in `optionalDependencies`) -> bar -> baz
quux -> bar -> baz
Here both bar
and baz
will have _reference.isOptional === true
because of foo
. However, as this code doesn't mark the reference when performing this logic (only setting depinfo.isRequired = true
), baz
will stay optional
as bar._reference.optional === true
. If we changed the logic to also do a depRef.setOptional(false)
in addition to setting depinfo.isRequired
this approach would work. That was my initial fix, but I changed to this approach as I wasn't sure about doing that work from the package hoister. Thoughts?
@BYK I ended up doing:
as this field seems to be used for a couple things. That's likely a sign this should be cleaned up(?) Given yarn/src/cli/commands/outdated.js Line 33 in 19c8cd5
Thoughts? I'd of course prefer to not extend the scope of this PR ;) |
@BYK Just back from my holidays, so I thought I'd ping you about my suggestion above. |
@kjbekkelund I think the PR is fine but I have realized this is masking another issue which makes this part of the code as complex as it is right now which is marking all transient dependencies of optional dependencies as optional too, opening the door for potentially corrupt installations for optional dependencies. Also we apparently already have a test case for this behavior: yarn/__tests__/commands/install/integration.js Lines 956 to 967 in 3e1c3a7
May be instead of adding all these new files, you can fix that test to cover transient dependencies (or just extend it). What do you think? |
Ember CLI detects the yarn.lock file to decide whether to use Yarn or not. Pending yarnpkg/yarn#5059, we should switch to npm by default.
Ember CLI detects the yarn.lock file to decide whether to use Yarn or not. Pending yarnpkg/yarn#5059, we should switch to npm by default.
@BYK Finally had some time to get this done. Are you okey with this approach? Is the |
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.
Hey! This new version looks solid. I think fs.readdir
approach is actually neat and makes it more readable but I'll see what @arcanis thinks before merging.
Thanks a lot for coming back and pushing this forward! ❤️
const pushDeps = ( | ||
depType, | ||
manifest: Object, | ||
{hint, optional}: {hint: ?constants.RequestHint, optional: boolean}, |
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.
Why make hint
optional here?
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.
Because of
yarn/src/cli/commands/install.js
Line 327 in 5ef113f
pushDeps('dependencies', projectManifestJson, {hint: null, optional: false}, true); |
Not sure if constants.RequestHint | null
or ?constants.RequestHint
is preferred(?) Or maybe add a production
hint and always require hint
?
@@ -124,3 +124,5 @@ export const VERSION_COLOR_SCHEME: {[key: string]: VersionColor} = { | |||
}; | |||
|
|||
export type VersionColor = 'red' | 'yellow' | 'green' | 'white'; | |||
|
|||
export type RequestHint = 'dev' | 'optional' | 'resolution' | 'workspaces'; |
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.
Don't see other hints being used anywhere else. Do we need anything other than 'dev'
and 'optional'
at the moment?
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.
resolution
is used in:
yarn/src/cli/commands/install.js
Line 281 in 5ef113f
resolutionDeps = [...resolutionDeps, {registry, pattern, optional: false, hint: 'resolution'}]; |
and workspaces
in:
yarn/src/cli/commands/install.js
Line 379 in 5ef113f
pushDeps('workspaces', {workspaces: virtualDep}, {hint: 'workspaces', optional: false}, true); |
I'll merge this since it fixes an important issue but I'd love to get answers to my final comments and maybe get a follow-up to address them. |
This option is apparently fixed in yarn 1.6.0: yarnpkg/yarn#5059
This option is apparently fixed in yarn 1.6.0: yarnpkg/yarn#5059
Summary
Closes #5054, closes #4876, closes #5152.
Currently when running
yarn --ignore-optional
required dependencies can be marked as optional because they exist in the tree ofoptionalDependencies
of one of the dependencies (but it's required by some other non-optional dependency).For example:
once
depends onwrappy
, butwrappy
is also in the chain fromfsevents
(glob
->inflight
->wrappy
) which is an optional dependency ofchokidar
. So if the firstwrappy
comes from an "optional chain" when yarn processes it, it ends up being marked as_reference.optional = true
, and the package hoister therefore doesn't mark it as required.When running the released version of yarn with https://github.com/spalger/reproduce-issues/blob/master/yarn-ignores-non-optional-dependencies you'll see
This fix checks whether or not the parent is marked as required, and if so, marking the dependency as required unless it's listed in the parent's
optionalDependencies
.Test plan
I tested this implementation with both https://github.com/spalger/reproduce-issues/blob/master/yarn-ignores-non-optional-dependencies and #4876, and in both cases
yarn check --verify-tree
succeeded after runningyarn --ignore-optional
.Also added automated tests.