For the tree:
a
+-- b
| +-- c
| +-- d
| | +-- e
| +-- f
+-- x
+-- y
+-- z
To turn it into:
a
+-- b'
| +-- c'
| +-- d
| | +-- e'
| +-- f
+-- x
+-- y'
+-- z
That is, update b, c, e, and y, but not d, x, or z.
- Windows will fail with EPERM when trying to move or delete a folder with current (or even recent) file operations happening inside it.
- Nodes with
bundleDependencies
also bundle the meta-deps of their bundled deps. But, we don't know what those are from the manifest, and it's possible that there's overlap between bundled metadeps and regular dependencies, meaning that there can be extraneous nodes in the ideal tree once all the tarballs are unpacked. - This is exceedingly time-sensitive.
- Users generally expect that a failed install should not make their app unusable.
This is the fastest and most efficient way to proceed, but does not admit rollbacks very easily in any way that is EPERM friendly on Windows.
Scan actualTree inventory. For each node:
- If not in idealTree, rimraf path, remove from actualTree (so we don't check its children).
- If in ideal tree and identical resolved/integrity, leave it alone.
- If in ideal tree and different, and no unbundled children, rimraf path, remove from actualTree
- If in ideal tree and different and has unbundled children,
- rimraf package contents
- rimraf bundled children and remove them from tree
mkdir
every path in ideal tree that does not exist in actualTree
- Group all nodes with bundles by depth.
- for depth = 0, for all nodes at that depth with bundleDependencies,
- If no nodes at that level with bundles still in the tree, done.
- extract all nodes with bundles at that depth level in parallel
- call
loadActual
with a new Arborist on each one, and move each depth=1 child node into the ideal tree.
- if any bundle deps unpacked, prune tree
pacote.extract
all remaining nodes in parallel- rm any leaf node folders not extracted into
Extract all remaining packages in parallel.
Their folders already exist, so it's fine to just dump them all into place.
This algorithm avoids ever renaming a directory, or removing a directory with recent writes (except in the case of failure rollbacks), so as to minimize the chances of hitting Windows file-locking EPERM issues.
It is very safe, and somewhat disk-inefficient.
a
+-- b (.b-original)
| +-- c
| +-- d
| | +-- e
| +-- f
+-- x
+-- y (.y-original)
+-- z
Fail: rename each .${name}-original
to ${name}
a
+-- b (.b-original)
| +-- c
| +-- d
| | +-- e
| +-- f
+-- b'
| +-- c'
| +-- d (empty)
| +-- e'
+-- x
+-- y (.y-original)
| +-- z
+-- y'
To maximize parallelization while minimizing unnecessary fetches for bundled deps and meta-deps:
mkdirp
all the leaf nodes in the tree in parallel- Group all nodes with bundles by depth.
- for depth = 0, for all nodes at that depth with bundleDependencies,
- If no nodes at that level with bundles still in the tree, done.
- extract all nodes with bundles at that depth level in parallel
- call
loadActual
with a new Arborist on each one, and move each depth=1 child node into the ideal tree.
- if any bundle deps unpacked, prune tree
pacote.extract
all remaining nodes in parallel- rm any leaf node folders not extracted into
Fail: remove sparse tree, fail step 1
a
+-- b (.b-original)
| +-- c
| +-- d (empty)
| +-- e
+-- b'
| +-- c'
| +-- d
| | +-- e'
| +-- f
+-- x
+-- y (.y-original)
+-- y'
+-- z
This actually means that we move each unchanging node's contents (other
than node_mdules
) into the new location. (Maybe we ought to only ever
move files, not directories?)
Fail: move unchanging nodes back to staged tree, fail step 2
Windows Consideration! Extremely easy for a failure in this step to lead to EPERM in the rollback, if we try to rimraf the sparse tree before we're fully moved out of it.
a
+-- b'
| +-- c'
| +-- d
| | +-- e'
| +-- f
+-- x
+-- y'
+-- z
Fail: report failure as a warning and instruct user to rm -rf
the garbage
directory.