mirror of
https://github.com/cloudflare/cloudflare-docs.git
synced 2026-01-11 20:06:58 +00:00
[Docs Site] Fixes all Astro TypeScript issues (#16457)
* fix: many many typescript issues chore: bump dependencies * fix: assign-pr script when no codeowners found * fix: ExternalResources TS * fix: check all functions * chore: minor dep bumps * chore: fixups * chore: merge fixups
This commit is contained in:
parent
42347a1d2f
commit
8ccb6d75ab
62 changed files with 4905 additions and 4227 deletions
5
.github/workflows/ci.yml
vendored
5
.github/workflows/ci.yml
vendored
|
|
@ -35,8 +35,11 @@ jobs:
|
|||
${{ runner.os }}-node-
|
||||
|
||||
- run: npm ci
|
||||
- run: npm run check
|
||||
|
||||
- run: npx astro build
|
||||
## TODO: formatting checks
|
||||
|
||||
- run: npm run build
|
||||
env:
|
||||
NODE_OPTIONS: "--max-old-space-size=4192"
|
||||
|
||||
|
|
|
|||
1
.node-version
Normal file
1
.node-version
Normal file
|
|
@ -0,0 +1 @@
|
|||
22
|
||||
|
|
@ -4,85 +4,85 @@ import core from "@actions/core";
|
|||
const navigationTimeout = 120000; // Set the navigation timeout to 120 seconds (120,000 milliseconds)
|
||||
|
||||
function arrayToHTMLList(array) {
|
||||
let html = "<ul>";
|
||||
let html = "<ul>";
|
||||
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
html += "<li>" + array[i] + "</li>";
|
||||
}
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
html += "<li>" + array[i] + "</li>";
|
||||
}
|
||||
|
||||
html += "</ul>";
|
||||
html += "</ul>";
|
||||
|
||||
return html;
|
||||
return html;
|
||||
}
|
||||
|
||||
async function checkLinks() {
|
||||
const browser = await puppeteer.launch({
|
||||
headless: "new",
|
||||
});
|
||||
const page = await browser.newPage();
|
||||
const browser = await puppeteer.launch({
|
||||
headless: "new",
|
||||
});
|
||||
const page = await browser.newPage();
|
||||
|
||||
const sitemapUrl = "https://developers.cloudflare.com/sitemap.xml";
|
||||
await page.goto(sitemapUrl, { timeout: navigationTimeout });
|
||||
const sitemapUrl = "https://developers.cloudflare.com/sitemap.xml";
|
||||
await page.goto(sitemapUrl, { timeout: navigationTimeout });
|
||||
|
||||
const sitemapLinks = await page.$$eval("url loc", (elements) =>
|
||||
elements.map((el) => el.textContent)
|
||||
);
|
||||
const sitemapLinks = await page.$$eval("url loc", (elements) =>
|
||||
elements.map((el) => el.textContent),
|
||||
);
|
||||
|
||||
const visitedLinks = [];
|
||||
const brokenLinks = [];
|
||||
const visitedLinks = [];
|
||||
const brokenLinks = [];
|
||||
|
||||
for (const link of sitemapLinks) {
|
||||
if (!link) {
|
||||
continue; // Skip if the link is empty
|
||||
}
|
||||
for (const link of sitemapLinks) {
|
||||
if (!link) {
|
||||
continue; // Skip if the link is empty
|
||||
}
|
||||
|
||||
await page.goto(link, {
|
||||
waitUntil: "networkidle0",
|
||||
timeout: navigationTimeout,
|
||||
});
|
||||
await page.goto(link, {
|
||||
waitUntil: "networkidle0",
|
||||
timeout: navigationTimeout,
|
||||
});
|
||||
|
||||
const pageLinks = await page.$$eval("a", (elements) =>
|
||||
elements.map((el) => el.href)
|
||||
);
|
||||
const pageLinks = await page.$$eval("a", (elements) =>
|
||||
elements.map((el) => el.href),
|
||||
);
|
||||
|
||||
for (const pageLink of pageLinks) {
|
||||
if (!pageLink || visitedLinks.includes(pageLink)) {
|
||||
continue; // Skip if the pageLink is empty or has already been visited
|
||||
}
|
||||
for (const pageLink of pageLinks) {
|
||||
if (!pageLink || visitedLinks.includes(pageLink)) {
|
||||
continue; // Skip if the pageLink is empty or has already been visited
|
||||
}
|
||||
|
||||
if (
|
||||
pageLink.includes("developers.cloudflare.com/api/operations/") ||
|
||||
pageLink.startsWith("/api/operations/")
|
||||
) {
|
||||
console.log(`Evaluating link: ${pageLink}`);
|
||||
await page.goto(pageLink, {
|
||||
waitUntil: "networkidle0",
|
||||
timeout: navigationTimeout,
|
||||
});
|
||||
visitedLinks.push(pageLink);
|
||||
if (
|
||||
pageLink.includes("developers.cloudflare.com/api/operations/") ||
|
||||
pageLink.startsWith("/api/operations/")
|
||||
) {
|
||||
console.log(`Evaluating link: ${pageLink}`);
|
||||
await page.goto(pageLink, {
|
||||
waitUntil: "networkidle0",
|
||||
timeout: navigationTimeout,
|
||||
});
|
||||
visitedLinks.push(pageLink);
|
||||
|
||||
const statusCode = await page.evaluate(() => {
|
||||
return {
|
||||
url: window.location.href,
|
||||
};
|
||||
});
|
||||
if (statusCode.url === "https://developers.cloudflare.com/api/") {
|
||||
brokenLinks.push(pageLink);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const statusCode = await page.evaluate(() => {
|
||||
return {
|
||||
url: window.location.href,
|
||||
};
|
||||
});
|
||||
if (statusCode.url === "https://developers.cloudflare.com/api/") {
|
||||
brokenLinks.push(pageLink);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await browser.close();
|
||||
console.log("Broken links:");
|
||||
console.log(brokenLinks);
|
||||
if (brokenLinks.length > 0) {
|
||||
core.setOutput("brokenLinks", arrayToHTMLList(brokenLinks));
|
||||
}
|
||||
process.exit(0);
|
||||
await browser.close();
|
||||
console.log("Broken links:");
|
||||
console.log(brokenLinks);
|
||||
if (brokenLinks.length > 0) {
|
||||
core.setOutput("brokenLinks", arrayToHTMLList(brokenLinks));
|
||||
}
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
checkLinks().catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
|
|
|||
355
bin/crawl.ts
355
bin/crawl.ts
|
|
@ -1,3 +1,4 @@
|
|||
// @ts-nocheck TODO: refactor this for modern Astro, or remove
|
||||
/**
|
||||
* 1. Crawl the `/public` directory (HTML files) and assert:
|
||||
* - all anchor tags (<a>) do not point to broken links
|
||||
|
|
@ -22,229 +23,229 @@ const VERBOSE = process.argv.includes("--verbose");
|
|||
const EXTERNALS = process.argv.includes("--externals");
|
||||
|
||||
async function walk(dir: string) {
|
||||
let files = await fs.readdir(dir);
|
||||
await Promise.all(
|
||||
files.map(async (name) => {
|
||||
let abs = join(dir, name);
|
||||
if (name.endsWith(".html")) return task(abs);
|
||||
let stats = await fs.stat(abs);
|
||||
if (stats.isDirectory()) return walk(abs);
|
||||
})
|
||||
);
|
||||
let files = await fs.readdir(dir);
|
||||
await Promise.all(
|
||||
files.map(async (name) => {
|
||||
let abs = join(dir, name);
|
||||
if (name.endsWith(".html")) return task(abs);
|
||||
let stats = await fs.stat(abs);
|
||||
if (stats.isDirectory()) return walk(abs);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
let CACHE = new Map<string, boolean>();
|
||||
|
||||
function HEAD(url: string): Promise<boolean> {
|
||||
let value = CACHE.has(url);
|
||||
if (value != null) return Promise.resolve(value);
|
||||
let value = CACHE.has(url);
|
||||
if (value != null) return Promise.resolve(value);
|
||||
|
||||
let options: https.RequestOptions = {
|
||||
method: "HEAD",
|
||||
headers: {
|
||||
"user-agent": "dev-docs",
|
||||
},
|
||||
};
|
||||
let options: https.RequestOptions = {
|
||||
method: "HEAD",
|
||||
headers: {
|
||||
"user-agent": "dev-docs",
|
||||
},
|
||||
};
|
||||
|
||||
if (url.startsWith("http://")) {
|
||||
options.agent = http.globalAgent;
|
||||
}
|
||||
if (url.startsWith("http://")) {
|
||||
options.agent = http.globalAgent;
|
||||
}
|
||||
|
||||
let req = https.request(url, options);
|
||||
let req = https.request(url, options);
|
||||
|
||||
return new Promise((r) => {
|
||||
req.on("error", (err) => {
|
||||
console.log(url, err);
|
||||
CACHE.set(url, false);
|
||||
return r(false);
|
||||
});
|
||||
return new Promise((r) => {
|
||||
req.on("error", (err) => {
|
||||
console.log(url, err);
|
||||
CACHE.set(url, false);
|
||||
return r(false);
|
||||
});
|
||||
|
||||
req.on("response", (res) => {
|
||||
let bool = res.statusCode > 199 && res.statusCode < 400;
|
||||
console.log({ url, bool });
|
||||
CACHE.set(url, bool);
|
||||
return r(bool);
|
||||
});
|
||||
req.on("response", (res) => {
|
||||
let bool = res.statusCode > 199 && res.statusCode < 400;
|
||||
console.log({ url, bool });
|
||||
CACHE.set(url, bool);
|
||||
return r(bool);
|
||||
});
|
||||
|
||||
req.end();
|
||||
});
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
interface Message {
|
||||
type: "error" | "warn";
|
||||
html?: string;
|
||||
value?: string;
|
||||
text?: string;
|
||||
type: "error" | "warn";
|
||||
html?: string;
|
||||
value?: string;
|
||||
text?: string;
|
||||
}
|
||||
|
||||
async function testREDIRECTS(file: string) {
|
||||
const textPlaceholder = await fs.readFile(file, "utf-8");
|
||||
const destinationURLRegex = new RegExp(/\/.*\/*? (\/.*\/)/);
|
||||
const textPlaceholder = await fs.readFile(file, "utf-8");
|
||||
const destinationURLRegex = new RegExp(/\/.*\/*? (\/.*\/)/);
|
||||
|
||||
for (const line of textPlaceholder.split(/[\r\n]+/)) {
|
||||
let exists = false;
|
||||
if (!line.startsWith("#")) {
|
||||
const result = line.match(destinationURLRegex);
|
||||
if (result !== null) {
|
||||
const match = result[1];
|
||||
if (match.startsWith('/api/')) {
|
||||
return;
|
||||
} else {
|
||||
let local = join(PUBDIR, match);
|
||||
exists = existsSync(local);
|
||||
for (const line of textPlaceholder.split(/[\r\n]+/)) {
|
||||
let exists = false;
|
||||
if (!line.startsWith("#")) {
|
||||
const result = line.match(destinationURLRegex);
|
||||
if (result !== null) {
|
||||
const match = result[1];
|
||||
if (match.startsWith("/api/")) {
|
||||
return;
|
||||
} else {
|
||||
let local = join(PUBDIR, match);
|
||||
exists = existsSync(local);
|
||||
|
||||
if (!exists) {
|
||||
REDIRECT_ERRORS.push(`\n ✘ ${result[0]}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!exists) {
|
||||
REDIRECT_ERRORS.push(`\n ✘ ${result[0]}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function task(file: string) {
|
||||
let html = await fs.readFile(file, "utf8");
|
||||
let html = await fs.readFile(file, "utf8");
|
||||
|
||||
let document = parse(html, {
|
||||
comment: false,
|
||||
blockTextElements: {
|
||||
script: false,
|
||||
noscript: false,
|
||||
style: false,
|
||||
pre: false,
|
||||
},
|
||||
});
|
||||
let document = parse(html, {
|
||||
comment: false,
|
||||
blockTextElements: {
|
||||
script: false,
|
||||
noscript: false,
|
||||
style: false,
|
||||
pre: false,
|
||||
},
|
||||
});
|
||||
|
||||
let placeholder = "http://foo.io";
|
||||
// build this file's URL; without "index.html" at end
|
||||
let self = file
|
||||
.substring(PUBDIR.length, file.length - 10)
|
||||
.replace(/\\+/g, "/");
|
||||
let url = new URL(self, placeholder);
|
||||
let placeholder = "http://foo.io";
|
||||
// build this file's URL; without "index.html" at end
|
||||
let self = file
|
||||
.substring(PUBDIR.length, file.length - 10)
|
||||
.replace(/\\+/g, "/");
|
||||
let url = new URL(self, placeholder);
|
||||
|
||||
let messages: Message[] = [];
|
||||
let items = document.querySelectorAll("a[href],img[src]");
|
||||
let messages: Message[] = [];
|
||||
let items = document.querySelectorAll("a[href],img[src]");
|
||||
|
||||
await Promise.all(
|
||||
items.map(async (item) => {
|
||||
let content = item.outerHTML;
|
||||
let target = item.getAttribute("src") || item.getAttribute("href");
|
||||
await Promise.all(
|
||||
items.map(async (item) => {
|
||||
let content = item.outerHTML;
|
||||
let target = item.getAttribute("src") || item.getAttribute("href");
|
||||
|
||||
if (!target && item.rawTagName === "a") {
|
||||
// parsing error; this is actually `<a ... href=/>
|
||||
if (/logo-link/.test(item.classNames)) return;
|
||||
return messages.push({
|
||||
type: "warn",
|
||||
html: content,
|
||||
text: `Missing "href" value`,
|
||||
});
|
||||
}
|
||||
if (!target && item.rawTagName === "a") {
|
||||
// parsing error; this is actually `<a ... href=/>
|
||||
if (/logo-link/.test(item.classNames)) return;
|
||||
return messages.push({
|
||||
type: "warn",
|
||||
html: content,
|
||||
text: `Missing "href" value`,
|
||||
});
|
||||
}
|
||||
|
||||
if (target && (target.startsWith("/api/") || target === "/api")) {
|
||||
return;
|
||||
}
|
||||
if (target && (target.startsWith("/api/") || target === "/api")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (target && target.includes("discord.gg/cloudflaredev")) {
|
||||
return messages.push({
|
||||
type: "error",
|
||||
html: content,
|
||||
text: "Use 'https://discord.cloudflare.com' instead of 'https://discord.gg/cloudflaredev'.",
|
||||
});
|
||||
}
|
||||
if (target && target.includes("discord.gg/cloudflaredev")) {
|
||||
return messages.push({
|
||||
type: "error",
|
||||
html: content,
|
||||
text: "Use 'https://discord.cloudflare.com' instead of 'https://discord.gg/cloudflaredev'.",
|
||||
});
|
||||
}
|
||||
|
||||
let exists: boolean;
|
||||
let external = false;
|
||||
let resolved = new URL(target, url);
|
||||
let exists: boolean;
|
||||
let external = false;
|
||||
let resolved = new URL(target, url);
|
||||
|
||||
if (!/https?/.test(resolved.protocol)) return;
|
||||
if (!/https?/.test(resolved.protocol)) return;
|
||||
|
||||
if ((external = resolved.origin !== placeholder)) {
|
||||
// only fetch external URLs with `--externals` flag
|
||||
exists = EXTERNALS ? await HEAD(target) : true;
|
||||
}
|
||||
if ((external = resolved.origin !== placeholder)) {
|
||||
// only fetch external URLs with `--externals` flag
|
||||
exists = EXTERNALS ? await HEAD(target) : true;
|
||||
}
|
||||
|
||||
if (!external) {
|
||||
let local = join(PUBDIR, resolved.pathname);
|
||||
if (!external) {
|
||||
let local = join(PUBDIR, resolved.pathname);
|
||||
|
||||
// is this HTML page? eg; "/foo/"
|
||||
if (extname(local).length === 0) {
|
||||
// TODO? log warning about no trailing slash
|
||||
if (!local.endsWith("/")) local += "/";
|
||||
local += "index.html";
|
||||
}
|
||||
// is this HTML page? eg; "/foo/"
|
||||
if (extname(local).length === 0) {
|
||||
// TODO? log warning about no trailing slash
|
||||
if (!local.endsWith("/")) local += "/";
|
||||
local += "index.html";
|
||||
}
|
||||
|
||||
exists = existsSync(local);
|
||||
}
|
||||
exists = existsSync(local);
|
||||
}
|
||||
|
||||
if (!exists) {
|
||||
messages.push({
|
||||
type: "error",
|
||||
html: content,
|
||||
value: target,
|
||||
});
|
||||
}
|
||||
})
|
||||
);
|
||||
if (!exists) {
|
||||
messages.push({
|
||||
type: "error",
|
||||
html: content,
|
||||
value: target,
|
||||
});
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
if (messages.length > 0) {
|
||||
let output = file.substring(PUBDIR.length);
|
||||
if (messages.length > 0) {
|
||||
let output = file.substring(PUBDIR.length);
|
||||
|
||||
messages.forEach((msg) => {
|
||||
if (msg.type === "error") {
|
||||
output += "\n ✘";
|
||||
ERRORS++;
|
||||
} else {
|
||||
output += "\n ⚠";
|
||||
WARNS++;
|
||||
}
|
||||
output += " " + (msg.text || msg.value);
|
||||
if (VERBOSE) output += "\n " + msg.html;
|
||||
});
|
||||
messages.forEach((msg) => {
|
||||
if (msg.type === "error") {
|
||||
output += "\n ✘";
|
||||
ERRORS++;
|
||||
} else {
|
||||
output += "\n ⚠";
|
||||
WARNS++;
|
||||
}
|
||||
output += " " + (msg.text || msg.value);
|
||||
if (VERBOSE) output += "\n " + msg.html;
|
||||
});
|
||||
|
||||
console.log(output + "\n");
|
||||
}
|
||||
console.log(output + "\n");
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await walk(PUBDIR);
|
||||
await walk(PUBDIR);
|
||||
|
||||
if (!ERRORS && !WARNS) {
|
||||
console.log("\n~> Regular files DONE~!\n\n");
|
||||
} else {
|
||||
let msg = "\n~> Regular files DONE with:";
|
||||
if (ERRORS > 0) {
|
||||
process.exitCode = 1;
|
||||
msg += "\n - " + ERRORS.toLocaleString() + " error(s)";
|
||||
}
|
||||
if (WARNS > 0) {
|
||||
msg += "\n - " + WARNS.toLocaleString() + " warning(s)";
|
||||
}
|
||||
console.log(msg + "\n\n");
|
||||
}
|
||||
if (!ERRORS && !WARNS) {
|
||||
console.log("\n~> Regular files DONE~!\n\n");
|
||||
} else {
|
||||
let msg = "\n~> Regular files DONE with:";
|
||||
if (ERRORS > 0) {
|
||||
process.exitCode = 1;
|
||||
msg += "\n - " + ERRORS.toLocaleString() + " error(s)";
|
||||
}
|
||||
if (WARNS > 0) {
|
||||
msg += "\n - " + WARNS.toLocaleString() + " warning(s)";
|
||||
}
|
||||
console.log(msg + "\n\n");
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err.stack || err);
|
||||
process.exit(1);
|
||||
console.error(err.stack || err);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
await testREDIRECTS(REDIRECT_FILE);
|
||||
if (REDIRECT_ERRORS.length == 0) {
|
||||
console.log("\n~> /content/_redirects file DONE~!\n\n");
|
||||
} else {
|
||||
let msg = "\n~> /content/_redirects file DONE with:";
|
||||
process.exitCode = 1;
|
||||
msg +=
|
||||
"\n - " +
|
||||
REDIRECT_ERRORS.length.toLocaleString() +
|
||||
" error(s)" +
|
||||
" (due to bad destination URLs)" +
|
||||
"\n\n";
|
||||
for (let i = 0; i < REDIRECT_ERRORS.length; i++) {
|
||||
msg += REDIRECT_ERRORS[i];
|
||||
}
|
||||
console.log(msg + "\n\n");
|
||||
}
|
||||
await testREDIRECTS(REDIRECT_FILE);
|
||||
if (REDIRECT_ERRORS.length == 0) {
|
||||
console.log("\n~> /content/_redirects file DONE~!\n\n");
|
||||
} else {
|
||||
let msg = "\n~> /content/_redirects file DONE with:";
|
||||
process.exitCode = 1;
|
||||
msg +=
|
||||
"\n - " +
|
||||
REDIRECT_ERRORS.length.toLocaleString() +
|
||||
" error(s)" +
|
||||
" (due to bad destination URLs)" +
|
||||
"\n\n";
|
||||
for (let i = 0; i < REDIRECT_ERRORS.length; i++) {
|
||||
msg += REDIRECT_ERRORS[i];
|
||||
}
|
||||
console.log(msg + "\n\n");
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err.stack || err);
|
||||
process.exit(1);
|
||||
console.error(err.stack || err);
|
||||
process.exit(1);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,91 +0,0 @@
|
|||
import * as fs from 'fs/promises';
|
||||
import { join, resolve } from 'path';
|
||||
import { Callback, Pool } from './pool';
|
||||
import { options } from './prettier.config';
|
||||
|
||||
import type { Result } from './worker';
|
||||
|
||||
const ROOT = resolve('.');
|
||||
const ROOTLEN = ROOT.length + 1;
|
||||
|
||||
const isSILENT = process.argv.includes('--quiet');
|
||||
const isCHECK = process.argv.includes('--check');
|
||||
|
||||
const isFILE = /\.(mdx?|[mc]?[tj]sx?|json|ya?ml|s?css)$/;
|
||||
|
||||
// Unknown languages / missing parsers
|
||||
const Missing = new Set<string>();
|
||||
|
||||
let warns = 0;
|
||||
let errors = 0;
|
||||
|
||||
const task = new Pool({
|
||||
script: join(ROOT, 'bin/worker.ts'),
|
||||
spawn: {
|
||||
execArgv: ['--loader', 'tsm'],
|
||||
env: { NODE_NO_WARNINGS: '1' },
|
||||
workerData: { isCHECK, ROOTLEN, options },
|
||||
},
|
||||
});
|
||||
|
||||
const handler: Callback<Result> = (err, result) => {
|
||||
if (err) return console.error(err);
|
||||
|
||||
errors += result.error;
|
||||
warns += result.warn;
|
||||
|
||||
if (result.warn && isCHECK) process.exitCode = 1;
|
||||
else if (result.error) process.exitCode = 1;
|
||||
|
||||
result.missing.forEach(x => {
|
||||
Missing.add(x);
|
||||
});
|
||||
};
|
||||
|
||||
async function walk(dir: string): Promise<void> {
|
||||
let files = await fs.readdir(dir);
|
||||
|
||||
await Promise.all(
|
||||
files.map(fname => {
|
||||
let absolute = join(dir, fname);
|
||||
|
||||
if (fname === '.github') return walk(absolute);
|
||||
if (fname === 'node_modules' || fname === 'public') return;
|
||||
if (/^[._]/.test(fname) || /\.hbs$/.test(fname)) return;
|
||||
// TODO: temporarily disable `*.hbs` formatting
|
||||
|
||||
if (isFILE.test(fname)) {
|
||||
return task.run(absolute, handler);
|
||||
}
|
||||
|
||||
return fs.stat(absolute).then(stats => {
|
||||
if (stats.isDirectory()) return walk(absolute);
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
await walk(ROOT);
|
||||
|
||||
if (Missing.size > 0) {
|
||||
let langs = [...Missing].sort();
|
||||
console.warn('\n\nMissing parser for language(s):\n');
|
||||
console.warn(langs.map(x => ' - ' + x).join('\n'));
|
||||
}
|
||||
|
||||
if (errors || warns) {
|
||||
console.error('\n');
|
||||
if (errors) {
|
||||
console.error('Finished with %d error(s)', errors);
|
||||
}
|
||||
if (isCHECK && warns) {
|
||||
console.error('Finished with %d warning(s)', warns);
|
||||
}
|
||||
console.error('\n');
|
||||
isSILENT || process.exit(1);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err.stack || err);
|
||||
process.exit(1);
|
||||
}
|
||||
223
bin/format.ts
223
bin/format.ts
|
|
@ -1,223 +0,0 @@
|
|||
import prettier from 'prettier';
|
||||
import * as fs from 'fs/promises';
|
||||
import { join, resolve } from 'path';
|
||||
import { langs } from './prism.config';
|
||||
import { options } from './prettier.config';
|
||||
|
||||
const ROOT = resolve('.');
|
||||
const ROOTLEN = ROOT.length + 1;
|
||||
|
||||
const isSILENT = process.argv.includes('--quiet');
|
||||
const isCHECK = process.argv.includes('--check');
|
||||
const isBAIL = process.argv.includes('--bail');
|
||||
|
||||
const isMD = /\.md$/;
|
||||
const isFILE = /\.([mc]?[tj]sx?|json|ya?ml|s?css)$/;
|
||||
const YAML = /^\s*(---[^]+(?:---\r?\n))/;
|
||||
|
||||
// Unknown languages / missing parsers
|
||||
const Missing = new Set<string>();
|
||||
|
||||
// Prism languages to ignore
|
||||
const Ignores = new Set(['txt', 'diff', 'bash', 'sh', 'toml']);
|
||||
|
||||
// Prism language -> prettier parser
|
||||
export const Parsers: Record<string, prettier.BuiltInParserName> = {
|
||||
js: 'babel',
|
||||
javascript: 'babel',
|
||||
|
||||
md: 'mdx',
|
||||
markdown: 'mdx',
|
||||
mdx: 'mdx',
|
||||
|
||||
json: 'json',
|
||||
json5: 'json5',
|
||||
|
||||
ts: 'typescript',
|
||||
typescript: 'typescript',
|
||||
|
||||
gql: 'graphql',
|
||||
graphql: 'graphql',
|
||||
|
||||
xml: 'html',
|
||||
html: 'html',
|
||||
svelte: 'html',
|
||||
hbs: 'html',
|
||||
vue: 'vue',
|
||||
|
||||
yaml: 'yaml',
|
||||
yml: 'yaml',
|
||||
};
|
||||
|
||||
interface Metadata {
|
||||
file: string;
|
||||
lang: string;
|
||||
content?: string;
|
||||
}
|
||||
|
||||
let warns = 0;
|
||||
let errors = 0;
|
||||
|
||||
function toError(msg: string, meta: Metadata): void {
|
||||
errors++;
|
||||
|
||||
msg += '\n~> file: ' + meta.file.substring(ROOTLEN);
|
||||
msg += '\n~> language: ' + meta.lang;
|
||||
if (meta.content) {
|
||||
msg += '\n~> code: ';
|
||||
meta.content.split(/\r?\n/g).forEach(txt => {
|
||||
msg += '\n\t' + txt;
|
||||
});
|
||||
}
|
||||
console.error('\n\n' + msg);
|
||||
}
|
||||
|
||||
function format(code: string, lang: string) {
|
||||
let parser = Parsers[lang];
|
||||
if (parser == null) {
|
||||
Missing.add(lang);
|
||||
return code;
|
||||
}
|
||||
return prettier.format(code, { ...options, parser });
|
||||
}
|
||||
|
||||
async function write(file: string, output: string, isMatch: boolean) {
|
||||
let txt = isCHECK ? 'PASS' : 'OK';
|
||||
if (isCHECK && !isMatch) {
|
||||
process.exitCode = 1;
|
||||
txt = 'FAIL';
|
||||
warns++;
|
||||
}
|
||||
isCHECK || (await fs.writeFile(file, output));
|
||||
process.stdout.write(`[${txt}] ${file.substring(ROOTLEN)}\n`);
|
||||
}
|
||||
|
||||
async function prettify(file: string, lang?: string) {
|
||||
let extn = file.substring(file.lastIndexOf('.') + 1);
|
||||
|
||||
let input = await fs.readFile(file, 'utf8');
|
||||
let output = format(input, lang || langs[extn] || extn);
|
||||
await write(file, output, input === output);
|
||||
}
|
||||
|
||||
async function walk(dir: string): Promise<void> {
|
||||
let files = await fs.readdir(dir);
|
||||
|
||||
await Promise.all(
|
||||
files.map(fname => {
|
||||
let absolute = join(dir, fname);
|
||||
|
||||
if (fname === '.github') return walk(absolute);
|
||||
if (fname === 'node_modules' || fname === 'public') return;
|
||||
if (/^[._]/.test(fname) || /\.hbs$/.test(fname)) return;
|
||||
// TODO: temporarily disable `*.hbs` formatting
|
||||
|
||||
if (isMD.test(fname)) return markdown(absolute);
|
||||
if (isFILE.test(fname)) return prettify(absolute);
|
||||
|
||||
return fs.stat(absolute).then(stats => {
|
||||
if (stats.isDirectory()) return walk(absolute);
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
async function markdown(file: string): Promise<void> {
|
||||
let last = 0;
|
||||
let output = '';
|
||||
let match: RegExpExecArray | null;
|
||||
let input = await fs.readFile(file, 'utf8');
|
||||
let BACKTICKS = /^( +)?([`]{3})([A-Za-z]+?)\n([^]+?)(\2)/gm;
|
||||
|
||||
while ((match = BACKTICKS.exec(input))) {
|
||||
let [full, lead, open, hint, inner, close] = match;
|
||||
|
||||
let current = match.index;
|
||||
output += input.substring(last, current);
|
||||
|
||||
lead = lead || '';
|
||||
hint = hint || 'txt';
|
||||
let lang = (langs[hint] || hint).toLowerCase();
|
||||
|
||||
if (Ignores.has(lang) || Missing.has(lang)) {
|
||||
last = current + full.length;
|
||||
output += full;
|
||||
continue;
|
||||
}
|
||||
|
||||
let isYAML = YAML.exec(inner);
|
||||
let frontmatter = (isYAML && isYAML[1]) || '';
|
||||
|
||||
if (frontmatter.length > 0) {
|
||||
// TODO: parse for `format: false` value
|
||||
inner = inner.substring(frontmatter.length + lead.length);
|
||||
|
||||
if (lead.length > 0) {
|
||||
frontmatter = frontmatter.replace(new RegExp('\n' + lead, 'g'), '\n');
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
var pretty = format(inner, lang).trimEnd();
|
||||
} catch (err) {
|
||||
toError('Error formatting code snippet!', { file, lang });
|
||||
if (isBAIL) throw err;
|
||||
return console.error(err.message || err);
|
||||
}
|
||||
|
||||
output += lead + '```' + lang + '\n';
|
||||
|
||||
if (lead.length > 0) {
|
||||
(frontmatter + pretty).split(/\r?\n/g).forEach(line => {
|
||||
output += lead + line + '\n';
|
||||
});
|
||||
} else {
|
||||
output += frontmatter + pretty + '\n';
|
||||
}
|
||||
|
||||
output += lead + '```';
|
||||
|
||||
last = current + full.length;
|
||||
}
|
||||
|
||||
if (last && last < input.length) {
|
||||
output += input.substring(last);
|
||||
} else if (last < 1) {
|
||||
output = input;
|
||||
}
|
||||
|
||||
try {
|
||||
output = format(output, 'mdx');
|
||||
} catch (err) {
|
||||
toError('Error w/ final MDX format!', { file, lang: 'mdx' });
|
||||
if (isBAIL) throw err;
|
||||
return console.error(err.stack || err);
|
||||
}
|
||||
|
||||
await write(file, output, input === output);
|
||||
}
|
||||
|
||||
try {
|
||||
await walk(ROOT);
|
||||
|
||||
if (Missing.size > 0) {
|
||||
let langs = [...Missing].sort();
|
||||
console.warn('\n\nMissing parser for language(s):\n');
|
||||
console.warn(langs.map(x => ' - ' + x).join('\n'));
|
||||
}
|
||||
|
||||
if (errors || warns) {
|
||||
console.error('\n');
|
||||
if (errors) {
|
||||
console.error('Finished with %d error(s)', errors);
|
||||
}
|
||||
if (isCHECK && warns) {
|
||||
console.error('Finished with %d warning(s)', warns);
|
||||
}
|
||||
console.error('\n');
|
||||
isSILENT || process.exit(1);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err.stack || err);
|
||||
process.exit(1);
|
||||
}
|
||||
111
bin/highlight.ts
111
bin/highlight.ts
|
|
@ -1,111 +0,0 @@
|
|||
/**
|
||||
* tsm bin/highlight.ts replace
|
||||
* tsm bin/highlight.ts restore
|
||||
*/
|
||||
import * as fs from 'fs/promises';
|
||||
import { join, resolve } from 'path';
|
||||
import { highlight } from './prism.config';
|
||||
|
||||
const ROOT = resolve('.');
|
||||
const ROOTLEN = ROOT.length + 1;
|
||||
const CONTENT = join(ROOT, 'content');
|
||||
|
||||
const REPLACE = process.argv.includes('replace');
|
||||
const RESTORE = !REPLACE && process.argv.includes('restore');
|
||||
|
||||
const isMD = /\.md$/;
|
||||
const isBACKUP = /\.md\.backup$/;
|
||||
// const YAML = /^\s*(---[^]+(?:---\r?\n))/;
|
||||
|
||||
async function walk(dir: string): Promise<void> {
|
||||
let ignores = new Set(['static', 'media']);
|
||||
let files = await fs.readdir(dir, { withFileTypes: true });
|
||||
|
||||
await Promise.all(
|
||||
files.map(dirent => {
|
||||
let fname = dirent.name;
|
||||
let absolute = join(dir, fname);
|
||||
|
||||
if (isMD.test(fname)) {
|
||||
if (REPLACE) return markdown(absolute);
|
||||
else return;
|
||||
}
|
||||
|
||||
if (isBACKUP.test(fname)) {
|
||||
if (RESTORE) return restore(absolute);
|
||||
else return;
|
||||
}
|
||||
|
||||
if (dirent.isDirectory() && !ignores.has(fname)) {
|
||||
return walk(absolute);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// mv foo.md.backup foo.md
|
||||
async function restore(backup: string) {
|
||||
let original = backup.substring(0, backup.length - 7);
|
||||
await fs.copyFile(backup, original);
|
||||
await fs.unlink(backup);
|
||||
}
|
||||
|
||||
// @modified `bin/format.ts` function
|
||||
async function markdown(file: string): Promise<void> {
|
||||
let last = 0;
|
||||
let output = '';
|
||||
let match: RegExpExecArray | null;
|
||||
let input = await fs.readFile(file, 'utf8');
|
||||
let BACKTICKS = /^(\s+)?([`]{3})([A-Za-z]+)?\r?\n([^]+?)(\2)/gm;
|
||||
|
||||
while ((match = BACKTICKS.exec(input))) {
|
||||
let current = match.index;
|
||||
let [full, ws, open, hint, inner, close] = match;
|
||||
output += input.substring(last, current);
|
||||
|
||||
ws = ws || '';
|
||||
|
||||
// codeblock => HTML markup
|
||||
let lang = (hint || 'txt').toLowerCase();
|
||||
|
||||
// dedent codeblock, only if indented
|
||||
let [spaces] = ws.match(/[ ]+$/) || '';
|
||||
if (spaces && spaces.length > 0) {
|
||||
let rgx = new RegExp('^([ ]){' + spaces.length + '}', 'gm');
|
||||
inner = inner.replace(rgx, '');
|
||||
}
|
||||
|
||||
let html = highlight(inner, lang, file.substring(ROOTLEN));
|
||||
|
||||
// prevent hugo from looking at "{{<" pattern
|
||||
output += "\n\n" + ' '.repeat(spaces?.length ?? 0) + '{{<raw>}}' + html.replace(/\{\{\</g, '{\\{<') + '{{</raw>}}';
|
||||
|
||||
last = current + full.length;
|
||||
}
|
||||
|
||||
if (last && last < input.length) {
|
||||
output += input.substring(last);
|
||||
} else if (last < 1) {
|
||||
output = input;
|
||||
}
|
||||
|
||||
let label = 'SKIP';
|
||||
|
||||
if (last > 0) {
|
||||
label = 'PASS';
|
||||
//~> cp foo.md foo.md.backup
|
||||
await fs.writeFile(file + '.backup', input);
|
||||
// overwrite with HTML replacements
|
||||
await fs.writeFile(file, output);
|
||||
}
|
||||
|
||||
process.stdout.write(`[${label}] ` + file.substring(ROOTLEN) + '\n');
|
||||
}
|
||||
|
||||
try {
|
||||
await walk(CONTENT);
|
||||
console.log('~> DONE~!');
|
||||
} catch (err) {
|
||||
console.error(err.stack || err);
|
||||
process.exit(1);
|
||||
}
|
||||
|
|
@ -1,33 +1,33 @@
|
|||
import lzstring from "lz-string";
|
||||
|
||||
export function serialiseWorker(code: string): FormData {
|
||||
const formData = new FormData();
|
||||
const formData = new FormData();
|
||||
|
||||
const metadata = {
|
||||
main_module: "index.js",
|
||||
};
|
||||
const metadata = {
|
||||
main_module: "index.js",
|
||||
};
|
||||
|
||||
formData.set(
|
||||
"index.js",
|
||||
new Blob([code], {
|
||||
type: "application/javascript+module",
|
||||
}),
|
||||
"index.js"
|
||||
);
|
||||
formData.set(
|
||||
"index.js",
|
||||
new Blob([code], {
|
||||
type: "application/javascript+module",
|
||||
}),
|
||||
"index.js",
|
||||
);
|
||||
|
||||
formData.set(
|
||||
"metadata",
|
||||
new Blob([JSON.stringify(metadata)], { type: "application/json" })
|
||||
);
|
||||
formData.set(
|
||||
"metadata",
|
||||
new Blob([JSON.stringify(metadata)], { type: "application/json" }),
|
||||
);
|
||||
|
||||
return formData;
|
||||
return formData;
|
||||
}
|
||||
|
||||
export async function compressWorker(worker: FormData) {
|
||||
const serialisedWorker = new Response(worker);
|
||||
return lzstring.compressToEncodedURIComponent(
|
||||
`${serialisedWorker.headers.get(
|
||||
"content-type"
|
||||
)}:${await serialisedWorker.text()}`
|
||||
);
|
||||
const serialisedWorker = new Response(worker);
|
||||
return lzstring.compressToEncodedURIComponent(
|
||||
`${serialisedWorker.headers.get(
|
||||
"content-type",
|
||||
)}:${await serialisedWorker.text()}`,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
139
bin/pool.ts
139
bin/pool.ts
|
|
@ -1,139 +0,0 @@
|
|||
import { AsyncResource } from 'async_hooks';
|
||||
import { isMainThread, parentPort, Worker } from 'worker_threads';
|
||||
import { EventEmitter } from 'events';
|
||||
import { cpus } from 'os';
|
||||
|
||||
import type { WorkerOptions } from 'worker_threads';
|
||||
|
||||
const FREE = Symbol('FREE');
|
||||
|
||||
export interface Options {
|
||||
script: URL | string;
|
||||
spawn?: WorkerOptions;
|
||||
max?: number;
|
||||
}
|
||||
|
||||
export type Callback<T = unknown> = (err: Error | null, result: T) => Promise<void> | void;
|
||||
|
||||
export interface Task<I = any, R = unknown> {
|
||||
input: I;
|
||||
handle?: Callback<R>;
|
||||
resolve(value: R): void;
|
||||
reject(reason?: any): void;
|
||||
}
|
||||
|
||||
async function exec<R = unknown>(callback: Callback<R>, err: Error | null, result: R | null) {
|
||||
let item = new AsyncResource('Task');
|
||||
let output = await item.runInAsyncScope(callback, null, err, result);
|
||||
item.emitDestroy(); // single use
|
||||
return output;
|
||||
}
|
||||
|
||||
export class Pool extends EventEmitter {
|
||||
jobs: Map<number, Callback<unknown>>;
|
||||
idles: Worker[];
|
||||
workers: Set<Worker>;
|
||||
script: URL | string;
|
||||
tasks: Task[];
|
||||
|
||||
private options?: WorkerOptions;
|
||||
private exit?: boolean;
|
||||
|
||||
constructor(options: Options) {
|
||||
super();
|
||||
|
||||
this.idles = [];
|
||||
this.script = options.script;
|
||||
this.workers = new Set();
|
||||
this.jobs = new Map();
|
||||
this.tasks = [];
|
||||
|
||||
this.options = {
|
||||
execArgv: [],
|
||||
...options.spawn,
|
||||
};
|
||||
|
||||
let i = 0;
|
||||
let max = Math.max(1, options.max || cpus().length);
|
||||
while (i++ < max) this.spawn();
|
||||
|
||||
this.on(FREE, () => {
|
||||
if (this.tasks.length > 0) {
|
||||
let task = this.tasks.shift()!;
|
||||
this.dispatch(task);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private dispatch(task: Task): void {
|
||||
if (this.idles.length < 1) {
|
||||
this.tasks.push(task);
|
||||
return;
|
||||
}
|
||||
|
||||
let worker = this.idles.pop()!;
|
||||
|
||||
worker.once('message', async result => {
|
||||
if (task.handle) {
|
||||
result = await exec(task.handle, null, result);
|
||||
}
|
||||
|
||||
worker.removeAllListeners('message');
|
||||
this.idles.push(worker);
|
||||
task.resolve(result);
|
||||
this.emit(FREE);
|
||||
});
|
||||
|
||||
worker.once('error', async err => {
|
||||
let result;
|
||||
if (task.handle) {
|
||||
result = await exec(task.handle, err, null);
|
||||
}
|
||||
|
||||
// replace current/dead worker
|
||||
this.workers.delete(worker);
|
||||
|
||||
// TODO: options.retry
|
||||
this.spawn();
|
||||
|
||||
if (result == null) task.reject(err);
|
||||
else task.resolve(result);
|
||||
});
|
||||
|
||||
worker.postMessage(task.input);
|
||||
}
|
||||
|
||||
spawn(options?: WorkerOptions): void {
|
||||
if (this.exit) return;
|
||||
|
||||
let worker = new Worker(this.script, {
|
||||
...this.options,
|
||||
...options,
|
||||
});
|
||||
|
||||
this.workers.add(worker);
|
||||
this.idles.push(worker);
|
||||
this.emit(FREE);
|
||||
}
|
||||
|
||||
run<T, R>(input: T, handle?: Callback<R>) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.dispatch({ input, handle, resolve, reject } as Task);
|
||||
});
|
||||
}
|
||||
|
||||
close(): void {
|
||||
this.exit = true;
|
||||
for (let worker of this.workers) {
|
||||
worker.terminate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const isMain = isMainThread;
|
||||
export const isWorker = !isMainThread;
|
||||
|
||||
export function listen<R = unknown>(callback: Callback<R>) {
|
||||
if (parentPort) parentPort.on('message', callback);
|
||||
else throw new Error('Missing `parentPort` link');
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
import type { Options } from 'prettier';
|
||||
|
||||
export const options: Options = {
|
||||
arrowParens: 'avoid',
|
||||
bracketSameLine: false,
|
||||
bracketSpacing: true,
|
||||
embeddedLanguageFormatting: 'auto',
|
||||
htmlWhitespaceSensitivity: 'css',
|
||||
insertPragma: false,
|
||||
jsxSingleQuote: false,
|
||||
printWidth: 100,
|
||||
proseWrap: 'preserve',
|
||||
quoteProps: 'consistent',
|
||||
requirePragma: false,
|
||||
semi: true,
|
||||
singleQuote: true,
|
||||
tabWidth: 2,
|
||||
trailingComma: 'es5',
|
||||
useTabs: false,
|
||||
vueIndentScriptAndStyle: true,
|
||||
};
|
||||
|
|
@ -1,390 +0,0 @@
|
|||
import Prism from "prismjs";
|
||||
import rangeParser from "parse-numeric-range";
|
||||
|
||||
import type { Token, TokenStream } from "prismjs";
|
||||
|
||||
globalThis.Prism = Prism;
|
||||
import "prismjs/components/prism-bash.min.js";
|
||||
import "prismjs/components/prism-c.min.js";
|
||||
import "prismjs/components/prism-csharp.min.js";
|
||||
import "prismjs/components/prism-csv.min.js";
|
||||
import "prismjs/components/prism-diff.min.js";
|
||||
import "prismjs/components/prism-git.min.js";
|
||||
import "prismjs/components/prism-go.min.js";
|
||||
import "prismjs/components/prism-graphql.min.js";
|
||||
import "prismjs/components/prism-hcl.min.js";
|
||||
import "prismjs/components/prism-http.min.js";
|
||||
import "prismjs/components/prism-ini.min.js";
|
||||
import "prismjs/components/prism-java.min.js";
|
||||
import "prismjs/components/prism-json.min.js";
|
||||
import "prismjs/components/prism-jsx.min.js";
|
||||
import "prismjs/components/prism-markdown.min.js";
|
||||
import "prismjs/components/prism-perl.min.js";
|
||||
import "prismjs/components/prism-php.min.js";
|
||||
import "prismjs/components/prism-powershell.min.js";
|
||||
import "prismjs/components/prism-python.min.js";
|
||||
import "prismjs/components/prism-ruby.min.js";
|
||||
import "prismjs/components/prism-rust.min.js";
|
||||
import "prismjs/components/prism-sql.min.js";
|
||||
import "prismjs/components/prism-typescript.min.js";
|
||||
import "prismjs/components/prism-toml.min.js";
|
||||
import "prismjs/components/prism-yaml.min.js";
|
||||
import "prismjs/components/prism-kotlin.min.js";
|
||||
import "prismjs/components/prism-swift.min.js";
|
||||
import { compressWorker, serialiseWorker } from "./playground";
|
||||
|
||||
// Custom `shell` grammar
|
||||
Prism.languages.sh = {
|
||||
comment: {
|
||||
pattern: /(^|[^'{\\$])#.*/,
|
||||
alias: "unselectable",
|
||||
lookbehind: true,
|
||||
},
|
||||
|
||||
directory: {
|
||||
pattern: /^[^\r\n$*!]+(?=[$])/m,
|
||||
alias: "unselectable",
|
||||
},
|
||||
|
||||
command: {
|
||||
pattern: /[$](?:[^\r\n])+/,
|
||||
inside: {
|
||||
prompt: {
|
||||
pattern: /^[$] /,
|
||||
alias: "unselectable",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
output: {
|
||||
pattern: /.(?:.*(?:[\r\n]|.$))*/,
|
||||
alias: "unselectable"
|
||||
}
|
||||
};
|
||||
|
||||
const originalGrammar = Prism.languages.powershell;
|
||||
|
||||
// Custom `powershell` grammar
|
||||
Prism.languages.powershell = {
|
||||
comment: {
|
||||
pattern: /(^|[^'{\\$])#.*/,
|
||||
alias: "unselectable",
|
||||
lookbehind: true,
|
||||
},
|
||||
|
||||
directory: {
|
||||
pattern: /^PS (?=\w:[\w\\-]+> )/m,
|
||||
alias: "unselectable",
|
||||
},
|
||||
|
||||
command: {
|
||||
pattern: /\w:[\w\\-]+> [^\r\n]+/,
|
||||
inside: {
|
||||
'prompt': {
|
||||
pattern: /^\w:[\w\\-]+> /,
|
||||
alias: "unselectable",
|
||||
},
|
||||
'comment': originalGrammar.comment,
|
||||
'string': originalGrammar.string,
|
||||
'boolean': originalGrammar.boolean,
|
||||
'variable': /\$\w+\b/,
|
||||
'function': originalGrammar.function,
|
||||
'keyword': originalGrammar.keyword,
|
||||
'operator': [
|
||||
{
|
||||
pattern: /(^|\W)(?:!|-(?:b?(?:and|x?or)|as|(?:Not)?(?:Contains|In|Like|Match)|eq|ge|gt|is(?:Not)?|Join|le|lt|ne|not|Replace|sh[lr])\b|[*%]=?)/i,
|
||||
lookbehind: true
|
||||
}
|
||||
],
|
||||
'punctuation': originalGrammar.punctuation,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// Prism language aliases
|
||||
export const langs: Record<string, string> = {
|
||||
tf: "hcl", // terraform -> hashicorp config lang
|
||||
rs: "rust",
|
||||
shell: "sh",
|
||||
curl: "bash",
|
||||
gql: "graphql",
|
||||
svelte: "html",
|
||||
javascript: "js",
|
||||
jsonc: "json",
|
||||
typescript: "ts",
|
||||
plaintext: "txt",
|
||||
text: "txt",
|
||||
py: "python",
|
||||
vue: "html",
|
||||
rb: "ruby",
|
||||
};
|
||||
|
||||
// Custom token transforms
|
||||
const transformations: Record<string, any> = {
|
||||
js: {
|
||||
keyword: {
|
||||
to: "declaration-keyword",
|
||||
for: new Set([
|
||||
"const",
|
||||
"let",
|
||||
"var",
|
||||
"async",
|
||||
"await",
|
||||
"function",
|
||||
"class",
|
||||
]),
|
||||
},
|
||||
punctuation: {
|
||||
to: "operator",
|
||||
for: new Set(["."]),
|
||||
},
|
||||
"class-name": {
|
||||
to: "api",
|
||||
for: new Set(["HTMLRewriter", "Request", "Response", "URL", "Error"]),
|
||||
},
|
||||
function: {
|
||||
to: "builtin",
|
||||
for: new Set([
|
||||
"fetch",
|
||||
"console",
|
||||
"addEventListener",
|
||||
"atob",
|
||||
"btoa",
|
||||
"setInterval",
|
||||
"clearInterval",
|
||||
"setTimeout",
|
||||
"clearTimeout",
|
||||
]),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
transformations.ts = transformations.js;
|
||||
|
||||
transformations.html = {
|
||||
keyword: transformations.js.keyword,
|
||||
};
|
||||
|
||||
interface Node {
|
||||
types: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
type Line = Node[];
|
||||
|
||||
const ESCAPE = /[&"<>]/g;
|
||||
const CHARS = {
|
||||
'"': """,
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
};
|
||||
|
||||
// @see lukeed/tempura
|
||||
function toEscape(value: string) {
|
||||
let tmp = 0;
|
||||
let out = "";
|
||||
let last = (ESCAPE.lastIndex = 0);
|
||||
while (ESCAPE.test(value)) {
|
||||
tmp = ESCAPE.lastIndex - 1;
|
||||
out += value.substring(last, tmp) + CHARS[value[tmp] as keyof typeof CHARS];
|
||||
last = tmp + 1;
|
||||
}
|
||||
return out + value.substring(last);
|
||||
}
|
||||
|
||||
function normalize(tokens: (Token | string)[]) {
|
||||
let line: Line = [];
|
||||
let lines: Line[] = [];
|
||||
|
||||
function loop(types: string, item: TokenStream) {
|
||||
if (Array.isArray(item)) {
|
||||
item.forEach((x) => loop(types, x));
|
||||
} else if (typeof item === "string") {
|
||||
types = types || "CodeBlock--token-plain";
|
||||
|
||||
if (item === "") {
|
||||
// ignore
|
||||
} else if (item === "\n") {
|
||||
line.push({ types, content: item });
|
||||
lines.push(line);
|
||||
line = [];
|
||||
} else if (item === "\n\n") {
|
||||
line.push({ types, content: "\n" });
|
||||
lines.push(line);
|
||||
|
||||
line = [{ types: "CodeBlock--token-plain", content: "\n" }];
|
||||
lines.push(line);
|
||||
|
||||
line = [];
|
||||
} else if (item.includes("\n")) {
|
||||
item.split(/\r?\n/g).forEach((txt, idx, arr) => {
|
||||
if (!txt && !idx && idx < arr.length) return;
|
||||
let content = txt ? toEscape(txt) : "\n";
|
||||
|
||||
if (idx > 0) {
|
||||
lines.push(line);
|
||||
line = [];
|
||||
}
|
||||
line.push({ types, content });
|
||||
});
|
||||
} else {
|
||||
let content = toEscape(item);
|
||||
line.push({ types, content });
|
||||
}
|
||||
} else if (item) {
|
||||
if (types) types += " ";
|
||||
types += "CodeBlock--token-" + item.type;
|
||||
|
||||
if (item.alias) {
|
||||
([] as string[]).concat(item.alias).forEach((tt) => {
|
||||
if (!types.includes(tt)) {
|
||||
if (types) types += " ";
|
||||
types += "CodeBlock--token-" + tt;
|
||||
}
|
||||
});
|
||||
}
|
||||
loop(types, item.content);
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < tokens.length; i++) {
|
||||
loop("", tokens[i]);
|
||||
}
|
||||
|
||||
if (line.length > 0) {
|
||||
lines.push(line);
|
||||
}
|
||||
|
||||
let arr: Line[] = [];
|
||||
while ((line = lines.shift())) {
|
||||
if (line.length > 1 && line[0].content === "\n") {
|
||||
// remove extra leading "\n" items for non-whitespace lines
|
||||
line[0].content = "";
|
||||
arr.push(line);
|
||||
} else {
|
||||
arr.push(line);
|
||||
}
|
||||
}
|
||||
|
||||
lines = arr;
|
||||
|
||||
// check for useless newline
|
||||
// ~> last line will be single-item Array
|
||||
let last = lines.pop();
|
||||
if (last.length !== 1 || last[0].content.trim().length > 1) {
|
||||
lines.push(last); // add it back, was useful
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
export async function highlight(
|
||||
code: string,
|
||||
lang: string,
|
||||
file: string
|
||||
): Promise<string> {
|
||||
lang = langs[lang] || lang || "txt";
|
||||
let grammar = Prism.languages[lang.toLowerCase()];
|
||||
|
||||
if (!grammar) {
|
||||
console.warn('[prism] Missing "%s" grammar; using "txt" fallback', lang);
|
||||
grammar = Prism.languages.txt;
|
||||
}
|
||||
|
||||
let frontmatter: {
|
||||
theme?: string | "light";
|
||||
highlight?: `[${string}]` | string;
|
||||
filename?: string;
|
||||
header?: string;
|
||||
playground?: boolean;
|
||||
} = {};
|
||||
|
||||
// Check for a YAML frontmatter,
|
||||
// and ensure it's not something like -----BEGIN CERTIFICATE-----
|
||||
if (code.substring(0, 3) === "---" && code[3] != "-") {
|
||||
let index = code.indexOf("---", 3);
|
||||
if (index > 3) {
|
||||
index += 3;
|
||||
let content = code.substring(0, index);
|
||||
code = code.substring(index).replace(/^(\r?\n)+/, "");
|
||||
|
||||
// TODO: pass in `utils.frontmatter` here
|
||||
// frontmatter = utils.frontmatter(content);
|
||||
|
||||
let match = /^---\r?\n([\s\S]+?)\r?\n---/.exec(content);
|
||||
if (match != null)
|
||||
match[1].split("\n").forEach((pair) => {
|
||||
let [key, ...v] = pair.split(":");
|
||||
frontmatter[key.trim() as "theme"] = v.join(":").trim();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let highlights: Set<number>;
|
||||
|
||||
try {
|
||||
let highlight = frontmatter.highlight;
|
||||
// let range-parser do the heavy lifting. It handles all supported cases
|
||||
if (highlight?.startsWith("[")) {
|
||||
highlight = highlight.substring(1, highlight.length - 1);
|
||||
}
|
||||
const parsedRange = rangeParser(highlight || "");
|
||||
highlights = new Set(parsedRange.map((x: number) => x - 1));
|
||||
} catch (err) {
|
||||
process.stderr.write(
|
||||
`[ERROR] ${file}\nSyntax highlighting error: You must specify the lines to highlight as an array (e.g., '[2]'). Found '${frontmatter.highlight}'.\n`
|
||||
);
|
||||
// still throwing the original error because it could be something else
|
||||
throw err;
|
||||
}
|
||||
|
||||
// tokenize & build custom string output
|
||||
let tokens = Prism.tokenize(code, grammar);
|
||||
let output = "";
|
||||
|
||||
let theme = frontmatter.theme || "light";
|
||||
output +=
|
||||
'<pre class="CodeBlock CodeBlock-with-rows CodeBlock-scrolls-horizontally';
|
||||
|
||||
if (theme === "light") output += " CodeBlock-is-light-in-light-theme";
|
||||
output += ` CodeBlock--language-${lang}" language="${lang}"`;
|
||||
if (frontmatter.header) output += ` title="${frontmatter.header}">`;
|
||||
else output += ">"
|
||||
|
||||
if (frontmatter.header)
|
||||
output += `<span class="CodeBlock--header">${frontmatter.header}</span>`;
|
||||
else if (frontmatter.filename)
|
||||
output += `<span class="CodeBlock--filename">${frontmatter.filename}</span>`;
|
||||
|
||||
if (frontmatter.playground) {
|
||||
const serialised = await compressWorker(serialiseWorker(code));
|
||||
const playgroundUrl = `https://workers.cloudflare.com/playground#${serialised}`;
|
||||
|
||||
output += `<a target="__blank" href="${playgroundUrl}" class="playground-link"><svg fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M6.21 12.293l-3.215-4.3 3.197-4.178-.617-.842-3.603 4.712-.005.603 3.62 4.847.623-.842z"></path><path d="M7.332 1.988H6.095l4.462 6.1-4.357 5.9h1.245L11.8 8.09 7.332 1.988z"></path><path d="M9.725 1.988H8.472l4.533 6.027-4.533 5.973h1.255l4.303-5.67v-.603L9.725 1.988z"></path></svg><span> Run Worker</span></a>`;
|
||||
}
|
||||
|
||||
output += "<code>";
|
||||
output += '<span class="CodeBlock--rows">';
|
||||
output += '<span class="CodeBlock--rows-content">';
|
||||
|
||||
let i = 0;
|
||||
let row = "";
|
||||
let line: Line;
|
||||
let lines = normalize(tokens);
|
||||
|
||||
for (; i < lines.length; i++) {
|
||||
line = lines[i];
|
||||
row = '<span class="CodeBlock--row';
|
||||
row += highlights.has(i) ? ' CodeBlock--row-is-highlighted">' : '">';
|
||||
row += '<span class="CodeBlock--row-indicator"></span>';
|
||||
row += '<div class="CodeBlock--row-content">';
|
||||
for (let j = 0; j < line.length; j++) {
|
||||
row +=
|
||||
'<span class="' + line[j].types + '">' + line[j].content + "</span>";
|
||||
}
|
||||
output += row + "</div></span>";
|
||||
}
|
||||
|
||||
return output + "</span></span></code></pre>";
|
||||
}
|
||||
|
|
@ -1,56 +1,56 @@
|
|||
import { readFile } from 'fs/promises';
|
||||
import { readFile } from "fs/promises";
|
||||
|
||||
async function main( ){
|
||||
const redirects = await readFile('public/_redirects', { encoding: 'utf-8' });
|
||||
async function main() {
|
||||
const redirects = await readFile("public/_redirects", { encoding: "utf-8" });
|
||||
|
||||
let numInfiniteRedirects = 0;
|
||||
let numUrlsWithFragment = 0;
|
||||
let numDuplicateRedirects = 0;
|
||||
let redirectSourceUrls: string[] = [];
|
||||
let numInfiniteRedirects = 0;
|
||||
let numUrlsWithFragment = 0;
|
||||
let numDuplicateRedirects = 0;
|
||||
let redirectSourceUrls: string[] = [];
|
||||
|
||||
for (const line of redirects.split('\n')) {
|
||||
if (line.startsWith('#') || line.trim() === '') continue;
|
||||
for (const line of redirects.split("\n")) {
|
||||
if (line.startsWith("#") || line.trim() === "") continue;
|
||||
|
||||
const [from, to] = line.split(' ');
|
||||
const [from, to] = line.split(" ");
|
||||
|
||||
if (from === to) {
|
||||
console.log(`✘ Found infinite redirect:\n ${from} -> ${to}`);
|
||||
numInfiniteRedirects++;
|
||||
}
|
||||
if (from === to) {
|
||||
console.log(`✘ Found infinite redirect:\n ${from} -> ${to}`);
|
||||
numInfiniteRedirects++;
|
||||
}
|
||||
|
||||
if (from.includes('#')) {
|
||||
console.log(`✘ Found source URL with fragment:\n ${from}`);
|
||||
numUrlsWithFragment++;
|
||||
}
|
||||
if (from.includes("#")) {
|
||||
console.log(`✘ Found source URL with fragment:\n ${from}`);
|
||||
numUrlsWithFragment++;
|
||||
}
|
||||
|
||||
if (redirectSourceUrls.includes(from)) {
|
||||
console.log(`✘ Found repeated source URL:\n ${from}`)
|
||||
numDuplicateRedirects++;
|
||||
} else {
|
||||
redirectSourceUrls.push(from);
|
||||
}
|
||||
}
|
||||
if (redirectSourceUrls.includes(from)) {
|
||||
console.log(`✘ Found repeated source URL:\n ${from}`);
|
||||
numDuplicateRedirects++;
|
||||
} else {
|
||||
redirectSourceUrls.push(from);
|
||||
}
|
||||
}
|
||||
|
||||
if (numInfiniteRedirects || numUrlsWithFragment || numDuplicateRedirects) {
|
||||
console.log("\nDetected errors:");
|
||||
if (numInfiniteRedirects || numUrlsWithFragment || numDuplicateRedirects) {
|
||||
console.log("\nDetected errors:");
|
||||
|
||||
if (numInfiniteRedirects > 0) {
|
||||
console.log(`- ${numInfiniteRedirects} infinite redirect(s)`);
|
||||
}
|
||||
if (numInfiniteRedirects > 0) {
|
||||
console.log(`- ${numInfiniteRedirects} infinite redirect(s)`);
|
||||
}
|
||||
|
||||
if (numUrlsWithFragment > 0) {
|
||||
console.log(`- ${numUrlsWithFragment} source URL(s) with a fragment`);
|
||||
}
|
||||
if (numUrlsWithFragment > 0) {
|
||||
console.log(`- ${numUrlsWithFragment} source URL(s) with a fragment`);
|
||||
}
|
||||
|
||||
if (numDuplicateRedirects > 0) {
|
||||
console.log(`- ${numDuplicateRedirects} repeated source URL(s)`);
|
||||
}
|
||||
if (numDuplicateRedirects > 0) {
|
||||
console.log(`- ${numDuplicateRedirects} repeated source URL(s)`);
|
||||
}
|
||||
|
||||
console.log("\nPlease fix the errors above before merging :)");
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.log("\nDone!");
|
||||
}
|
||||
console.log("\nPlease fix the errors above before merging :)");
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.log("\nDone!");
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
|
|
|
|||
184
bin/worker.ts
184
bin/worker.ts
|
|
@ -1,184 +0,0 @@
|
|||
import prettier from 'prettier';
|
||||
import * as fs from 'fs/promises';
|
||||
import * as thread from 'worker_threads';
|
||||
import { langs } from './prism.config';
|
||||
|
||||
export interface Result {
|
||||
file: string;
|
||||
missing: string[];
|
||||
error: 1 | 0;
|
||||
warn: 1 | 0;
|
||||
}
|
||||
|
||||
// Environment / inherited information
|
||||
const { isCHECK, ROOTLEN, options } = thread.workerData;
|
||||
const Parent = thread.parentPort!;
|
||||
|
||||
const YAML = /^\s*(---[^]+(?:---\r?\n))/;
|
||||
|
||||
// Unknown languages / missing parsers
|
||||
const Missing = new Set<string>();
|
||||
|
||||
// Prism languages to ignore
|
||||
const Ignores = new Set(['txt', 'diff', 'bash', 'sh', 'toml']);
|
||||
|
||||
// Prism language -> prettier parser
|
||||
const Parsers: Record<string, prettier.BuiltInParserName> = {
|
||||
js: 'babel',
|
||||
javascript: 'babel',
|
||||
|
||||
md: 'mdx',
|
||||
markdown: 'mdx',
|
||||
mdx: 'mdx',
|
||||
|
||||
json: 'json',
|
||||
json5: 'json5',
|
||||
|
||||
ts: 'typescript',
|
||||
typescript: 'typescript',
|
||||
|
||||
gql: 'graphql',
|
||||
graphql: 'graphql',
|
||||
|
||||
xml: 'html',
|
||||
html: 'html',
|
||||
svelte: 'html',
|
||||
hbs: 'html',
|
||||
vue: 'vue',
|
||||
|
||||
yaml: 'yaml',
|
||||
yml: 'yaml',
|
||||
};
|
||||
|
||||
function reply(
|
||||
label: 'ERRO' | 'OK' | 'PASS' | 'FAIL',
|
||||
data: Omit<Result, 'missing'>,
|
||||
extra?: string
|
||||
) {
|
||||
let text = `[${label}] ${data.file.substring(ROOTLEN)}\n`;
|
||||
if (extra) text += '\n\t' + extra.replace(/(\n)/g, '$1\t') + '\n\n';
|
||||
process.stdout.write(text);
|
||||
|
||||
let missing = [...Missing];
|
||||
let result: Result = { ...data, missing };
|
||||
Parent.postMessage(result);
|
||||
}
|
||||
|
||||
function toError(msg: string, file: string, lang: string, extra?: Error | string): never {
|
||||
let error = msg;
|
||||
if (lang) error += ' (lang = ' + lang + ')';
|
||||
if (extra) error += '\n' + extra;
|
||||
|
||||
reply('ERRO', { file, error: 1, warn: 0 }, error);
|
||||
throw 1;
|
||||
}
|
||||
|
||||
function format(code: string, lang: string) {
|
||||
let parser = Parsers[lang];
|
||||
|
||||
if (parser == null) {
|
||||
Missing.add(lang);
|
||||
return code;
|
||||
}
|
||||
|
||||
return prettier.format(code, { ...options, parser });
|
||||
}
|
||||
|
||||
async function markdown(file: string, input: string): Promise<string> {
|
||||
let last = 0;
|
||||
let output = '';
|
||||
let match: RegExpExecArray | null;
|
||||
|
||||
let BACKTICKS = /^( +)?([`]{3})([A-Za-z]+?)\n([^]+?)(\2)/gm;
|
||||
|
||||
while ((match = BACKTICKS.exec(input))) {
|
||||
let [full, lead, open, hint, inner, close] = match;
|
||||
|
||||
let current = match.index;
|
||||
output += input.substring(last, current);
|
||||
|
||||
lead = lead || '';
|
||||
hint = hint || 'txt';
|
||||
let lang = (langs[hint] || hint).toLowerCase();
|
||||
|
||||
if (Ignores.has(lang) || Missing.has(lang)) {
|
||||
last = current + full.length;
|
||||
output += full;
|
||||
continue;
|
||||
}
|
||||
|
||||
let isYAML = YAML.exec(inner);
|
||||
let frontmatter = (isYAML && isYAML[1]) || '';
|
||||
|
||||
if (frontmatter.length > 0) {
|
||||
// TODO: parse for `format: false` value
|
||||
inner = inner.substring(frontmatter.length + lead.length);
|
||||
|
||||
if (lead.length > 0) {
|
||||
frontmatter = frontmatter.replace(new RegExp('\n' + lead, 'g'), '\n');
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
var pretty = format(inner, lang).trimEnd();
|
||||
} catch (err) {
|
||||
throw toError('Error formatting code snippet!', file, lang, err.message || err);
|
||||
}
|
||||
|
||||
output += lead + '```' + lang + '\n';
|
||||
|
||||
if (lead.length > 0) {
|
||||
(frontmatter + pretty).split(/\r?\n/g).forEach(line => {
|
||||
output += lead + line + '\n';
|
||||
});
|
||||
} else {
|
||||
output += frontmatter + pretty + '\n';
|
||||
}
|
||||
|
||||
output += lead + '```';
|
||||
|
||||
last = current + full.length;
|
||||
}
|
||||
|
||||
if (last && last < input.length) {
|
||||
output += input.substring(last);
|
||||
} else if (last < 1) {
|
||||
output = input;
|
||||
}
|
||||
|
||||
try {
|
||||
output = format(output, 'mdx');
|
||||
} catch (err) {
|
||||
throw toError('Error w/ final MDX format!', file, 'mdx', err.stack || err);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
// Respond to `format.ts` task pool
|
||||
Parent.on('message', async file => {
|
||||
let input = await fs.readFile(file, 'utf8');
|
||||
let output: string | void;
|
||||
|
||||
try {
|
||||
if (/\.mdx?$/.test(file)) {
|
||||
output = await markdown(file, input);
|
||||
} else {
|
||||
let extn = file.substring(file.lastIndexOf('.') + 1);
|
||||
output = format(input, langs[extn] || extn);
|
||||
}
|
||||
|
||||
if (!isCHECK && output) {
|
||||
await fs.writeFile(file, output);
|
||||
}
|
||||
|
||||
const isMatch = input === output;
|
||||
const warn = +(isCHECK && !isMatch) as 1 | 0;
|
||||
const label = isCHECK ? (isMatch ? 'PASS' : 'FAIL') : 'OK';
|
||||
|
||||
return reply(label, { file, warn, error: 0 });
|
||||
} catch (err) {
|
||||
if (err === 1) return; // already handled
|
||||
return reply('ERRO', { file, warn: 0, error: 1 });
|
||||
}
|
||||
});
|
||||
|
|
@ -1,44 +1,47 @@
|
|||
import redirects from "./redirects"
|
||||
import redirects from "./redirects";
|
||||
|
||||
const apiBase = "https://cloudflare-api-docs-frontend.pages.dev"
|
||||
const apiBase = "https://cloudflare-api-docs-frontend.pages.dev";
|
||||
|
||||
const rewriteStaticAssets = {
|
||||
element: (element: Element) => {
|
||||
const prefixAttribute = (attr: string) => {
|
||||
const value = element.getAttribute(attr)
|
||||
element: (element: Element) => {
|
||||
const prefixAttribute = (attr: string) => {
|
||||
const value = element.getAttribute(attr);
|
||||
|
||||
if (value.startsWith("http")) {
|
||||
return
|
||||
}
|
||||
if (value.startsWith("http")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedValue = `/api/${value.startsWith('/') ? value.slice(1) : value}`
|
||||
element.setAttribute(attr, updatedValue)
|
||||
}
|
||||
const updatedValue = `/api/${value.startsWith("/") ? value.slice(1) : value}`;
|
||||
element.setAttribute(attr, updatedValue);
|
||||
};
|
||||
|
||||
const attrs = ['href', 'src']
|
||||
attrs.forEach(attr => {
|
||||
if (element.getAttribute(attr)) prefixAttribute(attr)
|
||||
})
|
||||
}
|
||||
}
|
||||
const attrs = ["href", "src"];
|
||||
attrs.forEach((attr) => {
|
||||
if (element.getAttribute(attr)) prefixAttribute(attr);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export const onRequestGet: PagesFunction<{}> = async ({ request }) => {
|
||||
const apiPath = "/api"
|
||||
const apiPath = "/api";
|
||||
|
||||
const url = new URL(request.url)
|
||||
const url = new URL(request.url);
|
||||
|
||||
let subpath = url.pathname.replace(apiPath, "")
|
||||
let normalizedSubpath = subpath.slice(-1) === "/" ? subpath.substring(0, subpath.length - 1) : subpath;
|
||||
if(normalizedSubpath in redirects) {
|
||||
url.pathname = redirects[normalizedSubpath]
|
||||
return Response.redirect(url.toString(), 301)
|
||||
}
|
||||
const proxyUrl = `${apiBase}/${subpath}`
|
||||
const proxyResponse = await fetch(proxyUrl)
|
||||
let subpath = url.pathname.replace(apiPath, "");
|
||||
let normalizedSubpath =
|
||||
subpath.slice(-1) === "/"
|
||||
? subpath.substring(0, subpath.length - 1)
|
||||
: subpath;
|
||||
if (normalizedSubpath in redirects) {
|
||||
url.pathname = redirects[normalizedSubpath];
|
||||
return Response.redirect(url.toString(), 301);
|
||||
}
|
||||
const proxyUrl = `${apiBase}/${subpath}`;
|
||||
const proxyResponse = await fetch(proxyUrl);
|
||||
|
||||
return new HTMLRewriter()
|
||||
.on("script", rewriteStaticAssets)
|
||||
.on("link", rewriteStaticAssets)
|
||||
.on("img", rewriteStaticAssets)
|
||||
.transform(proxyResponse)
|
||||
}
|
||||
return new HTMLRewriter()
|
||||
.on("script", rewriteStaticAssets)
|
||||
.on("link", rewriteStaticAssets)
|
||||
.on("img", rewriteStaticAssets)
|
||||
.transform(proxyResponse);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,22 +1,22 @@
|
|||
export default {
|
||||
"/v4docs": "/fundamentals/api/how-to/make-api-calls/",
|
||||
"/tokens": "/fundamentals/api/get-started/",
|
||||
"/tokens/create": "/fundamentals/api/get-started/create-token/",
|
||||
"/tokens/create/template": "/fundamentals/api/reference/template/",
|
||||
"/tokens/create/permissions": "/fundamentals/api/reference/permissions/",
|
||||
"/tokens/advanced": "/fundamentals/api/how-to/",
|
||||
"/tokens/advanced/restrictions": "/fundamentals/api/how-to/restrict-tokens/",
|
||||
"/tokens/advanced/api": "/fundamentals/api/how-to/create-via-api/",
|
||||
"/keys": "/fundamentals/api/get-started/keys/",
|
||||
"/limits": "/fundamentals/api/reference/limits/",
|
||||
"/how-to/make-api-calls": "/fundamentals/api/how-to/make-api-calls/",
|
||||
"/get-started": "/fundamentals/api/get-started/",
|
||||
"/get-started/create-token": "/fundamentals/api/get-started/create-token/",
|
||||
"/reference/template": "/fundamentals/api/reference/template/",
|
||||
"/reference/permissions": "/fundamentals/api/reference/permissions/",
|
||||
"/how-to": "/fundamentals/api/how-to/",
|
||||
"/how-to/restrict-tokens": "/fundamentals/api/how-to/restrict-tokens/",
|
||||
"/how-to/create-via-api": "/fundamentals/api/how-to/create-via-api/",
|
||||
"/get-started/keys": "/fundamentals/api/get-started/keys/",
|
||||
"/reference/limits": "/fundamentals/api/reference/limits"
|
||||
}
|
||||
"/v4docs": "/fundamentals/api/how-to/make-api-calls/",
|
||||
"/tokens": "/fundamentals/api/get-started/",
|
||||
"/tokens/create": "/fundamentals/api/get-started/create-token/",
|
||||
"/tokens/create/template": "/fundamentals/api/reference/template/",
|
||||
"/tokens/create/permissions": "/fundamentals/api/reference/permissions/",
|
||||
"/tokens/advanced": "/fundamentals/api/how-to/",
|
||||
"/tokens/advanced/restrictions": "/fundamentals/api/how-to/restrict-tokens/",
|
||||
"/tokens/advanced/api": "/fundamentals/api/how-to/create-via-api/",
|
||||
"/keys": "/fundamentals/api/get-started/keys/",
|
||||
"/limits": "/fundamentals/api/reference/limits/",
|
||||
"/how-to/make-api-calls": "/fundamentals/api/how-to/make-api-calls/",
|
||||
"/get-started": "/fundamentals/api/get-started/",
|
||||
"/get-started/create-token": "/fundamentals/api/get-started/create-token/",
|
||||
"/reference/template": "/fundamentals/api/reference/template/",
|
||||
"/reference/permissions": "/fundamentals/api/reference/permissions/",
|
||||
"/how-to": "/fundamentals/api/how-to/",
|
||||
"/how-to/restrict-tokens": "/fundamentals/api/how-to/restrict-tokens/",
|
||||
"/how-to/create-via-api": "/fundamentals/api/how-to/create-via-api/",
|
||||
"/get-started/keys": "/fundamentals/api/get-started/keys/",
|
||||
"/reference/limits": "/fundamentals/api/reference/limits",
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,83 +1,89 @@
|
|||
export async function onRequestGet(context) {
|
||||
const cachedSchema = await context.env.API_DOCS_KV.get("schema", "json")
|
||||
if (cachedSchema) {
|
||||
return new Response(JSON.stringify(cachedSchema), {
|
||||
headers: { 'Content-type': 'application/json' }
|
||||
})
|
||||
}
|
||||
const cachedSchema = await context.env.API_DOCS_KV.get("schema", "json");
|
||||
if (cachedSchema) {
|
||||
return new Response(JSON.stringify(cachedSchema), {
|
||||
headers: { "Content-type": "application/json" },
|
||||
});
|
||||
}
|
||||
|
||||
const schemaUrl = "https://raw.githubusercontent.com/cloudflare/api-schemas/main/openapi.json"
|
||||
const schemaUrl =
|
||||
"https://raw.githubusercontent.com/cloudflare/api-schemas/main/openapi.json";
|
||||
|
||||
const req = new Request(schemaUrl)
|
||||
const req = new Request(schemaUrl);
|
||||
|
||||
const cache = caches.default
|
||||
let response = await cache.match(req)
|
||||
const cache = caches.default;
|
||||
let response = await cache.match(req);
|
||||
|
||||
try {
|
||||
if (!response) {
|
||||
response = await fetch(req)
|
||||
let schema = await response.json()
|
||||
try {
|
||||
if (!response) {
|
||||
response = await fetch(req);
|
||||
let schema = await response.json();
|
||||
|
||||
const pathsByTag = {}
|
||||
const pathsByTag = {};
|
||||
|
||||
Object.keys(schema.paths).forEach(key => {
|
||||
const path = schema.paths[key]
|
||||
const tag = Object.values(path).find(endpoint => {
|
||||
const tags = endpoint.tags
|
||||
return tags && tags.length && tags[0]
|
||||
})
|
||||
if (tag) {
|
||||
if (!pathsByTag[tag]) pathsByTag[tag] = []
|
||||
pathsByTag[tag].push({ path, key })
|
||||
}
|
||||
})
|
||||
Object.keys(schema.paths).forEach((key) => {
|
||||
const path = schema.paths[key];
|
||||
const tag = Object.values(path).find((endpoint) => {
|
||||
const tags = endpoint.tags;
|
||||
return tags && tags.length && tags[0];
|
||||
});
|
||||
if (tag) {
|
||||
if (!pathsByTag[tag]) pathsByTag[tag] = [];
|
||||
pathsByTag[tag].push({ path, key });
|
||||
}
|
||||
});
|
||||
|
||||
let sortedPaths = {}
|
||||
const sortedTags = Object.keys(pathsByTag).sort()
|
||||
sortedTags.forEach(tag => {
|
||||
const tagArray = pathsByTag[tag]
|
||||
tagArray.forEach(({ key, path }) => {
|
||||
if (sortedPaths[key]) console.log('key already exists')
|
||||
sortedPaths[key] = path
|
||||
})
|
||||
})
|
||||
let sortedPaths = {};
|
||||
const sortedTags = Object.keys(pathsByTag).sort();
|
||||
sortedTags.forEach((tag) => {
|
||||
const tagArray = pathsByTag[tag];
|
||||
tagArray.forEach(({ key, path }) => {
|
||||
if (sortedPaths[key]) console.log("key already exists");
|
||||
sortedPaths[key] = path;
|
||||
});
|
||||
});
|
||||
|
||||
// sort sortedPaths by tag
|
||||
sortedPaths = Object.entries(sortedPaths).sort((a, b) => {
|
||||
const aVal = a[1]
|
||||
const bVal = b[1]
|
||||
const firstAVal = Object.values(aVal).find(endpoint => {
|
||||
const tags = endpoint.tags
|
||||
return tags && tags.length && tags[0]
|
||||
})
|
||||
const aTag = firstAVal && firstAVal.tags[0] || ''
|
||||
const firstBVal = Object.values(bVal).find(endpoint => {
|
||||
const tags = endpoint.tags
|
||||
return tags && tags.length && tags[0]
|
||||
})
|
||||
const bTag = firstBVal && firstBVal.tags[0] || ''
|
||||
if (aTag < bTag) return -1
|
||||
if (aTag > bTag) return 1
|
||||
return 0
|
||||
}).reduce((obj, [key, val]) => {
|
||||
obj[key] = val
|
||||
return obj
|
||||
}, {})
|
||||
// sort sortedPaths by tag
|
||||
sortedPaths = Object.entries(sortedPaths)
|
||||
.sort((a, b) => {
|
||||
const aVal = a[1];
|
||||
const bVal = b[1];
|
||||
const firstAVal = Object.values(aVal).find((endpoint) => {
|
||||
const tags = endpoint.tags;
|
||||
return tags && tags.length && tags[0];
|
||||
});
|
||||
const aTag = (firstAVal && firstAVal.tags[0]) || "";
|
||||
const firstBVal = Object.values(bVal).find((endpoint) => {
|
||||
const tags = endpoint.tags;
|
||||
return tags && tags.length && tags[0];
|
||||
});
|
||||
const bTag = (firstBVal && firstBVal.tags[0]) || "";
|
||||
if (aTag < bTag) return -1;
|
||||
if (aTag > bTag) return 1;
|
||||
return 0;
|
||||
})
|
||||
.reduce((obj, [key, val]) => {
|
||||
obj[key] = val;
|
||||
return obj;
|
||||
}, {});
|
||||
|
||||
let sortedSchema = Object.assign({}, schema, { paths: sortedPaths })
|
||||
let sortedSchema = Object.assign({}, schema, { paths: sortedPaths });
|
||||
|
||||
response = new Response(JSON.stringify(sortedSchema), {
|
||||
headers: { 'Content-type': 'application/json' }
|
||||
})
|
||||
response = new Response(JSON.stringify(sortedSchema), {
|
||||
headers: { "Content-type": "application/json" },
|
||||
});
|
||||
|
||||
const expirationTtl = 60 * 60
|
||||
await context.env.API_DOCS_KV.put("schema", JSON.stringify(sortedSchema), { expirationTtl })
|
||||
}
|
||||
|
||||
return response
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
return fetch(req)
|
||||
}
|
||||
const expirationTtl = 60 * 60;
|
||||
await context.env.API_DOCS_KV.put(
|
||||
"schema",
|
||||
JSON.stringify(sortedSchema),
|
||||
{ expirationTtl },
|
||||
);
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
return fetch(req);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "CommonJS",
|
||||
"lib": ["ES2020"],
|
||||
"types": ["@cloudflare/workers-types"]
|
||||
}
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "CommonJS",
|
||||
"lib": ["ES2020"],
|
||||
"types": ["@cloudflare/workers-types"]
|
||||
},
|
||||
"include": ["./**/*.ts"]
|
||||
}
|
||||
|
||||
|
|
|
|||
5209
package-lock.json
generated
5209
package-lock.json
generated
File diff suppressed because it is too large
Load diff
62
package.json
62
package.json
|
|
@ -1,18 +1,22 @@
|
|||
{
|
||||
"name": "cloudflare-docs-starlight",
|
||||
"type": "module",
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"start": "astro dev",
|
||||
"build": "astro check && astro build",
|
||||
"preview": "astro preview",
|
||||
"astro": "astro",
|
||||
"postinstall": "patch-package",
|
||||
"format": "prettier --write \"**/*.{js,jsx,ts,tsx,mjs,astro,css,json,yaml,yml}\""
|
||||
"build": "astro build",
|
||||
"check": "npm run check:functions && npm run check:astro",
|
||||
"check:astro": "npm run sync && astro check",
|
||||
"check:functions": "tsc --noEmit -p ./functions/tsconfig.json",
|
||||
"dev": "astro dev",
|
||||
"format": "prettier --write \"**/*.{js,jsx,ts,tsx,mjs,astro,css,json,yaml,yml}\"",
|
||||
"postinstall": "patch-package && npm run sync",
|
||||
"preview": "astro preview",
|
||||
"start": "astro dev",
|
||||
"sync": "astro sync"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astro-community/astro-embed-youtube": "^0.5.2",
|
||||
"devDependencies": {
|
||||
"@astro-community/astro-embed-youtube": "^0.5.3",
|
||||
"@astrojs/check": "^0.9.3",
|
||||
"@astrojs/rss": "^4.0.7",
|
||||
"@astrojs/sitemap": "^3.1.6",
|
||||
|
|
@ -20,43 +24,45 @@
|
|||
"@astrojs/starlight-docsearch": "^0.1.1",
|
||||
"@astrojs/starlight-tailwind": "^2.0.3",
|
||||
"@astrojs/tailwind": "^5.1.0",
|
||||
"@cloudflare/workers-types": "^4.20240903.0",
|
||||
"@codingheads/sticky-header": "^1.0.2",
|
||||
"@types/node": "^20.16.1",
|
||||
"algoliasearch": "^4.24.0",
|
||||
"astro": "^4.15.5",
|
||||
"astro-breadcrumbs": "^2.3.1",
|
||||
"astro-icon": "^1.1.0",
|
||||
"astro-live-code": "^0.0.2",
|
||||
"astro-icon": "^1.1.1",
|
||||
"astro-live-code": "^0.0.3",
|
||||
"date-fns": "^3.6.0",
|
||||
"dot-prop": "^9.0.0",
|
||||
"github-slugger": "^2.0.0",
|
||||
"hastscript": "^9.0.0",
|
||||
"instantsearch.css": "^8.4.0",
|
||||
"instantsearch.js": "^4.73.4",
|
||||
"instantsearch.css": "^8.5.0",
|
||||
"instantsearch.js": "^4.74.0",
|
||||
"littlefoot": "^4.1.1",
|
||||
"marked": "^13.0.1",
|
||||
"mathjs": "^13.0.3",
|
||||
"mermaid": "^10.9.1",
|
||||
"lz-string": "^1.5.0",
|
||||
"marked": "^14.1.1",
|
||||
"mathjs": "^13.1.1",
|
||||
"mermaid": "^11.1.1",
|
||||
"node-html-parser": "^6.1.13",
|
||||
"patch-package": "^8.0.0",
|
||||
"prettier": "^3.3.3",
|
||||
"prettier-plugin-astro": "^0.14.1",
|
||||
"rehype-autolink-headings": "^7.1.0",
|
||||
"rehype-external-links": "^3.0.0",
|
||||
"rehype-mermaid": "^2.1.0",
|
||||
"rehype-slug": "^6.0.0",
|
||||
"sharp": "^0.32.5",
|
||||
"sharp": "^0.33.5",
|
||||
"solarflare-theme": "^0.0.2",
|
||||
"starlight-image-zoom": "^0.6.0",
|
||||
"starlight-package-managers": "^0.6.0",
|
||||
"tailwindcss": "^3.4.4",
|
||||
"starlight-image-zoom": "^0.8.0",
|
||||
"starlight-package-managers": "^0.7.0",
|
||||
"tailwindcss": "^3.4.10",
|
||||
"tippy.js": "^6.3.7",
|
||||
"typescript": "^5.5.2",
|
||||
"wrangler": "^3.71.0",
|
||||
"yaml": "^2.4.5"
|
||||
"typescript": "^5.5.4",
|
||||
"wrangler": "^3.75.0",
|
||||
"yaml": "^2.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.14.12",
|
||||
"lz-string": "^1.5.0",
|
||||
"prettier": "^3.3.3",
|
||||
"prettier-plugin-astro": "^0.14.1"
|
||||
"engines": {
|
||||
"node": ">=22"
|
||||
},
|
||||
"volta": {
|
||||
"node": "22.8.0"
|
||||
|
|
|
|||
|
|
@ -7,24 +7,24 @@ type Props = z.infer<typeof props>;
|
|||
|
||||
const props = z.object({
|
||||
title: z.string(),
|
||||
depth: z.number(),
|
||||
depth: z.number().min(1).max(6),
|
||||
});
|
||||
|
||||
const { title, depth } = props.parse(Astro.props);
|
||||
|
||||
const Heading = `h${depth}`;
|
||||
const Heading = `h${depth}` as "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
|
||||
---
|
||||
|
||||
<div tabindex="-1" class=`heading-wrapper level-h${depth}`>
|
||||
<Heading id={slug(title)} set:html={marked.parseInline(title)} />
|
||||
<Heading id={slug(title)} set:html={marked.parseInline(title) as string} />
|
||||
<a class="anchor-link" href={`#${slug(title)}`}>
|
||||
<span aria-hidden class="anchor-icon">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24">
|
||||
<path
|
||||
fill="currentcolor"
|
||||
d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 0 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 1 0-1.42-1.42Zm8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 0 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 1 0 1.42 1.42l3.88-3.89a4.49 4.49 0 0 0 0-6.33ZM8.83 15.17a1 1 0 0 0 1.1.22 1 1 0 0 0 .32-.22l4.92-4.92a1 1 0 0 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42Z"
|
||||
/>
|
||||
></path>
|
||||
</svg>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -16,24 +16,24 @@ const props = z
|
|||
|
||||
const { product, notificationFilter } = props.parse(Astro.props);
|
||||
|
||||
let notifications = (await getEntry("notifications", "index"))["data"][
|
||||
let notificationsRaw = (await getEntry("notifications", "index"))["data"][
|
||||
"entries"
|
||||
];
|
||||
|
||||
if (product) {
|
||||
notifications = notifications.filter(
|
||||
notificationsRaw = notificationsRaw.filter(
|
||||
(x) => x.associatedProducts.toLowerCase() === product.toLowerCase(),
|
||||
);
|
||||
}
|
||||
|
||||
if (notificationFilter) {
|
||||
notifications = notifications.filter(
|
||||
notificationsRaw = notificationsRaw.filter(
|
||||
(x) => x.name.toLowerCase() === notificationFilter.toLowerCase(),
|
||||
);
|
||||
}
|
||||
|
||||
notifications = Object.groupBy(
|
||||
notifications,
|
||||
const notifications = Object.groupBy(
|
||||
notificationsRaw,
|
||||
(entry) => entry.associatedProducts,
|
||||
);
|
||||
|
||||
|
|
@ -46,7 +46,7 @@ const showProductHeadings = !product && !notificationFilter;
|
|||
.map(([product, entries]) => (
|
||||
<>
|
||||
{showProductHeadings && <AnchorHeading depth={2} title={product} />}
|
||||
{entries.map((notification) => (
|
||||
{(entries ?? []).map((notification) => (
|
||||
<Details header={notification.name}>
|
||||
<strong>Who is it for?</strong>
|
||||
<p set:html={marked.parse(notification.audience)} />
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ const resources = await getEntry(type, "index");
|
|||
|
||||
const filtered = resources.data.entries.filter((entry) => {
|
||||
return (
|
||||
(cloudflare ? cloudflare : false) &&
|
||||
(cloudflare ? entry.cloudflare : false) &&
|
||||
(tags ? entry.tags?.some((v: string) => tags.includes(v)) : true) &&
|
||||
(products
|
||||
? entry.products?.some((v: string) => products.includes(v))
|
||||
|
|
@ -31,8 +31,7 @@ filtered.sort((a, b) => Number(b.updated) - Number(a.updated));
|
|||
<ul>
|
||||
{
|
||||
filtered.map((entry) => {
|
||||
// @ts-expect-error TODO: fix resource types
|
||||
const title = entry.name ?? entry.title;
|
||||
const title = "name" in entry ? entry.name : entry.title;
|
||||
return (
|
||||
<li>
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@ const props = z
|
|||
|
||||
const { id } = props.parse(Astro.props);
|
||||
|
||||
const plan = await indexPlans(id);
|
||||
// TODO: improve product features types
|
||||
const plan = (await indexPlans(id)) as any;
|
||||
|
||||
// @ts-ignore types not implemented for plans JSON
|
||||
const properties = plan.properties;
|
||||
|
|
@ -39,8 +40,8 @@ const markdown = (content: any) => {
|
|||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
Object.entries(properties).map(([k, v]) => {
|
||||
const renderTitle = (title) => {
|
||||
Object.entries(properties).map(([_, v]: [string, any]) => {
|
||||
const renderTitle = (title: string) => {
|
||||
const placeholder = "[[GLOSSARY_TOOLTIP_SNIPPETS_SUBREQUEST]]";
|
||||
|
||||
if (title.includes(placeholder)) {
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ if (directory) {
|
|||
const examples = await getCollection("docs", (entry) => {
|
||||
return (
|
||||
entry.data.pcx_content_type === "example" &&
|
||||
(entry.slug.startsWith(target) ||
|
||||
((target && entry.slug.startsWith(target)) ||
|
||||
(additionalProducts !== undefined &&
|
||||
entry.data.products?.some((v: string) =>
|
||||
additionalProducts?.includes(v),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
---
|
||||
import { z } from "astro:schema";
|
||||
import { getCollection, getEntry } from "astro:content";
|
||||
import { getCollection, getEntry, type InferEntrySchema } from "astro:content";
|
||||
import { formatDistance } from "date-fns";
|
||||
|
||||
const currentSection = Astro.params.slug?.split("/")[0];
|
||||
|
|
@ -20,13 +19,23 @@ const tutorials = await getCollection("docs", (entry) => {
|
|||
entry.data.pcx_content_type === "tutorial" &&
|
||||
// /src/content/r2/**/*.mdx
|
||||
(entry.slug.startsWith(`${currentSection}/`) ||
|
||||
// products: [R2]
|
||||
entry.data.products?.some(
|
||||
(v: string) => v.toUpperCase() === productTitle.toUpperCase(),
|
||||
))
|
||||
// products: [R2]
|
||||
entry.data.products?.some(
|
||||
(v: string) => v.toUpperCase() === productTitle.toUpperCase(),
|
||||
))
|
||||
);
|
||||
});
|
||||
|
||||
type VideoEntry = InferEntrySchema<"videos">["entries"][number];
|
||||
type FinalTutorials = {
|
||||
slug?: string;
|
||||
data: InferEntrySchema<"docs"> | VideoEntry;
|
||||
}[];
|
||||
const finalTutorials: FinalTutorials = tutorials.map((x) => ({
|
||||
slug: x.slug,
|
||||
data: x.data,
|
||||
}));
|
||||
|
||||
const videos = await getEntry("videos", "index");
|
||||
const filteredVideos = videos.data.entries.filter((x) =>
|
||||
x.products.some(
|
||||
|
|
@ -35,12 +44,12 @@ const filteredVideos = videos.data.entries.filter((x) =>
|
|||
);
|
||||
|
||||
if (filteredVideos) {
|
||||
filteredVideos.forEach((x) => tutorials.push({ data: x }));
|
||||
filteredVideos.forEach((x) => finalTutorials.push({ data: x }));
|
||||
}
|
||||
|
||||
tutorials.sort((a, b) => b.data.updated - a.data.updated);
|
||||
finalTutorials.sort((a, b) => Number(b.data.updated) - Number(a.data.updated));
|
||||
|
||||
const timeAgo = (date: Date) => {
|
||||
const timeAgo = (date?: Date) => {
|
||||
if (!date) return undefined;
|
||||
return formatDistance(date, new Date(), { addSuffix: true });
|
||||
};
|
||||
|
|
@ -57,7 +66,8 @@ const timeAgo = (date: Date) => {
|
|||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
tutorials.map((tutorial) => {
|
||||
finalTutorials.map((tutorial) => {
|
||||
// @ts-expect-error TODO: improve types
|
||||
const href = tutorial.slug ? `/${tutorial.slug}` : tutorial.data.link;
|
||||
return (
|
||||
<tr>
|
||||
|
|
|
|||
|
|
@ -1,48 +1,51 @@
|
|||
{
|
||||
import.meta.env.PROD ? (
|
||||
<>
|
||||
<script
|
||||
src="https://ot.www.cloudflare.com/public/vendor/onetrust/scripttemplates/otSDKStub.js"
|
||||
type="text/javascript"
|
||||
charset="UTF-8"
|
||||
data-domain-script="b1e05d49-f072-4bae-9116-bdb78af15448"
|
||||
/>
|
||||
<script type="text/javascript">function OptanonWrapper() {}</script>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<script
|
||||
src="https://ot.www.cloudflare.com/public/vendor/onetrust/scripttemplates/otSDKStub.js"
|
||||
type="text/javascript"
|
||||
charset="UTF-8"
|
||||
data-domain-script="b1e05d49-f072-4bae-9116-bdb78af15448-test"
|
||||
/>
|
||||
<script type="text/javascript">function OptanonWrapper() {}</script>
|
||||
</>
|
||||
)
|
||||
import.meta.env.PROD ? (
|
||||
<>
|
||||
<script
|
||||
src="https://ot.www.cloudflare.com/public/vendor/onetrust/scripttemplates/otSDKStub.js"
|
||||
type="text/javascript"
|
||||
charset="UTF-8"
|
||||
data-domain-script="b1e05d49-f072-4bae-9116-bdb78af15448"
|
||||
is:inline
|
||||
/>
|
||||
<script type="text/javascript" is:inline>
|
||||
function OptanonWrapper() {}
|
||||
</script>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<script
|
||||
src="https://ot.www.cloudflare.com/public/vendor/onetrust/scripttemplates/otSDKStub.js"
|
||||
type="text/javascript"
|
||||
charset="UTF-8"
|
||||
data-domain-script="b1e05d49-f072-4bae-9116-bdb78af15448-test"
|
||||
is:inline
|
||||
/>
|
||||
<script type="text/javascript" is:inline>
|
||||
function OptanonWrapper() {}
|
||||
</script>
|
||||
</>
|
||||
)
|
||||
}
|
||||
<span class="DocsFooter--content-additional-wrapper">
|
||||
<!-- OneTrust Cookies Settings button start -->
|
||||
<a
|
||||
role="button"
|
||||
id="ot-sdk-btn"
|
||||
class="ot-sdk-show-settings"
|
||||
>Cookie Settings</a
|
||||
>
|
||||
<!-- OneTrust Cookies Settings button end -->
|
||||
<!-- OneTrust Cookies Settings button start -->
|
||||
<a role="button" id="ot-sdk-btn" class="ot-sdk-show-settings"
|
||||
>Cookie Settings</a
|
||||
>
|
||||
<!-- OneTrust Cookies Settings button end -->
|
||||
</span>
|
||||
|
||||
<style>
|
||||
#ot-sdk-btn.ot-sdk-show-settings {
|
||||
border: none !important;
|
||||
color: inherit !important;
|
||||
font-size: inherit !important;
|
||||
line-height: inherit !important;
|
||||
padding: inherit !important;
|
||||
font-family: var(--sl-font-family) !important;
|
||||
}
|
||||
#ot-sdk-btn.ot-sdk-show-settings {
|
||||
border: none !important;
|
||||
color: inherit !important;
|
||||
font-size: inherit !important;
|
||||
line-height: inherit !important;
|
||||
padding: inherit !important;
|
||||
font-family: var(--sl-font-family) !important;
|
||||
}
|
||||
|
||||
#ot-sdk-btn.ot-sdk-show-settings:hover {
|
||||
background-color: inherit !important;
|
||||
}
|
||||
</style>
|
||||
#ot-sdk-btn.ot-sdk-show-settings:hover {
|
||||
background-color: inherit !important;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
import { getEntry } from "astro:content";
|
||||
import { getEntry, type CollectionEntry } from "astro:content";
|
||||
import { marked } from "marked";
|
||||
import { getChangelogs } from "~/util/changelogs";
|
||||
import AnchorHeading from "~/components/AnchorHeading.astro";
|
||||
|
|
@ -25,13 +25,13 @@ if (page.data.changelog_file_name && page.data.changelog_file_name.length > 1) {
|
|||
}
|
||||
|
||||
const name =
|
||||
page.data.changelog_product_area_name ?? page.data.changelog_file_name[0];
|
||||
page.data.changelog_product_area_name ?? page.data.changelog_file_name?.[0];
|
||||
|
||||
let changelogs;
|
||||
|
||||
if (page.data.changelog_product_area_name) {
|
||||
const opts = {
|
||||
filter: (entry) => {
|
||||
filter: (entry: CollectionEntry<"changelogs">) => {
|
||||
return entry.data.productArea === name;
|
||||
},
|
||||
};
|
||||
|
|
@ -44,7 +44,7 @@ if (page.data.changelog_product_area_name) {
|
|||
({ changelogs } = await getChangelogs(opts));
|
||||
} else {
|
||||
const opts = {
|
||||
filter: (entry) => {
|
||||
filter: (entry: CollectionEntry<"changelogs">) => {
|
||||
return entry.id === name;
|
||||
},
|
||||
};
|
||||
|
|
@ -63,7 +63,7 @@ if (!changelogs) {
|
|||
changelogs.map(([date, entries]) => (
|
||||
<div data-date={date}>
|
||||
<AnchorHeading depth={2} title={date} />
|
||||
{entries.map((entry) => (
|
||||
{(entries ?? []).map((entry) => (
|
||||
<div data-product={entry.product.toLowerCase()}>
|
||||
{page.data.changelog_product_area_name && (
|
||||
<h3 class="!mt-4">
|
||||
|
|
@ -71,7 +71,7 @@ if (!changelogs) {
|
|||
</h3>
|
||||
)}
|
||||
{entry.title && <strong>{entry.title}</strong>}
|
||||
<Fragment set:html={marked.parse(entry.description)} />
|
||||
<Fragment set:html={marked.parse(entry.description ?? "")} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -25,7 +25,8 @@ const entries = Object.entries(plan);
|
|||
---
|
||||
|
||||
{
|
||||
entries.map(([key, value]) => {
|
||||
// TODO: improve product features types
|
||||
entries.map(([key, value]: [string, any]) => {
|
||||
if (key === "title" || key === "link") return;
|
||||
|
||||
return (
|
||||
|
|
@ -37,7 +38,7 @@ const entries = Object.entries(plan);
|
|||
<a href={value.link}>{value.title}</a>
|
||||
</p>
|
||||
)}
|
||||
{Object.entries(value.properties).map(([key, value]) => (
|
||||
{Object.entries(value.properties).map(([_, value]: [string, any]) => (
|
||||
<p>
|
||||
<strong
|
||||
set:html={marked.parseInline(
|
||||
|
|
@ -57,13 +58,17 @@ const entries = Object.entries(plan);
|
|||
/>
|
||||
</li>
|
||||
{additional_descriptions && (
|
||||
<li><strong>Lite: </strong>
|
||||
{value.lite ? (<Fragment
|
||||
set:html={marked.parseInline(value.lite.toString())}
|
||||
/>): (<Fragment
|
||||
set:html={marked.parseInline(value.free.toString())}
|
||||
/>)}
|
||||
|
||||
<li>
|
||||
<strong>Lite: </strong>
|
||||
{value.lite ? (
|
||||
<Fragment
|
||||
set:html={marked.parseInline(value.lite.toString())}
|
||||
/>
|
||||
) : (
|
||||
<Fragment
|
||||
set:html={marked.parseInline(value.free.toString())}
|
||||
/>
|
||||
)}
|
||||
</li>
|
||||
)}
|
||||
<li>
|
||||
|
|
@ -71,13 +76,17 @@ const entries = Object.entries(plan);
|
|||
<Fragment set:html={marked.parseInline(value.pro.toString())} />
|
||||
</li>
|
||||
{additional_descriptions && (
|
||||
<li><strong>Pro Plus: </strong>
|
||||
{value.pro_plus ? (<Fragment
|
||||
set:html={marked.parseInline(value.pro_plus.toString())}
|
||||
/>): (<Fragment
|
||||
set:html={marked.parseInline(value.pro.toString())}
|
||||
/>)}
|
||||
|
||||
<li>
|
||||
<strong>Pro Plus: </strong>
|
||||
{value.pro_plus ? (
|
||||
<Fragment
|
||||
set:html={marked.parseInline(value.pro_plus.toString())}
|
||||
/>
|
||||
) : (
|
||||
<Fragment
|
||||
set:html={marked.parseInline(value.pro.toString())}
|
||||
/>
|
||||
)}
|
||||
</li>
|
||||
)}
|
||||
<li>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
---
|
||||
import { z } from "astro:schema";
|
||||
import { getEntry } from "astro:content";
|
||||
import { marked } from "marked";
|
||||
|
||||
type Props = z.infer<typeof props>;
|
||||
|
||||
|
|
|
|||
|
|
@ -32,8 +32,8 @@ url.searchParams.set("poster", encodeURI(thumbnailUrl.toString()));
|
|||
</div>
|
||||
<script is:inline src="https://embed.cloudflarestream.com/embed/sdk.latest.js"
|
||||
></script>
|
||||
<script is:inline define:vars={{ videoId, videoTitle }}>
|
||||
const video = document.getElementById(videoId);
|
||||
<script is:inline define:vars={{ vidId: videoId, videoTitle }}>
|
||||
const video = document.getElementById(vidId);
|
||||
Stream(video).addEventListener("play", () => {
|
||||
zaraz.track("play docs video", { title: videoTitle });
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
---
|
||||
import Default from "@astrojs/starlight/components";
|
||||
---
|
||||
|
|
@ -7,6 +7,7 @@ const troubleshootingTypes = ["troubleshooting", "faq"];
|
|||
|
||||
const resources = await getCollection("docs", (entry) => {
|
||||
return (
|
||||
entry.data.pcx_content_type &&
|
||||
troubleshootingTypes.includes(entry.data.pcx_content_type) &&
|
||||
entry.slug.startsWith(`${currentSection}/`)
|
||||
);
|
||||
|
|
|
|||
|
|
@ -125,23 +125,25 @@ const metrics = Object.entries({
|
|||
import { evaluate, round } from "mathjs";
|
||||
|
||||
function calculate() {
|
||||
const non_dns_udp_req_per_sec = document.querySelector(
|
||||
const non_dns_udp_req_per_sec = document.querySelector<HTMLInputElement>(
|
||||
"#non_dns_udp_req_per_sec",
|
||||
)?.value;
|
||||
const avg_non_dns_udp_session_timeout = document.querySelector(
|
||||
"#avg_non_dns_udp_session_timeout",
|
||||
)?.value;
|
||||
const private_dns_req_per_sec = document.querySelector(
|
||||
const avg_non_dns_udp_session_timeout =
|
||||
document.querySelector<HTMLInputElement>(
|
||||
"#avg_non_dns_udp_session_timeout",
|
||||
)?.value;
|
||||
const private_dns_req_per_sec = document.querySelector<HTMLInputElement>(
|
||||
"#private_dns_req_per_sec",
|
||||
)?.value;
|
||||
const dns_udp_timeout_in_sec = document.querySelector(
|
||||
const dns_udp_timeout_in_sec = document.querySelector<HTMLInputElement>(
|
||||
"#dns_udp_timeout_in_sec",
|
||||
)?.value;
|
||||
const tcp_per_sec = document.querySelector("#tcp_per_sec")?.value;
|
||||
const available_ports_per_host = document.querySelector(
|
||||
const tcp_per_sec =
|
||||
document.querySelector<HTMLInputElement>("#tcp_per_sec")?.value;
|
||||
const available_ports_per_host = document.querySelector<HTMLInputElement>(
|
||||
"#available_ports_per_host",
|
||||
)?.value;
|
||||
const cloudflared_replicas = document.querySelector(
|
||||
const cloudflared_replicas = document.querySelector<HTMLInputElement>(
|
||||
"#cloudflared_replicas",
|
||||
)?.value;
|
||||
|
||||
|
|
@ -162,16 +164,32 @@ const metrics = Object.entries({
|
|||
60`,
|
||||
);
|
||||
|
||||
document.querySelector("#percent_capacity_per_replica").value = round(
|
||||
percent_capacity_per_replica,
|
||||
2,
|
||||
);
|
||||
document.querySelector("#percent_capacity_across_all_replicas").value =
|
||||
round(percent_capacity_across_all_replicas, 2);
|
||||
document.querySelector("#max_dns_request_per_min").value = round(
|
||||
max_dns_request_per_min,
|
||||
2,
|
||||
const perceptCapacityPerReplicaInput =
|
||||
document.querySelector<HTMLInputElement>("#percent_capacity_per_replica");
|
||||
if (perceptCapacityPerReplicaInput) {
|
||||
perceptCapacityPerReplicaInput.value = round(
|
||||
percent_capacity_per_replica,
|
||||
2,
|
||||
);
|
||||
}
|
||||
|
||||
const percentCapacityAcrossAllReplicasInput =
|
||||
document.querySelector<HTMLInputElement>(
|
||||
"#percent_capacity_across_all_replicas",
|
||||
);
|
||||
if (percentCapacityAcrossAllReplicasInput) {
|
||||
percentCapacityAcrossAllReplicasInput.value = round(
|
||||
percent_capacity_across_all_replicas,
|
||||
2,
|
||||
);
|
||||
}
|
||||
|
||||
const maxDnsRequestPerMinInput = document.querySelector<HTMLInputElement>(
|
||||
"#max_dns_request_per_min",
|
||||
);
|
||||
if (maxDnsRequestPerMinInput) {
|
||||
maxDnsRequestPerMinInput.value = round(max_dns_request_per_min, 2);
|
||||
}
|
||||
}
|
||||
|
||||
document
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
import type { ComponentProps } from "astro/types";
|
||||
import { getCollection } from "astro:content";
|
||||
import { slug } from "github-slugger";
|
||||
|
||||
import AnchorHeading from "~/components/AnchorHeading.astro";
|
||||
import InlineBadge from "~/components/InlineBadge.astro";
|
||||
|
|
@ -9,14 +9,19 @@ const models = await getCollection("workers-ai-models");
|
|||
const mapped = models.map((x) => x.data);
|
||||
const grouped = Object.groupBy(mapped, (entry) => entry.task_type);
|
||||
const entries = Object.entries(grouped).reverse();
|
||||
|
||||
type InlineBadgeProps = ComponentProps<typeof InlineBadge>;
|
||||
---
|
||||
|
||||
{
|
||||
entries.map(([_, models]) => (
|
||||
<>
|
||||
<AnchorHeading depth={2} title={models?.at(0).model.task.name} />
|
||||
<AnchorHeading
|
||||
depth={2}
|
||||
title={models?.at(0)?.model?.task?.name ?? "Unknown"}
|
||||
/>
|
||||
|
||||
<p>{models?.at(0).model.task.description}</p>
|
||||
<p>{models?.at(0)?.model?.task?.description ?? "Unknown"}</p>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
|
|
@ -27,26 +32,27 @@ const entries = Object.entries(grouped).reverse();
|
|||
</thead>
|
||||
<tbody>
|
||||
{models?.map((model) => {
|
||||
const badges = model.model.properties.flatMap(
|
||||
({ property_id, value }) => {
|
||||
const badges = model.model.properties
|
||||
.flatMap(({ property_id, value }): InlineBadgeProps | null => {
|
||||
if (property_id === "beta" && value === "true") {
|
||||
return {
|
||||
preset: "beta",
|
||||
};
|
||||
text: "Beta",
|
||||
} as const;
|
||||
}
|
||||
|
||||
if (property_id === "lora" && value === "true") {
|
||||
return {
|
||||
variant: "tip",
|
||||
text: "LoRA",
|
||||
};
|
||||
} as const;
|
||||
}
|
||||
|
||||
if (property_id === "function_calling" && value === "true") {
|
||||
return {
|
||||
variant: "note",
|
||||
text: "Function calling",
|
||||
};
|
||||
} as const;
|
||||
}
|
||||
|
||||
if (property_id === "planned_deprecation_date") {
|
||||
|
|
@ -61,9 +67,9 @@ const entries = Object.entries(grouped).reverse();
|
|||
return { variant: "danger", text: "Planned deprecation" };
|
||||
}
|
||||
|
||||
return [];
|
||||
},
|
||||
);
|
||||
return null;
|
||||
})
|
||||
.filter(Boolean) as InlineBadgeProps[];
|
||||
|
||||
return (
|
||||
<tr>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
---
|
||||
// @ts-nocheck xmlns in SVGs makes Astro very upset, so we'll just ignore this file
|
||||
---
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
|
|
@ -1139,4 +1143,4 @@
|
|||
stroke-miterlimit="10"
|
||||
pointer-events="none"></path></g
|
||||
></svg
|
||||
>
|
||||
>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
|
@ -55,7 +55,7 @@ const blocks = [
|
|||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
] as const;
|
||||
---
|
||||
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ if (currentSection) {
|
|||
Astro.props.entry.data.head.push({
|
||||
tag: "meta",
|
||||
attrs: { property: "og:title", content: title },
|
||||
content: "",
|
||||
});
|
||||
}
|
||||
if (product.data.product.title) {
|
||||
|
|
@ -58,6 +59,7 @@ if (currentSection) {
|
|||
}
|
||||
|
||||
Astro.props.entry.data.description ??= await getPageDescription(
|
||||
// @ts-expect-error TODO: improve types
|
||||
Astro.props.entry,
|
||||
);
|
||||
|
||||
|
|
@ -70,7 +72,7 @@ if (Astro.props.entry.data.pcx_content_type) {
|
|||
tag: "meta",
|
||||
attrs: {
|
||||
name: "pcx_content_type",
|
||||
content: Astro.props.entry.data.pcx_content_type,
|
||||
content: contentType,
|
||||
},
|
||||
content: "",
|
||||
});
|
||||
|
|
@ -78,7 +80,7 @@ if (Astro.props.entry.data.pcx_content_type) {
|
|||
tag: "meta",
|
||||
attrs: {
|
||||
name: "algolia_content_type",
|
||||
content: Astro.props.entry.data.pcx_content_type,
|
||||
content: contentType,
|
||||
},
|
||||
content: "",
|
||||
});
|
||||
|
|
@ -119,7 +121,7 @@ if (Astro.props.entry.data.updated) {
|
|||
tag: "meta",
|
||||
attrs: {
|
||||
name: "pcx_last_reviewed",
|
||||
content: daysBetween,
|
||||
content: daysBetween.toString(),
|
||||
},
|
||||
content: "",
|
||||
});
|
||||
|
|
@ -128,7 +130,7 @@ if (Astro.props.entry.data.updated) {
|
|||
// end metadata
|
||||
|
||||
if (Astro.props.entry.data.pcx_content_type === "changelog") {
|
||||
const href = new URL(Astro.site);
|
||||
const href = new URL(Astro.site ?? "");
|
||||
href.pathname = Astro.props.entry.slug.concat("/index.xml");
|
||||
|
||||
Astro.props.entry.data.head.push({
|
||||
|
|
@ -136,7 +138,7 @@ if (Astro.props.entry.data.pcx_content_type === "changelog") {
|
|||
attrs: {
|
||||
rel: "alternate",
|
||||
type: "application/rss+xml",
|
||||
href: href,
|
||||
href: href.toString(),
|
||||
},
|
||||
content: "",
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
---
|
||||
import type { Props } from "@astrojs/starlight/props";
|
||||
import '@astrojs/starlight/style/markdown.css';
|
||||
// @ts-expect-error no types available
|
||||
import ImageZoom from "starlight-image-zoom/components/ImageZoom.astro";
|
||||
/*
|
||||
MIT License
|
||||
|
|
@ -118,4 +117,4 @@ const { tableOfContents } = Astro.props.entry.data;
|
|||
margin: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -4,31 +4,41 @@ import Default from "@astrojs/starlight/components/Sidebar.astro";
|
|||
|
||||
import { Icon as AstroIcon } from "astro-icon/components";
|
||||
import { getEntry } from "astro:content";
|
||||
import { z } from "astro:schema";
|
||||
import { Badge } from "@astrojs/starlight/components";
|
||||
import type { ComponentProps, HTMLAttributes } from "astro/types";
|
||||
import type { AstroBuiltinAttributes } from "astro";
|
||||
|
||||
const { sidebar, slug } = Astro.props;
|
||||
|
||||
const linkHTMLAttributesSchema = z.record(
|
||||
z.union([z.string(), z.number(), z.boolean(), z.undefined()]),
|
||||
) as z.Schema<
|
||||
Omit<HTMLAttributes<"a">, keyof AstroBuiltinAttributes | "children">
|
||||
>;
|
||||
type LinkHTMLAttributes = z.infer<typeof linkHTMLAttributesSchema>;
|
||||
|
||||
interface Link {
|
||||
type: "link";
|
||||
label: string;
|
||||
href: string;
|
||||
isCurrent: boolean;
|
||||
badge: any | undefined;
|
||||
attrs: any;
|
||||
badge: ComponentProps<typeof Badge> | undefined;
|
||||
attrs: LinkHTMLAttributes;
|
||||
order: number;
|
||||
}
|
||||
type SidebarEntry = Link | Group;
|
||||
|
||||
interface Group {
|
||||
type: "group";
|
||||
label: string;
|
||||
entries: (Link | Group)[];
|
||||
entries: SidebarEntry[];
|
||||
collapsed: boolean;
|
||||
badge: Badge | undefined;
|
||||
badge: ComponentProps<typeof Badge> | undefined;
|
||||
order: number;
|
||||
}
|
||||
``;
|
||||
|
||||
type SidebarEntry = Link | Group;
|
||||
|
||||
const currentSection = slug?.split("/")[0];
|
||||
|
||||
let filtered = sidebar.filter(
|
||||
|
|
@ -133,9 +143,11 @@ async function handleLink(link: Link): Promise<Link> {
|
|||
...link,
|
||||
label: link.label.concat(" ↗"),
|
||||
href: frontmatter.external_link,
|
||||
badge: frontmatter.external_link.startsWith("/api") && {
|
||||
text: "API",
|
||||
},
|
||||
badge: frontmatter.external_link.startsWith("/api")
|
||||
? {
|
||||
text: "API",
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -163,14 +175,14 @@ const lookupProductTitle = async (slug: string) => {
|
|||
if (product === "learning-paths") {
|
||||
const path = segments[1];
|
||||
|
||||
const { data } = await getEntry("learning-paths", path);
|
||||
const entryData = await getEntry("learning-paths", path);
|
||||
|
||||
return `${data.title} (Learning Paths)`;
|
||||
return `${entryData?.data?.title} (Learning Paths)`;
|
||||
}
|
||||
|
||||
const { data } = await getEntry("products", product);
|
||||
const entryData = await getEntry("products", product);
|
||||
|
||||
return data.product.title;
|
||||
return entryData?.data?.product?.title ?? "Unknown";
|
||||
};
|
||||
---
|
||||
|
||||
|
|
@ -188,6 +200,7 @@ const lookupProductTitle = async (slug: string) => {
|
|||
</strong>
|
||||
</span>
|
||||
</a>
|
||||
<!-- @ts-expect-error sidebar props don't match as we add additional things -->
|
||||
<Default {...Astro.props} sidebar={filtered.entries}><slot /></Default>
|
||||
|
||||
<style is:global>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
---
|
||||
/// <reference path="../../../node_modules/@astrojs/starlight/virtual.d.ts" />
|
||||
import { logos } from "virtual:starlight/user-images";
|
||||
import config from "virtual:starlight/user-config";
|
||||
import type { Props } from "../props";
|
||||
import type { Props } from "@astrojs/starlight/props";
|
||||
const { siteTitle, siteTitleHref } = Astro.props;
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
name: Registrar
|
||||
logo: <svg class="c_hf c_ck c_dw c_hg" role="presentation" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" aria-hidden="true" focusable="false"><path d="M8.21 1.503C8.168 1.5 8.127 1.5 8.085 1.5h-.082a6.5 6.5 0 100 13h.082A6.5 6.5 0 008.21 1.503zm4.775 4.165H11.7a7.625 7.625 0 00-.975-2.446 5.54 5.54 0 012.258 2.446zM8.5 2.561c.93.276 1.759 1.457 2.175 3.107H8.5V2.561zm-1 .061v3.046H5.496C5.886 4.12 6.64 2.983 7.5 2.622zm-1.965.464a7.47 7.47 0 00-1.066 2.582H3.022a5.538 5.538 0 012.513-2.583zm-2.556 7.15h1.469a7.555 7.555 0 001.081 2.676 5.534 5.534 0 01-2.55-2.675zM7.5 13.38c-.88-.367-1.646-1.54-2.027-3.142H7.5v3.142zm1 .06v-3.202h2.197C10.291 11.94 9.45 13.16 8.5 13.439zm2.231-.665a7.71 7.71 0 00.991-2.537h1.305a5.534 5.534 0 01-2.296 2.538zM2.643 9.237a5.522 5.522 0 01.023-2.569h10.675c.21.843.217 1.723.023 2.569H2.644z"></path><path d="M6.236 8.165h-.01l-.183-.78h-.382l-.178.786h-.01l-.166-.786h-.392l.32 1.227h.418l.192-.715h.014l.191.715h.418l.32-1.227h-.392l-.16.78zM8.177 8.165h-.011l-.182-.78h-.383l-.177.786h-.01l-.166-.786h-.392l.32 1.227h.418l.192-.715h.013l.192.715h.418l.32-1.227h-.392l-.16.78zM10.117 8.165h-.01l-.182-.78h-.383l-.177.786h-.01l-.166-.786h-.392l.319 1.227h.419l.191-.715h.014l.192.715h.418l.32-1.227h-.392l-.16.78zM10.881 8.222a.2.2 0 00-.147.06.193.193 0 00-.06.146.195.195 0 00.06.146.199.199 0 00.147.061c.036 0 .071-.01.102-.028a.22.22 0 00.075-.075.2.2 0 00.014-.184.195.195 0 00-.047-.066.2.2 0 00-.144-.06z"></path></svg>
|
||||
logo: <svg class="c_hf c_ck c_dw c_hg" role="presentation" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" aria-hidden="true"><path d="M8.21 1.503C8.168 1.5 8.127 1.5 8.085 1.5h-.082a6.5 6.5 0 100 13h.082A6.5 6.5 0 008.21 1.503zm4.775 4.165H11.7a7.625 7.625 0 00-.975-2.446 5.54 5.54 0 012.258 2.446zM8.5 2.561c.93.276 1.759 1.457 2.175 3.107H8.5V2.561zm-1 .061v3.046H5.496C5.886 4.12 6.64 2.983 7.5 2.622zm-1.965.464a7.47 7.47 0 00-1.066 2.582H3.022a5.538 5.538 0 012.513-2.583zm-2.556 7.15h1.469a7.555 7.555 0 001.081 2.676 5.534 5.534 0 01-2.55-2.675zM7.5 13.38c-.88-.367-1.646-1.54-2.027-3.142H7.5v3.142zm1 .06v-3.202h2.197C10.291 11.94 9.45 13.16 8.5 13.439zm2.231-.665a7.71 7.71 0 00.991-2.537h1.305a5.534 5.534 0 01-2.296 2.538zM2.643 9.237a5.522 5.522 0 01.023-2.569h10.675c.21.843.217 1.723.023 2.569H2.644z"></path><path d="M6.236 8.165h-.01l-.183-.78h-.382l-.178.786h-.01l-.166-.786h-.392l.32 1.227h.418l.192-.715h.014l.191.715h.418l.32-1.227h-.392l-.16.78zM8.177 8.165h-.011l-.182-.78h-.383l-.177.786h-.01l-.166-.786h-.392l.32 1.227h.418l.192-.715h.013l.192.715h.418l.32-1.227h-.392l-.16.78zM10.117 8.165h-.01l-.182-.78h-.383l-.177.786h-.01l-.166-.786h-.392l.319 1.227h.419l.191-.715h.014l.192.715h.418l.32-1.227h-.392l-.16.78zM10.881 8.222a.2.2 0 00-.147.06.193.193 0 00-.06.146.195.195 0 00.06.146.199.199 0 00.147.061c.036 0 .071-.01.102-.028a.22.22 0 00.075-.075.2.2 0 00.014-.184.195.195 0 00-.047-.066.2.2 0 00-.144-.06z"></path></svg>
|
||||
|
||||
product:
|
||||
title: Registrar
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
<svg class="c_hf c_ck c_dw c_hg" role="presentation" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" aria-hidden="true" focusable="false"><path d="M8.21 1.503C8.168 1.5 8.127 1.5 8.085 1.5h-.082a6.5 6.5 0 100 13h.082A6.5 6.5 0 008.21 1.503zm4.775 4.165H11.7a7.625 7.625 0 00-.975-2.446 5.54 5.54 0 012.258 2.446zM8.5 2.561c.93.276 1.759 1.457 2.175 3.107H8.5V2.561zm-1 .061v3.046H5.496C5.886 4.12 6.64 2.983 7.5 2.622zm-1.965.464a7.47 7.47 0 00-1.066 2.582H3.022a5.538 5.538 0 012.513-2.583zm-2.556 7.15h1.469a7.555 7.555 0 001.081 2.676 5.534 5.534 0 01-2.55-2.675zM7.5 13.38c-.88-.367-1.646-1.54-2.027-3.142H7.5v3.142zm1 .06v-3.202h2.197C10.291 11.94 9.45 13.16 8.5 13.439zm2.231-.665a7.71 7.71 0 00.991-2.537h1.305a5.534 5.534 0 01-2.296 2.538zM2.643 9.237a5.522 5.522 0 01.023-2.569h10.675c.21.843.217 1.723.023 2.569H2.644z"></path><path d="M6.236 8.165h-.01l-.183-.78h-.382l-.178.786h-.01l-.166-.786h-.392l.32 1.227h.418l.192-.715h.014l.191.715h.418l.32-1.227h-.392l-.16.78zM8.177 8.165h-.011l-.182-.78h-.383l-.177.786h-.01l-.166-.786h-.392l.32 1.227h.418l.192-.715h.013l.192.715h.418l.32-1.227h-.392l-.16.78zM10.117 8.165h-.01l-.182-.78h-.383l-.177.786h-.01l-.166-.786h-.392l.319 1.227h.419l.191-.715h.014l.192.715h.418l.32-1.227h-.392l-.16.78zM10.881 8.222a.2.2 0 00-.147.06.193.193 0 00-.06.146.195.195 0 00.06.146.199.199 0 00.147.061c.036 0 .071-.01.102-.028a.22.22 0 00.075-.075.2.2 0 00.014-.184.195.195 0 00-.047-.066.2.2 0 00-.144-.06z"></path></svg>
|
||||
<svg class="c_hf c_ck c_dw c_hg" role="presentation" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" aria-hidden="true"><path d="M8.21 1.503C8.168 1.5 8.127 1.5 8.085 1.5h-.082a6.5 6.5 0 100 13h.082A6.5 6.5 0 008.21 1.503zm4.775 4.165H11.7a7.625 7.625 0 00-.975-2.446 5.54 5.54 0 012.258 2.446zM8.5 2.561c.93.276 1.759 1.457 2.175 3.107H8.5V2.561zm-1 .061v3.046H5.496C5.886 4.12 6.64 2.983 7.5 2.622zm-1.965.464a7.47 7.47 0 00-1.066 2.582H3.022a5.538 5.538 0 012.513-2.583zm-2.556 7.15h1.469a7.555 7.555 0 001.081 2.676 5.534 5.534 0 01-2.55-2.675zM7.5 13.38c-.88-.367-1.646-1.54-2.027-3.142H7.5v3.142zm1 .06v-3.202h2.197C10.291 11.94 9.45 13.16 8.5 13.439zm2.231-.665a7.71 7.71 0 00.991-2.537h1.305a5.534 5.534 0 01-2.296 2.538zM2.643 9.237a5.522 5.522 0 01.023-2.569h10.675c.21.843.217 1.723.023 2.569H2.644z"></path><path d="M6.236 8.165h-.01l-.183-.78h-.382l-.178.786h-.01l-.166-.786h-.392l.32 1.227h.418l.192-.715h.014l.191.715h.418l.32-1.227h-.392l-.16.78zM8.177 8.165h-.011l-.182-.78h-.383l-.177.786h-.01l-.166-.786h-.392l.32 1.227h.418l.192-.715h.013l.192.715h.418l.32-1.227h-.392l-.16.78zM10.117 8.165h-.01l-.182-.78h-.383l-.177.786h-.01l-.166-.786h-.392l.319 1.227h.419l.191-.715h.014l.192.715h.418l.32-1.227h-.392l-.16.78zM10.881 8.222a.2.2 0 00-.147.06.193.193 0 00-.06.146.195.195 0 00.06.146.199.199 0 00.147.061c.036 0 .071-.01.102-.028a.22.22 0 00.075-.075.2.2 0 00.014-.184.195.195 0 00-.047-.066.2.2 0 00-.144-.06z"></path></svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" aria-hidden="true" focusable="false"><path d="M10.72 1.5H9.265v3.198l1.455.004V1.5ZM7.36 3.347l1.516 1.517-1.032 1.025-1.513-1.513 1.03-1.029ZM4.485 6.28h3.202l-.005 1.455H4.485V6.28Zm1.848 3.36 1.515-1.516 1.026 1.032-1.512 1.512-1.03-1.029Zm2.932 2.875V9.313l1.455.005v3.197H9.265Zm3.36-1.845-1.517-1.517 1.032-1.026 1.514 1.514-1.03 1.029ZM15.5 7.735h-3.202l.005-1.455H15.5v1.455Zm-1.847-3.359-1.516 1.516-1.025-1.032 1.513-1.513 1.028 1.03ZM2 1.829v.82h-.822v1.315H2v.821h1.314v-.82h.821V2.65h-.821v-.821H2Zm0 12.842v-1.5H.5v-1.314H2v-1.499h1.314v1.499h1.5v1.314h-1.5v1.5H2Z"></path></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" role="presentation" viewBox="0 0 20 20" aria-hidden="true"><path d="M10.72 1.5H9.265v3.198l1.455.004V1.5ZM7.36 3.347l1.516 1.517-1.032 1.025-1.513-1.513 1.03-1.029ZM4.485 6.28h3.202l-.005 1.455H4.485V6.28Zm1.848 3.36 1.515-1.516 1.026 1.032-1.512 1.512-1.03-1.029Zm2.932 2.875V9.313l1.455.005v3.197H9.265Zm3.36-1.845-1.517-1.517 1.032-1.026 1.514 1.514-1.03 1.029ZM15.5 7.735h-3.202l.005-1.455H15.5v1.455Zm-1.847-3.359-1.516 1.516-1.025-1.032 1.513-1.513 1.028 1.03ZM2 1.829v.82h-.822v1.315H2v.821h1.314v-.82h.821V2.65h-.821v-.821H2Zm0 12.842v-1.5H.5v-1.314H2v-1.499h1.314v1.499h1.5v1.314h-1.5v1.5H2Z"></path></svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 653 B After Width: | Height: | Size: 656 B |
10
src/modules.d.ts
vendored
Normal file
10
src/modules.d.ts
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
declare module "astro-live-code" {
|
||||
type LiveCodeOptions = {
|
||||
layout?: string;
|
||||
wrapper?: string;
|
||||
defaultProps?: Record<string, any>;
|
||||
};
|
||||
export default function AstroLiveCode(
|
||||
options?: LiveCodeOptions,
|
||||
): import("astro").AstroIntegration;
|
||||
}
|
||||
|
|
@ -1,117 +1,142 @@
|
|||
import rss from '@astrojs/rss';
|
||||
import rss from "@astrojs/rss";
|
||||
import { getCollection, getEntry } from "astro:content";
|
||||
import type { APIRoute } from 'astro';
|
||||
import { marked, type Token } from 'marked';
|
||||
import { getWranglerChangelog } from '~/util/changelogs';
|
||||
import { slug } from "github-slugger"
|
||||
import { entryToString } from '~/util/container';
|
||||
import type { APIRoute } from "astro";
|
||||
import { marked, type Token } from "marked";
|
||||
import { getWranglerChangelog } from "~/util/changelogs";
|
||||
import { slug } from "github-slugger";
|
||||
import { entryToString } from "~/util/container";
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const changelogs = await getCollection("docs", (entry) => {
|
||||
return entry.data.pcx_content_type === "changelog" && entry.data.changelog_file_name || entry.data.changelog_product_area_name
|
||||
});
|
||||
const changelogs = await getCollection("docs", (entry) => {
|
||||
return (
|
||||
(entry.data.pcx_content_type === "changelog" &&
|
||||
entry.data.changelog_file_name) ||
|
||||
entry.data.changelog_product_area_name
|
||||
);
|
||||
});
|
||||
|
||||
return changelogs.map((entry) => {
|
||||
return {
|
||||
params: {
|
||||
changelog: entry.slug + `/index`
|
||||
},
|
||||
props: {
|
||||
entry
|
||||
}
|
||||
}
|
||||
})
|
||||
return changelogs.map((entry) => {
|
||||
return {
|
||||
params: {
|
||||
changelog: entry.slug + `/index`,
|
||||
},
|
||||
props: {
|
||||
entry,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export const GET: APIRoute = async (context) => {
|
||||
function walkTokens(token: Token) {
|
||||
if (token.type === 'image' || token.type === 'link') {
|
||||
if (token.href.startsWith("/")) {
|
||||
token.href = context.site + token.href.slice(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
function walkTokens(token: Token) {
|
||||
if (token.type === "image" || token.type === "link") {
|
||||
if (token.href.startsWith("/")) {
|
||||
token.href = context.site + token.href.slice(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
marked.use({ walkTokens });
|
||||
marked.use({ walkTokens });
|
||||
|
||||
const entry = context.props.entry;
|
||||
const entry = context.props.entry;
|
||||
|
||||
if (!entry.data.changelog_file_name && !entry.data.changelog_product_area_name) {
|
||||
throw new Error(`One of changelog_file_name or changelog_product_area_name is required on ${entry.id}, to generate RSS feeds.`)
|
||||
}
|
||||
if (
|
||||
!entry.data.changelog_file_name &&
|
||||
!entry.data.changelog_product_area_name
|
||||
) {
|
||||
throw new Error(
|
||||
`One of changelog_file_name or changelog_product_area_name is required on ${entry.id}, to generate RSS feeds.`,
|
||||
);
|
||||
}
|
||||
|
||||
const changelogs = await getCollection("changelogs", (changelog) => {
|
||||
return entry.data.changelog_file_name?.includes(changelog.id) || changelog.data.productArea === entry.data.changelog_product_area_name
|
||||
})
|
||||
const changelogs = await getCollection("changelogs", (changelog) => {
|
||||
return (
|
||||
entry.data.changelog_file_name?.includes(changelog.id) ||
|
||||
changelog.data.productArea === entry.data.changelog_product_area_name
|
||||
);
|
||||
});
|
||||
|
||||
if (entry.data.changelog_file_name?.includes("wrangler")) {
|
||||
changelogs.push(await getWranglerChangelog());
|
||||
}
|
||||
if (entry.data.changelog_file_name?.includes("wrangler")) {
|
||||
changelogs.push(await getWranglerChangelog());
|
||||
}
|
||||
|
||||
const mapped = await Promise.all(changelogs.flatMap((product) => {
|
||||
return product.data.entries.map(async (entry) => {
|
||||
let description;
|
||||
if (entry.individual_page) {
|
||||
const link = entry.link;
|
||||
const mapped = await Promise.all(
|
||||
changelogs.flatMap((product) => {
|
||||
return product.data.entries.map(async (entry) => {
|
||||
let description;
|
||||
if (entry.individual_page) {
|
||||
const link = entry.link;
|
||||
|
||||
const page = await getEntry("docs", link.slice(1, -1));
|
||||
if (!link)
|
||||
throw new Error(
|
||||
`Changelog entry points to individual page but no link is provided`,
|
||||
);
|
||||
|
||||
if (!page) throw new Error(`Changelog entry points to ${link.slice(1, -1)} but unable to find entry with that slug`)
|
||||
const page = await getEntry("docs", link.slice(1, -1));
|
||||
|
||||
description = await entryToString(page) ?? page.body;
|
||||
} else {
|
||||
description = entry.description;
|
||||
}
|
||||
if (!page)
|
||||
throw new Error(
|
||||
`Changelog entry points to ${link.slice(1, -1)} but unable to find entry with that slug`,
|
||||
);
|
||||
|
||||
let link;
|
||||
if (entry.link) {
|
||||
link = entry.link
|
||||
} else {
|
||||
const anchor = slug(entry.title ?? entry.publish_date);
|
||||
link = product.data.link.concat(`#${anchor}`);
|
||||
}
|
||||
description = (await entryToString(page)) ?? page.body;
|
||||
} else {
|
||||
description = entry.description;
|
||||
}
|
||||
|
||||
let title;
|
||||
if (entry.scheduled) {
|
||||
title = `Scheduled for ${entry.scheduled_date}`
|
||||
} else {
|
||||
title = entry.title;
|
||||
}
|
||||
let link;
|
||||
if (entry.link) {
|
||||
link = entry.link;
|
||||
} else {
|
||||
const anchor = slug(entry.title ?? entry.publish_date);
|
||||
link = product.data.link.concat(`#${anchor}`);
|
||||
}
|
||||
|
||||
return {
|
||||
product: product.data.productName,
|
||||
link,
|
||||
date: entry.publish_date,
|
||||
description,
|
||||
title,
|
||||
};
|
||||
});
|
||||
}));
|
||||
let title;
|
||||
if (entry.scheduled) {
|
||||
title = `Scheduled for ${entry.scheduled_date}`;
|
||||
} else {
|
||||
title = entry.title;
|
||||
}
|
||||
|
||||
const entries = mapped.sort((a, b) => {
|
||||
return (a.date < b.date) ? 1 : ((a.date > b.date) ? -1 : 0);
|
||||
});
|
||||
return {
|
||||
product: product.data.productName,
|
||||
link,
|
||||
date: entry.publish_date,
|
||||
description,
|
||||
title,
|
||||
};
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
const rssName = entry.data.changelog_product_area_name || changelogs[0].data.productName;
|
||||
const entries = mapped.sort((a, b) => {
|
||||
return a.date < b.date ? 1 : a.date > b.date ? -1 : 0;
|
||||
});
|
||||
|
||||
const site = new URL(context.site);
|
||||
site.pathname = entry.slug.concat("/");
|
||||
const rssName =
|
||||
entry.data.changelog_product_area_name || changelogs[0].data.productName;
|
||||
|
||||
const isArea = Boolean(entry.data.changelog_product_area_name);
|
||||
const site = new URL(context.site ?? "");
|
||||
site.pathname = entry.slug.concat("/");
|
||||
|
||||
return rss({
|
||||
title: `Changelog | ${rssName}`,
|
||||
description: `Updates to ${rssName}`,
|
||||
site,
|
||||
trailingSlash: false,
|
||||
items: entries.map((entry) => {
|
||||
return {
|
||||
title: `${entry.product} - ${entry.title ?? entry.date}`,
|
||||
description: marked.parse(entry.description),
|
||||
pubDate: new Date(entry.date),
|
||||
link: entry.link,
|
||||
customData: isArea ? `<product>${entry.product}</product>` : undefined,
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
const isArea = Boolean(entry.data.changelog_product_area_name);
|
||||
|
||||
return rss({
|
||||
title: `Changelog | ${rssName}`,
|
||||
description: `Updates to ${rssName}`,
|
||||
site,
|
||||
trailingSlash: false,
|
||||
items: entries.map((entry) => {
|
||||
return {
|
||||
title: `${entry.product} - ${entry.title ?? entry.date}`,
|
||||
description: marked.parse(entry.description ?? "", {
|
||||
async: false,
|
||||
}) as string,
|
||||
pubDate: new Date(entry.date),
|
||||
link: entry.link,
|
||||
customData: isArea ? `<product>${entry.product}</product>` : undefined,
|
||||
};
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,39 +9,76 @@ const { products, productAreas, changelogs } = await getChangelogs();
|
|||
|
||||
<StarlightPage frontmatter={{ title: "Changelogs", template: "splash" }}>
|
||||
<Badge text="Beta" variant="caution" size="medium" />
|
||||
<br/>
|
||||
<br/>
|
||||
<p id="productDescription">Subscribe to all Changelog posts via <a href="/changelog/index.xml">RSS</a>.</p>
|
||||
<p id="productAreaDescription" style="display:none">Subscribe to all Changelog posts via <a class="rssLink" href="/changelog/index.xml">RSS</a>.</p>
|
||||
<p>Unless otherwise noted, all dates refer to the release date of the change.</p>
|
||||
<br/>
|
||||
<br />
|
||||
<br />
|
||||
<p id="productDescription">
|
||||
Subscribe to all Changelog posts via <a href="/changelog/index.xml">RSS</a>.
|
||||
</p>
|
||||
<p id="productAreaDescription" style="display:none">
|
||||
Subscribe to all Changelog posts via <a
|
||||
class="rssLink"
|
||||
href="/changelog/index.xml">RSS</a
|
||||
>.
|
||||
</p>
|
||||
<p>
|
||||
Unless otherwise noted, all dates refer to the release date of the change.
|
||||
</p>
|
||||
<br />
|
||||
<label for="products">Product:</label>
|
||||
<select name="products" id="products">
|
||||
<option value="all">Select...</option>
|
||||
{products.map((product) => <option value={product.toLowerCase()}>{product}</option>)}
|
||||
{
|
||||
products.map((product) => (
|
||||
<option value={product.toLowerCase()}>{product}</option>
|
||||
))
|
||||
}
|
||||
</select>
|
||||
<label for="productAreas">Product area:</label>
|
||||
<select name="productAreas" id="productAreas">
|
||||
<option value="all">Select...</option>
|
||||
{productAreas.map((productAreas) => <option value={productAreas.toLowerCase()}>{productAreas}</option>)}
|
||||
{
|
||||
productAreas.map((productAreas) => (
|
||||
<option value={productAreas.toLowerCase()}>{productAreas}</option>
|
||||
))
|
||||
}
|
||||
</select>
|
||||
{
|
||||
changelogs.map(([date, entries]) => (
|
||||
<div style="overflow-anchor: none;" data-date={date}>
|
||||
<h2>{date}</h2>
|
||||
{entries?.map((entry) => (
|
||||
<div data-product={entry.product.toLowerCase()} data-productArea={entry.productAreaName.toLowerCase()}>
|
||||
<div
|
||||
data-product={entry.product.toLowerCase()}
|
||||
data-productArea={entry.productAreaName.toLowerCase()}
|
||||
>
|
||||
<h3>
|
||||
<a href={entry.link}>{entry.product}</a>
|
||||
</h3>
|
||||
{["WAF", "DDoS protection"].includes(entry.product) ? (
|
||||
<p set:html={marked.parse((entry.scheduled ? "**" + "Scheduled changes for " + (entry.date ?? "") + "**" : "**" + (entry.date ?? "") + "**"))}></p>
|
||||
<p
|
||||
set:html={marked.parse(
|
||||
entry.scheduled
|
||||
? "**" +
|
||||
"Scheduled changes for " +
|
||||
(entry.date ?? "") +
|
||||
"**"
|
||||
: "**" + (entry.date ?? "") + "**",
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<p set:html={marked.parse("**" + (entry.title ?? "") + "**")} />
|
||||
)}
|
||||
{["WAF", "DDoS protection"].includes(entry.product) ? (<p set:html={marked.parse("For more details, refer to the [changelog page](" + entry.link + ").")}></p>
|
||||
) : (
|
||||
<p set:html={marked.parse(entry.description ?? "")} />)}
|
||||
{["WAF", "DDoS protection"].includes(entry.product) ? (
|
||||
<p
|
||||
set:html={marked.parse(
|
||||
"For more details, refer to the [changelog page](" +
|
||||
entry.link +
|
||||
").",
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<p set:html={marked.parse(entry.description ?? "")} />
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
|
@ -50,20 +87,25 @@ const { products, productAreas, changelogs } = await getChangelogs();
|
|||
</StarlightPage>
|
||||
|
||||
<script>
|
||||
import StickyHeader from '@codingheads/sticky-header';
|
||||
import StickyHeader from "@codingheads/sticky-header";
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const navHeightRem = getComputedStyle(document.body).getPropertyValue('--sl-nav-height');
|
||||
const navHeightRem = getComputedStyle(document.body).getPropertyValue(
|
||||
"--sl-nav-height",
|
||||
);
|
||||
const navHeightPx = Number(navHeightRem.split("rem")[0]) * 16 + 16;
|
||||
|
||||
const headers = document.querySelectorAll<HTMLElement>("[data-date] > h2");
|
||||
headers.forEach((header) => new StickyHeader(header, { offset: 0 - navHeightPx }));
|
||||
headers.forEach(
|
||||
(header) => new StickyHeader(header, { offset: 0 - navHeightPx }),
|
||||
);
|
||||
});
|
||||
|
||||
const productFilter = document.querySelector<HTMLSelectElement>("#products");
|
||||
productFilter?.addEventListener("change", filterEntries);
|
||||
|
||||
const productAreaFilter = document.querySelector<HTMLSelectElement>("#productAreas");
|
||||
const productAreaFilter =
|
||||
document.querySelector<HTMLSelectElement>("#productAreas");
|
||||
productAreaFilter?.addEventListener("change", filterEntries);
|
||||
|
||||
function filterEntries() {
|
||||
|
|
@ -72,7 +114,10 @@ const { products, productAreas, changelogs } = await getChangelogs();
|
|||
for (const date of dates) {
|
||||
const entries = date.querySelectorAll<HTMLElement>("[data-product]");
|
||||
|
||||
if (productAreaFilter.value === "all" && productFilter.value === "all") {
|
||||
if (
|
||||
productAreaFilter?.value === "all" &&
|
||||
productFilter?.value === "all"
|
||||
) {
|
||||
dates.forEach((x) => {
|
||||
x.style.display = "";
|
||||
});
|
||||
|
|
@ -86,15 +131,19 @@ const { products, productAreas, changelogs } = await getChangelogs();
|
|||
|
||||
for (const entry of entries) {
|
||||
const product = entry.dataset.product;
|
||||
const productArea = entry.dataset.productarea
|
||||
const productArea = entry.dataset.productarea;
|
||||
|
||||
if ((productFilter?.value === product || productFilter?.value === "all") && (productAreaFilter?.value === productArea || productAreaFilter?.value === "all") ) {
|
||||
if (
|
||||
(productFilter?.value === product ||
|
||||
productFilter?.value === "all") &&
|
||||
(productAreaFilter?.value === productArea ||
|
||||
productAreaFilter?.value === "all")
|
||||
) {
|
||||
entry.style.display = "";
|
||||
date.style.display = "";
|
||||
} else {
|
||||
entry.style.display = "none";
|
||||
entriesHidden++;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,83 +1,95 @@
|
|||
import rss from '@astrojs/rss';
|
||||
import rss from "@astrojs/rss";
|
||||
import { getCollection, getEntry } from "astro:content";
|
||||
import type { APIRoute } from 'astro';
|
||||
import { marked } from 'marked';
|
||||
import { getWranglerChangelog } from '~/util/changelogs';
|
||||
import { slug } from "github-slugger"
|
||||
import { entryToString } from '~/util/container';
|
||||
import type { APIRoute } from "astro";
|
||||
import { marked, type Token } from "marked";
|
||||
import { getWranglerChangelog } from "~/util/changelogs";
|
||||
import { slug } from "github-slugger";
|
||||
import { entryToString } from "~/util/container";
|
||||
|
||||
export const GET: APIRoute = async (context) => {
|
||||
function walkTokens(token: Token) {
|
||||
if (token.type === 'image' || token.type === 'link') {
|
||||
if (token.href.startsWith("/")) {
|
||||
token.href = context.site + token.href.slice(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
function walkTokens(token: Token) {
|
||||
if (token.type === "image" || token.type === "link") {
|
||||
if (token.href.startsWith("/")) {
|
||||
token.href = context.site + token.href.slice(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
marked.use({ walkTokens });
|
||||
marked.use({ walkTokens });
|
||||
|
||||
const changelogs = await getCollection("changelogs")
|
||||
const changelogs = await getCollection("changelogs");
|
||||
|
||||
changelogs.push(await getWranglerChangelog());
|
||||
changelogs.push(await getWranglerChangelog());
|
||||
|
||||
const mapped = await Promise.all(changelogs.flatMap((product) => {
|
||||
return product.data.entries.map(async (entry) => {
|
||||
let description;
|
||||
if (entry.individual_page) {
|
||||
const link = entry.link;
|
||||
const mapped = await Promise.all(
|
||||
changelogs.flatMap((product) => {
|
||||
return product.data.entries.map(async (entry) => {
|
||||
let description;
|
||||
if (entry.individual_page) {
|
||||
const link = entry.link;
|
||||
|
||||
const page = await getEntry("docs", link.slice(1, -1));
|
||||
if (!link)
|
||||
throw new Error(
|
||||
`Changelog entry points to individual page but no link is provided`,
|
||||
);
|
||||
|
||||
if (!page) throw new Error(`Changelog entry points to ${link.slice(1, -1)} but unable to find entry with that slug`)
|
||||
const page = await getEntry("docs", link.slice(1, -1));
|
||||
|
||||
description = await entryToString(page) ?? page.body;
|
||||
} else {
|
||||
description = entry.description;
|
||||
}
|
||||
if (!page)
|
||||
throw new Error(
|
||||
`Changelog entry points to ${link.slice(1, -1)} but unable to find entry with that slug`,
|
||||
);
|
||||
|
||||
let link;
|
||||
if (entry.link) {
|
||||
link = entry.link
|
||||
} else {
|
||||
const anchor = slug(entry.title ?? entry.publish_date);
|
||||
link = product.data.link.concat(`#${anchor}`);
|
||||
}
|
||||
description = (await entryToString(page)) ?? page.body;
|
||||
} else {
|
||||
description = entry.description;
|
||||
}
|
||||
|
||||
let title;
|
||||
if (entry.scheduled) {
|
||||
title = `Scheduled for ${entry.scheduled_date}`
|
||||
} else {
|
||||
title = entry.title;
|
||||
}
|
||||
let link;
|
||||
if (entry.link) {
|
||||
link = entry.link;
|
||||
} else {
|
||||
const anchor = slug(entry.title ?? entry.publish_date);
|
||||
link = product.data.link.concat(`#${anchor}`);
|
||||
}
|
||||
|
||||
return {
|
||||
product: product.data.productName,
|
||||
link,
|
||||
date: entry.publish_date,
|
||||
description,
|
||||
title,
|
||||
};
|
||||
});
|
||||
}));
|
||||
let title;
|
||||
if (entry.scheduled) {
|
||||
title = `Scheduled for ${entry.scheduled_date}`;
|
||||
} else {
|
||||
title = entry.title;
|
||||
}
|
||||
|
||||
const entries = mapped.sort((a, b) => {
|
||||
return (a.date < b.date) ? 1 : ((a.date > b.date) ? -1 : 0);
|
||||
});
|
||||
return {
|
||||
product: product.data.productName,
|
||||
link,
|
||||
date: entry.publish_date,
|
||||
description,
|
||||
title,
|
||||
};
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
return rss({
|
||||
title: `Cloudflare product changelog`,
|
||||
description: `Updates to various Cloudflare products.`,
|
||||
site: "https://developers.cloudflare.com/changelog/",
|
||||
trailingSlash: false,
|
||||
items: entries.map((entry) => {
|
||||
return {
|
||||
title: `${entry.product} - ${entry.title ?? entry.date}`,
|
||||
description: marked.parse(entry.description),
|
||||
pubDate: new Date(entry.date),
|
||||
link: entry.link,
|
||||
customData: `<product>${entry.product}</product>`
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
const entries = mapped.sort((a, b) => {
|
||||
return a.date < b.date ? 1 : a.date > b.date ? -1 : 0;
|
||||
});
|
||||
|
||||
return rss({
|
||||
title: `Cloudflare product changelog`,
|
||||
description: `Updates to various Cloudflare products.`,
|
||||
site: "https://developers.cloudflare.com/changelog/",
|
||||
trailingSlash: false,
|
||||
items: entries.map((entry) => {
|
||||
return {
|
||||
title: `${entry.product} - ${entry.title ?? entry.date}`,
|
||||
description: marked.parse(entry.description ?? "", {
|
||||
async: false,
|
||||
}) as string,
|
||||
pubDate: new Date(entry.date),
|
||||
link: entry.link,
|
||||
customData: `<product>${entry.product}</product>`,
|
||||
};
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
---
|
||||
import StarlightPage from "@astrojs/starlight/components/StarlightPage.astro";
|
||||
import { Glossary } from "~/components";
|
||||
import { Glossary as GlossaryComponent } from "~/components";
|
||||
import { Badge } from "~/components";
|
||||
---
|
||||
|
||||
<StarlightPage frontmatter={{ title: "Glossary", template: "splash" }}>
|
||||
<Badge text="Beta" variant="caution" size="medium" />
|
||||
<br/>
|
||||
<Glossary/>
|
||||
<br />
|
||||
<GlossaryComponent />
|
||||
</StarlightPage>
|
||||
|
|
|
|||
|
|
@ -10,245 +10,257 @@ import SectionImageZeroTrustDark from "~/assets/zero-trust-dark.svg";
|
|||
import SectionImageZeroTrustLight from "~/assets/zero-trust-light.svg";
|
||||
|
||||
import {
|
||||
FourCardGrid,
|
||||
ListCard,
|
||||
TryItSection,
|
||||
FeaturedContentSection,
|
||||
RecommendedContentSection,
|
||||
FooterHeroBlock,
|
||||
FourCardGrid,
|
||||
ListCard,
|
||||
TryItSection,
|
||||
FeaturedContentSection,
|
||||
RecommendedContentSection,
|
||||
FooterHeroBlock,
|
||||
} from "~/components";
|
||||
|
||||
const frontmatter = {
|
||||
title: "Welcome to Cloudflare",
|
||||
description:
|
||||
"Explore guides and tutorials to start building on Cloudflare's platform",
|
||||
template: "splash",
|
||||
editUrl: false,
|
||||
hero: {
|
||||
title: "Welcome to Cloudflare",
|
||||
tagline:
|
||||
"Explore guides and tutorials to start building on Cloudflare's platform",
|
||||
image: {
|
||||
dark: HeroImageDark,
|
||||
light: HeroImageLight,
|
||||
},
|
||||
},
|
||||
};
|
||||
title: "Welcome to Cloudflare",
|
||||
description:
|
||||
"Explore guides and tutorials to start building on Cloudflare's platform",
|
||||
template: "splash",
|
||||
hero: {
|
||||
title: "Welcome to Cloudflare",
|
||||
tagline:
|
||||
"Explore guides and tutorials to start building on Cloudflare's platform",
|
||||
image: {
|
||||
dark: HeroImageDark,
|
||||
light: HeroImageLight,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
const topCards = [
|
||||
{
|
||||
title: "Featured",
|
||||
links: [
|
||||
{ text: "Add web analytics", href: "/web-analytics/" },
|
||||
{
|
||||
text: "Troubleshoot errors",
|
||||
href: "/support/troubleshooting/cloudflare-errors/",
|
||||
},
|
||||
{ text: "Register a domain", href: "/registrar/" },
|
||||
{ text: "Setup 1.1.1.1", href: "/1.1.1.1/setup/" },
|
||||
{
|
||||
text: "Get started with Cloudflare",
|
||||
href: "/learning-paths/get-started/",
|
||||
},
|
||||
],
|
||||
cta: {
|
||||
text: "View all products",
|
||||
href: "/products/",
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Developer Products",
|
||||
links: [
|
||||
{ text: "Workers", href: "/workers/" },
|
||||
{ text: "Pages", href: "/pages/" },
|
||||
{ text: "R2", href: "/r2/" },
|
||||
{ text: "Images", href: "/images/" },
|
||||
{ text: "Stream", href: "/stream/" },
|
||||
],
|
||||
cta: {
|
||||
text: "View all developer products",
|
||||
href: "/products/?product-group=Developer+platform",
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "AI Products",
|
||||
links: [
|
||||
{
|
||||
text: "Build a RAG",
|
||||
href: "/workers-ai/tutorials/build-a-retrieval-augmented-generation-ai/",
|
||||
},
|
||||
{ text: "Workers AI", href: "/workers-ai/" },
|
||||
{ text: "Vectorize", href: "/vectorize/" },
|
||||
{ text: "AI Gateway", href: "/ai-gateway/" },
|
||||
{ text: "AI Playground", href: "https://playground.ai.cloudflare.com/", target: "_blank" },
|
||||
],
|
||||
cta: {
|
||||
text: "View all AI products",
|
||||
href: "/products/?product-group=AI",
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Zero Trust",
|
||||
links: [
|
||||
{ text: "Access", href: "/cloudflare-one/policies/access/" },
|
||||
{ text: "Tunnel", href: "/cloudflare-one/connections/connect-networks/" },
|
||||
{ text: "Gateway", href: "/cloudflare-one/policies/gateway/" },
|
||||
{ text: "Browser Isolation", href: "/cloudflare-one/policies/browser-isolation/" },
|
||||
{ text: "Replace your VPN", href: "/learning-paths/replace-vpn/" },
|
||||
],
|
||||
cta: {
|
||||
text: "View all Cloudflare One products",
|
||||
href: "/products/?product-group=Cloudflare+One",
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Featured",
|
||||
links: [
|
||||
{ text: "Add web analytics", href: "/web-analytics/" },
|
||||
{
|
||||
text: "Troubleshoot errors",
|
||||
href: "/support/troubleshooting/cloudflare-errors/",
|
||||
},
|
||||
{ text: "Register a domain", href: "/registrar/" },
|
||||
{ text: "Setup 1.1.1.1", href: "/1.1.1.1/setup/" },
|
||||
{
|
||||
text: "Get started with Cloudflare",
|
||||
href: "/learning-paths/get-started/",
|
||||
},
|
||||
],
|
||||
cta: {
|
||||
text: "View all products",
|
||||
href: "/products/",
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Developer Products",
|
||||
links: [
|
||||
{ text: "Workers", href: "/workers/" },
|
||||
{ text: "Pages", href: "/pages/" },
|
||||
{ text: "R2", href: "/r2/" },
|
||||
{ text: "Images", href: "/images/" },
|
||||
{ text: "Stream", href: "/stream/" },
|
||||
],
|
||||
cta: {
|
||||
text: "View all developer products",
|
||||
href: "/products/?product-group=Developer+platform",
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "AI Products",
|
||||
links: [
|
||||
{
|
||||
text: "Build a RAG",
|
||||
href: "/workers-ai/tutorials/build-a-retrieval-augmented-generation-ai/",
|
||||
},
|
||||
{ text: "Workers AI", href: "/workers-ai/" },
|
||||
{ text: "Vectorize", href: "/vectorize/" },
|
||||
{ text: "AI Gateway", href: "/ai-gateway/" },
|
||||
{
|
||||
text: "AI Playground",
|
||||
href: "https://playground.ai.cloudflare.com/",
|
||||
target: "_blank",
|
||||
},
|
||||
],
|
||||
cta: {
|
||||
text: "View all AI products",
|
||||
href: "/products/?product-group=AI",
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Zero Trust",
|
||||
links: [
|
||||
{ text: "Access", href: "/cloudflare-one/policies/access/" },
|
||||
{ text: "Tunnel", href: "/cloudflare-one/connections/connect-networks/" },
|
||||
{ text: "Gateway", href: "/cloudflare-one/policies/gateway/" },
|
||||
{
|
||||
text: "Browser Isolation",
|
||||
href: "/cloudflare-one/policies/browser-isolation/",
|
||||
},
|
||||
{ text: "Replace your VPN", href: "/learning-paths/replace-vpn/" },
|
||||
],
|
||||
cta: {
|
||||
text: "View all Cloudflare One products",
|
||||
href: "/products/?product-group=Cloudflare+One",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const featuredSections = [
|
||||
{
|
||||
title: "Developer Platform",
|
||||
text: "The Cloudflare Developer Platform provides a serverless execution environment that allows you to create entirely new applications or augment existing ones without configuring or maintaining infrastructure.",
|
||||
image: {
|
||||
light: SectionImageDeveloperPlatformLight,
|
||||
dark: SectionImageDeveloperPlatformDark,
|
||||
},
|
||||
imagePosition: "before",
|
||||
cards: [
|
||||
{
|
||||
title: "Create API Tokens",
|
||||
text: "If you are going to be using the Cloudflare API, you first need an API token to authenticate your requests.",
|
||||
cta: {
|
||||
title: "Create Tokens",
|
||||
href: "/fundamentals/api/get-started/create-token/",
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "View Workers Examples",
|
||||
text: "Review fully functional sample scripts to get started with Workers.",
|
||||
cta: {
|
||||
title: "View Examples",
|
||||
href: "/workers/examples/",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Zero Trust",
|
||||
text: "Cloudflare Zero Trust replaces legacy security perimeters with our global network, making the Internet faster and safer for teams around the world.",
|
||||
image: {
|
||||
light: SectionImageZeroTrustLight,
|
||||
dark: SectionImageZeroTrustDark,
|
||||
},
|
||||
imagePosition: "after",
|
||||
cards: [
|
||||
{
|
||||
title: "Install the WARP Client",
|
||||
text: "The Cloudflare WARP client allows individuals and organizations to have a faster, more secure, and more private experience online.",
|
||||
cta: {
|
||||
title: "Get started",
|
||||
href: "/cloudflare-one/connections/connect-devices/warp/",
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Set up a tunnel",
|
||||
text: "Cloudflare Tunnel provides you with a secure way to connect your resources to Cloudflare without a publicly routable IP address.",
|
||||
cta: {
|
||||
title: "Set up a tunnel",
|
||||
href: "/cloudflare-one/connections/connect-networks/",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Developer Platform",
|
||||
text: "The Cloudflare Developer Platform provides a serverless execution environment that allows you to create entirely new applications or augment existing ones without configuring or maintaining infrastructure.",
|
||||
image: {
|
||||
light: SectionImageDeveloperPlatformLight,
|
||||
dark: SectionImageDeveloperPlatformDark,
|
||||
},
|
||||
imagePosition: "before",
|
||||
cards: [
|
||||
{
|
||||
title: "Create API Tokens",
|
||||
text: "If you are going to be using the Cloudflare API, you first need an API token to authenticate your requests.",
|
||||
cta: {
|
||||
title: "Create Tokens",
|
||||
href: "/fundamentals/api/get-started/create-token/",
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "View Workers Examples",
|
||||
text: "Review fully functional sample scripts to get started with Workers.",
|
||||
cta: {
|
||||
title: "View Examples",
|
||||
href: "/workers/examples/",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Zero Trust",
|
||||
text: "Cloudflare Zero Trust replaces legacy security perimeters with our global network, making the Internet faster and safer for teams around the world.",
|
||||
image: {
|
||||
light: SectionImageZeroTrustLight,
|
||||
dark: SectionImageZeroTrustDark,
|
||||
},
|
||||
imagePosition: "after",
|
||||
cards: [
|
||||
{
|
||||
title: "Install the WARP Client",
|
||||
text: "The Cloudflare WARP client allows individuals and organizations to have a faster, more secure, and more private experience online.",
|
||||
cta: {
|
||||
title: "Get started",
|
||||
href: "/cloudflare-one/connections/connect-devices/warp/",
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Set up a tunnel",
|
||||
text: "Cloudflare Tunnel provides you with a secure way to connect your resources to Cloudflare without a publicly routable IP address.",
|
||||
cta: {
|
||||
title: "Set up a tunnel",
|
||||
href: "/cloudflare-one/connections/connect-networks/",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const recommendedSection = {
|
||||
title: "Other docs you might also like",
|
||||
cards: [
|
||||
{
|
||||
title: "Install an Origin CA certificate",
|
||||
text: "Use Origin Certificate Authority (CA) certificates to encrypt traffic between Cloudflare and your origin web server and reduce origin bandwidth.",
|
||||
cta: {
|
||||
title: "Install Origin CA",
|
||||
href: "/ssl/origin-configuration/origin-ca/",
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Change your nameservers",
|
||||
text: "Make Cloudflare your primary DNS provider by updating your authoritative nameservers at your domain registrar.",
|
||||
cta: {
|
||||
title: "Update nameservers",
|
||||
href: "/dns/zone-setups/full-setup/setup/",
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "SSL/TLS Encryption mode",
|
||||
text: "Your domain's encryption mode controls how Cloudflare connects to your origin server and how SSL certificates at your origin will be validated.",
|
||||
cta: {
|
||||
title: "Set encryption mode",
|
||||
href: "/ssl/origin-configuration/ssl-modes/",
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Allow traffic from specific countries only",
|
||||
text: "Block requests based on a list of allowed countries by configuring a custom rule in the Web Application Firewall (WAF).",
|
||||
cta: {
|
||||
title: "Allow traffic from specific countries only",
|
||||
href: "/waf/custom-rules/use-cases/allow-traffic-from-specific-countries/",
|
||||
},
|
||||
},
|
||||
],
|
||||
title: "Other docs you might also like",
|
||||
cards: [
|
||||
{
|
||||
title: "Install an Origin CA certificate",
|
||||
text: "Use Origin Certificate Authority (CA) certificates to encrypt traffic between Cloudflare and your origin web server and reduce origin bandwidth.",
|
||||
cta: {
|
||||
title: "Install Origin CA",
|
||||
href: "/ssl/origin-configuration/origin-ca/",
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Change your nameservers",
|
||||
text: "Make Cloudflare your primary DNS provider by updating your authoritative nameservers at your domain registrar.",
|
||||
cta: {
|
||||
title: "Update nameservers",
|
||||
href: "/dns/zone-setups/full-setup/setup/",
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "SSL/TLS Encryption mode",
|
||||
text: "Your domain's encryption mode controls how Cloudflare connects to your origin server and how SSL certificates at your origin will be validated.",
|
||||
cta: {
|
||||
title: "Set encryption mode",
|
||||
href: "/ssl/origin-configuration/ssl-modes/",
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Allow traffic from specific countries only",
|
||||
text: "Block requests based on a list of allowed countries by configuring a custom rule in the Web Application Firewall (WAF).",
|
||||
cta: {
|
||||
title: "Allow traffic from specific countries only",
|
||||
href: "/waf/custom-rules/use-cases/allow-traffic-from-specific-countries/",
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
---
|
||||
|
||||
<StarlightPage frontmatter={frontmatter}>
|
||||
<FourCardGrid>
|
||||
{
|
||||
topCards.map((card) => (
|
||||
<ListCard title={card.title}>
|
||||
<ul>
|
||||
{card.links.map((link) => (
|
||||
<li>
|
||||
<a href={link.href} target={link.target} class="!text-black hover:!text-accent-600 dark:!text-accent-200 dark:hover:!text-white">{link.text}</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<p>
|
||||
<strong>
|
||||
<a href={card.cta.href}>{card.cta.text}</a>
|
||||
</strong>
|
||||
</p>
|
||||
</ListCard>
|
||||
))
|
||||
}
|
||||
</FourCardGrid>
|
||||
<div class="hidden md:block">
|
||||
<hr />
|
||||
<TryItSection />
|
||||
<hr />
|
||||
{
|
||||
featuredSections.map((section) => (
|
||||
<>
|
||||
<FeaturedContentSection
|
||||
title={section.title}
|
||||
text={section.text}
|
||||
image={section.image}
|
||||
imagePosition={section.imagePosition}
|
||||
cards={section.cards}
|
||||
/>
|
||||
<hr />
|
||||
</>
|
||||
))
|
||||
}
|
||||
<RecommendedContentSection
|
||||
title={recommendedSection.title}
|
||||
cards={recommendedSection.cards}
|
||||
/>
|
||||
<hr />
|
||||
<FooterHeroBlock />
|
||||
</div>
|
||||
<FourCardGrid>
|
||||
{
|
||||
topCards.map((card) => (
|
||||
<ListCard title={card.title}>
|
||||
<ul>
|
||||
{card.links.map((link) => (
|
||||
<li>
|
||||
<a
|
||||
href={link.href}
|
||||
target={link.target}
|
||||
class="!text-black hover:!text-accent-600 dark:!text-accent-200 dark:hover:!text-white"
|
||||
>
|
||||
{link.text}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<p>
|
||||
<strong>
|
||||
<a href={card.cta.href}>{card.cta.text}</a>
|
||||
</strong>
|
||||
</p>
|
||||
</ListCard>
|
||||
))
|
||||
}
|
||||
</FourCardGrid>
|
||||
<div class="hidden md:block">
|
||||
<hr />
|
||||
<TryItSection />
|
||||
<hr />
|
||||
{
|
||||
featuredSections.map((section) => (
|
||||
<>
|
||||
<FeaturedContentSection
|
||||
title={section.title}
|
||||
text={section.text}
|
||||
image={section.image}
|
||||
imagePosition={section.imagePosition}
|
||||
cards={section.cards}
|
||||
/>
|
||||
<hr />
|
||||
</>
|
||||
))
|
||||
}
|
||||
<RecommendedContentSection
|
||||
title={recommendedSection.title}
|
||||
cards={recommendedSection.cards}
|
||||
/>
|
||||
<hr />
|
||||
<FooterHeroBlock />
|
||||
</div>
|
||||
</StarlightPage>
|
||||
|
||||
<style>
|
||||
html:not([data-has-sidebar]) {
|
||||
--sl-content-width: 80rem;
|
||||
}
|
||||
</style>
|
||||
html:not([data-has-sidebar]) {
|
||||
--sl-content-width: 80rem;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ const frontmatter = {
|
|||
description:
|
||||
"Learning paths guide you through modules and projects so you can get started with Cloudflare as quickly as possible.",
|
||||
template: "splash",
|
||||
};
|
||||
} as const;
|
||||
|
||||
const learningPaths = await getCollection("learning-paths");
|
||||
---
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
import { getCollection } from "astro:content";
|
||||
|
||||
export async function GET() {
|
||||
const entries = await getCollection("pages-build-environment");
|
||||
const entries = await getCollection("pages-build-environment");
|
||||
|
||||
const data = entries.flatMap(x => {
|
||||
x.data.enable_date = new Date(x.data.enable_date).toISOString();
|
||||
const data = entries.flatMap((x) => {
|
||||
x.data.enable_date = new Date(x.data.enable_date).toISOString();
|
||||
|
||||
x.data.status ??= null;
|
||||
|
||||
return x.data
|
||||
});
|
||||
return {
|
||||
...x.data,
|
||||
status: x.data.status ?? null,
|
||||
};
|
||||
});
|
||||
|
||||
return Response.json(data);
|
||||
}
|
||||
return Response.json(data);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ const frontmatter = {
|
|||
description:
|
||||
"API reference, how-to guides, tutorials, example code, and more.",
|
||||
template: "splash",
|
||||
};
|
||||
} as const;
|
||||
|
||||
function productGroups(products: Array<CollectionEntry<"products">>) {
|
||||
const groups = products.flatMap((p) => p.data.product.group ?? []);
|
||||
|
|
|
|||
|
|
@ -34,15 +34,18 @@ import "instantsearch.css/themes/satellite.css";
|
|||
|
||||
// Functions needed for dropdowns, pulled from https://www.algolia.com/doc/guides/building-search-ui/ui-and-ux-patterns/facet-dropdown/js/
|
||||
|
||||
function hasClassName(elem: HTMLElement, className: string) {
|
||||
function hasClassName(elem: HTMLElement | null, className: string) {
|
||||
if (!elem) return;
|
||||
return elem.className.split(" ").indexOf(className) >= 0;
|
||||
}
|
||||
|
||||
function addClassName(elem: HTMLElement, className: string) {
|
||||
function addClassName(elem: HTMLElement | null, className: string) {
|
||||
if (!elem) return;
|
||||
elem.className = [...elem.className.split(" "), className].join(" ");
|
||||
}
|
||||
|
||||
function removeClassName(elem: HTMLElement, className: string) {
|
||||
function removeClassName(elem: HTMLElement | null, className: string) {
|
||||
if (!elem) return;
|
||||
elem.className = elem.className
|
||||
.split(" ")
|
||||
.filter((name) => name !== className)
|
||||
|
|
@ -61,12 +64,17 @@ import "instantsearch.css/themes/satellite.css";
|
|||
const cx = (...args: string[]) => args.filter(Boolean).join(" ");
|
||||
|
||||
export function createDropdown(
|
||||
baseWidget,
|
||||
baseWidget: any,
|
||||
{
|
||||
cssClasses: userCssClasses = {},
|
||||
buttonText,
|
||||
buttonClassName,
|
||||
closeOnChange,
|
||||
}: {
|
||||
cssClasses?: Record<string, string>;
|
||||
buttonText?: string;
|
||||
buttonClassName?: string | ((options: any) => string);
|
||||
closeOnChange?: boolean | (() => boolean);
|
||||
} = {},
|
||||
) {
|
||||
// Merge class names with the default ones and the ones from user
|
||||
|
|
@ -79,7 +87,7 @@ import "instantsearch.css/themes/satellite.css";
|
|||
),
|
||||
closeButton: cx(CLASS_CLOSE_BUTTON, userCssClasses.closeButton),
|
||||
};
|
||||
const makeWidget = panel({
|
||||
const makeWidget = panel<typeof refinementList>({
|
||||
cssClasses,
|
||||
templates: {
|
||||
header: (options) => {
|
||||
|
|
@ -121,10 +129,14 @@ import "instantsearch.css/themes/satellite.css";
|
|||
},
|
||||
})(baseWidget);
|
||||
|
||||
return (widgetParams) => {
|
||||
return (widgetParams: any) => {
|
||||
const widget = makeWidget(widgetParams);
|
||||
let state = {};
|
||||
let rootElem, headerElem, closeButtonElem;
|
||||
let state: {
|
||||
windowClickListener?: (event: MouseEvent) => void;
|
||||
} = {};
|
||||
let rootElem: HTMLElement | null;
|
||||
let headerElem: HTMLElement | null;
|
||||
let closeButtonElem: HTMLButtonElement | null;
|
||||
|
||||
const open = () => {
|
||||
addClassName(rootElem, CLASS_OPENED);
|
||||
|
|
@ -134,7 +146,7 @@ import "instantsearch.css/themes/satellite.css";
|
|||
setTimeout(() => {
|
||||
state.windowClickListener = (event) => {
|
||||
// Close if the outside is clicked
|
||||
if (!event.composedPath().includes(rootElem)) {
|
||||
if (rootElem && !event.composedPath().includes(rootElem)) {
|
||||
close();
|
||||
}
|
||||
};
|
||||
|
|
@ -145,7 +157,9 @@ import "instantsearch.css/themes/satellite.css";
|
|||
const close = () => {
|
||||
removeClassName(rootElem, CLASS_OPENED);
|
||||
// Remove the event listener when the panel is closed
|
||||
window.removeEventListener("click", state.windowClickListener);
|
||||
if (state.windowClickListener) {
|
||||
window.removeEventListener("click", state.windowClickListener);
|
||||
}
|
||||
delete state.windowClickListener;
|
||||
};
|
||||
const isOpened = () => hasClassName(rootElem, CLASS_OPENED);
|
||||
|
|
@ -158,10 +172,10 @@ import "instantsearch.css/themes/satellite.css";
|
|||
};
|
||||
|
||||
// Add a click listener to the header (button) and the caret symbol
|
||||
const buttonListener = (event) => {
|
||||
const buttonListener = (event: MouseEvent) => {
|
||||
if (
|
||||
!event.target.matches("." + CLASS_BUTTON) &&
|
||||
!event.target.matches(".caretDownFilter")
|
||||
!(event.target as HTMLElement)?.matches("." + CLASS_BUTTON) &&
|
||||
!(event.target as HTMLElement)?.matches(".caretDownFilter")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -170,7 +184,7 @@ import "instantsearch.css/themes/satellite.css";
|
|||
|
||||
// Setup a clean-up function, which will be called in `dispose`.
|
||||
const cleanUp = () => {
|
||||
headerElem.removeEventListener("click", buttonListener);
|
||||
headerElem?.removeEventListener("click", buttonListener);
|
||||
if (state.windowClickListener) {
|
||||
window.removeEventListener("click", state.windowClickListener);
|
||||
}
|
||||
|
|
@ -180,7 +194,7 @@ import "instantsearch.css/themes/satellite.css";
|
|||
return {
|
||||
...widget,
|
||||
$$widgetType: "cmty.facetDropdown",
|
||||
render: (options) => {
|
||||
render: (options: any) => {
|
||||
if (!rootElem) {
|
||||
rootElem = document
|
||||
.querySelector(widgetParams.container)
|
||||
|
|
@ -188,15 +202,16 @@ import "instantsearch.css/themes/satellite.css";
|
|||
}
|
||||
|
||||
if (!headerElem) {
|
||||
headerElem = rootElem.querySelector(".ais-Panel-header");
|
||||
headerElem = rootElem?.querySelector(".ais-Panel-header") ?? null;
|
||||
|
||||
headerElem.addEventListener("click", buttonListener);
|
||||
headerElem?.addEventListener("click", buttonListener);
|
||||
}
|
||||
|
||||
if (!closeButtonElem) {
|
||||
closeButtonElem = rootElem.querySelector("." + CLASS_CLOSE_BUTTON);
|
||||
closeButtonElem =
|
||||
rootElem?.querySelector("." + CLASS_CLOSE_BUTTON) ?? null;
|
||||
|
||||
closeButtonElem.addEventListener("click", close);
|
||||
closeButtonElem?.addEventListener("click", close);
|
||||
}
|
||||
|
||||
// Whenever uiState changes, it closes the panel.
|
||||
|
|
@ -217,7 +232,7 @@ import "instantsearch.css/themes/satellite.css";
|
|||
|
||||
return widget.render.call(widget, options);
|
||||
},
|
||||
dispose: (options) => {
|
||||
dispose: (options: any) => {
|
||||
if (typeof cleanUp === "function") {
|
||||
cleanUp();
|
||||
}
|
||||
|
|
@ -236,6 +251,7 @@ import "instantsearch.css/themes/satellite.css";
|
|||
|
||||
const search = instantsearch({
|
||||
indexName: indexName,
|
||||
// @ts-expect-error TODO: improve types
|
||||
searchClient,
|
||||
insights: true,
|
||||
routing: {
|
||||
|
|
@ -257,6 +273,7 @@ import "instantsearch.css/themes/satellite.css";
|
|||
indexUiState.refinementList.content_type,
|
||||
};
|
||||
},
|
||||
// @ts-expect-error TODO: improve types
|
||||
routeToState(routeState) {
|
||||
return {
|
||||
[indexName]: {
|
||||
|
|
@ -355,6 +372,7 @@ import "instantsearch.css/themes/satellite.css";
|
|||
},
|
||||
}),
|
||||
configure({
|
||||
// @ts-expect-error TODO: improve types
|
||||
hitsPerPage: 10,
|
||||
attributesToSnippet: ["content:30"],
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import TranslationResponses from "~/components/models/responses/TranslationRespo
|
|||
import TranslationCode from "~/components/models/code/TranslationCode.astro";
|
||||
import StableDiffusionV15Img2ImgCode from "~/components/models/code/StableDiffusion-v1-5-img2imgCode.astro";
|
||||
import StableDiffusionV15InpaintingCode from "~/components/models/code/StableDiffusion-v1-5-inpaintingCode.astro";
|
||||
import type { ComponentProps } from "astro/types";
|
||||
|
||||
export const getStaticPaths = (async () => {
|
||||
const models = await getCollection("workers-ai-models");
|
||||
|
|
@ -52,106 +53,118 @@ const hasLora = Boolean(
|
|||
data.model.properties.find((x) => x.property_id === "lora"),
|
||||
);
|
||||
|
||||
const badges = data.model.properties.flatMap(({ property_id, value }) => {
|
||||
if (property_id === "beta" && value === "true") {
|
||||
return {
|
||||
preset: "beta",
|
||||
};
|
||||
}
|
||||
type InlineBadgeProps = ComponentProps<typeof InlineBadge>;
|
||||
|
||||
if (property_id === "lora" && value === "true") {
|
||||
return {
|
||||
variant: "tip",
|
||||
text: "LoRA",
|
||||
};
|
||||
}
|
||||
|
||||
if (property_id === "function_calling" && value === "true") {
|
||||
return {
|
||||
variant: "note",
|
||||
text: "Function calling",
|
||||
};
|
||||
}
|
||||
|
||||
if (property_id === "planned_deprecation_date") {
|
||||
const timestamp = Math.floor(new Date(value).getTime() / 1000);
|
||||
|
||||
if (Date.now() > timestamp) {
|
||||
return { variant: "danger", text: "Deprecated" };
|
||||
const badges = data.model.properties
|
||||
.flatMap(({ property_id, value }): InlineBadgeProps | null => {
|
||||
if (property_id === "beta" && value === "true") {
|
||||
return {
|
||||
preset: "beta",
|
||||
text: "Beta",
|
||||
} as const;
|
||||
}
|
||||
|
||||
return { variant: "danger", text: "Planned deprecation" };
|
||||
}
|
||||
if (property_id === "lora" && value === "true") {
|
||||
return {
|
||||
variant: "tip",
|
||||
text: "LoRA",
|
||||
} as const;
|
||||
}
|
||||
|
||||
return [];
|
||||
});
|
||||
if (property_id === "function_calling" && value === "true") {
|
||||
return {
|
||||
variant: "note",
|
||||
text: "Function calling",
|
||||
} as const;
|
||||
}
|
||||
|
||||
let CodeExamples;
|
||||
let Responses;
|
||||
if (property_id === "planned_deprecation_date") {
|
||||
const timestamp = Math.floor(new Date(value).getTime() / 1000);
|
||||
|
||||
switch (data.task_type) {
|
||||
case "text-generation": {
|
||||
CodeExamples = TextGenerationCode;
|
||||
Responses = TextGenerationResponses;
|
||||
if (Date.now() > timestamp) {
|
||||
return { variant: "danger", text: "Deprecated" } as const;
|
||||
}
|
||||
|
||||
return { variant: "danger", text: "Planned deprecation" } as const;
|
||||
}
|
||||
|
||||
return null;
|
||||
})
|
||||
.filter(Boolean) as InlineBadgeProps[];
|
||||
|
||||
function getComponents(info: typeof data) {
|
||||
switch (info.task_type) {
|
||||
case "text-generation": {
|
||||
return {
|
||||
CodeExamples: TextGenerationCode,
|
||||
Responses: TextGenerationResponses,
|
||||
};
|
||||
}
|
||||
case "automatic-speech-recognition":
|
||||
return {
|
||||
CodeExamples: AutomaticSpeechRecognitionCode,
|
||||
Responses: AutomaticSpeechRecognitionResponses,
|
||||
};
|
||||
case "image-classification":
|
||||
return {
|
||||
CodeExamples: ImageClassificationCode,
|
||||
Responses: ImageClassificationResponses,
|
||||
};
|
||||
case "image-to-text":
|
||||
return {
|
||||
CodeExamples: ImageToTextCode,
|
||||
Responses: ImageToTextResponses,
|
||||
};
|
||||
case "object-detection":
|
||||
return {
|
||||
CodeExamples: ObjectDetectionCode,
|
||||
Responses: ObjectDetectionResponses,
|
||||
};
|
||||
case "summarization":
|
||||
return {
|
||||
CodeExamples: SummarizationCode,
|
||||
Responses: SummarizationResponses,
|
||||
};
|
||||
case "text-classification":
|
||||
return {
|
||||
CodeExamples: TextClassificationCode,
|
||||
Responses: TextClassificationResponses,
|
||||
};
|
||||
case "text-embeddings":
|
||||
return {
|
||||
CodeExamples: TextEmbeddingCode,
|
||||
Responses: TextEmbeddingsResponses,
|
||||
};
|
||||
case "text-to-image":
|
||||
return {
|
||||
CodeExamples: TextToImageCode,
|
||||
Responses: TextToImageResponses,
|
||||
};
|
||||
case "translation":
|
||||
return {
|
||||
CodeExamples: TranslationCode,
|
||||
Responses: TranslationResponses,
|
||||
};
|
||||
}
|
||||
break;
|
||||
case "automatic-speech-recognition": {
|
||||
CodeExamples = AutomaticSpeechRecognitionCode;
|
||||
Responses = AutomaticSpeechRecognitionResponses;
|
||||
}
|
||||
break;
|
||||
case "image-classification": {
|
||||
CodeExamples = ImageClassificationCode;
|
||||
Responses = ImageClassificationResponses;
|
||||
}
|
||||
break;
|
||||
case "image-to-text": {
|
||||
CodeExamples = ImageToTextCode;
|
||||
Responses = ImageToTextResponses;
|
||||
}
|
||||
break;
|
||||
case "object-detection": {
|
||||
CodeExamples = ObjectDetectionCode;
|
||||
Responses = ObjectDetectionResponses;
|
||||
}
|
||||
break;
|
||||
case "summarization": {
|
||||
CodeExamples = SummarizationCode;
|
||||
Responses = SummarizationResponses;
|
||||
}
|
||||
break;
|
||||
case "text-classification": {
|
||||
CodeExamples = TextClassificationCode;
|
||||
Responses = TextClassificationResponses;
|
||||
}
|
||||
break;
|
||||
case "text-embeddings": {
|
||||
CodeExamples = TextEmbeddingCode;
|
||||
Responses = TextEmbeddingsResponses;
|
||||
}
|
||||
break;
|
||||
case "text-to-image": {
|
||||
CodeExamples = TextToImageCode;
|
||||
Responses = TextToImageResponses;
|
||||
}
|
||||
break;
|
||||
case "translation": {
|
||||
CodeExamples = TranslationCode;
|
||||
Responses = TranslationResponses;
|
||||
}
|
||||
break;
|
||||
// default
|
||||
return {
|
||||
CodeExamples: null,
|
||||
Responses: null,
|
||||
};
|
||||
}
|
||||
const components = getComponents(data);
|
||||
const CodeExamples = components.CodeExamples;
|
||||
const Responses = components.Responses;
|
||||
|
||||
if (data.model.name === "@cf/runwayml/stable-diffusion-v1-5-inpainting") {
|
||||
CodeExamples = StableDiffusionV15InpaintingCode;
|
||||
components.CodeExamples = StableDiffusionV15InpaintingCode;
|
||||
} else if (data.model.name === "@cf/runwayml/stable-diffusion-v1-5-img2img") {
|
||||
CodeExamples = StableDiffusionV15Img2ImgCode;
|
||||
components.CodeExamples = StableDiffusionV15Img2ImgCode;
|
||||
}
|
||||
|
||||
---
|
||||
|
||||
<StarlightPage frontmatter={{ title: name, description }}>
|
||||
{badges.map((badge) => <InlineBadge {...badge} /> )}
|
||||
{badges.map((badge) => <InlineBadge {...badge} />)}
|
||||
<p>
|
||||
<strong>Model ID: </strong>
|
||||
<code>{data.model.name}</code>
|
||||
|
|
@ -170,12 +183,12 @@ if (data.model.name === "@cf/runwayml/stable-diffusion-v1-5-inpainting") {
|
|||
<a href={`/workers-ai/models/#${data.task_type}`}>{data.model.task.name}</a>
|
||||
</p>
|
||||
{showPlayground && <Playground name={data.model.name} />}
|
||||
<CodeExamples name={data.model.name} lora={hasLora} />
|
||||
{CodeExamples && <CodeExamples name={data.model.name} lora={hasLora} />}
|
||||
{
|
||||
data.task_type === "text-generation" && (
|
||||
<TextGenerationPrompting lora={hasLora} />
|
||||
)
|
||||
}
|
||||
<Responses name={data.model.name} />
|
||||
{Responses && <Responses name={data.model.name} />}
|
||||
<Schemas schemas={data.json_schema} />
|
||||
</StarlightPage>
|
||||
</StarlightPage>
|
||||
|
|
|
|||
|
|
@ -2,16 +2,26 @@
|
|||
import StarlightPage from "@astrojs/starlight/components/StarlightPage.astro";
|
||||
import { Image } from "astro:assets";
|
||||
|
||||
import CursorDark from "~/assets/images/workers/ai/cursor-dark.png"
|
||||
import CursorLight from "~/assets/images/workers/ai/cursor-light.png"
|
||||
import CursorDark from "~/assets/images/workers/ai/cursor-dark.png";
|
||||
import CursorLight from "~/assets/images/workers/ai/cursor-light.png";
|
||||
---
|
||||
|
||||
<StarlightPage frontmatter={{ title: "AI Assistant", tableOfContents: false }}>
|
||||
<section id="assistant-header">
|
||||
<div class="not-content">
|
||||
<Image src={CursorDark} alt="Cursor illustration" width="300" class="light:sl-hidden"/>
|
||||
<Image src={CursorLight} alt="Cursor illustration" width="300" class="dark:sl-hidden"/>
|
||||
</div>
|
||||
<div class="not-content">
|
||||
<Image
|
||||
src={CursorDark}
|
||||
alt="Cursor illustration"
|
||||
width="300"
|
||||
class="light:sl-hidden"
|
||||
/>
|
||||
<Image
|
||||
src={CursorLight}
|
||||
alt="Cursor illustration"
|
||||
width="300"
|
||||
class="dark:sl-hidden"
|
||||
/>
|
||||
</div>
|
||||
<div id="text">
|
||||
<h1>
|
||||
<div class="illustration"></div>
|
||||
|
|
@ -37,7 +47,12 @@ import CursorLight from "~/assets/images/workers/ai/cursor-light.png"
|
|||
<ul id="messages"></ul>
|
||||
|
||||
<form id="ai-form">
|
||||
<input id="prompt" placeholder="How do Cloudflare Workers work?" minlength="8" maxlength="256" />
|
||||
<input
|
||||
id="prompt"
|
||||
placeholder="How do Cloudflare Workers work?"
|
||||
minlength="8"
|
||||
maxlength="256"
|
||||
/>
|
||||
<button type="submit">→</button>
|
||||
</form>
|
||||
|
||||
|
|
@ -268,112 +283,107 @@ import CursorLight from "~/assets/images/workers/ai/cursor-light.png"
|
|||
</style>
|
||||
|
||||
<script is:inline>
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
const formEl = document.getElementById("ai-form");
|
||||
const promptEl = document.getElementById("prompt");
|
||||
const messagesEl = document.getElementById("messages");
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
const formEl = document.getElementById("ai-form");
|
||||
const promptEl = document.getElementById("prompt");
|
||||
const messagesEl = document.getElementById("messages");
|
||||
|
||||
const messages = {};
|
||||
const sourceToUrl = (mdFilename) =>
|
||||
`https://developers.cloudflare.com/${mdFilename
|
||||
.replace(/_index\.md$/, "")
|
||||
.replace(/\.md$/, "")}`;
|
||||
|
||||
const sourceToUrl = (mdFilename) =>
|
||||
`https://developers.cloudflare.com/${mdFilename
|
||||
.replace(/_index\.md$/, "")
|
||||
.replace(/\.md$/, "")}`;
|
||||
formEl.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
formEl.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const prompt = promptEl.value;
|
||||
|
||||
const prompt = promptEl.value;
|
||||
if (prompt.length < 8 || prompt.length > 256) return;
|
||||
|
||||
if (prompt.length < 8 || prompt.length > 256) return;
|
||||
promptEl.value = "";
|
||||
|
||||
promptEl.value = "";
|
||||
const messageEl = document.createElement("li");
|
||||
messageEl.setAttribute("class", "prompt");
|
||||
messageEl.innerText = prompt;
|
||||
|
||||
const messageEl = document.createElement("li");
|
||||
messageEl.setAttribute("class", "prompt");
|
||||
messageEl.innerText = prompt;
|
||||
messagesEl.appendChild(messageEl);
|
||||
|
||||
messagesEl.appendChild(messageEl);
|
||||
const url = new URL("https://bot-api.developers.cloudflare.com/stream");
|
||||
url.searchParams.set("question", prompt);
|
||||
const source = new EventSource(url);
|
||||
|
||||
const url = new URL("https://bot-api.developers.cloudflare.com/stream");
|
||||
url.searchParams.set("question", prompt);
|
||||
const source = new EventSource(url);
|
||||
let completion = "";
|
||||
let sources = [];
|
||||
|
||||
let completion = "";
|
||||
let sources = [];
|
||||
const responseEl = document.createElement("li");
|
||||
responseEl.setAttribute("class", "response");
|
||||
|
||||
const responseEl = document.createElement("li");
|
||||
responseEl.setAttribute("class", "response");
|
||||
const responseTextEl = document.createElement("p");
|
||||
responseEl.appendChild(responseTextEl);
|
||||
const sourcesEl = document.createElement("ul");
|
||||
sourcesEl.style.display = "none";
|
||||
responseEl.appendChild(sourcesEl);
|
||||
|
||||
const responseTextEl = document.createElement("p");
|
||||
responseEl.appendChild(responseTextEl);
|
||||
const sourcesEl = document.createElement("ul");
|
||||
sourcesEl.style.display = "none";
|
||||
responseEl.appendChild(sourcesEl);
|
||||
const loadingEl = document.createElement("div");
|
||||
loadingEl.setAttribute("class", "loader");
|
||||
loadingEl.innerHTML = '<div class="dot-flashing"></div>';
|
||||
responseEl.appendChild(loadingEl);
|
||||
|
||||
const loadingEl = document.createElement("div");
|
||||
loadingEl.setAttribute("class", "loader");
|
||||
loadingEl.innerHTML = '<div class="dot-flashing"></div>';
|
||||
responseEl.appendChild(loadingEl);
|
||||
messagesEl.appendChild(responseEl);
|
||||
|
||||
messagesEl.appendChild(responseEl);
|
||||
promptEl.setAttribute("disabled", true);
|
||||
|
||||
promptEl.setAttribute("disabled", true);
|
||||
source.onerror = () => {
|
||||
// Errors within an EventSource are indiscernible — this could occur due to rate limits or issues with a prompt.
|
||||
// Let's fail gracefully and revert to default state.
|
||||
|
||||
source.onerror = (e) => {
|
||||
// Errors within an EventSource are indiscernible — this could occur due to rate limits or issues with a prompt.
|
||||
// Let's fail gracefully and revert to default state.
|
||||
loadingEl.remove();
|
||||
promptEl.removeAttribute("disabled");
|
||||
|
||||
loadingEl.remove();
|
||||
promptEl.removeAttribute("disabled");
|
||||
responseTextEl.innerText =
|
||||
"I'm currently unable to answer this question. Please try rephrasing, or ask again at a later date.";
|
||||
|
||||
responseTextEl.innerText =
|
||||
"I'm currently unable to answer this question. Please try rephrasing, or ask again at a later date.";
|
||||
source.close();
|
||||
};
|
||||
|
||||
source.close();
|
||||
};
|
||||
source.onmessage = ({ data }) => {
|
||||
if (data === "[DONE]") {
|
||||
source.close();
|
||||
|
||||
source.onmessage = ({ data }) => {
|
||||
if (data === "[DONE]") {
|
||||
source.close();
|
||||
promptEl.removeAttribute("disabled");
|
||||
|
||||
promptEl.removeAttribute("disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
loadingEl.remove();
|
||||
|
||||
loadingEl.remove();
|
||||
const {
|
||||
choices: [{ text }],
|
||||
} = JSON.parse(data);
|
||||
completion += text;
|
||||
|
||||
const {
|
||||
choices: [{ text }],
|
||||
} = JSON.parse(data);
|
||||
completion += text;
|
||||
//const isCompleted = completion.indexOf("SOURCES:") > -1;
|
||||
|
||||
const isCompleted = completion.indexOf("SOURCES:") > -1;
|
||||
const [answer, sourcesString] = completion.split("SOURCES:");
|
||||
|
||||
const [answer, sourcesString] = completion.split("SOURCES:");
|
||||
responseTextEl.innerText = answer.trim();
|
||||
|
||||
responseTextEl.innerText = answer.trim();
|
||||
if (sourcesString) {
|
||||
sourcesEl.style.display = "block";
|
||||
sources = sourcesString
|
||||
.trim()
|
||||
.split(", ")
|
||||
.map((s) => sourceToUrl(s))
|
||||
.filter((url) => url !== "https://developers.cloudflare.com/N/A");
|
||||
}
|
||||
|
||||
if (sourcesString) {
|
||||
sourcesEl.style.display = "block";
|
||||
sources = sourcesString
|
||||
.trim()
|
||||
.split(", ")
|
||||
.map((s) => sourceToUrl(s))
|
||||
.filter((url) => url !== "https://developers.cloudflare.com/N/A");
|
||||
}
|
||||
|
||||
sourcesEl.innerHTML =
|
||||
"<li>These sources might provide additional context:</li>" +
|
||||
sources
|
||||
.map(
|
||||
(url) =>
|
||||
`<li class="source"><a href="${url}">${url}</a></li>`,
|
||||
)
|
||||
.join("");
|
||||
};
|
||||
});
|
||||
});
|
||||
sourcesEl.innerHTML =
|
||||
"<li>These sources might provide additional context:</li>" +
|
||||
sources
|
||||
.map((url) => `<li class="source"><a href="${url}">${url}</a></li>`)
|
||||
.join("");
|
||||
};
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,31 +1,31 @@
|
|||
import { getCollection } from "astro:content";
|
||||
|
||||
export async function GET() {
|
||||
const entries = await getCollection("compatibility-dates");
|
||||
|
||||
entries.sort((a, b) => a.data.sort_date - b.data.sort_date)
|
||||
const entries = await getCollection("compatibility-dates");
|
||||
|
||||
const flags = entries.flatMap((x) => {
|
||||
delete x.data.sort_date;
|
||||
|
||||
if (!x.data.enable_flag) {
|
||||
x.data.enable_flag = null
|
||||
};
|
||||
entries.sort((a, b) => a.data.sort_date.localeCompare(b.data.sort_date));
|
||||
|
||||
if (!x.data.enable_date) {
|
||||
x.data.enable_date = null
|
||||
}
|
||||
const flags = entries.flatMap((x) => {
|
||||
if (!x.data.enable_flag) {
|
||||
x.data.enable_flag = null;
|
||||
}
|
||||
|
||||
if (!x.data.disable_flag) {
|
||||
x.data.disable_flag = null
|
||||
}
|
||||
if (!x.data.enable_date) {
|
||||
x.data.enable_date = null;
|
||||
}
|
||||
|
||||
return {
|
||||
...x.data,
|
||||
description: x.body.trim(),
|
||||
experimental: x.data.experimental ?? false
|
||||
}
|
||||
});
|
||||
if (!x.data.disable_flag) {
|
||||
x.data.disable_flag = null;
|
||||
}
|
||||
|
||||
return Response.json(flags);
|
||||
}
|
||||
// omit sort_date from output
|
||||
const { sort_date, ...data } = x.data;
|
||||
return {
|
||||
...data,
|
||||
description: x.body.trim(),
|
||||
experimental: x.data.experimental ?? false,
|
||||
};
|
||||
});
|
||||
|
||||
return Response.json(flags);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ export const baseSchema = z.object({
|
|||
operation: z.string().array().optional(),
|
||||
sidebar: z
|
||||
.object({
|
||||
order: z.number().optional(),
|
||||
group: z
|
||||
.object({
|
||||
label: z
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ export type CompatibilityDatesSchema = z.infer<typeof compatibilityDatesSchema>;
|
|||
|
||||
export const compatibilityDatesSchema = z.object({
|
||||
name: z.string(),
|
||||
enable_date: z.string().optional(),
|
||||
enable_flag: z.string(),
|
||||
disable_flag: z.string().optional(),
|
||||
enable_date: z.string().optional().nullable(),
|
||||
enable_flag: z.string().nullable(),
|
||||
disable_flag: z.string().optional().nullable(),
|
||||
sort_date: z.string(),
|
||||
experimental: z.boolean().optional(),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -20,5 +20,6 @@ export const pagesBuildEnvironmentSchema = z
|
|||
operating_system: z.string(),
|
||||
architecture: z.string(),
|
||||
}),
|
||||
status: z.string().optional().nullable(),
|
||||
})
|
||||
.strict();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
const links = document.querySelectorAll<HTMLAnchorElement>("a");
|
||||
|
||||
function $zarazLinkEvent(type: string, link: HTMLAnchorElement) {
|
||||
// @ts-expect-error TODO: type zaraz
|
||||
zaraz.track(type, { href: link.href, hostname: link.hostname });
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,21 +2,30 @@ import { z } from "astro:schema";
|
|||
import { getCollection } from "astro:content";
|
||||
import { type CollectionEntry } from "astro:content";
|
||||
|
||||
export async function getChangelogs(opts?: { filter?: Function, wranglerOnly?: boolean }) {
|
||||
export async function getChangelogs(opts?: {
|
||||
filter?: Parameters<typeof getCollection<"changelogs">>[1];
|
||||
wranglerOnly?: boolean;
|
||||
}) {
|
||||
let changelogs;
|
||||
|
||||
if (opts?.wranglerOnly) {
|
||||
changelogs = [await getWranglerChangelog()];
|
||||
} else if (opts?.filter) {
|
||||
changelogs = await getCollection("changelogs", opts.filter);
|
||||
} else {
|
||||
changelogs = await getCollection("changelogs", opts?.filter);
|
||||
changelogs = await getCollection("changelogs");
|
||||
}
|
||||
|
||||
if (!changelogs) {
|
||||
throw new Error(`[getChangelogs] Unable to find any changelogs with ${JSON.stringify(opts)}`);
|
||||
throw new Error(
|
||||
`[getChangelogs] Unable to find any changelogs with ${JSON.stringify(opts)}`,
|
||||
);
|
||||
}
|
||||
|
||||
const products = [...new Set(changelogs.flatMap((x) => x.data.productName))];
|
||||
const productAreas = [...new Set(changelogs.flatMap((x) => x.data.productArea))];
|
||||
const productAreas = [
|
||||
...new Set(changelogs.flatMap((x) => x.data.productArea)),
|
||||
];
|
||||
|
||||
const mapped = changelogs.flatMap((product) => {
|
||||
return product.data.entries.map((entry) => {
|
||||
|
|
@ -64,7 +73,7 @@ export async function getWranglerChangelog(): Promise<
|
|||
.array()
|
||||
.parse(json);
|
||||
|
||||
releases = releases.filter(x => x.name.startsWith("wrangler@"))
|
||||
releases = releases.filter((x) => x.name.startsWith("wrangler@"));
|
||||
|
||||
return {
|
||||
// @ts-expect-error id is a union of on-disk YAML files but we're adding this one dynamically
|
||||
|
|
|
|||
|
|
@ -6,5 +6,5 @@
|
|||
"~/*": ["src/*"]
|
||||
}
|
||||
},
|
||||
"exclude": ["dist"]
|
||||
"exclude": ["dist", "functions"]
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue