| author | |
| committer | |
| log | 8f6b21a12e8c6509649883da8e36d375e5aa1fdf |
| tree | cb25078bb068c6f579039c82c7fdabf6ef180c0b |
| parent | dc530f7a7e21dac998126024b2b112fc893df404 |
71 files changed, 353 insertions(+), 148 deletions(-)
apps/server/integration/OrchestrationEngineHarness.integration.ts+1-1| ... | ... | @@ -3,7 +3,7 @@ import os from "node:os"; |
| 3 | 3 | import path from "node:path"; |
| 4 | 4 | import { execFileSync } from "node:child_process"; |
| 5 | 5 | |
| 6 | import { NodeServices } from "@effect/platform-node"; | |
| 6 | import * as NodeServices from "@effect/platform-node/NodeServices"; | |
| 7 | 7 | import { |
| 8 | 8 | ApprovalRequestId, |
| 9 | 9 | type OrchestrationEvent, |
apps/server/integration/providerService.integration.test.ts+1-1| ... | ... | @@ -1,6 +1,6 @@ |
| 1 | 1 | import type { ProviderRuntimeEvent, ProviderSessionId } from "@t3tools/contracts"; |
| 2 | 2 | import { ThreadId } from "@t3tools/contracts"; |
| 3 | import { NodeServices } from "@effect/platform-node"; | |
| 3 | import * as NodeServices from "@effect/platform-node/NodeServices"; | |
| 4 | 4 | import { it, assert } from "@effect/vitest"; |
| 5 | 5 | import { Effect, FileSystem, Layer, Path, Queue, Stream } from "effect"; |
| 6 | 6 |
apps/server/package.json+4-4| ... | ... | @@ -18,17 +18,17 @@ |
| 18 | 18 | "test": "vitest run" |
| 19 | 19 | }, |
| 20 | 20 | "dependencies": { |
| 21 | "@effect/platform-node": "4.0.0-beta.10", | |
| 22 | "@effect/sql-sqlite-bun": "4.0.0-beta.10", | |
| 21 | "@effect/platform-node": "catalog:", | |
| 22 | "@effect/sql-sqlite-bun": "catalog:", | |
| 23 | 23 | "@pierre/diffs": "^1.1.0-beta.16", |
| 24 | "effect": "4.0.0-beta.10", | |
| 24 | "effect": "catalog:", | |
| 25 | 25 | "node-pty": "^1.1.0", |
| 26 | 26 | "open": "^10.1.0", |
| 27 | 27 | "ws": "^8.18.0" |
| 28 | 28 | }, |
| 29 | 29 | "devDependencies": { |
| 30 | 30 | "@effect/language-service": "0.75.1", |
| 31 | "@effect/vitest": "4.0.0-beta.10", | |
| 31 | "@effect/vitest": "catalog:", | |
| 32 | 32 | "@t3tools/contracts": "workspace:*", |
| 33 | 33 | "@t3tools/web": "workspace:*", |
| 34 | 34 | "@types/bun": "^1.3.9", |
apps/server/src/checkpointing/Services/CheckpointDiffQuery.ts+1-1| ... | ... | @@ -46,4 +46,4 @@ export interface CheckpointDiffQueryShape { |
| 46 | 46 | export class CheckpointDiffQuery extends ServiceMap.Service< |
| 47 | 47 | CheckpointDiffQuery, |
| 48 | 48 | CheckpointDiffQueryShape |
| 49 | >()("checkpointing/CheckpointDiffQuery") {} | |
| 49 | >()("t3/checkpointing/Services/CheckpointDiffQuery") {} |
apps/server/src/checkpointing/Services/CheckpointStore.ts+1-1| ... | ... | @@ -96,5 +96,5 @@ export interface CheckpointStoreShape { |
| 96 | 96 | * CheckpointStore - Service tag for checkpoint persistence and restore operations. |
| 97 | 97 | */ |
| 98 | 98 | export class CheckpointStore extends ServiceMap.Service<CheckpointStore, CheckpointStoreShape>()( |
| 99 | "checkpointing/CheckpointStore", | |
| 99 | "t3/checkpointing/Services/CheckpointStore", | |
| 100 | 100 | ) {} |
apps/server/src/config.ts+7-7| ... | ... | @@ -35,7 +35,7 @@ export interface ServerConfigShape { |
| 35 | 35 | * ServerConfig - Service tag for server runtime configuration. |
| 36 | 36 | */ |
| 37 | 37 | export class ServerConfig extends ServiceMap.Service<ServerConfig, ServerConfigShape>()( |
| 38 | "server/ServerConfig", | |
| 38 | "t3/config/ServerConfig", | |
| 39 | 39 | ) {} |
| 40 | 40 | |
| 41 | 41 | // Helpers |
| ... | ... | @@ -61,7 +61,7 @@ export interface NetServiceShape { |
| 61 | 61 | * NetService - Service tag for startup networking helpers. |
| 62 | 62 | */ |
| 63 | 63 | export class NetService extends ServiceMap.Service<NetService, NetServiceShape>()( |
| 64 | "server/NetService", | |
| 64 | "t3/config/NetService", | |
| 65 | 65 | ) { |
| 66 | 66 | static readonly layer = Layer.succeed(NetService, { |
| 67 | 67 | findAvailablePort: (preferred) => |
| ... | ... | @@ -102,18 +102,18 @@ export class NetService extends ServiceMap.Service<NetService, NetServiceShape>( |
| 102 | 102 | |
| 103 | 103 | export const resolveStaticDir = Effect.fn(function* () { |
| 104 | 104 | const { join, resolve } = yield* Path.Path; |
| 105 | const { stat } = yield* FileSystem.FileSystem; | |
| 105 | const { exists } = yield* FileSystem.FileSystem; | |
| 106 | 106 | const bundledClient = resolve(join(import.meta.dirname, "client")); |
| 107 | const bundledStat = yield* stat(join(bundledClient, "index.html")).pipe( | |
| 108 | Effect.orElseSucceed(() => undefined), | |
| 107 | const bundledStat = yield* exists(join(bundledClient, "index.html")).pipe( | |
| 108 | Effect.orElseSucceed(() => false), | |
| 109 | 109 | ); |
| 110 | 110 | if (bundledStat) { |
| 111 | 111 | return bundledClient; |
| 112 | 112 | } |
| 113 | 113 | |
| 114 | 114 | const monorepoClient = resolve(join(import.meta.dirname, "../../web/dist")); |
| 115 | const monorepoStat = yield* stat(join(monorepoClient, "index.html")).pipe( | |
| 116 | Effect.orElseSucceed(() => undefined), | |
| 115 | const monorepoStat = yield* exists(join(monorepoClient, "index.html")).pipe( | |
| 116 | Effect.orElseSucceed(() => false), | |
| 117 | 117 | ); |
| 118 | 118 | if (monorepoStat) { |
| 119 | 119 | return monorepoClient; |
apps/server/src/git/Layers/CodexTextGeneration.test.ts+1-1| ... | ... | @@ -1,4 +1,4 @@ |
| 1 | import { NodeServices } from "@effect/platform-node"; | |
| 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; | |
| 2 | 2 | import { it } from "@effect/vitest"; |
| 3 | 3 | import { Effect, FileSystem, Layer, Path } from "effect"; |
| 4 | 4 | import { expect } from "vitest"; |
apps/server/src/git/Layers/GitCore.test.ts+1-1| ... | ... | @@ -1,7 +1,7 @@ |
| 1 | 1 | import { existsSync } from "node:fs"; |
| 2 | 2 | import path from "node:path"; |
| 3 | 3 | |
| 4 | import { NodeServices } from "@effect/platform-node"; | |
| 4 | import * as NodeServices from "@effect/platform-node/NodeServices"; | |
| 5 | 5 | import { it } from "@effect/vitest"; |
| 6 | 6 | import { Effect, FileSystem, Layer, PlatformError, Scope } from "effect"; |
| 7 | 7 | import { describe, expect, vi } from "vitest"; |
apps/server/src/git/Layers/GitManager.test.ts+1-1| ... | ... | @@ -1,7 +1,7 @@ |
| 1 | 1 | import fs from "node:fs"; |
| 2 | 2 | import path from "node:path"; |
| 3 | 3 | |
| 4 | import { NodeServices } from "@effect/platform-node"; | |
| 4 | import * as NodeServices from "@effect/platform-node/NodeServices"; | |
| 5 | 5 | import { it } from "@effect/vitest"; |
| 6 | 6 | import { Effect, FileSystem, Layer, PlatformError, Scope } from "effect"; |
| 7 | 7 | import { expect } from "vitest"; |
apps/server/src/git/Layers/GitService.test.ts+1-1| ... | ... | @@ -1,4 +1,4 @@ |
| 1 | import { NodeServices } from "@effect/platform-node"; | |
| 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; | |
| 2 | 2 | import { it, assert } from "@effect/vitest"; |
| 3 | 3 | import { Effect, Layer } from "effect"; |
| 4 | 4 |
apps/server/src/git/Services/GitCore.ts+3-1| ... | ... | @@ -143,4 +143,6 @@ export interface GitCoreShape { |
| 143 | 143 | /** |
| 144 | 144 | * GitCore - Service tag for low-level Git repository operations. |
| 145 | 145 | */ |
| 146 | export class GitCore extends ServiceMap.Service<GitCore, GitCoreShape>()("git/GitCore") {} | |
| 146 | export class GitCore extends ServiceMap.Service<GitCore, GitCoreShape>()( | |
| 147 | "t3/git/Services/GitCore", | |
| 148 | ) {} |
apps/server/src/git/Services/GitHubCli.ts+3-1| ... | ... | @@ -63,4 +63,6 @@ export interface GitHubCliShape { |
| 63 | 63 | /** |
| 64 | 64 | * GitHubCli - Service tag for GitHub CLI process execution. |
| 65 | 65 | */ |
| 66 | export class GitHubCli extends ServiceMap.Service<GitHubCli, GitHubCliShape>()("git/GitHubCli") {} | |
| 66 | export class GitHubCli extends ServiceMap.Service<GitHubCli, GitHubCliShape>()( | |
| 67 | "t3/git/Services/GitHubCli", | |
| 68 | ) {} |
apps/server/src/git/Services/GitManager.ts+4-2| ... | ... | @@ -23,7 +23,9 @@ export interface GitManagerShape { |
| 23 | 23 | /** |
| 24 | 24 | * Read current repository Git status plus open PR metadata when available. |
| 25 | 25 | */ |
| 26 | readonly status: (input: GitStatusInput) => Effect.Effect<GitStatusResult, GitManagerServiceError>; | |
| 26 | readonly status: ( | |
| 27 | input: GitStatusInput, | |
| 28 | ) => Effect.Effect<GitStatusResult, GitManagerServiceError>; | |
| 27 | 29 | |
| 28 | 30 | /** |
| 29 | 31 | * Run a stacked Git action (`commit`, `commit_push`, `commit_push_pr`). |
| ... | ... | @@ -37,5 +39,5 @@ export interface GitManagerShape { |
| 37 | 39 | * GitManager - Service tag for stacked Git workflow orchestration. |
| 38 | 40 | */ |
| 39 | 41 | export class GitManager extends ServiceMap.Service<GitManager, GitManagerShape>()( |
| 40 | "git/GitManager", | |
| 42 | "t3/git/Services/GitManager", | |
| 41 | 43 | ) {} |
apps/server/src/git/Services/GitService.ts+1-1| ... | ... | @@ -41,5 +41,5 @@ export interface GitServiceShape { |
| 41 | 41 | * GitService - Service for Git command execution. |
| 42 | 42 | */ |
| 43 | 43 | export class GitService extends ServiceMap.Service<GitService, GitServiceShape>()( |
| 44 | "git/GitService", | |
| 44 | "t3/git/Services/GitService", | |
| 45 | 45 | ) {} |
apps/server/src/git/Services/TextGeneration.ts+1-1| ... | ... | @@ -67,5 +67,5 @@ export interface TextGenerationShape { |
| 67 | 67 | * TextGeneration - Service tag for commit and PR text generation. |
| 68 | 68 | */ |
| 69 | 69 | export class TextGeneration extends ServiceMap.Service<TextGeneration, TextGenerationShape>()( |
| 70 | "git/TextGeneration", | |
| 70 | "t3/git/Services/TextGeneration", | |
| 71 | 71 | ) {} |
apps/server/src/keybindings.test.ts+22-5| ... | ... | @@ -1,5 +1,5 @@ |
| 1 | 1 | import { KeybindingCommand, KeybindingRule, KeybindingsConfig } from "@t3tools/contracts"; |
| 2 | import { NodeServices } from "@effect/platform-node"; | |
| 2 | import * as NodeServices from "@effect/platform-node/NodeServices"; | |
| 3 | 3 | import { assert, it } from "@effect/vitest"; |
| 4 | 4 | import { assertFailure } from "@effect/vitest/utils"; |
| 5 | 5 | import { Effect, FileSystem, Layer, Logger, Path, Schema } from "effect"; |
| ... | ... | @@ -178,7 +178,10 @@ it.layer(NodeServices.layer)("keybindings", (it) => { |
| 178 | 178 | return yield* keybindings.loadConfigState; |
| 179 | 179 | }); |
| 180 | 180 | |
| 181 | assert.deepEqual(configState.keybindings, compileResolvedKeybindingsConfig(DEFAULT_KEYBINDINGS)); | |
| 181 | assert.deepEqual( | |
| 182 | configState.keybindings, | |
| 183 | compileResolvedKeybindingsConfig(DEFAULT_KEYBINDINGS), | |
| 184 | ); | |
| 182 | 185 | assert.deepEqual(configState.issues, [ |
| 183 | 186 | { |
| 184 | 187 | kind: "keybindings.malformed-config", |
| ... | ... | @@ -208,10 +211,24 @@ it.layer(NodeServices.layer)("keybindings", (it) => { |
| 208 | 211 | }); |
| 209 | 212 | |
| 210 | 213 | assert.isTrue(configState.keybindings.some((entry) => entry.command === "terminal.toggle")); |
| 211 | assert.isFalse(configState.keybindings.some((entry) => entry.command === "invalid.command")); | |
| 214 | assert.isFalse( | |
| 215 | configState.keybindings.some( | |
| 216 | (entry) => | |
| 217 | // @ts-expect-error - invalid command | |
| 218 | entry.command === "invalid.command", | |
| 219 | ), | |
| 220 | ); | |
| 212 | 221 | assert.deepEqual(configState.issues, [ |
| 213 | { kind: "keybindings.invalid-entry", index: 1, message: configState.issues[0]?.message ?? "" }, | |
| 214 | { kind: "keybindings.invalid-entry", index: 2, message: configState.issues[1]?.message ?? "" }, | |
| 222 | { | |
| 223 | kind: "keybindings.invalid-entry", | |
| 224 | index: 1, | |
| 225 | message: configState.issues[0]?.message ?? "", | |
| 226 | }, | |
| 227 | { | |
| 228 | kind: "keybindings.invalid-entry", | |
| 229 | index: 2, | |
| 230 | message: configState.issues[1]?.message ?? "", | |
| 231 | }, | |
| 215 | 232 | ]); |
| 216 | 233 | }).pipe(Effect.provide(makeKeybindingsLayer())), |
| 217 | 234 | ); |
apps/server/src/keybindings.ts+1-1| ... | ... | @@ -498,7 +498,7 @@ export interface KeybindingsShape { |
| 498 | 498 | * Keybindings - Service tag for keybinding configuration operations. |
| 499 | 499 | */ |
| 500 | 500 | export class Keybindings extends ServiceMap.Service<Keybindings, KeybindingsShape>()( |
| 501 | "server/Keybindings", | |
| 501 | "t3/keybindings", | |
| 502 | 502 | ) {} |
| 503 | 503 | |
| 504 | 504 | const makeKeybindings = Effect.gen(function* () { |
apps/server/src/main.test.ts+1-1| ... | ... | @@ -1,5 +1,5 @@ |
| 1 | 1 | import * as Http from "node:http"; |
| 2 | import { NodeServices } from "@effect/platform-node"; | |
| 2 | import * as NodeServices from "@effect/platform-node/NodeServices"; | |
| 3 | 3 | import { assert, it, vi } from "@effect/vitest"; |
| 4 | 4 | import * as ConfigProvider from "effect/ConfigProvider"; |
| 5 | 5 | import * as Effect from "effect/Effect"; |
apps/server/src/main.ts+3-1| ... | ... | @@ -63,7 +63,9 @@ export interface CliConfigShape { |
| 63 | 63 | /** |
| 64 | 64 | * CliConfig - Service tag for startup CLI/runtime helpers. |
| 65 | 65 | */ |
| 66 | export class CliConfig extends ServiceMap.Service<CliConfig, CliConfigShape>()("server/CliConfig") { | |
| 66 | export class CliConfig extends ServiceMap.Service<CliConfig, CliConfigShape>()( | |
| 67 | "t3/main/CliConfig", | |
| 68 | ) { | |
| 67 | 69 | static readonly layer = Layer.effect( |
| 68 | 70 | CliConfig, |
| 69 | 71 | Effect.gen(function* () { |
apps/server/src/open.ts+1-1| ... | ... | @@ -50,7 +50,7 @@ export interface OpenShape { |
| 50 | 50 | /** |
| 51 | 51 | * Open - Service tag for browser/editor launch operations. |
| 52 | 52 | */ |
| 53 | export class Open extends ServiceMap.Service<Open, OpenShape>()("server/Open") {} | |
| 53 | export class Open extends ServiceMap.Service<Open, OpenShape>()("t3/open") {} | |
| 54 | 54 | |
| 55 | 55 | // ============================== |
| 56 | 56 | // Implementations |
apps/server/src/orchestration/Layers/CheckpointReactor.test.ts+1-1| ... | ... | @@ -14,7 +14,7 @@ import { |
| 14 | 14 | ThreadId, |
| 15 | 15 | TurnId, |
| 16 | 16 | } from "@t3tools/contracts"; |
| 17 | import { NodeServices } from "@effect/platform-node"; | |
| 17 | import * as NodeServices from "@effect/platform-node/NodeServices"; | |
| 18 | 18 | import { Effect, Exit, Layer, ManagedRuntime, PubSub, Scope, Stream } from "effect"; |
| 19 | 19 | import { afterEach, describe, expect, it, vi } from "vitest"; |
| 20 | 20 |
apps/server/src/orchestration/Layers/OrchestrationEngine.ts+2-2| ... | ... | @@ -5,7 +5,7 @@ import type { |
| 5 | 5 | ThreadId, |
| 6 | 6 | } from "@t3tools/contracts"; |
| 7 | 7 | import { OrchestrationCommand } from "@t3tools/contracts"; |
| 8 | import { Deferred, Effect, Layer, Option, PubSub, Queue, Stream } from "effect"; | |
| 8 | import { Deferred, Effect, Layer, Option, PubSub, Queue, Schema, Stream } from "effect"; | |
| 9 | 9 | import * as SqlClient from "effect/unstable/sql/SqlClient"; |
| 10 | 10 | |
| 11 | 11 | import { toPersistenceSqlError } from "../../persistence/Errors.ts"; |
| ... | ... | @@ -181,7 +181,7 @@ const makeOrchestrationEngine = Effect.gen(function* () { |
| 181 | 181 | ), |
| 182 | 182 | ); |
| 183 | 183 | |
| 184 | if (error instanceof OrchestrationCommandInvariantError) { | |
| 184 | if (Schema.is(OrchestrationCommandInvariantError)(error)) { | |
| 185 | 185 | const aggregateRef = commandToAggregateRef(envelope.command); |
| 186 | 186 | yield* commandReceiptRepository |
| 187 | 187 | .upsert({ |
apps/server/src/orchestration/Layers/ProjectionPipeline.test.ts+1-1| ... | ... | @@ -10,7 +10,7 @@ import { |
| 10 | 10 | ThreadId, |
| 11 | 11 | TurnId, |
| 12 | 12 | } from "@t3tools/contracts"; |
| 13 | import { NodeServices } from "@effect/platform-node"; | |
| 13 | import * as NodeServices from "@effect/platform-node/NodeServices"; | |
| 14 | 14 | import { assert, it } from "@effect/vitest"; |
| 15 | 15 | import { Effect, Layer } from "effect"; |
| 16 | 16 | import fs from "node:fs"; |
apps/server/src/orchestration/Services/CheckpointReactor.ts+1-1| ... | ... | @@ -31,4 +31,4 @@ export interface CheckpointReactorShape { |
| 31 | 31 | export class CheckpointReactor extends ServiceMap.Service< |
| 32 | 32 | CheckpointReactor, |
| 33 | 33 | CheckpointReactorShape |
| 34 | >()("orchestration/CheckpointReactor") {} | |
| 34 | >()("t3/orchestration/Services/CheckpointReactor") {} |
apps/server/src/orchestration/Services/OrchestrationEngine.ts+1-1| ... | ... | @@ -77,4 +77,4 @@ export interface OrchestrationEngineShape { |
| 77 | 77 | export class OrchestrationEngineService extends ServiceMap.Service< |
| 78 | 78 | OrchestrationEngineService, |
| 79 | 79 | OrchestrationEngineShape |
| 80 | >()("orchestration/Engine") {} | |
| 80 | >()("t3/orchestration/Services/OrchestrationEngine/OrchestrationEngineService") {} |
apps/server/src/orchestration/Services/OrchestrationReactor.ts+1-1| ... | ... | @@ -28,4 +28,4 @@ export interface OrchestrationReactorShape { |
| 28 | 28 | export class OrchestrationReactor extends ServiceMap.Service< |
| 29 | 29 | OrchestrationReactor, |
| 30 | 30 | OrchestrationReactorShape |
| 31 | >()("orchestration/OrchestrationReactor") {} | |
| 31 | >()("t3/orchestration/Services/OrchestrationReactor") {} |
apps/server/src/orchestration/Services/ProjectionPipeline.ts+1-1| ... | ... | @@ -39,4 +39,4 @@ export interface OrchestrationProjectionPipelineShape { |
| 39 | 39 | export class OrchestrationProjectionPipeline extends ServiceMap.Service< |
| 40 | 40 | OrchestrationProjectionPipeline, |
| 41 | 41 | OrchestrationProjectionPipelineShape |
| 42 | >()("orchestration/ProjectionPipeline") {} | |
| 42 | >()("t3/orchestration/Services/ProjectionPipeline/OrchestrationProjectionPipeline") {} |
apps/server/src/orchestration/Services/ProjectionSnapshotQuery.ts+1-1| ... | ... | @@ -31,4 +31,4 @@ export interface ProjectionSnapshotQueryShape { |
| 31 | 31 | export class ProjectionSnapshotQuery extends ServiceMap.Service< |
| 32 | 32 | ProjectionSnapshotQuery, |
| 33 | 33 | ProjectionSnapshotQueryShape |
| 34 | >()("orchestration/ProjectionSnapshotQuery") {} | |
| 34 | >()("t3/orchestration/Services/ProjectionSnapshotQuery") {} |
apps/server/src/orchestration/Services/ProviderCommandReactor.ts+1-1| ... | ... | @@ -31,4 +31,4 @@ export interface ProviderCommandReactorShape { |
| 31 | 31 | export class ProviderCommandReactor extends ServiceMap.Service< |
| 32 | 32 | ProviderCommandReactor, |
| 33 | 33 | ProviderCommandReactorShape |
| 34 | >()("orchestration/ProviderCommandReactor") {} | |
| 34 | >()("t3/orchestration/Services/ProviderCommandReactor") {} |
apps/server/src/orchestration/Services/ProviderRuntimeIngestion.ts+1-1| ... | ... | @@ -31,4 +31,4 @@ export interface ProviderRuntimeIngestionShape { |
| 31 | 31 | export class ProviderRuntimeIngestionService extends ServiceMap.Service< |
| 32 | 32 | ProviderRuntimeIngestionService, |
| 33 | 33 | ProviderRuntimeIngestionShape |
| 34 | >()("orchestration/ProviderRuntimeIngestion") {} | |
| 34 | >()("t3/orchestration/Services/ProviderRuntimeIngestion/ProviderRuntimeIngestionService") {} |
apps/server/src/persistence/Layers/OrchestrationEventStore.test.ts+2-2| ... | ... | @@ -1,6 +1,6 @@ |
| 1 | 1 | import { CommandId, EventId, ProjectId } from "@t3tools/contracts"; |
| 2 | 2 | import { assert, it } from "@effect/vitest"; |
| 3 | import { Effect, Layer, Stream } from "effect"; | |
| 3 | import { Effect, Layer, Schema, Stream } from "effect"; | |
| 4 | 4 | import * as SqlClient from "effect/unstable/sql/SqlClient"; |
| 5 | 5 | |
| 6 | 6 | import { PersistenceDecodeError } from "../Errors.ts"; |
| ... | ... | @@ -107,7 +107,7 @@ layer("OrchestrationEventStore", (it) => { |
| 107 | 107 | ); |
| 108 | 108 | assert.equal(replayResult._tag, "Failure"); |
| 109 | 109 | if (replayResult._tag === "Failure") { |
| 110 | assert.ok(replayResult.failure instanceof PersistenceDecodeError); | |
| 110 | assert.ok(Schema.is(PersistenceDecodeError)(replayResult.failure)); | |
| 111 | 111 | assert.ok( |
| 112 | 112 | replayResult.failure.operation.includes( |
| 113 | 113 | "OrchestrationEventStore.readFromSequence:decodeRows", |
apps/server/src/persistence/Layers/ProviderSessions.ts+2-6| ... | ... | @@ -51,14 +51,10 @@ function toValidationError( |
| 51 | 51 | }); |
| 52 | 52 | } |
| 53 | 53 | |
| 54 | function decodeInput<A>( | |
| 55 | schema: Schema.Schema<A>, | |
| 56 | input: unknown, | |
| 57 | operation: string, | |
| 58 | ): Effect.Effect<A, ProviderSessionRepositoryValidationError> { | |
| 54 | function decodeInput<S extends Schema.Top>(schema: S, input: unknown, operation: string) { | |
| 59 | 55 | return Schema.decodeUnknownEffect(schema)(input).pipe( |
| 60 | 56 | Effect.mapError((cause) => toValidationError(operation, cause)), |
| 61 | ) as Effect.Effect<A, ProviderSessionRepositoryValidationError>; | |
| 57 | ); | |
| 62 | 58 | } |
| 63 | 59 | |
| 64 | 60 | function toPersistenceError( |
apps/server/src/persistence/NodeSqliteClient.ts+1-1| ... | ... | @@ -32,7 +32,7 @@ export type TypeId = "~local/sqlite-node/SqliteClient"; |
| 32 | 32 | /** |
| 33 | 33 | * SqliteClient - Effect service tag for the sqlite SQL client. |
| 34 | 34 | */ |
| 35 | export const SqliteClient = ServiceMap.Service<Client.SqlClient>("@local/sqlite-node/SqliteClient"); | |
| 35 | export const SqliteClient = ServiceMap.Service<Client.SqlClient>("t3/persistence/NodeSqliteClient"); | |
| 36 | 36 | |
| 37 | 37 | export interface SqliteClientConfig { |
| 38 | 38 | readonly filename: string; |
apps/server/src/persistence/Services/OrchestrationCommandReceipts.ts+1-1| ... | ... | @@ -66,4 +66,4 @@ export interface OrchestrationCommandReceiptRepositoryShape { |
| 66 | 66 | export class OrchestrationCommandReceiptRepository extends ServiceMap.Service< |
| 67 | 67 | OrchestrationCommandReceiptRepository, |
| 68 | 68 | OrchestrationCommandReceiptRepositoryShape |
| 69 | >()("persistence/OrchestrationCommandReceiptRepository") {} | |
| 69 | >()("t3/persistence/Services/OrchestrationCommandReceipts/OrchestrationCommandReceiptRepository") {} |
apps/server/src/persistence/Services/OrchestrationEventStore.ts+1-1| ... | ... | @@ -67,4 +67,4 @@ export interface OrchestrationEventStoreShape { |
| 67 | 67 | export class OrchestrationEventStore extends ServiceMap.Service< |
| 68 | 68 | OrchestrationEventStore, |
| 69 | 69 | OrchestrationEventStoreShape |
| 70 | >()("orchestration/EventStore") {} | |
| 70 | >()("t3/persistence/Services/OrchestrationEventStore") {} |
apps/server/src/persistence/Services/ProjectionCheckpoints.ts+1-1| ... | ... | @@ -90,4 +90,4 @@ export interface ProjectionCheckpointRepositoryShape { |
| 90 | 90 | export class ProjectionCheckpointRepository extends ServiceMap.Service< |
| 91 | 91 | ProjectionCheckpointRepository, |
| 92 | 92 | ProjectionCheckpointRepositoryShape |
| 93 | >()("persistence/ProjectionCheckpointRepository") {} | |
| 93 | >()("t3/persistence/Services/ProjectionCheckpoints/ProjectionCheckpointRepository") {} |
apps/server/src/persistence/Services/ProjectionPendingApprovals.ts+1-1| ... | ... | @@ -88,4 +88,4 @@ export interface ProjectionPendingApprovalRepositoryShape { |
| 88 | 88 | export class ProjectionPendingApprovalRepository extends ServiceMap.Service< |
| 89 | 89 | ProjectionPendingApprovalRepository, |
| 90 | 90 | ProjectionPendingApprovalRepositoryShape |
| 91 | >()("persistence/ProjectionPendingApprovalRepository") {} | |
| 91 | >()("t3/persistence/Services/ProjectionPendingApprovals/ProjectionPendingApprovalRepository") {} |
apps/server/src/persistence/Services/ProjectionProjects.ts+1-1| ... | ... | @@ -76,4 +76,4 @@ export interface ProjectionProjectRepositoryShape { |
| 76 | 76 | export class ProjectionProjectRepository extends ServiceMap.Service< |
| 77 | 77 | ProjectionProjectRepository, |
| 78 | 78 | ProjectionProjectRepositoryShape |
| 79 | >()("persistence/ProjectionProjectRepository") {} | |
| 79 | >()("t3/persistence/Services/ProjectionProjects/ProjectionProjectRepository") {} |
apps/server/src/persistence/Services/ProjectionState.ts+1-1| ... | ... | @@ -61,4 +61,4 @@ export interface ProjectionStateRepositoryShape { |
| 61 | 61 | export class ProjectionStateRepository extends ServiceMap.Service< |
| 62 | 62 | ProjectionStateRepository, |
| 63 | 63 | ProjectionStateRepositoryShape |
| 64 | >()("persistence/ProjectionStateRepository") {} | |
| 64 | >()("t3/persistence/Services/ProjectionState/ProjectionStateRepository") {} |
apps/server/src/persistence/Services/ProjectionThreadActivities.ts+1-1| ... | ... | @@ -77,4 +77,4 @@ export interface ProjectionThreadActivityRepositoryShape { |
| 77 | 77 | export class ProjectionThreadActivityRepository extends ServiceMap.Service< |
| 78 | 78 | ProjectionThreadActivityRepository, |
| 79 | 79 | ProjectionThreadActivityRepositoryShape |
| 80 | >()("persistence/ProjectionThreadActivityRepository") {} | |
| 80 | >()("t3/persistence/Services/ProjectionThreadActivities/ProjectionThreadActivityRepository") {} |
apps/server/src/persistence/Services/ProjectionThreadMessages.ts+1-1| ... | ... | @@ -76,4 +76,4 @@ export interface ProjectionThreadMessageRepositoryShape { |
| 76 | 76 | export class ProjectionThreadMessageRepository extends ServiceMap.Service< |
| 77 | 77 | ProjectionThreadMessageRepository, |
| 78 | 78 | ProjectionThreadMessageRepositoryShape |
| 79 | >()("persistence/ProjectionThreadMessageRepository") {} | |
| 79 | >()("t3/persistence/Services/ProjectionThreadMessages/ProjectionThreadMessageRepository") {} |
apps/server/src/persistence/Services/ProjectionThreadSessions.ts+1-1| ... | ... | @@ -73,4 +73,4 @@ export interface ProjectionThreadSessionRepositoryShape { |
| 73 | 73 | export class ProjectionThreadSessionRepository extends ServiceMap.Service< |
| 74 | 74 | ProjectionThreadSessionRepository, |
| 75 | 75 | ProjectionThreadSessionRepositoryShape |
| 76 | >()("persistence/ProjectionThreadSessionRepository") {} | |
| 76 | >()("t3/persistence/Services/ProjectionThreadSessions/ProjectionThreadSessionRepository") {} |
apps/server/src/persistence/Services/ProjectionThreads.ts+1-1| ... | ... | @@ -82,4 +82,4 @@ export interface ProjectionThreadRepositoryShape { |
| 82 | 82 | export class ProjectionThreadRepository extends ServiceMap.Service< |
| 83 | 83 | ProjectionThreadRepository, |
| 84 | 84 | ProjectionThreadRepositoryShape |
| 85 | >()("persistence/ProjectionThreadRepository") {} | |
| 85 | >()("t3/persistence/Services/ProjectionThreads/ProjectionThreadRepository") {} |
apps/server/src/persistence/Services/ProjectionTurns.ts+1-1| ... | ... | @@ -158,4 +158,4 @@ export interface ProjectionTurnRepositoryShape { |
| 158 | 158 | export class ProjectionTurnRepository extends ServiceMap.Service< |
| 159 | 159 | ProjectionTurnRepository, |
| 160 | 160 | ProjectionTurnRepositoryShape |
| 161 | >()("persistence/ProjectionTurnRepository") {} | |
| 161 | >()("t3/persistence/Services/ProjectionTurns/ProjectionTurnRepository") {} |
apps/server/src/persistence/Services/ProviderSessionRuntime.ts+1-1| ... | ... | @@ -84,4 +84,4 @@ export interface ProviderSessionRuntimeRepositoryShape { |
| 84 | 84 | export class ProviderSessionRuntimeRepository extends ServiceMap.Service< |
| 85 | 85 | ProviderSessionRuntimeRepository, |
| 86 | 86 | ProviderSessionRuntimeRepositoryShape |
| 87 | >()("persistence/ProviderSessionRuntimeRepository") {} | |
| 87 | >()("t3/persistence/Services/ProviderSessionRuntime/ProviderSessionRuntimeRepository") {} |
apps/server/src/persistence/Services/ProviderSessions.ts+1-1| ... | ... | @@ -78,4 +78,4 @@ export interface ProviderSessionRepositoryShape { |
| 78 | 78 | export class ProviderSessionRepository extends ServiceMap.Service< |
| 79 | 79 | ProviderSessionRepository, |
| 80 | 80 | ProviderSessionRepositoryShape |
| 81 | >()("persistence/ProviderSessionRepository") {} | |
| 81 | >()("t3/persistence/Services/ProviderSessions/ProviderSessionRepository") {} |
apps/server/src/provider/Layers/ProviderAdapterRegistry.test.ts+1-1| ... | ... | @@ -8,7 +8,7 @@ import { CodexAdapter, CodexAdapterShape } from "../Services/CodexAdapter.ts"; |
| 8 | 8 | import { ProviderAdapterRegistry } from "../Services/ProviderAdapterRegistry.ts"; |
| 9 | 9 | import { ProviderAdapterRegistryLive } from "./ProviderAdapterRegistry.ts"; |
| 10 | 10 | import { ProviderUnsupportedError } from "../Errors.ts"; |
| 11 | import { NodeServices } from "@effect/platform-node"; | |
| 11 | import * as NodeServices from "@effect/platform-node/NodeServices"; | |
| 12 | 12 | |
| 13 | 13 | const fakeCodexAdapter: CodexAdapterShape = { |
| 14 | 14 | provider: "codex", |
apps/server/src/provider/Layers/ProviderService.test.ts+1-1| ... | ... | @@ -37,7 +37,7 @@ import { ProviderService } from "../Services/ProviderService.ts"; |
| 37 | 37 | import { ProviderSessionDirectory } from "../Services/ProviderSessionDirectory.ts"; |
| 38 | 38 | import { makeProviderServiceLive } from "./ProviderService.ts"; |
| 39 | 39 | import { ProviderSessionDirectoryLive } from "./ProviderSessionDirectory.ts"; |
| 40 | import { NodeServices } from "@effect/platform-node"; | |
| 40 | import * as NodeServices from "@effect/platform-node/NodeServices"; | |
| 41 | 41 | import { ProviderSessionRuntimeRepositoryLive } from "../../persistence/Layers/ProviderSessionRuntime.ts"; |
| 42 | 42 | import { ProviderSessionRuntimeRepository } from "../../persistence/Services/ProviderSessionRuntime.ts"; |
| 43 | 43 | import { |
apps/server/src/provider/Layers/ProviderSessionDirectory.test.ts+1-1| ... | ... | @@ -2,7 +2,7 @@ import fs from "node:fs"; |
| 2 | 2 | import os from "node:os"; |
| 3 | 3 | import path from "node:path"; |
| 4 | 4 | |
| 5 | import { NodeServices } from "@effect/platform-node"; | |
| 5 | import * as NodeServices from "@effect/platform-node/NodeServices"; | |
| 6 | 6 | import { ProviderSessionId, ProviderThreadId, ThreadId } from "@t3tools/contracts"; |
| 7 | 7 | import { it, assert } from "@effect/vitest"; |
| 8 | 8 | import { assertFailure, assertSome } from "@effect/vitest/utils"; |
apps/server/src/provider/Services/CodexAdapter.ts+1-1| ... | ... | @@ -26,5 +26,5 @@ export interface CodexAdapterShape extends ProviderAdapterShape<ProviderAdapterE |
| 26 | 26 | * CodexAdapter - Service tag for Codex provider adapter operations. |
| 27 | 27 | */ |
| 28 | 28 | export class CodexAdapter extends ServiceMap.Service<CodexAdapter, CodexAdapterShape>()( |
| 29 | "provider/CodexAdapter", | |
| 29 | "t3/provider/Services/CodexAdapter", | |
| 30 | 30 | ) {} |
apps/server/src/provider/Services/ProviderAdapterRegistry.ts+1-1| ... | ... | @@ -37,6 +37,6 @@ export interface ProviderAdapterRegistryShape { |
| 37 | 37 | export class ProviderAdapterRegistry extends ServiceMap.Service< |
| 38 | 38 | ProviderAdapterRegistry, |
| 39 | 39 | ProviderAdapterRegistryShape |
| 40 | >()("provider/ProviderAdapterRegistry") {} | |
| 40 | >()("t3/provider/Services/ProviderAdapterRegistry") {} | |
| 41 | 41 | |
| 42 | 42 | // Dummy comment for workflow testing. |
apps/server/src/provider/Services/ProviderService.ts+1-1| ... | ... | @@ -105,5 +105,5 @@ export interface ProviderServiceShape { |
| 105 | 105 | * ProviderService - Service tag for provider orchestration. |
| 106 | 106 | */ |
| 107 | 107 | export class ProviderService extends ServiceMap.Service<ProviderService, ProviderServiceShape>()( |
| 108 | "provider/ProviderService", | |
| 108 | "t3/provider/Services/ProviderService", | |
| 109 | 109 | ) {} |
apps/server/src/provider/Services/ProviderSessionDirectory.ts+1-1| ... | ... | @@ -99,4 +99,4 @@ export interface ProviderSessionDirectoryShape { |
| 99 | 99 | export class ProviderSessionDirectory extends ServiceMap.Service< |
| 100 | 100 | ProviderSessionDirectory, |
| 101 | 101 | ProviderSessionDirectoryShape |
| 102 | >()("provider/ProviderSessionDirectory") {} | |
| 102 | >()("t3/provider/Services/ProviderSessionDirectory") {} |
apps/server/src/serverLayers.ts+1-1| ... | ... | @@ -1,6 +1,6 @@ |
| 1 | 1 | import path from "node:path"; |
| 2 | 2 | |
| 3 | import { NodeServices } from "@effect/platform-node"; | |
| 3 | import * as NodeServices from "@effect/platform-node/NodeServices"; | |
| 4 | 4 | import { Effect, Layer } from "effect"; |
| 5 | 5 | import * as SqlClient from "effect/unstable/sql/SqlClient"; |
| 6 | 6 |
apps/server/src/terminal/Layers/Manager.test.ts+42-4| ... | ... | @@ -9,7 +9,13 @@ import { |
| 9 | 9 | } from "@t3tools/contracts"; |
| 10 | 10 | import { afterEach, describe, expect, it } from "vitest"; |
| 11 | 11 | |
| 12 | import type { PtyAdapterShape, PtyExitEvent, PtyProcess, PtySpawnInput } from "../Services/PTY"; | |
| 12 | import { | |
| 13 | PtySpawnError, | |
| 14 | type PtyAdapterShape, | |
| 15 | type PtyExitEvent, | |
| 16 | type PtyProcess, | |
| 17 | type PtySpawnInput, | |
| 18 | } from "../Services/PTY"; | |
| 13 | 19 | import { TerminalManagerRuntime } from "./Manager"; |
| 14 | 20 | import { Effect, Encoding } from "effect"; |
| 15 | 21 | |
| ... | ... | @@ -69,14 +75,33 @@ class FakePtyAdapter implements PtyAdapterShape { |
| 69 | 75 | readonly spawnFailures: Error[] = []; |
| 70 | 76 | private nextPid = 9000; |
| 71 | 77 | |
| 72 | spawn(input: PtySpawnInput): Effect.Effect<PtyProcess> { | |
| 78 | constructor(private readonly mode: "sync" | "async" = "sync") {} | |
| 79 | ||
| 80 | spawn(input: PtySpawnInput): Effect.Effect<PtyProcess, PtySpawnError> { | |
| 73 | 81 | this.spawnInputs.push(input); |
| 74 | 82 | const failure = this.spawnFailures.shift(); |
| 75 | 83 | if (failure) { |
| 76 | throw failure; | |
| 84 | return Effect.fail( | |
| 85 | new PtySpawnError({ | |
| 86 | adapter: "fake", | |
| 87 | message: "Failed to spawn PTY process", | |
| 88 | cause: failure, | |
| 89 | }), | |
| 90 | ); | |
| 77 | 91 | } |
| 78 | 92 | const process = new FakePtyProcess(this.nextPid++); |
| 79 | 93 | this.processes.push(process); |
| 94 | if (this.mode === "async") { | |
| 95 | return Effect.tryPromise({ | |
| 96 | try: async () => process, | |
| 97 | catch: (cause) => | |
| 98 | new PtySpawnError({ | |
| 99 | adapter: "fake", | |
| 100 | message: "Failed to spawn PTY process", | |
| 101 | cause, | |
| 102 | }), | |
| 103 | }); | |
| 104 | } | |
| 80 | 105 | return Effect.succeed(process); |
| 81 | 106 | } |
| 82 | 107 | } |
| ... | ... | @@ -150,11 +175,12 @@ describe("TerminalManager", () => { |
| 150 | 175 | subprocessPollIntervalMs?: number; |
| 151 | 176 | processKillGraceMs?: number; |
| 152 | 177 | maxRetainedInactiveSessions?: number; |
| 178 | ptyAdapter?: FakePtyAdapter; | |
| 153 | 179 | } = {}, |
| 154 | 180 | ) { |
| 155 | 181 | const logsDir = fs.mkdtempSync(path.join(os.tmpdir(), "t3code-terminal-")); |
| 156 | 182 | tempDirs.push(logsDir); |
| 157 | const ptyAdapter = new FakePtyAdapter(); | |
| 183 | const ptyAdapter = options.ptyAdapter ?? new FakePtyAdapter(); | |
| 158 | 184 | const manager = new TerminalManagerRuntime({ |
| 159 | 185 | logsDir, |
| 160 | 186 | ptyAdapter, |
| ... | ... | @@ -189,6 +215,18 @@ describe("TerminalManager", () => { |
| 189 | 215 | manager.dispose(); |
| 190 | 216 | }); |
| 191 | 217 | |
| 218 | it("supports asynchronous PTY spawn effects", async () => { | |
| 219 | const { manager, ptyAdapter } = makeManager(5, { ptyAdapter: new FakePtyAdapter("async") }); | |
| 220 | ||
| 221 | const snapshot = await manager.open(openInput()); | |
| 222 | ||
| 223 | expect(snapshot.status).toBe("running"); | |
| 224 | expect(ptyAdapter.spawnInputs).toHaveLength(1); | |
| 225 | expect(ptyAdapter.processes).toHaveLength(1); | |
| 226 | ||
| 227 | manager.dispose(); | |
| 228 | }); | |
| 229 | ||
| 192 | 230 | it("forwards write and resize to active pty process", async () => { |
| 193 | 231 | const { manager, ptyAdapter } = makeManager(); |
| 194 | 232 | await manager.open(openInput()); |
apps/server/src/terminal/Layers/Manager.ts+75-20| ... | ... | @@ -117,8 +117,43 @@ function resolveShellCandidates(shellResolver: () => string): ShellCandidate[] { |
| 117 | 117 | } |
| 118 | 118 | |
| 119 | 119 | function isRetryableShellSpawnError(error: unknown): boolean { |
| 120 | if (!(error instanceof Error)) return false; | |
| 121 | const message = error.message.toLowerCase(); | |
| 120 | const queue: unknown[] = [error]; | |
| 121 | const seen = new Set<unknown>(); | |
| 122 | const messages: string[] = []; | |
| 123 | ||
| 124 | while (queue.length > 0) { | |
| 125 | const current = queue.shift(); | |
| 126 | if (!current || seen.has(current)) { | |
| 127 | continue; | |
| 128 | } | |
| 129 | seen.add(current); | |
| 130 | ||
| 131 | if (typeof current === "string") { | |
| 132 | messages.push(current); | |
| 133 | continue; | |
| 134 | } | |
| 135 | ||
| 136 | if (current instanceof Error) { | |
| 137 | messages.push(current.message); | |
| 138 | const cause = (current as { cause?: unknown }).cause; | |
| 139 | if (cause) { | |
| 140 | queue.push(cause); | |
| 141 | } | |
| 142 | continue; | |
| 143 | } | |
| 144 | ||
| 145 | if (typeof current === "object") { | |
| 146 | const value = current as { message?: unknown; cause?: unknown }; | |
| 147 | if (typeof value.message === "string") { | |
| 148 | messages.push(value.message); | |
| 149 | } | |
| 150 | if (value.cause) { | |
| 151 | queue.push(value.cause); | |
| 152 | } | |
| 153 | } | |
| 154 | } | |
| 155 | ||
| 156 | const message = messages.join(" ").toLowerCase(); | |
| 122 | 157 | return ( |
| 123 | 158 | message.includes("posix_spawnp failed") || |
| 124 | 159 | message.includes("enoent") || |
| ... | ... | @@ -354,7 +389,7 @@ export class TerminalManagerRuntime extends EventEmitter<TerminalManagerEvents> |
| 354 | 389 | }; |
| 355 | 390 | this.sessions.set(sessionKey, session); |
| 356 | 391 | this.evictInactiveSessionsIfNeeded(); |
| 357 | this.startSession(session, { ...input, cols, rows }, "started"); | |
| 392 | await this.startSession(session, { ...input, cols, rows }, "started"); | |
| 358 | 393 | return this.snapshot(session); |
| 359 | 394 | } |
| 360 | 395 | |
| ... | ... | @@ -380,7 +415,7 @@ export class TerminalManagerRuntime extends EventEmitter<TerminalManagerEvents> |
| 380 | 415 | } |
| 381 | 416 | |
| 382 | 417 | if (!existing.process) { |
| 383 | this.startSession(existing, { ...input, cols: targetCols, rows: targetRows }, "started"); | |
| 418 | await this.startSession(existing, { ...input, cols: targetCols, rows: targetRows }, "started"); | |
| 384 | 419 | return this.snapshot(existing); |
| 385 | 420 | } |
| 386 | 421 | |
| ... | ... | @@ -477,7 +512,7 @@ export class TerminalManagerRuntime extends EventEmitter<TerminalManagerEvents> |
| 477 | 512 | |
| 478 | 513 | session.history = ""; |
| 479 | 514 | await this.persistHistory(input.threadId, input.terminalId, session.history); |
| 480 | this.startSession(session, { ...input, cols, rows }, "restarted"); | |
| 515 | await this.startSession(session, { ...input, cols, rows }, "restarted"); | |
| 481 | 516 | return this.snapshot(session); |
| 482 | 517 | }); |
| 483 | 518 | } |
| ... | ... | @@ -528,11 +563,11 @@ export class TerminalManagerRuntime extends EventEmitter<TerminalManagerEvents> |
| 528 | 563 | this.persistQueues.clear(); |
| 529 | 564 | } |
| 530 | 565 | |
| 531 | private startSession( | |
| 566 | private async startSession( | |
| 532 | 567 | session: TerminalSessionState, |
| 533 | 568 | input: TerminalStartInput, |
| 534 | 569 | eventType: "started" | "restarted", |
| 535 | ): void { | |
| 570 | ): Promise<void> { | |
| 536 | 571 | this.stopProcess(session); |
| 537 | 572 | |
| 538 | 573 | session.status = "starting"; |
| ... | ... | @@ -551,26 +586,46 @@ export class TerminalManagerRuntime extends EventEmitter<TerminalManagerEvents> |
| 551 | 586 | const terminalEnv = createTerminalSpawnEnv(process.env, session.runtimeEnv); |
| 552 | 587 | let lastSpawnError: unknown = null; |
| 553 | 588 | |
| 554 | for (const candidate of shellCandidates) { | |
| 589 | const spawnWithCandidate = (candidate: ShellCandidate) => | |
| 590 | Effect.runPromise( | |
| 591 | this.ptyAdapter.spawn({ | |
| 592 | shell: candidate.shell, | |
| 593 | ...(candidate.args ? { args: candidate.args } : {}), | |
| 594 | cwd: session.cwd, | |
| 595 | cols: session.cols, | |
| 596 | rows: session.rows, | |
| 597 | env: terminalEnv, | |
| 598 | }), | |
| 599 | ); | |
| 600 | ||
| 601 | const trySpawn = async ( | |
| 602 | candidates: ShellCandidate[], | |
| 603 | index = 0, | |
| 604 | ): Promise<{ process: PtyProcess; shellLabel: string } | null> => { | |
| 605 | if (index >= candidates.length) { | |
| 606 | return null; | |
| 607 | } | |
| 608 | const candidate = candidates[index]; | |
| 609 | if (!candidate) { | |
| 610 | return null; | |
| 611 | } | |
| 612 | ||
| 555 | 613 | try { |
| 556 | ptyProcess = Effect.runSync( | |
| 557 | this.ptyAdapter.spawn({ | |
| 558 | shell: candidate.shell, | |
| 559 | ...(candidate.args ? { args: candidate.args } : {}), | |
| 560 | cwd: session.cwd, | |
| 561 | cols: session.cols, | |
| 562 | rows: session.rows, | |
| 563 | env: terminalEnv, | |
| 564 | }), | |
| 565 | ); | |
| 566 | startedShell = formatShellCandidate(candidate); | |
| 567 | break; | |
| 614 | const process = await spawnWithCandidate(candidate); | |
| 615 | return { process, shellLabel: formatShellCandidate(candidate) }; | |
| 568 | 616 | } catch (error) { |
| 569 | 617 | lastSpawnError = error; |
| 570 | 618 | if (!isRetryableShellSpawnError(error)) { |
| 571 | 619 | throw error; |
| 572 | 620 | } |
| 621 | return trySpawn(candidates, index + 1); | |
| 573 | 622 | } |
| 623 | }; | |
| 624 | ||
| 625 | const spawnResult = await trySpawn(shellCandidates); | |
| 626 | if (spawnResult) { | |
| 627 | ptyProcess = spawnResult.process; | |
| 628 | startedShell = spawnResult.shellLabel; | |
| 574 | 629 | } |
| 575 | 630 | |
| 576 | 631 | if (!ptyProcess) { |
apps/server/src/terminal/Layers/NodePTY.test.ts+1-1| ... | ... | @@ -2,7 +2,7 @@ import { FileSystem, Path, Effect } from "effect"; |
| 2 | 2 | import { assert, it } from "@effect/vitest"; |
| 3 | 3 | |
| 4 | 4 | import { ensureNodePtySpawnHelperExecutable } from "./NodePTY"; |
| 5 | import { NodeServices } from "@effect/platform-node"; | |
| 5 | import * as NodeServices from "@effect/platform-node/NodeServices"; | |
| 6 | 6 | |
| 7 | 7 | it.layer(NodeServices.layer)("ensureNodePtySpawnHelperExecutable", (it) => { |
| 8 | 8 | it.effect("adds executable bits when helper exists but is not executable", () => |
apps/server/src/terminal/Layers/NodePTY.ts+5-4| ... | ... | @@ -37,11 +37,12 @@ export const ensureNodePtySpawnHelperExecutable = Effect.fn(function* (explicitP |
| 37 | 37 | didEnsureSpawnHelperExecutable = true; |
| 38 | 38 | } |
| 39 | 39 | |
| 40 | const stat = yield* fs.stat(helperPath); | |
| 41 | const mode = stat.mode & 0o777; | |
| 42 | if ((mode & 0o111) === 0) { | |
| 43 | yield* fs.chmod(helperPath, mode | 0o111); | |
| 40 | if (!(yield* fs.exists(helperPath))) { | |
| 41 | return; | |
| 44 | 42 | } |
| 43 | ||
| 44 | // Best-effort: avoid FileSystem.stat in packaged mode where some fs metadata can be missing. | |
| 45 | yield* fs.chmod(helperPath, 0o755).pipe(Effect.orElseSucceed(() => undefined)); | |
| 45 | 46 | }); |
| 46 | 47 | |
| 47 | 48 | class NodePtyProcess implements PtyProcess { |
apps/server/src/terminal/Services/Manager.ts+1-1| ... | ... | @@ -113,5 +113,5 @@ export interface TerminalManagerShape { |
| 113 | 113 | * TerminalManager - Service tag for terminal session orchestration. |
| 114 | 114 | */ |
| 115 | 115 | export class TerminalManager extends ServiceMap.Service<TerminalManager, TerminalManagerShape>()( |
| 116 | "terminal/TerminalManager", | |
| 116 | "t3/terminal/Services/Manager/TerminalManager", | |
| 117 | 117 | ) {} |
apps/server/src/terminal/Services/PTY.ts+12-3| ... | ... | @@ -6,7 +6,16 @@ |
| 6 | 6 | * |
| 7 | 7 | * @module PtyAdapter |
| 8 | 8 | */ |
| 9 | import { Effect, ServiceMap } from "effect"; | |
| 9 | import { Effect, Schema, ServiceMap } from "effect"; | |
| 10 | ||
| 11 | /** | |
| 12 | * PtyError - Error type for PTY adapter operations. | |
| 13 | */ | |
| 14 | export class PtySpawnError extends Schema.TaggedErrorClass<PtySpawnError>()("PtySpawnError", { | |
| 15 | adapter: Schema.String, | |
| 16 | message: Schema.String, | |
| 17 | cause: Schema.optional(Schema.Defect), | |
| 18 | }) {} | |
| 10 | 19 | |
| 11 | 20 | export interface PtyExitEvent { |
| 12 | 21 | exitCode: number; |
| ... | ... | @@ -38,12 +47,12 @@ export interface PtyAdapterShape { |
| 38 | 47 | /** |
| 39 | 48 | * Spawn a PTY process for a terminal session. |
| 40 | 49 | */ |
| 41 | spawn(input: PtySpawnInput): Effect.Effect<PtyProcess>; | |
| 50 | spawn(input: PtySpawnInput): Effect.Effect<PtyProcess, PtySpawnError>; | |
| 42 | 51 | } |
| 43 | 52 | |
| 44 | 53 | /** |
| 45 | 54 | * PtyAdapter - Service tag for PTY process integration. |
| 46 | 55 | */ |
| 47 | 56 | export class PtyAdapter extends ServiceMap.Service<PtyAdapter, PtyAdapterShape>()( |
| 48 | "terminal/PtyAdapter", | |
| 57 | "t3/terminal/Services/PTY/PtyAdapter", | |
| 49 | 58 | ) {} |
apps/server/src/wsServer.ts+10-14| ... | ... | @@ -23,7 +23,7 @@ import { |
| 23 | 23 | WsPush, |
| 24 | 24 | WsResponse, |
| 25 | 25 | } from "@t3tools/contracts"; |
| 26 | import { NodeHttpServer } from "@effect/platform-node"; | |
| 26 | import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer"; | |
| 27 | 27 | import { Cause, Effect, Exit, Layer, Ref, Schema, Scope, ServiceMap, Stream, Struct } from "effect"; |
| 28 | 28 | import { WebSocketServer, type WebSocket } from "ws"; |
| 29 | 29 | |
| ... | ... | @@ -79,7 +79,7 @@ export interface ServerShape { |
| 79 | 79 | /** |
| 80 | 80 | * Server - Service tag for HTTP/WebSocket lifecycle management. |
| 81 | 81 | */ |
| 82 | export class Server extends ServiceMap.Service<Server, ServerShape>()("server/Server") {} | |
| 82 | export class Server extends ServiceMap.Service<Server, ServerShape>()("t3/wsServer/Server") {} | |
| 83 | 83 | |
| 84 | 84 | const isServerNotRunningError = (error: unknown): boolean => { |
| 85 | 85 | if (!(error instanceof Error)) return false; |
| ... | ... | @@ -320,22 +320,18 @@ export const createServer = Effect.fn(function* (): Effect.fn.Return< |
| 320 | 320 | yield* Effect.addFinalizer(() => Scope.close(subscriptionsScope, Exit.void)); |
| 321 | 321 | |
| 322 | 322 | yield* Stream.runForEach(orchestrationEngine.streamDomainEvents, (event) => |
| 323 | Effect.gen(function* () { | |
| 324 | yield* broadcastPush({ | |
| 325 | type: "push", | |
| 326 | channel: ORCHESTRATION_WS_CHANNELS.domainEvent, | |
| 327 | data: event, | |
| 328 | }); | |
| 323 | broadcastPush({ | |
| 324 | type: "push", | |
| 325 | channel: ORCHESTRATION_WS_CHANNELS.domainEvent, | |
| 326 | data: event, | |
| 329 | 327 | }), |
| 330 | 328 | ).pipe(Effect.forkIn(subscriptionsScope)); |
| 331 | 329 | |
| 332 | 330 | yield* Stream.runForEach(keybindingsManager.changes, (event) => |
| 333 | Effect.gen(function* () { | |
| 334 | yield* broadcastPush({ | |
| 335 | type: "push", | |
| 336 | channel: WS_CHANNELS.serverConfigUpdated, | |
| 337 | data: event, | |
| 338 | }); | |
| 331 | broadcastPush({ | |
| 332 | type: "push", | |
| 333 | channel: WS_CHANNELS.serverConfigUpdated, | |
| 334 | data: event, | |
| 339 | 335 | }), |
| 340 | 336 | ).pipe(Effect.forkIn(subscriptionsScope)); |
| 341 | 337 |
apps/server/tsconfig.json+8-1| ... | ... | @@ -8,7 +8,14 @@ |
| 8 | 8 | "allowImportingTsExtensions": true, |
| 9 | 9 | "plugins": [ |
| 10 | 10 | { |
| 11 | "name": "@effect/language-service" | |
| 11 | "name": "@effect/language-service", | |
| 12 | "namespaceImportPackages": ["@effect/platform-node"], | |
| 13 | "diagnosticSeverity": { | |
| 14 | "importFromBarrel": "error", | |
| 15 | "anyUnknownInErrorContext": "warning", | |
| 16 | "instanceOfSchema": "warning", | |
| 17 | "deterministicKeys": "warning" | |
| 18 | } | |
| 12 | 19 | } |
| 13 | 20 | ] |
| 14 | 21 | }, |
apps/web/package.json+2-1| ... | ... | @@ -21,7 +21,7 @@ |
| 21 | 21 | "@xterm/addon-fit": "^0.11.0", |
| 22 | 22 | "@xterm/xterm": "^6.0.0", |
| 23 | 23 | "class-variance-authority": "^0.7.1", |
| 24 | "effect": "4.0.0-beta.10", | |
| 24 | "effect": "catalog:", | |
| 25 | 25 | "highlight.js": "^11.11.1", |
| 26 | 26 | "lucide-react": "^0.564.0", |
| 27 | 27 | "react": "^19.0.0", |
| ... | ... | @@ -32,6 +32,7 @@ |
| 32 | 32 | "tailwind-merge": "^3.4.0" |
| 33 | 33 | }, |
| 34 | 34 | "devDependencies": { |
| 35 | "@effect/language-service": "catalog:", | |
| 35 | 36 | "@tailwindcss/vite": "^4.0.0", |
| 36 | 37 | "@tanstack/router-plugin": "^1.161.0", |
| 37 | 38 | "@types/react": "^19.0.0", |
apps/web/src/components/DiffWorkerPoolProvider.tsx+2-2| ... | ... | @@ -1,4 +1,5 @@ |
| 1 | 1 | import { WorkerPoolContextProvider } from "@pierre/diffs/react"; |
| 2 | import DiffsWorker from "@pierre/diffs/worker/worker.js?worker"; | |
| 2 | 3 | import { useMemo, type ReactNode } from "react"; |
| 3 | 4 | |
| 4 | 5 | export function DiffWorkerPoolProvider({ children }: { children?: ReactNode }) { |
| ... | ... | @@ -11,8 +12,7 @@ export function DiffWorkerPoolProvider({ children }: { children?: ReactNode }) { |
| 11 | 12 | return ( |
| 12 | 13 | <WorkerPoolContextProvider |
| 13 | 14 | poolOptions={{ |
| 14 | workerFactory: () => | |
| 15 | new Worker(new URL("../workers/diffs.worker.ts", import.meta.url), { type: "module" }), | |
| 15 | workerFactory: () => new DiffsWorker(), | |
| 16 | 16 | poolSize: workerPoolSize, |
| 17 | 17 | totalASTLRUCacheSize: 240, |
| 18 | 18 | }} |
apps/web/src/workers/diffs.worker.ts deleted-1| ... | ... | @@ -1 +0,0 @@ |
| 1 | import "@pierre/diffs/worker/worker.js"; |
apps/web/tsconfig.json+13-1| ... | ... | @@ -7,7 +7,19 @@ |
| 7 | 7 | "types": ["vite/client"], |
| 8 | 8 | "paths": { |
| 9 | 9 | "~/*": ["./src/*"] |
| 10 | } | |
| 10 | }, | |
| 11 | "plugins": [ | |
| 12 | { | |
| 13 | "name": "@effect/language-service", | |
| 14 | "namespaceImportPackages": ["@effect/platform-node"], | |
| 15 | "diagnosticSeverity": { | |
| 16 | "importFromBarrel": "error", | |
| 17 | "anyUnknownInErrorContext": "warning", | |
| 18 | "instanceOfSchema": "warning", | |
| 19 | "deterministicKeys": "warning" | |
| 20 | } | |
| 21 | } | |
| 22 | ] | |
| 11 | 23 | }, |
| 12 | 24 | "include": ["src", "vite.config.ts"] |
| 13 | 25 | } |
bun.lock+20-12| ... | ... | @@ -35,17 +35,17 @@ |
| 35 | 35 | "t3": "./dist/index.mjs", |
| 36 | 36 | }, |
| 37 | 37 | "dependencies": { |
| 38 | "@effect/platform-node": "4.0.0-beta.10", | |
| 39 | "@effect/sql-sqlite-bun": "4.0.0-beta.10", | |
| 38 | "@effect/platform-node": "catalog:", | |
| 39 | "@effect/sql-sqlite-bun": "catalog:", | |
| 40 | 40 | "@pierre/diffs": "^1.1.0-beta.16", |
| 41 | "effect": "4.0.0-beta.10", | |
| 41 | "effect": "catalog:", | |
| 42 | 42 | "node-pty": "^1.1.0", |
| 43 | 43 | "open": "^10.1.0", |
| 44 | 44 | "ws": "^8.18.0", |
| 45 | 45 | }, |
| 46 | 46 | "devDependencies": { |
| 47 | 47 | "@effect/language-service": "0.75.1", |
| 48 | "@effect/vitest": "4.0.0-beta.10", | |
| 48 | "@effect/vitest": "catalog:", | |
| 49 | 49 | "@t3tools/contracts": "workspace:*", |
| 50 | 50 | "@t3tools/web": "workspace:*", |
| 51 | 51 | "@types/bun": "^1.3.9", |
| ... | ... | @@ -70,7 +70,7 @@ |
| 70 | 70 | "@xterm/addon-fit": "^0.11.0", |
| 71 | 71 | "@xterm/xterm": "^6.0.0", |
| 72 | 72 | "class-variance-authority": "^0.7.1", |
| 73 | "effect": "4.0.0-beta.10", | |
| 73 | "effect": "catalog:", | |
| 74 | 74 | "highlight.js": "^11.11.1", |
| 75 | 75 | "lucide-react": "^0.564.0", |
| 76 | 76 | "react": "^19.0.0", |
| ... | ... | @@ -81,6 +81,7 @@ |
| 81 | 81 | "tailwind-merge": "^3.4.0", |
| 82 | 82 | }, |
| 83 | 83 | "devDependencies": { |
| 84 | "@effect/language-service": "catalog:", | |
| 84 | 85 | "@tailwindcss/vite": "^4.0.0", |
| 85 | 86 | "@tanstack/router-plugin": "^1.161.0", |
| 86 | 87 | "@types/react": "^19.0.0", |
| ... | ... | @@ -96,16 +97,23 @@ |
| 96 | 97 | "name": "@t3tools/contracts", |
| 97 | 98 | "version": "0.0.0", |
| 98 | 99 | "dependencies": { |
| 99 | "effect": "4.0.0-beta.10", | |
| 100 | "effect": "catalog:", | |
| 100 | 101 | }, |
| 101 | 102 | "devDependencies": { |
| 102 | 103 | "@effect/language-service": "0.75.1", |
| 103 | "@effect/vitest": "4.0.0-beta.10", | |
| 104 | "@effect/vitest": "catalog:", | |
| 104 | 105 | "tsdown": "^0.20.3", |
| 105 | 106 | "typescript": "^5.7.3", |
| 106 | 107 | }, |
| 107 | 108 | }, |
| 108 | 109 | }, |
| 110 | "catalog": { | |
| 111 | "@effect/language-service": "0.75.1", | |
| 112 | "@effect/platform-node": "https://pkg.pr.new/Effect-TS/effect-smol/@effect/platform-node@f591738", | |
| 113 | "@effect/sql-sqlite-bun": "https://pkg.pr.new/Effect-TS/effect-smol/@effect/sql-sqlite-bun@f591738", | |
| 114 | "@effect/vitest": "https://pkg.pr.new/Effect-TS/effect-smol/@effect/vitest@f591738", | |
| 115 | "effect": "https://pkg.pr.new/Effect-TS/effect-smol/effect@f591738", | |
| 116 | }, | |
| 109 | 117 | "packages": { |
| 110 | 118 | "@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="], |
| 111 | 119 | |
| ... | ... | @@ -157,13 +165,13 @@ |
| 157 | 165 | |
| 158 | 166 | "@effect/language-service": ["@effect/language-service@0.75.1", "", { "bin": { "effect-language-service": "cli.js" } }, "sha512-g9xD2tAQgRFpYC2YgpZq02VeSL5fBbFJ0B/g1o+14NuNmwtaYJc7SjiLWAA9eyhJHosNrn6h1Ye+Kx6j5mN0AA=="], |
| 159 | 167 | |
| 160 | "@effect/platform-node": ["@effect/platform-node@4.0.0-beta.10", "", { "dependencies": { "@effect/platform-node-shared": "^4.0.0-beta.10", "mime": "^4.1.0", "undici": "^7.20.0" }, "peerDependencies": { "effect": "^4.0.0-beta.10", "ioredis": "^5.7.0" } }, "sha512-ikZ/uyos5okYAHE+wWOwZkHgriFUTY3aY8Kdqbj/ro8xw0z9YXWN4fbyQAZUWkBcieDKS36N+QfHRMPG4fEWRA=="], | |
| 168 | "@effect/platform-node": ["@effect/platform-node@https://pkg.pr.new/Effect-TS/effect-smol/@effect/platform-node@f591738", { "dependencies": { "@effect/platform-node-shared": "https://pkg.pr.new/Effect-TS/effect-smol/@effect/platform-node-shared@f59173885df0eb490dc2e6619ae89467feac0413", "mime": "^4.1.0", "undici": "^7.20.0" }, "peerDependencies": { "effect": "^4.0.0-beta.19", "ioredis": "^5.7.0" } }], | |
| 161 | 169 | |
| 162 | "@effect/platform-node-shared": ["@effect/platform-node-shared@4.0.0-beta.10", "", { "dependencies": { "@types/ws": "^8.18.1", "ws": "^8.19.0" }, "peerDependencies": { "effect": "^4.0.0-beta.10" } }, "sha512-gBSeOG2bfchXFdgdi5oipHKynOzSd7BlzTy5QcjFOoCUTtLP9igqohPB9hTkV3a6seWE2JJqsgsQ79VuLb7fAg=="], | |
| 170 | "@effect/platform-node-shared": ["@effect/platform-node-shared@https://pkg.pr.new/Effect-TS/effect-smol/@effect/platform-node-shared@f59173885df0eb490dc2e6619ae89467feac0413", { "dependencies": { "@types/ws": "^8.18.1", "ws": "^8.19.0" }, "peerDependencies": { "effect": "^4.0.0-beta.19" } }], | |
| 163 | 171 | |
| 164 | "@effect/sql-sqlite-bun": ["@effect/sql-sqlite-bun@4.0.0-beta.10", "", { "peerDependencies": { "effect": "^4.0.0-beta.10" } }, "sha512-LY1jNu2uqGYsJCVjVU73nzcZ+kAYBDsG7O8x5ddmkIXj10RmnIhjYMStWBa0NZOFg4Hsl+CadxtsDy3edaOVeA=="], | |
| 172 | "@effect/sql-sqlite-bun": ["@effect/sql-sqlite-bun@https://pkg.pr.new/Effect-TS/effect-smol/@effect/sql-sqlite-bun@f591738", { "peerDependencies": { "effect": "^4.0.0-beta.19" } }], | |
| 165 | 173 | |
| 166 | "@effect/vitest": ["@effect/vitest@4.0.0-beta.10", "", { "peerDependencies": { "effect": "^4.0.0-beta.10", "vitest": "^3.0.0" } }, "sha512-WmE8vCNw+eDoAq9CwwymrAdIBw1f+8hDP3AUF7jyC7/Cj/kyAYOFwkbtKmDXIFaTlf7G/2fmulEXeCO5W8j0kQ=="], | |
| 174 | "@effect/vitest": ["@effect/vitest@https://pkg.pr.new/Effect-TS/effect-smol/@effect/vitest@f591738", { "peerDependencies": { "effect": "^4.0.0-beta.19", "vitest": "^3.0.0" } }], | |
| 167 | 175 | |
| 168 | 176 | "@electron/get": ["@electron/get@2.0.3", "", { "dependencies": { "debug": "^4.1.1", "env-paths": "^2.2.0", "fs-extra": "^8.1.0", "got": "^11.8.5", "progress": "^2.0.3", "semver": "^6.2.0", "sumchecker": "^3.0.1" }, "optionalDependencies": { "global-agent": "^3.0.0" } }, "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ=="], |
| 169 | 177 | |
| ... | ... | @@ -729,7 +737,7 @@ |
| 729 | 737 | |
| 730 | 738 | "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], |
| 731 | 739 | |
| 732 | "effect": ["effect@4.0.0-beta.10", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.5.3", "find-my-way-ts": "^0.1.6", "ini": "^6.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^1.11.8", "multipasta": "^0.2.7", "toml": "^3.0.0", "uuid": "^13.0.0", "yaml": "^2.8.2" } }, "sha512-UvxWmT0uzWZqNmQODfW7y1zb0LMlH3xtSn/UESpck2Wvv6SogxibYPWKKNcUsRMm5wwprOoW2IwItYLQyZpe8Q=="], | |
| 740 | "effect": ["effect@https://pkg.pr.new/Effect-TS/effect-smol/effect@f591738", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.5.3", "find-my-way-ts": "^0.1.6", "ini": "^6.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^1.11.8", "multipasta": "^0.2.7", "toml": "^3.0.0", "uuid": "^13.0.0", "yaml": "^2.8.2" } }], | |
| 733 | 741 | |
| 734 | 742 | "electron": ["electron@40.6.0", "", { "dependencies": { "@electron/get": "^2.0.0", "@types/node": "^24.9.0", "extract-zip": "^2.0.1" }, "bin": { "electron": "cli.js" } }, "sha512-ett8W+yOFGDuM0vhJMamYSkrbV3LoaffzJd9GfjI96zRAxyrNqUSKqBpf/WGbQCweDxX2pkUCUfrv4wwKpsFZA=="], |
| 735 | 743 |
package.json+13-4| ... | ... | @@ -1,10 +1,19 @@ |
| 1 | 1 | { |
| 2 | 2 | "name": "@t3tools/monorepo", |
| 3 | 3 | "private": true, |
| 4 | "workspaces": [ | |
| 5 | "apps/*", | |
| 6 | "packages/*" | |
| 7 | ], | |
| 4 | "workspaces": { | |
| 5 | "packages": [ | |
| 6 | "apps/*", | |
| 7 | "packages/*" | |
| 8 | ], | |
| 9 | "catalog": { | |
| 10 | "effect": "https://pkg.pr.new/Effect-TS/effect-smol/effect@f591738", | |
| 11 | "@effect/platform-node": "https://pkg.pr.new/Effect-TS/effect-smol/@effect/platform-node@f591738", | |
| 12 | "@effect/sql-sqlite-bun": "https://pkg.pr.new/Effect-TS/effect-smol/@effect/sql-sqlite-bun@f591738", | |
| 13 | "@effect/vitest": "https://pkg.pr.new/Effect-TS/effect-smol/@effect/vitest@f591738", | |
| 14 | "@effect/language-service": "0.75.1" | |
| 15 | } | |
| 16 | }, | |
| 8 | 17 | "scripts": { |
| 9 | 18 | "dev": "bun run scripts/dev-runner.mjs dev", |
| 10 | 19 | "dev:server": "bun run scripts/dev-runner.mjs dev:server", |
packages/contracts/package.json+2-2| ... | ... | @@ -24,11 +24,11 @@ |
| 24 | 24 | "test": "vitest run" |
| 25 | 25 | }, |
| 26 | 26 | "dependencies": { |
| 27 | "effect": "4.0.0-beta.10" | |
| 27 | "effect": "catalog:" | |
| 28 | 28 | }, |
| 29 | 29 | "devDependencies": { |
| 30 | 30 | "@effect/language-service": "0.75.1", |
| 31 | "@effect/vitest": "4.0.0-beta.10", | |
| 31 | "@effect/vitest": "catalog:", | |
| 32 | 32 | "tsdown": "^0.20.3", |
| 33 | 33 | "typescript": "^5.7.3" |
| 34 | 34 | } |
packages/contracts/tsconfig.json+8-1| ... | ... | @@ -3,7 +3,14 @@ |
| 3 | 3 | "compilerOptions": { |
| 4 | 4 | "plugins": [ |
| 5 | 5 | { |
| 6 | "name": "@effect/language-service" | |
| 6 | "name": "@effect/language-service", | |
| 7 | "namespaceImportPackages": ["@effect/platform-node"], | |
| 8 | "diagnosticSeverity": { | |
| 9 | "importFromBarrel": "error", | |
| 10 | "anyUnknownInErrorContext": "warning", | |
| 11 | "instanceOfSchema": "warning", | |
| 12 | "deterministicKeys": "warning" | |
| 13 | } | |
| 7 | 14 | } |
| 8 | 15 | ] |
| 9 | 16 | }, |
scripts/build-desktop-dmg.mjs+43-1| ... | ... | @@ -206,6 +206,42 @@ async function validateBundledClientAssets(clientDir) { |
| 206 | 206 | } |
| 207 | 207 | } |
| 208 | 208 | |
| 209 | function readWorkspaceCatalog(rootPackageJson) { | |
| 210 | const workspaces = rootPackageJson?.workspaces; | |
| 211 | if (!workspaces || typeof workspaces !== "object" || Array.isArray(workspaces)) { | |
| 212 | return {}; | |
| 213 | } | |
| 214 | ||
| 215 | const catalog = workspaces.catalog; | |
| 216 | if (!catalog || typeof catalog !== "object" || Array.isArray(catalog)) { | |
| 217 | return {}; | |
| 218 | } | |
| 219 | ||
| 220 | return catalog; | |
| 221 | } | |
| 222 | ||
| 223 | function resolveCatalogDependencies(dependencies, catalog, dependencySourceLabel) { | |
| 224 | return Object.fromEntries( | |
| 225 | Object.entries(dependencies).map(([dependencyName, spec]) => { | |
| 226 | if (typeof spec !== "string" || !spec.startsWith("catalog:")) { | |
| 227 | return [dependencyName, spec]; | |
| 228 | } | |
| 229 | ||
| 230 | const catalogKey = spec.slice("catalog:".length).trim(); | |
| 231 | const lookupKey = catalogKey.length > 0 ? catalogKey : dependencyName; | |
| 232 | const resolvedSpec = catalog[lookupKey]; | |
| 233 | if (typeof resolvedSpec !== "string" || resolvedSpec.length === 0) { | |
| 234 | fail( | |
| 235 | `Unable to resolve '${spec}' for ${dependencySourceLabel} dependency '${dependencyName}'. ` + | |
| 236 | `Expected key '${lookupKey}' in root workspace catalog.`, | |
| 237 | ); | |
| 238 | } | |
| 239 | ||
| 240 | return [dependencyName, resolvedSpec]; | |
| 241 | }), | |
| 242 | ); | |
| 243 | } | |
| 244 | ||
| 209 | 245 | async function main() { |
| 210 | 246 | const options = parseArgs(process.argv.slice(2)); |
| 211 | 247 | if (options.help) { |
| ... | ... | @@ -217,6 +253,7 @@ async function main() { |
| 217 | 253 | fail("This script only builds macOS .dmg artifacts and must run on macOS."); |
| 218 | 254 | } |
| 219 | 255 | |
| 256 | const rootPkg = await readJson(path.join(repoRoot, "package.json")); | |
| 220 | 257 | const desktopPkg = await readJson(path.join(repoRoot, "apps/desktop/package.json")); |
| 221 | 258 | const serverPkg = await readJson(path.join(repoRoot, "apps/server/package.json")); |
| 222 | 259 | |
| ... | ... | @@ -229,6 +266,11 @@ async function main() { |
| 229 | 266 | if (!dependencies || Object.keys(dependencies).length === 0) { |
| 230 | 267 | fail("Could not resolve production dependencies from apps/server/package.json."); |
| 231 | 268 | } |
| 269 | const resolvedDependencies = resolveCatalogDependencies( | |
| 270 | dependencies, | |
| 271 | readWorkspaceCatalog(rootPkg), | |
| 272 | "apps/server", | |
| 273 | ); | |
| 232 | 274 | |
| 233 | 275 | const appVersion = options.version ?? serverPkg.version ?? "0.1.0"; |
| 234 | 276 | const stageRoot = path.join(os.tmpdir(), "t3code-desktop-dmg-stage"); |
| ... | ... | @@ -291,7 +333,7 @@ async function main() { |
| 291 | 333 | category: "public.app-category.developer-tools", |
| 292 | 334 | }, |
| 293 | 335 | }, |
| 294 | dependencies, | |
| 336 | dependencies: resolvedDependencies, | |
| 295 | 337 | devDependencies: { |
| 296 | 338 | electron: electronVersion, |
| 297 | 339 | }, |