Skip to content

Commit 7bedf33

Browse files
vishrclaude
andcommitted
fix(docs): address PR review + live GitHub star count
Review fixes (site/): - editLink baseUrl pointed at website/, now site/ (Edit-page links were wrong) - wrap dark-first localStorage script in try/catch (no private-mode throw/FOUC) - AskEcho: null-guard DOM lookups + early-return logging, clear pending timeout, add "Demo - not connected" label, fix stub echo-jwt v4 -> v5 - make content `description` schema-required (build-enforced; used for SEO/OG) - add target=_blank rel=noopener noreferrer to external homepage links Live GitHub stars: - fetch star count at build time (src/data/github.ts) with graceful fallback; dedupe the two hardcoded "32.4k" spots to this single source - add daily schedule cron + workflow_dispatch + authenticated GITHUB_TOKEN to the deploy workflow so the count refreshes without manual deploys Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent f9809d0 commit 7bedf33

7 files changed

Lines changed: 145 additions & 79 deletions

File tree

.github/workflows/deploy.yaml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,12 @@ on:
44
push:
55
branches:
66
- master
7-
# Review gh actions docs if you want to further define triggers, paths, etc
8-
# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#on
7+
# Daily rebuild so build-time data — e.g. the GitHub star count fetched in the
8+
# Astro site's src/data/github.ts — stays fresh without a manual deploy.
9+
schedule:
10+
- cron: '0 6 * * *'
11+
# Allow manual runs from the Actions tab.
12+
workflow_dispatch:
913

1014
jobs:
1115
deploy:
@@ -25,6 +29,9 @@ jobs:
2529
- name: Build website
2630
run: npm run build
2731
working-directory: website
32+
env:
33+
# Lifts the GitHub API rate limit for build-time stats fetches.
34+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
2835

2936
# Popular action to deploy to GitHub Pages:
3037
# Docs: https://github.com/peaceiris/actions-gh-pages#%EF%B8%8F-docusaurus

