| author | |
| committer | |
| log | e4a84a348e59d59e03bca15baa627aed99aec790 |
| tree | bf455396c10034ff8daca6c3bbb3c1a65a447a6b |
| parent | 61e6d89d69240120dd08fae72459e5fd7e7a2908 |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: codex <codex@users.noreply.github.com>4 files changed, 205 insertions(+), 18 deletions(-)
scripts/resolve-nightly-release.test.ts+26| ... | @@ -1,5 +1,7 @@ | ... | @@ -1,5 +1,7 @@ |
| 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 2 | import { assert, it } from "@effect/vitest"; | 2 | import { assert, it } from "@effect/vitest"; |
| 3 | import * as Config from "effect/Config"; | ||
| 4 | import * as ConfigProvider from "effect/ConfigProvider"; | ||
| 3 | import * as Effect from "effect/Effect"; | 5 | import * as Effect from "effect/Effect"; |
| 4 | import * as FileSystem from "effect/FileSystem"; | 6 | import * as FileSystem from "effect/FileSystem"; |
| 5 | import * as Path from "effect/Path"; | 7 | import * as Path from "effect/Path"; |
| ... | @@ -10,6 +12,7 @@ import { | ... | @@ -10,6 +12,7 @@ import { |
| 10 | resolveNightlyBaseVersion, | 12 | resolveNightlyBaseVersion, |
| 11 | resolveNightlyReleaseMetadata, | 13 | resolveNightlyReleaseMetadata, |
| 12 | resolveNightlyTargetVersion, | 14 | resolveNightlyTargetVersion, |
| 15 | writeNightlyReleaseOutput, | ||
| 13 | } from "./resolve-nightly-release.ts"; | 16 | } from "./resolve-nightly-release.ts"; |
| 14 | 17 | ||
| 15 | it("strips prerelease and build metadata when deriving the nightly base version", () => { | 18 | it("strips prerelease and build metadata when deriving the nightly base version", () => { |
| ... | @@ -49,6 +52,29 @@ it("derives nightly metadata including the short commit sha in the release name" | ... | @@ -49,6 +52,29 @@ it("derives nightly metadata including the short commit sha in the release name" |
| 49 | ); | 52 | ); |
| 50 | }); | 53 | }); |
| 51 | 54 | ||
| 55 | it.effect("preserves the GITHUB_OUTPUT configuration cause", () => { | ||
| 56 | const metadata = resolveNightlyReleaseMetadata("1.2.4", "20260620", 42, "abcdef1234567890"); | ||
| 57 | const configCause = new ConfigProvider.SourceError({ message: "environment unavailable" }); | ||
| 58 | |||
| 59 | return Effect.gen(function* () { | ||
| 60 | const configError = yield* writeNightlyReleaseOutput(metadata, true).pipe( | ||
| 61 | Effect.provideService(FileSystem.FileSystem, FileSystem.makeNoop({})), | ||
| 62 | Effect.provideService( | ||
| 63 | ConfigProvider.ConfigProvider, | ||
| 64 | ConfigProvider.make(() => Effect.fail(configCause)), | ||
| 65 | ), | ||
| 66 | Effect.flip, | ||
| 67 | ); | ||
| 68 | |||
| 69 | if (configError._tag !== "NightlyReleaseGitHubOutputConfigError") { | ||
| 70 | return assert.fail(`Unexpected error: ${configError._tag}`); | ||
| 71 | } | ||
| 72 | assert.instanceOf(configError.cause, Config.ConfigError); | ||
| 73 | assert.strictEqual(configError.cause.cause, configCause); | ||
| 74 | assert.notInclude(configError.message, configCause.message); | ||
| 75 | }); | ||
| 76 | }); | ||
| 77 | |||
| 52 | it.layer(NodeServices.layer)("readDesktopBaseVersion", (it) => { | 78 | it.layer(NodeServices.layer)("readDesktopBaseVersion", (it) => { |
| 53 | it.effect("preserves desktop package read context and its platform cause", () => | 79 | it.effect("preserves desktop package read context and its platform cause", () => |
| 54 | Effect.gen(function* () { | 80 | Effect.gen(function* () { |
scripts/resolve-nightly-release.ts+43-5| ... | @@ -11,7 +11,7 @@ import * as Path from "effect/Path"; | ... | @@ -11,7 +11,7 @@ import * as Path from "effect/Path"; |
| 11 | import * as Schema from "effect/Schema"; | 11 | import * as Schema from "effect/Schema"; |
| 12 | import { Command, Flag } from "effect/unstable/cli"; | 12 | import { Command, Flag } from "effect/unstable/cli"; |
| 13 | 13 | ||
| 14 | interface NightlyReleaseMetadata { | 14 | export interface NightlyReleaseMetadata { |
| 15 | readonly baseVersion: string; | 15 | readonly baseVersion: string; |
| 16 | readonly version: string; | 16 | readonly version: string; |
| 17 | readonly tag: string; | 17 | readonly tag: string; |
| ... | @@ -53,6 +53,29 @@ export class NightlyReleaseDesktopPackageError extends Schema.TaggedErrorClass<N | ... | @@ -53,6 +53,29 @@ export class NightlyReleaseDesktopPackageError extends Schema.TaggedErrorClass<N |
| 53 | } | 53 | } |
| 54 | } | 54 | } |
| 55 | 55 | ||
| 56 | export class NightlyReleaseGitHubOutputConfigError extends Schema.TaggedErrorClass<NightlyReleaseGitHubOutputConfigError>()( | ||
| 57 | "NightlyReleaseGitHubOutputConfigError", | ||
| 58 | { | ||
| 59 | cause: Schema.Defect(), | ||
| 60 | }, | ||
| 61 | ) { | ||
| 62 | override get message(): string { | ||
| 63 | return "Failed to resolve the GITHUB_OUTPUT path for nightly release metadata."; | ||
| 64 | } | ||
| 65 | } | ||
| 66 | |||
| 67 | export class NightlyReleaseGitHubOutputAppendError extends Schema.TaggedErrorClass<NightlyReleaseGitHubOutputAppendError>()( | ||
| 68 | "NightlyReleaseGitHubOutputAppendError", | ||
| 69 | { | ||
| 70 | outputPath: Schema.String, | ||
| 71 | cause: Schema.Defect(), | ||
| 72 | }, | ||
| 73 | ) { | ||
| 74 | override get message(): string { | ||
| 75 | return `Failed to append nightly release metadata to ${this.outputPath}.`; | ||
| 76 | } | ||
| 77 | } | ||
| 78 | |||
| 56 | const RepoRoot = Effect.service(Path.Path).pipe( | 79 | const RepoRoot = Effect.service(Path.Path).pipe( |
| 57 | Effect.flatMap((path) => path.fromFileUrl(new URL("..", import.meta.url))), | 80 | Effect.flatMap((path) => path.fromFileUrl(new URL("..", import.meta.url))), |
| 58 | ); | 81 | ); |
| ... | @@ -120,7 +143,7 @@ export const readDesktopBaseVersion = Effect.fn("readDesktopBaseVersion")(functi | ... | @@ -120,7 +143,7 @@ export const readDesktopBaseVersion = Effect.fn("readDesktopBaseVersion")(functi |
| 120 | return yield* resolveNightlyTargetVersion(packageJson.version); | 143 | return yield* resolveNightlyTargetVersion(packageJson.version); |
| 121 | }); | 144 | }); |
| 122 | 145 | ||
| 123 | const writeOutput = Effect.fn("writeOutput")(function* ( | 146 | export const writeNightlyReleaseOutput = Effect.fn("writeNightlyReleaseOutput")(function* ( |
| 124 | metadata: NightlyReleaseMetadata, | 147 | metadata: NightlyReleaseMetadata, |
| 125 | writeGithubOutput: boolean, | 148 | writeGithubOutput: boolean, |
| 126 | ) { | 149 | ) { |
| ... | @@ -135,9 +158,24 @@ const writeOutput = Effect.fn("writeOutput")(function* ( | ... | @@ -135,9 +158,24 @@ const writeOutput = Effect.fn("writeOutput")(function* ( |
| 135 | ] as const; | 158 | ] as const; |
| 136 | 159 | ||
| 137 | if (writeGithubOutput) { | 160 | if (writeGithubOutput) { |
| 138 | const githubOutputPath = yield* Config.nonEmptyString("GITHUB_OUTPUT"); | 161 | const githubOutputPath = yield* Config.nonEmptyString("GITHUB_OUTPUT").pipe( |
| 162 | Effect.mapError( | ||
| 163 | (cause) => | ||
| 164 | new NightlyReleaseGitHubOutputConfigError({ | ||
| 165 | cause, | ||
| 166 | }), | ||
| 167 | ), | ||
| 168 | ); | ||
| 139 | const serialized = entries.map(([key, value]) => `${key}=${value}\n`).join(""); | 169 | const serialized = entries.map(([key, value]) => `${key}=${value}\n`).join(""); |
| 140 | yield* fs.writeFileString(githubOutputPath, serialized, { flag: "a" }); | 170 | yield* fs.writeFileString(githubOutputPath, serialized, { flag: "a" }).pipe( |
| 171 | Effect.mapError( | ||
| 172 | (cause) => | ||
| 173 | new NightlyReleaseGitHubOutputAppendError({ | ||
| 174 | outputPath: githubOutputPath, | ||
| 175 | cause, | ||
| 176 | }), | ||
| 177 | ), | ||
| 178 | ); | ||
| 141 | } else { | 179 | } else { |
| 142 | for (const [key, value] of entries) { | 180 | for (const [key, value] of entries) { |
| 143 | yield* Console.log(`${key}=${value}`); | 181 | yield* Console.log(`${key}=${value}`); |
| ... | @@ -172,7 +210,7 @@ const command = Command.make( | ... | @@ -172,7 +210,7 @@ const command = Command.make( |
| 172 | ({ date, runNumber, sha, githubOutput, root }) => | 210 | ({ date, runNumber, sha, githubOutput, root }) => |
| 173 | readDesktopBaseVersion(Option.getOrUndefined(root)).pipe( | 211 | readDesktopBaseVersion(Option.getOrUndefined(root)).pipe( |
| 174 | Effect.map((baseVersion) => resolveNightlyReleaseMetadata(baseVersion, date, runNumber, sha)), | 212 | Effect.map((baseVersion) => resolveNightlyReleaseMetadata(baseVersion, date, runNumber, sha)), |
| 175 | Effect.flatMap((metadata) => writeOutput(metadata, githubOutput)), | 213 | Effect.flatMap((metadata) => writeNightlyReleaseOutput(metadata, githubOutput)), |
| 176 | ), | 214 | ), |
| 177 | ).pipe(Command.withDescription("Resolve nightly release version metadata.")); | 215 | ).pipe(Command.withDescription("Resolve nightly release version metadata.")); |
| 178 | 216 |
scripts/resolve-previous-release-tag.test.ts+86-3| ... | @@ -1,11 +1,17 @@ | ... | @@ -1,11 +1,17 @@ |
| 1 | import { assert, it } from "@effect/vitest"; | 1 | import { assert, it } from "@effect/vitest"; |
| 2 | import * as ConfigProvider from "effect/ConfigProvider"; | ||
| 2 | import * as Effect from "effect/Effect"; | 3 | import * as Effect from "effect/Effect"; |
| 4 | import * as FileSystem from "effect/FileSystem"; | ||
| 3 | import * as PlatformError from "effect/PlatformError"; | 5 | import * as PlatformError from "effect/PlatformError"; |
| 4 | import * as Sink from "effect/Sink"; | 6 | import * as Sink from "effect/Sink"; |
| 5 | import * as Stream from "effect/Stream"; | 7 | import * as Stream from "effect/Stream"; |
| 6 | import { ChildProcessSpawner } from "effect/unstable/process"; | 8 | import { ChildProcessSpawner } from "effect/unstable/process"; |
| 7 | 9 | ||
| 8 | import { listGitTags, resolvePreviousReleaseTag } from "./resolve-previous-release-tag.ts"; | 10 | import { |
| 11 | listGitTags, | ||
| 12 | resolvePreviousReleaseTag, | ||
| 13 | writePreviousReleaseTagOutput, | ||
| 14 | } from "./resolve-previous-release-tag.ts"; | ||
| 9 | 15 | ||
| 10 | const encoder = new TextEncoder(); | 16 | const encoder = new TextEncoder(); |
| 11 | 17 | ||
| ... | @@ -13,6 +19,8 @@ function mockHandle(options: { | ... | @@ -13,6 +19,8 @@ function mockHandle(options: { |
| 13 | readonly exitCode: number; | 19 | readonly exitCode: number; |
| 14 | readonly stdout?: string; | 20 | readonly stdout?: string; |
| 15 | readonly stderr?: string; | 21 | readonly stderr?: string; |
| 22 | readonly stdoutError?: PlatformError.PlatformError; | ||
| 23 | readonly stderrError?: PlatformError.PlatformError; | ||
| 16 | }) { | 24 | }) { |
| 17 | return ChildProcessSpawner.makeHandle({ | 25 | return ChildProcessSpawner.makeHandle({ |
| 18 | pid: ChildProcessSpawner.ProcessId(1), | 26 | pid: ChildProcessSpawner.ProcessId(1), |
| ... | @@ -21,8 +29,12 @@ function mockHandle(options: { | ... | @@ -21,8 +29,12 @@ function mockHandle(options: { |
| 21 | kill: () => Effect.void, | 29 | kill: () => Effect.void, |
| 22 | unref: Effect.succeed(Effect.void), | 30 | unref: Effect.succeed(Effect.void), |
| 23 | stdin: Sink.drain, | 31 | stdin: Sink.drain, |
| 24 | stdout: Stream.make(encoder.encode(options.stdout ?? "")), | 32 | stdout: options.stdoutError |
| 25 | stderr: Stream.make(encoder.encode(options.stderr ?? "")), | 33 | ? Stream.fail(options.stdoutError) |
| 34 | : Stream.make(encoder.encode(options.stdout ?? "")), | ||
| 35 | stderr: options.stderrError | ||
| 36 | ? Stream.fail(options.stderrError) | ||
| 37 | : Stream.make(encoder.encode(options.stderr ?? "")), | ||
| 26 | all: Stream.empty, | 38 | all: Stream.empty, |
| 27 | getInputFd: () => Sink.drain, | 39 | getInputFd: () => Sink.drain, |
| 28 | getOutputFd: () => Stream.empty, | 40 | getOutputFd: () => Stream.empty, |
| ... | @@ -95,6 +107,43 @@ it.effect("preserves git tag spawn context and the exact platform cause", () => | ... | @@ -95,6 +107,43 @@ it.effect("preserves git tag spawn context and the exact platform cause", () => |
| 95 | }); | 107 | }); |
| 96 | }); | 108 | }); |
| 97 | 109 | ||
| 110 | it.effect("distinguishes stdout and stderr read failures", () => | ||
| 111 | Effect.gen(function* () { | ||
| 112 | for (const [stream, operation] of [ | ||
| 113 | ["stdout", "read-stdout"], | ||
| 114 | ["stderr", "read-stderr"], | ||
| 115 | ] as const) { | ||
| 116 | const cause = PlatformError.systemError({ | ||
| 117 | _tag: "Unknown", | ||
| 118 | module: "ChildProcess", | ||
| 119 | method: stream, | ||
| 120 | description: `${stream} unavailable`, | ||
| 121 | }); | ||
| 122 | const error = yield* listGitTags("/repo").pipe( | ||
| 123 | Effect.scoped, | ||
| 124 | Effect.provideService( | ||
| 125 | ChildProcessSpawner.ChildProcessSpawner, | ||
| 126 | ChildProcessSpawner.make(() => | ||
| 127 | Effect.succeed( | ||
| 128 | mockHandle({ | ||
| 129 | exitCode: 0, | ||
| 130 | ...(stream === "stdout" ? { stdoutError: cause } : { stderrError: cause }), | ||
| 131 | }), | ||
| 132 | ), | ||
| 133 | ), | ||
| 134 | ), | ||
| 135 | Effect.flip, | ||
| 136 | ); | ||
| 137 | |||
| 138 | if (error._tag !== "ReleaseTagListProcessError") { | ||
| 139 | return assert.fail(`Unexpected error: ${error._tag}`); | ||
| 140 | } | ||
| 141 | assert.equal(error.operation, operation); | ||
| 142 | assert.strictEqual(error.cause, cause); | ||
| 143 | } | ||
| 144 | }), | ||
| 145 | ); | ||
| 146 | |||
| 98 | it.effect("reports git tag non-zero exits without manufacturing a cause", () => | 147 | it.effect("reports git tag non-zero exits without manufacturing a cause", () => |
| 99 | Effect.gen(function* () { | 148 | Effect.gen(function* () { |
| 100 | const error = yield* listGitTags("/repo").pipe( | 149 | const error = yield* listGitTags("/repo").pipe( |
| ... | @@ -128,3 +177,37 @@ it.effect("reports git tag non-zero exits without manufacturing a cause", () => | ... | @@ -128,3 +177,37 @@ it.effect("reports git tag non-zero exits without manufacturing a cause", () => |
| 128 | assert.notProperty(error, "stderr"); | 177 | assert.notProperty(error, "stderr"); |
| 129 | }), | 178 | }), |
| 130 | ); | 179 | ); |
| 180 | |||
| 181 | it.effect("preserves the GITHUB_OUTPUT append path and exact cause", () => { | ||
| 182 | const outputPath = "/tmp/previous-tag-github-output"; | ||
| 183 | const appendCause = PlatformError.systemError({ | ||
| 184 | _tag: "PermissionDenied", | ||
| 185 | module: "FileSystem", | ||
| 186 | method: "writeFileString", | ||
| 187 | pathOrDescriptor: outputPath, | ||
| 188 | }); | ||
| 189 | |||
| 190 | return Effect.gen(function* () { | ||
| 191 | const appendError = yield* writePreviousReleaseTagOutput("v1.2.3", true).pipe( | ||
| 192 | Effect.provideService( | ||
| 193 | FileSystem.FileSystem, | ||
| 194 | FileSystem.makeNoop({ | ||
| 195 | writeFileString: () => Effect.fail(appendCause), | ||
| 196 | }), | ||
| 197 | ), | ||
| 198 | Effect.provideService( | ||
| 199 | ConfigProvider.ConfigProvider, | ||
| 200 | ConfigProvider.fromEnv({ env: { GITHUB_OUTPUT: outputPath } }), | ||
| 201 | ), | ||
| 202 | Effect.flip, | ||
| 203 | ); | ||
| 204 | |||
| 205 | if (appendError._tag !== "PreviousReleaseTagGitHubOutputAppendError") { | ||
| 206 | return assert.fail(`Unexpected error: ${appendError._tag}`); | ||
| 207 | } | ||
| 208 | assert.equal(appendError.outputPath, outputPath); | ||
| 209 | assert.strictEqual(appendError.cause, appendCause); | ||
| 210 | assert.notProperty(appendError, "contents"); | ||
| 211 | assert.notInclude(appendError.message, appendCause.message); | ||
| 212 | }); | ||
| 213 | }); |
scripts/resolve-previous-release-tag.ts+50-10| ... | @@ -26,9 +26,11 @@ export class InvalidReleaseTagError extends Schema.TaggedErrorClass<InvalidRelea | ... | @@ -26,9 +26,11 @@ export class InvalidReleaseTagError extends Schema.TaggedErrorClass<InvalidRelea |
| 26 | } | 26 | } |
| 27 | } | 27 | } |
| 28 | 28 | ||
| 29 | const NonNegativeInt = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)); | ||
| 30 | |||
| 29 | const releaseTagListProcessContext = { | 31 | const releaseTagListProcessContext = { |
| 30 | executable: Schema.Literal("git"), | 32 | executable: Schema.Literal("git"), |
| 31 | argumentCount: Schema.Number, | 33 | argumentCount: NonNegativeInt, |
| 32 | cwd: Schema.String, | 34 | cwd: Schema.String, |
| 33 | }; | 35 | }; |
| 34 | 36 | ||
| ... | @@ -36,7 +38,7 @@ export class ReleaseTagListProcessError extends Schema.TaggedErrorClass<ReleaseT | ... | @@ -36,7 +38,7 @@ export class ReleaseTagListProcessError extends Schema.TaggedErrorClass<ReleaseT |
| 36 | "ReleaseTagListProcessError", | 38 | "ReleaseTagListProcessError", |
| 37 | { | 39 | { |
| 38 | ...releaseTagListProcessContext, | 40 | ...releaseTagListProcessContext, |
| 39 | operation: Schema.Literals(["spawn", "communicate", "wait-for-exit"]), | 41 | operation: Schema.Literals(["spawn", "read-stdout", "read-stderr", "wait-for-exit"]), |
| 40 | cause: Schema.Defect(), | 42 | cause: Schema.Defect(), |
| 41 | }, | 43 | }, |
| 42 | ) { | 44 | ) { |
| ... | @@ -50,8 +52,8 @@ export class ReleaseTagListProcessExitError extends Schema.TaggedErrorClass<Rele | ... | @@ -50,8 +52,8 @@ export class ReleaseTagListProcessExitError extends Schema.TaggedErrorClass<Rele |
| 50 | { | 52 | { |
| 51 | ...releaseTagListProcessContext, | 53 | ...releaseTagListProcessContext, |
| 52 | exitCode: Schema.Number, | 54 | exitCode: Schema.Number, |
| 53 | stdoutLength: Schema.Number, | 55 | stdoutLength: NonNegativeInt, |
| 54 | stderrLength: Schema.Number, | 56 | stderrLength: NonNegativeInt, |
| 55 | }, | 57 | }, |
| 56 | ) { | 58 | ) { |
| 57 | override get message(): string { | 59 | override get message(): string { |
| ... | @@ -59,6 +61,29 @@ export class ReleaseTagListProcessExitError extends Schema.TaggedErrorClass<Rele | ... | @@ -59,6 +61,29 @@ export class ReleaseTagListProcessExitError extends Schema.TaggedErrorClass<Rele |
| 59 | } | 61 | } |
| 60 | } | 62 | } |
| 61 | 63 | ||
| 64 | export class PreviousReleaseTagGitHubOutputConfigError extends Schema.TaggedErrorClass<PreviousReleaseTagGitHubOutputConfigError>()( | ||
| 65 | "PreviousReleaseTagGitHubOutputConfigError", | ||
| 66 | { | ||
| 67 | cause: Schema.Defect(), | ||
| 68 | }, | ||
| 69 | ) { | ||
| 70 | override get message(): string { | ||
| 71 | return "Failed to resolve the GITHUB_OUTPUT path for the previous release tag."; | ||
| 72 | } | ||
| 73 | } | ||
| 74 | |||
| 75 | export class PreviousReleaseTagGitHubOutputAppendError extends Schema.TaggedErrorClass<PreviousReleaseTagGitHubOutputAppendError>()( | ||
| 76 | "PreviousReleaseTagGitHubOutputAppendError", | ||
| 77 | { | ||
| 78 | outputPath: Schema.String, | ||
| 79 | cause: Schema.Defect(), | ||
| 80 | }, | ||
| 81 | ) { | ||
| 82 | override get message(): string { | ||
| 83 | return `Failed to append the previous release tag to ${this.outputPath}.`; | ||
| 84 | } | ||
| 85 | } | ||
| 86 | |||
| 62 | interface StableVersion { | 87 | interface StableVersion { |
| 63 | readonly major: number; | 88 | readonly major: number; |
| 64 | readonly minor: number; | 89 | readonly minor: number; |
| ... | @@ -238,7 +263,7 @@ export const listGitTags = Effect.fn("listGitTags")(function* (cwd = process.cwd | ... | @@ -238,7 +263,7 @@ export const listGitTags = Effect.fn("listGitTags")(function* (cwd = process.cwd |
| 238 | (cause) => | 263 | (cause) => |
| 239 | new ReleaseTagListProcessError({ | 264 | new ReleaseTagListProcessError({ |
| 240 | ...context, | 265 | ...context, |
| 241 | operation: "communicate", | 266 | operation: "read-stdout", |
| 242 | cause, | 267 | cause, |
| 243 | }), | 268 | }), |
| 244 | ), | 269 | ), |
| ... | @@ -248,7 +273,7 @@ export const listGitTags = Effect.fn("listGitTags")(function* (cwd = process.cwd | ... | @@ -248,7 +273,7 @@ export const listGitTags = Effect.fn("listGitTags")(function* (cwd = process.cwd |
| 248 | (cause) => | 273 | (cause) => |
| 249 | new ReleaseTagListProcessError({ | 274 | new ReleaseTagListProcessError({ |
| 250 | ...context, | 275 | ...context, |
| 251 | operation: "communicate", | 276 | operation: "read-stderr", |
| 252 | cause, | 277 | cause, |
| 253 | }), | 278 | }), |
| 254 | ), | 279 | ), |
| ... | @@ -280,7 +305,7 @@ export const listGitTags = Effect.fn("listGitTags")(function* (cwd = process.cwd | ... | @@ -280,7 +305,7 @@ export const listGitTags = Effect.fn("listGitTags")(function* (cwd = process.cwd |
| 280 | return stdout.split(/\r?\n/).map(String.trim).filter(String.isNonEmpty); | 305 | return stdout.split(/\r?\n/).map(String.trim).filter(String.isNonEmpty); |
| 281 | }); | 306 | }); |
| 282 | 307 | ||
| 283 | const writeOutput = Effect.fn("writeOutput")(function* ( | 308 | export const writePreviousReleaseTagOutput = Effect.fn("writePreviousReleaseTagOutput")(function* ( |
| 284 | previousTag: string | undefined, | 309 | previousTag: string | undefined, |
| 285 | writeGithubOutput: boolean, | 310 | writeGithubOutput: boolean, |
| 286 | ) { | 311 | ) { |
| ... | @@ -288,8 +313,23 @@ const writeOutput = Effect.fn("writeOutput")(function* ( | ... | @@ -288,8 +313,23 @@ const writeOutput = Effect.fn("writeOutput")(function* ( |
| 288 | 313 | ||
| 289 | if (writeGithubOutput) { | 314 | if (writeGithubOutput) { |
| 290 | const fs = yield* FileSystem.FileSystem; | 315 | const fs = yield* FileSystem.FileSystem; |
| 291 | const githubOutputPath = yield* Config.nonEmptyString("GITHUB_OUTPUT"); | 316 | const githubOutputPath = yield* Config.nonEmptyString("GITHUB_OUTPUT").pipe( |
| 292 | yield* fs.writeFileString(githubOutputPath, entry, { flag: "a" }); | 317 | Effect.mapError( |
| 318 | (cause) => | ||
| 319 | new PreviousReleaseTagGitHubOutputConfigError({ | ||
| 320 | cause, | ||
| 321 | }), | ||
| 322 | ), | ||
| 323 | ); | ||
| 324 | yield* fs.writeFileString(githubOutputPath, entry, { flag: "a" }).pipe( | ||
| 325 | Effect.mapError( | ||
| 326 | (cause) => | ||
| 327 | new PreviousReleaseTagGitHubOutputAppendError({ | ||
| 328 | outputPath: githubOutputPath, | ||
| 329 | cause, | ||
| 330 | }), | ||
| 331 | ), | ||
| 332 | ); | ||
| 293 | return; | 333 | return; |
| 294 | } | 334 | } |
| 295 | 335 | ||
| ... | @@ -313,7 +353,7 @@ const command = Command.make( | ... | @@ -313,7 +353,7 @@ const command = Command.make( |
| 313 | ({ channel, currentTag, githubOutput }) => | 353 | ({ channel, currentTag, githubOutput }) => |
| 314 | listGitTags().pipe( | 354 | listGitTags().pipe( |
| 315 | Effect.flatMap((tags) => resolvePreviousReleaseTag(channel, currentTag, tags)), | 355 | Effect.flatMap((tags) => resolvePreviousReleaseTag(channel, currentTag, tags)), |
| 316 | Effect.flatMap((previousTag) => writeOutput(previousTag, githubOutput)), | 356 | Effect.flatMap((previousTag) => writePreviousReleaseTagOutput(previousTag, githubOutput)), |
| 317 | ), | 357 | ), |
| 318 | ).pipe(Command.withDescription("Resolve the previous release tag for a stable or nightly series.")); | 358 | ).pipe(Command.withDescription("Resolve the previous release tag for a stable or nightly series.")); |
| 319 | 359 |