-
Notifications
You must be signed in to change notification settings - Fork 240
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
RFC: Robust Lifecycle Scripts #437
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,71 @@ | ||||||||||||||
# {{TITLE: a human-readable title for this RFC!}} | ||||||||||||||
|
||||||||||||||
## Summary | ||||||||||||||
|
||||||||||||||
Lifecycle scripts need to have more context passed to them about why an event is being invoked. Due to the way packages are deduped and hoisted, not all use cases are properly covered by simply event names. | ||||||||||||||
|
||||||||||||||
## Motivation | ||||||||||||||
|
||||||||||||||
Lifecycle scripts such as `preinstall` `install` and `postinstall` are very commonly used, but difficult to reason about due to the way package trees are installed and updated. In the case of `install`, a package could be directly installed as a dependency by a user, installed as a global, indirectly installed as a dependency, or added when dependency versions changed to the point where a dependency could no longer be shared between two packages as a single version. Worst still, `uninstall` could be fired when the package won't be removed at all due to one dependant no longer requiring it, but another still needing to keep it. | ||||||||||||||
|
||||||||||||||
An `uninstall` script could assume that the directory will be removed or not used in the future, and put the package into a bad state if it was merely deduped, for example. By providing scripts with more context, like whether a new directory is being made or destroyed, which depenants caused the installation, etc, we could properly cover all use-cases and remove confusion that would lead to destroyed data, bugs, and frustration. | ||||||||||||||
|
||||||||||||||
Future versions of npm could use different reify engines and methods, potentially leading to more needed context for these scripts. | ||||||||||||||
|
||||||||||||||
The primary reason for this RFC is the lack of `uninstall` lifecycle scripts in npm v7, and how difficult it would be to re-add them in a clear way. | ||||||||||||||
|
||||||||||||||
See issue: #3042 | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
|
||||||||||||||
## Detailed Explanation | ||||||||||||||
|
||||||||||||||
Lifecycle scripts need to be re-engineered and the old approach should be deprecated. | ||||||||||||||
|
||||||||||||||
`package.json` will include a new index called `"lifecycle"` | ||||||||||||||
|
||||||||||||||
```js | ||||||||||||||
{ | ||||||||||||||
"lifecycle": { | ||||||||||||||
"install": { | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think having discrete points in the reify process is better than trying to support the pre/post paradigm like this. It was an inadequate model and confuses people a lot (i.e. "pre" means "it runs before the x scripts" not "it runs before x") I'd prefer not having pre/post be baked in like this. If we have a need for something to run at a time, we put an event there and name it accordingly. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree. Also, I think it would be good to first make the list of the "event moments" we want to expose, and identify where each current There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. imo "it runs before x" is the semantic every single person wants, and the other semantic is "silly sugar for |
||||||||||||||
"pre" "bin/life_install.sh --phase=pre ${dependant} ${depenant_path} ${reify-reason}" | ||||||||||||||
"pre" "bin/life_install.sh --phase=current ${dependant} ${reify-reason}" | ||||||||||||||
"pre" "bin/life_install.sh --phase=post ${dependant} ${reify-reason}" | ||||||||||||||
Comment on lines
+29
to
+31
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If this is running during the reserved phase, then we're in trouble. Before diffing, we don't know what will actually be done. Before unpacking, we don't have the scripts on disk to run. After unpacking, it's arguably not "pre"-install. But a userland script can modify the folder tree (or even the dependency graph, if it writes to package.json files), so it invalidates the work already done. We'd have to keep reloading the trees and recalculating the diff if any changes were made. If we don't do that, then we have security problems. Better to just keep the "pre" to "here's what we were asked to do, we are about to start getting ready to figure out how to do it", and the "post" is "here's what we did, and it's all the way done now". So something like: {
"lifecycle": {
"beforeTreeChange": "_loadTrees has not yet been called",
"afterTreeChange": "_reifyPackages has completed",
"beforeSave": "have not yet written to package.json and package-lock.json",
"afterSave": "completely wrote lockfile and manifest"
}
} If There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems like we'd be exposing implementation pretty directly, which is subject to change. For example, a module-map based solution might not use trees at all. Is there a way we could be more future-proof and still be accurate about the lifecycle? Do package maintainers need all of that granularity? |
||||||||||||||
}, | ||||||||||||||
"publish": { | ||||||||||||||
"pre": "prepublish" // default | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
``` | ||||||||||||||
|
||||||||||||||
By default, the lifecycle script sections will reference the old reserved scripts, maintaining compatibility. | ||||||||||||||
|
||||||||||||||
## Rationale and Alternatives | ||||||||||||||
|
||||||||||||||
We could solve this problem by simply adding more lifecycle scripts, to an extent. | ||||||||||||||
|
||||||||||||||
We could solve this problem by clearly documenting exactly when when a lifecycle script happens, and dramatically limiting their scope. | ||||||||||||||
|
||||||||||||||
We could have one lifecycle script that we pass a full arborist diff to and let the package maintainer deal with the changes. | ||||||||||||||
|
||||||||||||||
This RFC provides a good balance of flexibility, allows the old lifecycle scripts to continue to exist, and solves the problem of lack of context. | ||||||||||||||
|
||||||||||||||
## Implementation | ||||||||||||||
|
||||||||||||||
### TODO: | ||||||||||||||
* list all lifecycles | ||||||||||||||
* provide lists of context keys | ||||||||||||||
* describe how it could be expanded in the future | ||||||||||||||
* describe defaults and default behavior | ||||||||||||||
* describe effect on older packages | ||||||||||||||
* describe future changes to arborists impact | ||||||||||||||
|
||||||||||||||
## Prior Art | ||||||||||||||
|
||||||||||||||
### TODO: | ||||||||||||||
* is there prior art? What other build systems deal with lifecycles in this way? | ||||||||||||||
* are there lessons to be learned from the likes of grunt, etc? | ||||||||||||||
|
||||||||||||||
## Unresolved Questions and Bikeshedding | ||||||||||||||
|
||||||||||||||
* will future changes to arborists or additional reify engines change things? | ||||||||||||||
* can we provide a stable base for future needs? |
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 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 generally use the American spellings of things, so
dependent
(for both noun and adjective) rather thandependant
. Sorry, total nitpick, I know. (Still reading, will provide more substantive feedback as full review.)