1import * as NodeServices from "@effect/platform-node/NodeServices";
2import { assert, describe, it } from "@effect/vitest";
3import * as Effect from "effect/Effect";
4import * as FileSystem from "effect/FileSystem";
5import * as Path from "effect/Path";
6import { Command, CliError } from "effect/cli";
7
8import {
9 mergePlatformUpdateManifests,
10 mergeUpdateManifestsCommand,
11 parsePlatformUpdateManifest,
12 serializePlatformUpdateManifest,
13} from "./merge-update-manifests.ts";
14
15const runCli = Command.runWith(mergeUpdateManifestsCommand, { version: "0.0.0" });
16
17describe("merge-update-manifests", () => {
18 it("merges arm64 and x64 macOS update manifests into one multi-arch manifest", () => {
19 const arm64 = parsePlatformUpdateManifest(
20 "mac",
21 `version: 0.0.4
22files:
23 - url: T3-Code-0.0.4-arm64.zip
24 sha512: arm64zip
25 size: 125621344
26 - url: T3-Code-0.0.4-arm64.dmg
27 sha512: arm64dmg
28 size: 131754935
29path: T3-Code-0.0.4-arm64.zip
30sha512: arm64zip
31releaseDate: '2026-03-07T10:32:14.587Z'
32`,
33 "latest-mac.yml",
34 );
35
36 const x64 = parsePlatformUpdateManifest(
37 "mac",
38 `version: 0.0.4
39files:
40 - url: T3-Code-0.0.4-x64.zip
41 sha512: x64zip
42 size: 132000112
43 - url: T3-Code-0.0.4-x64.dmg
44 sha512: x64dmg
45 size: 138148807
46path: T3-Code-0.0.4-x64.zip
47sha512: x64zip
48releaseDate: '2026-03-07T10:36:07.540Z'
49`,
50 "latest-mac-x64.yml",
51 );
52
53 const merged = mergePlatformUpdateManifests("mac", arm64, x64);
54
55 assert.equal(merged.version, "0.0.4");
56 assert.equal(merged.releaseDate, "2026-03-07T10:36:07.540Z");
57 assert.deepStrictEqual(
58 merged.files.map((file) => file.url),
59 [
60 "T3-Code-0.0.4-arm64.zip",
61 "T3-Code-0.0.4-arm64.dmg",
62 "T3-Code-0.0.4-x64.zip",
63 "T3-Code-0.0.4-x64.dmg",
64 ],
65 );
66
67 const serialized = serializePlatformUpdateManifest("mac", merged);
68 assert.ok(!serialized.includes("path:"));
69 assert.equal((serialized.match(/- url:/g) ?? []).length, 4);
70 });
71
72 it("merges arm64 and x64 Windows update manifests into one multi-arch manifest", () => {
73 const arm64 = parsePlatformUpdateManifest(
74 "win",
75 `version: 0.0.4
76files:
77 - url: T3-Code-0.0.4-arm64.exe
78 sha512: arm64exe
79 size: 125621344
80 - url: T3-Code-0.0.4-arm64.exe.blockmap
81 sha512: arm64blockmap
82 size: 131754
83path: T3-Code-0.0.4-arm64.exe
84sha512: arm64exe
85releaseDate: '2026-03-07T10:32:14.587Z'
86`,
87 "latest-win-arm64.yml",
88 );
89
90 const x64 = parsePlatformUpdateManifest(
91 "win",
92 `version: 0.0.4
93files:
94 - url: T3-Code-0.0.4-x64.exe
95 sha512: x64exe
96 size: 132000112
97 - url: T3-Code-0.0.4-x64.exe.blockmap
98 sha512: x64blockmap
99 size: 138148
100path: T3-Code-0.0.4-x64.exe
101sha512: x64exe
102releaseDate: '2026-03-07T10:36:07.540Z'
103`,
104 "latest-win-x64.yml",
105 );
106
107 const merged = mergePlatformUpdateManifests("win", arm64, x64);
108
109 assert.equal(merged.version, "0.0.4");
110 assert.equal(merged.releaseDate, "2026-03-07T10:36:07.540Z");
111 assert.deepStrictEqual(
112 merged.files.map((file) => file.url),
113 [
114 "T3-Code-0.0.4-arm64.exe",
115 "T3-Code-0.0.4-arm64.exe.blockmap",
116 "T3-Code-0.0.4-x64.exe",
117 "T3-Code-0.0.4-x64.exe.blockmap",
118 ],
119 );
120
121 const serialized = serializePlatformUpdateManifest("win", merged);
122 assert.ok(!serialized.includes("path:"));
123 assert.equal((serialized.match(/- url:/g) ?? []).length, 4);
124 });
125
126 it("rejects mismatched manifest versions", () => {
127 const primary = parsePlatformUpdateManifest(
128 "win",
129 `version: 0.0.4
130files:
131 - url: T3-Code-0.0.4-arm64.exe
132 sha512: arm64exe
133 size: 1
134releaseDate: '2026-03-07T10:32:14.587Z'
135`,
136 "latest-win-arm64.yml",
137 );
138
139 const secondary = parsePlatformUpdateManifest(
140 "win",
141 `version: 0.0.5
142files:
143 - url: T3-Code-0.0.5-x64.exe
144 sha512: x64exe
145 size: 1
146releaseDate: '2026-03-07T10:36:07.540Z'
147`,
148 "latest-win-x64.yml",
149 );
150
151 assert.throws(
152 () => mergePlatformUpdateManifests("win", primary, secondary),
153 /different versions/,
154 );
155 });
156
157 it("preserves quoted scalars as strings", () => {
158 const manifest = parsePlatformUpdateManifest(
159 "mac",
160 `version: '1.0'
161files:
162 - url: T3-Code-1.0-x64.zip
163 sha512: zipsha
164 size: 1
165releaseName: 'true'
166minimumSystemVersion: '13.0'
167stagingPercentage: 50
168releaseDate: '2026-03-07T10:36:07.540Z'
169`,
170 "latest-mac.yml",
171 );
172
173 assert.equal(manifest.version, "1.0");
174 assert.equal(manifest.extras.releaseName, "true");
175 assert.equal(manifest.extras.minimumSystemVersion, "13.0");
176 assert.equal(manifest.extras.stagingPercentage, 50);
177 });
178
179 it("round-trips numeric-looking versions as strings", () => {
180 const original = parsePlatformUpdateManifest(
181 "win",
182 `version: '1.0'
183files:
184 - url: T3-Code-1.0-x64.exe
185 sha512: exesha
186 size: 1
187releaseDate: '2026-03-07T10:36:07.540Z'
188`,
189 "latest-win-x64.yml",
190 );
191
192 const serialized = serializePlatformUpdateManifest("win", original);
193 assert.ok(serialized.includes("version: '1.0'"));
194
195 const reparsed = parsePlatformUpdateManifest("win", serialized, "latest-win-x64.yml");
196 assert.equal(reparsed.version, "1.0");
197 });
198});
199
200it.layer(NodeServices.layer)("merge-update-manifests cli", (it) => {
201 const arm64MacManifest = `version: 0.0.4
202files:
203 - url: T3-Code-0.0.4-arm64.zip
204 sha512: arm64zip
205 size: 125621344
206 - url: T3-Code-0.0.4-arm64.dmg
207 sha512: arm64dmg
208 size: 131754935
209path: T3-Code-0.0.4-arm64.zip
210sha512: arm64zip
211releaseDate: '2026-03-07T10:32:14.587Z'
212`;
213
214 const x64MacManifest = `version: 0.0.4
215files:
216 - url: T3-Code-0.0.4-x64.zip
217 sha512: x64zip
218 size: 132000112
219 - url: T3-Code-0.0.4-x64.dmg
220 sha512: x64dmg
221 size: 138148807
222path: T3-Code-0.0.4-x64.zip
223sha512: x64zip
224releaseDate: '2026-03-07T10:36:07.540Z'
225`;
226
227 it.effect("writes the merged manifest back to the primary path by default", () =>
228 Effect.gen(function* () {
229 const fs = yield* FileSystem.FileSystem;
230 const path = yield* Path.Path;
231 const baseDir = yield* fs.makeTempDirectoryScoped({
232 prefix: "merge-update-manifests-cli-",
233 });
234 const primaryPath = path.join(baseDir, "latest-mac.yml");
235 const secondaryPath = path.join(baseDir, "latest-mac-x64.yml");
236
237 yield* fs.writeFileString(primaryPath, arm64MacManifest);
238 yield* fs.writeFileString(secondaryPath, x64MacManifest);
239
240 yield* runCli(["--platform", "mac", primaryPath, secondaryPath]);
241
242 const merged = yield* fs.readFileString(primaryPath);
243 assert.ok(merged.includes("T3-Code-0.0.4-arm64.zip"));
244 assert.ok(merged.includes("T3-Code-0.0.4-x64.zip"));
245 assert.ok(!merged.includes("path:"));
246 }),
247 );
248
249 it.effect("writes the merged manifest to an explicit output path", () =>
250 Effect.gen(function* () {
251 const fs = yield* FileSystem.FileSystem;
252 const path = yield* Path.Path;
253 const baseDir = yield* fs.makeTempDirectoryScoped({
254 prefix: "merge-update-manifests-cli-output-",
255 });
256 const primaryPath = path.join(baseDir, "latest-win-arm64.yml");
257 const secondaryPath = path.join(baseDir, "latest-win-x64.yml");
258 const outputPath = path.join(baseDir, "latest-win.yml");
259
260 yield* fs.writeFileString(
261 primaryPath,
262 `version: 0.0.4
263files:
264 - url: T3-Code-0.0.4-arm64.exe
265 sha512: arm64exe
266 size: 125621344
267releaseDate: '2026-03-07T10:32:14.587Z'
268`,
269 );
270 yield* fs.writeFileString(
271 secondaryPath,
272 `version: 0.0.4
273files:
274 - url: T3-Code-0.0.4-x64.exe
275 sha512: x64exe
276 size: 132000112
277releaseDate: '2026-03-07T10:36:07.540Z'
278`,
279 );
280
281 yield* runCli(["--platform", "win", primaryPath, secondaryPath, outputPath]);
282
283 const merged = yield* fs.readFileString(outputPath);
284 assert.ok(merged.includes("T3-Code-0.0.4-arm64.exe"));
285 assert.ok(merged.includes("T3-Code-0.0.4-x64.exe"));
286 }),
287 );
288
289 it.effect("rejects invalid platform values during cli parsing", () =>
290 Effect.gen(function* () {
291 const error = yield* runCli(["--platform", "linux", "a.yml", "b.yml"]).pipe(Effect.flip);
292
293 if (!CliError.isCliError(error)) {
294 assert.fail(`Expected CliError, got ${String(error)}`);
295 }
296
297 const platformError =
298 error._tag === "ShowHelp" ? (error.errors[0] as CliError.CliError | undefined) : error;
299
300 if (!platformError || platformError._tag !== "InvalidValue") {
301 assert.fail(`Expected InvalidValue, got ${String(platformError?._tag)}`);
302 }
303
304 assert.equal(platformError.option, "platform");
305 assert.equal(platformError.value, "linux");
306 }),
307 );
308});