jackwener/OpenCLI · error · PluginError
Invalid plugin structure: - ${validation.errors.join(' - ')}
Error message
Invalid plugin structure:
- ${validation.errors.join('
- ')} What it means
ensureStandalonePluginReady validates the cloned/updated plugin directory via validatePluginStructure and throws a PluginError listing all structural problems. The check requires at least one .ts or .js command file, and for TS plugins a package.json with "type": "module". opencli throws this so broken plugins never reach installation.
Source
Thrown at src/plugin.ts:624
* For monorepos that do NOT use workspaces, sub-plugins may declare their own
* production dependencies in their package.json. We install those per sub-plugin
* so that runtime imports (e.g. `undici`) can be resolved from the sub-plugin
* directory. When the root already satisfies all deps this is a fast no-op.
*/
function postInstallMonorepoLifecycle(repoDir: string, pluginDirs: string[]): void {
installDependencies(repoDir);
for (const pluginDir of pluginDirs) {
if (pluginDir !== repoDir && hasOwnDependencies(pluginDir)) {
installDependencies(pluginDir);
}
finalizePluginRuntime(pluginDir);
}
}
function ensureStandalonePluginReady(pluginDir: string): void {
const validation = validatePluginStructure(pluginDir);
if (!validation.valid) {
throw new PluginError(`Invalid plugin structure:\n- ${validation.errors.join('\n- ')}`);
}
postInstallLifecycle(pluginDir);
}
type LockEntryInput = Omit<LockEntry, 'installedAt'> & Partial<Pick<LockEntry, 'installedAt'>>;
function upsertLockEntry(
lock: Record<string, LockEntry>,
name: string,
entry: LockEntryInput,
): void {
lock[name] = {
...entry,
installedAt: entry.installedAt ?? new Date().toISOString(),
};
}
View on GitHub (pinned to 49907e53dc)
Solutions
- Read the bullet list in the error and fix each item in the plugin repo.
- Add at least one .ts or .js command file at the plugin root.
- For TS plugins, add a package.json containing "type": "module" and the @jackwener/opencli peer dependency.
- Ensure you're installing the intended plugin repo, not an unrelated repository.
- Fix malformed JSON in package.json if the error reports it.
Example fix
// before (package.json)
{ "name": "my-plugin" }
// after
{ "name": "my-plugin", "type": "module", "peerDependencies": { "@jackwener/opencli": "*" } } Defensive patterns
Strategy: validation
Validate before calling
import * as fs from 'node:fs';
import * as path from 'node:path';
function looksLikePlugin(dir) {
if (!fs.existsSync(dir)) return false;
const files = fs.readdirSync(dir);
const hasTs = files.some(f => f.endsWith('.ts'));
const hasJs = files.some(f => f.endsWith('.js'));
if (!hasTs && !hasJs) return false;
if (hasTs) {
const pkgPath = path.join(dir, 'package.json');
if (!fs.existsSync(pkgPath)) return false;
try { return JSON.parse(fs.readFileSync(pkgPath, 'utf-8')).type === 'module'; }
catch { return false; }
}
return true;
} Try / catch
try {
installPlugin(source);
} catch (err) {
if (err instanceof PluginError && err.message.startsWith('Invalid plugin structure')) {
// surface err.message bullets to the plugin author
} else throw err;
} Prevention
- Ship at least one root-level .ts or .js command file in every plugin repo.
- Include a package.json with "type": "module" for TypeScript plugins.
- Validate the plugin locally with opencli before publishing the repo.
- Confirm you're installing a plugin repo, not an unrelated repository.
When it happens
Trigger: installSinglePlugin (via installPlugin of a non-monorepo repo) or updatePlugin on a standalone plugin whose repo: contains no command files, has .ts files but no package.json, package.json lacks "type": "module", or has malformed package.json.
Common situations: Pointing installPlugin at a repo that isn't actually an opencli plugin (wrong repo, docs-only repo); plugin author forgot package.json or shipped CommonJS config; repo restructured and command files moved into subdirectories (only root files are scanned).
Related errors
- INVALID_ARGUMENT
- INVALID_ARGUMENT
- ${label} is required
- ${label} returned an unexpected payload shape; expected an o
- ${label} returned an unexpected payload shape; expected an a
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/4fe2788019b5cd5f.
Report an issue: GitHub.