bc2f310e73
* feat(plugins): add plugin marketplace with curated registry and updates Discover and install plugins from inside kandev instead of only by URL/upload. - Backend marketplace subpackage: SQLite-backed sources, catalog fetch/merge (dedup, install_state, degraded-source handling), and the /api/plugins/ marketplace[/sources] API. Install reuses the existing install-by-URL pipeline. - Frontend: Settings > Plugins gains a Browse tab (search/category/star sort), source management, one-click install, and an Update button on the Installed tab when the catalog has a newer version. Plugins may ship an icon (manifest `icon` -> catalog icon_url) with a letter-tile fallback. - Registry infra: plugin-registry/ (curated plugins.yaml + schema.json) and two GitHub Actions (zero-dependency Node build-index.mjs -> index.json on Pages, daily star refresh). Ships empty at launch; the empty catalog is valid. - Spec: docs/specs/plugins/marketplace.md. No download telemetry; stars only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(plugins): address marketplace PR review feedback - CodeQL: stub fetch routers keyed off a path segment, not a hostname substring. - Nullable stars end to end: a failed star lookup stays unknown (not 0) and sorts last, instead of decoding null -> 0 and corrupting ranking. - Fetch marketplace sources concurrently and dedupe identical in-flight downloads (singleflight), so a slow source can't serialize the Browse tab. - Register /marketplace routes before the /:id wildcard (gin tree ordering). - Duplicate source URL returns 409 ErrDuplicateSource, not a raw SQL error. - Browse: filter category client-side so the category dropdown never collapses. - marketplaceInstall swallows after toasting (no unhandled promise rejection). - Source badge keys off source_id, not a name substring. - build-index: read block-sequence manifest categories, not just inline. - Registry index workflow validates on pull_request; Pages deploy gated to push/schedule/dispatch. Spec: align package_sha256 (advisory in v1) + sort enum. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(plugins): guard catalog repo_url against javascript: href injection repo_url comes from an untrusted index.json, so a malicious/compromised source could set a javascript: URL that runs in the operator's session when the Repo link is clicked (rel/target don't block it). Only render the anchor for http(s) schemes. Adds regression tests for the scheme guard and null-star rendering. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(plugins): require https for remote sources; test oversized download - Reject remote plain-http marketplace sources (MITM catalog-poisoning risk); loopback http stays allowed for local dev/e2e fixtures. - Add the missing oversized-response assertion to TestDownloadRejectsNon200AndOversized so the maxIndexBytes memory bound has coverage. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(plugins): detach source download from singleflight leader's context Two concurrent Browse opens after a cache Refresh share one singleflight download; if the leader's request context is canceled (it navigates away), the waiters must not inherit that cancellation. Use context.WithoutCancel so values are preserved but the leader can't cancel everyone; the client's 20s timeout still bounds the download. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Kandev Agent <agent@kandev.dev> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
92 lines
3.2 KiB
YAML
92 lines
3.2 KiB
YAML
name: Plugin Registry Index
|
|
|
|
# Builds the marketplace catalog (plugin-registry/index.json) from the curated
|
|
# plugins.yaml pointer list and publishes it to GitHub Pages. This is the single
|
|
# source of the built catalog: its daily schedule also serves as the star-count
|
|
# refresh (see plugin-registry-stars.yml for a manual "refresh now" trigger).
|
|
|
|
on:
|
|
# Rebuild whenever the curated list or the build tooling changes.
|
|
push:
|
|
branches: [main]
|
|
paths:
|
|
- "plugin-registry/**"
|
|
- ".github/workflows/plugin-registry-index.yml"
|
|
# Validate registry edits before merge (build + test run; the Pages deploy
|
|
# steps below are gated to non-PR events, so a PR never deploys).
|
|
pull_request:
|
|
paths:
|
|
- "plugin-registry/**"
|
|
- ".github/workflows/plugin-registry-index.yml"
|
|
# Manual rebuild.
|
|
workflow_dispatch:
|
|
# Allow a sibling workflow (star refresh) to invoke this build.
|
|
workflow_call:
|
|
# Daily rebuild — keeps star counts and latest versions fresh without commits.
|
|
schedule:
|
|
- cron: "0 6 * * *"
|
|
|
|
# Least-privilege for a Pages deploy: read source, write Pages, mint the OIDC
|
|
# token deploy-pages needs.
|
|
permissions:
|
|
contents: read
|
|
pages: write
|
|
id-token: write
|
|
|
|
# Only one concurrent Pages deployment; let an in-progress deploy finish.
|
|
concurrency:
|
|
group: plugin-registry-pages
|
|
cancel-in-progress: false
|
|
|
|
jobs:
|
|
build-and-deploy:
|
|
name: Build index.json and deploy to Pages
|
|
runs-on: ubuntu-latest
|
|
environment:
|
|
name: github-pages
|
|
url: ${{ steps.deploy.outputs.page_url }}
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Set up Node
|
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
|
with:
|
|
node-version: "22"
|
|
|
|
- name: Test build script
|
|
run: node --test plugin-registry/build-index.test.mjs
|
|
|
|
- name: Build index.json
|
|
# Zero-dependency Node script (stdlib + global fetch); no install step.
|
|
# GITHUB_TOKEN authenticates the GitHub API calls. It reads the public
|
|
# plugin repos fine here; at larger scale, reading arbitrary external
|
|
# repos' stars reliably may want a PAT with `public_repo` scope set as
|
|
# this secret instead.
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
run: node plugin-registry/build-index.mjs
|
|
|
|
- name: Stage Pages site
|
|
# Publish index.json under /plugins/index.json on the Pages host.
|
|
run: |
|
|
mkdir -p _site/plugins
|
|
cp plugin-registry/index.json _site/plugins/index.json
|
|
|
|
- name: Configure Pages
|
|
if: github.event_name != 'pull_request'
|
|
uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5
|
|
|
|
- name: Upload Pages artifact
|
|
if: github.event_name != 'pull_request'
|
|
uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3
|
|
with:
|
|
path: _site
|
|
|
|
- name: Deploy to GitHub Pages
|
|
id: deploy
|
|
if: github.event_name != 'pull_request'
|
|
uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4
|