Skip to content

Commit e6330ea

Browse files
[codex] Bump Effect to beta.73 and migrate compatibility APIs (#2840)
Co-authored-by: codex <codex@users.noreply.github.com>
1 parent 4f0f24f commit e6330ea

47 files changed

Lines changed: 1088 additions & 850 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,14 @@ jobs:
4646
- name: Install dependencies
4747
run: bun install --frozen-lockfile
4848

49+
- name: Ensure Electron runtime is installed
50+
run: |
51+
if ! node -e "require('./apps/desktop/node_modules/electron')" >/dev/null 2>&1; then
52+
rm -rf apps/desktop/node_modules/electron/dist apps/desktop/node_modules/electron/path.txt
53+
bun apps/desktop/node_modules/electron/install.js
54+
fi
55+
node -e "require('./apps/desktop/node_modules/electron')"
56+
4957
- name: Format
5058
run: bun run fmt:check
5159

apps/desktop/src/app/DesktopApp.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import * as Cause from "effect/Cause";
2+
import * as Crypto from "effect/Crypto";
23
import * as Data from "effect/Data";
34
import * as Effect from "effect/Effect";
45
import * as Option from "effect/Option";
5-
import * as Random from "effect/Random";
66
import * as Ref from "effect/Ref";
77

88
import * as NetService from "@t3tools/shared/Net";
@@ -26,7 +26,8 @@ const DEFAULT_DESKTOP_BACKEND_PORT = 3773;
2626
const MAX_TCP_PORT = 65_535;
2727
const DESKTOP_BACKEND_PORT_PROBE_HOSTS = ["127.0.0.1", "0.0.0.0", "::"] as const;
2828

29-
const makeDesktopRunId = Random.nextUUIDv4.pipe(
29+
const makeDesktopRunId = Crypto.Crypto.pipe(
30+
Effect.flatMap((crypto) => crypto.randomUUIDv4),
3031
Effect.map((value) => value.replaceAll("-", "").slice(0, 12)),
3132
);
3233

apps/desktop/src/backend/DesktopBackendConfiguration.ts

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { parsePersistedServerObservabilitySettings } from "@t3tools/shared/serverSettings";
22
import * as Context from "effect/Context";
3+
import * as Crypto from "effect/Crypto";
34
import * as Effect from "effect/Effect";
5+
import * as Encoding from "effect/Encoding";
46
import * as FileSystem from "effect/FileSystem";
57
import * as Layer from "effect/Layer";
68
import * as Option from "effect/Option";
7-
import * as Random from "effect/Random";
9+
import * as PlatformError from "effect/PlatformError";
810
import * as Ref from "effect/Ref";
911

1012
import * as DesktopBackendManager from "./DesktopBackendManager.ts";
@@ -13,7 +15,10 @@ import * as DesktopObservability from "../app/DesktopObservability.ts";
1315
import * as DesktopServerExposure from "./DesktopServerExposure.ts";
1416

1517
export interface DesktopBackendConfigurationShape {
16-
readonly resolve: Effect.Effect<DesktopBackendManager.DesktopBackendStartConfig>;
18+
readonly resolve: Effect.Effect<
19+
DesktopBackendManager.DesktopBackendStartConfig,
20+
PlatformError.PlatformError
21+
>;
1722
}
1823

1924
export class DesktopBackendConfiguration extends Context.Service<
@@ -80,23 +85,6 @@ const readPersistedBackendObservabilitySettings: Effect.Effect<
8085
};
8186
});
8287

