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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ distllms/
# generated types
.astro/

# skills/ is fetched from middlecache via bin/fetch-skills.ts
skills/

# dependencies
node_modules/

Expand Down
105 changes: 59 additions & 46 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@
├── public/ # Static files served as-is (images, redirects, robots.txt)
├── worker/ # Cloudflare Worker for serving the site
├── bin/ # Build scripts and CI helpers
├── skills/ # Interactive exercises (astro-skills)
│ └── fetch-skills.ts # Downloads skills.tar.gz from middlecache, extracts to skills/
├── skills/ # Agent Skills served at /.well-known/skills/ — GENERATED, do not edit
│ # Fetched from https://middlecache.ced.cloudflare.com/v1/cloudflare-skills/skills.tar.gz
│ # by bin/fetch-skills.ts, which runs automatically via prebuild/predev hooks.
│ # skills/ is in .gitignore and is NOT committed to the repository.
├── astro.config.ts # Astro + Starlight configuration
├── ec.config.mjs # Expressive Code (syntax highlighting) configuration
├── package.json
Expand Down Expand Up @@ -63,19 +67,19 @@

```yaml
---
title: Page Title # Required
description: SEO meta description # Recommended
pcx_content_type: how-to # Page type (see below)
title: Page Title # Required
description: SEO meta description # Recommended
pcx_content_type: how-to # Page type (see below)
sidebar:
order: 1 # Sort order in sidebar
label: Custom Label # Override sidebar text
tags: # Optional, validated against allowlist
order: 1 # Sort order in sidebar
label: Custom Label # Override sidebar text
tags: # Optional, validated against allowlist
- JavaScript
- Workers
products: # References to src/content/products/ entries
products: # References to src/content/products/ entries
- workers
difficulty: Beginner # For tutorials: Beginner | Intermediate | Advanced
reviewed: 2025-01-15 # YYYY-MM-DD of last content review
difficulty: Beginner # For tutorials: Beginner | Intermediate | Advanced
reviewed: 2025-01-15 # YYYY-MM-DD of last content review
---
```

Expand All @@ -87,10 +91,10 @@

MDX is parsed as JSX, not plain Markdown. These characters have special meaning and **will break the build** if used unescaped in prose:

| Character | Problem | Fix |
|-----------|---------|-----|
| `{` `}` | Interpreted as JS expressions | Wrap in backticks or use `\{` `\}` |
| `<` `>` | Interpreted as JSX elements | Use `&lt;` `&gt;` or wrap in backticks |
| Character | Problem | Fix |
| --------- | ----------------------------- | -------------------------------------- |
| `{` `}` | Interpreted as JS expressions | Wrap in backticks or use `\{` `\}` |
| `<` `>` | Interpreted as JSX elements | Use `&lt;` `&gt;` or wrap in backticks |

This is the single most common build failure. Always check prose, tables, and headings for these characters.

Expand Down Expand Up @@ -130,7 +134,14 @@
Components are imported from `~/components` in MDX files:

```mdx
import { Render, TypeScriptExample, WranglerConfig, Details } from "~/components";
import {
Render,
TypeScriptExample,
WranglerConfig,
Details,
} from "~/components";

;
```

Components **must** be imported after the frontmatter block. Forgetting the import is a common mistake.
Expand All @@ -143,6 +154,7 @@
<Render file="partial-name" product="workers" />

<!-- With parameters: -->

<Render file="partial-name" product="workers" params={{ key: "value" }} />
```

Expand All @@ -164,14 +176,15 @@

Shows Wrangler configuration in both TOML and JSON formats with synced tabs. Auto-converts between formats.

```mdx
````mdx
<WranglerConfig>

```toml
name = "my-worker"
main = "src/index.ts"
compatibility_date = "$today"
```
````

</WranglerConfig>
```
Expand All @@ -185,8 +198,8 @@
import { Tabs, TabItem } from "~/components";

<Tabs>
<TabItem label="npm">npm install package</TabItem>
<TabItem label="yarn">yarn add package</TabItem>
<TabItem label="npm">npm install package</TabItem>
<TabItem label="yarn">yarn add package</TabItem>
</Tabs>
```

Expand All @@ -212,19 +225,19 @@

### Other frequently used components

