From 497df8651ef093566e98e9a85aa8b47b44d97b36 Mon Sep 17 00:00:00 2001
From: Not Darko <93942788+darkobas2@users.noreply.github.com>
Date: Tue, 4 Aug 2026 09:33:06 +0200
Subject: [PATCH 1/4] ci: add Swarm upload workflow
Publishes the built docs to Swarm on every push to master and updates the
"swarm-docs" feed, mirroring the setup already used by ethswarm-nextjs and
ethswarm-blog-hugo.
Runs on the self-hosted runner because the upload targets an internal bee
node that GitHub-hosted runners cannot reach. Output dir is ./build
(Docusaurus) and package-manager-cache is disabled because setup-node
>=6.5.0 probes 'npm --version', which hangs on those runners.
---
.github/workflows/swarm-upload.yaml | 66 +++++++++++++++++++++++++++++
1 file changed, 66 insertions(+)
create mode 100644 .github/workflows/swarm-upload.yaml
diff --git a/.github/workflows/swarm-upload.yaml b/.github/workflows/swarm-upload.yaml
new file mode 100644
index 00000000..f5e4fd06
--- /dev/null
+++ b/.github/workflows/swarm-upload.yaml
@@ -0,0 +1,66 @@
+name: Swarm Upload
+
+on:
+ push:
+ branches:
+ - 'master'
+
+jobs:
+ deploy:
+ # Self-hosted: the upload targets an internal bee node that is not
+ # reachable from GitHub-hosted runners.
+ runs-on: [self-hosted, Linux, bee]
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ fetch-depth: 0
+
+ - uses: actions/setup-node@v6
+ with:
+ node-version: '24'
+ # setup-node >=6.5.0 probes `npm --version` to auto-detect the package
+ # manager. That probe hangs indefinitely on the self-hosted runners.
+ # package.json declares no packageManager/devEngines field, so no
+ # dependency caching is lost by disabling it.
+ 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'
From 40e8d695f25cda741b1672834ad6277a4127fff9 Mon Sep 17 00:00:00 2001
From: Not Darko <93942788+darkobas2@users.noreply.github.com>
Date: Tue, 4 Aug 2026 09:36:59 +0200
Subject: [PATCH 2/4] ci: run Swarm upload on GitHub-hosted runners
The bee endpoint is publicly reachable and gated by PRIVATE_API_TOKEN, so a
self-hosted runner is not required. Verified via devcon-swarm-exporter, which
uploads from ubuntu-latest and reaches the endpoint (its failures are HTTP 403
from a missing token, not a connectivity error).
Avoids granting a public repository access to the private runner group.
---
.github/workflows/swarm-upload.yaml | 15 ++++++++-------
1 file changed, 8 insertions(+), 7 deletions(-)
diff --git a/.github/workflows/swarm-upload.yaml b/.github/workflows/swarm-upload.yaml
index f5e4fd06..478efd0b 100644
--- a/.github/workflows/swarm-upload.yaml
+++ b/.github/workflows/swarm-upload.yaml
@@ -7,9 +7,10 @@ on:
jobs:
deploy:
- # Self-hosted: the upload targets an internal bee node that is not
- # reachable from GitHub-hosted runners.
- runs-on: [self-hosted, Linux, bee]
+ # 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:
@@ -18,10 +19,10 @@ jobs:
- uses: actions/setup-node@v6
with:
node-version: '24'
- # setup-node >=6.5.0 probes `npm --version` to auto-detect the package
- # manager. That probe hangs indefinitely on the self-hosted runners.
- # package.json declares no packageManager/devEngines field, so no
- # dependency caching is lost by disabling it.
+ # 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
From a2dffad917fb88fdbf3140f834b5b831b6145b0b Mon Sep 17 00:00:00 2001
From: Not Darko <93942788+darkobas2@users.noreply.github.com>
Date: Tue, 4 Aug 2026 10:20:09 +0200
Subject: [PATCH 3/4] fix(prebuild): awesome-swarm renamed its default branch
to main
ethersphere/awesome-swarm no longer has a master branch, so the prebuild
fetch returned 404 and failed every build - including the gh-pages deploy,
not just PR checks.
Points SOURCE_URL, REPO_HTTP_BASE and RAW_HTTP_BASE at main.
---
scripts/fetch-awesome-swarm.mjs | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/scripts/fetch-awesome-swarm.mjs b/scripts/fetch-awesome-swarm.mjs
index fdf58f34..d6d62c52 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');
From b8dce6e12ef3582b8c8d1f5bb57f2dec4da415d5 Mon Sep 17 00:00:00 2001
From: Not Darko <93942788+darkobas2@users.noreply.github.com>
Date: Tue, 4 Aug 2026 10:24:56 +0200
Subject: [PATCH 4/4] fix(prebuild): make fetched awesome-swarm content
MDX-safe
The upstream README embeds a raw
inside an . MDX requires void
elements to be self-closed, so the build failed with end-tag-mismatch.
Also rewrites relative paths in raw HTML src/href attributes - the existing
rewriting only covered markdown link syntax, so the embedded logo would have
404'd on the rendered page.
---
scripts/fetch-awesome-swarm.mjs | 26 +++++++++++++++++++++++++-
1 file changed, 25 insertions(+), 1 deletion(-)
diff --git a/scripts/fetch-awesome-swarm.mjs b/scripts/fetch-awesome-swarm.mjs
index d6d62c52..cacc5f6d 100644
--- a/scripts/fetch-awesome-swarm.mjs
+++ b/scripts/fetch-awesome-swarm.mjs
@@ -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