mirror of
https://github.com/github/awesome-copilot.git
synced 2026-07-16 19:03:26 +00:00
1a77b83008
- replace cookbook recipe/example modal views with dedicated detail pages - add legacy #file cookbook hash redirects to canonical routes - remove modal component/runtime and associated CSS and stale page references Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: ca1a64ec-678f-45ac-ba68-33a37717398a
171 lines
5.2 KiB
TypeScript
171 lines
5.2 KiB
TypeScript
/**
|
|
* Custom Pagefind integration that extends Starlight's search index
|
|
* with resource records (agents, skills, instructions, hooks, workflows, plugins).
|
|
*
|
|
* Starlight's pagefind is enabled (pagefind: true) so the search UI renders,
|
|
* but this integration runs AFTER Starlight's and overwrites the index with
|
|
* HTML pages + custom resource records combined.
|
|
*/
|
|
import type { AstroIntegration } from "astro";
|
|
import { readFileSync } from "node:fs";
|
|
import { fileURLToPath } from "node:url";
|
|
import * as pagefind from "pagefind";
|
|
|
|
interface SearchRecord {
|
|
type: string;
|
|
id: string;
|
|
title: string;
|
|
description: string;
|
|
path: string;
|
|
tags?: string[];
|
|
searchText: string;
|
|
}
|
|
|
|
const TYPE_LABELS: Record<string, string> = {
|
|
agent: "Agent",
|
|
instruction: "Instruction",
|
|
skill: "Skill",
|
|
hook: "Hook",
|
|
workflow: "Workflow",
|
|
plugin: "Plugin",
|
|
tool: "Tool",
|
|
};
|
|
|
|
const TYPE_PAGES: Record<string, string> = {
|
|
agent: "/agents/",
|
|
instruction: "/instructions/",
|
|
skill: "/skills/",
|
|
hook: "/hooks/",
|
|
workflow: "/workflows/",
|
|
plugin: "/plugins/",
|
|
tool: "/tools/",
|
|
};
|
|
|
|
// Resource types that have a dedicated detail page at /<type>/<id>/. Search
|
|
// results for these should deep-link to the canonical detail page.
|
|
const DETAIL_ROUTE_TYPES = new Set([
|
|
"agent",
|
|
"instruction",
|
|
"skill",
|
|
"hook",
|
|
"workflow",
|
|
"plugin",
|
|
]);
|
|
|
|
export default function pagefindResources(): AstroIntegration {
|
|
let siteBase = "/";
|
|
|
|
return {
|
|
name: "pagefind-resources",
|
|
hooks: {
|
|
"astro:config:done": ({ config }) => {
|
|
siteBase = config.base;
|
|
},
|
|
"astro:build:done": async ({ dir, logger }) => {
|
|
const log = logger.fork("pagefind-resources");
|
|
const now = performance.now();
|
|
|
|
try {
|
|
log.info("Building search index with Pagefind + resource records...");
|
|
|
|
const response = await pagefind.createIndex();
|
|
if (response.errors.length > 0) {
|
|
for (const err of response.errors) log.error(err);
|
|
throw new Error("Failed to create Pagefind index");
|
|
}
|
|
const { index } = response;
|
|
|
|
if (!index) {
|
|
throw new Error("Pagefind index is undefined");
|
|
}
|
|
|
|
// Index all built HTML pages (same as Starlight's default)
|
|
const indexResult = await index.addDirectory({
|
|
path: fileURLToPath(dir),
|
|
});
|
|
if (indexResult.errors.length > 0) {
|
|
for (const err of indexResult.errors) log.error(err);
|
|
throw new Error("Failed to index HTML directory");
|
|
}
|
|
log.info(`Indexed ${indexResult.page_count} HTML pages.`);
|
|
|
|
// Read and index resource records from search-index.json
|
|
const searchIndexPath = fileURLToPath(
|
|
new URL("./data/search-index.json", dir)
|
|
);
|
|
let records: SearchRecord[];
|
|
try {
|
|
records = JSON.parse(readFileSync(searchIndexPath, "utf-8"));
|
|
} catch {
|
|
log.warn(
|
|
"Could not read search-index.json, skipping resource indexing."
|
|
);
|
|
records = [];
|
|
}
|
|
|
|
// Use the base path from Astro config (e.g. "/")
|
|
const base = siteBase.endsWith("/") ? siteBase : `${siteBase}/`;
|
|
|
|
let added = 0;
|
|
for (const record of records) {
|
|
const hasDetailPage =
|
|
DETAIL_ROUTE_TYPES.has(record.type) && Boolean(record.id);
|
|
const typePage = TYPE_PAGES[record.type];
|
|
// Skip records we can neither deep-link nor point at a listing page.
|
|
if (!hasDetailPage && !typePage) continue;
|
|
|
|
const url = hasDetailPage
|
|
? `${base}${record.type}/${encodeURIComponent(record.id)}/`
|
|
: `${base}${typePage.slice(1)}`;
|
|
const typeLabel = TYPE_LABELS[record.type] || record.type;
|
|
|
|
const addResult = await index.addCustomRecord({
|
|
url,
|
|
content:
|
|
record.searchText || `${record.title} ${record.description}`,
|
|
language: "en",
|
|
meta: {
|
|
title: `${record.title} — ${typeLabel}`,
|
|
},
|
|
filters: {
|
|
type: [record.type],
|
|
},
|
|
});
|
|
|
|
if (addResult.errors.length > 0) {
|
|
for (const err of addResult.errors)
|
|
log.warn(`Record ${record.id}: ${err}`);
|
|
} else {
|
|
added++;
|
|
}
|
|
}
|
|
|
|
log.info(`Added ${added} resource records.`);
|
|
|
|
// Write the combined index
|
|
const writeResult = await index.writeFiles({
|
|
outputPath: fileURLToPath(new URL("./pagefind/", dir)),
|
|
});
|
|
if (writeResult.errors.length > 0) {
|
|
for (const err of writeResult.errors) log.error(err);
|
|
throw new Error("Failed to write Pagefind files");
|
|
}
|
|
|
|
const elapsed = performance.now() - now;
|
|
log.info(
|
|
`Search index built in ${
|
|
elapsed < 750
|
|
? `${Math.round(elapsed)}ms`
|
|
: `${(elapsed / 1000).toFixed(2)}s`
|
|
}.`
|
|
);
|
|
} catch (cause) {
|
|
throw new Error("Failed to build Pagefind search index.", { cause });
|
|
} finally {
|
|
await pagefind.close();
|
|
}
|
|
},
|
|
},
|
|
};
|
|
}
|