Skip to content
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

Hoist may delete certain packages from the packages folder during bootstrap #951

Closed
danielo515 opened this issue Aug 2, 2017 · 41 comments
Closed

Comments

@danielo515
Copy link
Contributor

Today I ran into one of the worst things a software can do because a bug: fail destructively.

I recently started to use hoist to save lot of startup time and space during development. However I find a situation where hoist behaves destructively, which should never happen in this kind of tool. Luckly git (specifically VSCode git interface) show me a bunch of deleted files in a bright red that took my attention. This may be the result of a mixture of uncommon variables:

  • I wanted to hoist certain local packages, so I added the root of the repo to the packages array
  • There is a mismatch on the package version and the packages that depend on it

Expected Behavior

I expected lerna to warn me some way, instead of deleting my package and fail with a long and unreadable stack trace

Current Behavior

The package is deleted and sometimes bootstrap fails, and other times it succeeds. Very erratic

Steps to Reproduce (for bugs)

  1. On a lerna repo in lerna.json add "." to the array of packages
  2. Make one package with a version, let's say 0.2.4
  3. Add one or two more packages depending on the first one but with a different version than the current one, fort example 0.2.2
  4. Add hoist: true to lerna.json or add the option during the bootstrap command
  5. Execute lerna bootstrap
{
  "lerna": "2.0.0",
  "packages": [
    ".",
    "src/*"
  ],
  "hoist": true,
  "registry": "https://tools.caseonit.com/npm",
  "commands": {
    "publish": {
      "conventionalCommits": true,
      "message": "chore: Publish"
    }
  },
  "version": "independent"
}

lerna-debug.log

No lerna-debug is generated

Your Environment

Executable Version
lerna --version 2.0.0
npm --version 4.1.2
node --version 7.7.1
OS Version
MacOSX El Capitan
@evocateur
Copy link
Member

What does your root package.json look like? I assume you've got dependencies or devDependencies there that point to packages under src, and you're publishing the root package.json as its own package as well?

@danielo515
Copy link
Contributor Author

Hello,
Yes, I have one dependency on the root package that points to one subpackage under src. I did this because I wanted one local package to be joisted and this seems to be the only solution. I have excludes the root package from publishing, but I did not from bootstrap. May this be the root of the problem ? In any case I find that deleting the package is some kind of bug or at least unwanted behavior

@evocateur
Copy link
Member

Hoisting has nothing to do with the location of the managed package. Hoisting just optimizes the installation of external npm dependencies, using the node module resolution algorithm. It's not necessary to create explicit dependencies on monorepo-local packages.

@danielo515
Copy link
Contributor Author

In my particular case it is necessary. I need to hoist the local package to avoid it getting its own version of certain shared library

@evocateur
Copy link
Member

A shared external library with identical version range specifier? I'd just add it to the root package.json as a devDependency and then you'd never have to worry about that (as hoisting will use whatever is already installed in the root if it matches).

I want to be extra clear: there is no sense of "hoist" as lerna uses it that has any effect to a local (lerna-managed) package. Those are always symlinked wherever it finds a matching name+version. --hoist only ever affects external dependencies, and only bails out if the versions specified between the root and the leaf do not satisfy the semver range (which means it is then installed under the leaf so that it does not resolve to the root installation).

@tomccabe
Copy link

tomccabe commented Aug 2, 2017

I am currently running into this issue. One of my packages is completely blown away on bootstrap. This had not been an issue until today. The root is not a published package, just the ./packages folder.

lerna.json:

{
  "lerna": "2.0.0-rc.4",
  "packages": [
    "packages/*"
  ],
  "version": "independent"
}

Whatever the issue with the script, deleting packages is...not ideal behavior.

@evocateur
Copy link
Member

@tomccabe I can't reproduce it with just that information. Is this a public repo, perhaps?

@tomccabe
Copy link

tomccabe commented Aug 2, 2017 via email

@tomccabe
Copy link

tomccabe commented Aug 3, 2017

Hi @evocateur -- debugged the issue tonight and I hadn't updated semvers in packages that were consuming the package getting deleted on bootstrap. We stopped using the lerna publish command early on (just too aggressive), so those weren't updated automatically. I'm not sure that explains why the package source code was being removed, but maybe @danielo515's issue is cause by the same.

@fyodorvi
Copy link

Hello there, I managed to fully reproduce the error, here is the repo with minimal config:

https://github.com/fyodorvi/lerna-hoist-bug

We have two packages in that repo: lerna-hoist-test-wheel and lerna-hoist-test-tyre. Wheel depends on tyre.

