@sightmap/sightmap 0.1.0 → 0.2.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/README.md +30 -1
- package/dist/browser-ChD_xQt8.d.ts +253 -0
- package/dist/browser.d.ts +1 -0
- package/dist/browser.js +353 -0
- package/dist/browser.js.map +1 -0
- package/dist/cli/index.js +336 -79
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +10 -252
- package/dist/index.js +74 -29
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,248 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*/
|
|
4
|
-
type Memory = string[];
|
|
5
|
-
/**
|
|
6
|
-
* Freeform notes surfaced to agents at runtime in the [Guide] section.
|
|
7
|
-
*/
|
|
8
|
-
type Memory1 = string[];
|
|
9
|
-
/**
|
|
10
|
-
* A CSS selector, or a list of alternative selectors tried in order.
|
|
11
|
-
*/
|
|
12
|
-
type Selector = string | [string, ...string[]];
|
|
13
|
-
/**
|
|
14
|
-
* A sightmap file: one YAML document under .sightmap/ describing views, components, requests, and memory entries. See https://github.com/sightmap/spec/blob/main/spec/v1/schema.md for the human-readable reference.
|
|
15
|
-
*/
|
|
16
|
-
interface SightmapV1 {
|
|
17
|
-
/**
|
|
18
|
-
* Must be 1 for files that conform to this version of the spec.
|
|
19
|
-
*/
|
|
20
|
-
version: 1;
|
|
21
|
-
memory?: Memory;
|
|
22
|
-
views?: View[];
|
|
23
|
-
/**
|
|
24
|
-
* Global components — matched against every view.
|
|
25
|
-
*/
|
|
26
|
-
components?: Component[];
|
|
27
|
-
/**
|
|
28
|
-
* Global requests — matched against every view.
|
|
29
|
-
*/
|
|
30
|
-
requests?: Request[];
|
|
31
|
-
}
|
|
32
|
-
interface View {
|
|
33
|
-
name: string;
|
|
34
|
-
/**
|
|
35
|
-
* Glob pattern matched against the URL pathname. * matches one segment, ** matches any depth.
|
|
36
|
-
*/
|
|
37
|
-
route: string;
|
|
38
|
-
description?: string;
|
|
39
|
-
source?: string;
|
|
40
|
-
memory?: Memory1;
|
|
41
|
-
components?: Component[];
|
|
42
|
-
requests?: Request[];
|
|
43
|
-
}
|
|
44
|
-
interface Component {
|
|
45
|
-
name: string;
|
|
46
|
-
selector: Selector;
|
|
47
|
-
source?: string;
|
|
48
|
-
description?: string;
|
|
49
|
-
memory?: Memory1;
|
|
50
|
-
/**
|
|
51
|
-
* Nested components. Selectors are scoped to the parent's matched subtree.
|
|
52
|
-
*/
|
|
53
|
-
children?: Component[];
|
|
54
|
-
}
|
|
55
|
-
interface Request {
|
|
56
|
-
name: string;
|
|
57
|
-
/**
|
|
58
|
-
* Glob pattern. Express-style :param segments normalize to *.
|
|
59
|
-
*/
|
|
60
|
-
route: string;
|
|
61
|
-
/**
|
|
62
|
-
* HTTP method filter. Case-insensitive; commonly GET, POST, PUT, PATCH, DELETE.
|
|
63
|
-
*/
|
|
64
|
-
method?: string;
|
|
65
|
-
description?: string;
|
|
66
|
-
source?: string;
|
|
67
|
-
request?: Payload;
|
|
68
|
-
response?: Payload;
|
|
69
|
-
headers?: string[];
|
|
70
|
-
memory?: Memory1;
|
|
71
|
-
}
|
|
72
|
-
interface Payload {
|
|
73
|
-
fields?: Field[];
|
|
74
|
-
}
|
|
75
|
-
interface Field {
|
|
76
|
-
name: string;
|
|
77
|
-
type?: string;
|
|
78
|
-
description?: string;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
type Severity = "error" | "warning" | "info";
|
|
82
|
-
interface Diagnostic {
|
|
83
|
-
severity: Severity;
|
|
84
|
-
code: string;
|
|
85
|
-
message: string;
|
|
86
|
-
file?: string;
|
|
87
|
-
path?: string;
|
|
88
|
-
loc?: {
|
|
89
|
-
line: number;
|
|
90
|
-
column: number;
|
|
91
|
-
};
|
|
92
|
-
source?: string;
|
|
93
|
-
}
|
|
94
|
-
declare const PARSE_ERROR = "parse-error";
|
|
95
|
-
declare const SCHEMA_VALIDATION_FAILED = "schema-validation-failed";
|
|
96
|
-
declare const UNKNOWN_VERSION = "unknown-version";
|
|
97
|
-
declare const MERGE_COLLISION_VIEW = "merge-collision-view";
|
|
98
|
-
declare const MERGE_COLLISION_COMPONENT = "merge-collision-component";
|
|
99
|
-
declare const DUPLICATE_VIEW_NAME = "duplicate-view-name";
|
|
100
|
-
declare const DUPLICATE_ROUTE = "duplicate-route";
|
|
101
|
-
declare const ROUTE_SHADOWING = "route-shadowing";
|
|
102
|
-
declare const UNKNOWN_SOURCE = "unknown-source";
|
|
103
|
-
declare const SELECTOR_SYNTAX = "selector-syntax";
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* A single-file fragment after parsing and selector normalization.
|
|
107
|
-
* Branded to prevent querying an unmerged fragment.
|
|
108
|
-
*/
|
|
109
|
-
interface SightmapFragment extends SightmapV1 {
|
|
110
|
-
readonly __brand: "SightmapFragment";
|
|
111
|
-
readonly __sourceFile?: string;
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* A merged, queryable sightmap produced by `merge()` or `loadDirectory()`.
|
|
115
|
-
*/
|
|
116
|
-
interface Sightmap {
|
|
117
|
-
readonly version: 1;
|
|
118
|
-
readonly views: readonly View[];
|
|
119
|
-
readonly globalComponents: readonly Component[];
|
|
120
|
-
readonly globalRequests: readonly Request[];
|
|
121
|
-
readonly fileMemory: readonly {
|
|
122
|
-
memory: string[];
|
|
123
|
-
sourceFile: string;
|
|
124
|
-
}[];
|
|
125
|
-
/**
|
|
126
|
-
* Diagnostics produced during merge (e.g. collisions, duplicate names across files).
|
|
127
|
-
* Lint diagnostics are returned separately by `lint()`.
|
|
128
|
-
*/
|
|
129
|
-
readonly diagnostics: readonly Diagnostic[];
|
|
130
|
-
readonly __brand: "Sightmap";
|
|
131
|
-
}
|
|
132
|
-
interface ResolvedView {
|
|
133
|
-
name: string;
|
|
134
|
-
route: string;
|
|
135
|
-
source?: string;
|
|
136
|
-
description?: string;
|
|
137
|
-
memory: string[];
|
|
138
|
-
definedIn: {
|
|
139
|
-
file: string;
|
|
140
|
-
line?: number;
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
interface ResolvedComponent {
|
|
144
|
-
name: string;
|
|
145
|
-
selector: string[];
|
|
146
|
-
source?: string;
|
|
147
|
-
description?: string;
|
|
148
|
-
memory: string[];
|
|
149
|
-
parentChain: string[];
|
|
150
|
-
scope: "global" | "view-scoped";
|
|
151
|
-
/** Present iff `scope === "view-scoped"`. */
|
|
152
|
-
scopedToView?: string;
|
|
153
|
-
definedIn: {
|
|
154
|
-
file: string;
|
|
155
|
-
line?: number;
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
interface ResolvedRequest {
|
|
159
|
-
name: string;
|
|
160
|
-
route: string;
|
|
161
|
-
method?: string;
|
|
162
|
-
source?: string;
|
|
163
|
-
description?: string;
|
|
164
|
-
request?: {
|
|
165
|
-
fields: readonly Field[];
|
|
166
|
-
};
|
|
167
|
-
response?: {
|
|
168
|
-
fields: readonly Field[];
|
|
169
|
-
};
|
|
170
|
-
headers?: readonly string[];
|
|
171
|
-
memory: string[];
|
|
172
|
-
definedIn: {
|
|
173
|
-
file: string;
|
|
174
|
-
line?: number;
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
interface MatchResult {
|
|
178
|
-
view: ResolvedView | null;
|
|
179
|
-
components: ResolvedComponent[];
|
|
180
|
-
requests: ResolvedRequest[];
|
|
181
|
-
memory: string[];
|
|
182
|
-
}
|
|
183
|
-
type ExplainMatchedAs = "name" | "path" | "name-and-path";
|
|
184
|
-
/**
|
|
185
|
-
* Discriminated union: narrowing on `type` automatically narrows `entry`.
|
|
186
|
-
* if (hit.type === "view") { hit.entry.route; } // no cast needed
|
|
187
|
-
*/
|
|
188
|
-
type ExplainHit = {
|
|
189
|
-
type: "view";
|
|
190
|
-
matchedAs: ExplainMatchedAs;
|
|
191
|
-
entry: ResolvedView;
|
|
192
|
-
} | {
|
|
193
|
-
type: "component";
|
|
194
|
-
matchedAs: ExplainMatchedAs;
|
|
195
|
-
entry: ResolvedComponent;
|
|
196
|
-
} | {
|
|
197
|
-
type: "request";
|
|
198
|
-
matchedAs: ExplainMatchedAs;
|
|
199
|
-
entry: ResolvedRequest;
|
|
200
|
-
};
|
|
201
|
-
interface ExplainResult {
|
|
202
|
-
query: string;
|
|
203
|
-
hits: ExplainHit[];
|
|
204
|
-
}
|
|
205
|
-
/** Result returned by validate() — never throws. */
|
|
206
|
-
type ValidateResult = {
|
|
207
|
-
ok: true;
|
|
208
|
-
value: SightmapFragment;
|
|
209
|
-
} | {
|
|
210
|
-
ok: false;
|
|
211
|
-
diagnostics: readonly Diagnostic[];
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
interface ParseOptions {
|
|
215
|
-
/** Optional source file path; recorded on the fragment for canonical-order merging. */
|
|
216
|
-
sourceFile?: string;
|
|
217
|
-
}
|
|
218
|
-
/**
|
|
219
|
-
* Parse a single sightmap file (YAML string or pre-parsed object).
|
|
220
|
-
* Throws on parse error, missing version, or version mismatch.
|
|
221
|
-
* Normalizes `selector: string` to `selector: [string]` everywhere.
|
|
222
|
-
*/
|
|
223
|
-
declare function parse(input: string | object, opts?: ParseOptions): SightmapFragment;
|
|
1
|
+
import { V as ValidateResult, S as Sightmap, D as Diagnostic, a as SightmapV1 } from './browser-ChD_xQt8.js';
|
|
2
|
+
export { C as Component, b as DUPLICATE_ROUTE, c as DUPLICATE_VIEW_NAME, E as ExplainHit, d as ExplainMatchedAs, e as ExplainResult, F as Field, M as MERGE_COLLISION_COMPONENT, f as MERGE_COLLISION_VIEW, g as MatchResult, P as PARSE_ERROR, R as ROUTE_SHADOWING, h as Request, i as ResolvedComponent, j as ResolvedRequest, k as ResolvedView, l as SCHEMA_VALIDATION_FAILED, m as SELECTOR_SYNTAX, n as Severity, o as SightmapFragment, U as UNKNOWN_SOURCE, p as UNKNOWN_VERSION, q as View, r as explain, s as match, t as merge, u as parse } from './browser-ChD_xQt8.js';
|
|
224
3
|
|
|
225
4
|
interface ValidateOptions {
|
|
226
5
|
sourceFile?: string;
|
|
227
6
|
}
|
|
228
7
|
declare function validate(input: string | object, opts?: ValidateOptions): ValidateResult;
|
|
229
8
|
|
|
230
|
-
/**
|
|
231
|
-
* Merge fragments into a queryable Sightmap.
|
|
232
|
-
*
|
|
233
|
-
* Canonical order: fragments are sorted by `__sourceFile` (code-point order; locale-independent
|
|
234
|
-
* for cross-environment determinism). Fragments without a `__sourceFile` sort first, and ES2019
|
|
235
|
-
* sort stability preserves their input order. Within each fragment, declaration order is
|
|
236
|
-
* preserved. The merged collection's order = (sourceFile order, then declaration order).
|
|
237
|
-
*
|
|
238
|
-
* Duplicate view names produce `merge-collision-view` warnings; duplicate global component
|
|
239
|
-
* names produce `merge-collision-component`. The first occurrence wins.
|
|
240
|
-
*
|
|
241
|
-
* Returned arrays are fresh, but element objects (View/Component/Request) are shared with
|
|
242
|
-
* the input fragments — callers must not mutate them.
|
|
243
|
-
*/
|
|
244
|
-
declare function merge(fragments: SightmapFragment[]): Sightmap;
|
|
245
|
-
|
|
246
9
|
interface LoadDirectoryOptions {
|
|
247
10
|
/** Optional root for `file` fields in diagnostics. Default: the directory passed in. */
|
|
248
11
|
diagnosticRoot?: string;
|
|
@@ -256,18 +19,6 @@ interface LoadDirectoryOptions {
|
|
|
256
19
|
*/
|
|
257
20
|
declare function loadDirectory(dir: string, opts?: LoadDirectoryOptions): Promise<Sightmap>;
|
|
258
21
|
|
|
259
|
-
interface MatchOptions {
|
|
260
|
-
url: string;
|
|
261
|
-
method?: string;
|
|
262
|
-
}
|
|
263
|
-
declare function match(sightmap: Sightmap, opts: MatchOptions): MatchResult;
|
|
264
|
-
|
|
265
|
-
interface ExplainOptions {
|
|
266
|
-
by?: "name" | "path";
|
|
267
|
-
type?: "view" | "component" | "request";
|
|
268
|
-
}
|
|
269
|
-
declare function explain(sightmap: Sightmap, query: string, opts?: ExplainOptions): ExplainResult;
|
|
270
|
-
|
|
271
22
|
interface LintOptions {
|
|
272
23
|
/** Per-rule enable/disable. Default: all rules enabled. */
|
|
273
24
|
rules?: Record<string, boolean>;
|
|
@@ -276,4 +27,11 @@ interface LintOptions {
|
|
|
276
27
|
}
|
|
277
28
|
declare function lint(sightmap: Sightmap, opts?: LintOptions): Promise<Diagnostic[]>;
|
|
278
29
|
|
|
279
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Input shape for `format`: the value-side of `SightmapV1` plus any
|
|
32
|
+
* forward-compat unknown keys we should pass through.
|
|
33
|
+
*/
|
|
34
|
+
type FormatInput = SightmapV1 & Record<string, unknown>;
|
|
35
|
+
declare function format(input: FormatInput): string;
|
|
36
|
+
|
|
37
|
+
export { Diagnostic, type FormatInput, Sightmap, ValidateResult, format, lint, loadDirectory, validate };
|
package/dist/index.js
CHANGED
|
@@ -42,6 +42,12 @@ function parse(input, opts = {}) {
|
|
|
42
42
|
return fragment;
|
|
43
43
|
}
|
|
44
44
|
function normalizeComponent(c) {
|
|
45
|
+
if (c["selectors"] !== void 0 && c.selector === void 0) {
|
|
46
|
+
const name = c.name ?? "(unnamed)";
|
|
47
|
+
throw new Error(
|
|
48
|
+
`Component "${name}": use \`selector\` (singular), not \`selectors\` (plural). \`selector\` accepts either a single string or an array of strings.`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
45
51
|
const sel = c.selector;
|
|
46
52
|
const normalized = {
|
|
47
53
|
...c,
|
|
@@ -73,20 +79,25 @@ var SELECTOR_SYNTAX = "selector-syntax";
|
|
|
73
79
|
import { existsSync, readFileSync } from "fs";
|
|
74
80
|
import { fileURLToPath } from "url";
|
|
75
81
|
import { resolve, dirname } from "path";
|
|
76
|
-
var
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
)
|
|
82
|
+
var _ajvValidate;
|
|
83
|
+
function getValidator() {
|
|
84
|
+
if (_ajvValidate) return _ajvValidate;
|
|
85
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
86
|
+
const schemaCandidates = [
|
|
87
|
+
resolve(__dirname, "./vendored/sightmap.schema.json"),
|
|
88
|
+
resolve(__dirname, "../vendored/sightmap.schema.json")
|
|
89
|
+
];
|
|
90
|
+
const schemaPath = schemaCandidates.find((p) => existsSync(p));
|
|
91
|
+
if (schemaPath === void 0) {
|
|
92
|
+
throw new Error(
|
|
93
|
+
`vendored sightmap.schema.json not found. Looked in: ${schemaCandidates.join(", ")}`
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
const schema = JSON.parse(readFileSync(schemaPath, "utf8"));
|
|
97
|
+
const ajv = new Ajv2020({ allErrors: true, strict: false });
|
|
98
|
+
_ajvValidate = ajv.compile(schema);
|
|
99
|
+
return _ajvValidate;
|
|
86
100
|
}
|
|
87
|
-
var schema = JSON.parse(readFileSync(schemaPath, "utf8"));
|
|
88
|
-
var ajv = new Ajv2020({ allErrors: true, strict: false });
|
|
89
|
-
var ajvValidate = ajv.compile(schema);
|
|
90
101
|
function validate(input, opts = {}) {
|
|
91
102
|
const diagnostics = [];
|
|
92
103
|
let doc;
|
|
@@ -127,6 +138,7 @@ function validate(input, opts = {}) {
|
|
|
127
138
|
});
|
|
128
139
|
return { ok: false, diagnostics };
|
|
129
140
|
}
|
|
141
|
+
const ajvValidate = getValidator();
|
|
130
142
|
const ok = ajvValidate(obj);
|
|
131
143
|
if (!ok) {
|
|
132
144
|
for (const e of ajvValidate.errors ?? []) {
|
|
@@ -521,29 +533,35 @@ function routeShadowing(sightmap) {
|
|
|
521
533
|
const views = sightmap.views;
|
|
522
534
|
for (let j = 1; j < views.length; j++) {
|
|
523
535
|
const later = views[j];
|
|
524
|
-
const
|
|
536
|
+
const laterKey = matchSetKey(later.route);
|
|
537
|
+
const laterScore = specificity(later.route);
|
|
525
538
|
for (let i = 0; i < j; i++) {
|
|
526
539
|
const earlier = views[i];
|
|
527
540
|
if (earlier.route === later.route) continue;
|
|
528
|
-
if (
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
});
|
|
534
|
-
|
|
535
|
-
|
|
541
|
+
if (matchSetKey(earlier.route) !== laterKey) continue;
|
|
542
|
+
if (specificity(earlier.route) < laterScore) continue;
|
|
543
|
+
out.push({
|
|
544
|
+
severity: "warning",
|
|
545
|
+
code: ROUTE_SHADOWING,
|
|
546
|
+
message: `Route "${later.route}" (view "${later.name}") is shadowed by earlier route "${earlier.route}" (view "${earlier.name}"); they match the same URLs and the earlier route is at least as specific, making this route unreachable.`
|
|
547
|
+
});
|
|
548
|
+
break;
|
|
536
549
|
}
|
|
537
550
|
}
|
|
538
551
|
return out;
|
|
539
552
|
}
|
|
540
|
-
function
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
553
|
+
function matchSetKey(route) {
|
|
554
|
+
return route.split("/").map((seg) => seg.startsWith(":") ? "*" : seg).join("/");
|
|
555
|
+
}
|
|
556
|
+
function specificity(route) {
|
|
557
|
+
let total = 0;
|
|
558
|
+
for (const seg of route.split("/")) {
|
|
559
|
+
if (seg === "" || seg === "**") continue;
|
|
560
|
+
if (seg === "*") total += 1;
|
|
561
|
+
else if (seg.startsWith(":")) total += 2;
|
|
562
|
+
else total += 3;
|
|
563
|
+
}
|
|
564
|
+
return total;
|
|
547
565
|
}
|
|
548
566
|
|
|
549
567
|
// src/lintRules/unknownSource.ts
|
|
@@ -628,6 +646,32 @@ async function lint(sightmap, opts = {}) {
|
|
|
628
646
|
}
|
|
629
647
|
return out;
|
|
630
648
|
}
|
|
649
|
+
|
|
650
|
+
// src/format/index.ts
|
|
651
|
+
import { stringify } from "yaml";
|
|
652
|
+
var CANONICAL_KEY_ORDER = [
|
|
653
|
+
"version",
|
|
654
|
+
"memory",
|
|
655
|
+
"views",
|
|
656
|
+
"components",
|
|
657
|
+
"requests"
|
|
658
|
+
];
|
|
659
|
+
function format(input) {
|
|
660
|
+
const ordered = {};
|
|
661
|
+
for (const key of CANONICAL_KEY_ORDER) {
|
|
662
|
+
if (input[key] !== void 0) ordered[key] = input[key];
|
|
663
|
+
}
|
|
664
|
+
for (const key of Object.keys(input)) {
|
|
665
|
+
if (key.startsWith("__")) continue;
|
|
666
|
+
if (!(key in ordered)) ordered[key] = input[key];
|
|
667
|
+
}
|
|
668
|
+
const yaml3 = stringify(ordered, {
|
|
669
|
+
indent: 2,
|
|
670
|
+
lineWidth: 0,
|
|
671
|
+
minContentWidth: 0
|
|
672
|
+
});
|
|
673
|
+
return yaml3.endsWith("\n") ? yaml3 : yaml3 + "\n";
|
|
674
|
+
}
|
|
631
675
|
export {
|
|
632
676
|
DUPLICATE_ROUTE,
|
|
633
677
|
DUPLICATE_VIEW_NAME,
|
|
@@ -640,6 +684,7 @@ export {
|
|
|
640
684
|
UNKNOWN_SOURCE,
|
|
641
685
|
UNKNOWN_VERSION,
|
|
642
686
|
explain,
|
|
687
|
+
format,
|
|
643
688
|
lint,
|
|
644
689
|
loadDirectory,
|
|
645
690
|
match,
|