Skip to content

Commit e5483d7

Browse files
committed
refactor(cli): friendly fetch/JSON errors + tighten invariants
- Use Effect.tryPromise + CliError for fetch and response.json so network/parse failures surface as clean one-liners instead of FiberFailure stack traces. - Make the InstanceRef invariant explicit: Effect.die instead of a silent early return when (impossibly) absent. - Reword the legacy-bootstrap comment to describe Effect.ensuring's exit-path coverage directly.
1 parent 6a0ddf8 commit e5483d7

1 file changed

Lines changed: 20 additions & 7 deletions

File tree

packages/opencode/src/cli/cmd/import.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { Session as SDKSession, Message, Part } from "@opencode-ai/sdk/v2"
22
import { Session } from "@/session/session"
33
import { MessageV2 } from "../../session/message-v2"
4-
import { effectCmd } from "../effect-cmd"
4+
import { CliError, effectCmd } from "../effect-cmd"
55
import { Database } from "@/storage/db"
66
import { SessionTable, MessageTable, PartTable } from "../../session/session.sql"
77
import { InstanceRef } from "@/effect/instance-ref"
@@ -88,11 +88,12 @@ export const ImportCommand = effectCmd({
8888
demandOption: true,
8989
}),
9090
handler: Effect.fn("Cli.import")(function* (args) {
91+
// effectCmd always provides InstanceRef via InstanceStore.Service.provide; this is an invariant.
9192
const ctx = yield* InstanceRef
92-
if (!ctx) return
93+
if (!ctx) return yield* Effect.die("InstanceRef not provided")
9394
const store = yield* InstanceStore.Service
94-
// Match legacy bootstrap() finally — dispose runs disposers + emits
95-
// server.instance.disposed even on early-return / error paths.
95+
// Ensure store.dispose runs disposers and emits server.instance.disposed
96+
// on every exit path: success, early return, typed failure, defect, interrupt.
9697
return yield* runImport(args.file, ctx.project.id).pipe(Effect.ensuring(store.dispose(ctx)))
9798
}),
9899
})
@@ -117,11 +118,20 @@ const runImport = Effect.fn("Cli.import.body")(function* (file: string, projectI
117118
const req = yield* Effect.orDie(share.request())
118119
const headers = shouldAttachShareAuthHeaders(file, req.baseUrl) ? req.headers : {}
119120

121+
const tryFetch = (url: string) =>
122+
Effect.tryPromise({
123+
try: () => fetch(url, { headers }),
124+
catch: (e) =>
125+
new CliError({
126+
message: `Failed to fetch share data: ${e instanceof Error ? e.message : String(e)}`,
127+
}),
128+
})
129+
120130
const dataPath = req.api.data(slug)
121-
let response = yield* Effect.promise(() => fetch(`${baseUrl}${dataPath}`, { headers }))
131+
let response = yield* tryFetch(`${baseUrl}${dataPath}`)
122132

123133
if (!response.ok && dataPath !== `/api/share/${slug}/data`) {
124-
response = yield* Effect.promise(() => fetch(`${baseUrl}/api/share/${slug}/data`, { headers }))
134+
response = yield* tryFetch(`${baseUrl}/api/share/${slug}/data`)
125135
}
126136

127137
if (!response.ok) {
@@ -130,7 +140,10 @@ const runImport = Effect.fn("Cli.import.body")(function* (file: string, projectI
130140
return
131141
}
132142

133-
const shareData = (yield* Effect.promise(() => response.json())) as ShareData[]
143+
const shareData = yield* Effect.tryPromise({
144+
try: () => response.json() as Promise<ShareData[]>,
145+
catch: () => new CliError({ message: "Share data was not valid JSON" }),
146+
})
134147
const transformed = transformShareData(shareData)
135148

136149
if (!transformed) {

0 commit comments

Comments
 (0)