actual-tui-plugins
Sebastian Herrlinger 2026-03-09 20:17:54 +01:00
parent 8e1392cad5
commit f727f17c22
5 changed files with 54 additions and 54 deletions

View File

@ -51,25 +51,6 @@ const cfg = (options: Record<string, unknown> | undefined) => {
}
}
const boot = (meta?: TuiPluginInit) => {
if (!meta) {
return {
state: "unknown",
first: false,
updated: false,
count: 0,
source: "n/a",
}
}
return {
state: meta.state,
first: meta.first,
updated: meta.updated,
count: meta.entry.load_count,
source: meta.entry.source,
}
}
const names = (input: ReturnType<typeof cfg>) => {
return {
modal: `${input.route}.modal`,
@ -358,7 +339,7 @@ const Screen = (props: {
input: ReturnType<typeof cfg>
route: ReturnType<typeof names>
keys: Keys
meta: ReturnType<typeof boot>
meta: TuiPluginInit
params?: Record<string, unknown>
}) => {
const dim = useTerminalDimensions()
@ -549,9 +530,9 @@ const Screen = (props: {
<text fg={skin.muted}>plugin state: {props.meta.state}</text>
<text fg={skin.muted}>
first: {props.meta.first ? "yes" : "no"} · updated: {props.meta.updated ? "yes" : "no"} · loads:{" "}
{props.meta.count}
{props.meta.entry.load_count}
</text>
<text fg={skin.muted}>plugin source: {props.meta.source}</text>
<text fg={skin.muted}>plugin source: {props.meta.entry.source}</text>
<text fg={skin.muted}>source: {value.source}</text>
<text fg={skin.muted}>note: {value.note || "(none)"}</text>
<text fg={skin.muted}>selected: {value.selected || "(none)"}</text>
@ -875,24 +856,23 @@ const reg = (api: TuiApi, input: ReturnType<typeof cfg>, keys: Keys) => {
])
}
const tui = async (input: TuiPluginInput, options?: Record<string, unknown>, meta?: TuiPluginInit) => {
const tui = async (input: TuiPluginInput, options: Record<string, unknown> | null, meta: TuiPluginInit) => {
if (options?.enabled === false) return
await input.api.theme.install("./smoke-theme.json")
input.api.theme.set("smoke-theme")
const value = cfg(options)
const value = cfg(options ?? undefined)
const route = names(value)
const keys = input.api.keybind.create(bind, value.keybinds)
const fx = new VignetteEffect(value.vignette)
const info = boot(meta)
input.renderer.addPostProcessFn(fx.apply.bind(fx))
input.api.route.register([
{
name: route.screen,
render: ({ params }) => (
<Screen api={input.api} input={value} route={route} keys={keys} meta={info} params={params} />
<Screen api={input.api} input={value} route={route} keys={keys} meta={meta} params={params} />
),
},
{

View File

@ -129,15 +129,6 @@ function makeInstallFn(meta: TuiConfig.PluginMeta, root: string): TuiTheme["inst
}
}
function initData(meta: { state: PluginMeta.State; entry: PluginMeta.Entry }): TuiPluginInit {
return {
state: meta.state,
first: meta.state === "new",
updated: meta.state === "changed",
entry: meta.entry,
}
}
export namespace TuiPlugin {
const log = Log.create({ service: "tui.plugin" })
let loaded: Promise<void> | undefined
@ -225,6 +216,31 @@ export namespace TuiPlugin {
})
}
const now = Date.now()
const init: TuiPluginInit = meta
? {
state: meta.state,
first: meta.state === "new",
updated: meta.state === "changed",
entry: meta.entry,
}
: {
state: "new",
first: true,
updated: false,
entry: {
name: spec,
source: spec.startsWith("file://") ? "file" : "npm",
spec,
target,
first_time: now,
last_time: now,
time_changed: now,
load_count: 1,
fingerprint: target,
},
}
const root = pluginRoot(spec, target)
const install = makeInstallFn(getPluginMeta(config, item), root)
const mod = await import(target).catch((error) => {
@ -238,7 +254,7 @@ export namespace TuiPlugin {
spec,
mod,
install,
init: meta ? initData(meta) : undefined,
init,
}
}
@ -293,7 +309,7 @@ export namespace TuiPlugin {
}),
},
},
Config.pluginOptions(load.item),
Config.pluginOptions(load.item) ?? null,
load.init,
)
}

View File

@ -28,6 +28,7 @@ mock.module("@opentui/solid/jsx-runtime", () => ({
}))
const { allThemes, addTheme } = await import("../../../src/cli/cmd/tui/context/theme")
const { TuiPlugin } = await import("../../../src/cli/cmd/tui/plugin")
const { PluginMeta } = await import("../../../src/plugin/meta")
async function waitForLog(text: string, timeout = 1000) {
const start = Date.now()
@ -281,6 +282,12 @@ export const object_plugin = {
})
process.env.OPENCODE_PLUGIN_META_FILE = path.join(tmp.path, "plugin-meta.json")
if (!process.env.OPENCODE_PLUGIN_META_FILE) throw new Error("missing meta file")
await PluginMeta.touch(tmp.extra.localSpec, tmp.extra.localSpec)
await PluginMeta.touch(tmp.extra.globalSpec, tmp.extra.globalSpec)
await PluginMeta.persist()
await Bun.sleep(20)
const text = await Bun.file(tmp.extra.globalPluginPath).text()
await Bun.write(tmp.extra.globalPluginPath, `${text}\n`)
const cwd = spyOn(process, "cwd").mockImplementation(() => tmp.path)
let selected = "opencode"
@ -410,21 +417,21 @@ export const object_plugin = {
expect(local.depth_after).toBe(1)
expect(local.open_after).toBe(true)
expect(local.open_clear).toBe(false)
expect(local.init_state).toBe("new")
expect(local.init_first).toBe(true)
expect(local.init_state).toBe("same")
expect(local.init_first).toBe(false)
expect(local.init_updated).toBe(false)
expect(local.init_source).toBe("file")
expect(local.init_load_count).toBe(1)
expect(local.init_load_count).toBe(2)
const global = JSON.parse(await fs.readFile(tmp.extra.globalMarker, "utf8"))
expect(global.has).toBe(true)
expect(global.set_installed).toBe(true)
expect(global.selected).toBe(tmp.extra.globalThemeName)
expect(global.init_state).toBe("new")
expect(global.init_first).toBe(true)
expect(global.init_updated).toBe(false)
expect(global.init_state).toBe("changed")
expect(global.init_first).toBe(false)
expect(global.init_updated).toBe(true)
expect(global.init_source).toBe("file")
expect(global.init_load_count).toBe(1)
expect(global.init_load_count).toBe(2)
const preloaded = JSON.parse(await fs.readFile(tmp.extra.preloadedMarker, "utf8"))
expect(preloaded.before).toBe(true)
@ -470,9 +477,12 @@ export const object_plugin = {
const meta = JSON.parse(await fs.readFile(path.join(tmp.path, "plugin-meta.json"), "utf8")) as Record<
string,
{ spec: string; load_count: number }
{ name: string; load_count: number }
>
expect(Object.keys(meta).length > 0).toBe(true)
const rows = Object.values(meta)
expect(rows.find((item) => item.name === "local-plugin")?.load_count).toBe(2)
expect(rows.find((item) => item.name === "global-plugin")?.load_count).toBe(2)
expect(rows.find((item) => item.name === "preloaded-plugin")?.load_count).toBe(1)
} finally {
cwd.mockRestore()
if (backup === undefined) {

View File

@ -6,12 +6,6 @@ mock.module("@opentui/solid/jsx-runtime", () => ({
jsxs: () => null,
jsxDEV: () => null,
}))
mock.module("@opentui/solid", () => ({
useRenderer: () => ({
getPalette: async () => ({ palette: [] as string[] }),
clearPaletteCache: () => {},
}),
}))
const { DEFAULT_THEMES, allThemes, addTheme, hasTheme } = await import("../../../src/cli/cmd/tui/context/theme")

View File

@ -223,8 +223,8 @@ export type TuiPluginInput<Renderer = CliRenderer, Node = unknown> = {
export type TuiPlugin<Renderer = CliRenderer, Node = unknown> = (
input: TuiPluginInput<Renderer, Node>,
options?: PluginOptions,
init?: TuiPluginInit,
options: PluginOptions | null,
init: TuiPluginInit,
) => Promise<void>
export type TuiPluginModule<Renderer = CliRenderer, Node = unknown> = {