diff --git a/.github/workflows/build-index.yml b/.github/workflows/build-index.yml index a89d959..061747b 100644 --- a/.github/workflows/build-index.yml +++ b/.github/workflows/build-index.yml @@ -29,6 +29,8 @@ jobs: - name: Generate site run: go run generate-index.go + env: + ISSUES_TOKEN: ${{ secrets.ISSUES_TOKEN }} - uses: actions/upload-pages-artifact@v3 with: diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..45ddf0a --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +site/ diff --git a/generate-index.go b/generate-index.go index 5492b64..eefad1c 100644 --- a/generate-index.go +++ b/generate-index.go @@ -64,6 +64,11 @@ func main() { os.Exit(1) } + if err := generateSkeleton(); err != nil { + fmt.Fprintf(os.Stderr, "Error generating skeleton: %v\n", err) + os.Exit(1) + } + // Copy CNAME and static root files to site directory if cname, err := os.ReadFile("CNAME"); err == nil { os.WriteFile("site/CNAME", cname, 0644) @@ -86,6 +91,13 @@ func generate404() error { `), 0644) } +func generateSkeleton() error { + if err := os.MkdirAll("site/generating", 0755); err != nil { + return err + } + return os.WriteFile("site/generating/index.html", []byte(skeletonTemplate), 0644) +} + func generateSitemap(cfg Config) error { var b strings.Builder b.WriteString(`` + "\n") @@ -99,6 +111,11 @@ func generateSitemap(cfg Config) error { return os.WriteFile("site/sitemap.xml", []byte(b.String()), 0644) } +type PageData struct { + Config + Token string +} + func generateIndex(cfg Config) error { tmpl, err := template.New("index").Funcs(template.FuncMap{ "escape": html.EscapeString, @@ -133,7 +150,7 @@ func generateIndex(cfg Config) error { } defer f.Close() - return tmpl.Execute(f, cfg) + return tmpl.Execute(f, PageData{Config: cfg, Token: os.Getenv("ISSUES_TOKEN")}) } const indexTemplate = ` @@ -519,7 +536,8 @@ a:focus-visible { outline: 2px solid var(--accent-light); outline-offset: 2px; b var feedback = document.getElementById('submit-feedback'); var noResultsRequest = document.getElementById('no-results-request'); - var API_URL = '/api/request'; + var GH_TOKEN = '{{.Token}}'; + var GH_REPO = 'supermodeltools/supermodeltools.github.io'; // --- Search --- searchInput.addEventListener('input', function() { @@ -573,6 +591,11 @@ a:focus-visible { outline: 2px solid var(--accent-light); outline-offset: 2px; b var parsed = parseRepo(submitInput.value); if (!parsed) return; + if (!GH_TOKEN) { + showFeedback('Generate is not configured yet.', 'error'); + return; + } + var repoUrl = 'https://github.com/' + parsed; var name = parsed.split('/')[1]; @@ -582,22 +605,30 @@ a:focus-visible { outline: 2px solid var(--accent-light); outline-offset: 2px; b showFeedback('Setting up ' + name + '...', 'preview'); try { - var resp = await fetch(API_URL, { + var resp = await fetch('https://api.github.com/repos/' + GH_REPO + '/issues', { method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ url: repoUrl }), + headers: { + 'Authorization': 'Bearer ' + GH_TOKEN, + 'Accept': 'application/vnd.github+json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + title: '[Repo Request] ' + name, + body: '### Repository URL\n\n' + repoUrl, + labels: ['repo-request'], + }), }); - var data = await resp.json(); - if (!resp.ok || !data.success) { - showFeedback(data.error || 'Something went wrong. Please try again.', 'error'); + if (!resp.ok) { + var err = await resp.json().catch(function() { return {}; }); + showFeedback(err.message || 'Something went wrong. Please try again.', 'error'); submitBtn.classList.remove('loading'); submitBtn.textContent = 'Generate'; return; } - // Redirect to the skeleton loading page — served by the worker - window.location.href = data.generating_url; + // Redirect to the skeleton loading page + window.location.href = '/generating/?repo=' + encodeURIComponent(name); } catch (e) { showFeedback('Network error. Please try again.', 'error'); submitBtn.classList.remove('loading'); @@ -624,3 +655,168 @@ a:focus-visible { outline: 2px solid var(--accent-light); outline-offset: 2px; b ` +const skeletonTemplate = ` + + + + + Generating — Architecture Documentation + + + + + + + +
+
+
+

+
+ View on GitHub + Star + Fork +
+

Architecture documentation generated from code analysis. Explore every file, function, class, and domain.

+
+
Total Entities
+
Node Types
+
Languages
+
Domains
+
Subdomains
+
Top Directories
+
+
+

Architecture Overview

+

Codebase Composition

+

Node Types

+
+
+
+
+
+
+
+

Domains

+
+
+
+
+
+

Languages

