Skip to content

Commit 192a3b1

Browse files
Merge branch 'main' into main
2 parents 602ca22 + d0b80bc commit 192a3b1

File tree

176 files changed

+7769
-7749
lines changed

Some content is hidden

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

176 files changed

+7769
-7749
lines changed

.github/workflows/lint.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,32 @@ jobs:
3737
working-directory: "reference_gen"
3838
run: deno task doc
3939

40+
- name: Restore OG image cache
41+
uses: actions/cache@v4
42+
id: og-cache
43+
with:
44+
path: .og-cache
45+
key: og-images-${{ hashFiles('open_graph/**') }}
46+
restore-keys: og-images-
47+
4048
- name: Build
4149
env:
4250
ORAMA_PROJECT_ID: ${{ vars.ORAMA_PROJECT_ID }}
4351
ORAMA_DATASOURCE_ID: ${{ vars.ORAMA_DATASOURCE_ID }}
4452
ORAMA_PRIVATE_API_KEY: ${{ secrets.ORAMA_PRIVATE_API_KEY }}
53+
SKIP_OG: ${{ steps.og-cache.outputs.cache-hit == 'true' && '1' || '' }}
4554
run: deno task build
4655

56+
- name: Restore cached OG images
57+
if: steps.og-cache.outputs.cache-hit == 'true'
58+
run: cp -r .og-cache/_site/* _site/ 2>/dev/null || true
59+
60+
- name: Save OG images to cache
61+
if: steps.og-cache.outputs.cache-hit != 'true'
62+
run: |
63+
mkdir -p .og-cache/_site
64+
cd _site && find . -name "index.png" -exec cp --parents {} ../.og-cache/_site/ \;
65+
4766
- name: Run server
4867
run: deno run --allow-read=. --allow-net --allow-env --lock=deno.lock server.ts &
4968

.gitignore

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,14 @@ reference_gen/.gen-cache*.json
1515
reference_gen/.node-incremental-cache.json
1616

1717
# LLM documentation files (generated at build time)
18-
static/llms.txt
1918
static/llms-full.txt
19+
static/llms-summary.txt
20+
static/llms.json
21+
22+
# other generated content
23+
runtime/reference/std
24+
!runtime/reference/std/_overrides
25+
!runtime/reference/std/_overrides/**
2026

2127
# Orama search index files (generated at build time)
2228
static/orama-index.json

_components/CopyPage.tsx

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
export default function CopyPage({ file }: { file: string | undefined }) {
2+
if (!file || file.includes("[")) return null;
3+
4+
const markdownUrl = `https://docs.deno.com${file}`;
5+
const claudeUrl = `https://claude.ai/new?q=${
6+
encodeURIComponent(
7+
`Read this page from the Deno docs: ${markdownUrl} and answer questions about the content.`,
8+
)
9+
}`;
10+
11+
return (
12+
<div class="copy-page-split inline-flex shrink-0 rounded-full border-2 border-foreground-primary dark:border-background-tertiary">
13+
{/* Primary: directly copies the URL */}
14+
<button
15+
type="button"
16+
class="copy-page-main-btn flex items-center gap-2 px-4 py-2 text-sm font-semibold text-foreground-primary bg-transparent hover:bg-header-highlight dark:hover:text-background-primary rounded-l-full transition-colors cursor-pointer select-none"
17+
>
18+
<svg
19+
xmlns="http://www.w3.org/2000/svg"
20+
aria-hidden="true"
21+
fill="currentColor"
22+
width="12"
23+
height="12"
24+
viewBox="0 0 16 16"
25+
>
26+
<path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z" />
27+
<path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z" />
28+
</svg>
29+
<span class="copy-page-main-label">Copy page</span>
30+
</button>
31+
32+
{/* Visual divider between the two halves */}
33+
<span
34+
class="self-stretch w-0.5 bg-foreground-primary dark:bg-background-tertiary shrink-0"
35+
aria-hidden="true"
36+
>
37+
</span>
38+
39+
{/* Chevron: opens the popover panel */}
40+
<button
41+
type="button"
42+
class="copy-page-toggle-btn flex items-center px-3 py-2 text-foreground-primary bg-transparent hover:bg-header-highlight dark:hover:text-background-primary rounded-r-full transition-colors cursor-pointer select-none"
43+
popovertarget="copy-page-menu"
44+
aria-label="More copy options"
45+
aria-haspopup="menu"
46+
>
47+
<svg
48+
class="copy-page-chevron transition-transform duration-200"
49+
xmlns="http://www.w3.org/2000/svg"
50+
aria-hidden="true"
51+
fill="currentColor"
52+
width="12"
53+
height="12"
54+
viewBox="0 0 16 16"
55+
>
56+
<path d="M12.78 5.22a.749.749 0 0 1 0 1.06l-4.25 4.25a.749.749 0 0 1-1.06 0L3.22 6.28a.749.749 0 1 1 1.06-1.06L8 8.939l3.72-3.719a.749.749 0 0 1 1.06 0Z" />
57+
</svg>
58+
</button>
59+
60+
{/* Popover panel — lives in top layer, positioned via JS */}
61+
<div id="copy-page-menu" popover="auto" class="copy-page-panel">
62+
<a
63+
href={file}
64+
target="_blank"
65+
class="no-underline flex items-start gap-3 px-4 py-3 hover:bg-background-secondary dark:hover:bg-gray-800 transition-colors"
66+
>
67+
<svg
68+
xmlns="http://www.w3.org/2000/svg"
69+
aria-hidden="true"
70+
fill="currentColor"
71+
width="16"
72+
height="16"
73+
viewBox="0 0 16 16"
74+
class="mt-0.5 shrink-0"
75+
>
76+
<path d="M3.75 2h3.5a.75.75 0 0 1 0 1.5h-3.5a.25.25 0 0 0-.25.25v8.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-3.5a.75.75 0 0 1 1.5 0v3.5A1.75 1.75 0 0 1 12.25 14h-8.5A1.75 1.75 0 0 1 2 12.25v-8.5C2 2.784 2.784 2 3.75 2Zm6.854-1h4.146a.25.25 0 0 1 .25.25v4.146a.25.25 0 0 1-.427.177L13.03 4.03 9.28 7.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.75-3.75-1.543-1.543A.25.25 0 0 1 10.604 1Z" />
77+
</svg>
78+
<span>
79+
<span class="block text-sm font-medium">View Page as Markdown</span>
80+
<span class="block text-xs text-foreground-secondary mt-0.5">
81+
Open the Markdown file in a new tab
82+
</span>
83+
</span>
84+
</a>
85+
<a
86+
href={claudeUrl}
87+
target="_blank"
88+
class="no-underline flex items-start gap-3 px-4 py-3 border-t border-foreground-tertiary hover:bg-background-secondary dark:hover:bg-gray-800 transition-colors"
89+
>
90+
<svg
91+
xmlns="http://www.w3.org/2000/svg"
92+
aria-hidden="true"
93+
fill="currentColor"
94+
width="16"
95+
height="16"
96+
viewBox="0 0 24 24"
97+
class="mt-0.5 shrink-0"
98+
>
99+
<path d="M12 0C12 0 10.5 10.5 0 12C10.5 12 12 24 12 24C12 24 13.5 13.5 24 12C13.5 12 12 0 12 0Z" />
100+
</svg>
101+
<span>
102+
<span class="block text-sm font-medium">Open in Claude</span>
103+
<span class="block text-xs text-foreground-secondary mt-0.5">
104+
Ask Claude about this page
105+
</span>
106+
</span>
107+
</a>
108+
</div>
109+
</div>
110+
);
111+
}

