mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-30 14:58:08 +00:00
The sign-in callback, initiate and logout pages, authz, auto, the
connectors page, base, the blog index and its three posts, and the Go
module page wrap their copy: server pages with <T>, client pages with
t(), and copy held in data with phrase(). Figures in the posts' prose
are values; product names, code and IAM's own codes stay as written.
Models says its two sentences whole ("{n}+ models", "every model on
the gateway"); the noun prop no caller passed is gone.
214 lines
8.0 KiB
TypeScript
214 lines
8.0 KiB
TypeScript
import type { Metadata } from 'next'
|
|
import type { ReactNode } from 'react'
|
|
import { notFound } from 'next/navigation'
|
|
import { T } from '@hanzo/i18n/react'
|
|
import { Box, Text, View } from '@/components/ui'
|
|
import { Code } from '@/components/code'
|
|
|
|
import { OWNED, UNREFERENCED, goImport, modulePathOf, repoOf } from '@/lib/go-modules'
|
|
|
|
type Params = { params: Promise<{ mod: string[] }> }
|
|
|
|
/** `--text-sm` at its own leading: the command and the facts. */
|
|
const SMALL = { fontSize: '$2', lineHeight: '1.1607rem' } as const
|
|
|
|
// The listed modules are the whole set: a name that is not one of them is not a
|
|
// module, and must fall through to the site's own not-found rather than become a
|
|
// page. In a static export an unlisted param has nowhere to be rendered anyway.
|
|
export const dynamicParams = false
|
|
|
|
export function generateStaticParams() {
|
|
// A CATCH-ALL, because a module path is not always one segment. `hanzo.ai/lz4/v4`
|
|
// carries a slash — the `/vN` suffix is part of the module path by Go's own rule —
|
|
// and `[mod]` could express only one, so Next wrote those out percent-encoded
|
|
// (`/lz4%2Fv4`), an address the go tool never asks for. They were filtered out
|
|
// rather than served, which is why every versioned module 404'd: lz4/v4,
|
|
// datastore-go/v2, gateway/v2, gomail/v2, iamsdk/v2, kv-go/v9.
|
|
//
|
|
// `[...mod]` gives each its real address. The param is the SEGMENTS; the module
|
|
// path is what they join back into.
|
|
return OWNED.map((mod) => ({ mod: mod.split('/') }))
|
|
}
|
|
|
|
export async function generateMetadata({ params }: Params): Promise<Metadata> {
|
|
const mod = (await params).mod.join('/')
|
|
return {
|
|
title: `hanzo.ai/${mod}`,
|
|
description: `The Go module hanzo.ai/${mod}, and where its source lives.`,
|
|
other: goImport(mod),
|
|
}
|
|
}
|
|
|
|
// One page per Go module, at the address the import path will be. It answers two
|
|
// callers with one artifact: the go tool reads the meta tag above, and a person
|
|
// who followed an import out of a stack trace lands somewhere that says what the
|
|
// module is and where to read it.
|
|
//
|
|
// THE COMMAND BELOW IS THE PATH THE MODULE DECLARES. Go accepts a module only
|
|
// under the path its own go.mod names, and pkg.go.dev files it there. A few
|
|
// modules declare this address (hanzo.ai/tygoja), and for those the go-import tag
|
|
// above is what `go get` reads. Most declare their GitHub path, and some a GitHub
|
|
// path other than the repo's (hanzo-go/sdk declares github.com/hanzoai/go-sdk/v8).
|
|
// Any other path fetches the repo and then fails, so modulePathOf names the
|
|
// declared one.
|
|
//
|
|
// The tag stays on every module, ready for the day a module's `module` line moves
|
|
// to this address. That move is a breaking change for every importer, which is
|
|
// why it is a decision rather than a cleanup.
|
|
export default async function ModulePage({ params }: Params) {
|
|
const mod = (await params).mod.join('/')
|
|
if (!(OWNED as readonly string[]).includes(mod)) notFound()
|
|
|
|
const repo = repoOf(mod)
|
|
const path = `hanzo.ai/${mod}`
|
|
const importPath = modulePathOf(mod)
|
|
const About = ABOUT[mod]
|
|
|
|
return (
|
|
<Box render="main" minH="100vh" bg="var(--black)" px={24} py={96} $platform-web={{ color: 'var(--white)' }}>
|
|
<Box mx="auto" maxW={672}>
|
|
<Text render="h1" display="block" fontSize="$8" lineHeight="1.95rem" fontWeight="600" color="inherit" $platform-web={{ letterSpacing: 'var(--tracking-tight)' }}>
|
|
{path}
|
|
</Text>
|
|
<Text render="p" display="block" mt={12} fontSize="$3" lineHeight="1.25rem" color="var(--muted-foreground)">
|
|
<T>{'A Go module. This address is where its documentation lives; the source is on GitHub.'}</T>
|
|
</Text>
|
|
|
|
<Sample lang="bash" code={`go get ${importPath}`} />
|
|
|
|
{/* Source and reference, one term over its value. */}
|
|
<View render="dl" display="grid" gap={16} mt={32} mb={13}>
|
|
<Fact term={<T>Source</T>}>
|
|
<Link href={repo}>{repo}</Link>
|
|
</Fact>
|
|
<Fact term={<T>Reference</T>}>
|
|
{UNREFERENCED.has(mod) ? (
|
|
<T>{'pkg.go.dev has no page for this module.'}</T>
|
|
) : (
|
|
<Link href={`https://pkg.go.dev/${importPath}`}>pkg.go.dev/{importPath}</Link>
|
|
)}
|
|
</Fact>
|
|
</View>
|
|
|
|
{About ? <About /> : null}
|
|
</Box>
|
|
</Box>
|
|
)
|
|
}
|
|
|
|
/** A command or a file, in a frame that scrolls rather than wraps a line. */
|
|
/** A sample set off from the prose, highlighted at build (components/code). */
|
|
function Sample({ code, lang }: { code: string; lang: string }) {
|
|
return (
|
|
<Code
|
|
code={code}
|
|
lang={lang}
|
|
mt={32}
|
|
mb={13}
|
|
borderWidth={1}
|
|
borderColor="var(--white-10)"
|
|
rounded="var(--radius-lg)"
|
|
{...SMALL}
|
|
/>
|
|
)
|
|
}
|
|
|
|
/** A heading under the module's facts. */
|
|
function Heading({ children }: { children: ReactNode }) {
|
|
return (
|
|
<Text render="h2" display="block" mt={48} fontSize="$5" lineHeight="1.6rem" fontWeight="600" color="inherit">
|
|
{children}
|
|
</Text>
|
|
)
|
|
}
|
|
|
|
/** A paragraph at the lede's size. */
|
|
function Para({ children }: { children: ReactNode }) {
|
|
return (
|
|
<Text render="p" display="block" mt={12} fontSize="$3" lineHeight="1.25rem" color="var(--muted-foreground)">
|
|
{children}
|
|
</Text>
|
|
)
|
|
}
|
|
|
|
/**
|
|
* What a module is, where it has more to say than its name: the page a reader
|
|
* lands on from an import says what they imported.
|
|
*/
|
|
const ABOUT: Partial<Record<string, () => ReactNode>> = { ci: Ci }
|
|
|
|
/** hanzo.ai/ci is Hanzo CI, the pipeline every Hanzo repo imports. */
|
|
function Ci() {
|
|
return (
|
|
<>
|
|
<Heading>Hanzo CI</Heading>
|
|
<Para>
|
|
<T>{'One pipeline builds, tests and publishes every Hanzo repo. A repo says what it makes; the pipeline makes it the same way everywhere.'}</T>
|
|
</Para>
|
|
|
|
<Heading>hanzo.yml</Heading>
|
|
<Para>
|
|
<T>{'One file at the root of a repo names its images, the tests that gate them, and the static site it publishes, if it has one. The repo carries no build logic of its own.'}</T>
|
|
</Para>
|
|
<Sample
|
|
lang="yaml"
|
|
code={`images:
|
|
- { name: api, context: ., repo: ghcr.io/acme/api }
|
|
test:
|
|
- { name: unit, run: go test ./... }`}
|
|
/>
|
|
|
|
<Heading><T>The shared workflow</T></Heading>
|
|
<Para>
|
|
<T>{'A short workflow imports the pipeline from hanzoai/ci, so every repo runs the same steps from one definition. Tests run first, and a commit that fails them publishes nothing.'}</T>
|
|
</Para>
|
|
<Sample
|
|
lang="yaml"
|
|
code={`jobs:
|
|
cicd:
|
|
uses: hanzoai/ci/.github/workflows/build.yml@v2
|
|
secrets: inherit`}
|
|
/>
|
|
|
|
<Heading><T>Runners</T></Heading>
|
|
<Para>
|
|
<T values={{ runner: <code>runner</code> }}>
|
|
{"Jobs run on Hanzo's runner fleet. To run them on your own machines, pass your runners' labels as {runner}."}
|
|
</T>
|
|
</Para>
|
|
|
|
<Heading><T>The build door</T></Heading>
|
|
<Para>
|
|
<T values={{ mode: <code>mode: delegate</code>, route: <code>POST /v1/build</code> }}>
|
|
{"With {mode}, a job hands each image to the build door, {route} on api.hanzo.ai. It builds every declared architecture on a native node and pushes one multi-architecture image, only into registries your organization owns. It reads the organization from the job's Hanzo identity, so it needs no secret of its own."}
|
|
</T>
|
|
</Para>
|
|
<Para>
|
|
<T>{'A build ends at a published image. What runs where is declared separately, and applied from that declaration.'}</T>
|
|
</Para>
|
|
</>
|
|
)
|
|
}
|
|
|
|
/** One term over its value. */
|
|
function Fact({ term, children }: { term: ReactNode; children: ReactNode }) {
|
|
return (
|
|
<Box>
|
|
<Text render="dt" display="block" {...SMALL} color="var(--muted-foreground)">
|
|
{term}
|
|
</Text>
|
|
<Text render="dd" display="block" ml={40} {...SMALL} color="inherit">
|
|
{children}
|
|
</Text>
|
|
</Box>
|
|
)
|
|
}
|
|
|
|
function Link({ href, children }: { href: string; children: ReactNode }) {
|
|
return (
|
|
<Text render={<a href={href} />} color="inherit" textDecorationLine="underline">
|
|
{children}
|
|
</Text>
|
|
)
|
|
}
|