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 @@
11import * as NodeServices from "@effect/platform-node/NodeServices";
22import { assert, it } from "@effect/vitest";
3import * as Config from "effect/Config";
34import * as ConfigProvider from "effect/ConfigProvider";
45import * as Effect from "effect/Effect";
56import * as FileSystem from "effect/FileSystem";
67import * as Layer from "effect/Layer";
78import * as Path from "effect/Path";
9import * as PlatformError from "effect/PlatformError";
810import * as Schema from "effect/Schema";
911import { Command, CliError } from "effect/unstable/cli";
1012import * as TestConsole from "effect/testing/TestConsole";
1113import { fromJsonStringPretty } from "@t3tools/shared/schemaJson";
1214
1315import {
16 ReleaseGitHubOutputConfigurationError,
17 ReleaseGitHubOutputWriteError,
18 ReleasePackageManifestError,
1419 releasePackageFiles,
1520 updateReleasePackageVersions,
1621 updateReleasePackageVersionsCommand,
......@@ -103,6 +108,73 @@ it.layer(ScriptTestLayer)("update-release-package-versions", (it) => {
103108 }),
104109 );
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
106178 it.effect("accepts flags before the version positional and appends changed output", () =>
107179 Effect.gen(function* () {
108180 const fs = yield* FileSystem.FileSystem;
......@@ -164,9 +236,43 @@ it.layer(ScriptTestLayer)("update-release-package-versions", (it) => {
164236 Effect.flip,
165237 );
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);
167273 assert.equal(
168274 error.message,
169 'SchemaError(Expected string, got undefined\n at ["GITHUB_OUTPUT"])',
275 `Failed to append release package version output to '${baseDir}'.`,
170276 );
171277 }),
172278 );
scripts/update-release-package-versions.ts+91-5
......@@ -12,6 +12,40 @@ import * as Schema from "effect/Schema";
1212import { Argument, Command, Flag } from "effect/unstable/cli";
1313import { 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
1549export const releasePackageFiles = [
1650 "apps/server/package.json",
1751 "apps/desktop/package.json",
......@@ -39,13 +73,50 @@ export const updateReleasePackageVersions = Effect.fn("updateReleasePackageVersi
3973
4074 for (const relativePath of releasePackageFiles) {
4175 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 );
4396 if (packageJson.version === version) {
4497 continue;
4598 }
4699
47 const packageJsonString = yield* encodePackageJson({ ...packageJson, version });
48 yield* fs.writeFileString(filePath, `${packageJsonString}\n`);
100 const packageJsonString = yield* encodePackageJson({ ...packageJson, version }).pipe(
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 );
49120 changed = true;
50121 }
51122
......@@ -54,8 +125,23 @@ export const updateReleasePackageVersions = Effect.fn("updateReleasePackageVersi
54125
55126const writeGithubOutput = Effect.fn("writeGithubOutput")(function* (changed: boolean) {
56127 const fs = yield* FileSystem.FileSystem;
57 const githubOutputPath = yield* Config.nonEmptyString("GITHUB_OUTPUT");
58 yield* fs.writeFileString(githubOutputPath, `changed=${changed}\n`, { flag: "a" });
128 const githubOutputPath = yield* Config.nonEmptyString("GITHUB_OUTPUT").pipe(
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 );
59145});
60146
61147export const updateReleasePackageVersionsCommand = Command.make(