/*
URL transformations and semantic ID generation for structured data. Framework-agnostic — used across all Warpgogol apps.
- Do not perform raw URL parsing beyond basic structure validation.
- Do not manage application configuration or external service interactions.
- RFC-0910: add canonicalRootUrl — unprefixed root URL for entity identity (Organization.url, WebSite.url).
- RFC-1097: step 6 — compass.migrate codemod run
Mechanical v1 to v2 header migration across the workspace: 942 files rewritten — CHANGE_SUMMARY windows collapsed into history, forbidden v1 blocks stripped, KEY_DECISIONS seeded from @ai-invariant comments (5 files) or TODO placeholders (103 files), blocks reordered to canonical order.
- RFC-1097: sweep — werkstatt-engine clean
Sweep batch 4: 73 Compass headers on headerless engine files (certification, component-runtime, isolation, evolution, testing), real KEY_DECISIONS on 75 files (kernel, cache, dht, swim, gitmesh, runtime), ~80 purpose expansions (CONTRACT-02/PURPOSE-02), non-goals on 13 CONTRACT-03 files, CS-07 history literal fix repo-wide (253 files). Policy: .template.ts/.template.astro excludedPaths. werkstatt-engine now 0 diagnostics.
*/
export function getBaseUrl(url: URL | string): string {
const resolvedUrl = new URL(typeof url === "string" ? url : url.toString());
return resolvedUrl.origin;
}
export function toCanonicalUrl(url: URL | string): string {
const resolvedUrl = new URL(typeof url === "string" ? url : url.toString());
resolvedUrl.search = "";
resolvedUrl.hash = "";
return resolvedUrl.toString();
}
export function toAbsoluteUrl(baseUrl: string, path: string): string {
return new URL(path, `${baseUrl}/`).toString();
}
/**
* RFC-0910: produce the canonical root URL for entity identity.
*
* The entity root URL is language-independent — it is always `https://site/`
* regardless of the default language. This contrasts with page URLs, which
* are language-prefixed for non-default languages and unprefixed for the
* default language per RFC-0160.
*/
export function canonicalRootUrl(baseUrl: string): string {
return new URL("/", `${baseUrl}/`).toString();
}
export function toPathname(url: string): string {
return new URL(url).pathname;
}
export function markdownTwinRelPath(
pathname: string,
opts: { supportedLangs: readonly string[] },
): string {
const p = pathname.replace(/^\/+|\/+$/g, "");
if (p === "") return "index.md";
const isLangRoot = !p.includes("/") && opts.supportedLangs.includes(p);
if (isLangRoot) return `${p}/index.md`;
return `${p}.md`;
}
export function markdownTwinUrlPath(
pathname: string,
opts: { supportedLangs: readonly string[] },
): string {
return `/${markdownTwinRelPath(pathname, opts)}`;
}
function toIdSegment(value: string): string {
return (
value
.toLowerCase()
.normalize("NFKD")
.replace(/[\u0300-\u036f]/g, "")
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-+|-+$/g, "") || "entity"
);
}
export function createSemanticIds(baseUrl: string) {
const normalizedBaseUrl = baseUrl.replace(/\/$/, "");
return {
organization: `${normalizedBaseUrl}/#/schema/organization`,
website: `${normalizedBaseUrl}/#/schema/website`,
webpage: (pageUrl: string) => `${toCanonicalUrl(pageUrl)}#/schema/webpage`,
breadcrumb: (pageUrl: string) => `${toCanonicalUrl(pageUrl)}#/schema/breadcrumb`,
person: (name: string) => `${normalizedBaseUrl}/#/schema/person/${toIdSegment(name)}`,
initiative: (name: string) => `${normalizedBaseUrl}/#/schema/initiative/${toIdSegment(name)}`,
service: (name: string) => `${normalizedBaseUrl}/#/schema/service/${toIdSegment(name)}`,
faq: (pageUrl: string) => `${toCanonicalUrl(pageUrl)}#/schema/faq`,
};
}