Note that wheel version 0.1.0 is published to npm - that is very important.

Initially all versions match: wheel and tyre have 0.1.0 version, wheel depends on tyre as ^0.1.0.

Steps to reproduce:

  1. Checkout the repo, run npm install
  2. Run npm run lerna - it will run bootstrap with hoisting. Everything should be fine at that point.
  3. Go into lerna-hoist-test-tyre and manually bump the version in package json to 0.2.0
  4. Run npm run lerna

Result:

packages/lerna-hoist-test-tyre is deleted.

@evocateur
Copy link
Member

evocateur commented Aug 16, 2017 via email

@rsouthgate
Copy link

Any update on this?

For the workflow where a lot of different devs work on a lot of different packages we see this quite a bit as the lerna version is bumped in master and devs merge master into their feature branches. If the dev is working on a new package that didn't exist on master and forgets to update the version number in their package.json to match the incoming bump, lerna will delete their package and any of their local dependencies (if they were not updated too).

Any way to prevent destructive behaviour in the mean time?

@tomredman
Copy link

+1 for updates - we've also run into this and lost some work during development.

@evocateur
Copy link
Member

  1. If you're working in a feature branch, you shouldn't be modifying the version property of any package.json (this is identical to "normal" npm package development patterns).
  2. If you need to test a pre-release package from a feature branch, use --canary.
  3. Don't use long-lived feature branches.
  4. The hoisted directory pruning should probably also filter out lerna-managed siblings, and/or the rimraf call should not follow symlinks.

@danielo515
Copy link
Contributor Author

If you're working in a feature branch, you shouldn't be modifying the version property of any package.json (this is identical to "normal" npm package development patterns).

So you should merge it into develop and create a release branch for the version bump ?

@evocateur
Copy link
Member

evocateur commented Nov 16, 2017 via email

@Offirmo
Copy link

Offirmo commented Dec 2, 2017

Just run into that. My new package vanished into oblivion 😢 super complicated code not yet commited.

If that can help:

  • the package was originally OUTSIDE of lerna, I may have npm linked it in one of the existing lerna packages from outside during dev
  • I then decided to copy it into lerna packages/
  • manually changed the version in its package.json and some other changes
  • lerna bootstrap: gone!

@Offirmo
Copy link

Offirmo commented Dec 2, 2017

@evocateur

The hoisted directory pruning should probably also filter out lerna-managed siblings, and/or the rimraf call should not follow symlinks.

definitely YES 😿

[edit] managed to recover most of the code 🤕

@Robinfr
Copy link

Robinfr commented Feb 14, 2018

I'm having issues where --hoist is removing several of the dependencies that were already installed. I was running this:

lerna bootstrap --hoist="{mocha,webpack,webpack-dev-server,node-sass}"

and it would just remove the webpack-node-externals module that was installed in the root package..

@evocateur
Copy link
Member

evocateur commented Feb 14, 2018 via email

@Robinfr
Copy link

Robinfr commented Feb 14, 2018 via email

@evocateur
Copy link
Member

evocateur commented Feb 14, 2018 via email

@Robinfr
Copy link

Robinfr commented Feb 15, 2018 via email

@evocateur
Copy link
Member

evocateur commented Feb 15, 2018 via email

@Robinfr
Copy link

Robinfr commented Feb 15, 2018 via email

@Robinfr
Copy link

Robinfr commented Feb 15, 2018

Actually, I tried disabling the package-lock.json, it made absolutely no difference. So that's not the problem..

@erwinverdonk
Copy link

erwinverdonk commented Apr 26, 2018

Here some info regarding the cause of this issue:

The actual problem here is not directly related to Lerna. It is related to the Lerna usage of the rimraf module. This module uses lstat and thus treats symbolic links, like the ones Lerna creates to link packages, as directories. The result is that when Lerna wants to remove a symbolic link dependency from node_modules, it will actually remove the directory it is linked to, which is undesired.

Solution would be to use stat instead of lstat, which does not follow symbolic links for information, but that is something either has to be added as an option to rimraf or use something else other than rimraf for the removal of dependencies in the node_modules folder.

Now that the cause is clear, we have to think of a solution for Lerna. However I first would like to know what @evocateur thinks about this.

I see that @evocateur already mentioned something similar to this a few posts up. So yeah now we only need someone who will add a PR for the fix. Maybe I will cook something, but am a bit busy lately :).

@evocateur
Copy link
Member

evocateur commented Apr 26, 2018 via email

