Electron 44 turns clipboard.writeText into a Promise and removes clipboard.writeImage in favour of clipboard.write with ClipboardItem, so the shell and preview manager follow. node-abi is pinned to a release that knows the new Electron ABI so prebuilt native modules resolve during packaging, and the release-age policy excludes this exact version.
Unified · Split
| @@ -22,7 +22,7 @@ · 21 unmodified lines | ||
| 22 | 22 | "@t3tools/ssh": "workspace:*", |
| 23 | 23 | "@t3tools/tailscale": "workspace:*", |
| 24 | 24 | "effect": "catalog:", |
| 25 | − "electron": "43.4.1", | |
| 25 | + "electron": "44.1.0", | |
| 26 | 26 | "electron-store": "^8.2.0", |
| 27 | 27 | "electron-updater": "^6.6.2", |
| 28 | 28 | "playwright-core": "1.60.0", |
| @@ -36,6 +36,28 @@ describe("ElectronShell", () => { · 35 unmodified lines | ||
| 36 | 36 | }).pipe(Effect.provide(ElectronShell.layer)), |
| 37 | 37 | ); |
| 38 | 38 | |
| 39 | + it.effect("copies text to the system clipboard", () => | |
| 40 | + Effect.gen(function* () { | |
| 41 | + writeTextMock.mockResolvedValue(undefined); | |
| 42 | + | |
| 43 | + const electronShell = yield* ElectronShell.ElectronShell; | |
| 44 | + yield* electronShell.copyText("https://example.com/path"); | |
| 45 | + | |
| 46 | + assert.deepEqual(writeTextMock.mock.calls, [["https://example.com/path"]]); | |
| 47 | + }).pipe(Effect.provide(ElectronShell.layer)), | |
| 48 | + ); | |
| 49 | + | |
| 50 | + it.effect("does not fail when the clipboard write rejects", () => | |
| 51 | + Effect.gen(function* () { | |
| 52 | + writeTextMock.mockRejectedValue(new Error("write failed")); | |
| 53 | + | |
| 54 | + const electronShell = yield* ElectronShell.ElectronShell; | |
| 55 | + yield* electronShell.copyText("https://example.com/path"); | |
| 56 | + | |
| 57 | + assert.deepEqual(writeTextMock.mock.calls, [["https://example.com/path"]]); | |
| 58 | + }).pipe(Effect.provide(ElectronShell.layer)), | |
| 59 | + ); | |
| 60 | + | |
| 39 | 61 | it.effect("opens the Full Disk Access settings anchor", () => |
| 40 | 62 | Effect.gen(function* () { |
| 41 | 63 | openExternalMock.mockResolvedValue(undefined); |
| @@ -87,9 +87,7 @@ export const make = ElectronShell.of({ · 86 unmodified lines | ||
| 87 | 87 | ), |
| 88 | 88 | ), |
| 89 | 89 | copyText: (text) => |
| 90 | − Effect.sync(() => { | |
| 91 | − Electron.clipboard.writeText(text); | |
| 92 | − }), | |
| 90 | + Effect.promise(() => Electron.clipboard.writeText(text).catch(() => undefined)), | |
| 93 | 91 | }); |
| 94 | 92 | |
| 95 | 93 | export const layer = Layer.succeed(ElectronShell, make); |
| @@ -113,6 +113,7 @@ describe("previewWindowOpenAction", () => { · 112 unmodified lines | ||
| 113 | 113 | |
| 114 | 114 | const { |
| 115 | 115 | browserWindowConstructor, |
| 116 | + clipboardItemConstructor, | |
| 116 | 117 | createFromPath, |
| 117 | 118 | fromId, |
| 118 | 119 | getFocusedWebContents, |
| @@ -120,23 +121,32 @@ const { · 1 unmodified line | ||
| 120 | 121 | showItemInFolder, |
| 121 | 122 | webviewSend, |
| 122 | 123 | writeFile, |
| 123 | − writeImage, | |
| 124 | + writeClipboard, | |
| 124 | 125 | } = vi.hoisted(() => ({ |
| 125 | 126 | browserWindowConstructor: vi.fn(), |
| 126 | − createFromPath: vi.fn((): { readonly isEmpty: () => boolean } => ({ isEmpty: () => false })), | |
| 127 | + clipboardItemConstructor: vi.fn(), | |
| 128 | + createFromPath: vi.fn((): { readonly isEmpty: () => boolean; readonly toPNG: () => Buffer } => ({ | |
| 129 | + isEmpty: () => false, | |
| 130 | + toPNG: () => Buffer.from("png"), | |
| 131 | + })), | |
| 127 | 132 | fromId: vi.fn<(_id?: number) => Electron.WebContents | null>((_id?: number) => null), |
| 128 | 133 | getFocusedWebContents: vi.fn(() => null), |
| 129 | 134 | mkdir: vi.fn((_path: string) => undefined), |
| 130 | 135 | showItemInFolder: vi.fn(), |
| 131 | 136 | webviewSend: vi.fn(), |
| 132 | 137 | writeFile: vi.fn((_path: string, _data: Uint8Array) => undefined), |
| 133 | − writeImage: vi.fn(), | |
| 138 | + writeClipboard: vi.fn(async () => undefined), | |
| 134 | 139 | })); |
| 135 | 140 | |
| 136 | 141 | vi.mock("electron", () => ({ |
| 137 | 142 | BrowserWindow: browserWindowConstructor, |
| 143 | + ClipboardItem: class { | |
| 144 | + constructor(data: Record<string, unknown>) { | |
| 145 | + clipboardItemConstructor(data); | |
| 146 | + } | |
| 147 | + }, | |
| 138 | 148 | clipboard: { |
| 139 | − writeImage, | |
| 149 | + write: writeClipboard, | |
| 140 | 150 | }, |
| 141 | 151 | nativeImage: { |
| 142 | 152 | createFromPath, |
| @@ -471,7 +481,8 @@ describe("PreviewManager", () => { · 328 unmodified lines | ||
| 471 | 481 | mkdir.mockClear(); |
| 472 | 482 | writeFile.mockClear(); |
| 473 | 483 | showItemInFolder.mockClear(); |
| 474 | − writeImage.mockClear(); | |
| 484 | + clipboardItemConstructor.mockClear(); | |
| 485 | + writeClipboard.mockClear(); | |
| 475 | 486 | createFromPath.mockClear(); |
| 476 | 487 | webviewSend.mockClear(); |
| 477 | 488 | }); |
| @@ -3671,7 +3682,10 @@ describe("PreviewManager", () => { · 3193 unmodified lines | ||
| 3671 | 3682 | yield* manager.copyArtifactToClipboard(artifactPath); |
| 3672 | 3683 | |
| 3673 | 3684 | expect(createFromPath).toHaveBeenCalledWith(artifactPath); |
| 3674 | − expect(writeImage).toHaveBeenCalledOnce(); | |
| 3685 | + expect(clipboardItemConstructor).toHaveBeenCalledWith({ | |
| 3686 | + "image/png": expect.any(Blob), | |
| 3687 | + }); | |
| 3688 | + expect(writeClipboard).toHaveBeenCalledOnce(); | |
| 3675 | 3689 | const exit = yield* Effect.exit( |
| 3676 | 3690 | manager.copyArtifactToClipboard("/tmp/t3/dev/settings.json"), |
| 3677 | 3691 | ); |
| @@ -3685,7 +3699,10 @@ describe("PreviewManager", () => { · 7 unmodified lines | ||
| 3685 | 3699 | }); |
| 3686 | 3700 | expect("cause" in error).toBe(false); |
| 3687 | 3701 | |
| 3688 | − createFromPath.mockReturnValueOnce({ isEmpty: () => true }); | |
| 3702 | + createFromPath.mockReturnValueOnce({ | |
| 3703 | + isEmpty: () => true, | |
| 3704 | + toPNG: () => Buffer.from("invalid"), | |
| 3705 | + }); | |
| 3689 | 3706 | const invalidImageExit = yield* Effect.exit(manager.copyArtifactToClipboard(artifactPath)); |
| 3690 | 3707 | expect(Exit.isFailure(invalidImageExit)).toBe(true); |
| 3691 | 3708 | if (Exit.isSuccess(invalidImageExit)) return; |
| @@ -3693,6 +3710,18 @@ describe("PreviewManager", () => { · 1 unmodified line | ||
| 3693 | 3710 | _tag: "PreviewArtifactImageLoadError", |
| 3694 | 3711 | artifactPath, |
| 3695 | 3712 | }); |
| 3713 | + | |
| 3714 | + const writeCause = new Error("clipboard write failed"); | |
| 3715 | + writeClipboard.mockRejectedValueOnce(writeCause); | |
| 3716 | + const writeExit = yield* Effect.exit(manager.copyArtifactToClipboard(artifactPath)); | |
| 3717 | + expect(Exit.isFailure(writeExit)).toBe(true); | |
| 3718 | + if (Exit.isSuccess(writeExit)) return; | |
| 3719 | + expect(Option.getOrThrow(Cause.findErrorOption(writeExit.cause))).toMatchObject({ | |
| 3720 | + _tag: "PreviewOperationError", | |
| 3721 | + operation: "copyArtifactToClipboard.write", | |
| 3722 | + artifactPath, | |
| 3723 | + cause: writeCause, | |
| 3724 | + }); | |
| 3696 | 3725 | }), |
| 3697 | 3726 | ), |
| 3698 | 3727 | ); |
| @@ -32,7 +32,15 @@ import type { · 31 unmodified lines | ||
| 32 | 32 | } from "@t3tools/contracts"; |
| 33 | 33 | import { HostProcessPlatform } from "@t3tools/shared/hostProcess"; |
| 34 | 34 | import { normalizePreviewUrl } from "@t3tools/shared/preview"; |
| 35 | −import { BrowserWindow, type Session, clipboard, nativeImage, shell, webContents } from "electron"; | |
| 35 | +import { | |
| 36 | + BrowserWindow, | |
| 37 | + ClipboardItem, | |
| 38 | + type Session, | |
| 39 | + clipboard, | |
| 40 | + nativeImage, | |
| 41 | + shell, | |
| 42 | + webContents, | |
| 43 | +} from "electron"; | |
| 36 | 44 | import * as Cause from "effect/Cause"; |
| 37 | 45 | import * as Clock from "effect/Clock"; |
| 38 | 46 | import * as Context from "effect/Context"; |
| @@ -4107,8 +4115,14 @@ const makeNativeOperations = Effect.fn("PreviewManager.makeOperations")(function · 4068 unmodified lines | ||
| 4107 | 4115 | if (image.isEmpty()) { |
| 4108 | 4116 | return yield* new PreviewArtifactImageLoadError({ artifactPath: resolvedPath }); |
| 4109 | 4117 | } |
| 4110 | − yield* attempt({ operation: "copyArtifactToClipboard.write", artifactPath: resolvedPath }, () => | |
| 4111 | − clipboard.writeImage(image), | |
| 4118 | + yield* attemptPromise( | |
| 4119 | + { operation: "copyArtifactToClipboard.write", artifactPath: resolvedPath }, | |
| 4120 | + () => | |
| 4121 | + clipboard.write([ | |
| 4122 | + new ClipboardItem({ | |
| 4123 | + "image/png": new Blob([Uint8Array.from(image.toPNG())], { type: "image/png" }), | |
| 4124 | + }), | |
| 4125 | + ]), | |
| 4112 | 4126 | ); |
| 4113 | 4127 | }); |
| 4114 | 4128 | |
| @@ -79,6 +79,7 @@ overrides: · 78 unmodified lines | ||
| 79 | 79 | expo-router: 57.0.17 |
| 80 | 80 | expo-sharing>@expo/config-plugins: 57.0.9 |
| 81 | 81 | expo-sharing>@expo/config-types: 57.0.2 |
| 82 | + node-abi: 4.33.0 | |
| 82 | 83 | lightningcss: 1.33.0 |
| 83 | 84 | tailwindcss: 4.3.3 |
| 84 | 85 | vite: npm:@voidzero-dev/vite-plus-core@0.3.0 |
| @@ -129,7 +130,7 @@ importers: · 44 unmodified lines | ||
| 129 | 130 | dependencies: |
| 130 | 131 | '@clerk/electron': |
| 131 | 132 | specifier: 0.0.37 |
| 132 | − version: 0.0.37(@clerk/electron-passkeys@0.0.3)(electron-store@8.2.0)(electron@43.4.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) | |
| 133 | + version: 0.0.37(@clerk/electron-passkeys@0.0.3)(electron-store@8.2.0)(electron@44.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) | |
| 133 | 134 | '@clerk/electron-passkeys': |
| 134 | 135 | specifier: 0.0.3 |
| 135 | 136 | version: 0.0.3 |
| @@ -158,8 +159,8 @@ importers: · 22 unmodified lines | ||
| 158 | 159 | specifier: 4.0.0-beta.103 |
| 159 | 160 | version: 4.0.0-beta.103(patch_hash=af36b7948b6f9c56623074662b51dade5699880c1a7c71245de73e13c3185fb6) |
| 160 | 161 | electron: |
| 161 | − specifier: 43.4.1 | |
| 162 | − version: 43.4.1 | |
| 162 | + specifier: 44.1.0 | |
| 163 | + version: 44.1.0 | |
| 163 | 164 | electron-store: |
| 164 | 165 | specifier: ^8.2.0 |
| 165 | 166 | version: 8.2.0 |
| @@ -556,7 +557,7 @@ importers: · 390 unmodified lines | ||
| 556 | 557 | version: 1.5.0(@types/react@19.2.16)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) |
| 557 | 558 | '@clerk/electron': |
| 558 | 559 | specifier: 0.0.37 |
| 559 | − version: 0.0.37(@clerk/electron-passkeys@0.0.3)(electron-store@8.2.0)(electron@43.4.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) | |
| 560 | + version: 0.0.37(@clerk/electron-passkeys@0.0.3)(electron-store@8.2.0)(electron@44.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) | |
| 560 | 561 | '@clerk/react': |
| 561 | 562 | specifier: 6.14.7 |
| 562 | 563 | version: 6.14.7(react-dom@19.2.6(react@19.2.6))(react@19.2.6) |
| @@ -6688,8 +6689,8 @@ packages: · 6125 unmodified lines | ||
| 6688 | 6689 | resolution: {integrity: sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg==} |
| 6689 | 6690 | engines: {node: '>=8.0.0'} |
| 6690 | 6691 | |
| 6691 | − electron@43.4.1: | |
| 6692 | − resolution: {integrity: sha512-5b+EuiwkgG5iRcsEL34rimgRpkYp15SsfZOa0pC5kXs0Tb82TH4n95rpQzTZa7yRCbA7tm0WoEbuBL6NaAhAcA==} | |
| 6692 | + electron@44.1.0: | |
| 6693 | + resolution: {integrity: sha512-kmLg8axOg22DC3fXx5NCwBYW8fx0rK2zzJ3Tf67GjNCkxfoxEcd+yo0QfDjlBtHJs3xeA8fTg64b6iVzFTVErg==} | |
| 6693 | 6694 | engines: {node: '>= 22.12.0'} |
| 6694 | 6695 | hasBin: true |
| 6695 | 6696 | |
| @@ -8573,8 +8574,8 @@ packages: · 1877 unmodified lines | ||
| 8573 | 8574 | nlcst-to-string@4.0.0: |
| 8574 | 8575 | resolution: {integrity: sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA==} |
| 8575 | 8576 | |
| 8576 | − node-abi@4.31.0: | |
| 8577 | − resolution: {integrity: sha512-Erq5w/t3syw3s4sDsUaX4QttIdBPsGKTT1DTRsCkTonGggczhlDKm/wDX3o+HPJpQ41EjXCbcmXf0tgr5YZJXw==} | |
| 8577 | + node-abi@4.33.0: | |
| 8578 | + resolution: {integrity: sha512-vLBWCKb+7LWsX+TbfzWOkw0W81m377tyx3hOweBTjO43CXZnRGS1/JPWs20fr0PgZyDXk6ROYrylsEycK8raDA==} | |
| 8578 | 8579 | engines: {node: '>=22.12.0'} |
| 8579 | 8580 | |
| 8580 | 8581 | node-addon-api@7.1.1: |
| @@ -11846,12 +11847,12 @@ snapshots: · 3265 unmodified lines | ||
| 11846 | 11847 | '@clerk/electron-passkeys-win32-arm64-msvc': 0.0.3 |
| 11847 | 11848 | '@clerk/electron-passkeys-win32-x64-msvc': 0.0.3 |
| 11848 | 11849 | |
| 11849 | − '@clerk/electron@0.0.37(@clerk/electron-passkeys@0.0.3)(electron-store@8.2.0)(electron@43.4.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': | |
| 11850 | + '@clerk/electron@0.0.37(@clerk/electron-passkeys@0.0.3)(electron-store@8.2.0)(electron@44.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': | |
| 11850 | 11851 | dependencies: |
| 11851 | 11852 | '@clerk/clerk-js': 6.30.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6) |
| 11852 | 11853 | '@clerk/react': 6.14.7(react-dom@19.2.6(react@19.2.6))(react@19.2.6) |
| 11853 | 11854 | '@clerk/shared': 4.30.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6) |
| 11854 | − electron: 43.4.1 | |
| 11855 | + electron: 44.1.0 | |
| 11855 | 11856 | react: 19.2.6 |
| 11856 | 11857 | tslib: 2.8.1 |
| 11857 | 11858 | optionalDependencies: |
| @@ -12228,7 +12229,7 @@ snapshots: · 370 unmodified lines | ||
| 12228 | 12229 | dependencies: |
| 12229 | 12230 | '@malept/cross-spawn-promise': 2.0.0 |
| 12230 | 12231 | debug: 4.4.3 |
| 12231 | − node-abi: 4.31.0 | |
| 12232 | + node-abi: 4.33.0 | |
| 12232 | 12233 | node-api-version: 0.2.1 |
| 12233 | 12234 | node-gyp: 12.3.0 |
| 12234 | 12235 | read-binary-file-arch: 1.0.6 |
| @@ -16741,7 +16742,7 @@ snapshots: · 4506 unmodified lines | ||
| 16741 | 16742 | transitivePeerDependencies: |
| 16742 | 16743 | - supports-color |
| 16743 | 16744 | |
| 16744 | − electron@43.4.1: | |
| 16745 | + electron@44.1.0: | |
| 16745 | 16746 | dependencies: |
| 16746 | 16747 | '@electron-internal/extract-zip': 1.0.5 |
| 16747 | 16748 | '@electron/get': 5.1.0 |
| @@ -19350,7 +19351,7 @@ snapshots: · 2602 unmodified lines | ||
| 19350 | 19351 | dependencies: |
| 19351 | 19352 | '@types/nlcst': 2.0.3 |
| 19352 | 19353 | |
| 19353 | − node-abi@4.31.0: | |
| 19354 | + node-abi@4.33.0: | |
| 19354 | 19355 | dependencies: |
| 19355 | 19356 | semver: 7.8.5 |
| 19356 | 19357 | |
| @@ -90,6 +90,7 @@ minimumReleaseAgeExclude: · 89 unmodified lines | ||
| 90 | 90 | - expo-updates@57.0.19 |
| 91 | 91 | - expo@57.0.18 |
| 92 | 92 | - expo-widgets@57.0.15 |
| 93 | + - electron@44.1.0 | |
| 93 | 94 | - "@anthropic-ai/claude-agent-sdk@0.3.260" |
| 94 | 95 | |
| 95 | 96 | overrides: |
| @@ -137,6 +138,7 @@ overrides: · 41 unmodified lines | ||
| 137 | 138 | expo-router: 57.0.17 |
| 138 | 139 | "expo-sharing>@expo/config-plugins": 57.0.9 |
| 139 | 140 | "expo-sharing>@expo/config-types": 57.0.2 |
| 141 | + node-abi: 4.33.0 | |
| 140 | 142 | lightningcss: "catalog:" |
| 141 | 143 | tailwindcss: "catalog:" |
| 142 | 144 | vite: "catalog:" |