Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions .github/workflows/validate-vendor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ name: ✅ Validate Vendor PR

on:
pull_request:
types: [opened, edited, synchronize, reopened, ready_for_review]
paths:
- "vendors/**"
- "vendors/_schema.json"
- "logos/**"
- "scripts/validate-all.mjs"
- "scripts/**"
- "package.json"
- "package-lock.json"
- ".github/workflows/validate-vendor.yml"

permissions:
contents: read
Expand All @@ -30,3 +33,6 @@ jobs:

- name: Run repository validator
run: npm run validate

- name: Run PR metadata validator
run: npm run validate:pr
5 changes: 3 additions & 2 deletions MAINTAINERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ Both trigger the sync on merge. Both are transparent — the change is visible i

```bash
npm install
npm run validate # validate all vendor files locally
npm run sync # manual sync (requires FRAMER_PROJECT_URL and FRAMER_API_KEY exported in your shell)
npm run validate # validate all vendor files locally
npm run validate:pr # validates PR title/body in GitHub Actions; skips locally without GITHUB_EVENT_PATH
npm run sync # manual sync (requires FRAMER_PROJECT_URL and FRAMER_API_KEY exported in your shell)
```
Binary file added logos/ustarminer.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"type": "module",
"scripts": {
"sync": "node scripts/sync-to-framer.mjs",
"validate": "node scripts/validate-all.mjs"
"validate": "node scripts/validate-all.mjs",
"validate:pr": "node scripts/validate-pr.mjs"
},
"dependencies": {
"framer-api": "0.1.1"
Expand Down
93 changes: 93 additions & 0 deletions scripts/validate-pr.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/**
* validate-pr.mjs
*
* Validates PR title/body hygiene using the GitHub pull_request event payload.
* Run in CI with: npm run validate:pr
*/

import fs from "node:fs"

const eventPath = process.env.GITHUB_EVENT_PATH

if (!eventPath) {
console.log("Skipping PR metadata validation: GITHUB_EVENT_PATH is not set")
process.exit(0)
}

let payload
try {
payload = JSON.parse(fs.readFileSync(eventPath, "utf-8").replace(/^\uFEFF/, ""))
} catch (err) {
console.error(`Failed to read GitHub event payload: ${err.message}`)
process.exit(1)
}

const pr = payload.pull_request
if (!pr) {
console.log("Skipping PR metadata validation: event is not a pull_request")
process.exit(0)
}

const title = pr.title.trim()
const body = pr.body || ""
const errors = []
const titleMatch = title.match(/^(Add vendor|Update vendor|Remove vendor|Deactivate vendor): .+/)
const prType = titleMatch?.[1]

if (!titleMatch) {
errors.push("PR title must start with one of: 'Add vendor:', 'Update vendor:', 'Remove vendor:', or 'Deactivate vendor:'")
}

const placeholders = [
"Your shop name",
"https://yourshop.com",
"Your X, Discord, Instagram, TikTok, Nostr, or other public link",
]

for (const placeholder of placeholders) {
if (body.includes(placeholder)) {
errors.push(`Replace PR template placeholder: ${placeholder}`)
}
}

if (!/\|\s*\*\*Shop name\*\*\s*\|\s*\S/.test(body)) {
errors.push("PR body must include a completed Shop name row")
}

if (!/\|\s*\*\*Website\*\*\s*\|\s*https:\/\/\S+/.test(body)) {
errors.push("PR body must include a completed HTTPS Website row")
}

if (!/\|\s*\*\*Where to find you in the community\*\*\s*\|\s*\S/.test(body)) {
errors.push("PR body must include a completed community contact row")
}

const confirmationRows = [
"I sell genuine Bitaxe hardware",
"My shop is live and active",
"I am reachable in the community",
"I use the correct name `Bitaxe` on my Bitaxe product pages",
"My Bitaxe product pages use real product photos",
"My Bitaxe product pages link to `https://bitaxe.org`",
"I do not sell Bitaxe-derived products in ways that violate Bitaxe open-source license terms",
"I am not aware of open fraud or counterfeit reports about my shop",
"I understand listings can be removed if confirmed issues arise",
]

if (prType === "Add vendor" || prType === "Update vendor") {
for (const label of confirmationRows) {
const escapedLabel = label.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
const rowPattern = new RegExp(`\\|\\s*${escapedLabel}\\s*\\|\\s*(Yes|No)\\s*\\|`, "i")
if (!rowPattern.test(body)) {
errors.push(`Vendor confirmation must be answered Yes/No: ${label}`)
}
}
}

if (errors.length > 0) {
console.error("PR metadata validation failed:")
for (const error of errors) console.error(`- ${error}`)
process.exit(1)
}

console.log("PR metadata is valid")
16 changes: 16 additions & 0 deletions vendors/ustarminer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "Ustarminer",
"slug": "ustarminer",
"website": "https://ustarminer.com",
"region": "Asia Pacific",
"country": "Hongkong",
"description": "Professional distributor of genuine Bitaxe solo miners. We are the creators of the Starminer APP, available on Google Play and the Apple App Store—the only specialized companion application of its kind in the world for managing your solo mining experience.",
"active": true,
"social": {
"x": "https://x.com/StarMiner01",
"instagram": "",
"youtube": "https://www.youtube.com/@starminer-s7o",
"tiktok": "",
"nostr": ""
}
}
Loading