| Component | Purpose |
|-----------|---------|
| `Plan` | Display plan availability (e.g., `<Plan type="enterprise" />`) |
| `GlossaryTooltip` | Inline hover tooltip with glossary definition |
| `InlineBadge` | Status badges: `<InlineBadge preset="beta" />` |
| `LinkTitleCard` | Navigation card with icon, title, and description |
| `DirectoryListing` | Auto-generated listing of child pages |
| `YouTube` | Embed YouTube video by ID |
| `Stream` | Embed Cloudflare Stream video |
| `APIRequest` | Generate curl commands from the Cloudflare OpenAPI schema |
| `DashButton` | "Go to Dashboard" button with validated deeplink |
| `ListTutorials` | Auto-generated tutorial listing table |
| `GitHubCode` | Fetch and display code from a GitHub repository |
| Component | Purpose |
| ------------------ | -------------------------------------------------------------- |
| `Plan` | Display plan availability (e.g., `<Plan type="enterprise" />`) |
| `GlossaryTooltip` | Inline hover tooltip with glossary definition |
| `InlineBadge` | Status badges: `<InlineBadge preset="beta" />` |
| `LinkTitleCard` | Navigation card with icon, title, and description |
| `DirectoryListing` | Auto-generated listing of child pages |
| `YouTube` | Embed YouTube video by ID |
| `Stream` | Embed Cloudflare Stream video |
| `APIRequest` | Generate curl commands from the Cloudflare OpenAPI schema |
| `DashButton` | "Go to Dashboard" button with validated deeplink |
| `ListTutorials` | Auto-generated tutorial listing table |
| `GitHubCode` | Fetch and display code from a GitHub repository |

For the full component list and their props, see `src/components/index.ts` (barrel export) and the individual `.astro` / `.tsx` files.

Expand Down Expand Up @@ -294,7 +307,7 @@
6. Redirect validation (`bin/validate-redirects.ts`)
7. `npm run test` (all Vitest suites)

A separate Semgrep workflow checks style guide compliance (dates, "coming soon" phrases) and produces warnings.

Check warning on line 310 in AGENTS.md

View workflow job for this annotation

GitHub Actions / Semgrep

semgrep.style-guide-coming-soon

Found forbidden string 'coming soon'. Too often we set expectations unfairly by attaching this phrase to a feature that may not actually arrive soon. (add [skip style guide checks] to commit message to skip)

## Common mistakes to avoid

Expand All @@ -315,27 +328,27 @@

The site defines 20 content collections in `src/content.config.ts` with schemas in `src/schemas/`. The major ones:

| Collection | Location | Description |
|-----------|----------|-------------|
| `docs` | `src/content/docs/` | Main documentation pages (MDX) |
| `partials` | `src/content/partials/` | Reusable content snippets (MDX) |
| `changelog` | `src/content/changelog/` | Product changelogs (MDX) |
| `glossary` | `src/content/glossary/` | Glossary terms (YAML) |
| `products` | `src/content/products/` | Product metadata (YAML) |
| `plans` | `src/content/plans/` | Plan/pricing data (YAML) |
| `workers-ai-models` | `src/content/workers-ai-models/` | AI model definitions (JSON) |
| `fields` | `src/content/fields/` | Ruleset engine field definitions (YAML) |
| `learning-paths` | `src/content/learning-paths/` | Learning path definitions (JSON) |
| Collection | Location | Description |
| ------------------- | -------------------------------- | --------------------------------------- |
| `docs` | `src/content/docs/` | Main documentation pages (MDX) |
| `partials` | `src/content/partials/` | Reusable content snippets (MDX) |
| `changelog` | `src/content/changelog/` | Product changelogs (MDX) |
| `glossary` | `src/content/glossary/` | Glossary terms (YAML) |
| `products` | `src/content/products/` | Product metadata (YAML) |
| `plans` | `src/content/plans/` | Plan/pricing data (YAML) |
| `workers-ai-models` | `src/content/workers-ai-models/` | AI model definitions (JSON) |
| `fields` | `src/content/fields/` | Ruleset engine field definitions (YAML) |
| `learning-paths` | `src/content/learning-paths/` | Learning path definitions (JSON) |

## Testing

Tests use Vitest with three workspace projects (`vitest.workspace.ts`):

| Suite | File pattern | Runtime |
|-------|-------------|---------|
| Suite | File pattern | Runtime |
| ------- | ------------------ | --------------------------------- |
| Workers | `*.worker.test.ts` | `@cloudflare/vitest-pool-workers` |
| Node | `*.node.test.ts` | Node.js |
| Astro | `*.astro.test.ts` | Astro Vite config |
| Node | `*.node.test.ts` | Node.js |
| Astro | `*.astro.test.ts` | Astro Vite config |

