mirror of
https://github.com/penpot/penpot.git
synced 2026-07-28 00:48:22 +00:00
Move all development tools from tools/ to scripts/ for consistency. Rename lint/fmt/check-fmt to lint-clj/fmt-clj/check-fmt-clj to clarify they target Clojure specifically. Remove unused scripts (attach-opencode, start-opencode, start-opencode-server) and the backport-commit skill. Update all internal references across .serena/, AGENTS.md, and CONTRIBUTING.md to point to the new script locations. Simplify CONTRIBUTING.md by delegating module-specific fmt/lint instructions to the respective serena memories. AI-assisted-by: deepseek-v4-flash
300 lines
7.5 KiB
JavaScript
300 lines
7.5 KiB
JavaScript
import { tool } from "@opencode-ai/plugin"
|
|
import path from "path"
|
|
import { spawn } from "child_process"
|
|
|
|
const penpotPsqlTool = tool({
|
|
description:
|
|
"Execute a SQL command against the Penpot database. Uses the defaults from scripts/psql.",
|
|
|
|
args: {
|
|
sql: tool.schema
|
|
.string()
|
|
.describe("SQL command to execute"),
|
|
|
|
test: tool.schema
|
|
.boolean()
|
|
.describe("Use the penpot_test database")
|
|
.optional(),
|
|
},
|
|
|
|
async execute(args, context) {
|
|
const host = process.env.PENPOT_DB_HOST || "postgres"
|
|
const user = process.env.PENPOT_DB_USER || "penpot"
|
|
const db = args.test
|
|
? "penpot_test"
|
|
: process.env.PENPOT_DB_NAME || "penpot"
|
|
const password = process.env.PENPOT_DB_PASSWORD || "penpot"
|
|
|
|
const psqlArgs = ["-h", host, "-U", user, "-d", db, "-c", args.sql]
|
|
|
|
return new Promise((resolve) => {
|
|
let stdout = ""
|
|
let stderr = ""
|
|
|
|
const proc = spawn("psql", psqlArgs, {
|
|
cwd: context.worktree,
|
|
env: { ...process.env, PGPASSWORD: password },
|
|
})
|
|
|
|
proc.stdout.on("data", (data) => {
|
|
stdout += data.toString()
|
|
})
|
|
|
|
proc.stderr.on("data", (data) => {
|
|
stderr += data.toString()
|
|
})
|
|
|
|
proc.on("error", (error) => {
|
|
resolve(`Error: ${error.message}`)
|
|
})
|
|
|
|
proc.on("close", (exitCode) => {
|
|
const output =
|
|
exitCode === 0
|
|
? stdout.trim() || "Query executed successfully"
|
|
: `Error (exit ${exitCode}): ${
|
|
(stderr || stdout).trim() || "No error output"
|
|
}`
|
|
resolve(output)
|
|
})
|
|
})
|
|
},
|
|
})
|
|
|
|
const parenRepairTool = tool({
|
|
description:
|
|
"Fix mismatched parentheses/braces in Clojure files (.clj, .cljs, .cljc) then reformat with cljfmt.",
|
|
|
|
args: {
|
|
// A string is used instead of an array so OpenCode displays it
|
|
// in the generic tool invocation.
|
|
files: tool.schema
|
|
.string()
|
|
.describe(
|
|
"Comma-separated file paths to fix, for example: frontend/src/app/config.cljs, backend/src/core.clj",
|
|
)
|
|
.optional(),
|
|
|
|
code: tool.schema
|
|
.string()
|
|
.describe("Code string to fix via stdin")
|
|
.optional(),
|
|
},
|
|
|
|
async execute(args, context) {
|
|
const script = path.join(context.worktree, "scripts/paren-repair")
|
|
|
|
const files = args.files
|
|
? args.files
|
|
.split(",")
|
|
.map((file) => file.trim())
|
|
.filter(Boolean)
|
|
: []
|
|
|
|
const paramInfo =
|
|
files.length > 0
|
|
? `files=[${files.join(", ")}]`
|
|
: args.code !== undefined
|
|
? `code=(${args.code.length} chars)`
|
|
: "none"
|
|
|
|
return new Promise((resolve) => {
|
|
const childArgs =
|
|
files.length > 0
|
|
? [script, ...files]
|
|
: [script]
|
|
|
|
const proc = spawn("bb", childArgs, {
|
|
cwd: context.worktree,
|
|
})
|
|
|
|
let stdout = ""
|
|
let stderr = ""
|
|
|
|
proc.stdout.on("data", (data) => {
|
|
stdout += data.toString()
|
|
})
|
|
|
|
proc.stderr.on("data", (data) => {
|
|
stderr += data.toString()
|
|
})
|
|
|
|
proc.on("error", (error) => {
|
|
resolve(`Error: ${error.message}`)
|
|
})
|
|
|
|
proc.on("close", (exitCode) => {
|
|
const output =
|
|
exitCode === 0
|
|
? stdout.trim() || "No changes needed"
|
|
: `Error (exit ${exitCode}): ${
|
|
(stderr || stdout).trim() || "No error output"
|
|
}`
|
|
|
|
resolve(output)
|
|
})
|
|
|
|
// Close stdin in all cases so the process cannot wait indefinitely.
|
|
if (args.code !== undefined) {
|
|
proc.stdin.end(args.code)
|
|
} else {
|
|
proc.stdin.end()
|
|
}
|
|
})
|
|
},
|
|
})
|
|
|
|
export default async function plugin() {
|
|
return {
|
|
tool: {
|
|
"paren-repair": parenRepairTool,
|
|
"penpot-psql": penpotPsqlTool,
|
|
},
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// import { tool } from "@opencode-ai/plugin"
|
|
// import path from "path"
|
|
// import { spawn } from "child_process"
|
|
|
|
// function formatFiles(files) {
|
|
// if (files.length === 0) return "stdin"
|
|
|
|
// // Keep the visible tool title reasonably short.
|
|
// if (files.length <= 3) return files.join(", ")
|
|
|
|
// return `${files.slice(0, 3).join(", ")} (+${files.length - 3} more)`
|
|
// }
|
|
|
|
// const parenRepairTool = tool({
|
|
// description:
|
|
// "Fix mismatched parentheses/braces in Clojure files, then reformat with cljfmt.",
|
|
|
|
// args: {
|
|
// files: tool.schema
|
|
// .array(tool.schema.string())
|
|
// .describe("Array of file paths to fix")
|
|
// .optional(),
|
|
|
|
// code: tool.schema
|
|
// .string()
|
|
// .describe("Code string to fix via stdin")
|
|
// .optional(),
|
|
// },
|
|
|
|
// async execute(args, context) {
|
|
// const script = path.join(context.worktree, "scripts/paren-repair")
|
|
|
|
// const files = (args.files ?? []).map((file) => {
|
|
// const absolute = path.isAbsolute(file)
|
|
// ? file
|
|
// : path.resolve(context.worktree, file)
|
|
|
|
// return path.relative(context.worktree, absolute)
|
|
// })
|
|
|
|
// const targetSummary =
|
|
// files.length > 0
|
|
// ? formatFiles(files)
|
|
// : args.code !== undefined
|
|
// ? `stdin (${args.code.length} chars)`
|
|
// : "no input"
|
|
|
|
// // This updates the tool-call title immediately, while it is running.
|
|
// await context.metadata({
|
|
// title: `Paren repair: ${targetSummary}`,
|
|
// metadata: {
|
|
// files,
|
|
// codeChars: args.code?.length,
|
|
// },
|
|
// })
|
|
|
|
// const childArgs =
|
|
// args.files && args.files.length > 0
|
|
// ? [script, ...args.files]
|
|
// : [script]
|
|
|
|
// return new Promise((resolve) => {
|
|
// const proc = spawn("bb", childArgs, {
|
|
// cwd: context.worktree,
|
|
// })
|
|
|
|
// let stdout = ""
|
|
// let stderr = ""
|
|
|
|
// if (args.code !== undefined) {
|
|
// proc.stdin.end(args.code)
|
|
// }
|
|
|
|
// proc.stdout.on("data", (data) => {
|
|
// stdout += data.toString()
|
|
// })
|
|
|
|
// proc.stderr.on("data", (data) => {
|
|
// stderr += data.toString()
|
|
// })
|
|
|
|
// proc.on("close", (exitCode) => {
|
|
// const successful = exitCode === 0
|
|
|
|
// const commandOutput = successful
|
|
// ? stdout.trim() || "No changes needed"
|
|
// : `Error (exit ${exitCode}): ${(stderr || stdout).trim()}`
|
|
|
|
// const parameterOutput =
|
|
// files.length > 0
|
|
// ? `Files passed:\n${files.map((file) => `- ${file}`).join("\n")}`
|
|
// : args.code !== undefined
|
|
// ? `Input passed through stdin: ${args.code.length} characters`
|
|
// : "No files or stdin input were passed"
|
|
|
|
// resolve({
|
|
// title: `Paren repair: ${targetSummary}`,
|
|
// output: `${parameterOutput}\n\n${commandOutput}`,
|
|
// metadata: {
|
|
// files,
|
|
// codeChars: args.code?.length,
|
|
// exitCode,
|
|
// successful,
|
|
// },
|
|
// })
|
|
// })
|
|
|
|
// proc.on("error", (error) => {
|
|
// resolve({
|
|
// title: `Paren repair failed: ${targetSummary}`,
|
|
// output: [
|
|
// files.length > 0
|
|
// ? `Files passed:\n${files.map((file) => `- ${file}`).join("\n")}`
|
|
// : `Input: ${targetSummary}`,
|
|
// `Failed to start bb: ${error.message}`,
|
|
// ].join("\n\n"),
|
|
// metadata: {
|
|
// files,
|
|
// codeChars: args.code?.length,
|
|
// successful: false,
|
|
// },
|
|
// })
|
|
// })
|
|
// })
|
|
// },
|
|
// })
|
|
|
|
// export default async function plugin() {
|
|
// return {
|
|
// tool: {
|
|
// "paren-repair": parenRepairTool,
|
|
// },
|
|
// }
|
|
// }
|