mirror of
https://github.com/affaan-m/ECC.git
synced 2026-10-30 12:25:19 +00:00
The inert stub written when hook consent is not granted was
`export default async () => ({})`. OpenCode v2 validates every plugin
module before running it and requires the default export to be a definition
object with an `id` and an `effect` or `setup` function, so a declined or
runtime-less home install printed the same startup error as the V1 hook
plugin:
PluginModule.LoadError: Plugin must export a default definition with an id
and an effect or setup function.
(cause: SchemaError(Expected object at ["default"]))
Write `export default { id: "ecc-hooks", setup() {} }` instead. It keeps the
inert behaviour (no imports, no module initialization of the replaced
plugin) and satisfies the loader.
Tests that pinned the generated bytes now compare against
getDisabledOpenCodePluginContent() rather than a duplicated literal, and
tests/opencode-plugin-loader-contract.test.js asserts the loader contract on
the generated payload. That test fails on the previous stub
("default export must be an object, got function").
Refs #3537. The same assertion should cover .opencode/plugins/*.ts once a V2
entrypoint lands (#3536, #3224); those plugins are scanned independently in
home installs, so both the barrel and the implementation are loaded.
100 lines
3.4 KiB
JavaScript
100 lines
3.4 KiB
JavaScript
/**
|
|
* Tests for the OpenCode V2 plugin loader contract.
|
|
*
|
|
* OpenCode v2 validates every plugin module before running it: the default export
|
|
* must be an object with an `id` and an `effect` or `setup` function
|
|
* (packages/core/src/plugin/module.ts). A factory default export, which is what a
|
|
* V1 plugin used, fails at startup with
|
|
*
|
|
* PluginModule.LoadError: Plugin must export a default definition with an id and
|
|
* an effect or setup function. (cause: SchemaError(Expected object at ["default"]))
|
|
*
|
|
* so a declined-hooks install still printed the same error as the V1 plugin did
|
|
* (issue #3537). These tests pin the contract on the payload ECC generates itself.
|
|
*
|
|
* Run with: node tests/opencode-plugin-loader-contract.test.js
|
|
*/
|
|
|
|
const assert = require('assert');
|
|
const fs = require('fs');
|
|
const os = require('os');
|
|
const path = require('path');
|
|
const { pathToFileURL } = require('url');
|
|
|
|
const { getDisabledOpenCodePluginContent } = require('../scripts/lib/install/hook-consent');
|
|
|
|
function test(name, fn) {
|
|
try {
|
|
fn();
|
|
console.log(` ✓ ${name}`);
|
|
return true;
|
|
} catch (err) {
|
|
console.log(` ✗ ${name}`);
|
|
console.log(` Error: ${err.message}`);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/** Loads generated plugin source the way the OpenCode loader does: import it. */
|
|
async function loadPluginModule(source) {
|
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-plugin-contract-'));
|
|
const file = path.join(dir, 'plugin.mjs');
|
|
fs.writeFileSync(file, source);
|
|
return import(pathToFileURL(file).href);
|
|
}
|
|
|
|
/** Mirrors the loader schema: { default: { id, effect } | { id, setup } }. */
|
|
function assertV2PluginDefinition(namespace, label) {
|
|
const definition = namespace.default;
|
|
assert.ok(
|
|
definition && typeof definition === 'object',
|
|
`${label}: default export must be an object, got ${typeof definition}`
|
|
);
|
|
assert.strictEqual(typeof definition.id, 'string', `${label}: default export needs a string id`);
|
|
assert.ok(
|
|
typeof definition.effect === 'function' || typeof definition.setup === 'function',
|
|
`${label}: default export needs an effect or setup function`
|
|
);
|
|
}
|
|
|
|
async function main() {
|
|
let passed = 0;
|
|
let failed = 0;
|
|
|
|
const check = async (name, fn) => {
|
|
try {
|
|
await fn();
|
|
console.log(` ✓ ${name}`);
|
|
passed += 1;
|
|
} catch (err) {
|
|
console.log(` ✗ ${name}`);
|
|
console.log(` Error: ${err.message}`);
|
|
failed += 1;
|
|
}
|
|
};
|
|
|
|
const stub = getDisabledOpenCodePluginContent();
|
|
const stubModule = await loadPluginModule(stub);
|
|
|
|
await check('declined-hooks stub satisfies the V2 loader schema', () => {
|
|
assertV2PluginDefinition(stubModule, 'disabled OpenCode plugin stub');
|
|
});
|
|
|
|
await check('the stub check still rejects a V1 factory default export', async () => {
|
|
const v1 = await loadPluginModule('export default async () => ({});\n');
|
|
assert.throws(() => assertV2PluginDefinition(v1, 'V1 factory'), /default export must be an object/);
|
|
});
|
|
|
|
await check('the stub does not import the module it replaces', () => {
|
|
assert.ok(!/^\s*import\s/m.test(stub), 'stub must stay free of imports');
|
|
});
|
|
|
|
// The same check applies to every shipped plugin entrypoint once a V2 entrypoint
|
|
// lands (.opencode/plugins/*.ts is scanned independently in home installs).
|
|
// See issue #3537 and the API ports in #3536 and #3224.
|
|
|
|
console.log(`\n ${passed} passed, ${failed} failed\n`);
|
|
process.exit(failed === 0 ? 0 : 1);
|
|
}
|
|
|
|
main(); |