mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-21 15:23:27 +00:00
feat(posts): add Astro posts site (#43720)
This commit is contained in:
parent
ebc2504ef3
commit
cc15c2a488
17 changed files with 437 additions and 0 deletions
37
.github/workflows/deploy-posts.yml
vendored
Normal file
37
.github/workflows/deploy-posts.yml
vendored
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
name: deploy-posts
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- v2
|
||||
paths:
|
||||
- packages/posts/**
|
||||
- bun.lock
|
||||
- .github/workflows/deploy-posts.yml
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: deploy-posts-${{ github.ref_name }}
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
if: github.repository == 'anomalyco/opencode' && github.ref_name == 'v2'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Build
|
||||
working-directory: packages/posts
|
||||
run: bun run build
|
||||
|
||||
- name: Deploy
|
||||
working-directory: packages/posts
|
||||
run: bun run deploy
|
||||
env:
|
||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
4
packages/posts/.gitignore
vendored
Normal file
4
packages/posts/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
.astro/
|
||||
dist/
|
||||
node_modules/
|
||||
.wrangler/
|
||||
16
packages/posts/astro.config.ts
Normal file
16
packages/posts/astro.config.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import cloudflare from "@astrojs/cloudflare"
|
||||
import mdx from "@astrojs/mdx"
|
||||
import { defineConfig } from "astro/config"
|
||||
|
||||
export default defineConfig({
|
||||
site: "https://opencode.ai",
|
||||
base: "/posts",
|
||||
output: "server",
|
||||
adapter: cloudflare({ imageService: "passthrough" }),
|
||||
integrations: [mdx()],
|
||||
vite: {
|
||||
server: {
|
||||
allowedHosts: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
24
packages/posts/package.json
Normal file
24
packages/posts/package.json
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/posts",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"build": "astro build && bun script/prepare-cloudflare.ts",
|
||||
"deploy": "wrangler deploy --config dist/server/wrangler.json",
|
||||
"typecheck": "astro check"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/mdx": "7.0.5",
|
||||
"@fontsource/commit-mono": "5.3.0",
|
||||
"astro": "7.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@astrojs/cloudflare": "14.1.4",
|
||||
"@astrojs/check": "0.9.6",
|
||||
"@types/bun": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"wrangler": "4.110.0"
|
||||
}
|
||||
}
|
||||
10
packages/posts/public/logo-dark.svg
Normal file
10
packages/posts/public/logo-dark.svg
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<svg width="641" height="115" viewBox="0 0 641 115" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M49.2857 32.8573H16.4286V82.143H49.2857V32.8573ZM65.7143 98.5716H0V16.4287H65.7143V98.5716Z" fill="white"/>
|
||||
<path d="M98.5692 82.143H131.426V32.8573H98.5692V82.143ZM147.855 98.5716H98.5692V115H82.1406V16.4287H147.855V98.5716Z" fill="white"/>
|
||||
<path d="M230.003 65.7144H180.718V82.143H230.003V98.5716H164.289V16.4287H230.003V65.7144ZM180.718 49.2859H213.575V32.8573H180.718V49.2859Z" fill="white"/>
|
||||
<path d="M295.715 32.8573H262.858V98.5716H246.43V16.4287H295.715V32.8573ZM312.144 98.5716H295.715V32.8573H312.144V98.5716Z" fill="white"/>
|
||||
<path d="M394.285 32.8573H344.999V82.143H394.285V98.5716H328.57V16.4287H394.285V32.8573Z" fill="white"/>
|
||||
<path d="M459.997 32.8573H427.14V82.143H459.997V32.8573ZM476.425 98.5716H410.711V16.4287H476.425V98.5716Z" fill="white"/>
|
||||
<path d="M542.145 32.8571H509.288V82.1429H542.145V32.8571ZM558.574 98.5714H492.859V16.4286H542.145V0H558.574V98.5714Z" fill="white"/>
|
||||
<path d="M591.429 32.8573V49.2859H624.286V32.8573H591.429ZM640.714 65.7144H591.429V82.143H640.714V98.5716H575V16.4287H640.714V65.7144Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
10
packages/posts/script/prepare-cloudflare.ts
Normal file
10
packages/posts/script/prepare-cloudflare.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
export {}
|
||||
|
||||
const path = "dist/server/wrangler.json"
|
||||
const config = await Bun.file(path).json()
|
||||
|
||||
delete config.kv_namespaces
|
||||
delete config.images
|
||||
delete config.previews
|
||||
|
||||
await Bun.write(path, JSON.stringify(config))
|
||||
45
packages/posts/src/components/Counter.astro
Normal file
45
packages/posts/src/components/Counter.astro
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
<div class="counter" data-counter>
|
||||
<span>Clicks</span>
|
||||
<output>0</output>
|
||||
<button type="button">Increment</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.querySelectorAll<HTMLElement>("[data-counter]").forEach((counter) => {
|
||||
const output = counter.querySelector("output")
|
||||
counter.querySelector("button")?.addEventListener("click", () => {
|
||||
if (output) output.value = String(Number(output.value) + 1)
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.counter {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
align-items: center;
|
||||
gap: 0.75rem 1rem;
|
||||
margin-block: 2.5rem;
|
||||
padding: 1rem;
|
||||
border: 1px solid var(--border);
|
||||
background: var(--code);
|
||||
}
|
||||
|
||||
output {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
button {
|
||||
grid-column: 1 / -1;
|
||||
padding: 0.65rem 1rem;
|
||||
border: 0;
|
||||
color: var(--background);
|
||||
background: var(--foreground);
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
opacity: 0.85;
|
||||
}
|
||||
</style>
|
||||
14
packages/posts/src/content.config.ts
Normal file
14
packages/posts/src/content.config.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { defineCollection } from "astro:content"
|
||||
import { glob } from "astro/loaders"
|
||||
import { z } from "astro/zod"
|
||||
|
||||
const posts = defineCollection({
|
||||
loader: glob({ pattern: "**/*.{md,mdx}", base: "./src/content/posts" }),
|
||||
schema: z.object({
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
published: z.coerce.date(),
|
||||
}),
|
||||
})
|
||||
|
||||
export const collections = { posts }
|
||||
17
packages/posts/src/content/posts/hello.mdx
Normal file
17
packages/posts/src/content/posts/hello.mdx
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
title: "Example one"
|
||||
description: "Markdown for the words, components for everything else."
|
||||
published: 2026-08-20
|
||||
---
|
||||
|
||||
import Counter from "../../components/Counter.astro"
|
||||
|
||||
This is a post written in MDX. Most of it is ordinary Markdown, rendered to static HTML with no client-side JavaScript.
|
||||
|
||||
## Interactive when it matters
|
||||
|
||||
Components can appear directly between paragraphs. This one is an Astro component with a tiny browser script:
|
||||
|
||||
<Counter />
|
||||
|
||||
The component could also come from another workspace package or use React, Solid, Svelte, or Vue when the interaction calls for it.
|
||||
9
packages/posts/src/content/posts/inside-the-loop.mdx
Normal file
9
packages/posts/src/content/posts/inside-the-loop.mdx
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
title: "Lorem ipsum"
|
||||
description: "A closer look at what happens between a prompt and a result."
|
||||
published: 2026-08-07
|
||||
---
|
||||
|
||||
An agent alternates between reasoning, acting, and observing. The useful part is not any single step, but how quickly the loop incorporates new information.
|
||||
|
||||
Interactive explanations let us inspect that process instead of reducing it to a static diagram.
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
title: "Example two"
|
||||
description: "The best interface gets out of the way of the work."
|
||||
published: 2026-08-14
|
||||
---
|
||||
|
||||
Software should make difficult work feel direct. Every control has to earn the attention it asks for.
|
||||
|
||||
The goal is not fewer capabilities. It is less distance between intent and result.
|
||||
32
packages/posts/src/layouts/Layout.astro
Normal file
32
packages/posts/src/layouts/Layout.astro
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
---
|
||||
import "@fontsource/commit-mono/400.css"
|
||||
import "@fontsource/commit-mono/500.css"
|
||||
import "../styles/global.css"
|
||||
|
||||
interface Props {
|
||||
title?: string
|
||||
description?: string
|
||||
}
|
||||
|
||||
const title = Astro.props.title ? `${Astro.props.title} | OpenCode Posts` : "OpenCode Posts"
|
||||
const description = Astro.props.description ?? "Notes from the OpenCode team."
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta name="description" content={description} />
|
||||
<meta name="theme-color" content="#131010" />
|
||||
<title>{title}</title>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<a class="site-logo" href="https://opencode.ai/" aria-label="OpenCode home">
|
||||
<img src={`${import.meta.env.BASE_URL}/logo-dark.svg`} alt="OpenCode" />
|
||||
</a>
|
||||
<slot />
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
27
packages/posts/src/pages/[...slug].astro
Normal file
27
packages/posts/src/pages/[...slug].astro
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
import { getCollection, render } from "astro:content"
|
||||
import Layout from "../layouts/Layout.astro"
|
||||
|
||||
export const prerender = true
|
||||
|
||||
export async function getStaticPaths() {
|
||||
return (await getCollection("posts")).map((post) => ({ params: { slug: post.id }, props: { post } }))
|
||||
}
|
||||
|
||||
const post = Astro.props.post
|
||||
const rendered = await render(post)
|
||||
const Content = rendered.Content
|
||||
---
|
||||
|
||||
<Layout title={post.data.title} description={post.data.description}>
|
||||
<article>
|
||||
<div class="post-header">
|
||||
<time datetime={post.data.published.toISOString()}>
|
||||
{post.data.published.toLocaleDateString("en", { month: "long", day: "numeric", year: "numeric" })}
|
||||
</time>
|
||||
<h1>{post.data.title}</h1>
|
||||
<p class="description">{post.data.description}</p>
|
||||
</div>
|
||||
<Content />
|
||||
</article>
|
||||
</Layout>
|
||||
25
packages/posts/src/pages/index.astro
Normal file
25
packages/posts/src/pages/index.astro
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
import { getCollection } from "astro:content"
|
||||
import Layout from "../layouts/Layout.astro"
|
||||
|
||||
export const prerender = true
|
||||
|
||||
const posts = (await getCollection("posts")).sort(
|
||||
(a, b) => b.data.published.valueOf() - a.data.published.valueOf(),
|
||||
)
|
||||
---
|
||||
|
||||
<Layout>
|
||||
<ol class="post-list">
|
||||
{posts.map((post) => (
|
||||
<li>
|
||||
<a href={`${import.meta.env.BASE_URL}/${post.id}/`}>
|
||||
<span>{post.data.title}</span>
|
||||
<time datetime={post.data.published.toISOString()}>
|
||||
{post.data.published.toLocaleDateString("en", { month: "short", day: "numeric", year: "numeric" })}
|
||||
</time>
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</Layout>
|
||||
139
packages/posts/src/styles/global.css
Normal file
139
packages/posts/src/styles/global.css
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
:root {
|
||||
color-scheme: dark;
|
||||
font-family: "Commit Mono", ui-monospace, monospace;
|
||||
color: var(--foreground);
|
||||
background: var(--background);
|
||||
font-synthesis: none;
|
||||
--foreground: #f1efe8;
|
||||
--background: #131010;
|
||||
--muted: rgb(241 239 232 / 45%);
|
||||
--border: rgb(241 239 232 / 12%);
|
||||
--code: rgb(255 255 255 / 6%);
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
background: var(--background);
|
||||
font-size: 18px;
|
||||
line-height: 1.75;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
main {
|
||||
width: min(100% - 2rem, 672px);
|
||||
margin-inline: auto;
|
||||
padding-block: 4rem;
|
||||
}
|
||||
|
||||
.site-logo {
|
||||
display: block;
|
||||
width: 8.75rem;
|
||||
margin-bottom: 5rem;
|
||||
}
|
||||
|
||||
.site-logo img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2 {
|
||||
font-weight: 500;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0 0 1rem;
|
||||
color: #fff;
|
||||
font-size: clamp(2rem, 8vw, 3rem);
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 3rem 0 1rem;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-block: 0 1.25rem;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-underline-offset: 0.2em;
|
||||
}
|
||||
|
||||
time,
|
||||
.description {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 1.05rem;
|
||||
}
|
||||
|
||||
.post-header {
|
||||
margin-bottom: 4rem;
|
||||
}
|
||||
|
||||
.post-header time {
|
||||
display: block;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 0.75rem;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.post-list {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.post-list a {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
align-items: baseline;
|
||||
gap: 1rem;
|
||||
margin-inline: -1.25rem;
|
||||
padding: 1.1rem 1.25rem;
|
||||
text-decoration: none;
|
||||
transition: background-color 120ms ease;
|
||||
}
|
||||
|
||||
.post-list a:hover {
|
||||
background: rgb(255 255 255 / 4%);
|
||||
}
|
||||
|
||||
.post-list span:first-child {
|
||||
color: #fff;
|
||||
font-size: 1.125rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.post-list time {
|
||||
font-size: 0.75rem;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
code {
|
||||
padding: 0.15em 0.35em;
|
||||
background: var(--code);
|
||||
font: inherit;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.post-list a {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
}
|
||||
6
packages/posts/tsconfig.json
Normal file
6
packages/posts/tsconfig.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"extends": "astro/tsconfigs/strict",
|
||||
"compilerOptions": {
|
||||
"types": ["bun"]
|
||||
}
|
||||
}
|
||||
13
packages/posts/wrangler.jsonc
Normal file
13
packages/posts/wrangler.jsonc
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"$schema": "node_modules/wrangler/config-schema.json",
|
||||
"name": "opencode-posts",
|
||||
"compatibility_date": "2026-07-25",
|
||||
"workers_dev": false,
|
||||
"preview_urls": false,
|
||||
"routes": [
|
||||
{
|
||||
"pattern": "opencode.ai/posts*",
|
||||
"zone_name": "opencode.ai"
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue