| author | |
| committer | |
| log | cc60753aa2279722149e182fb942ca87f70ff804 |
| tree | d2f25a587141262dedc2e379df713b673445ac47 |
| parent | bfef973d9ec64e580fff4197ac53085207471ffd |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: Claude Code <noreply@anthropic.com>10 files changed, 65 insertions(+), 33 deletions(-)
apps/desktop/src/app/DesktopAppIdentity.test.ts+2| ... | ... | @@ -1,3 +1,4 @@ |
| 1 | import * as NodePath from "@effect/platform-node/NodePath"; | |
| 1 | 2 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 2 | 3 | import { assert, describe, it } from "@effect/vitest"; |
| 3 | 4 | import * as Effect from "effect/Effect"; |
| ... | ... | @@ -88,6 +89,7 @@ const makeEnvironmentLayer = (overrides: TestEnvironmentInput = {}) => { |
| 88 | 89 | Layer.provide( |
| 89 | 90 | Layer.mergeAll( |
| 90 | 91 | NodeServices.layer, |
| 92 | NodePath.layerPosix, | |
| 91 | 93 | DesktopConfig.layerTest({ |
| 92 | 94 | ...env, |
| 93 | 95 | }), |
apps/desktop/src/app/DesktopAssets.test.ts+7-1| ... | ... | @@ -1,3 +1,4 @@ |
| 1 | import * as NodePath from "@effect/platform-node/NodePath"; | |
| 1 | 2 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 2 | 3 | import { assert, describe, it } from "@effect/vitest"; |
| 3 | 4 | import * as Effect from "effect/Effect"; |
| ... | ... | @@ -20,7 +21,11 @@ const environmentLayer = DesktopEnvironment.layer({ |
| 20 | 21 | isPackaged: true, |
| 21 | 22 | resourcesPath: "/Applications/T3 Code.app/Contents/Resources", |
| 22 | 23 | runningUnderArm64Translation: false, |
| 23 | }).pipe(Layer.provide(Layer.mergeAll(NodeServices.layer, DesktopConfig.layerTest({})))); | |
| 24 | }).pipe( | |
| 25 | Layer.provide( | |
| 26 | Layer.mergeAll(NodeServices.layer, NodePath.layerPosix, DesktopConfig.layerTest({})), | |
| 27 | ), | |
| 28 | ); | |
| 24 | 29 | |
| 25 | 30 | describe("DesktopAssets", () => { |
| 26 | 31 | it.effect("uses canonical source-tree icons for unpackaged development", () => |
| ... | ... | @@ -39,6 +44,7 @@ describe("DesktopAssets", () => { |
| 39 | 44 | Layer.provide( |
| 40 | 45 | Layer.mergeAll( |
| 41 | 46 | NodeServices.layer, |
| 47 | NodePath.layerPosix, | |
| 42 | 48 | DesktopConfig.layerTest({ VITE_DEV_SERVER_URL: "http://localhost:5733" }), |
| 43 | 49 | ), |
| 44 | 50 | ), |
apps/desktop/src/app/DesktopEnvironment.test.ts+6-1| ... | ... | @@ -1,3 +1,4 @@ |
| 1 | import * as NodePath from "@effect/platform-node/NodePath"; | |
| 1 | 2 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 2 | 3 | import { assert, describe, it } from "@effect/vitest"; |
| 3 | 4 | import * as Effect from "effect/Effect"; |
| ... | ... | @@ -26,7 +27,11 @@ const makeEnvironmentLayer = ( |
| 26 | 27 | DesktopEnvironment.layer({ |
| 27 | 28 | ...defaultInput, |
| 28 | 29 | ...overrides, |
| 29 | }).pipe(Layer.provide(Layer.mergeAll(NodeServices.layer, DesktopConfig.layerTest(env)))); | |
| 30 | }).pipe( | |
| 31 | Layer.provide( | |
| 32 | Layer.mergeAll(NodeServices.layer, NodePath.layerPosix, DesktopConfig.layerTest(env)), | |
| 33 | ), | |
| 34 | ); | |
| 30 | 35 | |
| 31 | 36 | const makeEnvironment = ( |
| 32 | 37 | overrides: Partial<DesktopEnvironment.MakeDesktopEnvironmentInput> = {}, |
apps/server/src/auth/ServerSecretStore.test.ts+2-2| ... | ... | @@ -109,7 +109,7 @@ const ConcurrentReadMissFileSystemLayer = Layer.effect( |
| 109 | 109 | return { |
| 110 | 110 | ...fileSystem, |
| 111 | 111 | readFile: (path) => |
| 112 | String(path).endsWith("/session-signing-key.bin") | |
| 112 | /[\\/]session-signing-key\.bin$/.test(String(path)) | |
| 113 | 113 | ? Ref.updateAndGet(readCountRef, (count) => count + 1).pipe( |
| 114 | 114 | Effect.flatMap((count) => { |
| 115 | 115 | if (count > 2) { |
| ... | ... | @@ -219,7 +219,7 @@ it.layer(NodeServices.layer)("ServerSecretStore.layer", (it) => { |
| 219 | 219 | yield* secretStore.set("session-signing-key", Uint8Array.from([1, 2, 3])); |
| 220 | 220 | |
| 221 | 221 | assert.isTrue( |
| 222 | chmodCalls.some((call) => call.mode === 0o700 && call.path.endsWith("/secrets")), | |
| 222 | chmodCalls.some((call) => call.mode === 0o700 && /[\\/]secrets$/.test(call.path)), | |
| 223 | 223 | ); |
| 224 | 224 | assert.isAtLeast(chmodCalls.filter((call) => call.mode === 0o600).length, 2); |
| 225 | 225 | }).pipe(Effect.provide(NodeServices.layer)), |
apps/server/src/project/ProjectFaviconResolver.test.ts+3-2| ... | ... | @@ -54,11 +54,12 @@ it.layer(TestLayer)("ProjectFaviconResolverLive", (it) => { |
| 54 | 54 | it.effect("serves repeated resolves from cache instead of re-walking candidates", () => |
| 55 | 55 | Effect.gen(function* () { |
| 56 | 56 | const resolver = yield* ProjectFaviconResolver.ProjectFaviconResolver; |
| 57 | const path = yield* Path.Path; | |
| 57 | 58 | const cwd = yield* makeTempDir; |
| 58 | 59 | yield* writeTextFile(cwd, "public/favicon.svg", "<svg>public</svg>"); |
| 59 | 60 | |
| 60 | 61 | const resolved = yield* resolver.resolvePath(cwd); |
| 61 | expect(resolved?.endsWith("public/favicon.svg")).toBe(true); | |
| 62 | expect(resolved?.endsWith(path.join("public", "favicon.svg"))).toBe(true); | |
| 62 | 63 | |
| 63 | 64 | // `favicon.svg` outranks `public/favicon.svg`, so a resolver that walked |
| 64 | 65 | // the candidate list again would switch to it. Staying on the original |
| ... | ... | @@ -71,7 +72,7 @@ it.layer(TestLayer)("ProjectFaviconResolverLive", (it) => { |
| 71 | 72 | |
| 72 | 73 | yield* TestClock.adjust(Duration.minutes(11)); |
| 73 | 74 | |
| 74 | expect((yield* resolver.resolvePath(cwd))?.endsWith("/favicon.svg")).toBe(true); | |
| 75 | expect(yield* resolver.resolvePath(cwd)).toBe(path.join(cwd, "favicon.svg")); | |
| 75 | 76 | expect(yield* resolver.resolvePath(cwd)).not.toBe(resolved); |
| 76 | 77 | }).pipe(Effect.provide(TestClock.layer())), |
| 77 | 78 | ); |
apps/server/src/provider/Drivers/ClaudeSkills.test.ts+13-7| ... | ... | @@ -1,3 +1,4 @@ |
| 1 | import * as NodePath from "@effect/platform-node/NodePath"; | |
| 1 | 2 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 2 | 3 | import { assert, it } from "@effect/vitest"; |
| 3 | 4 | import * as Effect from "effect/Effect"; |
| ... | ... | @@ -492,7 +493,11 @@ it.layer(NodeServices.layer)("discoverClaudeSkills", (it) => { |
| 492 | 493 | |
| 493 | 494 | it.effect("lets the administrator's managed policy outrank every other settings file", () => |
| 494 | 495 | Effect.gen(function* () { |
| 495 | const path = yield* Path.Path; | |
| 496 | // The function is pure on its `path` argument, so hand it the | |
| 497 | // implementation matching each platform under test rather than the | |
| 498 | // host's. | |
| 499 | const path = yield* Path.Path.pipe(Effect.provide(NodePath.layerPosix)); | |
| 500 | const win32Path = yield* Path.Path.pipe(Effect.provide(NodePath.layerWin32)); | |
| 496 | 501 | |
| 497 | 502 | for (const [platform, expected] of [ |
| 498 | 503 | ["darwin", "/Library/Application Support/ClaudeCode/managed-settings.json"], |
| ... | ... | @@ -508,14 +513,15 @@ it.layer(NodeServices.layer)("discoverClaudeSkills", (it) => { |
| 508 | 513 | } |
| 509 | 514 | |
| 510 | 515 | assert.deepEqual( |
| 511 | skillOverrideSettingsPaths(path, "/home/.claude", undefined, "win32", { | |
| 512 | PROGRAMDATA: "C:/ProgramData", | |
| 516 | skillOverrideSettingsPaths(win32Path, "C:\\Users\\me\\.claude", undefined, "win32", { | |
| 517 | PROGRAMDATA: "C:\\ProgramData", | |
| 513 | 518 | }).at(-1), |
| 514 | "C:/ProgramData/ClaudeCode/managed-settings.json", | |
| 519 | "C:\\ProgramData\\ClaudeCode\\managed-settings.json", | |
| 520 | ); | |
| 521 | assert.deepEqual( | |
| 522 | skillOverrideSettingsPaths(win32Path, "C:\\Users\\me\\.claude", undefined, "win32", {}), | |
| 523 | ["C:\\Users\\me\\.claude\\settings.json"], | |
| 515 | 524 | ); |
| 516 | assert.deepEqual(skillOverrideSettingsPaths(path, "/home/.claude", undefined, "win32", {}), [ | |
| 517 | "/home/.claude/settings.json", | |
| 518 | ]); | |
| 519 | 525 | |
| 520 | 526 | // Only the repository root's local file joins in, after the |
| 521 | 527 | // workspace's own local file so it wins. |
packages/ssh/src/config.test.ts+2-1| ... | ... | @@ -1,3 +1,4 @@ |
| 1 | import * as NodePath from "@effect/platform-node/NodePath"; | |
| 1 | 2 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 2 | 3 | import { assert, describe, it } from "@effect/vitest"; |
| 3 | 4 | import * as Effect from "effect/Effect"; |
| ... | ... | @@ -133,6 +134,6 @@ describe("ssh config", () => { |
| 133 | 134 | "/tmp/home", |
| 134 | 135 | ); |
| 135 | 136 | assert.equal(pattern, "/tmp/home/.ssh/config.d/*.conf"); |
| 136 | }).pipe(Effect.provide(NodeServices.layer)), | |
| 137 | }).pipe(Effect.provide(NodePath.layerPosix)), | |
| 137 | 138 | ); |
| 138 | 139 | }); |
packages/tailscale/src/tailscale.test.ts+14-12| ... | ... | @@ -8,6 +8,7 @@ import * as Sink from "effect/Sink"; |
| 8 | 8 | import * as Stream from "effect/Stream"; |
| 9 | 9 | import * as TestClock from "effect/testing/TestClock"; |
| 10 | 10 | import { ChildProcessSpawner } from "effect/unstable/process"; |
| 11 | import { HostProcessPlatform } from "@t3tools/shared/hostProcess"; | |
| 11 | 12 | |
| 12 | 13 | import { |
| 13 | 14 | buildTailscaleHttpsBaseUrl, |
| ... | ... | @@ -98,14 +99,22 @@ function neverFinishingMockHandle() { |
| 98 | 99 | }); |
| 99 | 100 | } |
| 100 | 101 | |
| 102 | // The executable name depends on the host platform (`tailscale.exe` on | |
| 103 | // Windows), so pin it: these tests assert the posix spelling. | |
| 104 | function spawnerLayer(spawner: ChildProcessSpawner.ChildProcessSpawner["Service"]) { | |
| 105 | return Layer.merge( | |
| 106 | Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner), | |
| 107 | Layer.succeed(HostProcessPlatform, "linux"), | |
| 108 | ); | |
| 109 | } | |
| 110 | ||
| 101 | 111 | function mockSpawnerLayer( |
| 102 | 112 | handler: ( |
| 103 | 113 | command: string, |
| 104 | 114 | args: ReadonlyArray<string>, |
| 105 | 115 | ) => { stdout?: string; stderr?: string; code?: number }, |
| 106 | 116 | ) { |
| 107 | return Layer.succeed( | |
| 108 | ChildProcessSpawner.ChildProcessSpawner, | |
| 117 | return spawnerLayer( | |
| 109 | 118 | ChildProcessSpawner.make((command) => { |
| 110 | 119 | const childProcess = command as unknown as { |
| 111 | 120 | readonly command: string; |
| ... | ... | @@ -194,10 +203,7 @@ describe("tailscale", () => { |
| 194 | 203 | method: "spawn", |
| 195 | 204 | cause: systemCause, |
| 196 | 205 | }); |
| 197 | const layer = Layer.succeed( | |
| 198 | ChildProcessSpawner.ChildProcessSpawner, | |
| 199 | ChildProcessSpawner.make(() => Effect.fail(cause)), | |
| 200 | ); | |
| 206 | const layer = spawnerLayer(ChildProcessSpawner.make(() => Effect.fail(cause))); | |
| 201 | 207 | |
| 202 | 208 | return Effect.gen(function* () { |
| 203 | 209 | const error = yield* readTailscaleStatus.pipe(Effect.flip, Effect.provide(layer)); |
| ... | ... | @@ -218,8 +224,7 @@ describe("tailscale", () => { |
| 218 | 224 | // inside an `Effect.callback` registration, so that throw arrives as a |
| 219 | 225 | // defect rather than a typed error - the shape reproduced here. |
| 220 | 226 | const defect = Object.assign(new Error("spawn tailscale ENOTDIR"), { code: "ENOTDIR" }); |
| 221 | const layer = Layer.succeed( | |
| 222 | ChildProcessSpawner.ChildProcessSpawner, | |
| 227 | const layer = spawnerLayer( | |
| 223 | 228 | ChildProcessSpawner.make(() => |
| 224 | 229 | Effect.callback<never, never>(() => { |
| 225 | 230 | throw defect; |
| ... | ... | @@ -297,10 +302,7 @@ describe("tailscale", () => { |
| 297 | 302 | it.effect("times out tailscale status through TestClock", () => { |
| 298 | 303 | const layer = Layer.merge( |
| 299 | 304 | TestClock.layer(), |
| 300 | Layer.succeed( | |
| 301 | ChildProcessSpawner.ChildProcessSpawner, | |
| 302 | ChildProcessSpawner.make(() => Effect.succeed(neverFinishingMockHandle())), | |
| 303 | ), | |
| 305 | spawnerLayer(ChildProcessSpawner.make(() => Effect.succeed(neverFinishingMockHandle()))), | |
| 304 | 306 | ); |
| 305 | 307 | |
| 306 | 308 | return Effect.gen(function* () { |
scripts/mobile-showcase.test.ts+14-6| ... | ... | @@ -1,3 +1,5 @@ |
| 1 | // @effect-diagnostics nodeBuiltinImport:off - expectations mirror the host path joins the script under test performs. | |
| 2 | import * as NodePath from "node:path"; | |
| 1 | 3 | import { assert, it } from "@effect/vitest"; |
| 2 | 4 | import { PNG } from "pngjs"; |
| 3 | 5 | |
| ... | ... | @@ -167,8 +169,14 @@ it("expands both appearances into independent upload-ready directories", () => { |
| 167 | 169 | directory: showcaseCaptureDirectory("/captures", capture), |
| 168 | 170 | })), |
| 169 | 171 | [ |
| 170 | { appearance: "light", directory: "/captures/apple/iphone-test/light/t3-code" }, | |
| 171 | { appearance: "dark", directory: "/captures/apple/iphone-test/dark/t3-code" }, | |
| 172 | { | |
| 173 | appearance: "light", | |
| 174 | directory: NodePath.join("/captures", "apple", "iphone-test", "light", "t3-code"), | |
| 175 | }, | |
| 176 | { | |
| 177 | appearance: "dark", | |
| 178 | directory: NodePath.join("/captures", "apple", "iphone-test", "dark", "t3-code"), | |
| 179 | }, | |
| 172 | 180 | ], |
| 173 | 181 | ); |
| 174 | 182 | }); |
| ... | ... | @@ -192,10 +200,10 @@ it("expands themes into independent upload-ready directories per appearance", () |
| 192 | 200 | showcaseCaptureDirectory("/captures", capture), |
| 193 | 201 | ), |
| 194 | 202 | [ |
| 195 | "/captures/apple/iphone-test/light/ocean", | |
| 196 | "/captures/apple/iphone-test/light/ember", | |
| 197 | "/captures/apple/iphone-test/dark/ocean", | |
| 198 | "/captures/apple/iphone-test/dark/ember", | |
| 203 | NodePath.join("/captures", "apple", "iphone-test", "light", "ocean"), | |
| 204 | NodePath.join("/captures", "apple", "iphone-test", "light", "ember"), | |
| 205 | NodePath.join("/captures", "apple", "iphone-test", "dark", "ocean"), | |
| 206 | NodePath.join("/captures", "apple", "iphone-test", "dark", "ember"), | |
| 199 | 207 | ], |
| 200 | 208 | ); |
| 201 | 209 | }); |
scripts/mobile-showcase.ts+2-1| ... | ... | @@ -53,7 +53,8 @@ export function resolveAndroidSdkRoot( |
| 53 | 53 | const configured = environment.ANDROID_HOME ?? environment.ANDROID_SDK_ROOT; |
| 54 | 54 | if (configured) return configured; |
| 55 | 55 | const home = environment.HOME ?? environment.USERPROFILE ?? ""; |
| 56 | return NodePath.join(home, platform === "darwin" ? "Library/Android/sdk" : "Android/Sdk"); | |
| 56 | const path = platform === "win32" ? NodePath.win32 : NodePath.posix; | |
| 57 | return path.join(home, platform === "darwin" ? "Library/Android/sdk" : "Android/Sdk"); | |
| 57 | 58 | } |
| 58 | 59 | |
| 59 | 60 | const ANDROID_SDK_ROOT = resolveAndroidSdkRoot(NodeProcess.env); |