@sightmap/sightmap 0.5.2 → 0.7.1

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/dist/browser.js CHANGED
@@ -156,6 +156,17 @@ function splitSegments(path) {
156
156
  const trimmed = path.startsWith("/") ? path.slice(1) : path;
157
157
  return trimmed.split("/");
158
158
  }
159
+ function routeSpecificity(route) {
160
+ if (route === "/") return 1;
161
+ let total = 0;
162
+ for (const seg of route.split("/")) {
163
+ if (seg === "" || seg === "**") continue;
164
+ if (seg === "*") total += 1;
165
+ else if (seg.startsWith(":")) total += 2;
166
+ else total += 3;
167
+ }
168
+ return total;
169
+ }
159
170
  function matchSegs(pattern, url) {
160
171
  if (pattern.length === 0 && url.length === 0) return true;
161
172
  if (pattern.length === 0) return false;
@@ -178,10 +189,13 @@ function matchSegs(pattern, url) {
178
189
  // src/resolver.ts
179
190
  function resolveByUrl(sightmap, url, method) {
180
191
  let matchedView = null;
192
+ let bestScore = -1;
181
193
  for (const v of sightmap.views) {
182
- if (routeMatch(v.route, url)) {
194
+ if (!routeMatch(v.route, url)) continue;
195
+ const score = routeSpecificity(v.route);
196
+ if (score > bestScore) {
197
+ bestScore = score;
183
198
  matchedView = v;
184
- break;
185
199
  }
186
200
  }
187
201
  const components = [];
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/parse.ts","../src/diagnostics.ts","../src/merge.ts","../src/routeMatch.ts","../src/resolver.ts","../src/match.ts","../src/explain.ts"],"sourcesContent":["import yaml from \"js-yaml\";\nimport type { SightmapFragment } from \"./sightmap.js\";\nimport type { Component } from \"./types.js\";\n\nexport interface ParseOptions {\n /** Optional source file path; recorded on the fragment for canonical-order merging. */\n sourceFile?: string;\n}\n\n/**\n * Parse a single sightmap file (YAML string or pre-parsed object).\n * Throws on parse error, missing version, or version mismatch.\n * Normalizes `selector: string` to `selector: [string]` everywhere.\n */\nexport function parse(input: string | object, opts: ParseOptions = {}): SightmapFragment {\n let doc: unknown;\n if (typeof input === \"string\") {\n try {\n doc = yaml.load(input);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`YAML parse error: ${msg}`);\n }\n } else {\n // Shallow-clone caller's object so we don't mutate it.\n doc = { ...input };\n }\n\n if (doc === null || typeof doc !== \"object\" || Array.isArray(doc)) {\n throw new Error(\"Expected sightmap document to be an object at the root\");\n }\n\n const obj = doc as Record<string, unknown>;\n if (obj[\"version\"] === undefined) {\n throw new Error(\"Missing required `version` field\");\n }\n if (obj[\"version\"] !== 1) {\n throw new Error(`Unsupported version: ${String(obj[\"version\"])} (expected 1)`);\n }\n\n // Normalize selectors recursively. Spread each view to avoid mutating\n // shared view objects when the caller passed a pre-parsed input.\n if (Array.isArray(obj[\"components\"])) {\n obj[\"components\"] = (obj[\"components\"] as Component[]).map(normalizeComponent);\n }\n if (Array.isArray(obj[\"views\"])) {\n obj[\"views\"] = (obj[\"views\"] as Array<Record<string, unknown>>).map((v) => {\n const out = { ...v };\n if (Array.isArray(out[\"components\"])) {\n out[\"components\"] = (out[\"components\"] as Component[]).map(normalizeComponent);\n }\n return out;\n });\n }\n\n const fragment = {\n ...obj,\n __brand: \"SightmapFragment\" as const,\n ...(opts.sourceFile !== undefined ? { __sourceFile: opts.sourceFile } : {}),\n } as SightmapFragment;\n return fragment;\n}\n\nfunction normalizeComponent(c: Component): Component {\n // Common migration error: `selectors` (plural) instead of `selector` (singular).\n // The schema only accepts singular `selector`; the singular field already takes\n // a string or string-array, so a plural alias would be redundant. Surface\n // explicitly rather than silently dropping the value.\n if ((c as unknown as Record<string, unknown>)[\"selectors\"] !== undefined && c.selector === undefined) {\n const name = (c as { name?: string }).name ?? \"(unnamed)\";\n throw new Error(\n `Component \"${name}\": use \\`selector\\` (singular), not \\`selectors\\` (plural). ` +\n `\\`selector\\` accepts either a single string or an array of strings.`,\n );\n }\n const sel = c.selector;\n const normalized: Component = {\n ...c,\n selector: typeof sel === \"string\" ? [sel] : (sel as [string, ...string[]]),\n };\n if (Array.isArray(c.children)) {\n normalized.children = c.children.map(normalizeComponent);\n }\n return normalized;\n}\n","export type Severity = \"error\" | \"warning\" | \"info\";\n\nexport interface Diagnostic {\n severity: Severity;\n code: string;\n message: string;\n file?: string;\n path?: string;\n loc?: { line: number; column: number };\n source?: string;\n}\n\n// Stable, kebab-case codes. Renames require an SEP.\nexport const PARSE_ERROR = \"parse-error\";\nexport const SCHEMA_VALIDATION_FAILED = \"schema-validation-failed\";\nexport const UNKNOWN_VERSION = \"unknown-version\";\nexport const MERGE_COLLISION_VIEW = \"merge-collision-view\";\nexport const MERGE_COLLISION_COMPONENT = \"merge-collision-component\";\nexport const DUPLICATE_VIEW_NAME = \"duplicate-view-name\";\nexport const DUPLICATE_ROUTE = \"duplicate-route\";\nexport const ROUTE_SHADOWING = \"route-shadowing\";\nexport const UNKNOWN_SOURCE = \"unknown-source\";\nexport const SELECTOR_SYNTAX = \"selector-syntax\";\n\n// fmt CLI diagnostic codes (sightmap/spec docs/authoring-conventions.md#diagnostic-codes-authoring-side).\nexport const FMT_NOT_CANONICAL = \"fmt.not-canonical\";\nexport const FMT_PARSE_ERROR = \"fmt.parse-error\";\nexport const FMT_SCHEMA_INVALID = \"fmt.schema-invalid\";\n\n// Repo-conventions codes (WI-6). See docs/repo-conventions.md in the spec repo.\nexport const CONVENTION_SEP_FILENAME = \"convention.sep-filename\";\nexport const CONVENTION_FIXTURE_DIRNAME = \"convention.fixture-dirname\";\nexport const CONVENTION_INVALID_SLUG = \"convention.invalid-slug\";\nexport const CONVENTION_UNEXPECTED_FILE = \"convention.unexpected-file\";\n","import type { SightmapFragment, Sightmap } from \"./sightmap.js\";\nimport type { View, Component, Request } from \"./types.js\";\nimport {\n MERGE_COLLISION_VIEW,\n MERGE_COLLISION_COMPONENT,\n type Diagnostic,\n} from \"./diagnostics.js\";\n\n/**\n * Merge fragments into a queryable Sightmap.\n *\n * Canonical order: fragments are sorted by `__sourceFile` (code-point order; locale-independent\n * for cross-environment determinism). Fragments without a `__sourceFile` sort first, and ES2019\n * sort stability preserves their input order. Within each fragment, declaration order is\n * preserved. The merged collection's order = (sourceFile order, then declaration order).\n *\n * Duplicate view names produce `merge-collision-view` warnings; duplicate global component\n * names produce `merge-collision-component`. The first occurrence wins.\n *\n * Returned arrays are fresh, but element objects (View/Component/Request) are shared with\n * the input fragments — callers must not mutate them.\n */\nexport function merge(fragments: SightmapFragment[]): Sightmap {\n const sorted = [...fragments].sort((a, b) => {\n const aFile = a.__sourceFile ?? \"\";\n const bFile = b.__sourceFile ?? \"\";\n return aFile < bFile ? -1 : aFile > bFile ? 1 : 0;\n });\n\n const views: View[] = [];\n const globalComponents: Component[] = [];\n const globalRequests: Request[] = [];\n const fileMemory: { memory: string[]; sourceFile: string }[] = [];\n const diagnostics: Diagnostic[] = [];\n\n const seenViewNames = new Map<string, string>(); // name → sourceFile\n const seenComponentNames = new Map<string, string>();\n\n for (const f of sorted) {\n const file = f.__sourceFile ?? \"<unknown>\";\n\n for (const v of f.views ?? []) {\n const prev = seenViewNames.get(v.name);\n if (prev !== undefined) {\n diagnostics.push({\n severity: \"warning\",\n code: MERGE_COLLISION_VIEW,\n message: `View name \"${v.name}\" defined in both \"${prev}\" and \"${file}\"; first occurrence wins.`,\n file,\n });\n } else {\n seenViewNames.set(v.name, file);\n }\n views.push(v); // keep all views; first-match-wins is enforced at resolve time\n }\n\n for (const c of f.components ?? []) {\n const prev = seenComponentNames.get(c.name);\n if (prev !== undefined) {\n diagnostics.push({\n severity: \"warning\",\n code: MERGE_COLLISION_COMPONENT,\n message: `Component name \"${c.name}\" defined in both \"${prev}\" and \"${file}\"; first occurrence wins.`,\n file,\n });\n } else {\n seenComponentNames.set(c.name, file);\n }\n globalComponents.push(c);\n }\n\n for (const r of f.requests ?? []) {\n globalRequests.push(r);\n }\n\n if (Array.isArray(f.memory) && f.memory.length > 0) {\n fileMemory.push({ memory: [...f.memory], sourceFile: file });\n }\n }\n\n return {\n version: 1,\n views,\n globalComponents,\n globalRequests,\n fileMemory,\n diagnostics,\n __brand: \"Sightmap\",\n };\n}\n","/**\n * Canonicalize a URL or pathname for matching.\n * - Accepts absolute URL or pathname.\n * - Strips scheme, host, query string, and fragment.\n * - Normalizes trailing slashes (except for the root \"/\").\n */\nexport function canonicalizeUrl(input: string): string {\n let s = input;\n // Strip absolute prefix.\n const protoMatch = /^[a-z][a-z0-9+.-]*:\\/\\/[^/]*/i.exec(s);\n if (protoMatch) {\n s = s.slice(protoMatch[0].length) || \"/\";\n }\n // Strip fragment.\n const hash = s.indexOf(\"#\");\n if (hash !== -1) s = s.slice(0, hash);\n // Strip query.\n const q = s.indexOf(\"?\");\n if (q !== -1) s = s.slice(0, q);\n // Trailing slash.\n if (s.length > 1 && s.endsWith(\"/\")) s = s.slice(0, -1);\n return s;\n}\n\n/**\n * Test whether a glob route pattern matches a URL pathname.\n * Pattern syntax (per spec):\n * - Literal segments match themselves\n * - \"*\" matches exactly one path segment\n * - \"**\" matches any depth of segments\n * - \":param\" segments normalize to \"*\"\n * - Matching is case-sensitive\n * - Trailing slashes ignored\n */\nexport function routeMatch(pattern: string, url: string): boolean {\n const p = normalizePattern(pattern);\n const u = canonicalizeUrl(url);\n const patternSegs = splitSegments(p);\n const urlSegs = splitSegments(u);\n return matchSegs(patternSegs, urlSegs);\n}\n\nfunction normalizePattern(p: string): string {\n // Replace \":param\" segments with \"*\"\n let s = p\n .split(\"/\")\n .map((seg) => (seg.startsWith(\":\") ? \"*\" : seg))\n .join(\"/\");\n if (s.length > 1 && s.endsWith(\"/\")) s = s.slice(0, -1);\n return s;\n}\n\nfunction splitSegments(path: string): string[] {\n if (path === \"/\" || path === \"\") return [];\n const trimmed = path.startsWith(\"/\") ? path.slice(1) : path;\n return trimmed.split(\"/\");\n}\n\nfunction matchSegs(pattern: string[], url: string[]): boolean {\n if (pattern.length === 0 && url.length === 0) return true;\n if (pattern.length === 0) return false;\n\n const head = pattern[0]!;\n const rest = pattern.slice(1);\n\n if (head === \"**\") {\n // Match zero or more segments.\n if (rest.length === 0) return true;\n for (let i = 0; i <= url.length; i++) {\n if (matchSegs(rest, url.slice(i))) return true;\n }\n return false;\n }\n\n if (url.length === 0) return false;\n if (head === \"*\" || head === url[0]) {\n return matchSegs(rest, url.slice(1));\n }\n return false;\n}\n","import type {\n Sightmap,\n MatchResult,\n ExplainHit,\n ResolvedView,\n ResolvedComponent,\n ResolvedRequest,\n} from \"./sightmap.js\";\nimport type { View, Component, Request } from \"./types.js\";\nimport { routeMatch } from \"./routeMatch.js\";\n\nexport function resolveByUrl(\n sightmap: Sightmap,\n url: string,\n method?: string,\n): MatchResult {\n // First-match-wins on views.\n let matchedView: View | null = null;\n for (const v of sightmap.views) {\n if (routeMatch(v.route, url)) {\n matchedView = v;\n break;\n }\n }\n\n const components: ResolvedComponent[] = [];\n for (const c of sightmap.globalComponents) {\n components.push(...flattenComponent(c, [], \"global\", undefined));\n }\n if (matchedView !== null && Array.isArray(matchedView.components)) {\n for (const c of matchedView.components) {\n components.push(...flattenComponent(c, [], \"view-scoped\", matchedView.name));\n }\n }\n\n const requests: ResolvedRequest[] = [];\n const requestPool: Request[] = [\n ...sightmap.globalRequests,\n ...((matchedView?.requests ?? [])),\n ];\n for (const req of requestPool) {\n if (!routeMatch(req.route, url)) continue;\n if (method !== undefined && req.method !== undefined && req.method !== method) continue;\n requests.push(toResolvedRequest(req));\n }\n\n const memory: string[] = [];\n for (const fm of sightmap.fileMemory) memory.push(...fm.memory);\n if (matchedView?.memory) memory.push(...matchedView.memory);\n\n return {\n view: matchedView !== null ? toResolvedView(matchedView) : null,\n components,\n requests,\n memory,\n };\n}\n\nexport function resolveByName(sightmap: Sightmap, name: string): ExplainHit[] {\n const hits: ExplainHit[] = [];\n for (const v of sightmap.views) {\n if (v.name === name) hits.push({ type: \"view\", matchedAs: \"name\", entry: toResolvedView(v) });\n }\n for (const c of sightmap.globalComponents) {\n for (const rc of flattenComponent(c, [], \"global\", undefined)) {\n if (rc.name === name) hits.push({ type: \"component\", matchedAs: \"name\", entry: rc });\n }\n }\n for (const v of sightmap.views) {\n for (const c of v.components ?? []) {\n for (const rc of flattenComponent(c, [], \"view-scoped\", v.name)) {\n if (rc.name === name) hits.push({ type: \"component\", matchedAs: \"name\", entry: rc });\n }\n }\n }\n for (const r of sightmap.globalRequests) {\n if (r.name === name) hits.push({ type: \"request\", matchedAs: \"name\", entry: toResolvedRequest(r) });\n }\n for (const v of sightmap.views) {\n for (const r of v.requests ?? []) {\n if (r.name === name) hits.push({ type: \"request\", matchedAs: \"name\", entry: toResolvedRequest(r) });\n }\n }\n return hits;\n}\n\nexport function resolveBySourcePath(sightmap: Sightmap, path: string): ExplainHit[] {\n const hits: ExplainHit[] = [];\n for (const v of sightmap.views) {\n if (v.source === path) hits.push({ type: \"view\", matchedAs: \"path\", entry: toResolvedView(v) });\n }\n for (const c of sightmap.globalComponents) {\n for (const rc of flattenComponent(c, [], \"global\", undefined)) {\n if (rc.source === path) hits.push({ type: \"component\", matchedAs: \"path\", entry: rc });\n }\n }\n for (const v of sightmap.views) {\n for (const c of v.components ?? []) {\n for (const rc of flattenComponent(c, [], \"view-scoped\", v.name)) {\n if (rc.source === path) hits.push({ type: \"component\", matchedAs: \"path\", entry: rc });\n }\n }\n }\n for (const r of sightmap.globalRequests) {\n if (r.source === path) hits.push({ type: \"request\", matchedAs: \"path\", entry: toResolvedRequest(r) });\n }\n return hits;\n}\n\nfunction flattenComponent(\n c: Component,\n parentChain: string[],\n scope: \"global\" | \"view-scoped\",\n scopedToView: string | undefined,\n): ResolvedComponent[] {\n const out: ResolvedComponent[] = [];\n const selector = Array.isArray(c.selector) ? c.selector : [c.selector as unknown as string];\n out.push({\n name: c.name,\n selector,\n ...(c.source !== undefined ? { source: c.source } : {}),\n ...(c.description !== undefined ? { description: c.description } : {}),\n memory: c.memory ? [...c.memory] : [],\n parentChain: [...parentChain],\n scope,\n ...(scopedToView !== undefined ? { scopedToView } : {}),\n definedIn: { file: \"<unknown>\" },\n });\n for (const child of c.children ?? []) {\n out.push(...flattenComponent(child, [...parentChain, c.name], scope, scopedToView));\n }\n return out;\n}\n\nfunction toResolvedView(v: View): ResolvedView {\n return {\n name: v.name,\n route: v.route,\n ...(v.source !== undefined ? { source: v.source } : {}),\n ...(v.description !== undefined ? { description: v.description } : {}),\n memory: v.memory ? [...v.memory] : [],\n definedIn: { file: \"<unknown>\" },\n };\n}\n\nfunction toResolvedRequest(r: Request): ResolvedRequest {\n return {\n name: r.name,\n route: r.route,\n ...(r.method !== undefined ? { method: r.method } : {}),\n ...(r.source !== undefined ? { source: r.source } : {}),\n ...(r.description !== undefined ? { description: r.description } : {}),\n ...(r.request !== undefined ? { request: { fields: r.request.fields ?? [] } } : {}),\n ...(r.response !== undefined ? { response: { fields: r.response.fields ?? [] } } : {}),\n ...(r.headers !== undefined ? { headers: r.headers } : {}),\n memory: r.memory ? [...r.memory] : [],\n definedIn: { file: \"<unknown>\" },\n };\n}\n","import type { Sightmap, MatchResult } from \"./sightmap.js\";\nimport { resolveByUrl } from \"./resolver.js\";\n\nexport interface MatchOptions {\n url: string;\n method?: string;\n}\n\nexport function match(sightmap: Sightmap, opts: MatchOptions): MatchResult {\n return resolveByUrl(sightmap, opts.url, opts.method);\n}\n","import type { Sightmap, ExplainResult, ExplainHit, ExplainMatchedAs } from \"./sightmap.js\";\nimport { resolveByName, resolveBySourcePath } from \"./resolver.js\";\n\nexport interface ExplainOptions {\n by?: \"name\" | \"path\";\n type?: \"view\" | \"component\" | \"request\";\n}\n\nexport function explain(\n sightmap: Sightmap,\n query: string,\n opts: ExplainOptions = {},\n): ExplainResult {\n const byName = opts.by === \"path\" ? [] : resolveByName(sightmap, query);\n const byPath = opts.by === \"name\" ? [] : resolveBySourcePath(sightmap, query);\n\n // Merge: when an entry appears in both lookups, label as \"name-and-path\".\n // Use entry identity (type + name) as the dedup key.\n const key = (h: ExplainHit): string => `${h.type}:${h.entry.name}`;\n const namedKeys = new Set(byName.map(key));\n const pathKeys = new Set(byPath.map(key));\n\n const merged: ExplainHit[] = [];\n for (const h of byName) {\n if (pathKeys.has(key(h))) {\n merged.push(withMatchedAs(h, \"name-and-path\"));\n } else {\n merged.push(h);\n }\n }\n for (const h of byPath) {\n if (!namedKeys.has(key(h))) {\n merged.push(h);\n }\n // (entries already in both have been pushed above with \"name-and-path\".)\n }\n\n const filtered =\n opts.type !== undefined ? merged.filter((h) => h.type === opts.type) : merged;\n return { query, hits: filtered };\n}\n\nfunction withMatchedAs(h: ExplainHit, matchedAs: ExplainMatchedAs): ExplainHit {\n switch (h.type) {\n case \"view\":\n return { type: \"view\", matchedAs, entry: h.entry };\n case \"component\":\n return { type: \"component\", matchedAs, entry: h.entry };\n case \"request\":\n return { type: \"request\", matchedAs, entry: h.entry };\n }\n}\n"],"mappings":";AAAA,OAAO,UAAU;AAcV,SAAS,MAAM,OAAwB,OAAqB,CAAC,GAAqB;AACvF,MAAI;AACJ,MAAI,OAAO,UAAU,UAAU;AAC7B,QAAI;AACF,YAAM,KAAK,KAAK,KAAK;AAAA,IACvB,SAAS,KAAK;AACZ,YAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC3D,YAAM,IAAI,MAAM,qBAAqB,GAAG,EAAE;AAAA,IAC5C;AAAA,EACF,OAAO;AAEL,UAAM,EAAE,GAAG,MAAM;AAAA,EACnB;AAEA,MAAI,QAAQ,QAAQ,OAAO,QAAQ,YAAY,MAAM,QAAQ,GAAG,GAAG;AACjE,UAAM,IAAI,MAAM,wDAAwD;AAAA,EAC1E;AAEA,QAAM,MAAM;AACZ,MAAI,IAAI,SAAS,MAAM,QAAW;AAChC,UAAM,IAAI,MAAM,kCAAkC;AAAA,EACpD;AACA,MAAI,IAAI,SAAS,MAAM,GAAG;AACxB,UAAM,IAAI,MAAM,wBAAwB,OAAO,IAAI,SAAS,CAAC,CAAC,eAAe;AAAA,EAC/E;AAIA,MAAI,MAAM,QAAQ,IAAI,YAAY,CAAC,GAAG;AACpC,QAAI,YAAY,IAAK,IAAI,YAAY,EAAkB,IAAI,kBAAkB;AAAA,EAC/E;AACA,MAAI,MAAM,QAAQ,IAAI,OAAO,CAAC,GAAG;AAC/B,QAAI,OAAO,IAAK,IAAI,OAAO,EAAqC,IAAI,CAAC,MAAM;AACzE,YAAM,MAAM,EAAE,GAAG,EAAE;AACnB,UAAI,MAAM,QAAQ,IAAI,YAAY,CAAC,GAAG;AACpC,YAAI,YAAY,IAAK,IAAI,YAAY,EAAkB,IAAI,kBAAkB;AAAA,MAC/E;AACA,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAEA,QAAM,WAAW;AAAA,IACf,GAAG;AAAA,IACH,SAAS;AAAA,IACT,GAAI,KAAK,eAAe,SAAY,EAAE,cAAc,KAAK,WAAW,IAAI,CAAC;AAAA,EAC3E;AACA,SAAO;AACT;AAEA,SAAS,mBAAmB,GAAyB;AAKnD,MAAK,EAAyC,WAAW,MAAM,UAAa,EAAE,aAAa,QAAW;AACpG,UAAM,OAAQ,EAAwB,QAAQ;AAC9C,UAAM,IAAI;AAAA,MACR,cAAc,IAAI;AAAA,IAEpB;AAAA,EACF;AACA,QAAM,MAAM,EAAE;AACd,QAAM,aAAwB;AAAA,IAC5B,GAAG;AAAA,IACH,UAAU,OAAO,QAAQ,WAAW,CAAC,GAAG,IAAK;AAAA,EAC/C;AACA,MAAI,MAAM,QAAQ,EAAE,QAAQ,GAAG;AAC7B,eAAW,WAAW,EAAE,SAAS,IAAI,kBAAkB;AAAA,EACzD;AACA,SAAO;AACT;;;ACpEO,IAAM,uBAAuB;AAC7B,IAAM,4BAA4B;;;ACKlC,SAAS,MAAM,WAAyC;AAC7D,QAAM,SAAS,CAAC,GAAG,SAAS,EAAE,KAAK,CAAC,GAAG,MAAM;AAC3C,UAAM,QAAQ,EAAE,gBAAgB;AAChC,UAAM,QAAQ,EAAE,gBAAgB;AAChC,WAAO,QAAQ,QAAQ,KAAK,QAAQ,QAAQ,IAAI;AAAA,EAClD,CAAC;AAED,QAAM,QAAgB,CAAC;AACvB,QAAM,mBAAgC,CAAC;AACvC,QAAM,iBAA4B,CAAC;AACnC,QAAM,aAAyD,CAAC;AAChE,QAAM,cAA4B,CAAC;AAEnC,QAAM,gBAAgB,oBAAI,IAAoB;AAC9C,QAAM,qBAAqB,oBAAI,IAAoB;AAEnD,aAAW,KAAK,QAAQ;AACtB,UAAM,OAAO,EAAE,gBAAgB;AAE/B,eAAW,KAAK,EAAE,SAAS,CAAC,GAAG;AAC7B,YAAM,OAAO,cAAc,IAAI,EAAE,IAAI;AACrC,UAAI,SAAS,QAAW;AACtB,oBAAY,KAAK;AAAA,UACf,UAAU;AAAA,UACV,MAAM;AAAA,UACN,SAAS,cAAc,EAAE,IAAI,sBAAsB,IAAI,UAAU,IAAI;AAAA,UACrE;AAAA,QACF,CAAC;AAAA,MACH,OAAO;AACL,sBAAc,IAAI,EAAE,MAAM,IAAI;AAAA,MAChC;AACA,YAAM,KAAK,CAAC;AAAA,IACd;AAEA,eAAW,KAAK,EAAE,cAAc,CAAC,GAAG;AAClC,YAAM,OAAO,mBAAmB,IAAI,EAAE,IAAI;AAC1C,UAAI,SAAS,QAAW;AACtB,oBAAY,KAAK;AAAA,UACf,UAAU;AAAA,UACV,MAAM;AAAA,UACN,SAAS,mBAAmB,EAAE,IAAI,sBAAsB,IAAI,UAAU,IAAI;AAAA,UAC1E;AAAA,QACF,CAAC;AAAA,MACH,OAAO;AACL,2BAAmB,IAAI,EAAE,MAAM,IAAI;AAAA,MACrC;AACA,uBAAiB,KAAK,CAAC;AAAA,IACzB;AAEA,eAAW,KAAK,EAAE,YAAY,CAAC,GAAG;AAChC,qBAAe,KAAK,CAAC;AAAA,IACvB;AAEA,QAAI,MAAM,QAAQ,EAAE,MAAM,KAAK,EAAE,OAAO,SAAS,GAAG;AAClD,iBAAW,KAAK,EAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,KAAK,CAAC;AAAA,IAC7D;AAAA,EACF;AAEA,SAAO;AAAA,IACL,SAAS;AAAA,IACT;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS;AAAA,EACX;AACF;;;ACnFO,SAAS,gBAAgB,OAAuB;AACrD,MAAI,IAAI;AAER,QAAM,aAAa,gCAAgC,KAAK,CAAC;AACzD,MAAI,YAAY;AACd,QAAI,EAAE,MAAM,WAAW,CAAC,EAAE,MAAM,KAAK;AAAA,EACvC;AAEA,QAAM,OAAO,EAAE,QAAQ,GAAG;AAC1B,MAAI,SAAS,GAAI,KAAI,EAAE,MAAM,GAAG,IAAI;AAEpC,QAAM,IAAI,EAAE,QAAQ,GAAG;AACvB,MAAI,MAAM,GAAI,KAAI,EAAE,MAAM,GAAG,CAAC;AAE9B,MAAI,EAAE,SAAS,KAAK,EAAE,SAAS,GAAG,EAAG,KAAI,EAAE,MAAM,GAAG,EAAE;AACtD,SAAO;AACT;AAYO,SAAS,WAAW,SAAiB,KAAsB;AAChE,QAAM,IAAI,iBAAiB,OAAO;AAClC,QAAM,IAAI,gBAAgB,GAAG;AAC7B,QAAM,cAAc,cAAc,CAAC;AACnC,QAAM,UAAU,cAAc,CAAC;AAC/B,SAAO,UAAU,aAAa,OAAO;AACvC;AAEA,SAAS,iBAAiB,GAAmB;AAE3C,MAAI,IAAI,EACL,MAAM,GAAG,EACT,IAAI,CAAC,QAAS,IAAI,WAAW,GAAG,IAAI,MAAM,GAAI,EAC9C,KAAK,GAAG;AACX,MAAI,EAAE,SAAS,KAAK,EAAE,SAAS,GAAG,EAAG,KAAI,EAAE,MAAM,GAAG,EAAE;AACtD,SAAO;AACT;AAEA,SAAS,cAAc,MAAwB;AAC7C,MAAI,SAAS,OAAO,SAAS,GAAI,QAAO,CAAC;AACzC,QAAM,UAAU,KAAK,WAAW,GAAG,IAAI,KAAK,MAAM,CAAC,IAAI;AACvD,SAAO,QAAQ,MAAM,GAAG;AAC1B;AAEA,SAAS,UAAU,SAAmB,KAAwB;AAC5D,MAAI,QAAQ,WAAW,KAAK,IAAI,WAAW,EAAG,QAAO;AACrD,MAAI,QAAQ,WAAW,EAAG,QAAO;AAEjC,QAAM,OAAO,QAAQ,CAAC;AACtB,QAAM,OAAO,QAAQ,MAAM,CAAC;AAE5B,MAAI,SAAS,MAAM;AAEjB,QAAI,KAAK,WAAW,EAAG,QAAO;AAC9B,aAAS,IAAI,GAAG,KAAK,IAAI,QAAQ,KAAK;AACpC,UAAI,UAAU,MAAM,IAAI,MAAM,CAAC,CAAC,EAAG,QAAO;AAAA,IAC5C;AACA,WAAO;AAAA,EACT;AAEA,MAAI,IAAI,WAAW,EAAG,QAAO;AAC7B,MAAI,SAAS,OAAO,SAAS,IAAI,CAAC,GAAG;AACnC,WAAO,UAAU,MAAM,IAAI,MAAM,CAAC,CAAC;AAAA,EACrC;AACA,SAAO;AACT;;;ACpEO,SAAS,aACd,UACA,KACA,QACa;AAEb,MAAI,cAA2B;AAC/B,aAAW,KAAK,SAAS,OAAO;AAC9B,QAAI,WAAW,EAAE,OAAO,GAAG,GAAG;AAC5B,oBAAc;AACd;AAAA,IACF;AAAA,EACF;AAEA,QAAM,aAAkC,CAAC;AACzC,aAAW,KAAK,SAAS,kBAAkB;AACzC,eAAW,KAAK,GAAG,iBAAiB,GAAG,CAAC,GAAG,UAAU,MAAS,CAAC;AAAA,EACjE;AACA,MAAI,gBAAgB,QAAQ,MAAM,QAAQ,YAAY,UAAU,GAAG;AACjE,eAAW,KAAK,YAAY,YAAY;AACtC,iBAAW,KAAK,GAAG,iBAAiB,GAAG,CAAC,GAAG,eAAe,YAAY,IAAI,CAAC;AAAA,IAC7E;AAAA,EACF;AAEA,QAAM,WAA8B,CAAC;AACrC,QAAM,cAAyB;AAAA,IAC7B,GAAG,SAAS;AAAA,IACZ,GAAK,aAAa,YAAY,CAAC;AAAA,EACjC;AACA,aAAW,OAAO,aAAa;AAC7B,QAAI,CAAC,WAAW,IAAI,OAAO,GAAG,EAAG;AACjC,QAAI,WAAW,UAAa,IAAI,WAAW,UAAa,IAAI,WAAW,OAAQ;AAC/E,aAAS,KAAK,kBAAkB,GAAG,CAAC;AAAA,EACtC;AAEA,QAAM,SAAmB,CAAC;AAC1B,aAAW,MAAM,SAAS,WAAY,QAAO,KAAK,GAAG,GAAG,MAAM;AAC9D,MAAI,aAAa,OAAQ,QAAO,KAAK,GAAG,YAAY,MAAM;AAE1D,SAAO;AAAA,IACL,MAAM,gBAAgB,OAAO,eAAe,WAAW,IAAI;AAAA,IAC3D;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAEO,SAAS,cAAc,UAAoB,MAA4B;AAC5E,QAAM,OAAqB,CAAC;AAC5B,aAAW,KAAK,SAAS,OAAO;AAC9B,QAAI,EAAE,SAAS,KAAM,MAAK,KAAK,EAAE,MAAM,QAAQ,WAAW,QAAQ,OAAO,eAAe,CAAC,EAAE,CAAC;AAAA,EAC9F;AACA,aAAW,KAAK,SAAS,kBAAkB;AACzC,eAAW,MAAM,iBAAiB,GAAG,CAAC,GAAG,UAAU,MAAS,GAAG;AAC7D,UAAI,GAAG,SAAS,KAAM,MAAK,KAAK,EAAE,MAAM,aAAa,WAAW,QAAQ,OAAO,GAAG,CAAC;AAAA,IACrF;AAAA,EACF;AACA,aAAW,KAAK,SAAS,OAAO;AAC9B,eAAW,KAAK,EAAE,cAAc,CAAC,GAAG;AAClC,iBAAW,MAAM,iBAAiB,GAAG,CAAC,GAAG,eAAe,EAAE,IAAI,GAAG;AAC/D,YAAI,GAAG,SAAS,KAAM,MAAK,KAAK,EAAE,MAAM,aAAa,WAAW,QAAQ,OAAO,GAAG,CAAC;AAAA,MACrF;AAAA,IACF;AAAA,EACF;AACA,aAAW,KAAK,SAAS,gBAAgB;AACvC,QAAI,EAAE,SAAS,KAAM,MAAK,KAAK,EAAE,MAAM,WAAW,WAAW,QAAQ,OAAO,kBAAkB,CAAC,EAAE,CAAC;AAAA,EACpG;AACA,aAAW,KAAK,SAAS,OAAO;AAC9B,eAAW,KAAK,EAAE,YAAY,CAAC,GAAG;AAChC,UAAI,EAAE,SAAS,KAAM,MAAK,KAAK,EAAE,MAAM,WAAW,WAAW,QAAQ,OAAO,kBAAkB,CAAC,EAAE,CAAC;AAAA,IACpG;AAAA,EACF;AACA,SAAO;AACT;AAEO,SAAS,oBAAoB,UAAoB,MAA4B;AAClF,QAAM,OAAqB,CAAC;AAC5B,aAAW,KAAK,SAAS,OAAO;AAC9B,QAAI,EAAE,WAAW,KAAM,MAAK,KAAK,EAAE,MAAM,QAAQ,WAAW,QAAQ,OAAO,eAAe,CAAC,EAAE,CAAC;AAAA,EAChG;AACA,aAAW,KAAK,SAAS,kBAAkB;AACzC,eAAW,MAAM,iBAAiB,GAAG,CAAC,GAAG,UAAU,MAAS,GAAG;AAC7D,UAAI,GAAG,WAAW,KAAM,MAAK,KAAK,EAAE,MAAM,aAAa,WAAW,QAAQ,OAAO,GAAG,CAAC;AAAA,IACvF;AAAA,EACF;AACA,aAAW,KAAK,SAAS,OAAO;AAC9B,eAAW,KAAK,EAAE,cAAc,CAAC,GAAG;AAClC,iBAAW,MAAM,iBAAiB,GAAG,CAAC,GAAG,eAAe,EAAE,IAAI,GAAG;AAC/D,YAAI,GAAG,WAAW,KAAM,MAAK,KAAK,EAAE,MAAM,aAAa,WAAW,QAAQ,OAAO,GAAG,CAAC;AAAA,MACvF;AAAA,IACF;AAAA,EACF;AACA,aAAW,KAAK,SAAS,gBAAgB;AACvC,QAAI,EAAE,WAAW,KAAM,MAAK,KAAK,EAAE,MAAM,WAAW,WAAW,QAAQ,OAAO,kBAAkB,CAAC,EAAE,CAAC;AAAA,EACtG;AACA,SAAO;AACT;AAEA,SAAS,iBACP,GACA,aACA,OACA,cACqB;AACrB,QAAM,MAA2B,CAAC;AAClC,QAAM,WAAW,MAAM,QAAQ,EAAE,QAAQ,IAAI,EAAE,WAAW,CAAC,EAAE,QAA6B;AAC1F,MAAI,KAAK;AAAA,IACP,MAAM,EAAE;AAAA,IACR;AAAA,IACA,GAAI,EAAE,WAAW,SAAY,EAAE,QAAQ,EAAE,OAAO,IAAI,CAAC;AAAA,IACrD,GAAI,EAAE,gBAAgB,SAAY,EAAE,aAAa,EAAE,YAAY,IAAI,CAAC;AAAA,IACpE,QAAQ,EAAE,SAAS,CAAC,GAAG,EAAE,MAAM,IAAI,CAAC;AAAA,IACpC,aAAa,CAAC,GAAG,WAAW;AAAA,IAC5B;AAAA,IACA,GAAI,iBAAiB,SAAY,EAAE,aAAa,IAAI,CAAC;AAAA,IACrD,WAAW,EAAE,MAAM,YAAY;AAAA,EACjC,CAAC;AACD,aAAW,SAAS,EAAE,YAAY,CAAC,GAAG;AACpC,QAAI,KAAK,GAAG,iBAAiB,OAAO,CAAC,GAAG,aAAa,EAAE,IAAI,GAAG,OAAO,YAAY,CAAC;AAAA,EACpF;AACA,SAAO;AACT;AAEA,SAAS,eAAe,GAAuB;AAC7C,SAAO;AAAA,IACL,MAAM,EAAE;AAAA,IACR,OAAO,EAAE;AAAA,IACT,GAAI,EAAE,WAAW,SAAY,EAAE,QAAQ,EAAE,OAAO,IAAI,CAAC;AAAA,IACrD,GAAI,EAAE,gBAAgB,SAAY,EAAE,aAAa,EAAE,YAAY,IAAI,CAAC;AAAA,IACpE,QAAQ,EAAE,SAAS,CAAC,GAAG,EAAE,MAAM,IAAI,CAAC;AAAA,IACpC,WAAW,EAAE,MAAM,YAAY;AAAA,EACjC;AACF;AAEA,SAAS,kBAAkB,GAA6B;AACtD,SAAO;AAAA,IACL,MAAM,EAAE;AAAA,IACR,OAAO,EAAE;AAAA,IACT,GAAI,EAAE,WAAW,SAAY,EAAE,QAAQ,EAAE,OAAO,IAAI,CAAC;AAAA,IACrD,GAAI,EAAE,WAAW,SAAY,EAAE,QAAQ,EAAE,OAAO,IAAI,CAAC;AAAA,IACrD,GAAI,EAAE,gBAAgB,SAAY,EAAE,aAAa,EAAE,YAAY,IAAI,CAAC;AAAA,IACpE,GAAI,EAAE,YAAY,SAAY,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,UAAU,CAAC,EAAE,EAAE,IAAI,CAAC;AAAA,IACjF,GAAI,EAAE,aAAa,SAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,UAAU,CAAC,EAAE,EAAE,IAAI,CAAC;AAAA,IACpF,GAAI,EAAE,YAAY,SAAY,EAAE,SAAS,EAAE,QAAQ,IAAI,CAAC;AAAA,IACxD,QAAQ,EAAE,SAAS,CAAC,GAAG,EAAE,MAAM,IAAI,CAAC;AAAA,IACpC,WAAW,EAAE,MAAM,YAAY;AAAA,EACjC;AACF;;;ACtJO,SAAS,MAAM,UAAoB,MAAiC;AACzE,SAAO,aAAa,UAAU,KAAK,KAAK,KAAK,MAAM;AACrD;;;ACFO,SAAS,QACd,UACA,OACA,OAAuB,CAAC,GACT;AACf,QAAM,SAAS,KAAK,OAAO,SAAS,CAAC,IAAI,cAAc,UAAU,KAAK;AACtE,QAAM,SAAS,KAAK,OAAO,SAAS,CAAC,IAAI,oBAAoB,UAAU,KAAK;AAI5E,QAAM,MAAM,CAAC,MAA0B,GAAG,EAAE,IAAI,IAAI,EAAE,MAAM,IAAI;AAChE,QAAM,YAAY,IAAI,IAAI,OAAO,IAAI,GAAG,CAAC;AACzC,QAAM,WAAW,IAAI,IAAI,OAAO,IAAI,GAAG,CAAC;AAExC,QAAM,SAAuB,CAAC;AAC9B,aAAW,KAAK,QAAQ;AACtB,QAAI,SAAS,IAAI,IAAI,CAAC,CAAC,GAAG;AACxB,aAAO,KAAK,cAAc,GAAG,eAAe,CAAC;AAAA,IAC/C,OAAO;AACL,aAAO,KAAK,CAAC;AAAA,IACf;AAAA,EACF;AACA,aAAW,KAAK,QAAQ;AACtB,QAAI,CAAC,UAAU,IAAI,IAAI,CAAC,CAAC,GAAG;AAC1B,aAAO,KAAK,CAAC;AAAA,IACf;AAAA,EAEF;AAEA,QAAM,WACJ,KAAK,SAAS,SAAY,OAAO,OAAO,CAAC,MAAM,EAAE,SAAS,KAAK,IAAI,IAAI;AACzE,SAAO,EAAE,OAAO,MAAM,SAAS;AACjC;AAEA,SAAS,cAAc,GAAe,WAAyC;AAC7E,UAAQ,EAAE,MAAM;AAAA,IACd,KAAK;AACH,aAAO,EAAE,MAAM,QAAQ,WAAW,OAAO,EAAE,MAAM;AAAA,IACnD,KAAK;AACH,aAAO,EAAE,MAAM,aAAa,WAAW,OAAO,EAAE,MAAM;AAAA,IACxD,KAAK;AACH,aAAO,EAAE,MAAM,WAAW,WAAW,OAAO,EAAE,MAAM;AAAA,EACxD;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/parse.ts","../src/diagnostics.ts","../src/merge.ts","../src/routeMatch.ts","../src/resolver.ts","../src/match.ts","../src/explain.ts"],"sourcesContent":["import yaml from \"js-yaml\";\nimport type { SightmapFragment } from \"./sightmap.js\";\nimport type { Component } from \"./types.js\";\n\nexport interface ParseOptions {\n /** Optional source file path; recorded on the fragment for canonical-order merging. */\n sourceFile?: string;\n}\n\n/**\n * Parse a single sightmap file (YAML string or pre-parsed object).\n * Throws on parse error, missing version, or version mismatch.\n * Normalizes `selector: string` to `selector: [string]` everywhere.\n */\nexport function parse(input: string | object, opts: ParseOptions = {}): SightmapFragment {\n let doc: unknown;\n if (typeof input === \"string\") {\n try {\n doc = yaml.load(input);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`YAML parse error: ${msg}`);\n }\n } else {\n // Shallow-clone caller's object so we don't mutate it.\n doc = { ...input };\n }\n\n if (doc === null || typeof doc !== \"object\" || Array.isArray(doc)) {\n throw new Error(\"Expected sightmap document to be an object at the root\");\n }\n\n const obj = doc as Record<string, unknown>;\n if (obj[\"version\"] === undefined) {\n throw new Error(\"Missing required `version` field\");\n }\n if (obj[\"version\"] !== 1) {\n throw new Error(`Unsupported version: ${String(obj[\"version\"])} (expected 1)`);\n }\n\n // Normalize selectors recursively. Spread each view to avoid mutating\n // shared view objects when the caller passed a pre-parsed input.\n if (Array.isArray(obj[\"components\"])) {\n obj[\"components\"] = (obj[\"components\"] as Component[]).map(normalizeComponent);\n }\n if (Array.isArray(obj[\"views\"])) {\n obj[\"views\"] = (obj[\"views\"] as Array<Record<string, unknown>>).map((v) => {\n const out = { ...v };\n if (Array.isArray(out[\"components\"])) {\n out[\"components\"] = (out[\"components\"] as Component[]).map(normalizeComponent);\n }\n return out;\n });\n }\n\n const fragment = {\n ...obj,\n __brand: \"SightmapFragment\" as const,\n ...(opts.sourceFile !== undefined ? { __sourceFile: opts.sourceFile } : {}),\n } as SightmapFragment;\n return fragment;\n}\n\nfunction normalizeComponent(c: Component): Component {\n // Common migration error: `selectors` (plural) instead of `selector` (singular).\n // The schema only accepts singular `selector`; the singular field already takes\n // a string or string-array, so a plural alias would be redundant. Surface\n // explicitly rather than silently dropping the value.\n if ((c as unknown as Record<string, unknown>)[\"selectors\"] !== undefined && c.selector === undefined) {\n const name = (c as { name?: string }).name ?? \"(unnamed)\";\n throw new Error(\n `Component \"${name}\": use \\`selector\\` (singular), not \\`selectors\\` (plural). ` +\n `\\`selector\\` accepts either a single string or an array of strings.`,\n );\n }\n const sel = c.selector;\n const normalized: Component = {\n ...c,\n selector: typeof sel === \"string\" ? [sel] : (sel as [string, ...string[]]),\n };\n if (Array.isArray(c.children)) {\n normalized.children = c.children.map(normalizeComponent);\n }\n return normalized;\n}\n","export type Severity = \"error\" | \"warning\" | \"info\";\n\nexport interface Diagnostic {\n severity: Severity;\n code: string;\n message: string;\n file?: string;\n path?: string;\n loc?: { line: number; column: number };\n source?: string;\n}\n\n// Stable, kebab-case codes. Renames require an SEP.\nexport const PARSE_ERROR = \"parse-error\";\nexport const SCHEMA_VALIDATION_FAILED = \"schema-validation-failed\";\nexport const UNKNOWN_VERSION = \"unknown-version\";\nexport const MERGE_COLLISION_VIEW = \"merge-collision-view\";\nexport const MERGE_COLLISION_COMPONENT = \"merge-collision-component\";\nexport const DUPLICATE_VIEW_NAME = \"duplicate-view-name\";\nexport const DUPLICATE_ROUTE = \"duplicate-route\";\nexport const ROUTE_SHADOWING = \"route-shadowing\";\nexport const UNKNOWN_SOURCE = \"unknown-source\";\nexport const SELECTOR_SYNTAX = \"selector-syntax\";\n\n// fmt CLI diagnostic codes (sightmap/spec docs/authoring-conventions.md#diagnostic-codes-authoring-side).\nexport const FMT_NOT_CANONICAL = \"fmt.not-canonical\";\nexport const FMT_PARSE_ERROR = \"fmt.parse-error\";\nexport const FMT_SCHEMA_INVALID = \"fmt.schema-invalid\";\n\n// Repo-conventions codes (WI-6). See docs/repo-conventions.md in the spec repo.\nexport const CONVENTION_SEP_FILENAME = \"convention.sep-filename\";\nexport const CONVENTION_FIXTURE_DIRNAME = \"convention.fixture-dirname\";\nexport const CONVENTION_INVALID_SLUG = \"convention.invalid-slug\";\nexport const CONVENTION_UNEXPECTED_FILE = \"convention.unexpected-file\";\n","import type { SightmapFragment, Sightmap } from \"./sightmap.js\";\nimport type { View, Component, Request } from \"./types.js\";\nimport {\n MERGE_COLLISION_VIEW,\n MERGE_COLLISION_COMPONENT,\n type Diagnostic,\n} from \"./diagnostics.js\";\n\n/**\n * Merge fragments into a queryable Sightmap.\n *\n * Canonical order: fragments are sorted by `__sourceFile` (code-point order; locale-independent\n * for cross-environment determinism). Fragments without a `__sourceFile` sort first, and ES2019\n * sort stability preserves their input order. Within each fragment, declaration order is\n * preserved. The merged collection's order = (sourceFile order, then declaration order).\n *\n * Duplicate view names produce `merge-collision-view` warnings; duplicate global component\n * names produce `merge-collision-component`. The first occurrence wins.\n *\n * Returned arrays are fresh, but element objects (View/Component/Request) are shared with\n * the input fragments — callers must not mutate them.\n */\nexport function merge(fragments: SightmapFragment[]): Sightmap {\n const sorted = [...fragments].sort((a, b) => {\n const aFile = a.__sourceFile ?? \"\";\n const bFile = b.__sourceFile ?? \"\";\n return aFile < bFile ? -1 : aFile > bFile ? 1 : 0;\n });\n\n const views: View[] = [];\n const globalComponents: Component[] = [];\n const globalRequests: Request[] = [];\n const fileMemory: { memory: string[]; sourceFile: string }[] = [];\n const diagnostics: Diagnostic[] = [];\n\n const seenViewNames = new Map<string, string>(); // name → sourceFile\n const seenComponentNames = new Map<string, string>();\n\n for (const f of sorted) {\n const file = f.__sourceFile ?? \"<unknown>\";\n\n for (const v of f.views ?? []) {\n const prev = seenViewNames.get(v.name);\n if (prev !== undefined) {\n diagnostics.push({\n severity: \"warning\",\n code: MERGE_COLLISION_VIEW,\n message: `View name \"${v.name}\" defined in both \"${prev}\" and \"${file}\"; first occurrence wins.`,\n file,\n });\n } else {\n seenViewNames.set(v.name, file);\n }\n views.push(v); // keep all views; resolveByUrl picks the most-specific match at query time\n }\n\n for (const c of f.components ?? []) {\n const prev = seenComponentNames.get(c.name);\n if (prev !== undefined) {\n diagnostics.push({\n severity: \"warning\",\n code: MERGE_COLLISION_COMPONENT,\n message: `Component name \"${c.name}\" defined in both \"${prev}\" and \"${file}\"; first occurrence wins.`,\n file,\n });\n } else {\n seenComponentNames.set(c.name, file);\n }\n globalComponents.push(c);\n }\n\n for (const r of f.requests ?? []) {\n globalRequests.push(r);\n }\n\n if (Array.isArray(f.memory) && f.memory.length > 0) {\n fileMemory.push({ memory: [...f.memory], sourceFile: file });\n }\n }\n\n return {\n version: 1,\n views,\n globalComponents,\n globalRequests,\n fileMemory,\n diagnostics,\n __brand: \"Sightmap\",\n };\n}\n","/**\n * Canonicalize a URL or pathname for matching.\n * - Accepts absolute URL or pathname.\n * - Strips scheme, host, query string, and fragment.\n * - Normalizes trailing slashes (except for the root \"/\").\n */\nexport function canonicalizeUrl(input: string): string {\n let s = input;\n // Strip absolute prefix.\n const protoMatch = /^[a-z][a-z0-9+.-]*:\\/\\/[^/]*/i.exec(s);\n if (protoMatch) {\n s = s.slice(protoMatch[0].length) || \"/\";\n }\n // Strip fragment.\n const hash = s.indexOf(\"#\");\n if (hash !== -1) s = s.slice(0, hash);\n // Strip query.\n const q = s.indexOf(\"?\");\n if (q !== -1) s = s.slice(0, q);\n // Trailing slash.\n if (s.length > 1 && s.endsWith(\"/\")) s = s.slice(0, -1);\n return s;\n}\n\n/**\n * Test whether a glob route pattern matches a URL pathname.\n * Pattern syntax (per spec):\n * - Literal segments match themselves\n * - \"*\" matches exactly one path segment\n * - \"**\" matches any depth of segments\n * - \":param\" segments normalize to \"*\"\n * - Matching is case-sensitive\n * - Trailing slashes ignored\n */\nexport function routeMatch(pattern: string, url: string): boolean {\n const p = normalizePattern(pattern);\n const u = canonicalizeUrl(url);\n const patternSegs = splitSegments(p);\n const urlSegs = splitSegments(u);\n return matchSegs(patternSegs, urlSegs);\n}\n\nfunction normalizePattern(p: string): string {\n // Replace \":param\" segments with \"*\"\n let s = p\n .split(\"/\")\n .map((seg) => (seg.startsWith(\":\") ? \"*\" : seg))\n .join(\"/\");\n if (s.length > 1 && s.endsWith(\"/\")) s = s.slice(0, -1);\n return s;\n}\n\nfunction splitSegments(path: string): string[] {\n if (path === \"/\" || path === \"\") return [];\n const trimmed = path.startsWith(\"/\") ? path.slice(1) : path;\n return trimmed.split(\"/\");\n}\n\n/**\n * Specificity score for a route pattern (higher = more specific).\n *\n * Used by the matcher to pick among multiple views whose `route` patterns\n * all match a given URL. Per-segment weights: literal = 3, `:param` = 2,\n * `*` = 1, `**` and empty segments contribute 0. Declaration order is the\n * tiebreak when two patterns score equal — enforced by the caller, not\n * here.\n *\n * Total-sum scoring matches React Router 7's general spirit: static\n * segments dominate, parameters outrank single-segment wildcards, and\n * `**` is the catchall fallback that only wins when nothing else matches.\n */\nexport function routeSpecificity(route: string): number {\n // The root route \"/\" is a literal match for exactly one URL and is more\n // specific than any wildcard-only pattern (e.g. \"/**\" scores 0).\n if (route === \"/\") return 1;\n let total = 0;\n for (const seg of route.split(\"/\")) {\n if (seg === \"\" || seg === \"**\") continue;\n if (seg === \"*\") total += 1;\n else if (seg.startsWith(\":\")) total += 2;\n else total += 3;\n }\n return total;\n}\n\nfunction matchSegs(pattern: string[], url: string[]): boolean {\n if (pattern.length === 0 && url.length === 0) return true;\n if (pattern.length === 0) return false;\n\n const head = pattern[0]!;\n const rest = pattern.slice(1);\n\n if (head === \"**\") {\n // Match zero or more segments.\n if (rest.length === 0) return true;\n for (let i = 0; i <= url.length; i++) {\n if (matchSegs(rest, url.slice(i))) return true;\n }\n return false;\n }\n\n if (url.length === 0) return false;\n if (head === \"*\" || head === url[0]) {\n return matchSegs(rest, url.slice(1));\n }\n return false;\n}\n","import type {\n Sightmap,\n MatchResult,\n ExplainHit,\n ResolvedView,\n ResolvedComponent,\n ResolvedRequest,\n} from \"./sightmap.js\";\nimport type { View, Component, Request } from \"./types.js\";\nimport { routeMatch, routeSpecificity } from \"./routeMatch.js\";\n\nexport function resolveByUrl(\n sightmap: Sightmap,\n url: string,\n method?: string,\n): MatchResult {\n // Most-specific match wins; declaration order tiebreaks equal specificity.\n // Strict `>` keeps the earlier-declared view on ties (declaration order =\n // the order views appear in `sightmap.views`, which is `merge.ts`'s\n // sourceFile-sorted view list).\n let matchedView: View | null = null;\n let bestScore = -1;\n for (const v of sightmap.views) {\n if (!routeMatch(v.route, url)) continue;\n // routeSpecificity expects the raw route string (preserves `:param` segments).\n const score = routeSpecificity(v.route);\n if (score > bestScore) {\n bestScore = score;\n matchedView = v;\n }\n }\n\n const components: ResolvedComponent[] = [];\n for (const c of sightmap.globalComponents) {\n components.push(...flattenComponent(c, [], \"global\", undefined));\n }\n if (matchedView !== null && Array.isArray(matchedView.components)) {\n for (const c of matchedView.components) {\n components.push(...flattenComponent(c, [], \"view-scoped\", matchedView.name));\n }\n }\n\n const requests: ResolvedRequest[] = [];\n const requestPool: Request[] = [\n ...sightmap.globalRequests,\n ...((matchedView?.requests ?? [])),\n ];\n for (const req of requestPool) {\n if (!routeMatch(req.route, url)) continue;\n if (method !== undefined && req.method !== undefined && req.method !== method) continue;\n requests.push(toResolvedRequest(req));\n }\n\n const memory: string[] = [];\n for (const fm of sightmap.fileMemory) memory.push(...fm.memory);\n if (matchedView?.memory) memory.push(...matchedView.memory);\n\n return {\n view: matchedView !== null ? toResolvedView(matchedView) : null,\n components,\n requests,\n memory,\n };\n}\n\nexport function resolveByName(sightmap: Sightmap, name: string): ExplainHit[] {\n const hits: ExplainHit[] = [];\n for (const v of sightmap.views) {\n if (v.name === name) hits.push({ type: \"view\", matchedAs: \"name\", entry: toResolvedView(v) });\n }\n for (const c of sightmap.globalComponents) {\n for (const rc of flattenComponent(c, [], \"global\", undefined)) {\n if (rc.name === name) hits.push({ type: \"component\", matchedAs: \"name\", entry: rc });\n }\n }\n for (const v of sightmap.views) {\n for (const c of v.components ?? []) {\n for (const rc of flattenComponent(c, [], \"view-scoped\", v.name)) {\n if (rc.name === name) hits.push({ type: \"component\", matchedAs: \"name\", entry: rc });\n }\n }\n }\n for (const r of sightmap.globalRequests) {\n if (r.name === name) hits.push({ type: \"request\", matchedAs: \"name\", entry: toResolvedRequest(r) });\n }\n for (const v of sightmap.views) {\n for (const r of v.requests ?? []) {\n if (r.name === name) hits.push({ type: \"request\", matchedAs: \"name\", entry: toResolvedRequest(r) });\n }\n }\n return hits;\n}\n\nexport function resolveBySourcePath(sightmap: Sightmap, path: string): ExplainHit[] {\n const hits: ExplainHit[] = [];\n for (const v of sightmap.views) {\n if (v.source === path) hits.push({ type: \"view\", matchedAs: \"path\", entry: toResolvedView(v) });\n }\n for (const c of sightmap.globalComponents) {\n for (const rc of flattenComponent(c, [], \"global\", undefined)) {\n if (rc.source === path) hits.push({ type: \"component\", matchedAs: \"path\", entry: rc });\n }\n }\n for (const v of sightmap.views) {\n for (const c of v.components ?? []) {\n for (const rc of flattenComponent(c, [], \"view-scoped\", v.name)) {\n if (rc.source === path) hits.push({ type: \"component\", matchedAs: \"path\", entry: rc });\n }\n }\n }\n for (const r of sightmap.globalRequests) {\n if (r.source === path) hits.push({ type: \"request\", matchedAs: \"path\", entry: toResolvedRequest(r) });\n }\n return hits;\n}\n\nfunction flattenComponent(\n c: Component,\n parentChain: string[],\n scope: \"global\" | \"view-scoped\",\n scopedToView: string | undefined,\n): ResolvedComponent[] {\n const out: ResolvedComponent[] = [];\n const selector = Array.isArray(c.selector) ? c.selector : [c.selector as unknown as string];\n out.push({\n name: c.name,\n selector,\n ...(c.source !== undefined ? { source: c.source } : {}),\n ...(c.description !== undefined ? { description: c.description } : {}),\n memory: c.memory ? [...c.memory] : [],\n parentChain: [...parentChain],\n scope,\n ...(scopedToView !== undefined ? { scopedToView } : {}),\n definedIn: { file: \"<unknown>\" },\n });\n for (const child of c.children ?? []) {\n out.push(...flattenComponent(child, [...parentChain, c.name], scope, scopedToView));\n }\n return out;\n}\n\nfunction toResolvedView(v: View): ResolvedView {\n return {\n name: v.name,\n route: v.route,\n ...(v.source !== undefined ? { source: v.source } : {}),\n ...(v.description !== undefined ? { description: v.description } : {}),\n memory: v.memory ? [...v.memory] : [],\n definedIn: { file: \"<unknown>\" },\n };\n}\n\nfunction toResolvedRequest(r: Request): ResolvedRequest {\n return {\n name: r.name,\n route: r.route,\n ...(r.method !== undefined ? { method: r.method } : {}),\n ...(r.source !== undefined ? { source: r.source } : {}),\n ...(r.description !== undefined ? { description: r.description } : {}),\n ...(r.request !== undefined ? { request: { fields: r.request.fields ?? [] } } : {}),\n ...(r.response !== undefined ? { response: { fields: r.response.fields ?? [] } } : {}),\n ...(r.headers !== undefined ? { headers: r.headers } : {}),\n memory: r.memory ? [...r.memory] : [],\n definedIn: { file: \"<unknown>\" },\n };\n}\n","import type { Sightmap, MatchResult } from \"./sightmap.js\";\nimport { resolveByUrl } from \"./resolver.js\";\n\nexport interface MatchOptions {\n url: string;\n method?: string;\n}\n\nexport function match(sightmap: Sightmap, opts: MatchOptions): MatchResult {\n return resolveByUrl(sightmap, opts.url, opts.method);\n}\n","import type { Sightmap, ExplainResult, ExplainHit, ExplainMatchedAs } from \"./sightmap.js\";\nimport { resolveByName, resolveBySourcePath } from \"./resolver.js\";\n\nexport interface ExplainOptions {\n by?: \"name\" | \"path\";\n type?: \"view\" | \"component\" | \"request\";\n}\n\nexport function explain(\n sightmap: Sightmap,\n query: string,\n opts: ExplainOptions = {},\n): ExplainResult {\n const byName = opts.by === \"path\" ? [] : resolveByName(sightmap, query);\n const byPath = opts.by === \"name\" ? [] : resolveBySourcePath(sightmap, query);\n\n // Merge: when an entry appears in both lookups, label as \"name-and-path\".\n // Use entry identity (type + name) as the dedup key.\n const key = (h: ExplainHit): string => `${h.type}:${h.entry.name}`;\n const namedKeys = new Set(byName.map(key));\n const pathKeys = new Set(byPath.map(key));\n\n const merged: ExplainHit[] = [];\n for (const h of byName) {\n if (pathKeys.has(key(h))) {\n merged.push(withMatchedAs(h, \"name-and-path\"));\n } else {\n merged.push(h);\n }\n }\n for (const h of byPath) {\n if (!namedKeys.has(key(h))) {\n merged.push(h);\n }\n // (entries already in both have been pushed above with \"name-and-path\".)\n }\n\n const filtered =\n opts.type !== undefined ? merged.filter((h) => h.type === opts.type) : merged;\n return { query, hits: filtered };\n}\n\nfunction withMatchedAs(h: ExplainHit, matchedAs: ExplainMatchedAs): ExplainHit {\n switch (h.type) {\n case \"view\":\n return { type: \"view\", matchedAs, entry: h.entry };\n case \"component\":\n return { type: \"component\", matchedAs, entry: h.entry };\n case \"request\":\n return { type: \"request\", matchedAs, entry: h.entry };\n }\n}\n"],"mappings":"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","names":[]}
package/dist/cli/index.js CHANGED
@@ -489,6 +489,67 @@ function duplicateRoute(sightmap) {
489
489
  return out;
490
490
  }
491
491
 
492
+ // src/routeMatch.ts
493
+ function canonicalizeUrl(input) {
494
+ let s = input;
495
+ const protoMatch = /^[a-z][a-z0-9+.-]*:\/\/[^/]*/i.exec(s);
496
+ if (protoMatch) {
497
+ s = s.slice(protoMatch[0].length) || "/";
498
+ }
499
+ const hash = s.indexOf("#");
500
+ if (hash !== -1) s = s.slice(0, hash);
501
+ const q = s.indexOf("?");
502
+ if (q !== -1) s = s.slice(0, q);
503
+ if (s.length > 1 && s.endsWith("/")) s = s.slice(0, -1);
504
+ return s;
505
+ }
506
+ function routeMatch(pattern, url) {
507
+ const p = normalizePattern(pattern);
508
+ const u = canonicalizeUrl(url);
509
+ const patternSegs = splitSegments(p);
510
+ const urlSegs = splitSegments(u);
511
+ return matchSegs(patternSegs, urlSegs);
512
+ }
513
+ function normalizePattern(p) {
514
+ let s = p.split("/").map((seg) => seg.startsWith(":") ? "*" : seg).join("/");
515
+ if (s.length > 1 && s.endsWith("/")) s = s.slice(0, -1);
516
+ return s;
517
+ }
518
+ function splitSegments(path) {
519
+ if (path === "/" || path === "") return [];
520
+ const trimmed = path.startsWith("/") ? path.slice(1) : path;
521
+ return trimmed.split("/");
522
+ }
523
+ function routeSpecificity(route) {
524
+ if (route === "/") return 1;
525
+ let total = 0;
526
+ for (const seg of route.split("/")) {
527
+ if (seg === "" || seg === "**") continue;
528
+ if (seg === "*") total += 1;
529
+ else if (seg.startsWith(":")) total += 2;
530
+ else total += 3;
531
+ }
532
+ return total;
533
+ }
534
+ function matchSegs(pattern, url) {
535
+ if (pattern.length === 0 && url.length === 0) return true;
536
+ if (pattern.length === 0) return false;
537
+ const head = pattern[0];
538
+ const rest = pattern.slice(1);
539
+ if (head === "**") {
540
+ if (rest.length === 0) return true;
541
+ for (let i = 0; i <= url.length; i++) {
542
+ if (matchSegs(rest, url.slice(i))) return true;
543
+ }
544
+ return false;
545
+ }
546
+ if (url.length === 0) return false;
547
+ if (head === "*" || head === url[0]) {
548
+ return matchSegs(rest, url.slice(1));
549
+ }
550
+ return false;
551
+ }
552
+
492
553
  // src/lintRules/routeShadowing.ts
493
554
  function routeShadowing(sightmap) {
494
555
  const out = [];
@@ -496,12 +557,12 @@ function routeShadowing(sightmap) {
496
557
  for (let j = 1; j < views.length; j++) {
497
558
  const later = views[j];
498
559
  const laterKey = matchSetKey(later.route);
499
- const laterScore = specificity(later.route);
560
+ const laterScore = routeSpecificity(later.route);
500
561
  for (let i = 0; i < j; i++) {
501
562
  const earlier = views[i];
502
563
  if (earlier.route === later.route) continue;
503
564
  if (matchSetKey(earlier.route) !== laterKey) continue;
504
- if (specificity(earlier.route) < laterScore) continue;
565
+ if (routeSpecificity(earlier.route) < laterScore) continue;
505
566
  out.push({
506
567
  severity: "warning",
507
568
  code: ROUTE_SHADOWING,
@@ -515,16 +576,6 @@ function routeShadowing(sightmap) {
515
576
  function matchSetKey(route) {
516
577
  return route.split("/").map((seg) => seg.startsWith(":") ? "*" : seg).join("/");
517
578
  }
518
- function specificity(route) {
519
- let total = 0;
520
- for (const seg of route.split("/")) {
521
- if (seg === "" || seg === "**") continue;
522
- if (seg === "*") total += 1;
523
- else if (seg.startsWith(":")) total += 2;
524
- else total += 3;
525
- }
526
- return total;
527
- }
528
579
 
529
580
  // src/lintRules/unknownSource.ts
530
581
  import { stat } from "fs/promises";
@@ -651,63 +702,16 @@ function parseRules(spec) {
651
702
  // src/cli/match.ts
652
703
  import { resolve as resolve6 } from "path";
653
704
 
654
- // src/routeMatch.ts
655
- function canonicalizeUrl(input) {
656
- let s = input;
657
- const protoMatch = /^[a-z][a-z0-9+.-]*:\/\/[^/]*/i.exec(s);
658
- if (protoMatch) {
659
- s = s.slice(protoMatch[0].length) || "/";
660
- }
661
- const hash = s.indexOf("#");
662
- if (hash !== -1) s = s.slice(0, hash);
663
- const q = s.indexOf("?");
664
- if (q !== -1) s = s.slice(0, q);
665
- if (s.length > 1 && s.endsWith("/")) s = s.slice(0, -1);
666
- return s;
667
- }
668
- function routeMatch(pattern, url) {
669
- const p = normalizePattern(pattern);
670
- const u = canonicalizeUrl(url);
671
- const patternSegs = splitSegments(p);
672
- const urlSegs = splitSegments(u);
673
- return matchSegs(patternSegs, urlSegs);
674
- }
675
- function normalizePattern(p) {
676
- let s = p.split("/").map((seg) => seg.startsWith(":") ? "*" : seg).join("/");
677
- if (s.length > 1 && s.endsWith("/")) s = s.slice(0, -1);
678
- return s;
679
- }
680
- function splitSegments(path) {
681
- if (path === "/" || path === "") return [];
682
- const trimmed = path.startsWith("/") ? path.slice(1) : path;
683
- return trimmed.split("/");
684
- }
685
- function matchSegs(pattern, url) {
686
- if (pattern.length === 0 && url.length === 0) return true;
687
- if (pattern.length === 0) return false;
688
- const head = pattern[0];
689
- const rest = pattern.slice(1);
690
- if (head === "**") {
691
- if (rest.length === 0) return true;
692
- for (let i = 0; i <= url.length; i++) {
693
- if (matchSegs(rest, url.slice(i))) return true;
694
- }
695
- return false;
696
- }
697
- if (url.length === 0) return false;
698
- if (head === "*" || head === url[0]) {
699
- return matchSegs(rest, url.slice(1));
700
- }
701
- return false;
702
- }
703
-
704
705
  // src/resolver.ts
705
706
  function resolveByUrl(sightmap, url, method) {
706
707
  let matchedView = null;
708
+ let bestScore = -1;
707
709
  for (const v of sightmap.views) {
708
- if (routeMatch(v.route, url)) {
710
+ if (!routeMatch(v.route, url)) continue;
711
+ const score = routeSpecificity(v.route);
712
+ if (score > bestScore) {
713
+ bestScore = score;
709
714
  matchedView = v;
710
- break;
711
715
  }
712
716
  }
713
717
  const components = [];