@sightmap/sightmap 0.6.0 → 0.8.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/dist/browser.js +16 -2
- package/dist/browser.js.map +1 -1
- package/dist/cli/index.js +108 -101
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +18 -14
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
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 = [];
|
package/dist/browser.js.map
CHANGED
|
@@ -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":"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","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 =
|
|
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 (
|
|
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 = [];
|
|
@@ -2133,6 +2137,37 @@ function hasGroup(groups, candidate) {
|
|
|
2133
2137
|
);
|
|
2134
2138
|
}
|
|
2135
2139
|
|
|
2140
|
+
// src/cli/init/hooks-setup/settings-hooks.ts
|
|
2141
|
+
function buildLintHooks(opts) {
|
|
2142
|
+
const matcher = "Write|Edit|MultiEdit";
|
|
2143
|
+
return {
|
|
2144
|
+
PreToolUse: [
|
|
2145
|
+
{
|
|
2146
|
+
matcher,
|
|
2147
|
+
hooks: [
|
|
2148
|
+
{
|
|
2149
|
+
type: "command",
|
|
2150
|
+
command: `${opts.cliCommand} check --json`,
|
|
2151
|
+
timeout: 5e3
|
|
2152
|
+
}
|
|
2153
|
+
]
|
|
2154
|
+
}
|
|
2155
|
+
],
|
|
2156
|
+
PostToolUse: [
|
|
2157
|
+
{
|
|
2158
|
+
matcher,
|
|
2159
|
+
hooks: [
|
|
2160
|
+
{
|
|
2161
|
+
type: "command",
|
|
2162
|
+
command: `${opts.cliCommand} audit --json`,
|
|
2163
|
+
timeout: 8e3
|
|
2164
|
+
}
|
|
2165
|
+
]
|
|
2166
|
+
}
|
|
2167
|
+
]
|
|
2168
|
+
};
|
|
2169
|
+
}
|
|
2170
|
+
|
|
2136
2171
|
// src/cli/init/skills-copy.ts
|
|
2137
2172
|
async function copyPluginAssets(opts) {
|
|
2138
2173
|
const targets = resolveTargets(opts.host, opts.projectDir);
|
|
@@ -2174,8 +2209,10 @@ async function installHooksIntoSettings(opts) {
|
|
|
2174
2209
|
} catch {
|
|
2175
2210
|
}
|
|
2176
2211
|
const merged = mergeHooksIntoSettings(existing, rewritten);
|
|
2212
|
+
const lintHooks = buildLintHooks({ cliCommand: "npx @sightmap/sightmap" });
|
|
2213
|
+
const finalMerged = mergeHooksIntoSettings(merged, lintHooks);
|
|
2177
2214
|
await mkdir3(resolve20(opts.settingsPath, ".."), { recursive: true });
|
|
2178
|
-
await writeFile6(opts.settingsPath, JSON.stringify(
|
|
2215
|
+
await writeFile6(opts.settingsPath, JSON.stringify(finalMerged, null, 2) + "\n", "utf8");
|
|
2179
2216
|
}
|
|
2180
2217
|
async function installLegacyHooksJson(opts) {
|
|
2181
2218
|
const srcRaw = JSON.parse(
|
|
@@ -2474,30 +2511,6 @@ async function installAdapter(opts) {
|
|
|
2474
2511
|
});
|
|
2475
2512
|
}
|
|
2476
2513
|
|
|
2477
|
-
// src/cli/init/framework-setup/codegen.ts
|
|
2478
|
-
import { spawn as spawn3 } from "child_process";
|
|
2479
|
-
var REACT_FRAMEWORKS = ["react-vite", "react-cra", "next-app", "next-pages", "react-router-7"];
|
|
2480
|
-
var EXEC_VERB = {
|
|
2481
|
-
pnpm: ["exec"],
|
|
2482
|
-
yarn: ["exec"],
|
|
2483
|
-
npm: ["exec", "--"],
|
|
2484
|
-
bun: ["x"]
|
|
2485
|
-
};
|
|
2486
|
-
function buildCodegenCommand(opts) {
|
|
2487
|
-
if (!REACT_FRAMEWORKS.includes(opts.framework)) return null;
|
|
2488
|
-
return {
|
|
2489
|
-
command: opts.packageManager,
|
|
2490
|
-
args: [...EXEC_VERB[opts.packageManager], "sightmap-react", "gen", "--router=auto"]
|
|
2491
|
-
};
|
|
2492
|
-
}
|
|
2493
|
-
async function runCodegen(cwd, cmd) {
|
|
2494
|
-
await new Promise((resolveP, reject) => {
|
|
2495
|
-
const child = spawn3(cmd.command, cmd.args, { cwd, stdio: "inherit" });
|
|
2496
|
-
child.on("error", reject);
|
|
2497
|
-
child.on("exit", (code) => code === 0 ? resolveP() : reject(new Error(`codegen exited ${code}`)));
|
|
2498
|
-
});
|
|
2499
|
-
}
|
|
2500
|
-
|
|
2501
2514
|
// src/cli/init/framework-setup/provider-codemod.ts
|
|
2502
2515
|
import * as recast from "recast";
|
|
2503
2516
|
import * as parser from "@babel/parser";
|
|
@@ -2633,7 +2646,7 @@ function formatExistingCorpusReport(input) {
|
|
|
2633
2646
|
}
|
|
2634
2647
|
|
|
2635
2648
|
// src/cli/init/index.ts
|
|
2636
|
-
var
|
|
2649
|
+
var REACT_FRAMEWORKS = ["react-vite", "react-cra", "next-app", "next-pages", "react-router-7"];
|
|
2637
2650
|
async function runInit(opts) {
|
|
2638
2651
|
intro2();
|
|
2639
2652
|
let detect = {
|
|
@@ -2710,7 +2723,7 @@ async function runInit(opts) {
|
|
|
2710
2723
|
return 0;
|
|
2711
2724
|
}
|
|
2712
2725
|
async function runFreshFrameworkSetup(opts, detect) {
|
|
2713
|
-
if (!
|
|
2726
|
+
if (!REACT_FRAMEWORKS.includes(detect.framework)) {
|
|
2714
2727
|
await mkdir4(resolve22(opts.cwd, ".sightmap"), { recursive: true });
|
|
2715
2728
|
await writeFile7(
|
|
2716
2729
|
resolve22(opts.cwd, ".sightmap/app.yaml"),
|
|
@@ -2729,14 +2742,9 @@ views: []
|
|
|
2729
2742
|
if (!opts.noProvider) {
|
|
2730
2743
|
await runProviderStep(opts, detect);
|
|
2731
2744
|
}
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
spinner3.start("Running sightmap-react gen");
|
|
2736
|
-
await runCodegen(opts.cwd, cmd);
|
|
2737
|
-
spinner3.stop("Scaffolded .sightmap/");
|
|
2738
|
-
}
|
|
2739
|
-
}
|
|
2745
|
+
clack3.log.info(
|
|
2746
|
+
"Sightmap runtime installed. Run `/sightmap:bootstrap` from your agent (after starting `pnpm dev` in another terminal) to populate `.sightmap/` from the live app."
|
|
2747
|
+
);
|
|
2740
2748
|
}
|
|
2741
2749
|
async function runProviderStep(opts, detect) {
|
|
2742
2750
|
const entry = await findAppEntry(opts.cwd, detect.framework);
|
|
@@ -2955,7 +2963,7 @@ program.command("fmt [path]").description("Canonicalize .sightmap/*.yaml files (
|
|
|
2955
2963
|
process.exit(code);
|
|
2956
2964
|
}
|
|
2957
2965
|
);
|
|
2958
|
-
program.command("init").description("Initialize Sightmap in this project (detects framework + coding agent, writes config).").option("--yes", "accept all defaults (non-interactive)").option("--plugin", "force plugin install path").option("--manual", "force manual install path").option("--host <names>", "comma-separated hosts: claude-code,codex,cursor,opencode").option("--with-browser", "force bundled Playwright + browser tools").option("--curate-only", "force curation-only (skip browser tools)").option("--no-
|
|
2966
|
+
program.command("init").description("Initialize Sightmap in this project (detects framework + coding agent, writes config).").option("--yes", "accept all defaults (non-interactive)").option("--plugin", "force plugin install path").option("--manual", "force manual install path").option("--host <names>", "comma-separated hosts: claude-code,codex,cursor,opencode").option("--with-browser", "force bundled Playwright + browser tools").option("--curate-only", "force curation-only (skip browser tools)").option("--no-provider", "skip <SightmapProvider> codemod").option("--no-smoke", "skip the MCP launch smoke test (CI / headless use)").action(async (opts) => {
|
|
2959
2967
|
const code = await runInit({
|
|
2960
2968
|
cwd: program.opts().cwd,
|
|
2961
2969
|
yes: opts.yes === true,
|
|
@@ -2964,7 +2972,6 @@ program.command("init").description("Initialize Sightmap in this project (detect
|
|
|
2964
2972
|
...opts.host !== void 0 ? { hosts: opts.host.split(",").map((s) => s.trim()) } : {},
|
|
2965
2973
|
...opts.withBrowser ? { withBrowser: true } : {},
|
|
2966
2974
|
...opts.curateOnly ? { curateOnly: true } : {},
|
|
2967
|
-
...opts.codegen === false ? { noCodegen: true } : {},
|
|
2968
2975
|
...opts.provider === false ? { noProvider: true } : {},
|
|
2969
2976
|
...opts.smoke === false ? { noSmoke: true } : {}
|
|
2970
2977
|
});
|