cat / 012
clone

a spreadsheet for the terminal: lotus 1-2-3 looks, google sheets keys and formulas, one go binary

Docs site: redirects keep old addresses; doccheck asks for them

Dami committed 0aeb75d
/docs/terminal/nushell/ went 404 when the page was split into the
nushell folder: a static site has nothing at an address whose page
moved. @docusaurus/plugin-client-redirects (3.10.2, pinned) writes a
page at each address listed in website/redirects.json that sends the
browser on, and fails the build on a redirect to no page; this one goes
to /docs/nushell/notebooks/. doccheck now lists every docs page the site
has had since its first commit (from git's history) and fails when one
is gone without a redirect, so a future move can't break old links
silently. docs/contributing/site.md says how.
commit
0aeb75d34c87d8b00619fb654a9a0de47432763b
parent
80df9677424d34d857ff143dbe7dcf58bae45ad5
author
Dami <dami.etoile@gmail.com>
kind
feat risk score 1.1 of 3, Jev's estimate
8 files changed, +179 -3
Mdocs/contributing/site.md18+++++
Mdocs/contributing/testing.md6+-
Mscripts/doccheck/main.go5+-
Ascripts/doccheck/redirects.go116++++++++++++++++++++++++++++++
Mwebsite/docusaurus.config.ts8++
Mwebsite/package-lock.json25++++++
Mwebsite/package.json1+
Awebsite/redirects.json3+
M docs/contributing/site.md +18 -0
M docs/contributing/testing.md +4 -2
M scripts/doccheck/main.go +4 -1
A scripts/doccheck/redirects.go +116 -0
@@ -0,0 +1,116 @@
1+package main2+3+import (4+ "encoding/json"5+ "fmt"6+ "os"7+ "os/exec"8+ "path"9+ "slices"10+ "strings"11+)12+13+// The docs site keeps its old addresses: a page that moves or goes14+// leaves a redirect in website/redirects.json, from its old address to15+// where its reader goes now. The site's build checks that each redirect16+// leads to a page; checkRedirects checks that none is missing, by17+// finding every docs page the site has had since it began (in git's18+// history) that isn't a page any more.19+20+// siteConfig is the file whose first commit began the site.21+const siteConfig = "website/docusaurus.config.ts"22+23+// redirectsFile lists the site's redirects.24+const redirectsFile = "website/redirects.json"25+26+// checkRedirects reports each docs page the site has published that is27+// gone without a redirect from its address. Without git or the history28+// back to the site's beginning (a shallow clone), there is nothing to29+// compare with, and it checks nothing.30+func checkRedirects(docs map[string]*doc) []string {31+ published, ok := publishedPages()32+ if !ok {33+ return nil34+ }35+ b, err := os.ReadFile(redirectsFile)36+ if err != nil {37+ return []string{redirectsFile + ": " + err.Error()}38+ }39+ var redirects []struct{ From, To string }40+ if err := json.Unmarshal(b, &redirects); err != nil {41+ return []string{redirectsFile + ": " + err.Error()}42+ }43+ from := map[string]bool{}44+ for _, r := range redirects {45+ from[r.From] = true46+ }47+ var problems []string48+ for _, p := range published {49+ if _, still := docs[p]; still {50+ continue51+ }52+ if url := pageURL(p); !from[url] {53+ problems = append(problems, fmt.Sprintf("%s: gone, but the site has published it at %s: add a redirect from there in %s", p, url, redirectsFile))54+ }55+ }56+ return problems57+}58+59+// publishedPages lists the docs pages that existed when the site began60+// or were added, changed or deleted since, sorted.61+func publishedPages() ([]string, bool) {62+ adds, err := gitLines("log", "--diff-filter=A", "--format=%H", "--", siteConfig)63+ if err != nil || len(adds) == 0 {64+ return nil, false65+ }66+ first := adds[len(adds)-1]67+ then, err := gitLines("ls-tree", "-r", "--name-only", first, "--", "docs")68+ if err != nil {69+ return nil, false70+ }71+ since, err := gitLines("log", "--no-renames", "--format=", "--name-only", first+"..HEAD", "--", "docs")72+ if err != nil {73+ return nil, false74+ }75+ var pages []string76+ for _, p := range append(then, since...) {77+ if isPage(p) {78+ pages = append(pages, p)79+ }80+ }81+ slices.Sort(pages)82+ return slices.Compact(pages), true83+}84+85+func gitLines(args ...string) ([]string, error) {86+ out, err := exec.Command("git", args...).Output()87+ if err != nil {88+ return nil, err89+ }90+ return strings.Fields(string(out)), nil91+}92+93+// isPage reports whether p, a path under docs/, is a page of the site:94+// Markdown not excluded by a leading _ (see the docs preset's exclude).95+func isPage(p string) bool {96+ if path.Ext(p) != ".md" {97+ return false98+ }99+ for _, part := range strings.Split(p, "/") {100+ if strings.HasPrefix(part, "_") {101+ return false102+ }103+ }104+ return true105+}106+107+// pageURL is where the site serves the page at p: its path without108+// .md and with a trailing slash, a folder's README.md at the folder's109+// address.110+func pageURL(p string) string {111+ p = strings.TrimSuffix(p, ".md")112+ if base := path.Base(p); base == "README" {113+ p = path.Dir(p)114+ }115+ return "/" + p + "/"116+}
M website/docusaurus.config.ts +8 -0
M website/package-lock.json +25 -0
M website/package.json +1 -0
A website/redirects.json +3 -0