squashed
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
import fs from "fs/promises"
|
||||
import { Flock } from "../../src/util/flock"
|
||||
|
||||
type Msg = {
|
||||
key: string
|
||||
dir: string
|
||||
staleMs?: number
|
||||
timeoutMs?: number
|
||||
baseDelayMs?: number
|
||||
maxDelayMs?: number
|
||||
holdMs?: number
|
||||
ready?: string
|
||||
active?: string
|
||||
done?: string
|
||||
}
|
||||
|
||||
function sleep(ms: number) {
|
||||
return new Promise<void>((resolve) => {
|
||||
setTimeout(resolve, ms)
|
||||
})
|
||||
}
|
||||
|
||||
function input() {
|
||||
const raw = process.argv[2]
|
||||
if (!raw) {
|
||||
throw new Error("Missing flock worker input")
|
||||
}
|
||||
|
||||
return JSON.parse(raw) as Msg
|
||||
}
|
||||
|
||||
async function job(input: Msg) {
|
||||
if (input.ready) {
|
||||
await fs.writeFile(input.ready, String(process.pid))
|
||||
}
|
||||
|
||||
if (input.active) {
|
||||
await fs.writeFile(input.active, String(process.pid), { flag: "wx" })
|
||||
}
|
||||
|
||||
try {
|
||||
if (input.holdMs && input.holdMs > 0) {
|
||||
await sleep(input.holdMs)
|
||||
}
|
||||
|
||||
if (input.done) {
|
||||
await fs.appendFile(input.done, "1\n")
|
||||
}
|
||||
} finally {
|
||||
if (input.active) {
|
||||
await fs.rm(input.active, { force: true })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const msg = input()
|
||||
|
||||
await Flock.withLock(msg.key, () => job(msg), {
|
||||
dir: msg.dir,
|
||||
staleMs: msg.staleMs,
|
||||
timeoutMs: msg.timeoutMs,
|
||||
baseDelayMs: msg.baseDelayMs,
|
||||
maxDelayMs: msg.maxDelayMs,
|
||||
})
|
||||
}
|
||||
|
||||
await main().catch((err) => {
|
||||
const text = err instanceof Error ? (err.stack ?? err.message) : String(err)
|
||||
process.stderr.write(text)
|
||||
process.exit(1)
|
||||
})
|
||||
@@ -0,0 +1,99 @@
|
||||
import path from "path"
|
||||
import { mkdir } from "fs/promises"
|
||||
|
||||
import { createPlugTask, type PlugCtx, type PlugDeps } from "../../src/cli/cmd/plug"
|
||||
import { Filesystem } from "../../src/util/filesystem"
|
||||
|
||||
type Msg = {
|
||||
dir: string
|
||||
target: string
|
||||
mod: string
|
||||
global?: boolean
|
||||
force?: boolean
|
||||
globalDir?: string
|
||||
vcs?: string
|
||||
worktree?: string
|
||||
directory?: string
|
||||
holdMs?: number
|
||||
}
|
||||
|
||||
function sleep(ms: number) {
|
||||
return new Promise<void>((resolve) => {
|
||||
setTimeout(resolve, ms)
|
||||
})
|
||||
}
|
||||
|
||||
function input() {
|
||||
const raw = process.argv[2]
|
||||
if (!raw) {
|
||||
throw new Error("Missing plug worker input")
|
||||
}
|
||||
|
||||
const msg = JSON.parse(raw) as Partial<Msg>
|
||||
if (!msg.dir || !msg.target || !msg.mod) {
|
||||
throw new Error("Invalid plug worker input")
|
||||
}
|
||||
|
||||
return msg as Msg
|
||||
}
|
||||
|
||||
function deps(msg: Msg): PlugDeps {
|
||||
return {
|
||||
spinner: () => ({
|
||||
start() {},
|
||||
stop() {},
|
||||
}),
|
||||
log: {
|
||||
error() {},
|
||||
info() {},
|
||||
success() {},
|
||||
},
|
||||
mkdir: async (dir, opts) => {
|
||||
await mkdir(dir, opts)
|
||||
},
|
||||
resolve: async () => msg.target,
|
||||
stat: (file) => Filesystem.stat(file),
|
||||
readJson: (file) => Filesystem.readJson(file),
|
||||
readText: (file) => Filesystem.readText(file),
|
||||
write: async (file, text) => {
|
||||
if (msg.holdMs && msg.holdMs > 0) {
|
||||
await sleep(msg.holdMs)
|
||||
}
|
||||
await Filesystem.write(file, text)
|
||||
},
|
||||
exists: (file) => Filesystem.exists(file),
|
||||
files: (dir, name) => [path.join(dir, `${name}.jsonc`), path.join(dir, `${name}.json`)],
|
||||
global: msg.globalDir ?? path.join(msg.dir, ".global"),
|
||||
}
|
||||
}
|
||||
|
||||
function ctx(msg: Msg): PlugCtx {
|
||||
return {
|
||||
vcs: msg.vcs ?? "git",
|
||||
worktree: msg.worktree ?? msg.dir,
|
||||
directory: msg.directory ?? msg.dir,
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const msg = input()
|
||||
const run = createPlugTask(
|
||||
{
|
||||
mod: msg.mod,
|
||||
global: msg.global,
|
||||
force: msg.force,
|
||||
},
|
||||
deps(msg),
|
||||
)
|
||||
|
||||
const ok = await run(ctx(msg))
|
||||
if (!ok) {
|
||||
throw new Error("Plug task failed")
|
||||
}
|
||||
}
|
||||
|
||||
await main().catch((err) => {
|
||||
const text = err instanceof Error ? (err.stack ?? err.message) : String(err)
|
||||
process.stderr.write(text)
|
||||
process.exit(1)
|
||||
})
|
||||
@@ -0,0 +1,24 @@
|
||||
type Msg = {
|
||||
file: string
|
||||
spec: string
|
||||
target: string
|
||||
}
|
||||
|
||||
const raw = process.argv[2]
|
||||
if (!raw) throw new Error("Missing worker payload")
|
||||
|
||||
const value = JSON.parse(raw)
|
||||
if (!value || typeof value !== "object") {
|
||||
throw new Error("Invalid worker payload")
|
||||
}
|
||||
|
||||
const msg = Object.fromEntries(Object.entries(value))
|
||||
if (typeof msg.file !== "string" || typeof msg.spec !== "string" || typeof msg.target !== "string") {
|
||||
throw new Error("Invalid worker payload")
|
||||
}
|
||||
|
||||
process.env.OPENCODE_PLUGIN_META_FILE = msg.file
|
||||
|
||||
const { PluginMeta } = await import("../../src/plugin/meta")
|
||||
|
||||
await PluginMeta.touch(msg.spec, msg.target)
|
||||
@@ -0,0 +1,190 @@
|
||||
import { createOpencodeClient } from "@opencode-ai/sdk/v2"
|
||||
import type { CliRenderer } from "@opentui/core"
|
||||
import { createPluginKeybind } from "../../src/cli/cmd/tui/context/plugin-keybinds"
|
||||
import type { HostPluginApi } from "../../src/cli/cmd/tui/plugin/slots"
|
||||
|
||||
type Count = {
|
||||
event_add: number
|
||||
event_drop: number
|
||||
route_add: number
|
||||
route_drop: number
|
||||
command_add: number
|
||||
command_drop: number
|
||||
}
|
||||
|
||||
type Opts = {
|
||||
client?: HostPluginApi["client"]
|
||||
renderer?: HostPluginApi["renderer"]
|
||||
count?: Count
|
||||
keybind?: Partial<HostPluginApi["keybind"]>
|
||||
tuiConfig?: HostPluginApi["tuiConfig"]
|
||||
state?: {
|
||||
session?: Partial<HostPluginApi["state"]["session"]>
|
||||
lsp?: HostPluginApi["state"]["lsp"]
|
||||
mcp?: HostPluginApi["state"]["mcp"]
|
||||
}
|
||||
theme?: {
|
||||
selected?: string
|
||||
has?: HostPluginApi["theme"]["has"]
|
||||
set?: HostPluginApi["theme"]["set"]
|
||||
install?: HostPluginApi["theme"]["install"]
|
||||
mode?: HostPluginApi["theme"]["mode"]
|
||||
ready?: boolean
|
||||
current?: HostPluginApi["theme"]["current"]
|
||||
}
|
||||
}
|
||||
|
||||
export function createTuiPluginApi(opts: Opts = {}): HostPluginApi {
|
||||
const kv: Record<string, unknown> = {}
|
||||
const count = opts.count
|
||||
let depth = 0
|
||||
let size: "medium" | "large" = "medium"
|
||||
const has = opts.theme?.has ?? (() => false)
|
||||
let selected = opts.theme?.selected ?? "opencode"
|
||||
const key = {
|
||||
match: opts.keybind?.match ?? (() => false),
|
||||
print: opts.keybind?.print ?? ((name: string) => name),
|
||||
}
|
||||
const set =
|
||||
opts.theme?.set ??
|
||||
((name: string) => {
|
||||
if (!has(name)) return false
|
||||
selected = name
|
||||
return true
|
||||
})
|
||||
const renderer: CliRenderer = opts.renderer ?? {
|
||||
...Object.create(null),
|
||||
once(this: CliRenderer) {
|
||||
return this
|
||||
},
|
||||
}
|
||||
|
||||
function kvGet(name: string): unknown
|
||||
function kvGet<Value>(name: string, fallback: Value): Value
|
||||
function kvGet(name: string, fallback?: unknown) {
|
||||
const value = kv[name]
|
||||
if (value === undefined) return fallback
|
||||
return value
|
||||
}
|
||||
|
||||
return {
|
||||
client:
|
||||
opts.client ??
|
||||
createOpencodeClient({
|
||||
baseUrl: "http://localhost:4096",
|
||||
}),
|
||||
event: {
|
||||
on: () => {
|
||||
if (count) count.event_add += 1
|
||||
return () => {
|
||||
if (!count) return
|
||||
count.event_drop += 1
|
||||
}
|
||||
},
|
||||
},
|
||||
renderer,
|
||||
command: {
|
||||
register: () => {
|
||||
if (count) count.command_add += 1
|
||||
return () => {
|
||||
if (!count) return
|
||||
count.command_drop += 1
|
||||
}
|
||||
},
|
||||
trigger: () => {},
|
||||
},
|
||||
route: {
|
||||
register: () => {
|
||||
if (count) count.route_add += 1
|
||||
return () => {
|
||||
if (!count) return
|
||||
count.route_drop += 1
|
||||
}
|
||||
},
|
||||
navigate: () => {},
|
||||
get current() {
|
||||
return { name: "home" }
|
||||
},
|
||||
},
|
||||
ui: {
|
||||
Dialog: () => null,
|
||||
DialogAlert: () => null,
|
||||
DialogConfirm: () => null,
|
||||
DialogPrompt: () => null,
|
||||
DialogSelect: () => null,
|
||||
toast: () => {},
|
||||
dialog: {
|
||||
replace: () => {
|
||||
depth = 1
|
||||
},
|
||||
clear: () => {
|
||||
depth = 0
|
||||
size = "medium"
|
||||
},
|
||||
setSize: (next) => {
|
||||
size = next
|
||||
},
|
||||
get size() {
|
||||
return size
|
||||
},
|
||||
get depth() {
|
||||
return depth
|
||||
},
|
||||
get open() {
|
||||
return depth > 0
|
||||
},
|
||||
},
|
||||
},
|
||||
keybind: {
|
||||
...key,
|
||||
create:
|
||||
opts.keybind?.create ??
|
||||
((defaults, over) => {
|
||||
return createPluginKeybind(key, defaults, over)
|
||||
}),
|
||||
},
|
||||
tuiConfig: opts.tuiConfig ?? {},
|
||||
kv: {
|
||||
get: kvGet,
|
||||
set(name, value) {
|
||||
kv[name] = value
|
||||
},
|
||||
get ready() {
|
||||
return true
|
||||
},
|
||||
},
|
||||
state: {
|
||||
session: {
|
||||
diff: opts.state?.session?.diff ?? (() => []),
|
||||
todo: opts.state?.session?.todo ?? (() => []),
|
||||
},
|
||||
lsp: opts.state?.lsp ?? (() => []),
|
||||
mcp: opts.state?.mcp ?? (() => []),
|
||||
},
|
||||
theme: {
|
||||
get current() {
|
||||
return opts.theme?.current ?? {}
|
||||
},
|
||||
get selected() {
|
||||
return selected
|
||||
},
|
||||
has(name) {
|
||||
return has(name)
|
||||
},
|
||||
set(name) {
|
||||
return set(name)
|
||||
},
|
||||
async install(file) {
|
||||
if (opts.theme?.install) return opts.theme.install(file)
|
||||
throw new Error("base theme.install should not run")
|
||||
},
|
||||
mode() {
|
||||
if (opts.theme?.mode) return opts.theme.mode()
|
||||
return "dark"
|
||||
},
|
||||
get ready() {
|
||||
return opts.theme?.ready ?? true
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user