site/astro.config.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export default defineConfig({
1717
{ icon: 'github', label: 'GitHub', href: 'https://github.com/labstack/echo' },
1818
],
1919
editLink: {
20-
baseUrl: 'https://github.com/labstack/echox/edit/master/website/',
20+
baseUrl: 'https://github.com/labstack/echox/edit/master/site/',
2121
},
2222
lastUpdated: true,
2323
// Echo "E" cube mark; .ico kept as legacy fallback, apple-touch-icon added in head.
@@ -31,7 +31,7 @@ export default defineConfig({
3131
// Dark-first: default new visitors to dark unless they've chosen otherwise.
3232
{
3333
tag: 'script',
34-
content: "if(!localStorage.getItem('starlight-theme')){localStorage.setItem('starlight-theme','dark');document.documentElement.dataset.theme='dark';}",
34+
content: "try{if(!localStorage.getItem('starlight-theme')){localStorage.setItem('starlight-theme','dark');document.documentElement.dataset.theme='dark';}}catch(e){document.documentElement.dataset.theme='dark';}",
3535
},
3636
{ tag: 'link', attrs: { rel: 'preconnect', href: 'https://fonts.googleapis.com' } },
3737
{ tag: 'link', attrs: { rel: 'preconnect', href: 'https://fonts.gstatic.com', crossorigin: true } },

site/src/components/AskEcho.astro

Lines changed: 75 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
---
22
// Vanilla Astro island — no React. Fab + ⌘-style modal with a (stubbed) streamed answer.
3+
// NOTE: the answer is a hardcoded DEMO, not a live model. To connect a real provider,
4+
// replace the timer block in ask() with a fetch (see website/src/components/AskEcho.js
5+
// for a kapa.ai-style sketch).
36
const SUGGESTIONS = [
47
{ icon: 'ph-lock-key', q: 'How do I add JWT authentication?' },
58
{ icon: 'ph-globe', q: 'How do I enable CORS?' },
@@ -23,68 +26,85 @@ const SUGGESTIONS = [
2326
{SUGGESTIONS.map((s) => (
2427
<button class="ask-s" data-q={s.q} type="button"><i class={`ph ${s.icon}`}></i> {s.q}</button>
2528
))}
29+
<p class="ask-demo">Demo — answers are illustrative and not yet connected to a live model.</p>
2630
</div>
2731
</div>
2832
</div>
2933
</div>
3034

35+
<style>
36+
.ask-demo { margin: 16px 4px 2px; font-size: 0.72rem; line-height: 1.5; color: var(--sl-color-gray-4); font-family: var(--sl-font-mono); }
37+
</style>
38+
3139
<script>
32-
const fab = document.getElementById('ask-fab')!;
33-
const overlay = document.getElementById('ask-overlay')!;
34-
const input = document.getElementById('ask-input') as HTMLInputElement;
35-
const body = document.getElementById('ask-body')!;
36-
// Portal to <body> so position:fixed + z-index escape any Starlight stacking context.
37-
document.body.appendChild(fab);
38-
document.body.appendChild(overlay);
39-
const suggestHTML = body.innerHTML;
40-
let timer: ReturnType<typeof setInterval> | undefined;
40+
function initAskEcho() {
41+
const fab = document.getElementById('ask-fab');
42+
const overlay = document.getElementById('ask-overlay');
43+
const input = document.getElementById('ask-input') as HTMLInputElement | null;
44+
const body = document.getElementById('ask-body');
45+
if (!fab || !overlay || !input || !body) {
46+
console.error('[AskEcho] missing required DOM nodes; island not initialized');
47+
return;
48+
}
49+
// Portal to <body> so position:fixed + z-index escape any Starlight stacking context.
50+
document.body.appendChild(fab);
51+
document.body.appendChild(overlay);
52+
const suggestHTML = body.innerHTML;
53+
let timer: ReturnType<typeof setInterval> | undefined;
54+
let pending: ReturnType<typeof setTimeout> | undefined;
4155

42-
const ANSWER =
43-
`To add JWT authentication, use Echo's built-in <strong>JWT middleware</strong>. Register it on the routes (or group) you want to protect:\n\n<pre>import echojwt "github.com/labstack/echo-jwt/v4"\n\ne.Use(echojwt.WithConfig(echojwt.Config{\n SigningKey: []byte("your-secret"),\n}))</pre>\n\nRequests must then send <code>Authorization: Bearer &lt;token&gt;</code>. Read claims with <code>c.Get("user")</code>.`;
44-
const SOURCES = ['Guide › Middleware › JWT', 'Cookbook › JWT Authentication', 'API › echo-jwt'];
56+
// Hardcoded DEMO answer — swap the timer block in ask() for a real provider fetch.
57+
const ANSWER =
58+
`To add JWT authentication, use Echo's built-in <strong>JWT middleware</strong>. Register it on the routes (or group) you want to protect:\n\n<pre>import echojwt "github.com/labstack/echo-jwt/v5"\n\ne.Use(echojwt.WithConfig(echojwt.Config{\n SigningKey: []byte("your-secret"),\n}))</pre>\n\nRequests must then send <code>Authorization: Bearer &lt;token&gt;</code>. Read claims with <code>c.Get("user")</code>.`;
59+
const SOURCES = ['Guide › Middleware › JWT', 'Cookbook › JWT Authentication', 'API › echo-jwt'];
4560

46-
function bindSuggest() {
47-
body.querySelectorAll<HTMLButtonElement>('.ask-s').forEach((b) =>
48-
b.addEventListener('click', () => ask(b.dataset.q || ''))
49-
);
50-
}
51-
function open() {
52-
overlay.hidden = false; fab.hidden = true;
53-
body.innerHTML = suggestHTML; bindSuggest(); input.value = '';
54-
setTimeout(() => input.focus(), 40);
55-
}
56-
function close() {
57-
overlay.hidden = true; fab.hidden = false;
58-
if (timer) clearInterval(timer);
59-
}
60-
function sourcesHTML() {
61-
return '<div class="ask-sources"><h5>Sources</h5>' +
62-
SOURCES.map((s, i) => `<div class="ask-src"><span class="ask-n">${i + 1}</span> ${s}</div>`).join('') +
63-
'</div>';
64-
}
65-
function ask(q: string) {
66-
input.value = q;
67-
body.innerHTML = '<div class="ask-badge"><span class="ask-dot"></span> Ask Echo is thinking…</div><div class="ask-answer" id="ask-ans"></div>';
68-
const ans = document.getElementById('ask-ans')!;
69-
setTimeout(() => {
70-
ans.parentElement!.querySelector('.ask-badge')!.innerHTML = '<span class="ask-dot"></span> Ask Echo';
71-
let i = 0;
72-
timer = setInterval(() => {
73-
i += 5;
74-
ans.innerHTML = ANSWER.slice(0, i).replace(/\n/g, '<br/>') + '<span class="ask-cursor"></span>';
75-
if (i >= ANSWER.length) {
76-
if (timer) clearInterval(timer);
77-
ans.innerHTML = ANSWER.replace(/\n/g, '<br/>');
78-
ans.insertAdjacentHTML('beforeend', sourcesHTML());
79-
}
80-
}, 12);
81-
}, 420);
82-
}
61+
function bindSuggest() {
62+
body.querySelectorAll<HTMLButtonElement>('.ask-s').forEach((b) =>
63+
b.addEventListener('click', () => ask(b.dataset.q || ''))
64+
);
65+
}
66+
function open() {
67+
overlay.hidden = false; fab.hidden = true;
68+
body.innerHTML = suggestHTML; bindSuggest(); input.value = '';
69+
setTimeout(() => input.focus(), 40);
70+
}
71+
function close() {
72+
overlay.hidden = true; fab.hidden = false;
73+
if (timer) clearInterval(timer);
74+
if (pending) clearTimeout(pending);
75+
}
76+
function sourcesHTML() {
77+
return '<div class="ask-sources"><h5>Sources</h5>' +
78+
SOURCES.map((s, i) => `<div class="ask-src"><span class="ask-n">${i + 1}</span> ${s}</div>`).join('') +
79+
'</div>';
80+
}
81+
function ask(q: string) {
82+
input.value = q;
83+
body.innerHTML = '<div class="ask-badge"><span class="ask-dot"></span> Ask Echo is thinking…</div><div class="ask-answer" id="ask-ans"></div>';
84+
const ans = document.getElementById('ask-ans');
85+
if (!ans) return;
86+
pending = setTimeout(() => {
87+
const badge = ans.parentElement?.querySelector('.ask-badge');
88+
if (badge) badge.innerHTML = '<span class="ask-dot"></span> Ask Echo';
89+
let i = 0;
90+
timer = setInterval(() => {
91+
i += 5;
92+
ans.innerHTML = ANSWER.slice(0, i).replace(/\n/g, '<br/>') + '<span class="ask-cursor"></span>';
93+
if (i >= ANSWER.length) {
94+
if (timer) clearInterval(timer);
95+
ans.innerHTML = ANSWER.replace(/\n/g, '<br/>');
96+
ans.insertAdjacentHTML('beforeend', sourcesHTML());
97+
}
98+
}, 12);
99+
}, 420);
100+
}
83101

84-
bindSuggest();
85-
fab.addEventListener('click', open);
86-
window.addEventListener('ask-echo-open', open);
87-
overlay.addEventListener('mousedown', (e) => { if (e.target === overlay) close(); });
88-
input.addEventListener('keydown', (e) => { if (e.key === 'Enter' && input.value.trim()) ask(input.value.trim()); });
89-
window.addEventListener('keydown', (e) => { if (e.key === 'Escape') close(); });
102+
bindSuggest();
103+
fab.addEventListener('click', open);
104+
window.addEventListener('ask-echo-open', open);
105+
overlay.addEventListener('mousedown', (e) => { if (e.target === overlay) close(); });
106+
input.addEventListener('keydown', (e) => { if (e.key === 'Enter' && input.value.trim()) ask(input.value.trim()); });
107+
window.addEventListener('keydown', (e) => { if (e.key === 'Escape') close(); });
108+
}
109+
initAskEcho();
90110
</script>

site/src/components/HomeHero.astro

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Living Terminal hero: code window + a terminal pane that types `curl`
33
// and streams Echo's JSON response. Animation is client-side, with a
44
// static final-state fallback for reduced-motion / no-JS.
5+
import { starsLabel } from '../data/github.ts';
56
---
67

78
<section class="hh">
@@ -15,7 +16,7 @@
1516
</p>
1617
<div class="hh-cta">
1718
<a class="hh-btn pri" href="/guide/quickstart/">Get Started <i class="ph ph-arrow-right"></i></a>
18-
<a class="hh-btn sec" href="https://github.com/labstack/echo"><i class="ph ph-star"></i> 32.4k on GitHub</a>
19+
<a class="hh-btn sec" href="https://github.com/labstack/echo" target="_blank" rel="noopener noreferrer"><i class="ph ph-star"></i> {starsLabel} on GitHub</a>
1920
</div>
2021
<button class="hh-install" type="button" data-copy="go get github.com/labstack/echo/v5" aria-label="Copy install command">
2122
<span class="p">$</span> go get github.com/labstack/echo/v5 <i class="ph ph-copy cp"></i>

site/src/content.config.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import { defineCollection } from 'astro:content';
22
import { docsLoader } from '@astrojs/starlight/loaders';
33
import { docsSchema } from '@astrojs/starlight/schema';
4+
import { z } from 'astro:schema';
45

56
export const collections = {
6-
docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }),
7+
docs: defineCollection({
8+
loader: docsLoader(),
9+
// `description` is required (not optional as in the stock schema): every page uses it
10+
// for SEO/OG meta, so enforce it at build time rather than by convention.
11+
schema: docsSchema({ extend: z.object({ description: z.string().min(1) }) }),
12+
}),
713
};

site/src/content/docs/index.mdx

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,12 @@ tableOfContents: false
1515
---
1616

1717
import HomeHero from '../../components/HomeHero.astro';
18+
import { starsLabel } from '../../data/github.ts';
1819

1920
<HomeHero />
2021

2122
<div class="echo-stats">
22-
<div><b>32.4k</b><span>GitHub stars</span></div>
23+
<div><b>{starsLabel}</b><span>GitHub stars</span></div>
2324
<div><b>0 allocs</b><span>router, per request</span></div>
2425
<div><b>25+</b><span>built-in middlewares</span></div>
2526
<div><b>MIT</b><span>open source license</span></div>
@@ -50,27 +51,27 @@ e.Start(":1323")</pre></div>
5051
<div class="echo-h"><span class="ey">Ecosystem</span><h2>Official packages, ready to plug in.</h2></div>
5152

5253
<div class="echo-eco">
53-
<a class="eco-card" href="https://github.com/labstack/echo-jwt"><h4>echo-jwt</h4><p>JWT authentication middleware backed by golang-jwt.</p></a>
54-
<a class="eco-card" href="https://github.com/labstack/echo-contrib"><h4>echo-contrib</h4><p>Prometheus, Casbin, Jaeger, pprof, Zipkin &amp; session helpers.</p></a>
55-
<a class="eco-card" href="https://github.com/swaggo/echo-swagger"><h4>echo-swagger</h4><p>Generate and serve interactive Swagger / OpenAPI docs.</p></a>
56-
<a class="eco-card" href="https://pkg.go.dev/github.com/labstack/echo/v5"><h4>API Reference</h4><p>Full package documentation on pkg.go.dev.</p></a>
54+
<a class="eco-card" href="https://github.com/labstack/echo-jwt" target="_blank" rel="noopener noreferrer"><h4>echo-jwt</h4><p>JWT authentication middleware backed by golang-jwt.</p></a>
55+
<a class="eco-card" href="https://github.com/labstack/echo-contrib" target="_blank" rel="noopener noreferrer"><h4>echo-contrib</h4><p>Prometheus, Casbin, Jaeger, pprof, Zipkin &amp; session helpers.</p></a>
56+
<a class="eco-card" href="https://github.com/swaggo/echo-swagger" target="_blank" rel="noopener noreferrer"><h4>echo-swagger</h4><p>Generate and serve interactive Swagger / OpenAPI docs.</p></a>
57+
<a class="eco-card" href="https://pkg.go.dev/github.com/labstack/echo/v5" target="_blank" rel="noopener noreferrer"><h4>API Reference</h4><p>Full package documentation on pkg.go.dev.</p></a>
5758
</div>
5859

5960
<div class="echo-h"><span class="ey">Sponsors</span><h2>Backed by teams who build on Echo.</h2></div>
6061

61-
<a class="sp-featured" href="https://encore.dev">
62+
<a class="sp-featured" href="https://encore.dev" target="_blank" rel="noopener noreferrer">
6263
<img src="https://github.com/encoredev.png?size=88" alt="Encore" />
6364
<span class="t"><b>Encore</b><span>The platform for building Go-based cloud backends.</span></span>
6465
</a>
6566

6667
<div class="sp-grid">
67-
<a class="sp-card" href="https://github.com/customerio"><img src="https://github.com/customerio.png?size=52" alt="Customer.io" /><span>Customer.io</span></a>
68-
<a class="sp-card" href="https://github.com/gravitycarbon"><img src="https://github.com/gravitycarbon.png?size=52" alt="Gravity Carbon" /><span>Gravity Carbon</span></a>
69-
<a class="sp-card" href="https://github.com/mjslabs"><img src="https://github.com/mjslabs.png?size=52" alt="MJS Labs" /><span>MJS Labs</span></a>
70-
<a class="sp-card" href="https://github.com/Codeerror"><img src="https://github.com/Codeerror.png?size=52" alt="Codeerror" /><span>Codeerror</span></a>
68+
<a class="sp-card" href="https://github.com/customerio" target="_blank" rel="noopener noreferrer"><img src="https://github.com/customerio.png?size=52" alt="Customer.io" /><span>Customer.io</span></a>
69+
<a class="sp-card" href="https://github.com/gravitycarbon" target="_blank" rel="noopener noreferrer"><img src="https://github.com/gravitycarbon.png?size=52" alt="Gravity Carbon" /><span>Gravity Carbon</span></a>
70+
<a class="sp-card" href="https://github.com/mjslabs" target="_blank" rel="noopener noreferrer"><img src="https://github.com/mjslabs.png?size=52" alt="MJS Labs" /><span>MJS Labs</span></a>
71+
<a class="sp-card" href="https://github.com/Codeerror" target="_blank" rel="noopener noreferrer"><img src="https://github.com/Codeerror.png?size=52" alt="Codeerror" /><span>Codeerror</span></a>
7172
</div>
7273

73-
<div class="sp-cta"><a href="https://github.com/sponsors/labstack"><i class="ph ph-heart"></i> Become a sponsor</a></div>
74+
<div class="sp-cta"><a href="https://github.com/sponsors/labstack" target="_blank" rel="noopener noreferrer"><i class="ph ph-heart"></i> Become a sponsor</a></div>
7475

7576
<div class="echo-foot">
7677
<div class="brand">
@@ -86,15 +87,15 @@ e.Start(":1323")</pre></div>
8687
</div>
8788
<div class="col">
8889
<h4>Project</h4>
89-
<a href="https://github.com/labstack/echo">GitHub</a>
90-
<a href="https://github.com/labstack/echo/releases">Releases</a>
91-
<a href="https://github.com/labstack/echo/discussions">Discussions</a>
92-
<a href="https://github.com/labstack/echo/blob/master/.github/CONTRIBUTING.md">Contributing</a>
90+
<a href="https://github.com/labstack/echo" target="_blank" rel="noopener noreferrer">GitHub</a>
91+
<a href="https://github.com/labstack/echo/releases" target="_blank" rel="noopener noreferrer">Releases</a>
92+
<a href="https://github.com/labstack/echo/discussions" target="_blank" rel="noopener noreferrer">Discussions</a>
93+
<a href="https://github.com/labstack/echo/blob/master/.github/CONTRIBUTING.md" target="_blank" rel="noopener noreferrer">Contributing</a>
9394
</div>
9495
<div class="col">
9596
<h4>Resources</h4>
96-
<a href="https://pkg.go.dev/github.com/labstack/echo/v5">API Reference</a>
97-
<a href="https://github.com/sponsors/labstack">Sponsor</a>
98-
<a href="https://github.com/labstack/echo/blob/master/LICENSE">MIT License</a>
97+
<a href="https://pkg.go.dev/github.com/labstack/echo/v5" target="_blank" rel="noopener noreferrer">API Reference</a>
98+
<a href="https://github.com/sponsors/labstack" target="_blank" rel="noopener noreferrer">Sponsor</a>
99+
<a href="https://github.com/labstack/echo/blob/master/LICENSE" target="_blank" rel="noopener noreferrer">MIT License</a>
99100
</div>
100101
</div>

site/src/data/github.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Build-time GitHub stats. Fetched once during `astro build` and inlined into the
2+
// static HTML — no client JS, no runtime rate limits. The daily deploy cron
3+
// (.github/workflows/deploy.yaml) re-runs the build so the number stays fresh.
4+
// Falls back gracefully so an offline / rate-limited build never fails.
5+
const REPO = 'labstack/echo';
6+
const FALLBACK_STARS = 32400;
7+
8+
async function fetchStars(): Promise<number> {
9+
try {
10+
const headers: Record<string, string> = {
11+
Accept: 'application/vnd.github+json',
12+
'User-Agent': 'echo-docs-build',
13+
};
14+
// Optional: set GITHUB_TOKEN in CI to lift the 60 req/hr unauthenticated limit.
15+
const token = process.env.GITHUB_TOKEN;
16+
if (token) headers.Authorization = `Bearer ${token}`;
17+
18+
const res = await fetch(`https://api.github.com/repos/${REPO}`, { headers });
19+
if (!res.ok) throw new Error(`GitHub API responded ${res.status}`);
20+
const data = await res.json();
21+
return typeof data.stargazers_count === 'number' ? data.stargazers_count : FALLBACK_STARS;
22+
} catch (e) {
23+
console.warn(`[github] star fetch failed; using fallback ${FALLBACK_STARS}:`, e);
24+
return FALLBACK_STARS;
25+
}
26+
}
27+
28+
export const stars = await fetchStars();
29+
30+
/** e.g. 32412 -> "32.4k" */
31+
export const starsLabel = stars >= 1000 ? `${(stars / 1000).toFixed(1)}k` : String(stars);

0 commit comments

Comments
 (0)