| author | |
| committer | |
| log | 12b6d026bdfba0cc8438fa5e323a8587d65bd2af |
| tree | 942c5f28428ddf1fb448b0001d18a069efbb3a85 |
| parent | f083f520fbde1e1bf4332399df47b45c2bd99722 |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: Claude Code <noreply@anthropic.com>4 files changed, 61 insertions(+), 29 deletions(-)
apps/server/src/project/RepositoryIdentityResolver.test.ts+8-4| ... | @@ -1,3 +1,5 @@ | ... | @@ -1,3 +1,5 @@ |
| 1 | // @effect-diagnostics nodeBuiltinImport:off - realpathSync.native resolves Windows 8.3 short names, which the Effect realPath does not. | ||
| 2 | import * as NodeFS from "node:fs"; | ||
| 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 3 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 2 | import { expect, it } from "@effect/vitest"; | 4 | import { expect, it } from "@effect/vitest"; |
| 3 | import * as Duration from "effect/Duration"; | 5 | import * as Duration from "effect/Duration"; |
| ... | @@ -131,9 +133,11 @@ it.layer(NodeServices.layer)("RepositoryIdentityResolverLive", (it) => { | ... | @@ -131,9 +133,11 @@ it.layer(NodeServices.layer)("RepositoryIdentityResolverLive", (it) => { |
| 131 | 133 | ||
| 132 | const resolver = yield* RepositoryIdentityResolver.RepositoryIdentityResolver; | 134 | const resolver = yield* RepositoryIdentityResolver.RepositoryIdentityResolver; |
| 133 | const identity = yield* resolver.resolve(cwd); | 135 | const identity = yield* resolver.resolve(cwd); |
| 136 | // Native realpath, since git reports the long form of a directory the | ||
| 137 | // temp dir may name by its 8.3 short form on Windows. | ||
| 134 | const resolvedIdentityRoot = | 138 | const resolvedIdentityRoot = |
| 135 | identity?.rootPath === undefined ? "" : yield* fileSystem.realPath(identity.rootPath); | 139 | identity?.rootPath === undefined ? "" : NodeFS.realpathSync.native(identity.rootPath); |
| 136 | const resolvedCwd = yield* fileSystem.realPath(cwd); | 140 | const resolvedCwd = NodeFS.realpathSync.native(cwd); |
| 137 | 141 | ||
| 138 | expect(identity).not.toBeNull(); | 142 | expect(identity).not.toBeNull(); |
| 139 | expect(identity?.canonicalKey).toBe("github.com/t3tools/t3code"); | 143 | expect(identity?.canonicalKey).toBe("github.com/t3tools/t3code"); |
| ... | @@ -161,8 +165,8 @@ it.layer(NodeServices.layer)("RepositoryIdentityResolverLive", (it) => { | ... | @@ -161,8 +165,8 @@ it.layer(NodeServices.layer)("RepositoryIdentityResolverLive", (it) => { |
| 161 | const resolver = yield* RepositoryIdentityResolver.RepositoryIdentityResolver; | 165 | const resolver = yield* RepositoryIdentityResolver.RepositoryIdentityResolver; |
| 162 | const identity = yield* resolver.resolve(nestedWorkspace); | 166 | const identity = yield* resolver.resolve(nestedWorkspace); |
| 163 | const resolvedIdentityRoot = | 167 | const resolvedIdentityRoot = |
| 164 | identity?.rootPath === undefined ? "" : yield* fileSystem.realPath(identity.rootPath); | 168 | identity?.rootPath === undefined ? "" : NodeFS.realpathSync.native(identity.rootPath); |
| 165 | const resolvedRepoRoot = yield* fileSystem.realPath(repoRoot); | 169 | const resolvedRepoRoot = NodeFS.realpathSync.native(repoRoot); |
| 166 | 170 | ||
| 167 | expect(identity).not.toBeNull(); | 171 | expect(identity).not.toBeNull(); |
| 168 | expect(identity?.canonicalKey).toBe("github.com/t3tools/t3code"); | 172 | expect(identity?.canonicalKey).toBe("github.com/t3tools/t3code"); |
apps/server/src/testUtils/gitConfig.setup.ts created+21| ... | @@ -0,0 +1,21 @@ | ||
| 1 | // Pins git behaviour for every repository the suite creates, ahead of the | ||
| 2 | // host's ~/.gitconfig. Git for Windows installs with core.autocrlf=true, | ||
| 3 | // which checks committed LF files out as CRLF and breaks every byte-exact | ||
| 4 | // content assertion; a signing key or a non-default init branch on the | ||
| 5 | // developer's machine breaks fixtures the same way. Set as environment so | ||
| 6 | // each git child the driver spawns sees it without touching the fixtures. | ||
| 7 | const entries: ReadonlyArray<readonly [key: string, value: string]> = [ | ||
| 8 | ["core.autocrlf", "false"], | ||
| 9 | ["core.filemode", "false"], | ||
| 10 | ["core.longpaths", "true"], | ||
| 11 | ["commit.gpgsign", "false"], | ||
| 12 | ["tag.gpgsign", "false"], | ||
| 13 | ["init.defaultBranch", "main"], | ||
| 14 | ]; | ||
| 15 | |||
| 16 | const existing = Number(process.env.GIT_CONFIG_COUNT ?? "0"); | ||
| 17 | process.env.GIT_CONFIG_COUNT = String(existing + entries.length); | ||
| 18 | entries.forEach(([key, value], index) => { | ||
| 19 | process.env[`GIT_CONFIG_KEY_${existing + index}`] = key; | ||
| 20 | process.env[`GIT_CONFIG_VALUE_${existing + index}`] = value; | ||
| 21 | }); | ||
apps/server/src/vcs/GitVcsDriverCore.test.ts+31-25| ... | @@ -1,4 +1,7 @@ | ... | @@ -1,4 +1,7 @@ |
| 1 | // @effect-diagnostics nodeBuiltinImport:off - realpathSync.native resolves Windows 8.3 short names, which the Effect realPath does not. | ||
| 2 | import * as NodeFS from "node:fs"; | ||
| 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 3 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 4 | import { HostProcessPlatform } from "@t3tools/shared/hostProcess"; | ||
| 2 | import { assert, it, describe } from "@effect/vitest"; | 5 | import { assert, it, describe } from "@effect/vitest"; |
| 3 | import * as Deferred from "effect/Deferred"; | 6 | import * as Deferred from "effect/Deferred"; |
| 4 | import * as Effect from "effect/Effect"; | 7 | import * as Effect from "effect/Effect"; |
| ... | @@ -570,7 +573,6 @@ it.effect("backs off failed upstream refreshes across linked worktrees", () => | ... | @@ -570,7 +573,6 @@ it.effect("backs off failed upstream refreshes across linked worktrees", () => |
| 570 | const driver = yield* makeGitVcsDriverCore().pipe( | 573 | const driver = yield* makeGitVcsDriverCore().pipe( |
| 571 | Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, failingFetchSpawner), | 574 | Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, failingFetchSpawner), |
| 572 | ); | 575 | ); |
| 573 | const fileSystem = yield* FileSystem.FileSystem; | ||
| 574 | const cwd = yield* makeTmpDir(); | 576 | const cwd = yield* makeTmpDir(); |
| 575 | const remote = yield* makeTmpDir("git-vcs-driver-remote-"); | 577 | const remote = yield* makeTmpDir("git-vcs-driver-remote-"); |
| 576 | const worktreesRoot = yield* makeTmpDir("git-vcs-driver-worktrees-"); | 578 | const worktreesRoot = yield* makeTmpDir("git-vcs-driver-worktrees-"); |
| ... | @@ -606,9 +608,11 @@ it.effect("backs off failed upstream refreshes across linked worktrees", () => | ... | @@ -606,9 +608,11 @@ it.effect("backs off failed upstream refreshes across linked worktrees", () => |
| 606 | "rev-parse", | 608 | "rev-parse", |
| 607 | "--git-common-dir", | 609 | "--git-common-dir", |
| 608 | ])).stdout.trim(); | 610 | ])).stdout.trim(); |
| 611 | // Native realpath, since git reports the long form of a directory the | ||
| 612 | // temp dir may name by its 8.3 short form on Windows. | ||
| 609 | assert.equal( | 613 | assert.equal( |
| 610 | yield* fileSystem.realPath(pathService.resolve(cwd, rootCommonDir)), | 614 | NodeFS.realpathSync.native(pathService.resolve(cwd, rootCommonDir)), |
| 611 | yield* fileSystem.realPath(pathService.resolve(worktreePath, linkedCommonDir)), | 615 | NodeFS.realpathSync.native(pathService.resolve(worktreePath, linkedCommonDir)), |
| 612 | ); | 616 | ); |
| 613 | yield* Ref.set(fetchAttempts, 0); | 617 | yield* Ref.set(fetchAttempts, 0); |
| 614 | 618 | ||
| ... | @@ -1376,31 +1380,33 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => { | ... | @@ -1376,31 +1380,33 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => { |
| 1376 | }); | 1380 | }); |
| 1377 | 1381 | ||
| 1378 | describe("worktree operations", () => { | 1382 | describe("worktree operations", () => { |
| 1379 | it.effect("preserves newline characters in worktree paths when listing refs", () => | 1383 | // NTFS rejects a newline in a file name, so there is nothing to preserve there. |
| 1380 | Effect.gen(function* () { | 1384 | it.effect.skipIf(HostProcessPlatform.defaultValue() === "win32")( |
| 1381 | const cwd = yield* makeTmpDir(); | 1385 | "preserves newline characters in worktree paths when listing refs", |
| 1382 | yield* initRepoWithCommit(cwd); | 1386 | () => |
| 1383 | const worktreesRoot = yield* makeTmpDir("git-vcs-driver-worktrees-"); | 1387 | Effect.gen(function* () { |
| 1384 | const fileSystem = yield* FileSystem.FileSystem; | 1388 | const cwd = yield* makeTmpDir(); |
| 1385 | const pathService = yield* Path.Path; | 1389 | yield* initRepoWithCommit(cwd); |
| 1386 | const worktreePath = pathService.join(worktreesRoot, "linked\nworktree"); | 1390 | const worktreesRoot = yield* makeTmpDir("git-vcs-driver-worktrees-"); |
| 1387 | const driver = yield* GitVcsDriver.GitVcsDriver; | 1391 | const pathService = yield* Path.Path; |
| 1392 | const worktreePath = pathService.join(worktreesRoot, "linked\nworktree"); | ||
| 1393 | const driver = yield* GitVcsDriver.GitVcsDriver; | ||
| 1388 | 1394 | ||
| 1389 | yield* git(cwd, ["worktree", "add", "-b", "feature/newline-path", worktreePath]); | 1395 | yield* git(cwd, ["worktree", "add", "-b", "feature/newline-path", worktreePath]); |
| 1390 | 1396 | ||
| 1391 | const refs = yield* driver.listRefs({ cwd, refresh: true }); | 1397 | const refs = yield* driver.listRefs({ cwd, refresh: true }); |
| 1392 | const listedPath = refs.refs.find( | 1398 | const listedPath = refs.refs.find( |
| 1393 | (ref) => ref.name === "feature/newline-path", | 1399 | (ref) => ref.name === "feature/newline-path", |
| 1394 | )?.worktreePath; | 1400 | )?.worktreePath; |
| 1395 | 1401 | ||
| 1396 | if (typeof listedPath !== "string") { | 1402 | if (typeof listedPath !== "string") { |
| 1397 | return assert.fail("expected the linked branch to include its worktree path"); | 1403 | return assert.fail("expected the linked branch to include its worktree path"); |
| 1398 | } | 1404 | } |
| 1399 | assert.equal( | 1405 | assert.equal( |
| 1400 | yield* fileSystem.realPath(listedPath), | 1406 | NodeFS.realpathSync.native(listedPath), |
| 1401 | yield* fileSystem.realPath(worktreePath), | 1407 | NodeFS.realpathSync.native(worktreePath), |
| 1402 | ); | 1408 | ); |
| 1403 | }), | 1409 | }), |
| 1404 | ); | 1410 | ); |
| 1405 | 1411 | ||
| 1406 | it.effect("checks out submodules in a new worktree", () => | 1412 | it.effect("checks out submodules in a new worktree", () => |
apps/server/vite.config.ts+1| ... | @@ -78,6 +78,7 @@ export default mergeConfig( | ... | @@ -78,6 +78,7 @@ export default mergeConfig( |
| 78 | // The server suite exercises sqlite, git, temp worktrees, and orchestration | 78 | // The server suite exercises sqlite, git, temp worktrees, and orchestration |
| 79 | // runtimes heavily. Running files in parallel introduces load-sensitive flakes. | 79 | // runtimes heavily. Running files in parallel introduces load-sensitive flakes. |
| 80 | fileParallelism: false, | 80 | fileParallelism: false, |
| 81 | setupFiles: ["./src/testUtils/gitConfig.setup.ts"], | ||
| 81 | // Server integration tests exercise sqlite, git, and orchestration together. | 82 | // Server integration tests exercise sqlite, git, and orchestration together. |
| 82 | // Under package-wide runs they can exceed the default budget on loaded CI hosts. | 83 | // Under package-wide runs they can exceed the default budget on loaded CI hosts. |
| 83 | hookTimeout: 120_000, | 84 | hookTimeout: 120_000, |