Something went wrong. Try again.
Plugins for CMSs providing rich, customisable embeds for various developer sites and services. vale.rocks/portfolio/developer-embeds
Something went wrong. Try again.
6.7 kB · 257 lines
JavaScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258import { existsSync } from "node:fs";import { readFile } from "node:fs/promises";import { dirname, join } from "node:path";
function fail(message) { throw new Error(message);}
function blockBody(source, marker) { const start = source.indexOf(marker);
if (start === -1) { fail(`Missing ${marker}`); }
const open = source.indexOf("{", start); let depth = 0;
for (let index = open; index < source.length; index++) { if (source[index] === "{") { depth++; } else if (source[index] === "}") { depth--;
if (depth === 0) { return source.slice(open + 1, index); } } }
fail(`Unclosed ${marker}`);}
function objectKeys(body) { return [...body.matchAll(/^\t\t(?:"([^"]+)"|'([^']+)'|([A-Za-z0-9_-]+)):\s/gm)].map((match) => match[1] || match[2] || match[3]);}
function stringArray(source, name) { const match = source.match(new RegExp(`(?:const|static) ${name} = \\[([^\\]]+)\\]`));
if (!match) { fail(`Missing ${name}`); }
return JSON.parse(`[${match[1]}]`);}
function detailAliases(source) { const body = blockBody(source, "const DETAIL_ALIASES = {"); const aliases = {};
for (const line of body.split("\n")) { const match = line.match(/^\t\t(?:"([^"]+)"|'([^']+)'|([A-Za-z0-9_-]+)):\s*"([^"]+)"/);
if (match) { aliases[match[1] || match[2] || match[3]] = match[4]; } }
return aliases;}
function readmeMetadata(readme, tag) { const attributes = {}; const tableStart = readme.indexOf("| Attribute |");
if (tableStart !== -1) { let started = false;
for (const line of readme.slice(tableStart).split("\n")) { if (!line.startsWith("|")) { if (started) { break; }
continue; }
const cells = line.split("|").map((cell) => cell.trim()); const name = cells[1]?.replace(/`/g, "");
if (!name || name === "Attribute" || /^-+$/.test(cells[1])) { continue; }
started = true; attributes[name] = { required: cells[2] === "Yes", description: cells[3] || "", values: cells[4] || "", }; } }
const example = {}; const exampleMatch = readme.match(new RegExp(`<${tag}\\s+([^>]+)>`));
if (exampleMatch) { for (const match of exampleMatch[1].matchAll(/([a-z-]+)="([^"]*)"/g)) { example[match[1]] = match[2]; } }
return { attributes, example };}
function tokens(values) { return [...values.matchAll(/`([^`]+)`/g)].map((match) => match[1]);}
function defaultToken(values) { return values.match(/`([^`]+)`\s*\(default\)/i)?.[1] || null;}
function humanize(name) { return name .split("-") .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) .join(" ");}
function fieldLabel(ui, name) { return ui.labels?.[name] || humanize(name);}
function selectOptions(ui, values) { const defaultValue = defaultToken(values); const options = [];
if (!defaultValue) { options.push({ label: "Auto-detect", value: "" }); }
for (const token of tokens(values)) { options.push({ label: ui.selectLabels?.[token] || humanize(token), value: token === defaultValue ? "" : token, }); }
return options.filter((option, index) => options.findIndex((candidate) => candidate.value === option.value) === index);}
function buildField(ui, attribute, meta, example, required, details, aliases) { const values = meta?.values || ""; const valueTokens = tokens(values); const isDetails = attribute === "details"; const isSelect = !isDetails && valueTokens.length >= 2; const field = { name: attribute, label: fieldLabel(ui, attribute), type: isDetails ? "details" : isSelect ? "select" : "text", help: meta?.description || "", required: attribute === required, };
if (isDetails) { field.customDefault = details.defaults.join(","); field.available = details.available; field.aliases = aliases; } else if (isSelect) { field.options = selectOptions(ui, values); } else { const placeholder = example[attribute] || valueTokens[0] || ui.placeholders?.[attribute] || "";
if (placeholder) { field.placeholder = placeholder; } }
if (!field.help) { delete field.help; }
return field;}
export async function loadComponentMetadata({ require, ui, components }) { const output = [];
for (const name of components) { const packagePath = require.resolve(`${name}/package.json`); const packageDir = dirname(packagePath); const pkg = JSON.parse(await readFile(packagePath, "utf8"));
if (pkg.version === "0.0.0") { fail(`${name}@0.0.0 is a placeholder on npm. Use the local package until it is published.`); }
const file = pkg.main || `${name}.js`; const entry = join(packageDir, file);
if (!existsSync(entry)) { fail(`Missing entrypoint for ${name}: ${entry}`); }
const source = await readFile(entry, "utf8"); const readmePath = join(packageDir, "README.md"); const readme = existsSync(readmePath) ? await readFile(readmePath, "utf8") : ""; const tag = source.match(/customElements\.define\(\s*["']([^"']+)["']/)?.[1];
if (!tag) { fail(`Could not find the custom element tag in ${name}`); }
const observed = stringArray(source, "observedAttributes"); const details = { available: objectKeys(blockBody(source, "const detailRenderers = {")), defaults: stringArray(source, "DEFAULT_DETAILS"), }; const aliases = detailAliases(source); const metadata = readmeMetadata(readme, tag);
if (!Object.keys(metadata.attributes).length) { console.warn(`Warning: no attribute metadata found in ${name}/README.md`); }
const required = Object.keys(metadata.attributes).find((attribute) => metadata.attributes[attribute].required) || observed[0]; const presentation = ui.components?.[name] || { title: `${humanize(name.replace(/-embed$/, ""))} Card`, description: pkg.description || "", icon: "editor-code", emdashIcon: "code", keywords: [], };
if (!details.available.length || !details.defaults.length) { fail(`Could not read details metadata from ${name}`); }
const attributes = Object.fromEntries(observed.map((attribute) => [attribute, { type: "string", default: "" }])); const fields = observed.map((attribute) => buildField(ui, attribute, metadata.attributes[attribute], metadata.example, required, details, aliases)); const license = ["LICENSE", "LICENSE.md", "LICENSE.txt"].map((file) => join(packageDir, file)).find((file) => existsSync(file));
output.push({ package: name, tag, file, entry, packageDir, license, version: pkg.version, title: presentation.title, description: presentation.description, icon: presentation.icon || "editor-code", emdashIcon: presentation.emdashIcon || "code", keywords: presentation.keywords || [], required, attributes, fields, details, aliases, }); }
return output;}