Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ node_modules
tmp/
dist/
build/
docs/
/docs/
scratchpad/*
!scratchpad/tsconfig.json
.turbo
Expand Down
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,25 @@ This implementation is designed for building developer tooling that requires dee

Other packages in this repo (`@evm-effect/ethereum-types`, `@evm-effect/crypto`, `@evm-effect/rlp`, and internal packages) are published as needed for those two; you typically install `evm` and/or `solc` and let the package manager resolve the rest.

## Documentation

The documentation site lives in [`apps/docs`](apps/docs) — a Next.js app using
[Fumadocs](https://fumadocs.dev) and Tailwind CSS. Its API reference is generated
from the TSDoc comments of every published package with
[TypeDoc](https://typedoc.org), so it always matches the source.

```bash
pnpm docs:dev # build the packages, generate the API reference, serve on :3000
pnpm docs:build # production build
pnpm docs:api # regenerate content/docs/api only
```

Guides are written in `apps/docs/content/docs`. The `api/` section underneath it
is generated and git-ignored; regenerate it with `pnpm docs:api` after changing
a package's doc comments. TypeDoc settings shared by every package live in
[`typedoc.base.json`](typedoc.base.json), with per-package entry points in each
`packages/*/typedoc.json`.

## Test Coverage

This implementation is extensively tested against the official [Ethereum Execution Specs](https://github.com/ethereum/execution-specs) state tests and blockchain tests:
Expand Down
28 changes: 28 additions & 0 deletions apps/docs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# deps
/node_modules

# generated content
.source
/.typedoc
/content/docs/api

# test & build
/coverage
/.next/
/out/
/build
*.tsbuildinfo

# misc
.DS_Store
*.pem
/.pnp
.pnp.js
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# others
.env*.local
.vercel
next-env.d.ts
9 changes: 9 additions & 0 deletions apps/docs/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<!-- BEGIN:nextjs-agent-rules -->

# This is NOT the Next.js you know

This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` (resolved from this file's directory; in monorepos the `next` package may not be visible from the repo root) before writing any code. Heed deprecation notices.

This block is written and re-added by `next dev` — verify at `node_modules/next/dist/server/lib/generate-agent-files.js`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean.

<!-- END:nextjs-agent-rules -->
1 change: 1 addition & 0 deletions apps/docs/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@AGENTS.md
47 changes: 47 additions & 0 deletions apps/docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# @evm-effect/docs

The documentation site for [evm-effect](https://github.com/julia-script/evm-effect):
[Next.js](https://nextjs.org) + [Fumadocs](https://fumadocs.dev) + Tailwind CSS,
with an API reference generated from the packages' TSDoc comments.

## Commands

```bash
pnpm dev # generate the API reference, then start the dev server
pnpm build # generate the API reference, then build for production
pnpm api # regenerate content/docs/api only
pnpm check # type-check the app
```

Run these from the repository root as `pnpm docs:dev` / `pnpm docs:build` /
`pnpm docs:api` to get the workspace packages built first — the API generation
reads the published type declarations, so `pnpm build` must have run at least
once for the packages this site documents.

## Layout

| Path | Description |
| ---- | ----------- |
| `content/docs` | Hand-written guides (MDX) |
| `content/docs/api` | **Generated** API reference — git-ignored, rebuilt by `pnpm api` |
| `lib/source.ts` | Fumadocs content source; collections are defined with the Macro API |
| `lib/layout.shared.tsx` | Shared layout options (nav, links, GitHub URL) |
| `scripts/generate-api-docs.ts` | Runs TypeDoc and converts its markdown into Fumadocs content |
| `typedoc.json` | TypeDoc entry points and markdown output options |

Sidebar order comes from `meta.json` files: hand-written ones for the guides,
generated ones for the API reference.

## How the API reference is generated

1. TypeDoc runs in [packages mode](https://typedoc.org/documents/Options.Input.html#entrypointstrategy)
over every published package. Shared options live in `../../typedoc.base.json`
and each package contributes its entry point in `packages/*/typedoc.json`.
2. `typedoc-plugin-markdown` writes plain markdown to `.typedoc`.
3. `scripts/generate-api-docs.ts` flattens `@evm-effect/<pkg>` to `<pkg>`,
rewrites every cross-reference, adds frontmatter and writes the `meta.json`
files, leaving the result in `content/docs/api`.

To document a new package, add it to the `entryPoints` in `typedoc.json` and to
`PACKAGES` in `scripts/generate-api-docs.ts`, then give it a `typedoc.json` like
the existing ones.
6 changes: 6 additions & 0 deletions apps/docs/app/(home)/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { HomeLayout } from "fumadocs-ui/layouts/home";
import { baseOptions } from "@/lib/layout.shared";

export default function Layout({ children }: LayoutProps<"/">) {
return <HomeLayout {...baseOptions()}>{children}</HomeLayout>;
}
78 changes: 78 additions & 0 deletions apps/docs/app/(home)/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import Link from "next/link";
import { gitConfig } from "@/lib/shared";

const features = [
{
title: "Every released fork",
description:
"Frontier through Osaka, validated against the official Ethereum execution spec state and blockchain tests.",
},
{
title: "Built for introspection",
description:
"Structured concurrency, typed errors and EIP-3155 tracing make every step of execution observable.",
},
{
title: "Composable by construction",
description:
"Forks, state and blockchain access are Effect layers, so they can be swapped, mocked or instrumented.",
},
];

export default function HomePage() {
return (
<main className="flex flex-1 flex-col items-center justify-center px-4 py-24">
<div className="flex w-full max-w-3xl flex-col items-center text-center">
<h1 className="font-mono text-4xl font-bold tracking-tight sm:text-5xl">
evm-effect
</h1>
<p className="mt-6 text-lg text-fd-muted-foreground">
An Ethereum Virtual Machine implementation in TypeScript, built on{" "}
<a
href="https://effect.website/"
className="font-medium text-fd-foreground underline underline-offset-4"
>
Effect
</a>
, with a focus on debuggability.
</p>

<div className="mt-8 flex flex-wrap items-center justify-center gap-3">
<Link
href="/docs/getting-started"
className="rounded-lg bg-fd-primary px-4 py-2 text-sm font-medium text-fd-primary-foreground"
>
Get started
</Link>
<Link
href="/docs/api"
className="rounded-lg border px-4 py-2 text-sm font-medium"
>
API reference
</Link>
<a
href={`https://github.com/${gitConfig.user}/${gitConfig.repo}`}
className="rounded-lg border px-4 py-2 text-sm font-medium"
>
GitHub
</a>
</div>

<code className="mt-8 rounded-lg border bg-fd-secondary px-4 py-2 font-mono text-sm">
npm add @evm-effect/evm effect
</code>

<div className="mt-16 grid w-full gap-4 text-left sm:grid-cols-3">
{features.map((feature) => (
<div key={feature.title} className="rounded-xl border p-4">
<h2 className="text-sm font-semibold">{feature.title}</h2>
<p className="mt-2 text-sm text-fd-muted-foreground">
{feature.description}
</p>
</div>
))}
</div>
</div>
</main>
);
}
4 changes: 4 additions & 0 deletions apps/docs/app/api/search/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { createFromSource } from "fumadocs-core/search/server";
import { source } from "@/lib/source";

export const { GET } = createFromSource(source);
69 changes: 69 additions & 0 deletions apps/docs/app/docs/[[...slug]]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import {
DocsBody,
DocsDescription,
DocsPage,
DocsTitle,
MarkdownCopyButton,
ViewOptionsPopover,
} from "fumadocs-ui/layouts/docs/page";
import { createRelativeLink } from "fumadocs-ui/mdx";
import type { Metadata } from "next";
import { notFound } from "next/navigation";
import { getMDXComponents } from "@/components/mdx";
import { gitConfig } from "@/lib/shared";
import { getPageImageUrl, getPageMarkdownUrl, source } from "@/lib/source";

export default async function Page(props: PageProps<"/docs/[[...slug]]">) {
const params = await props.params;
const page = source.getPage(params.slug);
if (!page) notFound();

const MDX = page.data.body;
const markdownUrl = getPageMarkdownUrl(page).url;
// Pages under `api/` are generated from TSDoc at build time, so they have no
// counterpart in the repository to link to.
const githubUrl = page.path.startsWith("api/")
? undefined
: `https://github.com/${gitConfig.user}/${gitConfig.repo}/blob/${gitConfig.branch}/${gitConfig.contentDir}/${page.path}`;

return (
<DocsPage toc={page.data.toc} full={page.data.full}>
<DocsTitle>{page.data.title}</DocsTitle>
<DocsDescription className="mb-0">
{page.data.description}
</DocsDescription>
<div className="flex flex-row gap-2 items-center border-b pb-6">
<MarkdownCopyButton markdownUrl={markdownUrl} />
<ViewOptionsPopover markdownUrl={markdownUrl} githubUrl={githubUrl} />
</div>
<DocsBody>
<MDX
components={getMDXComponents({
// this allows you to link to other pages with relative file paths
a: createRelativeLink(source, page),
})}
/>
</DocsBody>
</DocsPage>
);
}

export async function generateStaticParams() {
return source.generateParams();
}

export async function generateMetadata(
props: PageProps<"/docs/[[...slug]]">,
): Promise<Metadata> {
const params = await props.params;
const page = source.getPage(params.slug);
if (!page) notFound();

return {
title: page.data.title,
description: page.data.description,
openGraph: {
images: getPageImageUrl(page).url,
},
};
}
11 changes: 11 additions & 0 deletions apps/docs/app/docs/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { DocsLayout } from "fumadocs-ui/layouts/docs";
import { baseOptions } from "@/lib/layout.shared";
import { source } from "@/lib/source";

export default function Layout({ children }: LayoutProps<"/docs">) {
return (
<DocsLayout tree={source.getPageTree()} {...baseOptions()}>
{children}
</DocsLayout>
);
}
12 changes: 12 additions & 0 deletions apps/docs/app/global.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
@import "tailwindcss";
@import "fumadocs-ui/css/neutral.css";
@import "fumadocs-ui/css/preset.css";

html {
scrollbar-gutter: stable;
}

html > body[data-scroll-locked] {
margin-right: 0px;
--removed-body-scroll-bar-size: 0px;
}
17 changes: 17 additions & 0 deletions apps/docs/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { RootProvider } from "fumadocs-ui/provider/next";
import "./global.css";
import { Inter } from "next/font/google";

const inter = Inter({
subsets: ["latin"],
});

export default function Layout({ children }: LayoutProps<"/">) {
return (
<html lang="en" className={inter.className} suppressHydrationWarning>
<body className="flex flex-col min-h-screen">
<RootProvider>{children}</RootProvider>
</body>
</html>
);
}
10 changes: 10 additions & 0 deletions apps/docs/app/llms-full.txt/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { getLLMText, source } from "@/lib/source";

export const revalidate = false;

export async function GET() {
const scan = source.getPages().map(getLLMText);
const scanned = await Promise.all(scan);

return new Response(scanned.join("\n\n"));
}
26 changes: 26 additions & 0 deletions apps/docs/app/llms.mdx/docs/[[...slug]]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { notFound } from "next/navigation";
import { getLLMText, getPageMarkdownUrl, source } from "@/lib/source";

export const revalidate = false;

export async function GET(
_req: Request,
{ params }: RouteContext<"/llms.mdx/docs/[[...slug]]">,
) {
const { slug } = await params;
const page = source.getPage(slug?.slice(0, -1));
if (!page) notFound();

return new Response(await getLLMText(page), {
headers: {
"Content-Type": "text/markdown",
},
});
}

export function generateStaticParams() {
return source.getPages().map((page) => ({
lang: page.locale,
slug: getPageMarkdownUrl(page).segments,
}));
}
8 changes: 8 additions & 0 deletions apps/docs/app/llms.txt/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { llms } from "fumadocs-core/source";
import { source } from "@/lib/source";

export const revalidate = false;

export function GET() {
return new Response(llms(source).index());
}
Loading
Loading