83-
const getOrCreateBootstrapToken = Effect.fn("desktop.backendConfiguration.bootstrapToken")(
84-
function* (tokenRef: Ref.Ref<Option.Option<string>>) {
85-
const existing = yield* Ref.get(tokenRef);
86-
if (Option.isSome(existing)) {
87-
return existing.value;
88-
}
89-
90-
let token = "";
91-
while (token.length < 48) {
92-
token += (yield* Random.nextUUIDv4).replace(/-/g, "");
93-
}
94-
token = token.slice(0, 48);
95-
yield* Ref.set(tokenRef, Option.some(token));
96-
return token;
97-
},
98-
);
99-
10088
const resolveBackendStartConfig = Effect.fn("desktop.backendConfiguration.resolveStartConfig")(
10189
function* (input: {
10290
readonly bootstrapToken: string;
@@ -148,11 +136,22 @@ export const layer = Layer.effect(
148136
const environment = yield* DesktopEnvironment.DesktopEnvironment;
149137
const fileSystem = yield* FileSystem.FileSystem;
150138
const serverExposure = yield* DesktopServerExposure.DesktopServerExposure;
139+
const crypto = yield* Crypto.Crypto;
151140
const tokenRef = yield* Ref.make(Option.none<string>());
141+
const getOrCreateBootstrapToken = Effect.gen(function* () {
142+
const existing = yield* Ref.get(tokenRef);
143+
if (Option.isSome(existing)) {
144+
return existing.value;
145+
}
146+
147+
const token = Encoding.encodeHex(yield* crypto.randomBytes(24));
148+
yield* Ref.set(tokenRef, Option.some(token));
149+
return token;
150+
});
152151

153152
return DesktopBackendConfiguration.of({
154153
resolve: Effect.gen(function* () {
155-
const bootstrapToken = yield* getOrCreateBootstrapToken(tokenRef);
154+
const bootstrapToken = yield* getOrCreateBootstrapToken;
156155
const observabilitySettings = yield* readPersistedBackendObservabilitySettings.pipe(
157156
Effect.provideService(FileSystem.FileSystem, fileSystem),
158157
Effect.provideService(DesktopEnvironment.DesktopEnvironment, environment),

apps/desktop/src/backend/DesktopBackendManager.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -329,21 +329,31 @@ const makeDesktopBackendManager = Effect.fn("makeDesktopBackendManager")(functio
329329
}
330330

331331
yield* Ref.set(desktopState.backendReady, false);
332-
const config = yield* configuration.resolve;
332+
const config = yield* configuration.resolve.pipe(
333+
Effect.tapError((error) =>
334+
logBackendManagerError("failed to generate desktop backend configuration", {
335+
cause: error.message,
336+
}),
337+
),
338+
Effect.option,
339+
);
340+
if (Option.isNone(config)) {
341+
return;
342+
}
333343
const entryExists = yield* fileSystem
334-
.exists(config.entryPath)
344+
.exists(config.value.entryPath)
335345
.pipe(Effect.orElseSucceed(() => false));
336346

337347
yield* cancelRestart;
338348
yield* Ref.update(state, (latest) => ({
339349
...latest,
340350
desiredRunning: true,
341351
ready: false,
342-
config: Option.some(config),
352+
config: Option.some(config.value),
343353
}));
344354

345355
if (!entryExists) {
346-
yield* scheduleRestart(`missing server entry at ${config.entryPath}`);
356+
yield* scheduleRestart(`missing server entry at ${config.value.entryPath}`);
347357
return;
348358
}
349359

@@ -425,15 +435,15 @@ const makeDesktopBackendManager = Effect.fn("makeDesktopBackendManager")(functio
425435
});
426436

427437
const program = runBackendProcess({
428-
...config,
438+
...config.value,
429439
onStarted: Effect.fn("desktop.backendManager.onStarted")(function* (pid) {
430440
yield* updateActiveRun(runId, (run) => ({
431441
...run,
432442
pid: Option.some(pid),
433443
}));
434444
yield* backendOutputLog.writeSessionBoundary({
435445
phase: "START",
436-
details: `pid=${pid} port=${config.bootstrap.port} cwd=${config.cwd}`,
446+
details: `pid=${pid} port=${config.value.bootstrap.port} cwd=${config.value.cwd}`,
437447
});
438448
}),
439449
onReady: Effect.fn("desktop.backendManager.onReady")(function* () {

apps/desktop/src/settings/DesktopAppSettings.ts

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@ import {
66
} from "@t3tools/contracts";
77
import { fromLenientJson } from "@t3tools/shared/schemaJson";
88
import * as Context from "effect/Context";
9+
import * as Crypto from "effect/Crypto";
910
import * as Data from "effect/Data";
1011
import * as Effect from "effect/Effect";
1112
import * as FileSystem from "effect/FileSystem";
1213
import * as Layer from "effect/Layer";
1314
import * as Option from "effect/Option";
1415
import * as Path from "effect/Path";
1516
import * as PlatformError from "effect/PlatformError";
16-
import * as Random from "effect/Random";
1717
import * as Schema from "effect/Schema";
1818
import * as SynchronizedRef from "effect/SynchronizedRef";
1919

@@ -222,10 +222,10 @@ const writeSettings = Effect.fn("desktop.settings.writeSettings")(function* (inp
222222
readonly settingsPath: string;
223223
readonly settings: DesktopSettings;
224224
readonly defaultSettings: DesktopSettings;
225+
readonly suffix: string;
225226
}): Effect.fn.Return<void, PlatformError.PlatformError | Schema.SchemaError> {
226227
const directory = input.path.dirname(input.settingsPath);
227-
const suffix = (yield* Random.nextUUIDv4).replace(/-/g, "");
228-
const tempPath = `${input.settingsPath}.${process.pid}.${suffix}.tmp`;
228+
const tempPath = `${input.settingsPath}.${process.pid}.${input.suffix}.tmp`;
229229
const encoded = yield* encodeDesktopSettingsJson(
230230
toDesktopSettingsDocument(input.settings, input.defaultSettings),
231231
);
@@ -240,6 +240,7 @@ export const layer = Layer.effect(
240240
const environment = yield* DesktopEnvironment.DesktopEnvironment;
241241
const fileSystem = yield* FileSystem.FileSystem;
242242
const path = yield* Path.Path;
243+
const crypto = yield* Crypto.Crypto;
243244
const settingsRef = yield* SynchronizedRef.make(environment.defaultDesktopSettings);
244245

245246
const persist = (
@@ -251,13 +252,18 @@ export const layer = Layer.effect(
251252
return Effect.succeed([settingsChange(settings, false), settings] as const);
252253
}
253254

254-
return writeSettings({
255-
fileSystem,
256-
path,
257-
settingsPath: environment.desktopSettingsPath,
258-
settings: nextSettings,
259-
defaultSettings: environment.defaultDesktopSettings,
260-
}).pipe(
255+
return crypto.randomUUIDv4.pipe(
256+
Effect.map((uuid) => uuid.replace(/-/g, "")),
257+
Effect.flatMap((suffix) =>
258+
writeSettings({
259+
fileSystem,
260+
path,
261+
settingsPath: environment.desktopSettingsPath,
262+
settings: nextSettings,
263+
defaultSettings: environment.defaultDesktopSettings,
264+
suffix,
265+
}),
266+
),
261267
Effect.mapError((cause) => new DesktopSettingsWriteError({ cause })),
262268
Effect.as([settingsChange(nextSettings, true), nextSettings] as const),
263269
);

apps/desktop/src/settings/DesktopClientSettings.ts

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import { ClientSettingsSchema, type ClientSettings } from "@t3tools/contracts";
22
import { fromLenientJson } from "@t3tools/shared/schemaJson";
33
import * as Context from "effect/Context";
4+
import * as Crypto from "effect/Crypto";
45
import * as Data from "effect/Data";
56
import * as Effect from "effect/Effect";
67
import * as FileSystem from "effect/FileSystem";
78
import * as Layer from "effect/Layer";
89
import * as Option from "effect/Option";
910
import * as Path from "effect/Path";
1011
import * as PlatformError from "effect/PlatformError";
11-
import * as Random from "effect/Random";
1212
import * as Schema from "effect/Schema";
1313
import * as Ref from "effect/Ref";
1414

@@ -74,10 +74,10 @@ const writeClientSettings = Effect.fnUntraced(function* (input: {
7474
readonly path: Path.Path;
7575
readonly settingsPath: string;
7676
readonly settings: ClientSettings;
77+
readonly suffix: string;
7778
}): Effect.fn.Return<void, PlatformError.PlatformError | Schema.SchemaError> {
7879
const directory = input.path.dirname(input.settingsPath);
79-
const suffix = (yield* Random.nextUUIDv4).replace(/-/g, "");
80-
const tempPath = `${input.settingsPath}.${process.pid}.${suffix}.tmp`;
80+
const tempPath = `${input.settingsPath}.${process.pid}.${input.suffix}.tmp`;
8181
const encoded = yield* encodeClientSettingsJson(input.settings);
8282
yield* input.fileSystem.makeDirectory(directory, { recursive: true });
8383
yield* input.fileSystem.writeFileString(tempPath, `${encoded}\n`);
@@ -90,18 +90,24 @@ export const layer = Layer.effect(
9090
const environment = yield* DesktopEnvironment.DesktopEnvironment;
9191
const fileSystem = yield* FileSystem.FileSystem;
9292
const path = yield* Path.Path;
93+
const crypto = yield* Crypto.Crypto;
9394

9495
return DesktopClientSettings.of({
9596
get: readClientSettings(fileSystem, environment.clientSettingsPath).pipe(
9697
Effect.withSpan("desktop.clientSettings.get"),
9798
),
9899
set: (settings) =>
99-
writeClientSettings({
100-
fileSystem,
101-
path,
102-
settingsPath: environment.clientSettingsPath,
103-
settings,
104-
}).pipe(
100+
crypto.randomUUIDv4.pipe(
101+
Effect.map((uuid) => uuid.replace(/-/g, "")),
102+
Effect.flatMap((suffix) =>
103+
writeClientSettings({
104+
fileSystem,
105+
path,
106+
settingsPath: environment.clientSettingsPath,
107+
settings,
108+
suffix,
109+
}),
110+
),
105111
Effect.mapError((cause) => new DesktopClientSettingsWriteError({ cause })),
106112
Effect.withSpan("desktop.clientSettings.set"),
107113
),

apps/desktop/src/settings/DesktopSavedEnvironments.ts

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { EnvironmentId, type PersistedSavedEnvironmentRecord } from "@t3tools/contracts";
22
import { fromLenientJson } from "@t3tools/shared/schemaJson";
33
import * as Context from "effect/Context";
4+
import * as Crypto from "effect/Crypto";
45
import * as Data from "effect/Data";
56
import * as Effect from "effect/Effect";
67
import * as Encoding from "effect/Encoding";
@@ -9,7 +10,6 @@ import * as Layer from "effect/Layer";
910
import * as Option from "effect/Option";
1011
import * as Path from "effect/Path";
1112
import * as PlatformError from "effect/PlatformError";
12-
import * as Random from "effect/Random";
1313
import * as Schema from "effect/Schema";
1414
import * as Ref from "effect/Ref";
1515

@@ -200,10 +200,10 @@ const writeRegistryDocument = Effect.fn("desktop.savedEnvironments.writeRegistry
200200
readonly path: Path.Path;
201201
readonly registryPath: string;
202202
readonly document: SavedEnvironmentRegistryDocument;
203+
readonly suffix: string;
203204
}): Effect.fn.Return<void, PlatformError.PlatformError | Schema.SchemaError> {
204205
const directory = input.path.dirname(input.registryPath);
205-
const suffix = (yield* Random.nextUUIDv4).replace(/-/g, "");
206-
const tempPath = `${input.registryPath}.${process.pid}.${suffix}.tmp`;
206+
const tempPath = `${input.registryPath}.${process.pid}.${input.suffix}.tmp`;
207207
const encoded = yield* encodeSavedEnvironmentRegistryDocumentJson(input.document);
208208
yield* input.fileSystem.makeDirectory(directory, { recursive: true });
209209
yield* input.fileSystem.writeFileString(tempPath, `${encoded}\n`);
@@ -247,14 +247,22 @@ export const layer = Layer.effect(
247247
const fileSystem = yield* FileSystem.FileSystem;
248248
const path = yield* Path.Path;
249249
const safeStorage = yield* ElectronSafeStorage.ElectronSafeStorage;
250+
const crypto = yield* Crypto.Crypto;
250251

251252
const writeDocument = (document: SavedEnvironmentRegistryDocument) =>
252-
writeRegistryDocument({
253-
fileSystem,
254-
path,
255-
registryPath: environment.savedEnvironmentRegistryPath,
256-
document,
257-
}).pipe(Effect.mapError((cause) => new DesktopSavedEnvironmentsWriteError({ cause })));
253+
crypto.randomUUIDv4.pipe(
254+
Effect.map((uuid) => uuid.replace(/-/g, "")),
255+
Effect.flatMap((suffix) =>
256+
writeRegistryDocument({
257+
fileSystem,
258+
path,
259+
registryPath: environment.savedEnvironmentRegistryPath,
260+
document,
261+
suffix,
262+
}),
263+
),
264+
Effect.mapError((cause) => new DesktopSavedEnvironmentsWriteError({ cause })),
265+
);
258266

259267
return DesktopSavedEnvironments.of({
260268
getRegistry: readRegistryDocument(fileSystem, environment.savedEnvironmentRegistryPath).pipe(

apps/desktop/src/ssh/DesktopSshPasswordPrompts.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { assert, describe, it } from "@effect/vitest";
2+
import * as NodeServices from "@effect/platform-node/NodeServices";
23
import * as Duration from "effect/Duration";
34
import * as Effect from "effect/Effect";
45
import * as Fiber from "effect/Fiber";
@@ -86,6 +87,7 @@ function makeElectronWindowLayer(window: ReturnType<typeof makeTestWindow>["wind
8687
function makeLayer(window: ReturnType<typeof makeTestWindow>["window"]) {
8788
return DesktopSshPasswordPrompts.layer({ passwordPromptTimeoutMs: 1_000 }).pipe(
8889
Layer.provide(makeElectronWindowLayer(window)),
90+
Layer.provide(NodeServices.layer),
8991
Layer.provideMerge(TestClock.layer()),
9092
);
9193
}

apps/desktop/src/ssh/DesktopSshPasswordPrompts.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@ import type { DesktopSshPasswordPromptRequest } from "@t3tools/contracts";
22
import { DesktopSshPasswordPromptResolutionInputSchema } from "@t3tools/contracts";
33
import type { SshPasswordRequest } from "@t3tools/ssh/auth";
44
import * as Context from "effect/Context";
5+
import * as Crypto from "effect/Crypto";
56
import * as Data from "effect/Data";
67
import * as DateTime from "effect/DateTime";
78
import * as Deferred from "effect/Deferred";
89
import * as Duration from "effect/Duration";
910
import * as Effect from "effect/Effect";
1011
import * as Layer from "effect/Layer";
1112
import * as Option from "effect/Option";
12-
import * as Random from "effect/Random";
1313
import * as Ref from "effect/Ref";
1414

1515
import * as IpcChannels from "../ipc/channels.ts";
@@ -163,6 +163,7 @@ const failPending = (
163163

164164
const make = Effect.fn("desktop.sshPasswordPrompts.make")(function* (options: LayerOptions = {}) {
165165
const electronWindow = yield* ElectronWindow.ElectronWindow;
166+
const crypto = yield* Crypto.Crypto;
166167
const pendingRef = yield* Ref.make(new Map<string, PendingSshPasswordPrompt>());
167168
const passwordPromptTimeoutMs =
168169
options.passwordPromptTimeoutMs ?? DEFAULT_SSH_PASSWORD_PROMPT_TIMEOUT_MS;
@@ -230,7 +231,11 @@ const make = Effect.fn("desktop.sshPasswordPrompts.make")(function* (options: La
230231
});
231232
}
232233

233-
const requestId = yield* Random.nextUUIDv4;
234+
const requestId = yield* crypto.randomUUIDv4.pipe(
235+
Effect.mapError(
236+
() => new DesktopSshPromptUnavailableError({ reason: "Secure randomness is unavailable." }),
237+
),
238+
);
234239
const now = yield* DateTime.now;
235240
const expiresAt = DateTime.formatIso(
236241
DateTime.add(now, { milliseconds: passwordPromptTimeoutMs }),

0 commit comments

Comments
 (0)