@danielo515
Copy link
Contributor Author

Yes, this would whack any nohoist or root-conflicting leaf install, but it’s an edge case, and infrequent.

Could you expand on that? I'm a happy user of nohoist

@evocateur
Copy link
Member

--nohoist will still work, it'll just be a clean slate (under packages/* or wherever packages are configured) before every lerna bootstrap --hoist to avoid the obscenely slow and error prone "pruning" of existing hoisted packages.

The only change in the context of --nohoist is that repeated run of lerna bootstrap --hoist (who does that?) will be re-installing the non-hoisted packages (mostly from warm caches, so still pretty fast). Previously you could sometimes avoid re-installing them because they were already there (but it was pretty much unknowable, and would be installed over anyway, which many times would be a no-op).

@danielo515
Copy link
Contributor Author

Ok, seems it is not a problem for me since I configure both hoist and nohoist in lerna.json

@danielo515
Copy link
Contributor Author

This happened to me today again.
The problem was because I run npm-publish and I skipped a package that depends on the other published packages. Because of that, the skipped package was not updated with the new versions. Then, I just run bootstrap and It deleted the depended packages and then I got an error.

@stale
Copy link

stale bot commented Dec 27, 2018

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@ziofat
Copy link

ziofat commented Mar 28, 2019

I ran into this problem today. None of the packages are published.

I added hoist yesterday and keep all versions are same. Then I added a new devDep today in one of the packages, it crashed with a lot of deletion.

lerna version is 3.13.1

@daffl
Copy link

daffl commented Apr 1, 2019

I think I am seeing the same thing.

Steps to reproduce:

Clone https://github.com/feathersjs/feathers, install dependencies, try to add a dependency to any repository.

lerna --version
# 3.13.1

git clone git@github.com:feathersjs/feathers.git
cd feathers
npm install
lerna add @types/debug --scope @feathersjs/authentication-local --dev

Expected:

@types/debug is added as a devDependency to packages/authentication-local

Actual:

All folders of local dependencies of packages/authentication-local are being deleted. It looks like they are not being recognized as local dependencies:

lerna WARN bootstrap Installing local packages that do not match filters from registry
lerna sill batched [ PackageGraphNode {
lerna sill batched     name: '@feathersjs/authentication-local',
lerna sill batched     externalDependencies:
lerna sill batched      Map {
lerna sill batched        '@feathersjs/authentication' => [Object],
lerna sill batched        '@feathersjs/feathers' => [Object],
lerna sill batched        'mocha' => [Object],
lerna sill batched        '@feathersjs/errors' => [Object],
lerna sill batched        '@types/debug' => [Object],
lerna sill batched        'bcryptjs' => [Object],
lerna sill batched        'debug' => [Object],
lerna sill batched        'lodash' => [Object] },
lerna sill batched     localDependencies: Map {},
lerna sill batched     localDependents: Map {} } ]

It should be a pretty standard Lerna setup (it doesn't look like anything mentioned here applies - or I'm missing it). Not sure where to start debugging.

@knopkem
Copy link

knopkem commented Apr 16, 2019

+1 from me, lerna seems to randomly delete my own packages using --hoist option

@indiejoseph
Copy link

+1 having commit for a while, so have to rewrite my packages....

@willdavidow
Copy link

Ran into this issue just now and it turned out that the issue was sub-package dependency/package-lock collisions.

Fixed by running lerna clean and then re-running lerna bootstrap --hoist

@evocateur
Copy link
Member

oh hey, i think 71174e4 weaponized --scope to a degree that lerna add should no longer be passing it along...

@piperchester
Copy link
Contributor

piperchester commented Aug 7, 2019

Similar to @daffl - we have a monorepo with 80+ packages. I major bumped one of the packages (let's say A) from v1 to v2 locally (before it's been deployed to be installed by).

Two other packages (B and C) depend on v1.x of A. So the root package structure might look like

root/packages/A # v2.0
root/packages/B # A@v1.0
root/packages/C # A@v1.0

When I run lerna bootstrap --hoist, A is deleted:

root/packages/B # A@v1.0
root/packages/C # A@v1.0

And B and C's package-lock.json remains the same. I suspect because B and C's dependence on v1 take precedence over the newly versioned v2.

Has anyone experienced similar behavior to this?

Edit: lerna run clean may solve this issue.

@evocateur
Copy link
Member

TL;DR: Don't use lerna bootstrap. Yarn workspaces, pnpm recursive, and npm workspaces (whenever they're released, lol) should be more than sufficient.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests