| 243 | } |
| 244 | |
| 245 | function createThemeInstaller( |
| 246 | meta: ConfigPlugin.Origin, |
| 247 | root: string, |
| 248 | spec: string, |
| 249 | plugin: PluginEntry, |
| 250 | ): TuiTheme["install"] { |
| 251 | return async (file) => { |
| 252 | const src = Filesystem.resolveFilePath(root, file) |
| 253 | const name = path.basename(src, path.extname(src)) |
| 254 | const source_dir = path.dirname(meta.source) |
| 255 | const local_dir = |
| 256 | path.basename(source_dir) === ".opencode" |
| 257 | ? path.join(source_dir, "themes") |
| 258 | : path.join(source_dir, ".opencode", "themes") |
| 259 | const dest_dir = meta.scope === "local" ? local_dir : path.join(Global.Path.config, "themes") |
| 260 | const dest = path.join(dest_dir, `${name}.json`) |
| 261 | const stat = await Filesystem.statAsync(src) |
| 262 | const mtime = stat ? Math.floor(typeof stat.mtimeMs === "bigint" ? Number(stat.mtimeMs) : stat.mtimeMs) : undefined |
| 263 | const size = stat ? (typeof stat.size === "bigint" ? Number(stat.size) : stat.size) : undefined |
| 264 | const info = { |
| 265 | src, |
| 266 | dest, |
| 267 | mtime, |
| 268 | size, |
| 269 | } |
| 270 | |
| 271 | await Flock.withLock(`tui-theme:${dest}`, async () => { |
| 272 | const save = async () => { |
| 273 | plugin.themes[name] = info |
| 274 | await PluginMeta.setTheme(plugin.id, name, info).catch(() => {}) |
| 275 | } |
| 276 | |
| 277 | const exists = hasTheme(name) |
| 278 | const prev = plugin.themes[name] |
| 279 | if (exists) { |
| 280 | if (plugin.meta.state !== "updated") { |
| 281 | if (!prev && (await Filesystem.exists(dest))) { |
| 282 | await save() |
| 283 | } |
| 284 | return |
| 285 | } |
| 286 | if (prev?.dest === dest && prev.mtime === mtime && prev.size === size) return |
| 287 | } |
| 288 | |
| 289 | const text = await Filesystem.readText(src).catch(() => undefined) |
| 290 | if (text === undefined) return |
| 291 | |
| 292 | const fail = Symbol() |
| 293 | const data = await Promise.resolve(text) |
| 294 | .then((x) => JSON.parse(x)) |
| 295 | .catch(() => fail) |
| 296 | if (data === fail) return |
| 297 | |
| 298 | if (!isTheme(data)) { |
| 299 | return |
| 300 | } |
| 301 | |
| 302 | if (exists || !(await Filesystem.exists(dest))) { |