authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-06-20 19:09:02-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-06-20 19:09:02-07:00
log8c2d33abde95fa21595f4e3b49ff3c5ba2e3cab4
tree38b6182feb0e02684e7ec532842cdcc845c55025
parent60dc4af916d0a62840d74fe95fe104e8b1c4ac92
signature Signed by PGP key B5690EEEBB952194

[codex] Structure release package updater failures (#3468)

Co-authored-by: codex <codex@users.noreply.github.com>

2 files changed, 198 insertions(+), 6 deletions(-)

scripts/update-release-package-versions.test.ts+107-1
...@@ -1,16 +1,21 @@...@@ -1,16 +1,21 @@
1import * as NodeServices from "@effect/platform-node/NodeServices";1import * as NodeServices from "@effect/platform-node/NodeServices";
2import { assert, it } from "@effect/vitest";2import { assert, it } from "@effect/vitest";
3import * as Config from "effect/Config";
3import * as ConfigProvider from "effect/ConfigProvider";4import * as ConfigProvider from "effect/ConfigProvider";
4import * as Effect from "effect/Effect";5import * as Effect from "effect/Effect";
5import * as FileSystem from "effect/FileSystem";6import * as FileSystem from "effect/FileSystem";
6import * as Layer from "effect/Layer";7import * as Layer from "effect/Layer";
7import * as Path from "effect/Path";8import * as Path from "effect/Path";
9import * as PlatformError from "effect/PlatformError";
8import * as Schema from "effect/Schema";10import * as Schema from "effect/Schema";
9import { Command, CliError } from "effect/unstable/cli";11import { Command, CliError } from "effect/unstable/cli";
10import * as TestConsole from "effect/testing/TestConsole";12import * as TestConsole from "effect/testing/TestConsole";
11import { fromJsonStringPretty } from "@t3tools/shared/schemaJson";13import { fromJsonStringPretty } from "@t3tools/shared/schemaJson";
1214
13import {15import {
16 ReleaseGitHubOutputConfigurationError,
17 ReleaseGitHubOutputWriteError,
18 ReleasePackageManifestError,
14 releasePackageFiles,19 releasePackageFiles,
15 updateReleasePackageVersions,20 updateReleasePackageVersions,
16 updateReleasePackageVersionsCommand,21 updateReleasePackageVersionsCommand,
...@@ -103,6 +108,73 @@ it.layer(ScriptTestLayer)("update-release-package-versions", (it) => {...@@ -103,6 +108,73 @@ it.layer(ScriptTestLayer)("update-release-package-versions", (it) => {
103 }),108 }),
104 );109 );
105110
111 it.effect("preserves manifest read context and the filesystem cause", () =>
112 Effect.gen(function* () {
113 const fs = yield* FileSystem.FileSystem;
114 const path = yield* Path.Path;
115 const baseDir = yield* fs.makeTempDirectoryScoped({
116 prefix: "update-release-package-versions-read-error-",
117 });
118 const filePath = path.join(baseDir, releasePackageFiles[0]);
119
120 const error = yield* updateReleasePackageVersions("1.2.3", {
121 rootDir: baseDir,
122 }).pipe(Effect.flip);
123
124 assert.instanceOf(error, ReleasePackageManifestError);
125 assert.equal(error.operation, "read");
126 assert.equal(error.filePath, filePath);
127 assert.instanceOf(error.cause, PlatformError.PlatformError);
128 assert.equal(error.message, `Failed to read release package manifest '${filePath}'.`);
129 }),
130 );
131
132 it.effect("preserves manifest decode context and the schema cause", () =>
133 Effect.gen(function* () {
134 const fs = yield* FileSystem.FileSystem;
135 const path = yield* Path.Path;
136 const baseDir = yield* fs.makeTempDirectoryScoped({
137 prefix: "update-release-package-versions-decode-error-",
138 });
139 const filePath = path.join(baseDir, releasePackageFiles[0]);
140
141 yield* writePackageJsonFixtures(baseDir, "0.0.1");
142 yield* fs.writeFileString(filePath, "not json");
143
144 const error = yield* updateReleasePackageVersions("1.2.3", {
145 rootDir: baseDir,
146 }).pipe(Effect.flip);
147
148 assert.equal(error.operation, "decode");
149 assert.equal(error.filePath, filePath);
150 assert.isTrue(Schema.isSchemaError(error.cause));
151 assert.equal(error.message, `Failed to decode release package manifest '${filePath}'.`);
152 }),
153 );
154
155 it.effect("preserves manifest write context and the filesystem cause", () =>
156 Effect.gen(function* () {
157 const fs = yield* FileSystem.FileSystem;
158 const path = yield* Path.Path;
159 const baseDir = yield* fs.makeTempDirectoryScoped({
160 prefix: "update-release-package-versions-write-error-",
161 });
162 const filePath = path.join(baseDir, releasePackageFiles[0]);
163
164 yield* writePackageJsonFixtures(baseDir, "0.0.1");
165 yield* fs.chmod(filePath, 0o400);
166
167 const error = yield* updateReleasePackageVersions("1.2.3", {
168 rootDir: baseDir,
169 }).pipe(Effect.flip, Effect.ensuring(fs.chmod(filePath, 0o600).pipe(Effect.orDie)));
170
171 assert.equal(error.operation, "write");
172 assert.equal(error.filePath, filePath);
173 assert.instanceOf(error.cause, PlatformError.PlatformError);
174 assert.equal(error.message, `Failed to write release package manifest '${filePath}'.`);
175 }),
176 );
177
106 it.effect("accepts flags before the version positional and appends changed output", () =>178 it.effect("accepts flags before the version positional and appends changed output", () =>
107 Effect.gen(function* () {179 Effect.gen(function* () {
108 const fs = yield* FileSystem.FileSystem;180 const fs = yield* FileSystem.FileSystem;
...@@ -164,9 +236,43 @@ it.layer(ScriptTestLayer)("update-release-package-versions", (it) => {...@@ -164,9 +236,43 @@ it.layer(ScriptTestLayer)("update-release-package-versions", (it) => {
164 Effect.flip,236 Effect.flip,
165 );237 );
166238
239 assert.instanceOf(error, ReleaseGitHubOutputConfigurationError);
240 assert.instanceOf(error.cause, Config.ConfigError);
241 assert.equal(
242 error.message,
243 "Failed to resolve GITHUB_OUTPUT for release package version output.",
244 );
245 }),
246 );
247
248 it.effect("preserves GITHUB_OUTPUT write context and the filesystem cause", () =>
249 Effect.gen(function* () {
250 const fs = yield* FileSystem.FileSystem;
251 const baseDir = yield* fs.makeTempDirectoryScoped({
252 prefix: "update-release-package-versions-cli-output-error-",
253 });
254
255 yield* writePackageJsonFixtures(baseDir, "0.0.1");
256
257 const error = yield* runCli(["4.0.0", "--root", baseDir, "--github-output"]).pipe(
258 Effect.provide(
259 ConfigProvider.layer(
260 ConfigProvider.fromEnv({
261 env: {
262 GITHUB_OUTPUT: baseDir,
263 },
264 }),
265 ),
266 ),
267 Effect.flip,
268 );
269
270 assert.instanceOf(error, ReleaseGitHubOutputWriteError);
271 assert.equal(error.filePath, baseDir);
272 assert.instanceOf(error.cause, PlatformError.PlatformError);
167 assert.equal(273 assert.equal(
168 error.message,274 error.message,
169 'SchemaError(Expected string, got undefined\n at ["GITHUB_OUTPUT"])',275 `Failed to append release package version output to '${baseDir}'.`,
170 );276 );
171 }),277 }),
172 );278 );
scripts/update-release-package-versions.ts+91-5
...@@ -12,6 +12,40 @@ import * as Schema from "effect/Schema";...@@ -12,6 +12,40 @@ import * as Schema from "effect/Schema";
12import { Argument, Command, Flag } from "effect/unstable/cli";12import { Argument, Command, Flag } from "effect/unstable/cli";
13import { fromJsonStringPretty } from "@t3tools/shared/schemaJson";13import { fromJsonStringPretty } from "@t3tools/shared/schemaJson";
1414
15export class ReleasePackageManifestError extends Schema.TaggedErrorClass<ReleasePackageManifestError>()(
16 "ReleasePackageManifestError",
17 {
18 operation: Schema.Literals(["read", "decode", "encode", "write"]),
19 filePath: Schema.String,
20 cause: Schema.Defect(),
21 },
22) {
23 override get message(): string {
24 return `Failed to ${this.operation} release package manifest '${this.filePath}'.`;
25 }
26}
27
28export class ReleaseGitHubOutputConfigurationError extends Schema.TaggedErrorClass<ReleaseGitHubOutputConfigurationError>()(
29 "ReleaseGitHubOutputConfigurationError",
30 { cause: Schema.Defect() },
31) {
32 override get message(): string {
33 return "Failed to resolve GITHUB_OUTPUT for release package version output.";
34 }
35}
36
37export class ReleaseGitHubOutputWriteError extends Schema.TaggedErrorClass<ReleaseGitHubOutputWriteError>()(
38 "ReleaseGitHubOutputWriteError",
39 {
40 filePath: Schema.String,
41 cause: Schema.Defect(),
42 },
43) {
44 override get message(): string {
45 return `Failed to append release package version output to '${this.filePath}'.`;
46 }
47}
48
15export const releasePackageFiles = [49export const releasePackageFiles = [
16 "apps/server/package.json",50 "apps/server/package.json",
17 "apps/desktop/package.json",51 "apps/desktop/package.json",
...@@ -39,13 +73,50 @@ export const updateReleasePackageVersions = Effect.fn("updateReleasePackageVersi...@@ -39,13 +73,50 @@ export const updateReleasePackageVersions = Effect.fn("updateReleasePackageVersi
3973
40 for (const relativePath of releasePackageFiles) {74 for (const relativePath of releasePackageFiles) {
41 const filePath = path.join(rootDir, relativePath);75 const filePath = path.join(rootDir, relativePath);
42 const packageJson = yield* fs.readFileString(filePath).pipe(Effect.flatMap(decodePackageJson));76 const packageJsonText = yield* fs.readFileString(filePath).pipe(
77 Effect.mapError(
78 (cause) =>
79 new ReleasePackageManifestError({
80 operation: "read",
81 filePath,
82 cause,
83 }),
84 ),
85 );
86 const packageJson = yield* decodePackageJson(packageJsonText).pipe(
87 Effect.mapError(
88 (cause) =>
89 new ReleasePackageManifestError({
90 operation: "decode",
91 filePath,
92 cause,
93 }),
94 ),
95 );
43 if (packageJson.version === version) {96 if (packageJson.version === version) {
44 continue;97 continue;
45 }98 }
4699
47 const packageJsonString = yield* encodePackageJson({ ...packageJson, version });100 const packageJsonString = yield* encodePackageJson({ ...packageJson, version }).pipe(
48 yield* fs.writeFileString(filePath, `${packageJsonString}\n`);101 Effect.mapError(
102 (cause) =>
103 new ReleasePackageManifestError({
104 operation: "encode",
105 filePath,
106 cause,
107 }),
108 ),
109 );
110 yield* fs.writeFileString(filePath, `${packageJsonString}\n`).pipe(
111 Effect.mapError(
112 (cause) =>
113 new ReleasePackageManifestError({
114 operation: "write",
115 filePath,
116 cause,
117 }),
118 ),
119 );
49 changed = true;120 changed = true;
50 }121 }
51122
...@@ -54,8 +125,23 @@ export const updateReleasePackageVersions = Effect.fn("updateReleasePackageVersi...@@ -54,8 +125,23 @@ export const updateReleasePackageVersions = Effect.fn("updateReleasePackageVersi
54125
55const writeGithubOutput = Effect.fn("writeGithubOutput")(function* (changed: boolean) {126const writeGithubOutput = Effect.fn("writeGithubOutput")(function* (changed: boolean) {
56 const fs = yield* FileSystem.FileSystem;127 const fs = yield* FileSystem.FileSystem;
57 const githubOutputPath = yield* Config.nonEmptyString("GITHUB_OUTPUT");128 const githubOutputPath = yield* Config.nonEmptyString("GITHUB_OUTPUT").pipe(
58 yield* fs.writeFileString(githubOutputPath, `changed=${changed}\n`, { flag: "a" });129 Effect.mapError(
130 (cause) =>
131 new ReleaseGitHubOutputConfigurationError({
132 cause,
133 }),
134 ),
135 );
136 yield* fs.writeFileString(githubOutputPath, `changed=${changed}\n`, { flag: "a" }).pipe(
137 Effect.mapError(
138 (cause) =>
139 new ReleaseGitHubOutputWriteError({
140 filePath: githubOutputPath,
141 cause,
142 }),
143 ),
144 );
59});145});
60146
61export const updateReleasePackageVersionsCommand = Command.make(147export const updateReleasePackageVersionsCommand = Command.make(