_components/Footer.tsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
export default function Footer_new() {
22
return (
3-
<footer class="text-smaller bg-gray-50 dark:bg-gray-950 p-4 pt-12 sm:px-8 border-t border-t-foreground-tertiary">
3+
<footer className="text-smaller bg-gray-50 dark:bg-gray-950 p-4 pt-12 sm:px-8 border-t border-t-foreground-tertiary">
44
<nav className="flex flex-col gap-x-4 gap-y-12 max-w-7xl md:flex-row md:flex-wrap md:justify-between md:w-full md:gap-y-8 md:mx-auto">
55
{data.map((category) => (
6-
<section class="flex-auto">
7-
<h3 class="mb-2 uppercase font-bold text-foreground-primary whitespace-pre">
6+
<section className="flex-auto">
7+
<h3 className="mb-2 uppercase font-bold text-foreground-primary whitespace-pre">
88
{category.title}
99
</h3>
10-
<ul class="m-0 p-0 pl-3 border-l border-l-background-tertiary list-none">
10+
<ul className="m-0 p-0 pl-3 border-l border-l-background-tertiary list-none">
1111
{category.items.map((item) => (
1212
<li>
1313
<a
14-
class="block mb-2 hover:text-primary hover:underline"
14+
className="block mb-2 hover:text-primary hover:underline"
1515
href={item.to ?? item.href}
1616
dangerouslySetInnerHTML={{ __html: item.label }}
1717
/>
@@ -21,7 +21,7 @@ export default function Footer_new() {
2121
</section>
2222
))}
2323
</nav>
24-
<p class="m-0 mt-16 mx-auto text-center text-xs text-foreground-secondary">
24+
<p className="m-0 mt-16 mx-auto text-center text-xs text-foreground-secondary">
2525
Copyright © {new Date().getFullYear()} the Deno authors.
2626
</p>
2727
</footer>
@@ -153,6 +153,10 @@ const data = [
153153
label: "Privacy Policy",
154154
href: "/deploy/privacy_policy",
155155
},
156+
{
157+
label: "LLMs",
158+
href: "/llms.txt",
159+
},
156160
],
157161
},
158162
] satisfies FooterCategory[];

_components/Header.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ export default function (
1010
) {
1111
const hrefIsInCurrentSection = (href: string, currentSection: string) => {
1212
return href.includes(currentSection) ||
13-
href === "/services/" &&
14-
["deploy", "subhosting", "services", "sandboxes"].includes(
13+
href === "/deploy/" &&
14+
["deploy", "subhosting", "services", "sandbox"].includes(
1515
currentSection,
1616
);
1717
};

_components/TableOfContents.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export default function TableOfContents({ data, toc, hasSubNav }: {
1212

1313
return (
1414
<ul
15-
className={`toc-list hidden sticky p-4 pr-0 h-screen-minus-header overflow-y-auto border-l border-l-foreground-tertiary lg:block lg:w-full ${topClasses}`}
15+
className={`toc-list hidden sticky ${topClasses} h-screen-minus-header overflow-y-auto border-l border-l-foreground-tertiary p-4 pr-0 lg:flex lg:flex-col lg:w-full`}
1616
id="toc"
1717
>
1818
{toc.map((item: TableOfContentsItem_) => (

_config.ts

Lines changed: 92 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import title from "https://deno.land/x/lume_markdown_plugins@v0.7.0/title.ts";
1616
import toc from "https://deno.land/x/lume_markdown_plugins@v0.7.0/toc.ts";
1717
// See note below about GFM CSS
1818
// import { CSS as GFM_CSS } from "https://jsr.io/@deno/gfm/0.11.0/style.ts";
19+
import { walk } from "@std/fs";
20+
import { dirname } from "@std/path";
1921
import { log } from "lume/core/utils/log.ts";
2022
import anchor from "npm:markdown-it-anchor@9";
2123
import admonitionPlugin from "./markdown-it/admonition.ts";
@@ -27,6 +29,8 @@ import apiDocumentContentTypeMiddleware from "./middleware/apiDocContentType.ts"
2729
import createRoutingMiddleware from "./middleware/functionRoutes.ts";
2830
import createGAMiddleware from "./middleware/googleAnalytics.ts";
2931
import redirectsMiddleware from "./middleware/redirects.ts";
32+
import createLlmsFilesMiddleware from "./middleware/llmsFiles.ts";
33+
import createMarkdownSourceMiddleware from "./middleware/markdownSource.ts";
3034
import { toFileAndInMemory } from "./utils/redirects.ts";
3135
import { cliNow } from "./timeUtils.ts";
3236

@@ -72,10 +76,12 @@ const site = lume(
7276
server: {
7377
middlewares: [
7478
redirectsMiddleware,
79+
createMarkdownSourceMiddleware({ root: "_site" }),
7580
createRoutingMiddleware(),
7681
createGAMiddleware({
7782
addr: { transport: "tcp", hostname: "localhost", port: 3000 },
7883
}),
84+
createLlmsFilesMiddleware({ root: "_site" }),
7985
apiDocumentContentTypeMiddleware,
8086
],
8187
page404: "/404/",
@@ -137,14 +143,13 @@ site.copy("deploy/images");
137143
site.copy("deploy/classic/images");
138144
site.copy("deploy/kv/images");
139145
site.copy("deploy/tutorials/images");
140-
site.copy("sandboxes/images");
146+
site.copy("sandbox/images");
141147
site.copy("runtime/fundamentals/images");
142148
site.copy("runtime/getting_started/images");
143149
site.copy("runtime/reference/images");
144150
site.copy("runtime/contributing/images");
145151
site.copy("examples/tutorials/images");
146152
site.copy("deploy/manual/images");
147-
site.copy("deploy/images");
148153
site.copy("examples/scripts");
149154

150155
site.use(
@@ -185,33 +190,111 @@ site.addEventListener("afterBuild", async () => {
185190
/* NOTE: we used to get gfm.css from the jsr.io CDN, but now we simply have a local copy. This is because it needs to be placed on a CSS layer, which isn't possible with an imported file. */
186191
// Deno.writeTextFileSync(site.dest("gfm.css"), GFM_CSS);
187192

193+
// Copy lint rule markdown source files to _site so they're served at /lint/rules/*.md.
194+
// These files are excluded from Lume's processing pipeline (site.ignore) because
195+
// lint_rule.page.tsx handles page generation, but we still want the raw .md accessible.
196+
try {
197+
await Deno.mkdir(site.dest("lint/rules"), { recursive: true });
198+
for await (const entry of Deno.readDir("lint/rules")) {
199+
if (entry.isFile && entry.name.endsWith(".md")) {
200+
await Deno.copyFile(
201+
`lint/rules/${entry.name}`,
202+
site.dest(`lint/rules/${entry.name}`),
203+
);
204+
}
205+
}
206+
log.info("Copied lint rule markdown files to _site/lint/rules/");
207+
} catch (error) {
208+
log.error("Error copying lint rule markdown files: " + error);
209+
}
210+
188211
// Generate LLMs documentation files directly to _site directory
189212
if (Deno.env.get("BUILD_TYPE") == "FULL") {
190213
try {
191214
const { default: generateModule } = await import(
192215
"./generate_llms_files.ts"
193216
);
194-
const { collectFiles, generateLlmsTxt, generateLlmsFullTxt } =
195-
generateModule;
217+
const {
218+
collectFiles,
219+
generateLlmsSummaryTxt,
220+
generateLlmsFullTxt,
221+
generateLlmsJson,
222+
loadOramaSummaryIndex,
223+
} = generateModule;
196224

197225
log.info("Generating LLM-friendly documentation files...");
198226

199227
const files = await collectFiles();
200228
log.info(`Collected ${files.length} documentation files for LLMs`);
201229

202-
// Generate llms.txt
203-
const llmsTxt = generateLlmsTxt(files);
204-
Deno.writeTextFileSync(site.dest("llms.txt"), llmsTxt);
205-
log.info("Generated llms.txt in site root");
230+
// Generate llms-summary.txt
231+
const llmsSummaryTxt = generateLlmsSummaryTxt(files);
232+
Deno.writeTextFileSync(site.dest("llms-summary.txt"), llmsSummaryTxt);
233+
log.info("Generated llms-summary.txt in site root");
206234

207235
// Generate llms-full.txt
208236
const llmsFullTxt = generateLlmsFullTxt(files);
209237
Deno.writeTextFileSync(site.dest("llms-full.txt"), llmsFullTxt);
210238
log.info("Generated llms-full.txt in site root");
239+
240+
// Generate llms.json
241+
const oramaSummary = await loadOramaSummaryIndex();
242+
if (oramaSummary) {
243+
const llmsJson = generateLlmsJson(oramaSummary);
244+
Deno.writeTextFileSync(site.dest("llms.json"), llmsJson);
245+
log.info("Generated llms.json in site root");
246+
} else {
247+
log.warn(
248+
"Skipped llms.json generation (orama-index-summary.json not found)",
249+
);
250+
}
211251
} catch (error) {
212252
log.error("Error generating LLMs files:" + error);
213253
}
214254
}
255+
256+
// Copy source .md files to _site so AI agents can request them directly.
257+
// Excludes "reference/" (dynamically generated, no static .md source files).
258+
const contentDirs = [
259+
"runtime",
260+
"deploy",
261+
"sandbox",
262+
"subhosting",
263+
"examples",
264+
];
265+
let mdCopied = 0;
266+
let mdErrors = false;
267+
for (const dir of contentDirs) {
268+
// Skip directories that don't exist in this build
269+
try {
270+
await Deno.stat(dir);
271+
} catch (error) {
272+
if (!(error instanceof Deno.errors.NotFound)) {
273+
log.error(`Error accessing content directory ${dir}: ${error}`);
274+
}
275+
continue;
276+
}
277+
try {
278+
for await (
279+
const entry of walk(dir, { exts: [".md"], includeDirs: false })
280+
) {
281+
const destPath = site.dest(entry.path);
282+
await Deno.mkdir(dirname(destPath), { recursive: true });
283+
await Deno.copyFile(entry.path, destPath);
284+
mdCopied++;
285+
}
286+
} catch (error) {
287+
log.error(`Error copying markdown files from ${dir}: ${error}`);
288+
mdErrors = true;
289+
}
290+
}
291+
if (mdErrors) {
292+
log.warn(
293+
`Copied ${mdCopied} source markdown files to _site (some directories had errors, see above)`,
294+
);
295+
} else {
296+
log.info(`Copied ${mdCopied} source markdown files to _site`);
297+
}
215298
});
216299

217300
site.copy("reference_gen/gen/deno/page.css", "/api/deno/page.css");
@@ -289,7 +372,7 @@ site.data("apiCategories", {
289372
});
290373

291374
// Do more expensive operations if we're building the full site
292-
if (Deno.env.get("BUILD_TYPE") == "FULL") {
375+
if (Deno.env.get("BUILD_TYPE") == "FULL" && !Deno.env.has("SKIP_OG")) {
293376
// Use Lume's built in date function to get the last modified date of the file
294377
// site.data("date", "Git Last Modified");;
295378

0 commit comments

Comments
 (0)