Skip to content

feat(plugins): external chart plugin loading via manifest (#423) - #589

Merged
alfredo1996 merged 3 commits into
release/2.0from
feat/issue-423-external-plugins
Apr 21, 2026
Merged

feat(plugins): external chart plugin loading via manifest (#423)#589
alfredo1996 merged 3 commits into
release/2.0from
feat/issue-423-external-plugins

Conversation

@alfredo1996

@alfredo1996 alfredo1996 commented Apr 21, 2026

Copy link
Copy Markdown
Owner

Summary

Phase 1 of #423 — external chart plugins can now be loaded from npm packages via a build-time manifest.

Manifest

  • neoboard-plugins.json at repo root lists external packages that export ChartPlugin definitions
  • JSON schema (neoboard-plugins.schema.json) drives editor autocompletion
  • Entry shape: { package, export?, overrides? }

Build-time codegen

  • scripts/generate-plugin-imports.mjs runs as predev + prebuild
  • Validates the manifest, emits app/src/plugins/external-plugins.generated.ts with static import statements
  • Idempotent (skips rewrite when content unchanged)
  • Rejects: malformed JSON, missing package, unknown keys, duplicate package+export pairs

Bootstrap

  • app/src/plugins/index.ts imports the generated EXTERNAL_PLUGINS array after built-ins
  • Same-type conflicts throw loudly unless the manifest entry sets "overrides": true — explicit opt-in, never silent

Trust model (documented in docs/plugins/authoring.md)

Test plan

  • 25 generator tests (validation, rendering, idempotent writes, error cases)
  • 7 bootstrap tests (unique register, conflict rejection, overrides replace, batch-abort)
  • Full suite: 2097 tests pass
  • Build passes (codegen wired into prebuild)

Deferred to follow-ups

  • Example plugin workspace (examples/plugin-sparkline/) — a copy-paste starter. Keeping it out of this PR to keep the diff reviewable.
  • Plugin SDK package (@neoboard/plugin-sdk) — stable public re-export of defineChartPlugin + types. Right now plugins import from app/src/lib/plugin/… which is fragile.

Closes #423

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Build-time support for external chart plugins via a manifest, including optional overriding of built-in chart types (conflicts fail startup unless overrides enabled).
  • Documentation

    • Added a comprehensive plugin authoring guide covering manifest format, package layout, development workflow, and testing recommendations.
  • Tests

    • New test suites validating plugin registration behavior, manifest validation, and the generator’s output/idempotency.
  • Chores

    • Added a generator step to build pipeline and ignore the generated plugin output.

Closes #423 — Phase 1.

New: neoboard-plugins.json
- JSON manifest at repo root lists external npm packages that export
  ChartPlugin definitions
- JSON schema (neoboard-plugins.schema.json) for editor autocompletion
- Entries: { package, export?, overrides? }

New: scripts/generate-plugin-imports.mjs
- Runs at predev + prebuild
- Validates the manifest, generates
  app/src/plugins/external-plugins.generated.ts with static import
  statements
- Idempotent — skips writing when content is unchanged
- Rejects duplicate package+export pairs, unknown keys, malformed JSON

Plugin bootstrap (app/src/plugins/index.ts)
- Imports the generated EXTERNAL_PLUGINS array after built-ins
- Same-type conflicts throw loudly unless the manifest entry sets
  "overrides": true (explicit opt-in, never silent)

Trust model
- External plugins run in-process with full app access — same trust
  as any npm dep bundled at build time
- Adding a plugin requires repo commit + npm install; there is no
  runtime plugin surface
- Documented in docs/plugins/authoring.md

Tests
- 25 generator tests (validateEntry/validateManifest/renderSource/
  runGenerator, temp-dir e2e, idempotent write, invalid JSON,
  duplicates)
- 7 bootstrap tests (unique register, conflict rejection, overrides
  replace, batch-abort-on-first-conflict, end-to-end real registry)
- Full suite: 2097 pass

Deferred to follow-ups:
- Example plugin workspace (examples/plugin-sparkline/)
- Plugin SDK re-export package (right now plugins import from
  app/src/lib — SDK package will stabilize the public surface)

Closes #423

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Apr 21, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@alfredo1996 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 54 minutes and 10 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 54 minutes and 10 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 344f4a50-ddee-4203-90c8-065c8daff6bc

📥 Commits

Reviewing files that changed from the base of the PR and between 57c1577 and 46f0f91.

📒 Files selected for processing (2)
  • .gitignore
  • app/src/plugins/external-plugins.generated.ts

Walkthrough

A build-time manifest-driven external plugin system: a JSON manifest and schema define external plugins; a Node generator produces app/src/plugins/external-plugins.generated.ts; runtime plugin bootstrap registers external plugins with conflict detection and optional overrides. Tests and authoring docs added.

Changes

Cohort / File(s) Summary
Manifest & Config
./.gitignore, neoboard-plugins.json, neoboard-plugins.schema.json, package.json, app/package.json
Add ignore for generated app/src/plugins/external-plugins.generated.ts; add empty neoboard-plugins.json with $schema; add JSON Schema for manifest; wire generator into predev/prebuild hooks.
Generator & Tests
scripts/generate-plugin-imports.mjs, scripts/__tests__/generate-plugin-imports.test.mjs
New CLI/library that validates manifest entries, rejects duplicates/unknown keys, renders deterministic TypeScript imports + EXTERNAL_PLUGINS, idempotent write; comprehensive unit/CLI tests for validation, rendering, and runtime behavior.
Runtime Registration & Tests
app/src/plugins/index.ts, app/src/plugins/__tests__/external-plugins-bootstrap.test.ts
Import generated EXTERNAL_PLUGINS and register them after built-ins; on type conflict throw unless overrides: true then replace; add tests covering registration, conflicts, overrides, batch semantics, no-op, and E2E import with mocked chart components.
Docs
docs/plugins/authoring.md
New authoring guide describing manifest format, package layout, trust model (in-process), override semantics, dev workflow, failure modes, and testing guidance.

Sequence Diagram(s)

sequenceDiagram
  participant Dev as Developer (build)
  participant FS as File System / Generator
  participant Manifest as neoboard-plugins.json
  participant Build as Build process (predev/prebuild)
  participant App as App runtime
  participant Registry as pluginRegistry

  Dev->>FS: edit `neoboard-plugins.json`
  Build->>FS: runs `generate-plugin-imports.mjs`
  FS->>Manifest: read & validate manifest
  FS->>FS: render `external-plugins.generated.ts` (idempotent)
  FS->>File System: write generated file if changed
  Build->>App: start dev/build (imports generated file)
  App->>Registry: register built-in plugins
  App->>Registry: iterate EXTERNAL_PLUGINS
  alt plugin.type exists and overrides == false
    Registry-->>App: throw startup error (conflict)
  else plugin.type exists and overrides == true
    Registry->>Registry: unregister existing
    Registry->>Registry: register external plugin
  else
    Registry->>Registry: register external plugin
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Suggested labels

documentation, pkg:app

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main feature: external chart plugin loading via manifest. It is concise, specific, and clearly conveys the primary change.
Linked Issues check ✅ Passed The PR successfully implements all coding requirements from issue #423: manifest structure, build-time codegen script with validation, JSON schema, bootstrap integration with conflict handling, documentation, and comprehensive test coverage.
Out of Scope Changes check ✅ Passed All changes are directly scoped to issue #423. The PR adds manifest infrastructure, codegen, schema, tests, documentation, and bootstrap logic. Example plugin workspace and SDK package are intentionally deferred as noted in objectives.
Docstring Coverage ✅ Passed Docstring coverage is 85.71% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/issue-423-external-plugins

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (2)
app/src/plugins/__tests__/external-plugins-bootstrap.test.ts (1)

24-45: Test the production registration helper instead of mirroring it.

This duplicates the loop from plugins/index.ts, so the tests can pass even if the real bootstrap logic drifts. Consider extracting the external-registration loop into a pure helper and importing that here.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/plugins/__tests__/external-plugins-bootstrap.test.ts` around lines 24
- 45, The test currently duplicates the external-plugin registration loop
(function registerExternalPlugins) instead of using the production helper;
remove this duplicated function and instead import and call the single pure
helper used by the runtime (extract it from plugins/index.ts into a small pure
function if it doesn't yet exist), then update the tests to call that helper
with the same signature (accepting the registry from createPluginRegistry and
entries array of { plugin, overrides }) so the test exercises the real
registration logic and uses registry.has, registry.unregister and
registry.register from the production implementation.
scripts/__tests__/generate-plugin-imports.test.mjs (1)

48-67: Add tests for special characters in package and export names.

The renderSource tests only validate simple strings like "@a/one" and "heatmap". Since package and export are rendered directly into import statements without escaping (lines 143, 145 in the generator), add regression cases for quotes, newlines, and invalid identifier syntax to ensure malformed TypeScript cannot be generated.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/__tests__/generate-plugin-imports.test.mjs` around lines 48 - 67, Add
regression tests to prevent unescaped special characters in generated import
statements: extend the test suite (the same file with validateEntry and
renderSource coverage) to include cases where package and export contain quotes,
newlines, and invalid identifier characters (e.g., package: '"bad"', '"pkg\n"',
export: 'weird-name!', etc.). Use validateEntry to assert these produce
validation errors (or that renderSource refuses/throws) by matching messages
indicating invalid/unsafe package or export values; reference validateEntry and
renderSource so reviewers can locate and add tests that ensure the generator
does not emit unescaped/malformed TypeScript imports.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/plugins/authoring.md`:
- Line 59: The fenced code block in the docs/plugins/authoring.md currently
lacks a language tag; update the opening fence for the directory tree (the block
containing "my-neoboard-plugin/") to use the text language (change the
triple-backtick fence to ```text) so the Markdown linter stops flagging it and
the tree renders as plain text.

In `@neoboard-plugins.schema.json`:
- Around line 17-24: The schema lets "package" and "export" be arbitrary
strings, which permits whitespace and invalid JS identifiers that will break
generated TypeScript; update the neoboard-plugins.schema.json definitions for
"package" to include a "pattern" that forbids whitespace and matches valid
import specifiers (e.g. allow scoped packages, relative paths and characters
typically valid in package names like letters, digits, -, _, ., / and @) and for
"export" to include a "pattern" that enforces valid JS export names (e.g.
^[A-Za-z_$][A-Za-z0-9_$]*$) or the literal "default"; keep the existing
"minLength" checks and add concise regex patterns on the "package" and "export"
properties so editors validate manifests the same way the generator expects.

In `@package.json`:
- Around line 13-18: The test and lint scripts currently don't generate
external-plugins.generated.ts (which app/src/plugins/index.ts imports) before
running, causing CI/local `npm test` or `npm run lint` to fail; add pretest,
pretest:app, and prelint scripts (or update existing test scripts) in
package.json to run "node scripts/generate-plugin-imports.mjs" so generation
runs before test/lint tasks and ensures external-plugins.generated.ts exists.

In `@scripts/__tests__/generate-plugin-imports.test.mjs`:
- Around line 196-207: Replace the current idempotency assertion that only
checks second.wrote with a filesystem mtime check: after the first runGenerator
call (in the "is idempotent" test using withTempDir), record the output file's
mtime via fs.statSync (or fs.promises.stat) and then run the generator a second
time; assert that second.wrote is false and that the output file's mtime is
unchanged (compare the stat.mtime value from before and after). Use the existing
runGenerator, readFileSync and output path variables to locate the file and
fs.utimesSync only if you need to set a known mtime for a deterministic
comparison. Ensure the test still reads the file contents to confirm content
equality as before.

In `@scripts/generate-plugin-imports.mjs`:
- Around line 76-84: validateManifest currently only checks that raw is an
object and that m.plugins is an array, but it doesn't reject unknown top-level
keys contrary to the schema's additionalProperties:false; update
validateManifest to detect any top-level keys other than "plugins" (e.g., using
Object.keys(m) and filtering out "plugins"), push a descriptive error like
"unknown top-level manifest keys: X" into the errors array when extras exist,
and return the error result (same shape as the other early returns) so manifests
with extra keys are rejected; reference the validateManifest function and the
local variable m/plugins when making the change.
- Around line 43-65: The validateEntry function must enforce safe module
specifiers and export identifiers before code generation: add regex checks in
validateEntry to reject package strings containing whitespace, quotes (" or '),
backslashes, or other unsafe chars (e.g., /^[^\s"\'\\]+$/) and validate e.export
when present matches a valid JS identifier (e.g., /^[A-Za-z_$][A-Za-z0-9_$]*$/);
return a descriptive error message on failure. Then, when emitting import code
for e.package and e.export (where template interpolation currently occurs),
ensure you use escaped/serialized values (e.g., JSON.stringify(e.package)) for
the module specifier and only emit a named export if e.export passed validation.
Also ensure overrides handling still respects the allowed keys check in
validateEntry.

---

Nitpick comments:
In `@app/src/plugins/__tests__/external-plugins-bootstrap.test.ts`:
- Around line 24-45: The test currently duplicates the external-plugin
registration loop (function registerExternalPlugins) instead of using the
production helper; remove this duplicated function and instead import and call
the single pure helper used by the runtime (extract it from plugins/index.ts
into a small pure function if it doesn't yet exist), then update the tests to
call that helper with the same signature (accepting the registry from
createPluginRegistry and entries array of { plugin, overrides }) so the test
exercises the real registration logic and uses registry.has, registry.unregister
and registry.register from the production implementation.

In `@scripts/__tests__/generate-plugin-imports.test.mjs`:
- Around line 48-67: Add regression tests to prevent unescaped special
characters in generated import statements: extend the test suite (the same file
with validateEntry and renderSource coverage) to include cases where package and
export contain quotes, newlines, and invalid identifier characters (e.g.,
package: '"bad"', '"pkg\n"', export: 'weird-name!', etc.). Use validateEntry to
assert these produce validation errors (or that renderSource refuses/throws) by
matching messages indicating invalid/unsafe package or export values; reference
validateEntry and renderSource so reviewers can locate and add tests that ensure
the generator does not emit unescaped/malformed TypeScript imports.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ff3dee81-92c7-42d9-aca2-9dbe6cb9e539

📥 Commits

Reviewing files that changed from the base of the PR and between ee8388f and 01f67fb.

📒 Files selected for processing (9)
  • .gitignore
  • app/src/plugins/__tests__/external-plugins-bootstrap.test.ts
  • app/src/plugins/index.ts
  • docs/plugins/authoring.md
  • neoboard-plugins.json
  • neoboard-plugins.schema.json
  • package.json
  • scripts/__tests__/generate-plugin-imports.test.mjs
  • scripts/generate-plugin-imports.mjs

Comment thread docs/plugins/authoring.md

Your plugin should be an npm package. Minimum structure:

```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add a language to the package-layout fence.

Markdown lint flags this fenced block; use text for the directory tree.

Proposed fix
-```
+```text
 my-neoboard-plugin/
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```
🧰 Tools
🪛 markdownlint-cli2 (0.22.0)

[warning] 59-59: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/plugins/authoring.md` at line 59, The fenced code block in the
docs/plugins/authoring.md currently lacks a language tag; update the opening
fence for the directory tree (the block containing "my-neoboard-plugin/") to use
the text language (change the triple-backtick fence to ```text) so the Markdown
linter stops flagging it and the tree renders as plain text.

Comment on lines +17 to +24
"package": {
"type": "string",
"minLength": 1,
"description": "The npm package name to import (e.g. '@myorg/neoboard-heatmap' or './plugins/my-local')."
},
"export": {
"type": "string",
"minLength": 1,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Constrain manifest strings to values codegen can import.

The schema currently accepts whitespace and invalid named exports that will later generate broken TypeScript. Mirror the generator’s stricter rules here so editors catch bad manifests early.

Proposed schema tightening
           "package": {
             "type": "string",
             "minLength": 1,
+            "pattern": "^\\S+$",
             "description": "The npm package name to import (e.g. '@myorg/neoboard-heatmap' or './plugins/my-local')."
           },
           "export": {
             "type": "string",
             "minLength": 1,
+            "pattern": "^[A-Za-z_$][A-Za-z0-9_$]*$",
             "default": "default",
             "description": "Named export to import. Defaults to 'default' (the package's default export)."
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"package": {
"type": "string",
"minLength": 1,
"description": "The npm package name to import (e.g. '@myorg/neoboard-heatmap' or './plugins/my-local')."
},
"export": {
"type": "string",
"minLength": 1,
"package": {
"type": "string",
"minLength": 1,
"pattern": "^\\S+$",
"description": "The npm package name to import (e.g. '@myorg/neoboard-heatmap' or './plugins/my-local')."
},
"export": {
"type": "string",
"minLength": 1,
"pattern": "^[A-Za-z_$][A-Za-z0-9_$]*$",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@neoboard-plugins.schema.json` around lines 17 - 24, The schema lets "package"
and "export" be arbitrary strings, which permits whitespace and invalid JS
identifiers that will break generated TypeScript; update the
neoboard-plugins.schema.json definitions for "package" to include a "pattern"
that forbids whitespace and matches valid import specifiers (e.g. allow scoped
packages, relative paths and characters typically valid in package names like
letters, digits, -, _, ., / and @) and for "export" to include a "pattern" that
enforces valid JS export names (e.g. ^[A-Za-z_$][A-Za-z0-9_$]*$) or the literal
"default"; keep the existing "minLength" checks and add concise regex patterns
on the "package" and "export" properties so editors validate manifests the same
way the generator expects.

Comment thread package.json
Comment on lines +196 to +207
it("is idempotent — second run with unchanged manifest does not rewrite", () => {
withTempDir(({ manifest, output }) => {
writeFileSync(manifest, JSON.stringify({ plugins: [] }));
runGenerator({ manifestPath: manifest, outputPath: output });
const first = readFileSync(output, "utf8");
const second = runGenerator({
manifestPath: manifest,
outputPath: output,
});
assert.equal(second.wrote, false);
assert.equal(readFileSync(output, "utf8"), first);
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Assert the file was not rewritten.

This currently trusts second.wrote; it would still pass if the generator rewrote identical content but returned false. Set a known mtime after the first run and assert it is unchanged after the second run.

Proposed test hardening
-import { mkdtempSync, writeFileSync, readFileSync, existsSync } from "node:fs";
+import {
+  mkdtempSync,
+  writeFileSync,
+  readFileSync,
+  existsSync,
+  statSync,
+  utimesSync,
+} from "node:fs";
@@
       writeFileSync(manifest, JSON.stringify({ plugins: [] }));
       runGenerator({ manifestPath: manifest, outputPath: output });
+      const untouchedTimestamp = new Date("2000-01-01T00:00:00.000Z");
+      utimesSync(output, untouchedTimestamp, untouchedTimestamp);
       const first = readFileSync(output, "utf8");
+      const firstMtimeMs = statSync(output).mtimeMs;
       const second = runGenerator({
         manifestPath: manifest,
         outputPath: output,
       });
       assert.equal(second.wrote, false);
       assert.equal(readFileSync(output, "utf8"), first);
+      assert.equal(statSync(output).mtimeMs, firstMtimeMs);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/__tests__/generate-plugin-imports.test.mjs` around lines 196 - 207,
Replace the current idempotency assertion that only checks second.wrote with a
filesystem mtime check: after the first runGenerator call (in the "is
idempotent" test using withTempDir), record the output file's mtime via
fs.statSync (or fs.promises.stat) and then run the generator a second time;
assert that second.wrote is false and that the output file's mtime is unchanged
(compare the stat.mtime value from before and after). Use the existing
runGenerator, readFileSync and output path variables to locate the file and
fs.utimesSync only if you need to set a known mtime for a deterministic
comparison. Ensure the test still reads the file contents to confirm content
equality as before.

Comment thread scripts/generate-plugin-imports.mjs
Comment on lines +76 to +84
export function validateManifest(raw) {
const errors = [];
if (typeof raw !== "object" || raw === null) {
return { errors: ["manifest must be a JSON object"], entries: [] };
}
const m = /** @type {Record<string, unknown>} */ (raw);
if (!Array.isArray(m.plugins)) {
return { errors: ["manifest.plugins must be an array"], entries: [] };
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Reject unknown top-level manifest keys too.

The schema has additionalProperties: false, but the generator only validates plugins. A manifest with extra top-level keys will pass codegen while editors/schema validation reject it.

Proposed fix
   }
   const m = /** `@type` {Record<string, unknown>} */ (raw);
+  const allowedTopLevel = new Set(["$schema", "plugins"]);
+  for (const key of Object.keys(m)) {
+    if (!allowedTopLevel.has(key)) {
+      errors.push(`manifest has unknown key "${key}"`);
+    }
+  }
   if (!Array.isArray(m.plugins)) {
     return { errors: ["manifest.plugins must be an array"], entries: [] };
   }
+  if (errors.length > 0) {
+    return { errors, entries: [] };
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/generate-plugin-imports.mjs` around lines 76 - 84, validateManifest
currently only checks that raw is an object and that m.plugins is an array, but
it doesn't reject unknown top-level keys contrary to the schema's
additionalProperties:false; update validateManifest to detect any top-level keys
other than "plugins" (e.g., using Object.keys(m) and filtering out "plugins"),
push a descriptive error like "unknown top-level manifest keys: X" into the
errors array when extras exist, and return the error result (same shape as the
other early returns) so manifests with extra keys are rejected; reference the
validateManifest function and the local variable m/plugins when making the
change.

alfredorubin96 and others added 2 commits April 21, 2026 16:36
Dockerfile does 'cd app && npm run build', bypassing the root prebuild
hook. Adding predev/prebuild to app/package.json that invokes the root
codegen script ensures the generated file exists regardless of entry
point.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The TypeScript type-check CI job runs tsc directly without invoking
prebuild, so a gitignored generated file breaks type-check. Commit
the generated file (empty by default) and let predev/prebuild
regenerate it when the manifest changes. Codegen is idempotent, so
no-op writes don't dirty git.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants