1import * as NodeServices from "@effect/platform-node/NodeServices";
2import { assert, it } from "@effect/vitest";
3import * as Config from "effect/Config";
4import * as ConfigProvider from "effect/ConfigProvider";
5import * as Effect from "effect/Effect";
6import * as FileSystem from "effect/FileSystem";
7import * as Path from "effect/Path";
8import * as PlatformError from "effect/PlatformError";
9
10import {
11 readDesktopBaseVersion,
12 resolveNightlyBaseVersion,
13 resolveNightlyReleaseMetadata,
14 resolveNightlyTargetVersion,
15 writeNightlyReleaseOutput,
16} from "./resolve-nightly-release.ts";
17
18it("strips prerelease and build metadata when deriving the nightly base version", () => {
19 assert.equal(resolveNightlyBaseVersion("0.0.17"), "0.0.17");
20 assert.equal(resolveNightlyBaseVersion("9.9.9-smoke.0"), "9.9.9");
21 assert.equal(resolveNightlyBaseVersion("1.2.3-beta.4+build.9"), "1.2.3");
22});
23
24it.effect("bumps the patch version before deriving nightly prerelease versions", () =>
25 Effect.gen(function* () {
26 assert.equal(yield* resolveNightlyTargetVersion("0.0.17"), "0.0.18");
27 assert.equal(yield* resolveNightlyTargetVersion("9.9.9-smoke.0"), "9.9.10");
28 assert.equal(yield* resolveNightlyTargetVersion("1.2.3-beta.4+build.9"), "1.2.4");
29 }),
30);
31
32it.effect("reports the invalid desktop package version", () =>
33 Effect.gen(function* () {
34 const error = yield* resolveNightlyTargetVersion("nightly").pipe(Effect.flip);
35
36 assert.equal(error._tag, "InvalidDesktopPackageVersionError");
37 assert.equal(error.version, "nightly");
38 assert.equal(error.message, "Invalid desktop package version 'nightly'.");
39 }),
40);
41
42it("derives nightly metadata including the short commit sha in the release name", () => {
43 assert.deepStrictEqual(
44 resolveNightlyReleaseMetadata("9.9.10", "20260413", 321, "abcdef1234567890"),
45 {
46 baseVersion: "9.9.10",
47 version: "9.9.10-nightly.20260413.321",
48 tag: "v9.9.10-nightly.20260413.321",
49 name: "T3 Code Nightly 9.9.10-nightly.20260413.321 (abcdef123456)",
50 shortSha: "abcdef123456",
51 },
52 );
53});
54
55it("derives preview metadata under its own prerelease identifier", () => {
56 assert.deepStrictEqual(
57 resolveNightlyReleaseMetadata("9.9.10", "20260413", 321, "abcdef1234567890", "preview"),
58 {
59 baseVersion: "9.9.10",
60 version: "9.9.10-preview.20260413.321",
61 tag: "v9.9.10-preview.20260413.321",
62 name: "T3 Code Preview (maintainer test build, do not install) 9.9.10-preview.20260413.321 (abcdef123456)",
63 shortSha: "abcdef123456",
64 },
65 );
66});
67
68it.effect("preserves the GITHUB_OUTPUT configuration cause", () => {
69 const metadata = resolveNightlyReleaseMetadata("1.2.4", "20260620", 42, "abcdef1234567890");
70 const configCause = new ConfigProvider.SourceError({ message: "environment unavailable" });
71
72 return Effect.gen(function* () {
73 const configError = yield* writeNightlyReleaseOutput(metadata, true).pipe(
74 Effect.provideService(FileSystem.FileSystem, FileSystem.makeNoop({})),
75 Effect.provideService(
76 ConfigProvider.ConfigProvider,
77 ConfigProvider.make(() => Effect.fail(configCause)),
78 ),
79 Effect.flip,
80 );
81
82 if (configError._tag !== "NightlyReleaseGitHubOutputConfigError") {
83 return assert.fail(`Unexpected error: ${configError._tag}`);
84 }
85 assert.instanceOf(configError.cause, Config.ConfigError);
86 assert.strictEqual(configError.cause.cause, configCause);
87 assert.notInclude(configError.message, configCause.message);
88 });
89});
90
91it.layer(NodeServices.layer)("readDesktopBaseVersion", (it) => {
92 it.effect("preserves desktop package read context and its platform cause", () =>
93 Effect.gen(function* () {
94 const fs = yield* FileSystem.FileSystem;
95 const path = yield* Path.Path;
96 const rootDir = yield* fs.makeTempDirectoryScoped({
97 prefix: "resolve-nightly-release-read-",
98 });
99 const packageJsonPath = path.join(rootDir, "apps/desktop/package.json");
100
101 const error = yield* readDesktopBaseVersion(rootDir).pipe(Effect.flip);
102
103 if (error._tag !== "NightlyReleaseDesktopPackageError") {
104 return assert.fail(`Unexpected error: ${error._tag}`);
105 }
106 assert.equal(error.operation, "read");
107 assert.equal(error.packageJsonPath, packageJsonPath);
108 assert.instanceOf(error.cause, PlatformError.PlatformError);
109 assert.notInclude(error.message, String((error.cause as Error).message));
110 }),
111 );
112
113 it.effect("preserves desktop package decode context and its schema cause", () =>
114 Effect.gen(function* () {
115 const fs = yield* FileSystem.FileSystem;
116 const path = yield* Path.Path;
117 const rootDir = yield* fs.makeTempDirectoryScoped({
118 prefix: "resolve-nightly-release-decode-",
119 });
120 const packageJsonPath = path.join(rootDir, "apps/desktop/package.json");
121 yield* fs.makeDirectory(path.dirname(packageJsonPath), { recursive: true });
122 yield* fs.writeFileString(packageJsonPath, "{");
123
124 const error = yield* readDesktopBaseVersion(rootDir).pipe(Effect.flip);
125
126 if (error._tag !== "NightlyReleaseDesktopPackageError") {
127 return assert.fail(`Unexpected error: ${error._tag}`);
128 }
129 assert.equal(error.operation, "decode");
130 assert.equal(error.packageJsonPath, packageJsonPath);
131 assert.ok(error.cause !== undefined);
132 assert.notInclude(error.message, String((error.cause as Error).message));
133 }),
134 );
135});