(text: string, configFilepath: string)
| 97 | }) |
| 98 | |
| 99 | const load = (text: string, configFilepath: string): Effect.Effect<Info> => |
| 100 | Effect.gen(function* () { |
| 101 | const expanded = yield* Effect.promise(() => |
| 102 | ConfigVariable.substitute({ text, type: "path", path: configFilepath, missing: "empty" }), |
| 103 | ) |
| 104 | const data = ConfigParse.jsonc(expanded, configFilepath) |
| 105 | if (!isRecord(data)) return {} as Info |
| 106 | // Flatten a nested "tui" key so users who wrote `{ "tui": { ... } }` inside tui.json |
| 107 | // (mirroring the old opencode.json shape) still get their settings applied. |
| 108 | const normalized = dropUnknownKeybinds(normalize(data)) |
| 109 | const parsed = ConfigParse.schema(Info, normalized, configFilepath) |
| 110 | const validated = parsed.attention?.sounds |
| 111 | ? { |
| 112 | ...parsed, |
| 113 | attention: { |
| 114 | ...parsed.attention, |
| 115 | sounds: resolveHostAttentionSoundPaths(path.dirname(configFilepath), parsed.attention.sounds), |
| 116 | }, |
| 117 | } |
| 118 | : parsed |
| 119 | return yield* resolvePlugins(validated, configFilepath) |
| 120 | }).pipe( |
| 121 | // catchCause (not tapErrorCause + orElseSucceed) because JSONC parsing and validation |
| 122 | // can sync-throw — those become defects, which orElseSucceed wouldn't catch. |
| 123 | Effect.catchCause((cause) => |
| 124 | Effect.logWarning("skipping invalid tui config", { |
| 125 | path: configFilepath, |
| 126 | reason: FormatError(Cause.squash(cause)) ?? FormatUnknownError(Cause.squash(cause)), |
| 127 | }).pipe(Effect.as({} as Info)), |
| 128 | ), |
| 129 | ) |
| 130 | |
| 131 | const loadFile = (filepath: string): Effect.Effect<Info> => |
| 132 | Effect.gen(function* () { |
no test coverage detected