diff --git a/.gitignore b/.gitignore index c18dd8d..e296aed 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,5 @@ __pycache__/ + +# Plugin binaries are never committed to this metadata-only repository. +# Packaged extensions are hosted on SourceForge. +*.orplug diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..9a449df --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,154 @@ +# Contributing an Extension to the OpenROM Marketplace + +This repository is the **metadata-only** catalog behind the OpenROM Marketplace +(`openrom-store.pages.dev`). It holds publisher records, extension manifests, and +the static storefront. + +**Binary `.orplug` bundles are never committed here.** They are hosted on +SourceForge and referenced from your manifest by download URL and SHA-256 +checksum. `*.orplug` is git-ignored. + +Submitting an extension takes five steps. + +--- + +## 1. Publish your `.orplug` bundle to SourceForge + +1. Build your extension and package it as `-.orplug`. +2. Upload it to your SourceForge project's release files. +3. Copy the **direct download URL** for the uploaded file, for example: + + ``` + https://downloads.sourceforge.net/project//plugins/-.orplug + ``` + + Use the `downloads.sourceforge.net` form so mirrors and redirects work. + +## 2. Compute the SHA-256 checksum + +Every extension is verified against the checksum in its manifest before it is +listed. Generate it from the exact file you uploaded: + +```bash +sha256sum -.orplug # Linux +shasum -a 256 -.orplug # macOS +certutil -hashfile -.orplug SHA256 # Windows +``` + +Paste the 64-character lowercase hex digest into the manifest's `sha256` field. + +## 3. Register as a publisher + +Create `publishers/.json` (use a short, lowercase, hyphenated id — +this is the same id you will use in your manifest): + +```json +{ + "id": "your-publisher-id", + "name": "Your Display Name", + "github_username": "your-github-handle", + "github": "https://github.com/your-org", + "avatar_url": "https://github.com/your-github-handle.png", + "bio": "One sentence about you or your team.", + "is_verified": false, + "badge": null +} +``` + +> **Verification is granted by maintainers, never self-declared.** The +> authoritative list is `verified_publishers.json`, which only maintainers edit. +> Any `is_verified` / `verified` value in your publisher file or manifest is +> ignored, and claiming verification in a manifest fails validation. + +## 4. Add your extension manifest + +Manifests live at `manifests/m////manifest.json` +— note the `m/` shard directory. The `` path segment **must** match +the manifest's `publisher_id`, or validation fails. + +```json +{ + "id": "your-plugin-id", + "name": "Your Plugin Name", + "version": "1.0.0", + "author": "Your Display Name", + "publisher_id": "your-publisher-id", + "min_core_version": "3.9.0", + "license": "MIT", + "description": "One or two sentences shown on the storefront card.", + "homepage": "https://github.com/your-org/your-plugin", + "icon": "gamepad", + "download_url": "https://downloads.sourceforge.net/project//plugins/-.orplug", + "sha256": "<64-char lowercase hex digest from step 2>", + "tags": ["nintendo", "wua"], + "extensions": [".iso", ".wud", ".wua"], + "binaries": { + "win32": "your-tool.exe", + "linux": "your-tool", + "darwin": "your-tool" + }, + "conversions": [ + { "from": "ISO", "to": "WUA", "command": "{bin} convert --in {input} --out {output}" } + ] +} +``` + +### Field reference + +| Field | Required | Notes | +| --- | --- | --- | +| `id` | yes | Lowercase, hyphenated, unique across the store | +| `name` | yes | Display name on the storefront | +| `version` | yes | Semantic version, e.g. `1.0.0` | +| `author` | yes | Human-readable author name | +| `publisher_id` | yes | Must match the `publishers/` id **and** the manifest path | +| `description` | yes | Short summary shown on the card | +| `download_url` | yes | Absolute `https://` URL to your SourceForge `.orplug` | +| `sha256` | yes | Lowercase hex digest of the uploaded bundle | +| `conversions` | yes | Non-empty list of `from` / `to` route mappings | +| `min_core_version` | no | Lowest compatible OpenROM Core version | +| `license` | no | SPDX identifier (e.g. `MIT`, `GPL-3.0`) | +| `homepage` | no | Project or source URL | +| `icon` | no | A [Lucide](https://lucide.dev) icon name, or an absolute image URL | +| `tags` | no | Search keywords | +| `extensions` | no | File extensions your tool accepts | +| `binaries` | no | Per-platform executable names inside the bundle | + +## 5. Open a pull request + +1. Fork this repository and create a branch, e.g. `add/your-plugin-id`. +2. Add your publisher file and manifest. **Do not** add any `.orplug` file — + the pull request will be rejected if binaries are included. +3. Run the checks locally: + + ```bash + python scripts/validate_plugin.py # validates every manifest + python scripts/build_catalog.py # regenerates public/api/catalog.json + python -m unittest discover tests # security tests + ``` + +4. Commit, push, and open a pull request against `main`. + +CI runs `scripts/validate_plugin.py` and `scripts/build_catalog.py` on every pull +request. On merge to `main` it regenerates `public/api/catalog.json` and Cloudflare +Pages redeploys the storefront. + +## What reviewers check + +- The `download_url` resolves to the uploaded bundle on SourceForge. +- The `sha256` matches that exact bundle. +- `publisher_id` matches both the publisher record and the manifest directory. +- `conversions` are accurate and the description is clear. +- No binaries, no secrets, no self-declared verification. + +Once merged, your extension appears in the marketplace automatically and can be +installed with: + +```bash +openrom-core --install-plugin +``` + +## Questions + +Open a [Discord](https://discord.gg/openrom) thread or a draft pull request and a +maintainer will help you get it over the line. diff --git a/README.md b/README.md index 0e4e350..0d4da8e 100644 --- a/README.md +++ b/README.md @@ -1 +1,109 @@ -# OpenROM-Plugins-Store \ No newline at end of file +# OpenROM Marketplace + +The metadata-only extension storefront for the **OpenROM** retro-gaming preservation +toolchain. This repository hosts the catalog, extension manifests, and the static +web storefront deployed to Cloudflare Pages at **openrom-store.pages.dev**. + +> **This repository never stores plugin binaries.** Packaged `.orplug` bundles live +> exclusively on SourceForge and are referenced from the catalog by URL + SHA-256. +> `*.orplug` is git-ignored and has been purged from the entire Git history. + +## Layout + +| Path | Purpose | +| --- | --- | +| `public/` | Static storefront (Cloudflare Pages build output) | +| `public/index.html` | Single-page marketplace app (Tailwind + Alpine.js) | +| `public/plugins.json` | Showcase catalog consumed by the storefront | +| `public/api/catalog.json` | Generated catalog API (built by CI) | +| `manifests/m////manifest.json` | Per-extension manifests | +| `publishers/` | Publisher records | +| `scripts/` | Validation, catalog build, and packaging tooling | +| `schema.sql` | Cloudflare D1 schema (optional dynamic backend) | + +## Extension data contract (`public/plugins.json`) + +The storefront reads a flat JSON array of extension objects: + +```json +[ + { + "id": "nkit", + "name": "NKit", + "version": "3.1.0", + "minCoreVersion": "3.9.0", + "publisher": { "id": "nanook", "name": "Nanook", "verified": true }, + "category": "Nintendo", + "description": "Lossless GameCube & Wii preservation, WUA conversion, and Redump restoration.", + "readme": "### Features\n- 1:1 Redump disc verification\n- WUA compression\n- Lossless GameCube scrubbing", + "routes": ["ISO -> WUA", "WUD -> Loadiine", "GCZ -> ISO"], + "platforms": ["Windows", "Linux", "macOS"], + "downloadUrl": "https://downloads.sourceforge.net/project/openrom-binaries/plugins/nkit-3.1.0.orplug", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "icon": "gamepad", + "downloads": 1420, + "lastUpdated": "2026-10-25", + "license": "MIT", + "tags": ["nintendo", "gamecube", "wii", "wua"], + "rating": 4.9 + } +] +``` + +`icon` is a [Lucide](https://lucide.dev) icon name; `routes` are human-readable +`INPUT -> OUTPUT` mappings rendered as visual badges in the details drawer. + +## Storefront features + +- **Header** with OpenROM Marketplace branding, live search, GitHub / Discord links, + and a **Publish an Extension** call to action. +- **Discovery**: category sidebar (All, Nintendo, PlayStation, Sega, Xbox, Utility, + Patchers), platform chips (Windows / Linux / macOS), publisher filter, and instant + client-side search across name, description, publisher, category, tags and routes. +- **Extension cards** with icon, name, verified-publisher badge, download count, + rating, category, route preview, and a 1-click *Copy CLI install* button. +- **VS Code-style details drawer** with a tabbed interface: + *Overview* (rendered Markdown), *Supported Routes* (`ISO ➔ WUA` badges), + *Installation* (CLI + manual GUI drag-and-drop), and *Manifest* (raw JSON viewer). +- **Dark theme** by default (zinc-900/950 with neon cyan/purple accents). + +## Local preview + +```bash +python3 -m http.server 12000 --directory public +# open http://localhost:12000 +``` + +## Deploy to Cloudflare Pages + +The app is 100% static. Point a Pages project at this repository with: + +- **Build output directory:** `public` +- **Build command:** *(none)* + +Or deploy directly: + +```bash +npx wrangler pages deploy public --project-name openrom-store +``` + +Security headers and legacy redirects are declared in `public/_headers` and +`public/_redirects`. + +## Publishing an extension + +1. Add `manifests/m////manifest.json`. +2. Ensure a matching record exists at `publishers/.json`. +3. Open a pull request. CI runs `scripts/validate_plugin.py` and + `scripts/build_catalog.py`, then commits the regenerated `public/api/catalog.json`. + +Manifests may not self-declare verification, and the manifest's `publisher_id` +must match its directory path — see `tests/test_security.py`. + +## Tooling + +```bash +python scripts/validate_plugin.py # validate manifests +python scripts/build_catalog.py # regenerate public/api/catalog.json +python -m unittest discover tests # run the security test suite +``` diff --git a/packages/nintendo-nkit.orplug b/packages/nintendo-nkit.orplug deleted file mode 100644 index e91d0b8..0000000 Binary files a/packages/nintendo-nkit.orplug and /dev/null differ diff --git a/packages/saturn-ssp.orplug b/packages/saturn-ssp.orplug deleted file mode 100644 index e805a9e..0000000 Binary files a/packages/saturn-ssp.orplug and /dev/null differ diff --git a/packages/xbox360-tools.orplug b/packages/xbox360-tools.orplug deleted file mode 100644 index d19aa34..0000000 Binary files a/packages/xbox360-tools.orplug and /dev/null differ diff --git a/public/_headers b/public/_headers new file mode 100644 index 0000000..94b3a98 --- /dev/null +++ b/public/_headers @@ -0,0 +1,19 @@ +# Cloudflare Pages headers for the OpenROM Marketplace (openrom-store.pages.dev) + +/* + X-Content-Type-Options: nosniff + X-Frame-Options: SAMEORIGIN + Referrer-Policy: strict-origin-when-cross-origin + Permissions-Policy: geolocation=(), microphone=(), camera=(), interest-cohort=() + Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.tailwindcss.com https://cdn.jsdelivr.net https://unpkg.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self' + +/index.html + Cache-Control: public, max-age=0, must-revalidate + +/plugins.json + Content-Type: application/json; charset=utf-8 + Cache-Control: public, max-age=300 + +/api/* + Content-Type: application/json; charset=utf-8 + Cache-Control: public, max-age=300 diff --git a/public/_redirects b/public/_redirects new file mode 100644 index 0000000..e9571c8 --- /dev/null +++ b/public/_redirects @@ -0,0 +1,5 @@ +# The storefront is now a single-page app; legacy deep links resolve to it. +/plugin.html /index.html 301 +/publisher.html /index.html 301 +/plugins /index.html 301 +/store /index.html 301 diff --git a/public/api/catalog.json b/public/api/catalog.json index 81a31c2..975cc6a 100644 --- a/public/api/catalog.json +++ b/public/api/catalog.json @@ -1,6 +1,6 @@ { "version": "1.0", - "generated_at": "2026-10-17T15:35:08.536769+00:00", + "generated_at": "2026-11-02T01:26:28.684907+00:00", "total_plugins": 3, "plugins": [ { diff --git a/public/index.html b/public/index.html index 6b03f53..d207436 100644 --- a/public/index.html +++ b/public/index.html @@ -1,238 +1,697 @@ - + - OpenROM Plugin Store - + OpenROM Marketplace — Extensions for Retro Preservation + + + + + + + + + - -
-