jackwener/OpenCLI · warning

Plugin "${name}" structure invalid:\n- ${validation.errors.j

Error message

Plugin "${name}" structure invalid:\n- ${validation.errors.join('\n- ')}

What it means

During plugin installation (installFromLocal-style flow), validatePluginStructure checks the resolved plugin directory for required files/layout (plugin.json, entry points, etc.). If validation fails, this warning lists each structural problem; installation continues (postInstallLifecycle and lock entry upsert still run), so the plugin is registered despite being structurally suspect.

Source

Thrown at src/plugin.ts:831

    source: { kind: 'local', path: resolvedPath },
    commitHash: commitHash ?? 'local',
  });
  writeLockFile(lock);

  return pluginName;
}

function updateLocalPlugin(
  name: string,
  targetDir: string,
  lock: Record<string, LockEntry>,
  lockEntry?: LockEntry,
): void {
  const pluginDir = fs.realpathSync(targetDir);

  const validation = validatePluginStructure(pluginDir);
  if (!validation.valid) {
    log.warn(`Plugin "${name}" structure invalid:\n- ${validation.errors.join('\n- ')}`);
  }

  postInstallLifecycle(pluginDir);

  upsertLockEntry(lock, name, {
    source: lockEntry?.source ?? { kind: 'local', path: pluginDir },
    commitHash: getCommitHash(pluginDir) ?? 'local',
    installedAt: lockEntry?.installedAt ?? new Date().toISOString(),
    updatedAt: new Date().toISOString(),
  });
  writeLockFile(lock);
}

/** Install sub-plugins from a monorepo. */
function installMonorepo(
  cloneDir: string,
  cloneUrl: string,
  repoName: string,

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Read the listed validation errors — they name exactly which files/fields are missing or wrong.
  2. Create or fix the plugin manifest (plugin.json) with correct name/main fields.
  3. Build the plugin if entry points point to unbuilt TS/dist output.
  4. Validate JSON syntax of the manifest with a linter before reinstalling.

Example fix

// before
my-plugin/
  index.ts      (no manifest)
// after
my-plugin/
  plugin.json   { "name": "my-plugin", "main": "dist/index.js" }
  dist/index.js
Defensive patterns

Strategy: validation

Validate before calling

const manifest = JSON.parse(fs.readFileSync(path.join(pluginDir, 'plugin.json'), 'utf-8'));
if (!manifest.name || !manifest.main) throw new Error('plugin.json missing name/main');
if (!fs.existsSync(path.join(pluginDir, manifest.main))) throw new Error(`entry not found: ${manifest.main}`);

Type guard

function isPluginDirValid(dir: string): boolean {
  try {
    const m = JSON.parse(fs.readFileSync(path.join(dir, 'plugin.json'), 'utf-8'));
    return typeof m.name === 'string' && typeof m.main === 'string' && fs.existsSync(path.join(dir, m.main));
  } catch { return false; }
}

Try / catch

const validation = validatePluginStructure(pluginDir);
if (!validation.valid) {
  validation.errors.forEach((e) => console.error(`plugin structure: ${e}`));
  process.exitCode = 1; // fail fast instead of installing a broken plugin
}

Prevention

When it happens

Trigger: validatePluginStructure(pluginDir) returns { valid: false, errors: [...] } — missing plugin manifest, missing declared entry file, wrong directory layout, or unreadable manifest JSON — right after fs.realpathSync(targetDir) resolves the install target.

Common situations: Hand-writing a plugin and forgetting plugin.json or the main entry; renaming files referenced by the manifest; installing a directory that only contains source without build output; manifest JSON with a syntax error.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/84810b306de43a22. Report an issue: GitHub.