134 lines · 5.4 KB · TypeScript
RawDownload
// @effect-diagnostics nodeBuiltinImport:off - CLI integration uses temporary Node paths.import * as NodeFS from "node:fs";import * as NodeOS from "node:os";import * as NodePath from "node:path"; import * as NodeServices from "@effect/platform-node/NodeServices";import { assert, it } from "@effect/vitest";import { EnvironmentInternalError } from "@t3tools/contracts";import * as NetService from "@t3tools/shared/Net";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer";import * as References from "effect/References";import { Command } from "effect/unstable/cli"; import { cli } from "../bin.ts";import * as ServerConfig from "../config.ts";import { ProjectServiceLayerLive } from "../orchestration-v2/runtimeLayer.ts";import { layerConfig as SqlitePersistenceLayerLive } from "../persistence/Layers/Sqlite.ts";import * as ProjectEnrichmentService from "../project/ProjectEnrichmentService.ts";import * as ProjectFaviconResolver from "../project/ProjectFaviconResolver.ts";import * as ProjectService from "../project/ProjectService.ts";import * as RepositoryIdentityResolver from "../project/RepositoryIdentityResolver.ts";import * as T3ProjectFileLoader from "../project/T3ProjectFileLoader.ts";import * as WorkspacePaths from "../workspace/WorkspacePaths.ts";import {  ProjectLiveServerDeclaredResponseError,  ProjectLiveServerRequestError,  projectCommandErrorFromLiveServerRequest,} from "./project.ts"; const CliRuntimeLayer = Layer.mergeAll(NodeServices.layer, NetService.layer);const runCli = (args: ReadonlyArray<string>) =>  Command.runWith(cli, { version: "0.0.0" })(args).pipe(Effect.provide(CliRuntimeLayer)); const makeConfig = (baseDir: string) =>  Effect.gen(function* () {    const derivedPaths = yield* ServerConfig.deriveServerPaths(baseDir, undefined);    return {      logLevel: "Info",      traceMinLevel: "Info",      traceTimingEnabled: true,      traceBatchWindowMs: 200,      traceMaxBytes: 10 * 1024 * 1024,      traceMaxFiles: 10,      otlpTracesUrl: undefined,      otlpMetricsUrl: undefined,      otlpExportIntervalMs: 10_000,      otlpServiceName: "t3-server",      mode: "web",      port: 0,      host: "127.0.0.1",      cwd: process.cwd(),      baseDir,      ...derivedPaths,      staticDir: undefined,      devUrl: undefined,      devAllowedOrigins: [],      noBrowser: true,      startupPresentation: "browser",      desktopBootstrapToken: undefined,      autoBootstrapProjectFromCwd: false,      logWebSocketEvents: false,      tailscaleServeEnabled: false,      tailscaleServePort: 443,    } satisfies ServerConfig.ServerConfig["Service"];  }); const readProjects = (baseDir: string) =>  Effect.gen(function* () {    const config = yield* makeConfig(baseDir);    const layer = ProjectServiceLayerLive.pipe(      Layer.provideMerge(ProjectEnrichmentService.layer),      Layer.provideMerge(RepositoryIdentityResolver.layer),      Layer.provideMerge(ProjectFaviconResolver.layer),      Layer.provideMerge(T3ProjectFileLoader.layer),      Layer.provideMerge(WorkspacePaths.layer),      Layer.provideMerge(SqlitePersistenceLayerLive),      Layer.provideMerge(NodeServices.layer),      Layer.provide(ServerConfig.layer(config)),      Layer.provide(Layer.succeed(References.MinimumLogLevel, config.logLevel)),    );    return yield* ProjectService.ProjectService.pipe(      Effect.flatMap((projects) => projects.snapshot),      Effect.provide(layer),    );  }); it("maps declared server failures into structural project command errors", () => {  const cause = new EnvironmentInternalError({    code: "internal_error",    reason: "access_token_issuance_failed",    traceId: "trace-123",  });   const error = projectCommandErrorFromLiveServerRequest(cause);   assert.instanceOf(error, ProjectLiveServerDeclaredResponseError);  assert.strictEqual(error.operation, "callLiveServer");  assert.strictEqual(error.code, "internal_error");  assert.strictEqual(error.traceId, "trace-123");  assert.strictEqual(error.message, "Server request failed (internal_error, trace trace-123).");  assert.strictEqual(error.cause, cause);}); it("preserves unexpected server failures without deriving the message from them", () => {  const cause = new Error("credential abc123 was rejected");   const error = projectCommandErrorFromLiveServerRequest(cause);   assert.instanceOf(error, ProjectLiveServerRequestError);  assert.strictEqual(error.operation, "callLiveServer");  assert.strictEqual(error.message, "Failed to call the running server.");  assert.strictEqual(error.cause, cause);}); it.effect("adds, renames, and removes projects through the V2 project CLI domain", () =>  Effect.gen(function* () {    const baseDir = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "t3-v2-project-cli-"));    const workspaceRoot = NodeFS.mkdtempSync(      NodePath.join(NodeOS.tmpdir(), "t3-v2-project-workspace-"),    );     yield* runCli(["project", "add", workspaceRoot, "--title", "Alpha", "--base-dir", baseDir]);    const added = (yield* readProjects(baseDir)).projects[0];    assert.equal(added?.title, "Alpha");    assert.equal(added?.workspaceRoot, workspaceRoot);     yield* runCli(["project", "rename", workspaceRoot, "Beta", "--base-dir", baseDir]);    assert.equal((yield* readProjects(baseDir)).projects[0]?.title, "Beta");     yield* runCli(["project", "remove", added?.id ?? "", "--base-dir", baseDir]);    assert.deepEqual((yield* readProjects(baseDir)).projects, []);  }).pipe(Effect.provide(NodeServices.layer)),);