From 1397b304feb084d31cad29d302f002eb21848b46 Mon Sep 17 00:00:00 2001 From: DerekLoadout Date: Fri, 20 Mar 2026 16:11:29 +0100 Subject: [PATCH] Add TikTok and Nostr socials with stricter slug rules Extend vendor social support end-to-end across schema, sync, validation, and docs, and align CI + docs on lowercase single-hyphen slug/filename conventions. --- .github/pull_request_template.md | 2 +- .github/workflows/validate-vendor.yml | 4 +-- MAINTAINERS.md | 7 +++--- README.md | 4 ++- VENDOR_GUIDE.md | 22 ++++++++++++---- scripts/sync-to-framer.mjs | 4 +++ scripts/validate-all.mjs | 4 +-- vendors/_example.json | 4 ++- vendors/_schema.json | 36 ++++++++++++++++++++++++--- 9 files changed, 68 insertions(+), 19 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 30fdc73..7c2f7ba 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -18,7 +18,7 @@ _Replace the example values below with your real details._ | **Shop name** | Your shop name | | **Website** | https://yourshop.com | | **Region** | Your region | -| **Where to find you in the community** | Your X, Discord, Instagram, or other public link | +| **Where to find you in the community** | Your X, Discord, Instagram, TikTok, Nostr, or other public link (PR context only; JSON `social` supports only `x`, `instagram`, `youtube`, `tiktok`, `nostr`) | --- diff --git a/.github/workflows/validate-vendor.yml b/.github/workflows/validate-vendor.yml index cff4807..f7a4455 100644 --- a/.github/workflows/validate-vendor.yml +++ b/.github/workflows/validate-vendor.yml @@ -42,8 +42,8 @@ jobs: continue fi - if [[ ! "$base" =~ ^[A-Za-z0-9_-]+\.json$ ]]; then - echo "::error file=$f::Invalid vendor filename. Use only letters, numbers, hyphens, or underscores" + if [[ ! "$base" =~ ^[a-z0-9]+(-[a-z0-9]+)*\.json$ ]]; then + echo "::error file=$f::Invalid vendor filename. Use lowercase letters, numbers, and single hyphens only" ERRORS=$((ERRORS + 1)) fi done diff --git a/MAINTAINERS.md b/MAINTAINERS.md index eec0bcd..e7055db 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -61,10 +61,11 @@ This creates the Managed Collection in Framer with all fields. Only needs to be ## ➕ Adding or updating a vendor -1. Create `vendors/{slug}.json` — copy from `vendors/_example.json` +1. Create `vendors/{slug}.json` — copy from `vendors/_example.json` (`slug` uses lowercase letters, numbers, and hyphens only) 2. Add logo at `logos/{slug}.{ext}` (square 400×400px recommended, max 200 KB — png, jpg, webp) -3. Open a PR — CI validates automatically -4. Merge → sync and deploy run automatically +3. Social keys supported by schema: `x`, `instagram`, `youtube`, `tiktok`, `nostr` +4. Open a PR — CI validates automatically +5. Merge → sync and deploy run automatically --- diff --git a/README.md b/README.md index 96b4e16..c99b075 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Every report is reviewed. Every removal is transparent. ## 🤝 How It Works ``` -Vendor submits PR → Community reviews → Core team merges → Listed on site +Vendor submits PR → Community reviews → Maintainers merge → Listed on site ``` That's it. No forms, no emails, no waiting in a queue. Fork the repo, add your files, open a PR. The conversation happens right there. @@ -53,6 +53,8 @@ Each vendor is two files: For the easiest step-by-step path, follow the [Vendor Guide](VENDOR_GUIDE.md). `vendors/_example.json` is the raw template. +Supported social fields: `x`, `instagram`, `youtube`, `tiktok`, `nostr`. + --- ## 🌍 Regions diff --git a/VENDOR_GUIDE.md b/VENDOR_GUIDE.md index ab1eaf6..646a45e 100644 --- a/VENDOR_GUIDE.md +++ b/VENDOR_GUIDE.md @@ -46,7 +46,8 @@ Before you begin, make sure you have: - your website URL - your region and country - your logo file -- at least one place where the community can find you (for example X, Discord, or Instagram) +- at least one place where the community can find you (for example X, Discord, Instagram, TikTok, or Nostr) + (community contact links can be shared in PR text; JSON `social` keys are only `x`, `instagram`, `youtube`, `tiktok`, `nostr`) For your logo: - use a square image @@ -88,6 +89,11 @@ Example: Choose one short name for your shop, like `pivotal-mining`. Use that same name for your shop file, the `slug` field, and your logo file. +Slug format is strict: lowercase letters and numbers, separated by single hyphens only. + +Valid: `my-shop`, `shop2`, `my-shop-2` +Invalid: `my--shop`, `-shop`, `shop-`, `my_shop`, `My-Shop` + Paste this example, then replace the sample details with your own shop details: ```json @@ -103,7 +109,9 @@ Paste this example, then replace the sample details with your own shop details: "social": { "x": "https://x.com/yourhandle", "instagram": "", - "youtube": "" + "youtube": "", + "tiktok": "", + "nostr": "" } } ``` @@ -111,7 +119,11 @@ Paste this example, then replace the sample details with your own shop details: A few notes: - `description` is optional, but recommended - leave `active` as `true` -- for website and social links, use full HTTPS URLs or leave social fields empty: `""` +- `website` must be a full HTTPS URL (starting with `https://`) and cannot be empty +- social fields may be full HTTPS URLs or empty strings: `""` +- social fields supported by schema: `x`, `instagram`, `youtube`, `tiktok`, `nostr` +- only these social keys are allowed in JSON (do not add custom keys like `discord`) +- for `nostr`, use an HTTPS profile page URL (not a raw `npub...` or `nostr:` identifier) Valid regions: `Europe` · `North America` · `South America` · `Asia Pacific` · `Middle East` · `Africa` · `India` @@ -168,7 +180,7 @@ Then click **Create pull request**. If your PR is closed, you can fix the issues and open a new one. -These are maintainer and community review checks, not automated CI checks. +Maintainer/community trust checks are manual. File format and schema checks run automatically in CI. --- @@ -177,7 +189,7 @@ These are maintainer and community review checks, not automated CI checks. If your PR check fails, use this checklist: - **Invalid vendor filename** - Your file must be in `vendors/` and end with `.json` + Your file must be in `vendors/`, end with `.json`, and use lowercase letters and numbers separated by single hyphens only Example: `your-shop-name.json` - **Slug does not match filename** diff --git a/scripts/sync-to-framer.mjs b/scripts/sync-to-framer.mjs index 8ccdf8d..fa1be85 100644 --- a/scripts/sync-to-framer.mjs +++ b/scripts/sync-to-framer.mjs @@ -42,6 +42,8 @@ const FIELDS = [ { id: "socialX", type: "link", name: "X / Twitter" }, { id: "socialIg", type: "link", name: "Instagram" }, { id: "socialYt", type: "link", name: "YouTube" }, + { id: "socialTt", type: "link", name: "TikTok" }, + { id: "socialNostr", type: "link", name: "Nostr" }, ] // ─── Helpers ───────────────────────────────────────────────────────────────── @@ -112,6 +114,8 @@ function vendorToItem(v) { socialX: link(v.social?.x), socialIg: link(v.social?.instagram), socialYt: link(v.social?.youtube), + socialTt: link(v.social?.tiktok), + socialNostr: link(v.social?.nostr), }, } } diff --git a/scripts/validate-all.mjs b/scripts/validate-all.mjs index cf783dc..cb74949 100644 --- a/scripts/validate-all.mjs +++ b/scripts/validate-all.mjs @@ -33,8 +33,8 @@ for (const file of allVendorEntries) { continue } - if (!/^[a-zA-Z0-9_-]+\.json$/.test(file)) { - console.error(`❌ ${file} — Invalid vendor filename. Use only letters, numbers, hyphens, or underscores`) // no spaces or extra dots + if (!/^[a-z0-9]+(?:-[a-z0-9]+)*\.json$/.test(file)) { + console.error(`❌ ${file} — Invalid vendor filename. Use lowercase letters, numbers, and hyphens only`) // no spaces, underscores, uppercase, or extra dots errors++ } } diff --git a/vendors/_example.json b/vendors/_example.json index 9a33e16..3337d54 100644 --- a/vendors/_example.json +++ b/vendors/_example.json @@ -10,6 +10,8 @@ "social": { "x": "https://x.com/examplevendor", "instagram": "https://instagram.com/examplevendor", - "youtube": "" + "youtube": "", + "tiktok": "", + "nostr": "" } } diff --git a/vendors/_schema.json b/vendors/_schema.json index 0ce50c4..29dcf64 100644 --- a/vendors/_schema.json +++ b/vendors/_schema.json @@ -21,8 +21,8 @@ }, "slug": { "type": "string", - "pattern": "^[a-zA-Z0-9_-]+$", - "description": "URL-safe identifier, must match filename \u2014 letters, numbers, hyphens, underscores (e.g. \"pivotal-mining\" or \"pivotal_mining\")" + "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$", + "description": "URL-safe identifier, must match filename \u2014 lowercase letters, numbers, and hyphens only (e.g. \"pivotal-mining\")" }, "website": { "type": "string", @@ -51,8 +51,8 @@ }, "logo": { "type": "string", - "pattern": "^[a-zA-Z0-9_-]+\\.([Pp][Nn][Gg]|[Jj][Pp][Gg]|[Ww][Ee][Bb][Pp])$", - "description": "Logo filename only (e.g. 'pivotal-mining.png') \u2014 stored in the 'logos/' folder, PNG/JPG/WebP, square 400\u00d7400px recommended" + "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*\\.(png|jpg|webp)$", + "description": "Logo filename only (e.g. 'pivotal-mining.png') \u2014 stored in the 'logos/' folder, png/jpg/webp, square 400\u00d7400px recommended" }, "description": { "type": "string", @@ -108,6 +108,34 @@ "maxLength": 0 } ] + }, + "tiktok": { + "description": "Full HTTPS URL to TikTok profile, or empty string to omit", + "oneOf": [ + { + "type": "string", + "format": "uri", + "pattern": "^https://" + }, + { + "type": "string", + "maxLength": 0 + } + ] + }, + "nostr": { + "description": "Full HTTPS URL to Nostr profile page, or empty string to omit", + "oneOf": [ + { + "type": "string", + "format": "uri", + "pattern": "^https://" + }, + { + "type": "string", + "maxLength": 0 + } + ] } } }