@graftland/package-manager 0.0.0-stage → 0.2.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,202 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
202
+
package/README.md CHANGED
@@ -1,3 +1,251 @@
1
- # Temporary Holding Version
1
+ # @graftland/package-manager
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ English |
4
+ [简体中文](https://github.com/graftland/graft/blob/main/packages/package-manager/README.zh-CN.md)
5
+ |
6
+ [繁體中文](https://github.com/graftland/graft/blob/main/packages/package-manager/README.zh-TW.md)
7
+
8
+ Installs dependencies with npm, pnpm, or Yarn, and rolls back on failure.
9
+
10
+ The minimum supported Node.js version is 22.19. Windows is provided as is.
11
+ Process-tree cleanup and rollback on Windows are not verified, and they are not
12
+ promised.
13
+
14
+ ```sh
15
+ npm install @graftland/package-manager effect
16
+ ```
17
+
18
+ `make` detects the project's package manager. Its `install` adds, updates, or
19
+ removes dependencies. If the install fails, it restores `package.json` and the
20
+ lockfile. `graft add` and `graft remove` use it.
21
+
22
+ ## Installation ownership and recovery
23
+
24
+ Use one manager per project. The caller must give the manager and its child
25
+ processes exclusive write access to the project. This lasts from the start of an
26
+ `install` call until it finishes, including recovery.
27
+
28
+ - Do not edit project files during this time.
29
+ - Do not run another installer at the same time.
30
+ - Calls are serialized within one manager. Graft does not enforce a lock across
31
+ managers, editors, or processes.
32
+
33
+ On POSIX, process cleanup covers only the installer's original process group.
34
+ Installation scripts and recovery scripts must not create unmanaged writers.
35
+ Examples are children in independent process groups, and daemons that keep
36
+ writing project files. Graft does not provide OS-level containment. It does not
37
+ discover escaped writers. Confirming that the original group has stopped does
38
+ not prove that every possible descendant has stopped. Scripts that break this
39
+ rule are outside the rollback guarantee.
40
+
41
+ Managed paths must be regular files, or must be absent. `make` and each
42
+ `install` use `lstat` to check the snapshot set. The snapshot set is
43
+ `package.json`, lockfiles, and tracked configuration and installation-state
44
+ files.
45
+
46
+ - Symbolic links are rejected, including dangling links. Other file types are
47
+ rejected too.
48
+ - The error is `UnsupportedFileTypeError`. It is raised before any transaction
49
+ write and before any installer starts.
50
+ - The error shows the `path` and the `fileType`. The link and its target stay
51
+ unchanged.
52
+ - A rejection before the transaction begins does not need recovery. It does not
53
+ make the manager unusable.
54
+
55
+ ### Rollback
56
+
57
+ Rollback runs when installation fails or is interrupted before commit. It needs
58
+ confirmation that the original process group has stopped. Then it does these
59
+ things:
60
+
61
+ - It restores the backed-up package files byte for byte. It keeps their recorded
62
+ permission bits and ownership.
63
+ - It removes tracked files that were absent before installation.
64
+
65
+ The tracked files include `package.json`, lockfiles, and the package-manager
66
+ configuration and installation-state files that the manager tracks.
67
+
68
+ Rollback overwrites changes made during installation. It does not detect
69
+ conflicts. It does not merge edits.
70
+
71
+ When the project already had a lockfile for the selected package manager,
72
+ recovery also tries to repair `node_modules`. Recovery errors are kept together
73
+ with the original failure. Recovery after a process crash is not promised.
74
+
75
+ ### File writes and process cleanup
76
+
77
+ Accepted file writes have a completion boundary. Cancellation waits for the
78
+ native operation to finish. This happens before rollback, before snapshot
79
+ cleanup, and before the manager's lock is released. The transaction tracks its
80
+ own file writes and the original installer group. Snapshot creation and rollback
81
+ writes have the same completion guarantee.
82
+
83
+ Each installer process and each recovery process has a ten-minute exit wait.
84
+ Cancellation or expiry closes its process scope before files are restored.
85
+
86
+ - The process receives SIGTERM.
87
+ - If it has not stopped after five seconds, it receives SIGKILL.
88
+ - On POSIX, even a successful kill must be followed by evidence that the
89
+ original process group no longer exists. Checks run every 20 milliseconds,
90
+ with at most 100 additional checks (two seconds of waiting) for short-lived
91
+ zombie groups to be reaped. EPERM is treated as continued presence, never as
92
+ absence.
93
+ - If the group remains or a check fails, termination is unconfirmed: the manager
94
+ keeps its snapshot, skips rollback and recovery, and enters
95
+ `RecoveryRequired`.
96
+ - Process cleanup failures stay in the returned cause, together with
97
+ installation and recovery failures.
98
+ - The exit timeout does not include process cleanup or file restoration. It is
99
+ not a deadline for the whole install-and-recover operation.
100
+
101
+ The Node adapter may replace the original OS errno with a generic kill failure.
102
+ Only after a POSIX group check confirms absence does PackageManager resolve
103
+ signal failures carrying ESRCH or the adapter's specific `Unknown` kill error
104
+ with cause `Failed to kill child process`. This does not resolve other reasons
105
+ in the same Cause: join failures, other kill errors, defects, and interruption
106
+ remain failures. If the group still exists, the original Cause is preserved; if
107
+ the group check fails, both complete Causes are retained.
108
+
109
+ ## Committed installs and snapshot cleanup
110
+
111
+ A successful `install` result means the dependency changes are committed.
112
+
113
+ `result.snapshotCleanupFailure` is an `Option<SnapshotCleanupFailure>`:
114
+
115
+ - `None` means snapshot deletion succeeded.
116
+ - `Some` contains `snapshotDirectory` and the complete cleanup `cause`,
117
+ including defects.
118
+
119
+ A failure to delete the snapshot after commit does not turn the install into a
120
+ failure. It does not trigger rollback. The manager stays usable, and
121
+ `recoveryRequired` stays `None`.
122
+
123
+ What callers should do:
124
+
125
+ - Inspect this field. Report or record its Cause.
126
+ - Retry the deletion of that snapshot directory after the filesystem problem is
127
+ fixed. The directory may be partly removed.
128
+ - Do not restore the old snapshot. Do not repeat the install only because
129
+ snapshot deletion failed. Those changes are already committed.
130
+ - The kept data may contain credentials, so make sure it is removed in the end.
131
+
132
+ Failures before commit still fail the operation and run rollback. Any snapshot
133
+ cleanup failure is combined with the original failure.
134
+
135
+ Cancellation can stop a result from being delivered after commit. It is not
136
+ proof that the changes were undone. In that case, cleanup failures are kept in
137
+ the interrupted Cause.
138
+
139
+ ## When manual recovery is required
140
+
141
+ Before an install changes project files, it saves a snapshot. The snapshot is in
142
+ a private `graft-recovery-*` directory in the OS temporary directory.
143
+
144
+ - Only a successful install or a complete rollback allows the snapshot to be
145
+ deleted and another install to run.
146
+ - A committed install with a failed snapshot deletion is reported as described
147
+ above.
148
+ - Process termination and transaction completion are separate conditions.
149
+ Stopping the writer lets rollback begin. It does not prove that rollback
150
+ succeeded.
151
+
152
+ If any rollback step fails, the manager keeps the snapshot and needs manual
153
+ recovery. This includes restoring files before or after recovery, and running
154
+ the recovery installer. It applies even when the final file restoration
155
+ succeeds, because failed recovery may have left `node_modules` incomplete.
156
+
157
+ How the manager restores a file:
158
+
159
+ 1. It creates a temporary file in the target directory, exclusively and with
160
+ mode `0600`.
161
+ 2. It restores the saved uid/gid when the platform reports them. Then it
162
+ restores the permission bits.
163
+ 3. It syncs and closes the file.
164
+ 4. It renames the file over the target.
165
+
166
+ Metadata failures and temporary-file cleanup failures also need manual recovery.
167
+ They stay in the Cause. Timestamps, ACLs, and extended attributes are not
168
+ recorded.
169
+
170
+ If process termination cannot be confirmed, no further restoration or recovery
171
+ runs. This avoids a conflict with a process that may still be writing.
172
+
173
+ ### The recovery error
174
+
175
+ The Cause of the failed call keeps the original failure and all rollback and
176
+ termination failures. It includes a `RecoveryRequiredError`, which has these
177
+ fields:
178
+
179
+ - `snapshotDirectory` gives the saved location.
180
+ - `directory` identifies the project.
181
+ - `recoveryCause` keeps the complete original and recovery Cause.
182
+ - `operation` is `rollback` for an incomplete rollback with stopped writers. For
183
+ unconfirmed termination, it is `install` or `recover`. In that case,
184
+ `processId` and `terminationCause` give the process details.
185
+
186
+ After cancellation, callers can also read `manager.recoveryRequired`. It is an
187
+ Effect that yields `Option<RecoveryRequiredError>`. Callers do not need to catch
188
+ an interrupted call.
189
+
190
+ The manager rejects all later `install` calls for good, with this typed error.
191
+ This includes calls that are already queued and calls with empty change sets.
192
+ `readState` stays a read-only diagnostic operation. It may fail while project
193
+ files are incomplete.
194
+
195
+ ### Steps to recover manually
196
+
197
+ 1. Identify the failed installer and all of its descendants, and stop them. The
198
+ recorded process ID is for diagnosis only. It does not prove that a later
199
+ process with the same ID belongs to the installation. Do not restore files or
200
+ start another installer until all writers have stopped.
201
+ 2. Fix the filesystem problem that stopped the restoration, such as file
202
+ permissions. Then restore the files:
203
+ - Open `snapshot.json` in `snapshotDirectory`. Check its project `directory`
204
+ and `agent`.
205
+ - For each `files` entry, copy the bytes from the named `backup` into a
206
+ temporary file beside the project's `name`. Create the temporary file
207
+ privately and exclusively.
208
+ - Restore the recorded `uid` and `gid` when they are not null. Then restore
209
+ `mode`. Do this before you replace the target.
210
+ - These metadata values are JSON integers. `mode` is the numeric permission
211
+ bitmask, not an octal-formatted string.
212
+ - Create parent directories as needed.
213
+ - A null `backup` means that the file was absent. Remove it if it exists now.
214
+ 3. Repair `node_modules` with the selected package manager. Use the restored
215
+ manifest and lockfile. After that process has stopped, restore the snapshot
216
+ files again if it rewrote them. The snapshot does not contain `node_modules`.
217
+ 4. Verify the project. Then delete the snapshot and create a new manager with
218
+ `make`. The old manager has no reset operation.
219
+
220
+ Snapshots can contain credentials from package-manager configuration files. Keep
221
+ them private. They survive the closing of the manager and of the Effect scope.
222
+ But the OS may clean temporary directories. If manual recovery will be delayed,
223
+ copy a kept snapshot to a safe, private place.
224
+
225
+ This mechanism is not crash recovery. It is not a project lock across processes.
226
+ Creating a new manager does not make a still-live installer safe.
227
+
228
+ ## Documentation
229
+
230
+ - [Command line](https://graftland.github.io/graft/cli/)
231
+
232
+ Part of [Graft](https://github.com/graftland/graft). Licensed under
233
+ [Apache 2.0](https://github.com/graftland/graft/blob/main/LICENSE).
234
+
235
+ ## Package manager declarations
236
+
237
+ The shared `PackageManagerDeclaration` Schema accepts `npm`, `pnpm`, or `yarn`
238
+ followed by an exact SemVer version (at most 256 characters), including optional
239
+ prerelease identifiers. It rejects empty suffixes, leading zeros in numeric
240
+ version/prerelease components, ranges, tags, URLs, and surrounding whitespace. A
241
+ `+` suffix is reserved for a
242
+ [Corepack hash](https://github.com/nodejs/corepack#when-authoring-packages):
243
+ `sha224`, `sha256`, `sha384`, or `sha512`, followed by a dot and exactly 56, 64,
244
+ 96, or 128 lowercase hexadecimal characters respectively. Arbitrary SemVer build
245
+ metadata is not supported here. Hash validation checks the declaration's syntax;
246
+ it does not download or authenticate the package-manager archive.
247
+
248
+ Both project creation and package-manager detection use this Schema, exported
249
+ from `@graftland/package-manager/PackageManagerDeclaration`. Creation preserves
250
+ the supplied declaration verbatim. An explicit detection `agent` still overrides
251
+ the manifest declaration.
@@ -0,0 +1,183 @@
1
+ # @graftland/package-manager
2
+
3
+ [English](https://github.com/graftland/graft/blob/main/packages/package-manager/README.md)
4
+ | 简体中文 |
5
+ [繁體中文](https://github.com/graftland/graft/blob/main/packages/package-manager/README.zh-TW.md)
6
+
7
+ 使用 npm、pnpm 或 Yarn 安装依赖,并在失败时回滚。
8
+
9
+ 支持的最低 Node.js 版本是 22.19。Windows 按现状提供。Windows 上的进程树清理和回滚没有经过验证,也不作承诺。
10
+
11
+ ```sh
12
+ npm install @graftland/package-manager effect
13
+ ```
14
+
15
+ `make` 会检测项目所用的包管理器。它的 `install`
16
+ 可以添加、更新或移除依赖。如果安装失败,它会恢复 `package.json`
17
+ 和锁文件。`graft add` 和 `graft remove` 使用它。
18
+
19
+ ## 安装的所有权与恢复
20
+
21
+ 每个项目只使用一个管理器。调用方必须让管理器及其子进程独占项目的写入权限。这从一次
22
+ `install` 调用开始,持续到它结束,包括恢复阶段。
23
+
24
+ - 在此期间,不要编辑项目文件。
25
+ - 不要同时运行另一个安装程序。
26
+ - 同一个管理器内的调用是串行的。Graft 不会在多个管理器、编辑器或进程之间强制加锁。
27
+
28
+ 在 POSIX 上,进程清理只覆盖安装程序最初的进程组。安装脚本和恢复脚本不得创建不受管理的写入者。例如,处于独立进程组的子进程,以及持续写入项目文件的守护进程。Graft 不提供操作系统级别的隔离,也不会发现逃逸的写入者。确认最初的进程组已经停止,并不能证明所有可能的后代都已经停止。违反这条规则的脚本不在回滚保证的范围内。
29
+
30
+ 受管理的路径必须是普通文件,或者不存在。`make` 和每次 `install` 都使用 `lstat`
31
+ 检查快照集合。快照集合包括
32
+ `package.json`、锁文件,以及被跟踪的配置文件和安装状态文件。
33
+
34
+ - 符号链接会被拒绝,包括悬空链接。其他文件类型也会被拒绝。
35
+ - 错误是
36
+ `UnsupportedFileTypeError`。它在任何事务写入之前、任何安装程序启动之前抛出。
37
+ - 错误会给出 `path` 和 `fileType`。链接及其目标保持不变。
38
+ - 在事务开始之前的拒绝,不需要恢复,也不会让管理器不可用。
39
+
40
+ ### 回滚
41
+
42
+ 当安装在提交之前失败或被中断时,会执行回滚。回滚需要先确认最初的进程组已经停止。然后它会做这些事:
43
+
44
+ - 把已备份的包文件逐字节恢复,并保留它们记录下来的权限位和所有者。
45
+ - 移除安装之前不存在的被跟踪文件。
46
+
47
+ 被跟踪的文件包括
48
+ `package.json`、锁文件,以及管理器所跟踪的包管理器配置文件和安装状态文件。
49
+
50
+ 回滚会覆盖安装期间所做的更改。它不会检测冲突,也不会合并编辑。
51
+
52
+ 如果项目已经有所选包管理器的锁文件,恢复阶段还会尝试修复
53
+ `node_modules`。恢复错误会与原始失败一起保留。不承诺在进程崩溃之后恢复。
54
+
55
+ ### 文件写入与进程清理
56
+
57
+ 已接受的文件写入有一个完成边界。取消会等待原生操作完成,然后才进行回滚、清理快照,以及释放管理器的锁。事务会跟踪它自己的文件写入以及最初的安装程序进程组。创建快照和回滚写入也有同样的完成保证。
58
+
59
+ 每个安装进程和每个恢复进程都有十分钟的退出等待时间。取消或超时会在恢复文件之前关闭它的进程 scope。
60
+
61
+ - 进程会收到 SIGTERM。
62
+ - 如果五秒后它还没有停止,就会收到 SIGKILL。
63
+ - 在 POSIX 上,即使 kill 成功,也必须确认原进程组已不存在。每 20 毫秒检查一次,最多追加 100 次检查(等待两秒),给短暂残留的僵尸进程组留出回收窗口。EPERM 视为仍然存在,不视为已消失。
64
+ - 如果进程组仍存在或检查失败,则终止未获确认:管理器保留快照,不执行回滚和恢复安装,并进入
65
+ `RecoveryRequired`。
66
+ - 进程清理失败会与安装和恢复的失败一起保留在返回的 cause 中。
67
+ - 退出超时不包括进程清理和文件恢复,也不是整个“安装并恢复”操作的截止时间。
68
+
69
+ Node 适配器可能会把原始的操作系统 errno 替换为通用的 kill 失败。只有 POSIX 检查确认进程组不存在后,PackageManager 才消解携带 ESRCH 的信号失败,或适配器特定的
70
+ `Unknown` kill 错误(cause 为
71
+ `Failed to kill child process`)。同一 Cause 中的其他原因仍然保留,包括 join 失败、其他 kill 错误、defect 和中断。进程组仍存在时保留原始 Cause;检查失败时保留两个完整 Cause。
72
+
73
+ ## 已提交的安装与快照清理
74
+
75
+ `install` 成功的结果表示依赖更改已经提交。
76
+
77
+ `result.snapshotCleanupFailure` 是 `Option<SnapshotCleanupFailure>`:
78
+
79
+ - `None` 表示快照删除成功。
80
+ - `Some` 包含 `snapshotDirectory` 和完整的清理 `cause`,包括 defect。
81
+
82
+ 提交之后删除快照失败,不会让这次安装变成失败,也不会触发回滚。管理器仍然可用,`recoveryRequired`
83
+ 仍然是 `None`。
84
+
85
+ 调用方应该这样做:
86
+
87
+ - 检查这个字段,报告或记录它的 Cause。
88
+ - 在文件系统问题解决之后,重试删除该快照目录。该目录可能已被部分删除。
89
+ - 不要恢复旧的快照,也不要仅仅因为快照删除失败就重复安装。那些更改已经提交了。
90
+ - 保留下来的数据可能包含凭据,所以要确保它最终被删除。
91
+
92
+ 提交之前的失败仍然会使操作失败并执行回滚。任何快照清理失败都会与原始失败合并。
93
+
94
+ 取消可能会使提交之后的结果无法送达。这并不能证明更改已被撤销。这种情况下,清理失败会保留在被中断的 Cause 中。
95
+
96
+ ## 何时需要手动恢复
97
+
98
+ 在安装更改项目文件之前,会先保存一份快照。快照位于操作系统临时目录中一个私有的
99
+ `graft-recovery-*` 目录里。
100
+
101
+ - 只有成功的安装或完整的回滚,才允许删除快照并运行下一次安装。
102
+ - 已提交但快照删除失败的安装,按上文所述报告。
103
+ - 进程终止和事务完成是两个独立的条件。停止写入者,回滚才能开始,但这并不证明回滚成功。
104
+
105
+ 只要有任何一个回滚步骤失败,管理器就会保留快照,并需要手动恢复。这包括在恢复之前或之后恢复文件,以及运行恢复安装程序。即使最后的文件恢复成功,这条规则也适用,因为失败的恢复可能让
106
+ `node_modules` 不完整。
107
+
108
+ 管理器如何恢复一个文件:
109
+
110
+ 1. 在目标目录中以独占方式创建临时文件,模式为 `0600`。
111
+ 2. 当平台报告了保存的 uid/gid 时,先恢复它们,然后恢复权限位。
112
+ 3. 对文件执行 sync 并关闭。
113
+ 4. 把该文件重命名覆盖到目标上。
114
+
115
+ 元数据失败和临时文件清理失败同样需要手动恢复,并保留在 Cause 中。时间戳、ACL 和扩展属性不会被记录。
116
+
117
+ 如果无法确认进程已经终止,就不会再进行任何恢复。这是为了避免与可能仍在写入的进程发生冲突。
118
+
119
+ ### 恢复错误
120
+
121
+ 失败调用的 Cause 会保留原始失败以及所有回滚和终止的失败。它包含一个
122
+ `RecoveryRequiredError`,其字段如下:
123
+
124
+ - `snapshotDirectory` 给出保存的位置。
125
+ - `directory` 标识项目。
126
+ - `recoveryCause` 保留完整的原始 Cause 和恢复 Cause。
127
+ - `operation`:在写入者已停止但回滚不完整时是 `rollback`;在无法确认终止时是
128
+ `install` 或 `recover`,此时 `processId` 和 `terminationCause`
129
+ 给出进程的详细信息。
130
+
131
+ 取消之后,调用方还可以读取 `manager.recoveryRequired`。它是一个产出
132
+ `Option<RecoveryRequiredError>` 的 Effect。调用方不需要去捕获被中断的调用。
133
+
134
+ 管理器会永久拒绝之后所有的 `install`
135
+ 调用,并返回这个带类型的错误。这包括已经排队的调用,以及变更集为空的调用。`readState`
136
+ 仍然是只读的诊断操作。在项目文件不完整时,它可能失败。
137
+
138
+ ### 手动恢复的步骤
139
+
140
+ 1. 找出失败的安装程序及其所有后代,并停止它们。记录下来的进程 ID 只用于诊断,不能证明之后具有相同 ID 的进程属于这次安装。在所有写入者停止之前,不要恢复文件,也不要启动另一个安装程序。
141
+ 2. 解决阻止恢复的文件系统问题,例如文件权限。然后恢复文件:
142
+ - 打开 `snapshotDirectory` 中的 `snapshot.json`。检查其中的项目 `directory`
143
+ 和 `agent`。
144
+ - 对每个 `files` 条目,把指定的 `backup` 中的字节复制到项目 `name`
145
+ 旁边的临时文件中。以私有、独占的方式创建该临时文件。
146
+ - 当 `uid` 和 `gid` 不为 null 时,恢复记录下来的值,然后恢复
147
+ `mode`。请在替换目标之前完成这些。
148
+ - 这些元数据值是 JSON 整数。`mode`
149
+ 是数值形式的权限位掩码,不是八进制格式的字符串。
150
+ - 按需创建父目录。
151
+ - `backup` 为 null 表示该文件原本不存在。如果它现在存在,就删除它。
152
+ 3. 使用所选的包管理器,基于已恢复的清单文件和锁文件修复
153
+ `node_modules`。该进程停止之后,如果它重写了快照中的文件,请再次恢复这些文件。快照不包含
154
+ `node_modules`。
155
+ 4. 验证项目。然后删除快照,并用 `make`
156
+ 创建一个新的管理器。旧的管理器没有重置操作。
157
+
158
+ 快照可能包含来自包管理器配置文件的凭据。请保持私有。它们在管理器和 Effect
159
+ scope 关闭之后依然存在。但是操作系统可能会清理临时目录。如果手动恢复会被推迟,请把保留的快照复制到一个安全的私有位置。
160
+
161
+ 这个机制不是崩溃恢复,也不是跨进程的项目锁。创建新的管理器,并不会让仍在运行的安装程序变得安全。
162
+
163
+ ## 文档
164
+
165
+ - [命令行](https://graftland.github.io/graft/zh-cn/cli/)
166
+
167
+ 属于 [Graft](https://github.com/graftland/graft)。基于
168
+ [Apache 2.0](https://github.com/graftland/graft/blob/main/LICENSE) 许可。
169
+
170
+ ## 包管理器声明
171
+
172
+ 共享的 `PackageManagerDeclaration` Schema 接受 `npm`、`pnpm` 或
173
+ `yarn`,后跟精确的 SemVer 版本(版本部分最多 256 个字符),可包含预发布标识。它拒绝空后缀、版本及预发布数字标识的前导零、版本范围、标签、URL 和首尾空白。
174
+
175
+ `+` 后缀仅用于
176
+ [Corepack hash](https://github.com/nodejs/corepack#when-authoring-packages):支持
177
+ `sha224`、`sha256`、`sha384`、`sha512`,后跟点号及分别为 56、64、96、128 个字符的小写十六进制摘要。不支持普通 SemVer
178
+ build metadata。这里只校验声明语法,不下载或验证包管理器压缩包的真实性。
179
+
180
+ 创建项目与检测包管理器使用同一个 Schema,可从
181
+ `@graftland/package-manager/PackageManagerDeclaration`
182
+ 导入。创建时逐字保留传入的声明;检测时显式指定的 `agent`
183
+ 仍优先于 manifest 声明。