Skip to content

Commit fa32a20

Browse files
authored
Get cloudflare skills from middlecache (#28658)
Removes the committed copy of Cloudflare Skills from the repo and replaces it with a fetch-at-build-time approach sourcing from middlecache.
1 parent 58ba4a2 commit fa32a20

File tree

347 files changed

+167
-54530
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

347 files changed

+167
-54530
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ distllms/
55
# generated types
66
.astro/
77

8+
# skills/ is fetched from middlecache via bin/fetch-skills.ts
9+
skills/
10+
811
# dependencies
912
node_modules/
1013

AGENTS.md

Lines changed: 59 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,11 @@ cloudflare-docs/
3535
├── public/ # Static files served as-is (images, redirects, robots.txt)
3636
├── worker/ # Cloudflare Worker for serving the site
3737
├── bin/ # Build scripts and CI helpers
38-
├── skills/ # Interactive exercises (astro-skills)
38+
│ └── fetch-skills.ts # Downloads skills.tar.gz from middlecache, extracts to skills/
39+
├── skills/ # Agent Skills served at /.well-known/skills/ — GENERATED, do not edit
40+
│ # Fetched from https://middlecache.ced.cloudflare.com/v1/cloudflare-skills/skills.tar.gz
41+
│ # by bin/fetch-skills.ts, which runs automatically via prebuild/predev hooks.
42+
│ # skills/ is in .gitignore and is NOT committed to the repository.
3943
├── astro.config.ts # Astro + Starlight configuration
4044
├── ec.config.mjs # Expressive Code (syntax highlighting) configuration
4145
├── package.json
@@ -63,19 +67,19 @@ All docs pages require frontmatter. Key fields:
6367

6468
```yaml
6569
---
66-
title: Page Title # Required
67-
description: SEO meta description # Recommended
68-
pcx_content_type: how-to # Page type (see below)
70+
title: Page Title # Required
71+
description: SEO meta description # Recommended
72+
pcx_content_type: how-to # Page type (see below)
6973
sidebar:
70-
order: 1 # Sort order in sidebar
71-
label: Custom Label # Override sidebar text
72-
tags: # Optional, validated against allowlist
74+
order: 1 # Sort order in sidebar
75+
label: Custom Label # Override sidebar text
76+
tags: # Optional, validated against allowlist
7377
- JavaScript
7478
- Workers
75-
products: # References to src/content/products/ entries
79+
products: # References to src/content/products/ entries
7680
- workers
77-
difficulty: Beginner # For tutorials: Beginner | Intermediate | Advanced
78-
reviewed: 2025-01-15 # YYYY-MM-DD of last content review
81+
difficulty: Beginner # For tutorials: Beginner | Intermediate | Advanced
82+
reviewed: 2025-01-15 # YYYY-MM-DD of last content review
7983
---
8084
```
8185

@@ -87,10 +91,10 @@ Tags are validated against an allowlist in `src/schemas/tags.ts`. Invalid tags w
8791

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

90-
| Character | Problem | Fix |
91-
|-----------|---------|-----|
92-
| `{` `}` | Interpreted as JS expressions | Wrap in backticks or use `\{` `\}` |
93-
| `<` `>` | Interpreted as JSX elements | Use `&lt;` `&gt;` or wrap in backticks |
94+
| Character | Problem | Fix |
95+
| --------- | ----------------------------- | -------------------------------------- |
96+
| `{` `}` | Interpreted as JS expressions | Wrap in backticks or use `\{` `\}` |
97+
| `<` `>` | Interpreted as JSX elements | Use `&lt;` `&gt;` or wrap in backticks |
9498

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

@@ -130,7 +134,14 @@ The full style guide is at `src/content/docs/style-guide/`. Key rules:
130134
Components are imported from `~/components` in MDX files:
131135

132136
```mdx
133-
import { Render, TypeScriptExample, WranglerConfig, Details } from "~/components";
137+
import {
138+
Render,
139+
TypeScriptExample,
140+
WranglerConfig,
141+
Details,
142+
} from "~/components";
143+
144+
;
134145
```
135146

136147
Components **must** be imported after the frontmatter block. Forgetting the import is a common mistake.
@@ -143,6 +154,7 @@ Renders a reusable partial from `src/content/partials/`. This is the primary con
143154
<Render file="partial-name" product="workers" />
144155

145156
<!-- With parameters: -->
157+
146158
<Render file="partial-name" product="workers" params={{ key: "value" }} />
147159
```
148160

@@ -164,14 +176,15 @@ Auto-transpiles TypeScript to JavaScript and shows both in synced tabs.
164176

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

167-
```mdx
179+
````mdx
168180
<WranglerConfig>
169181

170182
```toml
171183
name = "my-worker"
172184
main = "src/index.ts"
173185
compatibility_date = "$today"
174186
```
187+
````
175188

176189
</WranglerConfig>
177190
```
@@ -185,8 +198,8 @@ Starlight's `<Tabs>` and `<TabItem>` are re-exported from `~/components`:
185198
import { Tabs, TabItem } from "~/components";
186199
187200
<Tabs>
188-
<TabItem label="npm">npm install package</TabItem>
189-
<TabItem label="yarn">yarn add package</TabItem>
201+
<TabItem label="npm">npm install package</TabItem>
202+
<TabItem label="yarn">yarn add package</TabItem>
190203
</Tabs>
191204
```
192205

@@ -212,19 +225,19 @@ Content inside the collapsible section.
212225

213226
### Other frequently used components
214227

215-
| Component | Purpose |
216-
|-----------|---------|
217-
| `Plan` | Display plan availability (e.g., `<Plan type="enterprise" />`) |
218-
| `GlossaryTooltip` | Inline hover tooltip with glossary definition |
219-
| `InlineBadge` | Status badges: `<InlineBadge preset="beta" />` |
220-
| `LinkTitleCard` | Navigation card with icon, title, and description |
221-
| `DirectoryListing` | Auto-generated listing of child pages |
222-
| `YouTube` | Embed YouTube video by ID |
223-
| `Stream` | Embed Cloudflare Stream video |
224-
| `APIRequest` | Generate curl commands from the Cloudflare OpenAPI schema |
225-
| `DashButton` | "Go to Dashboard" button with validated deeplink |
226-
| `ListTutorials` | Auto-generated tutorial listing table |
227-
| `GitHubCode` | Fetch and display code from a GitHub repository |
228+
| Component | Purpose |
229+
| ------------------ | -------------------------------------------------------------- |
230+
| `Plan` | Display plan availability (e.g., `<Plan type="enterprise" />`) |
231+
| `GlossaryTooltip` | Inline hover tooltip with glossary definition |
232+
| `InlineBadge` | Status badges: `<InlineBadge preset="beta" />` |
233+
| `LinkTitleCard` | Navigation card with icon, title, and description |
234+
| `DirectoryListing` | Auto-generated listing of child pages |
235+
| `YouTube` | Embed YouTube video by ID |
236+
| `Stream` | Embed Cloudflare Stream video |
237+
| `APIRequest` | Generate curl commands from the Cloudflare OpenAPI schema |
238+
| `DashButton` | "Go to Dashboard" button with validated deeplink |
239+
| `ListTutorials` | Auto-generated tutorial listing table |
240+
| `GitHubCode` | Fetch and display code from a GitHub repository |
228241

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

@@ -315,27 +328,27 @@ A separate Semgrep workflow checks style guide compliance (dates, "coming soon"
315328

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

318-
| Collection | Location | Description |
319-
|-----------|----------|-------------|
320-
| `docs` | `src/content/docs/` | Main documentation pages (MDX) |
321-
| `partials` | `src/content/partials/` | Reusable content snippets (MDX) |
322-
| `changelog` | `src/content/changelog/` | Product changelogs (MDX) |
323-
| `glossary` | `src/content/glossary/` | Glossary terms (YAML) |
324-
| `products` | `src/content/products/` | Product metadata (YAML) |
325-
| `plans` | `src/content/plans/` | Plan/pricing data (YAML) |
326-
| `workers-ai-models` | `src/content/workers-ai-models/` | AI model definitions (JSON) |
327-
| `fields` | `src/content/fields/` | Ruleset engine field definitions (YAML) |
328-
| `learning-paths` | `src/content/learning-paths/` | Learning path definitions (JSON) |
331+
| Collection | Location | Description |
332+
| ------------------- | -------------------------------- | --------------------------------------- |
333+
| `docs` | `src/content/docs/` | Main documentation pages (MDX) |
334+
| `partials` | `src/content/partials/` | Reusable content snippets (MDX) |
335+
| `changelog` | `src/content/changelog/` | Product changelogs (MDX) |
336+
| `glossary` | `src/content/glossary/` | Glossary terms (YAML) |
337+
| `products` | `src/content/products/` | Product metadata (YAML) |
338+
| `plans` | `src/content/plans/` | Plan/pricing data (YAML) |
339+
| `workers-ai-models` | `src/content/workers-ai-models/` | AI model definitions (JSON) |
340+
| `fields` | `src/content/fields/` | Ruleset engine field definitions (YAML) |
341+
| `learning-paths` | `src/content/learning-paths/` | Learning path definitions (JSON) |
329342

330343
## Testing
331344

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

334-
| Suite | File pattern | Runtime |
335-
|-------|-------------|---------|
347+
| Suite | File pattern | Runtime |
348+
| ------- | ------------------ | --------------------------------- |
336349
| Workers | `*.worker.test.ts` | `@cloudflare/vitest-pool-workers` |
337-
| Node | `*.node.test.ts` | Node.js |
338-
| Astro | `*.astro.test.ts` | Astro Vite config |
350+
| Node | `*.node.test.ts` | Node.js |
351+
| Astro | `*.astro.test.ts` | Astro Vite config |
339352

340353
Run all tests: `npm run test`
341354

bin/fetch-skills.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
#!/usr/bin/env tsx
2+
3+
import { spawn } from "child_process";
4+
import fs from "fs";
5+
import { join } from "path";
6+
7+
import { downloadToDotTempIfNotPresent } from "../src/util/custom-loaders";
8+
9+
const MIDDLECACHE_BASE_URL = "https://middlecache.ced.cloudflare.com/";
10+
const SKILLS_MIDDLECACHE_PATH = "v1/cloudflare-skills/skills.tar.gz";
11+
const SKILLS_DOT_TMP_PATH = `middlecache/${SKILLS_MIDDLECACHE_PATH}`;
12+
const MANIFEST_MIDDLECACHE_PATH = "v1/cloudflare-skills/skills-manifest.json";
13+
const MANIFEST_DOT_TMP_PATH = `middlecache/${MANIFEST_MIDDLECACHE_PATH}`;
14+
const SKILLS_DIR = "./skills";
15+
16+
// --soft: warn and continue on failure instead of exiting non-zero.
17+
// Used by the predev hook so a network failure doesn't block local development.
18+
// --force: re-fetch even if skills/ already exists.
19+
const soft = process.argv.includes("--soft");
20+
const force = process.argv.includes("--force");
21+
22+
const fail = (message: string): never => {
23+
if (soft) {
24+
const hasExisting = fs.existsSync(SKILLS_DIR);
25+
console.warn(
26+
hasExisting
27+
? `Warning: ${message} — continuing with existing Cloudflare Skills`
28+
: `Warning: ${message} — skills/ does not exist, /.well-known/skills/ will not work`,
29+
);
30+
process.exit(0);
31+
}
32+
console.error(`Error: ${message}`);
33+
process.exit(1);
34+
};
35+
36+
if (fs.existsSync(SKILLS_DIR) && !force) {
37+
console.log(
38+
"/skills directory already exists, skipping fetch. (run `npx tsx bin/fetch-skills.ts --force` to re-fetch)",
39+
);
40+
process.exit(0);
41+
}
42+
43+
console.log("Fetching Cloudflare Skills from middlecache");
44+
45+
try {
46+
await Promise.all([
47+
downloadToDotTempIfNotPresent(
48+
`${MIDDLECACHE_BASE_URL}${SKILLS_MIDDLECACHE_PATH}`,
49+
SKILLS_DOT_TMP_PATH,
50+
),
51+
downloadToDotTempIfNotPresent(
52+
`${MIDDLECACHE_BASE_URL}${MANIFEST_MIDDLECACHE_PATH}`,
53+
MANIFEST_DOT_TMP_PATH,
54+
),
55+
]);
56+
} catch (err) {
57+
fail(`fetch failed: ${err}`);
58+
}
59+
60+
const tarballPath = join(".tmp", ...SKILLS_DOT_TMP_PATH.split("/"));
61+
62+
// Remove existing skills/ directory so stale Cloudflare Skills don't accumulate
63+
fs.rmSync(SKILLS_DIR, { recursive: true, force: true });
64+
fs.mkdirSync(SKILLS_DIR, { recursive: true });
65+
66+
// Extract the tarball from .tmp/ into ./skills/.
67+
// The archive contains skills/<skill-name>/... so we strip the leading "skills/"
68+
// component and extract into SKILLS_DIR.
69+
const tar = spawn(
70+
"tar",
71+
["--strip-components=1", "-xz", "-C", SKILLS_DIR, "-f", tarballPath],
72+
{ stdio: "inherit" },
73+
);
74+
75+
const exitCode = await new Promise<number | null>((resolve) =>
76+
tar.on("close", resolve),
77+
);
78+
79+
if (exitCode !== 0) {
80+
fail(`tar exited with code ${exitCode}`);
81+
}
82+
83+
const cloudflareSkills = fs
84+
.readdirSync(SKILLS_DIR)
85+
.filter((entry) => fs.statSync(`${SKILLS_DIR}/${entry}`).isDirectory());
86+
87+
console.log(
88+
`Fetched ${cloudflareSkills.length} Cloudflare Skills: ${cloudflareSkills.join(", ")}`,
89+
);

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44
"type": "module",
55
"scripts": {
66
"astro": "npx astro",
7+
"prebuild": "npx tsx bin/fetch-skills.ts",
78
"build": "export NODE_OPTIONS='--max-old-space-size=6192' || set NODE_OPTIONS=\"--max-old-space-size=6192\" && npx astro build",
89
"typegen:worker": "npx wrangler types ./worker/worker-configuration.d.ts",
910
"check": "npm run check:astro && npm run check:worker",
1011
"check:astro": "npx astro check",
1112
"check:worker": "npx tsc --noEmit -p ./worker/tsconfig.json",
13+
"predev": "npx tsx bin/fetch-skills.ts --soft",
1214
"dev": "npx astro dev",
1315
"format": "npm run format:core:fix && npm run format:data",
1416
"format:core": "npx prettier \"**/*.{js,jsx,ts,tsx,mjs,css}\"",

0 commit comments

Comments
 (0)