| ... | @@ -6,6 +6,9 @@ import * as Layer from "effect/Layer"; | ... | @@ -6,6 +6,9 @@ import * as Layer from "effect/Layer"; |
| 6 | import * as Path from "effect/Path"; | 6 | import * as Path from "effect/Path"; |
| 7 | import * as PlatformError from "effect/PlatformError"; | 7 | import * as PlatformError from "effect/PlatformError"; |
| 8 | import * as Scope from "effect/Scope"; | 8 | import * as Scope from "effect/Scope"; |
| | 9 | import * as Sink from "effect/Sink"; |
| | 10 | import * as Stream from "effect/Stream"; |
| | 11 | import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; |
| 9 | | 12 | |
| 10 | import { GitCommandError } from "@t3tools/contracts"; | 13 | import { GitCommandError } from "@t3tools/contracts"; |
| 11 | import { ServerConfig } from "../config.ts"; | 14 | import { ServerConfig } from "../config.ts"; |
| ... | @@ -20,6 +23,21 @@ const TestLayer = GitVcsDriver.layer.pipe( | ... | @@ -20,6 +23,21 @@ const TestLayer = GitVcsDriver.layer.pipe( |
| 20 | Layer.provideMerge(NodeServices.layer), | 23 | Layer.provideMerge(NodeServices.layer), |
| 21 | ); | 24 | ); |
| 22 | | 25 | |
| | 26 | const makeNonRepositoryHandle = () => |
| | 27 | ChildProcessSpawner.makeHandle({ |
| | 28 | pid: ChildProcessSpawner.ProcessId(1), |
| | 29 | exitCode: Effect.succeed(ChildProcessSpawner.ExitCode(128)), |
| | 30 | isRunning: Effect.succeed(false), |
| | 31 | kill: () => Effect.void, |
| | 32 | unref: Effect.succeed(Effect.void), |
| | 33 | stdin: Sink.drain, |
| | 34 | stdout: Stream.empty, |
| | 35 | stderr: Stream.encodeText(Stream.make("fatal: not a git repository")), |
| | 36 | all: Stream.empty, |
| | 37 | getInputFd: () => Sink.drain, |
| | 38 | getOutputFd: () => Stream.empty, |
| | 39 | }); |
| | 40 | |
| 23 | const makeTmpDir = ( | 41 | const makeTmpDir = ( |
| 24 | prefix = "git-vcs-driver-test-", | 42 | prefix = "git-vcs-driver-test-", |
| 25 | ): Effect.Effect<string, PlatformError.PlatformError, FileSystem.FileSystem | Scope.Scope> => | 43 | ): Effect.Effect<string, PlatformError.PlatformError, FileSystem.FileSystem | Scope.Scope> => |
| ... | @@ -77,7 +95,62 @@ const initRepoWithCommit = ( | ... | @@ -77,7 +95,62 @@ const initRepoWithCommit = ( |
| 77 | return { initialBranch }; | 95 | return { initialBranch }; |
| 78 | }); | 96 | }); |
| 79 | | 97 | |
| | 98 | it.effect("uses stable diagnostics for every parsed non-repository command", () => { |
| | 99 | const commands: Array<{ readonly args: ReadonlyArray<string>; readonly lcAll?: string }> = []; |
| | 100 | const spawner = ChildProcessSpawner.make((command) => |
| | 101 | Effect.sync(() => { |
| | 102 | if (!ChildProcess.isStandardCommand(command)) { |
| | 103 | return assert.fail("expected a standard Git command"); |
| | 104 | } |
| | 105 | commands.push({ |
| | 106 | args: command.args, |
| | 107 | ...(command.options.env?.LC_ALL ? { lcAll: command.options.env.LC_ALL } : {}), |
| | 108 | }); |
| | 109 | return makeNonRepositoryHandle(); |
| | 110 | }), |
| | 111 | ); |
| | 112 | const nodeServicesLayer = Layer.merge( |
| | 113 | NodeServices.layer, |
| | 114 | Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner), |
| | 115 | ); |
| | 116 | const layer = GitVcsDriver.layer.pipe( |
| | 117 | Layer.provide(ServerConfigLayer), |
| | 118 | Layer.provideMerge(nodeServicesLayer), |
| | 119 | ); |
| | 120 | |
| | 121 | return Effect.gen(function* () { |
| | 122 | const driver = yield* GitVcsDriver.GitVcsDriver; |
| | 123 | const cwd = "/repo"; |
| | 124 | |
| | 125 | yield* driver.statusDetailsLocal(cwd); |
| | 126 | yield* driver.statusDetailsRemote(cwd, { refreshUpstream: false }); |
| | 127 | yield* driver.listRefs({ cwd }); |
| | 128 | |
| | 129 | assert.deepStrictEqual(commands, [ |
| | 130 | { args: ["status", "--porcelain=2", "--branch"], lcAll: "C" }, |
| | 131 | { args: ["rev-parse", "--abbrev-ref", "HEAD"], lcAll: "C" }, |
| | 132 | { args: ["branch", "--no-color", "--no-column"], lcAll: "C" }, |
| | 133 | ]); |
| | 134 | }).pipe(Effect.provide(layer)); |
| | 135 | }); |
| | 136 | |
| 80 | it.layer(TestLayer)("GitVcsDriver core integration", (it) => { | 137 | it.layer(TestLayer)("GitVcsDriver core integration", (it) => { |
| | 138 | describe("process environment", () => { |
| | 139 | it.effect("preserves the caller locale for general Git subprocesses", () => |
| | 140 | Effect.gen(function* () { |
| | 141 | const cwd = yield* makeTmpDir(); |
| | 142 | |
| | 143 | const locale = yield* git( |
| | 144 | cwd, |
| | 145 | ["-c", 'alias.print-locale=!printf "%s" "$LC_ALL"', "print-locale"], |
| | 146 | { LC_ALL: "zh_CN.UTF-8" }, |
| | 147 | ); |
| | 148 | |
| | 149 | assert.equal(locale, "zh_CN.UTF-8"); |
| | 150 | }), |
| | 151 | ); |
| | 152 | }); |
| | 153 | |
| 81 | describe("structured errors", () => { | 154 | describe("structured errors", () => { |
| 82 | it.effect("preserves structured spawn context and the platform cause", () => | 155 | it.effect("preserves structured spawn context and the platform cause", () => |
| 83 | Effect.gen(function* () { | 156 | Effect.gen(function* () { |