@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 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 | ChildProcessSpawner.ChildProcessSpawner>;
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
@@ -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;iBAmEP;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,SACP,YACP,OAAO,OACT,gBACA,OACA,WAAW,aAAa,KAAK,OAAO,oBAAoB"}
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.decodeUnknownOption(PackageManagerDeclaration);
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) return yield* Option.match(decodePackageManager(manifest.packageManager), {
131
- onNone: () => PackageManagerError.make({
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
- onSome: ([name, , version]) => Effect.succeed(agentOf(name, `${version.major}`))
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 = (options) => Effect.gen(function* () {
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]) => Effect.gen(function* () {
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() })))), (process) => Effect.gen(function* () {
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
- }), (process) => Effect.gen(function* () {
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 = (changes) => lock.withPermits(1)(Effect.gen(function* () {
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 }) => Effect.gen(function* () {
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")), (snapshotDirectory) => Effect.gen(function* () {
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() })), () => Effect.gen(function* () {
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
- }), (_, exit) => Effect.gen(function* () {
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((exit) => Effect.gen(function* () {
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) => Effect.gen(function* () {
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({
@@ -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) =>\n\tEffect.gen(function* () {\n\t\tconst killed = yield* Effect.suspend(() => process.kill(options)).pipe(\n\t\t\tEffect.exit,\n\t\t)\n\t\t// Windows retains its as-is adapter contract; POSIX requires group evidence.\n\t\tif (NodeProcess.platform === \"win32\") return yield* killed\n\t\tconst checked = yield* Effect.suspend(() => probe(process.pid)).pipe(\n\t\t\tEffect.exit,\n\t\t\tEffect.repeat({\n\t\t\t\twhile: (exit) => Exit.isSuccess(exit) && exit.value,\n\t\t\t\ttimes: 100,\n\t\t\t\tschedule: Schedule.spaced(\"20 millis\"),\n\t\t\t}),\n\t\t)\n\t\tif (Exit.isFailure(checked))\n\t\t\treturn yield* Effect.failCause(\n\t\t\t\tExit.isFailure(killed)\n\t\t\t\t\t? Cause.combine(killed.cause, checked.cause)\n\t\t\t\t\t: checked.cause,\n\t\t\t)\n\t\tif (checked.value) {\n\t\t\tif (Exit.isFailure(killed)) return yield* Effect.failCause(killed.cause)\n\t\t\treturn yield* PlatformError.systemError({\n\t\t\t\t_tag: \"Unknown\",\n\t\t\t\tmodule: \"ChildProcess\",\n\t\t\t\tmethod: \"confirmGroupStopped\",\n\t\t\t\tdescription: `Process group ${process.pid} still exists after the two-second confirmation window`,\n\t\t\t})\n\t\t}\n\t\tif (Exit.isFailure(killed)) {\n\t\t\t// Absence resolves only a failed signal to a nonexistent group, not other cleanup failures.\n\t\t\tconst remaining = Array.filter(\n\t\t\t\tkilled.cause.reasons,\n\t\t\t\t(reason) =>\n\t\t\t\t\t!(Cause.isFailReason(reason) && isSignalFailure(reason.error)),\n\t\t\t)\n\t\t\tif (remaining.length > 0)\n\t\t\t\treturn yield* Effect.failCause(Cause.fromReasons(remaining))\n\t\t}\n\t})\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,gBACZ,SACA,SACA,QAA4B,gBAE5B,OAAO,IAAI,aAAa;CACvB,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;AAOF,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"}
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, (name) => Effect.gen(function* () {
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, (entry, index) => Effect.gen(function* () {
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)), (file) => Effect.gen(function* () {
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;
@@ -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
@@ -3,7 +3,7 @@
3
3
  "engines": {
4
4
  "node": ">=22.19.0"
5
5
  },
6
- "version": "0.3.0",
6
+ "version": "0.3.2",
7
7
  "type": "module",
8
8
  "description": "Installs dependencies with npm, pnpm or Yarn, and rolls back on failure",
9
9
  "license": "Apache-2.0",
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.decodeUnknownOption(
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
- return yield* Option.match(decodePackageManager(manifest.packageManager), {
222
- onNone: () =>
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
- onSome: ([name, , version]) =>
228
- Effect.succeed(agentOf(name, `${version.major}`)),
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.Effect<
368
+ ): Effect.fn.Return<
366
369
  PackageManager,
367
370
  Error,
368
371
  FileSystem.FileSystem | Path.Path | ChildProcessSpawner.ChildProcessSpawner
369
- > =>
370
- Effect.gen(function* () {
371
- const fs = yield* FileSystem.FileSystem
372
- const path = yield* Path.Path
373
- const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
374
- const directory = path.resolve(options.directory)
375
- yield* inspectManagedFiles(fs, path, directory, backupFiles)
376
- const agent = yield* detectAgent({ ...options, directory })
377
- const lock = yield* Semaphore.make(1)
378
- const transaction = yield* Ref.make<ManagerState>(ManagerState.Ready())
379
- const manifestFile = path.join(directory, "package.json")
380
- const readManifest = readSource(fs, manifestFile).pipe(
381
- Effect.flatMap(decodeManifest),
382
- )
383
- const installed = Schema.decodeUnknownEffect(
384
- Schema.fromJsonString(
385
- Schema.Struct({ version: Schema.optionalKey(Schema.String) }),
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 stateOf = Effect.fnUntraced(function* (manifest: Manifest) {
389
- if (yield* fs.exists(path.join(directory, ".pnp.cjs")))
390
- return yield* PackageManagerError.make({
391
- operation: "state",
392
- message:
393
- "Plug'n'Play is not supported; configure Yarn with nodeLinker: node-modules",
394
- })
395
- const deps = Array.reduce(
396
- fields,
397
- {} as Readonly<Record<string, string>>,
398
- (all, field) => ({ ...all, ...manifest[field] }),
399
- )
400
- const entries = yield* Effect.forEach(
401
- Record.toEntries(deps),
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
- if (Option.isNone(source))
425
- return [
426
- name,
427
- { spec, version: undefined, path: undefined },
428
- ] as const
429
- const pkg = yield* installed(source.value)
430
- return [
431
- name,
432
- {
433
- spec,
434
- version: pkg.version,
435
- path: yield* fs.realPath(path.dirname(file)),
436
- },
437
- ] as const
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
- { concurrency: 16 },
440
- )
441
- return HashMap.fromIterable<string, PackageState>(entries)
442
- })
443
- const readState = Effect.flatMap(readManifest, stateOf)
444
- const termination: ChildProcess.KillOptions = {
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
- (process) =>
473
- Effect.gen(function* () {
474
- const code = yield* process.exitCode.pipe(
475
- Effect.timeoutOrElse({
476
- duration: "10 minutes",
477
- orElse: () =>
478
- PackageManagerError.make({
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
- exitCode: code,
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
- }, Effect.scoped)
514
- // AbortSignal does not join accepted OS writes. Keep the writer owned until completion.
515
- const withFileWriter = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
516
- Effect.acquireUseRelease(
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.FileWrite() }),
516
+ ManagerState.Active({ writer: WriterState.ManagedWritersIdle() }),
520
517
  ),
521
- () => effect,
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({ writer: WriterState.ManagedWritersIdle() }),
581
+ ManagerState.Active({
582
+ writer: WriterState.ManagedWritersIdle(),
583
+ }),
526
584
  ),
527
- ).pipe(Effect.uninterruptible)
528
-
529
- const install = (changes: Changes) =>
530
- lock.withPermits(1)(
531
- Effect.gen(function* () {
532
- const state = yield* Ref.get(transaction)
533
- if (state._tag === "RecoveryRequired") return yield* state.error
534
- const validated = yield* Schema.decodeEffect(Changes)(changes)
535
- const inspected = yield* inspectManagedFiles(
536
- fs,
537
- path,
538
- directory,
539
- backupFiles,
540
- )
541
- const backups = yield* Effect.forEach(
542
- inspected,
543
- ({ name, file, metadata }) =>
544
- Effect.gen(function* () {
545
- const contents = yield* Option.match(metadata, {
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
- return {
663
- ...completed.value,
664
- snapshotCleanupFailure: completed.snapshotCleanupFailure,
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
- directory,
670
- agent,
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.gen(function* () {
58
- const killed = yield* Effect.suspend(() => process.kill(options)).pipe(
59
- Effect.exit,
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
- // Windows retains its as-is adapter contract; POSIX requires group evidence.
62
- if (NodeProcess.platform === "win32") return yield* killed
63
- const checked = yield* Effect.suspend(() => probe(process.pid)).pipe(
64
- Effect.exit,
65
- Effect.repeat({
66
- while: (exit) => Exit.isSuccess(exit) && exit.value,
67
- times: 100,
68
- schedule: Schedule.spaced("20 millis"),
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 (Exit.isFailure(checked))
72
- return yield* Effect.failCause(
73
- Exit.isFailure(killed)
74
- ? Cause.combine(killed.cause, checked.cause)
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(names, (name) =>
101
- Effect.gen(function* () {
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(backups, (entry, index) =>
169
- Effect.gen(function* () {
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
- Effect.gen(function* () {
236
- yield* file.writeAll(contents.bytes)
237
- const current = yield* file.stat
238
- const { uid, gid } = contents
239
- if (
240
- Option.isSome(uid) &&
241
- Option.isSome(gid) &&
242
- (!Equal.equals(uid, current.uid) ||
243
- !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
- }),
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