Run all tests: `npm run test`

Expand Down
89 changes: 89 additions & 0 deletions bin/fetch-skills.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#!/usr/bin/env tsx

import { spawn } from "child_process";
import fs from "fs";
import { join } from "path";

import { downloadToDotTempIfNotPresent } from "../src/util/custom-loaders";

const MIDDLECACHE_BASE_URL = "https://middlecache.ced.cloudflare.com/";
const SKILLS_MIDDLECACHE_PATH = "v1/cloudflare-skills/skills.tar.gz";
const SKILLS_DOT_TMP_PATH = `middlecache/${SKILLS_MIDDLECACHE_PATH}`;
const MANIFEST_MIDDLECACHE_PATH = "v1/cloudflare-skills/skills-manifest.json";
const MANIFEST_DOT_TMP_PATH = `middlecache/${MANIFEST_MIDDLECACHE_PATH}`;
const SKILLS_DIR = "./skills";

// --soft: warn and continue on failure instead of exiting non-zero.
// Used by the predev hook so a network failure doesn't block local development.
// --force: re-fetch even if skills/ already exists.
const soft = process.argv.includes("--soft");
const force = process.argv.includes("--force");

const fail = (message: string): never => {
if (soft) {
const hasExisting = fs.existsSync(SKILLS_DIR);
console.warn(
hasExisting
? `Warning: ${message} — continuing with existing Cloudflare Skills`
: `Warning: ${message} — skills/ does not exist, /.well-known/skills/ will not work`,
);
process.exit(0);
}
console.error(`Error: ${message}`);
process.exit(1);
};

if (fs.existsSync(SKILLS_DIR) && !force) {
console.log(
"/skills directory already exists, skipping fetch. (run `npx tsx bin/fetch-skills.ts --force` to re-fetch)",
);
process.exit(0);
}

console.log("Fetching Cloudflare Skills from middlecache");

try {
await Promise.all([
downloadToDotTempIfNotPresent(
`${MIDDLECACHE_BASE_URL}${SKILLS_MIDDLECACHE_PATH}`,
SKILLS_DOT_TMP_PATH,
),
downloadToDotTempIfNotPresent(
`${MIDDLECACHE_BASE_URL}${MANIFEST_MIDDLECACHE_PATH}`,
MANIFEST_DOT_TMP_PATH,
),
]);
} catch (err) {
fail(`fetch failed: ${err}`);
}

const tarballPath = join(".tmp", ...SKILLS_DOT_TMP_PATH.split("/"));

// Remove existing skills/ directory so stale Cloudflare Skills don't accumulate
fs.rmSync(SKILLS_DIR, { recursive: true, force: true });
fs.mkdirSync(SKILLS_DIR, { recursive: true });

// Extract the tarball from .tmp/ into ./skills/.
// The archive contains skills/<skill-name>/... so we strip the leading "skills/"
// component and extract into SKILLS_DIR.
const tar = spawn(
"tar",
["--strip-components=1", "-xz", "-C", SKILLS_DIR, "-f", tarballPath],
{ stdio: "inherit" },
);

const exitCode = await new Promise<number | null>((resolve) =>
tar.on("close", resolve),
);

if (exitCode !== 0) {
fail(`tar exited with code ${exitCode}`);
}

const cloudflareSkills = fs
.readdirSync(SKILLS_DIR)
.filter((entry) => fs.statSync(`${SKILLS_DIR}/${entry}`).isDirectory());

console.log(
`Fetched ${cloudflareSkills.length} Cloudflare Skills: ${cloudflareSkills.join(", ")}`,
);
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
"type": "module",
"scripts": {
"astro": "npx astro",
"prebuild": "npx tsx bin/fetch-skills.ts",
"build": "export NODE_OPTIONS='--max-old-space-size=6192' || set NODE_OPTIONS=\"--max-old-space-size=6192\" && npx astro build",
"typegen:worker": "npx wrangler types ./worker/worker-configuration.d.ts",
"check": "npm run check:astro && npm run check:worker",
"check:astro": "npx astro check",
"check:worker": "npx tsc --noEmit -p ./worker/tsconfig.json",
"predev": "npx tsx bin/fetch-skills.ts --soft",
"dev": "npx astro dev",
"format": "npm run format:core:fix && npm run format:data",
"format:core": "npx prettier \"**/*.{js,jsx,ts,tsx,mjs,css}\"",
Expand Down
Loading
Loading