+
+
+
+
+
+
+ +
+
+
Generating docs — forking repository…
+
+ + + +` + diff --git a/worker/index.js b/worker/index.js deleted file mode 100644 index 030b807..0000000 --- a/worker/index.js +++ /dev/null @@ -1,428 +0,0 @@ -/** - * Cloudflare Worker — repo request proxy + loading page - * - * POST /api/request — creates GitHub issue, returns docs URL - * GET /generating/* — serves skeleton loading page - * - * Environment secrets (set via wrangler secret put): - * GITHUB_TOKEN — fine-grained PAT with issues:write on supermodeltools.github.io - */ - -const CORS_HEADERS = { - 'Access-Control-Allow-Origin': 'https://repos.supermodeltools.com', - 'Access-Control-Allow-Methods': 'POST, OPTIONS', - 'Access-Control-Allow-Headers': 'Content-Type', -}; - -const REPO_RE = /github\.com\/([a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+)/; - -export default { - async fetch(request, env) { - const url = new URL(request.url); - - // CORS preflight - if (request.method === 'OPTIONS') { - return new Response(null, { status: 204, headers: CORS_HEADERS }); - } - - // POST /api/request — create issue - if (url.pathname === '/api/request' && request.method === 'POST') { - return handleRequest(request, env); - } - - // GET /generating/{name} — serve loading page - const genMatch = url.pathname.match(/^\/generating\/([a-zA-Z0-9._-]+)/); - if (genMatch && request.method === 'GET') { - return serveSkeleton(genMatch[1]); - } - - return new Response('Not found', { status: 404 }); - }, -}; - -async function handleRequest(request, env) { - let body; - try { - body = await request.json(); - } catch { - return jsonResponse({ error: 'Invalid JSON' }, 400); - } - - const rawUrl = (body.url || '').trim().replace(/\/+$/, '').replace(/\.git$/, ''); - const match = rawUrl.match(REPO_RE); - if (!match) { - return jsonResponse({ error: 'Invalid GitHub repository URL' }, 400); - } - - const upstream = match[1]; - const name = upstream.split('/')[1]; - - const ghResponse = await fetch( - 'https://api.github.com/repos/supermodeltools/supermodeltools.github.io/issues', - { - method: 'POST', - headers: { - Authorization: `Bearer ${env.GITHUB_TOKEN}`, - Accept: 'application/vnd.github+json', - 'User-Agent': 'supermodel-request-bot', - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - title: `[Repo Request] ${name}`, - body: `### Repository URL\n\nhttps://github.com/${upstream}`, - labels: ['repo-request'], - }), - } - ); - - if (!ghResponse.ok) { - console.error('GitHub API error:', ghResponse.status, await ghResponse.text()); - return jsonResponse({ error: 'Failed to submit. Please try again.' }, 502); - } - - return jsonResponse({ - success: true, - name, - upstream, - generating_url: `/generating/${name}`, - docs_url: `https://repos.supermodeltools.com/${name}/`, - }); -} - -function serveSkeleton(name) { - const html = SKELETON_HTML.replaceAll('{{NAME}}', escapeHtml(name)); - return new Response(html, { - status: 200, - headers: { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store' }, - }); -} - -function jsonResponse(data, status = 200) { - return new Response(JSON.stringify(data), { - status, - headers: { 'Content-Type': 'application/json', ...CORS_HEADERS }, - }); -} - -function escapeHtml(s) { - return s.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); -} - -// --------------------------------------------------------------------------- -// Skeleton page — mirrors the real arch-docs layout with shimmer placeholders -// --------------------------------------------------------------------------- -const SKELETON_HTML = ` - - - - - {{NAME}} — Architecture Documentation - - - - - - - - - - -
-
- - -
-

{{NAME}}

-
- View on GitHub - Star - Fork -
-

Architecture documentation generated from code analysis. Explore every file, function, class, and domain.

-
-
Total Entities
-
Node Types
-
Languages
-
Domains
-
Subdomains
-
Top Directories
-
-
- - -
-

Architecture Overview

-
-
- - -
-

Codebase Composition

-
-
- - -
-

Node Types

-
-
-
-
-
-
-
-
-
- -
-

Domains

-
-
-
-
-
-
-
- -
-

Languages

-
-
-
-
-
-
-
-
- - - - -
-
-
Generating docs — analyzing codebase…
-
- - - -`; diff --git a/worker/wrangler.toml b/worker/wrangler.toml deleted file mode 100644 index e3bac6d..0000000 --- a/worker/wrangler.toml +++ /dev/null @@ -1,10 +0,0 @@ -name = "repo-request" -main = "index.js" -compatibility_date = "2024-01-01" - -# Routes: intercept /api/* and /generating/* on the custom domain -# Requires the domain to be proxied through Cloudflare -# routes = [ -# { pattern = "repos.supermodeltools.com/api/*", zone_name = "supermodeltools.com" }, -# { pattern = "repos.supermodeltools.com/generating/*", zone_name = "supermodeltools.com" } -# ]