Skip to content

Commit 967c8cf

Browse files
authored
hello standard.site (#436)
2 parents 9eec874 + 8838454 commit 967c8cf

12 files changed

Lines changed: 812 additions & 2 deletions

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ blog-main
22

33
# build output
44
dist/
5+
.sequoia/
6+
.sequoia-state.json
7+
.tmp-standard-site-*/
58

69
# generated types
710
.astro/

knip.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"@tailwindcss/typography",
2020
"shiki",
2121
"tailwindcss",
22+
"sequoia-cli",
2223
"@tanstack/react-router",
2324
"arktype"
2425
]

netlify.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
[build]
2+
command = "pnpm run build:netlify"
3+
publish = "dist"
4+
15
[[redirects]]
26
from = "/"
37
to = "/blog"

package.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"dev": "astro dev",
88
"build:astro": "astro build",
99
"build": "pnpm run build:astro",
10+
"build:netlify": "node --import tsx ./scripts/netlify-build.ts",
1011
"preview": "astro preview",
1112
"typecheck": "astro check",
1213
"astro": "astro",
@@ -17,7 +18,10 @@
1718
"test": "vitest run",
1819
"format:check": "oxfmt --check . && prettier --check \"src/**/*.astro\"",
1920
"format:write": "oxfmt . && prettier --write \"src/**/*.astro\"",
20-
"ogimages": "tsx ./scripts/og-image.tsx"
21+
"ogimages": "tsx ./scripts/og-image.tsx",
22+
"standard-site:content": "node --import tsx ./scripts/standard-site-content.ts",
23+
"standard-site:details": "node --import tsx ./scripts/standard-site-content.ts --details",
24+
"standard-site:update-publication": "node --import tsx ./scripts/update-standard-site-publication.ts"
2125
},
2226
"dependencies": {
2327
"@atcute/bluesky-richtext-segmenter": "^3.0.0",
@@ -50,8 +54,8 @@
5054
"@astrojs/rss": "^4.0.17",
5155
"@astrojs/sitemap": "^3.7.1",
5256
"@tailwindcss/typography": "^0.5.19",
53-
"@tanstack/react-router": "^1.170.8",
5457
"@tanstack/react-query": "^5.90.21",
58+
"@tanstack/react-router": "^1.170.8",
5559
"@types/mdast": "^4.0.4",
5660
"@types/react": "^19.2.2",
5761
"@types/react-dom": "^19.2.2",
@@ -72,6 +76,7 @@
7276
"rehype-external-links": "^3.0.0",
7377
"remark-directive": "^4.0.0",
7478
"remark-smartypants": "^3.0.2",
79+
"sequoia-cli": "^0.5.6",
7580
"sharp": "^0.34.4",
7681
"shiki": "^3.22.0",
7782
"sirv": "^3.0.2",

pnpm-lock.yaml

Lines changed: 451 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

public/.well-known/.gitkeep

Whitespace-only changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
at://did:plc:3nqrhu5mthmias3zc4a2ovzj/site.standard.publication/3mmyb25hudm2r

scripts/netlify-build.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { spawnSync } from "node:child_process";
2+
3+
function run(command: string, args: Array<string>) {
4+
const result = spawnSync(command, args, {
5+
stdio: "inherit",
6+
shell: false,
7+
});
8+
9+
if (result.status !== 0) {
10+
process.exit(result.status ?? 1);
11+
}
12+
}
13+
14+
run("pnpm", ["run", "build:astro"]);
15+
16+
if (process.env.CONTEXT !== "production") {
17+
console.log(
18+
`Skipping standard.site publish for Netlify context: ${process.env.CONTEXT ?? "unknown"}`,
19+
);
20+
process.exit(0);
21+
}
22+
23+
run("pnpm", ["run", "standard-site:content"]);
24+
run("pnpm", ["exec", "sequoia", "sync", "--update-frontmatter"]);
25+
run("pnpm", ["exec", "sequoia", "publish"]);
26+
run("pnpm", ["exec", "sequoia", "inject", "--output", "./dist"]);
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import fs from "node:fs";
2+
import { describe, expect, it } from "vitest";
3+
4+
describe("standard.site configuration", () => {
5+
it("publishes generated rendered content through Sequoia", () => {
6+
const config = JSON.parse(fs.readFileSync("sequoia.json", "utf-8"));
7+
8+
expect(config.siteUrl).toBe("https://tkdodo.eu");
9+
expect(config.contentDir).toBe("./.sequoia/generated/posts");
10+
expect(config.outputDir).toBe("./dist");
11+
expect(config.pathPrefix).toBe("/blog");
12+
expect(config.removeIndexFromSlug).toBe(true);
13+
expect(config.frontmatter.publishDate).toBe("date");
14+
expect(config.frontmatter.slugField).toBe("slug");
15+
expect(config.frontmatter.coverImage).toBe("ogImage");
16+
expect(config.textContentField).toBeUndefined();
17+
expect(config.publishContent).toBe(false);
18+
expect(config.bluesky).toMatchObject({ maxAgeDays: 7 });
19+
expect(typeof config.bluesky.enabled).toBe("boolean");
20+
});
21+
22+
it("keeps well-known publication verification in sync", () => {
23+
const config = JSON.parse(fs.readFileSync("sequoia.json", "utf-8"));
24+
const verification = fs
25+
.readFileSync("public/.well-known/site.standard.publication", "utf-8")
26+
.trim();
27+
28+
expect(verification).toBe(config.publicationUri);
29+
expect(verification).toMatch(
30+
/^at:\/\/did:plc:3nqrhu5mthmias3zc4a2ovzj\/site\.standard\.publication\//,
31+
);
32+
});
33+
});
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import fs from "node:fs/promises";
2+
import path from "node:path";
3+
import { afterEach, describe, expect, it } from "vitest";
4+
import {
5+
generateStandardSiteContent,
6+
getStandardSiteSlug,
7+
} from "./standard-site-content";
8+
9+
const tmpDirs: Array<string> = [];
10+
11+
afterEach(async () => {
12+
await Promise.all(
13+
tmpDirs
14+
.splice(0)
15+
.map((dir) => fs.rm(dir, { recursive: true, force: true })),
16+
);
17+
});
18+
19+
async function createTmpDir() {
20+
const dir = await fs.mkdtemp(path.join(process.cwd(), ".tmp-standard-site-"));
21+
tmpDirs.push(dir);
22+
return dir;
23+
}
24+
25+
describe("getStandardSiteSlug", () => {
26+
it("prefers explicit frontmatter slugs", () => {
27+
expect(
28+
getStandardSiteSlug("content/posts/folder/index.mdx", "custom"),
29+
).toBe("custom");
30+
});
31+
32+
it("falls back to the parent folder for index.mdx posts", () => {
33+
expect(getStandardSiteSlug("content/posts/my-post/index.mdx")).toBe(
34+
"my-post",
35+
);
36+
});
37+
});
38+
39+
describe("generateStandardSiteContent", () => {
40+
it("writes generated MDX with metadata-only standard.site frontmatter", async () => {
41+
const rootDir = await createTmpDir();
42+
const contentDir = path.join(rootDir, "content/posts");
43+
const outputDir = path.join(rootDir, ".sequoia/generated/posts");
44+
45+
await fs.mkdir(path.join(contentDir, "my-post"), { recursive: true });
46+
await fs.writeFile(
47+
path.join(contentDir, "my-post/index.mdx"),
48+
`---
49+
title: My Post
50+
description: A useful post
51+
date: 2026-05-29
52+
slug: my-custom-post
53+
tags:
54+
- React
55+
- TypeScript
56+
---
57+
58+
import Aside from '@components/Aside'
59+
60+
<Aside>Implementation detail</Aside>
61+
62+
Article source
63+
`,
64+
);
65+
66+
const result = await generateStandardSiteContent({
67+
contentDir,
68+
outputDir,
69+
});
70+
71+
expect(result).toEqual([
72+
{
73+
canonicalPath: "/blog/my-custom-post",
74+
coverImage: "my-custom-post.png",
75+
slug: "my-custom-post",
76+
title: "My Post",
77+
},
78+
]);
79+
80+
const generated = await fs.readFile(
81+
path.join(outputDir, "my-custom-post/index.mdx"),
82+
"utf-8",
83+
);
84+
85+
expect(generated).toContain("title: My Post");
86+
expect(generated).toContain("description: A useful post");
87+
expect(generated).toContain("date: 2026-05-29");
88+
expect(generated).toContain("ogImage: my-custom-post.png");
89+
expect(generated).toContain("slug: my-custom-post");
90+
expect(generated).toContain("- React");
91+
expect(generated).toContain(
92+
"Source metadata is generated for standard.site publishing.",
93+
);
94+
});
95+
});

0 commit comments

Comments
 (0)