From 511ddd4d6278962dcd291481f4857d4973e4f0fc Mon Sep 17 00:00:00 2001 From: D3VL0PR <44045943+ayushere@users.noreply.github.com> Date: Thu, 9 Jul 2026 15:14:31 +0530 Subject: [PATCH] fix(opencode): stop exporting parseCommandFile from plugin module (#301) OpenCode's legacy plugin loader iterates every exported function in a plugin module and tries to invoke each one with the plugin context object. parseCommandFile was exported alongside the default plugin function, so the loader called it with an object instead of a file path and crashed with 'path must be a string or a file descriptor' before any hook ran, which aborted the whole plugin load. Move the frontmatter parser to its own .cjs sibling; the plugin module now exposes exactly one top-level export (the default plugin function). The plugin requires the parser via createRequire, and the existing parseCommandFile tests now require the sibling directly. Co-authored-by: ayushere --- .opencode/plugins/ponytail-frontmatter.cjs | 23 ++++++++++++++++++++++ .opencode/plugins/ponytail.mjs | 10 +--------- tests/opencode-plugin.test.js | 6 +++++- 3 files changed, 29 insertions(+), 10 deletions(-) create mode 100644 .opencode/plugins/ponytail-frontmatter.cjs diff --git a/.opencode/plugins/ponytail-frontmatter.cjs b/.opencode/plugins/ponytail-frontmatter.cjs new file mode 100644 index 0000000..1145929 --- /dev/null +++ b/.opencode/plugins/ponytail-frontmatter.cjs @@ -0,0 +1,23 @@ +'use strict'; + +// ponytail command-file frontmatter parser. +// +// Pulled out of ponytail.mjs so the plugin module's only top-level export is +// the plugin function itself. OpenCode's legacy plugin loader (the one that +// runs before v1 plugins are detected) treats every function exported from a +// plugin module as a plugin; calling the frontmatter parser as one threw +// "path must be a string or a file descriptor" because it got the plugin +// context object as its first argument. Keeping the parser in its own module +// leaves exactly one plugin-shaped export on ponytail.mjs. + +function parseCommandFile(filePath) { + const fs = require('fs'); + const content = fs.readFileSync(filePath, 'utf8'); + // Tolerate CRLF: a Windows checkout (autocrlf) delivers \r\n, npm ships \n. + const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/); + if (!match) return null; + const description = match[1].match(/description:\s*(.+)/)?.[1]?.trim(); + return { description, template: match[2].trim() }; +} + +module.exports = { parseCommandFile }; diff --git a/.opencode/plugins/ponytail.mjs b/.opencode/plugins/ponytail.mjs index 4db6f4a..6a77468 100644 --- a/.opencode/plugins/ponytail.mjs +++ b/.opencode/plugins/ponytail.mjs @@ -21,6 +21,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url)); const require = createRequire(import.meta.url); const { getPonytailInstructions } = require('../../hooks/ponytail-instructions'); const { getDefaultMode, normalizePersistedMode } = require('../../hooks/ponytail-config'); +const { parseCommandFile } = require('./ponytail-frontmatter.cjs'); // OpenCode has no flag-file convention of its own; keep mode beside its config. const statePath = path.join( @@ -42,15 +43,6 @@ function writeMode(mode) { fs.writeFileSync(statePath, mode); } -export function parseCommandFile(filePath) { - const content = fs.readFileSync(filePath, 'utf8'); - // Tolerate CRLF: a Windows checkout (autocrlf) delivers \r\n, npm ships \n. - const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/); - if (!match) return null; - const description = match[1].match(/description:\s*(.+)/)?.[1]?.trim(); - return { description, template: match[2].trim() }; -} - export default async ({ client } = {}) => { const log = (level, message) => { try { client && client.app && client.app.log({ body: { service: 'ponytail', level, message } }); } catch (e) {} diff --git a/tests/opencode-plugin.test.js b/tests/opencode-plugin.test.js index bb5b8ca..d598643 100644 --- a/tests/opencode-plugin.test.js +++ b/tests/opencode-plugin.test.js @@ -23,7 +23,11 @@ test.before(async () => { const url = pathToFileURL(path.join(__dirname, '..', '.opencode', 'plugins', 'ponytail.mjs')); const mod = await import(url); loadPlugin = mod.default; - parseCommandFile = mod.parseCommandFile; + // The frontmatter parser used to be exported from the plugin module itself. + // OpenCode's legacy loader treats every exported function as a plugin and + // tried to invoke it with the plugin context object, which crashed. The + // parser now lives in its own .cjs sibling; require it directly. + parseCommandFile = require(path.join(__dirname, '..', '.opencode', 'plugins', 'ponytail-frontmatter.cjs')).parseCommandFile; }); function transform(hooks) {