-
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: Remove devDependencies from list output when environment is production #4092
Conversation
Related #4095. |
src/constants.js
Outdated
} | ||
|
||
export function isProduction(env: Env): boolean { | ||
return getEnv(env) === 'production'; |
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.
You should make 'development
and 'production
strings constants too ;)
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.
Will do!
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.
@BYK So, I looked around the project and noticed 'production' is used elsewhere. I'll add the constants, and maybe open a subsequent PR to update the cases where 'production' is referring to NODE_ENV
src/cli/commands/list.js
Outdated
export async function run(config: Config, reporter: Reporter, flags: Object, args: Array<string>): Promise<void> { | ||
const lockfile = await Lockfile.fromDirectory(config.lockfileFolder, reporter); | ||
const install = new Install(flags, config, reporter, lockfile); | ||
const {requests: depRequests, patterns, workspaceLayout} = await install.fetchRequestFromCwd(); | ||
|
||
|
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.
These extra blank lines would probably fail lint.
src/cli/commands/list.js
Outdated
await install.resolver.init(depRequests, { | ||
isFlat: install.flags.flat, | ||
isFrozen: install.flags.frozenLockfile, | ||
workspaceLayout, | ||
}); | ||
|
||
let pkgPatterns = []; | ||
if (isProduction(process.env)) { |
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 would just use process.env
at all times instead of passing this. Also this should probably be a method on config
since we also support passing --prod
flag. Not sure that flag applies to list
but we can make it so?
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 thought about that, but it seemed that it would decrease the testability of isProduction
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.
You should be able to mock process.env
during tests ;)
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.
Added tests for these. Feels a little weird to store and restore the NODE_ENV
instead of injecting, but if there's no reason for other portions of the code to care about process.env
, then this makes sense :)
src/cli/commands/list.js
Outdated
let pkgPatterns = []; | ||
if (isProduction(process.env)) { | ||
const devDeps = getDevDeps(manifest); | ||
pkgPatterns = patterns.filter(pattern => !devDeps.includes(pattern)); |
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 would convert devDeps
to a Set
for faster look up. Technically, both can be sets since this is a set subtraction but the Set
class doesn't have that method unless you use a third-party library.
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.
Good point. This is probably O(n)^n since filter
loops over the entire collection and then includes
checks the entire collection for existence there, too.
devDeps
being a set could get the filtering down to constant access to see if a package is a dependency, but I don't see how converting patterns
to a set would benefit this particular piece of code, since its creation would be linear as well. I could be blindly missing something, though :)
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.
Where n
is the length of devDeps
and m
being the length of patterns it should go as follows:
- O(n) for creating the set
- O(1) for each look up
- O(m) for creating the other set
- O(m) for walking over the set
So I think you're right. Just converting one to a set for faster look up should be the best :)
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 wait, there's one important nuance tho:
If m
> n
which usually is the case, then converting m
to a set and then going over devDeps
(O(n)) and removing items from the set should be faster.
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.
Hmm ...
So, let's say a
is our dependencies and b
is our dev dependencies.
For production, we're looking for the subset, c
, which is found by a
- b
In both cases where the length of a
> b
and the length of b
> a
you would have to create a set of the largest ( or smallest depending on where you want to take the linear hit ) and then iterate over the other collection to find the subset of a
- b
.
I think converting devDeps
to a set will get us to O(n) + m, which is pretty okay :)
Well, you broke the build :D |
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.
Please fix the build.
__tests__/constants.js
Outdated
@@ -10,3 +10,45 @@ test('getPathKey', () => { | |||
expect(getPathKey('linux', {})).toBe('PATH'); | |||
expect(getPathKey('darwin', {})).toBe('PATH'); | |||
}); | |||
|
|||
test('NODE_ENV_DEV', () => expect(NODE_ENV_DEV).toBe('development')); |
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 think these are useful :)
@BYK Build fixed. Was going to test stdout. Let me know what you think: 1.) Install packages from a fixture that has a dev dependency |
@olingern that plans sounds good to me. Keep in mind that you can create your own small fixtures using |
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 a few comments but overall looks good.
I can land this as it is but if you want to go back one last round to address my comments, I'm good with that too. Holding the merge until I hear back from you :)
__tests__/commands/list.js
Outdated
const trees = [makeTree('gulp@3.9.1', {color: 'bold'}), makeTree('gulp-babel@6.1.2', {color: 'bold'})]; | ||
test('lists all dependencies when not production', (): Promise<void> => { | ||
const nodeEnv = process.env.NODE_ENV; | ||
process.env.NODE_ENV = 'development'; |
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.
Mocking isProduction
may be the better approach here since that should also be affected by the --production
flag on the CLI.
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.
Gotcha
|
||
rprtr.tree('list', trees); | ||
|
||
expect(tree).toEqual(rprtr.getBuffer()); |
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.
May be instead of constructing the tree manually, you can just do expect(reporter.getBuffer()).toMatchSnapshot()
?
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'm new-er to Jest, and just watched @cpojer's talk on Jest's new features. As I understand it, snapshots don't ensure validity, but keep a historical record (snapshot) of the current state. I could see snapshots being useful for quick smoke tests, but I'm curious to how you would use them 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.
Here's my reasoning:
If you manually tested this and know this output is correct, we can safely assume that the output should not change unless the behavior changes. Thus, we can store this sample output as a reference and in this output we should/should not have devDependencies based on the prod state. Makes sense?
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.
If, for some reason, the functionality of how yarn list
changed -- .toMatchSnapshot()
would catch the error, but it wouldn't give a diff on why / how to fix it (or maybe it would, I'm not privy to snapshot's expected / result output).
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.
but it wouldn't give a diff on why / how to fix it (or maybe it would, I'm not privy to snapshot's expected / result output).
It would :) It'd say "I expected this, and I got this other thing instead. Here's the diff."
__tests__/constants.js
Outdated
@@ -10,3 +10,22 @@ test('getPathKey', () => { | |||
expect(getPathKey('linux', {})).toBe('PATH'); | |||
expect(getPathKey('darwin', {})).toBe('PATH'); | |||
}); | |||
|
|||
test('isProduction', () => { |
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.
Protip: you can have describe('isProduction')
at the top that wraps all this and then put each separate scenario in their own test()
calls.
Btw. changing globals like process.env.NODE_ENV
in tests is a bit dangerous since we run tests in parallel. It may yield to other problems. A no-to-great way is to define isProduction
as follows that lets you do dependency injection:
function isProduction(env = process.env) {
return env.NODE_ENV === 'production';
}
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.
Yah, wasn't sure of the overall test structure / style. It seemed that mosts tests are at parent level to each file, so I didn't know if nesting tests inside of a describe was good or bad style here.
Btw. changing globals like process.env.NODE_ENV in tests is a bit dangerous since we run tests in parallel
Agreed. Wasn't sure of how to successfully mock process.env.NODE_ENV
, but your advice above sorts that out :)
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.
It seemed that mosts tests are at parent level to each file, so I didn't know if nesting tests inside of a describe was good or bad style here.
Yeah, that's something I want to address soon. Apologies for the confusion :(
@@ -0,0 +1,8 @@ | |||
{ | |||
"dependencies": { | |||
"left-pad": "^1.1.3" |
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.
Instead of relying on "real" dependencies, you can just construct local packages which may simplify and speed up things.
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.
Hmmm ... could you point me to a test that follows that pattern? I was basing most of my structure off of the no-args
tests / fixtures
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.
src/cli/commands/list.js
Outdated
@@ -180,21 +181,36 @@ export function filterTree(tree: Tree, filters: Array<string>): boolean { | |||
return notDim && (found || hasChildren); | |||
} | |||
|
|||
export function getDevDeps(manifest: Object): Set<string> { | |||
const devDepSet = new Set(); | |||
Object.keys(manifest.devDependencies).forEach(key => devDepSet.add(`${key}@${manifest.devDependencies[key]}`)); |
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.
Even better:
return new Set(Object.keys(manifest.devDependencies).map(key => `${key}@${manifest.devDependencies[key]}`));
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.
Nice, will amend
src/cli/commands/list.js
Outdated
await install.resolver.init(depRequests, { | ||
isFlat: install.flags.flat, | ||
isFrozen: install.flags.frozenLockfile, | ||
workspaceLayout, | ||
}); | ||
|
||
let pkgPatterns = []; |
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.
May be a more explanatory name than pkgPatterns
? May be usedPatterns
or activePatterns
?
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.
Good point. activePatterns
has a nice "ring" to it.
@olingern ping me when you push the final update then! |
@BYK I have couple questions / comments, but will get around to making all the fixes later today. 🎉 |
load devDeps from manifest and filter if production add constants for node env and dont pass in env to getEnv add tests for NODE_ENV_DEV, NODE_ENV_PROD, getEnv, and isProduction remove extra line break Test list output dependent upon environment rename pkgPatterns update list test with snapshot and use local pkg
That seems like the right place for me :) Seems like you have a few lint issues ( Thanks a lot for all the follow-ups, very much appreciated! |
@arcanis I can make sure linting / tests pass today |
That would be great 😃 I tried running prettier, but unfortunately some Flow errors remain |
Summary
Fixes: #3788.
This PR loads
devDependencies
from the manifest, re-builds the patterns package structure, and filters packages if theNODE_ENV
is production.Test plan
Added new tests.