@graftland/package-manager 0.3.0 → 0.3.2
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/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -18
- package/dist/index.js.map +1 -1
- package/dist/process.js +1 -1
- package/dist/process.js.map +1 -1
- package/dist/snapshot.js +3 -3
- package/dist/snapshot.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +281 -305
- package/src/process.ts +39 -40
- package/src/snapshot.ts +22 -22
package/dist/index.d.ts
CHANGED
|
@@ -95,6 +95,6 @@ export interface PackageManager {
|
|
|
95
95
|
* Committed installs report snapshot deletion failures in snapshotCleanupFailure
|
|
96
96
|
* rather than failing or rolling back. No crash recovery is promised.
|
|
97
97
|
*/
|
|
98
|
-
export declare const make: (options: Options) => Effect.Effect<PackageManager, Error, FileSystem.FileSystem | Path.Path
|
|
98
|
+
export declare const make: (options: Options) => Effect.Effect<PackageManager, Error, ChildProcessSpawner.ChildProcessSpawner | FileSystem.FileSystem | Path.Path>;
|
|
99
99
|
export { Changes, PackageManagerDeclaration, PackageName, type SnapshotCleanupFailure, UnsupportedFileTypeError };
|
|
100
100
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;qBAiDa,OAAK,OAAA;YAMN,eAAe,MAAM;;;;;;WAGd;;qBAFN,4BAA4B;;;;;;;;;;WAUtB;;qBAFN,8BAA8B;YAoC/B,QACT,sBACA,2BACA,wBACA,cAAc,gBACd,OAAO;iBACO;;WAEP;;WAEA,QAAQ;;WAER;;;qBAkGG,cAAW,SAAA,YAAA,OAAA,uDAAA,sBAAA,cAAA,gBAAA,OAAA,aAAA,WAAA,aAAA,KAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;qBAiDa,OAAK,OAAA;YAMN,eAAe,MAAM;;;;;;WAGd;;qBAFN,4BAA4B;;;;;;;;;;WAUtB;;qBAFN,8BAA8B;YAoC/B,QACT,sBACA,2BACA,wBACA,cAAc,gBACd,OAAO;iBACO;;WAEP;;WAEA,QAAQ;;WAER;;;qBAkGG,cAAW,SAAA,YAAA,OAAA,uDAAA,sBAAA,cAAA,gBAAA,OAAA,aAAA,WAAA,aAAA,KAAA;iBAsEP;WACP;;WAEA;WACA;;;YAGE,QAAQ,QAAQ,gBAAgB;iBAC3B;WACP;WACA,QAAQ;WACR,OAAO;;iBAEA;WACP,OAAO,cAAc;WACrB,SAAS,cAAc;WACvB,SAAS,cAAc;;qBAEpB,YAAS,QAAY,OAAK,OAAS,UAAQ;iBA0BvC,sBAAsB;WAC7B,OAAO;;WAEP,wBAAwB,OAAO,OAAO;;iBAE/B;WACP;WACA,OAAO;WACP,WAAW,OAAO,OAAO,OAAO;;WAEhC,kBAAkB,OAAO,OAAO,OAAO,OAAO;;WAE9C,UAAU,SAAS,YAAY,OAAO,OAAO,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBA6BzD,OAAI,SAAA,YAAA,OAAA,OAAA,gBAAA,OAAA,oBAAA,sBAAA,WAAA,aAAA,KAAA"}
|
package/dist/index.js
CHANGED
|
@@ -112,7 +112,7 @@ const readSource = Effect.fnUntraced(function* (fs, file) {
|
|
|
112
112
|
});
|
|
113
113
|
const Executable = PackageManagerName;
|
|
114
114
|
const Digits = Schema.String.check(Schema.isPattern(/^\d+$/));
|
|
115
|
-
const decodePackageManager = Schema.
|
|
115
|
+
const decodePackageManager = Schema.decodeUnknownEffect(PackageManagerDeclaration);
|
|
116
116
|
/** `name/version ...`, as package managers set `npm_config_user_agent`. */
|
|
117
117
|
const decodeUserAgent = Schema.decodeUnknownOption(Schema.TemplateLiteralParser([
|
|
118
118
|
Executable,
|
|
@@ -127,13 +127,13 @@ const detectAgent = Effect.fnUntraced(function* (options) {
|
|
|
127
127
|
const fs = yield* FileSystem.FileSystem;
|
|
128
128
|
const path = yield* Path.Path;
|
|
129
129
|
const manifest = yield* readSource(fs, path.join(options.directory, "package.json")).pipe(Effect.flatMap(decodeManifest));
|
|
130
|
-
if (manifest.packageManager !== void 0)
|
|
131
|
-
|
|
130
|
+
if (manifest.packageManager !== void 0) {
|
|
131
|
+
const [name, , version] = yield* decodePackageManager(manifest.packageManager).pipe(Effect.mapError((error) => PackageManagerError.make({
|
|
132
132
|
operation: "detect",
|
|
133
|
-
message: `Unsupported packageManager: ${manifest.packageManager}`
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
}
|
|
133
|
+
message: `Unsupported packageManager: ${manifest.packageManager}\n${error.message}`
|
|
134
|
+
})));
|
|
135
|
+
return agentOf(name, `${version.major}`);
|
|
136
|
+
}
|
|
137
137
|
const found = yield* Effect.filter(lockfiles, (file) => fs.exists(path.join(options.directory, file)));
|
|
138
138
|
const kinds = Array.dedupe(Array.map(found, (file) => file === "pnpm-lock.yaml" ? "pnpm" : file === "yarn.lock" ? "yarn" : "npm"));
|
|
139
139
|
if (kinds.length > 1) return yield* PackageManagerError.make({
|
|
@@ -191,7 +191,7 @@ const diffState = (before, after) => {
|
|
|
191
191
|
* Committed installs report snapshot deletion failures in snapshotCleanupFailure
|
|
192
192
|
* rather than failing or rolling back. No crash recovery is promised.
|
|
193
193
|
*/
|
|
194
|
-
const make =
|
|
194
|
+
const make = Effect.fnUntraced(function* (options) {
|
|
195
195
|
const fs = yield* FileSystem.FileSystem;
|
|
196
196
|
const path = yield* Path.Path;
|
|
197
197
|
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
|
|
@@ -215,7 +215,7 @@ const make = (options) => Effect.gen(function* () {
|
|
|
215
215
|
...all,
|
|
216
216
|
...manifest[field]
|
|
217
217
|
}));
|
|
218
|
-
const entries = yield* Effect.forEach(Record.toEntries(deps), ([name, spec])
|
|
218
|
+
const entries = yield* Effect.forEach(Record.toEntries(deps), Effect.fnUntraced(function* ([name, spec]) {
|
|
219
219
|
const file = path.join(directory, "node_modules", name, "package.json");
|
|
220
220
|
const source = yield* fs.readFileString(file).pipe(Effect.asSome, Effect.catchCauseIf((cause) => Array.every(cause.reasons, (reason) => Cause.isFailReason(reason) && reason.error.reason.module === "FileSystem" && reason.error.reason._tag === "NotFound"), () => Effect.succeedNone));
|
|
221
221
|
if (Option.isNone(source)) return [name, {
|
|
@@ -244,7 +244,7 @@ const make = (options) => Effect.gen(function* () {
|
|
|
244
244
|
detached: true,
|
|
245
245
|
...termination,
|
|
246
246
|
env: command.env
|
|
247
|
-
})).pipe(Effect.tap(() => Ref.set(transaction, ManagerState.Active({ writer: WriterState.Running() })))),
|
|
247
|
+
})).pipe(Effect.tap(() => Ref.set(transaction, ManagerState.Active({ writer: WriterState.Running() })))), Effect.fnUntraced(function* (process) {
|
|
248
248
|
const code = yield* process.exitCode.pipe(Effect.timeoutOrElse({
|
|
249
249
|
duration: "10 minutes",
|
|
250
250
|
orElse: () => PackageManagerError.make({
|
|
@@ -257,7 +257,7 @@ const make = (options) => Effect.gen(function* () {
|
|
|
257
257
|
exitCode: code,
|
|
258
258
|
message: `${command.executable} ${operation} exited with code ${code}`
|
|
259
259
|
});
|
|
260
|
-
}),
|
|
260
|
+
}), Effect.fnUntraced(function* (process) {
|
|
261
261
|
const result = yield* terminateProcess(process, termination);
|
|
262
262
|
yield* Ref.set(transaction, ManagerState.Active({ writer: result._tag === "OriginalGroupStopped" ? WriterState.ManagedWritersIdle() : WriterState.Unconfirmed({
|
|
263
263
|
operation,
|
|
@@ -268,12 +268,12 @@ const make = (options) => Effect.gen(function* () {
|
|
|
268
268
|
}));
|
|
269
269
|
}, Effect.scoped);
|
|
270
270
|
const withFileWriter = (effect) => Effect.acquireUseRelease(Ref.set(transaction, ManagerState.Active({ writer: WriterState.FileWrite() })), () => effect, () => Ref.set(transaction, ManagerState.Active({ writer: WriterState.ManagedWritersIdle() }))).pipe(Effect.uninterruptible);
|
|
271
|
-
const install =
|
|
271
|
+
const install = Effect.fnUntraced(function* (changes) {
|
|
272
272
|
const state = yield* Ref.get(transaction);
|
|
273
273
|
if (state._tag === "RecoveryRequired") return yield* state.error;
|
|
274
274
|
const validated = yield* Schema.decodeEffect(Changes)(changes);
|
|
275
275
|
const inspected = yield* inspectManagedFiles(fs, path, directory, backupFiles);
|
|
276
|
-
const backups = yield* Effect.forEach(inspected, ({ name, file, metadata })
|
|
276
|
+
const backups = yield* Effect.forEach(inspected, Effect.fnUntraced(function* ({ name, file, metadata }) {
|
|
277
277
|
return {
|
|
278
278
|
name,
|
|
279
279
|
file,
|
|
@@ -292,10 +292,10 @@ const make = (options) => Effect.gen(function* () {
|
|
|
292
292
|
const text = yield* encodeManifest(applyChanges(manifest, validated), original);
|
|
293
293
|
const agentLocks = strategy(agent, "install").lockfiles;
|
|
294
294
|
const hasLock = Array.some(backups, ({ name, contents }) => Array.contains(agentLocks, name) && Option.isSome(contents));
|
|
295
|
-
const completed = yield* withSnapshot(fs, Ref.get(transaction).pipe(Effect.map((state) => state._tag === "Ready")),
|
|
295
|
+
const completed = yield* withSnapshot(fs, Ref.get(transaction).pipe(Effect.map((state) => state._tag === "Ready")), Effect.fnUntraced(function* (snapshotDirectory) {
|
|
296
296
|
const snapshot = yield* writeSnapshot(fs, path, snapshotDirectory, directory, agent, backups);
|
|
297
297
|
const restore = Effect.flatMap(Ref.get(transaction), (state) => state._tag === "Active" && state.writer._tag === "ManagedWritersIdle" ? withFileWriter(snapshot.restore(state.writer)) : Effect.void);
|
|
298
|
-
return yield* Effect.acquireUseRelease(Ref.set(transaction, ManagerState.Active({ writer: WriterState.ManagedWritersIdle() })),
|
|
298
|
+
return yield* Effect.acquireUseRelease(Ref.set(transaction, ManagerState.Active({ writer: WriterState.ManagedWritersIdle() })), Effect.fnUntraced(function* () {
|
|
299
299
|
yield* withFileWriter(fs.writeFileString(manifestFile, text));
|
|
300
300
|
yield* run("install");
|
|
301
301
|
const after = yield* readState;
|
|
@@ -303,7 +303,7 @@ const make = (options) => Effect.gen(function* () {
|
|
|
303
303
|
agent,
|
|
304
304
|
...diffState(before, after)
|
|
305
305
|
};
|
|
306
|
-
}),
|
|
306
|
+
}), Effect.fnUntraced(function* (_, exit) {
|
|
307
307
|
if (Exit.isFailure(exit)) {
|
|
308
308
|
const state = yield* Ref.get(transaction);
|
|
309
309
|
if (state._tag !== "Active" || state.writer._tag !== "ManagedWritersIdle") return;
|
|
@@ -311,7 +311,7 @@ const make = (options) => Effect.gen(function* () {
|
|
|
311
311
|
if (hasLock) yield* run("recover").pipe(Effect.onExit(() => restore));
|
|
312
312
|
}
|
|
313
313
|
yield* Ref.set(transaction, ManagerState.Ready());
|
|
314
|
-
})).pipe(Effect.onExit(
|
|
314
|
+
})).pipe(Effect.onExit(Effect.fnUntraced(function* (exit) {
|
|
315
315
|
const state = yield* Ref.get(transaction);
|
|
316
316
|
if (Exit.isSuccess(exit) || state._tag !== "Active") return;
|
|
317
317
|
const writer = state.writer;
|
|
@@ -334,7 +334,7 @@ const make = (options) => Effect.gen(function* () {
|
|
|
334
334
|
...completed.value,
|
|
335
335
|
snapshotCleanupFailure: completed.snapshotCleanupFailure
|
|
336
336
|
};
|
|
337
|
-
}));
|
|
337
|
+
}, lock.withPermits(1));
|
|
338
338
|
return {
|
|
339
339
|
directory,
|
|
340
340
|
agent,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["/** Non-interactive package installation. Provide platform services at the application boundary. */\nimport {\n\tArray,\n\tCause,\n\tData,\n\tRef,\n\tEffect,\n\tExit,\n\tFileSystem,\n\tHashMap,\n\tMatch,\n\tOption,\n\tPath,\n\tPlatformError,\n\tRecord,\n\tSchema,\n\tSemaphore,\n\tString,\n\tStream,\n} from \"effect\"\nimport { ChildProcess, ChildProcessSpawner } from \"effect/process\"\nimport {\n\tPackageManagerDeclaration,\n\tPackageManagerName,\n} from \"./PackageManagerDeclaration\"\nexport { PackageManagerDeclaration } from \"./PackageManagerDeclaration\"\nimport { terminateProcess } from \"./process\"\nimport {\n\twriteSnapshot,\n\twithSnapshot,\n\ttype SnapshotCleanupFailure,\n\tinspectManagedFiles,\n\tUnsupportedFileTypeError,\n\ttype Contents,\n} from \"./snapshot\"\nexport {\n\tUnsupportedFileTypeError,\n\ttype SnapshotCleanupFailure,\n} from \"./snapshot\"\nimport {\n\tapplyChanges,\n\tChanges,\n\tdecodeManifest,\n\tencodeManifest,\n\tfields,\n\ttype Manifest,\n} from \"./manifest\"\nexport { Changes, PackageName } from \"./manifest\"\n\nexport const Agent = Schema.Literals([\n\t\"npm\",\n\t\"pnpm\",\n\t\"yarn-classic\",\n\t\"yarn-berry\",\n])\nexport type Agent = typeof Agent.Type\nexport class PackageManagerError extends Schema.TaggedError<\n\tPackageManagerError,\n\t{ readonly brand: unique symbol }\n>()(\"PackageManagerError\", {\n\toperation: Schema.Literals([\"detect\", \"install\", \"recover\", \"state\"]),\n\tmessage: Schema.String,\n\texitCode: Schema.optionalKey(Schema.Int),\n}) {}\nexport class RecoveryRequiredError extends Schema.TaggedError<\n\tRecoveryRequiredError,\n\t{ readonly brand: unique symbol }\n>()(\"RecoveryRequiredError\", {\n\tdirectory: Schema.String,\n\tsnapshotDirectory: Schema.String,\n\toperation: Schema.Literals([\"install\", \"recover\", \"rollback\"]),\n\tprocessId: Schema.optionalKey(Schema.Int),\n\tmessage: Schema.String,\n\tterminationCause: Schema.optionalKey(\n\t\tSchema.Cause(Schema.Unknown, Schema.Unknown),\n\t),\n\trecoveryCause: Schema.Cause(Schema.Unknown, Schema.Unknown),\n}) {}\n\ntype WriterState = Data.TaggedEnum<{\n\tManagedWritersIdle: {}\n\tRunning: {}\n\tFileWrite: {}\n\tUnconfirmed: {\n\t\treadonly operation: \"install\" | \"recover\"\n\t\treadonly processId: number\n\t\treadonly cause: Cause.Cause<PlatformError.PlatformError>\n\t}\n}>\n\nconst WriterState = Data.taggedEnum<WriterState>()\n\ntype ManagerState = Data.TaggedEnum<{\n\tReady: {}\n\tActive: { readonly writer: WriterState }\n\tRecoveryRequired: { readonly error: RecoveryRequiredError }\n}>\n\nconst ManagerState = Data.taggedEnum<ManagerState>()\n\nexport type Error =\n\t| PackageManagerError\n\t| UnsupportedFileTypeError\n\t| RecoveryRequiredError\n\t| PlatformError.PlatformError\n\t| Schema.SchemaError\nexport interface Options {\n\t/** Project directory, containing package.json. Use the workspace root for workspace installs. */\n\treadonly directory: string\n\t/** Explicit selection takes precedence over detection. */\n\treadonly agent?: Agent\n\t/** Optional npm_config_user_agent supplied by the caller; used only as a fallback. */\n\treadonly userAgent?: string\n}\nconst lockfiles = [\n\t\"package-lock.json\",\n\t\"npm-shrinkwrap.json\",\n\t\"pnpm-lock.yaml\",\n\t\"yarn.lock\",\n] as const\nconst backupFiles = [\n\t\"package.json\",\n\t...lockfiles,\n\t\".npmrc\",\n\t\"pnpm-workspace.yaml\",\n\t\".yarnrc\",\n\t\".yarnrc.yml\",\n\t\".yarn/install-state.gz\",\n\t\".pnp.cjs\",\n\t\".pnp.loader.mjs\",\n]\nconst strategy = (agent: Agent, operation: \"install\" | \"recover\") => {\n\tconst recovery = operation === \"recover\"\n\treturn Match.value(agent).pipe(\n\t\tMatch.when(\"npm\", () => ({\n\t\t\texecutable: \"npm\",\n\t\t\targs: [\n\t\t\t\t\"install\",\n\t\t\t\t\"--no-audit\",\n\t\t\t\t\"--no-fund\",\n\t\t\t\t...(recovery ? [\"--prefer-offline\"] : []),\n\t\t\t],\n\t\t\tenv: { CI: \"true\" },\n\t\t\tlockfiles: [\"package-lock.json\", \"npm-shrinkwrap.json\"],\n\t\t})),\n\t\tMatch.when(\"pnpm\", () => ({\n\t\t\texecutable: \"pnpm\",\n\t\t\targs: [\n\t\t\t\t\"install\",\n\t\t\t\trecovery ? \"--frozen-lockfile\" : \"--no-frozen-lockfile\",\n\t\t\t\t\"--config.confirmModulesPurge=false\",\n\t\t\t],\n\t\t\tenv: { CI: \"true\" },\n\t\t\tlockfiles: [\"pnpm-lock.yaml\"],\n\t\t})),\n\t\tMatch.when(\"yarn-classic\", () => ({\n\t\t\texecutable: \"yarn\",\n\t\t\targs: [\n\t\t\t\t\"install\",\n\t\t\t\t\"--non-interactive\",\n\t\t\t\t...(recovery ? [\"--frozen-lockfile\", \"--prefer-offline\"] : []),\n\t\t\t],\n\t\t\tenv: { CI: \"true\" },\n\t\t\tlockfiles: [\"yarn.lock\"],\n\t\t})),\n\t\tMatch.when(\"yarn-berry\", () => ({\n\t\t\texecutable: \"yarn\",\n\t\t\targs: [\"install\", ...(recovery ? [\"--immutable\"] : [])],\n\t\t\tenv: {\n\t\t\t\tCI: \"true\",\n\t\t\t\tYARN_ENABLE_IMMUTABLE_INSTALLS: recovery ? \"true\" : \"false\",\n\t\t\t\tYARN_NODE_LINKER: \"node-modules\",\n\t\t\t},\n\t\t\tlockfiles: [\"yarn.lock\"],\n\t\t})),\n\t\tMatch.exhaustive,\n\t)\n}\n\nconst readSource = Effect.fnUntraced(function* (\n\tfs: FileSystem.FileSystem,\n\tfile: string,\n) {\n\tconst bytes = yield* fs.readFile(file)\n\treturn yield* Effect.try({\n\t\ttry: () =>\n\t\t\tnew TextDecoder(\"utf-8\", { ignoreBOM: true, fatal: true }).decode(bytes),\n\t\tcatch: (cause) =>\n\t\t\tPlatformError.badArgument({\n\t\t\t\tmodule: \"FileSystem\",\n\t\t\t\tmethod: \"readManifest\",\n\t\t\t\tdescription: \"The manifest must contain valid UTF-8\",\n\t\t\t\tcause,\n\t\t\t}),\n\t})\n})\n\nconst Executable = PackageManagerName\nconst Digits = Schema.String.check(Schema.isPattern(/^\\d+$/))\nconst decodePackageManager = Schema.decodeUnknownOption(\n\tPackageManagerDeclaration,\n)\n/** `name/version ...`, as package managers set `npm_config_user_agent`. */\nconst decodeUserAgent = Schema.decodeUnknownOption(\n\tSchema.TemplateLiteralParser([Executable, \"/\", Digits, Schema.String]),\n)\nconst agentOf = (name: typeof Executable.Type, major: string): Agent =>\n\tname === \"yarn\" ? (major === \"1\" ? \"yarn-classic\" : \"yarn-berry\") : name\n\n/** packageManager, then lockfile, then caller user-agent, then npm. Conflicting lockfiles fail. */\nexport const detectAgent = Effect.fnUntraced(function* (options: Options) {\n\tif (options.agent !== undefined)\n\t\treturn yield* Schema.decodeEffect(Agent)(options.agent)\n\tconst fs = yield* FileSystem.FileSystem\n\tconst path = yield* Path.Path\n\tconst manifest = yield* readSource(\n\t\tfs,\n\t\tpath.join(options.directory, \"package.json\"),\n\t).pipe(Effect.flatMap(decodeManifest))\n\tif (manifest.packageManager !== undefined) {\n\t\treturn yield* Option.match(decodePackageManager(manifest.packageManager), {\n\t\t\tonNone: () =>\n\t\t\t\tPackageManagerError.make({\n\t\t\t\t\toperation: \"detect\",\n\t\t\t\t\tmessage: `Unsupported packageManager: ${manifest.packageManager}`,\n\t\t\t\t}),\n\t\t\tonSome: ([name, , version]) =>\n\t\t\t\tEffect.succeed(agentOf(name, `${version.major}`)),\n\t\t})\n\t}\n\tconst found = yield* Effect.filter(lockfiles, (file) =>\n\t\tfs.exists(path.join(options.directory, file)),\n\t)\n\tconst kinds = Array.dedupe(\n\t\tArray.map(found, (file) =>\n\t\t\tfile === \"pnpm-lock.yaml\"\n\t\t\t\t? \"pnpm\"\n\t\t\t\t: file === \"yarn.lock\"\n\t\t\t\t\t? \"yarn\"\n\t\t\t\t\t: \"npm\",\n\t\t),\n\t)\n\tif (kinds.length > 1)\n\t\treturn yield* PackageManagerError.make({\n\t\t\toperation: \"detect\",\n\t\t\tmessage: \"Conflicting lockfiles; select an agent explicitly\",\n\t\t})\n\tif (kinds[0] === \"yarn\") {\n\t\tconst metadata = yield* fs\n\t\t\t.stream(path.join(options.directory, \"yarn.lock\"))\n\t\t\t.pipe(\n\t\t\t\tStream.decodeText(),\n\t\t\t\tStream.mapAccum(\n\t\t\t\t\t// The initial newline models the start of the file.\n\t\t\t\t\t() => \"\\n\",\n\t\t\t\t\t(tail, chunk) => {\n\t\t\t\t\t\tconst text = tail + chunk\n\t\t\t\t\t\t// Preserve all ECMAScript line terminators, including across chunk boundaries.\n\t\t\t\t\t\t// Eleven characters retain a partial marker and its preceding character.\n\t\t\t\t\t\treturn [\n\t\t\t\t\t\t\tString.slice(-11)(text),\n\t\t\t\t\t\t\t[/[\\n\\r\\u2028\\u2029]__metadata:/.test(text)],\n\t\t\t\t\t\t]\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t\tStream.filter((found) => found),\n\t\t\t\tStream.runHead,\n\t\t\t)\n\t\treturn Option.isSome(metadata) ? \"yarn-berry\" : \"yarn-classic\"\n\t}\n\tif (kinds[0] !== undefined) return kinds[0]\n\treturn Option.match(decodeUserAgent(options.userAgent ?? \"\"), {\n\t\tonNone: () => \"npm\" as const,\n\t\tonSome: ([name, , major]) => agentOf(name, major),\n\t})\n})\n\nexport interface PackageState {\n\treadonly spec: string\n\t/** Missing optional or not-yet-installed dependencies have no version/path. */\n\treadonly version: string | undefined\n\treadonly path: string | undefined\n}\n/** Direct dependencies only, keyed by their manifest names (including aliases). */\nexport type State = HashMap.HashMap<string, PackageState>\nexport interface PackageChange {\n\treadonly name: string\n\treadonly before: PackageState | undefined\n\treadonly after: PackageState | undefined\n}\nexport interface StateDiff {\n\treadonly added: ReadonlyArray<PackageChange>\n\treadonly removed: ReadonlyArray<PackageChange>\n\treadonly updated: ReadonlyArray<PackageChange>\n}\nexport const diffState = (before: State, after: State): StateDiff => {\n\tconst changes = Array.map(\n\t\tArray.sort(\n\t\t\tArray.dedupe([...HashMap.keys(before), ...HashMap.keys(after)]),\n\t\t\tString.Order,\n\t\t),\n\t\t(name) => ({\n\t\t\tname,\n\t\t\tbefore: Option.getOrUndefined(HashMap.get(before, name)),\n\t\t\tafter: Option.getOrUndefined(HashMap.get(after, name)),\n\t\t}),\n\t)\n\treturn {\n\t\tadded: Array.filter(changes, (change) => change.before === undefined),\n\t\tremoved: Array.filter(changes, (change) => change.after === undefined),\n\t\tupdated: Array.filter(\n\t\t\tchanges,\n\t\t\t({ before, after }) =>\n\t\t\t\tbefore !== undefined &&\n\t\t\t\tafter !== undefined &&\n\t\t\t\t(before.spec !== after.spec ||\n\t\t\t\t\tbefore.version !== after.version ||\n\t\t\t\t\tbefore.path !== after.path),\n\t\t),\n\t}\n}\nexport interface InstallResult extends StateDiff {\n\treadonly agent: Agent\n\t/** The install committed; retry only snapshot deletion, not installation. */\n\treadonly snapshotCleanupFailure: Option.Option<SnapshotCleanupFailure>\n}\nexport interface PackageManager {\n\treadonly directory: string\n\treadonly agent: Agent\n\treadonly readState: Effect.Effect<State, Error>\n\t/** Present when manual recovery is required; the manager cannot be reused. */\n\treadonly recoveryRequired: Effect.Effect<Option.Option<RecoveryRequiredError>>\n\t/** Serialized per manager. Empty changes installs the current manifest, including first installs. */\n\treadonly install: (changes: Changes) => Effect.Effect<InstallResult, Error>\n}\n\n/**\n * Own one manager per project. The caller must give this manager and its child\n * processes exclusive write access to the project for each install, including\n * recovery. Do not edit project files or run another installer concurrently.\n * Calls are serialized only within this manager; no cross-process lock is taken.\n * Managed paths must be regular files or absent. make and install reject symbolic\n * links and other file types with UnsupportedFileTypeError before any write.\n *\n * Accepted file writes finish before cancellation can start rollback or release\n * the lock. File writes and the original process group participate in the transaction.\n * Scripts must not create escaped writers in independent groups or daemons.\n * Failed or interrupted installs before commit restore backed-up package files byte-for-byte\n * with recorded permissions and ownership, and remove tracked files that did not\n * exist before installation. Writes made\n * during installation are overwritten by rollback, never merged. When a lockfile\n * existed, recovery also attempts to repair node_modules. Recovery failures are\n * combined with the original cause. If process termination cannot be confirmed,\n * no further restoration or recovery runs. Any failed rollback step also requires\n * manual recovery, even if all writers stopped. Only successful installation or\n * complete rollback permits snapshot deletion and reuse. RecoveryRequiredError\n * and recoveryRequired expose the retained snapshot directory and complete cause;\n * this manager rejects\n * all later installs. Complete manual recovery before making a new manager.\n * Committed installs report snapshot deletion failures in snapshotCleanupFailure\n * rather than failing or rolling back. No crash recovery is promised.\n */\nexport const make = (\n\toptions: Options,\n): Effect.Effect<\n\tPackageManager,\n\tError,\n\tFileSystem.FileSystem | Path.Path | ChildProcessSpawner.ChildProcessSpawner\n> =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem.FileSystem\n\t\tconst path = yield* Path.Path\n\t\tconst spawner = yield* ChildProcessSpawner.ChildProcessSpawner\n\t\tconst directory = path.resolve(options.directory)\n\t\tyield* inspectManagedFiles(fs, path, directory, backupFiles)\n\t\tconst agent = yield* detectAgent({ ...options, directory })\n\t\tconst lock = yield* Semaphore.make(1)\n\t\tconst transaction = yield* Ref.make<ManagerState>(ManagerState.Ready())\n\t\tconst manifestFile = path.join(directory, \"package.json\")\n\t\tconst readManifest = readSource(fs, manifestFile).pipe(\n\t\t\tEffect.flatMap(decodeManifest),\n\t\t)\n\t\tconst installed = Schema.decodeUnknownEffect(\n\t\t\tSchema.fromJsonString(\n\t\t\t\tSchema.Struct({ version: Schema.optionalKey(Schema.String) }),\n\t\t\t),\n\t\t)\n\t\tconst stateOf = Effect.fnUntraced(function* (manifest: Manifest) {\n\t\t\tif (yield* fs.exists(path.join(directory, \".pnp.cjs\")))\n\t\t\t\treturn yield* PackageManagerError.make({\n\t\t\t\t\toperation: \"state\",\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t\"Plug'n'Play is not supported; configure Yarn with nodeLinker: node-modules\",\n\t\t\t\t})\n\t\t\tconst deps = Array.reduce(\n\t\t\t\tfields,\n\t\t\t\t{} as Readonly<Record<string, string>>,\n\t\t\t\t(all, field) => ({ ...all, ...manifest[field] }),\n\t\t\t)\n\t\t\tconst entries = yield* Effect.forEach(\n\t\t\t\tRecord.toEntries(deps),\n\t\t\t\t([name, spec]) =>\n\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\tconst file = path.join(\n\t\t\t\t\t\t\tdirectory,\n\t\t\t\t\t\t\t\"node_modules\",\n\t\t\t\t\t\t\tname,\n\t\t\t\t\t\t\t\"package.json\",\n\t\t\t\t\t\t)\n\t\t\t\t\t\tconst source = yield* fs.readFileString(file).pipe(\n\t\t\t\t\t\t\tEffect.asSome,\n\t\t\t\t\t\t\tEffect.catchCauseIf(\n\t\t\t\t\t\t\t\t(cause) =>\n\t\t\t\t\t\t\t\t\tArray.every(\n\t\t\t\t\t\t\t\t\t\tcause.reasons,\n\t\t\t\t\t\t\t\t\t\t(reason) =>\n\t\t\t\t\t\t\t\t\t\t\tCause.isFailReason(reason) &&\n\t\t\t\t\t\t\t\t\t\t\treason.error.reason.module === \"FileSystem\" &&\n\t\t\t\t\t\t\t\t\t\t\treason.error.reason._tag === \"NotFound\",\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t() => Effect.succeedNone,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t)\n\t\t\t\t\t\tif (Option.isNone(source))\n\t\t\t\t\t\t\treturn [\n\t\t\t\t\t\t\t\tname,\n\t\t\t\t\t\t\t\t{ spec, version: undefined, path: undefined },\n\t\t\t\t\t\t\t] as const\n\t\t\t\t\t\tconst pkg = yield* installed(source.value)\n\t\t\t\t\t\treturn [\n\t\t\t\t\t\t\tname,\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tspec,\n\t\t\t\t\t\t\t\tversion: pkg.version,\n\t\t\t\t\t\t\t\tpath: yield* fs.realPath(path.dirname(file)),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t] as const\n\t\t\t\t\t}),\n\t\t\t\t{ concurrency: 16 },\n\t\t\t)\n\t\t\treturn HashMap.fromIterable<string, PackageState>(entries)\n\t\t})\n\t\tconst readState = Effect.flatMap(readManifest, stateOf)\n\t\tconst termination: ChildProcess.KillOptions = {\n\t\t\tforceKillAfter: \"5 seconds\",\n\t\t}\n\t\tconst run = Effect.fnUntraced(function* (operation: \"install\" | \"recover\") {\n\t\t\tconst command = strategy(agent, operation)\n\t\t\t// Acquire and install the release handler atomically, then wait interruptibly.\n\t\t\treturn yield* Effect.acquireUseRelease(\n\t\t\t\tspawner\n\t\t\t\t\t.spawn(\n\t\t\t\t\t\tChildProcess.make(command.executable, command.args, {\n\t\t\t\t\t\t\tcwd: directory,\n\t\t\t\t\t\t\textendEnv: true,\n\t\t\t\t\t\t\tstdin: \"ignore\",\n\t\t\t\t\t\t\tstdout: \"inherit\",\n\t\t\t\t\t\t\tstderr: \"inherit\",\n\t\t\t\t\t\t\tdetached: true,\n\t\t\t\t\t\t\t...termination,\n\t\t\t\t\t\t\tenv: command.env,\n\t\t\t\t\t\t}),\n\t\t\t\t\t)\n\t\t\t\t\t.pipe(\n\t\t\t\t\t\tEffect.tap(() =>\n\t\t\t\t\t\t\tRef.set(\n\t\t\t\t\t\t\t\ttransaction,\n\t\t\t\t\t\t\t\tManagerState.Active({ writer: WriterState.Running() }),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t(process) =>\n\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\tconst code = yield* process.exitCode.pipe(\n\t\t\t\t\t\t\tEffect.timeoutOrElse({\n\t\t\t\t\t\t\t\tduration: \"10 minutes\",\n\t\t\t\t\t\t\t\torElse: () =>\n\t\t\t\t\t\t\t\t\tPackageManagerError.make({\n\t\t\t\t\t\t\t\t\t\toperation,\n\t\t\t\t\t\t\t\t\t\tmessage: `${command.executable} ${operation} timed out`,\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t)\n\t\t\t\t\t\tif (code !== 0)\n\t\t\t\t\t\t\treturn yield* PackageManagerError.make({\n\t\t\t\t\t\t\t\toperation,\n\t\t\t\t\t\t\t\texitCode: code,\n\t\t\t\t\t\t\t\tmessage: `${command.executable} ${operation} exited with code ${code}`,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t}),\n\t\t\t\t// The command owns its group; a stopped leader may leave live descendants.\n\t\t\t\t// Observe kill failures here rather than relying on best-effort scope cleanup.\n\t\t\t\t(process) =>\n\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\tconst result = yield* terminateProcess(process, termination)\n\t\t\t\t\t\tyield* Ref.set(\n\t\t\t\t\t\t\ttransaction,\n\t\t\t\t\t\t\tManagerState.Active({\n\t\t\t\t\t\t\t\twriter:\n\t\t\t\t\t\t\t\t\tresult._tag === \"OriginalGroupStopped\"\n\t\t\t\t\t\t\t\t\t\t? WriterState.ManagedWritersIdle()\n\t\t\t\t\t\t\t\t\t\t: WriterState.Unconfirmed({\n\t\t\t\t\t\t\t\t\t\t\t\toperation,\n\t\t\t\t\t\t\t\t\t\t\t\tprocessId: process.pid,\n\t\t\t\t\t\t\t\t\t\t\t\tcause: result.cause,\n\t\t\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t)\n\t\t\t\t\t\tif (result._tag === \"Unconfirmed\")\n\t\t\t\t\t\t\treturn yield* Effect.failCause(result.cause)\n\t\t\t\t\t}),\n\t\t\t)\n\t\t}, Effect.scoped)\n\t\t// AbortSignal does not join accepted OS writes. Keep the writer owned until completion.\n\t\tconst withFileWriter = <A, E, R>(effect: Effect.Effect<A, E, R>) =>\n\t\t\tEffect.acquireUseRelease(\n\t\t\t\tRef.set(\n\t\t\t\t\ttransaction,\n\t\t\t\t\tManagerState.Active({ writer: WriterState.FileWrite() }),\n\t\t\t\t),\n\t\t\t\t() => effect,\n\t\t\t\t() =>\n\t\t\t\t\tRef.set(\n\t\t\t\t\t\ttransaction,\n\t\t\t\t\t\tManagerState.Active({ writer: WriterState.ManagedWritersIdle() }),\n\t\t\t\t\t),\n\t\t\t).pipe(Effect.uninterruptible)\n\n\t\tconst install = (changes: Changes) =>\n\t\t\tlock.withPermits(1)(\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\tconst state = yield* Ref.get(transaction)\n\t\t\t\t\tif (state._tag === \"RecoveryRequired\") return yield* state.error\n\t\t\t\t\tconst validated = yield* Schema.decodeEffect(Changes)(changes)\n\t\t\t\t\tconst inspected = yield* inspectManagedFiles(\n\t\t\t\t\t\tfs,\n\t\t\t\t\t\tpath,\n\t\t\t\t\t\tdirectory,\n\t\t\t\t\t\tbackupFiles,\n\t\t\t\t\t)\n\t\t\t\t\tconst backups = yield* Effect.forEach(\n\t\t\t\t\t\tinspected,\n\t\t\t\t\t\t({ name, file, metadata }) =>\n\t\t\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\t\t\tconst contents = yield* Option.match(metadata, {\n\t\t\t\t\t\t\t\t\tonNone: () => Effect.succeedNone,\n\t\t\t\t\t\t\t\t\tonSome: (info) =>\n\t\t\t\t\t\t\t\t\t\tfs\n\t\t\t\t\t\t\t\t\t\t\t.readFile(file)\n\t\t\t\t\t\t\t\t\t\t\t.pipe(\n\t\t\t\t\t\t\t\t\t\t\t\tEffect.map((bytes) =>\n\t\t\t\t\t\t\t\t\t\t\t\t\tOption.some<Contents>({ ...info, bytes }),\n\t\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\treturn { name, file, contents }\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t)\n\t\t\t\t\tconst original = yield* readSource(fs, manifestFile)\n\t\t\t\t\tconst manifest = yield* decodeManifest(original)\n\t\t\t\t\tconst before = yield* stateOf(manifest)\n\t\t\t\t\tconst text = yield* encodeManifest(\n\t\t\t\t\t\tapplyChanges(manifest, validated),\n\t\t\t\t\t\toriginal,\n\t\t\t\t\t)\n\n\t\t\t\t\tconst agentLocks = strategy(agent, \"install\").lockfiles\n\t\t\t\t\tconst hasLock = Array.some(\n\t\t\t\t\t\tbackups,\n\t\t\t\t\t\t({ name, contents }) =>\n\t\t\t\t\t\t\tArray.contains(agentLocks, name) && Option.isSome(contents),\n\t\t\t\t\t)\n\t\t\t\t\tconst completed = yield* withSnapshot(\n\t\t\t\t\t\tfs,\n\t\t\t\t\t\tRef.get(transaction).pipe(\n\t\t\t\t\t\t\tEffect.map((state) => state._tag === \"Ready\"),\n\t\t\t\t\t\t),\n\t\t\t\t\t\t(snapshotDirectory) =>\n\t\t\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\t\t\tconst snapshot = yield* writeSnapshot(\n\t\t\t\t\t\t\t\t\tfs,\n\t\t\t\t\t\t\t\t\tpath,\n\t\t\t\t\t\t\t\t\tsnapshotDirectory,\n\t\t\t\t\t\t\t\t\tdirectory,\n\t\t\t\t\t\t\t\t\tagent,\n\t\t\t\t\t\t\t\t\tbackups,\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\tconst restore = Effect.flatMap(Ref.get(transaction), (state) =>\n\t\t\t\t\t\t\t\t\tstate._tag === \"Active\" &&\n\t\t\t\t\t\t\t\t\tstate.writer._tag === \"ManagedWritersIdle\"\n\t\t\t\t\t\t\t\t\t\t? withFileWriter(snapshot.restore(state.writer))\n\t\t\t\t\t\t\t\t\t\t: Effect.void,\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\treturn yield* Effect.acquireUseRelease(\n\t\t\t\t\t\t\t\t\tRef.set(\n\t\t\t\t\t\t\t\t\t\ttransaction,\n\t\t\t\t\t\t\t\t\t\tManagerState.Active({\n\t\t\t\t\t\t\t\t\t\t\twriter: WriterState.ManagedWritersIdle(),\n\t\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t() =>\n\t\t\t\t\t\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\t\t\t\t\t\tyield* withFileWriter(\n\t\t\t\t\t\t\t\t\t\t\t\tfs.writeFileString(manifestFile, text),\n\t\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\t\tyield* run(\"install\")\n\t\t\t\t\t\t\t\t\t\t\tconst after = yield* readState\n\t\t\t\t\t\t\t\t\t\t\treturn { agent, ...diffState(before, after) }\n\t\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t\t(_, exit) =>\n\t\t\t\t\t\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\t\t\t\t\t\tif (Exit.isFailure(exit)) {\n\t\t\t\t\t\t\t\t\t\t\t\tconst state = yield* Ref.get(transaction)\n\t\t\t\t\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\t\t\t\t\tstate._tag !== \"Active\" ||\n\t\t\t\t\t\t\t\t\t\t\t\t\tstate.writer._tag !== \"ManagedWritersIdle\"\n\t\t\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t\t\t\t\t\tyield* withFileWriter(snapshot.restore(state.writer))\n\t\t\t\t\t\t\t\t\t\t\t\tif (hasLock)\n\t\t\t\t\t\t\t\t\t\t\t\t\tyield* run(\"recover\").pipe(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t// Even a failed recovery must restore files, once its writer has stopped.\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tEffect.onExit(() => restore),\n\t\t\t\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t// Idle managed writers alone do not prove that the transaction completed.\n\t\t\t\t\t\t\t\t\t\t\tyield* Ref.set(transaction, ManagerState.Ready())\n\t\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t).pipe(\n\t\t\t\t\t\t\t\t\tEffect.onExit((exit) =>\n\t\t\t\t\t\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\t\t\t\t\t\tconst state = yield* Ref.get(transaction)\n\t\t\t\t\t\t\t\t\t\t\tif (Exit.isSuccess(exit) || state._tag !== \"Active\")\n\t\t\t\t\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t\t\t\t\tconst writer = state.writer\n\t\t\t\t\t\t\t\t\t\t\tconst error = RecoveryRequiredError.make({\n\t\t\t\t\t\t\t\t\t\t\t\tdirectory,\n\t\t\t\t\t\t\t\t\t\t\t\tsnapshotDirectory,\n\t\t\t\t\t\t\t\t\t\t\t\toperation:\n\t\t\t\t\t\t\t\t\t\t\t\t\twriter._tag === \"Unconfirmed\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t? writer.operation\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t: \"rollback\",\n\t\t\t\t\t\t\t\t\t\t\t\t...(writer._tag === \"Unconfirmed\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tprocessId: writer.processId,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tterminationCause: writer.cause,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t\t\t\t\t\t\t\tmessage: `Manual recovery is required. Stop all writers before restoring the snapshot at ${snapshotDirectory}`,\n\t\t\t\t\t\t\t\t\t\t\t\trecoveryCause: exit.cause,\n\t\t\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t\t\tyield* Ref.set(\n\t\t\t\t\t\t\t\t\t\t\t\ttransaction,\n\t\t\t\t\t\t\t\t\t\t\t\tManagerState.RecoveryRequired({ error }),\n\t\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\t\treturn yield* error\n\t\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t)\n\t\t\t\t\treturn {\n\t\t\t\t\t\t...completed.value,\n\t\t\t\t\t\tsnapshotCleanupFailure: completed.snapshotCleanupFailure,\n\t\t\t\t\t}\n\t\t\t\t}),\n\t\t\t)\n\t\treturn {\n\t\t\tdirectory,\n\t\t\tagent,\n\t\t\trecoveryRequired: Ref.get(transaction).pipe(\n\t\t\t\tEffect.map((state) =>\n\t\t\t\t\tstate._tag === \"RecoveryRequired\"\n\t\t\t\t\t\t? Option.some(state.error)\n\t\t\t\t\t\t: Option.none(),\n\t\t\t\t),\n\t\t\t),\n\t\t\treadState: lock.withPermits(1)(readState),\n\t\t\tinstall,\n\t\t}\n\t})\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["/** Non-interactive package installation. Provide platform services at the application boundary. */\nimport {\n\tArray,\n\tCause,\n\tData,\n\tRef,\n\tEffect,\n\tExit,\n\tFileSystem,\n\tHashMap,\n\tMatch,\n\tOption,\n\tPath,\n\tPlatformError,\n\tRecord,\n\tSchema,\n\tSemaphore,\n\tString,\n\tStream,\n} from \"effect\"\nimport { ChildProcess, ChildProcessSpawner } from \"effect/process\"\nimport {\n\tPackageManagerDeclaration,\n\tPackageManagerName,\n} from \"./PackageManagerDeclaration\"\nexport { PackageManagerDeclaration } from \"./PackageManagerDeclaration\"\nimport { terminateProcess } from \"./process\"\nimport {\n\twriteSnapshot,\n\twithSnapshot,\n\ttype SnapshotCleanupFailure,\n\tinspectManagedFiles,\n\tUnsupportedFileTypeError,\n\ttype Contents,\n} from \"./snapshot\"\nexport {\n\tUnsupportedFileTypeError,\n\ttype SnapshotCleanupFailure,\n} from \"./snapshot\"\nimport {\n\tapplyChanges,\n\tChanges,\n\tdecodeManifest,\n\tencodeManifest,\n\tfields,\n\ttype Manifest,\n} from \"./manifest\"\nexport { Changes, PackageName } from \"./manifest\"\n\nexport const Agent = Schema.Literals([\n\t\"npm\",\n\t\"pnpm\",\n\t\"yarn-classic\",\n\t\"yarn-berry\",\n])\nexport type Agent = typeof Agent.Type\nexport class PackageManagerError extends Schema.TaggedError<\n\tPackageManagerError,\n\t{ readonly brand: unique symbol }\n>()(\"PackageManagerError\", {\n\toperation: Schema.Literals([\"detect\", \"install\", \"recover\", \"state\"]),\n\tmessage: Schema.String,\n\texitCode: Schema.optionalKey(Schema.Int),\n}) {}\nexport class RecoveryRequiredError extends Schema.TaggedError<\n\tRecoveryRequiredError,\n\t{ readonly brand: unique symbol }\n>()(\"RecoveryRequiredError\", {\n\tdirectory: Schema.String,\n\tsnapshotDirectory: Schema.String,\n\toperation: Schema.Literals([\"install\", \"recover\", \"rollback\"]),\n\tprocessId: Schema.optionalKey(Schema.Int),\n\tmessage: Schema.String,\n\tterminationCause: Schema.optionalKey(\n\t\tSchema.Cause(Schema.Unknown, Schema.Unknown),\n\t),\n\trecoveryCause: Schema.Cause(Schema.Unknown, Schema.Unknown),\n}) {}\n\ntype WriterState = Data.TaggedEnum<{\n\tManagedWritersIdle: {}\n\tRunning: {}\n\tFileWrite: {}\n\tUnconfirmed: {\n\t\treadonly operation: \"install\" | \"recover\"\n\t\treadonly processId: number\n\t\treadonly cause: Cause.Cause<PlatformError.PlatformError>\n\t}\n}>\n\nconst WriterState = Data.taggedEnum<WriterState>()\n\ntype ManagerState = Data.TaggedEnum<{\n\tReady: {}\n\tActive: { readonly writer: WriterState }\n\tRecoveryRequired: { readonly error: RecoveryRequiredError }\n}>\n\nconst ManagerState = Data.taggedEnum<ManagerState>()\n\nexport type Error =\n\t| PackageManagerError\n\t| UnsupportedFileTypeError\n\t| RecoveryRequiredError\n\t| PlatformError.PlatformError\n\t| Schema.SchemaError\nexport interface Options {\n\t/** Project directory, containing package.json. Use the workspace root for workspace installs. */\n\treadonly directory: string\n\t/** Explicit selection takes precedence over detection. */\n\treadonly agent?: Agent\n\t/** Optional npm_config_user_agent supplied by the caller; used only as a fallback. */\n\treadonly userAgent?: string\n}\nconst lockfiles = [\n\t\"package-lock.json\",\n\t\"npm-shrinkwrap.json\",\n\t\"pnpm-lock.yaml\",\n\t\"yarn.lock\",\n] as const\nconst backupFiles = [\n\t\"package.json\",\n\t...lockfiles,\n\t\".npmrc\",\n\t\"pnpm-workspace.yaml\",\n\t\".yarnrc\",\n\t\".yarnrc.yml\",\n\t\".yarn/install-state.gz\",\n\t\".pnp.cjs\",\n\t\".pnp.loader.mjs\",\n]\nconst strategy = (agent: Agent, operation: \"install\" | \"recover\") => {\n\tconst recovery = operation === \"recover\"\n\treturn Match.value(agent).pipe(\n\t\tMatch.when(\"npm\", () => ({\n\t\t\texecutable: \"npm\",\n\t\t\targs: [\n\t\t\t\t\"install\",\n\t\t\t\t\"--no-audit\",\n\t\t\t\t\"--no-fund\",\n\t\t\t\t...(recovery ? [\"--prefer-offline\"] : []),\n\t\t\t],\n\t\t\tenv: { CI: \"true\" },\n\t\t\tlockfiles: [\"package-lock.json\", \"npm-shrinkwrap.json\"],\n\t\t})),\n\t\tMatch.when(\"pnpm\", () => ({\n\t\t\texecutable: \"pnpm\",\n\t\t\targs: [\n\t\t\t\t\"install\",\n\t\t\t\trecovery ? \"--frozen-lockfile\" : \"--no-frozen-lockfile\",\n\t\t\t\t\"--config.confirmModulesPurge=false\",\n\t\t\t],\n\t\t\tenv: { CI: \"true\" },\n\t\t\tlockfiles: [\"pnpm-lock.yaml\"],\n\t\t})),\n\t\tMatch.when(\"yarn-classic\", () => ({\n\t\t\texecutable: \"yarn\",\n\t\t\targs: [\n\t\t\t\t\"install\",\n\t\t\t\t\"--non-interactive\",\n\t\t\t\t...(recovery ? [\"--frozen-lockfile\", \"--prefer-offline\"] : []),\n\t\t\t],\n\t\t\tenv: { CI: \"true\" },\n\t\t\tlockfiles: [\"yarn.lock\"],\n\t\t})),\n\t\tMatch.when(\"yarn-berry\", () => ({\n\t\t\texecutable: \"yarn\",\n\t\t\targs: [\"install\", ...(recovery ? [\"--immutable\"] : [])],\n\t\t\tenv: {\n\t\t\t\tCI: \"true\",\n\t\t\t\tYARN_ENABLE_IMMUTABLE_INSTALLS: recovery ? \"true\" : \"false\",\n\t\t\t\tYARN_NODE_LINKER: \"node-modules\",\n\t\t\t},\n\t\t\tlockfiles: [\"yarn.lock\"],\n\t\t})),\n\t\tMatch.exhaustive,\n\t)\n}\n\nconst readSource = Effect.fnUntraced(function* (\n\tfs: FileSystem.FileSystem,\n\tfile: string,\n) {\n\tconst bytes = yield* fs.readFile(file)\n\treturn yield* Effect.try({\n\t\ttry: () =>\n\t\t\tnew TextDecoder(\"utf-8\", { ignoreBOM: true, fatal: true }).decode(bytes),\n\t\tcatch: (cause) =>\n\t\t\tPlatformError.badArgument({\n\t\t\t\tmodule: \"FileSystem\",\n\t\t\t\tmethod: \"readManifest\",\n\t\t\t\tdescription: \"The manifest must contain valid UTF-8\",\n\t\t\t\tcause,\n\t\t\t}),\n\t})\n})\n\nconst Executable = PackageManagerName\nconst Digits = Schema.String.check(Schema.isPattern(/^\\d+$/))\nconst decodePackageManager = Schema.decodeUnknownEffect(\n\tPackageManagerDeclaration,\n)\n/** `name/version ...`, as package managers set `npm_config_user_agent`. */\nconst decodeUserAgent = Schema.decodeUnknownOption(\n\tSchema.TemplateLiteralParser([Executable, \"/\", Digits, Schema.String]),\n)\nconst agentOf = (name: typeof Executable.Type, major: string): Agent =>\n\tname === \"yarn\" ? (major === \"1\" ? \"yarn-classic\" : \"yarn-berry\") : name\n\n/** packageManager, then lockfile, then caller user-agent, then npm. Conflicting lockfiles fail. */\nexport const detectAgent = Effect.fnUntraced(function* (options: Options) {\n\tif (options.agent !== undefined)\n\t\treturn yield* Schema.decodeEffect(Agent)(options.agent)\n\tconst fs = yield* FileSystem.FileSystem\n\tconst path = yield* Path.Path\n\tconst manifest = yield* readSource(\n\t\tfs,\n\t\tpath.join(options.directory, \"package.json\"),\n\t).pipe(Effect.flatMap(decodeManifest))\n\tif (manifest.packageManager !== undefined) {\n\t\tconst [name, , version] = yield* decodePackageManager(\n\t\t\tmanifest.packageManager,\n\t\t).pipe(\n\t\t\t// Keep the schema issue: it names the constraint the declaration failed.\n\t\t\tEffect.mapError((error) =>\n\t\t\t\tPackageManagerError.make({\n\t\t\t\t\toperation: \"detect\",\n\t\t\t\t\tmessage: `Unsupported packageManager: ${manifest.packageManager}\\n${error.message}`,\n\t\t\t\t}),\n\t\t\t),\n\t\t)\n\t\treturn agentOf(name, `${version.major}`)\n\t}\n\tconst found = yield* Effect.filter(lockfiles, (file) =>\n\t\tfs.exists(path.join(options.directory, file)),\n\t)\n\tconst kinds = Array.dedupe(\n\t\tArray.map(found, (file) =>\n\t\t\tfile === \"pnpm-lock.yaml\"\n\t\t\t\t? \"pnpm\"\n\t\t\t\t: file === \"yarn.lock\"\n\t\t\t\t\t? \"yarn\"\n\t\t\t\t\t: \"npm\",\n\t\t),\n\t)\n\tif (kinds.length > 1)\n\t\treturn yield* PackageManagerError.make({\n\t\t\toperation: \"detect\",\n\t\t\tmessage: \"Conflicting lockfiles; select an agent explicitly\",\n\t\t})\n\tif (kinds[0] === \"yarn\") {\n\t\tconst metadata = yield* fs\n\t\t\t.stream(path.join(options.directory, \"yarn.lock\"))\n\t\t\t.pipe(\n\t\t\t\tStream.decodeText(),\n\t\t\t\tStream.mapAccum(\n\t\t\t\t\t// The initial newline models the start of the file.\n\t\t\t\t\t() => \"\\n\",\n\t\t\t\t\t(tail, chunk) => {\n\t\t\t\t\t\tconst text = tail + chunk\n\t\t\t\t\t\t// Preserve all ECMAScript line terminators, including across chunk boundaries.\n\t\t\t\t\t\t// Eleven characters retain a partial marker and its preceding character.\n\t\t\t\t\t\treturn [\n\t\t\t\t\t\t\tString.slice(-11)(text),\n\t\t\t\t\t\t\t[/[\\n\\r\\u2028\\u2029]__metadata:/.test(text)],\n\t\t\t\t\t\t]\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t\tStream.filter((found) => found),\n\t\t\t\tStream.runHead,\n\t\t\t)\n\t\treturn Option.isSome(metadata) ? \"yarn-berry\" : \"yarn-classic\"\n\t}\n\tif (kinds[0] !== undefined) return kinds[0]\n\treturn Option.match(decodeUserAgent(options.userAgent ?? \"\"), {\n\t\tonNone: () => \"npm\" as const,\n\t\tonSome: ([name, , major]) => agentOf(name, major),\n\t})\n})\n\nexport interface PackageState {\n\treadonly spec: string\n\t/** Missing optional or not-yet-installed dependencies have no version/path. */\n\treadonly version: string | undefined\n\treadonly path: string | undefined\n}\n/** Direct dependencies only, keyed by their manifest names (including aliases). */\nexport type State = HashMap.HashMap<string, PackageState>\nexport interface PackageChange {\n\treadonly name: string\n\treadonly before: PackageState | undefined\n\treadonly after: PackageState | undefined\n}\nexport interface StateDiff {\n\treadonly added: ReadonlyArray<PackageChange>\n\treadonly removed: ReadonlyArray<PackageChange>\n\treadonly updated: ReadonlyArray<PackageChange>\n}\nexport const diffState = (before: State, after: State): StateDiff => {\n\tconst changes = Array.map(\n\t\tArray.sort(\n\t\t\tArray.dedupe([...HashMap.keys(before), ...HashMap.keys(after)]),\n\t\t\tString.Order,\n\t\t),\n\t\t(name) => ({\n\t\t\tname,\n\t\t\tbefore: Option.getOrUndefined(HashMap.get(before, name)),\n\t\t\tafter: Option.getOrUndefined(HashMap.get(after, name)),\n\t\t}),\n\t)\n\treturn {\n\t\tadded: Array.filter(changes, (change) => change.before === undefined),\n\t\tremoved: Array.filter(changes, (change) => change.after === undefined),\n\t\tupdated: Array.filter(\n\t\t\tchanges,\n\t\t\t({ before, after }) =>\n\t\t\t\tbefore !== undefined &&\n\t\t\t\tafter !== undefined &&\n\t\t\t\t(before.spec !== after.spec ||\n\t\t\t\t\tbefore.version !== after.version ||\n\t\t\t\t\tbefore.path !== after.path),\n\t\t),\n\t}\n}\nexport interface InstallResult extends StateDiff {\n\treadonly agent: Agent\n\t/** The install committed; retry only snapshot deletion, not installation. */\n\treadonly snapshotCleanupFailure: Option.Option<SnapshotCleanupFailure>\n}\nexport interface PackageManager {\n\treadonly directory: string\n\treadonly agent: Agent\n\treadonly readState: Effect.Effect<State, Error>\n\t/** Present when manual recovery is required; the manager cannot be reused. */\n\treadonly recoveryRequired: Effect.Effect<Option.Option<RecoveryRequiredError>>\n\t/** Serialized per manager. Empty changes installs the current manifest, including first installs. */\n\treadonly install: (changes: Changes) => Effect.Effect<InstallResult, Error>\n}\n\n/**\n * Own one manager per project. The caller must give this manager and its child\n * processes exclusive write access to the project for each install, including\n * recovery. Do not edit project files or run another installer concurrently.\n * Calls are serialized only within this manager; no cross-process lock is taken.\n * Managed paths must be regular files or absent. make and install reject symbolic\n * links and other file types with UnsupportedFileTypeError before any write.\n *\n * Accepted file writes finish before cancellation can start rollback or release\n * the lock. File writes and the original process group participate in the transaction.\n * Scripts must not create escaped writers in independent groups or daemons.\n * Failed or interrupted installs before commit restore backed-up package files byte-for-byte\n * with recorded permissions and ownership, and remove tracked files that did not\n * exist before installation. Writes made\n * during installation are overwritten by rollback, never merged. When a lockfile\n * existed, recovery also attempts to repair node_modules. Recovery failures are\n * combined with the original cause. If process termination cannot be confirmed,\n * no further restoration or recovery runs. Any failed rollback step also requires\n * manual recovery, even if all writers stopped. Only successful installation or\n * complete rollback permits snapshot deletion and reuse. RecoveryRequiredError\n * and recoveryRequired expose the retained snapshot directory and complete cause;\n * this manager rejects\n * all later installs. Complete manual recovery before making a new manager.\n * Committed installs report snapshot deletion failures in snapshotCleanupFailure\n * rather than failing or rolling back. No crash recovery is promised.\n */\nexport const make = Effect.fnUntraced(function* (\n\toptions: Options,\n): Effect.fn.Return<\n\tPackageManager,\n\tError,\n\tFileSystem.FileSystem | Path.Path | ChildProcessSpawner.ChildProcessSpawner\n> {\n\tconst fs = yield* FileSystem.FileSystem\n\tconst path = yield* Path.Path\n\tconst spawner = yield* ChildProcessSpawner.ChildProcessSpawner\n\tconst directory = path.resolve(options.directory)\n\tyield* inspectManagedFiles(fs, path, directory, backupFiles)\n\tconst agent = yield* detectAgent({ ...options, directory })\n\tconst lock = yield* Semaphore.make(1)\n\tconst transaction = yield* Ref.make<ManagerState>(ManagerState.Ready())\n\tconst manifestFile = path.join(directory, \"package.json\")\n\tconst readManifest = readSource(fs, manifestFile).pipe(\n\t\tEffect.flatMap(decodeManifest),\n\t)\n\tconst installed = Schema.decodeUnknownEffect(\n\t\tSchema.fromJsonString(\n\t\t\tSchema.Struct({ version: Schema.optionalKey(Schema.String) }),\n\t\t),\n\t)\n\tconst stateOf = Effect.fnUntraced(function* (manifest: Manifest) {\n\t\tif (yield* fs.exists(path.join(directory, \".pnp.cjs\")))\n\t\t\treturn yield* PackageManagerError.make({\n\t\t\t\toperation: \"state\",\n\t\t\t\tmessage:\n\t\t\t\t\t\"Plug'n'Play is not supported; configure Yarn with nodeLinker: node-modules\",\n\t\t\t})\n\t\tconst deps = Array.reduce(\n\t\t\tfields,\n\t\t\t{} as Readonly<Record<string, string>>,\n\t\t\t(all, field) => ({ ...all, ...manifest[field] }),\n\t\t)\n\t\tconst entries = yield* Effect.forEach(\n\t\t\tRecord.toEntries(deps),\n\t\t\tEffect.fnUntraced(function* ([name, spec]) {\n\t\t\t\tconst file = path.join(directory, \"node_modules\", name, \"package.json\")\n\t\t\t\tconst source = yield* fs.readFileString(file).pipe(\n\t\t\t\t\tEffect.asSome,\n\t\t\t\t\tEffect.catchCauseIf(\n\t\t\t\t\t\t(cause) =>\n\t\t\t\t\t\t\tArray.every(\n\t\t\t\t\t\t\t\tcause.reasons,\n\t\t\t\t\t\t\t\t(reason) =>\n\t\t\t\t\t\t\t\t\tCause.isFailReason(reason) &&\n\t\t\t\t\t\t\t\t\treason.error.reason.module === \"FileSystem\" &&\n\t\t\t\t\t\t\t\t\treason.error.reason._tag === \"NotFound\",\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t() => Effect.succeedNone,\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t\tif (Option.isNone(source))\n\t\t\t\t\treturn [name, { spec, version: undefined, path: undefined }] as const\n\t\t\t\tconst pkg = yield* installed(source.value)\n\t\t\t\treturn [\n\t\t\t\t\tname,\n\t\t\t\t\t{\n\t\t\t\t\t\tspec,\n\t\t\t\t\t\tversion: pkg.version,\n\t\t\t\t\t\tpath: yield* fs.realPath(path.dirname(file)),\n\t\t\t\t\t},\n\t\t\t\t] as const\n\t\t\t}),\n\t\t\t{ concurrency: 16 },\n\t\t)\n\t\treturn HashMap.fromIterable<string, PackageState>(entries)\n\t})\n\tconst readState = Effect.flatMap(readManifest, stateOf)\n\tconst termination: ChildProcess.KillOptions = {\n\t\tforceKillAfter: \"5 seconds\",\n\t}\n\tconst run = Effect.fnUntraced(function* (operation: \"install\" | \"recover\") {\n\t\tconst command = strategy(agent, operation)\n\t\t// Acquire and install the release handler atomically, then wait interruptibly.\n\t\treturn yield* Effect.acquireUseRelease(\n\t\t\tspawner\n\t\t\t\t.spawn(\n\t\t\t\t\tChildProcess.make(command.executable, command.args, {\n\t\t\t\t\t\tcwd: directory,\n\t\t\t\t\t\textendEnv: true,\n\t\t\t\t\t\tstdin: \"ignore\",\n\t\t\t\t\t\tstdout: \"inherit\",\n\t\t\t\t\t\tstderr: \"inherit\",\n\t\t\t\t\t\tdetached: true,\n\t\t\t\t\t\t...termination,\n\t\t\t\t\t\tenv: command.env,\n\t\t\t\t\t}),\n\t\t\t\t)\n\t\t\t\t.pipe(\n\t\t\t\t\tEffect.tap(() =>\n\t\t\t\t\t\tRef.set(\n\t\t\t\t\t\t\ttransaction,\n\t\t\t\t\t\t\tManagerState.Active({ writer: WriterState.Running() }),\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\tEffect.fnUntraced(function* (process) {\n\t\t\t\tconst code = yield* process.exitCode.pipe(\n\t\t\t\t\tEffect.timeoutOrElse({\n\t\t\t\t\t\tduration: \"10 minutes\",\n\t\t\t\t\t\torElse: () =>\n\t\t\t\t\t\t\tPackageManagerError.make({\n\t\t\t\t\t\t\t\toperation,\n\t\t\t\t\t\t\t\tmessage: `${command.executable} ${operation} timed out`,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t}),\n\t\t\t\t)\n\t\t\t\tif (code !== 0)\n\t\t\t\t\treturn yield* PackageManagerError.make({\n\t\t\t\t\t\toperation,\n\t\t\t\t\t\texitCode: code,\n\t\t\t\t\t\tmessage: `${command.executable} ${operation} exited with code ${code}`,\n\t\t\t\t\t})\n\t\t\t}),\n\t\t\t// The command owns its group; a stopped leader may leave live descendants.\n\t\t\t// Observe kill failures here rather than relying on best-effort scope cleanup.\n\t\t\tEffect.fnUntraced(function* (process) {\n\t\t\t\tconst result = yield* terminateProcess(process, termination)\n\t\t\t\tyield* Ref.set(\n\t\t\t\t\ttransaction,\n\t\t\t\t\tManagerState.Active({\n\t\t\t\t\t\twriter:\n\t\t\t\t\t\t\tresult._tag === \"OriginalGroupStopped\"\n\t\t\t\t\t\t\t\t? WriterState.ManagedWritersIdle()\n\t\t\t\t\t\t\t\t: WriterState.Unconfirmed({\n\t\t\t\t\t\t\t\t\t\toperation,\n\t\t\t\t\t\t\t\t\t\tprocessId: process.pid,\n\t\t\t\t\t\t\t\t\t\tcause: result.cause,\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t}),\n\t\t\t\t)\n\t\t\t\tif (result._tag === \"Unconfirmed\")\n\t\t\t\t\treturn yield* Effect.failCause(result.cause)\n\t\t\t}),\n\t\t)\n\t}, Effect.scoped)\n\t// AbortSignal does not join accepted OS writes. Keep the writer owned until completion.\n\tconst withFileWriter = <A, E, R>(effect: Effect.Effect<A, E, R>) =>\n\t\tEffect.acquireUseRelease(\n\t\t\tRef.set(\n\t\t\t\ttransaction,\n\t\t\t\tManagerState.Active({ writer: WriterState.FileWrite() }),\n\t\t\t),\n\t\t\t() => effect,\n\t\t\t() =>\n\t\t\t\tRef.set(\n\t\t\t\t\ttransaction,\n\t\t\t\t\tManagerState.Active({ writer: WriterState.ManagedWritersIdle() }),\n\t\t\t\t),\n\t\t).pipe(Effect.uninterruptible)\n\n\tconst install = Effect.fnUntraced(function* (changes: Changes) {\n\t\tconst state = yield* Ref.get(transaction)\n\t\tif (state._tag === \"RecoveryRequired\") return yield* state.error\n\t\tconst validated = yield* Schema.decodeEffect(Changes)(changes)\n\t\tconst inspected = yield* inspectManagedFiles(\n\t\t\tfs,\n\t\t\tpath,\n\t\t\tdirectory,\n\t\t\tbackupFiles,\n\t\t)\n\t\tconst backups = yield* Effect.forEach(\n\t\t\tinspected,\n\t\t\tEffect.fnUntraced(function* ({ name, file, metadata }) {\n\t\t\t\tconst contents = yield* Option.match(metadata, {\n\t\t\t\t\tonNone: () => Effect.succeedNone,\n\t\t\t\t\tonSome: (info) =>\n\t\t\t\t\t\tfs\n\t\t\t\t\t\t\t.readFile(file)\n\t\t\t\t\t\t\t.pipe(\n\t\t\t\t\t\t\t\tEffect.map((bytes) =>\n\t\t\t\t\t\t\t\t\tOption.some<Contents>({ ...info, bytes }),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t),\n\t\t\t\t})\n\t\t\t\treturn { name, file, contents }\n\t\t\t}),\n\t\t)\n\t\tconst original = yield* readSource(fs, manifestFile)\n\t\tconst manifest = yield* decodeManifest(original)\n\t\tconst before = yield* stateOf(manifest)\n\t\tconst text = yield* encodeManifest(\n\t\t\tapplyChanges(manifest, validated),\n\t\t\toriginal,\n\t\t)\n\n\t\tconst agentLocks = strategy(agent, \"install\").lockfiles\n\t\tconst hasLock = Array.some(\n\t\t\tbackups,\n\t\t\t({ name, contents }) =>\n\t\t\t\tArray.contains(agentLocks, name) && Option.isSome(contents),\n\t\t)\n\t\tconst completed = yield* withSnapshot(\n\t\t\tfs,\n\t\t\tRef.get(transaction).pipe(Effect.map((state) => state._tag === \"Ready\")),\n\t\t\tEffect.fnUntraced(function* (snapshotDirectory) {\n\t\t\t\tconst snapshot = yield* writeSnapshot(\n\t\t\t\t\tfs,\n\t\t\t\t\tpath,\n\t\t\t\t\tsnapshotDirectory,\n\t\t\t\t\tdirectory,\n\t\t\t\t\tagent,\n\t\t\t\t\tbackups,\n\t\t\t\t)\n\t\t\t\tconst restore = Effect.flatMap(Ref.get(transaction), (state) =>\n\t\t\t\t\tstate._tag === \"Active\" && state.writer._tag === \"ManagedWritersIdle\"\n\t\t\t\t\t\t? withFileWriter(snapshot.restore(state.writer))\n\t\t\t\t\t\t: Effect.void,\n\t\t\t\t)\n\t\t\t\treturn yield* Effect.acquireUseRelease(\n\t\t\t\t\tRef.set(\n\t\t\t\t\t\ttransaction,\n\t\t\t\t\t\tManagerState.Active({\n\t\t\t\t\t\t\twriter: WriterState.ManagedWritersIdle(),\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t\tEffect.fnUntraced(function* () {\n\t\t\t\t\t\tyield* withFileWriter(fs.writeFileString(manifestFile, text))\n\t\t\t\t\t\tyield* run(\"install\")\n\t\t\t\t\t\tconst after = yield* readState\n\t\t\t\t\t\treturn { agent, ...diffState(before, after) }\n\t\t\t\t\t}),\n\t\t\t\t\tEffect.fnUntraced(function* (_, exit) {\n\t\t\t\t\t\tif (Exit.isFailure(exit)) {\n\t\t\t\t\t\t\tconst state = yield* Ref.get(transaction)\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\tstate._tag !== \"Active\" ||\n\t\t\t\t\t\t\t\tstate.writer._tag !== \"ManagedWritersIdle\"\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\tyield* withFileWriter(snapshot.restore(state.writer))\n\t\t\t\t\t\t\tif (hasLock)\n\t\t\t\t\t\t\t\tyield* run(\"recover\").pipe(\n\t\t\t\t\t\t\t\t\t// Even a failed recovery must restore files, once its writer has stopped.\n\t\t\t\t\t\t\t\t\tEffect.onExit(() => restore),\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Idle managed writers alone do not prove that the transaction completed.\n\t\t\t\t\t\tyield* Ref.set(transaction, ManagerState.Ready())\n\t\t\t\t\t}),\n\t\t\t\t).pipe(\n\t\t\t\t\tEffect.onExit(\n\t\t\t\t\t\tEffect.fnUntraced(function* (exit) {\n\t\t\t\t\t\t\tconst state = yield* Ref.get(transaction)\n\t\t\t\t\t\t\tif (Exit.isSuccess(exit) || state._tag !== \"Active\") return\n\t\t\t\t\t\t\tconst writer = state.writer\n\t\t\t\t\t\t\tconst error = RecoveryRequiredError.make({\n\t\t\t\t\t\t\t\tdirectory,\n\t\t\t\t\t\t\t\tsnapshotDirectory,\n\t\t\t\t\t\t\t\toperation:\n\t\t\t\t\t\t\t\t\twriter._tag === \"Unconfirmed\" ? writer.operation : \"rollback\",\n\t\t\t\t\t\t\t\t...(writer._tag === \"Unconfirmed\"\n\t\t\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t\t\tprocessId: writer.processId,\n\t\t\t\t\t\t\t\t\t\t\tterminationCause: writer.cause,\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t\t\t\tmessage: `Manual recovery is required. Stop all writers before restoring the snapshot at ${snapshotDirectory}`,\n\t\t\t\t\t\t\t\trecoveryCause: exit.cause,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\tyield* Ref.set(\n\t\t\t\t\t\t\t\ttransaction,\n\t\t\t\t\t\t\t\tManagerState.RecoveryRequired({ error }),\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\treturn yield* error\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t}),\n\t\t)\n\t\treturn {\n\t\t\t...completed.value,\n\t\t\tsnapshotCleanupFailure: completed.snapshotCleanupFailure,\n\t\t}\n\t}, lock.withPermits(1))\n\treturn {\n\t\tdirectory,\n\t\tagent,\n\t\trecoveryRequired: Ref.get(transaction).pipe(\n\t\t\tEffect.map((state) =>\n\t\t\t\tstate._tag === \"RecoveryRequired\"\n\t\t\t\t\t? Option.some(state.error)\n\t\t\t\t\t: Option.none(),\n\t\t\t),\n\t\t),\n\t\treadState: lock.withPermits(1)(readState),\n\t\tinstall,\n\t}\n})\n"],"mappings":"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"}
|
package/dist/process.js
CHANGED
|
@@ -19,7 +19,7 @@ const groupExists = (pid) => Effect.try({
|
|
|
19
19
|
})
|
|
20
20
|
}).pipe(Effect.catchIf((error) => missingGroup(error.cause) || unsignallableGroup(error.cause), (error) => Effect.succeed(!missingGroup(error.cause))));
|
|
21
21
|
/** Close the original group, including descendants that remain in it after leader exit. */
|
|
22
|
-
const closeProcess = (process, options, probe = groupExists)
|
|
22
|
+
const closeProcess = Effect.fnUntraced(function* (process, options, probe = groupExists) {
|
|
23
23
|
const killed = yield* Effect.suspend(() => process.kill(options)).pipe(Effect.exit);
|
|
24
24
|
if (NodeProcess.platform === "win32") return yield* killed;
|
|
25
25
|
const checked = yield* Effect.suspend(() => probe(process.pid)).pipe(Effect.exit, Effect.repeat({
|
package/dist/process.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process.js","names":[],"sources":["../src/process.ts"],"sourcesContent":["// The spawner exposes no process-group existence check after kill loses errno.\nimport * as NodeProcess from \"node:process\"\nimport {\n\tArray,\n\tCause,\n\tData,\n\tEffect,\n\tExit,\n\tPlatformError,\n\tSchedule,\n\tSchema,\n} from \"effect\"\nimport type { ChildProcess, ChildProcessSpawner } from \"effect/process\"\n\nconst missingGroup = Schema.is(Schema.Struct({ code: Schema.Literal(\"ESRCH\") }))\nconst unsignallableGroup = Schema.is(\n\tSchema.Struct({ code: Schema.Literal(\"EPERM\") }),\n)\n// Node's kill(false) fallback loses errno, but has a specific signal-failure shape.\nconst failedSignal = Schema.is(\n\tSchema.Struct({ message: Schema.Literal(\"Failed to kill child process\") }),\n)\nconst isSignalFailure = (error: PlatformError.PlatformError) =>\n\terror.reason.module === \"ChildProcess\" &&\n\terror.reason.method === \"kill\" &&\n\t(missingGroup(error.cause) ||\n\t\t(error.reason._tag === \"Unknown\" && failedSignal(error.cause)))\n\n/** Signal zero checks the group without signalling any member. */\nexport const groupExists = (pid: ChildProcessSpawner.ProcessId) =>\n\tEffect.try({\n\t\ttry: () => {\n\t\t\tNodeProcess.kill(-pid, 0)\n\t\t\treturn true\n\t\t},\n\t\tcatch: (cause) =>\n\t\t\tPlatformError.systemError({\n\t\t\t\t_tag: \"Unknown\",\n\t\t\t\tmodule: \"ChildProcess\",\n\t\t\t\tmethod: \"probeGroup\",\n\t\t\t\tcause,\n\t\t\t}),\n\t}).pipe(\n\t\tEffect.catchIf(\n\t\t\t(error) => missingGroup(error.cause) || unsignallableGroup(error.cause),\n\t\t\t// EPERM can mean zombies on macOS. It never establishes group absence.\n\t\t\t(error) => Effect.succeed(!missingGroup(error.cause)),\n\t\t),\n\t)\n\n/** Close the original group, including descendants that remain in it after leader exit. */\nexport const closeProcess = (\n\tprocess: ChildProcessSpawner.ChildProcessHandle,\n\toptions: ChildProcess.KillOptions,\n\tprobe: typeof groupExists = groupExists,\n)
|
|
1
|
+
{"version":3,"file":"process.js","names":[],"sources":["../src/process.ts"],"sourcesContent":["// The spawner exposes no process-group existence check after kill loses errno.\nimport * as NodeProcess from \"node:process\"\nimport {\n\tArray,\n\tCause,\n\tData,\n\tEffect,\n\tExit,\n\tPlatformError,\n\tSchedule,\n\tSchema,\n} from \"effect\"\nimport type { ChildProcess, ChildProcessSpawner } from \"effect/process\"\n\nconst missingGroup = Schema.is(Schema.Struct({ code: Schema.Literal(\"ESRCH\") }))\nconst unsignallableGroup = Schema.is(\n\tSchema.Struct({ code: Schema.Literal(\"EPERM\") }),\n)\n// Node's kill(false) fallback loses errno, but has a specific signal-failure shape.\nconst failedSignal = Schema.is(\n\tSchema.Struct({ message: Schema.Literal(\"Failed to kill child process\") }),\n)\nconst isSignalFailure = (error: PlatformError.PlatformError) =>\n\terror.reason.module === \"ChildProcess\" &&\n\terror.reason.method === \"kill\" &&\n\t(missingGroup(error.cause) ||\n\t\t(error.reason._tag === \"Unknown\" && failedSignal(error.cause)))\n\n/** Signal zero checks the group without signalling any member. */\nexport const groupExists = (pid: ChildProcessSpawner.ProcessId) =>\n\tEffect.try({\n\t\ttry: () => {\n\t\t\tNodeProcess.kill(-pid, 0)\n\t\t\treturn true\n\t\t},\n\t\tcatch: (cause) =>\n\t\t\tPlatformError.systemError({\n\t\t\t\t_tag: \"Unknown\",\n\t\t\t\tmodule: \"ChildProcess\",\n\t\t\t\tmethod: \"probeGroup\",\n\t\t\t\tcause,\n\t\t\t}),\n\t}).pipe(\n\t\tEffect.catchIf(\n\t\t\t(error) => missingGroup(error.cause) || unsignallableGroup(error.cause),\n\t\t\t// EPERM can mean zombies on macOS. It never establishes group absence.\n\t\t\t(error) => Effect.succeed(!missingGroup(error.cause)),\n\t\t),\n\t)\n\n/** Close the original group, including descendants that remain in it after leader exit. */\nexport const closeProcess = Effect.fnUntraced(function* (\n\tprocess: ChildProcessSpawner.ChildProcessHandle,\n\toptions: ChildProcess.KillOptions,\n\tprobe: typeof groupExists = groupExists,\n) {\n\tconst killed = yield* Effect.suspend(() => process.kill(options)).pipe(\n\t\tEffect.exit,\n\t)\n\t// Windows retains its as-is adapter contract; POSIX requires group evidence.\n\tif (NodeProcess.platform === \"win32\") return yield* killed\n\tconst checked = yield* Effect.suspend(() => probe(process.pid)).pipe(\n\t\tEffect.exit,\n\t\tEffect.repeat({\n\t\t\twhile: (exit) => Exit.isSuccess(exit) && exit.value,\n\t\t\ttimes: 100,\n\t\t\tschedule: Schedule.spaced(\"20 millis\"),\n\t\t}),\n\t)\n\tif (Exit.isFailure(checked))\n\t\treturn yield* Effect.failCause(\n\t\t\tExit.isFailure(killed)\n\t\t\t\t? Cause.combine(killed.cause, checked.cause)\n\t\t\t\t: checked.cause,\n\t\t)\n\tif (checked.value) {\n\t\tif (Exit.isFailure(killed)) return yield* Effect.failCause(killed.cause)\n\t\treturn yield* PlatformError.systemError({\n\t\t\t_tag: \"Unknown\",\n\t\t\tmodule: \"ChildProcess\",\n\t\t\tmethod: \"confirmGroupStopped\",\n\t\t\tdescription: `Process group ${process.pid} still exists after the two-second confirmation window`,\n\t\t})\n\t}\n\tif (Exit.isFailure(killed)) {\n\t\t// Absence resolves only a failed signal to a nonexistent group, not other cleanup failures.\n\t\tconst remaining = Array.filter(\n\t\t\tkilled.cause.reasons,\n\t\t\t(reason) =>\n\t\t\t\t!(Cause.isFailReason(reason) && isSignalFailure(reason.error)),\n\t\t)\n\t\tif (remaining.length > 0)\n\t\t\treturn yield* Effect.failCause(Cause.fromReasons(remaining))\n\t}\n})\n\nexport type Termination = Data.TaggedEnum<{\n\tOriginalGroupStopped: {}\n\tUnconfirmed: { readonly cause: Cause.Cause<PlatformError.PlatformError> }\n}>\n\nexport const Termination = Data.taggedEnum<Termination>()\n\nexport type OriginalGroupStopped = Data.TaggedEnum.Value<\n\tTermination,\n\t\"OriginalGroupStopped\"\n>\n\n/** Only proves termination of the original group, not detached groups or daemons. */\nexport const terminateProcess = (\n\tprocess: ChildProcessSpawner.ChildProcessHandle,\n\toptions: ChildProcess.KillOptions,\n) =>\n\tEffect.suspend(() => closeProcess(process, options)).pipe(\n\t\tEffect.exit,\n\t\tEffect.map((exit): Termination =>\n\t\t\tExit.isSuccess(exit)\n\t\t\t\t? Termination.OriginalGroupStopped()\n\t\t\t\t: Termination.Unconfirmed({ cause: exit.cause }),\n\t\t),\n\t)\n"],"mappings":";;;AAcA,MAAM,eAAe,OAAO,GAAG,OAAO,OAAO,EAAE,MAAM,OAAO,QAAQ,OAAO,EAAE,CAAC,CAAC;AAC/E,MAAM,qBAAqB,OAAO,GACjC,OAAO,OAAO,EAAE,MAAM,OAAO,QAAQ,OAAO,EAAE,CAAC,CAChD;AAEA,MAAM,eAAe,OAAO,GAC3B,OAAO,OAAO,EAAE,SAAS,OAAO,QAAQ,8BAA8B,EAAE,CAAC,CAC1E;AACA,MAAM,mBAAmB,UACxB,MAAM,OAAO,WAAW,kBACxB,MAAM,OAAO,WAAW,WACvB,aAAa,MAAM,KAAK,KACvB,MAAM,OAAO,SAAS,aAAa,aAAa,MAAM,KAAK;;AAG9D,MAAa,eAAe,QAC3B,OAAO,IAAI;CACV,WAAW;EACV,YAAY,KAAK,CAAC,KAAK,CAAC;EACxB,OAAO;CACR;CACA,QAAQ,UACP,cAAc,YAAY;EACzB,MAAM;EACN,QAAQ;EACR,QAAQ;EACR;CACD,CAAC;AACH,CAAC,CAAC,CAAC,KACF,OAAO,SACL,UAAU,aAAa,MAAM,KAAK,KAAK,mBAAmB,MAAM,KAAK,IAErE,UAAU,OAAO,QAAQ,CAAC,aAAa,MAAM,KAAK,CAAC,CACrD,CACD;;AAGD,MAAa,eAAe,OAAO,WAAW,WAC7C,SACA,SACA,QAA4B,aAC3B;CACD,MAAM,SAAS,OAAO,OAAO,cAAc,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,KACjE,OAAO,IACR;CAEA,IAAI,YAAY,aAAa,SAAS,OAAO,OAAO;CACpD,MAAM,UAAU,OAAO,OAAO,cAAc,MAAM,QAAQ,GAAG,CAAC,CAAC,CAAC,KAC/D,OAAO,MACP,OAAO,OAAO;EACb,QAAQ,SAAS,KAAK,UAAU,IAAI,KAAK,KAAK;EAC9C,OAAO;EACP,UAAU,SAAS,OAAO,WAAW;CACtC,CAAC,CACF;CACA,IAAI,KAAK,UAAU,OAAO,GACzB,OAAO,OAAO,OAAO,UACpB,KAAK,UAAU,MAAM,IAClB,MAAM,QAAQ,OAAO,OAAO,QAAQ,KAAK,IACzC,QAAQ,KACZ;CACD,IAAI,QAAQ,OAAO;EAClB,IAAI,KAAK,UAAU,MAAM,GAAG,OAAO,OAAO,OAAO,UAAU,OAAO,KAAK;EACvE,OAAO,OAAO,cAAc,YAAY;GACvC,MAAM;GACN,QAAQ;GACR,QAAQ;GACR,aAAa,iBAAiB,QAAQ,IAAI;EAC3C,CAAC;CACF;CACA,IAAI,KAAK,UAAU,MAAM,GAAG;EAE3B,MAAM,YAAY,MAAM,OACvB,OAAO,MAAM,UACZ,WACA,EAAE,MAAM,aAAa,MAAM,KAAK,gBAAgB,OAAO,KAAK,EAC9D;EACA,IAAI,UAAU,SAAS,GACtB,OAAO,OAAO,OAAO,UAAU,MAAM,YAAY,SAAS,CAAC;CAC7D;AACD,CAAC;AAOD,MAAa,cAAc,KAAK,WAAwB;;AAQxD,MAAa,oBACZ,SACA,YAEA,OAAO,cAAc,aAAa,SAAS,OAAO,CAAC,CAAC,CAAC,KACpD,OAAO,MACP,OAAO,KAAK,SACX,KAAK,UAAU,IAAI,IAChB,YAAY,qBAAqB,IACjC,YAAY,YAAY,EAAE,OAAO,KAAK,MAAM,CAAC,CACjD,CACD"}
|
package/dist/snapshot.js
CHANGED
|
@@ -21,7 +21,7 @@ var UnsupportedFileTypeError = class extends Schema.TaggedError()("UnsupportedFi
|
|
|
21
21
|
}) {};
|
|
22
22
|
/** lstat distinguishes missing paths from links, including dangling links. */
|
|
23
23
|
const inspectManagedFiles = Effect.fnUntraced(function* (fs, path, directory, names) {
|
|
24
|
-
return yield* Effect.forEach(names,
|
|
24
|
+
return yield* Effect.forEach(names, Effect.fnUntraced(function* (name) {
|
|
25
25
|
const file = path.join(directory, name);
|
|
26
26
|
return {
|
|
27
27
|
name,
|
|
@@ -51,7 +51,7 @@ const encodeInventory = Schema.encodeEffect(Schema.fromJsonString(Schema.Struct(
|
|
|
51
51
|
}), { space: 2 }));
|
|
52
52
|
/** Persist every byte before allowing the installer to modify the project. */
|
|
53
53
|
const writeSnapshot = Effect.fnUntraced(function* (fs, path, snapshotDirectory, directory, agent, backups) {
|
|
54
|
-
const files = yield* Effect.forEach(backups,
|
|
54
|
+
const files = yield* Effect.forEach(backups, Effect.fnUntraced(function* (entry, index) {
|
|
55
55
|
if (Option.isNone(entry.contents)) return {
|
|
56
56
|
name: entry.name,
|
|
57
57
|
backup: null,
|
|
@@ -88,7 +88,7 @@ const restoreFile = Effect.fnUntraced(function* (fs, path, target, contents) {
|
|
|
88
88
|
yield* Effect.acquireUseRelease(Scope.make(), (scope) => Effect.acquireUseRelease(fs.open(temporary, {
|
|
89
89
|
flag: "wx",
|
|
90
90
|
mode: 384
|
|
91
|
-
}).pipe(Effect.provideService(Scope.Scope, scope)),
|
|
91
|
+
}).pipe(Effect.provideService(Scope.Scope, scope)), Effect.fnUntraced(function* (file) {
|
|
92
92
|
yield* file.writeAll(contents.bytes);
|
|
93
93
|
const current = yield* file.stat;
|
|
94
94
|
const { uid, gid } = contents;
|
package/dist/snapshot.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"snapshot.js","names":[],"sources":["../src/snapshot.ts"],"sourcesContent":["import {\n\tArray,\n\tCause,\n\tEffect,\n\tEqual,\n\tExit,\n\tOption,\n\tRandom,\n\tSchema,\n\tScope,\n} from \"effect\"\nimport type { FileSystem, Path, PlatformError } from \"effect\"\n/** File writes have completed and the original installer group is stopped or absent. */\nexport interface ManagedWritersIdle {\n\treadonly _tag: \"ManagedWritersIdle\"\n}\n\nexport interface SnapshotCleanupFailure {\n\treadonly snapshotDirectory: string\n\treadonly cause: Cause.Cause<PlatformError.PlatformError>\n}\n\n/** Separate the transaction's outcome from disposal of its snapshot. */\nexport const withSnapshot = <A, E, R>(\n\tfs: FileSystem.FileSystem,\n\tcanDelete: Effect.Effect<boolean>,\n\tuse: (directory: string) => Effect.Effect<A, E, R>,\n) =>\n\tEffect.uninterruptibleMask((resume) =>\n\t\tEffect.gen(function* () {\n\t\t\tconst snapshotDirectory = yield* fs.makeTempDirectory({\n\t\t\t\tprefix: \"graft-recovery-\",\n\t\t\t})\n\t\t\tconst outcome = yield* resume(\n\t\t\t\tEffect.suspend(() => use(snapshotDirectory)),\n\t\t\t).pipe(Effect.exit)\n\t\t\tconst cleanup = (yield* canDelete)\n\t\t\t\t? yield* Effect.suspend(() =>\n\t\t\t\t\t\tfs.remove(snapshotDirectory, { recursive: true }),\n\t\t\t\t\t).pipe(Effect.exit)\n\t\t\t\t: Exit.void\n\t\t\tif (Exit.isFailure(outcome)) {\n\t\t\t\treturn yield* Effect.failCause(\n\t\t\t\t\tExit.isFailure(cleanup)\n\t\t\t\t\t\t? Cause.combine(outcome.cause, cleanup.cause)\n\t\t\t\t\t\t: outcome.cause,\n\t\t\t\t)\n\t\t\t}\n\t\t\treturn yield* resume(\n\t\t\t\tEffect.succeed({\n\t\t\t\t\tvalue: outcome.value,\n\t\t\t\t\tsnapshotCleanupFailure: Exit.isFailure(cleanup)\n\t\t\t\t\t\t? Option.some<SnapshotCleanupFailure>({\n\t\t\t\t\t\t\t\tsnapshotDirectory,\n\t\t\t\t\t\t\t\tcause: cleanup.cause,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t: Option.none<SnapshotCleanupFailure>(),\n\t\t\t\t}),\n\t\t\t).pipe(\n\t\t\t\t// Cancellation can prevent delivery, but must not hide a cleanup failure.\n\t\t\t\tEffect.onExit((exit) =>\n\t\t\t\t\tExit.isFailure(exit) && Exit.isFailure(cleanup)\n\t\t\t\t\t\t? Effect.failCause(cleanup.cause)\n\t\t\t\t\t\t: Effect.void,\n\t\t\t\t),\n\t\t\t)\n\t\t}),\n\t)\n\nexport class UnsupportedFileTypeError extends Schema.TaggedError<\n\tUnsupportedFileTypeError,\n\t{ readonly brand: unique symbol }\n>()(\"UnsupportedFileTypeError\", {\n\tpath: Schema.String,\n\tfileType: Schema.String,\n\tmessage: Schema.String,\n}) {}\n\ninterface RegularFileMetadata {\n\treadonly mode: number\n\treadonly uid: Option.Option<number>\n\treadonly gid: Option.Option<number>\n}\nexport interface Contents extends RegularFileMetadata {\n\treadonly bytes: Uint8Array\n}\nexport interface Backup {\n\treadonly name: string\n\treadonly file: string\n\treadonly contents: Option.Option<Contents>\n}\n\n/** lstat distinguishes missing paths from links, including dangling links. */\nexport const inspectManagedFiles = Effect.fnUntraced(function* (\n\tfs: FileSystem.FileSystem,\n\tpath: Path.Path,\n\tdirectory: string,\n\tnames: ReadonlyArray<string>,\n) {\n\treturn yield* Effect.forEach(names, (name) =>\n\t\tEffect.gen(function* () {\n\t\t\tconst file = path.join(directory, name)\n\t\t\tconst metadata = yield* fs.lstat(file).pipe(\n\t\t\t\tEffect.flatMap((info) =>\n\t\t\t\t\tinfo.type === \"File\"\n\t\t\t\t\t\t? Effect.succeed<RegularFileMetadata>({\n\t\t\t\t\t\t\t\tmode: info.mode & 0o7777,\n\t\t\t\t\t\t\t\tuid: info.uid,\n\t\t\t\t\t\t\t\tgid: info.gid,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t: Effect.fail(\n\t\t\t\t\t\t\t\tUnsupportedFileTypeError.make({\n\t\t\t\t\t\t\t\t\tpath: file,\n\t\t\t\t\t\t\t\t\tfileType: info.type,\n\t\t\t\t\t\t\t\t\tmessage: `Managed paths must be regular files or absent: ${file} is ${info.type}`,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t\tEffect.asSome,\n\t\t\t\t// Only an entirely missing-path failure establishes absence.\n\t\t\t\tEffect.catchCauseIf(\n\t\t\t\t\t(cause) =>\n\t\t\t\t\t\tArray.every(\n\t\t\t\t\t\t\tcause.reasons,\n\t\t\t\t\t\t\t(reason) =>\n\t\t\t\t\t\t\t\tCause.isFailReason(reason) &&\n\t\t\t\t\t\t\t\treason.error._tag === \"PlatformError\" &&\n\t\t\t\t\t\t\t\treason.error.reason.module === \"FileSystem\" &&\n\t\t\t\t\t\t\t\treason.error.reason.method === \"lstat\" &&\n\t\t\t\t\t\t\t\treason.error.reason._tag === \"NotFound\",\n\t\t\t\t\t\t),\n\t\t\t\t\t() => Effect.succeedNone,\n\t\t\t\t),\n\t\t\t)\n\t\t\treturn { name, file, metadata }\n\t\t}),\n\t)\n})\n\nconst encodeInventory = Schema.encodeEffect(\n\tSchema.fromJsonString(\n\t\tSchema.Struct({\n\t\t\tdirectory: Schema.String,\n\t\t\tagent: Schema.String,\n\t\t\tfiles: Schema.Array(\n\t\t\t\tSchema.Struct({\n\t\t\t\t\tname: Schema.String,\n\t\t\t\t\tbackup: Schema.NullOr(Schema.String),\n\t\t\t\t\tmode: Schema.NullOr(Schema.Int),\n\t\t\t\t\tuid: Schema.NullOr(Schema.Int),\n\t\t\t\t\tgid: Schema.NullOr(Schema.Int),\n\t\t\t\t}),\n\t\t\t),\n\t\t}),\n\t\t{ space: 2 },\n\t),\n)\n\n/** Persist every byte before allowing the installer to modify the project. */\nexport const writeSnapshot = Effect.fnUntraced(function* (\n\tfs: FileSystem.FileSystem,\n\tpath: Path.Path,\n\tsnapshotDirectory: string,\n\tdirectory: string,\n\tagent: string,\n\tbackups: ReadonlyArray<Backup>,\n) {\n\tconst files = yield* Effect.forEach(backups, (entry, index) =>\n\t\tEffect.gen(function* () {\n\t\t\tif (Option.isNone(entry.contents))\n\t\t\t\treturn {\n\t\t\t\t\tname: entry.name,\n\t\t\t\t\tbackup: null,\n\t\t\t\t\tmode: null,\n\t\t\t\t\tuid: null,\n\t\t\t\t\tgid: null,\n\t\t\t\t}\n\t\t\tconst { bytes, mode, uid, gid } = entry.contents.value\n\t\t\tconst backup = `${index}.bin`\n\t\t\tyield* fs.writeFile(path.join(snapshotDirectory, backup), bytes, {\n\t\t\t\tmode: 0o600,\n\t\t\t})\n\t\t\treturn {\n\t\t\t\tname: entry.name,\n\t\t\t\tbackup,\n\t\t\t\tmode,\n\t\t\t\tuid: Option.getOrNull(uid),\n\t\t\t\tgid: Option.getOrNull(gid),\n\t\t\t}\n\t\t}),\n\t)\n\tconst inventory = yield* encodeInventory({ directory, agent, files })\n\tyield* fs.writeFileString(\n\t\tpath.join(snapshotDirectory, \"snapshot.json\"),\n\t\tinventory,\n\t\t{ mode: 0o600 },\n\t)\n\treturn {\n\t\t// The caller must establish that file writes and the original process group have stopped.\n\t\trestore: (_idle: ManagedWritersIdle) =>\n\t\t\tArray.reduce<Backup, Effect.Effect<void, PlatformError.PlatformError>>(\n\t\t\t\tbackups,\n\t\t\t\tEffect.void,\n\t\t\t\t(previous, { file, contents }) =>\n\t\t\t\t\tprevious.pipe(\n\t\t\t\t\t\tEffect.onExit(() =>\n\t\t\t\t\t\t\tOption.match(contents, {\n\t\t\t\t\t\t\t\tonNone: () => fs.remove(file, { force: true }),\n\t\t\t\t\t\t\t\tonSome: (contents) => restoreFile(fs, path, file, contents),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t),\n\t}\n}, Effect.uninterruptible)\n\n/** Create privately and restore metadata before making the replacement visible. */\nconst restoreFile = Effect.fnUntraced(function* (\n\tfs: FileSystem.FileSystem,\n\tpath: Path.Path,\n\ttarget: string,\n\tcontents: Contents,\n) {\n\tyield* fs.makeDirectory(path.dirname(target), { recursive: true })\n\tconst suffix = yield* Random.nextIntBetween(0, 0xffffffff)\n\tconst temporary = `${target}.${suffix.toString(16)}.tmp`\n\tyield* Effect.acquireUseRelease(\n\t\tScope.make(),\n\t\t(scope) =>\n\t\t\tEffect.acquireUseRelease(\n\t\t\t\tfs\n\t\t\t\t\t.open(temporary, { flag: \"wx\", mode: 0o600 })\n\t\t\t\t\t.pipe(Effect.provideService(Scope.Scope, scope)),\n\t\t\t\t(file) =>\n\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\tyield* file.writeAll(contents.bytes)\n\t\t\t\t\t\tconst current = yield* file.stat\n\t\t\t\t\t\tconst { uid, gid } = contents\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tOption.isSome(uid) &&\n\t\t\t\t\t\t\tOption.isSome(gid) &&\n\t\t\t\t\t\t\t(!Equal.equals(uid, current.uid) ||\n\t\t\t\t\t\t\t\t!Equal.equals(gid, current.gid))\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tyield* fs.chown(temporary, uid.value, gid.value)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tyield* fs.chmod(temporary, contents.mode)\n\t\t\t\t\t\tyield* file.sync\n\t\t\t\t\t\tyield* Scope.close(scope, Exit.void)\n\t\t\t\t\t\tyield* fs.rename(temporary, target)\n\t\t\t\t\t}),\n\t\t\t\t(_, exit) =>\n\t\t\t\t\tExit.isSuccess(exit)\n\t\t\t\t\t\t? Effect.void\n\t\t\t\t\t\t: Scope.close(scope, exit).pipe(\n\t\t\t\t\t\t\t\tEffect.onExit(() => fs.remove(temporary, { force: true })),\n\t\t\t\t\t\t\t),\n\t\t\t),\n\t\t(scope, exit) => Scope.close(scope, exit),\n\t)\n})\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"snapshot.js","names":[],"sources":["../src/snapshot.ts"],"sourcesContent":["import {\n\tArray,\n\tCause,\n\tEffect,\n\tEqual,\n\tExit,\n\tOption,\n\tRandom,\n\tSchema,\n\tScope,\n} from \"effect\"\nimport type { FileSystem, Path, PlatformError } from \"effect\"\n/** File writes have completed and the original installer group is stopped or absent. */\nexport interface ManagedWritersIdle {\n\treadonly _tag: \"ManagedWritersIdle\"\n}\n\nexport interface SnapshotCleanupFailure {\n\treadonly snapshotDirectory: string\n\treadonly cause: Cause.Cause<PlatformError.PlatformError>\n}\n\n/** Separate the transaction's outcome from disposal of its snapshot. */\nexport const withSnapshot = <A, E, R>(\n\tfs: FileSystem.FileSystem,\n\tcanDelete: Effect.Effect<boolean>,\n\tuse: (directory: string) => Effect.Effect<A, E, R>,\n) =>\n\tEffect.uninterruptibleMask((resume) =>\n\t\tEffect.gen(function* () {\n\t\t\tconst snapshotDirectory = yield* fs.makeTempDirectory({\n\t\t\t\tprefix: \"graft-recovery-\",\n\t\t\t})\n\t\t\tconst outcome = yield* resume(\n\t\t\t\tEffect.suspend(() => use(snapshotDirectory)),\n\t\t\t).pipe(Effect.exit)\n\t\t\tconst cleanup = (yield* canDelete)\n\t\t\t\t? yield* Effect.suspend(() =>\n\t\t\t\t\t\tfs.remove(snapshotDirectory, { recursive: true }),\n\t\t\t\t\t).pipe(Effect.exit)\n\t\t\t\t: Exit.void\n\t\t\tif (Exit.isFailure(outcome)) {\n\t\t\t\treturn yield* Effect.failCause(\n\t\t\t\t\tExit.isFailure(cleanup)\n\t\t\t\t\t\t? Cause.combine(outcome.cause, cleanup.cause)\n\t\t\t\t\t\t: outcome.cause,\n\t\t\t\t)\n\t\t\t}\n\t\t\treturn yield* resume(\n\t\t\t\tEffect.succeed({\n\t\t\t\t\tvalue: outcome.value,\n\t\t\t\t\tsnapshotCleanupFailure: Exit.isFailure(cleanup)\n\t\t\t\t\t\t? Option.some<SnapshotCleanupFailure>({\n\t\t\t\t\t\t\t\tsnapshotDirectory,\n\t\t\t\t\t\t\t\tcause: cleanup.cause,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t: Option.none<SnapshotCleanupFailure>(),\n\t\t\t\t}),\n\t\t\t).pipe(\n\t\t\t\t// Cancellation can prevent delivery, but must not hide a cleanup failure.\n\t\t\t\tEffect.onExit((exit) =>\n\t\t\t\t\tExit.isFailure(exit) && Exit.isFailure(cleanup)\n\t\t\t\t\t\t? Effect.failCause(cleanup.cause)\n\t\t\t\t\t\t: Effect.void,\n\t\t\t\t),\n\t\t\t)\n\t\t}),\n\t)\n\nexport class UnsupportedFileTypeError extends Schema.TaggedError<\n\tUnsupportedFileTypeError,\n\t{ readonly brand: unique symbol }\n>()(\"UnsupportedFileTypeError\", {\n\tpath: Schema.String,\n\tfileType: Schema.String,\n\tmessage: Schema.String,\n}) {}\n\ninterface RegularFileMetadata {\n\treadonly mode: number\n\treadonly uid: Option.Option<number>\n\treadonly gid: Option.Option<number>\n}\nexport interface Contents extends RegularFileMetadata {\n\treadonly bytes: Uint8Array\n}\nexport interface Backup {\n\treadonly name: string\n\treadonly file: string\n\treadonly contents: Option.Option<Contents>\n}\n\n/** lstat distinguishes missing paths from links, including dangling links. */\nexport const inspectManagedFiles = Effect.fnUntraced(function* (\n\tfs: FileSystem.FileSystem,\n\tpath: Path.Path,\n\tdirectory: string,\n\tnames: ReadonlyArray<string>,\n) {\n\treturn yield* Effect.forEach(\n\t\tnames,\n\t\tEffect.fnUntraced(function* (name) {\n\t\t\tconst file = path.join(directory, name)\n\t\t\tconst metadata = yield* fs.lstat(file).pipe(\n\t\t\t\tEffect.flatMap((info) =>\n\t\t\t\t\tinfo.type === \"File\"\n\t\t\t\t\t\t? Effect.succeed<RegularFileMetadata>({\n\t\t\t\t\t\t\t\tmode: info.mode & 0o7777,\n\t\t\t\t\t\t\t\tuid: info.uid,\n\t\t\t\t\t\t\t\tgid: info.gid,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t: Effect.fail(\n\t\t\t\t\t\t\t\tUnsupportedFileTypeError.make({\n\t\t\t\t\t\t\t\t\tpath: file,\n\t\t\t\t\t\t\t\t\tfileType: info.type,\n\t\t\t\t\t\t\t\t\tmessage: `Managed paths must be regular files or absent: ${file} is ${info.type}`,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t\tEffect.asSome,\n\t\t\t\t// Only an entirely missing-path failure establishes absence.\n\t\t\t\tEffect.catchCauseIf(\n\t\t\t\t\t(cause) =>\n\t\t\t\t\t\tArray.every(\n\t\t\t\t\t\t\tcause.reasons,\n\t\t\t\t\t\t\t(reason) =>\n\t\t\t\t\t\t\t\tCause.isFailReason(reason) &&\n\t\t\t\t\t\t\t\treason.error._tag === \"PlatformError\" &&\n\t\t\t\t\t\t\t\treason.error.reason.module === \"FileSystem\" &&\n\t\t\t\t\t\t\t\treason.error.reason.method === \"lstat\" &&\n\t\t\t\t\t\t\t\treason.error.reason._tag === \"NotFound\",\n\t\t\t\t\t\t),\n\t\t\t\t\t() => Effect.succeedNone,\n\t\t\t\t),\n\t\t\t)\n\t\t\treturn { name, file, metadata }\n\t\t}),\n\t)\n})\n\nconst encodeInventory = Schema.encodeEffect(\n\tSchema.fromJsonString(\n\t\tSchema.Struct({\n\t\t\tdirectory: Schema.String,\n\t\t\tagent: Schema.String,\n\t\t\tfiles: Schema.Array(\n\t\t\t\tSchema.Struct({\n\t\t\t\t\tname: Schema.String,\n\t\t\t\t\tbackup: Schema.NullOr(Schema.String),\n\t\t\t\t\tmode: Schema.NullOr(Schema.Int),\n\t\t\t\t\tuid: Schema.NullOr(Schema.Int),\n\t\t\t\t\tgid: Schema.NullOr(Schema.Int),\n\t\t\t\t}),\n\t\t\t),\n\t\t}),\n\t\t{ space: 2 },\n\t),\n)\n\n/** Persist every byte before allowing the installer to modify the project. */\nexport const writeSnapshot = Effect.fnUntraced(function* (\n\tfs: FileSystem.FileSystem,\n\tpath: Path.Path,\n\tsnapshotDirectory: string,\n\tdirectory: string,\n\tagent: string,\n\tbackups: ReadonlyArray<Backup>,\n) {\n\tconst files = yield* Effect.forEach(\n\t\tbackups,\n\t\tEffect.fnUntraced(function* (entry, index) {\n\t\t\tif (Option.isNone(entry.contents))\n\t\t\t\treturn {\n\t\t\t\t\tname: entry.name,\n\t\t\t\t\tbackup: null,\n\t\t\t\t\tmode: null,\n\t\t\t\t\tuid: null,\n\t\t\t\t\tgid: null,\n\t\t\t\t}\n\t\t\tconst { bytes, mode, uid, gid } = entry.contents.value\n\t\t\tconst backup = `${index}.bin`\n\t\t\tyield* fs.writeFile(path.join(snapshotDirectory, backup), bytes, {\n\t\t\t\tmode: 0o600,\n\t\t\t})\n\t\t\treturn {\n\t\t\t\tname: entry.name,\n\t\t\t\tbackup,\n\t\t\t\tmode,\n\t\t\t\tuid: Option.getOrNull(uid),\n\t\t\t\tgid: Option.getOrNull(gid),\n\t\t\t}\n\t\t}),\n\t)\n\tconst inventory = yield* encodeInventory({ directory, agent, files })\n\tyield* fs.writeFileString(\n\t\tpath.join(snapshotDirectory, \"snapshot.json\"),\n\t\tinventory,\n\t\t{ mode: 0o600 },\n\t)\n\treturn {\n\t\t// The caller must establish that file writes and the original process group have stopped.\n\t\trestore: (_idle: ManagedWritersIdle) =>\n\t\t\tArray.reduce<Backup, Effect.Effect<void, PlatformError.PlatformError>>(\n\t\t\t\tbackups,\n\t\t\t\tEffect.void,\n\t\t\t\t(previous, { file, contents }) =>\n\t\t\t\t\tprevious.pipe(\n\t\t\t\t\t\tEffect.onExit(() =>\n\t\t\t\t\t\t\tOption.match(contents, {\n\t\t\t\t\t\t\t\tonNone: () => fs.remove(file, { force: true }),\n\t\t\t\t\t\t\t\tonSome: (contents) => restoreFile(fs, path, file, contents),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t),\n\t}\n}, Effect.uninterruptible)\n\n/** Create privately and restore metadata before making the replacement visible. */\nconst restoreFile = Effect.fnUntraced(function* (\n\tfs: FileSystem.FileSystem,\n\tpath: Path.Path,\n\ttarget: string,\n\tcontents: Contents,\n) {\n\tyield* fs.makeDirectory(path.dirname(target), { recursive: true })\n\tconst suffix = yield* Random.nextIntBetween(0, 0xffffffff)\n\tconst temporary = `${target}.${suffix.toString(16)}.tmp`\n\tyield* Effect.acquireUseRelease(\n\t\tScope.make(),\n\t\t(scope) =>\n\t\t\tEffect.acquireUseRelease(\n\t\t\t\tfs\n\t\t\t\t\t.open(temporary, { flag: \"wx\", mode: 0o600 })\n\t\t\t\t\t.pipe(Effect.provideService(Scope.Scope, scope)),\n\t\t\t\tEffect.fnUntraced(function* (file) {\n\t\t\t\t\tyield* file.writeAll(contents.bytes)\n\t\t\t\t\tconst current = yield* file.stat\n\t\t\t\t\tconst { uid, gid } = contents\n\t\t\t\t\tif (\n\t\t\t\t\t\tOption.isSome(uid) &&\n\t\t\t\t\t\tOption.isSome(gid) &&\n\t\t\t\t\t\t(!Equal.equals(uid, current.uid) || !Equal.equals(gid, current.gid))\n\t\t\t\t\t) {\n\t\t\t\t\t\tyield* fs.chown(temporary, uid.value, gid.value)\n\t\t\t\t\t}\n\t\t\t\t\tyield* fs.chmod(temporary, contents.mode)\n\t\t\t\t\tyield* file.sync\n\t\t\t\t\tyield* Scope.close(scope, Exit.void)\n\t\t\t\t\tyield* fs.rename(temporary, target)\n\t\t\t\t}),\n\t\t\t\t(_, exit) =>\n\t\t\t\t\tExit.isSuccess(exit)\n\t\t\t\t\t\t? Effect.void\n\t\t\t\t\t\t: Scope.close(scope, exit).pipe(\n\t\t\t\t\t\t\t\tEffect.onExit(() => fs.remove(temporary, { force: true })),\n\t\t\t\t\t\t\t),\n\t\t\t),\n\t\t(scope, exit) => Scope.close(scope, exit),\n\t)\n})\n"],"mappings":"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"}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -197,7 +197,7 @@ const readSource = Effect.fnUntraced(function* (
|
|
|
197
197
|
|
|
198
198
|
const Executable = PackageManagerName
|
|
199
199
|
const Digits = Schema.String.check(Schema.isPattern(/^\d+$/))
|
|
200
|
-
const decodePackageManager = Schema.
|
|
200
|
+
const decodePackageManager = Schema.decodeUnknownEffect(
|
|
201
201
|
PackageManagerDeclaration,
|
|
202
202
|
)
|
|
203
203
|
/** `name/version ...`, as package managers set `npm_config_user_agent`. */
|
|
@@ -218,15 +218,18 @@ export const detectAgent = Effect.fnUntraced(function* (options: Options) {
|
|
|
218
218
|
path.join(options.directory, "package.json"),
|
|
219
219
|
).pipe(Effect.flatMap(decodeManifest))
|
|
220
220
|
if (manifest.packageManager !== undefined) {
|
|
221
|
-
|
|
222
|
-
|
|
221
|
+
const [name, , version] = yield* decodePackageManager(
|
|
222
|
+
manifest.packageManager,
|
|
223
|
+
).pipe(
|
|
224
|
+
// Keep the schema issue: it names the constraint the declaration failed.
|
|
225
|
+
Effect.mapError((error) =>
|
|
223
226
|
PackageManagerError.make({
|
|
224
227
|
operation: "detect",
|
|
225
|
-
message: `Unsupported packageManager: ${manifest.packageManager}`,
|
|
228
|
+
message: `Unsupported packageManager: ${manifest.packageManager}\n${error.message}`,
|
|
226
229
|
}),
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
})
|
|
230
|
+
),
|
|
231
|
+
)
|
|
232
|
+
return agentOf(name, `${version.major}`)
|
|
230
233
|
}
|
|
231
234
|
const found = yield* Effect.filter(lockfiles, (file) =>
|
|
232
235
|
fs.exists(path.join(options.directory, file)),
|
|
@@ -360,322 +363,295 @@ export interface PackageManager {
|
|
|
360
363
|
* Committed installs report snapshot deletion failures in snapshotCleanupFailure
|
|
361
364
|
* rather than failing or rolling back. No crash recovery is promised.
|
|
362
365
|
*/
|
|
363
|
-
export const make = (
|
|
366
|
+
export const make = Effect.fnUntraced(function* (
|
|
364
367
|
options: Options,
|
|
365
|
-
): Effect.
|
|
368
|
+
): Effect.fn.Return<
|
|
366
369
|
PackageManager,
|
|
367
370
|
Error,
|
|
368
371
|
FileSystem.FileSystem | Path.Path | ChildProcessSpawner.ChildProcessSpawner
|
|
369
|
-
>
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
Schema.
|
|
385
|
-
|
|
386
|
-
|
|
372
|
+
> {
|
|
373
|
+
const fs = yield* FileSystem.FileSystem
|
|
374
|
+
const path = yield* Path.Path
|
|
375
|
+
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
|
|
376
|
+
const directory = path.resolve(options.directory)
|
|
377
|
+
yield* inspectManagedFiles(fs, path, directory, backupFiles)
|
|
378
|
+
const agent = yield* detectAgent({ ...options, directory })
|
|
379
|
+
const lock = yield* Semaphore.make(1)
|
|
380
|
+
const transaction = yield* Ref.make<ManagerState>(ManagerState.Ready())
|
|
381
|
+
const manifestFile = path.join(directory, "package.json")
|
|
382
|
+
const readManifest = readSource(fs, manifestFile).pipe(
|
|
383
|
+
Effect.flatMap(decodeManifest),
|
|
384
|
+
)
|
|
385
|
+
const installed = Schema.decodeUnknownEffect(
|
|
386
|
+
Schema.fromJsonString(
|
|
387
|
+
Schema.Struct({ version: Schema.optionalKey(Schema.String) }),
|
|
388
|
+
),
|
|
389
|
+
)
|
|
390
|
+
const stateOf = Effect.fnUntraced(function* (manifest: Manifest) {
|
|
391
|
+
if (yield* fs.exists(path.join(directory, ".pnp.cjs")))
|
|
392
|
+
return yield* PackageManagerError.make({
|
|
393
|
+
operation: "state",
|
|
394
|
+
message:
|
|
395
|
+
"Plug'n'Play is not supported; configure Yarn with nodeLinker: node-modules",
|
|
396
|
+
})
|
|
397
|
+
const deps = Array.reduce(
|
|
398
|
+
fields,
|
|
399
|
+
{} as Readonly<Record<string, string>>,
|
|
400
|
+
(all, field) => ({ ...all, ...manifest[field] }),
|
|
387
401
|
)
|
|
388
|
-
const
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
([name, spec]) =>
|
|
403
|
-
Effect.gen(function* () {
|
|
404
|
-
const file = path.join(
|
|
405
|
-
directory,
|
|
406
|
-
"node_modules",
|
|
407
|
-
name,
|
|
408
|
-
"package.json",
|
|
409
|
-
)
|
|
410
|
-
const source = yield* fs.readFileString(file).pipe(
|
|
411
|
-
Effect.asSome,
|
|
412
|
-
Effect.catchCauseIf(
|
|
413
|
-
(cause) =>
|
|
414
|
-
Array.every(
|
|
415
|
-
cause.reasons,
|
|
416
|
-
(reason) =>
|
|
417
|
-
Cause.isFailReason(reason) &&
|
|
418
|
-
reason.error.reason.module === "FileSystem" &&
|
|
419
|
-
reason.error.reason._tag === "NotFound",
|
|
420
|
-
),
|
|
421
|
-
() => Effect.succeedNone,
|
|
402
|
+
const entries = yield* Effect.forEach(
|
|
403
|
+
Record.toEntries(deps),
|
|
404
|
+
Effect.fnUntraced(function* ([name, spec]) {
|
|
405
|
+
const file = path.join(directory, "node_modules", name, "package.json")
|
|
406
|
+
const source = yield* fs.readFileString(file).pipe(
|
|
407
|
+
Effect.asSome,
|
|
408
|
+
Effect.catchCauseIf(
|
|
409
|
+
(cause) =>
|
|
410
|
+
Array.every(
|
|
411
|
+
cause.reasons,
|
|
412
|
+
(reason) =>
|
|
413
|
+
Cause.isFailReason(reason) &&
|
|
414
|
+
reason.error.reason.module === "FileSystem" &&
|
|
415
|
+
reason.error.reason._tag === "NotFound",
|
|
422
416
|
),
|
|
423
|
-
)
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
417
|
+
() => Effect.succeedNone,
|
|
418
|
+
),
|
|
419
|
+
)
|
|
420
|
+
if (Option.isNone(source))
|
|
421
|
+
return [name, { spec, version: undefined, path: undefined }] as const
|
|
422
|
+
const pkg = yield* installed(source.value)
|
|
423
|
+
return [
|
|
424
|
+
name,
|
|
425
|
+
{
|
|
426
|
+
spec,
|
|
427
|
+
version: pkg.version,
|
|
428
|
+
path: yield* fs.realPath(path.dirname(file)),
|
|
429
|
+
},
|
|
430
|
+
] as const
|
|
431
|
+
}),
|
|
432
|
+
{ concurrency: 16 },
|
|
433
|
+
)
|
|
434
|
+
return HashMap.fromIterable<string, PackageState>(entries)
|
|
435
|
+
})
|
|
436
|
+
const readState = Effect.flatMap(readManifest, stateOf)
|
|
437
|
+
const termination: ChildProcess.KillOptions = {
|
|
438
|
+
forceKillAfter: "5 seconds",
|
|
439
|
+
}
|
|
440
|
+
const run = Effect.fnUntraced(function* (operation: "install" | "recover") {
|
|
441
|
+
const command = strategy(agent, operation)
|
|
442
|
+
// Acquire and install the release handler atomically, then wait interruptibly.
|
|
443
|
+
return yield* Effect.acquireUseRelease(
|
|
444
|
+
spawner
|
|
445
|
+
.spawn(
|
|
446
|
+
ChildProcess.make(command.executable, command.args, {
|
|
447
|
+
cwd: directory,
|
|
448
|
+
extendEnv: true,
|
|
449
|
+
stdin: "ignore",
|
|
450
|
+
stdout: "inherit",
|
|
451
|
+
stderr: "inherit",
|
|
452
|
+
detached: true,
|
|
453
|
+
...termination,
|
|
454
|
+
env: command.env,
|
|
438
455
|
}),
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
forceKillAfter: "5 seconds",
|
|
446
|
-
}
|
|
447
|
-
const run = Effect.fnUntraced(function* (operation: "install" | "recover") {
|
|
448
|
-
const command = strategy(agent, operation)
|
|
449
|
-
// Acquire and install the release handler atomically, then wait interruptibly.
|
|
450
|
-
return yield* Effect.acquireUseRelease(
|
|
451
|
-
spawner
|
|
452
|
-
.spawn(
|
|
453
|
-
ChildProcess.make(command.executable, command.args, {
|
|
454
|
-
cwd: directory,
|
|
455
|
-
extendEnv: true,
|
|
456
|
-
stdin: "ignore",
|
|
457
|
-
stdout: "inherit",
|
|
458
|
-
stderr: "inherit",
|
|
459
|
-
detached: true,
|
|
460
|
-
...termination,
|
|
461
|
-
env: command.env,
|
|
462
|
-
}),
|
|
463
|
-
)
|
|
464
|
-
.pipe(
|
|
465
|
-
Effect.tap(() =>
|
|
466
|
-
Ref.set(
|
|
467
|
-
transaction,
|
|
468
|
-
ManagerState.Active({ writer: WriterState.Running() }),
|
|
469
|
-
),
|
|
456
|
+
)
|
|
457
|
+
.pipe(
|
|
458
|
+
Effect.tap(() =>
|
|
459
|
+
Ref.set(
|
|
460
|
+
transaction,
|
|
461
|
+
ManagerState.Active({ writer: WriterState.Running() }),
|
|
470
462
|
),
|
|
471
463
|
),
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
operation,
|
|
480
|
-
message: `${command.executable} ${operation} timed out`,
|
|
481
|
-
}),
|
|
482
|
-
}),
|
|
483
|
-
)
|
|
484
|
-
if (code !== 0)
|
|
485
|
-
return yield* PackageManagerError.make({
|
|
464
|
+
),
|
|
465
|
+
Effect.fnUntraced(function* (process) {
|
|
466
|
+
const code = yield* process.exitCode.pipe(
|
|
467
|
+
Effect.timeoutOrElse({
|
|
468
|
+
duration: "10 minutes",
|
|
469
|
+
orElse: () =>
|
|
470
|
+
PackageManagerError.make({
|
|
486
471
|
operation,
|
|
487
|
-
|
|
488
|
-
message: `${command.executable} ${operation} exited with code ${code}`,
|
|
489
|
-
})
|
|
490
|
-
}),
|
|
491
|
-
// The command owns its group; a stopped leader may leave live descendants.
|
|
492
|
-
// Observe kill failures here rather than relying on best-effort scope cleanup.
|
|
493
|
-
(process) =>
|
|
494
|
-
Effect.gen(function* () {
|
|
495
|
-
const result = yield* terminateProcess(process, termination)
|
|
496
|
-
yield* Ref.set(
|
|
497
|
-
transaction,
|
|
498
|
-
ManagerState.Active({
|
|
499
|
-
writer:
|
|
500
|
-
result._tag === "OriginalGroupStopped"
|
|
501
|
-
? WriterState.ManagedWritersIdle()
|
|
502
|
-
: WriterState.Unconfirmed({
|
|
503
|
-
operation,
|
|
504
|
-
processId: process.pid,
|
|
505
|
-
cause: result.cause,
|
|
506
|
-
}),
|
|
472
|
+
message: `${command.executable} ${operation} timed out`,
|
|
507
473
|
}),
|
|
508
|
-
)
|
|
509
|
-
if (result._tag === "Unconfirmed")
|
|
510
|
-
return yield* Effect.failCause(result.cause)
|
|
511
474
|
}),
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
475
|
+
)
|
|
476
|
+
if (code !== 0)
|
|
477
|
+
return yield* PackageManagerError.make({
|
|
478
|
+
operation,
|
|
479
|
+
exitCode: code,
|
|
480
|
+
message: `${command.executable} ${operation} exited with code ${code}`,
|
|
481
|
+
})
|
|
482
|
+
}),
|
|
483
|
+
// The command owns its group; a stopped leader may leave live descendants.
|
|
484
|
+
// Observe kill failures here rather than relying on best-effort scope cleanup.
|
|
485
|
+
Effect.fnUntraced(function* (process) {
|
|
486
|
+
const result = yield* terminateProcess(process, termination)
|
|
487
|
+
yield* Ref.set(
|
|
488
|
+
transaction,
|
|
489
|
+
ManagerState.Active({
|
|
490
|
+
writer:
|
|
491
|
+
result._tag === "OriginalGroupStopped"
|
|
492
|
+
? WriterState.ManagedWritersIdle()
|
|
493
|
+
: WriterState.Unconfirmed({
|
|
494
|
+
operation,
|
|
495
|
+
processId: process.pid,
|
|
496
|
+
cause: result.cause,
|
|
497
|
+
}),
|
|
498
|
+
}),
|
|
499
|
+
)
|
|
500
|
+
if (result._tag === "Unconfirmed")
|
|
501
|
+
return yield* Effect.failCause(result.cause)
|
|
502
|
+
}),
|
|
503
|
+
)
|
|
504
|
+
}, Effect.scoped)
|
|
505
|
+
// AbortSignal does not join accepted OS writes. Keep the writer owned until completion.
|
|
506
|
+
const withFileWriter = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
|
|
507
|
+
Effect.acquireUseRelease(
|
|
508
|
+
Ref.set(
|
|
509
|
+
transaction,
|
|
510
|
+
ManagerState.Active({ writer: WriterState.FileWrite() }),
|
|
511
|
+
),
|
|
512
|
+
() => effect,
|
|
513
|
+
() =>
|
|
517
514
|
Ref.set(
|
|
518
515
|
transaction,
|
|
519
|
-
ManagerState.Active({ writer: WriterState.
|
|
516
|
+
ManagerState.Active({ writer: WriterState.ManagedWritersIdle() }),
|
|
520
517
|
),
|
|
521
|
-
|
|
522
|
-
|
|
518
|
+
).pipe(Effect.uninterruptible)
|
|
519
|
+
|
|
520
|
+
const install = Effect.fnUntraced(function* (changes: Changes) {
|
|
521
|
+
const state = yield* Ref.get(transaction)
|
|
522
|
+
if (state._tag === "RecoveryRequired") return yield* state.error
|
|
523
|
+
const validated = yield* Schema.decodeEffect(Changes)(changes)
|
|
524
|
+
const inspected = yield* inspectManagedFiles(
|
|
525
|
+
fs,
|
|
526
|
+
path,
|
|
527
|
+
directory,
|
|
528
|
+
backupFiles,
|
|
529
|
+
)
|
|
530
|
+
const backups = yield* Effect.forEach(
|
|
531
|
+
inspected,
|
|
532
|
+
Effect.fnUntraced(function* ({ name, file, metadata }) {
|
|
533
|
+
const contents = yield* Option.match(metadata, {
|
|
534
|
+
onNone: () => Effect.succeedNone,
|
|
535
|
+
onSome: (info) =>
|
|
536
|
+
fs
|
|
537
|
+
.readFile(file)
|
|
538
|
+
.pipe(
|
|
539
|
+
Effect.map((bytes) =>
|
|
540
|
+
Option.some<Contents>({ ...info, bytes }),
|
|
541
|
+
),
|
|
542
|
+
),
|
|
543
|
+
})
|
|
544
|
+
return { name, file, contents }
|
|
545
|
+
}),
|
|
546
|
+
)
|
|
547
|
+
const original = yield* readSource(fs, manifestFile)
|
|
548
|
+
const manifest = yield* decodeManifest(original)
|
|
549
|
+
const before = yield* stateOf(manifest)
|
|
550
|
+
const text = yield* encodeManifest(
|
|
551
|
+
applyChanges(manifest, validated),
|
|
552
|
+
original,
|
|
553
|
+
)
|
|
554
|
+
|
|
555
|
+
const agentLocks = strategy(agent, "install").lockfiles
|
|
556
|
+
const hasLock = Array.some(
|
|
557
|
+
backups,
|
|
558
|
+
({ name, contents }) =>
|
|
559
|
+
Array.contains(agentLocks, name) && Option.isSome(contents),
|
|
560
|
+
)
|
|
561
|
+
const completed = yield* withSnapshot(
|
|
562
|
+
fs,
|
|
563
|
+
Ref.get(transaction).pipe(Effect.map((state) => state._tag === "Ready")),
|
|
564
|
+
Effect.fnUntraced(function* (snapshotDirectory) {
|
|
565
|
+
const snapshot = yield* writeSnapshot(
|
|
566
|
+
fs,
|
|
567
|
+
path,
|
|
568
|
+
snapshotDirectory,
|
|
569
|
+
directory,
|
|
570
|
+
agent,
|
|
571
|
+
backups,
|
|
572
|
+
)
|
|
573
|
+
const restore = Effect.flatMap(Ref.get(transaction), (state) =>
|
|
574
|
+
state._tag === "Active" && state.writer._tag === "ManagedWritersIdle"
|
|
575
|
+
? withFileWriter(snapshot.restore(state.writer))
|
|
576
|
+
: Effect.void,
|
|
577
|
+
)
|
|
578
|
+
return yield* Effect.acquireUseRelease(
|
|
523
579
|
Ref.set(
|
|
524
580
|
transaction,
|
|
525
|
-
ManagerState.Active({
|
|
581
|
+
ManagerState.Active({
|
|
582
|
+
writer: WriterState.ManagedWritersIdle(),
|
|
583
|
+
}),
|
|
526
584
|
),
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
onNone: () => Effect.succeedNone,
|
|
547
|
-
onSome: (info) =>
|
|
548
|
-
fs
|
|
549
|
-
.readFile(file)
|
|
550
|
-
.pipe(
|
|
551
|
-
Effect.map((bytes) =>
|
|
552
|
-
Option.some<Contents>({ ...info, bytes }),
|
|
553
|
-
),
|
|
554
|
-
),
|
|
555
|
-
})
|
|
556
|
-
return { name, file, contents }
|
|
557
|
-
}),
|
|
558
|
-
)
|
|
559
|
-
const original = yield* readSource(fs, manifestFile)
|
|
560
|
-
const manifest = yield* decodeManifest(original)
|
|
561
|
-
const before = yield* stateOf(manifest)
|
|
562
|
-
const text = yield* encodeManifest(
|
|
563
|
-
applyChanges(manifest, validated),
|
|
564
|
-
original,
|
|
565
|
-
)
|
|
566
|
-
|
|
567
|
-
const agentLocks = strategy(agent, "install").lockfiles
|
|
568
|
-
const hasLock = Array.some(
|
|
569
|
-
backups,
|
|
570
|
-
({ name, contents }) =>
|
|
571
|
-
Array.contains(agentLocks, name) && Option.isSome(contents),
|
|
572
|
-
)
|
|
573
|
-
const completed = yield* withSnapshot(
|
|
574
|
-
fs,
|
|
575
|
-
Ref.get(transaction).pipe(
|
|
576
|
-
Effect.map((state) => state._tag === "Ready"),
|
|
577
|
-
),
|
|
578
|
-
(snapshotDirectory) =>
|
|
579
|
-
Effect.gen(function* () {
|
|
580
|
-
const snapshot = yield* writeSnapshot(
|
|
581
|
-
fs,
|
|
582
|
-
path,
|
|
583
|
-
snapshotDirectory,
|
|
584
|
-
directory,
|
|
585
|
-
agent,
|
|
586
|
-
backups,
|
|
587
|
-
)
|
|
588
|
-
const restore = Effect.flatMap(Ref.get(transaction), (state) =>
|
|
589
|
-
state._tag === "Active" &&
|
|
590
|
-
state.writer._tag === "ManagedWritersIdle"
|
|
591
|
-
? withFileWriter(snapshot.restore(state.writer))
|
|
592
|
-
: Effect.void,
|
|
593
|
-
)
|
|
594
|
-
return yield* Effect.acquireUseRelease(
|
|
595
|
-
Ref.set(
|
|
596
|
-
transaction,
|
|
597
|
-
ManagerState.Active({
|
|
598
|
-
writer: WriterState.ManagedWritersIdle(),
|
|
599
|
-
}),
|
|
600
|
-
),
|
|
601
|
-
() =>
|
|
602
|
-
Effect.gen(function* () {
|
|
603
|
-
yield* withFileWriter(
|
|
604
|
-
fs.writeFileString(manifestFile, text),
|
|
605
|
-
)
|
|
606
|
-
yield* run("install")
|
|
607
|
-
const after = yield* readState
|
|
608
|
-
return { agent, ...diffState(before, after) }
|
|
609
|
-
}),
|
|
610
|
-
(_, exit) =>
|
|
611
|
-
Effect.gen(function* () {
|
|
612
|
-
if (Exit.isFailure(exit)) {
|
|
613
|
-
const state = yield* Ref.get(transaction)
|
|
614
|
-
if (
|
|
615
|
-
state._tag !== "Active" ||
|
|
616
|
-
state.writer._tag !== "ManagedWritersIdle"
|
|
617
|
-
)
|
|
618
|
-
return
|
|
619
|
-
yield* withFileWriter(snapshot.restore(state.writer))
|
|
620
|
-
if (hasLock)
|
|
621
|
-
yield* run("recover").pipe(
|
|
622
|
-
// Even a failed recovery must restore files, once its writer has stopped.
|
|
623
|
-
Effect.onExit(() => restore),
|
|
624
|
-
)
|
|
625
|
-
}
|
|
626
|
-
// Idle managed writers alone do not prove that the transaction completed.
|
|
627
|
-
yield* Ref.set(transaction, ManagerState.Ready())
|
|
628
|
-
}),
|
|
629
|
-
).pipe(
|
|
630
|
-
Effect.onExit((exit) =>
|
|
631
|
-
Effect.gen(function* () {
|
|
632
|
-
const state = yield* Ref.get(transaction)
|
|
633
|
-
if (Exit.isSuccess(exit) || state._tag !== "Active")
|
|
634
|
-
return
|
|
635
|
-
const writer = state.writer
|
|
636
|
-
const error = RecoveryRequiredError.make({
|
|
637
|
-
directory,
|
|
638
|
-
snapshotDirectory,
|
|
639
|
-
operation:
|
|
640
|
-
writer._tag === "Unconfirmed"
|
|
641
|
-
? writer.operation
|
|
642
|
-
: "rollback",
|
|
643
|
-
...(writer._tag === "Unconfirmed"
|
|
644
|
-
? {
|
|
645
|
-
processId: writer.processId,
|
|
646
|
-
terminationCause: writer.cause,
|
|
647
|
-
}
|
|
648
|
-
: {}),
|
|
649
|
-
message: `Manual recovery is required. Stop all writers before restoring the snapshot at ${snapshotDirectory}`,
|
|
650
|
-
recoveryCause: exit.cause,
|
|
651
|
-
})
|
|
652
|
-
yield* Ref.set(
|
|
653
|
-
transaction,
|
|
654
|
-
ManagerState.RecoveryRequired({ error }),
|
|
655
|
-
)
|
|
656
|
-
return yield* error
|
|
657
|
-
}),
|
|
658
|
-
),
|
|
585
|
+
Effect.fnUntraced(function* () {
|
|
586
|
+
yield* withFileWriter(fs.writeFileString(manifestFile, text))
|
|
587
|
+
yield* run("install")
|
|
588
|
+
const after = yield* readState
|
|
589
|
+
return { agent, ...diffState(before, after) }
|
|
590
|
+
}),
|
|
591
|
+
Effect.fnUntraced(function* (_, exit) {
|
|
592
|
+
if (Exit.isFailure(exit)) {
|
|
593
|
+
const state = yield* Ref.get(transaction)
|
|
594
|
+
if (
|
|
595
|
+
state._tag !== "Active" ||
|
|
596
|
+
state.writer._tag !== "ManagedWritersIdle"
|
|
597
|
+
)
|
|
598
|
+
return
|
|
599
|
+
yield* withFileWriter(snapshot.restore(state.writer))
|
|
600
|
+
if (hasLock)
|
|
601
|
+
yield* run("recover").pipe(
|
|
602
|
+
// Even a failed recovery must restore files, once its writer has stopped.
|
|
603
|
+
Effect.onExit(() => restore),
|
|
659
604
|
)
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
605
|
+
}
|
|
606
|
+
// Idle managed writers alone do not prove that the transaction completed.
|
|
607
|
+
yield* Ref.set(transaction, ManagerState.Ready())
|
|
608
|
+
}),
|
|
609
|
+
).pipe(
|
|
610
|
+
Effect.onExit(
|
|
611
|
+
Effect.fnUntraced(function* (exit) {
|
|
612
|
+
const state = yield* Ref.get(transaction)
|
|
613
|
+
if (Exit.isSuccess(exit) || state._tag !== "Active") return
|
|
614
|
+
const writer = state.writer
|
|
615
|
+
const error = RecoveryRequiredError.make({
|
|
616
|
+
directory,
|
|
617
|
+
snapshotDirectory,
|
|
618
|
+
operation:
|
|
619
|
+
writer._tag === "Unconfirmed" ? writer.operation : "rollback",
|
|
620
|
+
...(writer._tag === "Unconfirmed"
|
|
621
|
+
? {
|
|
622
|
+
processId: writer.processId,
|
|
623
|
+
terminationCause: writer.cause,
|
|
624
|
+
}
|
|
625
|
+
: {}),
|
|
626
|
+
message: `Manual recovery is required. Stop all writers before restoring the snapshot at ${snapshotDirectory}`,
|
|
627
|
+
recoveryCause: exit.cause,
|
|
628
|
+
})
|
|
629
|
+
yield* Ref.set(
|
|
630
|
+
transaction,
|
|
631
|
+
ManagerState.RecoveryRequired({ error }),
|
|
632
|
+
)
|
|
633
|
+
return yield* error
|
|
634
|
+
}),
|
|
635
|
+
),
|
|
636
|
+
)
|
|
637
|
+
}),
|
|
638
|
+
)
|
|
668
639
|
return {
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
recoveryRequired: Ref.get(transaction).pipe(
|
|
672
|
-
Effect.map((state) =>
|
|
673
|
-
state._tag === "RecoveryRequired"
|
|
674
|
-
? Option.some(state.error)
|
|
675
|
-
: Option.none(),
|
|
676
|
-
),
|
|
677
|
-
),
|
|
678
|
-
readState: lock.withPermits(1)(readState),
|
|
679
|
-
install,
|
|
640
|
+
...completed.value,
|
|
641
|
+
snapshotCleanupFailure: completed.snapshotCleanupFailure,
|
|
680
642
|
}
|
|
681
|
-
})
|
|
643
|
+
}, lock.withPermits(1))
|
|
644
|
+
return {
|
|
645
|
+
directory,
|
|
646
|
+
agent,
|
|
647
|
+
recoveryRequired: Ref.get(transaction).pipe(
|
|
648
|
+
Effect.map((state) =>
|
|
649
|
+
state._tag === "RecoveryRequired"
|
|
650
|
+
? Option.some(state.error)
|
|
651
|
+
: Option.none(),
|
|
652
|
+
),
|
|
653
|
+
),
|
|
654
|
+
readState: lock.withPermits(1)(readState),
|
|
655
|
+
install,
|
|
656
|
+
}
|
|
657
|
+
})
|
package/src/process.ts
CHANGED
|
@@ -49,51 +49,50 @@ export const groupExists = (pid: ChildProcessSpawner.ProcessId) =>
|
|
|
49
49
|
)
|
|
50
50
|
|
|
51
51
|
/** Close the original group, including descendants that remain in it after leader exit. */
|
|
52
|
-
export const closeProcess = (
|
|
52
|
+
export const closeProcess = Effect.fnUntraced(function* (
|
|
53
53
|
process: ChildProcessSpawner.ChildProcessHandle,
|
|
54
54
|
options: ChildProcess.KillOptions,
|
|
55
55
|
probe: typeof groupExists = groupExists,
|
|
56
|
-
)
|
|
57
|
-
Effect.
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
) {
|
|
57
|
+
const killed = yield* Effect.suspend(() => process.kill(options)).pipe(
|
|
58
|
+
Effect.exit,
|
|
59
|
+
)
|
|
60
|
+
// Windows retains its as-is adapter contract; POSIX requires group evidence.
|
|
61
|
+
if (NodeProcess.platform === "win32") return yield* killed
|
|
62
|
+
const checked = yield* Effect.suspend(() => probe(process.pid)).pipe(
|
|
63
|
+
Effect.exit,
|
|
64
|
+
Effect.repeat({
|
|
65
|
+
while: (exit) => Exit.isSuccess(exit) && exit.value,
|
|
66
|
+
times: 100,
|
|
67
|
+
schedule: Schedule.spaced("20 millis"),
|
|
68
|
+
}),
|
|
69
|
+
)
|
|
70
|
+
if (Exit.isFailure(checked))
|
|
71
|
+
return yield* Effect.failCause(
|
|
72
|
+
Exit.isFailure(killed)
|
|
73
|
+
? Cause.combine(killed.cause, checked.cause)
|
|
74
|
+
: checked.cause,
|
|
60
75
|
)
|
|
61
|
-
|
|
62
|
-
if (
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
76
|
+
if (checked.value) {
|
|
77
|
+
if (Exit.isFailure(killed)) return yield* Effect.failCause(killed.cause)
|
|
78
|
+
return yield* PlatformError.systemError({
|
|
79
|
+
_tag: "Unknown",
|
|
80
|
+
module: "ChildProcess",
|
|
81
|
+
method: "confirmGroupStopped",
|
|
82
|
+
description: `Process group ${process.pid} still exists after the two-second confirmation window`,
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
if (Exit.isFailure(killed)) {
|
|
86
|
+
// Absence resolves only a failed signal to a nonexistent group, not other cleanup failures.
|
|
87
|
+
const remaining = Array.filter(
|
|
88
|
+
killed.cause.reasons,
|
|
89
|
+
(reason) =>
|
|
90
|
+
!(Cause.isFailReason(reason) && isSignalFailure(reason.error)),
|
|
70
91
|
)
|
|
71
|
-
if (
|
|
72
|
-
return yield* Effect.failCause(
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
: checked.cause,
|
|
76
|
-
)
|
|
77
|
-
if (checked.value) {
|
|
78
|
-
if (Exit.isFailure(killed)) return yield* Effect.failCause(killed.cause)
|
|
79
|
-
return yield* PlatformError.systemError({
|
|
80
|
-
_tag: "Unknown",
|
|
81
|
-
module: "ChildProcess",
|
|
82
|
-
method: "confirmGroupStopped",
|
|
83
|
-
description: `Process group ${process.pid} still exists after the two-second confirmation window`,
|
|
84
|
-
})
|
|
85
|
-
}
|
|
86
|
-
if (Exit.isFailure(killed)) {
|
|
87
|
-
// Absence resolves only a failed signal to a nonexistent group, not other cleanup failures.
|
|
88
|
-
const remaining = Array.filter(
|
|
89
|
-
killed.cause.reasons,
|
|
90
|
-
(reason) =>
|
|
91
|
-
!(Cause.isFailReason(reason) && isSignalFailure(reason.error)),
|
|
92
|
-
)
|
|
93
|
-
if (remaining.length > 0)
|
|
94
|
-
return yield* Effect.failCause(Cause.fromReasons(remaining))
|
|
95
|
-
}
|
|
96
|
-
})
|
|
92
|
+
if (remaining.length > 0)
|
|
93
|
+
return yield* Effect.failCause(Cause.fromReasons(remaining))
|
|
94
|
+
}
|
|
95
|
+
})
|
|
97
96
|
|
|
98
97
|
export type Termination = Data.TaggedEnum<{
|
|
99
98
|
OriginalGroupStopped: {}
|
package/src/snapshot.ts
CHANGED
|
@@ -97,8 +97,9 @@ export const inspectManagedFiles = Effect.fnUntraced(function* (
|
|
|
97
97
|
directory: string,
|
|
98
98
|
names: ReadonlyArray<string>,
|
|
99
99
|
) {
|
|
100
|
-
return yield* Effect.forEach(
|
|
101
|
-
|
|
100
|
+
return yield* Effect.forEach(
|
|
101
|
+
names,
|
|
102
|
+
Effect.fnUntraced(function* (name) {
|
|
102
103
|
const file = path.join(directory, name)
|
|
103
104
|
const metadata = yield* fs.lstat(file).pipe(
|
|
104
105
|
Effect.flatMap((info) =>
|
|
@@ -165,8 +166,9 @@ export const writeSnapshot = Effect.fnUntraced(function* (
|
|
|
165
166
|
agent: string,
|
|
166
167
|
backups: ReadonlyArray<Backup>,
|
|
167
168
|
) {
|
|
168
|
-
const files = yield* Effect.forEach(
|
|
169
|
-
|
|
169
|
+
const files = yield* Effect.forEach(
|
|
170
|
+
backups,
|
|
171
|
+
Effect.fnUntraced(function* (entry, index) {
|
|
170
172
|
if (Option.isNone(entry.contents))
|
|
171
173
|
return {
|
|
172
174
|
name: entry.name,
|
|
@@ -231,24 +233,22 @@ const restoreFile = Effect.fnUntraced(function* (
|
|
|
231
233
|
fs
|
|
232
234
|
.open(temporary, { flag: "wx", mode: 0o600 })
|
|
233
235
|
.pipe(Effect.provideService(Scope.Scope, scope)),
|
|
234
|
-
(file)
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
yield* fs.rename(temporary, target)
|
|
251
|
-
}),
|
|
236
|
+
Effect.fnUntraced(function* (file) {
|
|
237
|
+
yield* file.writeAll(contents.bytes)
|
|
238
|
+
const current = yield* file.stat
|
|
239
|
+
const { uid, gid } = contents
|
|
240
|
+
if (
|
|
241
|
+
Option.isSome(uid) &&
|
|
242
|
+
Option.isSome(gid) &&
|
|
243
|
+
(!Equal.equals(uid, current.uid) || !Equal.equals(gid, current.gid))
|
|
244
|
+
) {
|
|
245
|
+
yield* fs.chown(temporary, uid.value, gid.value)
|
|
246
|
+
}
|
|
247
|
+
yield* fs.chmod(temporary, contents.mode)
|
|
248
|
+
yield* file.sync
|
|
249
|
+
yield* Scope.close(scope, Exit.void)
|
|
250
|
+
yield* fs.rename(temporary, target)
|
|
251
|
+
}),
|
|
252
252
|
(_, exit) =>
|
|
253
253
|
Exit.isSuccess(exit)
|
|
254
254
|
? Effect.void
|