okf-catalog 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/LICENSE +202 -0
- package/NOTICE +10 -0
- package/README.md +87 -0
- package/dist/bundle/cat-file.js +94 -0
- package/dist/bundle/cat-file.js.map +1 -0
- package/dist/bundle/contract.js +60 -0
- package/dist/bundle/contract.js.map +1 -0
- package/dist/bundle/frontmatter.js +106 -0
- package/dist/bundle/frontmatter.js.map +1 -0
- package/dist/bundle/git-tree.js +109 -0
- package/dist/bundle/git-tree.js.map +1 -0
- package/dist/bundle/index-file.js +90 -0
- package/dist/bundle/index-file.js.map +1 -0
- package/dist/bundle/links.js +57 -0
- package/dist/bundle/links.js.map +1 -0
- package/dist/bundle/load.js +363 -0
- package/dist/bundle/load.js.map +1 -0
- package/dist/bundle/manifest.js +95 -0
- package/dist/bundle/manifest.js.map +1 -0
- package/dist/bundle/markdown.js +210 -0
- package/dist/bundle/markdown.js.map +1 -0
- package/dist/bundle/model.js +7 -0
- package/dist/bundle/model.js.map +1 -0
- package/dist/bundle/page.js +374 -0
- package/dist/bundle/page.js.map +1 -0
- package/dist/bundle/paths.js +36 -0
- package/dist/bundle/paths.js.map +1 -0
- package/dist/bundle/reserved.js +56 -0
- package/dist/bundle/reserved.js.map +1 -0
- package/dist/bundle/timestamp.js +49 -0
- package/dist/bundle/timestamp.js.map +1 -0
- package/dist/catalog/model.js +32 -0
- package/dist/catalog/model.js.map +1 -0
- package/dist/catalog/nearest.js +56 -0
- package/dist/catalog/nearest.js.map +1 -0
- package/dist/catalog/outputs.js +438 -0
- package/dist/catalog/outputs.js.map +1 -0
- package/dist/catalog/provenance.js +37 -0
- package/dist/catalog/provenance.js.map +1 -0
- package/dist/catalog/runtime.js +2 -0
- package/dist/catalog/runtime.js.map +1 -0
- package/dist/catalog/text.js +140 -0
- package/dist/catalog/text.js.map +1 -0
- package/dist/cli.js +81 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/check.js +155 -0
- package/dist/commands/check.js.map +1 -0
- package/dist/commands/pack.js +182 -0
- package/dist/commands/pack.js.map +1 -0
- package/dist/commands/serve.js +452 -0
- package/dist/commands/serve.js.map +1 -0
- package/dist/config/company-config.js +272 -0
- package/dist/config/company-config.js.map +1 -0
- package/dist/derive/derived-document.js +42 -0
- package/dist/derive/derived-document.js.map +1 -0
- package/dist/engine/qmd-render.js +54 -0
- package/dist/engine/qmd-render.js.map +1 -0
- package/dist/engine/qmd.js +241 -0
- package/dist/engine/qmd.js.map +1 -0
- package/dist/fs/cache-dir.js +140 -0
- package/dist/fs/cache-dir.js.map +1 -0
- package/dist/fs/company-lock.js +162 -0
- package/dist/fs/company-lock.js.map +1 -0
- package/dist/fs/walk.js +168 -0
- package/dist/fs/walk.js.map +1 -0
- package/dist/log.js +65 -0
- package/dist/log.js.map +1 -0
- package/dist/mcp/server.js +30 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/stdio.js +45 -0
- package/dist/mcp/stdio.js.map +1 -0
- package/dist/mcp/tools.js +249 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/report/report.js +43 -0
- package/dist/report/report.js.map +1 -0
- package/dist/search/engine.js +2 -0
- package/dist/search/engine.js.map +1 -0
- package/dist/search/query.js +91 -0
- package/dist/search/query.js.map +1 -0
- package/dist/search/search.js +224 -0
- package/dist/search/search.js.map +1 -0
- package/dist/search/snippet.js +95 -0
- package/dist/search/snippet.js.map +1 -0
- package/dist/serve/poller.js +119 -0
- package/dist/serve/poller.js.map +1 -0
- package/dist/serve/runtime.js +390 -0
- package/dist/serve/runtime.js.map +1 -0
- package/dist/source/git-runner.js +232 -0
- package/dist/source/git-runner.js.map +1 -0
- package/dist/source/git.js +457 -0
- package/dist/source/git.js.map +1 -0
- package/dist/source/local.js +20 -0
- package/dist/source/local.js.map +1 -0
- package/dist/source/source.js +2 -0
- package/dist/source/source.js.map +1 -0
- package/npm-shrinkwrap.json +5482 -0
- package/package.json +80 -0
- package/plugin/claude-code/.claude-plugin/plugin.json +15 -0
- package/plugin/claude-code/.mcp.json +10 -0
- package/plugin/claude-code/skills/okf-catalog/SKILL.md +15 -0
- package/recipes/publish/README.md +32 -0
- package/recipes/publish/checkers.lock +435 -0
- package/recipes/publish/checkers.txt +5 -0
- package/recipes/publish/pack.sh +60 -0
- package/recipes/publish/publish.yml +88 -0
- package/recipes/publish/push.sh +63 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page.js","sourceRoot":"","sources":["../../src/bundle/page.ts"],"names":[],"mappings":"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","sourcesContent":["import { decodeUtf8, parseFrontmatter, splitFrontmatter } from \"./frontmatter.js\";\nimport { type LinkIndex, resolveLink } from \"./links.js\";\nimport { sha256Hex } from \"./manifest.js\";\nimport { readBody } from \"./markdown.js\";\nimport type {\n BundleFile,\n Degradation,\n DegradationCode,\n Link,\n Page,\n PagePath,\n Refusal,\n RefusalRule,\n Source,\n SpecText,\n StaleAfter,\n Status,\n Timestamp,\n Trust,\n Verification,\n} from \"./model.js\";\nimport { folderOf } from \"./paths.js\";\nimport { parseTimestamp } from \"./timestamp.js\";\n\nexport interface PageContext {\n linkIndex: LinkIndex;\n specText: SpecText;\n}\n\nexport type ParsePageResult = { ok: true; page: Page } | { ok: false; refusal: Refusal };\n\nconst STATUSES: ReadonlySet<string> = new Set<Status>([\"draft\", \"stable\", \"deprecated\"]);\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/** What a value is, for a sentence: \"a list\", \"a mapping\", \"a number\", \"empty\". */\nfunction kindOf(value: unknown): string {\n if (value === null || value === undefined) return \"empty\";\n if (Array.isArray(value)) return \"a list\";\n if (typeof value === \"object\") return \"a mapping\";\n return `a ${typeof value}`;\n}\n\nfunction stemOf(path: string): string {\n return path.slice(path.lastIndexOf(\"/\") + 1).replace(/\\.md$/, \"\");\n}\n\n/** The verification with the latest instant; among entries without one, the last listed. */\nexport function latestVerification(verified: readonly Verification[]): Verification | undefined {\n let best: Verification | undefined;\n let bestAt = Number.NEGATIVE_INFINITY;\n for (const v of verified) {\n const at = v.at?.at?.getTime();\n if (at === undefined) {\n if (best === undefined) best = v;\n continue;\n }\n if (best === undefined || best.at?.at === undefined || at >= bestAt) {\n best = v;\n bestAt = at;\n }\n }\n if (best !== undefined && best.at?.at === undefined) {\n for (const v of verified) if (v.at?.at === undefined) best = v;\n }\n return best;\n}\n\n/** Turns one file into a page, deriving every field with its source and a degradation wherever a fallback was used. */\nexport function parsePage(file: BundleFile, ctx: PageContext): ParsePageResult {\n const path = file.path;\n const degradations: Degradation[] = [];\n const degrade = (code: DegradationCode, field: string, detail: string): void => {\n degradations.push({ path, code, field, detail });\n };\n const refuse = (rule: RefusalRule, detail: string): ParsePageResult => ({\n ok: false,\n refusal: { path, rule, detail },\n });\n\n const decoded = decodeUtf8(file.bytes);\n if (!decoded.ok) return refuse(\"not-utf8\", \"the file is not valid UTF-8\");\n const split = splitFrontmatter(decoded.text);\n if (split.block === undefined) {\n return refuse(\n \"no-frontmatter\",\n split.unclosed === true\n ? \"the frontmatter block opened on line 1 is never closed\"\n : \"no frontmatter block opens the file\",\n );\n }\n const parsed = parseFrontmatter(split.block, 1);\n if (!parsed.ok) return refuse(\"frontmatter-unparseable\", parsed.error);\n const { data, sources: scalarText } = parsed;\n for (const warning of parsed.warnings) degrade(\"frontmatter-warning\", \"frontmatter\", warning);\n\n /** A top-level value read as text: strings as they are, numbers and booleans by their source text, reported. */\n const text = (key: string): string | undefined => {\n const value = data[key];\n if (typeof value === \"string\") return value;\n if (typeof value === \"number\" || typeof value === \"boolean\") {\n const asText = scalarText[key] ?? String(value);\n degrade(\"scalar-coerced\", key, `${key} is a ${typeof value}, read as \"${asText}\"`);\n return asText;\n }\n return undefined;\n };\n\n if (!Object.hasOwn(data, \"type\")) return refuse(\"no-type\", \"the frontmatter has no type\");\n const type = text(\"type\")?.trim();\n if (type === undefined) return refuse(\"no-type\", `type is ${kindOf(data.type)}, not text`);\n if (type.length === 0) return refuse(\"no-type\", \"type is empty\");\n\n const facts = readBody(split.body);\n if (facts.unanalysed) {\n degrade(\n \"body-unanalysed\",\n \"body\",\n \"the body nests deeper than the analyser allows; no title, description or links were taken from it\",\n );\n }\n if (facts.truncated)\n degrade(\"body-truncated\", \"body\", \"only the first 256 KiB of the body were analysed\");\n\n let title = text(\"title\")?.trim();\n let titleSource: Page[\"titleSource\"] = \"frontmatter\";\n if (Object.hasOwn(data, \"title\") && title === undefined) {\n degrade(\"field-ignored\", \"title\", `title is ${kindOf(data.title)}, not text; ignored`);\n }\n if (title === undefined || title.length === 0) {\n if (facts.firstHeading !== undefined && facts.firstHeading.length > 0) {\n title = facts.firstHeading;\n titleSource = \"heading\";\n degrade(\n \"title-from-heading\",\n \"title\",\n `no title field; the first heading \"${title}\" is used`,\n );\n } else {\n title = stemOf(path);\n titleSource = \"filename\";\n degrade(\n \"title-from-filename\",\n \"title\",\n `no title field and no heading; the file name \"${title}\" is used`,\n );\n }\n }\n\n let description = text(\"description\")?.trim();\n let descriptionSource: Page[\"descriptionSource\"] = \"frontmatter\";\n if (Object.hasOwn(data, \"description\") && description === undefined) {\n degrade(\n \"field-ignored\",\n \"description\",\n `description is ${kindOf(data.description)}, not text; ignored`,\n );\n }\n if (description === undefined || description.length === 0) {\n if (facts.firstSentence !== undefined) {\n description = facts.firstSentence;\n descriptionSource = \"body\";\n degrade(\n \"description-from-body\",\n \"description\",\n \"no description field; the body's first sentence is used\",\n );\n } else {\n description = undefined;\n descriptionSource = \"none\";\n degrade(\n \"description-missing\",\n \"description\",\n \"no description field and no body text to take one from\",\n );\n }\n }\n\n const tags: string[] = [];\n const rawTags = data.tags;\n if (Array.isArray(rawTags)) {\n rawTags.forEach((tag, i) => {\n if (typeof tag === \"string\") tags.push(tag);\n else if (typeof tag === \"number\" || typeof tag === \"boolean\") tags.push(String(tag));\n else degrade(\"field-ignored\", \"tags\", `tags[${i}] is ${kindOf(tag)}, not text; ignored`);\n });\n } else if (typeof rawTags === \"string\") {\n tags.push(rawTags);\n degrade(\"tags-not-list\", \"tags\", \"tags is a single string, read as one tag\");\n } else if (rawTags !== undefined && rawTags !== null) {\n degrade(\"tags-not-list\", \"tags\", `tags is ${kindOf(rawTags)}, not a list; ignored`);\n }\n\n const statusResult = normaliseStatus(data.status, Object.hasOwn(data, \"status\"), degrade);\n\n let staleAfter: StaleAfter | undefined;\n if (\n Object.hasOwn(data, \"stale_after\") &&\n data.stale_after !== null &&\n data.stale_after !== undefined\n ) {\n const raw = text(\"stale_after\") ?? JSON.stringify(data.stale_after);\n const ts = parseTimestamp(raw);\n const expected = ctx.specText === \"2026-08-15\" ? \"date\" : \"datetime\";\n if (ts.kind === \"date\") staleAfter = { raw, form: \"date\", at: ts.at };\n else if (ts.kind === \"datetime\") {\n staleAfter = { raw, form: \"datetime\", at: ts.at };\n if (!ts.offset)\n degrade(\"stale-after-no-offset\", \"stale_after\", `${raw} has no offset and is read as UTC`);\n } else {\n staleAfter = { raw, form: \"unparseable\" };\n degrade(\n \"stale-after-unparseable\",\n \"stale_after\",\n `${raw} is not a date or a datetime; the page is never overdue`,\n );\n }\n if (staleAfter.form !== \"unparseable\" && staleAfter.form !== expected) {\n degrade(\n \"stale-after-unexpected-form\",\n \"stale_after\",\n `the ${ctx.specText} text expects a ${expected}; ${raw} is a ${staleAfter.form}`,\n );\n }\n }\n\n const timestamp = (value: unknown, field: string): Timestamp => {\n const raw = typeof value === \"string\" ? value : JSON.stringify(value);\n const ts = parseTimestamp(raw);\n if (ts.kind === \"invalid\") {\n degrade(\"timestamp-invalid\", field, `${raw} is not a date or a datetime`);\n return { raw };\n }\n return { raw, at: ts.at };\n };\n\n let generated: Page[\"generated\"];\n if (Object.hasOwn(data, \"generated\")) {\n const g = data.generated;\n if (isRecord(g) && typeof g.by === \"string\" && g.by.trim().length > 0) {\n generated = { by: g.by.trim() };\n if (Object.hasOwn(g, \"at\")) generated.at = timestamp(g.at, \"generated.at\");\n } else {\n degrade(\n \"generated-malformed\",\n \"generated\",\n `generated is ${kindOf(g)} without a by actor; ignored`,\n );\n }\n }\n\n const verified: Verification[] = [];\n if (Object.hasOwn(data, \"verified\")) {\n const v = data.verified;\n const entries: unknown[] = Array.isArray(v) ? v : isRecord(v) ? [v] : [];\n if (!Array.isArray(v) && !isRecord(v)) {\n degrade(\n \"verified-entry-malformed\",\n \"verified\",\n `verified is ${kindOf(v)}, neither a list nor a mapping; ignored`,\n );\n }\n entries.forEach((entry, i) => {\n if (isRecord(entry) && typeof entry.by === \"string\" && entry.by.trim().length > 0) {\n const verification: Verification = { by: entry.by.trim() };\n if (Object.hasOwn(entry, \"at\") && entry.at !== null)\n verification.at = timestamp(entry.at, `verified[${i}].at`);\n else\n degrade(\n \"verification-without-at\",\n \"verified\",\n `verified[${i}] by ${verification.by} has no at`,\n );\n verified.push(verification);\n } else {\n degrade(\"verified-entry-malformed\", \"verified\", `verified[${i}] has no by actor; ignored`);\n }\n });\n }\n const trust: Trust =\n verified.length === 0\n ? \"unverified\"\n : verified.some((v) => v.by.startsWith(\"human:\"))\n ? \"human-reviewed\"\n : \"machine-confirmed\";\n\n const sources: Source[] = [];\n if (Object.hasOwn(data, \"sources\")) {\n const s = data.sources;\n if (Array.isArray(s)) {\n s.forEach((entry, i) => {\n if (isRecord(entry) && typeof entry.resource === \"string\" && entry.resource.length > 0) {\n const source: Source = { resource: entry.resource };\n if (typeof entry.id === \"string\") source.id = entry.id;\n else if (typeof entry.id === \"number\" || typeof entry.id === \"boolean\")\n source.id = String(entry.id);\n if (typeof entry.title === \"string\") source.title = entry.title;\n if (typeof entry.author === \"string\") source.author = entry.author;\n if (typeof entry.usage_count === \"number\") {\n if (Number.isFinite(entry.usage_count)) source.usageCount = entry.usage_count;\n else\n degrade(\n \"source-malformed\",\n \"sources\",\n `sources[${i}].usage_count is not a finite number; ignored`,\n );\n }\n if (typeof entry.last_modified === \"string\") source.lastModified = entry.last_modified;\n const w = entry.usage_window;\n if (isRecord(w) && typeof w.from === \"string\" && typeof w.to === \"string\")\n source.usageWindow = { from: w.from, to: w.to };\n sources.push(source);\n } else {\n degrade(\"source-malformed\", \"sources\", `sources[${i}] has no resource; ignored`);\n }\n });\n } else {\n degrade(\"source-malformed\", \"sources\", `sources is ${kindOf(s)}, not a list; ignored`);\n }\n }\n\n let usageWindow: Page[\"usageWindow\"];\n const w = data.usage_window;\n if (isRecord(w) && typeof w.from === \"string\" && typeof w.to === \"string\")\n usageWindow = { from: w.from, to: w.to };\n\n const resource = text(\"resource\");\n\n const links: Link[] = facts.links.map((l) => {\n const resolved = resolveLink(l.url, path, ctx.linkIndex);\n const link: Link = { raw: l.url, kind: resolved.kind };\n if (resolved.target !== undefined) link.target = resolved.target;\n return link;\n });\n\n // GFM lower-cases footnote identifiers; the join with sources[].id is case-insensitive, and each missing id is reported once.\n const sourceIds = new Set(\n sources.map((s) => s.id?.toLowerCase()).filter((id): id is string => id !== undefined),\n );\n const missing = new Set<string>();\n for (const id of facts.footnoteReferences) if (!sourceIds.has(id.toLowerCase())) missing.add(id);\n for (const id of missing)\n degrade(\"footnote-without-source\", \"sources\", `footnote ${id} has no matching sources entry`);\n\n if (facts.htmlBlocks > 0 || facts.inlineHtml > 0 || facts.hasScriptLike) {\n const parts: string[] = [];\n if (facts.htmlBlocks > 0)\n parts.push(`${facts.htmlBlocks} HTML block${facts.htmlBlocks === 1 ? \"\" : \"s\"}`);\n if (facts.inlineHtml > 0)\n parts.push(`${facts.inlineHtml} inline HTML element${facts.inlineHtml === 1 ? \"\" : \"s\"}`);\n if (facts.hasScriptLike) parts.push(\"including a script-like element\");\n degrade(\n \"body-html\",\n \"body\",\n `${parts.join(\", \")}; indexed as text, never executed, kept out of the description`,\n );\n }\n\n const page: Page = {\n path,\n folder: folderOf(path),\n hash: sha256Hex(file.bytes),\n type,\n title,\n titleSource,\n descriptionSource,\n tags,\n status: statusResult.status,\n statusSource: statusResult.source,\n verified,\n trust,\n sources,\n links,\n footnoteReferences: facts.footnoteReferences,\n frontmatter: data,\n body: split.body,\n ...(facts.prose === undefined ? {} : { prose: facts.prose }),\n degradations,\n };\n if (description !== undefined) page.description = description;\n if (statusResult.raw !== undefined) page.statusRaw = statusResult.raw;\n if (staleAfter !== undefined) page.staleAfter = staleAfter;\n if (generated !== undefined) page.generated = generated;\n const latest = latestVerification(verified);\n if (latest !== undefined) page.latestVerification = latest;\n if (usageWindow !== undefined) page.usageWindow = usageWindow;\n if (resource !== undefined) page.resource = resource;\n return { ok: true, page };\n}\n\nfunction normaliseStatus(\n value: unknown,\n present: boolean,\n degrade: (code: DegradationCode, field: string, detail: string) => void,\n): { status: Status; source: Page[\"statusSource\"]; raw?: string } {\n if (!present || value === undefined || value === null)\n return { status: \"stable\", source: \"default\" };\n const raw = typeof value === \"string\" ? value : JSON.stringify(value);\n const normalised = raw.trim().toLowerCase();\n if (normalised.length === 0) return { status: \"stable\", source: \"default\" };\n if (STATUSES.has(normalised)) return { status: normalised as Status, source: \"frontmatter\", raw };\n degrade(\n \"status-unknown\",\n \"status\",\n `status \"${raw}\" is not draft, stable or deprecated; treated as draft`,\n );\n return { status: \"draft\", source: \"frontmatter\", raw };\n}\n\nconst ARTICLE: Record<string, string> = {\n folder: \"a folder\",\n reserved: \"an index or log file\",\n attachment: \"an attachment\",\n};\n\n/**\n * The replacement for a deprecated page: the first body link that is not a same-page anchor decides. It is the\n * replacement when it resolves to an admitted page other than this one; otherwise there is none, and the reason is\n * recorded. Nothing is decided for a page that is not deprecated.\n */\nexport function decideReplacement(\n page: Page,\n admitted: ReadonlySet<PagePath>,\n): { replacement?: PagePath; degradation?: Degradation } {\n if (page.status !== \"deprecated\") return {};\n const reason = (code: DegradationCode, detail: string) => ({\n degradation: { path: page.path, code, field: \"replacement\", detail },\n });\n const first = page.links.find((l) => l.kind !== \"anchor\");\n if (first === undefined)\n return reason(\n \"replacement-missing\",\n \"a deprecated page should link to the page that replaced it; no link was found\",\n );\n switch (first.kind) {\n case \"external\":\n return reason(\n \"replacement-external\",\n `the first link, ${first.raw}, points outside the bundle`,\n );\n case \"broken\":\n return reason(\n \"replacement-broken\",\n `the first link, ${first.raw}, points at nothing in the bundle`,\n );\n case \"page\": {\n const target = first.target ?? \"\";\n if (target === page.path)\n return reason(\"replacement-self\", \"the first link points at the page itself\");\n if (!admitted.has(target))\n return reason(\n \"replacement-not-served\",\n `the first link points at ${target}, which is not served`,\n );\n return { replacement: target };\n }\n default:\n return reason(\n \"replacement-not-a-page\",\n `the first link, ${first.raw}, points at ${ARTICLE[first.kind] ?? first.kind}, not a page`,\n );\n }\n}\n"]}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** Path helpers shared by the core. Bundle paths are POSIX, relative, and never contain a backslash. */
|
|
2
|
+
export const BOM = "";
|
|
3
|
+
export function folderOf(path) {
|
|
4
|
+
const i = path.lastIndexOf("/");
|
|
5
|
+
return i === -1 ? "" : path.slice(0, i);
|
|
6
|
+
}
|
|
7
|
+
export const byCodeUnit = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
|
8
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: a path holding one is refused
|
|
9
|
+
const PATH_CONTROLS = /[\u0000-\u001f\u007f-\u009f\u2028\u2029]/;
|
|
10
|
+
/** A bundle-relative path: no leading slash, no backslash, no control or line-separator character, no empty, `.`, `..` or `__proto__` segment. */
|
|
11
|
+
export function isSafeRelativePath(path) {
|
|
12
|
+
if (path.length === 0 ||
|
|
13
|
+
path.startsWith("/") ||
|
|
14
|
+
path.includes("\\") ||
|
|
15
|
+
PATH_CONTROLS.test(path)) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
return path
|
|
19
|
+
.split("/")
|
|
20
|
+
.every((segment) => segment !== "" && segment !== "." && segment !== ".." && segment !== "__proto__");
|
|
21
|
+
}
|
|
22
|
+
/** Two paths that agree under this key would be one file to the engine (and to a case-folding file system). */
|
|
23
|
+
export const collisionKey = (path) => path.normalize("NFC").toUpperCase().toLowerCase();
|
|
24
|
+
/** The first pair of paths the engine's key folds together, or undefined when every path is distinct under it. */
|
|
25
|
+
export function findCollision(paths) {
|
|
26
|
+
const seen = new Map();
|
|
27
|
+
for (const path of paths) {
|
|
28
|
+
const key = collisionKey(path);
|
|
29
|
+
const other = seen.get(key);
|
|
30
|
+
if (other !== undefined)
|
|
31
|
+
return [other, path];
|
|
32
|
+
seen.set(key, path);
|
|
33
|
+
}
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/bundle/paths.ts"],"names":[],"mappings":"AAAA,wGAAwG;AAExG,MAAM,CAAC,MAAM,GAAG,GAAG,GAAG,CAAC;AAEvB,MAAM,UAAU,QAAQ,CAAC,IAAY;IACnC,MAAM,CAAC,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAChC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAC1C,CAAC;AAED,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,CAAS,EAAE,CAAS,EAAU,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAEzF,yFAAyF;AACzF,MAAM,aAAa,GAAG,0CAA0C,CAAC;AAEjE,kJAAkJ;AAClJ,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,IACE,IAAI,CAAC,MAAM,KAAK,CAAC;QACjB,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QACpB,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;QACnB,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,EACxB,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,IAAI;SACR,KAAK,CAAC,GAAG,CAAC;SACV,KAAK,CACJ,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,KAAK,EAAE,IAAI,OAAO,KAAK,GAAG,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,KAAK,WAAW,CAC9F,CAAC;AACN,CAAC;AAED,+GAA+G;AAC/G,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,IAAY,EAAU,EAAE,CACnD,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC,WAAW,EAAE,CAAC;AAEpD,kHAAkH;AAClH,MAAM,UAAU,aAAa,CAAC,KAAwB;IACpD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAC;IACvC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;QAC/B,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC5B,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QAC9C,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IACtB,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC","sourcesContent":["/** Path helpers shared by the core. Bundle paths are POSIX, relative, and never contain a backslash. */\n\nexport const BOM = \"\";\n\nexport function folderOf(path: string): string {\n const i = path.lastIndexOf(\"/\");\n return i === -1 ? \"\" : path.slice(0, i);\n}\n\nexport const byCodeUnit = (a: string, b: string): number => (a < b ? -1 : a > b ? 1 : 0);\n\n// biome-ignore lint/suspicious/noControlCharactersInRegex: a path holding one is refused\nconst PATH_CONTROLS = /[\\u0000-\\u001f\\u007f-\\u009f\\u2028\\u2029]/;\n\n/** A bundle-relative path: no leading slash, no backslash, no control or line-separator character, no empty, `.`, `..` or `__proto__` segment. */\nexport function isSafeRelativePath(path: string): boolean {\n if (\n path.length === 0 ||\n path.startsWith(\"/\") ||\n path.includes(\"\\\\\") ||\n PATH_CONTROLS.test(path)\n ) {\n return false;\n }\n return path\n .split(\"/\")\n .every(\n (segment) => segment !== \"\" && segment !== \".\" && segment !== \"..\" && segment !== \"__proto__\",\n );\n}\n\n/** Two paths that agree under this key would be one file to the engine (and to a case-folding file system). */\nexport const collisionKey = (path: string): string =>\n path.normalize(\"NFC\").toUpperCase().toLowerCase();\n\n/** The first pair of paths the engine's key folds together, or undefined when every path is distinct under it. */\nexport function findCollision(paths: readonly string[]): [string, string] | undefined {\n const seen = new Map<string, string>();\n for (const path of paths) {\n const key = collisionKey(path);\n const other = seen.get(key);\n if (other !== undefined) return [other, path];\n seen.set(key, path);\n }\n return undefined;\n}\n"]}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { parseFrontmatter, splitFrontmatter } from "./frontmatter.js";
|
|
2
|
+
import { folderOf } from "./paths.js";
|
|
3
|
+
const KNOWN_VERSIONS = new Set(["0.1", "0.2"]);
|
|
4
|
+
/** index.md and log.md are reserved at any depth (specification §3.1); nothing else is. */
|
|
5
|
+
export function reservedKind(path) {
|
|
6
|
+
const name = path.slice(path.lastIndexOf("/") + 1);
|
|
7
|
+
if (name === "index.md")
|
|
8
|
+
return "index";
|
|
9
|
+
if (name === "log.md")
|
|
10
|
+
return "log";
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
/** Reads a reserved file. Frontmatter is tolerated and kept; when it does not parse, that is a degradation, never a refusal. */
|
|
14
|
+
export function parseReserved(path, text) {
|
|
15
|
+
const kind = reservedKind(path);
|
|
16
|
+
if (kind === undefined)
|
|
17
|
+
throw new Error(`${path} is not a reserved file name`);
|
|
18
|
+
const folder = folderOf(path);
|
|
19
|
+
const degradations = [];
|
|
20
|
+
const split = splitFrontmatter(text);
|
|
21
|
+
let frontmatter;
|
|
22
|
+
let okfVersion;
|
|
23
|
+
if (split.block !== undefined) {
|
|
24
|
+
const parsed = parseFrontmatter(split.block);
|
|
25
|
+
if (parsed.ok) {
|
|
26
|
+
frontmatter = parsed.data;
|
|
27
|
+
if (kind === "index" && folder === "" && Object.hasOwn(parsed.data, "okf_version")) {
|
|
28
|
+
const raw = parsed.data.okf_version;
|
|
29
|
+
okfVersion = parsed.sources.okf_version ?? (typeof raw === "string" ? raw : String(raw));
|
|
30
|
+
if (!KNOWN_VERSIONS.has(okfVersion)) {
|
|
31
|
+
degradations.push({
|
|
32
|
+
path,
|
|
33
|
+
code: "okf-version-unknown",
|
|
34
|
+
field: "okf_version",
|
|
35
|
+
detail: `okf_version ${okfVersion} is not a version this server knows; read as best effort`,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
degradations.push({
|
|
42
|
+
path,
|
|
43
|
+
code: "reserved-frontmatter-unparseable",
|
|
44
|
+
field: "frontmatter",
|
|
45
|
+
detail: parsed.error,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const result = { kind, path, folder, body: split.body, text, degradations };
|
|
50
|
+
if (frontmatter !== undefined)
|
|
51
|
+
result.frontmatter = frontmatter;
|
|
52
|
+
if (okfVersion !== undefined)
|
|
53
|
+
result.okfVersion = okfVersion;
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=reserved.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reserved.js","sourceRoot":"","sources":["../../src/bundle/reserved.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEtE,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;AAE/C,2FAA2F;AAC3F,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;IACnD,IAAI,IAAI,KAAK,UAAU;QAAE,OAAO,OAAO,CAAC;IACxC,IAAI,IAAI,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IACpC,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,gIAAgI;AAChI,MAAM,UAAU,aAAa,CAAC,IAAY,EAAE,IAAY;IACtD,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IAChC,IAAI,IAAI,KAAK,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,8BAA8B,CAAC,CAAC;IAC/E,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC9B,MAAM,YAAY,GAAkB,EAAE,CAAC;IACvC,MAAM,KAAK,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;IACrC,IAAI,WAAgD,CAAC;IACrD,IAAI,UAA8B,CAAC;IACnC,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QAC9B,MAAM,MAAM,GAAG,gBAAgB,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC7C,IAAI,MAAM,CAAC,EAAE,EAAE,CAAC;YACd,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC;YAC1B,IAAI,IAAI,KAAK,OAAO,IAAI,MAAM,KAAK,EAAE,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,aAAa,CAAC,EAAE,CAAC;gBACnF,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC;gBACpC,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,WAAW,IAAI,CAAC,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;gBACzF,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;oBACpC,YAAY,CAAC,IAAI,CAAC;wBAChB,IAAI;wBACJ,IAAI,EAAE,qBAAqB;wBAC3B,KAAK,EAAE,aAAa;wBACpB,MAAM,EAAE,eAAe,UAAU,0DAA0D;qBAC5F,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;QACH,CAAC;aAAM,CAAC;YACN,YAAY,CAAC,IAAI,CAAC;gBAChB,IAAI;gBACJ,IAAI,EAAE,kCAAkC;gBACxC,KAAK,EAAE,aAAa;gBACpB,MAAM,EAAE,MAAM,CAAC,KAAK;aACrB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,MAAM,MAAM,GAAiB,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC;IAC1F,IAAI,WAAW,KAAK,SAAS;QAAE,MAAM,CAAC,WAAW,GAAG,WAAW,CAAC;IAChE,IAAI,UAAU,KAAK,SAAS;QAAE,MAAM,CAAC,UAAU,GAAG,UAAU,CAAC;IAC7D,OAAO,MAAM,CAAC;AAChB,CAAC","sourcesContent":["import { parseFrontmatter, splitFrontmatter } from \"./frontmatter.js\";\nimport type { Degradation, ReservedFile } from \"./model.js\";\nimport { folderOf } from \"./paths.js\";\n\nconst KNOWN_VERSIONS = new Set([\"0.1\", \"0.2\"]);\n\n/** index.md and log.md are reserved at any depth (specification §3.1); nothing else is. */\nexport function reservedKind(path: string): \"index\" | \"log\" | undefined {\n const name = path.slice(path.lastIndexOf(\"/\") + 1);\n if (name === \"index.md\") return \"index\";\n if (name === \"log.md\") return \"log\";\n return undefined;\n}\n\n/** Reads a reserved file. Frontmatter is tolerated and kept; when it does not parse, that is a degradation, never a refusal. */\nexport function parseReserved(path: string, text: string): ReservedFile {\n const kind = reservedKind(path);\n if (kind === undefined) throw new Error(`${path} is not a reserved file name`);\n const folder = folderOf(path);\n const degradations: Degradation[] = [];\n const split = splitFrontmatter(text);\n let frontmatter: Record<string, unknown> | undefined;\n let okfVersion: string | undefined;\n if (split.block !== undefined) {\n const parsed = parseFrontmatter(split.block);\n if (parsed.ok) {\n frontmatter = parsed.data;\n if (kind === \"index\" && folder === \"\" && Object.hasOwn(parsed.data, \"okf_version\")) {\n const raw = parsed.data.okf_version;\n okfVersion = parsed.sources.okf_version ?? (typeof raw === \"string\" ? raw : String(raw));\n if (!KNOWN_VERSIONS.has(okfVersion)) {\n degradations.push({\n path,\n code: \"okf-version-unknown\",\n field: \"okf_version\",\n detail: `okf_version ${okfVersion} is not a version this server knows; read as best effort`,\n });\n }\n }\n } else {\n degradations.push({\n path,\n code: \"reserved-frontmatter-unparseable\",\n field: \"frontmatter\",\n detail: parsed.error,\n });\n }\n }\n const result: ReservedFile = { kind, path, folder, body: split.body, text, degradations };\n if (frontmatter !== undefined) result.frontmatter = frontmatter;\n if (okfVersion !== undefined) result.okfVersion = okfVersion;\n return result;\n}\n"]}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const DATE = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
2
|
+
const DATETIME = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{1,9}))?)?(Z|[+-]\d{2}(?::?\d{2})?)?$/;
|
|
3
|
+
function utcDay(year, month, day) {
|
|
4
|
+
const d = new Date(0);
|
|
5
|
+
d.setUTCFullYear(year, month - 1, day);
|
|
6
|
+
d.setUTCHours(0, 0, 0, 0);
|
|
7
|
+
if (d.getUTCFullYear() !== year || d.getUTCMonth() !== month - 1 || d.getUTCDate() !== day)
|
|
8
|
+
return undefined;
|
|
9
|
+
return d.getTime();
|
|
10
|
+
}
|
|
11
|
+
export function parseTimestamp(raw) {
|
|
12
|
+
const date = DATE.exec(raw);
|
|
13
|
+
if (date) {
|
|
14
|
+
const ms = utcDay(Number(date[1]), Number(date[2]), Number(date[3]));
|
|
15
|
+
return ms === undefined
|
|
16
|
+
? { kind: "invalid", reason: "not a calendar date" }
|
|
17
|
+
: { kind: "date", at: new Date(ms) };
|
|
18
|
+
}
|
|
19
|
+
const dt = DATETIME.exec(raw);
|
|
20
|
+
if (!dt)
|
|
21
|
+
return { kind: "invalid", reason: "not a date or an RFC 3339 datetime" };
|
|
22
|
+
const day = utcDay(Number(dt[1]), Number(dt[2]), Number(dt[3]));
|
|
23
|
+
if (day === undefined)
|
|
24
|
+
return { kind: "invalid", reason: "not a calendar date" };
|
|
25
|
+
const hour = Number(dt[4]);
|
|
26
|
+
const minute = Number(dt[5]);
|
|
27
|
+
let second = dt[6] === undefined ? 0 : Number(dt[6]);
|
|
28
|
+
let millis = dt[7] === undefined ? 0 : Number(`${dt[7]}000`.slice(0, 3));
|
|
29
|
+
if (second === 60) {
|
|
30
|
+
second = 59;
|
|
31
|
+
millis = 999;
|
|
32
|
+
}
|
|
33
|
+
if (hour > 23 || minute > 59 || second > 59)
|
|
34
|
+
return { kind: "invalid", reason: "time of day out of range" };
|
|
35
|
+
let offsetMinutes = 0;
|
|
36
|
+
const offset = dt[8];
|
|
37
|
+
if (offset !== undefined && offset !== "Z") {
|
|
38
|
+
const sign = offset.startsWith("-") ? -1 : 1;
|
|
39
|
+
const digits = offset.slice(1).replace(":", "");
|
|
40
|
+
const oh = Number(digits.slice(0, 2));
|
|
41
|
+
const om = digits.length > 2 ? Number(digits.slice(2, 4)) : 0;
|
|
42
|
+
if (oh > 23 || om > 59)
|
|
43
|
+
return { kind: "invalid", reason: "offset out of range" };
|
|
44
|
+
offsetMinutes = sign * (oh * 60 + om);
|
|
45
|
+
}
|
|
46
|
+
const at = new Date(day + ((hour * 60 + minute) * 60 + second) * 1000 + millis - offsetMinutes * 60_000);
|
|
47
|
+
return { kind: "datetime", at, offset: offset !== undefined };
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=timestamp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timestamp.js","sourceRoot":"","sources":["../../src/bundle/timestamp.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Timestamps by explicit grammar, never by Date.parse: a date is `YYYY-MM-DD`, a datetime is an RFC 3339 /\n * ISO 8601 profile with an optional `Z`, `±hh:mm`, `±hhmm` or `±hh` offset. Values are built in UTC and checked\n * against the calendar, so an impossible date is rejected rather than rolled over, years 0001 to 0099 are\n * not mapped onto the twentieth century, a leap second reads as the last millisecond of its minute, and the\n * machine's zone never matters.\n */\nexport type ParsedTimestamp =\n | { kind: \"date\"; at: Date }\n | { kind: \"datetime\"; at: Date; offset: boolean }\n | { kind: \"invalid\"; reason: string };\n\nconst DATE = /^(\\d{4})-(\\d{2})-(\\d{2})$/;\nconst DATETIME =\n /^(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2})(?::(\\d{2})(?:\\.(\\d{1,9}))?)?(Z|[+-]\\d{2}(?::?\\d{2})?)?$/;\n\nfunction utcDay(year: number, month: number, day: number): number | undefined {\n const d = new Date(0);\n d.setUTCFullYear(year, month - 1, day);\n d.setUTCHours(0, 0, 0, 0);\n if (d.getUTCFullYear() !== year || d.getUTCMonth() !== month - 1 || d.getUTCDate() !== day)\n return undefined;\n return d.getTime();\n}\n\nexport function parseTimestamp(raw: string): ParsedTimestamp {\n const date = DATE.exec(raw);\n if (date) {\n const ms = utcDay(Number(date[1]), Number(date[2]), Number(date[3]));\n return ms === undefined\n ? { kind: \"invalid\", reason: \"not a calendar date\" }\n : { kind: \"date\", at: new Date(ms) };\n }\n const dt = DATETIME.exec(raw);\n if (!dt) return { kind: \"invalid\", reason: \"not a date or an RFC 3339 datetime\" };\n const day = utcDay(Number(dt[1]), Number(dt[2]), Number(dt[3]));\n if (day === undefined) return { kind: \"invalid\", reason: \"not a calendar date\" };\n const hour = Number(dt[4]);\n const minute = Number(dt[5]);\n let second = dt[6] === undefined ? 0 : Number(dt[6]);\n let millis = dt[7] === undefined ? 0 : Number(`${dt[7]}000`.slice(0, 3));\n if (second === 60) {\n second = 59;\n millis = 999;\n }\n if (hour > 23 || minute > 59 || second > 59)\n return { kind: \"invalid\", reason: \"time of day out of range\" };\n let offsetMinutes = 0;\n const offset = dt[8];\n if (offset !== undefined && offset !== \"Z\") {\n const sign = offset.startsWith(\"-\") ? -1 : 1;\n const digits = offset.slice(1).replace(\":\", \"\");\n const oh = Number(digits.slice(0, 2));\n const om = digits.length > 2 ? Number(digits.slice(2, 4)) : 0;\n if (oh > 23 || om > 59) return { kind: \"invalid\", reason: \"offset out of range\" };\n offsetMinutes = sign * (oh * 60 + om);\n }\n const at = new Date(\n day + ((hour * 60 + minute) * 60 + second) * 1000 + millis - offsetMinutes * 60_000,\n );\n return { kind: \"datetime\", at, offset: offset !== undefined };\n}\n"]}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export function buildCatalog(input) {
|
|
2
|
+
const byType = new Map();
|
|
3
|
+
for (const page of input.pages.values()) {
|
|
4
|
+
const list = byType.get(page.type) ?? [];
|
|
5
|
+
list.push(page.path);
|
|
6
|
+
byType.set(page.type, list);
|
|
7
|
+
}
|
|
8
|
+
for (const list of byType.values())
|
|
9
|
+
list.sort();
|
|
10
|
+
const catalog = {
|
|
11
|
+
company: input.company,
|
|
12
|
+
loadedAt: input.loadedAt,
|
|
13
|
+
pages: input.pages,
|
|
14
|
+
folders: input.folders,
|
|
15
|
+
byType,
|
|
16
|
+
};
|
|
17
|
+
if (input.commit !== undefined)
|
|
18
|
+
catalog.commit = input.commit;
|
|
19
|
+
if (input.okfVersion !== undefined)
|
|
20
|
+
catalog.okfVersion = input.okfVersion;
|
|
21
|
+
return catalog;
|
|
22
|
+
}
|
|
23
|
+
export function getPage(catalog, path) {
|
|
24
|
+
return catalog.pages.get(path);
|
|
25
|
+
}
|
|
26
|
+
export function getFolder(catalog, folder) {
|
|
27
|
+
return catalog.folders.get(folder);
|
|
28
|
+
}
|
|
29
|
+
export function listTypes(catalog) {
|
|
30
|
+
return [...catalog.byType.keys()].sort();
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.js","sourceRoot":"","sources":["../../src/catalog/model.ts"],"names":[],"mappings":"AAsBA,MAAM,UAAU,YAAY,CAAC,KAO5B;IACC,MAAM,MAAM,GAAG,IAAI,GAAG,EAAsB,CAAC;IAC7C,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;QACxC,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QACzC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrB,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC9B,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,MAAM,EAAE;QAAE,IAAI,CAAC,IAAI,EAAE,CAAC;IAChD,MAAM,OAAO,GAAY;QACvB,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,MAAM;KACP,CAAC;IACF,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS;QAAE,OAAO,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;IAC9D,IAAI,KAAK,CAAC,UAAU,KAAK,SAAS;QAAE,OAAO,CAAC,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC;IAC1E,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,OAAgB,EAAE,IAAc;IACtD,OAAO,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,OAAgB,EAAE,MAAc;IACxD,OAAO,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,OAAgB;IACxC,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;AAC3C,CAAC","sourcesContent":["import type { Page, PagePath, ReservedFile } from \"../bundle/model.js\";\n\nexport interface Folder {\n /** The folder's index: the company's own file, or one generated from the folder's admitted pages. */\n index?: ReservedFile;\n indexSource: \"file\" | \"generated\";\n log?: ReservedFile;\n pages: PagePath[];\n subfolders: string[];\n}\n\n/** The immutable view of one loaded bundle: admitted pages only, folders with their index and log, pages by type. */\nexport interface Catalog {\n company: string;\n commit?: string;\n loadedAt: Date;\n okfVersion?: string;\n pages: ReadonlyMap<PagePath, Page>;\n folders: ReadonlyMap<string, Folder>;\n byType: ReadonlyMap<string, PagePath[]>;\n}\n\nexport function buildCatalog(input: {\n company: string;\n commit?: string;\n loadedAt: Date;\n okfVersion?: string;\n pages: Map<PagePath, Page>;\n folders: Map<string, Folder>;\n}): Catalog {\n const byType = new Map<string, PagePath[]>();\n for (const page of input.pages.values()) {\n const list = byType.get(page.type) ?? [];\n list.push(page.path);\n byType.set(page.type, list);\n }\n for (const list of byType.values()) list.sort();\n const catalog: Catalog = {\n company: input.company,\n loadedAt: input.loadedAt,\n pages: input.pages,\n folders: input.folders,\n byType,\n };\n if (input.commit !== undefined) catalog.commit = input.commit;\n if (input.okfVersion !== undefined) catalog.okfVersion = input.okfVersion;\n return catalog;\n}\n\nexport function getPage(catalog: Catalog, path: PagePath): Page | undefined {\n return catalog.pages.get(path);\n}\n\nexport function getFolder(catalog: Catalog, folder: string): Folder | undefined {\n return catalog.folders.get(folder);\n}\n\nexport function listTypes(catalog: Catalog): string[] {\n return [...catalog.byType.keys()].sort();\n}\n"]}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { byCodeUnit } from "../bundle/paths.js";
|
|
2
|
+
/** Characters of a path compared by edit distance; a longer input is cut, so a hostile path cannot stall the loop. */
|
|
3
|
+
const COMPARE_CAP = 256;
|
|
4
|
+
function levenshtein(a, b) {
|
|
5
|
+
if (a === b)
|
|
6
|
+
return 0;
|
|
7
|
+
if (a.length === 0)
|
|
8
|
+
return b.length;
|
|
9
|
+
if (b.length === 0)
|
|
10
|
+
return a.length;
|
|
11
|
+
let previous = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
12
|
+
for (let i = 1; i <= a.length; i++) {
|
|
13
|
+
const current = [i];
|
|
14
|
+
for (let j = 1; j <= b.length; j++) {
|
|
15
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
16
|
+
current.push(Math.min((previous[j] ?? 0) + 1, (current[j - 1] ?? 0) + 1, (previous[j - 1] ?? 0) + cost));
|
|
17
|
+
}
|
|
18
|
+
previous = current;
|
|
19
|
+
}
|
|
20
|
+
return previous[b.length] ?? 0;
|
|
21
|
+
}
|
|
22
|
+
function sharedLeadingSegments(a, b) {
|
|
23
|
+
let n = 0;
|
|
24
|
+
while (n < a.length - 1 && n < b.length - 1 && a[n] === b[n])
|
|
25
|
+
n++;
|
|
26
|
+
return n;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The served paths nearest to one that was not found: more shared leading folders first, then the smaller edit
|
|
30
|
+
* distance between what remains, then path order. Never more than `n`.
|
|
31
|
+
*/
|
|
32
|
+
/** Full edit distances computed per call; beyond this the cheaper length bound decides, so a miss on a huge bundle stays fast. */
|
|
33
|
+
const DISTANCE_BUDGET = 2_000;
|
|
34
|
+
export function nearestPaths(paths, path, n = 3) {
|
|
35
|
+
const wanted = path.slice(0, COMPARE_CAP);
|
|
36
|
+
const wantedSegments = wanted.split("/");
|
|
37
|
+
// Shared folders first, then the length difference, which is a lower bound on the edit distance: the full
|
|
38
|
+
// distance is computed for the most promising candidates only.
|
|
39
|
+
const prepared = [...paths]
|
|
40
|
+
.map((candidate) => {
|
|
41
|
+
const segments = candidate.split("/");
|
|
42
|
+
const shared = sharedLeadingSegments(segments, wantedSegments);
|
|
43
|
+
const a = segments.slice(shared).join("/").slice(0, COMPARE_CAP);
|
|
44
|
+
const b = wantedSegments.slice(shared).join("/").slice(0, COMPARE_CAP);
|
|
45
|
+
return { candidate, shared, a, b, bound: Math.abs(a.length - b.length) };
|
|
46
|
+
})
|
|
47
|
+
.sort((x, y) => y.shared - x.shared || x.bound - y.bound || byCodeUnit(x.candidate, y.candidate));
|
|
48
|
+
const ranked = prepared.map((p, i) => ({
|
|
49
|
+
candidate: p.candidate,
|
|
50
|
+
shared: p.shared,
|
|
51
|
+
distance: i < DISTANCE_BUDGET ? levenshtein(p.a, p.b) : p.bound + COMPARE_CAP,
|
|
52
|
+
}));
|
|
53
|
+
ranked.sort((x, y) => y.shared - x.shared || x.distance - y.distance || byCodeUnit(x.candidate, y.candidate));
|
|
54
|
+
return ranked.slice(0, Math.max(0, n)).map((r) => r.candidate);
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=nearest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nearest.js","sourceRoot":"","sources":["../../src/catalog/nearest.ts"],"names":[],"mappings":"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","sourcesContent":["import { byCodeUnit } from \"../bundle/paths.js\";\n\n/** Characters of a path compared by edit distance; a longer input is cut, so a hostile path cannot stall the loop. */\nconst COMPARE_CAP = 256;\n\nfunction levenshtein(a: string, b: string): number {\n if (a === b) return 0;\n if (a.length === 0) return b.length;\n if (b.length === 0) return a.length;\n let previous = Array.from({ length: b.length + 1 }, (_, i) => i);\n for (let i = 1; i <= a.length; i++) {\n const current = [i];\n for (let j = 1; j <= b.length; j++) {\n const cost = a[i - 1] === b[j - 1] ? 0 : 1;\n current.push(\n Math.min((previous[j] ?? 0) + 1, (current[j - 1] ?? 0) + 1, (previous[j - 1] ?? 0) + cost),\n );\n }\n previous = current;\n }\n return previous[b.length] ?? 0;\n}\n\nfunction sharedLeadingSegments(a: string[], b: string[]): number {\n let n = 0;\n while (n < a.length - 1 && n < b.length - 1 && a[n] === b[n]) n++;\n return n;\n}\n\n/**\n * The served paths nearest to one that was not found: more shared leading folders first, then the smaller edit\n * distance between what remains, then path order. Never more than `n`.\n */\n/** Full edit distances computed per call; beyond this the cheaper length bound decides, so a miss on a huge bundle stays fast. */\nconst DISTANCE_BUDGET = 2_000;\n\nexport function nearestPaths(paths: Iterable<string>, path: string, n = 3): string[] {\n const wanted = path.slice(0, COMPARE_CAP);\n const wantedSegments = wanted.split(\"/\");\n // Shared folders first, then the length difference, which is a lower bound on the edit distance: the full\n // distance is computed for the most promising candidates only.\n const prepared = [...paths]\n .map((candidate) => {\n const segments = candidate.split(\"/\");\n const shared = sharedLeadingSegments(segments, wantedSegments);\n const a = segments.slice(shared).join(\"/\").slice(0, COMPARE_CAP);\n const b = wantedSegments.slice(shared).join(\"/\").slice(0, COMPARE_CAP);\n return { candidate, shared, a, b, bound: Math.abs(a.length - b.length) };\n })\n .sort(\n (x, y) => y.shared - x.shared || x.bound - y.bound || byCodeUnit(x.candidate, y.candidate),\n );\n const ranked = prepared.map((p, i) => ({\n candidate: p.candidate,\n shared: p.shared,\n distance: i < DISTANCE_BUDGET ? levenshtein(p.a, p.b) : p.bound + COMPARE_CAP,\n }));\n ranked.sort(\n (x, y) =>\n y.shared - x.shared || x.distance - y.distance || byCodeUnit(x.candidate, y.candidate),\n );\n return ranked.slice(0, Math.max(0, n)).map((r) => r.candidate);\n}\n"]}
|