1import * as NodeServices from "@effect/platform-node/NodeServices";
2import { assert, it } from "@effect/vitest";
3import * as Deferred from "effect/Deferred";
4import * as Effect from "effect/Effect";
5import * as FileSystem from "effect/FileSystem";
6import * as Fiber from "effect/Fiber";
7import * as Path from "effect/Path";
8import { HttpClient, HttpClientResponse } from "effect/http";
9import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
10
11import * as ProcessRunner from "../processRunner.ts";
12import {
13 ensurePinnedRuntimeInstalled,
14 pinnedRuntimeCommand,
15 pinnedRuntimePaths,
16 PinnedRuntimeInstallError,
17 type PinnedRuntimeProgress,
18} from "./pinnedRuntime.ts";
19
20// Every install fetches the release archive, checks it against SHA256SUMS,
21// and unpacks it with tar. The fake client serves both files; the fake runner
22// stands in for tar and drops the executable where extraction would.
23const version = "1.2.3";
24const archiveName = `t3-${version}-linux-x64.tar.gz`;
25const archiveBytes = new TextEncoder().encode("not really a tarball");
26const archiveHex = (bytes: Uint8Array) =>
27 Effect.promise(() => crypto.subtle.digest("SHA-256", bytes)).pipe(
28 Effect.map((digest) =>
29 Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join(""),
30 ),
31 );
32const validChecksums = archiveHex(archiveBytes).pipe(
33 Effect.map((hex) => `${hex} ${archiveName}\n`),
34);
35const releaseHttpClient = (checksums: string, requests: string[] = []) =>
36 HttpClient.make((request) => {
37 requests.push(request.url);
38 const body = request.url.endsWith("/SHA256SUMS") ? checksums : archiveBytes;
39 return Effect.succeed(HttpClientResponse.fromWeb(request, new Response(body)));
40 });
41const extractingRunner = (fs: FileSystem.FileSystem, path: Path.Path, commands: string[] = []) =>
42 ProcessRunner.ProcessRunner.of({
43 run: (input) =>
44 Effect.gen(function* () {
45 commands.push(input.command);
46 const targetIndex = input.args.indexOf("-C");
47 const stagingDir = input.args[targetIndex + 1];
48 if (input.command !== "tar" || stagingDir === undefined) {
49 return yield* Effect.die(`unexpected command ${input.command}`);
50 }
51 yield* fs.writeFileString(path.join(stagingDir, "t3"), "#!/bin/sh\n").pipe(Effect.orDie);
52 return {
53 stdout: "",
54 stderr: "",
55 code: ChildProcessSpawner.ExitCode(0),
56 timedOut: false,
57 stdoutTruncated: false,
58 stderrTruncated: false,
59 stdoutInvalidUtf8: false,
60 stderrInvalidUtf8: false,
61 };
62 }),
63 });
64
65it.layer(NodeServices.layer)("ensurePinnedRuntimeInstalled", (it) => {
66 it.effect("installs the verified release archive as the runtime executable", () =>
67 Effect.gen(function* () {
68 const fs = yield* FileSystem.FileSystem;
69 const path = yield* Path.Path;
70 const baseDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-pinned-archive-" });
71 const requests: string[] = [];
72 const commands: string[] = [];
73 const paths = yield* ensurePinnedRuntimeInstalled({
74 baseDir,
75 version,
76 fs,
77 path,
78 platform: "linux",
79 arch: "x64",
80 httpClient: releaseHttpClient(yield* validChecksums, requests),
81 releaseBaseUrl: "https://releases.example/download",
82 runner: extractingRunner(fs, path, commands),
83 validate: (staging) =>
84 fs.exists(staging.entryPath).pipe(
85 Effect.flatMap((exists) => (exists ? Effect.void : Effect.die("missing runtime"))),
86 Effect.orDie,
87 ),
88 });
89 assert.equal(paths.entryPath, path.join(paths.versionDir, "t3"));
90 assert.deepEqual(pinnedRuntimeCommand(paths), { command: paths.entryPath, args: [] });
91 assert.deepEqual(requests, [
92 `https://releases.example/download/v${version}/SHA256SUMS`,
93 `https://releases.example/download/v${version}/${archiveName}`,
94 ]);
95 assert.deepEqual(commands, ["tar"]);
96 assert.equal(yield* fs.readFileString(paths.sentinelPath), `${version}\n`);
97 assert.isFalse(yield* fs.exists(path.join(paths.versionDir, "t3-runtime-archive")));
98 }),
99 );
100
101 it.effect.each([true, false])(
102 "reports bytes before completion, then verifies and extracts (known size: %s)",
103 (knownSize) =>
104 Effect.gen(function* () {
105 const fs = yield* FileSystem.FileSystem;
106 const path = yield* Path.Path;
107 const baseDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-pinned-progress-" });
108 const firstChunk = yield* Deferred.make<void>();
109 let archiveController: ReadableStreamDefaultController<Uint8Array> | undefined;
110 const checksums = yield* validChecksums;
111 const progress: PinnedRuntimeProgress[] = [];
112 const client = HttpClient.make((request) =>
113 Effect.succeed(
114 HttpClientResponse.fromWeb(
115 request,
116 request.url.endsWith("/SHA256SUMS")
117 ? new Response(checksums)
118 : new Response(
119 new ReadableStream({
120 start(controller) {
121 archiveController = controller;
122 controller.enqueue(archiveBytes.slice(0, 4));
123 },
124 }),
125 { headers: knownSize ? { "content-length": String(archiveBytes.length) } : {} },
126 ),
127 ),
128 ),
129 );
130 const install = yield* ensurePinnedRuntimeInstalled({
131 baseDir,
132 version,
133 fs,
134 path,
135 platform: "linux",
136 arch: "x64",
137 httpClient: client,
138 runner: extractingRunner(fs, path),
139 validate: () => Effect.void,
140 onProgress: (event) => {
141 progress.push(event);
142 if (event.stage === "download" && event.received === 4) {
143 Deferred.doneUnsafe(firstChunk, Effect.void);
144 }
145 },
146 }).pipe(Effect.forkScoped);
147 yield* Deferred.await(firstChunk);
148 assert.deepEqual(progress.at(-1), {
149 stage: "download",
150 received: 4,
151 total: knownSize ? archiveBytes.length : undefined,
152 });
153 assert.isFalse(progress.some((event) => event.stage === "extract"));
154 assert.isDefined(archiveController);
155 archiveController!.enqueue(archiveBytes.slice(4));
156 archiveController!.close();
157 const installed = yield* Fiber.join(install);
158 assert.deepEqual(progress.slice(-4), [
159 { stage: "download", received: archiveBytes.length, total: archiveBytes.length },
160 { stage: "verify" },
161 { stage: "extract" },
162 { stage: "validate" },
163 ]);
164 assert.equal(yield* fs.readFileString(installed.sentinelPath), `${version}\n`);
165 }),
166 );
167
168 it.effect("cleans up an interrupted download without reporting verification or extraction", () =>
169 Effect.gen(function* () {
170 const fs = yield* FileSystem.FileSystem;
171 const path = yield* Path.Path;
172 const baseDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-pinned-progress-failed-" });
173 const checksums = yield* validChecksums;
174 const progress: PinnedRuntimeProgress[] = [];
175 let cancelled = false;
176 const client = HttpClient.make((request) =>
177 Effect.succeed(
178 HttpClientResponse.fromWeb(
179 request,
180 request.url.endsWith("/SHA256SUMS")
181 ? new Response(checksums)
182 : new Response(
183 new ReadableStream({
184 start(controller) {
185 controller.enqueue(archiveBytes.slice(0, 4));
186 },
187 cancel() {
188 cancelled = true;
189 },
190 }),
191 ),
192 ),
193 ),
194 );
195 const firstChunk = yield* Deferred.make<void>();
196 const install = yield* ensurePinnedRuntimeInstalled({
197 baseDir,
198 version,
199 fs,
200 path,
201 platform: "linux",
202 arch: "x64",
203 httpClient: client,
204 runner: extractingRunner(fs, path),
205 validate: () => Effect.die("must not validate an interrupted archive"),
206 onProgress: (event) => {
207 progress.push(event);
208 if (event.stage === "download" && event.received === 4)
209 Deferred.doneUnsafe(firstChunk, Effect.void);
210 },
211 }).pipe(Effect.forkScoped);
212 yield* Deferred.await(firstChunk);
213 yield* Fiber.interrupt(install);
214 assert.deepEqual(progress.at(-1), { stage: "download", received: 4, total: undefined });
215 assert.isTrue(progress.every((event) => event.stage === "download"));
216 assert.isTrue(cancelled);
217 assert.deepEqual(yield* fs.readDirectory(path.join(baseDir, "runtime", "versions")), []);
218 }),
219 );
220
221 it.effect("refuses an archive whose checksum does not match the release", () =>
222 Effect.gen(function* () {
223 const fs = yield* FileSystem.FileSystem;
224 const path = yield* Path.Path;
225 const baseDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-pinned-archive-bad-" });
226 const commands: string[] = [];
227 const error = yield* ensurePinnedRuntimeInstalled({
228 baseDir,
229 version,
230 fs,
231 path,
232 platform: "linux",
233 arch: "x64",
234 httpClient: releaseHttpClient(`${"0".repeat(64)} ${archiveName}\n`),
235 runner: extractingRunner(fs, path, commands),
236 validate: () => Effect.die("must not validate an unverified archive"),
237 }).pipe(Effect.flip);
238 assert.instanceOf(error, PinnedRuntimeInstallError);
239 assert.equal(error.step, "verifying the t3 release archive checksum");
240 assert.deepEqual(commands, []);
241 assert.deepEqual(yield* fs.readDirectory(path.join(baseDir, "runtime", "versions")), []);
242 }),
243 );
244
245 it.effect("validates a staging tree before atomically publishing it", () =>
246 Effect.gen(function* () {
247 const fs = yield* FileSystem.FileSystem;
248 const path = yield* Path.Path;
249 const baseDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-pinned-runtime-test-" });
250 const finalPaths = pinnedRuntimePaths(path, baseDir, version, "linux");
251 let validatedDirectory = "";
252
253 const installed = yield* ensurePinnedRuntimeInstalled({
254 baseDir,
255 version,
256 fs,
257 path,
258 platform: "linux",
259 arch: "x64",
260 httpClient: releaseHttpClient(yield* validChecksums),
261 runner: extractingRunner(fs, path),
262 validate: (staging) =>
263 Effect.gen(function* () {
264 validatedDirectory = staging.versionDir;
265 assert.isFalse(yield* fs.exists(finalPaths.versionDir));
266 assert.isTrue(yield* fs.exists(staging.entryPath));
267 }).pipe(Effect.orDie),
268 });
269
270 assert.notEqual(validatedDirectory, finalPaths.versionDir);
271 assert.deepEqual(installed, finalPaths);
272 assert.isTrue(yield* fs.exists(finalPaths.entryPath));
273 assert.equal(yield* fs.readFileString(finalPaths.sentinelPath), `${version}\n`);
274 }),
275 );
276
277 it.effect("removes staging and leaves no final runtime when validation fails", () =>
278 Effect.gen(function* () {
279 const fs = yield* FileSystem.FileSystem;
280 const path = yield* Path.Path;
281 const baseDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-pinned-runtime-test-" });
282 const finalPaths = pinnedRuntimePaths(path, baseDir, version, "linux");
283
284 yield* ensurePinnedRuntimeInstalled({
285 baseDir,
286 version,
287 fs,
288 path,
289 platform: "linux",
290 arch: "x64",
291 httpClient: releaseHttpClient(yield* validChecksums),
292 runner: extractingRunner(fs, path),
293 validate: () =>
294 Effect.fail(new PinnedRuntimeInstallError({ step: "validating the staged runtime" })),
295 }).pipe(Effect.flip);
296
297 assert.isFalse(yield* fs.exists(finalPaths.versionDir));
298 assert.deepEqual(
299 (yield* fs.readDirectory(path.dirname(finalPaths.versionDir))).filter((entry) =>
300 entry.startsWith(".staging-"),
301 ),
302 [],
303 );
304 }),
305 );
306
307 it.effect("replaces an incomplete pinned runtime", () =>
308 Effect.gen(function* () {
309 const fs = yield* FileSystem.FileSystem;
310 const path = yield* Path.Path;
311 const baseDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-pinned-runtime-repair-" });
312 const finalPaths = pinnedRuntimePaths(path, baseDir, version, "linux");
313 yield* fs.makeDirectory(finalPaths.versionDir, { recursive: true });
314 yield* fs.writeFileString(path.join(finalPaths.versionDir, "partial"), "incomplete\n");
315
316 yield* ensurePinnedRuntimeInstalled({
317 baseDir,
318 version,
319 fs,
320 path,
321 platform: "linux",
322 arch: "x64",
323 httpClient: releaseHttpClient(yield* validChecksums),
324 runner: extractingRunner(fs, path),
325 validate: () => Effect.void,
326 });
327
328 assert.isFalse(yield* fs.exists(path.join(finalPaths.versionDir, "partial")));
329 assert.isTrue(yield* fs.exists(finalPaths.entryPath));
330 }),
331 );
332
333 it.effect("preserves a completed runtime when validation fails", () =>
334 Effect.gen(function* () {
335 const fs = yield* FileSystem.FileSystem;
336 const path = yield* Path.Path;
337 const baseDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-pinned-runtime-repair-" });
338 const finalPaths = pinnedRuntimePaths(path, baseDir, version, "linux");
339 yield* fs.makeDirectory(path.dirname(finalPaths.entryPath), { recursive: true });
340 yield* fs.writeFileString(finalPaths.entryPath, "broken\n");
341 yield* fs.writeFileString(finalPaths.sentinelPath, `${version}\n`);
342
343 let validations = 0;
344 const requests: string[] = [];
345 yield* ensurePinnedRuntimeInstalled({
346 baseDir,
347 version,
348 fs,
349 path,
350 platform: "linux",
351 arch: "x64",
352 httpClient: releaseHttpClient(yield* validChecksums, requests),
353 runner: extractingRunner(fs, path),
354 validate: (paths) =>
355 Effect.gen(function* () {
356 validations += 1;
357 const source = yield* fs.readFileString(paths.entryPath).pipe(Effect.orDie);
358 if (source === "broken\n") {
359 return yield* new PinnedRuntimeInstallError({ step: "validating the runtime" });
360 }
361 }),
362 }).pipe(Effect.flip);
363
364 assert.equal(validations, 1);
365 assert.deepEqual(requests, []);
366 assert.equal(yield* fs.readFileString(finalPaths.entryPath), "broken\n");
367 }),
368 );
369
370 it.effect("removes staging when installation is interrupted", () =>
371 Effect.gen(function* () {
372 const fs = yield* FileSystem.FileSystem;
373 const path = yield* Path.Path;
374 const baseDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-pinned-runtime-interrupt-" });
375 const started = yield* Deferred.make<void>();
376 const runner = ProcessRunner.ProcessRunner.of({
377 run: () => Deferred.succeed(started, undefined).pipe(Effect.andThen(Effect.never)),
378 });
379 const install = yield* ensurePinnedRuntimeInstalled({
380 baseDir,
381 version,
382 fs,
383 path,
384 platform: "linux",
385 arch: "x64",
386 httpClient: releaseHttpClient(yield* validChecksums),
387 runner,
388 validate: () => Effect.void,
389 }).pipe(Effect.forkScoped);
390
391 yield* Deferred.await(started);
392 yield* Fiber.interrupt(install);
393 const versionsDir = path.join(baseDir, "runtime", "versions");
394 assert.deepEqual(yield* fs.readDirectory(versionsDir), []);
395 }),
396 );
397});