mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-09 22:25:02 +03:00
75 lines
3.6 KiB
TypeScript
75 lines
3.6 KiB
TypeScript
// Writes src/component/site/docs-file-icons.ts: the file-type icons the docs use, cut from the
|
|
// desktop app's sprite (@opencode/ui), so the site bundles a dozen icons instead of 1,089.
|
|
// The names come from the shared mapping (chooseIconName) applied to every code block title and
|
|
// every file and folder in a file tree ("├── name" lines) in the docs content, and the filenames on
|
|
// /dev/docs-components, plus each icon's _light variant.
|
|
// Run after adding a new kind of file to the docs: bun script/docs-file-icons.ts
|
|
import { chooseIconName } from "@opencode/ui/icons/file-type-name"
|
|
import { readdirSync } from "node:fs"
|
|
import path from "node:path"
|
|
|
|
const app = path.resolve(import.meta.dir, "..")
|
|
const content = path.resolve(app, "src/docs/content")
|
|
const sprite = await Bun.file(path.resolve(app, "../../packages/ui/src/components/file-icons/sprite.svg")).text()
|
|
|
|
const walk = (dir: string): string[] =>
|
|
readdirSync(dir, { withFileTypes: true }).flatMap((entry) =>
|
|
entry.isDirectory()
|
|
? walk(path.join(dir, entry.name))
|
|
: entry.name.endsWith(".mdx")
|
|
? [path.join(dir, entry.name)]
|
|
: [],
|
|
)
|
|
const sources = await Promise.all(walk(content).map((file) => Bun.file(file).text()))
|
|
const titles = sources.flatMap((text) =>
|
|
[...text.matchAll(/```\w*\s+title="([^"]+)"/g)].map((match) => match[1].replace(/ \(excerpt\)$/, "")),
|
|
)
|
|
// File trees: the name after the "├── " prefix, without the note after it (two spaces or "←").
|
|
const tree = sources
|
|
.flatMap((text) => [...text.matchAll(/```text\n([\s\S]*?)```/g)].map((match) => match[1]))
|
|
.filter((block) => /[├└]──/.test(block))
|
|
.flatMap((block) => block.split("\n"))
|
|
.map((line) => line.replace(/^[│├└─\s]+/, "").replace(/(\s{2,}|\s+←).*$/, ""))
|
|
.filter(Boolean)
|
|
.flatMap((name) =>
|
|
name
|
|
.replace(/\/$/, "")
|
|
.split("/")
|
|
.map((part, index, parts) => ({
|
|
name: part,
|
|
directory: index < parts.length - 1 || name.endsWith("/"),
|
|
})),
|
|
)
|
|
// Filenames and folders shown on the site that aren't titles in the docs content.
|
|
const site = [".env", ".github/workflows/opencode.yml", "mise.toml", "install.sh", "count-lines.ts", "release.md"]
|
|
const folders = [".opencode", "agents", "commands", "plugins", "tools", ".github", "workflows"]
|
|
|
|
const names = new Set([
|
|
...[...titles, ...site].map((name) => chooseIconName(name, "file", false)),
|
|
...folders.map((name) => chooseIconName(name, "directory", false)),
|
|
...tree.map((entry) => chooseIconName(entry.name, entry.directory ? "directory" : "file", false)),
|
|
// The fallbacks.
|
|
chooseIconName("unknown", "file", false),
|
|
chooseIconName("unknown", "directory", false),
|
|
])
|
|
const symbol = (name: string) => sprite.match(new RegExp(`<symbol([^>]*)id="${name}"([^>]*)>([\\s\\S]*?)</symbol>`))
|
|
const icons = [...names, ...[...names].map((name) => `${name}_light`)].flatMap((name) => {
|
|
const match = symbol(name)
|
|
if (!match) return []
|
|
const attrs = `${match[1]}${match[2]}`.replace(/\s+/g, " ").trim()
|
|
const body = match[3].replace(/\s+/g, " ").replace(/>\s+</g, "><").trim()
|
|
return [[name, `<svg xmlns="http://www.w3.org/2000/svg" ${attrs}>${body}</svg>`] as const]
|
|
})
|
|
|
|
await Bun.write(
|
|
path.join(app, "src/component/site/docs-file-icons.ts"),
|
|
`// Generated by script/docs-file-icons.ts from @opencode/ui's file-type sprite. Do not edit.
|
|
import type { IconName } from "@opencode/ui/icons/file-type"
|
|
|
|
export const docsFileIcons: Partial<Record<IconName, string>> = {
|
|
${icons.map(([name, svg]) => ` ${name}: ${JSON.stringify(svg)},`).join("\n")}
|
|
}
|
|
`,
|
|
)
|
|
console.log(`${icons.length} icons: ${icons.map((icon) => icon[0]).join(", ")}`)
|