Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

DevTool plugins — skeleton (main)

Optional plugins for DevTool, installable at runtime (Settings → Extensions → paste a manifest URL) instead of being compiled into the main app — see docs/decisions/architecture/platform-plugin-architecture.md in the main repo for the full reasoning.

Branch layout

This branch (main) has no plugin code. It's the skeleton every plugin branch builds on top of:

  • shared/ — a vendored, trimmed copy of the app's shadcn/ui kit and a few src/lib/* utilities. It's a deliberate duplication, not a shared package: an externally-installed plugin bundle can't import the app's own src/components/ui/* (only react/react-dom/react/jsx-runtime are shared, via window.__DEVTOOL_VENDOR__ — see shared/vendor.d.ts and shared/platform.ts).
  • scripts/makePluginConfig.mjs — the shared Vite config factory every plugin's vite.config.ts calls into.
  • root package.json/tsconfig.json/vitest.config.ts — the toolchain versions and lint/type rules every plugin branch starts from.
  • catalog.json — the aggregated, install-ready plugin list. Read by starlight-site's /plugins page via raw.githubusercontent.com/.../main/catalog.json (release-asset storage doesn't send CORS headers, so it has to be a committed file, not a release asset). Nothing on main writes this file — each plugin branch's own release updates only its own entry when it ships (see below), so this stays live without main ever needing a release of its own.

Each actual plugin lives on its own long-lived branch, app/<id>/main (currently app/redis-client/main, app/rabbit-client/main, app/container-manager/main, app/kafka-explorer/main), forked from this skeleton. A plugin branch has:

  • plugins/<id>/ — that plugin's ui/, entry.tsx, manifest.*.json templates, and (if it has a sidecar) sidecar/ — same layout as before, just one plugin per branch instead of all of them in one tree.
  • Its own package.json/ci.yml/release.yml, trimmed to build/test/release only that one plugin.
  • scripts/merge-catalog-entry.mjs — used by that branch's release.yml to update just its own entry in main's catalog.json on release, without touching any other plugin's entry.

Why branches instead of one monorepo tree: the previous single-tree design meant one plugin's broken build failed CI/release for every plugin at once (a single npm run build chain and a single bash loop over every sidecar, set -e'd together), and every release stamped every plugin with the same repo-wide tag whether or not it had actually changed. Splitting each plugin onto its own branch with its own CI/release/tag gives each one real fault isolation and independent versioning — and a real place to attach separate push permissions later, if that's ever needed, since GitHub branch protection is per-branch.

To pull skeleton updates (a shared/ fix, a toolchain bump) into a plugin branch: git fetch origin main && git merge origin/main on that plugin branch, same as merging any upstream change.

Styling — the host owns the stylesheet

A plugin bundle is JavaScript only: scripts/makePluginConfig.mjs builds one ESM file and emits no CSS. Everything on screen is styled by the host app's already-compiled Tailwind sheet, which is generated by scanning the host's own src/** (tailwind.config.js → content). That never includes this repo — a plugin is fetched at runtime from a URL, long after the host was built.

So a class written only in a plugin lands in the DOM with no rule behind it and is silently a no-op. This is not theoretical: it had broken all four plugins at once. bottom-3, pl-1.5, py-5, h-64, m-4, mx-5, min-h-16, min-h-20, -mt-2.5, align-top and translate-x-full all had no rule — while max-h-64, sitting beside h-64 in the same file, did. The container log dialog lost h-[68vh], fell back to height: auto, grew with every line that streamed in and ran off both ends of the screen.

Write sizing and layout values as an inline style. No build step can drop one, and it works on whatever host version the user already has.

Two guards, and they cover different halves:

  • plugins/<id>/ui/hostCssClasses.test.ts — scaffolded into every new plugin by create-plugin.mjs and run by npm test. Catches arbitrary-value sizing and layout classes, which no host-side safelist can ever cover. Offline.

  • scripts/check-host-classes.mjs — the complete check. Resolves every class a plugin writes against a real host stylesheet, which is the only ground truth:

    # in the host checkout
    npm run build
    # back here
    node scripts/check-host-classes.mjs ../developer-desktop-utils/dist/assets/*.css

    Run it against the oldest host release you still support — a plugin installs into whatever host the user already has.

    Two host-side files decide what resolves, and neither is a guarantee:

    • externalPluginClassnamesSafelist.ts — a snapshot of plugin class names extracted once, when these tools left that repo. Its own comment says it has "no automated re-sync", and that is the whole story behind the breakage above: the classes plugin code added after that snapshot are exactly the ones with no rule. max-h-64 was in it and h-64 was not.
    • plugin-utilities.css — enumerates the common scales rather than actual usage, so it needs no plugin checkout and cannot go stale. It cannot cover arbitrary values, which is why the guard above gates those.

The same reasoning is why shared/components/ui/code-editor-base.tsx takes a style prop: a plugin had no other way to set an editor's height that survives, since tailwind-merge cannot override a rule that was never emitted.

Adding a new plugin

From main, with a clean working tree:

node scripts/create-plugin.mjs                    # interactive — prompts for whatever's missing
# or, non-interactive:
node scripts/create-plugin.mjs <new-id> --label "Label" --description "..." [--sidecar]

This creates app/<new-id>/main, scaffolds plugins/<new-id>/ (ui/, entry.tsx, manifest templates, the CSS guard described above, sidecar/ if requested), points package.json/tsconfig.json/vitest.config.ts/ci.yml/release.yml at it, runs the same verify steps CI runs (reported, not blocking — a fresh scaffold has a placeholder UI by design), and adds this plugin's dependabot.yml entries on main (Dependabot only ever reads that file from the repo's default branch, using each entry's target-branch to reach the others). Both commits (new branch + main) stay local — nothing is pushed automatically.

Then:

  1. Fill in the real UI (ui/*.tsx), sidecar (sidecar/src/main.rs) if any, and manifest.plugin.json's keywords/permissions/icon.
  2. Add real tests beside the scaffolded CSS guard, re-verify: npm run lint:manifests && npm run typecheck && npm test && npm run build
  3. git push -u origin main && git push -u origin app/<new-id>/main
  4. git tag <new-id>-v0.1.0 && git push origin <new-id>-v0.1.0 — triggers its first release.

Installing a plugin

In DevTool: Settings → Extensions, paste the plugin's manifest URL (from catalog.json's pluginManifestUrl), install, then do the same with serviceManifestUrl for its sidecar, and restart the app.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages