diff --git a/.github/workflows/swarm-upload.yaml b/.github/workflows/swarm-upload.yaml
new file mode 100644
index 00000000..478efd0b
--- /dev/null
+++ b/.github/workflows/swarm-upload.yaml
@@ -0,0 +1,67 @@
+name: Swarm Upload
+
+on:
+ push:
+ branches:
+ - 'master'
+
+jobs:
+ deploy:
+ # GitHub-hosted is sufficient: the bee endpoint is publicly reachable and
+ # gated by PRIVATE_API_TOKEN, so this does not need the self-hosted runners
+ # (and a public repo should not be granted access to them).
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ fetch-depth: 0
+
+ - uses: actions/setup-node@v6
+ with:
+ node-version: '24'
+ # No-op for caching here (package.json declares no packageManager /
+ # devEngines field), but it also skips setup-node's `npm --version`
+ # probe, which is known to hang on the self-hosted runners if this
+ # job is ever moved there.
+ package-manager-cache: false
+
+ - name: Build
+ run: |
+ npm ci
+ npm run build
+
+ - name: Upload to Swarm
+ uses: ethersphere/swarm-actions/upload-dir@latest
+ id: upload
+ with:
+ dir: ./build
+ index-document: index.html
+ postage-batch-id: ${{ secrets.PRIVATE_POSTAGE_BATCH_ID }}
+ bee-url: ${{ secrets.PRIVATE_BEE_URL }}
+ timeout: 300000
+ deferred: false
+ headers: |
+ authorization: ${{ secrets.PRIVATE_API_TOKEN }}
+
+ - name: Setup feed
+ uses: ethersphere/swarm-actions/write-feed@latest
+ id: feed
+ with:
+ reference: ${{ steps.upload.outputs.reference }}
+ topic: "swarm-docs"
+ postage-batch-id: ${{ secrets.PRIVATE_POSTAGE_BATCH_ID }}
+ bee-url: ${{ secrets.PRIVATE_BEE_URL }}
+ signer: ${{ secrets.PRIVATE_SIGNER }}
+ headers: |
+ authorization: ${{ secrets.PRIVATE_API_TOKEN }}
+
+ - uses: ethersphere/swarm-actions/reference-to-cid@v0
+ id: cid
+ with:
+ reference: ${{ steps.feed.outputs.manifest }}
+
+ - run: |
+ echo 'Chunk Reference: ${{ steps.upload.outputs.reference }}'
+ echo 'Feed Reference: ${{ steps.feed.outputs.reference }}'
+ echo 'Feed Manifest: ${{ steps.feed.outputs.manifest }}'
+ echo 'Feed Bzz.link: https://${{ steps.cid.outputs.cid }}.bzz.link'
diff --git a/scripts/fetch-awesome-swarm.mjs b/scripts/fetch-awesome-swarm.mjs
index fdf58f34..cacc5f6d 100644
--- a/scripts/fetch-awesome-swarm.mjs
+++ b/scripts/fetch-awesome-swarm.mjs
@@ -5,12 +5,12 @@ import { mkdir, writeFile } from 'node:fs/promises';
import { resolve, dirname } from 'node:path';
const SOURCE_URL =
- 'https://raw.githubusercontent.com/ethersphere/awesome-swarm/refs/heads/master/README.md';
+ 'https://raw.githubusercontent.com/ethersphere/awesome-swarm/refs/heads/main/README.md';
const OUT_PATH = resolve(process.cwd(), 'docs/references/awesome-list.mdx');
-const REPO_HTTP_BASE = 'https://github.com/ethersphere/awesome-swarm/blob/master/';
-const RAW_HTTP_BASE = 'https://raw.githubusercontent.com/ethersphere/awesome-swarm/master/';
+const REPO_HTTP_BASE = 'https://github.com/ethersphere/awesome-swarm/blob/main/';
+const RAW_HTTP_BASE = 'https://raw.githubusercontent.com/ethersphere/awesome-swarm/main/';
function normalizeNewlines(str) {
return str.replace(/\r\n/g, '\n');
@@ -30,6 +30,30 @@ function rewriteRelativeLinks(md) {
return md;
}
+// MDX (Docusaurus) requires void HTML elements to be self-closed. The upstream
+// README embeds a raw
inside an ; left as-is the build fails with
+// "end-tag-mismatch".
+function selfCloseVoidTags(md) {
+ return md.replace(
+ /<(img|br|hr|input|meta|source)\b([^>]*?)\s*\/?>/gi,
+ (_m, tag, attrs) => `<${tag}${attrs.replace(/\s+$/, '')} />`
+ );
+}
+
+// Relative paths inside raw HTML attributes are not covered by the markdown
+// link rewriting above, so they would 404 once rendered on the docs site.
+function rewriteHtmlAssetPaths(md) {
+ md = md.replace(
+ /(<[^>]*\ssrc=")(?!https?:|data:|#|\/)([^"]+)(")/gi,
+ (_m, pre, path, post) => `${pre}${RAW_HTTP_BASE}${path}${post}`
+ );
+ md = md.replace(
+ /(<[^>]*\shref=")(?!https?:|mailto:|#|\/)([^"]+)(")/gi,
+ (_m, pre, path, post) => `${pre}${REPO_HTTP_BASE}${path}${post}`
+ );
+ return md;
+}
+
async function main() {
const res = await fetch(SOURCE_URL);
if (!res.ok) throw new Error(`Fetch failed: ${res.status} ${res.statusText}`);
@@ -39,7 +63,7 @@ async function main() {
// Optional: strip upstream H1 to avoid a second big title under our frontmatter title
md = md.replace(/^# .*\n+/, '');
- md = rewriteRelativeLinks(md);
+ md = selfCloseVoidTags(rewriteHtmlAssetPaths(rewriteRelativeLinks(md)));
const header = `---
title: Awesome Swarm